    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017
    018    package org.apache.commons.net.smtp;
    019
    020    import java.io.BufferedWriter;
    021    import java.io.IOException;
    022    import java.io.InputStreamReader;
    023    import java.io.OutputStreamWriter;
    024
    025    import javax.net.ssl.KeyManager;
    026    import javax.net.ssl.SSLContext;
    027    import javax.net.ssl.SSLException;
    028    import javax.net.ssl.SSLSocket;
    029    import javax.net.ssl.SSLSocketFactory;
    030    import javax.net.ssl.TrustManager;
    031
    032    import org.apache.commons.net.io.CRLFLineReader;
    033    import org.apache.commons.net.util.SSLContextUtils;
    034
    035    /**
    036     * SMTP over SSL processing. Copied from FTPSClient.java and modified to suit SMTP.
    037     * If implicit mode is selected (NOT the default), SSL/TLS negotiation starts right
    038     * after the connection has been established. In explicit mode (the default), SSL/TLS
    039     * negotiation starts when the user calls execTLS() and the server accepts the command.
    040     * Implicit usage:
    041     *               SMTPSClient c = new SMTPSClient(true);
    042     *               c.connect("127.0.0.1", 465);
    043     * Explicit usage:
    044     *               SMTPSClient c = new SMTPSClient();
    045     *               c.connect("127.0.0.1", 25);
    046     *               if (c.execTLS()) { /rest of the commands here/ }
    047     * @since 3.0
    048     */
    049    public class SMTPSClient extends SMTPClient
    050    {
    051        /** Default secure socket protocol name, like TLS */
    052        private static final String DEFAULT_PROTOCOL = "TLS";
    053
    054        /** The security mode. True - Implicit Mode / False - Explicit Mode. */
    055        private final boolean isImplicit;
    056        /** The secure socket protocol to be used, like SSL/TLS. */
    057        private final String protocol;
    058        /** The context object. */
    059        private SSLContext context = null;
    060        /** The cipher suites. SSLSockets have a default set of these anyway,
    061            so no initialization required. */
    062        private String[] suites = null;
    063        /** The protocol versions. */
    064        private String[] protocols = null;
    065
    066        /** The {@link TrustManager} implementation, default null (i.e. use system managers). */
    067        private TrustManager trustManager = null;
    068
    069        /** The {@link KeyManager}, default null (i.e. use system managers). */
    070        private KeyManager keyManager = null; // seems not to be required
    071
    072        /**
    073         * Constructor for SMTPSClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS
    074         * Sets security mode to explicit (isImplicit = false).
    075         */
    076        public SMTPSClient()
    077        {
    078            this(DEFAULT_PROTOCOL, false);
    079        }
    080
    081        /**
    082         * Constructor for SMTPSClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS
    083         * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit
    084         */
    085        public SMTPSClient(boolean implicit)
    086        {
    087            this(DEFAULT_PROTOCOL, implicit);
    088        }
    089
    090        /**
    091         * Constructor for SMTPSClient, using explicit security mode.
    092         * @param proto the protocol.
    093         */
    094        public SMTPSClient(String proto)
    095        {
    096            this(proto, false);
    097        }
    098
    099        /**
    100         * Constructor for SMTPSClient.
    101         * @param proto the protocol.
    102         * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit
    103         */
    104        public SMTPSClient(String proto, boolean implicit)
    105        {
    106            protocol = proto;
    107            isImplicit = implicit;
    108        }
    109
    110        /**
    111         * Constructor for SMTPSClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS
    112         * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit
    113         * @param ctx A pre-configured SSL Context.
    114         */
    115        public SMTPSClient(boolean implicit, SSLContext ctx)
    116        {
    117            isImplicit = implicit;
    118            context = ctx;
    119            protocol = DEFAULT_PROTOCOL;
    120        }
    121
    122        /**
    123         * Constructor for SMTPSClient.
    124         * @param context A pre-configured SSL Context.
    125         * @see #SMTPSClient(boolean, SSLContext)
    126         */
    127        public SMTPSClient(SSLContext context)
    128        {
    129            this(false, context);
    130        }
    131
    132        /**
    133         * Because there are so many connect() methods,
    134         * the _connectAction_() method is provided as a means of performing
    135         * some action immediately after establishing a connection,
    136         * rather than reimplementing all of the connect() methods.
    137         * @throws IOException If it is thrown by _connectAction_().
    138         * @see org.apache.commons.net.SocketClient#_connectAction_()
    139         */
    140        @Override
    141        protected void _connectAction_() throws IOException
    142        {
    143            // Implicit mode.
    144            if (isImplicit) {
    145                performSSLNegotiation();
    146            }
    147            super._connectAction_();
    148            // Explicit mode - don't do anything. The user calls execTLS()
    149        }
    150
    151        /**
    152         * Performs a lazy init of the SSL context.
    153         * @throws IOException When could not initialize the SSL context.
    154         */
    155        private void initSSLContext() throws IOException
    156        {
    157            if (context == null)
    158            {
    159                context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager());
    160            }
    161        }
    162
    163        /**
    164         * SSL/TLS negotiation. Acquires an SSL socket of a
    165         * connection and carries out handshake processing.
    166         * @throws IOException If server negotiation fails.
    167         */
    168        private void performSSLNegotiation() throws IOException
    169        {
    170            initSSLContext();
    171
    172            SSLSocketFactory ssf = context.getSocketFactory();
    173            String ip = getRemoteAddress().getHostAddress();
    174            int port = getRemotePort();
    175            SSLSocket socket =
    176                (SSLSocket) ssf.createSocket(_socket_, ip, port, true);
    177            socket.setEnableSessionCreation(true);
    178            socket.setUseClientMode(true);
    179
    180            if (protocols != null) {
    181                socket.setEnabledProtocols(protocols);
    182            }
    183            if (suites != null) {
    184                socket.setEnabledCipherSuites(suites);
    185            }
    186            socket.startHandshake();
    187
    188            _socket_ = socket;
    189            _input_ = socket.getInputStream();
    190            _output_ = socket.getOutputStream();
    191            _reader = new CRLFLineReader(
    192                            new InputStreamReader(_input_, encoding));
    193            _writer = new BufferedWriter(
    194                            new OutputStreamWriter(_output_, encoding));
    195
    196        }
    197
    198        /**
    199         * Get the {@link KeyManager} instance.
    200         * @return The current {@link KeyManager} instance.
    201         */
    202        public KeyManager getKeyManager()
    203        {
    204            return keyManager;
    205        }
    206
    207        /**
    208         * Set a {@link KeyManager} to use.
    209         * @param newKeyManager The KeyManager implementation to set.
    210         * @see org.apache.commons.net.util.KeyManagerUtils
    211         */
    212        public void setKeyManager(KeyManager newKeyManager)
    213        {
    214            keyManager = newKeyManager;
    215        }
    216
    217        /**
    218         * Controls which particular cipher suites are enabled for use on this
    219         * connection. Called before server negotiation.
    220         * @param cipherSuites The cipher suites.
    221         */
    222        public void setEnabledCipherSuites(String[] cipherSuites)
    223        {
    224            suites = new String[cipherSuites.length];
    225            System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length);
    226        }
    227
    228        /**
    229         * Returns the names of the cipher suites which could be enabled
    230         * for use on this connection.
    231         * When the underlying {@link java.net.Socket Socket} is not an {@link SSLSocket} instance, returns null.
    232         * @return An array of cipher suite names, or <code>null</code>.
    233         */
    234        public String[] getEnabledCipherSuites()
    235        {
    236            if (_socket_ instanceof SSLSocket)
    237            {
    238                return ((SSLSocket)_socket_).getEnabledCipherSuites();
    239            }
    240            return null;
    241        }
    242
    243        /**
    244         * Controls which particular protocol versions are enabled for use on this
    245         * connection. I perform setting before a server negotiation.
    246         * @param protocolVersions The protocol versions.
    247         */
    248        public void setEnabledProtocols(String[] protocolVersions)
    249        {
    250            protocols = new String[protocolVersions.length];
    251            System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length);
    252        }
    253
    254        /**
    255         * Returns the names of the protocol versions which are currently
    256         * enabled for use on this connection.
    257         * When the underlying {@link java.net.Socket Socket} is not an {@link SSLSocket} instance, returns null.
    258         * @return An array of protocols, or <code>null</code>.
    259         */
    260        public String[] getEnabledProtocols()
    261        {
    262            if (_socket_ instanceof SSLSocket)
    263            {
    264                return ((SSLSocket)_socket_).getEnabledProtocols();
    265            }
    266            return null;
    267        }
    268
    269        /**
    270         * The TLS command execution.
    271         * @throws IOException If an I/O error occurs while sending
    272         * the command or performing the negotiation.
    273         * @return TRUE if the command and negotiation succeeded.
    274         */
    275        public boolean execTLS() throws SSLException, IOException
    276        {
    277            if (!SMTPReply.isPositiveCompletion(sendCommand("STARTTLS")))
    278            {
    279                return false;
    280                //throw new SSLException(getReplyString());
    281            }
    282            performSSLNegotiation();
    283            return true;
    284        }
    285
    286        /**
    287         * Get the currently configured {@link TrustManager}.
    288         * @return A TrustManager instance.
    289         */
    290        public TrustManager getTrustManager()
    291        {
    292            return trustManager;
    293        }
    294
    295        /**
    296         * Override the default {@link TrustManager} to use.
    297         * @param newTrustManager The TrustManager implementation to set.
    298         * @see org.apache.commons.net.util.TrustManagerUtils
    299         */
    300        public void setTrustManager(TrustManager newTrustManager)
    301        {
    302            trustManager = newTrustManager;
    303        }
    304    }
    305
    306    /* kate: indent-width 4; replace-tabs on; */