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