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