[comp.sources.misc] v09i077: newsclip 1.1, part 8 of 15

brad@looking.ON.CA (Brad Templeton) (12/20/89)

Posting-number: Volume 9, Issue 77
Submitted-by: brad@looking.ON.CA (Brad Templeton)
Archive-name: newsclip/part08

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 8 (of 15)."
# Contents:  doc/filter.man getdate.y nrc.c
# Wrapped by allbery@uunet on Tue Dec 19 20:10:01 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'doc/filter.man' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'doc/filter.man'\"
else
echo shar: Extracting \"'doc/filter.man'\" \(18501 characters\)
sed "s/^X//" >'doc/filter.man' <<'END_OF_FILE'
X.if n .ds La '
X.if n .ds Ra '
X.if t .ds La `
X.if t .ds Ra '
X.if n .ds Lq "
X.if n .ds Rq "
X.if t .ds Lq ``
X.if t .ds Rq ''
X.de Ch
X\\$3\\*(Lq\\$1\\*(Rq\\$2
X..
X.TH FILTER 5 "May 10, 1989"
X.ds ]W News Filter
X.SH NAME
Xfilter \- reader/newsfilter communications protocol.
X.SH SYNOPSIS
XThis document describes version 1.00 of the protocol used to implement
Xnewsreader communication with newsfilter processes. The intent is to support
Xconstruction of newsfilters that can communicate with any standard reader.
X.SH DESCRIPTION
XDuring initialization, newsreaders may attempt to establish communication
Xwith a newsfilter process. If it succeeds, information on each article will
Xbe passed to the newsfilter using a protocol described in this document;
Xthe newsfilter will pass back an `interest score' which the reader may use
Xto determine whether and how the article should be presented.
X.PP
XThe news sources distribution provides C libraries for both the `top' (reader)
Xend, and the `bottom' (filter) end.
X.SH THEORY OF OPERATION
XProtocol execution may be thought of a series of \fRtransactions\fR; in each
Xtransaction, the newsreader sends command down to the newsfilter, and
Xblocks waiting a response. Optionally, the reader may time out if a response
Xis not received within some maximum time.
X.PP
XSome transactions group into \fIdialogues\fR; these are logical sequences 
Xof transactions which share state (i.e. affect shared data in the protocol
Xservice routines).
X.PP
XA protocol session consists of a \fIstart dialogue\fR, followed by any
Xnumber of \fIcommand dialogues\fR, terminated by an \fIend dialogue\fR.
XThere are presently two kinds of command dialogue;
X\fInewsgroup dialogues\fR and \fIarticle dialogues\fR. Specifications for
Xeach of these are given below.
X.SH COMMAND AND RESPONSE FORMAT
XAll messages start with a fixed-size \fIheader\fR. Some messages
Xmay be followed by an \fIargument list\fR, the length of which is provided as
Xpart of the header. In a few cases, the argument list may be followed by an
Xadditional variable-length \fItext section\fR.
X.PP
XHere is the format of a header:
X.nf
X	0	Type: `C' = call, `R' = response, `Q' = query, `A' = answer
X	1	A command code letter.
X	2	A space ` '
X	3-8	A 6-digit decimal command sequence number.
X	9	A space ` '
X	10-12	A 3-digit decimal argument list length.
X	13	A terminating 0 (NUL) or newline byte.
X.fi
X.PP
XIn a call, the sequence number is 1 for the first command issued and
Xincreases by one in each following command.
XIn a response, the sequence number field is the number of the command to which
Xthe response pertains.  The newsreader is not required to provide meaningful
Xsequence numbers, but whatever number the newsreader sends will be used in
Xall filter responses to that command.
X.PP
XIf a numeric field is smaller than the full field width,
Xit should be either left justified and
Xspace-filled to the right or 0 filled from the left. The latter form is
Xrecommended and is the one shown in this document's examples.
X.PP
XThe \fIargument list\fR (if any) of a message is interpreted as a
Xsequence of NUL-separated character strings. The
Xlength field in the header must count the the terminating zero byte found at
Xthe end of the last argument.  The length of
Xthe argument list is limited to 256 characters counting the NUL bytes.
X.P
XThe \fItext section\fR (in the non-implemented pipe mode) may consist of
Xeither (a) an RFC-822 message
Xheader followed and terminated by two carriage returns (a blank line), or b)
XASCII data.  Both are presented in a special packet format described below.
X.SH STARTUP
XWhile it is possible that this protocol may be implemented using other
Xforms of inter process communication, this first version of the protocol
Xis expected to be implemented using two nameless pipes.   News filter
Xprograms will take their commands from the master newsreading program
Xby reading from the standard input.  They will give their responses and
Xqueries to the standard output.
X.PP
XA typical newsreader will fork a child news filter process, create pipes
Xto talk to the standard input of the child and read from the standard
Xoutput of the child, and then execute a news filter executable program.
X.PP
XWhile a newsreader may use any means to decide the location of the
Xnews filter program it executes, the standard name is
X.B nclip.
XThe nclip program should be found in the same directory the newsreader
Xuses to keep user files, such as the
X.N .newsrc
Xfile.  The newsreader may also search the directories listed in the
Xuser's PATH environment variable for this executable.
X.PP
XWhen the filter program is executed, it should be executed with the
Xfollowing argument:
X.TP 0
Xmode=pipe
X.PP
XOptionally, if the newsreader has a directory in which it places
Xuser files, it should pass the name of that directory in a second argument
Xof the form:
X.TP 0
Xdot=<dirname>
X.SS INITIAL SEQUENCE
XWhen a news filter starts up operation, it should immediately send
Xa ``response'' with the OK message (see below) to its standard output.  This
Xis not a response to any command, just a response to being executed.
XThis will indicate that the news filter program has started correctly.
X.PP
XNewsreading programs which start up a news filter and do not get
Xthis immediate initial response should assume the filter has failed
Xto start, and act as though it is not present.
X.PP
XIf the OK message is detected, the newsreader should then send a
XVersion command to the news filter and await a Version response.  If
Xall goes well, general operation may then continue.
X.PP
XNews filter programs should be sure to handle signals properly.  For
Xexample, a filter program should probably ignore INT (break) signals, as
Xit is not talking to a terminal but will still be in the newsreader's
Xprocess group (on Unix).
X.PP
XAt the end of a session, the newsreader should send the Quit command
Xto the filter, await an OK, and then terminate or go on with the knowledge
Xthe news filter program is not in operation.
X
X.SH DIALOGUE SPECIFICATIONS
XIn the following specifications, the form of a message is given 
Xas a pseudo-BNF listing the code byte of the header and the meaning of the
Xarguments following. Required and computed format elements such as the leading
Xtype byte, the embedded spaces, the sequence number, the argument list length
Xfield, and various NUL separators should be understood from the request format
Xdescription above.
X.PP
XEach command or response specification is followed by an example. The first
Xline of each example shows a sample header of the given type, and the second
Xline an argument list.
X.SS The `Start' Dialogue
XThe `start dialogue' consists of a single transaction. The reader sends a
X`V' (Version) command and expects a `V' (Version) response. These are defined
Xas follows:
X.TP 0
XCOMMAND: V <version-string>
X.TP 0
X	CV 000001 005\\0
X	V100\\0
X.PP
XThis command can be sent to a newsfilter to establish the newsfilter
Xlanguage protocol understood by both programs.  The newsfilter
Xprogram will respond with a version line of its own, including
Xthe list of valid commands it understands and the list of responses it
Xcan give back.  The newsreader should only send those commands in
Xthe list given; others will produce an `error' response.  All newsfilters must
Xaccept the set of commands listed in this document -- the specification for
XV100 of the newsfilter interface language.
X.TP 0
XRESPONSE: V <version> <commands> <responses> <plang> <pversion>
X.TP 0
X	RV 000001 029\\0
X	V100\\0ABHNPQV\\0ABEHORV\\0newsclip\\0100\\0
X.PP
XThe <vnum> argument is a Version number for the command
Xlanguage understood by the newsfiltering program.  This language is
Xversion V100.  Later releases will have a higher number.
X.PP
XThe <commands> arg is the set of command codes the newsfilter understands.
XThe <responses> arg is the set of responses that it knows to send back.
X.PP
XThe <plang> argument is the name of the filter language ('P' commands) that
Xthe newsfilter understands, and the <pversion> number is a version number.
XIf the newsfilter does not understand any language for P commands, it should
Xuse the name `NULL' and a version number of 0.
X.PP
XIf the newsreader gets an `error' response to this message, it should assume
Xthat the filter is present but cannot handle the language specified, or has
Xfailed to initialize properly.  The reader may attempt different Version
Xcommands, or may decide to send a Quit command.
X.PP
XDefined names currently are:
X		NULL
X		newsclip
X		rnkill
X.PP
XNames can be registered via email to newsfilters@looking.on.ca
X.SS The `End' Dialogue
XThe \fIend dialogue\fR consists of a single transaction; the reader sends
Xa `Q' (Quit) command and expects an `O' (Ok) response.
X.TP
XCOMMAND: Q
X.TP
X	CQ 000236 000\\0
X.PP
XThe newsfilter program should terminate.  An response of `Ok' is
Xexpected, after which the pipes will close.
X.PP
XClosing the command pipe, causing EOF for the newsfilter, should
Xalso cause the newsfilter to terminate.  If a newsreader detects EOF
Xon the answer pipe, it should assume the newsfilter has terminated and
Xact accordingly, possibly giving an error message to the user.
X.TP 0
XRESPONSE: O
X.TP 0
X	RO 052317 000\\0
X.PP
XThis response confirms that the newsfilter is exiting gracefully.
X.SS The `Program' Dialogue
X.PP
XThe \fIprogram dialogue\fR begins with an 'P' (Program) command and ends with
Xone of the responses 'O' (Ok) or `E' (Error).
X.TP 0
XCOMMAND: P <command-string>
X.TP 0
X	CK 012321 016\\0
X	kill From: Eric\\0
X.PP
XThe first arg passes free format commands to the newsfilter.  Normally these
Xwill be things to add to the newsfilter's "kill files" or other such
Xcommands.  The format of the commands is entirely up to the newsfilter.
X.P
XThe above command might be request to kill all articles that include the string
X"Eric" in their From line.  It would be generated by a newsreader that
Xknew how to translate user requests into commands to this particular
Xnewsfilter.
X.PP
XThe newsfilter should interpret the command.  If it is a valid command, it
Xshould execute it and issue an OK response.  If it is not a valid command,
Xit should issue an Error response.  The newsreader may decide to issue
Xan error message because of the error response, or do further
Xanalysis of the command.
X.TP 0
XRESPONSE: O
X.TP 0
X	RO 12321 000\\0
X.PP
XThis response confirms that the argument was accepted as a valid command
Xby the newsfilter program.
X.TP 0
XRESPONSE: E <error-message>
X.TP 0
X	RE 12321 017\\0
X	No such command!\\0
X.PP
XThis response tells the reader the argument was rejected as an invalid command
Xby the newsfilter program.
X.SS The `Newsgroup' Dialogue 
XThe \fINewsgroup dialogue\fR consists of a single transaction; the reader sends
Xan `N' (Newsgroup) command, and expects an Accept, Reject or Ok response.
X.TP 0
XCOMMAND: N <newsgroup>
X.TP 0
X	CN 00005 015\\0
X	news.groups\\0
X.PP
XThis command asks the newsfilter for general information on
Xa newsgroup.  Responses can be `A' (Accept), which indicates that
Xall articles in this group should be accepted without consulting the
Xnewsfilter, `R' (Reject) which means that all articles should be rejected
Xwithout consulting the newsfilter or `O' (Ok - Consult), which means that
Xarticles should be fed to the newsfilter for examination.
X.PP
XNote that even in the case of an `A' or `R', articles in that group
Xmay still be sent for examination.  It is just less efficient to do so.
X.TP 0
XRESPONSE: A <score>
X.TP 0
X	RA 000005 003\\0
X	22\\0
X.PP
XThis response indicates that the article should be accepted. The single
Xargument is an `interest score' computed by the newskiller; it may be omitted
Xto indicate a value of 1.
X.TP 0
XRESPONSE: R <score>
X.TP 0
X	RR 000005 003\\0
X	-2\\0
X.PP
XThis response indicates that the article should be rejected. The single
Xargument is an `interest score' computed by the newskiller; it may be omitted
Xto indicate a value of -1.
X.PP
XBy convention, the `R' response carries a zero or negative score and the `A'
Xresponse a positive one.
X.PP
XThe `O' response is as documented for the `Q' (Quit) command above.
X.SS The `Article' Dialogue
XThe \fIarticle dialogue\fR begins with an 'A' (Article) command and ends with
Xone of the responses 'A' (Accept) or `R' (Reject). There may be one or more
Xtransactions in this dialogue.
X.TP 0
XCOMMAND: A <newsgroup> <number> <mode> [<filename>]
X.TP 0
X	CA 000006 048\\0
X	news.groups\\034\\0R\\0/usr/spool/news/news/groups/43\\0
X.PP
XThe first two arguments are a normal newsgroup and article-number pair; if
Xthe newsfilter
Xcan deduce a final interest score from these, it will do so and return accept
Xor reject immediately. Otherwise, the newsfilter can return article information
Xrequests to see portions of the article; see the following description of
Xarticle information exchange. The <filename> argument, if present, is used
Xin resolving the article information request.  The <mode> argument
Xindicates whether the article is present in the file, or must be
Xrequested.
X.PP
XText may be passed down to the newsfilter in one of two modes; \fIpipe mode\fR
Xor \fIfile mode\fR.  The mode is triggered by the signle character <mode>
Xbyte argument.  File modes require a file name
Xname argument on the reader command that triggered the article information
Xrequest.  For pipe mode, no file name is given and a mode character of
X'P' is provided.  The two file modes use mode characters of
X'F' (full) and 'R' (request).
X.PP
XPipe mode is currently not
Ximplemented in any of the news filtering or newsreading programs using
Xthis protocol.  It is defined for future expansion.
X.PP
XIn \fIpipe mode\fR article portions are passed down in the text sections of
XHeader and Body replies from the newsreader in accordance with text query 
Xsequences started by the newsfilter. Text query sequence protocol is specified
Xbelow.
X.PP
XIn \fIfile mode\fR the article information is passed in the file named.
XThis file may be either the permanent location of the article, or a tempfile.
X.PP
XIn the former case, it is likely (but not necessary) that the 'F' (full)
Xfile mode will be used.  In this case, the entire article is already present
Xin the file, and no further queries should be issued by the newsfilter.
XThe only acceptable responses to a full file mode Article command are
XAccept, Reject and Error.
X.PP
XIn the latter case of request mode, the newsfilter should issue text queries
Xbefore attempting
Xto read first the header, and later the body of the article.  After
Xissuing such queries, the filter should wait for a response from the
Xnewsreader before reading into the file.
X.PP
XA text query sequence consists of a series of 'H' (Header) and 'B' (Body)
Xrequests sent by the newsfilter to the newsreader, and corresponding responses
Xby the newsreader.
X.TP 0
XQUERY: H
X.TP 0
X	QH 000340 000\\0
X.PP
XThis query requests the RFC-822 header of the article selected by a previous
X`A' command, or of the article selected by the newsreader at the time of
Xissuance of a 'P' command.
X.TP 0
XANSWER: H [<size> [<asize>]]
X.TP 0
X	AH 000340 004\\0
X	364\\0
X.PP
XThis answer signifies that the newsreader has header data ready in response
Xto a previous 'H' query. The optional <size> argument specifies the total
Xlength of the header.  The body of the article may exist beyond the header.
XFilters should not assume they will read EOF at the end of a header.
X.PP
XIn pipe mode, this answer is followed immediately by a text section in RFC-822
Xformat (see above), using message packet format. 
XMessage packets consist of a length byte, followed
Xby 0 to 255 text data bytes.  A 0 length packet indicates the end of the
Xheader, which must be preceded by a blank line.
X.PP
XThe optional <asize> is the size of the entire article, but only if the
Xnewsreader has it handy.
X.TP 0
XQUERY: B [<size>]
X.TP 0
X	QB 000341 004\\0
X	125\\0
X.PP
XThis query asks the reader to send down all or a portion of the body of the
Xcurrent article.   The filter may optionally communicate the most it wants
Xof the article with the <size> argument.  If this is present, the newsreader
Xneed not transmit or place more than <size> bytes of the body.  The
Xnewsreader is still always free to send the entire body -- this is merely
Xan optimization.  If the <size> argument is not present, the entire body
Xmust be made available.
X.TP 0
XANSWER: B [<size>]
X.TP 0
X	AB 000341 004\\0
X	125\\0
X.PP
XThe 'B' response signifies that the newsreader has text data ready for the
Xnewsfilter.  The argument optionally gives the length of the data in bytes. This
Xlength may be smaller or larger than the requested length.  It will only
Xbe smaller if the article body itself is smaller than the requested length.
X.PP
XNote that a newsreader can use request mode even if it always has
Xcomplete article files ready for the filter.  It should merely respond
Xto queries immediately, doing nothing.  The 'F' (full) mode simply allows
Xa reader to be sure it will never receive queries.  This allows very
Xsimple reader implementations of this protocol.
X.PP
XNNTP readers and other readers that do not have access to single
Xarticle files should use the request mode, building a temporary file
Xfor the filter as requested.
X.PP
XIn pipe mode, this answer is followed immediately by a text section in
Xmessage packet format.   Message packets consist of a length byte, followed
Xby 0 to 255 text data bytes.  A 0 length packet indicates EOF.
X.SH NOTES
XThe protocol is designed for use over the most primitive IPC common on UNIX,
Xa pair of nameless pipes. Some older UNIXes (V7 in particular) feature pipe
Ximplementations that behave rather badly (as in, cause a lockup or sudden
Xprocess death) if reads and writes are not carefully synchronized. Thus the
Xrigid alternation of fixed-size with variable-size transmissions, and the
Xcare in specifying lengths of variable parts in fixed-part blocks.
X.PP
XUnder a more forgiving IPC implementation (such as System V message queues),
Xthe fixed and variable-length parts might be sent in one transmission; this
Xis an implementation detail left up to service libraries.
X.PP
XThe all-ASCII format avoids potential alignment problems.
X.PP
XIs is expected that the protocol service libraries will automatically choose
Xpipe or file mode for text query sequence, depending on whether the calling
Xnewsreader browses a file hierarchy or talks to some sort of network daemon.
X.PP
XThe 'F' (full) file mode is the simplest mode, intended for use on
Xsystems where the article files reside in normal format on the user's
Xmachine.  A newsreader can be adapted to this mode of operation with
Xminimal changes.
X.SH AUTHORS
XThis protocol was developed by Brad Templeton and Eric Raymond.
END_OF_FILE
if test 18501 -ne `wc -c <'doc/filter.man'`; then
    echo shar: \"'doc/filter.man'\" unpacked with wrong size!
fi
# end of 'doc/filter.man'
fi
if test -f 'getdate.y' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'getdate.y'\"
else
echo shar: Extracting \"'getdate.y'\" \(12599 characters\)
sed "s/^X//" >'getdate.y' <<'END_OF_FILE'
X%token ID MONTH DAY MERIDIAN NUMBER UNIT MUNIT SUNIT ZONE DAYZONE AGO
X%{
X	/* 	Steven M. Bellovin (unc!smb)			*/
X	/*	Dept. of Computer Science			*/
X	/*	University of North Carolina at Chapel Hill	*/
X	/*	@(#)getdate.y	2.13	9/16/86 */
X
X#include <sys/types.h>
X#ifdef USG
Xstruct timeb
X{
X	time_t	time;
X	unsigned short millitm;
X	short	timezone;
X	short	dstflag;
X};
X#else
X#include <sys/timeb.h>
X#endif
X#include <ctype.h>
X
X#if defined(BSD4_2) || defined (BSD4_1C)
X#include <sys/time.h>
X#else /*sane*/
X#include <time.h>
X#endif /*sane*/
X
X#define	NULL	0
X#define daysec (24L*60L*60L)
X	static int timeflag, zoneflag, dateflag, dayflag, relflag;
X	static time_t relsec, relmonth;
X	static int hh, mm, ss, merid, daylight;
X	static int dayord, dayreq;
X	static int month, day, year;
X	static int ourzone;
X#define AM 1
X#define PM 2
X#define DAYLIGHT 1
X#define STANDARD 2
X#define MAYBE    3
X%}
X
X%%
Xtimedate: 		/* empty */
X	| timedate item;
X
Xitem:	tspec =
X		{timeflag++;}
X	| zone =
X		{zoneflag++;}
X	| dtspec =
X		{dateflag++;}
X	| dyspec =
X		{dayflag++;}
X	| rspec =
X		{relflag++;}
X	| nspec;
X
Xnspec:	NUMBER =
X		{if (timeflag && dateflag && !relflag) year = $1;
X		else {timeflag++;hh = $1/100;mm = $1%100;ss = 0;merid = 24;}};
X
Xtspec:	NUMBER MERIDIAN =
X		{hh = $1; mm = 0; ss = 0; merid = $2;}
X	| NUMBER ':' NUMBER =
X		{hh = $1; mm = $3; merid = 24;}
X	| NUMBER ':' NUMBER MERIDIAN =
X		{hh = $1; mm = $3; merid = $4;}
X	| NUMBER ':' NUMBER NUMBER =
X		{hh = $1; mm = $3; merid = 24;
X		daylight = STANDARD; ourzone = $4%100 + 60*$4/100;}
X	| NUMBER ':' NUMBER ':' NUMBER =
X		{hh = $1; mm = $3; ss = $5; merid = 24;}
X	| NUMBER ':' NUMBER ':' NUMBER MERIDIAN =
X		{hh = $1; mm = $3; ss = $5; merid = $6;}
X	| NUMBER ':' NUMBER ':' NUMBER NUMBER =
X		{hh = $1; mm = $3; ss = $5; merid = 24;
X		daylight = STANDARD; ourzone = $6%100 + 60*$6/100;};
X
Xzone:	ZONE =
X		{ourzone = $1; daylight = STANDARD;}
X	| DAYZONE =
X		{ourzone = $1; daylight = DAYLIGHT;};
X
Xdyspec:	DAY =
X		{dayord = 1; dayreq = $1;}
X	| DAY ',' =
X		{dayord = 1; dayreq = $1;}
X	| NUMBER DAY =
X		{dayord = $1; dayreq = $2;};
X
Xdtspec:	NUMBER '/' NUMBER =
X		{month = $1; day = $3;}
X	| NUMBER '/' NUMBER '/' NUMBER =
X		{month = $1; day = $3; year = $5;}
X	| MONTH NUMBER =
X		{month = $1; day = $2;}
X	| MONTH NUMBER ',' NUMBER =
X		{month = $1; day = $2; year = $4;}
X	| NUMBER MONTH =
X		{month = $2; day = $1;}
X	| NUMBER MONTH NUMBER =
X		{month = $2; day = $1; year = $3;};
X
X
Xrspec:	NUMBER UNIT =
X		{relsec +=  60L * $1 * $2;}
X	| NUMBER MUNIT =
X		{relmonth += $1 * $2;}
X	| NUMBER SUNIT =
X		{relsec += $1;}
X	| UNIT =
X		{relsec +=  60L * $1;}
X	| MUNIT =
X		{relmonth += $1;}
X	| SUNIT =
X		{relsec++;}
X	| rspec AGO =
X		{relsec = -relsec; relmonth = -relmonth;};
X%%
X
Xstatic int mdays[12] =
X	{31, 0, 31,  30, 31, 30,  31, 31, 30,  31, 30, 31};
X#define epoch 1970
X
Xextern struct tm *localtime();
Xtime_t dateconv(mm, dd, yy, h, m, s, mer, zone, dayflag)
Xint mm, dd, yy, h, m, s, mer, zone, dayflag;
X{
X	time_t tod, jdate;
X	register int i;
X	time_t timeconv();
X
X	if (yy < 0) yy = -yy;
X	if (yy < 100) yy += 1900;
X	mdays[1] = 28 + (yy%4 == 0 && (yy%100 != 0 || yy%400 == 0));
X	if (yy < epoch || yy > 1999 || mm < 1 || mm > 12 ||
X		dd < 1 || dd > mdays[--mm]) return (-1);
X	jdate = dd-1;
X        for (i=0; i<mm; i++) jdate += mdays[i];
X	for (i = epoch; i < yy; i++) jdate += 365 + (i%4 == 0);
X	jdate *= daysec;
X	jdate += zone * 60L;
X	if ((tod = timeconv(h, m, s, mer)) < 0) return (-1);
X	jdate += tod;
X	if (dayflag==DAYLIGHT || (dayflag==MAYBE&&localtime(&jdate)->tm_isdst))
X		jdate += -1*60*60;
X	return (jdate);
X}
X
Xtime_t dayconv(ord, day, now) int ord, day; time_t now;
X{
X	register struct tm *loctime;
X	time_t tod;
X	time_t daylcorr();
X
X	tod = now;
X	loctime = localtime(&tod);
X	tod += daysec * ((day - loctime->tm_wday + 7) % 7);
X	tod += 7*daysec*(ord<=0?ord:ord-1);
X	return daylcorr(tod, now);
X}
X
Xtime_t timeconv(hh, mm, ss, mer) register int hh, mm, ss, mer;
X{
X	if (mm < 0 || mm > 59 || ss < 0 || ss > 59) return (-1);
X	switch (mer) {
X		case AM: if (hh < 1 || hh > 12) return(-1);
X			 return (60L * ((hh%12)*60L + mm)+ss);
X		case PM: if (hh < 1 || hh > 12) return(-1);
X			 return (60L * ((hh%12 +12)*60L + mm)+ss);
X		case 24: if (hh < 0 || hh > 23) return (-1);
X			 return (60L * (hh*60L + mm)+ss);
X		default: return (-1);
X	}
X}
Xtime_t monthadd(sdate, relmonth) time_t sdate, relmonth;
X{
X	struct tm *ltime;
X	time_t dateconv();
X	time_t daylcorr();
X	int mm, yy;
X
X	if (relmonth == 0) return 0;
X	ltime = localtime(&sdate);
X	mm = 12*ltime->tm_year + ltime->tm_mon + relmonth;
X	yy = mm/12;
X	mm = mm%12 + 1;
X	return daylcorr(dateconv(mm, ltime->tm_mday, yy, ltime->tm_hour,
X		ltime->tm_min, ltime->tm_sec, 24, ourzone, MAYBE), sdate);
X}
X
Xtime_t daylcorr(future, now) time_t future, now;
X{
X	int fdayl, nowdayl;
X
X	nowdayl = (localtime(&now)->tm_hour+1) % 24;
X	fdayl = (localtime(&future)->tm_hour+1) % 24;
X	return (future-now) + 60L*60L*(nowdayl-fdayl);
X}
X
Xstatic char *lptr;
X
Xyylex()
X{
X	extern int yylval;
X	int sign;
X	register char c;
X	register char *p;
X	char idbuf[20];
X	int pcnt;
X
X	for (;;) {
X		while (isspace(*lptr)) lptr++;
X
X		if (isdigit(c = *lptr) || c == '-' || c == '+') {
X			if (c== '-' || c == '+') {
X				if (c=='-') sign = -1;
X				else sign = 1;
X				if (!isdigit(*++lptr)) {
X					/* yylval = sign; return (NUMBER); */
X					return yylex();	/* skip the '-' sign */
X				}
X			} else sign = 1;
X			yylval = 0;
X			while (isdigit(c = *lptr++)) yylval = 10*yylval + c - '0';
X			yylval *= sign;
X			lptr--;
X			return (NUMBER);
X
X		} else if (isalpha(c)) {
X			p = idbuf;
X			while (isalpha(c = *lptr++) || c=='.')
X				*p++ = c;
X			*p = '\0';
X			lptr--;
X			return (lookup(idbuf));
X		}
X
X		else if (c == '(') {
X			pcnt = 0;
X			do {
X				c = *lptr++;
X				if (c == '\0') return(c);
X				else if (c == '(') pcnt++;
X				else if (c == ')') pcnt--;
X			} while (pcnt > 0);
X		}
X
X		else return (*lptr++);
X	}
X}
X
Xstruct table {
X	char *name;
X	int type, value;
X};
X
Xstruct table mdtab[] = {
X	{"January", MONTH, 1},
X	{"February", MONTH, 2},
X	{"March", MONTH, 3},
X	{"April", MONTH, 4},
X	{"May", MONTH, 5},
X	{"June", MONTH, 6},
X	{"July", MONTH, 7},
X	{"August", MONTH, 8},
X	{"September", MONTH, 9},
X	{"Sept", MONTH, 9},
X	{"October", MONTH, 10},
X	{"November", MONTH, 11},
X	{"December", MONTH, 12},
X
X	{"Sunday", DAY, 0},
X	{"Monday", DAY, 1},
X	{"Tuesday", DAY, 2},
X	{"Tues", DAY, 2},
X	{"Wednesday", DAY, 3},
X	{"Wednes", DAY, 3},
X	{"Thursday", DAY, 4},
X	{"Thur", DAY, 4},
X	{"Thurs", DAY, 4},
X	{"Friday", DAY, 5},
X	{"Saturday", DAY, 6},
X	{0, 0, 0}};
X
X#define HRS *60
X#define HALFHR 30
Xstruct table mztab[] = {
X	{"a.m.", MERIDIAN, AM},
X	{"am", MERIDIAN, AM},
X	{"p.m.", MERIDIAN, PM},
X	{"pm", MERIDIAN, PM},
X	{"nst", ZONE, 3 HRS + HALFHR},		/* Newfoundland */
X	{"n.s.t.", ZONE, 3 HRS + HALFHR},
X	{"ast", ZONE, 4 HRS},		/* Atlantic */
X	{"a.s.t.", ZONE, 4 HRS},
X	{"adt", DAYZONE, 4 HRS},
X	{"a.d.t.", DAYZONE, 4 HRS},
X	{"est", ZONE, 5 HRS},		/* Eastern */
X	{"e.s.t.", ZONE, 5 HRS},
X	{"edt", DAYZONE, 5 HRS},
X	{"e.d.t.", DAYZONE, 5 HRS},
X	{"cst", ZONE, 6 HRS},		/* Central */
X	{"c.s.t.", ZONE, 6 HRS},
X	{"cdt", DAYZONE, 6 HRS},
X	{"c.d.t.", DAYZONE, 6 HRS},
X	{"mst", ZONE, 7 HRS},		/* Mountain */
X	{"m.s.t.", ZONE, 7 HRS},
X	{"mdt", DAYZONE, 7 HRS},
X	{"m.d.t.", DAYZONE, 7 HRS},
X	{"pst", ZONE, 8 HRS},		/* Pacific */
X	{"p.s.t.", ZONE, 8 HRS},
X	{"pdt", DAYZONE, 8 HRS},
X	{"p.d.t.", DAYZONE, 8 HRS},
X	{"yst", ZONE, 9 HRS},		/* Yukon */
X	{"y.s.t.", ZONE, 9 HRS},
X	{"ydt", DAYZONE, 9 HRS},
X	{"y.d.t.", DAYZONE, 9 HRS},
X	{"hst", ZONE, 10 HRS},		/* Hawaii */
X	{"h.s.t.", ZONE, 10 HRS},
X	{"hdt", DAYZONE, 10 HRS},
X	{"h.d.t.", DAYZONE, 10 HRS},
X
X	{"gmt", ZONE, 0 HRS},
X	{"g.m.t.", ZONE, 0 HRS},
X	{"bst", DAYZONE, 0 HRS},		/* British Summer Time */
X	{"b.s.t.", DAYZONE, 0 HRS},
X	{"eet", ZONE, 0 HRS},		/* European Eastern Time */
X	{"e.e.t.", ZONE, 0 HRS},
X	{"eest", DAYZONE, 0 HRS},	/* European Eastern Summer Time */
X	{"e.e.s.t.", DAYZONE, 0 HRS},
X	{"met", ZONE, -1 HRS},		/* Middle European Time */
X	{"m.e.t.", ZONE, -1 HRS},
X	{"mest", DAYZONE, -1 HRS},	/* Middle European Summer Time */
X	{"m.e.s.t.", DAYZONE, -1 HRS},
X	{"wet", ZONE, -2 HRS },		/* Western European Time */
X	{"w.e.t.", ZONE, -2 HRS },
X	{"west", DAYZONE, -2 HRS},	/* Western European Summer Time */
X	{"w.e.s.t.", DAYZONE, -2 HRS},
X
X	{"jst", ZONE, -9 HRS},		/* Japan Standard Time */
X	{"j.s.t.", ZONE, -9 HRS},	/* Japan Standard Time */
X					/* No daylight savings time */
X
X	{"aest", ZONE, -10 HRS},	/* Australian Eastern Time */
X	{"a.e.s.t.", ZONE, -10 HRS},
X	{"aesst", DAYZONE, -10 HRS},	/* Australian Eastern Summer Time */
X	{"a.e.s.s.t.", DAYZONE, -10 HRS},
X	{"acst", ZONE, -(9 HRS + HALFHR)},	/* Australian Central Time */
X	{"a.c.s.t.", ZONE, -(9 HRS + HALFHR)},
X	{"acsst", DAYZONE, -(9 HRS + HALFHR)},	/* Australian Central Summer */
X	{"a.c.s.s.t.", DAYZONE, -(9 HRS + HALFHR)},
X	{"awst", ZONE, -8 HRS},		/* Australian Western Time */
X	{"a.w.s.t.", ZONE, -8 HRS},	/* (no daylight time there, I'm told */
X	{0, 0, 0}};
X
Xstruct table unittb[] = {
X	{"year", MUNIT, 12},
X	{"month", MUNIT, 1},
X	{"fortnight", UNIT, 14*24*60},
X	{"week", UNIT, 7*24*60},
X	{"day", UNIT, 1*24*60},
X	{"hour", UNIT, 60},
X	{"minute", UNIT, 1},
X	{"min", UNIT, 1},
X	{"second", SUNIT, 1},
X	{"sec", SUNIT, 1},
X	{0, 0, 0}};
X
Xstruct table othertb[] = {
X	{"tomorrow", UNIT, 1*24*60},
X	{"yesterday", UNIT, -1*24*60},
X	{"today", UNIT, 0},
X	{"now", UNIT, 0},
X	{"last", NUMBER, -1},
X	{"this", UNIT, 0},
X	{"next", NUMBER, 2},
X	{"first", NUMBER, 1},
X	/* {"second", NUMBER, 2}, */
X	{"third", NUMBER, 3},
X	{"fourth", NUMBER, 4},
X	{"fifth", NUMBER, 5},
X	{"sixth", NUMBER, 6},
X	{"seventh", NUMBER, 7},
X	{"eigth", NUMBER, 8},
X	{"ninth", NUMBER, 9},
X	{"tenth", NUMBER, 10},
X	{"eleventh", NUMBER, 11},
X	{"twelfth", NUMBER, 12},
X	{"ago", AGO, 1},
X	{0, 0, 0}};
X
Xstruct table milzone[] = {
X	{"a", ZONE, 1 HRS},
X	{"b", ZONE, 2 HRS},
X	{"c", ZONE, 3 HRS},
X	{"d", ZONE, 4 HRS},
X	{"e", ZONE, 5 HRS},
X	{"f", ZONE, 6 HRS},
X	{"g", ZONE, 7 HRS},
X	{"h", ZONE, 8 HRS},
X	{"i", ZONE, 9 HRS},
X	{"k", ZONE, 10 HRS},
X	{"l", ZONE, 11 HRS},
X	{"m", ZONE, 12 HRS},
X	{"n", ZONE, -1 HRS},
X	{"o", ZONE, -2 HRS},
X	{"p", ZONE, -3 HRS},
X	{"q", ZONE, -4 HRS},
X	{"r", ZONE, -5 HRS},
X	{"s", ZONE, -6 HRS},
X	{"t", ZONE, -7 HRS},
X	{"u", ZONE, -8 HRS},
X	{"v", ZONE, -9 HRS},
X	{"w", ZONE, -10 HRS},
X	{"x", ZONE, -11 HRS},
X	{"y", ZONE, -12 HRS},
X	{"z", ZONE, 0 HRS},
X	{0, 0, 0}};
X
Xlookup(id) char *id;
X{
X#define gotit (yylval=i->value,  i->type)
X#define getid for(j=idvar, k=id; *j++ = *k++; )
X
X	char idvar[20];
X	register char *j, *k;
X	register struct table *i;
X	int abbrev;
X
X	getid;
X	if (strlen(idvar) == 3) abbrev = 1;
X	else if (strlen(idvar) == 4 && idvar[3] == '.') {
X		abbrev = 1;
X		idvar[3] = '\0';
X	}
X	else abbrev = 0;
X
X	if (islower(*idvar)) *idvar = toupper(*idvar);
X
X	for (i = mdtab; i->name; i++) {
X		k = idvar;
X		for (j = i->name; *j++ == *k++;) {
X			if (abbrev && j==i->name+3) return gotit;
X			if (j[-1] == 0) return gotit;
X		}
X	}
X
X	getid;
X	for (i = mztab; i->name; i++)
X		if (strcmp(i->name, idvar) == 0) return gotit;
X
X	for (j = idvar; *j; j++)
X		if (isupper(*j)) *j = tolower(*j);
X	for (i=mztab; i->name; i++)
X		if (strcmp(i->name, idvar) == 0) return gotit;
X
X	getid;
X	for (i=unittb; i->name; i++)
X		if (strcmp(i->name, idvar) == 0) return gotit;
X
X	if (idvar[strlen(idvar)-1] == 's')
X		idvar[strlen(idvar)-1] = '\0';
X	for (i=unittb; i->name; i++)
X		if (strcmp(i->name, idvar) == 0) return gotit;
X
X	getid;
X	for (i = othertb; i->name; i++)
X		if (strcmp(i->name, idvar) == 0) return gotit;
X
X	getid;
X	if (strlen(idvar) == 1 && isalpha(*idvar)) {
X		if (isupper(*idvar)) *idvar = tolower(*idvar);
X		for (i = milzone; i->name; i++)
X			if (strcmp(i->name, idvar) == 0) return gotit;
X	}
X
X	return(ID);
X}
X
Xtime_t getdate(p) char *p;
X{
X#define mcheck(f)	if (f>1) err++
X	time_t monthadd();
X	int err;
X	struct tm *lt;
X	struct timeb ftz;
X	extern long time_now;
X	extern unsigned int zone_offset;	/* in minutes */
X
X	time_t sdate, tod;
X
X	lptr = p;
X	lt = localtime(&time_now);
X	year = lt->tm_year;
X	month = lt->tm_mon+1;
X	day = lt->tm_mday;
X	relsec = 0; relmonth = 0;
X	timeflag=zoneflag=dateflag=dayflag=relflag=0;
X	ourzone = zone_offset;
X	daylight = MAYBE;
X	hh = mm = ss = 0;
X	merid = 24;
X
X	if (err = yyparse()) return (-1);
X
X	mcheck(timeflag);
X	mcheck(zoneflag);
X	mcheck(dateflag);
X	mcheck(dayflag);
X
X	if (err) return (-1);
X	if (dateflag || timeflag || dayflag) {
X		sdate = dateconv(month,day,year,hh,mm,ss,merid,ourzone,daylight);
X		if (sdate < 0) return -1;
X	}
X	else {
X		sdate = time_now;
X		if (relflag == 0)
X			sdate -= (lt->tm_sec + lt->tm_min*60 +
X				lt->tm_hour*(60L*60L));
X	}
X
X	sdate += relsec;
X	sdate += monthadd(sdate, relmonth);
X
X	if (dayflag && !dateflag) {
X		tod = dayconv(dayord, dayreq, sdate);
X		sdate += tod;
X	}
X
X	return sdate;
X}
X
Xyyerror(s) char *s;
X{}
END_OF_FILE
if test 12599 -ne `wc -c <'getdate.y'`; then
    echo shar: \"'getdate.y'\" unpacked with wrong size!
fi
# end of 'getdate.y'
fi
if test -f 'nrc.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'nrc.c'\"
else
echo shar: Extracting \"'nrc.c'\" \(16971 characters\)
sed "s/^X//" >'nrc.c' <<'END_OF_FILE'
X
X
X#include "nl.h"
X
X/*
X * These are the routines that handle the .newsrc file and all the
X * activities related to using it, including reading the active file and
X * the .nglas file.
X */
X
X /*
X  * Newsclip(TM) Library Source Code.
X  * Copyright 1989 Looking Glass Software Limited.  All Rights Reserved.
X  * Unless otherwise licenced, the only authorized use of this source
X  * code is compilation into a binary of the newsclip library for the
X  * use of licenced Newsclip customers.  Minor source code modifications
X  * are allowed.
X  * Use of this code for a short term evaluation of the product, as defined
X  * in the associated file, 'Licence', is permitted.
X  */
X
X
X
Xnewsgroup main_newsgroup = NULL_NEWSGROUP;/* the group we are looping through */
Xint article_number;		/* number of the current article */
Xextern dbptr ng_base;		/* the database of known newsgroup names */
X
Xextern int group_count;		/* count of newsgroups in database */
X
X
X/*
X * General initialization of newsgroup table
X */
X
X
X/* First the routine to read the 'las' file */
X
Xread_las()
X{
X
X	FILE *howfile;
X	char buf[MAX_NGLEN+20];		/* buffer for line */
X	char ngname[MAX_NGLEN];
X	char formats[10];
X	long maxnum;
X	ngrec *group;
X	extern char *lasname;		/* name of las file */
X	extern bool newsrc_allread;	/* mark all newsrc as read */
X
X	/* First read the "how far" file that notes the last article
X	 * seen by this program for each newsgroup in the .newsrc file.
X	 */
X	
X	sprintf( formats, "%%%ds %%lu\n", MAX_NGLEN );
X	
X	howfile = fopen( lasname, "r" );
X
X	if( !howfile ) {
X		warning( 4, "LAS file %s missing\n", lasname );
X		return;
X		}
X
X	while( fgets( buf, sizeof(buf), howfile ) ) {
X		maxnum = 0;
X		/* the maxnum entry might not be read if the last article
X		   seen number is not present */
X		sscanf( buf, formats, ngname, &maxnum );
X		group = (ngrec *)add_rec( ng_base, ngname, AR_CREATE );
X		if( group->ngnumber == 0 )
X			group->ngnumber = group_count++;
X		group->las = (int32)maxnum;
X		group->gflag |= GF_LASGROUP;
X		}
X
X	fclose( howfile );
X
X}
X
X
Xngrec *rc_chain;		/* start of chain of groups from .newsrc */
X
X
Xread_newsrc()
X{
X	FILE *rcfile;			/* stream of the newsrc file */
X	char buf[MAX_LLEN];		/* line buffer */
X	char *subchar;			/* position of colon in line */
X	ngrec *group;			/* record for this group */
X	ngrec *lastgroup;		/* record for previous group */
X	int len;			/* length of seen list */
X	char *bslist;			/* seen list in buffer */
X	char *slist;			/* final seen list */
X	extern bool only_las;		/* only do groups in LAS file */
X	extern bool allow_unsub;	/* allow unsubscribed groups? */
X	bool unsub;			/* subscribed? */
X	extern char *newsrcname;	/* newsrc file */
X
X
X	/* Lock access to the .newsrc? */
X
X	rcfile = mustopen( newsrcname, "r", ".newsrc file" );
X	lastgroup = (ngrec *)0;
X
X	while( fgets( buf, sizeof(buf), rcfile ) ) {
X		/* skip option lines */
X		if( strncmp( buf, "options ", 8 ) == 0 || isspace(buf[0]) ||
X					buf[0] == '<' )
X			continue;
X		unsub = FALSE;
X		subchar = strchr( buf, ':' );
X		if( subchar == NULL ) {
X			if( allow_unsub ) {
X				subchar = strchr( buf, '!' );
X				unsub = subchar != NULL;
X				}
X			if( subchar == NULL )
X				continue;	/* not a newsgroup line */
X			}
X		*subchar = 0;		/* terminate group name */
X		if( only_las ) {
X			group = (ngrec *)get_rec(ng_base, buf);
X			if( !group || !(group->gflag & GF_LASGROUP ) )
X				continue;
X			}
X		 else
X			group = (ngrec *)add_rec( ng_base, buf, AR_CREATE );
X		if( group->ngnumber == 0 )
X			group->ngnumber = group_count++;
X		group->gflag |= GF_RCGROUP;		/* group from .newsrc*/
X
X		/* build the RC order chain */
X		if( lastgroup ) 
X			lastgroup->chain = group->ngnumber;
X		 else
X			rc_chain = group;
X		lastgroup = group;
X		if( unsub )
X			group->gflag |= GF_UNSUB;
X		/* now add the line from the .newsrc */
X
X		bslist = whitestrip( subchar+1 );
X			
X		len = strlen( bslist );
X		if( bslist[len-1] != '\n' ) {
X			long seekback;
X			int extras;
X			char c;
X			/* the line was too long for the buffer! */
X			/* find out how long it really is */
X			seekback = ftell( rcfile );
X			for( extras = 0; (c = getc(rcfile)) != EOF && c != '\n';
X							extras++ )
X					;
X			/* go back and read it in for real */
X			fseek( rcfile, seekback, 0 );
X			slist = perm_alloc( len + extras + 1+1 + SLIST_EXTRAS );
X			strcpy( slist, bslist );
X			fgets( slist+len, extras, rcfile );
X			len += extras + 1;
X			if( slist[len-1] == '\n' )
X				slist[--len] = 0;
X			}
X		 else {
X			slist = perm_alloc( len + 1 + SLIST_EXTRAS );
X			strcpy( slist, bslist );
X			slist[--len] = 0;
X			}
X		group->seenlist = slist;
X		group->slistlen = len + SLIST_EXTRAS;
X		}
X	fclose( rcfile );		/* for now */
X
X}
X
X
X#define set_bit( map, i )	map[i>>3] |= 1 << (i&7)
X#define is_bit_set( map, i )	((map[i>>3] >> (i&7)) & 1)
X#define clear_bit( map, i )	map[i>>3] &= ~(1 << (i&7))
X
X/*
X * Translate the SEEN list of a newsgroup to a bitmap
X * Bitmap maximum size 32767 bits or 4K bytes with 16 bit int -- no problem.
X */
X
Xint
Xseen_to_bitmap( group, bitmap, bmsize, num_unseen )
Xngrec *group;		/* newsgroup, with seen article list */
Xchar *bitmap;
Xint bmsize;		/* max size of bitmap */
Xint *num_unseen;	/* return number of unread articles */
X{
X	int bsize;
X	char *p;		/* pointer into list */
X	int num_new_seen;
X
X	
X	bsize = group->highest - group->lowest + 1;
X	if( bsize < 0 || (bsize+7)/8 > bmsize )
X		return -1;
X
X	/* clear out the bitmap first */
X	zero( bitmap, bmsize );
X	num_new_seen = 0;
X
X	p = group->seenlist;
X
X	while( *p ) {
X		int32 first,last;
X
X		first = atoi32(p);
X		while( isdigit(*p) )
X			p++;
X		if( *p == '-' ) {
X			last = atoi32(++p);
X			/* make sure last is within bounds of group */
X			last = min( last, group->highest );
X			while( isdigit(*p) )
X				p++;
X			}
X		 else
X			last = first;
X		if( last >= group->lowest && last >= first ) {
X			int topbit, i;
X			/* move first up to within group */
X			first = max( first, group->lowest );
X			topbit = last - group->lowest;
X			/* set all the bits for read articles */
X			for( i = first - group->lowest; i <= topbit; i++ ) {
X				set_bit( bitmap, i );
X				if( i + group->lowest > group->las )
X					num_new_seen++;
X				}
X			}
X		if( *p == ',' )
X			p++;
X		}
X	if( num_unseen )
X		*num_unseen = group->highest - group->las - num_new_seen;
X	return bsize;
X}
X
X/* scan through the newsrc for unread articles and process them */
X
Xdo_newsrc()
X{
X	ngrec *group;
X	char bitmap[BITMAP_SIZE];
X	char newsfile[MAX_FNAME];
X	int bsize;			/* number of bits in bitmap */
X	int art;			/* article number (less lowest)*/
X	int dirty;			/* was this article marked read? */
X	extern char *news_spool_dir;
X	extern bool list_always;	/* always list articles */
X	int num_unread;			/* number of unread articles */
X	extern bool newsrc_allread;	/* mark all articles read */
X	bool foundone;			/* we liked an article here */
X	extern int reading_mode;
X
X	reading_mode = FILE_FULL;
X
X	for( group = rc_chain; group; group = ngarray[group->chain] ) {
X
X		/* test if group was found in active file */
X		if( !( group->gflag & GF_ACTIVE ) )
X			continue;
X
X		/* adjust last article seen */
X
X		group->las = max( group->lowest-1, group->las );
X
X		if( group->las >= group->highest )
X			continue;		/* all messages already done*/
X		dirty = FALSE;
X		foundone = FALSE;
X		
X		bsize = seen_to_bitmap( group, bitmap, sizeof(bitmap),
X							&num_unread );
X		if( bsize < 0 ) {
X			warning( 1,"Invalid Active file line for %s\n",
X				group->key);
X			continue;		/* error on this group */
X			}
X		
X		/* if no unread articles, don't bother, but update las */
X		if( num_unread <= 0 ) {
X			group->las = group->highest;
X			continue;
X			}
X
X		main_newsgroup = group->ngnumber;
X
X		Ustartgroup( num_unread );
X
X		/* Here is the actual loop were we call the user code to
X		   accept or reject the acticle */
X
X
X		for( art = group->las - group->lowest+1 ; art < bsize; art++ ) {
X			if( !is_bit_set( bitmap, art ) ) {
X				/* an unread article! */
X				reset_tempalloc();
X				sprintf( newsfile, "%s/%s/%lu", news_spool_dir,
X						ngdir(group->key),
X						(long)(art + group->lowest) );
X				article_number=makeint((long)art+group->lowest);
X				main_newsgroup = group->ngnumber;
X				if( !accept_article( newsfile ) ) {
X					extern array *xref;
X					set_bit( bitmap, art );
X					dirty = TRUE;
X					if( xref && xref->arsize > 2 )
X						kill_xrefs( main_newsgroup );
X					}
X				 else {
X					/* in list mode, assure we don't
X					   list cross postings */
X					if( list_always ) {
X						extern array *xref;
X						printf( "%s\n", newsfile );
X						if( xref && xref->arsize > 2 )
X							kill_xrefs(
X								main_newsgroup);
X						}
X					/* mark it read anyway in batch mode */
X					if( newsrc_allread ) {
X						set_bit( bitmap, art );
X						dirty = TRUE;
X						}
X					foundone = TRUE;
X					}
X				}
X			}
X		/* bitmap processed */
X		group->las = group->highest;
X		/* now turn bitmap into seen list again */
X		if( dirty ) {
X			bitmap_to_seen( bitmap, group, bsize );
X			group->gflag |= GF_DIRTY;
X			}
X		if( foundone )
X			/* turn off unsub it if it was on, resubscribe group */
X			group->gflag &= ~GF_UNSUB;
X		finish_group( );
X		}
X						
X
X}
X
X#define digits(n)   (n >= 10000 ? ( n >= 100000 ? 6 : 5 ) : n >= 100 ? (n >= 1000 ? 4 : 3) : ( n < 10 ? 1 : 2 ) )
X
X/* convert a bitmap to a seen list, return pointer to that.
X * (A 'seen list' is the string of numbers found in a .newsrc entry.
X */
X
X
Xbitmap_to_seen( bitmap, group, numbits )
Xchar *bitmap;		/* the bitmap itself */
Xngrec *group;		/* group record */
Xint numbits;		/* the number of bits in the bitmap */
X{
X	int i;
X	int size;		/* size of output string */
X	bool oldstat;		/* status of old bit */
X	int32 oldnum;		/* start of stream of read articles */
X	bool bit;		/* bit in bitmap */
X	char *p;		/* pointer storing slist */
X	char *slist;		/*new seen list */
X	int32 lowest;		/* lowest article in group */
X
X
X	oldnum = 1;
X	size = 0;
X	/* clear the bit off the end */
X	clear_bit( bitmap, numbits );
X
X	lowest = group->lowest;
X
X	/* set stat of 'previous' bit */
X	oldstat = lowest > 1;
X	for( i = 0; i <= numbits; i++ ) {
X		bit = is_bit_set( bitmap, i );
X		if( bit != oldstat ) {
X			oldstat = bit;
X			if( bit ) 
X				oldnum = i + lowest;
X			 else {
X				size += 1 + digits(oldnum);
X				if( i + lowest > oldnum+1 )
X					size += 1 + digits( i + lowest-1 );
X				}
X			}
X		}
X	if( size >= group->slistlen ) {
X		perm_free( group->seenlist );
X		group->seenlist = perm_alloc( size+SLIST_EXTRAS+1 );
X		group->slistlen = size+SLIST_EXTRAS;
X		}
X
X	/* now repeat the procedure, storing this time */
X	/* set stat of 'previous' bit */
X
X	p = group->seenlist;
X	oldnum = 1;
X
X	oldstat = lowest > 1;
X	for( i = 0; i <= numbits; i++ ) {
X		bit = is_bit_set( bitmap, i );
X		if( bit != oldstat ) {
X			oldstat = bit;
X			if( bit ) 
X				oldnum = i + lowest;
X			 else {
X				sprintf( p, "%lu", (long)oldnum );
X				p += digits(oldnum);
X				if( i + lowest > oldnum+1 ) {
X					sprintf(p,"-%lu",(long)i+lowest-1);
X					p += strlen(p);
X					}
X				*p++ = ',';
X				}
X			}
X		/* sanity check */
X		if( p - group->seenlist >= group->slistlen ) {
X			fprintf( stderr,"Seen list %d bytes too big, only %d\n",
X					p-group->seenlist, group->slistlen);
X			fprintf(stderr,"%s: %s\n", group->key, group->seenlist);
X			abort();
X			}
X		}
X	if( p[-1] == ',' )
X		p[-1] = 0;
X	 else
X		p[0] = 0;
X
X}
X
X/* structure for subscribe list */
X
Xstruct sublist {
X	char *gname;
X	struct sublist *next;
X	} *newgroups;
X
X/* subscribe to a group for the next time around - for newsrc modes only */
X
Xsubscribe( grp )
Xchar *grp;
X{
X	ngrec *group;
X	struct sublist *sptr;
X
X	/* first see if we know about it already */
X	group = (ngrec *)get_rec( ng_base, grp );
X	if( group && group->gflag & GF_RCGROUP ) {
X		/* turn off unsubscribed flag, if on */
X		group->gflag &= ~GF_UNSUB;
X		group->gflag |= GF_DIRTY;
X		}
X	 else {
X		/* list this group for later concat to .newsrc */
X
X		/* scan to see if already present */
X		for( sptr = newgroups; sptr; sptr = sptr->next )
X			if( strcmp( sptr->gname, grp ) == 0 )
X				return;
X		sptr = (struct sublist *)perm_alloc( sizeof(struct sublist) );	
X		sptr->gname = allocstring(grp);
X		/* stick on front of list */
X		sptr->next = newgroups;
X		if( newgroups == 0 )
X			newgroups = sptr;
X		}
X
X}
X
X
X
X/* Write out a new .newsrc to the specified descriptor.  Return true
X   if a write error, false if all is well.  The descriptor must not
X   point to the real, old .newsrc which is used to contruct the new one.
X   */
X
Xint
Xwrite_newsrc( desc )
XFILE *desc;			/* descriptor of output .newsrc */
X{
X	FILE *oldrc;		/* old .newsrc we replace */
X	char rbuf[MAX_LLEN];
X	char *subchar;
X	ngrec *group;
X	extern bool allow_unsub;	/* allow unsubscribed groups */
X	bool we;			/* write error flag */
X	extern char *strpbrk();
X	extern char *newsrcname;
X	struct sublist *sptr;
X
X
X	oldrc = mustopen( newsrcname, "r", ".newsrc file for update" );
X	we = FALSE;
X
X	while( !we && fgets( rbuf, sizeof(rbuf), oldrc ) ) {
X		subchar = strpbrk( rbuf, ":! \t," );
X		if( !isspace(rbuf[0]) && subchar && (*subchar == ':' ||
X					(allow_unsub && *subchar == '!') ) ) {
X			char oldchar;		
X			/* option lines have no colon or ! */
X			oldchar = *subchar;
X			*subchar = 0;
X			group = (ngrec *)get_rec( ng_base, rbuf );
X			if( group && group->gflag & GF_DIRTY ) {
X				we |= fprintf( desc, "%s%c %s\n", group->key,
X						group->gflag&GF_UNSUB ? '!':':',
X						group->seenlist ) < 0;
X				continue;
X				}
X			 else
X				*subchar  = oldchar;
X			}
X		/* was not modified, so just copy what's there */
X		we |= fputs( rbuf, desc ) == EOF;
X		/* loop to copy extra parts of long lines */
X		while( rbuf[strlen(rbuf)-1] != '\n' && fgets( rbuf,
X					sizeof(rbuf), oldrc ) )
X			we |= fputs( rbuf, desc ) == EOF;
X		}
X	/* write out new groups to subscribe to */
X	for( sptr = newgroups; sptr; sptr = sptr->next )
X		we |= fprintf( desc, "%s:\n", sptr->gname ) < 0;
X	
X	fclose( oldrc );
X	fclose( desc );
X	return we;
X}
X
X/* Output a new .nglas file */
X
Xwrite_las()
X{
X	FILE *lasfile;
X	ngrec *group;
X	bool we;			/* write error */
X	extern char *lasname;		/* name of las file */
X	struct sublist *sptr;
X
X	lasfile = fopen( lasname, "w" );
X
X	if( !lasfile ) {
X		warning( 1, "Could not open file %s\n", lasname );
X		return;
X		}
X
X	we = FALSE;
X
X	for( group = rc_chain; group && !we; group = ngarray[group->chain] )
X		we |= fprintf( lasfile, "%s %lu\n", group->key,
X					(long)group->las ) < 0;
X	/* write out new groups to subscribe to */
X	for( sptr = newgroups; sptr; sptr = sptr->next )
X		we |= fprintf( lasfile, "%s 0\n", sptr->gname ) < 0;
X	fclose( lasfile );
X	if( we )
X		warning( 1, "Unable to write file %s\n", lasname );
X
X}
X
X
X/* the master loop for .newsrc mode */
X
X
Xprocess_newsrc()
X{
X	FILE *nrdesc;				/* newsrc descriptor */
X	extern bool out_newsrc;		/* do we write a .newsrc? */
X	extern char *newsrcname;
X	extern char *temprc;		/* temporary write location */
X	extern bool newsrc_allread;	/* newsrc to be marked all read? */
X
X	/* init the newsgroups list and read in the data */
X	initngs(TRUE);
X
X	Uinit();
X
X	/* go through the .newsrc and process the articles */
X	do_newsrc();
X
X	Uterminate();
X
X	if( !out_newsrc )
X		return;
X
X	/* Now write out the .newsrc file */
X
X	nrdesc = mustopen( temprc, "w", "temporary .newsrc for update" );
X
X	if( !nrdesc || write_newsrc( nrdesc ) ) {
X		char pidbuf[40];
X
X		warning( 0, "Could not write out .newsrc\n" );
X		sprintf( pidbuf, "/tmp/nr%d", getpid() );
X		nrdesc = mustopen( pidbuf, "w", "emergency .newsrc" );
X		if( !nrdesc || write_newsrc( nrdesc ) ) 
X			warning( 0, "Attempt to write to %s failed\n",
X							pidbuf );
X		 else
X			warning( 0, "Copy written to %s\n", pidbuf );
X		}
X	 else {
X		/* release the old .newsrc */
X		if( unlink( newsrcname ) )
X			warning( 0, "Could not unlink old %s\n", newsrcname );
X		 else {
X			if( link( temprc, newsrcname ) )
X				warning( 0, "Could not link in %s\n", temprc );
X			 else
X				unlink( temprc );
X			}
X		write_las();
X		}
X	
X}
X
X/* Mark articles listed as cross references as read in their groups */
X
Xkill_xrefs( mgroup )
Xnewsgroup mgroup;				/* main newsgroup */
X{
X	extern array *xref;			/* array of xrefs */
X	extern char *sitename;			/* short name of system */
X	int i;
X	char bitmap[BITMAP_SIZE];
X	int num_unread;				/* a dummy */
X	int bsize;				/* size of bitmap */
X	int32 artbase;
X
X	if( xref && cleq( xref->vals[0].ustring, sitename ) ) 
X		for( i = 1; i < xref->arsize; i++ ) {
X			ngrec *group;
X			char *colon;
X			char *xrpat;
X
X			xrpat = xref->vals[i].ustring;
X			colon = strchr( xrpat, ':' );
X			if( colon ) {
X				*colon++ = 0;
X				group = (ngrec *)get_rec( ng_base, xrpat );
X				/* if not a subscribed group or the current
X				   group, then skip this one */
X				if( group == 0 || group->ngnumber == mgroup ||
X						!(group->gflag & GF_RCGROUP) )
X					continue;
X				artbase = atol(colon) - group->lowest;
X				if( artbase < 0 )
X					continue;
X				/* ok, we have an article to mark unread */
X				bsize = seen_to_bitmap( group, bitmap,
X					sizeof(bitmap), &num_unread );
X				/* if no change, keep going */
X				if(artbase>bsize||is_bit_set(bitmap,artbase))
X					continue;
X				/* set the bit */
X				set_bit( bitmap, artbase );
X				/* put back new bitmap */
X				bitmap_to_seen( bitmap, group, bsize );
X				group->gflag |= GF_DIRTY;
X				}
X			}
X}
END_OF_FILE
if test 16971 -ne `wc -c <'nrc.c'`; then
    echo shar: \"'nrc.c'\" unpacked with wrong size!
fi
# end of 'nrc.c'
fi
echo shar: End of archive 8 \(of 15\).
cp /dev/null ark8isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 15 archives.
    rm -f ark[1-9]isdone ark[1-9][0-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0