/*
 * Copyright (c) 1998,1999,2000  Kazushi (Jam) Marukawa
 * All rights of my changes are reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice in the documentation and/or other materials provided with
 *    the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* $Orig-Id: util.c,v 1.22 1997/07/23 18:35:18 agulbra Exp $ */
/*

Written by Arnt Gulbrandsen <agulbra@troll.no> and copyright 1995
Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47
22646949.

Use, modification and distribution is allowed without limitation,
warranty, or liability of any kind. */

/*
This code is derived from only leafnode+ by using same structure
of Cornelius's leafnode to prepare for merging with Cornelius's code.
*/

#ifdef SOCKS
#include <socks.h>
#endif

#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <netdb.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <dirent.h>

#include "leafnode.h"

#if !defined (UIO_MAXIOV)
#define UIO_MAXIOV 8
#endif

/* return 1 if xover is a legal overview line, 0 else */
int legalxoverline(const char* xover)
{
    const char* p;
    const char* q;

    if (!xover)
	return 0;

    /* anything that isn't tab, printable ascii, or latin-* ? then killit */

#if STRICT_XOVER
    p = xover;
    while (*p) {
	int c = (unsigned char)*p++;

	if (((c != '\t' && c!= '\033') && c < ' ') ||
	    (c > 126 && c < 160)) {
	    if (verbose)
		printf("illegal character: %s\n", xover);
	    return 0;
	}
    }
#endif

    p = xover;
    q = strchr(p, '\t');
    if (!q)
	return 0;

    /* article number */

    while (p != q) {
	if (!isdigit(*p)) {
	    if (verbose)
		printf("non-digit in article number: %s\n", xover);
	    return 0;
	}
	p++;
    }
    p = q + 1;
    q = strchr(p, '\t');
    if (!q) {
	if (verbose)
	    printf("premature end before subject: %s\n", xover);
	return 0;
    }

    /* subject: no limitations */

    p = q + 1;
    q = strchr(p, '\t');
    if (!q) {
	if (verbose)
	    printf("premature end before from: %s\n", xover);
	return 0;
    }

    /* from: no limitations */

    p = q + 1;
    q = strchr(p, '\t');
    if (!q) {
	if (verbose)
	    printf("premature end before date: %s\n", xover);
	return 0;
    }

    /* date: no limitations */

    p = q + 1;
    q = strchr(p, '\t');
    if (!q) {
	if (verbose)
	    printf("premature end before message-id: %s\n", xover);
	return 0;
    }

    /* message-id: <*@*> */

    p = skipspaces((char*)p);
    if (*p != '<') {
	if (verbose)
	    printf("message-id does not start with '<': %s\n", xover);
	return 0;
    }
    while (p != q && *p != '@' && *p != '>' && *p != ' ')
	p++;
    if (*p != '@') {
	if (verbose)
	    printf("message-id does not contain '@': %s\n", xover);
	return 0;
    }
    while (p != q && *p != '>' && *p != ' ')
	p++;
    if (*p != '>') {
	if (verbose)
	    printf("message-id does not contain '>': %s\n", xover);
	return 0;
    }
    if (++p != q) {
	if (verbose)
	    printf("message-id does not end with '>': %s\n", xover);
	return 0;
    }
    p = q + 1;
    q = strchr(p, '\t');
    if (!q) {
	if (verbose)
	    printf("premature end before references: %s\n", xover);
	return 0;
    }

    /* references: a series of <*@*> */

#if STRICT_XOVER
    while (p != q) {
	if (*p != '<') {
	    if (verbose)
		printf("id in references does not start with '<': %s\n", xover);
	    return 0;
	}
	while (p != q && *p != '@' && *p != '>' && *p != ' ')
	    p++;
	if (*p != '@') {
	    if (verbose)
		printf("id in references does not contain '@': %s\n", xover);
	    return 0;
	}
	while (p != q && *p != '>' && *p != ' ')
	    p++;
	if (*p++ != '>') {
	    if (verbose)
		printf("id in references does not end with '>': %s\n", xover);
	    return 0;
	}
	while (p != q && *p == ' ')
	    p++;
    }
#endif
    p = q + 1;
    q = strchr(p, '\t');
    if (!q) {
	if (verbose)
	    printf("premature end before byte count: %s\n", xover);
	return 0;
    }

    /* byte count */

#if STRICT_XOVER
    while (p != q) {
	if (!isdigit(*p)) {
	    if (verbose)
		printf("illegal digit in byte count: %s\n", xover);
	    return 0;
	}
	p++;
    }
#endif
    p = q + 1;
    q = strchr(p, '\t');

    /* line count */

#if STRICT_XOVER
    while (p && *p && p != q) {
	if (!isdigit(*p)) {
	    if (verbose)
		printf("illegal digit in line count: %s\n", xover);
	    return 0;
	}
	p++;
    }
#endif

    return 1;
}

char* getxoverline(const char* filename)
{
    FILE* f;

    if ((f = fopen(filename, "r"))) {
	struct header_info* p = parse_all(f);
	fclose (f);
	if (p->from && p->date && p->subject && p->msgid &&
	    p->n_bytes && p->n_lines) {
	    char * result;

	    result = critmalloc(strlen(p->from) + strlen(p->date) +
				strlen(p->subject) + strlen(p->msgid) +
				(p->references ? strlen(p->references) : 0) +
				100 + (p->xref ? strlen(p->xref) : 0),
				"computing overview line");
	    sprintf(result, "%s\t%s\t%s\t%s\t%s\t%s\t%d\t%d",
		    filename, p->subject, p->from, p->date, p->msgid,
		    p->references ? p->references : "" ,
		    p->n_bytes, p->n_lines);
	    if (p->xref) {
		strcat(result, "\tXref: ");
		strcat(result, p->xref);
	    }
	    free_header_info(p);
	    return result;
	}
	free_header_info(p);
    }
    return NULL;
}

/* utility routine to pull the xover info into memory
   returns 0 if there's some error, non-zere else */

struct xoverinfo * xoverinfo;
unsigned long xfirst, xlast;

int getxover(void)
{
    DIR* d;
    struct dirent* de;
    int fd;
    struct stat st;
    unsigned long art;
    static char* overview;
    int error;
    char* p;
    char* q;
    char newsdir[1024];

    /* free any memory left over from last time */

    error = 0;

    if (xoverinfo) {
	for(art = xfirst; art <= xlast; art++) {
	    if (xoverinfo[art - xfirst].mallocd &&
		xoverinfo[art - xfirst].text) {
		free(xoverinfo[art - xfirst].text);
		xoverinfo[art - xfirst].text = NULL;
	    }
	}
    }

    if (overview) {
	free(overview);
	overview = NULL;
    }

    /* find current directory */

    if (getcwd(newsdir, 1024) == NULL) {
	mysyslog(LOG_ERR, "getcwd: %s", strerror(errno));
	return 0;
    }

    /* find article range, without locking problems */

    d = opendir(".");
    if (!d) {
	mysyslog(LOG_ERR, "opendir: %s", strerror(errno));
	return 0;
    }

    xfirst = ULONG_MAX;
    xlast = 0;
    while ((de = readdir(d))) {
	art = strtoul(de->d_name, &p, 10);
	if (p && !*p) {
	    if (art < xfirst)
		xfirst = art;
	    if (art > xlast)
		xlast = art;
	}
    }

    if (xlast < xfirst) {
	closedir(d);
	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 *)
		critrealloc((char*)xoverinfo,
			    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*)malloc(st.st_size + 1)) != NULL &&
	read(fd, overview, st.st_size) == st.st_size) {

	close(fd);
	overview[st.st_size] = '\0';

	/* okay, we have the content, so let's parse it roughly */

	p = overview;
	while (p && *p) {
	    p = skipspaces(p);
	    q = strchr(p, '\n');
	    if (q)
		*q++ = '\0';

	    art = strtoul(p, NULL, 10);
	    if (art > xlast || art < xfirst) {
		error++;
	    } else if (xoverinfo[art - xfirst].text) {
		error++;
		xoverinfo[art - xfirst].text = NULL;
	    } else {
		xoverinfo[art - xfirst].text = p;
	    }

	    p = q;
	}
    }

    /* so, what was missing? */

    while ((de = readdir(d))) {
	art = strtoul(de->d_name, &p, 10);
	if (p && !*p) {
	    if (art >= xfirst && art <= xlast) {
		if (!xoverinfo[art - xfirst].text) {
		    if (verbose > 2)
			printf("reading XOVER info from %s/%s\n",
			       newsdir, de->d_name);
		    error++;
		    if ((xoverinfo[art - xfirst].text =
			 getxoverline(de->d_name)) != NULL &&
			legalxoverline(xoverinfo[art - xfirst].text)) {
			xoverinfo[art - xfirst].mallocd = 1;
		    } else {
#if TEST_TEXPIRE
			if (verbose)
			    printf("testing about texpire: will unlink "
				   "illegal article: %s/%s\n",
				   newsdir, de->d_name);
#else
			(void) unlink(de->d_name); /* most dubious, this */
			if (verbose)
			    printf("unlinked illegal article: %s/%s\n",
				   newsdir, de->d_name);
#endif
		    }
		}
		if (xoverinfo[art - xfirst].text)
		    xoverinfo[art - xfirst].exists = 1;
	    } else if (verbose) {
		if (art > 0 && art < xfirst)
		    printf("article %lu is below the low-water mark (%lu)\n",
			   art, xfirst);
		else
		    printf("article %lu is above the high-water mark (%lu)\n",
			   art, xlast);
	    }
	} else {
	    if (art == 0 && strcmp(de->d_name, ".overview") != 0 &&
		strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0) {
#if TEST_TEXPIRE
		if (verbose)
		    printf("testing about texpire: will unlink junk: %s/%s\n",
			   newsdir, de->d_name);
#else
		if (unlink(de->d_name) == 0) {
		    if (verbose > 1)
			printf("unlinked junk: %s/%s\n",
			       newsdir, de->d_name);
		} else if (errno != EISDIR && errno != EPERM) {
		    mysyslog(LOG_ERR, "attempted to delete %s in case "
			     "it's junk: %s\n", de->d_name,
			     errno ? strerror(errno) : "OK");
		}
#endif
	    }
	}
    }

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

	if (verbose) {
	    printf("corrected %d lines in %s/.overview\n",
		   error, newsdir);
	}

	strcpy(newfile, ".overview.XXXXXX");
	if ((wfd = open(mktemp(newfile), O_WRONLY|O_CREAT|O_EXCL, 0664)) >= 0) {
	    struct iovec oooh[UIO_MAXIOV];
	    int vi, vc, va;

	    vi = vc = va = 0;
	    for (art = xfirst; art <= xlast; art++) {
		if (xoverinfo[art - xfirst].exists &&
		    xoverinfo[art - xfirst].text) {
		    oooh[vi].iov_base = xoverinfo[art - xfirst].text;
		    oooh[vi].iov_len = strlen(xoverinfo[art - xfirst].text);
		    vc += oooh[vi++].iov_len + 1;
		    oooh[vi].iov_base = "\n";
		    oooh[vi++].iov_len = 1;
		    if (vi >= (UIO_MAXIOV - 1)) {
			if (writev(wfd, oooh, vi) != vc) {
			    mysyslog(LOG_ERR, "writev() for .overview "
				     "failed: %s", strerror(errno));
			    art = xlast + 1; /* so the loop will stop */
			}
			vi = vc = 0;
			va = 1;
		    }
		}
	    }
	    if (vi) {
		if (writev(wfd, oooh, vi) != vc) {
		    mysyslog(LOG_ERR, "writev() for .overview failed: %s",
			     strerror(errno));
		} else {
		    va = 1;
		}
	    }
	    fchmod(wfd, 0664);
	    close(wfd);
	    if (va) {
		if (rename(newfile, ".overview")) {
		    if (unlink(newfile)) {
			mysyslog(LOG_ERR, "rename() and unlink() both "
				 "failed: %s", strerror(errno));
		    } else {
			mysyslog(LOG_ERR, "rename(%s/%s, .overview) failed: "
				 "%s", newsdir, newfile, strerror(errno));
		    }
		}
	    } else {
		unlink(newfile);
		/* the group must be newly empty: I want to keep the old
		   .overview file I think */
	    }
	} else {
	    mysyslog(LOG_ERR, "open(O_WRONLY|O_CREAT|O_EXCL) of new "
		     ".overview failed: %s", strerror(errno));
	}
    }
    closedir(d);
    return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1