Beispiel #1
0
  <D> ChannelFuture send(Command command, String args, SettableFuture<D> valueFuture) {
    Long seq = sequence.incrementAndGet();

    String commandString =
        seq + " " + command.toString() + (null == args ? "" : " " + args) + "\r\n";

    // Log the command but clip the \r\n
    log.debug("Sending {} to server...", commandString.substring(0, commandString.length() - 2));
    Boolean toStdOut = logAllMessagesForUsers.get(config.getUsername());
    if (toStdOut != null) {
      if (toStdOut)
        System.out.println(
            "IMAPsnd["
                + config.getUsername()
                + "]: "
                + commandString.substring(0, commandString.length() - 2));
      else
        log.info(
            "IMAPsnd[{}]: {}",
            config.getUsername(),
            commandString.substring(0, commandString.length() - 2));
    }

    // Enqueue command.
    mailClientHandler.enqueue(new CommandCompletion(command, seq, valueFuture, commandString));

    return channel.write(commandString);
  }
  /** This is synchronized to ensure that we process the queue serially. */
  private synchronized void complete(String message) {
    // This is a weird problem with writing stuff while idling. Need to investigate it more, but
    // for now just ignore it.
    if (MESSAGE_COULDNT_BE_FETCHED_REGEX.matcher(message).matches()) {
      log.warn(
          "Some messages in the batch could not be fetched for {}\n"
              + "---cmd---\n{}\n---wire---\n{}\n---end---\n",
          new Object[] {config.getUsername(), getCommandTrace(), getWireTrace()});
      errorStack.push(new Error(completions.peek(), message, wireTrace.list()));
      throw new RuntimeException(
          "Some messages in the batch could not be fetched for user " + config.getUsername());
    }

    CommandCompletion completion = completions.peek();
    if (completion == null) {
      if ("+ idling".equalsIgnoreCase(message)) {
        synchronized (idleMutex) {
          idler.idleStart();
          log.trace("IDLE entered.");
          idleAcknowledged.set(true);
        }
      } else {
        log.error("Could not find the completion for message {} (Was it ever issued?)", message);
        errorStack.push(new Error(null, "No completion found!", wireTrace.list()));
      }
      return;
    }

    if (completion.complete(message)) {
      completions.poll();
    }
  }
Beispiel #3
0
  /** Connects to the IMAP server logs in with the given credentials. */
  @Override
  public synchronized boolean connect(final DisconnectListener listener) {
    reset();

    ChannelFuture future =
        bootstrap.connect(new InetSocketAddress(config.getHost(), config.getPort()));

    Channel channel = future.awaitUninterruptibly().getChannel();
    if (!future.isSuccess()) {
      throw new RuntimeException("Could not connect channel", future.getCause());
    }

    this.channel = channel;
    this.disconnectListener = listener;
    if (null != listener) {
      // https://issues.jboss.org/browse/NETTY-47?page=com.atlassian.jirafisheyeplugin%3Afisheye-issuepanel#issue-tabs
      channel
          .getCloseFuture()
          .addListener(
              new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                  mailClientHandler.idleAcknowledged.set(false);
                  mailClientHandler.disconnected();
                  listener.disconnected();
                }
              });
    }
    return login();
  }
 public MailClientHandler(Idler idler, MailClientConfig config) {
   this.idler = idler;
   this.config = config;
   mBeanRegistration =
       JmxUtil.registerMBean(
           this, "com.google.sitebricks", "MailClientHandler", config.getUsername());
 }
Beispiel #5
0
  @Override
  public ListenableFuture<List<MessageStatus>> listUidThin(
      Folder folder, List<Sequence> sequences) {
    Preconditions.checkState(
        mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in");
    Preconditions.checkState(
        !mailClientHandler.idleRequested.get(),
        "Can't execute command while idling (are you watching a folder?)");

    checkCurrentFolder(folder);
    SettableFuture<List<MessageStatus>> valueFuture = SettableFuture.create();

    // -ve end range means get everything (*).
    String extensions = config.useGmailExtensions() ? " X-GM-MSGID X-GM-THRID X-GM-LABELS UID" : "";
    StringBuilder argsBuilder = new StringBuilder();

    // Emit ranges.
    for (int i = 0, sequencesSize = sequences.size(); i < sequencesSize; i++) {
      Sequence seq = sequences.get(i);
      argsBuilder.append(toUpperBound(seq.start));
      if (seq.end != 0) argsBuilder.append(':').append(toUpperBound(seq.end));
      if (i < sequencesSize - 1) argsBuilder.append(',');
    }
    argsBuilder.append(" (FLAGS" + extensions + ")");
    send(Command.FETCH_THIN_HEADERS_UID, argsBuilder.toString(), valueFuture);

    return valueFuture;
  }
Beispiel #6
0
  @Override
  public ListenableFuture<List<MessageStatus>> list(Folder folder, int start, int end) {
    Preconditions.checkState(
        mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in");
    Preconditions.checkState(
        !mailClientHandler.idleRequested.get(),
        "Can't execute command while idling (are you watching a folder?)");

    checkCurrentFolder(folder);
    checkRange(start, end);
    Preconditions.checkArgument(
        start > 0, "Start must be greater than zero (IMAP uses 1-based " + "indexing)");
    SettableFuture<List<MessageStatus>> valueFuture = SettableFuture.create();

    // -ve end range means get everything (*).
    String extensions = config.useGmailExtensions() ? " X-GM-MSGID X-GM-THRID X-GM-LABELS UID" : "";
    String args =
        start
            + ":"
            + toUpperBound(end)
            + " (RFC822.SIZE INTERNALDATE FLAGS ENVELOPE"
            + extensions
            + ")";
    send(Command.FETCH_HEADERS, args, valueFuture);

    return valueFuture;
  }
Beispiel #7
0
  /**
   * Logs out of the current IMAP session and releases all resources, including executor services.
   */
  @Override
  public synchronized void disconnect() {
    try {
      // If there is an error with the handler, dont bother logging out.
      if (!mailClientHandler.isHalted()) {
        if (mailClientHandler.idleRequested.get()) {
          log.warn("Disconnect called while IDLE, leaving idle and logging out.");
          done();
        }

        // Log out of the IMAP Server.
        channel.write(". logout\n");
      }

      currentFolder = null;
    } catch (Exception e) {
      // swallow any exceptions.
    } finally {
      // Shut down all channels and exit (leave threadpools as is--for reconnects).
      // The Netty channel close listener will fire a disconnect event to our client,
      // automatically. See connect() for details.
      try {
        channel.close().awaitUninterruptibly(config.getTimeout(), TimeUnit.MILLISECONDS);
      } catch (Exception e) {
        // swallow any exceptions.
      } finally {
        mailClientHandler.idleAcknowledged.set(false);
        mailClientHandler.disconnected();
        if (disconnectListener != null) disconnectListener.disconnected();
      }
    }
  }
Beispiel #8
0
  private boolean login() {
    try {
      channel.write(". CAPABILITY\r\n");
      if (config.getPassword() != null)
        channel.write(". login " + config.getUsername() + " " + config.getPassword() + "\r\n");
      else {
        // Use xoauth login instead.
        OAuthConfig oauth = config.getOAuthConfig();
        Preconditions.checkArgument(
            oauth != null, "Must specify a valid oauth config if not using password auth");

        //noinspection ConstantConditions
        String oauthString =
            new XoauthSasl(config.getUsername(), oauth.clientId, oauth.clientSecret)
                .build(Protocol.IMAP, oauth.accessToken, oauth.tokenSecret);

        channel.write(". AUTHENTICATE XOAUTH " + oauthString + "\r\n");
      }
      return mailClientHandler.awaitLogin();
    } catch (Exception e) {
      // Capture the wire trace and log it for some extra context here.
      StringBuilder trace = new StringBuilder();
      for (String line : mailClientHandler.getWireTrace()) {
        trace.append(line).append("\n");
      }

      log.warn(
          "Could not oauth or login for {}. Partial trace follows:\n"
              + "----begin wiretrace----\n{}\n----end wiretrace----",
          new Object[] {config.getUsername(), trace.toString(), e});
    }
    return false;
  }
Beispiel #9
0
  @Override
  public ListenableFuture<List<Integer>> searchUid(Folder folder, String query, Date since) {
    Preconditions.checkState(
        mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in");
    Preconditions.checkState(
        !mailClientHandler.idleRequested.get(),
        "Can't execute command while idling (are you watching a folder?)");

    checkCurrentFolder(folder);
    SettableFuture<List<Integer>> valueFuture = SettableFuture.create();

    StringBuilder argsBuilder = new StringBuilder();

    if (config.useGmailExtensions()) {
      argsBuilder.append("X-GM-RAW \"").append(query).append('"');
    } else argsBuilder.append(query);

    if (since != null) argsBuilder.append(" since ").append(SINCE_FORMAT.format(since));

    send(Command.SEARCH_RAW_UID, argsBuilder.toString(), valueFuture);

    return valueFuture;
  }
 @ManagedOperation
 public void logAllMessages(boolean b) {
   log.info("logAllMessagesForUsers[" + config.getUsername() + "] = " + b);
   if (b) logAllMessagesForUsers.put(config.getUsername(), false);
   else logAllMessagesForUsers.remove(config.getUsername());
 }
  private void processMessage(String message) throws Exception {
    Boolean toStdOut = logAllMessagesForUsers.get(config.getUsername());
    if (toStdOut != null) {
      if (toStdOut) System.out.println("IMAPrcv[" + config.getUsername() + "]: " + message);
      else log.info("IMAPrcv[{}]: {}", config.getUsername(), message);
    }

    wireTrace.add(message);
    log.trace(message);
    if (SYSTEM_ERROR_REGEX.matcher(message).matches()
        || ". NO [ALERT] Account exceeded command or bandwidth limits. (Failure)"
            .equalsIgnoreCase(message.trim())) {
      log.warn(
          "{} disconnected by IMAP Server due to system error: {}", config.getUsername(), message);
      disconnectAbnormally(message);
      return;
    }

    try {
      if (halt) {
        log.error(
            "Mail client for {} is halted but continues to receive messages, ignoring!",
            config.getUsername());
        return;
      }
      if (loginSuccess.getCount() > 0) {
        if (message.startsWith(CAPABILITY_PREFIX)) {
          this.capabilities =
              Arrays.asList(message.substring(CAPABILITY_PREFIX.length() + 1).split("[ ]+"));
          return;
        } else if (AUTH_SUCCESS_REGEX.matcher(message).matches()) {
          log.info("Authentication success for user {}", config.getUsername());
          loginSuccess.countDown();
        } else {
          Matcher matcher = COMMAND_FAILED_REGEX.matcher(message);
          if (matcher.find()) {
            // WARNING: DO NOT COUNTDOWN THE LOGIN LATCH ON FAILURE!!!

            log.warn("Authentication failed for {} due to: {}", config.getUsername(), message);
            errorStack.push(
                new Error(
                    null /* logins have no completion */, extractError(matcher), wireTrace.list()));
            disconnectAbnormally(message);
          }
        }
        return;
      }

      // Copy to local var as the value can change underneath us.
      FolderObserver observer = this.observer;
      if (idleRequested.get() || idleAcknowledged.get()) {
        synchronized (idleMutex) {
          if (IDLE_ENDED_REGEX.matcher(message).matches()) {
            idleRequested.compareAndSet(true, false);
            idleAcknowledged.set(false);

            // Now fire the events.
            PushedData data = pushedData;
            pushedData = null;

            idler.idleEnd();
            observer.changed(
                data.pushAdds.isEmpty() ? null : data.pushAdds,
                data.pushRemoves.isEmpty() ? null : data.pushRemoves);
            return;
          }

          // Queue up any push notifications to publish to the client in a second.
          Matcher existsMatcher = IDLE_EXISTS_REGEX.matcher(message);
          boolean matched = false;
          if (existsMatcher.matches()) {
            int number = Integer.parseInt(existsMatcher.group(1));
            pushedData.pushAdds.add(number);
            pushedData.pushRemoves.remove(number);
            matched = true;
          } else {
            Matcher expungeMatcher = IDLE_EXPUNGE_REGEX.matcher(message);
            if (expungeMatcher.matches()) {
              int number = Integer.parseInt(expungeMatcher.group(1));
              pushedData.pushRemoves.add(number);
              pushedData.pushAdds.remove(number);
              matched = true;
            }
          }

          // Stop idling, when we get the idle ended message (next cycle) we can publish what's been
          // gathered.
          if (matched) {
            if (!pushedData.idleExitSent) {
              idler.done();
              pushedData.idleExitSent = true;
            }
            return;
          }
        }
      }

      complete(message);
    } catch (Exception ex) {
      CommandCompletion completion = completions.poll();
      if (completion != null) completion.error(message, ex);
      else {
        log.error(
            "Strange exception during mail processing (no completions available!): {}",
            message,
            ex);
        errorStack.push(new Error(null, "No completions available!", wireTrace.list()));
      }
      throw ex;
    }
  }
Beispiel #12
0
 public void enableSendLogging(boolean enable) {
   log.info("Logging of sent IMAP commands for user {} = {}", config.getUsername(), enable);
   if (enable) logAllMessagesForUsers.put(config.getUsername(), false);
   else logAllMessagesForUsers.remove(config.getUsername());
 }
Beispiel #13
0
 @Override
 public synchronized void updateOAuthAccessToken(String accessToken, String tokenSecret) {
   config.getOAuthConfig().accessToken = accessToken;
   config.getOAuthConfig().tokenSecret = tokenSecret;
 }