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
63 private static final String softwaretype = "JAMES POP3 Server "
64 + Constants.SOFTWARE_VERSION;
65
66
67 private final static String OK_RESPONSE = "+OK";
68
69
70 private final static String ERR_RESPONSE = "-ERR";
71
72
73
74
75
76
77 private final static int AUTHENTICATION_READY = 0;
78
79 private final static int AUTHENTICATION_USERSET = 1;
80
81
82 private final static int TRANSACTION = 2;
83
84
85
86
87 private static final Mail DELETED = new MailImpl();
88
89
90
91
92
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;
148
149
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
202 } finally {
203 socket = null;
204 }
205
206 synchronized (this) {
207
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
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
319
320 try {
321 if (socket != null) {
322 socket.close();
323 socket = null;
324 }
325 } catch (IOException ioe) {
326
327 } finally {
328 socket = null;
329 }
330
331 try {
332 if (in != null) {
333 in.close();
334 }
335 } catch (Exception e) {
336
337 } finally {
338 in = null;
339 }
340
341 try {
342 if (out != null) {
343 out.close();
344 }
345 } catch (Exception e) {
346
347 } finally {
348 out = null;
349 }
350
351 try {
352 if (outs != null) {
353 outs.close();
354 }
355 } catch (Exception e) {
356
357 } finally {
358 outs = null;
359 }
360
361 synchronized (this) {
362 handlerThread = null;
363 }
364
365
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
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
399
400 if (mc == null) {
401 continue;
402 }
403 userMailbox.add(mc);
404 }
405 } catch(MessagingException e) {
406
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
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
1073
1074
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