Path: utzoo!utgpu!cs.utexas.edu!swrinde!zaphod.mps.ohio-state.edu!unix.cis.pitt.edu!dsinc!bagate!cbmvax!amix!blekko!skrenta From: skrenta@blekko.commodore.com (Rich Skrenta) Newsgroups: alt.sources Subject: Tass newsreader (part 1 of 2) Message-ID: <145@blekko.commodore.com> Date: 14 Feb 91 19:33:07 GMT References: <144@blekko.commodore.com> Organization: BlekkoTek World Headquarters, Frazer PA, USA Lines: 3417 # 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 #include #include "tass.h" /* Hopefully one of these is right for you. */ #ifdef BSD # include # include # define DIR_BUF struct direct # define D_LENGTH d_namlen #endif #ifdef M_XENIX # include # define DIR_BUF struct direct # define D_LENGTH d_namlen #endif #ifndef DIR_BUF # include # include # 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 #include #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 # else # include # endif # else # include #endif */ #include /* #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 #include #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(" Read current article\r\n"); printf(" 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 #include #include #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 #include #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 #include #include #include #include #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