Loading k9mail-library/src/main/java/com/fsck/k9/mail/store/imap/ImapConnection.java +183 −147 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ package com.fsck.k9.mail.store.imap; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ConnectException; import java.net.InetAddress; Loading @@ -12,10 +13,12 @@ import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.security.GeneralSecurityException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.Security; import java.security.cert.CertificateException; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Set; Loading @@ -27,7 +30,6 @@ 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; Loading Loading @@ -107,96 +109,51 @@ class ImapConnection { boolean authSuccess = false; nextCommandTag = 1; adjustDNSCacheTTL(); try { socket = connect(settings, socketFactory); setReadTimeout(socketReadTimeout); inputStream = new PeekableInputStream(new BufferedInputStream(socket.getInputStream(), BUFFER_SIZE)); responseParser = new ImapResponseParser(inputStream); outputStream = new BufferedOutputStream(socket.getOutputStream(), BUFFER_SIZE); capabilities.clear(); ImapResponse nullResponse = responseParser.readResponse(); if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) { Log.v(LOG_TAG, getLogId() + "<<<" + nullResponse); } List<ImapResponse> nullResponses = new LinkedList<ImapResponse>(); nullResponses.add(nullResponse); receiveCapabilities(nullResponses); adjustDNSCacheTTL(); if (capabilities.isEmpty()) { if (K9MailLib.isDebug()) { Log.i(LOG_TAG, "Did not get capabilities in banner, requesting CAPABILITY for " + getLogId()); } try { socket = connect(); configureSocket(); setUpStreamsAndParserFromSocket(); List<ImapResponse> responses = receiveCapabilities(executeSimpleCommand(COMMAND_CAPABILITY)); if (responses.size() != 2) { throw new MessagingException("Invalid CAPABILITY response received"); } } readInitialResponse(); requestCapabilitiesIfNecessary(); if (settings.getConnectionSecurity() == STARTTLS_REQUIRED) { if (hasCapability("STARTTLS")) { startTLS(); } else { /* * This exception triggers a "Certificate error" * notification that takes the user to the incoming * server settings for review. This might be needed if * the account was configured with an obsolete * "STARTTLS (if available)" setting. */ throw new CertificateValidationException("STARTTLS connection security not available"); } } upgradeToTlsIfNecessary(); authenticate(settings.getAuthType()); authenticate(); authSuccess = true; if (K9MailLib.isDebug()) { Log.d(LOG_TAG, CAPABILITY_COMPRESS_DEFLATE + " = " + hasCapability(CAPABILITY_COMPRESS_DEFLATE)); } enableCompressionIfRequested(); if (hasCapability(CAPABILITY_COMPRESS_DEFLATE) && shouldEnableCompression()) { enableCompression(); } if (K9MailLib.isDebug()) { Log.d(LOG_TAG, "NAMESPACE = " + hasCapability(CAPABILITY_NAMESPACE) + ", mPathPrefix = " + settings.getPathPrefix()); } retrievePathPrefixIfNecessary(); retrievePathDelimiterIfNecessary(); if (settings.getPathPrefix() == null) { if (hasCapability(CAPABILITY_NAMESPACE)) { if (K9MailLib.isDebug()) { Log.i(LOG_TAG, "pathPrefix is unset and server has NAMESPACE capability"); } handleNamespace(); } else { if (K9MailLib.isDebug()) { Log.i(LOG_TAG, "pathPrefix is unset but server does not have NAMESPACE capability"); } catch (SSLException e) { handleSslException(e); } catch (ConnectException e) { handleConnectException(e); } catch (GeneralSecurityException e) { throw new MessagingException("Unable to open connection to IMAP server due to security error.", e); } finally { if (!authSuccess) { Log.e(LOG_TAG, "Failed to login, closing connection for " + getLogId()); close(); } settings.setPathPrefix(""); } } if (settings.getPathDelimiter() == null) { getPathDelimiter(); } } catch (SSLException e) { private void handleSslException(SSLException e) throws CertificateValidationException, SSLException { if (e.getCause() instanceof CertificateException) { throw new CertificateValidationException(e.getMessage(), e); } else { throw e; } } catch (GeneralSecurityException e) { throw new MessagingException("Unable to open connection to IMAP server due to security error.", e); } catch (ConnectException e) { } private void handleConnectException(ConnectException e) throws ConnectException { String message = e.getMessage(); String[] tokens = message.split("-"); Loading @@ -206,12 +163,6 @@ class ImapConnection { } else { throw e; } } finally { if (!authSuccess) { Log.e(LOG_TAG, "Failed to login, closing connection for " + getLogId()); close(); } } } public boolean isOpen() { Loading @@ -233,17 +184,29 @@ class ImapConnection { } } private Socket connect(ImapSettings settings, TrustedSocketFactory socketFactory) throws GeneralSecurityException, MessagingException, IOException { private Socket connect() throws GeneralSecurityException, MessagingException, IOException { Exception connectException = null; InetAddress[] inetAddresses = InetAddress.getAllByName(settings.getHost()); for (InetAddress address : inetAddresses) { try { return connectToAddress(address); } catch (IOException e) { Log.w(LOG_TAG, "Could not connect to " + address, e); connectException = e; } } throw new MessagingException("Cannot connect to host", connectException); } private Socket connectToAddress(InetAddress address) throws NoSuchAlgorithmException, KeyManagementException, MessagingException, IOException { String host = settings.getHost(); int port = settings.getPort(); String clientCertificateAlias = settings.getClientCertificateAlias(); InetAddress[] inetAddresses = InetAddress.getAllByName(host); for (InetAddress address : inetAddresses) { try { if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) { Log.d(LOG_TAG, "Connecting to " + host + " as " + address); } Loading @@ -259,64 +222,109 @@ class ImapConnection { socket.connect(socketAddress, socketConnectTimeout); // Successfully connected to the server; don't try any other addresses return socket; } catch (IOException e) { Log.w(LOG_TAG, "could not connect to " + address, e); connectException = e; } private void configureSocket() throws SocketException { socket.setSoTimeout(socketReadTimeout); } throw new MessagingException("Cannot connect to host", connectException); private void setUpStreamsAndParserFromSocket() throws IOException { setUpStreamsAndParser(socket.getInputStream(), socket.getOutputStream()); } private void startTLS() throws IOException, MessagingException, GeneralSecurityException { executeSimpleCommand("STARTTLS"); private void setUpStreamsAndParser(InputStream input, OutputStream output) { inputStream = new PeekableInputStream(new BufferedInputStream(input, BUFFER_SIZE)); responseParser = new ImapResponseParser(inputStream); outputStream = new BufferedOutputStream(output, BUFFER_SIZE); } String host = settings.getHost(); int port = settings.getPort(); String clientCertificateAlias = settings.getClientCertificateAlias(); private void readInitialResponse() throws IOException { ImapResponse initialResponse = responseParser.readResponse(); socket = socketFactory.createSocket(socket, host, port, clientCertificateAlias); socket.setSoTimeout(socketReadTimeout); if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) { Log.v(LOG_TAG, getLogId() + "<<<" + initialResponse); } inputStream = new PeekableInputStream(new BufferedInputStream(socket.getInputStream(), BUFFER_SIZE)); responseParser = new ImapResponseParser(inputStream); outputStream = new BufferedOutputStream(socket.getOutputStream(), BUFFER_SIZE); extractCapabilities(Collections.singletonList(initialResponse)); } private List<ImapResponse> extractCapabilities(List<ImapResponse> responses) { Set<String> receivedCapabilities = ImapResponseParser.parseCapabilities(responses); // 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()); Log.d(LOG_TAG, "Saving " + receivedCapabilities + " capabilities for " + getLogId()); } capabilities.addAll(receivedCapabilities); return responses; } private void requestCapabilitiesIfNecessary() throws IOException, MessagingException { if (!capabilities.isEmpty()) { return; } if (K9MailLib.isDebug()) { Log.i(LOG_TAG, "Did not get capabilities in banner, requesting CAPABILITY for " + getLogId()); } requestCapabilities(); } private void requestCapabilities() throws IOException, MessagingException { capabilities.clear(); List<ImapResponse> responses = receiveCapabilities(executeSimpleCommand(COMMAND_CAPABILITY)); List<ImapResponse> responses = extractCapabilities(executeSimpleCommand(COMMAND_CAPABILITY)); if (responses.size() != 2) { throw new MessagingException("Invalid CAPABILITY response received"); } } private List<ImapResponse> receiveCapabilities(List<ImapResponse> responses) { Set<String> receivedCapabilities = ImapResponseParser.parseCapabilities(responses); private void upgradeToTlsIfNecessary() throws IOException, MessagingException, GeneralSecurityException { if (settings.getConnectionSecurity() == STARTTLS_REQUIRED) { upgradeToTls(); } } /* RFC 3501 6.2.3 A server MAY include a CAPABILITY response code in the tagged OK response to a successful LOGIN command in order to send capabilities automatically. It is unnecessary for a client to send a separate CAPABILITY command if it recognizes these automatic capabilities. private void upgradeToTls() throws IOException, MessagingException, GeneralSecurityException { if (!hasCapability("STARTTLS")) { /* * This exception triggers a "Certificate error" * notification that takes the user to the incoming * server settings for review. This might be needed if * the account was configured with an obsolete * "STARTTLS (if available)" setting. */ if (K9MailLib.isDebug()) { Log.d(LOG_TAG, "Saving " + receivedCapabilities + " capabilities for " + getLogId()); throw new CertificateValidationException("STARTTLS connection security not available"); } capabilities.addAll(receivedCapabilities); startTLS(); } return responses; private void startTLS() throws IOException, MessagingException, GeneralSecurityException { executeSimpleCommand("STARTTLS"); String host = settings.getHost(); int port = settings.getPort(); String clientCertificateAlias = settings.getClientCertificateAlias(); socket = socketFactory.createSocket(socket, host, port, clientCertificateAlias); configureSocket(); setUpStreamsAndParserFromSocket(); // 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()); } private void authenticate(AuthType authType) throws MessagingException, IOException { switch (authType) { requestCapabilities(); } @SuppressWarnings("EnumSwitchStatementWhichMissesCases") private void authenticate() throws MessagingException, IOException { switch (settings.getAuthType()) { case CRAM_MD5: { if (hasCapability(CAPABILITY_AUTH_CRAM_MD5)) { authCramMD5(); Loading Loading @@ -369,7 +377,7 @@ class ImapConnection { outputStream.flush(); try { receiveCapabilities(responseParser.readStatusResponse(tag, command, getLogId(), null)); extractCapabilities(responseParser.readStatusResponse(tag, command, getLogId(), null)); } catch (MessagingException e) { throw new AuthenticationFailedException(e.getMessage()); } Loading Loading @@ -398,7 +406,7 @@ class ImapConnection { outputStream.flush(); try { receiveCapabilities(responseParser.readStatusResponse(tag, command, getLogId(), null)); extractCapabilities(responseParser.readStatusResponse(tag, command, getLogId(), null)); } catch (MessagingException e) { throw new AuthenticationFailedException(e.getMessage()); } Loading @@ -418,7 +426,7 @@ class ImapConnection { String password = p.matcher(settings.getPassword()).replaceAll(replacement); try { receiveCapabilities(executeSimpleCommand(String.format("LOGIN \"%s\" \"%s\"", username, password), true)); extractCapabilities(executeSimpleCommand(String.format("LOGIN \"%s\" \"%s\"", username, password), true)); } catch (ImapException e) { throw new AuthenticationFailedException(e.getMessage()); } Loading @@ -426,7 +434,7 @@ class ImapConnection { private void saslAuthExternal() throws IOException, MessagingException { try { receiveCapabilities(executeSimpleCommand( extractCapabilities(executeSimpleCommand( String.format("AUTHENTICATE EXTERNAL %s", Base64.encode(settings.getUsername())), false)); } catch (ImapException e) { /* Loading @@ -440,6 +448,12 @@ class ImapConnection { } } private void enableCompressionIfRequested() throws IOException, MessagingException { if (hasCapability(CAPABILITY_COMPRESS_DEFLATE) && shouldEnableCompression()) { enableCompression(); } } private boolean shouldEnableCompression() { boolean useCompression = true; Loading Loading @@ -470,13 +484,11 @@ class ImapConnection { } try { InflaterInputStream zInputStream = new InflaterInputStream(socket.getInputStream(), new Inflater(true)); inputStream = new PeekableInputStream(new BufferedInputStream(zInputStream, BUFFER_SIZE)); responseParser = new ImapResponseParser(inputStream); InflaterInputStream input = new InflaterInputStream(socket.getInputStream(), new Inflater(true)); ZOutputStream output = new ZOutputStream(socket.getOutputStream(), JZlib.Z_BEST_SPEED, true); output.setFlushMode(JZlib.Z_PARTIAL_FLUSH); ZOutputStream zOutputStream = new ZOutputStream(socket.getOutputStream(), JZlib.Z_BEST_SPEED, true); outputStream = new BufferedOutputStream(zOutputStream, BUFFER_SIZE); zOutputStream.setFlushMode(JZlib.Z_PARTIAL_FLUSH); setUpStreamsAndParser(input, output); if (K9MailLib.isDebug()) { Log.i(LOG_TAG, "Compression enabled for " + getLogId()); Loading @@ -487,6 +499,24 @@ class ImapConnection { } } private void retrievePathPrefixIfNecessary() throws IOException, MessagingException { if (settings.getPathPrefix() != null) { return; } if (hasCapability(CAPABILITY_NAMESPACE)) { if (K9MailLib.isDebug()) { Log.i(LOG_TAG, "pathPrefix is unset and server has NAMESPACE capability"); } handleNamespace(); } else { if (K9MailLib.isDebug()) { Log.i(LOG_TAG, "pathPrefix is unset but server does not have NAMESPACE capability"); } settings.setPathPrefix(""); } } private void handleNamespace() throws IOException, MessagingException { List<ImapResponse> responses = executeSimpleCommand(COMMAND_NAMESPACE); Loading Loading @@ -525,7 +555,13 @@ class ImapConnection { } } private void getPathDelimiter() throws IOException, MessagingException { private void retrievePathDelimiterIfNecessary() throws IOException, MessagingException { if (settings.getPathDelimiter() == null) { retrievePathDelimiter(); } } private void retrievePathDelimiter() throws IOException, MessagingException { List<ImapResponse> listResponses; try { listResponses = executeSimpleCommand("LIST \"\" \"\""); Loading Loading
k9mail-library/src/main/java/com/fsck/k9/mail/store/imap/ImapConnection.java +183 −147 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ package com.fsck.k9.mail.store.imap; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ConnectException; import java.net.InetAddress; Loading @@ -12,10 +13,12 @@ import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.security.GeneralSecurityException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.Security; import java.security.cert.CertificateException; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Set; Loading @@ -27,7 +30,6 @@ 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; Loading Loading @@ -107,96 +109,51 @@ class ImapConnection { boolean authSuccess = false; nextCommandTag = 1; adjustDNSCacheTTL(); try { socket = connect(settings, socketFactory); setReadTimeout(socketReadTimeout); inputStream = new PeekableInputStream(new BufferedInputStream(socket.getInputStream(), BUFFER_SIZE)); responseParser = new ImapResponseParser(inputStream); outputStream = new BufferedOutputStream(socket.getOutputStream(), BUFFER_SIZE); capabilities.clear(); ImapResponse nullResponse = responseParser.readResponse(); if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) { Log.v(LOG_TAG, getLogId() + "<<<" + nullResponse); } List<ImapResponse> nullResponses = new LinkedList<ImapResponse>(); nullResponses.add(nullResponse); receiveCapabilities(nullResponses); adjustDNSCacheTTL(); if (capabilities.isEmpty()) { if (K9MailLib.isDebug()) { Log.i(LOG_TAG, "Did not get capabilities in banner, requesting CAPABILITY for " + getLogId()); } try { socket = connect(); configureSocket(); setUpStreamsAndParserFromSocket(); List<ImapResponse> responses = receiveCapabilities(executeSimpleCommand(COMMAND_CAPABILITY)); if (responses.size() != 2) { throw new MessagingException("Invalid CAPABILITY response received"); } } readInitialResponse(); requestCapabilitiesIfNecessary(); if (settings.getConnectionSecurity() == STARTTLS_REQUIRED) { if (hasCapability("STARTTLS")) { startTLS(); } else { /* * This exception triggers a "Certificate error" * notification that takes the user to the incoming * server settings for review. This might be needed if * the account was configured with an obsolete * "STARTTLS (if available)" setting. */ throw new CertificateValidationException("STARTTLS connection security not available"); } } upgradeToTlsIfNecessary(); authenticate(settings.getAuthType()); authenticate(); authSuccess = true; if (K9MailLib.isDebug()) { Log.d(LOG_TAG, CAPABILITY_COMPRESS_DEFLATE + " = " + hasCapability(CAPABILITY_COMPRESS_DEFLATE)); } enableCompressionIfRequested(); if (hasCapability(CAPABILITY_COMPRESS_DEFLATE) && shouldEnableCompression()) { enableCompression(); } if (K9MailLib.isDebug()) { Log.d(LOG_TAG, "NAMESPACE = " + hasCapability(CAPABILITY_NAMESPACE) + ", mPathPrefix = " + settings.getPathPrefix()); } retrievePathPrefixIfNecessary(); retrievePathDelimiterIfNecessary(); if (settings.getPathPrefix() == null) { if (hasCapability(CAPABILITY_NAMESPACE)) { if (K9MailLib.isDebug()) { Log.i(LOG_TAG, "pathPrefix is unset and server has NAMESPACE capability"); } handleNamespace(); } else { if (K9MailLib.isDebug()) { Log.i(LOG_TAG, "pathPrefix is unset but server does not have NAMESPACE capability"); } catch (SSLException e) { handleSslException(e); } catch (ConnectException e) { handleConnectException(e); } catch (GeneralSecurityException e) { throw new MessagingException("Unable to open connection to IMAP server due to security error.", e); } finally { if (!authSuccess) { Log.e(LOG_TAG, "Failed to login, closing connection for " + getLogId()); close(); } settings.setPathPrefix(""); } } if (settings.getPathDelimiter() == null) { getPathDelimiter(); } } catch (SSLException e) { private void handleSslException(SSLException e) throws CertificateValidationException, SSLException { if (e.getCause() instanceof CertificateException) { throw new CertificateValidationException(e.getMessage(), e); } else { throw e; } } catch (GeneralSecurityException e) { throw new MessagingException("Unable to open connection to IMAP server due to security error.", e); } catch (ConnectException e) { } private void handleConnectException(ConnectException e) throws ConnectException { String message = e.getMessage(); String[] tokens = message.split("-"); Loading @@ -206,12 +163,6 @@ class ImapConnection { } else { throw e; } } finally { if (!authSuccess) { Log.e(LOG_TAG, "Failed to login, closing connection for " + getLogId()); close(); } } } public boolean isOpen() { Loading @@ -233,17 +184,29 @@ class ImapConnection { } } private Socket connect(ImapSettings settings, TrustedSocketFactory socketFactory) throws GeneralSecurityException, MessagingException, IOException { private Socket connect() throws GeneralSecurityException, MessagingException, IOException { Exception connectException = null; InetAddress[] inetAddresses = InetAddress.getAllByName(settings.getHost()); for (InetAddress address : inetAddresses) { try { return connectToAddress(address); } catch (IOException e) { Log.w(LOG_TAG, "Could not connect to " + address, e); connectException = e; } } throw new MessagingException("Cannot connect to host", connectException); } private Socket connectToAddress(InetAddress address) throws NoSuchAlgorithmException, KeyManagementException, MessagingException, IOException { String host = settings.getHost(); int port = settings.getPort(); String clientCertificateAlias = settings.getClientCertificateAlias(); InetAddress[] inetAddresses = InetAddress.getAllByName(host); for (InetAddress address : inetAddresses) { try { if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) { Log.d(LOG_TAG, "Connecting to " + host + " as " + address); } Loading @@ -259,64 +222,109 @@ class ImapConnection { socket.connect(socketAddress, socketConnectTimeout); // Successfully connected to the server; don't try any other addresses return socket; } catch (IOException e) { Log.w(LOG_TAG, "could not connect to " + address, e); connectException = e; } private void configureSocket() throws SocketException { socket.setSoTimeout(socketReadTimeout); } throw new MessagingException("Cannot connect to host", connectException); private void setUpStreamsAndParserFromSocket() throws IOException { setUpStreamsAndParser(socket.getInputStream(), socket.getOutputStream()); } private void startTLS() throws IOException, MessagingException, GeneralSecurityException { executeSimpleCommand("STARTTLS"); private void setUpStreamsAndParser(InputStream input, OutputStream output) { inputStream = new PeekableInputStream(new BufferedInputStream(input, BUFFER_SIZE)); responseParser = new ImapResponseParser(inputStream); outputStream = new BufferedOutputStream(output, BUFFER_SIZE); } String host = settings.getHost(); int port = settings.getPort(); String clientCertificateAlias = settings.getClientCertificateAlias(); private void readInitialResponse() throws IOException { ImapResponse initialResponse = responseParser.readResponse(); socket = socketFactory.createSocket(socket, host, port, clientCertificateAlias); socket.setSoTimeout(socketReadTimeout); if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) { Log.v(LOG_TAG, getLogId() + "<<<" + initialResponse); } inputStream = new PeekableInputStream(new BufferedInputStream(socket.getInputStream(), BUFFER_SIZE)); responseParser = new ImapResponseParser(inputStream); outputStream = new BufferedOutputStream(socket.getOutputStream(), BUFFER_SIZE); extractCapabilities(Collections.singletonList(initialResponse)); } private List<ImapResponse> extractCapabilities(List<ImapResponse> responses) { Set<String> receivedCapabilities = ImapResponseParser.parseCapabilities(responses); // 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()); Log.d(LOG_TAG, "Saving " + receivedCapabilities + " capabilities for " + getLogId()); } capabilities.addAll(receivedCapabilities); return responses; } private void requestCapabilitiesIfNecessary() throws IOException, MessagingException { if (!capabilities.isEmpty()) { return; } if (K9MailLib.isDebug()) { Log.i(LOG_TAG, "Did not get capabilities in banner, requesting CAPABILITY for " + getLogId()); } requestCapabilities(); } private void requestCapabilities() throws IOException, MessagingException { capabilities.clear(); List<ImapResponse> responses = receiveCapabilities(executeSimpleCommand(COMMAND_CAPABILITY)); List<ImapResponse> responses = extractCapabilities(executeSimpleCommand(COMMAND_CAPABILITY)); if (responses.size() != 2) { throw new MessagingException("Invalid CAPABILITY response received"); } } private List<ImapResponse> receiveCapabilities(List<ImapResponse> responses) { Set<String> receivedCapabilities = ImapResponseParser.parseCapabilities(responses); private void upgradeToTlsIfNecessary() throws IOException, MessagingException, GeneralSecurityException { if (settings.getConnectionSecurity() == STARTTLS_REQUIRED) { upgradeToTls(); } } /* RFC 3501 6.2.3 A server MAY include a CAPABILITY response code in the tagged OK response to a successful LOGIN command in order to send capabilities automatically. It is unnecessary for a client to send a separate CAPABILITY command if it recognizes these automatic capabilities. private void upgradeToTls() throws IOException, MessagingException, GeneralSecurityException { if (!hasCapability("STARTTLS")) { /* * This exception triggers a "Certificate error" * notification that takes the user to the incoming * server settings for review. This might be needed if * the account was configured with an obsolete * "STARTTLS (if available)" setting. */ if (K9MailLib.isDebug()) { Log.d(LOG_TAG, "Saving " + receivedCapabilities + " capabilities for " + getLogId()); throw new CertificateValidationException("STARTTLS connection security not available"); } capabilities.addAll(receivedCapabilities); startTLS(); } return responses; private void startTLS() throws IOException, MessagingException, GeneralSecurityException { executeSimpleCommand("STARTTLS"); String host = settings.getHost(); int port = settings.getPort(); String clientCertificateAlias = settings.getClientCertificateAlias(); socket = socketFactory.createSocket(socket, host, port, clientCertificateAlias); configureSocket(); setUpStreamsAndParserFromSocket(); // 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()); } private void authenticate(AuthType authType) throws MessagingException, IOException { switch (authType) { requestCapabilities(); } @SuppressWarnings("EnumSwitchStatementWhichMissesCases") private void authenticate() throws MessagingException, IOException { switch (settings.getAuthType()) { case CRAM_MD5: { if (hasCapability(CAPABILITY_AUTH_CRAM_MD5)) { authCramMD5(); Loading Loading @@ -369,7 +377,7 @@ class ImapConnection { outputStream.flush(); try { receiveCapabilities(responseParser.readStatusResponse(tag, command, getLogId(), null)); extractCapabilities(responseParser.readStatusResponse(tag, command, getLogId(), null)); } catch (MessagingException e) { throw new AuthenticationFailedException(e.getMessage()); } Loading Loading @@ -398,7 +406,7 @@ class ImapConnection { outputStream.flush(); try { receiveCapabilities(responseParser.readStatusResponse(tag, command, getLogId(), null)); extractCapabilities(responseParser.readStatusResponse(tag, command, getLogId(), null)); } catch (MessagingException e) { throw new AuthenticationFailedException(e.getMessage()); } Loading @@ -418,7 +426,7 @@ class ImapConnection { String password = p.matcher(settings.getPassword()).replaceAll(replacement); try { receiveCapabilities(executeSimpleCommand(String.format("LOGIN \"%s\" \"%s\"", username, password), true)); extractCapabilities(executeSimpleCommand(String.format("LOGIN \"%s\" \"%s\"", username, password), true)); } catch (ImapException e) { throw new AuthenticationFailedException(e.getMessage()); } Loading @@ -426,7 +434,7 @@ class ImapConnection { private void saslAuthExternal() throws IOException, MessagingException { try { receiveCapabilities(executeSimpleCommand( extractCapabilities(executeSimpleCommand( String.format("AUTHENTICATE EXTERNAL %s", Base64.encode(settings.getUsername())), false)); } catch (ImapException e) { /* Loading @@ -440,6 +448,12 @@ class ImapConnection { } } private void enableCompressionIfRequested() throws IOException, MessagingException { if (hasCapability(CAPABILITY_COMPRESS_DEFLATE) && shouldEnableCompression()) { enableCompression(); } } private boolean shouldEnableCompression() { boolean useCompression = true; Loading Loading @@ -470,13 +484,11 @@ class ImapConnection { } try { InflaterInputStream zInputStream = new InflaterInputStream(socket.getInputStream(), new Inflater(true)); inputStream = new PeekableInputStream(new BufferedInputStream(zInputStream, BUFFER_SIZE)); responseParser = new ImapResponseParser(inputStream); InflaterInputStream input = new InflaterInputStream(socket.getInputStream(), new Inflater(true)); ZOutputStream output = new ZOutputStream(socket.getOutputStream(), JZlib.Z_BEST_SPEED, true); output.setFlushMode(JZlib.Z_PARTIAL_FLUSH); ZOutputStream zOutputStream = new ZOutputStream(socket.getOutputStream(), JZlib.Z_BEST_SPEED, true); outputStream = new BufferedOutputStream(zOutputStream, BUFFER_SIZE); zOutputStream.setFlushMode(JZlib.Z_PARTIAL_FLUSH); setUpStreamsAndParser(input, output); if (K9MailLib.isDebug()) { Log.i(LOG_TAG, "Compression enabled for " + getLogId()); Loading @@ -487,6 +499,24 @@ class ImapConnection { } } private void retrievePathPrefixIfNecessary() throws IOException, MessagingException { if (settings.getPathPrefix() != null) { return; } if (hasCapability(CAPABILITY_NAMESPACE)) { if (K9MailLib.isDebug()) { Log.i(LOG_TAG, "pathPrefix is unset and server has NAMESPACE capability"); } handleNamespace(); } else { if (K9MailLib.isDebug()) { Log.i(LOG_TAG, "pathPrefix is unset but server does not have NAMESPACE capability"); } settings.setPathPrefix(""); } } private void handleNamespace() throws IOException, MessagingException { List<ImapResponse> responses = executeSimpleCommand(COMMAND_NAMESPACE); Loading Loading @@ -525,7 +555,13 @@ class ImapConnection { } } private void getPathDelimiter() throws IOException, MessagingException { private void retrievePathDelimiterIfNecessary() throws IOException, MessagingException { if (settings.getPathDelimiter() == null) { retrievePathDelimiter(); } } private void retrievePathDelimiter() throws IOException, MessagingException { List<ImapResponse> listResponses; try { listResponses = executeSimpleCommand("LIST \"\" \"\""); Loading