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