Path: utzoo!utgpu!cs.utexas.edu!uunet!elroy.jpl.nasa.gov!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 2 of 2) Message-ID: <146@blekko.commodore.com> Date: 14 Feb 91 19:35:43 GMT References: <144@blekko.commodore.com> <145@blekko.commodore.com> Organization: BlekkoTek World Headquarters, Frazer PA, USA Lines: 2388 # 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 #include #include #include #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(" Skip to next base article\r\n"); printf(" 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 #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 #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 #include #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(" Read current group\r\n"); printf(" 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 #include 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