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

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

#include <assert.h>
#include <stdarg.h>
#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>
#ifndef NOFNMATCH
#include <fnmatch.h>
#endif
#ifdef HAVE_POSIX_REGCOMP
#include <regex.h>
#endif
#ifdef HAVE_REGCMP
char *regcmp();
char *regex();
extern char *__loc1;
#endif
#ifdef HAVE_V8_REGCOMP
#include <regexp.h>
#endif

#include "leafnode.h"

char* fqdn = NULL;
int verbose = 0;


char* getaheader(FILE* f, const char* h)
{
    int hlen = strlen(h);
    static char* buf;       /* buffer for line */
    static size_t size;     /* size of buffer */
    size_t len;             /* # of chars stored into buf before '\0' */
    char* l;

    if (!f)
	return "";

    if (!size)
	size = 256;
    if (!buf)
	buf = critmalloc(size, "reading header");

    /* look for header: */
    do {
	l = getaline(f);
    } while (l && *l && (l[hlen] != ':' || strncasecmp(h, l, hlen)));

    /* not find, then return "" */
    if (!l || !*l) {
	rewind(f);
	return "";
    }

    /* process following lines */
    len = 0;
    buf[len] = 0;
    do {
	int llen = strlen(l);
	if (len + llen > size) {
	    size = len + llen + 100;
	    buf = critrealloc(buf, size, "extending header");
	}
	strcat(&buf[len], l);
	len += llen;
	l = getaline(f);
    } while (l && isspace(*l));

    /* split only header: value */
    l = skipspaces(skipnonspaces(buf));

    /* rewind f and return header or "" */
    rewind(f);
    return l;
}


void removearts(const char* msgid)
{
    struct articles* arts;
    int i;
    struct stat st;
    const char* p;

    assert(msgid != NULL && *msgid != '\0');
    arts = findarticles(msgid);
    if (arts == 0)
	return;
    for (i = 0; i < arts->num; i++) {
	p = getartfname(arts->arts[i].ng, arts->arts[i].art);
	if (unlink(p) < 0) {
	    if (errno != ENOTDIR && errno != ENOENT) {
		mysyslog(LOG_WARNING, "failed to unlink %s: %s",
			 p, strerror(errno));
	    }
	} else {
	    if (verbose > 1)
		printf("removed %s\n", p);
	}
    }

    p = getmsgidfname(msgid);
    if (stat(p, &st) == 0) {
	if (st.st_nlink == 1) {
	    if (unlink(p) < 0) {
		mysyslog(LOG_WARNING, "failed to unlink %s: %s",
			 p, strerror(errno));
	    } else {
		if (verbose > 1)
		    printf("removed %s\n", p);
	    }
	} else {
	    mysyslog(LOG_WARNING, "Leave %s because it has links more than 1",
		     p);
	}
    } else {
	mysyslog(LOG_WARNING, "failed to unlink %s since it isn't be there", p);
    }
}



/*
 * replacement for malloc, syslogs allocation failures
 * and exits with the error message
 */
void* critmalloc(size_t size, const char* message)
{
    void* a;

    a = malloc(size);
    if (!a) {
	mysyslog(LOG_ERR, "malloc(%d) failed: %s: %s",
		 (int)size, message, strerror(errno));
	exit(1);
    }
    return a;
}

/*
 * replacement for realloc, syslogs allocation failures
 * and exits with the error message
 */
void* critrealloc(void* a, size_t size, const char* message)
{
    a = realloc(a, size);
    if (!a) {
	mysyslog(LOG_ERR, "realloc(%d) failed: %s: %s",
		 (int)size, message, strerror(errno));
	exit(1);
    }
    return a;
}

#define LM_SIZE 65536

static void makedir(const char* d)
{
    static struct string a = { 0, 0 };
    char* p;
    char* q;
    struct stat st;

    if (d == 0 || *d != '/')
	return;

    setstring(&a, d);
    if (stat(a.str, &st) == 0 && S_ISDIR(st.st_mode))
	return;
    for (p = a.str + strlen(a.str) - 1; p > a.str; --p) {
	if (*p == '/') {
	    *p = '\0';
	    if (stat(a.str, &st) == 0 && S_ISDIR(st.st_mode))
		break;
	    *p = '/';
	}
    }
    while ((q = strchr(&p[1], '/')) != 0) {
	*q = '\0';
	*p = '/';
	if (mkdir(a.str, 0775) < 0) {
	    mysyslog(LOG_ERR, "mkdir %s: %s", d, strerror(errno));
	    exit(1);
	}
	p = q;
    }
    *p = '/';
    if (mkdir(a.str, 0775) < 0) {
	mysyslog(LOG_ERR, "mkdir %s: %s", d, strerror(errno));
	exit(1);
    }
}


/* chdir to the directory of the argument if it's a valid group
   return TRUE if it is valid, FALSE otherwise */
int chdirgroup(const struct newsgroup* ng, int creatf)
{
    static const struct newsgroup* lastng = 0;
    const char* s;

    if (ng == 0)
	return 0;
    if (lastng == ng)
	return 1;	/* group is same as a last chdirgrouped group */
    lastng = ng;

    /* chdir for the new group */
    s = getngdname(ng);
    if (chdir(s) < 0) {
	if (!creatf)
	    return 0;	/* don't change directory */
	makedir(s);
	if (chdir(s) < 0)
	    return 0;
    }
    return 1;
}


/*
 * get active file from remote server
 */
void nntpactive(void)
{
    char* l;

    if (verbose)
	printf("Reading list of active newsgroups\n");

    sprintf(lineout, "LIST ACTIVE\r\n");
    putaline();

    if (nntpreply() != 215)
	return;

    while ((l = getaline(nntpin)) && *l != '.') {
	char* p;

	p = skipnonspaces(l);
	if (*p)
	    *p++ = '\0';

	/*
	 * Doesn't register all of newsgroups to local groupinfo.
	 * Skips some of them if "newgroups" or "filteredngs" are defined.
	 */
        if (servers->newsgroups.num > 0) {
	    /* should we register this news group? */
	    int i;
	    for (i = 0; i < servers->newsgroups.num; i++) {
		if (myfnmatch(servers->newsgroups.strarray[i], l) == 0)
		    break;
	    }
	    /* no, skip this news group. */
	    if (i >= servers->newsgroups.num) {
		if (verbose > 1)
		    printf("Not registering %s since it isn't in newsgroups list\n", l);
		continue;
	    }
	}

        if (servers->filteredngs.num > 0) {
	    /* should we filter this news group? */
	    int i;
	    for (i = 0; i < servers->filteredngs.num; i++) {
		if (myfnmatch(servers->filteredngs.strarray[i], l) == 0)
		    break;
	    }
	    /* yes, skip this news group. */
	    if (i < servers->filteredngs.num) {
		if (verbose > 1)
		    printf("Not registering %s since it is in filter list\n", l);
		continue;
	    }
	}

	if (!findgroup(l)) {
	    /*
	     * There are no article, so we start it from 2.
	     * And we have to minus one again to build pseudo article.
	     * Currently server's last article number is read
	     * from each server's information file.  So we use 0 here.
	     */
	    insertgroup(l, 2, 1, "", 0);
	    if (verbose > 1)
		printf("Registered group %s\n", l);
	}
    }

    if (verbose)
	printf("Reading newsgroup descriptions\n");

    /*
     * add descriptions
     */
    fprintf(nntpout, "LIST NEWSGROUPS\r\n");
    fflush(nntpout);
    if (nntpreply() == 215) {
	while ((l = getaline(nntpin)) && *l != '.') {
	    char* p;
	    struct newsgroup* g;

	    p = skipnonspaces(l);
	    if (*p)
		*p++ = '\0';
	    p = skipspaces(p);
	    if ((g = findgroup(l)) != 0) {
		if (*p && legalnewsdesc(p)) {
		    if (g->desc && strcmp(g->desc, p) == 0) {
			/* nothing to do */
		    } else {
			if (verbose)
			    printf("new description of %s: '%s' changed from "
				   "'%s'\n", g->name, p, g->desc);
			g->desc = strdup(p);	/* discard original data */
		    }
		} else {
		    if (verbose > 1) {
			printf("ignore a description since it is wrong: "
			       "%s: %s\n", l, p);
		    }
		}
	    } else {
		if (verbose > 2)
		    printf("ignore a description since there is no group: "
			   "%s: %s\n", l, p);
	    }
	}
    }
}

/*
 * correct newsgroup descriptions
 */
static void helpcorrectnewsdesc(struct newsgroup* g)
{
    if (g) {
	helpcorrectnewsdesc(g->right);
	if (strcmp(g->desc, "-x-") != 0 && !legalnewsdesc(g->desc)) {
	    if (verbose)
		printf("Deleting wrong news description: %s, %s\n",
		       g->name, g->desc);
	    g->desc = "";
	}
	helpcorrectnewsdesc(g->left);
    }
}

void correctnewsdesc(void)
{
    helpcorrectnewsdesc(active);
}


static void clearserver(struct newsgroup* g)
{
    if (g) {
	clearserver(g->right);
	clearserver(g->left);
	g->server = 0;
	g->newarticles = 0;
    }
}


void readserver(void)
{
    char* p;
    char* q;
    unsigned long server;
    int fd;
    struct newsgroup * g;
    struct stat st;

    static char* stuff;

    const char* s;

    if (stuff) {
	free((char*)stuff);
	stuff = NULL;
    }

    s = getserverfname(servers->servername);
    fd = open(s, O_RDONLY);
    if (stat(s, &st) < 0) {
	mysyslog(LOG_ERR, "can't stat %s: %s", s, strerror(errno));
	return;
    }

    clearserver(active);

    stuff = critmalloc(st.st_size + 1, "Reading group info");
    if ((fd = open(s, O_RDONLY)) < 0 ||
	read(fd, stuff, st.st_size) < st.st_size) {
	mysyslog(LOG_ERR, "can't open/read %s: %s", s, strerror(errno));
	return;
    } else {
	close(fd);
	stuff[st.st_size] = '\0'; /* 0-terminate string */
    }

    p = stuff;
    while (p && *p) {
	q = p;
	p = strchr(p, ' ');
	if (p && *p)
	    *p++ = '\0';
	if (*q) {
	    server = p ? strtoul(p, &p, 10) : 0;

	    if ((g = findgroup(q)) == NULL) {
		mysyslog(LOG_ERR, "can't find group %s", q);
	    } else {
		g->server = server;
	    }
	}
	p = strchr(p, '\n');
	if (p)
	    p++;
    }
}


static void helpwriteserver(struct newsgroup* g, FILE* f)
{
    if (g) {
	helpwriteserver(g->right, f);
	if (g->server > 0)
	    fprintf(f, "%s %lu\n", g->name, g->server);
	helpwriteserver(g->left, f);
    }
}


void writeserver(void)
{
    FILE* a;
    const char* s;
    char c[PATH_MAX];

    s = getservernewfname(servers->servername);
    a = fopen(s, "w");
    if (!a)
	return;
    helpwriteserver(active, a);
    fclose(a);
    strcpy(c, s);
    s = getserverfname(servers->servername);
    rename(c, s);
}


/* get the fully qualified domain name of this box into fqdn */

void whoami(void)
{
#ifdef INET6
    struct addrinfo hints, *res0;
#else
    struct hostent* he;
#endif

#ifdef NI_MAXHOST
    char name[NI_MAXHOST];
#else
    char name[1025];
#endif

    if (fqdn != NULL) {
	/* fqdn is already set (via config file) */
	return;
    }

#ifdef INET6
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_CANONNAME;
    if (!gethostname(name, sizeof(name)) &&
	!getaddrinfo(name, NULL, &hints, &res0)) {
	if(res0->ai_canonname){
	    strncpy(name, res0->ai_canonname, sizeof(name));
	}
	freeaddrinfo(res0);
	fqdn = strdup(name);
    } else {
	fqdn = NULL;
    }
#else
    if (!gethostname(name, sizeof(name)) &&
	(he = gethostbyname(name)) != NULL) {
	strncpy(name, he->h_name, sizeof(name));
	name[sizeof(name)-1] = '\0';
	if (strchr(name, '.') == NULL) {
	    char** alias;
	    alias = he->h_aliases;
	    while (alias && *alias) {
	        if (strchr(*alias, '.') && (strlen(*alias) > strlen(name))) {
		    strncpy(name, *alias, sizeof(name));
		    name[sizeof(name)-1] = '\0';
		} else {
		    alias++;
		}
	    }
	}
	fqdn = strdup(name);
    } else {
	fqdn = NULL;
    }
#endif
}

/* next few routines implement a mapping from message-id to article
   number, and clearing the entire space */

struct msgidtree {
    struct msgidtree* left;
    struct msgidtree* right;
    unsigned long art;
    char msgid[1];
};

static struct msgidtree* head; /* starts as NULL */

void insertmsgid(const char* msgid, unsigned long art)
{
    struct msgidtree** a;
    int c;

    if (strchr(msgid, '@') == 0)
	return;

    a = &head;
    while (a) {
	if (*a) {
	    /* comparing only by msgid is uncool because the tree becomes
	       very unbalanced */
	    c = strcmp(strchr((*a)->msgid, '@'), strchr(msgid, '@'));
	    if (c == 0)
		c = strcmp((*a)->msgid, msgid);
	    if (c < 0)
		a = &((*a)->left);
	    else if (c > 0)
		a = &((*a)->right);
	    else {
		return;
	    }
	} else {
	    *a = (struct msgidtree *)
		 critmalloc(sizeof(struct msgidtree) + strlen(msgid),
			    "Building expiry database");
	    (*a)->left = (*a)->right = NULL;
	    strcpy((*a)->msgid, msgid);
	    (*a)->art = art;
	    return;
	}
    }
}

unsigned long findmsgid( const char* msgid)
{
    struct msgidtree* a;
    int c;
    char* domainpart;

    /* domain part differs more than local-part, so try it first */

    domainpart = strchr(msgid, '@');
    if (domainpart == NULL)
	return 0;

    a = head;
    while (a) {
	c = strcmp(strchr(a->msgid, '@'), domainpart);
	if (c == 0)
	    c = strcmp(a->msgid, msgid);
	if (c < 0)
	    a = a->left;
	else if (c > 0)
	    a = a->right;
	else
	    return a->art;
    }
    return 0;
}

static void begone(struct msgidtree* m)
{
    if (m) {
	begone(m->right);
	begone(m->left);
	free((char*)m);
    }
}

void clearidtree(void)
{
    if (head) {
	begone(head->right);
	begone(head->left);
	free((char*)head);
    }
    head = NULL;
}


struct newsgroup* getexpire(struct newsgroup* g, char* l)
{
    struct newsgroup* dummy = NULL;

    if (g) {
        if (strcasecmp(l, g->name) == 0)
            dummy = g;
	else if ((dummy = getexpire(g->left, l)) == NULL)
	    dummy = getexpire(g->right, l);
    }
    return dummy;
}


/*
05/25/97 - T. Sweeney - Modified to read user name and password for AUTHINFO.
			Security questionable as password is stored in
			plaintext in insecure file.
*/

/* this thing -really- needs a redo.  that many parameters means that
   cut and paste is not a good strategy any more */
int artlimit = 0;
int initiallimit = 0;
int maxold = 10;
int maxlines = 0;
int minlines = 0;
int maxbytes = 0;
int maxgroups = 0;
struct strlist killsubject = {0, 0, 0};
struct strlist killsubjecti = {0, 0, 0};
struct strlist killfrom = {0, 0, 0};
struct strlist killfromaddress = {0, 0, 0};
time_t expire;
long timeout_short = TIMEOUT_SHORT;
long timeout_long = TIMEOUT_LONG;
long timeout_active = TIMEOUT_ACTIVE;
struct groupexpires groupexpires = {0, 0, 0};
struct server * servers;
struct newsgroup * g;

void addexpire(struct groupexpires* p, const struct groupexpire* a)
{
    if (p->num + 1 >= p->size) {
	p->size = (p->num + 1 + 255) / 256 * 256;
	p->array = critrealloc(p->array, p->size * sizeof(struct groupexpire),
			       "addexpire");
    }
    p->array[p->num++] = *a;
}

static struct server* reverse_servers(struct server* list)
{
    static int postable = 1;
    if (list->postable)
	list->postable = postable++;
    if (list->next) {
	struct server* reversed_rest = reverse_servers(list->next);
	struct server* p;
	for (p = reversed_rest; p->next; p = p->next)
	    ;
	p->next = list;
	list->next = NULL;
	return reversed_rest;
    } else {
	return list;
    }
}

static int parsestring(char* l, char* string, char** value)
{
    int len = strlen(string);
    char* p;

    if (strncasecmp(l, string, len) == 0) {
	p = skipspaces(l + len);
	if (*p == '=') {
	    p = skipspaces(p + 1);
	    *value = strdup(p);
	} else {
	    mysyslog(LOG_ERR, "unable to parse %s (%s has no =)", string, l);
	    exit(2);
	}
	return 1;
    } else {
	return 0;
    }
}

static int parselong(char* l, char* string, long* value)
{
    int len = strlen(string);
    char* p;

    if (strncasecmp(l, string, len) == 0) {
	p = skipspaces(l + len);
	if (*p == '=') {
	    unsigned long i;
	    char* q;
	    p = skipspaces(p + 1);
	    q = skipnonspaces(p);
	    *q = '\0';
	    i = strtoul(p, &q, 0);
	    if (p && *p && q && *q == '\0') {
		*value = i;
	    } else {
		mysyslog(LOG_ERR, "unable to parse %s (%s number)", string, l);
		exit(2);
	    }
	} else {
	    mysyslog(LOG_ERR, "unable to parse %s (%s has no =)", string, l);
	    exit(2);
	}
	return 1;
    } else {
	return 0;
    }
}

static int parsestrlong(char* l, char* string, char** svalue, long* lvalue)
{
    int len = strlen(string);
    char* p;

    if (strncasecmp(l, string, len) == 0) {
	p = skipspaces(l + len);
	if (*p == '=') {
	    unsigned long i;
	    char* q;
	    p = skipspaces(p + 1);
	    q = skipnonspaces(p);
	    if (*q == '\0') {
		mysyslog(LOG_ERR, "unable to parse %s (%s has only string "
			 "not number)", string, l);
		exit(2);
	    }
	    *q = '\0';
	    *svalue = strdup(p);
	    p = skipnonspaces(++q);
	    *p = '\0';
	    i = strtoul(q, &p, 0);
	    if (q && *q && p && *p == '\0') {
		*lvalue = i;
	    } else {
		mysyslog(LOG_ERR, "unable to parse %s (%s has only string "
			 "not number)", string, l);
		exit(2);
	    }
	} else {
	    mysyslog(LOG_ERR, "unable to parse %s (%s has no =)", string, l);
	    exit(2);
	}
	return 1;
    } else {
	return 0;
    }
}

void readconfig(void)
{
    FILE* f;
    char* l;
    char* p;
    const char* s;
    long lvalue;
    char* svalue;

    servers = NULL;

    s = getconfigfname();
    if ((f = fopen(s, "r"))) {
	while ((l = getaline(f))) {
	    p = strchr(l, '#');
	    if (p)
		*p = '\0';
	    l = skipspaces(l);

	    if (parsestring(l, "server", &svalue)) {
		/* get server value */
		struct server* ns = (struct server*)
		    critmalloc(sizeof(struct server),
			       "allocating for server info");
		ns->servername = svalue;
		ns->viahost = 0;
		ns->port = 0;
		ns->preconnect = 0;
		ns->newsgroups.num = 0;
		ns->newsgroups.size = 0;
		ns->newsgroups.strarray = 0;
		ns->filteredngs.num = 0;
		ns->filteredngs.size = 0;
		ns->filteredngs.strarray = 0;
		if (!servers)
		    ns->postable = 1;	/* first server is marked as postable */
		else
		    ns->postable = 0;	/* others are not marked */
		ns->username = 0;
		ns->password = 0;
		ns->timeout_open = TIMEOUT_OPEN;
		ns->timeout_read = TIMEOUT_READ;
		ns->next = servers;
		servers = ns;
	    } else if (parsestring(l, "viahost", &svalue)) {
		/* get viahost value */
		if (servers) {
		    servers->viahost = svalue;
		} else {
		    mysyslog(LOG_ERR, "no servers before viahost");
		    exit(2);
		}
	    } else if (parselong(l, "port", &lvalue)) {
		/* get port value */
		if (servers) {
		    servers->port = lvalue;
		} else {
		    mysyslog(LOG_ERR, "no servers number before port");
		    exit(2);
		}
	    } else if (parsestring(l, "preconnect", &svalue)) {
		/* get preconnect value */
		if (servers) {
		    servers->preconnect = svalue;
		} else {
		    mysyslog(LOG_ERR, "no servers before preconnect");
		    exit(2);
		}
	    } else if (parsestring(l, "newsgroups", &svalue)) {
		/* get newsgroups value */
		if (servers) {
		    addstr(&servers->newsgroups, svalue);
		} else {
		    mysyslog(LOG_ERR, "no servers before newsgroups");
		    exit(2);
		}
	    } else if (parsestring(l, "filteredngs", &svalue)) {
		/* get filteredngs value */
		if (servers) {
		    addstr(&servers->filteredngs, svalue);
		} else {
		    mysyslog(LOG_ERR, "no servers before filteredngs");
		    exit(2);
		}
	    } else if (parselong(l, "postable", &lvalue)) {
		/* get postable value */
		if (servers) {
		    servers->postable = lvalue;
		} else {
		    mysyslog(LOG_ERR, "no servers before postable");
		    exit(2);
		}
	    } else if (parsestring(l, "username", &svalue)) {
		/* get username value */
		if (servers) {
		    servers->username = svalue;
		} else {
		    mysyslog(LOG_ERR, "no servers before username");
		    exit(2);
		}
	    } else if (parsestring(l, "password", &svalue)) {
		/* get password value */
		if (servers) {
		    servers->password = svalue;
		} else {
		    mysyslog(LOG_ERR, "no server before password");
		    exit(2);
		}
	    } else if (parselong(l, "timeout_open", &lvalue)) {
		/* get timeout_short value */
		if (servers) {
		    servers->timeout_open = lvalue;
		} else {
		    mysyslog(LOG_ERR, "no servers before timeout_open");
		    exit(2);
		}
	    } else if (parselong(l, "timeout_read", &lvalue)) {
		/* get timeout_read value */
		if (servers) {
		    servers->timeout_read = lvalue;
		} else {
		    mysyslog(LOG_ERR, "no servers before timeout_read");
		    exit(2);
		}
	    } else if (parselong(l, "expire", &lvalue)) {
		/* get expire value */
		expire = time(NULL)-(time_t)(86400*lvalue);
	    } else if (parsestrlong(l, "groupexpire", &svalue, &lvalue)) {
		/* get expire value */
		struct groupexpire a;
		a.group = svalue;
		a.xtime = time(NULL)-(time_t)(86400*lvalue);
		addexpire(&groupexpires, &a);
	    } else if (parselong(l, "timeout_short", &lvalue)) {
		/* get timeout_short value */
		timeout_short = lvalue;
	    } else if (parselong(l, "timeout_long", &lvalue)) {
		/* get timeout_long value */
		timeout_long = lvalue;
	    } else if (parselong(l, "timeout_active", &lvalue)) {
		/* get timeout_active value */
		timeout_active = lvalue;
	    } else if (parselong(l, "maxcount", &lvalue)) {
		/* get maxcount value */
		artlimit = lvalue;
	    } else if (parselong(l, "maxfetch", &lvalue)) {
		/* get maxfetch value */
		artlimit = lvalue;
	    } else if (parselong(l, "initialfetch", &lvalue)) {
		/* get initialfetch value */
		initiallimit = lvalue;
	    } else if (parselong(l, "maxold", &lvalue)) {
		/* get maxold value */
		maxold = lvalue;
	    } else if (parselong(l, "maxage", &lvalue)) {
		/* get maxage value; for compatibility with leafnode-1.7 */
		maxold = lvalue;
	    } else if (parselong(l, "maxlines", &lvalue)) {
		/* get maxlines value */
		maxlines = lvalue;
	    } else if (parselong(l, "minlines", &lvalue)) {
		/* get minlines value; for compatibility with leafnode-1.7 */
		minlines = lvalue;
	    } else if (parselong(l, "maxbytes", &lvalue)) {
		/* get maxbytes value; for compatibility with leafnode-1.7 */
		maxbytes = lvalue;
	    } else if (parselong(l, "maxgroups", &lvalue)) {
		/* get maxgroups value */
		maxgroups = lvalue;
	    } else if (parselong(l, "maxcrosspost", &lvalue)) {
		/* get maxcrosspost value; for compatibility with */
		/* leafnode-1.7 */
		maxgroups = lvalue;
	    } else if (parsestring(l, "killsubjecti", &svalue)) {
		/* get killsubjecti value */
		addstr(&killsubjecti, svalue);
	    } else if (parsestring(l, "killsubject", &svalue)) {
		/* get killsubject value */
		addstr(&killsubject, svalue);
	    } else if (parsestring(l, "killfromaddress", &svalue)) {
		/* get killfromaddress value */
		addstr(&killfromaddress, svalue);
	    } else if (parsestring(l, "killfrom", &svalue)) {
		/* get killfrom value */
		addstr(&killfrom, svalue);
	    } else if (parsestring(l, "create_all_links", &svalue)) {
		/* get create_all_links flag; for compatibility with */
		/* leafnode-1.7 */
		/* XXX: But it is not implemented yet. */
	    } else if (parselong(l, "delay_body", &lvalue)) {
		/* get delay_body value; for compatibility with leafnode-1.7 */
		/* XXX: But it is not implemented yet. */
	    } else if (parsestring(l, "supplement", &svalue)) {
		/* get supplement value */
		struct server* ns = (struct server*)
		    critmalloc(sizeof(struct server),
			       "allocating for supplementary server info");
		ns->servername = svalue;
		ns->viahost = 0;
		ns->port = 0;
		ns->preconnect = 0;
		ns->newsgroups.num = 0;
		ns->newsgroups.size = 0;
		ns->newsgroups.strarray = 0;
		ns->filteredngs.num = 0;
		ns->filteredngs.size = 0;
		ns->filteredngs.strarray = 0;
		ns->postable = 0;
		ns->username = 0;
		ns->password = 0;
		ns->timeout_open = TIMEOUT_OPEN;
		ns->timeout_read = TIMEOUT_READ;
		ns->next = servers;
		servers = ns;
	    } else if (parsestring(l, "fqdn", &svalue)) {
		/* get fqdn value */
		fqdn = svalue;
	    }
	}

	/* reverse servers and calcurate postable number */
	servers = reverse_servers(servers);
    } else {
	mysyslog(LOG_ERR, "cannot open %s", s);
    }
}



/* return 1 if newsdesc is a legal newsgroup description, 0 else */

int legalnewsdesc(const char* newsdesc)
{
    if (newsdesc && *newsdesc &&
	strcmp(newsdesc, "?") != 0 &&
	strcmp(newsdesc, "-x-") != 0 &&
	strcmp(newsdesc, "???") != 0 &&
	strncasecmp(newsdesc, "No description", 14) != 0)
	return 1;
    else
	return 0;
}

void stripspace(char* p)
{
    char* p1;
    char* p2;

    p1 = p2 = p;
    while (*p1) {
	if (isspace(*p1)) {
	    *p2 = ' ';
	    do {
		p1++;
	    } while (isspace(*p1));
	    if (*p1)
		p2++;
	} else {
	    *p2++ = *p1++;
	}
    }
    *p2 = '\0';
}

const char* rfctime(void)
{
    static char date[128];
    const char* months[] = { "Jan", "Feb", "Mar", "Apr",
	"May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
    const char* days[] = { "Sun", "Mon", "Tue", "Wed",
	"Thu", "Fri", "Sat" };
    time_t now;
    struct tm gmt;
    struct tm local;
    struct tm conv;
    signed int diff;

    /* get local and Greenwich times */
    now = time(0);
    gmt	  = *(gmtime(&now));
    local = *(localtime(&now));
    conv  = *(localtime(&now));

    /* if GMT were local, what would it be? */
    conv.tm_year = gmt.tm_year;
    conv.tm_mon	 = gmt.tm_mon;
    conv.tm_mday = gmt.tm_mday;
    conv.tm_hour = gmt.tm_hour;
    conv.tm_min	 = gmt.tm_min;
    conv.tm_sec	 = gmt.tm_sec;
    conv.tm_wday = gmt.tm_wday;
    conv.tm_yday = gmt.tm_yday;

    /* see how far the real local is from GMT-as-local */
    /* should convert to signed int from double properly */
    diff = (mktime(&local) - mktime(&conv)) / 60.0;

    /* finally print the string */
    sprintf(date, "%3s, %d %3s %4d %02d:%02d:%02d %+03d%02d",
	    days[local.tm_wday], local.tm_mday, months[local.tm_mon],
	    local.tm_year+1900, local.tm_hour, local.tm_min, local.tm_sec,
	    diff/60, diff%60);

    return (date);
}

char* skipspaces(char* p)
{
    if (p == 0)
	return 0;
    while (isspace(*p))
	p++;
    return p;
}

char* skipnonspaces(char* p)
{
    if (p == 0)
	return 0;
    while (*p && !isspace(*p))
	p++;
    return p;
}

char* skipre(char* p)
{
    int i;
    if ((p[0] == 'R' || p[0] == 'r') && (p[1] == 'E' || p[1] == 'e')) {
	if (p[2] != '^' || p[3] < '0' || p[3] > '9') {
	    i = 2;
	} else {
	    /* allow Re^2: */
	    for (i = 4; p[i] >= '0' && p[i] <= '9'; i++);
	}
	if (p[i] == ':') p += i + 1;
    }
    return p;
}

void strlower(char* p)
{
    while (*p) {
	*p = tolower(*p);
	p++;
    }
}

void setstring(struct string* p, const char* str)
{
    int len = strlen(str);
    if (len + 1 > p->size) {
	p->size = ((len + 1 + 255) / 256) * 256;
	p->str = critrealloc(p->str, p->size, "setstring");
    }
    strcpy(p->str, str);
}

void addstr(struct strlist* p, char* str)
{
    if (p->num + 1 > p->size) {
	p->size = ((p->size + 1 + 63) / 64) * 64;
	p->strarray = critrealloc(p->strarray, p->size * sizeof(char*),
				  "addstr");
    }
    p->strarray[p->num++] = str;
}

void clearstrlist(struct strlist* p)
{
    int i;
    for (i = 0; i < p->num; i++) {
	free(p->strarray[i]);
    }
    if (p->strarray)
	free(p->strarray);
    p->num = 0;
}

void initpatterns(struct patterns* p, struct strlist* patlist, int icase)
{
    if (p->num > 0)
	clearpatterns(p);
    if (patlist->num > 0) {
	/* compile patterns. */
	int i;
	char* pat;

#ifdef HAVE_POSIX_REGCOMP
	regex_t* array = (regex_t*)critmalloc(sizeof(regex_t) * patlist->num,
					      "compiling patterns");
	int err;
	char errbuf[2048];
#endif
#ifdef HAVE_REGCMP
	char** array = (char**)critmalloc(sizeof(char*) * patlist->num,
					  "compiling patterns");
#endif
#ifdef HAVE_V8_REGCOMP
	struct regexp** array = (struct regexp**)
		critmalloc(sizeof(struct regexp*) * patlist->num,
			   "compiling patterns");
#endif
#ifdef NO_RE
	char** array = (char**)critmalloc(sizeof(char*) * patlist->num,
					  "compiling patterns");
#endif
	p->num = patlist->num;
	p->icase = icase;
	p->patarray = array;
	for (i = 0; i < patlist->num; i++) {
	    static struct string a = { 0, 0 };
	    pat = patlist->strarray[i];
	    if (icase) {
		setstring(&a, pat);
		strlower(a.str);
		pat = a.str;
	    }
#ifdef HAVE_POSIX_REGCOMP
	    if ((err = regcomp(&array[i], pat, REG_EXTENDED)) != 0) {
		regerror(err, &array[i], errbuf, sizeof(errbuf));
		mysyslog(LOG_ERR, "regcomp: %s", errbuf);
	    }
#endif
#ifdef HAVE_REGCMP
	    if ((array[i] = regcmp(pat, NULL)) == NULL) {
		mysyslog(LOG_ERR, "regcomp: invalid pattern: %s", pat);
	    }
#endif
#ifdef HAVE_V8_REGCOMP
	    if ((array[i] = regcomp(pat)) == NULL) {
		mysyslog(LOG_ERR, "regcomp: invalid pattern: %s", pat);
	    }
#endif
#ifdef NO_RE
	    if ((array[i] = strdup(pat)) == NULL) {
		mysyslog(LOG_ERR, "strdup: %s", strerror(errno));
	    }
#endif
	}
    }
}

int matchpatterns(struct patterns* p, char* str)
{
    int i;

    if (p->icase) {
	static struct string a = { 0, 0 };

	setstring(&a, str);
	strlower(a.str);
	str = a.str;
    }
    for (i = 0; i < p->num; i++) {
#ifdef HAVE_POSIX_REGCOMP
	int err;
	char errbuf[2048];
	regex_t* array = (regex_t*)p->patarray;

	if ((err = regexec(&array[i], str, 0, NULL, 0)) == 0) {
	    return i;
	} else if (err != REG_NOMATCH) {
	    regerror(err, &array[i], errbuf, sizeof(errbuf));
	    mysyslog(LOG_ERR, "regexec: %s", errbuf);
	}
#endif
#ifdef HAVE_REGCMP
	char** array = (char**)p->patarray;

	if (regex(array[i], str) != NULL) {
	    return i;
	}
#endif
#ifdef HAVE_V8_REGCOMP
	struct regexp** array = (struct regexp**)p->patarray;

	if (regexec(array[i], str)) {
	    return i;
	}
#endif
#ifdef NO_RE
	char** array = (char**)p->patarray;

	if (strstr(str, array[i]) != NULL) {
	    return i;
	}
#endif
    }
    return -1;
}

void clearpatterns(struct patterns* p)
{
    if (p->num > 0) {
	int i;
	for (i = 0; i < p->num; i++) {
#ifdef HAVE_POSIX_REGCOMP
	    regex_t* array = (regex_t*)p->patarray;
	    regfree(&array[i]);
#endif
#ifdef HAVE_REGCMP
	    char** array = (char**)p->patarray;
	    free(array[i]);
#endif
#ifdef HAVE_V8_REGCOMP
	    struct regexp** array = (struct regexp**)p->patarray;
	    free(array[i]);
#endif
#ifdef NO_RE
	    char** array = (char**)p->patarray;
	    free(array[i]);
#endif
	}
	free(p->patarray);
	p->num = 0;
    }
}

#ifndef HAVE_STRDUP
char* strdup(const char* s)
{
    char* s1;
    s1 = malloc(strlen(s) + 1);
    if (!s1)
	return 0;
    strcpy(s1, s);
    return s1;
}
#endif

int myfnmatch(const char* pattern, const char* str)
{
#ifndef NOFNMATCH
    return fnmatch(pattern, str, 0);
#else
    int len = strlen(pattern);
    if (pattern[0] == '*') {
	if (len == 1) {
	    /* If a pattern is '*', any strings are matched. */
	    return 0;
	} else if (pattern[len - 1] != '*') {
	    /* If a pattern has leading '*' and no other '*' at the end, */
	    /* compare it at the end of string.  */
	    len = strlen(str) - len + 1;  /* skip leading '*' */
	    if (len >= 0 && strcmp(pattern + 1, str + len) == 0)
		return 0;
	} else if (len == 2) {
	    /* If a pattern is '**', any strings are matched. */
	    return 0;
	} else {
	    /* If a pattern has '*' at the beginning and the end, */
	    /* comprare it with all substrings of a string. */
	    if (len == 3) {
		if (strchr(str, pattern[1]))
		    return 0;
	    } else {
		/* make a new pattern removed '*' from beginning and end. */
		static struct string a = { 0, 0 };
		setstring(&a, &pattern[1]);
		a.str[len - 2] = '\0';
		/* compare it with all substrings of the given string. */
		if (strstr(str, a.str) != NULL)
		    return 0;
	    }
	}
    } else if (pattern[len - 1] == '*') {
	/* If pattern is terminated with '*', compare it at the beginning. */
	if (strncmp(pattern, str, len - 1) == 0)
	    return 0;
    } else {
	/* Otherwise, treat string as just string. */
	if (strcmp(pattern, str) == 0)
	    return 0;
    }
    return 1;
#endif
}

void mysyslog(int priority, const char* message, ...)
{
    va_list ap;
    va_start(ap, message);
    vsyslog(priority, message, ap);
    if (priority <= LOG_ERR) {
	vprintf(message, ap);
	printf("\n");
    } else if (priority == LOG_WARNING) {
	if (verbose) {
	    vprintf(message, ap);
	    printf("\n");
	}
    } else {
	if (verbose > 1) {
	    vprintf(message, ap);
	    printf("\n");
	}
    }
    va_end(ap);
}


syntax highlighted by Code2HTML, v. 0.9.1