Example #1
0
  void dropConnection() {
    ImapConnection conn = mConnection;
    mConnection = null;
    if (conn == null) return;

    // FIXME: should close cleanly (i.e. with tagged LOGOUT)
    ZimbraLog.imap.info("closing proxy connection");
    conn.close();
  }
Example #2
0
  boolean idle(final ImapRequest req, final boolean begin) throws IOException {
    if (begin == ImapHandler.IDLE_STOP) {
      // check state -- don't want to send DONE if we're somehow not in IDLE
      ImapHandler handler = mHandler;
      if (handler == null) throw new IOException("proxy connection already closed");
      Thread idle = mIdleThread;
      if (idle == null)
        throw new IOException("bad proxy state: no IDLE thread active when attempting DONE");
      // send the DONE, which elicits the tagged response that causes the IDLE thread (below) to
      // exit
      writeRequest(req.toByteArray());
      // make sure that the idle thread actually exits; otherwise we're in a bad place and we must
      // kill the whole session
      mIdleThread = null;
      try {
        idle.join(5 * Constants.MILLIS_PER_SECOND);
      } catch (InterruptedException ie) {
      }
      if (idle.isAlive()) handler.dropConnection(false);
    } else {
      final ImapHandler handler = mHandler;
      final ImapConnection conn = mConnection;
      if (conn == null) throw new IOException("proxy connection already closed");

      ImapConfig config = conn.getImapConfig();
      final int oldTimeout =
          config != null ? config.getReadTimeout() : LC.javamail_imap_timeout.intValue();
      // necessary because of subsequent race condition with req.cleanup()
      final byte[] payload = req.toByteArray();

      mIdleThread =
          new Thread() {
            @Override
            public void run() {
              boolean success = false;
              try {
                // the standard aggressive read timeout is inappropriate for IDLE
                conn.setReadTimeout(mHandler.getConfig().getAuthenticatedMaxIdleSeconds());
                // send the IDLE command; this call waits until the subsequent DONE is acknowledged
                boolean ok = proxyCommand(req.getTag(), payload, true, true);
                // restore the old read timeout
                conn.setReadTimeout(oldTimeout);
                // don't set <code>success</code> until we're past things that can throw
                // IOExceptions
                success = ok;
              } catch (IOException e) {
                ZimbraLog.imap.warn("error encountered during IDLE; dropping connection", e);
              }
              if (!success) handler.dropConnection();
            }
          };
      mIdleThread.setName("Imap-Idle-Proxy-" + Thread.currentThread().getName());
      mIdleThread.start();
    }
    return true;
  }
Example #3
0
  private ImapConnection writeRequest(final byte[] payload) throws IOException {
    ImapConnection conn = mConnection;
    if (conn == null) throw new IOException("proxy connection already closed");

    // proxy the request over to the remote server
    OutputStream remote = conn.getOutputStream();
    if (remote == null) {
      dropConnection();
      throw new IOException("proxy connection already closed");
    }
    remote.write(payload);
    remote.flush();

    return conn;
  }
Example #4
0
  ImapProxy(final ImapHandler handler, final ImapPath path) throws ServiceException {
    mHandler = handler;
    mPath = path;

    Account acct = handler.getCredentials().getAccount();
    Server server = Provisioning.getInstance().getServer(path.getOwnerAccount());
    String host = server.getServiceHostname();
    if (acct == null)
      throw ServiceException.PROXY_ERROR(
          new Exception("no such authenticated user"), path.asImapPath());

    ImapConfig config = new ImapConfig();
    config.setAuthenticationId(acct.getName());
    config.setMechanism(ZimbraAuthenticator.MECHANISM);
    config.setAuthenticatorFactory(sAuthenticatorFactory);
    config.setReadTimeout(LC.javamail_imap_timeout.intValue());
    config.setConnectTimeout(config.getReadTimeout());
    config.setHost(host);
    if (server.isImapServerEnabled()) {
      config.setPort(server.getIntAttr(Provisioning.A_zimbraImapBindPort, ImapConfig.DEFAULT_PORT));
    } else if (server.isImapSSLServerEnabled()) {
      config.setPort(
          server.getIntAttr(Provisioning.A_zimbraImapSSLBindPort, ImapConfig.DEFAULT_SSL_PORT));
      config.setSecurity(MailConfig.Security.SSL);
    } else {
      throw ServiceException.PROXY_ERROR(
          new Exception("no open IMAP port for server " + host), path.asImapPath());
    }

    ZimbraLog.imap.info(
        "opening proxy connection (user="******", host="
            + host
            + ", path="
            + path.getReferent().asImapPath()
            + ')');

    ImapConnection conn = mConnection = new ImapConnection(config);
    try {
      conn.connect();
      conn.authenticate(AuthProvider.getAuthToken(acct).getEncoded());
    } catch (Exception e) {
      dropConnection();
      throw ServiceException.PROXY_ERROR(e, null);
    }
  }
Example #5
0
  boolean proxyCommand(
      final String tag,
      final byte[] payload,
      final boolean includeTaggedResponse,
      final boolean isIdle)
      throws IOException {
    //        System.out.write(payload);  System.out.flush();
    ImapConnection conn = writeRequest(payload);

    MailInputStream min = conn.getInputStream();
    OutputStream out = mHandler.mOutputStream;
    if (out == null) {
      dropConnection();
      throw new IOException("proxy connection already closed");
    }

    // copy the response back to the handler's output (i.e. the original client)
    boolean success = false;
    int first;
    while ((first = min.peek()) != -1) {
      // XXX: may want to check that the "tagged" response's tag actually matches the request's
      // tag...
      boolean tagged = first != '*' && first != '+';
      boolean structured = first == '*';
      boolean proxy = (first != '+' || isIdle) && (!tagged || includeTaggedResponse);

      ByteArrayOutputStream line = proxy ? new ByteArrayOutputStream() : null;
      StringBuilder debug =
          proxy && ZimbraLog.imap.isDebugEnabled() ? new StringBuilder("  pxy: ") : null;
      StringBuilder condition = new StringBuilder(10);

      boolean quoted = false, escaped = false, space1 = false, space2 = false;
      int c, literal = -1;
      while ((c = min.read()) != -1) {
        // check for success and also determine whether we should be paying attention to structure
        if (!space2) {
          if (c == ' ' && !space1) {
            space1 = true;
          } else if (c == ' ') {
            space2 = true;
            String code = condition.toString().toUpperCase();
            if (tagged) success = code.equals("OK") || (isIdle && code.equals("BAD"));
            structured &= !UNSTRUCTURED_CODES.contains(code);
          } else if (space1) {
            condition.append((char) c);
          }
        }

        // if it's a structured response, pay attention to quoting, literals, etc.
        if (structured) {
          if (escaped) escaped = false;
          else if (quoted && c == '\\') escaped = true;
          else if (c == '"') quoted = !quoted;
          else if (!quoted && c == '{') literal = 0;
          else if (literal != -1 && c >= '0' && c <= '9') literal = literal * 10 + (c - '0');
        }

        if (!quoted && c == '\r' && min.peek() == '\n') {
          // skip the terminal LF
          min.read();
          // write the line back to the client
          if (proxy) {
            out.write(line.toByteArray());
            out.write(ImapHandler.LINE_SEPARATOR_BYTES);
            line.reset();
            if (isIdle) out.flush();
          }
          // if it's end of line (i.e. no literal), we're done
          if (literal == -1) break;
          // if there's a literal, copy it and then handle the following line
          byte buffer[] = literal == 0 ? null : new byte[Math.min(literal, 65536)];
          while (literal > 0) {
            int read = min.read(buffer, 0, Math.min(literal, buffer.length));
            if (read == -1) break;
            if (proxy) out.write(buffer, 0, read);
            literal -= read;
          }
          literal = -1;
          if (isIdle) out.flush();
        } else if (proxy) {
          line.write(c);
          if (debug != null) debug.append((char) c);
        }
      }

      if (debug != null) ZimbraLog.imap.debug(debug.toString());

      if (tagged) break;
    }

    out.flush();
    return success;
  }
Example #6
0
 /** Retrieves the set of notifications pending on the remote server. */
 void fetchNotifications() throws IOException {
   String tag = mConnection == null ? "1" : mConnection.newTag();
   proxyCommand(tag, (tag + " NOOP\r\n").getBytes(), false, false);
 }