FileDocCategorySizeDatePackage
POP3Handler.javaAPI DocApache James 2.3.145681Fri Jan 12 12:56:24 GMT 2007org.apache.james.pop3server

POP3Handler.java

/****************************************************************
 * 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.james.pop3server;

import org.apache.avalon.cornerstone.services.connection.ConnectionHandler;
import org.apache.avalon.excalibur.pool.Poolable;
import org.apache.avalon.framework.container.ContainerUtil;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.commons.collections.ListUtils;
import org.apache.james.Constants;
import org.apache.james.core.MailImpl;
import org.apache.james.services.MailRepository;
import org.apache.james.util.CRLFTerminatedReader;
import org.apache.james.util.ExtraDotOutputStream;
import org.apache.james.util.InternetPrintWriter;
import org.apache.james.util.watchdog.BytesWrittenResetOutputStream;
import org.apache.james.util.watchdog.Watchdog;
import org.apache.james.util.watchdog.WatchdogTarget;
import org.apache.mailet.Mail;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;

/**
 * The handler class for POP3 connections.
 *
 */
public class POP3Handler
    extends AbstractLogEnabled
    implements ConnectionHandler, Poolable {

    // POP3 Server identification string used in POP3 headers
    private static final String softwaretype        = "JAMES POP3 Server "
                                                        + Constants.SOFTWARE_VERSION;

    // POP3 response prefixes
    private final static String OK_RESPONSE = "+OK";    // OK response.  Requested content
                                                        // will follow

    private final static String ERR_RESPONSE = "-ERR";  // Error response.  Requested content
                                                        // will not be provided.  This prefix
                                                        // is followed by a more detailed
                                                        // error message

    // Authentication states for the POP3 interaction

    private final static int AUTHENTICATION_READY = 0;    // Waiting for user id

    private final static int AUTHENTICATION_USERSET = 1;  // User id provided, waiting for
                                                          // password

    private final static int TRANSACTION = 2;             // A valid user id/password combination
                                                          // has been provided.  In this state
                                                          // the client can access the mailbox
                                                          // of the specified user

    private static final Mail DELETED = new MailImpl();   // A placeholder for emails deleted
                                                          // during the course of the POP3
                                                          // transaction.  This Mail instance
                                                          // is used to enable fast checks as
                                                          // to whether an email has been
                                                          // deleted from the inbox.

    /**
     * The per-service configuration data that applies to all handlers
     */
    private POP3HandlerConfigurationData theConfigData;

    /**
     * The mail server's copy of the user's inbox
     */
    private MailRepository userInbox;

    /**
     * The thread executing this handler
     */
    private Thread handlerThread;

    /**
     * The TCP/IP socket over which the POP3 interaction
     * is occurring
     */
    private Socket socket;

    /**
     * The reader associated with incoming characters.
     */
    private CRLFTerminatedReader in;

    /**
     * The writer to which outgoing messages are written.
     */
    private PrintWriter out;

    /**
     * The socket's output stream
     */
    private OutputStream outs;

    /**
     * The current transaction state of the handler
     */
    private int state;

    /**
     * The user id associated with the POP3 dialogue
     */
    private String user;

    /**
     * A dynamic list representing the set of
     * emails in the user's inbox at any given time
     * during the POP3 transaction.
     */
    private ArrayList userMailbox = new ArrayList();

    private ArrayList backupUserMailbox;         // A snapshot list representing the set of
                                                 // emails in the user's inbox at the beginning
                                                 // of the transaction

    /**
     * The watchdog being used by this handler to deal with idle timeouts.
     */
    private Watchdog theWatchdog;

    /**
     * The watchdog target that idles out this handler.
     */
    private WatchdogTarget theWatchdogTarget = new POP3WatchdogTarget();

    /**
     * Set the configuration data for the handler.
     *
     * @param theData the configuration data
     */
    void setConfigurationData(POP3HandlerConfigurationData theData) {
        theConfigData = theData;
    }

    /**
     * Set the Watchdog for use by this handler.
     *
     * @param theWatchdog the watchdog
     */
    void setWatchdog(Watchdog theWatchdog) {
        this.theWatchdog = theWatchdog;
    }

    /**
     * Gets the Watchdog Target that should be used by Watchdogs managing
     * this connection.
     *
     * @return the WatchdogTarget
     */
    WatchdogTarget getWatchdogTarget() {
        return theWatchdogTarget;
    }

    /**
     * Idle out this connection
     */
    void idleClose() {
        if (getLogger() != null) {
            getLogger().error("POP3 Connection has idled out.");
        }
        try {
            if (socket != null) {
                socket.close();
            }
        } catch (Exception e) {
            // ignored
        } finally {
            socket = null;
        }

        synchronized (this) {
            // Interrupt the thread to recover from internal hangs
            if (handlerThread != null) {
                handlerThread.interrupt();
                handlerThread = null;
            }
        }

    }

    /**
     * @see org.apache.avalon.cornerstone.services.connection.ConnectionHandler#handleConnection(Socket)
     */
    public void handleConnection( Socket connection )
            throws IOException {

        String remoteHost = "";
        String remoteIP = "";

        try {
            this.socket = connection;
            synchronized (this) {
                handlerThread = Thread.currentThread();
            }
            // in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "ASCII"), 512);
            in = new CRLFTerminatedReader(new BufferedInputStream(socket.getInputStream(), 512), "ASCII");
            remoteIP = socket.getInetAddress().getHostAddress ();
            remoteHost = socket.getInetAddress().getHostName ();
        } catch (Exception e) {
            if (getLogger().isErrorEnabled()) {
                StringBuffer exceptionBuffer =
                    new StringBuffer(256)
                            .append("Cannot open connection from ")
                            .append(remoteHost)
                            .append(" (")
                            .append(remoteIP)
                            .append("): ")
                            .append(e.getMessage());
                getLogger().error( exceptionBuffer.toString(), e );
            }
        }

        if (getLogger().isInfoEnabled()) {
            StringBuffer logBuffer =
                new StringBuffer(128)
                        .append("Connection from ")
                        .append(remoteHost)
                        .append(" (")
                        .append(remoteIP)
                        .append(") ");
            getLogger().info(logBuffer.toString());
        }

        try {
            outs = new BufferedOutputStream(socket.getOutputStream(), 1024);
            out = new InternetPrintWriter(outs, true);
            state = AUTHENTICATION_READY;
            user = "unknown";
            StringBuffer responseBuffer =
                new StringBuffer(256)
                        .append(OK_RESPONSE)
                        .append(" ")
                        .append(theConfigData.getHelloName())
                        .append(" POP3 server (")
                        .append(POP3Handler.softwaretype)
                        .append(") ready ");
            out.println(responseBuffer.toString());

            theWatchdog.start();
            while (parseCommand(readCommandLine())) {
                theWatchdog.reset();
            }
            theWatchdog.stop();
            if (getLogger().isInfoEnabled()) {
                StringBuffer logBuffer =
                    new StringBuffer(128)
                        .append("Connection for ")
                        .append(user)
                        .append(" from ")
                        .append(remoteHost)
                        .append(" (")
                        .append(remoteIP)
                        .append(") closed.");
                getLogger().info(logBuffer.toString());
            }
        } catch (Exception e) {
            out.println(ERR_RESPONSE + " Error closing connection.");
            out.flush();
            StringBuffer exceptionBuffer =
                new StringBuffer(128)
                        .append("Exception during connection from ")
                        .append(remoteHost)
                        .append(" (")
                        .append(remoteIP)
                        .append(") : ")
                        .append(e.getMessage());
            getLogger().error(exceptionBuffer.toString(), e );
        } finally {
            resetHandler();
        }
    }

    /**
     * Resets the handler data to a basic state.
     */
    private void resetHandler() {

        if (theWatchdog != null) {
            ContainerUtil.dispose(theWatchdog);
            theWatchdog = null;
        }

        // Close and clear streams, sockets

        try {
            if (socket != null) {
                socket.close();
                socket = null;
            }
        } catch (IOException ioe) {
            // Ignoring exception on close
        } finally {
            socket = null;
        }

        try {
            if (in != null) {
                in.close();
            }
        } catch (Exception e) {
            // Ignored
        } finally {
            in = null;
        }

        try {
            if (out != null) {
                out.close();
            }
        } catch (Exception e) {
            // Ignored
        } finally {
            out = null;
        }

        try {
           if (outs != null) {
               outs.close();
            }
        } catch (Exception e) {
            // Ignored
        } finally {
            outs = null;
        }

        synchronized (this) {
            handlerThread = null;
        }

        // Clear user data
        user = null;
        userInbox = null;
        if (userMailbox != null) {
            userMailbox.clear();
            userMailbox = null;
        }

        if (backupUserMailbox != null) {
            backupUserMailbox.clear();
            backupUserMailbox = null;
        }

        // Clear config data
        theConfigData = null;
    }

    /**
     * Implements a "stat".  If the handler is currently in
     * a transaction state, this amounts to a rollback of the
     * mailbox contents to the beginning of the transaction.
     * This method is also called when first entering the
     * transaction state to initialize the handler copies of the
     * user inbox.
     *
     */
    private void stat() {
        userMailbox = new ArrayList();
        userMailbox.add(DELETED);
        try {
            for (Iterator it = userInbox.list(); it.hasNext(); ) {
                String key = (String) it.next();
                Mail mc = userInbox.retrieve(key);
                // Retrieve can return null if the mail is no longer in the store.
                // In this case we simply continue to the next key
                if (mc == null) {
                    continue;
                }
                userMailbox.add(mc);
            }
        } catch(MessagingException e) {
            // In the event of an exception being thrown there may or may not be anything in userMailbox
            getLogger().error("Unable to STAT mail box ", e);
        }
        finally {
            backupUserMailbox = (ArrayList) userMailbox.clone();
        }
    }

    /**
     * Reads a line of characters off the command line.
     *
     * @return the trimmed input line
     * @throws IOException if an exception is generated reading in the input characters
     */
    final String readCommandLine() throws IOException {
        for (;;) try {
            String commandLine = in.readLine();
            if (commandLine != null) {
                commandLine = commandLine.trim();
            }
            return commandLine;
        } catch (CRLFTerminatedReader.TerminationException te) {
            writeLoggedFlushedResponse("-ERR Syntax error at character position " + te.position() + ". CR and LF must be CRLF paired.  See RFC 1939 #3.");
        }
    }

    /**
     * This method parses POP3 commands read off the wire in handleConnection.
     * Actual processing of the command (possibly including additional back and
     * forth communication with the client) is delegated to one of a number of
     * command specific handler methods.  The primary purpose of this method is
     * to parse the raw command string to determine exactly which handler should
     * be called.  It returns true if expecting additional commands, false otherwise.
     *
     * @param rawCommand the raw command string passed in over the socket
     *
     * @return whether additional commands are expected.
     */
    private boolean parseCommand(String rawCommand) {
        if (rawCommand == null) {
            return false;
        }
        boolean returnValue = true;
        String command = rawCommand;
        StringTokenizer commandLine = new StringTokenizer(command, " ");
        int arguments = commandLine.countTokens();
        if (arguments == 0) {
            return true;
        } else if(arguments > 0) {
            command = commandLine.nextToken().toUpperCase(Locale.US);
        }
        if (getLogger().isDebugEnabled()) {
            // Don't display password in logger
            if (!command.equals("PASS")) {
                getLogger().debug("Command received: " + rawCommand);
            } else {
                getLogger().debug("Command received: PASS <password omitted>");
            }
        }
        String argument = null;
        if(arguments > 1) {
            argument = commandLine.nextToken();
        }
        String argument1 = null;
        if(arguments > 2) {
            argument1 = commandLine.nextToken();
        }

        if (command.equals("USER")) {
            doUSER(command,argument,argument1);
        } else if (command.equals("PASS")) {
            doPASS(command,argument,argument1);
        } else if (command.equals("STAT")) {
            doSTAT(command,argument,argument1);
        } else if (command.equals("LIST")) {
            doLIST(command,argument,argument1);
        } else if (command.equals("UIDL")) {
            doUIDL(command,argument,argument1);
        } else if (command.equals("RSET")) {
            doRSET(command,argument,argument1);
        } else if (command.equals("DELE")) {
            doDELE(command,argument,argument1);
        } else if (command.equals("NOOP")) {
            doNOOP(command,argument,argument1);
        } else if (command.equals("RETR")) {
            doRETR(command,argument,argument1);
        } else if (command.equals("TOP")) {
            doTOP(command,argument,argument1);
        } else if (command.equals("QUIT")) {
            returnValue = false;
            doQUIT(command,argument,argument1);
        } else {
            doUnknownCmd(command,argument,argument1);
        }
        return returnValue;
    }

    /**
     * Handler method called upon receipt of a USER command.
     * Reads in the user id.
     *
     * @param command the command parsed by the parseCommand method
     * @param argument the first argument parsed by the parseCommand method
     * @param argument1 the second argument parsed by the parseCommand method
     */
    private void doUSER(String command,String argument,String argument1) {
        String responseString = null;
        if (state == AUTHENTICATION_READY && argument != null) {
            user = argument;
            state = AUTHENTICATION_USERSET;
            responseString = OK_RESPONSE;
        } else {
            responseString = ERR_RESPONSE;
        }
        writeLoggedFlushedResponse(responseString);
    }

    /**
     * Handler method called upon receipt of a PASS command.
     * Reads in and validates the password.
     *
     * @param command the command parsed by the parseCommand method
     * @param argument the first argument parsed by the parseCommand method
     * @param argument1 the second argument parsed by the parseCommand method
     */
    private void doPASS(String command,String argument,String argument1) {
        String responseString = null;
        if (state == AUTHENTICATION_USERSET && argument != null) {
            String passArg = argument;
            if (theConfigData.getUsersRepository().test(user, passArg)) {
                StringBuffer responseBuffer =
                    new StringBuffer(64)
                            .append(OK_RESPONSE)
                            .append(" Welcome ")
                            .append(user);
                responseString = responseBuffer.toString();
                state = TRANSACTION;
                writeLoggedFlushedResponse(responseString);
                userInbox = theConfigData.getMailServer().getUserInbox(user);
                stat();
            } else {
                responseString = ERR_RESPONSE + " Authentication failed.";
                state = AUTHENTICATION_READY;
                writeLoggedFlushedResponse(responseString);
            }
        } else {
            responseString = ERR_RESPONSE;
            writeLoggedFlushedResponse(responseString);
        }
    }

    /**
     * Handler method called upon receipt of a STAT command.
     * Returns the number of messages in the mailbox and its
     * aggregate size.
     *
     * @param command the command parsed by the parseCommand method
     * @param argument the first argument parsed by the parseCommand method
     * @param argument1 the second argument parsed by the parseCommand method
     */
    private void doSTAT(String command,String argument,String argument1) {
        String responseString = null;
        if (state == TRANSACTION) {
            long size = 0;
            int count = 0;
            try {
                for (Iterator i = userMailbox.iterator(); i.hasNext(); ) {
                    Mail mc = (Mail) i.next();
                    if (mc != DELETED) {
                        size += mc.getMessageSize();
                        count++;
                    }
                }
                StringBuffer responseBuffer =
                    new StringBuffer(32)
                            .append(OK_RESPONSE)
                            .append(" ")
                            .append(count)
                            .append(" ")
                            .append(size);
                responseString = responseBuffer.toString();
                writeLoggedFlushedResponse(responseString);
            } catch (MessagingException me) {
                responseString = ERR_RESPONSE;
                writeLoggedFlushedResponse(responseString);
            }
        } else {
            responseString = ERR_RESPONSE;
            writeLoggedFlushedResponse(responseString);
        }
    }

    /**
     * Handler method called upon receipt of a LIST command.
     * Returns the number of messages in the mailbox and its
     * aggregate size, or optionally, the number and size of
     * a single message.
     *
     * @param command the command parsed by the parseCommand method
     * @param argument the first argument parsed by the parseCommand method
     * @param argument1 the second argument parsed by the parseCommand method
     */
    private void doLIST(String command,String argument,String argument1) {
        String responseString = null;
        if (state == TRANSACTION) {
            if (argument == null) {
                long size = 0;
                int count = 0;
                try {
                    for (Iterator i = userMailbox.iterator(); i.hasNext(); ) {
                        Mail mc = (Mail) i.next();
                        if (mc != DELETED) {
                            size += mc.getMessageSize();
                            count++;
                        }
                    }
                    StringBuffer responseBuffer =
                        new StringBuffer(32)
                                .append(OK_RESPONSE)
                                .append(" ")
                                .append(count)
                                .append(" ")
                                .append(size);
                    responseString = responseBuffer.toString();
                    writeLoggedFlushedResponse(responseString);
                    count = 0;
                    for (Iterator i = userMailbox.iterator(); i.hasNext(); count++) {
                        Mail mc = (Mail) i.next();

                        if (mc != DELETED) {
                            responseBuffer =
                                new StringBuffer(16)
                                        .append(count)
                                        .append(" ")
                                        .append(mc.getMessageSize());
                            out.println(responseBuffer.toString());
                        }
                    }
                    out.println(".");
                    out.flush();
                } catch (MessagingException me) {
                    responseString = ERR_RESPONSE;
                    writeLoggedFlushedResponse(responseString);
                }
            } else {
                int num = 0;
                try {
                    num = Integer.parseInt(argument);
                    Mail mc = (Mail) userMailbox.get(num);
                    if (mc != DELETED) {
                        StringBuffer responseBuffer =
                            new StringBuffer(64)
                                    .append(OK_RESPONSE)
                                    .append(" ")
                                    .append(num)
                                    .append(" ")
                                    .append(mc.getMessageSize());
                        responseString = responseBuffer.toString();
                        writeLoggedFlushedResponse(responseString);
                    } else {
                        StringBuffer responseBuffer =
                            new StringBuffer(64)
                                    .append(ERR_RESPONSE)
                                    .append(" Message (")
                                    .append(num)
                                    .append(") already deleted.");
                        responseString = responseBuffer.toString();
                        writeLoggedFlushedResponse(responseString);
                    }
                } catch (IndexOutOfBoundsException npe) {
                    StringBuffer responseBuffer =
                        new StringBuffer(64)
                                .append(ERR_RESPONSE)
                                .append(" Message (")
                                .append(num)
                                .append(") does not exist.");
                    responseString = responseBuffer.toString();
                    writeLoggedFlushedResponse(responseString);
                } catch (NumberFormatException nfe) {
                    StringBuffer responseBuffer =
                        new StringBuffer(64)
                                .append(ERR_RESPONSE)
                                .append(" ")
                                .append(argument)
                                .append(" is not a valid number");
                    responseString = responseBuffer.toString();
                    writeLoggedFlushedResponse(responseString);
                } catch (MessagingException me) {
                    responseString = ERR_RESPONSE;
                    writeLoggedFlushedResponse(responseString);
               }
            }
        } else {
            responseString = ERR_RESPONSE;
            writeLoggedFlushedResponse(responseString);
        }
    }

    /**
     * Handler method called upon receipt of a UIDL command.
     * Returns a listing of message ids to the client.
     *
     * @param command the command parsed by the parseCommand method
     * @param argument the first argument parsed by the parseCommand method
     * @param argument1 the second argument parsed by the parseCommand method
     */
    private void doUIDL(String command,String argument,String argument1) {
        String responseString = null;
        if (state == TRANSACTION) {
            if (argument == null) {
                responseString = OK_RESPONSE + " unique-id listing follows";
                writeLoggedFlushedResponse(responseString);
                int count = 0;
                for (Iterator i = userMailbox.iterator(); i.hasNext(); count++) {
                    Mail mc = (Mail) i.next();
                    if (mc != DELETED) {
                        StringBuffer responseBuffer =
                            new StringBuffer(64)
                                    .append(count)
                                    .append(" ")
                                    .append(mc.getName());
                        out.println(responseBuffer.toString());
                    }
                }
                out.println(".");
                out.flush();
            } else {
                int num = 0;
                try {
                    num = Integer.parseInt(argument);
                    Mail mc = (Mail) userMailbox.get(num);
                    if (mc != DELETED) {
                        StringBuffer responseBuffer =
                            new StringBuffer(64)
                                    .append(OK_RESPONSE)
                                    .append(" ")
                                    .append(num)
                                    .append(" ")
                                    .append(mc.getName());
                        responseString = responseBuffer.toString();
                        writeLoggedFlushedResponse(responseString);
                    } else {
                        StringBuffer responseBuffer =
                            new StringBuffer(64)
                                    .append(ERR_RESPONSE)
                                    .append(" Message (")
                                    .append(num)
                                    .append(") already deleted.");
                        responseString = responseBuffer.toString();
                        writeLoggedFlushedResponse(responseString);
                    }
                } catch (IndexOutOfBoundsException npe) {
                    StringBuffer responseBuffer =
                        new StringBuffer(64)
                                .append(ERR_RESPONSE)
                                .append(" Message (")
                                .append(num)
                                .append(") does not exist.");
                    responseString = responseBuffer.toString();
                    writeLoggedFlushedResponse(responseString);
                } catch (NumberFormatException nfe) {
                    StringBuffer responseBuffer =
                        new StringBuffer(64)
                                .append(ERR_RESPONSE)
                                .append(" ")
                                .append(argument)
                                .append(" is not a valid number");
                    responseString = responseBuffer.toString();
                    writeLoggedFlushedResponse(responseString);
                }
            }
        } else {
            writeLoggedFlushedResponse(ERR_RESPONSE);
        }
    }

    /**
     * Handler method called upon receipt of a RSET command.
     * Calls stat() to reset the mailbox.
     *
     * @param command the command parsed by the parseCommand method
     * @param argument the first argument parsed by the parseCommand method
     * @param argument1 the second argument parsed by the parseCommand method
     */
    private void doRSET(String command,String argument,String argument1) {
        String responseString = null;
        if (state == TRANSACTION) {
            stat();
            responseString = OK_RESPONSE;
        } else {
            responseString = ERR_RESPONSE;
        }
        writeLoggedFlushedResponse(responseString);
    }

    /**
     * Handler method called upon receipt of a DELE command.
     * This command deletes a particular mail message from the
     * mailbox.
     *
     * @param command the command parsed by the parseCommand method
     * @param argument the first argument parsed by the parseCommand method
     * @param argument1 the second argument parsed by the parseCommand method
     */
    private void doDELE(String command,String argument,String argument1) {
        String responseString = null;
        if (state == TRANSACTION) {
            int num = 0;
            try {
                num = Integer.parseInt(argument);
            } catch (Exception e) {
                responseString = ERR_RESPONSE + " Usage: DELE [mail number]";
                writeLoggedFlushedResponse(responseString);
                return;
            }
            try {
                Mail mc = (Mail) userMailbox.get(num);
                if (mc == DELETED) {
                    StringBuffer responseBuffer =
                        new StringBuffer(64)
                                .append(ERR_RESPONSE)
                                .append(" Message (")
                                .append(num)
                                .append(") already deleted.");
                    responseString = responseBuffer.toString();
                    writeLoggedFlushedResponse(responseString);
                } else {
                    userMailbox.set(num, DELETED);
                    writeLoggedFlushedResponse(OK_RESPONSE + " Message deleted");
                }
            } catch (IndexOutOfBoundsException iob) {
                StringBuffer responseBuffer =
                    new StringBuffer(64)
                            .append(ERR_RESPONSE)
                            .append(" Message (")
                            .append(num)
                            .append(") does not exist.");
                responseString = responseBuffer.toString();
                writeLoggedFlushedResponse(responseString);
            }
        } else {
            responseString = ERR_RESPONSE;
            writeLoggedFlushedResponse(responseString);
        }
    }

    /**
     * Handler method called upon receipt of a NOOP command.
     * Like all good NOOPs, does nothing much.
     *
     * @param command the command parsed by the parseCommand method
     * @param argument the first argument parsed by the parseCommand method
     * @param argument1 the second argument parsed by the parseCommand method
     */
    private void doNOOP(String command,String argument,String argument1) {
        String responseString = null;
        if (state == TRANSACTION) {
            responseString = OK_RESPONSE;
            writeLoggedFlushedResponse(responseString);
        } else {
            responseString = ERR_RESPONSE;
            writeLoggedFlushedResponse(responseString);
        }
    }

    /**
     * Handler method called upon receipt of a RETR command.
     * This command retrieves a particular mail message from the
     * mailbox.
     *
     * @param command the command parsed by the parseCommand method
     * @param argument the first argument parsed by the parseCommand method
     * @param argument1 the second argument parsed by the parseCommand method
     */
    private void doRETR(String command,String argument,String argument1) {
        String responseString = null;
        if (state == TRANSACTION) {
            int num = 0;
            try {
                num = Integer.parseInt(argument.trim());
            } catch (Exception e) {
                responseString = ERR_RESPONSE + " Usage: RETR [mail number]";
                writeLoggedFlushedResponse(responseString);
                return;
            }
            try {
                Mail mc = (Mail) userMailbox.get(num);
                if (mc != DELETED) {
                    responseString = OK_RESPONSE + " Message follows";
                    writeLoggedFlushedResponse(responseString);
                    try {
                        ExtraDotOutputStream edouts =
                                new ExtraDotOutputStream(outs);
                        OutputStream nouts = new BytesWrittenResetOutputStream(edouts,
                                                                  theWatchdog,
                                                                  theConfigData.getResetLength());
                        mc.getMessage().writeTo(nouts);
                        nouts.flush();
                        edouts.checkCRLFTerminator();
                        edouts.flush();
                    } finally {
                        out.println(".");
                        out.flush();
                    }
                } else {
                    StringBuffer responseBuffer =
                        new StringBuffer(64)
                                .append(ERR_RESPONSE)
                                .append(" Message (")
                                .append(num)
                                .append(") already deleted.");
                    responseString = responseBuffer.toString();
                    writeLoggedFlushedResponse(responseString);
                }
            } catch (IOException ioe) {
                responseString = ERR_RESPONSE + " Error while retrieving message.";
                writeLoggedFlushedResponse(responseString);
            } catch (MessagingException me) {
                responseString = ERR_RESPONSE + " Error while retrieving message.";
                writeLoggedFlushedResponse(responseString);
            } catch (IndexOutOfBoundsException iob) {
                StringBuffer responseBuffer =
                    new StringBuffer(64)
                            .append(ERR_RESPONSE)
                            .append(" Message (")
                            .append(num)
                            .append(") does not exist.");
                responseString = responseBuffer.toString();
                writeLoggedFlushedResponse(responseString);
            }
        } else {
            responseString = ERR_RESPONSE;
            writeLoggedFlushedResponse(responseString);
        }
    }

    /**
     * Handler method called upon receipt of a TOP command.
     * This command retrieves the top N lines of a specified
     * message in the mailbox.
     *
     * The expected command format is
     *  TOP [mail message number] [number of lines to return]
     *
     * @param command the command parsed by the parseCommand method
     * @param argument the first argument parsed by the parseCommand method
     * @param argument1 the second argument parsed by the parseCommand method
     */
    private void doTOP(String command,String argument,String argument1) {
        String responseString = null;
        if (state == TRANSACTION) {
            int num = 0;
            int lines = 0;
            try {
                num = Integer.parseInt(argument);
                lines = Integer.parseInt(argument1);
            } catch (NumberFormatException nfe) {
                responseString = ERR_RESPONSE + " Usage: TOP [mail number] [Line number]";
                writeLoggedFlushedResponse(responseString);
                return;
            }
            try {
                Mail mc = (Mail) userMailbox.get(num);
                if (mc != DELETED) {
                    responseString = OK_RESPONSE + " Message follows";
                    writeLoggedFlushedResponse(responseString);
                    try {
                        for (Enumeration e = mc.getMessage().getAllHeaderLines(); e.hasMoreElements(); ) {
                            out.println(e.nextElement());
                        }
                        out.println();
                        ExtraDotOutputStream edouts =
                                new ExtraDotOutputStream(outs);
                        OutputStream nouts = new BytesWrittenResetOutputStream(edouts,
                                                                  theWatchdog,
                                                                  theConfigData.getResetLength());
                        writeMessageContentTo(mc.getMessage(),nouts,lines);
                        nouts.flush();
                        edouts.checkCRLFTerminator();
                        edouts.flush();
                    } finally {
                        out.println(".");
                        out.flush();
                    }
                } else {
                    StringBuffer responseBuffer =
                        new StringBuffer(64)
                                .append(ERR_RESPONSE)
                                .append(" Message (")
                                .append(num)
                                .append(") already deleted.");
                    responseString = responseBuffer.toString();
                    writeLoggedFlushedResponse(responseString);
                }
            } catch (IOException ioe) {
                responseString = ERR_RESPONSE + " Error while retrieving message.";
                writeLoggedFlushedResponse(responseString);
            } catch (MessagingException me) {
                responseString = ERR_RESPONSE + " Error while retrieving message.";
                writeLoggedFlushedResponse(responseString);
            } catch (IndexOutOfBoundsException iob) {
                StringBuffer exceptionBuffer =
                    new StringBuffer(64)
                            .append(ERR_RESPONSE)
                            .append(" Message (")
                            .append(num)
                            .append(") does not exist.");
                responseString = exceptionBuffer.toString();
                writeLoggedFlushedResponse(responseString);
            }
        } else {
            responseString = ERR_RESPONSE;
            writeLoggedFlushedResponse(responseString);
        }
    }

    /**
     * Writes the content of the message, up to a total number of lines, out to 
     * an OutputStream.
     *
     * @param out the OutputStream to which to write the content
     * @param lines the number of lines to write to the stream
     *
     * @throws MessagingException if the MimeMessage is not set for this MailImpl
     * @throws IOException if an error occurs while reading or writing from the stream
     */
    public void writeMessageContentTo(MimeMessage message, OutputStream out, int lines)
        throws IOException, MessagingException {
        String line;
        BufferedReader br;
        if (message != null) {
            br = new BufferedReader(new InputStreamReader(message.getRawInputStream()));
            try {
                while (lines-- > 0) {
                    if ((line = br.readLine()) == null) {
                        break;
                    }
                    line += "\r\n";
                    out.write(line.getBytes());
                }
            } finally {
                br.close();
            }
        } else {
            throw new MessagingException("No message set for this MailImpl.");
        }
    }

    /**
     * Handler method called upon receipt of a QUIT command.
     * This method handles cleanup of the POP3Handler state.
     *
     * @param command the command parsed by the parseCommand method
     * @param argument the first argument parsed by the parseCommand method
     * @param argument1 the second argument parsed by the parseCommand method
     */
    private void doQUIT(String command,String argument,String argument1) {
        String responseString = null;
        if (state == AUTHENTICATION_READY ||  state == AUTHENTICATION_USERSET) {
            responseString = OK_RESPONSE + " Apache James POP3 Server signing off.";
            writeLoggedFlushedResponse(responseString);
            return;
        }
        List toBeRemoved =  ListUtils.subtract(backupUserMailbox, userMailbox);
        try {
            userInbox.remove(toBeRemoved);
            // for (Iterator it = toBeRemoved.iterator(); it.hasNext(); ) {
            //    Mail mc = (Mail) it.next();
            //    userInbox.remove(mc.getName());
            //}
            responseString = OK_RESPONSE + " Apache James POP3 Server signing off.";
            writeLoggedFlushedResponse(responseString);
        } catch (Exception ex) {
            responseString = ERR_RESPONSE + " Some deleted messages were not removed";
            writeLoggedFlushedResponse(responseString);
            getLogger().error("Some deleted messages were not removed: " + ex.getMessage());
        }
    }

    /**
     * Handler method called upon receipt of an unrecognized command.
     * Returns an error response and logs the command.
     *
     * @param command the command parsed by the parseCommand method
     * @param argument the first argument parsed by the parseCommand method
     * @param argument1 the second argument parsed by the parseCommand method
     */
    private void doUnknownCmd(String command,String argument,String argument1) {
        writeLoggedFlushedResponse(ERR_RESPONSE);
    }

    /**
     * This method logs at a "DEBUG" level the response string that
     * was sent to the POP3 client.  The method is provided largely
     * as syntactic sugar to neaten up the code base.  It is declared
     * private and final to encourage compiler inlining.
     *
     * @param responseString the response string sent to the client
     */
    private final void logResponseString(String responseString) {
        if (getLogger().isDebugEnabled()) {
            getLogger().debug("Sent: " + responseString);
        }
    }

    /**
     * Write and flush a response string.  The response is also logged.
     * Should be used for the last line of a multi-line response or
     * for a single line response.
     *
     * @param responseString the response string sent to the client
     */
    final void writeLoggedFlushedResponse(String responseString) {
        out.println(responseString);
        out.flush();
        logResponseString(responseString);
    }

    /**
     * Write a response string.  The response is also logged.
     * Used for multi-line responses.
     *
     * @param responseString the response string sent to the client
     */
    final void writeLoggedResponse(String responseString) {
        out.println(responseString);
        logResponseString(responseString);
    }

    /**
     * A private inner class which serves as an adaptor
     * between the WatchdogTarget interface and this
     * handler class.
     */
    private class POP3WatchdogTarget
        implements WatchdogTarget {

        /**
         * @see org.apache.james.util.watchdog.WatchdogTarget#execute()
         */
        public void execute() {
            POP3Handler.this.idleClose();
        }

    }

}