private void deliverMessageToLocalMailboxes(
      Blob blob, BlobInputStream bis, byte[] data, MimeMessage mm, LmtpEnvelope env)
      throws ServiceException, IOException {

    List<LmtpAddress> recipients = env.getLocalRecipients();
    String envSender = env.getSender().getEmailAddress();

    boolean shared = recipients.size() > 1;
    List<Integer> targetMailboxIds = new ArrayList<Integer>(recipients.size());

    Map<LmtpAddress, RecipientDetail> rcptMap =
        new HashMap<LmtpAddress, RecipientDetail>(recipients.size());
    try {
      // Examine attachments indexing option for all recipients and
      // prepare ParsedMessage versions needed.  Parsing is done before
      // attempting delivery to any recipient.  Therefore, parse error
      // will result in non-delivery to all recipients.

      // ParsedMessage for users with attachments indexing
      ParsedMessage pmAttachIndex = null;
      // ParsedMessage for users without attachments indexing
      ParsedMessage pmNoAttachIndex = null;

      // message id for logging
      String msgId = null;

      for (LmtpAddress recipient : recipients) {
        String rcptEmail = recipient.getEmailAddress();

        Account account;
        Mailbox mbox;
        boolean attachmentsIndexingEnabled;
        try {
          account = Provisioning.getInstance().get(AccountBy.name, rcptEmail);
          if (account == null) {
            ZimbraLog.mailbox.warn("No account found delivering mail to " + rcptEmail);
            continue;
          }
          mbox = MailboxManager.getInstance().getMailboxByAccount(account);
          if (mbox == null) {
            ZimbraLog.mailbox.warn("No mailbox found delivering mail to " + rcptEmail);
            continue;
          }
          attachmentsIndexingEnabled = mbox.attachmentsIndexingEnabled();
        } catch (ServiceException se) {
          if (se.isReceiversFault()) {
            ZimbraLog.mailbox.info("Recoverable exception getting mailbox for " + rcptEmail, se);
            rcptMap.put(
                recipient, new RecipientDetail(null, null, null, false, DeliveryAction.defer));
          } else {
            ZimbraLog.mailbox.warn("Unrecoverable exception getting mailbox for " + rcptEmail, se);
          }
          continue;
        }

        if (account != null && mbox != null) {
          ParsedMessageOptions pmo;
          if (mm != null) {
            pmo =
                new ParsedMessageOptions()
                    .setContent(mm)
                    .setDigest(blob.getDigest())
                    .setSize(blob.getRawSize());
          } else {
            pmo = new ParsedMessageOptions(blob, data);
          }

          ParsedMessage pm;
          if (attachmentsIndexingEnabled) {
            if (pmAttachIndex == null) {
              pmo.setAttachmentIndexing(true);
              ZimbraLog.lmtp.debug(
                  "Creating ParsedMessage from %s with attachment indexing enabled",
                  data == null ? "file" : "memory");
              pmAttachIndex = new ParsedMessage(pmo);
            }
            pm = pmAttachIndex;
          } else {
            if (pmNoAttachIndex == null) {
              pmo.setAttachmentIndexing(false);
              ZimbraLog.lmtp.debug(
                  "Creating ParsedMessage from %s with attachment indexing disabled",
                  data == null ? "file" : "memory");
              pmNoAttachIndex = new ParsedMessage(pmo);
            }
            pm = pmNoAttachIndex;
          }

          msgId = pm.getMessageID();

          if (account.isPrefMailLocalDeliveryDisabled()) {
            ZimbraLog.lmtp.debug("Local delivery disabled for account %s", rcptEmail);
            rcptMap.put(
                recipient, new RecipientDetail(account, mbox, pm, false, DeliveryAction.discard));
            continue;
          }

          // For non-shared delivery (i.e. only one recipient),
          // always deliver regardless of backup mode.
          DeliveryAction da = DeliveryAction.deliver;
          boolean endSharedDelivery = false;
          if (shared) {
            if (mbox.beginSharedDelivery()) {
              endSharedDelivery = true;
            } else {
              // Skip delivery to mailboxes in backup mode.
              da = DeliveryAction.defer;
            }
          }
          rcptMap.put(recipient, new RecipientDetail(account, mbox, pm, endSharedDelivery, da));
          if (da == DeliveryAction.deliver) {
            targetMailboxIds.add(mbox.getId());
          }
        }
      }

      ZimbraLog.removeAccountFromContext();
      if (ZimbraLog.lmtp.isInfoEnabled()) {
        ZimbraLog.lmtp.info(
            "Delivering message: size=%s, nrcpts=%d, sender=%s, msgid=%s",
            env.getSize() == 0 ? "unspecified" : Integer.toString(env.getSize()) + " bytes",
            recipients.size(),
            env.getSender(),
            msgId == null ? "" : msgId);
      }

      DeliveryContext sharedDeliveryCtxt = new DeliveryContext(shared, targetMailboxIds);
      sharedDeliveryCtxt.setIncomingBlob(blob);

      // We now know which addresses are valid and which ParsedMessage
      // version each recipient needs.  Deliver!
      for (LmtpAddress recipient : recipients) {
        String rcptEmail = recipient.getEmailAddress();
        LmtpReply reply = LmtpReply.TEMPORARY_FAILURE;
        RecipientDetail rd = rcptMap.get(recipient);
        if (rd.account != null) ZimbraLog.addAccountNameToContext(rd.account.getName());
        if (rd.mbox != null) ZimbraLog.addMboxToContext(rd.mbox.getId());

        boolean success = false;
        try {
          if (rd != null) {
            switch (rd.action) {
              case discard:
                ZimbraLog.lmtp.info(
                    "accepted and discarded message from=%s,to=%s: local delivery is disabled",
                    envSender, rcptEmail);
                if (rd.account.getPrefMailForwardingAddress() != null) {
                  // mail forwarding is set up
                  for (LmtpCallback callback : callbacks) {
                    ZimbraLog.lmtp.debug("Executing callback %s", callback.getClass().getName());
                    callback.forwardWithoutDelivery(
                        rd.account, rd.mbox, envSender, rcptEmail, rd.pm);
                  }
                }
                reply = LmtpReply.DELIVERY_OK;
                break;
              case deliver:
                Account account = rd.account;
                Mailbox mbox = rd.mbox;
                ParsedMessage pm = rd.pm;
                List<ItemId> addedMessageIds = null;
                ReentrantLock lock = mailboxDeliveryLocks.get(mbox.getId());
                boolean acquiredLock;
                try {
                  // Wait for the lock, up to the timeout
                  acquiredLock =
                      lock.tryLock(LC.zimbra_mailbox_lock_timeout.intValue(), TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                  acquiredLock = false;
                }
                if (!acquiredLock) {
                  ZimbraLog.lmtp.info(
                      "try again for message from=%s,to=%s: another mail delivery in progress.",
                      envSender, rcptEmail);
                  reply = LmtpReply.TEMPORARY_FAILURE;
                  break;
                }
                try {
                  if (dedupe(pm, mbox)) {
                    // message was already delivered to this mailbox
                    ZimbraLog.lmtp.info(
                        "Not delivering message with duplicate Message-ID %s", pm.getMessageID());
                  } else if (recipient.getSkipFilters()) {
                    msgId = pm.getMessageID();
                    int folderId = Mailbox.ID_FOLDER_INBOX;
                    if (recipient.getFolder() != null) {
                      try {
                        Folder folder = mbox.getFolderByPath(null, recipient.getFolder());
                        folderId = folder.getId();
                      } catch (ServiceException se) {
                        if (se.getCode().equals(MailServiceException.NO_SUCH_FOLDER)) {
                          Folder folder =
                              mbox.createFolder(
                                  null,
                                  recipient.getFolder(),
                                  new Folder.FolderOptions().setDefaultView(MailItem.Type.MESSAGE));
                          folderId = folder.getId();
                        } else {
                          throw se;
                        }
                      }
                    }
                    int flags = Flag.BITMASK_UNREAD;
                    if (recipient.getFlags() != null) {
                      flags = Flag.toBitmask(recipient.getFlags());
                    }
                    DeliveryOptions dopt = new DeliveryOptions().setFolderId(folderId);
                    dopt.setFlags(flags).setTags(recipient.getTags()).setRecipientEmail(rcptEmail);
                    Message msg = mbox.addMessage(null, pm, dopt, sharedDeliveryCtxt);
                    addedMessageIds = Lists.newArrayList(new ItemId(msg));
                  } else if (!DebugConfig.disableIncomingFilter) {
                    // Get msgid first, to avoid having to reopen and reparse the blob
                    // file if Mailbox.addMessageInternal() closes it.
                    pm.getMessageID();
                    addedMessageIds =
                        RuleManager.applyRulesToIncomingMessage(
                            null,
                            mbox,
                            pm,
                            (int) blob.getRawSize(),
                            rcptEmail,
                            sharedDeliveryCtxt,
                            Mailbox.ID_FOLDER_INBOX,
                            false);
                  } else {
                    pm.getMessageID();
                    DeliveryOptions dopt =
                        new DeliveryOptions().setFolderId(Mailbox.ID_FOLDER_INBOX);
                    dopt.setFlags(Flag.BITMASK_UNREAD).setRecipientEmail(rcptEmail);
                    Message msg = mbox.addMessage(null, pm, dopt, sharedDeliveryCtxt);
                    addedMessageIds = Lists.newArrayList(new ItemId(msg));
                  }
                  success = true;
                  if (addedMessageIds != null && addedMessageIds.size() > 0) {
                    addToDedupeCache(pm, mbox);
                  }
                } finally {
                  lock.unlock();
                }

                if (addedMessageIds != null && addedMessageIds.size() > 0) {
                  // Execute callbacks
                  for (LmtpCallback callback : callbacks) {
                    for (ItemId id : addedMessageIds) {
                      if (id.belongsTo(mbox)) {
                        // Message was added to the local mailbox, as opposed to a mountpoint.
                        ZimbraLog.lmtp.debug(
                            "Executing callback %s", callback.getClass().getName());
                        try {
                          Message msg = mbox.getMessageById(null, id.getId());
                          callback.afterDelivery(account, mbox, envSender, rcptEmail, msg);
                        } catch (Throwable t) {
                          if (t instanceof OutOfMemoryError) {
                            Zimbra.halt("LMTP callback failed", t);
                          } else {
                            ZimbraLog.lmtp.warn("LMTP callback threw an exception", t);
                          }
                        }
                      }
                    }
                  }
                }
                reply = LmtpReply.DELIVERY_OK;
                break;
              case defer:
                // Delivery to mailbox skipped.  Let MTA retry again later.
                // This case happens for shared delivery to a mailbox in
                // backup mode.
                ZimbraLog.lmtp.info(
                    "try again for message from=%s,to=%s: mailbox skipped", envSender, rcptEmail);
                reply = LmtpReply.TEMPORARY_FAILURE;
                break;
            }
          } else {
            // Account or mailbox not found.
            ZimbraLog.lmtp.info(
                "rejecting message from=%s,to=%s: account or mailbox not found",
                envSender, rcptEmail);
            reply = LmtpReply.PERMANENT_FAILURE;
          }
        } catch (ServiceException e) {
          if (e.getCode().equals(MailServiceException.QUOTA_EXCEEDED)) {
            ZimbraLog.lmtp.info("rejecting message from=%s,to=%s: overquota", envSender, rcptEmail);
            if (config.isPermanentFailureWhenOverQuota()) {
              reply = LmtpReply.PERMANENT_FAILURE_OVER_QUOTA;
            } else {
              reply = LmtpReply.TEMPORARY_FAILURE_OVER_QUOTA;
            }
          } else if (e.isReceiversFault()) {
            ZimbraLog.lmtp.info("try again for message from=%s,to=%s", envSender, rcptEmail, e);
            reply = LmtpReply.TEMPORARY_FAILURE;
          } else {
            ZimbraLog.lmtp.info("rejecting message from=%s,to=%s", envSender, rcptEmail, e);
            reply = LmtpReply.PERMANENT_FAILURE;
          }
        } catch (Exception e) {
          reply = LmtpReply.TEMPORARY_FAILURE;
          ZimbraLog.lmtp.warn("try again for message from=%s,to=%s", envSender, rcptEmail, e);
        } finally {
          if (rd.action == DeliveryAction.deliver && !success) {
            // Message was not delivered.  Remove it from the dedupe
            // cache so we don't dedupe it on LMTP retry.
            removeFromDedupeCache(msgId, rd.mbox);
          }
          recipient.setDeliveryStatus(reply);
          if (shared && rd != null && rd.esd) {
            rd.mbox.endSharedDelivery();
            rd.esd = false;
          }
        }
      }

      // If this message is being streamed from disk, cache it
      ParsedMessage mimeSource = pmAttachIndex != null ? pmAttachIndex : pmNoAttachIndex;
      MailboxBlob mblob = sharedDeliveryCtxt.getMailboxBlob();
      if (mblob != null && mimeSource != null) {
        if (bis == null) {
          bis = mimeSource.getBlobInputStream();
        }
        if (bis != null) {
          try {
            // Update the MimeMessage with the blob that's stored inside the mailbox,
            // since the incoming blob will be deleted.
            Blob storedBlob = mblob.getLocalBlob();
            bis.fileMoved(storedBlob.getFile());
            MessageCache.cacheMessage(
                mblob.getDigest(), mimeSource.getOriginalMessage(), mimeSource.getMimeMessage());
          } catch (IOException e) {
            ZimbraLog.lmtp.warn("Unable to cache message for " + mblob, e);
          }
        }
      }
    } finally {
      // If there were any stray exceptions after the call to
      // beginSharedDelivery that caused endSharedDelivery to be not
      // called, we check and fix those here.
      if (shared) {
        for (RecipientDetail rd : rcptMap.values()) {
          if (rd.esd && rd.mbox != null) rd.mbox.endSharedDelivery();
        }
      }
    }
  }
Esempio n. 2
0
 private void fetchAndAddMessage(int msgno, int size, String uid, boolean allowFilterToMountpoint)
     throws ServiceException, IOException {
   ContentInputStream cis = null;
   MessageContent mc = null;
   checkIsEnabled();
   try {
     cis = connection.getMessage(msgno);
     mc = MessageContent.read(cis, size);
     ParsedMessage pm = mc.getParsedMessage(null, indexAttachments);
     if (pm == null) {
       LOG.warn("Empty message body for UID %d. Must be ignored.", uid);
       return;
     }
     Message msg = null;
     // bug 47796: Set received date to sent date if available otherwise use current time
     try {
       Date sentDate = pm.getMimeMessage().getSentDate();
       if (sentDate == null) {
         LOG.warn(
             "null sent date; probably due to parse error. Date header value: [%s]",
             pm.getMimeMessage().getHeader("Date", null));
       }
       pm.setReceivedDate(sentDate != null ? sentDate.getTime() : System.currentTimeMillis());
     } catch (MessagingException e) {
       LOG.warn(
           "unable to get sent date from parsed message due to exception, must use current time",
           e);
       pm.setReceivedDate(System.currentTimeMillis());
     }
     DeliveryContext dc = mc.getDeliveryContext();
     if (isOffline()) {
       msg = addMessage(null, pm, size, dataSource.getFolderId(), Flag.BITMASK_UNREAD, dc);
     } else {
       Integer localId =
           getFirstLocalId(
               RuleManager.applyRulesToIncomingMessage(
                   null,
                   mbox,
                   pm,
                   size,
                   dataSource.getEmailAddress(),
                   dc,
                   dataSource.getFolderId(),
                   true,
                   allowFilterToMountpoint));
       if (localId != null) {
         msg = mbox.getMessageById(null, localId);
       }
     }
     if (msg != null && uid != null) {
       PopMessage msgTracker = new PopMessage(dataSource, msg.getId(), uid);
       msgTracker.add();
     }
   } catch (CommandFailedException e) {
     LOG.warn("Error fetching message number %d: %s", msgno, e.getMessage());
   } finally {
     if (cis != null) {
       try {
         cis.close();
       } catch (ParseException pe) {
         LOG.error(
             "ParseException while closing ContentInputStream. Assuming cis is effectively closed",
             pe);
       }
     }
     if (mc != null) {
       mc.cleanup();
     }
   }
 }