KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > james > smtpserver > SMTPHandler


1 /***********************************************************************
2  * Copyright (c) 2000-2004 The Apache Software Foundation. *
3  * All rights reserved. *
4  * ------------------------------------------------------------------- *
5  * Licensed under the Apache License, Version 2.0 (the "License"); you *
6  * may not use this file except in compliance with the License. You *
7  * may obtain a copy of the License at: *
8  * *
9  * http://www.apache.org/licenses/LICENSE-2.0 *
10  * *
11  * Unless required by applicable law or agreed to in writing, software *
12  * distributed under the License is distributed on an "AS IS" BASIS, *
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
14  * implied. See the License for the specific language governing *
15  * permissions and limitations under the License. *
16  ***********************************************************************/

17
18 package org.apache.james.smtpserver;
19
20 import org.apache.avalon.cornerstone.services.connection.ConnectionHandler;
21 import org.apache.avalon.excalibur.pool.Poolable;
22 import org.apache.avalon.framework.activity.Disposable;
23 import org.apache.avalon.framework.logger.AbstractLogEnabled;
24 import org.apache.james.Constants;
25 import org.apache.james.core.MailHeaders;
26 import org.apache.james.core.MailImpl;
27 import org.apache.james.services.MailServer;
28 import org.apache.james.services.UsersRepository;
29 import org.apache.james.util.*;
30 import org.apache.james.util.watchdog.BytesReadResetInputStream;
31 import org.apache.james.util.watchdog.Watchdog;
32 import org.apache.james.util.watchdog.WatchdogTarget;
33 import org.apache.mailet.MailAddress;
34 import javax.mail.MessagingException JavaDoc;
35 import java.io.*;
36 import java.net.Socket JavaDoc;
37 import java.net.SocketException JavaDoc;
38 import java.util.*;
39 /**
40  * Provides SMTP functionality by carrying out the server side of the SMTP
41  * interaction.
42  *
43  * @version CVS $Revision: 1.35.4.19 $ $Date: 2004/05/08 02:28:30 $
44  */

45 public class SMTPHandler
46     extends AbstractLogEnabled
47     implements ConnectionHandler, Poolable {
48
49     /**
50      * SMTP Server identification string used in SMTP headers
51      */

52     private final static String JavaDoc SOFTWARE_TYPE = "JAMES SMTP Server "
53                                                  + Constants.SOFTWARE_VERSION;
54
55     // Keys used to store/lookup data in the internal state hash map
56

57     private final static String JavaDoc CURRENT_HELO_MODE = "CURRENT_HELO_MODE"; // HELO or EHLO
58
private final static String JavaDoc SENDER = "SENDER_ADDRESS"; // Sender's email address
59
private final static String JavaDoc MESG_FAILED = "MESG_FAILED"; // Message failed flag
60
private final static String JavaDoc MESG_SIZE = "MESG_SIZE"; // The size of the message
61
private final static String JavaDoc RCPT_LIST = "RCPT_LIST"; // The message recipients
62

63     /**
64      * The character array that indicates termination of an SMTP connection
65      */

66     private final static char[] SMTPTerminator = { '\r', '\n', '.', '\r', '\n' };
67
68     /**
69      * Static Random instance used to generate SMTP ids
70      */

71     private final static Random random = new Random();
72
73     /**
74      * Static RFC822DateFormat used to generate date headers
75      */

76     private final static RFC822DateFormat rfc822DateFormat = new RFC822DateFormat();
77
78     /**
79      * The text string for the SMTP HELO command.
80      */

81     private final static String JavaDoc COMMAND_HELO = "HELO";
82
83     /**
84      * The text string for the SMTP EHLO command.
85      */

86     private final static String JavaDoc COMMAND_EHLO = "EHLO";
87
88     /**
89      * The text string for the SMTP AUTH command.
90      */

91     private final static String JavaDoc COMMAND_AUTH = "AUTH";
92
93     /**
94      * The text string for the SMTP MAIL command.
95      */

96     private final static String JavaDoc COMMAND_MAIL = "MAIL";
97
98     /**
99      * The text string for the SMTP RCPT command.
100      */

101     private final static String JavaDoc COMMAND_RCPT = "RCPT";
102
103     /**
104      * The text string for the SMTP NOOP command.
105      */

106     private final static String JavaDoc COMMAND_NOOP = "NOOP";
107
108     /**
109      * The text string for the SMTP RSET command.
110      */

111     private final static String JavaDoc COMMAND_RSET = "RSET";
112
113     /**
114      * The text string for the SMTP DATA command.
115      */

116     private final static String JavaDoc COMMAND_DATA = "DATA";
117
118     /**
119      * The text string for the SMTP QUIT command.
120      */

121     private final static String JavaDoc COMMAND_QUIT = "QUIT";
122
123     /**
124      * The text string for the SMTP HELP command.
125      */

126     private final static String JavaDoc COMMAND_HELP = "HELP";
127
128     /**
129      * The text string for the SMTP VRFY command.
130      */

131     private final static String JavaDoc COMMAND_VRFY = "VRFY";
132
133     /**
134      * The text string for the SMTP EXPN command.
135      */

136     private final static String JavaDoc COMMAND_EXPN = "EXPN";
137
138     /**
139      * The text string for the SMTP AUTH type PLAIN.
140      */

141     private final static String JavaDoc AUTH_TYPE_PLAIN = "PLAIN";
142
143     /**
144      * The text string for the SMTP AUTH type LOGIN.
145      */

146     private final static String JavaDoc AUTH_TYPE_LOGIN = "LOGIN";
147
148     /**
149      * The text string for the SMTP MAIL command SIZE option.
150      */

151     private final static String JavaDoc MAIL_OPTION_SIZE = "SIZE";
152
153     /**
154      * The mail attribute holding the SMTP AUTH user name, if any.
155      */

156     private final static String JavaDoc SMTP_AUTH_USER_ATTRIBUTE_NAME = "org.apache.james.SMTPAuthUser";
157     
158     /**
159      * The thread executing this handler
160      */

161     private Thread JavaDoc handlerThread;
162
163     /**
164      * The TCP/IP socket over which the SMTP
165      * dialogue is occurring.
166      */

167     private Socket JavaDoc socket;
168
169     /**
170      * The incoming stream of bytes coming from the socket.
171      */

172     private InputStream in;
173
174     /**
175      * The writer to which outgoing messages are written.
176      */

177     private PrintWriter out;
178
179     /**
180      * A Reader wrapper for the incoming stream of bytes coming from the socket.
181      */

182     private BufferedReader inReader;
183
184     /**
185      * The remote host name obtained by lookup on the socket.
186      */

187     private String JavaDoc remoteHost;
188
189     /**
190      * The remote IP address of the socket.
191      */

192     private String JavaDoc remoteIP;
193
194     /**
195      * The user name of the authenticated user associated with this SMTP transaction.
196      */

197     private String JavaDoc authenticatedUser;
198
199     /**
200      * whether or not authorization is required for this connection
201      */

202     private boolean authRequired;
203
204     /**
205      * whether or not authorization is required for this connection
206      */

207     private boolean relayingAllowed;
208
209     /**
210      * The id associated with this particular SMTP interaction.
211      */

212     private String JavaDoc smtpID;
213
214     /**
215      * The per-service configuration data that applies to all handlers
216      */

217     private SMTPHandlerConfigurationData theConfigData;
218
219     /**
220      * The hash map that holds variables for the SMTP message transfer in progress.
221      *
222      * This hash map should only be used to store variable set in a particular
223      * set of sequential MAIL-RCPT-DATA commands, as described in RFC 2821. Per
224      * connection values should be stored as member variables in this class.
225      */

226     private HashMap state = new HashMap();
227
228     /**
229      * The watchdog being used by this handler to deal with idle timeouts.
230      */

231     Watchdog theWatchdog;
232
233     /**
234      * The watchdog target that idles out this handler.
235      */

236     WatchdogTarget theWatchdogTarget = new SMTPWatchdogTarget();
237
238     /**
239      * The per-handler response buffer used to marshal responses.
240      */

241     StringBuffer JavaDoc responseBuffer = new StringBuffer JavaDoc(256);
242
243     /**
244      * Set the configuration data for the handler
245      *
246      * @param theData the per-service configuration data for this handler
247      */

248     void setConfigurationData(SMTPHandlerConfigurationData theData) {
249         theConfigData = theData;
250     }
251
252     /**
253      * Set the Watchdog for use by this handler.
254      *
255      * @param theWatchdog the watchdog
256      */

257     void setWatchdog(Watchdog theWatchdog) {
258         this.theWatchdog = theWatchdog;
259     }
260
261     /**
262      * Gets the Watchdog Target that should be used by Watchdogs managing
263      * this connection.
264      *
265      * @return the WatchdogTarget
266      */

267     WatchdogTarget getWatchdogTarget() {
268         return theWatchdogTarget;
269     }
270
271     /**
272      * Idle out this connection
273      */

274     void idleClose() {
275         if (getLogger() != null) {
276             getLogger().error("SMTP Connection has idled out.");
277         }
278         try {
279             if (socket != null) {
280                 socket.close();
281             }
282         } catch (Exception JavaDoc e) {
283             // ignored
284
}
285
286         synchronized (this) {
287             // Interrupt the thread to recover from internal hangs
288
if (handlerThread != null) {
289                 handlerThread.interrupt();
290             }
291         }
292     }
293
294     /**
295      * @see org.apache.avalon.cornerstone.services.connection.ConnectionHandler#handleConnection(Socket)
296      */

297     public void handleConnection(Socket JavaDoc connection) throws IOException {
298
299         try {
300             this.socket = connection;
301             synchronized (this) {
302                 handlerThread = Thread.currentThread();
303             }
304             in = new BufferedInputStream(socket.getInputStream(), 1024);
305             // An ASCII encoding can be used because all transmissions other
306
// that those in the DATA command are guaranteed
307
// to be ASCII
308
// inReader = new BufferedReader(new InputStreamReader(in, "ASCII"), 512);
309
inReader = new CRLFTerminatedReader(in, "ASCII");
310             remoteIP = socket.getInetAddress().getHostAddress();
311             remoteHost = socket.getInetAddress().getHostName();
312             smtpID = random.nextInt(1024) + "";
313             relayingAllowed = theConfigData.isRelayingAllowed(remoteIP);
314             authRequired = theConfigData.isAuthRequired(remoteIP);
315             resetState();
316         } catch (Exception JavaDoc e) {
317             StringBuffer JavaDoc exceptionBuffer =
318                 new StringBuffer JavaDoc(256)
319                     .append("Cannot open connection from ")
320                     .append(remoteHost)
321                     .append(" (")
322                     .append(remoteIP)
323                     .append("): ")
324                     .append(e.getMessage());
325             String JavaDoc exceptionString = exceptionBuffer.toString();
326             getLogger().error(exceptionString, e );
327             throw new RuntimeException JavaDoc(exceptionString);
328         }
329
330         if (getLogger().isInfoEnabled()) {
331             StringBuffer JavaDoc infoBuffer =
332                 new StringBuffer JavaDoc(128)
333                         .append("Connection from ")
334                         .append(remoteHost)
335                         .append(" (")
336                         .append(remoteIP)
337                         .append(")");
338             getLogger().info(infoBuffer.toString());
339         }
340
341         try {
342
343             out = new InternetPrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()), 1024), false);
344
345             // Initially greet the connector
346
// Format is: Sat, 24 Jan 1998 13:16:09 -0500
347

348             responseBuffer.append("220 ")
349                           .append(theConfigData.getHelloName())
350                           .append(" SMTP Server (")
351                           .append(SOFTWARE_TYPE)
352                           .append(") ready ")
353                           .append(rfc822DateFormat.format(new Date()));
354             String JavaDoc responseString = clearResponseBuffer();
355             writeLoggedFlushedResponse(responseString);
356
357             theWatchdog.start();
358             while (parseCommand(readCommandLine())) {
359                 theWatchdog.reset();
360             }
361             theWatchdog.stop();
362             getLogger().debug("Closing socket.");
363         } catch (SocketException JavaDoc se) {
364             if (getLogger().isErrorEnabled()) {
365                 StringBuffer JavaDoc errorBuffer =
366                     new StringBuffer JavaDoc(64)
367                         .append("Socket to ")
368                         .append(remoteHost)
369                         .append(" (")
370                         .append(remoteIP)
371                         .append(") closed remotely.");
372                 getLogger().error(errorBuffer.toString(), se );
373             }
374         } catch ( InterruptedIOException iioe ) {
375             if (getLogger().isErrorEnabled()) {
376                 StringBuffer JavaDoc errorBuffer =
377                     new StringBuffer JavaDoc(64)
378                         .append("Socket to ")
379                         .append(remoteHost)
380                         .append(" (")
381                         .append(remoteIP)
382                         .append(") timeout.");
383                 getLogger().error( errorBuffer.toString(), iioe );
384             }
385         } catch ( IOException ioe ) {
386             if (getLogger().isErrorEnabled()) {
387                 StringBuffer JavaDoc errorBuffer =
388                     new StringBuffer JavaDoc(256)
389                             .append("Exception handling socket to ")
390                             .append(remoteHost)
391                             .append(" (")
392                             .append(remoteIP)
393                             .append(") : ")
394                             .append(ioe.getMessage());
395                 getLogger().error( errorBuffer.toString(), ioe );
396             }
397         } catch (Exception JavaDoc e) {
398             if (getLogger().isErrorEnabled()) {
399                 getLogger().error( "Exception opening socket: "
400                                    + e.getMessage(), e );
401             }
402         } finally {
403             resetHandler();
404         }
405     }
406
407     /**
408      * Resets the handler data to a basic state.
409      */

410     private void resetHandler() {
411         resetState();
412
413         clearResponseBuffer();
414         in = null;
415         inReader = null;
416         out = null;
417         remoteHost = null;
418         remoteIP = null;
419         authenticatedUser = null;
420         smtpID = null;
421
422         if (theWatchdog != null) {
423             if (theWatchdog instanceof Disposable) {
424                 ((Disposable)theWatchdog).dispose();
425             }
426             theWatchdog = null;
427         }
428
429         try {
430             if (socket != null) {
431                 socket.close();
432             }
433         } catch (IOException e) {
434             if (getLogger().isErrorEnabled()) {
435                 getLogger().error("Exception closing socket: "
436                                   + e.getMessage());
437             }
438         } finally {
439             socket = null;
440         }
441
442         synchronized (this) {
443             handlerThread = null;
444         }
445
446     }
447
448     /**
449      * Clears the response buffer, returning the String of characters in the buffer.
450      *
451      * @return the data in the response buffer
452      */

453     private String JavaDoc clearResponseBuffer() {
454         String JavaDoc responseString = responseBuffer.toString();
455         responseBuffer.delete(0,responseBuffer.length());
456         return responseString;
457     }
458
459     /**
460      * This method logs at a "DEBUG" level the response string that
461      * was sent to the SMTP client. The method is provided largely
462      * as syntactic sugar to neaten up the code base. It is declared
463      * private and final to encourage compiler inlining.
464      *
465      * @param responseString the response string sent to the client
466      */

467     private final void logResponseString(String JavaDoc responseString) {
468         if (getLogger().isDebugEnabled()) {
469             getLogger().debug("Sent: " + responseString);
470         }
471     }
472
473     /**
474      * Write and flush a response string. The response is also logged.
475      * Should be used for the last line of a multi-line response or
476      * for a single line response.
477      *
478      * @param responseString the response string sent to the client
479      */

480     final void writeLoggedFlushedResponse(String JavaDoc responseString) {
481         out.println(responseString);
482         out.flush();
483         logResponseString(responseString);
484     }
485
486     /**
487      * Write a response string. The response is also logged.
488      * Used for multi-line responses.
489      *
490      * @param responseString the response string sent to the client
491      */

492     final void writeLoggedResponse(String JavaDoc responseString) {
493         out.println(responseString);
494         logResponseString(responseString);
495     }
496
497     /**
498      * Reads a line of characters off the command line.
499      *
500      * @return the trimmed input line
501      * @throws IOException if an exception is generated reading in the input characters
502      */

503     final String JavaDoc readCommandLine() throws IOException {
504         for (;;) try {
505             String JavaDoc commandLine = inReader.readLine();
506             if (commandLine != null) {
507                 commandLine = commandLine.trim();
508             }
509             return commandLine;
510         } catch (CRLFTerminatedReader.TerminationException te) {
511             writeLoggedFlushedResponse("501 Syntax error at character position " + te.position() + ". CR and LF must be CRLF paired. See RFC 2821 #2.7.1.");
512         }
513     }
514
515     /**
516      * Sets the user name associated with this SMTP interaction.
517      *
518      * @param userID the user name
519      */

520     private void setUser(String JavaDoc userID) {
521         authenticatedUser = userID;
522     }
523
524     /**
525      * Returns the user name associated with this SMTP interaction.
526      *
527      * @return the user name
528      */

529     private String JavaDoc getUser() {
530         return authenticatedUser;
531     }
532
533     /**
534      * Resets message-specific, but not authenticated user, state.
535      *
536      */

537     private void resetState() {
538         ArrayList recipients = (ArrayList)state.get(RCPT_LIST);
539         if (recipients != null) {
540             recipients.clear();
541         }
542         state.clear();
543     }
544
545     /**
546      * This method parses SMTP commands read off the wire in handleConnection.
547      * Actual processing of the command (possibly including additional back and
548      * forth communication with the client) is delegated to one of a number of
549      * command specific handler methods. The primary purpose of this method is
550      * to parse the raw command string to determine exactly which handler should
551      * be called. It returns true if expecting additional commands, false otherwise.
552      *
553      * @param rawCommand the raw command string passed in over the socket
554      *
555      * @return whether additional commands are expected.
556      */

557     private boolean parseCommand(String JavaDoc command) throws Exception JavaDoc {
558         String JavaDoc argument = null;
559         boolean returnValue = true;
560         
561
562         if (command == null) {
563             return false;
564         }
565         if ((state.get(MESG_FAILED) == null) && (getLogger().isDebugEnabled())) {
566             getLogger().debug("Command received: " + command);
567         }
568         int spaceIndex = command.indexOf(" ");
569         if (spaceIndex > 0) {
570             argument = command.substring(spaceIndex + 1);
571             command = command.substring(0, spaceIndex);
572         }
573         command = command.toUpperCase(Locale.US);
574         if (command.equals(COMMAND_HELO)) {
575             doHELO(argument);
576         } else if (command.equals(COMMAND_EHLO)) {
577             doEHLO(argument);
578         } else if (command.equals(COMMAND_AUTH)) {
579             doAUTH(argument);
580         } else if (command.equals(COMMAND_MAIL)) {
581             doMAIL(argument);
582         } else if (command.equals(COMMAND_RCPT)) {
583             doRCPT(argument);
584         } else if (command.equals(COMMAND_NOOP)) {
585             doNOOP(argument);
586         } else if (command.equals(COMMAND_RSET)) {
587             doRSET(argument);
588         } else if (command.equals(COMMAND_DATA)) {
589             doDATA(argument);
590         } else if (command.equals(COMMAND_QUIT)) {
591             doQUIT(argument);
592             returnValue = false;
593         } else if (command.equals(COMMAND_VRFY)) {
594             doVRFY(argument);
595         } else if (command.equals(COMMAND_EXPN)) {
596             doEXPN(argument);
597         } else if (command.equals(COMMAND_HELP)) {
598             doHELP(argument);
599         } else {
600             if (state.get(MESG_FAILED) == null) {
601                 doUnknownCmd(command, argument);
602             }
603         }
604         return returnValue;
605     }
606
607     /**
608      * Handler method called upon receipt of a HELO command.
609      * Responds with a greeting and informs the client whether
610      * client authentication is required.
611      *
612      * @param argument the argument passed in with the command by the SMTP client
613      */

614     private void doHELO(String JavaDoc argument) {
615         String JavaDoc responseString = null;
616         if (argument == null) {
617             responseString = "501 Domain address required: " + COMMAND_HELO;
618             writeLoggedFlushedResponse(responseString);
619         } else {
620             resetState();
621             state.put(CURRENT_HELO_MODE, COMMAND_HELO);
622             if (authRequired) {
623                 //This is necessary because we're going to do a multiline response
624
responseBuffer.append("250-");
625             } else {
626                 responseBuffer.append("250 ");
627             }
628             responseBuffer.append(theConfigData.getHelloName())
629                           .append(" Hello ")
630                           .append(argument)
631                           .append(" (")
632                           .append(remoteHost)
633                           .append(" [")
634                           .append(remoteIP)
635                           .append("])");
636             responseString = clearResponseBuffer();
637             if (authRequired) {
638                 writeLoggedResponse(responseString);
639                 responseString = "250-AUTH LOGIN PLAIN";
640                 writeLoggedResponse(responseString);
641                 responseString = "250 AUTH=LOGIN PLAIN";
642             }
643             writeLoggedFlushedResponse(responseString);
644         }
645     }
646
647     /**
648      * Handler method called upon receipt of a EHLO command.
649      * Responds with a greeting and informs the client whether
650      * client authentication is required.
651      *
652      * @param argument the argument passed in with the command by the SMTP client
653      */

654     private void doEHLO(String JavaDoc argument) {
655         String JavaDoc responseString = null;
656         if (argument == null) {
657             responseString = "501 Domain address required: " + COMMAND_EHLO;
658             writeLoggedFlushedResponse(responseString);
659         } else {
660             resetState();
661             state.put(CURRENT_HELO_MODE, COMMAND_EHLO);
662             // Extension defined in RFC 1870
663
long maxMessageSize = theConfigData.getMaxMessageSize();
664             if (maxMessageSize > 0) {
665                 responseString = "250-SIZE " + maxMessageSize;
666                 writeLoggedResponse(responseString);
667             }
668             if (authRequired) {
669                 //This is necessary because we're going to do a multiline response
670
responseBuffer.append("250-");
671             } else {
672                 responseBuffer.append("250 ");
673             }
674             responseBuffer.append(theConfigData.getHelloName())
675                            .append(" Hello ")
676                            .append(argument)
677                            .append(" (")
678                            .append(remoteHost)
679                            .append(" [")
680                            .append(remoteIP)
681                            .append("])");
682             responseString = clearResponseBuffer();
683             if (authRequired) {
684                 writeLoggedResponse(responseString);
685                 responseString = "250-AUTH LOGIN PLAIN";
686                 writeLoggedResponse(responseString);
687                 responseString = "250 AUTH=LOGIN PLAIN";
688             }
689             writeLoggedFlushedResponse(responseString);
690         }
691     }
692
693     /**
694      * Handler method called upon receipt of a AUTH command.
695      * Handles client authentication to the SMTP server.
696      *
697      * @param argument the argument passed in with the command by the SMTP client
698      */

699     private void doAUTH(String JavaDoc argument)
700             throws Exception JavaDoc {
701         String JavaDoc responseString = null;
702         if (getUser() != null) {
703             responseString = "503 User has previously authenticated. "
704                         + " Further authentication is not required!";
705             writeLoggedFlushedResponse(responseString);
706         } else if (argument == null) {
707             responseString = "501 Usage: AUTH (authentication type) <challenge>";
708             writeLoggedFlushedResponse(responseString);
709         } else {
710             String JavaDoc initialResponse = null;
711             if ((argument != null) && (argument.indexOf(" ") > 0)) {
712                 initialResponse = argument.substring(argument.indexOf(" ") + 1);
713                 argument = argument.substring(0,argument.indexOf(" "));
714             }
715             String JavaDoc authType = argument.toUpperCase(Locale.US);
716             if (authType.equals(AUTH_TYPE_PLAIN)) {
717                 doPlainAuth(initialResponse);
718                 return;
719             } else if (authType.equals(AUTH_TYPE_LOGIN)) {
720                 doLoginAuth(initialResponse);
721                 return;
722             } else {
723                 doUnknownAuth(authType, initialResponse);
724                 return;
725             }
726         }
727     }
728
729     /**
730      * Carries out the Plain AUTH SASL exchange.
731      *
732      * According to RFC 2595 the client must send: [authorize-id] \0 authenticate-id \0 password.
733      *
734      * >>> AUTH PLAIN dGVzdAB0ZXN0QHdpei5leGFtcGxlLmNvbQB0RXN0NDI=
735      * Decoded: test\000test@wiz.example.com\000tEst42
736      *
737      * >>> AUTH PLAIN dGVzdAB0ZXN0AHRFc3Q0Mg==
738      * Decoded: test\000test\000tEst42
739      *
740      * @param initialResponse the initial response line passed in with the AUTH command
741      */

742     private void doPlainAuth(String JavaDoc initialResponse)
743             throws IOException {
744         String JavaDoc userpass = null, user = null, pass = null, responseString = null;
745         if (initialResponse == null) {
746             responseString = "334 OK. Continue authentication";
747             writeLoggedFlushedResponse(responseString);
748             userpass = readCommandLine();
749         } else {
750             userpass = initialResponse.trim();
751         }
752         try {
753             if (userpass != null) {
754                 userpass = Base64.decodeAsString(userpass);
755             }
756             if (userpass != null) {
757                 /* See: RFC 2595, Section 6
758                     The mechanism consists of a single message from the client to the
759                     server. The client sends the authorization identity (identity to
760                     login as), followed by a US-ASCII NUL character, followed by the
761                     authentication identity (identity whose password will be used),
762                     followed by a US-ASCII NUL character, followed by the clear-text
763                     password. The client may leave the authorization identity empty to
764                     indicate that it is the same as the authentication identity.
765
766                     The server will verify the authentication identity and password with
767                     the system authentication database and verify that the authentication
768                     credentials permit the client to login as the authorization identity.
769                     If both steps succeed, the user is logged in.
770                 */

771                 StringTokenizer authTokenizer = new StringTokenizer(userpass, "\0");
772                 String JavaDoc authorize_id = authTokenizer.nextToken(); // Authorization Identity
773
user = authTokenizer.nextToken(); // Authentication Identity
774
try {
775                     pass = authTokenizer.nextToken(); // Password
776
}
777                 catch (java.util.NoSuchElementException JavaDoc _) {
778                     // If we got here, this is what happened. RFC 2595
779
// says that "the client may leave the authorization
780
// identity empty to indicate that it is the same as
781
// the authentication identity." As noted above,
782
// that would be represented as a decoded string of
783
// the form: "\0authenticate-id\0password". The
784
// first call to nextToken will skip the empty
785
// authorize-id, and give us the authenticate-id,
786
// which we would store as the authorize-id. The
787
// second call will give us the password, which we
788
// think is the authenticate-id (user). Then when
789
// we ask for the password, there are no more
790
// elements, leading to the exception we just
791
// caught. So we need to move the user to the
792
// password, and the authorize_id to the user.
793
pass = user;
794                     user = authorize_id;
795                 }
796
797                 authTokenizer = null;
798             }
799         }
800         catch (Exception JavaDoc e) {
801             // Ignored - this exception in parsing will be dealt
802
// with in the if clause below
803
}
804         // Authenticate user
805
if ((user == null) || (pass == null)) {
806             responseString = "501 Could not decode parameters for AUTH PLAIN";
807             writeLoggedFlushedResponse(responseString);
808         } else if (theConfigData.getUsersRepository().test(user, pass)) {
809             setUser(user);
810             responseString = "235 Authentication Successful";
811             writeLoggedFlushedResponse(responseString);
812             getLogger().info("AUTH method PLAIN succeeded");
813         } else {
814             responseString = "535 Authentication Failed";
815             writeLoggedFlushedResponse(responseString);
816             getLogger().error("AUTH method PLAIN failed");
817         }
818         return;
819     }
820
821     /**
822      * Carries out the Login AUTH SASL exchange.
823      *
824      * @param initialResponse the initial response line passed in with the AUTH command
825      */

826     private void doLoginAuth(String JavaDoc initialResponse)
827             throws IOException {
828         String JavaDoc user = null, pass = null, responseString = null;
829         if (initialResponse == null) {
830             responseString = "334 VXNlcm5hbWU6"; // base64 encoded "Username:"
831
writeLoggedFlushedResponse(responseString);
832             user = readCommandLine();
833         } else {
834             user = initialResponse.trim();
835         }
836         if (user != null) {
837             try {
838                 user = Base64.decodeAsString(user);
839             } catch (Exception JavaDoc e) {
840                 // Ignored - this parse error will be
841
// addressed in the if clause below
842
user = null;
843             }
844         }
845         responseString = "334 UGFzc3dvcmQ6"; // base64 encoded "Password:"
846
writeLoggedFlushedResponse(responseString);
847         pass = readCommandLine();
848         if (pass != null) {
849             try {
850                 pass = Base64.decodeAsString(pass);
851             } catch (Exception JavaDoc e) {
852                 // Ignored - this parse error will be
853
// addressed in the if clause below
854
pass = null;
855             }
856         }
857         // Authenticate user
858
if ((user == null) || (pass == null)) {
859             responseString = "501 Could not decode parameters for AUTH LOGIN";
860         } else if (theConfigData.getUsersRepository().test(user, pass)) {
861             setUser(user);
862             responseString = "235 Authentication Successful";
863             if (getLogger().isDebugEnabled()) {
864                 // TODO: Make this string a more useful debug message
865
getLogger().debug("AUTH method LOGIN succeeded");
866             }
867         } else {
868             responseString = "535 Authentication Failed";
869             // TODO: Make this string a more useful error message
870
getLogger().error("AUTH method LOGIN failed");
871         }
872         writeLoggedFlushedResponse(responseString);
873         return;
874     }
875
876     /**
877      * Handles the case of an unrecognized auth type.
878      *
879      * @param authType the unknown auth type
880      * @param initialResponse the initial response line passed in with the AUTH command
881      */

882     private void doUnknownAuth(String JavaDoc authType, String JavaDoc initialResponse) {
883         String JavaDoc responseString = "504 Unrecognized Authentication Type";
884         writeLoggedFlushedResponse(responseString);
885         if (getLogger().isErrorEnabled()) {
886             StringBuffer JavaDoc errorBuffer =
887                 new StringBuffer JavaDoc(128)
888                     .append("AUTH method ")
889                         .append(authType)
890                         .append(" is an unrecognized authentication type");
891             getLogger().error(errorBuffer.toString());
892         }
893         return;
894     }
895
896     /**
897      * Handler method called upon receipt of a MAIL command.
898      * Sets up handler to deliver mail as the stated sender.
899      *
900      * @param argument the argument passed in with the command by the SMTP client
901      */

902     private void doMAIL(String JavaDoc argument) {
903         String JavaDoc responseString = null;
904
905         String JavaDoc sender = null;
906         if ((argument != null) && (argument.indexOf(":") > 0)) {
907             int colonIndex = argument.indexOf(":");
908             sender = argument.substring(colonIndex + 1);
909             argument = argument.substring(0, colonIndex);
910         }
911         if (state.containsKey(SENDER)) {
912             responseString = "503 Sender already specified";
913             writeLoggedFlushedResponse(responseString);
914         } else if (argument == null || !argument.toUpperCase(Locale.US).equals("FROM")
915                    || sender == null) {
916             responseString = "501 Usage: MAIL FROM:<sender>";
917             writeLoggedFlushedResponse(responseString);
918         } else {
919             sender = sender.trim();
920             // the next gt after the first lt ... AUTH may add more <>
921
int lastChar = sender.indexOf('>', sender.indexOf('<'));
922             // Check to see if any options are present and, if so, whether they are correctly formatted
923
// (separated from the closing angle bracket by a ' ').
924
if ((lastChar > 0) && (sender.length() > lastChar + 2) && (sender.charAt(lastChar + 1) == ' ')) {
925                 String JavaDoc mailOptionString = sender.substring(lastChar + 2);
926
927                 // Remove the options from the sender
928
sender = sender.substring(0, lastChar + 1);
929
930                 StringTokenizer optionTokenizer = new StringTokenizer(mailOptionString, " ");
931                 while (optionTokenizer.hasMoreElements()) {
932                     String JavaDoc mailOption = optionTokenizer.nextToken();
933                     int equalIndex = mailOptionString.indexOf('=');
934                     String JavaDoc mailOptionName = mailOption;
935                     String JavaDoc mailOptionValue = "";
936                     if (equalIndex > 0) {
937                         mailOptionName = mailOption.substring(0, equalIndex).toUpperCase(Locale.US);
938                         mailOptionValue = mailOption.substring(equalIndex + 1);
939                     }
940
941                     // Handle the SIZE extension keyword
942

943                     if (mailOptionName.startsWith(MAIL_OPTION_SIZE)) {
944                         if (!(doMailSize(mailOptionValue))) {
945                             return;
946                         }
947                     } else {
948                         // Unexpected option attached to the Mail command
949
if (getLogger().isDebugEnabled()) {
950                             StringBuffer JavaDoc debugBuffer =
951                                 new StringBuffer JavaDoc(128)
952                                     .append("MAIL command had unrecognized/unexpected option ")
953                                     .append(mailOptionName)
954                                     .append(" with value ")
955                                     .append(mailOptionValue);
956                             getLogger().debug(debugBuffer.toString());
957                         }
958                     }
959                 }
960             }
961             if (!sender.startsWith("<") || !sender.endsWith(">")) {
962                 responseString = "501 Syntax error in MAIL command";
963                 writeLoggedFlushedResponse(responseString);
964                 if (getLogger().isErrorEnabled()) {
965                     StringBuffer JavaDoc errorBuffer =
966                         new StringBuffer JavaDoc(128)
967                             .append("Error parsing sender address: ")
968                             .append(sender)
969                             .append(": did not start and end with < >");
970                     getLogger().error(errorBuffer.toString());
971                 }
972                 return;
973             }
974             MailAddress senderAddress = null;
975             //Remove < and >
976
sender = sender.substring(1, sender.length() - 1);
977             if (sender.length() == 0) {
978                 //This is the <> case. Let senderAddress == null
979
} else {
980                 if (sender.indexOf("@") < 0) {
981                     sender = sender + "@localhost";
982                 }
983                 try {
984                     senderAddress = new MailAddress(sender);
985                 } catch (Exception JavaDoc pe) {
986                     responseString = "501 Syntax error in sender address";
987                     writeLoggedFlushedResponse(responseString);
988                     if (getLogger().isErrorEnabled()) {
989                         StringBuffer JavaDoc errorBuffer =
990                             new StringBuffer JavaDoc(256)
991                                     .append("Error parsing sender address: ")
992                                     .append(sender)
993                                     .append(": ")
994                                     .append(pe.getMessage());
995                         getLogger().error(errorBuffer.toString());
996                     }
997                     return;
998                 }
999             }
1000            state.put(SENDER, senderAddress);
1001            responseBuffer.append("250 Sender <")
1002                          .append(sender)
1003                          .append("> OK");
1004            responseString = clearResponseBuffer();
1005            writeLoggedFlushedResponse(responseString);
1006        }
1007    }
1008
1009    /**
1010     * Handles the SIZE MAIL option.
1011     *
1012     * @param mailOptionValue the option string passed in with the SIZE option
1013     * @return true if further options should be processed, false otherwise
1014     */

1015    private boolean doMailSize(String JavaDoc mailOptionValue) {
1016        int size = 0;
1017        try {
1018            size = Integer.parseInt(mailOptionValue);
1019        } catch (NumberFormatException JavaDoc pe) {
1020            // This is a malformed option value. We return an error
1021
String JavaDoc responseString = "501 Syntactically incorrect value for SIZE parameter";
1022            writeLoggedFlushedResponse(responseString);
1023            getLogger().error("Rejected syntactically incorrect value for SIZE parameter.");
1024            return false;
1025        }
1026        if (getLogger().isDebugEnabled()) {
1027            StringBuffer JavaDoc debugBuffer =
1028                new StringBuffer JavaDoc(128)
1029                    .append("MAIL command option SIZE received with value ")
1030                    .append(size)
1031                    .append(".");
1032                    getLogger().debug(debugBuffer.toString());
1033        }
1034        long maxMessageSize = theConfigData.getMaxMessageSize();
1035        if ((maxMessageSize > 0) && (size > maxMessageSize)) {
1036            // Let the client know that the size limit has been hit.
1037
String JavaDoc responseString = "552 Message size exceeds fixed maximum message size";
1038            writeLoggedFlushedResponse(responseString);
1039            StringBuffer JavaDoc errorBuffer =
1040                new StringBuffer JavaDoc(256)
1041                    .append("Rejected message from ")
1042                    .append(state.get(SENDER).toString())
1043                    .append(" from host ")
1044                    .append(remoteHost)
1045                    .append(" (")
1046                    .append(remoteIP)
1047                    .append(") of size ")
1048                    .append(size)
1049                    .append(" exceeding system maximum message size of ")
1050                    .append(maxMessageSize)
1051                    .append("based on SIZE option.");
1052            getLogger().error(errorBuffer.toString());
1053            return false;
1054        } else {
1055            // put the message size in the message state so it can be used
1056
// later to restrict messages for user quotas, etc.
1057
state.put(MESG_SIZE, new Integer JavaDoc(size));
1058        }
1059        return true;
1060    }
1061
1062    /**
1063     * Handler method called upon receipt of a RCPT command.
1064     * Reads recipient. Does some connection validation.
1065     *
1066     * @param argument the argument passed in with the command by the SMTP client
1067     */

1068    private void doRCPT(String JavaDoc argument) {
1069        String JavaDoc responseString = null;
1070
1071        String JavaDoc recipient = null;
1072        if ((argument != null) && (argument.indexOf(":") > 0)) {
1073            int colonIndex = argument.indexOf(":");
1074            recipient = argument.substring(colonIndex + 1);
1075            argument = argument.substring(0, colonIndex);
1076        }
1077        if (!state.containsKey(SENDER)) {
1078            responseString = "503 Need MAIL before RCPT";
1079            writeLoggedFlushedResponse(responseString);
1080        } else if (argument == null || !argument.toUpperCase(Locale.US).equals("TO")
1081                   || recipient == null) {
1082            responseString = "501 Usage: RCPT TO:<recipient>";
1083            writeLoggedFlushedResponse(responseString);
1084        } else {
1085            Collection rcptColl = (Collection) state.get(RCPT_LIST);
1086            if (rcptColl == null) {
1087                rcptColl = new ArrayList();
1088            }
1089            recipient = recipient.trim();
1090            int lastChar = recipient.lastIndexOf('>');
1091            // Check to see if any options are present and, if so, whether they are correctly formatted
1092
// (separated from the closing angle bracket by a ' ').
1093
if ((lastChar > 0) && (recipient.length() > lastChar + 2) && (recipient.charAt(lastChar + 1) == ' ')) {
1094                String JavaDoc rcptOptionString = recipient.substring(lastChar + 2);
1095
1096                // Remove the options from the recipient
1097
recipient = recipient.substring(0, lastChar + 1);
1098
1099                StringTokenizer optionTokenizer = new StringTokenizer(rcptOptionString, " ");
1100                while (optionTokenizer.hasMoreElements()) {
1101                    String JavaDoc rcptOption = optionTokenizer.nextToken();
1102                    int equalIndex = rcptOptionString.indexOf('=');
1103                    String JavaDoc rcptOptionName = rcptOption;
1104                    String JavaDoc rcptOptionValue = "";
1105                    if (equalIndex > 0) {
1106                        rcptOptionName = rcptOption.substring(0, equalIndex).toUpperCase(Locale.US);
1107                        rcptOptionValue = rcptOption.substring(equalIndex + 1);
1108                    }
1109                    // Unexpected option attached to the RCPT command
1110
if (getLogger().isDebugEnabled()) {
1111                        StringBuffer JavaDoc debugBuffer =
1112                            new StringBuffer JavaDoc(128)
1113                                .append("RCPT command had unrecognized/unexpected option ")
1114                                .append(rcptOptionName)
1115                                .append(" with value ")
1116                                .append(rcptOptionValue);
1117                        getLogger().debug(debugBuffer.toString());
1118                    }
1119                }
1120                optionTokenizer = null;
1121            }
1122            if (!recipient.startsWith("<") || !recipient.endsWith(">")) {
1123                responseString = "501 Syntax error in parameters or arguments";
1124                writeLoggedFlushedResponse(responseString);
1125                if (getLogger().isErrorEnabled()) {
1126                    StringBuffer JavaDoc errorBuffer =
1127                        new StringBuffer JavaDoc(192)
1128                                .append("Error parsing recipient address: ")
1129                                .append(recipient)
1130                                .append(": did not start and end with < >");
1131                    getLogger().error(errorBuffer.toString());
1132                }
1133                return;
1134            }
1135            MailAddress recipientAddress = null;
1136            //Remove < and >
1137
recipient = recipient.substring(1, recipient.length() - 1);
1138            if (recipient.indexOf("@") < 0) {
1139                recipient = recipient + "@localhost";
1140            }
1141            try {
1142                recipientAddress = new MailAddress(recipient);
1143            } catch (Exception JavaDoc pe) {
1144                responseString = "501 Syntax error in recipient address";
1145                writeLoggedFlushedResponse(responseString);
1146
1147                if (getLogger().isErrorEnabled()) {
1148                    StringBuffer JavaDoc errorBuffer =
1149                        new StringBuffer JavaDoc(192)
1150                                .append("Error parsing recipient address: ")
1151                                .append(recipient)
1152                                .append(": ")
1153                                .append(pe.getMessage());
1154                    getLogger().error(errorBuffer.toString());
1155                }
1156                return;
1157            }
1158            if (authRequired) {
1159                // Make sure the mail is being sent locally if not
1160
// authenticated else reject.
1161
if (getUser() == null) {
1162                    String JavaDoc toDomain = recipientAddress.getHost();
1163                    if (!theConfigData.getMailServer().isLocalServer(toDomain)) {
1164                        responseString = "530 Authentication Required";
1165                        writeLoggedFlushedResponse(responseString);
1166                        getLogger().error("Rejected message - authentication is required for mail request");
1167                        return;
1168                    }
1169                } else {
1170                    // Identity verification checking
1171
if (theConfigData.isVerifyIdentity()) {
1172                        String JavaDoc authUser = (getUser()).toLowerCase(Locale.US);
1173                        MailAddress senderAddress = (MailAddress) state.get(SENDER);
1174                        boolean domainExists = false;
1175
1176                        if ((!authUser.equals(senderAddress.getUser())) ||
1177                            (!theConfigData.getMailServer().isLocalServer(senderAddress.getHost()))) {
1178                            responseString = "503 Incorrect Authentication for Specified Email Address";
1179                            writeLoggedFlushedResponse(responseString);
1180                            if (getLogger().isErrorEnabled()) {
1181                                StringBuffer JavaDoc errorBuffer =
1182                                    new StringBuffer JavaDoc(128)
1183                                        .append("User ")
1184                                        .append(authUser)
1185                                        .append(" authenticated, however tried sending email as ")
1186                                        .append(senderAddress);
1187                                getLogger().error(errorBuffer.toString());
1188                            }
1189                            return;
1190                        }
1191                    }
1192                }
1193            } else if (!relayingAllowed) {
1194                String JavaDoc toDomain = recipientAddress.getHost();
1195                if (!theConfigData.getMailServer().isLocalServer(toDomain)) {
1196                    responseString = "550 - Requested action not taken: relaying denied";
1197                    writeLoggedFlushedResponse(responseString);
1198                    getLogger().error("Rejected message - " + remoteIP + " not authorized to relay to " + toDomain);
1199                    return;
1200                }
1201            }
1202            rcptColl.add(recipientAddress);
1203            state.put(RCPT_LIST, rcptColl);
1204            responseBuffer.append("250 Recipient <")
1205                          .append(recipient)
1206                          .append("> OK");
1207            responseString = clearResponseBuffer();
1208            writeLoggedFlushedResponse(responseString);
1209        }
1210    }
1211
1212    /**
1213     * Handler method called upon receipt of a NOOP command.
1214     * Just sends back an OK and logs the command.
1215     *
1216     * @param argument the argument passed in with the command by the SMTP client
1217     */

1218    private void doNOOP(String JavaDoc argument) {
1219        String JavaDoc responseString = "250 OK";
1220        writeLoggedFlushedResponse(responseString);
1221    }
1222
1223    /**
1224     * Handler method called upon receipt of a RSET command.
1225     * Resets message-specific, but not authenticated user, state.
1226     *
1227     * @param argument the argument passed in with the command by the SMTP client
1228     */

1229    private void doRSET(String JavaDoc argument) {
1230        String JavaDoc responseString = "";
1231        if ((argument == null) || (argument.length() == 0)) {
1232            responseString = "250 OK";
1233            resetState();
1234        } else {
1235            responseString = "500 Unexpected argument provided with RSET command";
1236        }
1237        writeLoggedFlushedResponse(responseString);
1238    }
1239
1240    /**
1241     * Handler method called upon receipt of a DATA command.
1242     * Reads in message data, creates header, and delivers to
1243     * mail server service for delivery.
1244     *
1245     * @param argument the argument passed in with the command by the SMTP client
1246     */

1247    private void doDATA(String JavaDoc argument) {
1248        String JavaDoc responseString = null;
1249        if ((argument != null) && (argument.length() > 0)) {
1250            responseString = "500 Unexpected argument provided with DATA command";
1251            writeLoggedFlushedResponse(responseString);
1252        }
1253        if (!state.containsKey(SENDER)) {
1254            responseString = "503 No sender specified";
1255            writeLoggedFlushedResponse(responseString);
1256        } else if (!state.containsKey(RCPT_LIST)) {
1257            responseString = "503 No recipients specified";
1258            writeLoggedFlushedResponse(responseString);
1259        } else {
1260            responseString = "354 Ok Send data ending with <CRLF>.<CRLF>";
1261            writeLoggedFlushedResponse(responseString);
1262            InputStream msgIn = new CharTerminatedInputStream(in, SMTPTerminator);
1263            try {
1264                msgIn = new BytesReadResetInputStream(msgIn,
1265                                                      theWatchdog,
1266                                                      theConfigData.getResetLength());
1267
1268                // if the message size limit has been set, we'll
1269
// wrap msgIn with a SizeLimitedInputStream
1270
long maxMessageSize = theConfigData.getMaxMessageSize();
1271                if (maxMessageSize > 0) {
1272                    if (getLogger().isDebugEnabled()) {
1273                        StringBuffer JavaDoc logBuffer =
1274                            new StringBuffer JavaDoc(128)
1275                                    .append("Using SizeLimitedInputStream ")
1276                                    .append(" with max message size: ")
1277                                    .append(maxMessageSize);
1278                        getLogger().debug(logBuffer.toString());
1279                    }
1280                    msgIn = new SizeLimitedInputStream(msgIn, maxMessageSize);
1281                }
1282                // Removes the dot stuffing
1283
msgIn = new DotStuffingInputStream(msgIn);
1284                // Parse out the message headers
1285
MailHeaders headers = new MailHeaders(msgIn);
1286                headers = processMailHeaders(headers);
1287                processMail(headers, msgIn);
1288                headers = null;
1289            } catch (MessagingException JavaDoc me) {
1290                // Grab any exception attached to this one.
1291
Exception JavaDoc e = me.getNextException();
1292                // If there was an attached exception, and it's a
1293
// MessageSizeException
1294
if (e != null && e instanceof MessageSizeException) {
1295                    // Add an item to the state to suppress
1296
// logging of extra lines of data
1297
// that are sent after the size limit has
1298
// been hit.
1299
state.put(MESG_FAILED, Boolean.TRUE);
1300                    // then let the client know that the size
1301
// limit has been hit.
1302
responseString = "552 Error processing message: "
1303                                + e.getMessage();
1304                    StringBuffer JavaDoc errorBuffer =
1305                        new StringBuffer JavaDoc(256)
1306                            .append("Rejected message from ")
1307                            .append(state.get(SENDER).toString())
1308                            .append(" from host ")
1309                            .append(remoteHost)
1310                            .append(" (")
1311                            .append(remoteIP)
1312                            .append(") exceeding system maximum message size of ")
1313                            .append(theConfigData.getMaxMessageSize());
1314                    getLogger().error(errorBuffer.toString());
1315                } else {
1316                    responseString = "451 Error processing message: "
1317                                + me.getMessage();
1318                    getLogger().error("Unknown error occurred while processing DATA.", me);
1319                }
1320                writeLoggedFlushedResponse(responseString);
1321                return;
1322            } finally {
1323                if (msgIn != null) {
1324                    try {
1325                        msgIn.close();
1326                    } catch (Exception JavaDoc e) {
1327                        // Ignore close exception
1328
}
1329                    msgIn = null;
1330                }
1331            }
1332            resetState();
1333            responseString = "250 Message received";
1334            writeLoggedFlushedResponse(responseString);
1335        }
1336    }
1337
1338    private MailHeaders processMailHeaders(MailHeaders headers)
1339        throws MessagingException JavaDoc {
1340        // If headers do not contains minimum REQUIRED headers fields,
1341
// add them
1342
if (!headers.isSet(RFC2822Headers.DATE)) {
1343            headers.setHeader(RFC2822Headers.DATE, rfc822DateFormat.format(new Date()));
1344        }
1345        if (!headers.isSet(RFC2822Headers.FROM) && state.get(SENDER) != null) {
1346            headers.setHeader(RFC2822Headers.FROM, state.get(SENDER).toString());
1347        }
1348        // Determine the Return-Path
1349
String JavaDoc returnPath = headers.getHeader(RFC2822Headers.RETURN_PATH, "\r\n");
1350        headers.removeHeader(RFC2822Headers.RETURN_PATH);
1351        StringBuffer JavaDoc headerLineBuffer = new StringBuffer JavaDoc(512);
1352        if (returnPath == null) {
1353            if (state.get(SENDER) == null) {
1354                returnPath = "<>";
1355            } else {
1356                headerLineBuffer.append("<")
1357                                .append(state.get(SENDER))
1358                                .append(">");
1359                returnPath = headerLineBuffer.toString();
1360                headerLineBuffer.delete(0, headerLineBuffer.length());
1361            }
1362        }
1363        // We will rebuild the header object to put Return-Path and our
1364
// Received header at the top
1365
Enumeration headerLines = headers.getAllHeaderLines();
1366        MailHeaders newHeaders = new MailHeaders();
1367        // Put the Return-Path first
1368
// JAMES-281 fix for messages that improperly have multiple
1369
// Return-Path headers
1370
StringTokenizer tokenizer = new StringTokenizer(returnPath, "\r\n");
1371        while(tokenizer.hasMoreTokens()) {
1372            String JavaDoc path = tokenizer.nextToken();
1373            newHeaders.addHeaderLine(RFC2822Headers.RETURN_PATH + ": " + path);
1374        }
1375
1376        // Put our Received header next
1377
headerLineBuffer.append(RFC2822Headers.RECEIVED + ": from ")
1378                        .append(remoteHost)
1379                        .append(" ([")
1380                        .append(remoteIP)
1381                        .append("])");
1382
1383        newHeaders.addHeaderLine(headerLineBuffer.toString());
1384        headerLineBuffer.delete(0, headerLineBuffer.length());
1385
1386        headerLineBuffer.append(" by ")
1387                        .append(theConfigData.getHelloName())
1388                        .append(" (")
1389                        .append(SOFTWARE_TYPE)
1390                        .append(") with SMTP ID ")
1391                        .append(smtpID);
1392
1393        if (((Collection) state.get(RCPT_LIST)).size() == 1) {
1394            // Only indicate a recipient if they're the only recipient
1395
// (prevents email address harvesting and large headers in
1396
// bulk email)
1397
newHeaders.addHeaderLine(headerLineBuffer.toString());
1398            headerLineBuffer.delete(0, headerLineBuffer.length());
1399            headerLineBuffer.append(" for <")
1400                            .append(((List) state.get(RCPT_LIST)).get(0).toString())
1401                            .append(">;");
1402            newHeaders.addHeaderLine(headerLineBuffer.toString());
1403            headerLineBuffer.delete(0, headerLineBuffer.length());
1404        } else {
1405            // Put the ; on the end of the 'by' line
1406
headerLineBuffer.append(";");
1407            newHeaders.addHeaderLine(headerLineBuffer.toString());
1408            headerLineBuffer.delete(0, headerLineBuffer.length());
1409        }
1410        headerLineBuffer = null;
1411        newHeaders.addHeaderLine(" " + rfc822DateFormat.format(new Date()));
1412
1413        // Add all the original message headers back in next
1414
while (headerLines.hasMoreElements()) {
1415            newHeaders.addHeaderLine((String JavaDoc) headerLines.nextElement());
1416        }
1417        return newHeaders;
1418    }
1419
1420    /**
1421     * Processes the mail message coming in off the wire. Reads the
1422     * content and delivers to the spool.
1423     *
1424     * @param headers the headers of the mail being read
1425     * @param msgIn the stream containing the message content
1426     */

1427    private void processMail(MailHeaders headers, InputStream msgIn)
1428        throws MessagingException JavaDoc {
1429        ByteArrayInputStream headersIn = null;
1430        MailImpl mail = null;
1431        List recipientCollection = null;
1432        try {
1433            headersIn = new ByteArrayInputStream(headers.toByteArray());
1434            recipientCollection = (List) state.get(RCPT_LIST);
1435            mail =
1436                new MailImpl(theConfigData.getMailServer().getId(),
1437                             (MailAddress) state.get(SENDER),
1438                             recipientCollection,
1439                             new SequenceInputStream(headersIn, msgIn));
1440            // Call mail.getSize() to force the message to be
1441
// loaded. Need to do this to enforce the size limit
1442
if (theConfigData.getMaxMessageSize() > 0) {
1443                mail.getMessageSize();
1444            }
1445            mail.setRemoteHost(remoteHost);
1446            mail.setRemoteAddr(remoteIP);
1447            if (getUser() != null) {
1448                mail.setAttribute(SMTP_AUTH_USER_ATTRIBUTE_NAME, getUser());
1449            }
1450            theConfigData.getMailServer().sendMail(mail);
1451            Collection theRecipients = mail.getRecipients();
1452            String JavaDoc recipientString = "";
1453            if (theRecipients != null) {
1454                recipientString = theRecipients.toString();
1455            }
1456            if (getLogger().isInfoEnabled()) {
1457                StringBuffer JavaDoc infoBuffer =
1458                    new StringBuffer JavaDoc(256)
1459                        .append("Successfully spooled mail ")
1460                        .append(mail.getName())
1461                        .append(" from ")
1462                        .append(mail.getSender())
1463                        .append(" for ")
1464                        .append(recipientString);
1465                getLogger().info(infoBuffer.toString());
1466            }
1467        } finally {
1468            if (recipientCollection != null) {
1469                recipientCollection.clear();
1470            }
1471            recipientCollection = null;
1472            if (mail != null) {
1473                mail.dispose();
1474            }
1475            mail = null;
1476            if (headersIn != null) {
1477                try {
1478                    headersIn.close();
1479                } catch (IOException ioe) {
1480                    // Ignore exception on close.
1481
}
1482            }
1483            headersIn = null;
1484        }
1485    }
1486
1487    /**
1488     * Handler method called upon receipt of a QUIT command.
1489     * This method informs the client that the connection is
1490     * closing.
1491     *
1492     * @param argument the argument passed in with the command by the SMTP client
1493     */

1494    private void doQUIT(String JavaDoc argument) {
1495
1496        String JavaDoc responseString = "";
1497        if ((argument == null) || (argument.length() == 0)) {
1498            responseBuffer.append("221 ")
1499                          .append(theConfigData.getHelloName())
1500                          .append(" Service closing transmission channel");
1501            responseString = clearResponseBuffer();
1502        } else {
1503            responseString = "500 Unexpected argument provided with QUIT command";
1504        }
1505        writeLoggedFlushedResponse(responseString);
1506    }
1507
1508    /**
1509     * Handler method called upon receipt of a VRFY command.
1510     * This method informs the client that the command is
1511     * not implemented.
1512     *
1513     * @param argument the argument passed in with the command by the SMTP client
1514     */

1515    private void doVRFY(String JavaDoc argument) {
1516        String JavaDoc responseString = "502 VRFY is not supported";
1517        writeLoggedFlushedResponse(responseString);
1518    }
1519
1520    /**
1521     * Handler method called upon receipt of a EXPN command.
1522     * This method informs the client that the command is
1523     * not implemented.
1524     *
1525     * @param argument the argument passed in with the command by the SMTP client
1526     */

1527    private void doEXPN(String JavaDoc argument) {
1528
1529        String JavaDoc responseString = "502 EXPN is not supported";
1530        writeLoggedFlushedResponse(responseString);
1531    }
1532
1533    /**
1534     * Handler method called upon receipt of a HELP command.
1535     * This method informs the client that the command is
1536     * not implemented.
1537     *
1538     * @param argument the argument passed in with the command by the SMTP client
1539     */

1540    private void doHELP(String JavaDoc argument) {
1541
1542        String JavaDoc responseString = "502 HELP is not supported";
1543        writeLoggedFlushedResponse(responseString);
1544    }
1545
1546    /**
1547     * Handler method called upon receipt of an unrecognized command.
1548     * Returns an error response and logs the command.
1549     *
1550     * @param command the command parsed by the SMTP client
1551     * @param argument the argument passed in with the command by the SMTP client
1552     */

1553    private void doUnknownCmd(String JavaDoc command, String JavaDoc argument) {
1554        responseBuffer.append("500 ")
1555                      .append(theConfigData.getHelloName())
1556                      .append(" Syntax error, command unrecognized: ")
1557                      .append(command);
1558        String JavaDoc responseString = clearResponseBuffer();
1559        writeLoggedFlushedResponse(responseString);
1560    }
1561
1562    /**
1563     * A private inner class which serves as an adaptor
1564     * between the WatchdogTarget interface and this
1565     * handler class.
1566     */

1567    private class SMTPWatchdogTarget
1568        implements WatchdogTarget {
1569
1570        /**
1571         * @see org.apache.james.util.watchdog.WatchdogTarget#execute()
1572         */

1573        public void execute() {
1574            SMTPHandler.this.idleClose();
1575        }
1576
1577    }
1578}
1579
Popular Tags