o Index files are now 1/2 to 1/3 their previous size
	o Tass is much more conservative in its memory usage
	o Tass recognizes .signature and .Sig files
	o Screen updating is more efficient in many places
	o Job control fixed for BSD systems
	o The various mailing commands work better
	o Author search
	o Support for NNTP (rtass)
	o Many other enhancements
If you haven't seen Tass before, it's a threaded visual newsreader with
an index page which may remind you of Notes.
Rich Skrenta
--
skr...@blekko.commodore.com
echo x - COPYRIGHT
cat >COPYRIGHT <<'@EOF'
/*
 *  Tass, a visual Usenet news reader
 *  (c) Copyright 1990 by Rich Skrenta
 *
 *  Distribution agreement:
 *
 *	You may freely copy or redistribute this software, so long
 *	as there is no profit made from its use, sale, trade or
 *	reproduction.  You may not change this copyright notice,
 *	and it must be included prominently in any copy made.
 */
@EOF
chmod 600 COPYRIGHT
echo x - Makefile
cat >Makefile <<'@EOF'
#  Make sure LIBDIR, SPOOLDIR and MAILER are correct in tass.h
#
#  Make sure spool_open.c knows whether readdir returns struct direct or
#  struct dirent.  The defines below should take care of this.
#  For Berkeley systems:
#
# CFLAGS= -DBSD
# LIBS= -lcurses -ltermcap
#  For System V
#
CFLAGS=-g 
LIBS= -lcurses
#  For 286 Xenix
#
# CFLAGS=-g -M2l -F 8000
# LIBS= -lcurses -ltermcap -lx
#  For SCO Unix System V
#
# CFLAGS=-g -UM_XENIX -DSCO_UNIX
# LIBS= -lcurses -lgen
#  You only need to worry about the following two defines if you want
#  to build rtass (remote Tass via nntp)
#
# point NNTPLIB at the nntp clientlib.o support library
#
NNTPLIB=../nntp/common/clientlib.o
#
#  NETLIBS should be the networking libraries you need to link with
#  the nntp clientlib.o
#
NETLIBS=-lnsl -lsocket
OBJECTS	=	curses.o art.o group.o hashstr.o mail.o main.o misc.o \
		page.o prompt.o screen.o select.o time.o
tass: $(OBJECTS) spool_open.o
	cc $(CFLAGS) -o tass $(OBJECTS) spool_open.o $(LIBS)
rtass: $(OBJECTS) nntp_open.o
	cc $(CFLAGS) -o rtass $(OBJECTS) nntp_open.o $(LIBS) $(NNTPLIB) $(NETLIBS)
shar:
	-mv -f ../tass.shar ../tass.shar-
	shar -v [A-Z]* *.[ch] > ../tass.shar
clean:
	rm -f *.o
clobber: clean
	rm -f tass rtass
art.o:		art.c tass.h
curses.o:	curses.c
group.o:	group.c tass.h
hashstr.o:	hashstr.c
mail.o:		mail.c
main.o:		main.c tass.h
misc.o:		misc.c tass.h
nntp_open.o:	nntp_open.c tass.h nntp.h
page.o:		page.c tass.h
prompt.o:	prompt.c tass.h
screen.o:	screen.c tass.h
select.o:	select.c tass.h
spool_open.o:	spool_open.c tass.h
time.o:		time.c
@EOF
chmod 644 Makefile
echo x - README
cat >README <<'@EOF'
Tass is a full screen threaded newsreader.
	o Organizes articles by threads.  Displays a really nice
	  article selection page.
	o Group selection page makes it easy to scan newsgroups, subscribe,
	  unsubscribe, reorder your .newsrc
	o If you've ever used Notes, this is the program for you.
	  Tass looks a lot like Notes, but has a few improvements:
		visual group selection page, Notes didn't have one
		rn style unread article detection as opposed to single timeline
		uses standard /usr/spool/news article layout
I wrote Tass because I "learned" the Usenet on Notes and couldn't stand rn.
No rn flames here, but if you've wished you could view the news a different
way, try Tass.
Newsreading style under Tass tends to be different than with rn.  Instead of
plowing through each group reading everything unread, you may find yourself
reading fewer articles in more groups.  It's easier to skip about and only
read interesting threads with Tass.
Tass keeps an index file for each group.  The first time you enter a group,
it will be a bit slow creating this file.  After that Tass will incrementally
update the index file and there should be little delay.
You can also run Tass in "update mode" out of cron to update the indexes.
Building Tass
-------- ----
	1)  Edit the Makefile.  Select CFLAGS and LIBS for your system.
	2)  Edit tass.h.  Make sure that LIBDIR, SPOOLDIR, MAILER and
	    DEF_EDITOR are correct for your system.
	3)  'make'
To build the remote NNTP Tass (rtass) you will need the nntp sources;
specifically, clientlib.o.  Point the Makefile variable NNTPLIB at your
clientlib.o and say 'make rtass'.
Installing Tass
---------- ----
Copy the tass executable to some useful place.  If you make tass setuid news,
it will keep the indexes in the news spool directory.  If not, tass will hide
indexes in the user's home directory.
Don't make rtass setuid news since it will get articles from the NNTP host
and not from /usr/spool/news.
There is a brief man page (Tass.man) which may be copied to the appropriate
man directory.
NOTE:
-----
	Tass 3.2 uses a different name for the index files.  If you've been
	using Tass 3.0 or 3.1, you should remove these old indexes before
	running 3.2.  Do this with
		rm -rf $HOME/.tindex
	   or
		find /usr/spool/news -name '.tindex' -exec rm {} \;
Changes from 3.0
------- ---- ---
	o Index files are now 1/2 to 1/3 their previous size
	o Tass is much more conservative in its memory usage
	o Tass recognizes .signature and .Sig files
	o Screen updating is more efficient in many places
	o Job control fixed for BSD systems
o The various mailing commands should work much better
	o Author search
	o Support for NNTP (rtass)
	o Many other enhancements
Rich Skrenta
-- 
skr...@blekko.commodore.com
@EOF
chmod 644 README
echo x - Tass.man
cat >Tass.man <<'@EOF'
.TH TASS 1A
.SH NAME
tass, rtass \- Visual threaded Usenet news reader
.SH SYNOPSIS
.nf
tass [options] [newsgroups]
rtass [options] [newsgroups]
.fi
.SH DESCRIPTION
Tass is a full screen threaded Usenet newsreader.
Tass has three newsreading levels:
the newsgroup selection page, the group index page and the article viewer.
Use the 'h' (help) command to view a list of the commands available at a
particular level.
.PP
On startup Tass will show a list of the newsgroups found in $HOME/.newsrc.
An arrow will point to the first newsgroup.  Move the arrow by either using
the terminal arrow keys or 'j' and 'k'.  Control-D will page down, control-U
will page up.  Enter a newsgroup by pressing RETURN.
.PP
The TAB key may be used to advance to the next newsgroup with unread articles
and enter it.
.PP
rtass will attempt to connect to the NNTP port on the machine named in the
environment variable NNTPSERVER or contained in the file /etc/nntpserver.
rtass will index somewhat slower because the articles must be retrieved
via the NNTP protocol.
.PP
Refer to the Tass help screens for further commands.
.SH TASS INDEX FILES
In order to keep track of threads, Tass maintains an index for each group.
If Tass is made setuid to news, the indexes will be stored in the news spool
directory (typically /usr/spool/news).  If
Tass is not setuid, it will store
index files in the user's home directory, in a subdirectory called .tindx.
.PP
Entering a group the first time tends to be slow because the index file must
be built from scratch.  Subsequent readings of a group will cause
Tass to incrementally update the index file, adding or removing entries as new
articles come in or as news expires.
.PP
Tass may be run in update mode (the -u option) to update a series of groups
at one time.  tass -u is usually run from cron.
.PP
Do not make rtass setuid news since news will be obtained via NNTP and not
from /usr/spool/news.
.SH SIGNATURES
Tass will recognize a signature in either $HOME/.signature or $HOME/.Sig.
If .signature exists, then the signature will be pulled into the editor
for Tass mail commands.  A signature in .signature will not be pulled
into the editor for posting commands since the inews program
will append the signature itself.
.PP
A signature in .Sig will be pulled into the editor for both posting
and mailing commands.
.SH OPTIONS
.I Tass
recognizes the following options:
.TP
-f file
Use the indicated file in place of $HOME/.newsrc.
.TP
-u
Run Tass in update mode.  Tass will make indexes current for every group
in its newsrc.
A good way to keep Tass index files current is to run tass -u from cron:
.nf
20 6 * * *	/usr/local/bin/tass -u -f /usr/lib/news/tass_groups
.fi
This would update the index files for those groups appearing in
/usr/lib/news/tass_groups.  To index all of the groups on the system,
run tass -u with -f indicating the active file:
.nf
20 6 * * *	/usr/local/bin/tass -u -f /usr/lib/news/active
.fi
.SH AUTHOR
.nf
Rich Skrenta
skr...@blekko.commodore.com or skr...@blekko.uucp.
.fi
@EOF
chmod 644 Tass.man
echo x - art.c
cat >art.c <<'@EOF'
#include	<stdio.h>
#include	<ctype.h>
#include	<signal.h>
#include	"tass.h"
char index_file[LEN+1];
char *glob_art_group;
extern char *hash_str();
#ifdef SIGTSTP
void
art_susp(i)
int i;
{
	Raw(FALSE);
	putchar('\n');
	signal(SIGTSTP, SIG_DFL);
#ifdef BSD
        sigsetmask(sigblock(0) & ~(1 << (SIGTSTP - 1)));
#endif
	kill(0, SIGTSTP);
	signal(SIGTSTP, art_susp);
	Raw(TRUE);
	mail_setup();
	ClearScreen();
	MoveCursor(LINES, 0);
	printf("Group %s...    ", glob_art_group);
	fflush(stdout);
}
#endif
/*
 *  Convert a string to a long, only look at first n characters
 */
my_atol(s, n)
char *s;
int n;
{
	long ret = 0;
	while (*s && n--) {
		if (*s >= '0' && *s <= '9')
			ret = ret * 10 + (*s - '0');
		else
			return -1;
		s++;
	}
	return ret;
}
/*
 *  Construct the pointers to the basenotes of each thread
 *  arts[] contains every article in the group.  inthread is
 *  set on each article that is after the first article in the
 *  thread.  Articles which have been expired have their thread
 *  set to -2.
 */
find_base() {
	int i;
top_base = 0;
	for (i = 0; i < top; i++)
		if (!arts[i].inthread && arts[i].thread != -2) {
			if (top_base >= max_art)
				expand_art();
			base[top_base++] = i;
		}
}
/* 
 *  Count the number of non-expired articles in arts[]
 */
num_arts() {
	int sum = 0;
int i;
	for (i = 0; i < top; i++)
		if (arts[i].thread != -2)
			sum++;
	return sum;
}
/*
 *  Do we have an entry for article art?
 */
valid_artnum(art)
long art;
{
	int i;
	for (i = 0; i < top; i++)
		if (arts[i].artnum == art)
			return i;
	return -1;
}
/*
 *  Return TRUE if arts[] contains any expired articles
 *  (articles we have an entry for which don't have a corresponding
 *   article file in the spool directory)
 */
purge_needed() {
	int i;
	for (i = 0; i < top; i++)
		if (arts[i].thread == -2)
			return TRUE;
	return FALSE;
}
/*
 *  Main group indexing routine.  Group should be the name of the
 *  newsgroup, i.e. "comp.unix.amiga".  group_path should be the
 *  same but with the .'s turned into /'s: "comp/unix/amiga"
 *
 *  Will read any existing index, create or incrementally update
 *  the index by looking at the articles in the spool directory,
 *  and attempt to write a new index if necessary.
 */
index_group(group, group_path)
char *group;
char *group_path;
{
	int modified;
glob_art_group = group;
#ifdef SIGTSTP
	signal(SIGTSTP, art_susp);
#endif
	if (!update) {
		clear_message();
		MoveCursor(LINES, 0);
		printf("Group %s...    ", group);
		fflush(stdout);
	}
	hash_reclaim();
	if (local_index)
		find_local_index(group);
	else
		sprintf(index_file, "%s/%s/.tindx", SPOOLDIR, group_path);
	load_index();
	modified = read_group(group, group_path);
	make_threads();
	if (modified || purge_needed()) {
		if (local_index) {	/* writing index in home directory */
			setuid(real_uid);	/* so become them */
			setgid(real_gid);
		}
		dump_index(group);
		if (local_index) {
			setuid(tass_uid);
			setgid(tass_gid);
		}
	}
	find_base();
	if (modified && !update)
		clear_message();
}
/*
 *  Index a group.  Assumes any existing index has already been
 *  loaded.
 */
read_group(group, group_path)
char *group;
char *group_path;
{
	int fd;
	long art;
	int count;
	int modified = FALSE;
	int respnum;
	int i;
	setup_base(group, group_path);	  /* load article numbers into base[] */
	count = 0;
	for (i = 0; i < top_base; i++) {	/* for each article # */
		art = base[i];
/*
 *  Do we already have this article in our index?  Change thread from
 *  -2 to -1 if so and skip the header eating.
 */
		if ((respnum = valid_artnum(art)) >= 0) {
			arts[respnum].thread = -1;
			arts[respnum].unread = 1;
			continue;
		}
		if (!modified)
			modified = TRUE;   /* we've modified the index */
					   /* it will need to be re-written */
		fd = open_header_fd(group_path, art);
		if (fd < 0)
			continue;
/*
 *  Add article to arts[]
 */
		if (top >= max_art)
			expand_art();
		arts[top].artnum = art;
		arts[top].thread = -1;
		arts[top].inthread = FALSE;
		arts[top].unread = 1;
		if (!parse_headers(fd, &arts[top]))
			continue;
		top++;
		close(fd);
		if (++count % 10 == 0 && !update) {
			printf("\b\b\b\b%4d", count);
			fflush(stdout);
		}
	}
	return modified;
}
/*
 *  Go through the articles in arts[] and use .thread to snake threads
 *  through them.  Use the subject line to construct threads.  The
 *  first article in a thread should have .inthread set to FALSE, the
 *  rest TRUE.  Only do unexprired articles we haven't visited yet
 *  (arts[].thread == -1).
 */
make_threads() {
	int i;
	int j;
	for (i = 0; i < top; i++) {
		if (arts[i].thread == -1)
		    for (j = i+1; j < top; j++)
			if (arts[j].thread != -2
			&&  arts[i].subject == arts[j].subject) {
				arts[i].thread = j;
				arts[j].inthread = TRUE;
				break;
			}
	}
}
/*
 *  Return a pointer into s eliminating any leading Re:'s.  Example:
 *
 *	  Re: Reorganization of misc.jobs
 *	  ^   ^
 */
char *
eat_re(s)
char *s;
{
	while (*s == 'r' || *s == 'R') {
		if ((*(s+1) == 'e' || *(s+1) == 'E')) {
			if (*(s+2) == ':')
				s += 3;
			else if (*(s+2) == '^' && isdigit(*(s+3)) && *(s+4) == ':')
				s += 5;			/* hurray nn */
			else
				break;
		} else
			break;
		while (*s == ' ')
			s++;
	}
	return s;
}
parse_headers(fd, h)
int fd;
struct header *h;
{
	char buf[1024];
	char *p, *q;
	char flag;
	int n;
	char buf2[1024];
	char *s;
	n = read(fd, buf, 1024);
	if (n <= 0)
		return FALSE;
buf[n - 1] = '\0';
	h->subject = "<no subject>";
	h->from = "<no from>";
	p = buf;
	while (1) {
		for (q = p; *p && *p != '\n'; p++)
			if (((*p) & 0x7F) < 32)
				*p = ' ';
		flag = *p;
		*p++ = '\0';
		if (strncmp(q, "From: ", 6) == 0) {
			strncpy(buf2, &q[6], MAX_FROM-1);
			buf2[MAX_FROM-1] = '\0';
			h->from = hash_str(buf2);
		} else if (strncmp(q, "Subject: ", 9) == 0) {
			strcpy(buf2, &q[9]);
			s = eat_re(buf2);
			s[MAX_SUBJ-1] = '\0';
			h->subject = hash_str(eat_re(s));
		}
		if (!flag || *p == '\n')
			break;
	}
	return TRUE;
}
/* 
 *  Write out a .tindx file.  Write the group name first so if
 *  local indexing is done we can disambiguate between group name
 *  hash collisions by looking at the index file.
 */
dump_index(group)
char *group;
{
	int i;
	char buf[200];
	char nam[200];
	FILE *fp;
	int *iptr;
	int realnum;
	sprintf(nam, "%s.%d", index_file, getpid());
	fp = fopen(nam, "w");
	if (fp == NULL)
		return;
	fprintf(fp, "%s\n", group);
	fprintf(fp, "%d\n", num_arts());
	realnum = 0;
	for (i = 0; i < top; i++)
		if (arts[i].thread != -2) {
			fprintf(fp, "%ld\n", arts[i].artnum);
			iptr = (int *) arts[i].subject;
			iptr--;
			if (*iptr < 0) {
				fprintf(fp, " %s\n", arts[i].subject);
				*iptr = realnum;
			} else
				fprintf(fp, "%%%d\n", *iptr);
			iptr = (int *) arts[i].from;
			iptr--;
			if (*iptr < 0) {
				fprintf(fp, " %s\n", arts[i].from);
				*iptr = realnum;
			} else
				fprintf(fp, "%%%d\n", *iptr);
			realnum++;
		}
	fclose(fp);
	chmod(nam, 0644);
	unlink(index_file);
	link(nam, index_file);
	unlink(nam);
}
/*
 *  strncpy that stops at a newline and null terminates
 */
my_strncpy(p, q, n)
char *p;
char *q;
int n;
{
	while (n--) {
		if (!*q || *q == '\n')
			break;
		*p++ = *q++;
	}
	*p = '\0';
}
/*
 *  Read in a .tindx file.
 */
load_index()
{
	int i;
	long j;
	char buf[200];
	FILE *fp;
	int first = TRUE;
	char *p;
	int n;
	char *err;
top = 0;
	fp = fopen(index_file, "r");
	if (fp == NULL)
		return;
	if (fgets(buf, 200, fp) == NULL
	||  fgets(buf, 200, fp) == NULL) {
		err = "one";
		goto corrupt_index;
	}
	i = atol(buf);
	while (top < i) {
		if (top >= max_art)
			expand_art();
		arts[top].thread = -2;
		arts[top].inthread = FALSE;
		if (fgets(buf, 200, fp) == NULL) {
			err = "two";
			goto corrupt_index;
		}
		arts[top].artnum = atol(buf);
		if (fgets(buf, 200, fp) == NULL) {
			err = "three";
			goto corrupt_index;
		}
		if (buf[0] == '%') {
			n = atoi(&buf[1]);
			if (n >= top || n < 0) {
				err = "eight";
				goto corrupt_index;
			}
			arts[top].subject = arts[n].subject;
		} else if (buf[0] == ' ') {
			for (p = &buf[1]; *p && *p != '\n'; p++) ;
			*p = '\0';
			buf[MAX_SUBJ] = '\0';
			arts[top].subject = hash_str(&buf[1]);
		} else {
			err = "six";
			goto corrupt_index;
		}
				
		if (fgets(buf, 200, fp) == NULL) {
			err = "four";
			goto corrupt_index;
		}
		if (buf[0] == '%') {
			n = atoi(&buf[1]);
			if (n >= top || n < 0) {
				err = "nine";
				goto corrupt_index;
			}
			arts[top].from = arts[n].from;
		} else if (buf[0] == ' ') {
			for (p = &buf[1]; *p && *p != '\n'; p++) ;
			*p = '\0';
			buf[MAX_FROM] = '\0';
			arts[top].from = hash_str(&buf[1]);
		} else {
			err = "seven";
			goto corrupt_index;
		}
		top++;
	}
	fclose(fp);
	return;
corrupt_index:
	fprintf(stderr, "\r\n%s: index file %s corrupt, top=%d\r\n",
						err, index_file, top);
	unlink(index_file);
	top = 0;
}
/*
 *  Look in the local $HOME/.tindx (or wherever) directory for the
 *  index file for the given group.  Hashing the group name gets
 *  a number.  See if that #.1 file exists; if so, read first line.
 *  Group we want?  If no, try #.2.  Repeat until no such file or
 *  we find an existing file that matches our group.
 */
find_local_index(group)
char *group;
{
	unsigned long h;
	static char buf[200];
	int i;
	char *p;
	FILE *fp;
h = hash_groupname(group);
	i = 1;
	while (1) {
		sprintf(index_file, "%s/%lu.%d", indexdir, h, i);
		fp = fopen(index_file, "r");
		if (fp == NULL)
			return;
		if (fgets(buf, 200, fp) == NULL) {
			fclose(fp);
			return;
		}
		fclose(fp);
		for (p = buf; *p && *p != '\n'; p++) ;
		*p = '\0';
		if (strcmp(buf, group) == 0)
			return;
		i++;
	}
}
/*
 *  Run the index file updater only for the groups we've loaded.
 */
do_update() {
	int i;
	char group_path[200];
	char *p;
	for (i = 0; i < local_top; i++) {
		strcpy(group_path, active[my_group[i]].name);
		for (p = group_path; *p; p++)
			if (*p == '.')
				*p = '/';
		index_group(active[my_group[i]].name, group_path);
	}
}
@EOF
chmod 644 art.c
echo x - curses.c
cat >curses.c <<'@EOF'
/*
 *  This is a screen management library borrowed with permission from the
 *  Elm mail system (a great mailer--I highly recommend it!).
 *
 *  I've hacked this library to only provide what Tass needs.
 *
 *  Original copyright follows:
 */
/*******************************************************************************
 *  The Elm Mail System  -  $Revision: 2.1 $   $State: Exp $
 *
 * 			Copyright (c) 1986 Dave Taylor
 ******************************************************************************/
#include <stdio.h>
#include <curses.h>
#define		TRUE		1
#define		FALSE		0
#define		BACKSPACE	'\b'
#define		VERY_LONG_STRING	2500
int LINES=23;
int COLS=80;
int inverse_okay = TRUE;
/*
#ifdef BSD
#  ifndef BSD4_1
#    include <sgtty.h>
#  else
#    include <termio.h>
#  endif
# else
#  include <termio.h>
#endif
*/
#include <ctype.h>
/*
#ifdef BSD
#undef tolower
#endif
*/
#define TTYIN 0
#ifdef SHORTNAMES
# define _clearinverse	_clrinv
# define _cleartoeoln	_clrtoeoln
# define _cleartoeos	_clr2eos
#endif
#ifndef BSD
struct termio _raw_tty, 
              _original_tty;
#else
#define TCGETA	TIOCGETP
#define TCSETAW	TIOCSETP
struct sgttyb _raw_tty,
	      _original_tty;
#endif
static int _inraw = 0; /* are we IN rawmode? */
#define DEFAULT_LINES_ON_TERMINAL	24
#define DEFAULT_COLUMNS_ON_TERMINAL	80
static int _memory_locked = 0; /* are we IN memlock?? */
static int _intransmit; /* are we transmitting keys? */
static
char *_clearscreen, *_moveto, *_cleartoeoln, *_cleartoeos,
	*_setinverse, *_clearinverse;
static
int _lines,_columns;
static char _terminal[1024];              /* Storage for terminal entry */
static char _capabilities[1024];           /* String for cursor motion */
static char *ptr = _capabilities; /* for buffering */
int    outchar();			/* char output for tputs */
char  *tgetstr(),     		       /* Get termcap capability */
      *tgoto();				/* and the goto stuff    */
InitScreen()
{
int  tgetent(),      /* get termcap entry */
     err;
char termname[40];
char *strcpy(), *getenv();
	
	if (getenv("TERM") == NULL) {
		fprintf(stderr,
		  "TERM variable not set; Tass requires screen capabilities\n");
		return(FALSE);
	}
	if (strcpy(termname, getenv("TERM")) == NULL) {
		fprintf(stderr,"Can't get TERM variable\n");
		return(FALSE);
	}
	if ((err = tgetent(_terminal, termname)) != 1) {
		fprintf(stderr,"Can't get entry for TERM\n");
		return(FALSE);
	}
	/* load in all those pesky values */
	_clearscreen       = tgetstr("cl", &ptr);
	_moveto            = tgetstr("cm", &ptr);
	_cleartoeoln       = tgetstr("ce", &ptr);
	_cleartoeos        = tgetstr("cd", &ptr);
	_lines	      	   = tgetnum("li");
	_columns	   = tgetnum("co");
	_setinverse        = tgetstr("so", &ptr);
	_clearinverse      = tgetstr("se", &ptr);
	if (!_clearscreen) {
		fprintf(stderr,
			"Terminal must have clearscreen (cl) capability\n");
		return(FALSE);
	}
	if (!_moveto) {
		fprintf(stderr,
			"Terminal must have cursor motion (cm)\n");
		return(FALSE);
	}
	if (!_cleartoeoln) {
		fprintf(stderr,
			"Terminal must have clear to end-of-line (ce)\n");
		return(FALSE);
	}
	if (!_cleartoeos) {
		fprintf(stderr,
			"Terminal must have clear to end-of-screen (cd)\n");
		return(FALSE);
	}
	if (_lines == -1)
		_lines = DEFAULT_LINES_ON_TERMINAL;
	if (_columns == -1)
		_columns = DEFAULT_COLUMNS_ON_TERMINAL;
	return(TRUE);
}
ScreenSize(lines, columns)
int *lines, *columns;
{
	/** returns the number of lines and columns on the display. **/
	if (_lines == 0) _lines = DEFAULT_LINES_ON_TERMINAL;
	if (_columns == 0) _columns = DEFAULT_COLUMNS_ON_TERMINAL;
	*lines = _lines - 1;		/* assume index from zero*/
	*columns = _columns;		/* assume index from one */
}
ClearScreen()
{
	/* clear the screen: returns -1 if not capable */
	tputs(_clearscreen, 1, outchar);
	fflush(stdout);      /* clear the output buffer */
}
MoveCursor(row, col)
int row, col;
{
	/** move cursor to the specified row column on the screen.
            0,0 is the top left! **/
char *stuff, *tgoto();
	stuff = tgoto(_moveto, col, row);
	tputs(stuff, 1, outchar);
	fflush(stdout);
}
CleartoEOLN()
{
	/** clear to end of line **/
	tputs(_cleartoeoln, 1, outchar);
	fflush(stdout);  /* clear the output buffer */
}
CleartoEOS()
{
	/** clear to end of screen **/
	tputs(_cleartoeos, 1, outchar);
	fflush(stdout);  /* clear the output buffer */
}
StartInverse()
{
	/** set inverse video mode **/
	if (_setinverse && inverse_okay)
		tputs(_setinverse, 1, outchar);
/*	fflush(stdout);	*/
}
EndInverse()
{
	/** compliment of startinverse **/
	if (_clearinverse && inverse_okay)
		tputs(_clearinverse, 1, outchar);
/*	fflush(stdout);	*/
}
RawState()
{
	/** returns either 1 or 0, for ON or OFF **/
	return( _inraw );
}
Raw(state)
int state;
{
	/** state is either TRUE or FALSE, as indicated by call **/
	if (state == FALSE && _inraw) {
	  (void) ioctl(TTYIN, TCSETAW, &_original_tty);
	  _inraw = 0;
	}
	else if (state == TRUE && ! _inraw) {
(void) ioctl(TTYIN, TCGETA, &_original_tty); /** current setting **/
	  (void) ioctl(TTYIN, TCGETA, &_raw_tty);    /** again! **/
#ifdef BSD
	  _raw_tty.sg_flags &= ~(ECHO | CRMOD);	/* echo off */
	  _raw_tty.sg_flags |= CBREAK;	/* raw on    */
#else
	  _raw_tty.c_lflag &= ~(ICANON | ECHO);	/* noecho raw mode        */
	  _raw_tty.c_cc[VMIN] = '\01';	/* minimum # of chars to queue    */
	  _raw_tty.c_cc[VTIME] = '\0';	/* minimum time to wait for input */
#endif
	  (void) ioctl(TTYIN, TCSETAW, &_raw_tty);
	  _inraw = 1;
	}
}
int
ReadCh()
{
	/** read a character with Raw mode set! **/
	register int result;
	char ch;
	result = read(0, &ch, 1);
        return((result <= 0 ) ? EOF : ch & 0x7F);
}
outchar(c)
char c;
{
	/** output the given character.  From tputs... **/
	/** Note: this CANNOT be a macro!              **/
	putc(c, stdout);
}
@EOF
chmod 644 curses.c
echo x - group.c
cat >group.c <<'@EOF'
#include	<stdio.h>
#include	<signal.h>
#include	"tass.h"
int index_point;
int first_subj_on_screen;
int last_subj_on_screen;
char subject_search_string[LEN+1];
char author_search_string[LEN+1];
extern int cur_groupnum;
extern int last_resp;		/* page.c */
extern int this_resp;		/* page.c */
extern int space_mode;		/* select.c */
extern char *cvers;
char *glob_group;
#ifdef SIGTSTP
void
group_susp(i)
int i;
{
	Raw(FALSE);
	putchar('\n');
	signal(SIGTSTP, SIG_DFL);
#ifdef BSD
        sigsetmask(sigblock(0) & ~(1 << (SIGTSTP - 1)));
#endif
	kill(0, SIGTSTP);
	signal(SIGTSTP, group_susp);
	Raw(TRUE);
	mail_setup();
	show_group_page(glob_group);
}
#endif
group_page(group)
char *group;
{
	char ch;
	int i, n;
	char group_path[200];
	char *p;
	char buf[200];
	int flag;
	int sav_groupnum;
	glob_group = group;
	sav_groupnum = cur_groupnum;
	strcpy(group_path, group);		/* turn comp.unix.amiga into */
	for (p = group_path; *p; p++)		/* comp/unix/amiga */
		if (*p == '.')
			*p = '/';
	last_resp = -1;
	this_resp = -1;
	index_group(group, group_path);		/* update index file */
	read_newsrc_line(group);		/* get sequencer information */
	if (space_mode) {
		for (i = 0; i < top_base; i++)
			if (new_responses(i))
				break;
		if (i < top_base)
			index_point = i;
		else
			index_point = top_base - 1;
	} else
		index_point = top_base - 1;
show_group_page(group);
	while (1) {
		ch = ReadCh();
		if (ch > '0' && ch <= '9') {	/* 0 goes to basenote */
			prompt_subject_num(ch, group);
		} else switch (ch) {
			case 'a':	/* author search forward */
			case 'A':	/* author search backward */
				if (index_point < 0) {
					info_message("No articles");
					break;
				}
i = (ch == 'a');
				n = search_author((int) base[index_point],
								i, group);
				if (n < 0)
					break;
				index_point = show_page(n, group, group_path);
				if (index_point < 0) {
					space_mode = FALSE;
					goto group_done;
				}
				show_group_page(group);
				break;
			case 'I':	/* toggle inverse video */
				inverse_okay = !inverse_okay;
				if (inverse_okay)
					info_message("Inverse video enabled");
				else
					info_message("Inverse video disabled");
				break;
			case 's':	/* subscribe to this group */
				subscribe(group, ':', my_group[cur_groupnum],
									TRUE);
				sprintf(buf, "subscribed to %s", group);
				info_message(buf);
				break;
			case 'u':	/* unsubscribe to this group */
				subscribe(group, '!', my_group[cur_groupnum],
									TRUE);
				sprintf(buf, "unsubscribed to %s", group);
				info_message(buf);
				break;
			case 'g':	/* choose a new group by name */
				n = choose_new_group();
				if (n >= 0 && n != cur_groupnum) {
					cur_groupnum = n;
					index_point = -3;
					goto group_done;
				}
				break;
			case 'c':	/* catchup--mark all articles as read */
			    if (prompt_yn("Mark everything as read? (y/n): ")) {
				for (n = 0; n < top; n++)
					arts[n].unread = 0;
 				for (n = INDEX_TOP ;
					n < NOTESLINES + INDEX_TOP; n++ ) {
 					MoveCursor(n, COLS - 2);
					putchar(' ');
 				}
				fflush(stdout);
 /*				show_group_page(group); */
				info_message("All articles marked as read");
			    }
			    break;
			case 27:	/* common arrow keys */
				ch = ReadCh();
				if (ch == '[' || ch == 'O')
					ch = ReadCh();
				switch (ch) {
				case 'A':
				case 'D':
				case 'i':
					goto group_up;
				case 'B':
				case 'I':
				case 'C':
					goto group_down;
				}
				break;
			case 'n':	/* next group */
				clear_message();
				if (cur_groupnum + 1 >= local_top)
					info_message("No more groups");
				else {
					cur_groupnum++;
					index_point = -3;
					space_mode = FALSE;
					goto group_done;
				}
				break;
			case 'p':	/* previous group */
				clear_message();
				if (cur_groupnum <= 0)
					info_message("No previous group");
				else {
					cur_groupnum--;
					index_point = -3;
					space_mode = FALSE;
					goto group_done;
				}
				break;
			case '\t':
				space_mode = TRUE;
				if (index_point < 0
				|| (n=next_unread((int) base[index_point]))<0) {
					for (i = cur_groupnum+1;
							i < local_top; i++)
						if (unread[i] > 0)
							break;
					if (i >= local_top)
						goto group_done;
					cur_groupnum = i;
					index_point = -3;
					goto group_done;
				}
				index_point = show_page(n, group, group_path);
				if (index_point < 0)
					goto group_done;
				show_group_page(group);
				break;
			case 'K':	/* mark rest of thread as read */
				if (new_responses(index_point)) {
				    for (i = base[index_point]; i >= 0;
							i = arts[i].thread)
					arts[i].unread = 0;
				    MoveCursor(INDEX_TOP +
				      (index_point - first_subj_on_screen), 78);
				    putchar(' ');
				    fflush(stdout);
				    flag = FALSE;
				} else
				    flag = TRUE;
				n = next_unread(
					next_response(base[index_point]));
				if (n < 0) {
				    if (flag)
					info_message("No next unread article");
				    else
					MoveCursor(LINES, 0);
				    break;
				}
				n = which_base(n);
				if (n < 0) {
					info_message(
					    "Internal error: K which_base < 0");
					break;
				}
				if (n >= last_subj_on_screen) {
					index_point = n;
					show_group_page(group);
				} else {
					erase_subject_arrow();
					index_point = n;
					draw_subject_arrow();
				}
				break;
			case 'N':	/* go to next unread article */
				if (index_point < 0) {
					info_message("No next unread article");
					break;
				}
				n = next_unread( (int) base[index_point]);
				if (n == -1)
					info_message("No next unread article");
				else {
					index_point =
						show_page(n, group, group_path);
					if (index_point < 0) {
						space_mode = FALSE;
						goto group_done;
					}
					show_group_page(group);
				}
				break;
			case 'P':	/* go to previous unread article */
				if (index_point < 0) {
				    info_message("No previous unread article");
				    break;
				}
				n = prev_response( (int) base[index_point]);
				n = prev_unread(n);
				if (n == -1)
				    info_message("No previous unread article");
				else {
					index_point =
						show_page(n, group, group_path);
					if (index_point < 0) {
						space_mode = FALSE;
						goto group_done;
					}
					show_group_page(group);
				}
				break;
			case 'w':	/* post a basenote */
				post_base(group);
				update_newsrc(group, my_group[cur_groupnum]);
				index_group(group, group_path);
				read_newsrc_line(group);
				index_point = top_base - 1;
				show_group_page(group);
				break;
			case 't':	/* return to group selection page */
				goto group_done;
			case ' ':
			case '\r':
			case '\n':	/* read current basenote */
				if (index_point < 0) {
					info_message("*** No Articles ***");
					break;
				}
				index_point = show_page((int) base[index_point],
							group, group_path);
				if (index_point < 0) {
					space_mode = FALSE;
					goto group_done;
				}
				show_group_page(group);
				break;
			case ctrl('D'):		/* page down */
				if (!top_base || index_point == top_base - 1)
					break;
				erase_subject_arrow();
				index_point += NOTESLINES / 2;
				if (index_point >= top_base)
					index_point = top_base - 1;
				if (index_point < first_subj_on_screen
				|| index_point >= last_subj_on_screen)
					show_group_page(group);
				else
					draw_subject_arrow();
				break;
			case '-':	/* go to last viewed article */
				if (this_resp < 0) {
					info_message("No last message");
					break;
				}
				index_point = show_page(this_resp,
							group, group_path);
				if (index_point < 0) {
					space_mode = FALSE;
					goto group_done;
				}
				show_group_page(group);
				break;
			case ctrl('U'):		/* page up */
				if (!top_base)
					break;
				erase_subject_arrow();
				index_point -= NOTESLINES / 2;
				if (index_point < 0)
					index_point = 0;
				if (index_point < first_subj_on_screen
				|| index_point >= last_subj_on_screen)
					show_group_page(group);
				else
					draw_subject_arrow();
				break;
			case 'v':
				info_message(cvers);
				break;
			case '!':
				shell_escape();
				show_group_page(group);
				break;
			case ctrl('N'):
			case 'j':		/* line down */
group_down:
				if (!top_base || index_point + 1 >= top_base)
					break;
				if (index_point + 1 >= last_subj_on_screen) {
					index_point++;
					show_group_page(group);
				} else {
					erase_subject_arrow();
					index_point++;
					draw_subject_arrow();
				}
				break;
			case ctrl('P'):
			case 'k':		/* line up */
group_up:
				if (!top_base || !index_point)
					break;
				if (index_point <= first_subj_on_screen) {
					index_point--;
					show_group_page(group);
				} else {
					erase_subject_arrow();
					index_point--;
					draw_subject_arrow();
				}
				break;
			case ctrl('R'):
			case ctrl('L'):
			case ctrl('W'):
			case 'i':		/* return to index */
					show_group_page(group);
					break;
			case '/':		/* forward search */
					search_subject(TRUE, group);
					break;
			case '?':		/* backward search */
					search_subject(FALSE, group);
					break;
			case 'q':		/* quit */
					index_point = -2;
					space_mode = FALSE;
					goto group_done;
			case 'h':
				tass_group_help();
				show_group_page(group);
				break;
			default:
			    info_message("Bad command.  Type 'h' for help.");
		}
	}
group_done:
	fix_new_highest(sav_groupnum);
	update_newsrc(group, my_group[sav_groupnum]);
	if (index_point == -2)
		tass_done(0);
}
/*
 *  Correct highest[] for the group selection page display since
 *  new articles may have been read or marked unread
 */
fix_new_highest(groupnum)
int groupnum;
{
	int i;
	int sum = 0;
	for (i = 0; i < top; i++)
		if (arts[i].unread)
			sum++;
	unread[groupnum] = sum;
}
show_group_page(group)
char *group;
{
	int i;
	int n;
	char resps[10];
	char new_resps;
	int respnum;
#ifdef SIGTSTP
	signal(SIGTSTP, group_susp);
#endif
	ClearScreen();
	printf("%s\r\n", nice_time());	/* time in upper left */
	center_line(1, group);
	if (mail_check()) {			/* you have mail message in */
		MoveCursor(0, 66);		/* upper right */
		printf("you have mail\n");
	}
MoveCursor(INDEX_TOP, 0);
	first_subj_on_screen = (index_point / NOTESLINES) * NOTESLINES;
	if (first_subj_on_screen < 0)
		first_subj_on_screen = 0;
	last_subj_on_screen = first_subj_on_screen + NOTESLINES;
	if (last_subj_on_screen >= top_base) {
		last_subj_on_screen = top_base;
		first_subj_on_screen = top_base - NOTESLINES;
		if (first_subj_on_screen < 0)
			first_subj_on_screen = 0;
	}
	for (i = first_subj_on_screen; i < last_subj_on_screen; i++) {
		if (new_responses(i))
			new_resps = '+';
		else
			new_resps = ' ';
		n = nresp(i);
		if (n)
			sprintf(resps, "%4d", n);
		else
			strcpy(resps, "    ");
respnum = base[i];
		printf("  %4d  %-*s %s %-*s %c\r\n",
				i + 1,
				MAX_SUBJ,
				arts[respnum].subject,
				resps,
				MAX_FROM,
				arts[respnum].from,
				new_resps);
	}
	if (top_base <= 0)
		info_message("*** No Articles ***");
	else if (last_subj_on_screen == top_base)
		info_message("*** End of Articles ***");
	if (top_base > 0)
		draw_subject_arrow();
}
draw_subject_arrow() {
	draw_arrow(INDEX_TOP + (index_point-first_subj_on_screen) );
}
erase_subject_arrow() {
	erase_arrow(INDEX_TOP + (index_point-first_subj_on_screen) );
}
prompt_subject_num(ch, group)
char ch;
char *group;
{
int num;
	clear_message();
	if ((num = parse_num(ch, "Read article> ")) == -1) {
		clear_message();
		return FALSE;
	}
	num--;		/* index from 0 (internal) vs. 1 (user) */
	if (num >= top_base)
		num = top_base - 1;
	if (num >= first_subj_on_screen
	&&  num < last_subj_on_screen) {
		erase_subject_arrow();
		index_point = num;
		draw_subject_arrow();
	} else {
		index_point = num;
		show_group_page(group);
	}
}
search_author(current_art, forward, group)
int current_art;
int forward;
char *group;
{
	char buf[LEN+1];
	char buf2[LEN+1];
	int i;
	int len;
	char *prompt;
clear_message();
	if (forward)
		prompt = "Author search forward: ";
	else
		prompt = "Author search backward: ";
	if (!parse_string(prompt, buf))
		return -1;
	if (strlen(buf))
		strcpy(author_search_string, buf);
	else if (!strlen(author_search_string)) {
		info_message("No search string");
		return -1;
	}
	make_lower(author_search_string, buf);
	len = strlen(buf);
i = current_art;
	do {
		if (forward) {
			i = next_response(i);
			if (i < 0)
				i = 0;
		} else {
			i = prev_response(i);
			if (i < 0)
				i = top - 1;
		}
		make_lower(arts[i].from, buf2);
		if (match(buf, buf2, len))
			return i;
	} while (i != current_art);
	info_message("No match");
	return -1;
}
search_subject(forward, group)
int forward;
char *group;
{
	char buf[LEN+1];
	char buf2[LEN+1];
	int i;
	int len;
	char *prompt;
clear_message();
	if (forward)
		prompt = "/";
	else
		prompt = "?";
	if (!parse_string(prompt, buf))
		return;
	if (strlen(buf))
		strcpy(subject_search_string, buf);
	else if (!strlen(subject_search_string)) {
		info_message("No search string");
		return;
	}
i = index_point;
	make_lower(subject_search_string, buf);
	len = strlen(buf);
	do {
		if (forward)
			i++;
		else
			i--;
		if (i >= top_base)
			i = 0;
		if (i < 0)
			i = top_base - 1;
		make_lower(arts[base[i]].subject, buf2);
		if (match(buf, buf2, len)) {
			if (i >= first_subj_on_screen
			&&  i < last_subj_on_screen) {
				erase_subject_arrow();
				index_point = i;
				draw_subject_arrow();
			} else {
				index_point = i;
				show_group_page(group);
			}
			return;
		}
	} while (i != index_point);
	info_message("No match");
}
/*
 *  Post an original article (not a followup)
 */
post_base(group)
char *group;
{
	FILE *fp;
	char nam[100];
	char ch;
	char subj[LEN+1];
	char buf[200];
	if (!parse_string("Subject: ", subj))
		return;
	if (subj[0] == '\0')
		return;
	setuid(real_uid);
	setgid(real_gid);
	sprintf(nam, "%s/.article", homedir);
	if ((fp = fopen(nam, "w")) == NULL) {
		fprintf(stderr, "can't open %s: ", nam);
		perror("");
		setuid(tass_uid);
		setgid(tass_gid);
		return(FALSE);
	}
	chmod(nam, 0600);
	fprintf(fp, "Subject: %s\n", subj);
	fprintf(fp, "Newsgroups: %s\n", group);
	fprintf(fp, "Distribution: \n");
	if (*my_org)
		fprintf(fp, "Organization: %s\n", my_org);
	fprintf(fp, "\n");
	add_signature(fp, FALSE);
	fclose(fp);
	ch = 'e';
	while (1) {
		switch (ch) {
		case 'e':
			invoke_editor(nam);
			break;
		case 'a':
			setuid(tass_uid);
			setgid(tass_gid);
			return FALSE;
		case 'p':
			printf("\nPosting...  ");
			fflush(stdout);
			sprintf(buf, "%s/inews -h < %s", LIBDIR, nam);
			if (invoke_cmd(buf)) {
				printf("article posted\n");
				fflush(stdout);
				goto post_base_done;
			} else {
				printf("article rejected\n");
				fflush(stdout);
				break;
			}
		}
		do {
			MoveCursor(LINES, 0);
			fputs("abort, edit, post: ", stdout);
			fflush(stdout);
			ch = ReadCh();
		} while (ch != 'a' && ch != 'e' && ch != 'p');
	}
post_base_done:
	setuid(tass_uid);
	setgid(tass_gid);
continue_prompt();
	return(TRUE);
}
/*
 *  Return the number of unread articles there are within a thread
 */
new_responses(thread)
int thread;
{
	int i;
	int sum = 0;
	for (i = base[thread]; i >= 0; i = arts[i].thread)
		if (arts[i].unread)
			sum++;
	return sum;
}
tass_group_help() {
	char title[100];
	sprintf(title, "%s, Index Page Commands", TASS_HEADER);
	ClearScreen();
	center_line(0, title);
MoveCursor(2, 0);
	printf("\t4\tSelect article 4\r\n");
	printf("\t<CR>\tRead current article\r\n");
	printf("\t<TAB>\tView next unread article or group\r\n");
	printf("\t^D^U\tPage down (^U=page up)\r\n");
	printf("\taA\tAuthor search forward (A=backward)\r\n");
	printf("\tc\tMark all articles as read\r\n");
	printf("\tg\tChoose a new group by name\r\n");
	printf("\tjk\tDown (k=up) a line\r\n");
	printf("\tK\tMark thread as read & advance\r\n");
	printf("\tnp\tGo to next (p=previous) group\r\n");
	printf("\tNP\tGo to next (P=previous) unread article\r\n");
	printf("\tq\tQuit\r\n");
	printf("\tsu\tSubscribe (u=unsubscribe) to this group\r\n");
	printf("\tt\tReturn to group selection index\r\n");
	printf("\tw\tPost an article\r\n");
	printf("\t/?\tSearch forward (?=backward) for subject\r\n");
	printf("\t-\tShow last article\r\n");
	center_line(LINES, "-- hit any key --");
	ReadCh();
}
@EOF
chmod 644 group.c
echo x - hashstr.c
cat >hashstr.c <<'@EOF'
#include <stdio.h>
/*
 *  Maintain a table of all strings we have seen.
 *  If a new string comes in, add it to the table and return a pointer
 *  to it.  If we've seen it before, just return the pointer to it.
 *
 *  Usage:  hash_str("some string") returns char *
 *
 *  Spillovers are chained on the end
 */
/*
 *  Arbitrary table size, but make sure it's prime!
 */
/* #define TABLE_SIZE 1409 */
#define TABLE_SIZE 2411
struct hashnode {
	char *s;			/* the string we're saving */
	struct hashnode *next;		/* chain for spillover */
};
struct hashnode *table[ TABLE_SIZE ];
extern char *my_malloc();
struct hashnode *add_string();
char *
hash_str(s)
char *s;
{
	struct hashnode *p;	/* used to descend the spillover structs */
	long h;			/* result of hash:  index into hash table */
	if (s == NULL)
		return(NULL);
	{
		char *t = s;
		h = *t++;
		while (*t)
			h = ((h << 1) ^ *t++) % TABLE_SIZE;
	/*		h = (h * 128 + *t++) % TABLE_SIZE;	*/
	}
p = table[h];
	if (p == NULL) {
		table[h] = add_string(s);
		return table[h]->s;
	}
	while (1) {
		if (strcmp(s, p->s) == 0)
			return(p->s);
		if (p->next == NULL) {
			p->next = add_string(s);
			return p->next->s;
		} else
			p = p->next;
	}
}
struct hashnode *
add_string(s)
char *s;
{
	struct hashnode *p;
	extern char *strcpy();
	int *iptr;
p = (struct hashnode *) my_malloc(sizeof(*p));
	p->next = NULL;
	iptr = (int *) my_malloc(strlen(s) + sizeof(int) + 1);
	*iptr++ = -1;
	p->s = (char *) iptr;
	strcpy(p->s, s);
	return(p);
}
hash_init() {
	int i;
	for (i = 0; i < TABLE_SIZE; i++)
		table[i] = NULL;
}
hash_reclaim() {
	int i;
	struct hashnode *p, *next;
	int *iptr;
	for (i = 0; i < TABLE_SIZE; i++)
		if (table[i] != NULL) {
			p = table[i];
			while (p != NULL) {
				next = p->next;
				iptr = (int *) p->s;
				free(--iptr);
				free(p);
				p = next;
			}
			table[i] = NULL;
		}
}
@EOF
chmod 600 hashstr.c
exit 0
echo x - mail.c
cat >mail.c <<'@EOF'
#include	<stdio.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#define		TRUE		1
#define		FALSE		0
char *mailbox_name = NULL;
off_t mailbox_size;
/*
 *  Record size of mailbox so we can detect if new mail has arrived
 */
mail_setup() {
	struct stat buf;
	extern char *getenv();
	if (mailbox_name == NULL)
		mailbox_name = getenv("MAIL");
	if (mailbox_name == NULL)
		mailbox_size = 0;
	else {
		if (stat(mailbox_name, &buf) >= 0)
			mailbox_size = buf.st_size;
		else
			mailbox_size = 0;
	}
}
/*
 *  Return TRUE if new mail has arrived
 */
mail_check() {
	struct stat buf;
	if (mailbox_name != NULL
	&&  stat(mailbox_name, &buf) >= 0
	&&  mailbox_size < buf.st_size)
		return TRUE;
	return FALSE;
}
@EOF
chmod 640 mail.c
echo x - main.c
cat >main.c <<'@EOF'
/*
 *  Tass, a visual Usenet news reader
 *  (c) Copyright 1990 by Rich Skrenta
 *
 *  Distribution agreement:
 *
 *	You may freely copy or redistribute this software, so long
 *	as there is no profit made from its use, sale, trade or
 *	reproduction.  You may not change this copyright notice,
 *	and it must be included prominently in any copy made.
 */
#include	<stdio.h>
#include	<signal.h>
#include	<termio.h>		/* for struct winsize */
#ifdef SCO_UNIX
#include	<sys/types.h>
#include	<sys/stream.h>
#include	<sys/ptem.h>
#endif
#include	"tass.h"
int LINES, COLS;
int max_active;
struct group_ent *active;		/* active file */
int group_hash[TABLE_SIZE];		/* group name --> active[] */
int *my_group;				/* .newsrc --> active[] */
int *unread;				/* highest art read in group */
int num_active;				/* one past top of active */
int local_top;				/* one past top of my_group */
int update = FALSE;			/* update index files only mode */
struct header *arts;
long *base;
int max_art;
int top = 0;
int top_base;
int tass_uid;
int tass_gid;
int real_uid;
int real_gid;
int local_index; /* do private indexing? */
char *cvers = "Tass 3.2 (c) Copyright 1991 by Rich Skrenta. All rights reserved";
#ifdef SIGTSTP
void
main_susp(i)
int i;
{
	Raw(FALSE);
	putchar('\n');
	signal(SIGTSTP, SIG_DFL);
kill(0, SIGTSTP);
	signal(SIGTSTP, main_susp);
	mail_setup();
	Raw(TRUE);
}
#endif
main(argc, argv)
int argc;
char **argv;
{
	extern int optind, opterr;
	extern char *optarg;
	int errflag = 0;
	int i;
	int c;
	extern char group_search_string[];
	extern char author_search_string[];
	extern char subject_search_string[];
	extern char *is_remote();
	group_search_string[0] = '\0';
	author_search_string[0] = '\0';
	subject_search_string[0] = '\0';
hash_init();
	for (i = 0; i < TABLE_SIZE; i++)
group_hash[i] = -1;
	signal(SIGPIPE, SIG_IGN);
#ifdef SIGTSTP
	signal(SIGTSTP, main_susp);
#endif
	tass_uid = geteuid();
	tass_gid = getegid();
	real_uid = getuid();
	real_gid = getgid();
	init_selfinfo();	/* set up char *'s: homedir, newsrc, etc. */
	init_alloc();		/* allocate initial array sizes */
	if (tass_uid == real_uid) {	/* run out of someone's account */
		local_index = TRUE;	/* index in their home directory */
		mkdir(indexdir, 0755);
	} else			/* we're setuid, index in /usr/spool/news */
		local_index = FALSE;
	while ((c = getopt(argc, argv, "f:u")) != -1) {
		switch(c) {
		case 'f':
			strcpy(newsrc, optarg);
			break;
		case 'u':
			update = TRUE;
			break;
		case '?':
		default:
			errflag++;
		}
	}
	if (errflag) {
	    fprintf(stderr, "usage: tass [options] [newsgroups]\n");
	    fprintf(stderr, "   -f file   use file instead of $HOME/.newsrc\n");
	    fprintf(stderr, "   -u        update index files only\n");
	    exit(1);
	}
	if (!update)
		printf("Tass 3.2%s\n", is_remote());
	nntp_startup();		/* connect to server if we're using nntp */
	read_active();		/* load the active file into active[] */
	if (optind < argc) {
		while (optind < argc) {
			if (add_group(argv[optind], TRUE) < 0)
				fprintf(stderr,
					"group %s not found in active file\n",
								argv[optind]);
			optind++;
		}
	} else
		read_newsrc(TRUE);
	if (update) {			/* index file updater only */
		do_update();
		exit(0);
	}
	if (InitScreen() == FALSE) {
		fprintf(stderr,"Screen initialization failed\n");
		exit(1);
	}
	ScreenSize(&LINES, &COLS);
	Raw(TRUE);
#ifdef TIOCGWINSZ
	{
		struct winsize win;
		if (ioctl(0, TIOCGWINSZ, &win) == 0) {
			if (win.ws_row != 0)
				LINES = win.ws_row - 1;
			if (win.ws_col != 0)
				COLS = win.ws_col;
		}
	}
#endif
	mail_setup();		/* record mailbox size for "you have mail" */
	selection_index();
	tass_done(0);
}
tass_done(ret)
int ret;
{
	nntp_finish();		/* close connection if we're using nntp */
	MoveCursor(LINES, 0);
	printf("\r\n");
	Raw(FALSE);
	exit(ret);
}
/*
 *  Dynamic table management
 *  These settings are memory conservative:  small initial allocations
 *  and a 50% expansion on table overflow.  A fast vm system with
 *  much memory might want to start with higher initial allocations
 *  and a 100% expansion on overflow, especially for the arts[] array.
 */
init_alloc() {
max_active = 100; /* initial alloc */
	active = (struct group_ent *) my_malloc(sizeof(*active) * max_active);
	my_group = (int *) my_malloc(sizeof(int) * max_active);
	unread = (int *) my_malloc(sizeof(int) * max_active);
max_art = 300; /* initial alloc */
	arts = (struct header *) my_malloc(sizeof(*arts) * max_art);
	base = (long *) my_malloc(sizeof(long) * max_art);
}
expand_art() {
max_art += max_art / 2; /* increase by 50% */
	arts = (struct header *) my_realloc(arts, sizeof(*arts) * max_art);
	base = (long *) my_realloc(base, sizeof(long) * max_art);
}
expand_active() {
max_active += max_active / 2; /* increase by 50% */
	active = (struct group_ent *) my_realloc(active,
						sizeof(*active) * max_active);
	my_group = (int *) my_realloc(my_group, sizeof(int) * max_active);
	unread = (int *) my_realloc(unread, sizeof(int) * max_active);
}
/*
 *  Load the active file into active[]
 */
read_active()
{
	FILE *fp;
	char *p, *q;
	char buf[200];
	long h;
	int i;
	extern long hash_groupname();
	FILE *open_active_fp();
num_active = 0;
	fp = open_active_fp();
	if (fp == NULL) {
		fprintf(stderr, "can't get active file\n");
		exit(1);
	}
	while (fgets(buf, 200, fp) != NULL) {
		for (p = buf; *p && *p != ' '; p++) ;
		if (*p != ' ') {
			fprintf(stderr, "active file corrupt\n");
			continue;
		}
		*p++ = '\0';
		if (num_active >= max_active)
			expand_active();
h = hash_groupname(buf);
		if (group_hash[h] == -1)
			group_hash[h] = num_active;
		else {				/* hash linked list chaining */
			for (i = group_hash[h]; active[i].next >= 0;
						i = active[i].next) {
				if (strcmp(active[i].name, buf) == 0)
					goto read_active_continue;
							/* kill dups */
			}
			if (strcmp(active[i].name, buf) == 0)
				goto read_active_continue;
			active[i].next = num_active;
		}
		for (q = p; *q && *q != ' '; q++) ;
		if (*q != ' ') {
			fprintf(stderr, "active file corrupt\n");
			continue;
		}
		active[num_active].name = str_save(buf);
		active[num_active].max = atol(p);
		active[num_active].min = atol(q);
		active[num_active].next = -1;	    /* hash chaining */
		active[num_active].flag = NOTGOT;   /* not in my_group[] yet */
num_active++;
read_active_continue:;
}
	fclose(fp);
}
/*
 *  Read $HOME/.newsrc into my_group[].  my_group[] ints point to
 *  active[] entries.  Sub_only determines whether we just read
 *  subscribed groups or all of them.
 */
read_newsrc(sub_only)
int sub_only;		/* TRUE=subscribed groups only, FALSE=all groups */
{
	FILE *fp;
	char *p;
	char buf[8192];
	char c;
	int i;
local_top = 0;
	fp = fopen(newsrc, "r");
	if (fp == NULL) {		/* attempt to make a .newsrc */
		for (i = 0; i < num_active; i++) {
			if (local_top >= max_active)
				expand_active();
			my_group[local_top] = i;
			active[i].flag = 0;
			unread[local_top] = -1;
			local_top++;
		}
		write_newsrc();
		return;
	}
	while (fgets(buf, 8192, fp) != NULL) {
		p = buf;
		while (*p && *p != '\n' && *p != ' ' && *p != ':' && *p != '!')
			p++;
		c = *p;
		*p++ = '\0';
		if (c == '!' && sub_only)
			continue;		/* unsubscribed */
		if ((i = add_group(buf, FALSE)) < 0) {
		    fprintf(stderr, "group %s not found in active file\n", buf);
		    continue;
		}
		if (c != '!')		/* if we're subscribed to it */
			active[my_group[i]].flag |= SUBS;
		unread[i] = parse_unread(p, my_group[i]);
	}
	fclose(fp);
}
/*
 *  Write a new newsrc from my_group[] and active[]
 *  Used to a create a new .newsrc if there isn't one already, or when
 *  the newsrc is reset.
 */
write_newsrc() {
	FILE *fp;
	int i;
	setuid(real_uid);	/* become the user to write in his */
	setgid(real_gid);	/* home directory */
	fp = fopen(newsrc, "w");
	if (fp == NULL)
		goto write_newsrc_done;
	for (i = 0; i < num_active; i++)
		fprintf(fp, "%s: \n", active[i].name);
fclose(fp);
write_newsrc_done:
	setuid(tass_uid);
	setgid(tass_gid);
}
/*
 *  Load the sequencer rang lists and mark arts[] according to the
 *  .newsrc info for a particular group.  i.e.  rec.arts.comics: 1-94,97
 */
read_newsrc_line(group)
char *group;
{
	FILE *fp;
	char buf[8192];
	char *p;
fp = fopen(newsrc, "r");
	if (fp == NULL)
		return;
	while (fgets(buf, 8192, fp) != NULL) {
		p = buf;
		while (*p && *p != '\n' && *p != ' ' && *p != ':' && *p != '!')
			p++;
		*p++ = '\0';
		if (strcmp(buf, group) != 0)
			continue;
		parse_seq(p);
		break;
	}
	fclose(fp);
}
/*
 *  For our current group, update the sequencer information in .newsrc
 */
update_newsrc(group, groupnum)
char *group;
int groupnum;			/* index into active[] for this group */
{
	FILE *fp;
	FILE *newfp;
	char buf[8192];
	char *p;
	char c;
	int gotit = FALSE;
	setuid(real_uid);
	setgid(real_gid);
	fp = fopen(newsrc, "r");
	newfp = fopen(newnewsrc, "w");
	if (newfp == NULL)
		goto update_done;
	if (fp != NULL) {
		while (fgets(buf, 8192, fp) != NULL) {
			for (p = buf; *p; p++)
				if (*p == '\n') {
					*p = '\0';
					break;
				}
			p = buf;
			while (*p && *p != ' ' && *p != ':' && *p != '!')
					p++;
			c = *p;
			if (c != '\0')
				*p++ = '\0';
			if (c != '!')
				c = ':';
			if (strcmp(buf, group) == 0) {
				fprintf(newfp, "%s%c ", buf, c);
				gotit = TRUE;
				print_seq(newfp, groupnum);
				fprintf(newfp, "\n");
			} else
				fprintf(newfp, "%s%c%s\n", buf, c, p);
		}
		fclose(fp);
	}
	fclose(newfp);
	unlink(newsrc);
	link(newnewsrc, newsrc);
	unlink(newnewsrc);
update_done:
	setuid(tass_uid);
	setgid(tass_gid);
}
/*
 *  Subscribe/unsubscribe to a group in .newsrc.  ch should either be
 *  '!' to unsubscribe or ':' to subscribe.  num is the group's index
 *  in active[].
 */
subscribe(group, ch, num, out_seq)
char *group;
char ch;
int num;
int out_seq;				/* output sequencer info? */
{
	FILE *fp;
	FILE *newfp;
	char buf[8192];
	char *p;
	char c;
	int gotit = FALSE;
	if (ch == '!')
		active[num].flag &= ~SUBS;
	else
		active[num].flag |= SUBS;
	setuid(real_uid);
	setgid(real_gid);
	fp = fopen(newsrc, "r");
	newfp = fopen(newnewsrc, "w");
	if (newfp == NULL)
		goto subscribe_done;
	if (fp != NULL) {
		while (fgets(buf, 8192, fp) != NULL) {
			for (p = buf; *p; p++)
				if (*p == '\n') {
					*p = '\0';
					break;
				}
			p = buf;
			while (*p && *p != ' ' && *p != ':' && *p != '!')
					p++;
			c = *p;
			if (c != '\0')
				*p++ = '\0';
			if (c != '!')
				c = ':';
			if (strcmp(buf, group) == 0) {
				fprintf(newfp, "%s%c%s\n", buf, ch, p);
				gotit = TRUE;
			} else
				fprintf(newfp, "%s%c%s\n", buf, c, p);
		}
		fclose(fp);
	}
	if (!gotit) {
		if (out_seq) {
			fprintf(newfp, "%s%c ", group, ch);
			print_seq(newfp, num);
			fprintf(newfp, "\n");
		} else
			fprintf(newfp, "%s%c\n", group, ch);
	}
	fclose(newfp);
	unlink(newsrc);
	link(newnewsrc, newsrc);
	unlink(newnewsrc);
subscribe_done:
	setuid(tass_uid);
	setgid(tass_gid);
}
print_seq(fp, groupnum)
FILE *fp;
int groupnum;			/* index into active[] for this group */
{
	int i;
	int flag = FALSE;
	if (top <= 0) {
		if (active[groupnum].min > 1) {
			fprintf(fp, "1-%ld", active[groupnum].min);
			fflush(fp);
		}
		return;
	}
	i = 0;
	if (arts[0].artnum > 1) {
		for (; i < top && !arts[i].unread; i++) ;
		if (i > 0)
			fprintf(fp, "1-%ld", arts[i-1].artnum);
		else
			fprintf(fp, "1-%ld", arts[0].artnum - 1);
		flag = TRUE;
	}
	for (; i < top; i++) {
		if (!arts[i].unread) {
			if (flag)
				fprintf(fp, ",");
			else
				flag = TRUE;
			fprintf(fp, "%ld", arts[i].artnum);
			if (i+1 < top && !arts[i+1].unread) {
				while (i+1 < top && !arts[i+1].unread)
					i++;
				fprintf(fp, "-%ld", arts[i].artnum);
			}
		}
	}
	if (!flag && active[groupnum].min > 1)
		fprintf(fp, "1-%ld", active[groupnum].min);
	fflush(fp);
}
parse_seq(s)
char *s;
{
	long low, high;
	int i;
	while (*s) {
		while (*s && (*s < '0' || *s > '9'))
			s++;
		if (*s && *s >= '0' && *s <= '9') {
			low = atol(s);
			while (*s && *s >= '0' && *s <= '9')
				s++;
			if (*s == '-') {
				s++;
				high = atol(s);
				while (*s && *s >= '0' && *s <= '9')
					s++;
			}  else
				high = low;
			for (i = 0; i < top; i++)
				if (arts[i].artnum >= low &&
				    arts[i].artnum <= high)
					arts[i].unread = 0;
		}
	}
}
parse_unread(s, groupnum)
char *s;
int groupnum;			/* index for group in active[] */
{
	long low, high;
	long last_high;
	int i;
	int sum = 0;
	int gotone = FALSE;
	int n;
/*
 *  Read the first range from the .newsrc sequencer information.  If the
 *  top of the first range is higher than what the active file claims is
 *  the bottom, use it as the new bottom instead
 */
	high = 0;
	if (*s) {
		while (*s && (*s < '0' || *s > '9'))
			s++;
		if (*s && *s >= '0' && *s <= '9') {
			low = atol(s);
			while (*s && *s >= '0' && *s <= '9')
				s++;
			if (*s == '-') {
				s++;
				high = atol(s);
				while (*s && *s >= '0' && *s <= '9')
					s++;
			}  else
				high = low;
			gotone = TRUE;
		}
	}
	if (high < active[groupnum].min)
		high = active[groupnum].min;
	while (*s) {
		last_high = high;
		while (*s && (*s < '0' || *s > '9'))
			s++;
		if (*s && *s >= '0' && *s <= '9') {
			low = atol(s);
			while (*s && *s >= '0' && *s <= '9')
				s++;
			if (*s == '-') {
				s++;
				high = atol(s);
				while (*s && *s >= '0' && *s <= '9')
					s++;
			}  else
				high = low;
			if (low > last_high)	/* otherwise seq out of order */
				sum += (low - last_high) - 1;
		}
	}
	if (gotone) {
		if (active[groupnum].max > high)
			sum += active[groupnum].max - high;
		return sum;
	}
	n = (int) (active[groupnum].max - active[groupnum].min);
	if (n < 2)
		return 0;
	return -1;
}
get_line_unread(group, groupnum)
char *group;
int groupnum;				/* index for group in active[] */
{
	FILE *fp;
	char buf[8192];
	char *p;
	int ret = -1;
	fp = fopen(newsrc, "r");
	if (fp == NULL)
		return -1;
	while (fgets(buf, 8192, fp) != NULL) {
		p = buf;
		while (*p && *p != '\n' && *p != ' ' && *p != ':' && *p != '!')
			p++;
		*p++ = '\0';
		if (strcmp(buf, group) != 0)
			continue;
		ret = parse_unread(p, groupnum);
		break;
	}
	fclose(fp);
	return ret;
}
reset_newsrc()
{
	FILE *fp;
	FILE *newfp;
	char buf[8192];
	char *p;
	char c;
	int gotit = FALSE;
	int i;
	setuid(real_uid);
	setgid(real_gid);
	fp = fopen(newsrc, "r");
	newfp = fopen(newnewsrc, "w");
	if (newfp == NULL)
		goto update_done;
	if (fp != NULL) {
		while (fgets(buf, 8192, fp) != NULL) {
			for (p = buf; *p && *p != '\n'; p++) ;
			*p = '\0';
			p = buf;
			while (*p && *p != ' ' && *p != ':' && *p != '!')
					p++;
			c = *p;
			if (c != '\0')
				*p++ = '\0';
			if (c != '!')
				c = ':';
			fprintf(newfp, "%s%c\n", buf, c);
		}
		fclose(fp);
	}
	fclose(newfp);
	unlink(newsrc);
	link(newnewsrc, newsrc);
	unlink(newnewsrc);
update_done:
	setuid(tass_uid);
	setgid(tass_gid);
	for (i = 0; i < local_top; i++)
		unread[i] = -1;
}
delete_group(group)
char *group;
{
	FILE *fp;
	FILE *newfp;
	char buf[8192];
	char *p;
	char c;
	int gotit = FALSE;
	FILE *del;
	setuid(real_uid);
	setgid(real_gid);
	fp = fopen(newsrc, "r");
	newfp = fopen(newnewsrc, "w");
	if (newfp == NULL)
		goto del_done;
	del = fopen(delgroups, "a+");
	if (del == NULL)
		goto del_done;
	if (fp != NULL) {
		while (fgets(buf, 8192, fp) != NULL) {
			for (p = buf; *p && *p != '\n'; p++) ;
			*p = '\0';
			p = buf;
			while (*p && *p != ' ' && *p != ':' && *p != '!')
					p++;
			c = *p;
			if (c != '\0')
				*p++ = '\0';
			if (c != '!')
				c = ':';
			if (strcmp(buf, group) == 0) {
				fprintf(del, "%s%c%s\n", buf, c, p);
				gotit = TRUE;
			} else
				fprintf(newfp, "%s%c%s\n", buf, c, p);
		}
		fclose(fp);
	}
fclose(newfp);
	if (!gotit)
		fprintf(del, "%s! \n", group);
	fclose(del);
	unlink(newsrc);
	link(newnewsrc, newsrc);
	unlink(newnewsrc);
del_done:
	setuid(tass_uid);
	setgid(tass_gid);
}
undel_group() {
	FILE *del;
	FILE *newfp;
	FILE *fp;
	char buf[2][8192];
	char *p;
	int which = 0;
	long h;
	extern int cur_groupnum;
	int i, j;
	char c;
	setuid(real_uid);
	setgid(real_gid);
	del = fopen(delgroups, "r");
	if (del == NULL) {
		setuid(tass_uid);
		setgid(tass_gid);
		return FALSE;
	}
	unlink(delgroups);
	newfp = fopen(delgroups, "w");
	if (newfp == NULL) {
		setuid(tass_uid);
		setgid(tass_gid);
		return FALSE;
	}
	buf[0][0] = '\0';
	buf[1][0] = '\0';
	while (fgets(buf[which], 8192, del) != NULL) {
		which = !which;
		if (*buf[which])
			fputs(buf[which], newfp);
	}
	fclose(del);
	fclose(newfp);
	which = !which;
	if (!*buf[which]) {
		setuid(tass_uid);
		setgid(tass_gid);
		return FALSE;
	}
	for (p = buf[which]; *p && *p != '\n'; p++) ;
	*p = '\0';
	p = buf[which];
	while (*p && *p != ' ' && *p != ':' && *p != '!')
		p++;
	c = *p;
	if (c != '\0')
		*p++ = '\0';
	if (c != '!')
		c = ':';
	{			/* find the hash of the group name */
		char *t = buf[which];
		h = *t++;
		while (*t)
			h = (h * 64 + *t++) % TABLE_SIZE;
	}
	for (i = group_hash[h]; i >= 0; i = active[i].next) {
		if (strcmp(buf[which], active[i].name) == 0) {
			for (j = 0; j < local_top; j++)
				if (my_group[j] == i) {
					setuid(tass_uid);
					setgid(tass_gid);
					return j;
				}
			active[i].flag &= ~NOTGOT;   /* mark that we got it */
			if (c != '!')
				active[i].flag |= SUBS;
			if (local_top >= max_active)
				expand_active();
			local_top++;
			for (j = local_top; j > cur_groupnum; j--) {
				my_group[j] = my_group[j-1];
				unread[j] = unread[j-1];
			}
			my_group[cur_groupnum] = i;
			unread[cur_groupnum] = parse_unread(p, i);
			fp = fopen(newsrc, "r");
			if (fp == NULL) {
				setuid(tass_uid);
				setgid(tass_gid);
				return FALSE;
			}
			newfp = fopen(newnewsrc, "w");
			if (newfp == NULL) {
				fclose(fp);
				setuid(tass_uid);
				setgid(tass_gid);
				return FALSE;
			}
			i = 0;
			while (fgets(buf[!which], 8192, fp) != NULL) {
				for (p = buf[!which]; *p && *p != '\n'; p++) ;
				*p = '\0';
				p = buf[!which];
				while (*p && *p!=' ' && *p != ':' && *p != '!')
					p++;
				c = *p;
				if (c != '\0')
					*p++ = '\0';
				if (c != '!')
					c = ':';
				while (i < cur_groupnum) {
					if (strcmp(buf[!which],
					  active[my_group[i]].name) == 0) {
						fprintf(newfp, "%s%c%s\n",
							buf[!which], c, p);
						goto foo_cont;
					}
					i++;
				}
				fprintf(newfp, "%s%c%s\n", buf[which], c, p);
				fprintf(newfp, "%s%c%s\n", buf[!which], c, p);
				break;
foo_cont:;
			}
			while (fgets(buf[!which], 8192, fp) != NULL)
				fputs(buf[!which], newfp);
			fclose(newfp);
			fclose(fp);
			unlink(newsrc);
			link(newnewsrc, newsrc);
			unlink(newnewsrc);
			setuid(tass_uid);
			setgid(tass_gid);
			return TRUE;
		}
	}
	setuid(tass_uid);
	setgid(tass_gid);
	return FALSE;
}
mark_group_read(group, groupnum)
char *group;
int groupnum;			/* index into active[] for this group */
{
	FILE *fp;
	FILE *newfp;
	char buf[8192];
	char *p;
	char c;
	int gotit = FALSE;
	if (active[groupnum].max < 2)
		return;
	setuid(real_uid);
	setgid(real_gid);
	fp = fopen(newsrc, "r");
	newfp = fopen(newnewsrc, "w");
	if (newfp == NULL)
		goto mark_group_read_done;
	if (fp != NULL) {
		while (fgets(buf, 8192, fp) != NULL) {
			for (p = buf; *p; p++)
				if (*p == '\n') {
					*p = '\0';
					break;
				}
			p = buf;
			while (*p && *p != ' ' && *p != ':' && *p != '!')
					p++;
			c = *p;
			if (c != '\0')
				*p++ = '\0';
			if (c != '!')
				c = ':';
			if (strcmp(buf, group) == 0) {
				fprintf(newfp, "%s%c 1-%ld\n", buf, c,
						active[groupnum].max);
				gotit = TRUE;
			} else
				fprintf(newfp, "%s%c%s\n", buf, c, p);
		}
		fclose(fp);
	}
	fclose(newfp);
	unlink(newsrc);
	link(newnewsrc, newsrc);
	unlink(newnewsrc);
mark_group_read_done:
	setuid(tass_uid);
	setgid(tass_gid);
}
long
hash_groupname(buf)		/* hash group name for fast lookup later */
char *buf;
{
	char *t = buf;
	unsigned long h;
	h = *t++;
	while (*t)
		h = ((h << 1) ^ *t++) % TABLE_SIZE;
/* h = (h * 64 + *t++) % TABLE_SIZE; */
	return h;
}
#ifdef M_XENIX
mkdir(path, mode)
char *path;
int mode;
{
	char buf[200];
	sprintf(buf, "mkdir %s", path);
	system(buf);
	chmod(path, mode);
}
#endif
@EOF
chmod 644 main.c
echo x - misc.c
cat >misc.c <<'@EOF'
#include	<stdio.h>
#include	<ctype.h>
#include	<signal.h>
#include	<pwd.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	"tass.h"
char active_file[LEN];
char homedir[LEN];
char userid[LEN];
char delgroups[LEN];
char newsrc[LEN];
char newnewsrc[LEN];
char indexdir[LEN];
char my_org[LEN];		/* organization */
char sig[LEN];
char signature[LEN];
/*
 *  Which base note (an index into base[]) does a respnum
 *  (an index into arts[]) corresponsd to?
 *
 *  In other words, base[] points to an entry in arts[] which is
 *  the head of a thread, linked with arts[].thread.  For any q: arts[q],
 *  find i such that base[i]->arts[n]->arts[o]->...->arts[q]
 */
which_base(n)
int n;
{
	int i, j;
	for (i = 0; i < top_base; i++)
		for (j = base[i]; j >= 0; j = arts[j].thread)
			if (j == n)
				return i;
	fprintf(stderr, "can't find base article\n");
	return 0;
}
/*
 *  Find how deep in a thread a response is.  Start counting at zero
 */
which_resp(n)
int n;
{
	int i, j;
	int num = 0;
i = which_base(n);
	for (j = base[i]; j != -1; j = arts[j].thread)
		if (j == n)
			break;
		else
			num++;
	return num;
}
/*
 *  Given an index into base[], find the number of responses for
 *  that basenote
 */
nresp(n)
int n;
{
	int i;
	int oldi = -3;
	int sum = 0;
assert(n < top_base);
	for (i = base[n]; i != -1; i = arts[i].thread) {
		assert(i != -2);
		assert(i != oldi);
		oldi = i;
		sum++;
	}
	return sum - 1;
}
asfail(file, line, cond)
char	*file;
int	line;
char	*cond;
{
	fprintf(stderr, "tass: assertion failure: %s (%d): %s\n",
							file, line, cond);
	exit(1);
}
/*
 * init_selfinfo
 *   Deterimines users home directory, userid, and a path
 *   for an rc file in the home directory
 */
init_selfinfo()
{
	struct passwd *myentry;
	extern struct passwd *getpwuid();
	struct stat sb;
	char nam[LEN];
	char *p;
	extern char *getenv();
	FILE *fp;
	myentry = getpwuid(getuid());
	strcpy(userid, myentry->pw_name);
	strcpy(homedir, myentry->pw_dir);
	sprintf(signature, "%s/.signature", homedir);
	sprintf(sig, "%s/.Sig", homedir);
	sprintf(newsrc, "%s/.newsrc", homedir);
	sprintf(newnewsrc, "%s/.newnewsrc", homedir);
	sprintf(delgroups, "%s/.delgroups", homedir);
	sprintf(indexdir, "%s/.tindx", homedir);
	sprintf(active_file, "%s/active", LIBDIR);
	if (stat(active_file, &sb) >= 0)
		goto got_active;
/*
 *  I hate forgetting to define LIBDIR correctly.  Guess a
 *  couple of likely places if it's not where LIBDIR says it is.
 */
	strcpy(active_file, "/usr/lib/news/active");
	if (stat(active_file, &sb) >= 0)
		goto got_active;
	strcpy(active_file, "/usr/local/lib/news/active");
	if (stat(active_file, &sb) >= 0)
		goto got_active;
	strcpy(active_file, "/usr/public/lib/news/active");
	if (stat(active_file, &sb) >= 0)
		goto got_active;
/*
 *  Oh well.  Revert to what LIBDIR says it is to produce a
 *  useful error message when read_active() fails later.
 */
sprintf(active_file, "%s/active", LIBDIR);
got_active:
	*my_org = '\0';
	p = getenv("ORGANIZATION");
	if (p != NULL) {
		strcpy(my_org, p);
		goto got_org;
	}
	sprintf(nam, "%s/organization", LIBDIR);
	fp = fopen(nam, "r");
	if (fp == NULL) {
		sprintf(nam, "/usr/lib/news/organization");
		fp = fopen(nam, "r");
	}
	if (fp == NULL) {
		sprintf(nam, "/usr/local/lib/news/organization");
		fp = fopen(nam, "r");
	}
	if (fp == NULL) {
		sprintf(nam, "/usr/public/lib/news/organization");
		fp = fopen(nam, "r");
	}
	if (fp == NULL) {
		sprintf(nam, "/etc/organization");
		fp = fopen(nam, "r");
	}
	if (fp != NULL) {
		if (fgets(my_org, LEN, fp) != NULL) {
			for (p = my_org; *p && *p != '\n'; p++) ;
			*p = '\0';
		}
		fclose(fp);
	}
got_org:;
}
char *
my_malloc(size)
unsigned size;
{
	char *p;
	extern char *malloc();
	p = malloc(size);
	if (p == NULL) {
		fprintf(stderr, "tass: out of memory\n");
		exit(1);
	}
	return p;
}
char *
my_realloc(p, size)
char *p;
unsigned size;
{
	extern char *malloc();
	extern char *realloc();
	if (p == NULL)
		p = malloc(size);
	else
		p = realloc(p, size);
	if (p == NULL) {
		fprintf(stderr, "tass: out of memory\n");
		exit(1);
	}
	return p;
}
char *
str_save(s)
char *s;
{
char *p;
	assert(s != NULL);
	
	p = my_malloc(strlen(s) + 1);
	strcpy(p, s);
	return(p);
}
copy_fp(a, b, prefix)
FILE *a;
FILE *b;
char *prefix;
{
	char buf[8192];
	while (fgets(buf, 8192, a) != NULL)
		fprintf(b, "%s%s", prefix, buf);
}
char *
get_val(env, def)
char *env;		/* Environment variable we're looking for	*/
char *def;		/* Default value if no environ value found	*/
{
	extern char *getenv();
	char *ptr;
	if ((ptr = getenv(env)) != NULL)
		return(ptr);
	else
		return(def);
}
invoke_editor(nam)
char *nam;
{
	char buf[200];
	static int first = TRUE;
	static char editor[200];
	int ret;
	if (first) {
		strcpy(editor, get_val("EDITOR", DEF_EDITOR));
		first = FALSE;
	}
	sprintf(buf, "%s %s", editor, nam);
	printf("\r%s\n", buf);
	ret = invoke_cmd(buf);
	setuid(real_uid);
	setgid(real_gid);
	return ret;
}
invoke_cmd(nam)
char *nam;
{
	int ret;
#ifdef SIGTSTP
	void (*susp)();
#endif
	Raw(FALSE);
	setuid(real_uid);
	setgid(real_gid);
#ifdef SIGTSTP
	susp = signal(SIGTSTP, SIG_DFL);
#endif
ret = system(nam);
#ifdef SIGTSTP
	signal(SIGTSTP, susp);
#endif
	setuid(tass_uid);
	setgid(tass_gid);
	Raw(TRUE);
	return ret == 0;
}
shell_escape() {
	char shell[LEN];
	char *p;
#ifdef SIGTSTP
	void (*susp)();
#endif
	if (!parse_string("!", shell))
		strcpy(shell, get_val("SHELL", "/bin/sh"));
for (p = shell; *p && (*p == ' ' || *p == '\t'); p++) ;
	if (!*p)
		strcpy(shell, get_val("SHELL", "/bin/sh"));
	
	Raw(FALSE);
	setuid(real_uid);
	setgid(real_gid);
fputs("\r\n", stdout);
#ifdef SIGTSTP
	susp = signal(SIGTSTP, SIG_DFL);
#endif
system(p);
#ifdef SIGTSTP
	signal(SIGTSTP, susp);
#endif
	setuid(tass_uid);
	setgid(tass_gid);
Raw(TRUE);
	continue_prompt();
	mail_setup();
}
/*
 *  Find the previous response.  Go to the last response in the previous
 *  thread if we go past the beginning of this thread.
 */
prev_response(n)
int n;
{
	int resp;
	int i;
resp = which_resp(n);
	if (resp > 0)
		return choose_resp( which_base(n), resp-1 );
i = which_base(n) - 1;
	if (i < 0)
		return -1;
	return choose_resp( i, nresp(i) );
}
/*
 *  Find the next response.  Go to the next basenote if there
 *  are no more responses in this thread
 */
next_response(n)
int n;
{
	int i;
	if (arts[n].thread >= 0)
		return arts[n].thread;
i = which_base(n) + 1;
	if (i >= top_base)
		return -1;
	return base[i];
}
/*
 *  Given a respnum (index into arts[]), find the respnum of the
 *  next basenote
 */
next_basenote(n)
int n;
{
	int i;
	i = which_base(n) + 1;
	if (i >= top_base)
		return -1;
	return base[i];
}
/*
 *  Find the next unread response in this group 
 */
next_unread(n)
int n;
{
	while (n >= 0) {
		if (arts[n].unread == 1)
			return n;
		n = next_response(n);
	}
	return -1;
}
/*
 *  Find the previous unread response in this thread
 */
prev_unread(n)
int n;
{
	while (n >= 0) {
		if (arts[n].unread == 1)
			return n;
		n = prev_response(n);
	}
	return -1;
}
add_signature(fp, flag)
FILE *fp;
int flag;
{
	FILE *sigf;
	sigf = fopen(signature, "r");
	if (sigf != NULL) {
		if (flag) {
			fprintf(fp, "\n--\n");
			copy_fp(sigf, fp, "");
		}
		fclose(sigf);
		return;
	}
	sigf = fopen(sig, "r");
	if (sigf != NULL) {
		fprintf(fp, "\n--\n");
		copy_fp(sigf, fp, "");
		fclose(sigf);
	}
}
make_lower(s, t)
char *s;
char *t;
{
	while (*s) {
		if (isupper(*s))
			*t = tolower(*s);
		else
			*t = *s;
		s++;
		t++;
	}
	*t = 0;
}
match(s, t, n)
char *s;
char *t;
int n;
{
	while (*t) {
		if (*s == *t && strncmp(s, t, n) == 0)
			return TRUE;
		t++;
	}
	return FALSE;
}
@EOF
chmod 644 misc.c
echo x - nntp.h
cat >nntp.h <<'@EOF'
/* nntp.h -- nntp support for tass */
/* Changed a bit so nntp knows about Tass */
/*
 *  This file is originally from the nntp 1.5 source,
 *  but modified a bit
 */
#define NNTP_SERVER "/etc/nntpserver"
/*
 *  External routine declarations
 */
extern char *getserverbyfile();
extern int server_init();
extern int get_tcp_socket();
extern int handle_server_response();
extern void put_server();
extern int get_server();
extern void close_server();
/*
 *  External file descriptors for the server connection
 */
extern FILE *ser_wr_fp;
extern FILE *ser_wr_fp;
/*
 * Response codes for NNTP server
 *
 * @(#)nntp.h	1.7	(Berkeley) 1/11/88
 *
 * First digit:
 *
 *	1xx	Informative message
 *	2xx	Command ok
 *	3xx	Command ok so far, continue
 *	4xx	Command was correct, but couldn't be performed
 *		for some specified reason.
 *	5xx	Command unimplemented, incorrect, or a
 *		program error has occured.
 *
 * Second digit:
 *
 *	x0x	Connection, setup, miscellaneous
 *	x1x	Newsgroup selection
 *	x2x	Article selection
 *	x3x	Distribution
 *	x4x	Posting
 */
#define	CHAR_INF	'1'
#define	CHAR_OK		'2'
#define	CHAR_CONT	'3'
#define	CHAR_ERR	'4'
#define	CHAR_FATAL	'5'
#define	INF_HELP	100	/* Help text on way */
#define	INF_DEBUG	199	/* Debug output */
#define	OK_CANPOST	200	/* Hello; you can post */
#define	OK_NOPOST	201	/* Hello; you can't post */
#define	OK_SLAVE	202	/* Slave status noted */
#define	OK_GOODBYE	205	/* Closing connection */
#define	OK_GROUP	211	/* Group selected */
#define	OK_GROUPS	215	/* Newsgroups follow */
#define OK_TASSINDEX 218 /* Tass index follows */
#define	OK_ARTICLE	220	/* Article (head & body) follows */
#define	OK_HEAD		221	/* Head follows */
#define	OK_BODY		222	/* Body follows */
#define	OK_NOTEXT	223	/* No text sent -- stat, next, last */
#define	OK_NEWNEWS	230	/* New articles by message-id follow */
#define	OK_NEWGROUPS	231	/* New newsgroups follow */
#define	OK_XFERED	235	/* Article transferred successfully */
#define	OK_POSTED	240	/* Article posted successfully */
#define CONT_XFER	335	/* Continue to send article */
#define	CONT_POST	340	/* Continue to post article */
#define	ERR_GOODBYE	400	/* Have to hang up for some reason */
#define	ERR_NOGROUP	411	/* No such newsgroup */
#define	ERR_NCING	412	/* Not currently in newsgroup */
#define ERR_NOTASS 418 /* No tass index for this group */
#define	ERR_NOCRNT	420	/* No current article selected */
#define	ERR_NONEXT	421	/* No next article in this group */
#define	ERR_NOPREV	422	/* No previous article in this group */
#define	ERR_NOARTIG	423	/* No such article in this group */
#define ERR_NOART	430	/* No such article at all */
#define ERR_GOTIT	435	/* Already got that article, don't send */
#define ERR_XFERFAIL	436	/* Transfer failed */
#define	ERR_XFERRJCT	437	/* Article rejected, don't resend */
#define	ERR_NOPOST	440	/* Posting not allowed */
#define	ERR_POSTFAIL	441	/* Posting failed */
#define	ERR_COMMAND	500	/* Command not recognized */
#define	ERR_CMDSYN	501	/* Command syntax error */
#define	ERR_ACCESS	502	/* Access to server denied */
#define ERR_FAULT	503	/* Program fault, command not performed */
/* RFC 977 defines this; don't change it. */
#define	NNTP_STRLEN	512
@EOF
chmod 644 nntp.h
echo x - nntp_open.c
cat >nntp_open.c <<'@EOF'
#include	<stdio.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	"nntp.h"
#include	"tass.h"
char *
is_remote() {
	return " (remote)";
}
nntp_startup() {
	char *server_name;
	int ret;
	extern char *getenv();
	server_name = getserverbyfile(NNTP_SERVER);
	if (server_name == NULL) {
		fprintf(stderr, "Can't get nntp server name\n");
		fprintf(stderr, "Either put the name in the file %s, or put\n",
							NNTP_SERVER);
		fprintf(stderr, "it in the environment variable NNTPSERVER\n");
		exit(1);
	}
ret = server_init(server_name);
	switch (ret) {
	case OK_CANPOST:
	case OK_NOPOST:
		break;
	case -1:
		fprintf(stderr, "failed to connect to server\n");
		exit(1);
	default:
		fprintf(stderr, "rejected by server, nntp error %d\n", ret);
		exit(1);
	}
}
nntp_finish() {
	close_server();
}
/*
 *  get_respcode
 *  get a response code from the server and return it to the caller
 */
int get_respcode() {
	char line[NNTP_STRLEN];
	if (get_server(line, NNTP_STRLEN) == -1) {
		fprintf(stderr, "connection to server broken\n");
		tass_done(1);
	}
	return atoi(line);
}
stuff_nntp(fnam)
char *fnam;
{
	FILE *fp;
	char line[NNTP_STRLEN];
	extern char *mktemp();
	struct stat sb;
	extern long note_size;
	strcpy(fnam, "/tmp/tass_nntpXXXXXX");
	mktemp(fnam);
	fp = fopen(fnam, "w");
	if (fp == NULL) {
		fprintf(stderr, "stuff_nntp: can't open %s: ", fnam);
		perror("");
		return FALSE;
	}
	while (1) {
		if (get_server(line, NNTP_STRLEN) == -1) {
			fprintf(stderr, "connection to server broken\n");
			tass_done(1);
		}
		if (strcmp(line, ".") == 0)
			break;			/* end of text */
		strcat(line, "\n");
		if (line[0] == '.')		/* reduce leading .'s */
			fputs(&line[1], fp);
		else
			fputs(line, fp);
	}
	fclose(fp);
	if (stat(fnam, &sb) < 0)
		note_size = 0;
	else
		note_size = sb.st_size;
	return TRUE;
}
FILE *
nntp_to_fp() {
	char fnam[LEN];
	FILE *fp;
	if (!stuff_nntp(fnam))
		return NULL;
	fp = fopen(fnam, "r");
	if (fp == NULL) {
		fprintf(stderr, "nntp_to_fp: can't reopen %s: ", fnam);
		perror("");
		return NULL;
	}
	unlink(fnam);
	return fp;
}
nntp_to_fd() {
	char fnam[LEN];
	int fd;
	if (!stuff_nntp(fnam))
		return NULL;
	fd = open(fnam, 0);
	if (fd == NULL) {
		fprintf(stderr, "nntp_to_fd: can't reopen %s: ", fnam);
		perror("");
		return -1;
	}
	unlink(fnam);
	return fd;
}
FILE *
open_active_fp() {
	put_server("list");
	if (get_respcode() != OK_GROUPS)
		return NULL;
	return nntp_to_fp();
}
FILE *
open_art_fp(group_path, art)
char *group_path;
long art;
{
	char buf[LEN];
sprintf(buf, "article %ld", art);
	put_server(buf);
	if (get_respcode() != OK_ARTICLE)
		return NULL;
	return nntp_to_fp();
}
open_header_fd(group_path, art)
char *group_path;
long art;
{
	char buf[LEN];
	sprintf(buf, "head %ld", art);
	put_server(buf);
	if (get_respcode() != OK_HEAD)
		return -1;
	return nntp_to_fd();
}
setup_base(group, group_path)
char *group;
char *group_path;
{
	char buf[LEN];
	char line[NNTP_STRLEN];
	long start, last, dummy, count;
top_base = 0;
	sprintf(buf, "group %s", group);
	put_server(buf);
	if (get_server(line, NNTP_STRLEN) == -1) {
		fprintf(stderr, "connection to server broken\n");
		tass_done(1);
	}
	if (atoi(line) != OK_GROUP)
		return;
	sscanf(line,"%ld %ld %ld %ld", &dummy, &count, &start, &last);
	if (last - count > start)
		start = last - count;
while (start <= last) {
		if (top_base >= max_art)
			expand_art();
		base[top_base++] = start++;
	}
}
@EOF
chmod 644 nntp_open.c
echo x - prompt.c
cat >prompt.c <<'@EOF'
#include	<stdio.h>
#include	"tass.h"
/*
 *  parse_num
 *  get a number from the user
 *  Return -1 if missing or bad number typed
 */
parse_num(ch, prompt)
char ch;
char *prompt;
{
	char buf[40];
	int len;
	int i;
	int num;
	MoveCursor(LINES,0);
	printf("%s %c",prompt,ch);
	fflush(stdout);
	buf[0] = ch;
	buf[1] = '\0';
	len = 1;
	ch = ReadCh();
	while (ch != '\n'&& ch != '\r') {
		if (ch >= '0' && ch <= '9' && len < 4) {
			buf[len++] = ch;
			buf[len] = '\0';
			putchar(ch);
		} else if (ch == 8 || ch == 127) {
			if (len) {
				len--;
				buf[len] = '\0';
				putchar('\b');
				putchar(' ');
				putchar('\b');
			} else {
				MoveCursor(LINES, 0);
				CleartoEOLN();
				return(-1);
			}
		} else if (ch == 21) {	/* control-U	*/
			for (i = len;i>0;i--) {
				putchar('\b');
				putchar(' ');
				putchar('\b');
			}
			buf[0] = '\0';
			len = 0;
		} else
			putchar(7);
		fflush(stdout);
		ch = ReadCh();
	}
	MoveCursor(LINES, 0);
	CleartoEOLN();
	if (len) {
		num = atoi(buf);
		return(num);
	} else
		return(-1);
}
/*
 *  parse_string
 *  get a string from the user
 *  Return TRUE if a valid string was typed, FALSE otherwise
 */
parse_string(prompt, buf)
char *prompt;
char *buf;
{
int len;
int i;
char ch;
	clear_message();
	MoveCursor(LINES,0);
	printf("%s", prompt);
	fflush(stdout);
	buf[0] = '\0';
	len = 0;
	ch = ReadCh();
	while (ch != '\n' && ch != '\r') {
		if (ch >= ' ' && len < 60) {
			buf[len++] = ch;
			buf[len] = '\0';
			putchar(ch);
		} else if (ch == 8 || ch == 127) {
			if (len) {
				len--;
				buf[len] = '\0';
				putchar('\b');
				putchar(' ');
				putchar('\b');
			} else {
				MoveCursor(LINES, 0);
				CleartoEOLN();
				return(FALSE);
			}
		} else if (ch == 21) {	/* control-U	*/
			for (i = len;i>0;i--) {
				putchar('\b');
				putchar(' ');
				putchar('\b');
			}
			buf[0] = '\0';
			len = 0;
		} else
			putchar(7);
		fflush(stdout);
		ch = ReadCh();
	}
	MoveCursor(LINES,0);
	CleartoEOLN();
	return TRUE;
}
prompt_yn(prompt)
char *prompt;
{
	char ch;
	clear_message();
	MoveCursor(LINES,0);
	printf("%s", prompt);
	fflush(stdout);
	ch = ReadCh();
	clear_message();
	if (ch == 'y' || ch == 'Y')
		return TRUE;
	return FALSE;
}
continue_prompt() {
	printf("-Hit return to continue-");
	fflush(stdout);
	ReadCh();
}
@EOF
chmod 644 prompt.c
echo x - tass.h
cat >tass.h <<'@EOF'
#define		LIBDIR		"/usr/lib/news"
#define		SPOOLDIR	"/usr/spool/news"
#define		MAILER		"/bin/rmail"
#define		DEF_EDITOR	"/usr/bin/vi"
#define		TRUE		1
#define		FALSE		0
#define LEN 200
#define		INDEX_TOP		4
#define		NOTESLINES		(LINES - INDEX_TOP - 2)
#define		RIGHT_POS		(COLS - 16)
#define		MORE_POS		(COLS - 20)
#define		MAX_FROM	25
#define		MAX_SUBJ	38
#define		TABLE_SIZE	1409		/* should be prime */
/* #define MAX_SUBJ (COLS - 42) */
struct header {
	long artnum;
	char *subject;
	char *from;
	int thread;
	int inthread;
	int unread;		/* has this article been read? */
				/* 0 = read, 1 = unread, 2 = will return */
};
/*
 *  header.artnum:
 *	article number in spool directory for group
 *
 *  header.thread:
 *	initially -1
 *	points to another arts[] (struct header): zero and up
 *	-2 means article has expired (wasn't found in file search
 *	of spool directory for the group)
 *
 *  header.inthread:
 *	FALSE for the first article in a thread, TRUE for all
 *	following articles in thread
 *
 *  header.read:
 *	boolean, has this article been read or not
 */
struct group_ent {
	char *name;
	long max;
	long min;
	int next;		/* next active entry in hash chain */
	int flag;
};
#define		NOTGOT		0x01	/* haven't put in my_group yet */
#define		SUBS		0x02	/* subscribed to */
extern int top;
extern struct header *arts;
extern long *base;
extern int max_art;
extern char sig[LEN];
extern char signature[LEN];
extern char userid[LEN];
extern char homedir[LEN];
extern char indexdir[LEN];
extern char my_org[LEN];
extern char active_file[LEN];
extern char newsrc[LEN];
extern char newnewsrc[LEN];
extern char delgroups[LEN];
extern int top_base;
extern int LINES, COLS;
extern char *str_save();
extern char *my_malloc();
extern char *my_realloc();
extern int group_hash[TABLE_SIZE];
extern int num_active;
extern struct group_ent *active;
extern int *my_group;
extern int *unread;
extern int max_active;
extern int local_top;
extern char *eat_re();
extern char *nice_time();
extern int update;
extern int inverse_okay;
extern int tass_uid;
extern int tass_gid;
extern int real_uid;
extern int real_gid;
extern int local_index;
extern char *strcpy();
extern char *strncat();
extern char *strncpy();
extern long atol();
#define		ctrl(c)			((c) & 0x1F)
/*
 *  Assertion verifier
 */
#ifdef __STDC__
#define	assert(p)	if(! (p)) asfail(__FILE__, __LINE__, #p); else
#else
#define	assert(p)	if(! (p)) asfail(__FILE__, __LINE__, "p"); else
#endif
#define TASS_HEADER "Tass 3.2"
@EOF
chmod 644 tass.h
echo x - time.c
cat >time.c <<'@EOF'
#include	<sys/types.h>
#include	<time.h>
nicedate(timestr, newstr)
char *timestr, *newstr;
{
	int i;
	for (i = 0; i <= 7; i++)
		*newstr++ = timestr[i];
	if (timestr[8] != ' ')
		*newstr++ = timestr[8];
	*newstr++ = timestr[9];
	*newstr++ = ',';
	*newstr++ = ' ';
	for (i = 20;i <= 23; i++)
		*newstr++ = timestr[i];
	*newstr++ = '\0';
}
nicetime(timestr, newstr)
char *timestr, *newstr;
{
	int hours;
	char dayornite[3];
	if (timestr[11] == ' ')
		hours = timestr[12] - '0';
	else
		hours = (timestr[11]-'0')*10 + (timestr[12]-'0');
	if (hours < 12)
		strcpy(dayornite, "am");
	else
		strcpy(dayornite, "pm");
	if (hours >= 13)
		hours -= 12;
	if (!hours)
		hours = 12;
	sprintf(newstr, "%d:%c%c%s", hours, timestr[14],
					timestr[15], dayornite);
}
char *nice_time() {
	char *timestr;
	char the_date[17];
	char the_time[8];
	extern char *ctime();
	long time_now;
	static char buf[25];
	time(&time_now);
	timestr = ctime(&time_now);
	nicedate(timestr, the_date);
	nicetime(timestr, the_time);
	sprintf(buf,"%s  %s", the_date, the_time);
	return(buf);
}
@EOF
chmod 644 time.c
exit 0
echo x - page.c
cat >page.c <<'@EOF'
#include	<stdio.h>
#include	<signal.h>
#include	<sys/types.h>
#include	"tass.h"
#define		MAX_PAGES	1000
#define		NOTE_UNAVAIL	-1
char note_h_path[LEN];			/* Path:	*/
char note_h_date[LEN];			/* Date:	*/
char note_h_subj[LEN];			/* Subject:	*/
char note_h_from[LEN];			/* From:	*/
char note_h_org[LEN];			/* Organization: */
char note_h_newsgroups[LEN];		/* Newsgroups:	*/
char note_h_messageid[LEN];		/* Message-ID:	*/
char note_h_distrib[LEN];		/* Distribution: */
char note_h_followup[LEN];		/* Followup-To: */
int	note_line;
int	note_page;		/* what page we're on */
long	note_mark[MAX_PAGES];	/* ftells on beginnings of pages */
FILE	*note_fp;		/* the body of the current article */
int	note_end;		/* we're done showing this article */
int	rotate;			/* 0=normal, 13=rot13 decode */
long note_size; /* stat size in bytes of article */
char	note_full_name[100];
char	note_from_addr[100];
int last_resp;		/* current & previous article for - command */
int this_resp;
int glob_respnum;
char *glob_page_group;
extern int cur_groupnum;
#ifdef SIGTSTP
void
page_susp(i)
int i;
{
	Raw(FALSE);
	putchar('\n');
	signal(SIGTSTP, SIG_DFL);
#ifdef BSD
        sigsetmask(sigblock(0) & ~(1 << (SIGTSTP - 1)));
#endif
	kill(0, SIGTSTP);
	signal(SIGTSTP, page_susp);
	mail_setup();
	Raw(TRUE);
	redraw_page(glob_respnum, glob_page_group);
}
#endif
show_page(respnum, group, group_path)
int respnum;
char *group;
char *group_path;
{
	char ch;
	int n;
	int i;
	long art;
restart:
	glob_respnum = respnum;
	glob_page_group = group;
#ifdef SIGTSTP
	signal(SIGTSTP, page_susp);
#endif
	if (respnum != this_resp) {	   /* remember current & previous */
		last_resp = this_resp;	   /* articles for - command */
		this_resp = respnum;
	}
	rotate = 0;			/* normal mode, not rot13 */
	art = arts[respnum].artnum;
	arts[respnum].unread = 0;	/* mark article as read */
	open_note(art, group_path);
	if (note_page == NOTE_UNAVAIL) {
		ClearScreen();
		printf("[Article %ld unvailable]\r\r", art);
		fflush(stdout);
	} else
		show_note_page(respnum, group);
	while (1) {
		ch = ReadCh();
if (ch >= '0' && ch <= '9') {
			n = prompt_response(ch, respnum);
			if (n != -1) {
				respnum = n;
				goto restart;
			}
		} else switch (ch) {
			case 'a':	/* author search forward */
			case 'A':	/* author search backward */
				i = (ch == 'a');
				n = search_author(respnum, i, group);
				if (n < 0)
					break;
				respnum = n;
				goto restart;
				break;
			case '|':	/* pipe article into command */
				pipe_article();
				redraw_page(respnum, group);
				break;
			case 'I':	/* toggle inverse video */
				inverse_okay = !inverse_okay;
				if (inverse_okay)
					info_message("Inverse video enabled");
				else
					info_message("Inverse video disabled");
				goto pager_ctrlr;
				break;
			case 's':
				save_art_to_file();
				break;
			case 'S':
				save_thread_to_file(respnum, group_path);
				break;
			case ctrl('X'):
			case '%':	/* toggle rot-13 mode */
				if (rotate)
					rotate = 0;
				else
					rotate = 13;
				redraw_page(respnum, group);
		/*		goto pager_ctrlr;	*/
				break;
			case 'P':	/* previous unread article */
				n = prev_unread(prev_response(respnum));
				if (n == -1)
				    info_message("No previous unread article");
				else {
					note_cleanup();
					respnum = n;
					goto restart;
				}
				break;
			case 'F':	/* post a followup to this article */
				if (post_response(group, TRUE)) {
					update_newsrc(group,
						my_group[cur_groupnum]);
					n = which_base(respnum);
					note_cleanup();
					index_group(group, group_path);
					read_newsrc_line(group);
					respnum = choose_resp(n, nresp(n));
					goto restart;
				} else
					redraw_page(respnum, group);
				break;
			case 'f':	/* post a followup to this article */
				if (post_response(group, FALSE)) {
					update_newsrc(group,
						my_group[cur_groupnum]);
					n = which_base(respnum);
					note_cleanup();
					index_group(group, group_path);
					read_newsrc_line(group);
					respnum = choose_resp(n, nresp(n));
					goto restart;
				} else
					redraw_page(respnum, group);
				break;
			case 'z':	/* mark article as unread (to return) */
				arts[respnum].unread = 2;
				info_message("Article marked as unread");
				break;
			case 'K':	/* mark rest of thread as read */
for (n = respnum; n >= 0; n = arts[n].thread)
					arts[n].unread = 0;
				n = next_unread(next_response(respnum));
				if (n == -1)
					goto return_to_index;
				else {
					note_cleanup();
					respnum = n;
					goto restart;
				}
				break;
			case 'i':	/* return to index page */
return_to_index:
				note_cleanup();
				return( which_base(respnum) );
			case 't':	/* return to group selection page */
				note_cleanup();
				return -1;
			case ctrl('R'):	  /* redraw beginning of article */
pager_ctrlr:
				if (note_page == NOTE_UNAVAIL) {
					ClearScreen();
					printf("[Article %ld unvailable]\r\n",
							arts[respnum].artnum);
					fflush(stdout);
				} else {
					note_page = 0;
					note_end = FALSE;
					fseek(note_fp, note_mark[0], 0);
					show_note_page(respnum, group);
				}
				break;
			case '!':
				shell_escape();
				redraw_page(respnum, group);
				break;
			case '\b':
			case 'b':	/* back a page */
				if (note_page == NOTE_UNAVAIL
				||  note_page <= 1) {
					note_cleanup();
					n = prev_response(respnum);
					if (n == -1)
						return( which_resp(respnum) );
					respnum = n;
					goto restart;
				} else {
					note_page -= 2;
					note_end = FALSE;
					fseek(note_fp, note_mark[note_page], 0);
					show_note_page(respnum, group);
				}
				break;
			case 'm':	/* mail article to somebody */
				mail_to_someone();
				redraw_page(respnum, group);
				break;
			case 'r':	/* reply to author through mail */
				mail_to_author(FALSE);
				redraw_page(respnum, group);
				break;
			case 'R':	/* reply to author, copy text */
				mail_to_author(TRUE);
				redraw_page(respnum, group);
				break;
			case '-':	/* show last viewed article */
				if (last_resp < 0) {
					info_message("No last message");
					break;
				}
				note_cleanup();
				respnum = last_resp;
				goto restart;
			case 'p':	/* previous article */
				note_cleanup();
				n = prev_response(respnum);
				if (n == -1)
					return( which_resp(respnum) );
				respnum = n;
				goto restart;
			case 'n':	/* skip to next article */
				note_cleanup();
				n = next_response(respnum);
				if (n == -1)
					return( which_base(respnum) );
				respnum = n;
				goto restart;
			case 'k':
				if (note_page == NOTE_UNAVAIL) {
					n = next_unread(next_response(respnum));
					if (n == -1)
						return( which_base(respnum) );
					respnum = n;
					goto restart;
				} else {
					note_cleanup();
					n = next_unread(next_response(respnum));
					if (n == -1)
						return( which_base(respnum) );
					respnum = n;
					goto restart;
				}
				break;
			case ' ': 	/* next page or response */
				if (note_page == NOTE_UNAVAIL) {
					n = next_response(respnum);
					if (n == -1)
						return( which_base(respnum) );
					respnum = n;
					goto restart;
				} else if (note_end) {
					note_cleanup();
					n = next_response(respnum);
					if (n == -1)
						return( which_base(respnum) );
					respnum = n;
					goto restart;
				} else
					show_note_page(respnum, group);
				break;
			case '\t': 	/* next page or unread response */
				if (note_page == NOTE_UNAVAIL) {
					n = next_unread(next_response(respnum));
					if (n == -1)
						return( which_base(respnum) );
					respnum = n;
					goto restart;
				} else if (note_end) {
					note_cleanup();
					n = next_unread(next_response(respnum));
					if (n == -1)
						return( which_base(respnum) );
					respnum = n;
					goto restart;
				} else
					show_note_page(respnum, group);
				break;
			case 'N':	/* next unread article */
				n = next_unread(next_response(respnum));
				if (n == -1)
					info_message("No next unread article");
				else {
					note_cleanup();
					respnum = n;
					goto restart;
				}
				break;
			case '\r':
			case '\n':	/* go to start of next thread */
				note_cleanup();
				n = next_basenote(respnum);
				if (n == -1)
					return( which_base(respnum) );
				respnum = n;
				goto restart;
			case 'q':	/* quit */
return -2;
			case 'H':	/* show article headers */
				if (note_page == NOTE_UNAVAIL) {
					n = next_response(respnum);
					if (n == -1)
						return( which_base(respnum) );
					respnum = n;
					goto restart;
				} else {
					note_page = 0;
					note_end = FALSE;
					fseek(note_fp, 0L, 0);
					show_note_page(respnum, group);
				}
				break;
			case 'h':
				tass_page_help();
				redraw_page(respnum, group);
				break;
			default:
			    info_message("Bad command.  Type 'h' for help.");
		}
	}
}
note_cleanup() {
	if (note_page != NOTE_UNAVAIL)
		fclose(note_fp);
}
redraw_page(respnum, group)
int respnum;
char *group;
{
	if (note_page == NOTE_UNAVAIL) {
		ClearScreen();
		printf("[Article %ld unvailable]\r\r", arts[respnum].artnum);
		fflush(stdout);
	} else if (note_page > 0) {
		note_page--;
		fseek(note_fp, note_mark[note_page], 0);
		show_note_page(respnum, group);
	}
}
show_note_page(respnum, group)
int respnum;
char *group;
{
	char buf[LEN];
	char buf2[LEN+50];
	int percent;
	char *p, *q;
	int i, j;
	int ctrl_L;		/* form feed character detected */
ClearScreen();
note_line = 1;
	if (note_page == 0)
		show_first_header(respnum, group);
	else
		show_cont_header(respnum);
	ctrl_L = FALSE;
	while (note_line < LINES) {
		if (fgets(buf, LEN, note_fp) == NULL) {
			note_end = TRUE;
			break;
		}
		buf[LEN-1] = '\0';
		if (rotate)
			for (p = buf, q = buf2;
					*p && *p != '\n' && q<&buf2[LEN]; p++) {
				if (*p == '\b' && q > buf2) {
					q--;
				} else if (*p == 12) {		/* ^L */
					*q++ = '^';
					*q++ = 'L';
					ctrl_L = TRUE;
				} else if (*p == '\t') {
					i = q - buf2;
					j = (i|7) + 1;
					while (i++ < j)
						*q++ = ' ';
				} else if (((*p)&0x7F) < 32) {
					*q++ = '^';
					*q++ = ((*p)&0x7F) + '@';
				} else if (*p >= 'A' && *p <= 'Z')
					*q++ = 'A' + (*p - 'A' + rotate) % 26;
				else if (*p >= 'a' && *p <= 'z')
					*q++ = 'a' + (*p - 'a' + rotate) % 26;
				else
					*q++ = *p;
			}
		else
			for (p = buf, q = buf2;
					*p && *p != '\n' && q<&buf2[LEN]; p++) {
				if (*p == '\b' && q > buf2) {
					q--;
				} else if (*p == 12) {		/* ^L */
					*q++ = '^';
					*q++ = 'L';
					ctrl_L = TRUE;
				} else if (*p == '\t') {
					i = q - buf2;
					j = (i|7) + 1;
					while (i++ < j)
						*q++ = ' ';
				} else if (((*p)&0x7F) < 32) {
					*q++ = '^';
					*q++ = ((*p)&0x7F) + '@';
				} else
					*q++ = *p;
			}
*q = '\0';
printf("%s\r\n", buf2);
#if 1
		note_line += (strlen(buf2) / COLS) + 1;
#else
		if (*buf2)
			note_line += (strlen(buf2) + COLS) / (COLS+1);
		else
			note_line++;
#endif
		if (ctrl_L)
			break;
	}
note_mark[++note_page] = ftell(note_fp);
	MoveCursor(LINES, MORE_POS);
/*	StartInverse();	*/
	if (note_end) {
		if (arts[respnum].thread != -1)
			printf("-- next response --");
		else
			printf("-- last response --");
	} else {
		if (note_size > 0) {
		    percent = note_mark[note_page] * 100 / note_size;
		    printf("--More--(%d%%)", percent);
		} else
		    printf("--More--");
	}
/*	EndInverse();	*/
	fflush(stdout);
}
show_first_header(respnum, group)
int respnum;
char *group;
{
	int whichresp;
	int x_resp;
	char buf[200];
	char tmp[200];
	int pos, i;
	int n;
	whichresp = which_resp( respnum );
	x_resp = nresp( which_base(respnum) );
ClearScreen();
	strcpy(buf, note_h_date);
	pos = (COLS - strlen(group)) / 2;
	for (i = strlen(buf); i < pos; i++)
		buf[i] = ' ';
	buf[i] = '\0';
strcat(buf, group);
	for (i = strlen(buf); i < RIGHT_POS; i++)
		buf[i] = ' ';
	buf[i] = '\0';
printf("%sNote %3d of %3d\r\n", buf, which_base(respnum) + 1, top_base);
	sprintf(buf, "Article %ld  ", arts[respnum].artnum);
	n = strlen(buf);
	fputs(buf, stdout);
pos = (COLS - strlen( note_h_subj )) / 2 - 2;
	if (pos > n)
		MoveCursor(1, pos);
	else
		MoveCursor(1, n);
	StartInverse();
	strcpy(buf, note_h_subj);
	buf[RIGHT_POS - 2 - n] = '\0';
	fputs(buf, stdout);
	EndInverse();
	MoveCursor(1, RIGHT_POS);
	if (whichresp)
		printf("Resp %3d of %3d\r\n", whichresp, x_resp);
	else {
		if (x_resp == 0)
			printf("No responses\r\n");
		else if (x_resp == 1)
			printf("1 Response\r\n");
		else
			printf("%d Responses\r\n", x_resp);
	}
	if (*note_h_org)
		sprintf(tmp, "%s at %s", note_full_name, note_h_org);
	else
		strcpy(tmp, note_full_name);
tmp[79] = '\0';
sprintf(buf, "%s ", note_from_addr);
	pos = COLS - 1 - strlen(tmp);
	if (strlen(buf) + strlen(tmp) >= COLS - 1) {
		strncat(buf, tmp, COLS - 1 - strlen(buf));
		buf[COLS - 1] = '\0';
	} else {
		for (i = strlen(buf); i < pos; i++)
			buf[i] = ' ';
		buf[i] = '\0';
		strcat(buf, tmp);
	}
	printf("%s\r\n\r\n", buf);
	note_line += 4;
}
show_cont_header(respnum)
int respnum;
{
int whichresp;
int whichbase;
char buf[200];
	whichresp = which_resp(respnum);
	whichbase = which_base(respnum);
assert (whichbase < top_base);
	if (whichresp)
		sprintf(buf, "Note %d of %d, Resp %d (page %d):  %s",
			whichbase + 1,
			top_base,
			whichresp,
			note_page + 1,
			note_h_subj);
	else
		sprintf(buf, "Note %d of %d (page %d):  %s",
			whichbase + 1,
			top_base,
			note_page + 1,
			note_h_subj);
	buf[COLS] = '\0';
	printf("%s\r\n\r\n", buf);
	note_line += 2;
}
open_note(art, group_path)
long art;
char *group_path;
{
	char buf[1025];
	char *p;
	extern FILE *open_art_fp();
note_page = 0;
	note_fp = open_art_fp(group_path, art);
	if (note_fp == NULL) {
		note_page = NOTE_UNAVAIL;
		return;
	}
	note_h_from[0] = '\0';
	note_h_path[0] = '\0';
	note_h_subj[0] = '\0';
	note_h_org[0] = '\0';
	note_h_date[0] = '\0';
	note_h_newsgroups[0] = '\0';
	note_h_messageid[0] = '\0';
	note_h_distrib[0] = '\0';
	note_h_followup[0] = '\0';
	while (fgets(buf, 1024, note_fp) != NULL) {
		buf[1024] = '\0';
		for (p = buf; *p && *p != '\n'; p++)
			if (((*p)&0x7F) < 32)
				*p = ' ';
		*p = '\0';
		if (*buf == '\0')
			break;
		if (strncmp(buf, "From: ", 6) == 0) {
			strncpy(note_h_from, &buf[6], LEN);
			note_h_from[LEN-1] = '\0';
		} else if (strncmp(buf, "Path: ", 6) == 0) {
			strncpy(note_h_path, &buf[6], LEN);
			note_h_path[LEN-1] = '\0';
		} else if (strncmp(buf, "Subject: ", 9) == 0) {
			strncpy(note_h_subj, &buf[9], LEN);
			note_h_subj[LEN-1] = '\0';
		} else if (strncmp(buf, "Organization: ", 14) == 0) {
			strncpy(note_h_org, &buf[14], LEN);
			note_h_org[LEN-1] = '\0';
		} else if (strncmp(buf, "Date: ", 6) == 0) {
			strncpy(note_h_date, &buf[6], LEN);
			note_h_date[LEN-1] = '\0';
		} else if (strncmp(buf, "Newsgroups: ", 12) == 0) {
			strncpy(note_h_newsgroups, &buf[12], LEN);
			note_h_newsgroups[LEN-1] = '\0';
		} else if (strncmp(buf, "Message-ID: ", 12) == 0) {
			strncpy(note_h_messageid, &buf[12], LEN);
			note_h_messageid[LEN-1] = '\0';
		} else if (strncmp(buf, "Distribution: ", 14) == 0) {
			strncpy(note_h_distrib, &buf[14], LEN);
			note_h_distrib[LEN-1] = '\0';
		} else if (strncmp(buf, "Followup-To: ", 13) == 0) {
			strncpy(note_h_followup, &buf[13], LEN);
			note_h_followup[LEN-1] = '\0';
		}
	}
	note_page = 0;
	note_mark[0] = ftell(note_fp);
	parse_from(note_h_from, note_from_addr, note_full_name);
	note_end = FALSE;
	return;
}
prompt_response(ch, respnum)
int respnum;
{
	int num;
clear_message();
	if ((num = parse_num(ch, "Read response> ")) == -1) {
		clear_message();
		return(-1);
	}
	return choose_resp( which_base(respnum), num );
}
/*
 *  return response number n from thread i
 */
choose_resp(i, n)
int i;
int n;
{
	int j;
j = base[i];
	while (n-- && arts[j].thread >= 0)
		j = arts[j].thread;
	return j;
}
/*
 *  Parse various From: lines into the component mail addresses and
 *  real names
 */
parse_from(str, addr, name)
char *str;
char *addr;
char *name;
{
	char *p;
for (p = str; *p; p++)
		if (((*p) & 0x7F) < 32)
			*p = ' ';
	while (*str && *str != ' ')
		*addr++ = *str++;
	*addr = '\0';
	if (*str++ == ' ') {
		if (*str++ == '(') {
			if (*str == '"')
				str++;  /* Kill "quotes around names"         */
					/* But don't touch quotes inside the  */
					/* Name (that's what that nonsense    */
					/* below is for			      */
			while (*str && *str != ')' && !(*str=='"'&&str[1]==')'))
				*name++ = *str++;
		}
	}
	*name = '\0';
}
tass_page_help() {
	char title[100];
sprintf(title, "%s, Article Pager Commands", TASS_HEADER);
	ClearScreen();
	center_line(0, title);
MoveCursor(2, 0);
	printf("\t4\tRead response 4 in this thread (0 is basenote)\r\n");
	printf("\t<CR>\tSkip to next base article\r\n");
	printf("\t<TAB>\tAdvance to next page or unread article\r\n");
	printf("\taA\tAuthor search forward (A=backward)\r\n");
	printf("\tb\tBack a page\r\n");
	printf("\tfF\tPost a followup (F=include text)\r\n");
	printf("\tH\tShow article headers\r\n");
	printf("\ti\tReturn to index page\r\n");
printf("\tkK\tMark article (K=thread) as read & advance to next unread\r\n");
	printf("\tm\tMail this article to someone\r\n");
	printf("\tnN\tSkip to the next (N=unread) article)\r\n");
	printf("\tpP\tGo to the previous (P=unread) article\r\n");
	printf("\tq\tQuit\r\n");
	printf("\trR\tReply through mail (R=include text) to author\r\n");
	printf("\tsS\tSave article (S=thread) to file\r\n");
	printf("\tt\tReturn to group selection page\r\n");
	printf("\tz\tMark article as unread\r\n");
	printf("\t^R\tRedisplay first page of article\r\n");
	printf("\t%%, ^X\tToggle rot-13 decoding for this article\r\n");
	printf("\t-\tShow last article\r\n");
printf("\t|\tPipe article into command\r\n");
	center_line(LINES, "-- hit any key --");
	ReadCh();
}
yank_to_addr(orig, addr)
char *orig;
char *addr;
{
	char *p;
for (p = orig; *p; p++)
		if (((*p) & 0x7F) < 32)
			*p = ' ';
	while (*addr)
		addr++;
	while (*orig) {
		while (*orig && (*orig == ' ' || *orig == '"' || *orig == ','))
			orig++;
		*addr++ = ' ';
		while (*orig && (*orig != ' ' && *orig != ',' && *orig != '"'))
			*addr++ = *orig++;
		while (*orig && (*orig == ' ' || *orig == '"' || *orig == ','))
			orig++;
		if (*orig == '(') {
			while (*orig && *orig != ')')
				orig++;
			if (*orig == ')')
				orig++;
		}
	}
	*addr = '\0';
}
/*
 *  Read a file grabbing the address given for To: and
 *  sticking it in mail_to
 */
find_new_to(nam, mail_to)
char *nam;
char *mail_to;
{
	FILE *fp;
	char buf[LEN];
	char buf2[LEN];
	char dummy[LEN];
	char new_mail_to[LEN];
	char *p;
*new_mail_to = '\0';
	fp = fopen(nam, "r");
	if (fp == NULL) {
		fprintf(stderr, "reopen of %s failed\n", nam);
		return;
	}
while (fgets(buf, 1024, fp) != NULL) {
		for (p = buf; *p && *p != '\n'; p++) ;
		*p = '\0';
		if (*buf == '\0')
			break;
		if (strncmp(buf, "To: ", 4) == 0) {
			strncpy(buf2, &buf[4], LEN);
			buf2[LEN-1] = '\0';
			yank_to_addr(buf2, new_mail_to);
		} else if (strncmp(buf, "Cc: ", 4) == 0) {
			strncpy(buf2, &buf[4], LEN);
			buf2[LEN-1] = '\0';
			yank_to_addr(buf2, new_mail_to);
		}
	}
	fclose(fp);
	if (new_mail_to[0] == ' ')
		strcpy(mail_to, &new_mail_to[1]);
	else
		strcpy(mail_to, new_mail_to);
}
mail_to_someone() {
	char nam[100];
	FILE *fp;
	char ch;
	char buf[200];
	char mail_to[LEN+1];
	char subj[LEN+1];
	if (!parse_string("Mail article to: ", mail_to))
		return;
	if (mail_to[0] == '\0')
		return;
	setuid(real_uid);
	setgid(real_gid);
sprintf(nam, "%s/.letter", homedir);
	if ((fp = fopen(nam, "w")) == NULL) {
		fprintf(stderr, "can't open %s: ", nam);
		perror("");
		setuid(tass_uid);
		setgid(tass_gid);
		return(FALSE);
	}
	chmod(nam, 0600);
	fprintf(fp, "To: %s\n", mail_to);
	fprintf(fp, "Subject: %s\n", note_h_subj);
	if (*note_h_followup)
		fprintf(fp, "Newsgroups: %s\n\n", note_h_followup);
	else
		fprintf(fp, "Newsgroups: %s\n", note_h_newsgroups);
	if (*my_org)
		fprintf(fp, "Organization: %s\n", my_org);
fputs("\n", fp);
	fseek(note_fp, 0L, 0);
	copy_fp(note_fp, fp, "");
	add_signature(fp, TRUE);
	fclose(fp);
	while (1) {
		do {
			MoveCursor(LINES, 0);
			fputs("abort, edit, send: ", stdout);
			fflush(stdout);
			ch = ReadCh();
		} while (ch != 'a' && ch != 'e' && ch != 's');
		switch (ch) {
		case 'e':
			invoke_editor(nam);
			break;
		case 'a':
			setuid(tass_uid);
			setgid(tass_gid);
			return FALSE;
		case 's':
/*
 *  Open letter an get the To: line in case they changed it with
 *  the editor
 */
			find_new_to(nam, mail_to);
			printf("\r\nMailing to %s...", mail_to);
			fflush(stdout);
			sprintf(buf, "%s %s < %s", MAILER,
							mail_to, nam);
			if (invoke_cmd(buf)) {
				printf("Message sent\r\n");
				fflush(stdout);
				goto mail_to_someone_done;
			} else {
				printf("Command failed: %s\r\n", buf);
				fflush(stdout);
				break;
			}
		}
	}
mail_to_someone_done:
	setuid(tass_uid);
	setgid(tass_gid);
continue_prompt();
	return TRUE;
}
mail_to_author(copy_text)
int copy_text;
{
	char nam[100];
	FILE *fp;
	char ch;
	char buf[200];
	char mail_to[LEN+1];
	setuid(real_uid);
	setgid(real_gid);
printf("\r\nMailing to %s...\r\n\r\n", note_h_from);
sprintf(nam, "%s/.letter", homedir);
	if ((fp = fopen(nam, "w")) == NULL) {
		fprintf(stderr, "can't open %s: ", nam);
		perror("");
		setuid(tass_uid);
		setgid(tass_gid);
		return(FALSE);
	}
	chmod(nam, 0600);
	fprintf(fp, "To: %s\n", note_h_from);
	fprintf(fp, "Subject: Re: %s\n", eat_re(note_h_subj) );
	fprintf(fp, "Newsgroups: %s\n", note_h_newsgroups);
	if (*my_org)
		fprintf(fp, "Organization: %s\n", my_org);
fputs("\n", fp);
	if (copy_text) {		/* if "copy_text" */
		fprintf(fp, "In article %s you write:\n", note_h_messageid);
		fseek(note_fp, note_mark[0], 0);
		copy_fp(note_fp, fp, "> ");
	}
	add_signature(fp, TRUE);
	fclose(fp);
	ch = 'e';
	while (1) {
		switch (ch) {
		case 'e':
			invoke_editor(nam);
			break;
		case 'a':
			setuid(tass_uid);
			setgid(tass_gid);
			return FALSE;
		case 's':
			strcpy(mail_to, note_from_addr);
			find_new_to(nam, mail_to);
			printf("\r\nMailing to %s...  ", mail_to);
			fflush(stdout);
			sprintf(buf, "%s %s < %s", MAILER, mail_to, nam);
			if (invoke_cmd(buf)) {
				printf("Message sent\r\n");
				fflush(stdout);
				goto mail_to_author_done;
			} else {
				printf("Command failed: %s\r\n", buf);
				fflush(stdout);
				break;
			}
		}
		do {
			MoveCursor(LINES, 0);
			fputs("abort, edit, send: ", stdout);
			fflush(stdout);
			ch = ReadCh();
		} while (ch != 'a' && ch != 'e' && ch != 's');
	}
mail_to_author_done:
	setuid(tass_uid);
	setgid(tass_gid);
continue_prompt();
	return TRUE;
}
post_response(group, respnum)
int respnum;
{
	FILE *fp;
	char nam[100];
	char ch;
	char buf[200];
	int post_anyway = FALSE;
	if (*note_h_followup && strcmp(note_h_followup, "poster") == 0) {
		clear_message();
		MoveCursor(LINES,0);
		printf("Note: Responses have been directed to the poster");
		if (!prompt_yn("Post anyway? (y/n): "))
			return FALSE;
		*note_h_followup = '\0';
	} else if (*note_h_followup && strcmp(note_h_followup, group) != 0) {
	    clear_message();
	    MoveCursor(LINES,0);
	    printf("Note:  Responses have been directed to %s\r\n\r\n",
							note_h_followup);
	    if (!prompt_yn("Continue? (y/n): "))
		return FALSE;
	}
	setuid(real_uid);
	setgid(real_gid);
	sprintf(nam, "%s/.article", homedir);
	if ((fp = fopen(nam, "w")) == NULL) {
		fprintf(stderr, "can't open %s: ", nam);
		perror("");
		setuid(tass_uid);
		setgid(tass_gid);
		return FALSE;
	}
	chmod(nam, 0600);
fprintf(fp, "Subject: Re: %s\n", eat_re(note_h_subj));
	if (*note_h_followup && strcmp(note_h_followup, "poster") != 0)
		fprintf(fp, "Newsgroups: %s\n", note_h_followup);
	else
		fprintf(fp, "Newsgroups: %s\n", note_h_newsgroups);
	if (*my_org)
		fprintf(fp, "Organization: %s\n", my_org);
	if (note_h_distrib != '\0')
		fprintf(fp, "Distribution: %s\n", note_h_distrib);
	fprintf(fp, "References: %s\n", note_h_messageid);
	fprintf(fp, "\n");
	if (respnum) {		/* if "copy_text" */
		fprintf(fp, "%s writes:\n", note_h_from);
		fseek(note_fp, note_mark[0], 0);
		copy_fp(note_fp, fp, "> ");
	}
	add_signature(fp, FALSE);
	fclose(fp);
	ch = 'e';
	while (1) {
		switch (ch) {
		case 'e':
			invoke_editor(nam);
			break;
		case 'a':
			setuid(tass_uid);
			setgid(tass_gid);
			return FALSE;
		case 'p':
			printf("Posting...  ");
			fflush(stdout);
			sprintf(buf, "%s/inews -h < %s", LIBDIR, nam);
			if (invoke_cmd(buf)) {
				printf("article posted\r\n");
				fflush(stdout);
				goto post_response_done;
			} else {
				printf("article rejected\r\n");
				fflush(stdout);
				break;
			}
		}
		do {
			MoveCursor(LINES, 0);
			fputs("abort, edit, post: ", stdout);
			fflush(stdout);
ch = ReadCh();
		} while (ch != 'a' && ch != 'e' && ch != 'p');
	}
post_response_done:
	setuid(tass_uid);
	setgid(tass_gid);
continue_prompt();
	return TRUE;
}
save_art_to_file()
{
	char nam[LEN];
	FILE *fp;
	char *p;
	if (!parse_string("Save article to file: ", nam))
		return;
	if (nam[0] == '\0')
		return;
	for (p = nam; *p && (*p == ' ' || *p == '\t'); p++) ;
	if (!*p)
		return;
	setuid(real_uid);
	setgid(real_gid);
if ((fp = fopen(p, "a+")) == NULL) {
		fprintf(stderr, "can't open %s: ", nam);
		perror("");
		info_message("-- article not saved --");
		setuid(real_uid);
		setgid(real_gid);
		return;
	}
	MoveCursor(LINES, 0);
	fputs("Saving...", stdout);
	fflush(stdout);
fprintf(fp, "From %s %s\n", note_h_path, note_h_date);
	fseek(note_fp, 0L, 0);
	copy_fp(note_fp, fp, "");
	fputs("\n", fp);
fclose(fp);
	setuid(real_uid);
	setgid(real_gid);
	info_message("-- article saved --");
}
save_thread_to_file(respnum, group_path)
long respnum;
char *group_path;
{
	char nam[LEN];
	FILE *fp;
	FILE *art;
	int i;
	char buf[8192];
	int b;
	int count = 0;
	char *p;
b = which_base(respnum);
	if (!parse_string("Save thread to file: ", nam))
		return;
	if (nam[0] == '\0')
		return;
	for (p = nam; *p && (*p == ' ' || *p == '\t'); p++) ;
	if (!*p)
		return;
	setuid(real_uid);
	setgid(real_gid);
if ((fp = fopen(nam, "a+")) == NULL) {
		fprintf(stderr, "can't open %s: ", nam);
		perror("");
		info_message("-- thread not saved --");
		setuid(real_uid);
		setgid(real_gid);
		return;
	}
	MoveCursor(LINES, 0);
	fputs("Saving...    ", stdout);
	fflush(stdout);
note_cleanup();
	for (i = base[b]; i >= 0; i = arts[i].thread) {
		open_note(arts[i].artnum, group_path);
		fprintf(fp, "From %s %s\n", note_h_path, note_h_date);
		fseek(note_fp, 0L, 0);
		copy_fp(note_fp, fp, "");
		fputs("\n", fp);
		note_cleanup();
		printf("\b\b\b\b%4d", ++count);
		fflush(stdout);
	}
	fclose(fp);
	setuid(real_uid);
	setgid(real_gid);
	info_message("-- thread saved --");
	open_note(arts[respnum].artnum, group_path);
}
pipe_article() {
	char command[LEN];
	FILE *fp;
	if (!parse_string("Pipe to command: ", command))
		return;
	if (command[0] == '\0')
		return;
	fp = popen(command, "w");
	if (fp == NULL) {
		fprintf(stderr, "command failed: ");
		perror("");
		goto pipe_article_done;
	}
	fseek(note_fp, 0L, 0);
	copy_fp(note_fp, fp, "");
	pclose(fp);
pipe_article_done:
	continue_prompt();
}
@EOF
chmod 644 page.c
echo x - screen.c
cat >screen.c <<'@EOF'
#include	<stdio.h>
#include	"tass.h"
info_message(msg)
char *msg;
{
	clear_message();	  /* Clear any old messages hanging around */
	center_line(LINES, msg);  /* center the message at screen bottom  */
	MoveCursor(LINES, 0);
}
clear_message()
{
	MoveCursor(LINES, 0);
	CleartoEOLN();
}
center_line(line, str)
int line;
char *str;
{
int pos;
	pos = (COLS - strlen(str)) / 2;
	MoveCursor(line, pos);
	printf("%s", str);
	fflush(stdout);
}
draw_arrow(line)
int line;
{
	MoveCursor(line, 0);
	printf("->");
	fflush(stdout);
	MoveCursor(LINES, 0);
}
erase_arrow(line)
int line;
{
	MoveCursor(line, 0);
	printf("  ");
	fflush(stdout);
}
@EOF
chmod 644 screen.c
echo x - select.c
cat >select.c <<'@EOF'
#include	<stdio.h>
#include	<signal.h>
#include	"tass.h"
int first_group_on_screen;
int last_group_on_screen;
int cur_groupnum = 0;
extern int index_point;
int space_mode;
extern char *cvers;
char group_search_string[LEN+1];
#ifdef SIGTSTP
void
select_susp(i)
int i;
{
	Raw(FALSE);
	putchar('\n');
	signal(SIGTSTP, SIG_DFL);
#ifdef BSD
        sigsetmask(sigblock(0) & ~(1 << (SIGTSTP - 1)));
#endif
	kill(0, SIGTSTP);
	signal(SIGTSTP, select_susp);
	Raw(TRUE);
	mail_setup();
	group_selection_page();
}
#endif
selection_index()
{
	char ch;
	int n;
	int i;
	char buf[200];
group_selection_page(); /* display group selection page */
	while (1) {
		ch = ReadCh();
		if (ch > '0' && ch <= '9') {
			prompt_group_num(ch);
		} else switch (ch) {
			case 'c':	/* catchup--mark all articles as read */
			    if (prompt_yn("Mark group as read? (y/n): ")) {
				unread[cur_groupnum] = 0;
				mark_group_read(
					    active[my_group[cur_groupnum]].name,
					    my_group[cur_groupnum]);
 				MoveCursor(INDEX_TOP + (cur_groupnum -
						first_group_on_screen), 47);
 				printf("     ");
				MoveCursor(LINES, 0);
 				fflush(stdout);
/*				group_selection_page();		*/
			    }
			    break;
			case ctrl('K'):
				if (local_top <= 0) {
					info_message("No groups to delete");
					break;
				}
				delete_group(
					active[my_group[cur_groupnum]].name);
				active[my_group[cur_groupnum]].flag = NOTGOT;
				local_top--;
				for (i = cur_groupnum; i < local_top; i++) {
					my_group[i] = my_group[i+1];
					unread[i] = unread[i+1];
				}
				if (cur_groupnum >= local_top)
					cur_groupnum = local_top - 1;
				group_selection_page();
				break;
			case ctrl('Y'):
				undel_group();
				group_selection_page();
				break;
			case 'I':		/* toggle inverse video */
				inverse_okay = !inverse_okay;
				if (inverse_okay)
					info_message("Inverse video enabled");
				else
					info_message("Inverse video disabled");
				break;
			case ctrl('R'):	/* reset .newsrc */
			    if (prompt_yn("Reset newsrc? (y/n): ")) {
				reset_newsrc();
				cur_groupnum = 0;
				group_selection_page();
			    }
			    break;
			case '$':	/* reread .newsrc, no unsub groups */
				cur_groupnum = 0;
				local_top = 0;
				for (i = 0; i < num_active; i++)
					active[i].flag = NOTGOT;
				read_newsrc(TRUE);
				group_selection_page();
				break;
			case 's':	/* subscribe to current group */
			    MoveCursor(INDEX_TOP +
				(cur_groupnum-first_group_on_screen), 3);
			    putchar(' ');
			    fflush(stdout);
			    MoveCursor(LINES, 0);
			    subscribe(active[my_group[cur_groupnum]].name,
					':', my_group[cur_groupnum], FALSE);
			    sprintf(buf, "subscribed to %s",
					active[my_group[cur_groupnum]].name);
			    info_message(buf);
			    break;
			case 'u':	/* unsubscribe to current group */
			    MoveCursor(INDEX_TOP +
				(cur_groupnum-first_group_on_screen), 3);
			    putchar('u');
			    fflush(stdout);
			    MoveCursor(LINES, 0);
			    subscribe(active[my_group[cur_groupnum]].name,
					'!', my_group[cur_groupnum], FALSE);
			    sprintf(buf, "unsubscribed to %s",
					active[my_group[cur_groupnum]].name);
			    info_message(buf);
			    break;
			case '\t':
				for (i = cur_groupnum; i < local_top; i++)
					if (unread[i] != 0)
						break;
				if (i >= local_top) {
					info_message("No more groups to read");
					break;
				}
				erase_group_arrow();
				cur_groupnum = i;
				if (cur_groupnum >= last_group_on_screen)
					group_selection_page();
				else
					draw_group_arrow();
				space_mode = TRUE;
				goto go_into_group;
			case 'g':	/* prompt for a new group name */
				n = choose_new_group();
				if (n >= 0) {
					erase_group_arrow();
					cur_groupnum = n;
					if (cur_groupnum < first_group_on_screen
					|| cur_groupnum >= last_group_on_screen)
						group_selection_page();
					else
						draw_group_arrow();
				}
				break;
case 27: /* (ESC) common arrow keys */
				ch = ReadCh();
				if (ch == '[' || ch == 'O')
					ch = ReadCh();
				switch (ch) {
				case 'A':
				case 'D':
				case 'i':
goto select_up;
				case 'B':
				case 'I':
				case 'C':
					goto select_down;
				}
				break;
			case 'y':	/* pull in rest of groups from active */
				n = local_top;
				for (i = 0; i < num_active; i++)
					active[i].flag = NOTGOT;
				read_newsrc(FALSE);
				for (i = 0; i < num_active; i++)
if (active[i].flag & NOTGOT) {
						active[i].flag &= ~NOTGOT;
my_group[local_top] = i;
						unread[local_top] = -1;
						local_top++;
					}
				if (n < local_top) {
					sprintf(buf, "Added %d group%s",
						local_top - n,
						local_top - n == 1 ? "" : "s");
					group_selection_page();
					info_message(buf);
				} else
				    info_message("No more groups to yank in");
				break;
			case ctrl('U'):		/* page up */
				erase_group_arrow();
				cur_groupnum -= NOTESLINES / 2;
				if (cur_groupnum < 0)
					cur_groupnum = 0;
				if (cur_groupnum < first_group_on_screen
				||  cur_groupnum >= last_group_on_screen)
					group_selection_page();
				else
					draw_group_arrow();
				break;
			case ctrl('D'):		/* page down */
				erase_group_arrow();
				cur_groupnum += NOTESLINES / 2;
				if (cur_groupnum >= local_top)
					cur_groupnum = local_top - 1;
				if (cur_groupnum <= first_group_on_screen
				||  cur_groupnum >= last_group_on_screen)
					group_selection_page();
				else
					draw_group_arrow();
				break;
			case '!':
				shell_escape();
				group_selection_page();
				break;
			case 'v':
				info_message(cvers);
				break;
			case ctrl('N'):		/* line down */
			case 'j':
select_down:
				if (cur_groupnum + 1 >= local_top)
break;
				if (cur_groupnum + 1 >= last_group_on_screen) {
					cur_groupnum++;
					group_selection_page();
				} else {
					erase_group_arrow();
					cur_groupnum++;
					draw_group_arrow();
				}
				break;
			case ctrl('P'):		/* line up */
			case 'k':
select_up:
				if (!cur_groupnum)
					break;
				if (cur_groupnum <= first_group_on_screen) {
					cur_groupnum--;
					group_selection_page();
				} else {
					erase_group_arrow();
					cur_groupnum--;
					draw_group_arrow();
				}
				break;
			case 't':		/* redraw */
			case ctrl('W'):
			case ctrl('L'):
				group_selection_page();
				break;
			case ' ':
			case '\r':	/* go into group */
			case '\n':
				space_mode = FALSE;
go_into_group:
				clear_message();
				index_point = -1;
				do {
					group_page(
					  active[my_group[cur_groupnum]].name);
				} while (index_point == -3);
				group_selection_page();
				break;
			case '/':	/* search forward */
				search_group(TRUE);
				break;
			case '?':	/* search backward */
				search_group(FALSE);
				break;
			case 'q':	/* quit */
tass_done(0);
			case 'h':
				tass_select_help();
				group_selection_page();
				break;
			default:
			    info_message("Bad command.  Type 'h' for help.");
		}
	}
}
group_selection_page() {
	int i;
	int n;
	char new[10];
	char subs;
#ifdef SIGTSTP
	signal(SIGTSTP, select_susp);
#endif
	ClearScreen();
	printf("%s\r\n", nice_time());		/* print time in upper left */
	if (mail_check()) {			/* you have mail message */
		MoveCursor(0, 66);		/* in upper right */
		printf("you have mail\n");
	}
	center_line(1, "Group Selection");
	MoveCursor(INDEX_TOP, 0);
first_group_on_screen = (cur_groupnum / NOTESLINES) * NOTESLINES;
	last_group_on_screen = first_group_on_screen + NOTESLINES;
	if (last_group_on_screen >= local_top)
		last_group_on_screen = local_top;
	for (i = first_group_on_screen; i < last_group_on_screen; i++) {
		switch (unread[i]) {
		case -2:
			strcpy(new, "?   ");
			break;
		case -1:
			strcpy(new, "-   ");
			break;
		case 0:
			strcpy(new, "    ");
			break;
		default:
			sprintf(new, "%-4d", unread[i]);
		}
		n = my_group[i];
		if (active[n].flag & SUBS)	/* subscribed? */
			subs = ' ';
		else
			subs = 'u';	/* u next to unsubscribed groups */
		printf("   %c %4d  %-35s %s\r\n", subs, i+1,
							active[n].name, new);
	}
	draw_group_arrow();
}
prompt_group_num(ch)
char ch;
{
int num;
clear_message();
if ((num = parse_num(ch, "Select group> ")) == -1) {
		clear_message();
		return FALSE;
	}
	num--;		/* index from 0 (internal) vs. 1 (user) */
	if (num >= local_top)
		num = local_top - 1;
	if (num >= first_group_on_screen
	&&  num < last_group_on_screen) {
		erase_group_arrow();
		cur_groupnum = num;
		draw_group_arrow();
	} else {
		cur_groupnum = num;
		group_selection_page();
	}
	return TRUE;
}
erase_group_arrow() {
	erase_arrow(INDEX_TOP + (cur_groupnum-first_group_on_screen) );
}
draw_group_arrow() {
	draw_arrow(INDEX_TOP + (cur_groupnum-first_group_on_screen) );
}
search_group(forward)
int forward;
{
	char buf[LEN+1];
	char buf2[LEN+1];
	int i;
	int len;
	char *prompt;
clear_message();
	if (forward)
		prompt = "/";
	else
		prompt = "?";
	if (!parse_string(prompt, buf))
		return;
	if (strlen(buf))
		strcpy(group_search_string, buf);
	else if (!strlen(group_search_string)) {
		info_message("No search string");
		return;
	}
i = cur_groupnum;
	make_lower(group_search_string, buf);
	len = strlen(buf);
	do {
		if (forward)
			i++;
		else
			i--;
if (i >= local_top)
			i = 0;
		if (i < 0)
i = local_top - 1;
		make_lower(active[my_group[i]].name, buf2);
		if (match(buf, buf2, len)) {
			if (i >= first_group_on_screen
			&&  i < last_group_on_screen) {
				erase_group_arrow();
				cur_groupnum = i;
				draw_group_arrow();
			} else {
				cur_groupnum = i;
				group_selection_page();
			}
			return;
		}
	} while (i != cur_groupnum);
	info_message("No match");
}
tass_select_help() {
	char title[100];
sprintf(title, "%s, Group Selection Commands", TASS_HEADER);
	ClearScreen();
	center_line(0, title);
MoveCursor(2, 0);
	printf("\t4\tSelect group 4\r\n");
	printf("\t^D\tPage down\r\n");
	printf("\t^R\tReset .newsrc\r\n");
	printf("\t^U\tPage up\r\n");
	printf("\t^K\tDelete group\r\n");
	printf("\t^Y\tUndelete group\r\n");
	printf("\t<CR>\tRead current group\r\n");
	printf("\t<TAB>\tView next unread group\r\n");
	printf("\tc\tMark group as all read\r\n");
	printf("\tg\tChoose a new group by name\r\n");
	printf("\tj\tDown a line\r\n");
	printf("\tk\tUp a line\r\n");
	printf("\tq\tQuit\r\n");
	printf("\ts\tSubscribe to current group\r\n");
	printf("\tu\tUnsubscribe to current group\r\n");
	printf("\ty\tYank in groups that are not in the .newsrc\r\n");
	printf("\t$\tReread group list from .newsrc\r\n");
	printf("\t/?\tGroup search forward (?=backward)\r\n");
	center_line(LINES, "-- hit any key --");
	ReadCh();
}
choose_new_group() {
	char buf[LEN+1];
	char *p;
	int ret;
	if (!parse_string("Newsgroup> ", buf))
		return -1;
	for (p = buf; *p && (*p == ' ' || *p == '\t'); p++) ;
	if (*p == '\0')
		return -1;
	ret = add_group(p, TRUE);
	if (ret < 0)
		info_message("Group not found in active file");
	return ret;
}
/*
 *  Add a group to the selection list (my_group[])
 *  Return the index of my_group[] if group is added or was already
 *  there.  Return -1 if named group is not in active[].
 */
add_group(s, get_unread)
char *s;
int get_unread;			/* look in .newsrc for sequencer unread info? */
{
	long h;
	int i, j;
	extern long hash_groupname();
h = hash_groupname(s);
	for (i = group_hash[h]; i >= 0; i = active[i].next)
if (strcmp(s, active[i].name) == 0) {
			for (j = 0; j < local_top; j++)
				if (my_group[j] == i)
return j;
			active[i].flag &= ~NOTGOT;   /* mark that we got it */
my_group[local_top] = i;
			if (get_unread)
				unread[local_top] = get_line_unread(s, i);
			else
				unread[local_top] = -2;
			local_top++;
			return local_top - 1;
		}
	return -1;
}
@EOF
chmod 644 select.c
echo x - spool_open.c
cat >spool_open.c <<'@EOF'
#include	<stdio.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include "tass.h"
/* Hopefully one of these is right for you. */
#ifdef BSD
#	include <sys/types.h>
#	include <sys/dir.h>
#	define		DIR_BUF		struct direct
#	define		D_LENGTH	d_namlen
#endif
#ifdef M_XENIX
#	include <sys/ndir.h>
#	define		DIR_BUF		struct direct
#	define		D_LENGTH	d_namlen
#endif
#ifndef DIR_BUF
#	include	<sys/types.h>
#	include	<dirent.h>
#	define		DIR_BUF		struct dirent
#	define		D_LENGTH	d_reclen
#endif
char *
is_remote() {
	return "";
}
nntp_startup() {
}
nntp_finish() {
}
FILE *open_active_fp() {
	FILE *fp;
	fp = fopen(active_file, "r");
	if (fp == NULL) {
		fprintf(stderr, "can't open %s: ", active_file);
		perror("");
		exit(1);
	}
	return fp;
}
FILE *
open_art_fp(group_path, art)
char *group_path;
long art;
{
	char buf[LEN];
	struct stat sb;
	extern long note_size;
sprintf(buf, "%s/%s/%ld", SPOOLDIR, group_path, art);
if (stat(buf, &sb) < 0)
		note_size = 0;
	else
		note_size = sb.st_size;
	return fopen(buf, "r");
}
open_header_fd(group_path, art)
char *group_path;
long art;
{
	char buf[LEN];
	sprintf(buf, "%s/%s/%ld", SPOOLDIR, group_path, art);
	return open(buf, 0);
}
/*
 *  Longword comparison routine for the qsort()
 */
base_comp(a, b)
long *a;
long *b;
{
	if (*a < *b)
		return -1;
	if (*a > *b)
		return 1;
	return 0;
}
/*
 *  Read the article numbers existing in a group's spool directory
 *  into base[] and sort them.  base_top is one past top.
 */
setup_base(group, group_path)
char *group;
char *group_path;
{
	DIR *d;
	DIR_BUF *e;
	long art;
	char buf[200];
top_base = 0;
sprintf(buf, "%s/%s", SPOOLDIR, group_path);
	if (access(buf, 4) != 0)
		return;
	d = opendir(buf);
	if (d != NULL) {
		while ((e = readdir(d)) != NULL) {
			art = my_atol(e->d_name, e->D_LENGTH);
			if (art >= 0) {
				if (top_base >= max_art)
					expand_art();
				base[top_base++] = art;
			}
		}
		closedir(d);
		qsort(base, top_base, sizeof(long), base_comp);
	}
}
@EOF
chmod 644 spool_open.c
exit 0