1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.james.nntpserver;
23
24 import org.apache.james.core.MailHeaders;
25 import org.apache.james.nntpserver.repository.NNTPArticle;
26 import org.apache.james.nntpserver.repository.NNTPGroup;
27 import org.apache.james.socket.ProtocolHandler;
28 import org.apache.james.socket.ProtocolHandlerHelper;
29 import org.apache.james.util.stream.CharTerminatedInputStream;
30 import org.apache.james.util.stream.DotStuffingInputStream;
31 import org.apache.james.util.stream.ExtraDotOutputStream;
32 import org.apache.mailet.base.RFC2980DateFormat;
33 import org.apache.mailet.base.RFC977DateFormat;
34 import org.apache.mailet.base.SimplifiedDateFormat;
35
36 import javax.mail.MessagingException;
37
38 import java.io.ByteArrayInputStream;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.SequenceInputStream;
42 import java.text.ParseException;
43 import java.util.ArrayList;
44 import java.util.Calendar;
45 import java.util.Date;
46 import java.util.Iterator;
47 import java.util.List;
48 import java.util.Locale;
49 import java.util.StringTokenizer;
50
51
52
53
54
55
56
57
58 public class NNTPHandler implements ProtocolHandler {
59
60 private ProtocolHandlerHelper helper;
61
62
63
64
65 private static final SimplifiedDateFormat DF_RFC977 = new RFC977DateFormat();
66
67
68
69
70 private static final SimplifiedDateFormat DF_RFC2980 = new RFC2980DateFormat();
71
72
73
74
75 public static final long UTC_OFFSET = Calendar.getInstance().get(Calendar.ZONE_OFFSET);
76
77
78
79
80 private final static String COMMAND_MODE = "MODE";
81
82
83
84
85 private final static String COMMAND_LIST = "LIST";
86
87
88
89
90 private final static String COMMAND_GROUP = "GROUP";
91
92
93
94
95 private final static String COMMAND_NEXT = "NEXT";
96
97
98
99
100 private final static String COMMAND_LAST = "LAST";
101
102
103
104
105 private final static String COMMAND_ARTICLE = "ARTICLE";
106
107
108
109
110 private final static String COMMAND_HEAD = "HEAD";
111
112
113
114
115 private final static String COMMAND_BODY = "BODY";
116
117
118
119
120 private final static String COMMAND_STAT = "STAT";
121
122
123
124
125 private final static String COMMAND_POST = "POST";
126
127
128
129
130 private final static String COMMAND_IHAVE = "IHAVE";
131
132
133
134
135 private final static String COMMAND_QUIT = "QUIT";
136
137
138
139
140 private final static String COMMAND_SLAVE = "SLAVE";
141
142
143
144
145 private final static String COMMAND_DATE = "DATE";
146
147
148
149
150 private final static String COMMAND_HELP = "HELP";
151
152
153
154
155 private final static String COMMAND_NEWGROUPS = "NEWGROUPS";
156
157
158
159
160 private final static String COMMAND_NEWNEWS = "NEWNEWS";
161
162
163
164
165 private final static String COMMAND_LISTGROUP = "LISTGROUP";
166
167
168
169
170 private final static String COMMAND_OVER = "OVER";
171
172
173
174
175 private final static String COMMAND_XOVER = "XOVER";
176
177
178
179
180 private final static String COMMAND_HDR = "HDR";
181
182
183
184
185 private final static String COMMAND_XHDR = "XHDR";
186
187
188
189
190 private final static String COMMAND_AUTHINFO = "AUTHINFO";
191
192
193
194
195 private final static String COMMAND_PAT = "PAT";
196
197
198
199
200 private final static String MODE_TYPE_READER = "READER";
201
202
203
204
205 private final static String MODE_TYPE_STREAM = "STREAM";
206
207
208
209
210 private final static String AUTHINFO_PARAM_USER = "USER";
211
212
213
214
215 private final static String AUTHINFO_PARAM_PASS = "PASS";
216
217
218
219
220 private final static char[] NNTPTerminator = { '\r', '\n', '.', '\r', '\n' };
221
222
223
224
225 private NNTPGroup group;
226
227
228
229
230 private int currentArticleNumber = -1;
231
232
233
234
235
236 private NNTPHandlerConfigurationData theConfigData;
237
238
239
240
241 private String user = null;
242
243
244
245
246 private String password = null;
247
248
249
250
251
252 boolean isAlreadyAuthenticated = false;
253
254
255
256
257 public void setConfigurationData(Object theData) {
258 if (theData instanceof NNTPHandlerConfigurationData) {
259 theConfigData = (NNTPHandlerConfigurationData) theData;
260 } else {
261 throw new IllegalArgumentException("Configuration object does not implement NNTPHandlerConfigurationData");
262 }
263 }
264
265
266
267
268 public void handleProtocol() throws IOException {
269
270 if ( theConfigData.getNNTPRepository().isReadOnly() ) {
271 StringBuffer respBuffer =
272 new StringBuffer(128)
273 .append("201 ")
274 .append(theConfigData.getHelloName())
275 .append(" NNTP Service Ready, posting prohibited");
276 helper.writeLoggedFlushedResponse(respBuffer.toString());
277 } else {
278 StringBuffer respBuffer =
279 new StringBuffer(128)
280 .append("200 ")
281 .append(theConfigData.getHelloName())
282 .append(" NNTP Service Ready, posting permitted");
283 helper.writeLoggedFlushedResponse(respBuffer.toString());
284 }
285
286 helper.getWatchdog().start();
287 while (parseCommand(helper.getInputReader().readLine())) {
288 helper.getWatchdog().reset();
289 }
290 helper.getWatchdog().stop();
291
292 helper.getAvalonLogger().info("Connection closed");
293 }
294
295
296
297
298 public void errorHandler(RuntimeException e) {
299 helper.defaultErrorHandler(e);
300
301
302
303
304 if (helper.getSocket() != null) {
305 try {
306 doQUIT(null);
307 } catch (Throwable t) {}
308 }
309 }
310
311
312
313
314 public void resetHandler() {
315
316 group = null;
317 currentArticleNumber = -1;
318
319
320 user = null;
321 password = null;
322 isAlreadyAuthenticated = false;
323
324
325 theConfigData = null;
326 }
327
328
329
330
331
332
333
334
335
336
337
338
339
340 private boolean parseCommand(String commandRaw) {
341 if (commandRaw == null) {
342 return false;
343 }
344 if (helper.getAvalonLogger().isDebugEnabled()) {
345 helper.getAvalonLogger().debug("Command received: " + commandRaw);
346 }
347
348 String command = commandRaw.trim();
349 String argument = null;
350 int spaceIndex = command.indexOf(" ");
351 if (spaceIndex >= 0) {
352 argument = command.substring(spaceIndex + 1);
353 command = command.substring(0, spaceIndex);
354 }
355 command = command.toUpperCase(Locale.US);
356
357 boolean returnValue = true;
358 if (!isAuthorized(command) ) {
359 helper.writeLoggedFlushedResponse("480 User is not authenticated");
360 helper.getAvalonLogger().debug("Command not allowed.");
361 return returnValue;
362 }
363 if ((command.equals(COMMAND_MODE)) && (argument != null)) {
364 if (argument.toUpperCase(Locale.US).equals(MODE_TYPE_READER)) {
365 doMODEREADER(argument);
366 } else if (argument.toUpperCase(Locale.US).equals(MODE_TYPE_STREAM)) {
367 doMODESTREAM(argument);
368 } else {
369 helper.writeLoggedFlushedResponse("500 Command not understood");
370 }
371 } else if ( command.equals(COMMAND_LIST)) {
372 doLIST(argument);
373 } else if ( command.equals(COMMAND_GROUP) ) {
374 doGROUP(argument);
375 } else if ( command.equals(COMMAND_NEXT) ) {
376 doNEXT(argument);
377 } else if ( command.equals(COMMAND_LAST) ) {
378 doLAST(argument);
379 } else if ( command.equals(COMMAND_ARTICLE) ) {
380 doARTICLE(argument);
381 } else if ( command.equals(COMMAND_HEAD) ) {
382 doHEAD(argument);
383 } else if ( command.equals(COMMAND_BODY) ) {
384 doBODY(argument);
385 } else if ( command.equals(COMMAND_STAT) ) {
386 doSTAT(argument);
387 } else if ( command.equals(COMMAND_POST) ) {
388 doPOST(argument);
389 } else if ( command.equals(COMMAND_IHAVE) ) {
390 doIHAVE(argument);
391 } else if ( command.equals(COMMAND_QUIT) ) {
392 doQUIT(argument);
393 returnValue = false;
394 } else if ( command.equals(COMMAND_DATE) ) {
395 doDATE(argument);
396 } else if ( command.equals(COMMAND_HELP) ) {
397 doHELP(argument);
398 } else if ( command.equals(COMMAND_NEWGROUPS) ) {
399 doNEWGROUPS(argument);
400 } else if ( command.equals(COMMAND_NEWNEWS) ) {
401 doNEWNEWS(argument);
402 } else if ( command.equals(COMMAND_LISTGROUP) ) {
403 doLISTGROUP(argument);
404 } else if ( command.equals(COMMAND_OVER) ) {
405 doOVER(argument);
406 } else if ( command.equals(COMMAND_XOVER) ) {
407 doXOVER(argument);
408 } else if ( command.equals(COMMAND_HDR) ) {
409 doHDR(argument);
410 } else if ( command.equals(COMMAND_XHDR) ) {
411 doXHDR(argument);
412 } else if ( command.equals(COMMAND_AUTHINFO) ) {
413 doAUTHINFO(argument);
414 } else if ( command.equals(COMMAND_SLAVE) ) {
415 doSLAVE(argument);
416 } else if ( command.equals(COMMAND_PAT) ) {
417 doPAT(argument);
418 } else {
419 doUnknownCommand(command, argument);
420 }
421 return returnValue;
422 }
423
424
425
426
427
428
429
430 private void doUnknownCommand(String command, String argument) {
431 if (helper.getAvalonLogger().isDebugEnabled()) {
432 StringBuffer logBuffer =
433 new StringBuffer(128)
434 .append("Received unknown command ")
435 .append(command)
436 .append(" with argument ")
437 .append(argument);
438 helper.getAvalonLogger().debug(logBuffer.toString());
439 }
440 helper.writeLoggedFlushedResponse("500 Unknown command");
441 }
442
443
444
445
446
447
448
449
450 private void doAUTHINFO(String argument) {
451 String command = null;
452 String value = null;
453 if (argument != null) {
454 int spaceIndex = argument.indexOf(" ");
455 if (spaceIndex >= 0) {
456 command = argument.substring(0, spaceIndex);
457 value = argument.substring(spaceIndex + 1);
458 }
459 }
460 if (command == null) {
461 helper.writeLoggedFlushedResponse("501 Syntax error");
462 return;
463 }
464 command = command.toUpperCase(Locale.US);
465 if ( command.equals(AUTHINFO_PARAM_USER) ) {
466
467 if ( isAlreadyAuthenticated ) {
468 helper.writeLoggedFlushedResponse("482 Already authenticated - rejecting new credentials");
469 }
470
471 if (user != null) {
472 user = null;
473 password = null;
474 isAlreadyAuthenticated = false;
475 helper.writeLoggedFlushedResponse("482 User already specified - rejecting new user");
476 return;
477 }
478 user = value;
479 helper.writeLoggedFlushedResponse("381 More authentication information required");
480 } else if ( command.equals(AUTHINFO_PARAM_PASS) ) {
481
482 if (user == null) {
483 helper.writeLoggedFlushedResponse("482 User not yet specified. Rejecting user.");
484 return;
485 }
486
487 if (password != null) {
488 user = null;
489 password = null;
490 isAlreadyAuthenticated = false;
491 helper.writeLoggedFlushedResponse("482 Password already specified - rejecting new password");
492 return;
493 }
494 password = value;
495 isAlreadyAuthenticated = isAuthenticated();
496 if ( isAlreadyAuthenticated ) {
497 helper.writeLoggedFlushedResponse("281 Authentication accepted");
498 } else {
499 helper.writeLoggedFlushedResponse("482 Authentication rejected");
500
501 user = null;
502 password = null;
503 }
504 } else {
505 helper.writeLoggedFlushedResponse("501 Syntax error");
506 return;
507 }
508 }
509
510
511
512
513
514
515
516
517 private void doNEWNEWS(String argument) {
518
519
520 String wildmat = "*";
521
522 if (argument != null) {
523 int spaceIndex = argument.indexOf(" ");
524 if (spaceIndex >= 0) {
525 wildmat = argument.substring(0, spaceIndex);
526 argument = argument.substring(spaceIndex + 1);
527 } else {
528 helper.getAvalonLogger().error("NEWNEWS had an invalid argument");
529 helper.writeLoggedFlushedResponse("501 Syntax error");
530 return;
531 }
532 } else {
533 helper.getAvalonLogger().error("NEWNEWS had a null argument");
534 helper.writeLoggedFlushedResponse("501 Syntax error");
535 return;
536 }
537
538 Date theDate = null;
539 try {
540 theDate = getDateFrom(argument);
541 } catch (NNTPException nntpe) {
542 helper.getAvalonLogger().error("NEWNEWS had an invalid argument", nntpe);
543 helper.writeLoggedFlushedResponse("501 Syntax error");
544 return;
545 }
546
547 helper.writeLoggedFlushedResponse("230 list of new articles by message-id follows");
548 Iterator groupIter = theConfigData.getNNTPRepository().getMatchedGroups(wildmat);
549 while ( groupIter.hasNext() ) {
550 Iterator articleIter = ((NNTPGroup)(groupIter.next())).getArticlesSince(theDate);
551 while (articleIter.hasNext()) {
552 StringBuffer iterBuffer =
553 new StringBuffer(64)
554 .append(((NNTPArticle)articleIter.next()).getUniqueID());
555 helper.writeLoggedResponse(iterBuffer.toString());
556 }
557 }
558 helper.writeLoggedFlushedResponse(".");
559 }
560
561
562
563
564
565
566
567
568 private void doNEWGROUPS(String argument) {
569
570
571
572
573
574
575
576
577
578 Date theDate = null;
579 try {
580 theDate = getDateFrom(argument);
581 } catch (NNTPException nntpe) {
582 helper.getAvalonLogger().error("NEWGROUPS had an invalid argument", nntpe);
583 helper.writeLoggedFlushedResponse("501 Syntax error");
584 return;
585 }
586 Iterator iter = theConfigData.getNNTPRepository().getGroupsSince(theDate);
587 helper.writeLoggedFlushedResponse("231 list of new newsgroups follows");
588 while ( iter.hasNext() ) {
589 NNTPGroup currentGroup = (NNTPGroup)iter.next();
590 StringBuffer iterBuffer =
591 new StringBuffer(128)
592 .append(currentGroup.getName())
593 .append(" ")
594 .append(currentGroup.getLastArticleNumber())
595 .append(" ")
596 .append(currentGroup.getFirstArticleNumber())
597 .append(" ")
598 .append((currentGroup.isPostAllowed()?"y":"n"));
599 helper.writeLoggedResponse(iterBuffer.toString());
600 }
601 helper.writeLoggedFlushedResponse(".");
602 }
603
604
605
606
607
608
609 private void doHELP(String argument) {
610 helper.writeLoggedResponse("100 Help text follows");
611 helper.writeLoggedFlushedResponse(".");
612 }
613
614
615
616
617
618
619
620 private void doSLAVE(String argument) {
621 helper.writeLoggedFlushedResponse("202 slave status noted");
622 }
623
624
625
626
627
628
629 private void doDATE(String argument) {
630 Date dt = new Date(System.currentTimeMillis()-UTC_OFFSET);
631 String dtStr = DF_RFC2980.format(new Date(dt.getTime() - UTC_OFFSET));
632 helper.writeLoggedFlushedResponse("111 " + dtStr);
633 }
634
635
636
637
638
639
640 private void doQUIT(String argument) {
641 helper.writeLoggedFlushedResponse("205 closing connection");
642 }
643
644
645
646
647
648
649 private void doLIST(String argument) {
650
651 String wildmat = "*";
652 boolean isListNewsgroups = false;
653
654 String extension = argument;
655 if (argument != null) {
656 int spaceIndex = argument.indexOf(" ");
657 if (spaceIndex >= 0) {
658 wildmat = argument.substring(spaceIndex + 1);
659 extension = argument.substring(0, spaceIndex);
660 }
661 extension = extension.toUpperCase(Locale.US);
662 }
663
664 if (extension != null) {
665 if (extension.equals("ACTIVE")) {
666 isListNewsgroups = false;
667 } else if (extension.equals("NEWSGROUPS") ) {
668 isListNewsgroups = true;
669 } else if (extension.equals("EXTENSIONS") ) {
670 doLISTEXTENSIONS();
671 return;
672 } else if (extension.equals("OVERVIEW.FMT") ) {
673 doLISTOVERVIEWFMT();
674 return;
675 } else if (extension.equals("ACTIVE.TIMES") ) {
676
677 helper.writeLoggedFlushedResponse("503 program error, function not performed");
678 return;
679 } else if (extension.equals("DISTRIBUTIONS") ) {
680
681 helper.writeLoggedFlushedResponse("503 program error, function not performed");
682 return;
683 } else if (extension.equals("DISTRIB.PATS") ) {
684
685 helper.writeLoggedFlushedResponse("503 program error, function not performed");
686 return;
687 } else {
688 helper.writeLoggedFlushedResponse("501 Syntax error");
689 return;
690 }
691 }
692
693 Iterator iter = theConfigData.getNNTPRepository().getMatchedGroups(wildmat);
694 helper.writeLoggedFlushedResponse("215 list of newsgroups follows");
695 while ( iter.hasNext() ) {
696 NNTPGroup theGroup = (NNTPGroup)iter.next();
697 if (isListNewsgroups) {
698 helper.writeLoggedResponse(theGroup.getListNewsgroupsFormat());
699 } else {
700 helper.writeLoggedResponse(theGroup.getListFormat());
701 }
702 }
703 helper.writeLoggedFlushedResponse(".");
704 }
705
706
707
708
709
710
711
712 private void doIHAVE(String id) {
713
714 if (!isMessageId(id)) {
715 helper.writeLoggedFlushedResponse("501 command syntax error");
716 return;
717 }
718 NNTPArticle article = theConfigData.getNNTPRepository().getArticleFromID(id);
719 if ( article != null ) {
720 helper.writeLoggedFlushedResponse("435 article not wanted - do not send it");
721 } else {
722 helper.writeLoggedFlushedResponse("335 send article to be transferred. End with <CR-LF>.<CR-LF>");
723 try {
724 createArticle();
725 } catch (RuntimeException e) {
726 helper.writeLoggedFlushedResponse("436 transfer failed - try again later");
727 throw e;
728 }
729 helper.writeLoggedFlushedResponse("235 article received ok");
730 }
731 }
732
733
734
735
736
737
738 private void doPOST(String argument) {
739
740 if ( argument != null ) {
741 helper.writeLoggedFlushedResponse("501 Syntax error - unexpected parameter");
742 }
743 helper.writeLoggedFlushedResponse("340 send article to be posted. End with <CR-LF>.<CR-LF>");
744 createArticle();
745 helper.writeLoggedFlushedResponse("240 article received ok");
746 }
747
748
749
750
751
752
753
754
755
756
757 private void doSTAT(String param) {
758
759 NNTPArticle article = null;
760 if (isMessageId(param)) {
761 article = theConfigData.getNNTPRepository().getArticleFromID(param);
762 if ( article == null ) {
763 helper.writeLoggedFlushedResponse("430 no such article");
764 return;
765 } else {
766 StringBuffer respBuffer =
767 new StringBuffer(64)
768 .append("223 0 ")
769 .append(param);
770 helper.writeLoggedFlushedResponse(respBuffer.toString());
771 }
772 } else {
773 int newArticleNumber = currentArticleNumber;
774 if ( group == null ) {
775 helper.writeLoggedFlushedResponse("412 no newsgroup selected");
776 return;
777 } else {
778 if ( param == null ) {
779 if ( currentArticleNumber < 0 ) {
780 helper.writeLoggedFlushedResponse("420 no current article selected");
781 return;
782 } else {
783 article = group.getArticle(currentArticleNumber);
784 }
785 }
786 else {
787 newArticleNumber = Integer.parseInt(param);
788 article = group.getArticle(newArticleNumber);
789 }
790 if ( article == null ) {
791 helper.writeLoggedFlushedResponse("423 no such article number in this group");
792 return;
793 } else {
794 currentArticleNumber = newArticleNumber;
795 String articleID = article.getUniqueID();
796 if (articleID == null) {
797 articleID = "<0>";
798 }
799 StringBuffer respBuffer =
800 new StringBuffer(128)
801 .append("223 ")
802 .append(article.getArticleNumber())
803 .append(" ")
804 .append(articleID);
805 helper.writeLoggedFlushedResponse(respBuffer.toString());
806 }
807 }
808 }
809 }
810
811
812
813
814
815
816
817
818
819
820 private void doBODY(String param) {
821
822 NNTPArticle article = null;
823 if (isMessageId(param)) {
824 article = theConfigData.getNNTPRepository().getArticleFromID(param);
825 if ( article == null ) {
826 helper.writeLoggedFlushedResponse("430 no such article");
827 return;
828 } else {
829 StringBuffer respBuffer =
830 new StringBuffer(64)
831 .append("222 0 ")
832 .append(param);
833 helper.writeLoggedFlushedResponse(respBuffer.toString());
834 }
835 } else {
836 int newArticleNumber = currentArticleNumber;
837 if ( group == null ) {
838 helper.writeLoggedFlushedResponse("412 no newsgroup selected");
839 return;
840 } else {
841 if ( param == null ) {
842 if ( currentArticleNumber < 0 ) {
843 helper.writeLoggedFlushedResponse("420 no current article selected");
844 return;
845 } else {
846 article = group.getArticle(currentArticleNumber);
847 }
848 }
849 else {
850 newArticleNumber = Integer.parseInt(param);
851 article = group.getArticle(newArticleNumber);
852 }
853 if ( article == null ) {
854 helper.writeLoggedFlushedResponse("423 no such article number in this group");
855 return;
856 } else {
857 currentArticleNumber = newArticleNumber;
858 String articleID = article.getUniqueID();
859 if (articleID == null) {
860 articleID = "<0>";
861 }
862 StringBuffer respBuffer =
863 new StringBuffer(128)
864 .append("222 ")
865 .append(article.getArticleNumber())
866 .append(" ")
867 .append(articleID);
868 helper.writeLoggedFlushedResponse(respBuffer.toString());
869 }
870 }
871 }
872 if (article != null) {
873 helper.getOutputWriter().flush();
874 article.writeBody(new ExtraDotOutputStream(helper.getOutputStream()));
875 helper.writeLoggedFlushedResponse("\r\n.");
876 }
877 }
878
879
880
881
882
883
884
885
886
887
888 private void doHEAD(String param) {
889
890 NNTPArticle article = null;
891 if (isMessageId(param)) {
892 article = theConfigData.getNNTPRepository().getArticleFromID(param);
893 if ( article == null ) {
894 helper.writeLoggedFlushedResponse("430 no such article");
895 return;
896 } else {
897 StringBuffer respBuffer =
898 new StringBuffer(64)
899 .append("221 0 ")
900 .append(param);
901 helper.writeLoggedFlushedResponse(respBuffer.toString());
902 }
903 } else {
904 int newArticleNumber = currentArticleNumber;
905 if ( group == null ) {
906 helper.writeLoggedFlushedResponse("412 no newsgroup selected");
907 return;
908 } else {
909 if ( param == null ) {
910 if ( currentArticleNumber < 0 ) {
911 helper.writeLoggedFlushedResponse("420 no current article selected");
912 return;
913 } else {
914 article = group.getArticle(currentArticleNumber);
915 }
916 }
917 else {
918 newArticleNumber = Integer.parseInt(param);
919 article = group.getArticle(newArticleNumber);
920 }
921 if ( article == null ) {
922 helper.writeLoggedFlushedResponse("423 no such article number in this group");
923 return;
924 } else {
925 currentArticleNumber = newArticleNumber;
926 String articleID = article.getUniqueID();
927 if (articleID == null) {
928 articleID = "<0>";
929 }
930 StringBuffer respBuffer =
931 new StringBuffer(128)
932 .append("221 ")
933 .append(article.getArticleNumber())
934 .append(" ")
935 .append(articleID);
936 helper.writeLoggedFlushedResponse(respBuffer.toString());
937 }
938 }
939 }
940 if (article != null) {
941 helper.getOutputWriter().flush();
942 article.writeHead(new ExtraDotOutputStream(helper.getOutputStream()));
943 helper.writeLoggedFlushedResponse(".");
944 }
945 }
946
947
948
949
950
951
952
953
954
955
956 private void doARTICLE(String param) {
957
958 NNTPArticle article = null;
959 if (isMessageId(param)) {
960 article = theConfigData.getNNTPRepository().getArticleFromID(param);
961 if ( article == null ) {
962 helper.writeLoggedFlushedResponse("430 no such article");
963 return;
964 } else {
965 StringBuffer respBuffer =
966 new StringBuffer(64)
967 .append("220 0 ")
968 .append(param);
969 helper.writeLoggedResponse(respBuffer.toString());
970 }
971 } else {
972 int newArticleNumber = currentArticleNumber;
973 if ( group == null ) {
974 helper.writeLoggedFlushedResponse("412 no newsgroup selected");
975 return;
976 } else {
977 if ( param == null ) {
978 if ( currentArticleNumber < 0 ) {
979 helper.writeLoggedFlushedResponse("420 no current article selected");
980 return;
981 } else {
982 article = group.getArticle(currentArticleNumber);
983 }
984 }
985 else {
986 newArticleNumber = Integer.parseInt(param);
987 article = group.getArticle(newArticleNumber);
988 }
989 if ( article == null ) {
990 helper.writeLoggedFlushedResponse("423 no such article number in this group");
991 return;
992 } else {
993 currentArticleNumber = newArticleNumber;
994 String articleID = article.getUniqueID();
995 if (articleID == null) {
996 articleID = "<0>";
997 }
998 StringBuffer respBuffer =
999 new StringBuffer(128)
1000 .append("220 ")
1001 .append(article.getArticleNumber())
1002 .append(" ")
1003 .append(articleID);
1004 helper.writeLoggedFlushedResponse(respBuffer.toString());
1005 }
1006 }
1007 }
1008 if (article != null) {
1009 helper.getOutputWriter().flush();
1010 article.writeArticle(new ExtraDotOutputStream(helper.getOutputStream()));
1011
1012 helper.writeLoggedFlushedResponse("\r\n.");
1013 }
1014 }
1015
1016
1017
1018
1019
1020
1021 private void doNEXT(String argument) {
1022
1023 if ( argument != null ) {
1024 helper.writeLoggedFlushedResponse("501 Syntax error - unexpected parameter");
1025 } else if ( group == null ) {
1026 helper.writeLoggedFlushedResponse("412 no newsgroup selected");
1027 } else if ( currentArticleNumber < 0 ) {
1028 helper.writeLoggedFlushedResponse("420 no current article has been selected");
1029 } else if ( currentArticleNumber >= group.getLastArticleNumber() ) {
1030 helper.writeLoggedFlushedResponse("421 no next article in this group");
1031 } else {
1032 currentArticleNumber++;
1033 NNTPArticle article = group.getArticle(currentArticleNumber);
1034 StringBuffer respBuffer =
1035 new StringBuffer(64)
1036 .append("223 ")
1037 .append(article.getArticleNumber())
1038 .append(" ")
1039 .append(article.getUniqueID());
1040 helper.writeLoggedFlushedResponse(respBuffer.toString());
1041 }
1042 }
1043
1044
1045
1046
1047
1048
1049
1050 private void doLAST(String argument) {
1051
1052 if ( argument != null ) {
1053 helper.writeLoggedFlushedResponse("501 Syntax error - unexpected parameter");
1054 } else if ( group == null ) {
1055 helper.writeLoggedFlushedResponse("412 no newsgroup selected");
1056 } else if ( currentArticleNumber < 0 ) {
1057 helper.writeLoggedFlushedResponse("420 no current article has been selected");
1058 } else if ( currentArticleNumber <= group.getFirstArticleNumber() ) {
1059 helper.writeLoggedFlushedResponse("422 no previous article in this group");
1060 } else {
1061 currentArticleNumber--;
1062 NNTPArticle article = group.getArticle(currentArticleNumber);
1063 StringBuffer respBuffer =
1064 new StringBuffer(64)
1065 .append("223 ")
1066 .append(article.getArticleNumber())
1067 .append(" ")
1068 .append(article.getUniqueID());
1069 helper.writeLoggedFlushedResponse(respBuffer.toString());
1070 }
1071 }
1072
1073
1074
1075
1076
1077
1078 private void doGROUP(String groupName) {
1079 if (groupName == null) {
1080 helper.writeLoggedFlushedResponse("501 Syntax error - missing required parameter");
1081 return;
1082 }
1083 NNTPGroup newGroup = theConfigData.getNNTPRepository().getGroup(groupName);
1084
1085 if ( newGroup == null ) {
1086 helper.writeLoggedFlushedResponse("411 no such newsgroup");
1087 } else {
1088 group = newGroup;
1089
1090
1091
1092
1093
1094 int articleCount = group.getNumberOfArticles();
1095 int lowWaterMark = group.getFirstArticleNumber();
1096 int highWaterMark = group.getLastArticleNumber();
1097
1098
1099
1100
1101 if (articleCount != 0) {
1102 currentArticleNumber = lowWaterMark;
1103 } else {
1104 currentArticleNumber = -1;
1105 }
1106 StringBuffer respBuffer =
1107 new StringBuffer(128)
1108 .append("211 ")
1109 .append(articleCount)
1110 .append(" ")
1111 .append(lowWaterMark)
1112 .append(" ")
1113 .append(highWaterMark)
1114 .append(" ")
1115 .append(group.getName())
1116 .append(" group selected");
1117 helper.writeLoggedFlushedResponse(respBuffer.toString());
1118 }
1119 }
1120
1121
1122
1123
1124 private void doLISTEXTENSIONS() {
1125
1126 helper.writeLoggedResponse("202 Extensions supported:");
1127 helper.writeLoggedResponse("LISTGROUP");
1128 helper.writeLoggedResponse("AUTHINFO");
1129 helper.writeLoggedResponse("OVER");
1130 helper.writeLoggedResponse("XOVER");
1131 helper.writeLoggedResponse("HDR");
1132 helper.writeLoggedResponse("XHDR");
1133 helper.writeLoggedFlushedResponse(".");
1134 }
1135
1136
1137
1138
1139
1140
1141 private void doMODEREADER(String argument) {
1142
1143 helper.writeLoggedFlushedResponse(theConfigData.getNNTPRepository().isReadOnly()
1144 ? "201 Posting Not Permitted" : "200 Posting Permitted");
1145 }
1146
1147
1148
1149
1150
1151
1152 private void doMODESTREAM(String argument) {
1153
1154 helper.writeLoggedFlushedResponse("500 Command not understood");
1155 }
1156
1157
1158
1159
1160
1161
1162
1163 private void doLISTGROUP(String groupName) {
1164
1165 if (groupName==null) {
1166 if ( group == null) {
1167 helper.writeLoggedFlushedResponse("412 no news group currently selected");
1168 return;
1169 }
1170 }
1171 else {
1172 group = theConfigData.getNNTPRepository().getGroup(groupName);
1173 if ( group == null ) {
1174 helper.writeLoggedFlushedResponse("411 no such newsgroup");
1175 return;
1176 }
1177 }
1178 if ( group != null ) {
1179
1180
1181
1182
1183
1184 if (group.getNumberOfArticles() > 0) {
1185 currentArticleNumber = group.getFirstArticleNumber();
1186 } else {
1187 currentArticleNumber = -1;
1188 }
1189
1190 helper.writeLoggedFlushedResponse("211 list of article numbers follow");
1191
1192 Iterator iter = group.getArticles();
1193 while (iter.hasNext()) {
1194 NNTPArticle article = (NNTPArticle)iter.next();
1195 helper.writeLoggedResponse(article.getArticleNumber() + "");
1196 }
1197 helper.writeLoggedFlushedResponse(".");
1198 }
1199 }
1200
1201
1202
1203
1204 private void doLISTOVERVIEWFMT() {
1205
1206 helper.writeLoggedFlushedResponse("215 Information follows");
1207 String[] overviewHeaders = theConfigData.getNNTPRepository().getOverviewFormat();
1208 for (int i = 0; i < overviewHeaders.length; i++) {
1209 helper.writeLoggedResponse(overviewHeaders[i]);
1210 }
1211 helper.writeLoggedFlushedResponse(".");
1212 }
1213
1214
1215
1216
1217
1218
1219 private void doPAT(String argument) {
1220
1221 helper.writeLoggedFlushedResponse("500 Command not recognized");
1222 }
1223
1224
1225
1226
1227
1228
1229
1230 private void doXHDR(String argument) {
1231 doHDR(argument);
1232 }
1233
1234
1235
1236
1237
1238
1239
1240 private void doHDR(String argument) {
1241
1242 if (argument == null) {
1243 helper.writeLoggedFlushedResponse("501 Syntax error - missing required parameter");
1244 return;
1245 }
1246 String hdr = argument;
1247 String range = null;
1248 int spaceIndex = hdr.indexOf(" ");
1249 if (spaceIndex >= 0 ) {
1250 range = hdr.substring(spaceIndex + 1);
1251 hdr = hdr.substring(0, spaceIndex);
1252 }
1253 if (group == null ) {
1254 helper.writeLoggedFlushedResponse("412 No news group currently selected.");
1255 return;
1256 }
1257 if ((range == null) && (currentArticleNumber < 0)) {
1258 helper.writeLoggedFlushedResponse("420 No current article selected");
1259 return;
1260 }
1261 NNTPArticle[] article = getRange(range);
1262 if ( article == null ) {
1263 helper.writeLoggedFlushedResponse("412 no newsgroup selected");
1264 } else if ( article.length == 0 ) {
1265 helper.writeLoggedFlushedResponse("430 no such article");
1266 } else {
1267 helper.writeLoggedFlushedResponse("221 Header follows");
1268 for ( int i = 0 ; i < article.length ; i++ ) {
1269 String val = article[i].getHeader(hdr);
1270 if ( val == null ) {
1271 val = "";
1272 }
1273 StringBuffer hdrBuffer =
1274 new StringBuffer(128)
1275 .append(article[i].getArticleNumber())
1276 .append(" ")
1277 .append(val);
1278 helper.writeLoggedResponse(hdrBuffer.toString());
1279 }
1280 helper.writeLoggedFlushedResponse(".");
1281 }
1282 }
1283
1284
1285
1286
1287
1288
1289
1290 private void doXOVER(String range) {
1291 doOVER(range);
1292 }
1293
1294
1295
1296
1297
1298
1299
1300 private void doOVER(String range) {
1301
1302 if ( group == null ) {
1303 helper.writeLoggedFlushedResponse("412 No newsgroup selected");
1304 return;
1305 }
1306 if ((range == null) && (currentArticleNumber < 0)) {
1307 helper.writeLoggedFlushedResponse("420 No current article selected");
1308 return;
1309 }
1310 NNTPArticle[] article = getRange(range);
1311 if ( article.length == 0 ) {
1312 helper.writeLoggedFlushedResponse("420 No article(s) selected");
1313 } else {
1314 helper.writeLoggedResponse("224 Overview information follows");
1315 for ( int i = 0 ; i < article.length ; i++ ) {
1316 article[i].writeOverview(helper.getOutputStream());
1317 if (i % 100 == 0) {
1318
1319
1320
1321 helper.getWatchdog().reset();
1322 }
1323 }
1324 helper.writeLoggedFlushedResponse(".");
1325 }
1326 }
1327
1328
1329
1330
1331 private void createArticle() {
1332 try {
1333 InputStream msgIn = new CharTerminatedInputStream(helper.getInputStream(), NNTPTerminator);
1334
1335 msgIn = new DotStuffingInputStream(msgIn);
1336 MailHeaders headers = new MailHeaders(msgIn);
1337 processMessageHeaders(headers);
1338 processMessage(headers, msgIn);
1339 } catch (MessagingException me) {
1340 throw new NNTPException("MessagingException encountered when loading article.");
1341 }
1342 }
1343
1344
1345
1346
1347
1348
1349 private MailHeaders processMessageHeaders(MailHeaders headers)
1350 throws MessagingException {
1351 return headers;
1352 }
1353
1354
1355
1356
1357
1358
1359
1360
1361 private void processMessage(MailHeaders headers, InputStream bodyIn)
1362 throws MessagingException {
1363 InputStream messageIn = null;
1364 try {
1365 messageIn = new SequenceInputStream(new ByteArrayInputStream(headers.toByteArray()), bodyIn);
1366 theConfigData.getNNTPRepository().createArticle(messageIn);
1367 } finally {
1368 if (messageIn != null) {
1369 try {
1370 messageIn.close();
1371 } catch (IOException ioe) {
1372
1373 }
1374 messageIn = null;
1375 }
1376 }
1377 }
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387 private Date getDateFrom(String argument) {
1388 if (argument == null) {
1389 throw new NNTPException("Date argument was absent.");
1390 }
1391 StringTokenizer tok = new StringTokenizer(argument, " ");
1392 if (tok.countTokens() < 2) {
1393 throw new NNTPException("Date argument was ill-formed.");
1394 }
1395 String date = tok.nextToken();
1396 String time = tok.nextToken();
1397 boolean utc = ( tok.hasMoreTokens() );
1398 try {
1399 StringBuffer dateStringBuffer =
1400 new StringBuffer(64)
1401 .append(date)
1402 .append(" ")
1403 .append(time);
1404 Date dt = DF_RFC977.parse(dateStringBuffer.toString());
1405 if ( utc ) {
1406 dt = new Date(dt.getTime()+UTC_OFFSET);
1407 }
1408 return dt;
1409 } catch ( ParseException pe ) {
1410 StringBuffer exceptionBuffer =
1411 new StringBuffer(128)
1412 .append("Date extraction failed: ")
1413 .append(date)
1414 .append(",")
1415 .append(time)
1416 .append(",")
1417 .append(utc);
1418 throw new NNTPException(exceptionBuffer.toString());
1419 }
1420 }
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432 private NNTPArticle[] getRange(String range) {
1433
1434 if ( isMessageId(range)) {
1435 NNTPArticle article = theConfigData.getNNTPRepository().getArticleFromID(range);
1436 return ( article == null )
1437 ? new NNTPArticle[0] : new NNTPArticle[] { article };
1438 }
1439
1440 if ( range == null ) {
1441 range = "" + currentArticleNumber;
1442 }
1443
1444 int start = -1;
1445 int end = -1;
1446 int idx = range.indexOf('-');
1447 if ( idx == -1 ) {
1448 start = Integer.parseInt(range);
1449 end = start;
1450 } else {
1451 start = Integer.parseInt(range.substring(0,idx));
1452 if ( (idx + 1) == range.length() ) {
1453 end = group.getLastArticleNumber();
1454 } else {
1455 end = Integer.parseInt(range.substring(idx + 1));
1456 }
1457 }
1458 List list = new ArrayList();
1459 for ( int i = start ; i <= end ; i++ ) {
1460 NNTPArticle article = group.getArticle(i);
1461 if ( article != null ) {
1462 list.add(article);
1463 }
1464 }
1465 return (NNTPArticle[])list.toArray(new NNTPArticle[0]);
1466 }
1467
1468
1469
1470
1471
1472
1473
1474
1475 private boolean isAuthorized(String command) {
1476 isAlreadyAuthenticated = isAlreadyAuthenticated || isAuthenticated();
1477 if (isAlreadyAuthenticated) {
1478 return true;
1479 }
1480
1481 boolean allowed = command.equals("AUTHINFO");
1482 allowed = allowed || command.equals("MODE");
1483 allowed = allowed || command.equals("QUIT");
1484 return allowed;
1485 }
1486
1487
1488
1489
1490
1491
1492 private boolean isAuthenticated() {
1493 if ( theConfigData.isAuthRequired() ) {
1494 if ((user != null) && (password != null) && (theConfigData.getUsersRepository() != null)) {
1495 return theConfigData.getUsersRepository().test(user,password);
1496 } else {
1497 return false;
1498 }
1499 } else {
1500 return true;
1501 }
1502 }
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512 private static boolean isMessageId(String testString) {
1513 if ((testString != null) &&
1514 (testString.startsWith("<")) &&
1515 (testString.endsWith(">"))) {
1516 return true;
1517 } else {
1518 return false;
1519 }
1520 }
1521
1522 public void setProtocolHandlerHelper(ProtocolHandlerHelper phh) {
1523 this.helper = phh;
1524 }
1525
1526 }