/*
 * Copyright (c) 1998,1999,2000  Kazushi (Jam) Marukawa
 * All rights of my changes are reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice in the documentation and/or other materials provided with
 *    the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* $Orig-Id: nntpd.c,v 1.27 1997/07/21 14:08:40 agulbra Exp $ */

/* Written by Arnt Gulbrandsen <agulbra@troll.no> and copyright 1995
Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47
22646949.

Use, modification and distribution is allowed without limitation,
warranty, or liability of any kind.  */

#ifdef SOCKS
#include <socks.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <dirent.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 <time.h>
#include <sys/time.h>
#include <unistd.h>

#include "leafnode.h"

int write2(int fd, const char* msg);
int hash(const char*);
FILE* fopenart(const char*);
FILE* fopenpseudoart(const char* arg, const int article_num);
void list(struct newsgroup*, int);
void rereadactive(void);

void parser(void);
void error(const char*msg);
void doarticle(const char*, int);
void dostat(const char*);
void dogroup(const char*);
void dohelp(void);
void domove(int);
void domove(int);
void dolist(const char*);
void donewgroups(const char*);
void donewnews(const char*);
void dopost(void);
void doxhdr(char*);
void doxover(const char*);
void dolistgroup(const char*);
void markinterest(void);


struct newsgroup* group; /* current group, initially none */
struct newsgroup* xovergroup;
unsigned long artno = 0; /* current article number, initially 0 and almost unused */
char* cmd; /* current command line */
struct newsgroup* active;
time_t activetime;
static int justaftergroup;


void rereadactive(void)
{
    const char* s;
    struct stat st;

    s = getinfofname();
    if ((stat(s, &st) == 0 && st.st_mtime > activetime) ||
	active == NULL) {
	mysyslog(LOG_DEBUG, "rereading %s", s);
	readactive();
	activetime = st.st_mtime;
    }
}



void error(const char* msg)
{
    printf("%s: %s\r\n", msg, strerror(errno));
}


void parser(void)
{
    char* arg;
    int n;

    while(1) {
	if (!(cmd = getaline(stdin))) {
	    error("421 Network error");
	    return;
	}

	n = 0;
	while (isalpha(cmd[n]) && n < 1000) {
	    cmd[n] = tolower(cmd[n]);
	    n++;
	}

	if (n == 0)
	    continue; /* outside the spec, but let's do it anyway */

	if (n > 999) {
	    printf("400 Dazed and confused\r\n");
	    return;
	}

	while (isspace(cmd[n]) && n < 1000)
	    cmd[n++] = '\0';

	arg = cmd + n;

	mysyslog(LOG_DEBUG, "%s %s", cmd, arg);

	while (cmd[n] && n < 1000)
	    n++;
	n--;

	while (isspace(cmd[n]))
	    cmd[n--] = '\0';

	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, "ihave")) {
	    printf("500 IHAVE is for big news servers\r\n");
	} 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, "mode")) {
	    printf("200 Modal interfaces are for losers\r\n");
	} else if (!strcasecmp(cmd, "newgroups")) {
	    donewgroups(arg);
	} else if (!strcasecmp(cmd, "newnews")) {
	    donewnews(arg);
	} else if (!strcasecmp(cmd, "post")) {
	    dopost();
	} else if (!strcasecmp(cmd, "quit")) {
	    printf("205 Always happy to serve!\r\n");
	    return;
	} else if (!strcasecmp(cmd, "slave")) {
	    printf("202 Cool - I always wanted a slave\r\n");
	} else if (!strcasecmp(cmd, "xhdr")) {
	    doxhdr(arg);
	} else if (!strcasecmp(cmd, "xover")) {
	    doxover(arg);
	} else if (!strcasecmp(cmd, "listgroup")) {
	    dolistgroup(arg);
	} else if (!strcasecmp(cmd, "group")) {
	    dogroup(arg);
	} else
	    printf("500 Unknown command\r\n");
	fflush(stdout);
    }
}


/* open a pseudo art */
FILE* fopenpseudoart(const char* arg, const int article_num)
{
    FILE* f = NULL;
    char msgidbuf[128];
    char* c;
    struct newsgroup* g;

    if (article_num > 0 && article_num < group->first) {
	f = buildpseudoart(group->name);
    } else if (!article_num) {
	if (!strncmp(arg, "<leafnode:placeholder:", 22)) {
	    strncpy(msgidbuf, arg + 22, 128);
	    msgidbuf[127] = '\0';
	    if ((c = strchr(msgidbuf, '@')) != NULL) {
		*c = '\0';
		g = findgroup(msgidbuf);
		if (g) {
		    f = buildpseudoart(g->name);
		}
	    }
	}
    }
    return f;
}


/* open an article by number or message-id */
FILE* fopenart(const char* arg)
{
    unsigned long int a;
    FILE* f;
    char* t;

    t = NULL;
    a = strtoul(arg, &t, 10);
    if (a && t && !*t && group) {
	f = fopen(arg, "r");
	if (f)
	    artno = a;
	else
	    f = fopenpseudoart(arg, a);
	markinterest();
	/* else try message-id lookup */
    } else if (arg && *arg=='<') {
	f = fopen(getmsgidfname(arg), "r");
	if (!f)
	    f = fopenpseudoart(arg, a);
    } else if (group && artno) {
	char s[256];

	sprintf(s, "%lu", artno);
	f = fopen(s, "r");
	if (!f)
	    f = fopenpseudoart(s, a);
	markinterest();
    } else {
	f = NULL;
    }
    return f;
}



/* display an article or somesuch */
void doarticle(const char* arg, int what)
{
    FILE* f;
    char* p;
    unsigned long yuck;
    char s[1024];

    if (!group) {
	printf("412 No newsgroup selected\r\n");
	return;
    }

    f = fopenart(arg);
    if (!f) {
	if (strlen(arg))
	    printf("430 No such article: %s\r\n", arg);
	else
	    printf("430 No such article: %lu\r\n", artno);
	return;
    }

    yuck = strtoul(arg, &p, 10);
    if (!p || *p) {
	/* parse an article to get artno. */
	char* xref = getaheader(f, "Xref");
	if (xref && (xref = strstr(xref, group->name)) != NULL) {
	    xref += strlen(group->name) + 1;
	    yuck = strtoul(xref, &p, 10);
	    if (!p || *p)
		yuck = 0;
	} else {
	    yuck = 0;
	}
    }

    printf("%3d %lu %s Text follows, then a line consisting of a single '.'\r\n",
	   223 - what, yuck, getaheader(f, "Message-ID"));

    while (fgets(s, 1024, f) && *s && *s != '\n') {
	if (what & 2) {
	    p = s;
	    if ((p = strchr(p, '\n')))
		*p = '\0';

	    printf("%s%s\r\n", *s=='.' ? "." : "", s); /* . on headers :( */
	}
    }

    if (what==3)
	printf("\r\n"); /* empty separator line */

    while ((what & 1) && fgets(s, 1024, f) && *s) {
	p = s;
	if ((p = strchr(p, '\n')))
	    *p = '\0';

	printf("%s%s\r\n", *s=='.' ? "." : "", s);
    }
    if (what)
	printf(".\r\n");
    fclose(f);

    return; /* OF COURSE there were no errors */
}


/* note bug.. need not be _immediately after_ GROUP */
void markinterest(void)
{
    int f;
    const char* s = getinterestingngfname(group);

    if (!justaftergroup)
	return;

    if ((f = open(s, O_RDONLY, 0)) >= 0) {
	/* if there is file, changed access time by reading it */
	char a;
	(void)read(f, &a, 1);
	close(f);
    } else {
	/* Otherwise, create it */
	if ((f = open(s, O_WRONLY|O_CREAT|O_TRUNC, 0644)) >= 0) {
	    (void)write(f, "*", 1);
	    close(f);
	}
    }
    justaftergroup = FALSE;
}



/* change to group - note no checks on group name */
void dogroup(const char* arg)
{
    struct newsgroup* g;

    rereadactive();
    g = findgroup(arg);
    if (g) {
	group = g;
	chdirgroup(g, 1);
	if (g->last < g->first)
	    printf("211 %lu %lu %lu %s group selected (pseudo article)\r\n",
		   1LU, g->last, g->last, g->name);
	else
	    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);
	artno = g->first;
	fflush(stdout);
	justaftergroup = TRUE;
#if DOTNGFILE
	{
	int f;
	const char* s = getinterestingngdotfname(group);

	if ((f = open(s, O_WRONLY|O_CREAT|O_TRUNC, 0644)) >= 0)
	    close(f);
	}
#endif
    } else {
	printf("411 No such group\r\n");
    }
}


void dohelp(void)
{
    printf("500 Read RFC 977 and 1036 for elucidation\r\n");
}


void domove(int by)
{
    if (group) {
	if (artno) {
	    unsigned long art = artno;
	    FILE* f = NULL;
	    char buffer[256];

	    while (f == NULL) {
		art += by;
		sprintf(buffer, "%lu", art);
		f = fopenart(buffer);
		/* hit boundaries */
		if (art > group->last) {
		    printf("421 There is no next article\r\n");
		    return;
		}
		if (art < group->first) {
		    printf("422 There is no previous article\r\n");
		    return;
		}
	    }
	    printf("223 %lu %s Article number is now %lu\r\n",
		   artno, getaheader(f, "Message-ID"), artno);
	    fclose(f);
	} else {
	    printf("420 There is no current article\r\n");
	}
    } else {
	printf("412 No newsgroup selected\r\n");
    }
}



/* LIST ACTIVE if what==0, else LIST NEWSGROUPS */
void list(struct newsgroup* g, int what)
{
    if (g) {
	list(g->right, what);
	if (what) {
	    printf("%s\t%s\r\n", g->name, g->desc ? g->desc : "");
	} else {
	    /* If the group is empty, but has server arts, fudge a
               dummy article in... */
	    if (g->last < g->first)
		printf("%s %010lu %010lu y\r\n", g->name, g->last, g->last);
	    else
		printf("%s %010lu %010lu y\r\n", g->name, g->last, g->first);
	}
	list (g->left, what);
    }
}


void dolist(const char* arg)
{
    rereadactive();
    initconfig();
    if (chdir(spooldir)) {
	printf("503 News spool directory does not exist!\r\n");
    } else if (!active) {
	printf("503 Group information file does not exist!\r\n");
    } else {
	if (!*arg || !strcasecmp(arg, "active")) {
	    printf("215 Newsgroups in form \"group high low flags\".\r\n");
	    list (active, 0);
	    printf(".\r\n");
	} else if (!strcasecmp(arg, "newsgroups")) {
	    printf("215 Descriptions in form \"group description\".\r\n");
	    list (active, 1);
	    printf(".\r\n");
	} else if (!strcasecmp(arg, "extensions")) {
	    printf("501 No extensions available.\r\n");
	} else if (!strcasecmp(arg, "overview.fmt")) {
	    printf("215 Order of fields in overview database.\r\n"
		   "Subject:\r\nFrom:\r\nDate:\r\nMessage-ID:\r\n"
		   "References:\r\nBytes:\r\nLines:\r\n"
		   "Xref:full\r\n.\r\n");
	} else if (!strcasecmp(arg, "active.times")) {
	    printf("215 Placeholder - Leafnode will fetch groups on demand\r\n"
		   "news.announce.newusers 42 tale@uunet.uu.net\r\n"
		   "news.answers 42 tale@uunet.uu.net\r\n"
		   ".\r\n");
#if 0
	} else if (!strcasecmp(arg, "xactive")) {
	} else if (!strcasecmp(arg, "distributions")) {
	} else if (!strcasecmp(arg, "searches")) {
	} else if (!strcasecmp(arg, "searchable")) {
	} else if (!strcasecmp(arg, "srchfields")) {
	} else if (!strcasecmp(arg, "motd")) {
	} else if (!strcasecmp(arg, "prettynames")) {
	} else if (!strcasecmp(arg, "subscriptions")) {
#endif
	} else {
	    printf("501 Syntax error\r\n");
	}
    }
}


void donewgroups(const char* arg)
{
    printf("231 Command not supported\r\n.\r\n");
}

void donewnews(const char* arg)
{
    printf("500 NEWNEWS is meaningless for this server\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	673416000L

static char ALPHABET[] = "0123456789abcdefghijklmnopqrstuv";

char* generateMessageID(void);

char* generateMessageID(void)
{
    static char buff[80];
    static time_t then;
    static unsigned int fudge;
    time_t now;
    char* p;
    int n;

    if (fqdn == NULL) {
	return NULL; /* this must be not called if fqdn is NULL. */
    }

    now = time(0); /* might be 0, in which case fudge will almost fix it */
    if (now != then)
	fudge = 0;
    else
	fudge++;
    then = now;

    p = buff;
    *p++ = '<';
    n = (unsigned int)now - OFFSET;
    while (n) {
	*p++ = ALPHABET[(int)(n & 31)];
	n >>= 5;
    }
    *p++ = '.';
    n = fudge * 32768 + (unsigned long)getpid();
    while (n) {
	*p++ = ALPHABET[(int)(n & 31)];
	n >>= 5;
    }
    sprintf(p, ".ln@%s>", fqdn);
    return buff;
}

/* the end of what I stole from rsalz and then mangled */


void 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;
    int len;
    int out;
    char filename[80];
    const char* outname;
    static int postingno; /* starts as 0 */

    sprintf(filename, "%d-%d-%d", (int)getpid(), (int)time(NULL), ++postingno);
    outname = getoutgoingfname(filename);

    out = open(outname, O_WRONLY|O_EXCL|O_CREAT, 0444);
    if (out < 0) {
	char dir[80];
	sprintf(dir, "%s/out.going", spooldir);
	mkdir(dir, 0755);
	out = open(outname, O_WRONLY|O_EXCL|O_CREAT, 0444);
	if (out < 0) {
	    printf("440 Unable to open spool file %s\r\n", outname);
	    return;
	}
    }

    printf("340 Go ahead.\r\n");
    fflush(stdout);

    do {
	int len;
	line = getaline(stdin);
	if (!line) {
	    unlink(outname);
	    exit(0);
	}
	if (!strncasecmp(line, "From: ", 6)) {
	    if (havefrom)
		err = TRUE;
	    else
		havefrom = TRUE;
	}
	if (!strncasecmp(line, "Path: ", 6)) {
	    if (havepath)
		err = TRUE;
	    else
		havepath = TRUE;
	}
	if (!strncasecmp(line, "Message-ID: ", 12)) {
	    if (havemessageid)
		err = TRUE;
	    else
		havemessageid = TRUE;
	}
	if (!strncasecmp(line, "Subject: ", 9)) {
	    if (havesubject)
		err = TRUE;
	    else
		havesubject = TRUE;
	}
	if (!strncasecmp(line, "Newsgroups: ", 12)) {
	    if (havenewsgroups)
		err = TRUE;
	    else
		havenewsgroups = TRUE;
	}
	if (!strncasecmp(line, "Date: ", 6)) {
	    if (havedate)
		err = TRUE;
	    else
		havedate = TRUE;
	}
	len = strlen(line);
	if (len && line[len - 1] == '\n')
	    line[--len] = '\0';
	if (len && line[len - 1] == '\r')
	    line[--len] = '\0';
	if (len && line[len - 1] == '\r')
	    line[--len] = '\0';
	if (len)
	    write(out, line, len);
	else {
	    if (!havepath) {
		write(out, "Path: ", 6);
		if (fqdn != NULL) {
		    write(out, fqdn, strlen(fqdn));
		    write(out, "!nobody\r\n", 9);
		} else {
		    write(out, "nobody\r\n", 8);
		}
	    }
	    if (!havedate) {
		const char* l = rfctime();
		write(out, "Date: ", 6);
		write(out, l, strlen(l));
		write(out, "\r\n", 2);
	    }
#ifndef NOMSGID
	    if (!havemessageid && fqdn != NULL) {
		char tmp[80];
		sprintf(tmp, "Message-ID: %s\r\n",
			generateMessageID());
		write(out, tmp, strlen(tmp));
	    }
#endif
	}
	write(out, "\r\n", 2);
    } while (*line);

    do {
	line = getaline(stdin);
	if (!line) {
	    unlink(outname);
	    exit(1);
	}

	len = strlen(line);
	if (len && line[len - 1] == '\n')
	    line[--len] = '\0';
	if (len && line[len - 1] == '\r')
	    line[--len] = '\0';
	if (len && line[len - 1] == '\r')
	    line[--len] = '\0';
	if (len)
	    write(out, line, len);
	write(out, "\r\n", 2);
    } while (line[0] != '.' || line[1] != '\0');
    close(out);

    if (havefrom && havesubject && havenewsgroups && !err) {
	printf("240 Article posted, now be patient\r\n");
	return;
    }

    unlink(outname);
    if (!havefrom) {
	printf("440 No From field, article not posted\r\n");
    } else if (!havesubject) {
	printf("440 No Subject field, article not posted\r\n");
    } else if (!havenewsgroups) {
	printf("440 No Newsgroups field, article not posted\r\n");
    } else {
	printf("440 Formatting error, article not posted\r\n");
    }
}

void doxhdr(char* arg)
{
    int arglen = strlen(arg);
    char* h[] = { "Subject", "From", "Date", "Message-ID", "References",
		  "Bytes", "Lines", "Xref" };
    int n = 8;
    char* l;
    unsigned long int a,b,c;
    FILE* f;

    /* have to keep arg since it is a part of a buffer of getaline(). */
    arg = strdup(arg);

    l = skipnonspaces(arg);
    if (l && *l)
	*l++ = '\0';
    l = skipspaces(l);

    if (l && *l == '<') { /* handle message-id form (well) */
	FILE* f;
	f = fopenart(l);
	if (!f) {
	    printf("430 No such article\r\n");
	    free(arg);
	    return;
	}
	do {
	    l = getaline(f);
	} while (l && *l &&
					/* uh, space before : allowed? */
		 (l[arglen] != ':' || strncasecmp(arg, l, arglen)));
	l = skipspaces(skipnonspaces(l));
	printf("221 First line of %s header follows:\r\n%s\r\n.\r\n",
	       arg, l ? l : ""); /* dubious - if not found, "" */
	fclose(f);
	free(arg);
	return;
    } else if (l && isdigit(*l)) {
	b = a = strtoul(l, &l, 10);
	if (a && l && *l) {
	    l = skipspaces(l);
	    if (*l == '-') {
		l++;
		if (l && *l)
		    b = strtoul(l, &l, 10);
		else
		    b = xlast;
	    }
	    l = skipspaces(l);
	    if (l && *l) {
		printf("502 Usage: XHDR header first[-last] or XHDR header message-id\r\n");
		free(arg);
		return;
	    }
	    if (b == 0)
		b = xlast;
	}
    } else {
	a = b = artno;
    }

    if (!group) {
	printf("412 Use the GROUP command first\r\n");
	free(arg);
	return;
    }

    if (!arg || !*arg) {
	printf("502 Usage: XHDR header first[-last] or XHDR header message-id\r\n");
	free(arg);
	return;
    }

    do {
	n--;
    } while (n > -1 && strncasecmp(arg, h[n], strlen(h[n])));

    markinterest();

    if (a < group->first)
	a = group->first;
    else if (a > group->last)
	a = group->last;

    if (b < a)
	b = a;
    else if (b > group->last)
	b = group->last;

    if (n >= 0) {
	printf("221 %s header (from overview) for postings %lu-%lu:\r\n",
	       h[n], a, b);

	if (xovergroup != group) {
	    getxover();
	    xovergroup = group;
	}

	for(c = a; c <= b; c++) {
	    if (xoverinfo &&
		c >= xfirst && c <= xlast &&
		xoverinfo[c - xfirst].text) {
		char* l = xoverinfo[c - xfirst].text;
		int d;
		for(d = 0; l && d <= n; d++)
		    l = strchr(l + 1, '\t');
		if (l) {
		    if (n == 7) /* dirty Xref: hack */
			l = l + 6 + 1;
		    else
			l = l + 1;
		}
		printf("%lu ", c);
		if (l) {
		    while (*l && *l != '\t')
			putchar(*l++);
		}
		printf("\r\n");
	    }
	}
    } else {
	printf("221 %s header (from article files) for postings %lu-%lu:\r\n",
	       arg, a, b);
	n = strlen(arg);
	for(c = a; c <= b; c++) {
	    char s[256];

	    sprintf(s, "%lu", c);
	    f = fopen(s, "r");
	    while (f) {
		l = getaline(f);
		if (!l || !*l || *l == '\r' || *l == '\n') {
		    fclose(f);
		    f = 0;
		} else if (l[n] == ':' && strncasecmp(arg, l, n) == 0) {
		    printf("%lu %s\r\n", c, l + n + 2);
		}
	    }
	}
    }

    printf(".\r\n");
    free(arg);
    return;
}


void doxover(const char* arg)
{
    char* l;
    unsigned long int a,b, art;

    if (!group) {
	printf("412 Use the GROUP command first\r\n");
	return;
    }

    markinterest();

    l = NULL;
    b = a = strtoul(arg, &l, 10);
    if (a && l && *l) {
	l = skipspaces(l);
	if (*l=='-')
	    b = strtoul(++l, &l, 10);
	l = skipspaces(l);
	if (l && *l) {
	    printf("502 Usage: XOVER first[-last]\r\n");
	    return;
	}
    }

    if (xovergroup != group && !getxover()) {
	printf("224 Overview information not available\r\n.\r\n");
	return;
    }
    xovergroup = group;

    if (b == 0)
	b = xlast;

    if (a > xlast)
	b = xlast;
    if (b > xlast)
	b = xlast;
    if (a < xfirst)
	a = xfirst;
    if (b < xfirst)
	b = xfirst;

    printf("224 Overview information for postings %lu-%lu:\r\n", a, b);
    for (art = a; art <= b; art++) {
	if (xoverinfo[art - xfirst].text)
	    printf("%s\r\n", xoverinfo[art-xfirst].text);
    }
    printf(".\r\n");
}



void dolistgroup(const char* arg)
{
    struct newsgroup* g;
    unsigned long int art;

    if (justaftergroup && group && group->name) {
#if DOTNGFILE
	int f;
	const char* s = getinterestingngdotfname(group);
	/* mark the previous group as _possibly_ interesting */
	if ((f = open(s, O_WRONLY|O_CREAT|O_TRUNC, 0644)) >= 0)
	    close(f);
#endif
    }

    rereadactive();

    if (arg && strlen(arg)) {
	g = findgroup(arg);
	if (!g) {
	    printf("481 No such group: %s\r\n", arg);
	    return;
	} else {
	    group = g;
	    artno = g->first;
	    justaftergroup = TRUE;
	}
    } else if (group) {
	g = group;
    } else {
	printf("481 No group specified\r\n");
	return;
    }

    markinterest();
    group = g;
    chdirgroup(g, 1);

    if ((xovergroup != group || xovergroup == NULL) && !getxover()) {
	printf("481 Overview information not available\r\n");
	return;
    }
    xovergroup = group;

    printf("211 Article list for %s follows\r\n", g->name);
    for(art = xfirst; art <= xlast; art++) {
	if (xoverinfo[art - xfirst].text)
	    printf("%lu \r\n", art);
    }
    printf(".\r\n");

    if (group)
	chdirgroup(group, 1);
}



int main(int argc, char** argv)
{
    int fodder;
#ifdef INET6
    struct sockaddr_storage sa;
#else
    struct hostent* he;
    struct sockaddr_in sa;
#endif

#ifdef NI_MAXHOST
    char name[NI_MAXHOST];
#else
    char name[1025];
#endif

    /* initialize FQDN. */
    fodder = sizeof(sa);
    if (getsockname(0, (struct sockaddr *)&sa, &fodder)) {
	strcpy(name, "localhost");
    } else {
#ifdef INET6
	name[0] = '\0';
	getnameinfo((struct sockaddr *)&sa, fodder,
		    name, sizeof(name), NULL, 0, 0);
#else
	he = gethostbyaddr((char *)&sa.sin_addr.s_addr,
			   sizeof(sa.sin_addr.s_addr),
			   AF_INET);
	strncpy(name,
		he && he->h_name ? he->h_name : inet_ntoa(sa.sin_addr),
		sizeof(name));
#endif
	name[sizeof(name)-1] = '\0';
    }
    if (strncasecmp(name, "localhost", 9) == 0) {
	whoami(); /* initialize FQDN by general way. */
    } else {
	fqdn = strdup(name);
    }

    umask(2);

    openlog("leafnode", LOG_PID|LOG_CONS, LOG_NEWS);

    printf("200 Leafnode+ NNTP Daemon, version %s running at %s\r\n",
	   version, fqdn ? fqdn : "nowhere");
    fflush(stdout);

    readconfig();

    rereadactive();

    parser();
    fflush(stdout);
    if (nntpout)
	fprintf(nntpout, "QUIT\r\n");

    exit(0);
}


syntax highlighted by Code2HTML, v. 0.9.1