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; */