| 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  |