diff --git a/src/com/fsck/k9/mail/store/imap/ImapConnection.java b/src/com/fsck/k9/mail/store/imap/ImapConnection.java index f3d20a399..77da82b66 100644 --- a/src/com/fsck/k9/mail/store/imap/ImapConnection.java +++ b/src/com/fsck/k9/mail/store/imap/ImapConnection.java @@ -4,6 +4,7 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.util.Log; +import com.fsck.k9.mail.AuthType; import com.fsck.k9.mail.Authentication; import com.fsck.k9.mail.AuthenticationFailedException; import com.fsck.k9.mail.CertificateValidationException; @@ -13,7 +14,6 @@ import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.filter.Base64; import com.fsck.k9.mail.filter.PeekableInputStream; import com.fsck.k9.mail.ssl.TrustedSocketFactory; -import com.fsck.k9.mail.store.RemoteStore; import com.fsck.k9.mail.transport.imap.ImapSettings; import com.jcraft.jzlib.JZlib; import com.jcraft.jzlib.ZOutputStream; @@ -44,13 +44,26 @@ import java.util.zip.InflaterInputStream; import javax.net.ssl.SSLException; +import static com.fsck.k9.mail.ConnectionSecurity.STARTTLS_REQUIRED; import static com.fsck.k9.mail.K9MailLib.DEBUG_PROTOCOL_IMAP; import static com.fsck.k9.mail.K9MailLib.LOG_TAG; +import static com.fsck.k9.mail.store.RemoteStore.SOCKET_CONNECT_TIMEOUT; +import static com.fsck.k9.mail.store.RemoteStore.SOCKET_READ_TIMEOUT; +import static com.fsck.k9.mail.store.imap.ImapCommands.CAPABILITY_AUTH_CRAM_MD5; +import static com.fsck.k9.mail.store.imap.ImapCommands.CAPABILITY_AUTH_EXTERNAL; +import static com.fsck.k9.mail.store.imap.ImapCommands.CAPABILITY_AUTH_PLAIN; +import static com.fsck.k9.mail.store.imap.ImapCommands.CAPABILITY_CAPABILITY; +import static com.fsck.k9.mail.store.imap.ImapCommands.CAPABILITY_COMPRESS_DEFLATE; +import static com.fsck.k9.mail.store.imap.ImapCommands.CAPABILITY_LOGINDISABLED; +import static com.fsck.k9.mail.store.imap.ImapCommands.COMMAND_CAPABILITY; +import static com.fsck.k9.mail.store.imap.ImapResponseParser.equalsIgnoreCase; + /** * A cacheable class that stores the details for a single IMAP connection. */ class ImapConnection { + private static final int BUFFER_SIZE = 1024; private Socket mSocket; private PeekableInputStream mIn; @@ -82,73 +95,22 @@ class ImapConnection { return "conn" + hashCode(); } - public void open() throws IOException, MessagingException { if (isOpen()) { return; } boolean authSuccess = false; - mNextCommandTag = 1; - try { - Security.setProperty("networkaddress.cache.ttl", "0"); - } catch (Exception e) { - Log.w(LOG_TAG, "Could not set DNS ttl to 0 for " + getLogId(), e); - } - + adjustDNSCacheTTL(); try { - Security.setProperty("networkaddress.cache.negative.ttl", "0"); - } catch (Exception e) { - Log.w(LOG_TAG, "Could not set DNS negative ttl to 0 for " + getLogId(), e); - } + mSocket = connect(mSettings, mSocketFactory); + setReadTimeout(SOCKET_READ_TIMEOUT); - try { - ConnectionSecurity connectionSecurity = mSettings.getConnectionSecurity(); - - // Try all IPv4 and IPv6 addresses of the host - InetAddress[] addresses = InetAddress.getAllByName(mSettings.getHost()); - for (int i = 0; i < addresses.length; i++) { - try { - if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) { - Log.d(LOG_TAG, "Connecting to " + mSettings.getHost() + " as " + - addresses[i]); - } - - SocketAddress socketAddress = new InetSocketAddress(addresses[i], - mSettings.getPort()); - - if (connectionSecurity == ConnectionSecurity.SSL_TLS_REQUIRED) { - mSocket = mSocketFactory.createSocket( - null, - mSettings.getHost(), - mSettings.getPort(), - mSettings.getClientCertificateAlias()); - } else { - mSocket = new Socket(); - } - - mSocket.connect(socketAddress, RemoteStore.SOCKET_CONNECT_TIMEOUT); - - // Successfully connected to the server; don't try any other addresses - break; - } catch (SocketException e) { - if (i < (addresses.length - 1)) { - // There are still other addresses for that host to try - continue; - } - throw new MessagingException("Cannot connect to host", e); - } - } - - setReadTimeout(RemoteStore.SOCKET_READ_TIMEOUT); - - mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(), - 1024)); + mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(), BUFFER_SIZE)); mParser = new ImapResponseParser(mIn); - mOut = new BufferedOutputStream(mSocket.getOutputStream(), 1024); - + mOut = new BufferedOutputStream(mSocket.getOutputStream(), BUFFER_SIZE); capabilities.clear(); ImapResponse nullResponse = mParser.readResponse(); if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) @@ -158,39 +120,18 @@ class ImapConnection { nullResponses.add(nullResponse); receiveCapabilities(nullResponses); - if (!hasCapability(ImapCommands.CAPABILITY_CAPABILITY)) { + if (!hasCapability(CAPABILITY_CAPABILITY)) { if (K9MailLib.isDebug()) Log.i(LOG_TAG, "Did not get capabilities in banner, requesting CAPABILITY for " + getLogId()); - List responses = receiveCapabilities(executeSimpleCommand(ImapCommands.COMMAND_CAPABILITY)); + List responses = receiveCapabilities(executeSimpleCommand(COMMAND_CAPABILITY)); if (responses.size() != 2) { throw new MessagingException("Invalid CAPABILITY response received"); } } - if (mSettings.getConnectionSecurity() == ConnectionSecurity.STARTTLS_REQUIRED) { - + if (mSettings.getConnectionSecurity() == STARTTLS_REQUIRED) { if (hasCapability("STARTTLS")) { - // STARTTLS - executeSimpleCommand("STARTTLS"); - - mSocket = mSocketFactory.createSocket( - mSocket, - mSettings.getHost(), - mSettings.getPort(), - mSettings.getClientCertificateAlias()); - mSocket.setSoTimeout(RemoteStore.SOCKET_READ_TIMEOUT); - mIn = new PeekableInputStream(new BufferedInputStream(mSocket - .getInputStream(), 1024)); - mParser = new ImapResponseParser(mIn); - mOut = new BufferedOutputStream(mSocket.getOutputStream(), 1024); - // Per RFC 2595 (3.1): Once TLS has been started, reissue CAPABILITY command - if (K9MailLib.isDebug()) - Log.i(LOG_TAG, "Updating capabilities after STARTTLS for " + getLogId()); - capabilities.clear(); - List responses = receiveCapabilities(executeSimpleCommand(ImapCommands.COMMAND_CAPABILITY)); - if (responses.size() != 2) { - throw new MessagingException("Invalid CAPABILITY response received"); - } + startTLS(); } else { /* * This exception triggers a "Certificate error" @@ -199,137 +140,40 @@ class ImapConnection { * the account was configured with an obsolete * "STARTTLS (if available)" setting. */ - throw new CertificateValidationException( - "STARTTLS connection security not available"); + throw new CertificateValidationException("STARTTLS connection security not available"); } } - - switch (mSettings.getAuthType()) { - case CRAM_MD5: - if (hasCapability(ImapCommands.CAPABILITY_AUTH_CRAM_MD5)) { - authCramMD5(); - } else { - throw new MessagingException( - "Server doesn't support encrypted passwords using CRAM-MD5."); - } - break; - - case PLAIN: - if (hasCapability(ImapCommands.CAPABILITY_AUTH_PLAIN)) { - saslAuthPlain(); - } else if (!hasCapability(ImapCommands.CAPABILITY_LOGINDISABLED)) { - login(); - } else { - throw new MessagingException( - "Server doesn't support unencrypted passwords using AUTH=PLAIN and LOGIN is disabled."); - } - break; - - case EXTERNAL: - if (hasCapability(ImapCommands.CAPABILITY_AUTH_EXTERNAL)) { - saslAuthExternal(); - } else { - // Provide notification to user of a problem authenticating using client certificates - throw new CertificateValidationException(CertificateValidationException.Reason.MissingCapability); - } - break; - - default: - throw new MessagingException( - "Unhandled authentication method found in the server settings (bug)."); - } + authenticate(mSettings.getAuthType()); authSuccess = true; + if (K9MailLib.isDebug()) { - Log.d(LOG_TAG, ImapCommands.CAPABILITY_COMPRESS_DEFLATE + " = " + hasCapability(ImapCommands.CAPABILITY_COMPRESS_DEFLATE)); + Log.d(LOG_TAG, CAPABILITY_COMPRESS_DEFLATE + " = " + hasCapability(CAPABILITY_COMPRESS_DEFLATE)); } - if (hasCapability(ImapCommands.CAPABILITY_COMPRESS_DEFLATE)) { - boolean useCompression = true; - - NetworkInfo netInfo = mConnectivityManager.getActiveNetworkInfo(); - if (netInfo != null) { - int type = netInfo.getType(); - if (K9MailLib.isDebug()) - Log.d(LOG_TAG, "On network type " + type); - useCompression = mSettings.useCompression(type); - - } - if (K9MailLib.isDebug()) - Log.d(LOG_TAG, "useCompression " + useCompression); - if (useCompression) { - try { - executeSimpleCommand(ImapCommands.COMMAND_COMPRESS_DEFLATE); - Inflater inf = new Inflater(true); - InflaterInputStream zInputStream = new InflaterInputStream(mSocket.getInputStream(), inf); - mIn = new PeekableInputStream(new BufferedInputStream(zInputStream, 1024)); - mParser = new ImapResponseParser(mIn); - ZOutputStream zOutputStream = new ZOutputStream(mSocket.getOutputStream(), JZlib.Z_BEST_SPEED, true); - mOut = new BufferedOutputStream(zOutputStream, 1024); - zOutputStream.setFlushMode(JZlib.Z_PARTIAL_FLUSH); - if (K9MailLib.isDebug()) { - Log.i(LOG_TAG, "Compression enabled for " + getLogId()); - } - } catch (Exception e) { - Log.e(LOG_TAG, "Unable to negotiate compression", e); - } - } + if (hasCapability(CAPABILITY_COMPRESS_DEFLATE) && shouldEnableCompression()) { + enableCompression(); } - - if (K9MailLib.isDebug()) + if (K9MailLib.isDebug()) { Log.d(LOG_TAG, "NAMESPACE = " + hasCapability(ImapCommands.CAPABILITY_NAMESPACE) - + ", mPathPrefix = " + mSettings.getPathPrefix()); + + ", mPathPrefix = " + mSettings.getPathPrefix()); + } if (mSettings.getPathPrefix() == null) { if (hasCapability(ImapCommands.CAPABILITY_NAMESPACE)) { - if (K9MailLib.isDebug()) - Log.i(LOG_TAG, "mPathPrefix is unset and server has NAMESPACE capability"); - List namespaceResponses = - executeSimpleCommand(ImapCommands.COMMAND_NAMESPACE); - for (ImapResponse response : namespaceResponses) { - if (ImapResponseParser.equalsIgnoreCase(response.get(0), ImapCommands.COMMAND_NAMESPACE)) { - if (K9MailLib.isDebug()) - Log.d(LOG_TAG, "Got NAMESPACE response " + response + " on " + getLogId()); - - Object personalNamespaces = response.get(1); - if (personalNamespaces != null && personalNamespaces instanceof ImapList) { - if (K9MailLib.isDebug()) - Log.d(LOG_TAG, "Got personal namespaces: " + personalNamespaces); - ImapList bracketed = (ImapList)personalNamespaces; - Object firstNamespace = bracketed.get(0); - if (firstNamespace != null && firstNamespace instanceof ImapList) { - if (K9MailLib.isDebug()) - Log.d(LOG_TAG, "Got first personal namespaces: " + firstNamespace); - bracketed = (ImapList)firstNamespace; - mSettings.setPathPrefix(bracketed.getString(0)); - mSettings.setPathDelimeter(bracketed.getString(1)); - mSettings.setCombinedPrefix(null); - if (K9MailLib.isDebug()) - Log.d(LOG_TAG, "Got path '" + mSettings.getPathPrefix() + "' and separator '" + mSettings.getPathDelimeter() + "'"); - } - } - } + if (K9MailLib.isDebug()) { + Log.i(LOG_TAG, "pathPrefix is unset and server has NAMESPACE capability"); } + handleNamespace(); } else { - if (K9MailLib.isDebug()) - Log.i(LOG_TAG, "mPathPrefix is unset but server does not have NAMESPACE capability"); + if (K9MailLib.isDebug()) { + Log.i(LOG_TAG, "pathPrefix is unset but server does not have NAMESPACE capability"); + } mSettings.setPathPrefix(""); } } - if (mSettings.getPathDelimeter() == null) { - try { - List nameResponses = - executeSimpleCommand("LIST \"\" \"\""); - for (ImapResponse response : nameResponses) { - if (ImapResponseParser.equalsIgnoreCase(response.get(0), "LIST")) { - mSettings.setPathDelimeter(response.getString(2)); - mSettings.setCombinedPrefix(null); - if (K9MailLib.isDebug()) - Log.d(LOG_TAG, "Got path delimeter '" + mSettings.getPathDelimeter() + "' for " + getLogId()); - } - } - } catch (Exception e) { - Log.e(LOG_TAG, "Unable to get path delimeter using LIST", e); - } + + if (mSettings.getPathDelimiter() == null) { + getPathDelimiter(); } } catch (SSLException e) { @@ -358,12 +202,106 @@ class ImapConnection { } } - private List receiveCapabilities(List responses) { - capabilities = ImapResponseParser.parseCapabilities(responses); - if (K9MailLib.isDebug()) { - Log.d(LOG_TAG, "Saving " + capabilities + " capabilities for " + getLogId()); + public boolean isOpen() { + return (mIn != null && mOut != null && mSocket != null && mSocket.isConnected() && !mSocket.isClosed()); + } + + public void close() { +// if (isOpen()) { +// try { +// executeSimpleCommand("LOGOUT"); +// } catch (Exception e) { +// +// } +// } + IOUtils.closeQuietly(mIn); + IOUtils.closeQuietly(mOut); + IOUtils.closeQuietly(mSocket); + mIn = null; + mOut = null; + mSocket = null; + } + + public ImapResponse readResponse() throws IOException, MessagingException { + return readResponse(null); + } + + public ImapResponse readResponse(ImapResponseCallback callback) throws IOException { + try { + ImapResponse response = mParser.readResponse(callback); + if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) + Log.v(LOG_TAG, getLogId() + "<<<" + response); + + return response; + } catch (IOException ioe) { + close(); + throw ioe; } - return responses; + } + + public void sendContinuation(String continuation) throws IOException { + mOut.write(continuation.getBytes()); + mOut.write('\r'); + mOut.write('\n'); + mOut.flush(); + + if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) + Log.v(LOG_TAG, getLogId() + ">>> " + continuation); + + } + + public String sendCommand(String command, boolean sensitive) throws MessagingException, IOException { + try { + open(); + String tag = Integer.toString(mNextCommandTag++); + String commandToSend = tag + " " + command + "\r\n"; + mOut.write(commandToSend.getBytes()); + mOut.flush(); + + if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) { + if (sensitive && !K9MailLib.isDebugSensitive()) { + Log.v(LOG_TAG, getLogId() + ">>> " + + "[Command Hidden, Enable Sensitive Debug Logging To Show]"); + } else { + Log.v(LOG_TAG, getLogId() + ">>> " + commandToSend); + } + } + + return tag; + } catch (IOException ioe) { + close(); + throw ioe; + } catch (ImapException ie) { + close(); + throw ie; + } catch (MessagingException me) { + close(); + throw me; + } + } + + public List executeSimpleCommand(String command) throws IOException, + MessagingException { + return executeSimpleCommand(command, false, null); + } + + public List executeSimpleCommand(String command, boolean sensitive) throws IOException, + MessagingException { + return executeSimpleCommand(command, sensitive, null); + } + + public List executeSimpleCommand(String command, boolean sensitive, UntaggedHandler untaggedHandler) + throws IOException, MessagingException { + String commandToLog = command; + if (sensitive && !K9MailLib.isDebugSensitive()) { + commandToLog = "*sensitive*"; + } + //if (K9MailLib.isDebug()) + // Log.v(LOG_TAG, "Sending IMAP command " + commandToLog + " on connection " + getLogId()); + String tag = sendCommand(command, sensitive); + //if (K9MailLib.isDebug()) + // Log.v(LOG_TAG, "Sent IMAP command " + commandToLog + " with tag " + tag + " for " + getLogId()); + return mParser.readStatusResponse(tag, commandToLog, getLogId(), untaggedHandler); } protected void login() throws IOException, MessagingException { @@ -426,23 +364,6 @@ class ImapConnection { } } - private void saslAuthExternal() throws IOException, MessagingException { - try { - receiveCapabilities(executeSimpleCommand( - String.format("AUTHENTICATE EXTERNAL %s", - Base64.encode(mSettings.getUsername())), false)); - } catch (ImapException e) { - /* - * Provide notification to the user of a problem authenticating - * using client certificates. We don't use an - * AuthenticationFailedException because that would trigger a - * "Username or password incorrect" notification in - * AccountSetupCheckSettings. - */ - throw new CertificateValidationException(e.getMessage()); - } - } - protected ImapResponse readContinuationResponse(String tag) throws IOException, MessagingException { ImapResponse response; @@ -462,7 +383,6 @@ class ImapConnection { return response; } - protected void setReadTimeout(int millis) throws SocketException { Socket sock = mSocket; if (sock != null) { @@ -481,106 +401,211 @@ class ImapConnection { return capabilities.contains(capability.toUpperCase(Locale.US)); } - public boolean isOpen() { - return (mIn != null && mOut != null && mSocket != null && mSocket.isConnected() && !mSocket.isClosed()); - } - - public void close() { -// if (isOpen()) { -// try { -// executeSimpleCommand("LOGOUT"); -// } catch (Exception e) { -// -// } -// } - IOUtils.closeQuietly(mIn); - IOUtils.closeQuietly(mOut); - IOUtils.closeQuietly(mSocket); - mIn = null; - mOut = null; - mSocket = null; - } - - public ImapResponse readResponse() throws IOException, MessagingException { - return readResponse(null); - } - - public ImapResponse readResponse(ImapResponseCallback callback) throws IOException { + private void saslAuthExternal() throws IOException, MessagingException { try { - ImapResponse response = mParser.readResponse(callback); - if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) - Log.v(LOG_TAG, getLogId() + "<<<" + response); - - return response; - } catch (IOException ioe) { - close(); - throw ioe; + receiveCapabilities(executeSimpleCommand( + String.format("AUTHENTICATE EXTERNAL %s", + Base64.encode(mSettings.getUsername())), false)); + } catch (ImapException e) { + /* + * Provide notification to the user of a problem authenticating + * using client certificates. We don't use an + * AuthenticationFailedException because that would trigger a + * "Username or password incorrect" notification in + * AccountSetupCheckSettings. + */ + throw new CertificateValidationException(e.getMessage()); } } - public void sendContinuation(String continuation) throws IOException { - mOut.write(continuation.getBytes()); - mOut.write('\r'); - mOut.write('\n'); - mOut.flush(); - - if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) - Log.v(LOG_TAG, getLogId() + ">>> " + continuation); - - } - - public String sendCommand(String command, boolean sensitive) throws MessagingException, IOException { + private void getPathDelimiter() { try { - open(); - String tag = Integer.toString(mNextCommandTag++); - String commandToSend = tag + " " + command + "\r\n"; - mOut.write(commandToSend.getBytes()); - mOut.flush(); - - if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) { - if (sensitive && !K9MailLib.isDebugSensitive()) { - Log.v(LOG_TAG, getLogId() + ">>> " - + "[Command Hidden, Enable Sensitive Debug Logging To Show]"); - } else { - Log.v(LOG_TAG, getLogId() + ">>> " + commandToSend); + List nameResponses = executeSimpleCommand("LIST \"\" \"\""); + for (ImapResponse response : nameResponses) { + if (equalsIgnoreCase(response.get(0), "LIST")) { + mSettings.setPathDelimiter(response.getString(2)); + mSettings.setCombinedPrefix(null); + if (K9MailLib.isDebug()) { + Log.d(LOG_TAG, "Got path delimiter '" + mSettings.getPathDelimiter() + "' for " + getLogId()); + } } } - - return tag; - } catch (IOException ioe) { - close(); - throw ioe; - } catch (ImapException ie) { - close(); - throw ie; - } catch (MessagingException me) { - close(); - throw me; + } catch (Exception e) { + Log.e(LOG_TAG, "Unable to get path delimiter using LIST", e); } } - public List executeSimpleCommand(String command) throws IOException, - MessagingException { - return executeSimpleCommand(command, false, null); - } + private void handleNamespace() throws IOException, MessagingException { + List responses = executeSimpleCommand(ImapCommands.COMMAND_NAMESPACE); + for (ImapResponse response : responses) { + if (equalsIgnoreCase(response.get(0), ImapCommands.COMMAND_NAMESPACE)) { + if (K9MailLib.isDebug()) { + Log.d(LOG_TAG, "Got NAMESPACE response " + response + " on " + getLogId()); + } - public List executeSimpleCommand(String command, boolean sensitive) throws IOException, - MessagingException { - return executeSimpleCommand(command, sensitive, null); - } - - public List executeSimpleCommand(String command, boolean sensitive, UntaggedHandler untaggedHandler) - throws IOException, MessagingException { - String commandToLog = command; - if (sensitive && !K9MailLib.isDebugSensitive()) { - commandToLog = "*sensitive*"; + Object personalNamespaces = response.get(1); + if (personalNamespaces instanceof ImapList) { + if (K9MailLib.isDebug()) + Log.d(LOG_TAG, "Got personal namespaces: " + personalNamespaces); + ImapList bracketed = (ImapList)personalNamespaces; + Object firstNamespace = bracketed.get(0); + if (firstNamespace != null && firstNamespace instanceof ImapList) { + if (K9MailLib.isDebug()) + Log.d(LOG_TAG, "Got first personal namespaces: " + firstNamespace); + bracketed = (ImapList)firstNamespace; + mSettings.setPathPrefix(bracketed.getString(0)); + mSettings.setPathDelimiter(bracketed.getString(1)); + mSettings.setCombinedPrefix(null); + if (K9MailLib.isDebug()) + Log.d(LOG_TAG, "Got path '" + mSettings.getPathPrefix() + "' and separator '" + mSettings.getPathDelimiter() + "'"); + } + } + } } - //if (K9MailLib.isDebug()) - // Log.v(LOG_TAG, "Sending IMAP command " + commandToLog + " on connection " + getLogId()); - String tag = sendCommand(command, sensitive); - //if (K9MailLib.isDebug()) - // Log.v(LOG_TAG, "Sent IMAP command " + commandToLog + " with tag " + tag + " for " + getLogId()); + } - return mParser.readStatusResponse(tag, commandToLog, getLogId(), untaggedHandler); + private boolean shouldEnableCompression() { + boolean useCompression = true; + NetworkInfo netInfo = mConnectivityManager.getActiveNetworkInfo(); + if (netInfo != null) { + int type = netInfo.getType(); + if (K9MailLib.isDebug()) { + Log.d(LOG_TAG, "On network type " + type); + } + useCompression = mSettings.useCompression(type); + } + if (K9MailLib.isDebug()) { + Log.d(LOG_TAG, "useCompression " + useCompression); + } + return useCompression; + } + + private void enableCompression() { + try { + executeSimpleCommand(ImapCommands.COMMAND_COMPRESS_DEFLATE); + InflaterInputStream zInputStream = new InflaterInputStream(mSocket.getInputStream(), new Inflater(true)); + mIn = new PeekableInputStream(new BufferedInputStream(zInputStream, BUFFER_SIZE)); + mParser = new ImapResponseParser(mIn); + ZOutputStream zOutputStream = new ZOutputStream(mSocket.getOutputStream(), JZlib.Z_BEST_SPEED, true); + mOut = new BufferedOutputStream(zOutputStream, BUFFER_SIZE); + zOutputStream.setFlushMode(JZlib.Z_PARTIAL_FLUSH); + if (K9MailLib.isDebug()) { + Log.i(LOG_TAG, "Compression enabled for " + getLogId()); + } + } catch (Exception e) { + Log.e(LOG_TAG, "Unable to negotiate compression", e); + } + } + + private void authenticate(AuthType authType) throws MessagingException, IOException { + switch (authType) { + case CRAM_MD5: + if (hasCapability(CAPABILITY_AUTH_CRAM_MD5)) { + authCramMD5(); + } else { + throw new MessagingException("Server doesn't support encrypted passwords using CRAM-MD5."); + } + break; + + case PLAIN: + if (hasCapability(CAPABILITY_AUTH_PLAIN)) { + saslAuthPlain(); + } else if (!hasCapability(CAPABILITY_LOGINDISABLED)) { + login(); + } else { + throw new MessagingException( "Server doesn't support unencrypted passwords using AUTH=PLAIN and LOGIN is disabled."); + } + break; + + case EXTERNAL: + if (hasCapability(CAPABILITY_AUTH_EXTERNAL)) { + saslAuthExternal(); + } else { + // Provide notification to user of a problem authenticating using client certificates + throw new CertificateValidationException(CertificateValidationException.Reason.MissingCapability); + } + break; + default: + throw new MessagingException("Unhandled authentication method found in the server settings (bug)."); + } + } + + private void startTLS() throws IOException, MessagingException, GeneralSecurityException { + executeSimpleCommand("STARTTLS"); + mSocket = mSocketFactory.createSocket( + mSocket, + mSettings.getHost(), + mSettings.getPort(), + mSettings.getClientCertificateAlias()); + + mSocket.setSoTimeout(SOCKET_READ_TIMEOUT); + mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(), BUFFER_SIZE)); + mParser = new ImapResponseParser(mIn); + mOut = new BufferedOutputStream(mSocket.getOutputStream(), BUFFER_SIZE); + // Per RFC 2595 (3.1): Once TLS has been started, reissue CAPABILITY command + if (K9MailLib.isDebug()) { + Log.i(LOG_TAG, "Updating capabilities after STARTTLS for " + getLogId()); + } + capabilities.clear(); + List responses = receiveCapabilities(executeSimpleCommand(COMMAND_CAPABILITY)); + if (responses.size() != 2) { + throw new MessagingException("Invalid CAPABILITY response received"); + } + } + + private static Socket connect(ImapSettings settings, TrustedSocketFactory socketFactory) + throws GeneralSecurityException, MessagingException, IOException { + // Try all IPv4 and IPv6 addresses of the host + InetAddress[] addresses = InetAddress.getAllByName(settings.getHost()); + for (int i = 0; i < addresses.length; i++) { + try { + if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) { + Log.d(LOG_TAG, "Connecting to " + settings.getHost() + " as " + addresses[i]); + } + + SocketAddress socketAddress = new InetSocketAddress(addresses[i], settings.getPort()); + Socket socket; + if (settings.getConnectionSecurity() == ConnectionSecurity.SSL_TLS_REQUIRED) { + socket = socketFactory.createSocket( + null, + settings.getHost(), + settings.getPort(), + settings.getClientCertificateAlias()); + } else { + socket = new Socket(); + } + socket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT); + // Successfully connected to the server; don't try any other addresses + return socket; + } catch (SocketException e) { + if (i < (addresses.length - 1)) { + // There are still other addresses for that host to try + continue; + } + throw new MessagingException("Cannot connect to host", e); + } + } + throw new MessagingException("Cannot connect to host"); + } + + private void adjustDNSCacheTTL() { + try { + Security.setProperty("networkaddress.cache.ttl", "0"); + } catch (Exception e) { + Log.w(LOG_TAG, "Could not set DNS ttl to 0 for " + getLogId(), e); + } + try { + Security.setProperty("networkaddress.cache.negative.ttl", "0"); + } catch (Exception e) { + Log.w(LOG_TAG, "Could not set DNS negative ttl to 0 for " + getLogId(), e); + } + } + + private List receiveCapabilities(List responses) { + capabilities = ImapResponseParser.parseCapabilities(responses); + if (K9MailLib.isDebug()) { + Log.d(LOG_TAG, "Saving " + capabilities + " capabilities for " + getLogId()); + } + return responses; } } diff --git a/src/com/fsck/k9/mail/store/imap/ImapStore.java b/src/com/fsck/k9/mail/store/imap/ImapStore.java index 20450b6e0..6bf42431e 100644 --- a/src/com/fsck/k9/mail/store/imap/ImapStore.java +++ b/src/com/fsck/k9/mail/store/imap/ImapStore.java @@ -2945,13 +2945,13 @@ public class ImapStore extends RemoteStore { } @Override - public String getPathDelimeter() { + public String getPathDelimiter() { return mPathDelimiter; } @Override - public void setPathDelimeter(String delimeter) { - mPathDelimiter = delimeter; + public void setPathDelimiter(String delimiter) { + mPathDelimiter = delimiter; } @Override diff --git a/src/com/fsck/k9/mail/transport/imap/ImapSettings.java b/src/com/fsck/k9/mail/transport/imap/ImapSettings.java index 4423a2d38..115477840 100644 --- a/src/com/fsck/k9/mail/transport/imap/ImapSettings.java +++ b/src/com/fsck/k9/mail/transport/imap/ImapSettings.java @@ -28,9 +28,9 @@ public interface ImapSettings { void setPathPrefix(String prefix); - String getPathDelimeter(); + String getPathDelimiter(); - void setPathDelimeter(String delimeter); + void setPathDelimiter(String delimiter); String getCombinedPrefix();