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