GERONIMO-4174 Restructure the SMTPTransport class to use the comment MailConnection class. 



git-svn-id: https://svn.apache.org/repos/asf/geronimo/javamail/trunk/geronimo-javamail_1.4@673649 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java
index 00e7e8b..de490dc 100644
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java
@@ -50,9 +50,6 @@
 
 /**
  * Simple implementation of NNTP transport. Just does plain RFC977-ish delivery.
- * <p/> There is no way to indicate failure for a given recipient (it's possible
- * to have a recipient address rejected). The sun impl throws exceptions even if
- * others successful), but maybe we do a different way... <p/>
  * 
  * @version $Rev$ $Date$
  */
@@ -79,9 +76,6 @@
     // the last response line received from the server.
     protected NNTPReply lastServerResponse = null;
 
-    // map of server extension arguments
-    protected HashMap serverExtensionArgs;
-
     // the welcome string from the server.
     protected String welcomeString = null;
     
@@ -318,8 +312,8 @@
      *         supported.
      */
     public String extensionParameter(String name) {
-        if (serverExtensionArgs != null) {
-            return (String) serverExtensionArgs.get(name);
+        if (capabilities != null) {
+            return (String) capabilities.get(name);
         }
         return null;
     }
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java
index 93f4060..ec399c4 100644
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java
@@ -254,7 +254,7 @@
     public void close() throws MessagingException {
         // This is done to ensure proper event notification.
         super.close();
+        // NB:  We reuse the connection if asked to reconnect 
         connection.close();
-        connection = null;
     }
 }
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPConnection.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPConnection.java
new file mode 100644
index 0000000..32f7572
--- /dev/null
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPConnection.java
@@ -0,0 +1,1345 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.javamail.transport.smtp;
+
+import java.io.BufferedReader;
+import java.io.BufferedOutputStream; 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException; 
+import java.util.ArrayList; 
+import java.util.HashMap;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import javax.mail.Address;
+import javax.mail.AuthenticationFailedException;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage; 
+import javax.mail.internet.MimeMultipart; 
+import javax.mail.internet.MimePart;
+import javax.mail.Session;
+
+import org.apache.geronimo.javamail.authentication.ClientAuthenticator; 
+import org.apache.geronimo.javamail.authentication.AuthenticatorFactory; 
+import org.apache.geronimo.javamail.util.CountingOutputStream;
+import org.apache.geronimo.javamail.util.MailConnection; 
+import org.apache.geronimo.javamail.util.MIMEOutputStream;
+import org.apache.geronimo.javamail.util.ProtocolProperties; 
+import org.apache.geronimo.mail.util.Base64;
+import org.apache.geronimo.mail.util.XText;
+
+/**
+ * Simple implementation of SMTP transport. Just does plain RFC977-ish delivery.
+ * 
+ * @version $Rev$ $Date$
+ */
+public class SMTPConnection extends MailConnection {
+    protected static final String MAIL_SMTP_QUITWAIT = "quitwait";
+    protected static final String MAIL_SMTP_EXTENSION = "mailextension";
+    protected static final String MAIL_SMTP_EHLO = "ehlo";
+    protected static final String MAIL_SMTP_ALLOW8BITMIME = "allow8bitmime";
+    protected static final String MAIL_SMTP_REPORT_SUCCESS = "reportsuccess";
+    protected static final String MAIL_SMTP_STARTTLS_ENABLE = "starttls.enable";
+    protected static final String MAIL_SMTP_AUTH = "auth";
+    protected static final String MAIL_SMTP_FROM = "from";
+    protected static final String MAIL_SMTP_DSN_RET = "dsn.ret";
+    protected static final String MAIL_SMTP_SUBMITTER = "submitter";
+
+    /**
+     * property keys for protocol properties.
+     */
+    protected static final int DEFAULT_NNTP_PORT = 119;
+    
+    // the last response line received from the server.
+    protected SMTPReply lastServerResponse = null;
+    
+    // input reader wrapped around the socket input stream 
+    protected BufferedReader reader; 
+    // output writer wrapped around the socket output stream. 
+    protected PrintWriter writer; 
+    
+    // do we report success after completion of each mail send.
+    protected boolean reportSuccess;
+    // does the server support transport level security?
+    protected boolean serverTLS = false;
+    // is TLS enabled on our part?
+    protected boolean useTLS = false;
+    // should we use 8BITMIME encoding if supported by the server?
+    protected boolean use8bit = false; 
+
+    /**
+     * Normal constructor for an SMTPConnection() object.
+     * 
+     * @param props  The property bundle for this protocol instance.
+     */
+    public SMTPConnection(ProtocolProperties props) {
+        super(props);
+        
+        // check to see if we need to throw an exception after a send operation.
+        reportSuccess = props.getBooleanProperty(MAIL_SMTP_REPORT_SUCCESS, false);
+        // and also check for TLS enablement.
+        useTLS = props.getBooleanProperty(MAIL_SMTP_STARTTLS_ENABLE, false);
+        // and also check for 8bitmime support  
+        use8bit = props.getBooleanProperty(MAIL_SMTP_ALLOW8BITMIME, false);
+    }
+    
+    
+    /**
+     * 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 {
+
+        // now check to see if we need to authenticate. If we need this, then
+        // we must have a username and
+        // password specified. Failing this may result in a user prompt to
+        // collect the information.
+        boolean mustAuthenticate = props.getBooleanProperty(MAIL_SMTP_AUTH, false);
+
+        // if we need to authenticate, and we don't have both a userid and
+        // password, then we fail this
+        // immediately. The Service.connect() method will try to obtain the user
+        // information and retry the
+        // connection one time.
+        if (mustAuthenticate && (username == null || password == null)) {
+            debugOut("Failing connection for missing authentication information");
+            return false;
+        }
+        
+        super.protocolConnect(host, port, username, password); 
+        
+        try {
+            // create socket and connect to server.
+            getConnection();
+
+            // receive welcoming message
+            if (!getWelcome()) {
+                debugOut("Error getting welcome message"); 
+                throw new MessagingException("Error in getting welcome msg");
+            }
+
+            // say hello
+            if (!sendHandshake()) {
+                debugOut("Error getting processing handshake message"); 
+                throw new MessagingException("Error in saying EHLO to server");
+            }
+
+            // authenticate with the server, if necessary
+            if (!processAuthentication()) {
+                debugOut("User authentication failure");
+                throw new AuthenticationFailedException("Error authenticating with server");
+            }
+        } catch (IOException e) {
+            debugOut("I/O exception establishing connection", e);
+            throw new MessagingException("Connection error", e);
+        }
+        debugOut("Successful connection"); 
+        return true;
+    }
+    
+
+    /**
+     * Close the connection. On completion, we'll be disconnected from the
+     * server and unable to send more data.
+     * 
+     * @exception MessagingException
+     */
+    public void close() throws MessagingException {
+        // if we're already closed, get outta here.
+        if (socket == null) {
+            return;
+        }
+        try {
+            // say goodbye
+            sendQuit();
+        } finally {
+            // and close up the connection. We do this in a finally block to
+            // make sure the connection
+            // is shut down even if quit gets an error.
+            closeServerConnection();
+        }
+    }
+
+    public String toString() {
+        return "SMTPConnection host: " + serverHost + " port: " + serverPort;
+    }
+
+    
+    /**
+     * Set the sender for this mail.
+     * 
+     * @param message
+     *                   The message we're sending.
+     * 
+     * @return True if the command was accepted, false otherwise. 
+     * @exception MessagingException
+     */
+    protected boolean sendMailFrom(Message message) throws MessagingException {
+
+        // need to sort the from value out from a variety of sources.
+        String from = null;
+
+        // first potential source is from the message itself, if it's an
+        // instance of SMTPMessage.
+        if (message instanceof SMTPMessage) {
+            from = ((SMTPMessage) message).getEnvelopeFrom();
+        }
+
+        // if not available from the message, check the protocol property next
+        if (from == null || from.length() == 0) {
+            // the from value can be set explicitly as a property
+            from = props.getProperty(MAIL_SMTP_FROM);
+        }
+
+        // if not there, see if we have something in the message header.
+        if (from == null || from.length() == 0) {
+            Address[] fromAddresses = message.getFrom();
+
+            // if we have some addresses in the header, then take the first one
+            // as our From: address
+            if (fromAddresses != null && fromAddresses.length > 0) {
+                from = ((InternetAddress) fromAddresses[0]).getAddress();
+            }
+            // get what the InternetAddress class believes to be the local
+            // address.
+            else {
+                InternetAddress local = InternetAddress.getLocalAddress(session);
+                if (local != null) {
+                    from = local.getAddress(); 
+                }
+            }
+        }
+
+        if (from == null || from.length() == 0) {
+            throw new MessagingException("no FROM address");
+        }
+
+        StringBuffer command = new StringBuffer();
+
+        // start building up the command
+        command.append("MAIL FROM: ");
+        command.append(fixEmailAddress(from));
+        
+        // If the server supports the 8BITMIME extension, we might need to change the 
+        // transfer encoding for the content to allow for direct transmission of the 
+        // 8-bit codes. 
+        if (supportsExtension("8BITMIME")) {
+            // we only do this if the capability was enabled via a property option or 
+            // by explicitly setting the property on the message object. 
+            if (use8bit || (message instanceof SMTPMessage && ((SMTPMessage)message).getAllow8bitMIME())) {
+                // make sure we add the BODY= option to the FROM message. 
+                command.append(" BODY=8BITMIME"); 
+                
+                // go check the content and see if the can convert the transfer encoding to 
+                // allow direct 8-bit transmission. 
+                if (convertTransferEncoding((MimeMessage)message)) {
+                    // if we changed the encoding on any of the parts, then we 
+                    // need to save the message again 
+                    message.saveChanges(); 
+                }
+            }
+        }
+        
+        // some servers ask for a size estimate on the initial send 
+        if (supportsExtension("SIZE")) {
+            int estimate = getSizeEstimate(message); 
+            if (estimate > 0) {
+                command.append(" SIZE=" + estimate); 
+            }
+        }
+
+        // does this server support Delivery Status Notification? Then we may
+        // need to add some extra to the command.
+        if (supportsExtension("DSN")) {
+            String returnNotification = null;
+
+            // the return notification stuff might be set as value on the
+            // message object itself.
+            if (message instanceof SMTPMessage) {
+                // we need to convert the option into a string value.
+                switch (((SMTPMessage) message).getReturnOption()) {
+                case SMTPMessage.RETURN_FULL:
+                    returnNotification = "FULL";
+                    break;
+
+                case SMTPMessage.RETURN_HDRS:
+                    returnNotification = "HDRS";
+                    break;
+                }
+            }
+
+            // if not obtained from the message object, it can also be set as a
+            // property.
+            if (returnNotification == null) {
+                // the DSN value is set by yet another property.
+                returnNotification = props.getProperty(MAIL_SMTP_DSN_RET);
+            }
+
+            // if we have a target, add the notification stuff to our FROM
+            // command.
+            if (returnNotification != null) {
+                command.append(" RET=");
+                command.append(returnNotification);
+            }
+        }
+
+        // if this server supports AUTH and we have submitter information, then
+        // we also add the
+        // "AUTH=" keyword to the MAIL FROM command (see RFC 2554).
+
+        if (supportsExtension("AUTH")) {
+            String submitter = null;
+
+            // another option that can be specified on the message object.
+            if (message instanceof SMTPMessage) {
+                submitter = ((SMTPMessage) message).getSubmitter();
+            }
+            // if not part of the object, try for a propery version.
+            if (submitter == null) {
+                // we only send the extra keyword is a submitter is specified.
+                submitter = props.getProperty(MAIL_SMTP_SUBMITTER);
+            }
+            // we have one...add the keyword, plus the submitter info in xtext
+            // format (defined by RFC 1891).
+            if (submitter != null) {
+                command.append(" AUTH=");
+                try {
+                    // add this encoded
+                    command.append(new String(XText.encode(submitter.getBytes("US-ASCII"))));
+                } catch (UnsupportedEncodingException e) {
+                    throw new MessagingException("Invalid submitter value " + submitter);
+                }
+            }
+        }
+
+        String extension = null;
+
+        // now see if we need to add any additional extension info to this
+        // command. The extension is not
+        // checked for validity. That's the reponsibility of the caller.
+        if (message instanceof SMTPMessage) {
+            extension = ((SMTPMessage) message).getMailExtension();
+        }
+        // this can come either from the object or from a set property.
+        if (extension == null) {
+            extension = props.getProperty(MAIL_SMTP_EXTENSION);
+        }
+
+        // have something real to add?
+        if (extension != null && extension.length() != 0) {
+            // tack this on the end with a blank delimiter.
+            command.append(' ');
+            command.append(extension);
+        }
+
+        // and finally send the command
+        SMTPReply line = sendCommand(command.toString());
+
+        // 250 response indicates success.
+        return line.getCode() == SMTPReply.COMMAND_ACCEPTED;
+    }
+    
+    
+    /**
+     * Check to see if a MIME body part can have its 
+     * encoding changed from quoted-printable or base64
+     * encoding to 8bit encoding.  In order for this 
+     * to work, it must follow the rules laid out in 
+     * RFC 2045.  To qualify for conversion, the text 
+     * must be: 
+     * 
+     * 1)  No more than 998 bytes long 
+     * 2)  All lines are terminated with CRLF sequences
+     * 3)  CR and LF characters only occur in properly 
+     * formed line separators 
+     * 4)  No null characters are allowed. 
+     * 
+     * The conversion will only be applied to text 
+     * elements, and this will recurse through the 
+     * different elements of MultiPart content. 
+     * 
+     * @param bodyPart The bodyPart to convert. Initially, this will be
+     *                 the message itself.
+     * 
+     * @return true if any conversion was performed, false if 
+     *         nothing was converted.
+     */
+    protected boolean convertTransferEncoding(MimePart bodyPart)
+    {
+        boolean converted = false; 
+        try {
+            // if this is a multipart element, apply the conversion rules 
+            // to each of the parts. 
+            if (bodyPart.isMimeType("multipart/")) {
+                MimeMultipart parts = (MimeMultipart)bodyPart.getContent(); 
+                for (int i = 0; i < parts.getCount(); i++) {
+                    // convert each body part, and accumulate the conversion result 
+                    converted = converted && convertTransferEncoding((MimePart)parts.getBodyPart(i)); 
+                }
+            }
+            else {
+                // we only do this if the encoding is quoted-printable or base64
+                String encoding =  bodyPart.getEncoding(); 
+                if (encoding != null) {
+                    encoding = encoding.toLowerCase(); 
+                    if (encoding.equals("quoted-printable") || encoding.equals("base64")) {
+                        // this requires encoding.  Read the actual content to see if 
+                        // it conforms to the 8bit encoding rules. 
+                        if (isValid8bit(bodyPart.getInputStream())) {
+                            // There's a huge hidden gotcha lurking under the covers here. 
+                            // If the content just exists as an encoded byte array, then just 
+                            // switching the transfer encoding will mess things up because the 
+                            // already encoded data gets transmitted in encoded form, but with 
+                            // and 8bit encoding style.  As a result, it doesn't get unencoded on 
+                            // the receiving end.  This is a nasty problem to debug.  
+                            //
+                            // The solution is to get the content as it's object type, set it back 
+                            // on the the message in raw form.  Requesting the content will apply the 
+                            // current transfer encoding value to the data.  Once we have set the 
+                            // content value back, we can reset the transfer encoding. 
+                            bodyPart.setContent(bodyPart.getContent(), bodyPart.getContentType()); 
+                            
+                            // it's valid, so change the transfer encoding to just 
+                            // pass the data through.  
+                            bodyPart.setHeader("Content-Transfer-Encoding", "8bit"); 
+                            converted = true;   // we've changed something
+                        }
+                    }
+                }
+            }
+        } catch (MessagingException e) {
+        } catch (IOException e) {
+        }
+        return converted; 
+    }
+
+    
+    /**
+     * Get the server's welcome blob from the wire....
+     */
+    protected boolean getWelcome() throws MessagingException {
+        SMTPReply line = getReply();
+        // just return the error status...we don't care about any of the 
+        // response information
+        return !line.isError();
+    }
+    
+    
+    /**
+     * Get an estimate of the transmission size for this 
+     * message.  This size is the complete message as it is 
+     * encoded and transmitted on the DATA command, not counting 
+     * the terminating ".CRLF". 
+     * 
+     * @param msg    The message we're sending.
+     * 
+     * @return The count of bytes, if it can be calculated. 
+     */
+    protected int getSizeEstimate(Message msg) {
+        // now the data... I could look at the type, but
+        try {
+            CountingOutputStream outputStream = new CountingOutputStream(); 
+            
+            // the data content has two requirements we need to meet by
+            // filtering the
+            // output stream. Requirement 1 is to conicalize any line breaks.
+            // All line
+            // breaks will be transformed into properly formed CRLF sequences.
+            //
+            // Requirement 2 is to perform byte-stuff for any line that begins
+            // with a "."
+            // so that data is not confused with the end-of-data marker (a
+            // "\r\n.\r\n" sequence.
+            //
+            // The MIME output stream performs those two functions on behalf of
+            // the content
+            // writer.
+            MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
+
+            msg.writeTo(mimeOut);
+
+            // now to finish, we make sure there's a line break at the end.  
+            mimeOut.forceTerminatingLineBreak();   
+            // and flush the data to send it along 
+            mimeOut.flush();   
+            
+            return outputStream.getCount();   
+        } catch (IOException e) {
+            return 0;     // can't get an estimate 
+        } catch (MessagingException e) {
+            return 0;     // can't get an estimate 
+        }
+    }
+    
+
+    /**
+     * Sends the data in the message down the socket. This presumes the server
+     * is in the right place and ready for getting the DATA message and the data
+     * right place in the sequence
+     */
+    protected void sendData(Message msg) throws MessagingException {
+
+        // send the DATA command
+        SMTPReply line = sendCommand("DATA");
+
+        if (line.isError()) {
+            throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
+        }
+
+        // now the data... I could look at the type, but
+        try {
+            // the data content has two requirements we need to meet by
+            // filtering the
+            // output stream. Requirement 1 is to conicalize any line breaks.
+            // All line
+            // breaks will be transformed into properly formed CRLF sequences.
+            //
+            // Requirement 2 is to perform byte-stuff for any line that begins
+            // with a "."
+            // so that data is not confused with the end-of-data marker (a
+            // "\r\n.\r\n" sequence.
+            //
+            // The MIME output stream performs those two functions on behalf of
+            // the content
+            // writer.
+            MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
+
+            msg.writeTo(mimeOut);
+
+            // now to finish, we send a CRLF sequence, followed by a ".".
+            mimeOut.writeSMTPTerminator();           
+            // and flush the data to send it along 
+            mimeOut.flush();   
+        } catch (IOException e) {
+            throw new MessagingException(e.toString());
+        } catch (MessagingException e) {
+            throw new MessagingException(e.toString());
+        }
+
+        // use a longer time out here to give the server time to process the
+        // data.
+        try {
+            line = new SMTPReply(receiveLine(TIMEOUT * 2));
+        } catch (MalformedSMTPReplyException e) {
+            throw new MessagingException(e.toString());
+        } catch (MessagingException e) {
+            throw new MessagingException(e.toString());
+        }
+
+        if (line.isError()) {
+            throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
+        }
+    }
+
+    /**
+     * Sends the QUIT message and receieves the response
+     */
+    protected void sendQuit() throws MessagingException {
+        // there's yet another property that controls whether we should wait for
+        // a reply for a QUIT command. If true, we're suppposed to wait for a response 
+        // from the QUIT command.  Otherwise we just send the QUIT and bail.  The default 
+        // is "false"
+        if (props.getBooleanProperty(MAIL_SMTP_QUITWAIT, true)) {
+            // handle as a real command...we're going to ignore the response.
+            sendCommand("QUIT");
+        } else {
+            // just send the command without waiting for a response. 
+            sendLine("QUIT");
+        }
+    }
+
+    /**
+     * Sets a receiver address for the current message
+     *
+     * @param addr
+     *            The target address.
+     * @param dsn
+     *            An optional DSN option appended to the RCPT TO command.
+     *
+     * @return The status for this particular send operation.
+     * @exception MessagingException
+     */
+    public SendStatus sendRcptTo(InternetAddress addr, String dsn) throws MessagingException {
+        // compose the command using the fixed up email address. Normally, this
+        // involves adding
+        // "<" and ">" around the address.
+
+        StringBuffer command = new StringBuffer();
+
+        // compose the first part of the command
+        command.append("RCPT TO: ");
+        command.append(fixEmailAddress(addr.getAddress()));
+
+        // if we have DSN information, append it to the command.
+        if (dsn != null) {
+            command.append(" NOTIFY=");
+            command.append(dsn);
+        }
+
+        // get a string version of this command.
+        String commandString = command.toString();
+
+        SMTPReply line = sendCommand(commandString);
+
+        switch (line.getCode()) {
+        // these two are both successful transmissions
+        case SMTPReply.COMMAND_ACCEPTED:
+        case SMTPReply.ADDRESS_NOT_LOCAL:
+            // we get out of here with the status information.
+            return new SendStatus(SendStatus.SUCCESS, addr, commandString, line);
+
+        // these are considered invalid address errors
+        case SMTPReply.PARAMETER_SYNTAX_ERROR:
+        case SMTPReply.INVALID_COMMAND_SEQUENCE:
+        case SMTPReply.MAILBOX_NOT_FOUND:
+        case SMTPReply.INVALID_MAILBOX:
+        case SMTPReply.USER_NOT_LOCAL:
+            // we get out of here with the status information.
+            return new SendStatus(SendStatus.INVALID_ADDRESS, addr, commandString, line);
+
+        // the command was valid, but something went wrong in the server.
+        case SMTPReply.SERVICE_NOT_AVAILABLE:
+        case SMTPReply.MAILBOX_BUSY:
+        case SMTPReply.PROCESSING_ERROR:
+        case SMTPReply.INSUFFICIENT_STORAGE:
+        case SMTPReply.MAILBOX_FULL:
+            // we get out of here with the status information.
+            return new SendStatus(SendStatus.SEND_FAILURE, addr, commandString, line);
+
+        // everything else is considered really bad...
+        default:
+            // we get out of here with the status information.
+            return new SendStatus(SendStatus.GENERAL_ERROR, addr, commandString, line);
+        }
+    }
+
+    /**
+     * Send a command to the server, returning the first response line back as a
+     * reply.
+     *
+     * @param data
+     *            The data to send.
+     *
+     * @return A reply object with the reply line.
+     * @exception MessagingException
+     */
+    protected SMTPReply sendCommand(String data) throws MessagingException {
+        sendLine(data);
+        return getReply();
+    }
+
+    /**
+     * Sends a message down the socket and terminates with the appropriate CRLF
+     */
+    protected void sendLine(String data) throws MessagingException {
+        if (socket == null || !socket.isConnected()) {
+            throw new MessagingException("no connection");
+        }
+        try {
+            outputStream.write(data.getBytes());
+            outputStream.write(CR);
+            outputStream.write(LF);
+            outputStream.flush();
+        } catch (IOException e) {
+            throw new MessagingException(e.toString());
+        }
+    }
+
+    /**
+     * Receives one line from the server. A line is a sequence of bytes
+     * terminated by a CRLF
+     *
+     * @return the line from the server as String
+     */
+    protected String receiveLine() throws MessagingException {
+        return receiveLine(TIMEOUT);
+    }
+
+    /**
+     * Get a reply line for an SMTP command.
+     *
+     * @return An SMTP reply object from the stream.
+     */
+    protected SMTPReply getReply() throws MessagingException {
+        try {
+            lastServerResponse = new SMTPReply(receiveLine());
+            // if the first line we receive is a continuation, continue 
+            // reading lines until we reach the non-continued one. 
+            while (lastServerResponse.isContinued()) {
+                lastServerResponse.addLine(receiveLine()); 
+            }
+        } catch (MalformedSMTPReplyException e) {
+            throw new MessagingException(e.toString());
+        } catch (MessagingException e) {
+            throw e;
+        }
+        return lastServerResponse;
+    }
+
+    /**
+     * Retrieve the last response received from the SMTP server.
+     *
+     * @return The raw response string (including the error code) returned from
+     *         the SMTP server.
+     */
+    public SMTPReply getLastServerResponse() {
+        return lastServerResponse; 
+    }
+    
+
+    /**
+     * Receives one line from the server. A line is a sequence of bytes
+     * terminated by a CRLF
+     *
+     * @return the line from the server as String
+     */
+    protected String receiveLine(int delayMillis) throws MessagingException {
+        if (socket == null || !socket.isConnected()) {
+            throw new MessagingException("no connection");
+        }
+
+        int timeout = 0;
+
+        try {
+            // for now, read byte for byte, looking for a CRLF
+            timeout = socket.getSoTimeout();
+
+            socket.setSoTimeout(delayMillis);
+
+            StringBuffer buff = new StringBuffer();
+
+            int c;
+            boolean crFound = false, lfFound = false;
+
+            while ((c = inputStream.read()) != -1 && crFound == false && lfFound == false) {
+                // we're looking for a CRLF sequence, so mark each one as seen.
+                // Any other
+                // character gets appended to the end of the buffer.
+                if (c == CR) {
+                    crFound = true;
+                } else if (c == LF) {
+                    lfFound = true;
+                } else {
+                    buff.append((char) c);
+                }
+            }
+
+            String line = buff.toString();
+            return line;
+
+        } catch (SocketException e) {
+            throw new MessagingException(e.toString());
+        } catch (IOException e) {
+            throw new MessagingException(e.toString());
+        } finally {
+            try {
+                socket.setSoTimeout(timeout);
+            } catch (SocketException e) {
+                // ignore - was just trying to do the decent thing...
+            }
+        }
+    }
+
+    /**
+     * Convert an InternetAddress into a form sendable on an SMTP mail command.
+     * InternetAddress.getAddress() generally returns just the address portion
+     * of the full address, minus route address markers. We need to ensure we
+     * have an address with '<' and '>' delimiters.
+     *
+     * @param mail
+     *            The mail address returned from InternetAddress.getAddress().
+     *
+     * @return A string formatted for sending.
+     */
+    protected String fixEmailAddress(String mail) {
+        if (mail.charAt(0) == '<') {
+            return mail;
+        }
+        return "<" + mail + ">";
+    }
+
+    /**
+     * Start the handshake process with the server, including setting up and
+     * TLS-level work. At the completion of this task, we should be ready to
+     * authenticate with the server, if needed.
+     */
+    protected boolean sendHandshake() throws MessagingException {
+        // check to see what sort of initial handshake we need to make.
+        boolean useEhlo = props.getBooleanProperty(MAIL_SMTP_EHLO, true);
+        // if we're to use Ehlo, send it and then fall back to just a HELO
+        // message if it fails.
+        if (useEhlo) {
+            if (!sendEhlo()) {
+                sendHelo();
+            }
+        } else {
+            // send the initial hello response.
+            sendHelo();
+        }
+
+        if (useTLS) {
+            // if we've been told to use TLS, and this server doesn't support
+            // it, then this is a failure
+            if (!serverTLS) {
+                throw new MessagingException("Server doesn't support required transport level security");
+            }
+            // if the server supports TLS, then use it for the connection.
+            // on our connection.
+            getConnectedTLSSocket();
+
+            // some servers (gmail is one that I know of) only send a STARTTLS
+            // extension message on the
+            // first EHLO command. Now that we have the TLS handshaking
+            // established, we need to send a
+            // second EHLO message to retrieve the AUTH records from the server.
+            if (!sendEhlo()) {
+                throw new MessagingException("Failure sending EHLO command to SMTP server");
+            }
+        }
+
+        // this worked.
+        return true;
+    }
+
+
+    /**
+     * Switch the connection to using TLS level security, switching to an SSL
+     * socket.
+     */
+    protected void getConnectedTLSSocket() throws MessagingException {
+        debugOut("Attempting to negotiate STARTTLS with server " + serverHost);
+        // tell the server of our intention to start a TLS session
+        SMTPReply line = sendCommand("STARTTLS");
+
+        if (line.getCode() != SMTPReply.SERVICE_READY) {
+            debugOut("STARTTLS command rejected by SMTP server " + serverHost);
+            throw new MessagingException("Unable to make TLS server connection");
+        }
+        
+        debugOut("STARTTLS command accepted"); 
+        
+        // the base class handles the socket switch details 
+        super.getConnectedTLSSocket(); 
+    }
+
+    
+    /**
+     * Send the EHLO command to the SMTP server.
+     *
+     * @return True if the command was accepted ok, false for any errors.
+     * @exception SMTPTransportException
+     * @exception MalformedSMTPReplyException
+     * @exception MessagingException
+     */
+    protected boolean sendEhlo() throws MessagingException {
+        sendLine("EHLO " + getLocalHost());
+
+        SMTPReply reply = getReply();
+
+        // we get a 250 code back. The first line is just a greeting, and
+        // extensions are identifed on
+        // continuations. If this fails, then we'll try once more with HELO to
+        // establish bona fides.
+        if (reply.getCode() != SMTPReply.COMMAND_ACCEPTED) {
+            return false;
+        }
+
+        // create a fresh mapping and authentications table 
+        capabilities = new HashMap();
+        authentications = new ArrayList(); 
+
+        List lines = reply.getLines(); 
+        // process all of the continuation lines
+        for (int i = 1; i < lines.size(); i++) {
+            // go process the extention
+            processExtension((String)lines.get(i));
+        }
+        return true;
+    }
+
+    /**
+     * Send the HELO command to the SMTP server.
+     *
+     * @exception MessagingException
+     */
+    protected void sendHelo() throws MessagingException {
+        // create a fresh mapping and authentications table 
+        // these will be empty, but it will prevent NPEs 
+        capabilities = new HashMap();
+        authentications = new ArrayList(); 
+        
+        sendLine("HELO " + getLocalHost());
+
+        SMTPReply line = getReply();
+
+        // we get a 250 code back. The first line is just a greeting, and
+        // extensions are identifed on
+        // continuations. If this fails, then we'll try once more with HELO to
+        // establish bona fides.
+        if (line.getCode() != SMTPReply.COMMAND_ACCEPTED) {
+            throw new MessagingException("Failure sending HELO command to SMTP server");
+        }
+    }
+
+    /**
+     * Return the current startTLS property.
+     *
+     * @return The current startTLS property.
+     */
+    public boolean getStartTLS() {
+        return useTLS;
+    }
+
+    
+    /**
+     * Set a new value for the startTLS property.
+     *
+     * @param start
+     *            The new setting.
+     */
+    public void setStartTLS(boolean start) {
+        useTLS = start;
+    }
+
+    
+    /**
+     * Process an extension string passed back as the EHLP response.
+     *
+     * @param extension
+     *            The string value of the extension (which will be of the form
+     *            "NAME arguments").
+     */
+    protected void processExtension(String extension) {
+        debugOut("Processing extension " + extension); 
+        String extensionName = extension.toUpperCase();
+        String argument = "";
+
+        int delimiter = extension.indexOf(' ');
+        // if we have a keyword with arguments, parse them out and add to the
+        // argument map.
+        if (delimiter != -1) {
+            extensionName = extension.substring(0, delimiter).toUpperCase();
+            argument = extension.substring(delimiter + 1);
+        }
+        
+        // add this to the map so it can be tested later.
+        capabilities.put(extensionName, argument);
+
+        // process a few special ones that don't require extra parsing.
+        // AUTH and AUTH=LOGIN are handled the same
+        if (extensionName.equals("AUTH")) {
+            // if we don't have an argument on AUTH, this means LOGIN.
+            if (argument == null) {
+                authentications.add("LOGIN");
+            } else {
+                // The security mechanisms are blank delimited tokens.
+                StringTokenizer tokenizer = new StringTokenizer(argument);
+
+                while (tokenizer.hasMoreTokens()) {
+                    String mechanism = tokenizer.nextToken().toUpperCase();
+                    authentications.add(mechanism);
+                }
+            }
+        }
+        // special case for some older servers.
+        else if (extensionName.equals("AUTH=LOGIN")) {
+            authentications.add("LOGIN");
+        }
+        // does this support transport level security?
+        else if (extensionName.equals("STARTTLS")) {
+            // flag this for later
+            serverTLS = true;
+        }
+    }
+
+    
+    /**
+     * Retrieve any argument information associated with a extension reported
+     * back by the server on the EHLO command.
+     *
+     * @param name
+     *            The name of the target server extension.
+     *
+     * @return Any argument passed on a server extension. Returns null if the
+     *         extension did not include an argument or the extension was not
+     *         supported.
+     */
+    public String extensionParameter(String name) {
+        if (capabilities != null) {
+            return (String)capabilities.get(name);
+        }
+        return null;
+    }
+    
+
+    /**
+     * Tests whether the target server supports a named extension.
+     *
+     * @param name
+     *            The target extension name.
+     *
+     * @return true if the target server reported on the EHLO command that is
+     *         supports the targer server, false if the extension was not
+     *         supported.
+     */
+    public boolean supportsExtension(String name) {
+        // this only returns null if we don't have this extension
+        return extensionParameter(name) != null;
+    }
+    
+
+    /**
+     * Authenticate with the server, if necessary (or possible).
+     *
+     * @return true if we are ok to proceed, false for an authentication
+     *         failures.
+     */
+    protected boolean processAuthentication() throws MessagingException {
+        // no authentication defined?
+        if (!props.getBooleanProperty(MAIL_SMTP_AUTH, false)) {
+            return true;
+        }
+
+        // we need to authenticate, but we don't have userid/password
+        // information...fail this
+        // immediately.
+        if (username == null || password == null) {
+            return false;
+        }
+        
+        // if unable to get an appropriate authenticator, just fail it. 
+        ClientAuthenticator authenticator = getSaslAuthenticator(); 
+        if (authenticator == null) {
+            throw new MessagingException("Unable to obtain SASL authenticator"); 
+        }
+        
+        
+        if (debug) {
+            debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
+        }
+
+        // if the authenticator has some initial data, we compose a command
+        // containing the initial data.
+        if (authenticator.hasInitialResponse()) {
+            StringBuffer command = new StringBuffer();
+            // the auth command initiates the handshaking.
+            command.append("AUTH ");
+            // and tell the server which mechanism we're using.
+            command.append(authenticator.getMechanismName());
+            command.append(" ");
+            // and append the response data
+            command.append(new String(Base64.encode(authenticator.evaluateChallenge(null))));
+            // send the command now
+            sendLine(command.toString());
+        }
+        // we just send an auth command with the command type.
+        else {
+            StringBuffer command = new StringBuffer();
+            // the auth command initiates the handshaking.
+            command.append("AUTH ");
+            // and tell the server which mechanism we're using.
+            command.append(authenticator.getMechanismName());
+            // send the command now
+            sendLine(command.toString());
+        }
+
+        // now process the challenge sequence. We get a 235 response back when
+        // the server accepts the
+        // authentication, and a 334 indicates we have an additional challenge.
+        while (true) {
+            // get the next line, and if it is an error response, return now.
+            SMTPReply line;
+            try {
+                line = new SMTPReply(receiveLine());
+            } catch (MalformedSMTPReplyException e) {
+                throw new MessagingException(e.toString());
+            } catch (MessagingException e) {
+                throw e;
+            }
+
+            // if we get a completion return, we've passed muster, so give an
+            // authentication response.
+            if (line.getCode() == SMTPReply.AUTHENTICATION_COMPLETE) {
+                debugOut("Successful SMTP authentication");
+                return true;
+            }
+            // we have an additional challenge to process.
+            else if (line.getCode() == SMTPReply.AUTHENTICATION_CHALLENGE) {
+                // Does the authenticator think it is finished? We can't answer
+                // an additional challenge,
+                // so fail this.
+                if (authenticator.isComplete()) {
+                    return false;
+                }
+
+                // we're passed back a challenge value, Base64 encoded.
+                byte[] challenge = Base64.decode(line.getMessage().getBytes());
+
+                // have the authenticator evaluate and send back the encoded
+                // response.
+                sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge))));
+            }
+            // completion or challenge are the only responses we know how to
+            // handle. Anything else must
+            // be a failure.
+            else {
+                if (debug) {
+                    debugOut("Authentication failure " + line);
+                }
+                return false;
+            }
+        }
+    }
+    
+    
+    /**
+     * Attempt to retrieve a SASL authenticator for this 
+     * protocol. 
+     * 
+     * @return A SASL authenticator, or null if a suitable one 
+     *         was not located.
+     */
+    protected ClientAuthenticator getSaslAuthenticator() {
+        return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm); 
+    }
+
+    
+    /**
+     * Read the bytes in a stream a test to see if this 
+     * conforms to the RFC 2045 rules for 8bit encoding. 
+     * 
+     * 1)  No more than 998 bytes long 
+     * 2)  All lines are terminated with CRLF sequences
+     * 3)  CR and LF characters only occur in properly 
+     * formed line separators 
+     * 4)  No null characters are allowed. 
+     * 
+     * @param inStream The source input stream.
+     * 
+     * @return true if this can be transmitted successfully 
+     *         using 8bit encoding, false if an alternate encoding
+     *         will be required.
+     */
+    protected boolean isValid8bit(InputStream inStream) { 
+        try {
+            int ch;  
+            int lineLength = 0; 
+            while ((ch = inStream.read()) >= 0) {
+                // nulls are decidedly not allowed 
+                if (ch == 0) {
+                    return false; 
+                }
+                // start of a CRLF sequence (potentially) 
+                else if (ch == '\r') {
+                    // check the next character.  There must be one, 
+                    // and it must be a LF for this to be value 
+                    ch = inStream.read(); 
+                    if (ch != '\n') {
+                        return false; 
+                    }
+                    // reset the line length 
+                    lineLength = 0; 
+                }
+                else {
+                    // a normal character 
+                    lineLength++; 
+                    // make sure the line is not too long 
+                    if (lineLength > 998) {
+                        return false; 
+                    }
+                }
+                
+            }
+        } catch (IOException e) {
+            return false;  // can't read this, don't try passing it 
+        }
+        // this converted ok 
+        return true; 
+    }
+
+    
+    /**
+     * Simple holder class for the address/send status duple, as we can have
+     * mixed success for a set of addresses and a message
+     */
+    static public class SendStatus {
+        public final static int SUCCESS = 0;
+
+        public final static int INVALID_ADDRESS = 1;
+
+        public final static int SEND_FAILURE = 2;
+
+        public final static int GENERAL_ERROR = 3;
+
+        // the status type of the send operation.
+        int status;
+
+        // the address associated with this status
+        InternetAddress address;
+
+        // the command string send to the server.
+        String cmd;
+
+        // the reply from the server.
+        SMTPReply reply;
+
+        /**
+         * Constructor for a SendStatus item.
+         *
+         * @param s
+         *            The status type.
+         * @param a
+         *            The address this is the status for.
+         * @param c
+         *            The command string associated with this status.
+         * @param r
+         *            The reply information from the server.
+         */
+        public SendStatus(int s, InternetAddress a, String c, SMTPReply r) {
+            this.cmd = c;
+            this.status = s;
+            this.address = a;
+            this.reply = r;
+        }
+
+        /**
+         * Get the status information for this item.
+         *
+         * @return The current status code.
+         */
+        public int getStatus() {
+            return this.status;
+        }
+
+        /**
+         * Retrieve the InternetAddress object associated with this send
+         * operation.
+         *
+         * @return The associated address object.
+         */
+        public InternetAddress getAddress() {
+            return this.address;
+        }
+
+        /**
+         * Retrieve the reply information associated with this send operati
+         *
+         * @return The SMTPReply object received for the operation.
+         */
+        public SMTPReply getReply() {
+            return reply;
+        }
+
+        /**
+         * Get the command string sent for this send operation.
+         *
+         * @return The command string for the MAIL TO command sent to the
+         *         server.
+         */
+        public String getCommand() {
+            return cmd;
+        }
+
+        /**
+         * Get an exception object associated with this send operation. There is
+         * a mechanism for reporting send success via a send operation, so this
+         * will be either a success or failure exception.
+         *
+         * @param reportSuccess
+         *            Indicates if we want success operations too.
+         *
+         * @return A newly constructed exception object.
+         */
+        public MessagingException getException(boolean reportSuccess) {
+            if (status != SUCCESS) {
+                return new SMTPAddressFailedException(address, cmd, reply.getCode(), reply.getMessage());
+            } else {
+                if (reportSuccess) {
+                    return new SMTPAddressSucceededException(address, cmd, reply.getCode(), reply.getMessage());
+                }
+            }
+            return null;
+        }
+    }
+
+    
+    /**
+     * Reset the server connection after an error.
+     *
+     * @exception MessagingException
+     */
+    public void resetConnection() throws MessagingException {
+        // we want the caller to retrieve the last response responsbile for
+        // requiring the reset, so save and
+        // restore that info around the reset.
+        SMTPReply last = lastServerResponse;
+
+        // send a reset command.
+        SMTPReply line = sendCommand("RSET");
+
+        // if this did not reset ok, just close the connection
+        if (line.getCode() != SMTPReply.COMMAND_ACCEPTED) {
+            close();
+        }
+        // restore this.
+        lastServerResponse = last;
+    }
+
+    
+    /**
+     * Return the current reportSuccess property.
+     *
+     * @return The current reportSuccess property.
+     */
+    public boolean getReportSuccess() {
+        return reportSuccess; 
+    }
+
+    /**
+     * Set a new value for the reportSuccess property.
+     *
+     * @param report
+     *            The new setting.
+     */
+    public void setReportSuccess(boolean report) {
+        reportSuccess = report; 
+    }
+}
+
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java
index ce564c7..ec9999f 100644
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java
@@ -28,6 +28,49 @@
  * @version $Rev$ $Date$
  */
 class SMTPReply {
+    // SMTP reply codes
+    public static final int SERVICE_READY = 220;
+
+    public static final int SERVICE_CLOSING = 221;
+
+    public static final int AUTHENTICATION_COMPLETE = 235;
+
+    public static final int COMMAND_ACCEPTED = 250;
+
+    public static final int ADDRESS_NOT_LOCAL = 251;
+
+    public static final int AUTHENTICATION_CHALLENGE = 334;
+
+    public static final int START_MAIL_INPUT = 354;
+
+    public static final int SERVICE_NOT_AVAILABLE = 421;
+
+    public static final int MAILBOX_BUSY = 450;
+
+    public static final int PROCESSING_ERROR = 451;
+
+    public static final int INSUFFICIENT_STORAGE = 452;
+
+    public static final int COMMAND_SYNTAX_ERROR = 500;
+
+    public static final int PARAMETER_SYNTAX_ERROR = 501;
+
+    public static final int COMMAND_NOT_IMPLEMENTED = 502;
+
+    public static final int INVALID_COMMAND_SEQUENCE = 503;
+
+    public static final int COMMAND_PARAMETER_NOT_IMPLEMENTED = 504;
+
+    public static final int MAILBOX_NOT_FOUND = 550;
+
+    public static final int USER_NOT_LOCAL = 551;
+
+    public static final int MAILBOX_FULL = 552;
+
+    public static final int INVALID_MAILBOX = 553;
+
+    public static final int TRANSACTION_FAILED = 553;
+    
     // The original reply string
     private final String reply;
 
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
index 7b56a9d..4552f7f 100644
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
@@ -20,20 +20,9 @@
 package org.apache.geronimo.javamail.transport.smtp;
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintStream;
 import java.io.UnsupportedEncodingException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.SocketException;
-import java.net.UnknownHostException;
+import java.net.Socket; 
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.StringTokenizer;
 
 import javax.mail.Address;
 import javax.mail.AuthenticationFailedException;
@@ -47,19 +36,9 @@
 import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeMultipart;     
 import javax.mail.internet.MimePart;     
-import javax.net.ssl.SSLSocket;
 
-import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
-import org.apache.geronimo.javamail.authentication.CramMD5Authenticator;
-import org.apache.geronimo.javamail.authentication.DigestMD5Authenticator;
-import org.apache.geronimo.javamail.authentication.LoginAuthenticator;
-import org.apache.geronimo.javamail.authentication.PlainAuthenticator;
-import org.apache.geronimo.javamail.util.CountingOutputStream;
-import org.apache.geronimo.javamail.util.MIMEOutputStream;
-import org.apache.geronimo.javamail.util.TraceInputStream;
-import org.apache.geronimo.javamail.util.TraceOutputStream;
-import org.apache.geronimo.mail.util.Base64;
-import org.apache.geronimo.mail.util.XText;
+import org.apache.geronimo.javamail.util.ProtocolProperties; 
+import org.apache.geronimo.javamail.transport.smtp.SMTPConnection.SendStatus; 
 
 /**
  * Simple implementation of SMTP transport. Just does plain RFC821-ish delivery.
@@ -77,203 +56,32 @@
  * @version $Rev$ $Date$
  */
 public class SMTPTransport extends Transport {
-
-    /**
-     * constants for EOL termination
-     */
-    protected static final char CR = '\r';
-
-    protected static final char LF = '\n';
-
-    /**
-     * property keys for top level session properties.
-     */
-    protected static final String MAIL_LOCALHOST = "mail.localhost";
-
-    protected static final String MAIL_SSLFACTORY_CLASS = "mail.SSLSocketFactory.class";
-
     /**
      * property keys for protocol properties. The actual property name will be
      * appended with "mail." + protocol + ".", where the protocol is either
      * "smtp" or "smtps".
      */
-    protected static final String MAIL_SMTP_AUTH = "auth";
-
-    protected static final String MAIL_SMTP_PORT = "port";
-
-    protected static final String MAIL_SMTP_LOCALHOST = "localhost";
-
-    protected static final String MAIL_SMTP_TIMEOUT = "timeout";
-
-    protected static final String MAIL_SMTP_SASL_REALM = "sasl.realm";
-
-    protected static final String MAIL_SMTP_TLS = "starttls.enable";
-
-    protected static final String MAIL_SMTP_FACTORY_CLASS = "socketFactory.class";
-
-    protected static final String MAIL_SMTP_FACTORY_FALLBACK = "socketFactory.fallback";
-
-    protected static final String MAIL_SMTP_FACTORY_PORT = "socketFactory.port";
-
-    protected static final String MAIL_SMTP_REPORT_SUCCESS = "reportsuccess";
-
-    protected static final String MAIL_SMTP_STARTTLS_ENABLE = "starttls.enable";
-
     protected static final String MAIL_SMTP_DSN_NOTIFY = "dsn.notify";
-
     protected static final String MAIL_SMTP_SENDPARTIAL = "sendpartial";
-
-    protected static final String MAIL_SMTP_LOCALADDRESS = "localaddress";
-
-    protected static final String MAIL_SMTP_LOCALPORT = "localport";
-
-    protected static final String MAIL_SMTP_QUITWAIT = "quitwait";
-
-    protected static final String MAIL_SMTP_FROM = "from";
-
-    protected static final String MAIL_SMTP_DSN_RET = "dsn.ret";
-
-    protected static final String MAIL_SMTP_SUBMITTER = "submitter";
-
     protected static final String MAIL_SMTP_EXTENSION = "mailextension";
-
-    protected static final String MAIL_SMTP_EHLO = "ehlo";
-
-    protected static final String MAIL_SMTP_ENCODE_TRACE = "encodetrace";
-    
-    protected static final String MAIL_SMTP_ALLOW8BITMIME = "allow8bitmime";
-    
-    protected static final String MAIL_SMTP_SSL_PROTOCOLS = "ssl.protocols"; 
-    
-    protected static final String MAIL_SMTP_SSL_CIPHERSUITES = "ssl.ciphersuites"; 
-
-    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 int DEFAULT_MAIL_SMTP_PORT = 25;
-
     protected static final int DEFAULT_MAIL_SMTPS_PORT = 465;
 
-    // SMTP reply codes
-    protected static final int SERVICE_READY = 220;
-
-    protected static final int SERVICE_CLOSING = 221;
-
-    protected static final int AUTHENTICATION_COMPLETE = 235;
-
-    protected static final int COMMAND_ACCEPTED = 250;
-
-    protected static final int ADDRESS_NOT_LOCAL = 251;
-
-    protected static final int AUTHENTICATION_CHALLENGE = 334;
-
-    protected static final int START_MAIL_INPUT = 354;
-
-    protected static final int SERVICE_NOT_AVAILABLE = 421;
-
-    protected static final int MAILBOX_BUSY = 450;
-
-    protected static final int PROCESSING_ERROR = 451;
-
-    protected static final int INSUFFICIENT_STORAGE = 452;
-
-    protected static final int COMMAND_SYNTAX_ERROR = 500;
-
-    protected static final int PARAMETER_SYNTAX_ERROR = 501;
-
-    protected static final int COMMAND_NOT_IMPLEMENTED = 502;
-
-    protected static final int INVALID_COMMAND_SEQUENCE = 503;
-
-    protected static final int COMMAND_PARAMETER_NOT_IMPLEMENTED = 504;
-
-    protected static final int MAILBOX_NOT_FOUND = 550;
-
-    protected static final int USER_NOT_LOCAL = 551;
-
-    protected static final int MAILBOX_FULL = 552;
-
-    protected static final int INVALID_MAILBOX = 553;
-
-    protected static final int TRANSACTION_FAILED = 553;
-
-    protected static final String AUTHENTICATION_PLAIN = "PLAIN";
-
-    protected static final String AUTHENTICATION_LOGIN = "LOGIN";
-
-    protected static final String AUTHENTICATION_CRAMMD5 = "CRAM-MD5";
-
-    protected static final String AUTHENTICATION_DIGESTMD5 = "DIGEST-MD5";
-
-    // the protocol we're working with. This will be either "smtp" or "smtps".
-    protected String protocol;
-
-    // the target host
-    protected String host;
-
-    // the default port to use for this protocol (differs between "smtp" and
-    // "smtps").
-    protected int defaultPort;
-
-    // the target server port.
-    protected int port;
-
-    // the connection socket...can be a plain socket or SSLSocket, if TLS is
-    // being used.
-    protected Socket socket;
-
-    // our local host name
-    protected String localHost;
-
-    // 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;
-
-    // list of authentication mechanisms supported by the server
-    protected HashMap serverAuthenticationMechanisms;
-
-    // map of server extension arguments
-    protected HashMap serverExtensionArgs;
-
-    // do we report success after completion of each mail send.
-    protected boolean reportSuccess;
-
-    // does the server support transport level security?
-    protected boolean serverTLS = false;
-
-    // is TLS enabled on our part?
-    protected boolean useTLS = false;
-
-    // should we use 8BITMIME encoding if supported by the server?
-    protected boolean use8bit = false; 
 
     // do we use SSL for our initial connection?
     protected boolean sslConnection = false;
-
-    // the username we connect with
-    protected String username;
-
-    // the authentication password.
-    protected String password;
-
-    // the target SASL realm (normally null unless explicitly set or we have an
-    // authentication mechanism that
-    // requires it.
-    protected String realm;
+    
+    // our accessor for protocol properties and the holder of 
+    // protocol-specific information 
+    protected ProtocolProperties props; 
+    // our active connection object 
+    protected SMTPConnection connection;
 
     // the last response line received from the server.
     protected SMTPReply lastServerResponse = null;
 
-    // our session provided debug output stream.
-    protected PrintStream debugStream;
-
     /**
      * Normal constructor for an SMTPTransport() object. This constructor is
      * used to build a transport instance for the "smtp" protocol.
@@ -286,6 +94,7 @@
     public SMTPTransport(Session session, URLName name) {
         this(session, name, "smtp", DEFAULT_MAIL_SMTP_PORT, false);
     }
+    
 
     /**
      * Common constructor used by the SMTPTransport and SMTPSTransport classes
@@ -309,22 +118,15 @@
      */
     protected SMTPTransport(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
         super(session, name);
-        this.protocol = protocol;
-
-        // these are defaults based on what the superclass specifies.
-        this.defaultPort = defaultPort;
-        this.sslConnection = sslConnection;
-        // check to see if we need to throw an exception after a send operation.
-        reportSuccess = isProtocolPropertyTrue(MAIL_SMTP_REPORT_SUCCESS);
-        // and also check for TLS enablement.
-        useTLS = isProtocolPropertyTrue(MAIL_SMTP_STARTTLS_ENABLE);
-        // and also check for TLS enablement.
-        use8bit = isProtocolPropertyTrue(MAIL_SMTP_ALLOW8BITMIME);
-
-        // get our debug output.
-        debugStream = session.getDebugOut();
+        
+        // create the protocol property holder.  This gives an abstraction over the different 
+        // flavors of the protocol. 
+        props = new ProtocolProperties(session, protocol, sslConnection, defaultPort); 
+        // the connection manages connection for the transport 
+        connection = new SMTPConnection(props); 
     }
 
+    
     /**
      * Connect to a server using an already created socket. This connection is
      * just like any other connection, except we will not create a new socket.
@@ -333,9 +135,10 @@
      *            The socket connection to use.
      */
     public void connect(Socket socket) throws MessagingException {
-        this.socket = socket;
+        connection.connect(socket); 
         super.connect();
     }
+    
 
     /**
      * Do the protocol connection for an SMTP transport. This handles server
@@ -358,71 +161,8 @@
      */
     protected boolean protocolConnect(String host, int port, String username, String password)
             throws MessagingException {
-        if (debug) {
-            debugOut("Connecting to server " + host + ":" + port + " for user " + username);
-        }
-
-        // now check to see if we need to authenticate. If we need this, then
-        // we must have a username and
-        // password specified. Failing this may result in a user prompt to
-        // collect the information.
-        boolean mustAuthenticate = isProtocolPropertyTrue(MAIL_SMTP_AUTH);
-
-        // if we need to authenticate, and we don't have both a userid and
-        // password, then we fail this
-        // immediately. The Service.connect() method will try to obtain the user
-        // information and retry the
-        // connection one time.
-        if (mustAuthenticate && (username == null || password == null)) {
-            debugOut("Failing connection for missing authentication information");
-            return false;
-        }
-
-        // 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) {
-            // take the default first.
-            port = defaultPort;
-            String configuredPort = getProtocolProperty(MAIL_SMTP_PORT);
-            if (configuredPort != null) {
-                port = Integer.parseInt(configuredPort);
-            }
-        }
-    	
-    	// Before we do anything, let's make sure that we succesfully received a host
-    	if ( host == null ) {
-    		host = DEFAULT_MAIL_HOST;
-    	}
-
-        try {
-
-            // create socket and connect to server.
-            getConnection(host, port, username, password);
-
-            // receive welcoming message
-            if (!getWelcome()) {
-                debugOut("Error getting welcome message"); 
-                throw new MessagingException("Error in getting welcome msg");
-            }
-
-            // say hello
-            if (!sendHandshake()) {
-                debugOut("Error getting processing handshake message"); 
-                throw new MessagingException("Error in saying EHLO to server");
-            }
-
-            // authenticate with the server, if necessary
-            if (!processAuthentication()) {
-                debugOut("User authentication failure");
-                throw new AuthenticationFailedException("Error authenticating with server");
-            }
-        } catch (IOException e) {
-            debugOut("I/O exception establishing connection", e);
-            throw new MessagingException("Connection error", e);
-        }
-        debugOut("Successful connection"); 
-        return true;
+        // the connection pool handles all of the details here. 
+        return connection.protocolConnect(host, port, username, password);
     }
 
     /**
@@ -454,6 +194,22 @@
         if (addresses == null || addresses.length == 0) {
             throw new MessagingException("Null or empty address array");
         }
+        
+        boolean reportSuccess = getReportSuccess(); 
+        
+        // now see how we're configured for this send operation.
+        boolean partialSends = false;
+
+        // this can be attached directly to the message.
+        if (message instanceof SMTPMessage) {
+            partialSends = ((SMTPMessage) message).getSendPartial();
+        }
+
+        // if still false on the message object, check for a property
+        // version also
+        if (!partialSends) {
+            partialSends = props.getBooleanProperty(MAIL_SMTP_SENDPARTIAL, false);
+        }
 
         boolean haveGroup = false;
 
@@ -483,33 +239,12 @@
         Address[] sent = null;
         Address[] unsent = null;
         Address[] invalid = null;
-        
-        boolean sendAs8bit = false; 
-        
-        // If the server supports the 8BITMIME extension, we might need to change the 
-        // transfer encoding for the content to allow for direct transmission of the 
-        // 8-bit codes. 
-        if (supportsExtension("8BITMIME")) {
-            // we only do this if the capability was enabled via a property option or 
-            // by explicitly setting the property on the message object. 
-            if (use8bit || (message instanceof SMTPMessage && ((SMTPMessage)message).getAllow8bitMIME())) {
-                // make sure we add the BODY= option to the FROM message. 
-                sendAs8bit = true; 
-                // go check the content and see if the can convert the transfer encoding to 
-                // allow direct 8-bit transmission. 
-                if (convertTransferEncoding((MimeMessage)message)) {
-                    // if we changed the encoding on any of the parts, then we 
-                    // need to save the message again 
-                    message.saveChanges(); 
-                }
-            }
-        }
 
         try {
             // send sender first. If this failed, send a failure notice of the
             // event, using the full list of
             // addresses as the unsent, and nothing for the rest.
-            if (!sendMailFrom(message, sendAs8bit)) {
+            if (!connection.sendMailFrom(message)) {
                 unsent = addresses;
                 sent = new Address[0];
                 invalid = new Address[0];
@@ -517,70 +252,14 @@
                 notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
 
                 // include the reponse information here.
-                SMTPReply last = lastServerResponse;
+                SMTPReply last = connection.getLastServerResponse(); 
                 // now send an "uber-exception" to indicate the failure.
                 throw new SMTPSendFailedException("MAIL FROM", last.getCode(), last.getMessage(), null, sent, unsent,
                         invalid);
             }
 
-            String dsn = null;
-
-            // there's an optional notification argument that can be added to
-            // MAIL TO. See if we've been
-            // provided with one.
-
-            // an SMTPMessage object is the first source
-            if (message instanceof SMTPMessage) {
-                // get the notification options
-                int options = ((SMTPMessage) message).getNotifyOptions();
-
-                switch (options) {
-                // a zero value indicates nothing is set.
-                case 0:
-                    break;
-
-                case SMTPMessage.NOTIFY_NEVER:
-                    dsn = "NEVER";
-                    break;
-
-                case SMTPMessage.NOTIFY_SUCCESS:
-                    dsn = "SUCCESS";
-                    break;
-
-                case SMTPMessage.NOTIFY_FAILURE:
-                    dsn = "FAILURE";
-                    break;
-
-                case SMTPMessage.NOTIFY_DELAY:
-                    dsn = "DELAY";
-                    break;
-
-                // now for combinations...there are few enough combinations here
-                // that we can just handle this in the switch statement rather
-                // than have to
-                // concatentate everything together.
-                case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_FAILURE):
-                    dsn = "SUCCESS,FAILURE";
-                    break;
-
-                case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_DELAY):
-                    dsn = "SUCCESS,DELAY";
-                    break;
-
-                case (SMTPMessage.NOTIFY_FAILURE + SMTPMessage.NOTIFY_DELAY):
-                    dsn = "FAILURE,DELAY";
-                    break;
-
-                case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_FAILURE + SMTPMessage.NOTIFY_DELAY):
-                    dsn = "SUCCESS,FAILURE,DELAY";
-                    break;
-                }
-            }
-
-            // if still null, grab a property value (yada, yada, yada...)
-            if (dsn == null) {
-                dsn = getProtocolProperty(MAIL_SMTP_DSN_NOTIFY);
-            }
+            // get the additional notification status, if available 
+            String dsn = getDeliveryStatusNotification(message);
 
             // we need to know about any failures once we've gone through the
             // complete list, so keep a
@@ -606,50 +285,36 @@
                 InternetAddress target = (InternetAddress) addresses[i];
 
                 // write out the record now.
-                SendStatus status = sendRcptTo(target, dsn);
+                SendStatus status = connection.sendRcptTo(target, dsn);
                 stats[i] = status;
 
                 switch (status.getStatus()) {
-                // successfully sent
-                case SendStatus.SUCCESS:
-                    sentAddresses.add(target);
-                    break;
+                    // successfully sent
+                    case SendStatus.SUCCESS:
+                        sentAddresses.add(target);
+                        break;
 
-                // we have an invalid address of some sort, or a general sending
-                // error (which we'll
-                // interpret as due to an invalid address.
-                case SendStatus.INVALID_ADDRESS:
-                case SendStatus.GENERAL_ERROR:
-                    sendFailure = true;
-                    invalidAddresses.add(target);
-                    break;
+                    // we have an invalid address of some sort, or a general sending
+                    // error (which we'll
+                    // interpret as due to an invalid address.
+                    case SendStatus.INVALID_ADDRESS:
+                    case SendStatus.GENERAL_ERROR:
+                        sendFailure = true;
+                        invalidAddresses.add(target);
+                        break;
 
-                // good address, but this was a send failure.
-                case SendStatus.SEND_FAILURE:
-                    sendFailure = true;
-                    unsentAddresses.add(target);
-                    break;
-                }
+                    // good address, but this was a send failure.
+                    case SendStatus.SEND_FAILURE:
+                        sendFailure = true;
+                        unsentAddresses.add(target);
+                        break;
+                    }
             }
 
             // if we had a send failure, then we need to check if we allow
             // partial sends. If not allowed,
             // we abort the send operation now.
             if (sendFailure) {
-                // now see how we're configured for this send operation.
-                boolean partialSends = false;
-
-                // this can be attached directly to the message.
-                if (message instanceof SMTPMessage) {
-                    partialSends = ((SMTPMessage) message).getSendPartial();
-                }
-
-                // if still false on the message object, check for a property
-                // version also
-                if (!partialSends) {
-                    partialSends = isProtocolPropertyTrue(MAIL_SMTP_SENDPARTIAL);
-                }
-
                 // if we're not allowing partial successes or we've failed on
                 // all of the addresses, it's
                 // time to abort.
@@ -669,7 +334,7 @@
 
                     // go reset our connection so we can process additional
                     // sends.
-                    resetConnection();
+                    connection.resetConnection();
 
                     // get a list of chained exceptions for all of the failures.
                     MessagingException failures = generateExceptionChain(stats, false);
@@ -681,7 +346,7 @@
 
             try {
                 // try to send the data
-                sendData(message);
+                connection.sendData(message);
             } catch (MessagingException e) {
                 // If there's an error at this point, this is a complete
                 // delivery failure.
@@ -721,7 +386,7 @@
                 // get a list of chained exceptions for all of the failures (and
                 // the successes, if reportSuccess has been
                 // turned on).
-                MessagingException failures = generateExceptionChain(stats, getReportSuccess());
+                MessagingException failures = generateExceptionChain(stats, reportSuccess);
 
                 // now send an "uber-exception" to indicate the failure.
                 throw new SMTPSendFailedException("MAIL TO", 0, "Invalid Address", failures, sent, unsent, invalid);
@@ -751,30 +416,94 @@
             throw e;
         }
     }
+    
+    
+    /**
+     * Determine what delivery status notification should
+     * be added to the RCPT TO: command. 
+     * 
+     * @param message The message we're sending.
+     * 
+     * @return The string NOTIFY= value to add to the command. 
+     */
+    protected String getDeliveryStatusNotification(Message message) {
+        String dsn = null;
+
+        // there's an optional notification argument that can be added to
+        // MAIL TO. See if we've been
+        // provided with one.
+
+        // an SMTPMessage object is the first source
+        if (message instanceof SMTPMessage) {
+            // get the notification options
+            int options = ((SMTPMessage) message).getNotifyOptions();
+
+            switch (options) {
+            // a zero value indicates nothing is set.
+            case 0:
+                break;
+
+            case SMTPMessage.NOTIFY_NEVER:
+                dsn = "NEVER";
+                break;
+
+            case SMTPMessage.NOTIFY_SUCCESS:
+                dsn = "SUCCESS";
+                break;
+
+            case SMTPMessage.NOTIFY_FAILURE:
+                dsn = "FAILURE";
+                break;
+
+            case SMTPMessage.NOTIFY_DELAY:
+                dsn = "DELAY";
+                break;
+
+            // now for combinations...there are few enough combinations here
+            // that we can just handle this in the switch statement rather
+            // than have to
+            // concatentate everything together.
+            case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_FAILURE):
+                dsn = "SUCCESS,FAILURE";
+                break;
+
+            case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_DELAY):
+                dsn = "SUCCESS,DELAY";
+                break;
+
+            case (SMTPMessage.NOTIFY_FAILURE + SMTPMessage.NOTIFY_DELAY):
+                dsn = "FAILURE,DELAY";
+                break;
+
+            case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_FAILURE + SMTPMessage.NOTIFY_DELAY):
+                dsn = "SUCCESS,FAILURE,DELAY";
+                break;
+            }
+        }
+
+        // if still null, grab a property value (yada, yada, yada...)
+        if (dsn == null) {
+            dsn = props.getProperty(MAIL_SMTP_DSN_NOTIFY);
+        }
+        return dsn; 
+    }
+    
+
 
     /**
      * Close the connection. On completion, we'll be disconnected from the
      * server and unable to send more data.
-     *
+     * 
      * @exception MessagingException
      */
     public void close() throws MessagingException {
-        // if we're already closed, get outta here.
-        if (socket == null) {
-            return;
-        }
-        try {
-            // say goodbye
-            sendQuit();
-        } finally {
-            // and close up the connection. We do this in a finally block to
-            // make sure the connection
-            // is shut down even if quit gets an error.
-            closeServerConnection();
-            super.close();
-        }
+        // This is done to ensure proper event notification.
+        super.close();
+        // NB:  We reuse the connection if asked to reconnect 
+        connection.close();
     }
-
+    
+    
     /**
      * Turn a series of send status items into a chain of exceptions indicating
      * the state of each send operation.
@@ -810,28 +539,6 @@
     }
 
     /**
-     * Reset the server connection after an error.
-     *
-     * @exception MessagingException
-     */
-    protected void resetConnection() throws MessagingException {
-        // we want the caller to retrieve the last response responsbile for
-        // requiring the reset, so save and
-        // restore that info around the reset.
-        SMTPReply last = lastServerResponse;
-
-        // send a reset command.
-        SMTPReply line = sendCommand("RSET");
-
-        // if this did not reset ok, just close the connection
-        if (line.getCode() != COMMAND_ACCEPTED) {
-            close();
-        }
-        // restore this.
-        lastServerResponse = last;
-    }
-
-    /**
      * Expand the address list by converting any group addresses into single
      * address targets.
      *
@@ -864,1171 +571,7 @@
         // convert back into an array.
         return (Address[]) expandedAddresses.toArray(new Address[0]);
     }
-
-    /**
-     * Create a transport connection object and connect it to the target server.
-     *
-     * @param host
-     *            The target server host.
-     * @param port
-     *            The connection port.
-     *
-     * @exception MessagingException
-     */
-    protected void getConnection(String host, int port, String username, String password) throws IOException {
-        this.host = host;
-        this.port = port;
-        this.username = username;
-        this.password = password;
-        // and see if STARTTLS is enabled.
-        useTLS = isProtocolPropertyTrue(MAIL_SMTP_TLS);
-        serverAuthenticationMechanisms = new HashMap();
-        // We might have been passed a socket to connect with...if not, we need
-        // to create one of the correct type.
-        if (socket == null) {
-            // if this is the "smtps" 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 {
-            port = socket.getPort();
-            host = socket.getInetAddress().getHostName();
-        }
-        // now set up the input/output streams.
-        inputStream = new TraceInputStream(socket.getInputStream(), debugStream, debug,
-                isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
-         
-        outputStream = new TraceOutputStream(socket.getOutputStream(), debugStream, debug,
-                isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
-    }
-
-    /**
-     * Get a property associated with this mail protocol.
-     *
-     * @param name
-     *            The name of the property.
-     *
-     * @return The property value (returns null if the property has not been
-     *         set).
-     */
-    protected String getProtocolProperty(String name) {
-        // the name we're given is the least qualified part of the name. We
-        // construct the full property name
-        // using the protocol (either "smtp" or "smtps").
-        String fullName = "mail." + protocol + "." + name;
-        return getSessionProperty(fullName);
-    }
-
-    /**
-     * Get a property associated with this mail session.
-     *
-     * @param name
-     *            The name of the property.
-     *
-     * @return The property value (returns null if the property has not been
-     *         set).
-     */
-    protected String getSessionProperty(String name) {
-        return session.getProperty(name);
-    }
-
-    /**
-     * Get a property associated with this mail session. Returns the provided
-     * default if it doesn't exist.
-     *
-     * @param name
-     *            The name of the property.
-     * @param defaultValue
-     *            The default value to return if the property doesn't exist.
-     *
-     * @return The property value (returns defaultValue if the property has not
-     *         been set).
-     */
-    protected String getSessionProperty(String name, String defaultValue) {
-        String result = session.getProperty(name);
-        if (result == null) {
-            return defaultValue;
-        }
-        return result;
-    }
-
-    /**
-     * Get a property associated with this mail session. Returns the provided
-     * default if it doesn't exist.
-     *
-     * @param name
-     *            The name of the property.
-     * @param defaultValue
-     *            The default value to return if the property doesn't exist.
-     *
-     * @return The property value (returns defaultValue if the property has not
-     *         been set).
-     */
-    protected String getProtocolProperty(String name, String defaultValue) {
-        // the name we're given is the least qualified part of the name. We
-        // construct the full property name
-        // using the protocol (either "smtp" or "smtps").
-        String fullName = "mail." + protocol + "." + name;
-        return getSessionProperty(fullName, defaultValue);
-    }
-
-    /**
-     * Get a property associated with this mail session as an integer value.
-     * Returns the default value if the property doesn't exist or it doesn't
-     * have a valid int value.
-     *
-     * @param name
-     *            The name of the property.
-     * @param defaultValue
-     *            The default value to return if the property doesn't exist.
-     *
-     * @return The property value converted to an int.
-     */
-    protected int getIntSessionProperty(String name, int defaultValue) {
-        String result = getSessionProperty(name);
-        if (result != null) {
-            try {
-                // convert into an int value.
-                return Integer.parseInt(result);
-            } catch (NumberFormatException e) {
-            }
-        }
-        // return default value if it doesn't exist is isn't convertable.
-        return defaultValue;
-    }
-
-    /**
-     * Get a property associated with this mail session as an integer value.
-     * Returns the default value if the property doesn't exist or it doesn't
-     * have a valid int value.
-     *
-     * @param name
-     *            The name of the property.
-     * @param defaultValue
-     *            The default value to return if the property doesn't exist.
-     *
-     * @return The property value converted to an int.
-     */
-    protected int getIntProtocolProperty(String name, int defaultValue) {
-        // the name we're given is the least qualified part of the name. We
-        // construct the full property name
-        // using the protocol (either "smtp" or "smtps").
-        String fullName = "mail." + protocol + "." + name;
-        return getIntSessionProperty(fullName, defaultValue);
-    }
-
-    /**
-     * Process a session property as a boolean value, returning either true or
-     * false.
-     *
-     * @return True if the property value is "true". Returns false for any other
-     *         value (including null).
-     */
-    protected boolean isProtocolPropertyTrue(String name) {
-        // the name we're given is the least qualified part of the name. We
-        // construct the full property name
-        // using the protocol (either "smtp" or "smtps").
-        String fullName = "mail." + protocol + "." + name;
-        return isSessionPropertyTrue(fullName);
-    }
-
-    /**
-     * Process a session property as a boolean value, returning either true or
-     * false.
-     *
-     * @return True if the property value is "true". Returns false for any other
-     *         value (including null).
-     */
-    protected boolean isSessionPropertyTrue(String name) {
-        String property = session.getProperty(name);
-        if (property != null) {
-            return property.equals("true");
-        }
-        return false;
-    }
-
-    /**
-     * Process a session property as a boolean value, returning either true or
-     * false.
-     *
-     * @return True if the property value is "false". Returns false for other
-     *         value (including null).
-     */
-    protected boolean isSessionPropertyFalse(String name) {
-        String property = session.getProperty(name);
-        if (property != null) {
-            return property.equals("false");
-        }
-        return false;
-    }
-
-    /**
-     * Process a session property as a boolean value, returning either true or
-     * false.
-     *
-     * @return True if the property value is "false". Returns false for other
-     *         value (including null).
-     */
-    protected boolean isProtocolPropertyFalse(String name) {
-        // the name we're given is the least qualified part of the name. We
-        // construct the full property name
-        // using the protocol (either "smtp" or "smtps").
-        String fullName = "mail." + protocol + "." + name;
-        return isSessionPropertyFalse(fullName);
-    }
-
-    /**
-     * Close the server connection at termination.
-     */
-    protected void closeServerConnection() {
-        try {
-            socket.close();
-        } catch (IOException ignored) {
-        }
-
-        socket = null;
-        inputStream = null;
-        outputStream = null;
-    }
-
-    /**
-     * Creates a connected socket
-     *
-     * @exception MessagingException
-     */
-    protected void getConnectedSocket() throws IOException {
-        if (debug) {
-            debugOut("Attempting plain socket connection to server " + host + ":" + port);
-        }
-
-        // the socket factory can be specified via a session property. By
-        // default, we just directly
-        // instantiate a socket without using a factor.
-        String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS);
-
-        // there are several protocol properties that can be set to tune the
-        // created socket. We need to
-        // retrieve those bits before creating the socket.
-        int timeout = getIntProtocolProperty(MAIL_SMTP_TIMEOUT, -1);
-        InetAddress localAddress = null;
-        // see if we have a local address override.
-        String localAddrProp = getProtocolProperty(MAIL_SMTP_LOCALADDRESS);
-        if (localAddrProp != null) {
-            localAddress = InetAddress.getByName(localAddrProp);
-        }
-
-        // check for a local port...default is to allow socket to choose.
-        int localPort = getIntProtocolProperty(MAIL_SMTP_LOCALPORT, 0);
-
-        socket = null;
-
-        // if there is no socket factory defined (normal), we just create a
-        // socket directly.
-        if (socketFactory == null) {
-            socket = new Socket(host, port, localAddress, localPort);
-        }
-
-        else {
-            try {
-                int socketFactoryPort = getIntProtocolProperty(MAIL_SMTP_FACTORY_PORT, -1);
-
-                // we choose the port used by the socket based on overrides.
-                Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
-
-                // use the current context loader to resolve this.
-                ClassLoader loader = Thread.currentThread().getContextClassLoader();
-                Class factoryClass = loader.loadClass(socketFactory);
-
-                // done indirectly, we need to invoke the method using
-                // reflection.
-                // This retrieves a factory instance.
-                Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
-                Object defFactory = 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) {
-                    // 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[] { host, portArg, localAddress, new Integer(localPort) };
-                    socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
-                } 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[] { host, portArg };
-                    socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
-                }
-            } catch (Throwable e) {
-                // if a socket factor is specified, then we may need to fall
-                // back to a default. This behavior
-                // is controlled by (surprise) more session properties.
-                if (isProtocolPropertyTrue(MAIL_SMTP_FACTORY_FALLBACK)) {
-                    debugOut("First plain socket attempt faile, falling back to default factory", e);
-                    socket = new Socket(host, port, localAddress, localPort);
-                }
-                // 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("Plain socket creation failure", e);
-                    // throw this as an IOException, with the original exception
-                    // attached.
-                    IOException ioe = new IOException("Error connecting to " + host + ", " + port);
-                    ioe.initCause(e);
-                    throw ioe;
-                }
-            }
-        }
-
-        if (timeout >= 0) {
-            socket.setSoTimeout(timeout);
-        }
-    }
-
-    /**
-     * Creates a connected SSL socket for an initial SSL connection.
-     *
-     * @exception MessagingException
-     */
-    protected void getConnectedSSLSocket() throws IOException {
-        if (debug) {
-            debugOut("Attempting SSL socket connection to server " + host + ":" + port);
-        }
-        // 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.
-        String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS, getSessionProperty(MAIL_SSLFACTORY_CLASS,
-                "javax.net.ssl.SSLSocketFactory"));
-
-        // there are several protocol properties that can be set to tune the
-        // created socket. We need to
-        // retrieve those bits before creating the socket.
-        int timeout = getIntProtocolProperty(MAIL_SMTP_TIMEOUT, -1);
-        InetAddress localAddress = null;
-        // see if we have a local address override.
-        String localAddrProp = getProtocolProperty(MAIL_SMTP_LOCALADDRESS);
-        if (localAddrProp != null) {
-            localAddress = InetAddress.getByName(localAddrProp);
-        }
-
-        // check for a local port...default is to allow socket to choose.
-        int localPort = getIntProtocolProperty(MAIL_SMTP_LOCALPORT, 0);
-
-        socket = null;
-
-        // if there is no socket factory defined (normal), we just create a
-        // socket directly.
-        if (socketFactory == null) {
-            socket = new Socket(host, port, localAddress, localPort);
-        }
-
-        else {
-            // we'll try this with potentially two different factories if we're
-            // allowed to fall back.
-            boolean fallback = isProtocolPropertyTrue(MAIL_SMTP_FACTORY_FALLBACK);
-
-            while (true) {
-                try {
-                    if (debug) {
-                        debugOut("Creating SSL socket using factory " + socketFactory);
-                    }
-
-                    int socketFactoryPort = getIntProtocolProperty(MAIL_SMTP_FACTORY_PORT, -1);
-
-                    // we choose the port used by the socket based on overrides.
-                    Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
-
-                    // use the current context loader to resolve this.
-                    ClassLoader loader = Thread.currentThread().getContextClassLoader();
-                    Class factoryClass = loader.loadClass(socketFactory);
-
-                    // done indirectly, we need to invoke the method using
-                    // reflection.
-                    // This retrieves a factory instance.
-                    Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
-                    Object defFactory = 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) {
-                        // 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[] { host, portArg, localAddress, new Integer(localPort) };
-                        socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
-                    } 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[] { host, portArg };
-                        socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
-                    }
-                    // now break out and configure the socket.
-                    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 socket failed, falling back to default factory");
-                        socketFactory = "javax.net.ssl.SSLSocketFactory";
-                        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 socket", e);
-
-                        // throw this as an IOException, with the original
-                        // exception attached.
-                        IOException ioe = new IOException("Error connecting to " + host + ", " + port);
-                        ioe.initCause(e);
-                        throw ioe;
-                    }
-                }
-            }
-        }
-
-        if (timeout >= 0) {
-            socket.setSoTimeout(timeout);
-        }
-        
-        // if there is a list of protocols specified, we need to break this down into 
-        // the individual names 
-        String protocols = getProtocolProperty(MAIL_SMTP_SSL_PROTOCOLS); 
-        if (protocols != null) {
-            ArrayList<String> list = new ArrayList<String>(); 
-            StringTokenizer t = new StringTokenizer(protocols); 
-            
-            while (t.hasMoreTokens()) {
-                list.add(t.nextToken()); 
-            }
-            
-            ((SSLSocket)socket).setEnabledProtocols(list.toArray(new String[list.size()])); 
-        }
-        
-        // and do the same for any cipher suites 
-        String suites = getProtocolProperty(MAIL_SMTP_SSL_CIPHERSUITES); 
-        if (suites != null) {
-            ArrayList<String> list = new ArrayList<String>(); 
-            StringTokenizer t = new StringTokenizer(suites); 
-            
-            while (t.hasMoreTokens()) {
-                list.add(t.nextToken()); 
-            }
-            
-            ((SSLSocket)socket).setEnabledCipherSuites(list.toArray(new String[list.size()])); 
-        }
-    }
-
-    /**
-     * Switch the connection to using TLS level security, switching to an SSL
-     * socket.
-     */
-    protected void getConnectedTLSSocket() throws MessagingException {
-        if (debug) {
-            debugOut("Attempting to negotiate STARTTLS with server " + host);
-        }
-        // tell the server of our intention to start a TLS session
-        SMTPReply line = sendCommand("STARTTLS");
-
-        if (line.getCode() != SERVICE_READY) {
-            if (debug) {
-                debugOut("STARTTLS command rejected by SMTP server " + host);
-            }
-            throw new MessagingException("Unable to make TLS server connection");
-        }
-        // it worked, now switch the socket into TLS mode
-        try {
-
-            // we use the same target and port as the current connection.
-            String host = socket.getInetAddress().getHostName();
-            int port = socket.getPort();
-
-            // the socket factory can be specified via a session property. By
-            // default, we use
-            // the native SSL factory.
-            String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
-
-            // use the current context loader to resolve this.
-            ClassLoader loader = Thread.currentThread().getContextClassLoader();
-            Class factoryClass = loader.loadClass(socketFactory);
-
-            // done indirectly, we need to invoke the method using reflection.
-            // This retrieves a factory instance.
-            Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
-            Object defFactory = getDefault.invoke(new Object(), new Object[0]);
-
-            // now we need to invoke createSocket()
-            Class[] createSocketSig = new Class[] { Socket.class, String.class, Integer.TYPE, Boolean.TYPE };
-            Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
-
-            Object[] createSocketArgs = new Object[] { socket, host, new Integer(port), Boolean.TRUE };
-
-            // and finally create the socket
-            Socket sslSocket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
-
-            // if this is an instance of SSLSocket (very common), try setting
-            // the protocol to be
-            // "TLSv1". If this is some other class because of a factory
-            // override, we'll just have to
-            // accept that things will work.
-            if (sslSocket instanceof SSLSocket) {
-                ((SSLSocket) sslSocket).setEnabledProtocols(new String[] { "TLSv1" });
-                ((SSLSocket) sslSocket).setUseClientMode(true);
-                ((SSLSocket) sslSocket).startHandshake();
-            }
-
-            // and finally, as a last step, replace our input streams with the
-            // secure ones.
-            // now set up the input/output streams.
-            inputStream = new TraceInputStream(sslSocket.getInputStream(), debugStream, debug,
-                    isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
-            ;
-            outputStream = new TraceOutputStream(sslSocket.getOutputStream(), debugStream, debug,
-                    isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
-            // this is our active socket now
-            socket = sslSocket;
-
-        } catch (Exception e) {
-            debugOut("Failure attempting to convert connection to TLS", e);
-            throw new MessagingException("Unable to convert connection to SSL", e);
-        }
-    }
-
-    /**
-     * Get the servers welcome blob from the wire....
-     */
-    protected boolean getWelcome() throws MessagingException {
-        SMTPReply line = getReply();
-        // just return the error status...we don't care about any of the 
-        // response information
-        return !line.isError();
-    }
     
-    /**
-     * Get an estimate of the transmission size for this 
-     * message.  This size is the complete message as it is 
-     * encoded and transmitted on the DATA command, not counting 
-     * the terminating ".CRLF". 
-     * 
-     * @param msg    The message we're sending.
-     * 
-     * @return The count of bytes, if it can be calculated. 
-     */
-    protected int getSizeEstimate(Message msg) {
-        // now the data... I could look at the type, but
-        try {
-            CountingOutputStream outputStream = new CountingOutputStream(); 
-            
-            // the data content has two requirements we need to meet by
-            // filtering the
-            // output stream. Requirement 1 is to conicalize any line breaks.
-            // All line
-            // breaks will be transformed into properly formed CRLF sequences.
-            //
-            // Requirement 2 is to perform byte-stuff for any line that begins
-            // with a "."
-            // so that data is not confused with the end-of-data marker (a
-            // "\r\n.\r\n" sequence.
-            //
-            // The MIME output stream performs those two functions on behalf of
-            // the content
-            // writer.
-            MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
-
-            msg.writeTo(mimeOut);
-
-            // now to finish, we make sure there's a line break at the end.  
-            mimeOut.forceTerminatingLineBreak();   
-            // and flush the data to send it along 
-            mimeOut.flush();   
-            
-            return outputStream.getCount();   
-        } catch (IOException e) {
-            return 0;     // can't get an estimate 
-        } catch (MessagingException e) {
-            return 0;     // can't get an estimate 
-        }
-    }
-    
-
-    /**
-     * Sends the data in the message down the socket. This presumes the server
-     * is in the right place and ready for getting the DATA message and the data
-     * right place in the sequence
-     */
-    protected void sendData(Message msg) throws MessagingException {
-
-        // send the DATA command
-        SMTPReply line = sendCommand("DATA");
-
-        if (line.isError()) {
-            throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
-        }
-
-        // now the data... I could look at the type, but
-        try {
-            // the data content has two requirements we need to meet by
-            // filtering the
-            // output stream. Requirement 1 is to conicalize any line breaks.
-            // All line
-            // breaks will be transformed into properly formed CRLF sequences.
-            //
-            // Requirement 2 is to perform byte-stuff for any line that begins
-            // with a "."
-            // so that data is not confused with the end-of-data marker (a
-            // "\r\n.\r\n" sequence.
-            //
-            // The MIME output stream performs those two functions on behalf of
-            // the content
-            // writer.
-            MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
-
-            msg.writeTo(mimeOut);
-
-            // now to finish, we send a CRLF sequence, followed by a ".".
-            mimeOut.writeSMTPTerminator();           
-            // and flush the data to send it along 
-            mimeOut.flush();   
-        } catch (IOException e) {
-            throw new MessagingException(e.toString());
-        } catch (MessagingException e) {
-            throw new MessagingException(e.toString());
-        }
-
-        // use a longer time out here to give the server time to process the
-        // data.
-        try {
-            line = new SMTPReply(receiveLine(TIMEOUT * 2));
-        } catch (MalformedSMTPReplyException e) {
-            throw new MessagingException(e.toString());
-        } catch (MessagingException e) {
-            throw new MessagingException(e.toString());
-        }
-
-        if (line.isError()) {
-            throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
-        }
-    }
-
-    /**
-     * Sends the QUIT message and receieves the response
-     */
-    protected void sendQuit() throws MessagingException {
-        // there's yet another property that controls whether we should wait for
-        // a reply for a QUIT command. If true, we're suppposed to wait for a response 
-        // from the QUIT command.  Otherwise we just send the QUIT and bail.  The default 
-        // is "false"
-        if (isProtocolPropertyTrue(MAIL_SMTP_QUITWAIT)) {
-            // handle as a real command...we're going to ignore the response.
-            sendCommand("QUIT");
-        } else {
-            // just send the command without waiting for a response. 
-            sendLine("QUIT");
-        }
-    }
-
-    /**
-     * Sets a receiver address for the current message
-     *
-     * @param addr
-     *            The target address.
-     * @param dsn
-     *            An optional notification address appended to the MAIL command.
-     *
-     * @return The status for this particular send operation.
-     * @exception MessagingException
-     */
-    protected SendStatus sendRcptTo(InternetAddress addr, String dsn) throws MessagingException {
-        // compose the command using the fixed up email address. Normally, this
-        // involves adding
-        // "<" and ">" around the address.
-
-        StringBuffer command = new StringBuffer();
-
-        // compose the first part of the command
-        command.append("RCPT TO: ");
-        command.append(fixEmailAddress(addr.getAddress()));
-
-        // if we have DSN information, append it to the command.
-        if (dsn != null) {
-            command.append(" NOTIFY=");
-            command.append(dsn);
-        }
-
-        // get a string version of this command.
-        String commandString = command.toString();
-
-        SMTPReply line = sendCommand(commandString);
-
-        switch (line.getCode()) {
-        // these two are both successful transmissions
-        case COMMAND_ACCEPTED:
-        case ADDRESS_NOT_LOCAL:
-            // we get out of here with the status information.
-            return new SendStatus(SendStatus.SUCCESS, addr, commandString, line);
-
-        // these are considered invalid address errors
-        case PARAMETER_SYNTAX_ERROR:
-        case INVALID_COMMAND_SEQUENCE:
-        case MAILBOX_NOT_FOUND:
-        case INVALID_MAILBOX:
-        case USER_NOT_LOCAL:
-            // we get out of here with the status information.
-            return new SendStatus(SendStatus.INVALID_ADDRESS, addr, commandString, line);
-
-        // the command was valid, but something went wrong in the server.
-        case SERVICE_NOT_AVAILABLE:
-        case MAILBOX_BUSY:
-        case PROCESSING_ERROR:
-        case INSUFFICIENT_STORAGE:
-        case MAILBOX_FULL:
-            // we get out of here with the status information.
-            return new SendStatus(SendStatus.SEND_FAILURE, addr, commandString, line);
-
-        // everything else is considered really bad...
-        default:
-            // we get out of here with the status information.
-            return new SendStatus(SendStatus.GENERAL_ERROR, addr, commandString, line);
-        }
-    }
-
-    /**
-     * Set the sender for this mail.
-     * 
-     * @param message
-     *                   The message we're sending.
-     * @param sendAs8bit Indicates we're going to transmit the message body
-     *                   as 8BITMIME, so add the BODY= option to the FROM
-     *                   message.
-     * 
-     * @return True if the command was accepted, false otherwise. 
-     * @exception MessagingException
-     */
-    protected boolean sendMailFrom(Message message, boolean sendAs8bit) throws MessagingException {
-
-        // need to sort the from value out from a variety of sources.
-        String from = null;
-
-        // first potential source is from the message itself, if it's an
-        // instance of SMTPMessage.
-        if (message instanceof SMTPMessage) {
-            from = ((SMTPMessage) message).getEnvelopeFrom();
-        }
-
-        // if not available from the message, check the protocol property next
-        if (from == null || from.length() == 0) {
-            // the from value can be set explicitly as a property
-            from = getProtocolProperty(MAIL_SMTP_FROM);
-        }
-
-        // if not there, see if we have something in the message header.
-        if (from == null || from.length() == 0) {
-            Address[] fromAddresses = message.getFrom();
-
-            // if we have some addresses in the header, then take the first one
-            // as our From: address
-            if (fromAddresses != null && fromAddresses.length > 0) {
-                from = ((InternetAddress) fromAddresses[0]).getAddress();
-            }
-            // get what the InternetAddress class believes to be the local
-            // address.
-            else {
-                InternetAddress local = InternetAddress.getLocalAddress(session);
-                if (local != null) {
-                    from = local.getAddress(); 
-                }
-            }
-        }
-
-        if (from == null || from.length() == 0) {
-            throw new MessagingException("no FROM address");
-        }
-
-        StringBuffer command = new StringBuffer();
-
-        // start building up the command
-        command.append("MAIL FROM: ");
-        command.append(fixEmailAddress(from));
-        if (sendAs8bit) {
-            command.append(" BODY=8BITMIME"); 
-        }
-        
-        // some servers ask for a size estimate on the initial send 
-        if (supportsExtension("SIZE")) {
-            int estimate = getSizeEstimate(message); 
-            if (estimate > 0) {
-                command.append(" SIZE=" + estimate); 
-            }
-        }
-
-        // does this server support Delivery Status Notification? Then we may
-        // need to add some extra to the command.
-        if (supportsExtension("DSN")) {
-            String returnNotification = null;
-
-            // the return notification stuff might be set as value on the
-            // message object itself.
-            if (message instanceof SMTPMessage) {
-                // we need to convert the option into a string value.
-                switch (((SMTPMessage) message).getReturnOption()) {
-                case SMTPMessage.RETURN_FULL:
-                    returnNotification = "FULL";
-                    break;
-
-                case SMTPMessage.RETURN_HDRS:
-                    returnNotification = "HDRS";
-                    break;
-                }
-            }
-
-            // if not obtained from the message object, it can also be set as a
-            // property.
-            if (returnNotification == null) {
-                // the DSN value is set by yet another property.
-                returnNotification = getProtocolProperty(MAIL_SMTP_DSN_RET);
-            }
-
-            // if we have a target, add the notification stuff to our FROM
-            // command.
-            if (returnNotification != null) {
-                command.append(" RET=");
-                command.append(returnNotification);
-            }
-        }
-
-        // if this server supports AUTH and we have submitter information, then
-        // we also add the
-        // "AUTH=" keyword to the MAIL FROM command (see RFC 2554).
-
-        if (supportsExtension("AUTH")) {
-            String submitter = null;
-
-            // another option that can be specified on the message object.
-            if (message instanceof SMTPMessage) {
-                submitter = ((SMTPMessage) message).getSubmitter();
-            }
-            // if not part of the object, try for a propery version.
-            if (submitter == null) {
-                // we only send the extra keyword is a submitter is specified.
-                submitter = getProtocolProperty(MAIL_SMTP_SUBMITTER);
-            }
-            // we have one...add the keyword, plus the submitter info in xtext
-            // format (defined by RFC 1891).
-            if (submitter != null) {
-                command.append(" AUTH=");
-                try {
-                    // add this encoded
-                    command.append(new String(XText.encode(submitter.getBytes("US-ASCII"))));
-                } catch (UnsupportedEncodingException e) {
-                    throw new MessagingException("Invalid submitter value " + submitter);
-                }
-            }
-        }
-
-        String extension = null;
-
-        // now see if we need to add any additional extension info to this
-        // command. The extension is not
-        // checked for validity. That's the reponsibility of the caller.
-        if (message instanceof SMTPMessage) {
-            extension = ((SMTPMessage) message).getMailExtension();
-        }
-        // this can come either from the object or from a set property.
-        if (extension == null) {
-            extension = getProtocolProperty(MAIL_SMTP_EXTENSION);
-        }
-
-        // have something real to add?
-        if (extension != null && extension.length() != 0) {
-            // tack this on the end with a blank delimiter.
-            command.append(' ');
-            command.append(extension);
-        }
-
-        // and finally send the command
-        SMTPReply line = sendCommand(command.toString());
-
-        // 250 response indicates success.
-        return line.getCode() == COMMAND_ACCEPTED;
-    }
-
-    /**
-     * Send a command to the server, returning the first response line back as a
-     * reply.
-     *
-     * @param data
-     *            The data to send.
-     *
-     * @return A reply object with the reply line.
-     * @exception MessagingException
-     */
-    protected SMTPReply sendCommand(String data) throws MessagingException {
-        sendLine(data);
-        return getReply();
-    }
-
-    /**
-     * Sends a message down the socket and terminates with the appropriate CRLF
-     */
-    protected void sendLine(String data) throws MessagingException {
-        if (socket == null || !socket.isConnected()) {
-            throw new MessagingException("no connection");
-        }
-        try {
-            outputStream.write(data.getBytes());
-            outputStream.write(CR);
-            outputStream.write(LF);
-            outputStream.flush();
-        } catch (IOException e) {
-            throw new MessagingException(e.toString());
-        }
-    }
-
-    /**
-     * Receives one line from the server. A line is a sequence of bytes
-     * terminated by a CRLF
-     *
-     * @return the line from the server as String
-     */
-    protected String receiveLine() throws MessagingException {
-        return receiveLine(TIMEOUT);
-    }
-
-    /**
-     * Get a reply line for an SMTP command.
-     *
-     * @return An SMTP reply object from the stream.
-     */
-    protected SMTPReply getReply() throws MessagingException {
-        try {
-            lastServerResponse = new SMTPReply(receiveLine());
-            // if the first line we receive is a continuation, continue 
-            // reading lines until we reach the non-continued one. 
-            while (lastServerResponse.isContinued()) {
-                lastServerResponse.addLine(receiveLine()); 
-            }
-        } catch (MalformedSMTPReplyException e) {
-            throw new MessagingException(e.toString());
-        } catch (MessagingException e) {
-            throw e;
-        }
-        return lastServerResponse;
-    }
-
-    /**
-     * Retrieve the last response received from the SMTP server.
-     *
-     * @return The raw response string (including the error code) returned from
-     *         the SMTP server.
-     */
-    public String getLastServerResponse() {
-        if (lastServerResponse == null) {
-            return "";
-        }
-        return lastServerResponse.getReply();
-    }
-
-    /**
-     * Receives one line from the server. A line is a sequence of bytes
-     * terminated by a CRLF
-     *
-     * @return the line from the server as String
-     */
-    protected String receiveLine(int delayMillis) throws MessagingException {
-        if (socket == null || !socket.isConnected()) {
-            throw new MessagingException("no connection");
-        }
-
-        int timeout = 0;
-
-        try {
-            // for now, read byte for byte, looking for a CRLF
-            timeout = socket.getSoTimeout();
-
-            socket.setSoTimeout(delayMillis);
-
-            StringBuffer buff = new StringBuffer();
-
-            int c;
-            boolean crFound = false, lfFound = false;
-
-            while ((c = inputStream.read()) != -1 && crFound == false && lfFound == false) {
-                // we're looking for a CRLF sequence, so mark each one as seen.
-                // Any other
-                // character gets appended to the end of the buffer.
-                if (c == CR) {
-                    crFound = true;
-                } else if (c == LF) {
-                    lfFound = true;
-                } else {
-                    buff.append((char) c);
-                }
-            }
-
-            String line = buff.toString();
-            return line;
-
-        } catch (SocketException e) {
-            throw new MessagingException(e.toString());
-        } catch (IOException e) {
-            throw new MessagingException(e.toString());
-        } finally {
-            try {
-                socket.setSoTimeout(timeout);
-            } catch (SocketException e) {
-                // ignore - was just trying to do the decent thing...
-            }
-        }
-    }
-
-    /**
-     * Convert an InternetAddress into a form sendable on an SMTP mail command.
-     * InternetAddress.getAddress() generally returns just the address portion
-     * of the full address, minus route address markers. We need to ensure we
-     * have an address with '<' and '>' delimiters.
-     *
-     * @param mail
-     *            The mail address returned from InternetAddress.getAddress().
-     *
-     * @return A string formatted for sending.
-     */
-    protected String fixEmailAddress(String mail) {
-        if (mail.charAt(0) == '<') {
-            return mail;
-        }
-        return "<" + mail + ">";
-    }
-
-    /**
-     * Start the handshake process with the server, including setting up and
-     * TLS-level work. At the completion of this task, we should be ready to
-     * authenticate with the server, if needed.
-     */
-    protected boolean sendHandshake() throws MessagingException {
-        // check to see what sort of initial handshake we need to make.
-        boolean useEhlo = !isProtocolPropertyFalse(MAIL_SMTP_EHLO);
-        // if we're to use Ehlo, send it and then fall back to just a HELO
-        // message if it fails.
-        if (useEhlo) {
-            if (!sendEhlo()) {
-                sendHelo();
-            }
-        } else {
-            // send the initial hello response.
-            sendHelo();
-        }
-
-        if (useTLS) {
-            // if we've been told to use TLS, and this server doesn't support
-            // it, then this is a failure
-            if (!serverTLS) {
-                throw new MessagingException("Server doesn't support required transport level security");
-            }
-            // if the server supports TLS, then use it for the connection.
-            // on our connection.
-            getConnectedTLSSocket();
-
-            // some servers (gmail is one that I know of) only send a STARTTLS
-            // extension message on the
-            // first EHLO command. Now that we have the TLS handshaking
-            // established, we need to send a
-            // second EHLO message to retrieve the AUTH records from the server.
-            serverAuthenticationMechanisms.clear();
-            if (!sendEhlo()) {
-                throw new MessagingException("Failure sending EHLO command to SMTP server");
-            }
-        }
-
-        // this worked.
-        return true;
-    }
-
-    /**
-     * Send the EHLO command to the SMTP server.
-     *
-     * @return True if the command was accepted ok, false for any errors.
-     * @exception SMTPTransportException
-     * @exception MalformedSMTPReplyException
-     * @exception MessagingException
-     */
-    protected boolean sendEhlo() throws MessagingException {
-        sendLine("EHLO " + getLocalHost());
-
-        SMTPReply reply = getReply();
-
-        // we get a 250 code back. The first line is just a greeting, and
-        // extensions are identifed on
-        // continuations. If this fails, then we'll try once more with HELO to
-        // establish bona fides.
-        if (reply.getCode() != COMMAND_ACCEPTED) {
-            return false;
-        }
-
-        // get a fresh extension mapping table.
-        serverExtensionArgs = new HashMap();
-
-        List lines = reply.getLines(); 
-        // process all of the continuation lines
-        for (int i = 1; i < lines.size(); i++) {
-            // go process the extention
-            processExtension((String)lines.get(i));
-        }
-        return true;
-    }
-
-    /**
-     * Send the HELO command to the SMTP server.
-     *
-     * @exception MessagingException
-     */
-    protected void sendHelo() throws MessagingException {
-        sendLine("HELO " + getLocalHost());
-
-        SMTPReply line = getReply();
-
-        // we get a 250 code back. The first line is just a greeting, and
-        // extensions are identifed on
-        // continuations. If this fails, then we'll try once more with HELO to
-        // establish bona fides.
-        if (line.getCode() != COMMAND_ACCEPTED) {
-            throw new MessagingException("Failure sending HELO command to SMTP server");
-        }
-    }
 
     /**
      * Retrieve the local client host name.
@@ -2037,38 +580,28 @@
      * @exception SMTPTransportException
      */
     public String getLocalHost() throws MessagingException {
-        if (localHost == null) {
-
-            try {
-                localHost = InetAddress.getLocalHost().getHostName();
-            } catch (UnknownHostException e) {
-                // fine, we're misconfigured - ignore
-            }
-
-            if (localHost == null) {
-                localHost = getProtocolProperty(MAIL_SMTP_LOCALHOST);
-            }
-
-            if (localHost == null) {
-                localHost = getSessionProperty(MAIL_LOCALHOST);
-            }
-
-            if (localHost == null) {
-                throw new MessagingException("Can't get local hostname. "
-                        + " Please correctly configure JDK/DNS or set mail.smtp.localhost");
-            }
-        }
-
-        return localHost;
+        return connection.getLocalHost(); 
     }
 
+    
+    /**
+     * Explicitly set the local host information.
+     *
+     * @param localHost
+     *            The new localHost name.
+     */
+    public void setLocalHost(String localHost) {
+        connection.setLocalHost(localHost); 
+    }
+
+    
     /**
      * Return the current reportSuccess property.
      *
      * @return The current reportSuccess property.
      */
     public boolean getReportSuccess() {
-        return reportSuccess;
+        return connection.getReportSuccess(); 
     }
 
     /**
@@ -2078,7 +611,7 @@
      *            The new setting.
      */
     public void setReportSuccess(boolean report) {
-        reportSuccess = report;
+        connection.setReportSuccess(report); 
     }
 
     /**
@@ -2087,7 +620,7 @@
      * @return The current startTLS property.
      */
     public boolean getStartTLS() {
-        return reportSuccess;
+        return connection.getStartTLS(); 
     }
 
     /**
@@ -2097,7 +630,7 @@
      *            The new setting.
      */
     public void setStartTLS(boolean start) {
-        useTLS = start;
+        connection.setStartTLS(start); 
     }
 
     /**
@@ -2108,11 +641,7 @@
      * @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 = getProtocolProperty(MAIL_SMTP_SASL_REALM);
-        }
-        return realm;
+        return connection.getSASLRealm(); 
     }
 
     /**
@@ -2122,487 +651,6 @@
      *            The new realm name.
      */
     public void setSASLRealm(String name) {
-        realm = name;
-    }
-
-    /**
-     * Explicitly set the local host information.
-     *
-     * @param localHost
-     *            The new localHost name.
-     */
-    public void setLocalHost(String localHost) {
-        this.localHost = localHost;
-    }
-
-    /**
-     * Process an extension string passed back as the EHLP response.
-     *
-     * @param extension
-     *            The string value of the extension (which will be of the form
-     *            "NAME arguments").
-     */
-    protected void processExtension(String extension) {
-        debugOut("Processing extension " + extension); 
-        String extensionName = extension.toUpperCase();
-        String argument = "";
-
-        int delimiter = extension.indexOf(' ');
-        // if we have a keyword with arguments, parse them out and add to the
-        // argument map.
-        if (delimiter != -1) {
-            extensionName = extension.substring(0, delimiter).toUpperCase();
-            argument = extension.substring(delimiter + 1);
-        }
-        
-        // add this to the map so it can be tested later.
-        serverExtensionArgs.put(extensionName, argument);
-
-        // process a few special ones that don't require extra parsing.
-        // AUTH and AUTH=LOGIN are handled the same
-        if (extensionName.equals("AUTH")) {
-            // if we don't have an argument on AUTH, this means LOGIN.
-            if (argument == null) {
-                serverAuthenticationMechanisms.put("LOGIN", "LOGIN");
-            } else {
-                // The security mechanisms are blank delimited tokens.
-                StringTokenizer tokenizer = new StringTokenizer(argument);
-
-                while (tokenizer.hasMoreTokens()) {
-                    String mechanism = tokenizer.nextToken().toUpperCase();
-                    serverAuthenticationMechanisms.put(mechanism, mechanism);
-                }
-            }
-        }
-        // special case for some older servers.
-        else if (extensionName.equals("AUTH=LOGIN")) {
-            serverAuthenticationMechanisms.put("LOGIN", "LOGIN");
-        }
-        // does this support transport level security?
-        else if (extensionName.equals("STARTTLS")) {
-            // flag this for later
-            serverTLS = true;
-        }
-    }
-
-    /**
-     * Retrieve any argument information associated with a extension reported
-     * back by the server on the EHLO command.
-     *
-     * @param name
-     *            The name of the target server extension.
-     *
-     * @return Any argument passed on a server extension. Returns null if the
-     *         extension did not include an argument or the extension was not
-     *         supported.
-     */
-    public String extensionParameter(String name) {
-        if (serverExtensionArgs != null) {
-            return (String) serverExtensionArgs.get(name);
-        }
-        return null;
-    }
-
-    /**
-     * Tests whether the target server supports a named extension.
-     *
-     * @param name
-     *            The target extension name.
-     *
-     * @return true if the target server reported on the EHLO command that is
-     *         supports the targer server, false if the extension was not
-     *         supported.
-     */
-    public boolean supportsExtension(String name) {
-        // this only returns null if we don't have this extension
-        return extensionParameter(name) != null;
-    }
-
-    /**
-     * Determine if the target server supports a given authentication mechanism.
-     *
-     * @param mechanism
-     *            The mechanism name.
-     *
-     * @return true if the server EHLO response indicates it supports the
-     *         mechanism, false otherwise.
-     */
-    protected boolean supportsAuthentication(String mechanism) {
-        return serverAuthenticationMechanisms.get(mechanism) != null;
-    }
-
-    /**
-     * Authenticate with the server, if necessary (or possible).
-     *
-     * @return true if we are ok to proceed, false for an authentication
-     *         failures.
-     */
-    protected boolean processAuthentication() throws MessagingException {
-        // no authentication defined?
-        if (!isProtocolPropertyTrue(MAIL_SMTP_AUTH)) {
-            return true;
-        }
-
-        // we need to authenticate, but we don't have userid/password
-        // information...fail this
-        // immediately.
-        if (username == null || password == null) {
-            return false;
-        }
-
-        ClientAuthenticator authenticator = null;
-
-        // now go through the progression of mechanisms we support, from the
-        // most secure to the
-        // least secure.
-
-        if (supportsAuthentication(AUTHENTICATION_DIGESTMD5)) {
-            authenticator = new DigestMD5Authenticator(host, username, password, getSASLRealm());
-        } else if (supportsAuthentication(AUTHENTICATION_CRAMMD5)) {
-            authenticator = new CramMD5Authenticator(username, password);
-        } else if (supportsAuthentication(AUTHENTICATION_LOGIN)) {
-            authenticator = new LoginAuthenticator(username, password);
-        } else if (supportsAuthentication(AUTHENTICATION_PLAIN)) {
-            authenticator = new PlainAuthenticator(username, password);
-        } else {
-            // can't find a mechanism we support in common
-            return false;
-        }
-
-        if (debug) {
-            debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
-        }
-
-        // if the authenticator has some initial data, we compose a command
-        // containing the initial data.
-        if (authenticator.hasInitialResponse()) {
-            StringBuffer command = new StringBuffer();
-            // the auth command initiates the handshaking.
-            command.append("AUTH ");
-            // and tell the server which mechanism we're using.
-            command.append(authenticator.getMechanismName());
-            command.append(" ");
-            // and append the response data
-            command.append(new String(Base64.encode(authenticator.evaluateChallenge(null))));
-            // send the command now
-            sendLine(command.toString());
-        }
-        // we just send an auth command with the command type.
-        else {
-            StringBuffer command = new StringBuffer();
-            // the auth command initiates the handshaking.
-            command.append("AUTH ");
-            // and tell the server which mechanism we're using.
-            command.append(authenticator.getMechanismName());
-            // send the command now
-            sendLine(command.toString());
-        }
-
-        // now process the challenge sequence. We get a 235 response back when
-        // the server accepts the
-        // authentication, and a 334 indicates we have an additional challenge.
-        while (true) {
-            // get the next line, and if it is an error response, return now.
-            SMTPReply line;
-            try {
-                line = new SMTPReply(receiveLine());
-            } catch (MalformedSMTPReplyException e) {
-                throw new MessagingException(e.toString());
-            } catch (MessagingException e) {
-                throw e;
-            }
-
-            // if we get a completion return, we've passed muster, so give an
-            // authentication response.
-            if (line.getCode() == AUTHENTICATION_COMPLETE) {
-                debugOut("Successful SMTP authentication");
-                return true;
-            }
-            // we have an additional challenge to process.
-            else if (line.getCode() == AUTHENTICATION_CHALLENGE) {
-                // Does the authenticator think it is finished? We can't answer
-                // an additional challenge,
-                // so fail this.
-                if (authenticator.isComplete()) {
-                    return false;
-                }
-
-                // we're passed back a challenge value, Base64 encoded.
-                byte[] challenge = Base64.decode(line.getMessage().getBytes());
-
-                // have the authenticator evaluate and send back the encoded
-                // response.
-                sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge))));
-            }
-            // completion or challenge are the only responses we know how to
-            // handle. Anything else must
-            // be a failure.
-            else {
-                if (debug) {
-                    debugOut("Authentication failure " + line);
-                }
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Simple holder class for the address/send status duple, as we can have
-     * mixed success for a set of addresses and a message
-     */
-    public class SendStatus {
-        public final static int SUCCESS = 0;
-
-        public final static int INVALID_ADDRESS = 1;
-
-        public final static int SEND_FAILURE = 2;
-
-        public final static int GENERAL_ERROR = 3;
-
-        // the status type of the send operation.
-        int status;
-
-        // the address associated with this status
-        InternetAddress address;
-
-        // the command string send to the server.
-        String cmd;
-
-        // the reply from the server.
-        SMTPReply reply;
-
-        /**
-         * Constructor for a SendStatus item.
-         *
-         * @param s
-         *            The status type.
-         * @param a
-         *            The address this is the status for.
-         * @param c
-         *            The command string associated with this status.
-         * @param r
-         *            The reply information from the server.
-         */
-        public SendStatus(int s, InternetAddress a, String c, SMTPReply r) {
-            this.cmd = c;
-            this.status = s;
-            this.address = a;
-            this.reply = r;
-        }
-
-        /**
-         * Get the status information for this item.
-         *
-         * @return The current status code.
-         */
-        public int getStatus() {
-            return this.status;
-        }
-
-        /**
-         * Retrieve the InternetAddress object associated with this send
-         * operation.
-         *
-         * @return The associated address object.
-         */
-        public InternetAddress getAddress() {
-            return this.address;
-        }
-
-        /**
-         * Retrieve the reply information associated with this send operati
-         *
-         * @return The SMTPReply object received for the operation.
-         */
-        public SMTPReply getReply() {
-            return reply;
-        }
-
-        /**
-         * Get the command string sent for this send operation.
-         *
-         * @return The command string for the MAIL TO command sent to the
-         *         server.
-         */
-        public String getCommand() {
-            return cmd;
-        }
-
-        /**
-         * Get an exception object associated with this send operation. There is
-         * a mechanism for reporting send success via a send operation, so this
-         * will be either a success or failure exception.
-         *
-         * @param reportSuccess
-         *            Indicates if we want success operations too.
-         *
-         * @return A newly constructed exception object.
-         */
-        public MessagingException getException(boolean reportSuccess) {
-            if (status != SUCCESS) {
-                return new SMTPAddressFailedException(address, cmd, reply.getCode(), reply.getMessage());
-            } else {
-                if (reportSuccess) {
-                    return new SMTPAddressSucceededException(address, cmd, reply.getCode(), reply.getMessage());
-                }
-            }
-            return null;
-        }
-    }
-
-    /**
-     * Internal debug output routine.
-     *
-     * @param value
-     *            The string value to output.
-     */
-    protected void debugOut(String message) {
-        if (debug) {
-            debugStream.println("SMTPTransport 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);
-        }
-    }
-    
-    
-    /**
-     * Check to see if a MIME body part can have its 
-     * encoding changed from quoted-printable or base64
-     * encoding to 8bit encoding.  In order for this 
-     * to work, it must follow the rules laid out in 
-     * RFC 2045.  To qualify for conversion, the text 
-     * must be: 
-     * 
-     * 1)  No more than 998 bytes long 
-     * 2)  All lines are terminated with CRLF sequences
-     * 3)  CR and LF characters only occur in properly 
-     * formed line separators 
-     * 4)  No null characters are allowed. 
-     * 
-     * The conversion will only be applied to text 
-     * elements, and this will recurse through the 
-     * different elements of MultiPart content. 
-     * 
-     * @param bodyPart The bodyPart to convert. Initially, this will be
-     *                 the message itself.
-     * 
-     * @return true if any conversion was performed, false if 
-     *         nothing was converted.
-     */
-    protected boolean convertTransferEncoding(MimePart bodyPart)
-    {
-        boolean converted = false; 
-        try {
-            // if this is a multipart element, apply the conversion rules 
-            // to each of the parts. 
-            if (bodyPart.isMimeType("multipart/")) {
-                MimeMultipart parts = (MimeMultipart)bodyPart.getContent(); 
-                for (int i = 0; i < parts.getCount(); i++) {
-                    // convert each body part, and accumulate the conversion result 
-                    converted = converted && convertTransferEncoding((MimePart)parts.getBodyPart(i)); 
-                }
-            }
-            else {
-                // we only do this if the encoding is quoted-printable or base64
-                String encoding =  bodyPart.getEncoding(); 
-                if (encoding != null) {
-                    encoding = encoding.toLowerCase(); 
-                    if (encoding.equals("quoted-printable") || encoding.equals("base64")) {
-                        // this requires encoding.  Read the actual content to see if 
-                        // it conforms to the 8bit encoding rules. 
-                        if (isValid8bit(bodyPart.getInputStream())) {
-                            // There's a huge hidden gotcha lurking under the covers here. 
-                            // If the content just exists as an encoded byte array, then just 
-                            // switching the transfer encoding will mess things up because the 
-                            // already encoded data gets transmitted in encoded form, but with 
-                            // and 8bit encoding style.  As a result, it doesn't get unencoded on 
-                            // the receiving end.  This is a nasty problem to debug.  
-                            //
-                            // The solution is to get the content as it's object type, set it back 
-                            // on the the message in raw form.  Requesting the content will apply the 
-                            // current transfer encoding value to the data.  Once we have set the 
-                            // content value back, we can reset the transfer encoding. 
-                            bodyPart.setContent(bodyPart.getContent(), bodyPart.getContentType()); 
-                            
-                            // it's valid, so change the transfer encoding to just 
-                            // pass the data through.  
-                            bodyPart.setHeader("Content-Transfer-Encoding", "8bit"); 
-                            converted = true;   // we've changed something
-                        }
-                    }
-                }
-            }
-        } catch (MessagingException e) {
-        } catch (IOException e) {
-        }
-        return converted; 
-    }
-    
-    
-    /**
-     * Read the bytes in a stream a test to see if this 
-     * conforms to the RFC 2045 rules for 8bit encoding. 
-     * 
-     * 1)  No more than 998 bytes long 
-     * 2)  All lines are terminated with CRLF sequences
-     * 3)  CR and LF characters only occur in properly 
-     * formed line separators 
-     * 4)  No null characters are allowed. 
-     * 
-     * @param inStream The source input stream.
-     * 
-     * @return true if this can be transmitted successfully 
-     *         using 8bit encoding, false if an alternate encoding
-     *         will be required.
-     */
-    protected boolean isValid8bit(InputStream inStream) { 
-        try {
-            int ch;  
-            int lineLength = 0; 
-            while ((ch = inStream.read()) >= 0) {
-                // nulls are decidedly not allowed 
-                if (ch == 0) {
-                    return false; 
-                }
-                // start of a CRLF sequence (potentially) 
-                else if (ch == '\r') {
-                    // check the next character.  There must be one, 
-                    // and it must be a LF for this to be value 
-                    ch = inStream.read(); 
-                    if (ch != '\n') {
-                        return false; 
-                    }
-                    // reset the line length 
-                    lineLength = 0; 
-                }
-                else {
-                    // a normal character 
-                    lineLength++; 
-                    // make sure the line is not too long 
-                    if (lineLength > 998) {
-                        return false; 
-                    }
-                }
-                
-            }
-        } catch (IOException e) {
-            return false;  // can't read this, don't try passing it 
-        }
-        // this converted ok 
-        return true; 
+        connection.setSASLRealm(name); 
     }
 }
diff --git a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java
index e8475b5..86e30f2 100644
--- a/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java
+++ b/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java
@@ -122,6 +122,9 @@
     protected InetAddress localAddress;
     // our local port value 
     protected int localPort; 
+    // our local host name
+    protected String localHost;
+    
     // our timeout value 
     protected int timeout; 
     
@@ -219,6 +222,17 @@
     
     
     /**
+     * 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.
      *
@@ -482,7 +496,6 @@
      * switching to an SSL socket.
      */
     protected void getConnectedTLSSocket() throws MessagingException {
-        debugOut("Attempting to negotiate STARTTLS with server " + serverHost);
      	// it worked, now switch the socket into TLS mode
      	try {
 
@@ -518,6 +531,7 @@
             if (sslSocket instanceof SSLSocket) {
                 ((SSLSocket)sslSocket).setEnabledProtocols(new String[] {"TLSv1"} );
                 ((SSLSocket)sslSocket).setUseClientMode(true);
+                debugOut("Initiating STARTTLS handshake"); 
                 ((SSLSocket)sslSocket).startHandshake();
             }
 
@@ -525,6 +539,7 @@
             // this is our active socket now
             socket = sslSocket;
             getConnectionStreams(); 
+            debugOut("TLS connection established"); 
      	}
         catch (Exception e) {
             debugOut("Failure attempting to convert connection to TLS", e);
@@ -719,7 +734,7 @@
      */
     protected void debugOut(String message) {
         if (debug) {
-            debugStream.println("IMAPStore DEBUG: " + message);
+            debugStream.println(protocol + " DEBUG: " + message);
         }
     }
 
@@ -780,4 +795,48 @@
     public String getHost() {
         return serverHost; 
     }
+    
+
+    /**
+     * Retrieve the local client host name.
+     *
+     * @return The string version of the local host name.
+     * @exception SMTPTransportException
+     */
+    public String getLocalHost() throws MessagingException {
+        if (localHost == null) {
+
+            try {
+                localHost = InetAddress.getLocalHost().getHostName();
+            } catch (UnknownHostException e) {
+                // fine, we're misconfigured - ignore
+            }
+
+            if (localHost == null) {
+                localHost = props.getProperty(MAIL_LOCALHOST);
+            }
+
+            if (localHost == null) {
+                localHost = props.getSessionProperty(MAIL_LOCALHOST);
+            }
+
+            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;
+    }
 }