rsalz@uunet.uu.net (Rich Salz) (06/30/89)
Submitted-by: utzoo!henry Posting-number: Volume 19, Issue 95 Archive-name: cnews2/part18 : ---CUT HERE--- echo 'rna/header.c': sed 's/^X//' >'rna/header.c' <<'!' X/* X * extract/output headers X */ X X#include "defs.h" X X#if AUSAM Xextern struct pwent pe; X#else Xextern struct passwd *pp; X#endif Xextern char systemid[]; Xextern long now; X Xchar tzone[] = TIMEZONE; Xchar hform[] = "%s: %s\n"; X X/* Mandatory Headers */ Xchar t_relayversion[] = "Relay-Version"; Xchar t_postversion[] = "Posting-Version"; Xchar t_from[] = "From"; Xchar t_date[] = "Date"; Xchar t_newsgroups[] = "Newsgroups"; Xchar t_subject[] = "Subject"; Xchar t_messageid[] = "Message-ID"; Xchar t_path[] = "Path"; X X/* Optional Headers */ Xchar t_replyto[] = "Reply-To"; Xchar t_sender[] = "Sender"; Xchar t_followupto[] = "Followup-To"; Xchar t_datereceived[] = "Date-Received"; Xchar t_expires[] = "Expires"; Xchar t_references[] = "References"; Xchar t_control[] = "Control"; Xchar t_distribution[] = "Distribution"; Xchar t_organization[] = "Organization"; Xchar t_lines[] = "Lines"; X Xtypedef enum ft X{ X f_control, f_date, f_datereceived, f_distribution, X f_expires, f_followupto, f_from, f_lines, f_messageid, X f_newsgroups, f_organization, f_path, f_postversion, X f_references, f_relayversion, f_replyto, f_sender, X f_subject X} X X Xftype; X Xtypedef struct field { X char *f_name; X ftype f_type; X} field; X Xstatic field fields[] = X{ X { t_control, f_control }, X { t_date, f_date }, X { t_datereceived, f_datereceived }, X { t_distribution, f_distribution }, X { t_expires, f_expires }, X { t_followupto, f_followupto }, X { t_from, f_from }, X { t_lines, f_lines }, X { t_messageid, f_messageid }, X { t_newsgroups, f_newsgroups }, X { t_organization, f_organization }, X { t_path, f_path }, X { t_postversion, f_postversion }, X { t_references, f_references }, X { t_relayversion, f_relayversion }, X { t_replyto, f_replyto }, X { t_sender, f_sender }, X { t_subject, f_subject } X}; X X Xchar *weekdays[7] = X{ X "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" X}; X X Xchar *months[12] = X{ X "Jan", "Feb", "Mar", "Apr", "May", "Jun", X "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" X}; X X Xstatic Xfieldcmp(a, b) Xfield *a, *b; X{ X return CMP(a->f_name, b->f_name); X} X X X/* X * extract headers from file, X * position file to start of body X */ Xgethead(f, hp) XFILE *f; Xheader *hp; X{ X register char *colon, *space, *s; X register field *fp; X field af; X char buf[BUFLEN*2]; X X char *hfgets(); X X memset((char *) hp, 0, sizeof(header)); X while (hfgets(buf, sizeof(buf), f)) { X if (buf[0] == '\n') X return; X if (isupper(buf[0]) && (colon = strchr(buf, ':')) && (space = X strchr(buf, ' ')) && (colon + 1 == space)) { X *colon = '\0'; X af.f_name = buf; X fp = (field * ) bsearch((char *) & af, (char *) fields, X sizeof(fields) / sizeof(fields[0]), sizeof(fields[0]), X fieldcmp); X *colon = ':'; X } else X fp = NIL(field); X if (!fp) X if (hp->h_others) X hp->h_others = catstr(hp->h_others, buf); X else X hp->h_others = newstr(buf); X else X { X if (colon = strchr(space + 1, '\n')) X *colon = '\0'; X s = newstr(space + 1); X switch (fp->f_type) { X case f_control: X hp->h_control = s; X break; X case f_date: X hp->h_date = s; X break; X case f_datereceived: X hp->h_datereceived = s; X break; X case f_distribution: X hp->h_distribution = s; X break; X case f_expires: X hp->h_expires = s; X break; X case f_followupto: X hp->h_followupto = s; X break; X case f_from: X hp->h_from = s; X break; X case f_lines: X hp->h_lines = s; X break; X case f_messageid: X hp->h_messageid = s; X break; X case f_newsgroups: X hp->h_newsgroups = s; X break; X case f_organization: X hp->h_organisation = s; X break; X case f_path: X hp->h_path = s; X break; X case f_postversion: X hp->h_postversion = s; X break; X case f_references: X hp->h_references = s; X break; X case f_relayversion: X hp->h_relayversion = s; X break; X case f_replyto: X hp->h_replyto = s; X break; X case f_sender: X hp->h_sender = s; X break; X case f_subject: X hp->h_subject = s; X break; X } X } X } X} X X X/* X * put headers to file X */ Xputhead(hp, f, com) Xheader *hp; XFILE *f; Xpheadcom com; X{ X register char *s; X char *getunique(); X extern char *getenv(); X X if (hp->h_relayversion && com == printing) X (void) fprintf(f, hform, t_relayversion, hp->h_relayversion); X else if (com != printing) X (void) fprintf(f, "%s: version %s; site %s.%s\n", t_relayversion, NEWSVERSION, X systemid, MYDOMAIN); X X if (hp->h_postversion) X (void) fprintf(f, hform, t_postversion, hp->h_postversion); X else if (com == making) X (void) fprintf(f, "%s: version %s; site %s.%s\n", t_postversion, NEWSVERSION, X systemid, MYDOMAIN); X X X if (hp->h_from) X (void) fprintf(f, hform, t_from, hp->h_from); X else if(com == making) { X if(s = getenv("NAME")) X (void) fprintf(f, "%s: %s@%s.%s (%s)\n", t_from, X#if AUSAM X pe.pw_strings[LNAME], X#else X pp->pw_name, X#endif X systemid, MYDOMAIN, s); X else X (void) fprintf(f, X#if AUSAM X "%s: %s@%s.%s (%s %s)\n", X#else X "%s: %s@%s.%s\n", X#endif X t_from, X#if AUSAM X pe.pw_strings[LNAME], X#else X pp->pw_name, X#endif X systemid, MYDOMAIN X#if AUSAM X , X pe.pw_strings[FIRSTNAME], X pe.pw_strings[LASTNAME] X#endif X ); X } X X if (hp->h_date) X (void) fprintf(f, hform, t_date, hp->h_date); X else if (com == making) X (void) fprintf(f, hform, t_date, ttoa(now)); X X if (hp->h_newsgroups) X (void) fprintf(f, hform, t_newsgroups, hp->h_newsgroups); X else if (com == making) X (void) fprintf(f, hform, t_newsgroups, DFLTGRP); X X if (hp->h_subject) X (void) fprintf(f, hform, t_subject, hp->h_subject); X else if (com == making) X error("No subject field."); X X if (hp->h_messageid) X (void) fprintf(f, hform, t_messageid, hp->h_messageid); X else if (com == making) X error("No messageid."); X X if (hp->h_path && com == passing) X (void) fprintf(f, "%s: %s!%s\n", t_path, systemid, hp->h_path); X else if (hp->h_path) X (void) fprintf(f, hform, t_path, hp->h_path); X else if(com == making) X (void) fprintf(f, "%s: %s!%s\n", t_path, systemid, X#if AUSAM X pe.pw_strings[LNAME] X#else X pp->pw_name X#endif X ); X X /* optional */ X X if (hp->h_replyto) X (void) fprintf(f, hform, t_replyto, hp->h_replyto); X X if (hp->h_sender) X (void) fprintf(f, hform, t_sender, hp->h_sender); X X if (hp->h_followupto) X (void) fprintf(f, hform, t_followupto, hp->h_followupto); X X if (hp->h_datereceived && com == printing) X (void) fprintf(f, hform, t_datereceived, hp->h_datereceived); X else if (com != printing) X (void) fprintf(f, hform, t_datereceived, ttoa(now)); X X if (hp->h_expires) X (void) fprintf(f, hform, t_expires, hp->h_expires); X X if (hp->h_references) X (void) fprintf(f, hform, t_references, hp->h_references); X X if (hp->h_control) X (void) fprintf(f, hform, t_control, hp->h_control); X X if (hp->h_distribution) X (void) fprintf(f, hform, t_distribution, hp->h_distribution); X X if (hp->h_organisation) X (void) fprintf(f, hform, t_organization, hp->h_organisation); X else if (com == making) X (void) fprintf(f, hform, t_organization, (s = getenv("ORGANIZATION")) ? X s : MYORG); X X if (hp->h_lines) X (void) fprintf(f, hform, t_lines, hp->h_lines); X X if (hp->h_others) X fputs(hp->h_others, f); X} X X X/* X * free all strings allocated to header X */ Xfreehead(hp) Xregister header *hp; X{ X if (hp->h_relayversion) X free(hp->h_relayversion); X if (hp->h_postversion) X free(hp->h_postversion); X if (hp->h_from) X free(hp->h_from); X if (hp->h_date) X free(hp->h_date); X if (hp->h_newsgroups) X free(hp->h_newsgroups); X if (hp->h_subject) X free(hp->h_subject); X if (hp->h_messageid) X free(hp->h_messageid); X if (hp->h_path) X free(hp->h_path); X if (hp->h_replyto) X free(hp->h_replyto); X if (hp->h_sender) X free(hp->h_sender); X if (hp->h_followupto) X free(hp->h_followupto); X if (hp->h_datereceived) X free(hp->h_datereceived); X if (hp->h_expires) X free(hp->h_expires); X if (hp->h_references) X free(hp->h_references); X if (hp->h_control) X free(hp->h_control); X if (hp->h_distribution) X free(hp->h_distribution); X if (hp->h_organisation) X free(hp->h_organisation); X if (hp->h_lines) X free(hp->h_lines); X if (hp->h_others) X free(hp->h_others); X} X X X/* X * hfgets is like fgets, but deals with continuation lines. X * It also ensures that even if a line that is too long is X * received, the remainder of the line is thrown away X * instead of treated like a second line. X */ Xchar * Xhfgets(buf, len, fp) Xchar *buf; Xint len; XFILE *fp; X{ X register int c; X register char *cp, *tp; X X if ((cp = fgets(buf, len, fp)) == NIL(char)) X return NIL(char); X X if (*cp == '\n') X return cp; X X tp = cp + strlen(cp); X if (tp[-1] != '\n') { X /* Line too long - part read didn't fit into a newline */ X while ((c = getc(fp)) != '\n' && c != EOF) X ; X } else X *--tp = '\0'; /* clobber newline */ X X while ((c = getc(fp)) == ' ' || c == '\t') { X /* Continuation line. */ X while ((c = getc(fp)) == ' ' || c == '\t') X ; X if (tp - cp < len) { X *tp++ = ' '; X *tp++ = c; X } X while ((c = getc(fp)) != '\n' && c != EOF) X if (tp - cp < len) X *tp++ = c; X } X *tp++ = '\n'; X *tp++ = '\0'; X if (c != EOF) X ungetc(c, fp); /* push back first char of next header */ X return cp; X} X X X/* X * time to ascii X * leave time in static var X */ Xchar * Xttoa(t) Xlong t; X{ X static char buf[40]; X struct tm *tp; X extern struct tm *localtime(); X X tp = localtime(&t); X sprintf(buf, "%s, %d %s %d %02d:%02d:%02d %s", weekdays[tp->tm_wday], X tp->tm_mday, months[tp->tm_mon], tp->tm_year, tp->tm_hour, tp->tm_min, X tp->tm_sec, tzone); X return buf; X X} X X X/* X * ascii to time X * return 0L on error X */ Xlong Xatot(s) Xchar *s; X{ X char *argv[4]; X int day, year, hour, min, sec; X char month[10], sday[10], stime[10], syear[10]; X extern long maketime(); X X if (sscanf(s, "%*s %d %*[ -] %9[^ -] %*[ -] %d %2d:%2d:%2d", &day, month, X &year, &hour, &min, &sec) != 6) X return 0L; X sprintf(sday, "%d", day); X sprintf(stime, "%d:%d:%d", hour, min, sec); X sprintf(syear, "%d", 1900 + year); X argv[0] = sday; X argv[1] = month; X argv[2] = stime; X argv[3] = syear; X return maketime(4, argv, STIMES); X} X X ! echo 'rna/history.c': sed 's/^X//' >'rna/history.c' <<'!' X/* X * History file X * each line contains a message-id, install or expire time X * names of linked files X */ X X#include "defs.h" X Xstatic char histname[] = HISTORY; Xstatic char *histid; /* messageid to save */ Xstatic char *histline; /* list of linked files */ Xstatic long etime; /* expire time */ X Xtypedef enum stypes { X chk, delete } stype; X X/* X * do things with history file X * chk - see if id present X * delete - delete article with id X */ Xstatic bool Xsearchhist(id, type) Xchar *id; Xstype type; X{ X register FILE *f; X register char *s, *name; X register bool found; X char buf[BUFSIZ * 2]; X X extern char *newsdir; X X f = fopenl(histname); X X found = false; X while (fgets(buf, sizeof(buf), f)) { X if (s = strchr(buf, ' ')) X *s = '\0'; X else X error("Bad format: %s", histname); X if (CMP(buf, id) == 0) { X found = true; X break; X } X } X if (found && type == delete) { X if ((name = strchr(s + 1, ' ')) == NIL(char)) X error("Bad format: %s", histname); X name++; X while (name && (s = strpbrk(name, " \n"))) { X *s = '\0'; X name = newstr3(newsdir, "/", name); X remove(name); X free(name); X name = s + 1; X } X } X fclose(f); X#if !AUSAM X unlock(histname); X#endif X return found; X} X X X/* X * delete files given id X */ Xbool Xcancel(id) Xchar *id; X{ X bool searchhist(); X X return searchhist(id, delete); X} X X X/* X * check if article has been recieved X */ Xbool Xchkhist(id) Xchar *id; X{ X bool searchhist(); X X return searchhist(id, chk); X} X X X/* X * scan history, clearing uflag list entry if id not seen X */ Xscanhist(ulist, usize) Xchar **ulist; Xint usize; X{ X register FILE *f; X register char *s, **found; X register int i; X char *key[1]; X char buf[BUFSIZ * 2]; X bool * seen; X X extern char *newsdir; X X seen = (bool * ) myalloc((int) sizeof(bool) * usize); X memset((char *)seen, 0, (int) sizeof(bool) * usize); X X f = fopenf(histname, "r"); X while (fgets(buf, sizeof(buf), f)) { X if (s = strchr(buf, ' ')) X *s = '\0'; X else X error("Bad format: %s", histname); X key[0] = buf; X found = (char **) bsearch((char *) key, (char *) ulist, (unsigned) usize, X sizeof(char *), strpcmp); X if (found) X seen[found - ulist] = true; X } X fclose(f); X X for (i = 0; i < usize; i++) X if (!seen[i]) { X free(ulist[i]); X ulist[i] = NIL(char); X } X free((char *)seen); X} X X X/* X * open hist file, write id and time X */ Xopenhist(hp) Xheader *hp; X{ X X histid = newstr(hp->h_messageid); X if (hp->h_expires) X etime = atot(hp->h_expires); X else X etime = 0L; X histline = NIL(char); X} X X X/* X * write name of file article resides into history file X */ Xwritehist(fname) Xchar *fname; X{ X histline = (histline ? catstr2(histline, " ", fname) : newstr(fname)); X} X X X/* X * close history file X */ Xclosehist() X{ X register FILE *f; X extern long now; X X f = fopenl(histname); X fseek(f, 0L, 2); X (void) fprintf(f, "%s %s%ld %s\n", histid, etime ? "E" : "", etime ? etime : X now, histline); X fclose(f); X#if !AUSAM X unlock(histname); X#endif X free(histid); X free(histline); X} X X X/* X * remove a news item X * check owner first X */ Xstatic Xremove(fname) Xchar *fname; X{ X header h; X FILE * f; X register char *s, *mname; X X#if AUSAM X extern struct pwent pe; X#else X extern struct passwd *pp; X#endif X extern char systemid[]; X extern bool su; X extern bool pflag; X X if (!su && !pflag) { X f = fopenf(fname, "r"); X gethead(f, &h); X fclose(f); X if (s = strchr(h.h_from, ' ')) X *s = '\0'; X mname = newstr5( X#if AUSAM X pe.pw_strings[LNAME], X#else X pp->pw_name, X#endif X "@", systemid, ".", MYDOMAIN); X if (CMP(mname, h.h_from) != 0) X error("Can't cancel articles you didn't write."); X free(mname); X } X if (unlink(fname) != 0) X error("Couldn't unlink %s", fname); X X} ! echo 'rna/lib/bsearch.c': sed 's/^X//' >'rna/lib/bsearch.c' <<'!' X/*LINTLIBRARY*/ X/* X * Binary search algorithm, generalized from Knuth (6.2.1) Algorithm B. X * X * Written by J. S. Rugaber; rewritten by L. Rosler, Dept. 45175, August, 1981. X */ X Xtypedef char *POINTER; X XPOINTER Xbsearch(key, base, nel, width, compar) XPOINTER key; /* Key to be located */ XPOINTER base; /* Beginning of table */ Xunsigned nel; /* Number of elements in the table */ Xunsigned width; /* Width of an element (bytes) */ Xint (*compar)(); /* Comparison function */ X{ X int two_width = width + width; X POINTER last = base + width * (nel - 1); /* Last element in table */ X X while (last >= base) { X X register POINTER p = base + width * ((last - base)/two_width); X register int res = (*compar)(key, p); X X if (res == 0) X return (p); /* Key found */ X if (res < 0) X last = p - width; X else X base = p + width; X } X return ((POINTER) 0); /* Key not found */ X} ! echo 'rna/lib/itoa.c': sed 's/^X//' >'rna/lib/itoa.c' <<'!' Xchar * Xitoa(i) Xint i; X{ X static char buf[30]; X X sprintf(buf, "%d", i); X return buf; X} ! echo 'rna/lib/makefile': sed 's/^X//' >'rna/lib/makefile' <<'!' X# makefile for Australian readnews library XRANLIB=ranlib X# workaround for System V make bug XSHELL = /bin/sh X Xlib.a: bsearch.o itoa.o memset.o strpbrk.o X ar rv $@ bsearch.o itoa.o memset.o strpbrk.o X $(RANLIB) $@ Xclean: X rm -f *.o *.a ! echo 'rna/lib/memset.c': sed 's/^X//' >'rna/lib/memset.c' <<'!' X/*LINTLIBRARY*/ X/* X * Set an array of n chars starting at sp to the character c. X * Return sp. X */ Xchar * Xmemset(sp, c, n) Xregister char *sp, c; Xregister int n; X{ X register char *sp0 = sp; X X while (--n >= 0) X *sp++ = c; X return (sp0); X} ! echo 'rna/lib/strpbrk.c': sed 's/^X//' >'rna/lib/strpbrk.c' <<'!' X/* X * Return ptr to first occurance of any character from `brkset' X * in the character string `string'; NULL if none exists. X */ X X#define NULL (char *) 0 X Xchar * Xstrpbrk(string, brkset) Xregister char *string, *brkset; X{ X register char *p; X X do { X for(p=brkset; *p != '\0' && *p != *string; ++p) X ; X if(*p != '\0') X return(string); X } X while(*string++); X return(NULL); X} ! echo 'rna/COVER.pd': sed 's/^X//' >'rna/COVER.pd' <<'!' XFrom decvax!mulga!michaelr:elecvax Mon Jul 16 04:25:19 1984 XFrom: decvax!mulga!michaelr:elecvax XReceived: by decvax.UUCP (4.12/1.0) X id AA25742; Mon, 16 Jul 84 03:56:35 edt XReceived: by mulga.OZ (4.3) X id AA06943; Mon, 16 Jul 84 14:19:46 EST XTo: decvax!utcsstat!geoff:mulga XSubject: Re: your news re-write X XYes, my news re-write is in the public domain. XI couldn't sell it if I wanted to, since it was developed under an Xeducational license. X XPlease distribute it freely as you wish. X XMichael Rourke XUniversity of New South Wales, Kensington, N.S.W. 2033 AUSTRALIA XPhone: +61 2 662 2781 Netaddr: {decvax,vax135,sfjec}!mulga!michaelr:elecvax X X ! echo 'rna/makefile': sed 's/^X//' >'rna/makefile' <<'!' X# australian readnews makefile X# =()<NEWSARTS = @<NEWSARTS>@>()= XNEWSARTS = /usr/spool/news X# =()<NEWSBIN = @<NEWSBIN>@>()= XNEWSBIN = /usr/lib/newsbin X# =()<NEWSCTL = @<NEWSCTL>@>()= XNEWSCTL = /usr/lib/news X# workaround for System V make bug XSHELL = /bin/sh X XDEFINES= XCOPTS=-O XCFLAGS=$(COPTS) $(DEFINES) XINSTALL=:# install bigpdp XNFLAG = XLINT = lint -ha $(DEFINES) XI=/usr/include XC=/lib/libc.a XLIBS=lib/lib.a ../libcnews.a XBIN=/bin XBINDIR=$(BIN) X XCOMMANDS = postnews uurec readnews uusend expire postgroup XFILES = help XLCOMMANDS = postnews.lint uurec.lint readnews.lint uusend.lint expire.lint XPFILES = header.c postnews.c funcs.c active.c history.c maketime.c mtempnam.c XRFILES = header.c readnews.c funcs.c active.c newsrc.c history.c maketime.c XROFILES= header.o readnews.o funcs.o active.o newsrc.o history.o maketime.o XEFILES = expire.c funcs.c active.c X X.c.lint: X $(LINT) $< > $@ X X.c: X $(CC) $(CFLAGS) $*.c $(NFLAG) -o $(@F) X $(INSTALL) $* bin 711 $@ X X.sh: X $(INSTALL) -c $*.sh bin 755 $@ X Xall: readnews checknews X : warning, defs.h may not be right for your system X Xreadnews: $(ROFILES) defs.h $(LIBS) X $(CC) $(CFLAGS) $(ROFILES) $(LIBS) -o $@ X Xnewsinstall: X : "install all control files; don't overwrite any!" X -if test ! -r $(NEWSCTL)/news.help; then cp news.help $(NEWSCTL); fi X X# bininstall: make directories, install programs Xbininstall: install Xinstall: $(BINDIR)/readnews $(BINDIR)/checknews X$(BINDIR)/readnews: readnews X cp readnews $(BINDIR) X : "$(INSTALL) - readnews bin 711 $(BINDIR)/readnews $(BINDIR)/news" X$(BINDIR)/checknews: checknews X chmod +x checknews X cp checknews $(BINDIR) X Xdefs.h: at.h # $C X touch defs.h X Xlint: $(LCOMMANDS) X Xhelp: $(NEWSCTL)/help X$(NEWSCTL)/help: news.help X $(INSTALL) -c news.help news 644 $(NEWSCTL)/help X Xlib/lib.a: X : if you do not have ranlib, you will need to edit lib/makefile X (cd lib; make) X X$(ROFILES): defs.h X Xpostnews: $(BINDIR)/postnews X$(BINDIR)/postnews: $(PFILES) defs.h X $(CC) $(CFLAGS) $(PFILES) -o postnews X $(INSTALL) - postnews news 6711 $(BINDIR)/postnews X Xpostgroup: $(BINDIR)/postgroup X$(BINDIR)/postgroup: postgroup.sh X Xuurec: $(NEWSCTL)/uurec X$(NEWSCTL)/uurec: uurec.c defs.h X Xuusend: $(NEWSCTL)/uusend X$(NEWSCTL)/uusend: defs.h uusend.c X Xexpire: $(NEWSCTL)/expire X$(NEWSCTL)/expire: $(EFILES) defs.h X $(CC) $(CFLAGS) $(EFILES) $(NFLAG) -o expire X $(INSTALL) expire news 700 $(NEWSCTL)/expire X Xpostnews.lint: $(PFILES) X $(LINT) $(PFILES) > postnews.lint X Xreadnews.lint: $(RFILES) X $(LINT) $(RFILES) > readnews.lint X Xexpire.lint: $(EFILES) X $(LINT) $(EFILES) > expire.lint X Xuurec.lint: uurec.c defs.h Xuusend.lint: uusend.c defs.h X X.FINISH: X rm -s *.o Xclean: X rm -f *.o core a.out readnews X (cd lib ; make clean) ! echo 'rna/maketime.c': sed 's/^X//' >'rna/maketime.c' <<'!' X/* X * long X * maketime(argc, argv, type) X * X * A standard routine to convert a future time (in English) to seconds. X * Arguments are order-independent (except for suffixes), and words X * may be shortened to a non-ambiguous abbreviation. X * As the time must be in the future, unspecified years, months and days default X * to the "next" year, month or day if necessary; otherwise the current X * month, day and hour are used. X * X * type is either TIMES in which days, times are recognised, or just DAYS. X * X * Tries hard to give meaningful messages, and make sure the user X * gets the time she/he wanted! X * X * Return is in seconds or 0 if error. X * Error messages to stderr. X * X * Michael Rourke (UNSW) Christmas 1982 X * X * Syntax: X * X * timespec ::= { time | day | month | year } . X * X * time ::= [ hour [ ":" min [ ":" second ] ] ] [ timemodifier ] . X * X * timemodifier ::= "am" | "pm" | "noon" | "midday" | "midnight" | "now" . X * X * day ::= ( dayofweek [ "week" ] ) | number . X * X * dayofweek ::= "sunday" | "monday" | "tuesday" | "wednesday" | X * "thursday" | "friday" | "saturday" | "tomorrow" | X * "today" . X * X * month ::= "january" | "february" | "march" | "april" | "may" | "june" | X * "july" | "august" | "september" | "october" | "november" | X * "december" . X * X * year ::= "19" number . X * X */ X X#include "defs.h" X X#define NOW -1 X Xstatic timemod(), noonmid(), daymod(), weekday(), smonth(); X Xstatic struct slist { X char *s_name; X int (*s_action)(); X char s_val; X char s_type; X} slist[] = X{ X { "am", timemod, 0, TIMES, }, X { "pm", timemod, 12, TIMES, }, X { "noon", noonmid, 12, TIMES, }, X { "midday", noonmid, 12, TIMES, }, X { "midnight", noonmid, 0, TIMES, }, X { "now", noonmid, NOW, TIMES, }, X { "week", daymod, 0, DAYS, }, X { "sunday", weekday, 0, DAYS, }, X { "monday", weekday, 1, DAYS, }, X { "tuesday", weekday, 2, DAYS, }, X { "wednesday", weekday, 3, DAYS, }, X { "thursday", weekday, 4, DAYS, }, X { "friday", weekday, 5, DAYS, }, X { "saturday", weekday, 6, DAYS, }, X { "tomorrow", weekday, 7, DAYS, }, X { "today", weekday, 8, DAYS, }, X { "january", smonth, 0, DAYS, }, X { "february", smonth, 1, DAYS, }, X { "march", smonth, 2, DAYS, }, X { "april", smonth, 3, DAYS, }, X { "may", smonth, 4, DAYS, }, X { "june", smonth, 5, DAYS, }, X { "july", smonth, 6, DAYS, }, X { "august", smonth, 7, DAYS, }, X { "september", smonth, 8, DAYS, }, X { "october", smonth, 9, DAYS, }, X { "november", smonth, 10, DAYS, }, X { "december", smonth, 11, DAYS, }, X { "", 0, 0, 0, } X}; X X Xstatic char daysinmonth[12] = X{ X 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 X}; X X Xstatic int hour, minute, second, day, year, dayofweek, month; Xstatic int settime, setday, setyear, setdayofweek, setmonth; Xstatic int setweek, err, setmod; Xstatic char *curarg; Xstatic struct tm *tim; Xstatic int gtype; /* global `type' arg */ Xstatic short silent; X Xlong Xmaketime(argc, argv, type) Xint argc; Xchar **argv; Xint type; X{ X struct tm *localtime(); X long time(), construct(), now, then; X X if (type == STIMES) X type = TIMES, silent = 1; X else X silent = 0; X gtype = type; X now = time((long *) 0); X tim = localtime(&now); X X /* X * set defaults X */ X hour = tim->tm_hour; X minute = tim->tm_min; X second = tim->tm_sec; X day = tim->tm_mday; X year = tim->tm_year + 1900; X dayofweek = tim->tm_wday; X month = tim->tm_mon; X X settime = setday = setyear = setdayofweek = setmonth = 0; X setweek = err = setmod = 0; X X while (argc--) X timearg(curarg = *argv++); X if (err) X return (long) 0; X X checktime(); X if (err) X return (long) 0; X X then = construct(); X /* X if(now > then) X { X error("Time specified has passed."); X return (long) 0; X } X*/ X return then; X} X X Xstatic Xtimearg(s) Xchar *s; X{ X lower(s); X if (isdigit(*s)) X numbers(s); X else X words(s); X} X X Xstatic Xlower(s) Xregister char *s; X{ X while (*s) { X *s = tolower(*s); X s++; X } X} X X Xstatic Xnumbers(s) Xregister char *s; X{ X register int val; X X val = 0; X while (isdigit(*s)) X val = val * 10 + *s++ - '0'; X if (val > 1900) X if (setyear++) X reperror("year"); X else X year = val; X else if (*s == '\0') X if (setday++) X reperror("day"); X else X day = val; X else if (settime++) X reperror("time"); X else X { X hour = val; X if (*s == ':') { X s++; X val = 0; X while (isdigit(*s)) X val = val * 10 + *s++ - '0'; X minute = val; X if (*s == ':') { X s++; X val = 0; X while (isdigit(*s)) X val = val * 10 + *s++ - '0'; X second = val; X } else X second = 0; X } else X minute = second = 0; X } X if (*s) X words(curarg = s); X} X X Xstatic Xreperror(s) Xchar *s; X{ X error("Repeated %s argument: \"%s\"", s, curarg); X} X X X/* VARARGS1 */ Xstatic Xerror(s, a1, a2, a3, a4) Xchar *s; Xint a1, a2, a3, a4; X{ X err++; X if (silent) X return; X (void) fprintf(stderr, "Error in time specification: "); X (void) fprintf(stderr, s, a1, a2, a3, a4); X (void) fprintf(stderr, "\n"); X} X X Xstatic Xwords(s) Xchar *s; X{ X register struct slist *sp, *found; X register int size; X register char *wstart; X X sp = slist; X wstart = s; X size = 0; X while (*s && !isdigit(*s)) X size++, s++; X found = (struct slist *) 0; X while (*(sp->s_name)) { X if (sp->s_type <= gtype && CMPN(sp->s_name, wstart, size) == X 0) X if (!found) { X found = sp; X if (strlen(sp->s_name) == size) X break; /* otherwise an abbreviation */ X } X else X { X error("Ambiguous abbreviation: \"%.*s\"", size, X wstart); X return; X } X sp++; X } X if (found) X (*(found->s_action))(found->s_val); X else X error("Unknown word: \"%.*s\"", size, wstart); X if (*s) X numbers(curarg = s); X} X X Xstatic Xtimemod(val) Xint val; X{ X if (!settime) X error("Can only use \"am\" or \"pm\" after a time."); X else if (setmod++) X reperror("time modifier"); X else if (hour < 12) X hour += val; X else if (hour > 12) X error("Can't use \"am\" or \"pm\" with 24 hour clock."); X else if (val == 0) /* am && hour == 12 */ X hour = 0; /* 12am correction */ X} X X Xstatic Xnoonmid(val) Xint val; X{ X if (val < 0) /* NOW */ { X if (settime++) X reperror("time"); X /* let defaults work */ X } else if (setmod++) /* noon, midnight */ X reperror("time modifier"); X else X { X if (!settime) X settime++; X else if (hour != 12 || minute != 0 || second != 0) X error("Illegal time: %02d:%02d:%02d %s", hour, minute, X second, curarg); X hour = val; X minute = second = 0; X } X} X X Xstatic Xdaymod() X{ X if (setweek++) X reperror("\b"); X else if (!setdayofweek) X error("Can only use \"week\" after a weekday name."); X else X dayofweek += 7; X} X X Xstatic Xweekday(val) Xint val; X{ X if (setday++) X reperror("day"); X else X { X setdayofweek++; X if (val < 7) { X dayofweek = val - dayofweek; /* now a displacement */ X if (dayofweek <= 0) X dayofweek += 7; X } else if (val == 7) /* tomorrow */ X dayofweek = 1; X else /* today */ X dayofweek = 0; X } X} X X Xstatic Xsmonth(val) Xint val; X{ X if (setmonth++) X reperror("day of month"); X else X month = val; X} X X Xstatic Xchecktime() X{ X register int dim; X X if (gtype == DAYS && settime) X error("Times are not accepted."); X if (year < 1983 || year > 2038) X error("Year out of range."); X if (hour > 23 || minute > 59 || second > 59) X error("Illegal time: %02d:%02d:%02d", hour, minute, second); X if (!setdayofweek) { X dim = daysinmonth[month] + (month == 1 ? leapyear(year) : 0); X if (day > dim) X error("Month day out of range. (> %d)", dim); X } X if (setdayofweek && (setmonth || setyear)) X error("Can't specify a weekday as well as a month or year."); X} X X Xstatic Xleapyear(y) Xint y; X{ X return ((y % 4) == 0 && (y % 100) != 0) || (y % 400 == 0); X} X X Xstatic long Xconstruct() X{ X register int i, days; X X adjust(); X days = DAYSTO1983; X for (i = 1983; i < year; i++) X days += 365 + leapyear(i); X for (i = 0; i < month; i++) X days += daysinmonth[i] + (i == 1 ? leapyear(year) : 0); X days += day - 1; /* days since 1 Jan 1970 */ X if (setdayofweek) X days += dayofweek; X return days * SECINDAY + hour * SECINHOUR + minute * SECINMIN + second; X} X X Xstatic Xadjust() X{ X register int dim; X X /* X * make sure time defaults to the future X */ X if (setdayofweek || setyear || month > tim->tm_mon) X return; X if (month < tim->tm_mon) { X year++; X return; X } X /* X * month == tim->tm_mon X */ X if (day > tim->tm_mday) X return; X if (day < tim->tm_mday) { X if (setmonth || ++month / 12) X year++, month %= 12; X return; X } X /* X * month == tim->tm_mon && day == tim->tm_mday X */ X if ((long)(hour*SECINHOUR + minute*SECINMIN + second) < X (long)(tim->tm_hour*SECINHOUR + tim->tm_min*SECINMIN + tim->tm_sec)) { X dim = daysinmonth[month] + (month == 1? leapyear(month): 0); X if (setday || ++day / dim) { X if (setmonth || ++month / 12) X year++, month %= 12; X day %= dim; X } X return; X } X} X X ! echo 'rna/news.help': sed 's/^X//' >'rna/news.help' <<'!' XCommands to read news: X XRETURN either print current article or go to next article and print header X X. print current article + or n go to next article X- go to previous article s [file] save current article Xh print current header H print current header in full Xu unsubscribe from followups U unsubscribe from current group Xq or EOT quit x exit (as if no articles seen) X<number> go to article <number> !cmd shell escape XN [newsgrp] go to next newsgroup DEL break to command level X XCommands to post/reply/cancel news: X Xc cancel article r reply to sender (by mail) Xf post a followup article p post new article Xm person mail to person X XCommands when posting/replying (p, r, f and m commands): X X. or EOT terminate article .e edit article X.!cmd shell escape .i interpolate current item XDEL abort posting/mailing ! echo 'rna/newsrc.c': sed 's/^X//' >'rna/newsrc.c' <<'!' X/* X * newsrc file handling X */ X X#include "defs.h" X Xstatic char nrcname[] = NEWSRC; X Xstatic char *rcname; /* full pathname of .newsrc */ Xnewsrc *rc; /* internal .newsrc */ Xchar *rcgrps; /* subscription from .newsrc */ Xstatic newsrc *lastrc; /* last newsrc struct in list */ Xstatic int rclineno; /* current lineno in .newsrc */ Xstatic bool sortrc; /* if we should sort on output */ X Xstatic newsrc *findnewsrc(); X Xreadnewsrc() X{ X register FILE *f; X static char option[] = "options"; X char word[BUFSIZ], rest[BUFSIZ]; X extern char *getenv(); X X if ((rcname = getenv("HOME")) == NULL) X error("No $HOME in environment."); X rcname = newstr3(rcname, "/", nrcname); X if ((f = fopen(rcname, "r")) == NULL) X return; X X rclineno = 0; X while (getline(f, word, rest)) X if (CMP(word, option) == 0) X dooptions(rest); X else X dorcline(word, rest); X (void) fclose(f); X} X X/* X * Read a line from f, put first word into w and the rest into r. X * Discard trailing newline instead of storing it. X * This is a poor design, as w & r are unchecked for overrun. X */ Xstatic Xgetline(f, w, r) Xregister FILE *f; Xchar *w, *r; X{ X register int c; X register char *s; X X rclineno++; X s = w; X while ((c = getc(f)) != EOF && c != ' ' && c != '\t') X *s++ = c; /* stash first word */ X *s = '\0'; X X if (c != EOF) { X s = r; X while ((c = getc(f)) != EOF && c != '\n') X *s++ = c; /* stash the rest */ X *s = '\0'; X } X X if (c != '\n' && c != EOF) X error("Bad format: %s line %d: %s", rcname, rclineno, w); X X return c != EOF; X} X X/* X * Parse s into words and simulate command line arguments with them. X */ Xstatic Xdooptions(s) Xchar *s; X{ X register char *cp; X register int argc; X register char **argv; X X cp = s; X while (isspace(*cp)) X cp++; X if (!*cp) X return; X X argc = 1; X argv = (char **) myalloc(sizeof(char *)); X argv[argc - 1] = cp; X while (*cp && (cp = strpbrk(cp, " \t")) != NULL) { X while (*cp == ' ' || *cp == '\t') X *cp++ = '\0'; X if (*cp) { X argc++; X argv = (char **) myrealloc((char *) argv, X argc * (int)sizeof(char *)); X argv[argc - 1] = cp; X } X } X if (options(argc, argv, false)) X error("Bad options: %s line %d: %s", rcname, rclineno, s); X free((char *) argv); X} X X/* X * Parse w & r together as a .newsrc newsgroup line. X */ Xstatic Xdorcline(w, r) Xchar *w, *r; X{ X register char lastw; X register int len; X register newsrc *np; X X len = strlen(w); X lastw = w[len - 1]; /* save presumed colon or bang */ X w[len - 1] = '\0'; /* nuke presumed colon */ X while (*r == ' ' || *r == '\t') X r++; /* skip extra whitespace */ X X /* kludges, hacks, etc. for compatibility with other readers */ X if (strncmp(r, "1-", sizeof "1-"-1) == 0) X r += sizeof "1-"-1; /* skip usual `1-' */ X if (*r == '\0') /* rn's: `news.trash: ' */ X r = "0"; /* fake a zero */ X X if (lastw != ':' && lastw != NEGCHAR || !isdigit(*r)) X error("Bad line: %s line %d: %s", rcname, rclineno, w); X X np = NEW(newsrc); X np->n_subscribe = (bool) (lastw == ':'); /* colon or bang? */ X np->n_next = NIL(newsrc); X np->n_last = atoi(r); /* stash first number only */ X np->n_name = newstr(w); /* stash n.g. name */ X X if (rc == 0) X rc = np; X else X lastrc->n_next = np; X lastrc = np; X} X X/* X * for every group in active list, which belongs to the specified subscription X * list, and has messages to be read, call func X * if no mention in newsrc file, make new entry X */ Xapply(alist, group, func, dolast) Xactive *alist; Xchar *group; Xapplycom (*func)(); Xbool dolast; X{ X register active *ap; X register newsrc *np; X register applycom act; X register bool donesome; X X donesome = false; X do { X act = stop; X for (ap = alist; ap; ap = ap->a_next) { X if (ap->a_seq == 0 || ap->a_low > ap->a_seq) X continue; /* empty group */ X if (!ngmatch(ap->a_name, group)) X continue; X if ((np = findnewsrc(ap->a_name)) == NIL(newsrc)) { X np = NEW(newsrc); X np->n_name = newstr(ap->a_name); X np->n_next = NIL(newsrc); X np->n_last = 0; X np->n_subscribe = true; X if (!rc) X rc = np; X else X lastrc->n_next = np; X lastrc = np; X } X if (!np->n_subscribe) X continue; X /* X * if we haven't read any news for a while (or at all), X * or somehow seq got smaller (active corrupted?), X * set last read to oldest available article X */ X if (ap->a_low - 1 > np->n_last || ap->a_seq < np->n_last) X np->n_last = ap->a_low - 1; X while (np->n_last < ap->a_seq) { X donesome = true; X switch (act = (*func)(ap, np, false, false)) { X case stop: X return; X case next: X continue; X case nextgroup: X break; X case searchgroup: X break; X } X break; X } /* while */ X if (act == searchgroup) X break; X } /* for */ X if (act != searchgroup && dolast && donesome) X act = (*func)(NIL(active), NIL(newsrc), true, false); X } while (act == searchgroup); X} X X/* X * find if a newrc entry exists, X * taking advantange of the fact that requests should be X * in the same order X * X * detect when the newsrc gets out of order X * so it can be sorted at the end of the session X */ Xstatic newsrc * Xfindnewsrc(name) Xregister char *name; X{ X register newsrc *np, *start; X register bool found; X static newsrc *nextp; X X if (!rc) X return NIL(newsrc); X X found = false; X np = nextp ? nextp : rc; X nextp = start = np; X do { X if (CMP(np->n_name, name) == 0) { X found = true; X break; X } X np = np->n_next; X if (!np) X np = rc; X } while (np != nextp); X X if (!found) X return NIL(newsrc); X nextp = np->n_next; X if (np != start) X sortrc = true; X return np; X} X X/* X * rewrite the newsrc file X */ Xwritenewsrc(alist) Xactive *alist; X{ X register FILE *f; X register active *ap; X register newsrc *np; X register int i; X extern char **uflag; X extern int usize; X X if (!rc && !uflag && (!rcgrps || !*rcgrps)) X return; X X signal(SIGINT, SIG_IGN); X signal(SIGQUIT, SIG_IGN); X X f = fopenf(rcname, "w"); X if (rcgrps && *rcgrps) X (void) fprintf(f, "options -n %s\n", rcgrps); X if (uflag) { X scanhist(uflag, usize); /* forget id's not in history */ X for (i = 0; i < usize; i++) /* print whats left */ X if (uflag[i]) X (void) fprintf(f, "options -u %s\n", uflag[i]); X } X if (sortrc) { X /* X * sort newsrc so next time we use it, X * history/newsrc comparisons will be faster X */ X for (ap = alist; ap; ap = ap->a_next) X if (np = findnewsrc(ap->a_name)) X writengline(f, np); X } else X for (np = rc; np; np = np->n_next) X writengline(f, np); X (void) fclose(f); X} X Xstatic Xwritengline(f, np) /* write .newsrc n.g. line in normal form on f */ Xregister newsrc *np; X{ X (void) fprintf(f, "%s%c 1-%d\n", np->n_name, X (np->n_subscribe? ':': NEGCHAR), np->n_last); X} ! echo 'rna/readnews.1': sed 's/^X//' >'rna/readnews.1' <<'!' X.TH READNEWS 1 X.SH NAME Xnews, readnews \- read news articles X.SH SYNOPSIS X.B readnews X.RB [ -n Xnewsgroups] X.RB [ -i ] X.RB [ -clpC ] X.RB [ -s [ -+? X.RI [ group ]]] X.RB [ -u Xmessageid] X.SH DESCRIPTION X.I Readnews Xwithout arguments enters command mode, Xwhich allows printing of unread articles. XThis is the normal way of using X.IR readnews . X.P X.I Readnews Xmaintains a X.I .newsrc Xfile in the user's home directory that specifies Xall news articles already read. XIt is updated at the end of each reading session. X.P XSome useful functions are available which don't use command mode. XThe flags for these are: X.TP X.B -c XCheck if there is news, and if so print `You have news.'. XA line `readnews -c' is usually placed in the system X.I .profile X.RB ( /etc/profile ). X.TP X.B -C XCheck if there is news, and print the groups and number of Xarticles in each group to be read. X.TP X.B -l XList the titles of available news articles. X.TP X.B -p XPrint all articles on standard output, Xand update X.IR newsrc . X.TP X.B -s XPrint the newsgroup subscription list. X.TP X.BI -s+ " group" XAdd X.I group Xto the subscription list. X.TP X.BI -s- " group" XSubtract X.I group Xfrom the subscription list. X.TP X.B -s? XList currently active newsgroups. X.P XThe remaining flags determine article selection, Xand may also appear in the X.I .newsrc Xfile. XOptions may be specified in the X.I .newsrc Xfile by entering lines prefixed with the word `options', Xfollowed by the options arguments. XThis is most useful with the X.B -n Xflag, specifying the usual groups one wishes to subscribe to. X.TP X\fB-n \fInewsgroups\fR XSelect all articles belonging to X.IR newsgroups . X.I newsgroups Xis a comma separated list of newsgroup names. XThe character `!' may be used to exclude certain groups, Xand the word `all' can be used to match any group. Xe.g. `-n all,!net.jokes' X.TP X.B -i XIgnore X.I .newsrc Xfile. It is not read or updated. XThis allows selection of articles that have already been read. X.TP X\fB-u \fImessageid\fR XUnsubscribe to followup articles referring to X.IR messageid . X(This flag is usually only placed in the X.I .newsrc Xfile as a result of the `u' command.) X.SH COMMANDS XThis section details the commands available when X.I readnews Xis in command mode (no X.B -clpsC Xarguments). XThe simplest way of using this mode, is to enter RETURN after every Xprompt. XThis will present to the user, a short heading for an article, then a prompt. XTyping RETURN again will print the article body. XTyping RETURN yet again will print the next heading, and so on. XIf having read the heading, you don't wish to read the article, you may Xtype `n' (or `+' or ';') which will take you directly to the next heading. X.P X.P XAn article is treated as having been read, if either you have seen Xthe article body, or typed `n' to skip over it. X.P XA number of commands operate on the `current' article. XThis is defined as the article whose header you have most recently seen. X.P XThe commands to read news are: X.TP X.B RETURN XEither print the current article, Xor go to the next article and print its header. X.TP X\fBn\fR or \fB+\fR or \fB;\fR XGo to the next article and print its header. X.TP X.B . XPrint the current article. X.TP X.B - XGo back to the previous article. This is a toggle, typing it Xtwice returns you to the original article. X.TP X.I number XGo to the article X.I number Xin the current newsgroup. XLike the `-' command, Xyou always return to the original article Xafter reading the selected article. X.TP X\fBs \fR[\fIfile\fR] XSave the current article, either in the specified file, or Xin X.BR $HOME/articles . X.TP X.B h XPrint the current header (slightly more verbose than normal header). X.TP X.B H XPrint the current header in full (very verbose). X.TP X\fBN \fR[\fInewsgroup\fR] XGo to the next newsgroup, or to the specified newsgroup. X.TP X.B u XUnsubscribe from all further followup articles on this topic. X.TP X.B U XUnsubscribe from this newsgroup, and go to the next newsgroup. X.TP X\fB!\fIcommand\fB XShell escape. X.I Command Xis executed. XIf X.I command Xis `!' Xthe last escape command is executed. X.TP X\fBq\fR or \fBEOT\fR XQuit. XThe X.I .newsrc Xfile will be updated provided the flag X.B -i Xwas not specified. X.TP X.B x XExit. X.I .newsrc Xis left unchanged (as if no articles had been read). X.TP X.B DEL XAn interrupt will cause X.I readnews Xto terminate its current activity and return to command mode. XAn interrupt in command mode will cause `Interrupt' to be printed, Xand a subsequent interrupt will cause immediate exit (as in the `x' command). X.P XSome commands are available to send/reply or cancel news articles: X.TP X.B c XCancel article. Only the author, or news administrator can do this. X.IR postnews (1) Xis called to do the actual cancelling. X.TP X.B r XReply to sender of the current article by mail. X.I Readnews Xsets up the appropriate headers, and then calls X.IR mail (1) Xto send a reply to the sender. X.TP X.B f XPost a followup to the current article. X.I Readnews Xsets up the appropriate headers, and then calls X.IR postnews (1) Xto post the followup article. X.TP X.B p XPost an article on a new topic. X.IR postnews (1) Xis called to post the new article. X.TP X\fBm \fIperson\fB XMail to X.IR person . X.P XWhen replying by mail, or posting an article, the user Xis prompted for certain headers, and then the text of the article or mail Xitem is entered until a `.' or EOT is entered alone on a line. XThen the article/mail is posted/mailed. XOther commands are available: X.TP X\&\fB.e\fR XEdit the message/article collected so far (see X.IR ed (1)). XThe 'To:' or 'cc:' fields may be changed if mailing. XAfter editing further lines may be appended to the message. X.TP X\&\fB.i\fR XInterpolate Xthe current news article onto the end of the message. XThe interpolated item Xis indented by four spaces. X.TP X\&\fB.!\fIcmd\fR or \fB!\fIcmd\fR XShell escape. X.IR Cmd Xis executed. X.TP X.B DEL XCauses posting/mailing to be aborted, and the article entered so far Xis saved in X.B $HOME/dead.article Xor X.BR $HOME/dead.letter . X.P XIn order to permanently resubscribe to a newsgroup denied by `U', Xor a series of followups denied by `u' it is necessary to understand Xthe format of the X.I .newsrc Xfile. XThe X.I .newsrc Xfile consists of two types of lines: X.TP Xoption lines XThese start with the word `option' and contain the same arguments Xas the X.I readnews Xcommand on the command line. XFollowups are denied with `option -u <messageid>'. XTo resubscribe to further followups, the correct options line must be deleted. X.TP Xread newsgroup lines XThese have the format <newsgroup>`:' <number>, where X<number> represents the last item number seen in that particular newsgroup. XIf the newsgroup has been unsubscribed, the `:' is replaced by a `!'. XTo resubscribe the `!' must be changed back to a `:'. X.SH FILES X.ta 24 X.nf X$HOME/.newsrc options and list of previously read articles X%news where the articles are kept X/usr/lib/news/active current newsgroups X/usr/lib/news/help help file X.fi X.SH SEE ALSO Xpostnews(1), mail(1), ed(1), uusend(8), uurec(8). X.SH BUGS X.I Readnews Xwith the X.B -c Xflag may say "You have news.", when the available article is a unsubscribed Xfollowup article. X.P XYou may see followups, even if you have used the `u' command. XThis is because many sites have faulty news programs, which do Xnot follow the correct protocol, or the sender did not use the `r' command. X.SH AUTHOR XMichael Rourke, University of N.S.W (decvax!mulga!michaelr:elecvax) ! echo done -- Please send comp.sources.unix-related mail to rsalz@uunet.uu.net. Use a domain-based address or give alternate paths, or you may lose out.