[comp.sources.misc] v09i097: pexpire for Cnews

jrp@rducky.UUCP (JIM PICKERING) (01/05/90)

Posting-number: Volume 9, Issue 97
Submitted-by: jrp@rducky.UUCP (JIM PICKERING)
Archive-name: cn_pexpire

Here is a new version of pexpire that I have modified for Cnews.
Since it was originally copyrighted by Dave Taylor, I have enclosed his
correspondence allowing me to post it.

jim


Jim Pickering c/o Technical Solutions	|| (north) ..csustan!polyslo!rducky!jrp 
P.O. Box 1045				|| (south) ..sdsu!polyslo!rducky!jrp 
Arroyo Grande, CA 93421			|| (south) ..csun!polyslo!rducky!jrp
(805) 473-1037				|| (east)  ..csufres!polyslo!rducky!jrp

------------------------------------------------------------------------------
Jim,
	You are more than welcome to distribute a new version of my
Pexpire program.. thanks for modifying it to work with C news too!

				Ciao!
						-- Dave Taylor
Intuitive Systems
Mountain View, California

taylor@limbo.intuitive.com    or   {uunet!}{decwrl,apple}!limbo!taylor
------------------------------------------------------------------------------
#! /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 tncli as standard input via
# unshar, or by typing "sh <file", e.g..  If tncli archive is complete, you
# will see the following message at the end:
#		"End of archive 1 (of 1)."
# Contents:  MANIFEST Makefile README README.CNEWS bpexpire.1
#   cpexpire.1 dopexpire pexpire.batch pexpire.c pexpire.defs
#   pexpire.h pexplist
# Wrapped by jrp@rducky on Tue Jan  2 21:12:59 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'MANIFEST' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'MANIFEST'\"
else
echo shar: Extracting \"'MANIFEST'\" \(486 characters\)
sed "s/^X//" >'MANIFEST' <<'END_OF_FILE'
X   File Name		Archive #	Description
X-----------------------------------------------------------
X MANIFEST                   1	This shipping list
X Makefile                   1	
X README                     1	
X README.CNEWS               1	
X bpexpire.1                 1	
X cpexpire.1                 1	
X dopexpire                  1	
X pexpire.batch              1	
X pexpire.c                  1	
X pexpire.defs               1	
X pexpire.h                  1	
X pexplist                   1	
END_OF_FILE
if test 486 -ne `wc -c <'MANIFEST'`; then
    echo shar: \"'MANIFEST'\" unpacked with wrong size!
fi
# end of 'MANIFEST'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(3310 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X#
X#	Makefile for the pexpire program
X#      
X#  by Dave Taylor, Hewlett-Packard Co.
X#  Cnews modifications by Jim Pickering (jrp@rducky)
X
XSHELL    =	/bin/sh
XCFILES   =	pexpire.c
XHEADERS  =	pexpire.h
XOBJS     =	pexpire.o
X
X# our executable ... if you change this, you'll have to edit dopexpire (Cnews)
XPROGNAME =	pexpire
X
X# our doexpire(8) replacement for Cnews
XBATCHNAME=	dopexpire
X
X# Bnews man directory
XMANDIR	=	/usr/man/local/man1
X# Cnews man directory
XMANDIR	=	/usr/man/local/man8
X
X# Bnews install directory
XNEWSDIR	=	/usr/lib/news
X# Cnews install directory ... if you change this, you'll have to edit dopexpire
XNEWSDIR	=	/usr/lib/newsbin/expire
X
X# Cnews pexplist install directory ... if you change this, you'll have to edit
X# dopexpire
XLISTDIR	=	/usr/lib/news
X
X# the next is probably the only thing you'll need to locally customize
X# to reflect the top level location of the netnews source on your 
X# machine ... Bnews
X
XNEWS_SRC =	/usr/local/src/news.2.11
X
XINCLUDEDIR  =   -I${NEWS_SRC}
XLIBS     =   	-lPW
XRM       =	rm -f
X
X# use for BNEWS
XCFLAGS   = 	-O ${INCLUDEDIR}
X# use for CNEWS
XCFLAGS   = 	-O -DCNEWS
X
X# your favorite C compiler
XCC       =	/bin/cc
XCC       =	/bin/ccc
X
X${PROGNAME}: ${OBJS}
X	${CC} -o ${PROGNAME} -s ${OBJS} ${LIBS}
X
Xpexpire.c: pexpire.h
Xpexpire.o: pexpire.c
X
Xclean:
X	${RM} ${OBJS} LINT.OUT core $(PROGNAME) tempxxx
X
Xlint: LINT.OUT
X
XLINT.OUT: ${CFILES}
X	lint ${DEFINE} ${INCLUDEDIR} ${CFILES} ${LIBS} > LINT.OUT
X
Xshar:
X	${RM} Part* MANIFEST
X	makekit -oMANIFEST Makefile README* cpexpire.1 bpexpire.1 pexpire.c pexpire.defs pexpire.h pexpire.batch dopexpire pexplist
X	compress Part*
X
Xrealclean: shar clean
X	${RM} MANIFEST Makefile README* cpexpire.1 bpexpire.1 pexpire.c pexpire.defs pexpire.h pexpire.batch dopexpire pexplist
X
Xbnewsinstall: $(PROGNAME)
X	mv $(PROGNAME) $(NEWSDIR)
X	chmod 0750 $(NEWSDIR)/$(PROGNAME)
X	chown news $(NEWSDIR)/$(PROGNAME)
X	chgrp news $(NEWSDIR)/$(PROGNAME)
X	cp pexpire.defs $(NEWSDIR)
X	chown news $(NEWSDIR)/pexpire.defs
X	chgrp news $(NEWSDIR)/pexpire.defs
X	cp bpexpire.1 $(MANDIR)/pexpire.1
X	chown bin $(MANDIR)/pexpire.1
X	chgrp bin $(MANDIR)/pexpire.1
X	chmod 0444 $(MANDIR)/pexpire.1
X	@echo
X	@echo "\tInstall pexpire.batch and"
X	@echo "\t\tput an entry for $(PROGNAME) in /usr/lib/crontab"
X	@echo
X
Xbnewsuninstall:
X	${RM} $(NEWSDIR)/$(PROGNAME)
X	${RM} $(NEWSDIR)/pexpire.defs
X	${RM} $(MANDIR)/pexpire.1
X
Xcnewsinstall: $(PROGNAME) $(BATCHNAME)
X	mv $(PROGNAME) $(NEWSDIR)
X	chmod 0775 $(NEWSDIR)/$(PROGNAME)
X	chown bin $(NEWSDIR)/$(PROGNAME)
X	chgrp bin $(NEWSDIR)/$(PROGNAME)
X	cp $(BATCHNAME) $(NEWSDIR)
X	chmod 0775 $(NEWSDIR)/$(BATCHNAME)
X	chown bin $(NEWSDIR)/$(BATCHNAME)
X	chgrp bin $(NEWSDIR)/$(BATCHNAME)
X	cp pexplist $(LISTDIR)/pexplist
X	chown bin $(LISTDIR)/pexplist
X	chgrp bin $(LISTDIR)/pexplist
X	chmod 0444 $(LISTDIR)/pexplist
X	cp cpexpire.1 $(MANDIR)/pexpire.8
X	chown bin $(MANDIR)/pexpire.8
X	chgrp bin $(MANDIR)/pexpire.8
X	chmod 0444 $(MANDIR)/pexpire.8
X	${RM} $(MANDIR)/dopexpire.8
X	ln $(MANDIR)/pexpire.8 $(MANDIR)/dopexpire.8
X	@echo
X	@echo "Put a similar entry for $(BATCHNAME) in /usr/lib/crontab:"
X	@echo "\t44 0 * * * /bin/su news -c '/usr/lib/newsbin/expire/dopexpire'"
X	@echo
X
Xcnewsuninstall:
X	${RM} $(NEWSDIR)/$(PROGNAME)
X	${RM} $(NEWSDIR)/$(BATCHNAME)
X	${RM} $(LISTDIR)/pexplist
X	${RM} $(MANDIR)/pexpire.8
X	${RM} $(MANDIR)/dopexpire.8
END_OF_FILE
if test 3310 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'README' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README'\"
else
echo shar: Extracting \"'README'\" \(852 characters\)
sed "s/^X//" >'README' <<'END_OF_FILE'
X
X	     Additional Administrative Notes for Pexpire
X
X			  August 29th, 1988
X
X  It is strongly recommended that you read the man page for the
X  netnews expire() command, studying the '-e' and '-E' options,
X  then read the pexpire() man page distributed with the pexpire
X  package.
X
X  From there, you need to edit the Makefile to ensure that it
X  is pointing at the right source directory for netnews, then
X  edit the file "pexpire.h". 
X
X  You should notice that pexpire.h expects that the netnews
X  "defs.h" file is available -- it grabs the default expiration
X  times from that file (see the extensively commented "pexpire.h"
X  file for details).
X
X  Finally, this is a first distribution, so there might very well
X  be problems and non-portabilities.  If you come across anything,
X  please drop me a note.
X
X					Dave Taylor
X					
X  taylor@hplabs.hp.com
END_OF_FILE
if test 852 -ne `wc -c <'README'`; then
    echo shar: \"'README'\" unpacked with wrong size!
fi
# end of 'README'
fi
if test -f 'README.CNEWS' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README.CNEWS'\"
else
echo shar: Extracting \"'README.CNEWS'\" \(1093 characters\)
sed "s/^X//" >'README.CNEWS' <<'END_OF_FILE'
Xsubject:	Notes for Pexpire for Cnews
Xdate:		December 30, 1989
Xfrom:		Jim Pickering (jrp@rducky)
X
X
X
XIt is strongly recommended that you read the man page for
Xthe Cnews expire(8) command, studying the expire list line
Xoptions, then read the cpexpire.1 man page distributed with
Xthe pexpire package.  Note that dopexpire() does not use
X/usr/lib/news/explist.
X
XYou may need to edit the Makefile, edit the file
X"pexpire.h", edit the file "pexplist", and edit the file
X"dopexpire", paying particular attention to lines after '#
XYOU MAY WANT TO EDIT THE FOLLOWING'.  To install, as root
Xdo a 'make cnewsinstall'.
X
XCredit-where-credit-is-due department:
XDave Taylor (taylor@limbo.intuitive.com) originally wrote
Xpexpire.  I made some relatively minor modifications so that
Xit would run under Cnews.  His copyright stands.  Dopexpire
Xis just a doexpire ripoff from Cnews, written by Henry Spencer
Xand Geoff Collyer at U. of Toronto.  The man page is a
Xcombination of both Cnews' expire(8) and Dave's pexpire(1) man
Xpages.
X
XIf you come across problems and non-portabilities, please
Xdrop me a note.
X
Xjim
END_OF_FILE
if test 1093 -ne `wc -c <'README.CNEWS'`; then
    echo shar: \"'README.CNEWS'\" unpacked with wrong size!
fi
# end of 'README.CNEWS'
fi
if test -f 'bpexpire.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'bpexpire.1'\"
else
echo shar: Extracting \"'bpexpire.1'\" \(8460 characters\)
sed "s/^X//" >'bpexpire.1' <<'END_OF_FILE'
X.TH PEXPIRE 1M local
X.ad b
X.SH NAME
Xpexpire - expire netnews groups based on local readership
X.SH SYNOPSIS
X.B pexpire
X[-cgrov] 
X[-e cmd] 
X[-a N] 
X[-h N] 
X[-H N] 
X.SH HP-UX COMPATIBILITY
X.TP 10
XLevel:
XHP-UX/CONTRIBUTED
X.TP
XOrigin:
XHewlett-Packard
X.SH DESCRIPTION
X.I Pexpire
Xis intended to offer a finer granularity in the expiration of
Xnetnews articles on a multi-user machine.  The philosophy behind
Xit is that there are typically a large set of newsgroups that
Xno-one on the machine reads, which makes them very likely targets
Xfor shorter expiration times.  
X.PP
XThis program allows you to do exactly that \(em it lets you set
Xdefault expiration times for all groups on your machine depending
Xon if they are subscribed to or not, then checks each users
X".newsrc" to ascertain this information.  The finaly output of
Xthe program is a set of \fIexpire\fR commands suitable for automatic execution
Xout of cron.
X.PP
XThe flags understood are:
X.TP 8
X.B \-a n  	
XSet default history expire value to 'n'. 
X.sp
XThere are actually three
Xflags to do with expiration dates that \fIpexpire\fR understands:
Xthe `-a' flag to set the default history expire value, and the `-h'
Xand `-H' flags to set a bracketing for when the `-E' flag needs to
Xbe output.
X.sp
XThat doesn't make any sense, I'm sure, so let's look at it this
Xway: the \fIexpire\fR program uses two different expiration
Xdates, one for when the article should be removed, and another
Xfor when the entry should be removed from the ``history'' file.
XWith that in mind, the `-a' flag sets the default history expire
Xdate for the `history' file, and the `-h' and `-H' flags set up
Xthe window (eg. the program checks:
X.ft CW
X.nf
X
X	min-hist-expire < current-expire < max-hist-expire
X
X.fi
X.ft R
Xfor each \fIexpire\fR command output).  Please see the 
X\fIexpire\fR man page for more information on the `-e'
Xand `-E' flags.
X.sp
XThe default value for this is 28 days.
X.TP 8
X.B \-c    	
XMake the groupname list comma separated, rather than space
Xseparated.  This is a cosmetic change to the output, but you
Xmight have a version of \fIexpire\fR that wants one or the
Xother explicitly.
X.TP 8
X.B \-e cmd	
XUses 'cmd' for output rather than the default expire program
X.sp
XThe default set to ``/usr/local/lib/news/expire''.
X.TP 8
X.B \-g n  	
XForces `n' or less groups output per command.
XThis is because some versions of \fIexpire\fR have a limit as
Xto the number of groups they'll accept for expiration in a single
Xinvocation.
X.sp
XThe default is 50 groups.
X.TP 8
X.B \-h n  	
XSet the default minimum history expire value to 'n'  (see `-a' above)
X.sp
XThe current default is set to 14 days.
X.TP 8
X.B \-H n  	
XSet the default maximum history expire value to 'n'  (see `-a' above)
X.sp
XThe current default is set to 28 days.
X.TP 8
X.B \-r    	
XTakes user id 0 account ".newsrc" files into account \(em a lot of
Xsites have multiple roots, with each having their usual home
Xdirectory (eg. the one for their non-administrative account).
XIn a situation like this there is no reason to pay the extra
Xoverhead of checking their ".newsrc" file twice.
X.sp
XThe default is to ignore user id 0 accounts.
X.TP 8
X.B \-o    	
XForces one-group-per-line output format.
X.sp
XThe default is to use the value of the `-g' flag for groups per 
Xoutput line.
X.TP 8
X.B \-v    	
XTurns on verbose output mode.
X.PP
XIn addition, the program allows the administrator to define a file
Xthat contains default expiration times for groups or sets of 
Xgroups.  This file is called ``pexpire.defaults'' and the format
Xit expects is:
X.nf
X
X    pattern	+expire     -expire
X
X.fi
XWhere the pattern can be any reguler expression accepted by the
Xregexp(3c) package, the +expire is the number of days to expire
Xthe group if people are reading it, and -expire is the number
Xof days to expire the group if no-one is reading it currently.
X.SH EXAMPLES
XThe configuration we have locally for ourselves has the following
X"pexpire.defaults" file:
X.nf
X.ft CW
X
X  #
X  # This is the "pexpire" default expiration times file.  The 
X  # format of this file is:
X  #
X  #   <regular expression>	<+expire> 	<-expire>
X  #
X  # where <+expire> is the expiration date for groups that are 
X  # currently read by people on this machine, <-expire> are for 
X  # those that are unread, and <regular expression> is any regular 
X  # expression as per regexp(3c).
X  #
X  # It is recommended that you have ".*" as the first expression so 
X  # that you can set the default expiration for all groups.  The 
X  # processing order of this information is: 
X  #      for each pattern read in this file:
X  #        for each group in the active file:
X  #          if the pattern matches, set the dates accordingly.
X  #
X  # this means that the patterns "^comp.*" and "source" in that 
X  # order would result in "comp.unix.sources" having the source 
X  # expire times.
X  #
X  # NOTE: never lead an expression with an asterisk -- assume all 
X  #       patterns are unrooted, and use '^' to get them left rooted 
X  #       if you want to
X  .*                    14      1
X  ^hp.*                 30      15
X  ^comp.*               10      2
X  ^talk.*               7       1
X  ^soc.*                7       1
X  ^news.*               14      2
X  source                14      7
X  test                  1       1
X  comp.mail.elm         56      28
X
X.ft R
X.fi
XNotice that the first regular expression, ``.*'', gives us the default
Xexpiration time for all groups on our machine, then we modify it according
Xto local interests and needs.  Also notice that patterns default to being
Xable to `float', that is, ``source'' matches all groups that have the word
Xsource in their names, whether left, right, or not rooted at all.
X.sp
XAdditionally, we invoke the following shell script via cron:
X.nf
X.ft CW
X
X  : Use /bin/sh
X  
X  # expire news using pexpire()
X  # script written by Rob Sartin, HP  (sartin@hplabs.hp.com) 
X
X  expire_script="/tmp/expire$$"
X  trap 'rm -f $expire_script' 0 1 2 3 15
X  
X  # display our disk space utilization before the command ...
X
X  echo "Expiring old news"
X  echo "\\nBefore:"
X  bdf
X  
X  # get the netnews home directory by fiddling /etc/passwd:
X
X  eval `awk -F: "/^netnews:/"' { printf "LIBDIR=%s;\\n", $6 }' \\
X     < /etc/passwd`
X  
X  # create the new expire script
X
X  rm -f ${expire_script}
X  ${LIBDIR}/pexpire > ${expire_script} 2>/dev/null
X  chmod 0755 ${expire_script}
X
X  # and execute it:
X
X  ${expire_script}
X
X  # finally, output disk space utilization after the command
X
X  echo "\\nAfter:"
X  bdf
X
X  # and we're done.
X
X  exit 0
X
X.ft R
X.fi
XNote that you can have a minimal script by having the following
Xsequence instead, if you choose:
X.ft CW
X.nf
X
X  : Use /bin/sh
X
X  PEXPIRE=/usr/local/lib/news/pexpire
X  TMPFILE=/tmp/expire.$$
X
X  $PEXPIRE > $TMPFILE 
X
X  sh $TMPFILE
X
X  exit 0
X
X.fi
X.ft R
XThough the former is recommended.
X.PP
XAlso, you can test out the pexpire program to see what it thinks
Xthe expiration time of a specific group is, for example, by a
Xsequence like:
X.nf
X.ft CW
X
X  % pexpire -o -e expire | grep \fIgroup you're interested in\fR
X
X.ft R
X.fi
XFor example:
X.nf
X.ft CW
X
X  % pexpire -o -e expire | grep soc.singles
X
X  expire -e 7 -E 28 -n soc.singles
X
X.ft R
X.fi
XThis tells us that the group is to be expired in 7 days, but that
Xthe actual article entries are to remain in the netnews history
Xfile for 28 days.
X.PP
XWe can also find out what user ``.newsrc'' files are 
Xchecked with:
X.nf
X.ft CW
X
X  % pexpire > /dev/null
X
X  Checking against ".newsrc" for the following users:
X          sartin taylor jin markc
X
X.ft R
X.fi
Xor, with the `-v' verbose option turned on:
X.nf
X.ft CW
X
X  % pexpire -v | sed '/^$/,$d'
X
X  Read 511 groups out of the active file.
X  Checked against 10 patterns in the default-expire file.
X  Checking against .newsrc for user "sartin"
X  Checking against .newsrc for user "taylor"
X  Checking against .newsrc for user "jin"
X  Checking against .newsrc for user "markc"
X  
X.ft R
X.fi
X.SH AUTHOR
XDave Taylor, Hewlett-Packard Company  (taylor\s-1@\s+1hplabs.hp.com)
X.SH FILES
X.nf
X.if n .ta 26
X.if t .ta 20
X/etc/passwd	for accounts to check ".newsrc" files
X$USER/.newsrc	for each account on the machine, to check
X$NETNEWS	usually ``/usr/local/lib/news''
X$NETNEWS/active	Where the netnews active file lives
X$NETNEWS/expire	The `real' \fIexpire\fR command
X$NETNEWS/pexpire.default	home for the ``pexpire.default'' file
X/bin/sh	valid login shell for user
X/bin/csh	valid login shell for user
X/bin/ksh	valid login shell for user
X/bin/rsh	valid login shell for user
X.SH SEE\ ALSO
Xexpire(1)
END_OF_FILE
if test 8460 -ne `wc -c <'bpexpire.1'`; then
    echo shar: \"'bpexpire.1'\" unpacked with wrong size!
fi
# end of 'bpexpire.1'
fi
if test -f 'cpexpire.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'cpexpire.1'\"
else
echo shar: Extracting \"'cpexpire.1'\" \(3939 characters\)
sed "s/^X//" >'cpexpire.1' <<'END_OF_FILE'
X.\" =()<.ds a @<NEWSARTS>@>()=
X.ds a /usr/spool/news
X.\" =()<.ds b @<NEWSBIN>@>()=
X.ds b /usr/lib/newsbin
X.\" =()<.ds c @<NEWSCTL>@>()=
X.ds c /usr/lib/news
X.\" =()<.ds m @<NEWSMASTER>@>()=
X.ds m postmaster
X.ad b
X.TH PEXPIRE 8 local
X.SH NAME
Xpexpire, dopexpire - expire netnews groups based on local readership
X.SH SYNOPSIS
X.B \*b/expire/pexpire
X[
X.B \-s
X] 
X[
X.B \-v
X] 
X[
X.B \-e N
X] 
X[
X.B \-r N
X] 
X[
X.B \-p N
X] 
X[
X.B \-E N
X] 
X[
X.B \-R N
X] 
X[
X.B \-P N
X] 
X.br
X.B \*b/expire/doexpire
Xexpireoptions
X.SH DESCRIPTION
X.I Pexpire
Xis intended to offer a finer granularity in the expiration of
Xnetnews articles on a multi-user machine.  The philosophy behind
Xit is that there are typically a large set of newsgroups that
Xno-one on the machine reads, which makes them very likely targets
Xfor shorter expiration times.  
X.PP
XThis program allows you to do exactly that \(em it lets you set
Xdefault expiration times for all groups on your machine depending
Xon if they are subscribed to or not, then checks each users
X".newsrc" to ascertain this information.  The finaly output of
Xthe program is a set of \fIexpire\fR lines suitable for automatic execution
Xby \fIdopexpire\fR out of cron.
X.PP
XThe flags understood are:
X.TP 8
X.B \-e N  	
XSet expirey period of all read news groups to 'N'. 
XThe default value for this is 14 days.
X.TP 8
X.B \-r N  	
XSet retention period of all read news groups to 'N'. 
XThe default value for this is 14 days.
X.TP 8
X.B \-p N  	
XSet purge period of all read news groups to 'N'. 
XThe default value for this is 14 days.
X.TP 8
X.B \-E N  	
XSet expirey period of all unread news groups to 'N'. 
XThe default value for this is 1 day.
X.TP 8
X.B \-R N  	
XSet retention period of all unread news groups to 'N'. 
XThe default value for this is 1 day.
X.TP 8
X.B \-P N  	
XSet purge period of all unread news groups to 'N'. 
XThe default value for this is 1 day.
X.TP 8
X.B \-s    	
XTakes user id 0 account ".newsrc" files into account \(em a lot of
Xsites have multiple roots, with each having their usual home
Xdirectory (eg. the one for their non-administrative account).
XIn a situation like this there is no reason to pay the extra
Xoverhead of checking their ".newsrc" file twice.
X.sp
XThe default is to ignore user id 0 accounts.
X.TP 8
X.B \-v    	
XTurns on verbose output mode.
X.PP
XWe can find out what user ``.newsrc'' files are 
Xchecked with:
X.nf
X.ft CW
X
X.B 
X  % \*b/expire/pexpire -v | sed '/^$/,$d'
X
X  Read 511 groups out of the active file.
X  Checked against 10 patterns in the default-expire file.
X  Checking against .newsrc for user "sartin"
X  Checking against .newsrc for user "taylor"
X  Checking against .newsrc for user "jin"
X  Checking against .newsrc for user "markc"
X  
X.ft R
X.fi
X.PP
X.I Dopexpire
Xchecks whether another \fIdopexpire\fR is running,
Xchecks that there is enough disk space for expiry and archiving,
Xbuilds the temporary expire list using \fIpexpire\fR and \fI\*c/pexplist\fR,
Xinvokes \fIexpire\fR with any \fIexpireoptions\fR given and with
Xtemporary expire list as the control file,
Xand reports any difficulties by sending mail to \fI\*m\fR.
XThis is usually better than just running \fIexpire\fR directly.
XIf space is adequate for archiving but persistently inadequate for the
Xtemporaries needed for history rebuilding, \fIdopexpire\fR reports this
Xand invokes \fIexpire\fR with the \fB\-r\fR option.
X.SH AUTHOR
XDave Taylor, Hewlett-Packard Company  (taylor\s-1@\s+1hplabs.hp.com)
X.br
XModified for Cnews by Jim Pickering   (jrp\s-1@\s+1rducky)
X.SH FILES
X.nf
X.if n .ta 26
X.if t .ta 20
X/etc/passwd	for accounts to check ".newsrc" files
X$USER/.newsrc	for each account on the machine, to check
X\*c/active	where the netnews active file lives
X\*c/pexplist	where the system default pexpire list lives
X\*c/LOCKexpire	lock file
X\*c/L.$$	lock file
X/bin/sh	valid login shell for user
X/bin/csh	valid login shell for user
X/bin/ksh	valid login shell for user
X/bin/rsh	valid login shell for user
X.SH SEE ALSO
Xexpire(8), doexpire(8)
END_OF_FILE
if test 3939 -ne `wc -c <'cpexpire.1'`; then
    echo shar: \"'cpexpire.1'\" unpacked with wrong size!
fi
# end of 'cpexpire.1'
fi
if test -f 'dopexpire' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'dopexpire'\"
else
echo shar: Extracting \"'dopexpire'\" \(2449 characters\)
sed "s/^X//" >'dopexpire' <<'END_OF_FILE'
X#! /bin/sh
X# dopexpire - overall administration for expire using pexpire
X
X# =()<. ${NEWSCONFIG-@<NEWSCONFIG>@}>()=
X. ${NEWSCONFIG-/usr/lib/news/bin/config}
X
XPATH=$NEWSCTL/bin:$NEWSBIN/expire:$NEWSBIN:$NEWSPATH ; export PATH
Xumask $NEWSUMASK
X
Xlock="$NEWSCTL/LOCKexpire"
Xltemp="$NEWSCTL/L.$$"
X
X# retention, expire, and purge defaults for 'all' groups that we may have
X# missed with pexpire or our default file
Xretention_default="0"
Xexpirey_default="0"
Xpurge_default="0"
X
X#sysadmin supplied expiration defaults
Xdefaults_file="$NEWSCTL/pexplist"
X
X# expire list generated by pexpire
Xpexpire_list="/tmp/pexpire$$"
Xpexpire_tmp="/tmp/pexptmp$$"
X
X# expired and bounds strings for expire(8)
Xexpire_string="/expired/\tx\t14\t-"
Xbounds_string="/bounds/\tx\t0-1-90\t-"
X
Xecho $$ >$ltemp
Xtrap "rm -f $ltemp ; exit 0" 0 1 2 15
Xif newslock $ltemp $lock
Xthen
X	trap "rm -f $ltemp $lock ; exit 0" 0 1 2 15
Xelse
X	echo "$0: expire apparently already running" | mail "$NEWSMASTER"
X	exit 1
Xfi
X
Xif test " `spacefor 1 archive`" -le 0
Xthen
X	echo "$0: not enough space for archiving" | mail "$NEWSMASTER"
X	exit 1
Xfi
X
Xcd $NEWSCTL
Xropt=-r
Xfor counter in x x x x		# four tries
Xdo
X	size="`sizeof history history.pag history.dir`"
X	if test " `spacefor $size control`" -gt 0
X	then
X		ropt=
X		break
X	fi
X	sleep 600		# and hope it will improve
Xdone
Xif test " $ropt" = " -r"
Xthen
X	echo "$0: short of space for temporaries, resorting to \`expire -r'" |
X							mail "$NEWSMASTER"
Xfi
X
Xtrap "rm -f $pexpire_list $ltemp $lock ; exit 0" 0 1 2 15
X
Xecho $expire_string > $pexpire_list
Xecho $bounds_string >> $pexpire_list
Xif test -s $defaults_file
Xthen
X	cat $defaults_file >> $pexpire_list
Xfi
Xtrap "rm -f $pexpire_list $pexpire_tmp $ltemp $lock ; exit 0" 0 1 2 15
Xpexpire -E 0.5 -R 0.5 -P 0.5 -e 14 -r 14 -p 14 > $pexpire_tmp 2>/tmp/doex$$
Xif test -s /tmp/doex$$
Xthen
X	(echo 'pexpire problems:' ; cat /tmp/doex$$ ) | mail "$NEWSMASTER"
X	rm -f /tmp/doex$$
X	exit 1
Xfi
X# sort in reverse order so groups at top of hierarchy don't expire lower
X# groups, i.e. rec.humor may affect rec.humor.funny if before
Xsort -r $pexpire_tmp >> $pexpire_list
Xrm -f $pexpire_tmp
Xtrap "rm -f $pexpire_list $ltemp $lock ; exit 0" 0 1 2 15
Xecho "all\tx\t$retention_default-$expirey_default-$purge_default\t-" >> $pexpire_list
Xexpire $ropt $* $pexpire_list 2>/tmp/doex$$
Xif test -s /tmp/doex$$
Xthen
X	(echo 'expire problems:' ; cat /tmp/doex$$ ) | mail "$NEWSMASTER"
X	rm -f /tmp/doex$$
X	exit 1
Xfi
Xrm -f /tmp/doex$$
Xexit 0
END_OF_FILE
if test 2449 -ne `wc -c <'dopexpire'`; then
    echo shar: \"'dopexpire'\" unpacked with wrong size!
fi
chmod +x 'dopexpire'
# end of 'dopexpire'
fi
if test -f 'pexpire.batch' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pexpire.batch'\"
else
echo shar: Extracting \"'pexpire.batch'\" \(446 characters\)
sed "s/^X//" >'pexpire.batch' <<'END_OF_FILE'
X# Use /bin/sh
X# expire news using pexpire()
X# script written by Rob Sartin, HP (sartin@hplabs.hp.com)
Xexpire_script="/tmp/expire$$"
Xtrap 'rm -f $expire_script' 0 1 2 3 15
X
X# display our disk space utilization before the command ...
X
XPEXPIRE=/usr/lib/news/pexpire
X
X# create the new expire script
X
X${PEXPIRE} > ${expire_script} 2>/dev/null
Xchmod 0750 ${expire_script}
X
X# and execute it:
X
X${expire_script} > /dev/null 2>&1
X
X# and we're done.
Xexit 0
END_OF_FILE
if test 446 -ne `wc -c <'pexpire.batch'`; then
    echo shar: \"'pexpire.batch'\" unpacked with wrong size!
fi
chmod +x 'pexpire.batch'
# end of 'pexpire.batch'
fi
if test -f 'pexpire.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pexpire.c'\"
else
echo shar: Extracting \"'pexpire.c'\" \(21446 characters\)
sed "s/^X//" >'pexpire.c' <<'END_OF_FILE'
X/**                pexpire.c            **/
X
X/** This program is designed to set the expire dates of newsgroups according
X    to whether they are read locally or not.  The idea is that it is easy
X    to go into users .newsrc files and compile an overall list of who reads
X    what groups, then 1-day-expire those groups that no-one is reading.
X
X    This hinges on the availability of other local machines to serve as
X    archives for various groups, as well as the understanding of the 
X    local users that subscribing to a new newsgroup will more than likely
X    net you almost *no* new articles -- but will allow the news to flow
X    in normally and then gradually build up to a more reasonable level.
X
X    (C) Copyright 1988 Dave Taylor
X
X    ***************************************************************************
X    **  Permission is granted for unlimited modification, use, and dist-     **
X    **  ribution, except that this software may not be sold for profit       **
X    **  directly nor as part of any software package.  This software is made **
X    **  available with no warranty of any kind, express or implied.          **
X    ***************************************************************************
X
X**/
X
X#include <stdio.h>
X#include <pwd.h>
X
X#include "pexpire.h"
X
X#define ROOT_UID    0                 /* who's root? */
X
X#define MAX_GROUPS    1024            /* should be sufficient */
X
X#define SLEN        128
X
X#define COLON        ':'
X
X#ifndef TRUE
X# define TRUE        1
X# define FALSE        0
X#endif
X
X/** some easy to read and use macro functions **/
X
X#define whitespace(c)    (c == ' ' || c == '\t')
X#define matches(re,pat)    (regex(re, pat) != NULL)
X
X#define plural(n)    (n == 1 ? "" : "s")
X
X/** and data structures/global variables for the program **/
X
Xstruct group_rec {
X    char     *name;
X    int      is_read;
X#ifndef CNEWS
X    int      read_expire;
X    int      unread_expire;
X#endif
X       };
X
Xchar *login_shells[] = { "/bin/sh", "/bin/csh", "/bin/ksh", "/bin/rsh", "" };
X
Xstruct group_rec groups[MAX_GROUPS];
X
Xint group_count = 0,                /** total number of groups  **/
X    verbose = FALSE,                /** lots of output?         **/
X#ifndef CNEWS
X    comma_separated = TRUE,         /** output list format        **/
X    groups_per_cmd,                 /**  .. and more too        **/
X
X    min_history_expire,             /** for the expire() cmd    **/
X    max_history_expire,             /**     "   "               **/
X    default_history_expire,         /**     "   "            **/
X
X    output_one_per_line = FALSE,    /** final output format     **/
X#endif
X    include_root = FALSE;           /** include root .newsrc?   **/
X
X#ifndef CNEWS
Xchar *prog_name,                    /** program name for errors **/
X     expire_cmd[SLEN];              /** expire cmd we'll use    **/
X#else
Xchar *prog_name;                    /** program name for errors **/
Xdouble    read_retention_period,
X    read_purge_period,
X    read_expirey_period,
X    unread_retention_period,
X    unread_purge_period,
X    unread_expirey_period;
X#endif
X
X/** forward definitions and other stuff to keep LINT a happy clam   **/
X
Xchar *regcmp(), *regex(), *strcpy(), *strcat(), *strchr(), *malloc();
Xvoid    exit(), perror(), qsort();
X
X/** The algorithm that we'll be using here is:
X
X     1. Read in the active file to get a list of all newsgroups available
X
X     2. Go through the ``EXPIRE_DEFAULTS'' file (bnews version) to set initial
X        expiration dates (typically by high level groups -- it's left rooted).
X        This file typically has two sets of numbers, the first being for groups
X        that are being actively read, the second being for those that are
X        not.  For example:
X
X             soc.*    24    1
X
X        would set any soc.* group to a 24 day expire if read, and a 1 day
X        expire if not.
X
X     3. Then, for each user of the system:
X
X            if they have a .newsrc,
X            tag as 'read' any group that the user subscribes to
X     
X     4. Sort the newsgroups by expiration date (bnews version), then output a
X        shell script suitable for automatic execution (bnews version) or output
X        an expire list file for expire(1) (cnews version) ...
X
X**/
X
Xmain(argc, argv)
Xchar *argv[];
X{
X    extern char *optarg;
X    int c;
X
X    /** first off let's grab the program name for error messages **/
X
X    prog_name = *argv;
X
X    /** initialize some values that can be changed by the user **/
X#ifndef CNEWS
X    groups_per_cmd = DEFAULT_GROUPS_PER_LINE;
X
X    max_history_expire = DEFAULT_MAX_HISTORY_EXPIRE;
X    min_history_expire = DEFAULT_MIN_HISTORY_EXPIRE;
X
X    default_history_expire = DEFAULT_HISTORY_EXPIRE;
X
X    (void) strcpy(expire_cmd, EXPIRE);
X
X    /** now process the starting arguments **/
X
X    while ((c = getopt(argc, argv, "a:ch:H:e:g:rov")) != EOF) {
X#else
X    read_retention_period = READ_RETENTION_DEFAULT;
X    read_purge_period = READ_PURGE_DEFAULT;
X    read_expirey_period = READ_EXPIREY_DEFAULT;
X    unread_retention_period = UNREAD_RETENTION_DEFAULT;
X    unread_purge_period = UNREAD_PURGE_DEFAULT;
X    unread_expirey_period = UNREAD_EXPIREY_DEFAULT;
X    while ((c = getopt(argc, argv, "r:e:p:R:E:P:sv")) != EOF) {
X#endif
X      switch (c) {
X
X#ifndef CNEWS
X        case 'a' : default_history_expire = atoi(optarg);
X               break;
X
X        case 'c' : comma_separated = TRUE;
X               break;
X
X        case 'e' : (void) strcpy(expire_cmd, optarg);    
X               break;
X
X        case 'g' : groups_per_cmd = atoi(optarg);
X               break;
X
X        case 'h' : min_history_expire = atoi(optarg);
X               break;
X
X        case 'H' : max_history_expire = atoi(optarg);
X               break;
X
X        case 'r' : include_root = TRUE;
X               break;
X
X        case 'o' : output_one_per_line = TRUE;
X               break;
X
X        case 'v' : verbose = TRUE;
X               break;
X
X        default  : (void) fprintf(stderr,
X   "\nUsage: %s [-a n] [-c] [-e cmd] [-g] [-h n] [-H n] [-r] [-o] [-v]\n", 
X                  prog_name);
X               (void) fprintf(stderr,"\nWhere:\n");
X               (void) fprintf(stderr,
X   "   -a n  \tset default history expire value to 'n' (see the man page)\n");
X               (void) fprintf(stderr,
X   "         \t(the current default is set to %d day%s)\n", 
X                      DEFAULT_HISTORY_EXPIRE, 
X                  plural(DEFAULT_HISTORY_EXPIRE));
X               (void) fprintf(stderr,
X   "   -c    \tmake the groupname list comma separated, rather than space\n");
X               (void) fprintf(stderr,
X   "   -e cmd\tuses 'cmd' for output rather than the default expire program\n");
X               (void) fprintf(stderr,
X   "         \t(current default set to \"%s\")\n", EXPIRE);
X               (void) fprintf(stderr,
X   "   -g n  \tforces `n' or less groups output per command (default = %d)\n",
X                  DEFAULT_GROUPS_PER_LINE);
X               (void) fprintf(stderr,
X   "   -h n  \tset the default minimum history expire value to 'n'\n");
X               (void) fprintf(stderr,
X   "         \t(the current default is set to %d day%s)\n", 
X                      DEFAULT_MIN_HISTORY_EXPIRE, 
X                  plural(DEFAULT_MIN_HISTORY_EXPIRE));
X               (void) fprintf(stderr,
X   "   -H n  \tset the default maximum history expire value to 'n'\n");
X               (void) fprintf(stderr,
X   "         \t(the current default is set to %d day%s)\n", 
X                      DEFAULT_MAX_HISTORY_EXPIRE, 
X                  plural(DEFAULT_MAX_HISTORY_EXPIRE));
X               (void) fprintf(stderr,
X   "   -r    \ttakes UID 0 account .newsrc files into account\n");
X               (void) fprintf(stderr,
X   "   -o    \tforces one-group-per-line output format\n");
X               (void) fprintf(stderr,
X   "   -v    \tturns on verbose output mode\n\n");
X               exit(0);
X#else
X        case 's' : include_root = TRUE;
X               break;
X
X        case 'v' : verbose = TRUE;
X               break;
X
X        case 'r' : read_retention_period = atof(optarg);
X               sscanf(optarg,"%lg",&read_retention_period);
X               break;
X
X        case 'p' : read_purge_period = atof(optarg);
X               sscanf(optarg,"%lg",&read_purge_period);
X               break;
X
X        case 'e' : read_expirey_period = atof(optarg);
X               sscanf(optarg,"%lg",&read_expirey_period);
X               break;
X
X        case 'R' :
X               sscanf(optarg,"%lg",&unread_retention_period);
X               break;
X
X        case 'P' :
X               sscanf(optarg,"%lg",&unread_purge_period);
X               break;
X
X        case 'E' :
X               sscanf(optarg,"%lg",&unread_expirey_period);
X               break;
X
X        default  : (void) fprintf(stderr, "\nUsage: %s [-r n] [-e n] [-p n] [-R n] [-E n] [-P n] [-s] [-v]\n", prog_name);
X               (void) fprintf(stderr,"\nWhere:\n");
X               (void) fprintf(stderr, "\t-r n\tset retention period for read news groups to 'n',\n\t\twhere 'n' may be a decimal fraction (default = %g).\n", READ_RETENTION_DEFAULT);
X               (void) fprintf(stderr, "\t-e n\tset expirey period for read news groups to 'n',\n\t\twhere 'n' may be a decimal fraction (default = %g).\n", READ_EXPIREY_DEFAULT);
X               (void) fprintf(stderr, "\t-p n\tset purge period for read news groups to 'n',\n\t\twhere 'n' may be a decimal fraction (default = %g).\n", READ_PURGE_DEFAULT);
X               (void) fprintf(stderr, "\t-R n\tset retention period for unread news groups to 'n',\n\t\twhere 'n' may be a decimal fraction (default = %g).\n", UNREAD_RETENTION_DEFAULT);
X               (void) fprintf(stderr, "\t-E n\tset expirey period for unread news groups to 'n',\n\t\twhere 'n' may be a decimal fraction (default = %g).\n", UNREAD_EXPIREY_DEFAULT);
X               (void) fprintf(stderr, "\t-P n\tset purge period for unread news groups to 'n',\n\t\twhere 'n' may be a decimal fraction (default = %g).\n", UNREAD_PURGE_DEFAULT);
X               (void) fprintf(stderr, "\t-s\ttakes UID 0 account .newsrc files into account\n");
X               (void) fprintf(stderr, "\t-v\tturns on verbose output mode\n\n");
X               exit(0);
X#endif
X      }
X    }
X    
X    /** next let's read in the netnews active file **/
X
X    read_active_file();
X
X    /** read in the EXPIRE_DEFAULTS file and set default expires **/
X#ifndef CNEWS
X    set_default_expiration_dates();
X#endif
X    /** check each user for a .newsrc and mark groups subscribed **/
X
X    check_each_user();
X
X    /** whip through a quick resort by expiration time **/
X#ifndef CNEWS
X    sort_groups_by_expiration();
X#endif
X    /** and finally output the script that we can execute **/
X
X    output_script();
X
X    /** and we're done **/
X
X    return(0);
X}
X
Xread_active_file()
X{
X    /** this routine reads in the active file, sorts it, and 
X        returns.  It is assumed that it always works - if something
X        fails it will exit from here..
X    **/
X
X    int  compare();
X
X    FILE *fd;
X    char buffer[SLEN];
X    register int  i;
X    
X    if ((fd = fopen(ACTIVE_FILE, "r")) == NULL) {
X      (void) fprintf(stderr,"%s: cannot open active file '%s':\n",
X          prog_name, ACTIVE_FILE);
X      perror("fopen");
X      exit(1);
X    }
X
X    while (fgets(buffer, SLEN, fd) != NULL) {
X
X      /** get just the first word ... **/ 
X
X      for (i=0; ! whitespace(buffer[i]); i++) ;
X      buffer[i] = '\0';
X
X      if ((groups[group_count].name = malloc((unsigned)(i+1))) == NULL) {
X         (void) fprintf(stderr,"%s: couldn't malloc memory for group '%s'\n",
X            prog_name, buffer);
X        perror("malloc");
X        exit(1);
X      }
X
X      /** now load up the new record and increment our counter **/
X
X      (void) strcpy(groups[group_count].name, buffer);
X      groups[group_count].is_read = FALSE;
X#ifndef CNEWS
X      groups[group_count].read_expire = DEFAULT_READ_EXPIRE;
X      groups[group_count].unread_expire = DEFAULT_UNREAD_EXPIRE;
X#endif
X
X      group_count++;
X
X      /** and on to the next one... **/
X    }
X    
X    (void) fclose(fd);
X
X    qsort(groups, (unsigned) group_count, 
X          sizeof (struct group_rec), compare);
X
X    if (verbose)
X      (void) printf("Read %d group%s out of the active file.\n",
X          group_count, plural(group_count)); 
X}
X
X#ifndef CNEWS
Xset_default_expiration_dates()
X{
X    /** this routine is responsible for reading in the default
X        expire file and setting the default expiration dates on
X        all of the groups in memory.   If there is no file or it
X        is impossible to get to, then the defaults indicated in
X        this program will be used for all groups.
X
X        The format of the file is quite simple:
X
X            <regular expression> < tab > <+expire> <tab> <-expire>
X
X        where +expire is the expiration time if people are reading
X        the group, and -expire is if they're not.  The regular
X        expression format is that of regex(3c), so you can have
X        structures such as "^comp.*" and so on.
X    **/
X
X    FILE *fd;
X    char  buffer[SLEN], *regular_expression;
X    int   read_expire, unread_expire;
X    register int i, count = 0;
X
X    if ((fd = fopen(EXPIRE_DEFAULTS, "r")) == NULL) {
X      (void) fprintf(stderr,"%s: Couldn't read file '%s'\n", 
X          prog_name, EXPIRE_DEFAULTS);
X      (void) fprintf(stderr,
X          "(Using default expirations: read = %d, unread = %d)\n",
X          DEFAULT_READ_EXPIRE, DEFAULT_UNREAD_EXPIRE);
X      perror("fopen");
X      (void) fprintf(stderr,"---\n");
X
X      /** now spin through setting all expire dates accordingly **/
X
X      for (i=0; i < group_count; i++) {
X        groups[i].read_expire = DEFAULT_READ_EXPIRE;
X        groups[i].unread_expire = DEFAULT_UNREAD_EXPIRE;
X      }
X
X      return;
X    }
X
X    /** if we've gotten here we've got the file open and ready
X        to work with... **/
X
X    while (fgets(buffer, SLEN, fd) != NULL) {
X
X       if (buffer[0] == '#' || strlen(buffer) < 3) continue;
X
X      (void) sscanf(buffer, "%*s %d %d", &read_expire, &unread_expire);
X
X      count++;
X
X      for (i=0;! whitespace(buffer[i]); i++) ; 
X      buffer[i] = '\0';
X
X      /** now apply this pattern to all groups we've got, setting
X          the expire date as makes sense... **/
X
X      regular_expression = regcmp(buffer, (char *) 0);
X
X      for (i=0 ; i < group_count; i++)
X        if (matches(regular_expression, groups[i].name)) {
X          groups[i].unread_expire = unread_expire;
X          groups[i].read_expire = read_expire;
X        }
X    }
X
X    (void) fclose(fd);
X
X    if (verbose)
X     (void) printf("Checked against %d pattern%s in default-expire file.\n",
X         count, plural(count)); 
X}
X#endif
X
Xcheck_each_user()
X{
X    /** this routine goes through the /etc/passwd file to
X        find all the users on the machine.  For each entry
X        found, it will ascertain if they have a login shell
X        then look for a .newsrc file.  If they have one, it
X        will extract all the groups that they currently read,
X        marking each in memory as being read ..
X    **/
X
X    FILE   *fd;
X    struct passwd    *getpwent(), *pass;
X    char   newsrc[SLEN], buffer[SLEN], user_list[SLEN];
X    register int i;
X
X    /** initialize **/
X
X    user_list[0] = '\0';
X
X    /** and step through the password file .. **/
X
X    while ((pass = getpwent()) != NULL) {
X      /*if (has_login_shell(pass->pw_shell)) { */
X
X        if (pass->pw_uid == ROOT_UID && ! include_root)
X          continue;
X
X        (void) sprintf(newsrc, "%s/%s", pass->pw_dir, NEWSRC);
X        
X        if ((fd = fopen(newsrc, "r")) == NULL) continue;
X
X        if (verbose)
X          (void) printf("Checking against %s for user \"%s\"\n", 
X             NEWSRC, pass->pw_name);
X        else {
X          if (user_list[0] != '\0') (void) strcat(user_list, " ");
X          (void) strcat(user_list, pass->pw_name);
X        }
X
X        while (fgets(buffer, SLEN, fd) != NULL)
X          if (strchr(buffer, COLON) != (char *) NULL) {
X            for (i=0;buffer[i] != COLON; i++);
X        buffer[i] = '\0';
X            mark_as_read(buffer);
X          } 
X
X        (void) fclose(fd);
X      /*}*/
X    }
X
X    if (verbose && strlen(user_list) > 0) 
X      (void) fprintf(stderr, 
X          "Checked against \"%s\" for the following users:\n\t%s\n",
X          NEWSRC, user_list);
X}
X
X#ifndef CNEWS
Xsort_groups_by_expiration()
X{
X    /** We now resort the list according to the expiration date of
X        the group... 
X    **/
X
X    int compare_expirations();
X
X    qsort(groups, (unsigned) group_count, sizeof (struct group_rec), 
X          compare_expirations);
X}
X#endif
X
Xoutput_script()
X{
X    /** Now that we've gotten the groups sorted by their
X        expiration date, we can output a script that is suitable 
X        for input to the real netnews expire() program...
X    **/
X    
X    register int i; 
X#ifndef CNEWS
X    int current_expire_time = 0, expire, 
X        groups_on_line = 0, on_line = 0, in_expiration = 0;
X
X    /** set the current expiration time, then:
X          for each group that has the same date
X          output the group name
X        when we hit a new date output the new format line
X    **/
X
X    for (i=0; i < group_count; i++) {
X
X      /** set the expiration time based on if the group is 
X          currently being read or not... 
X      **/
X
X      expire = groups[i].is_read ? groups[i].read_expire : 
X             groups[i].unread_expire;
X
X      if (output_one_per_line) {
X        if (expire > max_history_expire)
X           (void) printf("%s -e %d -E %d -n %s\n", 
X              expire_cmd, expire, expire, groups[i].name);
X         else if (expire < min_history_expire)
X           (void) printf("%s -e %d -E %d -n %s\n", 
X              expire_cmd, expire, default_history_expire, 
X              groups[i].name);
X        else
X           (void) printf("%s -e %d -n %s\n", 
X              expire_cmd, expire, groups[i].name);
X      }
X      else {
X        if ( expire != current_expire_time || 
X         in_expiration > groups_per_cmd) {
X          if (expire > max_history_expire)
X            (void) printf("\n%s -e %d -E %d -n ", 
X                  expire_cmd, expire, expire);
X           else if (expire < min_history_expire)
X            (void) printf("\n%s -e %d -E %d -n ", 
X                  expire_cmd, expire, default_history_expire);
X          else
X            (void) printf("\n%s -e %d -n ", expire_cmd, expire);
X          groups_on_line = 0;
X          current_expire_time = expire;
X          in_expiration = 0;
X        }
X
X        in_expiration++;
X
X        on_line += strlen(groups[i].name) + 1;
X        
X        if (on_line > 66) {
X          (void) printf("%c \\\n\t", groups_on_line > 0? ',':' ');
X          on_line = 8 + strlen(groups[i].name);
X          groups_on_line = 0;
X        }
X
X        if (groups_on_line)
X          (void) printf("%c%s", comma_separated? ',' : ' ', groups[i].name);
X        else
X          (void) printf("%s", groups[i].name);
X
X        groups_on_line++;
X      }
X    }
X    (void) printf("\n");
X#else
X    for (i=0; i < group_count; i++) {
X
X      /** set the expiration time based on if the group is 
X          currently being read or not... 
X      **/
X
X      if (groups[i].is_read)
X           (void) printf("%s\tx\t%g-%g-%g\t-\n",groups[i].name, read_retention_period, read_expirey_period, read_purge_period);
X      else
X           (void) printf("%s\tx\t%g-%g-%g\t-\n",groups[i].name, unread_retention_period, unread_expirey_period, unread_purge_period);
X    }
X#endif
X}
X
Xint
Xcompare(a,b)
Xstruct group_rec *a, *b;
X{
X    /** strcmp() routine for our data structure, rather than the
X        simple expedient of just using strcmp directly.  See the
X        invocation of qsort() above
X    **/
X
X    return( strcmp(a->name, b->name) );
X}
X
X#ifndef CNEWS
Xint 
Xcompare_expirations(a, b)
Xstruct group_rec *a, *b;
X{
X    /** strcmp() routine for data for second sort -- this one
X        is a sort by the expiration date of the groups.  To
X        do this we want to look at the is_read flag and from
X        that decide which of the two expiration dates we want to be 
X        looking at.
X    **/
X
X    return ( (b->is_read ? b->read_expire : b->unread_expire) - 
X         (a->is_read ? a->read_expire : a->unread_expire) );
X}
X#endif
X 
Xint
Xhas_login_shell(shell_name)
Xchar *shell_name;
X{
X    /** returns TRUE iff the shell given is contained in the
X        list of possible login shells compiled with.
X    **/
X
X    register int i;
X
X    for (i=0; login_shells[i][0] != '\0'; i++)
X      if (strcmp(login_shells[i], shell_name) == 0) return(TRUE);
X    
X    return(FALSE);
X}
X      
Xmark_as_read(name)
Xchar *name;
X{
X    /** Mark the group specified as being read -- it's extracted
X        from a users .newsrc file.
X    **/
X
X    int index;
X
X    if ((index = find_group(name)) == -1)
X      (void) fprintf(stderr, 
X          "** Couldn't find group '%s' in internal tables?? **\n",
X          name);
X    else
X      groups[index].is_read = TRUE;
X}
X
Xint
Xfind_group(name)
Xchar *name;
X{
X    /** A binary search of the list to find the group - returns the
X        index into the 'groups' array of the group, or '-1' if not
X        in the list.
X     **/
X
X    register int first = 0, last, middle, difference;
X
X    last = group_count-1;
X
X    while (first <= last) {
X          middle = ((first+last) / 2);
X
X          difference = strcmp(name, groups[middle].name);
X
X          if (difference < 0)
X            last = middle - 1;
X          else if (difference == 0)
X            return(middle);
X          else
X            first = middle + 1;
X        }
X
X        return(-1);
X}
END_OF_FILE
if test 21446 -ne `wc -c <'pexpire.c'`; then
    echo shar: \"'pexpire.c'\" unpacked with wrong size!
fi
# end of 'pexpire.c'
fi
if test -f 'pexpire.defs' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pexpire.defs'\"
else
echo shar: Extracting \"'pexpire.defs'\" \(1087 characters\)
sed "s/^X//" >'pexpire.defs' <<'END_OF_FILE'
X#
X# This is the "pexpire" default expiration times file.  The format of this
X# file is:
X#
X#   <regular expression>	<+expire> 	<-expire>
X#
X# where <+expire> is the expiration date for groups that are currently
X# read by people on this machine, <-expire> are for those that are unread,
X# and <regular expression> is any regular expression as per regexp(3c).
X#
X# It is recommended that you have ".*" as the first expression so that you
X# can set the default expiration for all groups.  The processing order of
X# this information is: 
X#      for each pattern read in this file:
X#        for each group in the active file:
X#          if the pattern matches, set the dates accordingly.
X#
X# this means that the patterns "^comp.*" and "source" in that order
X# would result in "comp.unix.sources" having the source expire times.
X#
X# NOTE: never lead an expression with an asterisk -- assume all patterns
X#       are unrooted, and use '^' to get them left rooted if you want to
X.*		14	1
X^comp.*		8	2
X^talk.*		2	1
X^soc.*		2	1
X^news.*		5	2
X^ba.*		2	1
X^ca.*		2	1
X^misc.*		5	1
X^sci.*		2	1
X^slo.*		2	1
END_OF_FILE
if test 1087 -ne `wc -c <'pexpire.defs'`; then
    echo shar: \"'pexpire.defs'\" unpacked with wrong size!
fi
# end of 'pexpire.defs'
fi
if test -f 'pexpire.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pexpire.h'\"
else
echo shar: Extracting \"'pexpire.h'\" \(4166 characters\)
sed "s/^X//" >'pexpire.h' <<'END_OF_FILE'
X/**				   pexpire.h				   **/
X
X/****************************************************************************
X  This set of defines are those that might need to be localized or otherwise 
X  customized for your local system and setting.                            
X ****************************************************************************/
X
X/** first off, where's your netnews active file?  It'd be a suprise if
X    it wasn't as indicated here, but you can change it if you want.
X**/
X#define ACTIVE_FILE	"/usr/lib/news/active"
X
X/** NEWSRC is simply the name of the file kept in users home directories **/
X#define NEWSRC		".newsrc"
X
X
X
X
X#ifndef CNEWS
X/***********************/
X/***********************/
X/** define for Bnews **/
X/***********************/
X/***********************/
X
X/** Next, the pexpire program has a default set of rules
X    that can be applied to the set of groups to determine the expiration dates
X    either by top-level newsgroup (eg. "comp.*") or down to the 
X    specific group (eg. "comp.sys.hp").  Please see the expire man
X    page for more discussion of this file.
X**/
X
X#define EXPIRE_DEFAULTS	"/usr/lib/news/pexpire.defs"
X#endif
X
X
X/** Finally, this is the command used for expiration of news (bnews version).
X    Most likely it'll be located in the same directory as the active file
X    (see above).  If you're running a strange expire command you might want
X    to check to ensure it understands "-e", "-E" and "-n" flags... see the man
X    page for further details.
X**/
X
X#ifndef CNEWS
X#define EXPIRE 		"/usr/lib/news/expire"
X
X/** the default history expire (bnews version) is the standard number of days
X    that an article is allowed to live in the history file -- regardless of how
X    long it is on the machine in actual text form.  (This is different so that
X    you don't get into looping trouble with very fast expires and multiple news
X    feeds).
X
X    The netnews source has HISTEXP and DFLTEXP in seconds, and for our
X    own use, we'll change those back into days ...
X**/
X
X#define DEFAULT_MAX_HISTORY_EXPIRE	(HISTEXP / DAYS)
X#define DEFAULT_MIN_HISTORY_EXPIRE	(DFLTEXP / DAYS)
X
X/** if a group is being expired at less than the default minimum history
X    exiration time (bnews version), then we want to ensure that we have the
X    default time rather than the one specific to the group.  That is, if we
X    have a group with a 1 day expire, we still want to keep the articles
X    in the history file for, say, 2 weeks...
X**/
X
X#define DEFAULT_HISTORY_EXPIRE		(HISTEXP / DAYS)
X
X/** next, if the program cannot find your pexpire.default file (bnews version),
X    it will us the next two settings as the default for groups that are being
X    subscribed to and those that are not.  Recommended that the unread
X    expire not be incredibly short here in case the daemon messes up one
X    night - you might come back and a major chunk of news is gone!  
X**/
X
X#define DEFAULT_READ_EXPIRE	  7
X#define DEFAULT_UNREAD_EXPIRE	  0
X
X/** finally, when the program outputs the commands for eventual shell
X    execution (bnews version), it tries to keep them in a format that the
X    netnews expire(UTIL) command can deal with.  One of the problems is that
X    it is possible to have all 400 - 500 groups expire at the same
X    time, and it's too much for a single invocation.  Instead, you can
X    fine tune this to be the largest value possible, but smaller than
X    the max limit of expire().
X**/
X
X#define DEFAULT_GROUPS_PER_LINE   30
X
X/**************  end of local customization for Bnews ******************/
X
X#define DAYS	(60L*60L*24L)
X#define WEEKS	(7*DAYS)
X#define DFLTEXP	2*WEEKS	/* default no. of seconds to expire in		*/
X#define HISTEXP	4*WEEKS	/* default no. of seconds to forget in		*/
X#else
X
X/***********************/
X/***********************/
X/** defines for Cnews **/
X/***********************/
X/***********************/
X
X/*
X**    read/unread default retention, purge, and expirey days
X*/
X#define READ_RETENTION_DEFAULT    14.0
X#define READ_PURGE_DEFAULT        14.0
X#define READ_EXPIREY_DEFAULT      14.0
X#define UNREAD_RETENTION_DEFAULT  1.0
X#define UNREAD_PURGE_DEFAULT      1.0
X#define UNREAD_EXPIREY_DEFAULT    1.0
X
X
X#endif
END_OF_FILE
if test 4166 -ne `wc -c <'pexpire.h'`; then
    echo shar: \"'pexpire.h'\" unpacked with wrong size!
fi
# end of 'pexpire.h'
fi
if test -f 'pexplist' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pexplist'\"
else
echo shar: Extracting \"'pexplist'\" \(389 characters\)
sed "s/^X//" >'pexplist' <<'END_OF_FILE'
X#
X# pexplist
X#
X# This file contains entries that override later defaults generated by pexpire.
X# See dopexpire for use and expire(8) for format.
X#
X
X# big non-tech groups held long enough for a long weekend
X#ca,ba,sci,rec,talk,soc,misc,alt	x	4-4-4	-
X
X# real noise gets thrown away fast
Xjunk,control			x	1-1-1	-
X
X# dump all maps ... dopexpire being run by uuhosts
Xcomp.mail.maps			x	0-0-0	-
END_OF_FILE
if test 389 -ne `wc -c <'pexplist'`; then
    echo shar: \"'pexplist'\" unpacked with wrong size!
fi
# end of 'pexplist'
fi
echo shar: End of archive 1 \(of 1\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have tne archive.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0