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.InputStreamReader;
022import java.io.IOException;
023import java.io.OutputStreamWriter;
024
025import javax.net.ssl.KeyManager;
026import javax.net.ssl.SSLContext;
027import javax.net.ssl.SSLException;
028import javax.net.ssl.SSLSocket;
029import javax.net.ssl.SSLSocketFactory;
030import javax.net.ssl.TrustManager;
031
032import org.apache.commons.net.io.CRLFLineReader;
033import 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 */
049public 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; */