leafnode  1.12.0
About: Leafnode is a store & forward NNTP proxy for small (dialup) sites.
  Fossies Dox: leafnode-1.12.0.tar.xz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

nntpd.c
Go to the documentation of this file.
1/*
2nntpd -- the NNTP server
3
4Written by Arnt Gulbrandsen <agulbra@troll.no> and copyright 1995
5Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47
622646949.
7Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>
8and Randolf Skerka <Randolf.Skerka@gmx.de>.
9Copyright of the modifications 1997.
10Modified by Kent Robotti <robotti@erols.com>. Copyright of the
11modifications 1998.
12Modified by Markus Enzenberger <enz@cip.physik.uni-muenchen.de>.
13Copyright of the modifications 1998.
14Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>
15and Kazushi (Jam) Marukawa <jam@pobox.com>.
16Copyright of the modifications 1998, 1999.
17Modified by Ralf Wildenhues <ralf.wildenhues@gmx.de>
18Copyright of the modifications 2002.
19Modified by Jonathan Larmour <jifl@jifvik.org>
20Copyright of the modifications 2002.
21Modified by Matthias Andree <matthias.andree@gmx.de>
22Copyright of the modifications 2000 - 2021.
23
24See file COPYING for restrictions on the use of this software.
25*/
26
27#include "leafnode.h"
28#include "masock.h"
29#include "mastring.h"
30#include "validatefqdn.h"
31#include "strlcpy.h"
32#include "ln_log.h"
33#include "nntpd.h"
34
35#ifdef SOCKS
36#include <socks.h>
37#endif
38
39#include <sys/types.h>
40#include <sys/stat.h>
41#include <netinet/in.h>
42#ifndef __LCLINT__
43#include <arpa/inet.h>
44#endif
45#include <ctype.h>
46#include "system.h"
47#include <errno.h>
48#include <fcntl.h>
49#include <limits.h>
50#include <netdb.h>
51#include <signal.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <sys/socket.h>
56#include <syslog.h>
57#include <unistd.h>
58#include <utime.h>
59
60#define MAXLINELENGTH 1000
61
62#ifdef HAVE_IPV6
63/*
64 * * local union struct
65 */
66union sockaddr_union {
67 struct sockaddr sa;
68 struct sockaddr_in sin;
69 struct sockaddr_in6 sin6;
70};
71#endif
72
73/*@null@*/ static struct newsgroup *group; /* current group, initially none */
74/*@null@*/ static struct newsgroup *xovergroup = NULL;
75static unsigned long artno; /* current article number */
76/*@dependent@*/ /*@null@*/ static char *cmd; /* current command line */
77static time_t activetime;
78
79int debug = 0;
80int verbose = 0; /* verbose doesn't count here */
81
82static void
84{
85 /*@observer@*/ static const char *const e = "Write error on stdout.";
86 syslog(LOG_CRIT, "%s", e);
87 fprintf(stderr, "%s\n", e);
88 exit(EXIT_FAILURE);
89}
90
91static void
93{
94 struct stat st;
95 char s[SIZE_s+1];
96
97 (void)xsnprintf(s, SIZE_s, "%s/leaf.node/groupinfo", spooldir);
98
99 if ((!stat(s, &st) && (st.st_mtime > activetime)) || (active == NULL)) {
100 char *grouptmp = NULL;
101 if (debugmode)
102 syslog(LOG_DEBUG, "rereading %s", s);
103 if (group) {
104 grouptmp = critstrdup(group->name, "rereadactive");
105 }
106 readactive();
107 if (activesize == 0)
108 fakeactive();
109 else
110 activetime = st.st_mtime;
111 if (grouptmp) {
112 group = findgroup(grouptmp);
113 xovergroup = NULL;
114 free(grouptmp);
115 }
116 }
117}
118
119/*
120 * pseudo article stuff
121 */
122
123/* build and return an open fd to pseudoart in group */
124static FILE *
125buildpseudoart(const char *grp)
126{
127 FILE *f;
128 int fd;
129
131 (void)mastr_vcat(n, spooldir, "/temp.files/pseudo.XXXXXXXXXX", NULL);
133 if (fd < 0) {
134 syslog(LOG_ERR, "Could not create pseudoarticle: mkstemp failed: %m");
135 mastr_delete(n);
136 return NULL;
137 }
138
139 (void)unlink(mastr_str(n));
140
141 f = fdopen(fd, "w+b");
142 if (f == NULL) {
143 syslog(LOG_ERR, "Could not create pseudoarticle: fdopen failed: %m");
144 (void)close(fd);
145 mastr_delete(n);
146 return NULL;
147 }
148
149 fprintf(f, "Path: %s\n", fqdn);
150 fprintf(f, "Newsgroups: %s\n", grp);
151 fprintf(f, "From: Leafnode <%s>\n", newsadmin);
152 fprintf(f, "Subject: Leafnode placeholder for group %s\n", grp);
153 fprintf(f, "Date: %s\n", rfctime());
154 fprintf(f, "Message-ID: <leafnode:placeholder:%s@%s>\n", grp, fqdn);
155 fprintf(f, "\n");
156 fprintf(f,
157 "This server is running leafnode, which is a dynamic NNTP proxy.\n"
158 "This means that it does not retrieve newsgroups unless someone is\n"
159 "actively reading them.\n"
160 "\n"
161 "If you do an operation on a group - such as reading an article,\n"
162 "looking at the group table of contents or similar, then leafnode\n"
163 "will go and fetch articles from that group when it next updates.\n\n");
164 fprintf(f,
165 "Since you have read this dummy article, leafnode will retrieve\n"
166 "the newsgroup %s when fetchnews is run\n"
167 "the next time. If you'll look into this group a little later, you\n"
168 "will see real articles.\n\n", grp);
169 fprintf(f,
170 "If you see articles in groups you do not read, that is almost\n"
171 "always because of cross-posting. These articles do not occupy any\n"
172 "more space - they are hard-linked into each newsgroup directory.\n"
173 "\n"
174 "If you do not understand this, please talk to your newsmaster.\n"
175 "\n"
176 "Leafnode can be found at\n" "\thttp://www.leafnode.org/\n\n");
177
178 if (ferror(f)) {
179 (void)fclose(f);
180 f = NULL;
181 } else {
182 rewind(f);
183 }
184 mastr_delete(n);
185 return f;
186}
187
188static int
189parserange(char *arg, unsigned long *a, unsigned long *b)
190{
191 char *l = arg;
192 /* no argument */
193 if (!*l) return 0;
194
195 /* parse */
196 if (*l != '-')
197 *a = strtoul(arg, &l, 10);
198 SKIPLWS(l);
199 if (*l == '-') {
200 ++l;
201 SKIPLWS(l);
202 if (isdigit((unsigned char)*l))
203 *b = strtoul(l, &l, 10);
204 } else {
205 *b = *a;
206 }
207 SKIPLWS(l);
208
209 /* trailing garbage */
210 if (l && *l) {
211 return 0;
212 }
213
214 return 1;
215}
216
217static int
218is_pseudogroup(/*@null@*/ const struct newsgroup *g)
219{
220 if (!g) return 0;
221 if (!chdirgroup(g->name, FALSE)) return 1;
222/* if (isinteresting(g->name)) return 0; */
223 if (g->last < g->first) return 1;
224 if (g->last <= 1) return 1;
225 return 0;
226}
227
228/*
229 * XXX FIXME: an option is needed if the administrator
230 * locks the interesting.group permissions in order to have a fixed set
231 * of subscriptions that are always available, or if the subscription
232 * is handled outside leafnode.
233 */
234static void
235markinterest(const char *ng)
236{
237 struct stat st;
238 struct utimbuf buf;
239 int err;
240 time_t now;
241 FILE *f;
242 char s[SIZE_s+1];
243
244 /* OK, the user may be setting the file to root owned with group
245 * write permission. This is a problem when trying to set the ctime
246 * without setting the mtime, because we would need privileges to do
247 * that. Instead, we'll bump both times and warn the user. */
248
249 err = 0;
250 (void)xsnprintf(s, SIZE_s, "%s/interesting.groups/%s", spooldir, ng);
251 if (stat(s, &st) == 0) {
252 const char *touched = "ctime";
253 /* group was read before: keep mtime, but bump up ctime */
254 now = time(NULL);
255 buf.actime = (now < st.st_atime) ? st.st_atime : now;
256 /* now < update may happen through HW failures */
257 buf.modtime = st.st_mtime;
258 if (utime(s, &buf)) {
259 int oe = errno;
260 /* cannot set time - the file's user-id must be the NEWS_USER's
261 * 2nd best - we'll try to bump both times, but this means
262 * timeout_short applies, so warn the user. */
263 if (utime(s, NULL)) {
264 /* ok, didn't work either. complain and give up.
265 * We used to fall through to the code below, but that
266 * may truncate the file and thus lose delaybody article
267 * numbers. */
268 syslog(LOG_ERR, "Error: cannot set ctime/mtime timestamp of %s: %s.",
269 s, strerror(errno));
270 return;
271 } else {
272 /* complain that timeout_short applies, so that the user
273 * is not surprised by premature group unsubscription. */
274 touched = "ctime and mtime";
276 syslog(LOG_WARNING, "Warning: cannot set the ctime timestamp of %s without resetting mtime (%s). "
277 "This means that timeout_short will apply rather than timeout_long. "
278 "Either reset file ownership to " NEWS_USER " or set timeout_short and timeout_long to the same value to suppress this warning.",
279 s, strerror(oe));
280 }
281 }
282 }
283 if (debugmode && !err)
284 syslog(LOG_DEBUG, "markinterest: %s touched %s", ng, touched);
285 } else {
286 err = 1;
287 }
288 if (err) {
289 f = fopen(s, "w"); /* truncating sets ctime and mtime */
290 if (f == NULL || fclose(f) == EOF) {
291 syslog(LOG_ERR, "Could not create %s: %m", s);
292 } else {
293 if (debugmode)
294 syslog(LOG_DEBUG, "markinterest: %s new", ng);
295 }
296 } else {
297 int fd = open(".", O_RDONLY);
298 (void)chdirgroup(ng, FALSE);
299 if (fd >= 0) {
300 (void)fchdir(fd);
301 (void)close(fd);
302 }
303 }
304}
305
306/* open a pseudo art */
307/* WARNING: article_num MUST be 0 for selection based on Message-ID */
308static FILE *
309fopenpseudoart(const char *arg, const unsigned long article_num)
310{
311 FILE *f = NULL;
312 char *c;
313 struct newsgroup *g;
314
315 if (group && (article_num && ((article_num == group->first &&
318 } else if (!article_num) {
319 if (!strncmp(arg, "<leafnode:placeholder:", 22)) {
320 mastr *msgidbuf = mastr_new(1024);
321 (void)mastr_cpy(msgidbuf, arg + 22);
322 if ((c = strchr(mastr_modifyable_str(msgidbuf), '@')) != NULL) {
323 *c++ = '\0';
324 (void)strtok(c, ">");
325 if (0 == strcasecmp(c, fqdn)) {
326 g = findgroup(mastr_str(msgidbuf));
327 if (g && (g->last <= 1 || g->first >= g->last)) {
328 markinterest(g->name);
329 f = buildpseudoart(g->name);
330 }
331 }
332 }
333 mastr_delete(msgidbuf);
334 }
335 }
336 return f;
337}
338
339/* open an article by number or message-id */
340static FILE *
341fopenart(const char *arg)
342{
343 unsigned long int a;
344 FILE *f;
345 char *t;
346 struct stat st;
347 /*@temp@*/ char buf[32];
348
349 t = NULL;
350 a = strtoul(arg, &t, 10);
351 if (arg && *arg == '<') {
352 /* message ID given */
353 f = fopen(lookup(arg), "r");
354 if (!f) f = fopenpseudoart(arg, 0);
355 } else if (t && !*t && group != NULL) {
356 const char *ptr = arg;
357 /* number not given -> take current article pointer */
358 if (!a) {
359 a = artno;
360 sprintf(buf, "%lu", artno); /* RATS: ignore */
361 ptr = buf;
362 }
363 if (is_pseudogroup(group)) {
364 f = fopenpseudoart(ptr, a);
365 } else {
366 f = fopen(ptr, "r");
367 }
368 if (f != NULL)
369 artno = a;
371 } else {
372 f = NULL;
373 }
374 if (f != NULL && (fstat(fileno(f), &st) || st.st_size == 0)) {
375 (void)fclose(f);
376 f = NULL;
377 }
378 return f;
379}
380
381
382/*
383 * Mark an article for download by appending its number to the
384 * corresponding file in interesting.groups
385 */
386static int
387markdownload(const char *ng, unsigned long id)
388{
389 int i, e = 0;
390 unsigned long n;
391 FILE *f;
392 char *t;
393 char s[SIZE_s+1];
394
395 (void)xsnprintf(s, SIZE_s, "%s/interesting.groups/%s", spooldir, ng);
396 if ((f = fopen(s, "r+"))) {
397 i = 0;
398 while ((t = getaline(f))) {
399 if (sscanf(t, "%lu", &n) == 1 && n == id) {
400 (void)fclose(f); /* we only read from the file */
401 return 0; /* already marked */
402 }
403 if (ferror(f))
404 e = errno;
405 ++i;
406 }
407 if (i < BODY_DOWNLOAD_LIMIT) {
408 (void)fprintf(f, "%lu\n", id);
409 if (ferror(f))
410 e = errno;
411 if (debugmode)
412 syslog(LOG_DEBUG, "Marking %s %lu for download", ng, id);
413 } else {
414 syslog(LOG_ERR, "Too many bodies marked in %s", ng);
415 }
416 if (fclose(f))
417 e = errno;
418 }
419 if (e) {
420 syslog(LOG_ERR, "I/O error handling \"%s\": %s", s, strerror(e));
421 return -1;
422 }
423 return 1;
424}
425
426static void
428{
429 printf("412 Use the GROUP command first\r\n");
430 if (debugmode)
431 syslog(LOG_DEBUG, ">412 Use the GROUP command first");
432 return;
433}
434
435/* display an article or somesuch */
436/* DOARTICLE */
437static void
438doarticle(const char *arg, int what)
439{
440 FILE *f;
441 char *p = NULL;
442 char *q = NULL;
443 char *t;
444 unsigned long localartno;
445 unsigned long replyartno;
446 char *localmsgid, *xref = NULL;
447 char *markgroup = NULL;
448 char s[SIZE_s+1];
449
450 f = fopenart(arg);
451 if (!f) {
452 if (arg && *arg != '<' && !group) {
453 nogroup();
454 } else if (strlen(arg)) {
455 printf("430 No such article: %s\r\n", arg);
456 if (debugmode)
457 syslog(LOG_DEBUG, ">430 No such article: %s", arg);
458 } else {
459 printf("423 No such article: %lu\r\n", artno);
460 if (debugmode)
461 syslog(LOG_DEBUG, ">423 No such article: %lu", artno);
462 }
463 return;
464 }
465
466 if (!*arg) {
467 /* no. implicit */
468 localartno = artno;
469 localmsgid = fgetheader(f, "Message-ID:");
470 } else if (*arg == '<') {
471 /* message-id -- do not modify artno */
472 localartno = 0;
473 localmsgid = critstrdup(arg, "doarticle");
474 } else {
475 /* no. explicit */
476 localartno = strtoul(arg, NULL, 10);
477 localmsgid = fgetheader(f, "Message-ID:");
478 }
479
480 replyartno = localartno;
481 if (!localartno) {
482 /* we have to extract the article number and the newsgroup from
483 * the Xref: header */
484 xref = fgetheader(f, "Xref:");
485 p = xref;
486 if (p) {
487 /* skip host name */
488 while (*p && !isspace((unsigned char)*p)) {
489 p++;
490 }
491 while (isspace((unsigned char)*p)) {
492 p++;
493 }
494 }
495 if (p) {
496 /* search article number of current group in Xref: */
497 if (group) {
498 while ((q = strstr(p, group->name)) != NULL) {
499 q += strlen(group->name);
500 if (*q++ == ':') {
501 localartno = strtoul(q, NULL, 10);
502 markgroup = group->name;
503 break;
504 }
505 p = q + strcspn(q, " \t");
506 }
507 }
508 /* if we don't have a localartno, then we need to mark this
509 * article in a different news group */
510 if (!localartno) {
511 char *r = p;
512 while ((q = strchr(r, ':'))) {
513 *q++ = '\0';
514 if (isinteresting(p)) {
515 /* got one we can mark */
516 markgroup = r;
517 localartno = strtoul(q, NULL, 10);
518 break;
519 }
520 while (isdigit((unsigned char)*q)) {
521 q++;
522 }
523 while (isspace((unsigned char)*q)) {
524 q++;
525 }
526 r = q;
527 }
528 }
529 }
530 } else if (group) {
531 markgroup = group->name;
532 }
533
534 if (!localmsgid) {
535 const char *tmp = "423 Corrupt article.";
536 printf("%s\r\n", tmp);
537 syslog(LOG_WARNING, ">%s", tmp);
538 if (replyartno) {
539 (void)xsnprintf(s, SIZE_s, "%lu", replyartno);
540 (void)log_unlink(s, 0);
541 }
542 (void)fclose(f);
543 if (xref)
544 free(xref);
545 return;
546 }
547 (void)xsnprintf(s, SIZE_s - 24, "%3d %lu %s article retrieved - ",
548 223 - what, replyartno, localmsgid);
549 free(localmsgid);
550
551 if (what == 0)
552 strcat(s, "request text separately");
553 else if (what == 1)
554 strcat(s, "body follows");
555 else if (what == 2)
556 strcat(s, "head follows");
557 else
558 strcat(s, "text follows");
559 printf("%s\r\n", s);
560 if (debugmode)
561 syslog(LOG_DEBUG, ">%s", s);
562
563 while ((t = getaline(f)) && *t) {
564 if (what & 2) {
565 if (*t == '.')
566 (void)fputc('.', stdout);
567 (void)fputs(t, stdout);
568 (void)fputs("\r\n", stdout);
569 if (ferror(stdout))
570 fatal_write();
571 }
572 }
573 /* Matthias Andree, 2002-03-05:
574 * t == NULL or *t == '\0' makes a difference here.
575 * - t == NULL means we ran into EOF and don't have a body in delaybody mode.
576 * - t != NULL but *t == '\0' means we just read the empty separator line between header and body.
577 */
578
579 if (what == 3)
580 printf("\r\n"); /* empty separator line */
581
582 if (what & 1) {
583 /* delaybody:
584 t == NULL: body is missing, mark for download
585 t != NULL: body is present */
586 if (t == NULL) {
587 if (localartno && markgroup != NULL) {
588 switch (markdownload(markgroup, localartno)) {
589 case 0:
590 printf("\r\n\r\n"
591 "\t[ Leafnode: ]\r\n"
592 "\t[ This message has already been "
593 "marked for download. ]\r\n");
594 break;
595 case 1:
596 printf("\r\n\r\n"
597 "\t[ Leafnode: ]\r\n"
598 "\t[ Message %lu of %s ]\r\n"
599 "\t[ has been marked for download. ]\r\n",
600 localartno, markgroup);
601 break;
602 default:
603 printf("\r\n\r\n"
604 "\t[ Leafnode: ]\r\n"
605 "\t[ Message %lu of %s ]\r\n"
606 "\t[ cannot be marked for download. ]\r\n"
607 "\t[ (Check the server's syslog "
608 "for information). ]\r\n", localartno, markgroup);
609 break;
610 }
611 } else {
612 /* did not figure a group for which to mark this article */
613 syslog(LOG_ERR,
614 "cannot mark for body download: arg=\"%s\" "
615 "localartno=%lu markgroup=\"%s\" group=\"%s\"",
616 arg, localartno, markgroup ? markgroup : "(null)",
617 group ? group->name : "(null)");
618 printf("\r\n\r\n"
619 "\t[ Leafnode: ]\r\n"
620 "\t[ I cannot figure out a newsgroup for which to download ]\r\n"
621 "\t[ this article. Please report this condition to the ]\r\n"
622 "\t[ leafnode mailing list, with leafnode version, the name ]\r\n"
623 "\t[ and the version of your news reader and a log excerpt. ]\r\n");
624 }
625 } else { /* immediate body */
626 while ((t = getaline(f))) {
627 if (*t == '.')
628 (void)fputc('.', stdout);
629 (void)fputs(t, stdout);
630 (void)fputs("\r\n", stdout);
631 if (ferror(stdout))
632 fatal_write();
633 }
634 }
635 }
636 if (what)
637 printf(".\r\n");
638 (void)fclose(f);
639
640 if (xref)
641 free(xref);
642
643 return; /* OF COURSE there were no errors */
644}
645
646
647/* change to group - note no checks on group name */
648static int
649dogroup(const char *arg)
650{
651 struct newsgroup *g;
652
653 g = findgroup(arg);
654 if (g) {
655 group = g; /* global */
656 if (isinteresting(arg)) {
657 if (debugmode)
658 syslog(LOG_DEBUG, "marked group %s interesting", arg);
659 markinterest(arg);
660 }
661 if (!is_pseudogroup(g)) {
662 /* regular news group */
663 if (debugmode)
664 syslog(LOG_DEBUG, ">211 %lu %lu %lu %s group selected",
665 g->last >= g->first ? g->last - g->first + 1 : 0,
666 g->first, g->last, g->name);
667 printf("211 %lu %lu %lu %s group selected\r\n",
668 g->last >= g->first ? g->last - g->first + 1 : 0,
669 g->first, g->last, g->name);
670 } else {
671 /* pseudo news group */
672 if (debugmode)
673 syslog(LOG_DEBUG,
674 ">211 %lu %lu %lu %s group selected (pseudo article)",
675 1lu, g->first, g->first, g->name);
676 printf("211 %lu %lu %lu %s group selected (pseudo article)\r\n",
677 1lu, g->first, g->first, g->name);
678 }
679 artno = g->first;
680 } else {
681 if (debugmode)
682 syslog(LOG_DEBUG, ">411 No such group");
683 printf("411 No such group\r\n");
684 }
685 if (fflush(stdout)) return -1;
686 return 0;
687}
688
689static void
691{
692 fputs("100 Legal commands on THIS server:\r\n"
693 " ARTICLE [<Message-ID>|<Number>]\r\n"
694 " BODY [<Message-ID>|<Number>]\r\n"
695 " DATE\r\n"
696 " GROUP <Newsgroup>\r\n"
697 " HDR <Header> <Message-ID>|<Range>\r\n"
698 " HEAD [<Message-ID>|<Number>]\r\n"
699 " HELP\r\n"
700 " LAST\r\n"
701 " LIST [ACTIVE|NEWSGROUPS] [<Wildmat>]]\r\n"
702 " LIST [ACTIVE.TIMES|EXTENSIONS|OVERVIEW.FMT]\r\n"
703 " LISTGROUP <Newsgroup>\r\n"
704 " MODE READER\r\n"
705 " NEWGROUPS <yymmdd> <hhmmss> [GMT]\r\n"
706 " NEXT\r\n"
707 " POST\r\n"
708 " OVER <Range>\r\n"
709 " SLAVE\r\n"
710 " STAT [<Message-ID>|<Number>]\r\n"
711 " XHDR <Header> <Message-ID>|<Range>\r\n"
712 " XOVER <Range>\r\n"
713 ".\r\n", stdout);
714}
715
716static void
717domove(int by)
718{
719 char *msgid;
720 char s[SIZE_s+1];
721
722 by = (by < 0) ? -1 : 1;
723 if (group) {
724 if (artno) {
725 artno += by;
726 do {
727 sprintf(s, "%lu", artno);
728 msgid = getheader(s, "Message-ID:");
729 if (!msgid)
730 artno += by;
731 } while (msgid == NULL && artno >= group->first && artno <= group->last);
732 if (msgid && (artno > group->last || artno < group->first)) {
733 free(msgid);
734 msgid = NULL;
735 }
736 if (msgid == NULL) {
737 if (by > 0) {
738 artno = group->last;
739 printf("421 There is no next article\r\n");
740 if (debugmode)
741 syslog(LOG_DEBUG, ">421 There is no next article");
742 } else {
743 artno = group->first;
744 printf("422 There is no previous article\r\n");
745 if (debugmode)
746 syslog(LOG_DEBUG, ">422 There is no previous article");
747 }
748 } else {
749 printf("223 %lu %s article retrieved\r\n", artno, msgid);
750 if (debugmode)
751 syslog(LOG_DEBUG, ">223 %lu %s article retrieved",
752 artno, msgid);
753 free(msgid);
754 }
755 } else {
756 printf("420 There is no current article\r\n");
757 if (debugmode)
758 syslog(LOG_DEBUG, ">420 There is no current article");
759 }
760 } else {
761 nogroup();
762 }
763}
764
765static int is_pattern(const char *s) {
766 return s ? strcspn(s, "?*[") < strlen(s) : 1;
767}
768
769/* LIST ACTIVE if what==0,
770 * LIST NEWSGROUPS if what==1
771 * LIST ACTIVE.TIMES if what==2
772 */
773static void
774printlist(const struct newsgroup *g, const int what)
775{
776 switch(what) {
777 case 0:
778 if (is_pseudogroup(g))
779 printf("%s %lu %lu y\r\n", g->name, g->first, g->first);
780 else
781 printf("%s %lu %lu y\r\n", g->name, g->last, g->first);
782 break;
783 case 1:
784 printf("%s\t%s\r\n", g->name, g->desc ? g->desc : "-x-");
785 break;
786 case 2:
787 printf("%s %lu %s\r\n", g->name, (unsigned long)g->age, newsadmin);
788 break;
789 default:
790 abort();
791 break;
792 }
793}
794
795/* LIST ACTIVE if what==0,
796 * LIST NEWSGROUPS if what==1
797 * LIST ACTIVE.TIMES if what==2
798 */
799static void
800list(struct newsgroup *g, const int what, const char *pattern)
801{
802 if (is_pattern(pattern)) {
803 while(g->name) {
804 if (!pattern || !ngmatch(pattern, g->name)) {
805 printlist(g, what);
806 }
807 g++;
808 }
809 } else {
810 /* single group */
811 g = findgroup(pattern);
812 if (g) {
813 printlist(g, what);
814 if (what == 0 && isinteresting(pattern))
815 markinterest(pattern);
816 }
817 }
818}
819
820static void
821dolist(char *arg)
822{
823 if (!strcasecmp(arg, "extensions")) {
824 printf("202 extensions supported follow\r\n"
825 "HDR\r\n"
826 "LISTGROUP\r\n"
827 "OVER\r\n"
828 "XHDR\r\n"
829 "XOVER\r\n"
830 ".\r\n");
831 if (debugmode)
832 syslog(LOG_DEBUG, ">202 extensions supported follow");
833 } else if (!strcasecmp(arg, "overview.fmt")) {
834 printf("215 information follows\r\n"
835 "Subject:\r\n"
836 "From:\r\n"
837 "Date:\r\n"
838 "Message-ID:\r\n"
839 "References:\r\n"
840 "Bytes:\r\n"
841 "Lines:\r\n"
842 "Xref:full\r\n"
843 ".\r\n");
844 if (debugmode)
845 syslog(LOG_DEBUG, ">215 information follows");
846 } else if (!strncasecmp(arg, "active.times", 12)) {
847 if (active) {
848 const char m[] = "215 Note that leafnode will fetch groups on demand.";
849 printf("%s\r\n", m);
850 if (debugmode)
851 syslog(LOG_DEBUG, ">%s", m);
852 list(active, 2, NULL);
853 printf(".\r\n");
854 } else {
855 const char e[] = "503 Active file has not been read.";
856 printf("%s\r\n", e);
857 if (debugmode)
858 syslog(LOG_DEBUG, ">%s", e);
859 }
860 } else {
861 if (!active) {
862 printf("503 Group information file does not exist!\r\n");
863 syslog(LOG_ERR, ">503 Group information file does not exist!");
864 } else if (!*arg || !strncasecmp(arg, "active", 6)) {
865 printf("215 Newsgroups in form \"group high low flags\".\r\n");
866 if (debugmode)
867 syslog(LOG_DEBUG,
868 ">215 Newsgroups in form \"group high low flags\".");
869 if (active) {
870 if (!*arg || strlen(arg) == 6)
871 list(active, 0, NULL);
872 else {
873 while (*arg && (!isspace((unsigned char)*arg)))
874 arg++;
875 while (*arg && isspace((unsigned char)*arg))
876 arg++;
877 list(active, 0, arg);
878 }
879 }
880 printf(".\r\n");
881 } else if (!strncasecmp(arg, "newsgroups", 10)) {
882 printf("215 Descriptions in form \"group description\".\r\n");
883 if (debugmode)
884 syslog(LOG_DEBUG,
885 ">215 Descriptions in form \"group description\".");
886 if (active) {
887 if (strlen(arg) == 10)
888 list(active, 1, NULL);
889 else {
890 while (*arg && (!isspace((unsigned char)*arg)))
891 arg++;
892 while (*arg && isspace((unsigned char)*arg))
893 arg++;
894 list(active, 1, arg);
895 }
896 }
897 printf(".\r\n");
898 } else {
899 printf("503 Syntax error\r\n");
900 if (debugmode)
901 syslog(LOG_DEBUG, ">503 Syntax error");
902 }
903 }
904}
905
906static void
907donewgroups(const char *arg)
908{
909 struct tm timearray;
910 struct tm *ltime;
911 time_t age;
912 time_t now;
913 int year, century;
914 char *l;
915 long a;
916 long b;
917 struct newsgroup *ng;
918
919 now = time(NULL);
920 ltime = localtime(&now);
921 if (ltime == NULL) {
922 syslog(LOG_CRIT, "fatal: localtime returned NULL. abort.");
923 abort();
924 }
925 year = ltime->tm_year % 100;
926 century = ltime->tm_year / 100; /* 0 for 1900-1999, 1 for 2000-2099 etc */
927
928 memset(&timearray, 0, sizeof(timearray));
929 l = NULL;
930 a = (int)strtol(arg, &l, 10);
931 /* NEWGROUPS may have the form YYMMDD or YYYYMMDD.
932 Distinguish between the two */
933 b = a / 10000;
934 if (b < 100) {
935 /* YYMMDD */
936 if (b <= year)
937 timearray.tm_year = (int)(b + (century * 100));
938 else
939 timearray.tm_year = (int)(b + (century - 1) * 100);
940 } else if (b < 1000) {
941 /* YYYMMDD - happens with buggy newsreaders */
942 /* In these readers, YYY=100 is equivalent to YY=00 or YYYY=2000 */
943 syslog(LOG_NOTICE,
944 "NEWGROUPS year is %ld: please update your newsreader", b);
945 timearray.tm_year = (int)b;
946 } else {
947 /* YYYYMMDD */
948 timearray.tm_year = (int)b - 1900;
949 }
950 timearray.tm_mon = (int)(a % 10000 / 100) - 1;
951 timearray.tm_mday = (int)(a % 100);
952 while (*l && isspace((unsigned char)*l))
953 l++;
954 a = strtol(l, &l, 10); /* we don't care about the rest of the line */
955 while (*l && isspace((unsigned char)*l))
956 l++;
957 timearray.tm_hour = (int)(a / 10000);
958 timearray.tm_min = (int)(a % 10000 / 100);
959 timearray.tm_sec = (int)(a % 100);
960 /* mktime() shall guess correct value of tm_isdst (0 or 1) */
961 timearray.tm_isdst = -1;
962 if (0 == strncasecmp(l, "gmt", 3))
963 age = timegm(&timearray);
964 else
965 age = mktime(&timearray);
966
967 printf("231 List of new newsgroups since %ld follows\r\n", (long)age);
968 if (debugmode)
969 syslog(LOG_DEBUG, "231 List of new newsgroups since %ld follows",
970 (long)age);
971
972 ng = active;
973 if (ng != NULL)
974 while (ng->name) {
975 if (ng->age >= age)
976 printf("%s %lu %lu y\r\n", ng->name, ng->last, ng->first);
977 ng++;
978 }
979 printf(".\r\n");
980}
981
982/* next bit is copied from INN 1.4 and modified ("broken") by agulbra
983
984 mail to Rich $alz <rsalz@uunet.uu.net> bounced */
985
986/* Scale time back a bit, for shorter Message-ID's. */
987#define OFFSET (time_t)1026380000L
988
989/*@observer@*/ static char *
991{
992 static char ALPHABET[] = "0123456789abcdefghijklmnopqrstuv";
993
994 static char buff[1000];
995 static time_t then;
996 static unsigned int fudge;
997 time_t now;
998 char *p;
999 unsigned long n;
1000
1001 now = time(NULL); /* might be 0, in which case fudge
1002 will almost fix it */
1003 if (now < OFFSET) {
1005 "your system clock cannot be right. abort.");
1006 abort();
1007 }
1008 if (now != then)
1009 fudge = 0;
1010 else
1011 fudge++;
1012 then = now;
1013
1014 p = buff;
1015 *p++ = '<';
1016 n = (unsigned long)now - OFFSET;
1017 while (n) {
1018 *p++ = ALPHABET[(int)(n & 31)];
1019 n >>= 5;
1020 }
1021 *p++ = '-';
1022 n = fudge * 32768 + (int)getpid();
1023 while (n) {
1024 *p++ = ALPHABET[(int)(n & 31)];
1025 n >>= 5;
1026 }
1027 sprintf(p, ".ln1@%-.256s>", fqdn);
1028 return buff;
1029}
1030/* the end of what I stole from rsalz and then mangled */
1031
1032
1033static int
1035{
1036 char *line;
1037 int havefrom = 0;
1038 int havepath = 0;
1039 int havedate = 0;
1040 int havenewsgroups = 0;
1041 int havemessageid = 0;
1042 int havesubject = 0;
1043 int err = 0, ferr = 0;
1044 /*@observer@*/ const char *ferrstr = NULL;
1045 int o;
1046 size_t len;
1047 FILE *out;
1048 char outname[1000];
1049 static int postingno; /* starts as 0 */
1050 char *suggmid;
1051 /*@observer@*/ const char *appendheader = NULL;
1052
1053 if (getenv("LN_REJECT_POST_PRE")) {
1054 printf("400 Posting rejected - debug variable LN_REJECT_POST_PRE exists\r\n");
1055 return 0;
1056 }
1057
1058 do {
1059 (void)xsnprintf(outname, sizeof(outname), "%s/out.going/%d-%d-%d",
1060 spooldir, (int)getpid(), (int)time(NULL), ++postingno);
1061
1062 o = open(outname, O_WRONLY | O_EXCL | O_CREAT, 0244);
1063 if (o < 0 && errno != EEXIST) {
1064 char *errmsg = strerror(errno);
1065 printf("441 Unable to open spool file %s: %s\r\n", outname, errmsg);
1066 syslog(LOG_ERR, ">441 Unable to open spool file %s: %s", outname,
1067 errmsg);
1068 return 0;
1069 }
1070 } while (o < 0);
1071
1072 out = fdopen(o, "w");
1073 if (out == NULL) {
1074 char *errmsg = strerror(errno);
1075 printf("441 Unable to fdopen(%d): %s\r\n", o, errmsg);
1076 syslog(LOG_ERR, ">441 Unable to fdopen(%d): %s", o, errmsg);
1077 return 0;
1078 }
1079
1080 suggmid = generateMessageID();
1081 printf("340 Ok, recommended ID %s\r\n", suggmid);
1082 if (debugmode)
1083 syslog(LOG_DEBUG, ">340 Go ahead.");
1084 if (fflush(stdout)) return -1;
1085
1086 /* get headers */
1087 do {
1088 debug = 0;
1089 line = getaline(stdin);
1090 if (!line) { /* timeout */
1091 unlink(outname);
1092 exit(0);
1093 }
1094
1095 if (0 == strcmp(line, ".")) {
1096 ferr = TRUE;
1097 ferrstr = "No body found.";
1098 break;
1099 }
1100 debug = debugmode;
1101
1102 if (!strncasecmp(line, "From: ", 6)) {
1103 if (havefrom)
1104 ferr = TRUE, ferrstr = "Duplicate From: header";
1105 else
1106 havefrom = TRUE;
1107 }
1108 if (!strncasecmp(line, "Path: ", 6)) {
1109 if (havepath)
1110 ferr = TRUE, ferrstr = "Duplicate Path: header";
1111 else
1112 havepath = TRUE;
1113 }
1114 if (!strncasecmp(line, "Message-ID: ", 12)) {
1115 if (havemessageid)
1116 ferr = TRUE, ferrstr = "Duplicate Message-ID: header";
1117 else {
1118 char *vec[2]; /* RATS: ignore */
1119 int rc;
1120
1121 havemessageid = TRUE;
1122 if (debugmode)
1123 syslog(LOG_DEBUG, "debug header: %s", line);
1124 if (2 != (rc = ln_pcre_extract((unsigned char *)line,
1125 (unsigned char *)"Message-ID:\\s+<(?:[^>]+)@([^@>]+)>\\s*$",
1126 vec, 2))
1127 || vec[1] == NULL) {
1128 ferr = TRUE, ferrstr = "Malformatted Message-ID: header.";
1129 } else if (!strchr(vec[1], '.')) {
1130 ferr = TRUE, ferrstr = "Message-ID: header does not have domain name part.";
1131 } else if (!is_validfqdn(vec[1])) {
1132 ferr = TRUE, ferrstr = "Message-ID: header contains invalid domain name part.";
1133 }
1135 }
1136 }
1137 if (!strncasecmp(line, "Subject: ", 9)) {
1138 if (havesubject)
1139 ferr = TRUE, ferrstr = "Duplicate Subject: header";
1140 else
1141 havesubject = TRUE;
1142 }
1143 if (!strncasecmp(line, "Newsgroups: ", 12)) {
1144 if (havenewsgroups)
1145 ferr = TRUE, ferrstr = "Duplicate Newsgroups: header";
1146 else
1147 havenewsgroups = TRUE;
1148 }
1149 if (!strncasecmp(line, "Date: ", 6)) {
1150 if (havedate)
1151 ferr = TRUE, ferrstr = "Duplicate Date: header";
1152 else
1153 havedate = TRUE;
1154 }
1155
1156 len = strlen(line);
1157
1158 /* check for illegal 8bit/control stuff in header */
1159 {
1160 char *t;
1161 for (t = line; *t; t++) {
1162 if (*t & 0x80) {
1163 if (allow_8bit_headers) {
1164 appendheader = "X-Leafnode-Warning: administrator "
1165 "allowed illegal use of 8-bit data in header.\r\n";
1166 } else {
1167 ferr = TRUE;
1168 ferrstr = "Illegal use of 8-bit data in header.";
1169 break;
1170 }
1171 }
1172 if ((unsigned char)*t < (unsigned char)0x20u && *t != '\t') {
1173 ferr = TRUE;
1174 ferrstr = "Illegal use of control data in header.";
1175 break;
1176 }
1177 }
1178 }
1179
1180 /* checks for non-folded lines */
1181 if (*line && *line != ' ' && *line != '\t') {
1182 if (strchr(line, ':') == NULL) {
1183 /* must have a colon */
1184 ferr = TRUE;
1185 ferrstr = "Header tag not found.";
1186 } else if (strcspn(line, " \t") < strcspn(line, ":")) {
1187 /* must not have space before colon */
1188 ferr = TRUE;
1189 ferrstr = "Whitespace in header tag is not allowed.";
1190 }
1191 }
1192
1193 if (len) {
1194 if (fwrite(line, 1, len, out) != (size_t) len)
1195 err = 1;
1196 } else {
1197 if (!havepath) {
1198 if (fputs("Path: ", out) == EOF)
1199 err = 1;
1200 if (fputs(fqdn, out) == EOF)
1201 err = 1;
1202 if (fputs("!not-for-mail\r\n", out) == EOF)
1203 err = 1;
1204 }
1205 if (!havedate) {
1206 const char *l = rfctime();
1207 if (fputs("Date: ", out) == EOF)
1208 err = 1;
1209 if (fputs(l, out) == EOF)
1210 err = 1;
1211 if (fputs("\r\n", out) == EOF)
1212 err = 1;
1213 }
1214 if (!havemessageid) {
1215 if (fputs("Message-ID: ", out) == EOF)
1216 err = 1;
1217 if (fputs(suggmid, out) == EOF)
1218 err = 1;
1219 if (fputs("\r\n", out) == EOF)
1220 err = 1;
1221 }
1222 if (appendheader) {
1223 if (fputs(appendheader, out) == EOF)
1224 err = 1;
1225 }
1226 }
1227 if (fputs("\r\n", out) == EOF)
1228 err = 1;
1229 } while (*line);
1230
1231 /* get bodies */
1232 if (strcmp(line, "."))
1233 do {
1234 debug = 0;
1235 line = getaline(stdin);
1236 debug = debugmode;
1237 if (!line) {
1238 (void)unlink(outname);
1239 exit(1);
1240 }
1241
1242 len = strlen(line);
1243 if (line[0] == '.') {
1244 if (len > 1) {
1245 if (fputs(line + 1, out) == EOF)
1246 err = 1;
1247 if (fputs("\r\n", out) == EOF)
1248 err = 1;
1249 }
1250 } else {
1251 if (fputs(line, out) == EOF)
1252 err = 1;
1253 if (fputs("\r\n", out) == EOF)
1254 err = 1;
1255 }
1256 } while (line[0] != '.' || line[1] != '\0');
1257
1258 if (fflush(out))
1259 err = 1;
1260
1261 if (fsync(fileno(out)))
1262 err = 1;
1263
1264 if (fclose(out))
1265 err = 1;
1266
1267 if (!havenewsgroups)
1268 ferrstr = "Missing Newsgroups: header";
1269 if (!havesubject)
1270 ferrstr = "Missing Subject: header";
1271 if (!havefrom)
1272 ferrstr = "Missing From: header";
1273
1274 if (getenv("LN_REJECT_POST_POST"))
1275 ferr = 1;
1276
1277 if (havefrom && havesubject && havenewsgroups && !ferr) {
1278 if (!err && 0 == chmod(outname, 0644)) {
1279 printf("240 Article posted, now be patient\r\n");
1280 if (debugmode)
1281 syslog(LOG_DEBUG, ">240 Article posted, now be patient");
1282 return 0;
1283 } else {
1284 (void)unlink(outname);
1285 printf("441 I/O error, article not posted\r\n");
1286 syslog(LOG_INFO, ">441 I/O error, article not posted");
1287 return 0;
1288 }
1289 }
1290
1291 (void)unlink(outname);
1292
1293 if (getenv("LN_REJECT_POST_POST")) {
1294 printf("400 Posting rejected - debug variable LN_REJECT_POST_POST exists\r\n");
1295 syslog(LOG_INFO, ">400 Posting rejected - debug variable LN_REJECT_POST_POST exists\r\n");
1296 return 0;
1297 }
1298
1299 if (ferrstr) {
1300 printf("441 Post rejected, formatting error: %s\r\n", ferrstr);
1301 syslog(LOG_INFO, ">441 Post rejected, formatting error: %s", ferrstr);
1302 } else {
1303 printf("441 Post rejected, formatting error\r\n");
1304 syslog(LOG_INFO, ">441 Post rejected, formatting error");
1305 }
1306
1307 return 0;
1308}
1309
1310static void invalidrange(void)
1311{
1312 printf("420 No articles in specified range.\r\n");
1313 if (debugmode)
1314 syslog(LOG_DEBUG, ">420 No articles in specified range.");
1315}
1316
1317/* check if a - b is a valid range for the current group.
1318 * If it's not, print a 420 error and return 0.
1319 * If it is, do not print anything and return 1.
1320 * group must not be NULL!
1321 */
1322static int checkrange(const struct newsgroup *g,
1323 unsigned long a, unsigned long b)
1324{
1325 if ((a > b) || (g->first <= g->last
1326 ? (a > g->last) || (b < g->first)
1327 : (a > g->first) || (b < g->first))) {
1328 invalidrange();
1329 return 0;
1330 }
1331 return 1;
1332}
1333
1334
1335
1336static void
1337doxhdr(char *arg)
1338{
1339 static const char *h[] = { "Subject", "From", "Date", "Message-ID",
1340 "References", "Bytes", "Lines"
1341 };
1342
1343 int n = 7;
1344 size_t i;
1345 char *l;
1346 char *buf;
1347 unsigned long a, b = 0, c;
1348 char s[SIZE_s+1];
1349
1350 if (!arg || !*arg) {
1351 if (debugmode)
1352 syslog(LOG_DEBUG,
1353 ">502 Usage: HDR header first[-last] or "
1354 "HDR header message-id");
1355 printf("502 Usage: HDR header first[-last] or "
1356 "HDR header message-id\r\n");
1357 return;
1358 }
1359
1360 /* go figure header */
1361 l = arg;
1362 while (l && *l && !isspace((unsigned char)*l))
1363 l++;
1364 if (l && *l)
1365 *l++ = '\0';
1366 SKIPLWS(l);
1367
1368 buf = critmalloc((i = strlen(arg)) + 2, "doxhdr");
1369 strcpy(buf, arg); /* RATS: ignore */
1370 if (buf[i - 1] != ':')
1371 strcpy(buf + i, ":");
1372
1373 if (l && *l == '<') { /* handle message-id form (well) */
1374 FILE *f;
1375 char *m = critstrdup(l, "doxhdr");
1376 f = fopenart(l);
1377 if (!f) {
1378 printf("430 No such article\r\n");
1379 if (debugmode)
1380 syslog(LOG_DEBUG, ">430 No such article");
1381 free(buf);
1382 free(m);
1383 return;
1384 }
1385 l = fgetheader(f, buf);
1386 if (debugmode) {
1387 syslog(LOG_DEBUG, ">221 %s header of %s follows:", buf, m);
1388 if (l) syslog(LOG_DEBUG, ">%s %s", m, l);
1389 syslog(LOG_DEBUG, ">.");
1390 }
1391 printf("221 %s header of %s follows:\r\n", buf, m);
1392 if (l) printf("%s %s\r\n", m, l);
1393 printf(".\r\n");
1394 free(m);
1395 (void)fclose(f);
1396 free(buf);
1397 if (l) free(l);
1398 return;
1399 }
1400
1401 if (!group) {
1402 nogroup();
1403 free(buf);
1404 return;
1405 }
1406
1408
1409 a = group->first;
1410 b = group->last;
1411 if (b < a) b = a;
1412 if (!parserange(l, &a, &b)) {
1413 if (debugmode)
1414 syslog(LOG_DEBUG, ">502 Usage: XHDR header first[-last] "
1415 "or XHDR header message-id");
1416 printf("502 Usage: XHDR header first[-last] "
1417 "or XHDR header message-id\r\n");
1418 free(buf);
1419 return;
1420 }
1421
1422 if (!checkrange(group, a, b)) {
1423 free(buf);
1424 return;
1425 }
1426
1427 if (!is_pseudogroup(group)) {
1429 if (getxover())
1430 xovergroup = group;
1431 }
1432
1433 if (is_pseudogroup(group)) {
1434 do {
1435 n--;
1436 } while (n >= 0 && strncasecmp(h[n], buf, strlen(h[n])) != 0);
1437 if ((n < 0) && strncasecmp("Newsgroups", buf, 10)) {
1438 printf("430 No such header: %s\r\n", buf);
1439 if (debugmode)
1440 syslog(LOG_DEBUG, ">430 No such header: %s", buf);
1441 free(buf);
1442 return;
1443 }
1444 if (debugmode)
1445 syslog(LOG_DEBUG,
1446 ">221 First line of %s pseudo-header follows:", buf);
1447 printf("221 First line of %s pseudo-header follows:\r\n", buf);
1448 if (a <= b && a <= group->first && b >= group->last) {
1449 printf("%lu ", group->first);
1450 if (n == 0) /* Subject: */
1451 printf("Leafnode placeholder for group %s\r\n", group->name);
1452 else if (n == 1) /* From: */
1453 printf("Leafnode <%s>\r\n", newsadmin);
1454 else if (n == 2) /* Date: */
1455 printf("%s\r\n", rfctime());
1456 else if (n == 3) /* Message-ID: */
1457 printf("<leafnode:placeholder:%s@%s>\r\n", group->name, fqdn);
1458 else if (n == 4) /* References */
1459 printf("(none)\r\n");
1460 else if (n == 5) /* Bytes */
1461 printf("%d\r\n", 1024); /* FIXME: just a guess */
1462 else if (n == 6) /* Lines */
1463 printf("%d\r\n", 22); /* FIXME: from buildpseudoart() */
1464 else /* Newsgroups */
1465 printf("%s\r\n", group->name);
1466 }
1467 printf(".\r\n");
1468 free(buf);
1469 return;
1470 }
1471
1472 do {
1473 n--;
1474 } while (n > -1 && strncasecmp(buf, h[n], strlen(h[n])));
1475
1476 if (a < group->first)
1477 a = group->first;
1478
1479 if (b > group->last)
1480 b = group->last;
1481
1482 if (n >= 0) {
1483 if (debugmode)
1484 syslog(LOG_DEBUG, "221 %s header (from overview) "
1485 "for postings %lu-%lu:", h[n], a, b);
1486 printf("221 %s header (from overview) for postings %lu-%lu:\r\n",
1487 h[n], a, b);
1488
1489 s[sizeof(s)-1] = '\0';
1490 for (c = a; c <= b; c++) {
1491 if (xoverinfo &&
1492 c >= xfirst && c <= xlast && xoverinfo[c - xfirst].text) {
1493 char *l2 = xoverinfo[c - xfirst].text;
1494 int d;
1495 for (d = 0; l2 && d <= n; d++)
1496 l2 = strchr(l2 + 1, '\t');
1497 if (l2) {
1498 char *p;
1499 (void)strlcpy(s, ++l2, sizeof(s));
1500 p = strchr(s, '\t');
1501 if (p)
1502 *p = '\0';
1503 }
1504 if (l2 && *l2) printf("%lu %s\r\n", c, s);
1505 }
1506 }
1507 } else {
1508 if (debugmode)
1509 syslog(LOG_DEBUG, ">221 %s header (from article files) "
1510 "for postings %lu-%lu:", buf, a, b);
1511 printf("221 %s header (from article files) for postings %lu-%lu:\r\n",
1512 buf, a, b);
1513 for (c = a; c <= b; c++) {
1514 sprintf(s, "%lu", c);
1515 l = getheader(s, buf);
1516 if (l) {
1517 printf("%lu %s\r\n", c, l); /* (l && *l) ? l : "(none)" ); */
1518 free(l);
1519 }
1520 }
1521 }
1522
1523 free(buf);
1524 printf(".\r\n");
1525 return;
1526}
1527
1528static void
1529doxover(char *arg)
1530{
1531 unsigned long a, b, art;
1532
1533 if (!group) {
1534 nogroup();
1535 return;
1536 }
1537
1539 a = group->first;
1540 b = group->last;
1541 if (b < a) b = a;
1542
1543 if (!arg || !*arg)
1544 a = b = artno;
1545 else if (!parserange(arg, &a, &b)) {
1546 printf("502 Usage: OVER first[-[last]]\r\n");
1547 if (debugmode)
1548 syslog(LOG_DEBUG, ">502 Usage: OVER first[-[last]]");
1549 return;
1550 }
1551
1552 if (!checkrange(group, a, b))
1553 return;
1554
1555 if (!is_pseudogroup(group)) {
1557 if (getxover()) xovergroup = group;
1558
1559 if (NULL == xoverinfo) {
1560 invalidrange();
1561 return;
1562 }
1563 if (b > xlast)
1564 b = xlast;
1565 if (a < xfirst)
1566 a = xfirst;
1567
1568 printf("224 Overview information for postings %lu-%lu:\r\n", a, b);
1569 if (debugmode)
1570 syslog(LOG_DEBUG, ">224 Overview information for postings %lu-%lu:",
1571 a, b);
1572 for (art = a; art <= b; art++) {
1573 if (xoverinfo[art - xfirst].text)
1574 printf("%s\r\n", xoverinfo[art - xfirst].text);
1575 }
1576 printf(".\r\n");
1577 } else {
1578 if ((a > b) || (group->first <= group->last
1579 ? (a > group->last) || (b < group->first)
1580 : (a > group->first) || (b < group->first))) {
1581 printf("420 No articles in specified range.\r\n");
1582 if (debugmode)
1583 syslog(LOG_DEBUG, ">420 No articles in specified range.");
1584 return;
1585 }
1586
1587 printf("224 Overview information (pseudo) for postings %lu-%lu:\r\n",
1588 group->first, group->first);
1589 if (debugmode)
1590 syslog(LOG_DEBUG, ">224 Overview information (pseudo) for "
1591 "postings %lu-%lu:", group->first, group->first);
1592 printf("%lu\t"
1593 "Leafnode placeholder for group %s\t"
1594 "%s (Leafnode)\t%s\t"
1595 "<leafnode:placeholder:%s@%s>\t\t1000\t40\r\n", group->first,
1597 printf(".\r\n");
1598 if (debugmode)
1599 syslog(LOG_DEBUG, ">%lu\tLeafnode placeholder for group %s\t"
1600 "%s (Leafnode)\t%s\t<leafnode:placeholder:%s@%s>\t\t1000\t40",
1602 }
1603}
1604
1605static void
1606dolistgroup(const char *arg)
1607{
1608 unsigned long art;
1609
1610 if (arg && *(arg)) {
1611 struct newsgroup *g;
1612 g = findgroup(arg);
1613 if (g) {
1614 group = g;
1615 artno = g->first;
1616 } else {
1617 printf("411 No such group: %s\r\n", arg);
1618 if (debugmode)
1619 syslog(LOG_DEBUG, ">411 No such group: %s", arg);
1620 return;
1621 }
1622 }
1623
1624 if (!group) {
1625 nogroup();
1626 return;
1627 }
1628
1629 /* group = g; */
1631 if ((NULL == xovergroup || xovergroup != group)
1632 && chdirgroup(group->name, FALSE))
1633 if (getxover()) xovergroup = group;
1634
1635 if (is_pseudogroup(group)) {
1636 printf("211 Article list for %s follows (pseudo)\r\n", group->name);
1637 if (debugmode)
1638 syslog(LOG_DEBUG,
1639 ">211 Article list for %s follows (pseudo)", group->name);
1640 printf("%lu\r\n", group->first ? group->first : 1);
1641 } else {
1642 printf("211 Article list for %s follows\r\n", group->name);
1643 if (debugmode)
1644 syslog(LOG_DEBUG, ">211 Article list for %s follows", group->name);
1645 if (xoverinfo)
1646 for (art = xfirst; art <= xlast; art++) {
1647 if (xoverinfo[art - xfirst].text)
1648 printf("%lu\r\n", art);
1649 }
1650 }
1651 printf(".\r\n");
1652}
1653
1654static void
1656{
1657 char *arg;
1658 int n;
1659 size_t size;
1660
1662
1663 while ((cmd = mgetaline(stdin))) {
1664 if (debug == 1)
1665 syslog(LOG_DEBUG, "<%s", cmd);
1666
1667 size = strlen(cmd);
1668 if (size == 0)
1669 continue; /* ignore */
1670 if (size > MAXLINELENGTH || (long)size > (long)INT_MAX) {
1671 /* ignore attempts at buffer overflow */
1672 if (debugmode)
1673 syslog(LOG_DEBUG, ">500 Dazed and confused");
1674 printf("500 Dazed and confused\r\n");
1675 continue;
1676 }
1677
1678 /* parse command line */
1679 n = 0;
1680 while (isalpha((unsigned char)cmd[n]))
1681 n++;
1682 while (isspace((unsigned char)cmd[n]))
1683 cmd[n++] = '\0';
1684
1685 arg = cmd + n;
1686
1687 while (cmd[n])
1688 n++;
1689 n--;
1690 while (n >= 0 && isspace((unsigned char)cmd[n]))
1691 cmd[n--] = '\0';
1692
1693 if (!strcasecmp(cmd, "quit")) {
1694 if (debugmode)
1695 syslog(LOG_DEBUG, ">205 Always happy to serve!");
1696 printf("205 Always happy to serve!\r\n");
1697 return;
1698 }
1699 rereadactive();
1700 if (!strcasecmp(cmd, "article")) {
1701 doarticle(arg, 3);
1702 } else if (!strcasecmp(cmd, "head")) {
1703 doarticle(arg, 2);
1704 } else if (!strcasecmp(cmd, "body")) {
1705 doarticle(arg, 1);
1706 } else if (!strcasecmp(cmd, "stat")) {
1707 doarticle(arg, 0);
1708 } else if (!strcasecmp(cmd, "help")) {
1709 dohelp();
1710 } else if (!strcasecmp(cmd, "last")) {
1711 domove(-1);
1712 } else if (!strcasecmp(cmd, "next")) {
1713 domove(1);
1714 } else if (!strcasecmp(cmd, "list")) {
1715 dolist(arg);
1716 } else if (!strcasecmp(cmd, "date")) {
1717 dodate();
1718 } else if (!strcasecmp(cmd, "mode")) {
1719 if (debugmode)
1720 syslog(LOG_DEBUG, ">200 Leafnode %s, pleased to meet you!",
1721 version);
1722 printf("200 Leafnode %s, pleased to meet you!\r\n", version);
1723 } else if (!strcasecmp(cmd, "newgroups")) {
1724 donewgroups(arg);
1725 } else if (!strcasecmp(cmd, "newnews")) {
1726 if (debugmode)
1727 syslog(LOG_DEBUG,
1728 ">500 NEWNEWS is meaningless for this server");
1729 printf("500 NEWNEWS is meaningless for this server\r\n");
1730 } else if (!strcasecmp(cmd, "post")) {
1731 if (dopost()) break;
1732 } else if (!strcasecmp(cmd, "slave")) {
1733 if (debugmode)
1734 syslog(LOG_DEBUG, ">202 Cool - I always wanted a slave");
1735 printf("202 Cool - I always wanted a slave\r\n");
1736 } else if (!strcasecmp(cmd, "xhdr")) {
1737 doxhdr(arg);
1738 } else if (!strcasecmp(cmd, "hdr")) {
1739 doxhdr(arg);
1740 } else if (!strcasecmp(cmd, "xover")) {
1741 doxover(arg);
1742 } else if (!strcasecmp(cmd, "over")) {
1743 doxover(arg);
1744 } else if (!strcasecmp(cmd, "listgroup")) {
1745 dolistgroup(arg);
1746 } else if (!strcasecmp(cmd, "group")) {
1747 if (dogroup(arg)) break;
1748 } else {
1749 if (debugmode)
1750 syslog(LOG_DEBUG, ">500 Unknown command");
1751 printf("500 Unknown command\r\n");
1752 }
1753 if (ferror(stdout) || fflush(stdout)) {
1754 syslog(LOG_ERR, "Cannot write to client: %s", strerror(errno));
1755 break;
1756 }
1757 }
1758 if (debugmode)
1759 syslog(LOG_DEBUG, "Client timeout, disconnecting.");
1760
1761 /* There was once a 400 error message here. It confused broken
1762 * clients, most notably, tin.
1763 * Future NNTP drafts command that we don't send stuff back on
1764 * timeout, so we anticipate these. */
1765}
1766
1767int
1768main(int argc, char **argv)
1769{
1770 socklen_t fodder;
1771 char peername[256]; /* RATS: ignore */
1772#ifdef HAVE_IPV6
1773 char *st;
1774 int h_err;
1775#define ADDRLEN INET6_ADDRSTRLEN
1776 union sockaddr_union su;
1777#else
1778 struct hostent *he;
1779#ifdef INET_ADDRSTRLEN
1780#define ADDRLEN INET_ADDRSTRLEN
1781#else
1782#define ADDRLEN 16
1783#endif
1784 struct sockaddr_in sa;
1785#endif
1786 char peerip[ADDRLEN]; /* RATS: ignore */
1787 char ownip[ADDRLEN]; /* RATS: ignore */
1788 char origfqdn[FQDNLEN + 1]; /* RATS: ignore */
1789
1790 ln_log_use_console(0); /* disable console logging */
1791 (void)argc; /* quiet compiler warning */
1792 myopenlog("leafnode");
1793
1794 /* this gets the actual hostname */
1795 if (!initvars(argv[0]))
1796 exit(1);
1797
1798 artno = 0;
1799 verbose = 0;
1800 (void)umask(2);
1801
1802 /* this reads the host name from the config file */
1803 if (!readconfig(1)) {
1804 const char *m = "503 Unable to read configuration file, exiting; the server's syslog should have more information.";
1805 printf("%s\r\n", m);
1806 syslog(LOG_ERR, "%s", m);
1807 exit(1);
1808 }
1809 freeservers();
1810
1811 strcpy(origfqdn, fqdn); /* same size buffer */ /* RATS: ignore */
1812
1813 /* get own name */
1814#ifdef HAVE_IPV6
1815 fodder = sizeof(union sockaddr_union);
1816 if (0 == getsockname(0, (struct sockaddr *)&su, &fodder)) {
1817 if (su.sin.sin_family == AF_INET6)
1818 inet_ntop(AF_INET6, &su.sin6.sin6_addr, ownip, sizeof(ownip));
1819 else
1820 inet_ntop(AF_INET, &su.sin.sin_addr, ownip, sizeof(ownip));
1821
1822 if ((st = masock_sa2name((struct sockaddr *)&su, &h_err))) {
1823 xstrlcpy(fqdn, st, sizeof(fqdn));
1824 free(st);
1825 }
1826 }
1827#else
1828 fodder = sizeof(struct sockaddr_in);
1829 if (0 == getsockname(0, (struct sockaddr *)&sa, &fodder)) {
1830 he = gethostbyaddr((char *)&sa.sin_addr.s_addr,
1831 sizeof(sa.sin_addr.s_addr), AF_INET);
1832 *fqdn = '\0';
1833 (void)xstrlcpy(fqdn,
1834 he && he->h_name ? he->h_name : inet_ntoa(sa.sin_addr),
1835 sizeof(fqdn));
1836 strcpy(ownip, inet_ntoa(sa.sin_addr));
1837 }
1838#endif
1839 else {
1840 strcpy(ownip, "no IP");
1841 }
1842
1843 /* get remote name */
1844#ifdef HAVE_IPV6
1845 fodder = sizeof(union sockaddr_union);
1846 if (0 == getpeername(0, (struct sockaddr *)&su, &fodder)) {
1847 if (su.sa.sa_family == AF_INET6)
1848 inet_ntop(AF_INET6, &su.sin6.sin6_addr, peername, sizeof(peername));
1849 else
1850 inet_ntop(AF_INET, &su.sin.sin_addr, peername, sizeof(peername));
1851
1852 strcpy(peerip, peername);
1853
1854 if ((st = masock_sa2name((struct sockaddr *)&su, &h_err))) {
1855 xstrlcpy(peername, st, sizeof(peername));
1856 free(st);
1857 }
1858
1859 }
1860#else
1861 fodder = sizeof(struct sockaddr_in);
1862 if (0 == getpeername(0, (struct sockaddr *)&sa, &fodder)) {
1863 he = gethostbyaddr((char *)&sa.sin_addr.s_addr,
1864 sizeof(sa.sin_addr.s_addr), AF_INET);
1865 (void)xstrlcpy(peername,
1866 he && he->h_name ? he->h_name : inet_ntoa(sa.sin_addr),
1867 sizeof(peername));
1868 strcpy(peerip, inet_ntoa(sa.sin_addr));
1869 }
1870#endif
1871 else {
1872 if (errno == ENOTSOCK) {
1873 strcpy(peername, "(local file)");
1874 strcpy(peerip, "no IP");
1875 } else {
1876 strcpy(peerip, "unknown");
1877 strcpy(peername, "(unknown)");
1878 }
1879 }
1880
1881 if (allowstrangers == 0 && checkpeerlocal(0) != 1) {
1882 unsigned int i = 5;
1883
1884 syslog(LOG_NOTICE, "Refusing connect from %s (%s) to %s (%s) (my fqdn: %s), outside the local networks. (Check config.example.)",
1885 peername, peerip, fqdn, ownip, origfqdn);
1886 while (i)
1887 i = sleep(i);
1888 printf("502 Remote access denied.\n");
1889 exit(0);
1890 }
1891
1892 syslog(LOG_INFO, "connect from %s (%s) to %s (%s) (my fqdn: %s)",
1893 peername, peerip, fqdn, ownip, origfqdn);
1894
1895 printf("200 Leafnode NNTP Daemon, version %s "
1896 "running at %s (my fqdn: %s)\r\n",
1897 version, fqdn, origfqdn);
1898 if (fflush(stdout)) exit(0);
1899
1900 strcpy(fqdn, origfqdn);
1901
1902 rereadactive();
1903
1904 parser();
1905
1906 (void)fflush(stdout);
1908 freexover();
1909 freeconfig();
1910 sleep(1); /* protect against process ID induced file name collisions */
1911 exit(0);
1912}
void readactive(void)
Definition: activutil.c:373
void fakeactive(void)
Definition: activutil.c:515
void freeactive(struct newsgroup *act)
Definition: activutil.c:351
struct newsgroup * findgroup(const char *name)
Definition: activutil.c:260
size_t activesize
Definition: activutil.c:39
struct newsgroup * active
Definition: activutil.c:40
char * fgetheader(FILE *f, const char *header)
Definition: artutil.c:27
char * getheader(const char *filename, const char *header)
Definition: artutil.c:62
int checkpeerlocal(int sock)
int allowstrangers
Definition: configutil.c:87
char * newsadmin
Definition: configutil.c:91
void freeservers(void)
Definition: configutil.c:695
int debugmode
Definition: configutil.c:67
int timeout_long
Definition: configutil.c:76
int timeout_short
Definition: configutil.c:77
int timeout_client
Definition: configutil.c:79
int readconfig(int logtostderr)
Definition: configutil.c:208
void freeconfig(void)
Definition: configutil.c:722
int allow_8bit_headers
Definition: configutil.c:90
char * critstrdup(const char *source, const char *message)
Definition: critmem.c:92
char * critmalloc(size_t size, const char *message)
Definition: critmem.c:61
static time_t now
Definition: fetchnews.c:58
static int age(const char *date)
Definition: fetchnews.c:184
char * getaline(FILE *f)
Definition: getaline.c:84
char * mgetaline(FILE *f)
Definition: mgetaline.c:51
#define SKIPLWS(p)
Definition: leafnode.h:398
void freexover(void)
Definition: xoverutil.c:396
int safe_mkstemp(char *)
Definition: lockfile.c:163
const char * spooldir
unsigned long xlast
Definition: miscutil.c:64
int log_unlink(const char *f, int ignore_enoent)
Definition: log_unlink.c:13
void myopenlog(const char *ident)
Definition: syslog.c:24
void ln_pcre_extract_free(char **vec, int count)
Definition: pcre_extract.c:108
int initvars(char *progname)
Definition: miscutil.c:143
unsigned long xfirst
Definition: miscutil.c:64
void mgetaline_settimeout(unsigned int)
Definition: mgetaline.c:69
#define SIZE_s
Definition: leafnode.h:287
const char * rfctime(void)
Definition: miscutil.c:752
time_t timegm(struct tm *tm)
Definition: timegm.c:50
char fqdn[255+1]
Definition: miscutil.c:58
#define BODY_DOWNLOAD_LIMIT
Definition: leafnode.h:97
const char * lookup(const char *msgid)
Definition: miscutil.c:306
int ln_pcre_extract(const unsigned char *input, const unsigned char *pattern, char **output, size_t num)
Definition: pcre_extract.c:55
const char * version
#define TRUE
Definition: leafnode.h:29
#define FALSE
Definition: leafnode.h:32
int ngmatch(const char *pattern, const char *string)
Definition: miscutil.c:792
int isinteresting(const char *groupname)
Definition: miscutil.c:271
int xsnprintf(char *str, size_t n, const char *format,...)
Definition: miscutil.c:798
#define FQDNLEN
Definition: leafnode.h:288
#define PATH_MAX
Definition: leafnode.h:43
int chdirgroup(const char *group, int creatdir)
Definition: miscutil.c:446
size_t xstrlcpy(char *dst, const char *src, size_t size)
Definition: miscutil.c:812
int getxover(void)
Definition: xoverutil.c:415
void ln_log_use_console(int en)
Definition: ln_log.c:31
void ln_log(int sev, int ctx, const char *format,...)
Definition: ln_log.c:103
#define LNLOG_SCRIT
Definition: ln_log.h:12
#define LNLOG_CTOP
Definition: ln_log.h:22
char * masock_sa2name(const struct sockaddr *sa, int *h_error)
int mastr_cpy(mastr *m, const char *s)
Definition: mastring.c:102
void mastr_delete(mastr *m)
Definition: mastring.c:223
mastr * mastr_new(size_t size)
Definition: mastring.c:62
int mastr_vcat(mastr *m,...)
Definition: mastring.c:147
#define len
Definition: mastring.c:31
#define mastr_modifyable_str(m)
Definition: mastring.h:58
#define mastr_str(m)
Definition: mastring.h:57
#define MAXLINELENGTH
Definition: nntpd.c:60
int verbose
Definition: nntpd.c:80
static void doxhdr(char *arg)
Definition: nntpd.c:1337
static void parser(void)
Definition: nntpd.c:1655
static void rereadactive(void)
Definition: nntpd.c:92
static void dohelp(void)
Definition: nntpd.c:690
static void domove(int by)
Definition: nntpd.c:717
#define OFFSET
Definition: nntpd.c:987
static int dopost(void)
Definition: nntpd.c:1034
static void dolist(char *arg)
Definition: nntpd.c:821
static int checkrange(const struct newsgroup *g, unsigned long a, unsigned long b)
Definition: nntpd.c:1322
static void printlist(const struct newsgroup *g, const int what)
Definition: nntpd.c:774
int main(int argc, char **argv)
Definition: nntpd.c:1768
static void nogroup(void)
Definition: nntpd.c:427
static struct newsgroup * group
Definition: nntpd.c:73
#define ADDRLEN
static char * cmd
Definition: nntpd.c:76
static void list(struct newsgroup *g, const int what, const char *pattern)
Definition: nntpd.c:800
static void doarticle(const char *arg, int what)
Definition: nntpd.c:438
static void markinterest(const char *ng)
Definition: nntpd.c:235
static int dogroup(const char *arg)
Definition: nntpd.c:649
static FILE * fopenpseudoart(const char *arg, const unsigned long article_num)
Definition: nntpd.c:309
static FILE * buildpseudoart(const char *grp)
Definition: nntpd.c:125
static int markdownload(const char *ng, unsigned long id)
Definition: nntpd.c:387
static unsigned long artno
Definition: nntpd.c:75
static void dolistgroup(const char *arg)
Definition: nntpd.c:1606
static struct newsgroup * xovergroup
Definition: nntpd.c:74
static void donewgroups(const char *arg)
Definition: nntpd.c:907
int debug
Definition: nntpd.c:79
static void doxover(char *arg)
Definition: nntpd.c:1529
static void fatal_write(void)
Definition: nntpd.c:83
static void invalidrange(void)
Definition: nntpd.c:1310
static char * generateMessageID(void)
Definition: nntpd.c:990
static FILE * fopenart(const char *arg)
Definition: nntpd.c:341
static int is_pattern(const char *s)
Definition: nntpd.c:765
static int is_pseudogroup(const struct newsgroup *g)
Definition: nntpd.c:218
static time_t activetime
Definition: nntpd.c:77
static int parserange(char *arg, unsigned long *a, unsigned long *b)
Definition: nntpd.c:189
void dodate(void)
Definition: nntpd_dodate.c:6
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: strlcpy.c:47
Definition: mastring.h:28
unsigned long first
Definition: leafnode.h:122
char * desc
Definition: leafnode.h:125
char * name
Definition: leafnode.h:124
time_t age
Definition: leafnode.h:126
unsigned long last
Definition: leafnode.h:123
char * text
Definition: leafnode.h:378
int is_validfqdn(const char *f)
Definition: validatefqdn.c:66
static int rc
Definition: xsnprintf.c:11