@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();
    }
Esempio n. 2
0
  @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);
    }
  }