0001: /****************************************************************
0002: * Licensed to the Apache Software Foundation (ASF) under one *
0003: * or more contributor license agreements. See the NOTICE file *
0004: * distributed with this work for additional information *
0005: * regarding copyright ownership. The ASF licenses this file *
0006: * to you under the Apache License, Version 2.0 (the *
0007: * "License"); you may not use this file except in compliance *
0008: * with the License. You may obtain a copy of the License at *
0009: * *
0010: * http://www.apache.org/licenses/LICENSE-2.0 *
0011: * *
0012: * Unless required by applicable law or agreed to in writing, *
0013: * software distributed under the License is distributed on an *
0014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
0015: * KIND, either express or implied. See the License for the *
0016: * specific language governing permissions and limitations *
0017: * under the License. *
0018: ****************************************************************/package org.apache.james.pop3server;
0019:
0020: import org.apache.avalon.cornerstone.services.connection.ConnectionHandler;
0021: import org.apache.avalon.excalibur.pool.Poolable;
0022: import org.apache.avalon.framework.container.ContainerUtil;
0023: import org.apache.avalon.framework.logger.AbstractLogEnabled;
0024: import org.apache.commons.collections.ListUtils;
0025: import org.apache.james.Constants;
0026: import org.apache.james.core.MailImpl;
0027: import org.apache.james.services.MailRepository;
0028: import org.apache.james.util.CRLFTerminatedReader;
0029: import org.apache.james.util.ExtraDotOutputStream;
0030: import org.apache.james.util.InternetPrintWriter;
0031: import org.apache.james.util.watchdog.BytesWrittenResetOutputStream;
0032: import org.apache.james.util.watchdog.Watchdog;
0033: import org.apache.james.util.watchdog.WatchdogTarget;
0034: import org.apache.mailet.Mail;
0035:
0036: import javax.mail.MessagingException;
0037: import javax.mail.internet.MimeMessage;
0038:
0039: import java.io.BufferedInputStream;
0040: import java.io.BufferedOutputStream;
0041: import java.io.BufferedReader;
0042: import java.io.IOException;
0043: import java.io.InputStreamReader;
0044: import java.io.OutputStream;
0045: import java.io.PrintWriter;
0046: import java.net.Socket;
0047: import java.util.ArrayList;
0048: import java.util.Enumeration;
0049: import java.util.Iterator;
0050: import java.util.List;
0051: import java.util.Locale;
0052: import java.util.StringTokenizer;
0053:
0054: /**
0055: * The handler class for POP3 connections.
0056: *
0057: */
0058: public class POP3Handler extends AbstractLogEnabled implements
0059: ConnectionHandler, Poolable {
0060:
0061: // POP3 Server identification string used in POP3 headers
0062: private static final String softwaretype = "JAMES POP3 Server "
0063: + Constants.SOFTWARE_VERSION;
0064:
0065: // POP3 response prefixes
0066: private final static String OK_RESPONSE = "+OK"; // OK response. Requested content
0067: // will follow
0068:
0069: private final static String ERR_RESPONSE = "-ERR"; // Error response. Requested content
0070: // will not be provided. This prefix
0071: // is followed by a more detailed
0072: // error message
0073:
0074: // Authentication states for the POP3 interaction
0075:
0076: private final static int AUTHENTICATION_READY = 0; // Waiting for user id
0077:
0078: private final static int AUTHENTICATION_USERSET = 1; // User id provided, waiting for
0079: // password
0080:
0081: private final static int TRANSACTION = 2; // A valid user id/password combination
0082: // has been provided. In this state
0083: // the client can access the mailbox
0084: // of the specified user
0085:
0086: private static final Mail DELETED = new MailImpl(); // A placeholder for emails deleted
0087: // during the course of the POP3
0088: // transaction. This Mail instance
0089: // is used to enable fast checks as
0090: // to whether an email has been
0091: // deleted from the inbox.
0092:
0093: /**
0094: * The per-service configuration data that applies to all handlers
0095: */
0096: private POP3HandlerConfigurationData theConfigData;
0097:
0098: /**
0099: * The mail server's copy of the user's inbox
0100: */
0101: private MailRepository userInbox;
0102:
0103: /**
0104: * The thread executing this handler
0105: */
0106: private Thread handlerThread;
0107:
0108: /**
0109: * The TCP/IP socket over which the POP3 interaction
0110: * is occurring
0111: */
0112: private Socket socket;
0113:
0114: /**
0115: * The reader associated with incoming characters.
0116: */
0117: private CRLFTerminatedReader in;
0118:
0119: /**
0120: * The writer to which outgoing messages are written.
0121: */
0122: private PrintWriter out;
0123:
0124: /**
0125: * The socket's output stream
0126: */
0127: private OutputStream outs;
0128:
0129: /**
0130: * The current transaction state of the handler
0131: */
0132: private int state;
0133:
0134: /**
0135: * The user id associated with the POP3 dialogue
0136: */
0137: private String user;
0138:
0139: /**
0140: * A dynamic list representing the set of
0141: * emails in the user's inbox at any given time
0142: * during the POP3 transaction.
0143: */
0144: private ArrayList userMailbox = new ArrayList();
0145:
0146: private ArrayList backupUserMailbox; // A snapshot list representing the set of
0147: // emails in the user's inbox at the beginning
0148: // of the transaction
0149:
0150: /**
0151: * The watchdog being used by this handler to deal with idle timeouts.
0152: */
0153: private Watchdog theWatchdog;
0154:
0155: /**
0156: * The watchdog target that idles out this handler.
0157: */
0158: private WatchdogTarget theWatchdogTarget = new POP3WatchdogTarget();
0159:
0160: /**
0161: * Set the configuration data for the handler.
0162: *
0163: * @param theData the configuration data
0164: */
0165: void setConfigurationData(POP3HandlerConfigurationData theData) {
0166: theConfigData = theData;
0167: }
0168:
0169: /**
0170: * Set the Watchdog for use by this handler.
0171: *
0172: * @param theWatchdog the watchdog
0173: */
0174: void setWatchdog(Watchdog theWatchdog) {
0175: this .theWatchdog = theWatchdog;
0176: }
0177:
0178: /**
0179: * Gets the Watchdog Target that should be used by Watchdogs managing
0180: * this connection.
0181: *
0182: * @return the WatchdogTarget
0183: */
0184: WatchdogTarget getWatchdogTarget() {
0185: return theWatchdogTarget;
0186: }
0187:
0188: /**
0189: * Idle out this connection
0190: */
0191: void idleClose() {
0192: if (getLogger() != null) {
0193: getLogger().error("POP3 Connection has idled out.");
0194: }
0195: try {
0196: if (socket != null) {
0197: socket.close();
0198: }
0199: } catch (Exception e) {
0200: // ignored
0201: } finally {
0202: socket = null;
0203: }
0204:
0205: synchronized (this ) {
0206: // Interrupt the thread to recover from internal hangs
0207: if (handlerThread != null) {
0208: handlerThread.interrupt();
0209: handlerThread = null;
0210: }
0211: }
0212:
0213: }
0214:
0215: /**
0216: * @see org.apache.avalon.cornerstone.services.connection.ConnectionHandler#handleConnection(Socket)
0217: */
0218: public void handleConnection(Socket connection) throws IOException {
0219:
0220: String remoteHost = "";
0221: String remoteIP = "";
0222:
0223: try {
0224: this .socket = connection;
0225: synchronized (this ) {
0226: handlerThread = Thread.currentThread();
0227: }
0228: // in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "ASCII"), 512);
0229: in = new CRLFTerminatedReader(new BufferedInputStream(
0230: socket.getInputStream(), 512), "ASCII");
0231: remoteIP = socket.getInetAddress().getHostAddress();
0232: remoteHost = socket.getInetAddress().getHostName();
0233: } catch (Exception e) {
0234: if (getLogger().isErrorEnabled()) {
0235: StringBuffer exceptionBuffer = new StringBuffer(256)
0236: .append("Cannot open connection from ").append(
0237: remoteHost).append(" (").append(
0238: remoteIP).append("): ").append(
0239: e.getMessage());
0240: getLogger().error(exceptionBuffer.toString(), e);
0241: }
0242: }
0243:
0244: if (getLogger().isInfoEnabled()) {
0245: StringBuffer logBuffer = new StringBuffer(128).append(
0246: "Connection from ").append(remoteHost).append(" (")
0247: .append(remoteIP).append(") ");
0248: getLogger().info(logBuffer.toString());
0249: }
0250:
0251: try {
0252: outs = new BufferedOutputStream(socket.getOutputStream(),
0253: 1024);
0254: out = new InternetPrintWriter(outs, true);
0255: state = AUTHENTICATION_READY;
0256: user = "unknown";
0257: StringBuffer responseBuffer = new StringBuffer(256).append(
0258: OK_RESPONSE).append(" ").append(
0259: theConfigData.getHelloName()).append(
0260: " POP3 server (").append(POP3Handler.softwaretype)
0261: .append(") ready ");
0262: out.println(responseBuffer.toString());
0263:
0264: theWatchdog.start();
0265: while (parseCommand(readCommandLine())) {
0266: theWatchdog.reset();
0267: }
0268: theWatchdog.stop();
0269: if (getLogger().isInfoEnabled()) {
0270: StringBuffer logBuffer = new StringBuffer(128).append(
0271: "Connection for ").append(user)
0272: .append(" from ").append(remoteHost).append(
0273: " (").append(remoteIP).append(
0274: ") closed.");
0275: getLogger().info(logBuffer.toString());
0276: }
0277: } catch (Exception e) {
0278: out.println(ERR_RESPONSE + " Error closing connection.");
0279: out.flush();
0280: StringBuffer exceptionBuffer = new StringBuffer(128)
0281: .append("Exception during connection from ")
0282: .append(remoteHost).append(" (").append(remoteIP)
0283: .append(") : ").append(e.getMessage());
0284: getLogger().error(exceptionBuffer.toString(), e);
0285: } finally {
0286: resetHandler();
0287: }
0288: }
0289:
0290: /**
0291: * Resets the handler data to a basic state.
0292: */
0293: private void resetHandler() {
0294:
0295: if (theWatchdog != null) {
0296: ContainerUtil.dispose(theWatchdog);
0297: theWatchdog = null;
0298: }
0299:
0300: // Close and clear streams, sockets
0301:
0302: try {
0303: if (socket != null) {
0304: socket.close();
0305: socket = null;
0306: }
0307: } catch (IOException ioe) {
0308: // Ignoring exception on close
0309: } finally {
0310: socket = null;
0311: }
0312:
0313: try {
0314: if (in != null) {
0315: in.close();
0316: }
0317: } catch (Exception e) {
0318: // Ignored
0319: } finally {
0320: in = null;
0321: }
0322:
0323: try {
0324: if (out != null) {
0325: out.close();
0326: }
0327: } catch (Exception e) {
0328: // Ignored
0329: } finally {
0330: out = null;
0331: }
0332:
0333: try {
0334: if (outs != null) {
0335: outs.close();
0336: }
0337: } catch (Exception e) {
0338: // Ignored
0339: } finally {
0340: outs = null;
0341: }
0342:
0343: synchronized (this ) {
0344: handlerThread = null;
0345: }
0346:
0347: // Clear user data
0348: user = null;
0349: userInbox = null;
0350: if (userMailbox != null) {
0351: userMailbox.clear();
0352: userMailbox = null;
0353: }
0354:
0355: if (backupUserMailbox != null) {
0356: backupUserMailbox.clear();
0357: backupUserMailbox = null;
0358: }
0359:
0360: // Clear config data
0361: theConfigData = null;
0362: }
0363:
0364: /**
0365: * Implements a "stat". If the handler is currently in
0366: * a transaction state, this amounts to a rollback of the
0367: * mailbox contents to the beginning of the transaction.
0368: * This method is also called when first entering the
0369: * transaction state to initialize the handler copies of the
0370: * user inbox.
0371: *
0372: */
0373: private void stat() {
0374: userMailbox = new ArrayList();
0375: userMailbox.add(DELETED);
0376: try {
0377: for (Iterator it = userInbox.list(); it.hasNext();) {
0378: String key = (String) it.next();
0379: Mail mc = userInbox.retrieve(key);
0380: // Retrieve can return null if the mail is no longer in the store.
0381: // In this case we simply continue to the next key
0382: if (mc == null) {
0383: continue;
0384: }
0385: userMailbox.add(mc);
0386: }
0387: } catch (MessagingException e) {
0388: // In the event of an exception being thrown there may or may not be anything in userMailbox
0389: getLogger().error("Unable to STAT mail box ", e);
0390: } finally {
0391: backupUserMailbox = (ArrayList) userMailbox.clone();
0392: }
0393: }
0394:
0395: /**
0396: * Reads a line of characters off the command line.
0397: *
0398: * @return the trimmed input line
0399: * @throws IOException if an exception is generated reading in the input characters
0400: */
0401: final String readCommandLine() throws IOException {
0402: for (;;)
0403: try {
0404: String commandLine = in.readLine();
0405: if (commandLine != null) {
0406: commandLine = commandLine.trim();
0407: }
0408: return commandLine;
0409: } catch (CRLFTerminatedReader.TerminationException te) {
0410: writeLoggedFlushedResponse("-ERR Syntax error at character position "
0411: + te.position()
0412: + ". CR and LF must be CRLF paired. See RFC 1939 #3.");
0413: }
0414: }
0415:
0416: /**
0417: * This method parses POP3 commands read off the wire in handleConnection.
0418: * Actual processing of the command (possibly including additional back and
0419: * forth communication with the client) is delegated to one of a number of
0420: * command specific handler methods. The primary purpose of this method is
0421: * to parse the raw command string to determine exactly which handler should
0422: * be called. It returns true if expecting additional commands, false otherwise.
0423: *
0424: * @param rawCommand the raw command string passed in over the socket
0425: *
0426: * @return whether additional commands are expected.
0427: */
0428: private boolean parseCommand(String rawCommand) {
0429: if (rawCommand == null) {
0430: return false;
0431: }
0432: boolean returnValue = true;
0433: String command = rawCommand;
0434: StringTokenizer commandLine = new StringTokenizer(command, " ");
0435: int arguments = commandLine.countTokens();
0436: if (arguments == 0) {
0437: return true;
0438: } else if (arguments > 0) {
0439: command = commandLine.nextToken().toUpperCase(Locale.US);
0440: }
0441: if (getLogger().isDebugEnabled()) {
0442: // Don't display password in logger
0443: if (!command.equals("PASS")) {
0444: getLogger().debug("Command received: " + rawCommand);
0445: } else {
0446: getLogger().debug(
0447: "Command received: PASS <password omitted>");
0448: }
0449: }
0450: String argument = null;
0451: if (arguments > 1) {
0452: argument = commandLine.nextToken();
0453: }
0454: String argument1 = null;
0455: if (arguments > 2) {
0456: argument1 = commandLine.nextToken();
0457: }
0458:
0459: if (command.equals("USER")) {
0460: doUSER(command, argument, argument1);
0461: } else if (command.equals("PASS")) {
0462: doPASS(command, argument, argument1);
0463: } else if (command.equals("STAT")) {
0464: doSTAT(command, argument, argument1);
0465: } else if (command.equals("LIST")) {
0466: doLIST(command, argument, argument1);
0467: } else if (command.equals("UIDL")) {
0468: doUIDL(command, argument, argument1);
0469: } else if (command.equals("RSET")) {
0470: doRSET(command, argument, argument1);
0471: } else if (command.equals("DELE")) {
0472: doDELE(command, argument, argument1);
0473: } else if (command.equals("NOOP")) {
0474: doNOOP(command, argument, argument1);
0475: } else if (command.equals("RETR")) {
0476: doRETR(command, argument, argument1);
0477: } else if (command.equals("TOP")) {
0478: doTOP(command, argument, argument1);
0479: } else if (command.equals("QUIT")) {
0480: returnValue = false;
0481: doQUIT(command, argument, argument1);
0482: } else {
0483: doUnknownCmd(command, argument, argument1);
0484: }
0485: return returnValue;
0486: }
0487:
0488: /**
0489: * Handler method called upon receipt of a USER command.
0490: * Reads in the user id.
0491: *
0492: * @param command the command parsed by the parseCommand method
0493: * @param argument the first argument parsed by the parseCommand method
0494: * @param argument1 the second argument parsed by the parseCommand method
0495: */
0496: private void doUSER(String command, String argument,
0497: String argument1) {
0498: String responseString = null;
0499: if (state == AUTHENTICATION_READY && argument != null) {
0500: user = argument;
0501: state = AUTHENTICATION_USERSET;
0502: responseString = OK_RESPONSE;
0503: } else {
0504: responseString = ERR_RESPONSE;
0505: }
0506: writeLoggedFlushedResponse(responseString);
0507: }
0508:
0509: /**
0510: * Handler method called upon receipt of a PASS command.
0511: * Reads in and validates the password.
0512: *
0513: * @param command the command parsed by the parseCommand method
0514: * @param argument the first argument parsed by the parseCommand method
0515: * @param argument1 the second argument parsed by the parseCommand method
0516: */
0517: private void doPASS(String command, String argument,
0518: String argument1) {
0519: String responseString = null;
0520: if (state == AUTHENTICATION_USERSET && argument != null) {
0521: String passArg = argument;
0522: if (theConfigData.getUsersRepository().test(user, passArg)) {
0523: StringBuffer responseBuffer = new StringBuffer(64)
0524: .append(OK_RESPONSE).append(" Welcome ")
0525: .append(user);
0526: responseString = responseBuffer.toString();
0527: state = TRANSACTION;
0528: writeLoggedFlushedResponse(responseString);
0529: userInbox = theConfigData.getMailServer().getUserInbox(
0530: user);
0531: stat();
0532: } else {
0533: responseString = ERR_RESPONSE
0534: + " Authentication failed.";
0535: state = AUTHENTICATION_READY;
0536: writeLoggedFlushedResponse(responseString);
0537: }
0538: } else {
0539: responseString = ERR_RESPONSE;
0540: writeLoggedFlushedResponse(responseString);
0541: }
0542: }
0543:
0544: /**
0545: * Handler method called upon receipt of a STAT command.
0546: * Returns the number of messages in the mailbox and its
0547: * aggregate size.
0548: *
0549: * @param command the command parsed by the parseCommand method
0550: * @param argument the first argument parsed by the parseCommand method
0551: * @param argument1 the second argument parsed by the parseCommand method
0552: */
0553: private void doSTAT(String command, String argument,
0554: String argument1) {
0555: String responseString = null;
0556: if (state == TRANSACTION) {
0557: long size = 0;
0558: int count = 0;
0559: try {
0560: for (Iterator i = userMailbox.iterator(); i.hasNext();) {
0561: Mail mc = (Mail) i.next();
0562: if (mc != DELETED) {
0563: size += mc.getMessageSize();
0564: count++;
0565: }
0566: }
0567: StringBuffer responseBuffer = new StringBuffer(32)
0568: .append(OK_RESPONSE).append(" ").append(count)
0569: .append(" ").append(size);
0570: responseString = responseBuffer.toString();
0571: writeLoggedFlushedResponse(responseString);
0572: } catch (MessagingException me) {
0573: responseString = ERR_RESPONSE;
0574: writeLoggedFlushedResponse(responseString);
0575: }
0576: } else {
0577: responseString = ERR_RESPONSE;
0578: writeLoggedFlushedResponse(responseString);
0579: }
0580: }
0581:
0582: /**
0583: * Handler method called upon receipt of a LIST command.
0584: * Returns the number of messages in the mailbox and its
0585: * aggregate size, or optionally, the number and size of
0586: * a single message.
0587: *
0588: * @param command the command parsed by the parseCommand method
0589: * @param argument the first argument parsed by the parseCommand method
0590: * @param argument1 the second argument parsed by the parseCommand method
0591: */
0592: private void doLIST(String command, String argument,
0593: String argument1) {
0594: String responseString = null;
0595: if (state == TRANSACTION) {
0596: if (argument == null) {
0597: long size = 0;
0598: int count = 0;
0599: try {
0600: for (Iterator i = userMailbox.iterator(); i
0601: .hasNext();) {
0602: Mail mc = (Mail) i.next();
0603: if (mc != DELETED) {
0604: size += mc.getMessageSize();
0605: count++;
0606: }
0607: }
0608: StringBuffer responseBuffer = new StringBuffer(32)
0609: .append(OK_RESPONSE).append(" ").append(
0610: count).append(" ").append(size);
0611: responseString = responseBuffer.toString();
0612: writeLoggedFlushedResponse(responseString);
0613: count = 0;
0614: for (Iterator i = userMailbox.iterator(); i
0615: .hasNext(); count++) {
0616: Mail mc = (Mail) i.next();
0617:
0618: if (mc != DELETED) {
0619: responseBuffer = new StringBuffer(16)
0620: .append(count).append(" ").append(
0621: mc.getMessageSize());
0622: out.println(responseBuffer.toString());
0623: }
0624: }
0625: out.println(".");
0626: out.flush();
0627: } catch (MessagingException me) {
0628: responseString = ERR_RESPONSE;
0629: writeLoggedFlushedResponse(responseString);
0630: }
0631: } else {
0632: int num = 0;
0633: try {
0634: num = Integer.parseInt(argument);
0635: Mail mc = (Mail) userMailbox.get(num);
0636: if (mc != DELETED) {
0637: StringBuffer responseBuffer = new StringBuffer(
0638: 64).append(OK_RESPONSE).append(" ")
0639: .append(num).append(" ").append(
0640: mc.getMessageSize());
0641: responseString = responseBuffer.toString();
0642: writeLoggedFlushedResponse(responseString);
0643: } else {
0644: StringBuffer responseBuffer = new StringBuffer(
0645: 64).append(ERR_RESPONSE).append(
0646: " Message (").append(num).append(
0647: ") already deleted.");
0648: responseString = responseBuffer.toString();
0649: writeLoggedFlushedResponse(responseString);
0650: }
0651: } catch (IndexOutOfBoundsException npe) {
0652: StringBuffer responseBuffer = new StringBuffer(64)
0653: .append(ERR_RESPONSE).append(" Message (")
0654: .append(num).append(") does not exist.");
0655: responseString = responseBuffer.toString();
0656: writeLoggedFlushedResponse(responseString);
0657: } catch (NumberFormatException nfe) {
0658: StringBuffer responseBuffer = new StringBuffer(64)
0659: .append(ERR_RESPONSE).append(" ").append(
0660: argument).append(
0661: " is not a valid number");
0662: responseString = responseBuffer.toString();
0663: writeLoggedFlushedResponse(responseString);
0664: } catch (MessagingException me) {
0665: responseString = ERR_RESPONSE;
0666: writeLoggedFlushedResponse(responseString);
0667: }
0668: }
0669: } else {
0670: responseString = ERR_RESPONSE;
0671: writeLoggedFlushedResponse(responseString);
0672: }
0673: }
0674:
0675: /**
0676: * Handler method called upon receipt of a UIDL command.
0677: * Returns a listing of message ids to the client.
0678: *
0679: * @param command the command parsed by the parseCommand method
0680: * @param argument the first argument parsed by the parseCommand method
0681: * @param argument1 the second argument parsed by the parseCommand method
0682: */
0683: private void doUIDL(String command, String argument,
0684: String argument1) {
0685: String responseString = null;
0686: if (state == TRANSACTION) {
0687: if (argument == null) {
0688: responseString = OK_RESPONSE
0689: + " unique-id listing follows";
0690: writeLoggedFlushedResponse(responseString);
0691: int count = 0;
0692: for (Iterator i = userMailbox.iterator(); i.hasNext(); count++) {
0693: Mail mc = (Mail) i.next();
0694: if (mc != DELETED) {
0695: StringBuffer responseBuffer = new StringBuffer(
0696: 64).append(count).append(" ").append(
0697: mc.getName());
0698: out.println(responseBuffer.toString());
0699: }
0700: }
0701: out.println(".");
0702: out.flush();
0703: } else {
0704: int num = 0;
0705: try {
0706: num = Integer.parseInt(argument);
0707: Mail mc = (Mail) userMailbox.get(num);
0708: if (mc != DELETED) {
0709: StringBuffer responseBuffer = new StringBuffer(
0710: 64).append(OK_RESPONSE).append(" ")
0711: .append(num).append(" ").append(
0712: mc.getName());
0713: responseString = responseBuffer.toString();
0714: writeLoggedFlushedResponse(responseString);
0715: } else {
0716: StringBuffer responseBuffer = new StringBuffer(
0717: 64).append(ERR_RESPONSE).append(
0718: " Message (").append(num).append(
0719: ") already deleted.");
0720: responseString = responseBuffer.toString();
0721: writeLoggedFlushedResponse(responseString);
0722: }
0723: } catch (IndexOutOfBoundsException npe) {
0724: StringBuffer responseBuffer = new StringBuffer(64)
0725: .append(ERR_RESPONSE).append(" Message (")
0726: .append(num).append(") does not exist.");
0727: responseString = responseBuffer.toString();
0728: writeLoggedFlushedResponse(responseString);
0729: } catch (NumberFormatException nfe) {
0730: StringBuffer responseBuffer = new StringBuffer(64)
0731: .append(ERR_RESPONSE).append(" ").append(
0732: argument).append(
0733: " is not a valid number");
0734: responseString = responseBuffer.toString();
0735: writeLoggedFlushedResponse(responseString);
0736: }
0737: }
0738: } else {
0739: writeLoggedFlushedResponse(ERR_RESPONSE);
0740: }
0741: }
0742:
0743: /**
0744: * Handler method called upon receipt of a RSET command.
0745: * Calls stat() to reset the mailbox.
0746: *
0747: * @param command the command parsed by the parseCommand method
0748: * @param argument the first argument parsed by the parseCommand method
0749: * @param argument1 the second argument parsed by the parseCommand method
0750: */
0751: private void doRSET(String command, String argument,
0752: String argument1) {
0753: String responseString = null;
0754: if (state == TRANSACTION) {
0755: stat();
0756: responseString = OK_RESPONSE;
0757: } else {
0758: responseString = ERR_RESPONSE;
0759: }
0760: writeLoggedFlushedResponse(responseString);
0761: }
0762:
0763: /**
0764: * Handler method called upon receipt of a DELE command.
0765: * This command deletes a particular mail message from the
0766: * mailbox.
0767: *
0768: * @param command the command parsed by the parseCommand method
0769: * @param argument the first argument parsed by the parseCommand method
0770: * @param argument1 the second argument parsed by the parseCommand method
0771: */
0772: private void doDELE(String command, String argument,
0773: String argument1) {
0774: String responseString = null;
0775: if (state == TRANSACTION) {
0776: int num = 0;
0777: try {
0778: num = Integer.parseInt(argument);
0779: } catch (Exception e) {
0780: responseString = ERR_RESPONSE
0781: + " Usage: DELE [mail number]";
0782: writeLoggedFlushedResponse(responseString);
0783: return;
0784: }
0785: try {
0786: Mail mc = (Mail) userMailbox.get(num);
0787: if (mc == DELETED) {
0788: StringBuffer responseBuffer = new StringBuffer(64)
0789: .append(ERR_RESPONSE).append(" Message (")
0790: .append(num).append(") already deleted.");
0791: responseString = responseBuffer.toString();
0792: writeLoggedFlushedResponse(responseString);
0793: } else {
0794: userMailbox.set(num, DELETED);
0795: writeLoggedFlushedResponse(OK_RESPONSE
0796: + " Message deleted");
0797: }
0798: } catch (IndexOutOfBoundsException iob) {
0799: StringBuffer responseBuffer = new StringBuffer(64)
0800: .append(ERR_RESPONSE).append(" Message (")
0801: .append(num).append(") does not exist.");
0802: responseString = responseBuffer.toString();
0803: writeLoggedFlushedResponse(responseString);
0804: }
0805: } else {
0806: responseString = ERR_RESPONSE;
0807: writeLoggedFlushedResponse(responseString);
0808: }
0809: }
0810:
0811: /**
0812: * Handler method called upon receipt of a NOOP command.
0813: * Like all good NOOPs, does nothing much.
0814: *
0815: * @param command the command parsed by the parseCommand method
0816: * @param argument the first argument parsed by the parseCommand method
0817: * @param argument1 the second argument parsed by the parseCommand method
0818: */
0819: private void doNOOP(String command, String argument,
0820: String argument1) {
0821: String responseString = null;
0822: if (state == TRANSACTION) {
0823: responseString = OK_RESPONSE;
0824: writeLoggedFlushedResponse(responseString);
0825: } else {
0826: responseString = ERR_RESPONSE;
0827: writeLoggedFlushedResponse(responseString);
0828: }
0829: }
0830:
0831: /**
0832: * Handler method called upon receipt of a RETR command.
0833: * This command retrieves a particular mail message from the
0834: * mailbox.
0835: *
0836: * @param command the command parsed by the parseCommand method
0837: * @param argument the first argument parsed by the parseCommand method
0838: * @param argument1 the second argument parsed by the parseCommand method
0839: */
0840: private void doRETR(String command, String argument,
0841: String argument1) {
0842: String responseString = null;
0843: if (state == TRANSACTION) {
0844: int num = 0;
0845: try {
0846: num = Integer.parseInt(argument.trim());
0847: } catch (Exception e) {
0848: responseString = ERR_RESPONSE
0849: + " Usage: RETR [mail number]";
0850: writeLoggedFlushedResponse(responseString);
0851: return;
0852: }
0853: try {
0854: Mail mc = (Mail) userMailbox.get(num);
0855: if (mc != DELETED) {
0856: responseString = OK_RESPONSE + " Message follows";
0857: writeLoggedFlushedResponse(responseString);
0858: try {
0859: ExtraDotOutputStream edouts = new ExtraDotOutputStream(
0860: outs);
0861: OutputStream nouts = new BytesWrittenResetOutputStream(
0862: edouts, theWatchdog, theConfigData
0863: .getResetLength());
0864: mc.getMessage().writeTo(nouts);
0865: nouts.flush();
0866: edouts.checkCRLFTerminator();
0867: edouts.flush();
0868: } finally {
0869: out.println(".");
0870: out.flush();
0871: }
0872: } else {
0873: StringBuffer responseBuffer = new StringBuffer(64)
0874: .append(ERR_RESPONSE).append(" Message (")
0875: .append(num).append(") already deleted.");
0876: responseString = responseBuffer.toString();
0877: writeLoggedFlushedResponse(responseString);
0878: }
0879: } catch (IOException ioe) {
0880: responseString = ERR_RESPONSE
0881: + " Error while retrieving message.";
0882: writeLoggedFlushedResponse(responseString);
0883: } catch (MessagingException me) {
0884: responseString = ERR_RESPONSE
0885: + " Error while retrieving message.";
0886: writeLoggedFlushedResponse(responseString);
0887: } catch (IndexOutOfBoundsException iob) {
0888: StringBuffer responseBuffer = new StringBuffer(64)
0889: .append(ERR_RESPONSE).append(" Message (")
0890: .append(num).append(") does not exist.");
0891: responseString = responseBuffer.toString();
0892: writeLoggedFlushedResponse(responseString);
0893: }
0894: } else {
0895: responseString = ERR_RESPONSE;
0896: writeLoggedFlushedResponse(responseString);
0897: }
0898: }
0899:
0900: /**
0901: * Handler method called upon receipt of a TOP command.
0902: * This command retrieves the top N lines of a specified
0903: * message in the mailbox.
0904: *
0905: * The expected command format is
0906: * TOP [mail message number] [number of lines to return]
0907: *
0908: * @param command the command parsed by the parseCommand method
0909: * @param argument the first argument parsed by the parseCommand method
0910: * @param argument1 the second argument parsed by the parseCommand method
0911: */
0912: private void doTOP(String command, String argument, String argument1) {
0913: String responseString = null;
0914: if (state == TRANSACTION) {
0915: int num = 0;
0916: int lines = 0;
0917: try {
0918: num = Integer.parseInt(argument);
0919: lines = Integer.parseInt(argument1);
0920: } catch (NumberFormatException nfe) {
0921: responseString = ERR_RESPONSE
0922: + " Usage: TOP [mail number] [Line number]";
0923: writeLoggedFlushedResponse(responseString);
0924: return;
0925: }
0926: try {
0927: Mail mc = (Mail) userMailbox.get(num);
0928: if (mc != DELETED) {
0929: responseString = OK_RESPONSE + " Message follows";
0930: writeLoggedFlushedResponse(responseString);
0931: try {
0932: for (Enumeration e = mc.getMessage()
0933: .getAllHeaderLines(); e
0934: .hasMoreElements();) {
0935: out.println(e.nextElement());
0936: }
0937: out.println();
0938: ExtraDotOutputStream edouts = new ExtraDotOutputStream(
0939: outs);
0940: OutputStream nouts = new BytesWrittenResetOutputStream(
0941: edouts, theWatchdog, theConfigData
0942: .getResetLength());
0943: writeMessageContentTo(mc.getMessage(), nouts,
0944: lines);
0945: nouts.flush();
0946: edouts.checkCRLFTerminator();
0947: edouts.flush();
0948: } finally {
0949: out.println(".");
0950: out.flush();
0951: }
0952: } else {
0953: StringBuffer responseBuffer = new StringBuffer(64)
0954: .append(ERR_RESPONSE).append(" Message (")
0955: .append(num).append(") already deleted.");
0956: responseString = responseBuffer.toString();
0957: writeLoggedFlushedResponse(responseString);
0958: }
0959: } catch (IOException ioe) {
0960: responseString = ERR_RESPONSE
0961: + " Error while retrieving message.";
0962: writeLoggedFlushedResponse(responseString);
0963: } catch (MessagingException me) {
0964: responseString = ERR_RESPONSE
0965: + " Error while retrieving message.";
0966: writeLoggedFlushedResponse(responseString);
0967: } catch (IndexOutOfBoundsException iob) {
0968: StringBuffer exceptionBuffer = new StringBuffer(64)
0969: .append(ERR_RESPONSE).append(" Message (")
0970: .append(num).append(") does not exist.");
0971: responseString = exceptionBuffer.toString();
0972: writeLoggedFlushedResponse(responseString);
0973: }
0974: } else {
0975: responseString = ERR_RESPONSE;
0976: writeLoggedFlushedResponse(responseString);
0977: }
0978: }
0979:
0980: /**
0981: * Writes the content of the message, up to a total number of lines, out to
0982: * an OutputStream.
0983: *
0984: * @param out the OutputStream to which to write the content
0985: * @param lines the number of lines to write to the stream
0986: *
0987: * @throws MessagingException if the MimeMessage is not set for this MailImpl
0988: * @throws IOException if an error occurs while reading or writing from the stream
0989: */
0990: public void writeMessageContentTo(MimeMessage message,
0991: OutputStream out, int lines) throws IOException,
0992: MessagingException {
0993: String line;
0994: BufferedReader br;
0995: if (message != null) {
0996: br = new BufferedReader(new InputStreamReader(message
0997: .getRawInputStream()));
0998: try {
0999: while (lines-- > 0) {
1000: if ((line = br.readLine()) == null) {
1001: break;
1002: }
1003: line += "\r\n";
1004: out.write(line.getBytes());
1005: }
1006: } finally {
1007: br.close();
1008: }
1009: } else {
1010: throw new MessagingException(
1011: "No message set for this MailImpl.");
1012: }
1013: }
1014:
1015: /**
1016: * Handler method called upon receipt of a QUIT command.
1017: * This method handles cleanup of the POP3Handler state.
1018: *
1019: * @param command the command parsed by the parseCommand method
1020: * @param argument the first argument parsed by the parseCommand method
1021: * @param argument1 the second argument parsed by the parseCommand method
1022: */
1023: private void doQUIT(String command, String argument,
1024: String argument1) {
1025: String responseString = null;
1026: if (state == AUTHENTICATION_READY
1027: || state == AUTHENTICATION_USERSET) {
1028: responseString = OK_RESPONSE
1029: + " Apache James POP3 Server signing off.";
1030: writeLoggedFlushedResponse(responseString);
1031: return;
1032: }
1033: List toBeRemoved = ListUtils.subtract(backupUserMailbox,
1034: userMailbox);
1035: try {
1036: userInbox.remove(toBeRemoved);
1037: // for (Iterator it = toBeRemoved.iterator(); it.hasNext(); ) {
1038: // Mail mc = (Mail) it.next();
1039: // userInbox.remove(mc.getName());
1040: //}
1041: responseString = OK_RESPONSE
1042: + " Apache James POP3 Server signing off.";
1043: writeLoggedFlushedResponse(responseString);
1044: } catch (Exception ex) {
1045: responseString = ERR_RESPONSE
1046: + " Some deleted messages were not removed";
1047: writeLoggedFlushedResponse(responseString);
1048: getLogger().error(
1049: "Some deleted messages were not removed: "
1050: + ex.getMessage());
1051: }
1052: }
1053:
1054: /**
1055: * Handler method called upon receipt of an unrecognized command.
1056: * Returns an error response and logs the command.
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 doUnknownCmd(String command, String argument,
1063: String argument1) {
1064: writeLoggedFlushedResponse(ERR_RESPONSE);
1065: }
1066:
1067: /**
1068: * This method logs at a "DEBUG" level the response string that
1069: * was sent to the POP3 client. The method is provided largely
1070: * as syntactic sugar to neaten up the code base. It is declared
1071: * private and final to encourage compiler inlining.
1072: *
1073: * @param responseString the response string sent to the client
1074: */
1075: private final void logResponseString(String responseString) {
1076: if (getLogger().isDebugEnabled()) {
1077: getLogger().debug("Sent: " + responseString);
1078: }
1079: }
1080:
1081: /**
1082: * Write and flush a response string. The response is also logged.
1083: * Should be used for the last line of a multi-line response or
1084: * for a single line response.
1085: *
1086: * @param responseString the response string sent to the client
1087: */
1088: final void writeLoggedFlushedResponse(String responseString) {
1089: out.println(responseString);
1090: out.flush();
1091: logResponseString(responseString);
1092: }
1093:
1094: /**
1095: * Write a response string. The response is also logged.
1096: * Used for multi-line responses.
1097: *
1098: * @param responseString the response string sent to the client
1099: */
1100: final void writeLoggedResponse(String responseString) {
1101: out.println(responseString);
1102: logResponseString(responseString);
1103: }
1104:
1105: /**
1106: * A private inner class which serves as an adaptor
1107: * between the WatchdogTarget interface and this
1108: * handler class.
1109: */
1110: private class POP3WatchdogTarget implements WatchdogTarget {
1111:
1112: /**
1113: * @see org.apache.james.util.watchdog.WatchdogTarget#execute()
1114: */
1115: public void execute() {
1116: POP3Handler.this.idleClose();
1117: }
1118:
1119: }
1120:
1121: }
|