[comp.sources.misc] v09i074: newsclip 1.1, part 5 of 15

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

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

#! /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 5 (of 15)."
# Contents:  comp/out.c doc/nclip.1 header.c samples/feed.nc userdb.c
# Wrapped by allbery@uunet on Tue Dec 19 20:09:56 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'comp/out.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'comp/out.c'\"
else
echo shar: Extracting \"'comp/out.c'\" \(9187 characters\)
sed "s/^X//" >'comp/out.c' <<'END_OF_FILE'
X
X/*
X * Output C code from generated and typechecked code trees.
X */
X /*
X  * Newsclip(TM) Compiler 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 compiler for the
X  * use of licenced Newsclip customers.  Minor source code modifications
X  * are allowed before compiling.
X  * A short time evaluation of this product is also permitted.  See the file
X  * 'Licence' in the library source directory for details.
X  */
X
X#include "nc.h"
X
Xint indent_level = 0;		/* how far to indent each line */
X
Xout( tp )
Xnodep tp;			/* the tree pointer */
X{
X	extern struct node_info node_table[];
X	extern char *union_names[];	/* names of gen type union members */
X	extern char *typabbrevs[];	/* short names for types */
X	int nt;			/* node type */
X	char *tail;		/* stuff to print after printing the node */
X	dtype otype;
X
X	if( !tp )
X		return;
X
X	nt = tp->ntype;
X	tail = "";
X
X	/* Output the special casting functions based on type change marks */
X
X	switch( tp->nflags & CAST_MASK ) {
X		case CAST_NGNAME:
X			cout( "ngn(" );
X			tail = ")";
X			break;
X		case CAST_MAILNAME:
X			cout( "mailname(" );
X			tail = ")";
X			break;
X		case CAST_DATE:
X			cout( "(datehold)(" );
X			tail = ")";
X			break;
X		case CAST_INT:
X			cout( "(int)(" );
X			tail = ")";
X			break;
X		}
X
X	/* deal with the special cases */
X	switch( nt ) {
X		case N_LIST:
X			listprint( (listp)tp, "\n" );
X			return 0;
X		case N_FOREACH:
X			{
X			dtype atype;
X			dtype vtype;
X
X			atype = kid1(tp)->ndtype;
X			vtype = kid0(tp)->ndtype;
X			if( insist_variable( kid0(tp) ) )
X				break ;
X
X			if( atype & T_ARRAY ) {
X				extern char *union_names[];
X				codeprint( tp, "{int i;\n" );
X				codeprint( tp, "array *thear;\n" );
X				codeprint( tp, "thear = !1t;\n" );
X				codeprint( tp, "for( i = 0; !0t = thear->vals[i]." );
X				codeprint( tp, union_names[atype&T_BASETYPE] );
X				codeprint( tp, ", i < thear->arsize; i++ )!>\n" );
X				out( kid2(tp) );
X				codeprint( tp, "\n!<}" );
X				}
X			 else if( atype == T_DATABASE ) {
X				codeprint( tp, "{dbptr base;\n" );
X				codeprint( tp, "userdb *dbrec;\n" );
X				codeprint( tp, "base = !1t;\n" );
X				codeprint( tp, "for( dbrec = ufirst_rec(base); dbrec && (!0t = dbrec->name); dbrec = unext_rec(base,dbrec) )!>\n" );
X				out( kid2(tp) );
X				codeprint( tp, "\n!<}" );
X				}
X			break ;
X			}
X		case N_INDEX:
X			{
X			dtype k0t;
X			k0t = kid0(tp)->ndtype;
X			if( k0t == T_DATABASE ) {
X				if( tp->nflags & NF_LVALUE )
X					codeprint( tp,
X						"(db_create(!0t,!1t)->intval)");
X				 else
X					codeprint( tp,
X						"db_lookup(!0t,!1t)" );
X				}
X			 else {
X				
X				codeprint( tp, "!0t->vals[!1t]." );
X				cout( union_names[ kid0(tp)->ndtype
X								& T_BASETYPE ]);
X				}
X			break;
X			}
X		case N_EQ:
X			if( kid0(tp)->ndtype == T_STRING )
X				codeprint( tp, "str_eq(!0t,!1t)" );
X			 else
X				codeprint( tp, "!0t == !1t" );
X			break;
X		case N_NE:
X			if( kid0(tp)->ndtype == T_STRING )
X				codeprint( tp, "!!str_eq(!0t,!1t)" );
X			 else
X				codeprint( tp, "!0t !!= !1t" );
X				
X			break;
X		case N_NOT_IN:
X			coutc( '!' );
X		case N_IN:
X			{
X			dtype k0t;
X
X			k0t = kid0(tp)->ndtype;
X			if( kid1(tp)->ndtype == T_DATABASE ) {
X				if( k0t & T_ARRAY )
X					codeprint(tp,"arr_in_db(!0t,!1t)");
X				 else
X					codeprint(tp,"str_in_db(!0t,!1t)" );
X				}
X			 else {
X				/* must be a search in array */
X				/* arrays carry their types with them */
X				if( k0t & T_ARRAY )
X					cout("arr");
X				 else
X					cout(typabbrevs[k0t]);
X				codeprint( tp, "_in_arr(!0t,!1t)" );
X				}
X			}
X			break;
X		case N_NOT_HAS:
X			coutc( '!' );
X		case N_HAS: {
X			dtype k0t,k1t;
X
X			k0t = kid0(tp)->ndtype;
X			if( (k0t & T_ARRAY) )
X				cout( "arr" );
X			 else 
X				cout( typabbrevs[k0t] );
X			cout( "_has_" );
X			k1t = kid1(tp)->ndtype;
X			if( kid1(tp)->ntype == N_PATTERN )
X				cout( "pat" );
X			 else if( k1t & T_ARRAY )
X				cout( "arr" );
X			 else
X				cout( typabbrevs[k1t] );
X			codeprint( tp, "(!0t,!1t)" );
X				
X			break;
X			}
X		case N_PARSE:
X			cprintf( "uprepare_var(%d,",kid0(tp)->ndtype );
X			if( kid2(tp) )
X				codeprint( tp, "&!0t,!1t,!2t)" );
X			 else
X				codeprint( tp, "&!0t,!1t,(char *)0)" );
X			break;
X		case N_ARINIT:
X			codeprint( tp, "!0t = fresh_array(!1t," );
X			cprintf( "%d)",kid0(tp)->ndtype );
X			break;
X		case N_CALL:
X			{
X			extern int in_routine;
X			if( ((symptr)kid0(kid0(tp)))->sflags & SF_LOCAL &&
X							in_routine != ST_FUNC )
X				codeprint( tp, "{!0t(!1,); if( score == Accept || score == Reject ) return;}" );
X			 else
X				codeprint( tp, "!0t(!1,);" );
X			}
X			break;
X		default:
X			codeprint( tp, node_table[nt].printcode );
X			break;
X		}
X
X	cout( tail );
X
X}
X
X
Xcodeprint( tp, code )
Xnodep tp;			/* node being printed */
Xchar *code;			/* code to print */
X{
X	register char *cp;		/* pointer into print code */
X	nodep kid;
X
X	for( cp = code; *cp; cp++ )
X		if( *cp == '!' ) {
X			if( *++cp >= '0' && *cp <= '9' )
X				kid = tp->kids[*cp++-'0'];
X			switch( *cp ) {
X				case 0:
X				case '!':
X					coutc( '!' );
X					break;
X				case 't':
X					out( kid );
X					break;
X				case 'd':
X					if( kid ) {
X						kid = kid0(kid);
X						typeprint(((symptr)kid)->type);
X						coutc( ' ' );
X						}
X					 else
X						break;
X				case 'i':
X					/* preface user names with a 'U' */
X					locout( (symptr)kid );
X					break;
X				case 'n':
X				case 'N':
X				case 'P':
X					{
X					char ibuf[20];
X					sprintf( ibuf, "%d", (int)kid );
X					cout( ibuf );
X					break;
X					}
X				case '$':
X					/* for now  worry about escapes later */
X					patrout( (char *)kid );
X					break;
X				case 'l':
X					listprint( (listp)kid, "\n" );
X					break;
X				case ',':
X					listprint( (listp)kid, ", " );
X					break;
X				case '<':
X					if( indent_level > 0 )
X						--indent_level;
X					break;
X				case '>':
X					++indent_level;
X					break;
X				default:
X					coutc( *cp );
X				}
X			}
X		 else
X			coutc( *cp );
X				
X}
X
X
Xchar *union_names[] = {
X"uinteger",
X"uinteger",
X"udate",
X"uinteger",
X"ustring",
X"uusername"
X};
X
Xchar *typenames[] = {
X"",
X"int",
X"datehold",
X"newsgroup",
X"char *",
X"username *",
X"dbptr",
X"text"
X};
X
Xchar *typabbrevs[] = {
X"nul",
X"int",
X"date",
X"ng",
X"str",
X"name",
X"db",
X"text"
X};
X
X
X/* print the name of a type */
X
Xtypeprint( type )
Xdtype type;
X{
X	if( type & T_ARRAY )
X		cout( "array *" );
X	 else
X		cout( typenames[ type & T_BASETYPE ] );
X}
X
X/* output a byte to the C output file */
X
Xcoutc( ch )
Xchar ch;
X{
X	extern FILE *outstream;
X
X	if( ch == '\n' )
X		newline();
X	 else
X		putc( ch, outstream );
X}
X
X/* output a string to the C output file */
X
Xcout( str )
Xchar *str;
X{
X	register char *p;
X	extern FILE *outstream;
X
X	for( p = str; *p; p++ ) {
X		if( *p == '\n' )
X			newline();
X		 else
X			putc( *p, outstream );
X		}
X}
X
X/* a new line on the output stream */
X
Xnewline()
X{
X	int i;
X	extern FILE *outstream;
X
X
X	putc( '\n', outstream );
X	for( i = 0; i < indent_level; i++ )
X		putc( '\t', outstream );
X}
X
Xlistprint( lp, delim )
Xlistp lp;		/* head of the list */
Xchar *delim;		/* delimiter to put between lists */
X{
X	listp ourlist;
X
X	for( ourlist = lp; lp; lp = lp->next ) {
X		out( lp->kid );
X		if( lp->next )
X			cout( delim );
X		}
X
X}
X
Xoutsubr( thesym, atlist, code )
Xsymptr thesym;			/* id of the subroutine */
Xlistp atlist;			/* argument list */
Xnodep code;			/* internal code */
X{
X	listp sclist;
X
X	indent_level = 0;
X	if( thesym->decl_type != ST_PROC ) {
X		typeprint( thesym->type );
X		coutc( '\n' );
X		}
X	locout( thesym );
X	coutc( '(' );
X	for( sclist = atlist; sclist; sclist = sclist->next ) {
X		nodep decvar;
X		nodep decid;
X		symptr idsym;
X
X		if( (decvar = sclist->kid) && (decid = kid0(decvar)) &&
X				(idsym = (symptr)kid0(decid)) ) {
X			locout( idsym );
X			if( sclist->next )
X				cout( ", " );
X			}
X		}
X	cout( ")\n" );
X	/* now type declare the args */
X	for( sclist = atlist; sclist; sclist = sclist->next ) {
X		nodep decvar;
X		nodep decid;
X		symptr idsym;
X
X		if( (decvar = sclist->kid) && (decid = kid0(decvar)) &&
X				(idsym = (symptr)kid0(decid)) ) {
X			typeprint(idsym->type);
X			coutc( ' ' );
X			locout( idsym );
X			cout( ";\n" );
X			}
X		}
X	indent_level++;
X	cout( "{\n" );
X	out( code );
X	indent_level = 0;
X	cout( "\n}\n" );
X
X}
X
X/* output a local symbol name */
X
Xlocout( sym )
Xsymptr sym;
X{
X	if( sym->sflags & SF_LOCAL )
X		coutc( 'U' );
X	cout( sym->name );
X}
X
X/* output a string that might be a pattern.
X   We read the string from our source, and it contains backslashes,
X   expected to act as C escapes.  Those will be \\, \" and backslash
X   followed by a letter or an octal number.   These codes will be
X   put into the C source as is, so that they turn into the desired
X   escapted character.
X
X   If a backslash precedes another character, we will assume that the
X   user wanted a real backslash, to escape something for pattern matching.
X   We thus double the backslash
X
X   There will be no newslines in this string.
X */
X
Xpatrout( str )
Xchar *str;
X{
X	extern FILE *outstream;
X	bool escaped;
X	char *p;
X
X	putc( '"', outstream );
X
X	escaped = FALSE;
X
X	for( p = str; *p; p++ ) {
X		putc( *p, outstream );
X		if( *p == '\\' && !escaped ) {
X			if( !isalnum(p[1]) && p[1] != '"' && p[1] != '\\' )
X				putc( '\\', outstream );
X			escaped = TRUE;
X			}
X		 else
X		 	escaped = FALSE;
X		}
X
X	putc( '"', outstream );
X	
X}
X
END_OF_FILE
if test 9187 -ne `wc -c <'comp/out.c'`; then
    echo shar: \"'comp/out.c'\" unpacked with wrong size!
fi
# end of 'comp/out.c'
fi
if test -f 'doc/nclip.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'doc/nclip.1'\"
else
echo shar: Extracting \"'doc/nclip.1'\" \(8698 characters\)
sed "s/^X//" >'doc/nclip.1' <<'END_OF_FILE'
X.TH NCC 1
X.SH NAME
Xnclip - Newsclip(TM) Filter Program Options
X.SH SYNOPSIS
X.B nclip
X[mode=operational-mode] [ options ]
X.SH DESCRIPTION
X.I Nclip
Xprograms are those generated by the ncc(1) compiler.  (They need not have
Xthe name `nclip,' that is merely the default.)  These programs are news
Xfilters, which can control which articles in the USENET news system
Xwill be presented to the user for reading.
X.PP
XNews clipping programs allow you describe USENET news articles that you wish
Xto read or not read with the various news reading programs found on USENET
Xsystems such as readnews(1).  You can describe expressions, patterns and
Xgeneral program constructs to accept or reject news articles as desired.
XAs
X.I newsclip
Xis a fairly full featured programming language, you can filter your
Xarticles with descriptions as simple or as involved as you like.
X.PP
XThe compiled programs can be used in a number of ways.  These are
Xdescribed below.
XThe details and syntax of the language are described in the
X.I newsclip
Xsystem manuals.
X.SH OPTIONS
X(Note that while option names are displayed here in full, only the first
Xletter is actually required.  For +/\- options, using + turns the option on,
Xand using \- turns the option off.)   The first option sets the mode of
Xoperation for the news filtering program.
X.TP
X.B mode=filter
XThe program takes a list of filenames on the standard input, and outputs
Xa subset of that list on the standard output.  The filenames are assumed
Xto be the pathnames of USENET news article files, such as are found in
Xthe news spool directories.  For each file in the list, the nclip program
Xexamines the associated article, and outputs the filename on the
Xstandard output if the article is accepted according to the newsclip
Xprogram.  This is useful for filtering a set of articles that are to
Xbe batched and sent to another system.
X.TP
X.B mode=newsrc
XThe program examine's the user's
X.I .newsrc
Xfile (the subscription file used by readnews(1), rn(1) and many other
Xnewsreaders) for unread news articles.  It then examines those articles
Xwith the compiled newsclip program, and marks rejected articles as read,
Xso that the user will not see them in a newsreading session.  The
X.I .newsrc
Xfile is updated.   In this mode, the program also keeps a file called
X.I .newsrclas
Xin the same directory as the .newsrc file.  In this file, it keeps track
Xof the highest article that it has processed in each newsgroup, so that
Xit does not have to process accepted articles again on each invocation.
XThis mode of operation can't be used at the same time as a newsreader is
Xrunning.
X.TP
X.B mode=list
XThis mode is just like the
X.I newsrc
Xmode, except the
X.I .newsrc
Xand
X.I .newsrclas
Xfiles are
X.B not
Xupdated.  Instead, a list of article filenames of accepted, unread articles
Xis written to the standard output.
X.TP
X.B mode=batch
XThis mode acts like a combination of the
X.I newsrc
Xand
X.I list
Xmodes.  The program reads the
X.I .newsrc
Xfile and
X.I .newsrclas
Xfile, and produces a list of accepted, unread new articles on the standard
Xoutput.  The
X.I .newsrc
Xfile is updated, but all processed articles are marked as read.  This mode
Xcan be used to provide a filtered news feed for another system from a
X.I .newsrc
Xfile, without the need for an entry for that system in the
X.I sys
Xfile used by inews(1).
X.TP
X.B mode=pipe
XIn this mode, the news filter assumes it has been called by a news
Xreading program.  The news reading program will have hooked up pipes to
Xthe news filter's standard input and output, and will engague in a
Xcommand/response message passing dialogue with the filter program
Xwhile the user is reading news.   This dialogue protocol is documented
Xin a special document.
X.TP
X.B directory=path
XDefines the
X.I dot directory
Xwhere the
X.I .newsrc, .newsrclas, .rnlock
Xand other files may be found.  This is normally the user's home
Xdirectory, but it can also be set by the DOTDIR environment variable,
Xor with this option.
X.TP
X.B option=string
XThis passes the string as an option to the user's newsclip program.  The
Xoptions are all placed in the string array
X.I options
Xwhich may be declared and referenced by the user program.
X.TP
X.B newsrc=pathname
XThis option provides an alternate name for the
X.I .newsrc
Xfile.  The name of the
X.I .newsrclas
Xfile will be formed by appending ``las'' to this name.  If this option
Xis used, the RN locking mechanism is not used.  This option is useful
Xfor testing, and in combination with the mknewsrc(1) program.  The
Xenvironment variables NEWSRC and DOTDIR can also control the location
Xof the various files.
X.TP
X.B las=pathname
XSets an explicit name for the last article seen file.  Useful in combination
Xwith
X.B +only.
X.TP
X.B +only
XThis instructs the program, in one of the 3
X.I .newsrc
Xreading modes above, to only process newsgroups that are found in the
X.I .newsrclas
Xfile.  Normally the program processes all subscribed newsgroups.  Any
Xsubscribed newsgroup that does not have an entry in the
X.I .newsrclas
Xfile gets an entry created for it stating that nothing has been processed.
XTurn on the only option and only groups already in the
X.I .newsrclas
Xfile get processed.  This allows you to restrict newsclip filtering to
Xonly a select set of groups.  The other groups will pass through
Xunfiltered.
X.TP
X.B +unsubscribed
XThis instructs the program, in one of the three
X.I .newsrc
Xreading modes above, to also process unsubscribed newsgroups.  Articles
Xform unsubscribed newsgroups that get accepted will be marked unread in
Xthe .newsrc file or output in the file list, as appropriate.  If the
X.I .newsrc
Xfile is being updated, and an accepted article is found in an unsubscribed
Xgroup, then that group will be re-subscribed.
X.TP
X.B Libdir=dirpath
XDefines the news library directory, normally /usr/lib/news.
X.TP
X.B Spooldir=dirpath
XDefines the news spool directory, normally /usr/spool/news, but may be
Xsystem defined.
X.TP
X.B warning=level
XDefines the warning level.  Provide an integer.  The higher the level,
Xthe more warnings.  The default is 1.
X.SH LANGUAGE
XThe
X.I newsclip
Xlanguage is a stripped down version of C, with various extensions added
Xto make it a special purpose language.  The whole purpose of the bulk of
Xa newsclip program is to examine a B news format article and give it
Xa
X.I score.
XAt the end, if that score is greater than zero, the article will be
Xaccepted and eventually presented for the user to read.  Otherwise it is
Xrejected.
X.PP
XNewsclip programs usually consist of various conditional statements which
Xexamine things about the article and decide whether to adjust the score
Xor accept/reject the article out of hand.  Conditional expressions can
Xexamine the various header fields of an article or do pattern matching
Xon header fields and various sections of the article's text.
X.PP
XThis ability is combined with general language facilities such as
Xsubroutines, loops, variables, arrays, case statements and a variety of
Xprocessing and control variables and functions.
X.PP
XOne particularly useful facility is the database facility.  Users can
Xmaintain databases in regular files, and perform searches in the databases
Xfor substrings and patterns from the article header and body.  Unique to
Xnewsclip is the ability for programmed update of the databases.
X.PP
XFor example, a user might have a database of Message-IDs that the user
Xhas `killed,' this indicating that the user does not wish to see followups
Xto those messages.  A newsclip program might detect undesired articles
X(such as those from an obnoxious user) and have their Message-IDs put
Xin the database under automatic program control.  Thus the user would
Xnever see articles by the hated user, nor any followups to those
Xarticles.  It would be as though the hated user did not exist on the net.
X.PP
XThe potential for custom control of news filtering is limitless.
X.SH AUTHOR
X.PP
XThe
X.I Newsclip
Xsystem was written by Brad Templeton and Tim Tyhurst.  It is copyright
X1989 by Looking Glass Software Limited.  All rights reserved.
XIt is a commercial product and is only to be used by authorized, licenced
Xusers who have purchased or arranged a licence with the copyright owner.
XThese programs and their associated documentation are not to be copied for
Xuse by unlicenced parties.
X.PP
XThe regular expression routines are derived from those written by Henry
XSpencer and are used with permission.  The date parsing routine was
Xdeveloped by Steven Bellovin and is used with permission.
X.SH FILES
Xdistlist or /usr/lib/news/distlist,
X.newsrc
X.rnlock
X.newsrclas
X/usr/lib/news/active
X/usr/spool/news/*
X.SH "SEE ALSO"
X.I cc(1),
X.I readnews(1),
X.I rn(1),
X.I cpp(1),
X.I ncc(1),
X.I mknewsrc(1),
X.I NewsClip Manual,
X.I News Filter Protocol
X.SH VERSION
XVersion 1.0
END_OF_FILE
if test 8698 -ne `wc -c <'doc/nclip.1'`; then
    echo shar: \"'doc/nclip.1'\" unpacked with wrong size!
fi
# end of 'doc/nclip.1'
fi
if test -f 'header.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'header.c'\"
else
echo shar: Extracting \"'header.c'\" \(9087 characters\)
sed "s/^X//" >'header.c' <<'END_OF_FILE'
X#include "nl.h"
X
X/* Definitions of the header variables -- the user can define others */
X
Xchar	*message_id;
Xusername *approved;
Xchar	*control;
Xtime_t	date;
Xchar	*distribution;
Xtime_t	expires;
Xarray	*followup_to;
Xusername *from;
Xarray	*keywords;
Xint	lines;
Xarray	*newsgroups;
Xchar	*organization;
Xarray	*path;
Xchar	*posting_version;
Xarray	*references;
Xusername *reply_to;
Xusername *sender;
Xchar	*subject;
Xchar	*summary;
Xarray	*xref;
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  */
X
X
X
Xchar *hlines[MAX_HLINES];		/* pointer to header fields */
Xstruct hitem_list *htypes[MAX_HLINES];	/* what type of header line this is */
Xint header_size;			/* num bytes in header */
X
X/*
X * The master routine to read a header and parse it.
X * A header structure (an array of hitem_lists structures) has been
X * created in the user program, indicating which headers are to be
X * parsed into variables, and how they are to be parsed.
X */
X
Xint
Xread_header( in )
XFILE *in;
X{
X	int len;		/* length of string in buffer */
X	struct hitem_list *look_header();
X	int i;
X	int real_heads;		/* number of real header lines */
X	int num_heads;		/* number of headers we like */
X	struct hitem_list *whathead;	/* what header line this is */
X	bool wantcont;		/* do I want the continuation lines? */
X	char *delim;		/* location of header delimiter */
X	char buf[HLINE_SIZE];	/* buffer to read line into */
X	int oldsize;		/* size of last desired header line, including
X					the 0 byte at the end. */
X
X	clear_header();		/* erase all the header variables */
X
X
X	num_heads = 0;
X	real_heads = 0;
X	header_size = 0;
X
X
X	/* read in all the header lines, joining together multiple lines
X	   that are part of one field.  We do not allocate memory for
X	   lines we do not wish. */
X
X	/* For this routine, we use the temporary allocator.  When we
X	   get a multiple part line, we use the temporary reallocator.
X	   In this case, this will almost always not involve moving any
X	   memory, but just adjusting some pointers, and as such it will
X	   be very fast. */
X
X	while( fgets( buf, HLINE_SIZE, in ) ) {
X		len = strlen(buf);
X		header_size += len;
X		if( buf[len-1] == '\n' ) {
X			if( len == 1 )
X				break;		/* header terminated */
X			buf[--len] = 0;	/* clear away newline */
X			/* if starts with white space, join to previous line */
X			if( buf[0] == ' ' || buf[0] == '\t' ) {
X				if( wantcont ) {
X					hlines[num_heads-1] = temp_realloc(
X						hlines[num_heads-1], oldsize,
X						oldsize+len );
X					/* stick line into new buffer */
X					/* we write on top of the old zero */
X					strcpy(hlines[num_heads-1]+oldsize-1,
X								buf);
X					oldsize += len;
X					}
X				}
X			 else {
X				real_heads++;
X				delim = strchr( buf, ':' );
X				wantcont = FALSE;
X				if( delim ) {
X					*delim = 0;
X					whathead = look_header(buf);
X					if( whathead ) {
X						htypes[num_heads] = whathead;
X						/* delim-buf is 1 too small, and
X						   that gives us the 1 for the
X						   zero byte at the end */
X						oldsize = len-(delim-buf);
X						hlines[num_heads] = temp_alloc(
X							oldsize );
X						strcpy( hlines[num_heads],	
X								delim+1 );
X						num_heads++;
X						wantcont = TRUE;
X						}
X					}
X				}
X			}
X		 else {
X			/* a line longer than the very large buffer! --
X			   we ignore it, it's probably a garbled header. */
X			;
X			}
X		}
X
X
X	/* The header has now been read in.  Break it up and parse it */
X
X	for( i = 0; i < num_heads; i++ ) {
X		extern int preserve_case;
X		char *arg;	/* argument */
X
X		/* find out what header it was */
X		whathead = htypes[i];
X
X		/* find start of argument */
X		arg = whitestrip( hlines[i] );
X		/* lowercase the whole field unless it is an explicit
X		   two-case field */
X		if( !(whathead->dtype & T_DUALCASE) && !preserve_case )
X			lowercase( arg );
X
X		prepare_var( whathead->dtype, whathead->var, arg,
X					whathead->delims );
X				
X		}
X
X	/* now we should initialize the temporary memory allocator with
X	   what is left in the static buffer, plus the rest of the blocks
X	   allocated for it */
X
X	return real_heads;
X				
X}
X
X
X/* parse the various data types and store them in the variable provided */
X/* This does handle arrays with a bit of recursion */
X
Xprepare_var( type, var, arg, delims )
Xint type;		/* data type to parse */
Xdatau *var;		/* pointer to variable */
Xchar *arg;		/* string argument to be parsed */
Xchar *delims;		/* array parse delimiters */
X{
X	extern time_t getdate();
X
X	if( type & T_ARRAY ) {
X		*(array **)var = parse_array( arg, delims, type );
X		}
X	 else switch( type & T_BASETYPE ) {
X		case T_INTEGER:
X			var->uinteger = atoi(arg);
X			break;
X		case T_DATE:
X			var->udate = getdate( arg );
X			break;
X		case T_STRING:
X			var->ustring = arg;
X			break;
X		case T_NEWSGROUP:
X			var->uinteger = ng_number( arg );
X			break;
X		case T_USERNAME:
X			var->uusername =(username*)temp_alloc(sizeof(username));
X			proc_username( var->uusername, arg );
X			break;
X		}
X}
X
X/*
X * This routine searches for the header item that matches the line we have
X * been provided with, and returns the descriptor struct for it.
X */
X
Xstruct hitem_list *
Xlook_header( hstr )
Xchar *hstr;			/* string with header name */
X{
X	register int res;
X	register int low, high, mid;	/* binary search points */
X	extern int num_headers;		/* generated in user program */
X	extern struct hitem_list header_items[];
X
X	low = 0;
X	high = num_headers-1;
X
X	lowercase(hstr);
X
X	/* perform a binary search to see which header item this is */
X
X	while (low <= high) {
X		mid = (low + high) / 2;
X		res = strcmp(hstr, header_items[mid].hname);
X		if (res < 0)
X			high = mid - 1;
X		else if (res > 0)
X			low = mid + 1;
X		else
X			return &header_items[mid];
X	}
X	return (struct hitem_list *)0;
X}
X
X/* Zero out all the header variables before processing an article */
X
Xclear_header()
X{
X	int i;
X	datau *tv;
X	extern struct hitem_list header_items[];
X
X	for( i = 0; header_items[i].hname; i++ ) {
X		tv = header_items[i].var;
X		if( header_items[i].dtype & T_ARRAY )
X			*(array **)tv = (array *)0;
X		 else switch( header_items[i].dtype ) {
X			case T_INTEGER:
X			case T_NEWSGROUP:
X				tv->uinteger = 0;
X				break;
X			case T_DATE:
X				tv->udate = 0;
X				break;
X			case T_STRING:
X				tv->ustring = (char *)0;
X				break;
X			case T_USERNAME:
X				tv->uusername = 0;
X				break;
X			}
X		}
X}
X
X/* Turn a From: style line into an emailname and a full name */
X
X/*
X * The three formats allowed on usenet for From: lines are
X *	user@domain
X *	user@domain (Full name)
X *	Full name <user@domain>
X *
X */
X
Xproc_username( uname, string )
Xusername *uname;
Xchar *string;
X{
X	char *mailname;		/* place for mail name */
X	char *fname;		/* temp store of full name */
X
X	if( mailname = strchr( string, '<' ) ) {
X		/* null out the < character */
X		*mailname++ = 0;
X		mailname = white_delim_strip( mailname, '>' );
X		fname = whitestrip( string );
X		}
X	 else {
X		if( fname = strchr( string, '(' ) ) {
X			*fname++ = 0;
X			fname = white_delim_strip( fname, ')' );
X			}
X		mailname = whitestrip( string );
X		}
X			
X	uname->emailname = mailname;
X	uname->fullname = fname;
X}
X
X/*
X * Turn a string into an array.
X */
X
Xarray *
Xparse_array( str, delims, type )
Xchar *str;		/* the string to parse */
Xchar *delims;		/* delimiters to use */
Xint type;		/* type of data */
X{
X	/* parse this field into an array */
X	char *p;
X	char atemp[sizeof(array)+MAX_ARRAY*sizeof(datau)];
X	array *ar, *real;
X	bool strip;
X	int i, count;
X
X	/* allocate temporary place for array */
X	ar = (array *)/*&*/atemp;
X	count = 0;
X
X	/* if the first character of the delimiter list is an 'S', we
X	   strip white space from the tokens. */
X	if( strip = (delims[0] == STRIP_CODE) )
X		delims++;		/* eliminate the S */
X	/* loop, parsing the line */
X
X	for( p = strtok( str, delims ); p && count < MAX_ARRAY ;
X				p = strtok( NULL, delims ) ) {
X
X		if( strip )
X			p = whitestrip( p );
X
X		prepare_var( type & T_BASETYPE, &ar->vals[count++], p, NULL );
X		}
X
X	/* now create the array properly */
X
X	real = fresh_array( count, type );
X	for( i = 0; i < count; i++ )
X		real->vals[i] = ar->vals[i];
X	return real;
X}
X
X/* User routine to turn a string into a variable.  All this does is make
X   a temporary copy of the string first, since prepare_var is destructive
X   on the string */
X
Xuprepare_var( type, var, arg, delims )
Xint type;		/* data type to parse */
Xdatau *var;		/* pointer to variable */
Xchar *arg;		/* string argument to be parsed */
Xchar *delims;
X{
X	prepare_var( type, var, temp_string(arg), delims );
X}
X
X/* create an empty array of a given size */
X
Xarray *
Xfresh_array( nels, type )
Xint nels;			/* number of elements in array */
Xint type;			/* type of array */
X{
X	array *retar;
X	int size;
X
X	if( nels < 0 ) {
X		warning( 1, "Invalid Array size %d\n", nels );
X		nels = 0;
X		}
X
X	size = sizeof(array) + (nels-1) * sizeof(datau);
X	retar = (array *)temp_alloc( size );
X	zero( retar, size );
X	retar->arsize = nels;
X	retar->artype = type & T_BASETYPE;
X	return retar;
X}
END_OF_FILE
if test 9087 -ne `wc -c <'header.c'`; then
    echo shar: \"'header.c'\" unpacked with wrong size!
fi
# end of 'header.c'
fi
if test -f 'samples/feed.nc' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'samples/feed.nc'\"
else
echo shar: Extracting \"'samples/feed.nc'\" \(9231 characters\)
sed "s/^X//" >'samples/feed.nc' <<'END_OF_FILE'
X
X
X/*
X *  This is a template program for feeding a site using NewsClip
X *  Use your filter program in "batch" mode:
X *  First create a .newsrc file with the groups you wish to feed to
X *  the remote site.  No "las" file is needed.   Adjust the distributions
X *  below, or put them in a file and change the init code.
X *  Create the .newsrc with:
X *		mknewsrc p=0 control '^comp' '^rec' ....   >newsrcfile
X *
X *  Now run your filter program as follows, regularly from the cron, when
X *  it is time to batch news for the remote site:
X *
X *		nclip mode=batch n=newsrcfile >/usr/spool/batch/sitename
X *
X *  On the standard output, it lists the files to be fed.  This can then
X *  be fed to the news sendbatch program.
X *
X *  Be sure to change the "feedsite" string in the article() procedure,
X *  and the distributions below.
X */
X
X/*
X *  How this program works:
X *  New articles from groups in the site's .newsrc file are processed through
X *  the "article()" procedure.  No articles from outside those groups will
X *  be passed through.  The xrefs line (or a call to reject_xrefs()) makes
X *  sure each article is processed once.
X *
X *  First we check to see if this article is in a distribution that we wish
X *  to get.  We scan the "Rdistribution" header, and for each element, we
X *  take the leftmost components (parsing with dots).  If that component is
X *  in our database of distributions, we accept the article.  (This only
X *  supports top and 2nd-level distributions.  To support more levels, either
X *  define *  the distributions as regular expressions and use 'has', or
X *  search multiple components.)
X *
X *  If we're in the 'control' group, we check for newgroup messages.  If they
X *  already passed our distribution check, we want to subscribe to such
X *  groups.  We do this now so that it gets done even if the control article
X *  came from our feed site.
X *
X *  Next, we check to see that this article hasn't visited the site we are
X *  feeding.  We check for that site name in the first N-1 components of
X *  the Path: header.  We ignore the last component, which for ancient reasons
X *  is not a site name.
X *
X *  After that, we can do any particular checks to filter the articles.
X *  Otherwise, we accept all articles.  They get printed to the input file
X *  used by the news batching program.
X */
X
Xdatabase distfeed;		/* distributions to be fed */
X
Xheader string array control : "control", " ";	/* parse control messages */
X
Xextern string array xref;		/* to eliminate crossposts */
X					/* Or use an alternate crosspost
X					   eliminator -- see below */
Xextern newsgroup array Rdistribution;
X
Xextern string array path;		/* the path line */
X
Xprocedure init()
X{
X	distfeed = fresh_database( 30 );
X	/* alternatedly, use read_database to keep this in a file */
X	/*	distfeed = read_database( "distfile" ); */
X
X
X	/* Note in the following cases that the distribution must be coded
X	   or the article will not be sent.  The 'world' distribution is
X	   there only for articles truly posted to that distribution.  It
X	   need not be present for general articles.  In order to feed an
X	   article, it must be posted to one of the distributions listed,
X	   *and* be in the .newsrc file controlling the feeding.  If you
X	   put "news.misc" in the .newsrc, it will not be feed in its
X	   entirety unless "news" is a distribution listed below.
X
X	   BUT: You can change the code as you like.  Take out the distribution
X	   filter altogether and get the entire contents of the groups in
X	   the .newsrc, for example.
X	  */
X
X	/* hard code the desired distributions  1st & 2nd level only */
X	distfeed["comp"]++;
X	distfeed["rec"]++;
X	distfeed["news"]++;
X	distfeed["misc"]++;
X	distfeed["soc"]++;
X	distfeed["talk"]++;
X	distfeed["sci"]++;
X	distfeed["alt"]++;
X	distfeed["biz"]++;
X	/* distfeed["clari"]++; */	/* Only to paid ClariNet subscribers */
X	distfeed["world"]++;
X	distfeed["na"]++;		/* pick your favourite continent */
X	/* distfeed["can"]++; */	/* pick your country */
X	distfeed["usa"]++;
X	distfeed["to"]++;		/* for the to.sitename group */
X	distfeed["my state"]++;		
X	distfeed["my region"]++;
X	/* add your favourites here below */
X	distfeed["dummy"]++;		
X	/* if you feed local groups, you have to include them here as
X	   distributions, too.  Or you can modify the code that checks for
X	   proper distribution to pass all names in the .newsrc with no dot
X	   in them.
X	 */
X}
X
X/* This procedure rejects all articles that are not in a distribution
X   that we are supposed to get.  You can alter this to reject articles that
X   aren't entirely for your distribution, if you like.  It just takes the
X   left side of the names in the distribution (newsgroups) list, and checks
X   to see if they are in the database of desired distributions.
X
X   The Rdistribution variable is special.  It is the Distribution header if
X   that is defined, or the Newsgroups header if it isn't -- ie. the true
X   distribution list.  It is thus always defined.
X */
X
Xprocedure check_dist()
X{
X	int i;
X	extern string left( string, int );
X	/* reject all articles not in a distribution we get */
X
X	/* scan distributions, return if one matches what we are fed */
X	for( i = 0; i < count( Rdistribution ); i++ )
X		if( left( Rdistribution[i], 1 ) in distfeed ||
X				left( Rdistribution[i], 2 ) in distfeed )
X			return;
X	/* nothing matched, reject this article */
X	reject;
X}
X
X/*
X * Here is an alternate distribution check:
X * It accepts all messages with no specific distibutions, plus ones that
X * have a specific distribution matching the regular expression.
X	extern newsgroup array distribution;
X
X	if( distribution != nilarray &&
X			distribution !has "^(world|na|usa|state|city|company)" )
X		reject;
X */
X			
X
X
X/* This special procedure handles control messages, searching for the
X * 'newgroup' message.
X */
X
Xprocedure do_control()
X{
X	extern procedure subscribe( string );
X
X	/* look for newgroup control messages */
X
X	if( control != nilarray && count(control) >= 2 &&
X			control[0] == "newgroup" )
X		subscribe( control[1] );
X	/* control articles then go through accept/reject */
X	/* this does not catch netwide groups created locally by you */
X}
X
X/* the main article procedure, where you might put in filter code */
X
Xprocedure article()
X{
X	extern newsgroup main_newsgroup;
X	int i;
X
X	/* reject_xref(); */		/* if Xrefs line not defined */
X
X	check_dist();			/* check article is in their dist */
X
X	/* handle control articles with special subscribing code */
X	if( main_newsgroup == #control ) 
X		do_control();
X
X
X	/* reject any article that came from the feed site.  In this case
X	   the feed site is hard coded, but you could pass it as a command
X	   line option to avoid having multiple programs for similar sites.
X
X	   Note we don't just say 'reject if "feedsite" in path;' because that
X	   would reject articles posted by a user with a userid that matches
X	   the site.  I B news, a person with a userid of "uunet" would have
X	   articles blocked from that site.
X	 */
X
X	for( i = 0; i < count(path)-1; i++ )
X		if( path[i] == "feedsite" )
X			reject;
X
X	/* you might want to be clever here, and also reject articles that
X	   have been to other feeds of the site you are feeding.  Chances
X	   are, if an article's been there, your destination already has
X	   it.  This makes really short loops more efficient, although some
X	   risk is involved
X	 */
X
X	/* now insert code here to filter out undesired articles */
X
X	/* this rather extreme example rejects anything crossposted to
X  	   talk.bizzare, even if it was also posted to a group you otherwise
X	   wanted to get! (ie. impossible with B news) */
X
X	reject if is talk.bizarre;
X
X	/* otherwise accept all articles */
X	accept;
X}
X
X/* The procedure below, called at the start of "article", provides
X * an alternate means for rejection of cross-posted articles, even if
X * your site doesn't properly support the Xrefs line */
X
X /* DON'T USE THIS PROCEDURE AND DEFINE THE XREFS HEADER AT THE SAME TIME */
X
Xprocedure reject_xref()
X{
X	int i;
X	extern newsgroup main_newsgroup;
X	extern int newsrc_group( newsgroup );
X
X
X	/* loop through the groups on the Newsgroups line.  Find the
X	 * first one that is a subscribed group.  If that's the current
X	 * group, get on with processing the article.  If it's another group,
X	 * reject the article, as we will process it when we get to that
X	 * first subscribed group.  ie. the article in exactly one of the
X	 * groups on the Newsgroups line -- the first one of those that we
X	 * subscribe to.
X	 */
X
X	for( i = 0; i < count(newsgroups); i++ )
X		if( newsrc_group( newsgroups[i] ) )
X			if( newsgroups[i] == main_newsgroup )
X				return;		/* do nothing, first valid grp*/
X			 else
X				reject;		/* skip the article */
X}
X
X/*
X	The B news "sys" file contains some selection options.  You should be
X	able to code many of these easily in Newsclip, plus a bunch more.
X
X	The 'L' option, which only sends local articles, can be implemented
X	by accepting only articles where domain(Rsender) == my_domain, or
X	articles where count(path) <= 2 (or some other number, if you want
X	to broaden the definition of 'local').
X
X	Use of Newsclip for such a feed, unfortunately, is less efficient than
X	the sys file.  Such feeds rarely need filtering.
X
X	Newsclip lets you do a lot more than this of course.
X */
END_OF_FILE
if test 9231 -ne `wc -c <'samples/feed.nc'`; then
    echo shar: \"'samples/feed.nc'\" unpacked with wrong size!
fi
# end of 'samples/feed.nc'
fi
if test -f 'userdb.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'userdb.c'\"
else
echo shar: Extracting \"'userdb.c'\" \(8742 characters\)
sed "s/^X//" >'userdb.c' <<'END_OF_FILE'
X
X
X/*
X * Database routines and externals for access by the user program
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#include "nl.h"
X#include <ctype.h>
X/* The routine to read in a database from a file */
X
X/*
X * Good databases that we have written have a size field at the start.
X * They contain lines that have the field value, the access time and the
X * key, comma delimited.
X * We allow databases with no size field, and assume they aren't that big.
X * We also allow plain old string records.  In this case we give them a
X * value of one and an access time of now.
X * This will all get corrected with the database is written out.
X */
X
Xdbptr
Xread_database( fname )
Xchar *fname;
X{
X	char buf[MAX_LLEN];
X	dbptr ret;
X	FILE *dbfile;
X	int size;		/* predicted size for DB */
X	int value;		/* value of key */
X	long actime;		/* access time of key */
X	extern long time_now;	/* current time of day */
X	userdb *rec;		/* database record to add */
X
X	dbfilename( buf, fname );
X	dbfile = fopen( buf, "r" );
X	if( !dbfile ) {
X		ret = init_db( 30, sizeof(userdb) );
X		warning( 3, "Database file %s not found.\n", fname );
X		return ret;
X		}
X	if( fgets( buf, sizeof(buf), dbfile ) ) {
X		if( strncmp( buf, "NCDBSIZE=", 9 ) == 0 ) {
X			size = atoi(buf+9);
X			size = min( size, 20 );
X			}
X		 else {
X			/* no size information in this database */
X			size = 30;
X			rewind( dbfile );
X			}
X		ret = init_db( size, sizeof(userdb) );
X
X		/* scan the file so long as there are valid lines */
X		while( fgets(buf, sizeof(buf), dbfile ) ) {
X			int len;
X			char *key;
X
X			len = strlen( buf );
X			if( len > 0 ) {
X				if( buf[len-1] == '\n' )
X					buf[len-1] = 0;
X				 else {
X					/* throw away data to end of line */
X					int c;
X					while( ( c = getc(dbfile) ) != '\n' &&
X								c != EOF )
X							;
X					}
X				}
X			if( buf[0] == '>' && (isdigit(buf[1]) || buf[1]=='-')&&
X				sscanf( buf, ">%d,%ld,", &value, &actime ) ==2){
X
X				/* go to after the second comma */
X				key = strchr( strchr( buf, ',' ) + 1, ',' ) + 1;
X				}
X			 else {
X				key = buf;
X				value = 1;
X				actime = time_now;
X				}
X
X			rec = (userdb *)add_rec( ret, key, AR_CREATE );
X			rec->intval = value;
X			rec->access_date = actime;
X			}
X		}
X	 else {
X		/* empty file */
X		ret = init_db( 30, sizeof( userdb ) );
X		}
X	fclose( dbfile );
X	return ret;
X}
X
X/* Write out a database to a file in our format */
X/* This does not free or clear the database */
X
Xdbptr
Xwrite_database( db, str, oldest )
Xdbptr db;			/* the database to write */
Xchar *str;			/* the filename to write to */
Xdatehold oldest;		/* the oldest record to keep */
X{
X	FILE *dbfile;
X	char fnbuf[MAX_FNAME];
X	userdb *rec;
X	
X	dbfilename( fnbuf, str );
X	/* if the file is not there and the database is empty, do not write */
X	if( (db == 0 || size_db(db) == 0 ) && access( fnbuf, 0 ) != 0 )
X		return;
X	dbfile = fopen( fnbuf, "w" );
X	if( !dbfile ) {
X		warning( 1, "Could not write database %s\n", fnbuf );
X		/* Create directories?? */
X		return;
X		}
X
X	if( db == (dbptr)0 ) {
X		fclose( dbfile );
X		return;
X		}
X
X
X	/* we have a problem if a key contains a newline */
X
X	fprintf( dbfile, "NCDBSIZE=%ld\n", size_db( db ) );
X	for( rec = (userdb *)first_rec(db); rec; rec = next_rec(db,rec) ) {
X		if( rec->access_date >= oldest )
X			if( fprintf( dbfile, ">%d,%ld,%s\n", rec->intval,
X					(long)rec->access_date, rec->name )< 0){
X				warning( 1, "Error writing database %s\n", str );
X				break;
X				}
X		}
X	fclose( dbfile );
X}
X
X/* Create an fresh, empty database for the user */
X
Xdbptr
Xfresh_database(size)
Xint size;			/* estimate for database size */
X{
X	return init_db( size, sizeof(userdb) );
X}
X
X/* free up the memory used by a database */
X
Xfree_database( db )
Xdbptr db;
X{
X	free_db( db );
X}
X
X/* array index routine for lvalue in database */
X
Xuserdb *
Xdb_create( db, key )
Xdbptr db;			/* database to create or reference in */
Xchar *key;			/* name of key */
X{
X	userdb *rec;
X	extern long time_now;
X
X	rec = (userdb *)add_rec( db, key, AR_CREATE );
X	rec->access_date = time_now;
X	return rec;
X}
X
X/* array index routine for rvalue in database */
X
Xint
Xdb_lookup( db, key )
Xdbptr db;
Xchar *key;
X{
X	register userdb *rec;
X	extern long time_now;
X
X	rec = (userdb *)get_rec( db, key );
X	if( rec ) {
X		rec->access_date = time_now;
X		return rec->intval;
X		}
X	 else
X		return 0;
X}
X
X/* For "for( xxx in yyy )" loops -- this returns the first record in a
X * user's database.
X */
X
Xuserdb *
Xufirst_rec( db )
Xdbptr db;
X{
X	return (userdb *)first_rec(db);
X}
X
X/* delete element from user database */
X
Xdb_delete( db, str )
Xdbptr db;
Xchar *str;
X{
X	del_rec( db, str );
X}
X
X/* Does a file exist?  Returns true if it does */
X
Xexists( dbfile )
Xchar * dbfile;
X{
X	char fbuf[MAX_FNAME];
X	dbfilename( fbuf, dbfile );
X	return access( fbuf, 0 ) == 0;
X}
X
X/* Create a filename, mapping special characters into
X
X   ~	(followed by slash) User's home directory
X   ~.	User's 'dot' directory
X   ~n	current newsgroup name, with dots changed to slashes
X   ~N	current newsgroup in a probably unique single filename form
X   ~d	newsgroup in "dir_newsgroup" with dots changed to slashes
X   ~D	"dir_newsgroup" in a probably unique single filename form
X   ~s	News spool directory
X   ~l	News lib directory
X   ~u	Userid
X   ~p	PID
X
X
X   For example, if you're 'brad' in the group rec.humor, then:
X	"~/News/~n/badusers" expands to "/u/brad/News/rec/humor/badusers",
X	the name of a probable 'kill file' database.
X
X */
X
Xdbfilename( fnbuf, fname )
Xchar *fnbuf;		/* place to put the filename */
Xchar *fname;		/* database file name */
X{
X	register char *inp, *outp;	/* pointers for copying over name */
X	extern newsgroup main_newsgroup, dir_newsgroup;
X	extern char *dotdir, *news_spool_dir, *news_lib_dir;
X	extern char *userid, *homedir;
X
X	inp = fname;
X	outp = fnbuf;
X	while( *inp ) {
X		if( *inp == '~' ) switch( *++inp ) {
X			case 0:	/* end of string */
X				*outp = '~';
X				break;
X			case '~':	/* copy tilde and skip it */
X				*outp = *inp++;
X				break;
X			case '.':
X				strcpy( outp, dotdir );
X				outp += strlen( outp );
X				inp++;
X				break;
X			case '/':	/* just a tilde on its own */
X				strcpy( outp, homedir );
X				outp += strlen( outp );
X				break;
X			case 'n':
X			case 'd':
X				outp += outngf( outp, *inp == 'd'
X					? dir_newsgroup : main_newsgroup );
X				inp++;
X				break;
X			case 'N':
X			case 'D':
X				outp += probably_unique( outp, *inp == 'D'
X					? dir_newsgroup : main_newsgroup );
X				inp++;
X				break;
X
X			case 's':
X				strcpy( outp, news_spool_dir );
X				outp += strlen( outp );
X				inp++;
X				break;
X			case 'l':
X				strcpy( outp, news_lib_dir );
X				outp += strlen( outp );
X				inp++;
X				break;
X			case 'p':
X				sprintf( outp, "%d", getpid() );
X				outp += strlen( outp );
X				inp++;
X				break;
X			case 'u':
X				strcpy( outp, userid );
X				outp += strlen( outp );
X				inp++;
X				break;
X			}
X		 else
X			*outp++ = *inp++;
X		}
X	*outp = 0;
X}
X
X/* Stores a newsgroup name into a string, mapping dots to slashes.
X   Returns the length of the string */
X
Xint
Xoutngf( str, group )
Xchar *str;			/* string to store onto */
Xnewsgroup group;
X{
X	register char *gname;
X	int count;
X
X	count = 0;
X	for( gname = ngn(group); *gname; count++, gname++ )
X		*str++ = *gname == '.' ? '/' : *gname;
X	*str = 0;
X	return count;
X}
X
X/* Function to make a probably unique single filename from a newsgroup */
X
Xint
Xprobably_unique( str, group )
Xchar *str;			/* string to store onto */
Xnewsgroup group;		/* group to place in name */
X{
X	int csum;		/* checksum for name */
X	int len;		/* length of newsgroup name */
X	int bytes_left;		/* bytes left in which to store name */
X	char *name, *p;		/* name and pointers into it */
X	int lastdot;		/* did we do a dot last char? */
X	int bytes_out;
X
X	name = ngn(group);
X	len = strlen( name );
X	/* put in full name if it fits */
X	if( len <= MAX_FNAME_LEN ) {
X		strcpy( str, name );
X		return len;
X		}
X	/* checksum name */
X	for( csum = 0, p = name; *p; p++ )
X		csum += *p;
X	/* take mode 62 */
X	csum %= 62;
X	/* output checksum letter from 0..9A..Za..z */
X	*str++ = csum < 36 ? (csum >= 10 ?'A'+csum-10: '0'+csum) : 'a'+csum-36;
X
X	bytes_out = 1;
X	bytes_left = MAX_FNAME_LEN - bytes_out;
X	lastdot = TRUE;
X	for( p = name; *p && bytes_left > 0; p++, len-- ) {
X		if( len <= bytes_left ) {
X			strcpy( str, p );
X			return bytes_out+len;
X			}
X		if( lastdot ) {
X			*str++ = toupper( *p );
X			bytes_out++;
X			bytes_left--;
X			}
X		lastdot = *p == '.';
X		}
X	*str = 0;
X	return bytes_out;
X}
END_OF_FILE
if test 8742 -ne `wc -c <'userdb.c'`; then
    echo shar: \"'userdb.c'\" unpacked with wrong size!
fi
# end of 'userdb.c'
fi
echo shar: End of archive 5 \(of 15\).
cp /dev/null ark5isdone
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