/*
 * DIABLO.C	INTERNET NEWS TRANSIT AGENT, By Matthew Dillon
 *
 * Diablo implements the news transfer portion of the INN command
 * set.  Basically ihave, check, takethis, mode stream, head, and stat.
 * Not much more.  The purpose is to transfer news as quickly and
 * as efficiently as possible.
 *
 * Diablo is a forking server.  It supports both standard non-streaming
 * ihave feeds and streaming check/takethis feeds.  In fact, it supports
 * streaming on its entire command set if you wish to use it that way.
 * It forks on every connection and is able to do history file lookups
 * and article reception in parallel.  History file updates use minimal
 * locking and take advantage of the flexibility of INN's return codes.
 *
 * An active file is not required, and processing for things such as a 
 * newsgroups file and Control messages is left for the dreaderd reader
 * process to handle.  Diablo itself is strictly a backbone 
 * redistribution/transit agent.
 *
 *
 * (c)Copyright 1997, Matthew Dillon, All Rights Reserved.  Refer to
 *    the COPYRIGHT file in the base directory of this distribution 
 *    for specific rights granted.
 *
 * Modified 12/4/1997 to include support for compressed data streams.
 * Modifications (c) 1997, Christopher M Sedore, All Rights Reserved.
 * Modifications may be distributed freely as long as this copyright
 * notice is included without modification.
 */

#include "defs.h"

#if NEED_TERMIOS
#include <sys/termios.h>
#endif

typedef struct Feed {
    struct Feed *fe_Next;
    char	*fe_Label;
    int		fe_Fd;
    int		fe_NotifyFd;
    int		fe_Delayed;
    char	*fe_Buf;
    int		fe_BufIdx;
    int		fe_BufMax;
    int		fe_Failed;
} Feed;

typedef struct Retain {
    struct Retain *re_Next;
    FILE	*re_Fi;
    FILE	*re_Fo;
    int		re_What;
} Retain;

typedef struct Track {
    pid_t	tr_Pid;
    char	addr[64];
} Track;

#define RET_CLOSE	1
#define RET_PAUSE	2
#define RET_LOCK	3

#define REJMSGSIZE	1024

#define MINARTSIZE      80   /* Reject articles smaller than this. This is a conservative estimate */

void DiabloServer(int passedfd);
void DoAccept(int lfd);
void DoPipe(int fd);
void DoSession(int fd, int count);
void LogSession(void);
void LogSession2(void);
void DoCommand(int ufd);
void DoFeedNotify(FILE *fo, char *info);
void DoListNotify(FILE *fo, char *l);
void DoStats(FILE *fo, int dt, int raw);
int LoadArticle(Buffer *bi, const char *msgid, int noWrite, int headerOnly, char *refBuf, char *artType);
int SendArticle(const char *data, int fsize, FILE *fo, int doHead, int doBody);
void ArticleFileInit(void);
#ifdef USE_ZLIB
int ArticleFile(History *h, off_t *pbpos, int clvl, gzFile **cfile);
#else
int ArticleFile(History *h, off_t *pbpos, int clvl, char **cfile);
#endif
void ArticleFileCloseAll(void);
void ArticleFileCacheFlush(time_t t);
void ArticleFileClose(int i);
void ArticleFileTrunc(int artFd, off_t bpos);
void ArticleFileSetSize(int artFd);
void ngAddControl(char *nglist, int ngSize, const char *ctl);
void writePath(time_t t, char *path);

void writeFeedOut(const char *label, const char *file, const char *msgid, const char *offSize, int rt, int headOnly, const char *artType, const char *cSize);
void flushFeeds(int justClose);
void flushFeedOut(Feed *fe);

void FeedRSet(FILE *fo);
void FeedList(FILE *fo);
void FeedCommit(FILE *fo);
void FeedAddDel(FILE *fo, char *gwild, int add);

void FinishRetain(int what);
int QueueRange(const char *label, int *pqnum, int *pqarts, int *pqrun);
int countFds(fd_set *rfds);
int ArticleOpen(History *h, const char *msgid, char **pfi, int32 *rsize, int *pmart, int *pheadOnly, int *compressed);

void DoArtStats(int statgroup, int which, int bytes);
void DoSpoolStats(int which);

void Kill(pid_t pid, int sig);

typedef struct TotalStatsType {
    double	ArtsReceived;
    double	ArtsTested;
    double	ArtsBytes;
    double	ArtsFed;
} TotalStatsType;

typedef struct StoreStatsType {
    double	StoreBytes;
    double	StoreCompressedBytes;
} StoreStatsType;

FeedStats	Stats;
FeedStats	*HostStats;
TotalStatsType	TtlStats;
StoreStatsType	StoreStats;

char	*HName;
char	HLabel[256];
int	MaxFds = 0;
int	NumForks = 0;
int	ReadOnlyCount = 0;
int	PausedCount = 0;
fd_set	RFds;
Buffer	*PipeAry[MAXFDS];
Feed	*FeBase = NULL;
Retain	*ReBase = NULL;
Track	PidAry[MAXFDS];
volatile int Exiting = 0;
int	DidFork = 0;
int	NumForksExceeded = 0;
int	TxBufSize = 0;
int	RxBufSize = 0;
int	LogCount = 0;
time_t	SessionBeg = 0;
time_t	SessionMark = 0;
int	FeedTableReady = 0;
char	*DebugLabel = NULL;
char	PeerIpName[64];
hash_t	PeerIpHash;
int	HasStatusLine = 0;
int	RejectArtsWithNul = 0;
MemPool	*ParProcMemPool;
int	ReadOnlyCxn = 0;		/* Read-only client connection */
int	ReadOnlyMode = 0;		/* Server switched to RO mode */
pid_t	HostCachePid = 0;
time_t	SpoolAllocTime = 0;

FILE	*PathFd = NULL;
time_t	PathFdT = 0;
time_t	PathFdFileT = 0;

#define DEBUGLOG(msgid,msg) { \
    if (DebugOpt) \
	ddprintf("%d ** %-50s\t%s", (int)getpid(), (msgid ? msgid : "??"), msg); \
}

#define SETREJECT(msg) { \
    if (rejBuf) \
       snprintf(rejBuf, REJMSGSIZE, "%d %s %s", size, artType, msg); \
}

void
Usage(void)
{
    fprintf(stderr, "Usage: diablo -p pathhost [-A admin] [ -B bindip[:port] [-b fd]\n");
    fprintf(stderr, "              [-c commonpath ] [-d[#] ] [-e pcomexpire] [-F spamfilter]\n");
    fprintf(stderr, "              [-h hostname] [-M maxperremote ] [ -P [bindip:]port ]\n");
    fprintf(stderr, "              [-R rxbufsize] [-S spamopts ] [ -s statusline ]\n");
    fprintf(stderr, "              [-T txbufsize] [-V ] [ -X xrefhost ] [ -x xrefsync] [ -Z ]\n");
    fprintf(stderr, "              server\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "where:\n");
    fprintf(stderr, "  -A admin         Set the reported admin email address\n");
    fprintf(stderr, "  -B bindip[:port] Set the bind interface\n");
    fprintf(stderr, "  -b fd            Pass an open socket to listen on\n");
    fprintf(stderr, "  -c commonpath    Set a common pathhost entry\n");
    fprintf(stderr, "  -d [#]           Enable debugging\n");
    fprintf(stderr, "  -e pcomexpire    Set age of history cache\n");
    fprintf(stderr, "  -F spamfilter    Set path to external spamfilter\n");
    fprintf(stderr, "  -h hostname      Set reported hostname\n");
    fprintf(stderr, "  -M maxperremote  Set max incoming concurrent connections per remote host\n");
    fprintf(stderr, "  -P [bindip:]port Set listen port\n");
    fprintf(stderr, "  -R rxbufsize     Set TCP receive buffer size port\n");
    fprintf(stderr, "  -S spamopts      Enable internal spamfilter options\n");
    fprintf(stderr, "  -s statusline    Use this area of process space to write status data\n");
    fprintf(stderr, "  -T txbufsize     Set TCP transmit buffer size port\n");
    fprintf(stderr, "  -V               Display version and exit\n");
    fprintf(stderr, "  -X xrefhost      Set hostname used in generated Xref: lines\n");
    fprintf(stderr, "  -x xrefsync      Enable/disable updating of NX field in dactive from Xref:\n");
    fprintf(stderr, "  -Z               Reject articles containing a nul (\\0) character\n");
    exit(1);
}

int
main(int ac, char **av)
{
    char *op = NULL;
    int passedfd = -1;

    /*
     * On many modern UNIX systems, buffers for stdio are not allocated
     * until the first read or write AND, generally, large buffers (like 64K)
     * are allocated.  Since we print to stdout and stderr but do not really
     * need the buffers, we make them smaller.
     */

    LoadDiabloConfig(ac, av);
    (void)hhash("x");		/* prime hash table prior to forks */

    rsignal(SIGPIPE, SIG_IGN);
    SessionBeg = SessionMark = time(NULL);

    srandom((int32)SessionBeg ^ (getpid() * 100));
    random();
    random();

    bzero(&Stats, sizeof(Stats));
    bzero(&TtlStats, sizeof(TtlStats));

    /*
     * Options
     */

    {
	int i;
	char *p;
	PathListType *pl = DOpts.PathList;

	for (i = 1; i < ac; ++i) {
	    char *ptr = av[i];

	    if (*ptr != '-') {
		if (op) {
		    fprintf(stderr, "service option specified twice (%s/%s)\n", ptr, op);
		    exit(1);
		}
		op = ptr;
		continue;
	    }
	    ptr += 2;
	    switch(ptr[-1]) {
	    case 'b':
		passedfd = strtol(((*ptr) ? ptr : av[++i]), NULL, 0);
	    case 'A':
	    case 'N':
		strdupfree(&DOpts.NewsAdmin, (*ptr) ? ptr : av[++i], "");
		break;
	    case 'B':
		if (*ptr == 0)
		    ptr = av[++i];
                if (*ptr == '[') {
                    char *p = strchr(ptr, ']');
                    strdupfree(&DOpts.FeederBindHost, SanitiseAddr(ptr), NULL);
                    if (p != NULL && (p = strrchr(p, ':')) != NULL)
			strdupfree(&DOpts.FeederPort, p + 1, NULL);
		} else if ((p = strrchr(ptr, ':')) != NULL) {
                    *p++ = 0;
                    strdupfree(&DOpts.FeederPort, p, NULL);
                    strdupfree(&DOpts.FeederBindHost, SanitiseAddr(ptr), NULL);
		} else {
                    strdupfree(&DOpts.FeederBindHost, SanitiseAddr(ptr), NULL);
		}
		break;
	    case 'C':
		if (*ptr == 0)
		    ++i;
		break;
	    case 'c':
		{
		    char *p = (*ptr) ? ptr : av[++i];
		    if (*p == 0) 		/* 0-length string no good */
			break;
		    if (pl == NULL) {
			pl = zalloc(&SysMemPool, sizeof(PathListType));
			DOpts.PathList = pl;
		    } else {
			pl->next = zalloc(&SysMemPool, sizeof(PathListType));
			pl = pl->next;
		    }
		    pl->pathent = strdup(p);
		    pl->pathtype = 2;
		    pl->next = NULL;
		}
		break;
	    case 'd':
		if (isdigit((int)(unsigned char)*ptr)) {
		    DebugOpt = strtol(ptr, NULL, 0);
		} else {
		    --ptr;
		    while (*ptr == 'd') {
			++DebugOpt;
			++ptr;
		    }
		}
		break;
	    case 'e':
		SetCommand(stderr, "precommittime", (*ptr) ? ptr : av[++i], NULL);
		break;
	    case 'F':
		SetCommand(stderr, "feederfilter", (*ptr) ? ptr : av[++i], NULL);
		break;
	    case 'h':
		SetCommand(stderr, "feederhostname", (*ptr) ? ptr : av[++i], NULL);
		break;
	    case 'M':
		SetCommand(stderr, "maxconnect", (*ptr) ? ptr : av[++i], NULL);
		break;
	    case 'P':
		if (*ptr == 0)
		    ptr = av[++i];
                if (*ptr == '[') {
                    char *p = strchr(ptr, ']');
                    strdupfree(&DOpts.FeederBindHost, SanitiseAddr(ptr), NULL);
                    if (p != NULL && (p = strrchr(p, ':')) != NULL)
			strdupfree(&DOpts.FeederPort, p + 1, NULL);
		} else if ((p = strrchr(ptr, ':')) != NULL) {
                    *p++ = 0;
                    strdupfree(&DOpts.FeederPort, p, NULL);
                    strdupfree(&DOpts.FeederBindHost, SanitiseAddr(ptr), NULL);
		} else {
                    strdupfree(&DOpts.FeederPort, ptr, NULL);
		}
		break;
	    case 'p':
		strdupfree(&DOpts.FeederPathHost, (*ptr) ? ptr : av[++i], "");
		if (pl == NULL) {
		    pl = zalloc(&SysMemPool, sizeof(PathListType));
		    DOpts.PathList = pl;
		} else {
		    pl->next = zalloc(&SysMemPool, sizeof(PathListType));
		    pl = pl->next;
		}
		pl->pathent = strdup(DOpts.FeederPathHost);
		pl->pathtype = 1;
		pl->next = NULL;
		break;
	    case 'R':
		RxBufSize = strtol(((*ptr) ? ptr : av[++i]), NULL, 0);
		if (RxBufSize < 4096)
		    RxBufSize = 4096;
		break;
	    case 'r':   
		if (!*ptr)
                ptr = av[++i];
		DOpts.FeederMaxAcceptAge = TimeSpec(ptr, "d");
		if (DOpts.FeederMaxAcceptAge == -1)
		    Usage();
		break;
	    case 'S':
		SetCommand(stderr, "internalfilter", (*ptr) ? ptr : av[++i], NULL);
		break;
	    case 's':
		SetStatusLine(ptr - 2, strlen(ptr - 2));
		HasStatusLine = 1;
		break;
	    case 'T':
		TxBufSize = strtol(((*ptr) ? ptr : av[++i]), NULL, 0);
		break;
	    case 'V':
		PrintVersion();
		break;
	    case 'X':
		SetCommand(stderr, "feederxrefhost", (*ptr) ? ptr : av[++i], NULL);
		break;
	    case 'x':
		ptr = (*ptr) ? ptr : av[++i];
		SetCommand(stderr, "feederxrefhost", (*ptr) ? ptr : av[++i], NULL);
		break;
	    case 'Z':
		SetCommand(stderr, "rejectartswithnul", "1", NULL);
		break;
	    default:
		fprintf(stderr, "unknown option: %s\n", ptr - 2);
		Usage();
	    }
	}
	if (i > ac) {
	    fprintf(stderr, "expected argument to last option\n");
	    Usage();
	}
    }
     
    if (DOpts.FeederHostName == NULL)
	SetMyHostName(&DOpts.FeederHostName);
    if (DOpts.NewsAdmin == NULL)
	SetNewsAdmin(&DOpts.NewsAdmin, DOpts.FeederHostName);
    if (*DOpts.NewsAdmin == 0) {
	free(DOpts.NewsAdmin);
	DOpts.NewsAdmin = NULL;
    }

    /*
     * For our Path: insertion
     */
    if (DOpts.FeederPathHost == NULL) {
	fprintf(stderr, "No '-p newspathname' specified\n");
	Usage();
    }

    if(DOpts.FeederXRefHost == NULL && DOpts.FeederPathHost != NULL) 
	DOpts.FeederXRefHost = DOpts.FeederPathHost;

    /*
     * The chdir is no longer required, but we do it anyway to
     * have a 'starting point' to look for cores and such.
     */
    if (chdir(PatExpand(SpoolHomePat)) < 0) {
	fprintf(stderr, "%s: chdir('%s'): %s\n", av[0],
				PatExpand(SpoolHomePat), strerror(errno));
	exit(1);
    }

    if (op == NULL) {
	fprintf(stderr, "Must specify service option: (server)\n");
	Usage();
    } else if (strcmp(op, "server") == 0) {
	DiabloServer(passedfd);
    } else {
	fprintf(stderr, "unknown service option: %s\n", op);
	Usage();
	exit(1);
    }
    return(0);
}

void
Kill(pid_t pid, int sig)
{
    if (kill(pid, sig) < 0) {
	logit(LOG_ERR, "kill pid %d (%d) failed: %s", pid, sig, strerror(errno));
    }
}

/*
 * This needs fork.  What we are trying to accomplish
 * is to ensure that all the pipes from the children
 * are flushed back to the parent and the parent
 * writes them out before exiting.  Otherwise
 * feed redistribution might fail.
 */

void
sigHup(int sigNo)
{
    Exiting = 1;
    if (DidFork) {
	if (FeedFo) {
	    fflush(FeedFo);
	    fclose(FeedFo);
	    FeedFo = NULL;
	}
	ArticleFileCloseAll();
	DiabFilterClose();
	LogSession();	/* try, may not work */
	exit(1);
    } else {
	if (Exiting == 0) {
	    int i;

	    Exiting = 1;
	    for (i = 0; i < MAXFDS; ++i) {
		if (PidAry[i].tr_Pid) {
		    Kill(PidAry[i].tr_Pid, SIGHUP);
		}
	    }
	}
    }
}

void
sigUsr1(int sigNo)
{
    ++DebugOpt;
}

void
sigUsr2(int sigNo)
{
    DebugOpt = 0;
}

void
sigAlrm(int sigNo)
{
    if (ReadOnlyCxn) {
	ReadOnlyMode = 1;
	if (DidFork) {
	    struct sockaddr_un soun;
	    int ufd;
	    FILE *fo;

	    memset(&soun, 0, sizeof(soun));
	    if((ufd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
		/* Can't make socket; hang ourselves */
		logit(LOG_INFO, "Unable to create UNIX socket: %s\n",
			strerror(errno));
		sigHup(SIGHUP);
	    }

	    soun.sun_family = AF_UNIX;
	    sprintf(soun.sun_path, "%s", PatRunExpand(DiabloSocketPat));
	    if (connect(ufd, (struct sockaddr *)&soun,
		offsetof(struct sockaddr_un,
			 sun_path[strlen(soun.sun_path)+1])) < 0) {
		/* Can't connect; hang ourselves */
		logit(LOG_INFO, "Unable to connect to master %s: %s\n",
			soun.sun_path, strerror(errno));
		sigHup(SIGHUP);
	    }

	    fo = fdopen(ufd, "w");

	    fprintf(fo, "child-is-readonly\n");
	    fprintf(fo, "quit\n");
	    fflush(fo);

	    fclose(fo);

	    close(ufd);
	}
    } else {
	sigHup(SIGHUP);
    }
}

/*
 * DIABLOSERVER() - The master server process.  It accept()s connections
 *		    and forks off children.
 */

void
DiabloServer(int lfd)
{
    int ufd;
    int fdrotor = 0;

    /*
     * Detach
     */

    if (DebugOpt == 0) {
	pid_t pid = fork();

	if (pid < 0) {
	    perror("fork");
	    exit(1);
	}
	if (pid > 0) {
	    exit(0);
	}

	/*
	 * Child continues
	 */

	DDUseSyslog = 1;

	freopen("/dev/null", "w", stdout);
	freopen("/dev/null", "w", stderr);
	freopen("/dev/null", "r", stdin);
#if USE_TIOCNOTTY
	{
	    int fd = open("/dev/tty", O_RDWR);
	    if (fd >= 0) {
		ioctl(fd, TIOCNOTTY, 0);
		close(fd);
	    }
	}
#endif
#if USE_SYSV_SETPGRP
	setpgrp();
#else
	setpgrp(0, 0);
#endif
    }

    /*
     * select()/signal() setup
     */

    FD_ZERO(&RFds);

    rsignal(SIGHUP, sigHup);
    rsignal(SIGINT, sigHup);
    rsignal(SIGTERM, sigHup);
    rsignal(SIGUSR1, sigUsr1);
    rsignal(SIGUSR2, sigUsr2);
    rsignal(SIGALRM, sigAlrm);

    /*
     * logs, socket setup
     */

    OpenLog("diablo", (DebugOpt ? LOG_PERROR : 0)|LOG_PID|LOG_NDELAY);

    if (lfd == -1) {
#ifdef	INET6
	int error;
	struct addrinfo hints;
	struct addrinfo *res;

	/*
	 * Open a wildcard listening socket
	 */
	memset(&hints, 0, sizeof(hints));
	hints.ai_flags = AI_PASSIVE;
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	error = getaddrinfo(DOpts.FeederBindHost, DOpts.FeederPort,
							&hints, &res);
	if (error == EAI_NODATA) {
	    hints.ai_flags = 0;
	    error = getaddrinfo(DOpts.FeederBindHost, 0, &hints, &res);
	}
	if (error != 0) {
	    fprintf(stderr, "getaddrinfo: %s:%s: %s\n",
			DOpts.FeederBindHost ? DOpts.FeederBindHost : "ALL",
			DOpts.FeederPort, gai_strerror(error));
	    exit(1);
	}
	if ((lfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0) {
	    perror("socket");
	    exit(1);
	}
#else
	struct sockaddr_in sin;

	memset(&sin, 0, sizeof(sin));

	/*
	 * listen socket for news
	 */
	memset(&sin, 0, sizeof(sin));
	if ((lfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	    perror("socket");
	    exit(1);
	}

	sin.sin_family = AF_INET;

	/*
	 * Work out the interface to bind to
	 */
	if (DOpts.FeederBindHost == NULL) {
		sin.sin_addr.s_addr = INADDR_ANY;
	} else {
	    if (strtol(DOpts.FeederBindHost, NULL, 0) > 0) {
		sin.sin_addr.s_addr = inet_addr(DOpts.FeederBindHost);
	    } else {
		struct hostent *he;

		if ((he = gethostbyname(DOpts.FeederBindHost)) != NULL) {
		    sin.sin_addr = *(struct in_addr *)he->h_addr;
		} else {
		    fprintf(stderr, "Unknown bind host: %s\n",
							DOpts.FeederBindHost);
		    exit(1);
		}
	    }
	}
	/*
	 * Work out the port to bind to
	 */
	{
	    struct servent *sen;
	    int port;
	    if (DOpts.FeederPort == NULL) {
		DOpts.FeederPort = "nntp";
	    } else if ((port = strtol(DOpts.FeederPort, NULL, 0)) != 0) {
		sin.sin_port = htons(port);
	    } else {
		if ((sen = getservbyname(DOpts.FeederPort, "tcp")) != NULL) {
		    sin.sin_port = sen->s_port;
		} else {
		    fprintf(stderr, "Unknown service: %s\n", DOpts.FeederPort);
		    exit(1);
		}
	    }
	}

#endif	/* INET6 */

	{
	    int on = 1;
	    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
	    setsockopt(lfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
	    printf("Setsockopt\n");
	}
	if (TxBufSize) {
	    setsockopt(lfd, SOL_SOCKET, SO_SNDBUF, (void *)&TxBufSize, sizeof(int));
	}
	if (RxBufSize) {
	    setsockopt(lfd, SOL_SOCKET, SO_RCVBUF, (void *)&RxBufSize, sizeof(int));
	}

#ifdef INET6
	{
	    if (bind(lfd, res->ai_addr, res->ai_addrlen) < 0) {
		perror("bind");
		exit(1);
	    }
	    freeaddrinfo(res);
	}
#else
	if (bind(lfd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
	    perror("bind");
	    exit(1);
	}
#endif
	{
	    int on = 1;
	    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
	    setsockopt(lfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
	    printf("Setsockopt\n");
	}
	if (TxBufSize) {
	    setsockopt(lfd, SOL_SOCKET, SO_SNDBUF, (void *)&TxBufSize, sizeof(int));
	}
	if (RxBufSize) {
	    setsockopt(lfd, SOL_SOCKET, SO_RCVBUF, (void *)&RxBufSize, sizeof(int));
	}

#if NONBLK_ACCEPT_BROKEN
	/* HPUX is broken, see lib/config.h */
#else
	fcntl(lfd, F_SETFL, O_NONBLOCK);
#endif
    }

    if (listen(lfd, 10) < 0) {
	perror("listen");
	exit(1);
    }
    if (DOpts.FeederBindHost != NULL &&
				strchr(DOpts.FeederBindHost, ':') != NULL)
	logit(LOG_INFO, "Listening on [%s]:%s\n",
			DOpts.FeederBindHost ? DOpts.FeederBindHost : "ALL",
			DOpts.FeederPort);
    else
	logit(LOG_INFO, "Listening on %s:%s\n",
			DOpts.FeederBindHost ? DOpts.FeederBindHost : "ALL",
			DOpts.FeederPort);

    FD_SET(lfd, &RFds);
    if (MaxFds <= lfd)
	MaxFds = lfd + 1;

    /*
     * Upward compatibility hack - older versions of diablo create
     * the unix domain socket as root.  We have to make sure the
     * file path is cleared out so we can create the socket as user news.
     */
    remove(PatRunExpand(DiabloSocketPat));

    /*
     * change my uid/gid
     */

    {
	struct passwd *pw = getpwnam("news");
	struct group *gr = getgrnam("news");
	gid_t gid;

	if (pw == NULL) {
	    perror("getpwnam('news')");
	    exit(1);
	}
	if (gr == NULL) {
	    perror("getgrnam('news')");
	    exit(1);
	}
	gid = gr->gr_gid;
	setgroups(1, &gid);
	setgid(gr->gr_gid);
	setuid(pw->pw_uid);
    }

    /*
     * UNIX domain socket
     */

    {
	struct sockaddr_un soun;

	memset(&soun, 0, sizeof(soun));

	if ((ufd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
	    perror("udom-socket");
	    exit(1);
	}
	soun.sun_family = AF_UNIX;

	sprintf(soun.sun_path, "%s", PatRunExpand(DiabloSocketPat));
	remove(soun.sun_path);
	if (bind(ufd, (struct sockaddr *)&soun, offsetof(struct sockaddr_un, sun_path[strlen(soun.sun_path)+1])) < 0) {
	    perror("udom-bind");
	    exit(1);
	}
	chmod(soun.sun_path, 0770);

#if NONBLK_ACCEPT_BROKEN
	/* HPUX is broken, see lib/config.h */
#else
	fcntl(ufd, F_SETFL, O_NONBLOCK);
#endif

	if (listen(ufd, 200) < 0) {
	    perror("udom-listen");
	    exit(1);
	}

	FD_SET(ufd, &RFds);
	if (MaxFds <= ufd)
	    MaxFds = ufd + 1;
    }

    /*
     * Call InitPreCommit() and InitSpamFilter() to setup any shared memory
     * segments.  These need to be created and mapped prior to any forks that
     * we do.
     */

    InitPreCommit();
    SetSpamFilterOpt();
    if (DOpts.SpamFilterOpt != NULL)
	InitSpamFilter();

    /*
     * Initial load of dnewsfeeds file.  We recheck every select
     * (but it doesn't call stat() every time).  The master server
     * requires the entire file to be parsed, so we do not supply
     * a label.
     */

    LoadSpoolCtl(0, 1);		/* check spool partitions if specified */ 
    LoadNewsFeed(0, 1, NULL);

    {
	struct stat st;

	HostCachePid = LoadHostAccess(time(NULL), 1, DOpts.HostCacheRebuildTime);
	if (stat(PatDbExpand(DHostsCachePat), &st) < 0) {
	    logit(LOG_INFO, "No host cache - waiting for build to complete");
	    waitpid(HostCachePid, NULL, 0);
	    logit(LOG_INFO, "Host cache rebuild complete");
	}
    }

    logit(LOG_INFO, "Waiting for connections");
    /*
     * Main Loop
     */

    while (NumForks || Exiting == 0) {
	fd_set rfds = RFds;
	int i;
	int n;
	struct timeval tv = { 5, 0 };
	time_t t;

	if ((ReadOnlyMode || PausedCount) && (NumForks == ReadOnlyCount)) {
	    FinishRetain(RET_PAUSE);
	}

	t = time(NULL);

	LoadSpoolCtl(t, 0);	/* check spool partitions if specified */ 
	LoadNewsFeed(t, 0, NULL);
	if (HostCachePid == 0)
	    HostCachePid = LoadHostAccess(t, 0, DOpts.HostCacheRebuildTime);

	n = select(MaxFds, &rfds, NULL, NULL, &tv);

	if (lfd != -1 && FD_ISSET(lfd, &rfds))
	    DoAccept(lfd);
	if (ufd != -1 && FD_ISSET(ufd, &rfds))
	    DoCommand(ufd);

	/* exit this loop when either all fds have been examined, or
	 * when 10 ready pipes have been processed.
	 */
	n = 10;
	i = fdrotor;
	do {
	    if (i != lfd && i != ufd && FD_ISSET(i, &rfds)) {
		--n;
		DoPipe(i);
	    }
	    if (++i >= MaxFds)
		i = 0;
	} while (n && i != fdrotor);
	fdrotor = i;

	{
	    pid_t pid;

	    while ((pid = wait3(NULL, WNOHANG, NULL)) > 0) {
		int i;

		if (pid == HostCachePid) {
		    HostCachePid = 0;
		    continue;
		}

		for (i = 0; i < MaxFds; ++i) {
		    if (PidAry[i].tr_Pid == pid) {
			bzero(&PidAry[i], sizeof(PidAry[0]));
		    }
		}
	    }

	    if (pid < 0 && errno != ECHILD)
		logit(LOG_EMERG, "wait3 failed: %s", strerror(errno));
	}
	if (NumForks >= MAXFORKS || Exiting) {
	    if (NumForksExceeded == 0) {
		if (Exiting) {
		    logit(LOG_WARNING, "Exiting, waiting on children");
		} else {
		    logit(LOG_WARNING, "NumForks exceeded");
		}
		NumForksExceeded = 1;
	    }
	    if (lfd >= 0)
		FD_CLR(lfd, &RFds);
	    if (Exiting) {
		close(lfd);
		lfd = -1;
	    }
	} else {
	    if (NumForksExceeded) {
		if (NumForksExceeded == 1)
		    logit(LOG_WARNING, "NumForks ok, reaccepting");
		NumForksExceeded = 0;
	    }
	    FD_SET(lfd, &RFds);
	}
    }
    LogSession2();
    flushFeeds(0);
    ClosePathLog(1);
    CloseArtLog(1);
    DiabFilterClose();
    if (DOpts.SpamFilterOpt != NULL)
	TermSpamFilter();
    sleep(2);

    if (ShutdownCleanup && (strcmp(ShutdownCleanup, "NONE") != 0)) {
	system(ShutdownCleanup);
    }
    FinishRetain(RET_PAUSE);
    FinishRetain(RET_CLOSE);
}

/*
 * DOACCEPT()	- accept and deal with a new connection
 */

void
DoAccept(int lfd)
{
    int fd;
    int delaylen;
    int curtime = (int)(time(NULL));
    int dt = (int)(SessionMark);
    char addrst[NI_MAXHOST];
#ifdef INET6
    struct sockaddr_storage res;
    int reslen = sizeof(res);
#else
    struct sockaddr_in asin;
    ACCEPT_ARG3_TYPE alen = sizeof(asin);
#endif

    if (Exiting)
	return;

#ifdef INET6
    if ((fd = accept(lfd, (struct sockaddr *)&res, &reslen)) >= 0) {
#else
    if ((fd = accept(lfd, (struct sockaddr *)&asin, &alen)) >= 0) {
#endif
	int fds[2] = { -1, -1 };
	int ok = 0;
	int count = 0;

	fcntl(fd, F_SETFL, 0);

	/*
	 * Handle connection limit.  Note that we assume the TCP write buffer
	 * is large enough to hold our response message so the write() does
	 * not block.
	 */

	addrst[0] = 0;
	{
#ifdef INET6
	    char *st = NetAddrToSt(fd, NULL, 1, 0, 0);

	    if (st != NULL) {
		strcpy(addrst, st);
#else
	    struct sockaddr_in rsin;
	    ACCEPT_ARG3_TYPE alen = sizeof(rsin);

	    if (getpeername(fd, (struct sockaddr *)&rsin, &alen) == 0) {
		strncpy(addrst, inet_ntoa(rsin.sin_addr), sizeof(addrst) - 1);
		addrst[sizeof(addrst) - 1] = '\0';
#endif
		if (DOpts.MaxPerRemote) {
		    int i;

		    for (i = 0; i < MaxFds; ++i) {
			if (strcmp(PidAry[i].addr, addrst) == 0)
			    ++count;
		    }
		    if (DOpts.MaxPerRemote > 0 && count >= DOpts.MaxPerRemote) {
			char buf[256];

			logit(
			    LOG_WARNING, 
			    "Connect Limit exceeded (from -M/diablo.config) for %s (%d)",
			    addrst,
			    count
			);
			snprintf(
			    buf, sizeof(buf),
			    "502 %s: parallel connection limit is %d\r\n",
			    DOpts.FeederHostName,
			    DOpts.MaxPerRemote
			);
			write(fd, buf, strlen(buf));
			ok = -1;
		    }
		}
		sprintf(PeerIpName, "%s", addrst);
		bhash(&PeerIpHash, PeerIpName, strlen(PeerIpName));
	    } else if (DOpts.MaxPerRemote) {
		ok = -1;
	    }
	}

	SpoolAllocTime = time(NULL);
	if (AllocateSpools(SpoolAllocTime) == -1) {
	    char buf[256];
	    snprintf(buf, sizeof(buf), "502 %s: temporary server error\r\n",
						DOpts.FeederHostName);
	    write(fd, buf, strlen(buf));
	    sleep(2);
	    ok = -1;
	}

	if (ok == 0 && pipe(fds) == 0 && fds[0] < MAXFDS) {
	    pid_t pid;

	    /*
	     * we MUST clear our FILE stdio so a call to
	     * exit() doesn't flush FILE structures to
	     * the wrong descriptors!
	     *
	     * WARNING WARNING!  There cannot be a single
	     * critical FILE handle open for which we may
	     * close it's underlying descriptor!
	     */

	    fflush(stdout);
	    fflush(stderr);

	    if ((pid = fork()) == 0) {
		int i;
		time_t SessionCheck = SessionBeg = SessionMark = time(NULL);

		flushFeeds(1);	/* close feed descriptors without flushing */
		CloseIncomingLog();
		ClosePathLog(0);
		CloseArtLog(0);

		if (HasStatusLine)
		    stprintf("%s", addrst);

		CloseLog(NULL, 1);

		for (i = 0; i < MaxFds; ++i) {
		    if (i > 2 && i != fds[1] && i != fd && i != ZoneFd && i != BodyFilterFd && i != NphFilterFd) {
			if (PipeAry[fd] != NULL) {
			    bclose(PipeAry[fd], 0);
			    PipeAry[fd] = NULL;
			}
			close(i);
		    }
		}

		OpenLog("diablo", (DebugOpt ? LOG_PERROR : 0)|LOG_PID|LOG_NDELAY);

		if (HLabel[0] != 0)
		    nice(FeedPriority(HLabel));

		FeedFo = fdopen(fds[1], "w");
#ifdef INET6
		if ((HName = Authenticate(fd, (struct sockaddr *)&res, addrst, HLabel)) == NULL) {
#else
		if ((HName = Authenticate(fd, (struct sockaddr *)&asin,
						addrst, HLabel)) == NULL) {
#endif
		    FILE *fo = fdopen(fd, "w");

		    if (fo == NULL) {
			logit(LOG_CRIT, "fdopen() of socket failed");
			exit(1);
		    }
		    if (DOpts.DisplayAdminVersion && DOpts.NewsAdmin != NULL)
			xfprintf(fo, "502 %s: Transfer permission denied to %s - %s (DIABLO %s-%s)\r\n",
						DOpts.FeederHostName,
						addrst,
						DOpts.NewsAdmin,
						VERS, SUBREV);
		    else
			xfprintf(fo, "502 %s: Transfer permission denied to %s\r\n",
						DOpts.FeederHostName,
						addrst);
		    logit(LOG_INFO, "Connection %d from %s (no permission)",
			fds[0],
			addrst
		    );
		    exit(0);
		}
		if (HLabel[0] == 0) {
		    FILE *fo = fdopen(fd, "w");

		    if (fo == NULL) {
			logit(LOG_CRIT, "fdopen() of socket failed");
			exit(1);
		    }
		    if (DOpts.DisplayAdminVersion && DOpts.NewsAdmin != NULL)
			xfprintf(fo, "502 %s DIABLO Misconfiguration, label missing in dnewsfeeds, contact %s\r\n",
						DOpts.FeederHostName,
						DOpts.NewsAdmin);
		    else
			xfprintf(fo, "502 %s DIABLO Misconfiguration, label missing in dnewsfeeds\r\n",
						DOpts.FeederHostName);
		    logit(LOG_CRIT, "Diablo misconfiguration, label for %s not found in dnewsfeeds", HName);
		    exit(0);
		}

		if (strcmp(HLabel, "%STATS") == 0) {
		    FILE *fo = fdopen(fd, "w");
		    int dt = (int)(time(NULL) - SessionCheck);

		    if (fo == NULL) {
			logit(LOG_CRIT, "fdopen() of socket failed");
			exit(1);
		    }
		    DoStats(fo, dt, 0);
		    fflush(fo);
		    exit(0);
		}

		DidFork = 1;

		if (HasStatusLine)
		    stprintf("%s", HName);

		delaylen = FeedInDelay(HLabel);

		if((curtime - dt) < delaylen) {
			FILE *fo = fdopen(fd, "w");

			if (DOpts.DisplayAdminVersion && DOpts.NewsAdmin != NULL)
				xfprintf(fo, "400 %s: System starting up - Try again in a few minutes - %s (DIABLO %s-%s)\r\n",
						DOpts.FeederHostName,
						DOpts.NewsAdmin,
						VERS, SUBREV);
			else
				xfprintf(fo, "400 %s: System starting up - Try again in a few minutes\r\n",
						DOpts.FeederHostName);
			logit(LOG_INFO, "System starting up: %s is delayed for %d seconds", DOpts.FeederHostName, delaylen);
			exit(0);
		}

		logit(LOG_INFO, "Connection %d from %s %s",
		    fds[0],
		    HName,
		    addrst
		);

		/*
		 * Free the parent process memory pool, which the child does not
		 * use (obviously!)
		 */

		freePool(&ParProcMemPool);

		/*
		 * Free memory used by DiabFilter as well, because it's only
		 * used in the parent.
		 */

		DiabFilter_freeMem();

		/*
		 * Simple session debugging support
		 */
		if (DebugLabel && HLabel[0] && strcmp(DebugLabel, HLabel) == 0) {
		    char path[256];
		    int tfd;

		    sprintf(path, "/tmp/diablo.debug.%d", (int)getpid());
		    DebugOpt = 1;
		    remove(path);
		    if ((tfd = open(path, O_EXCL|O_CREAT|O_TRUNC|O_RDWR, 0600)) >= 0) {
			close(tfd);
			freopen(path, "a", stderr);
			freopen(path, "a", stdout);
			printf("Debug label %s pid %d\n", HLabel, (int)getpid());
		    } else {
			printf("Unable to create %s\n", path);
		    }
		}

		DoSession(fd, count);
		LogSession();
		logit(LOG_INFO, "Disconnect %d from %s %s (%d elapsed)",
		    fds[0],
		    HName,
		    addrst,
		    (int)(time(NULL) - SessionBeg)
		);
		exit(0);
	    }
	    if (pid < 0) {
		logit(LOG_EMERG, "fork failed: %s", strerror(errno));
	    } else {
		ok = 1;
		++NumForks;
		PidAry[fds[0]].tr_Pid = pid;
		strncpy(PidAry[fds[0]].addr, addrst,
					sizeof(PidAry[fds[0]].addr) - 1);
		PidAry[fds[0]].addr[sizeof(PidAry[fds[0]].addr) - 1] = '\0';
	    }
	}
	close(fd);
	if (fds[1] >= 0)
	    close(fds[1]);

	if (ok > 0) {
	    fcntl(fds[0], F_SETFL, O_NONBLOCK);
	    FD_SET(fds[0], &RFds);
	    if (MaxFds <= fds[0])
		MaxFds = fds[0] + 1;
	} else {
	    if (fds[0] >= 0) {
		logit(LOG_WARNING, "Maximum file descriptors exceeded");
		close(fds[0]);
	    } else if (ok == 0 && fds[0] < 0) {
		logit(LOG_EMERG, "pipe() failed: %s", strerror(errno));
	    }
	}
    }
}

/*
 * DOPIPE()	- handle data returned from our children over a pipe
 */

int 
fwCallBack(const char *hlabel, const char *msgid, const char *path, const char *offsize, int plfo, int headOnly, const char *artType, const char *cSize)
{
    writeFeedOut(hlabel, path, msgid, offsize, ((plfo > 0) ? 1 : 0), headOnly, artType, cSize);
    TtlStats.ArtsFed += 1.0;
    return(0);
}

void
DoPipe(int fd)
{
    char *ptr;
    int maxCount = 2;
    int bytes;

    if (PipeAry[fd] == NULL)
	PipeAry[fd] = bopen(fd, 1);

    while ((ptr = egets(PipeAry[fd], &bytes)) != NULL && ptr != (char *)-1) {
	char *s1;

	ptr[bytes - 1] = 0;	/* replace newline with NUL */

	if (DebugOpt > 2)
	    ddprintf("%d << %*.*s", (int)getpid(), bytes, bytes, ptr);

	s1 = strtok(ptr, "\t\n");

	if (s1 == NULL)
	    continue;

	if (strncmp(s1, "SOUT", 4) == 0) {
	    char *path = strtok(NULL, "\t\n");
	    char *offsize = strtok(NULL, "\t\n");
	    const char *msgid = MsgId(strtok(NULL, "\t\n"), NULL);
	    char *nglist = strtok(NULL, "\t\n");
	    char *dist = strtok(NULL, "\t\n");
	    char *npath = strtok(NULL, "\t\n");
	    char *headOnly = strtok(NULL, "\t\n");
	    char *artType = strtok(NULL, "\t\n");
	    char *cSize = strtok(NULL, "\t\n");

	    if (DebugOpt > 2) {
		ddprintf(
		    "%d SOUTLINE: %s %s %s %s %s HO=%s AT=%s %s\n",
		    (int)getpid(), 
		    path, 
		    offsize,
		    msgid,
		    nglist,
		    npath, 
		    headOnly != NULL ? headOnly : "",
		    artType != NULL ? artType : "",
		    cSize != NULL? cSize : ""
		);
	    }

	    if (path && offsize && msgid && nglist && npath && headOnly) {
		int spamArt = 0;

		bytes = 0;
		{
		    char *p;
		    if ((p = strchr(offsize, ',')) != NULL)
			bytes = strtol(p + 1, NULL, 0);
		}

		if (DOpts.FeederFilter != NULL && 
			FeedSpam(2, nglist, npath, dist, artType, bytes))
		{
		    static char loc[PATH_MAX];

		    snprintf(loc, sizeof(loc), "%s:%s", path, offsize);
		    spamArt = DiabFilter(DOpts.FeederFilter, loc, DOpts.WireFormat);
		}
		FeedWrite(1, fwCallBack, msgid, path, offsize, nglist,
				npath, dist, headOnly, artType, spamArt, cSize);
		{
		    TtlStats.ArtsBytes += (double)bytes;
		}
		TtlStats.ArtsReceived += 1.0;
		if (++LogCount == 1024) {
		    LogCount = 0;
		    LogSession2();
		}
		WritePath(npath);
		WriteArtLog(npath, bytes, artType, nglist);
	    }
	} else if (strncmp(s1, "FLUSH", 5) == 0) {
	    flushFeeds(0);
	}

	if (--maxCount == 0)
	    break;
    }

    bextfree(PipeAry[fd]);	/* don't keep large buffers around */

    if (ptr == (void *)-1) {
	if (PipeAry[fd] != NULL) {
	    bclose(PipeAry[fd], 0);
	    PipeAry[fd] = NULL;
	}
	close(fd);
	FD_CLR(fd, &RFds);
	--NumForks;
    }
}

/*
 * WRITEFEEDOUT()	- the master parent writes to the outgoing feed files
 * FLUSHFEEDOUT()
 */

void
writeFeedOut(const char *label, const char *file, const char *msgid, const char *offSize, int rt, int headOnly, const char *artType, const char *cSize)
{
    Feed *fe;
    char delayTime[16];

    /*
     * locate feed
     */

    for (fe = FeBase; fe; fe = fe->fe_Next) {
	if (strcmp(label, fe->fe_Label) == 0)
	    break;
    }

    /*
     * allocate feed if not found
     */

    if (fe == NULL) {
	fe = zalloc(&ParProcMemPool, sizeof(Feed) + strlen(label) + 1);
	fe->fe_Label = (char *)(fe + 1);
	strcpy(fe->fe_Label, label);
	if (strchr(label, '/') != NULL) {
	    char buf[PATH_MAX];
	    char *p;
	    snprintf(buf, sizeof(buf), "%s/%s", PatExpand(DQueueHomePat), label);
	    if ((p = strrchr(buf, '/')) != NULL) {
		struct stat st;
		*p = 0;
		if (stat(buf, &st) != 0 && mkdir(buf, 0755) != 0)
		    logit(LOG_ERR, "Unable to create dqueue path for %s (%s)",
							label, strerror(errno));
	    }
	    snprintf(buf, sizeof(buf), "%s/.%s", PatExpand(DQueueHomePat), label);
	    if ((p = strrchr(buf, '/')) != NULL) {
		struct stat st;
		*p = 0;
		if (stat(buf, &st) != 0 && mkdir(buf, 0755) != 0)
		    logit(LOG_ERR, "Unable to create dqueue path for %s (%s)",
							label, strerror(errno));
	    }
	}
	fe->fe_Fd = xopen(O_APPEND|O_RDWR|O_CREAT, 0644, "%s/%s",
					PatExpand(DQueueHomePat), label);
	if (fe->fe_Fd >= 0) {
	    int bsize;

	    fe->fe_Buf = pagealloc(&bsize, 1);
	    fe->fe_BufMax = bsize;
	    fe->fe_BufIdx = 0;
	    fe->fe_Failed = 0;
	    fe->fe_Delayed = IsDelayed(label);
	    fe->fe_NotifyFd = -1;
#if 0
	    if (1) {
		int fds[2];
		char *argv[9] = { NULL };
		argv[0] = "/news/dbin/dnewslink";
		argv[1] = "dnewslink";
		argv[2] = "-bzz";
		argv[3] = "-p";
		argv[4] = "-h127.0.0.1";
		argv[5] = "-P8119";
		argv[6] = "-r-1";
		argv[7] = NULL;
		if (RunProgramPipe(fds, RPF_STDOUT, argv, NULL) > 0) {
		    fe->fe_PipeFd = fds[1];
		} else {
		    logit(LOG_ERR, "Unable to start feed pipe");
		}
	    }
#endif
	} else {
	    logit(LOG_ERR, "Unable to open/create dqueue entry for %s (%s)",
							label, strerror(errno));
	}
	fe->fe_Next = FeBase;
	FeBase = fe;
    }

    /*
     * write to buffered feed, flushing buffers if there is not enough
     * room.  If the line is too long, something has gone wrong and we
     * throw it away.
     *
     * note that we cannot fill the buffer to 100%, because the trailing
     * nul (which we do not write) will overrun it.  I temporarily add 4 to
     * l instead of 3 to include the trailing nul in the calculations, but
     * subtract it off after the actual copy operation.
     */

    if (fe->fe_Fd >= 0) {
	int l = strlen(file) + strlen(msgid) + strlen(offSize) + (3 + 32 + 1);

	/*
	 * line would be too long?
	 */
	if (l < fe->fe_BufMax) {
	    /*
	     * line fits in buffer with trailing nul ?
	     */
	    if (l >= fe->fe_BufMax - fe->fe_BufIdx)
		flushFeedOut(fe);

	    if (fe->fe_Delayed)
		sprintf(delayTime, " D%d", (int)time(NULL));
	    else
		delayTime[0] = 0;
	    sprintf(fe->fe_Buf + fe->fe_BufIdx, "%s %s %s%s%s%s%s\n",
		file, msgid, offSize, 
		(headOnly ? " H" : ""),
		delayTime,
		cSize != NULL ? " C" : "",
		cSize != NULL ? cSize : ""
	    );
	    fe->fe_BufIdx += strlen(fe->fe_Buf + fe->fe_BufIdx);
	    if (rt)
		flushFeedOut(fe);
	}
    }
}

void
flushFeeds(int justClose)
{
    Feed *fe;

    while ((fe = FeBase) != NULL) {
	if (justClose == 0)
	    flushFeedOut(fe);
	if (fe->fe_Buf)
	    pagefree(fe->fe_Buf, 1);
	if (fe->fe_Fd >= 0)
	    close(fe->fe_Fd);
	fe->fe_Fd = -1;
	if (fe->fe_NotifyFd >= 0)
	    close(fe->fe_NotifyFd);
	fe->fe_NotifyFd = -1;
	fe->fe_Buf = NULL;
	FeBase = fe->fe_Next;
	zfree(&ParProcMemPool, fe, sizeof(Feed) + strlen(fe->fe_Label) + 1);
    }
}

void
flushFeedOut(Feed *fe)
{
    if (fe->fe_BufIdx && fe->fe_Buf && fe->fe_Fd >= 0) {
	/*
	 * flush buffer.  If the write fails, we undo it to ensure
	 * that we do not get garbaged feed files.
	 */
	int n = write(fe->fe_Fd, fe->fe_Buf, fe->fe_BufIdx);
	if (fe->fe_NotifyFd != -1) {
	    if (write(fe->fe_NotifyFd, &n, 1) < 0 && errno != EAGAIN) {
		close(fe->fe_NotifyFd);
		fe->fe_NotifyFd = -1;
	    }
	}
	if (n >= 0 && n != fe->fe_BufIdx) {
	    ftruncate(fe->fe_Fd, lseek(fe->fe_Fd, 0L, 1) - n);
	}
	if (n != fe->fe_BufIdx && fe->fe_Failed == 0) {
	    fe->fe_Failed = 1;
	    logit(LOG_INFO, "failure writing to feed %s", fe->fe_Label);
	}
    }
    fe->fe_BufIdx = 0;
}

/*
 * DOSESSION()	- a child process to handle a diablo connection
 */

void
DoSession(int fd, int count)
{
    Buffer *bi;
    FILE *fo;
    char *buf;
    int streamMode = 0;
#ifdef	USE_ZLIB
    int compressMode = 0;
#endif
    int headerOnly = 0;
    int syntax = 0;
    int unimp = 0;

    /*
     * reinitialize random generator
     */

    srandom((int32)random() ^ (getpid() * 100) ^ (int32)time(NULL));

    /*
     * Fixup pipe
     */

    {
	int nfd = dup(fd);
	if (nfd < 0) {
	    logit(LOG_CRIT, "DoSession: dup()");
	    exit(1);
	}
	bi = bopen(nfd, DOpts.FeederBufferSize);
	fo = fdopen(fd, "w");

	if (bi == NULL || fo == NULL) {
	    logit(LOG_CRIT, "DoSession() bopen failure");
	    exit(1);
	}
    }
    if (PausedCount) {
	if (DOpts.DisplayAdminVersion && DOpts.NewsAdmin != NULL)
	    xfprintf(fo, "502 %s: System currently paused - %s (DIABLO %s-%s)\r\n",
						DOpts.FeederHostName,
						DOpts.NewsAdmin,
						VERS, SUBREV);
	else
	    xfprintf(fo, "502 %s: System currently paused\r\n",
						DOpts.FeederHostName);
	fflush(fo);
	exit(0);
    }

    LoadNewsFeed(0, 1, HLabel);	/* free old memory and load only our label */

    if (ReadOnlyMode && !FeedReadOnly(HLabel)) {
	xfprintf(fo, "502 %s DIABLO is currently in read-only mode\r\n",
		DOpts.FeederHostName);
	fflush(fo);
	exit(0);
    }

    HistoryOpen(NULL, 0);

    switch(FeedValid(HLabel, &count)) {
    case FEED_VALID:
	break;
    case FEED_MAXCONNECT:
	xfprintf(fo, "502 %s DIABLO parallel connection limit is %d\r\n",
	    DOpts.FeederHostName,
	    count
	);
	logit(
	    LOG_WARNING, 
	    "Connect Limit exceeded (from dnewsfeeds) for %s", HName
	);
	fflush(fo);
	exit(0);
	/* not reached */
    case FEED_MISSINGLABEL:
	if (DOpts.DisplayAdminVersion && DOpts.NewsAdmin != NULL)
	    xfprintf(fo, "502 %s DIABLO misconfiguration, label missing in dnewsfeeds, contact %s\r\n",
						DOpts.FeederHostName,
						DOpts.NewsAdmin);
	else
	    xfprintf(fo, "502 %s DIABLO misconfiguration, label missing in dnewsfeeds\r\n",
						DOpts.FeederHostName);
	logit(LOG_CRIT, "Diablo misconfiguration, label %s not found in dnewsfeeds", HLabel);
	fflush(fo);
	exit(0);
	/* not reached */
    }

    ArticleFileInit();		/* initialize article file cache	   */
    ReadOnlyCxn = FeedReadOnly(HLabel);
				/* force read-only?			   */

    if (DOpts.FeederActiveEnabled && (DOpts.FeederXRefSync || DOpts.FeederXRefSlave == 0))
	InitDActive(ServerDActivePat); /* initialize dactive.kp if enabled   */

    bzero(&Stats, sizeof(Stats));
    switch (DOpts.FeederRTStats) {
	case RTSTATS_NONE:
		HostStats = NULL;
		break;
	case RTSTATS_LABEL:
		HostStats = FeedStatsFindSlot(HLabel);
		break;
	case RTSTATS_HOST:
		HostStats = FeedStatsFindSlot(HName);
		break;
    }
    if (HostStats != NULL) {
	++HostStats->RecStats.ConnectCnt;
	if (HostStats->RecStats.TimeStart == 0)
	    HostStats->RecStats.TimeStart = time(NULL);
	LockFeedRegion(HostStats, XLOCK_UN, FSTATS_IN);
	LockFeedRegion(HostStats, XLOCK_EX, FSTATS_SPOOL);
	++HostStats->SpoolStats.ConnectCnt;
	if (HostStats->SpoolStats.TimeStart == 0)
	    HostStats->SpoolStats.TimeStart = time(NULL);
	LockFeedRegion(HostStats, XLOCK_UN, FSTATS_SPOOL);
    }
    bzero(&StoreStats, sizeof(StoreStats));

    /*
     * print header, start processing commands
     */

    if (DOpts.DisplayAdminVersion && DOpts.NewsAdmin != NULL)
	xfprintf(fo, "%d %s NNTP Service Ready - %s (DIABLO %s-%s)\r\n",
					ReadOnlyCxn ? 201 : 200,
					DOpts.FeederHostName,
					DOpts.NewsAdmin,
					VERS, SUBREV);
    else
	xfprintf(fo, "%d %s NNTP Service Ready\r\n",
					ReadOnlyCxn ? 201 : 200,
					DOpts.FeederHostName);
    fflush(fo);

    while ((buf = bgets(bi, NULL)) != NULL && buf != (char *)-1) {
	char *cmd;


	if (DebugOpt > 2) {
	    ddprintf("%d << %s", (int)getpid(), buf);
	}

	cmd = strtok(buf, " \t\r\n");

	if (cmd == NULL)
	    continue;

	if (ReadOnlyCxn && (strcasecmp(cmd, "ihave") == 0)) {
	    xfprintf(fo, "480 Read-only connection.\r\n");
	} else if (strcasecmp(cmd, "ihave") == 0) {
	    const char *msgidbuf = "";
	    const char *msgid = MsgId(strtok(NULL, "\r\n"), &msgidbuf);
	    int pre = 0;

	    TtlStats.ArtsTested += 1.0;
	    DoArtStats(STATS_OFFERED, STATS_IHAVE, 0);

	    /*
	     * The PreCommit cache also doubles as a recent history 'hit'
	     * cache, so check it first.
	     */

	    if (strcmp(msgid, "<>") == 0) {
		xfprintf(fo, "435 %s Bad Message-ID\r\n", msgidbuf);
	    } else if (ArtHashIsFiltered(msgid)) {
		xfprintf(fo, "435 %s\r\n", msgid);
		DoArtStats(STATS_REFUSED, STATS_REF_HISTORY, 0);
	    } else
#if DO_PCOMMIT_POSTCACHE
	    if ((pre = PreCommit(msgid, PC_PRECOMM)) < 0) {
		if (pre < -1 && !FeedPrecommitReject(HLabel)) {
		    xfprintf(fo, "436 %s\r\n", msgid);
		    DoArtStats(STATS_REFUSED, STATS_REF_PRECOMMIT, 0);
		} else {
		    xfprintf(fo, "435 %s\r\n", msgid);
		    DoArtStats(STATS_REFUSED, STATS_REF_POSTCOMMIT, 0);
		}
	    } else if (HistoryLookup(msgid, NULL) == 0) {
#if USE_PCOMMIT_RW_MAP
		(void)PreCommit(msgid, PC_POSTCOMM);
#endif
		xfprintf(fo, "435 %s\r\n", msgid);
		DoArtStats(STATS_REFUSED, STATS_REF_HISTORY, 0);
#else /* !DO_PCOMMIT_POSTCACHE */
	    if (HistoryLookup(msgid, NULL) == 0
	    ) {
		xfprintf(fo, "435 %s\r\n", msgid);
		DoArtStats(STATS_REFUSED, STATS_REF_HISTORY, 0);
	    } else if ((pre = PreCommit(msgid, PC_PRECOMM)) < 0) {
		if (pre < -1 && !FeedPrecommitReject(HLabel)) {
		    xfprintf(fo, "436 %s\r\n", msgid);
		    DoArtStats(STATS_REFUSED, STATS_REF_PRECOMMIT, 0);
		} else {
		    xfprintf(fo, "435 %s\r\n", msgid);
		    DoArtStats(STATS_REFUSED, STATS_REF_POSTCOMMIT, 0);
		}
#endif  /* DO_PCOMMIT_POSTCACHE */
#if USE_OFFER_FILTER
	    } else if (OfferIsFiltered(HLabel, msgid)) {
		xfprintf(fo, "435 %s Unwanted\r\n", msgidbuf);
		DoArtStats(STATS_REFUSED, STATS_REF_BADMSGID, 0);
#endif  /* USE_OFFER_FILTER */
	    } else {	/* Not found in history or pre/post commit cache */
		int r;
		char rejMsg[REJMSGSIZE];
		char artType[10] = "";

		rejMsg[0] = 0;

		xfprintf(fo, "335 %s\r\n", msgid);
		fflush(fo);
		switch((r = LoadArticle(bi, msgid, 0, headerOnly, rejMsg, artType))) {
		case RCOK:
		    xfprintf(fo, "235\r\n");	/* article posted ok	*/
		    break;
		case RCALREADY:
		    /*
		     * see RELEASE_NOTES V1.16-test8.  435 changed to 437.
		     */
		    LogIncoming("%s - %s %s", HLabel, msgid, "Duplicate");
		    xfprintf(fo, "437 Duplicate\r\n");	/* already have it */
#ifdef NOTDEF
		    xfprintf(fo, "435\r\n");	/* already have it	*/
#endif
		    break;
		case RCTRYAGAIN:
		    LogIncoming("%s - %s %s", HLabel, msgid, rejMsg);
		    xfprintf(fo, "436 I/O error, try again later\r\n");
		    break;
		case RCREJECT:
		    LogIncoming("%s - %s %s", HLabel, msgid, rejMsg);
		    xfprintf(fo, "437 Rejected %s\r\n", rejMsg);
		    break;
		case RCERROR:
		    /*
		     * protocol error during transfer (e.g. no terminating .)
		     */
		    LogIncoming("%s - %s %s", HLabel, msgid, rejMsg);
		    xfprintf(fo, "436 Protocol error, missing terminator\r\n");
		    break;
		default:
		    /*
		     * An I/O error of some sort (e.g. disk full).
		     */
		    LogIncoming("%s - %s %s", HLabel, msgid, strerror(-r));
                    logit(LOG_ERR, "%-20s %s code 400 File Error: %s",
                        HName,
                        msgid,
                        strerror(-r)
                    ); 
		    LogSession();
		    sleep(30);	/* reduce remote reconnection rate */
		    xfprintf(fo, "400 File Error: %s\r\n", strerror(-r));
		    fflush(fo);
		    ArticleFileCloseAll();
		    ClosePathLog(1);
		    CloseArtLog(1);
		    DiabFilterClose();
		    sleep(5);
		    exit(1);
		    break;	/* not reached */
		}
	    }
	} else if (ReadOnlyCxn && (strcasecmp(cmd, "check") == 0)) {
	    xfprintf(fo, "480 Read-only connection.\r\n");
	} else if (strcasecmp(cmd, "check") == 0) {
	    const char *msgidbuf = "";
	    const char *msgid = MsgId(strtok(NULL, "\r\n"), &msgidbuf);
	    int pre = 0;

	    TtlStats.ArtsTested += 1.0;
	    DoArtStats(STATS_OFFERED, STATS_CHECK, 0);

	    /*
	     * The PreCommit cache may also double as a recent history 'hit'
	     * cache, so check it first in that case. 
	     */

	    if (strcmp(msgid, "<>") == 0) {
		xfprintf(fo, "438 %s Bad Message-ID\r\n", msgidbuf);
		DoArtStats(STATS_REFUSED, STATS_REF_BADMSGID, 0);
	    } else if (ArtHashIsFiltered(msgid)) {
		xfprintf(fo, "438 %s\r\n", msgid);
		DoArtStats(STATS_REFUSED, STATS_REF_HISTORY, 0);
	    } else
#if DO_PCOMMIT_POSTCACHE
	    if ((pre = PreCommit(msgid, PC_PRECOMM)) < 0) {
		if (pre < -1 && !FeedPrecommitReject(HLabel)) {
		    xfprintf(fo, "431 %s\r\n", msgid);
		    DoArtStats(STATS_REFUSED, STATS_REF_PRECOMMIT, 0);
		} else {
		    xfprintf(fo, "438 %s\r\n", msgid);
		    DoArtStats(STATS_REFUSED, STATS_REF_POSTCOMMIT, 0);
		}
	    } else if (HistoryLookup(msgid, NULL) == 0) {
#if USE_PCOMMIT_RW_MAP
		(void)PreCommit(msgid, PC_POSTCOMM);
#endif
		xfprintf(fo, "438 %s\r\n", msgid);
		DoArtStats(STATS_REFUSED, STATS_REF_HISTORY, 0);
#else  /* !DO_PCOMMIT_POSTCACHE */
	    if (HistoryLookup(msgid, NULL) == 0
	    ) {
		xfprintf(fo, "438 %s\r\n", msgid);
		(void)PreCommit(msgid, PC_POSTCOMM);
		DoArtStats(STATS_REFUSED, STATS_REF_HISTORY, 0);
	    } else if ((pre = PreCommit(msgid, PC_PRECOMM)) < 0) {
		if (pre < -1 && !FeedPrecommitReject(HLabel)) {
		    xfprintf(fo, "431 %s\r\n", msgid);
		    DoArtStats(STATS_REFUSED, STATS_REF_PRECOMMIT, 0);
		} else {
		    xfprintf(fo, "438 %s\r\n", msgid);
		    DoArtStats(STATS_REFUSED, STATS_REF_POSTCOMMIT, 0);
		}
#endif  /* DO_PCOMMIT_POSTCACHE */
#if USE_OFFER_FILTER
	    } else if (OfferIsFiltered(HLabel, msgid)) {
		xfprintf(fo, "438 %s Unwanted\r\n", msgidbuf);
		DoArtStats(STATS_REFUSED, STATS_REF_BADMSGID, 0);
#endif  /* USE_OFFER_FILTER */
	    } else {
		xfprintf(fo, "238 %s\r\n", msgid);
	    }
	} else if (ReadOnlyCxn && (strcasecmp(cmd, "takethis") == 0)) {
	    xfprintf(fo, "480 Read-only connection.\r\n");
	} else if (strcasecmp(cmd, "takethis") == 0) {
	    const char *msgid = MsgId(strtok(NULL, "\r\n"), NULL);
	    int r;
	    int alreadyResponded = 0;
	    char rejMsg[REJMSGSIZE];
	    char artType[10] = "";

	    rejMsg[0] = 0;

	    TtlStats.ArtsTested += 1.0;
	    DoArtStats(0, STATS_TAKETHIS, 0);
	    if (ArtHashIsFiltered(msgid) || HistoryLookup(msgid, NULL) == 0) {
		xfprintf(fo, "439 %s\r\n", msgid);
		fflush(fo);
		LoadArticle(bi, msgid, 1, headerOnly, NULL, NULL);
		r = RCALREADY;
		alreadyResponded = 1;
	    } else {
		r = LoadArticle(bi, msgid, 0, headerOnly, rejMsg, artType);
	    }

	    if (alreadyResponded == 0) {
		switch(r) {
		case RCOK:
		    xfprintf(fo, "239 %s\r\n", msgid);	/* thank you */
		    break;
		case RCALREADY:
		    /* already have it or do not requeue it */
		    LogIncoming("%s - %s %s", HLabel, msgid, rejMsg);
		    xfprintf(fo, "439 %s %s\r\n", msgid, rejMsg);
		    break;
		case RCTRYAGAIN:
		    LogIncoming("%s - %s %s", HLabel, msgid, rejMsg);
		    xfprintf(fo, "431 %s %s\r\n", msgid, rejMsg);
		    break;
		case RCREJECT:
		    LogIncoming("%s - %s %s", HLabel, msgid, rejMsg);
		    xfprintf(fo, "439 %s %s\r\n", msgid, rejMsg);
		    break;
		case RCERROR:
		    /* article failed due to something, do not req */
		    LogIncoming("%s - %s %s", HLabel, msgid, rejMsg);
		    xfprintf(fo, "431 %s %s\r\n", msgid, rejMsg);
		    break;
		default:
		    /*
		     * An I/O error of some sort (e.g. disk full).
		     */
		    strcat(rejMsg, ", ");
		    strcat(rejMsg, strerror(-r));
		    LogIncoming("%s - %s %s", HLabel, msgid, rejMsg);
		    logit(LOG_ERR, "%-20s %s code 400 File Error: %s",
			HName,
			msgid,
			strerror(-r)
		    ); 
		    LogSession();
		    sleep(30);	/* reduce remote reconnection rate */
		    xfprintf(fo, "400 File Error: %s %s, %s\r\n", msgid, rejMsg, strerror(-r));
		    fflush(fo);
		    ArticleFileCloseAll();
		    ClosePathLog(1);
		    CloseArtLog(1);
		    DiabFilterClose();
		    sleep(5);
		    exit(1);
		    break;	/* not reached */
		}
	    }
	} else if (strcasecmp(cmd, "whereis") == 0 && FeedWhereIs(HLabel)) {
	    const char *msgid = MsgId(strtok(NULL, " \t\r\n"), NULL);
	    const char *how = strtok(NULL, " \t\r\n");
	    History h;

	    if (strcmp(msgid, "<>") == 0) {
		xfprintf(fo, "443 Bad Message-ID\r\n");
	    } else if (HistoryLookup(msgid, &h) == 0 && !H_EXPIRED(h.exp)) {
		char path[PATH_MAX];
		if (how != NULL && strncmp(how, "REL", 3) == 0)
		    ArticleFileName(path, sizeof(path), &h, ARTFILE_FILE_REL);
		else
		    ArticleFileName(path, sizeof(path), &h, ARTFILE_FILE);
		xfprintf(fo, "223 0 whereis %s in %s offset %i length %i\r\n", msgid, path, h.boffset, h.bsize) ;
	    } else {
		if (H_EXPIRED(h.exp) && h.iter != (unsigned short)-1) {
		    xfprintf(fo, "430 Article expired\r\n");
		} else {
		    xfprintf(fo, "430 Article not found\r\n");
		}
	    }
	} else if (strcasecmp(cmd, "xrectime") == 0) {
	    const char *msgid = MsgId(strtok(NULL, " \t\r\n"), NULL);
	    History h;

	    if (strcmp(msgid, "<>") == 0) {
		xfprintf(fo, "443 Bad Message-ID\r\n");
	    } else if (HistoryLookup(msgid, &h) == 0 && !H_EXPIRED(h.exp)) {
		xfprintf(fo, "223 %d\r\n", h.gmt * 60);
	    } else {
		if (H_EXPIRED(h.exp) && h.iter != (unsigned short)-1) {
		    xfprintf(fo, "430 Article expired\r\n");
		} else {
		    xfprintf(fo, "430 Article not found\r\n");
		}
	    }
	} else if (strcasecmp(cmd, "head") == 0 ||
		   strcasecmp(cmd, "body") == 0 ||
		   strcasecmp(cmd, "article") == 0
	) {
	    const char *msgid = MsgId(strtok(NULL, "\r\n"), NULL);
	    char *data = NULL;
	    int32 fsize = 0;
	    int pmart = 0;
	    int headOnly = 0;
	    int compressed = 0;
	    History h;
	    enum ArtState { AS_ARTICLE, AS_BODY, AS_HEAD } as = AS_HEAD;

	    switch(cmd[0]) {
	    case 'b':
	    case 'B':
		as = AS_BODY;
		break;
	    case 'a':
	    case 'A':
		as = AS_ARTICLE;
		break;
	    default:	/* default, must be AS_HEAD */
		as = AS_HEAD;
		break;
	    }
	    h.exp = 0;
	    if (strcmp(msgid, "<>") == 0) {
		xfprintf(fo, "443 Bad Message-ID\r\n");
		switch(as) {
		case AS_BODY:
		    DoSpoolStats(STATS_S_BODYERR);
		    break;
		case AS_ARTICLE:
		    DoSpoolStats(STATS_S_ARTICLEERR);
		    break;
		default:
		    DoSpoolStats(STATS_S_HEADERR);
		    break;
		}
	    } else if (HistoryLookup(msgid, &h) == 0 && !H_EXPIRED(h.exp)) {
		if (ArticleOpen(&h, msgid, &data, &fsize, &pmart, &headOnly, &compressed) != 0)
		    data = NULL;
		if (data && headOnly && as != AS_HEAD) {
		    xfprintf(fo, "430 Article not found\r\n");
		    switch(as) {
		    case AS_BODY:
			DoSpoolStats(STATS_S_BODYMISS);
			break;
		    case AS_ARTICLE:
			DoSpoolStats(STATS_S_ARTICLEMISS);
			break;
		    default:
			DoSpoolStats(STATS_S_HEADMISS);
			break;
		    }
		    if (DebugOpt > 2)
			ddprintf(">> (NO DATA: BODY/ARTICLE REQUEST FOR HEADER-ONLY STORE)");
		} else if (data) {
		    int doHead = 0;
		    int doBody = 0;
		    int bytes;

#ifdef	STATS_ART_AGE
		    logit(LOG_INFO, "articleage %s %d", msgid, (int)(time(NULL)) - h.gmt * 60);
#endif	/*STATS_ART_AGE*/

		    switch(as) {
		    case AS_BODY:
			xfprintf(fo, "222 0 body %s\r\n", msgid);
			doBody = 1;
			DoSpoolStats(STATS_S_BODY);
			break;
		    case AS_ARTICLE:
			xfprintf(fo, "220 0 article %s\r\n", msgid);
			doHead = 1;
			doBody = 1;
			DoSpoolStats(STATS_S_ARTICLE);
			break;
		    default:
			doHead = 1;
			xfprintf(fo, "221 0 head %s\r\n", msgid);
			DoSpoolStats(STATS_S_HEAD);
			break;
		    }
		    if (doBody && !compressed) {
			if (DOpts.SpoolPreloadArt)
			    xadvise(data, fsize, XADV_WILLNEED);
			xadvise(data, fsize, XADV_SEQUENTIAL);
		    }

		    if (DebugOpt > 2)
			ddprintf(">> (DATA)");

		    bytes = SendArticle(data, fsize, fo, doHead, doBody);

		    Stats.SpoolStats.ArtsBytesSent += (double)bytes;
		    if (HostStats != NULL) {
			LockFeedRegion(HostStats, XLOCK_EX, FSTATS_SPOOL);
			HostStats->SpoolStats.ArtsBytesSent += (double)bytes;
			LockFeedRegion(HostStats, XLOCK_UN, FSTATS_SPOOL);
		    }
		} else {
		    xfprintf(fo, "430 Article not found\r\n");
		    switch(as) {
		    case AS_BODY:
			DoSpoolStats(STATS_S_BODYEXP);
			break;
		    case AS_ARTICLE:
			DoSpoolStats(STATS_S_ARTICLEEXP);
			break;
		    default:
			DoSpoolStats(STATS_S_HEADEXP);
			break;
		    }
		    if (DebugOpt > 2)
			ddprintf(">> (NO DATA: UNABLE TO FIND ARTICLE)");
		}
	    } else {
		if (H_EXPIRED(h.exp) && h.iter != (unsigned short)-1) {
		    xfprintf(fo, "430 Article expired\r\n");
		    switch(as) {
		    case AS_BODY:
			DoSpoolStats(STATS_S_BODYEXP);
			break;
		    case AS_ARTICLE:
			DoSpoolStats(STATS_S_ARTICLEEXP);
			break;
		    default:
			DoSpoolStats(STATS_S_HEADEXP);
			break;
		    }
		} else {
		    xfprintf(fo, "430 Article not found\r\n");
		    switch(as) {
		    case AS_BODY:
			DoSpoolStats(STATS_S_BODYMISS);
			break;
		    case AS_ARTICLE:
			DoSpoolStats(STATS_S_ARTICLEMISS);
			break;
		    default:
			DoSpoolStats(STATS_S_HEADMISS);
			break;
		    }
		}
	    }
	    if (data) {
		if (compressed)
		    free(data);
		else
		    xunmap(data, fsize + pmart);
	    }
	} else if (strcasecmp(cmd, "stat") == 0) {
	    const char *msgid = MsgId(strtok(NULL, "\r\n"), NULL);
	    History h;

	    h.exp = 0;
	    if (strcmp(msgid, "<>") == 0) {
		xfprintf(fo, "443 Bad Message-ID\r\n");
		DoSpoolStats(STATS_S_STATERR);
	    } else if (HistoryLookup(msgid, &h) == 0 && !H_EXPIRED(h.exp)) {
		char path[128];
		struct stat st;

		/*
		 * Make sure article file hasn't been removed
		 */
		ArticleFileName(path, sizeof(path), &h, ARTFILE_FILE);
		if (h.bsize == 0)  {
		    xfprintf(fo, "430 Article not found\r\n");
		    DoSpoolStats(STATS_S_STATEXP);
		} else if (stat(path, &st) == 0) {
		    xfprintf(fo, "223 0 %s\r\n", msgid);
		    DoSpoolStats(STATS_S_STAT);
		} else {
		    xfprintf(fo, "430 Article expired\r\n");
		    DoSpoolStats(STATS_S_STATEXP);
		}
	    } else {
		if (H_EXPIRED(h.exp) && h.iter != (unsigned short)-1) {
		    xfprintf(fo, "430 Article expired\r\n");
		    DoSpoolStats(STATS_S_STATEXP);
		} else {
		    xfprintf(fo, "430 Article not found\r\n");
		    DoSpoolStats(STATS_S_STATMISS);
		}
	    }
	} else if (strcasecmp(cmd, "feedrset") == 0) {
	    if (HLabel[0]) {
		FeedRSet(fo);
	    } else {
		xfprintf(fo, "490 Operation not allowed\r\n");
	    }
	} else if (strcasecmp(cmd, "feedlist") == 0) {
	    if (HLabel[0]) {
		FeedList(fo);
	    } else {
		xfprintf(fo, "490 Operation not allowed\r\n");
	    }
	} else if (strcasecmp(cmd, "feedcommit") == 0) {
	    if (HLabel[0]) {
		if (FeedTableReady)
		    FeedCommit(fo);
		else
		    xfprintf(fo, "491 No feedrset/add/del\r\n");
	    } else {
		xfprintf(fo, "490 Operation not allowed\r\n");
	    }
	} else if (strcasecmp(cmd, "feedadd") == 0) {
	    char *p = strtok(NULL, " \t\r\n");
	    if (p) {
		if (HLabel[0]) {
		    if (FeedTableReady)
			FeedAddDel(fo, p, 1);
		    else
			xfprintf(fo, "491 No feedrset\r\n");
		} else {
		    xfprintf(fo, "490 Operation not allowed\r\n");
		}
	    } else {
		xfprintf(fo, "491 Syntax Error\r\n");
	    }
	} else if (strcasecmp(cmd, "feeddel") == 0) {
	    char *p = strtok(NULL, " \t\r\n");
	    if (p) {
		if (HLabel[0]) {
		    if (FeedTableReady)
			FeedAddDel(fo, p, -1);
		    else
			xfprintf(fo, "491 No feedrset\r\n");
		} else {
		    xfprintf(fo, "490 Operation not allowed\r\n");
		}
	    } else {
		xfprintf(fo, "491 Syntax Error\r\n");
	    }
	} else if (strcasecmp(cmd, "feeddelany") == 0) {
	    char *p = strtok(NULL, " \t\r\n");
	    if (p) {
		if (HLabel[0]) {
		    if (FeedTableReady)
			FeedAddDel(fo, p, -2);
		    else
			xfprintf(fo, "491 No feedrset\r\n");
		} else {
		    xfprintf(fo, "490 Operation not allowed\r\n");
		}
	    } else {
		xfprintf(fo, "491 Syntax Error\r\n");
	    }
	} else if (strcasecmp(cmd, "mode") == 0) {
	    char *p = strtok(NULL, " \t\r\n");
	    if (p && strcasecmp(p, "stream") == 0) {
		streamMode = 1;
		xfprintf(fo, "203 StreamOK.\r\n");
	    } else if (p && strcasecmp(p, "headfeed") == 0) {
		headerOnly = 1;
		xfprintf(fo, "250 Mode Command OK.\r\n");
	    } else if (p && strcasecmp(p, "readonly") == 0) {
		ReadOnlyCxn = 1;
		xfprintf(fo, "250 Mode Command OK.\r\n");
	    } else if (p && strcasecmp(p, "artfeed") == 0) {
		headerOnly = 0;
		xfprintf(fo, "250 Mode Command OK.\r\n");
	    } else if (p && strcasecmp(p, "reader") == 0) {
		unimp = 1;
#ifdef	USE_ZLIB
	    } else if (p && strcasecmp(p, "compress") == 0) {
		/* added for compression support */
		bi->bu_CBuf = (CompressBuffer *)malloc(sizeof(CompressBuffer));
                bzero(bi->bu_CBuf,sizeof(CompressBuffer));
                inflateInit(&bi->bu_CBuf->z_str);
                compressMode = 1;
		xfprintf(fo, "207 Compression enabled\r\n");
#endif
	    } else {
		syntax = 1;
	    }
	} else if (strcasecmp(cmd, "outq") == 0) {
	    int qnum;
	    int qarts;
	    int qrun;

	    if (HLabel[0] && QueueRange(HLabel, &qnum, &qarts, &qrun) == 0) {
		xfprintf(fo, "290 qfile-backlog=%d arts=%d now-running=%d\r\n", 
		    qnum,
		    qarts,
		    qrun
		);
	    } else {
		xfprintf(fo, "491 No queue info available\r\n");
	    }
	} else if (strcasecmp(cmd, "authinfo") == 0) {
	    xfprintf(fo, "281 Authentication ok, no authentication required\r\n");
/*
	} else if (strcasecmp(cmd, "stats") == 0) {
	    int dt = (int)(time(NULL) - SessionBeg);
	    DoStats(fo, dt, 0);
	    fflush(fo);
	    break;
*/
	} else if (strcasecmp(cmd, "quit") == 0) {
	    xfprintf(fo, "205 %s closing channel.\r\n", DOpts.FeederHostName);
	    fflush(fo);
	    break;
	} else if (strcasecmp(cmd, "help") == 0) {
	    xfprintf(fo, "100 Legal commands\r\n");
	    xfprintf(fo, "\tauthinfo\r\n"
			"\thelp\r\n"
			"\tihave\r\n"
			"\tcheck\r\n"
			"\ttakethis\r\n"
			"\tmode\r\n"
			"\tquit\r\n"
			"\thead\r\n"
			"\tstat\r\n"
			"\toutq\r\n"
			"\tfeedrset\r\n"
			"\tfeedadd grpwildcard\r\n"
			"\tfeeddel grpwildcard\r\n"
			"\tfeeddelany grpwildcard\r\n"
			"\tfeedlist\r\n"
			"\tfeedcommit\r\n"
	    );
	    xfprintf(fo, ".\r\n");
	} else {
	    syntax = 1;
	    if (strcasecmp(cmd, "list") == 0)
		unimp = 1;
	    if (strcasecmp(cmd, "group") == 0)
		unimp = 1;
	    if (strcasecmp(cmd, "last") == 0)
		unimp = 1;
	    if (strcasecmp(cmd, "newgroups") == 0)
		unimp = 1;
	    if (strcasecmp(cmd, "newnews") == 0)
		unimp = 1;
	    if (strcasecmp(cmd, "next") == 0)
		unimp = 1;
	    if (strcasecmp(cmd, "post") == 0)
		unimp = 1;
	    if (strcasecmp(cmd, "slave") == 0)
		unimp = 1;
	    if (strcasecmp(cmd, "xhdr") == 0)
		unimp = 1;
	    if (strcasecmp(cmd, "xpath") == 0)
		unimp = 1;
	    if (strcasecmp(cmd, "xreplic") == 0)
		unimp = 1;
	    if (unimp)
		syntax = 0;
	}
	if (unimp) {
	    xfprintf(fo, "500 command not implemented\r\n");
	    unimp = 0;
	}
	if (syntax) {
	    xfprintf(fo, "500 Syntax error or bad command\r\n");
	    syntax = 0;
	}
	fflush(fo);
	if (HasStatusLine) {
	    stprintf("ihav=%-4d chk=%-4d rec=%-4d ent=%-4d %s",
		Stats.RecStats.Stats[STATS_IHAVE],
		Stats.RecStats.Stats[STATS_CHECK],
		Stats.RecStats.Stats[STATS_OFFERED],
		Stats.RecStats.Stats[STATS_ACCEPTED],
		HName
	    );
	}
    }

#ifdef	USE_ZLIB
    if (bi->bu_CBuf) {
	logit(LOG_INFO, "%-20s compbytes=%.0f decompbytes=%.0f (%.2f%% compression)",
			HName,
			bi->bu_CBuf->orig,
			bi->bu_CBuf->decomp,
			100 - ((bi->bu_CBuf->orig/bi->bu_CBuf->decomp)*100)
	);
    }
#endif

    bclose(bi, 1);
    fclose(fo);
    ArticleFileCloseAll();
}

/*
 * Send a mmap'ed article to a FILE, doing conversion if necessary
 */
int
SendArticle(const char *data, int fsize, FILE *fo, int doHead, int doBody)
{
    int b;
    int i;
    int inHeader = 1;
    int bytes = 0;
    SpoolArtHdr ah;

    bcopy(data, &ah, sizeof(ah));

    if (fsize > 24 && (uint8)ah.Magic1 == STORE_MAGIC1 &&
				(uint8)ah.Magic2 == STORE_MAGIC2) {
	fsize -= ah.HeadLen;
	data += ah.HeadLen;
	if (doHead && doBody)
	    ;
	else if (doHead) {
	    fsize = ah.ArtHdrLen;
	} else if (doBody) {
	    data += ah.ArtHdrLen;
	    fsize -= ah.ArtHdrLen;
	    if (ah.StoreType & STORETYPE_WIRE) {
		data += 2;
		fsize -= 2;
	    }
	}
	if (ah.StoreType & STORETYPE_WIRE) {
	    if (doBody) {
		return(fwrite(data, fsize, 1, fo));
	    } else {
		int b;
		b = fwrite(data, fsize, 1, fo);
		xfprintf(fo, ".\r\n");
		return(b + 3);
	    }
	}
    } else if (*data == 0) {
	data++;
    }

    for (i = b = 0; i < fsize; b = i) {
	int separator = 0;

	/*
	 * find the end of line
	 */
	while (i < fsize && data[i] != '\n')
	    ++i;

	/*
	 * if in the headers, check for a blank line
	 */

	if (inHeader && i - b == 0) {
	    inHeader = 0;
	    separator = 1;
	    if (doBody == 0)
		break;
	}

	/*
	 * if printing the headers and/or the body, do any
	 * appropriate escape, write the line non-inclusive
	 * of the \n, then write a CR+LF.
	 *
	 * the blank line separating the header and body
	 * is only printed for the 'article' command.
	 */

	if ((inHeader && doHead) || (!inHeader && doBody)) {
	    if (separator == 0 || (doHead && doBody)) {
		if (data[b] == '.')
		    fputc('.', fo);
		if ((i - b) > 0)
		    fwrite(data + b, i - b, 1, fo);
		bytes += (i - b);
		fwrite("\r\n", 2, 1, fo);
		bytes += 2;
	    }
	}

	++i;	/* skip the nl */

	/*
	 * if i > fsize, we hit the end of the file without
	 * a terminating LF.  We don't have to do anything
	 * since we've already terminated the last line.
	 */
    }
    xfprintf(fo, ".\r\n");
    return(bytes + 3);
}
/*
 * LOADARTICLE()	- get an article from the remote
 *
 * If noWrite==1, then we know we already have the article but have to
 * accept it because the peer is currently sending it. In this case we
 * just ignore it when it arrives
 *
 */

#define	LAERR_ACTIVEDROP	0x000001
#define	LAERR_PATHTAB		0x000002
#define	LAERR_NGTAB		0x000004
#define	LAERR_ARTHASNUL		0x000008
#define	LAERR_MINSIZE		0x000010
#define	LAERR_HEADER		0x000020
#define	LAERR_TOOOLD		0x000040
#define	LAERR_SPOOL		0x000080
#define	LAERR_IO		0x000100
#define	LAERR_MSGID		0x000200
#define	LAERR_NOBYTES		0x000400
#define	LAERR_NOGROUPS		0x000800
#define	LAERR_GRPFILTER		0x001000
#define	LAERR_INCFILTER		0x002000
#define	LAERR_INTSPAM		0x004000
#define	LAERR_EXTSPAM		0x008000
#define	LAERR_POSTDUP		0x010000
#define	LAERR_ARTINCOMPL	0x020000
#define	LAERR_REJSPOOL		0x040000
#define	LAERR_TOOBIG		0x080000

int
LoadArticle(Buffer *bi, const char *msgid, int noWrite, int headerOnly, char *rejBuf, char *artType)
{
    /*
     * create article file
     */
    int retcode = RCERROR;
    int size = 0;
    off_t bpos = -1;
    time_t t = time(NULL);
    History h = { 0 };
    int CompressLvl = -1;
    SpoolArtHdr artHdr;

    h.hv = hhash(msgid);

    if (rejBuf)
	strncpy(rejBuf, "0 000000 UnknownReject", REJMSGSIZE);

    /*
     * Obtain the file used to handle this article.  The file may already
     * be cached... generally, we wind up with one new file every 10 minutes.
     * to the file at the same time, we will be ok.
     */

    errno = 0;	/* in case noWrite is true */

    h.boffset = 1;	/* force new queue file format in ArticleFileName */
    h.bsize = 1;

    /*
     * Flush the article file cache to allow dexpire to delete old
     * files if necessary. This closes files older then 10 min.
     */
    ArticleFileCacheFlush(t);

    {
	Buffer *buffer = NULL;
	char *p;
	int inHeader = 1;
	int lastNl = 0;
	int thisNl = 1;
	int skipHeader = 0;
	int haveMsgId = 0;
	int haveBytes = 0;
	int artError = 0;
	int artFd = -1;
	int hdrDate = 0; /* number of Date: headers */
	int hdrSubject = 0;
	int hdrPath = 0;
	int hdrXref = 0;
	int hdrNewsgroups = 0;
	int hdrMessageId = 0;
	int haveApproved = 0;
	int bytes;
	int compressedSize = -1;
	int lncount = -1;
	int responded = 0;
	int headerEndLine;
	int bodyLines = 0;
	int addRejectToHistory = 0;
	int arttype = ARTTYPE_DEFAULT;
	char nglist[MAXLINE];
	char npath[MAXLINE];
	char xrefdata[MAXLINE];
	char dateBuf[64];
	char subject[80];
	char nntpPostingHost[64];
	char nntpXPostingHost[64];
	char control[64];
	char dist[80];
	int bps;
	int aflen = 0;
	int headerLen = 0;

	struct timespec delay;
	int delay_counter = 0;
	int delay_counter_max = 0;

	delay.tv_sec = 0;

	FeedGetThrottle(HLabel, (int *)&(delay.tv_nsec), &delay_counter_max);

	FeedGetMaxInboundRate(HLabel, &bps);

	nglist[0] = 0;
	npath[0] = 0;
	dateBuf[0] = 0;
	nntpPostingHost[0] = 0;
	nntpXPostingHost[0] = 0;
	subject[0] = 0;
	control[0] = 0;
	dist[0] = 0;
	xrefdata[0] = 0;

	if (BodyFilterFd != -1)
	    bhash_init();

	if (noWrite == 0) {
	    buffer = bopen(-1, DOpts.FeederBufferSize);
	    if (buffer == NULL) {
		logit(LOG_CRIT, "Unable to allocate memory buffer for incoming article");
		LogSession();
		ArticleFileCloseAll();
		ClosePathLog(1);
		CloseArtLog(1);
		DiabFilterClose();
		exit(1);
	    }
	}

	if (DOpts.FeederArtTypes && artType != NULL) {
	    arttype = ARTTYPE_DEFAULT;
	    InitArticleType();
	}
	while ((p = bgets(bi, &bytes)) != NULL && p != (char *)-1) {

	    if (delay_counter_max) {
		delay_counter++;
		if (delay_counter == delay_counter_max) {
		    delay_counter = 0;
		    if (delay.tv_nsec)
			nanosleep(&delay, NULL);
		}
	    }

	    size += bytes;

	    if (DOpts.FeederMaxArtSize > 0 && size > DOpts.FeederMaxArtSize)
		artError |= LAERR_TOOBIG;

	    lastNl = thisNl;
	    thisNl = (p[bytes-1] == '\n');
	    headerEndLine = 0;

	    if (artError == 0 && RejectArtsWithNul) {
		int i;
		for (i = 0; i < bytes; ++i) {
		    if (p[i] == 0) {
			artError |= LAERR_ARTHASNUL;
			break;
		    }
		}
	    }

	    /*
	     * defer lone CR's (only occurs if the buffer overflows)
	     * replace CRLF's with LF's
	     */

	    if (!DOpts.WireFormat) {
		if (p[bytes-1] == '\r') {
		    bunget(bi, 1);
		    --bytes;
		} else if (bytes > 1 && p[bytes-1] == '\n' && p[bytes-2] == '\r') {
		    p[bytes-2] = '\n';
		    --bytes;
		}
		aflen = 1;
	    } else {
		aflen = 2;
	    }

	    /*
	     * Look for end of article
	     */
	    if (lastNl &&
			((DOpts.WireFormat && bytes == 3 &&
						strncmp(p, ".\r\n", 3) == 0) ||
			(!DOpts.WireFormat && bytes == 2 &&
						strncmp(p, ".\n", 2) == 0))) {
		if (DOpts.WireFormat && buffer != NULL &&
						!skipHeader && !artError)
		    bwrite(buffer, p, bytes);
		if (retcode == RCERROR)
		    retcode = RCOK;
		break;
	    }

	    /*
	     * Look for dot escape.  bytes may be 0 after this.  NOTE! 
	     * THERE IS NO TERMINATOR
	     */
	    if (!DOpts.WireFormat) {
		if (lastNl && *p == '.') {
		    ++p;
		    --bytes;
		}
	    }

	    if (artError != 0)
		continue;

	    /*
	     * See if we can categorise the article type
	     */
	    if (DOpts.FeederArtTypes && artType != NULL)
		arttype = ArticleType(p, bytes - aflen, inHeader);

	    /*
	     * Extract certain header information
	     */
	    if (inHeader && buffer != NULL) {
		char hch;
		/*
		 * if skipping a header-in-progress
		 */
		if (skipHeader) {
		    /*
		     * We didn't get the whole line from the previous header,
		     * so this line is part of the previous header.
		     */
		    if (lastNl == 0) 
			continue;

		    /*
		     * Header begins with a space or tab, it is a continuation
		     * from a previous header.
		     */
		    if (bytes && (p[0] == ' ' || p[0] == '\t'))
			continue;

		    /*
		     * header complete, skip terminated
		     */

		    skipHeader = 0;
		}

		/*
		 * If not skipping header and this is a continuation of a
		 * prior header due to a buffer overflow, write it out and 
		 * continue.  (this way we can support long paths independant
		 * of the buffer size).
		 */
		if (lastNl == 0) {
		    bwrite(buffer, p, bytes);
		    continue;
		}

		hch = tolower(*p);
		if (bytes <= 2 && thisNl &&
			(p[0] == '\n' || (p[0] == '\r' && p[1] == '\n'))) {
		    /*
		     * We have reached the end of the headers
		     * Do a bunch of checks on what we have so that we
		     * don't have to save the article if we don't want it
		     */

		    if (control[0])
			ngAddControl(nglist, sizeof(nglist), control);

		    /*
		     * handle Xref: header.  If 'activedrop yes' is set 
		     * and none of the groups can be found in the active 
		     * file, the article is dropped.
		     */
		    if (DOpts.FeederActiveEnabled) {
			if (!DOpts.FeederXRefSlave && DOpts.FeederXRefHost) {
			    /*
			     * Handle Approved: header, if 'activedrop
			     * yes'. Drop if the article does not contain
			     * an Approved: header and is posted to at
			     * least one moderated group.
			     */
			    if (DOpts.FeederActiveDrop &&
				    (haveApproved == 0) &&
				    (CheckForModeratedGroups(nglist) == 1))
			    {
				artError |= LAERR_ACTIVEDROP;
			    } else if (GenerateXRef(buffer, nglist,
					DOpts.FeederXRefHost, control) == 0 &&
					DOpts.FeederActiveDrop &&
					strncmp(control, "newgroup", 8) != 0) {
				artError |= LAERR_ACTIVEDROP;
			    }

			} else if (DOpts.FeederXRefSync) {
			    if (UpdateActiveNX(xrefdata) == 0 &&
							DOpts.FeederActiveDrop)
				artError |= LAERR_ACTIVEDROP;
			}
		    }

		    headerLen = bsize(buffer);
		    inHeader = 0;
		    headerEndLine = 1;

		    /* End of headers */

#ifdef	LOG_FEED_SUPERCEDES
		/*
		   This is a test to catch Supersedes: headers and test
		   them a bit.  For now, we simply log them
		 */
		} else if (hch =='s' && bytes >= 11 &&
			   strncasecmp(p, "Supersedes:", 11) == 0) {
    			char lbuf[100];
			diablo_strlcpynl(lbuf, p, bytes, sizeof(lbuf));
			logit(LOG_DEBUG, "%s Supercede Detect %s %d %s %d",
			       lbuf, msgid, h.exp, nglist, lncount);
#endif	/* LOG_FEED_SUPERCEDES */
		} else if (hch =='b' && bytes >= 6 &&
			   strncasecmp(p, "Bytes:", 6) == 0) {
		    haveBytes = 1;
		    /* write thru */
		} else if (hch =='l' && bytes >= 6 &&
			   strncasecmp(p, "Lines:", 6) == 0) {
		    char lbuf[32];
		    diablo_strlcpynl(lbuf, p + 6, bytes - 6, sizeof(lbuf));
		    lncount = strtol(lbuf, NULL, 10);
		    /* write thru */
		} else if (hch == 'p' && bytes >= 5 &&
			   strncasecmp(p, "Path:", 5) == 0) {
		    char ipfail[128];
		    int idx = 0;
		    static int ReportedNoMatch = 0;
		    PathListType *pl;

		    hdrPath++;
		    /*
		     * Path: line, prepend our path element and store in npath.
		     */
		    p += 5;
		    bytes -= 5;

		    while (bytes && (*p == ' ' || *p == '\t')) {
			++p;
			--bytes;
		    }
		    /*
		     * FeedAdd breaks if Path: header contains a tab.  Drop
		     * the article (the idiots who put the tab in there have
		     * to fix their news system).
		     */
		    {
			int i;

			for (i = 0; i < bytes; ++i) {
			    if (p[i] == '\t' &&
					p[i+1] != '\r' && p[i+1] != '\n') {
				artError |= LAERR_PATHTAB;
			    }
			}
		    }

		    /*
		     * check first path element against aliases
		     * in dnewsfeeds file.
		     */
		    if (PathElmMatches(HLabel, p, bytes, &idx) < 0) {
			sprintf(ipfail, "%s.MISMATCH!", PeerIpName);
			if (ReportedNoMatch == 0) {
			    ReportedNoMatch = 1;
			    logit(LOG_NOTICE, "%-20s Path element fails to match aliases: %*.*s in %s",
				    HName, idx, idx, p, msgid);
			}
		    } else {
			strcpy(ipfail, "");
		    }
		    /*
		     * write out new Path: line.  FeederPathHost can be
		     * an empty string (make news node act as a bridge).
		     *
		     * write out all Path: options in the order they
		     * were specified on the command-line. This allows
		     * multiple entries to be specified
		     */
		    bwrite(buffer, "Path: ", 6);
		    for (pl = DOpts.PathList; pl != NULL; pl = pl->next) {
			if (pl->pathtype == 1 && pl->pathent[0]) {
			    bwrite(buffer, pl->pathent, strlen(pl->pathent));
			    bwrite(buffer, "!", 1);
			}
			if (pl->pathtype == 2 && 
				(CommonElmMatches(pl->pathent, p, bytes - aflen) < 0)) {
			    bwrite(buffer, pl->pathent, strlen(pl->pathent));
			    bwrite(buffer, "!", 1);
			}
		    }
		    bwrite(buffer, ipfail, strlen(ipfail));
		    diablo_strlcpynl(npath, p, bytes - aflen, sizeof(npath));
		} else if (hch == 'x' && bytes >= 5 &&
			   strncasecmp(p, "Xref:", 5) == 0) {
		    hdrXref++;
		    if (DOpts.FeederXRefSlave == 0) {
			skipHeader = 1;
		    } else if (DOpts.FeederXRefSync) {
			/* Keep Xref: for storing NX record */
			diablo_strlcpynl(xrefdata, p + 5, bytes - 5 - aflen,
							sizeof(xrefdata));
			if (DebugOpt > 2)
			    ddprintf("FOUND XREF INFO: %s\n",xrefdata);
		    }
		    /* write thru */
		} else if (hch == 'd' && bytes >= 13 &&
			   strncasecmp(p, "Distribution:", 13) == 0) {
		    diablo_strlcpynl(dist, p + 13, bytes - 13 - aflen, sizeof(dist));
		    /* write thru */
		} else if (hch == 'n' && bytes >= 11 &&
			   strncasecmp(p, "Newsgroups:", 11) == 0) {
		    hdrNewsgroups++;
		    diablo_strlcpynl(nglist, p + 11, bytes - 11 - aflen, MAXLINE);

		    /*
		     * FeedAdd breaks if newsgroups: header contains a
		     * tab, but we allow trailing tabs because many systems 
		     * are screwed and add one.  diablo_strlcpynl will deal with it.
		     */
		    {
			int i;

			for (i = 11; i < bytes; ++i) {
			    if (p[i] == '\t' &&
					p[i+1] != '\n' && p[i+1] != '\r') {
				artError |= LAERR_NGTAB;
			    }
			}
		    }
		} else if (hch == 's' && bytes >= 8 &&
			   strncasecmp(p, "Subject:", 8) == 0) {
		    hdrSubject++;
		    /*
		     * copy & remove newline
		     */
		    diablo_strlcpynl(subject, p + 8, bytes - 8 - aflen, sizeof(subject));
		    /* 
		     * if the body is the same but the subject is different
		     * (aka: control messages), include them in the body
		     * hash so the SpamFilter does not filter them.
		     */
		    if (BodyFilterFd != -1)
			bhash_update(p, bytes);
		} else if (hch == 'd' && bytes >= 5 &&
			   strncasecmp(p, "Date:", 5) == 0) {
		    hdrDate++;
		    if (DOpts.FeederMaxAcceptAge >= 0) {
		    	diablo_strlcpynl(dateBuf, p + 5, bytes - 5 - aflen, sizeof(dateBuf));
		    }
		} else if (NphFilterFd != -1 &&
			   hch == 'n' &&
			   bytes >= 18 && 
			   strncasecmp(p, "NNTP-Posting-Host:", 18) == 0) {
		    diablo_strlcpynl(nntpPostingHost, p + 18, bytes - 18 - aflen,
						sizeof(nntpPostingHost));
#ifdef	USE_X_ORIGINAL_NPH
		} else if (hch == 'x' && NphFilterFd != -1 && bytes >= 29 &&
			   (strncasecmp(p, "X-Original-NNTP-Posting-Host:", 29) == 0 )) {
			diablo_strlcpynl(nntpXPostingHost, p + 29, bytes - 29,
					 sizeof(nntpXPostingHost));
#endif
		} else if (hch =='m' && bytes >= 11 &&
				strncasecmp(p, "Message-ID:", 11) == 0) {
		    char *ps;
		    char *pe;

		    hdrMessageId++;
		    for (ps = p + 11; ps - p < bytes && *ps != '<'; ++ps)
			;
		    for (pe = ps; pe - p < bytes && *pe != '>'; ++pe)
			;

		    if (pe - p < bytes) {
			int l = pe - ps + 1;
			if (strlen(msgid) == l && strncmp(msgid, ps, l) == 0)
			    haveMsgId = 1;
		    }
		    if (haveMsgId == 0) {
			int l = bytes - (ps - p);

			logit(LOG_NOTICE, "%-20s message-id mismatch, command: %s, article: %*.*s",
			    HName,
			    msgid,
			    l, l, ps
			);
		    }
		} else if (hch == 'c' && bytes > 8 &&
			   strncasecmp(p, "Control:", 8) == 0) {
		    int i = 8;
		    int j = 0;

		    while (i < bytes && (p[i] == ' ' || p[i] == '\t'))
			++i;
		    while (j < sizeof(control) - 1 && i < bytes && isalpha((int)p[i]))
			control[j++] = p[i++];
		    control[j] = 0;
		} else if (hch == 'a' && bytes >= 9 &&
			   strncasecmp(p, "Approved:", 9) == 0) {
		    haveApproved = 1;
		    /* write thru */
		}
	    } /* inHeader */

	    /*
	     * If not in header hash the article body for the spam filter.
	     * This is relatively easy to defeat (the NNTP-Posting-Host:
	     * is less so), but will still catch a shitload of spam since
	     * the article body is usually 100% duplicated.
	     */

	    if (buffer != NULL && !skipHeader && !artError) {
		if (BodyFilterFd != -1 && !inHeader && !headerEndLine) {
		    bhash_update(p, bytes);
		    bodyLines++;
		}
		bwrite(buffer, p, bytes);
	    }
	} /* while bgets */

	if (artType)
	    sprintf(artType, "%06x", arttype);

	/*
	 * noWrite==1 means we received an article that we knew was going
	 * to be rejected when LoadArticle was called.
	 * i.e: A history lookup returned positive on a TAKETHIS
	 */
	if (noWrite)
	    artError |= LAERR_POSTDUP;

	/*
	 * If retcode == RCERROR then we didn't get a complete article, so
	 * reject it. Hopefully we get it from somewhere else.
	 *
	 * XXX Is it worth asking remote to try again? Could cause loops.
	 */
	if (retcode == RCERROR)
	    artError |= LAERR_ARTINCOMPL;

	/*
	 * Check that the article has a minimum size. An article of size
	 * 3 is quite usual if you have a peer which offers you an article,
	 * but doesn't actually deliver it (e.g: it no longer exists).
	 */
	if (retcode == RCOK && size < MINARTSIZE)
	    artError |= LAERR_MINSIZE;

	/*
	 * NOTE: The following header checks could be done just after
	 * receiving the article header, but then we won't catch them
	 * if there is no article body.
	 */

	/*
	 * Check that exactly one of each of the important
	 *headers exists
	 */
	if (!artError && (hdrDate != 1 || hdrSubject != 1 ||
				hdrMessageId != 1 || hdrPath != 1 ||
				hdrXref > 1 || hdrNewsgroups != 1)) {
	    logit(LOG_INFO, "%-20s header error in article %s: #Date=%d, #Subject=%d, #MessageId=%d, #Path=%d, #Xref=%d, #Newsgroups=%d",
					HName, msgid,
					hdrDate, hdrSubject, hdrMessageId,
					hdrPath, hdrXref, hdrNewsgroups
	    );
	    artError |= LAERR_HEADER;
	}

	/*
	 * Check that the article Message-ID: matches the
	 * msgid offered
	 */
	if (!artError && haveMsgId == 0)
	    artError |= LAERR_MSGID;

	/*
	 * A header-only feed must supply a Bytes: header
	 */
	if (headerOnly && haveBytes == 0)
	    artError |= LAERR_NOBYTES;

	/*
	 * An article with no newsgroups is a bit pointless
	 */
	if (!artError && nglist[0] == 0)
	    artError |= LAERR_NOGROUPS;

	/*
	 * If the messsage is too old, reject it.
	 *
	 * XXX reject the article if parsedate doesn't have
	 * a clue ?
	 */
	if (!artError && hdrDate && dateBuf[0]) {
	    time_t tart = parsedate(dateBuf);

	    if (tart != (time_t)-1) {
		int32 dt = t - tart;
		if (dt > DOpts.FeederMaxAcceptAge || dt < -DOpts.FeederMaxAcceptAge)
		    artError |= LAERR_TOOOLD;
	    }
	}

	/*
	 * If the article from this incoming feed is filtered
	 * out due to group firewall.  The default, if no
	 * filter directives match, is to not filter
	 * (hence > 0 rather then >= 0)
	 */

	if (!artError && IsFiltered(HLabel, nglist) > 0)
		artError |= LAERR_GRPFILTER;

	/*
	 * Check that the article passes the incoming filter(s)
	 */
	if (retcode == RCOK && !artError &&
		FeedFilter(HLabel, nglist, npath, dist, artType, size))
	    artError |= LAERR_INCFILTER;

	/*
	 * XXX put other inbound filters here.  Be careful in regards to
	 *     what gets added to the history file and what does not, the
	 *     message may be valid when received via some other path.
	 */

	/*
	 * If everything is ok, check the spam filter
	 */

	if (retcode == RCOK && !artError && DOpts.SpamFilterOpt != NULL &&
			FeedSpam(1, nglist, npath, dist, artType, size)){
	    int rv = 0;
	    int how = 0;

#ifdef	USE_X_ORIGINAL_NPH
	    if (nntpXPostingHost[0])
		strcpy(nntpPostingHost, nntpXPostingHost);
#endif

	    if (nntpPostingHost[0])
		rv = FeedQuerySpam(HLabel, nntpPostingHost);

	    if (rv == 0) {
		SpamInfo spamInfo;

		bzero(&spamInfo, sizeof(spamInfo));
		bhash_final(&spamInfo.BodyHash);
		if (nntpPostingHost[0]) {
		    spamInfo.PostingHost = nntpPostingHost;
		    memcpy(&spamInfo.PostingHostHash, md5hash(nntpPostingHost),
					sizeof(spamInfo.PostingHostHash));
		}
		spamInfo.Lines = bodyLines;
		spamInfo.MsgIdHash = hhash(msgid);
		rv = SpamFilter(t, &spamInfo, &how);
	    }

	    if (rv < 0) {
		/*
		 * Reject the article as being spam
		 */ 

		if ((-rv & 31) == 1) {
		    logit(LOG_INFO, "SpamFilter/%s copy #%d: %s %d %s (%s)", 
			((how == 0) ? "dnewsfeeds" : ((how == 1) ?
					"by-post-rate" : "by-dup-body")),
			((how == 0) ? -1 : -rv),
			msgid, bodyLines, nntpPostingHost, subject
		    );
		}
		if (DebugOpt > 1)
		    printf("SpamFilter: %s\t%s\n", msgid, nntpPostingHost);
		artError |= LAERR_INTSPAM;
	    }
	}

	/*
	 * Find which spool the article should reside on and open up
	 * a buffer fd - only if we want the article (noWrite == 0)
	 *
	 * Note that if the message is to be rejected, the article
	 * is set to a zero size on the spool, effectively deleting it
	 */

	if (retcode == RCOK && !artError && buffer != NULL) {
	    int interval = 0;
	    char z = 0;
	    uint16 spool = 0;

	    h.exp = 0;
	    spool = GetSpool(msgid, nglist, size, arttype, HLabel, &interval, &CompressLvl);
	    if (interval && t - SpoolAllocTime >= interval) {
		if (DebugOpt)
		    printf("Allocating new spools\n");
		ArticleFileCloseAll();
		SpoolAllocTime = t;
		if (AllocateSpools(SpoolAllocTime) == -1) {
		    sleep(10);
		    exit(-1);
		}
		spool = GetSpool(msgid, nglist, size, arttype,
						HLabel, NULL, &CompressLvl);
	    }
	    h.gmt = SpoolDirTime();
	    if (spool <= 100) {
#ifdef USE_ZLIB
		gzFile *cfile = NULL;
#else
		char *cfile = NULL;
#endif
		h.exp = spool + 100;
		h.bsize = bsize(buffer) + sizeof(artHdr);
		artFd = ArticleFile(&h, &bpos, CompressLvl, &cfile);
		if (artFd >= 0) {
		    h.bsize = bsize(buffer) + sizeof(artHdr);
		    artHdr.Magic1 = STORE_MAGIC1;
		    artHdr.Magic2 = STORE_MAGIC2;
		    artHdr.Version = STOREAPI_REVISION;
		    artHdr.StoreType = (DOpts.WireFormat ?
					STORETYPE_WIRE : STORETYPE_TEXT);
		    if (cfile != NULL)
			artHdr.StoreType |= STORETYPE_GZIP;
		    artHdr.HeadLen = sizeof(SpoolArtHdr);
		    artHdr.ArtHdrLen = headerLen;
		    artHdr.ArtLen = bsize(buffer);
		    artHdr.StoreLen = h.bsize + 1;
		    write(artFd, &artHdr, sizeof(artHdr));
		    bsetfd(buffer, artFd);
#ifdef USE_ZLIB
		    if (cfile != NULL)
			bsetcompress(buffer, cfile);
		    bflush(buffer);
		    if (cfile != NULL) {
			gzclose(cfile);
			bsetcompress(buffer, NULL);
		    }
#else
		    bflush(buffer);
#endif
		} else {
		    artError |= LAERR_IO;
		}
	    } else {
		/*
		 * This means we don't have metaspool object for this
		 * article.  Reject/Accept it and add it to history.
		 */
		artFd = -1;
		if ((int16)spool > -3)
		    artError |= LAERR_SPOOL;
		if ((int16)spool == -2)
		    artError |= LAERR_REJSPOOL;
	    }

	    /*
	     * Write out the article
	     */
	    h.boffset = (uint32)bpos;
#ifdef USE_ZLIB
	    if (CompressLvl >= 0 && CompressLvl <= 9) {
		off_t filePos;

		compressedSize = btell(buffer) - bpos;
		h.bsize = bzwrote(buffer) + sizeof(artHdr);
		filePos = lseek(artFd, 0, SEEK_CUR);
		lseek(artFd, bpos, SEEK_SET);
		artHdr.StoreLen = compressedSize;
		write(artFd, &artHdr, sizeof(artHdr));
		lseek(artFd, filePos, SEEK_SET);
	    }
#endif
	    bwrite(buffer, &z, 1);		/* terminator (sanity check) */
	    bflush(buffer);
	    if (DebugOpt > 1)
		ddprintf("%s: b=%08lx artFd=%d boff=%d bsize=%d",
			msgid, (long)buffer, artFd,
			(int)h.boffset, (int)h.bsize
		);
	}

	/*
	 * Now report on any accumulated errors
	 *
	 * NOTE: The ordering is important as it determines which errors
	 * are given priority
	 */

	if (!responded && artError > 0) {
	    if (artError & LAERR_POSTDUP) {
		DoArtStats(STATS_REJECTED, STATS_REJ_POSDUP, size);
		DEBUGLOG(msgid, "PostDuplicate2");
		SETREJECT("PostDuplicate2");
	    } else if (artError & LAERR_ARTINCOMPL) {
		DoArtStats(STATS_REJECTED, STATS_REJ_ARTINCOMPL, size);
		DEBUGLOG(msgid, "ArtIncomplete");
		SETREJECT("ArtIncomplete");
	    } else if (artError & LAERR_MINSIZE){
		DoArtStats(STATS_REJECTED, STATS_REJ_TOOSMALL, size);
		DEBUGLOG(msgid, "TooSmall");
		SETREJECT("TooSmall");
		(void)PreCommit(msgid, PC_DELCOMM);
	    } else if (artError & LAERR_TOOBIG){
		DoArtStats(STATS_REJECTED, STATS_REJ_TOOBIG, size);
		DEBUGLOG(msgid, "TooBig");
		SETREJECT("TooBig");
		(void)PreCommit(msgid, PC_DELCOMM);
		addRejectToHistory = 1;
	    } else if (artError & LAERR_HEADER){
		DoArtStats(STATS_REJECTED, STATS_REJ_HDRERROR, size);
		DEBUGLOG(msgid, "MissingHeader");
		SETREJECT("MissingHeader");
		(void)PreCommit(msgid, PC_DELCOMM);
	    } else if (artError & LAERR_NOGROUPS) {
		DoArtStats(STATS_REJECTED, STATS_REJ_MISSHDRS, size);
		DEBUGLOG(msgid, "NoNewsgroups");
		SETREJECT("NoNewsgroups");
		(void)PreCommit(msgid, PC_DELCOMM);
	    } else if (artError & LAERR_ACTIVEDROP) {
		DoArtStats(STATS_REJECTED, STATS_REJ_NOTINACTV, size);
		DEBUGLOG(msgid, "NotInActive");
		SETREJECT("NotInActive");
		addRejectToHistory = 1;
	    } else if (artError & LAERR_MSGID) {
		DoArtStats(STATS_REJECTED, STATS_REJ_MSGIDMIS, size);
		DEBUGLOG(msgid, "MsgIdMismatch");
		SETREJECT("MsgIdMismatch");
		(void)PreCommit(msgid, PC_DELCOMM);
	    } else if (artError & LAERR_TOOOLD) {
		DoArtStats(STATS_REJECTED, STATS_REJ_TOOOLD, size);
		DEBUGLOG(msgid, "TooOld");
		SETREJECT("TooOld");
		addRejectToHistory = 1;
	    } else if (artError & LAERR_PATHTAB) {
		DoArtStats(STATS_REJECTED, STATS_REJ_PATHTAB, size);
		DEBUGLOG(msgid, "PathTab");
		SETREJECT("PathTab");
		(void)PreCommit(msgid, PC_DELCOMM);
	    } else if (artError & LAERR_NGTAB) {
		DoArtStats(STATS_REJECTED, STATS_REJ_NGTAB, size);
		DEBUGLOG(msgid, "NewsgroupsTab");
		SETREJECT("NewsgroupsTab");
		(void)PreCommit(msgid, PC_DELCOMM);
	    } else if (artError & LAERR_ARTHASNUL) {
		DoArtStats(STATS_REJECTED, STATS_REJ_ARTNUL, size);
		DEBUGLOG(msgid, "ArtHasNul");
		SETREJECT("artHasNul");
		(void)PreCommit(msgid, PC_DELCOMM);
	    } else if (artError & LAERR_NOBYTES) {
		DoArtStats(STATS_REJECTED, STATS_REJ_NOBYTES, size);
		DEBUGLOG(msgid, "HdrOnlyNoBytes");
		SETREJECT("HdrOnlyNoBytes");
		(void)PreCommit(msgid, PC_DELCOMM);
	    } else if (artError & LAERR_GRPFILTER) {
		DoArtStats(STATS_REJECTED, STATS_REJ_GRPFILTER, size);
		DEBUGLOG(msgid, "GroupFilter");
		SETREJECT("GroupFilter");
		addRejectToHistory = 1;
	    } else if (artError & LAERR_INCFILTER) {
		DoArtStats(STATS_REJECTED, STATS_REJ_INCFILTER, size);
		DEBUGLOG(msgid, "IncomingFilter");
		SETREJECT("IncomingFilter");
		addRejectToHistory = 1;
	    } else if (artError & LAERR_INTSPAM) {
		DoArtStats(STATS_REJECTED, STATS_REJ_INTSPAMFILTER, size);
		DEBUGLOG(msgid, "InternalSpamFilter");
		SETREJECT("InternalSpamFilter");
		addRejectToHistory = 1;
	    } else if (artError & LAERR_EXTSPAM) {
		DoArtStats(STATS_REJECTED, STATS_REJ_EXTSPAMFILTER, size);
		DEBUGLOG(msgid, "ExternalSpamFilter");
		SETREJECT("ExternalSpamFilter");
		addRejectToHistory = 1;
	    } else if (artError & LAERR_REJSPOOL) {
		DoArtStats(STATS_REJECTED, STATS_REJ_NOSPOOL, size);
		DEBUGLOG(msgid, "RejSpool");
		SETREJECT("Unwanted");
		addRejectToHistory = 1;
	    } else if (artError & LAERR_SPOOL) {
		DoArtStats(STATS_REJECTED, STATS_REJ_NOSPOOL, size);
		DEBUGLOG(msgid, "NoSpool");
		SETREJECT("NoSpool");
		addRejectToHistory = 1;
	    } else if (artError & LAERR_IO) {
		DoArtStats(STATS_REJECTED, STATS_REJ_IOERROR, size);
		DEBUGLOG(msgid, "IOError/Missing-dir");
		SETREJECT("IOError/Missing-dir");
		(void)PreCommit(msgid, PC_DELCOMM);
	    }
	    responded = 1;
	    retcode = RCREJECT;
	}

	if (DebugOpt > 1)
	    ddprintf("nglist: %s", nglist);

	/*
	 * check output file for error, only if retcode == RCOK.
	 * If there was a problem, change the return code
	 * to RCTRYAGAIN.
	 */

	if (retcode == RCOK && buffer != NULL) {
	    int rcode;
	    if ((rcode = berror(buffer)) != 0) {
		sleep(1);		/* failsafe */
		logit(LOG_CRIT, "%s Error writing article file (%s)",
						HName, strerror(rcode));
		DoArtStats(STATS_REJECTED, STATS_REJ_FAILSAFE, size);
		DEBUGLOG(msgid, "FileWriteError");
		SETREJECT("FileWriteError");
		/* Don't add to history */
		(void)PreCommit(msgid, PC_DELCOMM);
		responded = 1;
		retcode = RCTRYAGAIN;
	    }
	}

	if (buffer != NULL) {
	    /*
	     * close our buffered I/O without closing the underlying
	     * descriptor (bclose() does not flush!).
	     */
	    bflush(buffer);
	    bclose(buffer, 0);
	    buffer = NULL;
	}

	/*
	 * If the article does not have a distribution, add one.
	 */

	if (dist[0] == 0)
	    strcpy(dist, "world");

	/*
	 * If everything is ok, commit the message
	 */

	if (retcode == RCOK) {
	    int rhist;

#if USE_SYSV_SIGNALS
	    sighold(SIGHUP);
	    sighold(SIGALRM);
#else
	    int smask = sigblock((1 << SIGHUP) | (1 << SIGALRM));
#endif
	    if (headerOnly)
		h.exp |= EXPF_HEADONLY;

	    /*
	     * If we didn't store the article, reflect that in history
	     * and don't offer to feeds
	     */
	    if (artFd == -1) {
		h.boffset = 0;
		h.bsize = 0;
		h.gmt = t / 60;
		h.iter = 0;
		h.exp |= EXPF_EXPIRED;
	    }

	    rhist = HistoryAdd(msgid, &h);
	    (void)PreCommit(msgid, PC_POSTCOMM);

	    if (artFd == -1) {
		SETREJECT("DontStore");
		LogIncoming("%s + %s %s", HLabel, msgid, rejBuf);
	    } else if (rhist == RCOK) {
		char cSize[20];
		if (compressedSize != -1)
		    sprintf(cSize, "%d", compressedSize);
		else
		    cSize[0] = 0;
		StoreStats.StoreBytes += h.bsize;
		if (compressedSize != -1)
		    StoreStats.StoreCompressedBytes += compressedSize;
		if (FeedAdd(msgid, t, &h, nglist, npath, dist,
					headerOnly, artType, cSize) < 0) {
		    /*
		     * If we lose our pipe, exit immediately.
		     */
		    ArticleFileCloseAll();
		    LogSession();
		    ClosePathLog(1);
		    CloseArtLog(1);
		    DiabFilterClose();
		    exit(1);
		}
	    } else if (rhist == RCALREADY) {
		DoArtStats(STATS_REJECTED, STATS_REJ_POSDUP, size);
		DEBUGLOG(msgid, "PostDuplicate");
		SETREJECT("PostDuplicate");
		responded = 1;
		retcode = RCALREADY;
	    } else {
		/*
		 * XXX: This should not happen
		 */
		DoArtStats(STATS_REJECTED, STATS_REJ_IOERROR, size);
		DEBUGLOG(msgid, "IOErrorX");
		SETREJECT("IOErrorX");
		(void)PreCommit(msgid, PC_DELCOMM);
		responded = 1;
		retcode = RCREJECT;
	    }
#if USE_SYSV_SIGNALS
	    sigrelse(SIGHUP);
	    sigrelse(SIGALRM);
#else
	    sigsetmask(smask);
#endif
	} else if (addRejectToHistory) {
	    int rhist;

#if USE_SYSV_SIGNALS
	    sighold(SIGHUP);
	    sighold(SIGALRM);
#else
	    int smask = sigblock((1 << SIGHUP) | (1 << SIGALRM));
#endif

	    h.boffset = 0;
	    h.bsize = 0;
	    h.gmt = t / 60;
	    h.iter = (unsigned short)-1;
	    h.exp |= EXPF_EXPIRED;
	    rhist = HistoryAdd(msgid, &h);
	    (void)PreCommit(msgid, PC_POSTCOMM);

#if USE_SYSV_SIGNALS
	    sigrelse(SIGHUP);
	    sigrelse(SIGALRM);
#else
	    sigsetmask(smask);
#endif
	}

	DoArtStats(STATS_RECEIVED, 0, size);

	if (retcode == RCOK) {
	    if (control[0]) {
		DoArtStats(STATS_ACCEPTED, STATS_CONTROL, size);
		DEBUGLOG(msgid, "AcceptedControl");
	    } else {
		DoArtStats(STATS_ACCEPTED, 0, size);
		DEBUGLOG(msgid, "Accepted");
	    }
	} else {
	    if (!responded) {
		logit(LOG_ERR, "Internal error: retcode=%d, but responded=0. Please report.\n",
		       retcode);
	    }
	}

	if ((Stats.RecStats.Stats[STATS_RECEIVED] % 1024) == 0)
	    LogSession();

	/*
	 * Record the current append position, possibly
	 * updating the filesize in the article file cache.
	 *
	 * If we created a file but the return code is
	 * not RCOK, truncate the file.
	 */
	if (artFd >= 0)
	    ArticleFileSetSize(artFd);

	if (retcode != RCOK && artFd >= 0) {
	    ArticleFileTrunc(artFd, bpos);
	}
    }
    return(retcode);
}

void
ngAddControl(char *nglist, int ngSize, const char *ctl)
{
    /*
     * If a control message, append 'control.CTLNAME' to grouplist
     */
    int l = strlen(nglist);

    if (l && nglist[l-1] == '\n')
	--l;
    if (l && nglist[l-1] == '\r')
	--l;

    if (strlen(ctl) + l < ngSize - 11)
	sprintf(nglist + l, ",control.%s", ctl);
}


/*
 * LOGSESSION()	- Log statistics for a session
 */

void
LogSession(void)
{
    time_t t = time(NULL);
    int32 dt = t - SessionMark;
    int32 nuse;

    nuse = Stats.RecStats.Stats[STATS_CHECK] +
					Stats.RecStats.Stats[STATS_IHAVE];
    if (nuse < Stats.RecStats.Stats[STATS_RECEIVED])
	nuse = Stats.RecStats.Stats[STATS_RECEIVED];

    if (StoreStats.StoreCompressedBytes > 0.0) {
	logit(LOG_INFO, "store secs=%d artbytes=%.0f compbytes=%.0f",
	 	dt,
		StoreStats.StoreBytes,
		StoreStats.StoreCompressedBytes
	);
	bzero(&StoreStats, sizeof(StoreStats));
    }

    logit(LOG_NOTICE, "%-20s secs=%d ihave=%d chk=%d takethis=%d rec=%d acc=%d ref=%d precom=%d postcom=%d his=%d badmsgid=%d rej=%d ctl=%d spam=%d err=%d recbytes=%.0f accbytes=%.0f rejbytes=%.0f (%d/sec)",
	HName,
	dt,
	Stats.RecStats.Stats[STATS_IHAVE],
	Stats.RecStats.Stats[STATS_CHECK],
	Stats.RecStats.Stats[STATS_TAKETHIS],
	Stats.RecStats.Stats[STATS_RECEIVED],
	Stats.RecStats.Stats[STATS_ACCEPTED],
	Stats.RecStats.Stats[STATS_REFUSED],
	Stats.RecStats.Stats[STATS_REF_PRECOMMIT],
	Stats.RecStats.Stats[STATS_REF_POSTCOMMIT],
	Stats.RecStats.Stats[STATS_REF_HISTORY],
	Stats.RecStats.Stats[STATS_REF_BADMSGID],
	Stats.RecStats.Stats[STATS_REJECTED],
	Stats.RecStats.Stats[STATS_CONTROL],
	Stats.RecStats.Stats[STATS_REJ_INTSPAMFILTER] +
				Stats.RecStats.Stats[STATS_REJ_EXTSPAMFILTER],
	Stats.RecStats.Stats[STATS_REJ_ERR],
	Stats.RecStats.ReceivedBytes,
	Stats.RecStats.AcceptedBytes,
	Stats.RecStats.RejectedBytes,
	nuse / ((dt == 0) ? 1 : dt)
    );
    if (Stats.SpoolStats.Arts[STATS_S_ARTICLE] ||
			Stats.SpoolStats.Arts[STATS_S_HEAD] ||
			Stats.SpoolStats.Arts[STATS_S_BODY] ||
			Stats.SpoolStats.Arts[STATS_S_STAT])
	logit(LOG_INFO, "%-20s spoolstats secs=%d stat=%d head=%d body=%d article=%d bytes=%.0f",
		HName,
		dt,
		Stats.SpoolStats.Arts[STATS_S_STAT],
		Stats.SpoolStats.Arts[STATS_S_HEAD],
		Stats.SpoolStats.Arts[STATS_S_BODY],
		Stats.SpoolStats.Arts[STATS_S_ARTICLE],
		Stats.SpoolStats.ArtsBytesSent
    );
    if (Stats.RecStats.Stats[STATS_REJECTED] > 0)
	logit(LOG_INFO, "%-20s rejstats rej=%d failsafe=%d misshdrs=%d tooold=%d grpfilt=%d intspamfilt=%d extspamfilt=%d incfilter=%d nospool=%d ioerr=%d notinactv=%d pathtab=%d ngtab=%d posdup=%d hdrerr=%d toosmall=%d incompl=%d nul=%d nobytes=%d proto=%d msgidmis=%d err=%d",
	HName,
	Stats.RecStats.Stats[STATS_REJECTED],
	Stats.RecStats.Stats[STATS_REJ_FAILSAFE],
	Stats.RecStats.Stats[STATS_REJ_MISSHDRS],
	Stats.RecStats.Stats[STATS_REJ_TOOOLD],
	Stats.RecStats.Stats[STATS_REJ_GRPFILTER],
	Stats.RecStats.Stats[STATS_REJ_INTSPAMFILTER],
	Stats.RecStats.Stats[STATS_REJ_EXTSPAMFILTER],
	Stats.RecStats.Stats[STATS_REJ_INCFILTER],
	Stats.RecStats.Stats[STATS_REJ_NOSPOOL],
	Stats.RecStats.Stats[STATS_REJ_IOERROR],
	Stats.RecStats.Stats[STATS_REJ_NOTINACTV],
	Stats.RecStats.Stats[STATS_REJ_PATHTAB],
	Stats.RecStats.Stats[STATS_REJ_NGTAB],
	Stats.RecStats.Stats[STATS_REJ_POSDUP],
	Stats.RecStats.Stats[STATS_REJ_HDRERROR],
	Stats.RecStats.Stats[STATS_REJ_TOOSMALL],
	Stats.RecStats.Stats[STATS_REJ_ARTINCOMPL],
	Stats.RecStats.Stats[STATS_REJ_ARTNUL],
	Stats.RecStats.Stats[STATS_REJ_NOBYTES],
	Stats.RecStats.Stats[STATS_REJ_PROTOERR],
	Stats.RecStats.Stats[STATS_REJ_MSGIDMIS],
	Stats.RecStats.Stats[STATS_REJ_ERR]
    );
    bzero(&Stats, sizeof(Stats));
    SessionMark = t;
}

void
LogSession2(void)
{
    int dt = (int)(time(NULL) - SessionMark);

    logit(LOG_INFO, 
	"DIABLO uptime=%d:%02d arts=%s tested=%s bytes=%s fed=%s",
	dt / 3600,
	dt / 60 % 60,
	ftos(TtlStats.ArtsReceived),
	ftos(TtlStats.ArtsTested),
	ftos(TtlStats.ArtsBytes),
	ftos(TtlStats.ArtsFed)
    );
}

void
DoStats(FILE *fo, int dt, int raw)
{
    if (raw)
	xfprintf(fo, "211 DIABLO timenow=%ld uptime=%d:%02d arts=%.0f tested=%.0f bytes=%.0f fed=%.0f\r\n",
		time(NULL),
		dt / 3600, dt / 60 % 60,
		TtlStats.ArtsReceived,
		TtlStats.ArtsTested,
		TtlStats.ArtsBytes,
		TtlStats.ArtsFed
    );
    else
	xfprintf(fo, "211 DIABLO timenow=%ld uptime=%d:%02d arts=%s tested=%s bytes=%s fed=%s\r\n",
		time(NULL),
		dt / 3600, dt / 60 % 60,
		ftos(TtlStats.ArtsReceived),
		ftos(TtlStats.ArtsTested),
		ftos(TtlStats.ArtsBytes),
		ftos(TtlStats.ArtsFed)
    );
}

/*
 * DOCOMMAND() - handles a control connection
 */

void
DoCommand(int ufd)
{
    int fd;
    int retain = 0;
    struct sockaddr_in asin;
    ACCEPT_ARG3_TYPE alen = sizeof(asin);
    int count = 20;

    /*
     * This can be a while() or an if(), but apparently some OS's fail to
     * properly handle O_NONBLOCK for accept() on unix domain sockets so...
     */

    while (--count > 0) {
	FILE *fi;
	FILE *fo;

	if ((fd = accept(ufd, (struct sockaddr *)&asin, &alen)) <= 0)
	    break;
	fcntl(fd, F_SETFL, 0);
	fo = fdopen(dup(fd), "w");
	fi = fdopen(fd, "r");

	if (fi && fo) {
	    char buf[MAXLINE];

	    while (fgets(buf, sizeof(buf), fi) != NULL) {
		char *s1;
		char *s2;

		if (DebugOpt)
		    printf("%d << %s\n", (int)getpid(), buf);

		if ((s1 = strtok(buf, " \t\r\n")) == NULL)
		    continue;
		s2 = strtok(NULL, "\n");
		if (strcmp(s1, "status") == 0) {
		    fprintf(fo, "211 Paused=%d Exiting=%d Forks=%d NFds=%d MaxFds=%d\r\n",
			PausedCount, Exiting, NumForks, countFds(&RFds), MaxFds
		    );
		} else if (strcmp(s1, "version") == 0) {
		    fprintf(fo, "211 DIABLO Version %s - %s\r\n", VERS, SUBREV);
		} else if (strcmp(s1, "flush") == 0) {
		    fprintf(fo, "211 Flushing feeds\n");
		    fflush(fo);
		    flushFeeds(0);
		    CloseIncomingLog();
		    ClosePathLog(1);
		    CloseArtLog(1);
		} else if (strcmp(s1, "pause") == 0) {
		    retain = RET_PAUSE;
		    ++PausedCount;
		    ReadOnlyMode = 0;
		    ReadOnlyCount = 0;
		    fprintf(fo, "200 Pause, count %d.\n", PausedCount);
		    if (PausedCount == 1) {
			int i;

			for (i = 0; i < MAXFDS; ++i) {
			    if (PidAry[i].tr_Pid) {
				Kill(PidAry[i].tr_Pid, SIGHUP);
			    }
			}
		    }
		} else if (strcmp(s1, "readonly") == 0) {
		    retain = RET_PAUSE;
		    ReadOnlyMode = 1;
		    ReadOnlyCount = 0;
		    fprintf(fo, "200 Read-only mode %d.\n", ReadOnlyMode);
		    {
			int i;

			for (i = 0; i < MAXFDS; ++i) {
			    if (PidAry[i].tr_Pid) {
				Kill(PidAry[i].tr_Pid, SIGALRM);
			    }
			}
		    }
		} else if (strcmp(s1, "child-is-readonly") == 0) {
		    ReadOnlyCount++;
		} else if (strcmp(s1, "go") == 0) {
		    int i;

		    if ((PausedCount == 1) && ReadOnlyMode) {
			for (i = 0; i < MAXFDS; ++i) {
			    if (PidAry[i].tr_Pid) {
				Kill(PidAry[i].tr_Pid, SIGHUP);
			    }
			}
			ReadOnlyMode = 0;
		    }

		    if (PausedCount)
			--PausedCount;

		    if (ReadOnlyMode && !PausedCount)
			ReadOnlyMode = 0;

		    fprintf(fo, "200 Resume, count %d\n", PausedCount);
		} else if (strcmp(s1, "quit") == 0) {
		    fprintf(fo, "200 Goodbye\n");
		    fprintf(fo, ".\n");
		    break;
		} else if (strcmp(s1, "stats") == 0) {
		    int dt = (int)(time(NULL) - SessionBeg);
		    DoStats(fo, dt, 0);
		    fprintf(fo, ".\n");
		    break;
		} else if (strcmp(s1, "rawstats") == 0) {
		    int dt = (int)(time(NULL) - SessionBeg);
		    DoStats(fo, dt, 1);
		    fprintf(fo, ".\n");
		    break;
		} else if (strcmp(s1, "spaminfo") == 0) {
		    if (DOpts.SpamFilterOpt != NULL)
			DumpSpamFilterCache(fo, 0);
		    else
			fprintf(fo, "Internal spamfilter disabled\n");
		    fprintf(fo, ".\n");
		    break;
		} else if (strcmp(s1, "feednotify") == 0) {
		    DoFeedNotify(fo, s2);
		    break;
		} else if (strcmp(s1, "listnotify") == 0) {
		    DoListNotify(fo, s2);
		    break;
		} else if (strcmp(s1, "dumpfeed") == 0) {
		    if (s2 != NULL)
			DumpFeedInfo(fo, s2);
		    else
			DumpAllFeedInfo(fo);
		    fprintf(fo, ".\n");
		    break;
		} else if (strcmp(s1, "info") == 0) {
		    fprintf(fo, "Max filedescriptors in use: %d\n", MaxFds);
		    fprintf(fo, "Internal Spamfilter: %s\n",
				DOpts.SpamFilterOpt != NULL ? "enabled" : "disabled");
		    fprintf(fo, "External Spamfilter: %s\n",
				DOpts.FeederFilter ? "enabled" : "disabled");
		    fprintf(fo, "Readonly mode: %s\n",
				ReadOnlyMode ? "on" : "off");
		    fprintf(fo, ".\n");
		    break;
		} else if (strcmp(s1, "exit") == 0 || strcmp(s1, "aexit")==0) {
		    int i;

		    if (s1[0] == 'e')
			retain = RET_CLOSE;

		    fprintf(fo, "211 Exiting\n");
		    fflush(fo);

		    if (Exiting == 0) {
			Exiting = 1;
			for (i = 0; i < MAXFDS; ++i) {
			    if (PidAry[i].tr_Pid) {
				Kill(PidAry[i].tr_Pid, SIGHUP);
			    }
			}
		    } else {
			fprintf(fo, "200 Exit is already in progress\n");
		    }
		    flushFeeds(0);
		} else if (strcmp(s1, "debug") == 0) {
		    char *s2 = strtok(NULL, " \t\r\n");

		    zfreeStr(&SysMemPool, &DebugLabel);

		    if (s2) {
			DebugLabel = zallocStr(&SysMemPool, s2);
			fprintf(fo, "200 Debugging '%s'\n", DebugLabel);
		    } else {
			DebugLabel = NULL;
			fprintf(fo, "200 Debugging turned off\n");
		    }
		} else if (strcmp(s1, "dumphist") == 0) {
		    /* There's a better way to do this JG200103050847 */
		    const char *f1 = PatDbExpand(DumpHistPat);

		    if (f1 && (strcmp(f1, "NONE") != 0)) {
			system(f1);
		    } else {
			fprintf(fo, "400 No dumphist program configured\n");
		    }
		} else if (strcmp(s1, "config") == 0) {
		    if (s2 != NULL) {
			char *opt;
			char *cmd;
			if ((cmd = strtok(s2, " \t")) != NULL &&
			    (opt = strtok(NULL, " \t")) != NULL) {
			    int cmdErr = 0;
			    if (SetCommand(fo, cmd, opt, &cmdErr) == 0) {
				CheckConfigOptions(1);
				fprintf(fo, "Set '%s' to '%s'\n", cmd, opt);
				InitSpamFilter();
			    } else if (cmdErr == 1) {
				fprintf(fo, "Invalid config command: %s %s\n",
								cmd, opt);
				fprintf(fo, "Usage: config command option\n");
			    } else {
				fprintf(fo, "Invalid option for config command: %s %s\n",
								 cmd, opt);
				fprintf(fo, "Usage: config command option\n");
			    }
			} else {
			    DumpConfigOptions(fo, s2, CONF_FEEDER);
			}
		    } else {
			DumpConfigOptions(fo, NULL, CONF_FEEDER);
		    }
		} else if (strcmp(s1, "help") == 0) {
		    fprintf(fo,"Supported commands:\n");
		    fprintf(fo,"   help     you are looking at it\n");
		    fprintf(fo,"   status   short status line\n");
		    fprintf(fo,"   info     show some running configuration options\n");
		    fprintf(fo,"   version  return diablo version\n");
		    fprintf(fo,"   pause    close all incoming connections and stop accpeting new ones\n");
		    fprintf(fo,"   go       continue after a pause\n");
		    fprintf(fo,"   readonly don't allow incoming articles\n");
		    fprintf(fo,"   config   view/set run-time config\n");
		    fprintf(fo,"   stats    general server statistics\n");
		    fprintf(fo,"   rawstats general server statistics (without pretty printing)\n");
		    fprintf(fo,"   spaminfo dump internal spamfilter cache\n");
		    fprintf(fo,"   dumpfeed dump in-memory copy of outgoing feed details\n");  
		    fprintf(fo,"   exit     close all connections and exit\n");
		} else {
		    fprintf(fo, "500 Unrecognized command: %s\n", s1);
		}
		if (retain == 0)
		    fprintf(fo, ".\n");
		fflush(fo);
		if (retain)
		    break;
	    }
	}
	if (retain) {
	    Retain *ret = zalloc(&ParProcMemPool, sizeof(Retain));

	    ret->re_Next = ReBase;
	    ret->re_Fi = fi;
	    ret->re_Fo = fo;
	    ret->re_What = retain;
	    ReBase = ret;
	} else {
	    if (fi)
		fclose(fi);
	    if (fo)
		fclose(fo);
	}
	{
	    fd_set rfds = RFds;
	    struct timeval tv = { 0, 0 };

	    select(ufd + 1, &rfds, NULL, NULL, &tv);
	    if (!FD_ISSET(ufd, &rfds))
		count = 1;
	}
    }
}

/*
 * Handle feed registration and notification
 *
 * We get a dicmd command of 'feednotify switch: on|off:label
 *
 * We then send 1 byte packets to the Unix domain DGRAM socket when
 * an article has been flushed to the dqueue file for that label.
 */
void
DoFeedNotify(FILE *fo, char *info)
{
    Feed *fe;
    int which = 1;

    if (info == NULL)
	return;
    if (strncmp(info, "off", 3) == 0)
	which = 0;
    info = strchr(info, ':');
    if (info == NULL)
	    return;
    info++;
    for (fe = FeBase; fe != NULL; fe = fe->fe_Next) {
	    if (strcmp(info, fe->fe_Label) == 0)
		break;
    }
    if (fe == NULL)
	return;
    if (which == 0) {
	if (fe->fe_NotifyFd != -1) {
	    close(fe->fe_NotifyFd);
	    fe->fe_NotifyFd = -1;
	}
	return;
    }
    if (fe->fe_NotifyFd != -1) {
	close(fe->fe_NotifyFd);
	fe->fe_NotifyFd = -1;
    }
    fe->fe_NotifyFd = OpenFeedNotify(fe->fe_Label);
}

void
DoListNotify(FILE *fo, char *l)
{
    Feed *fe;

    for (fe = FeBase; fe != NULL; fe = fe->fe_Next) {
	if (l == NULL || strcmp(l, fe->fe_Label) == 0)
	    fprintf(fo, "%3s %s\n", 
				(fe->fe_NotifyFd == -1) ? "off" : "on",
				fe->fe_Label);
    }
}

/*
 * REMOTE FEED CODE
 */

FILE *Ft;
int  FtNumCommit;

void
FeedRSet(FILE *fo)
{
    Ft = xfopen("w", "%s/%s.new", PatExpand(FeedsHomePat), HLabel);
    if (Ft != NULL) {
	FeedTableReady = 1;
	FtNumCommit = 0;
	xfprintf(fo, "290 Label Reset\r\n");
    } else {
	xfprintf(fo, "490 file create failed\r\n");
    }
}

void
FeedList(FILE *fo)
{
    FILE *f;
    char buf[MAXGNAME + 20];
    f = xfopen("r", "%s/%s", PatExpand(FeedsHomePat), HLabel);
    if (f != NULL) {
	char *cmd;
	char *pat;
	char *p;
	xfprintf(fo, "291 Feed list follows\r\n");
	while (!feof(f) && fgets(buf, sizeof(buf), f) != NULL) {
	    buf[strlen(buf) - 1] = '\0';
	    cmd = buf;
	    for (p = cmd; *p && !isspace((int)*p); p++);
	    *p = '\0';
	    if (strcmp(cmd, "addgroup") == 0)
		cmd = "feedadd";
	    else if (strcmp(cmd, "delgroup") == 0)
		cmd = "feeddel";
	    else if (strcmp(cmd, "delgroupany") == 0)
		cmd = "feeddelany";
	    for (pat = ++p; *pat && isspace((int)*pat); pat++);
	    xfprintf(fo, "%s %s\r\n", cmd, pat);
	}
	fclose(f);
	xfprintf(fo, ".\r\n");
    } else {
	xfprintf(fo, "490 No feed information available\r\n");
    }
}

void
FeedCommit(FILE *fo)
{
    char path1[256+64];
    char path2[256+64];

    sprintf(path1, "%s/%s.new", PatExpand(FeedsHomePat), HLabel);
    sprintf(path2, "%s/%s", PatExpand(FeedsHomePat), HLabel);
    fflush(Ft);
    if (ferror(Ft)) {
	remove(path1);
	xfprintf(fo, "490 file write failed\r\n");
    } else {
	if (FtNumCommit == 0) {
	    xfprintf(fo, "290 empty feed list, reverting to initial\r\n");
	    remove(path1);
	    remove(path2);
	} else {
	    xfprintf(fo, "290 feed commit complete\r\n");
	    rename(path1, path2);
	}
        TouchNewsFeed();
    }
    fclose(Ft);
    FeedTableReady = 0;
}

void
FeedAddDel(FILE *fo, char *gwild, int add)
{
    int i;

    if (strlen(gwild) > MAXGNAME - 8) {
	xfprintf(fo, "490 wildcard too long\r\n");
	return;
    }
    if (FtNumCommit == MAXFEEDTABLE) {
	xfprintf(fo, "490 too many entries, max is %d\r\n", MAXFEEDTABLE);
	return;
    }
    for (i = 0; gwild[i]; ++i) {
	if (isalnum((int)gwild[i]))
	    continue;
	if (gwild[i] == '*')
	    continue;
	if (gwild[i] == '?')
	    continue;
	if (gwild[i] == '.')
	    continue;
	if (gwild[i] == '-')
	    continue;
	if (gwild[i] == '+')
	    continue;
	xfprintf(fo, "490 illegal character: %c\r\n", gwild[i]);
	break;
    }
    if (gwild[i] == 0) {
	xfprintf(fo, "290 ok\r\n");
	fprintf(Ft, "%s\t%s\n",
		((add == 1) ? "addgroup" :
		 (add == -1) ? "delgroup" :
		 /* add == -2 */ "delgroupany"), gwild);
	++FtNumCommit;
    }
}

void
FinishRetain(int what)
{
    Retain *ret;
    Retain **pret;

    for (pret = &ReBase; (ret = *pret) != NULL; ) {
	if (ret->re_What == what) {
	    if (ret->re_Fo)
		fprintf(ret->re_Fo, "200 Operation Complete\n.\n");
	    *pret = ret->re_Next;
	    if (ret->re_Fi)
		fclose(ret->re_Fi);
	    if (ret->re_Fo)
		fclose(ret->re_Fo);
	    zfree(&ParProcMemPool, ret, sizeof(Retain));
	} else {
	    pret = &ret->re_Next;
	}
    }
}

int
QueueRange(const char *label, int *pqnum, int *pqarts, int *pqrun)
{
    FILE *fi = xfopen("r", "%s/.%s.seq", PatExpand(DQueueHomePat), label);
    int r = -1;

    if (fi) {
	int qbeg;
	int qend;
	if (fscanf(fi, "%d %d", &qbeg, &qend) == 2) {
	    r = 0;
	    *pqrun = 0;
	    *pqarts = -1;
	    *pqnum = qend - qbeg;

	    if (*pqnum < 500000) {
		int i;

		*pqarts = 0;
		for (i = qbeg; i < qend; ++i) {
		    FILE *fj = xfopen("r", "%s/%s.S%05d", PatExpand(DQueueHomePat), label, i);
		    if (fj) {
			char buf[256];

			while (fgets(buf, sizeof(buf), fj) != NULL) {
			    ++*pqarts;
			}
			if (xflock(fileno(fj), XLOCK_EX|XLOCK_NB) < 0)
			    ++*pqrun;
			fclose(fj);
		    }
		}
	    }
	}
	fclose(fi);
    }
    return(r);
}

int
countFds(fd_set *rfds)
{
    int i;
    int count = 0;

    for (i = 0; i < MAXFDS; ++i) {
	if (FD_ISSET(i, rfds))
	    ++count;
    }
    return(count);
}

/*
 * ArticleFile() - calculate article filename, open, and cache descriptors.
 *		   assigns h->iter, uses h->exp and h->gmt.  Assign *pbpos
 *		   and lseek's the descriptor to the start position for the
 *		   next article write.  The file is exclusively locked.
 *
 *		   NOTE!  We never create 'older' directories.  This could
 *		   lead to an offset,size being stored for a message, the
 *		   spool file getting deleted, then the spool file getting
 *		   recreated and a new message written.  Then an attempt to 
 *		   feed the old message would result in corrupt data.
 *
 *		   There are a number of post-write reject cases, including
 *		   spam cache hits, file too large, in-transit history 
 *		   collisions, and so forth.  Rather then ftruncate() the
 *		   spool file, the feeder now simply lseek's back and 
 *		   overwrites the dead article.  Diablo will do a final
 *		   ftruncate() on the file when the descriptor is finally
 *		   closed.
 */

typedef struct AFCache {
    int		af_Fd;
    off_t	af_AppendOff;	/* cached append offset		*/
    off_t	af_FileSize;	/* calculated current file size	*/
    uint32	af_Slot;	/* 10 minute bounded    	*/
    uint16	af_Iter;
    uint16	af_Spool;
    time_t	af_OpenTime;
} AFCache;

AFCache AFAry[MAXDIABLOFDCACHE];
int AFNum;
time_t AFFlushTime = 0;

void
ArticleFileInit(void)
{
    int i;

    bzero(&AFAry, sizeof(AFAry));
    for (i = 0; i < MAXDIABLOFDCACHE; ++i)
	AFAry[i].af_Fd = -1;
    AFFlushTime = time(NULL);
}

int
#ifdef USE_ZLIB
ArticleFile(History *h, off_t *pbpos, int clvl, gzFile **cfile)
#else
ArticleFile(History *h, off_t *pbpos, int clvl, char **cfile)
#endif
{
    AFCache	*af = NULL;
    int		rfd = -1;

    /*
     * Look for entry in cache.
     */

    {
	int i;

	for (i = 0; i < AFNum; ++i) {
	    AFCache *raf = &AFAry[i];

	    if (raf->af_Slot == h->gmt && raf->af_Spool == H_SPOOL(h->exp) &&
			raf->af_FileSize + h->bsize < SPOOL_MAX_FILE_SIZE) {
		af = raf;
		break;
	    }
	}
    }

    /*
     * Add new entry to cache.  If we hit the wall, close the last entry
     * in the cache.  If we are in feeder mode, the cache is degenerate with
     * only one entry.  If in reader mode, the cache is reasonably-sized.
     */

    if (af == NULL) {
	int cnt;
	int maxdfd = MAXDIABLOFDCACHE;

	/*
	 * blow away LRU if cache is full
	 */

	if (AFNum == maxdfd) {
	    ArticleFileClose(maxdfd - 1);
	    --AFNum;
	}

	/*
	 * our new entry
	 */

	af = &AFAry[AFNum];

	bzero(af, sizeof(AFCache));

	af->af_Iter = (uint16)(PeerIpHash.h1 ^ (PeerIpHash.h1 >> 16));
	af->af_Fd = -1;
	af->af_Slot = h->gmt;
	af->af_Spool = H_SPOOL(h->exp);

	for (cnt = 0; cnt < 10000; ++cnt) {
	    char path[PATH_MAX];

	    errno = 0;
	    af->af_Iter &= 0x7FFF;

	    h->iter = af->af_Iter;
	    ArticleFileName(path, sizeof(path), h, ARTFILE_FILE);

	    if ((af->af_Fd = open(cdcache(path), O_RDWR|O_CREAT, 0644)) >= 0) {
		struct stat st;

		if (xflock(af->af_Fd, XLOCK_EX|XLOCK_NB) < 0 || 
		    (fstat(af->af_Fd, &st), st.st_nlink) == 0 ||
		     st.st_size + h->bsize >= SPOOL_MAX_FILE_SIZE
		) {
		    close(af->af_Fd);
		    errno = 0;
		    af->af_Fd = -1;
		    ++af->af_Iter;	/* bump iteration */
		    continue;		/* try again 	  */
		}
		++AFNum;
		af->af_OpenTime = time(NULL);
		break;
	    }

	    /*
	     * Does the directory need creating?
	     */
	    if (errno == ENOENT) {
		char tpath[PATH_MAX];
		if (DebugOpt)
		    logit(LOG_DEBUG, "Creating spool directory %s - should already be created", tpath);
		ArticleFileName(tpath, sizeof(tpath), h, ARTFILE_DIR);
		if (mkdir(tpath, 0755) == 0)
		    continue;
	    }
		

	    if (errno == EEXIST) {
		++af->af_Iter;		/* bump iteration */
		continue;		/* try again 	  */
	    }

	    logit(LOG_CRIT, "unable to open/create article file %s (%s) - aborting",
							path, strerror(errno));
	    /*
	     * The intermediate directory is missing
	     */

	    sleep(1);
	    if (errno == ENOSPC || errno == EIO || errno == EMFILE || 
		errno == ENFILE
	    ) {
		break;
	    }

	    /*
	     * The spool article directory is missing. This should not
	     * happen so if it does, abort horribly.
	     */
	    ArticleFileCloseAll();
	    LogSession();
	    ClosePathLog(1);
	    CloseArtLog(1);
	    DiabFilterClose();
	    exit(1);

	} /* for */

	if (af->af_Fd >=0 ) {
	    af->af_FileSize = lseek(af->af_Fd, 0L, 2);
	    af->af_AppendOff = af->af_FileSize;
	}
    }

    /*
     * If we have a good descriptor, set the append position and
     * make sure the entry is at the beginning of the cache.
     */

    if ((rfd = af->af_Fd) >= 0) {
	*pbpos = af->af_AppendOff;
	lseek(rfd, af->af_AppendOff, 0);

#ifdef USE_ZLIB
	if (clvl >= 0 && clvl <= 9) {
	    char mode[10];

	    snprintf(mode, sizeof(mode), "ab%d", clvl);
	    *cfile = gzdopen(dup(rfd), mode);
	    if (*cfile == NULL)
		logit(LOG_ERR, "Unable to gzdopen - not compressing");
	}
#endif
	h->iter = af->af_Iter;
	if (af != &AFAry[0]) {
	    AFCache tmp = *af;
	    memmove(
		&AFAry[1],	/* dest 	*/
		&AFAry[0],	/* source 	*/
		(char *)af - (char *)&AFAry[0]
	    );
	    AFAry[0] = tmp;
	}
    }
    return(rfd);
}

void
ArticleFileCloseAll(void)
{
    int i;

    for (i = 0; i < MAXDIABLOFDCACHE; ++i) {
	if (AFAry[i].af_Fd >= 0)
	    ArticleFileClose(i);
    }
}

void
ArticleFileCacheFlush(time_t t)
{
    int i;

    if (t - AFFlushTime < 600)
	return;
    for (i = 0; i < MAXDIABLOFDCACHE; ++i) {
	if (AFAry[i].af_Fd >= 0 && (t - AFAry[i].af_OpenTime) >= 600)
	    ArticleFileClose(i);
    }
    AFFlushTime = t;
}

void
ArticleFileClose(int i)
{
    AFCache *aftmp = &AFAry[i];

    if (aftmp->af_AppendOff < aftmp->af_FileSize)
	ftruncate(aftmp->af_Fd, aftmp->af_AppendOff);
    close(aftmp->af_Fd);
    bzero(aftmp, sizeof(AFCache));
    aftmp->af_Fd = -1;
}

void
ArticleFileTrunc(int artFd, off_t bpos)
{
    AFCache *af =  &AFAry[0];

    if (af->af_Fd != artFd) {
	logit(LOG_ERR, "internal artFd mismatch %d/%d", artFd, af->af_Fd);
	return;
    }
    if (bpos >= 0)
	af->af_AppendOff = bpos;
}

void
ArticleFileSetSize(int artFd)
{
    AFCache *af =  &AFAry[0];
    off_t bpos = lseek(artFd, 0L, 1);

    if (af->af_Fd != artFd) {
	logit(LOG_ERR, "internal artFd mismatch %d/%d", artFd, af->af_Fd);
	return;
    }
    if (bpos > af->af_FileSize)
	af->af_FileSize = bpos;
    af->af_AppendOff = bpos;
}

int
MapArticle(int fd, char *fname, char **base, History *h, int *extra, int *artSize, int *compressedFormat)
{
    SpoolArtHdr tah = { 0 };

    /* 
     * Fetch the spool header for the article, this tells us how it was
     * stored 
     */

    lseek(fd, h->boffset, 0);
    if (read(fd, &tah, sizeof(SpoolArtHdr)) != sizeof(SpoolArtHdr)) {
        close(fd);
        logit(LOG_ERR, "Unable to read article header (%s)\n",
							strerror(errno));
        return(-1);
    }

    *compressedFormat = (tah.StoreType & STORETYPE_GZIP) ? 1 : 0;

    if (*compressedFormat) {
#ifdef USE_ZLIB
	gzFile *gzf;
	char *p;

	if ((uint8)tah.Magic1 != STORE_MAGIC1 &&
					(uint8)tah.Magic2 != STORE_MAGIC2) {
	    lseek(fd, h->boffset, 0);
	    tah.Magic1 = STORE_MAGIC1;
	    tah.Magic2 = STORE_MAGIC2;
	    tah.HeadLen = sizeof(tah);
	    tah.ArtLen = h->bsize;
	    tah.ArtHdrLen = h->bsize;
	    tah.StoreLen = h->bsize;
	    tah.StoreType = STORETYPE_GZIP;
	}
	gzf = gzdopen(dup(fd), "r");
	if (gzf == NULL) {
	    logit(LOG_ERR, "Error opening compressed article\n");
	    return(-1);
	}
    	*base = (char *)malloc(tah.ArtLen + tah.HeadLen + 2);
	if (*base == NULL) {
	    logit(LOG_CRIT, "Unable to malloc %d bytes for article (%s)\n",
				tah.ArtLen + tah.HeadLen + 2, strerror(errno));
	    gzclose(gzf);
	    return(-1);
	}
	p = *base;
	bcopy(&tah, p, tah.HeadLen);
	p += tah.HeadLen;
	if (gzread(gzf, p, tah.ArtLen) != tah.ArtLen) {
	    logit(LOG_ERR, "Error uncompressing article\n");
	    free(*base);
	    return(-1);
	}
	
	p[tah.ArtLen] = 0;
	*artSize = tah.ArtLen + tah.HeadLen;
	*compressedFormat = 1;
	gzclose(gzf);
#else
        logit(LOG_ERR, "Article was stored compressed and compression support has not been enabled\n");
#endif
    } else {
	    *base = xmap(
		NULL, 
		h->bsize + 1, 
		PROT_READ,
		MAP_SHARED, 
		fd, 
		h->boffset
	    );
	    *artSize = h->bsize;
    }

    if (*base == NULL) {
	logit(LOG_ERR, "Unable to map file %s: %s (%d,%d,%d)\n",
					fname,
					strerror(errno),
					(int)(h->boffset - *extra),
					 (int)(h->bsize + *extra + 1),
					*extra
	);
	*artSize = 0;
	return(-1);
    }
    return(0);
}

int
ArticleOpen(History *h, const char *msgid, char **pfi, int32 *rsize, int *pmart, int *pheadOnly, int *compressed)
{
    int r = -1;
    int z = 0;

    if (pheadOnly)
	*pheadOnly = (int)(h->exp & EXPF_HEADONLY);

    if (compressed != NULL) {
	if (SpoolCompressed(H_SPOOL(h->exp)))
	    *compressed = 1;
	else
	    *compressed = 0;
    } else {
	compressed = &z;
    }
	
    if (pfi) {
	char path[PATH_MAX];
	int fd;

	ArticleFileName(path, sizeof(path), h, ARTFILE_FILE);

	/*
	 * multi-article file ?  If so, articles are zero-terminated
	 */

	*pfi = NULL;
	*rsize = 0;
	*pmart = 1;

	/*
	 * get the file
	 */
	if ((fd = cdopen(path, O_RDONLY, 0)) >= 0) {
	    r = MapArticle(fd, path, pfi, h, pmart, rsize, compressed);

	    /*
	     * Sanity check.  Look for 0x00 guard character, make sure
	     * first char isn't 0x00, and make sure last char isn't 0x00
	     * (the last character actually must be an LF or the NNTP
	     * protocol wouldn't have worked).
	     */
	    if (*pfi == NULL ||
			h->bsize == 0 ||
			(*pfi)[0] == 0 ||
			(*pfi)[*rsize-1] == 0 ||
			(*pfi)[*rsize] != 0
	    ) {
		logit(LOG_ERR, "corrupt spool entry for %s@%d,%d %s",
		    path,
		    (int)h->boffset,
		    (int)h->bsize,
		    msgid
		);
		if (*pfi) {
		    if (*compressed)
			free(*pfi);
		    else
			xunmap(*pfi, *rsize);
		    *pfi = NULL;
		}
	    } 
		
	}
	if (fd != -1)
	    close(fd);
    }
   if (pfi && *pfi)
	r = 0;
    return(r);
}

void
DoArtStats(int statgroup, int which, int bytes)	{
    ++Stats.RecStats.Stats[statgroup];
    ++Stats.RecStats.Stats[which];
    switch (statgroup) {
	case STATS_ACCEPTED:
	    Stats.RecStats.AcceptedBytes += (double)bytes;
	    break;
	case STATS_RECEIVED:
	    Stats.RecStats.ReceivedBytes += (double)bytes;
	    break;
	default:
	    Stats.RecStats.RejectedBytes += (double)bytes;
	    break;
    }
    if (HostStats != NULL) {
	LockFeedRegion(HostStats, XLOCK_EX, FSTATS_IN);
	++HostStats->RecStats.Stats[statgroup];
	++HostStats->RecStats.Stats[which];
	switch (statgroup) {
	    case STATS_ACCEPTED:
		HostStats->RecStats.AcceptedBytes += (double)bytes;
		break;
	    case STATS_RECEIVED:
		HostStats->RecStats.ReceivedBytes += (double)bytes;
		break;
	    default:
		HostStats->RecStats.RejectedBytes += (double)bytes;
		break;
	}
	LockFeedRegion(HostStats, XLOCK_UN, FSTATS_IN);
    }
}

void
DoSpoolStats(which) {
    ++Stats.SpoolStats.Arts[which];
    if (HostStats != NULL) {
	LockFeedRegion(HostStats, XLOCK_EX, FSTATS_SPOOL);
	++HostStats->SpoolStats.Arts[which];
	LockFeedRegion(HostStats, XLOCK_UN, FSTATS_SPOOL);
    }
}



syntax highlighted by Code2HTML, v. 0.9.1