KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > james > nntpserver > NNTPHandler


1 /***********************************************************************
2  * Copyright (c) 2000-2004 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.activity.Disposable;
23 import org.apache.avalon.framework.configuration.Configurable;
24 import org.apache.avalon.framework.configuration.Configuration;
25 import org.apache.avalon.framework.configuration.ConfigurationException;
26 import org.apache.avalon.framework.logger.AbstractLogEnabled;
27 import org.apache.avalon.framework.logger.Logger;
28 import org.apache.james.core.MailHeaders;
29 import org.apache.james.nntpserver.repository.NNTPArticle;
30 import org.apache.james.nntpserver.repository.NNTPGroup;
31 import org.apache.james.nntpserver.repository.NNTPRepository;
32 import org.apache.james.services.UsersRepository;
33 import org.apache.james.services.UsersStore;
34 import org.apache.james.util.CharTerminatedInputStream;
35 import org.apache.james.util.DotStuffingInputStream;
36 import org.apache.james.util.ExtraDotOutputStream;
37 import org.apache.james.util.InternetPrintWriter;
38 import org.apache.james.util.RFC977DateFormat;
39 import org.apache.james.util.RFC2980DateFormat;
40 import org.apache.james.util.SimplifiedDateFormat;
41 import org.apache.james.util.watchdog.Watchdog;
42 import org.apache.james.util.watchdog.WatchdogTarget;
43
44 import java.io.BufferedInputStream JavaDoc;
45 import java.io.BufferedOutputStream JavaDoc;
46 import java.io.BufferedReader JavaDoc;
47 import java.io.BufferedWriter JavaDoc;
48 import java.io.ByteArrayInputStream JavaDoc;
49 import java.io.IOException JavaDoc;
50 import java.io.InputStream JavaDoc;
51 import java.io.InputStreamReader JavaDoc;
52 import java.io.OutputStream JavaDoc;
53 import java.io.OutputStreamWriter JavaDoc;
54 import java.io.PrintWriter JavaDoc;
55 import java.io.SequenceInputStream JavaDoc;
56 import java.net.Socket JavaDoc;
57 import java.text.DateFormat JavaDoc;
58 import java.text.ParseException JavaDoc;
59 import java.util.ArrayList JavaDoc;
60 import java.util.Calendar JavaDoc;
61 import java.util.Date JavaDoc;
62 import java.util.Iterator JavaDoc;
63 import java.util.List JavaDoc;
64 import java.util.Locale JavaDoc;
65 import java.util.StringTokenizer JavaDoc;
66 import javax.mail.MessagingException JavaDoc;
67
68 /**
69  * The NNTP protocol is defined by RFC 977.
70  * This implementation is based on IETF draft 15, posted on 15th July '2002.
71  * URL: http://www.ietf.org/internet-drafts/draft-ietf-nntpext-base-15.txt
72  *
73  * Common NNTP extensions are in RFC 2980.
74  */

75 public class NNTPHandler
76     extends AbstractLogEnabled
77     implements ConnectionHandler, Poolable {
78
79     /**
80      * used to calculate DATE from - see 11.3
81      */

82     private static final SimplifiedDateFormat DF_RFC977 = new RFC977DateFormat();
83
84     /**
85      * Date format for the DATE keyword - see 11.1.1
86      */

87     private static final SimplifiedDateFormat DF_RFC2980 = new RFC2980DateFormat();
88
89     /**
90      * The UTC offset for this time zone.
91      */

92     public static final long UTC_OFFSET = Calendar.getInstance().get(Calendar.ZONE_OFFSET);
93
94     /**
95      * The text string for the NNTP MODE command.
96      */

97     private final static String JavaDoc COMMAND_MODE = "MODE";
98
99     /**
100      * The text string for the NNTP LIST command.
101      */

102     private final static String JavaDoc COMMAND_LIST = "LIST";
103
104     /**
105      * The text string for the NNTP GROUP command.
106      */

107     private final static String JavaDoc COMMAND_GROUP = "GROUP";
108
109     /**
110      * The text string for the NNTP NEXT command.
111      */

112     private final static String JavaDoc COMMAND_NEXT = "NEXT";
113
114     /**
115      * The text string for the NNTP LAST command.
116      */

117     private final static String JavaDoc COMMAND_LAST = "LAST";
118
119     /**
120      * The text string for the NNTP ARTICLE command.
121      */

122     private final static String JavaDoc COMMAND_ARTICLE = "ARTICLE";
123
124     /**
125      * The text string for the NNTP HEAD command.
126      */

127     private final static String JavaDoc COMMAND_HEAD = "HEAD";
128
129     /**
130      * The text string for the NNTP BODY command.
131      */

132     private final static String JavaDoc COMMAND_BODY = "BODY";
133
134     /**
135      * The text string for the NNTP STAT command.
136      */

137     private final static String JavaDoc COMMAND_STAT = "STAT";
138
139     /**
140      * The text string for the NNTP POST command.
141      */

142     private final static String JavaDoc COMMAND_POST = "POST";
143
144     /**
145      * The text string for the NNTP IHAVE command.
146      */

147     private final static String JavaDoc COMMAND_IHAVE = "IHAVE";
148
149     /**
150      * The text string for the NNTP QUIT command.
151      */

152     private final static String JavaDoc COMMAND_QUIT = "QUIT";
153
154     /**
155      * The text string for the NNTP SLAVE command.
156      */

157     private final static String JavaDoc COMMAND_SLAVE = "SLAVE";
158
159     /**
160      * The text string for the NNTP DATE command.
161      */

162     private final static String JavaDoc COMMAND_DATE = "DATE";
163
164     /**
165      * The text string for the NNTP HELP command.
166      */

167     private final static String JavaDoc COMMAND_HELP = "HELP";
168
169     /**
170      * The text string for the NNTP NEWGROUPS command.
171      */

172     private final static String JavaDoc COMMAND_NEWGROUPS = "NEWGROUPS";
173
174     /**
175      * The text string for the NNTP NEWNEWS command.
176      */

177     private final static String JavaDoc COMMAND_NEWNEWS = "NEWNEWS";
178
179     /**
180      * The text string for the NNTP LISTGROUP command.
181      */

182     private final static String JavaDoc COMMAND_LISTGROUP = "LISTGROUP";
183
184     /**
185      * The text string for the NNTP OVER command.
186      */

187     private final static String JavaDoc COMMAND_OVER = "OVER";
188
189     /**
190      * The text string for the NNTP XOVER command.
191      */

192     private final static String JavaDoc COMMAND_XOVER = "XOVER";
193
194     /**
195      * The text string for the NNTP HDR command.
196      */

197     private final static String JavaDoc COMMAND_HDR = "HDR";
198
199     /**
200      * The text string for the NNTP XHDR command.
201      */

202     private final static String JavaDoc COMMAND_XHDR = "XHDR";
203
204     /**
205      * The text string for the NNTP AUTHINFO command.
206      */

207     private final static String JavaDoc COMMAND_AUTHINFO = "AUTHINFO";
208
209     /**
210      * The text string for the NNTP PAT command.
211      */

212     private final static String JavaDoc COMMAND_PAT = "PAT";
213
214     /**
215      * The text string for the NNTP MODE READER parameter.
216      */

217     private final static String JavaDoc MODE_TYPE_READER = "READER";
218
219     /**
220      * The text string for the NNTP MODE STREAM parameter.
221      */

222     private final static String JavaDoc MODE_TYPE_STREAM = "STREAM";
223
224     /**
225      * The text string for the NNTP AUTHINFO USER parameter.
226      */

227     private final static String JavaDoc AUTHINFO_PARAM_USER = "USER";
228
229     /**
230      * The text string for the NNTP AUTHINFO PASS parameter.
231      */

232     private final static String JavaDoc AUTHINFO_PARAM_PASS = "PASS";
233
234     /**
235      * The character array that indicates termination of an NNTP message
236      */

237     private final static char[] NNTPTerminator = { '\r', '\n', '.', '\r', '\n' };
238
239     /**
240      * The thread executing this handler
241      */

242     private Thread JavaDoc handlerThread;
243
244     /**
245      * The remote host name obtained by lookup on the socket.
246      */

247     private String JavaDoc remoteHost;
248
249     /**
250      * The remote IP address of the socket.
251      */

252     private String JavaDoc remoteIP;
253
254     /**
255      * The TCP/IP socket over which the POP3 interaction
256      * is occurring
257      */

258     private Socket JavaDoc socket;
259
260     /**
261      * The incoming stream of bytes coming from the socket.
262      */

263     private InputStream in;
264
265     /**
266      * The reader associated with incoming characters.
267      */

268     private BufferedReader JavaDoc reader;
269
270     /**
271      * The socket's output stream
272      */

273     private OutputStream outs;
274
275     /**
276      * The writer to which outgoing messages are written.
277      */

278     private PrintWriter writer;
279
280     /**
281      * The current newsgroup.
282      */

283     private NNTPGroup group;
284
285     /**
286      * The current newsgroup.
287      */

288     private int currentArticleNumber = -1;
289
290     /**
291      * Per-service configuration data that applies to all handlers
292      * associated with the service.
293      */

294     private NNTPHandlerConfigurationData theConfigData;
295
296     /**
297      * The user id associated with the NNTP dialogue
298      */

299     private String JavaDoc user = null;
300
301     /**
302      * The password associated with the NNTP dialogue
303      */

304     private String JavaDoc password = null;
305
306     /**
307      * Whether the user for this session has already authenticated.
308      * Used to optimize authentication checks
309      */

310     boolean isAlreadyAuthenticated = false;
311
312     /**
313      * The watchdog being used by this handler to deal with idle timeouts.
314      */

315     private Watchdog theWatchdog;
316
317     /**
318      * The watchdog target that idles out this handler.
319      */

320     private WatchdogTarget theWatchdogTarget = new NNTPWatchdogTarget();
321
322     /**
323      * Set the configuration data for the handler
324      *
325      * @param theData configuration data for the handler
326      */

327     void setConfigurationData(NNTPHandlerConfigurationData theData) {
328         theConfigData = theData;
329     }
330
331     /**
332      * Set the Watchdog for use by this handler.
333      *
334      * @param theWatchdog the watchdog
335      */

336     void setWatchdog(Watchdog theWatchdog) {
337         this.theWatchdog = theWatchdog;
338     }
339
340     /**
341      * Gets the Watchdog Target that should be used by Watchdogs managing
342      * this connection.
343      *
344      * @return the WatchdogTarget
345      */

346     WatchdogTarget getWatchdogTarget() {
347         return theWatchdogTarget;
348     }
349
350     /**
351      * Idle out this connection
352      */

353     void idleClose() {
354         if (getLogger() != null) {
355             getLogger().error("NNTP Connection has idled out.");
356         }
357         try {
358             if (socket != null) {
359                 socket.close();
360             }
361         } catch (Exception JavaDoc e) {
362             // ignored
363
} finally {
364             socket = null;
365         }
366
367         synchronized (this) {
368             // Interrupt the thread to recover from internal hangs
369
if (handlerThread != null) {
370                 handlerThread.interrupt();
371                 handlerThread = null;
372             }
373         }
374     }
375
376     /**
377      * @see org.apache.avalon.cornerstone.services.connection.ConnectionHandler#handleConnection(Socket)
378      */

379     public void handleConnection( Socket JavaDoc connection ) throws IOException JavaDoc {
380         try {
381             this.socket = connection;
382             synchronized (this) {
383                 handlerThread = Thread.currentThread();
384             }
385             remoteIP = socket.getInetAddress().getHostAddress();
386             remoteHost = socket.getInetAddress().getHostName();
387             in = new BufferedInputStream JavaDoc(socket.getInputStream(), 1024);
388             // An ASCII encoding can be used because all transmissions other
389
// that those in the message body command are guaranteed
390
// to be ASCII
391
reader = new BufferedReader JavaDoc(new InputStreamReader JavaDoc(in, "ASCII"), 512);
392             outs = new BufferedOutputStream JavaDoc(socket.getOutputStream(), 1024);
393             writer = new InternetPrintWriter(outs, true);
394         } catch (Exception JavaDoc e) {
395             StringBuffer JavaDoc exceptionBuffer =
396                 new StringBuffer JavaDoc(256)
397                     .append("Cannot open connection from ")
398                     .append(remoteHost)
399                     .append(" (")
400                     .append(remoteIP)
401                     .append("): ")
402                     .append(e.getMessage());
403             String JavaDoc exceptionString = exceptionBuffer.toString();
404             getLogger().error(exceptionString, e );
405         }
406
407         try {
408             // section 7.1
409
if ( theConfigData.getNNTPRepository().isReadOnly() ) {
410                 StringBuffer JavaDoc respBuffer =
411                     new StringBuffer JavaDoc(128)
412                         .append("201 ")
413                         .append(theConfigData.getHelloName())
414                         .append(" NNTP Service Ready, posting prohibited");
415                 writeLoggedFlushedResponse(respBuffer.toString());
416             } else {
417                 StringBuffer JavaDoc respBuffer =
418                     new StringBuffer JavaDoc(128)
419                             .append("200 ")
420                             .append(theConfigData.getHelloName())
421                             .append(" NNTP Service Ready, posting permitted");
422                 writeLoggedFlushedResponse(respBuffer.toString());
423             }
424
425             theWatchdog.start();
426             while (parseCommand(reader.readLine())) {
427                 theWatchdog.reset();
428             }
429             theWatchdog.stop();
430
431             getLogger().info("Connection closed");
432         } catch (Exception JavaDoc e) {
433             // If the connection has been idled out, the
434
// socket will be closed and null. Do NOT
435
// log the exception or attempt to send the
436
// closing connection message
437
if (socket != null) {
438                 try {
439                     doQUIT(null);
440                 } catch (Throwable JavaDoc t) {}
441                 getLogger().error( "Exception during connection:" + e.getMessage(), e );
442             }
443         } finally {
444             resetHandler();
445         }
446     }
447
448     /**
449      * Resets the handler data to a basic state.
450      */

451     private void resetHandler() {
452
453         // Clear the Watchdog
454
if (theWatchdog != null) {
455             if (theWatchdog instanceof Disposable) {
456                 ((Disposable)theWatchdog).dispose();
457             }
458             theWatchdog = null;
459         }
460
461         // Clear the streams
462
try {
463             if (reader != null) {
464                 reader.close();
465             }
466         } catch (IOException JavaDoc ioe) {
467             getLogger().warn("NNTPHandler: Unexpected exception occurred while closing reader: " + ioe);
468         } finally {
469             reader = null;
470         }
471
472         in = null;
473
474         if (writer != null) {
475             writer.close();
476             writer = null;
477         }
478         outs = null;
479
480         remoteHost = null;
481         remoteIP = null;
482         try {
483             if (socket != null) {
484                 socket.close();
485             }
486         } catch (IOException JavaDoc ioe) {
487             getLogger().warn("NNTPHandler: Unexpected exception occurred while closing socket: " + ioe);
488         } finally {
489             socket = null;
490         }
491
492         synchronized (this) {
493             handlerThread = null;
494         }
495
496         // Clear the selected group, article info
497
group = null;
498         currentArticleNumber = -1;
499
500         // Clear the authentication info
501
user = null;
502         password = null;
503         isAlreadyAuthenticated = false;
504
505         // Clear the config data
506
theConfigData = null;
507     }
508
509     /**
510      * This method parses NNTP commands read off the wire in handleConnection.
511      * Actual processing of the command (possibly including additional back and
512      * forth communication with the client) is delegated to one of a number of
513      * command specific handler methods. The primary purpose of this method is
514      * to parse the raw command string to determine exactly which handler should
515      * be called. It returns true if expecting additional commands, false otherwise.
516      *
517      * @param commandRaw the raw command string passed in over the socket
518      *
519      * @return whether additional commands are expected.
520      */

521     private boolean parseCommand(String JavaDoc commandRaw) {
522         if (commandRaw == null) {
523             return false;
524         }
525         if (getLogger().isDebugEnabled()) {
526             getLogger().debug("Command received: " + commandRaw);
527         }
528
529         String JavaDoc command = commandRaw.trim();
530         String JavaDoc argument = null;
531         int spaceIndex = command.indexOf(" ");
532         if (spaceIndex >= 0) {
533             argument = command.substring(spaceIndex + 1);
534             command = command.substring(0, spaceIndex);
535         }
536         command = command.toUpperCase(Locale.US);
537
538         boolean returnValue = true;
539         if (!isAuthorized(command) ) {
540             writeLoggedFlushedResponse("480 User is not authenticated");
541             getLogger().debug("Command not allowed.");
542             return returnValue;
543         }
544         if ((command.equals(COMMAND_MODE)) && (argument != null)) {
545             if (argument.toUpperCase(Locale.US).equals(MODE_TYPE_READER)) {
546                 doMODEREADER(argument);
547             } else if (argument.toUpperCase(Locale.US).equals(MODE_TYPE_STREAM)) {
548                 doMODESTREAM(argument);
549             } else {
550                 writeLoggedFlushedResponse("500 Command not understood");
551             }
552         } else if ( command.equals(COMMAND_LIST)) {
553             doLIST(argument);
554         } else if ( command.equals(COMMAND_GROUP) ) {
555             doGROUP(argument);
556         } else if ( command.equals(COMMAND_NEXT) ) {
557             doNEXT(argument);
558         } else if ( command.equals(COMMAND_LAST) ) {
559             doLAST(argument);
560         } else if ( command.equals(COMMAND_ARTICLE) ) {
561             doARTICLE(argument);
562         } else if ( command.equals(COMMAND_HEAD) ) {
563             doHEAD(argument);
564         } else if ( command.equals(COMMAND_BODY) ) {
565             doBODY(argument);
566         } else if ( command.equals(COMMAND_STAT) ) {
567             doSTAT(argument);
568         } else if ( command.equals(COMMAND_POST) ) {
569             doPOST(argument);
570         } else if ( command.equals(COMMAND_IHAVE) ) {
571             doIHAVE(argument);
572         } else if ( command.equals(COMMAND_QUIT) ) {
573             doQUIT(argument);
574             returnValue = false;
575         } else if ( command.equals(COMMAND_DATE) ) {
576             doDATE(argument);
577         } else if ( command.equals(COMMAND_HELP) ) {
578             doHELP(argument);
579         } else if ( command.equals(COMMAND_NEWGROUPS) ) {
580             doNEWGROUPS(argument);
581         } else if ( command.equals(COMMAND_NEWNEWS) ) {
582             doNEWNEWS(argument);
583         } else if ( command.equals(COMMAND_LISTGROUP) ) {
584             doLISTGROUP(argument);
585         } else if ( command.equals(COMMAND_OVER) ) {
586             doOVER(argument);
587         } else if ( command.equals(COMMAND_XOVER) ) {
588             doXOVER(argument);
589         } else if ( command.equals(COMMAND_HDR) ) {
590             doHDR(argument);
591         } else if ( command.equals(COMMAND_XHDR) ) {
592             doXHDR(argument);
593         } else if ( command.equals(COMMAND_AUTHINFO) ) {
594             doAUTHINFO(argument);
595         } else if ( command.equals(COMMAND_SLAVE) ) {
596             doSLAVE(argument);
597         } else if ( command.equals(COMMAND_PAT) ) {
598             doPAT(argument);
599         } else {
600             doUnknownCommand(command, argument);
601         }
602         return returnValue;
603     }
604
605     /**
606      * Handles an unrecognized command, logging that.
607      *
608      * @param command the command received from the client
609      * @param argument the argument passed in with the command
610      */

611     private void doUnknownCommand(String JavaDoc command, String JavaDoc argument) {
612         if (getLogger().isDebugEnabled()) {
613             StringBuffer JavaDoc logBuffer =
614                 new StringBuffer JavaDoc(128)
615                     .append("Received unknown command ")
616                     .append(command)
617                     .append(" with argument ")
618                     .append(argument);
619             getLogger().debug(logBuffer.toString());
620         }
621         writeLoggedFlushedResponse("500 Unknown command");
622     }
623
624     /**
625      * Implements only the originnal AUTHINFO.
626      * for simple and generic AUTHINFO, 501 is sent back. This is as
627      * per article 3.1.3 of RFC 2980
628      *
629      * @param argument the argument passed in with the AUTHINFO command
630      */

631     private void doAUTHINFO(String JavaDoc argument) {
632         String JavaDoc command = null;
633         String JavaDoc value = null;
634         if (argument != null) {
635             int spaceIndex = argument.indexOf(" ");
636             if (spaceIndex >= 0) {
637                 command = argument.substring(0, spaceIndex);
638                 value = argument.substring(spaceIndex + 1);
639             }
640         }
641         if (command == null) {
642             writeLoggedFlushedResponse("501 Syntax error");
643             return;
644         }
645         command = command.toUpperCase(Locale.US);
646         if ( command.equals(AUTHINFO_PARAM_USER) ) {
647             // Reject re-authentication
648
if ( isAlreadyAuthenticated ) {
649                 writeLoggedFlushedResponse("482 Already authenticated - rejecting new credentials");
650             }
651             // Reject doubly sent user
652
if (user != null) {
653                 user = null;
654                 password = null;
655                 isAlreadyAuthenticated = false;
656                 writeLoggedFlushedResponse("482 User already specified - rejecting new user");
657                 return;
658             }
659             user = value;
660             writeLoggedFlushedResponse("381 More authentication information required");
661         } else if ( command.equals(AUTHINFO_PARAM_PASS) ) {
662             // Reject password sent before user
663
if (user == null) {
664                 writeLoggedFlushedResponse("482 User not yet specified. Rejecting user.");
665                 return;
666             }
667             // Reject doubly sent password
668
if (password != null) {
669                 user = null;
670                 password = null;
671                 isAlreadyAuthenticated = false;
672                 writeLoggedFlushedResponse("482 Password already specified - rejecting new password");
673                 return;
674             }
675             password = value;
676             isAlreadyAuthenticated = isAuthenticated();
677             if ( isAlreadyAuthenticated ) {
678                 writeLoggedFlushedResponse("281 Authentication accepted");
679             } else {
680                 writeLoggedFlushedResponse("482 Authentication rejected");
681                 // Clear bad authentication
682
user = null;
683                 password = null;
684             }
685         } else {
686             writeLoggedFlushedResponse("501 Syntax error");
687             return;
688         }
689     }
690
691     /**
692      * Lists the articles posted since the date passed in as
693      * an argument.
694      *
695      * @param argument the argument passed in with the NEWNEWS command.
696      * Should be a wildmat followed by a date.
697      */

698     private void doNEWNEWS(String JavaDoc argument) {
699         // see section 11.4
700

701         String JavaDoc wildmat = "*";
702
703         if (argument != null) {
704             int spaceIndex = argument.indexOf(" ");
705             if (spaceIndex >= 0) {
706                 wildmat = argument.substring(0, spaceIndex);
707                 argument = argument.substring(spaceIndex + 1);
708             } else {
709                 getLogger().error("NEWNEWS had an invalid argument");
710                 writeLoggedFlushedResponse("501 Syntax error");
711                 return;
712             }
713         } else {
714             getLogger().error("NEWNEWS had a null argument");
715             writeLoggedFlushedResponse("501 Syntax error");
716             return;
717         }
718
719         Date JavaDoc theDate = null;
720         try {
721             theDate = getDateFrom(argument);
722         } catch (NNTPException nntpe) {
723             getLogger().error("NEWNEWS had an invalid argument", nntpe);
724             writeLoggedFlushedResponse("501 Syntax error");
725             return;
726         }
727
728         writeLoggedFlushedResponse("230 list of new articles by message-id follows");
729         Iterator JavaDoc groupIter = theConfigData.getNNTPRepository().getMatchedGroups(wildmat);
730         while ( groupIter.hasNext() ) {
731             Iterator JavaDoc articleIter = ((NNTPGroup)(groupIter.next())).getArticlesSince(theDate);
732             while (articleIter.hasNext()) {
733                 StringBuffer JavaDoc iterBuffer =
734                     new StringBuffer JavaDoc(64)
735                         .append(((NNTPArticle)articleIter.next()).getUniqueID());
736                 writeLoggedResponse(iterBuffer.toString());
737             }
738         }
739         writeLoggedFlushedResponse(".");
740     }
741
742     /**
743      * Lists the groups added since the date passed in as
744      * an argument.
745      *
746      * @param argument the argument passed in with the NEWGROUPS command.
747      * Should be a date.
748      */

749     private void doNEWGROUPS(String JavaDoc argument) {
750         // see section 11.3
751
// both draft-ietf-nntpext-base-15.txt and rfc977 have only group names
752
// in response lines, but INN sends
753
// '<group name> <last article> <first article> <posting allowed>'
754
// NOTE: following INN over either document.
755
//
756
// TODO: Check this. Audit at http://www.academ.com/pipermail/ietf-nntp/2001-July/002185.html
757
// doesn't mention the supposed discrepancy. Consider changing code to
758
// be in line with spec.
759
Date JavaDoc theDate = null;
760         try {
761             theDate = getDateFrom(argument);
762         } catch (NNTPException nntpe) {
763             getLogger().error("NEWGROUPS had an invalid argument", nntpe);
764             writeLoggedFlushedResponse("501 Syntax error");
765             return;
766         }
767         Iterator JavaDoc iter = theConfigData.getNNTPRepository().getGroupsSince(theDate);
768         writeLoggedFlushedResponse("231 list of new newsgroups follows");
769         while ( iter.hasNext() ) {
770             NNTPGroup currentGroup = (NNTPGroup)iter.next();
771             StringBuffer JavaDoc iterBuffer =
772                 new StringBuffer JavaDoc(128)
773                     .append(currentGroup.getName())
774                     .append(" ")
775                     .append(currentGroup.getLastArticleNumber())
776                     .append(" ")
777                     .append(currentGroup.getFirstArticleNumber())
778                     .append(" ")
779                     .append((currentGroup.isPostAllowed()?"y":"n"));
780             writeLoggedResponse(iterBuffer.toString());
781         }
782         writeLoggedFlushedResponse(".");
783     }
784
785     /**
786      * Lists the help text for the service.
787      *
788      * @param argument the argument passed in with the HELP command.
789      */

790     private void doHELP(String JavaDoc argument) {
791         writeLoggedResponse("100 Help text follows");
792         writeLoggedFlushedResponse(".");
793     }
794
795     /**
796      * Acknowledges a SLAVE command. No special preference is given
797      * to slave connections.
798      *
799      * @param argument the argument passed in with the SLAVE command.
800      */

801     private void doSLAVE(String JavaDoc argument) {
802         writeLoggedFlushedResponse("202 slave status noted");
803     }
804
805     /**
806      * Returns the current date according to the news server.
807      *
808      * @param argument the argument passed in with the DATE command
809      */

810     private void doDATE(String JavaDoc argument) {
811         Date JavaDoc dt = new Date JavaDoc(System.currentTimeMillis()-UTC_OFFSET);
812         String JavaDoc dtStr = DF_RFC2980.format(new Date JavaDoc(dt.getTime() - UTC_OFFSET));
813         writeLoggedFlushedResponse("111 " + dtStr);
814     }
815
816     /**
817      * Quits the transaction.
818      *
819      * @param argument the argument passed in with the QUIT command
820      */

821     private void doQUIT(String JavaDoc argument) {
822         writeLoggedFlushedResponse("205 closing connection");
823     }
824
825     /**
826      * Handles the LIST command and its assorted extensions.
827      *
828      * @param argument the argument passed in with the LIST command.
829      */

830     private void doLIST(String JavaDoc argument) {
831         // see section 9.4.1
832
String JavaDoc wildmat = "*";
833         boolean isListNewsgroups = false;
834
835         String JavaDoc extension = argument;
836         if (argument != null) {
837             int spaceIndex = argument.indexOf(" ");
838             if (spaceIndex >= 0) {
839                 wildmat = argument.substring(spaceIndex + 1);
840                 extension = argument.substring(0, spaceIndex);
841             }
842             extension = extension.toUpperCase(Locale.US);
843         }
844
845         if (extension != null) {
846             if (extension.equals("ACTIVE")) {
847                 isListNewsgroups = false;
848             } else if (extension.equals("NEWSGROUPS") ) {
849                 isListNewsgroups = true;
850             } else if (extension.equals("EXTENSIONS") ) {
851                 doLISTEXTENSIONS();
852                 return;
853             } else if (extension.equals("OVERVIEW.FMT") ) {
854                 doLISTOVERVIEWFMT();
855                 return;
856             } else if (extension.equals("ACTIVE.TIMES") ) {
857                 // not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
858
writeLoggedFlushedResponse("503 program error, function not performed");
859                 return;
860             } else if (extension.equals("DISTRIBUTIONS") ) {
861                 // not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
862
writeLoggedFlushedResponse("503 program error, function not performed");
863                 return;
864             } else if (extension.equals("DISTRIB.PATS") ) {
865                 // not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
866
writeLoggedFlushedResponse("503 program error, function not performed");
867                 return;
868             } else {
869                 writeLoggedFlushedResponse("501 Syntax error");
870                 return;
871             }
872         }
873
874         Iterator JavaDoc iter = theConfigData.getNNTPRepository().getMatchedGroups(wildmat);
875         writeLoggedFlushedResponse("215 list of newsgroups follows");
876         while ( iter.hasNext() ) {
877             NNTPGroup theGroup = (NNTPGroup)iter.next();
878             if (isListNewsgroups) {
879                 writeLoggedResponse(theGroup.getListNewsgroupsFormat());
880             } else {
881                 writeLoggedResponse(theGroup.getListFormat());
882             }
883         }
884         writeLoggedFlushedResponse(".");
885     }
886
887     /**
888      * Informs the server that the client has an article with the specified
889      * message-ID.
890      *
891      * @param id the message id
892      */

893     private void doIHAVE(String JavaDoc id) {
894         // see section 9.3.2.1
895
if (!isMessageId(id)) {
896             writeLoggedFlushedResponse("501 command syntax error");
897             return;
898         }
899         NNTPArticle article = theConfigData.getNNTPRepository().getArticleFromID(id);
900         if ( article != null ) {
901             writeLoggedFlushedResponse("435 article not wanted - do not send it");
902         } else {
903             writeLoggedFlushedResponse("335 send article to be transferred. End with <CR-LF>.<CR-LF>");
904             try {
905                 createArticle();
906             } catch (RuntimeException JavaDoc e) {
907                 writeLoggedFlushedResponse("436 transfer failed - try again later");
908                 throw e;
909             }
910             writeLoggedFlushedResponse("235 article received ok");
911         }
912     }
913
914     /**
915      * Posts an article to the news server.
916      *
917      * @param argument the argument passed in with the POST command
918      */

919     private void doPOST(String JavaDoc argument) {
920         // see section 9.3.1.1
921
if ( argument != null ) {
922             writeLoggedFlushedResponse("501 Syntax error - unexpected parameter");
923         }
924         writeLoggedFlushedResponse("340 send article to be posted. End with <CR-LF>.<CR-LF>");
925         createArticle();
926         writeLoggedFlushedResponse("240 article received ok");
927     }
928
929     /**
930      * Executes the STAT command. Sets the current article pointer,
931      * returns article information.
932      *
933      * @param the argument passed in to the STAT command,
934      * which should be an article number or message id.
935      * If no parameter is provided, the current selected
936      * article is used.
937      */

938     private void doSTAT(String JavaDoc param) {
939         // section 9.2.4
940
NNTPArticle article = null;
941         if (isMessageId(param)) {
942             article = theConfigData.getNNTPRepository().getArticleFromID(param);
943             if ( article == null ) {
944                 writeLoggedFlushedResponse("430 no such article");
945                 return;
946             } else {
947                 StringBuffer JavaDoc respBuffer =
948                     new StringBuffer JavaDoc(64)
949                             .append("223 0 ")
950                             .append(param);
951                 writeLoggedFlushedResponse(respBuffer.toString());
952             }
953         } else {
954             int newArticleNumber = currentArticleNumber;
955             if ( group == null ) {
956                 writeLoggedFlushedResponse("412 no newsgroup selected");
957                 return;
958             } else {
959                 if ( param == null ) {
960                     if ( currentArticleNumber < 0 ) {
961                         writeLoggedFlushedResponse("420 no current article selected");
962                         return;
963                     } else {
964                         article = group.getArticle(currentArticleNumber);
965                     }
966                 }
967                 else {
968                     newArticleNumber = Integer.parseInt(param);
969                     article = group.getArticle(newArticleNumber);
970                 }
971                 if ( article == null ) {
972                     writeLoggedFlushedResponse("423 no such article number in this group");
973                     return;
974                 } else {
975                     currentArticleNumber = newArticleNumber;
976                     String JavaDoc articleID = article.getUniqueID();
977                     if (articleID == null) {
978                         articleID = "<0>";
979                     }
980                     StringBuffer JavaDoc respBuffer =
981                         new StringBuffer JavaDoc(128)
982                                 .append("223 ")
983                                 .append(article.getArticleNumber())
984                                 .append(" ")
985                                 .append(articleID);
986                     writeLoggedFlushedResponse(respBuffer.toString());
987                 }
988             }
989         }
990     }
991
992     /**
993      * Executes the BODY command. Sets the current article pointer,
994      * returns article information and body.
995      *
996      * @param the argument passed in to the BODY command,
997      * which should be an article number or message id.
998      * If no parameter is provided, the current selected
999      * article is used.
1000     */

1001    private void doBODY(String JavaDoc param) {
1002        // section 9.2.3
1003
NNTPArticle article = null;
1004        if (isMessageId(param)) {
1005            article = theConfigData.getNNTPRepository().getArticleFromID(param);
1006            if ( article == null ) {
1007                writeLoggedFlushedResponse("430 no such article");
1008                return;
1009            } else {
1010                StringBuffer JavaDoc respBuffer =
1011                    new StringBuffer JavaDoc(64)
1012                            .append("222 0 ")
1013                            .append(param);
1014                writeLoggedFlushedResponse(respBuffer.toString());
1015            }
1016        } else {
1017            int newArticleNumber = currentArticleNumber;
1018            if ( group == null ) {
1019                writeLoggedFlushedResponse("412 no newsgroup selected");
1020                return;
1021            } else {
1022                if ( param == null ) {
1023                    if ( currentArticleNumber < 0 ) {
1024                        writeLoggedFlushedResponse("420 no current article selected");
1025                        return;
1026                    } else {
1027                        article = group.getArticle(currentArticleNumber);
1028                    }
1029                }
1030                else {
1031                    newArticleNumber = Integer.parseInt(param);
1032                    article = group.getArticle(newArticleNumber);
1033                }
1034                if ( article == null ) {
1035                    writeLoggedFlushedResponse("423 no such article number in this group");
1036                    return;
1037                } else {
1038                    currentArticleNumber = newArticleNumber;
1039                    String JavaDoc articleID = article.getUniqueID();
1040                    if (articleID == null) {
1041                        articleID = "<0>";
1042                    }
1043                    StringBuffer JavaDoc respBuffer =
1044                        new StringBuffer JavaDoc(128)
1045                                .append("222 ")
1046                                .append(article.getArticleNumber())
1047                                .append(" ")
1048                                .append(articleID);
1049                    writeLoggedFlushedResponse(respBuffer.toString());
1050                }
1051            }
1052        }
1053        if (article != null) {
1054            writer.flush();
1055            article.writeBody(new ExtraDotOutputStream(outs));
1056            writeLoggedFlushedResponse(".");
1057        }
1058    }
1059
1060    /**
1061     * Executes the HEAD command. Sets the current article pointer,
1062     * returns article information and headers.
1063     *
1064     * @param the argument passed in to the HEAD command,
1065     * which should be an article number or message id.
1066     * If no parameter is provided, the current selected
1067     * article is used.
1068     */

1069    private void doHEAD(String JavaDoc param) {
1070        // section 9.2.2
1071
NNTPArticle article = null;
1072        if (isMessageId(param)) {
1073            article = theConfigData.getNNTPRepository().getArticleFromID(param);
1074            if ( article == null ) {
1075                writeLoggedFlushedResponse("430 no such article");
1076                return;
1077            } else {
1078                StringBuffer JavaDoc respBuffer =
1079                    new StringBuffer JavaDoc(64)
1080                            .append("221 0 ")
1081                            .append(param);
1082                writeLoggedFlushedResponse(respBuffer.toString());
1083            }
1084        } else {
1085            int newArticleNumber = currentArticleNumber;
1086            if ( group == null ) {
1087                writeLoggedFlushedResponse("412 no newsgroup selected");
1088                return;
1089            } else {
1090                if ( param == null ) {
1091                    if ( currentArticleNumber < 0 ) {
1092                        writeLoggedFlushedResponse("420 no current article selected");
1093                        return;
1094                    } else {
1095                        article = group.getArticle(currentArticleNumber);
1096                    }
1097                }
1098                else {
1099                    newArticleNumber = Integer.parseInt(param);
1100                    article = group.getArticle(newArticleNumber);
1101                }
1102                if ( article == null ) {
1103                    writeLoggedFlushedResponse("423 no such article number in this group");
1104                    return;
1105                } else {
1106                    currentArticleNumber = newArticleNumber;
1107                    String JavaDoc articleID = article.getUniqueID();
1108                    if (articleID == null) {
1109                        articleID = "<0>";
1110                    }
1111                    StringBuffer JavaDoc respBuffer =
1112                        new StringBuffer JavaDoc(128)
1113                                .append("221 ")
1114                                .append(article.getArticleNumber())
1115                                .append(" ")
1116                                .append(articleID);
1117                    writeLoggedFlushedResponse(respBuffer.toString());
1118                }
1119            }
1120        }
1121        if (article != null) {
1122            writer.flush();
1123            article.writeHead(new ExtraDotOutputStream(outs));
1124            writeLoggedFlushedResponse(".");
1125        }
1126    }
1127
1128    /**
1129     * Executes the ARTICLE command. Sets the current article pointer,
1130     * returns article information and contents.
1131     *
1132     * @param the argument passed in to the ARTICLE command,
1133     * which should be an article number or message id.
1134     * If no parameter is provided, the current selected
1135     * article is used.
1136     */

1137    private void doARTICLE(String JavaDoc param) {
1138        // section 9.2.1
1139
NNTPArticle article = null;
1140        if (isMessageId(param)) {
1141            article = theConfigData.getNNTPRepository().getArticleFromID(param);
1142            if ( article == null ) {
1143                writeLoggedFlushedResponse("430 no such article");
1144                return;
1145            } else {
1146                StringBuffer JavaDoc respBuffer =
1147                    new StringBuffer JavaDoc(64)
1148                            .append("220 0 ")
1149                            .append(param);
1150                writeLoggedResponse(respBuffer.toString());
1151            }
1152        } else {
1153            int newArticleNumber = currentArticleNumber;
1154            if ( group == null ) {
1155                writeLoggedFlushedResponse("412 no newsgroup selected");
1156                return;
1157            } else {
1158                if ( param == null ) {
1159                    if ( currentArticleNumber < 0 ) {
1160                        writeLoggedFlushedResponse("420 no current article selected");
1161                        return;
1162                    } else {
1163                        article = group.getArticle(currentArticleNumber);
1164                    }
1165                }
1166                else {
1167                    newArticleNumber = Integer.parseInt(param);
1168                    article = group.getArticle(newArticleNumber);
1169                }
1170                if ( article == null ) {
1171                    writeLoggedFlushedResponse("423 no such article number in this group");
1172                    return;
1173                } else {
1174                    currentArticleNumber = newArticleNumber;
1175                    String JavaDoc articleID = article.getUniqueID();
1176                    if (articleID == null) {
1177                        articleID = "<0>";
1178                    }
1179                    StringBuffer JavaDoc respBuffer =
1180                        new StringBuffer JavaDoc(128)
1181                                .append("220 ")
1182                                .append(article.getArticleNumber())
1183                                .append(" ")
1184                                .append(articleID);
1185                    writeLoggedFlushedResponse(respBuffer.toString());
1186                }
1187            }
1188        }
1189        if (article != null) {
1190            writer.flush();
1191            article.writeArticle(new ExtraDotOutputStream(outs));
1192            writeLoggedFlushedResponse(".");
1193        }
1194    }
1195
1196    /**
1197     * Advances the current article pointer to the next article in the group.
1198     *
1199     * @param argument the argument passed in with the NEXT command
1200     */

1201    private void doNEXT(String JavaDoc argument) {
1202        // section 9.1.1.3.1
1203
if ( argument != null ) {
1204            writeLoggedFlushedResponse("501 Syntax error - unexpected parameter");
1205        } else if ( group == null ) {
1206            writeLoggedFlushedResponse("412 no newsgroup selected");
1207        } else if ( currentArticleNumber < 0 ) {
1208            writeLoggedFlushedResponse("420 no current article has been selected");
1209        } else if ( currentArticleNumber >= group.getLastArticleNumber() ) {
1210            writeLoggedFlushedResponse("421 no next article in this group");
1211        } else {
1212            currentArticleNumber++;
1213            NNTPArticle article = group.getArticle(currentArticleNumber);
1214            StringBuffer JavaDoc respBuffer =
1215                new StringBuffer JavaDoc(64)
1216                        .append("223 ")
1217                        .append(article.getArticleNumber())
1218                        .append(" ")
1219                        .append(article.getUniqueID());
1220            writeLoggedFlushedResponse(respBuffer.toString());
1221        }
1222    }
1223
1224    /**
1225     * Moves the currently selected article pointer to the article
1226     * previous to the currently selected article in the selected group.
1227     *
1228     * @param argument the argument passed in with the LAST command
1229     */

1230    private void doLAST(String JavaDoc argument) {
1231        // section 9.1.1.2.1
1232
if ( argument != null ) {
1233            writeLoggedFlushedResponse("501 Syntax error - unexpected parameter");
1234        } else if ( group == null ) {
1235            writeLoggedFlushedResponse("412 no newsgroup selected");
1236        } else if ( currentArticleNumber < 0 ) {
1237            writeLoggedFlushedResponse("420 no current article has been selected");
1238        } else if ( currentArticleNumber <= group.getFirstArticleNumber() ) {
1239            writeLoggedFlushedResponse("422 no previous article in this group");
1240        } else {
1241            currentArticleNumber--;
1242            NNTPArticle article = group.getArticle(currentArticleNumber);
1243            StringBuffer JavaDoc respBuffer =
1244                new StringBuffer JavaDoc(64)
1245                        .append("223 ")
1246                        .append(article.getArticleNumber())
1247                        .append(" ")
1248                        .append(article.getUniqueID());
1249            writeLoggedFlushedResponse(respBuffer.toString());
1250        }
1251    }
1252
1253    /**
1254     * Selects a group to be the current newsgroup.
1255     *
1256     * @param group the name of the group being selected.
1257     */

1258    private void doGROUP(String JavaDoc groupName) {
1259        if (groupName == null) {
1260            writeLoggedFlushedResponse("501 Syntax error - missing required parameter");
1261            return;
1262        }
1263        NNTPGroup newGroup = theConfigData.getNNTPRepository().getGroup(groupName);
1264        // section 9.1.1.1
1265
if ( newGroup == null ) {
1266            writeLoggedFlushedResponse("411 no such newsgroup");
1267        } else {
1268            group = newGroup;
1269            // if the number of articles in group == 0
1270
// then the server may return this information in 3 ways,
1271
// The clients must honor all those 3 ways.
1272
// our response is:
1273
// highWaterMark == lowWaterMark and number of articles == 0
1274
int articleCount = group.getNumberOfArticles();
1275            int lowWaterMark = group.getFirstArticleNumber();
1276            int highWaterMark = group.getLastArticleNumber();
1277
1278            // Set the current article pointer. If no
1279
// articles are in the group, the current article
1280
// pointer should be explicitly unset.
1281
if (articleCount != 0) {
1282                currentArticleNumber = lowWaterMark;
1283            } else {
1284                currentArticleNumber = -1;
1285            }
1286            StringBuffer JavaDoc respBuffer =
1287                new StringBuffer JavaDoc(128)
1288                        .append("211 ")
1289                        .append(articleCount)
1290                        .append(" ")
1291                        .append(lowWaterMark)
1292                        .append(" ")
1293                        .append(highWaterMark)
1294                        .append(" ")
1295                        .append(group.getName())
1296                        .append(" group selected");
1297            writeLoggedFlushedResponse(respBuffer.toString());
1298        }
1299    }
1300
1301    /**
1302     * Lists the extensions supported by this news server.
1303     */

1304    private void doLISTEXTENSIONS() {
1305        // 8.1.1
1306
writeLoggedResponse("202 Extensions supported:");
1307        writeLoggedResponse("LISTGROUP");
1308        writeLoggedResponse("AUTHINFO");
1309        writeLoggedResponse("OVER");
1310        writeLoggedResponse("XOVER");
1311        writeLoggedResponse("HDR");
1312        writeLoggedResponse("XHDR");
1313        writeLoggedFlushedResponse(".");
1314    }
1315
1316    /**
1317     * Informs the server that the client is a newsreader.
1318     *
1319     * @param argument the argument passed in with the MODE READER command
1320     */

1321    private void doMODEREADER(String JavaDoc argument) {
1322        // 7.2
1323
writeLoggedFlushedResponse(theConfigData.getNNTPRepository().isReadOnly()
1324                       ? "201 Posting Not Permitted" : "200 Posting Permitted");
1325    }
1326
1327    /**
1328     * Informs the server that the client is a news server.
1329     *
1330     * @param argument the argument passed in with the MODE STREAM command
1331     */

1332    private void doMODESTREAM(String JavaDoc argument) {
1333        // 7.2
1334
writeLoggedFlushedResponse("500 Command not understood");
1335    }
1336
1337    /**
1338     * Gets a listing of article numbers in specified group name
1339     * or in the already selected group if the groupName is null.
1340     *
1341     * @param groupName the name of the group to list
1342     */

1343    private void doLISTGROUP(String JavaDoc groupName) {
1344        // 9.5.1.1.1
1345
if (groupName==null) {
1346            if ( group == null) {
1347                writeLoggedFlushedResponse("412 no news group currently selected");
1348                return;
1349            }
1350        }
1351        else {
1352            group = theConfigData.getNNTPRepository().getGroup(groupName);
1353            if ( group == null ) {
1354                writeLoggedFlushedResponse("411 no such newsgroup");
1355                return;
1356            }
1357        }
1358        if ( group != null ) {
1359            // this.group = group;
1360

1361            // Set the current article pointer. If no
1362
// articles are in the group, the current article
1363
// pointer should be explicitly unset.
1364
if (group.getNumberOfArticles() > 0) {
1365                currentArticleNumber = group.getFirstArticleNumber();
1366            } else {
1367                currentArticleNumber = -1;
1368            }
1369
1370            writeLoggedFlushedResponse("211 list of article numbers follow");
1371
1372            Iterator JavaDoc iter = group.getArticles();
1373            while (iter.hasNext()) {
1374                NNTPArticle article = (NNTPArticle)iter.next();
1375                writeLoggedResponse(article.getArticleNumber() + "");
1376            }
1377            writeLoggedFlushedResponse(".");
1378        }
1379    }
1380
1381    /**
1382     * Handles the LIST OVERVIEW.FMT command. Not supported.
1383     */

1384    private void doLISTOVERVIEWFMT() {
1385        // 9.5.3.1.1
1386
writeLoggedFlushedResponse("215 Information follows");
1387        String JavaDoc[] overviewHeaders = theConfigData.getNNTPRepository().getOverviewFormat();
1388        for (int i = 0; i < overviewHeaders.length; i++) {
1389            writeLoggedResponse(overviewHeaders[i]);
1390        }
1391        writeLoggedFlushedResponse(".");
1392    }
1393
1394    /**
1395     * Handles the PAT command. Not supported.
1396     *
1397     * @param argument the argument passed in with the PAT command
1398     */

1399    private void doPAT(String JavaDoc argument) {
1400        // 9.5.3.1.1 in draft-12
1401
writeLoggedFlushedResponse("500 Command not recognized");
1402    }
1403
1404    /**
1405     * Get the values of the headers for the selected newsgroup,
1406     * with an optional range modifier.
1407     *
1408     * @param argument the argument passed in with the XHDR command.
1409     */

1410    private void doXHDR(String JavaDoc argument) {
1411        doHDR(argument);
1412    }
1413
1414    /**
1415     * Get the values of the headers for the selected newsgroup,
1416     * with an optional range modifier.
1417     *
1418     * @param argument the argument passed in with the HDR command.
1419     */

1420    private void doHDR(String JavaDoc argument) {
1421        // 9.5.3
1422
if (argument == null) {
1423            writeLoggedFlushedResponse("501 Syntax error - missing required parameter");
1424            return;
1425        }
1426        String JavaDoc hdr = argument;
1427        String JavaDoc range = null;
1428        int spaceIndex = hdr.indexOf(" ");
1429        if (spaceIndex >= 0 ) {
1430            range = hdr.substring(spaceIndex + 1);
1431            hdr = hdr.substring(0, spaceIndex);
1432        }
1433        if (group == null ) {
1434            writeLoggedFlushedResponse("412 No news group currently selected.");
1435            return;
1436        }
1437        if ((range == null) && (currentArticleNumber < 0)) {
1438            writeLoggedFlushedResponse("420 No current article selected");
1439            return;
1440        }
1441        NNTPArticle[] article = getRange(range);
1442        if ( article == null ) {
1443            writeLoggedFlushedResponse("412 no newsgroup selected");
1444        } else if ( article.length == 0 ) {
1445            writeLoggedFlushedResponse("430 no such article");
1446        } else {
1447            writeLoggedFlushedResponse("221 Header follows");
1448            for ( int i = 0 ; i < article.length ; i++ ) {
1449                String JavaDoc val = article[i].getHeader(hdr);
1450                if ( val == null ) {
1451                    val = "";
1452                }
1453                StringBuffer JavaDoc hdrBuffer =
1454                    new StringBuffer JavaDoc(128)
1455                            .append(article[i].getArticleNumber())
1456                            .append(" ")
1457                            .append(val);
1458                writeLoggedResponse(hdrBuffer.toString());
1459            }
1460            writeLoggedFlushedResponse(".");
1461        }
1462    }
1463
1464    /**
1465     * Returns information from the overview database regarding the
1466     * current article, or a range of articles.
1467     *
1468     * @param range the optional article range.
1469     */

1470    private void doXOVER(String JavaDoc range) {
1471        doOVER(range);
1472    }
1473
1474    /**
1475     * Returns information from the overview database regarding the
1476     * current article, or a range of articles.
1477     *
1478     * @param range the optional article range.
1479     */

1480    private void doOVER(String JavaDoc range) {
1481        // 9.5.2.2.1
1482
if ( group == null ) {
1483            writeLoggedFlushedResponse("412 No newsgroup selected");
1484            return;
1485        }
1486        if ((range == null) && (currentArticleNumber < 0)) {
1487            writeLoggedFlushedResponse("420 No current article selected");
1488            return;
1489        }
1490        NNTPArticle[] article = getRange(range);
1491        if ( article.length == 0 ) {
1492            writeLoggedFlushedResponse("420 No article(s) selected");
1493        } else {
1494            writeLoggedResponse("224 Overview information follows");
1495            for ( int i = 0 ; i < article.length ; i++ ) {
1496                article[i].writeOverview(outs);
1497                if (i % 100 == 0) {
1498                    // Reset the watchdog every hundred headers or so
1499
// to ensure the connection doesn't timeout for slow
1500
// clients
1501
theWatchdog.reset();
1502                }
1503            }
1504            writeLoggedFlushedResponse(".");
1505        }
1506    }
1507
1508    /**
1509     * Handles the transaction for getting the article data.
1510     */

1511    private void createArticle() {
1512        try {
1513            InputStream msgIn = new CharTerminatedInputStream(in, NNTPTerminator);
1514            // Removes the dot stuffing
1515
msgIn = new DotStuffingInputStream(msgIn);
1516            MailHeaders headers = new MailHeaders(msgIn);
1517            processMessageHeaders(headers);
1518            processMessage(headers, msgIn);
1519        } catch (MessagingException JavaDoc me) {
1520            throw new NNTPException("MessagingException encountered when loading article.");
1521        }
1522    }
1523
1524    /**
1525     * Processes the NNTP message headers coming in off the wire.
1526     *
1527     * @param headers the headers of the message being read
1528     */

1529    private MailHeaders processMessageHeaders(MailHeaders headers)
1530        throws MessagingException JavaDoc {
1531        return headers;
1532    }
1533
1534    /**
1535     * Processes the NNTP message coming in off the wire. Reads the
1536     * content and delivers to the spool.
1537     *
1538     * @param headers the headers of the message being read
1539     * @param msgIn the stream containing the message content
1540     */

1541    private void processMessage(MailHeaders headers, InputStream bodyIn)
1542        throws MessagingException JavaDoc {
1543        InputStream messageIn = null;
1544        try {
1545            messageIn = new SequenceInputStream JavaDoc(new ByteArrayInputStream JavaDoc(headers.toByteArray()), bodyIn);
1546            theConfigData.getNNTPRepository().createArticle(messageIn);
1547        } finally {
1548            if (messageIn != null) {
1549                try {
1550                    messageIn.close();
1551                } catch (IOException JavaDoc ioe) {
1552                    // Ignore exception on close.
1553
}
1554                messageIn = null;
1555            }
1556        }
1557    }
1558
1559    /**
1560     * Returns the date from @param input.
1561     * The input tokens are assumed to be in format date time [GMT|UTC] .
1562     * 'date' is in format [XX]YYMMDD. 'time' is in format 'HHMMSS'
1563     * NOTE: This routine could do with some format checks.
1564     *
1565     * @param argument the date string
1566     */

1567    private Date JavaDoc getDateFrom(String JavaDoc argument) {
1568        if (argument == null) {
1569            throw new NNTPException("Date argument was absent.");
1570        }
1571        StringTokenizer JavaDoc tok = new StringTokenizer JavaDoc(argument, " ");
1572        if (tok.countTokens() < 2) {
1573            throw new NNTPException("Date argument was ill-formed.");
1574        }
1575        String JavaDoc date = tok.nextToken();
1576        String JavaDoc time = tok.nextToken();
1577        boolean utc = ( tok.hasMoreTokens() );
1578        Date JavaDoc d = new Date JavaDoc();
1579        try {
1580            StringBuffer JavaDoc dateStringBuffer =
1581                new StringBuffer JavaDoc(64)
1582                    .append(date)
1583                    .append(" ")
1584                    .append(time);
1585            Date JavaDoc dt = DF_RFC977.parse(dateStringBuffer.toString());
1586            if ( utc ) {
1587                dt = new Date JavaDoc(dt.getTime()+UTC_OFFSET);
1588            }
1589            return dt;
1590        } catch ( ParseException JavaDoc pe ) {
1591            StringBuffer JavaDoc exceptionBuffer =
1592                new StringBuffer JavaDoc(128)
1593                    .append("Date extraction failed: ")
1594                    .append(date)
1595                    .append(",")
1596                    .append(time)
1597                    .append(",")
1598                    .append(utc);
1599            throw new NNTPException(exceptionBuffer.toString());
1600        }
1601    }
1602
1603    /**
1604     * Returns the list of articles that match the range.
1605     *
1606     * A precondition of this method is that the selected
1607     * group be non-null. The current article pointer must
1608     * be valid if no range is explicitly provided.
1609     *
1610     * @return null indicates insufficient information to
1611     * fetch the list of articles
1612     */

1613    private NNTPArticle[] getRange(String JavaDoc range) {
1614        // check for msg id
1615
if ( isMessageId(range)) {
1616            NNTPArticle article = theConfigData.getNNTPRepository().getArticleFromID(range);
1617            return ( article == null )
1618                ? new NNTPArticle[0] : new NNTPArticle[] { article };
1619        }
1620
1621        if ( range == null ) {
1622            range = "" + currentArticleNumber;
1623        }
1624
1625        int start = -1;
1626        int end = -1;
1627        int idx = range.indexOf('-');
1628        if ( idx == -1 ) {
1629            start = Integer.parseInt(range);
1630            end = start;
1631        } else {
1632            start = Integer.parseInt(range.substring(0,idx));
1633            if ( (idx + 1) == range.length() ) {
1634                end = group.getLastArticleNumber();
1635            } else {
1636                end = Integer.parseInt(range.substring(idx + 1));
1637            }
1638        }
1639        List JavaDoc list = new ArrayList JavaDoc();
1640        for ( int i = start ; i <= end ; i++ ) {
1641            NNTPArticle article = group.getArticle(i);
1642            if ( article != null ) {
1643                list.add(article);
1644            }
1645        }
1646        return (NNTPArticle[])list.toArray(new NNTPArticle[0]);
1647    }
1648
1649    /**
1650     * Return whether the user associated with the connection (possibly no
1651     * user) is authorized to execute the command.
1652     *
1653     * @param the command being tested
1654     * @return whether the command is authorized
1655     */

1656    private boolean isAuthorized(String JavaDoc command) {
1657        isAlreadyAuthenticated = isAlreadyAuthenticated || isAuthenticated();
1658        if (isAlreadyAuthenticated) {
1659            return true;
1660        }
1661        // some commands are authorized, even if the user is not authenticated
1662
boolean allowed = command.equals("AUTHINFO");
1663        allowed = allowed || command.equals("MODE");
1664        allowed = allowed || command.equals("QUIT");
1665        return allowed;
1666    }
1667
1668    /**
1669     * Return whether the connection has been authenticated.
1670     *
1671     * @return whether the connection has been authenticated.
1672     */

1673    private boolean isAuthenticated() {
1674        if ( theConfigData.isAuthRequired() ) {
1675            if ((user != null) && (password != null) && (theConfigData.getUsersRepository() != null)) {
1676                return theConfigData.getUsersRepository().test(user,password);
1677            } else {
1678                return false;
1679            }
1680        } else {
1681            return true;
1682        }
1683    }
1684
1685    /**
1686     * Tests a string to see whether it is formatted as a message
1687     * ID.
1688     *
1689     * @param testString the string to test
1690     *
1691     * @return whether the string is a candidate message ID
1692     */

1693    private static boolean isMessageId(String JavaDoc testString) {
1694        if ((testString != null) &&
1695            (testString.startsWith("<")) &&
1696            (testString.endsWith(">"))) {
1697            return true;
1698        } else {
1699            return false;
1700        }
1701   }
1702
1703    /**
1704     * This method logs at a "DEBUG" level the response string that
1705     * was sent to the SMTP client. The method is provided largely
1706     * as syntactic sugar to neaten up the code base. It is declared
1707     * private and final to encourage compiler inlining.
1708     *
1709     * @param responseString the response string sent to the client
1710     */

1711    private final void logResponseString(String JavaDoc responseString) {
1712        if (getLogger().isDebugEnabled()) {
1713            getLogger().debug("Sent: " + responseString);
1714        }
1715    }
1716
1717    /**
1718     * Write and flush a response string. The response is also logged.
1719     * Should be used for the last line of a multi-line response or
1720     * for a single line response.
1721     *
1722     * @param responseString the response string sent to the client
1723     */

1724    final void writeLoggedFlushedResponse(String JavaDoc responseString) {
1725        writer.println(responseString);
1726        writer.flush();
1727        logResponseString(responseString);
1728    }
1729
1730    /**
1731     * Write a response string. The response is also logged.
1732     * Used for multi-line responses.
1733     *
1734     * @param responseString the response string sent to the client
1735     */

1736    final void writeLoggedResponse(String JavaDoc responseString) {
1737        writer.println(responseString);
1738        logResponseString(responseString);
1739    }
1740
1741    /**
1742     * A private inner class which serves as an adaptor
1743     * between the WatchdogTarget interface and this
1744     * handler class.
1745     */

1746    private class NNTPWatchdogTarget
1747        implements WatchdogTarget {
1748
1749        /**
1750         * @see org.apache.james.util.watchdog.WatchdogTarget#execute()
1751         */

1752        public void execute() {
1753            NNTPHandler.this.idleClose();
1754        }
1755
1756    }
1757}
1758
Popular Tags