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)  

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