@Override public ItemId fileInto(String folderPath, Collection<ActionFlag> flagActions, String[] tags) throws ServiceException { Message source = getMessage(); // See if the message is already in the target folder. Folder targetFolder = null; try { targetFolder = mailbox.getFolderByPath(octxt, folderPath); } catch (NoSuchItemException ignored) { } if (targetFolder != null && source.getFolderId() == targetFolder.getId()) { ZimbraLog.filter.debug( "Ignoring fileinto action for message %d. It is already in %s.", messageId, folderPath); updateTagsAndFlagsIfNecessary(source, flagActions, tags); return null; } ZimbraLog.filter.info("Copying existing message %d to folder %s.", messageId, folderPath); if (isLocalExistingFolder(folderPath)) { // Copy item into to a local folder. Folder target = mailbox.getFolderByPath(octxt, folderPath); Message newMsg = (Message) mailbox.copy(octxt, messageId, MailItem.Type.MESSAGE, target.getId()); filtered = true; filed = true; // Apply flags and tags mailbox.setTags( octxt, newMsg.getId(), MailItem.Type.MESSAGE, FilterUtil.getFlagBitmask(flagActions, source.getFlagBitmask()), FilterUtil.getTagsUnion(source.getTags(), tags)); return new ItemId(mailbox, messageId); } ItemId id = FilterUtil.addMessage( new DeliveryContext(), mailbox, getParsedMessage(), mailbox.getAccount().getName(), folderPath, false, FilterUtil.getFlagBitmask(flagActions, source.getFlagBitmask()), tags, Mailbox.ID_AUTO_INCREMENT, octxt); if (id != null) { filtered = true; filed = true; } return id; }
// Caller is responsible for filtering out Appointments/Tasks marked private if the requester // is not the mailbox owner. public Iterator<? extends MailItem> getMailItems( UserServletContext context, long startTime, long endTime, long chunkSize) throws ServiceException { if (context.requestedItems != null) { return context.getRequestedItems(); } assert (context.target != null); String query = context.getQueryString(); if (query != null) { if (context.target instanceof Folder) { Folder f = (Folder) context.target; if (f.getId() != Mailbox.ID_FOLDER_USER_ROOT) { query = "in:" + f.getPath() + " " + query; } } Set<MailItem.Type> types; if (context.getTypesString() == null) { types = getDefaultSearchTypes(); } else { try { types = MailItem.Type.setOf(context.getTypesString()); } catch (IllegalArgumentException e) { throw MailServiceException.INVALID_TYPE(e.getMessage()); } } ZimbraQueryResults results = context.targetMailbox.index.search( context.opContext, query, types, SortBy.DATE_DESC, context.getOffset() + context.getLimit()); return new QueryResultIterator(results); } else if (context.target instanceof Folder) { Collection<? extends MailItem> items = getMailItemsFromFolder(context, (Folder) context.target, startTime, endTime, chunkSize); return items != null ? items.iterator() : null; } else { ArrayList<MailItem> result = new ArrayList<MailItem>(); result.add(context.target); return result.iterator(); } }
protected Collection<? extends MailItem> getMailItemsFromFolder( UserServletContext context, Folder folder, long startTime, long endTime, long chunkSize) throws ServiceException { switch (folder.getDefaultView()) { case APPOINTMENT: case TASK: return context.targetMailbox.getCalendarItemsForRange( context.opContext, startTime, endTime, folder.getId(), null); case CONTACT: return context.targetMailbox.getContactList( context.opContext, folder.getId(), SortBy.NAME_ASC); case DOCUMENT: case WIKI: return context.targetMailbox.getDocumentList( context.opContext, folder.getId(), SortBy.NAME_ASC); default: return context.targetMailbox.getItemList( context.opContext, MailItem.Type.MESSAGE, folder.getId()); } }
/** Verifies serializing, deserializing, and replaying for folder. */ @Test public void redoFolder() throws Exception { Mailbox mbox = MailboxManager.getInstance().getMailboxByAccountId(MockProvisioning.DEFAULT_ACCOUNT_ID); // Create folder. Folder folder = mbox.createFolder( null, "/redo", new Folder.FolderOptions().setDefaultView(MailItem.Type.MESSAGE)); assertEquals(0, folder.getRetentionPolicy().getKeepPolicy().size()); assertEquals(0, folder.getRetentionPolicy().getPurgePolicy().size()); // Create RedoableOp. RetentionPolicy rp = new RetentionPolicy( Arrays.asList(Policy.newSystemPolicy("123")), Arrays.asList(Policy.newUserPolicy("45m"))); SetRetentionPolicy redoPlayer = new SetRetentionPolicy(mbox.getId(), MailItem.Type.FOLDER, folder.getId(), rp); // Serialize, deserialize, and redo. byte[] data = redoPlayer.testSerialize(); redoPlayer = new SetRetentionPolicy(); redoPlayer.setMailboxId(mbox.getId()); redoPlayer.testDeserialize(data); redoPlayer.redo(); folder = mbox.getFolderById(null, folder.getId()); assertEquals(1, folder.getRetentionPolicy().getKeepPolicy().size()); assertEquals(1, folder.getRetentionPolicy().getPurgePolicy().size()); assertEquals("45m", folder.getRetentionPolicy().getPurgePolicy().get(0).getLifetime()); assertEquals("123", folder.getRetentionPolicy().getKeepPolicy().get(0).getId()); }
private void compile() throws ServiceException { assert clauses != null; if (clauses.isEmpty()) { return; } // Convert list of Queries into list of QueryOperations, then optimize them. // this generates all of the query operations operation = parseTree.compile(mailbox); ZimbraLog.search.debug("OP=%s", operation); // expand the is:local and is:remote parts into in:(LIST)'s operation = operation.expandLocalRemotePart(mailbox); ZimbraLog.search.debug("AFTEREXP=%s", operation); // optimize the query down operation = operation.optimize(mailbox); ZimbraLog.search.debug("OPTIMIZED=%s", operation); if (operation == null || operation instanceof NoTermQueryOperation) { operation = new NoResultsQueryOperation(); return; } // Use the OperationContext to update the set of visible referenced folders, local AND remote. Set<QueryTarget> targets = operation.getQueryTargets(); assert (operation instanceof UnionQueryOperation || QueryTarget.getExplicitTargetCount(targets) <= 1); // easiest to treat the query two unions: one the LOCAL and one REMOTE parts UnionQueryOperation remoteOps = new UnionQueryOperation(); UnionQueryOperation localOps = new UnionQueryOperation(); if (operation instanceof UnionQueryOperation) { UnionQueryOperation union = (UnionQueryOperation) operation; // separate out the LOCAL vs REMOTE parts... for (QueryOperation op : union.operations) { Set<QueryTarget> set = op.getQueryTargets(); // this assertion OK because we have already distributed multi-target query ops // during the optimize() step assert (QueryTarget.getExplicitTargetCount(set) <= 1); // the assertion above is critical: the code below all assumes // that we only have ONE target (ie we've already distributed if necessary) if (QueryTarget.hasExternalTarget(set)) { remoteOps.add(op); } else { localOps.add(op); } } } else { // single target: might be local, might be remote Set<QueryTarget> set = operation.getQueryTargets(); // this assertion OK because we have already distributed multi-target query ops // during the optimize() step assert (QueryTarget.getExplicitTargetCount(set) <= 1); if (QueryTarget.hasExternalTarget(set)) { remoteOps.add(operation); } else { localOps.add(operation); } } // Handle the REMOTE side: if (!remoteOps.operations.isEmpty()) { // Since optimize() has already been run, we know that each of our ops // only has one target (or none). Find those operations which have // an external target and wrap them in RemoteQueryOperations // iterate backwards so we can remove/add w/o screwing iteration for (int i = remoteOps.operations.size() - 1; i >= 0; i--) { QueryOperation op = remoteOps.operations.get(i); Set<QueryTarget> set = op.getQueryTargets(); // this assertion OK because we have already distributed multi-target query ops // during the optimize() step assert (QueryTarget.getExplicitTargetCount(set) <= 1); // the assertion above is critical: the code below all assumes // that we only have ONE target (ie we've already distributed if necessary) if (QueryTarget.hasExternalTarget(set)) { remoteOps.operations.remove(i); boolean foundOne = false; // find a remoteOp to add this one to for (QueryOperation remoteOp : remoteOps.operations) { if (remoteOp instanceof RemoteQueryOperation) { if (((RemoteQueryOperation) remoteOp).tryAddOredOperation(op)) { foundOne = true; break; } } } if (!foundOne) { RemoteQueryOperation remoteOp = new RemoteQueryOperation(); remoteOp.tryAddOredOperation(op); remoteOps.operations.add(i, remoteOp); } } } // ...we need to call setup on every RemoteQueryOperation we end up with... for (QueryOperation remoteOp : remoteOps.operations) { assert (remoteOp instanceof RemoteQueryOperation); try { ((RemoteQueryOperation) remoteOp).setup(protocol, octxt.getAuthToken(), params); } catch (Exception e) { ZimbraLog.search.info("Ignoring %s during RemoteQuery generation for %s", e, remoteOps); } } } // For the LOCAL parts of the query, do permission checks, do trash/spam exclusion if (!localOps.operations.isEmpty()) { ZimbraLog.search.debug("LOCAL_IN=%s", localOps); Account authAcct = octxt != null ? octxt.getAuthenticatedUser() : mailbox.getAccount(); // Now, for all the LOCAL PARTS of the query, add the trash/spam exclusion part boolean includeTrash = false; boolean includeSpam = false; if (params.inDumpster()) { // Always include trash and spam for dumpster searches. Excluding spam is a client side // choice. includeTrash = true; includeSpam = true; if (!mailbox.hasFullAdminAccess( octxt)) { // If the requester is not an admin, limit to recent date range. long now = octxt != null ? octxt.getTimestamp() : System.currentTimeMillis(); long mdate = now - authAcct.getDumpsterUserVisibleAge(); IntersectionQueryOperation and = new IntersectionQueryOperation(); DBQueryOperation db = new DBQueryOperation(); db.addMDateRange(mdate, false, -1L, false, true); and.addQueryOp((QueryOperation) localOps.clone()); and.addQueryOp(db); localOps.operations.clear(); localOps.operations.add(and); } } else { includeTrash = authAcct.isPrefIncludeTrashInSearch(); ; includeSpam = authAcct.isPrefIncludeSpamInSearch(); } if (!includeTrash || !includeSpam) { // First check that we aren't specifically looking for items in one of these. // For instance, in ZWC, if "Include Shared Items" is selected, the Trash list may currently // use a // search similar to : 'in:"trash" (inid:565 OR is:local)' // where 565 is the folder ID for a shared folder. We don't want to end up doing a search // for items // that are both in "trash" and NOT in "trash"... for (Query q : clauses) { if (q.toString().equalsIgnoreCase("Q(IN:Trash)")) { includeTrash = true; } if (q.toString().equalsIgnoreCase("Q(IN:Junk)")) { includeSpam = true; } } } if (!includeTrash || !includeSpam) { List<QueryOperation> toAdd = new ArrayList<QueryOperation>(); for (Iterator<QueryOperation> iter = localOps.operations.iterator(); iter.hasNext(); ) { QueryOperation cur = iter.next(); if (!cur.hasSpamTrashSetting()) { QueryOperation newOp = cur.ensureSpamTrashSetting(mailbox, includeTrash, includeSpam); if (newOp != cur) { iter.remove(); toAdd.add(newOp); } } } localOps.operations.addAll(toAdd); ZimbraLog.search.debug("LOCAL_AFTER_TRASH/SPAM/DUMPSTER=%s", localOps); } // Check to see if we need to filter out private appointment data boolean allowPrivateAccess = true; if (octxt != null) { allowPrivateAccess = AccessManager.getInstance() .allowPrivateAccess( octxt.getAuthenticatedUser(), mailbox.getAccount(), octxt.isUsingAdminPrivileges()); } // // bug 28892 - ACL.RIGHT_PRIVATE support: // // Basically, if ACL.RIGHT_PRIVATE is set somewhere, and if we're excluding private items from // search, then we need to run the query twice -- once over the whole mailbox with // private items excluded and then UNION it with a second run, this time only in the // RIGHT_PRIVATE enabled folders, with private items enabled. // UnionQueryOperation clonedLocal = null; Set<Folder> hasFolderRightPrivateSet = new HashSet<Folder>(); // ...don't do any of this if they aren't asking for a calendar type... Set<MailItem.Type> types = params.getTypes(); boolean hasCalendarType = types.contains(MailItem.Type.APPOINTMENT) || types.contains(MailItem.Type.TASK); if (hasCalendarType && !allowPrivateAccess && getTextOperationCount(localOps) > 0) { // the searcher is NOT allowed to see private items globally....lets check // to see if there are any individual folders that they DO have rights to... // if there are any, then we'll need to run special searches in those // folders Set<Folder> allVisibleFolders = mailbox.getVisibleFolders(octxt); if (allVisibleFolders == null) { allVisibleFolders = new HashSet<Folder>(); allVisibleFolders.addAll(mailbox.getFolderList(octxt, SortBy.NONE)); } for (Folder f : allVisibleFolders) { if (f.getType() == MailItem.Type.FOLDER && CalendarItem.allowPrivateAccess(f, authAcct, false)) { hasFolderRightPrivateSet.add(f); } } if (!hasFolderRightPrivateSet.isEmpty()) { clonedLocal = (UnionQueryOperation) localOps.clone(); } } Set<Folder> visibleFolders = mailbox.getVisibleFolders(octxt); localOps = handleLocalPermissionChecks(localOps, visibleFolders, allowPrivateAccess); ZimbraLog.search.debug("LOCAL_AFTER_PERM_CHECKS=%s", localOps); if (!hasFolderRightPrivateSet.isEmpty()) { ZimbraLog.search.debug("CLONED_LOCAL_BEFORE_PERM=%s", clonedLocal); // now we're going to setup the clonedLocal tree // to run with private access ALLOWED, over the set of folders // that have RIGHT_PRIVATE (note that we build this list from the visible // folder list, so we are clonedLocal = handleLocalPermissionChecks(clonedLocal, hasFolderRightPrivateSet, true); ZimbraLog.search.debug("CLONED_LOCAL_AFTER_PERM=%s", clonedLocal); // clonedLocal should only have the single INTERSECT in it assert (clonedLocal.operations.size() == 1); QueryOperation optimizedClonedLocal = clonedLocal.optimize(mailbox); ZimbraLog.search.debug("CLONED_LOCAL_AFTER_OPTIMIZE=%s", optimizedClonedLocal); UnionQueryOperation withPrivateExcluded = localOps; localOps = new UnionQueryOperation(); localOps.add(withPrivateExcluded); localOps.add(optimizedClonedLocal); ZimbraLog.search.debug("LOCAL_WITH_CLONED=%s", localOps); // // we should end up with: // // localOps = // UNION(withPrivateExcluded, // UNION(INTERSECT(clonedLocal, // UNION(hasFolderRightPrivateList) // ) // ) // ) // } } UnionQueryOperation union = new UnionQueryOperation(); union.add(localOps); union.add(remoteOps); ZimbraLog.search.debug("BEFORE_FINAL_OPT=%s", union); operation = union.optimize(mailbox); ZimbraLog.search.debug("COMPILED=%s", operation); }
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(); } } } }