[net.sources] vn 12/86

bobm@rtech.UUCP (Bob Mcqueer) (01/01/87)

Bob McQueer
{amdahl, sun, mtxinu, hoptoad, cpsc6a}!rtech!bobm

cut here
-----------------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	vn.c
#	config.h
#	head.h
#	reader.h
#	tty.h
#	tune.h
#	vn.h
# This archive created: Thu Jan  1 11:15:25 1987
export PATH; PATH=/bin:$PATH
echo shar: extracting "'vn.c'" '(25993 characters)'
if test -f 'vn.c'
then
	echo shar: will not over-write existing file "'vn.c'"
else
cat << \SHAR_EOF > 'vn.c'
/*
vn news reader for visual page oriented display of news
aimed at scanning large numbers of articles.

Original program by Bob McQueer in several versions 1983-1986.  Released
into the public domain in 1986.  While no copyright notice appears, the
original author asks that a history of changes crediting the proper people
be maintained.

Bob McQueer
{amdahl, sun, mtxinu, hoptoad, cpsc6a}!rtech!bobm

History:

	(bobm@rtech) 5/86 - first "public" version

	(bobm@rtech) 12/86 - version incorporates:

		bug fixes:
			str_store NULL string bug
			not picking up first article in newsgroup
			RESTART terminal reset for exit to editor.
			skip whitespace in "empty" digest lines while unpacking
			DISTRIBUTION line in followups.
			:100%: prompt on last line in reader.
			interpretation of multiple negations -w -t options.

			Many thanks to several people who noted the first
			two bugs as well as fixes.

			Thank you to Karl Williamson for helpful information
			tracking down the "concept terminal" bug (RESTART).

		SYSV ifdef's adapted from those done by Larry Tepper
		at ATT Denver - sent in by Karl Williamson, drutx!khw.
		Many people submitted SYSV ifdef's - thank you all.

		print capability from reader from Karl Williamson,
		drutx!khw

		Changes to use alternate header lines for mail, from changes
		by Andy Marrinson, andy@icom.UUCP (ihnp4!icom!andy).  Ifdef'ed
		to allow local configuration (bobm@rtech).

		"author_copy" file for followups, prevention of multiple
		"re: "'s, insert blank line and original author line
		before excerpted text from Andy Marrinson, andy@icom.UUCP

		Search string capability in reader, from Lawrie Brown,
		seismo!munnari!cdsadfa.oz!lpb (Australia).  Somewhat
		modified by interaction with the :100%: bug.

		Arrow key support, adapted from changes by Lawrie Brown.
		Modified to simply not allow control keys for arrows (allowing
		SOME controls is too prone to problems, esp. with .vnkey), and
		to allow the PAGEARROW ifdef (bobm@rtech)

		prevention of followups to "mod" and "announce", from
		Lawrie Brown.

		OLDRC ifdef adapted from changes by Lawrie Brown.  ADDRMUNGE
		added to allow OZ domain addressing changes from Australia
		to be grafted back in, and provide a hook for anybody else
		wanting to do something similar.

		Bob McQueer, bobm@rtech:

			a menu selection from the % command to jump to
			a new newsgroup

			linked list on hash table - no longer a compiled
			in limit for number of newsgroups

			.vnkey keystroke mapping file.

			options to get the % command list on entry, and to
			change how unsubscribed groups are handled for updates.

			allow configurable use of vs / ve pair for terminal
			handling.

Known bugs:

	non-erasure of stuff on prompt line when the new
	string includes an escape sequence (like PS1 maybe)
	because it doesn't realize that the escape sequence
	won't overprint the existing stuff

	control-w and update on exit may not update pages which have been
	scanned in funny orders by jumping into the middle of groups

	inaccurate numbers on '%' command results - reflect ranges, not
	actual numbers of articles.

	no arrow keys recognized which don't begin with <escape>

	doesn't know about the version 2.11 'm' in active list, or
	use the 'y' / 'n' either.

	crash due to embedding $\(\) type substring specifiers in regular
	expressions.  Obscure and hard to fix in a proper and portable way.
*/
#include <stdio.h>
#include <setjmp.h>
#include "config.h"
#include "tty.h"
#include "vn.h"

/* UNIX error number */
extern int errno;

extern NODE **Newsorder;
extern char Erasekey, Killkey;
extern int Rot;
extern char *Ps1,*Printer;
extern char *Orgdir,*Savefile,*Savedir;
extern int Ncount, Cur_page, Lrec, L_allow, C_allow;
extern int Headflag;
extern PAGE Page;
extern int Digest;
extern char *No_msg;
extern char *Hdon_msg;
extern char *Hdoff_msg;
extern char *Roton_msg;
extern char *Rotoff_msg;
extern char Cxitop[], Cxptoi[];

extern char *Aformat;

extern char *Contstr;

extern char *Kl,*Kr,*Ku,*Kd;

extern int Nounsub, Listfirst;

static int C_info;
static int Dskip, Drec;

static char *Unsub_msg = "Unsubscribed";
static char *Egroup_msg = "Entire newsgroup";

/*
	Help message table.  Character for command, plus its help
	message.  Table order is order of presentation to user.
*/
static struct HELPTAB
{
	char cmd, *msg;
	int dig;
	char *amsg;
} 
Helptab [] =
{
	{ QUIT, "quit", 1, NULL},
	{ UP, "(or up arrow) move up [number of lines]", 1, NULL},
	{ DOWN, "(or down arrow) move down [number of lines]", 1, NULL},
#ifdef PAGEARROW
	{ BACK, "(or left arrow) previous page [number of pages]", 1, NULL},
	{ FORWARD, "(or right arrow) next page [number of pages]", 1, NULL},
#else
	{ BACK, "previous page [number of pages]", 1, NULL},
	{ FORWARD, "next page [number of pages]", 1, NULL},
#endif
	{ DIGEST, "unpack digest", 1, "exit digest"},
	{ READ, "read article [number of articles]", 1, NULL},
	{ ALTREAD, "read article (alternate 'r')", 1, NULL},
	{ READALL, "read all articles on page", 1, NULL},
	{ READSTRING, "specify articles to read", 1, NULL},
	{ SAVE, "save or pipe article [number of articles]", 1, NULL},
	{ SAVEALL, "save or pipe all articles on page", 1, NULL},
	{ SAVESTRING, "specify articles to save", 1, NULL},
	{ ALTSAVE, "specify articles to save (alternate ctl-s)", 1, NULL},
	{ PRINT, "print article [number of articles]", 1, NULL},
	{ PRINTALL, "print all article on page", 1, NULL},
	{ PRINTSTRING, "specify articles to print", 1, NULL},
	{ UPDATE, "update .newsrc status to cursor", 0, NULL},
	{ UPALL, "update .newsrc status for whole newsgroup", 0, NULL},
	{ UPSEEN, "update .newsrc status for all pages displayed", 0, NULL},
	{ ORGGRP, "recover original .newsrc status for newsgroup", 0, NULL},
	{ ORGSTAT, "recover all original .newsrc status", 0, NULL},
	{ SSTAT, "display count of groups and pages - shown and total", 0, NULL},
	{ GRPLIST, "list newsgroups with new article, updated counts", 0, NULL},
	{ NEWGROUP, "specify newsgroup to display and/or resubscribe to", 1, NULL},
	{ UNSUBSCRIBE, "unsubscribe from group", 0, NULL},
	{ MARK, "mark/unmark article [number of articles]", 1, NULL},
	{ ART_MARK, "mark/unmark article [number of articles]", 1, NULL},
	{ UNMARK, "erase marks on articles", 1, NULL},
	{ HEADTOG, "toggle flag for display of headers when reading", 1, NULL},
	{ SETROT, "toggle rotation for reading", 1, NULL},
	{ REDRAW, "redraw screen", 1, NULL},
	{ UNESC, "escape to UNIX to execute a command", 1, NULL},
	{ HELP, "show this help menu", 1, NULL}
};

main(argc,argv)
int argc;
char **argv;
{
	/*
		initialize environment variables,
		scan .newsrc file, using any command line options present.
	 */
	term_set (START);
	envir_set ();
	sig_set (BRK_IN);

	scan_newsrc (argc-1,argv+1);
	tty_set (BACKSTOP);

	if (Lrec >= 0)
		session ();
	else
	{
		new_groups ();
		fprintf (stderr,"\nNo News\n");
	}

	tty_set (COOKED);
	wr_newsrc ();
	term_set (STOP);
}

/*
	main session handler processing input commands
	locals:
		count - count attached to command
		highrec - highest line on current page
		crec - current line

	NOTE: this is where a setjmp call is made to set the break reentry
		location.  Keep the possible user states in mind.
*/
session ()
{
	char alist [RECLEN], c;
	int newg, i, j, count, highrec, crec;
	jmp_buf brkbuf;

	tty_set (RAWMODE);
	newg = new_groups();
	find_page (0);
	Digest = 0;

	/* reentry point for break from within session interaction */
	setjmp (brkbuf);
	sig_set (BRK_SESS,brkbuf);
	Headflag = FALSE;
	Rot = 0;

	/* done this way so that user gets "really quit?" break treatment */
	if (newg > 0)
	{
		printf ("\n%s",Contstr);
		getnoctl();
		newg = 0;
	}

	/* list preview option - clear after first time for long jumps */
	if (Listfirst)
	{
		/* tot_list settings will be overwritten in this case */
		tot_list(&crec,&highrec);
		Listfirst = 0;
	}

	/* if breaking from a digest, recover original page */
	if (Digest)
	{
		find_page(Cur_page);
		Digest = 0;
	}
	show ();
	crec = RECBIAS;
	highrec = Page.h.artnum + RECBIAS;
	term_set (MOVE,0,crec);

	/*
		handle commands until QUIT, update global/local status
		and display for each.
	 */
	for (count = getkey(&c); c != QUIT; count = getkey(&c))
	{
		for (i=0; i < (j = sizeof(Helptab)/sizeof(struct HELPTAB)); ++i)
			if (Helptab[i].cmd == c)
				break;

		if (i >= j || (Digest && !Helptab[i].dig))
		{
			preinfo (UDKFORM,Cxptoi[HELP]);
			term_set (MOVE, 0, crec);
			continue;
		}

		switch (c)
		{
		case HEADTOG:
			if (Headflag)
			{
				Headflag = FALSE;
				prinfo (Hdoff_msg);
			}
			else
			{
				Headflag = TRUE;
				prinfo (Hdon_msg);
			}
			term_set (MOVE,0,crec);
			break;
		case SETROT:
			if (Rot == 0)
			{
				Rot = 13;
				prinfo (Roton_msg);
			}
			else
			{
				Rot = 0;
				prinfo (Rotoff_msg);
			}
			term_set (MOVE,0,crec);
			break;
		case SSTAT:
			count_msg ();
			term_set (MOVE,0,crec);
			break;
		case GRPLIST:
			tot_list (&crec,&highrec);
			show();
			term_set (MOVE,0,crec);
			break;
		case REDRAW:
			show();
			term_set (MOVE,0,crec);
			break;
		case UNSUBSCRIBE:
			(Page.h.group)->state &= ~FLG_SUB;
			wr_newsrc ();
			prinfo (Unsub_msg);
			term_set (MOVE,0,crec);
			break;

		case UPDATE:
			(Page.h.group)->rdnum = Page.b[crec-RECBIAS].art_id;
			wr_show ();
			wr_newsrc();
			term_set (MOVE,0,crec);
			break;
		case UPALL:
			(Page.h.group)->rdnum = (Page.h.group)->art;
			wr_newsrc();
			wr_show();
			prinfo (Egroup_msg);
			term_set (MOVE,0,crec);
			break;
		case ORGGRP:
			(Page.h.group)->rdnum = (Page.h.group)->orgrd;
			wr_newsrc();
			wr_show();
			prinfo (Egroup_msg);
			term_set (MOVE,0,crec);
			break;
		case UPSEEN:
			up_seen();
			prinfo ("All pages displayed to this point updated");
			wr_show();
			wr_newsrc();
			term_set (MOVE,0,crec);
			break;
		case ORGSTAT:
			for (i = 0; i < Ncount; ++i)
				(Newsorder[i])->rdnum = (Newsorder[i])->orgrd;
			prinfo ("Original data recovered");
			wr_show();
			wr_newsrc();
			term_set (MOVE,0,crec);
			break;
		case UP:
			if (crec != RECBIAS)
			{
				crec -= count;
				if (crec < RECBIAS)
					crec = RECBIAS;
				term_set (MOVE, 0, crec);
			}
			else
				putchar ('\07');
			break;
		case DOWN:
			if (crec < (highrec - 1))
			{
				crec += count;
				if (crec >= highrec)
					crec = highrec - 1;
				term_set (MOVE, 0, crec);
			}
			else
				putchar ('\07');
			break;
		case MARK:
		case ART_MARK:
			count += crec - 1;
			if (count >= highrec)
				count = highrec - 1;
			for (i=crec; i <= count; ++i)
			{
				if (Page.b[i-RECBIAS].art_mark != ART_MARK)
					Page.b[i-RECBIAS].art_mark = ART_MARK;
				else
					Page.b[i-RECBIAS].art_mark = ' ';
				if (i != crec)
					term_set (MOVE, 0, i);
				printf ("%c\010",Page.b[i-RECBIAS].art_mark);
			}
			if (count != crec)
				term_set (MOVE, 0, crec);
			write_page ();
			break;
		case UNMARK:
			for (i=0; i < Page.h.artnum; ++i)
			{
				if (Page.b[i].art_mark == ART_MARK)
				{
					Page.b[i].art_mark = ' ';
					term_set (MOVE, 0, i+RECBIAS);
					putchar (' ');
				}
			}
			term_set (MOVE, 0, crec);
			write_page ();
			break;
		case BACK:
			count *= -1;	/* fall through */
		case FORWARD:
			if (forward (count, &crec, &highrec) >= 0)
				show();
			else
				preinfo ("No more pages");
			term_set (MOVE,0,crec);
			break;
		case DIGEST:
			if (Digest)
			{
				Digest = 0;
				find_page (Cur_page);
				show();
				crec = Drec + RECBIAS + 1;
				highrec = Page.h.artnum + RECBIAS;
				if (crec >= highrec)
					crec = highrec - 1;
				term_set (MOVE,0,crec);
				break;
			}
			Dskip = count - 1;
			Drec = crec - RECBIAS;
			if (digest_page(Drec,Dskip) >= 0)
			{
				show();
				crec = RECBIAS;
				highrec = Page.h.artnum + RECBIAS;
				term_set (MOVE,0,crec);
				break;
			}
			Digest = 0;
			preinfo ("Can't unpack the article");
			term_set (MOVE,0,crec);
			break;
		case NEWGROUP:
			if ((i = spec_group()) < 0)
			{
				term_set (MOVE,0,crec);
				break;
			}
			Digest = 0;
			show();
			crec = RECBIAS;
			highrec = Page.h.artnum + RECBIAS;
			term_set (MOVE,0,crec);
			break;

		case SAVE:
			genlist (alist,crec-RECBIAS,count);
			savestr (alist);
			term_set (MOVE,0,crec);
			break;
		case SAVEALL:
			genlist (alist,0,L_allow);
			savestr (alist);
			term_set (MOVE,0,crec);
			break;
		case SAVESTRING:
		case ALTSAVE:
			userlist (alist);
			savestr (alist);
			term_set (MOVE,0,crec);
			break;
		case READ:
		case ALTREAD:
			genlist (alist,crec-RECBIAS,count);
			readstr (alist,&crec,&highrec,count);
			break;
		case READALL:
			genlist (alist,0,L_allow);
			readstr (alist,&crec,&highrec,0);
			break;
		case READSTRING:
			userlist (alist);
			readstr (alist,&crec,&highrec,0);
			break;
		case PRINT:
			genlist (alist,crec-RECBIAS,count);
			printstr (alist);
			term_set (MOVE,0,crec);
			break;
		case PRINTALL:
			genlist (alist,0,L_allow);
			printstr (alist);
			term_set (MOVE, 0, crec);
			break;
		case PRINTSTRING:
			userlist (alist);
			printstr (alist);
			term_set (MOVE, 0, crec);
			break;

		case HELP:
			help ();
			show ();
			term_set (MOVE, 0, crec);
			break;
		case UNESC:
			user_str (alist,Ps1,1);
			term_set (ERASE);
			fflush (stdout);
			tty_set (SAVEMODE);
			if (chdir(Orgdir) < 0)
				printf ("change to original directory, %s, failed",Orgdir);
			else
			{
				system (alist);
				tty_set (RESTORE);
				term_set (RESTART);
			}
			printf (Contstr);
			getnoctl ();
			cd_group ();
			show ();
			term_set (MOVE, 0, crec);
			break;
		default:
			printex ("Unhandled key: %c", c);
			break;
		}
	}

	Digest = 0;
	for (i=0; i < Ncount; ++i)
	{
		if ((Newsorder[i])->rdnum < (Newsorder[i])->pgrd)
			break;
	}
	if (i < Ncount)
	{
		user_str (alist,"Some displayed pages not updated - update ? ",1);
		if (alist[0] == 'y')
			up_seen();
	}
	sig_set (BRK_OUT);
}

/*
** update status of Newsgroups to all seen pages
*/
up_seen()
{
	int i;

	for (i = 0; i < Ncount; ++i)
	{
		if (Nounsub && ((Newsorder[i])->state & FLG_SUB) == 0)
		{
			(Newsorder[i])->rdnum = (Newsorder[i])->art;
			continue;
		}
		if ((Newsorder[i])->rdnum < (Newsorder[i])->pgrd)
			(Newsorder[i])->rdnum = (Newsorder[i])->pgrd;
	}
}

/*
	count_msg displays count information
*/
count_msg ()
{
	int i, gpnum, gscan, gpage;
	unsigned long mask;
	gpnum = 1;
	for (gscan = gpage = i = 0; i<Ncount; ++i)
	{
		if (((Newsorder[i])->state & FLG_PAGE) != 0)
		{
			if (((Newsorder[i])->pnum + (Newsorder[i])->pages - 1) < Cur_page)
				++gpnum;
			++gpage;
			for (mask=1; mask != 0L; mask <<= 1)
				if (((Newsorder[i])->pgshwn & mask) != 0L)
					++gscan;
		}
	}
	prinfo (CFORMAT,Cur_page+1,Lrec+1,gscan,gpnum,gpage);
}

/*
	forward utility handles paging to allow it to happen globally.
	(from readstr, for instance)
*/
forward (count, crec, highrec)
int count, *crec, *highrec;
{
	if (!Digest)
	{
		if ((count < 0 && Cur_page <= 0) || (count > 0 && Cur_page >= Lrec))
			return (-1);
		Cur_page += count;
		if (Cur_page < 0)
			Cur_page = 0;
		if (Cur_page > Lrec)
			Cur_page = Lrec;
		find_page (Cur_page);
		*crec = RECBIAS;
		*highrec = Page.h.artnum + RECBIAS;
		return (0);
	}
	/*
	** in digests, paging past the end of the digest returns to
	** page extracted from.
	*/
	if (Dskip > 0 && (Dskip + count*L_allow) < 0)
		Dskip = 0;
	else
		Dskip += count * L_allow;
	find_page (Cur_page);
	if (Dskip >= 0)
	{
		if (digest_page(Drec,Dskip) >= 0)
		{
			*crec = RECBIAS;
			*highrec = Page.h.artnum + RECBIAS;
			return (0);
		}
	}
	Digest = 0;
	*crec = Drec + RECBIAS + 1;
	*highrec = Page.h.artnum + RECBIAS;
	if (*crec >= *highrec)
		*crec = *highrec - 1;
	return (0);
}

/*
	error/abnormal condition cleanup and abort routine
	pass stack to printf
*/
printex (s,a,b,c,d,e,f)
char *s;
long a,b,c,d,e,f;
{
	static int topflag=0;
	if (topflag == 0)
	{
		++topflag;
		term_set (STOP);
		tty_set (COOKED);
		fflush (stdout);
		fprintf (stderr,s,a,b,c,d,e,f);
		fprintf (stderr," (error code %d)\n",errno);
		exit (1);
	}
	else
		fprintf (stderr,s,a,b,c,d,e,f);
}

/*
	getkey obtains user keystroke with count from leading
	numerics, if any.  Picks up arrow key sequences and maps
	them to other keys.  Also translates character through
	Cxitop array since this routine is only used in session
	loop.  Saves untranslating arrow keys.
*/
getkey (c)
char *c;
{
	int i, j;
	static char	ckseq[32];

	/* Check for leading count */
	for (i = 0; (*c = getchar() & 0x7f) >= '0' && *c <= '9'; i = i * 10 + *c - '0')
		;

	/* @#$!!! flakey front ends that won't map newlines in raw mode */
	if (*c == '\012' || *c == '\015')
		*c = '\n';

	/* @#$!!! flakey terminals which send control sequences for cursors! */
	if( *c == '\033' )
	{
		/*
		** Check if part of cursor key input sequence
		** (pitch unknown escape sequences)
		*/
		j = 0;
		ckseq[j] = *c; ckseq[j+1] = '\0';
		while(*c == Ku[j] || *c == Kd[j] || *c == Kl[j] || *c == Kr[j])
		{
			if( strcmp(ckseq, Ku) == 0 ) { *c = UP; break; }
			if( strcmp(ckseq, Kd) == 0 ) { *c = DOWN; break; }
#ifdef PAGEARROW
			if( strcmp(ckseq, Kl) == 0 ) { *c = BACK; break; }
			if( strcmp(ckseq, Kr) == 0 ) { *c = FORWARD; break; }
#else
			if( strcmp(ckseq, Kl) == 0 ) { *c = UP; break; }
			if( strcmp(ckseq, Kr) == 0 ) { *c = DOWN; break; }
#endif
			*c = (getchar() & 0x7f);
			ckseq[++j] = *c; ckseq[j+1] = '\0';
		}
	}
	else
		*c = Cxitop[*c];

	if (i <= 0)
		i = 1;
	return (i);
}


/*
	get user key ignoring most controls
*/
getnoctl ()
{
	char c;
	while ((c = getchar() & 0x7f) < ' ' || c == '\177')
	{
		if (c == '\015' || c == '\012')
			c = '\n';
		if (c == '\n' || c == '\b' || c == '\t')
			return (c);
	}
	return ((int) c);
}

/*
	generate list of articles on current page,
	count articles, starting with first.
*/
genlist (list,first,count)
char *list;
int first,count;
{
	int i;
	for (i=first; i < Page.h.artnum && count > 0; ++i)
	{
		sprintf (list,"%d ",Page.b[i].art_id);
		list += strlen(list);
		--count;
	}
}

/*
	send list of articles to printer
*/
printstr (s)
char *s;
{
	char *ptr, cmd [RECLEN], *strpbrk();
	prinfo ("preparing print command ....");
	for (ptr = s; (ptr = strpbrk(ptr, LIST_SEP)) != NULL; ++ptr)
		*ptr = ' ';
	while (*s == ' ')
		++s;
	if (Digest)
		dig_list (s);
	if (*s != '\0')
	{
		sprintf (cmd,"%s %s 2>/dev/null",Printer,s);
		if (system (cmd) == 0)
			prinfo ("Sent to printer");
		else
			preinfo ("Print failed");
	}
	else
		preinfo (No_msg);
	if (Digest)
		dig_ulist (s);
}

/*
	concatenate articles to save file with appropriate infoline messages.
	prompt for save file, giving default.  If save file begins with "|"
	handle as a filter to pipe to.  NOTE - every user specification of
	a new Savefile "loses" some storage, but it shouldn't be a very great
	amount.
*/
savestr (s)
char *s;
{
	char *ptr, cmd [RECLEN], newfile [MAX_C+1], prompt[MAX_C];
	char *strtok(), *strpbrk(), *str_store();

	for (ptr = s; (ptr = strpbrk(ptr, LIST_SEP)) != NULL; ++ptr)
		*ptr = ' ';
	while (*s == ' ')
		++s;
	if (Digest)
		dig_list (s);
	if (*s != '\0')
	{
		sprintf (prompt,SAVFORM,Savefile);
		user_str (newfile,prompt,1);
		ptr = newfile;
		if (*ptr == '|')
		{
			sprintf(cmd,"cat %s %s",s,ptr);
			term_set (ERASE);
			fflush (stdout);
			tty_set (SAVEMODE);
			system (cmd);
			tty_set (RESTORE);
			printf (Contstr);
			getnoctl ();
			show ();
		}
		else
		{
			prinfo ("saving .... ");
			if (*ptr == '\0')
				ptr = Savefile;
			else
				Savefile = str_store(ptr);
			if (*ptr != '/' && *ptr != '$')
				sprintf(cmd,"cat %s >>%s/%s 2>/dev/null",s,Savedir,ptr);
			else
				sprintf(cmd,"cat %s >>%s 2>/dev/null",s,ptr);
			if (system (cmd) == 0)
				prinfo ("Saved");
			else
				preinfo ("Could not append save file");
		}
	}
	else
		preinfo (No_msg);
	if (Digest)
		dig_ulist (s);
}

/*
	basic page display routine.  erase screen and format current page
*/
show ()
{
	int i;
	unsigned long mask;
	char helpstr[40]; 

	term_set (ERASE);
	C_info = 0;
	i = Cur_page - (Page.h.group)->pnum + 1;
	if (Digest)
		printf (DHFORMAT,Page.h.name);
	else
		printf (HFORMAT,Page.h.name,i,(Page.h.group)->pages);

	mask = 1L << (i-1);
	(Page.h.group)->pgshwn |= mask;
	mask = 1;
	for (--i; i > 0 && (mask & (Page.h.group)->pgshwn) != 0 ; --i)
		mask <<= 1;
	if (i <= 0)
		(Page.h.group)->pgrd = Page.b[(Page.h.artnum)-1].art_id;

	for (i=0; i < Page.h.artnum; ++i)
	{
		if (Digest)
		{
			printf(Aformat,Page.b[i].art_mark,ART_UNWRITTEN,Page.b[i].art_id);
			printf("%s",Page.b[i].art_t);
			continue;
		}

		if ((Page.h.group)->rdnum >= Page.b[i].art_id)
			printf(Aformat,Page.b[i].art_mark,ART_WRITTEN,Page.b[i].art_id);
		else
			printf(Aformat,Page.b[i].art_mark,ART_UNWRITTEN,Page.b[i].art_id);
		printf("%s",Page.b[i].art_t);
	}

	sprintf(helpstr,HELPFORM,Cxptoi[HELP]);
	if (!Digest && ((Page.h.group)->state & FLG_SUB) == 0)
		prinfo ("%s, %s",Unsub_msg,helpstr);
	else
		prinfo (helpstr);
}

/*
	update written status marks on screen
*/
wr_show ()
{
	int i,row;
	char c;

	row = RECBIAS;
	for (i=0; i < Page.h.artnum; ++i)
	{
		term_set (MOVE,WRCOL,row);
		if ((Page.h.group)->rdnum >= Page.b[i].art_id)
			c = ART_WRITTEN;
		else
			c = ART_UNWRITTEN;
		printf("%c",c);
		++row;
	}
}

/*
	obtain user input of group name, becomes current page if valid.
	returns -1 or page number.  calling routine does the show, if needed
*/
spec_group ()
{
	char nbuf [MAX_C + 1];
	NODE *p, *hashfind();

	user_str(nbuf,"Newsgroup ? ",1);

	if (*nbuf == '\0' || (p = hashfind(nbuf)) == NULL)
	{
		preinfo ("Not a newsgroup");
		return (-1);
	}
	if ((p->state & FLG_PAGE) == 0)
	{
		if ((p->state & FLG_SUB) == 0)
		{
			p->state |= FLG_SUB;
			wr_newsrc ();
			prinfo ("Not subscribed: resubscribed for next reading session");
		}
		else
			prinfo ("No news for that group");
		return (-1);
	}
	if ((p->state & FLG_SUB) == 0)
	{
		p->state |= FLG_SUB;
		wr_newsrc ();
	}
	find_page (p->pnum);
	return (p->pnum);
}

/*
	obtain user input with prompt p.  Optionally on info line.
	handle erase and kill characters, suppresses leading
	white space.
*/
user_str (s,p,iline)
char *s;
char *p;
int iline;
{
	int i,idx;

	if (iline)
	{
		prinfo ("%s",p);
		idx = C_info;
	}
	else
	{
		printf ("%s",p);
		idx = strlen(p);
	}

	for (i=0; idx < C_allow && (s[i] = getchar() & 0x7f) != '\012' && s[i] != '\015'; ++i)
	{
		if (s[i] == Erasekey)
		{
			if (i > 0)
			{
				term_set (RUBSEQ);
				i -= 2;
				--idx;
			}
			continue;
		}
		if (s[i] == Killkey)
		{
			prinfo ("%s",p);
			i = -1;
			continue;
		}
		if ((s[i] == ' ' || s[i] == '\t') && i == 0)
		{
			i = -1;
			continue;
		}
		++idx;
		putchar (s[i]);
	}

	if (iline)
		C_info = idx;

	s[i] = '\0';
}


/*
	print something on the information line,
	clearing any characters not overprinted.
	preinfo includes reverse video and a bell for error messages.
*/
preinfo (s,a,b,c,d,e,f)
{
	int l;
	char buf[RECLEN];

	term_set (MOVE,0,INFOLINE);
	putchar ('\07');
	term_set (ONREVERSE);
	sprintf (buf,s,a,b,c,d,e,f);
	printf (" %s ",buf);
	term_set (OFFREVERSE);
	l = strlen(buf) + 2;
	if (l < C_info)
		term_set (ZAP,l,C_info);
	C_info = l;
	fflush(stdout);
}

prinfo (s,a,b,c,d,e,f)
char *s;
long a,b,c,d,e,f;
{
	int l;
	char buf[RECLEN];
	term_set (MOVE,0,INFOLINE);
	sprintf (buf,s,a,b,c,d,e,f);
	printf ("%s",buf);
	l = strlen(buf);
	if (l < C_info)
		term_set (ZAP,l,C_info);
	C_info = l;
	fflush(stdout);
}

/*
	help menu
*/
help ()
{
	int i,lcount,lim; 
	term_set (ERASE);
	lim = L_allow + RECBIAS - 2;
	printf("%s\n",HELP_HEAD);
	lcount = HHLINES;
	for (i=0; i < (sizeof(Helptab))/(sizeof(struct HELPTAB)); ++i)
	{
		if (Digest && !(Helptab[i].dig))
			continue;
		++lcount;
		if (Digest && Helptab[i].amsg != NULL)
			h_print (Cxptoi[Helptab[i].cmd],Helptab[i].amsg);
		else
			h_print (Cxptoi[Helptab[i].cmd],Helptab[i].msg);
		if (lcount >= lim)
		{
			printf ("\n%s",Contstr);
			getnoctl ();
			term_set (MOVE,0,lim+1);
			term_set (ZAP,0,strlen(Contstr));
			term_set (MOVE,0,lim-1);
			putchar ('\n');
			lcount = 0;
		}
	}
	if (lcount > 0)
	{
		printf ("\n%s",Contstr);
		getnoctl ();
	}
}

/*
	h_print prints a character and a legend for a help menu.
*/
h_print(c,s)
char c,*s;
{
	if (strlen(s) > (C_allow - 14))
		s [C_allow - 14] = '\0';
	if (c > ' ' && c != '\177')
		printf ("	 %c - %s\n",c,s);
	else
	{
		switch (c)
		{
		case '\177':
			printf ("  <delete> - %s\n",s);  
			break;
		case '\040':
			printf ("   <space> - %s\n",s);  
			break;
		case '\033':
			printf ("  <escape> - %s\n",s);  
			break;
		case '\n':
			printf ("  <return> - %s\n",s);  
			break;
		case '\t':
			printf ("     <tab> - %s\n",s);  
			break;
		case '\b':
			printf (" <back sp> - %s\n",s);  
			break;
		case '\f':
			printf ("<formfeed> - %s\n",s);  
			break;
		case '\07':
			printf ("    <bell> - %s\n",s);  
			break;
		case '\0':
			printf ("    <null> - %s\n",s);  
			break;
		default:
			if (c < '\033')
			{
				c += 'a' - 1;
				printf(" control-%c - %s\n",c,s);
			}
			else
				printf("       %c0%o - %s\n",'\\',(int) c,s);
			break;
		}
	}
}

tot_list (rec,hirec)
int *rec;
int *hirec;
{
	int i,max,len;
	char c;
	char ff[MAX_C+1];

	term_set (ERASE);

	for (max=i=0; i < Ncount; ++i)
	{
		if ((Newsorder[i])->pages == 0)
			continue;
		if ((len = strlen((Newsorder[i])->nd_name)) > max)
			max = len;
	}

	sprintf (ff,"%%4d %%%ds: %%3d new %%3d updated\n",max);

	for (len=i=0; i < Ncount; ++i)
	{
		if ((Newsorder[i])->pages == 0)
			continue;
		printf (ff, i, (Newsorder[i])->nd_name,
				(Newsorder[i])->art - (Newsorder[i])->orgrd,
				(Newsorder[i])->rdnum - (Newsorder[i])->orgrd);
		++len;
		if (len == L_allow && i < (Ncount-1))
		{
			printf("\nr - return, n - new group, other to continue ... ");
			if ((c = getnoctl()) == 'r' || c == 'n')
				break;
			printf ("\n\n");
			len = 0;
		}
	}
	if (i >= Ncount)
	{
		printf("n - new group, other to return ... ");
		c = getnoctl();
	}
	if (c == 'n')
	{
		printf("\n");
		user_str(ff,"Newsgroup number ? ",0);
		i = atoi(ff);
		if (i < 0)
			i = 0;
		if (i >= Ncount)
			i = Ncount-1;
		find_page((Newsorder[i])->pnum);
		*rec = RECBIAS;
		*hirec = Page.h.artnum + RECBIAS;
	}
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'config.h'" '(3422 characters)'
if test -f 'config.h'
then
	echo shar: will not over-write existing file "'config.h'"
else
cat << \SHAR_EOF > 'config.h'
/*
** vn news reader.
**
** config.h - system configuration parameters
**
** see copyright disclaimer / history in vn.c source file
*/

#define DEF_ED "/usr/ucb/vi"	/* editor to use if no EDITOR variable */
#define DEF_PS1 "$ "		/* ! command prompt if no PS1 */
#define DEF_SAVE "vn.save"	/* save file */

/*
** mailer interface.  If INLETTER is defined, a "To:" line will be
** placed in the file being editted by the user.  Otherwise, the
** address will be an argument on the mailer's command line, with the
** user prompted for possible correction.  In either case, "Subject: "
** is included in the file.
**
** If MAILSMART is set, alternate header lines will be used instead of
** the "Path: " line to determine the address because we assume the mailer
** is intelligent enough to do routing.
**
** If ADDRMUNGE is set, it is the name of a local routine which will be
** called to make further address modifications before the address is used.
** It will be passed the address string, which it may modify.  RECLEN bytes
** of storage are available at the address passed.  It will only be called
** once for a given address.
**
** DEF_MAIL is the mailer used in absence of MAILER variable.
*/
#define INLETTER
#define MAILSMART
#define DEF_MAIL "/usr/lib/sendmail -t"

#define DEF_PRINT "/usr/ucb/lpr"		/* print command */
#define DEF_POST "/usr/lib/news/inews -h"	/* followup posting command */

#define DEF_NEWSRC ".newsrc"
#define DEF_CCFILE "author_copy"
#define DEF_KEYXLN ".vnkey"

#define SPOOLDIR "/usr/spool/news"
#define ACTFILE "/usr/lib/news/active"

/*
** foreground flag for messages.  applies only if JOBCONTROL undefined
** (SYS V). set to 1 to see newsgroup messages, etc. during reading phase,
** 0 for "silent" operation - be warned that this may suppress some
** non-fatal diagnostic messages - find all references to fgprintf to
** see what is suppressed.
*/
#define NOJOB_FG 1

/*
** arrow key treatment.  If PAGEARROW is defined, right and left arrow
** keys will be synonyms for <return> (next-page) and <backspace> (previous).
** Otherwise, the right arrow will function as down, and the left as up.
** Made configurable because while there is no lateral motion on the screen
** to associate with the right and left arrows, you might not like them
** changing pages on you.
*/
#define PAGEARROW

/*
** if USEVS is defined, terminal initialization / exit for vn will include the
** "vs"/"ve" pair as well as "ti"/"te".  This doesn't matter on a lot of
** terminals, but may make vn display behaviour closer to "vi" since vs/ve
** is vi's "visual mode" sequence.  For instance, I believe the commonly
** used definitions for these strings on multi-page concepts allows the
** program to run in the first page of the terminal, preserving the more
** recent part of your session on exit
**
** #define USEVS
*/

/*
** temp file name template for mktemp().  Used in tmpnam.c, does not apply
** if you use a system library tmpnam().   BE CAREFUL - VNTEMPNAME MUST
** contain a string of 6 X's for mktemp() (actually, a place where 6 X's
** are intended to go).  TMP_XOFFSET absolutely MUST point to the first of
** the X's.  Yes, writing into a literal string is sloppy.  To the best of
** my knowledge, tmpnam.c is the only place you'll find vn code doing it.
** We make this configurable in case you want temp files somewhere else.
*/
#define VNTEMPNAME "/usr/tmp/vnXXXXXX"
#define TMP_XOFFSET 11
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'head.h'" '(839 characters)'
if test -f 'head.h'
then
	echo shar: will not over-write existing file "'head.h'"
else
cat << \SHAR_EOF > 'head.h'
/*
** vn news reader.
**
** head.h - header line strings and lengths
**
** see copyright disclaimer / history in vn.c source file
*/

/*
	header lines and associated lengths.  Strings should
	actually be used once in strings.c.
*/ 
#define RHEAD "References: "
#define RHDLEN 12
#define MHEAD "Message-ID: "
#define MHDLEN 12
#define PHEAD "Path: "
#define PHDLEN 6
#define DHEAD "Date: "
#define DHDLEN 6
#define RTHEAD "Reply-To: "
#define RTHDLEN 10
#define TOHEAD "To: "
#define TOHDLEN 4
#define FHEAD "From: "
#define FHDLEN 6
#define FTHEAD "Followup-To: "
#define FTHDLEN 13
#define DISHEAD "Distribution: "
#define DISHDLEN 14
#define THEAD "Subject: "
#define THDLEN 9
#define LHEAD "Lines: "
#define LHDLEN 7
#define NHEAD "Newsgroups: "
#define NHDLEN 12

#define CHFIRST "FSL"	/* first char's of those used in page display */
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'reader.h'" '(1498 characters)'
if test -f 'reader.h'
then
	echo shar: will not over-write existing file "'reader.h'"
else
cat << \SHAR_EOF > 'reader.h'
/*
** vn news reader.
**
** reader.h - article reading interface definitions
**
** see copyright disclaimer / history in vn.c source file
*/

#define PAGE_MID ":more (%2d%%):"
#define PAGE_NEXT ":next article:"
#define PAGE_END ":end:"
#define PAGE_NO ":?:"
#define PPR_MAX 18	/* maximum length of PAGE prompts */

/*
	reading commands: no control chars, add help message to helppg
	SAVE, PRINT, HEADTOG and SETROT are also recognized
*/
#define HPG_HEAD "toggle header print flag"
#define HPG_ROT "toggle rotation"
#define HPG_SAVE "save article in a file"
#define HPG_PRINT "print article"
#define PG_NEXT 'n'
#define HPG_NEXT "next article, if any"
#define PG_QUIT 'q'
#define HPG_QUIT "quit reading articles, if any more to read"
#define PG_FLIP 'Q'
#define HPG_FLIP "quit reading, and turn to next page of articles"
#define PG_FOLLOW 'f'
#define HPG_FOLLOW "post followup to article"
#define PG_REPLY 'm'
#define HPG_REPLY "send mail to author of article"
#define PG_HELP '?'
#define HPG_HELP "see this help menu"
#define PG_REWIND 'r'
#define HPG_REWIND "rewind article to beginning"
#define PG_WIND 'e'
#define HPG_WIND "seek to end of article (to next/end prompt)"
#define PG_STEP '\n'
#define HPG_STEP "next line"
#define PG_SEARCH '/'
#define HPG_SEARCH "search for regular expression in remainder of article"
#define SEARCHFORM "search pattern (%s) ? "
#define HPG_DEF "\n anything else to continue normal reading"
#define HPG_EDEF "\n anything else to try reading next article, if any"
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'tty.h'" '(404 characters)'
if test -f 'tty.h'
then
	echo shar: will not over-write existing file "'tty.h'"
else
cat << \SHAR_EOF > 'tty.h'
/*
** vn news reader.
**
** tty.h - codes for tty_set and term_set
**
** see copyright disclaimer / history in vn.c source file
*/

#define MOVE 100
#define ERASE 101
#define START 102
#define STOP 103
#define RUBSEQ 104
#define ZAP 105
#define ONREVERSE 106
#define OFFREVERSE 107
#define RESTART 108

#define RAWMODE 200
#define COOKED 201
#define SAVEMODE 202
#define RESTORE 203
#define BACKSTOP 204
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'tune.h'" '(2613 characters)'
if test -f 'tune.h'
then
	echo shar: will not over-write existing file "'tune.h'"
else
cat << \SHAR_EOF > 'tune.h'
/*
** vn news reader.
**
** tune.h - system tuning parameters
**
** see copyright disclaimer / history in vn.c source file
*/

/*
**	buffer size needed for tmpnam()
*/
#ifndef L_tmpnam
#define L_tmpnam 48
#endif

/*
** hash table size.  linked list type of table which can expand to
** arbitrary density, including densities > 100%.  Number of entries
** will be number of newsgroups in active list.  This should be a prime
** number ("long division" of string modulo table size hash function).
*/
#define HASHSIZE 809

/*
**	maximum number of columns on terminal.  If made smaller, there
**	will be a savings in the size of the temporary file used
**	for holding displays, at the penalty of not being able to use
**	the entire screen width on terminals actually possessing more
**	columns than this.  A block roughly on the order of this value
**	times the number of lines the terminal has is maintained per page in
**	the temp file, and read / written as displays are interacted
**	with.  MIN_C put here because MAX_C > MIN_C.  MIN_C is the minumum
**	number of columns for which a "reasonable" display can be produced.
**	before making it smaller, look at all uses of C_allow and variable
**	to see that a setting that small won't screw up array bounds.
*/
#define MAX_C 132
#define MIN_C 36

/*
**	large size for general purpose local buffers.  only used in automatic
**	variable declarations.  Used with fgets for buffer size when reading
**	file records, to hold pathnames, commands, etc.  Reduce if you blow
**	out stack storage.  If reduced too far, will eventually show up
**	as syntax errors on reading .newsrc's and the active list, and
**	scrozzled article information arising from truncated header lines.
**	The reply path line will probably be the first thing to cause trouble.
**	Look through the reader to find the worst case chain of declarations
**	(on the order of 12 or so is probably the max).
*/
#define RECLEN 1200

/*
**	to protect against reading entire articles to find non-existent header
**	lines if an article should be hosed, only a limited number of records
**	are searched.  Should be big enough to get down to the last header
**	entry on legitimate articles.
*/
#define HDR_LINES 24	/* records of article to search for header line */

/* these determine some static array sizes */
#define OPTLINES 60	/* maximum number of option lines in .newsrc */
#define NUMFILTER 24	/* max number of filters on articles */

/* block sizes for allocation routines */
#define STRBLKSIZE 1800	/* string storage allocation block */
#define NDBLKSIZE 50	/* NODE structures to allocate at a time */
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'vn.h'" '(4278 characters)'
if test -f 'vn.h'
then
	echo shar: will not over-write existing file "'vn.h'"
else
cat << \SHAR_EOF > 'vn.h'
/*
** vn news reader.
**
** vn.h - general parameters
**
** see copyright disclaimer / history in vn.c source file
*/

#include "tune.h"

#define TRUE 1
#define FALSE 0

#ifdef OLDRC
#define NARGOPT "lprxfuMs"
#else
#define NARGOPT "lprxfuMsi"
#endif

#define FIL_AUTHOR 'w'
#define FIL_TITLE 't'

/*
	newsrc states
*/
#define NEWS_ON ':'
#define NEWS_OFF '!'

/* bit flags for state of newsgroup */
#define FLG_SCAN 1
#define FLG_SUB 2
#define FLG_PAGE 4
#define FLG_WRIT 8
#define FLG_SPEC 16

#define LIST_SEP " 	,"
#define ED_MARK '>'
#define ART_MARK '*'
#define ART_WRITTEN '_'
#define ART_UNWRITTEN ' '

#define FPFIX "Re: "
#define FPFLEN 4

#define ANFORM ":%s - %c for help:\n"
#define ANFLINES 1
#define NOFORM "can't open article %s\n"
#define NEWGFORM "groups not mentioned in %s:\n"
#define SAVFORM "save file (%s) ? "
#define UDKFORM "undefined key - %c for help"
#define HELPFORM "%c for help"

/*
	page display format and dependent parameters
*/
#define HFORMAT "\n%s (page %d of %d):"
#define DHFORMAT "\n%s (DIGEST EXTRACTION):"
#define TFORMAT "%s ~ %s %s"
#define AFORMAT "\n%c%c%d) "	/* begin with newline - see show routine */
#define CFORMAT "page %d of %d (%d shown), newsgroup %d of %d"
#define RECBIAS 2	/* lines before articles - depends on HFORMAT */
#define AFLEN 5		/* min. char. in article id - depends on AFORMAT */
#define WRCOL 1		/* column of written mark.  depends on AFORMAT */
#define INFOLINE 0	/* HFORMAT TFORMAT leaves for use */

/*
	command characters - don't use numerics or <ESC>
	ALTSAVE is a hack to avoid having to use ctl-s - XON/XOFF.
	Wanted to preserve "s" pneumonic and lower / control /cap
	convention.
*/
#define DIGEST 'd'
#define UP 'k'
#define DOWN 'j'
#define FORWARD '\012'
#define BACK '\010'
#define READ 'r'
#define ALTREAD ' '
#define READALL 'R'
#define READSTRING '\022'
#define SAVE 's'
#define SAVEALL 'S'
#define SAVESTRING '\023'
#define ALTSAVE '\024'
#define PRINT 'p'
#define PRINTALL 'P'
#define PRINTSTRING '\020'
#define MARK 'x'
#define UNMARK 'X'
#define REDRAW '\014'
#define QUIT 'q'
#define SSTAT '#'
#define GRPLIST '%'
#define ORGGRP 'o'
#define ORGSTAT 'O'
#define UPDATE 'w'
#define UNSUBSCRIBE 'u'
#define UPALL 'W'
#define UPSEEN '\027'
#define UNESC '!'
#define NEWGROUP 'n'
#define HEADTOG 'h'
#define SETROT 'z'
#define HELP '?'
#define HELP_HEAD "[...] = effect of optional number preceding command\n\
pipes are specified by filenames beginning with |\n\
articles specified as a list of numbers, title search string, or\n\
	* to specify marked articles.  ! may be used to negate any\n"

#define HHLINES 5	/* lines (CRs + 1) contained in HELP_HEAD */

/*
	state flags for handling breaks / values for sig_set calls.
	BRK_IN, BRK_SESS, BRK_READ and BRK_OUT are the states.  All
	but BRK_INIT are used as calls to sig_set.  BRK_RFIN indicates
	a return from BRK_READ to BRK_SESS (no jump location passed),
*/
#define BRK_INIT 0		/* initial value, indicating uncaught signals */
#define BRK_IN 1		/* in NEWSRC / article scanning phase */
#define BRK_SESS 2		/* in page interactive session */
#define BRK_READ 3		/* reading articles */
#define BRK_RFIN 4		/* finished reading, return to old mode */
#define BRK_OUT 5		/* NEWSRC updating phase */

#define BRK_PR "really quit ? "
#define BRK_MSG "\nQUIT (signal %d)"

/*
	newsgroup structure (node of hash table)
	next - hashtable link
	nd_name - name of newsgroup (key to reach node by)
	pnum - page number, initially used to establish Newsorder
	pages - number of pages for news display
	rdnum - articles read
	orgrd - original articles read number
	pgshwn - pages shown mask
	pgrd - article number on highest conecutively shown page
	art - articles in group
	state - status
*/
typedef struct _node
{
	struct _node *next;
	char *nd_name;
	int pnum,pages,art,rdnum,orgrd,pgrd;
	unsigned long pgshwn;
	unsigned state;
} NODE;

/*
	newsgroup information for page display
	name - of group
	group - pointer to table entry
	artnum - number of articles
*/
typedef struct
{
	char *name;
	NODE *group;
	int artnum;
} HEAD;

/*
	article information - id (spool) number, title string, mark, written.
*/
typedef struct
{
	int art_id;
	char art_mark;
	char art_written;
	char art_t[MAX_C-AFLEN];
} BODY;

typedef struct
{
	HEAD h;
	BODY *b;
} PAGE;
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0

bobm@rtech.UUCP (Bob Mcqueer) (01/01/87)

Bob McQueer
{amdahl, sun, mtxinu, hoptoad, cpsc6a}!rtech!bobm

cut here
-----------------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	reader.c
#	newsrc.c
# This archive created: Thu Jan  1 11:15:26 1987
export PATH; PATH=/bin:$PATH
echo shar: extracting "'reader.c'" '(18610 characters)'
if test -f 'reader.c'
then
	echo shar: will not over-write existing file "'reader.c'"
else
cat << \SHAR_EOF > 'reader.c'
/*
** vn news reader.
**
** reader.c - article reading interface - "more" like.
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>
#include <sys/types.h>
#include "tty.h"
#include "config.h"
#include "vn.h"
#include "head.h"
#include "reader.h"

#define PERTAB 8	/* tab expansion factor */
#define BACKTRACK 24

extern char *Printer,*Editor,*Mailer,*Poster,*Orgdir,*Savefile,*Savedir,*Ccfile;
extern int L_allow;
extern int C_allow;
extern int Rot;
extern int Headflag;
extern int Digest;
extern char *No_msg;
extern char *Roton_msg;
extern char *Rotoff_msg;
extern char *Hdon_msg;
extern char *Hdoff_msg;

extern char *T_head, *FT_head, *N_head, *L_head, *RT_head, *DIS_head;
extern char *TO_head, *F_head, *P_head, *M_head, *R_head;

extern char Cxrtoi[], Cxitor[];

static FILE *Fpread;
static char *Fname;
static char *Lookahead;
static int Rlines;
static int Hlines;

#ifdef ADDRMUNGE
static int Newaddr;
#endif

/*
	readstr routine is the "funnel" to the reading state,
	and controls signal setting.  Some "session" context is passed
	along to allow jumping back to a different display

	WARNING:

	NOTHING below readstr should call strtok()
*/

readstr (s,crec,highrec,count)
char *s;
int *crec, *highrec;
int count;
{
	char *fnext, *strtok();
	int pc;
	Fname = strtok(s,LIST_SEP);
	if (Fname != NULL)
	{
		term_set (ERASE);
		sig_set (BRK_READ,&Fpread);
		fnext = strtok(NULL,LIST_SEP);
		while (Fname != NULL && readfile(fnext,&pc) >= 0)
		{
			if (Digest)
				unlink (Fname);
			Fname = fnext;
			fnext = strtok (NULL,LIST_SEP);
		}
		if (Digest && Fname != NULL)
			unlink (Fname);
		if (pc != 0)
			forward (pc, crec, highrec);
		else
		{
			*crec += count;
			if (*crec >= *highrec)
				*crec = *highrec - 1;
		}
		sig_set (BRK_RFIN);
		show ();
		term_set (MOVE, 0, *crec);
	}
	else
	{
		preinfo ("%s",No_msg);
		term_set (MOVE, 0, *crec);
	}
}

/*
	readfile presents article:
		sn - name of next article, NULL if last.
		pages - pages to advance on return, if applicable
	returns 0 for "continue", <0 for "quit"
*/
static readfile (sn,pages)
char *sn;
int *pages;
{
	FILE *fopen();
	int lines,percent,artlin;
	long rew_pos, ftell();
	char c,  buf[RECLEN], mid[RECLEN], ngrp[RECLEN], dist[RECLEN];
 	char from[RECLEN], title[RECLEN], flto[RECLEN], reply[RECLEN];
 	char pstr[24], dgname[48], getpgch(), *index(), *digest_extract();
	char *tgetstr();

	*pages = 0;

	term_set(ERASE);

	if (Digest)
	{
		lines = atoi(Fname);
		if ((Fname = digest_extract(dgname,lines)) == NULL)
		{
			printf ("couldn't extract article %d",lines);
			return (0);
		}
	}

	if ((Fpread = fopen(Fname,"r")) == NULL)
	{
		printf ("couldn't open article %s",Fname);
		return (0);
	}

	Hlines = gethead (mid, from, title, ngrp, flto, reply, dist, &artlin);
	printf (ANFORM,Fname,Cxrtoi[PG_HELP]);
	lines = 1;
	rew_pos = ftell(Fpread);
	if (Headflag)
	{
		rewind(Fpread);
		Rlines = 0;
	}
	else
	{
		/* use do_out to guard against control sequences */
		Rlines = Hlines;
		sprintf (buf,"%s%s\n",T_head,title);
		lines += do_out(buf,1);
		sprintf (buf,"%s%s\n",F_head,from);
		lines += do_out(buf,1);
		if (index(ngrp,',') != NULL)
		{
			sprintf (buf,"%s%s\n",N_head,ngrp);
			lines += do_out(buf,1);
		}
		if (*flto != '\0')
		{
			sprintf (buf,"%s%s\n",FT_head,flto);
			lines += do_out(buf,1);
		}
		printf ("%s%d\n",L_head,artlin);	/* no controls */
		++lines;
	}

	/* will return out of outer while loop */
	Lookahead = NULL;
	while (1)
	{
		/*
		** lines counts folded lines from do_out.
		** globals Hlines and Rlines refer to records.
		** If Lookahead is null after this loop, we've
		** hit EOF.
		*/
		lines += do_out(Lookahead,L_allow-lines);
		while (1)
		{
			if (Lookahead == NULL)
			{
				if (fgets(buf,RECLEN-1,Fpread) == NULL)
					break;
				Lookahead = buf;
				if (Rot != 0 && Rlines >= Hlines)
					rot_line(buf);
				++Rlines;
			}
			if (lines >= L_allow)
				break;
			lines += do_out(buf,L_allow-lines);
		}

		if (Lookahead != NULL)
		{
			/*
			** calculation is truncated rather than rounded,
			** so we shouldn't get "100%".  Subtract 2 for
			** 1 line lookahead and empty line at beginning
			** of article.
			*/
			if (Headflag)
				percent = ((Rlines-2)*100)/(artlin+Hlines);
			else
				percent = ((Rlines-Hlines-2)*100)/artlin;
			sprintf (pstr,PAGE_MID,percent);
		}
		else
		{
			if (sn == NULL)
				strcpy (pstr,PAGE_END);
			else
			strcpy (pstr,PAGE_NEXT);
		}
		c = getpgch(pstr,mid,from,reply,title,ngrp,flto,dist);

		/*
			handle user input:
			CAUTION!!  return cases must close Fpread.
		*/
		switch (c)
		{
		case PG_NEXT:
			fclose (Fpread);
			return (0);
		case PG_FLIP:
			*pages = 1;	/* fall through */
		case PG_QUIT:
			fclose (Fpread);
			return (-1);
		case PG_REWIND:
			if (Headflag)
			{
				Rlines = 0;
				rewind (Fpread);
			}
			else
			{
				fseek (Fpread,rew_pos,0);
				Rlines = Hlines;
			}
			Lookahead = NULL;
			lines = 2 - RECBIAS;
			break;
		case PG_SEARCH:
			searcher(buf);
			lines = 2 - RECBIAS;
			lines += do_out(buf,L_allow-lines);
			break;
		case PG_WIND:
			fseek (Fpread,0L,2);
			lines = 2 - RECBIAS;
			Lookahead = NULL;
			break;
		case PG_STEP:
			if (Lookahead == NULL)
			{
				fclose (Fpread);
				return (0);
			}
			lines = L_allow - 1;
			break;
		default:
			if (Lookahead == NULL)
			{
				fclose (Fpread);
				return (0);
			}
			lines = 2 - RECBIAS;
			break;
		}
	}
}

/*
	gethead obtains subject, reply, message id, from, lines, newsgroup and
	followup-to lines of article for later use in mailing replies and
	posting followups, does not rewind, but leaves file at end of header
	lines.  Returns number of header lines.
*/
static gethead (mid, from, title, ngrp, flto, reply, dist, lin)
char *mid, *from, *title, *ngrp, *flto, *reply, *dist;
int *lin;
{
	int count;
	char buf [RECLEN], *index();
	long pos,ftell();

#ifdef ADDRMUNGE
	Newaddr = 1;
#endif

	*lin = 0;
	*dist = *mid = *from = *title = *ngrp = *flto = *reply = '\0';

	/* for conditional is abnormal - expected exit is break */
	for (count = 0; count < HDR_LINES && fgets(buf,RECLEN-1,Fpread) != NULL; ++count)
	{

		/* reset position and bail out at first non-header line */
		if (index(buf,':') == NULL)
		{
			pos = ftell(Fpread);
			pos -= strlen(buf);
			fseek (Fpread,pos,0);
			break;
		}

#ifdef MAILSMART
		if (strncmp(buf,RT_head,RTHDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			strcpy (reply,buf+RTHDLEN);
			continue;
		}
#else
		if (strncmp(buf,P_head,PHDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			strcpy (reply,buf+PHDLEN);
			continue;
		}
#endif
		if (strncmp(buf,DIS_head,DISHDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			strcpy (dist,buf+DISHDLEN);
			continue;
		}
		if (strncmp(buf,M_head,MHDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			strcpy (mid,buf+MHDLEN);
			continue;
		}
		if (strncmp(buf,F_head,FHDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			strcpy (from,buf+FHDLEN);
			continue;
		}
		if (strncmp(buf,T_head,THDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			strcpy (title,buf+THDLEN);
			continue;
		}
		if (strncmp(buf,N_head,NHDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			strcpy (ngrp,buf+NHDLEN);
			continue;
		}
		if (strncmp(buf,FT_head,FTHDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			strcpy (flto,buf+FTHDLEN);
			continue;
		}
		if (strncmp(buf,L_head,LHDLEN) == 0)
		{
			buf [strlen(buf)-1] = '\0';
			*lin = atoi(buf+LHDLEN);
			continue;
		}
	}
#ifdef MAILSMART
	if (*reply == '\0')
		strcpy(reply,from);
#endif
	return (count);
}

/*
	getpgch prints prompt and gets command from user
	handles "mail", "save" and "followup" internally
	as well as flag resets.
*/
static char getpgch(prompt,mid,from,reply,title,ngrp,flto,dist)
char *prompt, *mid, *from, *reply, *title, *ngrp, *flto, *dist;
{
	char c;
	int ic;
	term_set (ONREVERSE);
	printf("%s\015",prompt);
	term_set (OFFREVERSE);
	while ((ic=getnoctl()) != EOF)
	{
		switch (c = Cxitor[ic])
		{
		case SETROT:
			term_set (ZAP,0,PPR_MAX);
			if (Rot == 0)
			{
				Rot = 13;
				printf ("%s\n",Roton_msg);
			}
			else
			{
				Rot = 0;
				printf ("%s\n",Rotoff_msg);
			}
			if (Lookahead != NULL && Rlines > Hlines)
				rot_line(Lookahead);
			break;
		case HEADTOG:
			term_set (ZAP,0,PPR_MAX);
			if (Headflag)
			{
				Headflag = FALSE;
				printf ("%s\n",Hdoff_msg);
			}
			else
			{
				Headflag = TRUE;
				printf ("%s\n",Hdon_msg);
			}
			break;
		case PG_HELP:
			term_set (ZAP,0,PPR_MAX);
			help_pg ();
			break;
		case PG_REPLY:
			mail (reply,title,from);
			break;
		case PG_FOLLOW:
			followup (mid,title,ngrp,flto,from,dist);
			break;
		case SAVE:
			saver ();
			break;
		case PRINT:
			printr ();
			break;
		default:
			term_set (ZAP,0,PPR_MAX);
			return (c);
		}

		term_set (ONREVERSE);
		printf("%s\015",prompt);
		term_set (OFFREVERSE);
	}
	term_set (ZAP,0,PPR_MAX);
	return (c);
}

/*
	save article
	Like the savestr routine, it "loses" some storage every time
	the user specifies a new file, but this should not be significant
*/
static saver ()
{
	char *fn,cmd[RECLEN],*str_store(),*rprompt();

	tty_set (SAVEMODE);
	sprintf (cmd,SAVFORM,Savefile);
	fn = rprompt(cmd,cmd);
	if (fn != NULL)
		Savefile = str_store(fn);
	if (*Savefile != '/' && *Savefile != '$')
		sprintf (cmd,"cat %s >>%s/%s",Fname,Savedir,Savefile);
	else
		sprintf (cmd,"cat %s >>%s",Fname,Savefile);
	system (cmd);
	tty_set (RESTORE);
}

/*
	invoke editor on new temp file, mail using reply line,
	possibly first allowing user to overide the reply (not INLETTER)
*/
static mail (p, t, f)
char *p, *t, *f;
{
	char *new, fn[L_tmpnam], cmd [RECLEN+60], *rprompt ();
	FILE *fp, *fopen();

	tmpnam (fn);
	if ((fp = fopen(fn,"w")) == NULL)
		printex ("can't open %s\n",fn);

	if ((new = index(p, '(')) != NULL)
		*new = '\0';	/* a poor way of deleting comments */

#ifdef ADDRMUNGE
	if (Newaddr)
	{
		Newaddr = 0;
		ADDRMUNGE(p);
	}
#endif

	if (strncmp(t, FPFIX, FPFLEN) == 0)
		t += FPFLEN;	/* don't add multiple Re:s */
#ifdef INLETTER
 	fprintf (fp,"%s%s\n%s%s%s\n\n%s:\n", TO_head, p, T_head, FPFIX, t, f);
#else
	fprintf (fp,"%s%s%s\n\n%s:\n", T_head, FPFIX, t, f);
#endif

	edcopy (fp);
	fclose (fp);
	tty_set (SAVEMODE);

#ifndef INLETTER
	sprintf (cmd,"ADDRESS: %s\nreturn to accept, or input new address: ",p);
	if ((new = rprompt(cmd,cmd)) != NULL)
		strcpy (p,new);
#endif

	sprintf (cmd,"%s %s", Editor, fn);
	chdir (Orgdir);
	system (cmd);
	cd_group ();
	new = rprompt ("still want to mail it ? ",cmd);
	if (new != NULL && (*new == 'y' || *new == 'Y'))
	{
#ifndef INLETTER
		sprintf (cmd,"%s '%s' <%s", Mailer, p, fn);
#else
		sprintf (cmd,"%s <%s", Mailer, fn);
#endif
		system (cmd);
		printf ("given to mailer\n");
	}
	else
		printf ("not mailed\n");
	unlink (fn);
	tty_set (RESTORE);
	term_set (RESTART);
}

/*
	post a followup article, invoking editor for user after creating
	new temp file.  remove after posting.  Hack in ".followup" if posting
	newsgroup ends in ".general" - similar hack for preventing mod &
	announce groups - should really be more thorough and parse the
	whole string.  User can change, anyway.
*/
static followup (m,t,n,ft,oa,dist)
char *m, *t, *n, *ft, *oa, *dist;
{
	char fn[L_tmpnam], *new, cmd [RECLEN], *rprompt();
	FILE *f, *fopen();
	char *rindex();

	if (*ft != '\0')
		strcpy (cmd,ft);
	else
		strcpy (cmd,n);
	new = rindex(cmd,'.');
	if (new != NULL && strcmp(new,".general") == 0)
		strcpy (new,".followup");
	if ( strncmp(cmd, "mod.", 4) == 0 || strcmp(new, ".announce") == 0)
	{
		term_set (ONREVERSE);
		printf("Cannot post a follow-up to \"%s\", reply with mail to moderator\007\n",
			cmd);
		term_set (OFFREVERSE);
		return;
	}

	tmpnam (fn);
	if ((f = fopen(fn,"w")) == NULL)
		printex ("can't open %s\n",fn);

	if (strncmp(t, FPFIX, FPFLEN) == 0)
		t += FPFLEN;	/* don't add multiple Re:s */
	fprintf (f,"%s%s%s\n%s%s\n%s%s\n",T_head,FPFIX,t,N_head,cmd,R_head,m);
	if (*dist != '\0')
		fprintf(f,"%s%s\n",DIS_head,dist);
	fprintf (f,"\nin article %s, %s says:\n",m,oa);
	edcopy (f);
	fclose (f);
	tty_set (SAVEMODE);
	sprintf (cmd,"%s %s", Editor, fn);
	chdir (Orgdir);
	system (cmd);
	cd_group ();
	new = rprompt("still want to post it ? ",cmd);
	if (new != NULL && (*new == 'y' || *new == 'Y'))
	{
		sprintf (cmd,"%s <%s", Poster, fn);
		system (cmd);
		printf ("given to posting program\n");
		save_article (fn);
	}
	else
		printf ("not posted\n");
	unlink (fn);
	tty_set (RESTORE);
	term_set (RESTART);
}

/*
	get user buffer, return whitespace delimited token
	without using strtok().  buffer is allowed to overwrite
	prompt string.
*/
static char *
rprompt(s,buf)
char *s,*buf;
{
	printf("%s",s);
	fgets (buf,RECLEN-1,stdin);
	while (*buf == ' ' || *buf == '\t')
		++buf;
	if (*buf == '\n' || *buf == '\0')
		return (NULL);
	for (s = buf; *s != ' ' && *s != '\t' && *s != '\n' && *s != '\0'; ++s)
		;
	*s = '\0';
	return (buf);
}

/*
	edcopy copies article to file which user is editting for
	a reply or followup, so it may be referenced.  It places
	ED_MARK in the left hand margin.
*/
edcopy(fp)
FILE *fp;
{
	long current;
	char buf[RECLEN];
	int i;

	/* save position, rewind and skip over header lines */
	current = ftell(Fpread);
	rewind (Fpread);
	for (i=0; i < HDR_LINES; ++i)
	{
		if (fgets(buf,RECLEN-1,Fpread) == NULL)
			break;
		if (strncmp(buf,L_head,LHDLEN) == 0)
			break;
	}

	/* if line already begins with ED_MARK, forget about the space */
	while (fgets(buf,RECLEN-1,Fpread) != NULL)
	{
		if (buf[0] == ED_MARK)
			fprintf(fp,"%c%s",ED_MARK,buf);
		else
			fprintf(fp,"%c %s",ED_MARK,buf);
	}

	/* restore position */
	fseek(Fpread,current,0);
}

/*
	help menus
*/
static help_pg()
{
	h_print (Cxrtoi[PG_NEXT],HPG_NEXT);
	h_print (Cxrtoi[PG_QUIT],HPG_QUIT);
	h_print (Cxrtoi[PG_FLIP],HPG_FLIP);
	h_print (Cxrtoi[PG_REWIND],HPG_REWIND);
	h_print (Cxrtoi[PG_WIND],HPG_WIND);
	h_print (Cxrtoi[PG_SEARCH],HPG_SEARCH);
	h_print (Cxrtoi[PG_STEP],HPG_STEP);
	h_print (Cxrtoi[PG_REPLY],HPG_REPLY);
	h_print (Cxrtoi[PG_FOLLOW],HPG_FOLLOW);
	h_print (Cxrtoi[SAVE],HPG_SAVE);
	h_print (Cxrtoi[PRINT],HPG_PRINT);
	h_print (Cxrtoi[SETROT],HPG_ROT);
	h_print (Cxrtoi[HEADTOG],HPG_HEAD);
	h_print (Cxrtoi[PG_HELP],HPG_HELP);
	printf ("%s\n",HPG_DEF);
}

rot_line (s)
unsigned char *s;
{
	for ( ; *s != '\0'; ++s)
	{
		if (*s >= 'A' && *s <= 'Z')
		{
			*s += Rot;
			if (*s > 'Z')
				*s -= 26;
			continue;
		}
		if (*s >= 'a' && *s <= 'z')
		{
			*s += Rot;
			if (*s > 'z')
				*s -= 26;
		}
	}
}

/*
** output record.  folds record to terminal width on word boundaries,
** returning number of lines output.  If limit is reached, remainder
** of buffer waiting to be output is returned.  Processes several
** special characters:
**	form-feed - return "lim" lines so we stop on that line
**	tabs - counts "expanded" width
**	backspace - assumes they work, -1 width unless in first col.
**	bell - pass through with zero width
**	newline - end of record.
**	del - turns into '_'
**	other control - 'A' - 1 added ('01' = ctl-A).  Makes escape = "[".
**		(prevents "letter bombs" containing inappropriate control
**			sequences for the terminal).
**
** Sets Lookahead pointer to remainder of line or NULL.
*/
static do_out(s,lim)
char *s;
int lim;
{
	int len,i;
	char cs,*word,*start;

	Lookahead = NULL;
	if (s == NULL)
		return(0);
	len = 0;
	start = word = s;

	/*
	** NOTE: "normal" return is buried inside switch, at newline
	** ending record
	*/
	for (i=0; i < lim; ++i)
	{
		for ( ; len < C_allow; ++s)
		{
			switch (*s)
			{
			case '\n':
				*s = '\0';	/* fall through */
			case '\0':
				printf("%s\n",start);
				return(i+1);
			case '\t':
				len = ((len/PERTAB)+1)*PERTAB;
				word = s;
				break;
			case '\b':
				if (len > 0)
					--len;
				break;
			case '\014':
				*s = ' ';
				i = lim-1;	/* fall through */
			case ' ':
				word = s+1;
				++len;
				break;
			case '\177':
				*s = '_';
				++len;
				break;
			default:
				if (*s < ' ')
					*s += 'A' - 1;
				++len;		/* fall through */
			case '\07':
				break;
			}
		}
		cs = *s;
		*s = '\0';
		if ((len = strlen(word)) < BACKTRACK)
		{
			*s = cs;
			s = word;
			cs = *s;
			*s = '\0';
		}
		else
			len = 0;
		printf("%s\n",start);
		start = s;
		*s = cs;
	}
	Lookahead = start;
	return(lim);
}

save_article(tempfname)
char *tempfname;
{
	FILE *in, *out;
	int c;
	time_t timenow, time();
	char *today, *ctime();


	if ((in = fopen(tempfname, "r")) == NULL)
		return;
	if ((out = fopen(Ccfile, "a")) == NULL)
	{
	    fclose(in);
	    return;
	}
	timenow = time((time_t)0);
	today = ctime(&timenow);
	fprintf(out,"From vn %s",today);
	while ((c=getc(in)) != EOF)
		putc(c, out);
	putc('\n', out);
	fclose(in);
	fclose(out);
	printf ("a copy has been saved in %s\n", Ccfile);
}

/*
	send article to printer
*/
static printr ()
{
	char cmd[RECLEN];

	tty_set (SAVEMODE);
	printf("Sent to printer\n");
	sprintf (cmd,"%s %s 2>/dev/null",Printer,Fname);
	system (cmd);
	tty_set (RESTORE);
}

/*
	search article for specified search pattern, returning the line on which
		it is found in buf, a null buffer otherwise. The input file will
		be positioned either after the line on which the pattern is
		found, or unaaltered if match fails.
*/
searcher (buf)
char	*buf;
{
	static char	searchstr[RECLEN] = "";
	char	lasave[RECLEN];
	char	*s, *reg, *rprompt(), *regcmp(), *regex();
	long	current;
	int	orlines;

	/* save position, then request search pattern */
	current = ftell(Fpread);
	orlines = Rlines;

 	tty_set (SAVEMODE);
 	sprintf (lasave,SEARCHFORM,searchstr);
	s = rprompt(lasave,lasave);
	tty_set (RESTORE);
	if (s != NULL)
		strcpy(searchstr, lasave);
 
	/* Now compile the search string */
	if(( reg = regcmp(searchstr, (char *)0)) == NULL) {
		printf("Invalid search string \"%s\"\n", searchstr);
		*buf = '\0';
		return;
	}

	/* try lookahead buffer first */
	if (Lookahead != NULL && regex(reg,Lookahead) != NULL)
	{
		strcpy(buf,Lookahead);
		regfree(reg);
		return;
	}

	/* Lookahead can point into buf */
	if (Lookahead != NULL)
		strcpy(lasave,Lookahead);

	/* now start reading lines, rotating if necessary and do search */
	while (fgets(buf,RECLEN-1,Fpread) != NULL)
	{
		if (Rot != 0 && Rlines >= Hlines)
			rot_line(buf);
		++Rlines;
		if( regex(reg, buf) != NULL ){	/* Got it */
			term_set (ONREVERSE);
			printf("\n\tSkipping ....\n\n");
			term_set (OFFREVERSE);
			regfree(reg);
			return;
		}
	}

	/* no dice, so restore position */
	regfree(reg);
	term_set (ONREVERSE);
	printf("Cannot find string \"%s\" in remainder of article\007\n",
		searchstr);
	term_set (OFFREVERSE);
	fseek(Fpread,current,0);
	Rlines = orlines;
	if (Lookahead != NULL)
		strcpy(buf,lasave);
	else
		*buf = '\0';
	return(0.0);
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'newsrc.c'" '(11576 characters)'
if test -f 'newsrc.c'
then
	echo shar: will not over-write existing file "'newsrc.c'"
else
cat << \SHAR_EOF > 'newsrc.c'
/*
** vn news reader.
**
** newsrc.c - routines to deal with the newsrc file
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>
#include <ctype.h>
#include "config.h"
#include "tty.h"
#include "vn.h"

extern NODE **Newsorder;
extern char *Onews, *Newsrc;
extern int Ncount, Lrec, C_allow;
extern int Ntopt, Nntopt, Nwopt, Nnwopt;
extern char *Topt[], *Negtopt[], *Wopt[], *Negwopt[];
extern int Nounsub, Listfirst;

/*
	global flags signifying options set
*/
#define GF_ALL 1	/* -x option - scan everything */
#define GF_SPEC 2	/* -n option(s) - user specified groups */
#define GF_OVER 4	/* command line specification - overide marks */

static char *Options[OPTLINES];
static int New_idx, Max_name, Optlines;
static unsigned Gflags = 0;

/*
	routines for dealing with the .newsrc file and options
*/

/*
	command name argument is already omitted from argv argc in this
	routine.  Only the option arguments are present.  We process
	options before we scan the rest of .newsrc, which redoes Newsorder,
	ie. we don't clobber Ncount until options are processed.
*/
scan_newsrc (argc,argv)
int argc;
char **argv;
{
	FILE *fp, *fopen();
	static char marks[] =
	{ 
		NEWS_ON, NEWS_OFF, '\0' 
	};
	char *str_store ();
	int line, len, num;
	char buf [RECLEN], trail, optpflag, submark, *fret, *ptr, *strpbrk(), *strtok();


	/* initialize hash table, open temp file, fill table with active articles */
	hashinit ();
	fill_active ();
	temp_open();

	if (argc > 0)
	{
		Gflags |= GF_OVER;
		arg_opt(argc,argv);
		optpflag = 'y';
	}
	else
		optpflag = 'n';

	if ((fp = fopen (Newsrc,"r")) == NULL)
		printex ("can't open %s for reading",Newsrc);

	Optlines = 0;

	for (line = 1; (fret = fgets(buf,RECLEN-1,fp)) != NULL && emptyline(buf) == 1; ++line)
		;
	if (fret != NULL && strncmp (buf,"options",7) == 0)
	{
		Options[0] = str_store(buf);
		Optlines = 1;
		trail = buf [strlen(buf)-2];
		for ( ; (fret = fgets(buf,RECLEN-1,fp)) != NULL; ++line)
		{
			if (trail != '\\' && buf[0] != ' ' && buf[0] != '\t')
				break;
			if (Optlines >= OPTLINES)
				printex ("%s - too many option lines (%d allowed)",Newsrc,OPTLINES);
			Options[Optlines] = str_store(buf);
			++Optlines;
			if ((len = strlen(buf)) >= 2 && buf[len-2] != '\\')
				trail = buf[len-2];
			else
				trail = '\0';
		}
	}

	/* do the options from the newsrc file if there weren't command line args */
	if (Optlines > 0 && optpflag == 'n')
		newsrc_opt ();

	Ncount = 0;

	for ( ; fret != NULL; ++line, fret = fgets(buf,RECLEN-1,fp))
	{
		if (emptyline(buf) == 1)
			continue;
		if ((ptr = strpbrk(buf,marks)) == NULL)
		{
			fprintf (stderr,"\nwarning: line %d of %s (%s) - bad syntax\n",
			line,Newsrc,buf);
			continue;
		}
		submark = *ptr;
		*ptr = '\0';
		++ptr;
		num = 0;
		for (ptr = strtok(ptr," ,-\n"); ptr != NULL; ptr = strtok(NULL," ,-\n"))
		{
			len = atoi (ptr);
			for ( ; *ptr >= '0' && *ptr <= '9'; ++ptr)
				;
			if (*ptr != '\0' || len < num)
			{
				num = -1;
				fprintf (stderr,"\nwarning: line %d of %s (%s) - bad syntax\n",
				line,Newsrc,buf);
				break;
			}
			num = len;
		}
		if (num < 0)
			continue;
		chkgroup (buf,submark,num);
	}
	fclose (fp);

	/* now take care of groups not specified in .newsrc */
	art_active();

	/* free up the option string storage */
	for (num=0; num < Ntopt; ++num)
		regfree (Topt[num]);
	for (num=0; num < Nwopt; ++num)
		regfree (Wopt[num]);
	for (num=0; num < Nntopt; ++num)
		regfree (Negtopt[num]);
	for (num=0; num < Nnwopt; ++num)
		regfree (Negwopt[num]);
	Ntopt = Nwopt = Nntopt = Nnwopt = 0;
}

static emptyline(s)
char *s;
{
	while (isspace(*s))
		++s;
	if (*s == '\0')
		return (1);
	return (0);
}

/*
	fill hash table from active news group list
	temporarily makes "Newsorder" active list order.
	This is needed to be able to process options
	before scanning user order.
*/
static fill_active ()
{
	FILE *f,*fopen ();
	char *nread, act_rec[RECLEN], *strtok();
	int num,lownum;

	Max_name = 0;
	if ((f = fopen (ACTFILE,"r")) == NULL)
		printex ("couldn't open %s\n",ACTFILE);
	while (fgets(act_rec, RECLEN-1, f) != NULL)
	{
		if (strtok (act_rec," \n") == NULL)
			continue;
		nread = strtok (NULL, " \n");
		if (nread != NULL)
			num = atoi(nread);
		else
			num = 0;
		nread = strtok (NULL, " \n");
		if (nread != NULL)
			lownum = atoi(nread);
		else
			lownum = 0;
		if (lownum > 0)
			--lownum;
		if (strlen(act_rec) > Max_name)
			Max_name = strlen(act_rec);
		hashenter (act_rec, num, lownum);
	}

	/* construct initial Newsorder */
	entry_order();

	fclose (f);
}

/*
	check active newsgroups not mentioned in NEWSRC file
	(FLG_SCAN not set)
*/
static art_active ()
{
	char act_rec[RECLEN], *strtok();
	NODE *ptr,*hashfind();
	FILE *f,*fopen();
	if ((f = fopen (ACTFILE,"r")) == NULL)
		printex ("couldn't open %s\n",ACTFILE);
	New_idx = Ncount;
	while (fgets(act_rec, RECLEN-1, f) != NULL)
	{
		if (strtok (act_rec," \n") == NULL)
			continue;
		if ((ptr = hashfind (act_rec)) == NULL)
			printex("%s - unexpected hash table failure",act_rec);
		if ((ptr->state & FLG_SCAN) == 0)
			chkgroup (ptr->nd_name, NEWS_ON, 0);
	}
}

/*
	check group for new articles:
	s - group
	c - subscription indicator from NEWSRC
	n - number read
*/
static chkgroup (s,c,n)
char *s,c;
int n;
{
	NODE *ptr, *hashfind();
	int lold,lowart;
	lold = Lrec;
	if ((ptr = hashfind(s)) != NULL && (ptr->state & FLG_SCAN) == 0)
	{
		Newsorder [Ncount] = ptr;
		++Ncount;
		ptr->pages = 0;
		ptr->state |= FLG_SCAN;
		if (c == NEWS_ON)
			ptr->state |= FLG_SUB;
		/* if "read" more than exist reset to zero */
		if (n > ptr->art)
			n = 0;
		lowart = ptr->rdnum;
		if (n < ptr->rdnum)
			n = ptr->rdnum;
		ptr->orgrd = ptr->pgrd = ptr->rdnum = n;
		ptr->pgshwn = 0L;

		/*
		** scan decision is rather complex, since GF_ALL setting
		** overides "n" value, GF_SPEC indicates FLG_SPEC flag used.
		** if GF_OVER set, FLG_SPEC overides subscription mark, else
		** FLG_SPEC AND subscribed is neccesary.
		*/
		if ((Gflags & GF_SPEC) != 0)
		{
			if ((ptr->state & FLG_SPEC) == 0)
				c = NEWS_OFF;
			else
			{
				if ((Gflags & GF_OVER) != 0)
					c = NEWS_ON;
			}
		}
		if ((Gflags & GF_ALL) != 0)
			n = lowart;
		if (c == NEWS_ON && ptr->art > n)
		{
			outgroup (s,n,ptr->art);
			if (lold != Lrec)
			{
				ptr->pnum = lold+1;
				ptr->pages = Lrec - lold;
				ptr->state |= FLG_PAGE;
			}
		}
	}
}

/*
	wr_newsrc writes the .newsrc file
*/
wr_newsrc ()
{
	FILE *fp,*fopen();
	NODE *p;
	char c;
	int i,rc;

	if (link(Newsrc,Onews) < 0)
		printex ("can't backup %s to %s before writing",Newsrc,Onews);

	if (unlink(Newsrc) < 0 || (fp = fopen(Newsrc,"w")) == NULL)
		printex ("can't open %s for writing (backed up in %s)",Newsrc,Onews);
	else
	{
		clearerr(fp);
		for (i=0; (rc = ferror(fp)) == 0 && i < Optlines; ++i)
			fprintf (fp,"%s",Options[i]);
		for (i=0; rc == 0 && i < Ncount; ++i)
		{
			p = Newsorder[i];
			if ((p->state & FLG_SUB) == 0)
				c = NEWS_OFF;
			else
				c = NEWS_ON;
#ifdef OLDRC
			fprintf (fp,"%s%c %d\n",p->nd_name,c,p->rdnum);
#else
			if (p->rdnum > 0)
				fprintf (fp,"%s%c 1-%d\n",p->nd_name,c,p->rdnum);
			else
				fprintf (fp,"%s%c 0\n",p->nd_name,c);
#endif
			rc = ferror(fp);
		}
		fclose (fp);
		if (rc != 0)
			printex ("write of %s failed, old copy stored in %s",Newsrc,Onews);
		else
			unlink (Onews);
	}
}

new_groups ()
{
	int i,wrem,w;
	char fs[24],c_end;
	if (New_idx >= Ncount || C_allow < (w = Max_name+1))
		return (0);
	term_set (ERASE);
	printf (NEWGFORM,Newsrc);
	sprintf (fs,"%%-%ds%%c",Max_name);
	wrem = C_allow;
	for (i=New_idx; i < Ncount; ++i)
	{
		if ((wrem -= w) < w)
		{
			wrem = C_allow;
			c_end = '\n';
		}
		else
			c_end = ' ';
		printf (fs,(Newsorder[i])->nd_name,c_end);
	}
	if ((++wrem) < C_allow)
		putchar ('\n');
	return (i-New_idx);
}

/*
	arg_opt must be called prior to option scanning, since
	it uses the options array.  This is a bit of a kludge,
	but it saves a bunch of work.  NOTE - no command name argument
*/
static arg_opt (argc,argv)
int argc;
char **argv;
{
	if (argc > OPTLINES)
		printex ("too many command line options (%d allowed)\n",OPTLINES);
	for (Optlines=0; Optlines < argc; ++Optlines)
	{
		Options[Optlines] = *argv;
		++argv;
	}
	newsrc_opt();
}

/*
	option setting routine:
	sets global flags: GF_ALL for -x option GF_SPEC for -n.
	sets up filter array for article scanning
*/
static newsrc_opt()
{
	int i;
	char curopt,tmp[RECLEN],*tok,*strtok(),*index();

	Nounsub = Listfirst = 0;
	Ntopt = Nwopt = Nnwopt = Nntopt = 0;
	curopt = '\0';
	for (i=0; i < Optlines; ++i)
	{
		strcpy(tmp,Options[i]);
		for (tok = strtok(tmp,",\\ \t\n"); tok != NULL; tok = strtok(NULL,",\\ \t\n"))
		{
			if (*tok != '-')
				do_opt (curopt,tok);
			else
			{
				for (++tok; index("nwt",*tok) == NULL; ++tok)
				{
					/* options with no strings */
					switch(*tok)
					{
					case 'S':
						Gflags &= ~GF_OVER;
						break;
					case '%':
						Listfirst = 1;
						break;
					case 'U':
						Nounsub = 1;
						break;
#ifdef OLDRC
					case 'i':
					/* Treat "-i" as synonym for "-x" */
#endif
					case 'x':
						Gflags |= GF_ALL;
					default:
						break;
					}
				}
				curopt = *tok;
				if (*(++tok) != '\0')
					do_opt (curopt,tok);
			}
		}
	}
}

/* do_opt is for options with strings attached */
static do_opt (opt,str)
char opt, *str;
{
	switch (opt)
	{
	case 'n':
		Gflags |= GF_SPEC;
		specmark(str);
		break;
	case 'w':
		specfilter (FIL_AUTHOR,str);
		break;
	case 't':
		specfilter (FIL_TITLE,str);
		break;
	default:
#ifdef OLDRC
		Gflags |= GF_SPEC;	/* Assume anything else is newsgroup */
		specmark(str);
#endif
		break;
	}
}

static specfilter (comp,str)
char comp,*str;
{
	char *regcmp();
	int *count;
	char **rex;

	/*
	** we may set rex one past end of array.  we will error before
	** referencing it if that's the case, however.
	*/
	if (*str == '!')
	{
		if (comp == FIL_TITLE)
		{
			count = &Nntopt;
			rex = Negtopt + *count;
		}
		else
		{
			count = &Nnwopt;
			rex = Negwopt + *count;
		}
		++str;
	}
	else
	{
		if (comp == FIL_TITLE)
		{
			count = &Ntopt;
			rex = Topt + *count;
		}
		else
		{
			count = &Nwopt;
			rex = Wopt + *count;
		}
	}
	if (*count >= NUMFILTER)
		printex ("too many %c options, %d allowed",comp,NUMFILTER);
	if ((*rex = regcmp(str,(char *) 0)) == NULL)
		printex ("%c option regular expression syntax: %s",comp,str);
	++(*count);
}

/*
	handle the newsgroup specification string.
	("all" convention - braack!!!)
*/
static specmark (s)
char *s;
{
	unsigned ormask,andmask;
	int i,len;
	char *ptr,*re,pattern[RECLEN],*regex(),*regcmp();

	if (*s == '!')
	{
		++s;
		ormask = 0;
		andmask = ~FLG_SPEC;
		if (*s == '\0')
			return;
	}
	else
	{
		ormask = FLG_SPEC;
		andmask = 0xffff;
	}

	/* convert "all" not bounded by alphanumerics to ".*". ".all" becomes ".*" */
	for (ptr = s; (len = findall(ptr)) >= 0; ptr += len+1)
	{
		if (len > 0 && isalnum (s[len-1]))
			continue;
		if (isalnum (s[len+3]))
			continue;
		if (len > 0 && s[len-1] == '.')
		{
			--len;
			strcpy (s+len,s+len+1);
		}
		s[len] = '.';
		s[len+1] = '*';
		strcpy (s+len+2,s+len+3);
	}

	/* now use regular expressions */
	sprintf (pattern,"^%s$",s);
	if ((re = regcmp(pattern,(char *) 0)) == NULL)
		printex ("n option regular expression syntax: %s",s);
	for (i=0; i < Ncount; ++i)
	{
		if (regex(re,(Newsorder[i])->nd_name) != NULL)
		{
			(Newsorder[i])->state |= ormask;
			(Newsorder[i])->state &= andmask;
		}
	}
	regfree (re);
}

static findall (s)
char *s;
{
	int len;
	for (len=0; *s != '\0'; ++s,++len)
	{
		if (*s == 'a' && strncmp(s,"all",3) == 0)
			return (len);
	}
	return (-1);
}
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0

bobm@rtech.UUCP (Bob Mcqueer) (01/01/87)

Bob McQueer
{amdahl, sun, mtxinu, hoptoad, cpsc6a}!rtech!bobm

cut here
-----------------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	digest.c
#	envir_set.c
#	groupdir.c
#	hash.c
#	pagefile.c
#	reg.c
#	sig_set.c
#	storage.c
#	strings.c
#	strtok.c
#	term_set.c
#	tmpnam.c
#	tty_set.c
#	userlist.c
#	vnglob.c
# This archive created: Thu Jan  1 11:15:28 1987
export PATH; PATH=/bin:$PATH
echo shar: extracting "'digest.c'" '(5027 characters)'
if test -f 'digest.c'
then
	echo shar: will not over-write existing file "'digest.c'"
else
cat << \SHAR_EOF > 'digest.c'
/*
** vn news reader.
**
** digest.c - digest unpacking routines
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>
#include "config.h"
#include "vn.h"
#include "head.h"

extern int Digest;
extern int L_allow;
extern int C_allow;
extern PAGE Page;

extern char *F_head, *T_head, *L_head, *D_head;

digest_page (idx,skip)
int idx;
{
	char *ptr,name[24],*title,*index();
	FILE *fp;
	int i,len;
	char subj[RECLEN],date[RECLEN],from[RECLEN],junk[RECLEN],*str_store();
	long pos;

	Digest = Page.b[idx].art_id;
	sprintf (name,"%d", Digest);

	if ((fp = fopen(name,"r")) == NULL)
		return (-1);

	subj[0] = date[0] = from[0] = junk[0] = '\0';

	skip_header (fp);

	/* skip over some articles if requested to */
	for (i=skip; i > 0; --i)
	{
		if (dig_advance(fp,from,subj,date,junk,&pos) < 0)
			return (-1);
	}

	/* every new call to a digest Page "loses" a small amount of storage */
	title = str_store(Page.b[idx].art_t);
	if ((ptr = index(title,'~')) != 0)
		*ptr = '\0';
	title [C_allow - 20] = '\0';

	for (i=0; i < L_allow &&
			(len = dig_advance(fp,from,subj,date,junk,&pos)) >= 0; ++i)
	{
		Page.b[i].art_id = i+1+skip;
		Page.b[i].art_mark = ' ';
		subj [C_allow] = '\0';
		from [C_allow] = '\0';
		sprintf (name,"%d",len);
		form_title (date,subj,name,from,100);
		strcpy (Page.b[i].art_t,date);
	}

	fclose (fp);

	if (i == 0)
		return (-1);

	Page.h.name = title;
	Page.h.artnum = i;
	return (i);
}

/*
	returns name of file containing "article", NULL for failure
*/
char * digest_extract (s,art)
char *s;
int art;
{
	char name[24];
	FILE *fout,*fin;
	char subj[RECLEN],date[RECLEN],from[RECLEN],bufr[RECLEN];
	char extra[RECLEN];
	char *index();
	long pos;
	int lines;

	sprintf (name,"%d", Digest);
	if ((fin = fopen(name,"r")) == NULL)
		return (NULL);

	for (skip_header (fin); art > 0; --art)
		if ((lines = dig_advance(fin,from,subj,date,extra,&pos)) < 0)
		{
			fclose (fin);
			return (NULL);
		}

	tmpnam(s);

	if ((fout = fopen(s,"w")) == NULL)
	{
		fclose (fin);
		unlink (s);
		return (NULL);
	}

	fseek(fin,0L,0);

	while (fgets(bufr,RECLEN-1,fin) != NULL && index(bufr,':') != NULL)
	{
		if (strncmp(bufr,F_head,FHDLEN) == 0)
		{
			fprintf (fout,"%s%s\n",F_head,from);
			continue;
		}
		if (strncmp(bufr,T_head,THDLEN) == 0)
		{
			fprintf (fout,"%s%s\n",T_head,subj);
			continue;
		}
		if (strncmp(bufr,D_head,DHDLEN) == 0)
		{
			fprintf (fout,"%s%s\n",D_head,date);
			continue;
		}
		/* defer line count header - it comes last */
		if (strncmp(bufr,L_head,LHDLEN) == 0)
			continue;
		fprintf (fout,"%s",bufr);
	}

	/* toss in extra header lines, line count header, extra newline */
	fprintf (fout,"%s%s%d\n\n",extra,L_head,lines);

	fseek (fin,pos,0);

	while (fgets(bufr,RECLEN-1,fin) != NULL && strncmp(bufr,"--------",8) != 0)
		fprintf(fout,"%s",bufr);

	fclose (fin);
	fclose (fout);
	return (s);
}

dig_list (s)
char *s;
{
	char *ptr,*out,*new,ns[L_tmpnam],tmp[RECLEN],*strtok();
	int i;

	prinfo ("Extracting articles .....");
	strcpy (tmp,s);
	out = s;

	for (ptr = strtok(tmp," "); ptr != NULL; ptr = strtok(NULL," "))
	{
		i = atoi(ptr);
		if ((new = digest_extract(ns,i)) != NULL)
		{
			sprintf (out,"%s ",new);
			out += strlen(new) + 1;
		}
	}

	*out = '\0';

	if (*s == '\0')
		strcpy (s,"NULLDIGEST");
}

dig_ulist (s)
char *s;
{
	char *strtok();
	for (s = strtok(s," "); s != NULL; s = strtok(NULL," "))
		unlink (s);
}

/*
	returns # lines in article, -1 for failure
	scans past article, returns position of start.
	also returns "extra" header lines encountered, WITH newlines.
*/
static dig_advance (fp,from,subj,date,extra,pos)
FILE *fp;
char *from,*subj,*date,*extra;
long *pos;
{
	char buf[RECLEN];
	char *ptr, *index();
	int len,state,lcount;

	lcount = state = 0;
	*extra = '\0';

	while (fgets(buf,RECLEN-1,fp) != NULL)
	{
		buf[(len = strlen(buf) - 1)] = '\0';
		for (--len ; len >= 0 && buf[len] == ' ' || buf[len] == '\t'; --len)
			buf[len] = '\0';
		++len;

		switch(state)
		{
		case 0:
			/* skip blank lines before header */
			if (len == 0)
				break;
			state = 1;	/* fall through */
		case 1:
			if (strncmp(buf,F_head,FHDLEN) == 0)
			{
				strcpy (from,buf+FHDLEN);
				break;
			}
			if (strncmp(buf,T_head,THDLEN) == 0)
			{
				strcpy (subj,buf+THDLEN);
				break;
			}
			if (strncmp(buf,D_head,DHDLEN) == 0)
			{
				strcpy (date,buf+DHDLEN);
				break;
			}
			/* put wierd header lines in extra */
			if ((ptr = index(buf,':')) != NULL)
			{
				*ptr = '\0';
				if (index(buf, ' ') == NULL)
				{
					*ptr = ':';
					sprintf(extra,"%s\n",buf);
					extra += strlen(extra);
					break;
				}
				*ptr = ':';
			}
			state = 2;

			/* remember the newline we lopped off */
			*pos = ftell(fp)-strlen(buf)-1;	/* fall through */
		case 2:
			++lcount;
			if (strncmp("--------",buf,8) == 0)
			{
				--lcount;
				return (lcount);
			}
			break;
		}
	}

	return (-1);
}

static skip_header (fp)
FILE *fp;
{
	char buf[RECLEN];

	while (fgets(buf,RECLEN-1,fp) != NULL)
		if (strncmp("--------",buf,8) == 0)
			break;
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'envir_set.c'" '(2931 characters)'
if test -f 'envir_set.c'
then
	echo shar: will not over-write existing file "'envir_set.c'"
else
cat << \SHAR_EOF > 'envir_set.c'
/*
** vn news reader.
**
** envir_set.c - routine to obtain pertinent environment variable settings
**		and set up file / directory names
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>
#include <pwd.h>
#include <sys/param.h>
#include "config.h"

extern char *Editor,*Ps1,*Mailer,*Printer,*Poster;
extern char *Onews, *Newsrc, *Orgdir, *Savedir, *Ccfile;	/* path names */
extern char Cxitop[], Cxitor[], Cxrtoi[], Cxptoi[];

#ifdef SYSV
extern char *getcwd();
#define getwd(a) getcwd(a,sizeof(a))
#define	MAXPATHLEN 240
#else
extern char *getwd();
#endif

/*
	environment variable, original directory string setup.
*/

envir_set ()
{
 	char dbuf [MAXPATHLEN], *rcname, *ccname, *keyxln;
	char *getenv(), *getcwd(), *str_store();
	struct passwd *ptr, *getpwuid();

	if ((Ps1 = getenv("PS1")) == NULL)
		Ps1 = DEF_PS1;
	if ((Editor = getenv("EDITOR")) == NULL)
		Editor=DEF_ED;
	if ((Mailer = getenv("MAILER")) == NULL)
		Mailer=DEF_MAIL;
	if ((Poster = getenv("POSTER")) == NULL)
		Poster=DEF_POST;
	if ((Printer = getenv("PRINTER")) == NULL)
		Printer=DEF_PRINT;
	if ((rcname = getenv("NEWSRC")) == NULL)
		rcname=DEF_NEWSRC;
	if ((ccname = getenv("CCFILE")) == NULL)
		ccname=DEF_CCFILE;
	if ((keyxln = getenv("VNKEY")) == NULL)
		keyxln=DEF_KEYXLN;
	Savedir = getenv("VNSAVE");

	/*
		set original directory strings.  create empty Newsrc if it doesn't exist
	*/

	ptr = getpwuid (getuid());
	if ((Orgdir = getwd(dbuf)) == NULL)
		printex ("cannot stat pwd");
	Orgdir = str_store (Orgdir);
	if (Savedir == NULL)
		Savedir = Orgdir;
	if (*rcname != '/')
	{
		sprintf (dbuf, "%s/%s",ptr->pw_dir,rcname);
		Newsrc = str_store (dbuf);
	}
	else
		Newsrc = str_store (rcname);
	if (*ccname != '/')
	{
		sprintf (dbuf, "%s/%s",ptr->pw_dir,ccname);
		Ccfile = str_store (dbuf);
	}
	else
		Ccfile = str_store (ccname);
	sprintf (dbuf, "%s/%s%s",ptr->pw_dir,".vn","XXXXXX");
	Onews = str_store (mktemp(dbuf));
	if (access (Newsrc,0) != 0)
		creat (Newsrc,0666);

	if (*keyxln != '/')
	{
		sprintf(dbuf, "%s/%s",ptr->pw_dir,keyxln);
		set_kxln(dbuf);
	}
	else
		set_kxln(keyxln);
}

static
set_kxln(fname)
char *fname;
{
	FILE *fp;
	int i;
	char bufr[80];
	char in,out,*ptr;
	char *index(), xln_str();

	for (i=0; i < 128; ++i)
		Cxitop[i] = Cxitor[i] = Cxptoi[i] = Cxrtoi[i] = i;

	if ((fp = fopen(fname,"r")) != NULL)
	{
		while(fgets(bufr,79,fp) != NULL)
		{
			if (strncmp(bufr+1,"==",2) == 0)
				ptr = bufr+2;
			else
				ptr = index(bufr+1,'=');
			if (ptr == NULL)
				continue;
			*ptr = '\0';
			++ptr;
			in = xln_str(bufr+1);
			out = xln_str(ptr);
			switch(bufr[0])
			{
			case 'r':
			case 'R':
				Cxrtoi[out] = in;
				Cxitor[in] = out;
				break;
			case 'p':
			case 'P':
				Cxptoi[out] = in;
				Cxitop[in] = out;
			default:
				break;
			}
		}
		fclose(fp);
	}
}

static char
xln_str(s)
char *s;
{
	if (*s < '0' || *s > '9')
		return(*s & 0x7f);
	return((char)(atoi(s) & 0x7f));
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'groupdir.c'" '(686 characters)'
if test -f 'groupdir.c'
then
	echo shar: will not over-write existing file "'groupdir.c'"
else
cat << \SHAR_EOF > 'groupdir.c'
/*
** vn news reader.
**
** groupdir.c - translation between newsgroup name and directory
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>
#include "config.h"
#include "vn.h"

extern PAGE Page;

/*
	g_dir converts newsgroup name to directory string
*/
g_dir(s,t)
char *s,*t;
{
	char *ptr, *index();
	sprintf (t,"%s/%s",SPOOLDIR,s);
	for (ptr=t+strlen(SPOOLDIR)+1; (ptr = index(ptr,'.')) != NULL; *ptr = '/')
		;
}


/*
	change directory to group
*/
cd_group ()
{
	char dbuf [RECLEN];
	g_dir ((Page.h.group)->nd_name,dbuf);
	if (chdir(dbuf) < 0)
	{
		Page.h.artnum = 1;
		Page.b[0].art_id = 0;
		strcpy (Page.b[0].art_t, "CANNOT FIND NEWSGROUP");
	}
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'hash.c'" '(1842 characters)'
if test -f 'hash.c'
then
	echo shar: will not over-write existing file "'hash.c'"
else
cat << \SHAR_EOF > 'hash.c'
/*
** vn news reader.
**
** hash.c - hash table routines
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>
#include "config.h"
#include "tune.h"
#include "vn.h"

/*
** hash table manipulation routines:
**	also sets Ncount, allocates Newsorsder array, and sets Newsorder
**	initially to order newsgroups were entered in (active file order)
*/

extern int Ncount;
extern NODE **Newsorder;

static NODE *Tab [HASHSIZE];	/* hash Table */

hashinit ()
{
	int i;
	for (i=0; i < HASHSIZE; ++i)
		Tab[i] = NULL;
	Ncount = 0;
}

/*
	enter new node (name s, articles n, low l) in hash Table, 
	initial flags = 0.  As nodes are entered, pnum item is temporarily
	used to indiacte entry order for initial construction of Newsorder
	array via entry_order();
*/
NODE *hashenter(s,n,l)
char *s;
int n;
int l;
{
	char *str_store();
	NODE *ptr,*node_store();
	int i;

	i=hash(s);
	ptr = node_store();
	ptr->next = Tab[i];
	Tab[i] = ptr;
	if (l > n)
		l = n;
	ptr->pnum = Ncount;
	++Ncount;
	ptr->rdnum = l;
	ptr->state = 0;
	ptr->art = n;
	ptr->nd_name = str_store(s);
	return (ptr);
}

NODE *hashfind(s)
char *s;
{
	NODE *ptr;

	for (ptr = Tab[hash(s)]; ptr != NULL && strcmp(ptr->nd_name,s) != 0;
					ptr = ptr->next)
		    ;
	return (ptr);
}

/*
** entry order is called after all hash_enter's have been done, PRIOR
** to the use of pnum item for anything else.  It constructs the initial
** Newsorder array.
*/
entry_order()
{
	int i;
	NODE *ptr;

	if ((Newsorder = (NODE **) malloc(Ncount*sizeof(NODE *))) == NULL)
		printex("Cannot allocate memory for Newsorder array");
	for (i=0; i < HASHSIZE; ++i)
	{
		for (ptr = Tab[i]; ptr != NULL; ptr = ptr->next)
			Newsorder[ptr->pnum] = ptr;
	}
}

static hash (s)
char *s;
{
	int rem;
	for (rem=0; *s != '\0'; ++s)
		rem = (rem*128 + (*s&0x7f)) % HASHSIZE;
	return (rem);
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'pagefile.c'" '(5053 characters)'
if test -f 'pagefile.c'
then
	echo shar: will not over-write existing file "'pagefile.c'"
else
cat << \SHAR_EOF > 'pagefile.c'
/*
** vn news reader.
**
** pagefile.c - routines to deal with page display tempfile
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>

#ifdef SYSV
#include <sys/types.h>
#include <fcntl.h>
#endif

#include <sys/file.h>
#include "vn.h"
#include "head.h"

extern int Ncount,Lrec,L_allow,Cur_page,C_allow;
extern int Nwopt, Nnwopt, Ntopt, Nntopt;
extern char *Wopt[], *Topt[], *Negtopt[], *Negwopt[];
extern NODE **Newsorder;
extern PAGE Page;
extern int Digest;

extern char *Aformat;

extern char *T_head, *F_head, *L_head;

static int Tdes;	/* temp file descriptor */
static int Pgsize;	/* block size for seeking file */

/*
	routines which deal with the temp file containing
	display pages.  Note the "invisible" file feature -
	tempfile is unlinked from /usr/tmp immediately.  when
	Tdes is closed by UNIX the disk space will be given back.
*/

temp_open ()
{
	char tmpart [L_tmpnam];
	Lrec = -1;
	tmpnam (tmpart);
	Pgsize = sizeof (HEAD) + L_allow * sizeof(BODY);
	if ((Tdes = open(tmpart,O_RDWR|O_CREAT)) < 0)
		printex ("can't open %s",tmpart);
	unlink (tmpart);
}

/*
	create page records for newsgroup s
	all articles between low and hi are to be included.
*/
outgroup (s,low,hi)
char *s;
int low,hi;
{
	int i,aid;
	char title[RECLEN],gd[RECLEN];
	g_dir(s,gd);
	if (chdir(gd) < 0)
	{
		grp_indic(s,0);
		return;
	}
	grp_indic(s,1);
	aid = 0;
	for (i=low+1; i <= hi; ++i)
	{
		if (digname (i,title) >= 0)
		{
			Page.b[aid].art_id = i;
			Page.b[aid].art_mark = ' ';
			strcpy (Page.b[aid].art_t, title);
			if ((++aid) >= L_allow)
			{

				/* start next page */
				Page.h.artnum = L_allow;
				do_write ();
				++Lrec;
				aid = 0;
			}
		}
	}

	/* last page (partial) */
	if (aid != 0)
	{
		Page.h.artnum = aid;
		do_write ();
		++Lrec;
	}
}

/*
	set current page to n.  use Pgsize and lseek to find it in
	temp file (descriptor Tdes).
*/
find_page (n)
int n;
{
	long off,lseek();
	int i,last;
	Cur_page = n;
	off = Pgsize;
	off *= (long) n;
	lseek (Tdes, off, 0);
	if (read(Tdes, (char *) &(Page.h), sizeof(HEAD)) < sizeof(HEAD))
		printex("bad temp file read");
	i = Pgsize - sizeof(HEAD);
	if (read(Tdes, (char *) Page.b, i) < i)
		printex("bad temp file read");
	last = -1;
	for (i=0; i < Ncount; ++i)
	{
		if ((Newsorder[i])->pages > 0)
		{
			if ((Newsorder[i])->pnum > n)
				break;
			last = i;
		}
	}
	if (last < 0)
		printex ("can't find page %d",n);
	Page.h.group = Newsorder[last];
	Page.h.name = (Page.h.group)->nd_name;
	cd_group ();
}

write_page ()
{
	long off,lseek();
	if (!Digest)
	{
		off = Pgsize;
		off *= (long) Cur_page;
		lseek (Tdes, off, 0);
		do_write();
	}
}

static do_write()
{
	int num;

	if (write(Tdes, (char *) &(Page.h), sizeof(HEAD)) < sizeof(HEAD))
		printex ("Bad temp file write");
	num = L_allow * sizeof(BODY);
	if (write(Tdes, (char *) Page.b, num) < num)
		printex ("Bad temp file write");
}

/*
	find article title:
	n - articles id
	t - returned title - must have storage for RECLEN, assumed to be
		> 3 * max title length also.
*/
static digname (n, t)
int n;
char *t;
{
	int i,j;
	FILE *fp,*fopen();
	char ff [MAX_C+1],fn [MAX_C+1],fl [MAX_C+1],*index();

	/* open article */
	sprintf (t,"%d", n);
	if ((fp = fopen(t,"r")) == NULL)
		return (-1);

	/* get subject, from and lines by reading article */
	ff[0] = fn[0] = fl[0] = '?';
	ff[1] = fn[1] = fl[1] = '\0';
	ff[C_allow] = fn[C_allow] = fl[C_allow] = '\0';
	for (i = 0; i < HDR_LINES && fgets(t,RECLEN-1,fp) != NULL; ++i)
	{
		if (index(CHFIRST,t[0]) == NULL)
			continue;
		t[strlen(t) - 1] = '\0';
		if (strncmp(T_head,t,THDLEN) == 0)
		{
			for (j=0; j < Nntopt; ++j)
			{
				if (regex(Negtopt[j],t+THDLEN) != NULL)
				{
					fclose(fp);
					return(-1);
				}
			}
			if (Ntopt > 0)
			{
				for (j=0; j < Ntopt; ++j)
				{
					if (regex(Topt[j],t+THDLEN) != NULL)
						break;
				}
				if (j >= Ntopt)
				{
					fclose(fp);
					return(-1);
				}
			}
			strncpy(fn,t+THDLEN,C_allow);
			continue;
		}
		if (strncmp(F_head,t,FHDLEN) == 0)
		{
			for (j=0; j < Nnwopt; ++j)
			{
				if (regex(Negwopt[j],t+FHDLEN) != NULL)
				{
					fclose(fp);
					return(-1);
				}
			}
			if (Nwopt > 0)
			{
				for (j=0; j < Nwopt; ++j)
				{
					if (regex(Wopt[j],t+FHDLEN) != NULL)
						break;
				}
				if (j >= Nwopt)
				{
					fclose(fp);
					return(-1);
				}
			}
			strncpy(ff,t+FHDLEN,C_allow);
			continue;
		}
		if (strncmp(L_head,t,LHDLEN) == 0)
		{
			strncpy(fl,t+LHDLEN,C_allow);
			break;
		}
	}

	fclose (fp);

	/* reject empty or 1 line files */
	if (i < 2)
		return (-1);

	form_title (t,fn,fl,ff,n);
	return (0);
}

form_title (t,fn,fl,ff,n)
char *t,*fn,*fl,*ff;
int n;
{
	char *ptr,*index();
	int i;

	if ((ptr = index(ff,'(')) != NULL && strlen(ptr) > 3)
		ff = ptr;
	sprintf (t,TFORMAT,fn,fl,ff);
	sprintf(ff,Aformat,' ',' ',n);
	i = C_allow - strlen(ff) + 1;	/* remember newline in Aformat */
	t[i] = '\0';
	ctl_xlt(t);
	return (0);
}

/* replace control characters in titles */
static ctl_xlt(s)
char *s;
{
	while (*s != '\0')
	{
		if (*s < ' ')
			*s += 'A' - 1;
		++s;
	}
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'reg.c'" '(1656 characters)'
if test -f 'reg.c'
then
	echo shar: will not over-write existing file "'reg.c'"
else
cat << \SHAR_EOF > 'reg.c'
/*
** vn news reader.
**
** reg.c - implementation of regex / regcmp on top of UCB library
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>

#define RGBLKSIZE 20

struct _regtab
{
	struct _regtab *link;
	char *regstr;
};

typedef struct _regtab REGTAB;

static REGTAB *Chain = NULL;
static REGTAB *Free = NULL;
static REGTAB *Compiled = NULL;

regfree(s)
char *s;
{
	REGTAB *ptr,*cmp,*old;

	cmp = (REGTAB *) s;
	old = NULL;

	for (ptr = Chain; ptr != NULL; ptr = (old = ptr)->link)
	{
		if (ptr == cmp)
		{
			if (old == NULL)
				Chain = Chain->link;
			else
				old->link = ptr->link;
			ptr->link = Free;
			Free = ptr;
			break;
		}
	}
}

char *regcmp(str)
char *str;
{
	int i;
	char *str_store();
	char *re_comp();

	if (re_comp(str) != NULL)
	{
		Compiled = NULL;	/* make sure we're OK */
		return(NULL);
	}

	if (Free == NULL)
	{
		Free = (REGTAB *) malloc(RGBLKSIZE * sizeof(REGTAB));
		if (Free == NULL)
			printex ("regcmp: memory allocation failure");
		for (i = 0; i < RGBLKSIZE - 1; ++i)
			Free[i].link = Free + i + 1;
		Free[i].link = NULL;
	}

	Compiled = Free;
	Free = Free->link;

	Compiled->link = Chain;
	Chain = Compiled;
	Compiled->regstr = str_store(str);

	return ((char *) Compiled);
}

char *regex(reg,str)
char *reg,*str;
{
	REGTAB *cmp;

	cmp = (REGTAB *) reg;

	if (cmp == Compiled)
	{
		if (re_exec(str))
			return(str);
		return (NULL);
	}

	for (Compiled = Chain; Compiled != NULL; Compiled = Compiled->link)
	{
		if (Compiled == cmp)
			break;
	}

	if (Compiled == NULL)
		printex ("regex: bad pointer");

	re_comp(Compiled->regstr);

	if (re_exec(str))
		return(str);

	return(NULL);
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'sig_set.c'" '(4501 characters)'
if test -f 'sig_set.c'
then
	echo shar: will not over-write existing file "'sig_set.c'"
else
cat << \SHAR_EOF > 'sig_set.c'
/*
** vn news reader.
**
** sig_set.c - signal handler
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>
#include <sys/signal.h>
#include <sgtty.h>
#include <setjmp.h>
#include "tty.h"
#include "vn.h"
#include "config.h"

extern int L_allow;
extern char *Version;

static int Sigflag=BRK_INIT;	/* phase of interaction */
static FILE **Fpseek;		/* article reading file pointer pointer */
static int Foreground;
static jmp_buf Jumploc;		/* for BRK_SESS phase */
static char *Cur_scn;		/* current group name being scanned */

/*
	interrupt handler - unusual termination (longjmp and printex aborts)
	if not abort, remember to reset signal trap
	CAUTION - the passing of a jump buffer is a little dicey - assumes
	type jump_buf is an array.

	sigcatch and sig_set control a lot of i/o on stderr also, since
	it is so intimately related to signal interaction.  Note that the
	SIGTSTP action causes a "stopped on tty output" if raw terminal
	mode is restored by tty_set(RESTORE).  We don't get it if we were
	already cooked since tty_set avoids calling ioctl if it doesn't
	have to.
*/
static sigcatch (sig)
int sig;
{
	char buf [MAX_C+1];
	int pgrp;

	/* disable signal while processing it */
	signal (sig,SIG_IGN);

	switch (sig)
	{
	case SIGINT:
	case SIGQUIT:
		break;

#ifdef JOBCONTROL
	case SIGTSTP:
		/* ignore SIGTTOU so we don't get stopped if [kc]sh grabs the tty */
		signal(SIGTTOU, SIG_IGN);
		tty_set (SAVEMODE);
		term_set (MOVE,0,L_allow+RECBIAS-1);
		printf ("\n");
		Foreground = 0;
		fflush (stdout);
		fflush (stderr);
		signal(SIGTTOU, SIG_DFL);

		/* Send the TSTP signal to suspend our process group */
		signal(SIGTSTP, SIG_DFL);
		sigsetmask(0);
		kill (0, SIGTSTP);

		/* WE ARE NOW STOPPED */

		/*
				WELCOME BACK!
				if terminals process group is ours, we are foregrounded again
				and can turn newsgroup name printing back on
			*/
		tty_set (RESTORE);
		switch (Sigflag)
		{
		case BRK_SESS:
			signal (SIGTSTP,sigcatch);
			longjmp (Jumploc,1);
		case BRK_IN:
			ioctl (1,TIOCGPGRP,&pgrp);
			if (pgrp == getpgrp(0))
			{
				Foreground = 1;
				if (Cur_scn != NULL)
					fgprintf ("    %s\n",Cur_scn);
			}
			break;
		default:
			break;
		}
		signal (SIGTSTP,sigcatch);
		return;
#endif
	default:
		printex (BRK_MSG,sig);
	}

	/* QUIT and INTERRUPT signals */
	switch (Sigflag)
	{
	case BRK_SESS:
		/* if in session, ask if really a quit, do longjump if not */
		term_set (ERASE);
		tty_set (RAWMODE);
		user_str (buf, BRK_PR, 1);
		if (buf[0] == 'y')
			printex (BRK_MSG,sig);
		signal (sig,sigcatch);
		longjmp (Jumploc,1);
	case BRK_READ:
		/* if reading seek file to end to abort page printing */
		printf ("\n");
		if (*Fpseek == NULL || fseek(*Fpseek,0L,2) < 0)
			putchar ('\07');
		break;
	default:
		printex (BRK_MSG,sig);
	}
	signal (sig,sigcatch);
}

/*
	sig_set controls what will be done with a signal when picked up by
	sigcatch.  grp_indic / fgprintf is included here to keep knowledge
	of TSTP state localized.
*/
/* VARARGS */
sig_set (flag,dat)
int flag, *dat;
{
	int i, *xfer, pgrp;
	if (Sigflag == BRK_INIT)
	{
		Cur_scn = NULL;
		signal (SIGINT,sigcatch);
		signal (SIGQUIT,sigcatch);
		signal (SIGHUP,sigcatch);
		signal (SIGTERM,sigcatch);
#ifdef JOBCONTROL
		signal (SIGTSTP,sigcatch);
		ioctl (1,TIOCGPGRP,&pgrp);
		if (pgrp == getpgrp(0))
		{
			Foreground = 1;
			fgprintf ("Visual News, Release %s, reading:\n",Version);
		}
		else
			Foreground = 0;
#else
		Foreground = NOJOB_FG;
#endif
	}
	switch (flag)
	{
	case BRK_IN:
	case BRK_OUT:
		Sigflag = flag;
		break;
	case BRK_READ:
		if (Sigflag != BRK_SESS)
			printex ("unexpected read state, sig_set\n");
		Fpseek = (FILE **) dat;
		Sigflag = BRK_READ;
		break;
	case BRK_SESS:
		xfer = (int *) Jumploc;
		for (i=0; i < sizeof(Jumploc) / sizeof(int); ++i)
			xfer[i] = dat[i];
		Sigflag = BRK_SESS;
		break;
	case BRK_RFIN:
		if (Sigflag != BRK_READ)
			printex ("unexpected finish state, sig_set\n");
		Sigflag = BRK_SESS;
		break;
	default:
		printex ("bad state %d, sig_set\n",flag);
	}
}

grp_indic (s,ok)
char *s;
int ok;
{
	NODE *ptr,*hashfind();

	/* we go to hash table because s might be a temporary buffer */
	if ((ptr = hashfind(s)) != NULL)
	{
		Cur_scn = ptr->nd_name;
		if (Foreground)
		{
			if (ok)
				fgprintf("    %s\n",Cur_scn);
			else
				fgprintf("    %s - Can't access spool directory\n",Cur_scn);
		}
	}
}

fgprintf (fs,a,b,c,d,e)
char *fs;
int a,b,c,d,e;
{
	if (Foreground)
		fprintf (stderr,fs,a,b,c,d,e);
	fflush (stderr);
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'storage.c'" '(1309 characters)'
if test -f 'storage.c'
then
	echo shar: will not over-write existing file "'storage.c'"
else
cat << \SHAR_EOF > 'storage.c'
/*
** vn news reader.
**
** storage.c - storage allocation routines
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>
#include "vn.h"

extern char *malloc();

extern int L_allow;

extern PAGE Page;
/*
	Storage allocaters.  One more call to malloc in entry_order routine.
*/

char *str_store (s)
char *s;
{
	static unsigned av_len = 0;	/* current storage available */
	static char *avail;
	int len;

	if (s == NULL)
		s = "";

	if ((len = strlen(s)+1) > av_len)
	{
		if (len > STRBLKSIZE)
			av_len = len;
		else
			av_len = STRBLKSIZE;
		if ((avail = malloc(av_len)) == NULL)
			printex ("can't allocate memory for string storage");
	}
	strcpy (avail,s);
	s = avail;
	avail += len;
	av_len -= len;
	return (s);
}

/*
** called after number of terminal lines (L_allow) is known, to set
** up storage for Page.
*/
page_alloc ()
{
	char *body;

	if ((body = malloc(L_allow*sizeof(BODY))) == NULL)
		printex ("can't allocate memory for display storage");

	Page.b = (BODY *) body;
}

NODE
*node_store()
{
	static int nd_avail = 0;
	static NODE *nd;
	NODE *ret;

	if (nd_avail <= 0)
	{
		if ((nd = (NODE *) malloc(sizeof(NODE)*NDBLKSIZE)) == NULL)
			printex ("can't allocate memory for newsgroup table");
		nd_avail = NDBLKSIZE;
	}
	--nd_avail;
	ret = nd;
	++nd;
	return(ret);
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'strings.c'" '(720 characters)'
if test -f 'strings.c'
then
	echo shar: will not over-write existing file "'strings.c'"
else
cat << \SHAR_EOF > 'strings.c'
/*
** vn news reader.
**
** strings.c - character strings
**
** see copyright disclaimer / history in vn.c source file
*/

#include "vn.h"
#include "head.h"

char *Version = "12/86";

char *No_msg = "No articles";
char *Hdon_msg = "Headers being printed";
char *Hdoff_msg = "Headers being suppressed";
char *Roton_msg = "ROT 13";
char *Rotoff_msg = "NO ROT";

char *Aformat = AFORMAT;

char *Contstr = "  ******** any key to continue ********";

char *R_head = RHEAD;
char *M_head = MHEAD;
char *P_head = PHEAD;
char *D_head = DHEAD;
char *F_head = FHEAD;
char *FT_head = FTHEAD;
char *T_head = THEAD;
char *L_head = LHEAD;
char *N_head = NHEAD;
char *RT_head = RTHEAD;
char *TO_head = TOHEAD;
char *DIS_head = DISHEAD;
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'strtok.c'" '(1082 characters)'
if test -f 'strtok.c'
then
	echo shar: will not over-write existing file "'strtok.c'"
else
cat << \SHAR_EOF > 'strtok.c'
/*
** vn news reader.
**
** strtok.c - strtok() and strpbrk() string routines using UCB index().
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>

char *strpbrk (s,del)
char *s, *del;
{
	char *ptr,*index();
	if (s == NULL)
		return (NULL);
	for (; *del != '\0'; ++del)
		if ((ptr = index(s,*del)) != NULL)
			return (ptr);
	return (NULL);
}

char *strtok(str,delim)
char *str, *delim;
{
	char *tokstart, *tokend, *first_ch (), *last_ch();
	static char *save=NULL;

	if (str != NULL)
		save = str;

	if (save == NULL)
		return (NULL);

	tokstart = first_ch (save, delim);
	tokend = last_ch (tokstart, delim);
	save = first_ch (tokend, delim);
	*tokend = '\0';

	if (*tokstart == '\0')
		return (NULL);

	return (tokstart);
}

static char *first_ch (str,delim)
char *str,*delim;
{
	char *index ();
	char *f;

	for (f = str; *f != '\0' && index(delim,*f) != NULL; ++f)
		;

	return (f);
}

static char *last_ch (str,delim)
char *str,*delim;
{
	char *index ();
	char *f;

	for (f = str; *f != '\0' && index(delim,*f) == NULL; ++f)
		;

	return (f);
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'term_set.c'" '(4914 characters)'
if test -f 'term_set.c'
then
	echo shar: will not over-write existing file "'term_set.c'"
else
cat << \SHAR_EOF > 'term_set.c'
/*
** vn news reader.
**
** term_set.c - terminal control, hides termcap interface
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>
#include "tty.h"
#include "vn.h"

extern int L_allow, C_allow;
extern char *Ku, *Kd, *Kl, *Kr;	

static outc (c)
char c;
{
	putchar (c);
}

/*
	term_set controls terminal through termcap
	START sets global parameters related to terminal also,
	as well as allocating display buffer which depends on
	terminal lines, and allocating escape strings.  RESTART
	simply re-issues the initialization - used following system
	calls that could have goofed up the terminal state.
*/

/*
** Escape strings.
*/

static char *Cm,*Cl,*So,*Se,*Te,*Bc,*Ce,*Ti,*Ks,*Ke;
#ifdef USEVS
static char *Vs,*Ve;
#endif

static int Backspace;		/* backspace works */
static int Overstrike;		/* terminal overstrikes */

static t_setup()
{
	int i;
	char *tgetstr(), *getenv(), *str_store();
	char *c, tc_buf[2048],optstr[2048];

	c = optstr;
	if (tgetent(tc_buf,getenv("TERM")) != 1)
		printex ("%s - unknown terminal",getenv("TERM"));

	/* get needed capabilities */
	Cm = str_store(tgetstr("cm",&c));
	Cl = str_store(tgetstr("cl",&c));
	So = str_store(tgetstr("so",&c));
	Se = str_store(tgetstr("se",&c));
	Te = str_store(tgetstr("te",&c));
	Ti = str_store(tgetstr("ti",&c));
	Bc = str_store(tgetstr("bc",&c));
	Ce = str_store(tgetstr("ce",&c));
	Kd = str_store(tgetstr("kd",&c));
	Ke = str_store(tgetstr("ke",&c));
	Kl = str_store(tgetstr("kl",&c));
	Kr = str_store(tgetstr("kr",&c));
	Ks = str_store(tgetstr("ks",&c));
	Ku = str_store(tgetstr("ku",&c));
#ifdef USEVS
	Vs = str_store(tgetstr("vs",&c));
	Ve = str_store(tgetstr("ve",&c));
#endif
	Backspace = tgetflag("bs");
	Overstrike = tgetflag("os");

	if ( *Cm == '\0' || *Cl == '\0')
	{
		printex ("cursor control and erase capability needed");
	}

	/*
	** Checks for arrow keys which don't issue something beginning
	** with <ESC>.  This is more paranoid than we need to be, strictly
	** speaking - we could get away with any string which didn't
	** conflict with controls used for commands.  However, that would
	** be a maintenance headache - we will simply reserve <ESC> as the
	** only char not to be used for commands, and punt on terminals
	** which don't send reasonable arrow keys.  It would be confusing
	** to have keys work partially, also.  I know of no terminal with
	** one arrow key beginning with an escape, and another beginning
	** with something else, but let's be safe.  This also insists on
	** definitions for all 4 arrows, which seems reasonable.
	*/

	if ((*Ku != '\0' && *Ku != '\033') || *Kl != *Ku || *Kr != *Ku || *Kd != *Ku)
	{
		fgprintf("WARNING: arrow keys will not work for this terminal");
		Ku = Kd = Kl = Kr = Kd = Ke = "";
	}

	if (Overstrike)
		fgprintf ("WARNING: terminal overstrikes - can't update display without erase\n");

	i = RECBIAS+1 < HHLINES+2 ? HHLINES+2 : RECBIAS+1;
	if ((L_allow = tgetnum("li")) < i)
	{
		if (L_allow < 0)
			printex ("can't determine number of lines on terminal");
		printex ("too few lines for display - %d needed", i);
	}

	/*
	** C_allow set so as to not use extreme right column.
	** Avoids "bad wraparound" problems - we're deciding it's best
	** to ALWAYS assume no automargin, and take care of it ourselves
	*/
	if((C_allow = tgetnum("co")) > MAX_C)
		C_allow = MAX_C;
	else
		--C_allow;
	if (C_allow < MIN_C)
	{
		if (C_allow < 0)
			printex("can't determine number of columns on terminal.");
		printex ("too few columns for display - %d needed",MIN_C);
	}

	L_allow -= RECBIAS;
	page_alloc();
	tputs(Ti,1,outc);
	tputs(Ks,1,outc);
#ifdef USEVS
	tputs(Vs,1,outc);
#endif
}

/* VARARGS */
term_set(cmd,x,y)
int cmd,x,y;
{
	char *tgoto();
	int i;
	switch (cmd)
	{
	case MOVE:
		tputs (tgoto(Cm,x,y),1,outc);
		break;
	case ERASE:
		tputs(Cl,1,outc);
		break;
	case ONREVERSE:
		tputs(So,1,outc);
		break;
	case OFFREVERSE:
		tputs(Se,1,outc);
		break;
	case START:
		t_setup();
		break;
	case RESTART:
		tputs(Ti,1,outc);
		tputs(Ks,1,outc);
#ifdef USEVS
		tputs(Vs,1,outc);
#endif
		break;
	case STOP:
		term_set (MOVE,0,L_allow+RECBIAS-1);
		printf ("\n");
		tputs(Ke,1,outc);
		tputs(Te,1,outc);
#ifdef USEVS
		tputs(Ve,1,outc);
#endif
		break;
	case RUBSEQ:
		if (Overstrike)
		{
			/* space overprint is futile */
			if (Backspace)
				putchar('\010');
			else
				tputs(Bc,1,outc);
			break;
		}
		if (Backspace)
			printf("%c %c",'\010','\010');
		else
		{
			tputs(Bc,1,outc);  
			putchar(' ');  
			tputs(Bc,1,outc);
		}
		break;
	case ZAP:
		if (Ce != NULL && *Ce != '\0')
			tputs(Ce,1,outc);
		else
		{
			if (Overstrike)
				break;		/* punt */
			for (i=x; i < y; ++i)
				putchar(' ');
			if (Backspace)
			{
				for (i=x; i < y; ++i)
					putchar('\010');
			}
			else
			{
				for (i=x; i < y; ++i)
					tputs(Bc,1,outc);
			}
		}
		break;
	default:
		printex ("term_set unknown code (%d)",cmd);
		break;
	}
	return (0);
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'tmpnam.c'" '(420 characters)'
if test -f 'tmpnam.c'
then
	echo shar: will not over-write existing file "'tmpnam.c'"
else
cat << \SHAR_EOF > 'tmpnam.c'
/*
** vn news reader.
**
** tmpnam.c - tmpnam() replacement for UCB, also uses non-generic name.
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>
#include "config.h"

char *tmpnam (buf)
char *buf;
{
	static char *ptr = VNTEMPNAME;

	/* depends on string initialized above */
	sprintf (ptr+TMP_XOFFSET,"XXXXXX");

	mktemp (ptr);

	if (buf != NULL)
		strcpy (buf,ptr);

	return (ptr);
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'tty_set.c'" '(2552 characters)'
if test -f 'tty_set.c'
then
	echo shar: will not over-write existing file "'tty_set.c'"
else
cat << \SHAR_EOF > 'tty_set.c'
/*
** vn news reader.
**
** tty_set.c - interface to ioctl (system tty interface)
**
** see copyright disclaimer / history in vn.c source file
*/

#ifdef SYSV
#include <termio.h>
#else
#include <sgtty.h>
#endif

#include "tty.h"

extern char Erasekey,Killkey;

#ifdef SYSV
static struct termio C_tp, O_tp;
#else
static struct sgttyb C_tp;
static unsigned short O_lflag;
#endif

static unsigned S_flag=0;
static int R_ignore=0;		/* up/down counter of reset calls to ignore */

#define IO_GOT 1	/* have polled for original terminal mode */
#define IO_RAW 2	/* in RAW (CBREAK actually) mode */

/*
	tty_set handles ioctl calls.  SAVEMODE, RESTORE are used around
	system calls and interrupts to assure cooked mode, and restore
	raw if raw on SAVEMODE.  The pair results in no calls to ioctl
	if we are cooked already when SAVEMODE is called, and may be nested,
	provided we desire no "restore" of cooked mode after restoring raw.

	When we get the original terminal mode, we also save erase and kill.

	sig_set makes an ioctl call to get process group leader.  Otherwise
	ioctl calls should come through here.
*/
tty_set(cmd)
int cmd;
{
	int rc;
	unsigned mask;

	switch (cmd)
	{
	case BACKSTOP:
#ifdef JOBCONTROL
		if ((rc = ioctl(1,TIOCLGET,&mask)) != 0)
			break;
		mask |= LTOSTOP;
		rc = ioctl(1,TIOCLSET,&mask);
#else
		rc = 0;
#endif
		break;
	case RAWMODE:
		if ((S_flag & IO_RAW) != 0)
		{
			rc = 0;
			break;
		}
		if ((S_flag & IO_GOT) == 0)
		{
			/* Save original modes, get erase / kill */
#ifdef SYSV
			rc = ioctl(0,TCGETA,&C_tp);
			O_tp = C_tp;
			Erasekey = C_tp.c_cc[VERASE];
			Killkey = C_tp.c_cc[VKILL];
#else
			rc = ioctl(0,TIOCGETP,&C_tp);
			O_lflag = C_tp.sg_flags;
			Erasekey = C_tp.sg_erase;
			Killkey = C_tp.sg_kill;
#endif
		}
#ifdef SYSV
		C_tp.c_lflag &= ~(ECHO | ICANON);
		C_tp.c_cc[VMIN] = 1;
		rc = ioctl(0,TCSETAW,&C_tp);
#else
		C_tp.sg_flags |= CBREAK;
		C_tp.sg_flags &= ~ECHO;
		rc = ioctl(0,TIOCSETP,&C_tp);
#endif
		S_flag = IO_GOT|IO_RAW;
		break;
	case COOKED:
		if ((S_flag & IO_RAW) != 0)
		{
#ifdef SYSV
			C_tp = O_tp;
			rc = ioctl(0,TCSETAW,&C_tp);
#else
			C_tp.sg_flags = O_lflag;
			rc = ioctl(0,TIOCSETP,&C_tp);
#endif
			S_flag &= ~IO_RAW;
		}
		else
			rc = 0;
		break;
	case SAVEMODE:
		if ((S_flag & IO_RAW) != 0)
		{
			tty_set(COOKED);
			R_ignore = 0;
		}
		else
			++R_ignore;
		rc = 0;
		break;
	case RESTORE:
		if (R_ignore <= 0)
		{
			tty_set(RAWMODE);
		}
		else
			--R_ignore;
		rc = 0;
		break;
	default:
		rc = -1;
	}
	if (rc < 0)
		printex ("ioctl failure, tty_set: %d",cmd);
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'userlist.c'" '(1923 characters)'
if test -f 'userlist.c'
then
	echo shar: will not over-write existing file "'userlist.c'"
else
cat << \SHAR_EOF > 'userlist.c'
/*
** vn news reader.
**
** userlist.c - generate user's list of articles
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>
#include "vn.h"

extern PAGE Page;

/*
	generate user list of articles - either article numbers
	are input directly (numeric list), or input is a search
	string - invoke regular expression library and examine titles
	search string "*" reserved for marked articles.  Strings may
	be prefixed with '!' for negation.
*/
userlist (list)
char *list;
{
	int i,j,anum[RECLEN/2],acount;
	char neg, *s, sbuf[MAX_C+1], *reg, *regex(), *regcmp(), *index(), *strtok();

	user_str (sbuf,"Articles or title search string : ",1);
	if (sbuf[0] == '!')
	{
		neg = '!';
		s = sbuf+1;
	}
	else
	{
		neg = '\0';
		s = sbuf;
	}
	for (i=0; s[i] != '\0'; ++i)
	{
		if (index(LIST_SEP,s[i]) == NULL)
		{
			if (s[i] < '0' || s[i] > '9')
				break;
		}
	}
	acount = 0;

	if (s[i] == '\0')
	{
		for (s = strtok(s,LIST_SEP); s != NULL; s = strtok(NULL,LIST_SEP))
		{
			anum[acount] = atoi(s);
			++acount;
		}
	}
	else
	{
		if (s[0] == ART_MARK)
		{
			for (i=0; i < Page.h.artnum; ++i)
			{
				if (Page.b[i].art_mark == ART_MARK)
				{
					anum[acount] = Page.b[i].art_id;
					++acount;
				}
			}
		}
		else
		{
			reg = regcmp(s,(char *) 0);
			if (reg != NULL)
			{
				for (i=0; i < Page.h.artnum; ++i)
				{
					if (regex(reg,Page.b[i].art_t) != NULL)
					{
						anum[acount] = Page.b[i].art_id;
						++acount;
					}
				}
				regfree (reg);
			}
			else
				preinfo ("bad regular expression syntax");
		}
	}

	/* algorithm is inefficient, but we're only handling a few numbers */
	*list = '\0';
	for (i=0; i < Page.h.artnum; ++i)
	{
		for (j=0; j < acount && anum[j] != Page.b[i].art_id; ++j)
			;
		if (neg == '!')
		{
			if (j < acount)
				continue;
		}
		else
		{
			if (j >= acount)
				continue;
		}
		sprintf (list,"%d ",Page.b[i].art_id);
		list += strlen(list);
	}
}
SHAR_EOF
fi # end of overwriting check
echo shar: extracting "'vnglob.c'" '(1613 characters)'
if test -f 'vnglob.c'
then
	echo shar: will not over-write existing file "'vnglob.c'"
else
cat << \SHAR_EOF > 'vnglob.c'
/*
** vn news reader.
**
** vnglob.c - global variables - see string.c also
**
** see copyright disclaimer / history in vn.c source file
*/

#include <stdio.h>
#include "config.h"
#include "vn.h"
#include "head.h"

/*
	global data structure
*/
NODE **Newsorder;		/* .newsrc file order */

char *Editor,*Ps1,*Mailer,*Printer,*Poster;

char Erasekey, Killkey;		/* user keys from stty */
char *Newsrc, *Orgdir;		/* .newsrc file, and original pwd */
char *Onews;			/* temp. file for backing up .newsrc */
char *Savefile = DEF_SAVE;	/* file in which to save articles */
char *Savedir;			/* default directory for saved articles */
char *Ccfile;			/* author_copy file, stored /bin/mail fmt */

int Rot;	/* rotation */
int Headflag;	/* header printing flag */
int Digest;	/* if non-zero, digest article */

char *Ku, *Kd, *Kl, *Kr;	/* Cursor movement capabilities */

/* character translation arrays for commands */
char Cxitop[128], Cxitor[128], Cxrtoi[128], Cxptoi[128];

/*
	cur_page - current page displayed;
	lrec - last record
	l_allow - lines allowed for article display
	c_allow - columns allowed
	ncount = newsorder index
	nfltr - number of filters
*/
int Cur_page, Lrec, L_allow, C_allow, Ncount, Nfltr;

/*
	article filtration options.
*/
char *Wopt[NUMFILTER];		/* regular expressions for -w options */
char *Topt[NUMFILTER];		/* regular expressions for -t options */
char *Negwopt[NUMFILTER];	/* regular expressions for negated -w options */
char *Negtopt[NUMFILTER];	/* regular expressions for negated -t options */

int Nwopt, Ntopt, Nnwopt, Nntopt;

int Nounsub, Listfirst;
/*
	current page
*/
PAGE Page;
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0