1 17 18 package org.apache.james.nntpserver; 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.configuration.Configurable; 24 import org.apache.avalon.framework.configuration.Configuration; 25 import org.apache.avalon.framework.configuration.ConfigurationException; 26 import org.apache.avalon.framework.logger.AbstractLogEnabled; 27 import org.apache.avalon.framework.logger.Logger; 28 import org.apache.james.core.MailHeaders; 29 import org.apache.james.nntpserver.repository.NNTPArticle; 30 import org.apache.james.nntpserver.repository.NNTPGroup; 31 import org.apache.james.nntpserver.repository.NNTPRepository; 32 import org.apache.james.services.UsersRepository; 33 import org.apache.james.services.UsersStore; 34 import org.apache.james.util.CharTerminatedInputStream; 35 import org.apache.james.util.DotStuffingInputStream; 36 import org.apache.james.util.ExtraDotOutputStream; 37 import org.apache.james.util.InternetPrintWriter; 38 import org.apache.james.util.RFC977DateFormat; 39 import org.apache.james.util.RFC2980DateFormat; 40 import org.apache.james.util.SimplifiedDateFormat; 41 import org.apache.james.util.watchdog.Watchdog; 42 import org.apache.james.util.watchdog.WatchdogTarget; 43 44 import java.io.BufferedInputStream ; 45 import java.io.BufferedOutputStream ; 46 import java.io.BufferedReader ; 47 import java.io.BufferedWriter ; 48 import java.io.ByteArrayInputStream ; 49 import java.io.IOException ; 50 import java.io.InputStream ; 51 import java.io.InputStreamReader ; 52 import java.io.OutputStream ; 53 import java.io.OutputStreamWriter ; 54 import java.io.PrintWriter ; 55 import java.io.SequenceInputStream ; 56 import java.net.Socket ; 57 import java.text.DateFormat ; 58 import java.text.ParseException ; 59 import java.util.ArrayList ; 60 import java.util.Calendar ; 61 import java.util.Date ; 62 import java.util.Iterator ; 63 import java.util.List ; 64 import java.util.Locale ; 65 import java.util.StringTokenizer ; 66 import javax.mail.MessagingException ; 67 68 75 public class NNTPHandler 76 extends AbstractLogEnabled 77 implements ConnectionHandler, Poolable { 78 79 82 private static final SimplifiedDateFormat DF_RFC977 = new RFC977DateFormat(); 83 84 87 private static final SimplifiedDateFormat DF_RFC2980 = new RFC2980DateFormat(); 88 89 92 public static final long UTC_OFFSET = Calendar.getInstance().get(Calendar.ZONE_OFFSET); 93 94 97 private final static String COMMAND_MODE = "MODE"; 98 99 102 private final static String COMMAND_LIST = "LIST"; 103 104 107 private final static String COMMAND_GROUP = "GROUP"; 108 109 112 private final static String COMMAND_NEXT = "NEXT"; 113 114 117 private final static String COMMAND_LAST = "LAST"; 118 119 122 private final static String COMMAND_ARTICLE = "ARTICLE"; 123 124 127 private final static String COMMAND_HEAD = "HEAD"; 128 129 132 private final static String COMMAND_BODY = "BODY"; 133 134 137 private final static String COMMAND_STAT = "STAT"; 138 139 142 private final static String COMMAND_POST = "POST"; 143 144 147 private final static String COMMAND_IHAVE = "IHAVE"; 148 149 152 private final static String COMMAND_QUIT = "QUIT"; 153 154 157 private final static String COMMAND_SLAVE = "SLAVE"; 158 159 162 private final static String COMMAND_DATE = "DATE"; 163 164 167 private final static String COMMAND_HELP = "HELP"; 168 169 172 private final static String COMMAND_NEWGROUPS = "NEWGROUPS"; 173 174 177 private final static String COMMAND_NEWNEWS = "NEWNEWS"; 178 179 182 private final static String COMMAND_LISTGROUP = "LISTGROUP"; 183 184 187 private final static String COMMAND_OVER = "OVER"; 188 189 192 private final static String COMMAND_XOVER = "XOVER"; 193 194 197 private final static String COMMAND_HDR = "HDR"; 198 199 202 private final static String COMMAND_XHDR = "XHDR"; 203 204 207 private final static String COMMAND_AUTHINFO = "AUTHINFO"; 208 209 212 private final static String COMMAND_PAT = "PAT"; 213 214 217 private final static String MODE_TYPE_READER = "READER"; 218 219 222 private final static String MODE_TYPE_STREAM = "STREAM"; 223 224 227 private final static String AUTHINFO_PARAM_USER = "USER"; 228 229 232 private final static String AUTHINFO_PARAM_PASS = "PASS"; 233 234 237 private final static char[] NNTPTerminator = { '\r', '\n', '.', '\r', '\n' }; 238 239 242 private Thread handlerThread; 243 244 247 private String remoteHost; 248 249 252 private String remoteIP; 253 254 258 private Socket socket; 259 260 263 private InputStream in; 264 265 268 private BufferedReader reader; 269 270 273 private OutputStream outs; 274 275 278 private PrintWriter writer; 279 280 283 private NNTPGroup group; 284 285 288 private int currentArticleNumber = -1; 289 290 294 private NNTPHandlerConfigurationData theConfigData; 295 296 299 private String user = null; 300 301 304 private String password = null; 305 306 310 boolean isAlreadyAuthenticated = false; 311 312 315 private Watchdog theWatchdog; 316 317 320 private WatchdogTarget theWatchdogTarget = new NNTPWatchdogTarget(); 321 322 327 void setConfigurationData(NNTPHandlerConfigurationData theData) { 328 theConfigData = theData; 329 } 330 331 336 void setWatchdog(Watchdog theWatchdog) { 337 this.theWatchdog = theWatchdog; 338 } 339 340 346 WatchdogTarget getWatchdogTarget() { 347 return theWatchdogTarget; 348 } 349 350 353 void idleClose() { 354 if (getLogger() != null) { 355 getLogger().error("NNTP Connection has idled out."); 356 } 357 try { 358 if (socket != null) { 359 socket.close(); 360 } 361 } catch (Exception e) { 362 } finally { 364 socket = null; 365 } 366 367 synchronized (this) { 368 if (handlerThread != null) { 370 handlerThread.interrupt(); 371 handlerThread = null; 372 } 373 } 374 } 375 376 379 public void handleConnection( Socket connection ) throws IOException { 380 try { 381 this.socket = connection; 382 synchronized (this) { 383 handlerThread = Thread.currentThread(); 384 } 385 remoteIP = socket.getInetAddress().getHostAddress(); 386 remoteHost = socket.getInetAddress().getHostName(); 387 in = new BufferedInputStream (socket.getInputStream(), 1024); 388 reader = new BufferedReader (new InputStreamReader (in, "ASCII"), 512); 392 outs = new BufferedOutputStream (socket.getOutputStream(), 1024); 393 writer = new InternetPrintWriter(outs, true); 394 } catch (Exception e) { 395 StringBuffer exceptionBuffer = 396 new StringBuffer (256) 397 .append("Cannot open connection from ") 398 .append(remoteHost) 399 .append(" (") 400 .append(remoteIP) 401 .append("): ") 402 .append(e.getMessage()); 403 String exceptionString = exceptionBuffer.toString(); 404 getLogger().error(exceptionString, e ); 405 } 406 407 try { 408 if ( theConfigData.getNNTPRepository().isReadOnly() ) { 410 StringBuffer respBuffer = 411 new StringBuffer (128) 412 .append("201 ") 413 .append(theConfigData.getHelloName()) 414 .append(" NNTP Service Ready, posting prohibited"); 415 writeLoggedFlushedResponse(respBuffer.toString()); 416 } else { 417 StringBuffer respBuffer = 418 new StringBuffer (128) 419 .append("200 ") 420 .append(theConfigData.getHelloName()) 421 .append(" NNTP Service Ready, posting permitted"); 422 writeLoggedFlushedResponse(respBuffer.toString()); 423 } 424 425 theWatchdog.start(); 426 while (parseCommand(reader.readLine())) { 427 theWatchdog.reset(); 428 } 429 theWatchdog.stop(); 430 431 getLogger().info("Connection closed"); 432 } catch (Exception e) { 433 if (socket != null) { 438 try { 439 doQUIT(null); 440 } catch (Throwable t) {} 441 getLogger().error( "Exception during connection:" + e.getMessage(), e ); 442 } 443 } finally { 444 resetHandler(); 445 } 446 } 447 448 451 private void resetHandler() { 452 453 if (theWatchdog != null) { 455 if (theWatchdog instanceof Disposable) { 456 ((Disposable)theWatchdog).dispose(); 457 } 458 theWatchdog = null; 459 } 460 461 try { 463 if (reader != null) { 464 reader.close(); 465 } 466 } catch (IOException ioe) { 467 getLogger().warn("NNTPHandler: Unexpected exception occurred while closing reader: " + ioe); 468 } finally { 469 reader = null; 470 } 471 472 in = null; 473 474 if (writer != null) { 475 writer.close(); 476 writer = null; 477 } 478 outs = null; 479 480 remoteHost = null; 481 remoteIP = null; 482 try { 483 if (socket != null) { 484 socket.close(); 485 } 486 } catch (IOException ioe) { 487 getLogger().warn("NNTPHandler: Unexpected exception occurred while closing socket: " + ioe); 488 } finally { 489 socket = null; 490 } 491 492 synchronized (this) { 493 handlerThread = null; 494 } 495 496 group = null; 498 currentArticleNumber = -1; 499 500 user = null; 502 password = null; 503 isAlreadyAuthenticated = false; 504 505 theConfigData = null; 507 } 508 509 521 private boolean parseCommand(String commandRaw) { 522 if (commandRaw == null) { 523 return false; 524 } 525 if (getLogger().isDebugEnabled()) { 526 getLogger().debug("Command received: " + commandRaw); 527 } 528 529 String command = commandRaw.trim(); 530 String argument = null; 531 int spaceIndex = command.indexOf(" "); 532 if (spaceIndex >= 0) { 533 argument = command.substring(spaceIndex + 1); 534 command = command.substring(0, spaceIndex); 535 } 536 command = command.toUpperCase(Locale.US); 537 538 boolean returnValue = true; 539 if (!isAuthorized(command) ) { 540 writeLoggedFlushedResponse("480 User is not authenticated"); 541 getLogger().debug("Command not allowed."); 542 return returnValue; 543 } 544 if ((command.equals(COMMAND_MODE)) && (argument != null)) { 545 if (argument.toUpperCase(Locale.US).equals(MODE_TYPE_READER)) { 546 doMODEREADER(argument); 547 } else if (argument.toUpperCase(Locale.US).equals(MODE_TYPE_STREAM)) { 548 doMODESTREAM(argument); 549 } else { 550 writeLoggedFlushedResponse("500 Command not understood"); 551 } 552 } else if ( command.equals(COMMAND_LIST)) { 553 doLIST(argument); 554 } else if ( command.equals(COMMAND_GROUP) ) { 555 doGROUP(argument); 556 } else if ( command.equals(COMMAND_NEXT) ) { 557 doNEXT(argument); 558 } else if ( command.equals(COMMAND_LAST) ) { 559 doLAST(argument); 560 } else if ( command.equals(COMMAND_ARTICLE) ) { 561 doARTICLE(argument); 562 } else if ( command.equals(COMMAND_HEAD) ) { 563 doHEAD(argument); 564 } else if ( command.equals(COMMAND_BODY) ) { 565 doBODY(argument); 566 } else if ( command.equals(COMMAND_STAT) ) { 567 doSTAT(argument); 568 } else if ( command.equals(COMMAND_POST) ) { 569 doPOST(argument); 570 } else if ( command.equals(COMMAND_IHAVE) ) { 571 doIHAVE(argument); 572 } else if ( command.equals(COMMAND_QUIT) ) { 573 doQUIT(argument); 574 returnValue = false; 575 } else if ( command.equals(COMMAND_DATE) ) { 576 doDATE(argument); 577 } else if ( command.equals(COMMAND_HELP) ) { 578 doHELP(argument); 579 } else if ( command.equals(COMMAND_NEWGROUPS) ) { 580 doNEWGROUPS(argument); 581 } else if ( command.equals(COMMAND_NEWNEWS) ) { 582 doNEWNEWS(argument); 583 } else if ( command.equals(COMMAND_LISTGROUP) ) { 584 doLISTGROUP(argument); 585 } else if ( command.equals(COMMAND_OVER) ) { 586 doOVER(argument); 587 } else if ( command.equals(COMMAND_XOVER) ) { 588 doXOVER(argument); 589 } else if ( command.equals(COMMAND_HDR) ) { 590 doHDR(argument); 591 } else if ( command.equals(COMMAND_XHDR) ) { 592 doXHDR(argument); 593 } else if ( command.equals(COMMAND_AUTHINFO) ) { 594 doAUTHINFO(argument); 595 } else if ( command.equals(COMMAND_SLAVE) ) { 596 doSLAVE(argument); 597 } else if ( command.equals(COMMAND_PAT) ) { 598 doPAT(argument); 599 } else { 600 doUnknownCommand(command, argument); 601 } 602 return returnValue; 603 } 604 605 611 private void doUnknownCommand(String command, String argument) { 612 if (getLogger().isDebugEnabled()) { 613 StringBuffer logBuffer = 614 new StringBuffer (128) 615 .append("Received unknown command ") 616 .append(command) 617 .append(" with argument ") 618 .append(argument); 619 getLogger().debug(logBuffer.toString()); 620 } 621 writeLoggedFlushedResponse("500 Unknown command"); 622 } 623 624 631 private void doAUTHINFO(String argument) { 632 String command = null; 633 String value = null; 634 if (argument != null) { 635 int spaceIndex = argument.indexOf(" "); 636 if (spaceIndex >= 0) { 637 command = argument.substring(0, spaceIndex); 638 value = argument.substring(spaceIndex + 1); 639 } 640 } 641 if (command == null) { 642 writeLoggedFlushedResponse("501 Syntax error"); 643 return; 644 } 645 command = command.toUpperCase(Locale.US); 646 if ( command.equals(AUTHINFO_PARAM_USER) ) { 647 if ( isAlreadyAuthenticated ) { 649 writeLoggedFlushedResponse("482 Already authenticated - rejecting new credentials"); 650 } 651 if (user != null) { 653 user = null; 654 password = null; 655 isAlreadyAuthenticated = false; 656 writeLoggedFlushedResponse("482 User already specified - rejecting new user"); 657 return; 658 } 659 user = value; 660 writeLoggedFlushedResponse("381 More authentication information required"); 661 } else if ( command.equals(AUTHINFO_PARAM_PASS) ) { 662 if (user == null) { 664 writeLoggedFlushedResponse("482 User not yet specified. Rejecting user."); 665 return; 666 } 667 if (password != null) { 669 user = null; 670 password = null; 671 isAlreadyAuthenticated = false; 672 writeLoggedFlushedResponse("482 Password already specified - rejecting new password"); 673 return; 674 } 675 password = value; 676 isAlreadyAuthenticated = isAuthenticated(); 677 if ( isAlreadyAuthenticated ) { 678 writeLoggedFlushedResponse("281 Authentication accepted"); 679 } else { 680 writeLoggedFlushedResponse("482 Authentication rejected"); 681 user = null; 683 password = null; 684 } 685 } else { 686 writeLoggedFlushedResponse("501 Syntax error"); 687 return; 688 } 689 } 690 691 698 private void doNEWNEWS(String argument) { 699 701 String wildmat = "*"; 702 703 if (argument != null) { 704 int spaceIndex = argument.indexOf(" "); 705 if (spaceIndex >= 0) { 706 wildmat = argument.substring(0, spaceIndex); 707 argument = argument.substring(spaceIndex + 1); 708 } else { 709 getLogger().error("NEWNEWS had an invalid argument"); 710 writeLoggedFlushedResponse("501 Syntax error"); 711 return; 712 } 713 } else { 714 getLogger().error("NEWNEWS had a null argument"); 715 writeLoggedFlushedResponse("501 Syntax error"); 716 return; 717 } 718 719 Date theDate = null; 720 try { 721 theDate = getDateFrom(argument); 722 } catch (NNTPException nntpe) { 723 getLogger().error("NEWNEWS had an invalid argument", nntpe); 724 writeLoggedFlushedResponse("501 Syntax error"); 725 return; 726 } 727 728 writeLoggedFlushedResponse("230 list of new articles by message-id follows"); 729 Iterator groupIter = theConfigData.getNNTPRepository().getMatchedGroups(wildmat); 730 while ( groupIter.hasNext() ) { 731 Iterator articleIter = ((NNTPGroup)(groupIter.next())).getArticlesSince(theDate); 732 while (articleIter.hasNext()) { 733 StringBuffer iterBuffer = 734 new StringBuffer (64) 735 .append(((NNTPArticle)articleIter.next()).getUniqueID()); 736 writeLoggedResponse(iterBuffer.toString()); 737 } 738 } 739 writeLoggedFlushedResponse("."); 740 } 741 742 749 private void doNEWGROUPS(String argument) { 750 Date theDate = null; 760 try { 761 theDate = getDateFrom(argument); 762 } catch (NNTPException nntpe) { 763 getLogger().error("NEWGROUPS had an invalid argument", nntpe); 764 writeLoggedFlushedResponse("501 Syntax error"); 765 return; 766 } 767 Iterator iter = theConfigData.getNNTPRepository().getGroupsSince(theDate); 768 writeLoggedFlushedResponse("231 list of new newsgroups follows"); 769 while ( iter.hasNext() ) { 770 NNTPGroup currentGroup = (NNTPGroup)iter.next(); 771 StringBuffer iterBuffer = 772 new StringBuffer (128) 773 .append(currentGroup.getName()) 774 .append(" ") 775 .append(currentGroup.getLastArticleNumber()) 776 .append(" ") 777 .append(currentGroup.getFirstArticleNumber()) 778 .append(" ") 779 .append((currentGroup.isPostAllowed()?"y":"n")); 780 writeLoggedResponse(iterBuffer.toString()); 781 } 782 writeLoggedFlushedResponse("."); 783 } 784 785 790 private void doHELP(String argument) { 791 writeLoggedResponse("100 Help text follows"); 792 writeLoggedFlushedResponse("."); 793 } 794 795 801 private void doSLAVE(String argument) { 802 writeLoggedFlushedResponse("202 slave status noted"); 803 } 804 805 810 private void doDATE(String argument) { 811 Date dt = new Date (System.currentTimeMillis()-UTC_OFFSET); 812 String dtStr = DF_RFC2980.format(new Date (dt.getTime() - UTC_OFFSET)); 813 writeLoggedFlushedResponse("111 " + dtStr); 814 } 815 816 821 private void doQUIT(String argument) { 822 writeLoggedFlushedResponse("205 closing connection"); 823 } 824 825 830 private void doLIST(String argument) { 831 String wildmat = "*"; 833 boolean isListNewsgroups = false; 834 835 String extension = argument; 836 if (argument != null) { 837 int spaceIndex = argument.indexOf(" "); 838 if (spaceIndex >= 0) { 839 wildmat = argument.substring(spaceIndex + 1); 840 extension = argument.substring(0, spaceIndex); 841 } 842 extension = extension.toUpperCase(Locale.US); 843 } 844 845 if (extension != null) { 846 if (extension.equals("ACTIVE")) { 847 isListNewsgroups = false; 848 } else if (extension.equals("NEWSGROUPS") ) { 849 isListNewsgroups = true; 850 } else if (extension.equals("EXTENSIONS") ) { 851 doLISTEXTENSIONS(); 852 return; 853 } else if (extension.equals("OVERVIEW.FMT") ) { 854 doLISTOVERVIEWFMT(); 855 return; 856 } else if (extension.equals("ACTIVE.TIMES") ) { 857 writeLoggedFlushedResponse("503 program error, function not performed"); 859 return; 860 } else if (extension.equals("DISTRIBUTIONS") ) { 861 writeLoggedFlushedResponse("503 program error, function not performed"); 863 return; 864 } else if (extension.equals("DISTRIB.PATS") ) { 865 writeLoggedFlushedResponse("503 program error, function not performed"); 867 return; 868 } else { 869 writeLoggedFlushedResponse("501 Syntax error"); 870 return; 871 } 872 } 873 874 Iterator iter = theConfigData.getNNTPRepository().getMatchedGroups(wildmat); 875 writeLoggedFlushedResponse("215 list of newsgroups follows"); 876 while ( iter.hasNext() ) { 877 NNTPGroup theGroup = (NNTPGroup)iter.next(); 878 if (isListNewsgroups) { 879 writeLoggedResponse(theGroup.getListNewsgroupsFormat()); 880 } else { 881 writeLoggedResponse(theGroup.getListFormat()); 882 } 883 } 884 writeLoggedFlushedResponse("."); 885 } 886 887 893 private void doIHAVE(String id) { 894 if (!isMessageId(id)) { 896 writeLoggedFlushedResponse("501 command syntax error"); 897 return; 898 } 899 NNTPArticle article = theConfigData.getNNTPRepository().getArticleFromID(id); 900 if ( article != null ) { 901 writeLoggedFlushedResponse("435 article not wanted - do not send it"); 902 } else { 903 writeLoggedFlushedResponse("335 send article to be transferred. End with <CR-LF>.<CR-LF>"); 904 try { 905 createArticle(); 906 } catch (RuntimeException e) { 907 writeLoggedFlushedResponse("436 transfer failed - try again later"); 908 throw e; 909 } 910 writeLoggedFlushedResponse("235 article received ok"); 911 } 912 } 913 914 919 private void doPOST(String argument) { 920 if ( argument != null ) { 922 writeLoggedFlushedResponse("501 Syntax error - unexpected parameter"); 923 } 924 writeLoggedFlushedResponse("340 send article to be posted. End with <CR-LF>.<CR-LF>"); 925 createArticle(); 926 writeLoggedFlushedResponse("240 article received ok"); 927 } 928 929 938 private void doSTAT(String param) { 939 NNTPArticle article = null; 941 if (isMessageId(param)) { 942 article = theConfigData.getNNTPRepository().getArticleFromID(param); 943 if ( article == null ) { 944 writeLoggedFlushedResponse("430 no such article"); 945 return; 946 } else { 947 StringBuffer respBuffer = 948 new StringBuffer (64) 949 .append("223 0 ") 950 .append(param); 951 writeLoggedFlushedResponse(respBuffer.toString()); 952 } 953 } else { 954 int newArticleNumber = currentArticleNumber; 955 if ( group == null ) { 956 writeLoggedFlushedResponse("412 no newsgroup selected"); 957 return; 958 } else { 959 if ( param == null ) { 960 if ( currentArticleNumber < 0 ) { 961 writeLoggedFlushedResponse("420 no current article selected"); 962 return; 963 } else { 964 article = group.getArticle(currentArticleNumber); 965 } 966 } 967 else { 968 newArticleNumber = Integer.parseInt(param); 969 article = group.getArticle(newArticleNumber); 970 } 971 if ( article == null ) { 972 writeLoggedFlushedResponse("423 no such article number in this group"); 973 return; 974 } else { 975 currentArticleNumber = newArticleNumber; 976 String articleID = article.getUniqueID(); 977 if (articleID == null) { 978 articleID = "<0>"; 979 } 980 StringBuffer respBuffer = 981 new StringBuffer (128) 982 .append("223 ") 983 .append(article.getArticleNumber()) 984 .append(" ") 985 .append(articleID); 986 writeLoggedFlushedResponse(respBuffer.toString()); 987 } 988 } 989 } 990 } 991 992 1001 private void doBODY(String param) { 1002 NNTPArticle article = null; 1004 if (isMessageId(param)) { 1005 article = theConfigData.getNNTPRepository().getArticleFromID(param); 1006 if ( article == null ) { 1007 writeLoggedFlushedResponse("430 no such article"); 1008 return; 1009 } else { 1010 StringBuffer respBuffer = 1011 new StringBuffer (64) 1012 .append("222 0 ") 1013 .append(param); 1014 writeLoggedFlushedResponse(respBuffer.toString()); 1015 } 1016 } else { 1017 int newArticleNumber = currentArticleNumber; 1018 if ( group == null ) { 1019 writeLoggedFlushedResponse("412 no newsgroup selected"); 1020 return; 1021 } else { 1022 if ( param == null ) { 1023 if ( currentArticleNumber < 0 ) { 1024 writeLoggedFlushedResponse("420 no current article selected"); 1025 return; 1026 } else { 1027 article = group.getArticle(currentArticleNumber); 1028 } 1029 } 1030 else { 1031 newArticleNumber = Integer.parseInt(param); 1032 article = group.getArticle(newArticleNumber); 1033 } 1034 if ( article == null ) { 1035 writeLoggedFlushedResponse("423 no such article number in this group"); 1036 return; 1037 } else { 1038 currentArticleNumber = newArticleNumber; 1039 String articleID = article.getUniqueID(); 1040 if (articleID == null) { 1041 articleID = "<0>"; 1042 } 1043 StringBuffer respBuffer = 1044 new StringBuffer (128) 1045 .append("222 ") 1046 .append(article.getArticleNumber()) 1047 .append(" ") 1048 .append(articleID); 1049 writeLoggedFlushedResponse(respBuffer.toString()); 1050 } 1051 } 1052 } 1053 if (article != null) { 1054 writer.flush(); 1055 article.writeBody(new ExtraDotOutputStream(outs)); 1056 writeLoggedFlushedResponse("."); 1057 } 1058 } 1059 1060 1069 private void doHEAD(String param) { 1070 NNTPArticle article = null; 1072 if (isMessageId(param)) { 1073 article = theConfigData.getNNTPRepository().getArticleFromID(param); 1074 if ( article == null ) { 1075 writeLoggedFlushedResponse("430 no such article"); 1076 return; 1077 } else { 1078 StringBuffer respBuffer = 1079 new StringBuffer (64) 1080 .append("221 0 ") 1081 .append(param); 1082 writeLoggedFlushedResponse(respBuffer.toString()); 1083 } 1084 } else { 1085 int newArticleNumber = currentArticleNumber; 1086 if ( group == null ) { 1087 writeLoggedFlushedResponse("412 no newsgroup selected"); 1088 return; 1089 } else { 1090 if ( param == null ) { 1091 if ( currentArticleNumber < 0 ) { 1092 writeLoggedFlushedResponse("420 no current article selected"); 1093 return; 1094 } else { 1095 article = group.getArticle(currentArticleNumber); 1096 } 1097 } 1098 else { 1099 newArticleNumber = Integer.parseInt(param); 1100 article = group.getArticle(newArticleNumber); 1101 } 1102 if ( article == null ) { 1103 writeLoggedFlushedResponse("423 no such article number in this group"); 1104 return; 1105 } else { 1106 currentArticleNumber = newArticleNumber; 1107 String articleID = article.getUniqueID(); 1108 if (articleID == null) { 1109 articleID = "<0>"; 1110 } 1111 StringBuffer respBuffer = 1112 new StringBuffer (128) 1113 .append("221 ") 1114 .append(article.getArticleNumber()) 1115 .append(" ") 1116 .append(articleID); 1117 writeLoggedFlushedResponse(respBuffer.toString()); 1118 } 1119 } 1120 } 1121 if (article != null) { 1122 writer.flush(); 1123 article.writeHead(new ExtraDotOutputStream(outs)); 1124 writeLoggedFlushedResponse("."); 1125 } 1126 } 1127 1128 1137 private void doARTICLE(String param) { 1138 NNTPArticle article = null; 1140 if (isMessageId(param)) { 1141 article = theConfigData.getNNTPRepository().getArticleFromID(param); 1142 if ( article == null ) { 1143 writeLoggedFlushedResponse("430 no such article"); 1144 return; 1145 } else { 1146 StringBuffer respBuffer = 1147 new StringBuffer (64) 1148 .append("220 0 ") 1149 .append(param); 1150 writeLoggedResponse(respBuffer.toString()); 1151 } 1152 } else { 1153 int newArticleNumber = currentArticleNumber; 1154 if ( group == null ) { 1155 writeLoggedFlushedResponse("412 no newsgroup selected"); 1156 return; 1157 } else { 1158 if ( param == null ) { 1159 if ( currentArticleNumber < 0 ) { 1160 writeLoggedFlushedResponse("420 no current article selected"); 1161 return; 1162 } else { 1163 article = group.getArticle(currentArticleNumber); 1164 } 1165 } 1166 else { 1167 newArticleNumber = Integer.parseInt(param); 1168 article = group.getArticle(newArticleNumber); 1169 } 1170 if ( article == null ) { 1171 writeLoggedFlushedResponse("423 no such article number in this group"); 1172 return; 1173 } else { 1174 currentArticleNumber = newArticleNumber; 1175 String articleID = article.getUniqueID(); 1176 if (articleID == null) { 1177 articleID = "<0>"; 1178 } 1179 StringBuffer respBuffer = 1180 new StringBuffer (128) 1181 .append("220 ") 1182 .append(article.getArticleNumber()) 1183 .append(" ") 1184 .append(articleID); 1185 writeLoggedFlushedResponse(respBuffer.toString()); 1186 } 1187 } 1188 } 1189 if (article != null) { 1190 writer.flush(); 1191 article.writeArticle(new ExtraDotOutputStream(outs)); 1192 writeLoggedFlushedResponse("."); 1193 } 1194 } 1195 1196 1201 private void doNEXT(String argument) { 1202 if ( argument != null ) { 1204 writeLoggedFlushedResponse("501 Syntax error - unexpected parameter"); 1205 } else if ( group == null ) { 1206 writeLoggedFlushedResponse("412 no newsgroup selected"); 1207 } else if ( currentArticleNumber < 0 ) { 1208 writeLoggedFlushedResponse("420 no current article has been selected"); 1209 } else if ( currentArticleNumber >= group.getLastArticleNumber() ) { 1210 writeLoggedFlushedResponse("421 no next article in this group"); 1211 } else { 1212 currentArticleNumber++; 1213 NNTPArticle article = group.getArticle(currentArticleNumber); 1214 StringBuffer respBuffer = 1215 new StringBuffer (64) 1216 .append("223 ") 1217 .append(article.getArticleNumber()) 1218 .append(" ") 1219 .append(article.getUniqueID()); 1220 writeLoggedFlushedResponse(respBuffer.toString()); 1221 } 1222 } 1223 1224 1230 private void doLAST(String argument) { 1231 if ( argument != null ) { 1233 writeLoggedFlushedResponse("501 Syntax error - unexpected parameter"); 1234 } else if ( group == null ) { 1235 writeLoggedFlushedResponse("412 no newsgroup selected"); 1236 } else if ( currentArticleNumber < 0 ) { 1237 writeLoggedFlushedResponse("420 no current article has been selected"); 1238 } else if ( currentArticleNumber <= group.getFirstArticleNumber() ) { 1239 writeLoggedFlushedResponse("422 no previous article in this group"); 1240 } else { 1241 currentArticleNumber--; 1242 NNTPArticle article = group.getArticle(currentArticleNumber); 1243 StringBuffer respBuffer = 1244 new StringBuffer (64) 1245 .append("223 ") 1246 .append(article.getArticleNumber()) 1247 .append(" ") 1248 .append(article.getUniqueID()); 1249 writeLoggedFlushedResponse(respBuffer.toString()); 1250 } 1251 } 1252 1253 1258 private void doGROUP(String groupName) { 1259 if (groupName == null) { 1260 writeLoggedFlushedResponse("501 Syntax error - missing required parameter"); 1261 return; 1262 } 1263 NNTPGroup newGroup = theConfigData.getNNTPRepository().getGroup(groupName); 1264 if ( newGroup == null ) { 1266 writeLoggedFlushedResponse("411 no such newsgroup"); 1267 } else { 1268 group = newGroup; 1269 int articleCount = group.getNumberOfArticles(); 1275 int lowWaterMark = group.getFirstArticleNumber(); 1276 int highWaterMark = group.getLastArticleNumber(); 1277 1278 if (articleCount != 0) { 1282 currentArticleNumber = lowWaterMark; 1283 } else { 1284 currentArticleNumber = -1; 1285 } 1286 StringBuffer respBuffer = 1287 new StringBuffer (128) 1288 .append("211 ") 1289 .append(articleCount) 1290 .append(" ") 1291 .append(lowWaterMark) 1292 .append(" ") 1293 .append(highWaterMark) 1294 .append(" ") 1295 .append(group.getName()) 1296 .append(" group selected"); 1297 writeLoggedFlushedResponse(respBuffer.toString()); 1298 } 1299 } 1300 1301 1304 private void doLISTEXTENSIONS() { 1305 writeLoggedResponse("202 Extensions supported:"); 1307 writeLoggedResponse("LISTGROUP"); 1308 writeLoggedResponse("AUTHINFO"); 1309 writeLoggedResponse("OVER"); 1310 writeLoggedResponse("XOVER"); 1311 writeLoggedResponse("HDR"); 1312 writeLoggedResponse("XHDR"); 1313 writeLoggedFlushedResponse("."); 1314 } 1315 1316 1321 private void doMODEREADER(String argument) { 1322 writeLoggedFlushedResponse(theConfigData.getNNTPRepository().isReadOnly() 1324 ? "201 Posting Not Permitted" : "200 Posting Permitted"); 1325 } 1326 1327 1332 private void doMODESTREAM(String argument) { 1333 writeLoggedFlushedResponse("500 Command not understood"); 1335 } 1336 1337 1343 private void doLISTGROUP(String groupName) { 1344 if (groupName==null) { 1346 if ( group == null) { 1347 writeLoggedFlushedResponse("412 no news group currently selected"); 1348 return; 1349 } 1350 } 1351 else { 1352 group = theConfigData.getNNTPRepository().getGroup(groupName); 1353 if ( group == null ) { 1354 writeLoggedFlushedResponse("411 no such newsgroup"); 1355 return; 1356 } 1357 } 1358 if ( group != null ) { 1359 1361 if (group.getNumberOfArticles() > 0) { 1365 currentArticleNumber = group.getFirstArticleNumber(); 1366 } else { 1367 currentArticleNumber = -1; 1368 } 1369 1370 writeLoggedFlushedResponse("211 list of article numbers follow"); 1371 1372 Iterator iter = group.getArticles(); 1373 while (iter.hasNext()) { 1374 NNTPArticle article = (NNTPArticle)iter.next(); 1375 writeLoggedResponse(article.getArticleNumber() + ""); 1376 } 1377 writeLoggedFlushedResponse("."); 1378 } 1379 } 1380 1381 1384 private void doLISTOVERVIEWFMT() { 1385 writeLoggedFlushedResponse("215 Information follows"); 1387 String [] overviewHeaders = theConfigData.getNNTPRepository().getOverviewFormat(); 1388 for (int i = 0; i < overviewHeaders.length; i++) { 1389 writeLoggedResponse(overviewHeaders[i]); 1390 } 1391 writeLoggedFlushedResponse("."); 1392 } 1393 1394 1399 private void doPAT(String argument) { 1400 writeLoggedFlushedResponse("500 Command not recognized"); 1402 } 1403 1404 1410 private void doXHDR(String argument) { 1411 doHDR(argument); 1412 } 1413 1414 1420 private void doHDR(String argument) { 1421 if (argument == null) { 1423 writeLoggedFlushedResponse("501 Syntax error - missing required parameter"); 1424 return; 1425 } 1426 String hdr = argument; 1427 String range = null; 1428 int spaceIndex = hdr.indexOf(" "); 1429 if (spaceIndex >= 0 ) { 1430 range = hdr.substring(spaceIndex + 1); 1431 hdr = hdr.substring(0, spaceIndex); 1432 } 1433 if (group == null ) { 1434 writeLoggedFlushedResponse("412 No news group currently selected."); 1435 return; 1436 } 1437 if ((range == null) && (currentArticleNumber < 0)) { 1438 writeLoggedFlushedResponse("420 No current article selected"); 1439 return; 1440 } 1441 NNTPArticle[] article = getRange(range); 1442 if ( article == null ) { 1443 writeLoggedFlushedResponse("412 no newsgroup selected"); 1444 } else if ( article.length == 0 ) { 1445 writeLoggedFlushedResponse("430 no such article"); 1446 } else { 1447 writeLoggedFlushedResponse("221 Header follows"); 1448 for ( int i = 0 ; i < article.length ; i++ ) { 1449 String val = article[i].getHeader(hdr); 1450 if ( val == null ) { 1451 val = ""; 1452 } 1453 StringBuffer hdrBuffer = 1454 new StringBuffer (128) 1455 .append(article[i].getArticleNumber()) 1456 .append(" ") 1457 .append(val); 1458 writeLoggedResponse(hdrBuffer.toString()); 1459 } 1460 writeLoggedFlushedResponse("."); 1461 } 1462 } 1463 1464 1470 private void doXOVER(String range) { 1471 doOVER(range); 1472 } 1473 1474 1480 private void doOVER(String range) { 1481 if ( group == null ) { 1483 writeLoggedFlushedResponse("412 No newsgroup selected"); 1484 return; 1485 } 1486 if ((range == null) && (currentArticleNumber < 0)) { 1487 writeLoggedFlushedResponse("420 No current article selected"); 1488 return; 1489 } 1490 NNTPArticle[] article = getRange(range); 1491 if ( article.length == 0 ) { 1492 writeLoggedFlushedResponse("420 No article(s) selected"); 1493 } else { 1494 writeLoggedResponse("224 Overview information follows"); 1495 for ( int i = 0 ; i < article.length ; i++ ) { 1496 article[i].writeOverview(outs); 1497 if (i % 100 == 0) { 1498 theWatchdog.reset(); 1502 } 1503 } 1504 writeLoggedFlushedResponse("."); 1505 } 1506 } 1507 1508 1511 private void createArticle() { 1512 try { 1513 InputStream msgIn = new CharTerminatedInputStream(in, NNTPTerminator); 1514 msgIn = new DotStuffingInputStream(msgIn); 1516 MailHeaders headers = new MailHeaders(msgIn); 1517 processMessageHeaders(headers); 1518 processMessage(headers, msgIn); 1519 } catch (MessagingException me) { 1520 throw new NNTPException("MessagingException encountered when loading article."); 1521 } 1522 } 1523 1524 1529 private MailHeaders processMessageHeaders(MailHeaders headers) 1530 throws MessagingException { 1531 return headers; 1532 } 1533 1534 1541 private void processMessage(MailHeaders headers, InputStream bodyIn) 1542 throws MessagingException { 1543 InputStream messageIn = null; 1544 try { 1545 messageIn = new SequenceInputStream (new ByteArrayInputStream (headers.toByteArray()), bodyIn); 1546 theConfigData.getNNTPRepository().createArticle(messageIn); 1547 } finally { 1548 if (messageIn != null) { 1549 try { 1550 messageIn.close(); 1551 } catch (IOException ioe) { 1552 } 1554 messageIn = null; 1555 } 1556 } 1557 } 1558 1559 1567 private Date getDateFrom(String argument) { 1568 if (argument == null) { 1569 throw new NNTPException("Date argument was absent."); 1570 } 1571 StringTokenizer tok = new StringTokenizer (argument, " "); 1572 if (tok.countTokens() < 2) { 1573 throw new NNTPException("Date argument was ill-formed."); 1574 } 1575 String date = tok.nextToken(); 1576 String time = tok.nextToken(); 1577 boolean utc = ( tok.hasMoreTokens() ); 1578 Date d = new Date (); 1579 try { 1580 StringBuffer dateStringBuffer = 1581 new StringBuffer (64) 1582 .append(date) 1583 .append(" ") 1584 .append(time); 1585 Date dt = DF_RFC977.parse(dateStringBuffer.toString()); 1586 if ( utc ) { 1587 dt = new Date (dt.getTime()+UTC_OFFSET); 1588 } 1589 return dt; 1590 } catch ( ParseException pe ) { 1591 StringBuffer exceptionBuffer = 1592 new StringBuffer (128) 1593 .append("Date extraction failed: ") 1594 .append(date) 1595 .append(",") 1596 .append(time) 1597 .append(",") 1598 .append(utc); 1599 throw new NNTPException(exceptionBuffer.toString()); 1600 } 1601 } 1602 1603 1613 private NNTPArticle[] getRange(String range) { 1614 if ( isMessageId(range)) { 1616 NNTPArticle article = theConfigData.getNNTPRepository().getArticleFromID(range); 1617 return ( article == null ) 1618 ? new NNTPArticle[0] : new NNTPArticle[] { article }; 1619 } 1620 1621 if ( range == null ) { 1622 range = "" + currentArticleNumber; 1623 } 1624 1625 int start = -1; 1626 int end = -1; 1627 int idx = range.indexOf('-'); 1628 if ( idx == -1 ) { 1629 start = Integer.parseInt(range); 1630 end = start; 1631 } else { 1632 start = Integer.parseInt(range.substring(0,idx)); 1633 if ( (idx + 1) == range.length() ) { 1634 end = group.getLastArticleNumber(); 1635 } else { 1636 end = Integer.parseInt(range.substring(idx + 1)); 1637 } 1638 } 1639 List list = new ArrayList (); 1640 for ( int i = start ; i <= end ; i++ ) { 1641 NNTPArticle article = group.getArticle(i); 1642 if ( article != null ) { 1643 list.add(article); 1644 } 1645 } 1646 return (NNTPArticle[])list.toArray(new NNTPArticle[0]); 1647 } 1648 1649 1656 private boolean isAuthorized(String command) { 1657 isAlreadyAuthenticated = isAlreadyAuthenticated || isAuthenticated(); 1658 if (isAlreadyAuthenticated) { 1659 return true; 1660 } 1661 boolean allowed = command.equals("AUTHINFO"); 1663 allowed = allowed || command.equals("MODE"); 1664 allowed = allowed || command.equals("QUIT"); 1665 return allowed; 1666 } 1667 1668 1673 private boolean isAuthenticated() { 1674 if ( theConfigData.isAuthRequired() ) { 1675 if ((user != null) && (password != null) && (theConfigData.getUsersRepository() != null)) { 1676 return theConfigData.getUsersRepository().test(user,password); 1677 } else { 1678 return false; 1679 } 1680 } else { 1681 return true; 1682 } 1683 } 1684 1685 1693 private static boolean isMessageId(String testString) { 1694 if ((testString != null) && 1695 (testString.startsWith("<")) && 1696 (testString.endsWith(">"))) { 1697 return true; 1698 } else { 1699 return false; 1700 } 1701 } 1702 1703 1711 private final void logResponseString(String responseString) { 1712 if (getLogger().isDebugEnabled()) { 1713 getLogger().debug("Sent: " + responseString); 1714 } 1715 } 1716 1717 1724 final void writeLoggedFlushedResponse(String responseString) { 1725 writer.println(responseString); 1726 writer.flush(); 1727 logResponseString(responseString); 1728 } 1729 1730 1736 final void writeLoggedResponse(String responseString) { 1737 writer.println(responseString); 1738 logResponseString(responseString); 1739 } 1740 1741 1746 private class NNTPWatchdogTarget 1747 implements WatchdogTarget { 1748 1749 1752 public void execute() { 1753 NNTPHandler.this.idleClose(); 1754 } 1755 1756 } 1757} 1758 | Popular Tags |