1 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 ; 35 import java.io.*; 36 import java.net.Socket ; 37 import java.net.SocketException ; 38 import java.util.*; 39 45 public class SMTPHandler 46 extends AbstractLogEnabled 47 implements ConnectionHandler, Poolable { 48 49 52 private final static String SOFTWARE_TYPE = "JAMES SMTP Server " 53 + Constants.SOFTWARE_VERSION; 54 55 57 private final static String CURRENT_HELO_MODE = "CURRENT_HELO_MODE"; private final static String SENDER = "SENDER_ADDRESS"; private final static String MESG_FAILED = "MESG_FAILED"; private final static String MESG_SIZE = "MESG_SIZE"; private final static String RCPT_LIST = "RCPT_LIST"; 63 66 private final static char[] SMTPTerminator = { '\r', '\n', '.', '\r', '\n' }; 67 68 71 private final static Random random = new Random(); 72 73 76 private final static RFC822DateFormat rfc822DateFormat = new RFC822DateFormat(); 77 78 81 private final static String COMMAND_HELO = "HELO"; 82 83 86 private final static String COMMAND_EHLO = "EHLO"; 87 88 91 private final static String COMMAND_AUTH = "AUTH"; 92 93 96 private final static String COMMAND_MAIL = "MAIL"; 97 98 101 private final static String COMMAND_RCPT = "RCPT"; 102 103 106 private final static String COMMAND_NOOP = "NOOP"; 107 108 111 private final static String COMMAND_RSET = "RSET"; 112 113 116 private final static String COMMAND_DATA = "DATA"; 117 118 121 private final static String COMMAND_QUIT = "QUIT"; 122 123 126 private final static String COMMAND_HELP = "HELP"; 127 128 131 private final static String COMMAND_VRFY = "VRFY"; 132 133 136 private final static String COMMAND_EXPN = "EXPN"; 137 138 141 private final static String AUTH_TYPE_PLAIN = "PLAIN"; 142 143 146 private final static String AUTH_TYPE_LOGIN = "LOGIN"; 147 148 151 private final static String MAIL_OPTION_SIZE = "SIZE"; 152 153 156 private final static String SMTP_AUTH_USER_ATTRIBUTE_NAME = "org.apache.james.SMTPAuthUser"; 157 158 161 private Thread handlerThread; 162 163 167 private Socket socket; 168 169 172 private InputStream in; 173 174 177 private PrintWriter out; 178 179 182 private BufferedReader inReader; 183 184 187 private String remoteHost; 188 189 192 private String remoteIP; 193 194 197 private String authenticatedUser; 198 199 202 private boolean authRequired; 203 204 207 private boolean relayingAllowed; 208 209 212 private String smtpID; 213 214 217 private SMTPHandlerConfigurationData theConfigData; 218 219 226 private HashMap state = new HashMap(); 227 228 231 Watchdog theWatchdog; 232 233 236 WatchdogTarget theWatchdogTarget = new SMTPWatchdogTarget(); 237 238 241 StringBuffer responseBuffer = new StringBuffer (256); 242 243 248 void setConfigurationData(SMTPHandlerConfigurationData theData) { 249 theConfigData = theData; 250 } 251 252 257 void setWatchdog(Watchdog theWatchdog) { 258 this.theWatchdog = theWatchdog; 259 } 260 261 267 WatchdogTarget getWatchdogTarget() { 268 return theWatchdogTarget; 269 } 270 271 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 e) { 283 } 285 286 synchronized (this) { 287 if (handlerThread != null) { 289 handlerThread.interrupt(); 290 } 291 } 292 } 293 294 297 public void handleConnection(Socket 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 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 e) { 317 StringBuffer exceptionBuffer = 318 new StringBuffer (256) 319 .append("Cannot open connection from ") 320 .append(remoteHost) 321 .append(" (") 322 .append(remoteIP) 323 .append("): ") 324 .append(e.getMessage()); 325 String exceptionString = exceptionBuffer.toString(); 326 getLogger().error(exceptionString, e ); 327 throw new RuntimeException (exceptionString); 328 } 329 330 if (getLogger().isInfoEnabled()) { 331 StringBuffer infoBuffer = 332 new StringBuffer (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 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 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 se) { 364 if (getLogger().isErrorEnabled()) { 365 StringBuffer errorBuffer = 366 new StringBuffer (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 errorBuffer = 377 new StringBuffer (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 errorBuffer = 388 new StringBuffer (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 e) { 398 if (getLogger().isErrorEnabled()) { 399 getLogger().error( "Exception opening socket: " 400 + e.getMessage(), e ); 401 } 402 } finally { 403 resetHandler(); 404 } 405 } 406 407 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 453 private String clearResponseBuffer() { 454 String responseString = responseBuffer.toString(); 455 responseBuffer.delete(0,responseBuffer.length()); 456 return responseString; 457 } 458 459 467 private final void logResponseString(String responseString) { 468 if (getLogger().isDebugEnabled()) { 469 getLogger().debug("Sent: " + responseString); 470 } 471 } 472 473 480 final void writeLoggedFlushedResponse(String responseString) { 481 out.println(responseString); 482 out.flush(); 483 logResponseString(responseString); 484 } 485 486 492 final void writeLoggedResponse(String responseString) { 493 out.println(responseString); 494 logResponseString(responseString); 495 } 496 497 503 final String readCommandLine() throws IOException { 504 for (;;) try { 505 String 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 520 private void setUser(String userID) { 521 authenticatedUser = userID; 522 } 523 524 529 private String getUser() { 530 return authenticatedUser; 531 } 532 533 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 557 private boolean parseCommand(String command) throws Exception { 558 String 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 614 private void doHELO(String argument) { 615 String 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 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 654 private void doEHLO(String argument) { 655 String 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 long maxMessageSize = theConfigData.getMaxMessageSize(); 664 if (maxMessageSize > 0) { 665 responseString = "250-SIZE " + maxMessageSize; 666 writeLoggedResponse(responseString); 667 } 668 if (authRequired) { 669 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 699 private void doAUTH(String argument) 700 throws Exception { 701 String 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 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 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 742 private void doPlainAuth(String initialResponse) 743 throws IOException { 744 String 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 771 StringTokenizer authTokenizer = new StringTokenizer(userpass, "\0"); 772 String authorize_id = authTokenizer.nextToken(); user = authTokenizer.nextToken(); try { 775 pass = authTokenizer.nextToken(); } 777 catch (java.util.NoSuchElementException _) { 778 pass = user; 794 user = authorize_id; 795 } 796 797 authTokenizer = null; 798 } 799 } 800 catch (Exception e) { 801 } 804 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 826 private void doLoginAuth(String initialResponse) 827 throws IOException { 828 String user = null, pass = null, responseString = null; 829 if (initialResponse == null) { 830 responseString = "334 VXNlcm5hbWU6"; 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 e) { 840 user = null; 843 } 844 } 845 responseString = "334 UGFzc3dvcmQ6"; writeLoggedFlushedResponse(responseString); 847 pass = readCommandLine(); 848 if (pass != null) { 849 try { 850 pass = Base64.decodeAsString(pass); 851 } catch (Exception e) { 852 pass = null; 855 } 856 } 857 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 getLogger().debug("AUTH method LOGIN succeeded"); 866 } 867 } else { 868 responseString = "535 Authentication Failed"; 869 getLogger().error("AUTH method LOGIN failed"); 871 } 872 writeLoggedFlushedResponse(responseString); 873 return; 874 } 875 876 882 private void doUnknownAuth(String authType, String initialResponse) { 883 String responseString = "504 Unrecognized Authentication Type"; 884 writeLoggedFlushedResponse(responseString); 885 if (getLogger().isErrorEnabled()) { 886 StringBuffer errorBuffer = 887 new StringBuffer (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 902 private void doMAIL(String argument) { 903 String responseString = null; 904 905 String 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 int lastChar = sender.indexOf('>', sender.indexOf('<')); 922 if ((lastChar > 0) && (sender.length() > lastChar + 2) && (sender.charAt(lastChar + 1) == ' ')) { 925 String mailOptionString = sender.substring(lastChar + 2); 926 927 sender = sender.substring(0, lastChar + 1); 929 930 StringTokenizer optionTokenizer = new StringTokenizer(mailOptionString, " "); 931 while (optionTokenizer.hasMoreElements()) { 932 String mailOption = optionTokenizer.nextToken(); 933 int equalIndex = mailOptionString.indexOf('='); 934 String mailOptionName = mailOption; 935 String mailOptionValue = ""; 936 if (equalIndex > 0) { 937 mailOptionName = mailOption.substring(0, equalIndex).toUpperCase(Locale.US); 938 mailOptionValue = mailOption.substring(equalIndex + 1); 939 } 940 941 943 if (mailOptionName.startsWith(MAIL_OPTION_SIZE)) { 944 if (!(doMailSize(mailOptionValue))) { 945 return; 946 } 947 } else { 948 if (getLogger().isDebugEnabled()) { 950 StringBuffer debugBuffer = 951 new StringBuffer (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 errorBuffer = 966 new StringBuffer (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 sender = sender.substring(1, sender.length() - 1); 977 if (sender.length() == 0) { 978 } else { 980 if (sender.indexOf("@") < 0) { 981 sender = sender + "@localhost"; 982 } 983 try { 984 senderAddress = new MailAddress(sender); 985 } catch (Exception pe) { 986 responseString = "501 Syntax error in sender address"; 987 writeLoggedFlushedResponse(responseString); 988 if (getLogger().isErrorEnabled()) { 989 StringBuffer errorBuffer = 990 new StringBuffer (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 1015 private boolean doMailSize(String mailOptionValue) { 1016 int size = 0; 1017 try { 1018 size = Integer.parseInt(mailOptionValue); 1019 } catch (NumberFormatException pe) { 1020 String 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 debugBuffer = 1028 new StringBuffer (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 String responseString = "552 Message size exceeds fixed maximum message size"; 1038 writeLoggedFlushedResponse(responseString); 1039 StringBuffer errorBuffer = 1040 new StringBuffer (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 state.put(MESG_SIZE, new Integer (size)); 1058 } 1059 return true; 1060 } 1061 1062 1068 private void doRCPT(String argument) { 1069 String responseString = null; 1070 1071 String 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 if ((lastChar > 0) && (recipient.length() > lastChar + 2) && (recipient.charAt(lastChar + 1) == ' ')) { 1094 String rcptOptionString = recipient.substring(lastChar + 2); 1095 1096 recipient = recipient.substring(0, lastChar + 1); 1098 1099 StringTokenizer optionTokenizer = new StringTokenizer(rcptOptionString, " "); 1100 while (optionTokenizer.hasMoreElements()) { 1101 String rcptOption = optionTokenizer.nextToken(); 1102 int equalIndex = rcptOptionString.indexOf('='); 1103 String rcptOptionName = rcptOption; 1104 String rcptOptionValue = ""; 1105 if (equalIndex > 0) { 1106 rcptOptionName = rcptOption.substring(0, equalIndex).toUpperCase(Locale.US); 1107 rcptOptionValue = rcptOption.substring(equalIndex + 1); 1108 } 1109 if (getLogger().isDebugEnabled()) { 1111 StringBuffer debugBuffer = 1112 new StringBuffer (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 errorBuffer = 1127 new StringBuffer (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 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 pe) { 1144 responseString = "501 Syntax error in recipient address"; 1145 writeLoggedFlushedResponse(responseString); 1146 1147 if (getLogger().isErrorEnabled()) { 1148 StringBuffer errorBuffer = 1149 new StringBuffer (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 if (getUser() == null) { 1162 String 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 if (theConfigData.isVerifyIdentity()) { 1172 String 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 errorBuffer = 1182 new StringBuffer (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 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 1218 private void doNOOP(String argument) { 1219 String responseString = "250 OK"; 1220 writeLoggedFlushedResponse(responseString); 1221 } 1222 1223 1229 private void doRSET(String argument) { 1230 String 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 1247 private void doDATA(String argument) { 1248 String 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 long maxMessageSize = theConfigData.getMaxMessageSize(); 1271 if (maxMessageSize > 0) { 1272 if (getLogger().isDebugEnabled()) { 1273 StringBuffer logBuffer = 1274 new StringBuffer (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 msgIn = new DotStuffingInputStream(msgIn); 1284 MailHeaders headers = new MailHeaders(msgIn); 1286 headers = processMailHeaders(headers); 1287 processMail(headers, msgIn); 1288 headers = null; 1289 } catch (MessagingException me) { 1290 Exception e = me.getNextException(); 1292 if (e != null && e instanceof MessageSizeException) { 1295 state.put(MESG_FAILED, Boolean.TRUE); 1300 responseString = "552 Error processing message: " 1303 + e.getMessage(); 1304 StringBuffer errorBuffer = 1305 new StringBuffer (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 e) { 1327 } 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 { 1340 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 String returnPath = headers.getHeader(RFC2822Headers.RETURN_PATH, "\r\n"); 1350 headers.removeHeader(RFC2822Headers.RETURN_PATH); 1351 StringBuffer headerLineBuffer = new StringBuffer (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 Enumeration headerLines = headers.getAllHeaderLines(); 1366 MailHeaders newHeaders = new MailHeaders(); 1367 StringTokenizer tokenizer = new StringTokenizer(returnPath, "\r\n"); 1371 while(tokenizer.hasMoreTokens()) { 1372 String path = tokenizer.nextToken(); 1373 newHeaders.addHeaderLine(RFC2822Headers.RETURN_PATH + ": " + path); 1374 } 1375 1376 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 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 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 while (headerLines.hasMoreElements()) { 1415 newHeaders.addHeaderLine((String ) headerLines.nextElement()); 1416 } 1417 return newHeaders; 1418 } 1419 1420 1427 private void processMail(MailHeaders headers, InputStream msgIn) 1428 throws MessagingException { 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 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 recipientString = ""; 1453 if (theRecipients != null) { 1454 recipientString = theRecipients.toString(); 1455 } 1456 if (getLogger().isInfoEnabled()) { 1457 StringBuffer infoBuffer = 1458 new StringBuffer (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 } 1482 } 1483 headersIn = null; 1484 } 1485 } 1486 1487 1494 private void doQUIT(String argument) { 1495 1496 String 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 1515 private void doVRFY(String argument) { 1516 String responseString = "502 VRFY is not supported"; 1517 writeLoggedFlushedResponse(responseString); 1518 } 1519 1520 1527 private void doEXPN(String argument) { 1528 1529 String responseString = "502 EXPN is not supported"; 1530 writeLoggedFlushedResponse(responseString); 1531 } 1532 1533 1540 private void doHELP(String argument) { 1541 1542 String responseString = "502 HELP is not supported"; 1543 writeLoggedFlushedResponse(responseString); 1544 } 1545 1546 1553 private void doUnknownCmd(String command, String argument) { 1554 responseBuffer.append("500 ") 1555 .append(theConfigData.getHelloName()) 1556 .append(" Syntax error, command unrecognized: ") 1557 .append(command); 1558 String responseString = clearResponseBuffer(); 1559 writeLoggedFlushedResponse(responseString); 1560 } 1561 1562 1567 private class SMTPWatchdogTarget 1568 implements WatchdogTarget { 1569 1570 1573 public void execute() { 1574 SMTPHandler.this.idleClose(); 1575 } 1576 1577 } 1578} 1579 | Popular Tags |