/*
libutil -- handling xover records
Written by Arnt Gulbrandsen <agulbra@troll.no> and copyright 1995
Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47
22646949.
Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>
and Randolf Skerka <Randolf.Skerka@gmx.de>.
Copyright of the modifications 1997.
Modified by Kent Robotti <robotti@erols.com>. Copyright of the
modifications 1998.
Modified by Markus Enzenberger <enz@cip.physik.uni-muenchen.de>.
Copyright of the modifications 1998.
Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>.
Copyright of the modifications 1998, 1999.
Modified by Ralf Wildenhues <ralf.wildenhues@gmx.de>
Copyright of the modifications 2002.
Modified by Matthias Andree <matthias.andree@gmx.de>
Copyright of the modifications 2000 - 2005, 2007.
See file COPYING for restrictions on the use of this software.
*/
#include "leafnode.h"
#include <fcntl.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#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);
}
syntax highlighted by Code2HTML, v. 0.9.1