1 /*
2 fetchnews -- post articles to and get news from upstream server(s)
3 
4 Written by Arnt Gulbrandsen <agulbra@troll.no> and copyright 1995
5 Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47
6 22646949.
7 Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>
8 and Randolf Skerka <Randolf.Skerka@gmx.de>.
9 Copyright of the modifications 1997.
10 Modified by Kent Robotti <robotti@erols.com>. Copyright of the
11 modifications 1998.
12 Modified by Markus Enzenberger <enz@cip.physik.uni-muenchen.de>.
13 Copyright of the modifications 1998.
14 Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>.
15 Copyright of the modifications 1998, 1999.
16 Modified by Ralf Wildenhues <ralf.wildenhues@gmx.de>.
17 Copyright of the modifications 2002.
18 Modified by Jonathan Larmour <jifl@jifvik.org>.
19 Copyright of the modifications 2002.
20 Modified by Richard van der Hoff <richard@rvanderhoff.org.uk>
21 Copyright of the modifications 2002.
22 Enhanced and modified by Matthias Andree <matthias.andree@gmx.de>.
23 Copyright of the modifications 2000 - 2006.
24 
25 See file COPYING for restrictions on the use of this software.
26 */
27 
28 #include "leafnode.h"
29 #include "fetchnews.h"
30 #include "mastring.h"
31 #include "ln_log.h"
32 #include "mysigact.h"
33 
34 #include <sys/types.h>
35 #include <ctype.h>
36 #include "system.h"
37 #include <fcntl.h>
38 #include <netdb.h>
39 #include <netinet/in.h>
40 #include <setjmp.h>
41 #include <signal.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <sys/socket.h>
46 #include <sys/stat.h>
47 #include <syslog.h>
48 #include <sys/resource.h>
49 #include <unistd.h>
50 #include <utime.h>
51 #include <sys/wait.h>
52 
53 #include "groupselect.h"
54 
55 int verbose = 0;
56 int debug = 0;
57 
58 static time_t now;
59 
60 /* Variables set by command-line options which are specific for fetch */
61 static unsigned long extraarticles = 0;
62 static int usesupplement = 1;
63 static int postonly = 0;	/* if 1, don't read files from upstream */
64 static int noexpire = 0;	/* if 1, don't automatically unsubscribe */
65 static int forceactive = 0;	/* if 1, reread complete active file */
66 
67 static sigjmp_buf jmpbuffer;
68 static volatile sig_atomic_t canjump;
69 
70 static int age( /*@null@*/ const char *date);
71 static int postarticles(const struct server *current_server);
72 
73 static void
ignore_answer(FILE * f)74 ignore_answer(FILE * f)
75 {
76     char *l;
77     while (((l = mgetaline(f)) != NULL) && strcmp(l, "."));
78 }
79 
80 static void
sig_int(int signo)81 sig_int(int signo)
82 {
83     if (canjump == 0)
84 	return;		/* ignore unexpected signals */
85     if (signo == SIGINT || signo == SIGTERM) {
86 	canjump = 0;
87 	alarm(0);
88 	siglongjmp(jmpbuffer, signo);
89     }
90 }
91 
92 static void
usage(void)93 usage(void)
94 {
95     fprintf(stderr, "Usage: fetchnews [-q] [-v] [-x #] [-l] [-n] [-f] [-P] [-w]\n"
96 	    "  -q: quiet, suppress some warnings, cancels -v\n"
97 	    "  -v: more verbose (may be repeated), cancels -q\n"
98 	    "  -x: check for # extra articles in each group\n"
99 	    "  -l: do not use supplementary servers\n"
100 	    "  -n: do not automatically expire unread groups\n"
101 	    "  -f: force reload of groupinfo file\n"
102 	    "  -P: only post outgoing articles, don't fetch any\n"
103 	    "  -w: wait, run XOVER updater in foreground\n");
104 }
105 
106 /**
107  * check whether any of the newsgroups is on server
108  * return TRUE if yes, FALSE otherwise
109  */
110 static int
isgrouponserver(const struct server * current_server,char * newsgroups)111 isgrouponserver(const struct server *current_server,
112 	char *newsgroups /** string will be destroyed! */)
113 {
114     char *p, *q;
115     int retval;
116 
117     if (!newsgroups)
118 	return FALSE;
119 
120     retval = FALSE;
121     p = newsgroups;
122     do {
123 	q = strchr(p, ',');
124 	if (q)
125 	    *q++ = '\0';
126 	switch (gs_match(current_server->group_pcre, p)) {
127 	    case 1:
128 		xsnprintf(lineout, SIZE_lineout, "GROUP %s\r\n", p);
129 		putaline();
130 		if (nntpreply(current_server) == 211) {
131 		    if (debug > 1)
132 			syslog(LOG_DEBUG, "%s is matched by only_groups_pcre and is on server", p);
133 		    retval = TRUE;
134 		}
135 		break;
136 	    case 0:
137 		if (debug > 1)
138 		    syslog(LOG_DEBUG, "%s not matched by only_group_pcre", p);
139 		if (current_server->only_groups_match_all) {
140 		    if (debug > 1)
141 			syslog(LOG_DEBUG, "not posting article to this server, "
142 				"only_groups_match_all is set");
143 		    return FALSE;
144 		}
145 		break;
146 	    default:
147 		break;
148 	}
149 	p = q;
150 	if (p)
151 	    while (*p && isspace((unsigned char)*p))
152 		p++;
153     } while (q);
154 
155     return retval;
156 }
157 
158 /*
159  * check whether message-id is on server - msgid is expected
160  * to contain the angle brackets, i. e. <id@example.org>
161  * return 1 if yes, 0 if no, -1 for error
162  */
163 static int
ismsgidonserver(const struct server * current_server,char * msgid)164 ismsgidonserver(const struct server *current_server, char *msgid)
165 {
166     int r;
167     if (!msgid)
168 	return 0;
169     xsnprintf(lineout, SIZE_lineout, "%s %s\r\n",
170 	      stat_is_evil ? "HEAD" : "STAT", msgid);
171     putaline();
172     r = nntpreply(current_server);
173     if (r == 498 && lastreply() == NULL)
174 	return -1;
175     if (r >= 220 && r <= 223) {
176 	if (stat_is_evil)
177 	    ignore_answer(nntpin);
178 	return 1;
179     } else
180 	return 0;
181 }
182 
183 int
age(const char * date)184 age( /*@null@*/ const char *date)
185 {
186     char monthname[4]; /* RATS: ignore */
187     int month;
188     int year;
189     int day;
190     const char *d;
191     time_t tmp;
192     struct tm time_struct;
193 
194     if (!date)
195 	return 1000;		/* large number: OLD */
196     d = date;
197     if (!(strncasecmp(d, "date:", 5)))
198 	d += 5;
199     while (isspace((unsigned char)*d))
200 	d++;
201 
202     if (isalpha((unsigned char)*d)) {
203 	while (*d && !isspace((unsigned char)*d))	/* skip "Mon" or "Tuesday," */
204 	    d++;
205     }
206 
207     /* RFC 822 says we have 1*LWSP-char between tokens */
208     while (isspace((unsigned char)*d))
209 	d++;
210 
211     /* parsing with sscanf leads to crashes */
212     day = atoi(d);
213     while (isdigit((unsigned char)*d) || isspace((unsigned char)*d))
214 	d++;
215     if (!isalpha((unsigned char)*d)) {
216 	syslog(LOG_INFO, "Unable to parse %s", date);
217 	return 1003;
218     }
219     monthname[0] = *d++;
220     monthname[1] = *d++;
221     monthname[2] = *d++;
222     monthname[3] = '\0';
223     if (strlen(monthname) != 3) {
224 	syslog(LOG_INFO, "Unable to parse month in %s", date);
225 	return 1004;
226     }
227     while (isalpha((unsigned char)*d))
228 	d++;
229     while (isspace((unsigned char)*d))
230 	d++;
231     year = atoi(d);
232 
233     if ((year < 1970) && (year > 99)) {
234 	syslog(LOG_INFO, "Unable to parse year in %s", date);
235 	return 1005;
236     } else if (!(day > 0 && day < 32)) {
237 	syslog(LOG_INFO, "Unable to parse day in %s", date);
238 	return 1006;
239     } else {
240 	if (!strcasecmp(monthname, "jan"))
241 	    month = 0;
242 	else if (!strcasecmp(monthname, "feb"))
243 	    month = 1;
244 	else if (!strcasecmp(monthname, "mar"))
245 	    month = 2;
246 	else if (!strcasecmp(monthname, "apr"))
247 	    month = 3;
248 	else if (!strcasecmp(monthname, "may"))
249 	    month = 4;
250 	else if (!strcasecmp(monthname, "jun"))
251 	    month = 5;
252 	else if (!strcasecmp(monthname, "jul"))
253 	    month = 6;
254 	else if (!strcasecmp(monthname, "aug"))
255 	    month = 7;
256 	else if (!strcasecmp(monthname, "sep"))
257 	    month = 8;
258 	else if (!strcasecmp(monthname, "oct"))
259 	    month = 9;
260 	else if (!strcasecmp(monthname, "nov"))
261 	    month = 10;
262 	else if (!strcasecmp(monthname, "dec"))
263 	    month = 11;
264 	else {
265 	    syslog(LOG_INFO, "Unable to parse %s", date);
266 	    return 1001;
267 	}
268 	if (year < 70)		/* years 2000-2069 in two-digit form */
269 	    year += 100;
270 	else if (year > 1970)	/* years > 1970 in four-digit form */
271 	    year -= 1900;
272 
273 	memset(&time_struct, 0, sizeof(time_struct));
274 	time_struct.tm_sec = 0;
275 	time_struct.tm_min = 0;
276 	time_struct.tm_hour = 0;
277 	time_struct.tm_mday = day;
278 	time_struct.tm_mon = month;
279 	time_struct.tm_year = year;
280 	time_struct.tm_isdst = 0;
281 
282 	tmp = mktime(&time_struct);
283 
284 	if (tmp == -1)
285 	    return 1002;
286 
287 	return ((now - tmp) / SECONDS_PER_DAY);
288     }
289 }
290 
291 /*
292  * Get body of a single message of which the header has already been
293  * downloaded and append it to the file with the header.
294  * Returns 0 if file could not be retrieved, 1 otherwise.
295  */
296 static int
getbody_insitu(const struct server * current_server,struct newsgroup * group,unsigned long id)297 getbody_insitu(const struct server *current_server, struct newsgroup *group, unsigned long id)
298 {
299     const char *c;
300     int rc = 0;
301     char *l;
302     char *messageid;
303     FILE *f;
304     char s[SIZE_s+1];
305     off_t pos;
306 
307     if (!chdirgroup(group->name, FALSE))
308 	return 0;
309 
310     /* extract message-id: header */
311     xsnprintf(s, SIZE_s, "%lu", id);
312     messageid = getheader(s, "Message-ID:");
313     if (!messageid)
314 	return 0;
315 
316     /* check whether we can retrieve the body */
317     if (verbose > 2)
318 	printf("%s: BODY %s\n", group->name, messageid);
319     xsnprintf(lineout, SIZE_lineout, "BODY %s\r\n", messageid);
320     putaline();
321 
322     if (nntpreply(current_server) != 222) {
323 	ln_log(LNLOG_SERR, LNLOG_CARTICLE,
324 		"%s: Retrieving body %s failed. No response",
325 	       group->name, messageid);
326 	rc = 0;
327 	goto getbody_bail;
328     }
329 
330     xsnprintf(s, SIZE_s, "%lu", id);
331     c = lookup(messageid);
332     if (!(f = fopen(c, "a"))) {
333 	ln_log(LNLOG_SERR, LNLOG_CARTICLE,
334 		"%s: cannot open %s for appending", group->name, c);
335 	rc = 0;
336 	goto getbody_bail;
337     }
338     pos = ftell(f);
339     fputc('\n', f); /* blank line -- separate header and body */
340 
341     debug--;
342     while (((l = mgetaline(nntpin)) != NULL) && strcmp(l, ".") && !ferror(f)) {
343 	if (*l == '.')
344 	    ++l;
345 	fputs(l, f);
346 	fputc('\n', f);
347     }
348     debug = debugmode;
349 
350     if (!l) {
351 	ln_log(LNLOG_SERR, LNLOG_CARTICLE, "%s: Retrieving body %s failed. "
352 	       "Transmission interrupted.", group->name, messageid);
353 	ftruncate(fileno(f), pos);
354 	fclose(f);
355 	rc = 0;
356 	goto getbody_bail;
357     }
358 
359     /* abort when disk is full */
360     if (fclose(f) && errno == ENOSPC) {
361 	truncate(s, pos);
362 	raise(SIGINT);
363 	return 0;
364     }
365 
366     rc = 1;
367 
368   getbody_bail:
369     if (messageid)
370 	free(messageid);
371     return rc;
372 }
373 
374 static int
getbody_newno(const struct server * current_server,struct newsgroup * group,unsigned long id)375 getbody_newno(const struct server *current_server, struct newsgroup *group, unsigned long id)
376 {
377     char *c;
378     const char *cc;
379     int rc = 0;
380     char *l;
381     char *p, *q;
382     char *messageid;
383     char *newsgroups;		/* I hope this is enough */
384     char *xref;
385     FILE *f, *g;
386     char s[SIZE_s+1];
387 
388     if (!chdirgroup(group->name, FALSE))
389 	return 0;
390 
391     /* extract message-id: and xref: headers */
392     xsnprintf(s, SIZE_s, "%lu", id);
393     if (!(f = fopen(s, "r"))) {
394 	syslog(LOG_INFO, "%s: cannot open %s for reading -- possibly expired",
395 	       group->name, s);
396 	return 1;		/* pretend to have read file successfully so that
397 				   it is purged from the list */
398     }
399     messageid = NULL;
400     newsgroups = NULL;
401     xref = NULL;
402     debug--;
403     while ((l = getaline(f)) != NULL) {
404 	if (!newsgroups && !strncmp(l, "Newsgroups:", 11)) {
405 	    p = l+11;
406 	    SKIPLWS(p);
407 	    newsgroups = critstrdup(p, "getbody");
408 	}
409 	if (!messageid && !strncmp(l, "Message-ID:", 11)) {
410 	    p = l+11;
411 	    SKIPLWS(p);
412 	    messageid = critstrdup(p, "getbody");
413 	}
414 	if (!xref && !strncmp(l, "Xref:", 5)) {
415 	    p = l + 5;
416 	    SKIPLWS(p);
417 	    xref = critstrdup(p, "getbody");
418 	}
419     }
420     debug = debugmode;
421     fclose(f);
422 
423     if (!messageid || !*messageid) {
424 	/* haven't found Message-ID - cannot fetch */
425 	ln_log(LNLOG_SERR, LNLOG_CARTICLE,
426 		"%s: Retrieving body %lu failed, incomplete local header (MID).",
427 		group->name, id);
428 	rc = 1; /* pretend success, to purge the number from list -
429 		   retrying is pointless */
430 	goto getbody_bail;
431     }
432 
433     /* check whether we can retrieve the body */
434     if (verbose > 2)
435 	printf("%s: BODY %s\n", group->name, messageid);
436     xsnprintf(lineout, SIZE_lineout, "BODY %s\r\n", messageid);
437     putaline();
438 
439     if (nntpreply(current_server) != 222) {
440 	ln_log(LNLOG_SERR, LNLOG_CARTICLE, "%s: Retrieving body %s failed. No response",
441 	       group->name, messageid);
442 	rc = 0;
443 	goto getbody_bail;
444     }
445 
446     xsnprintf(s, SIZE_s, "%lu", id);
447     cc = lookup(messageid);
448     log_unlink(cc, 0);			/* make space for new file */
449 
450     if (!(f = fopen(cc, "w"))) {
451 	ln_log(LNLOG_SERR, LNLOG_CGROUP, "%s: cannot open %s for writing", group->name, cc);
452 	link(s, cc);		/* if we can't open new file restore old one */
453 	rc = 0;
454 	goto getbody_bail;
455     }
456 
457     /* copy all headers except Xref: into new file */
458     g = fopen(s, "r");
459     if (!g) {
460 	ln_log(LNLOG_SERR, LNLOG_CARTICLE, "%s: open %s failed", group->name, s);
461 	fclose(f);
462 	rc = 0;
463 	goto getbody_bail;
464     }
465     debug--;
466     while ((l = getaline(g)) != NULL) {
467 	/* skip xref: headers */
468 	if (strncasecmp(l, "Xref:", 5) != 0)
469 	    fprintf(f, "%s\n", l);
470     }
471     debug = debugmode;
472     fclose(g);
473 
474     /* create a whole bunch of new hardlinks */
475     store(cc, f, newsgroups, messageid);
476 
477     /* retrieve body */
478     fprintf(f, "\n");		/* Empty line between header and body. */
479     debug--;
480     while (((l = mgetaline(nntpin)) != NULL) && strcmp(l, ".") && !ferror(f)) {
481 	if (*l == '.')
482 	    ++l;
483 	fputs(l, f);
484 	fputc('\n', f);
485     }
486     debug = debugmode;
487     if (!l) {
488 	ln_log(LNLOG_SERR, LNLOG_CARTICLE, "%s: Retrieving body %s failed. "
489 	       "Transmission interrupted.", group->name, messageid);
490 	fprintf(f, "\n\t[ Leafnode: ]\n"
491 		"\t[ An error occured while " "retrieving this message. ]\n");
492 	fclose(f);
493 	rc = 0;
494 	goto getbody_bail;
495     }
496     /* abort when disk is full */
497     if (fclose(f) && errno == ENOSPC)
498 	raise(SIGINT);
499 
500     /* Remove old article files since we don't need them anymore.
501        This is done by evaluating the old Xref: header.
502      */
503     if (!xref) {
504 	/* no Xref: header --> make a fake one */
505 	xref = critmalloc(50 + strlen(fqdn) + strlen(group->name), "getbody");
506 	sprintf(xref, "%s %s:%lu", fqdn, group->name, id);	/* RATS: ignore */
507     }
508 
509     if (debugmode)
510 	syslog(LOG_DEBUG, "xref: %s", xref);
511     c = strchr(xref, ' ');
512 #ifdef __LCLINT__
513     assert(c != NULL);		/* we know c != NULL */
514 #endif				/* __LCLINT__ */
515     while ((c++) && (*c) && (q = strchr(c, ':')) != NULL) {
516 	*q++ = '\0';
517 	if ((p = strchr(q, ' ')) != NULL)
518 	    *p = '\0';
519 
520 	/* c points to the newsgroup, q to the article number */
521 	if (!chdirgroup(c, FALSE)) {
522 	    return 0;
523 	}
524 	if (unlink(q))
525 	    syslog(LOG_NOTICE,
526 		   "retrieved body, but unlinking headers-only file %s/%s failed",
527 		   c, q);
528 	else if (debugmode)
529 	    syslog(LOG_DEBUG,
530 		   "retrieved body, now unlinking headers-only file %s/%s", c,
531 		   q);
532 
533 	c = strchr(q, ' ');
534     }
535     rc = 1;
536   getbody_bail:
537     if (xref)
538 	free(xref);
539     if (messageid)
540 	free(messageid);
541     if (newsgroups)
542 	free(newsgroups);
543     return rc;
544 }
545 
546 static int
getbody(const struct server * cs,struct newsgroup * group,unsigned long id)547 getbody(const struct server *cs, struct newsgroup *group, unsigned long id) {
548     static int (*func)(const struct server *, struct newsgroup *, unsigned long);
549 
550     if (!func) {
551 	func = db_situ ? getbody_insitu : getbody_newno;
552     }
553 
554     return func(cs, group, id);
555 }
556 
557 /*
558  * Get bodies of messages that have marked for download.
559  * The group must already be selected at the remote server and
560  * the current directory must be the one of the group.
561  */
562 static void
getmarked(const struct server * current_server,struct newsgroup * group)563 getmarked(const struct server *current_server, struct newsgroup *group)
564 {
565     int n, i;
566     int had_bodies = 0;
567     FILE *f;
568     mastr *filename = mastr_new(PATH_MAX);
569     unsigned long id[BODY_DOWNLOAD_LIMIT]; /* RATS: ignore */
570     char *t;
571 
572     /* #1 read interesting.groups file */
573     n = 0;
574     mastr_vcat(filename, spooldir, "/interesting.groups/", group->name, NULL);
575     if (!(f = fopen(mastr_str(filename), "r")))
576 	ln_log(LNLOG_SERR, LNLOG_CGROUP, "Cannot open %s for reading", mastr_str(filename));
577     else {
578 	struct stat st;
579 	if (fstat(fileno(f), &st) == 0 && st.st_size > 0) {
580 	    had_bodies = 1;
581 	    if (verbose)
582 		printf("%s: getting bodies of marked messages...\n",
583 			group->name);
584 	    while ((t = getaline(f)) && n < BODY_DOWNLOAD_LIMIT) {
585 		if (sscanf(t, "%lu", &id[n]) == 1)
586 		    ++n;
587 	    }
588 	}
589 	fclose(f);
590     }
591     /* #2 get bodies */
592     if (delaybody || had_bodies) {
593 	syslog(LOG_INFO, "%s: marked bodies %d", group->name, n);
594 	if (verbose > 1)
595 	    printf("%s: marked bodies %d\n", group->name, n);
596     }
597     for (i = 0; i < n; ++i)
598 	if (getbody(current_server, group, id[i]))
599 	    id[i] = 0;
600 
601     /* #3 write back ids of all articles which could not be retrieved */
602     if (had_bodies) {
603 	if (!(f = fopen(mastr_str(filename), "w")))
604 	    ln_log(LNLOG_SERR, LNLOG_CGROUP, "Cannot open %s for writing", mastr_str(filename));
605 	else {
606 	    for (i = 0; i < n; ++i)
607 		if (id[i] != 0)
608 		    fprintf(f, "%lu\n", id[i]);
609 	    fclose(f);
610 	}
611     }
612     if (delaybody || had_bodies) {
613 	if (verbose)
614 	    printf("%s: Done getting article bodies.\n", group->name);
615     }
616     mastr_delete(filename);
617 }
618 
619 /** count number of colons in the string s_in. */
count_colons(const char * s_in)620 static int count_colons(const char *s_in) {
621     int ngs;
622     const char *t;
623 
624     ngs = 0;
625     t = s_in;
626     for(;;) {
627 	t += strcspn(t, ":");
628 	if (!*t) break;
629 	t++;
630 	ngs++;
631 	if (ngs < 0) {
632 	    /* overflow */
633 	    return INT_MAX;
634 	}
635     }
636     return ngs;
637 }
638 
639 /** check if article (by Message ID) is present in spool
640  \return - 0 for false (not present)
641          - 1 for true (present) */
is_articlepresent(const char * mid)642 static int is_articlepresent(const char *mid /**< Message-ID of article to check */)
643 {
644     struct stat st;
645     const char *t;
646 
647     t = lookup(mid);
648     if (lstat(t, &st) == 0) {
649 	if (st.st_size == 0) {
650 	    /* article was purged by XOVER-related code (illegal headers,
651 	     * corruption, ... - remove the message.id/NNN/ file so that we can
652 	     * try backfilling with fetchnews -x without a costly prior texpire
653 	     * -r run.
654 	     */
655 	    unlink(t);
656 	    return 0;
657 	}
658 	return 1;
659     }
660     return 0;
661 }
662 
663 /*
664  * get newsgroup from a server. "server" is the last article that
665  * was previously read from this group on that server
666  */
667 static unsigned long
getgroup(const struct server * current_server,struct newsgroup * g,unsigned long server)668 getgroup(const struct server *current_server,
669 	/*@null@*/ struct newsgroup *g,
670 	unsigned long server)
671 {
672 #define HD_MAX 10
673     static char *hd[HD_MAX];
674     const char *hnames[HD_MAX] = { "Path: ", "Message-ID: ", "From: ",
675 	"Newsgroups: ", "Subject: ", "Date: ",
676 	"References: ", "Lines: ", "Xref: ", ""
677     };
678 
679     /* order of headers in XOVER */
680     enum enames { /* 0: art. no. */ h_sub = 1, h_fro, h_dat, h_mid,
681 	h_ref, h_byt, h_lin, h_xref };
682 
683     /* XOVER fields: 0 Subject, 1 From, 2 Date, 3 Message-ID, 4
684      * References, 5 Bytes, 6 Lines, 7 Xref:full (optional) */
685 
686     unsigned long fetched, killed;
687     unsigned long h;
688     long n;
689     unsigned long last;
690     unsigned long window;	/* last ARTICLE n command sent */
691     char *l;
692     FILE *f;
693     const char *c;
694     struct stat st;
695     long outstanding = 0, j;
696     unsigned long i;
697     unsigned long *stufftoget;
698     int localmaxage = maxage;
699     int maxagelimit = -1;
700     const char *limitfrom = "";
701     int expdays;
702 
703     if (!g)
704 	abort();
705 
706     if (g->first > g->last && g->first - g->last > 1)
707 	g->last = g->first - 1;
708 
709     if ((expdays = lookup_expiredays(g->name)) > 0) {
710 	if (localmaxage > expdays) {
711 	    maxagelimit = expdays;
712 	    limitfrom = "groupexpire";
713 	}
714     } else {
715 	if (localmaxage > expiredays) {
716 	    maxagelimit = expiredays;
717 	    limitfrom = "global expire";
718 	}
719     }
720 
721     if (*limitfrom && localmaxage > maxagelimit) {
722 	if (clamp_maxage) {
723 	    syslog(LOG_NOTICE, "clamping maxage for %s to %s %d",
724 		   g->name, limitfrom, maxagelimit);
725 	    localmaxage = maxagelimit;
726 	} else {
727 	    fprintf(stderr,
728 		   "warning: group %s: maxage of %d is inappropriate for your "
729 		   "applicable %s of %d. This can cause excessive downloads of "
730 		   "articles that were previously downloaded and expired. "
731 		   "Fix your configuration.\n", g->name, localmaxage, limitfrom,
732 		   maxagelimit);
733 	    syslog(LOG_WARNING,
734 		   "warning: group %s: maxage of %d is inappropriate for your "
735 		   "applicable %s of %d. This can cause excessive downloads of "
736 		   "articles that were previously downloaded and expired. "
737 		   "Fix your configuration.", g->name, localmaxage, limitfrom,
738 		   maxagelimit);
739 	}
740     }
741 
742     if (server == 0ul)
743 	server = 1ul;
744 
745     /* skip */
746     if (gs_match(current_server->group_pcre, g->name) != 1) {
747 	if (verbose > 1) {
748 	    printf("%s: skipped %s, not in only_groups_pcre\n",
749 		    current_server->name, g->name);
750 	}
751 	return server;
752     }
753 
754     /* skip */
755     if ((l = getenv("LN_SKIP_GROUPS"))) {
756 	char *x, *y = critstrdup(l, "getgroup");
757 	for (x = strtok(y, ","); x; x = strtok(NULL, ",")) {
758 	    if (wildmat(g->name, x)) {
759 		if (verbose) {
760 		    printf("%s: skipped %s, LN_SKIP_GROUPS=%s\n",
761 			    current_server->name, g->name, l);
762 		    syslog(LOG_INFO, "%s: skipped %s, LN_SKIP_GROUPS=%s",
763 			    current_server->name, g->name, l);
764 		}
765 		free(y);
766 		return server;
767 	    }
768 	}
769 	free(y);
770     }
771 
772     xsnprintf(lineout, SIZE_lineout, "GROUP %s\r\n", g->name);
773     putaline();
774 
775     n = nntpreply(current_server);
776     if (n == 498) {
777 	return 0;
778     }
779     l = lastreply();
780 
781     if (n == 411) {		/* group not available on server */
782 	if (verbose > 1)
783 	    printf("%s: no such group\n", g->name);
784 	return server;
785     }
786 
787     if (sscanf(l, "%3ld %lu %lu %lu ", &n, &h, &window, &last) < 4 || n != 211)
788     {
789 	fprintf(stderr, "Warning: %s: cannot parse server reply \"%s\"\n", g->name, l);
790 	syslog(LOG_WARNING, "Warning: %s: cannot parse server reply \"%s\"", g->name, l);
791 	return 0;
792     }
793 
794     if (h == 0) {
795 	if (verbose > 1)
796 	    printf("%s: upstream group is empty\n", g->name);
797 	return server;
798     }
799 
800     if (extraarticles) {
801 	if (server > extraarticles)
802 	    i = server - extraarticles;
803 	else
804 	    i = 1;
805 	if (i < window)
806 	    i = window;
807 	if (verbose > 1 && server > 1)
808 	    printf("%s: backing up from %lu to %lu\n", g->name, server, i);
809 	server = i;
810     }
811 
812     if (server > last + 1) {
813 	syslog(LOG_INFO,
814 	       "%s: last seen article was %lu, server now has %lu-%lu",
815 	       g->name, server, window, last);
816 	if (server > last + 5) {
817 	    if (verbose)
818 		printf("%s: switched upstream servers? %lu > %lu\n",
819 		       g->name, server - 1, last);
820 	    server = window;	/* insane - recover thoroughly */
821 	} else {
822 	    if (verbose)
823 		printf("%s: rampant spam cancel? %lu > %lu\n", g->name, server - 1, last);
824 	    server = last - 5;	/* a little bit too much */
825 	}
826     }
827 
828     if (initiallimit && server == 1 && last > server
829 	&& last - server > initiallimit) {
830 	if (verbose > 1)
831 	    printf("%s: skipping articles %lu-%lu inclusive (initial limit)\n",
832 		   g->name, server, last - initiallimit);
833 	syslog(LOG_INFO, "%s: skipping articles %lu-%lu inclusive (initial limit)",
834 	       g->name, server, last - initiallimit);
835 	server = last - initiallimit + 1;
836     }
837 
838     if (artlimit && last > server && last - server > artlimit) {
839 	if (verbose > 1)
840 	    printf("%s: skipping articles %lu-%lu inclusive (article limit)\n",
841 		   g->name, server, last - artlimit - 1);
842 	syslog(LOG_INFO, "%s: skipping articles %lu-%lu inclusive (article limit)",
843 	       g->name, server, last - artlimit - 1);
844 	server = last - artlimit;
845     }
846 
847     getmarked(current_server, g);
848 
849     if (window < server)
850 	window = server;
851     if (window < 1)
852 	window = 1;
853     server = window;
854 
855     if (server > last) {
856 	if (verbose > 1)
857 	    printf("%s: no new articles\n", g->name);
858 	syslog(LOG_INFO, "%s: no new articles\n", g->name);
859 	return server;
860     }
861 
862     if (verbose > 1)
863 	printf("%s: considering articles %lu - %lu\n", g->name, server, last);
864     syslog(LOG_INFO, "%s: considering articles %lu - %lu\n", g->name, server,
865 	   last);
866 
867     fetched = 0;
868     killed = 0;
869 
870     stufftoget =
871 	(unsigned long *)malloc(sizeof(stufftoget[0]) * (last + 1 - server));
872     if (!stufftoget) {
873 	ln_log(LNLOG_SERR, LNLOG_CGROUP, "not enough memory for XHDRs");
874 	return server;
875     }
876     memset(stufftoget, 0, sizeof(stufftoget[0]) * (last + 1 - server));
877 
878     if (!current_server->noxover) {
879 	int tmp;
880 
881 	/* Try XOVER */
882 	xsnprintf(lineout, SIZE_lineout, "XOVER %lu-%lu\r\n", server, last);
883 	putaline();
884 	if (nntpreply(current_server) == 224) {
885 	    mastr *ol = mastr_new(1024);
886 
887 	    debug--;
888 	    while ((l = mgetaline(nntpin)) && strcmp(l, ".")) {
889 		/*@dependent@*/ char *fields[HD_MAX];
890 		unsigned long art;
891 		char *q;
892 
893 		mastr_cpy(ol, l);
894 		/* split xover */
895 		/*@+loopexec@*/
896 		for (i = 0; l && l[0] && i < HD_MAX; i++) {
897 		    char *y;
898 		    fields[i] = l;
899 		    if ((y = strchr(l, '\t'))) {
900 			y[0] = '\0';
901 			l = y + 1;
902 		    } else {
903 			l = NULL;
904 		    };
905 		};
906 		/*@=loopexec@*/
907 		/* short line -- log and skip */
908 		if (i < 7) {
909 		    ln_log(LNLOG_SWARNING, LNLOG_CTOP,
910 			    "%s: %s: Warning: got unparsable XOVER line from server, "
911 			    "too few fields (%lu): \"%s\"",
912 			    current_server->name, g->name, i, mastr_str(ol));
913 		    continue;
914 		}
915 		for (; i < HD_MAX; i++)
916 		    fields[i] = NULL;
917 		art = strtoul(fields[0], &q, 10);
918 		if (q && art >= server && art <= last) {
919 		    long artlines; /* invalid: -1 */
920 		    unsigned long artbytes; /* invalid: 0 */
921 		    if (fields[h_lin]) artlines = strtol(fields[h_lin], NULL, 10);
922 		    else artlines = -1;
923 		    if (fields[h_byt]) artbytes = strtoul(fields[h_byt], NULL, 10);
924 		    else artbytes = 0;
925 
926 		    if (maxbytes && fields[h_byt]
927 			    && (strtoul(fields[h_byt], NULL, 10) > maxbytes)) {
928 			killed++;
929 			ln_log(LNLOG_SINFO, 4, "%s: killed %lu (%s), "
930 				"too many bytes (%lu > %lu)",
931 				g->name, art, fields[h_mid],
932 				strtoul(fields[h_byt], NULL, 10), maxbytes);
933 		    } else if (maxbytes && artbytes != 0
934 			    && artbytes > maxbytes) {
935 			killed++;
936 			ln_log(LNLOG_SINFO, 4, "%s: killed %lu (%s), "
937 				"too large (%lu > %lu)",
938 				g->name, art, fields[h_mid],
939 				artbytes, maxbytes);
940 		    } else if (maxlines && artlines != -1
941 			    && artlines > maxlines) {
942 			killed++;
943 			ln_log(LNLOG_SINFO, 4, "%s: killed %lu (%s), "
944 				"too many lines (%ld > %ld)",
945 				g->name, art, fields[h_mid],
946 				artlines, maxlines);
947 		    } else if (minlines && artlines != -1
948 			    && artlines < minlines) {
949 			killed++;
950 			ln_log(LNLOG_SINFO, 4, "%s: killed %lu (%s), "
951 				"too few lines (%ld < %ld)",
952 				g->name, art, fields[h_mid],
953 				artlines, minlines);
954 		    } else if (localmaxage && fields[h_dat]
955 			    && (age(fields[h_dat]) > localmaxage)) {
956 			killed++;
957 			ln_log(LNLOG_SINFO, 4, "%s: killed %lu (%s), "
958 				"too old (%d > %d) days",
959 				g->name, art, fields[h_mid],
960 				age(fields[h_dat]), localmaxage);
961 		    } else if (crosspostlimit && fields[h_xref] && (tmp = count_colons(fields[h_xref]) - 1) > crosspostlimit) {
962 			/* -1 to skip over the header's name, "Xref:" */
963 			killed++;
964 			ln_log(LNLOG_SINFO, 4, "%s: killed %lu (%s), "
965 				"too many groups in Xref: header (%d > %ld)",
966 				g->name, art, fields[h_mid],
967 				tmp, crosspostlimit);
968 		    } else if (is_articlepresent(fields[h_mid])) {
969 			killed++;
970 			ln_log(LNLOG_SDEBUG, 4, "%s: killed %lu (%s), already fetched before",
971 				g->name, art, fields[h_mid]);
972 		    } else {
973 			stufftoget[outstanding] = art;
974 			outstanding++;
975 			if (verbose > 2)
976 			    printf("%s: will fetch %lu (%s)\n", g->name, art, fields[h_mid]);
977 		    }
978 		}
979 	    }
980 	    mastr_delete(ol);
981 	    debug = debugmode;
982 	    goto have_outstanding;
983 	}
984 
985 	ln_log(LNLOG_SINFO, 2, "XOVER failed, trying XHDR");
986     }
987 
988     xsnprintf(lineout, SIZE_lineout, "XHDR Message-ID %lu-%lu\r\n", server,
989 	      last);
990     putaline();
991     if (nntpreply(current_server) == 221) {
992 	debug--;
993 	while ((l = mgetaline(nntpin)) && strcmp(l, ".")) {
994 	    unsigned long art;
995 	    char *t;
996 	    art = strtoul(l, &t, 10);
997 	    if (t && isspace((unsigned char)*t)) {
998 		while (isspace((unsigned char)*t))
999 		    t++;
1000 		if (art >= server && art <= last && stat(lookup(t), &st) != 0) {
1001 		    stufftoget[outstanding] = art;
1002 		    outstanding++;
1003 		    if (verbose > 2)
1004 			printf("%s: will fetch %lu (%s)\n", g->name, art, t);
1005 		}
1006 	    }
1007 	}
1008 	debug = debugmode;
1009 	if (!l) {
1010 	    free(stufftoget);
1011 	    ln_log(LNLOG_SWARNING, LNLOG_CARTICLE,
1012 		    "warning: %s: %s: server disconnect or timeout after XHDR",
1013 		    current_server->name, g->name);
1014 	    return 0;
1015 	}
1016     } else {
1017 	free(stufftoget);
1018 	return server;
1019     }
1020 
1021     if (outstanding == 0) {
1022 	free(stufftoget);
1023 	return last + 1;
1024     }
1025 
1026     syslog(LOG_INFO, "%s: will fetch %ld article%s", g->name, outstanding, PLURAL(outstanding));
1027     if (verbose > 1)
1028 	printf("%s: will fetch %ld article%s\n", g->name, outstanding, PLURAL(outstanding));
1029 
1030     if (minlines || maxlines) {
1031 	xsnprintf(lineout, SIZE_lineout, "XHDR Lines %lu-%lu\r\n", server,
1032 		  last);
1033 	putaline();
1034 	if (nntpreply(current_server) == 221) {
1035 	    while ((l = mgetaline(nntpin)) && strcmp(l, ".")) {
1036 		unsigned long art;
1037 		long lines = 0;
1038 		char *t;
1039 		art = strtoul(l, &t, 10);
1040 		if (t)
1041 		    lines = strtol(t, NULL, 10);
1042 		for (j = 0; j < outstanding; j++) {
1043 		    if (art == stufftoget[j])
1044 			break;
1045 		}
1046 		if (j < outstanding)
1047 		    if ((minlines && lines < minlines)
1048 			    || (maxlines && lines > maxlines)) {
1049 		    stufftoget[j] = 0;
1050 		    syslog(LOG_INFO, "%s: Killed article %lu: %ld line%s",
1051 			   g->name, art, lines, PLURAL(lines));
1052 		    if (verbose > 2)
1053 			printf("%s: Killed article %lu: %ld line%s.\n",
1054 			       g->name, art, lines, PLURAL(lines));
1055 		    killed++;
1056 		}
1057 	    }
1058 	    if (!l) {		/* timeout */
1059 		free(stufftoget);
1060 		ln_log(LNLOG_SWARNING, LNLOG_CARTICLE,
1061 			"warning: %s: %s: server disconnect or timeout after XHDR Lines",
1062 			current_server->name, g->name);
1063 		return 0;
1064 	    }
1065 	}
1066     }
1067 
1068     if (maxbytes) {
1069 	xsnprintf(lineout, SIZE_lineout, "XHDR Bytes %lu-%lu\r\n", server,
1070 		  last);
1071 	putaline();
1072 	if (nntpreply(current_server) == 221) {
1073 	    while ((l = mgetaline(nntpin)) && strcmp(l, ".")) {
1074 		unsigned long art, bytes = 0;
1075 		char *t;
1076 		art = strtoul(l, &t, 10);
1077 		if (t)
1078 		    bytes = strtoul(t, NULL, 10);
1079 		for (j = 0; j < outstanding; j++) {
1080 		    if (art == stufftoget[j])
1081 			break;
1082 		}
1083 		if (j < outstanding && (bytes > maxbytes)) {
1084 		    stufftoget[j] = 0;
1085 		    syslog(LOG_INFO, "%s: Killed article %lu (%lu > %lu bytes)",
1086 			   g->name, art, bytes, maxbytes);
1087 		    if (verbose > 2)
1088 			printf("%s: Killed article %lu (%lu > %lu bytes).\n",
1089 			       g->name, art, bytes, maxbytes);
1090 		    killed++;
1091 		}
1092 	    }
1093 	    if (!l) {		/* timeout */
1094 		free(stufftoget);
1095 		ln_log(LNLOG_SWARNING, LNLOG_CARTICLE,
1096 			"warning: %s: %s: server disconnect or timeout after XHDR Bytes",
1097 			current_server->name, g->name);
1098 
1099 		return 0;
1100 	    }
1101 	}
1102     }
1103 
1104     if (localmaxage) {
1105 	xsnprintf(lineout, SIZE_lineout, "XHDR Date %lu-%lu\r\n", server, last);
1106 	putaline();
1107 	if (nntpreply(current_server) == 221) {
1108 	    debug--;
1109 	    while ((l = mgetaline(nntpin)) && strcmp(l, ".")) {
1110 		unsigned long art;
1111 		long aage = 0;
1112 		char *t;
1113 
1114 		art = strtoul(l, &t, 10);
1115 		if (t)
1116 		    aage = age(t);
1117 		for (j = 0; j < outstanding; j++) {
1118 		    if (art == stufftoget[j])
1119 			break;
1120 		}
1121 		if (j < outstanding && (aage > localmaxage)) {
1122 		    stufftoget[j] = 0;
1123 		    syslog(LOG_INFO,
1124 			   "%s: Killed article %lu (%ld > %d = localmaxage)", g->name,
1125 			   art, aage, localmaxage);
1126 		    if (verbose > 2)
1127 			printf("%s: Killed article %lu (%ld > %d = localmaxage).\n",
1128 			       g->name, art, aage, localmaxage);
1129 		    killed++;
1130 		}
1131 	    }
1132 
1133 	    if (!l) {		/* timeout */
1134 		free(stufftoget);
1135 		ln_log(LNLOG_SWARNING, LNLOG_CARTICLE,
1136 			"warning: %s: %s: server disconnect or timeout after XHDR Date",
1137 			current_server->name, g->name);
1138 
1139 		return 0;
1140 	    }
1141 
1142 	    debug = debugmode;
1143 	}
1144     }
1145 
1146     /* now we have a list of articles in stufftoget[] */
1147     /* let's get the header and possibly bodies of these */
1148   have_outstanding:
1149     for (i = 0; outstanding > 0; i++) {
1150 	int takethis = 1;
1151 	int requested_body;
1152 	const char *cmd;
1153 
1154 	outstanding--;
1155 	if (!stufftoget[i])
1156 	    continue;
1157 
1158 	if (stufftoget[i] < server) {
1159 	    if (verbose > 2)
1160 		printf("%s: skipping %lu - not available or too old\n",
1161 		       g->name, stufftoget[i]);
1162 	    syslog(LOG_INFO, "%s: skipping %lu - not available or too old",
1163 		   g->name, stufftoget[i]);
1164 	    continue;
1165 	}
1166 
1167 	debug = debugmode;
1168 	requested_body = ((!filterfile || article_despite_filter) && !delaybody);
1169 	cmd = requested_body ? "ARTICLE" : "HEAD";
1170 	xsnprintf(lineout, SIZE_lineout, "%s %lu\r\n", cmd, stufftoget[i]);
1171 	putaline();
1172 	l = mgetaline(nntpin);
1173 	/* timeout */
1174 	if (!l)
1175 	{
1176 	    ln_log(LNLOG_SERR, LNLOG_CARTICLE,
1177 		    "Server disconnection or timeout before article "
1178 		    "could be retrieved #1");
1179 	    free(stufftoget);
1180 	    return 0;
1181 	}
1182 	/* check proper reply code */
1183 	if (sscanf(l, "%3ld %lu", &n, &h) < 2 || ((n / 10) != 22)) {
1184 	    if (verbose > 2)
1185 		printf("%s %s %lu: reply %s (%ld more up in the air)\n",
1186 		       g->name, cmd, stufftoget[i], l, outstanding);
1187 	    syslog(LOG_INFO, "%s %s %lu: reply %s (%ld more up in the air)",
1188 		   g->name, cmd, stufftoget[i], l, outstanding);
1189 	    continue;
1190 	}
1191 
1192 	/* anything below this line will have to make sure that data is
1193 	 * drained properly in case */
1194 
1195 	debug--;
1196 	if (verbose > 2)
1197 	    printf("%s: receiving article %lu (%ld more up in the air)\n",
1198 		   g->name, stufftoget[i], outstanding);
1199 
1200 	for (h = 0; h < 10; h++) {
1201 	    if (hd[h])
1202 		free(hd[h]);
1203 	    hd[h] = critstrdup("", "getgroup");
1204 	}
1205 	c = NULL;
1206 	n = 9;			/* "other" header */
1207 	while ((l = getfoldedline(nntpin, mgetaline)) && *l && strcmp(l, ".")) {
1208 	    /* regexp pattern matching */
1209 	    if (filterfile && dofilter(l)) {
1210 		killed++;
1211 		if (verbose > 2)
1212 		    printf(".filtered article %lu: match on \"%s\"\n",
1213 			    stufftoget[i], l);
1214 		syslog(LOG_INFO, "filtered article %lu: match on \"%s\"",
1215 		       stufftoget[i], l);
1216 		takethis = 0;
1217 		free(l);
1218 		l = NULL;
1219 		continue;
1220 	    }
1221 
1222 	    n = 0;
1223 	    while (strncasecmp(l, hnames[n], strlen(hnames[n])))
1224 		n++;
1225 	    if (n < 9 && hd[n] && *(hd[n]))
1226 		/* second occurance of the same recognized header
1227 		 * is treated as if it was not listed
1228 		 * in hnames (as "other header") */
1229 		n = 9;
1230 	    hd[n] = critrealloc(hd[n], strlen(hd[n]) + strlen(l) + 2,
1231 				"Fetching article header");
1232 	    if (strlen(hd[n]))
1233 		strcat(hd[n], "\n");	/* RATS: ignore */
1234 	    strcat(hd[n], l);	/* RATS: ignore */
1235 	    if (debugmode > 1 && verbose > 3 && hnames[n] && *hnames[n])
1236 		printf("...saw header %s\n", hnames[n]);
1237 	    free(l);
1238 	    l = NULL;
1239 	} /* end while */
1240 
1241 	/* if server ended the fetch prematurely, assume we didn't
1242 	 * request a body to ignore - ignoring would cause timeout
1243 	 * waiting for data that is never sent.
1244 	 *
1245 	 * SourceForge bug 873149, reported 2004-01-08 by Toni Viemer�,
1246 	 * sourceforge user "skithund" */
1247 	if (l == NULL) {
1248 	    /* timeout - don't flush body */
1249 	    requested_body = FALSE;
1250 	} else if (strcmp(l, ".") == 0 && requested_body) {
1251 	    ln_log(LNLOG_SNOTICE, LNLOG_CTOP, "%s: %s:%lu: article without blank line after header, format violation",
1252 		    current_server->name, g->name, stufftoget[i]);
1253 	    requested_body = FALSE;
1254 	}
1255 
1256 	if (l)
1257 	    free(l);
1258 
1259 	if (!takethis) {
1260 	    if (requested_body) ignore_answer(nntpin);
1261 	    continue;		/* filtered article */
1262 	}
1263 
1264 	debug = debugmode;
1265 	if (!l) {		/* timeout */
1266 	    free(stufftoget);
1267 	    ln_log(LNLOG_SERR, LNLOG_CARTICLE,
1268 		    "Server disconnection or timeout before article "
1269 		    "could be retrieved #2");
1270 	    return 0;
1271 	}
1272 
1273 	/* check headers */
1274 	for (h = 0; h < 6; h++) {
1275 	    if (!hd[h] || !*(hd[h])) {
1276 		if (verbose)
1277 		    printf("Discarding article %lu - no %s found\n",
1278 			   stufftoget[i], hnames[h]);
1279 		syslog(LOG_NOTICE,
1280 		       "Discarding article %lu - no %s found",
1281 		       stufftoget[i], hnames[h]);
1282 		killed++;
1283 		if (requested_body) ignore_answer(nntpin);
1284 		takethis = 0;
1285 		break;
1286 	    }
1287 	}
1288 
1289 	/* mandatory header missing */
1290 	if (!takethis)
1291 	    continue;
1292 
1293 	if (localmaxage && age(hd[5]) > localmaxage) {
1294 	    if (verbose > 2)
1295 		printf("Discarding article %lu - older than %d day%s\n",
1296 		       stufftoget[i], localmaxage, PLURAL(localmaxage));
1297 	    syslog(LOG_INFO, "Discarding article %lu %s - older than %d day%s",
1298 		   stufftoget[i], hd[4], localmaxage, PLURAL(localmaxage));
1299 	    killed++;
1300 	    if (requested_body) ignore_answer(nntpin);
1301 	    continue;
1302 	}
1303 
1304 	if (minlines || maxlines) {
1305 	    char *t;
1306 	    t = strchr(hd[7], ' ');
1307 	    if (t) {
1308 		n = strtol(t, NULL, 10);
1309 		if (minlines && n < minlines) {
1310 		    if (verbose > 2)
1311 			printf("Discarding article %lu - %ld < minlines\n",
1312 			       stufftoget[i], n);
1313 		    syslog(LOG_INFO,
1314 			   "Discarding article %lu %s -- %ld < minlines",
1315 			   stufftoget[i], hd[4], n);
1316 		    killed++;
1317 		    if (requested_body) ignore_answer(nntpin);
1318 		    continue;
1319 		}
1320 		if (maxlines && n > maxlines) {
1321 		    if (verbose > 2)
1322 			printf("Discarding article %lu - %ld > maxlines\n",
1323 			       stufftoget[i], n);
1324 		    syslog(LOG_INFO,
1325 			   "Discarding article %lu %s -- %ld > maxlines",
1326 			   stufftoget[i], hd[4], n);
1327 		    killed++;
1328 		    if (requested_body) ignore_answer(nntpin);
1329 		    continue;
1330 		}
1331 	    }
1332 	}
1333 
1334 	if (crosspostlimit) {
1335 	    char *t;
1336 	    t = hd[3];
1337 	    n = 1;		/* number of groups the article is posted to */
1338 	    while ((t = strchr(t, ',')) != NULL) {
1339 		t++;
1340 		n++;
1341 	    }
1342 	    if (crosspostlimit < n) {
1343 		if (verbose > 2)
1344 		    printf("Discarding article %lu - posted to %ld groups "
1345 			   "(max. %ld)\n", stufftoget[i], n, crosspostlimit);
1346 		syslog(LOG_INFO,
1347 		       "Discarding article %lu %s - posted to %ld groups "
1348 		       "(max. %ld)", stufftoget[i], hd[4], n, crosspostlimit);
1349 		killed++;
1350 		if (requested_body) ignore_answer(nntpin);
1351 		continue;
1352 	    }
1353 	}
1354 
1355 	/* store articles */
1356 	f = NULL;
1357 	c = lookup(strchr(hd[1], '<'));	/* lookup also replaces '/' with '@' */
1358 
1359 	if (!c) {
1360 	    ln_log(LNLOG_SERR, LNLOG_CARTICLE, "lookup of %s failed", hd[1]);
1361 	    if (requested_body) ignore_answer(nntpin);
1362 	    continue;
1363 	}
1364 
1365 	if (!stat(c, &st)) {
1366 	    syslog(LOG_INFO, "article %s already stored", c);
1367 	    if (requested_body) ignore_answer(nntpin);
1368 	    continue;		/* for some reasons, article is already there */
1369 	} else if (errno == ENOENT) {
1370 	    f = fopen(c, "w");
1371 	    if (!f) {
1372 		ln_log(LNLOG_SERR, LNLOG_CARTICLE, "unable to create article %s: %m", c);
1373 		if (requested_body) ignore_answer(nntpin);
1374 		continue;
1375 	    }
1376 	} else {
1377 	    ln_log(LNLOG_SERR, LNLOG_CARTICLE, "unable to store article %s: %m", c);
1378 	    if (requested_body) ignore_answer(nntpin);
1379 	    continue;
1380 	}
1381 
1382 	for (h = 0; h < 10; h++)
1383 	    if (h != 8 && hd[h] && *(hd[h]))
1384 		fprintf(f, "%s\n", hd[h]);
1385 
1386 	h = 0;
1387 	/* replace tabs and other odd signs with spaces */
1388 	while (h < 8) {
1389 	    char *p1;
1390 	    char *p2;
1391 	    p1 = p2 = hd[h];
1392 	    while (p1 && *p1) {
1393 		if (isspace((unsigned char)*p1)) {
1394 		    *p2 = ' ';
1395 		    do {
1396 			p1++;
1397 		    } while (isspace((unsigned char)*p1));
1398 		} else {
1399 		    *p2 = *p1++;
1400 		}
1401 		p2++;
1402 	    }
1403 	    *p2 = '\0';
1404 	    h++;
1405 	}
1406 
1407 	if (fflush(f)) {
1408 	    (void)fclose(f);
1409 	    (void)unlink(c);
1410 	    if (requested_body) ignore_answer(nntpin);
1411 	    continue;
1412 	}
1413 
1414 	/* generate hardlinks; this procedure also increments g->last */
1415 	store(c, f, *hd[3] ? hd[3] + strlen(hnames[3]) : "",
1416 	      *hd[1] ? hd[1] + strlen(hnames[1]) : "");
1417 
1418 	if (delaybody) {
1419 	    if (fclose(f)) {
1420 		int e = errno;
1421 		(void)truncate(c, 0);
1422 		(void)unlink(c);
1423 		if (e == ENOSPC)
1424 		    raise(SIGINT);
1425 	    } else {
1426 		fetched++;
1427 	    }
1428 	    continue;
1429 	}
1430 
1431 	if (!requested_body) {
1432 	    xsnprintf(lineout, SIZE_lineout, "BODY %lu\r\n", stufftoget[i]);
1433 	    putaline();
1434 	    l = mgetaline(nntpin);
1435 	    if (!l) {		/* timeout */
1436 		(void)fflush(f);
1437 		(void)ftruncate(fileno(f), 0);
1438 		(void)fclose(f);
1439 		unlink(c);
1440 		ln_log(LNLOG_SWARNING, LNLOG_CARTICLE,
1441 			"warning: %s: %s: server disconnect or timeout after BODY %lu",
1442 			current_server->name, g->name, stufftoget[i]);
1443 		free(stufftoget);
1444 		return 0;
1445 	    }
1446 	    if (sscanf(l, "%3ld", &n) != 1 || (n / 10 != 22)) {
1447 		if (verbose > 2)
1448 		    printf("BODY %lu: reply %s\n", stufftoget[i], l);
1449 		syslog(LOG_NOTICE, "BODY %lu: reply %s", stufftoget[i], l);
1450 		(void)fflush(f);
1451 		(void)ftruncate(fileno(f), 0);
1452 		(void)fclose(f);
1453 		unlink(c);
1454 		continue;
1455 	    }
1456 	}
1457 	debug--;
1458 	fputs("\n", f);		/* empty line between header and body */
1459 	while (((l = mgetaline(nntpin)) != NULL) && strcmp(l, ".")) {
1460 	    if (*l == '.')
1461 		l++;
1462 	    clearerr(f);
1463 	    fputs(l, f);
1464 	    fputc('\n', f);
1465 	    if (feof(f)) {
1466 		l = NULL;
1467 		break;
1468 	    }
1469 	}
1470 	debug = debugmode;
1471 	fetched++;
1472 	if (fflush(f)) {
1473 	    l = NULL;
1474 	}
1475 	if (fclose(f)) {
1476 	    l = NULL;
1477 	}
1478 	if (l == NULL) {	/* article didn't terminate with a .: error */
1479 	    (void)truncate(c, 0);
1480 	    (void)unlink(c);
1481 		ln_log(LNLOG_SWARNING, LNLOG_CARTICLE,
1482 			"warning: %s: %s: server disconnect or timeout retrieving article %lu",
1483 			current_server->name, g->name, stufftoget[i]);
1484 	    free(stufftoget);
1485 	    return 0;
1486 	}
1487     }
1488 
1489     syslog(LOG_INFO, "%s: %lu article%s fetched (to %lu), %lu killed",
1490 	   g->name, fetched, PLURAL(fetched), g->last, killed);
1491     if (verbose > 1)
1492 	printf("%s: %lu article%s fetched, %lu killed\n",
1493 	       g->name, fetched, PLURAL(fetched), killed);
1494     free(stufftoget);
1495     return last + 1;
1496 }
1497 
1498 /* return 1 == success, 0 == failure */
expire_interesting(void)1499 static int expire_interesting(void) {
1500     DIR *d;
1501     struct dirent *de;
1502     char s[SIZE_s+1];
1503 
1504     xsnprintf(s, SIZE_s, "%s/interesting.groups/", spooldir);
1505 
1506     d = opendir(s);
1507     if (d == NULL) {
1508 	ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot open %s for reading: %m", s);
1509 	return 0;
1510     }
1511 
1512     while ((de = readdir(d))) {
1513 	struct stat st;
1514 
1515 	xsnprintf(s, SIZE_s, "%s/interesting.groups/%s", spooldir, de->d_name);
1516 	if (stat(s, &st) < 0)
1517 	    continue;
1518 	/* reading a newsgroup changes the ctime; if the newsgroup is
1519 	   newly created, the mtime is changed as well */
1520 	if (((st.st_mtime == st.st_ctime) &&
1521 	     (now - st.st_ctime > (timeout_short * SECONDS_PER_DAY))) ||
1522 	    (now - st.st_ctime > (timeout_long * SECONDS_PER_DAY))) {
1523 	    if (verbose > 1)
1524 		printf("unsubscribing from %s\n", de->d_name);
1525 	    syslog(LOG_INFO, "unsubscribing from %s (current time: %ld): "
1526 		   "ctime age %ld, mtime age %ld", de->d_name, (long)now,
1527 		   (long)now - st.st_ctime, (long)now - st.st_mtime);
1528 	    unlink(s);
1529 	}
1530     }
1531 
1532     (void)closedir(d);
1533     return 1;
1534 }
1535 
formatserver(char * d,size_t len,const struct server * serv,const char * suffix)1536 static void formatserver(char *d, size_t len, const struct server *serv, const char *suffix)
1537 {
1538     if (serv->port == 0 || serv->port == 119)
1539 	xsnprintf(d, len, "%s/leaf.node/%s%s", spooldir, serv->name, suffix);
1540     else
1541 	xsnprintf(d, len, "%s/leaf.node/%s:%u%s", spooldir, serv->name,
1542 		serv->port, suffix);
1543 }
1544 
1545 /** get active file from current_server.
1546  * \returns 0 for success, non-zero for error.
1547  */
1548 static int
nntpactive(struct server * current_server,time_t * stamp)1549 nntpactive(struct server *current_server, time_t *stamp)
1550 {
1551     struct stat st;
1552     char *l, *p;
1553     struct stringlist *groups = NULL;
1554     struct stringlist *helpptr = NULL;
1555     char timestr[64]; /* RATS: ignore */
1556     long reply = 0l;
1557     int error, merge = 0;
1558     char s[SIZE_s+1];
1559     int try_xgtitle = 1;
1560     static time_t cur_date;
1561     static int cur_date_init = 0;
1562     time_t t;
1563 
1564     if (!cur_date_init) {
1565 	cur_date = time(NULL);
1566 	cur_date_init = 1;
1567     }
1568 
1569     formatserver(s, SIZE_s, current_server, "");
1570     t = time(NULL);
1571     if (active && !forceactive && (stat(s, &st) == 0)) {
1572 	if (verbose)
1573 	    printf("%s: getting new newsgroups\n", current_server->name);
1574 	/* to avoid a compiler warning we print out a four-digit year;
1575 	 * but since we need only the last two digits, we skip them
1576 	 * in the next line
1577 	 */
1578 	*stamp = st.st_mtime;
1579 	(void)strftime(timestr, sizeof(timestr),
1580 		       "%Y%m%d %H%M%S", gmtime(&st.st_mtime));
1581 	xsnprintf(lineout, SIZE_lineout, "NEWGROUPS %s GMT\r\n", timestr + 2);
1582 	putaline();
1583 	/* we used to expect 231 here, but some broken servers (MC-link
1584 	 * Custom News-server V1.06) return 215 instead.
1585 	 * Just accept any 2XX code as success.
1586 	 */
1587 	if ((reply = nntpreply(current_server)) < 200 || reply >= 300) {
1588 	    const char *e = lastreply();
1589 	    if (!e) e = "server disconnect or timeout";
1590 	    ln_log(LNLOG_SERR, LNLOG_CSERVER,
1591 		    "%s: reading new newsgroups failed, reason \"%s\"",
1592 		    current_server->name, e);
1593 	    return -1;
1594 	}
1595 	while ((l = mgetaline(nntpin)) && (strcmp (l, "."))) {
1596 	    p = l;
1597 	    while (*p && !isspace((unsigned char)*p))
1598 		p++;
1599 	    if (*p)
1600 		*p = '\0';
1601 	    if (gs_match(current_server->group_pcre, l)) {
1602 		merge++;
1603 		insertgroup(l, 1, 0, cur_date);
1604 		prependtolist(&groups, l);
1605 	    }
1606 	}
1607 	if (!l) {		/* timeout */
1608 	    ln_log(LNLOG_SERR, LNLOG_CSERVER,
1609 		    "%s: reading new newsgroups failed, server disconnect or timeout.",
1610 		    current_server->name);
1611 	    return -1;
1612 	}
1613 	ln_log(LNLOG_SINFO, LNLOG_CSERVER,
1614 		    "%s: got %d new newsgroups.",
1615 		    current_server->name, merge);
1616 	if (merge) {
1617 	    mergegroups();	/* merge groups into active */
1618 	}
1619 	helpptr = groups;
1620 	if (verbose && helpptr && current_server->descriptions)
1621 	    printf("%s: getting newsgroup descriptions\n",
1622 		    current_server->name);
1623 	while (helpptr != NULL) {
1624 	    if (current_server->descriptions) {
1625 		error = 0;
1626 		if (try_xgtitle) {
1627 		    xsnprintf(lineout, SIZE_lineout, "XGTITLE %s\r\n",
1628 			    helpptr->string);
1629 		    putaline();
1630 		    reply = nntpreply(current_server);
1631 		}
1632 		if (!try_xgtitle || reply != 282) {
1633 		    try_xgtitle = 0;
1634 		    xsnprintf(lineout, SIZE_lineout, "LIST NEWSGROUPS %s\r\n",
1635 			      helpptr->string);
1636 		    putaline();
1637 		    reply = nntpreply(current_server);
1638 		    if (reply && (reply != 215))
1639 			error = 1;
1640 		}
1641 		if (!error) {
1642 		    l = mgetaline(nntpin);
1643 		    if (l && *l && strcmp(l, ".")) {
1644 			p = l;
1645 			while (*p && !isspace((unsigned char)*p))
1646 			    p++;
1647 			while (isspace((unsigned char)*p)) {
1648 			    *p = '\0';
1649 			    p++;
1650 			}
1651 			if (reply == 215 || reply == 282)
1652 			    changegroupdesc(l, *p ? p : NULL);
1653 			do {
1654 			    l = mgetaline(nntpin);
1655 			    error++;
1656 			} while (l && *l && strcmp(l, "."));
1657 			if (error > 1) {
1658 			    current_server->descriptions = 0;
1659 			    syslog(LOG_WARNING, "warning: %s does not process "
1660 				   "LIST NEWSGROUPS %s correctly: use nodesc\n",
1661 				   current_server->name, helpptr->string);
1662 			    fprintf(stderr, "warning: %s does not process LIST "
1663 				   "NEWSGROUPS %s correctly: use nodesc\n",
1664 				   current_server->name, helpptr->string);
1665 			}
1666 		    }
1667 		}
1668 	    }			/* if ( current_server->descriptions ) */
1669 	    helpptr = helpptr->next;
1670 	}
1671 	freelist(groups);
1672     } else {
1673 	ln_log(LNLOG_SINFO, LNLOG_CSERVER,
1674 	    "%s: getting all newsgroups (debug: active: %s, forceactive: %s)",
1675 		current_server->name,
1676 		active ? "set" : "nil", forceactive ? "true" : "false");
1677 	xsnprintf(lineout, SIZE_lineout, "LIST\r\n");
1678 	putaline();
1679 	if (nntpreply(current_server) != 215) {
1680 	    const char *e = lastreply();
1681 	    if (!e) e = "server disconnect or timeout";
1682 	    ln_log(LNLOG_SERR, LNLOG_CSERVER,
1683 		    "%s: reading all newsgroups failed, reason \"%s\".",
1684 		    current_server->name, e);
1685 	    return -2;
1686 	}
1687 	debug--;
1688 	while ((l = mgetaline(nntpin)) && (strcmp(l, "."))) {
1689 	    p = l;
1690 	    while (*p && !isspace((unsigned char)*p))
1691 		p++;
1692 	    while (isspace((unsigned char)*p)) {
1693 		*p = '\0';
1694 		p++;
1695 	    }
1696 	    if (gs_match(current_server->group_pcre, l)) {
1697 		insertgroup(l, 1, 0, cur_date);
1698 	    }
1699 	}
1700 	mergegroups();
1701 	if (!l) {		/* timeout */
1702 	    ln_log(LNLOG_SERR, LNLOG_CSERVER,
1703 		    "%s: reading all newsgroups failed, server disconnect or timeout.",
1704 		    current_server->name);
1705 	    return -2;
1706 	}
1707 	if (current_server->descriptions) {
1708 	    if (verbose)
1709 		printf("%s: getting newsgroup descriptions\n",
1710 			current_server->name);
1711 	    xsnprintf(lineout, SIZE_lineout, "LIST NEWSGROUPS\r\n");
1712 	    putaline();
1713 	    l = mgetaline(nntpin);
1714 	    /* correct reply starts with "215". However, INN 1.5.1 is broken
1715 	       and immediately returns the list of groups */
1716 	    if (l) {
1717 		if (debug)
1718 		    syslog(LOG_DEBUG, "<%s", l);
1719 		reply = strtol(l, &p, 10);
1720 		if ((reply == 215) && (*p == ' ' || *p == '\0')) {
1721 		    l = mgetaline(nntpin);	/* get first description */
1722 		} else if (*p != ' ' && *p != '\0') {
1723 		    int dummy = 0;
1724 		    /* INN 1.5.1: line already contains description */
1725 		    (void)dummy;
1726 		} else {
1727 		    ln_log(LNLOG_SERR, LNLOG_CSERVER,
1728 			    "%s: reading newsgroups descriptions failed: %s",
1729 			    current_server->name, l);
1730 		    ln_log(LNLOG_SERR, LNLOG_CSERVER,
1731 			    "Workaround: Add \"nodesc = 1\" (without quotes) below the server = %s line.", current_server->name);
1732 		    return -2;
1733 		}
1734 	    } else {
1735 		ln_log(LNLOG_SERR, LNLOG_CSERVER,
1736 			"%s: reading newsgroups descriptions failed: server disconnect or timeout.",
1737 			current_server->name);
1738 		return -2;
1739 	    }
1740 	    while (l && (strcmp(l, "."))) {
1741 		p = l;
1742 		while (*p && !isspace((unsigned char)*p))
1743 		    p++;
1744 		while (isspace((unsigned char)*p)) {
1745 		    *p = '\0';
1746 		    p++;
1747 		}
1748 		changegroupdesc(l, *p ? p : NULL);
1749 		l = mgetaline(nntpin);
1750 	    }
1751 	    if (!l) {		/* timeout */
1752 		ln_log(LNLOG_SERR, LNLOG_CSERVER,
1753 			"%s: reading newsgroup descriptions failed, server disconnect or timeout.",
1754 			current_server->name);
1755 		return -2;
1756 	    }
1757 	}
1758 	debug = debugmode;
1759 
1760 	/* mark active for /this/ server fetched */
1761 	{
1762 	    FILE *f = fopen(s, "a");
1763 	    if (!f) {
1764 		ln_log(LNLOG_SERR, LNLOG_CGROUP,
1765 			"cannot open \"%s\": %m", s);
1766 	    } else {
1767 		if (fclose(f))
1768 		    ln_log(LNLOG_SERR, LNLOG_CGROUP,
1769 			    "cannot close \"%s\": %m", s);
1770 	    }
1771 	}
1772     }
1773     *stamp = t;
1774     return 0;
1775 }
1776 
1777 static int
movetofailed(const char * name)1778 movetofailed(const char *name) {
1779     char s[SIZE_s + 1];
1780 
1781     xsnprintf(s, SIZE_s, "%s/failed.postings/%s", spooldir, name);
1782     ln_log(LNLOG_SERR, LNLOG_CARTICLE,
1783 	    "moving file %s to failed.postings", name);
1784     if (rename(name, s)) {
1785 	ln_log(LNLOG_SERR, LNLOG_CARTICLE,
1786 		"unable to move failed posting to %s: %m", s);
1787 	return -1;
1788     } else {
1789 	return 0;
1790     }
1791 }
1792 
1793 /*
1794  * post all spooled articles
1795  *
1796  * if all postings succeed, returns 1
1797  * if there are no postings to post, returns 1
1798  * if a posting is strange for some reason, returns 0
1799  * returns -1 if server should be skipped
1800  */
1801 static int
postarticles(const struct server * current_server)1802 postarticles(const struct server *current_server)
1803 {
1804     struct stat st;
1805     char *line;
1806     DIR *d;
1807     struct dirent *de;
1808     FILE *f;
1809     int r, haveid, n;
1810     char *p, *q;
1811     int savedir;
1812 
1813     n = 0;
1814 
1815     savedir = open(".", O_RDONLY);
1816     if (savedir < 0) {
1817 	ln_log(LNLOG_SERR, LNLOG_CTOP,
1818 		"postarticles: Unable to save current working directory: %m");
1819 	return 0;
1820     }
1821 
1822     if (chdir(spooldir) || chdir("out.going")) {
1823 	ln_log(LNLOG_SERR, LNLOG_CTOP,
1824 		"postarticles: Unable to cd to %s/out.going: %m",
1825 		spooldir);
1826 	fchdir(savedir);
1827 	close(savedir);
1828 	return 0;
1829     }
1830 
1831     d = opendir(".");
1832     if (!d) {
1833 	ln_log(LNLOG_SERR, LNLOG_CTOP,
1834 		"postarticles: Unable to opendir %s/out.going: %m", spooldir);
1835 	fchdir(savedir);
1836 	close(savedir);
1837 	return 0;
1838     }
1839 
1840     while ((de = readdir(d)) != NULL) {
1841 	f = NULL;
1842 	if (!strcmp(".", de->d_name) || !strcmp("..", de->d_name)) {
1843 	    continue;
1844 	}
1845 	q = NULL;
1846 	if ((lstat(de->d_name, &st) != 0)) {
1847 	    ln_log(LNLOG_SERR, LNLOG_CARTICLE,
1848 		    "postarticles: cannot stat %s: %m",
1849 		    de->d_name);
1850 	    continue;
1851 	}
1852 
1853 	if (!S_ISREG(st.st_mode)) {
1854 	    ln_log(LNLOG_SNOTICE, LNLOG_CARTICLE,
1855 		    "postarticles: %s is not a regular file",
1856 		    de->d_name);
1857 	    movetofailed(de->d_name);
1858 	    continue;
1859 	}
1860 
1861 	if (!(st.st_mode & S_IRUSR)) {
1862 	    ln_log(LNLOG_SINFO, LNLOG_CARTICLE,
1863 		    "postarticles: skipping %s, not complete",
1864 		    de->d_name);
1865 	    continue;
1866 	}
1867 
1868 	f = fopen(de->d_name, "r");
1869 	if (f == NULL) {
1870 	    ln_log(LNLOG_SERR, LNLOG_CARTICLE,
1871 		    "postarticles: cannot open file %s for reading: %m",
1872 		    de->d_name);
1873 	    movetofailed(de->d_name);
1874 	    continue;
1875 	}
1876 
1877 	p = fgetheader(f, "Newsgroups:");
1878 	q = fgetheader(f, "Message-ID:");
1879 	if (p == NULL || q == NULL) {
1880 	    ln_log(LNLOG_SERR, LNLOG_CARTICLE,
1881 		    "postarticles: file %s lacks Newsgroups "
1882 		    "and/or Message-ID header (this cannot happen)",
1883 		    de->d_name);
1884 	    movetofailed(de->d_name);
1885 	    goto free_cont;
1886 	}
1887 
1888 	if (!current_server->post_anygroup) {
1889 	    char *pp = critstrdup(p, "postarticles");
1890 	    if (!isgrouponserver(current_server, pp)) {
1891 		ln_log(LNLOG_SINFO, LNLOG_CARTICLE,
1892 			"%s: postarticles: file %s: only_groups_pcre excluded "
1893 			"or server does not carry newsgroups %s",
1894 			current_server->name, de->d_name, p);
1895 		free(pp);
1896 		goto free_cont;
1897 	    }
1898 	    free(pp);
1899 	}
1900 
1901 	haveid = ismsgidonserver(current_server, q);
1902 	switch(haveid) {
1903 	    case 0:
1904 		ln_log(LNLOG_SINFO, LNLOG_CARTICLE,
1905 			"%s: postarticles: trying to post file %s Message-ID %s",
1906 			current_server->name, de->d_name, q);
1907 
1908 		xsnprintf(lineout, SIZE_lineout, "POST\r\n");
1909 		putaline();
1910 		r = nntpreply(current_server);
1911 		if (r != 340) {
1912 		    char *e = lastreply();
1913 		    if (e == NULL) {
1914 			ln_log(LNLOG_SERR, LNLOG_CARTICLE,
1915 				"%s: postarticles: server disconnect or timeout"
1916 				" while trying to post file %s)",
1917 				current_server->name, de->d_name);
1918 			goto free_ret;
1919 		    }
1920 
1921 		    ln_log(LNLOG_SERR, LNLOG_CARTICLE,
1922 			    "%s: postarticles: server replied \"%s\" to our POST command, skipping server",
1923 			    current_server->name, e);
1924 		    goto free_ret;
1925 		} else {
1926 		    /* server replied 340, it is willing to accept our article */
1927 		    int postok = 0;
1928 		    debug--;
1929 		    while ((line = getaline(f)) != NULL) {
1930 			/* can't use putaline() here because
1931 			   line length of lineout is restricted */
1932 			if (line[0] == '.')
1933 			    fputc('.', nntpout);
1934 			fputs(line, nntpout);
1935 			fputs("\r\n", nntpout);
1936 		    };
1937 		    fflush(nntpout);
1938 		    debug = debugmode;
1939 		    xsnprintf(lineout, SIZE_lineout, ".\r\n");
1940 		    putaline();
1941 		    line = mgetaline(nntpin);
1942 		    if (!line) {	/* timeout: posting failed */
1943 			ln_log(LNLOG_SERR, LNLOG_CARTICLE,
1944 				"%s: postarticles: server disconnect or timeout"
1945 				" after sending article %s)",
1946 				current_server->name, de->d_name);
1947 			goto free_ret;
1948 		    }
1949 		    line = critstrdup(line, "postarticles");
1950 		    if (strncmp(line, "240", 3) == 0) {
1951 			postok = 1;
1952 		    } else if (ismsgidonserver(current_server, q) == 1) {
1953 			syslog(LOG_NOTICE,
1954 				"%s: postarticles: posting resulted in \"%s\", "
1955 				"but article is available upstream, assuming OK.",
1956 				current_server->name, line);
1957 			printf("%s: postarticles: posting resulted in \"%s\",\n"
1958 				"but article is available upstream, assuming OK.",
1959 				current_server->name, line);
1960 			postok = 1;
1961 		    }
1962 		    if (postok) {
1963 			if (verbose > 2)
1964 			    printf("%s: postarticles: POST of article %s OK\n",
1965 				    current_server->name, de->d_name);
1966 			n++;
1967 			if (unlink(de->d_name)) {
1968 			    ln_log(LNLOG_SERR, LNLOG_CARTICLE,
1969 				    "postarticles: unable to unlink posted article %s: %m",
1970 				    de->d_name);
1971 			}
1972 		    } else {
1973 			ln_log(LNLOG_SERR, LNLOG_CARTICLE,
1974 				"%s: postarticles: Article file %s Message-ID %s"
1975 				" was rejected: \"%s\"",
1976 				current_server->name, de->d_name, q, line);
1977 			movetofailed(de->d_name);
1978 		    }
1979 		    free(line);
1980 		}
1981 		break;
1982 	    case 1:
1983 		syslog(LOG_INFO, "%s: postarticles: Message-ID of %s already in use"
1984 			" upstream -- article discarded\n",
1985 			current_server->name, de->d_name);
1986 		if (verbose > 2)
1987 		    printf("%s: postarticles: %s already available upstream\n",
1988 			    current_server->name, de->d_name);
1989 		unlink(de->d_name);
1990 		break;
1991 	    case -1:
1992 		ln_log(LNLOG_SERR, LNLOG_CARTICLE,
1993 			"%s: postarticles: skipping server",
1994 			current_server->name);
1995 		goto free_ret;
1996 	}
1997 
1998 free_cont:
1999     fclose(f);
2000 	if (p) free(p);
2001 	if (q) free(q);
2002 	continue;
2003 free_ret:
2004 	if (p) free(p);
2005 	if (q) free(q);
2006 	fchdir(savedir);
2007 	close(savedir);
2008 	closedir(d);
2009 	return -1;
2010     } /* while de = readdir */
2011     closedir(d);
2012     if (verbose)
2013 	printf("%s: %d article%s posted.\n", current_server->name, n, PLURAL(n));
2014     syslog(LOG_INFO, "%s: %d article%s posted.", current_server->name, n, PLURAL(n));
2015     fchdir(savedir);
2016     close(savedir);
2017     return 1;
2018 }
2019 
2020 static int
processupstream(const struct server * serv,time_t stamp)2021 processupstream(const struct server *serv, time_t stamp)
2022 {
2023     FILE *f;
2024     DIR *d;
2025     struct dirent *de;
2026     struct newsgroup *g;
2027     int havefile;
2028     unsigned long newserver = 0;
2029     char *l;
2030     char *oldfile;
2031     struct stat st1, st2;
2032     int have_st1 = 0;
2033     char s[SIZE_s+1];
2034     int aborting = 0;
2035 
2036     struct stringlist *ngs = NULL, *a, *b = NULL;
2037 
2038     /* read server info */
2039     formatserver(s, SIZE_s, serv, "");
2040 
2041     oldfile = critstrdup(s, "processupstream");
2042     havefile = 0;
2043     if ((f = fopen(s, "r")) != NULL) {
2044 	if (fstat(fileno(f), &st1)) {
2045 	    int e = errno;
2046 	    ln_log(LNLOG_SERR, LNLOG_CSERVER, "Cannot stat %s: %s", s, strerror(e));
2047 	} else {
2048 	    have_st1 = 1;
2049 	}
2050 	/* a sorted array or a tree would be better than a list */
2051 	ngs = NULL;
2052 	debug--;
2053 	if (verbose > 1)
2054 	    printf("%s: reading server info from %s\n", serv->name, s);
2055 	syslog(LOG_INFO, "%s: reading server info from %s", serv->name, s);
2056 	while (((l = getaline(f)) != NULL) && (strlen(l))) {
2057 	    a = (struct stringlist *)critmalloc(sizeof(struct stringlist)
2058 						+ strlen(l),
2059 						"Reading server info");
2060 	    strcpy(a->string, l);	/* RATS: ignore */
2061 	    a->next = NULL;
2062 	    if (ngs == NULL)
2063 		ngs = a;
2064 	    else
2065 		b->next = a;
2066 	    b = a;
2067 	}
2068 	havefile = 1;
2069 	debug = debugmode;
2070 	fclose(f);
2071     }
2072 
2073     xsnprintf(s, SIZE_s, "%s/interesting.groups", spooldir);
2074     d = opendir(s);
2075     if (!d) {
2076 	ln_log(LNLOG_SERR, LNLOG_CSERVER, "opendir %s: %m", s);
2077 	free(oldfile);
2078 	return -1;
2079     }
2080 
2081     formatserver(s, SIZE_s, serv, "~");
2082     if (stat(s, &st2) == 0 && (!have_st1 || st2.st_mtime >
2083 			       st1.st_mtime) && (f = fopen(s, "r"))) {
2084 	int e = 0;
2085 
2086 	/* roll in changes of a previous crash */
2087 	syslog(LOG_INFO, "Merging in %s from previous run", s);
2088 	if (verbose > 1)
2089 	    printf("Merging in %s from previous run\n", s);
2090 	while (((l = getaline(f)) != NULL)) {
2091 	    char *p, *t = strchr(l, ' ');
2092 	    if (!t || !*++t)
2093 		continue;
2094 	    (void)strtoul(t, &p, 10);
2095 	    if (p && !*p)
2096 		replaceinlist(&ngs, l, (size_t)(t-l));
2097 	}
2098 	(void)fclose(f);	/* read-only file, assume no error */
2099 	xsnprintf(s, SIZE_s, "%s/leaf.node/%s.new", spooldir, serv->name);
2100 	f = fopen(s, "w");
2101 	if (!f) {
2102 	    e = 1;
2103 	} else {
2104 	    a = ngs;
2105 	    while (a && a->string && !ferror(f)) {
2106 		(void)fputs(a->string, f);
2107 		(void)fputc('\n', f);
2108 		a = a->next;
2109 	    }
2110 	    if (fclose(f))
2111 		e = 1;
2112 	}
2113 	if (e) {
2114 	    ln_log(LNLOG_SERR, LNLOG_CSERVER, "open %s: %m", s);
2115 	    (void)unlink(s);
2116 	    return -1;
2117 	}
2118 	if (rename(s, oldfile)) {
2119 	    ln_log(LNLOG_SERR, LNLOG_CSERVER, "rename %s -> %s: %m", s, oldfile);
2120 	    (void)unlink(s);
2121 	    return -1;
2122 	}
2123     }
2124 
2125     formatserver(s, SIZE_s, serv, "~");
2126     (void)unlink(s);
2127     f = fopen(s, "w");
2128     if (f == NULL) {
2129 	ln_log(LNLOG_SERR, LNLOG_CSERVER, "Could not open %s for writing: %s",
2130 		s, strerror(errno));
2131     } else {
2132 	/* make sure that at least SERVERINFO~ is complete */
2133 	if (setvbuf(f, NULL, _IOLBF, 4096)) {
2134 	    /* try to at least use unbuffered then */
2135 	    setvbuf(f, NULL, _IONBF, 0);
2136 	}
2137     }
2138     while ((de = readdir(d))) {
2139 	if (isalnum((unsigned char)*(de->d_name))) {
2140 	    g = findgroup(de->d_name);
2141 	    if (g != NULL) {
2142 		unsigned long newhigh;
2143 		xsnprintf(s, SIZE_s, "%s ", g->name);
2144 		newhigh = 1ul;
2145 		l = havefile ? findinlist(ngs, s) : NULL;
2146 		if (l && *l) {
2147 		    char *t;
2148 		    l = strchr(l, ' ');
2149 		    if (l) {
2150 			newhigh = strtoul(l, &t, 10);
2151 			if (t == l || *t)
2152 			    newhigh = 1ul;
2153 		    }
2154 		}
2155 		newserver = getgroup(serv, g, newhigh);
2156 		/* run this independent of delaybody mode, because
2157 		 * the admin may have switched delaybody off recently,
2158 		 * and we still want users to be able to retrieve
2159 		 * articles. */
2160 		if (newserver) newhigh = newserver;
2161 		if (f != NULL && newhigh > 0) {
2162 		    fprintf(f, "%s %lu\n", g->name, newhigh);
2163 		}
2164 		if (!newserver) {
2165 		    fprintf(stderr, "Warning: aborting fetch from %s due to previous condition.\n", serv->name);
2166 		    syslog(LOG_WARNING, "Warning: aborting fetch from %s due to previous condition.", serv->name);
2167 		    aborting = 1;
2168 		    break;
2169 		}
2170 	    } else {
2171 		if (verbose > 1)
2172 		    printf("%s not found in groupinfo file\n", de->d_name);
2173 		syslog(LOG_NOTICE, "%s not found in groupinfo file", de->d_name);
2174 	    }
2175 	} /* if isalnum */
2176     } /* while readdir */
2177     closedir(d);
2178     if (f != NULL) {
2179 	int ren = 1;
2180 	formatserver(s, SIZE_s, serv, "~");
2181 	if (ferror(f))
2182 	    ren = 0;
2183 	if (fflush(f))
2184 	    ren = 0;
2185 	if (fclose(f))
2186 	    ren = 0;
2187 	if (!aborting) {
2188 	    if (ren) {
2189 		struct utimbuf ut;
2190 		if (rename(s, oldfile)) {
2191 		    ln_log(LNLOG_SERR, LNLOG_CSERVER, "cannot rename %s to %s: %m",
2192 			    s, oldfile);
2193 		}
2194 		ut.modtime = ut.actime = stamp;
2195 		if (utime(oldfile, &ut)) {
2196 		    ln_log(LNLOG_SERR, LNLOG_CSERVER, "cannot set proper time for %s to %lu: %m",
2197 			    s, (unsigned long)stamp);
2198 		}
2199 	    } else {
2200 		ln_log(LNLOG_SERR, LNLOG_CSERVER, "write error on %s, old version of %s kept",
2201 			s, oldfile);
2202 	    }
2203 	}
2204     }
2205 
2206     free(oldfile);
2207     freelist(ngs);
2208     return 0;
2209 }
2210 
2211 /*
2212  * checks whether all newsgroups have to be retrieved anew
2213  * returns 0 if yes, time of last update if not
2214  * mtime is the time when active was fetched fully
2215  * atime is the time when active was last updated
2216  */
2217 static time_t
checkactive(void)2218 checkactive(void)
2219 {
2220     struct stat st;
2221     char *s = activeread();
2222 
2223     if (stat(s, &st)) {
2224 	free(s);
2225 	return 0;
2226     }
2227     if ((now - st.st_mtime) < (timeout_active * SECONDS_PER_DAY)) {
2228 	if (debugmode)
2229 	    syslog(LOG_DEBUG,
2230 		   "Last LIST done %d seconds ago: NEWGROUPS\n",
2231 		   (int)(now - st.st_mtime));
2232 	free(s);
2233 	return st.st_atime;
2234     } else {
2235 	if (debugmode)
2236 	    syslog(LOG_DEBUG, "Last LIST done %d seconds ago: LIST\n",
2237 		   (int)(now - st.st_mtime));
2238 	free(s);
2239 	return 0;
2240     }
2241 }
2242 
2243 static int
updateactive(void)2244 updateactive(void)
2245 {
2246     struct stat st;
2247     struct utimbuf buf;
2248     FILE *f;
2249     char *s = activeread();
2250     int rc = 0;
2251 
2252     if (stat(s, &st)) {
2253 	/* active.read probably doesn't exist */
2254 	(void)unlink(s); /* delete it in case it's junk */
2255 	if ((f = fopen(s, "w")) != NULL) {
2256 	    if (fsync(fileno(f))) rc = -1;
2257 	    if (fclose(f)) rc = -1;
2258 	} else {
2259 	    /* f == NULL, open error */
2260 	    rc = -1;
2261 	}
2262     } else {
2263 	buf.actime = (now < st.st_atime) ? st.st_atime : now;
2264 	/* now < update may happen through HW failures */
2265 	buf.modtime = st.st_mtime;
2266 	if (utime(s, &buf)) {
2267 	    rc = -1;
2268 	}
2269     }
2270     if (rc)
2271 	ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot update or create %s: %m", s);
2272     free(s);
2273     return rc;
2274 }
2275 
2276 static void
error_refetch(const char * e)2277 error_refetch(const char *e) {
2278     ln_log(LNLOG_SERR, LNLOG_CTOP,
2279 	    "ERROR: FETCHNEWS MUST REFETCH THE WHOLE ACTIVE FILE NEXT RUN.");
2280     ln_log(LNLOG_SERR, LNLOG_CTOP, "REASON: %s", e);
2281 }
2282 
2283 /** re-add non-expiring groups to active */
2284 static void
addnonexpiring(void)2285 addnonexpiring(void)
2286 {
2287     struct stringlist *t, *l = get_grouplist();
2288     int expdays;
2289 
2290     for (t=l; t != NULL; t = t->next) {
2291 	char *x = t->string;
2292 
2293 	if ((expdays = lookup_expiredays(x)) >= 0) {
2294 	    if (expdays == 0 || !(expdays = lookup_expire(x)))
2295 		expdays = expire;
2296 	} else {
2297 	    expdays = -1;
2298 	}
2299 
2300 	if (expdays == -1) {
2301 	    insertgroup(x, 0, 0, 0);
2302 	}
2303     }
2304     freelist(l);
2305 }
2306 
2307 int
main(int argc,char ** argv)2308 main(int argc, char **argv)
2309 {
2310     /* the volatile keyboard avoids clobbering by siglongjmp */
2311     volatile time_t lastrun;
2312     volatile int rc = 0, skip_servers = 0;
2313     volatile int anypost = 0, waitchild = 0, quiet;
2314     volatile int wantpost = 0;
2315     struct server *current_server;
2316     volatile int need_refetch = 0;
2317 
2318     int option, reply;
2319     pid_t pid;
2320 
2321     verbose = quiet = 0;
2322     postonly = waitchild = 0;
2323 
2324     myopenlog("fetchnews");
2325 
2326     if (!initvars(argv[0]))
2327 	exit(1);
2328 
2329     while ((option = getopt(argc, argv, "Pfhlnvx:qw")) != -1) {
2330 	if (option == 'v') {
2331 	    verbose++;
2332 	    quiet = 0;
2333 	} else if (option == 'h') {
2334 	    usage();
2335 	    exit(EXIT_SUCCESS);
2336 	} else if (option == 'x') {
2337 	    char *nptr, *endptr;
2338 	    nptr = optarg;
2339 	    endptr = NULL;
2340 	    extraarticles = strtoul(nptr, &endptr, 0);
2341 	    if (!nptr || !*nptr || !endptr || *endptr || !extraarticles) {
2342 		usage();
2343 		exit(1);
2344 	    }
2345 	    syslog(LOG_NOTICE, "fetchnews: run with option -x %lu", extraarticles);
2346 	} else if (option == 'l') {
2347 	    usesupplement = 0;	/* don't use supplementary servers */
2348 	} else if (option == 'n') {
2349 	    noexpire = 1;
2350 	} else if (option == 'f') {
2351 	    forceactive = 1;
2352 	} else if (option == 'P') {
2353 	    postonly = 1;
2354 	} else if (option == 'q') {
2355 	    verbose = 0;
2356 	    quiet = 1;
2357 	} else if (option == 'w') {
2358 	    waitchild = 1;
2359 	} else {
2360 	    usage();
2361 	    exit(1);
2362 	}
2363     }
2364 
2365     /* Set line buffering to ensure that logging gets displayed promptly. */
2366     if (setvbuf(stdout, NULL, _IOLBF, 4096)) {
2367 	/* Try to at least use unbuffered then */
2368 	setvbuf(stdout, NULL, _IONBF, 0);
2369     }
2370 
2371     now = time(NULL);
2372 
2373     umask(2);
2374 
2375     if (!readconfig(0)) {
2376 	fprintf(stderr, "Reading configuration failed, exiting "
2377 	       "(see syslog for more information).\n");
2378 	freeconfig();
2379 	exit(1);
2380     }
2381 
2382     if (debugmode)
2383 	syslog(LOG_DEBUG, "leafnode %s: verbosity level is %d, debugmode is %d",
2384 		version, verbose, debugmode);
2385     if (verbose || debugmode) {
2386 	printf("leafnode %s: verbosity level is %d, debugmode is %d\n",
2387 		version, verbose, debugmode);
2388 	if (verbose > 1 && noexpire) {
2389 	    printf("Don't automatically unsubscribe unread newsgroups.\n");
2390 	}
2391     }
2392 
2393     if (forceactive) {
2394 	ln_log(LNLOG_SNOTICE, LNLOG_CTOP,
2395 		"Forced active fetch requested from command-line (option -f).");
2396     }
2397 
2398     if (try_lock(timeout_lock)) {
2399 	ln_log(LNLOG_SERR, LNLOG_CTOP,
2400 		"Cannot obtain lock file, aborting.");
2401 	freeconfig();
2402 	exit(1);
2403     }
2404 
2405     if (!postonly) {
2406 	readactive();
2407 	if (!active) {
2408 	    addnonexpiring();
2409 	    fakeactive(); /* we need proper lowwater/highwater marks
2410 			     for the groups that exist */
2411 	    ln_log(LNLOG_SNOTICE, LNLOG_CTOP,
2412 		    "Forced active fetch after trouble reading active file.");
2413 	    forceactive |= 2;
2414 	}
2415 
2416 	readfilter(filterfile);
2417     }
2418 
2419     lastrun = 0;
2420     if (!postonly && !forceactive) {
2421 	lastrun = checkactive();
2422 	if (!lastrun) {
2423 	    ln_log(LNLOG_SNOTICE, LNLOG_CTOP, "Active has not been fetched completely in previous run");
2424 	    ln_log(LNLOG_SNOTICE, LNLOG_CTOP, "or has never been fetched, forcing active fetch.");
2425 	    forceactive |= 2;
2426 	}
2427     }
2428 
2429     if (!postonly && !forceactive) {
2430 	int staterror;
2431 	struct stat st;
2432 	char s[SIZE_s+1];
2433 
2434 	xsnprintf(s, SIZE_s, "%s/leaf.node/groupinfo", spooldir);
2435 	if ((staterror = stat(s, &st)) && errno != ENOENT) {
2436 	    /* this should happen only when the problem occurs after
2437 	     * readactive() above, but needs to be checked nonetheless */
2438 	    ln_log(LNLOG_SERR, LNLOG_CTOP, "Cannot open %s: %m", s);
2439 	    unlink(lockfile);
2440 	    freeconfig();
2441 	    exit(EXIT_FAILURE);
2442 	}
2443 	if (staterror || st.st_size < 7) {
2444 	    ln_log(LNLOG_SNOTICE, LNLOG_CTOP,
2445 		    "Groupinfo file %s not present or too small, "
2446 		    "forcing active fetch.", s);
2447 	    forceactive |= 2;
2448 	}
2449     }
2450 
2451     if (forceactive) {
2452 	oldactive = active;
2453 	oldactivesize = activesize;
2454 	active = NULL;
2455 	activesize = 0;
2456 	if (killactiveread()) exit(1);
2457 	addnonexpiring();
2458     }
2459 
2460     if (mysigact(SIGINT, 0, sig_int, SIGALRM) != 0)
2461 	fprintf(stderr, "Can't catch SIGINT.\n");
2462     if (mysigact(SIGTERM, 0, sig_int, SIGALRM) != 0)
2463 	fprintf(stderr, "Can't catch SIGTERM.\n");
2464     if (mysigact(SIGPIPE, 0, sig_int, SIGALRM) != 0)
2465 	fprintf(stderr, "Can't catch SIGPIPE.\n");
2466     {
2467 	int sig = sigsetjmp(jmpbuffer, 1);
2468 
2469 	if (sig != 0) {
2470 	    ln_log(LNLOG_SWARNING, LNLOG_CTOP,
2471 		    "fetchnews: caught signal %d, shutting down.", sig);
2472 	    nntpquit();
2473 	    if (!rc)
2474 		rc = 2;
2475 	    if (forceactive) {
2476 		error_refetch("caught signal that caused a premature abort.");
2477 		need_refetch = 1;
2478 	    }
2479 	    skip_servers = 1;	/* in this case, jump the while ... loop */
2480 	} else {
2481 	    canjump = 1;
2482 	}
2483     }
2484 
2485     /* remove groups that haven't been read in a long time */
2486     if (!noexpire)
2487 	expire_interesting();
2488 
2489     mgetaline_settimeout(timeout_fetchnews);
2490 
2491     /* main server loop */
2492     for (current_server = servers;
2493 		    !skip_servers && current_server;
2494 		    current_server = current_server->next) {
2495 	if (verbose) {
2496 	    if (current_server->port)
2497 		printf("%s: connecting to port %u...\n",
2498 		       current_server->name, current_server->port);
2499 	    else
2500 		printf("%s: connecting to port nntp...\n",
2501 		       current_server->name);
2502 	}
2503 	fflush(stdout);
2504 	reply = nntpconnect(current_server);
2505 	if (reply) {
2506 	    int r2;
2507 	    if (verbose) {
2508 		int namlen = strlen(current_server->name);
2509 		printf("%s: connected.\n", current_server->name);
2510 		if (stat_is_evil)
2511 		    printf("%s: server software does not implement\n"
2512 			   "%*s  STAT <message-ID> properly,\n"
2513 			   "%*s  using workaround with HEAD instead,\n"
2514 			   "%*s  at the expense of bandwidth.\n",
2515 			   current_server->name, namlen, "", namlen, "", namlen, "");
2516 		else
2517 		    printf("%s: using STAT <message-ID> command.\n",
2518 			    current_server->name);
2519 	    }
2520 
2521 	    if (current_server->username)
2522 		if (!authenticate(current_server) && current_server->password)
2523 		    ln_log(LNLOG_SERR, LNLOG_CTOP,
2524 			    "error: may have been caused by premature authentication and be rather harmless.");
2525 
2526 	    /* Get INN's nnrpd on the phone */
2527 	    xsnprintf(lineout, SIZE_lineout, "MODE READER\r\n");
2528 	    putaline();
2529 	    r2 = nntpreply(current_server);
2530 	    if (r2 < 400) reply = r2;
2531 
2532 	    if (reply == 498) {
2533 		ln_log(LNLOG_SERR, LNLOG_CTOP, "%s: protocol error after sending mode reader",
2534 		       current_server->name);
2535 	    } else {
2536 		if (current_server->nopost == 0) wantpost = 1;
2537 		if (reply == 200 && current_server->nopost == 0) {
2538 		    anypost = 1;
2539 		    if (-1 == postarticles(current_server))
2540 		    {
2541 			nntpquit();
2542 			goto connfail;
2543 		    }
2544 		} else if (verbose) {
2545 		    printf("Not posting to %s: ", current_server->name);
2546 		    if (reply != 200)
2547 			printf("non-permission ");
2548 		    if (current_server->nopost)
2549 			printf("nopost-set ");
2550 		    printf("\n");
2551 		}
2552 		if (!date_is_evil && !getenv("LN_SUPPRESS_DATE"))
2553 		    check_date(current_server);
2554 		if (!postonly && !current_server->noread) {
2555 		    time_t stamp = lastrun;
2556 
2557 		    /* get list of newsgroups or new newsgroups */
2558 		    if (forceactive & 1 || current_server->updateactive) {
2559 			int r;
2560 			if ((r = nntpactive(current_server, &stamp))) {
2561 			    if (forceactive & 2 || r == -2) {
2562 				error_refetch("obtaining the active file failed.");
2563 				need_refetch = 1;
2564 			    }
2565 			    rc = 1;
2566 			}
2567 		    } else {
2568 			if (verbose)
2569 			    printf("%s: not attempting to update newsgroups list\n",
2570 				   current_server->name);
2571 		    }
2572 
2573 		    processupstream(current_server, stamp);
2574 		}
2575 	    }
2576 	    nntpquit();
2577 	    if (verbose)
2578 		printf("%s: conversation completed, disconnected.\n", current_server->name);
2579 	} else { /* reply = nntpconnect */
2580 	    if (verbose)
2581 		printf("%s: connection failed.\n", current_server->name);
2582 connfail:
2583 	    if (forceactive & 1 && current_server->updateactive && !postonly && !current_server->noread) {
2584 		error_refetch("needed to fetch the active list from the server but couldn't connect.");
2585 		need_refetch = 1;
2586 	    }
2587 	    rc = 2;
2588 	}
2589 	if (!usesupplement)
2590 	    break;
2591     }
2592     mergegroups(); /* just in case we were interrupted while downloading
2593 		      the list. */
2594 
2595     (void)mysigact(SIGINT, 0, SIG_IGN, 0);	/* do not siglongjmp any more */
2596     (void)mysigact(SIGTERM, 0, SIG_IGN, 0);	/* do not siglongjmp any more */
2597     (void)mysigact(SIGPIPE, 0, SIG_IGN, 0);	/* SIGPIPE should not happen below */
2598 
2599     if (rc != 0 && oldactive) {
2600 	/* restore old active data to keep low/high marks */
2601 	unsigned long i;
2602 	for (i = 0; i < oldactivesize; i++) {
2603 	    insertgroup(oldactive[i].name, 0, 0, 0);
2604 	}
2605 	mergegroups();
2606 	killactiveread();
2607     }
2608 
2609     freeactive(oldactive);
2610     oldactive = NULL;
2611     oldactivesize = 0;
2612 
2613     if (anypost == 0 && wantpost != 0 && skip_servers == 0 && rc != 2) {
2614 	const char *e = "WARNING: found no server with posting permission!";
2615 	if (!quiet)
2616 	    fprintf(stderr, "%s\n", e);
2617 	syslog(LOG_WARNING, "%s", e);
2618     }
2619 
2620     if (rc == 2) {
2621 	const char *e = "WARNING: some servers have not been queried!";
2622 	if (!quiet)
2623 	    fprintf(stderr, "%s\n", e);
2624 	syslog(LOG_WARNING, "%s", e);
2625     }
2626 
2627     if (fflush(stdout)) {
2628 	/* to avoid double logging of stuff */
2629 	ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot flush standard output: %m");
2630     }
2631 
2632     {
2633 	/* OK, we do a pipe trick to hold the child until the parent
2634 	 * handed over the lock file (this must happen so it does not
2635 	 * inadvertently get removed as stale). The child reads from the
2636 	 * pipe and is blocked until the parent has written to the pipe.
2637 	 * The parent writes to the pipe after it has handed over the
2638 	 * lock.
2639 	 */
2640 	int pfd[2], pipeok;
2641 
2642 	pipeok = pipe(pfd);
2643 
2644 	if (!postonly) {
2645 	    /* only update active.read when have read active from all servers */
2646 	    if (writeactive()) {
2647 		ln_log(LNLOG_SERR, LNLOG_CTOP, "Error writing groupinfo.");
2648 		error_refetch("cannot write groupinfo file.");
2649 		rc = 1;
2650 		/* mark for refetch */
2651 		killactiveread();
2652 	    } else {
2653 		/* active written successfully */
2654 		if (updateactive()) {
2655 		    error_refetch("cannot update active.read file.");
2656 		    need_refetch = 1;
2657 		    rc = 1;
2658 		}
2659 
2660 		if (need_refetch) {
2661 		    /* mark for refetch */
2662 		    killactiveread();
2663 		}
2664 	    }
2665 
2666 #ifdef HAVE_WORKING_FORK
2667 	    pid = waitchild ? -1 : fork();
2668 #else
2669 	    pid = -1; waitchild = 1;
2670 #endif
2671 
2672 	    switch (pid) {
2673 		case -1:
2674 		    if (!waitchild)
2675 			syslog(LOG_NOTICE, "fork: %m, running on parent schedule.");
2676 		    if (verbose)
2677 			printf("updating overview data in the foreground...\n");
2678 		    fixxover();
2679 		    unlink(lockfile);
2680 		    if (verbose)
2681 			printf("done.\n");
2682 		    break;
2683 
2684 		case 0:
2685 		    (void)setsid();
2686 		    if (debugmode)
2687 			syslog(LOG_DEBUG, "Process forked.");
2688 		    if (pipeok == 0) {
2689 			/* wait for parent to hand over the lock */
2690 			char buf[4];
2691 			(void)close(pfd[1]);
2692 			/* we don't REALLY check for errors here, the worst thing
2693 			 * that could happen (in case of a kernal bug...) to
2694 			 * us is that we start early and delete the parent's
2695 			 * lock file before the parent handed it over -- but
2696 			 * at that time, we'll then exit and everything is
2697 			 * in order, with just one bogus message in the log.
2698 			 */
2699 			while (read(pfd[0], buf, sizeof(buf)) < 0) {
2700 			    if (errno != EAGAIN && errno != EINTR)
2701 				break;
2702 			}
2703 			(void)close(pfd[0]); }
2704 
2705 			fixxover();
2706 			freeactive(active);
2707 			if (unlink(lockfile))
2708 			    ln_log(LNLOG_SERR, LNLOG_CTOP,
2709 				    "unlink(\"%s\"): %m", lockfile);
2710 			if (debugmode)
2711 			    syslog(LOG_DEBUG, "Process done.");
2712 			freeconfig();
2713 			_exit(0);
2714 			break;
2715 
2716 		default:
2717 			{
2718 			    int lock_ok = handover_lock(pid);
2719 
2720 			    if (verbose) puts("Started process to update overview data in the background.\nNetwork activity has finished.");
2721 			    if (pipeok == 0) {
2722 				/* tell child it has the lock */
2723 				(void)close(pfd[0]);
2724 				(void)writes(pfd[1], "GO");
2725 				(void)close(pfd[1]);
2726 			    }
2727 
2728 			    if (lock_ok) {
2729 				/* could not hand over lock file to child, so wait until it
2730 				   dies */
2731 				syslog(LOG_NOTICE, "could not hand over lockfile to child %lu: %m, "
2732 					"waiting until child is done.",
2733 					(unsigned long)pid);
2734 				(void)waitpid(pid, NULL, 0);
2735 			    } else {
2736 				syslog(LOG_INFO, "child has process ID %lu",
2737 					(unsigned long)pid);
2738 			    }
2739 			    break;
2740 			}
2741 	    }
2742 	} else { /* if (postonly) */
2743 	    unlink(lockfile);
2744 	}
2745     }
2746 
2747     freeactive(active);
2748     freeconfig();
2749     exit(rc);
2750 }
2751