Tass newsreader (part 1 of 2)
Rich Skrenta
skrenta at blekko.commodore.com
Fri Feb 15 06:33:07 AEST 1991
# 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 Todo art.c
# curses.c group.c mail.c main.c
# misc.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'
# Edit the defines in tass.h to point tass at the correct news libdir
#
# For Berkeley systems:
#
# CFLAGS= -DBSD
# LIBS= -lcurses -ltermcap
# art.c needs to know whether readdir returns struct dirent or
# struct direct. You don't need to change anything if:
# you're bsd and have BSD defined
# you're Xenix 286
# you're SCO Unix and have -UM_XENIX defined
# you use struct dirent
#
# For System V and Xenix:
#
CFLAGS=-g
LIBS= -lcurses -lgen
OBJECTS = curses.o art.o group.o mail.o main.o misc.o page.o \
prompt.o screen.o select.o time.o
tass: $(OBJECTS)
cc $(CFLAGS) -o tass $(OBJECTS) $(LIBS)
shar:
-mv -f ../tass.shar ../tass.shar-
shar -v [A-Z]* *.[ch] > ../tass.shar
art.o: art.c tass.h
curses.o: curses.c
group.o: group.c tass.h
mail.o: mail.c
main.o: main.c tass.h
misc.o: misc.c tass.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
time.o: time.c
@EOF
chmod 644 Makefile
echo x - Todo
cat >Todo <<'@EOF'
make initial .newsrc just be created but contain nothing--must
subscribe
find_new_to doesnn't seem to work in mail_to_someone()
@EOF
chmod 644 Todo
echo x - art.c
cat >art.c <<'@EOF'
#include <stdio.h>
#include <signal.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 index_file[LEN+1];
char *glob_art_group;
#ifdef SIGTSTP
void
art_susp(i)
int i;
{
Raw(FALSE);
putchar('\n');
signal(SIGTSTP, SIG_DFL);
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);
}
if (local_index)
find_local_index(group);
else
sprintf(index_file, "%s/%s/.tindex", SPOOLDIR, group_path);
load_index();
modified = read_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();
}
/*
* 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.
*/
scan_dir(group)
char *group;
{
DIR *d;
DIR_BUF *e;
long art;
char buf[200];
top_base = 0;
sprintf(buf, "%s/%s", SPOOLDIR, group);
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);
}
/*
* Index a group. Assumes any existing index has already been
* loaded.
*/
read_group(group)
char *group;
{
char buf[200];
int fd;
long art;
int count;
int modified = FALSE;
int respnum;
int i;
scan_dir(group); /* 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 */
#if 0
if (!update) {
MoveCursor(LINES, 0);
fputs("Indexing... ", stdout);
fflush(stdout);
}
#endif
}
/*
* 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;
sprintf(buf, "%s/%s/%ld", SPOOLDIR, group, art);
fd = open(buf, 0);
if (fd < 0) {
fprintf(stderr, "can't open article %s: ", buf);
perror("");
continue;
}
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[i].hash == arts[j].hash
&& arts[j].thread != -2
&& strncmp(arts[i].nore, arts[j].nore, 10) == 0) {
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;
{
#if 1
while (*s == 'r' || *s == 'R') {
if ((*(s+1) == 'e' || *(s+1) == 'E') && *(s+2) == ':')
s += 3;
else
break;
if (*s == ' ')
s++;
}
#else
while (*s == 'R') {
if (strncmp(s, "Re: ", 4) == 0)
s += 4;
else if (strncmp(s, "Re:", 3) == 0)
s += 3;
else if (strncmp(s, "Re^2: ", 6) == 0)
s += 6;
else
break;
}
#endif
return s;
}
/*
* Hash the subjects (after eating the Re's off) for a quicker
* thread search later. We store the hashes for subjects in the
* index file for speed.
*/
long
hash_s(s)
char *s;
{
long h = 0;
while (*s)
h = h * 64 + *s++;
return h;
}
parse_headers(fd, h)
int fd;
struct header *h;
{
char buf[1024];
char *p, *q;
char flag;
if (read(fd, buf, 1024) <= 0)
return FALSE;
buf[1023] = '\0';
p = buf;
h->from[0] = '\0';
h->subject[0] = '\0';
h->nore = h->subject;
h->hash = 0;
while (1) {
q = p;
while (*p && *p != '\n') {
if (*p & 0x7F < 32)
*p = ' ';
p++;
}
flag = *p;
*p++ = '\0';
if (strncmp(q, "From: ", 6) == 0) {
strncpy(h->from, &q[6], MAX_FROM);
h->from[MAX_FROM-1] = '\0';
} else if (strncmp(q, "Subject: ", 9) == 0) {
h->hash = hash_s(eat_re(&q[9]));
strncpy(h->subject, &q[9], MAX_SUBJ);
h->subject[MAX_SUBJ-1] = '\0';
h->nore = eat_re(h->subject);
}
if (!flag)
break;
}
return TRUE;
}
/*
* Write out a .tindex 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];
FILE *fp;
char *p, *q;
long l;
fp = fopen(index_file, "w");
if (fp == NULL)
return;
fprintf(fp, "%s\n", group);
fprintf(fp, "%d\n", num_arts());
for (i = 0; i < top; i++)
if (arts[i].thread != -2) {
p = arts[i].nore;
q = arts[i].subject;
l = p - q;
fprintf(fp, "%ld\n%s\n%s\n%ld\n%ld\n",
arts[i].artnum,
arts[i].subject,
arts[i].from,
arts[i].hash,
#if 0
(long) arts[i].nore - (long) arts[i].subject);
#else
l);
#endif
}
fclose(fp);
chmod(index_file, 0644);
}
/*
* 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 .tindex file.
*/
load_index()
{
int i;
long j;
char buf[200];
FILE *fp;
int first = TRUE;
top = 0;
fp = fopen(index_file, "r");
if (fp == NULL)
return;
if (fgets(buf, 200, fp) == NULL
|| fgets(buf, 200, fp) == NULL) {
fprintf(stderr, "one\n");
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) {
fprintf(stderr, "two\n");
goto corrupt_index;
}
arts[top].artnum = atol(buf);
if (fgets(buf, 200, fp) == NULL) {
fprintf(stderr, "three\n");
goto corrupt_index;
}
my_strncpy(arts[top].subject, buf, MAX_SUBJ-1);
if (fgets(buf, 200, fp) == NULL) {
fprintf(stderr, "four\n");
goto corrupt_index;
}
my_strncpy(arts[top].from, buf, MAX_FROM-1);
if (fgets(buf, 200, fp) == NULL) {
fprintf(stderr, "five\n");
goto corrupt_index;
}
arts[top].hash = atol(buf);
if (fgets(buf, 200, fp) == NULL) {
fprintf(stderr, "six\n");
goto corrupt_index;
}
j = atol(buf);
#if 0
if (j < 0 || j > 100) {
#if 0
goto corrupt_index;
#else
arts[top].nore = eat_re(arts[top].subject);
#endif
} else
arts[top].nore = arts[top].subject + j;
#else
arts[top].nore = eat_re(arts[top].subject);
#endif
top++;
}
fclose(fp);
return;
corrupt_index:
fprintf(stderr, "index file %s corrupt\n", index_file);
fprintf(stderr, "top = %d\n", top);
exit(1);
unlink(index_file);
top = 0;
}
/*
* Look in the local $HOME/.tindex (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;
{
char *t = group;
h = *t++;
while (*t)
h = h * 64 + *t++;
}
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, *_setunderline, *_clearunderline;
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);
_setunderline = tgetstr("us", &ptr);
_clearunderline = tgetstr("ue", &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); */
}
#if 0
StartUnderline()
{
/** start underline mode **/
if (!_setunderline)
return(-1);
tputs(_setunderline, 1, outchar);
fflush(stdout);
return(0);
}
EndUnderline()
{
/** the compliment of start underline mode **/
if (!_clearunderline)
return(-1);
tputs(_clearunderline, 1, outchar);
fflush(stdout);
return(0);
}
#endif
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]; /* last search pattern */
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);
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];
glob_group = group;
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 '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) {
fix_new_highest(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;
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 {
fix_new_highest(cur_groupnum);
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 {
fix_new_highest(cur_groupnum);
cur_groupnum--;
index_point = -3;
space_mode = FALSE;
goto group_done;
}
break;
case ' ':
if (top_base <= 0)
info_message("*** No Articles ***");
else if (last_subj_on_screen == top_base)
info_message("*** End of Articles ***");
else
clear_message();
break;
case '\t':
fix_new_highest(cur_groupnum);
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 '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) {
fix_new_highest(cur_groupnum);
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) {
fix_new_highest(cur_groupnum);
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 */
fix_new_highest(cur_groupnum);
goto group_done;
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) {
fix_new_highest(cur_groupnum);
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) {
fix_new_highest(cur_groupnum);
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;
fix_new_highest(cur_groupnum);
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:
update_newsrc(group, my_group[cur_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_subject(forward, group)
int forward;
char *group;
{
char buf[LEN+1];
int i;
extern char *regcmp();
extern char *regex();
char *re;
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 pattern");
return;
}
i = index_point;
glob_name(subject_search_string, buf);
if ((re = regcmp(buf, NULL)) == NULL) {
info_message("Bad search pattern");
return;
}
do {
if (forward)
i++;
else
i--;
if (i >= top_base)
i = 0;
if (i < 0)
i = top_base - 1;
if (regex(re, arts[ base[i] ].subject) != NULL) {
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("");
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");
fclose(fp);
ch = 'e';
while (1) {
switch (ch) {
case 'e':
invoke_editor(nam);
break;
case 'a':
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 ch;
group_help_start:
ClearScreen();
center_line(0, TASS_HEADER);
center_line(1, "Index Page Commands (page 1 of 2)");
MoveCursor(3, 0);
printf("4 Select article 4\r\n");
printf("^D Page down\r\n");
printf("^U Page up\r\n");
printf("<CR> Read current article\r\n");
printf("<TAB> View next unread article or group\r\n");
printf("c Mark all articles as read\r\n");
printf("g Choose a new group by name\r\n");
printf("j Down a line\r\n");
printf("k Up a line\r\n");
printf("n Go to next group\n");
printf("N Go to next unread article\n");
printf("p Go to previous group\n");
printf("P Go to previous unread article\n");
printf("q Quit\r\n");
center_line(LINES, "-- hit space for more commands --");
ch = ReadCh();
if (ch != ' ')
return;
ClearScreen();
center_line(0, TASS_HEADER);
center_line(1, "Index Page Commands (page 2 of 2)");
MoveCursor(3, 0);
printf("s Subscribe to this group\r\n");
printf("t Return to group selection index\r\n");
printf("u Unsubscribe to this group\r\n");
printf("w Post an article\r\n");
printf("/ Search forward for subject\r\n");
printf("? Search backward for subject\r\n");
printf("- Show last message\r\n");
center_line(LINES, "-- hit any key --");
ch = ReadCh();
if (ch == 'b')
goto group_help_start;
}
@EOF
chmod 644 group.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 "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.0 (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;
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;
read_active(); /* load the active file into active[] */
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.0\n");
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);
mail_setup(); /* record mailbox size for "you have mail" */
selection_index();
tass_done(0);
}
tass_done(ret)
int ret;
{
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;
num_active = 0;
if ((fp = fopen(active_file, "r")) == NULL) {
fprintf(stderr, "can't open %s: ", active_file);
perror("");
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();
{ /* hash group name for fast lookup later */
char *t = buf;
h = *t++;
while (*t)
h = (h * 64 + *t++) % TABLE_SIZE;
}
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;
#if 0
unread[local_top] = active[i].max - active[i].min;
#else
unread[local_top] = -1;
#endif
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 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%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);
update_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);
}
@EOF
chmod 644 main.c
echo x - misc.c
cat >misc.c <<'@EOF'
#include <stdio.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 */
/*
* 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);
}
/*
* Make regular expressions pleasant for the masses: glob them
*/
glob_name(group, grp)
char *group;
char *grp;
{
char *p, *q;
/*
* Prefix the .'s in the group name so they won't be interpreted
* as regular expression commands. Change 'all' into '*'
*/
p = group;
q = grp;
if (strncmp(p, "all", 3) == 0 && (p[3] == '.' || p[3] == '\0')) {
*q++ = '.';
*q++ = '*';
p = &p[3];
}
while (*p != '\0') {
if (*p == '.') {
*q++ = '\\';
*q++ = '.';
p++;
if (strncmp(p, "all", 3) == 0 &&
(p[3] == '.' || p[3] == '\0')) {
*q++ = '.';
*q++ = '*';
p = &p[3];
}
} else if (*p == '*') {
*q++ = '.';
*q++ = '*';
p++;
} else
*q++ = *p++;
}
*q = '\0';
}
/*
* 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(newsrc, "%s/.newsrc", homedir);
sprintf(newnewsrc, "%s/.newnewsrc", homedir);
sprintf(delgroups, "%s/.delgroups", homedir);
sprintf(indexdir, "%s/.tindex", 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];
if (first) {
strcpy(editor, get_val("EDITOR", "/usr/bin/vi"));
first = FALSE;
}
sprintf(buf, "%s %s", editor, nam);
printf("%s\n", buf);
return invoke_cmd(buf);
}
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 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;
}
@EOF
chmod 644 misc.c
exit 0
--
skrenta at blekko.commodore.com
More information about the Alt.sources
mailing list