View Javadoc

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