private void checkExternalBlob(
     Mailbox mbox, boolean checkSize, BlobInfo blobInfo, ExternalStoreManager sm)
     throws ServiceException {
   MailboxBlob mblob = sm.getMailboxBlob(mbox, blobInfo.itemId, blobInfo.version, blobInfo.path);
   if (mblob == null) {
     results.missingBlobs.put(blobInfo.itemId, blobInfo);
   } else {
     try {
       unexpectedBlobPaths.remove(mblob.getLocator());
       Blob blob = sm.getLocalBlob(mbox, mblob.getLocator(), false);
       if (blob == null) {
         results.missingBlobs.put(blobInfo.itemId, blobInfo);
       } else {
         // blob exists for the locator
         blobInfo.fileModContent = blobInfo.modContent;
         if (reportUsedBlobs) {
           results.usedBlobs.put(blobInfo.itemId, blobInfo);
         }
         if (checkSize) {
           blobInfo.fileSize = blob.getFile().length();
           blobInfo.fileDataSize = getDataSize(blob.getFile(), blobInfo.dbSize);
           if (blobInfo.dbSize != blobInfo.fileDataSize) {
             results.incorrectSize.put(blobInfo.itemId, blobInfo);
           }
         }
       }
     } catch (IOException ioe) {
       blobInfo.fetchException = ioe;
       results.missingBlobs.put(blobInfo.itemId, blobInfo);
     }
   }
 }
 private void deliverMessageToRemoteMailboxes(Blob blob, byte[] data, LmtpEnvelope env) {
   Multimap<String, LmtpAddress> serverToRecipientsMap = env.getRemoteServerToRecipientsMap();
   for (String server : serverToRecipientsMap.keySet()) {
     LmtpClient lmtpClient = null;
     InputStream in = null;
     Collection<LmtpAddress> serverRecipients = serverToRecipientsMap.get(server);
     try {
       Server serverObj = Provisioning.getInstance().getServerByName(server);
       lmtpClient =
           new LmtpClient(
               server, new Integer(serverObj.getAttr(Provisioning.A_zimbraLmtpBindPort)), null);
       in = data == null ? blob.getInputStream() : new ByteArrayInputStream(data);
       boolean success =
           lmtpClient.sendMessage(
               in,
               getRecipientsEmailAddress(serverRecipients),
               env.getSender().getEmailAddress(),
               blob.getFile().getName(),
               blob.getRawSize());
       if (success) {
         setDeliveryStatuses(serverRecipients, LmtpReply.DELIVERY_OK);
       } else {
         ZimbraLog.lmtp.warn(
             "Unsuccessful remote mail delivery - LMTP response: %s", lmtpClient.getResponse());
         setDeliveryStatuses(serverRecipients, LmtpReply.TEMPORARY_FAILURE);
       }
     } catch (LmtpProtocolException e) {
       ZimbraLog.lmtp.warn(
           "Unsuccessful remote mail delivery - LMTP response: %s", e.getMessage());
       setDeliveryStatuses(serverRecipients, LmtpReply.TEMPORARY_FAILURE);
     } catch (Exception e) {
       ZimbraLog.lmtp.warn("Exception delivering remote mail", e);
       setDeliveryStatuses(serverRecipients, LmtpReply.TEMPORARY_FAILURE);
     } finally {
       ByteUtil.closeStream(in);
       if (lmtpClient != null) {
         lmtpClient.close();
       }
     }
   }
 }
 @Override
 public Results check(
     Collection<Short> volumeIds, int mboxId, boolean checkSize, boolean reportUsedBlobs)
     throws ServiceException {
   mailboxId = mboxId;
   this.checkSize = checkSize;
   this.reportUsedBlobs = reportUsedBlobs;
   results = new Results();
   Mailbox mbox = MailboxManager.getInstance().getMailboxById(mailboxId);
   DbConnection conn = null;
   assert (StoreManager.getInstance() instanceof ExternalStoreManager);
   ExternalStoreManager sm = (ExternalStoreManager) StoreManager.getInstance();
   try {
     unexpectedBlobPaths = sm.getAllBlobPaths(mbox);
   } catch (IOException ioe) {
     log.error("IOException getting remote blob list", ioe);
   }
   try {
     conn = DbPool.getConnection();
     int mailboxMaxId = DbBlobConsistency.getMaxId(conn, mbox);
     int minId = 0;
     int maxId = CHUNK_SIZE;
     while (minId <= mailboxMaxId) {
       for (BlobInfo blobInfo :
           DbBlobConsistency.getExternalMailItemBlobInfo(conn, mbox, minId, maxId)) {
         checkExternalBlob(mbox, checkSize, blobInfo, sm);
       }
       for (BlobInfo blobInfo :
           DbBlobConsistency.getExternalMailItemDumpsterBlobInfo(conn, mbox, minId, maxId)) {
         checkExternalBlob(mbox, checkSize, blobInfo, sm);
       }
       for (BlobInfo blobInfo :
           DbBlobConsistency.getExternalRevisionBlobInfo(conn, mbox, minId, maxId)) {
         checkExternalBlob(mbox, checkSize, blobInfo, sm);
       }
       for (BlobInfo blobInfo :
           DbBlobConsistency.getExternalRevisionDumpsterBlobInfo(conn, mbox, minId, maxId)) {
         checkExternalBlob(mbox, checkSize, blobInfo, sm);
       }
       minId = maxId + 1;
       maxId += CHUNK_SIZE;
     }
   } finally {
     DbPool.quietClose(conn);
   }
   for (String unexpected : unexpectedBlobPaths) {
     BlobInfo bi = new BlobInfo();
     bi.external = true;
     bi.locator = unexpected;
     bi.path = unexpected;
     results.unexpectedBlobs.put(0, bi);
     try {
       Blob blob = sm.getLocalBlob(mbox, unexpected, false);
       bi.fileSize = blob.getFile().length();
     } catch (IOException ioe) {
       // log this?
       bi.fileSize = 0L;
       bi.fetchException = ioe;
     }
   }
   return results;
 }
  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();
        }
      }
    }
  }