| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.geronimo.javamail.util; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.PrintStream; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.net.InetAddress; |
| import java.net.Socket; |
| import java.net.UnknownHostException; |
| import java.security.KeyManagementException; |
| import java.security.KeyStore; |
| import java.security.KeyStoreException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.cert.CertificateException; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.StringTokenizer; |
| |
| import javax.mail.MessagingException; |
| import javax.mail.Session; |
| import javax.net.SocketFactory; |
| import javax.net.ssl.SSLContext; |
| import javax.net.ssl.SSLSocket; |
| import javax.net.ssl.SSLSocketFactory; |
| import javax.net.ssl.TrustManager; |
| import javax.net.ssl.TrustManagerFactory; |
| import javax.net.ssl.X509TrustManager; |
| |
| import org.apache.geronimo.javamail.authentication.ClientAuthenticator; |
| import org.apache.geronimo.javamail.authentication.CramMD5Authenticator; |
| import org.apache.geronimo.javamail.authentication.DigestMD5Authenticator; |
| import org.apache.geronimo.javamail.authentication.LoginAuthenticator; |
| import org.apache.geronimo.javamail.authentication.PlainAuthenticator; |
| import org.apache.geronimo.javamail.authentication.SASLAuthenticator; |
| |
| /** |
| * Base class for all mail Store/Transport connection. Centralizes management |
| * of a lot of common connection handling. Actual protcol-specific |
| * functions are handled at the subclass level. |
| */ |
| public class MailConnection { |
| /** |
| * constants for EOL termination |
| */ |
| protected static final char CR = '\r'; |
| protected static final char LF = '\n'; |
| |
| /** |
| * property keys for protocol properties. |
| */ |
| protected static final String MAIL_PORT = "port"; |
| protected static final String MAIL_LOCALHOST = "localhost"; |
| protected static final String MAIL_STARTTLS_ENABLE = "starttls.enable"; |
| protected static final String MAIL_STARTTLS_REQUIRED = "starttls.required"; |
| protected static final String MAIL_SSL_ENABLE = "ssl.enable"; |
| protected static final String MAIL_TIMEOUT = "timeout"; |
| protected static final String MAIL_SASL_ENABLE = "sasl.enable"; |
| protected static final String MAIL_SASL_REALM = "sasl.realm"; |
| protected static final String MAIL_AUTHORIZATIONID = "sasl.authorizationid"; |
| protected static final String MAIL_SASL_MECHANISMS = "sasl.mechanisms"; |
| protected static final String MAIL_PLAIN_DISABLE = "auth.plain.disable"; |
| protected static final String MAIL_LOGIN_DISABLE = "auth.login.disable"; |
| |
| protected static final String MAIL_FACTORY = "socketFactory"; //GERONIMO-5429 |
| protected static final String MAIL_FACTORY_CLASS = "socketFactory.class"; |
| protected static final String MAIL_FACTORY_FALLBACK = "socketFactory.fallback"; |
| protected static final String MAIL_FACTORY_PORT = "socketFactory.port"; |
| |
| protected static final String MAIL_SSL_FACTORY = "ssl.socketFactory"; //GERONIMO-5429 |
| protected static final String MAIL_SSL_FACTORY_CLASS = "ssl.socketFactory.class"; |
| protected static final String MAIL_SSL_FACTORY_PORT = "ssl.socketFactory.port"; |
| protected static final String MAIL_SSL_PROTOCOLS = "ssl.protocols"; |
| protected static final String MAIL_SSL_CIPHERSUITES = "ssl.ciphersuites"; |
| protected static final String MAIL_SSL_TRUST = "ssl.trust"; |
| |
| protected static final String MAIL_LOCALADDRESS = "localaddress"; |
| protected static final String MAIL_LOCALPORT = "localport"; |
| protected static final String MAIL_ENCODE_TRACE = "encodetrace"; |
| |
| protected static final int MIN_MILLIS = 1000 * 60; |
| protected static final int TIMEOUT = MIN_MILLIS * 5; |
| protected static final String DEFAULT_MAIL_HOST = "localhost"; |
| |
| protected static final String CAPABILITY_STARTTLS = "STARTTLS"; |
| |
| protected static final String AUTHENTICATION_PLAIN = "PLAIN"; |
| protected static final String AUTHENTICATION_LOGIN = "LOGIN"; |
| protected static final String AUTHENTICATION_CRAMMD5 = "CRAM-MD5"; |
| protected static final String AUTHENTICATION_DIGESTMD5 = "DIGEST-MD5"; |
| |
| // The mail Session we're associated with |
| protected Session session; |
| // The protocol we're implementing |
| protected String protocol; |
| // There are usually SSL and non-SSL versions of these protocols. This |
| // indicates which version we're using. |
| protected boolean sslConnection; |
| // This is the default port we should be using for making a connection. Each |
| // protocol (and each ssl version of the protocol) normally has a different default that |
| // should be used. |
| protected int defaultPort; |
| |
| // a wrapper around our session to provide easier lookup of protocol |
| // specific property values |
| protected ProtocolProperties props; |
| |
| // The target server host |
| protected String serverHost; |
| // The target server port |
| protected int serverPort; |
| |
| // the connection socket...can be a plain socket or SSLSocket, if TLS is being used. |
| protected Socket socket; |
| |
| // our local host name |
| protected InetAddress localAddress; |
| // our local port value |
| protected int localPort; |
| // our local host name |
| protected String localHost; |
| |
| // our timeout value |
| protected int timeout; |
| |
| // our login username |
| protected String username; |
| // our login password |
| protected String password; |
| // our SASL security realm |
| protected String realm; |
| // our authorization id |
| protected String authid; |
| |
| // input stream used to read data. If Sasl is in use, this might be other than the |
| // direct access to the socket input stream. |
| protected InputStream inputStream; |
| // the other end of the connection pipeline. |
| protected OutputStream outputStream; |
| |
| // our session provided debug output stream. |
| protected PrintStream debugStream; |
| // our debug flag (passed from the hosting transport) |
| protected boolean debug; |
| |
| // list of authentication mechanisms supported by the server |
| protected List authentications; |
| // map of server extension arguments |
| protected Map capabilities; |
| // property list of authentication mechanisms |
| protected List mechanisms; |
| |
| protected MailConnection(ProtocolProperties props) |
| { |
| // this is our properties retriever utility, which will look up |
| // properties based on the appropriate "mail.protocol." prefix. |
| // this also holds other information we might need for access, such as |
| // the protocol name and the Session; |
| this.props = props; |
| this.protocol = props.getProtocol(); |
| this.session = props.getSession(); |
| this.sslConnection = props.getSSLConnection(); |
| this.defaultPort = props.getDefaultPort(); |
| |
| // initialize our debug settings from the session |
| debug = session.getDebug(); |
| debugStream = session.getDebugOut(); |
| |
| String mailSSLEnable = props.getProperty(MAIL_SSL_ENABLE); |
| if(mailSSLEnable != null) { |
| this.sslConnection = Boolean.valueOf(mailSSLEnable); |
| } |
| } |
| |
| |
| /** |
| * Connect to the server and do the initial handshaking. |
| * |
| * @param host The target host name. |
| * @param port The target port |
| * @param username The connection username (can be null) |
| * @param password The authentication password (can be null). |
| * |
| * @return true if we were able to obtain a connection and |
| * authenticate. |
| * @exception MessagingException |
| */ |
| public boolean protocolConnect(String host, int port, String username, String password) throws MessagingException { |
| // NOTE: We don't check for the username/password being null at this point. It's possible that |
| // the server will send back a PREAUTH response, which means we don't need to go through login |
| // processing. We'll need to check the capabilities response after we make the connection to decide |
| // if logging in is necesssary. |
| |
| // save this for subsequent connections. All pool connections will use this info. |
| // if the port is defaulted, then see if we have something configured in the session. |
| // if not configured, we just use the default default. |
| if (port == -1) { |
| // check for a property and fall back on the default if it's not set. |
| port = props.getIntProperty(MAIL_PORT, props.getDefaultPort()); |
| // it's possible that -1 might have been explicitly set, so one last check. |
| if (port == -1) { |
| port = props.getDefaultPort(); |
| } |
| } |
| |
| // Before we do anything, let's make sure that we successfully received a host |
| if ( host == null ) { |
| host = DEFAULT_MAIL_HOST; |
| } |
| |
| this.serverHost = host; |
| this.serverPort = port; |
| this.username = username; |
| this.password = password; |
| |
| // make sure we have the realm information |
| realm = props.getProperty(MAIL_SASL_REALM); |
| // get an authzid value, if we have one. The default is to use the username. |
| authid = props.getProperty(MAIL_AUTHORIZATIONID, username); |
| return true; |
| } |
| |
| |
| /** |
| * Establish a connection using an existing socket. |
| * |
| * @param s The socket to use. |
| */ |
| public void connect(Socket s) { |
| // just save the socket connection |
| this.socket = s; |
| } |
| |
| |
| /** |
| * Create a transport connection object and connect it to the |
| * target server. |
| * |
| * @exception MessagingException |
| */ |
| protected void getConnection() throws IOException, MessagingException |
| { |
| // We might have been passed a socket to connect with...if not, we need to create one of the correct type. |
| if (socket == null) { |
| // get the connection properties that control how we set this up. |
| getConnectionProperties(); |
| // if this is the SSL version of the protocol, we start with an SSLSocket |
| if (sslConnection) { |
| getConnectedSSLSocket(); |
| } |
| else |
| { |
| getConnectedSocket(); |
| } |
| } |
| // if we already have a socket, get some information from it and override what we've been passed. |
| else { |
| localPort = socket.getPort(); |
| localAddress = socket.getInetAddress(); |
| } |
| |
| // now set up the input/output streams. |
| getConnectionStreams(); |
| } |
| |
| /** |
| * Get common connection properties before creating a connection socket. |
| */ |
| protected void getConnectionProperties() { |
| |
| // there are several protocol properties that can be set to tune the created socket. We need to |
| // retrieve those bits before creating the socket. |
| timeout = props.getIntProperty(MAIL_TIMEOUT, -1); |
| localAddress = null; |
| // see if we have a local address override. |
| String localAddrProp = props.getProperty(MAIL_LOCALADDRESS); |
| if (localAddrProp != null) { |
| try { |
| localAddress = InetAddress.getByName(localAddrProp); |
| } catch (UnknownHostException e) { |
| // not much we can do if this fails. |
| } |
| } |
| |
| // check for a local port...default is to allow socket to choose. |
| localPort = props.getIntProperty(MAIL_LOCALPORT, 0); |
| } |
| |
| |
| /** |
| * Creates a connected socket |
| * |
| * @exception MessagingException |
| */ |
| protected void getConnectedSocket() throws IOException { |
| debugOut("Attempting plain socket connection to server " + serverHost + ":" + serverPort); |
| |
| // make sure this is null |
| socket = null; |
| |
| createSocket(false); |
| |
| // if we have a timeout value, set that before returning |
| if (timeout >= 0) { |
| socket.setSoTimeout(timeout); |
| } |
| } |
| |
| private boolean createSocketFromFactory(boolean ssl, boolean layer) throws IOException { |
| |
| String socketFactoryClass = props.getProperty(ssl?MAIL_SSL_FACTORY_CLASS:MAIL_FACTORY_CLASS); |
| |
| if(socketFactoryClass == null) { |
| return false; |
| } |
| |
| // we'll try this with potentially two different factories if we're allowed to fall back. |
| boolean fallback = props.getBooleanProperty(MAIL_FACTORY_FALLBACK, false); |
| int socketFactoryPort = props.getIntProperty(ssl?MAIL_SSL_FACTORY_PORT:MAIL_FACTORY_PORT, -1); |
| Integer portArg = new Integer(socketFactoryPort == -1 ? serverPort : socketFactoryPort); |
| |
| debugOut("Creating "+(ssl?"":"non-")+"SSL socket using factory " + socketFactoryClass+ " listening on port "+portArg); |
| |
| while (true) { |
| try { |
| |
| // use the current context loader to resolve this. |
| ClassLoader loader = Thread.currentThread().getContextClassLoader(); |
| Class factoryClass = loader.loadClass(socketFactoryClass); |
| |
| // done indirectly, we need to invoke the method using reflection. |
| // This retrieves a factory instance. |
| //Method getDefault = factoryClass.getMethod("getDefault", new Class[0]); //TODO check instantiation of socket factory |
| Object defFactory = factoryClass.newInstance();// getDefault.invoke(new Object(), new Object[0]); |
| // now that we have the factory, there are two different createSocket() calls we use, |
| // depending on whether we have a localAddress override. |
| |
| if (localAddress != null && !layer) { |
| // retrieve the createSocket(String, int, InetAddress, int) method. |
| Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE }; |
| Method createSocket = factoryClass.getMethod("createSocket", createSocketSig); |
| |
| Object[] createSocketArgs = new Object[] { serverHost, portArg, localAddress, new Integer(localPort) }; |
| socket = (Socket)createSocket.invoke(defFactory, createSocketArgs); |
| break; |
| } |
| else { |
| if(layer) { |
| // retrieve the createSocket(String, int) method. |
| Class[] createSocketSig = new Class[] { Socket.class, String.class, Integer.TYPE, Boolean.TYPE }; |
| Method createSocket = factoryClass.getMethod("createSocket", createSocketSig); |
| |
| Object[] createSocketArgs = new Object[] { socket, serverHost, new Integer(serverPort), Boolean.TRUE }; |
| socket = (Socket)createSocket.invoke(defFactory, createSocketArgs); |
| break; |
| } else { |
| // retrieve the createSocket(String, int) method. |
| Class[] createSocketSig = new Class[] { String.class, Integer.TYPE }; |
| Method createSocket = factoryClass.getMethod("createSocket", createSocketSig); |
| |
| Object[] createSocketArgs = new Object[] { serverHost, portArg }; |
| socket = (Socket)createSocket.invoke(defFactory, createSocketArgs); |
| break; |
| } |
| |
| |
| } |
| } catch (Throwable e) { |
| // if we're allowed to fallback, then use the default factory and try this again. We only |
| // allow this to happen once. |
| if (fallback) { |
| debugOut("First attempt at creating "+(ssl?"":"non-")+"SSL socket failed, falling back to default factory"); |
| socketFactoryClass = ssl?"javax.net.ssl.SSLSocketFactory":"javax.net.SocketFactory"; |
| fallback = false; |
| continue; |
| } |
| // we have an exception. We're going to throw an IOException, which may require unwrapping |
| // or rewrapping the exception. |
| else { |
| // we have an exception from the reflection, so unwrap the base exception |
| if (e instanceof InvocationTargetException) { |
| e = ((InvocationTargetException)e).getTargetException(); |
| } |
| |
| debugOut("Failure creating "+(ssl?"":"non-")+"SSL socket", e); |
| // throw this as an IOException, with the original exception attached. |
| IOException ioe = new IOException("Error connecting to " + serverHost + ", " + serverPort); |
| ioe.initCause(e); |
| throw ioe; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| private void createSocketFromFactory(SocketFactory sf, boolean layer) throws IOException { |
| |
| if(sf instanceof SSLSocketFactory && layer) { |
| socket = ((SSLSocketFactory) sf).createSocket(socket, serverHost, serverPort, true); |
| return; |
| } |
| |
| if (localAddress != null) { |
| socket = sf.createSocket(serverHost, serverPort, localAddress, localPort); |
| } else |
| { |
| socket = sf.createSocket(serverHost, serverPort); |
| } |
| } |
| |
| private boolean createSocketFromConfiguredFactoryInstance(boolean ssl, boolean layer) throws IOException { |
| |
| |
| |
| if (ssl) { |
| Object sfProp = props.getPropertyAsObject(MAIL_SSL_FACTORY); |
| if (sfProp != null && sfProp instanceof SSLSocketFactory) { |
| createSocketFromFactory((SSLSocketFactory) sfProp, layer); |
| debugOut("Creating "+(ssl?"":"non-")+"SSL "+(layer?"layered":"non-layered")+" socket using a instance of factory " + sfProp.getClass()+ " listening"); |
| return true; |
| } |
| } else { |
| Object sfProp = props.getPropertyAsObject(MAIL_FACTORY); |
| if (sfProp != null && sfProp instanceof SocketFactory) { |
| createSocketFromFactory((SocketFactory) sfProp, layer); |
| debugOut("Creating "+(ssl?"":"non-")+"SSL "+(layer?"layered":"non-layered")+" socket using a instance of factory " + sfProp.getClass()+ " listening"); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private void createSSLSocketFromSSLContext(boolean layer) throws IOException{ |
| |
| debugOut("Creating "+(layer?"layered ":"non-layered ")+"SSL socket using SSL Context"); |
| |
| |
| try { |
| SSLContext sslcontext = SSLContext.getInstance("TLS"); |
| |
| String sslTrust = props.getProperty(MAIL_SSL_TRUST); |
| |
| TrustManager trustManager = null; |
| |
| if(sslTrust != null) { |
| if(sslTrust.equals("*")) { |
| trustManager = new SSLTrustManager(null, true); //trust all |
| } else |
| { |
| String[] trustedHosts = sslTrust.split("\\s+"); |
| trustManager = new SSLTrustManager(trustedHosts, false); //trust some |
| |
| if(serverHost == null || serverHost.isEmpty() || !Arrays.asList(trustedHosts).contains(serverHost)) { |
| throw new IOException("Server is not trusted: " + serverHost); |
| } |
| |
| } |
| } else { |
| trustManager = new SSLTrustManager(null, false); //default |
| |
| } |
| |
| sslcontext.init(null, new TrustManager[]{trustManager}, null); |
| |
| createSocketFromFactory(sslcontext.getSocketFactory(), layer); |
| } catch (KeyManagementException e) { |
| //cannot happen |
| throw new IOException(e); |
| } catch (NoSuchAlgorithmException e) { |
| //cannot happen |
| throw new IOException(e); |
| } |
| } |
| |
| private void createSocket(boolean ssl) throws IOException { |
| |
| if(createSocketFromConfiguredFactoryInstance(ssl, false)) { |
| return; |
| } |
| |
| if(createSocketFromFactory(ssl, false)) { |
| return; |
| } |
| |
| if(!ssl) { |
| createSocketFromFactory(SocketFactory.getDefault(), false); |
| return; |
| } |
| |
| |
| createSSLSocketFromSSLContext(false); |
| } |
| |
| |
| /** |
| * Creates a connected SSL socket for an initial SSL connection. |
| * |
| * @exception MessagingException |
| */ |
| protected void getConnectedSSLSocket() throws IOException { |
| debugOut("Attempting SSL socket connection to server " + serverHost + ":" + serverPort); |
| // the socket factory can be specified via a protocol property, a session property, and if all else |
| // fails (which it usually does), we fall back to the standard factory class. |
| |
| // make sure this is null |
| socket = null; |
| |
| createSocket(true); |
| |
| // and set the timeout value |
| if (timeout >= 0) { |
| socket.setSoTimeout(timeout); |
| } |
| |
| // if there is a list of protocols specified, we need to break this down into |
| // the individual names |
| String protocols = props.getProperty(MAIL_SSL_PROTOCOLS); |
| if (protocols != null) { |
| ArrayList list = new ArrayList(); |
| StringTokenizer t = new StringTokenizer(protocols); |
| |
| while (t.hasMoreTokens()) { |
| list.add(t.nextToken()); |
| } |
| |
| ((SSLSocket)socket).setEnabledProtocols((String[])list.toArray(new String[list.size()])); |
| } |
| |
| // and do the same for any cipher suites |
| String suites = props.getProperty(MAIL_SSL_CIPHERSUITES); |
| if (suites != null) { |
| ArrayList list = new ArrayList(); |
| StringTokenizer t = new StringTokenizer(suites); |
| |
| while (t.hasMoreTokens()) { |
| list.add(t.nextToken()); |
| } |
| |
| ((SSLSocket)socket).setEnabledCipherSuites((String[])list.toArray(new String[list.size()])); |
| } |
| } |
| |
| |
| /** |
| * Switch the connection to using TLS level security, |
| * switching to an SSL socket. |
| */ |
| protected void getConnectedTLSSocket() throws MessagingException { |
| // it worked, now switch the socket into TLS mode |
| try { |
| |
| // we use the same target and port as the current connection. |
| serverHost = socket.getInetAddress().getHostName(); |
| serverPort = socket.getPort(); |
| |
| // the socket factory can be specified via a session property. By default, we use |
| // the native SSL factory. |
| if(createSocketFromConfiguredFactoryInstance(true, true)) { |
| debugOut("TLS socket factory configured as instance"); |
| } else if(createSocketFromFactory(true, true)) { |
| debugOut("TLS socket factory configured as class"); |
| } else { |
| debugOut("TLS socket factory from SSLContext"); |
| createSSLSocketFromSSLContext(true); |
| } |
| |
| // if this is an instance of SSLSocket (very common), try setting the protocol to be |
| // "TLSv1". If this is some other class because of a factory override, we'll just have to |
| // accept that things will work. |
| if (socket instanceof SSLSocket) { |
| String[] suites = ((SSLSocket)socket).getSupportedCipherSuites(); |
| ((SSLSocket)socket).setEnabledCipherSuites(suites); |
| ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1"} ); |
| ((SSLSocket)socket).setUseClientMode(true); |
| debugOut("Initiating STARTTLS handshake"); |
| ((SSLSocket)socket).startHandshake(); |
| } else { |
| throw new IOException("Socket is not an instance of SSLSocket, maybe wrong configured ssl factory?"); |
| } |
| |
| getConnectionStreams(); |
| debugOut("TLS connection established"); |
| } |
| catch (Exception e) { |
| debugOut("Failure attempting to convert connection to TLS", e); |
| throw new MessagingException("Unable to convert connection to SSL", e); |
| } |
| } |
| |
| |
| /** |
| * Set up the input and output streams for server communications once the |
| * socket connection has been made. |
| * |
| * @exception MessagingException |
| */ |
| protected void getConnectionStreams() throws MessagingException, IOException { |
| // and finally, as a last step, replace our input streams with the secure ones. |
| // now set up the input/output streams. |
| inputStream = new TraceInputStream(socket.getInputStream(), debugStream, debug, props.getBooleanProperty( |
| MAIL_ENCODE_TRACE, false)); |
| outputStream = new TraceOutputStream(socket.getOutputStream(), debugStream, debug, props.getBooleanProperty( |
| MAIL_ENCODE_TRACE, false)); |
| } |
| |
| |
| /** |
| * Close the server connection at termination. |
| */ |
| public void closeServerConnection() |
| { |
| try { |
| socket.close(); |
| } catch (IOException ignored) { |
| } |
| |
| socket = null; |
| inputStream = null; |
| outputStream = null; |
| } |
| |
| |
| /** |
| * Verify that we have a good connection before |
| * attempting to send a command. |
| * |
| * @exception MessagingException |
| */ |
| protected void checkConnected() throws MessagingException { |
| if (socket == null || !socket.isConnected()) { |
| throw new MessagingException("no connection"); |
| } |
| } |
| |
| |
| /** |
| * Retrieve the SASL realm used for DIGEST-MD5 authentication. |
| * This will either be explicitly set, or retrieved using the |
| * mail.imap.sasl.realm session property. |
| * |
| * @return The current realm information (which can be null). |
| */ |
| public String getSASLRealm() { |
| // if the realm is null, retrieve it using the realm session property. |
| if (realm == null) { |
| realm = props.getProperty(MAIL_SASL_REALM); |
| } |
| return realm; |
| } |
| |
| |
| /** |
| * Explicitly set the SASL realm used for DIGEST-MD5 authenticaiton. |
| * |
| * @param name The new realm name. |
| */ |
| public void setSASLRealm(String name) { |
| realm = name; |
| } |
| |
| |
| /** |
| * Get a list of the SASL mechanisms we're configured to accept. |
| * |
| * @return A list of mechanisms we're allowed to use. |
| */ |
| protected List getSaslMechanisms() { |
| if (mechanisms == null) { |
| mechanisms = new ArrayList(); |
| String mechList = props.getProperty(MAIL_SASL_MECHANISMS); |
| if (mechList != null) { |
| // the mechanisms are a blank or comma-separated list |
| StringTokenizer tokenizer = new StringTokenizer(mechList, " ,"); |
| |
| while (tokenizer.hasMoreTokens()) { |
| String mech = tokenizer.nextToken().toUpperCase(); |
| mechanisms.add(mech); |
| } |
| } |
| } |
| return mechanisms; |
| } |
| |
| |
| /** |
| * Get the list of authentication mechanisms the server |
| * is supposed to support. |
| * |
| * @return A list of the server supported authentication |
| * mechanisms. |
| */ |
| protected List getServerMechanisms() { |
| return authentications; |
| } |
| |
| |
| /** |
| * Merge the configured SASL mechanisms with the capabilities that the |
| * server has indicated it supports, returning a merged list that can |
| * be used for selecting a mechanism. |
| * |
| * @return A List representing the intersection of the configured list and the |
| * capabilities list. |
| */ |
| protected List selectSaslMechanisms() { |
| List configured = getSaslMechanisms(); |
| List supported = getServerMechanisms(); |
| |
| // if not restricted, then we'll select from anything supported. |
| if (configured.isEmpty()) { |
| return supported; |
| } |
| |
| List merged = new ArrayList(); |
| |
| // we might need a subset of the supported ones |
| for (int i = 0; i < configured.size(); i++) { |
| // if this is in both lists, add to the merged one. |
| String mech = (String)configured.get(i); |
| if (supported.contains(mech)) { |
| merged.add(mech); |
| } |
| } |
| return merged; |
| } |
| |
| |
| /** |
| * Process SASL-type authentication. |
| * |
| * @return An authenticator to process the login challenge/response handling. |
| * @exception MessagingException |
| */ |
| protected ClientAuthenticator getLoginAuthenticator() throws MessagingException { |
| |
| // get the list of mechanisms we're allowed to use. |
| List mechs = selectSaslMechanisms(); |
| |
| try { |
| String[] mechArray = (String[])mechs.toArray(new String[0]); |
| // create a SASLAuthenticator, if we can. A failure likely indicates we're not |
| // running on a Java 5 VM, and the Sasl API isn't available. |
| return new SASLAuthenticator(mechArray, session.getProperties(), protocol, serverHost, getSASLRealm(), authid, username, password); |
| } catch (Throwable e) { |
| } |
| |
| |
| // now go through the progression of mechanisms we support, from the most secure to the |
| // least secure. |
| |
| if (mechs.contains(AUTHENTICATION_DIGESTMD5)) { |
| return new DigestMD5Authenticator(serverHost, username, password, getSASLRealm()); |
| } |
| else if (mechs.contains(AUTHENTICATION_CRAMMD5)) { |
| return new CramMD5Authenticator(username, password); |
| } |
| else if (mechs.contains(AUTHENTICATION_LOGIN)) { |
| return new LoginAuthenticator(username, password); |
| } |
| else if (mechs.contains(AUTHENTICATION_PLAIN)) { |
| return new PlainAuthenticator(authid, username, password); |
| } |
| else { |
| // can't find a mechanism we support in common |
| return null; |
| } |
| } |
| |
| |
| /** |
| * Internal debug output routine. |
| * |
| * @param value The string value to output. |
| */ |
| protected void debugOut(String message) { |
| if (debug) { |
| debugStream.println(protocol + " DEBUG: " + message); |
| } |
| } |
| |
| /** |
| * Internal debugging routine for reporting exceptions. |
| * |
| * @param message A message associated with the exception context. |
| * @param e The received exception. |
| */ |
| protected void debugOut(String message, Throwable e) { |
| if (debug) { |
| debugOut("Received exception -> " + message); |
| debugOut("Exception message -> " + e.getMessage()); |
| e.printStackTrace(debugStream); |
| } |
| } |
| |
| |
| /** |
| * Test if this connection has a given capability. |
| * |
| * @param capability The capability name. |
| * |
| * @return true if this capability is in the list, false for a mismatch. |
| */ |
| public boolean hasCapability(String capability) { |
| return capabilities.containsKey(capability); |
| } |
| |
| /** |
| * Get the capabilities map. |
| * |
| * @return The capabilities map for the connection. |
| */ |
| public Map getCapabilities() { |
| return capabilities; |
| } |
| |
| |
| /** |
| * Test if the server supports a given mechanism. |
| * |
| * @param mech The mechanism name. |
| * |
| * @return true if the server has asserted support for the named |
| * mechanism. |
| */ |
| public boolean supportsMechanism(String mech) { |
| return authentications.contains(mech); |
| } |
| |
| |
| /** |
| * Retrieve the connection host. |
| * |
| * @return The host name. |
| */ |
| public String getHost() { |
| return serverHost; |
| } |
| |
| |
| /** |
| * Retrieve the local client host name. |
| * |
| * @return The string version of the local host name. |
| * @exception SMTPTransportException |
| */ |
| public String getLocalHost() throws MessagingException { |
| if (localHost == null) { |
| |
| if (localHost == null) { |
| localHost = props.getProperty(MAIL_LOCALHOST); |
| } |
| |
| if (localHost == null) { |
| localHost = props.getSessionProperty(MAIL_LOCALHOST); |
| } |
| |
| if (localHost == null) { |
| try { |
| localHost = InetAddress.getLocalHost().getHostName(); |
| } catch (UnknownHostException e) { |
| // fine, we're misconfigured - ignore |
| } |
| } |
| |
| if (localHost == null) { |
| throw new MessagingException("Can't get local hostname. " |
| + " Please correctly configure JDK/DNS or set mail.smtp.localhost"); |
| } |
| } |
| |
| return localHost; |
| } |
| |
| |
| /** |
| * Explicitly set the local host information. |
| * |
| * @param localHost |
| * The new localHost name. |
| */ |
| public void setLocalHost(String localHost) { |
| this.localHost = localHost; |
| } |
| |
| private class SSLTrustManager implements X509TrustManager { |
| |
| private final X509TrustManager defaultTrustManager; |
| |
| private final boolean trustAll; |
| private final String[] trustedHosts; |
| |
| SSLTrustManager(String[] trustedHosts, boolean trustAll) throws IOException{ |
| super(); |
| this.trustAll = trustAll; |
| this.trustedHosts = trustedHosts; |
| |
| try { |
| TrustManagerFactory defaultTrustManagerFactory = TrustManagerFactory.getInstance("X509"); |
| defaultTrustManagerFactory.init((KeyStore)null); |
| defaultTrustManager = (X509TrustManager) defaultTrustManagerFactory.getTrustManagers()[0]; |
| } catch (NoSuchAlgorithmException e) { |
| //cannot happen |
| throw new IOException(e); |
| } catch (KeyStoreException e) { |
| //cannot happen |
| throw new IOException(e); |
| } |
| |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[], java.lang.String) |
| */ |
| public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { |
| defaultTrustManager.checkClientTrusted(chain, authType); |
| |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[], java.lang.String) |
| */ |
| public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { |
| if (!trustAll || trustedHosts != null) { |
| defaultTrustManager.checkServerTrusted(chain, authType); |
| } |
| |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() |
| */ |
| public X509Certificate[] getAcceptedIssuers() { |
| return defaultTrustManager.getAcceptedIssuers(); |
| } |
| |
| } |
| } |