You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by jl...@apache.org on 2020/10/07 15:58:41 UTC

svn commit: r1882306 [12/17] - in /geronimo/javamail/trunk/geronimo-javamail_1.6: ./ geronimo-javamail_1.6_mail/ geronimo-javamail_1.6_mail/src/ geronimo-javamail_1.6_mail/src/site/ geronimo-javamail_1.6_provider/ geronimo-javamail_1.6_provider/src/ ge...

Added: geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java?rev=1882306&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java Wed Oct  7 15:58:39 2020
@@ -0,0 +1,359 @@
+/*
+ * 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.store.pop3;
+ 
+import java.io.PrintStream; 
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.mail.AuthenticationFailedException;
+import javax.mail.Folder;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.URLName;
+
+import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection; 
+import org.apache.geronimo.javamail.store.pop3.connection.POP3ConnectionPool; 
+import org.apache.geronimo.javamail.util.ProtocolProperties;
+
+/**
+ * POP3 implementation of javax.mail.Store POP protocol spec is implemented in
+ * org.apache.geronimo.javamail.store.pop3.POP3Connection
+ * 
+ * @version $Rev$ $Date$
+ */
+
+public class POP3Store extends Store {
+    protected static final int DEFAULT_POP3_PORT = 110;
+    protected static final int DEFAULT_POP3_SSL_PORT = 995;
+    
+    
+    // our accessor for protocol properties and the holder of 
+    // protocol-specific information 
+    protected ProtocolProperties props; 
+    // our connection object    
+    protected POP3ConnectionPool connectionPool; 
+    // our session provided debug output stream.
+    protected PrintStream debugStream;
+    // the debug flag 
+    protected boolean debug; 
+    // the root folder 
+    protected POP3RootFolder root; 
+    // until we're connected, we're closed 
+    boolean closedForBusiness = true; 
+    protected LinkedList openFolders = new LinkedList(); 
+    
+    
+    public POP3Store(Session session, URLName name) {
+        this(session, name, "pop3", DEFAULT_POP3_PORT, false);
+    }
+
+    /**
+     * Common constructor used by the POP3Store and POP3SSLStore classes
+     * to do common initialization of defaults.
+     *
+     * @param session
+     *            The host session instance.
+     * @param name
+     *            The URLName of the target.
+     * @param protocol
+     *            The protocol type ("pop3"). This helps us in
+     *            retrieving protocol-specific session properties.
+     * @param defaultPort
+     *            The default port used by this protocol. For pop3, this will
+     *            be 110. The default for pop3 with ssl is 995.
+     * @param sslConnection
+     *            Indicates whether an SSL connection should be used to initial
+     *            contact the server. This is different from the STARTTLS
+     *            support, which switches the connection to SSL after the
+     *            initial startup.
+     */
+    protected POP3Store(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
+        super(session, name);
+        
+        // create the protocol property holder.  This gives an abstraction over the different 
+        // flavors of the protocol. 
+        props = new ProtocolProperties(session, protocol, sslConnection, defaultPort); 
+
+        // get our debug settings
+        debugStream = session.getDebugOut();
+        debug = session.getDebug(); 
+        // the connection pool manages connections for the stores, folder, and message usage. 
+        connectionPool = new POP3ConnectionPool(this, props); 
+    }
+
+
+    /**
+     * Return a Folder object that represents the root of the namespace for the current user.
+     *
+     * Note that in some store configurations (such as IMAP4) the root folder might
+     * not be the INBOX folder.
+     *
+     * @return the root Folder
+     * @throws MessagingException if there was a problem accessing the store
+     */
+	public Folder getDefaultFolder() throws MessagingException {
+		checkConnectionStatus();
+        // if no root yet, create a root folder instance. 
+        if (root == null) {
+            return new POP3RootFolder(this);
+        }
+        return root;
+	}
+
+    /**
+     * Return the Folder corresponding to the given name.
+     * The folder might not physically exist; the {@link Folder#exists()} method can be used
+     * to determine if it is real.
+     * 
+     * @param name   the name of the Folder to return
+     * 
+     * @return the corresponding folder
+     * @throws MessagingException
+     *                if there was a problem accessing the store
+     */
+	public Folder getFolder(String name) throws MessagingException {
+        return getDefaultFolder().getFolder(name);
+	}
+
+    
+    /**
+     * Return the folder identified by the URLName; the URLName must refer to this Store.
+     * Implementations may use the {@link URLName#getFile()} method to determined the folder name.
+     * 
+     * @param url
+     * 
+     * @return the corresponding folder
+     * @throws MessagingException
+     *                if there was a problem accessing the store
+     */
+	public Folder getFolder(URLName url) throws MessagingException {
+        return getDefaultFolder().getFolder(url.getFile());
+	}
+
+    
+    /**
+     * @see javax.mail.Service#protocolConnect(String, int,
+     *      String, String)
+     */
+    protected synchronized boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
+        
+        if (debug) {
+            debugOut("Connecting to server " + host + ":" + port + " for user " + username);
+        }
+
+        // the connection pool handles all of the details here. 
+        if (connectionPool.protocolConnect(host, port, username, password)) 
+        {
+            // the store is now open 
+            closedForBusiness = false; 
+            return true; 
+        }
+        return false; 
+    }
+    
+    
+    /**
+     * Get a connection for the store. 
+     * 
+     * @return The request connection object. 
+     * @exception MessagingException
+     */
+    protected POP3Connection getConnection() throws MessagingException {
+        return connectionPool.getConnection(); 
+    }
+    
+    /**
+     * Return a connection back to the connection pool after 
+     * it has been used for a request. 
+     * 
+     * @param connection The return connection.
+     * 
+     * @exception MessagingException
+     */
+    protected void releaseConnection(POP3Connection connection) throws MessagingException {
+        connectionPool.releaseConnection(connection); 
+    }
+    
+    /**
+     * Get a connection object for a folder to use. 
+     * 
+     * @param folder The requesting folder (always the inbox for POP3).
+     * 
+     * @return An active POP3Connection. 
+     * @exception MessagingException
+     */
+    synchronized POP3Connection getFolderConnection(POP3Folder folder) throws MessagingException {
+        POP3Connection connection = connectionPool.getConnection(); 
+        openFolders.add(folder);
+        return connection; 
+    }
+    
+    /**
+     * Release a connection object after a folder is 
+     * finished with a request. 
+     * 
+     * @param folder     The requesting folder.
+     * @param connection
+     * 
+     * @exception MessagingException
+     */
+    synchronized void releaseFolderConnection(POP3Folder folder, POP3Connection connection) throws MessagingException {
+        openFolders.remove(folder); 
+        // return this back to the pool 
+        connectionPool.releaseConnection(connection); 
+    }
+    
+    /**
+     * Close all open folders.  We have a small problem here with a race condition.  There's no safe, single
+     * synchronization point for us to block creation of new folders while we're closing.  So we make a copy of
+     * the folders list, close all of those folders, and keep repeating until we're done.
+     */
+    protected void closeOpenFolders() {
+        // we're no longer accepting additional opens.  Any folders that open after this point will get an
+        // exception trying to get a connection.
+        closedForBusiness = true;
+
+        while (true) {
+            List folders = null;
+
+            // grab our lock, copy the open folders reference, and null this out.  Once we see a null
+            // open folders ref, we're done closing.
+            synchronized(connectionPool) {
+                folders = openFolders;
+                openFolders = new LinkedList();
+            }
+
+            // null folder, we're done
+            if (folders.isEmpty()) {
+                return;
+            }
+            // now close each of the open folders.
+            for (int i = 0; i < folders.size(); i++) {
+                POP3Folder folder = (POP3Folder)folders.get(i);
+                try {
+                    folder.close(false);
+                } catch (MessagingException e) {
+                }
+            }
+        }
+    }
+    
+
+    /**
+     * @see javax.mail.Service#isConnected()
+     */
+    public boolean isConnected() {
+        // the connect() method of the super class checks here first.  If the connected flag 
+        // is off, then it's too early for use to try to get a connection and verify we still 
+        // have a live one.  
+        if (!super.isConnected()) {
+            return false; 
+        }
+        try {
+            POP3Connection connection = getConnection(); 
+            // a null connection likely means we had a failure establishing a 
+            // new connection to the POP3 server.  
+            if (connection == null) {
+                return false; 
+            }
+            try {
+                // make sure the server is really there 
+                connection.pingServer(); 
+                return true; 
+            }
+            finally {
+                // return the connection to the pool when finished 
+                if (connection != null) {
+                    releaseConnection(connection); 
+                }
+            }
+        } catch (MessagingException e) {
+        }
+        return false; 
+    }
+
+    /**
+     * Close the store, and any open folders associated with the 
+     * store. 
+     * 
+     * @exception MessagingException
+     */
+	public synchronized void close() throws MessagingException{
+        // if already closed, nothing to do. 
+        if (closedForBusiness) {
+            return; 
+        }
+        
+        // close the folders first, then shut down the Store. 
+        closeOpenFolders();
+        
+        connectionPool.close(); 
+        connectionPool = null; 
+
+		// make sure we do the superclass close operation first so 
+        // notification events get broadcast properly. 
+		super.close();
+	}
+
+    /**
+     * Check the status of our connection. 
+     * 
+     * @exception MessagingException
+     */
+    private void checkConnectionStatus() throws MessagingException {
+        if (!this.isConnected()) {
+            throw new MessagingException("Not connected ");
+        }
+    }
+
+    /**
+     * Internal debug output routine.
+     *
+     * @param value  The string value to output.
+     */
+    void debugOut(String message) {
+        debugStream.println("POP3Store DEBUG: " + message);
+    }
+
+    /**
+     * Internal debugging routine for reporting exceptions.
+     *
+     * @param message A message associated with the exception context.
+     * @param e       The received exception.
+     */
+    void debugOut(String message, Throwable e) {
+        debugOut("Received exception -> " + message);
+        debugOut("Exception message -> " + e.getMessage());
+        e.printStackTrace(debugStream);
+    }
+    
+    /**
+     * Finalizer to perform IMAPStore() cleanup when 
+     * no longer in use. 
+     * 
+     * @exception Throwable
+     */
+    protected void finalize() throws Throwable {
+        super.finalize(); 
+        close(); 
+    }
+}

Added: geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Connection.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Connection.java?rev=1882306&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Connection.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Connection.java Wed Oct  7 15:58:39 2020
@@ -0,0 +1,693 @@
+/**
+ * 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.store.pop3.connection;
+
+import java.io.*;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetHeaders;
+
+import org.apache.geronimo.javamail.authentication.AuthenticatorFactory;
+import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseStream;
+import org.apache.geronimo.javamail.store.pop3.POP3Constants;
+import org.apache.geronimo.javamail.util.CommandFailedException;
+import org.apache.geronimo.javamail.util.InvalidCommandException;
+import org.apache.geronimo.javamail.util.MIMEInputReader;
+import org.apache.geronimo.javamail.util.MailConnection;
+import org.apache.geronimo.javamail.util.ProtocolProperties;
+import org.apache.geronimo.mail.util.Base64;
+import org.apache.geronimo.mail.util.Hex;
+
+/**
+ * Simple implementation of POP3 transport.
+ *
+ * @version $Rev$ $Date$
+ */
+public class POP3Connection extends MailConnection implements POP3Constants {
+
+    static final protected String MAIL_APOP_ENABLED = "apop.enable";
+    static final protected String MAIL_AUTH_ENABLED = "auth.enable";
+    static final protected String MAIL_RESET_QUIT = "rsetbeforequit";
+    static final protected String MAIL_DISABLE_TOP = "disabletop";
+    //static final protected String MAIL_FORGET_TOP = "forgettopheaders"; //TODO forgettopheaders
+
+    // the initial greeting string, which might be required for APOP authentication.
+    protected String greeting;
+    // is use of the AUTH command enabled
+    protected boolean authEnabled;
+    // is use of APOP command enabled
+    protected boolean apopEnabled;
+    // input reader wrapped around the socket input stream
+    protected BufferedReader reader;
+    // output writer wrapped around the socket output stream.
+    protected PrintWriter writer;
+    // this connection was closed unexpectedly
+    protected boolean closed;
+    // indicates whether this connection is currently logged in.  Once
+    // we send a QUIT, we're finished.
+    protected boolean loggedIn;
+    // indicates whether we need to avoid using the TOP command
+    // when retrieving headers
+    protected boolean topDisabled = false;
+    // is TLS enabled on our part?
+    protected boolean useTLS = false;
+    // is TLS required on our part?
+    protected boolean requireTLS = false;
+
+    /**
+     * Normal constructor for an POP3Connection() object.
+     *
+     * @param props  The protocol properties abstraction containing our
+     *               property modifiers.
+     */
+    public POP3Connection(ProtocolProperties props) {
+        super(props);
+
+        // get our login properties flags
+        authEnabled = props.getBooleanProperty(MAIL_AUTH_ENABLED, false);
+        apopEnabled = props.getBooleanProperty(MAIL_APOP_ENABLED, false);
+        topDisabled = props.getBooleanProperty(MAIL_DISABLE_TOP, false);
+        // and also check for TLS enablement.
+        useTLS = props.getBooleanProperty(MAIL_STARTTLS_ENABLE, false);
+        // and also check if TLS is required.
+        requireTLS = props.getBooleanProperty(MAIL_STARTTLS_REQUIRED, false);
+       
+    }
+
+
+    /**
+     * Connect to the server and do the initial handshaking.
+     *
+     * @exception MessagingException
+     */
+    public boolean protocolConnect(String host, int port, String authid, String realm, String username, String password) throws MessagingException {
+        this.serverHost = host;
+        this.serverPort = port;
+        this.realm = realm;
+        this.authid = authid;
+        this.username = username;
+        this.password = password;
+
+        try {
+            // create socket and connect to server.
+            getConnection();
+            // consume the welcome line
+            getWelcome();
+            
+            // if we're not already using an SSL connection, and we have permission to issue STARTTLS or its even required
+            // try to setup a SSL connection
+            if (!sslConnection && (useTLS || requireTLS)) {
+                
+                    // tell the server of our intention to start a TLS session
+                    POP3Response starttlsResponse = null;
+                    try {
+                        starttlsResponse = sendCommand("STLS");
+                    } catch (CommandFailedException e) {
+                       
+                    }
+                    
+                    //if the server does not support TLS check if its required.
+                    //If true then throw an error, if not establish a non SSL connection
+                    if(requireTLS && (starttlsResponse == null || starttlsResponse.isError())) {
+                        throw new MessagingException("Server doesn't support required transport level security");
+                    } else if(starttlsResponse != null && starttlsResponse.getStatus() == POP3Response.OK) {
+                     // The connection is then handled by the superclass level.
+                        getConnectedTLSSocket();
+                    } else {
+                        if (debug) {
+                            debugOut("STARTTLS is enabled but not required and server does not support it. So we establish a connection without transport level security");
+                        }
+                    }
+            }
+            
+            getConnection();
+            
+            // go login with the server
+            if (login())
+            {
+                loggedIn = true;
+                return true;
+            }
+            return false;
+        } catch (IOException e) {
+            if (debug) {
+                debugOut("I/O exception establishing connection", e);
+            }
+            throw new MessagingException("Connection error", e);
+        }
+    }
+
+
+    /**
+     * Create a transport connection object and connect it to the
+     * target server.
+     *
+     * @exception MessagingException
+     */
+    protected void getConnection() throws MessagingException
+    {
+        try {
+            // do all of the non-protocol specific set up.  This will get our socket established
+            // and ready use.
+            super.getConnection();
+        } catch (IOException e) {
+            throw new MessagingException("Unable to obtain a connection to the POP3 server", e);
+        }
+
+        // The POP3 protocol is inherently a string-based protocol, so we get
+        // string readers/writers for the connection streams.  Note that we explicitly
+        // set the encoding to ensure that an inappropriate native encoding is not picked up.
+        try {
+            reader = new BufferedReader(new InputStreamReader(inputStream, "ISO8859-1"));
+            writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(outputStream), "ISO8859-1"));
+        } catch (UnsupportedEncodingException e) {
+        }
+    }
+
+    protected void getWelcome() throws IOException {
+        // just read the line and consume it.  If debug is
+        // enabled, there I/O stream will be traced
+        greeting = reader.readLine();
+    }
+
+    public String toString() {
+        return "POP3Connection host: " + serverHost + " port: " + serverPort;
+    }
+
+
+    /**
+     * 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
+            logout();
+        } 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();
+            // get rid of our response processor too.
+            reader = null;
+            writer = null;
+        }
+    }
+
+
+    /**
+     * Tag this connection as having been closed by the
+     * server.  This will not be returned to the
+     * connection pool.
+     */
+    public void setClosed() {
+        closed = true;
+    }
+
+    /**
+     * Test if the connection has been forcibly closed.
+     *
+     * @return True if the server disconnected the connection.
+     */
+    public boolean isClosed() {
+        return closed;
+    }
+
+    protected POP3Response sendCommand(String cmd) throws MessagingException {
+        return sendCommand(cmd, false);
+    }
+
+    protected POP3Response sendMultiLineCommand(String cmd) throws MessagingException {
+        return sendCommand(cmd, true);
+    }
+
+    protected synchronized POP3Response sendCommand(String cmd, boolean multiLine) throws MessagingException {
+        if (socket.isConnected()) {
+            {
+                // NOTE:  We don't use println() because it uses the platform concept of a newline rather
+                // than using CRLF, which is required by the POP3 protocol.
+                writer.write(cmd);
+                writer.write("\r\n");
+                writer.flush();
+
+                POP3Response response = buildResponse(multiLine);
+                if (response.isError()) {
+                    throw new CommandFailedException("Error issuing POP3 command: " + cmd);
+                }
+                return response;
+            }
+        }
+        throw new MessagingException("Connection to Mail Server is lost, connection " + this.toString());
+    }
+
+    /**
+     * Build a POP3Response item from the response stream.
+     *
+     * @param isMultiLineResponse
+     *               If true, this command is expecting multiple lines back from the server.
+     *
+     * @return A POP3Response item with all of the command response data.
+     * @exception MessagingException
+     */
+    protected POP3Response buildResponse(boolean isMultiLineResponse) throws MessagingException {
+        int status = ERR;
+        byte[] data = null;
+
+        String line;
+        //MIMEInputReader source = new MIMEInputReader(reader); //TODO unused
+
+        try {
+            line = reader.readLine();
+        } catch (IOException e) {
+            throw new MessagingException("Error in receving response");
+        }
+
+        if (line == null || line.trim().equals("")) {
+            throw new MessagingException("Empty Response");
+        }
+
+        if (line.startsWith("+OK")) {
+            status = OK;
+            line = removeStatusField(line);
+            if (isMultiLineResponse) {
+                data = getMultiLineResponse();
+            }
+        } else if (line.startsWith("-ERR")) {
+            status = ERR;
+            line = removeStatusField(line);
+        }else if (line.startsWith("+")) {
+        	status = CHALLENGE;
+        	line = removeStatusField(line);
+        	if (isMultiLineResponse) {
+        		data = getMultiLineResponse();
+        	}
+        } else {
+            throw new MessagingException("Unexpected response: " + line);
+        }
+        return new POP3Response(status, line, data);
+    }
+
+    private static String removeStatusField(String line) {
+        return line.substring(line.indexOf(SPACE) + 1);
+    }
+
+    /**
+     * This could be a multiline response
+     */
+    private byte[] getMultiLineResponse() throws MessagingException {
+
+        MIMEInputReader source = new MIMEInputReader(reader);
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+        // it's more efficient to do this a buffer at a time.
+        // the MIMEInputReader takes care of the byte-stuffing and
+        // ".\r\n" input terminator for us.
+        try {
+            OutputStreamWriter outWriter = new OutputStreamWriter(out, "ISO8859-1");
+            char buffer[] = new char[500];
+            try {
+                int charsRead = -1;
+                while ((charsRead = source.read(buffer)) >= 0) {
+                    outWriter.write(buffer, 0, charsRead);
+                }
+                outWriter.flush();
+            } catch (IOException e) {
+                throw new MessagingException("Error processing a multi-line response", e);
+            }
+        } catch (UnsupportedEncodingException e) {
+        }
+        return out.toByteArray();
+    }
+
+
+    /**
+     * Retrieve the raw message content from the POP3
+     * server.  This is all of the message data, including
+     * the header.
+     *
+     * @param sequenceNumber
+     *               The message sequence number.
+     *
+     * @return A byte array containing all of the message data.
+     * @exception MessagingException
+     */
+    public byte[] retrieveMessageData(int sequenceNumber) throws MessagingException {
+        POP3Response msgResponse = sendMultiLineCommand("RETR " + sequenceNumber);
+        // we want the data directly in this case.
+        return msgResponse.getData();
+    }
+
+    /**
+     * Retrieve the message header information for a given
+     * message, returned as an input stream suitable
+     * for loading the message data.
+     *
+     * @param sequenceNumber
+     *               The server sequence number for the message.
+     *
+     * @return An inputstream that can be used to read the message
+     *         data.
+     * @exception MessagingException
+     */
+    public ByteArrayInputStream retrieveMessageHeaders(int sequenceNumber) throws MessagingException {
+        POP3Response msgResponse;
+
+        // some POP3 servers don't correctly implement TOP, so this can be disabled.  If
+        // we can't use TOP, then use RETR and retrieve everything.  We can just hand back
+        // the stream, as the header loading routine will stop at the first
+        // null line.
+        if (topDisabled) {
+            msgResponse = sendMultiLineCommand("RETR " + sequenceNumber);
+        }
+        else {
+            msgResponse = sendMultiLineCommand("TOP " + sequenceNumber + " 0");
+        }
+
+        // just load the returned message data as a set of headers
+        return msgResponse.getContentStream();
+    }
+
+    /**
+     * Retrieve the total message size from the mail
+     * server.  This is the size of the headers plus
+     * the size of the message content.
+     *
+     * @param sequenceNumber
+     *               The message sequence number.
+     *
+     * @return The full size of the message.
+     * @exception MessagingException
+     */
+    public int retrieveMessageSize(int sequenceNumber) throws MessagingException {
+        POP3Response msgResponse = sendCommand("LIST " + sequenceNumber);
+        // Convert this into the parsed response type we need.
+        POP3ListResponse list = new POP3ListResponse(msgResponse);
+        // this returns the total message size
+        return list.getSize();
+    }
+
+    /**
+     * Retrieve the mail drop status information.
+     *
+     * @return An object representing the returned mail drop status.
+     * @exception MessagingException
+     */
+    public POP3StatusResponse retrieveMailboxStatus() throws MessagingException {
+        // issue the STAT command and return this into a status response
+        return new POP3StatusResponse(sendCommand("STAT"));
+    }
+
+
+    /**
+     * Retrieve the UID for an individual message.
+     *
+     * @param sequenceNumber
+     *               The target message sequence number.
+     *
+     * @return The string UID maintained by the server.
+     * @exception MessagingException
+     */
+    public String retrieveMessageUid(int sequenceNumber) throws MessagingException {
+        POP3Response msgResponse = sendCommand("UIDL " + sequenceNumber);
+
+        String message = msgResponse.getFirstLine();
+        // the UID is everything after the blank separating the message number and the UID.
+        // there's not supposed to be anything else on the message, but trim it of whitespace
+        // just to be on the safe side.
+        return message.substring(message.indexOf(' ') + 1).trim();
+    }
+
+
+    /**
+     * Delete a single message from the mail server.
+     *
+     * @param sequenceNumber
+     *               The sequence number of the message to delete.
+     *
+     * @exception MessagingException
+     */
+    public void deleteMessage(int sequenceNumber) throws MessagingException {
+        // just issue the command...we ignore the command response
+        sendCommand("DELE " + sequenceNumber);
+    }
+
+    /**
+     * Logout from the mail server.  This sends a QUIT
+     * command, which will likely sever the mail connection.
+     *
+     * @exception MessagingException
+     */
+    public void logout() throws MessagingException {
+        // we may have already sent the QUIT command
+        if (!loggedIn) {
+            return;
+        }
+        // just issue the command...we ignore the command response
+        sendCommand("QUIT");
+        loggedIn = false;
+    }
+
+    /**
+     * Perform a reset on the mail server.
+     *
+     * @exception MessagingException
+     */
+    public void reset() throws MessagingException {
+        // some mail servers mark retrieved messages for deletion
+        // automatically.  This will reset the read flags before
+        // we go through normal cleanup.
+        if (props.getBooleanProperty(MAIL_RESET_QUIT, false)) {
+            // just send an RSET command first
+            sendCommand("RSET");
+        }
+    }
+
+    /**
+     * Ping the mail server to see if we still have an active connection.
+     *
+     * @exception MessagingException thrown if we do not have an active connection.
+     */
+    public void pingServer() throws MessagingException {
+        // just issue the command...we ignore the command response
+        sendCommand("NOOP");
+    }
+
+    /**
+     * Login to the mail server, using whichever method is
+     * configured.  This will try multiple methods, if allowed,
+     * in decreasing levels of security.
+     *
+     * @return true if the login was successful.
+     * @exception MessagingException
+     */
+    public synchronized boolean login() throws MessagingException {
+        // permitted to use the AUTH command?
+        if (authEnabled) {
+            try {
+                // go do the SASL thing
+                return processSaslAuthentication();
+            } catch (MessagingException e) {
+                // Any error here means fall back to the next mechanism
+            }
+        }
+
+        if (apopEnabled) {
+            try {
+                // go do the SASL thing
+                return processAPOPAuthentication();
+            } catch (MessagingException e) {
+                // Any error here means fall back to the next mechanism
+            }
+        }
+
+        try {
+            // do the tried and true login processing.
+            return processLogin();
+        } catch (MessagingException e) {
+        }
+        // everything failed...can't get in
+        return false;
+    }
+
+
+    /**
+     * Process a basic LOGIN operation, using the
+     * plain test USER/PASS command combo.
+     *
+     * @return true if we logged successfully.
+     * @exception MessagingException
+     */
+    public boolean processLogin() throws MessagingException {
+        // start by sending the USER command, followed by
+        // the PASS command
+        sendCommand("USER " + username);
+        sendCommand("PASS " + password);
+        return true;       // we're in
+    }
+
+    /**
+     * Process logging in using the APOP command.  Only
+     * works on servers that give a timestamp value
+     * in the welcome response.
+     *
+     * @return true if the login was accepted.
+     * @exception MessagingException
+     */
+    public boolean processAPOPAuthentication() throws MessagingException {
+        int timeStart = greeting.indexOf('<');
+        // if we didn't get an APOP challenge on the greeting, throw an exception
+        // the main login processor will swallow that and fall back to the next
+        // mechanism
+        if (timeStart == -1) {
+            throw new MessagingException("POP3 Server does not support APOP");
+        }
+        int timeEnd = greeting.indexOf('>');
+        String timeStamp = greeting.substring(timeStart, timeEnd + 1);
+
+        // we create the digest password using the timestamp value sent to use
+        // concatenated with the password.
+        String digestPassword = timeStamp + password;
+
+        byte[] digest;
+
+        try {
+            // create a digest value from the password.
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            digest = md.digest(digestPassword.getBytes("iso-8859-1"));
+        } catch (NoSuchAlgorithmException e) {
+            // this shouldn't happen, but if it does, we'll just try a plain
+            // login.
+            throw new MessagingException("Unable to create MD5 digest", e);
+        } catch (UnsupportedEncodingException e) {
+            // this shouldn't happen, but if it does, we'll just try a plain
+            // login.
+            throw new MessagingException("Unable to create MD5 digest", e);
+        }
+        // this will throw an exception if it gives an error failure
+        sendCommand("APOP " + username + " " + new String(Hex.encode(digest)));
+        // no exception, we must have passed
+        return true;
+    }
+
+
+    /**
+     * Process SASL-type authentication.
+     *
+     * @return Returns true if the server support a SASL authentication mechanism and
+     *         accepted reponse challenges.
+     * @exception MessagingException
+     */
+    protected boolean processSaslAuthentication() throws MessagingException {
+        // if unable to get an appropriate authenticator, just fail it.
+        ClientAuthenticator authenticator = getSaslAuthenticator();
+        if (authenticator == null) {
+            throw new MessagingException("Unable to obtain SASL authenticator");
+        }
+
+        // go process the login.
+        return processLogin(authenticator);
+    }
+
+    /**
+     * 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);
+    }
+
+
+    /**
+     * Process a login using the provided authenticator object.
+     *
+     * NB:  This method is synchronized because we have a multi-step process going on
+     * here.  No other commands should be sent to the server until we complete.
+     *
+     * @return Returns true if the server support a SASL authentication mechanism and
+     * accepted reponse challenges.
+     * @exception MessagingException
+     */
+    protected synchronized boolean processLogin(ClientAuthenticator authenticator) throws MessagingException {
+        if (debug) {
+            debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
+        }
+
+        POP3Response response = sendCommand("AUTH " + authenticator.getMechanismName());
+
+        // now process the challenge sequence.  We get a continuation response back for each stage of the
+        // authentication, and finally an OK when everything passes muster.
+        while (true) {
+            // this should be a continuation reply, if things are still good.
+            if (response.isChallenge()) {
+                // we're passed back a challenge value, Base64 encoded.
+                byte[] challenge = response.decodeChallengeResponse();
+
+                try {
+                    String responseString = new String(Base64.encode(authenticator.evaluateChallenge(challenge)), "US-ASCII");
+
+                    // have the authenticator evaluate and send back the encoded response.
+                    response = sendCommand(responseString);
+                } catch (UnsupportedEncodingException ex) {
+                }
+            }
+            else {
+                // there are only two choices here, OK or a continuation.  OK means
+                // we've passed muster and are in.
+                return true;
+            }
+        }
+    }
+
+
+    /**
+     * Merge the configured SASL mechanisms with the capabilities that the
+     * server has indicated it supports, returning a merged list that can
+     * be used for selecting a mechanism.
+     *
+     * @return A List representing the intersection of the configured list and the
+     *         capabilities list.
+     */
+    protected List selectSaslMechanisms() {
+        // just return the set that have been explicity permitted
+        return getSaslMechanisms();
+    }
+}
+

Added: geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ConnectionPool.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ConnectionPool.java?rev=1882306&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ConnectionPool.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ConnectionPool.java Wed Oct  7 15:58:39 2020
@@ -0,0 +1,224 @@
+/**
+ * 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.store.pop3.connection;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.mail.MessagingException; 
+import javax.mail.Session;
+import javax.mail.Store;
+
+import javax.mail.StoreClosedException;
+
+import org.apache.geronimo.javamail.store.pop3.POP3Store; 
+import org.apache.geronimo.javamail.util.ProtocolProperties; 
+
+public class POP3ConnectionPool {
+
+    protected static final String MAIL_PORT = "port";
+    
+    protected static final String MAIL_SASL_REALM = "sasl.realm"; 
+    protected static final String MAIL_AUTHORIZATIONID = "sasl.authorizationid"; 
+
+    protected static final String DEFAULT_MAIL_HOST = "localhost";
+
+    // Our hosting Store instance
+    protected POP3Store store;
+    // our Protocol abstraction 
+    protected ProtocolProperties props; 
+    // POP3 is not nearly as multi-threaded as IMAP.  We really just have a single folder, 
+    // plus the Store, but the Store doesn't really talk to the server very much.  We only
+    // hold one connection available, and on the off chance there is a situation where 
+    // we need to create a new one, we'll authenticate on demand.  The one case where 
+    // I know this might be an issue is a folder checking back with the Store to see it if
+    // it is still connected.  
+    protected POP3Connection availableConnection;      
+    
+    // our debug flag
+    protected boolean debug;
+
+    // the target host
+    protected String host;
+    // the target server port.
+    protected int port;
+    // the username we connect with
+    protected String username;
+    // the authentication password.
+    protected String password;
+    // the SASL realm name 
+    protected String realm; 
+    // the authorization id.  
+    protected String authid; 
+    // Turned on when the store is closed for business. 
+    protected boolean closed = false; 
+
+    /**
+     * Create a connection pool associated with a give POP3Store instance.  The
+     * connection pool manages handing out connections for both the Store and
+     * Folder and Message usage.
+     * 
+     * @param store  The Store we're creating the pool for.
+     * @param props  The protocol properties abstraction we use.
+     */
+    public POP3ConnectionPool(POP3Store store, ProtocolProperties props) {
+        this.store = store;
+        this.props = props; 
+    }
+
+
+    /**
+     * Manage the initial connection to the POP3 server.  This is the first 
+     * point where we obtain the information needed to make an actual server 
+     * connection.  Like the Store protocolConnect method, we return false 
+     * if there's any sort of authentication difficulties. 
+     * 
+     * @param host     The host of the mail server.
+     * @param port     The mail server connection port.
+     * @param user     The connection user name.
+     * @param password The connection password.
+     * 
+     * @return True if we were able to connect and authenticate correctly. 
+     * @exception MessagingException
+     */
+    public synchronized boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
+        // NOTE:  We don't check for the username/password being null at this point.  It's possible that 
+        // the server will send back a PREAUTH response, which means we don't need to go through login 
+        // processing.  We'll need to check the capabilities response after we make the connection to decide 
+        // if logging in is necesssary. 
+        
+        // save this for subsequent connections.  All pool connections will use this info.
+        // if the port is defaulted, then see if we have something configured in the session.
+        // if not configured, we just use the default default.
+        if (port == -1) {
+            // check for a property and fall back on the default if it's not set.
+            port = props.getIntProperty(MAIL_PORT, props.getDefaultPort());
+            // it's possible that -1 might have been explicitly set, so one last check. 
+            if (port == -1) {
+                port = props.getDefaultPort(); 
+            }
+        }
+    	
+    	// Before we do anything, let's make sure that we succesfully received a host
+    	if ( host == null ) {
+    		host = DEFAULT_MAIL_HOST;
+    	}
+        
+        this.host = host;
+        this.port = port;
+        this.username = username;
+        this.password = password;
+        
+        // make sure we have the realm information 
+        realm = props.getProperty(MAIL_SASL_REALM); 
+        // get an authzid value, if we have one.  The default is to use the username.
+        authid = props.getProperty(MAIL_AUTHORIZATIONID, username);
+
+        // go create a connection and just add it to the pool.  If there is an authenticaton error, 
+        // return the connect failure, and we may end up trying again. 
+        availableConnection = createPoolConnection(); 
+        if (availableConnection == null) {
+            return false; 
+        }
+        // we're connected, authenticated, and ready to go. 
+        return true; 
+    }
+
+    /**
+     * Creates an authenticated pool connection and adds it to
+     * the connection pool.  If there is an existing connection
+     * already in the pool, this returns without creating a new
+     * connection.
+     *
+     * @exception MessagingException
+     */
+    protected POP3Connection createPoolConnection() throws MessagingException {
+        POP3Connection connection = new POP3Connection(props);
+        if (!connection.protocolConnect(host, port, authid, realm, username, password)) {
+            // we only add live connections to the pool.  Sever the connections and 
+            // allow it to go free. 
+            connection.closeServerConnection(); 
+            return null; 
+        }
+        // just return this connection 
+        return connection; 
+    }
+
+
+    /**
+     * Get a connection from the pool.  We try to retrieve a live
+     * connection, but we test the connection's liveness before
+     * returning one.  If we don't have a viable connection in
+     * the pool, we'll create a new one.  The returned connection
+     * will be in the authenticated state already.
+     *
+     * @return A POP3Connection object that is connected to the server.
+     */
+    public synchronized POP3Connection getConnection() throws MessagingException {
+        // if we have an available one (common when opening the INBOX), just return it 
+        POP3Connection connection = availableConnection; 
+        
+        if (connection != null) {
+            availableConnection = null; 
+            return connection; 
+        }
+        // we need an additional connection...rare, but it can happen if we've closed the INBOX folder. 
+        return createPoolConnection(); 
+    }
+    
+    
+    /**
+     * Return a connection to the connection pool.
+     * 
+     * @param connection The connection getting returned.
+     * 
+     * @exception MessagingException
+     */
+    public synchronized void releaseConnection(POP3Connection connection) throws MessagingException
+    {
+        // we're generally only called if the store needed to talk to the server and 
+        // then returned the connection to the pool.  So it's pretty likely that we'll just cache this
+        if (availableConnection == null) {
+            availableConnection = connection; 
+        }
+        else {
+            // got too many connections created...not sure how, but get rid of this one. 
+            connection.close(); 
+        }
+    }
+    
+    
+    /**
+     * Close the entire connection pool. 
+     * 
+     * @exception MessagingException
+     */
+    public synchronized void close() throws MessagingException {
+        // we'll on have the single connection in reserver 
+        if (availableConnection != null) {
+            availableConnection.close(); 
+            availableConnection = null; 
+        }
+        // turn out the lights, hang the closed sign on the wall. 
+        closed = true; 
+    }
+}
+

Added: geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ListResponse.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ListResponse.java?rev=1882306&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ListResponse.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ListResponse.java Wed Oct  7 15:58:39 2020
@@ -0,0 +1,99 @@
+/*
+ * 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.store.pop3.connection;
+
+import java.io.ByteArrayInputStream; 
+
+import java.util.ArrayList; 
+import java.util.List;     
+
+import javax.mail.MessagingException;
+
+/**
+ * This class adds functionality to the basic response by parsing the reply for
+ * LIST command and obtaining specific information about the msgnum and the
+ * size. It could be for one or more msgs depending on wether a msg number was
+ * passed or not into the LIST command
+ * 
+ * @see org.apache.geronimo.javamail.store.pop3.POP3Response
+ * @see org.apache.geronimo.javamail.store.pop3.response.DefaultPOP3Response
+ * 
+ * @version $Rev$ $Date$
+ */
+
+public class POP3ListResponse extends POP3Response {
+
+    private int msgnum = 0;
+
+    private int size = 0;
+
+    private List multipleMsgs = null;
+
+    POP3ListResponse(POP3Response baseRes) throws MessagingException {
+        super(baseRes.getStatus(), baseRes.getFirstLine(), baseRes.getData());
+
+        // if ERR not worth proceeding any further
+        if (OK == getStatus()) {
+
+            // if data == null, then it mean it's a single line response
+            if (baseRes.getData() == null) {
+                String[] args = getFirstLine().split(SPACE);
+                try {
+                    msgnum = Integer.parseInt(args[0]);
+                } catch (NumberFormatException e) {
+                    throw new MessagingException("Invalid response for LIST command", e);
+                }
+                try {
+                    size = Integer.parseInt(args[1]);
+                } catch (NumberFormatException e) {
+                    throw new MessagingException("Invalid response for LIST command", e);
+                }
+            } else {
+                int totalMsgs = 0;
+                String[] args = getFirstLine().split(SPACE);
+                try {
+                    totalMsgs = Integer.parseInt(args[0]);
+                } catch (NumberFormatException e) {
+                    throw new MessagingException("Invalid response for LIST command", e);
+                }
+                multipleMsgs = new ArrayList(totalMsgs);
+                // Todo : multi-line response parsing
+            }
+
+        }
+    }
+
+    public int getMessageNumber() {
+        return msgnum;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    /**
+     * Messages can be accessed by multipleMsgs.getElementAt(msgnum)
+     * 
+     */
+    public List getMultipleMessageDetails() {
+        return multipleMsgs;
+    }
+
+}

Added: geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Response.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Response.java?rev=1882306&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Response.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Response.java Wed Oct  7 15:58:39 2020
@@ -0,0 +1,85 @@
+/*
+ * 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.store.pop3.connection;
+
+import java.io.ByteArrayInputStream;
+
+import org.apache.geronimo.javamail.store.pop3.POP3Constants;
+
+import org.apache.geronimo.mail.util.Base64;
+
+/**
+ * This class provides the basic implementation for the POP3Response.
+ * 
+ * @see org.apache.geronimo.javamail.store.pop3.POP3Response
+ * @version $Rev$ $Date$
+ */
+
+public class POP3Response implements POP3Constants {
+
+    private int status = ERR;
+
+    private String firstLine;
+
+    private byte[] data;
+
+    POP3Response(int status, String firstLine, byte []data) {
+        this.status = status;
+        this.firstLine = firstLine;
+        this.data = data;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+    
+    public byte[] getData() {
+        return data; 
+    }
+
+    public ByteArrayInputStream getContentStream() {
+        return new ByteArrayInputStream(data);
+    }
+
+    public String getFirstLine() {
+        return firstLine;
+    }
+    
+    public boolean isError() {
+        return status == ERR; 
+    }
+    
+    public boolean isChallenge() {
+        return status == CHALLENGE; 
+    }
+    
+    /**
+     * Decode the message portion of a continuation challenge response.
+     * 
+     * @return The byte array containing the decoded data. 
+     */
+    public byte[] decodeChallengeResponse() 
+    {
+        // the challenge response is a base64 encoded string...
+        return Base64.decode(firstLine.trim()); 
+    }
+
+}
+

Added: geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3StatusResponse.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3StatusResponse.java?rev=1882306&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3StatusResponse.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3StatusResponse.java Wed Oct  7 15:58:39 2020
@@ -0,0 +1,67 @@
+/*
+ * 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.store.pop3.connection;
+
+import javax.mail.MessagingException;
+
+/**
+ * This class adds functionality to the basic response by parsing the status
+ * line and obtaining specific information about num of msgs and the size
+ * 
+ * @see org.apache.geronimo.javamail.store.pop3.POP3Response
+ * @see org.apache.geronimo.javamail.store.pop3.response.DefaultPOP3Response
+ * 
+ * @version $Rev$ $Date$
+ */
+
+public class POP3StatusResponse extends POP3Response {
+
+    private int numMessages = 0;
+
+    private int size = 0;
+
+    POP3StatusResponse(POP3Response baseRes) throws MessagingException {
+        super(baseRes.getStatus(), baseRes.getFirstLine(), baseRes.getData());
+
+        // if ERR not worth proceeding any further
+        if (OK == getStatus()) {
+            String[] args = getFirstLine().split(SPACE);
+            try {
+                numMessages = Integer.parseInt(args[0]);
+            } catch (NumberFormatException e) {
+                throw new MessagingException("Invalid response for STAT command", e);
+            }
+            try {
+                size = Integer.parseInt(args[1]);
+            } catch (NumberFormatException e) {
+                throw new MessagingException("Invalid response for STAT command", e);
+            }
+        }
+    }
+
+    public int getNumMessages() {
+        return numMessages;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+}

Added: geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java?rev=1882306&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java Wed Oct  7 15:58:39 2020
@@ -0,0 +1,728 @@
+/*
+ * 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.nntp;
+
+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.OutputStreamWriter;
+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.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+
+import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
+import org.apache.geronimo.javamail.authentication.AuthenticatorFactory;
+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.SessionUtil;
+
+/**
+ * Simple implementation of NNTP transport. Just does plain RFC977-ish delivery.
+ *
+ * @version $Rev$ $Date$
+ */
+public class NNTPConnection extends MailConnection {
+
+    /**
+     * constants for EOL termination
+     */
+    protected static final char CR = '\r';
+
+    protected static final char LF = '\n';
+
+    /**
+     * property keys for protocol properties.
+     */
+    protected static final int DEFAULT_NNTP_PORT = 119;
+    // does the server support posting?
+    protected boolean postingAllowed = true;
+
+    // different authentication mechanisms
+    protected boolean authInfoUserAllowed = false;
+    protected boolean authInfoSaslAllowed = false;
+
+    // the last response line received from the server.
+    protected NNTPReply lastServerResponse = null;
+
+    // the welcome string from the server.
+    protected String welcomeString = null;
+
+    // input reader wrapped around the socket input stream
+    protected BufferedReader reader;
+    // output writer wrapped around the socket output stream.
+    protected PrintWriter writer;
+
+    /**
+     * Normal constructor for an NNTPConnection() object.
+     *
+     * @param props  The property bundle for this protocol instance.
+     */
+    public NNTPConnection(ProtocolProperties props) {
+        super(props);
+    }
+
+
+    /**
+     * 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 {
+        super.protocolConnect(host, port, username, password);
+        // create socket and connect to server.
+        getConnection();
+
+        // receive welcoming message
+        getWelcome();
+
+        return true;
+    }
+
+
+    /**
+     * Create a transport connection object and connect it to the
+     * target server.
+     *
+     * @exception MessagingException
+     */
+    protected void getConnection() throws MessagingException
+    {
+        try {
+            // do all of the non-protocol specific set up.  This will get our socket established
+            // and ready use.
+            super.getConnection();
+        } catch (IOException e) {
+            throw new MessagingException("Unable to obtain a connection to the NNTP server", e);
+        }
+
+        // The NNTP protocol is inherently a string-based protocol, so we get
+        // string readers/writers for the connection streams.  Note that we explicitly
+        // set the encoding to ensure that an inappropriate native encoding is not picked up.
+        try {
+            reader = new BufferedReader(new InputStreamReader(inputStream, "ISO8859-1"));
+            writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(outputStream), "ISO8859-1"));
+        } catch (UnsupportedEncodingException e) {
+        }
+    }
+
+
+    /**
+     * 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();
+            // get rid of our response processor too.
+            reader = null;
+            writer = null;
+        }
+    }
+
+    public String toString() {
+        return "NNTPConnection host: " + serverHost + " port: " + serverPort;
+    }
+
+
+    /**
+     * Get the servers welcome blob from the wire....
+     */
+    public void getWelcome() throws MessagingException {
+        NNTPReply line = getReply();
+
+        //
+        if (line.isError()) {
+            throw new MessagingException("Error connecting to news server: " + line.getMessage());
+        }
+
+        // remember we can post.
+        if (line.getCode() == NNTPReply.POSTING_ALLOWED) {
+            postingAllowed = true;
+        } else {
+            postingAllowed = false;
+        }
+
+        // the NNTP store will want to use the welcome string, so save it.
+        welcomeString = line.getMessage();
+
+        // find out what extensions this server supports.
+        getExtensions();
+    }
+
+
+    /**
+     * Sends the QUIT message and receieves the response
+     */
+    public void sendQuit() throws MessagingException {
+        sendLine("QUIT");
+    }
+
+
+    /**
+     * Tell the server to switch to a named group.
+     *
+     * @param name
+     *            The name of the target group.
+     *
+     * @return The server response to the GROUP command.
+     */
+    public NNTPReply selectGroup(String name) throws MessagingException {
+        // send the GROUP command
+        return sendCommand("GROUP " + name);
+    }
+
+
+    /**
+     * Ask the server what extensions it supports.
+     *
+     * @return True if the command was accepted ok, false for any errors.
+     * @exception MessagingException
+     */
+    protected void getExtensions() throws MessagingException {
+        NNTPReply reply = sendCommand("LIST EXTENSIONS", NNTPReply.EXTENSIONS_SUPPORTED);
+
+        // we get a 202 code back. The first line is just a greeting, and
+        // extensions are delivered as data
+        // lines terminated with a "." line.
+        if (reply.getCode() != NNTPReply.EXTENSIONS_SUPPORTED) {
+            return;
+        }
+
+        // get a fresh extension mapping table.
+        capabilities = new HashMap();
+        authentications = new ArrayList();
+
+        // get the extension data lines.
+        List extensions = reply.getData();
+
+        // process all of the continuation lines
+        for (int i = 0; i < extensions.size(); i++) {
+            // go process the extention
+            processExtension((String) extensions.get(i));
+        }
+    }
+
+
+    /**
+     * Process an extension string passed back as the LIST EXTENSIONS response.
+     *
+     * @param extension
+     *            The string value of the extension (which will be of the form
+     *            "NAME arguments").
+     */
+    protected void processExtension(String 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);
+
+        // we need to determine which authentication mechanisms are supported here
+        if (extensionName.equals("AUTHINFO")) {
+            StringTokenizer tokenizer = new StringTokenizer(argument);
+
+            while (tokenizer.hasMoreTokens()) {
+                // we only know how to do USER or SASL
+                String mechanism = tokenizer.nextToken().toUpperCase();
+                if (mechanism.equals("SASL")) {
+                    authInfoSaslAllowed = true;
+                }
+                else if (mechanism.equals("USER")) {
+                    authInfoUserAllowed = true;
+                }
+            }
+        }
+        // special case for some older servers.
+        else if (extensionName.equals("SASL")) {
+            // The security mechanisms are blank delimited tokens.
+            StringTokenizer tokenizer = new StringTokenizer(argument);
+
+            while (tokenizer.hasMoreTokens()) {
+                String mechanism = tokenizer.nextToken().toUpperCase();
+                authentications.add(mechanism);
+            }
+        }
+    }
+
+
+    /**
+     * 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;
+    }
+
+
+    /**
+     * 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
+     */
+    public synchronized void sendPost(Message msg) throws MessagingException {
+
+        // send the POST command
+        NNTPReply line = sendCommand("POST");
+
+        if (line.getCode() != NNTPReply.SEND_ARTICLE) {
+            throw new MessagingException("Server rejected POST command: " + line);
+        }
+
+        // we've received permission to send the data, so ask the message to
+        // write itself out.
+        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("I/O error posting message", e);
+        } catch (MessagingException e) {
+            throw new MessagingException("Exception posting message", e);
+        }
+
+        // use a longer time out here to give the server time to process the
+        // data.
+        line = new NNTPReply(receiveLine());
+
+        if (line.getCode() != NNTPReply.POSTED_OK) {
+            throw new MessagingException("Server rejected POST command: " + line);
+        }
+    }
+
+    /**
+     * Issue a command and retrieve the response. If the given success indicator
+     * is received, the command is returning a longer response, terminated by a
+     * "crlf.crlf" sequence. These lines are attached to the reply.
+     *
+     * @param command
+     *            The command to issue.
+     * @param success
+     *            The command reply that indicates additional data should be
+     *            retrieved.
+     *
+     * @return The command reply.
+     */
+    public synchronized NNTPReply sendCommand(String command, int success) throws MessagingException {
+        NNTPReply reply = sendCommand(command);
+        if (reply.getCode() == success) {
+            reply.retrieveData(reader);
+        }
+        return reply;
+    }
+
+    /**
+     * 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
+     */
+    public NNTPReply sendCommand(String data) throws MessagingException {
+        sendLine(data);
+        NNTPReply reply = getReply();
+        // did the server just inform us we need to authenticate? The spec
+        // allows this
+        // response to be sent at any time, so we need to try to authenticate
+        // and then retry the command.
+        if (reply.getCode() == NNTPReply.AUTHINFO_REQUIRED || reply.getCode() == NNTPReply.AUTHINFO_SIMPLE_REQUIRED) {
+            debugOut("Authentication required received from server.");
+            // authenticate with the server, if necessary
+            processAuthentication(reply.getCode());
+            // if we've safely authenticated, we can reissue the command and
+            // process the response.
+            sendLine(data);
+            reply = getReply();
+        }
+        return reply;
+    }
+
+    /**
+     * 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
+     */
+    public NNTPReply sendAuthCommand(String data) throws MessagingException {
+        sendLine(data);
+        return getReply();
+    }
+
+    /**
+     * Sends a message down the socket and terminates with the appropriate CRLF
+     */
+    public void sendLine(String data) throws MessagingException {
+        if (socket == null || !socket.isConnected()) {
+            throw new MessagingException("no connection");
+        }
+        try {
+            outputStream.write(data.getBytes("ISO8859-1"));
+            outputStream.write(CR);
+            outputStream.write(LF);
+            outputStream.flush();
+        } catch (IOException e) {
+            throw new MessagingException(e.toString());
+        }
+    }
+
+    /**
+     * Get a reply line for an NNTP command.
+     *
+     * @return An NNTP reply object from the stream.
+     */
+    public NNTPReply getReply() throws MessagingException {
+        lastServerResponse = new NNTPReply(receiveLine());
+        return lastServerResponse;
+    }
+
+    /**
+     * Retrieve the last response received from the NNTP server.
+     *
+     * @return The raw response string (including the error code) returned from
+     *         the NNTP 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
+     */
+    public String receiveLine() throws MessagingException {
+        if (socket == null || !socket.isConnected()) {
+            throw new MessagingException("no connection");
+        }
+
+        try {
+            String line = reader.readLine();
+            if (line == null) {
+                throw new MessagingException("Unexpected end of stream");
+            }
+            return line;
+        } catch (IOException e) {
+            throw new MessagingException("Error reading from server", e);
+        }
+    }
+
+
+    /**
+     * Authenticate with the server, if necessary (or possible).
+     */
+    protected void processAuthentication(int request) throws MessagingException {
+        // we need to authenticate, but we don't have userid/password
+        // information...fail this
+        // immediately.
+        if (username == null || password == null) {
+            throw new MessagingException("Server requires user authentication");
+        }
+
+        if (request == NNTPReply.AUTHINFO_SIMPLE_REQUIRED) {
+            processAuthinfoSimple();
+        } else {
+            if (!processSaslAuthentication()) {
+                processAuthinfoUser();
+            }
+        }
+    }
+
+    /**
+     * Process an AUTHINFO SIMPLE command. Not widely used, but if the server
+     * asks for it, we can respond.
+     *
+     * @exception MessagingException
+     */
+    protected void processAuthinfoSimple() throws MessagingException {
+        NNTPReply reply = sendAuthCommand("AUTHINFO SIMPLE");
+        if (reply.getCode() != NNTPReply.AUTHINFO_CONTINUE) {
+            throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
+        }
+        reply = sendAuthCommand(username + " " + password);
+        if (reply.getCode() != NNTPReply.AUTHINFO_ACCEPTED) {
+            throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
+        }
+    }
+
+
+    /**
+     * Process SASL-type authentication.
+     *
+     * @return Returns true if the server support a SASL authentication mechanism and
+     *         accepted reponse challenges.
+     * @exception MessagingException
+     */
+    protected boolean processSaslAuthentication() throws MessagingException {
+        // only do this if permitted
+        if (!authInfoSaslAllowed) {
+            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");
+        }
+
+        // go process the login.
+        return processLogin(authenticator);
+    }
+
+    /**
+     * 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);
+    }
+
+
+    /**
+     * Process a login using the provided authenticator object.
+     *
+     * NB:  This method is synchronized because we have a multi-step process going on
+     * here.  No other commands should be sent to the server until we complete.
+     *
+     * @return Returns true if the server support a SASL authentication mechanism and
+     * accepted reponse challenges.
+     * @exception MessagingException
+     */
+    protected synchronized boolean processLogin(ClientAuthenticator authenticator) throws MessagingException {
+        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("AUTHINFO SASL ");
+            // and tell the server which mechanism we're using.
+            command.append(authenticator.getMechanismName());
+            command.append(" ");
+            // and append the response data
+            try {
+                command.append(new String(Base64.encode(authenticator.evaluateChallenge(null)), "US-ASCII"));
+            } catch (UnsupportedEncodingException e) {
+            }
+            // 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("AUTHINFO SASL");
+            // 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.
+            NNTPReply line = getReply();
+
+            // if we get a completion return, we've passed muster, so give an
+            // authentication response.
+            if (line.getCode() == NNTPReply.AUTHINFO_ACCEPTED || line.getCode() == NNTPReply.AUTHINFO_ACCEPTED_FINAL) {
+                debugOut("Successful SMTP authentication");
+                return true;
+            }
+            // we have an additional challenge to process.
+            else if (line.getCode() == NNTPReply.AUTHINFO_CHALLENGE) {
+                // Does the authenticator think it is finished? We can't answer
+                // an additional challenge,
+                // so fail this.
+                if (authenticator.isComplete()) {
+                    debugOut("Extra authentication challenge " + line);
+                    return false;
+                }
+
+                // we're passed back a challenge value, Base64 encoded.
+                try {
+                    byte[] challenge = Base64.decode(line.getMessage().getBytes("ISO8859-1"));
+
+                    // have the authenticator evaluate and send back the encoded
+                    // response.
+                    sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge)), "US-ASCII"));
+                } catch (UnsupportedEncodingException e) {
+                }
+            }
+            // completion or challenge are the only responses we know how to
+            // handle. Anything else must
+            // be a failure.
+            else {
+                debugOut("Authentication failure " + line);
+                return false;
+            }
+        }
+    }
+
+
+    /**
+     * Process an AUTHINFO USER command. Most common form of NNTP
+     * authentication.
+     *
+     * @exception MessagingException
+     */
+    protected void processAuthinfoUser() throws MessagingException {
+        // only do this if allowed by the server
+        if (!authInfoUserAllowed) {
+            return;
+        }
+        NNTPReply reply = sendAuthCommand("AUTHINFO USER " + username);
+        // accepted without a password (uncommon, but allowed), we're done
+        if (reply.getCode() == NNTPReply.AUTHINFO_ACCEPTED) {
+            return;
+        }
+        // the only other non-error response is continue.
+        if (reply.getCode() != NNTPReply.AUTHINFO_CONTINUE) {
+            throw new MessagingException("Error authenticating with server using AUTHINFO USER: " + reply);
+        }
+        // now send the password. We expect an accepted response.
+        reply = sendAuthCommand("AUTHINFO PASS " + password);
+        if (reply.getCode() != NNTPReply.AUTHINFO_ACCEPTED) {
+            throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
+        }
+    }
+
+
+    /**
+     * Indicate whether posting is allowed for a given server.
+     *
+     * @return True if the server allows posting, false if the server is
+     *         read-only.
+     */
+    public boolean isPostingAllowed() {
+        return postingAllowed;
+    }
+
+    /**
+     * Retrieve the welcome string sent back from the server.
+     *
+     * @return The server provided welcome string.
+     */
+    public String getWelcomeString() {
+        return welcomeString;
+    }
+}