skrenta@blekko.commodore.com (Rich Skrenta) (02/15/91)
# This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # # This archive contains: # 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@blekko.commodore.com
skrenta@blekko.commodore.com (Rich Skrenta) (02/15/91)
# This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # # This archive contains: # page.c prompt.c screen.c select.c # tass.h time.c # echo x - page.c cat >page.c <<'@EOF' #include <stdio.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.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 */ struct stat note_stat; /* so we can tell how big it is */ 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); 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; 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 '|': /* 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; 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_stat.st_size > 0) { percent = note_mark[note_page] * 100 / note_stat.st_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]; note_page = 0; sprintf(buf, "/usr/spool/news/%s/%ld", group_path, art); if (stat(buf, ¬e_stat) < 0) note_stat.st_size = 0; note_fp = fopen(buf, "r"); if (note_fp == NULL) { fprintf(stderr, "can't open %s: ", buf); perror(""); 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'; buf[strlen(buf)-1] = '\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; { 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'; } /* * 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]; } tass_page_help() { char ch; page_help_start: ClearScreen(); center_line(0, TASS_HEADER); center_line(1, "Article Pager Commands (page 1 of 2)"); MoveCursor(3, 0); printf("0 Read the base article in this thread\r\n"); printf("4 Read response 4 in this thread\r\n"); printf("<CR> Skip to next base article\r\n"); printf("<TAB> Advance to next page or unread article\r\n"); printf("b Back a page\r\n"); printf("f Post a followup\r\n"); printf("F Post a followup, copy text)\r\n"); printf("H Show article headers\r\n"); printf("i Return to index page\r\n"); printf("k Mark article as read & advance to next unread\r\n"); printf("K Mark rest of thread as read && advance to next unread\r\n"); printf("m Mail this article to someone\r\n"); printf("n Skip to the next article)\r\n"); printf("N Skip to the next unread article\r\n"); printf("p Go to the previous article\r\n"); printf("P Go to the previous unread article\r\n"); center_line(LINES, "-- hit space for more commands --"); ch = ReadCh(); if (ch != ' ') return; ClearScreen(); center_line(0, TASS_HEADER); center_line(1, "Article Pager Commands (page 2 of 2)"); MoveCursor(3, 0); printf("q Quit\r\n"); printf("r Reply through mail to author\r\n"); printf("R Reply through mail to author, copy text\r\n"); printf("s Save article to file\r\n"); printf("S Save thread to file\r\n"); printf("t Return to group selection index\r\n"); printf("z Mark article as unread\r\n"); printf("^R Redisplay first page of article\r\n"); printf("%%, ^X Toggle rot-13 decoding for this article\r\n"); printf("- Show last message\r\n"); printf("| Pipe article into command\r\n"); center_line(LINES, "-- hit any key --"); ch = ReadCh(); if (ch == 'b') goto page_help_start; } /* * 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]; fp = fopen(nam, "r"); if (fp == NULL) return; while (fgets(buf, 1024, fp) != NULL) { if (*buf == '\n') break; if (strncmp(buf, "To: ", 4) == 0) { buf[strlen(buf)-1] = '\0'; strncpy(buf2, &buf[4], LEN); buf2[LEN-1] = '\0'; parse_from(buf2, mail_to, dummy); break; } } fclose(fp); } mail_to_someone() { char nam[100]; FILE *fp; char ch; char buf[200]; char mail_to[LEN+1]; char subj[LEN+1]; setuid(real_uid); setgid(real_gid); if (!parse_string("Mail article to: ", mail_to)) return; if (mail_to[0] == '\0') return; sprintf(nam, "%s/.letter", homedir); if ((fp = fopen(nam, "w")) == NULL) { fprintf(stderr, "can't open %s: ", nam); perror(""); 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, ""); 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': 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("\nMailing to %s...", mail_to); fflush(stdout); sprintf(buf, "%s \"%s\" < %s", MAILER, mail_to, nam); if (invoke_cmd(buf)) { printf("Message sent\n"); fflush(stdout); goto mail_to_someone_done; } else { printf("Command failed: %s\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(""); 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, "> "); } fclose(fp); ch = 'e'; while (1) { switch (ch) { case 'e': invoke_editor(nam); break; case 'a': return FALSE; case 's': strcpy(mail_to, note_from_addr); find_new_to(nam, mail_to); printf("\nMailing to %s... ", mail_to); fflush(stdout); sprintf(buf, "/usr/bin/rmail \"%s\" < %s", mail_to, nam); if (invoke_cmd(buf)) { printf("Message sent\n"); fflush(stdout); goto mail_to_author_done; } else { printf("Command failed: %s\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(""); 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, "> "); } fclose(fp); ch = 'e'; while (1) { switch (ch) { case 'e': invoke_editor(nam); break; case 'a': return FALSE; case 'p': printf("Posting... "); fflush(stdout); sprintf(buf, "%s/inews -h < %s", LIBDIR, nam); if (invoke_cmd(buf)) { printf("article posted\n"); fflush(stdout); goto post_response_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_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 - 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); while (ReadCh() != '\n') ; } @EOF chmod 644 prompt.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); 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]); 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 ' ': clear_message(); 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 '\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]; 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(group_search_string, buf); else if (!strlen(group_search_string)) { info_message("No search pattern"); return; } i = cur_groupnum; glob_name(group_search_string, buf); if ((re = regcmp(buf, NULL)) == NULL) { info_message("Bad search pattern"); return; } do { if (forward) i++; else i--; if (i >= local_top) i = 0; if (i < 0) i = local_top - 1; if (regex(re, active[my_group[i]].name) != NULL) { 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() { ClearScreen(); center_line(0, TASS_HEADER); center_line(1, "Group Selection Commands"); MoveCursor(3, 0); printf("4 Select group 4\r\n"); printf("^D Page down\r\n"); printf("^R Reset .newsrc\r\n"); printf("^U Page up\r\n"); printf("^K Delete group\r\n"); printf("^Y Undelete group\r\n"); printf("<CR> Read current group\r\n"); printf("<TAB> View next unread group\r\n"); printf("c Mark group as all 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("q Quit\r\n"); printf("s Subscribe to current group\r\n"); printf("u Unsubscribe to current group\r\n"); printf("y Yank in groups that are not in the .newsrc\r\n"); printf("$ Reread group list from .newsrc\r\n"); printf("/ Search forward for group\r\n"); printf("? Search backward for group\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; { /* find the hash of the group name */ char *t = s; h = *t++; while (*t) h = (h * 64 + *t++) % TABLE_SIZE; } 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 - tass.h cat >tass.h <<'@EOF' #define LIBDIR "/usr/lib/news" #define SPOOLDIR "/usr/spool/news" #define MAILER "/bin/rmail" #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 /* #define MAX_SUBJ (COLS - 42) */ struct header { long artnum; char subject[MAX_SUBJ]; char *nore; /* pointer into subject after Re: */ char from[MAX_FROM]; int thread; long hash; 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.nore * pointer into header.subject after the Re:'s. * * header.hash: * hash of the subject minus the re's. For fast subject comparison * * 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 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.0" @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 -- skrenta@blekko.commodore.com