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(); }
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; }
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; }
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); } }
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; }
/** 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); }