blob: af079ae19b3eb4b9355f877ac2c3159f81583263 [file] [log] [blame]
/**
* 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();
}
}
}