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