SMTPSClient.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.net.smtp;

  18. import java.io.BufferedWriter;
  19. import java.io.IOException;
  20. import java.io.InputStreamReader;
  21. import java.io.OutputStreamWriter;

  22. import javax.net.ssl.HostnameVerifier;
  23. import javax.net.ssl.KeyManager;
  24. import javax.net.ssl.SSLContext;
  25. import javax.net.ssl.SSLHandshakeException;
  26. import javax.net.ssl.SSLSocket;
  27. import javax.net.ssl.SSLSocketFactory;
  28. import javax.net.ssl.TrustManager;

  29. import org.apache.commons.net.io.CRLFLineReader;
  30. import org.apache.commons.net.util.SSLContextUtils;
  31. import org.apache.commons.net.util.SSLSocketUtils;

  32. /**
  33.  * SMTP over SSL processing. Copied from FTPSClient.java and modified to suit SMTP. If implicit mode is selected (NOT the default), SSL/TLS negotiation starts
  34.  * right after the connection has been established. In explicit mode (the default), SSL/TLS negotiation starts when the user calls execTLS() and the server
  35.  * accepts the command. Implicit usage:
  36.  *
  37.  * <pre>
  38.  * SMTPSClient c = new SMTPSClient(true);
  39.  * c.connect("127.0.0.1", 465);
  40.  * </pre>
  41.  *
  42.  * Explicit usage:
  43.  *
  44.  * <pre>
  45.  * SMTPSClient c = new SMTPSClient();
  46.  * c.connect("127.0.0.1", 25);
  47.  * if (c.execTLS()) {
  48.  *     // Rest of the commands here
  49.  * }
  50.  * </pre>
  51.  *
  52.  * <em>Warning</em>: the hostname is not verified against the certificate by default, use {@link #setHostnameVerifier(HostnameVerifier)} or
  53.  * {@link #setEndpointCheckingEnabled(boolean)} (on Java 1.7+) to enable verification.
  54.  *
  55.  * @since 3.0
  56.  */
  57. public class SMTPSClient extends SMTPClient {
  58.     /** Default secure socket protocol name, like TLS */
  59.     private static final String DEFAULT_PROTOCOL = "TLS";

  60.     /** The security mode. True - Implicit Mode / False - Explicit Mode. */

  61.     private final boolean isImplicit;
  62.     /** The secure socket protocol to be used, like SSL/TLS. */

  63.     private final String protocol;
  64.     /** The context object. */

  65.     private SSLContext context;
  66.     /**
  67.      * The cipher suites. SSLSockets have a default set of these anyway, so no initialization required.
  68.      */

  69.     private String[] suites;
  70.     /** The protocol versions. */

  71.     private String[] protocols;

  72.     /** The {@link TrustManager} implementation, default null (i.e. use system managers). */
  73.     private TrustManager trustManager;

  74.     /** The {@link KeyManager}, default null (i.e. use system managers). */
  75.     private KeyManager keyManager; // seems not to be required

  76.     /** The {@link HostnameVerifier} to use post-TLS, default null (i.e. no verification). */
  77.     private HostnameVerifier hostnameVerifier;

  78.     /** Use Java 1.7+ HTTPS Endpoint Identification Algorithm. */
  79.     private boolean tlsEndpointChecking;

  80.     /**
  81.      * Constructor for SMTPSClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS Sets security mode to explicit (isImplicit = false).
  82.      */
  83.     public SMTPSClient() {
  84.         this(DEFAULT_PROTOCOL, false);
  85.     }

  86.     /**
  87.      * Constructor for SMTPSClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS
  88.      *
  89.      * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit
  90.      */
  91.     public SMTPSClient(final boolean implicit) {
  92.         this(DEFAULT_PROTOCOL, implicit);
  93.     }

  94.     /**
  95.      * Constructor for SMTPSClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS
  96.      *
  97.      * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit
  98.      * @param ctx      A pre-configured SSL Context.
  99.      */
  100.     public SMTPSClient(final boolean implicit, final SSLContext ctx) {
  101.         isImplicit = implicit;
  102.         context = ctx;
  103.         protocol = DEFAULT_PROTOCOL;
  104.     }

  105.     /**
  106.      * Constructor for SMTPSClient.
  107.      *
  108.      * @param context A pre-configured SSL Context.
  109.      * @see #SMTPSClient(boolean, SSLContext)
  110.      */
  111.     public SMTPSClient(final SSLContext context) {
  112.         this(false, context);
  113.     }

  114.     /**
  115.      * Constructor for SMTPSClient, using explicit security mode.
  116.      *
  117.      * @param proto the protocol.
  118.      */
  119.     public SMTPSClient(final String proto) {
  120.         this(proto, false);
  121.     }

  122.     /**
  123.      * Constructor for SMTPSClient.
  124.      *
  125.      * @param proto    the protocol.
  126.      * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit
  127.      */
  128.     public SMTPSClient(final String proto, final boolean implicit) {
  129.         protocol = proto;
  130.         isImplicit = implicit;
  131.     }

  132.     /**
  133.      * Constructor for SMTPSClient.
  134.      *
  135.      * @param proto    the protocol.
  136.      * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit
  137.      * @param encoding the encoding
  138.      * @since 3.3
  139.      */
  140.     public SMTPSClient(final String proto, final boolean implicit, final String encoding) {
  141.         super(encoding);
  142.         protocol = proto;
  143.         isImplicit = implicit;
  144.     }

  145.     /**
  146.      * Because there are so many connect() methods, the _connectAction_() method is provided as a means of performing some action immediately after establishing
  147.      * a connection, rather than reimplementing all the connect() methods.
  148.      *
  149.      * @throws IOException If it is thrown by _connectAction_().
  150.      * @see org.apache.commons.net.SocketClient#_connectAction_()
  151.      */
  152.     @Override
  153.     protected void _connectAction_() throws IOException {
  154.         // Implicit mode.
  155.         if (isImplicit) {
  156.             applySocketAttributes();
  157.             performSSLNegotiation();
  158.         }
  159.         super._connectAction_();
  160.         // Explicit mode - don't do anything. The user calls execTLS()
  161.     }

  162.     /**
  163.      * The TLS command execution.
  164.      *
  165.      * @throws IOException If an I/O error occurs while sending the command or performing the negotiation.
  166.      * @return TRUE if the command and negotiation succeeded.
  167.      */
  168.     public boolean execTLS() throws IOException {
  169.         if (!SMTPReply.isPositiveCompletion(sendCommand("STARTTLS"))) {
  170.             return false;
  171.             // throw new SSLException(getReplyString());
  172.         }
  173.         performSSLNegotiation();
  174.         return true;
  175.     }

  176.     /**
  177.      * Returns the names of the cipher suites which could be enabled for use on this connection. When the underlying {@link java.net.Socket Socket} is not an
  178.      * {@link SSLSocket} instance, returns null.
  179.      *
  180.      * @return An array of cipher suite names, or {@code null}.
  181.      */
  182.     public String[] getEnabledCipherSuites() {
  183.         if (_socket_ instanceof SSLSocket) {
  184.             return ((SSLSocket) _socket_).getEnabledCipherSuites();
  185.         }
  186.         return null;
  187.     }

  188.     /**
  189.      * Returns the names of the protocol versions which are currently enabled for use on this connection. When the underlying {@link java.net.Socket Socket} is
  190.      * not an {@link SSLSocket} instance, returns null.
  191.      *
  192.      * @return An array of protocols, or {@code null}.
  193.      */
  194.     public String[] getEnabledProtocols() {
  195.         if (_socket_ instanceof SSLSocket) {
  196.             return ((SSLSocket) _socket_).getEnabledProtocols();
  197.         }
  198.         return null;
  199.     }

  200.     /**
  201.      * Gets the currently configured {@link HostnameVerifier}.
  202.      *
  203.      * @return A HostnameVerifier instance.
  204.      * @since 3.4
  205.      */
  206.     public HostnameVerifier getHostnameVerifier() {
  207.         return hostnameVerifier;
  208.     }

  209.     /**
  210.      * Gets the {@link KeyManager} instance.
  211.      *
  212.      * @return The current {@link KeyManager} instance.
  213.      */
  214.     public KeyManager getKeyManager() {
  215.         return keyManager;
  216.     }

  217.     /**
  218.      * Gets the currently configured {@link TrustManager}.
  219.      *
  220.      * @return A TrustManager instance.
  221.      */
  222.     public TrustManager getTrustManager() {
  223.         return trustManager;
  224.     }

  225.     /**
  226.      * Performs a lazy init of the SSL context.
  227.      *
  228.      * @throws IOException When could not initialize the SSL context.
  229.      */
  230.     private void initSSLContext() throws IOException {
  231.         if (context == null) {
  232.             context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager());
  233.         }
  234.     }

  235.     /**
  236.      * Return whether or not endpoint identification using the HTTPS algorithm on Java 1.7+ is enabled. The default behavior is for this to be disabled.
  237.      *
  238.      * @return True if enabled, false if not.
  239.      * @since 3.4
  240.      */
  241.     public boolean isEndpointCheckingEnabled() {
  242.         return tlsEndpointChecking;
  243.     }

  244.     /**
  245.      * SSL/TLS negotiation. Acquires an SSL socket of a connection and carries out handshake processing.
  246.      *
  247.      * @throws IOException If server negotiation fails.
  248.      */
  249.     private void performSSLNegotiation() throws IOException {
  250.         initSSLContext();

  251.         final SSLSocketFactory ssf = context.getSocketFactory();
  252.         final String host = _hostname_ != null ? _hostname_ : getRemoteAddress().getHostAddress();
  253.         final int port = getRemotePort();
  254.         final SSLSocket socket = (SSLSocket) ssf.createSocket(_socket_, host, port, true);
  255.         socket.setEnableSessionCreation(true);
  256.         socket.setUseClientMode(true);

  257.         if (tlsEndpointChecking) {
  258.             SSLSocketUtils.enableEndpointNameVerification(socket);
  259.         }
  260.         if (protocols != null) {
  261.             socket.setEnabledProtocols(protocols);
  262.         }
  263.         if (suites != null) {
  264.             socket.setEnabledCipherSuites(suites);
  265.         }
  266.         socket.startHandshake();

  267.         // TODO the following setup appears to duplicate that in the super class methods
  268.         _socket_ = socket;
  269.         _input_ = socket.getInputStream();
  270.         _output_ = socket.getOutputStream();
  271.         reader = new CRLFLineReader(new InputStreamReader(_input_, encoding));
  272.         writer = new BufferedWriter(new OutputStreamWriter(_output_, encoding));

  273.         if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) {
  274.             throw new SSLHandshakeException("Hostname doesn't match certificate");
  275.         }
  276.     }

  277.     /**
  278.      * Controls which particular cipher suites are enabled for use on this connection. Called before server negotiation.
  279.      *
  280.      * @param cipherSuites The cipher suites.
  281.      */
  282.     public void setEnabledCipherSuites(final String[] cipherSuites) {
  283.         suites = cipherSuites.clone();
  284.     }

  285.     /**
  286.      * Controls which particular protocol versions are enabled for use on this connection. I perform setting before a server negotiation.
  287.      *
  288.      * @param protocolVersions The protocol versions.
  289.      */
  290.     public void setEnabledProtocols(final String[] protocolVersions) {
  291.         protocols = protocolVersions.clone();
  292.     }

  293.     /**
  294.      * Automatic endpoint identification checking using the HTTPS algorithm is supported on Java 1.7+. The default behavior is for this to be disabled.
  295.      *
  296.      * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+.
  297.      * @since 3.4
  298.      */
  299.     public void setEndpointCheckingEnabled(final boolean enable) {
  300.         tlsEndpointChecking = enable;
  301.     }

  302.     /**
  303.      * Override the default {@link HostnameVerifier} to use.
  304.      *
  305.      * @param newHostnameVerifier The HostnameVerifier implementation to set or {@code null} to disable.
  306.      * @since 3.4
  307.      */
  308.     public void setHostnameVerifier(final HostnameVerifier newHostnameVerifier) {
  309.         hostnameVerifier = newHostnameVerifier;
  310.     }

  311.     /**
  312.      * Sets a {@link KeyManager} to use.
  313.      *
  314.      * @param newKeyManager The KeyManager implementation to set.
  315.      * @see org.apache.commons.net.util.KeyManagerUtils
  316.      */
  317.     public void setKeyManager(final KeyManager newKeyManager) {
  318.         keyManager = newKeyManager;
  319.     }

  320.     /**
  321.      * Override the default {@link TrustManager} to use.
  322.      *
  323.      * @param newTrustManager The TrustManager implementation to set.
  324.      * @see org.apache.commons.net.util.TrustManagerUtils
  325.      */
  326.     public void setTrustManager(final TrustManager newTrustManager) {
  327.         trustManager = newTrustManager;
  328.     }
  329. }