@Override public synchronized void open(int mode) throws MessagingException { if (isOpen()) { return; } if (!mName.equalsIgnoreCase(mStoreConfig.getInboxFolderName())) { throw new MessagingException("Folder does not exist"); } try { SocketAddress socketAddress = new InetSocketAddress(mHost, mPort); if (mConnectionSecurity == ConnectionSecurity.SSL_TLS_REQUIRED) { mSocket = mTrustedSocketFactory.createSocket(null, mHost, mPort, mClientCertificateAlias); } else { mSocket = new Socket(); } mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT); mIn = new BufferedInputStream(mSocket.getInputStream(), 1024); mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512); mSocket.setSoTimeout(SOCKET_READ_TIMEOUT); if (!isOpen()) { throw new MessagingException("Unable to connect socket"); } String serverGreeting = executeSimpleCommand(null); mCapabilities = getCapabilities(); if (mConnectionSecurity == ConnectionSecurity.STARTTLS_REQUIRED) { if (mCapabilities.stls) { executeSimpleCommand(STLS_COMMAND); mSocket = mTrustedSocketFactory.createSocket(mSocket, mHost, mPort, mClientCertificateAlias); mSocket.setSoTimeout(SOCKET_READ_TIMEOUT); mIn = new BufferedInputStream(mSocket.getInputStream(), 1024); mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512); if (!isOpen()) { throw new MessagingException("Unable to connect socket"); } mCapabilities = getCapabilities(); } 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"); } } switch (mAuthType) { case PLAIN: if (mCapabilities.authPlain) { authPlain(); } else { login(); } break; case CRAM_MD5: if (mCapabilities.cramMD5) { authCramMD5(); } else { authAPOP(serverGreeting); } break; case EXTERNAL: if (mCapabilities.external) { authExternal(); } else { // Provide notification to user of a problem authenticating using client certificates throw new CertificateValidationException(MissingCapability); } break; default: throw new MessagingException( "Unhandled authentication method found in the server settings (bug)."); } } catch (SSLException e) { if (e.getCause() instanceof CertificateException) { throw new CertificateValidationException(e.getMessage(), e); } else { throw new MessagingException("Unable to connect", e); } } catch (GeneralSecurityException gse) { throw new MessagingException( "Unable to open connection to POP server due to security error.", gse); } catch (IOException ioe) { throw new MessagingException("Unable to open connection to POP server.", ioe); } String response = executeSimpleCommand(STAT_COMMAND); String[] parts = response.split(" "); mMessageCount = Integer.parseInt(parts[1]); mUidToMsgMap.clear(); mMsgNumToMsgMap.clear(); mUidToMsgNumMap.clear(); }
@Override public void open() throws MessagingException { try { InetAddress[] addresses = InetAddress.getAllByName(mHost); for (int i = 0; i < addresses.length; i++) { try { SocketAddress socketAddress = new InetSocketAddress(addresses[i], mPort); if (mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED || mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) { SSLContext sslContext = SSLContext.getInstance("TLS"); boolean secure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED; sslContext.init( null, new TrustManager[] {TrustManagerFactory.get(mHost, secure)}, new SecureRandom()); mSocket = sslContext.getSocketFactory().createSocket(); mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT); mSecure = true; } else { mSocket = new Socket(); mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT); } } catch (ConnectException 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); } break; // connection success } // RFC 1047 mSocket.setSoTimeout(SOCKET_READ_TIMEOUT); mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(), 1024)); mOut = mSocket.getOutputStream(); // Eat the banner executeSimpleCommand(null); InetAddress localAddress = mSocket.getLocalAddress(); String localHost = localAddress.getCanonicalHostName(); String ipAddr = localAddress.getHostAddress(); if (localHost.equals("") || localHost.equals(ipAddr) || localHost.contains("_")) { // We don't have a FQDN or the hostname contains invalid // characters (see issue 2143), so use IP address. if (!ipAddr.equals("")) { if (localAddress instanceof Inet6Address) { localHost = "[IPV6:" + ipAddr + "]"; } else { localHost = "[" + ipAddr + "]"; } } else { // If the IP address is no good, set a sane default (see issue 2750). localHost = "android"; } } List<String> results = executeSimpleCommand("EHLO " + localHost); m8bitEncodingAllowed = results.contains("8BITMIME"); /* * TODO may need to add code to fall back to HELO I switched it from * using HELO on non STARTTLS connections because of AOL's mail * server. It won't let you use AUTH without EHLO. * We should really be paying more attention to the capabilities * and only attempting auth if it's available, and warning the user * if not. */ if (mConnectionSecurity == CONNECTION_SECURITY_TLS_OPTIONAL || mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED) { if (results.contains("STARTTLS")) { executeSimpleCommand("STARTTLS"); SSLContext sslContext = SSLContext.getInstance("TLS"); boolean secure = mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED; sslContext.init( null, new TrustManager[] {TrustManagerFactory.get(mHost, secure)}, new SecureRandom()); mSocket = sslContext.getSocketFactory().createSocket(mSocket, mHost, mPort, true); mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(), 1024)); mOut = mSocket.getOutputStream(); mSecure = true; /* * Now resend the EHLO. Required by RFC2487 Sec. 5.2, and more specifically, * Exim. */ results = executeSimpleCommand("EHLO " + localHost); } else if (mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED) { throw new MessagingException("TLS not supported but required"); } } boolean useAuthLogin = AUTH_LOGIN.equals(mAuthType); boolean useAuthPlain = AUTH_PLAIN.equals(mAuthType); boolean useAuthCramMD5 = AUTH_CRAM_MD5.equals(mAuthType); // Automatically choose best authentication method if none was explicitly selected boolean useAutomaticAuth = !(useAuthLogin || useAuthPlain || useAuthCramMD5); boolean authLoginSupported = false; boolean authPlainSupported = false; boolean authCramMD5Supported = false; for (String result : results) { if (result.matches(".*AUTH.*LOGIN.*$")) { authLoginSupported = true; } if (result.matches(".*AUTH.*PLAIN.*$")) { authPlainSupported = true; } if (result.matches(".*AUTH.*CRAM-MD5.*$")) { authCramMD5Supported = true; } if (result.matches(".*SIZE \\d*$")) { try { mLargestAcceptableMessage = Integer.parseInt(result.substring(result.lastIndexOf(' ') + 1)); } catch (Exception e) { if (K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) { Log.d( K9.LOG_TAG, "Tried to parse " + result + " and get an int out of the last word", e); } } } } if (mUsername != null && mUsername.length() > 0 && mPassword != null && mPassword.length() > 0) { if (useAuthCramMD5 || (useAutomaticAuth && authCramMD5Supported)) { if (!authCramMD5Supported && K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) { Log.d( K9.LOG_TAG, "Using CRAM_MD5 as authentication method although the " + "server didn't advertise support for it in EHLO response."); } saslAuthCramMD5(mUsername, mPassword); } else if (useAuthPlain || (useAutomaticAuth && authPlainSupported)) { if (!authPlainSupported && K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) { Log.d( K9.LOG_TAG, "Using PLAIN as authentication method although the " + "server didn't advertise support for it in EHLO response."); } try { saslAuthPlain(mUsername, mPassword); } catch (MessagingException ex) { // PLAIN is a special case. Historically, PLAIN has represented both PLAIN and LOGIN; // only the // protocol being advertised by the server would be used, with PLAIN taking precedence. // Instead // of using only the requested protocol, we'll try PLAIN and then try LOGIN. if (useAuthPlain && authLoginSupported) { if (K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) { Log.d(K9.LOG_TAG, "Using legacy PLAIN authentication behavior and trying LOGIN."); } saslAuthLogin(mUsername, mPassword); } else { // If it was auto detected and failed, continue throwing the exception back up. throw ex; } } } else if (useAuthLogin || (useAutomaticAuth && authLoginSupported)) { if (!authPlainSupported && K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) { Log.d( K9.LOG_TAG, "Using LOGIN as authentication method although the " + "server didn't advertise support for it in EHLO response."); } saslAuthLogin(mUsername, mPassword); } else { throw new MessagingException("No valid authentication mechanism found."); } } } catch (SSLException e) { throw new CertificateValidationException(e.getMessage(), e); } catch (GeneralSecurityException gse) { throw new MessagingException( "Unable to open connection to SMTP server due to security error.", gse); } catch (IOException ioe) { throw new MessagingException("Unable to open connection to SMTP server.", ioe); } }