1 /************************************************************************
2 * Copyright (c) 2000-2006 The Apache Software Foundation. *
3 * All rights reserved. *
4 * ------------------------------------------------------------------- *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you *
6 * may not use this file except in compliance with the License. You *
7 * may obtain a copy of the License at: *
8 * *
9 * http://www.apache.org/licenses/LICENSE-2.0 *
10 * *
11 * Unless required by applicable law or agreed to in writing, software *
12 * distributed under the License is distributed on an "AS IS" BASIS, *
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
14 * implied. See the License for the specific language governing *
15 * permissions and limitations under the License. *
16 ***********************************************************************/
17
18 package org.apache.james.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.container.ContainerUtil;
23 import org.apache.avalon.framework.logger.AbstractLogEnabled;
24 import org.apache.james.core.MailHeaders;
25 import org.apache.james.nntpserver.repository.NNTPArticle;
26 import org.apache.james.nntpserver.repository.NNTPGroup;
27 import org.apache.james.util.CharTerminatedInputStream;
28 import org.apache.james.util.DotStuffingInputStream;
29 import org.apache.james.util.ExtraDotOutputStream;
30 import org.apache.james.util.InternetPrintWriter;
31 import org.apache.mailet.dates.RFC977DateFormat;
32 import org.apache.mailet.dates.RFC2980DateFormat;
33 import org.apache.mailet.dates.SimplifiedDateFormat;
34 import org.apache.james.util.watchdog.Watchdog;
35 import org.apache.james.util.watchdog.WatchdogTarget;
36
37 import java.io.BufferedInputStream;
38 import java.io.BufferedOutputStream;
39 import java.io.BufferedReader;
40 import java.io.ByteArrayInputStream;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.io.InputStreamReader;
44 import java.io.OutputStream;
45 import java.io.PrintWriter;
46 import java.io.SequenceInputStream;
47 import java.net.Socket;
48 import java.text.ParseException;
49 import java.util.ArrayList;
50 import java.util.Calendar;
51 import java.util.Date;
52 import java.util.Iterator;
53 import java.util.List;
54 import java.util.Locale;
55 import java.util.StringTokenizer;
56 import javax.mail.MessagingException;
57
58 /***
59 * The NNTP protocol is defined by RFC 977.
60 * This implementation is based on IETF draft 15, posted on 15th July '2002.
61 * URL: http://www.ietf.org/internet-drafts/draft-ietf-nntpext-base-15.txt
62 *
63 * Common NNTP extensions are in RFC 2980.
64 */
65 public class NNTPHandler
66 extends AbstractLogEnabled
67 implements ConnectionHandler, Poolable {
68
69 /***
70 * used to calculate DATE from - see 11.3
71 */
72 private static final SimplifiedDateFormat DF_RFC977 = new RFC977DateFormat();
73
74 /***
75 * Date format for the DATE keyword - see 11.1.1
76 */
77 private static final SimplifiedDateFormat DF_RFC2980 = new RFC2980DateFormat();
78
79 /***
80 * The UTC offset for this time zone.
81 */
82 public static final long UTC_OFFSET = Calendar.getInstance().get(Calendar.ZONE_OFFSET);
83
84 /***
85 * The text string for the NNTP MODE command.
86 */
87 private final static String COMMAND_MODE = "MODE";
88
89 /***
90 * The text string for the NNTP LIST command.
91 */
92 private final static String COMMAND_LIST = "LIST";
93
94 /***
95 * The text string for the NNTP GROUP command.
96 */
97 private final static String COMMAND_GROUP = "GROUP";
98
99 /***
100 * The text string for the NNTP NEXT command.
101 */
102 private final static String COMMAND_NEXT = "NEXT";
103
104 /***
105 * The text string for the NNTP LAST command.
106 */
107 private final static String COMMAND_LAST = "LAST";
108
109 /***
110 * The text string for the NNTP ARTICLE command.
111 */
112 private final static String COMMAND_ARTICLE = "ARTICLE";
113
114 /***
115 * The text string for the NNTP HEAD command.
116 */
117 private final static String COMMAND_HEAD = "HEAD";
118
119 /***
120 * The text string for the NNTP BODY command.
121 */
122 private final static String COMMAND_BODY = "BODY";
123
124 /***
125 * The text string for the NNTP STAT command.
126 */
127 private final static String COMMAND_STAT = "STAT";
128
129 /***
130 * The text string for the NNTP POST command.
131 */
132 private final static String COMMAND_POST = "POST";
133
134 /***
135 * The text string for the NNTP IHAVE command.
136 */
137 private final static String COMMAND_IHAVE = "IHAVE";
138
139 /***
140 * The text string for the NNTP QUIT command.
141 */
142 private final static String COMMAND_QUIT = "QUIT";
143
144 /***
145 * The text string for the NNTP SLAVE command.
146 */
147 private final static String COMMAND_SLAVE = "SLAVE";
148
149 /***
150 * The text string for the NNTP DATE command.
151 */
152 private final static String COMMAND_DATE = "DATE";
153
154 /***
155 * The text string for the NNTP HELP command.
156 */
157 private final static String COMMAND_HELP = "HELP";
158
159 /***
160 * The text string for the NNTP NEWGROUPS command.
161 */
162 private final static String COMMAND_NEWGROUPS = "NEWGROUPS";
163
164 /***
165 * The text string for the NNTP NEWNEWS command.
166 */
167 private final static String COMMAND_NEWNEWS = "NEWNEWS";
168
169 /***
170 * The text string for the NNTP LISTGROUP command.
171 */
172 private final static String COMMAND_LISTGROUP = "LISTGROUP";
173
174 /***
175 * The text string for the NNTP OVER command.
176 */
177 private final static String COMMAND_OVER = "OVER";
178
179 /***
180 * The text string for the NNTP XOVER command.
181 */
182 private final static String COMMAND_XOVER = "XOVER";
183
184 /***
185 * The text string for the NNTP HDR command.
186 */
187 private final static String COMMAND_HDR = "HDR";
188
189 /***
190 * The text string for the NNTP XHDR command.
191 */
192 private final static String COMMAND_XHDR = "XHDR";
193
194 /***
195 * The text string for the NNTP AUTHINFO command.
196 */
197 private final static String COMMAND_AUTHINFO = "AUTHINFO";
198
199 /***
200 * The text string for the NNTP PAT command.
201 */
202 private final static String COMMAND_PAT = "PAT";
203
204 /***
205 * The text string for the NNTP MODE READER parameter.
206 */
207 private final static String MODE_TYPE_READER = "READER";
208
209 /***
210 * The text string for the NNTP MODE STREAM parameter.
211 */
212 private final static String MODE_TYPE_STREAM = "STREAM";
213
214 /***
215 * The text string for the NNTP AUTHINFO USER parameter.
216 */
217 private final static String AUTHINFO_PARAM_USER = "USER";
218
219 /***
220 * The text string for the NNTP AUTHINFO PASS parameter.
221 */
222 private final static String AUTHINFO_PARAM_PASS = "PASS";
223
224 /***
225 * The character array that indicates termination of an NNTP message
226 */
227 private final static char[] NNTPTerminator = { '\r', '\n', '.', '\r', '\n' };
228
229 /***
230 * The thread executing this handler
231 */
232 private Thread handlerThread;
233
234 /***
235 * The remote host name obtained by lookup on the socket.
236 */
237 private String remoteHost;
238
239 /***
240 * The remote IP address of the socket.
241 */
242 private String remoteIP;
243
244 /***
245 * The TCP/IP socket over which the POP3 interaction
246 * is occurring
247 */
248 private Socket socket;
249
250 /***
251 * The incoming stream of bytes coming from the socket.
252 */
253 private InputStream in;
254
255 /***
256 * The reader associated with incoming characters.
257 */
258 private BufferedReader reader;
259
260 /***
261 * The socket's output stream
262 */
263 private OutputStream outs;
264
265 /***
266 * The writer to which outgoing messages are written.
267 */
268 private PrintWriter writer;
269
270 /***
271 * The current newsgroup.
272 */
273 private NNTPGroup group;
274
275 /***
276 * The current newsgroup.
277 */
278 private int currentArticleNumber = -1;
279
280 /***
281 * Per-service configuration data that applies to all handlers
282 * associated with the service.
283 */
284 private NNTPHandlerConfigurationData theConfigData;
285
286 /***
287 * The user id associated with the NNTP dialogue
288 */
289 private String user = null;
290
291 /***
292 * The password associated with the NNTP dialogue
293 */
294 private String password = null;
295
296 /***
297 * Whether the user for this session has already authenticated.
298 * Used to optimize authentication checks
299 */
300 boolean isAlreadyAuthenticated = false;
301
302 /***
303 * The watchdog being used by this handler to deal with idle timeouts.
304 */
305 private Watchdog theWatchdog;
306
307 /***
308 * The watchdog target that idles out this handler.
309 */
310 private WatchdogTarget theWatchdogTarget = new NNTPWatchdogTarget();
311
312 /***
313 * Set the configuration data for the handler
314 *
315 * @param theData configuration data for the handler
316 */
317 void setConfigurationData(NNTPHandlerConfigurationData theData) {
318 theConfigData = theData;
319 }
320
321 /***
322 * Set the Watchdog for use by this handler.
323 *
324 * @param theWatchdog the watchdog
325 */
326 void setWatchdog(Watchdog theWatchdog) {
327 this.theWatchdog = theWatchdog;
328 }
329
330 /***
331 * Gets the Watchdog Target that should be used by Watchdogs managing
332 * this connection.
333 *
334 * @return the WatchdogTarget
335 */
336 WatchdogTarget getWatchdogTarget() {
337 return theWatchdogTarget;
338 }
339
340 /***
341 * Idle out this connection
342 */
343 void idleClose() {
344 if (getLogger() != null) {
345 getLogger().error("NNTP Connection has idled out.");
346 }
347 try {
348 if (socket != null) {
349 socket.close();
350 }
351 } catch (Exception e) {
352
353 } finally {
354 socket = null;
355 }
356
357 synchronized (this) {
358
359 if (handlerThread != null) {
360 handlerThread.interrupt();
361 handlerThread = null;
362 }
363 }
364 }
365
366 /***
367 * @see org.apache.avalon.cornerstone.services.connection.ConnectionHandler#handleConnection(Socket)
368 */
369 public void handleConnection( Socket connection ) throws IOException {
370 try {
371 this.socket = connection;
372 synchronized (this) {
373 handlerThread = Thread.currentThread();
374 }
375 remoteIP = socket.getInetAddress().getHostAddress();
376 remoteHost = socket.getInetAddress().getHostName();
377 in = new BufferedInputStream(socket.getInputStream(), 1024);
378
379
380
381 reader = new BufferedReader(new InputStreamReader(in, "ASCII"), 512);
382 outs = new BufferedOutputStream(socket.getOutputStream(), 1024);
383 writer = new InternetPrintWriter(outs, true);
384 } catch (Exception e) {
385 StringBuffer exceptionBuffer =
386 new StringBuffer(256)
387 .append("Cannot open connection from ")
388 .append(remoteHost)
389 .append(" (")
390 .append(remoteIP)
391 .append("): ")
392 .append(e.getMessage());
393 String exceptionString = exceptionBuffer.toString();
394 getLogger().error(exceptionString, e );
395 }
396
397 try {
398
399 if ( theConfigData.getNNTPRepository().isReadOnly() ) {
400 StringBuffer respBuffer =
401 new StringBuffer(128)
402 .append("201 ")
403 .append(theConfigData.getHelloName())
404 .append(" NNTP Service Ready, posting prohibited");
405 writeLoggedFlushedResponse(respBuffer.toString());
406 } else {
407 StringBuffer respBuffer =
408 new StringBuffer(128)
409 .append("200 ")
410 .append(theConfigData.getHelloName())
411 .append(" NNTP Service Ready, posting permitted");
412 writeLoggedFlushedResponse(respBuffer.toString());
413 }
414
415 theWatchdog.start();
416 while (parseCommand(reader.readLine())) {
417 theWatchdog.reset();
418 }
419 theWatchdog.stop();
420
421 getLogger().info("Connection closed");
422 } catch (Exception e) {
423
424
425
426
427 if (socket != null) {
428 try {
429 doQUIT(null);
430 } catch (Throwable t) {}
431 getLogger().error( "Exception during connection:" + e.getMessage(), e );
432 }
433 } finally {
434 resetHandler();
435 }
436 }
437
438 /***
439 * Resets the handler data to a basic state.
440 */
441 private void resetHandler() {
442
443
444 if (theWatchdog != null) {
445 ContainerUtil.dispose(theWatchdog);
446 theWatchdog = null;
447 }
448
449
450 try {
451 if (reader != null) {
452 reader.close();
453 }
454 } catch (IOException ioe) {
455 getLogger().warn("NNTPHandler: Unexpected exception occurred while closing reader: " + ioe);
456 } finally {
457 reader = null;
458 }
459
460 in = null;
461
462 if (writer != null) {
463 writer.close();
464 writer = null;
465 }
466 outs = null;
467
468 remoteHost = null;
469 remoteIP = null;
470 try {
471 if (socket != null) {
472 socket.close();
473 }
474 } catch (IOException ioe) {
475 getLogger().warn("NNTPHandler: Unexpected exception occurred while closing socket: " + ioe);
476 } finally {
477 socket = null;
478 }
479
480 synchronized (this) {
481 handlerThread = null;
482 }
483
484
485 group = null;
486 currentArticleNumber = -1;
487
488
489 user = null;
490 password = null;
491 isAlreadyAuthenticated = false;
492
493
494 theConfigData = null;
495 }
496
497 /***
498 * This method parses NNTP commands read off the wire in handleConnection.
499 * Actual processing of the command (possibly including additional back and
500 * forth communication with the client) is delegated to one of a number of
501 * command specific handler methods. The primary purpose of this method is
502 * to parse the raw command string to determine exactly which handler should
503 * be called. It returns true if expecting additional commands, false otherwise.
504 *
505 * @param commandRaw the raw command string passed in over the socket
506 *
507 * @return whether additional commands are expected.
508 */
509 private boolean parseCommand(String commandRaw) {
510 if (commandRaw == null) {
511 return false;
512 }
513 if (getLogger().isDebugEnabled()) {
514 getLogger().debug("Command received: " + commandRaw);
515 }
516
517 String command = commandRaw.trim();
518 String argument = null;
519 int spaceIndex = command.indexOf(" ");
520 if (spaceIndex >= 0) {
521 argument = command.substring(spaceIndex + 1);
522 command = command.substring(0, spaceIndex);
523 }
524 command = command.toUpperCase(Locale.US);
525
526 boolean returnValue = true;
527 if (!isAuthorized(command) ) {
528 writeLoggedFlushedResponse("480 User is not authenticated");
529 getLogger().debug("Command not allowed.");
530 return returnValue;
531 }
532 if ((command.equals(COMMAND_MODE)) && (argument != null)) {
533 if (argument.toUpperCase(Locale.US).equals(MODE_TYPE_READER)) {
534 doMODEREADER(argument);
535 } else if (argument.toUpperCase(Locale.US).equals(MODE_TYPE_STREAM)) {
536 doMODESTREAM(argument);
537 } else {
538 writeLoggedFlushedResponse("500 Command not understood");
539 }
540 } else if ( command.equals(COMMAND_LIST)) {
541 doLIST(argument);
542 } else if ( command.equals(COMMAND_GROUP) ) {
543 doGROUP(argument);
544 } else if ( command.equals(COMMAND_NEXT) ) {
545 doNEXT(argument);
546 } else if ( command.equals(COMMAND_LAST) ) {
547 doLAST(argument);
548 } else if ( command.equals(COMMAND_ARTICLE) ) {
549 doARTICLE(argument);
550 } else if ( command.equals(COMMAND_HEAD) ) {
551 doHEAD(argument);
552 } else if ( command.equals(COMMAND_BODY) ) {
553 doBODY(argument);
554 } else if ( command.equals(COMMAND_STAT) ) {
555 doSTAT(argument);
556 } else if ( command.equals(COMMAND_POST) ) {
557 doPOST(argument);
558 } else if ( command.equals(COMMAND_IHAVE) ) {
559 doIHAVE(argument);
560 } else if ( command.equals(COMMAND_QUIT) ) {
561 doQUIT(argument);
562 returnValue = false;
563 } else if ( command.equals(COMMAND_DATE) ) {
564 doDATE(argument);
565 } else if ( command.equals(COMMAND_HELP) ) {
566 doHELP(argument);
567 } else if ( command.equals(COMMAND_NEWGROUPS) ) {
568 doNEWGROUPS(argument);
569 } else if ( command.equals(COMMAND_NEWNEWS) ) {
570 doNEWNEWS(argument);
571 } else if ( command.equals(COMMAND_LISTGROUP) ) {
572 doLISTGROUP(argument);
573 } else if ( command.equals(COMMAND_OVER) ) {
574 doOVER(argument);
575 } else if ( command.equals(COMMAND_XOVER) ) {
576 doXOVER(argument);
577 } else if ( command.equals(COMMAND_HDR) ) {
578 doHDR(argument);
579 } else if ( command.equals(COMMAND_XHDR) ) {
580 doXHDR(argument);
581 } else if ( command.equals(COMMAND_AUTHINFO) ) {
582 doAUTHINFO(argument);
583 } else if ( command.equals(COMMAND_SLAVE) ) {
584 doSLAVE(argument);
585 } else if ( command.equals(COMMAND_PAT) ) {
586 doPAT(argument);
587 } else {
588 doUnknownCommand(command, argument);
589 }
590 return returnValue;
591 }
592
593 /***
594 * Handles an unrecognized command, logging that.
595 *
596 * @param command the command received from the client
597 * @param argument the argument passed in with the command
598 */
599 private void doUnknownCommand(String command, String argument) {
600 if (getLogger().isDebugEnabled()) {
601 StringBuffer logBuffer =
602 new StringBuffer(128)
603 .append("Received unknown command ")
604 .append(command)
605 .append(" with argument ")
606 .append(argument);
607 getLogger().debug(logBuffer.toString());
608 }
609 writeLoggedFlushedResponse("500 Unknown command");
610 }
611
612 /***
613 * Implements only the originnal AUTHINFO.
614 * for simple and generic AUTHINFO, 501 is sent back. This is as
615 * per article 3.1.3 of RFC 2980
616 *
617 * @param argument the argument passed in with the AUTHINFO command
618 */
619 private void doAUTHINFO(String argument) {
620 String command = null;
621 String value = null;
622 if (argument != null) {
623 int spaceIndex = argument.indexOf(" ");
624 if (spaceIndex >= 0) {
625 command = argument.substring(0, spaceIndex);
626 value = argument.substring(spaceIndex + 1);
627 }
628 }
629 if (command == null) {
630 writeLoggedFlushedResponse("501 Syntax error");
631 return;
632 }
633 command = command.toUpperCase(Locale.US);
634 if ( command.equals(AUTHINFO_PARAM_USER) ) {
635
636 if ( isAlreadyAuthenticated ) {
637 writeLoggedFlushedResponse("482 Already authenticated - rejecting new credentials");
638 }
639
640 if (user != null) {
641 user = null;
642 password = null;
643 isAlreadyAuthenticated = false;
644 writeLoggedFlushedResponse("482 User already specified - rejecting new user");
645 return;
646 }
647 user = value;
648 writeLoggedFlushedResponse("381 More authentication information required");
649 } else if ( command.equals(AUTHINFO_PARAM_PASS) ) {
650
651 if (user == null) {
652 writeLoggedFlushedResponse("482 User not yet specified. Rejecting user.");
653 return;
654 }
655
656 if (password != null) {
657 user = null;
658 password = null;
659 isAlreadyAuthenticated = false;
660 writeLoggedFlushedResponse("482 Password already specified - rejecting new password");
661 return;
662 }
663 password = value;
664 isAlreadyAuthenticated = isAuthenticated();
665 if ( isAlreadyAuthenticated ) {
666 writeLoggedFlushedResponse("281 Authentication accepted");
667 } else {
668 writeLoggedFlushedResponse("482 Authentication rejected");
669
670 user = null;
671 password = null;
672 }
673 } else {
674 writeLoggedFlushedResponse("501 Syntax error");
675 return;
676 }
677 }
678
679 /***
680 * Lists the articles posted since the date passed in as
681 * an argument.
682 *
683 * @param argument the argument passed in with the NEWNEWS command.
684 * Should be a wildmat followed by a date.
685 */
686 private void doNEWNEWS(String argument) {
687
688
689 String wildmat = "*";
690
691 if (argument != null) {
692 int spaceIndex = argument.indexOf(" ");
693 if (spaceIndex >= 0) {
694 wildmat = argument.substring(0, spaceIndex);
695 argument = argument.substring(spaceIndex + 1);
696 } else {
697 getLogger().error("NEWNEWS had an invalid argument");
698 writeLoggedFlushedResponse("501 Syntax error");
699 return;
700 }
701 } else {
702 getLogger().error("NEWNEWS had a null argument");
703 writeLoggedFlushedResponse("501 Syntax error");
704 return;
705 }
706
707 Date theDate = null;
708 try {
709 theDate = getDateFrom(argument);
710 } catch (NNTPException nntpe) {
711 getLogger().error("NEWNEWS had an invalid argument", nntpe);
712 writeLoggedFlushedResponse("501 Syntax error");
713 return;
714 }
715
716 writeLoggedFlushedResponse("230 list of new articles by message-id follows");
717 Iterator groupIter = theConfigData.getNNTPRepository().getMatchedGroups(wildmat);
718 while ( groupIter.hasNext() ) {
719 Iterator articleIter = ((NNTPGroup)(groupIter.next())).getArticlesSince(theDate);
720 while (articleIter.hasNext()) {
721 StringBuffer iterBuffer =
722 new StringBuffer(64)
723 .append(((NNTPArticle)articleIter.next()).getUniqueID());
724 writeLoggedResponse(iterBuffer.toString());
725 }
726 }
727 writeLoggedFlushedResponse(".");
728 }
729
730 /***
731 * Lists the groups added since the date passed in as
732 * an argument.
733 *
734 * @param argument the argument passed in with the NEWGROUPS command.
735 * Should be a date.
736 */
737 private void doNEWGROUPS(String argument) {
738
739
740
741
742
743
744
745
746
747 Date theDate = null;
748 try {
749 theDate = getDateFrom(argument);
750 } catch (NNTPException nntpe) {
751 getLogger().error("NEWGROUPS had an invalid argument", nntpe);
752 writeLoggedFlushedResponse("501 Syntax error");
753 return;
754 }
755 Iterator iter = theConfigData.getNNTPRepository().getGroupsSince(theDate);
756 writeLoggedFlushedResponse("231 list of new newsgroups follows");
757 while ( iter.hasNext() ) {
758 NNTPGroup currentGroup = (NNTPGroup)iter.next();
759 StringBuffer iterBuffer =
760 new StringBuffer(128)
761 .append(currentGroup.getName())
762 .append(" ")
763 .append(currentGroup.getLastArticleNumber())
764 .append(" ")
765 .append(currentGroup.getFirstArticleNumber())
766 .append(" ")
767 .append((currentGroup.isPostAllowed()?"y":"n"));
768 writeLoggedResponse(iterBuffer.toString());
769 }
770 writeLoggedFlushedResponse(".");
771 }
772
773 /***
774 * Lists the help text for the service.
775 *
776 * @param argument the argument passed in with the HELP command.
777 */
778 private void doHELP(String argument) {
779 writeLoggedResponse("100 Help text follows");
780 writeLoggedFlushedResponse(".");
781 }
782
783 /***
784 * Acknowledges a SLAVE command. No special preference is given
785 * to slave connections.
786 *
787 * @param argument the argument passed in with the SLAVE command.
788 */
789 private void doSLAVE(String argument) {
790 writeLoggedFlushedResponse("202 slave status noted");
791 }
792
793 /***
794 * Returns the current date according to the news server.
795 *
796 * @param argument the argument passed in with the DATE command
797 */
798 private void doDATE(String argument) {
799 Date dt = new Date(System.currentTimeMillis()-UTC_OFFSET);
800 String dtStr = DF_RFC2980.format(new Date(dt.getTime() - UTC_OFFSET));
801 writeLoggedFlushedResponse("111 " + dtStr);
802 }
803
804 /***
805 * Quits the transaction.
806 *
807 * @param argument the argument passed in with the QUIT command
808 */
809 private void doQUIT(String argument) {
810 writeLoggedFlushedResponse("205 closing connection");
811 }
812
813 /***
814 * Handles the LIST command and its assorted extensions.
815 *
816 * @param argument the argument passed in with the LIST command.
817 */
818 private void doLIST(String argument) {
819
820 String wildmat = "*";
821 boolean isListNewsgroups = false;
822
823 String extension = argument;
824 if (argument != null) {
825 int spaceIndex = argument.indexOf(" ");
826 if (spaceIndex >= 0) {
827 wildmat = argument.substring(spaceIndex + 1);
828 extension = argument.substring(0, spaceIndex);
829 }
830 extension = extension.toUpperCase(Locale.US);
831 }
832
833 if (extension != null) {
834 if (extension.equals("ACTIVE")) {
835 isListNewsgroups = false;
836 } else if (extension.equals("NEWSGROUPS") ) {
837 isListNewsgroups = true;
838 } else if (extension.equals("EXTENSIONS") ) {
839 doLISTEXTENSIONS();
840 return;
841 } else if (extension.equals("OVERVIEW.FMT") ) {
842 doLISTOVERVIEWFMT();
843 return;
844 } else if (extension.equals("ACTIVE.TIMES") ) {
845
846 writeLoggedFlushedResponse("503 program error, function not performed");
847 return;
848 } else if (extension.equals("DISTRIBUTIONS") ) {
849
850 writeLoggedFlushedResponse("503 program error, function not performed");
851 return;
852 } else if (extension.equals("DISTRIB.PATS") ) {
853
854 writeLoggedFlushedResponse("503 program error, function not performed");
855 return;
856 } else {
857 writeLoggedFlushedResponse("501 Syntax error");
858 return;
859 }
860 }
861
862 Iterator iter = theConfigData.getNNTPRepository().getMatchedGroups(wildmat);
863 writeLoggedFlushedResponse("215 list of newsgroups follows");
864 while ( iter.hasNext() ) {
865 NNTPGroup theGroup = (NNTPGroup)iter.next();
866 if (isListNewsgroups) {
867 writeLoggedResponse(theGroup.getListNewsgroupsFormat());
868 } else {
869 writeLoggedResponse(theGroup.getListFormat());
870 }
871 }
872 writeLoggedFlushedResponse(".");
873 }
874
875 /***
876 * Informs the server that the client has an article with the specified
877 * message-ID.
878 *
879 * @param id the message id
880 */
881 private void doIHAVE(String id) {
882
883 if (!isMessageId(id)) {
884 writeLoggedFlushedResponse("501 command syntax error");
885 return;
886 }
887 NNTPArticle article = theConfigData.getNNTPRepository().getArticleFromID(id);
888 if ( article != null ) {
889 writeLoggedFlushedResponse("435 article not wanted - do not send it");
890 } else {
891 writeLoggedFlushedResponse("335 send article to be transferred. End with <CR-LF>.<CR-LF>");
892 try {
893 createArticle();
894 } catch (RuntimeException e) {
895 writeLoggedFlushedResponse("436 transfer failed - try again later");
896 throw e;
897 }
898 writeLoggedFlushedResponse("235 article received ok");
899 }
900 }
901
902 /***
903 * Posts an article to the news server.
904 *
905 * @param argument the argument passed in with the POST command
906 */
907 private void doPOST(String argument) {
908
909 if ( argument != null ) {
910 writeLoggedFlushedResponse("501 Syntax error - unexpected parameter");
911 }
912 writeLoggedFlushedResponse("340 send article to be posted. End with <CR-LF>.<CR-LF>");
913 createArticle();
914 writeLoggedFlushedResponse("240 article received ok");
915 }
916
917 /***
918 * Executes the STAT command. Sets the current article pointer,
919 * returns article information.
920 *
921 * @param the argument passed in to the STAT command,
922 * which should be an article number or message id.
923 * If no parameter is provided, the current selected
924 * article is used.
925 */
926 private void doSTAT(String param) {
927
928 NNTPArticle article = null;
929 if (isMessageId(param)) {
930 article = theConfigData.getNNTPRepository().getArticleFromID(param);
931 if ( article == null ) {
932 writeLoggedFlushedResponse("430 no such article");
933 return;
934 } else {
935 StringBuffer respBuffer =
936 new StringBuffer(64)
937 .append("223 0 ")
938 .append(param);
939 writeLoggedFlushedResponse(respBuffer.toString());
940 }
941 } else {
942 int newArticleNumber = currentArticleNumber;
943 if ( group == null ) {
944 writeLoggedFlushedResponse("412 no newsgroup selected");
945 return;
946 } else {
947 if ( param == null ) {
948 if ( currentArticleNumber < 0 ) {
949 writeLoggedFlushedResponse("420 no current article selected");
950 return;
951 } else {
952 article = group.getArticle(currentArticleNumber);
953 }
954 }
955 else {
956 newArticleNumber = Integer.parseInt(param);
957 article = group.getArticle(newArticleNumber);
958 }
959 if ( article == null ) {
960 writeLoggedFlushedResponse("423 no such article number in this group");
961 return;
962 } else {
963 currentArticleNumber = newArticleNumber;
964 String articleID = article.getUniqueID();
965 if (articleID == null) {
966 articleID = "<0>";
967 }
968 StringBuffer respBuffer =
969 new StringBuffer(128)
970 .append("223 ")
971 .append(article.getArticleNumber())
972 .append(" ")
973 .append(articleID);
974 writeLoggedFlushedResponse(respBuffer.toString());
975 }
976 }
977 }
978 }
979
980 /***
981 * Executes the BODY command. Sets the current article pointer,
982 * returns article information and body.
983 *
984 * @param the argument passed in to the BODY command,
985 * which should be an article number or message id.
986 * If no parameter is provided, the current selected
987 * article is used.
988 */
989 private void doBODY(String param) {
990
991 NNTPArticle article = null;
992 if (isMessageId(param)) {
993 article = theConfigData.getNNTPRepository().getArticleFromID(param);
994 if ( article == null ) {
995 writeLoggedFlushedResponse("430 no such article");
996 return;
997 } else {
998 StringBuffer respBuffer =
999 new StringBuffer(64)
1000 .append("222 0 ")
1001 .append(param);
1002 writeLoggedFlushedResponse(respBuffer.toString());
1003 }
1004 } else {
1005 int newArticleNumber = currentArticleNumber;
1006 if ( group == null ) {
1007 writeLoggedFlushedResponse("412 no newsgroup selected");
1008 return;
1009 } else {
1010 if ( param == null ) {
1011 if ( currentArticleNumber < 0 ) {
1012 writeLoggedFlushedResponse("420 no current article selected");
1013 return;
1014 } else {
1015 article = group.getArticle(currentArticleNumber);
1016 }
1017 }
1018 else {
1019 newArticleNumber = Integer.parseInt(param);
1020 article = group.getArticle(newArticleNumber);
1021 }
1022 if ( article == null ) {
1023 writeLoggedFlushedResponse("423 no such article number in this group");
1024 return;
1025 } else {
1026 currentArticleNumber = newArticleNumber;
1027 String articleID = article.getUniqueID();
1028 if (articleID == null) {
1029 articleID = "<0>";
1030 }
1031 StringBuffer respBuffer =
1032 new StringBuffer(128)
1033 .append("222 ")
1034 .append(article.getArticleNumber())
1035 .append(" ")
1036 .append(articleID);
1037 writeLoggedFlushedResponse(respBuffer.toString());
1038 }
1039 }
1040 }
1041 if (article != null) {
1042 writer.flush();
1043 article.writeBody(new ExtraDotOutputStream(outs));
1044 writeLoggedFlushedResponse("\r\n.");
1045 }
1046 }
1047
1048 /***
1049 * Executes the HEAD command. Sets the current article pointer,
1050 * returns article information and headers.
1051 *
1052 * @param the argument passed in to the HEAD command,
1053 * which should be an article number or message id.
1054 * If no parameter is provided, the current selected
1055 * article is used.
1056 */
1057 private void doHEAD(String param) {
1058
1059 NNTPArticle article = null;
1060 if (isMessageId(param)) {
1061 article = theConfigData.getNNTPRepository().getArticleFromID(param);
1062 if ( article == null ) {
1063 writeLoggedFlushedResponse("430 no such article");
1064 return;
1065 } else {
1066 StringBuffer respBuffer =
1067 new StringBuffer(64)
1068 .append("221 0 ")
1069 .append(param);
1070 writeLoggedFlushedResponse(respBuffer.toString());
1071 }
1072 } else {
1073 int newArticleNumber = currentArticleNumber;
1074 if ( group == null ) {
1075 writeLoggedFlushedResponse("412 no newsgroup selected");
1076 return;
1077 } else {
1078 if ( param == null ) {
1079 if ( currentArticleNumber < 0 ) {
1080 writeLoggedFlushedResponse("420 no current article selected");
1081 return;
1082 } else {
1083 article = group.getArticle(currentArticleNumber);
1084 }
1085 }
1086 else {
1087 newArticleNumber = Integer.parseInt(param);
1088 article = group.getArticle(newArticleNumber);
1089 }
1090 if ( article == null ) {
1091 writeLoggedFlushedResponse("423 no such article number in this group");
1092 return;
1093 } else {
1094 currentArticleNumber = newArticleNumber;
1095 String articleID = article.getUniqueID();
1096 if (articleID == null) {
1097 articleID = "<0>";
1098 }
1099 StringBuffer respBuffer =
1100 new StringBuffer(128)
1101 .append("221 ")
1102 .append(article.getArticleNumber())
1103 .append(" ")
1104 .append(articleID);
1105 writeLoggedFlushedResponse(respBuffer.toString());
1106 }
1107 }
1108 }
1109 if (article != null) {
1110 writer.flush();
1111 article.writeHead(new ExtraDotOutputStream(outs));
1112 writeLoggedFlushedResponse(".");
1113 }
1114 }
1115
1116 /***
1117 * Executes the ARTICLE command. Sets the current article pointer,
1118 * returns article information and contents.
1119 *
1120 * @param the argument passed in to the ARTICLE command,
1121 * which should be an article number or message id.
1122 * If no parameter is provided, the current selected
1123 * article is used.
1124 */
1125 private void doARTICLE(String param) {
1126
1127 NNTPArticle article = null;
1128 if (isMessageId(param)) {
1129 article = theConfigData.getNNTPRepository().getArticleFromID(param);
1130 if ( article == null ) {
1131 writeLoggedFlushedResponse("430 no such article");
1132 return;
1133 } else {
1134 StringBuffer respBuffer =
1135 new StringBuffer(64)
1136 .append("220 0 ")
1137 .append(param);
1138 writeLoggedResponse(respBuffer.toString());
1139 }
1140 } else {
1141 int newArticleNumber = currentArticleNumber;
1142 if ( group == null ) {
1143 writeLoggedFlushedResponse("412 no newsgroup selected");
1144 return;
1145 } else {
1146 if ( param == null ) {
1147 if ( currentArticleNumber < 0 ) {
1148 writeLoggedFlushedResponse("420 no current article selected");
1149 return;
1150 } else {
1151 article = group.getArticle(currentArticleNumber);
1152 }
1153 }
1154 else {
1155 newArticleNumber = Integer.parseInt(param);
1156 article = group.getArticle(newArticleNumber);
1157 }
1158 if ( article == null ) {
1159 writeLoggedFlushedResponse("423 no such article number in this group");
1160 return;
1161 } else {
1162 currentArticleNumber = newArticleNumber;
1163 String articleID = article.getUniqueID();
1164 if (articleID == null) {
1165 articleID = "<0>";
1166 }
1167 StringBuffer respBuffer =
1168 new StringBuffer(128)
1169 .append("220 ")
1170 .append(article.getArticleNumber())
1171 .append(" ")
1172 .append(articleID);
1173 writeLoggedFlushedResponse(respBuffer.toString());
1174 }
1175 }
1176 }
1177 if (article != null) {
1178 writer.flush();
1179 article.writeArticle(new ExtraDotOutputStream(outs));
1180
1181 writeLoggedFlushedResponse("\r\n.");
1182 }
1183 }
1184
1185 /***
1186 * Advances the current article pointer to the next article in the group.
1187 *
1188 * @param argument the argument passed in with the NEXT command
1189 */
1190 private void doNEXT(String argument) {
1191
1192 if ( argument != null ) {
1193 writeLoggedFlushedResponse("501 Syntax error - unexpected parameter");
1194 } else if ( group == null ) {
1195 writeLoggedFlushedResponse("412 no newsgroup selected");
1196 } else if ( currentArticleNumber < 0 ) {
1197 writeLoggedFlushedResponse("420 no current article has been selected");
1198 } else if ( currentArticleNumber >= group.getLastArticleNumber() ) {
1199 writeLoggedFlushedResponse("421 no next article in this group");
1200 } else {
1201 currentArticleNumber++;
1202 NNTPArticle article = group.getArticle(currentArticleNumber);
1203 StringBuffer respBuffer =
1204 new StringBuffer(64)
1205 .append("223 ")
1206 .append(article.getArticleNumber())
1207 .append(" ")
1208 .append(article.getUniqueID());
1209 writeLoggedFlushedResponse(respBuffer.toString());
1210 }
1211 }
1212
1213 /***
1214 * Moves the currently selected article pointer to the article
1215 * previous to the currently selected article in the selected group.
1216 *
1217 * @param argument the argument passed in with the LAST command
1218 */
1219 private void doLAST(String argument) {
1220
1221 if ( argument != null ) {
1222 writeLoggedFlushedResponse("501 Syntax error - unexpected parameter");
1223 } else if ( group == null ) {
1224 writeLoggedFlushedResponse("412 no newsgroup selected");
1225 } else if ( currentArticleNumber < 0 ) {
1226 writeLoggedFlushedResponse("420 no current article has been selected");
1227 } else if ( currentArticleNumber <= group.getFirstArticleNumber() ) {
1228 writeLoggedFlushedResponse("422 no previous article in this group");
1229 } else {
1230 currentArticleNumber--;
1231 NNTPArticle article = group.getArticle(currentArticleNumber);
1232 StringBuffer respBuffer =
1233 new StringBuffer(64)
1234 .append("223 ")
1235 .append(article.getArticleNumber())
1236 .append(" ")
1237 .append(article.getUniqueID());
1238 writeLoggedFlushedResponse(respBuffer.toString());
1239 }
1240 }
1241
1242 /***
1243 * Selects a group to be the current newsgroup.
1244 *
1245 * @param group the name of the group being selected.
1246 */
1247 private void doGROUP(String groupName) {
1248 if (groupName == null) {
1249 writeLoggedFlushedResponse("501 Syntax error - missing required parameter");
1250 return;
1251 }
1252 NNTPGroup newGroup = theConfigData.getNNTPRepository().getGroup(groupName);
1253
1254 if ( newGroup == null ) {
1255 writeLoggedFlushedResponse("411 no such newsgroup");
1256 } else {
1257 group = newGroup;
1258
1259
1260
1261
1262
1263 int articleCount = group.getNumberOfArticles();
1264 int lowWaterMark = group.getFirstArticleNumber();
1265 int highWaterMark = group.getLastArticleNumber();
1266
1267
1268
1269
1270 if (articleCount != 0) {
1271 currentArticleNumber = lowWaterMark;
1272 } else {
1273 currentArticleNumber = -1;
1274 }
1275 StringBuffer respBuffer =
1276 new StringBuffer(128)
1277 .append("211 ")
1278 .append(articleCount)
1279 .append(" ")
1280 .append(lowWaterMark)
1281 .append(" ")
1282 .append(highWaterMark)
1283 .append(" ")
1284 .append(group.getName())
1285 .append(" group selected");
1286 writeLoggedFlushedResponse(respBuffer.toString());
1287 }
1288 }
1289
1290 /***
1291 * Lists the extensions supported by this news server.
1292 */
1293 private void doLISTEXTENSIONS() {
1294
1295 writeLoggedResponse("202 Extensions supported:");
1296 writeLoggedResponse("LISTGROUP");
1297 writeLoggedResponse("AUTHINFO");
1298 writeLoggedResponse("OVER");
1299 writeLoggedResponse("XOVER");
1300 writeLoggedResponse("HDR");
1301 writeLoggedResponse("XHDR");
1302 writeLoggedFlushedResponse(".");
1303 }
1304
1305 /***
1306 * Informs the server that the client is a newsreader.
1307 *
1308 * @param argument the argument passed in with the MODE READER command
1309 */
1310 private void doMODEREADER(String argument) {
1311
1312 writeLoggedFlushedResponse(theConfigData.getNNTPRepository().isReadOnly()
1313 ? "201 Posting Not Permitted" : "200 Posting Permitted");
1314 }
1315
1316 /***
1317 * Informs the server that the client is a news server.
1318 *
1319 * @param argument the argument passed in with the MODE STREAM command
1320 */
1321 private void doMODESTREAM(String argument) {
1322
1323 writeLoggedFlushedResponse("500 Command not understood");
1324 }
1325
1326 /***
1327 * Gets a listing of article numbers in specified group name
1328 * or in the already selected group if the groupName is null.
1329 *
1330 * @param groupName the name of the group to list
1331 */
1332 private void doLISTGROUP(String groupName) {
1333
1334 if (groupName==null) {
1335 if ( group == null) {
1336 writeLoggedFlushedResponse("412 no news group currently selected");
1337 return;
1338 }
1339 }
1340 else {
1341 group = theConfigData.getNNTPRepository().getGroup(groupName);
1342 if ( group == null ) {
1343 writeLoggedFlushedResponse("411 no such newsgroup");
1344 return;
1345 }
1346 }
1347 if ( group != null ) {
1348
1349
1350
1351
1352
1353 if (group.getNumberOfArticles() > 0) {
1354 currentArticleNumber = group.getFirstArticleNumber();
1355 } else {
1356 currentArticleNumber = -1;
1357 }
1358
1359 writeLoggedFlushedResponse("211 list of article numbers follow");
1360
1361 Iterator iter = group.getArticles();
1362 while (iter.hasNext()) {
1363 NNTPArticle article = (NNTPArticle)iter.next();
1364 writeLoggedResponse(article.getArticleNumber() + "");
1365 }
1366 writeLoggedFlushedResponse(".");
1367 }
1368 }
1369
1370 /***
1371 * Handles the LIST OVERVIEW.FMT command. Not supported.
1372 */
1373 private void doLISTOVERVIEWFMT() {
1374
1375 writeLoggedFlushedResponse("215 Information follows");
1376 String[] overviewHeaders = theConfigData.getNNTPRepository().getOverviewFormat();
1377 for (int i = 0; i < overviewHeaders.length; i++) {
1378 writeLoggedResponse(overviewHeaders[i]);
1379 }
1380 writeLoggedFlushedResponse(".");
1381 }
1382
1383 /***
1384 * Handles the PAT command. Not supported.
1385 *
1386 * @param argument the argument passed in with the PAT command
1387 */
1388 private void doPAT(String argument) {
1389
1390 writeLoggedFlushedResponse("500 Command not recognized");
1391 }
1392
1393 /***
1394 * Get the values of the headers for the selected newsgroup,
1395 * with an optional range modifier.
1396 *
1397 * @param argument the argument passed in with the XHDR command.
1398 */
1399 private void doXHDR(String argument) {
1400 doHDR(argument);
1401 }
1402
1403 /***
1404 * Get the values of the headers for the selected newsgroup,
1405 * with an optional range modifier.
1406 *
1407 * @param argument the argument passed in with the HDR command.
1408 */
1409 private void doHDR(String argument) {
1410
1411 if (argument == null) {
1412 writeLoggedFlushedResponse("501 Syntax error - missing required parameter");
1413 return;
1414 }
1415 String hdr = argument;
1416 String range = null;
1417 int spaceIndex = hdr.indexOf(" ");
1418 if (spaceIndex >= 0 ) {
1419 range = hdr.substring(spaceIndex + 1);
1420 hdr = hdr.substring(0, spaceIndex);
1421 }
1422 if (group == null ) {
1423 writeLoggedFlushedResponse("412 No news group currently selected.");
1424 return;
1425 }
1426 if ((range == null) && (currentArticleNumber < 0)) {
1427 writeLoggedFlushedResponse("420 No current article selected");
1428 return;
1429 }
1430 NNTPArticle[] article = getRange(range);
1431 if ( article == null ) {
1432 writeLoggedFlushedResponse("412 no newsgroup selected");
1433 } else if ( article.length == 0 ) {
1434 writeLoggedFlushedResponse("430 no such article");
1435 } else {
1436 writeLoggedFlushedResponse("221 Header follows");
1437 for ( int i = 0 ; i < article.length ; i++ ) {
1438 String val = article[i].getHeader(hdr);
1439 if ( val == null ) {
1440 val = "";
1441 }
1442 StringBuffer hdrBuffer =
1443 new StringBuffer(128)
1444 .append(article[i].getArticleNumber())
1445 .append(" ")
1446 .append(val);
1447 writeLoggedResponse(hdrBuffer.toString());
1448 }
1449 writeLoggedFlushedResponse(".");
1450 }
1451 }
1452
1453 /***
1454 * Returns information from the overview database regarding the
1455 * current article, or a range of articles.
1456 *
1457 * @param range the optional article range.
1458 */
1459 private void doXOVER(String range) {
1460 doOVER(range);
1461 }
1462
1463 /***
1464 * Returns information from the overview database regarding the
1465 * current article, or a range of articles.
1466 *
1467 * @param range the optional article range.
1468 */
1469 private void doOVER(String range) {
1470
1471 if ( group == null ) {
1472 writeLoggedFlushedResponse("412 No newsgroup selected");
1473 return;
1474 }
1475 if ((range == null) && (currentArticleNumber < 0)) {
1476 writeLoggedFlushedResponse("420 No current article selected");
1477 return;
1478 }
1479 NNTPArticle[] article = getRange(range);
1480 if ( article.length == 0 ) {
1481 writeLoggedFlushedResponse("420 No article(s) selected");
1482 } else {
1483 writeLoggedResponse("224 Overview information follows");
1484 for ( int i = 0 ; i < article.length ; i++ ) {
1485 article[i].writeOverview(outs);
1486 if (i % 100 == 0) {
1487
1488
1489
1490 theWatchdog.reset();
1491 }
1492 }
1493 writeLoggedFlushedResponse(".");
1494 }
1495 }
1496
1497 /***
1498 * Handles the transaction for getting the article data.
1499 */
1500 private void createArticle() {
1501 try {
1502 InputStream msgIn = new CharTerminatedInputStream(in, NNTPTerminator);
1503
1504 msgIn = new DotStuffingInputStream(msgIn);
1505 MailHeaders headers = new MailHeaders(msgIn);
1506 processMessageHeaders(headers);
1507 processMessage(headers, msgIn);
1508 } catch (MessagingException me) {
1509 throw new NNTPException("MessagingException encountered when loading article.");
1510 }
1511 }
1512
1513 /***
1514 * Processes the NNTP message headers coming in off the wire.
1515 *
1516 * @param headers the headers of the message being read
1517 */
1518 private MailHeaders processMessageHeaders(MailHeaders headers)
1519 throws MessagingException {
1520 return headers;
1521 }
1522
1523 /***
1524 * Processes the NNTP message coming in off the wire. Reads the
1525 * content and delivers to the spool.
1526 *
1527 * @param headers the headers of the message being read
1528 * @param msgIn the stream containing the message content
1529 */
1530 private void processMessage(MailHeaders headers, InputStream bodyIn)
1531 throws MessagingException {
1532 InputStream messageIn = null;
1533 try {
1534 messageIn = new SequenceInputStream(new ByteArrayInputStream(headers.toByteArray()), bodyIn);
1535 theConfigData.getNNTPRepository().createArticle(messageIn);
1536 } finally {
1537 if (messageIn != null) {
1538 try {
1539 messageIn.close();
1540 } catch (IOException ioe) {
1541
1542 }
1543 messageIn = null;
1544 }
1545 }
1546 }
1547
1548 /***
1549 * Returns the date from @param input.
1550 * The input tokens are assumed to be in format date time [GMT|UTC] .
1551 * 'date' is in format [XX]YYMMDD. 'time' is in format 'HHMMSS'
1552 * NOTE: This routine could do with some format checks.
1553 *
1554 * @param argument the date string
1555 */
1556 private Date getDateFrom(String argument) {
1557 if (argument == null) {
1558 throw new NNTPException("Date argument was absent.");
1559 }
1560 StringTokenizer tok = new StringTokenizer(argument, " ");
1561 if (tok.countTokens() < 2) {
1562 throw new NNTPException("Date argument was ill-formed.");
1563 }
1564 String date = tok.nextToken();
1565 String time = tok.nextToken();
1566 boolean utc = ( tok.hasMoreTokens() );
1567 Date d = new Date();
1568 try {
1569 StringBuffer dateStringBuffer =
1570 new StringBuffer(64)
1571 .append(date)
1572 .append(" ")
1573 .append(time);
1574 Date dt = DF_RFC977.parse(dateStringBuffer.toString());
1575 if ( utc ) {
1576 dt = new Date(dt.getTime()+UTC_OFFSET);
1577 }
1578 return dt;
1579 } catch ( ParseException pe ) {
1580 StringBuffer exceptionBuffer =
1581 new StringBuffer(128)
1582 .append("Date extraction failed: ")
1583 .append(date)
1584 .append(",")
1585 .append(time)
1586 .append(",")
1587 .append(utc);
1588 throw new NNTPException(exceptionBuffer.toString());
1589 }
1590 }
1591
1592 /***
1593 * Returns the list of articles that match the range.
1594 *
1595 * A precondition of this method is that the selected
1596 * group be non-null. The current article pointer must
1597 * be valid if no range is explicitly provided.
1598 *
1599 * @return null indicates insufficient information to
1600 * fetch the list of articles
1601 */
1602 private NNTPArticle[] getRange(String range) {
1603
1604 if ( isMessageId(range)) {
1605 NNTPArticle article = theConfigData.getNNTPRepository().getArticleFromID(range);
1606 return ( article == null )
1607 ? new NNTPArticle[0] : new NNTPArticle[] { article };
1608 }
1609
1610 if ( range == null ) {
1611 range = "" + currentArticleNumber;
1612 }
1613
1614 int start = -1;
1615 int end = -1;
1616 int idx = range.indexOf('-');
1617 if ( idx == -1 ) {
1618 start = Integer.parseInt(range);
1619 end = start;
1620 } else {
1621 start = Integer.parseInt(range.substring(0,idx));
1622 if ( (idx + 1) == range.length() ) {
1623 end = group.getLastArticleNumber();
1624 } else {
1625 end = Integer.parseInt(range.substring(idx + 1));
1626 }
1627 }
1628 List list = new ArrayList();
1629 for ( int i = start ; i <= end ; i++ ) {
1630 NNTPArticle article = group.getArticle(i);
1631 if ( article != null ) {
1632 list.add(article);
1633 }
1634 }
1635 return (NNTPArticle[])list.toArray(new NNTPArticle[0]);
1636 }
1637
1638 /***
1639 * Return whether the user associated with the connection (possibly no
1640 * user) is authorized to execute the command.
1641 *
1642 * @param the command being tested
1643 * @return whether the command is authorized
1644 */
1645 private boolean isAuthorized(String command) {
1646 isAlreadyAuthenticated = isAlreadyAuthenticated || isAuthenticated();
1647 if (isAlreadyAuthenticated) {
1648 return true;
1649 }
1650
1651 boolean allowed = command.equals("AUTHINFO");
1652 allowed = allowed || command.equals("MODE");
1653 allowed = allowed || command.equals("QUIT");
1654 return allowed;
1655 }
1656
1657 /***
1658 * Return whether the connection has been authenticated.
1659 *
1660 * @return whether the connection has been authenticated.
1661 */
1662 private boolean isAuthenticated() {
1663 if ( theConfigData.isAuthRequired() ) {
1664 if ((user != null) && (password != null) && (theConfigData.getUsersRepository() != null)) {
1665 return theConfigData.getUsersRepository().test(user,password);
1666 } else {
1667 return false;
1668 }
1669 } else {
1670 return true;
1671 }
1672 }
1673
1674 /***
1675 * Tests a string to see whether it is formatted as a message
1676 * ID.
1677 *
1678 * @param testString the string to test
1679 *
1680 * @return whether the string is a candidate message ID
1681 */
1682 private static boolean isMessageId(String testString) {
1683 if ((testString != null) &&
1684 (testString.startsWith("<")) &&
1685 (testString.endsWith(">"))) {
1686 return true;
1687 } else {
1688 return false;
1689 }
1690 }
1691
1692 /***
1693 * This method logs at a "DEBUG" level the response string that
1694 * was sent to the SMTP client. The method is provided largely
1695 * as syntactic sugar to neaten up the code base. It is declared
1696 * private and final to encourage compiler inlining.
1697 *
1698 * @param responseString the response string sent to the client
1699 */
1700 private final void logResponseString(String responseString) {
1701 if (getLogger().isDebugEnabled()) {
1702 getLogger().debug("Sent: " + responseString);
1703 }
1704 }
1705
1706 /***
1707 * Write and flush a response string. The response is also logged.
1708 * Should be used for the last line of a multi-line response or
1709 * for a single line response.
1710 *
1711 * @param responseString the response string sent to the client
1712 */
1713 final void writeLoggedFlushedResponse(String responseString) {
1714 writer.println(responseString);
1715 writer.flush();
1716 logResponseString(responseString);
1717 }
1718
1719 /***
1720 * Write a response string. The response is also logged.
1721 * Used for multi-line responses.
1722 *
1723 * @param responseString the response string sent to the client
1724 */
1725 final void writeLoggedResponse(String responseString) {
1726 writer.println(responseString);
1727 logResponseString(responseString);
1728 }
1729
1730 /***
1731 * A private inner class which serves as an adaptor
1732 * between the WatchdogTarget interface and this
1733 * handler class.
1734 */
1735 private class NNTPWatchdogTarget
1736 implements WatchdogTarget {
1737
1738 /***
1739 * @see org.apache.james.util.watchdog.WatchdogTarget#execute()
1740 */
1741 public void execute() {
1742 NNTPHandler.this.idleClose();
1743 }
1744
1745 }
1746 }