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

xoverutil.c
Go to the documentation of this file.
1/*
2libutil -- handling xover records
3
4Written by Arnt Gulbrandsen <agulbra@troll.no> and copyright 1995
5Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47
622646949.
7Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>
8and Randolf Skerka <Randolf.Skerka@gmx.de>.
9Copyright of the modifications 1997.
10Modified by Kent Robotti <robotti@erols.com>. Copyright of the
11modifications 1998.
12Modified by Markus Enzenberger <enz@cip.physik.uni-muenchen.de>.
13Copyright of the modifications 1998.
14Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>.
15Copyright of the modifications 1998, 1999.
16Modified by Ralf Wildenhues <ralf.wildenhues@gmx.de>
17Copyright of the modifications 2002.
18Modified by Matthias Andree <matthias.andree@gmx.de>
19Copyright of the modifications 2000 - 2005, 2007.
20
21See file COPYING for restrictions on the use of this software.
22*/
23
24#include "leafnode.h"
25#include <fcntl.h>
26#include <sys/param.h>
27#include <sys/socket.h>
28#include <netinet/in.h>
29#include <ctype.h>
30#include <errno.h>
31#include <limits.h>
32#include <stdlib.h>
33#include <netdb.h>
34#include <signal.h>
35#include <stdio.h>
36#include <string.h>
37#include <syslog.h>
38#include <sys/types.h>
39#include <sys/stat.h>
40#include <unistd.h>
41#include "system.h"
42#include "strlcpy.h"
43#include "ln_log.h"
44
45static void
47{
48 while (*t) {
49 if (*t == '\t')
50 *t = ' ';
51 t++;
52 }
53
54}
55
56static /*@null@*/ /*@only@*/
57char *getxoverline(const char *filename, const char **e /** error message is stored here */)
58{
59 char *l;
60 const char *em;
61 char *result;
62 FILE *f;
63
64 result = NULL;
65 *e = NULL;
66 debug = 0;
67 if ((f = fopen(filename, "r"))) {
68 char *from, *subject, *date, *msgid, *references, *lines, *xref;
69 unsigned long bytes, linecount;
70 char **h;
71 int body;
72
73 from = subject = date = msgid = references = xref = lines = NULL;
74 bytes = linecount = 0;
75 h = NULL;
76 body = 0;
77
78 while (!feof(f) && ((l = getaline(f)) != NULL)) {
79 tabstospaces(l);
80 linecount++;
81 bytes += strlen(l) + 2; /* normalize CR LF -> add 2 per line */
82 if (body || !l) {
83 /* do nothing */
84 } else if (!body && !*l) {
85 linecount = 0;
86 body = 1;
87 } else if (*l && isspace((unsigned char)*l)) {
88 /* cater for folded headers */
89 if (h) {
90 (*h) = critrealloc(*h, strlen(*h) + strlen(l) + 1,
91 "extending header");
92 strcat(*h, l); /* RATS: ignore */
93 }
94 } else if (!from && !strncasecmp("From:", l, 5)) {
95 l += 5;
96 SKIPLWS(l);
97 if (*l) {
98 from = critstrdup(l, "getxoverline");
99 h = &from;
100 }
101 } else if (!subject && !strncasecmp("Subject:", l, 8)) {
102 l += 8;
103 SKIPLWS(l);
104 if (*l) {
105 subject = critstrdup(l, "getxoverline");
106 h = &subject;
107 }
108 } else if (!date && !strncasecmp("Date:", l, 5)) {
109 l += 5;
110 SKIPLWS(l);
111 if (*l) {
112 date = critstrdup(l, "getxoverline");
113 h = &date;
114 }
115 } else if (!msgid && !strncasecmp("Message-ID:", l, 11)) {
116 l += 11;
117 SKIPLWS(l);
118 if (*l) {
119 msgid = critstrdup(l, "getxoverline");
120 h = &msgid;
121 }
122 } else if (!references && !strncasecmp("References:", l, 11)) {
123 l += 11;
124 SKIPLWS(l);
125 if (*l) {
126 references = critstrdup(l, "getxoverline");
127 h = &references;
128 }
129 } else if (!lines && !strncasecmp("Lines:", l, 6)) {
130 l += 6;
131 SKIPLWS(l);
132 if (*l) {
133 lines = critstrdup(l, "getxoverline");
134 h = &lines;
135 }
136 } else if (!xref && !strncasecmp("Xref:", l, 5)) {
137 l += 5;
138 SKIPLWS(l);
139 if (*l) {
140 xref = critstrdup(l, "getxoverline");
141 h = &xref;
142 }
143 } else {
144 h = NULL;
145 }
146 }
147 if (from != NULL && date != NULL && subject != NULL &&
148 msgid != NULL && bytes) {
149 result = critmalloc(strlen(filename) + strlen(subject) + strlen(from) +
150 strlen(date) + strlen(msgid) +
151 (references ? strlen(references) : 0) +
152 100 + (xref ? strlen(xref) : 0),
153 "computing overview line");
154 sprintf(result, "%s\t%s\t%s\t%s\t%s\t%s\t%lu\t%lu", /* RATS: ignore */
155 filename, subject, from, date, msgid,
156 references ? references : "",
157 bytes, lines ? strtoul(lines, NULL, 10) : linecount);
158 if (xref) {
159 strcat(result, "\tXref: "); /* RATS: ignore */
160 strcat(result, xref); /* RATS: ignore */
161 }
162 } else {
163 if (from == NULL)
164 *e = "missing From: header";
165 else if (date == NULL)
166 *e = "missing Date: header";
167 else if (subject == NULL)
168 *e = "missing Subject: header";
169 else if (msgid == NULL)
170 *e = "missing Message-ID: header";
171 else if (bytes == 0)
172 *e = "article has 0 bytes";
173 }
174 (void)fclose(f);
175 if (from)
176 free(from);
177 if (date)
178 free(date);
179 if (subject)
180 free(subject);
181 if (msgid)
182 free(msgid);
183 if (references)
184 free(references);
185 if (lines)
186 free(lines);
187 if (xref)
188 free(xref);
189 } else {
191 "error: getxoverline: cannot open %s: %m", filename);
192 }
194 if (result && !legalxoverline(result, &em)) {
195 *e = em;
196 free(result);
197 result = NULL;
198 }
199 return result;
200}
201
202/*
203 * return 1 if xover is a legal overview line, 0 else
204 */
205int
206legalxoverline(const char *xover, const char **e)
207{
208 const char *p;
209 const char *q;
210
211 if (!xover)
212 return 0;
213
214 /* anything that isn't tab, printable ascii, or latin-* -> kill */
215
216 p = xover;
217 while (*p) {
218 int c = (unsigned char)*p++;
219
220 if ((c != '\t' && c < ' ') || (c > 126 && c < 160)) {
221 *e = "non-printable characters in headers (relaxed check allows for iso-8859*)";
222 return 0;
223 }
224 }
225
226 p = xover;
227 q = strchr(p, '\t');
228 if (!q) {
229 *e = "missing Subject: header";
230 return 0;
231 }
232
233 /* article number */
234
235 while (p != q) {
236 if (!isdigit((unsigned char)*p)) {
237 *e = "article number contains non-digit characters";
238 return 0;
239 }
240 p++;
241 }
242
243 p = q + 1;
244 q = strchr(p, '\t');
245 if (!q) {
246 *e = "missing From: header";
247 return 0;
248 }
249
250 /* subject: no limitations */
251
252 p = q + 1;
253 q = strchr(p, '\t');
254 if (!q) {
255 *e = "missing Date: header";
256 return 0;
257 }
258
259 /* from: no limitations */
260
261 p = q + 1;
262 q = strchr(p, '\t');
263 if (!q) {
264 *e = "missing Message-ID: header";
265 return 0;
266 }
267
268 /* date: no limitations */
269
270 p = q + 1;
271 q = strchr(p, '\t');
272 if (!q) {
273 *e = "missing References: or Bytes: header";
274 return 0;
275 }
276
277 /* message-id: <*@*> */
278
279 if (*p != '<') {
280 *e = "Message-ID: does not start with \"<\"";
281 return 0;
282 }
283 while (p != q && *p != '@' && *p != '>' && *p != ' ')
284 p++;
285 if (*p != '@') {
286 *e = "Message-ID: does not contain @";
287 return 0;
288 }
289 while (p != q && *p != '>' && *p != ' ')
290 p++;
291 if (*p != '>') {
292 *e = "Message-ID: does not end with \">\"";
293 return 0;
294 }
295 if (++p != q) {
296 *e = "Message-ID: does not end with \">\"";
297 return 0;
298 }
299
300 p = q + 1;
301 q = strchr(p, '\t');
302 if (!q) {
303 *e = "missing Bytes: header";
304 return 0;
305 }
306
307 /* references: a series of <*@*> separated by space */
308
309#if 0
310 while (p != q) {
311 /* reference validation - users don't like it */
312 if (*p != '<') {
313 *e = "References: does not start with \"<\"";
314 return 0;
315 }
316 while (p != q && *p != '@' && *p != '>' && *p != ' ')
317 p++;
318 if (*p != '@') {
319 *e = "References: does not contain @";
320 return 0;
321 }
322 while (p != q && *p != '>' && *p != ' ')
323 p++;
324 if (*p++ != '>') {
325 *e = "References: does not end with \">\"";
326 return 0;
327 }
328 while (p != q && *p == ' ')
329 p++;
330 }
331#endif
332
333 p = q + 1;
334 q = strchr(p, '\t');
335 if (!q) {
336 *e = "missing Lines: header";
337 return 0;
338 }
339
340 /* byte count */
341
342 while (p != q) {
343 if (!isdigit((unsigned char)*p)) {
344 *e = "non-digit character in Bytes: header";
345 return 0;
346 }
347 p++;
348 }
349
350 p = q + 1;
351 q = strchr(p, '\t');
352
353 /* line count */
354
355 while (p && *p && p != q) {
356 if (!isdigit((unsigned char)*p)) {
357 *e = "non-digit character in Lines: header";
358 return 0;
359 }
360 p++;
361 }
362
363 if (!q) {
364 *e = "missing Xref: entry";
365 return 0;
366 }
367
368 {
369 p = q + 1;
370
371 /* xref */
372 if (0 != strncasecmp(p, "Xref:", 5)) {
373 *e = "Xref header is missing or lacks Xref: tag";
374 return 0;
375 }
376 }
377
378 return 1;
379}
380
381static void killcwd(void) {
382 char *t = NULL;
383 size_t s_t;
384
385 if (agetcwd(&t, &s_t)) {
386 if (chdir(spooldir)) {
387 ln_log(LNLOG_SERR, LNLOG_CTOP, "error: cannot chdir(%s): %m", spooldir);
388 }
389 if (rmdir(t) && errno != ENOTEMPTY && errno != EEXIST) {
390 ln_log(LNLOG_SERR, LNLOG_CTOP, "error: cannot rmdir(%s): %m", t);
391 }
392 free(t);
393 }
394}
395
396void freexover(void) {
397 unsigned long art;
398
399 if (xoverinfo) {
400 for (art = xfirst; art <= xlast; art++) {
401 if (xoverinfo[art - xfirst].text) {
402 free(xoverinfo[art - xfirst].text);
403 xoverinfo[art - xfirst].text = NULL;
404 }
405 }
406 free(xoverinfo);
407 xoverinfo = NULL;
408 }
409
410}
411
412/* utility routine to pull the xover info into memory
413 returns 0 if there's some error, non-zero else */
414int
416{
417 DIR *d;
418 struct dirent *de;
419 int fd;
420 struct stat st;
421 unsigned long art;
422 char *overview = NULL;
423 int error;
424 char *p, *q;
425 char *tt = NULL; size_t s_tt;
426
427 error = 0;
428
429 /* free any memory left over from last time */
430 freexover();
431
432 /* find article range */
433 d = opendir(".");
434 if (!d) {
435 ln_log(LNLOG_SERR, LNLOG_CTOP, "error: opendir: %m");
436 return 0;
437 }
438
439 xfirst = ULONG_MAX;
440 xlast = 0;
441 while ((de = readdir(d))) {
442 /* weed out temporary .overview files from aborted earlier run */
443 if (0 == strncmp(".overview.", de->d_name, 10))
444 log_unlink(de->d_name, 0);
445 if (!isdigit((unsigned char)de->d_name[0]))
446 continue; /* skip files that don't start with a digit */
447 /* WARNING: strtoul will happily return the negated value when
448 * fed a string that starts with a minus character! */
449 art = strtoul(de->d_name, &p, 10);
450 if (art && p && !*p) {
451 if (art < xfirst)
452 xfirst = art;
453 if (art > xlast)
454 xlast = art;
455 }
456 }
457
458 if (xlast < xfirst) {
459 /* we did not find any article files (1, 17, 815 or the like) */
460 closedir(d);
461 (void)unlink(".overview");
462 if (debugmode) {
463 char *t = NULL; size_t s_t;
464 if (!agetcwd(&t, &s_t)) {
465 ln_log(LNLOG_SERR, LNLOG_CGROUP, "error: getcwd: %m");
466 } else {
467 syslog(LOG_DEBUG, "removed .overview file for %s", t);
468 free(t);
469 }
470 }
471 killcwd();
472 return 0;
473 }
474
475 /* next, read .overview, correct it if it seems too different from
476 what the directory implies, and write the result back */
477 rewinddir(d);
478
479 xoverinfo = (struct xoverinfo *)
480 critmalloc(sizeof(struct xoverinfo) * (xlast + 1 - xfirst),
481 "allocating overview array");
482 memset(xoverinfo, 0, sizeof(struct xoverinfo) * (xlast + 1 - xfirst));
483
484 if ((fd = open(".overview", O_RDONLY)) >= 0 &&
485 fstat(fd, &st) == 0) {
486 overview = (char *)critmalloc(st.st_size + 1, "getxover");
487 if ((off_t) read(fd, overview, st.st_size) != st.st_size) {
488 int e = errno;
489 char *t = NULL; size_t s_t;
490 /* short read */
491 close(fd);
492
493 if (!agetcwd(&t, &s_t)) {
494 ln_log(LNLOG_SERR, LNLOG_CGROUP, "error: getcwd: %m");
495 } else {
497 "warning: short read on %s/.overview: %s",
498 t, strerror(e));
499 free(t);
500 }
501 } else {
502 close(fd);
503 overview[st.st_size] = '\0';
504
505 /* okay, we have the content, so let's parse it roughly */
506 /* iterate line-wise */
507 p = overview;
508 while (p && *p) {
509 const char *t;
510
511 while (p && isspace((unsigned char)*p))
512 p++;
513 q = strchr(p, '\n');
514 if (q)
515 *q++ = '\0';
516
517
518 art = strtoul(p, NULL, 10);
519 if (legalxoverline(p, &t)) {
520 if (art > xlast || art < xfirst) {
521 error++;
522 } else if (xoverinfo[art - xfirst].text) {
523 char *tt = NULL; size_t s_tt;
524 error++;
525 if (!agetcwd(&tt, &s_tt)) {
526 ln_log(LNLOG_SERR, LNLOG_CARTICLE, "error: getcwd: %m");
527 } else {
528 ln_log(LNLOG_SERR, LNLOG_CARTICLE, "error: multiple lines for article %lu "
529 "in .overview for %s", art, tt);
530 free(tt);
531 }
532 free (xoverinfo[art - xfirst].text);
533 xoverinfo[art - xfirst].text = NULL;
534 xoverinfo[art - xfirst].exists = -1;
535 } else if (xoverinfo[art - xfirst].exists == 0) {
536 xoverinfo[art - xfirst].text = critstrdup(p, "getxover");
537 }
538 } else {
539 char *tt = NULL; size_t s_tt;
540 if (!agetcwd(&tt, &s_tt)) {
541 ln_log(LNLOG_SERR, LNLOG_CTOP, "error: getcwd: %m");
542 } else {
543 ln_log(LNLOG_SNOTICE, LNLOG_CARTICLE, "illegal line for article %lu in .overview for %s: %s", art, tt, t);
544 free(tt);
545 }
546 }
547
548 p = q;
549 } /* while p && *p */
550 } /* if read went fine */
551 } /* if open && fstat */
552
553 if (!agetcwd(&tt, &s_tt)) {
554 ln_log(LNLOG_SERR, LNLOG_CTOP, "error: getcwd: %m");
555 closedir(d);
556 return 0;
557 }
558
559 /* so, what was missing? */
560 while ((de = readdir(d))) {
561 if (de->d_name[0] == '.')
562 continue;
563 art = strtoul(de->d_name, &p, 10);
564 if (p && !*p && art >= xfirst && art <= xlast) {
565 if (!xoverinfo[art - xfirst].text) {
566 const char *e;
567
568 xoverinfo[art - xfirst].exists = 0;
569 if (debugmode) {
570 syslog(LOG_DEBUG, "reading XOVER info from %s/%s",
571 tt, de->d_name);
572 }
573 error++;
574 if ((xoverinfo[art - xfirst].text =
575 getxoverline(de->d_name, &e)) == NULL) {
577 "article %s/%s contained illegal headers: %s",
578 tt, de->d_name, e);
579 if (truncate(de->d_name, (off_t)0))
581 "warning: failed to truncate broken %s/%s to 0 size: %m",
582 tt,de->d_name);
583 if ((lstat(de->d_name, &st) == 0) && S_ISREG(st.st_mode)) {
584 if (unlink(de->d_name))
586 "warning: failed to remove broken %s/%s: %m", tt, de->d_name);
587 } else {
589 "warning: %s/%s is not a regular file", tt, de->d_name);
590 }
591 }
592 }
593 }
594
595 if (art >= xfirst && art <= xlast && xoverinfo[art - xfirst].text) {
596 xoverinfo[art - xfirst].exists = 1;
597 } else {
598 /* kill non-article files, like "core" */
599 if (art == 0)
600 {
601 if (unlink(de->d_name)
602 && errno != EISDIR
603 && errno != EPERM
604 && verbose) {
606 "warning: deleting junk %s/%s failed: %s",
607 tt, de->d_name, strerror(errno));
608 }
609 }
610 }
611 } /* while (de = readdir(d)) */
612
613 /* count removed articles */
614 for (art = xfirst; art <= xlast; art++) {
615 if (xoverinfo[art - xfirst].text
616 && !xoverinfo[art - xfirst].exists) {
617 ++error;
618 free(xoverinfo[art - xfirst].text);
619 xoverinfo[art - xfirst].text = NULL;
620 }
621 }
622
623 /* if something had to be fixed, write a better file to disk for
624 next time - race conditions here, but none dangerous */
625 if (error) {
626 int wfd;
627 char newfile[20]; /* RATS: ignore */
628
629 if (debugmode)
630 syslog(LOG_DEBUG, "updated %d line%s in %s/.overview",
631 error, PLURAL(error), tt);
632
633 strcpy(newfile, ".overview.XXXXXX");
634 if ((wfd = mkstemp(newfile)) != -1) {
635 int va;
636
637 va = 1;
638 for (art = xfirst; art <= xlast; art++) {
639 if (xoverinfo[art - xfirst].exists
640 && xoverinfo[art - xfirst].text) {
641 if (writes(wfd, xoverinfo[art - xfirst].text) == - 1
642 || writes(wfd, "\n") == -1)
643 {
645 "error: write() for .overview failed: %m");
646 va = 0;
647 break;
648 }
649 }
650 }
651 if (fchmod(wfd, 0664)) va = 0;
652 if (fsync(wfd)) va = 0;
653 if (close(wfd)) va = 0;
654 if (va) {
655 if (rename(newfile, ".overview")) {
656 if (unlink(newfile))
658 "error: unlink(%s) failed: %m", newfile);
659 else
661 "error: rename(%s/%s, .overview) failed: %m",
662 tt, newfile);
663 } else {
664 if (debugmode)
665 syslog(LOG_DEBUG, "wrote %s/.overview", tt);
666 }
667 } else {
668 unlink(newfile);
669 /* the group must be newly empty: I want to keep the old
670 .overview file I think */
671 }
672 } else {
674 "error: mkstemp of new .overview failed: %m");
675 }
676 }
677
678 closedir(d);
679 free(tt);
680 if (overview) {
681 free(overview);
682 }
683 return 1;
684}
685
686void
688{
689 DIR *d;
690 struct dirent *de;
691 char s[SIZE_s + 1];
692
693 xsnprintf(s, SIZE_s, "%s/interesting.groups", spooldir);
694 d = opendir(s);
695 if (!d) {
696 ln_log(LNLOG_SERR, LNLOG_CGROUP, "error: opendir %s: %m", s);
697 return;
698 }
699
700 while ((de = readdir(d))) {
701 if (isalnum((unsigned char)*(de->d_name)) && findgroup(de->d_name)) {
702 if (chdirgroup(de->d_name, FALSE))
703 getxover();
704 freexover();
705 }
706 }
707 closedir(d);
708}
struct newsgroup * findgroup(const char *name)
Definition: activutil.c:260
int agetcwd(char **buf, size_t *size)
Definition: agetcwd.c:10
int verbose
Definition: applyfilter.c:31
int debug
Definition: applyfilter.c:30
int debugmode
Definition: configutil.c:67
char * critrealloc(char *a, size_t size, const char *message)
Definition: critmem.c:79
char * critstrdup(const char *source, const char *message)
Definition: critmem.c:92
char * critmalloc(size_t size, const char *message)
Definition: critmem.c:61
char * getaline(FILE *f)
Definition: getaline.c:84
#define PLURAL(no)
Definition: leafnode.h:35
#define SKIPLWS(p)
Definition: leafnode.h:398
const char * spooldir
unsigned long xlast
Definition: miscutil.c:64
int log_unlink(const char *f, int ignore_enoent)
Definition: log_unlink.c:13
unsigned long xfirst
Definition: miscutil.c:64
#define SIZE_s
Definition: leafnode.h:287
int mkstemp(char *)
Definition: mkstemp.c:27
ssize_t writes(int fd, const char *string)
Definition: writes.c:7
#define FALSE
Definition: leafnode.h:32
int xsnprintf(char *str, size_t n, const char *format,...)
Definition: miscutil.c:798
int chdirgroup(const char *group, int creatdir)
Definition: miscutil.c:446
void ln_log(int sev, int ctx, const char *format,...)
Definition: ln_log.c:103
#define LNLOG_SERR
Definition: ln_log.h:13
#define LNLOG_SWARNING
Definition: ln_log.h:14
#define LNLOG_SINFO
Definition: ln_log.h:16
#define LNLOG_CTOP
Definition: ln_log.h:22
#define LNLOG_CGROUP
Definition: ln_log.h:24
#define LNLOG_CARTICLE
Definition: ln_log.h:25
#define LNLOG_SNOTICE
Definition: ln_log.h:15
int exists
Definition: leafnode.h:379
char * text
Definition: leafnode.h:378
#define dirent
Definition: system.h:21
void freexover(void)
Definition: xoverutil.c:396
static void killcwd(void)
Definition: xoverutil.c:381
static char * getxoverline(const char *filename, const char **e)
Definition: xoverutil.c:57
int legalxoverline(const char *xover, const char **e)
Definition: xoverutil.c:206
static void tabstospaces(char *t)
Definition: xoverutil.c:46
void fixxover(void)
Definition: xoverutil.c:687
int getxover(void)
Definition: xoverutil.c:415