/* * readnews * * Michael Rourke (UNSW) April 1984 */ #include "defs.h" #define ARTSEP "/" char admsub[BUFLEN] = "general"; char dfltsub[BUFLEN] = "general"; char mailvia[BUFLEN] = ""; char *mailpath = MAIL; #define MAXARGV 10 /* used in building argv[]s below */ bool iflag; /* -i ignore .newsrc */ bool lflag; /* -l print headers only */ bool cflag; /* -c check for news only */ bool pflag; /* -p print everything selected */ bool Cflag; /* -C verbose -c */ bool sflag; /* -s print newsgroup subscription list */ bool splus; /* -s+ */ bool sminus; /* -s- */ char *sarg; /* arg to -s[+-] */ char *nflag; /* -n newsgroups */ extern char *rcgrps; /* -n newsgroups from newsrc file */ bool n_on_cline; /* nflag set on command line */ char *disable; /* -d disabled commands */ extern newsrc *rc; /* internal .newsrc */ active *alist; /* internal active list */ long now; /* current time */ bool interrupt; /* if interrupt hit */ char *newsdir; /* %news */ bool su; /* if super user (not used) */ applycom list(), check(), commands(); void onintr(); bool ureject(), seen(), subs(), subsub(); char *progname; main(argc, argv) int argc; char *argv[]; { char buf[BUFSIZ], *p; progname = argv[0]; setbuf(stdout, buf); if (options(argc, argv, true) < 0) { (void) fprintf(stderr, "Usage: readnews [-n newsgroups] [-i] [-clpCL] [-Agroup] [-Dgroup]\n"); exit(1); } now = time(&now); newsdir = newstr(fullartfile((char *)NULL)); getctl(); if (!iflag) readnewsrc(); if (nflag) convgrps(nflag); else nflag = dfltsub; if (rcgrps) convgrps(rcgrps); if (!n_on_cline && !ngmatch(admsub, nflag)) nflag = newstr3(admsub, NGSEPS, nflag); if ((int) sflag + (int) lflag + (int) cflag + (int) pflag > 1) error("-clpsC flags are mutually exclusive."); /* user has private mailer? */ if ((p = getenv("MAILER")) != NULL) mailpath = newstr(p); alist = readactive(); if (sflag) { if (subs() && !iflag) writenewsrc(alist); } else if (lflag) apply(alist, nflag, list, false); else if (cflag) apply(alist, nflag, check, false); else { if (!pflag) { if (signal(SIGINT, SIG_IGN) != SIG_IGN) (void) signal(SIGINT, onintr); if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) (void) signal(SIGQUIT, onintr); } apply(alist, nflag, commands, true); if (!iflag) writenewsrc(alist); } fflush(stdout); exit(0); } #if MANGRPS /* * if newsgroup "ng" isn't subscribed to, add it to subscription list */ addsub(ng, slist) char *ng; char **slist; { if (!ngmatch(ng, *slist)) *slist = newstr3(ng, NGSEPS, *slist); } #endif /* ARGSUSED */ void onintr(dummy) int dummy; { if (signal(SIGINT, SIG_IGN) != SIG_IGN) (void) signal(SIGINT, onintr); if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) (void) signal(SIGQUIT, onintr); interrupt = true; } /* * process options * can be called from readnewsrc() */ int /* < 0 failure, otherwise success */ options(argc, argv, cline) int argc; char *argv[]; bool cline; { int c; extern int optind; extern char *optarg; optind = 1; /* reset scan, if necessary */ while ((c = getopt(argc, argv, "n:d:iLA:D:plcC")) != EOF) switch (c) { case 'n': if (cline) nflag = optarg, n_on_cline = true; else { if (!n_on_cline) nflag = (nflag? catstr2(nflag, NGSEPS, optarg): newstr(optarg)); rcgrps = (rcgrps? catstr2(rcgrps, NGSEPS, optarg): newstr(optarg)); } break; case 'd': disable = (disable? catstr2(disable, "", optarg): newstr(optarg)); break; case 'i': iflag = true; break; case 'L': sflag = true; break; case 'A': sflag = true; splus = true; sarg = optarg; break; case 'D': sflag = true; sminus = true; sarg = optarg; break; case 'p': pflag = true; break; case 'l': lflag = true; break; case 'c': cflag = true; break; case 'C': cflag = Cflag = true; break; case '?': default: return(-1); break; } return(0); } /* * subscription list handling * return true if newsrc is to be re-written */ bool subs() { register newsrc *np; register active *ap; register char *tmp, *com; register FILE *f; if (splus || sminus) { if (strpbrk(sarg, BADGRPCHARS)) { (void) printf("%s: Illegal char in newsgroup.\n", sarg); return false; } if (ngmatch(sarg, nflag)) { /* * normally we subscribe, check for an exclusion */ for (np = rc; np; np = np->n_next) if (CMP(sarg, np->n_name) == 0) break; if (np) { /* * altering subscribe flag is all * we need to change */ np->n_subscribe = splus; return true; } if (sminus) { /* * try deleting from sub list */ if (subsub(sarg, rcgrps)) return true; /* * add specific exclusion */ rcgrps = newstr4(rcgrps, NGSEPS, NEGS, sarg); return true; } } else if (splus) { /* * we don't subscribe, * try deleting !sarg first */ tmp = newstr2(NEGS, sarg); subsub(tmp, rcgrps); if (!ngmatch(sarg, rcgrps)) /* * didn't work, so add explicit subscription */ rcgrps = rcgrps? newstr3(rcgrps, NGSEPS, sarg): newstr(sarg); return true; } } else { (void) printf("Subscription list: %s", nflag); for (np = rc; np; np = np->n_next) if (!np->n_subscribe && ngmatch(np->n_name, nflag)) (void) printf(",!%s", np->n_name); (void) printf("\n"); } return false; } /* * try and delete group from subscription list * return true if successful */ bool subsub(grp, slist) char *grp; char *slist; { register char *delim; while (*slist) { if (delim = strchr(slist, NGSEPCHAR)) *delim = '\0'; if (CMP(grp, slist) == 0) { if (delim) (void) strcpy(slist, delim + 1); else if (slist[-1] == ',') slist[-1] = '\0'; else slist[0] = '\0'; return true; } if (delim) *delim = NGSEPCHAR, slist = delim + 1; else break; } return false; } char * ltoa(l) long l; { static char buf[30]; sprintf(buf, "%ld", l); return buf; } /* * list titles command (-l) */ applycom list(ap, np) active *ap; newsrc *np; { static active *lastap; static bool first = true; register char *fname; register FILE *f; header h; ino_t ino; np->n_last++; fname = convg(newstr5(newsdir, "/", ap->a_name, ARTSEP, ltoa(np->n_last))); ino = 0; f = fopen(fname, "r"); free(fname); if (!f || seen(f, &ino)) return next; gethead(f, &h); if (first) { (void) printf("News articles:\n"); first = false; } if (lastap != ap) (void) printf(" %s:\n", ap->a_name); lastap = ap; (void) printf(" %-4d %s\n", np->n_last, h.h_subject); (void) fclose(f); freehead(&h); if (ino) seen(NIL(FILE), &ino); return next; } /* * check command (-c or -C) */ applycom check(ap, np) active *ap; newsrc *np; { static bool done; np->n_last++; if (Cflag) { register long num; if (!done) (void) printf("You have news:\n"); done = true; num = ap->a_seq - np->n_last + 1; (void) printf("\t%s at most %ld article%s\n", ap->a_name, num, (num > 1? "s": "")); return nextgroup; } else { (void) printf("You have news.\n"); fflush(stdout); exit(0); /* NOTREACHED */ } } /* * normal command handler (or pflag) * commands: * * \n print current article * n go to next article * q quit * r reply * f followup * p postnews * N [newsgrp] next newsgroup * s [file] save * U unsubscribe from group * !stuff shell escape * number or . go to number * - back to previous article (toggle) * x quick exit * h long header info * H full header * * inside r, f or p: * .e edit * .i interpolate * . or EOT terminate message * .!comd shell escape */ applycom commands(ap, np, last, pushed) active *ap; newsrc *np; bool last; bool pushed; { register char *com, *arg; register int c, size; register long i; register FILE *f; char *fname; header h; newsrc ntmp; ino_t ino; bool printed, pheader, verbose, hadinterrupt; applycom nextact; static char errmess[] = "Unknown command; type `?' for help.\n"; static char form[] = "%s: %s\n"; static char savedsys[BUFSIZ / 2]; static active *lastap, *rlastap; static newsrc lastn; static char number[20]; static active *wantap; extern char t_from[], t_subject[], t_date[]; extern char t_newsgroups[], t_path[], t_sender[]; extern char t_replyto[], t_organization[]; extern active *activep(); if (last) { /* * give user one last chance to * see this article */ ap = rlastap; np = &lastn; wantap = NIL(active); if (!ap || pflag) return stop; } else if (wantap) /* * doing an "n newsgroup" command */ if (wantap != ap) return nextgroup; else wantap = NULL; fname = convg(newstr5(newsdir, "/", ap->a_name, ARTSEP, ltoa(np->n_last + 1))); f = fopen(fname, "r"); ino = 0; if (!f || !last && !pushed && seen(f, &ino)) { if (pushed) (void) printf("Article %ld (%s) no longer exists.\n", np->n_last + 1, ap->a_name); else np->n_last++; if (f) (void) fclose(f); free(fname); return next; } gethead(f, &h); (void) printf("\n"); interrupt = hadinterrupt = verbose = false; if (last) { (void) printf("No more articles (press RETURN again to quit).\n"); printed = pheader = true; } else printed = pheader = false; while (1) { if (lastap != ap) { size = strlen(ap->a_name) + sizeof("Newsgroup"); for (i = 0; i < size; i++) (void) putc('-', stdout); (void) printf("\nNewsgroup %s\n", ap->a_name); for (i = 0; i < size; i++) (void) putc('-', stdout); (void) printf("\n\n"); } lastap = ap; if (!pheader) { time_t itsdate; (void) printf("Article %ld of %ld (%s)", np->n_last + 1, ap->a_seq, ap->a_name); if (h.h_lines != 0) (void) printf(" (%s lines)", h.h_lines); if (h.h_date != NULL) { itsdate = atot(h.h_date); (void) printf(" %s", ctime(&itsdate)); } else (void) printf(" %s", "\n"); (void) printf(form, t_from, h.h_from); (void) printf(form, t_subject, h.h_subject); if (verbose || pflag) { (void) printf(form, t_date, h.h_date); (void) printf(form, t_newsgroups, h.h_newsgroups); (void) printf(form, t_path, h.h_path); if (h.h_sender) (void) printf(form, t_sender, h.h_sender); if (h.h_replyto) (void) printf(form, t_replyto, h.h_replyto); if (h.h_organisation) (void) printf(form, t_organization, h.h_organisation); verbose = false; } pheader = true; } if (!pushed && number[0]) /* * just returned from a number command * and have another to do */ com = number; else if (pflag) /* * just print it */ com = ""; else { (void) printf("? "); if (fflush(stdout) == EOF) { (void) printf("\n? "); (void) fflush(stdout); } interrupt = false; if ((com = mgets()) == NIL(char)) { if (interrupt) if (!hadinterrupt) { clearerr(stdin); (void) printf("Interrupt\n"); hadinterrupt = true; interrupt = false; continue; } else exit(1); nextact = stop; break; } hadinterrupt = false; } if (disable && *com && strchr(disable, *com) != NULL) { (void) printf("Command `%c' disabled.\n", *com); continue; } if (*com == '!') { if (com[1] == '!') { (void) printf("!%s\n", savedsys); com = savedsys; } else com++; (void) fflush(stdout); (void) fcntl(fileno(f), F_SETFD, 1); /* close on exec */ (void) system(com); if (com != savedsys) strncpy(savedsys, com, sizeof(savedsys) - 1); (void) printf("!\n"); if (!printed) pheader = false; continue; } /* * check command syntax */ if (*com && !isdigit(*com) && com[1] && (!isspace(com[1]) || strchr("Nsm", *com) == NULL)) { (void) printf(errmess); continue; } if (c = *com) { arg = com; while (isspace(*++arg)) ; } else arg = NULL; switch (c) { case 0: case '.': if (!printed || c == '.') { if (pflag) (void) printf("\n"); print(&h, f); if (pflag) { nextact = next; break; } printed = true; continue; } case 'n': /* B compatible */ case '+': case ';': nextact = next; break; case '?': help(); continue; case 'r': reply(&h, fname); continue; case 'f': followup(&h, fname); continue; case 'p': pnews(ap->a_name); continue; case 'U': if (ngmatch(np->n_name, admsub)) { (void) printf( "Group \"%s\" can't be unsubscribed.\n", np->n_name); continue; } np->n_subscribe = false; nextact = nextgroup; break; case 'N': /* B compatible */ if (!*arg) { nextact = nextgroup; break; } if ((wantap = activep(arg)) == NIL(active)) { (void) printf("%s: non-existent newsgroup.\n", arg); continue; } if (!ngmatch(arg, nflag)) { (void) printf("%s: is not subscribed to!\n", arg); wantap = NULL; continue; } nextact = searchgroup; break; case 's': save(&h, f, arg); continue; case 'q': nextact = stop; break; case 'x': fflush(stdout); exit(0); case 'h': verbose = true; pheader = false; continue; case 'H': puthead(&h, stdout, printing); continue; case '-': if (pushed) { nextact = next; break; } if (!rlastap || !lastn.n_name) { (void) printf("Can't go back!\n"); continue; } nextact = commands(rlastap, &lastn, false, true); /* * number commands, after a "-" act on the * group of the "-" command */ while (number[0]) { ntmp = lastn; ntmp.n_last = atol(number) - 1; number[0] = '\0'; nextact = commands(rlastap, &ntmp, false, true); } if (nextact != next) break; (void) printf("\n"); pheader = false; continue; default: if (isdigit(c)) { /* i = atol(arg); */ i = c - '0'; while (isdigit(*arg)) i = i * 10 + *arg++ - '0'; } if (!isdigit(c) || *arg != '\0') { (void) printf(errmess); continue; } number[0] = '\0'; if (i < ap->a_low || i > ap->a_seq) { (void) printf( "Articles in \"%s\" group range %ld to %ld.\n", np->n_name, ap->a_low, ap->a_seq); continue; } if (pushed) { sprintf(number, "%ld", i); nextact = next; break; } ntmp = *np; ntmp.n_last = i - 1; if ((nextact = commands(ap, &ntmp, false, true)) != next) break; if (!number[0]) { (void) printf("\n"); pheader = false; } continue; } break; } rlastap = ap; lastn = *np; if (!pushed && (nextact == next || printed)) { np->n_last++; if (ino) seen(NIL(FILE), &ino); } freehead(&h); (void) fclose(f); free(fname); return nextact; } /* * see if the article has links, if so have we seen it? * close file if we return true * * called twice, * first (with f set) to test (and possibly set *ino) * again to put *ino in memory */ bool seen(f, ino) FILE *f; ino_t *ino; { static int num; static ino_t *ilist; struct stat statb; register int i; if (f) { if (fstat(fileno(f), &statb) != 0 || statb.st_nlink <= 1) return false; for (i = 0; i < num; i++) if (ilist[i] == statb.st_ino) { (void) fclose(f); return true; } *ino = statb.st_ino; return false; } else if (*ino) { num++; ilist = (ino_t * ) (ilist ? myrealloc((char *) ilist, (int) sizeof(ino_t) * num) : myalloc((int) sizeof(ino_t))); ilist[num - 1] = *ino; } return true; } /* * print out help file */ help() { register FILE *f; register int c; register char *helppath; helppath = ctlfile("readnews.help"); if ((f = fopen(helppath, "r")) == NIL(FILE)) { (void) printf("Can't open %s\n", helppath); return; } while ((c = getc(f)) != EOF) (void) putc(c, stdout); (void) fclose(f); } /* * reply to sender by mail */ /* ARGSUSED fname */ reply(hp, fname) header *hp; char *fname; { char *argv[MAXARGV]; register int argc; argc = 0; argv[argc++] = "mail"; #ifdef UNSWMAIL argv[argc++] = "-s"; if ((argv[argc++] = getsubject(hp)) == NIL(char)) return; argv[argc++] = "-i"; argv[argc++] = fname; #endif if ((argv[argc++] = getretaddr(hp)) == NIL(char)) { (void) printf("Can't work out an address!\n"); return; } argv[argc++] = NIL(char); run(mailpath, argv, false); free(argv[argc - 2]); } /* * generate correct headers for a followup article * then call postnews. */ followup(hp, fname) header *hp; char *fname; { char tmpf[50]; register FILE *fo; char *s = getsubject(hp); if (s == NULL) return; (void) strcpy(tmpf, "/tmp/rfXXXXXX"); (void) mktemp(tmpf); fo = fopen(tmpf, "w"); if (fo == NULL) error("can't create `%s'", tmpf); fprintf(fo, "Newsgroups: %s\n", (hp->h_followupto) ? hp->h_followupto : hp->h_newsgroups); fprintf(fo, "Subject: %s\n", s); free(s); if (hp->h_references && hp->h_messageid) fprintf(fo, "References: %s %s\n", hp->h_references, hp->h_messageid); else if (hp->h_messageid) fprintf(fo, "References: %s\n", hp->h_messageid); (void) fclose(fo); s = newstr3(binfile("inject/postnews"), " -h ", tmpf); system(s); free(s); (void) unlink(tmpf); } /* * get correct "Subject: Re: .." line */ char * getsubject(hp) register header *hp; { register char *s; if (!hp->h_subject) { (void) printf("Subject: Re: "); (void) fflush(stdout); if ((s = mgets()) == NIL(char) || !*s) { (void) printf("The Subject field is mandatory.\n"); return NIL(char); } return newstr2("Re: ", s); } else if (CMPN(hp->h_subject, "Re: ", 4) != 0 && CMPN(hp->h_subject, "re: ", 4) != 0) return newstr2("Re: ", hp->h_subject); else return newstr(hp->h_subject); } /* * run a command, optionally closing stdin */ run(com, argv, closein) char *com; char *argv[]; bool closein; { int pid, status, r; switch (pid = fork()) { default: /* parent */ break; case 0: /* child */ if (closein) close(fileno(stdin)); execvp(com, argv); error("can't exec %s", com); exit(1); case -1: error("can't fork"); } if (signal(SIGINT, SIG_IGN) != SIG_IGN) (void) signal(SIGINT, SIG_IGN); if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) (void) signal(SIGQUIT, SIG_IGN); while ((r = wait(&status)) != pid && r != -1) ; if (signal(SIGINT, SIG_IGN) != SIG_IGN) (void) signal(SIGINT, onintr); if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) (void) signal(SIGQUIT, onintr); } /* * call postnews */ pnews(group) char *group; { register char *s = newstr3(binfile("inject/postnews"), " ", group); system(s); free(s); } /* * save an article */ save(hp, f, s) header *hp; FILE *f; char *s; { register long pos; register int c; register char *cp; register FILE *sf; register char *aname; long then; extern char *getenv(); if (!*s) { if ((aname = getenv("HOME")) == NIL(char)) { (void) printf("No $HOME in environment.\n"); return; } s = aname = newstr3(aname, "/", ARTICLES); } else aname = NIL(char); if ((sf = fopen(s, "a")) == NIL(FILE)) { (void) fprintf(stderr, "readnews: can't open %s\n", s); return; } if (aname) free(aname); pos = ftell(f); rewind(f); if (cp = strchr(hp->h_from, ' ')) *cp = '\0'; if (hp->h_date) then = atot(hp->h_date); else then = 0L; (void) fprintf(sf, "From %s %s", hp->h_from, ctime(then ? &then : &now)); if (cp) *cp = ' '; while ((c = getc(f)) != EOF) (void) putc(c, sf); (void) putc('\n', sf); (void) fclose(sf); fseek(f, pos, 0); } /* * put - like putchar, but filtering control characters */ int /* like putchar */ put(c) int c; { register int trimc = c & 0177; /* trim off top bit */ register int newc; if (iscntrl(trimc) && trimc != '\n' && trimc != '\b' && trimc != '\t') newc = '#'; else newc = c; return(putchar(newc)); } /* * print an article, if it's long enough call page() */ /* ARGSUSED */ print(hp, f) header *hp; FILE *f; { register int c; register long pos; pos = ftell(f); if (!pflag) page(f); else while ((c = getc(f)) != EOF) (void) put(c); (void) fseek(f, pos, 0); } /* * copy article text to stdout, and break into pages */ page(f) FILE *f; { register int c; register unsigned lineno; char lbuf[80]; lineno = 1; while (!interrupt) { for (; lineno < PAGESIZE - 4 && !interrupt; lineno++) { while ((c = getc(f)) != EOF && c != '\n') (void) put(c); if (c == EOF) goto fastexit; if (lineno < PAGESIZE - 5) (void) put('\n'); } if (interrupt) break; if (fflush(stdout) == EOF) break; if (read(fileno(stdin), lbuf, sizeof(lbuf)) <= 0) break; lineno = 0; } if (lineno) (void) put('\n'); interrupt = false; fastexit: ; } /* VARARGS1 */ error(s, a0, a1, a2, a3) char *s; { (void) fprintf(stderr, "readnews: "); (void) fprintf(stderr, s, a0, a1, a2, a3); (void) fprintf(stderr, "\n"); fflush(stdout); /* just on principle */ exit(1); } /* - getctl - pick up control file for subscriptions etc. */ getctl() { register FILE *f; register char *fname; char line[BUFLEN]; register char *p; fname = ctlfile("readnews.ctl"); f = fopen(fname, "r"); if (f == NULL) return; while (fgets(line, sizeof(line), f) != NULL) { line[strlen(line)-1] = '\0'; /* dispose of newline */ p = strchr(line, '\t'); if (p == NULL) p = strchr(line, ' '); if (line[0] != '#' && p != NULL) { while (*p == ' ' || *p == '\t') *p++ = '\0'; if (strcmp(line, "defsub") == 0) (void) strcpy(dfltsub, p); else if (strcmp(line, "mustsub") == 0) (void) strcpy(admsub, p); else if (strcmp(line, "mailvia") == 0) (void) strcpy(mailvia, p); } } (void) fclose(f); }