View Javadoc

1   /*****************************************************************
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   ****************************************************************/
19  
20  package org.apache.james.pop3server;
21  
22  import org.apache.avalon.cornerstone.services.connection.ConnectionHandler;
23  import org.apache.avalon.excalibur.pool.Poolable;
24  import org.apache.avalon.framework.container.ContainerUtil;
25  import org.apache.avalon.framework.logger.AbstractLogEnabled;
26  import org.apache.commons.collections.ListUtils;
27  import org.apache.james.Constants;
28  import org.apache.james.core.MailImpl;
29  import org.apache.james.services.MailRepository;
30  import org.apache.james.util.CRLFTerminatedReader;
31  import org.apache.james.util.ExtraDotOutputStream;
32  import org.apache.james.util.InternetPrintWriter;
33  import org.apache.james.util.watchdog.BytesWrittenResetOutputStream;
34  import org.apache.james.util.watchdog.Watchdog;
35  import org.apache.james.util.watchdog.WatchdogTarget;
36  import org.apache.mailet.Mail;
37  
38  import javax.mail.MessagingException;
39  import javax.mail.internet.MimeMessage;
40  
41  import java.io.BufferedInputStream;
42  import java.io.BufferedOutputStream;
43  import java.io.BufferedReader;
44  import java.io.IOException;
45  import java.io.InputStreamReader;
46  import java.io.OutputStream;
47  import java.io.PrintWriter;
48  import java.net.Socket;
49  import java.util.ArrayList;
50  import java.util.Enumeration;
51  import java.util.Iterator;
52  import java.util.List;
53  import java.util.Locale;
54  import java.util.StringTokenizer;
55  
56  /***
57   * The handler class for POP3 connections.
58   *
59   */
60  public class POP3Handler
61      extends AbstractLogEnabled
62      implements ConnectionHandler, Poolable {
63  
64      // POP3 Server identification string used in POP3 headers
65      private static final String softwaretype        = "JAMES POP3 Server "
66                                                          + Constants.SOFTWARE_VERSION;
67  
68      // POP3 response prefixes
69      private final static String OK_RESPONSE = "+OK";    // OK response.  Requested content
70                                                          // will follow
71  
72      private final static String ERR_RESPONSE = "-ERR";  // Error response.  Requested content
73                                                          // will not be provided.  This prefix
74                                                          // is followed by a more detailed
75                                                          // error message
76  
77      // Authentication states for the POP3 interaction
78  
79      private final static int AUTHENTICATION_READY = 0;    // Waiting for user id
80  
81      private final static int AUTHENTICATION_USERSET = 1;  // User id provided, waiting for
82                                                            // password
83  
84      private final static int TRANSACTION = 2;             // A valid user id/password combination
85                                                            // has been provided.  In this state
86                                                            // the client can access the mailbox
87                                                            // of the specified user
88  
89      private static final Mail DELETED = new MailImpl();   // A placeholder for emails deleted
90                                                            // during the course of the POP3
91                                                            // transaction.  This Mail instance
92                                                            // is used to enable fast checks as
93                                                            // to whether an email has been
94                                                            // deleted from the inbox.
95  
96      /***
97       * The per-service configuration data that applies to all handlers
98       */
99      private POP3HandlerConfigurationData theConfigData;
100 
101     /***
102      * The mail server's copy of the user's inbox
103      */
104     private MailRepository userInbox;
105 
106     /***
107      * The thread executing this handler
108      */
109     private Thread handlerThread;
110 
111     /***
112      * The TCP/IP socket over which the POP3 interaction
113      * is occurring
114      */
115     private Socket socket;
116 
117     /***
118      * The reader associated with incoming characters.
119      */
120     private CRLFTerminatedReader in;
121 
122     /***
123      * The writer to which outgoing messages are written.
124      */
125     private PrintWriter out;
126 
127     /***
128      * The socket's output stream
129      */
130     private OutputStream outs;
131 
132     /***
133      * The current transaction state of the handler
134      */
135     private int state;
136 
137     /***
138      * The user id associated with the POP3 dialogue
139      */
140     private String user;
141 
142     /***
143      * A dynamic list representing the set of
144      * emails in the user's inbox at any given time
145      * during the POP3 transaction.
146      */
147     private ArrayList userMailbox = new ArrayList();
148 
149     private ArrayList backupUserMailbox;         // A snapshot list representing the set of
150                                                  // emails in the user's inbox at the beginning
151                                                  // of the transaction
152 
153     /***
154      * The watchdog being used by this handler to deal with idle timeouts.
155      */
156     private Watchdog theWatchdog;
157 
158     /***
159      * The watchdog target that idles out this handler.
160      */
161     private WatchdogTarget theWatchdogTarget = new POP3WatchdogTarget();
162 
163     /***
164      * Set the configuration data for the handler.
165      *
166      * @param theData the configuration data
167      */
168     void setConfigurationData(POP3HandlerConfigurationData theData) {
169         theConfigData = theData;
170     }
171 
172     /***
173      * Set the Watchdog for use by this handler.
174      *
175      * @param theWatchdog the watchdog
176      */
177     void setWatchdog(Watchdog theWatchdog) {
178         this.theWatchdog = theWatchdog;
179     }
180 
181     /***
182      * Gets the Watchdog Target that should be used by Watchdogs managing
183      * this connection.
184      *
185      * @return the WatchdogTarget
186      */
187     WatchdogTarget getWatchdogTarget() {
188         return theWatchdogTarget;
189     }
190 
191     /***
192      * Idle out this connection
193      */
194     void idleClose() {
195         if (getLogger() != null) {
196             getLogger().error("POP3 Connection has idled out.");
197         }
198         try {
199             if (socket != null) {
200                 socket.close();
201             }
202         } catch (Exception e) {
203             // ignored
204         } finally {
205             socket = null;
206         }
207 
208         synchronized (this) {
209             // Interrupt the thread to recover from internal hangs
210             if (handlerThread != null) {
211                 handlerThread.interrupt();
212                 handlerThread = null;
213             }
214         }
215 
216     }
217 
218     /***
219      * @see org.apache.avalon.cornerstone.services.connection.ConnectionHandler#handleConnection(Socket)
220      */
221     public void handleConnection( Socket connection )
222             throws IOException {
223 
224         String remoteHost = "";
225         String remoteIP = "";
226 
227         try {
228             this.socket = connection;
229             synchronized (this) {
230                 handlerThread = Thread.currentThread();
231             }
232             // in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "ASCII"), 512);
233             in = new CRLFTerminatedReader(new BufferedInputStream(socket.getInputStream(), 512), "ASCII");
234             remoteIP = socket.getInetAddress().getHostAddress ();
235             remoteHost = socket.getInetAddress().getHostName ();
236         } catch (Exception e) {
237             if (getLogger().isErrorEnabled()) {
238                 StringBuffer exceptionBuffer =
239                     new StringBuffer(256)
240                             .append("Cannot open connection from ")
241                             .append(remoteHost)
242                             .append(" (")
243                             .append(remoteIP)
244                             .append("): ")
245                             .append(e.getMessage());
246                 getLogger().error( exceptionBuffer.toString(), e );
247             }
248         }
249 
250         if (getLogger().isInfoEnabled()) {
251             StringBuffer logBuffer =
252                 new StringBuffer(128)
253                         .append("Connection from ")
254                         .append(remoteHost)
255                         .append(" (")
256                         .append(remoteIP)
257                         .append(") ");
258             getLogger().info(logBuffer.toString());
259         }
260 
261         try {
262             outs = new BufferedOutputStream(socket.getOutputStream(), 1024);
263             out = new InternetPrintWriter(outs, true);
264             state = AUTHENTICATION_READY;
265             user = "unknown";
266             StringBuffer responseBuffer =
267                 new StringBuffer(256)
268                         .append(OK_RESPONSE)
269                         .append(" ")
270                         .append(theConfigData.getHelloName())
271                         .append(" POP3 server (")
272                         .append(POP3Handler.softwaretype)
273                         .append(") ready ");
274             out.println(responseBuffer.toString());
275 
276             theWatchdog.start();
277             while (parseCommand(readCommandLine())) {
278                 theWatchdog.reset();
279             }
280             theWatchdog.stop();
281             if (getLogger().isInfoEnabled()) {
282                 StringBuffer logBuffer =
283                     new StringBuffer(128)
284                         .append("Connection for ")
285                         .append(user)
286                         .append(" from ")
287                         .append(remoteHost)
288                         .append(" (")
289                         .append(remoteIP)
290                         .append(") closed.");
291                 getLogger().info(logBuffer.toString());
292             }
293         } catch (Exception e) {
294             out.println(ERR_RESPONSE + " Error closing connection.");
295             out.flush();
296             StringBuffer exceptionBuffer =
297                 new StringBuffer(128)
298                         .append("Exception during connection from ")
299                         .append(remoteHost)
300                         .append(" (")
301                         .append(remoteIP)
302                         .append(") : ")
303                         .append(e.getMessage());
304             getLogger().error(exceptionBuffer.toString(), e );
305         } finally {
306             resetHandler();
307         }
308     }
309 
310     /***
311      * Resets the handler data to a basic state.
312      */
313     private void resetHandler() {
314 
315         if (theWatchdog != null) {
316             ContainerUtil.dispose(theWatchdog);
317             theWatchdog = null;
318         }
319 
320         // Close and clear streams, sockets
321 
322         try {
323             if (socket != null) {
324                 socket.close();
325                 socket = null;
326             }
327         } catch (IOException ioe) {
328             // Ignoring exception on close
329         } finally {
330             socket = null;
331         }
332 
333         try {
334             if (in != null) {
335                 in.close();
336             }
337         } catch (Exception e) {
338             // Ignored
339         } finally {
340             in = null;
341         }
342 
343         try {
344             if (out != null) {
345                 out.close();
346             }
347         } catch (Exception e) {
348             // Ignored
349         } finally {
350             out = null;
351         }
352 
353         try {
354            if (outs != null) {
355                outs.close();
356             }
357         } catch (Exception e) {
358             // Ignored
359         } finally {
360             outs = null;
361         }
362 
363         synchronized (this) {
364             handlerThread = null;
365         }
366 
367         // Clear user data
368         user = null;
369         userInbox = null;
370         if (userMailbox != null) {
371             userMailbox.clear();
372             userMailbox = null;
373         }
374 
375         if (backupUserMailbox != null) {
376             backupUserMailbox.clear();
377             backupUserMailbox = null;
378         }
379 
380         // Clear config data
381         theConfigData = null;
382     }
383 
384     /***
385      * Implements a "stat".  If the handler is currently in
386      * a transaction state, this amounts to a rollback of the
387      * mailbox contents to the beginning of the transaction.
388      * This method is also called when first entering the
389      * transaction state to initialize the handler copies of the
390      * user inbox.
391      *
392      */
393     private void stat() {
394         userMailbox = new ArrayList();
395         userMailbox.add(DELETED);
396         try {
397             for (Iterator it = userInbox.list(); it.hasNext(); ) {
398                 String key = (String) it.next();
399                 Mail mc = userInbox.retrieve(key);
400                 // Retrieve can return null if the mail is no longer in the store.
401                 // In this case we simply continue to the next key
402                 if (mc == null) {
403                     continue;
404                 }
405                 userMailbox.add(mc);
406             }
407         } catch(MessagingException e) {
408             // In the event of an exception being thrown there may or may not be anything in userMailbox
409             getLogger().error("Unable to STAT mail box ", e);
410         }
411         finally {
412             backupUserMailbox = (ArrayList) userMailbox.clone();
413         }
414     }
415 
416     /***
417      * Reads a line of characters off the command line.
418      *
419      * @return the trimmed input line
420      * @throws IOException if an exception is generated reading in the input characters
421      */
422     final String readCommandLine() throws IOException {
423         for (;;) try {
424             String commandLine = in.readLine();
425             if (commandLine != null) {
426                 commandLine = commandLine.trim();
427             }
428             return commandLine;
429         } catch (CRLFTerminatedReader.TerminationException te) {
430             writeLoggedFlushedResponse("-ERR Syntax error at character position " + te.position() + ". CR and LF must be CRLF paired.  See RFC 1939 #3.");
431         }
432     }
433 
434     /***
435      * This method parses POP3 commands read off the wire in handleConnection.
436      * Actual processing of the command (possibly including additional back and
437      * forth communication with the client) is delegated to one of a number of
438      * command specific handler methods.  The primary purpose of this method is
439      * to parse the raw command string to determine exactly which handler should
440      * be called.  It returns true if expecting additional commands, false otherwise.
441      *
442      * @param rawCommand the raw command string passed in over the socket
443      *
444      * @return whether additional commands are expected.
445      */
446     private boolean parseCommand(String rawCommand) {
447         if (rawCommand == null) {
448             return false;
449         }
450         boolean returnValue = true;
451         String command = rawCommand;
452         StringTokenizer commandLine = new StringTokenizer(command, " ");
453         int arguments = commandLine.countTokens();
454         if (arguments == 0) {
455             return true;
456         } else if(arguments > 0) {
457             command = commandLine.nextToken().toUpperCase(Locale.US);
458         }
459         if (getLogger().isDebugEnabled()) {
460             // Don't display password in logger
461             if (!command.equals("PASS")) {
462                 getLogger().debug("Command received: " + rawCommand);
463             } else {
464                 getLogger().debug("Command received: PASS <password omitted>");
465             }
466         }
467         String argument = null;
468         if(arguments > 1) {
469             argument = commandLine.nextToken();
470         }
471         String argument1 = null;
472         if(arguments > 2) {
473             argument1 = commandLine.nextToken();
474         }
475 
476         if (command.equals("USER")) {
477             doUSER(command,argument,argument1);
478         } else if (command.equals("PASS")) {
479             doPASS(command,argument,argument1);
480         } else if (command.equals("STAT")) {
481             doSTAT(command,argument,argument1);
482         } else if (command.equals("LIST")) {
483             doLIST(command,argument,argument1);
484         } else if (command.equals("UIDL")) {
485             doUIDL(command,argument,argument1);
486         } else if (command.equals("RSET")) {
487             doRSET(command,argument,argument1);
488         } else if (command.equals("DELE")) {
489             doDELE(command,argument,argument1);
490         } else if (command.equals("NOOP")) {
491             doNOOP(command,argument,argument1);
492         } else if (command.equals("RETR")) {
493             doRETR(command,argument,argument1);
494         } else if (command.equals("TOP")) {
495             doTOP(command,argument,argument1);
496         } else if (command.equals("QUIT")) {
497             returnValue = false;
498             doQUIT(command,argument,argument1);
499         } else {
500             doUnknownCmd(command,argument,argument1);
501         }
502         return returnValue;
503     }
504 
505     /***
506      * Handler method called upon receipt of a USER command.
507      * Reads in the user id.
508      *
509      * @param command the command parsed by the parseCommand method
510      * @param argument the first argument parsed by the parseCommand method
511      * @param argument1 the second argument parsed by the parseCommand method
512      */
513     private void doUSER(String command,String argument,String argument1) {
514         String responseString = null;
515         if (state == AUTHENTICATION_READY && argument != null) {
516             user = argument;
517             state = AUTHENTICATION_USERSET;
518             responseString = OK_RESPONSE;
519         } else {
520             responseString = ERR_RESPONSE;
521         }
522         writeLoggedFlushedResponse(responseString);
523     }
524 
525     /***
526      * Handler method called upon receipt of a PASS command.
527      * Reads in and validates the password.
528      *
529      * @param command the command parsed by the parseCommand method
530      * @param argument the first argument parsed by the parseCommand method
531      * @param argument1 the second argument parsed by the parseCommand method
532      */
533     private void doPASS(String command,String argument,String argument1) {
534         String responseString = null;
535         if (state == AUTHENTICATION_USERSET && argument != null) {
536             String passArg = argument;
537             if (theConfigData.getUsersRepository().test(user, passArg)) {
538                 StringBuffer responseBuffer =
539                     new StringBuffer(64)
540                             .append(OK_RESPONSE)
541                             .append(" Welcome ")
542                             .append(user);
543                 responseString = responseBuffer.toString();
544                 state = TRANSACTION;
545                 writeLoggedFlushedResponse(responseString);
546                 userInbox = theConfigData.getMailServer().getUserInbox(user);
547                 stat();
548             } else {
549                 responseString = ERR_RESPONSE + " Authentication failed.";
550                 state = AUTHENTICATION_READY;
551                 writeLoggedFlushedResponse(responseString);
552             }
553         } else {
554             responseString = ERR_RESPONSE;
555             writeLoggedFlushedResponse(responseString);
556         }
557     }
558 
559     /***
560      * Handler method called upon receipt of a STAT command.
561      * Returns the number of messages in the mailbox and its
562      * aggregate size.
563      *
564      * @param command the command parsed by the parseCommand method
565      * @param argument the first argument parsed by the parseCommand method
566      * @param argument1 the second argument parsed by the parseCommand method
567      */
568     private void doSTAT(String command,String argument,String argument1) {
569         String responseString = null;
570         if (state == TRANSACTION) {
571             long size = 0;
572             int count = 0;
573             try {
574                 for (Iterator i = userMailbox.iterator(); i.hasNext(); ) {
575                     Mail mc = (Mail) i.next();
576                     if (mc != DELETED) {
577                         size += mc.getMessageSize();
578                         count++;
579                     }
580                 }
581                 StringBuffer responseBuffer =
582                     new StringBuffer(32)
583                             .append(OK_RESPONSE)
584                             .append(" ")
585                             .append(count)
586                             .append(" ")
587                             .append(size);
588                 responseString = responseBuffer.toString();
589                 writeLoggedFlushedResponse(responseString);
590             } catch (MessagingException me) {
591                 responseString = ERR_RESPONSE;
592                 writeLoggedFlushedResponse(responseString);
593             }
594         } else {
595             responseString = ERR_RESPONSE;
596             writeLoggedFlushedResponse(responseString);
597         }
598     }
599 
600     /***
601      * Handler method called upon receipt of a LIST command.
602      * Returns the number of messages in the mailbox and its
603      * aggregate size, or optionally, the number and size of
604      * a single message.
605      *
606      * @param command the command parsed by the parseCommand method
607      * @param argument the first argument parsed by the parseCommand method
608      * @param argument1 the second argument parsed by the parseCommand method
609      */
610     private void doLIST(String command,String argument,String argument1) {
611         String responseString = null;
612         if (state == TRANSACTION) {
613             if (argument == null) {
614                 long size = 0;
615                 int count = 0;
616                 try {
617                     for (Iterator i = userMailbox.iterator(); i.hasNext(); ) {
618                         Mail mc = (Mail) i.next();
619                         if (mc != DELETED) {
620                             size += mc.getMessageSize();
621                             count++;
622                         }
623                     }
624                     StringBuffer responseBuffer =
625                         new StringBuffer(32)
626                                 .append(OK_RESPONSE)
627                                 .append(" ")
628                                 .append(count)
629                                 .append(" ")
630                                 .append(size);
631                     responseString = responseBuffer.toString();
632                     writeLoggedFlushedResponse(responseString);
633                     count = 0;
634                     for (Iterator i = userMailbox.iterator(); i.hasNext(); count++) {
635                         Mail mc = (Mail) i.next();
636 
637                         if (mc != DELETED) {
638                             responseBuffer =
639                                 new StringBuffer(16)
640                                         .append(count)
641                                         .append(" ")
642                                         .append(mc.getMessageSize());
643                             out.println(responseBuffer.toString());
644                         }
645                     }
646                     out.println(".");
647                     out.flush();
648                 } catch (MessagingException me) {
649                     responseString = ERR_RESPONSE;
650                     writeLoggedFlushedResponse(responseString);
651                 }
652             } else {
653                 int num = 0;
654                 try {
655                     num = Integer.parseInt(argument);
656                     Mail mc = (Mail) userMailbox.get(num);
657                     if (mc != DELETED) {
658                         StringBuffer responseBuffer =
659                             new StringBuffer(64)
660                                     .append(OK_RESPONSE)
661                                     .append(" ")
662                                     .append(num)
663                                     .append(" ")
664                                     .append(mc.getMessageSize());
665                         responseString = responseBuffer.toString();
666                         writeLoggedFlushedResponse(responseString);
667                     } else {
668                         StringBuffer responseBuffer =
669                             new StringBuffer(64)
670                                     .append(ERR_RESPONSE)
671                                     .append(" Message (")
672                                     .append(num)
673                                     .append(") already deleted.");
674                         responseString = responseBuffer.toString();
675                         writeLoggedFlushedResponse(responseString);
676                     }
677                 } catch (IndexOutOfBoundsException npe) {
678                     StringBuffer responseBuffer =
679                         new StringBuffer(64)
680                                 .append(ERR_RESPONSE)
681                                 .append(" Message (")
682                                 .append(num)
683                                 .append(") does not exist.");
684                     responseString = responseBuffer.toString();
685                     writeLoggedFlushedResponse(responseString);
686                 } catch (NumberFormatException nfe) {
687                     StringBuffer responseBuffer =
688                         new StringBuffer(64)
689                                 .append(ERR_RESPONSE)
690                                 .append(" ")
691                                 .append(argument)
692                                 .append(" is not a valid number");
693                     responseString = responseBuffer.toString();
694                     writeLoggedFlushedResponse(responseString);
695                 } catch (MessagingException me) {
696                     responseString = ERR_RESPONSE;
697                     writeLoggedFlushedResponse(responseString);
698                }
699             }
700         } else {
701             responseString = ERR_RESPONSE;
702             writeLoggedFlushedResponse(responseString);
703         }
704     }
705 
706     /***
707      * Handler method called upon receipt of a UIDL command.
708      * Returns a listing of message ids to the client.
709      *
710      * @param command the command parsed by the parseCommand method
711      * @param argument the first argument parsed by the parseCommand method
712      * @param argument1 the second argument parsed by the parseCommand method
713      */
714     private void doUIDL(String command,String argument,String argument1) {
715         String responseString = null;
716         if (state == TRANSACTION) {
717             if (argument == null) {
718                 responseString = OK_RESPONSE + " unique-id listing follows";
719                 writeLoggedFlushedResponse(responseString);
720                 int count = 0;
721                 for (Iterator i = userMailbox.iterator(); i.hasNext(); count++) {
722                     Mail mc = (Mail) i.next();
723                     if (mc != DELETED) {
724                         StringBuffer responseBuffer =
725                             new StringBuffer(64)
726                                     .append(count)
727                                     .append(" ")
728                                     .append(mc.getName());
729                         out.println(responseBuffer.toString());
730                     }
731                 }
732                 out.println(".");
733                 out.flush();
734             } else {
735                 int num = 0;
736                 try {
737                     num = Integer.parseInt(argument);
738                     Mail mc = (Mail) userMailbox.get(num);
739                     if (mc != DELETED) {
740                         StringBuffer responseBuffer =
741                             new StringBuffer(64)
742                                     .append(OK_RESPONSE)
743                                     .append(" ")
744                                     .append(num)
745                                     .append(" ")
746                                     .append(mc.getName());
747                         responseString = responseBuffer.toString();
748                         writeLoggedFlushedResponse(responseString);
749                     } else {
750                         StringBuffer responseBuffer =
751                             new StringBuffer(64)
752                                     .append(ERR_RESPONSE)
753                                     .append(" Message (")
754                                     .append(num)
755                                     .append(") already deleted.");
756                         responseString = responseBuffer.toString();
757                         writeLoggedFlushedResponse(responseString);
758                     }
759                 } catch (IndexOutOfBoundsException npe) {
760                     StringBuffer responseBuffer =
761                         new StringBuffer(64)
762                                 .append(ERR_RESPONSE)
763                                 .append(" Message (")
764                                 .append(num)
765                                 .append(") does not exist.");
766                     responseString = responseBuffer.toString();
767                     writeLoggedFlushedResponse(responseString);
768                 } catch (NumberFormatException nfe) {
769                     StringBuffer responseBuffer =
770                         new StringBuffer(64)
771                                 .append(ERR_RESPONSE)
772                                 .append(" ")
773                                 .append(argument)
774                                 .append(" is not a valid number");
775                     responseString = responseBuffer.toString();
776                     writeLoggedFlushedResponse(responseString);
777                 }
778             }
779         } else {
780             writeLoggedFlushedResponse(ERR_RESPONSE);
781         }
782     }
783 
784     /***
785      * Handler method called upon receipt of a RSET command.
786      * Calls stat() to reset the mailbox.
787      *
788      * @param command the command parsed by the parseCommand method
789      * @param argument the first argument parsed by the parseCommand method
790      * @param argument1 the second argument parsed by the parseCommand method
791      */
792     private void doRSET(String command,String argument,String argument1) {
793         String responseString = null;
794         if (state == TRANSACTION) {
795             stat();
796             responseString = OK_RESPONSE;
797         } else {
798             responseString = ERR_RESPONSE;
799         }
800         writeLoggedFlushedResponse(responseString);
801     }
802 
803     /***
804      * Handler method called upon receipt of a DELE command.
805      * This command deletes a particular mail message from the
806      * mailbox.
807      *
808      * @param command the command parsed by the parseCommand method
809      * @param argument the first argument parsed by the parseCommand method
810      * @param argument1 the second argument parsed by the parseCommand method
811      */
812     private void doDELE(String command,String argument,String argument1) {
813         String responseString = null;
814         if (state == TRANSACTION) {
815             int num = 0;
816             try {
817                 num = Integer.parseInt(argument);
818             } catch (Exception e) {
819                 responseString = ERR_RESPONSE + " Usage: DELE [mail number]";
820                 writeLoggedFlushedResponse(responseString);
821                 return;
822             }
823             try {
824                 Mail mc = (Mail) userMailbox.get(num);
825                 if (mc == DELETED) {
826                     StringBuffer responseBuffer =
827                         new StringBuffer(64)
828                                 .append(ERR_RESPONSE)
829                                 .append(" Message (")
830                                 .append(num)
831                                 .append(") already deleted.");
832                     responseString = responseBuffer.toString();
833                     writeLoggedFlushedResponse(responseString);
834                 } else {
835                     userMailbox.set(num, DELETED);
836                     writeLoggedFlushedResponse(OK_RESPONSE + " Message deleted");
837                 }
838             } catch (IndexOutOfBoundsException iob) {
839                 StringBuffer responseBuffer =
840                     new StringBuffer(64)
841                             .append(ERR_RESPONSE)
842                             .append(" Message (")
843                             .append(num)
844                             .append(") does not exist.");
845                 responseString = responseBuffer.toString();
846                 writeLoggedFlushedResponse(responseString);
847             }
848         } else {
849             responseString = ERR_RESPONSE;
850             writeLoggedFlushedResponse(responseString);
851         }
852     }
853 
854     /***
855      * Handler method called upon receipt of a NOOP command.
856      * Like all good NOOPs, does nothing much.
857      *
858      * @param command the command parsed by the parseCommand method
859      * @param argument the first argument parsed by the parseCommand method
860      * @param argument1 the second argument parsed by the parseCommand method
861      */
862     private void doNOOP(String command,String argument,String argument1) {
863         String responseString = null;
864         if (state == TRANSACTION) {
865             responseString = OK_RESPONSE;
866             writeLoggedFlushedResponse(responseString);
867         } else {
868             responseString = ERR_RESPONSE;
869             writeLoggedFlushedResponse(responseString);
870         }
871     }
872 
873     /***
874      * Handler method called upon receipt of a RETR command.
875      * This command retrieves a particular mail message from the
876      * mailbox.
877      *
878      * @param command the command parsed by the parseCommand method
879      * @param argument the first argument parsed by the parseCommand method
880      * @param argument1 the second argument parsed by the parseCommand method
881      */
882     private void doRETR(String command,String argument,String argument1) {
883         String responseString = null;
884         if (state == TRANSACTION) {
885             int num = 0;
886             try {
887                 num = Integer.parseInt(argument.trim());
888             } catch (Exception e) {
889                 responseString = ERR_RESPONSE + " Usage: RETR [mail number]";
890                 writeLoggedFlushedResponse(responseString);
891                 return;
892             }
893             try {
894                 Mail mc = (Mail) userMailbox.get(num);
895                 if (mc != DELETED) {
896                     responseString = OK_RESPONSE + " Message follows";
897                     writeLoggedFlushedResponse(responseString);
898                     try {
899                         ExtraDotOutputStream edouts =
900                                 new ExtraDotOutputStream(outs);
901                         OutputStream nouts = new BytesWrittenResetOutputStream(edouts,
902                                                                   theWatchdog,
903                                                                   theConfigData.getResetLength());
904                         mc.getMessage().writeTo(nouts);
905                         nouts.flush();
906                         edouts.checkCRLFTerminator();
907                         edouts.flush();
908                     } finally {
909                         out.println(".");
910                         out.flush();
911                     }
912                 } else {
913                     StringBuffer responseBuffer =
914                         new StringBuffer(64)
915                                 .append(ERR_RESPONSE)
916                                 .append(" Message (")
917                                 .append(num)
918                                 .append(") already deleted.");
919                     responseString = responseBuffer.toString();
920                     writeLoggedFlushedResponse(responseString);
921                 }
922             } catch (IOException ioe) {
923                 responseString = ERR_RESPONSE + " Error while retrieving message.";
924                 writeLoggedFlushedResponse(responseString);
925             } catch (MessagingException me) {
926                 responseString = ERR_RESPONSE + " Error while retrieving message.";
927                 writeLoggedFlushedResponse(responseString);
928             } catch (IndexOutOfBoundsException iob) {
929                 StringBuffer responseBuffer =
930                     new StringBuffer(64)
931                             .append(ERR_RESPONSE)
932                             .append(" Message (")
933                             .append(num)
934                             .append(") does not exist.");
935                 responseString = responseBuffer.toString();
936                 writeLoggedFlushedResponse(responseString);
937             }
938         } else {
939             responseString = ERR_RESPONSE;
940             writeLoggedFlushedResponse(responseString);
941         }
942     }
943 
944     /***
945      * Handler method called upon receipt of a TOP command.
946      * This command retrieves the top N lines of a specified
947      * message in the mailbox.
948      *
949      * The expected command format is
950      *  TOP [mail message number] [number of lines to return]
951      *
952      * @param command the command parsed by the parseCommand method
953      * @param argument the first argument parsed by the parseCommand method
954      * @param argument1 the second argument parsed by the parseCommand method
955      */
956     private void doTOP(String command,String argument,String argument1) {
957         String responseString = null;
958         if (state == TRANSACTION) {
959             int num = 0;
960             int lines = 0;
961             try {
962                 num = Integer.parseInt(argument);
963                 lines = Integer.parseInt(argument1);
964             } catch (NumberFormatException nfe) {
965                 responseString = ERR_RESPONSE + " Usage: TOP [mail number] [Line number]";
966                 writeLoggedFlushedResponse(responseString);
967                 return;
968             }
969             try {
970                 Mail mc = (Mail) userMailbox.get(num);
971                 if (mc != DELETED) {
972                     responseString = OK_RESPONSE + " Message follows";
973                     writeLoggedFlushedResponse(responseString);
974                     try {
975                         for (Enumeration e = mc.getMessage().getAllHeaderLines(); e.hasMoreElements(); ) {
976                             out.println(e.nextElement());
977                         }
978                         out.println();
979                         ExtraDotOutputStream edouts =
980                                 new ExtraDotOutputStream(outs);
981                         OutputStream nouts = new BytesWrittenResetOutputStream(edouts,
982                                                                   theWatchdog,
983                                                                   theConfigData.getResetLength());
984                         writeMessageContentTo(mc.getMessage(),nouts,lines);
985                         nouts.flush();
986                         edouts.checkCRLFTerminator();
987                         edouts.flush();
988                     } finally {
989                         out.println(".");
990                         out.flush();
991                     }
992                 } else {
993                     StringBuffer responseBuffer =
994                         new StringBuffer(64)
995                                 .append(ERR_RESPONSE)
996                                 .append(" Message (")
997                                 .append(num)
998                                 .append(") already deleted.");
999                     responseString = responseBuffer.toString();
1000                     writeLoggedFlushedResponse(responseString);
1001                 }
1002             } catch (IOException ioe) {
1003                 responseString = ERR_RESPONSE + " Error while retrieving message.";
1004                 writeLoggedFlushedResponse(responseString);
1005             } catch (MessagingException me) {
1006                 responseString = ERR_RESPONSE + " Error while retrieving message.";
1007                 writeLoggedFlushedResponse(responseString);
1008             } catch (IndexOutOfBoundsException iob) {
1009                 StringBuffer exceptionBuffer =
1010                     new StringBuffer(64)
1011                             .append(ERR_RESPONSE)
1012                             .append(" Message (")
1013                             .append(num)
1014                             .append(") does not exist.");
1015                 responseString = exceptionBuffer.toString();
1016                 writeLoggedFlushedResponse(responseString);
1017             }
1018         } else {
1019             responseString = ERR_RESPONSE;
1020             writeLoggedFlushedResponse(responseString);
1021         }
1022     }
1023 
1024     /***
1025      * Writes the content of the message, up to a total number of lines, out to 
1026      * an OutputStream.
1027      *
1028      * @param out the OutputStream to which to write the content
1029      * @param lines the number of lines to write to the stream
1030      *
1031      * @throws MessagingException if the MimeMessage is not set for this MailImpl
1032      * @throws IOException if an error occurs while reading or writing from the stream
1033      */
1034     public void writeMessageContentTo(MimeMessage message, OutputStream out, int lines)
1035         throws IOException, MessagingException {
1036         String line;
1037         BufferedReader br;
1038         if (message != null) {
1039             br = new BufferedReader(new InputStreamReader(message.getRawInputStream()));
1040             try {
1041                 while (lines-- > 0) {
1042                     if ((line = br.readLine()) == null) {
1043                         break;
1044                     }
1045                     line += "\r\n";
1046                     out.write(line.getBytes());
1047                 }
1048             } finally {
1049                 br.close();
1050             }
1051         } else {
1052             throw new MessagingException("No message set for this MailImpl.");
1053         }
1054     }
1055 
1056     /***
1057      * Handler method called upon receipt of a QUIT command.
1058      * This method handles cleanup of the POP3Handler state.
1059      *
1060      * @param command the command parsed by the parseCommand method
1061      * @param argument the first argument parsed by the parseCommand method
1062      * @param argument1 the second argument parsed by the parseCommand method
1063      */
1064     private void doQUIT(String command,String argument,String argument1) {
1065         String responseString = null;
1066         if (state == AUTHENTICATION_READY ||  state == AUTHENTICATION_USERSET) {
1067             responseString = OK_RESPONSE + " Apache James POP3 Server signing off.";
1068             writeLoggedFlushedResponse(responseString);
1069             return;
1070         }
1071         List toBeRemoved =  ListUtils.subtract(backupUserMailbox, userMailbox);
1072         try {
1073             userInbox.remove(toBeRemoved);
1074             // for (Iterator it = toBeRemoved.iterator(); it.hasNext(); ) {
1075             //    Mail mc = (Mail) it.next();
1076             //    userInbox.remove(mc.getName());
1077             //}
1078             responseString = OK_RESPONSE + " Apache James POP3 Server signing off.";
1079             writeLoggedFlushedResponse(responseString);
1080         } catch (Exception ex) {
1081             responseString = ERR_RESPONSE + " Some deleted messages were not removed";
1082             writeLoggedFlushedResponse(responseString);
1083             getLogger().error("Some deleted messages were not removed: " + ex.getMessage());
1084         }
1085     }
1086 
1087     /***
1088      * Handler method called upon receipt of an unrecognized command.
1089      * Returns an error response and logs the command.
1090      *
1091      * @param command the command parsed by the parseCommand method
1092      * @param argument the first argument parsed by the parseCommand method
1093      * @param argument1 the second argument parsed by the parseCommand method
1094      */
1095     private void doUnknownCmd(String command,String argument,String argument1) {
1096         writeLoggedFlushedResponse(ERR_RESPONSE);
1097     }
1098 
1099     /***
1100      * This method logs at a "DEBUG" level the response string that
1101      * was sent to the POP3 client.  The method is provided largely
1102      * as syntactic sugar to neaten up the code base.  It is declared
1103      * private and final to encourage compiler inlining.
1104      *
1105      * @param responseString the response string sent to the client
1106      */
1107     private final void logResponseString(String responseString) {
1108         if (getLogger().isDebugEnabled()) {
1109             getLogger().debug("Sent: " + responseString);
1110         }
1111     }
1112 
1113     /***
1114      * Write and flush a response string.  The response is also logged.
1115      * Should be used for the last line of a multi-line response or
1116      * for a single line response.
1117      *
1118      * @param responseString the response string sent to the client
1119      */
1120     final void writeLoggedFlushedResponse(String responseString) {
1121         out.println(responseString);
1122         out.flush();
1123         logResponseString(responseString);
1124     }
1125 
1126     /***
1127      * Write a response string.  The response is also logged.
1128      * Used for multi-line responses.
1129      *
1130      * @param responseString the response string sent to the client
1131      */
1132     final void writeLoggedResponse(String responseString) {
1133         out.println(responseString);
1134         logResponseString(responseString);
1135     }
1136 
1137     /***
1138      * A private inner class which serves as an adaptor
1139      * between the WatchdogTarget interface and this
1140      * handler class.
1141      */
1142     private class POP3WatchdogTarget
1143         implements WatchdogTarget {
1144 
1145         /***
1146          * @see org.apache.james.util.watchdog.WatchdogTarget#execute()
1147          */
1148         public void execute() {
1149             POP3Handler.this.idleClose();
1150         }
1151 
1152     }
1153 
1154 }
1155