View Javadoc

1   /****************************************************************
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   ****************************************************************/
19  
20  
21  
22  package org.apache.james.pop3server;
23  
24  import org.apache.avalon.framework.container.ContainerUtil;
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.socket.CRLFTerminatedReader;
29  import org.apache.james.socket.ProtocolHandler;
30  import org.apache.james.socket.ProtocolHandlerHelper;
31  import org.apache.james.util.watchdog.Watchdog;
32  import org.apache.mailet.Mail;
33  
34  import java.io.IOException;
35  import java.io.OutputStream;
36  import java.util.ArrayList;
37  import java.util.HashMap;
38  import java.util.Iterator;
39  import java.util.List;
40  import java.util.Locale;
41  
42  /**
43   * The handler class for POP3 connections.
44   *
45   */
46  public class POP3Handler implements POP3Session, ProtocolHandler {
47  
48      private ProtocolHandlerHelper helper;
49      
50      private final static byte COMMAND_MODE = 1;
51      private final static byte RESPONSE_MODE = 2;
52  
53      // POP3 Server identification string used in POP3 headers
54      private static final String softwaretype        = "JAMES POP3 Server "
55                                                          + Constants.SOFTWARE_VERSION;
56  
57      // POP3 response prefixes
58      final static String OK_RESPONSE = "+OK";    // OK response.  Requested content
59                                                          // will follow
60  
61      final static String ERR_RESPONSE = "-ERR";  // Error response.  Requested content
62                                                          // will not be provided.  This prefix
63                                                          // is followed by a more detailed
64                                                          // error message
65  
66      // Authentication states for the POP3 interaction
67  
68      final static int AUTHENTICATION_READY = 0;    // Waiting for user id
69  
70      final static int AUTHENTICATION_USERSET = 1;  // User id provided, waiting for
71                                                            // password
72  
73      final static int TRANSACTION = 2;             // A valid user id/password combination
74                                                            // has been provided.  In this state
75                                                            // the client can access the mailbox
76                                                            // of the specified user
77  
78      static final Mail DELETED = new MailImpl();   // A placeholder for emails deleted
79                                                            // during the course of the POP3
80                                                            // transaction.  This Mail instance
81                                                            // is used to enable fast checks as
82                                                            // to whether an email has been
83                                                            // deleted from the inbox.
84  
85      /**
86       * The per-service configuration data that applies to all handlers
87       */
88      private POP3HandlerConfigurationData theConfigData;
89  
90      /**
91       * The mail server's copy of the user's inbox
92       */
93      private MailRepository userInbox;
94  
95      /**
96       * The current transaction state of the handler
97       */
98      private int handlerState;
99  
100     /**
101      * A dynamic list representing the set of
102      * emails in the user's inbox at any given time
103      * during the POP3 transaction.
104      */
105     private List userMailbox = new ArrayList();
106 
107     private List backupUserMailbox;         // A snapshot list representing the set of
108                                                  // emails in the user's inbox at the beginning
109                                                  // of the transaction
110 
111     /**
112      * The per-handler response buffer used to marshal responses.
113      */
114     private StringBuffer responseBuffer = new StringBuffer(256);
115 
116 
117     /**
118      * The name of the currently parsed command
119      */
120     String curCommandName =  null;
121 
122     /**
123      * The value of the currently parsed command
124      */
125     String curCommandArgument =  null;
126 
127     /**
128      * The POP3HandlerChain object set by POP3Server
129      */
130     POP3HandlerChain handlerChain = null;
131 
132     /**
133      * The session termination status
134      */
135     private boolean sessionEnded = false;
136 
137 
138     /**
139      * The hash map that holds variables for the POP3 message transfer in progress.
140      *
141      * This hash map should only be used to store variable set in a particular
142      * set of sequential MAIL-RCPT-DATA commands, as described in RFC 2821.  Per
143      * connection values should be stored as member variables in this class.
144      */
145     private HashMap state = new HashMap();
146 
147     /**
148      * The user name of the authenticated user associated with this POP3 transaction.
149      */
150     private String authenticatedUser;
151 
152     /**
153      * The mode of the current session
154      */
155     private byte mode;
156     
157     
158     /**
159      * Set the configuration data for the handler.
160      *
161      * @param theData the configuration data
162      */
163     public void setConfigurationData(Object theData) {
164         if (theData instanceof POP3HandlerConfigurationData) {
165             theConfigData = (POP3HandlerConfigurationData) theData;
166         } else {
167             throw new IllegalArgumentException("Configuration object does not implement POP3HandlerConfigurationData");
168         }
169     }
170     
171     /**
172      * @see org.apache.james.socket.AbstractJamesHandler#handleProtocol()
173      */
174     public void handleProtocol() throws IOException {
175         handlerState = AUTHENTICATION_READY;
176         authenticatedUser = "unknown";
177 
178         sessionEnded = false;
179         resetState();
180 
181         // Initially greet the connector
182         // Format is:  Sat, 24 Jan 1998 13:16:09 -0500
183         responseBuffer.append(OK_RESPONSE)
184                     .append(" ")
185                     .append(theConfigData.getHelloName())
186                     .append(" POP3 server (")
187                     .append(POP3Handler.softwaretype)
188                     .append(") ready ");
189         String responseString = clearResponseBuffer();
190         helper.writeLoggedFlushedResponse(responseString);
191 
192         //Session started - RUN all connect handlers
193         List connectHandlers = handlerChain.getConnectHandlers();
194         if(connectHandlers != null) {
195             int count = connectHandlers.size();
196             for(int i = 0; i < count; i++) {
197                 ((ConnectHandler)connectHandlers.get(i)).onConnect(this);
198                 if(sessionEnded) {
199                     break;
200                 }
201             }
202         }
203 
204         
205         helper.getWatchdog().start();
206         while(!sessionEnded) {
207           //Reset the current command values
208           curCommandName = null;
209           curCommandArgument = null;
210           mode = COMMAND_MODE;
211 
212           //parse the command
213           String cmdString =  readCommandLine();
214           if (cmdString == null) {
215               break;
216           }
217           int spaceIndex = cmdString.indexOf(" ");
218           if (spaceIndex > 0) {
219               curCommandName = cmdString.substring(0, spaceIndex);
220               curCommandArgument = cmdString.substring(spaceIndex + 1);
221           } else {
222               curCommandName = cmdString;
223           }
224           curCommandName = curCommandName.toUpperCase(Locale.US);
225 
226           if (helper.getAvalonLogger().isDebugEnabled()) {
227               // Don't display password in logger
228               if (!curCommandName.equals("PASS")) {
229                   helper.getAvalonLogger().debug("Command received: " + cmdString);
230               } else {
231                   helper.getAvalonLogger().debug("Command received: PASS <password omitted>");
232               }
233           }
234 
235           //fetch the command handlers registered to the command
236           List commandHandlers = handlerChain.getCommandHandlers(curCommandName);
237           if(commandHandlers == null) {
238               //end the session
239               break;
240           } else {
241               int count = commandHandlers.size();
242               for(int i = 0; i < count; i++) {
243                   ((CommandHandler)commandHandlers.get(i)).onCommand(this);
244                   helper.getWatchdog().reset();
245                   //if the response is received, stop processing of command handlers
246                   if(mode != COMMAND_MODE) {
247                       break;
248                   }
249               }
250 
251           }
252         }
253         helper.getWatchdog().stop();
254         if (helper.getAvalonLogger().isInfoEnabled()) {
255             StringBuffer logBuffer =
256                 new StringBuffer(128)
257                     .append("Connection for ")
258                     .append(getUser())
259                     .append(" from ")
260                     .append(helper.getRemoteHost())
261                     .append(" (")
262                     .append(helper.getRemoteIP())
263                     .append(") closed.");
264             helper.getAvalonLogger().info(logBuffer.toString());
265         }
266     }
267     
268     /**
269      * @see org.apache.james.socket.AbstractJamesHandler#errorHandler(java.lang.RuntimeException)
270      */
271     public void errorHandler(RuntimeException e) {
272         helper.defaultErrorHandler(e);
273         try {
274             helper.getOutputWriter().println(ERR_RESPONSE + " Error closing connection.");
275             helper.getOutputWriter().flush();
276         } catch (Throwable t) {
277             
278         }
279     }
280 
281     /**
282      * Resets the handler data to a basic state.
283      */
284     public void resetHandler() {
285         // Clear user data
286         authenticatedUser = null;
287         userInbox = null;
288         if (userMailbox != null) {
289             Iterator i = userMailbox.iterator();
290             while (i.hasNext()) {
291                 ContainerUtil.dispose(i.next());
292             }
293             userMailbox.clear();
294             userMailbox = null;
295         }
296 
297         if (backupUserMailbox != null) {
298             Iterator i = backupUserMailbox.iterator();
299             while (i.hasNext()) {
300                 ContainerUtil.dispose(i.next());
301             }
302             backupUserMailbox.clear();
303             backupUserMailbox = null;
304         }
305 
306         // Clear config data
307         theConfigData = null;
308     }
309 
310     /**
311      * Reads a line of characters off the command line.
312      *
313      * @return the trimmed input line
314      * @throws IOException if an exception is generated reading in the input characters
315      */
316     public final String readCommandLine() throws IOException {
317         for (;;) try {
318             String commandLine = helper.getInputReader().readLine();
319             if (commandLine != null) {
320                 commandLine = commandLine.trim();
321             }
322             return commandLine;
323         } catch (CRLFTerminatedReader.TerminationException te) {
324             helper.writeLoggedFlushedResponse("-ERR Syntax error at character position " + te.position() + ". CR and LF must be CRLF paired.  See RFC 1939 #3.");
325         }
326     }
327 
328     /**
329      * This method parses POP3 commands read off the wire in handleConnection.
330      * Actual processing of the command (possibly including additional back and
331      * forth communication with the client) is delegated to one of a number of
332      * command specific handler methods.  The primary purpose of this method is
333      * to parse the raw command string to determine exactly which handler should
334      * be called.  It returns true if expecting additional commands, false otherwise.
335      */
336     
337     /**
338      * @see org.apache.james.pop3server.POP3Session#getRemoteHost()
339      */
340     public String getRemoteHost() {
341         return helper.getRemoteHost();
342     }
343 
344     /**
345      * @see org.apache.james.pop3server.POP3Session#getRemoteIPAddress()
346      */
347     public String getRemoteIPAddress() {
348         return helper.getRemoteIP();
349     }
350 
351     /**
352      * @see org.apache.james.pop3server.POP3Session#endSession()
353      */
354     public void endSession() {
355         sessionEnded = true;
356     }
357 
358     /**
359      * @see org.apache.james.pop3server.POP3Session#isSessionEnded()
360      */
361     public boolean isSessionEnded() {
362         return sessionEnded;
363     }
364 
365     /**
366      * @see org.apache.james.pop3server.POP3Session#resetState()
367      */
368     public void resetState() {
369         state.clear();
370     }
371 
372     /**
373      * @see org.apache.james.pop3server.POP3Session#getState()
374      */
375     public HashMap getState() {
376         return state;
377     }
378 
379     /**
380      * @see org.apache.james.pop3server.POP3Session#getUser()
381      */
382     public String getUser() {
383         return authenticatedUser;
384     }
385 
386     /**
387      * @see org.apache.james.pop3server.POP3Session#setUser(java.lang.String)
388      */
389     public void setUser(String userID) {
390         authenticatedUser = userID;
391     }
392 
393     /**
394      * @see org.apache.james.pop3server.POP3Session#getResponseBuffer()
395      */
396     public StringBuffer getResponseBuffer() {
397         return responseBuffer;
398     }
399 
400     /**
401      * @see org.apache.james.pop3server.POP3Session#clearResponseBuffer()
402      */
403     public String clearResponseBuffer() {
404         String responseString = responseBuffer.toString();
405         responseBuffer.delete(0,responseBuffer.length());
406         return responseString;
407     }
408 
409     /**
410      * @see org.apache.james.pop3server.POP3Session#getWatchdog()
411      */
412     public Watchdog getWatchdog() {
413         return helper.getWatchdog();
414     }
415 
416     /**
417      * Sets the POP3HandlerChain
418      *
419      * @param handlerChain POP3Handler object
420      */
421     public void setHandlerChain(POP3HandlerChain handlerChain) {
422         this.handlerChain = handlerChain;
423     }
424 
425     /**
426      * @see org.apache.james.pop3server.POP3Session#writeResponse(java.lang.String)
427      */
428     public void writeResponse(String respString) {
429         helper.writeLoggedFlushedResponse(respString);
430         //TODO Explain this well
431         if(mode == COMMAND_MODE) {
432             mode = RESPONSE_MODE;
433         }
434     }
435 
436     /**
437      * @see org.apache.james.pop3server.POP3Session#getCommandName()
438      */
439     public String getCommandName() {
440         return curCommandName;
441     }
442 
443     /**
444      * @see org.apache.james.pop3server.POP3Session#getCommandArgument()
445      */
446     public String getCommandArgument() {
447         return curCommandArgument;
448     }
449 
450     /**
451      * @see org.apache.james.pop3server.POP3Session#getConfigurationData()
452      */
453     public POP3HandlerConfigurationData getConfigurationData() {
454         return theConfigData;
455     }
456 
457     /**
458      * @see org.apache.james.pop3server.POP3Session#getHandlerState()
459      */
460     public int getHandlerState() {
461         return handlerState;
462     }
463 
464     /**
465      * @see org.apache.james.pop3server.POP3Session#setHandlerState(int)
466      */
467     public void setHandlerState(int handlerState) {
468         this.handlerState = handlerState;
469     }
470 
471     /**
472      * @see org.apache.james.pop3server.POP3Session#getUserInbox()
473      */
474     public MailRepository getUserInbox() {
475         return userInbox;
476     }
477 
478     /**
479      * @see org.apache.james.pop3server.POP3Session#setUserInbox(org.apache.james.services.MailRepository)
480      */
481     public void setUserInbox(MailRepository userInbox) {
482         this.userInbox = userInbox;
483     }
484 
485     /**
486      * @see org.apache.james.pop3server.POP3Session#getUserMailbox()
487      */
488     public List getUserMailbox() {
489         return userMailbox;
490     }
491 
492     /**
493      * @see org.apache.james.pop3server.POP3Session#setUserMailbox(java.util.List)
494      */
495     public void setUserMailbox(List userMailbox) {
496         this.userMailbox = userMailbox;
497     }
498 
499     /**
500      * @see org.apache.james.pop3server.POP3Session#getBackupUserMailbox()
501      */
502     public List getBackupUserMailbox() {
503         return backupUserMailbox;
504     }
505 
506 
507     /**
508      * @see org.apache.james.pop3server.POP3Session#setUserMailbox(List)
509      */
510     public void setBackupUserMailbox(List backupUserMailbox) {
511         this.backupUserMailbox = backupUserMailbox;
512     }
513 
514     /**
515      * @see org.apache.james.pop3server.POP3Session#getOutputStream()
516      */
517     public OutputStream getOutputStream() {
518         return helper.getOutputStream();
519     }
520 
521     /**
522      * @see org.apache.james.socket.ProtocolHandler#setProtocolHandlerHelper(org.apache.james.socket.ProtocolHandlerHelper)
523      */
524     public void setProtocolHandlerHelper(ProtocolHandlerHelper phh) {
525         this.helper = phh;
526     }
527 
528 }