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