Logo Search packages:      
Sourcecode: leafnode version File versions

xoverutil.c

/*
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 - 2003.

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 */

    while (p != q) {
      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++;
    }

    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);
}

Generated by  Doxygen 1.6.0   Back to index