You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by ri...@apache.org on 2006/05/01 18:56:09 UTC

svn commit: r398634 [1/3] - in /geronimo/branches/1.1/modules/javamail-transport/src: java/org/apache/geronimo/javamail/authentication/ java/org/apache/geronimo/javamail/transport/smtp/ java/org/apache/geronimo/javamail/util/ resources/META-INF/

Author: rickmcguire
Date: Mon May  1 09:56:06 2006
New Revision: 398634

URL: http://svn.apache.org/viewcvs?rev=398634&view=rev
Log:
GERONIMO-1957 upgrade the SMTP transport version to match the javamail spec implementation.



Added:
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/ClientAuthenticator.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/CramMD5Authenticator.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/DigestMD5Authenticator.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/LoginAuthenticator.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/PlainAuthenticator.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressFailedException.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressSucceededException.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPMessage.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPSTransport.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPSendFailedException.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/util/
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/util/MIMEOutputStream.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/util/TraceInputStream.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/util/TraceOutputStream.java
Modified:
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/MalformedSMTPReplyException.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
    geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransportException.java
    geronimo/branches/1.1/modules/javamail-transport/src/resources/META-INF/javamail.default.providers

Added: geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/ClientAuthenticator.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/ClientAuthenticator.java?rev=398634&view=auto
==============================================================================
--- geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/ClientAuthenticator.java (added)
+++ geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/ClientAuthenticator.java Mon May  1 09:56:06 2006
@@ -0,0 +1,84 @@
+/**
+ *
+ * Copyright 2003-2005 The Apache Software Foundation
+ *
+ *  Licensed 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.authentication;
+
+import javax.mail.MessagingException;
+
+/**
+ * Simplified version of the Java 5 SaslClient interface. This is used to
+ * implement a javamail authentication framework that mimics the Sasl framework
+ * on a 1.4.2 JVM. Only the methods required by the Javamail code are
+ * implemented here, but it should be a simple migration to the fuller SASL
+ * interface.
+ */
+public interface ClientAuthenticator {
+    /**
+     * Evaluate a challenge and return a response that can be sent back to the
+     * server. Bot the challenge information and the response information are
+     * "raw data", minus any special encodings used by the transport. For
+     * example, SMTP DIGEST-MD5 authentication protocol passes information as
+     * Base64 encoded strings. That encoding must be removed before calling
+     * evaluateChallenge() and the resulting respose must be Base64 encoced
+     * before transmission to the server.
+     * 
+     * It is the authenticator's responsibility to keep track of the state of
+     * the evaluations. That is, if the authentication process requires multiple
+     * challenge/response cycles, then the authenticator needs to keep track of
+     * context of the challenges.
+     * 
+     * @param challenge
+     *            The challenge data.
+     * 
+     * @return An appropriate response for the challenge data.
+     */
+
+    public byte[] evaluateChallenge(byte[] challenge) throws MessagingException;
+
+    /**
+     * Indicates that the authenticator has data that should be sent when the
+     * authentication process is initiated. For example, the SMTP PLAIN
+     * authentication sends userid/password without waiting for a challenge
+     * response.
+     * 
+     * If this method returns true, then the initial response is retrieved using
+     * evaluateChallenge() passing null for the challenge information.
+     * 
+     * @return True if the challenge/response process starts with an initial
+     *         response on the client side.
+     */
+    public boolean hasInitialResponse();
+
+    /**
+     * Indicates whether the client believes the challenge/response sequence is
+     * now complete.
+     * 
+     * @return true if the client has evaluated what it believes to be the last
+     *         challenge, false if there are additional stages to evaluate.
+     */
+
+    public boolean isComplete();
+
+    /**
+     * Return the mechanism name implemented by this authenticator.
+     * 
+     * @return The string name of the authentication mechanism. This name should
+     *         match the names commonly used by the mail servers (e.g., "PLAIN",
+     *         "LOGIN", "DIGEST-MD5", etc.).
+     */
+    public String getMechanismName();
+}

Added: geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/CramMD5Authenticator.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/CramMD5Authenticator.java?rev=398634&view=auto
==============================================================================
--- geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/CramMD5Authenticator.java (added)
+++ geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/CramMD5Authenticator.java Mon May  1 09:56:06 2006
@@ -0,0 +1,173 @@
+/**
+ *
+ * Copyright 2003-2005 The Apache Software Foundation
+ *
+ *  Licensed 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.authentication;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.mail.util.Hex;
+
+public class CramMD5Authenticator implements ClientAuthenticator {
+
+    // the user we're authenticating
+    protected String username;
+
+    // the user's password (the "shared secret")
+    protected String password;
+
+    // indicates whether we've gone through the entire challenge process.
+    protected boolean complete = false;
+
+    /**
+     * Main constructor.
+     * 
+     * @param username
+     *            The login user name.
+     * @param password
+     *            The login password.
+     */
+    public CramMD5Authenticator(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    /**
+     * Respond to the hasInitialResponse query. This mechanism does not have an
+     * initial response.
+     * 
+     * @return Always returns false.
+     */
+    public boolean hasInitialResponse() {
+        return false;
+    }
+
+    /**
+     * Indicate whether the challenge/response process is complete.
+     * 
+     * @return True if the last challenge has been processed, false otherwise.
+     */
+    public boolean isComplete() {
+        return complete;
+    }
+
+    /**
+     * Retrieve the authenticator mechanism name.
+     * 
+     * @return Always returns the string "CRAM-MD5"
+     */
+    public String getMechanismName() {
+        return "CRAM-MD5";
+    }
+
+    /**
+     * Evaluate a CRAM-MD5 login challenge, returning the a result string that
+     * should satisfy the clallenge.
+     * 
+     * @param challenge
+     *            The decoded challenge data, as a byte array.
+     * 
+     * @return A formatted challege response, as an array of bytes.
+     * @exception MessagingException
+     */
+    public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
+        // we create the challenge from the userid and password information (the
+        // "shared secret").
+        byte[] passBytes;
+
+        try {
+            // get the password in an UTF-8 encoding to create the token
+            passBytes = password.getBytes("UTF-8");
+            // compute the password digest using the key
+            byte[] digest = computeCramDigest(passBytes, challenge);
+
+            // create a unified string using the user name and the hex encoded
+            // digest
+            String responseString = username + " " + new String(Hex.encode(digest));
+            complete = true;
+            return responseString.getBytes();
+        } catch (UnsupportedEncodingException e) {
+            // got an error, fail this
+            throw new MessagingException("Invalid character encodings");
+        }
+
+    }
+
+    /**
+     * Compute a CRAM digest using the hmac_md5 algorithm. See the description
+     * of RFC 2104 for algorithm details.
+     * 
+     * @param key
+     *            The key (K) for the calculation.
+     * @param input
+     *            The encrypted text value.
+     * 
+     * @return The computed digest, as a byte array value.
+     * @exception NoSuchAlgorithmException
+     */
+    protected byte[] computeCramDigest(byte[] key, byte[] input) throws MessagingException {
+        // CRAM digests are computed using the MD5 algorithm.
+        MessageDigest digest;
+        try {
+            digest = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException e) {
+            throw new MessagingException("Unable to access MD5 message digest", e);
+        }
+
+        // if the key is longer than 64 bytes, then we get a digest of the key
+        // and use that instead.
+        // this is required by RFC 2104.
+        if (key.length > 64) {
+            digest.update(key);
+            key = digest.digest();
+        }
+
+        // now we create two 64 bit padding keys, initialized with the key
+        // information.
+        byte[] ipad = new byte[64];
+        byte[] opad = new byte[64];
+
+        System.arraycopy(key, 0, ipad, 0, key.length);
+        System.arraycopy(key, 0, opad, 0, key.length);
+
+        // and these versions are munged by XORing with "magic" values.
+
+        for (int i = 0; i < 64; i++) {
+            ipad[i] ^= 0x36;
+            opad[i] ^= 0x5c;
+        }
+
+        // now there are a pair of MD5 operations performed, and inner and an
+        // outer. The spec defines this as
+        // H(K XOR opad, H(K XOR ipad, text)), where H is the MD5 operation.
+
+        // inner operation
+        digest.reset();
+        digest.update(ipad);
+        digest.update(input); // this appends the text to the pad
+        byte[] md5digest = digest.digest();
+
+        // outer operation
+        digest.reset();
+        digest.update(opad);
+        digest.update(md5digest);
+        return digest.digest(); // final result
+    }
+}

Added: geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/DigestMD5Authenticator.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/DigestMD5Authenticator.java?rev=398634&view=auto
==============================================================================
--- geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/DigestMD5Authenticator.java (added)
+++ geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/DigestMD5Authenticator.java Mon May  1 09:56:06 2006
@@ -0,0 +1,622 @@
+/**
+ *
+ * Copyright 2003-2005 The Apache Software Foundation
+ *
+ *  Licensed 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.authentication;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+
+import javax.mail.AuthenticationFailedException;
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.mail.util.Base64;
+import org.apache.geronimo.mail.util.Hex;
+
+/**
+ * Process a DIGEST-MD5 authentication, using the challenge/response mechanisms.
+ */
+public class DigestMD5Authenticator implements ClientAuthenticator {
+
+    protected static final int AUTHENTICATE_CLIENT = 0;
+
+    protected static final int AUTHENTICATE_SERVER = 1;
+
+    protected static final int AUTHENTICATION_COMPLETE = 2;
+
+    // the host server name
+    protected String host;
+
+    // the user we're authenticating
+    protected String username;
+
+    // the user's password (the "shared secret")
+    protected String password;
+
+    // the target login realm
+    protected String realm;
+
+    // our message digest for processing the challenges.
+    MessageDigest digest;
+
+    // the string we send to the server on the first challenge.
+    protected String clientResponse;
+
+    // the response back from an authentication challenge.
+    protected String authenticationResponse = null;
+
+    // our list of realms received from the server (normally just one).
+    protected ArrayList realms;
+
+    // the nonce value sent from the server
+    protected String nonce;
+
+    // indicates whether we've gone through the entire challenge process.
+    protected int stage = AUTHENTICATE_CLIENT;
+
+    /**
+     * Main constructor.
+     * 
+     * @param host
+     *            The server host name.
+     * @param username
+     *            The login user name.
+     * @param password
+     *            The login password.
+     * @param realm
+     *            The target login realm (can be null).
+     */
+    public DigestMD5Authenticator(String host, String username, String password, String realm) {
+        this.host = host;
+        this.username = username;
+        this.password = password;
+        this.realm = realm;
+    }
+
+    /**
+     * Respond to the hasInitialResponse query. This mechanism does not have an
+     * initial response.
+     * 
+     * @return Always returns false.
+     */
+    public boolean hasInitialResponse() {
+        return false;
+    }
+
+    /**
+     * Indicate whether the challenge/response process is complete.
+     * 
+     * @return True if the last challenge has been processed, false otherwise.
+     */
+    public boolean isComplete() {
+        return stage == AUTHENTICATION_COMPLETE;
+    }
+
+    /**
+     * Retrieve the authenticator mechanism name.
+     * 
+     * @return Always returns the string "DIGEST-MD5"
+     */
+    public String getMechanismName() {
+        return "DIGEST-MD5";
+    }
+
+    /**
+     * Evaluate a DIGEST-MD5 login challenge, returning the a result string that
+     * should satisfy the clallenge.
+     * 
+     * @param challenge
+     *            The decoded challenge data, as a string.
+     * 
+     * @return A formatted challege response, as an array of bytes.
+     * @exception MessagingException
+     */
+    public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
+
+        // DIGEST-MD5 authentication goes in two stages. First state involves us
+        // validating with the
+        // server, the second stage is the server validating with us, using the
+        // shared secret.
+        switch (stage) {
+        // stage one of the process.
+        case AUTHENTICATE_CLIENT: {
+            // get the response and advance the processing stage.
+            byte[] response = authenticateClient(challenge);
+            stage = AUTHENTICATE_SERVER;
+            return response;
+        }
+
+        // stage two of the process.
+        case AUTHENTICATE_SERVER: {
+            // get the response and advance the processing stage to completed.
+            byte[] response = authenticateServer(challenge);
+            stage = AUTHENTICATION_COMPLETE;
+            return response;
+        }
+
+        // should never happen.
+        default:
+            throw new MessagingException("Invalid LOGIN challenge");
+        }
+    }
+
+    /**
+     * Evaluate a DIGEST-MD5 login server authentication challenge, returning
+     * the a result string that should satisfy the clallenge.
+     * 
+     * @param challenge
+     *            The decoded challenge data, as a string.
+     * 
+     * @return A formatted challege response, as an array of bytes.
+     * @exception MessagingException
+     */
+    public byte[] authenticateServer(byte[] challenge) throws MessagingException {
+        // parse the challenge string and validate.
+        if (!parseChallenge(challenge)) {
+            return null;
+        }
+
+        try {
+            // like all of the client validation steps, the following is order
+            // critical.
+            // first add in the URI information.
+            digest.update((":smtp/" + host).getBytes("US-ASCII"));
+            // now mix in the response we sent originally
+            String responseString = clientResponse + new String(Hex.encode(digest.digest()));
+            digest.update(responseString.getBytes("US-ASCII"));
+
+            // now convert that into a hex encoded string.
+            String validationText = new String(Hex.encode(digest.digest()));
+
+            // if everything went well, this calculated value should match what
+            // we got back from the server.
+            // our response back is just a null string....
+            if (validationText.equals(authenticationResponse)) {
+                return new byte[0];
+            }
+            throw new AuthenticationFailedException("Invalid DIGEST-MD5 response from server");
+        } catch (UnsupportedEncodingException e) {
+            throw new MessagingException("Invalid character encodings");
+        }
+
+    }
+
+    /**
+     * Evaluate a DIGEST-MD5 login client authentication challenge, returning
+     * the a result string that should satisfy the clallenge.
+     * 
+     * @param challenge
+     *            The decoded challenge data, as a string.
+     * 
+     * @return A formatted challege response, as an array of bytes.
+     * @exception MessagingException
+     */
+    public byte[] authenticateClient(byte[] challenge) throws MessagingException {
+        // parse the challenge string and validate.
+        if (!parseChallenge(challenge)) {
+            return null;
+        }
+
+        SecureRandom randomGenerator;
+        // before doing anything, make sure we can get the required crypto
+        // support.
+        try {
+            randomGenerator = new SecureRandom();
+            digest = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException e) {
+            throw new MessagingException("Unable to access cryptography libraries");
+        }
+
+        // if not configured for a realm, take the first realm from the list, if
+        // any
+        if (realm == null) {
+            // if not handed any realms, just use the host name.
+            if (realms.isEmpty()) {
+                realm = host;
+            } else {
+                // pretty arbitrary at this point, so just use the first one.
+                realm = (String) realms.get(0);
+            }
+        }
+
+        // use secure random to generate a collection of bytes. that is our
+        // cnonce value.
+        byte[] cnonceBytes = new byte[32];
+
+        randomGenerator.nextBytes(cnonceBytes);
+        // and get this as a base64 encoded string.
+        String cnonce = new String(Base64.encode(cnonceBytes));
+
+        // Now the digest computation part. This gets a bit tricky, and must be
+        // done in strict order.
+
+        try {
+            // this identifies where we're logging into.
+            String idString = username + ":" + realm + ":" + password;
+            // we get a digest for this string, then use the digest for the
+            // first stage
+            // of the next digest operation.
+            digest.update(digest.digest(idString.getBytes("US-ASCII")));
+
+            // now we add the nonce strings to the digest.
+            String nonceString = ":" + nonce + ":" + cnonce;
+            digest.update(nonceString.getBytes("US-ASCII"));
+
+            // hex encode this digest, and add on the string values
+            // NB, we only support "auth" for the quality of protection value
+            // (qop). We save this in an
+            // instance variable because we'll need this to validate the
+            // response back from the server.
+            clientResponse = new String(Hex.encode(digest.digest())) + ":" + nonce + ":00000001:" + cnonce + ":auth:";
+
+            // now we add in identification values to the hash.
+            String authString = "AUTHENTICATE:smtp/" + host;
+            digest.update(authString.getBytes("US-ASCII"));
+
+            // this gets added on to the client response
+            String responseString = clientResponse + new String(Hex.encode(digest.digest()));
+            // and this gets fed back into the digest
+            digest.update(responseString.getBytes("US-ASCII"));
+
+            // and FINALLY, the challege digest is hex encoded for sending back
+            // to the server (whew).
+            String challengeResponse = new String(Hex.encode(digest.digest()));
+
+            // now finally build the keyword/value part of the challenge
+            // response. These can be
+            // in any order.
+            StringBuffer response = new StringBuffer();
+
+            response.append("username=\"");
+            response.append(username);
+            response.append("\"");
+
+            response.append(",realm=\"");
+            response.append(realm);
+            response.append("\"");
+
+            // we only support auth qop values, and the nonce-count (nc) is
+            // always 1.
+            response.append(",qop=auth");
+            response.append(",nc=00000001");
+
+            response.append(",nonce=\"");
+            response.append(nonce);
+            response.append("\"");
+
+            response.append(",cnonce=\"");
+            response.append(cnonce);
+            response.append("\"");
+
+            response.append(",digest-uri=\"smtp/");
+            response.append(host);
+            response.append("\"");
+
+            response.append(",response=");
+            response.append(challengeResponse);
+
+            return response.toString().getBytes("US-ASCII");
+
+        } catch (UnsupportedEncodingException e) {
+            throw new MessagingException("Invalid character encodings");
+        }
+    }
+
+    /**
+     * Parse the challege string, pulling out information required for our
+     * challenge response.
+     * 
+     * @param challenge
+     *            The challenge data.
+     * 
+     * @return true if there were no errors parsing the string, false otherwise.
+     * @exception MessagingException
+     */
+    protected boolean parseChallenge(byte[] challenge) throws MessagingException {
+        realms = new ArrayList();
+
+        DigestParser parser = new DigestParser(new String(challenge));
+
+        // parse the entire string...but we ignore everything but the options we
+        // support.
+        while (parser.hasMore()) {
+            NameValuePair pair = parser.parseNameValuePair();
+
+            String name = pair.name;
+
+            // realm to add to our list?
+            if (name.equalsIgnoreCase("realm")) {
+                realms.add(pair.value);
+            }
+            // we need the nonce to evaluate the client challenge.
+            else if (name.equalsIgnoreCase("nonce")) {
+                nonce = pair.value;
+            }
+            // rspauth is the challenge replay back, which allows us to validate
+            // that server is also legit.
+            else if (name.equalsIgnoreCase("rspauth")) {
+                authenticationResponse = pair.value;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Inner class for parsing a DIGEST-MD5 challenge string, which is composed
+     * of "name=value" pairs, separated by "," characters.
+     */
+    class DigestParser {
+        // the challenge we're parsing
+        String challenge;
+
+        // length of the challenge
+        int length;
+
+        // current parsing position
+        int position;
+
+        /**
+         * Normal constructor.
+         * 
+         * @param challenge
+         *            The challenge string to be parsed.
+         */
+        public DigestParser(String challenge) {
+            this.challenge = challenge;
+            this.length = challenge.length();
+            position = 0;
+        }
+
+        /**
+         * Test if there are more values to parse.
+         * 
+         * @return true if we've not reached the end of the challenge string,
+         *         false if the challenge has been completely consumed.
+         */
+        private boolean hasMore() {
+            return position < length;
+        }
+
+        /**
+         * Return the character at the current parsing position.
+         * 
+         * @return The string character for the current parse position.
+         */
+        private char currentChar() {
+            return challenge.charAt(position);
+        }
+
+        /**
+         * step forward to the next character position.
+         */
+        private void nextChar() {
+            position++;
+        }
+
+        /**
+         * Skip over any white space characters in the challenge string.
+         */
+        private void skipSpaces() {
+            while (position < length && Character.isWhitespace(currentChar())) {
+                position++;
+            }
+        }
+
+        /**
+         * Parse a quoted string used with a name/value pair, accounting for
+         * escape characters embedded within the string.
+         * 
+         * @return The string value of the character string.
+         */
+        private String parseQuotedValue() {
+            // we're here because we found the starting double quote. Step over
+            // it and parse to the closing
+            // one.
+            nextChar();
+
+            StringBuffer value = new StringBuffer();
+
+            while (hasMore()) {
+                char ch = currentChar();
+
+                // is this an escape char?
+                if (ch == '\\') {
+                    // step past this, and grab the following character
+                    nextChar();
+                    // we have an invalid quoted string....
+                    if (!hasMore()) {
+                        return null;
+                    }
+                    value.append(currentChar());
+                }
+                // end of the string?
+                else if (ch == '"') {
+                    // step over this so the caller doesn't process it.
+                    nextChar();
+                    // return the constructed string.
+                    return value.toString();
+                } else {
+                    // step over the character and contine with the next
+                    // characteer1
+                    value.append(ch);
+                }
+                nextChar();
+            }
+            /* fell off the end without finding a closing quote! */
+            return null;
+        }
+
+        /**
+         * Parse a token value used with a name/value pair.
+         * 
+         * @return The string value of the token. Returns null if nothing is
+         *         found up to the separater.
+         */
+        private String parseTokenValue() {
+
+            StringBuffer value = new StringBuffer();
+
+            while (hasMore()) {
+                char ch = currentChar();
+                switch (ch) {
+                // process the token separators.
+                case ' ':
+                case '\t':
+                case '(':
+                case ')':
+                case '<':
+                case '>':
+                case '@':
+                case ',':
+                case ';':
+                case ':':
+                case '\\':
+                case '"':
+                case '/':
+                case '[':
+                case ']':
+                case '?':
+                case '=':
+                case '{':
+                case '}':
+                    // no token characters found? this is bad.
+                    if (value.length() == 0) {
+                        return null;
+                    }
+                    // return the accumulated characters.
+                    return value.toString();
+
+                default:
+                    // is this a control character? That's a delimiter (likely
+                    // invalid for the next step,
+                    // but it is a token terminator.
+                    if (ch < 32 || ch > 127) {
+                        // no token characters found? this is bad.
+                        if (value.length() == 0) {
+                            return null;
+                        }
+                        // return the accumulated characters.
+                        return value.toString();
+                    }
+                    value.append(ch);
+                    break;
+                }
+                // step to the next character.
+                nextChar();
+            }
+            // no token characters found? this is bad.
+            if (value.length() == 0) {
+                return null;
+            }
+            // return the accumulated characters.
+            return value.toString();
+        }
+
+        /**
+         * Parse out a name token of a name/value pair.
+         * 
+         * @return The string value of the name.
+         */
+        private String parseName() {
+            // skip to the value start
+            skipSpaces();
+
+            // the name is a token.
+            return parseTokenValue();
+        }
+
+        /**
+         * Parse out a a value of a name/value pair.
+         * 
+         * @return The string value associated with the name.
+         */
+        private String parseValue() {
+            // skip to the value start
+            skipSpaces();
+
+            // start of a quoted string?
+            if (currentChar() == '"') {
+                // parse it out as a string.
+                return parseQuotedValue();
+            }
+            // the value must be a token.
+            return parseTokenValue();
+        }
+
+        /**
+         * Parse a name/value pair in an DIGEST-MD5 string.
+         * 
+         * @return A NameValuePair object containing the two parts of the value.
+         * @exception MessagingException
+         */
+        public NameValuePair parseNameValuePair() throws MessagingException {
+            // get the name token
+            String name = parseName();
+            if (name == null) {
+                throw new MessagingException("Name syntax error");
+            }
+
+            // the name should be followed by an "=" sign
+            if (!hasMore() || currentChar() != '=') {
+                throw new MessagingException("Name/value pair syntax error");
+            }
+
+            // step over the equals
+            nextChar();
+
+            // now get the value part
+            String value = parseValue();
+            if (value == null) {
+                throw new MessagingException("Name/value pair syntax error");
+            }
+
+            // skip forward to the terminator, which should either be the end of
+            // the line or a ","
+            skipSpaces();
+            // all that work, only to have a syntax error at the end (sigh)
+            if (hasMore()) {
+                if (currentChar() != ',') {
+                    throw new MessagingException("Name/value pair syntax error");
+                }
+                // step over, and make sure we position ourselves at either the
+                // end or the first
+                // real character for parsing the next name/value pair.
+                nextChar();
+                skipSpaces();
+            }
+            return new NameValuePair(name, value);
+        }
+    }
+
+    /**
+     * Simple inner class to represent a name/value pair.
+     */
+    public class NameValuePair {
+        public String name;
+
+        public String value;
+
+        NameValuePair(String name, String value) {
+            this.name = name;
+            this.value = value;
+        }
+
+    }
+}

Added: geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/LoginAuthenticator.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/LoginAuthenticator.java?rev=398634&view=auto
==============================================================================
--- geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/LoginAuthenticator.java (added)
+++ geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/LoginAuthenticator.java Mon May  1 09:56:06 2006
@@ -0,0 +1,138 @@
+/**
+ *
+ * Copyright 2003-2005 The Apache Software Foundation
+ *
+ *  Licensed 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.authentication;
+
+import java.io.UnsupportedEncodingException;
+
+import javax.mail.MessagingException;
+
+public class LoginAuthenticator implements ClientAuthenticator {
+
+    // constants for the authentication stages
+    protected static final int USERNAME = 0;
+
+    protected static final int PASSWORD = 1;
+
+    protected static final int COMPLETE = 2;
+
+    // the user we're authenticating
+    protected String username;
+
+    // the user's password (the "shared secret")
+    protected String password;
+
+    // indicates whether we've gone through the entire challenge process.
+    protected int stage = USERNAME;
+
+    /**
+     * Main constructor.
+     * 
+     * @param username
+     *            The login user name.
+     * @param password
+     *            The login password.
+     */
+    public LoginAuthenticator(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    /**
+     * Respond to the hasInitialResponse query. This mechanism does not have an
+     * initial response.
+     * 
+     * @return Always returns false;
+     */
+    public boolean hasInitialResponse() {
+        return false;
+    }
+
+    /**
+     * Indicate whether the challenge/response process is complete.
+     * 
+     * @return True if the last challenge has been processed, false otherwise.
+     */
+    public boolean isComplete() {
+        return stage == COMPLETE;
+    }
+
+    /**
+     * Retrieve the authenticator mechanism name.
+     * 
+     * @return Always returns the string "LOGIN"
+     */
+    public String getMechanismName() {
+        return "LOGIN";
+    }
+
+    /**
+     * Evaluate a PLAIN login challenge, returning the a result string that
+     * should satisfy the clallenge.
+     * 
+     * @param challenge
+     *            The decoded challenge data, as a byte array
+     * 
+     * @return A formatted challege response, as an array of bytes.
+     * @exception MessagingException
+     */
+    public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
+
+        // process the correct stage for the challenge
+        switch (stage) {
+        // should never happen
+        case COMPLETE:
+            throw new MessagingException("Invalid LOGIN challenge");
+
+        case USERNAME: {
+            byte[] userBytes;
+
+            try {
+                // get the username and password in an UTF-8 encoding to create
+                // the token
+                userBytes = username.getBytes("UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                // got an error, fail this (this should never happen).
+                throw new MessagingException("Invalid encoding");
+            }
+
+            // next time through we're looking for a password.
+            stage = PASSWORD;
+            // the user bytes are the entire challenge respose.
+            return userBytes;
+        }
+
+        case PASSWORD: {
+            byte[] passBytes;
+
+            try {
+                // get the username and password in an UTF-8 encoding to create
+                // the token
+                passBytes = password.getBytes("UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                // got an error, fail this (this should never happen).
+                throw new MessagingException("Invalid encoding");
+            }
+            // we're finished
+            stage = COMPLETE;
+            return passBytes;
+        }
+        }
+        // should never get here.
+        throw new MessagingException("Invalid LOGIN challenge");
+    }
+}

Added: geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/PlainAuthenticator.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/PlainAuthenticator.java?rev=398634&view=auto
==============================================================================
--- geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/PlainAuthenticator.java (added)
+++ geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/authentication/PlainAuthenticator.java Mon May  1 09:56:06 2006
@@ -0,0 +1,110 @@
+/**
+ *
+ * Copyright 2003-2005 The Apache Software Foundation
+ *
+ *  Licensed 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.authentication;
+
+import java.io.UnsupportedEncodingException;
+
+import javax.mail.MessagingException;
+
+public class PlainAuthenticator implements ClientAuthenticator {
+
+    // the user we're authenticating
+    protected String username;
+
+    // the user's password (the "shared secret")
+    protected String password;
+
+    // indicates whether we've gone through the entire challenge process.
+    protected boolean complete = false;
+
+    /**
+     * Main constructor.
+     * 
+     * @param username
+     *            The login user name.
+     * @param password
+     *            The login password.
+     */
+    public PlainAuthenticator(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    /**
+     * Respond to the hasInitialResponse query. This mechanism does have an
+     * initial response, which is the entire challenge sequence.
+     * 
+     * @return Always returns true.
+     */
+    public boolean hasInitialResponse() {
+        return true;
+    }
+
+    /**
+     * Indicate whether the challenge/response process is complete.
+     * 
+     * @return True if the last challenge has been processed, false otherwise.
+     */
+    public boolean isComplete() {
+        return complete;
+    }
+
+    /**
+     * Retrieve the authenticator mechanism name.
+     * 
+     * @return Always returns the string "PLAIN"
+     */
+    public String getMechanismName() {
+        return "PLAIN";
+    }
+
+    /**
+     * Evaluate a PLAIN login challenge, returning the a result string that
+     * should satisfy the clallenge.
+     * 
+     * @param challenge
+     *            The decoded challenge data, as byte array.
+     * 
+     * @return A formatted challege response, as an array of bytes.
+     * @exception MessagingException
+     */
+    public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
+        try {
+            // get the username and password in an UTF-8 encoding to create the
+            // token
+            byte[] userBytes = username.getBytes("UTF-8");
+            byte[] passBytes = password.getBytes("UTF-8");
+
+            // our token has two copies of the username, one copy of the
+            // password, and nulls
+            // between
+            byte[] tokenBytes = new byte[(userBytes.length * 2) + passBytes.length + 2];
+
+            System.arraycopy(userBytes, 0, tokenBytes, 0, userBytes.length);
+            System.arraycopy(userBytes, 0, tokenBytes, userBytes.length + 1, userBytes.length);
+            System.arraycopy(passBytes, 0, tokenBytes, (userBytes.length * 2) + 2, passBytes.length);
+
+            complete = true;
+            return tokenBytes;
+
+        } catch (UnsupportedEncodingException e) {
+            // got an error, fail this
+            throw new MessagingException("Invalid encoding");
+        }
+    }
+}

Modified: geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/MalformedSMTPReplyException.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/MalformedSMTPReplyException.java?rev=398634&r1=398633&r2=398634&view=diff
==============================================================================
--- geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/MalformedSMTPReplyException.java (original)
+++ geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/MalformedSMTPReplyException.java Mon May  1 09:56:06 2006
@@ -22,7 +22,7 @@
  * 
  * @version $Rev$ $Date$
  */
-class MalformedSMTPReplyException  extends Exception {
+class MalformedSMTPReplyException extends Exception {
     MalformedSMTPReplyException() {
         super();
     }
@@ -31,7 +31,7 @@
         super(msg);
     }
 
-    MalformedSMTPReplyException(String msg, Throwable t) {
+    MalformedSMTPReplyException(String msg, Exception t) {
         super(msg, t);
     }
 }

Added: geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressFailedException.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressFailedException.java?rev=398634&view=auto
==============================================================================
--- geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressFailedException.java (added)
+++ geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressFailedException.java Mon May  1 09:56:06 2006
@@ -0,0 +1,78 @@
+/**
+ *
+ * Copyright 2003-2005 The Apache Software Foundation
+ *
+ *  Licensed 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 javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+
+public class SMTPAddressFailedException extends MessagingException {
+    // the failing address
+    InternetAddress addr;
+
+    // the failing command
+    protected String cmd;
+
+    // the error code for the failure
+    protected int rc;
+
+    /**
+     * Constructor for an SMTPAddressFailingException.
+     * 
+     * @param addr
+     *            The failing address.
+     * @param cmd
+     *            The failing command string.
+     * @param rc
+     *            The error code for the command.
+     * @param err
+     *            An error message for the exception.
+     */
+    SMTPAddressFailedException(InternetAddress addr, java.lang.String cmd, int rc, java.lang.String err) {
+        super(err);
+        this.cmd = cmd;
+        this.rc = rc;
+        this.addr = addr;
+    }
+
+    /**
+     * Get the failing command string for the exception.
+     * 
+     * @return The string value of the failing command.
+     */
+    public String getCommand() {
+        return cmd;
+    }
+
+    /**
+     * The failing command return code.
+     * 
+     * @return The failure return code.
+     */
+    public int getReturnCode() {
+        return rc;
+    }
+
+    /**
+     * Retrieve the internet address associated with this exception.
+     * 
+     * @return The provided InternetAddress object.
+     */
+    public InternetAddress getAddress() {
+        return addr;
+    }
+}

Added: geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressSucceededException.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressSucceededException.java?rev=398634&view=auto
==============================================================================
--- geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressSucceededException.java (added)
+++ geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressSucceededException.java Mon May  1 09:56:06 2006
@@ -0,0 +1,78 @@
+/**
+ *
+ * Copyright 2003-2005 The Apache Software Foundation
+ *
+ *  Licensed 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 javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+
+public class SMTPAddressSucceededException extends MessagingException {
+    // the succeeding address
+    InternetAddress addr;
+
+    // the failing command
+    protected String cmd;
+
+    // the error code for the failure
+    protected int rc;
+
+    /**
+     * Constructor for an SMTPAddressSucceededException.
+     * 
+     * @param addr
+     *            The succeeding address.
+     * @param cmd
+     *            The succeeding command string.
+     * @param rc
+     *            The error code for the command.
+     * @param err
+     *            An error message for the exception.
+     */
+    SMTPAddressSucceededException(InternetAddress addr, java.lang.String cmd, int rc, java.lang.String err) {
+        super(err);
+        this.cmd = cmd;
+        this.rc = rc;
+        this.addr = addr;
+    }
+
+    /**
+     * Get the failing command string for the exception.
+     * 
+     * @return The string value of the failing command.
+     */
+    public String getCommand() {
+        return cmd;
+    }
+
+    /**
+     * The failing command return code.
+     * 
+     * @return The failure return code.
+     */
+    public int getReturnCode() {
+        return rc;
+    }
+
+    /**
+     * Retrieve the internet address associated with this exception.
+     * 
+     * @return The provided InternetAddress object.
+     */
+    public InternetAddress getAddress() {
+        return addr;
+    }
+}

Added: geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPMessage.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPMessage.java?rev=398634&view=auto
==============================================================================
--- geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPMessage.java (added)
+++ geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPMessage.java Mon May  1 09:56:06 2006
@@ -0,0 +1,235 @@
+/**
+ *
+ * Copyright 2003-2005 The Apache Software Foundation
+ *
+ *  Licensed 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.InputStream;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+
+public class SMTPMessage extends MimeMessage {
+
+    // never notify
+    public static final int NOTIFY_NEVER = -1;
+
+    // notify of successful deliveries.
+    public static final int NOTIFY_SUCCESS = 1;
+
+    // notify of delivery failures.
+    public static final int NOTIFY_FAILURE = 2;
+
+    // notify of delivery delays
+    public static final int NOTIFY_DELAY = 4;
+
+    // return full message with status notifications
+    public static final int RETURN_FULL = 1;
+
+    // return only message headers with status notifications
+    public static final int RETURN_HDRS = 2;
+
+    // support 8BitMime encodings
+    protected boolean allow8bitMIME = false;
+
+    // a from address specified in the message envelope. Overrides other from
+    // sources.
+    protected String envelopeFrom = null;
+
+    // an option string to append to the MAIL command on sending.
+    protected String mailExtension = null;
+
+    // SMTP mail notification options if DSN is supported.
+    protected int notifyOptions = 0;
+
+    // DSN return option notification values.
+    protected int returnOption = 0;
+
+    // allow sending if some addresses give errors.
+    protected boolean sendPartial = false;
+
+    // an RFC 2554 AUTH= value.
+    protected String submitter = null;
+
+    /**
+     * Default (and normal) constructor for an SMTPMessage.
+     * 
+     * @param session
+     *            The hosting Javamail Session.
+     */
+    public SMTPMessage(Session session) {
+        // this is a simple one.
+        super(session);
+    }
+
+    /**
+     * Construct an SMTPMessage instance by reading and parsing the data from
+     * the provided InputStream. The InputStream will be left positioned at the
+     * end of the message data on constructor completion.
+     * 
+     * @param session
+     *            The hosting Javamail Session.
+     */
+    public SMTPMessage(Session session, InputStream source) throws MessagingException {
+        // this is a simple one.
+        super(session, source);
+    }
+
+    /**
+     * Construct an SMTPMimeMessage from another source MimeMessage object. The
+     * new object and the old object are independent of each other.
+     * 
+     * @param source
+     *            The source MimeMessage object.
+     */
+    public SMTPMessage(MimeMessage source) throws MessagingException {
+        super(source);
+    }
+
+    /**
+     * Change the allow8BitMime attribute for the message.
+     * 
+     * @param a
+     *            The new setting.
+     */
+    public void setAllow8bitMIME(boolean a) {
+        allow8bitMIME = a;
+    }
+
+    /**
+     * Retrieve the current 8bitMIME attribute.
+     * 
+     * @return The current attribute value.
+     */
+    public boolean getAllow8bitMIME() {
+        return allow8bitMIME;
+    }
+
+    /**
+     * Change the envelopeFrom attribute for the message.
+     * 
+     * @param from
+     *            The new setting.
+     */
+    public void setEnvelopeFrom(String from) {
+        envelopeFrom = from;
+    }
+
+    /**
+     * Retrieve the current evelopeFrom attribute.
+     * 
+     * @return The current attribute value.
+     */
+    public String getEnvelopeFrom() {
+        return envelopeFrom;
+    }
+
+    /**
+     * Change the mailExtension attribute for the message.
+     * 
+     * @param e
+     *            The new setting.
+     */
+    public void setMailExtension(String e) {
+        mailExtension = e;
+    }
+
+    /**
+     * Retrieve the current mailExtension attribute.
+     * 
+     * @return The current attribute value.
+     */
+    public String getMailExtension() {
+        return mailExtension;
+    }
+
+    /**
+     * Change the notifyOptions attribute for the message.
+     * 
+     * @param options
+     *            The new setting.
+     */
+    public void setNotifyOptions(int options) {
+        notifyOptions = options;
+    }
+
+    /**
+     * Retrieve the current notifyOptions attribute.
+     * 
+     * @return The current attribute value.
+     */
+    public int getNotifyOptions() {
+        return notifyOptions;
+    }
+
+    /**
+     * Change the returnOptions attribute for the message.
+     * 
+     * @param option
+     *            The new setting.
+     */
+    public void setReturnOption(int option) {
+        returnOption = option;
+    }
+
+    /**
+     * Retrieve the current returnOption attribute.
+     * 
+     * @return The current attribute value.
+     */
+    public int getReturnOption() {
+        return returnOption;
+    }
+
+    /**
+     * Change the sendPartial attribute for the message.
+     * 
+     * @param a
+     *            The new setting.
+     */
+    public void setSendPartial(boolean a) {
+        sendPartial = a;
+    }
+
+    /**
+     * Retrieve the current sendPartial attribute.
+     * 
+     * @return The current attribute value.
+     */
+    public boolean getSendPartial() {
+        return sendPartial;
+    }
+
+    /**
+     * Change the submitter attribute for the message.
+     * 
+     * @param s
+     *            The new setting.
+     */
+    public void setSubmitter(String s) {
+        submitter = s;
+    }
+
+    /**
+     * Retrieve the current submitter attribute.
+     * 
+     * @return The current attribute value.
+     */
+    public String getSubmitter() {
+        return submitter;
+    }
+}

Modified: geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java?rev=398634&r1=398633&r2=398634&view=diff
==============================================================================
--- geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java (original)
+++ geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java Mon May  1 09:56:06 2006
@@ -19,26 +19,49 @@
 
 /**
  * Util class to represent a reply from a SMTP server
- *
+ * 
  * @version $Rev$ $Date$
  */
 class SMTPReply {
+    // The original reply string
+    private final String reply;
+
+    // returned message code
     private final int code;
+
+    // the returned message text
     private final String message;
-    private final char firstChar;
+
+    // indicates that this is a continuation response
+    private boolean continued;
 
     SMTPReply(String s) throws MalformedSMTPReplyException {
-        // first three must be the return code
+        // save the reply
+        reply = s;
+
+        // In a normal response, the first 3 must be the return code. However,
+        // the response back from a QUIT command is frequently a null string.
+        // Therefore, if the result is
+        // too short, just default the code to -1 and use the entire text for
+        // the message.
         if (s == null || s.length() < 3) {
-            throw new MalformedSMTPReplyException("Too Short! : " + s);
+            code = -1;
+            message = s;
+            return;
         }
 
         try {
-            firstChar = s.charAt(0);
+            continued = false;
             code = Integer.parseInt(s.substring(0, 3));
 
-            // message should be separated by a space
+            // message should be separated by a space OR a continuation
+            // character if this is a
+            // multi-line response.
             if (s.length() > 4) {
+                //
+                if (s.charAt(3) == '-') {
+                    continued = true;
+                }
                 message = s.substring(4);
             } else {
                 message = "";
@@ -48,23 +71,50 @@
         }
     }
 
-    int getCode() {
+    /**
+     * Return the code value associated with the reply.
+     * 
+     * @return The integer code associated with the reply.
+     */
+    public int getCode() {
         return this.code;
     }
 
-    String getMessage() {
+    /**
+     * Get the message text associated with the reply.
+     * 
+     * @return The string value of the message from the reply.
+     */
+    public String getMessage() {
         return this.message;
     }
 
     /**
+     * Retrieve the raw reply string for the reponse.
+     * 
+     * @return The original reply string from the server.
+     */
+    public String getReply() {
+        return reply;
+    }
+
+    /**
      * Indicates if reply is an error condition
      */
     boolean isError() {
-        if (firstChar == '5' || firstChar == '4') {
-            return true;
-        }
+        // error codes are all above 400
+        return code >= 400;
+    }
 
-        return false;
+    /**
+     * Indicates whether this response is flagged as part of a multiple line
+     * response.
+     * 
+     * @return true if the response has multiple lines, false if this is the
+     *         last line of the response.
+     */
+    public boolean isContinued() {
+        return continued;
     }
 
     public String toString() {

Added: geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPSTransport.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPSTransport.java?rev=398634&view=auto
==============================================================================
--- geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPSTransport.java (added)
+++ geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPSTransport.java Mon May  1 09:56:06 2006
@@ -0,0 +1,31 @@
+/**
+ *
+ * Copyright 2003-2005 The Apache Software Foundation
+ *
+ *  Licensed 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 javax.mail.Session;
+import javax.mail.URLName;
+
+public class SMTPSTransport extends SMTPTransport {
+    /**
+     * @param session
+     * @param name
+     */
+    public SMTPSTransport(Session session, URLName name) {
+        super(session, name, "smtps", 465, true);
+    }
+}

Added: geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPSendFailedException.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPSendFailedException.java?rev=398634&view=auto
==============================================================================
--- geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPSendFailedException.java (added)
+++ geronimo/branches/1.1/modules/javamail-transport/src/java/org/apache/geronimo/javamail/transport/smtp/SMTPSendFailedException.java Mon May  1 09:56:06 2006
@@ -0,0 +1,72 @@
+/**
+ *
+ * Copyright 2003-2005 The Apache Software Foundation
+ *
+ *  Licensed 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 javax.mail.Address;
+import javax.mail.SendFailedException;
+
+public class SMTPSendFailedException extends SendFailedException {
+    // the failing command
+    protected String cmd;
+
+    // the error code for the failure
+    protected int rc;
+
+    /**
+     * Constructor for an SMTPSendFaileException.
+     * 
+     * @param cmd
+     *            The failing command string.
+     * @param rc
+     *            The error code for the failing command.
+     * @param err
+     *            An error message for the exception.
+     * @param ex
+     *            Any associated nested exception.
+     * @param vs
+     *            An array of valid, sent addresses.
+     * @param vus
+     *            An array of addresses that were valid, but were unsent.
+     * @param inv
+     *            An array of addresses deemed invalid.
+     */
+    SMTPSendFailedException(java.lang.String cmd, int rc, java.lang.String err, java.lang.Exception ex, Address[] vs,
+            Address[] vus, Address[] inv) {
+        super(err, ex, vs, vus, inv);
+        this.cmd = cmd;
+        this.rc = rc;
+    }
+
+    /**
+     * Get the failing command string for the exception.
+     * 
+     * @return The string value of the failing command.
+     */
+    public String getCommand() {
+        return cmd;
+    }
+
+    /**
+     * The failing command return code.
+     * 
+     * @return The failure return code.
+     */
+    public int getReturnCode() {
+        return rc;
+    }
+}