View Javadoc
1 package org.masukomi.aspirin.core; 2 3 4 /* 5 * Portions of this file are Copyright (C) The Apache Software Foundation. All 6 * rights reserved. 7 * 8 * This software is published under the terms of the Apache Software License 9 * version 1.1, a copy of which has been included with this distribution in 10 * the LICENSE file. 11 */ 12 13 14 import org.apache.james.core.MailHeaders; 15 16 import org.apache.james.util.*; 17 import org.apache.james.util.watchdog.BytesReadResetInputStream; 18 import org.apache.james.util.watchdog.Watchdog; 19 import org.apache.james.util.watchdog.WatchdogTarget; 20 import org.apache.james.smtpserver.*; 21 import org.apache.mailet.MailAddress; 22 23 import org.masukomi.aspirin.filters.*; 24 25 import org.masukomi.prefs.XMLClassPreferences; 26 27 import java.io.*; 28 29 import java.net.Socket; 30 import java.net.SocketException; 31 import java.net.InetAddress; 32 import java.net.UnknownHostException; 33 34 import java.util.*; 35 36 import javax.mail.MessagingException; 37 import javax.mail.*; 38 import javax.mail.internet.*; 39 40 41 /*** 42 * Provides SMTP functionality by carrying out the server side of the SMTP 43 * interaction. 44 * 45 * @author Serge Knystautas <sergek@lokitech.com> 46 * @author Federico Barbieri <scoobie@systemy.it> 47 * @author Jason Borden <jborden@javasense.com> 48 * @author Matthew Pangaro <mattp@lokitech.com> 49 * @author Danny Angus <danny@thought.co.uk> 50 * @author Peter M. Goldstein <farsight@alum.mit.edu> 51 * @author Kate Rhodes <masukomi@masukomi.org> 52 * 53 * @todo enable authentication (roughly lines 840, 1099, 10266) 54 */ 55 public class SMTPHandler extends Thread{ //implements SMTPHandler, Poolable { 56 57 //~ Static fields/initializers --------------------------------------------- 58 59 /*** 60 * SMTP Server identification string used in SMTP headers 61 */ 62 private final static String SOFTWARE_TYPE = "Aspirin SMTP Server "; 63 //+ Constants.SOFTWARE_VERSION; 64 65 // Keys used to store/lookup data in the internal state hash map 66 67 private static String DEFAULT_HELO_NAME = "UNKNOWN Aspirin Server"; 68 69 private final static boolean DEFAULT_AUTH_REQUIRED = false; 70 71 private final long DEFAULT_MAX_MESSAGE_SIZE =0; 72 73 private final int DEFAULT_RESET_LENGTH =20*1024; 74 75 76 /*** DOCUMENT ME! */ 77 private final static String CURRENT_HELO_MODE = "CURRENT_HELO_MODE"; // HELO or EHLO 78 79 /*** DOCUMENT ME! */ 80 private final static String SENDER = "SENDER_ADDRESS"; // Sender's email address 81 82 /*** DOCUMENT ME! */ 83 private final static String MESG_FAILED = "MESG_FAILED"; // Message failed flag 84 85 /*** DOCUMENT ME! */ 86 private final static String MESG_SIZE = "MESG_SIZE"; // The size of the message 87 88 /*** DOCUMENT ME! */ 89 private final static String RCPT_LIST = "RCPT_LIST"; // The message recipients 90 91 /*** 92 * The character array that indicates termination of an SMTP connection 93 */ 94 private final static char[] SMTPTerminator = { '\r', '\n', '.', '\r', '\n' }; 95 96 /*** 97 * Static Random instance used to generate SMTP ids 98 */ 99 private final static Random random = new Random(); 100 101 /*** 102 * Static RFC822DateFormat used to generate date headers 103 */ 104 private final static RFC822DateFormat rfc822DateFormat = new RFC822DateFormat(); 105 106 /*** 107 * The text string for the SMTP HELO command. 108 */ 109 private final static String COMMAND_HELO = "HELO"; 110 111 /*** 112 * The text string for the SMTP EHLO command. 113 */ 114 private final static String COMMAND_EHLO = "EHLO"; 115 116 /*** 117 * The text string for the SMTP AUTH command. 118 */ 119 private final static String COMMAND_AUTH = "AUTH"; 120 121 /*** 122 * The text string for the SMTP MAIL command. 123 */ 124 private final static String COMMAND_MAIL = "MAIL"; 125 126 /*** 127 * The text string for the SMTP RCPT command. 128 */ 129 private final static String COMMAND_RCPT = "RCPT"; 130 131 /*** 132 * The text string for the SMTP NOOP command. 133 */ 134 private final static String COMMAND_NOOP = "NOOP"; 135 136 /*** 137 * The text string for the SMTP RSET command. 138 */ 139 private final static String COMMAND_RSET = "RSET"; 140 141 /*** 142 * The text string for the SMTP DATA command. 143 */ 144 private final static String COMMAND_DATA = "DATA"; 145 146 /*** 147 * The text string for the SMTP QUIT command. 148 */ 149 private final static String COMMAND_QUIT = "QUIT"; 150 151 /*** 152 * The text string for the SMTP HELP command. 153 */ 154 private final static String COMMAND_HELP = "HELP"; 155 156 /*** 157 * The text string for the SMTP VRFY command. 158 */ 159 private final static String COMMAND_VRFY = "VRFY"; 160 161 /*** 162 * The text string for the SMTP EXPN command. 163 */ 164 private final static String COMMAND_EXPN = "EXPN"; 165 166 /*** 167 * The text string for the SMTP AUTH type PLAIN. 168 */ 169 private final static String AUTH_TYPE_PLAIN = "PLAIN"; 170 171 /*** 172 * The text string for the SMTP AUTH type LOGIN. 173 */ 174 private final static String AUTH_TYPE_LOGIN = "LOGIN"; 175 176 /*** 177 * The text string for the SMTP MAIL command SIZE option. 178 */ 179 private final static String MAIL_OPTION_SIZE = "SIZE"; 180 181 //~ Instance fields -------------------------------------------------------- 182 183 private Session currentSession; 184 185 private MimeMessage message; 186 187 /*** 188 * The per-handler response buffer used to marshal responses. 189 */ 190 StringBuffer responseBuffer = new StringBuffer(256); 191 192 /*** 193 * The watchdog being used by this handler to deal with idle timeouts. 194 */ 195 Watchdog theWatchdog; 196 197 /*** 198 * The watchdog target that idles out this handler. 199 */ 200 WatchdogTarget theWatchdogTarget = new SMTPWatchdogTarget(); 201 202 /*** 203 * A Reader wrapper for the incoming stream of bytes coming from the socket. 204 */ 205 private BufferedReader inReader; 206 207 /*** 208 * The hash map that holds variables for the SMTP message transfer in progress. 209 * 210 * This hash map should only be used to store variable set in a particular 211 * set of sequential MAIL-RCPT-DATA commands, as described in RFC 2821. Per 212 * connection values should be stored as member variables in this class. 213 */ 214 private HashMap state = new HashMap(); 215 216 /*** 217 * The incoming stream of bytes coming from the socket. 218 */ 219 private InputStream in; 220 221 /*** 222 * The writer to which outgoing messages are written. 223 */ 224 private PrintWriter out; 225 226 /*** 227 * The per-service configuration data that applies to all handlers 228 */ 229 230 //private SMTPHandlerConfigurationData theConfigData; 231 232 /*** 233 * The TCP/IP socket over which the SMTP 234 * dialogue is occurring. 235 */ 236 private Socket socket; 237 238 /*** 239 * The user name of the authenticated user associated with this SMTP transaction. 240 */ 241 private String authenticatedUser; 242 243 /*** 244 * The remote host name obtained by lookup on the socket. 245 */ 246 private String remoteHost; 247 248 /*** 249 * The remote IP address of the socket. 250 */ 251 private String remoteIP; 252 253 /*** 254 * The id associated with this particular SMTP interaction. 255 */ 256 private String smtpID; 257 258 /*** 259 * The thread executing this handler 260 */ 261 private Thread handlerThread; 262 263 /*** should contain the following keys 264 * <ul> 265 * <li>maxLineLength - the maximum length of a line to read from incoming 266 * sockets.</li> 267 * <li>heloName</li> 268 * <li>resetLength</li> 269 * <li>maxMessageSize</li> 270 * <li>authRequired</li> 271 * </ul> 272 */ 273 private XMLClassPreferences preferences; 274 275 276 private String rawMessage; 277 278 //~ Constructors ----------------------------------------------------------- 279 280 281 static { 282 try { 283 DEFAULT_HELO_NAME=InetAddress.getLocalHost().getHostName(); 284 } catch (UnknownHostException e) { 285 } 286 } 287 /*** 288 * Creates a new SMTPHandler object. 289 */ 290 public SMTPHandler() { 291 preferences = XMLClassPreferences.systemNodeForClass(this.getClass()); 292 currentSession = Session.getInstance(System.getProperties(), null); 293 } 294 /*** 295 * Creates a new SMTPHandler object. 296 */ 297 public SMTPHandler(Socket socket) { 298 preferences = XMLClassPreferences.systemNodeForClass(this.getClass()); 299 currentSession = Session.getInstance(System.getProperties(), null); 300 setSocket(socket); 301 } 302 303 //~ Methods ---------------------------------------------------------------- 304 305 public void setSocket(Socket socket){ 306 this.socket=socket; 307 remoteIP = socket.getInetAddress().getHostAddress(); 308 remoteHost = socket.getInetAddress().getHostName(); 309 StringBuffer threadName = new StringBuffer("SMTPHandler connection from "); 310 threadName.append(remoteHost); 311 threadName.append("("); 312 threadName.append(remoteIP); 313 threadName.append(")"); 314 setName(threadName.toString()); 315 316 } 317 318 /*** 319 * @see org.apache.avalon.cornerstone.services.connection.SMTPHandler#handleConnection(Socket) 320 */ 321 public void handleConnection() throws IOException { 322 323 try { 324 //this.socket = connection; 325 326 //synchronized (this) { 327 // handlerThread = Thread.currentThread(); 328 //} 329 330 in = new BufferedInputStream(socket.getInputStream(), 1024); 331 332 // An ASCII encoding can be used because all transmissions other 333 // that those in the DATA command are guaranteed 334 // to be ASCII 335 inReader = new BufferedReader(new InputStreamReader(in, "ASCII"), 336 512); 337 338 smtpID = random.nextInt(1024) + ""; 339 resetState(); 340 341 if (! RelayHostsFilter.isRelayableHost(remoteHost)){ 342 writeLoggedFlushedResponse("550 Relaying denied."); 343 return; 344 } 345 346 } catch (Exception e) { 347 StringBuffer exceptionBuffer = new StringBuffer(256).append( 348 "Cannot open connection from ").append(remoteHost) 349 .append(" (") 350 .append(remoteIP) 351 .append("): ") 352 .append(e 353 .getMessage()); 354 String exceptionString = exceptionBuffer.toString(); 355 //getLogger().error(exceptionString, e); 356 throw new RuntimeException(exceptionString); 357 } 358 359 //if (getLogger().isInfoEnabled()) { 360 StringBuffer infoBuffer = new StringBuffer(128).append( 361 "Connection from ").append(remoteHost).append(" (") 362 .append(remoteIP) 363 .append(")"); 364 //getLogger().info(infoBuffer.toString()); 365 System.out.println(infoBuffer.toString()); 366 //} 367 368 try { 369 out = new InternetPrintWriter(new BufferedWriter( 370 new OutputStreamWriter(socket.getOutputStream()), 1024), 371 false); 372 373 // Initially greet the connector 374 // Format is: Sat, 24 Jan 1998 13:16:09 -0500 375 responseBuffer.append("220 ").append(preferences.get("heloName", DEFAULT_HELO_NAME)) 376 .append(" SMTP Server (").append(SOFTWARE_TYPE) 377 .append(") ready ").append(rfc822DateFormat.format( 378 new Date())); 379 380 System.out.println(responseBuffer.toString()); 381 String responseString = clearResponseBuffer(); 382 writeLoggedFlushedResponse(responseString); 383 384 //theWatchdog.start(); 385 386 while (parseCommand(readCommandLine())) { 387 //theWatchdog.reset(); 388 } 389 390 //theWatchdog.stop(); 391 //getLogger().debug("Closing socket."); 392 393 } catch (SocketException se) { 394 //if (getLogger().isDebugEnabled()) { 395 StringBuffer errorBuffer = new StringBuffer(64).append( 396 "Socket to ").append(remoteHost).append(" (") 397 .append(remoteIP) 398 .append(") closed remotely."); 399 //getLogger().debug(errorBuffer.toString(), se); 400 System.out.println(errorBuffer.toString()); 401 //} 402 } catch (InterruptedIOException iioe) { 403 //if (getLogger().isDebugEnabled()) { 404 StringBuffer errorBuffer = new StringBuffer(64).append( 405 "Socket to ").append(remoteHost).append(" (") 406 .append(remoteIP) 407 .append(") timeout."); 408 //getLogger().debug(errorBuffer.toString(), iioe); 409 System.out.println(errorBuffer.toString()); 410 //} 411 } catch (IOException ioe) { 412 //if (getLogger().isDebugEnabled()) { 413 StringBuffer errorBuffer = new StringBuffer(256).append( 414 "Exception handling socket to ").append(remoteHost) 415 .append(" (") 416 .append(remoteIP) 417 .append(") : ") 418 .append(ioe 419 .getMessage()); 420 //getLogger().debug(errorBuffer.toString(), ioe); 421 System.out.println(errorBuffer.toString()); 422 //} 423 } catch (Exception e) { 424 //if (getLogger().isDebugEnabled()) { 425 // getLogger().debug("Exception opening socket: " + e.getMessage(), 426 // e); 427 System.out.println("Exception opening socket: " + e); 428 //} 429 } finally { 430 resetHandler(); 431 } 432 } 433 434 /*** 435 * Set the configuration data for the handler 436 * 437 * @param theData the per-service configuration data for this handler 438 */ 439 /*void setConfigurationData(SMTPHandlerConfigurationData theData) { 440 theConfigData = theData; 441 }*/ 442 443 /*** 444 * Set the Watchdog for use by this handler. 445 * 446 * @param theWatchdog the watchdog 447 */ 448 void setWatchdog(Watchdog theWatchdog) { 449 this.theWatchdog = theWatchdog; 450 } 451 452 /*** 453 * Gets the Watchdog Target that should be used by Watchdogs managing 454 * this connection. 455 * 456 * @return the WatchdogTarget 457 */ 458 WatchdogTarget getWatchdogTarget() { 459 return theWatchdogTarget; 460 } 461 462 /*** 463 * Idle out this connection 464 */ 465 void idleClose() { 466 /*if (getLogger() != null) { 467 getLogger().error("SMTP Connection has idled out."); 468 }*/ 469 System.out.println("SMTP Connection has idled out."); 470 471 try { 472 if (socket != null) { 473 socket.close(); 474 } 475 } catch (Exception e) { 476 // ignored 477 } 478 479 synchronized (this) { 480 // Interrupt the thread to recover from internal hangs 481 if (handlerThread != null) { 482 handlerThread.interrupt(); 483 } 484 } 485 } 486 487 /*** 488 * Reads a line of characters off the command line. 489 * 490 * @return the trimmed input line 491 * @throws IOException if an exception is generated reading in the input characters 492 */ 493 final String readCommandLine() throws IOException { 494 return inReader.readLine().trim(); 495 } 496 497 /*** 498 * Write and flush a response string. The response is also logged. 499 * Should be used for the last line of a multi-line response or 500 * for a single line response. 501 * 502 * @param responseString the response string sent to the client 503 */ 504 final void writeLoggedFlushedResponse(String responseString) { 505 out.println(responseString); 506 out.flush(); 507 logResponseString(responseString); 508 } 509 510 /*** 511 * Write a response string. The response is also logged. 512 * Used for multi-line responses. 513 * 514 * @param responseString the response string sent to the client 515 */ 516 final void writeLoggedResponse(String responseString) { 517 out.println(responseString); 518 logResponseString(responseString); 519 } 520 521 /*** 522 * Sets the user name associated with this SMTP interaction. 523 * 524 * @param userID the user name 525 */ 526 private void setUser(String userID) { 527 authenticatedUser = userID; 528 } 529 530 /*** 531 * Returns the user name associated with this SMTP interaction. 532 * 533 * @return the user name 534 */ 535 private String getUser() { 536 return authenticatedUser; 537 } 538 539 /*** 540 * Clears the response buffer, returning the String of characters in the buffer. 541 * 542 * @return the data in the response buffer 543 */ 544 private String clearResponseBuffer() { 545 String responseString = responseBuffer.toString(); 546 responseBuffer.delete(0, responseBuffer.length()); 547 548 return responseString; 549 } 550 551 /*** 552 * This method logs at a "DEBUG" level the response string that 553 * was sent to the SMTP client. The method is provided largely 554 * as syntactic sugar to neaten up the code base. It is declared 555 * private and final to encourage compiler inlining. 556 * 557 * @param responseString the response string sent to the client 558 */ 559 private final void logResponseString(String responseString) { 560 /*if (getLogger().isDebugEnabled()) { 561 getLogger().debug("Sent: " + responseString); 562 }*/ 563 //System.out.println("Sent: " + responseString); 564 } 565 566 /*** 567 * Handler method called upon receipt of a AUTH command. 568 * Handles client authentication to the SMTP server. 569 * 570 * @param argument the argument passed in with the command by the SMTP client 571 */ 572 private void doAUTH(String argument) throws Exception { 573 String responseString = null; 574 575 if (getUser() != null) { 576 responseString = "503 User has previously authenticated. " 577 + " Further authentication is not required!"; 578 writeLoggedFlushedResponse(responseString); 579 } else if (argument == null) { 580 responseString = "501 Usage: AUTH (authentication type) <challenge>"; 581 writeLoggedFlushedResponse(responseString); 582 } else { 583 String initialResponse = null; 584 585 if ((argument != null) && (argument.indexOf(" ") > 0)) { 586 initialResponse = argument.substring(argument.indexOf(" ") + 1); 587 argument = argument.substring(0, argument.indexOf(" ")); 588 } 589 590 String authType = argument.toUpperCase(Locale.US); 591 592 if (authType.equals(AUTH_TYPE_PLAIN)) { 593 doPlainAuth(initialResponse); 594 595 return; 596 } else if (authType.equals(AUTH_TYPE_LOGIN)) { 597 doLoginAuth(initialResponse); 598 599 return; 600 } else { 601 doUnknownAuth(authType, initialResponse); 602 603 return; 604 } 605 } 606 } 607 608 /*** 609 * Handler method called upon receipt of a DATA command. 610 * Reads in message data, creates header, and delivers to 611 * mail server service for delivery. 612 * 613 * @param argument the argument passed in with the command by the SMTP client 614 */ 615 private void doDATA(String argument) { 616 String responseString = null; 617 618 if ((argument != null) && (argument.length() > 0)) { 619 responseString = "500 Unexpected argument provided with DATA command"; 620 writeLoggedFlushedResponse(responseString); 621 } 622 623 if (!state.containsKey(SENDER)) { 624 responseString = "503 No sender specified"; 625 writeLoggedFlushedResponse(responseString); 626 } else if (!state.containsKey(RCPT_LIST)) { 627 responseString = "503 No recipients specified"; 628 writeLoggedFlushedResponse(responseString); 629 } else { 630 responseString = "354 Ok Send data ending with <CRLF>.<CRLF>"; 631 writeLoggedFlushedResponse(responseString); 632 633 InputStream msgIn = new CharTerminatedInputStream(in, SMTPTerminator); 634 635 try { 636 //msgIn = new BytesReadResetInputStream(msgIn, theWatchdog, 637 // theConfigData.getResetLength()); 638 msgIn = new BytesReadResetInputStream(msgIn, theWatchdog, 639 preferences.getInt("resetLength", DEFAULT_RESET_LENGTH)); 640 641 // if the message size limit has been set, we'll 642 // wrap msgIn with a SizeLimitedInputStream 643 //long maxMessageSize = theConfigData.getMaxMessageSize(); 644 long maxMessageSize = preferences.getLong("maxMessageSize", DEFAULT_MAX_MESSAGE_SIZE); // zero means no limit 645 646 if (maxMessageSize > 0) { 647 /*if (getLogger().isDebugEnabled()) { 648 StringBuffer logBuffer = new StringBuffer(128).append( 649 "Using SizeLimitedInputStream ") 650 .append(" with max message size: ") 651 .append(maxMessageSize); 652 getLogger().debug(logBuffer.toString()); 653 }*/ 654 655 msgIn = new SizeLimitedInputStream(msgIn, maxMessageSize); 656 } 657 658 // Removes the dot stuffing 659 msgIn = new SMTPInputStream(msgIn); 660 661 662 // Parse out the message headers 663 //MailHeaders headers = new MailHeaders(msgIn); 664 //headers = processMailHeaders(headers); 665 //processMail(headers, msgIn); 666 //headers = null; 667 processFullMail(msgIn); 668 } catch (MessagingException me) { 669 // Grab any exception attached to this one. 670 Exception e = me.getNextException(); 671 672 // If there was an attached exception, and it's a 673 // MessageSizeException 674 if ((e != null) && e instanceof MessageSizeException) { 675 // Add an item to the state to suppress 676 // logging of extra lines of data 677 // that are sent after the size limit has 678 // been hit. 679 state.put(MESG_FAILED, Boolean.TRUE); 680 681 // then let the client know that the size 682 // limit has been hit. 683 responseString = "552 Error processing message: " 684 + e.getMessage(); 685 686 /*StringBuffer errorBuffer = new StringBuffer(256).append( 687 "Rejected message from ") 688 .append(state.get( 689 SENDER).toString()).append(" from host ") 690 .append(remoteHost) 691 .append(" (") 692 .append(remoteIP) 693 .append(") exceeding system maximum message size of ") 694 .append(theConfigData 695 .getMaxMessageSize()); 696 697 */ 698 StringBuffer errorBuffer = new StringBuffer(256).append( 699 "Rejected message from ") 700 .append(state.get( 701 SENDER).toString()).append(" from host ") 702 .append(remoteHost) 703 .append(" (") 704 .append(remoteIP) 705 .append(") exceeding system maximum message size of ") 706 .append(preferences.getLong("maxMessageSize", DEFAULT_MAX_MESSAGE_SIZE)); 707 //getLogger().error(errorBuffer.toString()); 708 } else { 709 responseString = "451 Error processing message: " 710 + me.getMessage(); 711 //getLogger().error("Unknown error occurred while processing DATA.", 712 // me); 713 } 714 715 writeLoggedFlushedResponse(responseString); 716 717 return; 718 } finally { 719 if (msgIn != null) { 720 try { 721 msgIn.close(); 722 } catch (Exception e) { 723 // Ignore close exception 724 } 725 726 msgIn = null; 727 } 728 } 729 730 resetState(); 731 responseString = "250 Message received"; 732 writeLoggedFlushedResponse(responseString); 733 } 734 } 735 736 /*** 737 * Handler method called upon receipt of a EHLO command. 738 * Responds with a greeting and informs the client whether 739 * client authentication is required. 740 * 741 * @param argument the argument passed in with the command by the SMTP client 742 */ 743 private void doEHLO(String argument) { 744 String responseString = null; 745 746 if (argument == null) { 747 responseString = "501 Domain address required: " + COMMAND_EHLO; 748 writeLoggedFlushedResponse(responseString); 749 } else { 750 resetState(); 751 state.put(CURRENT_HELO_MODE, COMMAND_EHLO); 752 753 // Extension defined in RFC 1870 754 //long maxMessageSize = theConfigData.getMaxMessageSize(); 755 long maxMessageSize = preferences.getLong("maxMessageSize", DEFAULT_MAX_MESSAGE_SIZE); 756 if (maxMessageSize > 0) { 757 responseString = "250-SIZE " + maxMessageSize; 758 writeLoggedResponse(responseString); 759 } 760 761 //if (theConfigData.isAuthRequired()) { 762 if (preferences.getBoolean("authRequired", DEFAULT_AUTH_REQUIRED)){ 763 764 //This is necessary because we're going to do a multiline response 765 responseBuffer.append("250-"); 766 } else { 767 responseBuffer.append("250 "); 768 } 769 770 //responseBuffer.append(theConfigData.getHelloName()).append(" Hello ") 771 responseBuffer.append(preferences.get("heloName", DEFAULT_HELO_NAME)).append(" Hello ") 772 .append(argument).append(" (").append(remoteHost) 773 .append(" [").append(remoteIP).append("])"); 774 responseString = clearResponseBuffer(); 775 776 //if (theConfigData.isAuthRequired()) { 777 if (preferences.getBoolean("authRequired", DEFAULT_AUTH_REQUIRED)) { 778 writeLoggedResponse(responseString); 779 responseString = "250 AUTH LOGIN PLAIN"; 780 } 781 782 writeLoggedFlushedResponse(responseString); 783 } 784 } 785 786 /*** 787 * Handler method called upon receipt of a EXPN command. 788 * This method informs the client that the command is 789 * not implemented. 790 * 791 * @param argument the argument passed in with the command by the SMTP client 792 */ 793 private void doEXPN(String argument) { 794 String responseString = "502 EXPN is not supported"; 795 writeLoggedFlushedResponse(responseString); 796 } 797 798 /*** 799 * Handler method called upon receipt of a HELO command. 800 * Responds with a greeting and informs the client whether 801 * client authentication is required. 802 * 803 * @param argument the argument passed in with the command by the SMTP client 804 */ 805 private void doHELO(String argument) { 806 String responseString = null; 807 808 if (argument == null) { 809 responseString = "501 Domain address required: " + COMMAND_HELO; 810 writeLoggedFlushedResponse(responseString); 811 } else { 812 resetState(); 813 state.put(CURRENT_HELO_MODE, COMMAND_HELO); 814 815 //if (theConfigData.isAuthRequired()) { 816 if (preferences.getBoolean("authRequired", DEFAULT_AUTH_REQUIRED)) { 817 //This is necessary because we're going to do a multiline response 818 responseBuffer.append("250-"); 819 } else { 820 responseBuffer.append("250 "); 821 } 822 823 //responseBuffer.append(theConfigData.getHelloName()).append(" Hello ") 824 responseBuffer.append(preferences.get("heloName", DEFAULT_HELO_NAME)).append(" Hello ") 825 .append(argument).append(" (").append(remoteHost) 826 .append(" [").append(remoteIP).append("])"); 827 responseString = clearResponseBuffer(); 828 829 //if (theConfigData.isAuthRequired()) { 830 if (preferences.getBoolean("authRequired", DEFAULT_AUTH_REQUIRED)) { 831 writeLoggedResponse(responseString); 832 responseString = "250 AUTH LOGIN PLAIN"; 833 } 834 } 835 836 writeLoggedFlushedResponse(responseString); 837 } 838 839 /*** 840 * Handler method called upon receipt of a HELP command. 841 * This method informs the client that the command is 842 * not implemented. 843 * 844 * @param argument the argument passed in with the command by the SMTP client 845 */ 846 private void doHELP(String argument) { 847 String responseString = "502 HELP is not supported"; 848 writeLoggedFlushedResponse(responseString); 849 } 850 851 /*** 852 * Carries out the Login AUTH SASL exchange. 853 * 854 * @param initialResponse the initial response line passed in with the AUTH command 855 */ 856 private void doLoginAuth(String initialResponse) throws IOException { 857 String user = null; 858 String pass = null; 859 String responseString = null; 860 861 if (initialResponse == null) { 862 responseString = "334 VXNlcm5hbWU6"; // base64 encoded "Username:" 863 writeLoggedFlushedResponse(responseString); 864 user = readCommandLine(); 865 } else { 866 user = initialResponse.trim(); 867 } 868 869 if (user != null) { 870 try { 871 user = Base64.decodeAsString(user); 872 } catch (Exception e) { 873 // Ignored - this parse error will be 874 // addressed in the if clause below 875 user = null; 876 } 877 } 878 879 responseString = "334 UGFzc3dvcmQ6"; // base64 encoded "Password:" 880 writeLoggedFlushedResponse(responseString); 881 pass = readCommandLine(); 882 883 if (pass != null) { 884 try { 885 pass = Base64.decodeAsString(pass); 886 } catch (Exception e) { 887 // Ignored - this parse error will be 888 // addressed in the if clause below 889 pass = null; 890 } 891 } 892 893 // Authenticate user 894 /*if ((user == null) || (pass == null)) { 895 responseString = "501 Could not decode parameters for AUTH LOGIN"; 896 } else if (theConfigData.getUsersRepository().test(user, pass)) { 897 setUser(user); 898 */ responseString = "235 Authentication Successful"; 899 900 /*if (getLogger().isDebugEnabled()) { 901 // TODO: Make this string a more useful debug message 902 getLogger().debug("AUTH method LOGIN succeeded"); 903 }*/ 904 /*} else { 905 responseString = "535 Authentication Failed"; 906 907 // TODO: Make this string a more useful error message 908 //getLogger().error("AUTH method LOGIN failed"); 909 }*/ 910 911 writeLoggedFlushedResponse(responseString); 912 913 return; 914 } 915 916 /*** 917 * Handler method called upon receipt of a MAIL command. 918 * Sets up handler to deliver mail as the stated sender. 919 * 920 * @param argument the argument passed in with the command by the SMTP client 921 */ 922 private void doMAIL(String argument) { 923 String responseString = null; 924 925 String sender = null; 926 927 if ((argument != null) && (argument.indexOf(":") > 0)) { 928 int colonIndex = argument.indexOf(":"); 929 sender = argument.substring(colonIndex + 1); 930 argument = argument.substring(0, colonIndex); 931 } 932 933 if (state.containsKey(SENDER)) { 934 responseString = "503 Sender already specified"; 935 writeLoggedFlushedResponse(responseString); 936 } else if ((argument == null) 937 || !argument.toUpperCase(Locale.US).equals("FROM") 938 || (sender == null)) { 939 responseString = "501 Usage: MAIL FROM:<sender>"; 940 writeLoggedFlushedResponse(responseString); 941 } else { 942 sender = sender.trim(); 943 944 int lastChar = sender.lastIndexOf('>'); 945 946 // Check to see if any options are present and, if so, whether they are correctly formatted 947 // (separated from the closing angle bracket by a ' '). 948 if ((lastChar > 0) && (sender.length() > (lastChar + 2)) 949 && (sender.charAt(lastChar + 1) == ' ')) { 950 String mailOptionString = sender.substring(lastChar + 2); 951 952 // Remove the options from the sender 953 sender = sender.substring(0, lastChar + 1); 954 955 StringTokenizer optionTokenizer = new StringTokenizer(mailOptionString, 956 " "); 957 958 while (optionTokenizer.hasMoreElements()) { 959 String mailOption = optionTokenizer.nextToken(); 960 int equalIndex = mailOptionString.indexOf('='); 961 String mailOptionName = mailOption; 962 String mailOptionValue = ""; 963 964 if (equalIndex > 0) { 965 mailOptionName = mailOption.substring(0, equalIndex) 966 .toUpperCase(Locale.US); 967 mailOptionValue = mailOption.substring(equalIndex + 1); 968 } 969 970 // Handle the SIZE extension keyword 971 if (mailOptionName.startsWith(MAIL_OPTION_SIZE)) { 972 if (!(doMailSize(mailOptionValue))) { 973 return; 974 } 975 } else { 976 // Unexpected option attached to the Mail command 977 /*if (getLogger().isDebugEnabled()) { 978 StringBuffer debugBuffer = new StringBuffer(128).append( 979 "MAIL command had unrecognized/unexpected option ") 980 .append(mailOptionName) 981 .append(" with value ") 982 .append(mailOptionValue); 983 getLogger().debug(debugBuffer.toString()); 984 }*/ 985 } 986 } 987 } 988 989 if (!sender.startsWith("<") || !sender.endsWith(">")) { 990 responseString = "501 Syntax error in MAIL command"; 991 writeLoggedFlushedResponse(responseString); 992 993 /*if (getLogger().isErrorEnabled()) { 994 StringBuffer errorBuffer = new StringBuffer(128).append( 995 "Error parsing sender address: ").append(sender) 996 .append(": did not start and end with < >"); 997 getLogger().error(errorBuffer.toString()); 998 }*/ 999 1000 return; 1001 } 1002 1003 MailAddress senderAddress = null; 1004 1005 //Remove < and > 1006 //sender = sender.substring(1, sender.length() - 1); 1007 sender = extractEmailAddress(sender); 1008 if (sender.length() == 0) { 1009 //This is the <> case. Let senderAddress == null 1010 } else { 1011 if (sender.indexOf("@") < 0) { 1012 sender = sender + "@localhost"; 1013 } 1014 1015 1016 try { 1017 senderAddress = new MailAddress(sender); 1018 } catch (Exception pe) { 1019 responseString = "501 Syntax error in sender address. address read as " + sender; 1020 writeLoggedFlushedResponse(responseString); 1021 1022 /*if (getLogger().isErrorEnabled()) { 1023 StringBuffer errorBuffer = new StringBuffer(256).append( 1024 "Error parsing sender address: ").append(sender) 1025 .append(": ") 1026 .append(pe 1027 .getMessage()); 1028 getLogger().error(errorBuffer.toString()); 1029 }*/ 1030 1031 return; 1032 } 1033 } 1034 1035 state.put(SENDER, senderAddress); 1036 responseBuffer.append("250 Sender <").append(sender).append("> OK"); 1037 responseString = clearResponseBuffer(); 1038 writeLoggedFlushedResponse(responseString); 1039 } 1040 } 1041 1042 /*** 1043 * Handles the SIZE MAIL option. 1044 * 1045 * @param mailOptionValue the option string passed in with the SIZE option 1046 * @returns true if further options should be processed, false otherwise 1047 */ 1048 private boolean doMailSize(String mailOptionValue) { 1049 int size = 0; 1050 1051 try { 1052 size = Integer.parseInt(mailOptionValue); 1053 } catch (NumberFormatException pe) { 1054 // This is a malformed option value. We return an error 1055 String responseString = "501 Syntactically incorrect value for SIZE parameter"; 1056 writeLoggedFlushedResponse(responseString); 1057 //getLogger().error("Rejected syntactically incorrect value for SIZE parameter."); 1058 1059 return false; 1060 } 1061 1062 /*if (getLogger().isDebugEnabled()) { 1063 StringBuffer debugBuffer = new StringBuffer(128).append( 1064 "MAIL command option SIZE received with value ").append(size) 1065 .append("."); 1066 getLogger().debug(debugBuffer.toString()); 1067 }*/ 1068 1069 //long maxMessageSize = theConfigData.getMaxMessageSize(); 1070 long maxMessageSize = preferences.getLong("maxMessageSize", DEFAULT_MAX_MESSAGE_SIZE); 1071 1072 if ((maxMessageSize > 0) && (size > maxMessageSize)) { 1073 // Let the client know that the size limit has been hit. 1074 String responseString = "552 Message size exceeds fixed maximum message size"; 1075 writeLoggedFlushedResponse(responseString); 1076 1077 StringBuffer errorBuffer = new StringBuffer(256).append( 1078 "Rejected message from ") 1079 .append(state.get( 1080 SENDER).toString()).append(" from host ") 1081 .append(remoteHost) 1082 .append(" (") 1083 .append(remoteIP) 1084 .append(") of size ") 1085 .append(size) 1086 .append(" exceeding system maximum message size of ") 1087 .append(maxMessageSize) 1088 .append("based on SIZE option."); 1089 //getLogger().error(errorBuffer.toString()); 1090 1091 return false; 1092 } else { 1093 // put the message size in the message state so it can be used 1094 // later to restrict messages for user quotas, etc. 1095 state.put(MESG_SIZE, new Integer(size)); 1096 } 1097 1098 return true; 1099 } 1100 1101 /*** 1102 * Handler method called upon receipt of a NOOP command. 1103 * Just sends back an OK and logs the command. 1104 * 1105 * @param argument the argument passed in with the command by the SMTP client 1106 */ 1107 private void doNOOP(String argument) { 1108 String responseString = "250 OK"; 1109 writeLoggedFlushedResponse(responseString); 1110 } 1111 1112 /*** 1113 * Carries out the Plain AUTH SASL exchange. 1114 * 1115 * @param initialResponse the initial response line passed in with the AUTH command 1116 */ 1117 private void doPlainAuth(String initialResponse) throws IOException { 1118 String userpass = null; 1119 String user = null; 1120 String pass = null; 1121 String responseString = null; 1122 1123 if (initialResponse == null) { 1124 responseString = "334 OK. Continue authentication"; 1125 writeLoggedFlushedResponse(responseString); 1126 userpass = readCommandLine(); 1127 } else { 1128 userpass = initialResponse.trim(); 1129 } 1130 1131 try { 1132 if (userpass != null) { 1133 userpass = Base64.decodeAsString(userpass); 1134 } 1135 1136 if (userpass != null) { 1137 StringTokenizer authTokenizer = new StringTokenizer(userpass, 1138 "\0"); 1139 user = authTokenizer.nextToken(); 1140 pass = authTokenizer.nextToken(); 1141 authTokenizer = null; 1142 } 1143 } catch (Exception e) { 1144 // Ignored - this exception in parsing will be dealt 1145 // with in the if clause below 1146 } 1147 1148 // Authenticate user 1149 /*if ((user == null) || (pass == null)) { 1150 responseString = "501 Could not decode parameters for AUTH PLAIN"; 1151 writeLoggedFlushedResponse(responseString); 1152 } else if (theConfigData.getUsersRepository().test(user, pass)) { 1153 setUser(user); 1154 */ responseString = "235 Authentication Successful"; 1155 writeLoggedFlushedResponse(responseString); 1156 /* getLogger().info("AUTH method PLAIN succeeded"); 1157 } else { 1158 responseString = "535 Authentication Failed"; 1159 writeLoggedFlushedResponse(responseString); 1160 getLogger().error("AUTH method PLAIN failed"); 1161 }*/ 1162 1163 return; 1164 } 1165 1166 /*** 1167 * Handler method called upon receipt of a QUIT command. 1168 * This method informs the client that the connection is 1169 * closing. 1170 * 1171 * @param argument the argument passed in with the command by the SMTP client 1172 */ 1173 private void doQUIT(String argument) { 1174 String responseString = ""; 1175 1176 if ((argument == null) || (argument.length() == 0)) { 1177 responseBuffer.append("221 ").append(preferences.get("heloName", DEFAULT_HELO_NAME)) 1178 .append(" Service closing transmission channel"); 1179 responseString = clearResponseBuffer(); 1180 } else { 1181 responseString = "500 Unexpected argument provided with QUIT command"; 1182 } 1183 1184 writeLoggedFlushedResponse(responseString); 1185 } 1186 1187 /*** 1188 * Handler method called upon receipt of a RCPT command. 1189 * Reads recipient. Does some connection validation. 1190 * 1191 * @param argument the argument passed in with the command by the SMTP client 1192 */ 1193 private void doRCPT(String argument) { 1194 String responseString = null; 1195 1196 String recipient = null; 1197 1198 if ((argument != null) && (argument.indexOf(":") > 0)) { 1199 int colonIndex = argument.indexOf(":"); 1200 recipient = argument.substring(colonIndex + 1); 1201 argument = argument.substring(0, colonIndex); 1202 } 1203 1204 if (!state.containsKey(SENDER)) { 1205 responseString = "503 Need MAIL before RCPT"; 1206 writeLoggedFlushedResponse(responseString); 1207 } else if ((argument == null) 1208 || !argument.toUpperCase(Locale.US).equals("TO") 1209 || (recipient == null)) { 1210 responseString = "501 Usage: RCPT TO:<recipient>"; 1211 writeLoggedFlushedResponse(responseString); 1212 } else { 1213 Collection rcptColl = (Collection) state.get(RCPT_LIST); 1214 1215 if (rcptColl == null) { 1216 rcptColl = new ArrayList(); 1217 } 1218 1219 recipient = recipient.trim(); 1220 1221 int lastChar = recipient.lastIndexOf('>'); 1222 1223 // Check to see if any options are present and, if so, whether they are correctly formatted 1224 // (separated from the closing angle bracket by a ' '). 1225 if ((lastChar > 0) && (recipient.length() > (lastChar + 2)) 1226 && (recipient.charAt(lastChar + 1) == ' ')) { 1227 String rcptOptionString = recipient.substring(lastChar + 2); 1228 1229 // Remove the options from the recipient 1230 recipient = recipient.substring(0, lastChar + 1); 1231 1232 StringTokenizer optionTokenizer = new StringTokenizer(rcptOptionString, 1233 " "); 1234 1235 while (optionTokenizer.hasMoreElements()) { 1236 String rcptOption = optionTokenizer.nextToken(); 1237 int equalIndex = rcptOptionString.indexOf('='); 1238 String rcptOptionName = rcptOption; 1239 String rcptOptionValue = ""; 1240 1241 if (equalIndex > 0) { 1242 rcptOptionName = rcptOption.substring(0, equalIndex) 1243 .toUpperCase(Locale.US); 1244 rcptOptionValue = rcptOption.substring(equalIndex + 1); 1245 } 1246 1247 // Unexpected option attached to the RCPT command 1248 /*if (getLogger().isDebugEnabled()) { 1249 StringBuffer debugBuffer = new StringBuffer(128).append( 1250 "RCPT command had unrecognized/unexpected option ") 1251 .append(rcptOptionName) 1252 .append(" with value ") 1253 .append(rcptOptionValue); 1254 getLogger().debug(debugBuffer.toString()); 1255 }*/ 1256 } 1257 1258 optionTokenizer = null; 1259 } 1260 1261 if (!recipient.startsWith("<") || !recipient.endsWith(">")) { 1262 responseString = "501 Syntax error in parameters or arguments"; 1263 writeLoggedFlushedResponse(responseString); 1264 1265 /*if (getLogger().isErrorEnabled()) { 1266 StringBuffer errorBuffer = new StringBuffer(192).append( 1267 "Error parsing recipient address: ") 1268 .append(recipient) 1269 .append(": did not start and end with < >"); 1270 getLogger().error(errorBuffer.toString()); 1271 }*/ 1272 1273 return; 1274 } 1275 1276 MailAddress recipientAddress = null; 1277 1278 //Remove < and > 1279 //recipient = recipient.substring(1, recipient.length() - 1); 1280 recipient = extractEmailAddress(recipient); 1281 if (recipient.indexOf("@") < 0) { 1282 recipient = recipient + "@localhost"; 1283 } 1284 1285 try { 1286 recipientAddress = new MailAddress(recipient); 1287 } catch (Exception pe) { 1288 responseString = "501 Syntax error in recipient address"; 1289 writeLoggedFlushedResponse(responseString); 1290 1291 /*if (getLogger().isErrorEnabled()) { 1292 StringBuffer errorBuffer = new StringBuffer(192).append( 1293 "Error parsing recipient address: ") 1294 .append(recipient) 1295 .append(": ") 1296 .append(pe 1297 .getMessage()); 1298 getLogger().error(errorBuffer.toString()); 1299 }*/ 1300 1301 return; 1302 } 1303 1304 //if (theConfigData.isAuthRequired()) { 1305 if (preferences.getBoolean("authRequired", DEFAULT_AUTH_REQUIRED)) { 1306 // Make sure the mail is being sent locally if not 1307 // authenticated else reject. 1308 if (getUser() == null) { 1309 String toDomain = recipientAddress.getHost(); 1310 1311 //if (!theConfigData.getMailServer().isLocalServer(toDomain)) { 1312 if (RelayHostsFilter.isRelayableHost(toDomain)){ 1313 responseString = "530 Authentication Required"; 1314 1315 writeLoggedFlushedResponse(responseString); 1316 //getLogger().error("Rejected message - authentication is required for mail request"); 1317 1318 return; 1319 } 1320 } else { 1321 // Identity verification checking 1322 /* 1323 * 1324 if (theConfigData.isVerifyIdentity()) { 1325 String authUser = (getUser()).toLowerCase(Locale.US); 1326 MailAddress senderAddress = (MailAddress) state.get(SENDER); 1327 boolean domainExists = false; 1328 1329 if ((!authUser.equals(senderAddress.getUser())) 1330 || (!theConfigData.getMailServer() 1331 .isLocalServer(senderAddress 1332 .getHost()))) { 1333 responseString = "503 Incorrect Authentication for Specified Email Address"; 1334 writeLoggedFlushedResponse(responseString); 1335 1336 if (getLogger().isErrorEnabled()) { 1337 StringBuffer errorBuffer = new StringBuffer(128).append( 1338 "User ").append(authUser) 1339 .append(" authenticated, however tried sending email as ") 1340 .append(senderAddress); 1341 getLogger().error(errorBuffer.toString()); 1342 } 1343 1344 return; 1345 } 1346 } 1347 */ 1348 } 1349 } 1350 1351 // shouldn't this be in the above bracket set? 1352 rcptColl.add(recipientAddress); 1353 state.put(RCPT_LIST, rcptColl); 1354 responseBuffer.append("250 Recipient <").append(recipient).append("> OK"); 1355 responseString = clearResponseBuffer(); 1356 writeLoggedFlushedResponse(responseString); 1357 } 1358 } 1359 1360 /*** 1361 * Handler method called upon receipt of a RSET command. 1362 * Resets message-specific, but not authenticated user, state. 1363 * 1364 * @param argument the argument passed in with the command by the SMTP client 1365 */ 1366 private void doRSET(String argument) { 1367 String responseString = ""; 1368 1369 if ((argument == null) || (argument.length() == 0)) { 1370 responseString = "250 OK"; 1371 resetState(); 1372 } else { 1373 responseString = "500 Unexpected argument provided with RSET command"; 1374 } 1375 1376 writeLoggedFlushedResponse(responseString); 1377 } 1378 1379 /*** 1380 * Handles the case of an unrecognized auth type. 1381 * 1382 * @param authType the unknown auth type 1383 * @param initialResponse the initial response line passed in with the AUTH command 1384 */ 1385 private void doUnknownAuth(String authType, String initialResponse) { 1386 String responseString = "504 Unrecognized Authentication Type"; 1387 writeLoggedFlushedResponse(responseString); 1388 1389 /*if (getLogger().isErrorEnabled()) { 1390 StringBuffer errorBuffer = new StringBuffer(128).append( 1391 "AUTH method ").append(authType).append(" is an unrecognized authentication type"); 1392 getLogger().error(errorBuffer.toString()); 1393 }*/ 1394 1395 return; 1396 } 1397 1398 /*** 1399 * Handler method called upon receipt of an unrecognized command. 1400 * Returns an error response and logs the command. 1401 * 1402 * @param command the command parsed by the SMTP client 1403 * @param argument the argument passed in with the command by the SMTP client 1404 */ 1405 private void doUnknownCmd(String command, String argument) { 1406 //responseBuffer.append("500 ").append(theConfigData.getHelloName()) 1407 responseBuffer.append("500 ").append(preferences.get("heloName", DEFAULT_HELO_NAME)) 1408 .append(" Syntax error, command unrecognized: ").append(command); 1409 1410 String responseString = clearResponseBuffer(); 1411 writeLoggedFlushedResponse(responseString); 1412 } 1413 1414 /*** 1415 * Handler method called upon receipt of a VRFY command. 1416 * This method informs the client that the command is 1417 * not implemented. 1418 * 1419 * @param argument the argument passed in with the command by the SMTP client 1420 */ 1421 private void doVRFY(String argument) { 1422 String responseString = "502 VRFY is not supported"; 1423 writeLoggedFlushedResponse(responseString); 1424 } 1425 1426 /*** 1427 * This method parses SMTP commands read off the wire in handleConnection. 1428 * Actual processing of the command (possibly including additional back and 1429 * forth communication with the client) is delegated to one of a number of 1430 * command specific handler methods. The primary purpose of this method is 1431 * to parse the raw command string to determine exactly which handler should 1432 * be called. It returns true if expecting additional commands, false otherwise. 1433 * 1434 * @param rawCommand the raw command string passed in over the socket 1435 * 1436 * @return whether additional commands are expected. 1437 */ 1438 private boolean parseCommand(String rawCommand) throws Exception { 1439 1440 1441 1442 String argument = null; 1443 boolean returnValue = true; 1444 String command = rawCommand; 1445 1446 //System.out.println("Command received: " + command); 1447 1448 if (rawCommand == null) { 1449 return false; 1450 } 1451 1452 /*if ((state.get(MESG_FAILED) == null) && (getLogger().isDebugEnabled())) { 1453 getLogger().debug("Command received: " + command); 1454 }*/ 1455 1456 1457 int spaceIndex = command.indexOf(" "); 1458 1459 if (spaceIndex > 0) { 1460 argument = command.substring(spaceIndex + 1); 1461 command = command.substring(0, spaceIndex); 1462 } 1463 1464 command = command.toUpperCase(Locale.US); 1465 1466 if (command.equals(COMMAND_HELO)) { 1467 doHELO(argument); 1468 } else if (command.equals(COMMAND_EHLO)) { 1469 doEHLO(argument); 1470 } else if (command.equals(COMMAND_AUTH)) { 1471 doAUTH(argument); 1472 } else if (command.equals(COMMAND_MAIL)) { 1473 doMAIL(argument); 1474 } else if (command.equals(COMMAND_RCPT)) { 1475 doRCPT(argument); 1476 } else if (command.equals(COMMAND_NOOP)) { 1477 doNOOP(argument); 1478 } else if (command.equals(COMMAND_RSET)) { 1479 doRSET(argument); 1480 } else if (command.equals(COMMAND_DATA)) { 1481 doDATA(argument); 1482 } else if (command.equals(COMMAND_QUIT)) { 1483 doQUIT(argument); 1484 returnValue = false; 1485 } else if (command.equals(COMMAND_VRFY)) { 1486 doVRFY(argument); 1487 } else if (command.equals(COMMAND_EXPN)) { 1488 doEXPN(argument); 1489 } else if (command.equals(COMMAND_HELP)) { 1490 doHELP(argument); 1491 } else { 1492 if (state.get(MESG_FAILED) == null) { 1493 doUnknownCmd(command, argument); 1494 } 1495 } 1496 1497 return returnValue; 1498 } 1499 1500 /*** processes the full mail including the headers */ 1501 private void processFullMail(InputStream msgIn) throws MessagingException{ 1502 setMessage(new MimeMessage(currentSession, msgIn)); 1503 1504 //setRawMessage(new String(fullMessage)); 1505 //if ((getRawMessage() != null) && (getMessage() != null)){ 1506 FilterManager manager = new FilterManager(getRawMessage(), getMessage()); 1507 manager.start(); 1508 //} 1509 } 1510 1511 /*** 1512 * Processes the mail message coming in off the wire. Reads the 1513 * content and delivers to the spool. 1514 * 1515 * @param headers the headers of the mail being read 1516 * @param msgIn the stream containing the message content 1517 */ 1518 private void processMail(MailHeaders headers, InputStream msgIn) 1519 throws MessagingException { 1520 ByteArrayInputStream headersIn = null; 1521 //MailImpl mail = null; 1522 List recipientCollection = null; 1523 1524 1525 1526 1527 /* 1528 try { 1529 headersIn = new ByteArrayInputStream(headers.toByteArray()); 1530 recipientCollection = (List) state.get(RCPT_LIST); 1531 mail = new MailImpl(theConfigData.getMailServer().getId(), 1532 (MailAddress) state.get(SENDER), recipientCollection, 1533 new SequenceInputStream(headersIn, msgIn)); 1534 1535 1536 1537 // Call mail.getSize() to force the message to be 1538 // loaded. Need to do this to enforce the size limit 1539 //if (theConfigData.getMaxMessageSize() > 0) { 1540 if (preferences.getLong("maxMessageSize", DEFAULT_MAX_MESSAGE_SIZE) > 0) { 1541 mail.getMessageSize(); 1542 } 1543 1544 mail.setRemoteHost(remoteHost); 1545 mail.setRemoteAddr(remoteIP); 1546 1547 1548 theConfigData.getMailServer().sendMail(mail); 1549 1550 Collection theRecipients = mail.getRecipients(); 1551 String recipientString = ""; 1552 1553 if (theRecipients != null) { 1554 recipientString = theRecipients.toString(); 1555 } 1556 1557 if (getLogger().isDebugEnabled()) { 1558 StringBuffer debugBuffer = new StringBuffer(256).append( 1559 "Successfully spooled mail )").append(mail.getName()) 1560 .append(" from ") 1561 .append(mail 1562 .getSender()).append(" for ").append(recipientString); 1563 getLogger().debug(debugBuffer.toString()); 1564 } else if (getLogger().isInfoEnabled()) { 1565 StringBuffer infoBuffer = new StringBuffer(256).append( 1566 "Successfully spooled mail from ") 1567 .append(mail 1568 .getSender()).append(" for ").append(recipientString); 1569 getLogger().info(infoBuffer.toString()); 1570 } 1571 } finally { 1572 1573 if (recipientCollection != null) { 1574 recipientCollection.clear(); 1575 } 1576 1577 recipientCollection = null; 1578 1579 if (mail != null) { 1580 mail.dispose(); 1581 } 1582 1583 mail = null; 1584 1585 if (headersIn != null) { 1586 try { 1587 headersIn.close(); 1588 } catch (IOException ioe) { 1589 // Ignore exception on close. 1590 } 1591 } 1592 1593 headersIn = null; 1594 1595 } 1596 */ 1597 1598 1599 } 1600 1601 /*** 1602 * DOCUMENT ME! 1603 * 1604 * @param headers DOCUMENT ME! 1605 * 1606 * @return DOCUMENT ME! 1607 * 1608 * @throws MessagingException DOCUMENT ME! 1609 */ 1610 private MailHeaders processMailHeaders(MailHeaders headers) 1611 throws MessagingException { 1612 1613 if (message == null){ 1614 message = new MimeMessage(currentSession); 1615 } 1616 1617 // If headers do not contains minimum REQUIRED headers fields, 1618 // add them 1619 if (!headers.isSet(RFC2822Headers.DATE)) { 1620 headers.setHeader(RFC2822Headers.DATE, 1621 rfc822DateFormat.format(new Date())); 1622 1623 1624 } 1625 1626 if (!headers.isSet(RFC2822Headers.FROM) && (state.get(SENDER) != null)) { 1627 headers.setHeader(RFC2822Headers.FROM, state.get(SENDER).toString()); 1628 } 1629 1630 // Determine the Return-Path 1631 String returnPath = headers.getHeader(RFC2822Headers.RETURN_PATH, "\r\n"); 1632 headers.removeHeader(RFC2822Headers.RETURN_PATH); 1633 1634 StringBuffer headerLineBuffer = new StringBuffer(512); 1635 1636 if (returnPath == null) { 1637 if (state.get(SENDER) == null) { 1638 returnPath = "<>"; 1639 } else { 1640 headerLineBuffer.append("<").append(state.get(SENDER)).append(">"); 1641 returnPath = headerLineBuffer.toString(); 1642 headerLineBuffer.delete(0, headerLineBuffer.length()); 1643 } 1644 } 1645 1646 // We will rebuild the header object to put Return-Path and our 1647 // Received header at the top 1648 Enumeration headerLines = headers.getAllHeaderLines(); 1649 MailHeaders newHeaders = new MailHeaders(); 1650 1651 // Put the Return-Path first 1652 newHeaders.addHeaderLine(RFC2822Headers.RETURN_PATH + ": " + returnPath); 1653 1654 // Put our Received header next 1655 headerLineBuffer.append(RFC2822Headers.RECEIVED + ": from ") 1656 .append(remoteHost).append(" ([").append(remoteIP) 1657 .append("])"); 1658 1659 newHeaders.addHeaderLine(headerLineBuffer.toString()); 1660 message.addHeaderLine(headerLineBuffer.toString()); 1661 headerLineBuffer.delete(0, headerLineBuffer.length()); 1662 1663 headerLineBuffer.append(" by ") 1664 //.append(theConfigData.getHelloName()).append(" (") 1665 .append(preferences.get("heloName", DEFAULT_HELO_NAME)).append(" (") 1666 .append(SOFTWARE_TYPE).append(") with SMTP ID ").append(smtpID); 1667 1668 if (((Collection) state.get(RCPT_LIST)).size() == 1) { 1669 // Only indicate a recipient if they're the only recipient 1670 // (prevents email address harvesting and large headers in 1671 // bulk email) 1672 newHeaders.addHeaderLine(headerLineBuffer.toString()); 1673 message.addHeaderLine(headerLineBuffer.toString()); 1674 headerLineBuffer.delete(0, headerLineBuffer.length()); 1675 headerLineBuffer.append(" for <") 1676 .append(((List) state.get(RCPT_LIST)).get(0) 1677 .toString()).append(">;"); 1678 newHeaders.addHeaderLine(headerLineBuffer.toString()); 1679 message.addHeaderLine(headerLineBuffer.toString()); 1680 headerLineBuffer.delete(0, headerLineBuffer.length()); 1681 } else { 1682 // Put the ; on the end of the 'by' line 1683 headerLineBuffer.append(";"); 1684 newHeaders.addHeaderLine(headerLineBuffer.toString()); 1685 message.addHeaderLine(headerLineBuffer.toString()); 1686 headerLineBuffer.delete(0, headerLineBuffer.length()); 1687 } 1688 1689 headerLineBuffer = null; 1690 newHeaders.addHeaderLine(" " 1691 + rfc822DateFormat.format(new Date())); 1692 message.addHeaderLine(" " 1693 + rfc822DateFormat.format(new Date())); 1694 1695 // Add all the original message headers back in next 1696 while (headerLines.hasMoreElements()) { 1697 newHeaders.addHeaderLine((String) headerLines.nextElement()); 1698 message.addHeaderLine((String) headerLines.nextElement()); 1699 } 1700 1701 return newHeaders; 1702 } 1703 1704 /*** 1705 * Resets the handler data to a basic state. 1706 */ 1707 private void resetHandler() { 1708 resetState(); 1709 1710 clearResponseBuffer(); 1711 in = null; 1712 inReader = null; 1713 out = null; 1714 remoteHost = null; 1715 remoteIP = null; 1716 authenticatedUser = null; 1717 smtpID = null; 1718 1719 if (theWatchdog != null) { 1720 /*if (theWatchdog instanceof Disposable) { 1721 ((Disposable) theWatchdog).dispose(); 1722 }*/ 1723 1724 theWatchdog = null; 1725 } 1726 1727 try { 1728 if (socket != null) { 1729 socket.close(); 1730 } 1731 } catch (IOException e) { 1732 /*if (getLogger().isErrorEnabled()) { 1733 getLogger().error("Exception closing socket: " + e.getMessage()); 1734 }*/ 1735 } finally { 1736 socket = null; 1737 } 1738 1739 synchronized (this) { 1740 handlerThread = null; 1741 } 1742 } 1743 1744 /*** 1745 * Resets message-specific, but not authenticated user, state. 1746 * 1747 */ 1748 private void resetState() { 1749 ArrayList recipients = (ArrayList) state.get(RCPT_LIST); 1750 1751 if (recipients != null) { 1752 recipients.clear(); 1753 } 1754 1755 state.clear(); 1756 } 1757 1758 1759 1760 1761 /*** Reads a single message from the given reader, halting when the 1762 sequence CRLF-dot-CRLF is received, or the maximum message length 1763 is reached. 1764 @param reader the reader 1765 @return the message 1766 @exception IOException if any other IOException occurs 1767 */ 1768 private String readMessage(BufferedReader reader) throws IOException { 1769 StringBuffer buffer = new StringBuffer(); 1770 1771 boolean done = false; 1772 1773 while (!done) { 1774 // Read a line and add it to the buffer 1775 String line = readLine(reader, 1776 preferences.getInt("maxLineLength", 1000)); 1777 1778 if (line.equals(".\r\n") 1779 || (buffer.length() > preferences.getInt("maxLineLength", 1780 1000))) { 1781 // end of buffer 1782 done = true; 1783 } else if (line.startsWith(".")) { 1784 buffer.append(line.substring(1)); 1785 } else { 1786 buffer.append(line); 1787 } 1788 } 1789 1790 return (buffer.toString()); 1791 } 1792 1793 1794 /*** Reads a single line from the given reader, including EOL 1795 (\r\n); truncates the line at maxLength characters. 1796 @param reader the reader 1797 @param maxLength maximum length of the line 1798 @return the line 1799 @exception EOFException if an EOF is read 1800 @exception IOException if any other IOException occurs 1801 */ 1802 private String readLine(Reader reader, int maxLength) 1803 throws IOException { 1804 StringBuffer buffer = new StringBuffer(); 1805 short prev; 1806 short curr = 0; 1807 1808 do { 1809 prev = curr; 1810 curr = (short) reader.read(); 1811 1812 if (curr == -1) { 1813 throw new EOFException(); 1814 } 1815 1816 if (buffer.length() <= maxLength) { 1817 buffer.append((char) curr); 1818 } 1819 } while (!((prev == '\r') && (curr == '\n'))); 1820 1821 //System.out.println("ConnectionHandler.readLine()_read this: " + buffer.toString()); 1822 return (buffer.toString()); 1823 } 1824 1825 1826 1827 1828 1829 1830 1831 //~ Inner Classes ---------------------------------------------------------- 1832 1833 /*** 1834 * A private inner class which serves as an adaptor 1835 * between the WatchdogTarget interface and this 1836 * handler class. 1837 */ 1838 private class SMTPWatchdogTarget implements WatchdogTarget { 1839 //~ Methods ------------------------------------------------------------ 1840 1841 /*** 1842 * @see org.apache.james.util.watchdog.WatchdogTarget#execute() 1843 */ 1844 public void execute() { 1845 SMTPHandler.this.idleClose(); 1846 } 1847 } 1848 1849 public void run(){ 1850 1851 try { 1852 handleConnection(); 1853 } catch (IOException e) { 1854 // log error 1855 System.out.println("SMTPHandler.run(): " + e); 1856 } 1857 } 1858 1859 1860 /*** 1861 * Returns the rawMessage. 1862 * @return String 1863 */ 1864 public String getRawMessage() { 1865 return rawMessage; 1866 } 1867 1868 /*** 1869 * Sets the rawMessage. 1870 * @param rawMessage The rawMessage to set 1871 */ 1872 public void setRawMessage(String rawMessage) { 1873 this.rawMessage = rawMessage; 1874 } 1875 1876 /*** 1877 * Returns the message. 1878 * @return MimeMessage 1879 */ 1880 public MimeMessage getMessage() { 1881 return message; 1882 } 1883 1884 /*** 1885 * Sets the message. 1886 * @param message The message to set 1887 */ 1888 public void setMessage(MimeMessage message) { 1889 this.message = message; 1890 } 1891 1892 /*** extracts an e-mail address from a String 1893 * 1894 * @param addressString the string you wish to extract the e-mail address 1895 * from 1896 * @return String 1897 */ 1898 public static String extractEmailAddress(String addressString){ 1899 if (addressString.indexOf("<") > -1){ 1900 addressString = addressString.substring(addressString.indexOf("<")+1, addressString.lastIndexOf(">")); 1901 if (addressString.indexOf("<") > -1){ 1902 addressString = extractEmailAddress(addressString); 1903 } 1904 } 1905 1906 return addressString; 1907 } 1908 1909 1910 1911 1912 1913 1914 } 1915

This page was automatically generated by Maven