geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java [63:971]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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";
    protected static final String AUTHENTICATION_XOAUTH2 = "XOAUTH2";
    
    // 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);
        }
        
        String[] protocols = getFromWhitespaceSeparatedProperty(MAIL_SSL_PROTOCOLS);
        if(protocols != null) {
            ((SSLSocket)socket).setEnabledProtocols(protocols);
        }
        
        // and do the same for any cipher suites 
        String[] suites = getFromWhitespaceSeparatedProperty(MAIL_SSL_CIPHERSUITES);
        if (suites != null) {
            ((SSLSocket)socket).setEnabledCipherSuites(suites);
        }
    }

    /**
     * If there is a list specified, we need to break this down into the individual names
     *
     * @return {@code null}, if no list items are specified in the given property separated by whitespaces.
     */
    protected String[] getFromWhitespaceSeparatedProperty(String propertyName) {
        String property = props.getProperty(propertyName);
        if (property != null) {
            ArrayList<String> list = new ArrayList<>();
            StringTokenizer t = new StringTokenizer(property);

            while (t.hasMoreTokens()) {
                list.add(t.nextToken());
            }

            return list.toArray(new String[0]);
        }
        return null;
    }

    /**
     * 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.
            // 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 = getFromWhitespaceSeparatedProperty(MAIL_SSL_CIPHERSUITES);
                if (suites == null) {
                    // use all supported ciphers as no custom ciphers were specified
                    suites = ((SSLSocket)socket).getSupportedCipherSuites();
                    debugOut("No custom ciphers are specified, using all supported ciphers of the given SSLSocket: " + Arrays.toString(suites));
                }
                ((SSLSocket)socket).setEnabledCipherSuites(suites);
                String[] protocols = getFromWhitespaceSeparatedProperty(MAIL_SSL_PROTOCOLS);
                if(protocols != null) {
                    ((SSLSocket)socket).setEnabledProtocols(protocols);
                } else {
                    // no custom protocols were specified; use it as is.
                    debugOut("No custom protocols specified, using the enabled protocols of the given SSLSocket: " + Arrays.toString(((SSLSocket) socket).getEnabledProtocols()));
                }
                ((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 TLS", 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 message  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 MessagingException
     */
    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();
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



geronimo-mail_2.1/geronimo-mail_2.1_provider/src/main/java/org/apache/geronimo/mail/util/MailConnection.java [63:971]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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";
    protected static final String AUTHENTICATION_XOAUTH2 = "XOAUTH2";
    
    // 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);
        }
        
        String[] protocols = getFromWhitespaceSeparatedProperty(MAIL_SSL_PROTOCOLS);
        if(protocols != null) {
            ((SSLSocket)socket).setEnabledProtocols(protocols);
        }
        
        // and do the same for any cipher suites 
        String[] suites = getFromWhitespaceSeparatedProperty(MAIL_SSL_CIPHERSUITES);
        if (suites != null) {
            ((SSLSocket)socket).setEnabledCipherSuites(suites);
        }
    }

    /**
     * If there is a list specified, we need to break this down into the individual names
     *
     * @return {@code null}, if no list items are specified in the given property separated by whitespaces.
     */
    protected String[] getFromWhitespaceSeparatedProperty(String propertyName) {
        String property = props.getProperty(propertyName);
        if (property != null) {
            ArrayList<String> list = new ArrayList<>();
            StringTokenizer t = new StringTokenizer(property);

            while (t.hasMoreTokens()) {
                list.add(t.nextToken());
            }

            return list.toArray(new String[0]);
        }
        return null;
    }

    /**
     * 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.
            // 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 = getFromWhitespaceSeparatedProperty(MAIL_SSL_CIPHERSUITES);
                if (suites == null) {
                    // use all supported ciphers as no custom ciphers were specified
                    suites = ((SSLSocket)socket).getSupportedCipherSuites();
                    debugOut("No custom ciphers are specified, using all supported ciphers of the given SSLSocket: " + Arrays.toString(suites));
                }
                ((SSLSocket)socket).setEnabledCipherSuites(suites);
                String[] protocols = getFromWhitespaceSeparatedProperty(MAIL_SSL_PROTOCOLS);
                if(protocols != null) {
                    ((SSLSocket)socket).setEnabledProtocols(protocols);
                } else {
                    // no custom protocols were specified; use it as is.
                    debugOut("No custom protocols specified, using the enabled protocols of the given SSLSocket: " + Arrays.toString(((SSLSocket) socket).getEnabledProtocols()));
                }
                ((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 TLS", 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 message  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 MessagingException
     */
    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();
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



