/* libutil -- handling xover records Written by Arnt Gulbrandsen and copyright 1995 Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47 22646949. Modified by Cornelius Krasel and Randolf Skerka . Copyright of the modifications 1997. Modified by Kent Robotti . Copyright of the modifications 1998. Modified by Markus Enzenberger . Copyright of the modifications 1998. Modified by Cornelius Krasel . Copyright of the modifications 1998, 1999. Modified by Ralf Wildenhues Copyright of the modifications 2002. Modified by Matthias Andree Copyright of the modifications 2000 - 2005, 2007. See file COPYING for restrictions on the use of this software. */ #include "leafnode.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "system.h" #include "strlcpy.h" #include "ln_log.h" static void tabstospaces(char *t) { while (*t) { if (*t == '\t') *t = ' '; t++; } } static /*@null@*/ /*@only@*/ char *getxoverline(const char *filename, const char **e /** error message is stored here */) { char *l; const char *em; char *result; FILE *f; result = NULL; *e = NULL; debug = 0; if ((f = fopen(filename, "r"))) { char *from, *subject, *date, *msgid, *references, *lines, *xref; unsigned long bytes, linecount; char **h; int body; from = subject = date = msgid = references = xref = lines = NULL; bytes = linecount = 0; h = NULL; body = 0; while (!feof(f) && ((l = getaline(f)) != NULL)) { tabstospaces(l); linecount++; bytes += strlen(l) + 2; /* normalize CR LF -> add 2 per line */ if (body || !l) { /* do nothing */ } else if (!body && !*l) { linecount = 0; body = 1; } else if (*l && isspace((unsigned char)*l)) { /* cater for folded headers */ if (h) { (*h) = critrealloc(*h, strlen(*h) + strlen(l) + 1, "extending header"); strcat(*h, l); /* RATS: ignore */ } } else if (!from && !strncasecmp("From:", l, 5)) { l += 5; SKIPLWS(l); if (*l) { from = critstrdup(l, "getxoverline"); h = &from; } } else if (!subject && !strncasecmp("Subject:", l, 8)) { l += 8; SKIPLWS(l); if (*l) { subject = critstrdup(l, "getxoverline"); h = &subject; } } else if (!date && !strncasecmp("Date:", l, 5)) { l += 5; SKIPLWS(l); if (*l) { date = critstrdup(l, "getxoverline"); h = &date; } } else if (!msgid && !strncasecmp("Message-ID:", l, 11)) { l += 11; SKIPLWS(l); if (*l) { msgid = critstrdup(l, "getxoverline"); h = &msgid; } } else if (!references && !strncasecmp("References:", l, 11)) { l += 11; SKIPLWS(l); if (*l) { references = critstrdup(l, "getxoverline"); h = &references; } } else if (!lines && !strncasecmp("Lines:", l, 6)) { l += 6; SKIPLWS(l); if (*l) { lines = critstrdup(l, "getxoverline"); h = &lines; } } else if (!xref && !strncasecmp("Xref:", l, 5)) { l += 5; SKIPLWS(l); if (*l) { xref = critstrdup(l, "getxoverline"); h = &xref; } } else { h = NULL; } } if (from != NULL && date != NULL && subject != NULL && msgid != NULL && bytes) { result = critmalloc(strlen(filename) + strlen(subject) + strlen(from) + strlen(date) + strlen(msgid) + (references ? strlen(references) : 0) + 100 + (xref ? strlen(xref) : 0), "computing overview line"); sprintf(result, "%s\t%s\t%s\t%s\t%s\t%s\t%lu\t%lu", /* RATS: ignore */ filename, subject, from, date, msgid, references ? references : "", bytes, lines ? strtoul(lines, NULL, 10) : linecount); if (xref) { strcat(result, "\tXref: "); /* RATS: ignore */ strcat(result, xref); /* RATS: ignore */ } } else { if (from == NULL) *e = "missing From: header"; else if (date == NULL) *e = "missing Date: header"; else if (subject == NULL) *e = "missing Subject: header"; else if (msgid == NULL) *e = "missing Message-ID: header"; else if (bytes == 0) *e = "article has 0 bytes"; } (void)fclose(f); if (from) free(from); if (date) free(date); if (subject) free(subject); if (msgid) free(msgid); if (references) free(references); if (lines) free(lines); if (xref) free(xref); } else { ln_log(LNLOG_SERR, LNLOG_CARTICLE, "error: getxoverline: cannot open %s: %m", filename); } debug = debugmode; if (result && !legalxoverline(result, &em)) { *e = em; free(result); result = NULL; } return result; } /* * return 1 if xover is a legal overview line, 0 else */ int legalxoverline(const char *xover, const char **e) { const char *p; const char *q; if (!xover) return 0; /* anything that isn't tab, printable ascii, or latin-* -> kill */ p = xover; while (*p) { int c = (unsigned char)*p++; if ((c != '\t' && c < ' ') || (c > 126 && c < 160)) { *e = "non-printable characters in headers (relaxed check allows for iso-8859*)"; return 0; } } p = xover; q = strchr(p, '\t'); if (!q) { *e = "missing Subject: header"; return 0; } /* article number */ while (p != q) { if (!isdigit((unsigned char)*p)) { *e = "article number contains non-digit characters"; return 0; } p++; } p = q + 1; q = strchr(p, '\t'); if (!q) { *e = "missing From: header"; return 0; } /* subject: no limitations */ p = q + 1; q = strchr(p, '\t'); if (!q) { *e = "missing Date: header"; return 0; } /* from: no limitations */ p = q + 1; q = strchr(p, '\t'); if (!q) { *e = "missing Message-ID: header"; return 0; } /* date: no limitations */ p = q + 1; q = strchr(p, '\t'); if (!q) { *e = "missing References: or Bytes: header"; return 0; } /* message-id: <*@*> */ if (*p != '<') { *e = "Message-ID: does not start with \"<\""; return 0; } while (p != q && *p != '@' && *p != '>' && *p != ' ') p++; if (*p != '@') { *e = "Message-ID: does not contain @"; return 0; } while (p != q && *p != '>' && *p != ' ') p++; if (*p != '>') { *e = "Message-ID: does not end with \">\""; return 0; } if (++p != q) { *e = "Message-ID: does not end with \">\""; return 0; } p = q + 1; q = strchr(p, '\t'); if (!q) { *e = "missing Bytes: header"; return 0; } /* references: a series of <*@*> separated by space */ #if 0 while (p != q) { /* reference validation - users don't like it */ if (*p != '<') { *e = "References: does not start with \"<\""; return 0; } while (p != q && *p != '@' && *p != '>' && *p != ' ') p++; if (*p != '@') { *e = "References: does not contain @"; return 0; } while (p != q && *p != '>' && *p != ' ') p++; if (*p++ != '>') { *e = "References: does not end with \">\""; return 0; } while (p != q && *p == ' ') p++; } #endif p = q + 1; q = strchr(p, '\t'); if (!q) { *e = "missing Lines: header"; return 0; } /* byte count */ while (p != q) { if (!isdigit((unsigned char)*p)) { *e = "non-digit character in Bytes: header"; return 0; } p++; } p = q + 1; q = strchr(p, '\t'); /* line count */ while (p && *p && p != q) { if (!isdigit((unsigned char)*p)) { *e = "non-digit character in Lines: header"; return 0; } p++; } if (!q) { *e = "missing Xref: entry"; return 0; } { p = q + 1; /* xref */ if (0 != strncasecmp(p, "Xref:", 5)) { *e = "Xref header is missing or lacks Xref: tag"; return 0; } } return 1; } static void killcwd(void) { char *t = NULL; size_t s_t; if (agetcwd(&t, &s_t)) { if (chdir(spooldir)) { ln_log(LNLOG_SERR, LNLOG_CTOP, "error: cannot chdir(%s): %m", spooldir); } if (rmdir(t) && errno != ENOTEMPTY) { ln_log(LNLOG_SERR, LNLOG_CTOP, "error: cannot rmdir(%s): %m", t); } free(t); } } void freexover(void) { unsigned long art; if (xoverinfo) { for (art = xfirst; art <= xlast; art++) { if (xoverinfo[art - xfirst].text) { free(xoverinfo[art - xfirst].text); xoverinfo[art - xfirst].text = NULL; } } free(xoverinfo); xoverinfo = NULL; } } /* utility routine to pull the xover info into memory returns 0 if there's some error, non-zero else */ int getxover(void) { DIR *d; struct dirent *de; int fd; struct stat st; unsigned long art; char *overview = NULL; int error; char *p, *q; char *tt = NULL; size_t s_tt; error = 0; /* free any memory left over from last time */ freexover(); /* find article range */ d = opendir("."); if (!d) { ln_log(LNLOG_SERR, LNLOG_CTOP, "error: opendir: %m"); return 0; } xfirst = ULONG_MAX; xlast = 0; while ((de = readdir(d))) { /* weed out temporary .overview files from aborted earlier run */ if (0 == strncmp(".overview.", de->d_name, 10)) log_unlink(de->d_name, 0); if (!isdigit((unsigned char)de->d_name[0])) continue; /* skip files that don't start with a digit */ /* WARNING: strtoul will happily return the negated value when * fed a string that starts with a minus character! */ art = strtoul(de->d_name, &p, 10); if (art && p && !*p) { if (art < xfirst) xfirst = art; if (art > xlast) xlast = art; } } if (xlast < xfirst) { /* we did not find any article files (1, 17, 815 or the like) */ closedir(d); (void)unlink(".overview"); if (debugmode) { char *t = NULL; size_t s_t; if (!agetcwd(&t, &s_t)) { ln_log(LNLOG_SERR, LNLOG_CGROUP, "error: getcwd: %m"); } else { syslog(LOG_DEBUG, "removed .overview file for %s", t); free(t); } } killcwd(); return 0; } /* next, read .overview, correct it if it seems too different from what the directory implies, and write the result back */ rewinddir(d); xoverinfo = (struct xoverinfo *) critmalloc(sizeof(struct xoverinfo) * (xlast + 1 - xfirst), "allocating overview array"); memset(xoverinfo, 0, sizeof(struct xoverinfo) * (xlast + 1 - xfirst)); if ((fd = open(".overview", O_RDONLY)) >= 0 && fstat(fd, &st) == 0) { overview = (char *)critmalloc(st.st_size + 1, "getxover"); if ((off_t) read(fd, overview, st.st_size) != st.st_size) { int e = errno; char *t = NULL; size_t s_t; /* short read */ close(fd); if (!agetcwd(&t, &s_t)) { ln_log(LNLOG_SERR, LNLOG_CGROUP, "error: getcwd: %m"); } else { ln_log(LNLOG_SWARNING, LNLOG_CGROUP, "warning: short read on %s/.overview: %s", t, strerror(e)); free(t); } } else { close(fd); overview[st.st_size] = '\0'; /* okay, we have the content, so let's parse it roughly */ /* iterate line-wise */ p = overview; while (p && *p) { const char *t; while (p && isspace((unsigned char)*p)) p++; q = strchr(p, '\n'); if (q) *q++ = '\0'; art = strtoul(p, NULL, 10); if (legalxoverline(p, &t)) { if (art > xlast || art < xfirst) { error++; } else if (xoverinfo[art - xfirst].text) { char *tt = NULL; size_t s_tt; error++; if (!agetcwd(&tt, &s_tt)) { ln_log(LNLOG_SERR, LNLOG_CARTICLE, "error: getcwd: %m"); } else { ln_log(LNLOG_SERR, LNLOG_CARTICLE, "error: multiple lines for article %lu " "in .overview for %s", art, tt); free(tt); } free (xoverinfo[art - xfirst].text); xoverinfo[art - xfirst].text = NULL; xoverinfo[art - xfirst].exists = -1; } else if (xoverinfo[art - xfirst].exists == 0) { xoverinfo[art - xfirst].text = critstrdup(p, "getxover"); } } else { char *tt = NULL; size_t s_tt; if (!agetcwd(&tt, &s_tt)) { ln_log(LNLOG_SERR, LNLOG_CTOP, "error: getcwd: %m"); } else { ln_log(LNLOG_SNOTICE, LNLOG_CARTICLE, "illegal line for article %lu in .overview for %s: %s", art, tt, t); free(tt); } } p = q; } /* while p && *p */ } /* if read went fine */ } /* if open && fstat */ if (!agetcwd(&tt, &s_tt)) { ln_log(LNLOG_SERR, LNLOG_CTOP, "error: getcwd: %m"); closedir(d); return 0; } /* so, what was missing? */ while ((de = readdir(d))) { if (de->d_name[0] == '.') continue; art = strtoul(de->d_name, &p, 10); if (p && !*p && art >= xfirst && art <= xlast) { if (!xoverinfo[art - xfirst].text) { const char *e; xoverinfo[art - xfirst].exists = 0; if (debugmode) { syslog(LOG_DEBUG, "reading XOVER info from %s/%s", tt, de->d_name); } error++; if ((xoverinfo[art - xfirst].text = getxoverline(de->d_name, &e)) == NULL) { ln_log(LNLOG_SINFO, LNLOG_CARTICLE, "article %s/%s contained illegal headers: %s", tt, de->d_name, e); if (truncate(de->d_name, (off_t)0)) ln_log(LNLOG_SWARNING, LNLOG_CARTICLE, "warning: failed to truncate broken %s/%s to 0 size: %m", tt,de->d_name); if ((lstat(de->d_name, &st) == 0) && S_ISREG(st.st_mode)) { if (unlink(de->d_name)) ln_log(LNLOG_SWARNING, LNLOG_CARTICLE, "warning: failed to remove broken %s/%s: %m", tt, de->d_name); } else { ln_log(LNLOG_SWARNING, LNLOG_CARTICLE, "warning: %s/%s is not a regular file", tt, de->d_name); } } } } if (art >= xfirst && art <= xlast && xoverinfo[art - xfirst].text) { xoverinfo[art - xfirst].exists = 1; } else { /* kill non-article files, like "core" */ if (art == 0) { if (unlink(de->d_name) && errno != EISDIR && errno != EPERM && verbose) { ln_log(LNLOG_SWARNING, LNLOG_CGROUP, "warning: deleting junk %s/%s failed: %s", tt, de->d_name, strerror(errno)); } } } } /* while (de = readdir(d)) */ /* count removed articles */ for (art = xfirst; art <= xlast; art++) { if (xoverinfo[art - xfirst].text && !xoverinfo[art - xfirst].exists) { ++error; free(xoverinfo[art - xfirst].text); xoverinfo[art - xfirst].text = NULL; } } /* if something had to be fixed, write a better file to disk for next time - race conditions here, but none dangerous */ if (error) { int wfd; char newfile[20]; /* RATS: ignore */ if (debugmode) syslog(LOG_DEBUG, "updated %d line%s in %s/.overview", error, PLURAL(error), tt); strcpy(newfile, ".overview.XXXXXX"); if ((wfd = mkstemp(newfile)) != -1) { int va; va = 1; for (art = xfirst; art <= xlast; art++) { if (xoverinfo[art - xfirst].exists && xoverinfo[art - xfirst].text) { if (writes(wfd, xoverinfo[art - xfirst].text) == - 1 || writes(wfd, "\n") == -1) { ln_log(LNLOG_SERR, LNLOG_CGROUP, "error: write() for .overview failed: %m"); va = 0; break; } } } if (fchmod(wfd, 0664)) va = 0; if (fsync(wfd)) va = 0; if (close(wfd)) va = 0; if (va) { if (rename(newfile, ".overview")) { if (unlink(newfile)) ln_log(LNLOG_SERR, LNLOG_CGROUP, "error: unlink(%s) failed: %m", newfile); else ln_log(LNLOG_SERR, LNLOG_CGROUP, "error: rename(%s/%s, .overview) failed: %m", tt, newfile); } else { if (debugmode) syslog(LOG_DEBUG, "wrote %s/.overview", tt); } } else { unlink(newfile); /* the group must be newly empty: I want to keep the old .overview file I think */ } } else { ln_log(LNLOG_SERR, LNLOG_CGROUP, "error: mkstemp of new .overview failed: %m"); } } closedir(d); free(tt); if (overview) { free(overview); } return 1; } void fixxover(void) { DIR *d; struct dirent *de; char s[SIZE_s + 1]; xsnprintf(s, SIZE_s, "%s/interesting.groups", spooldir); d = opendir(s); if (!d) { ln_log(LNLOG_SERR, LNLOG_CGROUP, "error: opendir %s: %m", s); return; } while ((de = readdir(d))) { if (isalnum((unsigned char)*(de->d_name)) && findgroup(de->d_name)) { if (chdirgroup(de->d_name, FALSE)) getxover(); freexover(); } } closedir(d); }