/*
nntpd -- the NNTP server

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>
and Kazushi (Jam) Marukawa <jam@pobox.com>.
Copyright of the modifications 1998, 1999.
Modified by Matthias Andree <matthias.andree@gmx.de>
Copyright of the modifications 2000 - 2002.
Modified by Ralf Wildenhues <ralf.wildenhues@gmx.de>
Copyright of the modifications 2002.
Modified by Jonathan Larmour <jifl@jifvik.org>
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 <socks.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#ifndef __LCLINT__
#include <arpa/inet.h>
#endif
#include <ctype.h>
#include "system.h"
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <syslog.h>
#include <unistd.h>
#include <utime.h>

#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: <leafnode:placeholder:%s@%s>\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, "<leafnode:placeholder:", 22)) {
	    mastr *msgidbuf = mastr_new(1024);
	    (void)mastr_cpy(msgidbuf, arg + 22);
	    if ((c = strchr(mastr_modifyable_str(msgidbuf), '@')) != NULL) {
		*c++ = '\0';
		(void)strtok(c, ">");
		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 [<Message-ID>|<Number>]\r\n"
	    " BODY [<Message-ID>|<Number>]\r\n"
	    " DATE\r\n"
	    " GROUP <Newsgroup>\r\n"
	    " HDR <Header> <Message-ID>|<Range>\r\n"
	    " HEAD [<Message-ID>|<Number>]\r\n"
	    " HELP\r\n"
	    " LAST\r\n"
	    " LIST [ACTIVE|NEWSGROUPS] [<Wildmat>]]\r\n"
	    " LIST [ACTIVE.TIMES|EXTENSIONS|OVERVIEW.FMT]\r\n"
	    " LISTGROUP <Newsgroup>\r\n"
	    " MODE READER\r\n"
	    " NEWGROUPS <yymmdd> <hhmmss> [GMT]\r\n"
	    " NEXT\r\n"
	    " POST\r\n"
	    " OVER <Range>\r\n"
	    " SLAVE\r\n"
	    " STAT [<Message-ID>|<Number>]\r\n"
	    " XHDR <Header> <Message-ID>|<Range>\r\n"
	    " XOVER <Range>\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 <rsalz@uunet.uu.net> 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("<leafnode:placeholder:%s@%s>\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"
	       "<leafnode:placeholder:%s@%s>\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<leafnode:placeholder:%s@%s>\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);
}


syntax highlighted by Code2HTML, v. 0.9.1