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