/* libutil -- read config file Written by Arnt Gulbrandsen and copyright 1995 Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47 22646949. Modified by Cornelius Krasel and Randolf Skerka . Copyright of the modifications 1997. Modified by Kent Robotti . Copyright of the modifications 1998. Modified by Markus Enzenberger . Copyright of the modifications 1998. Modified by Cornelius Krasel . Copyright of the modifications 1998, 1999. Modified and copyright of the modifications 2002 by Ralf Wildenhues . Modified and copyright of the modifications 2001 - 2003 by Matthias Andree . 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 #include #include #include #include #include #ifndef __LCLINT__ #include #endif #include #include #include #include #include #include #include #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; }