BSD 4_4_Lite1 development
authorCSRG <csrg@ucbvax.Berkeley.EDU>
Thu, 3 Jun 1993 06:40:33 +0000 (22:40 -0800)
committerCSRG <csrg@ucbvax.Berkeley.EDU>
Thu, 3 Jun 1993 06:40:33 +0000 (22:40 -0800)
Work on file usr/src/contrib/news/trn3/edit_dist.c
Work on file usr/src/contrib/news/trn3/MANIFEST
Work on file usr/src/contrib/news/trn3/nghash.c
Work on file usr/src/contrib/news/trn3/ng.c
Work on file usr/src/contrib/news/trn3/parsedate.y
Work on file usr/src/contrib/news/trn3/rcstuff.c

Synthesized-from: CSRG/cd2/4.4BSD-Lite1

usr/src/contrib/news/trn3/MANIFEST [new file with mode: 0644]
usr/src/contrib/news/trn3/edit_dist.c [new file with mode: 0644]
usr/src/contrib/news/trn3/ng.c [new file with mode: 0644]
usr/src/contrib/news/trn3/nghash.c [new file with mode: 0644]
usr/src/contrib/news/trn3/parsedate.y [new file with mode: 0644]
usr/src/contrib/news/trn3/rcstuff.c [new file with mode: 0644]

diff --git a/usr/src/contrib/news/trn3/MANIFEST b/usr/src/contrib/news/trn3/MANIFEST
new file mode 100644 (file)
index 0000000..a526e85
--- /dev/null
@@ -0,0 +1,124 @@
+Configure          A shell script that installs everything system dependent.
+EXTERN.h           When included, makes other includes not belong to me.
+HACKERSGUIDE       A brief guide to the contorted innards of [t]rn.
+HINTS              Some helpful hints to get the most out of using trn.
+INIT               Sample system-wide switch file.
+INSTALL            Installation instructions.
+INTERN.h           When included, makes other includes belong to me.
+MANIFEST           This list of files.
+Makefile.SH        Shell script to generate the makefile.
+NEW                List of new features with trn 3.0.
+Pnews.1            Manual page for Pnews.
+Pnews.SH           A news posting shell script that knows about -h.
+README             Start by reading this file.
+Rnmail.1           Manual page for Rnmail.
+Rnmail.SH          A mailer that knows about -h.
+Speller.SH         An ispell wrapper that understands news articles.
+addng.c            Routines for scanning the active file for new newsgroups.
+addng.h            Public info regarding addng.c.
+art.c              Routines to display an article.
+art.h              Public info regarding art.c.
+artio.c            Reserved for the article abstract type, someday.
+artio.h            Public info regarding artio.c.
+artsrch.c          Routines for searching among articles.
+artsrch.h          Public info regarding artsrch.c.
+artstate.h         Info on the current state of the article.
+autosub.c          Routines to allow automatic subscription management
+autosub.h          Public info regarding autosub.c
+backpage.c         Routines for paging backwards in articles.
+backpage.h         Public info regarding backpage.c.
+bits.c             Bitmap management functions.
+bits.h             Public info regarding bits.c.
+cache.c            Routines to handle caching of articles.
+cache.h            Public info regarding cache.c.
+common.h           Global info.
+config.h.SH        Shell script to create config.h
+decode.c           Routines common to the binary decoders
+decode.h           Public info for the binary decoders.
+edit_dist.c        Routines to find the edit distance between two strings.
+final.c            Finalization (exit) routines.
+final.h            Public info regarding final.c.
+getactive.c        Used by shell scripts to get the active file (NNTP only).
+hash.c             Hashing routines.
+hash.h             Public/private info for hashing routines.
+head.c             Header parsing routines.
+head.h             Public info regarding head.c.
+help.c             Help routines.
+help.h             Public info regarding help.c.
+init.c             Initialization (startup) routines.
+init.h             Public info regarding init.c.
+intrp.c            Filename expansion and % interpretation routines.
+intrp.h            Public info regarding intrp.c.
+kfile.c            KILL file routines.
+kfile.h            Public info regarding kfile.c.
+last.c             Routines for handling the .rnlast file.
+last.h             Public info regarding last.c.
+makedepend.SH      Shell script to generate make dependencies.
+makedir.SH         Shell script to make nested subdirectories.
+mbox.saver.SH      Shell script to save an article to a mailbox.
+ndir.c             BSD 4.2 directory routine emulation.
+ndir.h             Public directory info.
+newsetup.1         Manual page for newsetup.
+newsetup.SH        Shell script to create a .newsrc file.
+newsgroups.1       Manual page for newsgroups.
+newsgroups.SH      Shell script to list unsubscribed newsgroups.
+newsnews.SH        A motd-like file that trn may print at startup.
+ng.c               Routines to display a newsgroup.
+ng.h               Public info regarding ng.c.
+ngdata.c           General data fetching routines for a newsgroup.
+ngdata.h           Public info regarding ngdata.c.
+nghash.c           Some hashing routines to speed up active file handling.
+ngsrch.c           Routines to search among newsgroups.
+ngsrch.h           Public info regarding ngsrch.c.
+ngstuff.c          Support routines for ng.c.
+ngstuff.h          Public info regarding ng.c.
+nntp.c             Routines for accessing NNTP commands.
+nntp.h             Public info regarding the NNTP routines.
+nntpclient.c       Custom version of the nntp client library.
+nntpclient.h       Public info for the nntp client library.
+nntpinit.c         Routines to implement server_init().
+norm.saver.SH      Shell script to save an article to a normal file.
+only.c             Routines to perform newsgroup restriction.
+only.h             Public info regarding only.c.
+overview.h         Defines for customizing the overview handling.
+parsedate.y        A yacc script for date parsing.
+patchlevel.h       Indicates current patch level.
+rcln.c             Routines to mung a .newsrc line.
+rcln.h             Public info regarding rcln.c.
+rcstuff.c          Routines to mung the .newsrc file.
+rcstuff.h          Public info regarding rcstuff.c.
+respond.c          Various routines for doing things with articles.
+respond.h          Public info regarding respond.c.
+rt-mt.c            Support for the mthread's .thread file format.
+rt-mt.h            Public/private info for rt-mt.c.
+rt-ov.c            Support for .overview files.
+rt-ov.h            Public info for rt-ov.c.
+rt-page.c          Routines to manipulate pages in the selector.
+rt-page.h          Public info for rt-page.c.
+rt-process.c       The thread-processing code.
+rt-process.h       Public info for rt-process.c.
+rt-select.c        The thread/subject/article selector.
+rt-select.h        Public/private info for rt-select.c.
+rt-util.c          Utility routines for reading threads.
+rt-util.h          Public info for rt-util.c.
+rt-wumpus.c        Routines to draw the character-oriented tree display.
+rt-wumpus.h        Public info for rt-wumpus.c.
+rthread.c          The basic routines added to init and read threads.
+rthread.h          Public info for rthread.c.
+search.c           Regular expression processing ala emacs.
+search.h           Public info regarding search.c.
+sw.c               Switch processing routines.
+sw.h               Public info regarding switch.c.
+term.c             Terminal interface routines.
+term.h             Public info regarding term.c.
+trn.1              Manual pages for trn -- PLEASE READ.
+trn.c              The main program.
+trn.h              Public info for trn.c.
+unipatch.c         A unified diff filter for use with old versions of patch.
+unship.c           Decodes ship files.
+util.c             Utility routines.
+util.h             Public info regarding util.c.
+uudecode.c         Decodes uuencoded files.
+nntp/nntp.patch    A patch for NNTP to add XTHREAD, XOVER, and LISTGROUP.
+nntp/acttimes.c    A program to maintain active.times for NNTP or standalone
+nntp/support.patch A patch for NNTP's support dir to use acttimes.c.
diff --git a/usr/src/contrib/news/trn3/edit_dist.c b/usr/src/contrib/news/trn3/edit_dist.c
new file mode 100644 (file)
index 0000000..08baea5
--- /dev/null
@@ -0,0 +1,275 @@
+#include "EXTERN.h"
+#include "common.h"            /* Declare MEM_SIZE */
+#include "util.h"              /* Declare safemalloc() */
+
+#ifdef EDIT_DISTANCE
+
+/* edit_dist -- returns the minimum edit distance between two strings
+
+       Program by:  Mark Maimone   CMU Computer Science   13 Nov 89
+       Last Modified:  28 Jan 90
+
+   If the input strings have length n and m, the algorithm runs in time
+   O(nm) and space O(min(m,n)).
+
+HISTORY
+   13 Nov 89 (mwm) Created edit_dist() and set_costs().
+
+   28 Jan 90 (mwm) Added view_costs().  Should verify that THRESHOLD
+   computations will work even when THRESHOLD is not a multiple of
+   sizeof(int).
+
+   17 May 93 (mwm) Improved performance when used with trn's newsgroup
+   processing; assume all costs are 1, and you can terminate when a
+   threshold is exceeded.
+*/
+
+
+#define        TRN_SPEEDUP             /* Use a less-general version of the
+                                  routine, one that's better for trn.
+                                  All change costs are 1, and it's okay
+                                  to terminate if the edit distance is
+                                  known to exceed MIN_DIST */
+
+#define THRESHOLD 4000         /* worry about allocating more memory only
+                                  when this # of bytes is exceeded */
+#define STRLENTHRESHOLD ((int) ((THRESHOLD / sizeof (int) - 3) / 2))
+
+#define SAFE_ASSIGN(x,y) (((x) != NULL) ? (*(x) = (y)) : (y))
+
+#define swap_int(x,y)  (_iswap = (x), (x) = (y), (y) = _iswap)
+#define swap_char(x,y) (_cswap = (x), (x) = (y), (y) = _cswap)
+#define min3(x,y,z) (_mx = (x), _my = (y), _mz = (z), (_mx < _my ? (_mx < _mz ? _mx : _mz) : (_mz < _my) ? _mz : _my))
+#define min2(x,y) (_mx = (x), _my = (y), (_mx < _my ? _mx : _my))
+
+
+static int insert_cost = 1;
+static int delete_cost = 1;
+static int change_cost = 1;
+static int swap_cost   = 1;
+
+static int _iswap;                     /* swap_int temp variable */
+static char *_cswap;                   /* swap_char temp variable */
+static int _mx, _my, _mz;              /* min2, min3 temp variables */
+
+
+
+void
+view_costs(ins, del, ch, swap)
+int *ins, *del, *ch, *swap;
+{
+    SAFE_ASSIGN(ins, insert_cost);
+    SAFE_ASSIGN(del, delete_cost);
+    SAFE_ASSIGN(ch, change_cost);
+    SAFE_ASSIGN(swap, swap_cost);
+} /* view_costs */
+
+void
+set_costs(ins, del, ch, swap)
+int ins, del, ch, swap;
+{
+    insert_cost = ins;
+    delete_cost = del;
+    change_cost = ch;
+    swap_cost   = swap;
+} /* set_costs */
+
+
+/* edit_distn -- returns the edit distance between two strings, or -1 on
+   failure */
+
+int
+edit_distn(from, from_len, to, to_len)
+char *from, *to;
+register int from_len, to_len;
+{
+#ifndef TRN_SPEEDUP
+    register int ins, del, ch;         /* local copies of edit costs */
+#endif
+    register int row, col, index;      /* dynamic programming counters */
+    register int radix;                        /* radix for modular indexing */
+#ifdef TRN_SPEEDUP
+    register int low;
+#endif
+    int *buffer;                       /* pointer to storage for one row
+                                          of the d.p. array */
+    static int store[THRESHOLD / sizeof (int)];
+                                       /* a small amount of static
+                                          storage, to be used when the
+                                          input strings are small enough */
+
+/* Handle trivial cases when one string is empty */
+
+    if (from == NULL || !from_len)
+       if (to == NULL || !to_len)
+           return 0;
+       else
+           return to_len * insert_cost;
+    else if (to == NULL || !to_len)
+       return from_len * delete_cost;
+
+/* Initialize registers */
+
+    radix = 2 * from_len + 3;
+#ifdef TRN_SPEEDUP
+#define ins 1
+#define del 1
+#define ch 1
+#define swap_cost 1
+#else
+    ins  = insert_cost;
+    del  = delete_cost;
+    ch   = change_cost;
+#endif
+
+/* Make   from   short enough to fit in the static storage, if it's at all
+   possible */
+
+    if (from_len > to_len && from_len > STRLENTHRESHOLD) {
+       swap_int(from_len, to_len);
+       swap_char(from, to);
+#ifndef TRN_SPEEDUP
+       swap_int(ins, del);
+#endif
+    } /* if from_len > to_len */
+
+/* Allocate the array storage (from the heap if necessary) */
+
+    if (from_len <= STRLENTHRESHOLD)
+       buffer = store;
+    else
+       buffer = (int *) safemalloc((MEM_SIZE) radix * sizeof (int));
+
+/* Here's where the fun begins.  We will find the minimum edit distance
+   using dynamic programming.  We only need to store two rows of the matrix
+   at a time, since we always progress down the matrix.  For example,
+   given the strings "one" and "two", and insert, delete and change costs
+   equal to 1:
+
+          _  o  n  e
+       _  0  1  2  3
+       t  1  1  2  3
+       w  2  2  2  3
+       o  3  2  3  3
+
+   The dynamic programming recursion is defined as follows:
+
+       ar(x,0) := x * insert_cost
+       ar(0,y) := y * delete_cost
+       ar(x,y) := min(a(x - 1, y - 1) + (from[x] == to[y] ? 0 : change),
+                      a(x - 1, y) + insert_cost,
+                      a(x, y - 1) + delete_cost,
+                      a(x - 2, y - 2) + (from[x] == to[y-1] &&
+                                         from[x-1] == to[y] ? swap_cost :
+                                         infinity))
+
+   Since this only looks at most two rows and three columns back, we need
+   only store the values for the two preceeding rows.  In this
+   implementation, we do not explicitly store the zero column, so only 2 *
+   from_len + 2   words are needed.  However, in the implementation of the
+   swap_cost   check, the current matrix value is used as a buffer; we
+   can't overwrite the earlier value until the   swap_cost   check has
+   been performed.  So we use   2 * from_len + 3   elements in the buffer.
+*/
+
+#define ar(x,y,index) (((x) == 0) ? (y) * del : (((y) == 0) ? (x) * ins : \
+       buffer[mod(index)]))
+#define NW(x,y)          ar(x, y, index + from_len + 2)
+#define N(x,y)   ar(x, y, index + from_len + 3)
+#define W(x,y)   ar(x, y, index + radix - 1)
+#define NNWW(x,y) ar(x, y, index + 1)
+#define mod(x) ((x) % radix)
+
+    index = 0;
+
+#ifdef DEBUG_EDITDIST
+    printf("      ");
+    for (col = 0; col < from_len; col++)
+       printf(" %c ", from[col]);
+    printf("\n   ");
+
+    for (col = 0; col <= from_len; col++)
+       printf("%2d ", col * del);
+#endif
+
+/* Row 0 is handled implicitly; its value at a given column is   col*del.
+   The loop below computes the values for Row 1.  At this point we know the
+   strings are nonempty.  We also don't need to consider swap costs in row
+   1.
+
+   COMMENT:  the indicies   row and col   below point into the STRING, so
+   the corresponding MATRIX indicies are   row+1 and col+1.
+*/
+
+    buffer[index++] = min2(ins + del, (from[0] == to[0] ? 0 : ch));
+#ifdef TRN_SPEEDUP
+    low = buffer[mod(index + radix - 1)];
+#endif
+
+#ifdef DEBUG_EDITDIST
+    printf("\n %c %2d %2d ", to[0], ins, buffer[index - 1]);
+#endif
+
+    for (col = 1; col < from_len; col++) {
+       buffer[index] = min3(
+               col * del + ((from[col] == to[0]) ? 0 : ch),
+               (col + 1) * del + ins,
+               buffer[index - 1] + del);
+#ifdef TRN_SPEEDUP
+       if (buffer[index] < low)
+           low = buffer[index];
+#endif
+       index++;
+
+#ifdef DEBUG_EDITDIST
+       printf("%2d ", buffer[index - 1]);
+#endif
+
+    } /* for col = 1 */
+
+#ifdef DEBUG_EDITDIST
+    printf("\n %c %2d ", to[1], 2 * ins);
+#endif
+
+/* Now handle the rest of the matrix */
+
+    for (row = 1; row < to_len; row++) {
+       for (col = 0; col < from_len; col++) {
+           buffer[index] = min3(
+                   NW(row, col) + ((from[col] == to[row]) ? 0 : ch),
+                   N(row, col + 1) + ins,
+                   W(row + 1, col) + del);
+           if (from[col] == to[row - 1] && col > 0 &&
+                   from[col - 1] == to[row])               
+               buffer[index] = min2(buffer[index],
+                       NNWW(row - 1, col - 1) + swap_cost);
+
+#ifdef DEBUG_EDITDIST
+           printf("%2d ", buffer[index]);
+#endif
+#ifdef TRN_SPEEDUP
+           if (buffer[index] < low || col == 0)
+               low = buffer[index];
+#endif
+
+           index = mod(index + 1);
+       } /* for col = 1 */
+#ifdef DEBUG_EDITDIST
+       if (row < to_len - 1)
+           printf("\n %c %2d ", to[row+1], (row + 2) * ins);
+       else
+           printf("\n");
+#endif
+#ifdef TRN_SPEEDUP
+       if (low > MIN_DIST)
+           break;
+#endif
+    } /* for row = 1 */
+
+    row = buffer[mod(index + radix - 1)];
+    if (buffer != store)
+       free((char *) buffer);
+    return row;
+} /* edit_distn */
+
+#endif /* EDIT_DISTANCE */
diff --git a/usr/src/contrib/news/trn3/ng.c b/usr/src/contrib/news/trn3/ng.c
new file mode 100644 (file)
index 0000000..b909917
--- /dev/null
@@ -0,0 +1,1399 @@
+/* $Id: ng.c,v 3.0 1992/02/01 03:09:32 davison Trn $
+ */
+/* This software is Copyright 1991 by Stan Barber. 
+ *
+ * Permission is hereby granted to copy, reproduce, redistribute or otherwise
+ * use this software as long as: there is no monetary profit gained
+ * specifically from the use or reproduction of this software, it is not
+ * sold, rented, traded or otherwise marketed, and this copyright notice is
+ * included prominently in any copy made. 
+ *
+ * The author make no claims as to the fitness or correctness of this software
+ * for any use whatsoever, and it is provided as is. Any use of this software
+ * is at the user's own risk. 
+ */
+
+#include "EXTERN.h"
+#include "common.h"
+#include "trn.h"
+#include "term.h"
+#include "final.h"
+#include "util.h"
+#include "cache.h"
+#include "bits.h"
+#include "artsrch.h"
+#include "help.h"
+#include "kfile.h"
+#include "rcstuff.h"
+#include "head.h"
+#include "art.h"
+#include "artio.h"
+#include "ngstuff.h"
+#include "intrp.h"
+#include "respond.h"
+#include "ngdata.h"
+#include "backpage.h"
+#include "rcln.h"
+#include "last.h"
+#include "search.h"
+#include "nntp.h"
+#include "rthread.h"
+#include "rt-select.h"
+#include "rt-wumpus.h"
+#include "decode.h"
+#include "INTERN.h"
+#include "ng.h"
+#include "artstate.h"                  /* somebody has to do it */
+
+/* art_switch() return values */
+
+#define AS_NORM 0
+#define AS_INP 1
+#define AS_ASK 2
+#define AS_CLEAN 3
+
+int exit_code = NG_NORM;
+
+void
+ng_init()
+{
+
+#ifdef KILLFILES
+    open_kfile(KF_GLOBAL);
+#endif
+#ifdef CUSTOMLINES
+    init_compex(&hide_compex);
+    init_compex(&page_compex);
+#endif
+}
+
+/* do newsgroup on line ng with name ngname */
+
+/* assumes that we are chdir'ed to NEWSSPOOL, and assures that that is
+ * still true upon return, but chdirs to NEWSSPOOL/ngname in between
+ *
+ * If you can understand this routine, you understand most of the program.
+ * The basic structure is:
+ *     for each desired article
+ *             for each desired page
+ *                     for each line on page
+ *                             if we need another line from file
+ *                                     get it
+ *                                     if it's a header line
+ *                                             do special things
+ *                             for each column on page
+ *                                     put out a character
+ *                             end loop
+ *                     end loop
+ *             end loop
+ *     end loop
+ *
+ *     (Actually, the pager is in another routine.)
+ *
+ * The chief problem is deciding what is meant by "desired".  Most of
+ * the messiness of this routine is due to the fact that people want
+ * to do unstructured things all the time.  I have used a few judicious
+ * goto's where I thought it improved readability.  The rest of the messiness
+ * arises from trying to be both space and time efficient.  Have fun.
+ */
+
+int
+do_newsgroup(start_command)
+char *start_command;                   /* command to fake up first */
+{
+    char oldmode = mode;
+    char *whatnext = "%sWhat next? [%s]";
+
+#ifdef ARTSEARCH
+    srchahead = (scanon && !ThreadedGroup      /* did they say -S? */
+             && ((ART_NUM)toread[ng]) >= scanon ? -1 : 0);
+#endif
+    
+    exit_code = NG_NORM;
+    save_ids = FALSE;
+    killfirst = 0;
+
+    if (extractdest) {
+       free(extractdest);
+       extractdest = Nullch;
+    }
+    if (extractprog) {
+       free(extractprog);
+       extractprog = Nullch;
+    }
+
+    /* initialize the newsgroup data structures */
+
+    if (!access_ng())
+       return -1;
+
+    /* FROM HERE ON, RETURN THRU CLEANUP OR WE ARE SCREWED */
+
+    in_ng = TRUE;                      /* tell the world we are here */
+    forcelast = TRUE;                  /* if 0 unread, do not bomb out */
+    recent_artp = curr_artp = Nullart;
+    recent_art = curr_art = lastart+1;
+    prompt = whatnext;
+
+    /* remember what newsgroup we were in for sake of posterity */
+
+    writelast();
+
+    /* see if there are any special searches to do */
+
+    has_normal_kills = FALSE;
+#ifdef KILLFILES
+    open_kfile(KF_LOCAL);
+# ifdef VERBOSE
+    IF(verbose)
+       kill_unwanted(firstart,"Processing memorized commands...\n\n",TRUE);
+    ELSE
+# endif
+# ifdef TERSE
+       kill_unwanted(firstart,"Auto-processing...\n\n",TRUE);
+# endif
+#endif
+    if (!selected_count)
+       selected_only = FALSE;
+    top_article();
+
+    /* do they want a special top line? */
+
+    firstline = getval("FIRSTLINE",Nullch);
+
+    /* custom line suppression, custom page ending */
+
+#ifdef CUSTOMLINES
+    if (hideline = getval("HIDELINE",Nullch))
+       compile(&hide_compex,hideline,TRUE,TRUE);
+    if (pagestop = getval("PAGESTOP",Nullch))
+       compile(&page_compex,pagestop,TRUE,TRUE);
+#endif
+
+    /* now read each unread article */
+
+    rc_changed = doing_ng = TRUE;      /* enter the twilight zone */
+    checkcount = 0;                    /* do not checkpoint for a while */
+    do_fseek = FALSE;                  /* start 1st article at top */
+    for (; art<=lastart+1; ) {         /* for each article */
+       mode = 'a';
+
+       /* do we need to "grow" the newsgroup? */
+
+       if (art > lastart || forcegrow) {
+           ART_NUM oldlast = lastart;
+#ifdef USE_NNTP
+           ART_NUM newlast = lastart;
+           while (nntp_stat(newlast+1))
+               newlast++;
+           if (newlast > oldlast) {
+               ngmax[ng] = newlast;
+               grow_ng(newlast);
+           }
+#else
+           grow_ng(getngsize(ng));
+#endif
+           if (forcelast && art > oldlast)
+               art = lastart+1;
+       }
+       find_article(art);              /* sets artp */
+       if (start_command) {            /* do we have an initial command? */
+           pushstring(start_command, 0);
+           free(start_command);
+           start_command = Nullch;
+           art = curr_art = lastart+1;
+           artp = curr_artp = Nullart;
+           if (input_pending())
+               goto reinp_article;
+       }
+       if (art>lastart) {              /* are we off the end still? */
+           ARTICLE *ap;
+           ART_NUM i;
+           art = lastart + 1;          /* keep pointer references sane */
+           if (!forcelast && toread[ng] && selected_only && !selected_count) {
+               art = curr_art;
+               artp = curr_artp;
+               strcpy(buf, "+");
+               goto article_level;
+           }
+           count_subjects(CS_NORM);
+           for (i=last_cached+1, ap=article_ptr(i); i<=lastart; i++, ap++)
+               if (!(ap->flags & AF_READ))
+                   article_count++;
+           toread[ng] = (ART_UNREAD)article_count;
+           if (artp != curr_artp) {
+               recent_art = curr_art;  /* remember last article # (for '-') */
+               curr_art = art;         /* set current article # */
+               recent_artp = curr_artp;
+               curr_artp = artp;
+           }
+           if (erase_screen)
+               clear();                        /* clear the screen */
+           else
+               fputs("\n\n",stdout) FLUSH;
+#ifdef VERBOSE
+           IF(verbose)
+               printf("End of newsgroup %s.",ngname);
+                                       /* print pseudo-article */
+           ELSE
+#endif
+#ifdef TERSE
+               printf("End of %s",ngname);
+#endif
+           if (article_count) {
+               if (selected_only)
+                   printf("  (%ld + %ld articles still unread)",
+                       (long)selected_count,
+                       (long)article_count-selected_count);
+               else
+                   printf("  (%ld article%s still unread)",
+                       (long)article_count,article_count==1?nullstr:"s");
+           }
+           else if (!forcelast)
+               goto cleanup;           /* actually exit newsgroup */
+           mode = 'e';
+           prompt = whatnext;
+#ifdef ARTSEARCH
+           srchahead = 0;              /* no more subject search mode */
+#endif
+           fputs("\n\n",stdout) FLUSH;
+       }
+       else if (!reread && (was_read(art) || (artp->flags & AF_MISSING)
+               || (selected_only && !(artp->flags & AF_SEL)))) {
+                                       /* has this article been read? */
+           inc_art(selected_only,FALSE);/* then skip it */
+           continue;
+       }
+       else if (!reread && !parseheader(art)) {
+           oneless(artp);              /* mark deleted as read */
+           ng_skip();
+       }
+       else {                          /* we have a real live article */
+           if (artp != curr_artp) {
+               recent_art = curr_art;  /* remember last article # (for '-') */
+               curr_art = art;         /* set current article # */
+               recent_artp = curr_artp;
+               curr_artp = artp;
+           }
+           if (!do_fseek) {            /* starting at top of article? */
+               artline = 0;            /* start at the beginning */
+               topline = -1;           /* and remember top line of screen */
+                                       /*  (line # within article file) */
+           }
+           clear();                    /* clear screen */
+           if (!artopen(art)) {        /* make sure article is found & open */
+               char tmpbuf[256];
+               /* see if we have tree data for this article anyway */
+               init_tree();
+               sprintf(tmpbuf,"%s: article is not available.",ngname);
+               if (artp && !(artp->flags & AF_CACHED)) {
+                   if (absfirst < first_cached || last_cached < lastart
+                    || !cached_all_in_range)
+                       sprintf(tmpbuf,"%s: article may show up in a moment.",
+                               ngname);
+               }
+               tree_puts(tmpbuf,0,0);
+               vwtary((ART_LINE)0,(ART_POS)0);
+               finish_tree(1);
+               prompt = whatnext;
+#ifdef ARTSEARCH
+               srchahead = 0;
+#endif
+           }
+           else {                      /* found it, so print it */
+               switch (do_article()) {
+               case DA_CLEAN:          /* quit newsgroup */
+                   goto cleanup;
+               case DA_TOEND:          /* do not mark as read */
+                   goto reask_article; 
+               case DA_RAISE:          /* reparse command at end of art */
+                   goto article_level;
+               case DA_NORM:           /* normal end of article */
+                   break;
+               }
+           }
+           if (art >= absfirst)        /* don't mark non-existant articles */
+               mark_as_read();         /* mark current article as read */
+           do_hiding = TRUE;
+#ifdef ROTATION
+           rotate = FALSE;
+#endif
+       }
+
+/* if these gotos bother you, think of this as a little state machine */
+
+reask_article:
+#ifdef MAILCALL
+       setmail(FALSE);
+#endif
+       setdfltcmd();
+#ifdef CLEAREOL
+       if (erase_screen && can_home_clear)
+           clear_rest();
+#endif /* CLEAREOL */
+       unflush_output();               /* disable any ^O in effect */
+       standout();                     /* enter standout mode */
+       printf(prompt,mailcall,dfltcmd);/* print prompt, whatever it is */
+       un_standout();                  /* leave standout mode */
+       putchar(' ');
+       fflush(stdout);
+reinp_article:
+       reread = FALSE;
+       forcelast = FALSE;
+       eat_typeahead();
+#ifdef PENDING
+       look_ahead();                   /* see what we can do in advance */
+       cache_until_key();
+#endif
+       art = curr_art;
+       artp = curr_artp;
+       getcmd(buf);
+       if (errno || *buf == '\f') {
+           if (LINES < 100 && !int_count)
+               *buf = '\f';            /* on CONT fake up refresh */
+           else {
+               putchar('\n') FLUSH;            /* but only on a crt */
+               goto reask_article;
+           }
+       }
+article_level:
+       output_chase_phrase = TRUE;
+
+       /* parse and process article level command */
+
+       switch (art_switch()) {
+       case AS_INP:                    /* multichar command rubbed out */
+           goto reinp_article;
+       case AS_ASK:                    /* reprompt "End of article..." */
+           goto reask_article;
+       case AS_CLEAN:                  /* exit newsgroup */
+           goto cleanup;
+       case AS_NORM:                   /* display article art */
+           break;
+       }
+    }                                  /* end of article selection loop */
+    
+/* shut down newsgroup */
+
+cleanup:
+    decode_end();
+#ifdef KILLFILES
+    kill_unwanted(firstart,"\nCleaning up...\n\n",FALSE);
+                                       /* do cleanup from KILL file, if any */
+#endif
+    in_ng = FALSE;                     /* leave newsgroup state */
+    if (artfp != Nullfp) {             /* article still open? */
+       fclose(artfp);                  /* close it */
+       artfp = Nullfp;                 /* and tell the world */
+       openart = 0;
+    }
+    putchar('\n') FLUSH;
+    deselect_all();
+    yankback();                                /* do a Y command */
+    bits_to_rc();                      /* reconstitute .newsrc line */
+    doing_ng = FALSE;                  /* tell sig_catcher to cool it */
+    write_rc();                                /* and update .newsrc */
+    rc_changed = FALSE;                        /* tell sig_catcher it is ok */
+    if (chdir(spool)) {
+       printf(nocd,spool) FLUSH;
+       sig_catcher(0);
+    }
+#ifdef KILLFILES
+    if (localkfp) {
+       fclose(localkfp);
+       localkfp = Nullfp;
+    }
+#endif
+    mode = oldmode;
+    return exit_code;
+}                                      /* Whew! */
+
+/* decide what to do at the end of an article */
+
+int
+art_switch()
+{
+    register ART_NUM i;
+      
+    setdef(buf,dfltcmd);
+#ifdef VERIFY
+    printcmd();
+#endif
+
+    switch (*buf) {
+    case '<':                  /* goto previous subject/thread */
+       prev_subject();
+       return AS_NORM;
+    case '>':                  /* goto next subject/thread */
+       next_subject();
+       return AS_NORM;
+    case 'U': {                        /* unread some articles */
+       char *u_prompt, *u_help_thread;
+
+       dfltcmd = "+";
+       if (!artp) {
+           u_help_thread = nullstr;
+#ifdef VERBOSE
+           IF(verbose)
+               u_prompt = "\nSet unread: +select or all? [+an] ";
+           ELSE
+#endif
+#ifdef TERSE
+               u_prompt = "\nSet unread? [+an] ";
+#endif
+       }
+       else {
+#ifdef VERBOSE
+           IF(verbose) {
+               u_prompt = "\n\
+Set unread: +select, thread, subthread, or all? [+tsan] ";
+               u_help_thread = "\
+Type t or SP to mark this thread's articles as unread.\n\
+Type s to mark the current article and its descendants as unread.\n";
+           }
+           ELSE
+#endif
+#ifdef TERSE
+           {
+               u_prompt = "\nSet unread? [+tsan] ";
+               u_help_thread = "\
+t or SP to mark thread unread.\n\
+s to mark subthread unread.\n";
+           }
+#endif
+       }
+      reask_unread:
+       in_char(u_prompt,'u');
+       setdef(buf,dfltcmd);
+#ifdef VERIFY
+       printcmd();
+#endif
+       putchar('\n') FLUSH;
+       if (*buf == 'h') {
+#ifdef VERBOSE
+           IF(verbose)
+           {
+               fputs("\
+Type + to enter select thread mode using all the unread articles.\n\
+(The selected threads will be marked as unread and displayed as usual.)\n\
+",stdout) FLUSH;
+               fputs(u_help_thread,stdout);
+               fputs("\
+Type a to mark all articles in this group as unread.\n\
+Type n to change nothing.\n\
+",stdout) FLUSH;
+           }
+           ELSE
+#endif
+#ifdef TERSE
+           {
+               fputs("\
++ to select threads from the unread.\n\
+",stdout) FLUSH;
+               fputs(u_help_thread,stdout);
+               fputs("\
+a to mark all articles unread.\n\
+n to change nothing.\n\
+",stdout) FLUSH;
+           }
+#endif
+           goto reask_unread;
+       }
+       else if (*buf == 'n' || *buf == 'q')
+           return AS_ASK;
+       else if (*buf == 't' && u_help_thread != nullstr) {
+           unkill_thread(artp->subj->thread);
+           if ((artp = first_art(artp->subj)) != Nullart)
+               art = article_num(artp);
+       } else if (*buf == 's' && u_help_thread != nullstr)
+           unkill_subthread(artp);
+       else if (*buf == 'a') {
+           register ARTICLE *ap;
+           check_first(absfirst);
+           ap = article_ptr(absfirst);
+           for (i = absfirst; i <= lastart; i++, ap++)
+               if ((ap->flags & (AF_READ|AF_MISSING)) == AF_READ) {
+                   ap->flags &= ~AF_READ;              /* mark as unread */
+                   toread[ng]++;
+               }
+           count_subjects(CS_NORM);
+       }
+       else if (*buf == '+') {
+           *buf = 'U';
+           goto run_the_selector;
+       }
+       else {
+           fputs(hforhelp,stdout) FLUSH;
+           settle_down();
+           goto reask_unread;
+       }
+       return AS_NORM;
+    }
+    case '[':                  /* goto parent article */
+    case '{':                  /* goto thread's root article */
+       if (artp) {
+           if (!find_parent(*buf == '{')) {
+               register char *cp = (*buf=='['?"parent":"root");
+#ifdef VERBOSE
+               IF(verbose)
+                   printf("\nThere is no %s article prior to this one.\n",
+                       cp) FLUSH;
+               ELSE
+#endif
+#ifdef TERSE
+                   printf("\nNo prior %s.\n",cp) FLUSH;
+#endif
+               return AS_ASK;
+           }
+           reread = TRUE;
+           return AS_NORM;
+       }
+not_threaded:
+       if (ThreadedGroup) {
+#ifdef VERBOSE
+           IF(verbose)
+               fputs("\nThis article is not threaded.\n",stdout) FLUSH;
+           ELSE
+#endif
+#ifdef TERSE
+               fputs("\nUnthreaded article.\n",stdout) FLUSH;
+#endif
+           return AS_ASK;
+       }
+#ifdef VERBOSE
+       IF(verbose)
+           fputs("\nThis group is not threaded.\n",stdout) FLUSH;
+       ELSE
+#endif
+#ifdef TERSE
+           fputs("\nUnthreaded group.\n",stdout) FLUSH;
+#endif
+       return AS_ASK;
+    case ']':                  /* goto child article */
+    case '}':                  /* goto thread's leaf article */
+       if (artp) {
+           if (!find_leaf(*buf == '}')) {
+#ifdef VERBOSE
+               IF(verbose)
+                   fputs("\n\
+This is the last leaf in this tree.\n",stdout) FLUSH;
+               ELSE
+#endif
+#ifdef TERSE
+                   fputs("\nLast leaf.\n",stdout) FLUSH;
+#endif
+               return AS_ASK;
+           }
+           reread = TRUE;
+           return AS_NORM;
+       }
+       goto not_threaded;
+    case '(':                  /* goto previous sibling */
+    case ')':                  /* goto next sibling */
+       if (artp) {
+           if (!(*buf == '(' ? find_prev_sib() : find_next_sib())) {
+               register char *cp = (*buf == '(' ? "previous" : "next");
+#ifdef VERBOSE
+               IF(verbose)
+                   printf("\nThis article has no %s sibling.\n",cp) FLUSH;
+               ELSE
+#endif
+#ifdef TERSE
+                   printf("\nNo %s sibling.\n",cp) FLUSH;
+#endif
+               return AS_ASK;
+           }
+           reread = TRUE;
+           return AS_NORM;
+       }
+       goto not_threaded;
+    case 'T':
+       if (!ThreadedGroup)
+           goto not_threaded;
+       /* FALL THROUGH */
+    case 'A':
+       if (!artp) {
+           printf("You're not at an article.\n");
+           return AS_ASK;
+       }
+       switch (ask_memorize(*buf)) {
+       case ',':  case 'j':
+           return AS_NORM;
+       }
+       return AS_ASK;
+    case 'K':
+       if (!artp) {
+           printf("You're not at an article.\n");
+           return AS_ASK;
+       }
+       /* first, write kill-subject command */
+       (void)art_search(buf, (sizeof buf), TRUE);
+       art = curr_art;
+       artp = curr_artp;
+       kill_subject(artp->subj,KF_ALL);/* take care of any prior subjects */
+       return AS_NORM;
+    case ',':          /* kill this node and all descendants */
+       if (ThreadedGroup)
+           kill_subthread(artp,KF_ALL);
+       else if (art >= absfirst && art <= lastart)
+           mark_as_read();
+       return AS_NORM;
+    case 'J':          /* Junk all nodes in this thread */
+       if (ThreadedGroup) {
+           kill_thread(artp->subj->thread,KF_ALL);
+           return AS_NORM;
+       }
+       /* FALL THROUGH */
+    case 'k':          /* kill current subject */
+       kill_subject(artp->subj,KF_ALL);
+       if (last_cached < lastart) {
+           *buf = 'k';
+           goto normal_search;
+       }
+       return AS_NORM;
+    case 't':
+       carriage_return();
+#ifndef CLEAREOL
+       erase_eol();            /* erase the prompt */
+#else
+       if (erase_screen && can_home_clear)
+           clear_rest();
+       else
+           erase_eol();        /* erase the prompt */
+#endif /* CLEAREOL */
+       fflush(stdout);
+       page_line = 1;
+       entire_tree(curr_artp);
+       return AS_ASK;
+    case ':':                  /* execute command on selected articles */
+       page_line = 1;
+       if (!use_selected())
+           return AS_INP;
+       putchar('\n');
+       art = curr_art;
+       artp = curr_artp;
+       return AS_ASK;
+    case 'p':                  /* find previous unread article */
+       do {
+           dec_art(selected_only,FALSE);
+       } while (art >= firstart && (was_read(art) || !parseheader(art)));
+#ifdef ARTSEARCH
+       srchahead = 0;
+#endif
+       if (art >= firstart)
+           return AS_NORM;
+       art = absfirst; 
+       /* FALL THROUGH */
+    case 'P':          /* goto previous article */
+       dec_art(FALSE,TRUE);
+      check_dec_art:
+       if (art < absfirst) {
+#ifdef VERBOSE
+           IF(verbose)
+               printf("\nThere are no%s%s articles prior to this one.\n",
+                       *buf=='P'?nullstr:" unread",
+                       selected_only?" selected":nullstr) FLUSH;
+           ELSE
+#endif
+#ifdef TERSE
+               printf("\nNo previous%s%s articles\n",
+                       *buf=='P'?nullstr:" unread",
+                       selected_only?" selected":nullstr) FLUSH;
+#endif
+           art = curr_art;
+           artp = curr_artp;
+           return AS_ASK;
+       }
+       reread = TRUE;
+#ifdef ARTSEARCH
+       srchahead = 0;
+#endif
+       return AS_NORM;
+    case '-':
+       if (recent_art >= 0) {
+           art = recent_art;
+           artp = recent_artp;
+           reread = TRUE;
+           forcelast = TRUE;
+#ifdef ARTSEARCH
+           srchahead = -(srchahead != 0);
+#endif
+           return AS_NORM;
+       }
+       else {
+           exit_code = NG_MINUS;
+           return AS_CLEAN;
+       }
+    case 'n':          /* find next unread article? */
+       if (art > lastart) {
+           if (!toread[ng])
+               return AS_CLEAN;
+           top_article();
+       }
+#ifdef ARTSEARCH
+       else if (scanon && !ThreadedGroup && srchahead) {
+           *buf = Ctl('n');
+           if (!next_art_with_subj())
+               goto normal_search;
+           return AS_NORM;
+       }
+#endif
+       else
+           inc_art(selected_only,FALSE);
+
+#ifdef ARTSEARCH
+       srchahead = 0;
+#endif
+       return AS_NORM;
+    case 'N':                  /* goto next article */
+       if (art > lastart)
+           if (!first_subject) {
+               art = absfirst;
+               artp = article_ptr(art);
+           } else {
+               artp = first_subject->articles;
+               if (artp->flags & AF_MISSING)
+                   inc_art(FALSE,TRUE);
+               else
+                   art = article_num(artp);
+           }
+       else
+           inc_art(FALSE,TRUE);
+       if (art <= lastart)
+           reread = TRUE;
+#ifdef ARTSEARCH
+       srchahead = 0;
+#endif
+       return AS_NORM;
+    case '$':
+       art = lastart+1;
+       artp = Nullart;
+       forcelast = TRUE;
+#ifdef ARTSEARCH
+       srchahead = 0;
+#endif
+       return AS_NORM;
+    case '1': case '2': case '3':      /* goto specified article */
+    case '4': case '5': case '6':      /* or do something with a range */
+    case '7': case '8': case '9': case '.':
+       forcelast = TRUE;
+       switch (numnum()) {
+       case NN_INP:
+           return AS_INP;
+       case NN_ASK:
+           return AS_ASK;
+       case NN_REREAD:
+           reread = TRUE;
+#ifdef ARTSEARCH
+           if (srchahead)
+               srchahead = -1;
+#endif
+           break;
+       case NN_NORM:
+           if (was_read(art)) {
+               top_article();
+               pad(just_a_sec/3);
+           }
+           else {
+               putchar('\n');
+               return AS_ASK;
+           }
+           break;
+       }
+       return AS_NORM;
+    case Ctl('k'):
+       edit_kfile();
+       return AS_ASK;
+    case Ctl('n'):     /* search for next article with same subject */
+    case Ctl('p'):     /* search for previous article with same subject */
+       if (*buf == Ctl('n')? next_art_with_subj() : prev_art_with_subj())
+           return AS_NORM;
+    case '/': case '?':
+normal_search:
+#ifdef ARTSEARCH
+    {          /* search for article by pattern */
+       char cmd = *buf;
+       
+       reread = TRUE;          /* assume this */
+       page_line = 1;
+       switch (art_search(buf, (sizeof buf), TRUE)) {
+       case SRCH_ERROR:
+           art = curr_art;
+           return AS_ASK;
+       case SRCH_ABORT:
+           art = curr_art;
+           return AS_INP;
+       case SRCH_INTR:
+#ifdef VERBOSE
+           IF(verbose)
+               printf("\n(Interrupted at article %ld)\n",(long)art) FLUSH;
+           ELSE
+#endif
+#ifdef TERSE
+               printf("\n(Intr at %ld)\n",(long)art) FLUSH;
+#endif
+           art = curr_art;         /* restore to current article */
+           return AS_ASK;
+       case SRCH_DONE:
+           fputs("done\n",stdout) FLUSH;
+           pad(just_a_sec/3);  /* 1/3 second */
+           if (!srchahead) {
+               art = curr_art;
+               return AS_ASK;
+           }
+           top_article();
+           reread = FALSE;
+           return AS_NORM;
+       case SRCH_SUBJDONE:
+#ifdef UNDEF
+           fputs("\n\n\n\nSubject not found.\n",stdout) FLUSH;
+           pad(just_a_sec/3);  /* 1/3 second */
+#endif
+           top_article();
+           reread = FALSE;
+           return AS_NORM;
+       case SRCH_NOTFOUND:
+           fputs("\n\n\n\nNot found.\n",stdout) FLUSH;
+           art = curr_art;  /* restore to current article */
+           return AS_ASK;
+       case SRCH_FOUND:
+           if (cmd == Ctl('n') || cmd == Ctl('p')) {
+               oldsubject = TRUE;
+               reread = FALSE;
+           }
+           break;
+       }
+       return AS_NORM;
+    }
+#else
+    buf[1] = '\0';
+    notincl(buf);
+    return AS_ASK;
+#endif
+    case 'u':                  /* unsubscribe from this newsgroup? */
+       rcchar[ng] = NEGCHAR;
+       return AS_CLEAN;
+    case 'M':
+       if (art <= lastart) {
+           delay_unmark(artp);
+           oneless(artp);
+           printf("\nArticle %ld will return.\n",(long)art) FLUSH;
+       }
+       return AS_ASK;
+    case 'm':
+       if (art >= absfirst && art <= lastart) {
+           unmark_as_read();
+           printf("\nArticle %ld marked as still unread.\n",(long)art) FLUSH;
+       }
+       return AS_ASK;
+    case 'c':                  /* catch up */
+       switch (ask_catchup()) {
+       case 'n':
+           return AS_ASK;
+       case 'u':
+           return AS_CLEAN;
+       }
+       art = lastart+1;
+       artp = Nullart;
+       forcelast = FALSE;
+       return AS_NORM;
+    case 'Q':
+       exit_code = NG_ASK;
+       /* FALL THROUGH */
+    case 'q':                  /* go back up to newsgroup level? */
+       return AS_CLEAN;
+    case 'j':
+       putchar('\n') FLUSH;
+       if (art >= absfirst && art <= lastart)
+           mark_as_read();
+       return AS_ASK;
+    case 'h': {                        /* help? */
+       int cmd;
+
+       if ((cmd = help_art()) > 0)
+           pushchar(cmd);
+       return AS_ASK;
+    }
+    case '&':
+       if (switcheroo()) /* get rest of command */
+           return AS_INP;      /* if rubbed out, try something else */
+       return AS_ASK;
+    case '#':
+#ifdef VERBOSE
+       IF(verbose)
+           printf("\nThe last article is %ld.\n",(long)lastart) FLUSH;
+       ELSE
+#endif
+#ifdef TERSE
+           printf("\n%ld\n",(long)lastart) FLUSH;
+#endif
+       return AS_ASK;
+    case '+':                  /* enter selection mode */
+run_the_selector:
+       *buf = do_selector(*buf);
+       switch (*buf) {
+       case '+':
+           putchar('\n') FLUSH;
+           return AS_ASK;
+       case 'Q':
+           exit_code = NG_ASK;
+           /* FALL THROUGH */
+       case 'q':
+           break;
+       case 'N':
+           exit_code = NG_SELNEXT;
+           break;
+       case 'P':
+           exit_code = NG_SELPRIOR;
+           break;
+       default:
+           if (toread[ng])
+               return AS_NORM;
+           break;
+       }
+       return AS_CLEAN;
+    case '=': {                        /* list subjects */
+       char tmpbuf[256];
+       ART_NUM oldart = art;
+       int cmd;
+       char *s;
+       char *subjline = getval("SUBJLINE",Nullch);
+       ARTICLE *ap = article_ptr(firstart);
+
+       page_init();
+       for (i=firstart; i<=lastart && !int_count; i++, ap++) {
+           if (!(ap->flags & AF_READ) && (s = fetchsubj(i,FALSE)) != Nullch) {
+               sprintf(tmpbuf,"%5ld ", i);
+               if (subjline) {
+                   art = i;
+                   interp(tmpbuf + 6, (sizeof tmpbuf) - 6, subjline);
+               }
+               else
+                   safecpy(tmpbuf + 6, s, (sizeof tmpbuf) - 6);
+               if (cmd = print_lines(tmpbuf,NOMARKING)) {
+                   if (cmd > 0)
+                       pushchar(cmd);
+                   break;
+               }
+           }
+       }
+       int_count = 0;
+       art = oldart;
+       return AS_ASK;
+    }
+    case '^':
+       top_article();
+#ifdef ARTSEARCH
+       srchahead = 0;
+#endif
+       return AS_NORM;
+#ifdef DEBUG
+    case 'D':
+       printf("\nFirst article: %ld\n",(long)firstart) FLUSH;
+       {
+           ARTICLE *ap = article_ptr(firstart);
+           for (i = firstart; i <= lastart && !int_count; i++, ap++) {
+               if (ap->subj)
+                   printf("%5ld %c %s\n",i,(was_read(i)?'y':'n'),
+                          ap->subj->str) FLUSH;
+           }
+       }
+       int_count = 0;
+       return AS_ASK;
+#endif
+    case 'v':
+       if (art <= lastart) {
+           reread = TRUE;
+           do_hiding = FALSE;
+       }
+       return AS_NORM;
+#ifdef ROTATION
+    case Ctl('x'):
+#endif
+    case Ctl('r'):
+#ifdef ROTATION
+       rotate = (*buf==Ctl('x'));
+#endif
+       if (art <= lastart)
+           reread = TRUE;
+       else
+           forcelast = TRUE;
+       return AS_NORM;
+#ifdef ROTATION
+    case 'X':
+       rotate = !rotate;
+       /* FALL THROUGH */
+#else
+    case Ctl('x'):
+    case 'x':
+    case 'X':
+       notincl("x");
+       return AS_ASK;
+#endif
+    case 'l': case Ctl('l'):           /* refresh screen */
+       if (art <= lastart) {
+           reread = TRUE;
+           clear();
+           do_fseek = TRUE;
+           artline = topline;
+           if (artline < 0)
+               artline = 0;
+       }
+       return AS_NORM;
+    case Ctl('f'):
+       carriage_return();
+       erase_eol();            /* erase the prompt */
+#ifdef MAILCALL
+       setmail(TRUE);          /* force a mail check */
+#endif
+       return AS_ASK;
+    case 'b': case Ctl('b'):           /* back up a page */
+       if (art <= lastart) {
+           ART_LINE target;
+
+           reread = TRUE;
+           clear();
+           do_fseek = TRUE;
+           target = topline - (LINES - 2);
+           artline = topline;
+           if (artline >= 0) do {
+               artline--;
+           } while(artline >= 0 && artline > target && vrdary(artline-1) >= 0);
+           topline = artline;
+           if (artline < 0)
+               artline = 0;
+       }
+       return AS_NORM;
+    case '!':                  /* shell escape */
+       if (escapade())
+           return AS_INP;
+       return AS_ASK;
+    case 'C': {
+       cancel_article();
+       return AS_ASK;
+    }
+    case 'Z':
+    case 'z': {
+       supersede_article();    /* supersedes */
+       return AS_ASK;
+    }
+    case 'R':
+    case 'r': {                        /* reply? */
+       reply();
+       return AS_ASK;
+    }
+    case 'F':
+    case 'f': {                        /* followup command */
+       followup();
+       forcegrow = TRUE;               /* recalculate lastart */
+       return AS_ASK;
+    }
+    case '|':
+    case 'w': case 'W':
+    case 's': case 'S':                /* save command */
+    case 'e':                  /* extract command */
+       if (save_article() == SAVE_ABORT)
+           return AS_INP;
+       int_count = 0;
+       return AS_ASK;
+    case 'E':
+       if (decode_fp)
+           decode_end();
+       else
+           putchar('\n') FLUSH;
+       return AS_ASK;
+    case 'Y':                          /* yank back M articles */
+       yankback();
+       top_article();                  /* from the beginning */
+       return AS_NORM;                 /* pretend nothing happened */
+#ifdef STRICTCR
+    case '\n':
+       fputs(badcr,stdout) FLUSH;
+       return AS_ASK;
+#endif
+    case '_':
+       if (!finish_dblchar())
+           return AS_INP;
+       switch (buf[1] & 0177) {
+       case 'P':
+           art--;
+           goto check_dec_art;
+       case 'N':
+           if (art > lastart)
+               art = absfirst;
+           else
+               art++;
+           if (art <= lastart)
+               reread = TRUE;
+#ifdef ARTSEARCH
+           srchahead = 0;
+#endif
+           return AS_NORM;
+       case '+':
+           if (ThreadedGroup) {
+               select_thread(artp->subj->thread, 0);
+               printf("\nSelected all articles in this thread.\n");
+           } else {
+               select_subject(artp->subj, 0);
+               printf("\nSelected all articles in this subject.\n");
+           }
+           return AS_ASK;
+       case '-':
+           if (sel_mode == SM_THREAD) {
+               deselect_thread(artp->subj->thread);
+               printf("\nDeselected all articles in this thread.\n");
+           } else {
+               deselect_subject(artp->subj);
+               printf("\nDeselected all articles in this subject.\n");
+           }
+           return AS_ASK;
+       case 'a':  case 's':  case 't':  case 'T':
+           *buf = buf[1];
+           goto run_the_selector;
+       }
+       /* FALL THROUGH */
+    default:
+       printf("\n%s",hforhelp) FLUSH;
+       settle_down();
+       break;
+    }
+    return AS_ASK;
+}
+
+#ifdef MAILCALL
+/* see if there is any mail */
+
+void
+setmail(force)
+bool_int force;
+{
+    if (force)
+       mailcount = 0;
+    if (!(mailcount++)) {
+       char *mailfile = filexp(getval("MAILFILE",MAILFILE));
+       
+       if (stat(mailfile,&filestat) < 0 || !filestat.st_size
+           || filestat.st_atime > filestat.st_mtime)
+           mailcall = nullstr;
+       else
+           mailcall = getval("MAILCALL","(Mail) ");
+    }
+    mailcount %= 10;                   /* check every 10 articles */
+}
+#endif
+
+void
+setdfltcmd()
+{
+    if (!toread[ng]) {
+       if (art > lastart)
+           dfltcmd = "qnp";
+       else
+           dfltcmd = "npq";
+    }
+    else {
+#ifdef ARTSEARCH
+       if (srchahead)
+           dfltcmd = "^Nnpq";
+       else
+#endif
+           dfltcmd = "npq";
+    }
+}
+
+/* Ask the user about catching-up the current group.  Returns 'y' if yes,
+** 'n' or 'N' if no ('N' means we used one line when in the selector),
+** or 'u' for yes with unsubscribe.  Actually performs the catchup and
+** unsubscription as needed.
+*/
+char
+ask_catchup()
+{
+    char ch;
+    bool use_one_line = (mode == 't');
+
+    if (!use_one_line)
+       putchar('\n') FLUSH;
+reask_catchup:
+#ifdef VERBOSE
+    IF(verbose)
+       in_char("Do you really want to mark everything as read? [yn] ", 'C');
+    ELSE
+#endif
+#ifdef TERSE
+       in_char("Really? [ynh] ", 'C');
+#endif
+    setdef(buf,"y");
+#ifdef VERIFY
+    printcmd();
+#endif
+    if ((ch = *buf) == 'h') {
+       use_one_line = FALSE;
+#ifdef VERBOSE
+       IF(verbose)
+           fputs("\n\
+Type y or SP to mark all articles as read.\n\
+Type n to leave articles marked as they are.\n\
+Type u to mark everything read and unsubscribe.\n\n\
+",stdout) FLUSH;
+       ELSE
+#endif
+#ifdef TERSE
+           fputs("\n\
+y or SP to mark all read.\n\
+n to forget it.\n\
+u to mark all and unsubscribe.\n\n\
+",stdout) FLUSH;
+#endif
+       goto reask_catchup;
+    }
+    if (ch == 'n' || ch == 'q') {
+       if (use_one_line)
+           return 'N';
+       putchar('\n') FLUSH;
+       return 'n';
+    }
+    if (ch != 'y' && ch != 'u') {
+       use_one_line = FALSE;
+       printf("\n%s\n", hforhelp) FLUSH;
+       settle_down();
+       goto reask_catchup;
+    }
+    if (mode == 'n') {
+       putchar('\n') FLUSH;
+       catch_up(ng);
+    }
+    else {
+       int i;
+       ARTICLE *ap;
+       for (i = firstart, ap = article_ptr(i); i <= lastart; i++, ap++)
+           ap->flags = ((ap->flags & ~sel_mask) | AF_READ);
+       selected_count = selected_subj_cnt = selected_only = 0;
+       toread[ng] = 0;
+       if (dmcount)
+           yankback();
+       putchar('\n') FLUSH;
+    }
+    if (ch == 'u')
+       rcchar[ng] = NEGCHAR;
+    return ch;
+}
+
+char
+ask_memorize(ch)
+char_int ch;
+{
+    bool thread_cmd = (ch == 'T');
+    bool use_one_line = (mode == 't');
+    char *mode_string = (thread_cmd? "thread" : "subject");
+    char *mode_phrase = (thread_cmd? "replies to this article" :
+                                    "this subject and all replies");
+    ART_NUM art_hold = art;
+    ARTICLE *artp_hold = artp;
+
+    if (!use_one_line)
+       putchar('\n') FLUSH;
+    sprintf(cmd_buf,"Memorize %s command: [+.j,cC]", mode_string);
+reask_memorize:
+    in_char(cmd_buf, 'm');
+    setdef(buf,"+");
+#ifdef VERIFY
+    printcmd();
+#endif
+    if ((ch = *buf) == 'h') {
+       use_one_line = FALSE;
+#ifdef VERBOSE
+       IF(verbose)
+           printf("\n\
+Type + or SP to auto-select this %s (i.e. includes future articles).\n\
+Type . to auto-select %s.\n\
+Type j to auto-kill (junk) this %s.\n\
+Type , to auto-kill %s.\n\
+Type c to clear all selection/killing on this %s.\n\
+Type C to clear all selection/killing on %s.\n\
+Type q to abort the operation.\n\n\
+",mode_string,mode_phrase,mode_string,mode_phrase,mode_string,mode_phrase) FLUSH;
+       ELSE
+#endif
+#ifdef TERSE
+           printf("\n\
++ or SP auto-selects this %s.\n\
+. auto-selects %s.\n\
+j auto-kills this %s.\n\
+, auto-kills %s.\n\
+c clears auto-commands for this %s.\n\
+C clears auto-commands for %s.\n\
+q aborts.\n\n\
+",mode_string,mode_phrase,mode_string,mode_phrase,mode_string,mode_phrase) FLUSH;
+#endif
+       goto reask_memorize;
+    }
+    if (ch == 'q') {
+       if (use_one_line)
+           return 'Q';
+       putchar('\n');
+       return 'q';
+    }
+    if (ch == '+') {
+       if (!thread_cmd) {
+           (void)art_search(buf, (sizeof buf), TRUE);
+           art = art_hold;
+           artp = artp_hold;
+           ch = '.';
+       } else
+           ch = (use_one_line? '+' : '.');
+       if (thread_cmd)
+           select_thread(artp->subj->thread, AF_AUTOSELECTALL);
+       else
+           select_subject(artp->subj, 0);
+       if (mode != 't')
+           printf("\nSelection memorized.\n");
+    } else if (ch == '.') {
+       if (!thread_cmd) {
+           (void)art_search(buf, (sizeof buf), TRUE);
+           art = art_hold;
+           artp = artp_hold;
+       } else
+           ch = (use_one_line? '+' : '.');
+       select_subthread(artp,thread_cmd? AF_AUTOSELECT : 0);
+       if (mode != 't')
+           printf("\nSelection memorized.\n");
+    } else if (ch == 'j') {
+       if (!thread_cmd) {
+           *buf = 'K';
+           (void)art_search(buf, (sizeof buf), TRUE);
+           art = art_hold;
+           artp = artp_hold;
+       }
+       if (thread_cmd)
+           kill_thread(artp->subj->thread,KF_ALL|KF_KILLFILE);
+       else
+           kill_subject(artp->subj,KF_ALL);
+       if (mode != 't')
+           printf("\nKill memorized.\n");
+    } else if (ch == ',') {
+       if (!thread_cmd) {
+           (void)art_search(buf, (sizeof buf), TRUE);
+           art = art_hold;
+           artp = artp_hold;
+       }
+       kill_subthread(artp,KF_ALL|(thread_cmd?KF_KILLFILE:0));
+       if (mode != 't')
+           printf("\nKill memorized.\n");
+    } else if (ch == 'c') {
+       if (thread_cmd)
+           clear_thread(artp->subj->thread);
+       else
+           clear_subject(artp->subj);
+    } else if (ch == 'C') {
+       clear_subthread(artp);
+    } else {
+       use_one_line = FALSE;
+       printf("\n%s\n", hforhelp) FLUSH;
+       settle_down();
+       goto reask_memorize;
+    }
+    if (!use_one_line)
+       putchar('\n') FLUSH;
+    return ch;
+}
diff --git a/usr/src/contrib/news/trn3/nghash.c b/usr/src/contrib/news/trn3/nghash.c
new file mode 100644 (file)
index 0000000..cd65f63
--- /dev/null
@@ -0,0 +1,333 @@
+/* $Id: nghash.c,v 3.0 1992/02/01 03:09:32 davison Trn $
+ */
+/*
+ * This software is Copyright 1991 by Stan Barber.
+ *
+ * Permission is hereby granted to copy, reproduce, redistribute or otherwise
+ * use this software as long as: there is no monetary profit gained
+ * specifically from the use or reproduction of this software, it is not
+ * sold, rented, traded or otherwise marketed, and this copyright notice is
+ * included prominently in any copy made.
+ *
+ * The author make no claims as to the fitness or correctness of this software
+ * for any use whatsoever, and it is provided as is. Any use of this software
+ * is at the user's own risk.
+ */
+
+#include "EXTERN.h"
+#include "common.h"
+#include "ndir.h"
+#include "rcstuff.h"
+#include "trn.h"
+#include "intrp.h"
+#include "final.h"
+#include "rcln.h"
+#include "util.h"
+#include "nntp.h"
+#include "ngdata.h"
+#include "hash.h"
+#include "term.h"
+
+static HASHTABLE *acthash;
+static char *actfile;
+
+void
+ngdatansrv_init()              /* non-NNTP initialisation */
+{
+    register long offset;
+    char *cp = filexp(ACTIVE);
+    struct stat actstat;
+
+    actfp = fopen(cp, "r");
+    if (actfp == Nullfp) {
+       printf(cantopen, cp) FLUSH;
+       finalize(1);
+    }
+
+    /* rn was being a slug about rereading the active file over and
+     * over; try using a really big buffer to keep it in core. */
+    (void) fstat(fileno(actfp), &actstat);
+    actfile = safemalloc(actstat.st_size + 1);
+    rewind(actfp);
+    /*
+     * NOTE: this won't work on machines with ints too small to hold
+     * the size of the active file.
+     */
+    if (fread(actfile, 1, (int)actstat.st_size, actfp) != actstat.st_size) {
+       fprintf(stderr, "active file not read\n");
+       actfile[0] = '\0';
+    }
+    rewind(actfp);
+    actfile[actstat.st_size] = '\0';
+
+    acthash = hashcreate((int)(actstat.st_size/40), (int (*)())NULL);
+
+    /* count entries */
+    for (activeitems=0, offset=0; offset < actstat.st_size; activeitems++) {
+       register char *p;
+       HASHDATUM data;
+
+       data.dat_ptr = actfile + offset;
+       if ((p = index(data.dat_ptr, '\n')) == NULL)
+           data.dat_len = actstat.st_size - offset;
+       else
+           data.dat_len = p - data.dat_ptr + 1;
+       if ((p = index(data.dat_ptr, ' ')) == NULL)
+           p = data.dat_ptr;
+       hashstore(acthash, data.dat_ptr, p - data.dat_ptr, data);
+       offset += data.dat_len;
+    }
+}
+
+ACT_POS
+findact(outbuf, nam, len, suggestion)
+register char *outbuf;
+register char *nam;
+register int len;
+register long suggestion;
+{
+    register ACT_POS retval;
+    extern int debug;
+
+    /* see if we know the right place and can just return */
+    if (suggestion != 0 && fseek(actfp, suggestion, 0) >= 0
+     && fgets(outbuf, LBUFLEN, actfp) != NULL && outbuf[len] == ' '
+     && strnEQ(outbuf, nam, len))
+       return suggestion;
+
+    /* hmm, apparently not, gotta look it up. */
+#ifdef DEBUG
+    if (debug & DEB_SOFT_POINTERS)
+       printf("Missed, looking for %s in %sLen = %d\n",nam,outbuf,len) FLUSH;
+#endif
+    /* can we just do a quick hashed lookup? */
+    if (acthash != NULL) {
+       HASHDATUM data;
+
+       outbuf[0] = '\0';
+       data = hashfetch(acthash, nam, len);
+       if (data.dat_ptr == NULL)
+           return -1;
+       else {
+           (void) bcopy(data.dat_ptr, outbuf, (int)data.dat_len);
+           outbuf[data.dat_len] = '\0';
+           return data.dat_ptr - actfile;
+       }
+    }
+
+    /* apparently not.  gotta do it the slow way. */
+    (void) fseek(actfp, 0L, 0);
+    for (retval=0; fgets(outbuf,LBUFLEN,actfp)!=NULL; retval+=strlen(outbuf))
+       if (outbuf[len] == ' ' && strnEQ(outbuf, nam, len))
+           break;
+    if (ferror(actfp)) {
+       perror("error on active file");
+       sig_catcher(0);
+       /* NOTREACHED */
+    } else if (feof(actfp))
+       return -1;      /* no such group */
+    return retval;
+}
+
+#ifdef EDIT_DISTANCE
+
+/* Edit Distance extension to trn
+ *
+ *     Mark Maimone (mwm@cmu.edu)
+ *     Carnegie Mellon Computer Science
+ *     9 May 1993
+ *
+ *     This code helps trn handle typos in newsgroup names much more
+ *   gracefully.  Instead of "... does not exist!!", it will pick the
+ *   nearest one, or offer you a choice if there are several options.
+ */
+
+#define LENGTH_HACK 5  /* Don't bother comparing strings with lengths
+                        * that differ by more than this; making this
+                        * smaller than MIN_DIST prunes the number of
+                        * calls to edit_dist().  We won't catch any
+                        * sequences of MIN_DIST INSERTs or DELETEs,
+                        * but we (should!) save some time. */
+#define MAX_NG 9       /* Maximum number of options */
+
+#define ABS(x) (((x) < 0) ? -(x) : (x))
+
+static void check_distance _((HASHDATUM*,int));
+
+static char **ngptrs;          /* List of potential matches */
+static int ngn;                        /* Length of list in ngptrs[] */
+static int nglen;              /* Length of name in ngname */
+static int best_match;         /* Value of best match */
+
+/* find_close_match -- Finds the closest match for the string given in
+ * global ngname.  If found, the result will be the corrected string
+ * returned in that global.
+ *
+ * We compare the (presumably misspelled) newsgroup name with all legal
+ * newsgroups, using the Edit Distance metric.  The edit distance between
+ * two strings is the minimum number of simple operations required to
+ * convert one string to another (the implementation here supports INSERT,
+ * DELETE, CHANGE and SWAP).  This gives every legal newsgroup an integer
+ * rank.
+ *
+ * You might want to present all of the closest matches, and let the user
+ * choose among them.  But because I'm lazy I chose to only keep track of
+ * all with newsgroups with the *single* smallest error, in array ngptrs[].
+ * A more flexible approach would keep around the 10 best matches, whether
+ * or not they had precisely the same edit distance, but oh well.
+ */
+
+int
+find_close_match()
+{
+   int ret = 0;
+
+    best_match = -1;
+    ngptrs = (char**)safemalloc(MAX_NG * sizeof (char*));
+    ngn = 0;
+    nglen = strlen(ngname);
+
+/* Iterate over all legal newsgroups */
+    hashwalk(acthash, check_distance, 0);
+
+/* ngn is the number of possibilities.  If there's just one, go with it. */
+
+    switch (ngn) {
+        case 0:
+           break;
+       case 1: {
+           char *cp = index(ngptrs[0], ' ');
+           *cp = '\0';
+#ifdef VERBOSE
+           IF(verbose)
+               printf("(I assume you meant %s)\n", ngptrs[0]) FLUSH;
+           ELSE
+#endif
+#ifdef TERSE
+               printf("(Using %s)\n", ngptrs[0]) FLUSH;
+#endif
+           set_ngname(ngptrs[0]);
+           *cp = ' ';
+           ret = 1;
+           break;
+       }
+       default:
+           ret = get_near_miss();
+           break;
+    } /* switch */
+    free((char*)ngptrs);
+    return ret;
+} /* find_close_match */
+
+static void
+check_distance(data, ngnum)
+HASHDATUM *data;
+int ngnum;
+{
+    int value, len;
+    char *cp;
+    if ((cp = index(data->dat_ptr, ' ')) == NULL)
+       return;
+    len = cp - data->dat_ptr;
+
+/* Efficiency:  don't call edit_dist when the lengths are already too
+ * different */
+
+    if (ABS(len - nglen) > LENGTH_HACK)
+       return;
+
+    value = edit_distn(ngname, strlen(ngname), data->dat_ptr, len);
+    if (value > MIN_DIST)
+       return;
+
+    if (value < best_match)
+       ngn = 0;
+    if (best_match < 0 || value <= best_match) {
+       best_match = value;
+       if (ngn < MAX_NG)
+           ngptrs[ngn++] = data->dat_ptr;
+    } /* if */
+}
+
+/* Now we've got several potential matches, and have to choose between them
+ * somehow.  Again, results will be returned in global ngname */
+
+int
+get_near_miss()
+{
+    char promptbuf[256];
+    char options[MAX_NG+10], *op = options;
+    int i;
+
+#ifdef VERBOSE
+    IF(verbose)
+       printf("However, here are some close matches:\n") FLUSH;
+#endif
+    if (ngn > 9)
+       ngn = 9;        /* Since we're using single digits.... */
+    *options = '\0';
+    for (i = 0; i < ngn; i++) {
+       char *cp = index(ngptrs[i], ' ');
+       *cp = '\0';
+       printf("  %d.  %s\n", i+1, ngptrs[i]);
+       sprintf(op++, "%d", i+1);       /* Expensive, but avoids ASCII deps */
+       *cp = ' ';
+    } /* for */
+
+#ifdef VERBOSE
+    IF(verbose)
+       sprintf(promptbuf, "Which of these would you like? [%sn] ", options);
+    ELSE
+#endif
+#ifdef TERSE
+       sprintf(promptbuf, "Which? [%sn] ", options);
+#endif
+reask:
+    in_char(promptbuf, 'A');
+    setdef(buf, "1");
+#ifdef VERIFY
+    printcmd();
+#endif
+    putchar('\n') FLUSH;
+    switch (*buf) {
+        case 'n':
+       case 'N':
+       case 'q':
+       case 'Q':
+       case 'x':
+       case 'X':
+           return 0;
+           break;
+       case 'h':
+       case 'H':
+#ifdef VERBOSE
+           IF(verbose)
+               fputs("  You entered an illegal newsgroup name, and these are the nearest possible\n  matches.  If you want one of these, then enter its number.  Otherwise\n  just say 'n'.\n", stdout) FLUSH;
+           ELSE
+#endif
+#ifdef TERSE
+               fputs("Illegal newsgroup, enter a number or 'n'.\n", stdout) FLUSH;
+#endif
+           goto reask;
+       default:
+           if (isdigit(*buf)) {
+               char *s = index(options, *buf);
+
+               i = s ? (s - options) : ngn;
+               if (i >= 0 && i < ngn) {
+                   char *cp = index(ngptrs[i], ' ');
+                   *cp = '\0';
+                   set_ngname(ngptrs[i]);
+                   *cp = ' ';
+                   return 1;
+               } /* if */
+           } /* if */
+           fputs(hforhelp, stdout) FLUSH;
+           break;
+    } /* switch */
+
+    settle_down();
+    goto reask;
+} /* get_near_miss */
+
+#endif /* EDIT_DISTANCE */
diff --git a/usr/src/contrib/news/trn3/parsedate.y b/usr/src/contrib/news/trn3/parsedate.y
new file mode 100644 (file)
index 0000000..f999333
--- /dev/null
@@ -0,0 +1,811 @@
+%{
+/* $Revision: 1.12 $
+**
+**  Originally written by Steven M. Bellovin <smb@research.att.com> while
+**  at the University of North Carolina at Chapel Hill.  Later tweaked by
+**  a couple of people on Usenet.  Completely overhauled by Rich $alz
+**  <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
+**  Further revised (removed obsolete constructs and cleaned up timezone
+**  names) in August, 1991, by Rich.  Paul Eggert <eggert@twinsun.com>
+**  helped in September, 1992.
+**
+**  This grammar has six shift/reduce conflicts.
+**
+**  This code is in the public domain and has no copyright.
+*/
+/* SUPPRESS 530 *//* Empty body for statement */
+/* SUPPRESS 593 on yyerrlab *//* Label was not used */
+/* SUPPRESS 593 on yynewstate *//* Label was not used */
+/* SUPPRESS 595 on yypvt *//* Automatic variable may be used before set */
+#include <stdio.h>
+#include <sys/types.h>
+#include <ctype.h>
+#include "config.h"
+#include <time.h>
+
+#define yyparse                date_parse
+#define yylex          date_lex
+#define yyerror                date_error
+
+
+    /* See the LeapYears table in Convert. */
+#define EPOCH          1970
+#define END_OF_TIME    2038
+    /* Constants for general time calculations. */
+#define DST_OFFSET     1
+#define SECSPERDAY     (24L * 60L * 60L)
+    /* Readability for TABLE stuff. */
+#define HOUR(x)                (x * 60)
+
+#define LPAREN         '('
+#define RPAREN         ')'
+#define IS7BIT(x)      ((unsigned int)(x) < 0200)
+
+#define SIZEOF(array)  ((int)(sizeof array / sizeof array[0]))
+#define ENDOF(array)   (&array[SIZEOF(array)])
+
+
+/*
+**  An entry in the lexical lookup table.
+*/
+typedef struct _TABLE {
+    char       *name;
+    int                type;
+    time_t     value;
+} TABLE;
+
+/*
+**  Daylight-savings mode:  on, off, or not yet known.
+*/
+typedef enum _DSTMODE {
+    DSTon, DSToff, DSTmaybe
+} DSTMODE;
+
+/*
+**  Meridian:  am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+    MERam, MERpm, MER24
+} MERIDIAN;
+
+
+/*
+**  Global variables.  We could get rid of most of them by using a yacc
+**  union, but this is more efficient.  (This routine predates the
+**  yacc %union construct.)
+*/
+static char    *yyInput;
+static DSTMODE yyDSTmode;
+static int     yyHaveDate;
+static int     yyHaveRel;
+static int     yyHaveTime;
+static time_t  yyTimezone;
+static time_t  yyDay;
+static time_t  yyHour;
+static time_t  yyMinutes;
+static time_t  yyMonth;
+static time_t  yySeconds;
+static time_t  yyYear;
+static MERIDIAN        yyMeridian;
+static time_t  yyRelMonth;
+static time_t  yyRelSeconds;
+
+
+extern struct tm       *localtime();
+
+static void            date_error();
+%}
+
+%union {
+    time_t             Number;
+    enum _MERIDIAN     Meridian;
+}
+
+%token tDAY tDAYZONE tMERIDIAN tMONTH tMONTH_UNIT tSEC_UNIT tSNUMBER
+%token tUNUMBER tZONE
+
+%type  <Number>        tDAYZONE tMONTH tMONTH_UNIT tSEC_UNIT
+%type  <Number>        tSNUMBER tUNUMBER tZONE numzone zone
+%type  <Meridian>      tMERIDIAN o_merid
+
+%%
+
+spec   : /* NULL */
+       | spec item
+       ;
+
+item   : time {
+           yyHaveTime++;
+#ifdef lint
+           /* I am compulsive about lint natterings... */
+           if (yyHaveTime == -1) {
+               YYERROR;
+           }
+#endif /* lint */
+       }
+       | time zone {
+           yyHaveTime++;
+           yyTimezone = $2;
+       }
+       | date {
+           yyHaveDate++;
+       }
+       | rel {
+           yyHaveRel = 1;
+       }
+       ;
+
+time   : tUNUMBER o_merid {
+           if ($1 < 100) {
+               yyHour = $1;
+               yyMinutes = 0;
+           }
+           else {
+               yyHour = $1 / 100;
+               yyMinutes = $1 % 100;
+           }
+           yySeconds = 0;
+           yyMeridian = $2;
+       }
+       | tUNUMBER ':' tUNUMBER o_merid {
+           yyHour = $1;
+           yyMinutes = $3;
+           yySeconds = 0;
+           yyMeridian = $4;
+       }
+       | tUNUMBER ':' tUNUMBER numzone {
+           yyHour = $1;
+           yyMinutes = $3;
+           yyTimezone = $4;
+           yyMeridian = MER24;
+           yyDSTmode = DSToff;
+       }
+       | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
+           yyHour = $1;
+           yyMinutes = $3;
+           yySeconds = $5;
+           yyMeridian = $6;
+       }
+       | tUNUMBER ':' tUNUMBER ':' tUNUMBER numzone {
+           yyHour = $1;
+           yyMinutes = $3;
+           yySeconds = $5;
+           yyTimezone = $6;
+           yyMeridian = MER24;
+           yyDSTmode = DSToff;
+       }
+       ;
+
+zone   : tZONE {
+           $$ = $1;
+           yyDSTmode = DSToff;
+       }
+       | tDAYZONE {
+           $$ = $1;
+           yyDSTmode = DSTon;
+       }
+       | tZONE numzone {
+           /* Only allow "GMT+300" and "GMT-0800" */
+           if ($1 != 0) {
+               YYABORT;
+           }
+           $$ = $2;
+           yyDSTmode = DSToff;
+       }
+       | numzone {
+           $$ = $1;
+           yyDSTmode = DSToff;
+       }
+       ;
+
+numzone        : tSNUMBER {
+           int         i;
+
+           /* Unix and GMT and numeric timezones -- a little confusing. */
+           if ($1 < 0) {
+               /* Don't work with negative modulus. */
+               $1 = -$1;
+               if ($1 > 9999 || (i = $1 % 100) >= 60) {
+                   YYABORT;
+               }
+               $$ = ($1 / 100) * 60 + i;
+           }
+           else {
+               if ($1 > 9999 || (i = $1 % 100) >= 60) {
+                   YYABORT;
+               }
+               $$ = -(($1 / 100) * 60 + i);
+           }
+       }
+       ;
+
+date   : tUNUMBER '/' tUNUMBER {
+           yyMonth = $1;
+           yyDay = $3;
+       }
+       | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
+           if ($1 > 100) {
+               yyYear = $1;
+               yyMonth = $3;
+               yyDay = $5;
+           }
+           else {
+               yyMonth = $1;
+               yyDay = $3;
+               yyYear = $5;
+           }
+       }
+       | tMONTH tUNUMBER {
+           yyMonth = $1;
+           yyDay = $2;
+       }
+       | tMONTH tUNUMBER ',' tUNUMBER {
+           yyMonth = $1;
+           yyDay = $2;
+           yyYear = $4;
+       }
+       | tUNUMBER tMONTH {
+           yyDay = $1;
+           yyMonth = $2;
+       }
+       | tUNUMBER tMONTH tUNUMBER {
+           yyDay = $1;
+           yyMonth = $2;
+           yyYear = $3;
+       }
+       | tDAY ',' tUNUMBER tMONTH tUNUMBER {
+           yyDay = $3;
+           yyMonth = $4;
+           yyYear = $5;
+       }
+       ;
+
+rel    : tSNUMBER tSEC_UNIT {
+           yyRelSeconds += $1 * $2;
+       }
+       | tUNUMBER tSEC_UNIT {
+           yyRelSeconds += $1 * $2;
+       }
+       | tSNUMBER tMONTH_UNIT {
+           yyRelMonth += $1 * $2;
+       }
+       | tUNUMBER tMONTH_UNIT {
+           yyRelMonth += $1 * $2;
+       }
+       ;
+
+o_merid        : /* NULL */ {
+           $$ = MER24;
+       }
+       | tMERIDIAN {
+           $$ = $1;
+       }
+       ;
+
+%%
+
+/* Month and day table. */
+static TABLE   MonthDayTable[] = {
+    { "january",       tMONTH,  1 },
+    { "february",      tMONTH,  2 },
+    { "march",         tMONTH,  3 },
+    { "april",         tMONTH,  4 },
+    { "may",           tMONTH,  5 },
+    { "june",          tMONTH,  6 },
+    { "july",          tMONTH,  7 },
+    { "august",                tMONTH,  8 },
+    { "september",     tMONTH,  9 },
+    { "october",       tMONTH, 10 },
+    { "november",      tMONTH, 11 },
+    { "december",      tMONTH, 12 },
+       /* The value of the day isn't used... */
+    { "sunday",                tDAY, 0 },
+    { "monday",                tDAY, 0 },
+    { "tuesday",       tDAY, 0 },
+    { "wednesday",     tDAY, 0 },
+    { "thursday",      tDAY, 0 },
+    { "friday",                tDAY, 0 },
+    { "saturday",      tDAY, 0 },
+};
+
+/* Time units table. */
+static TABLE   UnitsTable[] = {
+    { "year",          tMONTH_UNIT,    12 },
+    { "month",         tMONTH_UNIT,    1 },
+    { "week",          tSEC_UNIT,      7L * 24 * 60 * 60 },
+    { "day",           tSEC_UNIT,      1L * 24 * 60 * 60 },
+    { "hour",          tSEC_UNIT,      60 * 60 },
+    { "minute",                tSEC_UNIT,      60 },
+    { "min",           tSEC_UNIT,      60 },
+    { "second",                tSEC_UNIT,      1 },
+    { "sec",           tSEC_UNIT,      1 },
+};
+
+/* Timezone table. */
+static TABLE   TimezoneTable[] = {
+    { "gmt",   tZONE,     HOUR( 0) },  /* Greenwich Mean */
+    { "ut",    tZONE,     HOUR( 0) },  /* Universal */
+    { "utc",   tZONE,     HOUR( 0) },  /* Universal Coordinated */
+    { "cut",   tZONE,     HOUR( 0) },  /* Coordinated Universal */
+    { "z",     tZONE,     HOUR( 0) },  /* Greenwich Mean */
+    { "wet",   tZONE,     HOUR( 0) },  /* Western European */
+    { "bst",   tDAYZONE,  HOUR( 0) },  /* British Summer */
+    { "nst",   tZONE,     HOUR(3)+30 }, /* Newfoundland Standard */
+    { "ndt",   tDAYZONE,  HOUR(3)+30 }, /* Newfoundland Daylight */
+    { "ast",   tZONE,     HOUR( 4) },  /* Atlantic Standard */
+    { "adt",   tDAYZONE,  HOUR( 4) },  /* Atlantic Daylight */
+    { "est",   tZONE,     HOUR( 5) },  /* Eastern Standard */
+    { "edt",   tDAYZONE,  HOUR( 5) },  /* Eastern Daylight */
+    { "cst",   tZONE,     HOUR( 6) },  /* Central Standard */
+    { "cdt",   tDAYZONE,  HOUR( 6) },  /* Central Daylight */
+    { "mst",   tZONE,     HOUR( 7) },  /* Mountain Standard */
+    { "mdt",   tDAYZONE,  HOUR( 7) },  /* Mountain Daylight */
+    { "pst",   tZONE,     HOUR( 8) },  /* Pacific Standard */
+    { "pdt",   tDAYZONE,  HOUR( 8) },  /* Pacific Daylight */
+    { "yst",   tZONE,     HOUR( 9) },  /* Yukon Standard */
+    { "ydt",   tDAYZONE,  HOUR( 9) },  /* Yukon Daylight */
+    { "akst",  tZONE,     HOUR( 9) },  /* Alaska Standard */
+    { "akdt",  tDAYZONE,  HOUR( 9) },  /* Alaska Daylight */
+    { "hst",   tZONE,     HOUR(10) },  /* Hawaii Standard */
+    { "hast",  tZONE,     HOUR(10) },  /* Hawaii-Aleutian Standard */
+    { "hadt",  tDAYZONE,  HOUR(10) },  /* Hawaii-Aleutian Daylight */
+    { "ces",   tDAYZONE,  -HOUR(1) },  /* Central European Summer */
+    { "cest",  tDAYZONE,  -HOUR(1) },  /* Central European Summer */
+    { "mez",   tZONE,     -HOUR(1) },  /* Middle European */
+    { "mezt",  tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
+    { "cet",   tZONE,     -HOUR(1) },  /* Central European */
+    { "met",   tZONE,     -HOUR(1) },  /* Middle European */
+    { "eet",   tZONE,     -HOUR(2) },  /* Eastern Europe */
+    { "msk",   tZONE,     -HOUR(3) },  /* Moscow Winter */
+    { "msd",   tDAYZONE,  -HOUR(3) },  /* Moscow Summer */
+    { "wast",  tZONE,     -HOUR(8) },  /* West Australian Standard */
+    { "wadt",  tDAYZONE,  -HOUR(8) },  /* West Australian Daylight */
+    { "hkt",   tZONE,     -HOUR(8) },  /* Hong Kong */
+    { "cct",   tZONE,     -HOUR(8) },  /* China Coast */
+    { "jst",   tZONE,     -HOUR(9) },  /* Japan Standard */
+    { "kst",   tZONE,     -HOUR(9) },  /* Korean Standard */
+    { "kdt",   tZONE,     -HOUR(9) },  /* Korean Daylight */
+    { "cast",  tZONE,     -(HOUR(9)+30) }, /* Central Australian Standard */
+    { "cadt",  tDAYZONE,  -(HOUR(9)+30) }, /* Central Australian Daylight */
+    { "east",  tZONE,     -HOUR(10) }, /* Eastern Australian Standard */
+    { "eadt",  tDAYZONE,  -HOUR(10) }, /* Eastern Australian Daylight */
+    { "nzst",  tZONE,     -HOUR(12) }, /* New Zealand Standard */
+    { "nzdt",  tDAYZONE,  -HOUR(12) }, /* New Zealand Daylight */
+
+    /* For completeness we include the following entries. */
+#if 0
+
+    /* Duplicate names.  Either they conflict with a zone listed above
+     * (which is either more likely to be seen or just been in circulation
+     * longer), or they conflict with another zone in this section and
+     * we could not reasonably choose one over the other. */
+    { "fst",   tZONE,     HOUR( 2) },  /* Fernando De Noronha Standard */
+    { "fdt",   tDAYZONE,  HOUR( 2) },  /* Fernando De Noronha Daylight */
+    { "bst",   tZONE,     HOUR( 3) },  /* Brazil Standard */
+    { "est",   tZONE,     HOUR( 3) },  /* Eastern Standard (Brazil) */
+    { "edt",   tDAYZONE,  HOUR( 3) },  /* Eastern Daylight (Brazil) */
+    { "wst",   tZONE,     HOUR( 4) },  /* Western Standard (Brazil) */
+    { "wdt",   tDAYZONE,  HOUR( 4) },  /* Western Daylight (Brazil) */
+    { "cst",   tZONE,     HOUR( 5) },  /* Chile Standard */
+    { "cdt",   tDAYZONE,  HOUR( 5) },  /* Chile Daylight */
+    { "ast",   tZONE,     HOUR( 5) },  /* Acre Standard */
+    { "adt",   tDAYZONE,  HOUR( 5) },  /* Acre Daylight */
+    { "cst",   tZONE,     HOUR( 5) },  /* Cuba Standard */
+    { "cdt",   tDAYZONE,  HOUR( 5) },  /* Cuba Daylight */
+    { "est",   tZONE,     HOUR( 6) },  /* Easter Island Standard */
+    { "edt",   tDAYZONE,  HOUR( 6) },  /* Easter Island Daylight */
+    { "sst",   tZONE,     HOUR(11) },  /* Samoa Standard */
+    { "ist",   tZONE,     -HOUR(2) },  /* Israel Standard */
+    { "idt",   tDAYZONE,  -HOUR(2) },  /* Israel Daylight */
+    { "idt",   tDAYZONE,  -(HOUR(3)+30) }, /* Iran Daylight */
+    { "ist",   tZONE,     -(HOUR(3)+30) }, /* Iran Standard */
+    { "cst",    tZONE,     -HOUR(8) }, /* China Standard */
+    { "cdt",    tDAYZONE,  -HOUR(8) }, /* China Daylight */
+    { "sst",    tZONE,     -HOUR(8) }, /* Singapore Standard */
+
+    /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
+    { "gst",   tZONE,     HOUR( 3) },  /* Greenland Standard */
+    { "wat",   tZONE,     -HOUR(1) },  /* West Africa */
+    { "at",    tZONE,     HOUR( 2) },  /* Azores */
+    { "gst",   tZONE,     -HOUR(10) }, /* Guam Standard */
+    { "nft",   tZONE,     HOUR(3)+30 }, /* Newfoundland */
+    { "idlw",  tZONE,     HOUR(12) },  /* International Date Line West */
+    { "mewt",  tZONE,     -HOUR(1) },  /* Middle European Winter */
+    { "mest",  tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
+    { "swt",   tZONE,     -HOUR(1) },  /* Swedish Winter */
+    { "sst",   tDAYZONE,  -HOUR(1) },  /* Swedish Summer */
+    { "fwt",   tZONE,     -HOUR(1) },  /* French Winter */
+    { "fst",   tDAYZONE,  -HOUR(1) },  /* French Summer */
+    { "bt",    tZONE,     -HOUR(3) },  /* Baghdad */
+    { "it",    tZONE,     -(HOUR(3)+30) }, /* Iran */
+    { "zp4",   tZONE,     -HOUR(4) },  /* USSR Zone 3 */
+    { "zp5",   tZONE,     -HOUR(5) },  /* USSR Zone 4 */
+    { "ist",   tZONE,     -(HOUR(5)+30) }, /* Indian Standard */
+    { "zp6",   tZONE,     -HOUR(6) },  /* USSR Zone 5 */
+    { "nst",   tZONE,     -HOUR(7) },  /* North Sumatra */
+    { "sst",   tZONE,     -HOUR(7) },  /* South Sumatra */
+    { "jt",    tZONE,     -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
+    { "nzt",   tZONE,     -HOUR(12) }, /* New Zealand */
+    { "idle",  tZONE,     -HOUR(12) }, /* International Date Line East */
+    { "cat",   tZONE,     HOUR(10) },  /* -- expired 1967 */
+    { "nt",    tZONE,     HOUR(11) },  /* -- expired 1967 */
+    { "ahst",  tZONE,     HOUR(10) },  /* -- expired 1983 */
+    { "hdt",   tDAYZONE,  HOUR(10) },  /* -- expired 1986 */
+#endif /* 0 */
+};
+
+
+/* ARGSUSED */
+static void
+date_error(s)
+    char       *s;
+{
+    /* NOTREACHED */
+}
+
+
+static time_t
+ToSeconds(Hours, Minutes, Seconds, Meridian)
+    time_t     Hours;
+    time_t     Minutes;
+    time_t     Seconds;
+    MERIDIAN   Meridian;
+{
+    if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
+       return -1;
+    if (Meridian == MER24) {
+       if (Hours < 0 || Hours > 23)
+           return -1;
+    }
+    else {
+       if (Hours < 1 || Hours > 12)
+           return -1;
+       if (Hours == 12)
+           Hours = 0;
+       if (Meridian == MERpm)
+           Hours += 12;
+    }
+    return (Hours * 60L + Minutes) * 60L + Seconds;
+}
+
+
+static time_t
+Convert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, dst)
+    time_t     Month;
+    time_t     Day;
+    time_t     Year;
+    time_t     Hours;
+    time_t     Minutes;
+    time_t     Seconds;
+    MERIDIAN   Meridian;
+    DSTMODE    dst;
+{
+    static int DaysNormal[13] = {
+       0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+    };
+    static int DaysLeap[13] = {
+       0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+    };
+    static int LeapYears[] = {
+       1972, 1976, 1980, 1984, 1988, 1992, 1996,
+       2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
+    };
+    register int       *yp;
+    register int       *mp;
+    register time_t    Julian;
+    register int       i;
+    time_t             tod;
+
+    if (Year < 0)
+       Year = -Year;
+    if (Year < 100)
+       Year += 1900;
+    if (Year < EPOCH)
+       Year += 100;
+    for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
+       if (Year == *yp) {
+           mp = DaysLeap;
+           break;
+       }
+    if (Year < EPOCH || Year > END_OF_TIME
+     || Month < 1 || Month > 12
+     /* NOSTRICT *//* conversion from long may lose accuracy */
+     || Day < 1 || Day > mp[(int)Month])
+       return -1;
+
+    Julian = Day - 1 + (Year - EPOCH) * 365;
+    for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++)
+       if (Year <= *yp)
+           break;
+    for (i = 1; i < Month; i++)
+       Julian += *++mp;
+    Julian *= SECSPERDAY;
+    Julian += yyTimezone * 60L;
+    if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
+       return -1;
+    Julian += tod;
+    tod = Julian;
+    if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
+       Julian -= DST_OFFSET * 60L * 60L;
+    return Julian;
+}
+
+
+static time_t
+DSTcorrect(Start, Future)
+    time_t     Start;
+    time_t     Future;
+{
+    time_t     StartDay;
+    time_t     FutureDay;
+
+    StartDay = (localtime(&Start)->tm_hour + 1) % 24;
+    FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
+    return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60L * 60L;
+}
+
+
+static time_t
+RelativeMonth(Start, RelMonth)
+    time_t     Start;
+    time_t     RelMonth;
+{
+    struct tm  *tm;
+    time_t     Month;
+    time_t     Year;
+
+    tm = localtime(&Start);
+    Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
+    Year = Month / 12;
+    Month = Month % 12 + 1;
+    return DSTcorrect(Start,
+           Convert(Month, (time_t)tm->tm_mday, Year,
+               (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
+               MER24, DSTmaybe));
+}
+
+
+static int
+LookupWord(buff, length)
+    char               *buff;
+    register int       length;
+{
+    register char      *p;
+    register char      *q;
+    register TABLE     *tp;
+    register int       c;
+
+    p = buff;
+    c = p[0];
+
+    /* See if we have an abbreviation for a month. */
+    if (length == 3 || (length == 4 && p[3] == '.'))
+       for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
+           q = tp->name;
+           if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+       }
+    else
+       for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++)
+           if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+
+    /* Try for a timezone. */
+    for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
+       if (c == tp->name[0] && p[1] == tp->name[1]
+        && strcmp(p, tp->name) == 0) {
+           yylval.Number = tp->value;
+           return tp->type;
+       }
+
+    /* Try the units table. */
+    for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
+       if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+           yylval.Number = tp->value;
+           return tp->type;
+       }
+
+    /* Strip off any plural and try the units table again. */
+    if (--length > 0 && p[length] == 's') {
+       p[length] = '\0';
+       for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
+           if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+               p[length] = 's';
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+       p[length] = 's';
+    }
+    length++;
+
+    /* Drop out any periods. */
+    for (p = buff, q = (char*)buff; *q; q++)
+       if (*q != '.')
+           *p++ = *q;
+    *p = '\0';
+
+    /* Try the meridians. */
+    if (buff[1] == 'm' && buff[2] == '\0') {
+       if (buff[0] == 'a') {
+           yylval.Meridian = MERam;
+           return tMERIDIAN;
+       }
+       if (buff[0] == 'p') {
+           yylval.Meridian = MERpm;
+           return tMERIDIAN;
+       }
+    }
+
+    /* If we saw any periods, try the timezones again. */
+    if (p - buff != length) {
+       c = buff[0];
+       for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
+           if (c == tp->name[0] && p[1] == tp->name[1]
+           && strcmp(p, tp->name) == 0) {
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+    }
+
+    /* Unknown word -- assume GMT timezone. */
+    yylval.Number = 0;
+    return tZONE;
+}
+
+
+int
+date_lex()
+{
+    register char      c;
+    register char      *p;
+    char               buff[20];
+    register int       sign;
+    register int       i;
+    register int       nesting;
+
+    for ( ; ; ) {
+       /* Get first character after the whitespace. */
+       for ( ; ; ) {
+           while (isspace(*yyInput))
+               yyInput++;
+           c = *yyInput;
+
+           /* Ignore RFC 822 comments, typically time zone names. */
+           if (c != LPAREN)
+               break;
+           for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
+               if (c == LPAREN)
+                   nesting++;
+               else if (!IS7BIT(c) || c == '\0' || c == '\r'
+                    || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c))))
+                   /* Lexical error: bad comment. */
+                   return '?';
+           yyInput++;
+       }
+
+       /* A number? */
+       if (isdigit(c) || c == '-' || c == '+') {
+           if (c == '-' || c == '+') {
+               sign = c == '-' ? -1 : 1;
+               yyInput++;
+               if (!isdigit(*yyInput))
+                   /* Skip the plus or minus sign. */
+                   continue;
+           }
+           else
+               sign = 0;
+           for (i = 0; (c = *yyInput++) != '\0' && isdigit(c); )
+               i = 10 * i + c - '0';
+           yyInput--;
+           yylval.Number = sign < 0 ? -i : i;
+           return sign ? tSNUMBER : tUNUMBER;
+       }
+
+       /* A word? */
+       if (isalpha(c)) {
+           for (p = buff; (c = *yyInput++) == '.' || isalpha(c); )
+               if (p < &buff[sizeof buff - 1])
+                   *p++ = isupper(c) ? tolower(c) : c;
+           *p = '\0';
+           yyInput--;
+           return LookupWord(buff, p - buff);
+       }
+
+       return *yyInput++;
+    }
+}
+
+
+time_t
+parsedate(p)
+    char               *p;
+{
+    extern int         date_parse();
+    time_t             Start;
+
+    yyInput = p;
+
+    yyYear = 0;
+    yyMonth = 0;
+    yyDay = 0;
+    yyTimezone = 0;
+    yyDSTmode = DSTmaybe;
+    yyHour = 0;
+    yyMinutes = 0;
+    yySeconds = 0;
+    yyMeridian = MER24;
+    yyRelSeconds = 0;
+    yyRelMonth = 0;
+    yyHaveDate = 0;
+    yyHaveRel = 0;
+    yyHaveTime = 0;
+
+    if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
+       return -1;
+
+    if (yyHaveDate || yyHaveTime) {
+       Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
+                   yyMeridian, yyDSTmode);
+       if (Start < 0)
+           return -1;
+    }
+    else
+       return -1;
+
+    Start += yyRelSeconds;
+    if (yyRelMonth)
+       Start += RelativeMonth(Start, yyRelMonth);
+
+    /* Have to do *something* with a legitimate -1 so it's distinguishable
+     * from the error return value.  (Alternately could set errno on error.) */
+    return Start == -1 ? 0 : Start;
+}
+
+
+#ifdef TEST
+
+#if YYDEBUG
+extern int     yydebug;
+#endif /* YYDEBUG */
+
+/* ARGSUSED */
+int
+main(ac, av)
+    int                ac;
+    char       *av[];
+{
+    char       buff[128];
+    time_t     d;
+
+#if YYDEBUG
+    yydebug = 1;
+#endif /* YYDEBUG */
+
+    (void)printf("Enter date, or blank line to exit.\n\t> ");
+    for ( ; ; ) {
+       (void)printf("\t> ");
+       (void)fflush(stdout);
+       if (gets(buff) == NULL || buff[0] == '\n')
+           break;
+#if YYDEBUG
+       if (strcmp(buff, "yydebug") == 0) {
+           yydebug = !yydebug;
+           printf("yydebug = %s\n", yydebug ? "on" : "off");
+           continue;
+       }
+#endif /* YYDEBUG */
+       d = parsedate(buff, (TIMEINFO *)NULL);
+       if (d == -1)
+           (void)printf("Bad format - couldn't convert.\n");
+       else
+           (void)printf("%s", ctime(&d));
+    }
+
+    exit(0);
+    /* NOTREACHED */
+}
+#endif /* TEST */
diff --git a/usr/src/contrib/news/trn3/rcstuff.c b/usr/src/contrib/news/trn3/rcstuff.c
new file mode 100644 (file)
index 0000000..30a4290
--- /dev/null
@@ -0,0 +1,1068 @@
+/* $Id: rcstuff.c,v 3.0 1992/02/01 03:09:32 davison Trn $
+ */
+/* This software is Copyright 1991 by Stan Barber. 
+ *
+ * Permission is hereby granted to copy, reproduce, redistribute or otherwise
+ * use this software as long as: there is no monetary profit gained
+ * specifically from the use or reproduction of this software, it is not
+ * sold, rented, traded or otherwise marketed, and this copyright notice is
+ * included prominently in any copy made. 
+ *
+ * The author make no claims as to the fitness or correctness of this software
+ * for any use whatsoever, and it is provided as is. Any use of this software
+ * is at the user's own risk. 
+ */
+
+#include "EXTERN.h"
+#include "common.h"
+#include "util.h"
+#include "cache.h"
+#include "bits.h"
+#include "ngdata.h"
+#include "term.h"
+#include "final.h"
+#include "trn.h"
+#include "intrp.h"
+#include "only.h"
+#include "rcln.h"
+#include "nntp.h"
+#include "autosub.h"
+#include "hash.h"
+#include "INTERN.h"
+#include "rcstuff.h"
+
+char *rcname INIT(Nullch);             /* path name of .newsrc file */
+char *rctname INIT(Nullch);            /* path name of temp .newsrc file */
+char *rcbname INIT(Nullch);            /* path name of backup .newsrc file */
+char *softname INIT(Nullch);           /* path name of .rnsoft file */
+FILE *rcfp INIT(Nullfp);               /* .newsrc file pointer */
+
+static void grow_rc_arrays _((int));
+static void parse_rcline _((NG_NUM));
+
+#ifdef HASHNG
+static HASHTABLE *rc_hash;
+static int rcline_cmp _((char*,int,HASHDATUM));
+static void del_rc_line _((HASHDATUM*,int));
+static void ins_rc_line _((HASHDATUM*,int));
+#endif
+
+bool
+rcstuff_init()
+{
+    register NG_NUM newng;
+    register int i;
+    register bool foundany = FALSE;
+    char *some_buf;
+    long length;
+#ifdef USE_NNTP
+    char *cp;
+#endif
+
+    /* make filenames */
+
+#ifdef USE_NNTP
+    if (cp = getenv("NEWSRC"))
+       rcname = savestr(filexp(cp));
+    else
+       rcname = savestr(filexp(RCNAME));
+#else
+    rcname = savestr(filexp(RCNAME));
+#endif
+
+    rctname = savestr(filexp(RCTNAME));
+    rcbname = savestr(filexp(RCBNAME));
+    softname = savestr(filexp(SOFTNAME));
+    
+    /* make sure the .newsrc file exists */
+
+    newsrc_check();
+
+    /* open .rnsoft file containing soft ptrs to active file */
+
+    tmpfp = fopen(softname,"r");
+    if (tmpfp == Nullfp)
+       writesoft = TRUE;
+
+    /* allocate memory for rc file globals */
+    grow_rc_arrays(1500);
+
+    /* read in the .newsrc file */
+
+    for (nextrcline = 0;
+       (some_buf = get_a_line(buf,LBUFLEN,rcfp)) != Nullch;
+       nextrcline++)                   /* for each line in .newsrc */
+    {
+       char tmpbuf[10];
+
+       newng = nextrcline;             /* get it into a register */
+       length = len_last_line_got;     /* side effect of get_a_line */
+       if (length <= 1) {              /* only a newline??? */
+           nextrcline--;               /* compensate for loop increment */
+           continue;
+       }
+       if (newng >= maxrcline)         /* check for overflow */
+           grow_rc_arrays(maxrcline + 500);
+       if (tmpfp != Nullfp && fgets(tmpbuf,10,tmpfp) != Nullch)
+           softptr[newng] = atol(tmpbuf);
+       else
+           softptr[newng] = 0;
+       some_buf[--length] = '\0';      /* wipe out newline */
+       if (checkflag)                  /* no extra mallocs for -c */
+           rcline[newng] = some_buf;
+       else if (some_buf == buf)
+           rcline[newng] = savestr(some_buf);  /* make semipermanent copy */
+       else {
+           /*NOSTRICT*/
+#ifndef lint
+           some_buf = saferealloc(some_buf,(MEM_SIZE)(length+1));
+#endif
+           rcline[newng] = some_buf;
+       }
+       if (*some_buf == ' ' || *some_buf == '\t'
+        || strnEQ(some_buf,"options",7)) {     /* non-useful line? */
+           toread[newng] = TR_JUNK;
+           rcchar[newng] = ' ';
+           rcnums[newng] = 0;
+           continue;
+       }
+       parse_rcline(newng);
+       if (rcchar[newng] == NEGCHAR) {
+           toread[newng] = TR_UNSUB;
+           continue;
+       }
+
+       /* now find out how much there is to read */
+
+       if (!inlist(buf) || (suppress_cn && foundany && !paranoid))
+           toread[newng] = TR_NONE;    /* no need to calculate now */
+       else
+           set_toread(newng);
+#ifdef VERBOSE
+       if (!checkflag && softmisses == 1) {
+           softmisses++;               /* lie a little */
+           fputs("(Revising soft pointers -- be patient.)\n",stdout) FLUSH;
+       }
+#endif
+       if (toread[newng] > TR_NONE) {  /* anything unread? */
+           if (!foundany) {
+               starthere = newng;
+               foundany = TRUE;        /* remember that fact*/
+           }
+           if (suppress_cn) {          /* if no listing desired */
+               if (checkflag) {        /* if that is all they wanted */
+                   finalize(1);        /* then bomb out */
+               }
+           }
+           else {
+#ifdef VERBOSE
+               IF(verbose)
+                   printf("Unread news in %-40s %5ld article%s\n",
+                       rcline[newng],(long)toread[newng],
+                       toread[newng]==TR_ONE ? nullstr : "s") FLUSH;
+               ELSE
+#endif
+#ifdef TERSE
+                   printf("%s: %ld article%s\n",
+                       rcline[newng],(long)toread[newng],
+                       toread[newng]==TR_ONE ? nullstr : "s") FLUSH;
+#endif
+               if (int_count) {
+                   countdown = 1;
+                   int_count = 0;
+               }
+               if (countdown) {
+                   if (!--countdown) {
+                       fputs("etc.\n",stdout) FLUSH;
+                       if (checkflag)
+                           finalize(1);
+                       suppress_cn = TRUE;
+                   }
+               }
+           }
+       }
+    }
+    fclose(rcfp);                      /* close .newsrc */
+    if (tmpfp != Nullfp)
+       fclose(tmpfp);                  /* close .rnsoft */
+    if (checkflag)                     /* were we just checking? */
+       finalize(foundany);             /* tell them what we found */
+    if (paranoid)
+       cleanup_rc();
+
+#ifdef HASHNG
+    rc_hash = hashcreate((int)nextrcline, rcline_cmp);
+    for (i = 0; i < nextrcline; i++)
+       if (toread[i] >= TR_UNSUB)
+           sethash(i);
+#endif
+
+    return foundany;
+}
+
+static void
+parse_rcline(ngnum)
+NG_NUM ngnum;
+{
+    char *s;
+
+    for (s = rcline[ngnum]; *s && *s != ':' && *s != NEGCHAR; s++) ;
+    if (!*s && !checkflag) {
+#ifndef lint
+       rcline[ngnum] = saferealloc(rcline[ngnum],
+                               (MEM_SIZE)(s - rcline[ngnum]) + 3);
+#endif /* lint */
+       strcpy(s, ": ");
+    }
+    if (*s == ':' && s[1] && s[2] == '0') {
+       rcchar[ngnum] = '0';
+       s[2] = '1';
+    } else
+       rcchar[ngnum] = *s;     /* salt away the : or ! */
+    rcnums[ngnum] = (char)(s - rcline[ngnum]) + 1;
+                               /* remember where the numbers are */
+    *s = '\0';                 /* null terminate newsgroup name */
+}
+
+void
+abandon_ng(ngnum)
+NG_NUM ngnum;
+{
+    char *some_buf = Nullch;
+
+    /* open .oldnewsrc and try to find the prior value for the group. */
+    if ((rcfp = fopen(rcbname, "r")) != Nullfp) {
+       int length = rcnums[ngnum] - 1;
+
+       while ((some_buf = get_a_line(buf,LBUFLEN,rcfp)) != Nullch) {
+           if (len_last_line_got <= 0)
+               continue;
+           some_buf[len_last_line_got-1] = '\0';       /* wipe out newline */
+           if ((some_buf[length] == ':' || some_buf[length] == NEGCHAR)
+            && strnEQ(rcline[ngnum], some_buf, length)) {
+               break;
+           }
+           if (some_buf != buf)
+               free(some_buf);
+       }
+       fclose(rcfp);
+    } else if (errno != ENOENT) {
+       printf("Unable to open %s.\n", rcbname) FLUSH;
+       return;
+    }
+    if (some_buf == Nullch) {
+       some_buf = rcline[ngnum] + rcnums[ngnum];
+       if (*some_buf == ' ')
+           some_buf++;
+       *some_buf = '\0';
+       abs1st[ngnum] = 0;      /* force group to be re-calculated */
+    }
+    else {
+       free(rcline[ngnum]);
+       if (some_buf == buf) {
+           rcline[ngnum] = savestr(some_buf);
+       }
+       else {
+           /*NOSTRICT*/
+#ifndef lint
+           some_buf = saferealloc(some_buf, (MEM_SIZE)(len_last_line_got));
+#endif /* lint */
+           rcline[ngnum] = some_buf;
+       }
+    }
+    parse_rcline(ngnum);
+    if (rcchar[ngnum] == NEGCHAR)
+       rcchar[ngnum] = ':';
+    set_toread(ngnum);
+}
+
+/* try to find or add an explicitly specified newsgroup */
+/* returns TRUE if found or added, FALSE if not. */
+/* assumes that we are chdir'ed to NEWSSPOOL */
+
+bool
+get_ng(what,flags)
+char *what;
+int flags;
+{
+    char *ntoforget;
+    char promptbuf[128];
+    int autosub;
+
+#ifdef VERBOSE
+    IF(verbose)
+       ntoforget = "Type n to forget about this newsgroup.\n";
+    ELSE
+#endif
+#ifdef TERSE
+       ntoforget = "n to forget it.\n";
+#endif
+    if (index(what,'/')) {
+       dingaling();
+       printf("\nBad newsgroup name.\n") FLUSH;
+      check_fuzzy_match:
+       if (fuzzyGet && (flags & GNG_FUZZY)) {
+           if (find_close_match())
+               what = ngname;
+           else
+               return FALSE;
+       } else
+           return FALSE;
+    }
+    set_ngname(what);
+    ng = find_ng(ngname);
+    if (ng == nextrcline) {            /* not in .newsrc? */
+       if (ng >= maxrcline)            /* check for overflow */
+           grow_rc_arrays(maxrcline + 25);
+#ifdef USE_NNTP
+       softptr[ng] = 0;
+       if (!nntp_group(ngname))
+#else /* !USE_NNTP */
+       if ((softptr[ng] = findact(buf,ngname,strlen(ngname),0L)) < 0)
+#endif /* !USE_NNTP */
+       {
+           dingaling();
+#ifdef VERBOSE
+           IF(verbose)
+               printf("\nNewsgroup %s does not exist!\n",ngname) FLUSH;
+           ELSE
+#endif
+#ifdef TERSE
+               printf("\nNo %s!\n",ngname) FLUSH;
+#endif
+           if (novice_delays)
+               sleep(2);
+           goto check_fuzzy_match;
+       }
+       autosub = auto_subscribe(ngname);
+       if (!autosub) autosub = addnewbydefault;
+       if (autosub) {
+           if (append_unsub) {
+               printf("(Adding %s to end of your .newsrc %ssubscribed)\n",
+                      ngname, (autosub == ADDNEW_SUB) ? "" : "un") FLUSH;
+               ng = add_newsgroup(ngname, autosub);
+           } else {
+               if (autosub == ADDNEW_SUB) {
+                   printf("(Subscribing to %s)\n", ngname) FLUSH;
+                   ng = add_newsgroup(ngname, autosub);
+               } else {
+                   printf("(Ignoring %s)\n", ngname) FLUSH;
+                   return FALSE;
+               }
+           }
+           flags &= ~GNG_RELOC;
+       } else {
+#ifdef VERBOSE
+       IF(verbose)
+           sprintf(promptbuf,"\nNewsgroup %s not in .newsrc -- subscribe? [ynYN] ",ngname);
+       ELSE
+#endif
+#ifdef TERSE
+           sprintf(promptbuf,"\nSubscribe %s? [ynYN] ",ngname);
+#endif
+reask_add:
+       in_char(promptbuf,'A');
+       setdef(buf,"y");
+#ifdef VERIFY
+       printcmd();
+#endif
+       putchar('\n') FLUSH;
+       if (*buf == 'h') {
+#ifdef VERBOSE
+           IF(verbose)
+               printf("Type y or SP to subscribe to %s.\n\
+Type Y to subscribe to this and all remaining new groups.\n\
+Type N to leave all remaining new groups unsubscribed.\n", ngname)
+                 FLUSH;
+           ELSE
+#endif
+#ifdef TERSE
+               fputs("y or SP to subscribe, Y to subscribe all new groups, N to unsubscribe all\n",stdout) FLUSH;
+#endif
+           fputs(ntoforget,stdout) FLUSH;
+           goto reask_add;
+       }
+       else if (*buf == 'n' || *buf == 'q') {
+           if (append_unsub) {
+               ng = add_newsgroup(ngname, NEGCHAR);
+           }
+           return FALSE;
+       }
+       else if (*buf == 'y') {
+           ng = add_newsgroup(ngname, ':');
+           flags |= GNG_RELOC;
+       }
+       else if (*buf == 'Y') {
+           addnewbydefault = ADDNEW_SUB;
+           if (append_unsub)
+               printf("(Adding %s to end of your .newsrc subscribed)\n",
+                      ngname) FLUSH;
+           else
+               printf("(Subscribing to %s)\n", ngname) FLUSH;
+           ng = add_newsgroup(ngname, ':');
+           flags &= ~GNG_RELOC;
+       }
+       else if (*buf == 'N') {
+           addnewbydefault = ADDNEW_UNSUB;
+           if (append_unsub) {
+               printf("(Adding %s to end of your .newsrc unsubscribed)\n",
+                      ngname) FLUSH;
+               ng = add_newsgroup(ngname, NEGCHAR);
+               flags &= ~GNG_RELOC;
+           } else {
+               printf("(Ignoring %s)\n", ngname) FLUSH;
+               return FALSE;
+           }
+       }
+       else {
+           fputs(hforhelp,stdout) FLUSH;
+           settle_down();
+           goto reask_add;
+       }
+      }
+    }
+    else if (mode == 'i')              /* adding new groups during init? */
+       return FALSE;
+    else if (rcchar[ng] == NEGCHAR) {  /* unsubscribed? */
+#ifdef VERBOSE
+       IF(verbose)
+           sprintf(promptbuf,
+"\nNewsgroup %s is unsubscribed -- resubscribe? [yn] ",ngname)
+  FLUSH;
+       ELSE
+#endif
+#ifdef TERSE
+           sprintf(promptbuf,"\nResubscribe %s? [yn] ",ngname)
+             FLUSH;
+#endif
+reask_unsub:
+       in_char(promptbuf,'R');
+       setdef(buf,"y");
+#ifdef VERIFY
+       printcmd();
+#endif
+       putchar('\n') FLUSH;
+       if (*buf == 'h') {
+#ifdef VERBOSE
+           IF(verbose)
+               printf("Type y or SP to resubscribe to %s.\n", ngname) FLUSH;
+           ELSE
+#endif
+#ifdef TERSE
+               fputs("y or SP to resubscribe.\n",stdout) FLUSH;
+#endif
+           fputs(ntoforget,stdout) FLUSH;
+           goto reask_unsub;
+       }
+       else if (*buf == 'n' || *buf == 'q') {
+           return FALSE;
+       }
+       else if (*buf == 'y') {
+           register char *cp = rcline[ng] + rcnums[ng];
+           rcchar[ng] = (*cp && cp[1] == '0' ? '0' : ':');
+           flags &= ~GNG_RELOC;
+       }
+       else {
+           fputs(hforhelp,stdout) FLUSH;
+           settle_down();
+           goto reask_unsub;
+       }
+    }
+
+    /* now calculate how many unread articles in newsgroup */
+
+    set_toread(ng);
+#ifdef RELOCATE
+    if (flags & GNG_RELOC) {
+       ng = relocate_newsgroup(ng,-1);
+       if (ng < 0)
+           return FALSE;
+    }
+#endif
+    return toread[ng] >= TR_NONE;
+}
+
+/* add a newsgroup to the .newsrc file (eventually) */
+
+NG_NUM
+add_newsgroup(ngn, c)
+char *ngn;
+char_int c;
+{
+    register NG_NUM newng = nextrcline++;
+                                       /* increment max rcline index */
+
+    if (newng >= maxrcline)            /* check for overflow */
+       grow_rc_arrays(maxrcline + 25);
+
+    rcnums[newng] = strlen(ngn) + 1;
+    rcline[newng] = safemalloc((MEM_SIZE)(rcnums[newng] + 2));
+    strcpy(rcline[newng],ngn);         /* and copy over the name */
+    strcpy(rcline[newng]+rcnums[newng], " ");
+    rcchar[newng] = c;                 /* subscribe or unsubscribe */
+    toread[newng] = TR_NONE;   /* just for prettiness */
+#ifdef HASHNG
+    sethash(newng);                    /* so we can find it again */
+#endif
+    return newng;
+}
+
+#ifdef RELOCATE
+NG_NUM
+relocate_newsgroup(ngx,newng)
+NG_NUM ngx;
+NG_NUM newng;
+{
+    char *dflt = (ngx!=current_ng ? "$^.Lq" : "$^Lq");
+    char *tmprcline;
+    ART_UNREAD tmptoread;
+    char tmprcchar;
+    char tmprcnums;
+    ACT_POS tmpsoftptr;
+    register NG_NUM i;
+    ART_NUM tmpngmax;
+    ART_NUM tmpabs1st;
+    
+    starthere = 0;                      /* Disable this optimization */
+    writesoft = TRUE;                  /* Update soft pointer file */
+    if (ngx < nextrcline-1) {
+#ifdef HASHNG
+       if (rc_hash)
+           hashwalk(rc_hash, del_rc_line, ngx);
+#endif
+       tmprcline = rcline[ngx];
+       tmptoread = toread[ngx];
+       tmprcchar = rcchar[ngx];
+       tmprcnums = rcnums[ngx];
+       tmpsoftptr = softptr[ngx];
+       tmpngmax = ngmax[ngx];
+       tmpabs1st = abs1st[ngx];
+       for (i=ngx+1; i<nextrcline; i++) {
+           rcline[i-1] = rcline[i];
+           toread[i-1] = toread[i];
+           rcchar[i-1] = rcchar[i];
+           rcnums[i-1] = rcnums[i];
+           softptr[i-1] = softptr[i];
+           ngmax[i-1] = ngmax[i];
+           abs1st[i-1] = abs1st[i];
+       }
+       rcline[nextrcline-1] = tmprcline;
+       toread[nextrcline-1] = tmptoread;
+       rcchar[nextrcline-1] = tmprcchar;
+       rcnums[nextrcline-1] = tmprcnums;
+       softptr[nextrcline-1] = tmpsoftptr;
+       ngmax[nextrcline-1] = tmpngmax;
+       abs1st[nextrcline-1] = tmpabs1st;
+    }
+    if (current_ng > ngx)
+       current_ng--;
+    if (newng < 0) {
+      reask_reloc:
+       unflush_output();               /* disable any ^O in effect */
+#ifdef VERBOSE
+       IF(verbose)
+           printf("\nPut newsgroup where? [%s] ", dflt);
+       ELSE
+#endif
+#ifdef TERSE
+           printf("\nPut where? [%s] ", dflt);
+#endif
+       fflush(stdout);
+      reinp_reloc:
+       eat_typeahead();
+       getcmd(buf);
+       if (errno || *buf == '\f') {
+                           /* if return from stop signal */
+           goto reask_reloc;   /* give them a prompt again */
+       }
+       setdef(buf,dflt);
+#ifdef VERIFY
+       printcmd();
+#endif
+       if (*buf == 'h') {
+#ifdef VERBOSE
+           IF(verbose) {
+               printf("\n\n\
+Type ^ to put the newsgroup first (position 0).\n\
+Type $ to put the newsgroup last (position %d).\n", nextrcline-1);
+               printf("\
+Type . to put it before the current newsgroup (position %d).\n", current_ng);
+               printf("\
+Type -newsgroup name to put it before that newsgroup.\n\
+Type +newsgroup name to put it after that newsgroup.\n\
+Type a number between 0 and %d to put it at that position.\n", nextrcline-1);
+               printf("\
+Type L for a listing of newsgroups and their positions.\n\
+Type q to abort the current action.\n") FLUSH;
+           }
+           ELSE
+#endif
+#ifdef TERSE
+           {
+               printf("\n\n\
+^ to put newsgroup first (pos 0).\n\
+$ to put last (pos %d).\n", nextrcline-1);
+               printf("\
+. to put before current newsgroup (pos %d).\n", current_ng);
+               printf("\
+-newsgroup to put before newsgroup.\n\
++newsgroup to put after.\n\
+number in 0-%d to put at that pos.\n", nextrcline-1);
+               printf("\
+L for list of .newsrc.\n\
+q to abort\n") FLUSH;
+           }
+#endif
+           goto reask_reloc;
+       }
+       else if (*buf == 'q')
+           return -1;
+       else if (*buf == 'L') {
+           putchar('\n') FLUSH;
+           list_newsgroups();
+           goto reask_reloc;
+       }
+       else if (isdigit(*buf)) {
+           if (!finish_command(TRUE))  /* get rest of command */
+               goto reinp_reloc;
+           newng = atol(buf);
+           if (newng < 0)
+               newng = 0;
+           if (newng >= nextrcline)
+               return nextrcline-1;
+       }
+       else if (*buf == '^') {
+           putchar('\n') FLUSH;
+           newng = 0;
+       }
+       else if (*buf == '$') {
+           newng = nextrcline-1;
+       }
+       else if (*buf == '.') {
+           putchar('\n') FLUSH;
+           newng = current_ng;
+       }
+       else if (*buf == '-' || *buf == '+') {
+           if (!finish_command(TRUE))  /* get rest of command */
+               goto reinp_reloc;
+           newng = find_ng(buf+1);
+           if (newng == nextrcline) {
+               fputs("Not found.",stdout) FLUSH;
+               goto reask_reloc;
+           }
+           if (*buf == '+')
+               newng++;
+       }
+       else {
+           printf("\n%s",hforhelp) FLUSH;
+           settle_down();
+           goto reask_reloc;
+       }
+    }
+    if (newng < nextrcline-1) {
+#ifdef HASHNG
+       if (rc_hash)
+           hashwalk(rc_hash, ins_rc_line, newng);
+#endif
+       tmprcline = rcline[nextrcline-1];
+       tmptoread = toread[nextrcline-1];
+       tmprcchar = rcchar[nextrcline-1];
+       tmprcnums = rcnums[nextrcline-1];
+       tmpsoftptr = softptr[nextrcline-1];
+       tmpngmax = ngmax[nextrcline-1];
+       tmpabs1st = abs1st[nextrcline-1];
+       for (i=nextrcline-2; i>=newng; i--) {
+           rcline[i+1] = rcline[i];
+           toread[i+1] = toread[i];
+           rcchar[i+1] = rcchar[i];
+           rcnums[i+1] = rcnums[i];
+           softptr[i+1] = softptr[i];
+           ngmax[i+1] = ngmax[i];
+           abs1st[i+1] = abs1st[i];
+       }
+       rcline[newng] = tmprcline;
+       toread[newng] = tmptoread;
+       rcchar[newng] = tmprcchar;
+       rcnums[newng] = tmprcnums;
+       softptr[newng] = tmpsoftptr;
+       ngmax[newng] = tmpngmax;
+       abs1st[newng] = tmpabs1st;
+    }
+    if (current_ng >= newng)
+       current_ng++;
+    return newng;
+}
+#endif
+
+/* List out the newsrc with annotations */
+
+void
+list_newsgroups()
+{
+    register NG_NUM i;
+    char tmpbuf[2048];
+    static char *status[] = {"(READ)","(UNSUB)","(BOGUS)","(JUNK)"};
+    int cmd;
+
+    page_init();
+    print_lines("\
+  #  Status  Newsgroup\n\
+",STANDOUT);
+    for (i=0; i<nextrcline && !int_count; i++) {
+       if (toread[i] >= 0)
+           set_toread(i);
+       *(rcline[i] + rcnums[i] - 1) = RCCHAR(rcchar[i]);
+       if (toread[i] > 0)
+           sprintf(tmpbuf,"%3d %6ld   ",i,(long)toread[i]);
+       else
+           sprintf(tmpbuf,"%3d %7s  ",i,status[-toread[i]]);
+       safecpy(tmpbuf+13,rcline[i],2034);
+       *(rcline[i] + rcnums[i] - 1) = '\0';
+       if (cmd = print_lines(tmpbuf,NOMARKING)) {
+           if (cmd > 0)
+               pushchar(cmd);
+           break;
+       }
+    }
+    int_count = 0;
+}
+
+/* find a newsgroup in .newsrc */
+
+NG_NUM
+find_ng(ngnam)
+char *ngnam;
+{
+#ifdef HASHNG
+    HASHDATUM data;
+
+    assert(rc_hash != 0);
+    data = hashfetch(rc_hash, ngnam, strlen(ngnam));
+    if (!data.dat_ptr)
+       return nextrcline;              /* = notfound */
+    return data.dat_len;
+
+#else /* just do linear search */
+    register NG_NUM ngnum;
+
+    for (ngnum = 0; ngnum < nextrcline; ngnum++) {
+       if (strEQ(rcline[ngnum],ngnam))
+           break;
+    }
+    return ngnum;
+#endif
+}
+
+void
+cleanup_rc()
+{
+    register NG_NUM ngx;
+    register NG_NUM bogosity = 0;
+
+#ifdef VERBOSE
+    IF(verbose)
+       fputs("Checking out your .newsrc -- hang on a second...\n",stdout)
+         FLUSH;
+    ELSE
+#endif
+#ifdef TERSE
+       fputs("Checking .newsrc -- hang on...\n",stdout) FLUSH;
+#endif
+    for (ngx = 0; ngx < nextrcline; ngx++) {
+       if (toread[ngx] >= TR_UNSUB) {
+           set_toread(ngx);            /* this may reset newsgroup */
+                                       /* or declare it bogus */
+       }
+       if (toread[ngx] == TR_BOGUS)
+           bogosity++;
+    }
+    for (ngx = nextrcline-1; ngx >= 0 && toread[ngx] == TR_BOGUS; ngx--)
+       bogosity--;                     /* discount already moved ones */
+    if (nextrcline > 5 && bogosity > nextrcline / 2) {
+       fputs(
+"It looks like the active file is messed up.  Contact your news administrator,\n\
+",stdout);
+       fputs(
+"leave the \"bogus\" groups alone, and they may come back to normal.  Maybe.\n\
+",stdout) FLUSH;
+    }
+#ifdef RELOCATE
+    else if (bogosity) {
+#ifdef VERBOSE
+       IF(verbose)
+           fputs("Moving bogus newsgroups to the end of your .newsrc.\n",
+               stdout) FLUSH;
+       ELSE
+#endif
+#ifdef TERSE
+           fputs("Moving boguses to the end.\n",stdout) FLUSH;
+#endif
+       for (; ngx >= 0; ngx--) {
+           if (toread[ngx] == TR_BOGUS)
+               relocate_newsgroup(ngx,nextrcline-1);
+       }
+#ifdef DELBOGUS
+reask_bogus:
+       in_char("Delete bogus newsgroups? [ny] ", 'D');
+       setdef(buf,"n");
+#ifdef VERIFY
+       printcmd();
+#endif
+       putchar('\n') FLUSH;
+       if (*buf == 'h') {
+#ifdef VERBOSE
+           IF(verbose)
+               fputs("\
+Type y to delete bogus newsgroups.\n\
+Type n or SP to leave them at the end in case they return.\n\
+",stdout) FLUSH;
+           ELSE
+#endif
+#ifdef TERSE
+               fputs("y to delete, n to keep\n",stdout) FLUSH;
+#endif
+           goto reask_bogus;
+       }
+       else if (*buf == 'n' || *buf == 'q')
+           ;
+       else if (*buf == 'y') {
+           while (toread[nextrcline-1] == TR_BOGUS && nextrcline > 0)
+               --nextrcline;           /* real tough, huh? */
+       }
+       else {
+           fputs(hforhelp,stdout) FLUSH;
+           settle_down();
+           goto reask_bogus;
+       }
+#endif
+    }
+#else
+#ifdef VERBOSE
+    IF(verbose)
+       fputs("You should edit bogus newsgroups out of your .newsrc.\n",
+           stdout) FLUSH;
+    ELSE
+#endif
+#ifdef TERSE
+       fputs("Edit boguses from .newsrc.\n",stdout) FLUSH;
+#endif
+#endif
+    paranoid = FALSE;
+}
+
+#ifdef HASHNG
+/* make an entry in the hash table for the current newsgroup */
+
+void
+sethash(thisng)
+NG_NUM thisng;
+{
+    HASHDATUM data;
+
+    data.dat_ptr = nullstr;
+    data.dat_len = thisng;
+    hashstore(rc_hash, rcline[thisng], rcnums[thisng]-1, data);
+}
+
+static int
+rcline_cmp(key, keylen, data)
+char *key;
+int keylen;
+HASHDATUM data;
+{
+    /* We already know that the lengths are equal, just compare the strings */
+    return bcmp(key, rcline[data.dat_len], keylen);
+}
+
+static void
+del_rc_line(data, ngnum)
+HASHDATUM *data;
+int ngnum;
+{
+    if (data->dat_len == ngnum)
+       data->dat_len = nextrcline-1;
+    else if (data->dat_len > ngnum)
+       data->dat_len--;
+}
+
+static void
+ins_rc_line(data, ngnum)
+HASHDATUM *data;
+int ngnum;
+{
+    if (data->dat_len == nextrcline-1)
+       data->dat_len = ngnum;
+    else if (data->dat_len >= ngnum)
+       data->dat_len++;
+}
+#endif
+
+void
+newsrc_check()
+{
+    rcfp = fopen(rcname,"r");          /* open it */
+    if (rcfp == Nullfp) {                      /* not there? */
+#ifdef VERBOSE
+       IF(verbose)
+           fputs("\nTrying to set up a .newsrc file -- running newsetup...\n\n\
+",stdout) FLUSH;
+       ELSE
+#endif
+#ifdef TERSE
+           fputs("Setting up .newsrc...\n",stdout) FLUSH;
+#endif
+       if (doshell(sh,filexp(NEWSETUP)) ||
+           (rcfp = fopen(rcname,"r")) == Nullfp) {
+#ifdef VERBOSE
+           IF(verbose)
+               fputs("\nCan't create a .newsrc -- you must do it yourself.\n\
+",stdout) FLUSH;
+           ELSE
+#endif
+#ifdef TERSE
+               fputs("(Fatal)\n",stdout) FLUSH;
+#endif
+           finalize(1);
+       }
+    }
+    else {
+       UNLINK(rcbname);                /* unlink backup file name */
+       link(rcname,rcbname);           /* and backup current name */
+    }
+}
+
+/* checkpoint the .newsrc */
+
+void
+checkpoint_rc()
+{
+#ifdef DEBUG
+    if (debug & DEB_CHECKPOINTING) {
+       fputs("(ckpt)",stdout);
+       fflush(stdout);
+    }
+#endif
+    if (doing_ng)
+       bits_to_rc();                   /* do not restore M articles */
+    if (rc_changed)
+       write_rc();
+#ifdef DEBUG
+    if (debug & DEB_CHECKPOINTING) {
+       fputs("(done)",stdout);
+       fflush(stdout);
+    }
+#endif
+}
+
+/* write out the (presumably) revised .newsrc */
+
+void
+write_rc()
+{
+    register NG_NUM tmpng;
+    register char *delim;
+
+    rcfp = fopen(rctname, "w");                /* open .newnewsrc */
+    if (rcfp == Nullfp) {
+       printf(cantrecreate,".newsrc") FLUSH;
+       finalize(1);
+    }
+    if (stat(rcname,&filestat)>=0) {   /* preserve permissions */
+       chmod(rctname,filestat.st_mode&0666);
+       chown(rctname,filestat.st_uid,filestat.st_gid); /* if possible */
+    }
+
+    /* write out each line*/
+
+    for (tmpng = 0; tmpng < nextrcline; tmpng++) {
+       if (rcnums[tmpng]) {
+           delim = rcline[tmpng] + rcnums[tmpng] - 1;
+           *delim = RCCHAR(rcchar[tmpng]);
+           if (rcchar[tmpng] == '0' && delim[2] == '1')
+               delim[2] = '0';
+       }
+       else
+           delim = Nullch;
+#ifdef DEBUG
+       if (debug & DEB_NEWSRC_LINE)
+           printf("%s\n",rcline[tmpng]) FLUSH;
+#endif
+       if (fprintf(rcfp,"%s\n",rcline[tmpng]) < 0) {
+       write_error:
+           printf(cantrecreate,".newsrc") FLUSH;
+           fclose(rcfp);               /* close .newnewsrc */
+           UNLINK(rctname);
+           finalize(1);
+       }
+       if (delim) {
+           *delim = '\0';              /* might still need this line */
+           if (rcchar[tmpng] == '0' && delim[2] == '0')
+               delim[2] = '1';
+       }
+    }
+    fflush(rcfp);
+    if (ferror(rcfp))
+       goto write_error;
+
+    fclose(rcfp);                      /* close .newnewsrc */
+    UNLINK(rcname);
+#ifdef HAS_RENAME
+    rename(rctname,rcname);
+#else
+    link(rctname,rcname);
+    UNLINK(rctname);
+#endif
+
+    if (writesoft) {
+       tmpfp = fopen(filexp(softname), "w");   /* open .rnsoft */
+       if (tmpfp == Nullfp) {
+           printf(cantcreate,filexp(softname)) FLUSH;
+           return;
+       }
+       for (tmpng = 0; tmpng < nextrcline; tmpng++) {
+           fprintf(tmpfp,"%ld\n",(long)softptr[tmpng]);
+       }
+       fclose(tmpfp);
+    }
+}
+
+void
+get_old_rc()
+{
+    UNLINK(rctname);
+#ifdef HAS_RENAME
+    rename(rcname,rctname);
+    rename(rcbname,rcname);
+#else
+    link(rcname,rctname);
+    UNLINK(rcname);
+    link(rcbname,rcname);
+    UNLINK(rcbname);
+#endif
+}
+
+static void
+grow_rc_arrays(newsize)
+int newsize;
+{
+    abs1st = (ART_NUM*)saferealloc((char*)abs1st,
+               (MEM_SIZE)newsize * sizeof (ART_NUM));
+    ngmax = (ART_NUM*)saferealloc((char*)ngmax,
+               (MEM_SIZE)newsize * sizeof (ART_NUM));
+    rcline = (char**)saferealloc((char*)rcline,
+               (MEM_SIZE)newsize * sizeof (char*));
+    toread = (ART_UNREAD*)saferealloc((char*)toread,
+               (MEM_SIZE)newsize * sizeof(ART_UNREAD));
+    rcchar = (char *) saferealloc(rcchar,
+               (MEM_SIZE)newsize * sizeof (char));
+    rcnums = (char*)saferealloc(rcnums,
+               (MEM_SIZE)newsize * sizeof (char));
+    softptr = (ACT_POS*)saferealloc((char*)softptr,
+               (MEM_SIZE)newsize * sizeof (ACT_POS));
+
+    bzero((char*)(abs1st+maxrcline), (newsize-maxrcline) * sizeof (ART_NUM));
+    bzero((char*)(ngmax+maxrcline), (newsize-maxrcline) * sizeof (ART_NUM));
+    maxrcline = newsize;
+
+    return;
+}