[net.sources] vnews source -- visual.c

ka@spanky.UUCP (06/06/83)

/*
 * readr - visual news interface.
 */

static char SccsId[] = "@(#)visual.c	2.26	5/14/83";
static char Author[] = "@(#)visual interface written by Kenneth Almquist";

#ifdef HCURSES		/* Mark Horton's version of curses */
#define CURSES
#endif
#ifdef ACURSES		/* Ken Arnold's version of curses */
#define CURSES
#endif
#ifdef CURSES
#ifdef USG
#ifdef ACURSES
#include <termio.h>
#endif
#endif
#include <curses.h>
#define SLEN LINES
#else
#include <fcntl.h>
extern int _junked;
#define clearok(xxx, flag) _junked = flag
#define COLS 80
#define SLEN 24
#define HASSCROLL
#ifdef USG
#include <termio.h>
#else
#include <sgtty.h>
#endif USG
#endif CURSES

#include <errno.h>
#include "rparams.h"

#ifdef CURSES
#undef LINES
#endif
#define ARTWLEN	(SLEN-2)/* number of lines used to display article */
#define PRLINE	(SLEN-1)/* prompter line */
#define SPLINE	(SLEN-2)/* secondary prompt line */
#define SECPRLEN 100	/* length of secondary prompter */
#define INTR	0177	/* returned by vgetc on interrupt */
#define META	0200	/* meta chatacter bit (as in emacs) */
/* print (display) flags */
#define HDRONLY	0001	/* print header only */
#define NOPRT	0002	/* don't print at all */
#define NEWART	0004	/* force article display to be regenerated */
#define HELPMSG	0010	/* display currently contains help message */
/* prun flags */
#define CWAIT	0001	/* type "continue?" and wait for return */
#define BKGRND	0002	/* run process in the background */
/* values of curflag */
#define CURP1	1	/* cursor after prompt */
#define CURP2	2	/* cursor after secondary prompt */
#define CURHOME	3	/* cursor at home position */
/* shell procedures to reply and post followups */
#ifndef REPLYPROG
#define REPLYPROG "/usr/lib/news/reply"
#endif
#ifndef FOLLOWPROG
#define FOLLOWPROG "/usr/lib/news/followup"
#endif
/* other files */
#ifndef VHELP
#define VHELP "/usr/lib/news/vnews.help"
#endif


#define	saveart	oobit = bit;strcpy(ofilename1, filename);strcpy(ogroupdir, groupdir);hbufcp(&hbuf1, &h);ongsize = pngsize
#define NLINES(h, fp) (h.numlines[0] ? h.intnumlines : (h.intnumlines=linecnt(fp),sprintf(h.numlines, "%d", h.intnumlines), h.intnumlines))

extern int errno;
FILE *tmpfile();
int onint(), onquit();

/*
 * Kludge: space so that Mark's code can access beyond
 * the end of arrays without messing me up.
 */
static char junk[64];

/* variables shared between vnews routines */
static char linebuf[LBUFLEN];		/* temporary workspace */
static FILE *tfp;			/* temporary file */
static char tfname[] = "/tmp/vnXXXXXX";	/* name of temp file */
static long artbody;			/* offset of body into article */
static int quitflg;			/* if set, then quit */
static int erased;			/* current article has been erased */
static int artlines;			/* # lines in article body */
static int artread;			/* entire article has been read */
static int hdrend;			/* end of header */
static int lastlin;			/* number of lines in tempfile */
static int tflinno = 0;			/* next line in tempfile */
static char secpr[SECPRLEN];		/* secondary prompt */
static char prompt[30];			/* prompter */
static short prflags;			/* print flags (controls updscr) */
static short curflag;			/* where to locate cursor */
static int dlinno;			/* top line on screen */
static char timestr[20];		/* current time */
static int ismail;			/* true if user has mail */
static char *mailf;			/* user's mail file */
static int alflag;			/* set if unprocessed alarm signal */
static int atend;			/* set if at end of article */
static char cerase;			/* erase character */
static char ckill;			/* kill character */
static int ospeed;			/* terminal speed */
static int pstatus;			/* status return form process */
static int intflag;			/* set if interrupt received */
#ifdef DIGPAGE
static int endsuba;			/* end of sub-article in digest */
#endif


char *tft = "/usr/tmp/folXXXXXX";


/*
 * These were made static for u370 with its buggy cc.
 * I judged it better to have one copy with no ifdefs than
 * to conditionally compile them as automatic variables
 * in readr (which they originally were).  Performance
 * considerations might warrent moving some of the simple
 * things into register variables, but I don't know what
 * breaks the u370 cc.
 */
static char goodone[BUFLEN];		/* last decent article		*/
static char ogroupdir[BUFLEN];		/* last groupdir		*/
static int rfq = 0;			/* for last article		*/
static long ongsize;			/* Previous ngsize		*/
static long pngsize;			/* Printing ngsize		*/
static char *bptr;			/* temp pointer.		*/
static char *tfilename;			/* temporary file name 		*/
static char ofilename1[BUFLEN];		/* previous file name		*/
static struct hbuf hbuf1, hbuf2, *hptr;	/* for minusing			*/
static char *ptr1, *ptr2, *ptr3;	/* for reply manipulation	*/
static int  news = 0;
static int  aabs = FALSE;		/* TRUE if we asked absolutely	*/
static char *ed, tf[100];
static struct hbuf h;			/* ditto.			*/
static int i;
static int oobit;			/* last bit, really		*/
static char *oldsig;
static int dgest = 0;
static FILE *ofp;			/* Current output file to terminal*/
static FILE *fp;			/* current article to be printed*/
static int holdup;			/* 1 iff should stop before hdr */
static int ignorenews;			/* 1 iff readnews -p > /dev/null*/
static long timelastsaved;		/* time newsrc last written out */

int catchcont();

readr()
{

#ifdef DEBUG
	fprintf(stderr, "readr()\n");
#endif
	if (aflag) {
		if (*datebuf) {
			if ((atime = cgtdate(datebuf)) == -1)
				xerror("Cannot parse date string");
		} else 
			atime = 0L;
	}

	if (pflag && ignoring())
		ignorenews = TRUE;

	if (sigtrap)
		xxit(1);
	mktemp(tfname);
	if ((tfp = fopen(tfname, "w+")) == NULL)
		xerror("Can't create temp file");
	unlink(tfname);
	mailf = getenv("MAIL");
	ttysave();
	signal(SIGINT, onint);
	signal(SIGQUIT, onquit);
	if (sigtrap)
		xxit(1);
	ttyraw();
	timer();

	/* loop reading articles. */
	fp = NULL;
	obit = -1;
	nextng();
	quitflg = 0;
	while (quitflg == 0) {
		if (getnextart(FALSE))
			break;
#ifdef DEBUG
		printf("after getnextart, fp %x, pos %d, bit %d, group '%s', filename '%s'\n",
			fp, ftell(fp), bit, groupdir, filename);
#endif
		strcpy(goodone, filename);
		if (sigtrap)
			return;
		vcmd();
	}

	if (news)
		botscreen();
	ttycooked();
	if (!news)
		fprintf(stderr, "No news.\n");
}

/*
 * Read and execute a command.
 */

vcmd() {
	register c;
	char *p;
	int count;
	int countset;

#ifdef DIGPAGE
	appfile(fp, dlinno + ARTWLEN + 1);
	endsuba = findend(dlinno);
	if (artlines > dlinno + ARTWLEN
	 || endsuba > 0 && endsuba < artlines
#else
	if ((appfile(fp, dlinno + ARTWLEN + 1), artlines > dlinno + ARTWLEN)
#endif
	 || (prflags & HDRONLY) && artlines > hdrend) {
		atend = 0;
		strcpy(prompt, "more? ");
	} else {
		atend = 1;
		strcpy(prompt, "next? ");
		if (! erased)
			clear(bit);		/* article read */
	}
	curflag = CURP1;
	p = prompt + strlen(prompt);
	countset = 0;
	count = 0;
	while ((c = vgetc()) >= '0' && c <= '9') {
		count = (count * 10) + (c - '0');
		sprintf(p, "%d", count);
		countset = 1;
	}
	if (c == '\033') {			/* escape */
		strcat(prompt, "M-");
		c = vgetc();
		if (c != INTR)
			c |= META;
	}
	secpr[0] = '\0';
	if (countset == 0)
		count = 1;
	docmd(c, count);
	if (c != '?')		/* UGGH */
		prflags &=~ HELPMSG;
	if (dlinno > 0)
		prflags &=~ HDRONLY;
}


/*
 * Process one command, which has already been typed in.
 */
docmd(c, count)
{
	int i;
	char *findhist();

	switch (c) {

	/* Show more of current article, or advance to next article */
	case '\r':
		prflags &=~ NOPRT;
		if (atend)
			goto next;
		else if (prflags & HDRONLY)
			prflags &=~ HDRONLY;
#ifdef DIGPAGE
		else if (endsuba > 0)
			dlinno = endsuba;
#endif
#ifdef HASSCROLL
		else if ((appfile(fp, dlinno + 2 * ARTWLEN), artread)
		 && artlines - dlinno <= ARTWLEN + 2)
			dlinno = artlines - ARTWLEN;
#endif
		else
			dlinno += ARTWLEN;
		break;

	/* No.  Go on to next article. */
next:	case 'n':
		itsbeenseen(h.ident);
		readmode = NEXT;
		if (fp != NULL) {	/* always false? */
			fclose(fp);
			fp = NULL;
		}
		clear(bit);
		saveart;
		nextbit();
		break;


	/* Back up count pages */
	case '\2':	/* Control-B */
		dlinno -= ARTWLEN * count;
		if (dlinno < 0)
			dlinno = 0;
		break;

	/* forward half a page */
	case '\4':	/* Control-D, as in vi */
		dlinno += ARTWLEN/2 * count;
		break;

	/* forward count lines */
	case '\16':	/* Control-N */
	case '\32':	/* control-Z */
		dlinno += count;
		break;

	/* bakcwards count lines */
	case '\20':	/* Control-P */
	case '\31':	/* Control-Y */
		dlinno -= count;
		if (dlinno < 0)
			dlinno = 0;
		break;

	/* Turn displaying of article back on */
	case 'l':
	case 'd':
		prflags &=~ NOPRT;
		break;

	/* display header */
	case 'h':
		dlinno = 0;
		prflags |= HDRONLY;
		prflags &=~ NOPRT;
		break;

	/*
	 * Unsubscribe to the newsgroup and go on to next group
	 */

	case 'U':
	case 'u':
		strcat(prompt, "u");
		c = vgetc();
		if (c == 'g') {
			obit = -1;
			if (fp != NULL) {
				fclose(fp);
				fp = NULL;
			}
			zapng = TRUE;
			saveart;
			if (nextng()) {
				if (actdirect == BACKWARD)
					msg("Can't back up.");
				else
					quitflg = 1;	/* probably unnecessary */
			}
		} else {
			if (c != INTR && c != ckill)
				msg("Illegal command");
		}
		break;

		/* Print the current version of news */
	case 'v':
		msg("News version: %s", news_version);
		break;


	/* Decrypt joke.  Always does rot 13 */
	case 'D':
		appfile(fp, 32767);
		for (i = hdrend ; i < artlines ; i++) {
			register char c, *p;
			tfget(linebuf, i);
			for (p = linebuf ; (c = *p) != '\0' ; p++) {
				if (c >= 'a' && c <= 'z')
					*p = (c - 'a' + 13) % 26 + 'a';
				else if (c >= 'A' && c <= 'Z')
					*p = (c - 'A' + 13) % 26 + 'A';
			}
			tfput(linebuf, i);
		}
		prflags |= NEWART;
		prflags &=~ (HDRONLY|NOPRT);
		break;

		/* write out the article someplace */
		/* w writes out without the header */
	case 's':
	case 'w':
		if (prget("file: ", linebuf))
			break;
		if (linebuf[0] == '|') {
			/* not tested */
			sprintf(secpr, "!(%s)<$A", linebuf + 1);
			updscr();
			sprintf(linebuf, "A=%s export A;%s", filename, secpr + 1);
			secpr[0] = '\0';
			goto execcmd;
			break;
		}
		{
		char *grn = groupdir;
		bptr = linebuf;		/* vnews; look at this again later */
		if (*bptr != '|' && *bptr != '/') {
			char	hetyped[BUFLEN];
			char	*boxptr;
			strcpy(hetyped, bptr);
			if (boxptr = getenv("NEWSBOX"))
				if (index(boxptr, '%'))
					sprintf(bptr, boxptr, grn);
				else
					strcpy(bptr, boxptr);
			else if (hetyped[0] == '~' && hetyped[1] == '/') {
				strcpy(hetyped, bptr+2);
				strcpy(bptr, userhome);
			} else
				bptr[0] = '\0';
			if (bptr[0])
				strcat(bptr, "/");
			if (hetyped[0] != '\0')
				strcat(bptr, hetyped);
			else
				strcat(bptr, "Articles");
		}
		vsave(bptr, c == 's');
		}
		break;

		/* back up  */
	case '-':
	case 'b':
minus:
		aabs = TRUE;
		if (!*ofilename1) {
			msg("Can't back up.");
			break;
		}
		if (fp != NULL) {	/* always true? */
			fclose(fp);
			fp = NULL;
		}
		hbufcp(&hbuf2, &h);
		hbufcp(&h, &hbuf1);
		hbufcp(&hbuf1, &hbuf2);
		strcpy(bfr, filename);
		strcpy(filename, ofilename1);
		strcpy(ofilename1, bfr);
		obit = bit;
		if (strcmp(groupdir, ogroupdir)) {
			strcpy(bfr, groupdir);
			selectng(ogroupdir);
			strcpy(groupdir, ogroupdir);
			strcpy(ogroupdir, bfr);
			ngrp = 1;
			back();
		}
		bit = oobit;
		oobit = obit;
		obit = -1;
		getnextart(TRUE);
		return FALSE;

		/* skip forwards */
	case '+':
caseplus:	if (count == 0)
			break;
		saveart;
		last = bit;
		for (i = 0; i < count; i++) {
			nextbit();
			if ((bit > pngsize) || (rflag && bit < 1))
				break;
		}
		if (fp != NULL) {	/* always true? */
			fclose(fp);
			fp = NULL;
		}
		obit = -1;
		break;

	/* exit - time updated to that of most recently read article */
	case 'q':
		quitflg = 1;
		break;


	/* cancel the article. */
	case 'c':
		strcpy(prompt, "cancel? ");
		if (vgetc() != '\r')
			break;
		cancel_command();
		break;

	/* escape to shell */
	case '!': {
		register char *p;
		int flags;

		sprintf(linebuf, "A=%s export A;", filename);
		for (p = linebuf ; *p ; p++);
		if (prget("!", p))
			break;
execcmd:	flags = CWAIT;
		if (*p == '\0') {
			strcpy(p, SHELL);
			flags = 0;
		}
		while (*p) p++;
		while (p > linebuf && p[-1] == ' ')
			p--;
		if (*--p == '&') {
			*p = '\0';
			flags = BKGRND;
		} else {
			prflags |= NOPRT;
		}
		shcmd(linebuf, flags);
		break;
	}

	/* mail reply */
	case 'r':
		reply(0);
		break;


	/* next newsgroup */
	case 'N':
		if (fp != NULL) {
			fclose(fp);
			fp = NULL;
		}
		if (next_ng_command())
			quitflg = 1;
		break;

	/* sepecific number */
	case 'A':
		if (count > pngsize) {
			msg("not that many articles");
			break;
		}
		readmode = SPEC;
		aabs = TRUE;
		bit = count;
		obit = -1;
		break;

	/* display parent article */
	case 'p':
		if (h.followid[0] == '\0') {
			msg("no references line");
			break;
		}
		if ((ptr1 = rindex(h.followid, ' ')) != NULL)
			ptr1++;
		else
			ptr1 = h.followid;
		strcpy(linebuf, ptr1);
		msg("%s", linebuf);
		curflag = CURP2;
		updscr();		/* may take this out later */
		goto searchid;

	/* specific message ID. */
	case '<':
		/* could improve this */
		linebuf[0] = '<';
		if (prget("<", linebuf+1))
			break;
searchid:	secpr[0] = '\0';
		if (index(linebuf, '@') == NULL && index(linebuf, '>') == NULL) {
			ptr1 = linebuf;
			if (*ptr1 == '<')
				ptr1++;
			ptr2 = index(ptr1, '.');
			if (ptr2 != NULL) {
				*ptr2++ = '\0';
				sprintf(bfr, "<%s@%s.UUCP>", ptr2, ptr1);
				strcpy(linebuf, bfr);
			}
		}
		if (index(linebuf, '>') == NULL)
			strcat(linebuf, ">");

		ptr1 = findhist(linebuf);
		if (ptr1 == NULL) {
			msg("%s not found", linebuf);
			break;
		}
		ptr2 = index(ptr1, '\t');
		ptr3 = index(++ptr2, '\t');
		ptr2 = index(++ptr3, ' ');
		if (ptr2)
			*ptr2 = '\0';
		ptr2 = index(ptr3, '/');
		*ptr2++ = '\0';
		aabs = TRUE;
		if (cflag)
			clear(bit);
		else {
			fclose(fp);
			fp = NULL;
			putc('\n', ofp);
		}
		hbufcp(&hbuf1, &h);
		saveart;
		strcpy(ogroupdir, ptr3);
		if (strcmp(groupdir, ogroupdir)) {
			strcpy(bfr, groupdir);
			selectng(ogroupdir);
			strcpy(groupdir, ogroupdir);
			strcpy(ogroupdir, bfr);
			back();
		}
		sscanf(ptr2, "%d", &bit);
		oobit = obit;
		obit = -1;
		getnextart(TRUE);
		rfq = 0;
		break;

	/* follow-up article */
	case 'f':
		reply(1);
		break;

	/* erase - pretend we haven't seen this article. */
	case 'e':
		erased = 1;
		set(bit);
		goto caseplus;	/* skip this article for now */
		break;


	case '#':
		msg("Article %d of %ld",
			rfq ? oobit : bit, pngsize);
		break;

		/* error */
	case '?':
		{
			FILE *helpf;
			if ((helpf = fopen(VHELP, "r")) == NULL) {
				msg("Can't open help file");
				break;
			}
			move(0, 0);
			while (fgets(linebuf, 1000, helpf) != NULL)
				addstr(linebuf);
			fclose(helpf);
			prflags |= HELPMSG|NEWART;
		}
		break;

	/* Interrupt received */
	case INTR:
		break;

	default:
		if (c != ckill)
			msg("Illegal command");
		break;
	}

	return FALSE;
}

cancel_command()
{
	tfilename = filename;
	hptr = &h;
	readmode = SPEC;
	strcpy(rcbuf, hptr->path);
	ptr1 = index(rcbuf, ' ');
	if (ptr1)
		*ptr1 = 0;
	if (uid == ROOTID)
		i = 0;		/* root gets to cancel */
	else
		i = strcmp(username, rcbuf);
	if (i != 0) {
		msg("Can't cancel what you didn't write.");
		return;
	}
	if (!cancel(ofp, hptr, i) && hptr == &h) {
		clear(bit);
		saveart;
		nextbit();
		obit = -1;
		fp = NULL;
	}
	if (fp != NULL)
		fclose(fp);
	fp = NULL;
}

/*
 * Generate replies and followups.
 * We generate a file and then call a shell procedure to do the work.
 */

#define MODGROUPS	"fa.all,mod.all,all.mod,all.announce,"

reply(followup) {
	char *arg[8];
	FILE *tfp;
	char subj[132];
	char *p;
	char *replyname();

	strcpy(tf, tft);
	mktemp(tf);
	if ((tfp = fopen(tf, "w")) == NULL) {
		msg("Can't create %s", tf) ;
		return;
	}
	strcpy(subj, h.title);
	if (!prefix(subj, "Re:") && !prefix(subj, "re:")) {
		strcpy(bfr, subj);
		sprintf(subj, "Re: %s", bfr);
	}
	arg[0] = "/bin/sh";

	/* test for moderated group */
	if (followup) {
		strcpy(linebuf, h.nbuf);
		ngcat(linebuf);
		if (ngmatch(linebuf, MODGROUPS))
			followup = 2;
	}

	if (followup != 1) {		/* send mail */
#ifdef INTERNET
		if (followup == 2 && h.sender[0])
			p = h.sender;
		else
#endif
			p = replyname(&h);
		fprintf(tfp, "To: %s\n", p);
		fprintf(tfp, "Subject: %s\n", subj);
		if (followup != 2)
			fprintf(tfp, "In-reply-to: your article %s\n", h.ident);
		arg[1] = REPLYPROG;
		arg[4] = p;
	} else {
		p = h.nbuf;
		if (h.followto[0])
			p = h.followto;
		strcpy(linebuf, p);
		launder(linebuf);
		fprintf(tfp, "Newsgroups: %s\n", linebuf);
		fprintf(tfp, "Subject: %s\n", subj);
		if (h.keywords[0])
			fprintf(tfp, "Keywords: %s\n", h.keywords);
		if (h.distribution[0])
			fprintf(tfp, "Distribution: %s\n", h.distribution);
		arg[1] = FOLLOWPROG;
		arg[4] = linebuf;
	}
	if (followup != 0) {
		bfr[0] = 0;				/* References */
		if (h.followid[0]) {
			strcpy(bfr, h.followid);
			strcat(bfr, " ");
		}
		strcat(bfr, h.ident);
		fprintf(tfp, "References: %s\n", bfr);
	}
	putc('\n', tfp);
	fclose(tfp);

	arg[2] = tf;
	arg[3] = filename;
	/* arg[4] (name or newsgroup) set above */
	arg[5] = NULL;
	prun(arg, 0);
	if (pstatus != 0) {
		if (pstatus != 22 << 8)
			p = "Command failed";
		else if (followup == 1)
			p = "Article not posted";
		else
			p = "Mail not sent";
		msg(p);
	}
	prflags |= NOPRT;
}

next_ng_command()
{
	if (prget("group? ", linebuf))
		return FALSE;
	bptr = linebuf;
	if (!*bptr || *bptr == '-') {
		obit = -1;
		if (*bptr)
			actdirect = BACKWARD;
		saveart;
		if (nextng()) {
			if (actdirect == BACKWARD)
				msg("Can't back up.");
			else
				return TRUE;
		}
		return FALSE;
	}
	while (isspace(*bptr))
		bptr++;
	if (!validng(bptr)) {
		msg("No such group.");
		return FALSE;
	}
	obit = -1;
	readmode = SPEC;
	saveart;
	back();
	selectng(bptr);
	return FALSE;
}

/*
 * Find the next article we want to consider, if we're done with
 * the last one, and show the header.
 */
getnextart(minus)
int minus;
{
	if (minus)
		goto nextart2;	/* Kludge for "-" command. */

	if (bit == obit)	/* Return if still on same article as last time */
		return 0;

nextart:
	if (news) {
		curflag = CURHOME;
#ifdef CURSES
		move(0, 0);	/* let user know we are thinking */
		refresh();
#else
		_amove(0, 0);
		vflush();
#endif
	}
	dgest = 0;

	/* If done with this newsgroup, find the next one. */
	while (((long) bit > ngsize) || (rflag && bit < 1)) {
		int i;
		if (i=nextng()) {
			if (actdirect == BACKWARD) {
				msg("Can't back up.");
				actdirect = FORWARD;
				continue;
			} 
			else /* if (rfq++ || pflag || cflag) */
				return 1;
		}
		if (rflag)
			bit = ngsize + 1L;
		else
			bit = -1;
	}

	/* speed things up by not searching for article -1 */
	if (bit < 0) {
		bit = 0;
		nextbit();
		aabs = FALSE;
		goto nextart;
	}

nextart2:
#ifdef DEBUG
	fprintf(stderr, "article: %s/%d\n", groupdir, bit);
#endif
	if (rcreadok)
		rcreadok = 2;	/* have seen >= 1 article */
	sprintf(filename, "%s/%d", dirname(groupdir), bit);
	if (rfq && goodone[0])	/* ??? */
		strcpy(filename, goodone);
	if (sigtrap == SIGHUP)
		return 1;
#ifdef DEBUG
	fprintf(stderr, "filename = '%s'\n", filename);
#endif
	/* Decide if we want to show this article. */
	if (ignorenews
	|| ((fp = fopen(filename, "r")) == NULL)
	|| (hread(&h, fp, TRUE) == NULL)
	|| (!rfq && !select(&h, aabs))) {
#ifdef DEBUG
		fprintf(stderr, "Bad article '%s'\n", filename);
#endif
		if (fp != NULL) {
			fclose(fp);
			fp = NULL;
		}
		clear(bit);
		obit = -1;
		nextbit();
		aabs = FALSE;
		goto nextart;
	}
	aabs = FALSE;
	actdirect = FORWARD;
	news = TRUE;
	{	/* strip off any notesfile header */
		register c;
		if ((c = strlen(h.title)) >= 4
		 && strcmp(h.title + c - 4, "(nf)") == 0) {
			if ((c = getc(fp)) != '#') {
				ungetc(c, fp);
			} else {
				while ((c = getc(fp)) != '\n' && c != EOF);
				while ((c = getc(fp)) != '\n' && c != EOF);
				while ((c = getc(fp)) != '\n' && c != EOF);
			}
		}
	}
	artbody = ftell(fp);
	fmthdr();
	artlines = lastlin;
	artread = 0;
	prflags |= NEWART;
	prflags &=~ NOPRT;
	if (hdrend < ARTWLEN)
		prflags |= HDRONLY;
	dlinno = 0;
	erased = 0;
	
	obit = bit;
	return 0;
}

/*
 * Print out whatever the appropriate header is
 */
hdr() { abort(); }

fmthdr() {
	char *vbriefdate();

	lastlin = 0;
	if (ngrp) {
		pngsize = ngsize;
		ngrp--;
	}
	if (!hflag) {
		sprintf(linebuf, "Article %s %s",
			h.ident, vbriefdate(h.subdate));
		tfappend(linebuf);
	}
	vhprint(&h, pflag ? 1 : 0);
	sprintf(linebuf, "(%d lines)", NLINES(h, fp)); tfappend(linebuf);
	tfappend("");
	hdrend = lastlin;
}



/* Arpa format: Sat, 14-May-83 03:45:14 EDT */
/* Bugs: doesn't work on article with non-arpa dates */
char *
vbriefdate(q)
	register char *q;
	{
	register char *p;
	register i;
	char day[2];

	p = bfr;
	for (i = 3 ; --i >= 0 ; )
		*p++ = *q++;
	*p++ = ' ';
	q += 2;
	day[0] = *q++;
	if (*q != '-') day[1] = *q++;
	else day[1] = '\0';
	q++;
	for (i = 3 ; --i >= 0 ; )
		*p++ = *q++;
	*p++ = ' ';
	*p++ = day[0];
	if (day[1]) *p++ = day[1];
	q += 5;
	*p++ = ' ';
	if (q[-1] != '0') *p++ = q[-1];
	for (i = 4 ; --i >= 0 ; )
		*p++ = *q++;
	*p++ = '\0';
	return bfr;
}		

/*
 * Print the file header to the temp file.
 */
vhprint(hp, verbose)
register struct hbuf *hp;
int	verbose;
{
	register char	*p1, *p2;
	char	fname[BUFLEN];
	char *tailpath();

	fname[0] = '\0';		/* init name holder */



	p1 = index(hp->from, '(');	/* Find the sender's full name. */
	if (p1 == NULL && hp->path[0])
		p1 = index(hp->path, '(');
	if (p1 != NULL) {
		strcpy(fname, p1+1);
		p2 = index(fname, ')');
		if (p2 != NULL)
			*p2 = '\0';
	}

	sprintf(linebuf, "Subject: %s", hp->title); tfappend(linebuf);
	if (!hflag && hp->keywords[0])
		sprintf(linebuf, "Keywords: %s", hp->keywords), tfappend(linebuf);
	if (verbose) {
		sprintf(linebuf, "From: %s", hp->from); tfappend(linebuf);
		sprintf(linebuf, "Path: %s", hp->path); tfappend(linebuf);
		if (hp->organization[0])
			sprintf(linebuf, "Organization: %s", hp->organization), tfappend(linebuf);
	}
	else {
		if (p1 != NULL)
			*--p1 = '\0';		/* bump over the '(' */
#ifdef INTERNET
		/*
		 * Prefer Path line if it's in internet format, or if we don't
		 * understand internet format here, or if there is no reply-to.
		 */
		sprintf(linebuf, "From: %s", hp->from);
#else
		sprintf(linebuf, "Path: %s", tailpath(hp));
#endif
		if (fname[0] != '\0') {
			strcat(linebuf, " (");
			strcat(linebuf, fname);
			if (hp->organization[0] && !hflag) {
				strcat(linebuf, " @ ");
				strcat(linebuf, hp->organization);
			}
			strcat(linebuf, ")");
		}
		tfappend(linebuf);
		if (p1 != NULL)
			*p1 = ' ';
		if (hp->ctlmsg[0]) {
			sprintf(linebuf, "Control: %s", hp->ctlmsg);
			tfappend(linebuf);
		}
	}

	ngdel(strcpy(bfr, hp->nbuf));
	if (verbose) {
		sprintf(linebuf, "Newsgroups: %s", bfr); tfappend(linebuf);
		sprintf(linebuf, "Date: %s", hp->subdate); tfappend(linebuf);
		if (hp->sender[0])
			sprintf(linebuf, "Sender: %s", hp->sender), tfappend(linebuf);
		if (hp->replyto[0])
			sprintf(linebuf, "Reply-To: %s", hp->replyto), tfappend(linebuf);
		if (hp->followto[0])
			sprintf(linebuf, "Followup-To: %s", hp->followto), tfappend(linebuf);
	}
	else if (strcmp(bfr, groupdir) != 0)
		sprintf(linebuf, "Newsgroups: %s", bfr), tfappend(linebuf);
}





/*
 * Append file to temp file, handling control characters, folding lines, etc.
 * We don't grow the temp file to more than nlines so that a user won't have
 * to wait for 20 seconds to read in a monster file from net.sources.
 * What we rally want is coroutines--any year now.
 */

#define ULINE 0200
static char *maxcol;


appfile(iop, nlines)
	register FILE *iop;
	{
	register int c;
	register char *icol;	/* &linebuf[0] <= icol <= maxcol */

	if (artread || artlines >= nlines)
		return;
	maxcol = linebuf;
	icol = linebuf;
	while ((c = getc(iop)) != EOF) {
		switch (c) {
		case ' ':
			if (icol == maxcol && icol < linebuf + LBUFLEN - 1) {
				*icol++ = ' ';
				maxcol = icol;
			} else {
				icol++;
			}
			break;
		case '\t':
			icol = (icol - linebuf &~ 07) + 8 + linebuf;
			growlin(icol);
			break;
		case '\b':
			if (icol > linebuf) --icol;
			break;
		case '\n':
			outline();
			if (artlines >= nlines)
				return;
			icol = linebuf;
			break;
		case '\r':
			icol = linebuf;
			break;
		case '\f':
			outline(); outline(); outline();
			if (artlines >= nlines)
				return;
			icol = linebuf;
			break;
		default:
			if (c < ' ' || c > '~')
				break;
			else if (icol >= linebuf + LBUFLEN - 1)
				icol++;
			else if (icol == maxcol) {
				*icol++ = c;
				maxcol = icol; }
			else if (*icol == ' ')
				*icol++ = c;
			else if (c == '_')
				*icol++ |= ULINE;
			else
				*icol++ = (c | ULINE);
			break;
		}
	}
	if (maxcol != linebuf)		/* file not terminated with newline */
		outline();
	artread++;
}



growline(col)
	char *col;
	{
	while (maxcol < col && maxcol < linebuf + LBUFLEN - 1)
		*maxcol++ = ' ';
}


outline() {
	*maxcol = '\0';
	if (strncmp(linebuf, ">From ", 6) == 0) {
		register char *p;
		for (p = linebuf ; (*p = p[1]) != '\0' ; p++);
	}
	tfappend(linebuf);
	if (maxcol > linebuf)
		artlines = lastlin;
	maxcol = linebuf;
}



prget(prompter, buf)
	char *prompter, *buf;
	{
	char *p, *q, *r;
	int c, lastc;

	curflag = CURP2;
	r = buf;
	lastc = '\0';
	for (;;) {
		*r = '\0';
		p = secpr;
		for (q = prompter ; *q ; q++)
			*p++ = *q;
		for (q = buf ; *q ; q++) {
			if (p < &secpr[SECPRLEN-1] && *q >= ' ' && *p <= '~')
				*p++ = *q;
		}
		*p = '\0';
		c = vgetc();
		if (c == '\r' || c == INTR) {
			break;
		}
		if (c == cerase) {
			if (lastc == '\\')
				r[-1] = c;
			else if (r > buf)
				r--;
		} else if (c == ckill) {
			if (lastc == '\\')
				r[-1] = c;
			else
				r = buf;
		} else {
			*r++ = c;
		}
		lastc = c;
	}
	curflag = CURHOME;
	secpr[0] = '\0';
	return (c == INTR);
}



/*
 * Execute a shell command.
 */

shcmd(cmd, flags)
	char *cmd;
	{
	char *arg[4];

	arg[0] = SHELL, arg[1] = "-c", arg[2] = cmd, arg[3] = NULL;
	prun(arg, flags);
}


prun(args, flags)
	char **args;
	{
	int pid;
	int i;
	int (*savequit)();

	if (!(flags & BKGRND)) {
		botscreen();
		ttycooked();
	}
	while ((pid = fork()) == -1)
		sleep(1);		/* must not clear alarm */
	if (pid == 0) {
		for (i = 3 ; i < 20 ; i++)
			close(i);
		if (flags & BKGRND) {
			signal(SIGINT, SIG_IGN);
			signal(SIGQUIT, SIG_IGN);
			close(0), close(1), close(2);
			open("/dev/null", 2);
			dup(0), dup(0);
		}
		umask(savmask);
		execvp(args[0], args);
		fprintf(stderr, "%s: not found\n", args[0]);
		exit(20);
	}
	if (!(flags & BKGRND)) {
		savequit = signal(SIGQUIT, SIG_IGN);
		while ((i = wait(&pstatus)) != pid && (i != -1 || errno == EINTR));
		if (flags & CWAIT) {
			fprintf(stderr, "continue? ");
			while (getchar() != '\n');
		}
		signal(SIGQUIT, savequit);
		ttyraw();
		clearok(curscr, 1);
	}
}

#ifdef DIGPAGE


/*
 * Find end of current subarticle in digets.
 */

findend(l) {
	register i;
	register char *p;

	for (i = l ; i < l + ARTWLEN && i < lastlin ; i++) {
		tfget(linebuf, i);
		for (p = linebuf ; *p == '-' ; p++);
		if (p > linebuf + 24)
			return i + 1;
	}
	return 0;
}

#endif


/*** Routines for handling temporary file ***/

/*
 * Append to temp file.
 * Long lines are folded.
 */

tfappend(line)
	char *line;
	{
	while (strlen(line) > COLS) {
		tfput(line, lastlin++);
		line += COLS;
	}
	tfput(line, lastlin++);
}


tfput(line, linno)
	char *line;
	{
	register char *p;
	register FILE *rtfp;		/* try to make it a litte faster */
	register int i;

	p = line, i = COLS;
	tfseek(linno, 1);
	rtfp = tfp;
	while (--i >= 0) {
		if (*p)
			putc(*p++, rtfp);
		else
			putc('\0', rtfp);
	}
	tflinno++;
}


tfget(line, linno)
	char *line;
	{
	tfseek(linno, 0);
	fread(line, COLS, 1, tfp);
	line[COLS] = '\0';
	tflinno++;
}


tfseek(linno, wrflag) {
	static int last = 1;

	if (linno != tflinno || wrflag != last) {
		fseek(tfp, (long)linno * COLS, 0);
		tflinno = linno;
		last = wrflag;
	}
}



msg(s, a1, a2, a3, a4) char *s; {
	sprintf(secpr, s, a1, a2, a3, a4);
}


/*
 * Update the display.
 * The display is entirely controlled by this routine,
 * which means that this routine may get pretty snarled.
 */

static int savelinno = -1;		/* dlinno on last call to updscr */
static int savepr;			/* prflags on last call */

updscr() {
	int count;
	int i;

	if (checkin())
		return;
	if ((prflags & HELPMSG) == 0
	 && (dlinno != savelinno || savepr != prflags)
	 && quitflg == 0) {
		if (dlinno != savelinno)
			prflags &=~ NOPRT;
		count = ARTWLEN;
		if (prflags & NOPRT)
			count = 0;
		if ((prflags & HDRONLY) && count > hdrend)
			count = hdrend - dlinno;
#ifdef DIGPAGE
		if (endsuba > 0 && count > endsuba - dlinno)
			count = endsuba - dlinno;
#endif
#ifndef CURSES
		if ((prflags & NEWART) == 0 && abs(dlinno - savelinno) <= count/2)
			ushift(0, ARTWLEN - 1, dlinno - savelinno);
#endif
		if (count > lastlin - dlinno)
			count = lastlin - dlinno;
		for (i = 0 ; i < ARTWLEN ; i++)
			clrline(i);
		for (i = 0 ; i < count ; i++) {
			tfget(linebuf, dlinno + i);
			mvaddstr(i, 0, linebuf);
		}
		prflags &=~ NEWART;
		savepr = prflags;
		savelinno = dlinno;
	}
	clrline(SPLINE), clrline(PRLINE);
	if (strlen(secpr) <= COLS)
		mvaddstr(PRLINE, 0, prompt);
	mvaddstr(PRLINE, 48, timestr);
	mvaddstr(PRLINE, 20, groupdir);
	addch(' '); addnum(bit); addch('/'); addnum((int)pngsize); addch(' ');
	if (ismail)
		mvaddstr(PRLINE, 62, ismail > 1? "MAIL" : "mail");
	mvaddstr(SPLINE, 0, secpr);
	if (curflag == CURP1)
		move(PRLINE, strlen(prompt));
	else if (curflag == CURHOME)
		move(0, 0);
	refresh();
}


addnum(n) {
	if (n >= 10)
		addnum(n / 10);
	addch(n % 10 + '0');
}



/*** alarm handler ***/

/* #include <time.h> */


/*
 * Called on alarm signal.
 * Simply sets flag, signal processed later.
 */

onalarm() {
	alflag++;
}


/*
 * Process alarm signal (or start clock)
 */

timer() {
	long tod;
	int hour;
	int i;
	struct tm *t;
	struct stat statb;
	long time();
	static char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
	static long oldmsize = 1000000L;
	static int rccount = 10;

	alflag = 0;
	signal(SIGALRM, onalarm);
	time(&tod);
	t = localtime(&tod);
	i = 60 - t->tm_sec;
	alarm(i > 30? 30 : i);			/* reset alarm */
	hour = t->tm_hour % 12;
	if (hour == 0)  hour = 12;
	sprintf(timestr, "%.3s %d %d:%02d",
		months + 3 * t->tm_mon, t->tm_mday, hour, t->tm_min);
	ismail = 0;
	if (mailf != NULL && stat(mailf, &statb) >= 0 && statb.st_size > 0L) {
		ismail = 1;
		if (oldmsize < statb.st_size) {
			ismail = 2;		/* new mail */
			beep();
		}
	} else {
		statb.st_size = 0L;
	}
	oldmsize = statb.st_size;
	if (uflag && !xflag && --rccount < 0) {
		writeoutrc();
		if (secpr[0] == '\0')
			strcpy(secpr, ".newsrc updated");
		rccount = 10;
	}
}



/*** Terminal I/O ***/

#define INBUFSIZ 8

char inbuf[INBUFSIZ];			/* input buffer */
char outbuf[BUFSIZ];			/* output buffer */
int innleft = 0;			/* # of chars in input buffer */
int outnleft = BUFSIZ;			/* room left in output buffer */
char *innext;				/* next input character */
char *outnext = outbuf;			/* next space in output buffer */
#ifndef CURSES
#ifdef USG
int oflags;				/* fcntl flags (for nodelay read) */
#endif
#endif


/*
 * Input a character
 */

vgetc() {
	register c;

recurse:
	if (--innleft >= 0) {
		c = *innext++;
	} else {
		if (alflag)
			timer();
		updscr();	/* update the display */
		for (;;) {
			if (innleft > 0 || alflag)
				goto recurse;
			intflag = 0;
#ifndef CURSES
#ifdef USG
			if (oflags & O_NDELAY) {
				oflags &=~ O_NDELAY;
				fcntl(0, F_SETFL, oflags);
			}
#endif
#endif
			if ((innleft = read(0, inbuf, INBUFSIZ)) > 0)
				break;
			if (errno != EINTR)
				abort();	/* "Can't happen" */
			if (intflag) {
				intflag--;
				return INTR;
			}
		}
		innext = inbuf + 1;
		innleft--;
		c = inbuf[0];
	}
#ifndef USG
#ifndef CBREAK
	c &= 0177;
	if (c == '\034')	/* FS character */
		onquit();
#endif
#endif
	if (c == '\f') {
		clearok(curscr, 1);
		goto recurse;
	}
	return c;
}



/*
 * Check for terminal input
 */

checkin() {
#ifndef CURSES
#ifndef USG
	long count;

#endif
#endif
	if (innleft > 0 || alflag)
		return 1;
#if !defined(CURSES) && (defined(USG) || defined(FIONREAD))
	if (ospeed == B9600)
		return 0;
	vflush();
	if (ospeed <= B300)
		ttyowait();
#ifdef USG
	if ((oflags & O_NDELAY) == 0) {
		oflags |= O_NDELAY;
		fcntl(0, F_SETFL, oflags);
	}
	if ((innleft = read(0, inbuf, INBUFSIZ)) > 0) {
		innext = inbuf;
		return 1;
	}
#else
	count = 0;			/* in case FIONREAD fails */
	ioctl(0, FIONREAD, &count);
	if (count)
		return 1;
#endif
#endif
	return 0;
}



/*
 * flush terminal input queue.
 */

clearin() {
#ifdef USG
	ioctl(0, TCFLSH, 0);
#else
	struct sgttyb tty;
	gtty(0, &tty);
	stty(0, &tty);
#endif
	innleft = 0;
}


vputc(c) {
	if (--outnleft < 0) {
		vflush();
		outnleft--;
	}
	*outnext++ = c;
}


/*
 * Flush the output buffer
 */

vflush() {
	register char *p;
	register int i;

	for (p = outbuf ; p < outnext ; p += i) {
		if ((i = write(1, p, outnext - p)) < 0) {
			if (errno != EINTR)
				abort();	/* "Can't happen" */
			i = 0;
		}
	}
	outnleft = BUFSIZ;
	outnext = outbuf;
}




/*** terminal modes ***/

#ifndef CURSES
#ifdef USG
static struct termio oldtty, newtty;


/*
 * Save tty modes
 */

ttysave() {
	if (ioctl(1, TCGETA, &oldtty) < 0)
		xerror("Can't get tty modes");
	newtty = oldtty;
	newtty.c_iflag &=~ (INLCR|IGNCR|ICRNL);
	newtty.c_oflag &=~ (OPOST);
	newtty.c_lflag &=~ (ICANON|ECHO|ECHOE|ECHOK|ECHONL);
	newtty.c_lflag |=  (NOFLSH);
	newtty.c_cc[VMIN] = 1;
	newtty.c_cc[VTIME] = 0;
	cerase = oldtty.c_cc[VERASE];
	ckill = oldtty.c_cc[VKILL];
	ospeed = oldtty.c_cflag & CBAUD;
}


/*
 * Set tty modes for visual processing
 */

ttyraw() {
	while (ioctl(1, TCSETAF, &newtty) < 0 && errno == EINTR);
}



ttyowait() {	/* wait for output queue to drain */
	while (ioctl(1, TCSETAW, &newtty) < 0 && errno == EINTR);
}


/*
 * Restore tty modes
 */

ttycooked() {
	while (ioctl(1, TCSETAF, &oldtty) < 0 && errno == EINTR);
	oflags &=~ O_NDELAY;
	fcntl(0, F_SETFL, oflags) ;
}

#else

static struct sgttyb oldtty, newtty;


/*
 * Save tty modes
 */

ttysave() {
	if (gtty(1, &oldtty) < 0)
		xerror("Can't get tty modes");
	newtty = oldtty;
	newtty.sg_flags &=~ (CRMOD|ECHO|XTABS);
#ifdef CBREAK
	newtty.sg_flags |= CBREAK;
#else
	newtty.sg_flags |= RAW;
#endif
	cerase = oldtty.sg_erase;
	ckill = oldtty.sg_kill;
	ospeed = oldtty.sg_ospeed;
}


/*
 * Set tty modes for visual processing
 */

ttyraw() {
	while (stty(1, &newtty) < 0 && errno == EINTR);
}



ttyowait() {	/* wait for output queue to drain */
	/* I'm not sure how to do this on Berkeley */
}


/*
 * Restore tty modes
 */

ttycooked() {
	while (stty(1, &oldtty) < 0 && errno == EINTR);
}

#endif
#else
#ifdef HCURSES

ttysave() {
	initscr();
	idlok(stdscr, 1);
	intrflush(curscr, 0);
	cerase = erasechar();
	ckill = killchar();
}


ttyraw() {
	reset_prog_mode();
	cbreak();
	nonl();
	noecho();
}


ttycooked() {
	reset_shell_mode();
}


int _endwin;		/* [expletives deleted] */

#else

ttysave() {
#ifdef USG
	struct termio tty;
#else
	struct sgttyb tty;
#endif

	initscr();
#ifdef USG
	ioctl(0, TCGETA, &tty);
	cerase = tty.c_cc[VERASE];
	ckill = tty.c_cc[VKILL];
#else
	gtty(0, &tty);
	cerase = tty.sg_erase;
	ckill = tty.sg_kill;
#endif
}


ttyraw() {
	/*
	 * Vnews really isn't designed to work with RAW mode, so if you
	 * have anything approaching CBREAK mode, use it.
	 */
#ifdef CBREAK
	crmode();
#else
	raw();
#endif
	nonl();
	noecho();
}


ttycooked() {
	resetty();
}

#endif
#endif CURSES



/*** signal handlers ***/

onint() {
	if (!news) {
		ttycooked();
		xxit(1);
	}
	signal(SIGINT, onint);
	clearin();			/* flush input queue */
	intflag++;
}


onquit() {
	ttycooked();
	abort();	/* later, change to exit(0) */
}



/*** stolen from rfuncs2.c and modified ***/

vsave(to, hdflag)
register char *to;
{
	register FILE *ufp;
	int	isprogram = 0;
	int	isnew = 1;
	long	saveoff;

#define hh h
#define hfp fp
	saveoff = ftell(fp);
	fseek(fp, artbody, 0);
	if (hdflag) {
		ufp = fopen(to, "r");
		if (ufp != NULL) {
			fclose(ufp);
			isnew = 0;
		}
	}
	umask(savmask);

	if ((ufp = fopen(to, hdflag? "a" : "w")) == NULL) {
		msg("Cannot open %s", to);
		goto out;
	}
	/*
	 * V7MAIL code is here to conform to V7 mail format.
	 * If you need a different format to be able to
	 * use your local mail command (such as four ^A's
	 * on the end of articles) substitute it here.
	 */
	if (hdflag) {
#ifdef V7MAIL
		fprintf(ufp, "From %s %s",
#ifdef INTERNET
				hh.from,
#else
				hh.path,
#endif
					ctime(&hh.subtime));
#endif
		hprint(&hh, ufp, 2);
#ifdef V7MAIL
		tprint(hfp, ufp, TRUE);
		putc('\n', ufp);	/* force blank line at end (ugh) */
#else
		tprint(hfp, ufp, FALSE);
#endif
	} else {
		tprint(hfp, ufp, FALSE);
	}

	if (isprogram)
		pclose (ufp);
	else
		fclose(ufp);
	if (hdflag)
		msg("file: %s %s", to, isnew ? "created" : "appended");
	else
		msg("file: %s written", to);

out:
	umask(N_UMASK);
	fseek(fp, saveoff, 0);
}