/** 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());
  }
  @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;
  }
Beispiel #3
0
 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());
   }
 }
Beispiel #4
0
  // 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();
    }
  }
  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();
        }
      }
    }
  }