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