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