citadel
About: Citadel is an advanced messaging and collaboration system for groupware and BBS applications (preferred OS: Linux).
  Fossies Dox: citadel.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

serv_nntp.c
Go to the documentation of this file.
1//
2// NNTP server module (RFC 3977)
3//
4// Copyright (c) 2014-2020 by the citadel.org team
5//
6// This program is open source software; you can redistribute it and/or modify
7// it under the terms of the GNU General Public License version 3.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14
15#include "sysdep.h"
16#include <stdlib.h>
17#include <unistd.h>
18#include <stdio.h>
19#include <termios.h>
20#include <fcntl.h>
21#include <signal.h>
22#include <pwd.h>
23#include <errno.h>
24#include <sys/types.h>
25#include <syslog.h>
26#include <time.h>
27#include <sys/wait.h>
28#include <ctype.h>
29#include <string.h>
30#include <limits.h>
31#include <sys/socket.h>
32#include <netinet/in.h>
33#include <arpa/inet.h>
34#include <libcitadel.h>
35#include "citadel.h"
36#include "server.h"
37#include "citserver.h"
38#include "support.h"
39#include "config.h"
40#include "control.h"
41#include "user_ops.h"
42#include "room_ops.h"
43#include "database.h"
44#include "msgbase.h"
45#include "internet_addressing.h"
46#include "genstamp.h"
47#include "domain.h"
48#include "clientsocket.h"
49#include "locate_host.h"
50#include "citadel_dirs.h"
51#include "ctdl_module.h"
52#include "serv_nntp.h"
53
54extern long timezone;
55
56//
57// Tests whether the supplied string is a valid newsgroup name
58// Returns true (nonzero) or false (0)
59//
60int is_valid_newsgroup_name(char *name) {
61 char *ptr = name;
62 int has_a_letter = 0;
63 int num_dots = 0;
64
65 if (!ptr) return(0);
66 if (!strncasecmp(name, "ctdl.", 5)) return(0);
67
68 while (*ptr != 0) {
69
70 if (isalpha(ptr[0])) {
71 has_a_letter = 1;
72 }
73
74 if (ptr[0] == '.') {
75 ++num_dots;
76 }
77
78 if ( (isalnum(ptr[0]))
79 || (ptr[0] == '.')
80 || (ptr[0] == '+')
81 || (ptr[0] == '-')
82 ) {
83 ++ptr;
84 }
85 else {
86 return(0);
87 }
88 }
89 return( (has_a_letter) && (num_dots >= 1) ) ;
90}
91
92
93//
94// Convert a Citadel room name to a valid newsgroup name
95//
96void room_to_newsgroup(char *target, char *source, size_t target_size) {
97
98 if (!target) return;
99 if (!source) return;
100
101 if (is_valid_newsgroup_name(source)) {
102 strncpy(target, source, target_size);
103 return;
104 }
105
106 strcpy(target, "ctdl.");
107 int len = 5;
108 char *ptr = source;
109 char ch;
110
111 while (ch=*ptr++, ch!=0) {
112 if (len >= target_size) return;
113 if ( (isalnum(ch))
114 || (ch == '.')
115 || (ch == '-')
116 ) {
117 target[len++] = tolower(ch);
118 target[len] = 0;
119 }
120 else {
121 target[len++] = '+' ;
122 sprintf(&target[len], "%02x", ch);
123 len += 2;
124 target[len] = 0;
125 }
126 }
127}
128
129
130//
131// Convert a newsgroup name to a Citadel room name.
132// This function recognizes names converted with room_to_newsgroup() and restores them with full fidelity.
133//
134void newsgroup_to_room(char *target, char *source, size_t target_size) {
135
136 if (!target) return;
137 if (!source) return;
138
139 if (strncasecmp(source, "ctdl.", 5)) { // not a converted room name; pass through as-is
140 strncpy(target, source, target_size);
141 return;
142 }
143
144 target[0] = 0;
145 int len = 0;
146 char *ptr = &source[5];
147 char ch;
148
149 while (ch=*ptr++, ch!=0) {
150 if (len >= target_size) return;
151 if (ch == '+') {
152 char hex[3];
153 long digit;
154 hex[0] = *ptr++;
155 hex[1] = *ptr++;
156 hex[2] = 0;
157 digit = strtol(hex, NULL, 16);
158 ch = (char)digit;
159 }
160 target[len++] = ch;
161 target[len] = 0;
162 }
163}
164
165
166//
167// Here's where our NNTP session begins its happy day.
168//
169void nntp_greeting(void) {
170 strcpy(CC->cs_clientname, "NNTP session");
171 CC->cs_flags |= CS_STEALTH;
172
173 CC->session_specific_data = malloc(sizeof(citnntp));
174 citnntp *nntpstate = (citnntp *) CC->session_specific_data;
175 memset(nntpstate, 0, sizeof(citnntp));
176
177 if (CC->nologin==1) {
178 cprintf("451 Too many connections are already open; please try again later.\r\n");
180 return;
181 }
182
183 // Display the standard greeting
184 cprintf("200 %s NNTP Citadel server is not finished yet\r\n", CtdlGetConfigStr("c_fqdn"));
185}
186
187
188//
189// NNTPS is just like NNTP, except it goes crypto right away.
190//
191void nntps_greeting(void) {
192 CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
193#ifdef HAVE_OPENSSL
194 if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO; /* kill session if no crypto */
195#endif
197}
198
199
200//
201// implements the STARTTLS command
202//
203void nntp_starttls(void) {
204 char ok_response[SIZ];
205 char nosup_response[SIZ];
206 char error_response[SIZ];
207
208 sprintf(ok_response, "382 Begin TLS negotiation now\r\n");
209 sprintf(nosup_response, "502 Can not initiate TLS negotiation\r\n");
210 sprintf(error_response, "580 Internal error\r\n");
211 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
212}
213
214
215//
216// Implements the CAPABILITY command
217//
219 cprintf("101 Capability list:\r\n");
220 cprintf("IMPLEMENTATION Citadel %d\r\n", REV_LEVEL);
221 cprintf("VERSION 2\r\n");
222 cprintf("READER\r\n");
223 cprintf("MODE-READER\r\n");
224 cprintf("LIST ACTIVE NEWSGROUPS\r\n");
225 cprintf("OVER\r\n");
226#ifdef HAVE_OPENSSL
227 cprintf("STARTTLS\r\n");
228#endif
229 if (!CC->logged_in) {
230 cprintf("AUTHINFO USER\r\n");
231 }
232 cprintf(".\r\n");
233}
234
235
236//
237// Implements the QUIT command
238//
239void nntp_quit(void) {
240 cprintf("221 Goodbye...\r\n");
241 CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
242}
243
244
245//
246// Implements the AUTHINFO USER command (RFC 4643)
247//
248void nntp_authinfo_user(const char *username) {
249 int a = CtdlLoginExistingUser(username);
250 switch (a) {
252 cprintf("482 Already logged in\r\n");
253 return;
255 cprintf("481 Too many users are already online (maximum is %d)\r\n", CtdlGetConfigInt("c_maxsessions"));
256 return;
257 case login_ok:
258 cprintf("381 Password required for %s\r\n", CC->curr_user);
259 return;
260 case login_not_found:
261 cprintf("481 %s not found\r\n", username);
262 return;
263 default:
264 cprintf("502 Internal error\r\n");
265 }
266}
267
268
269//
270// Implements the AUTHINFO PASS command (RFC 4643)
271//
272void nntp_authinfo_pass(const char *buf) {
273 int a;
274
275 a = CtdlTryPassword(buf, strlen(buf));
276
277 switch (a) {
279 cprintf("482 Already logged in\r\n");
280 return;
281 case pass_no_user:
282 cprintf("482 Authentication commands issued out of sequence\r\n");
283 return;
285 cprintf("481 Authentication failed\r\n");
286 return;
287 case pass_ok:
288 cprintf("281 Authentication accepted\r\n");
289 return;
290 }
291}
292
293
294//
295// Implements the AUTHINFO extension (RFC 4643) in USER/PASS mode
296//
297void nntp_authinfo(const char *cmd) {
298
299 if (!strncasecmp(cmd, "authinfo user ", 14)) {
300 nntp_authinfo_user(&cmd[14]);
301 }
302
303 else if (!strncasecmp(cmd, "authinfo pass ", 14)) {
304 nntp_authinfo_pass(&cmd[14]);
305 }
306
307 else {
308 cprintf("502 command unavailable\r\n");
309 }
310}
311
312
313//
314// Utility function to fetch the current list of message numbers in a room
315//
317 struct nntp_msglist nm;
318 struct cdbdata *cdbfr;
319
320 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
321 if (cdbfr != NULL) {
322 nm.msgnums = (long*)cdbfr->ptr;
323 cdbfr->ptr = NULL;
324 nm.num_msgs = cdbfr->len / sizeof(long);
325 cdbfr->len = 0;
326 cdb_free(cdbfr);
327 } else {
328 nm.num_msgs = 0;
329 nm.msgnums = NULL;
330 }
331 return(nm);
332}
333
334
335//
336// Output a room name (newsgroup name) in formats required for LIST and NEWGROUPS command
337//
338void output_roomname_in_list_format(struct ctdlroom *qrbuf, int which_format, char *wildmat_pattern) {
339 char n_name[1024];
340 struct nntp_msglist nm;
341 long low_water_mark = 0;
342 long high_water_mark = 0;
343
344 room_to_newsgroup(n_name, qrbuf->QRname, sizeof n_name);
345
346 if ((wildmat_pattern != NULL) && (!IsEmptyStr(wildmat_pattern))) {
347 if (!wildmat(n_name, wildmat_pattern)) {
348 return;
349 }
350 }
351
353 if ((nm.num_msgs > 0) && (nm.msgnums != NULL)) {
354 low_water_mark = nm.msgnums[0];
355 high_water_mark = nm.msgnums[nm.num_msgs - 1];
356 }
357
358 // Only the mandatory formats are supported
359 switch(which_format) {
360 case NNTP_LIST_ACTIVE:
361 // FIXME we have hardcoded "n" for "no posting allowed" -- fix when we add posting
362 cprintf("%s %ld %ld n\r\n", n_name, high_water_mark, low_water_mark);
363 break;
365 cprintf("%s %s\r\n", n_name, qrbuf->QRname);
366 break;
367 }
368
369 if (nm.msgnums != NULL) {
370 free(nm.msgnums);
371 }
372}
373
374
375//
376// Called once per room by nntp_newgroups() to qualify and possibly output a single room
377//
378void nntp_newgroups_backend(struct ctdlroom *qrbuf, void *data) {
379 int ra;
380 int view;
381 time_t thetime = *(time_t *)data;
382
383 CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
384
385 /*
386 * The "created after <date/time>" heuristics depend on the happy coincidence
387 * that for a very long time we have used a unix timestamp as the room record's
388 * generation number (QRgen). When this module is merged into the master
389 * source tree we should rename QRgen to QR_create_time or something like that.
390 */
391
392 if (ra & UA_KNOWN) {
393 if (qrbuf->QRgen >= thetime) {
395 }
396 }
397}
398
399
400//
401// Implements the NEWGROUPS command
402//
403void nntp_newgroups(const char *cmd) {
405
406 char stringy_date[16];
407 char stringy_time[16];
408 char stringy_gmt[16];
409 struct tm tm;
410 time_t thetime;
411
412 extract_token(stringy_date, cmd, 1, ' ', sizeof stringy_date);
413 extract_token(stringy_time, cmd, 2, ' ', sizeof stringy_time);
414 extract_token(stringy_gmt, cmd, 3, ' ', sizeof stringy_gmt);
415
416 memset(&tm, 0, sizeof tm);
417 if (strlen(stringy_date) == 6) {
418 sscanf(stringy_date, "%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday);
419 tm.tm_year += 100;
420 }
421 else {
422 sscanf(stringy_date, "%4d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday);
423 tm.tm_year -= 1900;
424 }
425 tm.tm_mon -= 1; // tm_mon is zero based (0=January)
426 tm.tm_isdst = (-1); // let the C library figure out whether DST is in effect
427 sscanf(stringy_time, "%2d%2d%2d", &tm.tm_hour, &tm.tm_min ,&tm.tm_sec);
428 thetime = mktime(&tm);
429 if (!strcasecmp(stringy_gmt, "GMT")) {
430 tzset();
431 thetime += timezone;
432 }
433
434
435 cprintf("231 list of new newsgroups follows\r\n");
436 CtdlGetUser(&CC->user, CC->curr_user);
438 cprintf(".\r\n");
439}
440
441
442//
443// Called once per room by nntp_list() to qualify and possibly output a single room
444//
445void nntp_list_backend(struct ctdlroom *qrbuf, void *data) {
446 int ra;
447 int view;
448 struct nntp_list_data *nld = (struct nntp_list_data *)data;
449
450 CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
451 if (ra & UA_KNOWN) {
453 }
454}
455
456
457//
458// Implements the LIST commands
459//
460void nntp_list(const char *cmd) {
462
463 char list_format[64];
464 char wildmat_pattern[1024];
465 struct nntp_list_data nld;
466
467 extract_token(list_format, cmd, 1, ' ', sizeof list_format);
468 extract_token(wildmat_pattern, cmd, 2, ' ', sizeof wildmat_pattern);
469
470 if (strlen(wildmat_pattern) > 0) {
472 }
473 else {
474 nld.wildmat_pattern = NULL;
475 }
476
477 if ( (strlen(cmd) < 6) || (!strcasecmp(list_format, "ACTIVE")) ) {
479 }
480 else if (!strcasecmp(list_format, "NEWSGROUPS")) {
482 }
483 else if (!strcasecmp(list_format, "OVERVIEW.FMT")) {
485 }
486 else {
487 cprintf("501 syntax error , unsupported list format\r\n");
488 return;
489 }
490
491 // OVERVIEW.FMT delivers a completely different type of data than all of the
492 // other LIST commands. It's a stupid place to put it. But that's how it's
493 // written into RFC3977, so we have to handle it here.
495 cprintf("215 Order of fields in overview database.\r\n");
496 cprintf("Subject:\r\n");
497 cprintf("From:\r\n");
498 cprintf("Date:\r\n");
499 cprintf("Message-ID:\r\n");
500 cprintf("References:\r\n");
501 cprintf("Bytes:\r\n");
502 cprintf("Lines:\r\n");
503 cprintf(".\r\n");
504 return;
505 }
506
507 cprintf("215 list of newsgroups follows\r\n");
508 CtdlGetUser(&CC->user, CC->curr_user);
510 cprintf(".\r\n");
511}
512
513
514//
515// Implement HELP command.
516//
517void nntp_help(void) {
518 cprintf("100 This is the Citadel NNTP service.\r\n");
519 cprintf("RTFM http://www.ietf.org/rfc/rfc3977.txt\r\n");
520 cprintf(".\r\n");
521}
522
523
524//
525// Implement DATE command.
526//
527void nntp_date(void) {
528 time_t now;
529 struct tm nowLocal;
530 struct tm nowUtc;
531 char tsFromUtc[32];
532
533 now = time(NULL);
534 localtime_r(&now, &nowLocal);
535 gmtime_r(&now, &nowUtc);
536
537 strftime(tsFromUtc, sizeof(tsFromUtc), "%Y%m%d%H%M%S", &nowUtc);
538
539 cprintf("111 %s\r\n", tsFromUtc);
540}
541
542
543//
544// back end for the LISTGROUP command , called for each message number
545//
546void nntp_listgroup_backend(long msgnum, void *userdata) {
547
548 struct listgroup_range *lr = (struct listgroup_range *)userdata;
549
550 // check range if supplied
551 if (msgnum < lr->lo) return;
552 if ((lr->hi != 0) && (msgnum > lr->hi)) return;
553
554 cprintf("%ld\r\n", msgnum);
555}
556
557
558//
559// Implements the GROUP and LISTGROUP commands
560//
561void nntp_group(const char *cmd) {
563
564 citnntp *nntpstate = (citnntp *) CC->session_specific_data;
565 char verb[16];
566 char requested_group[1024];
567 char message_range[256];
568 char range_lo[256];
569 char range_hi[256];
570 char requested_room[ROOMNAMELEN];
571 char augmented_roomname[ROOMNAMELEN];
572 int c = 0;
573 int ok = 0;
574 int ra = 0;
575 struct ctdlroom QRscratch;
576 int msgs, new;
577 long oldest,newest;
578 struct listgroup_range lr;
579
580 extract_token(verb, cmd, 0, ' ', sizeof verb);
581 extract_token(requested_group, cmd, 1, ' ', sizeof requested_group);
582 extract_token(message_range, cmd, 2, ' ', sizeof message_range);
583 extract_token(range_lo, message_range, 0, '-', sizeof range_lo);
584 extract_token(range_hi, message_range, 1, '-', sizeof range_hi);
585 lr.lo = atoi(range_lo);
586 lr.hi = atoi(range_hi);
587
588 /* In LISTGROUP mode we can specify an empty name for 'currently selected' */
589 if ((!strcasecmp(verb, "LISTGROUP")) && (IsEmptyStr(requested_group))) {
590 room_to_newsgroup(requested_group, CC->room.QRname, sizeof requested_group);
591 }
592
593 /* First try a regular match */
594 newsgroup_to_room(requested_room, requested_group, sizeof requested_room);
595 c = CtdlGetRoom(&QRscratch, requested_room);
596
597 /* Then try a mailbox name match */
598 if (c != 0) {
599 CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, requested_room);
600 c = CtdlGetRoom(&QRscratch, augmented_roomname);
601 if (c == 0) {
602 safestrncpy(requested_room, augmented_roomname, sizeof(requested_room));
603 }
604 }
605
606 /* If the room exists, check security/access */
607 if (c == 0) {
608 /* See if there is an existing user/room relationship */
609 CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
610
611 /* normal clients have to pass through security */
612 if (ra & UA_KNOWN) {
613 ok = 1;
614 }
615 }
616
617 /* Fail here if no such room */
618 if (!ok) {
619 cprintf("411 no such newsgroup\r\n");
620 return;
621 }
622
623
624 /*
625 * CtdlUserGoto() formally takes us to the desired room, happily returning
626 * the number of messages and number of new messages.
627 */
628 memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom));
629 CtdlUserGoto(NULL, 0, 0, &msgs, &new, &oldest, &newest);
630 cprintf("211 %d %ld %ld %s\r\n", msgs, oldest, newest, requested_group);
631
632 // If this is a GROUP command, set the "current article number" to zero, and then stop here.
633 if (!strcasecmp(verb, "GROUP")) {
634 nntpstate->current_article_number = oldest;
635 return;
636 }
637
638 // If we get to this point we are running a LISTGROUP command. Fetch those message numbers.
639 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, nntp_listgroup_backend, &lr);
640 cprintf(".\r\n");
641}
642
643
644//
645// Implements the MODE command
646//
647void nntp_mode(const char *cmd) {
648
649 char which_mode[16];
650
651 extract_token(which_mode, cmd, 1, ' ', sizeof which_mode);
652
653 if (!strcasecmp(which_mode, "reader")) {
654 // FIXME implement posting and change to 200
655 cprintf("201 Reader mode activated\r\n");
656 }
657 else {
658 cprintf("501 unknown mode\r\n");
659 }
660}
661
662
663//
664// Implements the ARTICLE, HEAD, BODY, and STAT commands.
665// (These commands all accept the same parameters; they differ only in how they output the retrieved message.)
666//
667void nntp_article(const char *cmd) {
669
670 citnntp *nntpstate = (citnntp *) CC->session_specific_data;
671 char which_command[16];
672 int acmd = 0;
673 char requested_article[256];
674 long requested_msgnum = 0;
675 char *lb, *rb = NULL;
676 int must_change_currently_selected_article = 0;
677
678 // We're going to store one of these values in the variable 'acmd' so that
679 // we can quickly check later which version of the output we want.
680 enum {
681 ARTICLE,
682 HEAD,
683 BODY,
684 STAT
685 };
686
687 extract_token(which_command, cmd, 0, ' ', sizeof which_command);
688
689 if (!strcasecmp(which_command, "article")) {
690 acmd = ARTICLE;
691 }
692 else if (!strcasecmp(which_command, "head")) {
693 acmd = HEAD;
694 }
695 else if (!strcasecmp(which_command, "body")) {
696 acmd = BODY;
697 }
698 else if (!strcasecmp(which_command, "stat")) {
699 acmd = STAT;
700 }
701 else {
702 cprintf("500 I'm afraid I can't do that.\r\n");
703 return;
704 }
705
706 // Which NNTP command was issued, determines whether we will fetch headers, body, or both.
707 int headers_only = HEADERS_ALL;
708 if (acmd == HEAD) headers_only = HEADERS_FAST;
709 else if (acmd == BODY) headers_only = HEADERS_NONE;
710 else if (acmd == STAT) headers_only = HEADERS_FAST;
711
712 // now figure out what the client is asking for.
713 extract_token(requested_article, cmd, 1, ' ', sizeof requested_article);
714 lb = strchr(requested_article, '<');
715 rb = strchr(requested_article, '>');
716 requested_msgnum = atol(requested_article);
717
718 // If no article number or message-id is specified, the client wants the "currently selected article"
719 if (IsEmptyStr(requested_article)) {
720 if (nntpstate->current_article_number < 1) {
721 cprintf("420 No current article selected\r\n");
722 return;
723 }
724 requested_msgnum = nntpstate->current_article_number;
725 must_change_currently_selected_article = 1;
726 // got it -- now fall through and keep going
727 }
728
729 // If the requested article is numeric, it maps directly to a message number. Good.
730 else if (requested_msgnum > 0) {
731 must_change_currently_selected_article = 1;
732 // good -- fall through and keep going
733 }
734
735 // If the requested article has angle brackets, the client wants a specific message-id.
736 // We don't know how to do that yet.
737 else if ( (lb != NULL) && (rb != NULL) && (lb < rb) ) {
738 must_change_currently_selected_article = 0;
739 cprintf("500 I don't know how to fetch by message-id yet.\r\n"); // FIXME
740 return;
741 }
742
743 // Anything else is noncompliant gobbledygook and should die in a car fire.
744 else {
745 must_change_currently_selected_article = 0;
746 cprintf("500 syntax error\r\n");
747 return;
748 }
749
750 // At this point we know the message number of the "article" being requested.
751 // We have an awesome API call that does all the heavy lifting for us.
752 char *fetched_message_id = NULL;
753 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
754 int fetch = CtdlOutputMsg(requested_msgnum,
755 MT_RFC822, // output in RFC822 format ... sort of
756 headers_only, // headers, body, or both?
757 0, // don't do Citadel protocol responses
758 1, // CRLF newlines
759 NULL, // teh whole thing, not just a section
760 0, // no flags yet ... maybe new ones for Path: etc ?
761 NULL,
762 NULL,
763 &fetched_message_id // extract the message ID from the message as we go...
764 );
765 StrBuf *msgtext = CC->redirect_buffer;
766 CC->redirect_buffer = NULL;
767
768 if (fetch != om_ok) {
769 cprintf("423 no article with that number\r\n");
770 FreeStrBuf(&msgtext);
771 return;
772 }
773
774 // RFC3977 6.2.1.2 specifes conditions under which the "currently selected article"
775 // MUST or MUST NOT be set to the message we just referenced.
776 if (must_change_currently_selected_article) {
777 nntpstate->current_article_number = requested_msgnum;
778 }
779
780 // Now give the client what it asked for.
781 if (acmd == ARTICLE) {
782 cprintf("220 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
783 }
784 if (acmd == HEAD) {
785 cprintf("221 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
786 }
787 if (acmd == BODY) {
788 cprintf("222 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
789 }
790 if (acmd == STAT) {
791 cprintf("223 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
792 FreeStrBuf(&msgtext);
793 return;
794 }
795
796 client_write(SKEY(msgtext));
797 cprintf(".\r\n"); // this protocol uses a dot terminator
798 FreeStrBuf(&msgtext);
799 if (fetched_message_id) free(fetched_message_id);
800}
801
802
803//
804// Utility function for nntp_last_next() that turns a msgnum into a message ID.
805// The memory for the returned string is pwnz0red by the caller.
806//
807char *message_id_from_msgnum(long msgnum) {
808 char *fetched_message_id = NULL;
809 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
810 CtdlOutputMsg(msgnum,
811 MT_RFC822, // output in RFC822 format ... sort of
812 HEADERS_FAST, // headers, body, or both?
813 0, // don't do Citadel protocol responses
814 1, // CRLF newlines
815 NULL, // teh whole thing, not just a section
816 0, // no flags yet ... maybe new ones for Path: etc ?
817 NULL,
818 NULL,
819 &fetched_message_id // extract the message ID from the message as we go...
820 );
821 StrBuf *msgtext = CC->redirect_buffer;
822 CC->redirect_buffer = NULL;
823
824 FreeStrBuf(&msgtext);
825 return(fetched_message_id);
826}
827
828
829//
830// The LAST and NEXT commands are so similar that they are handled by a single function.
831//
832void nntp_last_next(const char *cmd) {
834
835 citnntp *nntpstate = (citnntp *) CC->session_specific_data;
836 char which_command[16];
837 int acmd = 0;
838
839 // We're going to store one of these values in the variable 'acmd' so that
840 // we can quickly check later which version of the output we want.
841 enum {
842 NNTP_LAST,
843 NNTP_NEXT
844 };
845
846 extract_token(which_command, cmd, 0, ' ', sizeof which_command);
847
848 if (!strcasecmp(which_command, "last")) {
849 acmd = NNTP_LAST;
850 }
851 else if (!strcasecmp(which_command, "next")) {
852 acmd = NNTP_NEXT;
853 }
854 else {
855 cprintf("500 I'm afraid I can't do that.\r\n");
856 return;
857 }
858
859 // ok, here we go ... fetch the msglist so we can figure out our place in the universe
860 struct nntp_msglist nm;
861 int i = 0;
862 long selected_msgnum = 0;
863 char *message_id = NULL;
864
865 nm = nntp_fetch_msglist(&CC->room);
866 if ((nm.num_msgs < 0) || (nm.msgnums == NULL)) {
867 cprintf("500 something bad happened\r\n");
868 return;
869 }
870
871 if ( (acmd == NNTP_LAST) && (nm.num_msgs == 0) ) {
872 cprintf("422 no previous article in this group\r\n"); // nothing here
873 }
874
875 else if ( (acmd == NNTP_LAST) && (nntpstate->current_article_number <= nm.msgnums[0]) ) {
876 cprintf("422 no previous article in this group\r\n"); // already at the beginning
877 }
878
879 else if (acmd == NNTP_LAST) {
880 for (i=0; ((i<nm.num_msgs)&&(selected_msgnum<=0)); ++i) {
881 if ( (nm.msgnums[i] >= nntpstate->current_article_number) && (i > 0) ) {
882 selected_msgnum = nm.msgnums[i-1];
883 }
884 }
885 if (selected_msgnum > 0) {
886 nntpstate->current_article_number = selected_msgnum;
887 message_id = message_id_from_msgnum(nntpstate->current_article_number);
888 cprintf("223 %ld <%s>\r\n", nntpstate->current_article_number, message_id);
889 if (message_id) free(message_id);
890 }
891 else {
892 cprintf("422 no previous article in this group\r\n");
893 }
894 }
895
896 else if ( (acmd == NNTP_NEXT) && (nm.num_msgs == 0) ) {
897 cprintf("421 no next article in this group\r\n"); // nothing here
898 }
899
900 else if ( (acmd == NNTP_NEXT) && (nntpstate->current_article_number >= nm.msgnums[nm.num_msgs-1]) ) {
901 cprintf("421 no next article in this group\r\n"); // already at the end
902 }
903
904 else if (acmd == NNTP_NEXT) {
905 for (i=0; ((i<nm.num_msgs)&&(selected_msgnum<=0)); ++i) {
906 if (nm.msgnums[i] > nntpstate->current_article_number) {
907 selected_msgnum = nm.msgnums[i];
908 }
909 }
910 if (selected_msgnum > 0) {
911 nntpstate->current_article_number = selected_msgnum;
912 message_id = message_id_from_msgnum(nntpstate->current_article_number);
913 cprintf("223 %ld <%s>\r\n", nntpstate->current_article_number, message_id);
914 if (message_id) free(message_id);
915 }
916 else {
917 cprintf("421 no next article in this group\r\n");
918 }
919 }
920
921 // should never get here
922 else {
923 cprintf("500 internal error\r\n");
924 }
925
926 if (nm.msgnums != NULL) {
927 free(nm.msgnums);
928 }
929
930}
931
932
933//
934// back end for the XOVER command , called for each message number
935//
936void nntp_xover_backend(long msgnum, void *userdata) {
937
938 struct listgroup_range *lr = (struct listgroup_range *)userdata;
939
940 // check range if supplied
941 if (msgnum < lr->lo) return;
942 if ((lr->hi != 0) && (msgnum > lr->hi)) return;
943
944 struct CtdlMessage *msg = CtdlFetchMessage(msgnum, 0);
945 if (msg == NULL) {
946 return;
947 }
948
949 // Teh RFC says we need:
950 // -------------------------
951 // Subject header content
952 // From header content
953 // Date header content
954 // Message-ID header content
955 // References header content
956 // :bytes metadata item
957 // :lines metadata item
958
959 time_t msgtime = atol(msg->cm_fields[eTimestamp]);
960 char strtimebuf[26];
961 ctime_r(&msgtime, strtimebuf);
962
963 // here we go -- print the line o'data
964 cprintf("%ld\t%s\t%s <%s>\t%s\t%s\t%s\t100\t10\r\n",
965 msgnum,
967 msg->cm_fields[eAuthor],
969 strtimebuf,
970 msg->cm_fields[emessageId],
972 );
973
974 CM_Free(msg);
975}
976
977
978//
979//
980// XOVER is used by some clients, even if we don't offer it
981//
982void nntp_xover(const char *cmd) {
984
985 citnntp *nntpstate = (citnntp *) CC->session_specific_data;
986 char range[256];
987 struct listgroup_range lr;
988
989 extract_token(range, cmd, 1, ' ', sizeof range);
990 lr.lo = atol(range);
991 if (lr.lo <= 0) {
992 lr.lo = nntpstate->current_article_number;
993 lr.hi = nntpstate->current_article_number;
994 }
995 else {
996 char *dash = strchr(range, '-');
997 if (dash != NULL) {
998 ++dash;
999 lr.hi = atol(dash);
1000 if (lr.hi == 0) {
1001 lr.hi = LONG_MAX;
1002 }
1003 if (lr.hi < lr.lo) {
1004 lr.hi = lr.lo;
1005 }
1006 }
1007 else {
1008 lr.hi = lr.lo;
1009 }
1010 }
1011
1012 cprintf("224 Overview information follows\r\n");
1013 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, nntp_xover_backend, &lr);
1014 cprintf(".\r\n");
1015}
1016
1017
1018//
1019// Main command loop for NNTP server sessions.
1020//
1022 StrBuf *Cmd = NewStrBuf();
1023 char cmdname[16];
1024
1025 time(&CC->lastcmd);
1026 if (CtdlClientGetLine(Cmd) < 1) {
1027 syslog(LOG_CRIT, "NNTP: client disconnected: ending session.\n");
1028 CC->kill_me = KILLME_CLIENT_DISCONNECTED;
1029 FreeStrBuf(&Cmd);
1030 return;
1031 }
1032 syslog(LOG_DEBUG, "NNTP: %s\n", ((!strncasecmp(ChrPtr(Cmd), "AUTHINFO", 8)) ? "AUTHINFO ..." : ChrPtr(Cmd)));
1033 extract_token(cmdname, ChrPtr(Cmd), 0, ' ', sizeof cmdname);
1034
1035 // Rumpelstiltskin lookups are *awesome*
1036
1037 if (!strcasecmp(cmdname, "quit")) {
1038 nntp_quit();
1039 }
1040
1041 else if (!strcasecmp(cmdname, "help")) {
1042 nntp_help();
1043 }
1044
1045 else if (!strcasecmp(cmdname, "date")) {
1046 nntp_date();
1047 }
1048
1049 else if (!strcasecmp(cmdname, "capabilities")) {
1051 }
1052
1053 else if (!strcasecmp(cmdname, "starttls")) {
1054 nntp_starttls();
1055 }
1056
1057 else if (!strcasecmp(cmdname, "authinfo")) {
1058 nntp_authinfo(ChrPtr(Cmd));
1059 }
1060
1061 else if (!strcasecmp(cmdname, "newgroups")) {
1062 nntp_newgroups(ChrPtr(Cmd));
1063 }
1064
1065 else if (!strcasecmp(cmdname, "list")) {
1066 nntp_list(ChrPtr(Cmd));
1067 }
1068
1069 else if (!strcasecmp(cmdname, "group")) {
1070 nntp_group(ChrPtr(Cmd));
1071 }
1072
1073 else if (!strcasecmp(cmdname, "listgroup")) {
1074 nntp_group(ChrPtr(Cmd));
1075 }
1076
1077 else if (!strcasecmp(cmdname, "mode")) {
1078 nntp_mode(ChrPtr(Cmd));
1079 }
1080
1081 else if (
1082 (!strcasecmp(cmdname, "article"))
1083 || (!strcasecmp(cmdname, "head"))
1084 || (!strcasecmp(cmdname, "body"))
1085 || (!strcasecmp(cmdname, "stat"))
1086 )
1087 {
1088 nntp_article(ChrPtr(Cmd));
1089 }
1090
1091 else if (
1092 (!strcasecmp(cmdname, "last"))
1093 || (!strcasecmp(cmdname, "next"))
1094 )
1095 {
1096 nntp_last_next(ChrPtr(Cmd));
1097 }
1098
1099 else if (
1100 (!strcasecmp(cmdname, "xover"))
1101 || (!strcasecmp(cmdname, "over"))
1102 )
1103 {
1104 nntp_xover(ChrPtr(Cmd));
1105 }
1106
1107 else {
1108 cprintf("500 I'm afraid I can't do that.\r\n");
1109 }
1110
1111 FreeStrBuf(&Cmd);
1112}
1113
1114
1115// ****************************************************************************
1116// MODULE INITIALIZATION STUFF
1117// ****************************************************************************
1118
1119
1120//
1121// This cleanup function blows away the temporary memory used by
1122// the NNTP server.
1123//
1125 /* Don't do this stuff if this is not an NNTP session! */
1126 if (CC->h_command_function != nntp_command_loop) return;
1127
1128 syslog(LOG_DEBUG, "Performing NNTP cleanup hook\n");
1129 citnntp *nntpstate = (citnntp *) CC->session_specific_data;
1130 if (nntpstate != NULL) {
1131 free(nntpstate);
1132 nntpstate = NULL;
1133 }
1134}
1135
1136const char *CitadelServiceNNTP="NNTP";
1137const char *CitadelServiceNNTPS="NNTPS";
1138
1140{
1141 if (!threading)
1142 {
1144 NULL,
1147 NULL,
1149
1150#ifdef HAVE_OPENSSL
1152 NULL,
1155 NULL,
1157#endif
1158
1160 }
1161
1162 /* return our module name for the log */
1163 return "nntp";
1164}
#define ROOMNAMELEN
Definition: citadel.h:56
#define REV_LEVEL
Definition: citadel.h:35
#define LONG_MAX
Definition: citadel.h:167
char * CtdlGetConfigStr(char *key)
Definition: config.c:384
int CtdlGetConfigInt(char *key)
Definition: config.c:417
#define CC
Definition: context.h:140
int CtdlAccessCheck(int)
Definition: user_ops.c:387
void CtdlModuleStartCryptoMsgs(char *ok_response, char *nosup_response, char *error_response)
void CtdlMailboxName(char *buf, size_t n, const struct ctdluser *who, const char *prefix)
Definition: user_ops.c:349
int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name)
Definition: room_ops.c:342
void CtdlRegisterServiceHook(int tcp_port, char *sockpath, void(*h_greeting_function)(void), void(*h_command_function)(void), void(*h_async_function)(void), const char *ServiceName)
void CtdlRegisterSessionHook(void(*fcn_ptr)(void), int EventType, int Priority)
void CtdlRoomAccess(struct ctdlroom *roombuf, struct ctdluser *userbuf, int *result, int *view)
Definition: room_ops.c:121
@ pass_ok
Definition: ctdl_module.h:290
@ pass_no_user
Definition: ctdl_module.h:292
@ pass_already_logged_in
Definition: ctdl_module.h:291
@ pass_wrong_password
Definition: ctdl_module.h:294
int CtdlTryPassword(const char *password, long len)
Definition: user_ops.c:864
void CtdlForEachRoom(ForEachRoomCallBack CB, void *in_data)
Definition: room_ops.c:606
@ login_too_many_users
Definition: ctdl_module.h:304
@ login_not_found
Definition: ctdl_module.h:305
@ login_ok
Definition: ctdl_module.h:302
@ login_already_logged_in
Definition: ctdl_module.h:303
int CtdlLoginExistingUser(const char *username)
Definition: user_ops.c:533
@ ac_logged_in_or_guest
Definition: ctdl_module.h:243
void CtdlUserGoto(char *where, int display_result, int transiently, int *msgs, int *new, long *oldest, long *newest)
Definition: room_ops.c:702
int CtdlGetUser(struct ctdluser *usbuf, char *name)
Definition: user_ops.c:75
#define PRIO_STOP
Definition: ctdl_module.h:87
#define CTDL_MODULE_INIT(module_name)
Definition: ctdl_module.h:50
void cdb_free(struct cdbdata *cdb)
Definition: database.c:609
struct cdbdata * cdb_fetch(int cdb, const void *key, int keylen)
Definition: database.c:546
#define UA_KNOWN
Definition: ipcdef.h:79
void CM_Free(struct CtdlMessage *msg)
Definition: msgbase.c:310
int CtdlOutputMsg(long msg_num, int mode, int headers_only, int do_proto, int crlf, char *section, int flags, char **Author, char **Address, char **MessageID)
Definition: msgbase.c:1481
int CtdlForEachMessage(int mode, long ref, char *search_string, char *content_type, struct CtdlMessage *compare, ForEachMsgCallback CallBack, void *userdata)
Definition: msgbase.c:626
struct CtdlMessage * CtdlFetchMessage(long msgnum, int with_body)
Definition: msgbase.c:1135
#define HEADERS_NONE
Definition: msgbase.h:41
#define HEADERS_ALL
Definition: msgbase.h:39
@ om_ok
Definition: msgbase.h:29
#define HEADERS_FAST
Definition: msgbase.h:42
@ MSGS_ALL
Definition: msgbase.h:6
void * malloc(size_t)
void free(void *)
struct ctdlroom qrbuf
Definition: serv_migrate.c:497
void nntp_help(void)
Definition: serv_nntp.c:517
int is_valid_newsgroup_name(char *name)
Definition: serv_nntp.c:60
const char * CitadelServiceNNTP
Definition: serv_nntp.c:1136
void nntps_greeting(void)
Definition: serv_nntp.c:191
void nntp_authinfo_user(const char *username)
Definition: serv_nntp.c:248
void nntp_date(void)
Definition: serv_nntp.c:527
long timezone
char * message_id_from_msgnum(long msgnum)
Definition: serv_nntp.c:807
void nntp_xover(const char *cmd)
Definition: serv_nntp.c:982
void newsgroup_to_room(char *target, char *source, size_t target_size)
Definition: serv_nntp.c:134
void nntp_group(const char *cmd)
Definition: serv_nntp.c:561
void nntp_quit(void)
Definition: serv_nntp.c:239
void nntp_newgroups_backend(struct ctdlroom *qrbuf, void *data)
Definition: serv_nntp.c:378
void nntp_listgroup_backend(long msgnum, void *userdata)
Definition: serv_nntp.c:546
struct nntp_msglist nntp_fetch_msglist(struct ctdlroom *qrbuf)
Definition: serv_nntp.c:316
void nntp_mode(const char *cmd)
Definition: serv_nntp.c:647
void nntp_article(const char *cmd)
Definition: serv_nntp.c:667
void nntp_authinfo_pass(const char *buf)
Definition: serv_nntp.c:272
void nntp_authinfo(const char *cmd)
Definition: serv_nntp.c:297
void nntp_cleanup_function(void)
Definition: serv_nntp.c:1124
void nntp_greeting(void)
Definition: serv_nntp.c:169
void output_roomname_in_list_format(struct ctdlroom *qrbuf, int which_format, char *wildmat_pattern)
Definition: serv_nntp.c:338
void nntp_starttls(void)
Definition: serv_nntp.c:203
void nntp_list(const char *cmd)
Definition: serv_nntp.c:460
void room_to_newsgroup(char *target, char *source, size_t target_size)
Definition: serv_nntp.c:96
void nntp_list_backend(struct ctdlroom *qrbuf, void *data)
Definition: serv_nntp.c:445
void nntp_capabilities(void)
Definition: serv_nntp.c:218
void nntp_newgroups(const char *cmd)
Definition: serv_nntp.c:403
void nntp_last_next(const char *cmd)
Definition: serv_nntp.c:832
const char * CitadelServiceNNTPS
Definition: serv_nntp.c:1137
void nntp_command_loop(void)
Definition: serv_nntp.c:1021
void nntp_xover_backend(long msgnum, void *userdata)
Definition: serv_nntp.c:936
int wildmat(const char *text, const char *p)
Definition: wildmat.c:166
@ NNTP_LIST_NEWSGROUPS
Definition: serv_nntp.h:52
@ NNTP_LIST_OVERVIEW_FMT
Definition: serv_nntp.h:53
@ NNTP_LIST_ACTIVE
Definition: serv_nntp.h:48
#define EVT_STOP
Definition: server.h:212
@ eWeferences
Definition: server.h:322
@ emessageId
Definition: server.h:311
@ erFc822Addr
Definition: server.h:310
@ eAuthor
Definition: server.h:307
@ eTimestamp
Definition: server.h:319
@ eMsgSubject
Definition: server.h:320
@ MT_RFC822
Definition: server.h:167
@ KILLME_CLIENT_DISCONNECTED
Definition: server.h:90
@ KILLME_CLIENT_LOGGED_OUT
Definition: server.h:88
@ KILLME_MAX_SESSIONS_EXCEEDED
Definition: server.h:93
@ KILLME_NO_CRYPTO
Definition: server.h:100
@ CDB_MSGLISTS
Definition: server.h:189
#define CS_STEALTH
Definition: server.h:110
char * cm_fields[256]
Definition: server.h:37
long current_article_number
Definition: serv_nntp.h:40
size_t len
Definition: server.h:203
char * ptr
Definition: server.h:204
char QRname[128]
Definition: citadel.h:108
long QRnumber
Definition: citadel.h:119
time_t QRgen
Definition: citadel.h:112
char * wildmat_pattern
Definition: serv_nntp.h:26
long * msgnums
Definition: serv_nntp.h:19
int num_msgs
Definition: serv_nntp.h:18
#define SIZ
Definition: sysconfig.h:33
int CtdlClientGetLine(StrBuf *Target)
Definition: sysdep.c:518
void cprintf(const char *format,...)
Definition: sysdep.c:381
int client_write(const char *buf, int nbytes)
Definition: sysdep.c:307