/*
libutil -- read config file

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 and copyright of the modifications 2002 by Ralf Wildenhues
<ralf.wildenhues@gmx.de>.
Modified and copyright of the modifications 2001 - 2003 by Matthias Andree
<matthias.andree@gmx.de>.

See file COPYING for restrictions on the use of this software.
*/

#include "leafnode.h"
#include "validatefqdn.h"
#include "ln_log.h"
#include "strlcpy.h"

#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <limits.h>
#include <netdb.h>
#include <netinet/in.h>
#ifndef __LCLINT__
#include <arpa/inet.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/resource.h>

#define COREFILESIZE 1024*1024*64
#define TOKENSIZE 4096

#include "groupselect.h"

#ifndef min
#define min(a,b) ((a < b) ? (a) : (b))
#endif

/*
 * misc. global variables, documented in leafnode.h
 */
time_t expire = 0;
int expiredays = 0;
struct expire_entry *expire_base;
unsigned long artlimit = 0;
unsigned long initiallimit = 0;
long crosspostlimit = 0;
int create_all_links = 0;
int delaybody = 0;
int db_situ = 0;
int debugmode = 0;		/* if 1, log lots of stuff via syslog */
int maxage = 10;
int article_despite_filter = 0;
long maxlines = 0;
long minlines = 0;
unsigned long maxbytes = 0;
static int linebuffer = 0;	/* if 1, make stdout and stderr explicitly 
				   line buffered, GNU libc makes them fully buffered
				   if redirected to files */
int timeout_long = 7;
int timeout_short = 2;
int timeout_active = 90;
int timeout_client = 15*60;	/* when newsreader is idle for this many seconds, disconnect */
int timeout_fetchnews = 5*60;	/* wait at most this many seconds for server replies in fetchnews */
int clamp_maxage = 1;           /* if 1, maxage will be lowered to
				 * groupexpire or expire if the
				 * applicable parameter is lower than
				 * maxage, to prevent duplicate fetches
				 * after a premature exit of
				 * fetchnews. */
int allowstrangers = 0;
char *filterfile;
struct server *servers = NULL;
int allow_8bit_headers = 0;
char *newsadmin;
unsigned long timeout_lock = 5UL;

/** parse the line in \a l, breaking it into param and value at the "="
 * delimiter. The right-hand side can be quoted with double quotes,
 * inside these a backslash escapes a quote that is part of the string.
 * \return success
 */
static int parse_line(
	/*@unique@*/ char *l /** input, will be modified */,
	/*@out@*/ char *param /** output, left-hand side */,
	/*@out@*/ char *value /** output, right-hand side */);

/* parse a line, destructively */
static int
parse_line(char *l, char *param, char *value)
{
    char *p;
    size_t le, len;
    enum modes { plain, quoted } mode = plain;

    p = l;
    /* skip leading spaces, read parameter */
    SKIPLWS(p);
    le = strcspn(p, "=#");
    /* strip trailing space */
    while(le && strchr(" \t", p[le-1])) le--;
    len = min(le, TOKENSIZE - 1);
    if (!len) return 0;
    memcpy(param, p, len);
    param[len] = '\0';
    p += le;

    SKIPLWS(p);
    if (*p++ != '=')
	return 0;
    SKIPLWS(p);

    /* strip trailing blanks from input */
    le = strlen(p);
    while (le--) {
	if (p[le] == ' ' || p[le] == '\t')
	    p[le] = '\0';
	else
	    break;
    }

    /* read value */
    for (le = 0 ; le < TOKENSIZE - 1 ; le ++) {
	char c = *p++;
	if (mode == plain) {
	    if (c == '#' || c == '\0') { break; }
	    if (c == '"') { mode = quoted; continue; }
	    *value++ = c;
	} else if (mode == quoted) {
	    if (c == '\\') {
		if (*p) {
		    *value++ = *p++; continue; 
		} else
		    return 0;
	    }
	    if (c == '\0') return 0;
	    if (c == '"') break;
	    *value++ = c;
	} else {
	    abort();
	}
    }
    *value = '\0';
    return 1;
}

/*
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.
1999-07-15 - Matthias Andree
             Set p and q defaults to 0
*/

/* parses value into timeout_lock, returns 0 for success, -1 for error */
static int read_timeout_lock(
	const char *value, /* input */
	const char *source /* where did value come from */) {
    char *t;
    unsigned long u;

    errno = 0;
    u = strtoul(value, &t, 10);
    if ((u != 0 || errno == 0)
	    && t > value
	    && (!*t || isspace((unsigned char)*t)))
    {
	timeout_lock = u;
	if (debugmode) {
	    if (timeout_lock) {
		syslog(LOG_DEBUG,
			"%s: waiting %lu second%s for lockfile",
			source, timeout_lock, PLURAL(timeout_lock));
	    } else {
		syslog(LOG_DEBUG,
			"%s: waiting indefinitely for lockfile", source);
	    }
	}
	return 0;
    } else {
	ln_log(LNLOG_SERR, LNLOG_CTOP, "error: cannot parse lockfile value \"%s\" from %s", value, source);
	return -1;
    }
}

int
readconfig(int logtostderr)
{
    struct server *p = 0, *q = 0;
    struct rlimit corelimit;
    struct expire_entry *ent = NULL, *prev = NULL;
    FILE *f;
    char *l;
    char *param, *value;
    char s[SIZE_s + 1];
    unsigned long curline = 0;

    artlimit = 0;
    param = critmalloc(TOKENSIZE, "allocating space for parsing");
    value = critmalloc(TOKENSIZE, "allocating space for parsing");

    xsnprintf(s, SIZE_s, "%s/config", sysconfdir);
    if ((f = fopen(s, "r")) == NULL) {
	syslog(LOG_ERR, "cannot open %s", s);
	free(param);
	free(value);
	return 0;
    }
    while ((l = getaline(f))) {
	++curline;
	if (parse_line(l, param, value)) {
	    if (strcmp("username", param) == 0) {
		if (p) {
		    if (p->username != NULL)
			free(p->username);
		    p->username = critstrdup(value, "readconfig");
		    if (debugmode)
			syslog(LOG_DEBUG, "config: found username for %s",
			       p->name);
		} else
		    syslog(LOG_ERR, "config: no server for username %s", value);
	    } else if (strcmp("password", param) == 0) {
		if (p) {
		    if (p->password != NULL)
			free(p->password);
		    p->password = critstrdup(value, "readconfig");
		    if (debugmode)
			syslog(LOG_DEBUG, "config: found password for %s",
			       p->name);
		} else
		    syslog(LOG_ERR, "config: no server for password");
	    } else if (strcmp("timeout", param) == 0) {
		if (p) {
		    p->timeout = atoi(value);
		    if (debugmode)
			syslog(LOG_DEBUG, "config: timeout is %d second%s",
			       p->timeout, PLURAL(p->timeout));
		} else
		    syslog(LOG_ERR, "config: no server for timeout");
	    } else if (strcmp("allowSTRANGERS", param) == 0) {
		if (value && strlen(value)) {
		    if (atoi(value) == 42)
			allowstrangers = 1;
		    if (debugmode)
			syslog(LOG_DEBUG,
				"config: allowstrangers is %s",
				allowstrangers ? "set" : "unset");
		}
	    } else if (strcmp("create_all_links", param) == 0) {
		if (value && strlen(value)) {
		    create_all_links = atoi(value);
		    if (create_all_links && debugmode)
			syslog(LOG_DEBUG,
				"config: link articles in all groups");
		}
	    } else if (strcmp("expire", param) == 0) {
		int i = atoi(value);
		if (i >= (INT_MAX / SECONDS_PER_DAY))
		    i = (INT_MAX / SECONDS_PER_DAY) - 1;
		if (i <= 0) {
		    ln_log(LNLOG_SERR, LNLOG_CTOP, "config: expire must be positive, not %d, abort", i);
		    exit(1);
		}
		expiredays = i;
		expire = time(NULL) - (time_t) (SECONDS_PER_DAY * i);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: expire is %d day%s", i, PLURAL(i));
	    } else if (strcmp("newsadmin", param) == 0) {
		if (debugmode)
		    syslog(LOG_DEBUG, "config: newsadmin is %s", value);
		newsadmin = critstrdup(value, "readconfig");
	    } else if (strcmp("filterfile", param) == 0) {
		if (debugmode)
		    syslog(LOG_DEBUG, "config: filterfile is %s", value);
		filterfile = critstrdup(value, "readconfig");
	    } else if ((strcmp("hostname", param) == 0) ||
		    (strcmp("fqdn", param) == 0)) {
		if (debugmode)
		    syslog(LOG_DEBUG, "config: hostname is %s", value);
		(void)xstrlcpy(fqdn, value, sizeof(fqdn));
	    } else if ((strcmp("maxcrosspost", param) == 0) ||
		       (strcmp("maxgroups", param) == 0)) {
		/* maxgroups is for compatibility with leafnode+ */
		crosspostlimit = strtol(value, NULL, 10);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: crosspostlimit is %ld group%s",
			   crosspostlimit, PLURAL(crosspostlimit));
	    } else if (strcmp("article_despite_filter", param) == 0) {
		article_despite_filter = atoi(value) ? 1 : 0;
		if (debugmode)
		    syslog(LOG_DEBUG, "config: article_despite_filter is %s",
			   article_despite_filter ? "TRUE" : "FALSE");
	    } else if (strcmp("clamp_maxage", param) == 0) {
		clamp_maxage = atoi(value);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: clamp_maxage is %s",
			   clamp_maxage ? "TRUE" : "FALSE");
	    } else if (strcmp("maxlines", param) == 0) {
		maxlines = strtol(value, NULL, 10);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: postings have max. %ld line%s",
			   maxlines, PLURAL(maxlines));
	    } else if (strcmp("minlines", param) == 0) {
		minlines = strtol(value, NULL, 10);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: postings have min. %ld line%s",
			   minlines, PLURAL(minlines));
	    } else if (strcmp("maxbytes", param) == 0) {
		maxbytes = strtoul(value, NULL, 10);
		if (debugmode)
		    syslog(LOG_DEBUG,
			   "config: postings have max. %lu byte%s",
			   maxbytes, PLURAL(maxbytes));
	    } else if (strcmp("linebuffer", param) == 0) {
		linebuffer = atoi(value);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: linebuffer is %d", linebuffer);
	    } else if (strcmp("allow_8bit_headers", param) == 0) {
		allow_8bit_headers = atoi(value);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: allow_8bit_headers is %d",
			    allow_8bit_headers);
	    } else if (strcmp("debugmode", param) == 0) {
		int d;
		d = atoi(value);
		debugmode = d > debugmode ? d : debugmode;
		if (debugmode)
		    syslog(LOG_DEBUG, "config: debugmode is %d", debugmode);
	    } else if (strcmp("delaybody_in_situ", param) == 0) {
		db_situ = atoi(value);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: delaybody_in_situ is %d (default 0)",
			   db_situ);
	    } else if (strcmp("delaybody", param) == 0) {
		delaybody = atoi(value);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: delaybody is %d (default 0)",
			   delaybody);
	    } else if (strcmp("timeout_short", param) == 0) {
		timeout_short = atoi(value);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: timeout_short is %d day%s",
			   timeout_short, PLURAL(timeout_short));
	    } else if (strcmp("timeout_long", param) == 0) {
		timeout_long = atoi(value);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: timeout_long is %d day%s",
			   timeout_long, PLURAL(timeout_long));
	    } else if (strcmp("timeout_active", param) == 0) {
		timeout_active = atoi(value);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: timeout_active is %d day%s",
			   timeout_active, PLURAL(timeout_active));
	    } else if (strcmp("timeout_client", param) == 0) {
		timeout_client = atoi(value);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: timeout_client is %d second%s",
			   timeout_client, PLURAL(timeout_client));
	    } else if (strcmp("timeout_fetchnews", param) == 0) {
		timeout_fetchnews = atoi(value);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: timeout_fetchnews is %d second%s",
			   timeout_fetchnews, PLURAL(timeout_fetchnews));
	    } else if (strcmp("timeout_lock", param) == 0) {
		read_timeout_lock(value, "config");
	    } else if (strncmp("groupexpire", param, 11) == 0) {
		char *m;
		m = param;
		while (*m && !(isspace((unsigned char)*m)))
		    m++;
		while (isspace((unsigned char)*m))
		    m++;
		if (m && *m) {
		    time_t e, i = (time_t) atol(value);
		    if (i >= (INT_MAX / SECONDS_PER_DAY))
			i = (INT_MAX / SECONDS_PER_DAY) - 1;
		    if (debugmode) {
			if ((long)i < 0)
			    syslog(LOG_DEBUG,
			           "config: groupexpire for %s is %ld (never)",
				   m, (long)i);
			else if (i == 0) {
			    fprintf(stderr, 
			       "config: groupexpire for %s is 0, which is treated as \"use the default expire\"\n", m);
			    syslog(LOG_INFO,
			       "config: groupexpire for %s is 0, which is treated as \"use the default expire\"",
			       m);
			} else
			    syslog(LOG_DEBUG,
			           "config: groupexpire for %s is %ld day%s",
			           m, (long)i, PLURAL(i));
		    }
		    e = time(NULL) - (time_t) (SECONDS_PER_DAY * i);
		    ent = (struct expire_entry *)
			critmalloc(sizeof(struct expire_entry), "readconfig");
		    ent->group = critstrdup(m, "readconfig");
		    ent->days = i;
		    ent->xtime = e;
		    ent->next = prev;
		    prev = ent;
		}
	    } else if ((strcmp("maxage", param) == 0) ||
		       (strcmp("maxold", param) == 0)) {
		/* maxold is for compatibility with leafnode+ */
		maxage = atoi(value);
		if (maxage > LONG_MAX / 86400) {
		    maxage = 24854; /* 32-bit: LONG_MAX / 86400 - 1 */
		    ln_log(LNLOG_SWARNING, LNLOG_CTOP,
			    "warning: config: maxage cannot exceed %d, "
			    "please fix %s", maxage, s);
		}
		if (debugmode)
		    syslog(LOG_DEBUG, "config: maxage is %d", maxage);
	    } else if (strcmp("maxfetch", param) == 0) {
		artlimit = strtoul(value, NULL, 10);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: maxfetch is %lu", artlimit);
	    } else if (strcmp("port", param) == 0) {
		unsigned long pp = strtoul(value, NULL, 10);
		if (p) {
		    if (pp == 0 || pp > 65535) {
			syslog(LOG_ERR,
			       "config: invalid port number for nntpport %s",
			       value);
		    } else {
			p->port = (unsigned int)pp;
			if (debugmode)
			    syslog(LOG_DEBUG, "config: nntpport is %u",
				   p->port);
		    }
		} else {
		    syslog(LOG_ERR, "config: no server for nntpport %s", value);
		}
	    } else if (strcmp("noactive", param) == 0) {
		long tmp;
		errno = 0;
		tmp = strtol(value,NULL,10);
		if (errno) {
		    syslog(LOG_ERR, "config: invalid value \"%s\" for noactive", value);
		} else {
		    if (p) {
			p->updateactive = !tmp;
			if (debugmode)
			    syslog(LOG_DEBUG, "config: %s active file updates for %s",
				    p->updateactive ? "enabled" : "no", p->name);
		    } else {
			syslog(LOG_ERR, "config: no server for noactive = %s", 
				value);
		    }
		}
	    } else if (strcmp("noxover", param) == 0) {
		if (p) {
		    p->noxover = TRUE;
		    if (debugmode)
			syslog(LOG_DEBUG, "config: no XOVER for %s",
			       p->name);
		} else
		    syslog(LOG_ERR, "config: no server for noxover = %s", value);
	    } else if (strcmp("nodesc", param) == 0) {
		if (p) {
		    p->descriptions = !atoi(value);
		    if (debugmode)
			syslog(LOG_DEBUG, "config: %s LIST NEWSGROUPS for %s",
			       p->descriptions ? "enabled" : "no", p->name);
		} else
		    syslog(LOG_ERR, "config: no server for nodesc = %s", value);
	    } else if (strcmp("nopost", param) == 0) {
		if (p) {
		    p->nopost = atoi(value);
		    if (debugmode)
			syslog(LOG_DEBUG, "config: nopost for %s is %d",
			       p->name, p->nopost);
		} else
		    syslog(LOG_ERR, "config: no server for nopost = %s", value);
	    } else if (strcmp("post_anygroup", param) == 0) {
		if (p) {
		    p->post_anygroup = atoi(value);
		    if (debugmode)
			syslog(LOG_DEBUG, "config: post_anygroup for %s is %d",
			       p->name, p->nopost);
		} else
		    syslog(LOG_ERR, "config: no server for post_anygroup = %s", value);
	    } else if (strcmp("noread", param) == 0) {
		if (p) {
		    p->noread = atoi(value);
		    if (debugmode)
			syslog(LOG_DEBUG, "config: noread for %s is %d",
			       p->name, p->noread);
		} else
		    syslog(LOG_ERR, "config: no server for noread = %s", value);
	    } else if (strcmp("only_groups_match_all", param) == 0) {
		if (p) {
		    p->only_groups_match_all = atoi(value);
		    if (debugmode)
			syslog(LOG_DEBUG, "config: only_groups_match_all for %s is %d",
			       p->name, p->only_groups_match_all);
		} else
		    syslog(LOG_ERR, "config: no server for only_groups_match_all = %s", value);
	    } else if (strcmp("initialfetch", param) == 0) {
		initiallimit = strtoul(value, NULL, 10);
		if (debugmode)
		    syslog(LOG_DEBUG, "config: initialfetch is %lu",
			   initiallimit);
	    } else if ((strcmp("server", param) == 0) ||
		       (strcmp("supplement", param) == 0)) {
		if (debugmode)
		    syslog(LOG_DEBUG, "config: server is %s", value);
		p = (struct server *)critmalloc(sizeof(struct server),
						    "allocating space for server");
		p->name = critstrdup(value, "readconfig");
		p->descriptions = TRUE;
		p->next = NULL;
		p->timeout = 30;	/* default 30 seconds */
		p->port = 0;
		p->username = NULL;
		p->password = NULL;
		p->nopost = 0;
		p->noread = 0;
		p->noxover = 0;
		p->post_anygroup = 0;
		p->updateactive = TRUE;
		p->group_pcre = NULL;
		p->only_groups_match_all = 0;
		if (servers == NULL)
		    servers = p;
		else
		    q->next = p;
		q = p;
	    } else if (0 == strcmp("only_groups_pcre", param)) {
		pcre *re = gs_compile(value);
		if (!re) exit(2);
		if (p) {
		    p->group_pcre = re;
		    if (debugmode)
			syslog(LOG_DEBUG, "config: only_groups_pcre for %s is %s",
			       p->name, value);
		} else {
		    free(re);
		    syslog(LOG_ERR, "config: no server for nopost = %s", value);
		}
	    } else {
		ln_log(LNLOG_SERR, LNLOG_CTOP,
			"config: unknown line %lu: \"%s = %s\"", curline,
			param, value);
	    }
	} else {
	    size_t i;
	    if ((i = strspn(l, " \t")) < strlen(l) && l[i] != '#') {
		ln_log(LNLOG_SERR, LNLOG_CTOP,
			"config: malformatted line %lu: \"%s\"", curline,
			l);
	    }
	}
    }
    if (maxage != 0 && maxage > expiredays && clamp_maxage == 0) {
	ln_log(LNLOG_SERR, LNLOG_CTOP,
		"config: maxage (%d) > expire (%d). This can cause duplicate download. Please fix your configuration, maxage must not be greater than expire.",
		maxage, expiredays);
	exit(1);
    }
    debug = debugmode;

    if (!newsadmin) {
	const char t[] = NEWS_USER;
	newsadmin = critmalloc(strlen(fqdn) + strlen(t) + 2, "readconfig");
	strcpy(newsadmin, t); /* RATS: ignore */
	strcat(newsadmin, "@");
	strcat(newsadmin, fqdn); /* RATS: ignore */
    }

    expire_base = ent;
    fclose(f);
    free(param);
    free(value);

    if (servers == NULL) {
	syslog(LOG_ERR, "no server declaration in config file");
	return 0;
    }
    if (!expire)
	syslog(LOG_ERR, "no expire declaration in config file");

    /* check for duplicate server configurations */
    {
	unsigned short port = 0;
	struct servent *sp = getservbyname("nntp", "tcp");
	if (sp) port = ntohs(sp->s_port);

	for (p = servers; p ; p=p->next) {
	    for (q = p->next ; q ; q=q->next) {
		unsigned short pp = p->port, qp = q->port;
		if (!pp) pp = port;
		if (!qp) qp = port;
		if (!pp || !qp) {
		    syslog(LOG_ERR, "Cannot resolve service \"nntp\" protocol \"tcp\".");
		    fprintf(stderr, "Cannot resolve service \"nntp\" protocol \"tcp\".\n");
		    return 0;
		}
		if (pp != qp) continue;
		if (strcasecmp(p->name, q->name)) continue;
		syslog(LOG_ERR, "Duplicate definition for server %s port %hu", p->name, pp);
		fprintf(stderr, "Duplicate definition for server %s port %hu\n", p->name, pp);
		return 0;
	    }
	}
    }

    if (debugmode > 1) {
	getrlimit(RLIMIT_CORE, &corelimit);
	corelimit.rlim_cur = COREFILESIZE;
	if (setrlimit(RLIMIT_CORE, &corelimit) < 0 && debugmode)
	    syslog(LOG_DEBUG, "Changing core file size failed: %m");
	corelimit.rlim_cur = 0;
	getrlimit(RLIMIT_CORE, &corelimit);
	if (debugmode)
	    syslog(LOG_DEBUG, "Core file size: %d", (int)corelimit.rlim_cur);
    }

    l = getenv("LN_LOCK_TIMEOUT");
    if (l && *l)
	read_timeout_lock(l, "LN_LOCK_TIMEOUT");

    if (linebuffer) {
	fflush(stdout);
	setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
	fflush(stderr);
	setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
    }

    validatefqdn(logtostderr);

    if (debugmode && verbose) {
	puts("");
	puts("WARNING:  Make sure that syslog.conf captures news.debug logging");
	puts("--------  and obtain your debug output from syslog.");
	puts("WARNING:  The screen output below is not sufficient. Check syslog!");
	puts("");
	sleep(3);
    }
    return 1;
}

/*
1997-05-27 - T. Sweeney - Find a group in the expireinfo linked list and return
                          its expire time. Otherwise, return zero.
*/
static struct expire_entry *
lookup_expireent(char *group)
{
    struct expire_entry *a;

    a = expire_base;
    while (a != NULL) {
	if (ngmatch(a->group, group) == 0)
	    return a;
	a = a->next;
    }
    return NULL;
}

static void
freeserver(struct server *a) {
    if (a->group_pcre) pcre_free(a->group_pcre);
    if (a->name) free(a->name);
    if (a->username) free(a->username);
    if (a->password) free(a->password);
    free(a);
}

void /* exported for exclusive use in nntpd.c */
freeservers(void) {
    struct server *i = servers, *n;

    while(i != NULL) {
	n = i->next;
	freeserver(i);
	i = n;
    }
    servers = NULL;
}


void
freeexpire(void)
{
    struct expire_entry *a, *b;

    a = expire_base;
    while(a)
    {
	b = a->next;
	free(a->group);
	free(a);
	a = b;
    }
}

void freeconfig(void) {
    freeservers();
    if (newsadmin)
	free(newsadmin);
    freefilter();
    if (filterfile)
	free(filterfile);
    freegetaline();
    freeexpire();
    (void)lookup(LOOKUP_FREE);
    freelastreply();
}

time_t lookup_expire(char *group)
{
    struct expire_entry *e;
    e = lookup_expireent(group);
    if (e) return e->xtime;
    return 0;
}

int lookup_expiredays(char *group)
{
    struct expire_entry *e;
    e = lookup_expireent(group);
    if (e) return e->days;
    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1