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