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; */