/*
 *	Client routines for Network News Tranfer Protocol ala RFC977
 *
 *	Copyright 1990 Anders Klemets - SM0RGV, All Rights Reserved.
 *	Permission granted for non-commercial copying and use, provided
 *	this notice is retained.
 *
 *	Changes copyright 1990 Bernie Roehl, All Rights Reserved.
 *	Permission granted for non-commercial copying and use, provided
 *	this notice is retained.
 *
 *  Revision history:
 *
 *     May 11, 1990 - br checked for invalid chars in news filenames
 *
 *     May 10, 1990 - br changed date stamp in 'From ' lines to
 *            seconds since GMT (to make parsing and expiry easier)
 *
 *     May 9, 1990 - br added locking of nntp.dat and history files,
 *            second parameter to NNTP DIR, fixed bug in updating of
 *            nntp.dat
 *
 *     early May, 1990 -- br added NNTP TRACE, NNTP DIR,
 *            server-specific newsgroups and connection windows,
 *            locking of newsgroup files using mlock() and rmlock(),
 *            date stamping of 'From ' lines, increased stack space,
 *            updating of nntp.dat only on successful sessions.
 *
 *     July 19, 1990 pa0gri Delinted and cleaned up. (calls and includes)
 *
 *	20 May 92	1.2		GT	Output file in "snews" batch format.
 *	21 May 92	1.3		GT	Fix 1.2.
 *	07 Jun 92	1.4		GT	"connect_wait_val" timeout on connect () call.
 *	11 Jun 92	1.5		GT	Booked out in error.
 *	15 Jun 92	1.6		GT	Flush history file write.
 *
 *	21-Jul-92	Chris Sowden (csowden@mouse.demon.co.uk)
 *					Added NNTP BATCH and NNTP VERBOSE commands.
 *					Re-wrote article fetching section to allow overlapped
 *					operation (batch mode).
 *					Added a 64K bit history hash table.
 *					Added 2 mins time adjustment to allow for a fast clock.
 *					Added status message showing articles, bytes and rate.
 *					Lock "nntp.dat" for duration of session to prevent races.
 *					Numerous changes to speed things up.
 *
 *	08 Aug 92	1.8		MM	Force disc writes of history file.
 *
 *	11-Sep-92	Chris Sowden (csowden@mouse.demon.co.uk)
 *					Look for newsgroup list in data file first.
 *					Allow multiple NEWNEWS requests.
 *					Added NNTP NEWGROUPS command.
 *					Write directly to batch file in newsbatch mode.
 *					Additional messages in verbose mode.
 *					Remove zero length files.
 *
 *	14 Sep 92	GT	Use GMT form of NEWNEWS and NEWGROUPS commands.
 *
 *	14-Sep-92	Chris Sowden (csowden@mouse.demon.co.uk)
 *					Added counting to verbose received mesage.
 *
 *	15 Sep 92	Chris Sowden (csowden@mouse.demon.co.uk)
 *					Fixed reentrancy problem.
 *
 *	15 Sep 92	GT	Fixed race condition on GMT vs. local time reading
 *					between news and mail.
 *					
 * 13-Nov-92	Chris Sowden (csowden@mouse.demon.co.uk)
 *					Added SAFETY command, expanded BATCH command
 *					Uses child processes for initialisation and requests
 *					New KILL file and GET file for selective article fetching
 *					Uses correct "#! rnews nnnn" style newsbatch separator
 *					Improved history file handling (especially for duplicates)
 *					Added progress messages/statistics in verbose mode
 *					Now logs all error messages and news summary
 *					
 * 01-Dec-92	Chris Sowden (csowden@mouse.demon.co.uk)
 *					Improved the opening sequence of KILL and GET files
 *					Corrected error levels of file opens
 *					Close temporary new message file
 *					Tried to reduce likelihood of lock files being left around
 *					Removed extra linefeed from batch files
 *
 *	08 May 93	1.13	GT	Fix warnings.
 *							Reinstate 2 minute fudge factor.
 *							Drop the word "article" from the "news
 *							arrived" message.
 *							Fix signed variables problem.
 *
 * 07 Nov 93	1.14	GT	Display NEWNEWS results when "nntp verb on".
 * 						Fix dot transparency. Fix blank lines in error log.
 * 
 * $Id: nntpcli.c 1.14 94/01/04 14:09:53 ROOT_DOS Exp $
 *
 * -------------------------------------------------------------------------
 *
 * Atari ST NOS Version by David Nash - dnash@chaos.demon.co.uk
 *
 * 16.08.94 - Removed Local profile code, mail style message batches,
 *            nntp_reg call
 * 25.07.94 - Improve status messages in donntpstat,
 *            Local profile code conditional on NNTP_PROF (default disabled)
 * 03.07.94 - response >= 400 now trace level 1 (from level 2)
 * 05.06.94 - newsbatch default changed to output in rnews fromat
 * 05.08.93 - articles for posting now suffixed .nwk and new format
 * 09.05.93 - added prototypes for __regargs 
 * 18.04.93 - rnews batched or unbatched format now selectable 
 * 09.04.93 - added donnstatus to report the status of a nntp session
 * 10.03.93 - added Profile and Post subcommands from ANOS version by G1YYH
 * 18.07.93 - post articles from Newsqdir, suffix now .nws. Fixed nntplist
 *
 * -------------------------------------------------------------------------
 */

#include <sys/types.h>
#include <sys/stat.h> /* ST NOS */
#include <time.h>
#ifndef ATARI			 /* ST NOS */
#include <sys/timeb.h>
#endif
#include <ctype.h>
#include <string.h>  /* for strchr() */
#include <io.h>
#ifdef MSDOS
#include <dir.h>
#endif
#include "global.h"
#include "timer.h"
#include "cmdparse.h"
#include "commands.h"
#include "socket.h"
#include "usock.h"
#include "netuser.h"
#include "proc.h"
#include "smtp.h"
#include "files.h"
#include "session.h" /* ST NOS */
#include "main.h" /* for main_exit */

void 	filedir(char *, int, char *);		/* from filedir.c							*/

/* The grouplist must fit on a line like "NEWNEWS <grouplist> 000000 000000 GMT\n" */

#define NNTPMAXLEN	512
#define NNTPMAXGROUPLIST NNTPMAXLEN-28

static struct nntpservers {
	struct timer nntpcli_t;
	char *name;
	char *groups;
	int lowtime, hightime;  /* for connect window */
	struct nntpservers *next;
};

struct textline {
	struct textline *nextline;					/* pointer to next text line 		*/
	char text[NNTPMAXLEN];						/* array holding text  				*/
};

struct	nntprequest;							/* hide nntprequest struct tag	*/

#define	NULLTEXTLINE (struct textline *) 0
#define	MINTEXTLINESIZE (sizeof(struct textline)-NNTPMAXLEN)

#define	NULLNNTP	(struct nntpservers *)NULL

#define	NEWSBATCH TRUE							/* output for batching newsreader */
#define	NEWSBATCHFORMAT "#! rnews %010lu\n"

static int newsbatch = 1;						/* output for offline unbatch		*/

struct nntpservers *Nntpservers = NULLNNTP;
static char *Nntpgroups = NULLCHAR;
static unsigned short nntptrace = 1;
static int nntpbatch 		= 0;
static int nntpbatchsize 	= 2;
static int nntpnewgroups 	= 0;
static int nntpsafety 		= 1;
static int nntpverbose 		= 1;

static char *validchars = "abcdefghijklmnopqrstuvwxyz0123456789-_";

ext_dokicks(void);
static void nntptick(void *tp);

static int dobatch(int argc,char *argv[],void *p);
static int doadds(int argc,char *argv[],void *p);
static int dondir(int argc,char *argv[],void *p);
static int dodrops(int argc,char *argv[],void *p);
static int dogroups(int argc,char *argv[],void *p);
static int dokicks(int argc,char *argv[],void *p);
static int dolists(int argc,char *argv[],void *p);
static int donewgroups(int argc,char *argv[],void *p);
static int dosafety(int argc,char *argv[],void *p);
static int donntrace(int argc,char *argv[],void *p);
static int doverbose(int argc,char *argv[],void *p);

static int donnstatus(int argc,char *argv[],void *p);
static int donntplist(int argc,char *argv[],void *p);
static int donntpkill(int argc,char *argv[],void *p);

static void error(int level,char *text);

#ifdef NEVER
static void freetextlinelist(struct textline **list);
#endif

static void createkilllists(void);
static void scankilllists(char *line,int *status);

static unsigned int hashmsgid(char *msgid,unsigned int modulus);
static void createhashtable(void);
static int isinhashtable(char *msgid);
static int isinhistory(char *msgid);
static void addtohashtable(char *msgid);
static void addtohistory(char *msgid);

static int stripgroups(char *s);
static unsigned long unitspersecond(unsigned long units,unsigned long milliseconds);

#ifdef NEVER
static int getreply(void);
static int getheader(struct nntprequest *request);
static void moveheadertofile(FILE *fp,struct nntprequest *request);
static int gettxt(FILE *fp,struct nntprequest *request, int newnews);
static int putarticle(FILE *msgf,long int *msgfpos,struct nntprequest *request);
#endif

static void __stdargs nntp_init(int argc,void *argv,void *parentproc);
static void __stdargs nntp_send(int argc,void *argv,void *parentproc);
static void __stdargs nntp_main(int i1,void *tp,void *v1);

static void freetextlinelist(struct textline **);
static void createkilllists(void);
static void createhashtable(void);
static int  getreply(void);
static int  getheader(struct nntprequest *);
static void moveheadertofile(FILE *, struct nntprequest *);
static int  putarticle(FILE *, long int *, struct nntprequest *);
static void nntp_reg (void);

/* Tracing levels:
	0 - no tracing
	1 - serious errors reported
	2 - transient errors reported
	3 - session progress reported
	4 - actual received articles displayed
 */

static struct cmds Nntpcmds[] = {
	{ "addserver",		doadds,		0,		3,	"nntp addserver <nntpserver> <interval>"},
	{ "batch",			dobatch,		0,		0,	NULLCHAR},
	{ "directory",		dondir,		0,		0,	NULLCHAR},
	{ "dropserver",	dodrops,		0,		2, "nntp dropserver <nntpserver>"},
	{ "groups",			dogroups,	0,		0,	NULLCHAR},
	{ "kick",			dokicks,		0,		0, "nntp kick <nntpserver>"},
   { "kill",        	donntpkill,	0, 	2, "kill <jobnumber>"},
	{ "listservers",	dolists,		0,		0,	NULLCHAR},
   { "messages",    	donntplist,	0, 	0, NULLCHAR},
	{ "newgroups",		donewgroups,0, 	0, NULLCHAR},
	{ "safety",			dosafety,	0,		0,	NULLCHAR},
	{ "status",			donnstatus,	0,		0,	NULLCHAR},
	{ "trace",			donntrace,	0,		0,	NULLCHAR},
	{ "verbose",		doverbose,	0,		0,	NULLCHAR},
	{ NULLCHAR },
};

int donntp(int argc, char *argv[], void *p)
{
	return subcmd(Nntpcmds,argc,argv,p);
}

static int doadds(int argc, char *argv[], void *p)
{
	struct nntpservers *np;

	for (np = Nntpservers; np != NULLNNTP; np = np->next)
		if (stricmp(np->name, argv[1]) == 0)
			break;

	if (np == NULLNNTP) {
		np = (struct nntpservers *) callocw(1,sizeof(struct nntpservers));
		np->name = strdup(argv[1]);
		np->next = Nntpservers;
		Nntpservers = np;
		np->groups = NULLCHAR;
		np->lowtime = np->hightime = -1;
		np->nntpcli_t.func = nntptick;	/* what to call on timeout */
		np->nntpcli_t.arg = (void *)np;
	}
	
	if (argc > 3) {
		int i;
		if (np->groups == NULLCHAR) {
			np->groups = mallocw(NNTPMAXLEN);
			*np->groups = '\0';
		}
		for (i = 3; i < argc; ++i) {
			if (isdigit(*argv[i])) {
				int lh, ll, hh, hl;
				sscanf(argv[i], "%d:%d-%d:%d", &lh, &ll, &hh, &hl);
				np->lowtime = lh * 100 + ll;
				np->hightime = hh * 100 + hl;
			} else if ((strlen(np->groups)+1+strlen(argv[i])) > NNTPMAXGROUPLIST)
				tprintf("Group list too long!  Group '%s' ignored!\n", argv[i]);
			else {  /* it's a group, and it fits... add it to list */
				if (*np->groups != '\0')
					strcat(np->groups, ",");
				strcat(np->groups, argv[i]);
			}
		}
		if (*np->groups == '\0') {	/* No groups specified? */
			free(np->groups);
			np->groups = NULLCHAR;
		}
	}
	/* set timer duration */
	set_timer(&np->nntpcli_t,atol(argv[2])*1000L);
	start_timer(&np->nntpcli_t);		/* and fire it up */
	return 0;
}

static int dodrops(int argc, char *argv[], void *p)
{
	struct nntpservers *np, *npprev = NULLNNTP;

	for (np = Nntpservers; np != NULLNNTP; npprev = np, np = np->next)
		if (stricmp(np->name,argv[1]) == 0) {
			stop_timer(&np->nntpcli_t);
			free(np->name);
			if (np->groups)
				free(np->groups);
			if(npprev != NULLNNTP)
				npprev->next = np->next;
			else
				Nntpservers = np->next;
			free((char *)np);
			return 0;
	}
	tprintf("No such server enabled\n");
	return 0;
}


static int dolists(int argc, char *argv[], void *p)
{
	struct nntpservers *np;
	
	for (np = Nntpservers; np != NULLNNTP; np = np->next) {
		char tbuf[80];
		if (np->lowtime != -1 && np->hightime != -1)
			sprintf(tbuf, " -- %02d:%02d-%02d:%02d", np->lowtime/100, np->lowtime%100, np->hightime/100, np->hightime%100);
		else
			tbuf[0] = '\0';
		tprintf("%-32s (%lu/%lu%s) %s\n", np->name,
			read_timer(&np->nntpcli_t) /1000L,
			dur_timer(&np->nntpcli_t) /1000L,
			tbuf, np->groups ? np->groups : "");
	}
	return 0;
}


static int donntrace(int argc, char *argv[], void *p)
{
	return setshort(&nntptrace, "NNTP tracing", argc, argv);
}
	
static char *News_spool = NULL;
static int np_all = 0;  /* non-zero if Newsdir is a malloc'ed space */

static int dondir(int argc, char *argv[], void *p)
{
	if (argc < 2) {
		tprintf("spool: %s\n", News_spool ? News_spool : Mailspool);
		tprintf("control: %s\n", Newsdir);
	} else {
		char *p;
		if ((p = strchr(argv[1], '=')) != NULLCHAR) {  /* set a groupdir */
				tprintf("Directory table not supported\n");
		}
		else {  /* no '=', so just set default */
			if (News_spool)
				free(News_spool);
			News_spool = strdup(argv[1]);
		}

		if (argc > 2) {  /* they specified a newsdir as well */
			if (np_all)
				free(Newsdir);
			Newsdir = strdup(argv[2]);
			np_all = 1;
		}
	}
	return 0;
}


int ext_dokicks(void)
{
	char *args[] = { "" };
 	
 	dokicks(1, args, NULL);
	return 0;
}


static int dokicks(int argc, char *argv[], void *p)
{
	struct nntpservers *np;

	for (np = Nntpservers; np != NULLNNTP; np = np->next)
		if (argc == 1 || stricmp(np->name,argv[1]) == 0) {
			/* If the timer is not running, the timeout function has
			* already been called and we don't want to call it again.
			*/
			if(run_timer(&np->nntpcli_t)) {
				stop_timer(&np->nntpcli_t);
				nntptick((void *)np);
			}
			if (argc != 1)
				return 0;
	}
	if (argc != 1)
		tprintf("No such server enabled.\n");
	return 0;
}


static int dogroups(int argc, char *argv[], void *p)
{
	char grouplist[NNTPMAXLEN];
	int i;

	if (argc < 2) {
		if(Nntpgroups == NULLCHAR || (Nntpgroups != NULLCHAR && strcmp(Nntpgroups,"*") == 0))
			tprintf("All groups are currently enabled\n");
		else
			tprintf("Currently enabled newsgroups:\n%s\n",Nntpgroups);
		return 0;
	}
	if (Nntpgroups != NULLCHAR)
		free(Nntpgroups);

	*grouplist = '\0';

	for (i=1; i < argc; ++i) {
		if ((strlen(grouplist)+1+strlen(argv[i])) > NNTPMAXGROUPLIST)
			tprintf("Group list too long!  Group '%s' ignored!\n", argv[i]);
		else {
			if(i > 1)
				strcat(grouplist,",");
			strcat(grouplist,argv[i]);
		}
	}
	Nntpgroups = strdup(grouplist);
	return 0;
}


static int dobatch(int argc, char *argv[], void *p)
{
	if (argc < 2) {
		tprintf("NNTP batch mode: %s, buffers: %d\n",nntpbatch ? "on":"off",nntpbatchsize);
		return 0;
	}
	if (argc > 2) {
		nntpbatchsize = atoi(argv[2]);
	}
	return setbool(&nntpbatch,"NNTP batch mode",argc,argv);
}


static int dosafety(int argc, char *argv[], void *p)
{
	return setbool(&nntpsafety, "NNTP safety mode", argc, argv);
}


static int doverbose(int argc, char *argv[], void *p)
{
	return setbool(&nntpverbose, "NNTP verbose mode", argc, argv);
}


static int donewgroups(int argc, char *argv[], void *p)
{
	return setbool(&nntpnewgroups, "NNTP get new groups", argc, argv);
}


/*
	nntptick - This is the routine that gets called every so often to connect to
              NNTP servers.
*/

static void nntptick(void *tp)
{
	newproc("NNTP client main", 3072, nntp_main, 0, tp, NULL, 0);
}

struct nntprequest {
	char 		msgid[NNTPMAXLEN];			/* id of requested header/body/article */
	struct 	textline *header;				/* header text */
	unsigned int lines;						/* lines in header/body/article */
	unsigned long chars;						/* characters in header/body/article */
	enum {
		reqempty = 0,							/* vacant request */
		reqgetarticle,							/* article from get file */
		reqnewarticle,							/* article from new file */
		reqheader,								/* header only */
		reqbody,									/* body to accompany header */
		reqkill,									/* kill this header */
		reqkeep,									/* get a body to accompany this header */
		reqend,									/* no more requests */
		reqquit									/* close connection */
	} status;
	struct nntprequest *nextrequest;		/* pointer to next request */
};

#define NULLNNTPREQUEST (struct nntprequest *) 0

struct nntpsession {
	int s;										/* NNTP socket */

	struct proc *childproc;					/* child process, either init or send */

	FILE *getmsgf;								/* file holding messages to get */
	FILE *newmsgf;								/* temporary file for NEWNEWS response */
	FILE *historyf;							/* history file */
	unsigned char *hashtable;				/* history hash table */

	struct nntprequest
		*requesthead,							/* request pointer used by sender */
		*requesttail;							/* request pointer used by receiver */

	struct textline
		*killlist,								/* kill patterns */
		*keeplist;								/* keep patterns */

	int quit;									/* if true quit immediately */

	unsigned int historyentries;			/* number of entries in history file */
	unsigned int historyscans;				/* number of complete history file scans */
	unsigned int getarticles;				/* number of lines in get file */
	unsigned int getinvalid;				/* number of invalid ids from get file */
	unsigned int getunavailable;			/* number of articles from get file unavailable */
	unsigned int getreceived;				/* number of complete articles received */
	unsigned int newarticles;				/* number of new article message ids received */
	unsigned int newduplicates;			/* number of duplicate articles */
	unsigned int newunavailable;			/* number of articles unavailalable */
	unsigned int newheadersreceived;		/* number of headers only received */
	unsigned int newreceived;				/* number of complete articles received */
	unsigned int groupsreceived;			/* number of new groups received */
	unsigned long receivedarticlechars;	/* number of characters in articles received */
	unsigned long receivednewschars;		/* number of characters in news altogether */
	unsigned long receivedtotalchars;	/* number of characters received altogether */
	unsigned long receivedheaderchars;	/* total number of header characters */
	unsigned long startsession;			/* clock value at start of session */
	unsigned long startarticles;			/* clock value at start of article fetch */
	unsigned long endsession; 				/* clock value at end of session */
};

#define NULLNNTPSESSION (struct nntpsession *) 0

static struct nntpsession *nntp = NULLNNTPSESSION;

static void error(int level, char *text)
{
	char *p;							/* -> text							*/

	p = mallocw(strlen (text) + 1);
	(void) strcpy(p, text);
	rip(p);

	if (level <= 2)
		log(nntp->s, "%s", p);

	if (level <= nntptrace || nntpverbose != 0)
		tprintf("%s\n", p);

	free (p);
}


static void
freetextlinelist(list)
struct textline **list;
{
	struct textline *lastline, *thisline = *list;

	while (thisline != NULLTEXTLINE) {
		lastline = thisline;
		thisline = thisline->nextline;
		free(lastline);
	}
	*list = NULLTEXTLINE;
}


static void
createkilllists()
{
	FILE *killf;
	char buf[NNTPMAXLEN], *s, *d;
	int keep, n, keeplines = 0, killlines = 0;
	struct textline *newline, *lastkillline = NULLTEXTLINE, *lastkeepline = NULLTEXTLINE;	

	/* If it exists, open the kill file */

	sprintf(buf, "%s/kill", Newsdir);
	
	if (access(buf, 0) == 0) {

		if (mlock(Newsdir, "kill")) {
			sprintf(buf, "NNTP Can't lock file %s/kill", Newsdir);
			error(2, buf);
			nntp->quit = TRUE;
			return;
		}
		
		if ((killf = fopen(buf, READ_TEXT)) == NULLFILE) {
			sprintf(buf, "NNTP Can't open file %s/kill", Newsdir);
			error(1, buf);
			rmlock(Newsdir, "kill");
			nntp->quit = TRUE;
			return;
		}

		while (fgets(buf, NNTPMAXLEN, killf) != NULLCHAR) {

			/* strip out extra characters and force lower case */
			s = d = buf;
			n = 0;
			keep = (*s == '!');
			if (keep)
				s++;
			while (*s != '\n' && *s != '\0') {
				if (isspace(*s)) {
					do s++; while (isspace(*s));
					*d++ = ' ';
				} else {
					*d++ = tolower(*s++);
				}
				n++;
			}
			*d = '\0';

			if ((newline = (struct textline *) malloc(MINTEXTLINESIZE+n+1)) == NULLTEXTLINE) {
				error(2, "NNTP Can't allocate kill buffer");
				fclose(killf);
				rmlock(Newsdir, "kill");
				nntp->quit = TRUE;
				return;
			}
			newline->nextline = NULLTEXTLINE;
			memcpy(newline->text, buf, n+1);

			if (keep) {
				if (lastkeepline == NULLTEXTLINE)
					nntp->keeplist = newline;
				else
					lastkeepline->nextline = newline;
				lastkeepline = newline;
				keeplines++;
			} else {
				if (lastkillline == NULLTEXTLINE)
					nntp->killlist = newline;
				else
					lastkillline->nextline = newline;
				lastkillline = newline;
				killlines++;
			}

			if (!nntp->quit && !main_exit)
				pwait(NULL);
		}
		if (nntptrace >= 3)
			tprintf("NNTP Kill file read: %d kill lines, %d keep lines\n",killlines,keeplines);

		fclose(killf);
		rmlock(Newsdir, "kill");
	}
}


static void
scankilllists(line,status)
char *line;
int *status;
{
	char buf[NNTPMAXLEN], *s, *d;
	struct textline *thisline;

	/* strip space and convert to lower case */
	s = line;
	d = buf;
	while (*s != '\n' && *s != '\0') {
		if (isspace(*s)) {
			do s++; while (isspace(*s));
			*d++ = ' ';
		} else {
			*d++ = tolower(*s++);
		}
	}
	*d = '\0';

	thisline = nntp->keeplist;
	while (thisline != NULLTEXTLINE) {
		if (wildmat(buf,thisline->text,NULLCHARP)) {
			if (nntptrace >= 3)
				tprintf("Keep: %s\n      %s\n",buf,thisline->text);
			*status = reqkeep;
			return;
		}
		thisline = thisline->nextline;
	}
	if (*status != reqkill) {
		thisline = nntp->killlist;
		while (thisline != NULLTEXTLINE) {
			if (wildmat(buf,thisline->text,NULLCHARP)) {
				if (nntptrace >= 3)
					tprintf("Kill: %s\n      %s\n",buf,thisline->text);
				*status = reqkill;
				return;
			}
			thisline = thisline->nextline;
		}
	}
}


#define HASH1 32749
#define HASH2 32719

unsigned int
hashmsgid(msgid, modulus)
char *msgid;
unsigned int modulus;
{
	unsigned long int i = 0;

	while (*msgid != '\0') {
		do i = (i<<8)|*msgid++; while (*msgid != '\0' && i < (1L<<24));
		i %= modulus;
	}
	return (unsigned int) i;
}


static void
createhashtable()
{
	char msgid[NNTPMAXLEN];
	unsigned char *hashtable = NULL;
	unsigned int i, lines = 0, collisions = 0;
	int found;

	/* If there is sufficient memory then build a 64K bit hash table from
	 *  the history file.
	 */

	if (availmem() > Memthresh+(8192*sizeof(unsigned char)) &&
			(hashtable = (unsigned char *) calloc(8192,sizeof(unsigned char))) != NULL) {
		rewind(nntp->historyf);
		while (fgets(msgid,NNTPMAXLEN,nntp->historyf) != NULL) {
			lines++;
			found = 1;
			i = hashmsgid(msgid,HASH1);
			if (!(hashtable[i/4]&(0x01<<(i%4)))) {
				hashtable[i/4] |= 0x01<<(i%4);
				found = 0;
			}
			i = hashmsgid(msgid,HASH2);
			if (!(hashtable[i/4]&(0x10<<(i%4)))) {
				hashtable[i/4] |= 0x10<<(i%4);
				found = 0;
			}
			if (found)
				collisions++;
			if (!nntp->quit && !main_exit)
				pwait(NULL);
		}
		rewind(nntp->historyf);
		if (nntptrace >= 3)
			tprintf("NNTP Hash table created: %u ids, %u collisions\n", lines, collisions);
	}
	nntp->hashtable = hashtable;
	nntp->historyentries = lines;
	nntp->historyscans = 1;
}


static int
isinhashtable(msgid)
char *msgid;
{
	unsigned int i, j;

	if (nntp->hashtable == NULL)
		return 1;
	else {
		i = hashmsgid(msgid,HASH1);
		j = hashmsgid(msgid,HASH2);
		if ((nntp->hashtable[i/4]&(0x01<<(i%4))) &&
			 (nntp->hashtable[j/4]&(0x10<<(j%4))))
			return 1;
	}
	return 0;
}


static int
isinhistory(msgid)
char *msgid;
{
	char historyid[NNTPMAXLEN];
	long historyfpos;

	if (isinhashtable(msgid)) {
		historyfpos = ftell(nntp->historyf);
		while(fgets(historyid,NNTPMAXLEN,nntp->historyf) != NULLCHAR) {
			if(strcmp(historyid,msgid) == 0)
				return 1;
			if (!nntp->quit && !main_exit)
				pwait(NULL);
		}
		nntp->historyscans++;
		rewind(nntp->historyf);
		while(fgets(historyid,NNTPMAXLEN,nntp->historyf) != NULLCHAR) {
			if(strcmp(historyid,msgid) == 0)
				return 1;
			if (ftell(nntp->historyf) >= historyfpos)
				break;
			if (!nntp->quit && !main_exit)
				pwait(NULL);
		}
	}
	return 0;
}
	

static void
addtohashtable(msgid)
char *msgid;
{
	unsigned int i, j;

	if (nntp->hashtable != NULL) {
		i = hashmsgid(msgid,HASH1);
		nntp->hashtable[i/4] |= 0x01<<(i%4);
		j = hashmsgid(msgid,HASH2);
		nntp->hashtable[j/4] |= 0x10<<(j%4);
	}
}


static void
addtohistory(msgid)
char *msgid;
{
	long historyfpos;
	int duphandle;

	addtohashtable(msgid);

	historyfpos = ftell(nntp->historyf);
	fseek(nntp->historyf,0L,SEEK_END);
	fputs(msgid,nntp->historyf);
	fflush(nntp->historyf);
	if (nntpsafety) {
		duphandle = dup(fileno(nntp->historyf));
		close(duphandle);
	}
	fseek(nntp->historyf,historyfpos,SEEK_SET);
	nntp->historyentries++;
}


/*
*	stripgroups reformats a group list in situ for a NEWNEWS request
*
*	returns -1 if there is leading space,
*				0 if line is blank otherwise
*				n the number of characters in the resultant string
*/

static int
stripgroups(s)
char *s;
{
	char *d = s;
	int count = 0;

	if (!isspace(*s)) return -1;

	do s++; while (*s == ',' || isspace(*s));

	while (*s != '\0') {
		if (count != 0) {*d++ = ','; count++;}
		do {
			*d++ = *s++; count++;
		} while (*s != '\0' && *s != ',' && !isspace(*s));
		while (*s == ',' || isspace(*s)) s++;
	}
	*d = '\0';
	return count;
}


static unsigned long
unitspersecond(units,milliseconds)
unsigned long units,milliseconds;
{
	if (milliseconds == 0) { /* Avoid divide-by-zero */
		return 0;
	} else {
		if(units < 4294967UL) {
			return (units*1000UL)/milliseconds;
		} else {	/* Avoid overflow */
			return units/(milliseconds/1000UL);
		}
	}
}

/*
	getreply - get reply from nntp server
	         - trace responses at level 3 or higher
*/
	
static int getreply(void)
{
	int n, response;
	char buf[NNTPMAXLEN];

	while ((n = recvline(nntp->s, buf, NNTPMAXLEN)) != -1) {
		nntp->receivedtotalchars += n;
		
		if (nntptrace >= 3)						/* write trace message ?		*/
			tprintf("< %s", buf);
			
		/* skip informative messages and blank lines */

		if(buf[0] == '\n' || buf[0] == '1')
			continue;

		sscanf(buf, "%d", &response);

		if (response >= 400) {
			error (1, buf);			/* display error message */
		}
		return response;
	}
	return -1;
}


static int
getheader(request)
struct nntprequest *request;
{
	char buf[NNTPMAXLEN];
	int n, nlines = 0;
	unsigned long nchars = 0L;
	struct textline *newline, *lastheaderline = NULLTEXTLINE;
	unsigned long endtime, starttime = msclock();

	while ((n = recvline(nntp->s,buf,NNTPMAXLEN)) != -1) {

		nntp->receivedtotalchars += n;
		nchars += n;

		if (nntptrace >= 4)
			tprintf("<==%s", buf);

		if(strcmp(buf,".\n") == 0) {
			request->lines += nlines;
			if (nntptrace >= 3) {
				endtime = msclock();
				tprintf("<--%d lines, %lu chars, %lu sec (%lu bytes/sec)\n",
					nlines,
					nchars,
					(endtime-starttime)/1000UL,
					unitspersecond(nchars,endtime-starttime));
			}
			/* default to getting the body */
			if (request->status != reqkill)
				request->status = reqkeep;
			return nlines;
		}

		nlines++;

		/* check for escaped '.' characters */
#if 0
		if(strcmp(buf,"..\n") == 0) {
			buf[1] = '\n';
			buf[2] = '\0';
			n--;
		}
#endif

		if (*buf == '.') {
			memmove(buf, buf + 1, n);
			n--;
		}

		request->chars += n;

		if ((newline = (struct textline *) malloc(MINTEXTLINESIZE+n+1)) == NULLTEXTLINE) {
			error(2,"NNTP Can't allocate header buffer");
			return -1;
		}
		newline->nextline = NULLTEXTLINE;
		memcpy(newline->text,buf,n+1);

		if (lastheaderline == NULLTEXTLINE)
			request->header = newline;
		else
			lastheaderline->nextline = newline;
		lastheaderline = newline;

		if (request->status != reqkeep)
			scankilllists(buf,(int *) &(request->status));
	}
	sprintf(buf,"NNTP Receive error after %d lines", nlines);
	error(2,buf);
	return -1;
}


static void
moveheadertofile(fp,request)
FILE *fp;
struct nntprequest *request;
{
	struct textline *doneline, *thisline = request->header;

	while (thisline != NULLTEXTLINE) {
		fputs(thisline->text,fp);
		doneline = thisline;
		thisline = thisline->nextline;
		free(doneline);
	}
	request->header = NULLTEXTLINE;
}


static int gettxt(FILE *fp, struct nntprequest *request, int newnews)
{
	char buf[NNTPMAXLEN];
	int n, nlines = 0;
	unsigned long nchars = 0L;
	unsigned long endtime, starttime = msclock();

	while ((n = recvline(nntp->s,buf,NNTPMAXLEN)) != -1) {

		nntp->receivedtotalchars += n;
		nchars += n;

		if (nntptrace >= 4)
			tprintf("<- %s", buf);

		if(strcmp(buf,".\n") == 0) {
			if (nntptrace < 4 && (newnews != 0 && nntpverbose != 0))	{
				tprintf ("\n");
			}

			if (request != NULLNNTPREQUEST)
				request->lines += nlines;
			if (nntptrace >= 3) {
				endtime = msclock();
				tprintf("<--%d lines, %lu chars, %lu sec (%lu bytes/sec)\n",
					nlines,
					nchars,
					(endtime-starttime)/1000UL,
					unitspersecond(nchars,endtime-starttime));
			}
			return nlines;
		}

		nlines++;
		if (nntptrace < 4 && (newnews != 0 && nntpverbose != 0))
			tprintf ("\rNews header %d received ", nlines);

		/* check for escaped '.' characters */
#if 0		
		if(strcmp(buf,"..\n") == 0) {
			buf[1] = '\n';
			buf[2] = '\0';
			n--;
		}
#endif

		if (*buf == '.') {
			(void)memmove(buf, buf + 1, n);
			n--;
		}

		if (request != NULLNNTPREQUEST)
			request->chars += n;

		(void) fputs(buf,fp);
	}

	if (nntptrace < 4 && (newnews != 0 && nntpverbose != 0))
		tprintf ("\n");
	
	sprintf(buf,"NNTP Receive error after %d lines", nlines);
	error(2,buf);
	return -1;
}


static int
putarticle(msgf,msgfpos,request)
FILE *msgf;
long int *msgfpos;
struct nntprequest *request;
{
	char buf[NNTPMAXLEN];
	int duphandle;

	if (EOF == fflush(msgf))  {
		error(1, "NNTP Error writing newsbatch file");
		return -1;
	}

	(void) fseek(msgf,*msgfpos,SEEK_SET);
	fprintf(msgf,NEWSBATCHFORMAT,request->chars);
	if (EOF == fflush(msgf))  {
		error(1, "NNTP Error writing newsbatch file");
		return -1;
	}

	if (nntpsafety) {
		duphandle = dup(fileno(msgf));
		close(duphandle);
	}

	if (nntpverbose) {
		(void) fseek(msgf,*msgfpos,SEEK_SET);
		while(fgets(buf,NNTPMAXLEN,msgf) != NULLCHAR)
			if(buf[0] == '\n' || strnicmp(buf,"Newsgroups: ",12) == 0)
				break;
		if (strnicmp(buf,"Newsgroups: ",12) == 0)
			rip(buf+12);
		else
			buf[12] = '\0';
		tprintf("News arrived (%u/%u): %s, %s",
			nntp->getreceived + nntp->newheadersreceived + nntp->newreceived + 1,
			(nntp->getarticles - nntp->getinvalid - nntp->getunavailable) + 
				(nntp->newarticles - nntp->newduplicates - nntp->newunavailable),
/*			buf+12, */ buf,
			request->msgid);
	}

	/* set new length */
	(void) fseek(msgf,0L,SEEK_END);
	*msgfpos = ftell(msgf);
	fprintf(msgf,NEWSBATCHFORMAT,0UL);

	return 0;
}



static void __stdargs
nntp_init(argc,argv,parentproc)
int argc;
void *argv,*parentproc;
{
	createhashtable();
	createkilllists();	

	psignal(parentproc,0);
	nntp->childproc = NULLPROC;
}


static void __stdargs
nntp_send(argc,argv,parentproc)
int argc;
void *argv,*parentproc;
{
	int quit = FALSE;

	if (nntp->getmsgf != NULLFILE)
		rewind(nntp->getmsgf);
	rewind(nntp->newmsgf);

	while (!quit) {

		switch (nntp->requesthead->status) {

			case reqempty:
				if (nntp->getmsgf != NULLFILE &&
						fgets(nntp->requesthead->msgid,NNTPMAXLEN,nntp->getmsgf) != NULLCHAR) {
					do { /* Find a line with a valid message id */
						char *cp = nntp->requesthead->msgid;
						nntp->getarticles++;
						if (*cp++ != '<') {
							nntp->getinvalid++;
							if (!nntp->quit && !main_exit)
								pwait(NULL);
							continue;
						}
						while (*cp != '>' && isgraph(*cp))
							cp++;
						if (*cp++ == '>') {
							*cp++ = '\n';
							*cp = '\0';
							nntp->requesthead->status = reqgetarticle;
							break;
						}
						nntp->getinvalid++;
						if (!nntp->quit && !main_exit)
							pwait(NULL);
					} while (fgets(nntp->requesthead->msgid,NNTPMAXLEN,nntp->getmsgf) != NULLCHAR);
				}
				if (nntp->requesthead->status == reqempty &&
						fgets(nntp->requesthead->msgid,NNTPMAXLEN,nntp->newmsgf) != NULLCHAR) {
					do {
						if (!isinhistory(nntp->requesthead->msgid)) {
							if (nntp->killlist == NULLTEXTLINE)
								nntp->requesthead->status = reqnewarticle;
							else
								nntp->requesthead->status = reqheader;
							break;
						}
						if (nntpverbose)
							tprintf("News duplicate: article %s",nntp->requesthead->msgid);
						nntp->newduplicates++;
						if (!nntp->quit && !main_exit)
							pwait(NULL);
					} while (fgets(nntp->requesthead->msgid,NNTPMAXLEN,nntp->newmsgf) != NULLCHAR);
				}
				if (nntp->requesthead->status == reqempty)
					nntp->requesthead->status = reqend;
				break;

			case reqkeep:
				nntp->requesthead->status = reqbody;
				break;

			case reqend:
				nntp->requesthead->status = reqquit;
				break;

			default: /* should never get here */
				error(2,"NNTP Send internal error 1");
				nntp->requesthead->status = reqquit;
		}

		if (!nntpbatch && nntp->requesthead != nntp->requesttail)
			pwait(Curproc);

		if (nntp->quit || main_exit) {
			psignal(parentproc,0);
			break;
		}

		switch (nntp->requesthead->status) {
			case reqend:
				break;
			case reqquit:
				if (nntptrace >= 3)
					tprintf("==>QUIT\n");
				usprintf(nntp->s,"QUIT\n");
				quit = TRUE;
				break;
			case reqgetarticle:
			case reqnewarticle:
				if (nntptrace >= 3)
					tprintf("==>ARTICLE %s",nntp->requesthead->msgid);
				usprintf(nntp->s,"ARTICLE %s",nntp->requesthead->msgid);
				break;
			case reqheader:
				if (nntptrace >= 3)
					tprintf("==>HEAD %s",nntp->requesthead->msgid);
				usprintf(nntp->s,"HEAD %s",nntp->requesthead->msgid);
				break;
			case reqbody:
				if (nntptrace >= 3)
					tprintf("==>BODY %s",nntp->requesthead->msgid);
				usprintf(nntp->s,"BODY %s",nntp->requesthead->msgid);
				break;
			default: /* should never get here */
				error(2,"NNTP Send internal error 2");
				if (nntptrace >= 3)
					tprintf("==>QUIT\n");
				usprintf(nntp->s,"QUIT\n");
				quit = TRUE;
		}

		if (nntpbatch && nntp->requesthead->nextrequest == nntp->requesttail)
			pwait(Curproc);

		nntp->requesthead = nntp->requesthead->nextrequest;
		psignal(parentproc,0);

	}

	/* let the folks know we've gone */
	nntp->childproc = NULLPROC;
}

/*
	nntpmain - nntp main routine
*/
 
static void __stdargs nntp_main(int i1, void *tp, void *v1)
{
	int reply, lines, i;
	char buf1[NNTPMAXLEN], buf2[NNTPMAXLEN], *cp;

	struct nntpservers *np = (struct nntpservers *)tp;
	struct sockaddr_in fsocket;

	int 	post_ok;									/* code to post via NNTP - ATARI	*/
	char 	*cp1;
	char	nnfix[9]; 
	char	wgroup[80];
	char	wmsgid[80];
   char	atmp[LINELEN];
   char	wtmp[LINELEN];
	char	ttmp[LINELEN];
	char	wfilename[13];
	FILE	*wfile;
	FILE	*tfile;
	
   FILE *nntp_dat;								/* handle for nntp.dat				*/
	FILE *tmpf1, *tmpf2, *gettmpf, *msgf = NULLFILE;
	long int tmpfpos, msgfpos;
	char datenow[14], lastdate[14];

	if (nntptrace >= 3)
		tprintf("NNTP daemon entered, target = %s\n", np->name);

	if (nntp != NULLNNTPSESSION) { 			/* nntp already active 				*/
		error(2, "NNTP Already active");
		start_timer(&np->nntpcli_t);
		return;
	}

	{ /* check the time - keep a record for updating the data file */
	
		time_t t;
		struct tm *ltm;
		int now;

		time(&t);									/* more portable than gettime() */
		t -= 120L;									/* 2 minute fudge factor			*/
		ltm = localtime(&t);
		now = ltm->tm_hour * 100 + ltm->tm_min;

		if ((np->lowtime < np->hightime) ? 	/* doesn't cross midnight */
			(now < np->lowtime || now >= np->hightime) :
			(now < np->lowtime && now >= np->hightime)) {
				sprintf(buf2, "NNTP Window to '%s' not open", np->name);
				error(2, buf2);
				start_timer(&np->nntpcli_t);
				return;
		}

		ltm = gmtime(&t);
		sprintf(datenow, "%02d%02d%02d %02d%02d%02d", ltm->tm_year%100,
			ltm->tm_mon+1, ltm->tm_mday, ltm->tm_hour, ltm->tm_min, ltm->tm_sec);
	}

	if (availmem() < Memthresh+sizeof(struct nntpsession) ||
		(nntp = (struct nntpsession *)calloc(1, sizeof(struct nntpsession))) == NULLNNTPSESSION) {
		error(2, "NNTP Memory too low");
		start_timer(&np->nntpcli_t);			/* restart nntp ick timer			*/
		return;
	}
	nntp->s = -1;

	if (nntpverbose)
		tprintf("Trying to connect to news server %s ....\n",np->name);

	/* set up a circular list of request buffers 									*/
	
	{
		struct nntprequest *head, *tail;

		if (availmem() < Memthresh+sizeof(struct nntprequest) ||
			(head = tail = 
				(struct nntprequest *)calloc(1, sizeof(struct nntprequest))) == NULLNNTPREQUEST) {
			error(2, "NNTP Memory too low");
			goto quit;
		}
		
		for (i = 1; i < nntpbatchsize || i < 2; i++) {
			if (availmem() < Memthresh+sizeof(struct nntprequest) ||
				(tail->nextrequest =
					(struct nntprequest *)calloc(1, sizeof(struct nntprequest))) == NULLNNTPREQUEST) {
				error(2, "NNTP Memory too low");
				goto quit;
			}
			tail = tail->nextrequest;
		}
		
		nntp->requesttail = nntp->requesthead = tail->nextrequest = head;
	}


	/* Find the server and try to connect 												*/
	
	fsocket.sin_addr.s_addr = resolve(np->name);

	if(fsocket.sin_addr.s_addr == 0) {  	/* No IP address found 				*/
		sprintf(buf2, "NNTP Can't resolve host: %s", np->name);
		error(2, buf2);
		goto quit;
	}
	
	fsocket.sin_family = AF_INET;				/* setup socket						*/
	fsocket.sin_port 	 = IPPORT_NNTP;
	nntp->s = socket(AF_INET,SOCK_STREAM,0);
	sockmode(nntp->s,SOCK_ASCII);

	alarm (connect_wait_val);					/* connection timeout alarm		*/
	
	if	(connect(nntp->s,	(char *)&fsocket,SOCKSIZE) == -1) {
		if (!run_timer(&Curproc->alarm))
			sprintf(buf2, "NNTP Connect failed: timeout");
		else {
			alarm (0L);								/* reset alarm							*/
			cp = sockerr(nntp->s);
			sprintf(buf2, "NNTP Connect failed: %s", cp != NULLCHAR ? cp : "reason unknown");
		}
		error(2, buf2);
		goto quit;									/* quit - connect failed connect */
	}
	
	alarm (0L);										/* reset alarm							*/

	nntp->startsession = msclock();			/* store session start time		*/
	nntp->startarticles = 0;					/* zero article start time			*/

	if (nntpverbose)
		tprintf("Connected to news server\n");

	/* Open the history file 																*/
	
	sprintf(buf1, "%s/history", Newsdir);
	if (mlock(Newsdir, "history")) {
		sprintf(buf2, "NNTP Can't lock file %s", buf1);
		error(2, buf2);
		goto quit; 									/* quit -can't lock history file	*/
	}
	
	if ((nntp->historyf = fopen(buf1, APPEND_TEXT)) == NULLFILE) {
		sprintf(buf2, "NNTP Can't open file %s", buf1);
		error(1, buf2);
		rmlock(Newsdir, "history");
		goto quit;									/* quit - can't open history 		*/
	}

	/* spawn a process to build the internal tables 								*/

	nntp->childproc = newproc("NNTP client init", 1024, nntp_init, 0, NULL, Curproc, 0);

	reply = getreply();							/* get banner from nntp server	*/ 

	if(!(reply == 200 || reply == 201)) { 
		sprintf(buf2, "NNTP Bad reply on banner (response was %d)", reply);
		error(1, buf2);
		goto quit;									/* quit - bad banner					*/
	}

/* --- ATARI Code to support posting of Articles - from ANOS NNTP ---------*/

	if (reply == 201) {
   	if (nntptrace >= 1)
      	tprintf("NNTP - No posting allowed on '%s'\n", np->name);
	}
	else {
		for (filedir(Newsqueue, 0, wfilename);
			  wfilename[0] != '\0';
      	  filedir(Newsqueue, 1, wfilename)) {

      	cp  = wfilename;
      	cp1 = nnfix;

      	while (*cp && *cp != '.')
         	*cp1++ = *cp++;
      	*cp1 = '\0';

      	sprintf(wtmp, "%s/%s", Newsqdir, wfilename);

      	if ((wfile = fopen(wtmp,READ_TEXT)) == NULLFILE) 
         	continue;
      	(void) fgets(wmsgid,LINELEN,wfile);      /* read messageid */
      	rip(wmsgid);

/* Validate the target groups	- post if 1 or more is valid						*/
   
      	post_ok = FALSE;

      	while (fgets(wgroup, LINELEN, wfile)) { /* read target group 		*/
      		rip(wgroup);
      		
	      	if (nntptrace >= 3)				/* write trace message ?			*/
   	      	tprintf("> GROUP %s\n", wgroup);
   	      	
      		usprintf(nntp->s, "GROUP %s\n", wgroup); /* send query			*/
      		reply = getreply();				/* get reply from server			*/
      		
      		if (reply == 411) {
         		if (nntptrace >= 1)
            		tprintf("NNTP No Such Newsgroup '%s'\n", wgroup);
         		continue;						/* try next group in the list		*/
         	}
         	
         	if (reply == 211)
         		post_ok = TRUE;				/* newsgroup OK						*/
			}

      	fclose(wfile);

      	if (post_ok) {							/* post if 1 or more valid groups*/
      	
         	if (nntptrace >= 3)				/* write trace message ?			*/
            	tprintf("> IHAVE %s \n", wmsgid);
            	
         	usprintf(nntp->s, "IHAVE %s\n", wmsgid);	/* offer item 			*/
         	reply = getreply();				/* get reply from server			*/

         	if (reply == 335) {				/* send new article					*/
            	sprintf(atmp, "%s/%s.txt", Newsqdir, nnfix);

            	if ((tfile = fopen(atmp, READ_TEXT)) != NULLFILE) {

						while(fgets(ttmp, LINELEN, tfile) != NULLCHAR) {
               		if (nntptrace >= 4)	/* write trace message ?			*/ 
               			tprintf("> ttmp");
                  	usprintf(nntp->s,ttmp);		/* send line of article		*/
                  }
                  	
						usprintf(nntp->s, ".\n");		/* send end-of-text marker	*/
               	fclose(tfile);
            	}

					reply = getreply();					/* article posted, get rep	*/

					if(reply == 235) {					/* posting OK ?				*/
               	remove(atmp);						/* remove article and		*/
               	remove(wtmp);						/*        work files			*/
            	}
         	}
         	
         	if(reply == 436)
            	if (nntptrace >= 1)
               	tprintf("NNTP '%s' Transfer Failed - Try later\n", wmsgid);
               	
         	if(reply == 435) {
            	if (nntptrace >= 1)
               	tprintf("NNTP Article '%s' Not wanted\n", wmsgid);
            	remove(wtmp);
         	}

         	if(reply == 437) {
            	if (nntptrace >= 1)
               	tprintf("NNTP Article '%s' Rejected\n", wmsgid);
            	remove(wtmp);
         	}
      	}
   	}
	}

	/*----------------------------------------------------------------------*/
	/* Now fetch new messages																*/
	/*----------------------------------------------------------------------*/

	/* Open nntp.dat 																			*/
	
	sprintf(buf1, "%s/nntp.dat", Newsdir);
	if (mlock(Newsdir, "nntp")) {
		sprintf(buf2, "NNTP Can't lock file %s", buf1);
		error(2, buf2);
		goto quit;									/* quit, can't lock nntp.dat		*/
	}
	
	if ((nntp_dat = fopen(buf1, APPEND_TEXT)) == NULLFILE) {
		sprintf(buf2, "NNTP Can't open file %s", buf1);
		error(1, buf2);
		rmlock(Newsdir, "nntp");
		goto quit;									/* quit, cant' open nntp.dat		*/
	}

	/* Open temporary file for message ids */
	
	if ((nntp->newmsgf = tmpfile()) == NULLFILE) {
		error(1, "NNTP Can't open temporary file for message ids");
		fclose(nntp_dat);
		rmlock(Newsdir,"nntp");
		goto quit;									/* quit, can't open msgid file	*/
	}

	/* Look for a line in the data file with a matching server name 			*/
	
	rewind(nntp_dat);
	strcpy(lastdate, "700101 000000");
	i = strlen(np->name);
	cp = buf1 + i;
	
	while (fgets(buf1, NNTPMAXLEN, nntp_dat) != NULLCHAR) {
		if (strnicmp(buf1, np->name, i) == 0 && isspace(*cp)) {
			unsigned long date, time;
			if (sscanf(cp, " %6lu %6lu", &date, &time) == 2)
				sprintf(lastdate, "%06lu %06lu", date, time);
			break;
		}
	}

	/* Look for a newsgroup list in the data file 									*/
	
	i = -1;

	while (fgets(buf2, NNTPMAXLEN, nntp_dat) != NULLCHAR)
		if ((i = stripgroups(buf2)) != 0)
			break;

	/* if there is a newsgroup list in the data file 								*/
	
	if (i > 0) {
		do {
			if (i > NNTPMAXGROUPLIST) {
				error(1, "NNTP Newsgroup line too long");
				fclose(nntp_dat);
				rmlock(Newsdir, "nntp");		/* free lock on nntp.dat			*/
				goto quit;							/* quit, NG line too long			*/
			}
			
			strcpy(buf1, buf2);
			i = -1;

			/* add entries until no more entries, blank line or the list is full */
			
			while ((fgets(buf2, NNTPMAXLEN, nntp_dat) != NULLCHAR) &&
					((i = stripgroups(buf2)) > 0))
				if (strlen(buf1)+i >= NNTPMAXGROUPLIST)
					break;
				else {
					strcat(buf1,",");
					strcat(buf1,buf2); i = -1;
				}

			/* send the request and get the response */
			
			if (nntptrace >= 3)
				tprintf("> NEWNEWS %s %s GMT\n", buf1, lastdate);
#if 0				
			nntp_reg();	
#endif
			usprintf(nntp->s, "NEWNEWS %s %s GMT\n", buf1, lastdate);

			if ((reply = getreply()) != 230 ||
				(lines = gettxt(nntp->newmsgf, NULL, 1)) == -1) {
				sprintf(buf2, "NNTP Error while getting NEWNEWS (response was %d)",
						  reply);
				error(1, buf2);
				fclose(nntp_dat);					/* close nntp.dat						*/
				rmlock(Newsdir, "nntp");		/* remove lock on nntp.dat			*/
				goto quit;							/* quit, NEWNEWS error				*/
			}
			
			nntp->newarticles += lines;

			/* if we're on a blank line then look for more newsgroup entries  */
			
			if (i == 0) {
				i = -1;
				while (fgets(buf2, NNTPMAXLEN, nntp_dat) != NULLCHAR)
					if ((i = stripgroups(buf2)) != 0)
						break;
			}
		} while (i > 0);

	} else { /* use the addservers entry, or the groups entry, or get everything (!) */

		if (nntptrace >= 3)
			tprintf("> NEWNEWS %s %s GMT\n",
				np->groups ? np->groups : (Nntpgroups ? Nntpgroups : "*"),
				lastdate);
				
		usprintf(nntp->s, "NEWNEWS %s %s GMT\n",
			np->groups ? np->groups : (Nntpgroups ? Nntpgroups : "*"),
				lastdate);

		if ((reply = getreply()) != 230 || (lines = gettxt(nntp->newmsgf,NULL, 1)) == -1) {
			sprintf(buf2,"NNTP Error while getting NEWNEWS (response was %d)",reply);
			error(1,buf2);
			fclose(nntp_dat);						/* close nntp.dat						*/
			rmlock(Newsdir,"nntp");				/* remove lock on nntp.dat			*/
			goto quit;								/* quit, NEWNEWS error				*/
		}
		
		nntp->newarticles = lines;
	}

	fclose(nntp_dat);								/* close nntp.dat						*/
	rmlock(Newsdir,"nntp");						/* remove lock on nntp.dat			*/

	if (nntp->quit || main_exit)
		goto quit;

	if (nntpverbose && nntp->newarticles != 0)
		tprintf("News available: %d articles\n", nntp->newarticles);

	if (nntpnewgroups) {

		/* Open the newgroups file */

		sprintf(buf1, "%s/newgroup", Newsdir);
		if (mlock(Newsdir, "newgroup")) {
			sprintf(buf2, "NNTP Can't lock file %s", buf1);
			error(2, buf2);
			goto quit;								/* quit, can't lock newgroups 	*/
		}
		
		if ((tmpf1 = fopen(buf1, APPEND_TEXT)) == NULLFILE) {
			sprintf(buf2, "NNTP Can't open file %s", buf1);
			error(1, buf2);
			rmlock(Newsdir, "newgroup");		/* remove lock on newgroup file  */
			goto quit;								/* quit, can't open newgroup 		*/
		}

		if (nntptrace >= 3)						/* write tracefile ?					*/
			tprintf("> NEWGROUPS %s GMT\n", lastdate);
			
		usprintf(nntp->s, "NEWGROUPS %s GMT\n", lastdate);

		if ((reply = getreply()) != 231 || (lines = gettxt(tmpf1,NULL, 0)) == -1) {
			sprintf(buf2,"NNTP Error while getting NEWGROUPS (response was %d)",reply);
			error(1,buf2);							/* report error						*/
			fclose(tmpf1);							/* close newgroups file				*/
			rmlock(Newsdir,"newgroup");		/* remove lock on newgroups file */
			goto quit;								/* quit, NEWGROUPS error			*/
		}

		fflush(tmpf1);
		fseek(tmpf1, 0L, SEEK_END);
		tmpfpos = ftell(tmpf1);
		fclose(tmpf1);
		if (tmpfpos == 0L) {
			sprintf(buf1,"%s/newgroup",Newsdir);
			remove(buf1);
		}
		rmlock(Newsdir,"newgroup");			/* remove lock on newgroups file */
  
		nntp->groupsreceived = lines;
		
		if (nntpverbose && nntp->groupsreceived != 0)
			tprintf("News available: %d new groups\n", nntp->groupsreceived);
	}


	/* If it exists, open the get file */
	
	sprintf(buf1,"%s/get",Newsdir);

	if (access(buf1, 0) == 0) {
		if (mlock(Newsdir,"get")) {
			sprintf(buf2,"NNTP Can't lock file %s",buf1);
			error(2, buf2);
		} else {
			if ((nntp->getmsgf = fopen(buf1, READ_TEXT)) == NULLFILE) {
				sprintf(buf2, "NNTP Can't open file %s", buf1);
				error(1, buf2);
				rmlock(Newsdir, "get");
			} else {
				
				/* Open temporary file for unavailable message ids */

				sprintf(buf1, "%s/get.tmp", Newsdir);

				if((gettmpf = fopen(buf1,WRITE_TEXT)) == NULLFILE) {
					sprintf(buf2,"NNTP Can't open file %s",buf1);
					error(1, buf2);
					fclose(nntp->getmsgf);
					nntp->getmsgf = NULLFILE;
					rmlock(Newsdir, "get");
				}
			}
		}
	}

	/* Open the batch file 																*/
		
	sprintf(buf1, "%s/batch.txt", News_spool ? News_spool : Mailspool);
	if (mlock(News_spool ? News_spool : Mailspool, "batch")) {
		sprintf(buf2, "NNTP Can't lock file %s", buf1);
		error(2, buf2);
		goto quit;								/* quit, can't lock batch file	*/
	}
		
	if((msgf = fopen(buf1, RW_LOOKUP_TEXT)) == NULLFILE &&
		(msgf = fopen(buf1, RW_CREATE_TEXT)) == NULLFILE) {
		sprintf(buf2, "NNTP Can't open file %s", buf1);
		error(1, buf2);
		rmlock(News_spool ? News_spool : Mailspool, "batch");
		goto quit;								/* quit, can't open batch file	*/
	}
		
	fseek(msgf, 0L, SEEK_END);
	msgfpos = ftell(msgf);
	fprintf(msgf, NEWSBATCHFORMAT, 0UL);

	/* wait for the tables to be built then start up the tx proc 				*/
	
	if (nntp->childproc != NULLPROC)			/* wait for init task to finish	*/
		if (pwait(Curproc) != 0)
			goto quit;

	if (nntp->quit || main_exit)
		goto quit;

	nntp->childproc = newproc("NNTP client send", 1024, nntp_send, 0, NULL, Curproc, 0);

	nntp->receivednewschars = nntp->receivedtotalchars;
	nntp->receivedheaderchars = nntp->receivedtotalchars;
	
	nntp->startarticles = msclock();

	while (!nntp->quit && !main_exit) {

		if (nntp->requesthead == nntp->requesttail) {
			if (nntp->childproc == NULLPROC) {
				error(2, "NNTP Receive internal error");
				goto quit;							/* quit, internel receive error	*/
			}
			
			if (pwait(Curproc) != 0)
				goto quit;
		}

		if (nntp->requesttail->status != reqend) {
			reply = getreply();

			switch (reply) {
				case 205: 							/* closing connection 				*/
					if (nntp->requesttail->status != reqquit) {
						error(2, "NNTP Receive internal error (reply was 205)");
						goto quit;					/* quit, internel receive error	*/
					}
					
					sprintf(buf1, "%s/nntp.dat", Newsdir);
					if (mlock(Newsdir, "nntp")) {
						sprintf(buf2, "NNTP Can't lock file %s", buf1);
						error(2, buf2);
						goto quit;					/* quit, can't lock nntp.dat		*/
					}
					
					if((nntp_dat = fopen(buf1, READ_TEXT)) == NULLFILE) {
						sprintf(buf2, "NNTP Can't open file %s", buf1);
						error(1, buf2);
						rmlock(Newsdir, "nntp");	/* remove nntp.dat lockfile	*/ 
						goto quit;					/* quit, can't open nntp.dat		*/
					}
					
					sprintf(buf1,"%s/nntp.tmp", Newsdir);
					if((tmpf2 = fopen(buf1,WRITE_TEXT)) == NULLFILE) {
						sprintf(buf2, "NNTP Can't open file %s", buf1);
						error(1, buf2);
						fclose(nntp_dat);				/* close nntp.dat						*/
						rmlock(Newsdir, "nntp");	/* remove lock						*/
						goto quit;					/* quit, can't open nntp.tmp		*/	
					}
					
					sprintf(buf1, "%s %s\n", np->name, datenow);
					i = strlen(np->name);
					cp = buf2 + i;
					
					while (fgets(buf2, NNTPMAXLEN, nntp_dat) != NULLCHAR)
						if (strnicmp(buf2,np->name,i) != 0 || !isspace(*cp))
							fputs(buf2, tmpf2);
						else {
							fputs(buf1, tmpf2);
							buf1[0] = '\0';
						}
					if (buf1[0] != '\0')
						fputs(buf1,tmpf2);
						fclose(nntp_dat);
						sprintf(buf1, "%s/nntp.dat", Newsdir);
						sprintf(buf2, "%s/nntp.tmp", Newsdir);

					if (ferror(tmpf2)) {
						fclose(tmpf2);
						remove(buf2);
						error(1, "Error writing new nntp.dat file");
					} else {
						fclose(tmpf2);
						remove(buf1);
						rename(buf2, buf1);
					}

					rmlock(Newsdir,"nntp");		/* remove lock on nntp.dat			*/
					goto quit;


				case 220: 							/* header and body follows 		*/
					if (nntp->requesttail->status != reqgetarticle &&
						nntp->requesttail->status != reqnewarticle) {
						error(2,	"NNTP Receive internal error (reply was 220)");
						goto quit;					/* quit, internel receive error	*/
					}
					
					nntp->requesttail->lines = 0;
					nntp->requesttail->chars = 0UL;

					if (gettxt(msgf, nntp->requesttail, 0) == -1)
						goto quit;
						
					if (putarticle(msgf,&msgfpos,nntp->requesttail) == -1)
						goto quit;

					/*
					*	The following is a compromise: if there is a hash
					*	collision then the id will erroneously not get added.
					*	However, scanning the history file here will cause
					*	problems as the child might be doing the same.
					*/

					if (nntp->requesttail->status == reqgetarticle) {
						if (!isinhashtable(nntp->requesttail->msgid))
							addtohistory(nntp->requesttail->msgid);
						nntp->getreceived++;
					} else {
						addtohistory(nntp->requesttail->msgid);
						nntp->newreceived++;
					}
					nntp->receivedarticlechars += nntp->requesttail->chars;
					nntp->requesttail->status = reqempty;
					break;


				case 221: 							/* header follows 					*/
					if (nntp->requesttail->status != reqheader) {
						error(2, "NNTP Receive internal error (reply was 221)");
						goto quit;					/* quit, internel receive error	*/
					}
					
					nntp->requesttail->lines = 0;
					nntp->requesttail->chars = 0UL;

					if (getheader(nntp->requesttail) == -1)
						goto quit;

					if (nntp->requesttail->status == reqkill) {
						moveheadertofile(msgf,nntp->requesttail);

						if (putarticle(msgf,&msgfpos,nntp->requesttail) == -1)
							goto quit;

						addtohistory(nntp->requesttail->msgid);
						nntp->newheadersreceived++;
						nntp->receivedarticlechars += nntp->requesttail->chars;
						nntp->requesttail->status   = reqempty;
					}
					break;

				case 222: 							/* body follows 						*/
					if (nntp->requesttail->status != reqbody) {
						error(2, "NNTP Receive internal error (reply was 222)");
						goto quit;					/* quit, internal receive error	*/
					}
					
					moveheadertofile(msgf,nntp->requesttail);
					fputc('\n', msgf);
					nntp->requesttail->lines++;
					nntp->requesttail->chars++;

					if (gettxt(msgf,nntp->requesttail, 0) == -1)
						goto quit;

					if (putarticle(msgf,&msgfpos,nntp->requesttail) == -1)
						goto quit;

					addtohistory(nntp->requesttail->msgid);
					nntp->newreceived++;
					nntp->receivedarticlechars += nntp->requesttail->chars;
					nntp->requesttail->status = reqempty;
					break;

				case 430: 							/* no such article 					*/
					if (nntp->requesttail->status != reqgetarticle &&
						 nntp->requesttail->status != reqnewarticle &&
						 nntp->requesttail->status != reqheader) {
						error(2,"NNTP Receive internal error (reply was 430)");
						goto quit;					/* quit, internal receive error	*/
					}
					
					if (nntp->requesttail->status == reqgetarticle) {
						fputs(nntp->requesttail->msgid,gettmpf);
						nntp->getunavailable++;
					} else
						nntp->newunavailable++;

					if (nntpverbose)
						tprintf("News unavailable: article %s",nntp->requesttail->msgid);

					nntp->requesttail->status = reqempty;
					break;

				case -1: /* error */
					error(2, "NNTP Receive error");
					goto quit;						/* quit, NNTP receive error		*/


				default:
					sprintf(buf2, "NNTP Unexpected reply from server (reply was %d)",reply);
					error(2, buf2);
					goto quit;						/* quit, unexpected reply			*/
			}
		}

		/* move on to next request                                           */
		
		nntp->requesttail = nntp->requesttail->nextrequest;
		psignal(nntp->childproc, 0);
	}

	/* quit nntp session																		*/
	
quit:

	nntp->endsession = msclock();
	nntp->receivednewschars = nntp->receivedtotalchars - nntp->receivednewschars;

	/* tidy up as necessary */

	if (msgf != NULLFILE) {
		fflush(msgf);				
		fclose(msgf);
		if (msgfpos == 0L) {
			sprintf(buf1, "%s/batch.txt",News_spool ? News_spool : Mailspool);
			(void) remove(buf1);
		}
		rmlock(News_spool ? News_spool : Mailspool, "batch");
	}
	
	else {
		if (msgf != NULLFILE) {
			fclose(msgf);
		}
	}

	nntp->quit = TRUE;
	if (nntp->childproc != NULLPROC)
		pwait(NULL);
	if (nntp->childproc != NULLPROC)
		killproc(nntp->childproc);

	/* Update get file with message ids we didn't get 								*/
	
	if (nntp->getmsgf != NULLFILE) {
		rewind(nntp->getmsgf);

		for (i = 0;
			  i < (nntp->getinvalid + nntp->getunavailable + nntp->getreceived);
			  i++)
			fgets(buf1, NNTPMAXLEN, nntp->getmsgf);

		while (fgets(buf1,NNTPMAXLEN,nntp->getmsgf) != NULLCHAR)
			fputs(buf1, gettmpf);
			
		fclose(nntp->getmsgf);
		sprintf(buf1, "%s/get", Newsdir);
		sprintf(buf2, "%s/get.tmp", Newsdir);
		
		if (ferror(gettmpf)) {
			fclose(gettmpf);
			remove(buf2);
			error(1,"Error writing new get file");
		} else {
			remove(buf1);
			fflush(gettmpf);
			fseek(gettmpf,0L,SEEK_END);
			tmpfpos = ftell(gettmpf);
			fclose(gettmpf);

			if (tmpfpos == 0L)
				remove(buf2);
			else
				rename(buf2, buf1);
		}
		rmlock(Newsdir,"get");
	}			

	if (nntp->newmsgf != NULLFILE)
		fclose(nntp->newmsgf);

	if (nntp->historyf != NULLFILE) {
		fclose(nntp->historyf);
		rmlock(Newsdir, "history");
	}
	if (nntp->hashtable != NULL)
		free(nntp->hashtable);

	/* Free all dynamically allocated buffer space */
	{
		struct nntprequest
			*tail = nntp->requesthead,
			*head = tail->nextrequest;
		tail->nextrequest = NULLNNTPREQUEST;
		while (head != NULLNNTPREQUEST) {
			freetextlinelist(&head->header);
			tail = head;
			head = head->nextrequest;
			free(tail);
		}
		freetextlinelist(&nntp->killlist);
		freetextlinelist(&nntp->keeplist);
	}

	/* Output statistics */

	if (nntp->getreceived != 0 || nntp->newheadersreceived != 0 || nntp->newreceived != 0) {
		sprintf(buf2,"News summary: %u articles (%lu bytes) in %lu sec (%lu bytes/sec)",
				nntp->getreceived + nntp->newheadersreceived + nntp->newreceived,
				nntp->receivedarticlechars,
				(nntp->endsession - nntp->startarticles)/1000UL,
				unitspersecond(nntp->receivedarticlechars,nntp->endsession - nntp->startarticles));
		log(nntp->s,buf2);
		tprintf(nntpverbose ? "\n%s\n" : "%s\n",buf2);
	}

	if (nntp->getarticles != 0) {
		sprintf(buf2,"Get articles: %u invalid, %u unavailable, %u received",
			nntp->getinvalid,
			nntp->getunavailable,
			nntp->getreceived);
		log(nntp->s,buf2);
		if (nntpverbose)
			tprintf("%s\n",buf2);
	}

	if (nntp->newarticles != 0) {
		sprintf(buf2,"New articles: %u duplicate, %u unavailable, %u headers, %u complete",
			nntp->newduplicates,
			nntp->newunavailable,
			nntp->newheadersreceived,
			nntp->newreceived);
		log(nntp->s,buf2);
		if (nntpverbose)
			tprintf("%s\n",buf2);
	}

	if (nntp->getreceived != 0 || nntp->newheadersreceived != 0 || nntp->newreceived != 0 ||
			nntp->getarticles != 0 || nntp->newarticles != 0) {
		sprintf(buf2,"History file: %u entries, %u complete scans",
			nntp->historyentries,
			nntp->historyscans);
		log(nntp->s,buf2);
		if (nntpverbose)
			tprintf("%s\n",buf2);
		sprintf(buf2,"Throughput  : %lu/%lu bytes in %lu/%lu sec (%lu/%lu bytes/sec)",
			nntp->receivednewschars,
			nntp->receivedtotalchars,
			(nntp->endsession - nntp->startarticles)/1000UL,
			(nntp->endsession - nntp->startsession)/1000UL,
			unitspersecond(nntp->receivednewschars,nntp->endsession - nntp->startarticles),
			unitspersecond(nntp->receivedtotalchars,nntp->endsession - nntp->startsession));
		log(nntp->s,buf2);
		if (nntpverbose)
			tprintf("%s\n\n",buf2);
	}

	if (nntpverbose)
		tprintf("Closing news session\n");

	close_s(nntp->s);
	free(nntp);
	nntp = NULLNNTPSESSION;

	if (nntptrace >= 3)
		tprintf("NNTP daemon exiting\n");

	/* Restart timer */
	start_timer(&np->nntpcli_t);

	return;
}

/*
	nntp_req
*/

static void nntp_reg(void)
{
	struct sockaddr_in sock;
	struct socket lsocket;
	struct socket rsocket;
	struct usock *usp;
	struct sockaddr_in *sinp;
	struct mbuf *bp;
	char buf[64];
 	int s;

	sock.sin_family = AF_INET;
	sock.sin_port = 2911;
	if ((sock.sin_addr.s_addr = resolve ("passwd.demon.co.uk")) == 0 ||
		(s = socket (AF_INET, SOCK_DGRAM, 0)) == -1 ||
		connect (s, (char *) &sock, sizeof (sock)) == -1)
		return;

	(void) sprintf (buf, "%s|%s|%s\n", Version, __DATE__, __TIME__);
	bp = ambufw (strlen (buf) + 1);
	(void) strcpy (bp->data, buf);
	usp = itop(s);
	sinp = (struct sockaddr_in *) usp->name;
	lsocket.address = sinp->sin_addr.s_addr;
	lsocket.port = sinp->sin_port;
	sinp = (struct sockaddr_in *)usp->peername;
	rsocket.address = sinp->sin_addr.s_addr;
	(void) send_udp (&lsocket, &rsocket, 0, 64, bp, bp->size, 0, 0);
	close_s (s);
}	


/*
	donnstats - Print NNTP statistics for current session
*/
	
static int donnstatus(int argc, char *argv[], void *p)
{
	unsigned long timenow;
	
	if (nntp == NULLNNTPSESSION)  {
		tprintf("News: No session active \n");
		return 0;
	}

	timenow = msclock();
	
	if (nntp->startarticles == 0) {				/* Collecting headers			*/
		tprintf("NNTP: Fetching new headers: Time %lu sec, Rate %lu chars/sec \n",
				(timenow - nntp->startsession) / 1000UL,
				unitspersecond(nntp->receivedtotalchars, timenow - nntp->startsession));
#if 0				
		tprintf("      %d headers received, %d duplicate, in %lu sec \n\n",
				nntp->newarticles, nntp->newduplicates,
				(timenow - nntp->startsession) / 1000UL );
#endif
		return 0;
	}
	
	tprintf("NNTP: Fetching articles: Time %lu sec, Rate %lu chars/sec\n",
				(timenow - nntp->startarticles) / 1000UL,
				unitspersecond(nntp->receivedtotalchars - nntp->receivedheaderchars,
				 timenow - nntp->startarticles ));

	tprintf("      %d headers received, %d duplicate, in %lu sec\n",
				nntp->newarticles, nntp->newduplicates,
				(nntp->startarticles - nntp->startsession) / 1000UL );

	if (nntp->getreceived > 0)
		tprintf("      %d/%d get articles received, %d invalid, %d unavailable \n",
				nntp->getreceived,
				nntp->getarticles - nntp->getinvalid - nntp->getunavailable,
				nntp->getinvalid,
				nntp->getunavailable);

	if (nntp->newreceived > 0)
		tprintf("      %d/%d new articles received, %d duplicates, %d unavailable \n",
				nntp->newreceived,
				nntp->newarticles - nntp->newduplicates - nntp->newunavailable,
				nntp->newduplicates,
				nntp->newunavailable);
				
#if 0 				
	if (nntp->getreceived != 0 || nntp->newheadersreceived != 0 || nntp->newreceived != 0) {
		tprintf("\nNews summary: %u/%u articles received \n",
				nntp->getreceived + nntp->newheadersreceived + nntp->newreceived,
				(nntp->getarticles - nntp->getinvalid - nntp->getunavailable) + 
				(nntp->newarticles - nntp->newduplicates - nntp->newunavailable));
	}

	if (nntp->getarticles != 0) {
		tprintf("Get articles: %u invalid, %u unavailable, %u received\n",
			nntp->getinvalid,
			nntp->getunavailable,
			nntp->getreceived);
	}

	if (nntp->newarticles != 0) {
		tprintf("New articles: %u duplicate, %u unavailable, %u headers, %u complete \n",
			nntp->newduplicates,
			nntp->newunavailable,
			nntp->newheadersreceived,
			nntp->newreceived);
	}
#endif
	if (nntp->getreceived != 0 || nntp->newheadersreceived != 0 || nntp->newreceived != 0 ||
			nntp->getarticles != 0 || nntp->newarticles != 0) {
		tprintf("      History file: %u entries, %u complete scans \n",
			nntp->historyentries,
			nntp->historyscans);
	}
	
	return 0;
}


/*
	donntplist - list messages wating to be sent
*/

static int donntplist(int argc, char *argv[], void *p)
{
   char tstring[80], line[20],
        group[LINELEN], *cp;
   struct stat stbuf;
   struct tm *tminfo;
   FILE *fp;

   int	statx(const char *, struct stat *);

   Current->flowmode = 1;          /* Enable the more mechanism */
   tprintf("    Job    Size  Date  Time  Message ID\n");
   filedir(Newsqueue,0,line);
   while(line[0] != '\0') {
      sprintf(tstring,"%s/%s",Newsqdir,line);
      if ((fp = fopen(tstring,READ_TEXT)) == NULLFILE) {
         tprintf("Can't open %s: %s\n",tstring,strerror(errno));
         continue;
      }
      if ((cp = strrchr(line,'.')) != NULLCHAR)
         *cp = '\0';
      sprintf(tstring,"%s/%s.txt",Newsqdir,line);
      statx(tstring,&stbuf);						/* call modified stat routine */
    
      tminfo = localtime(&stbuf.st_ctime);
      fgets(group,sizeof(group),fp);
      rip(group);
      tprintf("%7s %7ld  %02d/%02d %02d:%02d %-25s\n",
         line, stbuf.st_size,
         tminfo->tm_mon+1, tminfo->tm_mday, tminfo->tm_hour,
         tminfo->tm_min, group);
      (void) fclose(fp);
      pwait(NULL);
      filedir(Newsqueue,1,line);
   }
   Current->flowmode = 0;
   return 0;
}


/* kill a job in the mqueue */

static int donntpkill(int argc, char *argv[], void *p)
{
   char s[LINELEN];
   char *cp;

   sprintf(s,"%s/%s.nwk",Newsqdir,argv[1]);
   cp = strrchr(s,'.');
   if (remove(s))
      tprintf("Job id %s not found\n",argv[1]);
   strcpy(cp,".txt");
   (void) remove(s);
   return 0;
}
