[alt.sources] Tass 3.2 newsreader part 2/3

skrenta@blekko.commodore.com (Rich Skrenta) (04/18/91)

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# This archive contains:
#	mail.c		main.c		misc.c		nntp.h		
#	nntp_open.c	prompt.c	tass.h		time.c		
#

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