001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.geronimo.javamail.util;
019    
020    import java.io.IOException; 
021    import java.io.InputStream; 
022    import java.io.OutputStream; 
023    import java.io.PrintStream; 
024    import java.lang.reflect.InvocationTargetException;
025    import java.lang.reflect.Method;
026    import java.net.InetAddress;
027    import java.net.Socket; 
028    import java.net.UnknownHostException;
029    import java.util.ArrayList;
030    import java.util.List;
031    import java.util.Map;
032    import java.util.StringTokenizer; 
033    
034    import javax.mail.MessagingException;
035    import javax.mail.Session;
036    import javax.net.ssl.SSLSocket;
037    
038    import org.apache.geronimo.javamail.authentication.ClientAuthenticator; 
039    import org.apache.geronimo.javamail.authentication.CramMD5Authenticator; 
040    import org.apache.geronimo.javamail.authentication.DigestMD5Authenticator; 
041    import org.apache.geronimo.javamail.authentication.LoginAuthenticator; 
042    import org.apache.geronimo.javamail.authentication.PlainAuthenticator; 
043    import org.apache.geronimo.javamail.authentication.SASLAuthenticator; 
044    import org.apache.geronimo.javamail.util.CommandFailedException;      
045    import org.apache.geronimo.javamail.util.InvalidCommandException;      
046    
047    /**
048     * Base class for all mail Store/Transport connection.  Centralizes management
049     * of a lot of common connection handling.  Actual protcol-specific 
050     * functions are handled at the subclass level. 
051     */
052    public class MailConnection {
053        /**
054         * constants for EOL termination
055         */
056        protected static final char CR = '\r';
057        protected static final char LF = '\n';
058    
059        /**
060         * property keys for protocol properties.
061         */
062        protected static final String MAIL_AUTH = "auth";
063        protected static final String MAIL_PORT = "port";
064        protected static final String MAIL_LOCALHOST = "localhost";
065        protected static final String MAIL_STARTTLS_ENABLE = "starttls.enable";
066        protected static final String MAIL_SSL_ENABLE = "ssl.enable";
067        protected static final String MAIL_TIMEOUT = "timeout";
068        protected static final String MAIL_SASL_ENABLE = "sasl.enable";
069        protected static final String MAIL_SASL_REALM = "sasl.realm";
070        protected static final String MAIL_AUTHORIZATIONID = "sasl.authorizationid"; 
071        protected static final String MAIL_SASL_MECHANISMS = "sasl.mechanisms";
072        protected static final String MAIL_PLAIN_DISABLE = "auth.plain.disable";
073        protected static final String MAIL_LOGIN_DISABLE = "auth.login.disable";
074        protected static final String MAIL_FACTORY_CLASS = "socketFactory.class";
075        protected static final String MAIL_FACTORY_FALLBACK = "socketFactory.fallback";
076        protected static final String MAIL_FACTORY_PORT = "socketFactory.port";
077        protected static final String MAIL_SSL_PROTOCOLS = "ssl.protocols";
078        protected static final String MAIL_SSL_CIPHERSUITES = "ssl.ciphersuites";
079        protected static final String MAIL_LOCALADDRESS = "localaddress";
080        protected static final String MAIL_LOCALPORT = "localport";
081        protected static final String MAIL_ENCODE_TRACE = "encodetrace";
082    
083        protected static final int MIN_MILLIS = 1000 * 60;
084        protected static final int TIMEOUT = MIN_MILLIS * 5;
085        protected static final String DEFAULT_MAIL_HOST = "localhost";
086    
087        protected static final String CAPABILITY_STARTTLS = "STARTTLS";
088    
089        protected static final String AUTHENTICATION_PLAIN = "PLAIN";
090        protected static final String AUTHENTICATION_LOGIN = "LOGIN";
091        protected static final String AUTHENTICATION_CRAMMD5 = "CRAM-MD5";
092        protected static final String AUTHENTICATION_DIGESTMD5 = "DIGEST-MD5";
093        
094        // The mail Session we're associated with
095        protected Session session; 
096        // The protocol we're implementing 
097        protected String protocol; 
098        // There are usually SSL and non-SSL versions of these protocols.  This 
099        // indicates which version we're using.
100        protected boolean sslConnection; 
101        // This is the default port we should be using for making a connection.  Each 
102        // protocol (and each ssl version of the protocol) normally has a different default that 
103        // should be used. 
104        protected int defaultPort; 
105        
106        // a wrapper around our session to provide easier lookup of protocol 
107        // specific property values 
108        protected ProtocolProperties props; 
109        
110        // The target server host 
111        protected String serverHost;
112        // The target server port 
113        protected int serverPort; 
114        
115        // the connection socket...can be a plain socket or SSLSocket, if TLS is being used.
116        protected Socket socket;
117        
118        // our local host name
119        protected InetAddress localAddress;
120        // our local port value 
121        protected int localPort; 
122        // our local host name
123        protected String localHost;
124        
125        // our timeout value 
126        protected int timeout; 
127        
128        // our login username 
129        protected String username; 
130        // our login password 
131        protected String password; 
132        // our SASL security realm 
133        protected String realm; 
134        // our authorization id 
135        protected String authid; 
136        
137        // input stream used to read data.  If Sasl is in use, this might be other than the
138        // direct access to the socket input stream.
139        protected InputStream inputStream;
140        // the other end of the connection pipeline.
141        protected OutputStream outputStream;
142    
143        // our session provided debug output stream.
144        protected PrintStream debugStream;
145        // our debug flag (passed from the hosting transport)
146        protected boolean debug;
147    
148        // list of authentication mechanisms supported by the server
149        protected List authentications;
150        // map of server extension arguments
151        protected Map capabilities;        
152        // property list of authentication mechanisms
153        protected List mechanisms; 
154        
155        protected MailConnection(ProtocolProperties props) 
156        {
157            // this is our properties retriever utility, which will look up 
158            // properties based on the appropriate "mail.protocol." prefix. 
159            // this also holds other information we might need for access, such as 
160            // the protocol name and the Session; 
161            this.props = props; 
162            this.protocol = props.getProtocol(); 
163            this.session = props.getSession(); 
164            this.sslConnection = props.getSSLConnection(); 
165            this.defaultPort = props.getDefaultPort(); 
166            
167            // initialize our debug settings from the session 
168            debug = session.getDebug(); 
169            debugStream = session.getDebugOut();
170        }
171        
172        
173        /**
174         * Connect to the server and do the initial handshaking.
175         * 
176         * @param host     The target host name.
177         * @param port     The target port
178         * @param username The connection username (can be null)
179         * @param password The authentication password (can be null).
180         * 
181         * @return true if we were able to obtain a connection and 
182         *         authenticate.
183         * @exception MessagingException
184         */
185        public boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
186            // NOTE:  We don't check for the username/password being null at this point.  It's possible that 
187            // the server will send back a PREAUTH response, which means we don't need to go through login 
188            // processing.  We'll need to check the capabilities response after we make the connection to decide 
189            // if logging in is necesssary. 
190            
191            // save this for subsequent connections.  All pool connections will use this info.
192            // if the port is defaulted, then see if we have something configured in the session.
193            // if not configured, we just use the default default.
194            if (port == -1) {
195                // check for a property and fall back on the default if it's not set.
196                port = props.getIntProperty(MAIL_PORT, props.getDefaultPort());
197                // it's possible that -1 might have been explicitly set, so one last check. 
198                if (port == -1) {
199                    port = props.getDefaultPort(); 
200                }
201            }
202            
203            // Before we do anything, let's make sure that we succesfully received a host
204            if ( host == null ) {
205                    host = DEFAULT_MAIL_HOST;
206            }
207            
208            this.serverHost = host;
209            this.serverPort = port;
210            this.username = username;
211            this.password = password;
212            
213            // make sure we have the realm information 
214            realm = props.getProperty(MAIL_SASL_REALM); 
215            // get an authzid value, if we have one.  The default is to use the username.
216            authid = props.getProperty(MAIL_AUTHORIZATIONID, username);
217            return true; 
218        }
219        
220        
221        /**
222         * Establish a connection using an existing socket. 
223         * 
224         * @param s      The socket to use.
225         */
226        public void connect(Socket s) {
227            // just save the socket connection 
228            this.socket = s; 
229        }
230        
231        
232        /**
233         * Create a transport connection object and connect it to the
234         * target server.
235         *
236         * @exception MessagingException
237         */
238        protected void getConnection() throws IOException, MessagingException
239        {
240            // We might have been passed a socket to connect with...if not, we need to create one of the correct type.
241            if (socket == null) {
242                // get the connection properties that control how we set this up. 
243                getConnectionProperties(); 
244                // if this is the SSL version of the protocol, we start with an SSLSocket
245                if (sslConnection) {
246                    getConnectedSSLSocket();
247                }
248                else
249                {
250                    getConnectedSocket();
251                }
252            }
253            // if we already have a socket, get some information from it and override what we've been passed.
254            else {
255                localPort = socket.getPort();
256                localAddress = socket.getInetAddress();
257            }
258            
259            // now set up the input/output streams.
260            getConnectionStreams(); 
261        }
262    
263        /**
264         * Get common connection properties before creating a connection socket. 
265         */
266        protected void getConnectionProperties() {
267    
268            // there are several protocol properties that can be set to tune the created socket.  We need to
269            // retrieve those bits before creating the socket.
270            timeout = props.getIntProperty(MAIL_TIMEOUT, -1);
271            localAddress = null;
272            // see if we have a local address override.
273            String localAddrProp = props.getProperty(MAIL_LOCALADDRESS);
274            if (localAddrProp != null) {
275                try {
276                    localAddress = InetAddress.getByName(localAddrProp);
277                } catch (UnknownHostException e) {
278                    // not much we can do if this fails. 
279                }
280            }
281    
282            // check for a local port...default is to allow socket to choose.
283            localPort = props.getIntProperty(MAIL_LOCALPORT, 0);
284        }
285        
286    
287        /**
288         * Creates a connected socket
289         *
290         * @exception MessagingException
291         */
292        protected void getConnectedSocket() throws IOException {
293            debugOut("Attempting plain socket connection to server " + serverHost + ":" + serverPort);
294    
295            // check the properties that control how we connect. 
296            getConnectionProperties(); 
297    
298            // the socket factory can be specified via a session property.  By default, we just directly
299            // instantiate a socket without using a factory.
300            String socketFactory = props.getProperty(MAIL_FACTORY_CLASS);
301    
302            // make sure the socket is nulled out to start 
303            socket = null;
304    
305            // if there is no socket factory defined (normal), we just create a socket directly.
306            if (socketFactory == null) {
307                socket = new Socket(serverHost, serverPort, localAddress, localPort);
308            }
309    
310            else {
311                try {
312                    int socketFactoryPort = props.getIntProperty(MAIL_FACTORY_PORT, -1);
313    
314                    // we choose the port used by the socket based on overrides.
315                    Integer portArg = new Integer(socketFactoryPort == -1 ? serverPort : socketFactoryPort);
316    
317                    // use the current context loader to resolve this.
318                    ClassLoader loader = Thread.currentThread().getContextClassLoader();
319                    Class factoryClass = loader.loadClass(socketFactory);
320    
321                    // done indirectly, we need to invoke the method using reflection.
322                    // This retrieves a factory instance.
323                    Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
324                    Object defFactory = getDefault.invoke(new Object(), new Object[0]);
325    
326                    // now that we have the factory, there are two different createSocket() calls we use,
327                    // depending on whether we have a localAddress override.
328    
329                    if (localAddress != null) {
330                        // retrieve the createSocket(String, int, InetAddress, int) method.
331                        Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
332                        Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
333    
334                        Object[] createSocketArgs = new Object[] { serverHost, portArg, localAddress, new Integer(localPort) };
335                        socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
336                    }
337                    else {
338                        // retrieve the createSocket(String, int) method.
339                        Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
340                        Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
341    
342                        Object[] createSocketArgs = new Object[] { serverHost, portArg };
343                        socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
344                    }
345                } catch (Throwable e) {
346                    // if a socket factor is specified, then we may need to fall back to a default.  This behavior
347                    // is controlled by (surprise) more session properties.
348                    if (props.getBooleanProperty(MAIL_FACTORY_FALLBACK, false)) {
349                        debugOut("First plain socket attempt failed, falling back to default factory", e);
350                        socket = new Socket(serverHost, serverPort, localAddress, localPort);
351                    }
352                    // we have an exception.  We're going to throw an IOException, which may require unwrapping
353                    // or rewrapping the exception.
354                    else {
355                        // we have an exception from the reflection, so unwrap the base exception
356                        if (e instanceof InvocationTargetException) {
357                            e = ((InvocationTargetException)e).getTargetException();
358                        }
359    
360                        debugOut("Plain socket creation failure", e);
361    
362                        // throw this as an IOException, with the original exception attached.
363                        IOException ioe = new IOException("Error connecting to " + serverHost + ", " + serverPort);
364                        ioe.initCause(e);
365                        throw ioe;
366                    }
367                }
368            }
369            // if we have a timeout value, set that before returning 
370            if (timeout >= 0) {
371                socket.setSoTimeout(timeout);
372            }
373        }
374    
375    
376        /**
377         * Creates a connected SSL socket for an initial SSL connection.
378         *
379         * @exception MessagingException
380         */
381        protected void getConnectedSSLSocket() throws IOException {
382            debugOut("Attempting SSL socket connection to server " + serverHost + ":" + serverPort);
383            // the socket factory can be specified via a protocol property, a session property, and if all else
384            // fails (which it usually does), we fall back to the standard factory class.
385            String socketFactory = props.getProperty(MAIL_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
386    
387            // make sure this is null 
388            socket = null;
389    
390            // we'll try this with potentially two different factories if we're allowed to fall back.
391            boolean fallback = props.getBooleanProperty(MAIL_FACTORY_FALLBACK, false);
392    
393            while (true) {
394                try {
395                    debugOut("Creating SSL socket using factory " + socketFactory);
396    
397                    int socketFactoryPort = props.getIntProperty(MAIL_FACTORY_PORT, -1);
398    
399                    // we choose the port used by the socket based on overrides.
400                    Integer portArg = new Integer(socketFactoryPort == -1 ? serverPort : socketFactoryPort);
401    
402                    // use the current context loader to resolve this.
403                    ClassLoader loader = Thread.currentThread().getContextClassLoader();
404                    Class factoryClass = loader.loadClass(socketFactory);
405    
406                    // done indirectly, we need to invoke the method using reflection.
407                    // This retrieves a factory instance.
408                    Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
409                    Object defFactory = getDefault.invoke(new Object(), new Object[0]);
410    
411                    // now that we have the factory, there are two different createSocket() calls we use,
412                    // depending on whether we have a localAddress override.
413    
414                    if (localAddress != null) {
415                        // retrieve the createSocket(String, int, InetAddress, int) method.
416                        Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
417                        Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
418    
419                        Object[] createSocketArgs = new Object[] { serverHost, portArg, localAddress, new Integer(localPort) };
420                        socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
421                        break; 
422                    }
423                    else {
424                        // retrieve the createSocket(String, int) method.
425                        Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
426                        Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
427    
428                        Object[] createSocketArgs = new Object[] { serverHost, portArg };
429                        socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
430                        break; 
431                    }
432                } catch (Throwable e) {
433                    // if we're allowed to fallback, then use the default factory and try this again.  We only
434                    // allow this to happen once.
435                    if (fallback) {
436                        debugOut("First attempt at creating SSL socket failed, falling back to default factory");
437                        socketFactory = "javax.net.ssl.SSLSocketFactory";
438                        fallback = false;
439                        continue;
440                    }
441                    // we have an exception.  We're going to throw an IOException, which may require unwrapping
442                    // or rewrapping the exception.
443                    else {
444                        // we have an exception from the reflection, so unwrap the base exception
445                        if (e instanceof InvocationTargetException) {
446                            e = ((InvocationTargetException)e).getTargetException();
447                        }
448    
449                        debugOut("Failure creating SSL socket", e);
450                        // throw this as an IOException, with the original exception attached.
451                        IOException ioe = new IOException("Error connecting to " + serverHost + ", " + serverPort);
452                        ioe.initCause(e);
453                        throw ioe;
454                    }
455                }
456            }
457            // and set the timeout value 
458            if (timeout >= 0) {
459                socket.setSoTimeout(timeout);
460            }
461            
462            // if there is a list of protocols specified, we need to break this down into 
463            // the individual names 
464            String protocols = props.getProperty(MAIL_SSL_PROTOCOLS); 
465            if (protocols != null) {
466                ArrayList<String> list = new ArrayList<String>(); 
467                StringTokenizer t = new StringTokenizer(protocols); 
468                
469                while (t.hasMoreTokens()) {
470                    list.add(t.nextToken()); 
471                }
472                
473                ((SSLSocket)socket).setEnabledProtocols(list.toArray(new String[list.size()])); 
474            }
475            
476            // and do the same for any cipher suites 
477            String suites = props.getProperty(MAIL_SSL_CIPHERSUITES); 
478            if (suites != null) {
479                ArrayList<String> list = new ArrayList<String>(); 
480                StringTokenizer t = new StringTokenizer(suites); 
481                
482                while (t.hasMoreTokens()) {
483                    list.add(t.nextToken()); 
484                }
485                
486                ((SSLSocket)socket).setEnabledCipherSuites(list.toArray(new String[list.size()])); 
487            }
488        }
489    
490    
491        /**
492         * Switch the connection to using TLS level security,
493         * switching to an SSL socket.
494         */
495        protected void getConnectedTLSSocket() throws MessagingException {
496            // it worked, now switch the socket into TLS mode
497            try {
498    
499                // we use the same target and port as the current connection.
500                String host = socket.getInetAddress().getHostName();
501                int port = socket.getPort();
502    
503                // the socket factory can be specified via a session property.  By default, we use
504                // the native SSL factory.
505                String socketFactory = props.getProperty(MAIL_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
506    
507                // use the current context loader to resolve this.
508                ClassLoader loader = Thread.currentThread().getContextClassLoader();
509                Class factoryClass = loader.loadClass(socketFactory);
510    
511                // done indirectly, we need to invoke the method using reflection.
512                // This retrieves a factory instance.
513                Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
514                Object defFactory = getDefault.invoke(new Object(), new Object[0]);
515    
516                // now we need to invoke createSocket()
517                Class[] createSocketSig = new Class[] { Socket.class, String.class, Integer.TYPE, Boolean.TYPE };
518                Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
519    
520                Object[] createSocketArgs = new Object[] { socket, host, new Integer(port), Boolean.TRUE };
521    
522                // and finally create the socket
523                Socket sslSocket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
524    
525                // if this is an instance of SSLSocket (very common), try setting the protocol to be
526                // "TLSv1".  If this is some other class because of a factory override, we'll just have to
527                // accept that things will work.
528                if (sslSocket instanceof SSLSocket) {
529                    ((SSLSocket)sslSocket).setEnabledProtocols(new String[] {"TLSv1"} );
530                    ((SSLSocket)sslSocket).setUseClientMode(true);
531                    debugOut("Initiating STARTTLS handshake"); 
532                    ((SSLSocket)sslSocket).startHandshake();
533                }
534    
535    
536                // this is our active socket now
537                socket = sslSocket;
538                getConnectionStreams(); 
539                debugOut("TLS connection established"); 
540            }
541            catch (Exception e) {
542                debugOut("Failure attempting to convert connection to TLS", e);
543                throw new MessagingException("Unable to convert connection to SSL", e);
544            }
545        }
546        
547        
548        /**
549         * Set up the input and output streams for server communications once the 
550         * socket connection has been made. 
551         * 
552         * @exception MessagingException
553         */
554        protected void getConnectionStreams() throws MessagingException, IOException {
555            // and finally, as a last step, replace our input streams with the secure ones.
556            // now set up the input/output streams.
557            inputStream = new TraceInputStream(socket.getInputStream(), debugStream, debug, props.getBooleanProperty(
558                    MAIL_ENCODE_TRACE, false));
559            outputStream = new TraceOutputStream(socket.getOutputStream(), debugStream, debug, props.getBooleanProperty(
560                    MAIL_ENCODE_TRACE, false));
561        }
562        
563    
564        /**
565         * Close the server connection at termination.
566         */
567        public void closeServerConnection()
568        {
569            try {
570                socket.close();
571            } catch (IOException ignored) {
572            }
573    
574            socket = null;
575            inputStream = null;
576            outputStream = null;
577        }
578        
579        
580        /**
581         * Verify that we have a good connection before 
582         * attempting to send a command. 
583         * 
584         * @exception MessagingException
585         */
586        protected void checkConnected() throws MessagingException {
587            if (socket == null || !socket.isConnected()) {
588                throw new MessagingException("no connection");
589            }
590        }
591    
592    
593        /**
594         * Retrieve the SASL realm used for DIGEST-MD5 authentication.
595         * This will either be explicitly set, or retrieved using the
596         * mail.imap.sasl.realm session property.
597         *
598         * @return The current realm information (which can be null).
599         */
600        public String getSASLRealm() {
601            // if the realm is null, retrieve it using the realm session property.
602            if (realm == null) {
603                realm = props.getProperty(MAIL_SASL_REALM);
604            }
605            return realm;
606        }
607    
608    
609        /**
610         * Explicitly set the SASL realm used for DIGEST-MD5 authenticaiton.
611         *
612         * @param name   The new realm name.
613         */
614        public void setSASLRealm(String name) {
615            realm = name;
616        }
617    
618    
619        /**
620         * Get a list of the SASL mechanisms we're configured to accept.
621         *
622         * @return A list of mechanisms we're allowed to use.
623         */
624        protected List getSaslMechanisms() {
625            if (mechanisms == null) {
626                mechanisms = new ArrayList();
627                String mechList = props.getProperty(MAIL_SASL_MECHANISMS);
628                if (mechList != null) {
629                    // the mechanisms are a blank or comma-separated list
630                    StringTokenizer tokenizer = new StringTokenizer(mechList, " ,");
631    
632                    while (tokenizer.hasMoreTokens()) {
633                        String mech = tokenizer.nextToken().toUpperCase();
634                        mechanisms.add(mech);
635                    }
636                }
637            }
638            return mechanisms;
639        }
640        
641        
642        /**
643         * Get the list of authentication mechanisms the server
644         * is supposed to support. 
645         * 
646         * @return A list of the server supported authentication 
647         *         mechanisms.
648         */
649        protected List getServerMechanisms() {
650            return authentications; 
651        }
652        
653        
654        /**
655         * Merge the configured SASL mechanisms with the capabilities that the 
656         * server has indicated it supports, returning a merged list that can 
657         * be used for selecting a mechanism. 
658         * 
659         * @return A List representing the intersection of the configured list and the 
660         *         capabilities list.
661         */
662        protected List selectSaslMechanisms() {
663            List configured = getSaslMechanisms(); 
664            List supported = getServerMechanisms(); 
665            
666            // if not restricted, then we'll select from anything supported. 
667            if (configured.isEmpty()) {
668                return supported; 
669            }
670            
671            List merged = new ArrayList(); 
672            
673            // we might need a subset of the supported ones 
674            for (int i = 0; i < configured.size(); i++) {
675                // if this is in both lists, add to the merged one. 
676                String mech = (String)configured.get(i); 
677                if (supported.contains(mech)) {
678                    merged.add(mech); 
679                }
680            }
681            return merged; 
682        }
683    
684    
685        /**
686         * Process SASL-type authentication.
687         *
688         * @return An authenticator to process the login challenge/response handling.    
689         * @exception MessagingException
690         */
691        protected ClientAuthenticator getLoginAuthenticator() throws MessagingException {
692            
693            // get the list of mechanisms we're allowed to use. 
694            List mechs = selectSaslMechanisms(); 
695    
696            try {
697                String[] mechArray = (String[])mechs.toArray(new String[0]); 
698                // create a SASLAuthenticator, if we can.  A failure likely indicates we're not 
699                // running on a Java 5 VM, and the Sasl API isn't available. 
700                return new SASLAuthenticator(mechArray, session.getProperties(), protocol, serverHost, getSASLRealm(), authid, username, password); 
701            } catch (Throwable e) {
702            }
703            
704    
705            // now go through the progression of mechanisms we support, from the most secure to the
706            // least secure.
707    
708            if (mechs.contains(AUTHENTICATION_DIGESTMD5)) {
709                return new DigestMD5Authenticator(serverHost, username, password, getSASLRealm());
710            }
711            else if (mechs.contains(AUTHENTICATION_CRAMMD5)) {
712                return new CramMD5Authenticator(username, password);
713            }
714            else if (mechs.contains(AUTHENTICATION_LOGIN)) {
715                return new LoginAuthenticator(username, password);
716            }
717            else if (mechs.contains(AUTHENTICATION_PLAIN)) {
718                return new PlainAuthenticator(username, password);
719            }
720            else {
721                // can't find a mechanism we support in common
722                return null;  
723            }
724        }
725        
726        
727        /**
728         * Internal debug output routine.
729         *
730         * @param value  The string value to output.
731         */
732        protected void debugOut(String message) {
733            if (debug) {
734                debugStream.println(protocol + " DEBUG: " + message);
735            }
736        }
737    
738        /**
739         * Internal debugging routine for reporting exceptions.
740         *
741         * @param message A message associated with the exception context.
742         * @param e       The received exception.
743         */
744        protected void debugOut(String message, Throwable e) {
745            if (debug) {
746                debugOut("Received exception -> " + message);
747                debugOut("Exception message -> " + e.getMessage());
748                e.printStackTrace(debugStream);
749            }
750        }
751        
752        
753        /**
754         * Test if this connection has a given capability. 
755         * 
756         * @param capability The capability name.
757         * 
758         * @return true if this capability is in the list, false for a mismatch. 
759         */
760        public boolean hasCapability(String capability) {
761            return capabilities.containsKey(capability); 
762        }
763        
764        /**
765         * Get the capabilities map. 
766         * 
767         * @return The capabilities map for the connection. 
768         */
769        public Map getCapabilities() {
770            return capabilities; 
771        }
772        
773        
774        /**
775         * Test if the server supports a given mechanism. 
776         * 
777         * @param mech   The mechanism name.
778         * 
779         * @return true if the server has asserted support for the named 
780         *         mechanism.
781         */
782        public boolean supportsMechanism(String mech) {
783            return authentications.contains(mech); 
784        }
785        
786        
787        /**
788         * Retrieve the connection host. 
789         * 
790         * @return The host name. 
791         */
792        public String getHost() {
793            return serverHost; 
794        }
795        
796    
797        /**
798         * Retrieve the local client host name.
799         *
800         * @return The string version of the local host name.
801         * @exception SMTPTransportException
802         */
803        public String getLocalHost() throws MessagingException {
804            if (localHost == null) {
805    
806                try {
807                    localHost = InetAddress.getLocalHost().getHostName();
808                } catch (UnknownHostException e) {
809                    // fine, we're misconfigured - ignore
810                }
811    
812                if (localHost == null) {
813                    localHost = props.getProperty(MAIL_LOCALHOST);
814                }
815    
816                if (localHost == null) {
817                    localHost = props.getSessionProperty(MAIL_LOCALHOST);
818                }
819    
820                if (localHost == null) {
821                    throw new MessagingException("Can't get local hostname. "
822                            + " Please correctly configure JDK/DNS or set mail.smtp.localhost");
823                }
824            }
825    
826            return localHost;
827        }
828    
829        
830        /**
831         * Explicitly set the local host information.
832         *
833         * @param localHost
834         *            The new localHost name.
835         */
836        public void setLocalHost(String localHost) {
837            this.localHost = localHost;
838        }
839    }