/* nntpd -- the NNTP server 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 and Kazushi (Jam) Marukawa . Copyright of the modifications 1998, 1999. Modified by Matthias Andree Copyright of the modifications 2000 - 2002. Modified by Ralf Wildenhues Copyright of the modifications 2002. Modified by Jonathan Larmour Copyright of the modifications 2002. See file COPYING for restrictions on the use of this software. */ #include "leafnode.h" #include "masock.h" #include "mastring.h" #include "validatefqdn.h" #include "strlcpy.h" #include "ln_log.h" #include "nntpd.h" #ifdef SOCKS #include #endif #include #include #include #ifndef __LCLINT__ #include #endif #include #include "system.h" #include #include #include #include #include #include #include #include #include #include #include #include #define MAXLINELENGTH 1000 #ifdef HAVE_IPV6 /* * * local union struct */ union sockaddr_union { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; }; #endif /*@null@*/ static struct newsgroup *group; /* current group, initially none */ /*@null@*/ static struct newsgroup *xovergroup = NULL; static unsigned long artno; /* current article number */ /*@dependent@*/ /*@null@*/ static char *cmd; /* current command line */ static time_t activetime; int debug = 0; int verbose = 0; /* verbose doesn't count here */ static void fatal_write(void) { /*@observer@*/ static const char *const e = "Write error on stdout."; syslog(LOG_CRIT, "%s", e); fprintf(stderr, "%s\n", e); exit(EXIT_FAILURE); } static void rereadactive(void) { struct stat st; char s[SIZE_s+1]; (void)xsnprintf(s, SIZE_s, "%s/leaf.node/groupinfo", spooldir); if ((!stat(s, &st) && (st.st_mtime > activetime)) || (active == NULL)) { char *grouptmp = NULL; if (debugmode) syslog(LOG_DEBUG, "rereading %s", s); if (group) { grouptmp = critstrdup(group->name, "rereadactive"); } readactive(); if (activesize == 0) fakeactive(); else activetime = st.st_mtime; if (grouptmp) { group = findgroup(grouptmp); xovergroup = NULL; free(grouptmp); } } } /* * pseudo article stuff */ /* build and return an open fd to pseudoart in group */ static FILE * buildpseudoart(const char *grp) { FILE *f; int fd; mastr *n = mastr_new(PATH_MAX); (void)mastr_vcat(n, spooldir, "/temp.files/pseudo.XXXXXXXXXX", NULL); fd = safe_mkstemp(mastr_modifyable_str(n)); if (fd < 0) { syslog(LOG_ERR, "Could not create pseudoarticle: mkstemp failed: %m"); mastr_delete(n); return NULL; } (void)unlink(mastr_str(n)); f = fdopen(fd, "w+b"); if (f == NULL) { syslog(LOG_ERR, "Could not create pseudoarticle: fdopen failed: %m"); (void)close(fd); mastr_delete(n); return NULL; } fprintf(f, "Path: %s\n", fqdn); fprintf(f, "Newsgroups: %s\n", grp); fprintf(f, "From: Leafnode <%s>\n", newsadmin); fprintf(f, "Subject: Leafnode placeholder for group %s\n", grp); fprintf(f, "Date: %s\n", rfctime()); fprintf(f, "Message-ID: \n", grp, fqdn); fprintf(f, "\n"); fprintf(f, "This server is running leafnode, which is a dynamic NNTP proxy.\n" "This means that it does not retrieve newsgroups unless someone is\n" "actively reading them.\n" "\n" "If you do an operation on a group - such as reading an article,\n" "looking at the group table of contents or similar, then leafnode\n" "will go and fetch articles from that group when it next updates.\n\n"); fprintf(f, "Since you have read this dummy article, leafnode will retrieve\n" "the newsgroup %s when fetchnews is run\n" "the next time. If you'll look into this group a little later, you\n" "will see real articles.\n\n", grp); fprintf(f, "If you see articles in groups you do not read, that is almost\n" "always because of cross-posting. These articles do not occupy any\n" "more space - they are hard-linked into each newsgroup directory.\n" "\n" "If you do not understand this, please talk to your newsmaster.\n" "\n" "Leafnode can be found at\n" "\thttp://www.leafnode.org/\n\n"); if (ferror(f)) { (void)fclose(f); f = NULL; } else { rewind(f); } mastr_delete(n); return f; } static int parserange(char *arg, unsigned long *a, unsigned long *b) { char *l = arg; /* no argument */ if (!*l) return 0; /* parse */ if (*l != '-') *a = strtoul(arg, &l, 10); SKIPLWS(l); if (*l == '-') { ++l; SKIPLWS(l); if (isdigit((unsigned char)*l)) *b = strtoul(l, &l, 10); } else { *b = *a; } SKIPLWS(l); /* trailing garbage */ if (l && *l) { return 0; } return 1; } static int is_pseudogroup(/*@null@*/ const struct newsgroup *g) { if (!g) return 0; if (!chdirgroup(g->name, FALSE)) return 1; /* if (isinteresting(g->name)) return 0; */ if (g->last < g->first) return 1; if (g->last <= 1) return 1; return 0; } /* * XXX FIXME: an option is needed if the administrator * locks the interesting.group permissions in order to have a fixed set * of subscriptions that are always available, or if the subscription * is handled outside leafnode. */ static void markinterest(const char *ng) { struct stat st; struct utimbuf buf; int err; time_t now; FILE *f; char s[SIZE_s+1]; /* OK, the user may be setting the file to root owned with group * write permission. This is a problem when trying to set the ctime * without setting the mtime, because we would need privileges to do * that. Instead, we'll bump both times and warn the user. */ err = 0; (void)xsnprintf(s, SIZE_s, "%s/interesting.groups/%s", spooldir, ng); if (stat(s, &st) == 0) { const char *touched = "ctime"; /* group was read before: keep mtime, but bump up ctime */ now = time(NULL); buf.actime = (now < st.st_atime) ? st.st_atime : now; /* now < update may happen through HW failures */ buf.modtime = st.st_mtime; if (utime(s, &buf)) { int oe = errno; /* cannot set time - the file's user-id must be the NEWS_USER's * 2nd best - we'll try to bump both times, but this means * timeout_short applies, so warn the user. */ if (utime(s, NULL)) { /* ok, didn't work either. complain and give up. * We used to fall through to the code below, but that * may truncate the file and thus lose delaybody article * numbers. */ syslog(LOG_ERR, "Error: cannot set ctime/mtime timestamp of %s: %s.", s, strerror(errno)); return; } else { /* complain that timeout_short applies, so that the user * is not surprised by premature group unsubscription. */ touched = "ctime and mtime"; if (timeout_short < timeout_long) { syslog(LOG_WARNING, "Warning: cannot set the ctime timestamp of %s without resetting mtime (%s). " "This means that timeout_short will apply rather than timeout_long. " "Either reset file ownership to " NEWS_USER " or set timeout_short and timeout_long to the same value to suppress this warning.", s, strerror(oe)); } } } if (debugmode && !err) syslog(LOG_DEBUG, "markinterest: %s touched %s", ng, touched); } else { err = 1; } if (err) { f = fopen(s, "w"); /* truncating sets ctime and mtime */ if (f == NULL || fclose(f) == EOF) { syslog(LOG_ERR, "Could not create %s: %m", s); } else { if (debugmode) syslog(LOG_DEBUG, "markinterest: %s new", ng); } } else { int fd = open(".", O_RDONLY); (void)chdirgroup(ng, FALSE); if (fd >= 0) { (void)fchdir(fd); (void)close(fd); } } } /* open a pseudo art */ /* WARNING: article_num MUST be 0 for selection based on Message-ID */ static FILE * fopenpseudoart(const char *arg, const unsigned long article_num) { FILE *f = NULL; char *c; struct newsgroup *g; if (group && (article_num && ((article_num == group->first && group->first >= group->last) || is_pseudogroup(group)))) { f = buildpseudoart(group->name); } else if (!article_num) { if (!strncmp(arg, ""); if (0 == strcasecmp(c, fqdn)) { g = findgroup(mastr_str(msgidbuf)); if (g && (g->last <= 1 || g->first >= g->last)) { markinterest(g->name); f = buildpseudoart(g->name); } } } mastr_delete(msgidbuf); } } return f; } /* open an article by number or message-id */ static FILE * fopenart(const char *arg) { unsigned long int a; FILE *f; char *t; struct stat st; /*@temp@*/ char buf[32]; t = NULL; a = strtoul(arg, &t, 10); if (arg && *arg == '<') { /* message ID given */ f = fopen(lookup(arg), "r"); if (!f) f = fopenpseudoart(arg, 0); } else if (t && !*t && group != NULL) { const char *ptr = arg; /* number not given -> take current article pointer */ if (!a) { a = artno; sprintf(buf, "%lu", artno); /* RATS: ignore */ ptr = buf; } if (is_pseudogroup(group)) { f = fopenpseudoart(ptr, a); } else { f = fopen(ptr, "r"); } if (f != NULL) artno = a; markinterest(group->name); } else { f = NULL; } if (f != NULL && (fstat(fileno(f), &st) || st.st_size == 0)) { (void)fclose(f); f = NULL; } return f; } /* * Mark an article for download by appending its number to the * corresponding file in interesting.groups */ static int markdownload(const char *ng, unsigned long id) { int i, e = 0; unsigned long n; FILE *f; char *t; char s[SIZE_s+1]; (void)xsnprintf(s, SIZE_s, "%s/interesting.groups/%s", spooldir, ng); if ((f = fopen(s, "r+"))) { i = 0; while ((t = getaline(f))) { if (sscanf(t, "%lu", &n) == 1 && n == id) { (void)fclose(f); /* we only read from the file */ return 0; /* already marked */ } if (ferror(f)) e = errno; ++i; } if (i < BODY_DOWNLOAD_LIMIT) { (void)fprintf(f, "%lu\n", id); if (ferror(f)) e = errno; if (debugmode) syslog(LOG_DEBUG, "Marking %s %lu for download", ng, id); } else { syslog(LOG_ERR, "Too many bodies marked in %s", ng); } if (fclose(f)) e = errno; } if (e) { syslog(LOG_ERR, "I/O error handling \"%s\": %s", s, strerror(e)); return -1; } return 1; } static void nogroup(void) { printf("412 Use the GROUP command first\r\n"); if (debugmode) syslog(LOG_DEBUG, ">412 Use the GROUP command first"); return; } /* display an article or somesuch */ /* DOARTICLE */ static void doarticle(const char *arg, int what) { FILE *f; char *p = NULL; char *q = NULL; char *t; unsigned long localartno; unsigned long replyartno; char *localmsgid, *xref = NULL; char *markgroup = NULL; char s[SIZE_s+1]; f = fopenart(arg); if (!f) { if (arg && *arg != '<' && !group) { nogroup(); } else if (strlen(arg)) { printf("430 No such article: %s\r\n", arg); if (debugmode) syslog(LOG_DEBUG, ">430 No such article: %s", arg); } else { printf("423 No such article: %lu\r\n", artno); if (debugmode) syslog(LOG_DEBUG, ">423 No such article: %lu", artno); } return; } if (!*arg) { /* no. implicit */ localartno = artno; localmsgid = fgetheader(f, "Message-ID:"); } else if (*arg == '<') { /* message-id -- do not modify artno */ localartno = 0; localmsgid = critstrdup(arg, "doarticle"); } else { /* no. explicit */ localartno = strtoul(arg, NULL, 10); localmsgid = fgetheader(f, "Message-ID:"); } replyartno = localartno; if (!localartno) { /* we have to extract the article number and the newsgroup from * the Xref: header */ xref = fgetheader(f, "Xref:"); p = xref; if (p) { /* skip host name */ while (*p && !isspace((unsigned char)*p)) { p++; } while (isspace((unsigned char)*p)) { p++; } } if (p) { /* search article number of current group in Xref: */ if (group) { while ((q = strstr(p, group->name)) != NULL) { q += strlen(group->name); if (*q++ == ':') { localartno = strtoul(q, NULL, 10); markgroup = group->name; break; } p = q + strcspn(q, " \t"); } } /* if we don't have a localartno, then we need to mark this * article in a different news group */ if (!localartno) { char *r = p; while ((q = strchr(r, ':'))) { *q++ = '\0'; if (isinteresting(p)) { /* got one we can mark */ markgroup = r; localartno = strtoul(q, NULL, 10); break; } while (isdigit((unsigned char)*q)) { q++; } while (isspace((unsigned char)*q)) { q++; } r = q; } } } } else if (group) { markgroup = group->name; } if (!localmsgid) { const char *tmp = "423 Corrupt article."; printf("%s\r\n", tmp); syslog(LOG_WARNING, ">%s", tmp); if (replyartno) { (void)xsnprintf(s, SIZE_s, "%lu", replyartno); (void)log_unlink(s, 0); } (void)fclose(f); if (xref) free(xref); return; } (void)xsnprintf(s, SIZE_s - 24, "%3d %lu %s article retrieved - ", 223 - what, replyartno, localmsgid); free(localmsgid); if (what == 0) strcat(s, "request text separately"); else if (what == 1) strcat(s, "body follows"); else if (what == 2) strcat(s, "head follows"); else strcat(s, "text follows"); printf("%s\r\n", s); if (debugmode) syslog(LOG_DEBUG, ">%s", s); while ((t = getaline(f)) && *t) { if (what & 2) { if (*t == '.') (void)fputc('.', stdout); (void)fputs(t, stdout); (void)fputs("\r\n", stdout); if (ferror(stdout)) fatal_write(); } } /* Matthias Andree, 2002-03-05: * t == NULL or *t == '\0' makes a difference here. * - t == NULL means we ran into EOF and don't have a body in delaybody mode. * - t != NULL but *t == '\0' means we just read the empty separator line between header and body. */ if (what == 3) printf("\r\n"); /* empty separator line */ if (what & 1) { /* delaybody: t == NULL: body is missing, mark for download t != NULL: body is present */ if (t == NULL) { if (localartno && markgroup != NULL) { switch (markdownload(markgroup, localartno)) { case 0: printf("\r\n\r\n" "\t[ Leafnode: ]\r\n" "\t[ This message has already been " "marked for download. ]\r\n"); break; case 1: printf("\r\n\r\n" "\t[ Leafnode: ]\r\n" "\t[ Message %lu of %s ]\r\n" "\t[ has been marked for download. ]\r\n", localartno, markgroup); break; default: printf("\r\n\r\n" "\t[ Leafnode: ]\r\n" "\t[ Message %lu of %s ]\r\n" "\t[ cannot be marked for download. ]\r\n" "\t[ (Check the server's syslog " "for information). ]\r\n", localartno, markgroup); break; } } else { /* did not figure a group for which to mark this article */ syslog(LOG_ERR, "cannot mark for body download: arg=\"%s\" " "localartno=%lu markgroup=\"%s\" group=\"%s\"", arg, localartno, markgroup ? markgroup : "(null)", group ? group->name : "(null)"); printf("\r\n\r\n" "\t[ Leafnode: ]\r\n" "\t[ I cannot figure out a newsgroup for which to download ]\r\n" "\t[ this article. Please report this condition to the ]\r\n" "\t[ leafnode mailing list, with leafnode version, the name ]\r\n" "\t[ and the version of your news reader and a log excerpt. ]\r\n"); } } else { /* immediate body */ while ((t = getaline(f))) { if (*t == '.') (void)fputc('.', stdout); (void)fputs(t, stdout); (void)fputs("\r\n", stdout); if (ferror(stdout)) fatal_write(); } } } if (what) printf(".\r\n"); (void)fclose(f); if (xref) free(xref); return; /* OF COURSE there were no errors */ } /* change to group - note no checks on group name */ static int dogroup(const char *arg) { struct newsgroup *g; g = findgroup(arg); if (g) { group = g; /* global */ if (isinteresting(arg)) { if (debugmode) syslog(LOG_DEBUG, "marked group %s interesting", arg); markinterest(arg); } if (!is_pseudogroup(g)) { /* regular news group */ if (debugmode) syslog(LOG_DEBUG, ">211 %lu %lu %lu %s group selected", g->last >= g->first ? g->last - g->first + 1 : 0, g->first, g->last, g->name); printf("211 %lu %lu %lu %s group selected\r\n", g->last >= g->first ? g->last - g->first + 1 : 0, g->first, g->last, g->name); } else { /* pseudo news group */ if (debugmode) syslog(LOG_DEBUG, ">211 %lu %lu %lu %s group selected (pseudo article)", 1lu, g->first, g->first, g->name); printf("211 %lu %lu %lu %s group selected (pseudo article)\r\n", 1lu, g->first, g->first, g->name); } artno = g->first; } else { if (debugmode) syslog(LOG_DEBUG, ">411 No such group"); printf("411 No such group\r\n"); } if (fflush(stdout)) return -1; return 0; } static void dohelp(void) { fputs("100 Legal commands on THIS server:\r\n" " ARTICLE [|]\r\n" " BODY [|]\r\n" " DATE\r\n" " GROUP \r\n" " HDR
|\r\n" " HEAD [|]\r\n" " HELP\r\n" " LAST\r\n" " LIST [ACTIVE|NEWSGROUPS] []]\r\n" " LIST [ACTIVE.TIMES|EXTENSIONS|OVERVIEW.FMT]\r\n" " LISTGROUP \r\n" " MODE READER\r\n" " NEWGROUPS [GMT]\r\n" " NEXT\r\n" " POST\r\n" " OVER \r\n" " SLAVE\r\n" " STAT [|]\r\n" " XHDR
|\r\n" " XOVER \r\n" ".\r\n", stdout); } static void domove(int by) { char *msgid; char s[SIZE_s+1]; by = (by < 0) ? -1 : 1; if (group) { if (artno) { artno += by; do { sprintf(s, "%lu", artno); msgid = getheader(s, "Message-ID:"); if (!msgid) artno += by; } while (msgid == NULL && artno >= group->first && artno <= group->last); if (msgid && (artno > group->last || artno < group->first)) { free(msgid); msgid = NULL; } if (msgid == NULL) { if (by > 0) { artno = group->last; printf("421 There is no next article\r\n"); if (debugmode) syslog(LOG_DEBUG, ">421 There is no next article"); } else { artno = group->first; printf("422 There is no previous article\r\n"); if (debugmode) syslog(LOG_DEBUG, ">422 There is no previous article"); } } else { printf("223 %lu %s article retrieved\r\n", artno, msgid); if (debugmode) syslog(LOG_DEBUG, ">223 %lu %s article retrieved", artno, msgid); free(msgid); } } else { printf("420 There is no current article\r\n"); if (debugmode) syslog(LOG_DEBUG, ">420 There is no current article"); } } else { nogroup(); } } static int is_pattern(const char *s) { return s ? strcspn(s, "?*[") < strlen(s) : 1; } /* LIST ACTIVE if what==0, * LIST NEWSGROUPS if what==1 * LIST ACTIVE.TIMES if what==2 */ static void printlist(const struct newsgroup *g, const int what) { switch(what) { case 0: if (is_pseudogroup(g)) printf("%s %lu %lu y\r\n", g->name, g->first, g->first); else printf("%s %lu %lu y\r\n", g->name, g->last, g->first); break; case 1: printf("%s\t%s\r\n", g->name, g->desc ? g->desc : "-x-"); break; case 2: printf("%s %lu %s\r\n", g->name, (unsigned long)g->age, newsadmin); break; default: abort(); break; } } /* LIST ACTIVE if what==0, * LIST NEWSGROUPS if what==1 * LIST ACTIVE.TIMES if what==2 */ static void list(struct newsgroup *g, const int what, const char *pattern) { if (is_pattern(pattern)) { while(g->name) { if (!pattern || !ngmatch(pattern, g->name)) { printlist(g, what); } g++; } } else { /* single group */ g = findgroup(pattern); if (g) { printlist(g, what); if (what == 0 && isinteresting(pattern)) markinterest(pattern); } } } static void dolist(char *arg) { if (!strcasecmp(arg, "extensions")) { printf("202 extensions supported follow\r\n" "HDR\r\n" "LISTGROUP\r\n" "OVER\r\n" "XHDR\r\n" "XOVER\r\n" ".\r\n"); if (debugmode) syslog(LOG_DEBUG, ">202 extensions supported follow"); } else if (!strcasecmp(arg, "overview.fmt")) { printf("215 information follows\r\n" "Subject:\r\n" "From:\r\n" "Date:\r\n" "Message-ID:\r\n" "References:\r\n" "Bytes:\r\n" "Lines:\r\n" "Xref:full\r\n" ".\r\n"); if (debugmode) syslog(LOG_DEBUG, ">215 information follows"); } else if (!strncasecmp(arg, "active.times", 12)) { if (active) { const char m[] = "215 Note that leafnode will fetch groups on demand."; printf("%s\r\n", m); if (debugmode) syslog(LOG_DEBUG, ">%s", m); list(active, 2, NULL); printf(".\r\n"); } else { const char e[] = "503 Active file has not been read."; printf("%s\r\n", e); if (debugmode) syslog(LOG_DEBUG, ">%s", e); } } else { if (!active) { printf("503 Group information file does not exist!\r\n"); syslog(LOG_ERR, ">503 Group information file does not exist!"); } else if (!*arg || !strncasecmp(arg, "active", 6)) { printf("215 Newsgroups in form \"group high low flags\".\r\n"); if (debugmode) syslog(LOG_DEBUG, ">215 Newsgroups in form \"group high low flags\"."); if (active) { if (!*arg || strlen(arg) == 6) list(active, 0, NULL); else { while (*arg && (!isspace((unsigned char)*arg))) arg++; while (*arg && isspace((unsigned char)*arg)) arg++; list(active, 0, arg); } } printf(".\r\n"); } else if (!strncasecmp(arg, "newsgroups", 10)) { printf("215 Descriptions in form \"group description\".\r\n"); if (debugmode) syslog(LOG_DEBUG, ">215 Descriptions in form \"group description\"."); if (active) { if (strlen(arg) == 10) list(active, 1, NULL); else { while (*arg && (!isspace((unsigned char)*arg))) arg++; while (*arg && isspace((unsigned char)*arg)) arg++; list(active, 1, arg); } } printf(".\r\n"); } else { printf("503 Syntax error\r\n"); if (debugmode) syslog(LOG_DEBUG, ">503 Syntax error"); } } } static void donewgroups(const char *arg) { struct tm timearray; struct tm *ltime; time_t age; time_t now; int year, century; char *l; long a; long b; struct newsgroup *ng; now = time(NULL); ltime = localtime(&now); if (ltime == NULL) { syslog(LOG_CRIT, "fatal: localtime returned NULL. abort."); abort(); } year = ltime->tm_year % 100; century = ltime->tm_year / 100; /* 0 for 1900-1999, 1 for 2000-2099 etc */ memset(&timearray, 0, sizeof(timearray)); l = NULL; a = (int)strtol(arg, &l, 10); /* NEWGROUPS may have the form YYMMDD or YYYYMMDD. Distinguish between the two */ b = a / 10000; if (b < 100) { /* YYMMDD */ if (b <= year) timearray.tm_year = (int)(b + (century * 100)); else timearray.tm_year = (int)(b + (century - 1) * 100); } else if (b < 1000) { /* YYYMMDD - happens with buggy newsreaders */ /* In these readers, YYY=100 is equivalent to YY=00 or YYYY=2000 */ syslog(LOG_NOTICE, "NEWGROUPS year is %ld: please update your newsreader", b); timearray.tm_year = (int)b; } else { /* YYYYMMDD */ timearray.tm_year = (int)b - 1900; } timearray.tm_mon = (int)(a % 10000 / 100) - 1; timearray.tm_mday = (int)(a % 100); while (*l && isspace((unsigned char)*l)) l++; a = strtol(l, &l, 10); /* we don't care about the rest of the line */ while (*l && isspace((unsigned char)*l)) l++; timearray.tm_hour = (int)(a / 10000); timearray.tm_min = (int)(a % 10000 / 100); timearray.tm_sec = (int)(a % 100); /* mktime() shall guess correct value of tm_isdst (0 or 1) */ timearray.tm_isdst = -1; if (0 == strncasecmp(l, "gmt", 3)) age = timegm(&timearray); else age = mktime(&timearray); printf("231 List of new newsgroups since %ld follows\r\n", (long)age); if (debugmode) syslog(LOG_DEBUG, "231 List of new newsgroups since %ld follows", (long)age); ng = active; if (ng != NULL) while (ng->name) { if (ng->age >= age) printf("%s %lu %lu y\r\n", ng->name, ng->last, ng->first); ng++; } printf(".\r\n"); } /* next bit is copied from INN 1.4 and modified ("broken") by agulbra mail to Rich $alz bounced */ /* Scale time back a bit, for shorter Message-ID's. */ #define OFFSET (time_t)1026380000L /*@observer@*/ static char * generateMessageID(void) { static char ALPHABET[] = "0123456789abcdefghijklmnopqrstuv"; static char buff[1000]; static time_t then; static unsigned int fudge; time_t now; char *p; unsigned long n; now = time(NULL); /* might be 0, in which case fudge will almost fix it */ if (now < OFFSET) { ln_log(LNLOG_SCRIT, LNLOG_CTOP, "your system clock cannot be right. abort."); abort(); } if (now != then) fudge = 0; else fudge++; then = now; p = buff; *p++ = '<'; n = (unsigned long)now - OFFSET; while (n) { *p++ = ALPHABET[(int)(n & 31)]; n >>= 5; } *p++ = '-'; n = fudge * 32768 + (int)getpid(); while (n) { *p++ = ALPHABET[(int)(n & 31)]; n >>= 5; } sprintf(p, ".ln1@%-.256s>", fqdn); return buff; } /* the end of what I stole from rsalz and then mangled */ static int dopost(void) { char *line; int havefrom = 0; int havepath = 0; int havedate = 0; int havenewsgroups = 0; int havemessageid = 0; int havesubject = 0; int err = 0, ferr = 0; /*@observer@*/ const char *ferrstr = NULL; int o; size_t len; FILE *out; char outname[1000]; static int postingno; /* starts as 0 */ char *suggmid; /*@observer@*/ const char *appendheader = NULL; if (getenv("LN_REJECT_POST_PRE")) { printf("400 Posting rejected - debug variable LN_REJECT_POST_PRE exists\r\n"); return 0; } do { (void)xsnprintf(outname, sizeof(outname), "%s/out.going/%d-%d-%d", spooldir, (int)getpid(), (int)time(NULL), ++postingno); o = open(outname, O_WRONLY | O_EXCL | O_CREAT, 0244); if (o < 0 && errno != EEXIST) { char *errmsg = strerror(errno); printf("441 Unable to open spool file %s: %s\r\n", outname, errmsg); syslog(LOG_ERR, ">441 Unable to open spool file %s: %s", outname, errmsg); return 0; } } while (o < 0); out = fdopen(o, "w"); if (out == NULL) { char *errmsg = strerror(errno); printf("441 Unable to fdopen(%d): %s\r\n", o, errmsg); syslog(LOG_ERR, ">441 Unable to fdopen(%d): %s", o, errmsg); return 0; } suggmid = generateMessageID(); printf("340 Ok, recommended ID %s\r\n", suggmid); if (debugmode) syslog(LOG_DEBUG, ">340 Go ahead."); if (fflush(stdout)) return -1; /* get headers */ do { debug = 0; line = getaline(stdin); if (!line) { /* timeout */ unlink(outname); exit(0); } if (0 == strcmp(line, ".")) { ferr = TRUE; ferrstr = "No body found."; break; } debug = debugmode; if (!strncasecmp(line, "From: ", 6)) { if (havefrom) ferr = TRUE, ferrstr = "Duplicate From: header"; else havefrom = TRUE; } if (!strncasecmp(line, "Path: ", 6)) { if (havepath) ferr = TRUE, ferrstr = "Duplicate Path: header"; else havepath = TRUE; } if (!strncasecmp(line, "Message-ID: ", 12)) { if (havemessageid) ferr = TRUE, ferrstr = "Duplicate Message-ID: header"; else { char *vec[2]; /* RATS: ignore */ int rc; havemessageid = TRUE; if (debugmode) syslog(LOG_DEBUG, "debug header: %s", line); if (2 != (rc = pcre_extract(line, "Message-ID:\\s+<(?:[^>]+)@([^@>]+)>\\s*$", vec, 2)) || vec[1] == NULL) { ferr = TRUE, ferrstr = "Malformatted Message-ID: header."; } else if (!strchr(vec[1], '.')) { ferr = TRUE, ferrstr = "Message-ID: header does not have domain name part."; } else if (!is_validfqdn(vec[1])) { ferr = TRUE, ferrstr = "Message-ID: header contains invalid domain name part."; } pcre_extract_free(vec, rc); } } if (!strncasecmp(line, "Subject: ", 9)) { if (havesubject) ferr = TRUE, ferrstr = "Duplicate Subject: header"; else havesubject = TRUE; } if (!strncasecmp(line, "Newsgroups: ", 12)) { if (havenewsgroups) ferr = TRUE, ferrstr = "Duplicate Newsgroups: header"; else havenewsgroups = TRUE; } if (!strncasecmp(line, "Date: ", 6)) { if (havedate) ferr = TRUE, ferrstr = "Duplicate Date: header"; else havedate = TRUE; } len = strlen(line); /* check for illegal 8bit/control stuff in header */ { char *t; for (t = line; *t; t++) { if (*t & 0x80) { if (allow_8bit_headers) { appendheader = "X-Leafnode-Warning: administrator " "allowed illegal use of 8-bit data in header.\r\n"; } else { ferr = TRUE; ferrstr = "Illegal use of 8-bit data in header."; break; } } if ((unsigned char)*t < (unsigned char)0x20u && *t != '\t') { ferr = TRUE; ferrstr = "Illegal use of control data in header."; break; } } } /* checks for non-folded lines */ if (*line && *line != ' ' && *line != '\t') { if (strchr(line, ':') == NULL) { /* must have a colon */ ferr = TRUE; ferrstr = "Header tag not found."; } else if (strcspn(line, " \t") < strcspn(line, ":")) { /* must not have space before colon */ ferr = TRUE; ferrstr = "Whitespace in header tag is not allowed."; } } if (len) { if (fwrite(line, 1, len, out) != (size_t) len) err = 1; } else { if (!havepath) { if (fputs("Path: ", out) == EOF) err = 1; if (fputs(fqdn, out) == EOF) err = 1; if (fprintf(out, "!%s\r\n", NEWS_USER) < 0) err = 1; } if (!havedate) { const char *l = rfctime(); if (fputs("Date: ", out) == EOF) err = 1; if (fputs(l, out) == EOF) err = 1; if (fputs("\r\n", out) == EOF) err = 1; } if (!havemessageid) { if (fputs("Message-ID: ", out) == EOF) err = 1; if (fputs(suggmid, out) == EOF) err = 1; if (fputs("\r\n", out) == EOF) err = 1; } if (appendheader) { if (fputs(appendheader, out) == EOF) err = 1; } } if (fputs("\r\n", out) == EOF) err = 1; } while (*line); /* get bodies */ if (strcmp(line, ".")) do { debug = 0; line = getaline(stdin); debug = debugmode; if (!line) { (void)unlink(outname); exit(1); } len = strlen(line); if (line[0] == '.') { if (len > 1) { if (fputs(line + 1, out) == EOF) err = 1; if (fputs("\r\n", out) == EOF) err = 1; } } else { if (fputs(line, out) == EOF) err = 1; if (fputs("\r\n", out) == EOF) err = 1; } } while (line[0] != '.' || line[1] != '\0'); if (fflush(out)) err = 1; if (fsync(fileno(out))) err = 1; if (fclose(out)) err = 1; if (!havenewsgroups) ferrstr = "Missing Newsgroups: header"; if (!havesubject) ferrstr = "Missing Subject: header"; if (!havefrom) ferrstr = "Missing From: header"; if (getenv("LN_REJECT_POST_POST")) ferr = 1; if (havefrom && havesubject && havenewsgroups && !ferr) { if (!err && 0 == chmod(outname, 0644)) { printf("240 Article posted, now be patient\r\n"); if (debugmode) syslog(LOG_DEBUG, ">240 Article posted, now be patient"); return 0; } else { (void)unlink(outname); printf("441 I/O error, article not posted\r\n"); syslog(LOG_INFO, ">441 I/O error, article not posted"); return 0; } } (void)unlink(outname); if (getenv("LN_REJECT_POST_POST")) { printf("400 Posting rejected - debug variable LN_REJECT_POST_POST exists\r\n"); syslog(LOG_INFO, ">400 Posting rejected - debug variable LN_REJECT_POST_POST exists\r\n"); return 0; } if (ferrstr) { printf("441 Post rejected, formatting error: %s\r\n", ferrstr); syslog(LOG_INFO, ">441 Post rejected, formatting error: %s", ferrstr); } else { printf("441 Post rejected, formatting error\r\n"); syslog(LOG_INFO, ">441 Post rejected, formatting error"); } return 0; } static void invalidrange(void) { printf("420 No articles in specified range.\r\n"); if (debugmode) syslog(LOG_DEBUG, ">420 No articles in specified range."); } /* check if a - b is a valid range for the current group. * If it's not, print a 420 error and return 0. * If it is, do not print anything and return 1. * group must not be NULL! */ static int checkrange(const struct newsgroup *g, unsigned long a, unsigned long b) { if ((a > b) || (g->first <= g->last ? (a > g->last) || (b < g->first) : (a > g->first) || (b < g->first))) { invalidrange(); return 0; } return 1; } static void doxhdr(char *arg) { static const char *h[] = { "Subject", "From", "Date", "Message-ID", "References", "Bytes", "Lines" }; int n = 7; size_t i; char *l; char *buf; unsigned long a, b = 0, c; char s[SIZE_s+1]; if (!arg || !*arg) { if (debugmode) syslog(LOG_DEBUG, ">502 Usage: HDR header first[-last] or " "HDR header message-id"); printf("502 Usage: HDR header first[-last] or " "HDR header message-id\r\n"); return; } /* go figure header */ l = arg; while (l && *l && !isspace((unsigned char)*l)) l++; if (l && *l) *l++ = '\0'; SKIPLWS(l); buf = critmalloc((i = strlen(arg)) + 2, "doxhdr"); strcpy(buf, arg); /* RATS: ignore */ if (buf[i - 1] != ':') strcpy(buf + i, ":"); if (l && *l == '<') { /* handle message-id form (well) */ FILE *f; char *m = critstrdup(l, "doxhdr"); f = fopenart(l); if (!f) { printf("430 No such article\r\n"); if (debugmode) syslog(LOG_DEBUG, ">430 No such article"); free(buf); free(m); return; } l = fgetheader(f, buf); if (debugmode) { syslog(LOG_DEBUG, ">221 %s header of %s follows:", buf, m); if (l) syslog(LOG_DEBUG, ">%s %s", m, l); syslog(LOG_DEBUG, ">."); } printf("221 %s header of %s follows:\r\n", buf, m); if (l) printf("%s %s\r\n", m, l); printf(".\r\n"); free(m); (void)fclose(f); free(buf); if (l) free(l); return; } if (!group) { nogroup(); free(buf); return; } markinterest(group->name); a = group->first; b = group->last; if (b < a) b = a; if (!parserange(l, &a, &b)) { if (debugmode) syslog(LOG_DEBUG, ">502 Usage: XHDR header first[-last] " "or XHDR header message-id"); printf("502 Usage: XHDR header first[-last] " "or XHDR header message-id\r\n"); free(buf); return; } if (!checkrange(group, a, b)) { free(buf); return; } if (!is_pseudogroup(group)) { if (xovergroup != group && chdirgroup(group->name, FALSE)) if (getxover()) xovergroup = group; } if (is_pseudogroup(group)) { do { n--; } while (n >= 0 && strncasecmp(h[n], buf, strlen(h[n])) != 0); if ((n < 0) && strncasecmp("Newsgroups", buf, 10)) { printf("430 No such header: %s\r\n", buf); if (debugmode) syslog(LOG_DEBUG, ">430 No such header: %s", buf); free(buf); return; } if (debugmode) syslog(LOG_DEBUG, ">221 First line of %s pseudo-header follows:", buf); printf("221 First line of %s pseudo-header follows:\r\n", buf); if (a <= b && a <= group->first && b >= group->last) { printf("%lu ", group->first); if (n == 0) /* Subject: */ printf("Leafnode placeholder for group %s\r\n", group->name); else if (n == 1) /* From: */ printf("Leafnode <%s>\r\n", newsadmin); else if (n == 2) /* Date: */ printf("%s\r\n", rfctime()); else if (n == 3) /* Message-ID: */ printf("\r\n", group->name, fqdn); else if (n == 4) /* References */ printf("(none)\r\n"); else if (n == 5) /* Bytes */ printf("%d\r\n", 1024); /* FIXME: just a guess */ else if (n == 6) /* Lines */ printf("%d\r\n", 22); /* FIXME: from buildpseudoart() */ else /* Newsgroups */ printf("%s\r\n", group->name); } printf(".\r\n"); free(buf); return; } do { n--; } while (n > -1 && strncasecmp(buf, h[n], strlen(h[n]))); if (a < group->first) a = group->first; if (b > group->last) b = group->last; if (n >= 0) { if (debugmode) syslog(LOG_DEBUG, "221 %s header (from overview) " "for postings %lu-%lu:", h[n], a, b); printf("221 %s header (from overview) for postings %lu-%lu:\r\n", h[n], a, b); s[sizeof(s)-1] = '\0'; for (c = a; c <= b; c++) { if (xoverinfo && c >= xfirst && c <= xlast && xoverinfo[c - xfirst].text) { char *l2 = xoverinfo[c - xfirst].text; int d; for (d = 0; l2 && d <= n; d++) l2 = strchr(l2 + 1, '\t'); if (l2) { char *p; (void)strlcpy(s, ++l2, sizeof(s)); p = strchr(s, '\t'); if (p) *p = '\0'; } if (l2 && *l2) printf("%lu %s\r\n", c, s); } } } else { if (debugmode) syslog(LOG_DEBUG, ">221 %s header (from article files) " "for postings %lu-%lu:", buf, a, b); printf("221 %s header (from article files) for postings %lu-%lu:\r\n", buf, a, b); for (c = a; c <= b; c++) { sprintf(s, "%lu", c); l = getheader(s, buf); if (l) { printf("%lu %s\r\n", c, l); /* (l && *l) ? l : "(none)" ); */ free(l); } } } free(buf); printf(".\r\n"); return; } static void doxover(char *arg) { unsigned long a, b, art; if (!group) { nogroup(); return; } markinterest(group->name); a = group->first; b = group->last; if (b < a) b = a; if (!arg || !*arg) a = b = artno; else if (!parserange(arg, &a, &b)) { printf("502 Usage: OVER first[-[last]]\r\n"); if (debugmode) syslog(LOG_DEBUG, ">502 Usage: OVER first[-[last]]"); return; } if (!checkrange(group, a, b)) return; if (!is_pseudogroup(group)) { if (xovergroup != group && chdirgroup(group->name, FALSE)) if (getxover()) xovergroup = group; if (NULL == xoverinfo) { invalidrange(); return; } if (b > xlast) b = xlast; if (a < xfirst) a = xfirst; printf("224 Overview information for postings %lu-%lu:\r\n", a, b); if (debugmode) syslog(LOG_DEBUG, ">224 Overview information for postings %lu-%lu:", a, b); for (art = a; art <= b; art++) { if (xoverinfo[art - xfirst].text) printf("%s\r\n", xoverinfo[art - xfirst].text); } printf(".\r\n"); } else { if ((a > b) || (group->first <= group->last ? (a > group->last) || (b < group->first) : (a > group->first) || (b < group->first))) { printf("420 No articles in specified range.\r\n"); if (debugmode) syslog(LOG_DEBUG, ">420 No articles in specified range."); return; } printf("224 Overview information (pseudo) for postings %lu-%lu:\r\n", group->first, group->first); if (debugmode) syslog(LOG_DEBUG, ">224 Overview information (pseudo) for " "postings %lu-%lu:", group->first, group->first); printf("%lu\t" "Leafnode placeholder for group %s\t" "%s (Leafnode)\t%s\t" "\t\t1000\t40\r\n", group->first, group->name, newsadmin, rfctime(), group->name, fqdn); printf(".\r\n"); if (debugmode) syslog(LOG_DEBUG, ">%lu\tLeafnode placeholder for group %s\t" "%s (Leafnode)\t%s\t\t\t1000\t40", group->first, group->name, newsadmin, rfctime(), group->name, fqdn); } } static void dolistgroup(const char *arg) { unsigned long art; if (arg && *(arg)) { struct newsgroup *g; g = findgroup(arg); if (g) { group = g; artno = g->first; } else { printf("411 No such group: %s\r\n", arg); if (debugmode) syslog(LOG_DEBUG, ">411 No such group: %s", arg); return; } } if (!group) { nogroup(); return; } /* group = g; */ markinterest(group->name); if ((NULL == xovergroup || xovergroup != group) && chdirgroup(group->name, FALSE)) if (getxover()) xovergroup = group; if (is_pseudogroup(group)) { printf("211 Article list for %s follows (pseudo)\r\n", group->name); if (debugmode) syslog(LOG_DEBUG, ">211 Article list for %s follows (pseudo)", group->name); printf("%lu\r\n", group->first ? group->first : 1); } else { printf("211 Article list for %s follows\r\n", group->name); if (debugmode) syslog(LOG_DEBUG, ">211 Article list for %s follows", group->name); if (xoverinfo) for (art = xfirst; art <= xlast; art++) { if (xoverinfo[art - xfirst].text) printf("%lu\r\n", art); } } printf(".\r\n"); } static void parser(void) { char *arg; int n; size_t size; mgetaline_settimeout(timeout_client); while ((cmd = mgetaline(stdin))) { if (debug == 1) syslog(LOG_DEBUG, "<%s", cmd); size = strlen(cmd); if (size == 0) continue; /* ignore */ if (size > MAXLINELENGTH || (long)size > (long)INT_MAX) { /* ignore attempts at buffer overflow */ if (debugmode) syslog(LOG_DEBUG, ">500 Dazed and confused"); printf("500 Dazed and confused\r\n"); continue; } /* parse command line */ n = 0; while (isalpha((unsigned char)cmd[n])) n++; while (isspace((unsigned char)cmd[n])) cmd[n++] = '\0'; arg = cmd + n; while (cmd[n]) n++; n--; while (n >= 0 && isspace((unsigned char)cmd[n])) cmd[n--] = '\0'; if (!strcasecmp(cmd, "quit")) { if (debugmode) syslog(LOG_DEBUG, ">205 Always happy to serve!"); printf("205 Always happy to serve!\r\n"); return; } rereadactive(); if (!strcasecmp(cmd, "article")) { doarticle(arg, 3); } else if (!strcasecmp(cmd, "head")) { doarticle(arg, 2); } else if (!strcasecmp(cmd, "body")) { doarticle(arg, 1); } else if (!strcasecmp(cmd, "stat")) { doarticle(arg, 0); } else if (!strcasecmp(cmd, "help")) { dohelp(); } else if (!strcasecmp(cmd, "last")) { domove(-1); } else if (!strcasecmp(cmd, "next")) { domove(1); } else if (!strcasecmp(cmd, "list")) { dolist(arg); } else if (!strcasecmp(cmd, "date")) { dodate(); } else if (!strcasecmp(cmd, "mode")) { if (debugmode) syslog(LOG_DEBUG, ">200 Leafnode %s, pleased to meet you!", version); printf("200 Leafnode %s, pleased to meet you!\r\n", version); } else if (!strcasecmp(cmd, "newgroups")) { donewgroups(arg); } else if (!strcasecmp(cmd, "newnews")) { if (debugmode) syslog(LOG_DEBUG, ">500 NEWNEWS is meaningless for this server"); printf("500 NEWNEWS is meaningless for this server\r\n"); } else if (!strcasecmp(cmd, "post")) { if (dopost()) break; } else if (!strcasecmp(cmd, "slave")) { if (debugmode) syslog(LOG_DEBUG, ">202 Cool - I always wanted a slave"); printf("202 Cool - I always wanted a slave\r\n"); } else if (!strcasecmp(cmd, "xhdr")) { doxhdr(arg); } else if (!strcasecmp(cmd, "hdr")) { doxhdr(arg); } else if (!strcasecmp(cmd, "xover")) { doxover(arg); } else if (!strcasecmp(cmd, "over")) { doxover(arg); } else if (!strcasecmp(cmd, "listgroup")) { dolistgroup(arg); } else if (!strcasecmp(cmd, "group")) { if (dogroup(arg)) break; } else { if (debugmode) syslog(LOG_DEBUG, ">500 Unknown command"); printf("500 Unknown command\r\n"); } if (ferror(stdout) || fflush(stdout)) { syslog(LOG_ERR, "Cannot write to client."); break; } } if (debugmode) syslog(LOG_DEBUG, "Client timeout, disconnecting."); /* There was once a 400 error message here. It confused broken * clients, most notably, tin. * Future NNTP drafts command that we don't send stuff back on * timeout, so we anticipate these. */ } int main(int argc, char **argv) { socklen_t fodder; char peername[256]; /* RATS: ignore */ #ifdef HAVE_IPV6 char *st; int h_err; #define ADDRLEN INET6_ADDRSTRLEN union sockaddr_union su; #else struct hostent *he; #ifdef INET_ADDRSTRLEN #define ADDRLEN INET_ADDRSTRLEN #else #define ADDRLEN 16 #endif struct sockaddr_in sa; #endif char peerip[ADDRLEN]; /* RATS: ignore */ char ownip[ADDRLEN]; /* RATS: ignore */ char origfqdn[FQDNLEN + 1]; /* RATS: ignore */ ln_log_use_console(0); /* disable console logging */ (void)argc; /* quiet compiler warning */ myopenlog("leafnode"); /* this gets the actual hostname */ if (!initvars(argv[0])) exit(1); artno = 0; verbose = 0; (void)umask(2); /* this reads the host name from the config file */ if (!readconfig(1)) { const char *m = "503 Unable to read configuration file, exiting; the server's syslog should have more information."; printf("%s\r\n", m); syslog(LOG_ERR, "%s", m); exit(1); } freeservers(); strcpy(origfqdn, fqdn); /* same size buffer */ /* RATS: ignore */ /* get own name */ #ifdef HAVE_IPV6 fodder = sizeof(union sockaddr_union); if (0 == getsockname(0, (struct sockaddr *)&su, &fodder)) { if (su.sin.sin_family == AF_INET6) inet_ntop(AF_INET6, &su.sin6.sin6_addr, ownip, sizeof(ownip)); else inet_ntop(AF_INET, &su.sin.sin_addr, ownip, sizeof(ownip)); if ((st = masock_sa2name((struct sockaddr *)&su, &h_err))) { xstrlcpy(fqdn, st, sizeof(fqdn)); free(st); } } #else fodder = sizeof(struct sockaddr_in); if (0 == getsockname(0, (struct sockaddr *)&sa, &fodder)) { he = gethostbyaddr((char *)&sa.sin_addr.s_addr, sizeof(sa.sin_addr.s_addr), AF_INET); *fqdn = '\0'; (void)xstrlcpy(fqdn, he && he->h_name ? he->h_name : inet_ntoa(sa.sin_addr), sizeof(fqdn)); strcpy(ownip, inet_ntoa(sa.sin_addr)); } #endif else { strcpy(ownip, "no IP"); } /* get remote name */ #ifdef HAVE_IPV6 fodder = sizeof(union sockaddr_union); if (0 == getpeername(0, (struct sockaddr *)&su, &fodder)) { if (su.sa.sa_family == AF_INET6) inet_ntop(AF_INET6, &su.sin6.sin6_addr, peername, sizeof(peername)); else inet_ntop(AF_INET, &su.sin.sin_addr, peername, sizeof(peername)); strcpy(peerip, peername); if ((st = masock_sa2name((struct sockaddr *)&su, &h_err))) { xstrlcpy(peername, st, sizeof(peername)); free(st); } } #else fodder = sizeof(struct sockaddr_in); if (0 == getpeername(0, (struct sockaddr *)&sa, &fodder)) { he = gethostbyaddr((char *)&sa.sin_addr.s_addr, sizeof(sa.sin_addr.s_addr), AF_INET); (void)xstrlcpy(peername, he && he->h_name ? he->h_name : inet_ntoa(sa.sin_addr), sizeof(peername)); strcpy(peerip, inet_ntoa(sa.sin_addr)); } #endif else { if (errno == ENOTSOCK) { strcpy(peername, "(local file)"); strcpy(peerip, "no IP"); } else { strcpy(peerip, "unknown"); strcpy(peername, "(unknown)"); } } syslog(LOG_INFO, "connect from %s (%s) to %s (%s) (my fqdn: %s)", peername, peerip, fqdn, ownip, origfqdn); if (allowstrangers == 0 && checkpeerlocal(0) != 1) { unsigned int i = 5; syslog(LOG_NOTICE, "Denying access from address outside the local networks. (Check config.example.)"); while (i) i = sleep(i); printf("502 Remote access denied.\n"); exit(0); } printf("200 Leafnode NNTP Daemon, version %s " "running at %s (my fqdn: %s)\r\n", version, fqdn, origfqdn); if (fflush(stdout)) exit(0); strcpy(fqdn, origfqdn); rereadactive(); parser(); (void)fflush(stdout); freeactive(active); freexover(); freeconfig(); sleep(1); /* protect against process ID induced file name collisions */ exit(0); }