Esempio n. 1
0
  private List<String> executeSimpleCommand(String command, boolean sensitive)
      throws IOException, MessagingException {
    List<String> results = new ArrayList<String>();
    if (command != null) {
      writeLine(command, sensitive);
    }

    /*
     * Read lines as long as the length is 4 or larger, e.g. "220-banner text here".
     * Shorter lines are either errors of contain only a reply code. Those cases will
     * be handled by checkLine() below.
     */
    String line = readLine();
    while (line.length() >= 4) {
      if (line.length() > 4) {
        // Everything after the first four characters goes into the results array.
        results.add(line.substring(4));
      }

      if (line.charAt(3) != '-') {
        // If the fourth character isn't "-" this is the last line of the response.
        break;
      }
      line = readLine();
    }

    // Check if the reply code indicates an error.
    checkLine(line);

    return results;
  }
Esempio n. 2
0
  private void saslAuthCramMD5(String username, String password)
      throws MessagingException, AuthenticationFailedException, IOException {

    List<String> respList = executeSimpleCommand("AUTH CRAM-MD5");
    if (respList.size() != 1) {
      throw new AuthenticationFailedException("Unable to negotiate CRAM-MD5");
    }

    String b64Nonce = respList.get(0);
    String b64CRAMString = Authentication.computeCramMd5(mUsername, mPassword, b64Nonce);

    try {
      executeSimpleCommand(b64CRAMString, true);
    } catch (MessagingException me) {
      throw new AuthenticationFailedException("Unable to negotiate MD5 CRAM");
    }
  }
Esempio n. 3
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);
    }
  }