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 }