コード例 #1
0
  private MessageManager.MetaData selectMailbox(MailboxPath mailboxPath, ImapSession session)
      throws MailboxException {
    final MailboxManager mailboxManager = getMailboxManager();
    final MailboxSession mailboxSession = ImapSessionUtils.getMailboxSession(session);
    final MessageManager mailbox = mailboxManager.getMailbox(mailboxPath, mailboxSession);

    final SelectedMailbox sessionMailbox;
    final SelectedMailbox currentMailbox = session.getSelected();
    if (currentMailbox == null || !currentMailbox.getPath().equals(mailboxPath)) {

      // QRESYNC EXTENSION
      //
      // Response with the CLOSE return-code when the currently selected mailbox is closed
      // implicitly using the SELECT/EXAMINE command on another mailbox
      //
      // See rfc5162 3.7. CLOSED Response Code
      if (currentMailbox != null) {
        getStatusResponseFactory()
            .untaggedOk(HumanReadableText.QRESYNC_CLOSED, ResponseCode.closed());
      }
      session.selected(new SelectedMailboxImpl(getMailboxManager(), session, mailboxPath));

      sessionMailbox = session.getSelected();

    } else {
      // TODO: Check if we need to handle CONDSTORE there too
      sessionMailbox = currentMailbox;
    }
    final MessageManager.MetaData metaData =
        mailbox.getMetaData(
            !openReadOnly, mailboxSession, MessageManager.MetaData.FetchGroup.FIRST_UNSEEN);
    addRecent(metaData, sessionMailbox);
    return metaData;
  }
コード例 #2
0
  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
    getLogger(ctx.getChannel()).debug("Error while processing imap request", e.getCause());

    if (e.getCause() instanceof TooLongFrameException) {

      // Max line length exceeded
      // See RFC 2683 section 3.2.1
      //
      // "For its part, a server should allow for a command line of at
      // least
      // 8000 octets. This provides plenty of leeway for accepting
      // reasonable
      // length commands from clients. The server should send a BAD
      // response
      // to a command that does not end within the server's maximum
      // accepted
      // command length."
      //
      // See also JAMES-1190
      ImapResponseComposer composer = (ImapResponseComposer) ctx.getAttachment();
      composer.untaggedResponse(
          ImapConstants.BAD + " failed. Maximum command line length exceeded");
    } else {
      // logout on error not sure if that is the best way to handle it
      final ImapSession imapSession = (ImapSession) attributes.get(ctx.getChannel());
      if (imapSession != null) imapSession.logout();

      // Make sure we close the channel after all the buffers were flushed out
      Channel channel = ctx.getChannel();
      if (channel.isConnected()) {
        channel.write(ChannelBuffers.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
      }
    }
  }
コード例 #3
0
  @Override
  public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
    InetSocketAddress address = (InetSocketAddress) ctx.getChannel().getRemoteAddress();
    getLogger(ctx.getChannel())
        .info("Connection closed for " + address.getAddress().getHostAddress());

    // remove the stored attribute for the channel to free up resources
    // See JAMES-1195
    ImapSession imapSession = (ImapSession) attributes.remove(ctx.getChannel());
    if (imapSession != null) imapSession.logout();

    super.channelClosed(ctx, e);
  }
コード例 #4
0
  /**
   * @see
   *     org.apache.james.imap.processor.PermitEnableCapabilityProcessor#enable(org.apache.james.imap.api.ImapMessage,
   *     org.apache.james.imap.api.process.ImapProcessor.Responder,
   *     org.apache.james.imap.api.process.ImapSession, java.lang.String)
   */
  public void enable(
      ImapMessage message, Responder responder, ImapSession session, String capability)
      throws EnableException {

    if (EnableProcessor.getEnabledCapabilities(session).contains(capability) == false) {
      SelectedMailbox sm = session.getSelected();
      // Send a HIGHESTMODSEQ response if the there was a select mailbox before and the client just
      // enabled
      // QRESYNC or CONDSTORE
      //
      // See http://www.dovecot.org/list/dovecot/2008-March/029561.html
      if (capability.equalsIgnoreCase(ImapConstants.SUPPORTS_CONDSTORE)
          || capability.equalsIgnoreCase(ImapConstants.SUPPORTS_QRESYNC)) {
        try {
          MetaData metaData = null;
          boolean send = false;
          if (sm != null) {
            MessageManager mailbox = getSelectedMailbox(session);
            metaData =
                mailbox.getMetaData(
                    false, ImapSessionUtils.getMailboxSession(session), FetchGroup.NO_COUNT);
            send = true;
          }
          condstoreEnablingCommand(session, responder, metaData, send);
        } catch (MailboxException e) {
          throw new EnableException("Unable to enable " + capability, e);
        }
      }
    }
  }
コード例 #5
0
  /**
   * @see
   *     org.apache.james.imap.processor.AbstractMailboxProcessor#doProcess(org.apache.james.imap.api.message.request.ImapRequest,
   *     org.apache.james.imap.api.process.ImapSession, java.lang.String,
   *     org.apache.james.imap.api.ImapCommand,
   *     org.apache.james.imap.api.process.ImapProcessor.Responder)
   */
  protected void doProcess(
      M request, ImapSession session, String tag, ImapCommand command, Responder responder) {
    final String mailboxName = request.getMailboxName();
    try {
      final MailboxPath fullMailboxPath =
          PathConverter.forSession(session).buildFullPath(mailboxName);

      respond(tag, command, session, fullMailboxPath, request, responder);

    } catch (MailboxNotFoundException e) {
      session.getLog().debug("Select failed as mailbox does not exist " + mailboxName, e);
      responder.respond(
          statusResponseFactory.taggedNo(tag, command, HumanReadableText.FAILURE_NO_SUCH_MAILBOX));
    } catch (MailboxException e) {
      session.getLog().info("Select failed for mailbox " + mailboxName, e);
      no(command, tag, responder, HumanReadableText.SELECT);
    }
  }
コード例 #6
0
  @Override
  public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
    ImapSession session = (ImapSession) attributes.get(ctx.getChannel());
    ImapResponseComposer response = (ImapResponseComposer) ctx.getAttachment();
    ImapMessage message = (ImapMessage) e.getMessage();
    ChannelPipeline cp = ctx.getPipeline();

    try {
      if (cp.get(NettyConstants.EXECUTION_HANDLER) != null) {
        cp.addBefore(
            NettyConstants.EXECUTION_HANDLER, NettyConstants.HEARTBEAT_HANDLER, heartbeatHandler);
      } else {
        cp.addBefore(
            NettyConstants.CORE_HANDLER, NettyConstants.HEARTBEAT_HANDLER, heartbeatHandler);
      }
      final ResponseEncoder responseEncoder = new ResponseEncoder(encoder, response, session);
      processor.process(message, responseEncoder, session);

      if (session.getState() == ImapSessionState.LOGOUT) {
        // Make sure we close the channel after all the buffers were flushed out
        Channel channel = ctx.getChannel();
        if (channel.isConnected()) {
          channel.write(ChannelBuffers.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        }
      }
      final IOException failure = responseEncoder.getFailure();

      if (failure != null) {
        final Logger logger = session.getLog();
        logger.info(failure.getMessage());
        if (logger.isDebugEnabled()) {
          logger.debug("Failed to write " + message, failure);
        }
        throw failure;
      }
    } finally {
      ctx.getPipeline().remove(NettyConstants.HEARTBEAT_HANDLER);
    }

    super.messageReceived(ctx, e);
  }
コード例 #7
0
  private void respond(
      String tag,
      ImapCommand command,
      ImapSession session,
      MailboxPath fullMailboxPath,
      AbstractMailboxSelectionRequest request,
      Responder responder)
      throws MailboxException, MessageRangeException {

    Long lastKnownUidValidity = request.getLastKnownUidValidity();
    Long modSeq = request.getKnownModSeq();
    IdRange[] knownSequences = request.getKnownSequenceSet();
    UidRange[] knownUids = request.getKnownUidSet();

    // Check if a QRESYNC parameter was used and if so if QRESYNC was enabled before.
    // If it was not enabled before its needed to return a BAD response
    //
    // From RFC5162 3.1. QRESYNC Parameter to SELECT/EXAMINE
    //
    //    A server MUST respond with a tagged BAD response if the Quick
    //    Resynchronization parameter to SELECT/EXAMINE command is specified
    //    and the client hasn't issued "ENABLE QRESYNC" in the current
    //    connection.
    if (lastKnownUidValidity != null
        && !EnableProcessor.getEnabledCapabilities(session)
            .contains(ImapConstants.SUPPORTS_QRESYNC)) {
      taggedBad(command, tag, responder, HumanReadableText.QRESYNC_NOT_ENABLED);
      return;
    }

    final MessageManager.MetaData metaData = selectMailbox(fullMailboxPath, session);
    final SelectedMailbox selected = session.getSelected();
    MessageUid firstUnseen = metaData.getFirstUnseen();

    flags(responder, selected);
    exists(responder, metaData);
    recent(responder, selected);
    uidValidity(responder, metaData);

    // try to write the UNSEEN message to the client and retry if we fail because of concurrent
    // sessions.
    //
    // See IMAP-345
    int retryCount = 0;
    while (unseen(responder, firstUnseen, selected, ImapSessionUtils.getMailboxSession(session))
        == false) {
      // if we not was able to get find the unseen within 5 retries we should just not send it
      if (retryCount == 5) {
        if (session.getLog().isInfoEnabled()) {
          session
              .getLog()
              .info(
                  "Unable to uid for unseen message "
                      + firstUnseen
                      + " in mailbox "
                      + selected.getPath());
        }
        break;
      }
      firstUnseen = selectMailbox(fullMailboxPath, session).getFirstUnseen();
      retryCount++;
    }

    permanentFlags(responder, metaData, selected);
    highestModSeq(responder, metaData, selected);
    uidNext(responder, metaData);

    if (request.getCondstore()) {
      condstoreEnablingCommand(session, responder, metaData, false);
    }

    // Now do the QRESYNC processing if necessary
    //
    // If the mailbox does not store the mod-sequence in a permanent way its needed to not process
    // the QRESYNC paramters
    // The same is true if none are given ;)
    if (metaData.isModSeqPermanent() && lastKnownUidValidity != null) {
      if (lastKnownUidValidity == metaData.getUidValidity()) {

        final MailboxManager mailboxManager = getMailboxManager();
        final MailboxSession mailboxSession = ImapSessionUtils.getMailboxSession(session);
        final MessageManager mailbox = mailboxManager.getMailbox(fullMailboxPath, mailboxSession);

        //  If the provided UIDVALIDITY matches that of the selected mailbox, the
        //  server then checks the last known modification sequence.
        //
        //  The server sends the client any pending flag changes (using FETCH
        //  responses that MUST contain UIDs) and expunges those that have
        //  occurred in this mailbox since the provided modification sequence.
        SearchQuery sq = new SearchQuery();
        sq.andCriteria(SearchQuery.modSeqGreaterThan(request.getKnownModSeq()));

        UidRange[] uidSet = request.getUidSet();

        if (uidSet == null) {
          // See mailbox had some messages stored before, if not we don't need to query at all
          MessageUid uidNext = metaData.getUidNext();
          if (!uidNext.isFirst()) {
            // Use UIDNEXT -1 as max uid as stated in the QRESYNC RFC
            uidSet = new UidRange[] {new UidRange(MessageUid.MIN_VALUE, uidNext.previous())};
          }
        }

        if (uidSet != null) {
          // RFC5162 3.1. QRESYNC Parameter to SELECT/EXAMINE
          //
          // Message sequence match data:
          //
          //      A client MAY provide a parenthesized list of a message sequence set
          //      and the corresponding UID sets.  Both MUST be provided in ascending
          //      order.  The server uses this data to restrict the range for which it
          //      provides expunged message information.
          //
          //
          //      Conceptually, the client provides a small sample of sequence numbers
          //      for which it knows the corresponding UIDs.  The server then compares
          //      each sequence number and UID pair the client provides with the
          //      current state of the mailbox.  If a pair matches, then the client
          //      knows of any expunges up to, and including, the message, and thus
          //      will not include that range in the VANISHED response, even if the
          //      "mod-sequence-value" provided by the client is too old for the server
          //      to have data of when those messages were expunged.
          //
          //      Thus, if the Nth message number in the first set in the list is 4,
          //      and the Nth UID in the second set in the list is 8, and the mailbox's
          //      fourth message has UID 8, then no UIDs equal to or less than 8 are
          //      present in the VANISHED response.  If the (N+1)th message number is
          //      12, and the (N+1)th UID is 24, and the (N+1)th message in the mailbox
          //      has UID 25, then the lowest UID included in the VANISHED response
          //      would be 9.
          if (knownSequences != null && knownUids != null) {

            // Add all uids which are contained in the knownuidsset to a List so we can later access
            // them via the index
            List<MessageUid> knownUidsList = new ArrayList<MessageUid>();
            for (UidRange range : knownUids) {
              for (MessageUid uid : range) {
                knownUidsList.add(uid);
              }
            }

            // loop over the known sequences and check the UID for MSN X again the known UID X
            MessageUid firstUid = MessageUid.MIN_VALUE;
            int index = 0;
            for (IdRange knownSequence : knownSequences) {
              boolean done = false;
              for (Long uid : knownSequence) {

                // Check if we have uids left to check against
                if (knownUidsList.size() > index++) {
                  int msn = uid.intValue();
                  MessageUid knownUid = knownUidsList.get(index);

                  // Check if the uid mathc if not we are done here
                  if (selected.uid(msn).asSet().contains(knownUid)) {
                    done = true;
                    break;
                  } else {
                    firstUid = knownUid;
                  }

                } else {
                  done = true;
                  break;
                }
              }

              // We found the first uid to start with
              if (done) {
                firstUid = firstUid.next();

                // Ok now its time to filter out the IdRanges which we are not interested in
                List<UidRange> filteredUidSet = new ArrayList<UidRange>();
                for (UidRange r : uidSet) {
                  if (r.getLowVal().compareTo(firstUid) < 0) {
                    if (r.getHighVal().compareTo(firstUid) > 0) {
                      filteredUidSet.add(new UidRange(firstUid, r.getHighVal()));
                    }
                  } else {
                    filteredUidSet.add(r);
                  }
                }
                uidSet = filteredUidSet.toArray(new UidRange[0]);

                break;
              }
            }
          }

          List<MessageRange> ranges = new ArrayList<MessageRange>();
          for (UidRange range : uidSet) {
            MessageRange messageSet = range.toMessageRange();
            if (messageSet != null) {
              MessageRange normalizedMessageSet =
                  normalizeMessageRange(session.getSelected(), messageSet);
              ranges.add(normalizedMessageSet);
            }
          }

          // TODO: Reconsider if we can do something to make the handling better. Maybe at least
          // cache the triplets for the expunged
          //       while have the server running. This could maybe allow us to not return every
          // expunged message all the time
          //
          //      As we don't store the <<MSN, UID>, <MODSEQ>> in a permanent way its the best to
          // just ignore it here.
          //
          //      From RFC5162 4.1. Server Implementations That Don't Store Extra State
          //
          //
          //          Strictly speaking, a server implementation that doesn't remember mod-
          //          sequences associated with expunged messages can be considered
          //          compliant with this specification.  Such implementations return all
          //          expunged messages specified in the UID set of the UID FETCH
          //          (VANISHED) command every time, without paying attention to the
          //          specified CHANGEDSINCE mod-sequence.  Such implementations are
          //          discouraged, as they can end up returning VANISHED responses that are
          //          bigger than the result of a UID SEARCH command for the same UID set.
          //
          //          Clients that use the message sequence match data can reduce the scope
          //          of this VANISHED response substantially in the typical case where
          //          expunges have not happened, or happen only toward the end of the
          //          mailbox.
          //
          respondVanished(mailboxSession, mailbox, ranges, modSeq, metaData, responder);
        }
        taggedOk(responder, tag, command, metaData, HumanReadableText.SELECT);
      } else {

        taggedOk(responder, tag, command, metaData, HumanReadableText.QRESYNC_UIDVALIDITY_MISMATCH);
      }
    } else {
      taggedOk(responder, tag, command, metaData, HumanReadableText.SELECT);
    }

    // Reset the saved sequence-set after successful SELECT / EXAMINE
    // See RFC 5812 2.1. Normative Description of the SEARCHRES Extension
    SearchResUtil.resetSavedSequenceSet(session);
  }