Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Tass 3.2 newsreader

53 views
Skip to first unread message

Rich Skrenta

unread,
Apr 17, 1991, 12:55:41 PM4/17/91
to
Here is a new version of my Tass newsreader. This version has many bug fixes
and improvements, including NNTP support. Here is the list of changes from
the README:

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

Rich Skrenta

unread,
Apr 17, 1991, 1:00:36 PM4/17/91
to

# 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:
# COPYRIGHT Makefile README Tass.man
# art.c curses.c group.c hashstr.c
#

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

Rich Skrenta

unread,
Apr 17, 1991, 1:01:20 PM4/17/91
to

# 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

Rich Skrenta

unread,
Apr 17, 1991, 1:02:04 PM4/17/91
to

# 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:
# page.c screen.c select.c spool_open.c
#

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

0 new messages