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
018package org.apache.commons.net.imap;
019
020import java.io.BufferedWriter;
021import java.io.IOException;
022import java.io.InputStreamReader;
023import java.io.OutputStreamWriter;
024
025import javax.net.ssl.HostnameVerifier;
026import javax.net.ssl.KeyManager;
027import javax.net.ssl.SSLContext;
028import javax.net.ssl.SSLException;
029import javax.net.ssl.SSLHandshakeException;
030import javax.net.ssl.SSLSocket;
031import javax.net.ssl.SSLSocketFactory;
032import javax.net.ssl.TrustManager;
033
034import org.apache.commons.net.io.CRLFLineReader;
035import org.apache.commons.net.util.SSLContextUtils;
036import org.apache.commons.net.util.SSLSocketUtils;
037
038/**
039 * The IMAPSClient class provides SSL/TLS connection encryption to IMAPClient.
040 * Copied from
041 * <a href="http://commons.apache.org/proper/commons-net/apidocs/index.html?org/apache/commons/net/ftp/FTPSClient.html">
042 * FTPSClient</a> and modified to suit IMAP.
043 * If implicit mode is selected (NOT the default), SSL/TLS negotiation starts right
044 * after the connection has been established. In explicit mode (the default), SSL/TLS
045 * negotiation starts when the user calls execTLS() and the server accepts the command.
046 *
047 * <pre>
048 * {@code
049 * //Implicit usage:
050 *
051 *               IMAPSClient c = new IMAPSClient(true);
052 *               c.connect("127.0.0.1", 993);
053 *
054 * //Explicit usage:
055 *
056 *               IMAPSClient c = new IMAPSClient();
057 *               c.connect("127.0.0.1", 143);
058 *               if (c.execTLS()) { /rest of the commands here/ }
059 * }
060 * </pre>
061 * <b>Warning</b>: the hostname is not verified against the certificate by default, use
062 * {@link #setHostnameVerifier(HostnameVerifier)} or {@link #setEndpointCheckingEnabled(boolean)}
063 * (on Java 1.7+) to enable verification.
064 */
065public class IMAPSClient extends IMAPClient
066{
067    /** The default IMAP over SSL port. */
068    public static final int DEFAULT_IMAPS_PORT = 993;
069
070    /** Default secure socket protocol name. */
071    public static final String DEFAULT_PROTOCOL = "TLS";
072
073    /** The security mode. True - Implicit Mode / False - Explicit Mode. */
074    private final boolean isImplicit;
075    /** The secure socket protocol to be used, like SSL/TLS. */
076    private final String protocol;
077    /** The context object. */
078    private SSLContext context;
079    /** The cipher suites. SSLSockets have a default set of these anyway,
080        so no initialization required. */
081    private String[] suites;
082    /** The protocol versions. */
083    private String[] protocols  //null;
084            ;//{"SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "SSLv2Hello"};
085
086    /** The IMAPS {@link TrustManager} implementation, default null. */
087    private TrustManager trustManager;
088
089    /** The {@link KeyManager}, default null. */
090    private KeyManager keyManager;
091
092    /** The {@link HostnameVerifier} to use post-TLS, default null (i.e. no verification). */
093    private HostnameVerifier hostnameVerifier;
094
095    /** Use Java 1.7+ HTTPS Endpoint Identification Algorithim. */
096    private boolean tlsEndpointChecking;
097
098    /**
099     * Constructor for IMAPSClient.
100     * Sets security mode to explicit (isImplicit = false).
101     */
102    public IMAPSClient()
103    {
104        this(DEFAULT_PROTOCOL, false);
105    }
106
107    /**
108     * Constructor for IMAPSClient.
109     * @param implicit The security mode (Implicit/Explicit).
110     */
111    public IMAPSClient(final boolean implicit)
112    {
113        this(DEFAULT_PROTOCOL, implicit);
114    }
115
116    /**
117     * Constructor for IMAPSClient.
118     * @param proto the protocol.
119     */
120    public IMAPSClient(final String proto)
121    {
122        this(proto, false);
123    }
124
125    /**
126     * Constructor for IMAPSClient.
127     * @param proto the protocol.
128     * @param implicit The security mode(Implicit/Explicit).
129     */
130    public IMAPSClient(final String proto, final boolean implicit)
131    {
132        this(proto, implicit, null);
133    }
134
135    /**
136     * Constructor for IMAPSClient.
137     * @param proto the protocol.
138     * @param implicit The security mode(Implicit/Explicit).
139     * @param ctx the SSL context
140     */
141    public IMAPSClient(final String proto, final boolean implicit, final SSLContext ctx)
142    {
143        setDefaultPort(DEFAULT_IMAPS_PORT);
144        protocol = proto;
145        isImplicit = implicit;
146        context = ctx;
147    }
148
149    /**
150     * Constructor for IMAPSClient.
151     * @param implicit The security mode(Implicit/Explicit).
152     * @param ctx A pre-configured SSL Context.
153     */
154    public IMAPSClient(final boolean implicit, final SSLContext ctx)
155    {
156        this(DEFAULT_PROTOCOL, implicit, ctx);
157    }
158
159    /**
160     * Constructor for IMAPSClient.
161     * @param context A pre-configured SSL Context.
162     */
163    public IMAPSClient(final SSLContext context)
164    {
165        this(false, context);
166    }
167
168    /**
169     * Because there are so many connect() methods,
170     * the _connectAction_() method is provided as a means of performing
171     * some action immediately after establishing a connection,
172     * rather than reimplementing all of the connect() methods.
173     * @throws IOException If it is thrown by _connectAction_().
174     * @see org.apache.commons.net.SocketClient#_connectAction_()
175     */
176    @Override
177    protected void _connectAction_() throws IOException
178    {
179        // Implicit mode.
180        if (isImplicit) {
181            performSSLNegotiation();
182        }
183        super._connectAction_();
184        // Explicit mode - don't do anything. The user calls execTLS()
185    }
186
187    /**
188     * Performs a lazy init of the SSL context.
189     * @throws IOException When could not initialize the SSL context.
190     */
191    private void initSSLContext() throws IOException
192    {
193        if (context == null)
194        {
195            context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager());
196        }
197    }
198
199    /**
200     * SSL/TLS negotiation. Acquires an SSL socket of a
201     * connection and carries out handshake processing.
202     * @throws IOException If server negotiation fails.
203     */
204    private void performSSLNegotiation() throws IOException
205    {
206        initSSLContext();
207
208        final SSLSocketFactory ssf = context.getSocketFactory();
209        final String host = _hostname_ != null ? _hostname_ : getRemoteAddress().getHostAddress();
210        final int port = getRemotePort();
211        final SSLSocket socket =
212            (SSLSocket) ssf.createSocket(_socket_, host, port, true);
213        socket.setEnableSessionCreation(true);
214        socket.setUseClientMode(true);
215
216        if (tlsEndpointChecking) {
217            SSLSocketUtils.enableEndpointNameVerification(socket);
218        }
219
220        if (protocols != null) {
221            socket.setEnabledProtocols(protocols);
222        }
223        if (suites != null) {
224            socket.setEnabledCipherSuites(suites);
225        }
226        socket.startHandshake();
227
228        // TODO the following setup appears to duplicate that in the super class methods
229        _socket_ = socket;
230        _input_ = socket.getInputStream();
231        _output_ = socket.getOutputStream();
232        _reader =
233          new CRLFLineReader(new InputStreamReader(_input_,
234                                                   __DEFAULT_ENCODING));
235        __writer =
236          new BufferedWriter(new OutputStreamWriter(_output_,
237                                                    __DEFAULT_ENCODING));
238
239        if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) {
240            throw new SSLHandshakeException("Hostname doesn't match certificate");
241        }
242    }
243
244    /**
245     * Get the {@link KeyManager} instance.
246     * @return The current {@link KeyManager} instance.
247     */
248    private KeyManager getKeyManager()
249    {
250        return keyManager;
251    }
252
253    /**
254     * Set a {@link KeyManager} to use.
255     * @param newKeyManager The KeyManager implementation to set.
256     * @see org.apache.commons.net.util.KeyManagerUtils
257     */
258    public void setKeyManager(final KeyManager newKeyManager)
259    {
260        keyManager = newKeyManager;
261    }
262
263    /**
264     * Controls which particular cipher suites are enabled for use on this
265     * connection. Called before server negotiation.
266     * @param cipherSuites The cipher suites.
267     */
268    public void setEnabledCipherSuites(final String[] cipherSuites)
269    {
270        suites = cipherSuites.clone();
271    }
272
273    /**
274     * Returns the names of the cipher suites which could be enabled
275     * for use on this connection.
276     * When the underlying {@link java.net.Socket Socket} is not an {@link SSLSocket} instance, returns null.
277     * @return An array of cipher suite names, or <code>null</code>.
278     */
279    public String[] getEnabledCipherSuites()
280    {
281        if (_socket_ instanceof SSLSocket)
282        {
283            return ((SSLSocket)_socket_).getEnabledCipherSuites();
284        }
285        return null;
286    }
287
288    /**
289     * Controls which particular protocol versions are enabled for use on this
290     * connection. I perform setting before a server negotiation.
291     * @param protocolVersions The protocol versions.
292     */
293    public void setEnabledProtocols(final String[] protocolVersions)
294    {
295        protocols = protocolVersions.clone();
296    }
297
298    /**
299     * Returns the names of the protocol versions which are currently
300     * enabled for use on this connection.
301     * When the underlying {@link java.net.Socket Socket} is not an {@link SSLSocket} instance, returns null.
302     * @return An array of protocols, or <code>null</code>.
303     */
304    public String[] getEnabledProtocols()
305    {
306        if (_socket_ instanceof SSLSocket)
307        {
308            return ((SSLSocket)_socket_).getEnabledProtocols();
309        }
310        return null;
311    }
312
313    /**
314     * The TLS command execution.
315     * @throws SSLException If the server reply code is not positive.
316     * @throws IOException If an I/O error occurs while sending
317     * the command or performing the negotiation.
318     * @return TRUE if the command and negotiation succeeded.
319     */
320    public boolean execTLS() throws SSLException, IOException
321    {
322        if (sendCommand(IMAPCommand.getCommand(IMAPCommand.STARTTLS)) != IMAPReply.OK)
323        {
324            return false;
325            //throw new SSLException(getReplyString());
326        }
327        performSSLNegotiation();
328        return true;
329    }
330
331    /**
332     * Get the currently configured {@link TrustManager}.
333     * @return A TrustManager instance.
334     */
335    public TrustManager getTrustManager()
336    {
337        return trustManager;
338    }
339
340    /**
341     * Override the default {@link TrustManager} to use.
342     * @param newTrustManager The TrustManager implementation to set.
343     * @see org.apache.commons.net.util.TrustManagerUtils
344     */
345    public void setTrustManager(final TrustManager newTrustManager)
346    {
347        trustManager = newTrustManager;
348    }
349
350    /**
351     * Get the currently configured {@link HostnameVerifier}.
352     * @return A HostnameVerifier instance.
353     * @since 3.4
354     */
355    public HostnameVerifier getHostnameVerifier()
356    {
357        return hostnameVerifier;
358    }
359
360    /**
361     * Override the default {@link HostnameVerifier} to use.
362     * @param newHostnameVerifier The HostnameVerifier implementation to set or <code>null</code> to disable.
363     * @since 3.4
364     */
365    public void setHostnameVerifier(final HostnameVerifier newHostnameVerifier)
366    {
367        hostnameVerifier = newHostnameVerifier;
368    }
369
370    /**
371     * Return whether or not endpoint identification using the HTTPS algorithm
372     * on Java 1.7+ is enabled. The default behavior is for this to be disabled.
373     *
374     * @return True if enabled, false if not.
375     * @since 3.4
376     */
377    public boolean isEndpointCheckingEnabled()
378    {
379        return tlsEndpointChecking;
380    }
381
382    /**
383     * Automatic endpoint identification checking using the HTTPS algorithm
384     * is supported on Java 1.7+. The default behavior is for this to be disabled.
385     *
386     * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+.
387     * @since 3.4
388     */
389    public void setEndpointCheckingEnabled(final boolean enable)
390    {
391        tlsEndpointChecking = enable;
392    }
393}
394/* kate: indent-width 4; replace-tabs on; */