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