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