[net.sources] rn - A new and improved newsreader with an old name.

discolo@ucsbcsl.UUCP (Anthony V. Discolo) (05/04/85)

Here is the source for my rn, and tips on installing it.

(1) rn.h contains local pathnames, and misc2.c contains the
pathname for inews; look at these files before compiling.

(2) pager.diffs contains diffs to 4.2BSD more source to turn it
into a news paginator which has a nice header with article id,
author, and subject at the top of every page.  If you don't have
the source or are running another flavor of UNIX, then you can
just define PAGER in rn.h to be /usr/ucb/more.  If you do have
the 4.2BSD more source, then define PAGER to be the modified more.
If you don't end up using the news paginator, you will have to 
comment out the following lines:
		digest.c: 135-139
		misc2.c: 178-182, 211-215
which are of the form
		if (strcmp(pager, PAGER) == 0)
			sprintf(..)
		else
because the code was designed around PAGER having a header option.

NOTE: rn has been tested on a 4.2BSD system with all the terminals
that we have here (Wyse, Freedom, BitGraph, Z19, Tektronix), and
uses only the termlib library.  I don't know how it will work on
other terminals, or other flavors of UNIX.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CUT HERE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# This is a shar archive, extract by
#		% sh archive
echo x - Disclaimer
cat << ! > Disclaimer
Author:	Anthony V. Discolo
	Computer Systems Lab
	University of California
	Santa Barbara, CA 93106
	(805) 961-4178

Disclaimer:	I take no responsibility whatsoever for the condition
		of this software.  Unrestricted use is hereby granted
		as long as this header remains intact.
!
echo x - Makefile
cat << ! > Makefile
CFLAGS = -O
SRCS =	active.c articles.c digest.c inotab.c misc1.c misc2.c tty.c
OBJS =	active.o articles.o digest.o inotab.o misc1.o misc2.o tty.o

all: rn

rn: rn.o \${OBJS}
	cc \${CFLAGS} -o rn rn.o \${OBJS} -ltermlib
	
checknews: checknews.o active.o misc1.o misc2.o tty.o
	cc \${CFLAGS} -o checknews checknews.o active.o misc1.o misc2.o tty.o -ltermlib

convrc: convrc.o
	cc -O -o convrc convrc.o

lint:
	lint rn.c \${SRCS}

ctags:
	ctags rn.c \${SRCS}

clean:
	rm -f *.o
!
echo x - Readme
cat << ! > Readme
/staff/csl/discolo/src/cmd/rn/Readme: March 25, 1985
				      April  9, 1985
				      April 21, 1985

Disclaimer:	I take no responsibility whatsoever for the condition
		of this software.  Unrestricted use is hereby granted
		as long as this header remains intact.

This is a new version of readnews.  I think it has most of the
useful features of the old one.

What the new version of readnews gives you is a way to quickly
scan the articles to determine which ones you want to read.
This is done by grouping the articles of a single newsgroup
together with author and subject, and also being able to preview
the article[1].  I think the previewing feature is a very
nice one, and will help you weed out uninteresting articles.
If there are more articles than can fit on a screen, then the
articles are split into multiple screens, and you are able to
go back and forth between them, selecting the articles you
want to read.  Articles are gathered one screen at a time,
so even reading a newsgroup from the beginning does not take
a long initialization period[2].  This is the first phase.

After you have selected the articles you want to read, you
signal the program by a command, and it goes into the second
phase of reading news, which presents the articles that you've
selected through a news paginator.  After you read each article,
you can save it, reply or followup, etc., just like the old readnews.

You can go back and forth between the two phases for any
newsgroup; you can go back and forth between newsgroups.  If
you have already read an article, and are displaying the articles
a second time, you will see which articles have been previously
read.

Another personal design issue I have engineered into this version
of readnews is that the .newsrc file contains only the last
article id read, and cannot contain \`\`holes'' in the list for
unread articles.  Also, the .newsrc can contain unsubscribed
newsgroups (via the '!' operator), but there is not an options line.
Furthermore, this readnews will not pick up new newsgroups
automatically, but usually you will find out about new newsgroups
through existing newsgroups anyway[3].  The bottom line here is that
my version is not compatible with the older versions, and that
you will have to edit your .newsrc to use my version.

The format of the new version of .newsrc is
	<newsgroup>[:!] <last articleid>

Enough introduction, here is how you actually use my readnews.  The
usage is \`\`rn [ -Ppx ] [ -v lines ] [ -n ng1 ng2 ... ]'', where the
meaning for the options are
	P: Turn off automatic previewing
	p: Turn on automatic previewing
	x: Don't listen to .newsrc
	v: Define the number of lines in the preview area.
	n: Specify newsgroups to be read
By default, the previewing area is 5 lines, and automatic previewing
is silently turned off if the baud rate is less than 2400 baud (this
can be overridden with -p).

If there is news, you will get a listing of the articles in the first
newsgroup and be put at the first article.  This is the first phase.
Here you can use the following commands in the first phase:
	<return>, +, j	Next article
	-, k		Previous article
	c		Select article for reading and caesar decode it
	d		Select digest for reading
	n		Next screenful of articles
	p		Previous screenful of articles
	s		Select article for straight reading
	u		Unselect article/digest for reading
	v		Preview article
	<esc>		Read selected articles
	E		Save all articles for next time
	N		Next newsgroup
	P		Previous newsgroup
	Q		Quit, update .newsrc
	S		Save unread articles for next time
	U		Unsubscribe to a newsgroup
	V		Toggle automatic previewing
	X		Quit, don't update .newsrc
	^Z		Suspend

After you select articles for reading by pressing <esc>, you will be
presented with the articles in order through the news paginator.  This
is the second phase.  After you read an article, you can execute the
following commands:
	?		Gives a little help
	c [ rot ]	Decode last article
	e		Quit, return to same newsgroup
	f		Submit a followup article
	p		Reprint this article
	q		Quit, go to next newsgroup
	r		Reply via mail
	s <filename>	Save article in <filename>
	-		Back to previous article
	#		Display the newsgroup and number of articles

There is also a version of checknews which works with the new version
of .newsrc.

This should be enough to get you started.  If you have any questions
or comments, please address them to
		Anthony V. Discolo
		Department of Computer Science
		U.C. Santa Barbara
		Santa Barbara, CA 93106
		(805) 961-4178
		{ucbvax,cepu,arizona}!ucsbcsl!discolo
---------------
Footnotes:
[1] The idea for previewing an article came from Dave Elrod at the
Computer Systems Lab.  I wish I had thought of it.
[2] Gerry Pollack reminded me that gathering all the articles at
one time was a stupid way to do it.
[3] This feature will be added in when I figure out a good way
to do it.
!
echo x - active.c
cat << ! > active.c
/*
 * active - Routines dealing with ACTIVE file.
 */
#include <stdio.h>
#include <sys/param.h>
#include "rn.h"

/*
 * Read the ACTIVE file, inserting values into hash table.
 */
readactive() {
	FILE *a;
	int ngid;
	char ngnm[40];

	if ((a = fopen(ACTIVE, "r")) == NULL) {
		perror(ACTIVE);
		exit(1);
	}
	while (fscanf(a, "%s%d", ngnm, &ngid) != EOF)
		anginsert(ngnm, ngid);
	fclose(a);
}

/*
 * Insert newsgroup into hashed table.
 */
anginsert(ngname, maxid)
char *ngname;
int maxid;
{
	int hval;
	register struct angent *ang;

	hval = anghash(ngname);
	ang = angtab[hval];
	if (ang == NULL)
		ang = angtab[hval] = angalloc();
	else {
		while (ang->ang_next != NULL)
			ang = ang->ang_next;
		ang->ang_next = angalloc();
		ang = ang->ang_next;
	}
	ang->ang_next = NULL;
	strcpy(ang->ang_name, ngname);
	ang->ang_maxid = maxid;
}

/*
 * Return maximum article id given newsgroup.
 */
angval(ngname)
char *ngname;
{
	int hval, len;
	register struct angent *ang;

	len = strlen(ngname);
	hval = anghash(ngname);
	ang = angtab[hval];
	if (ang == NULL)
		return(0);
	while (ang != NULL) {
		if (strncmp(ngname, ang->ang_name, len) == 0)
			return(ang->ang_maxid);
		ang = ang->ang_next;
	}
	return(0);
}

anghash(name)
char *name;
{
	int h;

	h = 0;
	while (*name) {
		h += (int) (*name) * (int) (*name);
		name++;
	}
	return(h % ACTABSIZ);
}

struct angent *angalloc() {
	char *malloc();
	struct angent *a;

	a = (struct angent *) malloc(sizeof(struct angent));
	if (a == NULL) {
		printf("angalloc: No more memory\n");
		exit(1);
	}
	return(a);
}
!
echo x - articles.c
cat << ! > articles.c
/*
 * articles - Major article routines.  Miscellaneous article
 *	      routines are in misc2.c.
 */
#include <stdio.h>
#include <sys/param.h>
#include <sys/stat.h>
#include "rn.h"

/*
 * Gather one screenful of unread articles for a newsgroup.
 */
gatherarticles(ng)
struct newsgroup *ng;
{
	short screen;
	static short firstime = 1;
	int angv;
	register int anum, narticles;
	register struct article *a, *lasta;
	struct stat sbuf;

	screen = maxscreens(ng) + 1;
	if (!firstime && screen == 1)
		message("Searching %s", ng->ng_name, 0);
	/*
	 * If we've reached the end, no need in looking anymore.
	 */
	if ((angv = angval(ng->ng_name)) == ng->ng_lastid)
		return(0);
	if (screen != 1)
		message("Searching for more articles in %s", ng->ng_name);
	narticles = 0;
	lasta = ng->ng_end;
	for (anum = ng->ng_lastid + 1; anum <= angv; anum++) {
		ng->ng_lastid = anum;
		if ((af = openarticle(anum)) == NULL)
			continue;
		/*
		 * Check to see whether we have already
		 * seen this article in another newsgroup.
	 	 */
		if (fstat(fileno(af), &sbuf) < 0 ||
		    itval(sbuf.st_ino)) {
			fclose(af);
			continue;
		}
		/*
		 * Weed out garbaged articles.
		 */
		if (sbuf.st_size < 30) {
			fclose(af);
			continue;
		}
		itinsert(sbuf.st_ino);
		a = aralloc();
		if (ng->ng_start == NULL) {
			ng->ng_start = a;
			a->a_forw = a->a_back = NULL;
		}
		else {
			lasta->a_forw = a;
			a->a_back = lasta;
		}
		a->a_forw = NULL;
		a->a_id = anum;
		a->a_screen = screen;
		strncpy(a->a_date, getheader("Date"), sizeof(a->a_date));
		strncpy(a->a_from, getheader("From"), sizeof(a->a_from));
		strncpy(a->a_subj, getheader("Subject"), sizeof(a->a_subj));
		strncpy(a->a_keywds, getheader("Keywords"), sizeof(a->a_keywds));
		strncpy(a->a_ngs, getheader("Newsgroups"), sizeof(a->a_ngs));
		a->a_lines = atoi(getheader("Lines"));
		a->a_text = textstart();
		lasta = a;
		fclose(af);
		if (++narticles % screenful == 0)
			break;
	}
	if (narticles) {
		firstime = 0;
		ng->ng_narticles += narticles;
		ng->ng_end = lasta;
	}
	return(narticles);
}

/*
 * Select articles from a newsgroup.  This code is
 * terribly convoluted, mainly because you can go
 * forward and backward between newsgroups within
 * this routine, and certain operations imply others.
 */
selectarticles(ng)
struct newsgroup *ng;
{
	short scr, quit, noadvance;
	int art, rot;
	register char c;
	char *p, buf[BUFSIZ];
	struct newsgroup *lastn;
	register struct article *a;
	struct article *lasta;

	scr = 1;
startover:
	noecho();
	cooked();
	cd(ng->ng_dir);
	a = ng->ng_start;
	while (a != NULL && a->a_screen != scr)
		a = a->a_forw;
	if (a == NULL) {
		message("panic: Can't find screen %d", scr);
		exit(1);
	}
	signal(SIGTSTP, SIG_IGN);
	screensetup(ng, scr);
	listarticles(a);
	if (autoview)
		preview(a);
	toarticle(0);
	fflush(stdout);
	art = 0;
	signal(SIGTSTP, SIG_DFL);
	raw();
	for (;;) {
		startvisual();
		c = getchar();
		endvisual();
		switch (c) {
			/*
			 * Read selected articles.
			 */
			case '\033':
				if (selections(ng))
					goto out;
				else {
					message("No selections.", 0);
					toarticle(art);
				}
				break;
			/*
			 * Suspend.
			 */
			case CTRL(z):
				dosuspend(ng, scr);
				if (autoview)
					preview(a);
				toarticle(art);
				break;
			/*
			 * Save all the articles in this
			 * newsgroup for next time.
			 */
			case 'E':
				message("Saving all of %s for next time.", 
				    ng->ng_name, 0);
				sleep(2);
				ng->ng_touched = 0;
				ng->ng_narticles = 0;
				ng->ng_start = NULL;
				goto nextorprev;
			/*
			 * Next newsgroup.
			 */
			case 'N':
				/* 
				 * Before the last read newsgroup,
				 * or before the absolute last
				 * newsgroup.
				 */
				ng->ng_touched = 1;
				if ((lastng != NULL && ng != lastng) ||
				    ng != curng) {
					if ((ng = nextng(ng)) != NULL) {
						scr = 1;
						goto startover;
					}
					else {
						unsetty();
						return;
					}
				}
				/*
				 * At the absolute last newsgroup.
				 */
				else if (ng == lastng) {
					message("No more newsgroups.", 0);
					toarticle(art);
				}
				/*
				 * At the last read newsgroup.
				 */
				else if (ng == curng) {
					unsetty();
					return;
				}
				break;
			/*
			 * Previous newsgroup.
			 */
			case 'P':
				if ((lastn = prevng(ng)) != NULL) {
					ng = lastn;
					scr = 1;
					goto startover;
				}
				else {
					message("No previous newsgroup.", 0);
					toarticle(art);
				}
				break;
			/*
			 * Save some of the articles in this
			 * newsgroup for next time.
			 */
			case 'S':
				message("Saving %s/%d-%d for next time.", 
				    ng->ng_name, a->a_id, ng->ng_lastid, 0);
				sleep(2);
				ng->ng_lastid = a->a_id - 1;
				goto nextorprev;
			/*
			 * Quit the whole thing.
			 */
			case 'Q':
				done(0);
				break;
			/*
			 * Unsubscribe to this newsgroup, and try to
			 * go to the next newsgroup.  If there is no
			 * next newsgroup, try to go to the previous
			 * newsgroup.  Very ugly.
			 */
			case 'U':
				ng->ng_touched = ng->ng_unsubscribe = 1;
				message("Unsubscribed to %s", ng->ng_name);
				sleep(2);
nextorprev:
				/*
				 * If we are at the current
				 * newsgroup, there could be more,
				 * so just return.
				 */
				if (lastng == NULL && ng == curng) {
					unsetty();
					return;
				}
				/*
				 * Can we go forward a newsgroup?
				 */
				else if (ng != lastng &&
				    (lastn = nextng(ng)) != NULL) {
					ng = lastn;
					scr = 1;
					goto startover;
				}
				/*
				 * If we can't find the next newsgroup,
				 * try to find the previous newsgroup.
				 * Note that we reset lastng and curng
				 * to make it look like we are at the
				 * last newsgroup.
				 */
				else if (ng == lastng &&
				    (lastn = prevng(ng)) != NULL) {
					ng = lastng = curng = lastn;
					scr = 1;
					goto startover;
				}
				/*
				 * If we get to here there is no
				 * where to go, so just quit.
				 */
				else
					done(0);
				break;
			/*
			 * Toggle automatic previewing.
			 */
			case 'V':
				autoview = autoview ? 0 : 1;
				if (autoview)
					preview(a);
				message("Automatic previewing is now %s.",
				    autoview ? "on" : "off");
				toarticle(art);
				break;
			/*
			 * Quit without writing .newsrc.
			 */
			case 'X':
				done(1);
				break;
			/*
			 * Caesar decode this article.
			 */
			case 'c':
				markarticle(art, 'c');
				fflush(stdout);
				a->a_select = DECODE;
				goto nextart;
			/*
			 * Select digest article.
			 */
			case 'd':
				markarticle(art, 'd');
				fflush(stdout);
				a->a_select = DIGEST;
				/* fall through */
			/*
			 * Next article.
			 */
			case '+':
			case 'j':
			case '\r':
nextart:
				ng->ng_touched = 1;
				if (a->a_forw && a->a_forw->a_screen == scr) {
					a = a->a_forw;
					art++;
					if (autoview)
						preview(a);
					toarticle(art);
					fflush(stdout);
				}
				break;
			/*
			 * Previous article.
			 */
			case 'k':
			case '-':
				if (a->a_back) {
					a = a->a_back;
					art--;
					if (autoview)
						preview(a);
					toarticle(art);
					fflush(stdout);
				}
				break;
			/*
			 * Next screenful of articles.
			 */
			case 'n':
				ng->ng_touched = 1;
				if (scr < maxscreens(ng) ||
				    gatherarticles(ng) != 0) {
					scr++;
					goto startover;
				}
				else {
					message("No more screenfuls.", 0);
					toarticle(art);
				}
				break;
			/*
			 * Previous screenful of articles.
			 */
			case 'p':
				if (scr == 1) {
					message("No previous screenful.", 0);
					toarticle(art);
				}
				else {
					scr--;
					goto startover;
				}
				break;
			/*
			 * Select article for reading.
			 */
			case 's':
				markarticle(art, '+');
				fflush(stdout);
				ng->ng_touched = 1;
				a->a_select = LETTER;
				goto nextart;
			/*
			 * Unselect article.
			 */
			case 'u':
				if (a->a_read)
					markarticle(art, '*');
				else
					markarticle(art, ' ');
				fflush(stdout);
				a->a_select = 0;
				break;
			/*
			 * Preview an article.
			 */
			case 'v':
				if (!autoview) {
					preview(a);
					toarticle(art);
				}
				break;
		}
	}
out:
	unsetty();
	clearscreen();
	fflush(stdout);
	a = ng->ng_start;
realtop:
	quit = noadvance = 0;
	for (; a != NULL && !quit; a = a->a_forw) {
		if (!a->a_select)
			continue;
		a->a_read = a->a_select;
top:
		if (a->a_select == LETTER)
			prarticle(ng, a);
		else if (a->a_select == DECODE)
			caesar(ng, a, 13);
		else
			prdigest(a);
again:
		printf(": ");
		fflush(stdout);
		gets(buf);
		switch (buf[0]) {
			case '\0':
				break;
			case '?':
				dohelp();
				goto again;
			case '#':
				printf("%d article%c in %s\n",
				    ng->ng_narticles,
				    ng->ng_narticles > 1 ? 's' : 0,
				    ng->ng_name);
				goto again;
			/*
			 * If we can't go back two articles, just repeat
			 * the last article.
			 */
			case '-':
				if ((lasta = prevarticle(ng, a)) != NULL)
					a = lasta;
				goto top;
			/*
			 * Reprint article with decoding.
			 */
			case 'c':
				p = index(buf, ' ');
				if (p == NULL)
					rot = 13;
				else {
					rot = atoi(p);
					if (rot < 0)
						rot = 13;
				}
				caesar(ng, a, rot);
				goto again;
			/*
			 * Exit to same newsgroup.
			 */
			case 'e':
				noadvance++;
				goto nomore;
			/*
			 * Submit a followup article.
			 */
			case 'f':
				followup(a);
				goto again;
			/*
			 * Reprint this article.
			 */
			case 'p':
				goto top;
			case 'q':
				quit++;
				break;
			/*
			 * Compose a mail reply.
			 */
			case 'r':
				reply(a);
				goto again;
			case 's':
				savearticle(a, buf);
				goto again;
			default:
				printf("%s: No such command.  Type ? for help.\n", buf);
				goto again;
		}
	}
nomore:
	/*
	 * Mark articles as read, but not selected.
	 */
	for (a = ng->ng_start; a != NULL; a = a->a_forw) {
		if (a->a_read && a->a_select)
			a->a_select = 0;
	}
	/*
	 * Figure out what newsgroup to display next.
	 */
	if (!noadvance && ng != curng && (lastn = nextng(ng)) != NULL) {
		ng = lastn;
		scr = 1;
		goto startover;
	}
	else if (noadvance || ng == lastng)
		goto startover;
}
!
echo x - checknews.c
cat << ! > checknews.c
/*
 * checknews - Check to see if there is news.
 *
 * Author:	Anthony V. Discolo
 *		Computer Systems Lab
 *		University of California
 *		Santa Barbara, CA 93106
 *		(805) 961-4178
 *
 * Disclaimer:	I take no responsibility whatsoever for the condition
 *		of this software.  Unrestricted use is hereby granted
 *		as long as this header remains intact.
 */
#include <stdio.h>
#include <pwd.h>
#include <sys/param.h>
#include "rn.h"

int yes;
int no;
int silent;
int isnews;

main(argc, argv)
int argc;
char *argv[];
{
	char *p;

	if (argc == 1)
		yes++;
	while (--argc && (*++argv)[0] == '-') {
		switch ((*argv)[1]) {
			case 'n':
				no++;
				break;
			case 'q':
				silent++;
				break;
			case 'y':
				yes++;
				break;
			default:
				usage();
		}
	}
	pw = getpwuid(getuid());
	if (pw == NULL) {
		printf("Who are you?\n");
		exit(1);
	}
	if ((p = getenv("NEWSRC")) != NULL)
		strcpy(newsrc, p);
	else
		sprintf(newsrc, "%s/.newsrc", pw->pw_dir);
	if ((rcf = fopen(newsrc, "r")) == NULL) {
		perror(newsrc);
		exit(1);
	}
	readrc();
	fclose(rcf);
	readactive();
	docheck();
}

docheck() {
	register struct newsgroup *ng;

	while ((ng = getng()) != NULL) {
		if (!ng->ng_unsubscribe && ng->ng_begid < angval(ng->ng_name)) {
			isnews++;
			break;
		}
	}
	if (isnews) {
		if (yes) {
			printf("There is news.\n");
			exit(0);
		}
		else if (silent)
			exit(1);
	}
	else {
		if (no) {
			printf("No news is good news.\n");
			exit(0);
		}
		else if (silent)
			exit(0);
	}
}

usage() {
	printf("Usage: checknews [ -nyq ]\n");
	exit(1);
}
!
echo x - convrc.c
cat << ! > convrc.c
/*
 * convrc - Convert to new style .newsrc file.
 */
#include <stdio.h>
#include <ctype.h>

main(argc, argv)
int argc;
char *argv[];
{
	FILE *f;
	int maxid;
	char buf[BUFSIZ], ng[40], *p;

	if (argc != 2) {
		printf("Usage: convrc file\n");
		exit(1);
	}
	if ((f = fopen(argv[1], "r")) == NULL) {
		perror(argv[1]);
		exit(1);
	}
	while (fgets(buf, sizeof(buf), f) != NULL) {
		sscanf(buf, "%s", ng);
		if (strcmp(ng, "options") == 0 || buf[0] == '\t')
			continue;
		p = &buf[strlen(buf) - 2];
		while (isdigit(*p))
			--p;
		p++;
		maxid = atoi(p);
		printf("%s %d\n", ng, maxid);
	}
	fclose(f);
}
!
echo x - digest.c
cat << ! > digest.c
/*
 * digest - Digest routines.
 */
#include <stdio.h>
#include <ctype.h>
#include <sys/param.h>
#include "rn.h"

/*
 * Print a digest.
 */
prdigest(a)
struct article *a;
{
	FILE *f;
	int fpos, dtotal, quit;
	char *p, buf[BUFSIZ];
	struct article *digest, *curart, *lastart;

	if ((f = openarticle(a->a_id)) == NULL) {
		perror("digest");
		return;
	}
	dtotal = 0;
	digest = NULL;
	fseek(f, a->a_text, 0);
	/*
	 * Scan digest, gathering articles.
	 */
	for (;;) {
		while ((p = fgets(buf, sizeof(buf), f)) != NULL &&
		    !isheader(buf))
			;
		if (p == NULL)
			break;
		curart = aralloc();
		if (digest == NULL) {
			digest = curart;
			digest->a_back = NULL;
		}
		else {
			lastart->a_forw = curart;
			curart->a_back = lastart;
		}
		curart->a_id = a->a_id;
		curart->a_screen = ++dtotal;	/* screen = digest article # */
		curart->a_forw = NULL;
		do {
			buf[strlen(buf) - 1] = 0;
			if (strncmp(buf, "Date: ", 6) == 0)
				strncpy(curart->a_date, buf + 6,
				    sizeof(curart->a_date));
			else if (strncmp(buf, "From: ", 6) == 0)
				strncpy(curart->a_from, buf + 6,
				    sizeof(curart->a_from));
			else if (strncmp(buf, "Subject: ", 9) == 0)
				strncpy(curart->a_subj, buf + 9,
				    sizeof(curart->a_subj));
			else if (strncmp(buf, "To: ", 4) == 0)
				;	/* XXX */
			else if (strncmp(buf, "Cc: ", 4) == 0)
				;	/* XXX */
			fpos = ftell(f);
			if (fgets(buf, sizeof(buf), f) == NULL)
				break;
		} while (isheader(buf));
		curart->a_text = fpos;
		lastart = curart;
	}
	fseek(f, a->a_text, 0);
	/*
	 * Put out introductory ditty.
	 */
	while (fgets(buf, sizeof(buf), f) != NULL && !isheader(buf))
		fputs(buf, stdout);
	quit = 0;
	for (curart = digest; curart != NULL && !quit; curart = curart->a_forw) {
top:
		printf("\nDigest article %d of %d\n", curart->a_screen, dtotal);
		printf("Date: %s\n", curart->a_date);
		printf("From: %s\n", curart->a_from);
		printf("Subject: %s\n", curart->a_subj);
		printf("More? [ynq] ");
		gets(buf);
		switch (buf[0]) {
			case '-':
				if (curart->a_back != NULL)
					curart = curart->a_back;
				else
					printf("Can't go back.\n");
				goto top;
				break;
			case 'n':
				break;
			case 'q':
				quit++;
				break;
			case 's':
				savedarticle(curart, a, buf);
				break;
			case '\0':
			case 'y':
				prdarticle(curart, dtotal);
				if (curart->a_screen != dtotal) {
					printf(": ");
					fflush(stdout);
					gets(buf);
					if (buf[0] == 's')
						savedarticle(curart, a, buf);
				}
				else {
					printf("\nLast digest article? [-sq] ");
					fflush(stdout);
					gets(buf);
					if (buf[0] == '-')
						goto top;
					else if (buf[0] == 's')
						savedarticle(curart, a, buf);
				}
				break;
		}
	}
}

/*
 * Print digest article.
 */
prdarticle(a, tot)
struct article *a;
int tot;
{
	FILE *p, *f;
	char cmd[200], buf[BUFSIZ];

	if (strcmp(pager, PAGER) == 0)
		sprintf(cmd, "%s -p -h \"[Digest %d/%d %-23.23s %s]\"", pager,
		    a->a_screen, tot, justaddr(a->a_from),
		    fixsubj(a->a_subj, 38));
	else
		strcpy(cmd, pager);
	if ((p = popen(cmd, "w")) == NULL) {
		perror("pager");
		return;
	}
	if ((f = openarticle(a->a_id)) == NULL) {
		perror("article");
		return;
	}
	fseek(f, a->a_text, 0);
	while (fgets(buf, sizeof(buf), f) != NULL && !isheader(buf))
		fputs(buf, p);
	fclose(f);
	pclose(p);
}

savedarticle(a, ap, xname)
struct article *a;
struct article *ap;
char *xname;
{
	FILE *r, *w;
	short append;
	register int lines, chars;
	char *name, buf[BUFSIZ], cwd[MAXPATHLEN];

	if ((name = index(xname, ' ')) == NULL) {
oops:
		printf("Save requires a filename.\n");
		return;
	}
	name++;
	if (strlen(name) == 0)
		goto oops;
	if ((r = openarticle(a->a_id)) == NULL) {
		perror("article");
		return;
	}
	if (getwd(cwd) == 0) {
		fclose(r);
		puts(cwd);
		return;
	}
	cd(homedir);
	append = !access(name, 0);
	if ((w = fopen(name, "a")) == NULL) {
		perror(name);
		fclose(r);
		cd(cwd);
		return;
	}
	fprintf(w, "Digest article %d in %s/%d\n", a->a_screen,
	    curng->ng_name, ap->a_id);
	fprintf(w, "Date: %s\n", a->a_date);
	fprintf(w, "From: %s\n", a->a_from);
	fprintf(w, "Subject: %s\n", a->a_subj);
	fseek(r, a->a_text, 0);
	lines = chars = 0;
	while (fgets(buf, sizeof(buf), r) != NULL && !isheader(buf)) {
		lines++;
		chars += strlen(buf);
		fputs(buf, w);
	}
	cd(cwd);
	fclose(w);
	fclose(r);
	printf("\"%s\" [%s] %d/%d\n", name, append ? "Appended" : "New file",
	    lines, chars);
}

isheader(s)
char *s;
{
	register char *p;
	
	p = s;
	while (*p && isalpha(*p))
		p++;
	if (*p == ':') {
		return(strncmp(s, "Date: ", 6) == 0 ||
		    strncmp(s, "From: ", 6) == 0 ||
		    strncmp(s, "To: ", 4) == 0 ||
		    strncmp(s, "Cc: ", 4) == 0 ||
		    strncmp(s, "Subject: ", 9) == 0);
	}
	return(0);
}
!
echo x - inotab.c
cat << ! > inotab.c
/*
 * inotab - Inode hash table routines.
 */
#include <stdio.h>
#include <sys/param.h>
#include "rn.h"

/*
 * Insert inode into hashed table.
 */
itinsert(ino)
long ino;
{
	int hval;
	register struct inotab *it;

	hval = ino % ITABSIZ;
	it = itab[hval];
	if (it == NULL)
		it = itab[hval] = italloc();
	else {
		while (it->i_next != NULL)
			it = it->i_next;
		it->i_next = italloc();
		it = it->i_next;
	}
	it->i_ino = ino;
	it->i_next = NULL;
}

/*
 * Return whether an inode has been seen yet.
 */
itval(ino)
long ino;
{
	int hval;
	register struct inotab *it;

	hval = ino % ITABSIZ;
	it = itab[hval];
	if (it == NULL) {
		return(0);
	}
	while (it != NULL) {
		if (it->i_ino == ino) {
			return(1);
		}
		it = it->i_next;
	}
	return(0);
}

struct inotab *italloc() {
	char *malloc();
	struct inotab *it;

	it = (struct inotab *) malloc(sizeof(struct inotab));
	if (it == NULL) {
		printf("italloc: No more memory\n");
		exit(1);
	}
	return(it);
}
!
echo x - misc1.c
cat << ! > misc1.c
/*
 * misc1 - Miscellaneous routines.
 */
#include <stdio.h>
#include <ctype.h>
#include <sys/param.h>
#include "rn.h"

/*
 * Read and store .newsrc.
 */
readrc() {
	register int i;
	char buf[BUFSIZ];

	i = 0;
	while (fgets(buf, sizeof(buf), rcf) != NULL) {
		buf[strlen(buf) - 1] = 0;
		rcngs[i++] = strsave(buf);
	}
	rcngs[i] = NULL;
	ngindex = 0;
}

/*
 * Create a new .newsrc for the user.  Reopen path
 * for reading on rcf file descriptor.
 */
newrc(path)
char *path;
{
	FILE *a;
	char ng[40];

	if ((a = fopen(ACTIVE, "r")) == NULL) {
		perror(ACTIVE);
		exit(1);
	}
	if ((rcf = fopen(path, "w")) == NULL) {
		perror(path);
		exit(1);
	}
	while (fscanf(a, "%s%*d", ng) != EOF)
		fprintf(rcf, "%s 0\n", ng);
	fclose(rcf);
	fclose(a);
	rcf = fopen(path, "r");
}

message(s, a, b, c, d, e, f, g)
char *s;
{
	tgo(ttylines - 1, 0);
	fflush(stdout);
	clearline();
	inverseon();
	printf(s, a, b, c, d, e, f, g);
	inverseoff();
	fflush(stdout);
}

/*
 * Transform an integer into a string.
 */
char *idtoname(id)
int id;
{
	static char buf[20];

	sprintf(buf, "%d", id);
	return(buf);
}

/*
 * Add a newsgroup name to the newsgroup list
 * gathered from argv.
 */
addng(ngname)
char *ngname;
{
	static char **ngptr;

	if (ngptr == 0)
		ngptr = &nglist[0];
	*ngptr++ = ngname;
}

/*
 * Fake a .newsrc for argv supplied newsgroups.
 */
fakenrc() {
	register char **p;

	sprintf(fakerc, "/tmp/news%06d", getpid());
	if ((rcf = fopen(fakerc, "w")) == NULL) {
		perror(fakerc);
		exit(1);
	}
	for (p = nglist; *p != NULL; p++)
		fprintf(rcf, "%s: %d\n", *p, getlastart(*p));
	fclose(rcf);
	rcf = fopen(fakerc, "r");
}

/*
 * Return the last article number for a newsgroup in the
 * .newsrc, or 0 if .newsrc or newsgroup doesn't exist.
 * This is slow, so hopefully we won't have to do it a lot.
 */
getlastart(ngname)
char *ngname;
{
	FILE *f;
	int ngid;
	char ngnm[40];

	if ((f = fopen(newsrc, "r")) == NULL)
		return(0);
	while (fscanf(f, "%s%d", ngnm, &ngid) != EOF) {
		if (strncmp(ngnm, ngname, strlen(ngname)) == 0)
			return(ngid);
	}
	return(0);
}

/*
 * Replace '.' with '/' to make filename.
 */
char *ngtopath(s)
register char *s;
{
	register char *p;
	static char buf[BUFSIZ];

	p = buf;
	while (*s) {
		if (*s == '.')
			*p = '/';
		else
			*p = *s;
		p++;
		s++;
	}
	*p = 0;
	return(buf);
}

/*
 * Figure out the number of screens in a newsgroup.
 */
maxscreens(ng)
struct newsgroup *ng;
{
	short s, r;

	s = ng->ng_narticles / screenful;
	r = ng->ng_narticles % screenful;
	return(r ? s + 1 : s);
}

char *prchar(c)
char c;
{
	static char prbuf[10];

	if (c == ' ')
		strcpy(prbuf, "<blank>");
	else if (iscntrl(c))
		sprintf(prbuf, "^%c", c + '@');
	else
		sprintf(prbuf, "%c", c);	/* stupid, I know */
	return(prbuf);
}

char *strsave(s)
char *s;
{
	char *p, *malloc();

	p = malloc(strlen(s) + 1);
	if (p == NULL) {
		printf("alloc: No more memory\n");
		exit(1);
	}
	strcpy(p, s);
	return(p);
}

trim(s, len)
register char *s;
register int len;
{
	register int l = 0;

	while (*s) {
		if (l == len) {
			*s++ = '\n';
			*s = 0;
			return;
		}
		else if (l > len) {
			*(s - 1) = '\n';
			*++s = 0;
			return;
		}
		if (*s == '\t')
			l += 8 - (l % 8);
		s++;
		l++;
	}
}

cd(dir)
char *dir;
{
	if (chdir(dir) < 0) {
		message("panic: Can't chdir to %s", dir, 0);
		exit(1);
	}
}

done(nowrite)
short nowrite;
{
	FILE *f;
	int ngid;
	register char **p;
	char xnewsrc[MAXPATHLEN], ng[80];
	register struct newsgroup *n;

	unsetty();
	tgo(ttylines - 1, 0);
	clearline();
	fflush(stdout);
	fclose(rcf);
	if (nglist[0] != NULL)
		unlink(fakerc);
	if (nowrite || ignorerc)
		goto xit;
	cd(homedir);
	if (nglist[0] != NULL) {
		sprintf(xnewsrc, "%s.new", newsrc);
		if ((rcf = fopen(newsrc, "r")) == NULL) {
			perror(newsrc);
			exit(1);
		}
		if ((f = fopen(xnewsrc, "w")) == NULL) {
			perror(xnewsrc);
			exit(1);
		}
		while (fscanf(rcf, "%s%d", ng, &ngid) != EOF) {
			for (n = newsgroups; n != NULL; n = n->ng_forw) {
				if (strncmp(ng, n->ng_name, strlen(ng) - 1) == 0)
					break;
			}
			if (n != NULL) {
				fprintf(f, "%s: %d\n", n->ng_name,
				    n->ng_lastid);
			}
			else {
				fprintf(f, "%s %d\n", ng, ngid);
			}
		}
		fclose(f);
		fclose(rcf);
		if (rename(xnewsrc, newsrc) < 0)
			perror("rename");
	}
	else {
		if ((rcf = fopen(newsrc, "w")) == NULL) {
			perror(newsrc);
			exit(1);
		}
		/*
		 * Write out all newsgroups that have been read.
		 */
		for (n = newsgroups, p = rcngs; n != NULL;
		    n = n->ng_forw, p++) {
			if (n->ng_touched) {
				fprintf(rcf, "%s%c %d\n", n->ng_name,
				    n->ng_unsubscribe ? '!' : ':', n->ng_lastid);
			}
			else
				fprintf(rcf, "%s\n", *p);
		}
		/*
		 * Write out any unread newsgroups.
		 */
		for (; *p != 0; p++)
			fprintf(rcf, "%s\n", *p);
		fclose(rcf);
	}
xit:
	exit(0);
}
!
echo x - misc2.c
cat << ! > misc2.c
/*
 * misc2 - Miscellaneous article routines.
 */
#include <stdio.h>
#include <sys/param.h>
#include <sys/stat.h>
#include "rn.h"

/*
 * Submit a followup article.
 */
followup(a)
struct article *a;
{
	FILE *f;
	char buf[BUFSIZ], refs[80], id[40], temp[100];
	struct stat sbuf;

	printf("Posting followup article to network.  Please use\n");
	printf("reply ('r') instead unless your article is of general\n");
	printf("interest.\n");
	printf("\nDo you want to continue? [yn] ");
	gets(buf);
	if (buf[0] == 'n')
		return;
	if ((af = openarticle(a->a_id)) == NULL) {
		perror("article");
		return;
	}
	strncpy(refs, getheader("References"), sizeof(refs));
	strncpy(id, getheader("Message-ID"), sizeof(id));
	fclose(af);
	sprintf(temp, "/tmp/followup%06d", getpid());
	if ((f = fopen(temp, "w")) == NULL) {
		perror(temp);
		return;
	}
	fprintf(f, "Newsgroups: %s\n", a->a_ngs);
	fprintf(f, "Subject: ");
	if (strncmp(a->a_subj, "Re:", 3) != 0)
		fprintf(f, "Re: ");
	fprintf(f, "%s\n", a->a_subj);
	fprintf(f, "References: ");
	if (strlen(refs))
		fprintf(f, "%s, ", refs);
	fprintf(f, "%s\n", id);
	if (strlen(a->a_keywds))
		fprintf(f, "Keywords: %s\n", a->a_keywds);
	fclose(f);
	sprintf(buf, "%s %s", editor, temp);
	system(buf);
	if (stat(temp, &sbuf) < 0 || sbuf.st_size < 15)
		printf("Followup article mangled.  Article not posted.\n");
	else {
		sprintf(buf, "/usr/bin/inews -h -D < %s", temp);
		system(buf);
		printf("Article posted.\n");
	}
	unlink(temp);
}

/*
 * Reply via mail to an article.
 */
reply(a)
struct article *a;
{
	FILE *f;
	register char *p;
	char *rindex(), buf[BUFSIZ], path[BUFSIZ], refs[80], id[40], temp[100];
	struct stat sbuf;

	if ((af = openarticle(a->a_id)) == NULL) {
		perror("article");
		return;
	}
	strncpy(path, getheader("Path"), sizeof(path));
	if (strlen(path) == 0) {
		printf("Can't find return address.  Sorry.\n");
		fclose(af);
		return;
	}
	strncpy(refs, getheader("References"), sizeof(refs));
	strncpy(id, getheader("Message-ID"), sizeof(id));
	sprintf(temp, "/tmp/followup%06d", getpid());
	if ((f = fopen(temp, "w")) == NULL) {
		perror(temp);
		return;
	}
	p = index(path, '!');
	if (p == NULL) {
		printf("Return address too short.  Sorry.\n");
		fclose(af);
		return;
	}
	fprintf(f, "To: %s\n", p + 1);
	fprintf(f, "Subject: ");
	if (strncmp(a->a_subj, "Re:", 3) != 0)
		fprintf(f, "Re: ");
	fprintf(f, "%s\n", a->a_subj);
	fprintf(f, "Newsgroups: %s\n", a->a_ngs);
	fprintf(f, "References: ");
	if (strlen(refs))
		fprintf(f, "%s, ", refs);
	fprintf(f, "%s\n", id);
	if (strlen(a->a_keywds))
		fprintf(f, "Keywords: %s\n", a->a_keywds);
	fclose(f);
	p = rindex(mailer, '/');
	/*
	 * \`\`Regular'' mailer.
	 */
	if (p == NULL || strcmp(p + 1, "comp") != 0) {
		sprintf(buf, "%s %s", editor, temp);
		system(buf);
		if (stat(temp, &sbuf) < 0 || sbuf.st_size < 15) {
			printf("Reply article mangled.  Article not delivered.\n");
			return;
		}
		else
			sprintf(buf, "%s < %s", mailer, temp);
	}
	/*
	 * MH mailer.
	 */
	else
		sprintf(buf, "%s -form %s", mailer, temp);
	if (system(buf) == 0)
		printf("Reply sent.\n");
	unlink(temp);
}

/*
 * Set up the screen for selection.
 */
screensetup(ng, scr)
struct newsgroup *ng;
short scr;
{
	register int i;

	clearscreen();
	printf("Newsgroup: %s: Page %d%c\n", ng->ng_name, scr,
	     ng->ng_lastid == angval(ng->ng_name) ? 0 : '+');
	tgo(ttylines - (navlines + 2), 0);
	for (i = 0; i < ttycols; i++)
		putchar('-');
	tgo(1, 0);
}

/*
 * List all articles in this screenful.
 */
listarticles(a)
register struct article *a;
{
	short s;

	s = a->a_screen;
	while (a != NULL && a->a_screen == s) {
		printf(" %25.25s %c %-.48s\n", justaddr(a->a_from),
		   artstatus(a), a->a_subj);
		a = a->a_forw;
	}
}

/*
 * Print an article.
 */
prarticle(ng, a)
struct newsgroup *ng;
struct article *a;
{
	FILE *p, *f;
	register int i;
	char cmd[200], buf[BUFSIZ];

	if (strcmp(pager, PAGER) == 0)
		sprintf(cmd, "%s -p -h \"[%s/%d %-20.20s %s]\"", pager,
		    ng->ng_name, a->a_id, justaddr(a->a_from),
		    fixsubj(a->a_subj, 30));
	else
		strcpy(cmd, pager);
	if ((p = popen(cmd, "w")) == NULL) {
		perror("pager");
		return;
	}
	if ((f = openarticle(a->a_id)) == NULL) {
		perror("article");
		return;
	}
	fseek(f, a->a_text, 0);
	while (fgets(buf, sizeof(buf), f) != NULL)
		fputs(buf, p);
	fclose(f);
	pclose(p);
}

/*
 * Caesar decoding.
 */
caesar(ng, a, rot)
struct newsgroup *ng;
struct article *a;
int rot;
{
	FILE *p, *f;
	int n;
	char cmd[200], buf[BUFSIZ];

	if (strcmp(pager, PAGER) == 0)
		sprintf(cmd, "%s %d | %s -p -h \"[%s/%d %-20.20s %s]\"",
		    CAESAR, rot, pager, ng->ng_name, a->a_id,
		    justaddr(a->a_from), fixsubj(a->a_subj, 30));
	else
		sprintf(cmd, "%s %d | %s", CAESAR, rot, pager);
	if ((p = popen(cmd, "w")) == NULL) {
		perror("caesar");
		return;
	}
	if ((f = openarticle(a->a_id)) == NULL) {
		perror("article");
		return;
	}
	fseek(f, a->a_text, 0);
	while (fgets(buf, sizeof(buf), f) != NULL)
		fputs(buf, p);
	fclose(f);
	pclose(p);
	
}

/*
 * Put the first few lines of the article at
 * the bottom of the screen.
 */
preview(a)
struct article *a;
{
	FILE *ar;
	register int i;
	char buf[BUFSIZ];

	cooked();
	tgo(ttylines - (navlines + 1), 0);
	if ((ar = openarticle(a->a_id)) != NULL) {
		fseek(ar, a->a_text, 0);
		while (fgets(buf, sizeof(buf), ar) != NULL && strlen(buf) == 1)
			;
		clearline();
		trim(buf, ttycols - 1);
		fputs(buf, stdout);
		for (i = 0; i < navlines - 1; i++) {
			clearline();
			if (fgets(buf, sizeof(buf), ar) != NULL) {
				trim(buf, ttycols - 1);
				fputs(buf, stdout);
			}
			else
				putchar('\n');
		}
		fclose(ar);
	}
	fflush(stdout);
	raw();
}

savearticle(a, xname)
struct article *a;
char *xname;
{
	FILE *r, *w;
	short append;
	register int lines, chars;
	char *name, buf[BUFSIZ], cwd[MAXPATHLEN];

	if ((name = index(xname, ' ')) == NULL) {
oops:
		printf("Save requires a filename.\n");
		return;
	}
	name++;
	if (strlen(name) == 0)
		goto oops;
	if ((r = openarticle(a->a_id)) == NULL) {
		perror("article");
		return;
	}
	if (getwd(cwd) == 0) {
		fclose(r);
		puts(cwd);
		return;
	}
	cd(homedir);
	append = !access(name, 0);
	if ((w = fopen(name, "a")) == NULL) {
		perror(name);
		fclose(r);
		cd(cwd);
		return;
	}
	lines = chars = 0;
	while (fgets(buf, sizeof(buf), r) != NULL) {
		fputs(buf, w);
		lines++;
		chars += strlen(buf);
	}
	cd(cwd);
	fclose(w);
	fclose(r);
	printf("\"%s\" [%s] %d/%d\n", name, append ? "Appended" : "New file",
	    lines, chars);
}

markarticle(art, c)
short art;
char c;
{
	toarticle(art);
	putchar(c);
	putchar('\b');
}

/*
 * Give user minimal help.
 */
dohelp() {
	printf("Commands are:\n");
	printf(" \\\n		Print next article\n");
	printf(" c [ rot ]	Decode last article\n");
	printf(" e		Quit, return to same newsgroup\n");
	printf(" f		Submit followup article\n");
	printf(" p		Reprint this article\n");
	printf(" q		Quit, go to next newsgroup\n");
	printf(" r		Reply to article via mail\n");
	printf(" s <filename> 	Save article in a file\n");
	printf(" -		Go to previous article\n");
}

/*
 * Get the next newsgroup from .newsrc.
 */
struct newsgroup *getng() {
	int len;
	char *p, *malloc();
	struct newsgroup *n;

	n = (struct newsgroup *) malloc(sizeof(struct newsgroup));
	if (n == NULL) {
		printf("getng: No more memory\n");
		exit(1);
	}
next:
	if ((p = rcngs[ngindex++]) == NULL)
		return(NULL);
	if (rcngs[ngindex] == NULL)
		lastng = n;
	if (sscanf(p, "%s%d", n->ng_name, &n->ng_begid) != 2)
		return(NULL);
	len = strlen(n->ng_name);
	if (n->ng_name[len - 1] == '!') {
		n->ng_unsubscribe++;
		n->ng_lastid = n->ng_begid;
	}
	else if (n->ng_name[len - 1] != ':') {
		fprintf(stderr, "Illegal .newsrc line: \"%s\"\n", p);
		sleep(2);
		goto next;
	}
	n->ng_name[len - 1] = 0;	/* remove [:!] */
	if (ignorerc)
		n->ng_begid = 0;
	n->ng_narticles = n->ng_touched = 0;
	n->ng_lastid = n->ng_begid;
	strcpy(n->ng_dir, "/usr/spool/news/");
	strcat(n->ng_dir, ngtopath(n->ng_name));
	return(n);
}

/*
 * Get the named header from an article.
 */
char *getheader(xhname)
char *xhname;
{
	int len;
	static char buf[BUFSIZ];
	char hname[30];

	sprintf(hname, "%s: ", xhname);
	len = strlen(hname);
	rewind(af);
	while (fgets(buf, sizeof(buf), af) != NULL && strlen(buf) > 1) {
		if (strncmp(buf, hname, strlen(hname)) == 0) {
			buf[strlen(buf) - 1] = 0;
			return(&buf[len]);
		}
	}
	return("");
}

/*
 * See if we can go back one article
 * in the article list.  If the argument is
 * NULL, return the last article.
 */
struct article *prevarticle(ng, a)
struct newsgroup *ng;
register struct article *a;
{
	if (a == NULL) {
		a = ng->ng_start;
		while (a->a_forw != NULL)
			a = a->a_forw;
	}
	else
		a = a->a_back;
	while (a != NULL && !a->a_read && !a->a_select)
		a = a->a_back;
	return(a);
}

/*
 * Find the next readable article.
 */
struct article *nextarticle(a)
register struct article *a;
{
	if (a == NULL)
		return(NULL);
	a = a->a_forw;
	while (a != NULL && !a->a_select)
		a = a->a_forw;
	return(a);
}

/*
 * See if we can go back one newsgroup.  If
 * the argument is NULL, return the last newsgroup.
 */
struct newsgroup *prevng(ng)
register struct newsgroup *ng;
{
	if (ng == NULL) {
		ng = newsgroups;
		while (ng->ng_forw != NULL)
			ng = ng->ng_forw;
	}
	else
		ng = ng->ng_back;
	while (ng != NULL && (!ng->ng_narticles || ng->ng_unsubscribe))
		ng = ng->ng_back;
	return(ng);
}

/*
 * See if we can go forward one newsgroup.
 */
struct newsgroup *nextng(ng)
register struct newsgroup *ng;
{
	ng = ng->ng_forw;
	while (ng != NULL && (!ng->ng_narticles || ng->ng_unsubscribe))
		ng = ng->ng_forw;
	return(ng);
}

/*
 * Print article header.
 */
prartheader(ng, a)
struct newsgroup *ng;
struct article *a;
{
	printf("\nArticle %d of %d\n", a->a_id, ng->ng_lastid);
	printf("Date: %s\n", a->a_date);
	printf("From: %s\n", a->a_from);
	printf("Subject: %s\n", a->a_subj);
	if (strlen(a->a_keywds))
		printf("Keywords: %s\n", a->a_keywds);
	printf("Newsgroups: %s\n", a->a_ngs);
}

/*
 * Strip off real name, etc.
 */
char *justaddr(fromline)
char *fromline;
{
	register char *p, *q;
	static char buf[100];

	if ((q = index(fromline, '!')) != NULL ||
	    (q = index(fromline, '@')) != NULL) {
		while (*q != ' ' && *q != '<' && q != fromline)
			--q;
		if (*q == ' ' || *q == '<')
			q++;
		strcpy(buf, q);
		p = buf;
		while (*p && *p != ' ' && *p != '\t' && *p != '>')
			p++;
		*p = 0;
		return(buf);
	}
	else
		return(fromline);
}

/*
 * Figure out where the beginning of the article is.
 */
textstart() {
	register int len, cnt = 0;
	char buf[BUFSIZ];

	rewind(af);
	while (fgets(buf, sizeof(buf), af) != NULL) {
		if ((len = strlen(buf)) == 1)
			break;
		cnt += len;
	}
	return(cnt + 1);
}

/*
 * Return true if an article is selected
 * for reading.
 */
selections(ng)
struct newsgroup *ng;
{
	register struct article *a;

	for (a = ng->ng_start; a != NULL; a = a->a_forw) {
		if (a->a_select)
			return(1);
	}
	return(0);
}

artstatus(a)
struct article *a;
{
	if (a->a_select == LETTER)
		return('+');
	else if (a->a_select == DIGEST)
		return('d');
	else if (a->a_select == DECODE)
		return('c');
	else if (a->a_read)
		return('*');
	return(' ');
}

/*
 * An article memory allocator.
 */
struct article *aralloc() {
	char *malloc();
	struct article *a;

	a = (struct article *) malloc(sizeof(struct article));
	if (a == NULL) {
		printf("aralloc: No more memory\n");
		exit(1);
	}
	return(a);
}

/*
 * Have to escape all funny characters in the subject,
 * and fill out (or truncate) to len characters.
 */
char *fixsubj(s, len)
char *s;
unsigned len;
{
	register int i, fudge;
	register char *p, *q;
	static char subj[200];

	q = subj;
	fudge = 0;
	for (p = s; *p != NULL; p++) {
		if (*p == '"' || *p == '$' || *p == '\\\') {
			*q++ = '\\\';
			fudge++;
		}
		*q++ = *p;
	}
	subj[len + fudge] = 0;
	p = &subj[len + fudge - 1];
	while (p >= q)
		*p-- = ' ';
	return(subj);
}

dosuspend(ng, scr) 
struct newsgroup *ng;
short scr;
{
	register struct article *a;

	tgo(ttylines - 1, 0);
	clearline();
	unsetty();
	fflush(stdout);
	kill(0, SIGTSTP);
	screensetup(ng, scr);
	a = ng->ng_start;
	while (a->a_screen != scr)
		a = a->a_forw;
	listarticles(a);
	setty();
}
!
echo x - newsrc.example
cat << ! > newsrc.example
general: 33
ca.news.group: 5
ca.wanted: 6
fa.arpa-bboard: 134
fa.human-nets: 48
fa.info-mac: 1306
fa.info-terms: 1
fa.tcp-ip: 1
fa.telecom: 97
la.eats: 23
mod.map.uucp: 12
mod.movies: 14
mod.sources: 47
mod.std: 1
mod.unix: 30
net.bugs.4bsd: 460
net.bugs.v7: 20
net.bugs: 232
net.dcom: 336
net.games.rogue: 856
net.general: 481
net.info-terms: 195
net.jobs: 442
net.jokes: 3302
net.lan: 331
net.lang.st80: 108
net.mail: 236
net.micro.68k: 338
net.micro.mac: 888
net.micro: 1917
net.music.folk: 139
net.news.adm: 127
net.news.b: 281
net.news: 1021
net.research: 52
net.rumor: 246
net.sources.bugs: 244
net.sources.games: 19
net.sources: 1158
net.unix-wizards: 3331
net.unix: 1831
net.usenix: 146
net.usoft: 32
net.wanted.sources: 636
!
echo x - pager.diffs
cat << ! > pager.diffs
*** newspager.c	Thu May  2 11:48:31 1985
--- /usr/src/ucb/more/more.c	Fri Mar 30 09:14:56 1984
***************
*** 9,16
  **
  **	modified by Geoff Peck, UCB to add underlining, single spacing
  **	modified by John Foderaro, UCB to add -c and MORE environment variable
- **
- **	modified by Anthony Discolo, UCSB to add -h option for news.
  */
  
  #include <stdio.h>

--- 9,14 -----
  **
  **	modified by Geoff Peck, UCB to add underlining, single spacing
  **	modified by John Foderaro, UCB to add -c and MORE environment variable
  */
  
  #include <stdio.h>
***************
*** 64,70
  int		catch_susp;	/* We should catch the SIGTSTP signal */
  char		**fnames;	/* The list of file names */
  int		nfiles;		/* Number of files left to process */
- char		*header;	/* Print out at top of page */
  char		*shell;		/* The name of the shell to use */
  int		shellp;		/* A previous shell command exists */
  char		ch;

--- 62,67 -----
  int		catch_susp;	/* We should catch the SIGTSTP signal */
  char		**fnames;	/* The list of file names */
  int		nfiles;		/* Number of files left to process */
  char		*shell;		/* The name of the shell to use */
  int		shellp;		/* A previous shell command exists */
  char		ch;
***************
*** 116,129
      initterm ();
      if(s = getenv("MORE")) argscan(s);
      while (--nfiles > 0) {
! 	if ((ch = (*++fnames)[0]) == '-' && (*fnames)[1] == 'h') {
! 		--nfiles;
! 		fnames++;
! 		if (nfiles == 0)
! 			break;
! 		header = *fnames;
! 	}
! 	else if (ch == '-') {
  	    argscan(*fnames+1);
  	}
  	else if (ch == '+') {

--- 113,119 -----
      initterm ();
      if(s = getenv("MORE")) argscan(s);
      while (--nfiles > 0) {
! 	if ((ch = (*++fnames)[0]) == '-') {
  	    argscan(*fnames+1);
  	}
  	else if (ch == '+') {
***************
*** 144,151
  	}
  	else break;
      }
-     if (header)
- 	Lpp -= 2;
      /* allow clreol only if Home and eraseln and EodClr strings are
       *  defined, and in that case, make sure we are in noscroll mode
       */

--- 134,139 -----
  	}
  	else break;
      }
      /* allow clreol only if Home and eraseln and EodClr strings are
       *  defined, and in that case, make sure we are in noscroll mode
       */
***************
*** 391,397
  {
      register int c;
      register int nchars;
-     int hlen;
      int length;			/* length of current line */
      static int prev_len = 1;	/* length of previous line */
  

--- 379,384 -----
  {
      register int c;
      register int nchars;
      int length;			/* length of current line */
      static int prev_len = 1;	/* length of previous line */
  
***************
*** 395,401
      int length;			/* length of current line */
      static int prev_len = 1;	/* length of previous line */
  
-     hlen = 0;
      for (;;) {
  	while (num_lines > 0 && !Pause) {
  	    if (header && !hlen && num_lines > Lpp - 4) {

--- 382,387 -----
      int length;			/* length of current line */
      static int prev_len = 1;	/* length of previous line */
  
      for (;;) {
  	while (num_lines > 0 && !Pause) {
  	    if ((nchars = getline (f, &length)) == EOF)
***************
*** 398,408
      hlen = 0;
      for (;;) {
  	while (num_lines > 0 && !Pause) {
- 	    if (header && !hlen && num_lines > Lpp - 4) {
- 		hlen = strlen(header);
- 		prbuf(header, hlen > 79 ? 79 : hlen);
- 		printf("\n\n");
- 	    }
  	    if ((nchars = getline (f, &length)) == EOF)
  	    {
  		if (clreol)

--- 384,389 -----
  
      for (;;) {
  	while (num_lines > 0 && !Pause) {
  	    if ((nchars = getline (f, &length)) == EOF)
  	    {
  		if (clreol)
***************
*** 433,439
  		break;
  	    num_lines--;
  	}
- 	hlen = 0;
  	fflush(stdout);
  	if ((c = Getc(f)) == EOF)
  	{

--- 414,419 -----
  		break;
  	    num_lines--;
  	}
  	fflush(stdout);
  	if ((c = Getc(f)) == EOF)
  	{
!
echo x - rn.c
cat << ! > rn.c
/*
 * rn - Read news.
 *
 * Author:	Anthony V. Discolo
 *		Computer Systems Lab
 *		University of California
 *		Santa Barbara, CA 93106
 *		(805) 961-4178
 *
 * Disclaimer:	I take no responsibility whatsoever for the condition
 *		of this software.  Unrestricted use is hereby granted
 *		as long as this header remains intact.
 */
#include <stdio.h>
#include <pwd.h>
#include <signal.h>
#include <sys/param.h>
#include "rn.h"

main(argc, argv)
int argc;
char *argv[];
{
	short sawone;
	char *key;
	struct newsgroup *lastn;

	initerm();
	/*
	 * This may get turned off later.
	 */
	if (!autoview && ttybaud() > 1200)
		autoview++;
	navlines = 5;
	while (--argc && (*++argv)[0] == '-') {
		key = *argv;
		if (*(key + 1) == 0)
			usage();
		while (*++key) {
			switch (*key) {
				case 'n':
					if (argc <= 1)
						usage();
					while (argc > 1 &&
					    (*(argv + 1))[0] != '-') {
						--argc;
						addng(*++argv);
					}
					break;
				case 'P':
					autoview = 0;
					break;
				case 'p':
					autoview++;
					break;
				case 'x':
					ignorerc++;
					break;
				case 'v':
					if (argc > 1) {
						navlines = atoi(*++argv);
						if (navlines < 5)
							navlines = 5;
						else if (navlines >
						    ttylines / 2)
						    navlines = ttylines / 2;
					}
					else
						usage();
					break;
				default:
					usage();
			}
		}
	}
	/*
	 * Set screen size.
	 */
	screenful = ttylines - (navlines + 3);
	pw = getpwuid(getuid());
	if (pw == NULL) {
		printf("Who are you?\n");
		exit(1);
	}
	if ((key = getenv("NEWSRC")) != NULL)
		strcpy(newsrc, key);
	else
		sprintf(newsrc, "%s/.newsrc", pw->pw_dir);
	/*
	 * Fake an .newsrc if the user supplied us with
	 * a newsgroup list, fakerc() will reopen the
	 * fake .newsrc on rcf. 
	 */
	if (nglist[0] != NULL)
		fakenrc();
	/*
	 * If the .newsrc file doesn't exist, newrc() will
	 * create one and reopen it on rcf.
	 */
	else if ((rcf = fopen(newsrc, "r")) == NULL)
		newrc(newsrc);
	readrc();
	fclose(rcf);
	readactive();
	if ((pager = getenv("PAGER")) == NULL)
		pager = PAGER;
	if ((editor = getenv("EDITOR")) == NULL)
		editor = EDITOR;
	if ((mailer = getenv("MAILER")) == NULL)
		mailer = MAILER;
	if (getwd(homedir) == 0) {
		puts(homedir);
		exit(1);
	}
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);
	lastn = lastng = NULL;
	sawone = 0;
	while ((curng = getng()) != NULL) {
		if (newsgroups == NULL) {
			newsgroups = curng;
			curng->ng_forw = NULL;
			curng->ng_back = NULL;
		}
		else {
			lastn->ng_forw = curng;
			curng->ng_forw = NULL;
			curng->ng_back = lastn;
		}
		lastn = curng;
		if (chdir(curng->ng_dir) < 0)
			continue;
		if (!curng->ng_unsubscribe) {
			if (gatherarticles(curng)) {
				sawone++;
				selectarticles(curng);
			}
		}
	}
	if (sawone && (curng = prevng(NULL)) != NULL) {
		lastng = curng;
		message("No more newsgroups.", 0);
		sleep(2);
		cd(curng->ng_dir);
		selectarticles(curng);
	}
	else if (!sawone)
		printf("No news.\n");
	done(1);
}

usage() {
	printf("Usage: rn [ -Ppx ] [ -v lines ] [ -n ng1 ng2 ... ]\n");
	exit(1);
}
!
echo x - rn.h
cat << ! > rn.h
/*
 * rn.h
 */
#include "tty.h"

#define ACTIVE		"/usr/lib/news/active"	/* active newsgroups */
#define CAESAR		"/usr/lib/news/caesar"	/* decoding program */
#define PAGER		"/staff/csl/discolo/bin/newspager"
#define EDITOR		"/usr/ucb/vi"
#define MAILER		"/usr/lib/sendmail -t"
#define MAXNGS		400			/* number of argv newsgroups */
#define ACTABSIZ	137			/* must be prime */
#define ITABSIZ	1000				/* inode hash tabsiz */

/*
 * Article types.
 */
#define LETTER	1
#define DIGEST	2
#define DECODE	3

FILE *rcf;
FILE *af;
FILE *popen();
int ignorerc;					/* start at article 1 */
int autoview;					/* automatic previewing */
int ngindex;
int screenful;					/* # of articles/page */
int navlines;					/* # of autoview lines/page */
char *tgoto();
char *rcngs[MAXNGS];				/* from .newsrc */
char homedir[MAXPATHLEN];
char newsrc[MAXPATHLEN];
char fakerc[MAXPATHLEN];
char *nglist[MAXNGS];				/* from command line */
char *pager;
char *editor;
char *mailer;
char *getenv();
char *ngtopath();
char *getheader();
char *justaddr();
char *idtoname();
char *prchar();
char *fixsubj();
char *index();
char *strsave();
struct passwd *pw;
/*
 * Hashed version of the ACTIVE file.
 */
struct angent {
	char ang_name[80];		/* name */
	int ang_maxid;			/* maximum article id */
	struct angent *ang_next;	/* hash chain */
} *angtab[ACTABSIZ];

/*
 * Hashed inode table.  Used in finding duplicate articles.
 */
struct inotab {
	long i_ino;
	struct inotab *i_next;
} *itab[ITABSIZ];

/*
 * A newsgroup.
 */
struct newsgroup {
	char ng_name[80];		/* name */
	int ng_begid;			/* first article id */
	int ng_lastid;			/* last article id */
	int ng_narticles;		/* number of articles in newsgroup */
	char ng_dir[MAXPATHLEN];	/* directory */
	short ng_unsubscribe;		/* if user unsubscribes to this ng */
	short ng_touched;		/* this newsgroup has been perused */
	struct article *ng_start;	/* beginning of article list */
	struct article *ng_end;		/* end of article list */
	struct newsgroup *ng_forw;	/* pointers to other newsgroups */
	struct newsgroup *ng_back;
};

/*
 * An article.
 */
struct article {
	short a_read;			/* true if article has been read */
	short a_select;			/* selected for reading */
	short a_screen;			/* screen number */
	int a_id;			/* article id */
	char a_date[40];		/* date of article */
	char a_from[80];		/* address of author */
	char a_subj[80];		/* subject of article */
	char a_keywds[80];		/* keywords */
	char a_ngs[80];			/* newsgroups article applies */
	int a_lines;			/* lines in article */
	int a_text;			/* offset where text begins */
	struct article *a_forw;
	struct article *a_back;
};
struct angent *angalloc();
struct inotab *italloc();
struct newsgroup *newsgroups;		/* newsgroup list */
struct newsgroup *getng();
struct newsgroup *prevng();
struct newsgroup *nextng();
struct newsgroup *curng;		/* the \`\`current'' newsgroup */
struct newsgroup *lastng;		/* the last newsgroup */
struct article *aralloc();
struct article *prevarticle();

/*
 * Article macros.
 */
#define toarticle(n)	tputs(tgoto(CM, 27, n + 1), 1, putch)
#define openarticle(n)	fopen(idtoname(n), "r")

/*
 * Miscellaneous macros.
 */
#define CTRL(c)		('c' & 037)
!
echo x - tty.c
cat << ! > tty.c
/*
 * tty -- Termcap interface.
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sgtty.h>
#include "tty.h"

int mode;
int speeds[] = {
	0,	50,	75,	110,	134,	150,	200,
	300,	600,	1200,	1800,	2400,	4800,	9600,
	19200
};
char tbuf[BUFSIZ];
char clearbuf[100];
char *clearptr;
char *term;
char *junk;
char *BC;
char *BELL = "\007";
char *CE;
char *CL;
char *DO;
char *SE;
char *SO;
char *SP;
char *UP;
char *VE;
char *VS;
char *tgetstr();
char *getenv();
struct sgttyb tty;

/*
 * Get and initialize terminal characteristics.
 */
initerm() {
	if (ioctl(0, TIOCGETP, &tty) < 0) {
		printf("rn: Cannot ioctl ttychars\n");
		exit(1);
	}
	if ((term = getenv("TERM")) == NULL) {
		printf("rn: Terminal type unknown\n");
		exit(1);
	}
	if (tgetent(tbuf, term) != 1) {
		printf("rn: Cannot acquire termcap entry for %s\n", term);
		exit(1);
	}
	clearptr = clearbuf;
	if ((ttylines = tgetnum("li", &clearptr)) < 0)
		ttylines = 24;
	if ((ttycols = tgetnum("co", &clearptr)) < 0)
		ttycols = 80;
	CL = tgetstr("cl", &clearptr);
	CE = tgetstr("ce", &clearptr);
	DO = tgetstr("do", &clearptr);
	CM = tgetstr("cm", &clearptr);
	SP = tgetstr("nd", &clearptr);
	SO = tgetstr("so", &clearptr);
	SE = tgetstr("se", &clearptr);
	UP = tgetstr("up", &clearptr);
	VE = tgetstr("ve", &clearptr);
	VS = tgetstr("vs", &clearptr);
	if (!DO)
		DO = "\012";
	if (!CL || !CE || !CM) {
		printf("rn: %s needs addressible cursor\n", term);
		exit(1);
	}
	if (tgetflag("bs") != 1)
		BC = tgetstr("bc", &clearptr);
	else
		BC = "\010";
}

setty() {
	tty.sg_flags &= ~ECHO;
	tty.sg_flags |= RAW;
	ioctl(0, TIOCSETP, &tty);
	if (VS)
		tput(VS);
	fflush(stdout);
}

unsetty() {
	tty.sg_flags |= ECHO;
	tty.sg_flags &= ~RAW;
	ioctl(0, TIOCSETP, &tty);
	if (VE)
		tput(VE);
	fflush(stdout);
}

raw() {
	tty.sg_flags |= RAW;
	ioctl(0, TIOCSETP, &tty);
}

cooked() {
	tty.sg_flags &= ~RAW;
	ioctl(0, TIOCSETP, &tty);
}

echo() {
	tty.sg_flags |= ECHO;
	ioctl(0, TIOCSETP, &tty);
}

noecho() {
	tty.sg_flags &= ~ECHO;
	ioctl(0, TIOCSETP, &tty);
}

startvisual() {
	if (VS)
		tput(VS);
	fflush(stdout);
}

endvisual() {
	if (VE)
		tput(VE);
	fflush(stdout);
}


ttybaud() {
	return(tty.sg_ospeed ? speeds[tty.sg_ospeed] : -1);
}

putch(c) {
	putchar(c);
}

inverseon() {
	tput(SO);
}

inverseoff() {
	tput(SE);
}

clearscreen() {
	tput(CL);
}

beep() {
	tput(BELL);
}

upcur() {
	tput(UP);
}

downcur() {
	tput(DO);
}

rightcur() {
	tput(SP);
}

leftcur() {
	tput(BC);
}

clearline() {
	tput(CE);
}
!
echo x - tty.h
cat << ! > tty.h
/*
 * tty.h
 */
int putch();
int ttylines;
int ttycols;
char *CM;

/*
 * Termlib macros.
 */
#define tgo(l, c)	tputs(tgoto(CM, c, l), 1, putch)
#define tput(s)		tputs(s, 1, putch)
!
-- 

uucp: {ucbvax,cepu}!ucsbcsl!discolo
arpa: ucsbcsl!discolo@BERKELEY
csnet: discolo@ucsb
USMail: U.C. Santa Barbara
	Department of Computer Science
	Santa Barbara, CA  93106
GTE: (805) 961-4178