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