[comp.sources.unix] v19i093: Cnews production release, Part16/19

rsalz@uunet.uu.net (Rich Salz) (06/30/89)

Submitted-by: utzoo!henry
Posting-number: Volume 19, Issue 93
Archive-name: cnews2/part16

: ---CUT HERE---
echo 'relay/makefile':
sed 's/^X//' >'relay/makefile' <<'!'
X# makefile for C news relaynews
X
X# =()<NEWSARTS = @<NEWSARTS>@>()=
XNEWSARTS = /usr/spool/news
X# =()<NEWSBIN = @<NEWSBIN>@>()=
XNEWSBIN = /usr/lib/newsbin
X# =()<NEWSCTL = @<NEWSCTL>@>()=
XNEWSCTL = /usr/lib/news
X# workaround for System V make bug
XSHELL = /bin/sh
X
XBIN=/bin
XNPROC=2
X# -DVOID=int for libc.h & old lint libraries
XDEFINES= -I../include -I. -DVOID=int -DFLUSHEVERY=6
X#CC=CC +V
X#CC=gcc -ansi -pedantic -Wall -S
X#CC=redcc
XCOPTS= -O # -pg -g
XCFLAGS=$(DEFINES) $(COPTS)
XDBM = -ldbm
XLIBS= $(DBM)
XLINT=lint
XLINTFLAGS=-haz $(DEFINES)
XLLIBS=-llocal
X# I wish I could make lint shut the fk up about some things.  Grrr!
XLINTFILT=egrep -v '(possible pointer|long assign|can.t take|never used|nnfree|getdate|:$$)'
XPROPTS=
XP=stpr
XPP=pp -Tpsc -fR # lazywriter
XPPBACK=dps | stps # lazywriter
X
XLIBOBJS=../libcnews.a
XSRC=relaynews.c active.c article.c caches.c mkdirs.c control.c fileart.c \
X	hdrdefs.c hdrcommon.c hdrparse.c hdrmunge.c \
X	history.c io.c msgs.c procart.c \
X	sys.c transmit.c trbatch.c ihave.c $(LIBSRCS)
XOBJ=relaynews.o active.o article.o caches.o mkdirs.o control.o fileart.o \
X	hdrdefs.o hdrcommon.o hdrparse.o hdrmunge.o \
X	history.o io.o msgs.o procart.o \
X	sys.o transmit.o trbatch.o ihave.o $(LIBOBJS)
XFILES=$(NONCFILES) $(CFILES)
XNONCFILES= TODO* README ads/README ads/[0-9]* \
X	sh/inews sh/tear sh/anne.jones sh/defhdrs.awk \
X	sh/realrnews sh/serverrnews makefile
XCFILES= ../include/*.h \
X	active.h article.h caches.h mkdirs.h control.h cpu.h fileart.h \
X	hdrint.h headers.h history.h system.h transmit.h trbatch.h $(SRC)
X
Xall: makefile relaynews
X
Xmkfile: makefile
X	sed '/mkfile/d' makefile | mkconv | sed 's/make/mk/g' >$@
X
Xrelaynews: $(OBJ)
X	$(CC) $(CFLAGS) $(OBJ) $(LIBS) $(LIBOBJS) -o $@
Xlint: $(SRC)
X	$(LINT) $(LINTFLAGS) $(SRC) $(LLIBS) | $(LINTFILT)
Xlint-p: $(SRC)
X	$(LINT) $(LINTFLAGS) -p $(SRC) $(LLIBS) | $(LINTFILT)
X
Xnewsinstall:
X	: nothing
X
X# bininstall: make directories, install programs
Xbininstall: install
Xinstall: $(NEWSBIN)/relay/relaynews
X$(NEWSBIN)/relay/relaynews: relaynews
X	-mkdir $(NEWSBIN)/relay $(NEWSBIN)/inject $(NEWSBIN)/ctl
X	rm -f $(NEWSBIN)/relay/relaynews
X	cp relaynews $(NEWSBIN)/relay
X	: needs to be news-owned, setuid -- build looks after that
X	chmod +x sh/* aux/* ctl/*
X	cp sh/* $(NEWSBIN)/inject
X	cp ctl/* $(NEWSBIN)/ctl
X	cp aux/* $(NEWSBIN)/relay
X	cp sh/postnews sh/inews $(BIN)
X
XTODO.grep: TODO
X	-egrep TODO ../include/*.h *.h *.c sh/* | tr -s " \11" " " >$@
X	-egrep TODO ../lib*/*.[ch] | tr -s " \11" " " >>$@
X
Xv7 v8 v9 usg bsd42:
X	test -d libos && exit 1
X	mv lib$@ libos # or ln -s lib$@ libos
X	make
X
Xprint: printc printnonc
X	touch $@
Xprintc: $(CFILES)
X	$(PP) $? | $(PPBACK)
X	touch $@
Xprintnonc: $(NONCFILES)
X	pr $(PROPTS) $? | $P
X	touch $@
Xdistr: $(FILES)
X	(echo relaynews update of `date`; echo ""; bundle $?) | /bin/mail cnews-updates
X	touch $@
Xclean:
X	rm -f core a.out relaynews *.o	
X	rm -rf regress/tmp
X
Xr:	relaynews
X	chmod +x regress/regress
X	cd regress; ./regress
X	
X# header dependencies follow
Xactive.o: ../include/libc.h ../include/news.h ../include/config.h
Xactive.o: active.h
Xarticle.o: ../include/news.h article.h headers.h
Xcaches.o: ../include/news.h active.h caches.h transmit.h
Xmkdirs.o: ../include/libc.h ../include/news.h
Xcontrol.o: ../include/libc.h ../include/news.h ../include/config.h
Xcontrol.o: headers.h article.h caches.h history.h
Xfileart.o: ../include/libc.h ../include/news.h ../include/config.h
Xfileart.o: active.h mkdirs.h headers.h article.h history.h system.h
Xhdrcommon.o: ../include/news.h headers.h hdrint.h
Xhdrdefs.o: ../include/news.h headers.h hdrint.h
Xhdrmunge.o: ../include/libc.h ../include/news.h fileart.h headers.h
Xhdrmunge.o: article.h hdrint.h
Xhdrparse.o: ../include/libc.h ../include/news.h headers.h hdrint.h
Xhistory.o: ../include/libc.h ../include/news.h ../include/config.h
Xhistory.o: ../include/fgetmfs.h headers.h article.h history.h
Xhostname.o: ../include/news.h ../include/config.h
Xihave.o: ../include/libc.h ../include/news.h ../include/config.h
Xihave.o: headers.h article.h caches.h history.h
Xio.o: ../include/news.h headers.h article.h
Xmsgs.o: ../include/news.h headers.h article.h
Xprocart.o: ../include/libc.h ../include/news.h active.h control.h
Xprocart.o: headers.h article.h history.h system.h
Xrelaynews.o: ../include/libc.h ../include/news.h ../include/config.h
Xrelaynews.o: ../include/fgetmfs.h active.h caches.h cpu.h headers.h
Xrelaynews.o: history.h
Xstring.o: ../include/libc.h ../include/news.h
Xsys.o: ../include/libc.h ../include/fgetmfs.h ../include/news.h
Xsys.o: ../include/config.h system.h
Xtransmit.o: ../include/libc.h ../include/news.h ../include/config.h
Xtransmit.o: headers.h active.h article.h system.h trbatch.h transmit.h
Xtrbatch.o: ../include/libc.h ../include/news.h trbatch.h
!
echo 'relay/mkdirs.c':
sed 's/^X//' >'relay/mkdirs.c' <<'!'
X/*
X * mkdirs - make the directories implied by `name'
X */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include "libc.h"
X#include "news.h"
X
X/*
X * Given a/b/c/d, try to make any of a, a/b, a/b/c and a/b/c/d which are missing;
X * stop on first failure.
X * Returns success.
X */
Xboolean
Xmkdirs(name, uid, gid)
Xregister char *name;
Xint uid, gid;
X{
X	register char *cp;
X	register int isthere = YES;
X	struct stat stbuf;
X
X	for (cp = name; isthere && *cp != '\0'; cp++)
X		if (*cp == FNDELIM) {
X			*cp = '\0';
X			isthere = stat(name, &stbuf) >= 0;
X			if (!isthere) {
X				isthere = mkdir(name, 0777) >= 0;
X				(void) chown(name, uid, gid);
X			}
X			*cp = FNDELIM;
X		}
X	return isthere;
X}
!
echo 'relay/mkdirs.h':
sed 's/^X//' >'relay/mkdirs.h' <<'!'
X/* imports from mkdirs.c */
Xextern boolean mkdirs();
!
echo 'relay/mklint':
sed 's/^X//' >'relay/mklint' <<'!'
X#! /bin/sh
Xlint -hazu -I../include -I../include/bsd42 -I../rnews -DSTATIC= $* -llocal |
X	egrep -v '(possible pointer|long assign|can.t take|never used|:$)'
!
echo 'relay/msgs.c':
sed 's/^X//' >'relay/msgs.c' <<'!'
X/*
X * print common messages
X */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include "news.h"
X#include "headers.h"
X#include "article.h"
X#include "msgs.h"
X
Xvoid
Xfulldisk(art, file)		/* complain once & set ST_DISKFULL */
Xregister struct article *art;
Xchar *file;
X{
X	if (!(art->a_status&ST_DISKFULL))
X		art->a_status |= prfulldisk(file);
X}
X
Xstatust
Xprfulldisk(file)		/* complain once & return bad status */
Xchar *file;
X{
X	warning("error writing `%s', probably the disk filled", file);
X	return ST_DISKFULL|ST_DROPPED;
X}
!
echo 'relay/msgs.h':
sed 's/^X//' >'relay/msgs.h' <<'!'
X/* imports from msgs.c */
Xextern statust prfulldisk();
Xextern void fulldisk();
!
echo 'relay/procart.c':
sed 's/^X//' >'relay/procart.c' <<'!'
X/*
X * process a single incoming article
X */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include "libc.h"
X#include "news.h"
X#include "active.h"
X#include "control.h"
X#include "headers.h"
X#include "article.h"
X#include "history.h"
X#include "io.h"
X#include "msgs.h"
X#include "system.h"
X#include "transmit.h"
X
X/*
X * COPYSIZE is the length of a bulk-copying buffer: the bigger the better,
X * though fewer than 3% of articles exceed 8192 bytes (may 1988).
X * It holds header lines first, and later holds bytes of the body.
X * This buffer is allocated once at the start and never deallocated.
X */
X#ifndef COPYSIZE
X#ifdef SMALLMEM
X#define COPYSIZE BUFSIZ		/* conserve memory at the expense of speed */
X#else
X#define COPYSIZE 8192		/* big enough even for worst-case 4.2bsd blocks */
X#endif				/* SMALLMEM */
X#endif				/* COPYSIZE */
X
Xextern char *exclude;		/* for erik */
Xextern boolean okrefusal;	/* flag from command line */
X
X/* forwards */
Xextern void tossorfile(), surveydamage(), reject(), prefuse(), uninsart();
Xextern char *hdrcopy();
XFORWARD void copyart(), cpybody(), insart();
XFORWARD statust snuffmayreturn();
X
X/*
X * Copy the article on "in" to a temporary name in the news spool directory,
X * unlink temp name; *or* copy into the final names, if known early enough.
X * (Sets a_tmpf in or near hdrmunge() or hdrdump().)
X * If the spool file opened, install the article it contains.
X */
Xstatust
Xcpinsart(in, inname, maxima, blvmax)
XFILE *in;
Xregister char *inname;
Xlong maxima;
Xboolean blvmax;				/* believe maxima? */
X{
X	register struct article *artp;
X	register statust status;
X	struct article art;
X
X	artp = &art;
X	artinit(artp);
X	artp->a_blvmax = blvmax;
X	artp->a_unread = maxima;
X
X	/*
X	 * copyart() may reject() the article, and may fill the disk.
X	 * it calls fileart and logs rejected articles.
X	 */
X	copyart(artp, in, inname);
X
X	if (artp->a_status&ST_REFUSED) {
X		/* no good ngs (in fileart) or reject()ed; not serious */
X		artp->a_status &= ~ST_REFUSED;
X		/* paranoia; shouldn't happen */
X		nnfclose(artp, &artp->a_artf, inname);
X	} else if (artp->a_artf == NULL) {
X		warning("can't open spool file `%s'", artp->a_tmpf);
X		artp->a_status |= ST_DROPPED;
X	} else {
X		nnfclose(artp, &artp->a_artf, inname);
X		insart(artp);	/* logs accepted art.s during transmission */
X		if (artp->a_status&ST_JUNKED) {	/* yer welcome, henry */
X			artp->a_status &= ~ST_JUNKED;
X			timestamp(stdout, (time_t *)NULL);
X			(void) printf(" %s j %s junked due to groups `%s'\n",
X				sendersite(nullify(artp->h.h_path)),
X				artp->h.h_msgid, artp->h.h_ngs);
X		}
X	}
X	status = artp->a_status;
X	artfree(artp);
X	return status;
X}
X
X/*
X * Copy the next charcnt bytes of "in" (may be not a disk file)
X * to a permanent file under a (possibly) temporary name.
X * After the headers are seen, accept or reject the article.
X * If rejected and the headers fit in core, no files will be opened.
X * Must munge certain headers on the way & remember certain values.
X * hdrmunge() or hdrdump() sets art->a_tmpf & art->a_artf.
X * Unlink art->a_tmpf, if a temporary link.
X */
X/* ARGSUSED inname */
XSTATIC void
Xcopyart(art, in, inname)
Xregister struct article *art;
Xregister FILE *in;
Xchar *inname;
X{
X	boolean installed = YES;
X	char *body;
X
X	body = hdrcopy(art, in);
X	hdrdeflt(&art->h);
X	tossorfile(art, &installed);
X	/* assertion: header values (art->h) can be forgotten here */
X	cpybody(art, in, body);
X	surveydamage(art, &installed);
X}
X
X/*
X * The loop copies header lines from input to output or a
X * header output cache.  On exit, hdr will contain the first
X * non-header line, if any, left over from the end of header copying.
X *
X * Some people think the loop is ugly; I'm not sure why.
X * If the byte count is positive, read a line; if it doesn't return
X * EOF and is a header, then adjust byte count, stash and munge headers.
X * strlen(line) must be computed before hdrstash is called,
X * as hdrstash (and thus hdrdigest) removes newlines.
X */
Xchar *						/* first body line, from gethdr */
Xhdrcopy(art, in)
Xregister struct article *art;
XFILE *in;
X{
X	register char *hdr = NULL;
X	long limit;
X	int is_hdr = NO;
X
X	hdrwretch();				/* reset the header parser */
X	limit = (art->a_blvmax? art->a_unread+1: art->a_unread); /* 1 for NUL */
X	/* 1 is again for NUL */
X	while (limit > 1 && (hdr = gethdr(in, &limit, &is_hdr)) != NULL && is_hdr) {
X	    	hdrdigest(art, hdr, strlen(hdr));
X		hdr = NULL;			/* freed inside gethdr */
X	}
X	/* If we read a body line, gethdr has adjusted limit appropriately. */
X	art->a_unread = limit - 1;		/* limit updated by gethdr */
X	if (is_hdr)				/* no body: header fills limit */
X		hdr = NULL;
X	return hdr;
X}
X
X/*
X * Either reject the article described by art, or accept it and file it.
X * If rejecting it, remove any links and give back assigned #'s
X * (art->a_artf may still be open; arguably uninsart should close it).
X * If accepting it, dump any saved headers and file the article.
X * Unlink art->a_tmpf if it's a temporary link.
X */
Xvoid
Xtossorfile(art, installedp)
Xregister struct article *art;
Xboolean *installedp;
X{
X	reject(art);				/* duplicate, etc.? */
X	if (art->a_status&(ST_DROPPED|ST_REFUSED)) {
X		uninsart(art);
X		*installedp = NO;
X	} else
X		hdrdump(art, ALLHDRS);		/* ALLHDRS triggers fileart */
X
X	if (art->a_unlink) {
X		/* a_tmpf has had links made to it, so it can be removed. */
X		if (unlink(art->a_tmpf) < 0) {
X			warning("copyart can't unlink `%s'", art->a_tmpf);
X			art->a_status |= ST_ACCESS;
X		}
X		art->a_unlink = NO;		/* caution */
X	}
X}
X
X/*
X * Copy article body.
X * body will contain the first  non-header line, if any,
X * left over from the end of header copying.  Write it.
X * Copy at most COPYSIZE bytes of body at a time and exactly art->a_unread
X * bytes in total, barring EOF or a full disk. Then "block" is no longer needed.
X * Force the article to disk, mostly for the benefit of control message
X * processing.
X *
X * The copying buffer, block, is static because it is used repeatedly
X * and persists through most of execution, so dynamic allocation
X * and deallocation seems wasteful, but also for the benefit
X * of compilers for odd machines (e.g. PE, 370s) which make
X * implementing "large" automatic arrays difficult.
X */
XSTATIC void
Xcpybody(art, in, body)
Xregister struct article *art;
XFILE *in;
Xregister char *body;
X{
X	register int readcnt;
X	static char block[COPYSIZE];
X
X	if (body != NULL) {			/* read too far? */
X		register int bodylen = strlen(body);
X
X		if (art->a_artf != NULL &&
X		    fwrite(body, 1, bodylen, art->a_artf) != bodylen)
X			fulldisk(art, spoolnm(art));
X		art->a_charswritten += bodylen;
X	}
X	for (; art->a_unread > 0 && !(art->a_status&ST_DISKFULL) &&
X	    (readcnt=fread(block, 1, (int)min(art->a_unread, COPYSIZE), in)) > 0;
X	    art->a_unread -= readcnt, art->a_charswritten += readcnt)
X		if (art->a_artf != NULL &&
X		    fwrite(block, 1, readcnt, art->a_artf) != readcnt)
X			fulldisk(art, spoolnm(art));
X	if (art->a_artf != NULL && fflush(art->a_artf) == EOF)
X		fulldisk(art, spoolnm(art));
X}
X
X/*
X * If not yet uninstalled, and the disk filled, uninstall this article
X * to remove any zero-length links and decrement the active article number.
X * The ST_DISKFULL status will prevent a history entry from being generated.
X */
Xvoid
Xsurveydamage(art, installedp)
Xregister struct article *art;
Xregister boolean *installedp;
X{
X	if (art->a_unread > 0 && art->a_blvmax) {
X		(void) fprintf(stderr, "%s: article %s short by %ld bytes\n",
X			progname, (art->h.h_msgid != NULL? art->h.h_msgid: ""),
X			(long)art->a_unread);
X		art->a_status |= ST_SHORT;	/* NB.: don't uninstall this art. */
X	}
X	if (*installedp && art->a_status&ST_DISKFULL) {
X		uninsart(art);
X		*installedp = NO;
X	}
X#ifdef WATCHCORE
X	{
X		char stbot;
X		extern char *sbrk();
X
X		printf("debugging memory use: top of data=%u", (unsigned)sbrk(0));
X		printf(", bottom of stack=%u\n", (unsigned)&stbot);
X	}
X#endif
X}
X
X/*
X * Install the article on art->a_tmpf or art->a_files:
X * The article should have been accepted and filed in copyart().
X * Add history entries for the article.  Log arrival.
X * Transmit the article to our neighbours.
X * Process control mess(age)es.  ctlmsg can call transmit(fakeart,x)
X * and generate log lines for cancels and ihave/sendme.
X */
XSTATIC void
Xinsart(art)
Xregister struct article *art;
X{
X	if (!(art->a_status&(ST_DROPPED|ST_REFUSED|ST_DISKFULL))) {
X		if (!art->a_filed)			/* paranoia */
X			(void) fprintf(stderr, "%s: %s not filed by copyart!\n",
X				progname, art->h.h_msgid);
X		history(art, STARTLOG);
X		transmit(art, exclude);		/* writes systems on stdout */
X		(void) putchar('\n');		/* ends the log line */
X		if (art->h.h_ctlcmd != NULL)
X			ctlmsg(art);
X#ifdef notdef					/* it's only a log file! */
X		(void) fflush(stdout);		/* crash-proofness */
X#endif
X	}
X	art->a_status &= ~ST_REFUSED;	/* refusal is quite casual & common */
X}
X
X/*
X * Reject articles.  This can be arbitrarily picky.
X * Only the headers are used to decide, so this can be called before
X * the article is filed.
X * Be sure to put the fastest tests first, especially if they often result
X * in rejections.
X */
Xvoid
Xreject(art)
Xregister struct article *art;
X{
X	if (art->h.h_path == NULL) {
X		prefuse(art);
X		(void) printf("no Path: header\n");
X	} else if (alreadyseen(art->h.h_msgid)) {
X		prefuse(art);
X		(void) printf("duplicate\n");
X	} else if (art->h.h_path != NULL && hopcount(art->h.h_path) > 0 &&
X	    !ngmatch(oursys()->sy_ngs, art->h.h_ngs)) {
X		extern boolean histreject;
X
X		/*
X		 * non-local article, with all bad groups.
X		 * (local articles with bad groups will be bounced
X		 * by fileart when the groups aren't in active.)
X		 */
X		if (histreject)
X			history(art, NOLOG);
X		prefuse(art);
X		(void) printf("no subscribed groups in `%s'\n", art->h.h_ngs);
X	} else if (art->h.h_approved == NULL && moderated(art->h.h_ngs)) {
X		prefuse(art);
X		(void) printf("unapproved article in moderated group(s) `%s'\n",
X			art->h.h_ngs);
X	} else
X		return;			/* art was accepted */
X	art->a_status |= ST_REFUSED;
X	if (!okrefusal)
X		art->a_status |= ST_DROPPED;
X}
X
X/*
X * print the leader of a refusal message about the article in "art".
X */
Xvoid
Xprefuse(art)
Xregister struct article *art;
X{
X	timestamp(stdout, (time_t *)NULL);
X	(void) printf(" %s - %s ", sendersite(nullify(art->h.h_path)),
X		art->h.h_msgid);
X}
X
X/*
X * "Uninstall" an article: remove art->a_files (permanent names) and
X * a_tmpf (temporary name if a_unlink set), and return assigned article #'s.
X * If a_unlink isn't set, a_tmpf is a copy of the first link in art->a_files.
X * Must be called before history() is called, else there will be a
X * history entry for the article, but no spool files.
X */
Xvoid
Xuninsart(art)
Xregister struct article *art;
X{
X	if (art->a_unlink && art->a_tmpf != NULL) {
X		(void) unlink(art->a_tmpf);	/* I don't wanna know... */
X		art->a_unlink = NO;
X	}
X	/* return article numbers (YES) & ignore unlink errors */
X	(void) snuffmayreturn(art->a_files, YES);
X}
X
Xstatust
Xsnufffiles(filelist)		/* just unlink all files in filelist */
Xchar *filelist;
X{
X	/* don't return article numbers (NO) & return unlink errors */
X	return snuffmayreturn(filelist, NO);
X}
X
X/*
X * Unlink all files in filelist, and optionally return article numbers.
X * When removing a link, note any failure, but don't issue an error message.
X * For one thing, cancel controls fail routinely because the article has been
X * removed manually or never existed (a previous cancel arrived before its
X * subject and generated a fake history entry).
X */
XSTATIC statust
Xsnuffmayreturn(filelist, artret)
Xchar *filelist;
Xboolean artret;		/* return article numbers & note unlink errors? */
X{
X	register statust status = ST_OKAY;
X	register char *arts, *spacep, *slashp, *artnm;
X
X	/* this is a deadly tedious job and I really should automate it */
X	for (arts = filelist; arts != NULL && arts[0] != '\0';
X	     arts = (spacep == NULL? NULL: spacep+1)) {
X		spacep = index(arts, ' ');
X		if (spacep != NULL)
X			spacep[0] = '\0';	/* will be restored below */
X		artnm = strsave(arts);
X		if (spacep != NULL)
X			spacep[0] = ' ';	/* restore space */
X
X		slashp = index(artnm, FNDELIM);
X		if (slashp != NULL)
X			slashp[0] = '\0';	/* will be restored below */
X		if (artret)
X			/* prevartnum will complain on i/o error to active */
X			(void) prevartnum(artnm); /* return assigned # */
X		if (slashp != NULL)
X			slashp[0] = FNDELIM;	/* restore slash */
X
X		mkfilenm(artnm);
X		if (unlink(artnm) < 0)
X			status |= ST_ACCESS;
X		free(artnm);
X	}
X	return status;
X}
!
echo 'relay/relaynews.c':
sed 's/^X//' >'relay/relaynews.c' <<'!'
X/*
X * relaynews - relay Usenet news (version C)
X * See the file COPYRIGHT for the copyright notice.
X *
X * relaynews should be setuid-news, setgid-news.  You'll need to install
X * setnewsids setuid-root if setuid(geteuid()) doesn't work on your
X * machine (e.g. on V7 and possibly SystemIII).
X *
X * Written by Geoff Collyer, 15-20 November 1985 and revised periodically
X * since.
X *
X * relaynews parses article headers, rejects articles by newsgroup &
X * message-id, files articles, updates the active & history files,
X * transmits articles, and honours (infrequent) control messages, which do
X * all sorts of varied and rococo things.  Control messages are implemented
X * by separate programs.  relaynews reads a "sys" file to control the
X * transmission of articles but can function as a promiscuous leaf node
X * without one.  See ARPA Internet RFC 1036 nee 850 for the whole story.
X *
X * A truly radical notion: people may over-ride via environment variables
X * the compiled-in default directories so IHCC kludges are not needed and
X * testing is possible (and encouraged) in alternate directories.  This
X * does cause a loss of privilege, to avoid spoofing.
X *
X * The disused old unbatched ihave/sendme protocol is gone because it was
X * too wasteful; use the batched form instead (see the ihave sys flag
X * ("I") instead).
X *
X * Portability vs SystemV.  relaynews uses dbm(3) and makes no apologies
X * for so doing.  Imitation UNIX (registered trademark of AT&T in the
X * United States) brand operating systems that lack dbm are going to
X * have to use my incredibly slow dbm simulation, or another.
X */
X
X#include <stdio.h>
X#include <ctype.h>
X#include <signal.h>		/* to make locking safe */
X#include <sys/types.h>
X
X#include "libc.h"
X#include "news.h"
X#include "config.h"
X#include "fgetmfs.h"
X#include "active.h"
X#include "caches.h"
X#include "cpu.h"
X#include "fileart.h"
X#include "headers.h"
X#include "history.h"
X#include "transmit.h"
X
X/*
X * setuid-root program to set ids to news/news & rexec rnews with
X * NEWSPERMS in the environment to break loops.
X */
X#ifndef SETNEWSIDS
X#define SETNEWSIDS "setnewsids"
X#endif
X
X/* exports */
Xchar *progname;
Xboolean okrefusal = YES;			/* okay to refuse articles? */
Xchar *exclude = NULL;				/* site to exclude, for erik */
Xboolean histreject = NO;			/* keep history of rejects? */
X
X/* internal */
Xstatic boolean userealids = NO;
X
X/* imports */
Xextern int optind;			/* set by getopt */
Xextern char *optarg;
Xextern statust cpinsart();		/* from procart.c */
X
X/* forwards */
Xextern void prelude(), setids(), procopts(), redirectlogs(), logfile();
Xextern void getwdandcd();
Xextern statust procargs(), relnmprocess(), process(), unbatch();
Xextern boolean batchln();
XFORWARD boolean debugon();
X
X/*
X * main - take setuid precautions, switch to "news" ids, ignore signals,
X * handle options, lock news system, process files & unlock news system.
X */
Xint
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X	statust status = ST_OKAY;
X	int redirlogs = 0;		/* redirect n std output streams to logs */
X	char *origdir = NULL;		/* current directory at start */
X
X	progname = argv[0];
X#ifdef CSRIMALLOC
X	mal_debug(0);	/* was 2; 3 is too slow */
X	mal_leaktrace(0);	/* was 1 */
X#endif
X	prelude(argv);		/* various precautions; switch to "news" */
X
X	/* ignore signals (for locking). relaynews runs quickly, so don't worry. */
X	(void) signal(SIGINT, (sigarg_t)SIG_IGN);
X	(void) signal(SIGQUIT, (sigarg_t)SIG_IGN);
X	(void) signal(SIGHUP, (sigarg_t)SIG_IGN);
X	(void) signal(SIGTERM, (sigarg_t)SIG_IGN);
X
X	procopts(argc, argv, &redirlogs, &okrefusal);
X
X	newslock();			/* done here due to dbm internal cacheing */
X	if (redirlogs > 0) {
X		redirectlogs(redirlogs); /* redirect std output streams to logs */
X#ifdef MANYERRORS
X		(void) putc('\n', stderr);	/* leave a blank line */
X		/* prints "Jun  5 12:34:56" */
X		timestamp(stderr, (time_t *)NULL);
X		(void) putc('\n', stderr);
X#endif
X	}
X
X	getwdandcd(argc, argv, &origdir);
X	status |= procargs(argc, argv, &origdir);
X
X	status |= synccaches();		/* being cautious: write & close caches */
X	(void) fflush(stdout);		/* log file */
X	(void) fflush(stderr);		/* errlog file */
X
X#ifdef notdef
X#ifdef CSRIMALLOC
X	mal_dumpleaktrace(fileno(stderr));
X#endif
X#endif
X	newsunlock();
X	exit(status);
X	/* NOTREACHED */
X}
X
X/*
X * reset various environmental things for safety: umask, alarm,
X * environment variables (PATH, IFS), standard file descriptors,
X * user & group ids.
X */
Xvoid
Xprelude(argv)				/* setuid daemon prelude */
Xchar **argv;
X{
X	register char *newpath;
X
X	(void) umask(2);		/* undo silly umasks, ignore newsumask() */
X	(void) alarm(0);		/* cancel any pending alarm */
X	newpath = malloc(STRLEN("PATH=") + strlen(newspath()) + 1);
X	if (newpath == NULL)
X		exit(1);		/* no chatter until stdfdopen */
X	(void) strcpy(newpath, "PATH=");
X	(void) strcat(newpath, newspath());
X	if (putenv(newpath) ||
X	    putenv("IFS= \t\n"))
X		exit(1);		/* no chatter until stdfdopen */
X	closeall(1);			/* closes all but std descriptors */
X	stdfdopen();			/* ensure standard descriptors are open */
X	setids(argv);			/* change of real and effective ids */
X}
X
X/*
X * change real and effective ids to real ids if unprivileged() is called,
X * else to effective ("news") ids.  ctlfile((char *)0) will trigger a call
X * to unprivileged() if any environment variables override the default
X * path names.  unprivileged() in turn sets userealids.
X *
X * If setuid(geteuid()) fails, try execing a small, setuid-root program
X * to just do "getpwnam(), getgrnam() (with NEWSPERMS set), setgid(),
X * setuid()," and exec this program again.  If NEWSPERMS is set,
X * the failure is a fatal error (recursive loop).
X * This program (relaynews) can be setuid-news.
X *
X * The peculiar tests for failure (getuid() != newsuid) are to work
X * around a Xenix bug which returns 0 from setuid() upon failure.
X */
Xvoid
Xsetids(argv)
Xchar **argv;
X{
X	int newsuid, newsgid;
X
X	(void) ctlfile((char *)NULL);
X	if (userealids)
X		newsuid = getuid(), newsgid = getgid();
X	else
X		newsuid = geteuid(), newsgid = getegid();
X	if (setgid(newsgid) < 0 || setuid(newsuid) < 0 ||
X	    getgid() != newsgid || getuid() != newsuid) {
X		if (getenv("NEWSPERMS") != 0)
X			error("recursive loop setting ids", "");
X		execv(ctlfile(SETNEWSIDS), argv);
X		error("can't exec `%s' to set ids", ctlfile(SETNEWSIDS));
X		/* NOTREACHED */
X	}
X	/* we are now running as news, so you can all relax */
X}
X
X/*
X * parse options and set flags
X */
Xvoid
Xprocopts(argc, argv, redirlogsp, okrefusalp)
Xint argc;
Xchar **argv;
Xint *redirlogsp;
Xboolean *okrefusalp;
X{
X	int c, errflg = 0;
X
X	while ((c = getopt(argc, argv, "d:inrsx:")) != EOF)
X		switch (c) {
X		case 'd':		/* -d debug-options; thanks, henry */
X			if (!debugon(optarg))
X				errflg++;	/* debugon has complained */
X			break;
X		case 'i':		/* redirect stdout to log (inews) */
X			*redirlogsp = 1; /* just stdout */
X			break;
X		case 'n':		/* nntp mode: keep history of rejects */
X			histreject = YES;
X			break;
X		case 'r':		/* redirect std. ostreams to logs (rnews) */
X			*redirlogsp = 2; /* stdout & stderr */
X			break;
X		case 's':		/* dropping input is serious (inews) */
X			*okrefusalp = NO;
X			break;
X		case 'x':		/* -x site: don't send to site */
X			/* you're welcome, erik */
X			/* erik says he only needs one -x per inews */
X			if (exclude != NULL) {
X				(void) fprintf(stderr,
X					"%s: more than one -x site (%s)\n",
X					progname, optarg);
X				errflg++;
X			} else
X				exclude = optarg;
X			break;
X		default:
X			errflg++;
X			break;
X		}
X	if (errflg) {
X		(void) fprintf(stderr, "usage: %s [-inrs][-d fhlmt][-x site]\n",
X			progname);
X		exit(2);
X	}
X}
X
Xvoid
Xunprivileged()		/* called if NEWSARTS, NEWSCTL or NEWSBIN present */
X{
X	userealids = YES;
X}
X
XSTATIC boolean
Xdebugon(dbopt)
Xregister char *dbopt;
X{
X	statust status = YES;
X
X	for (; *dbopt != '\0'; dbopt++)
X		switch (*dbopt) {
X		case 'f':
X			filedebug(YES);
X			break;
X		case 'h':
X			hdrdebug(YES);
X			break;
X		case 'l':
X			lockdebug(YES);
X			break;
X		case 'm':
X			matchdebug(YES);
X			break;
X		case 't':
X			transdebug(YES);
X			break;
X		default:
X			status = NO;	/* unknown debugging option */
X			(void) fprintf(stderr, "%s: bad -d %c\n",
X				progname, *dbopt);
X			break;
X		}
X	return status;
X}
X
X/*
X * Redirect stdout or stderr into log files at known locations.
X */
Xvoid
Xredirectlogs(count)
Xint count;
X{
X	if (count > 0)
X		logfile(stdout, ctlfile("log"));
X	if (count > 1)
X		logfile(stderr, ctlfile("errlog"));
X}
X
Xvoid
Xlogfile(stream, name)			/* redirect stream into name */
XFILE *stream;
Xchar *name;
X{
X	if (freopen(name, "a", stream) == NULL)
X		errunlock("can't redirect standard stream to `%s'", name);
X}
X
X/*
X * if argv contains relative file name arguments, save current directory name
X * in malloced memory, through origdirp.
X * then change directory to the spool directory ($NEWSARTS).
X */
Xvoid
Xgetwdandcd(argc, argv, origdirp)
Xint argc;
Xchar **argv;
Xchar **origdirp;
X{
X	register int argind;
X	boolean needpwd = NO;
X	char dirtmp[MAXPATH];			/* much bigger than needed */
X
X	for (argind = optind; argind < argc; argind++)
X		if (argv[argind][0] != FNDELIM)
X			needpwd = YES;
X
X	*origdirp = "/???";			/* pessimism */
X	if (needpwd && getcwd(dirtmp, sizeof dirtmp) != 0)
X		*origdirp = dirtmp;
X	*origdirp = strsave(*origdirp);		/* save a smaller copy */
X	cd(fullartfile((char *)NULL));		/* move to spool directory */
X}
X
X/*
X * process files named as arguments (or implied)
X */
Xstatust
Xprocargs(argc, argv, origdirp)
Xint argc;
Xchar **argv;
Xchar **origdirp;
X{
X	register statust status = ST_OKAY;
X
X	if (optind == argc)
X		status |= process(stdin, "stdin");
X	else
X		for (; optind < argc; optind++)
X			status |= relnmprocess(argv[optind], *origdirp);
X	nnfree(origdirp);
X	return status;
X}
X
Xstatust
Xrelnmprocess(name, origdir)		/* process a (relative) file name */
Xchar *name, *origdir;
X{
X	register statust status = ST_OKAY;
X	register FILE *in;
X	register char *fullname;
X
X	fullname = nemalloc((unsigned)strlen(origdir) + STRLEN(SFNDELIM) +
X		strlen(name) + 1);
X	fullname[0] = '\0';
X
X	if (name[0] != FNDELIM) {	/* relative path */
X		(void) strcat(fullname, origdir);
X		(void) strcat(fullname, SFNDELIM);
X	}
X	(void) strcat(fullname, name);
X
X	in = fopenwclex(fullname, "r");
X	if (in != NULL) {
X		status |= process(in, fullname);
X		(void) nfclose(in);
X	}
X	free(fullname);
X	return status;
X}
X
X/*
X * process - process input file
X * If it starts with '#', assume it's a batch and unravel it,
X * else it's a single article, so just inject it.
X */
Xstatust
Xprocess(in, inname)
XFILE *in;
Xchar *inname;
X{
X	register int c;
X
X	if ((c = getc(in)) == EOF)
X		return ST_OKAY; 		/* normal EOF */
X	(void) ungetc(c, in);
X	if (c == '#')
X		return unbatch(in, inname);
X	else
X		return cpinsart(in, inname, MAXLONG, NO);
X}
X
X/*
X * Unwind "in" and insert each article.
X * For each article, call cpinsart to copy the article from "in" into
X * a (temporary) file in the news spool directory and rename the temp file
X * to the correct final name if it isn't right already.
X *
X * If the unbatcher gets out of sync with the input batch, the unbatcher
X * will print and discard each input line until it gets back in sync.
X */
Xstatust
Xunbatch(in, inname)
Xregister FILE *in;
Xchar *inname;
X{
X	register int c;
X	/* register */ char *line;
X	register statust status = ST_OKAY;
X	long charcnt;
X
X	while (!(status&ST_DISKFULL) && (c = getc(in)) != EOF) {
X		(void) ungetc(c, in);
X		while ((line = fgetms(in)) != NULL &&
X		    !batchln(line, &charcnt)) {		/* returns charcnt */
X			status |= ST_DROPPED;
X			(void) fprintf(stderr,
X			    "%s: unbatcher out of synch, tossing: ",
X				progname);
X		    	(void) fputs(line, stderr);
X			free(line);
X		}
X		nnfree(&line);			/* free "#! rnews n" */
X		if (!feof(in))
X			status |= cpinsart(in, inname, charcnt, YES);
X	}
X	if (ferror(in))
X		errunlock("error reading `%s'", inname);
X	return status;
X}
X
X/*
X * Is line a batcher-produced line (#! rnews count)?
X * If so, return the count through charcntp.
X * This is slightly less convenient than sscanf, but a lot smaller.
X */
Xboolean
Xbatchln(line, charcntp)
Xregister char *line;
Xregister long *charcntp;
X{
X	register char *countp;
X	static char batchtext[] = "#! rnews ";
X
X	countp = line + STRLEN(batchtext);
X	if (STREQN(line, batchtext, STRLEN(batchtext)) &&
X	    isascii(*countp) && isdigit(*countp)) {
X		*charcntp = atol(countp);
X		return YES;
X	} else {
X		*charcntp = 0;
X		return NO;
X	}
X}
!
echo 'relay/sh/anne.jones':
sed 's/^X//' >'relay/sh/anne.jones' <<'!'
X#! /bin/sh
X# anne.jones [file...] - censor headers: munge locally-generated headers in
X#  files, enforce feeble attempts at Usenet security, generate lots of silly
X#  headers.
X# (after the notorious ring-leader of the Ontario Film and Video Review Board
X# (nee Ontario Board of Censors), Ontario's very own Mrs. Mary Whitehouse.)
X# =()<. ${NEWSCONFIG-@<NEWSCONFIG>@}>()=
X. ${NEWSCONFIG-/usr/lib/news/bin/config}
Xexport NEWSCTL NEWSBIN NEWSARTS
XPATH=$NEWSCTL/bin:$NEWSBIN/inject:$NEWSBIN:$NEWSPATH ; export PATH
Xumask $NEWSUMASK
X
X# pass 0 - dredge up defaults
Xcase "$trversion" in
Xv[67])	;;
X*)	echo "$0: trversion is nonsense or missing from environment" >&2
X	exit 1 ;;
Xesac
Xif test -r $NEWSCTL/mailname; then
X	mailname="`tr -d ' \11' <$NEWSCTL/mailname`"
Xelse
X	mailname="`newshostname`"
X	case "$mailname" in
X	*.*)	;;			# not a uucp host name
X	*)	mailname="$mailname.uucp" ;;	# probably a uucp host name
X	esac
Xfi
X# badsites="pucc.bitnet!"		# tailor, syntax is "host1!host2!...host3!"
Xhost="$mailname"
X
X# dig up user's name (a simple task, you'd think, but you'd be wrong)
Xcase "$LOGNAME" in
X"")
X	# "who am i" on many Unixes does "ttyname(0)" and "getpwuid(getuid())"
X	# if that fails - it can be confused by empty utmp entries (per jerqs);
X	# "who am i </dev/null" yields your userid, not your login name.
X	# "tty" does "ttyname(0)"; also fallible.
X	# So, emulate a slightly-modified V7 getlogin(3) (actually ttyslot(3)):
X	# look for tty on /dev/tty, stdin, stdout, stderr (actually via ttyname(3)).
X	for fd in 3 0 1 2			# 3 is /dev/tty on V8
X	do
X		if test -t $fd; then
X			case "$USER" in
X			"")	USER="`who am i <&$fd |
X				    sed -e 's/[	 ].*//' -e '/!/s/^.*!//' `" ;;
X			esac
X		fi
X	done
X	case "$USER" in
X	"")	USER="`who am i </dev/null |	# last resort: use userid
X			sed -e 's/[	 ].*//' -e '/!/s/^.*!//' `" ;;
X	esac
X	;;
X*)	USER="$LOGNAME" ;;
Xesac
Xcase "$NAME" in
X"")
X	if test -s $HOME/.name; then
X		NAME=`cat $HOME/.name`
X	else
X		NAME=`(grep "^$USER:" /etc/passwd || ypmatch "$USER" passwd) |
X			sed 's/^[^:]*:[^:]*:[^:]*:[^:]*:\([^,:]*\).*$/\1/'  `
X		# tailor: for BTL RJE format, add
X		#	| sed -e 's/^[^-]*- *//' -e 's/ *(.*$//'
X		# otherwise for Berkeley format, use this
X		# (courtesy Rayan Zachariassen):
X		case "$NAME" in
X		*'&'*)
X			# generate Capitalised login name
X			NM=`echo "$USER" | sed -e 's/^\(.\)\(.*\)/\1:\2/'`
X			NM1=`expr "$NM" : '\(.\):.*' |
X				case "$trversion" in
X				v7)	tr a-z A-Z ;;
X				v6)	tr '[a-z]' '[A-Z]' ;;
X				esac
X				`
X			NMR=`expr "$NM" : '.:\(.*\)'`
X			CAPNM="$NM1$NMR"
X			# turn & into Capitalised login name
X			NAME=`echo "$NAME" | sed "s:&:$CAPNM:"`
X			;;
X		esac
X	fi
X	;;
Xesac
Xcase "$NAME" in
X"")	fullname="" ;;		# no full name, leave it off
X*)	fullname=" ($NAME)" ;;
Xesac
Xreallyfrom="$USER@$host$fullname"
XFROM="$reallyfrom"
X
X# generate a few defaults.
X# RFC 1036 requests a GMT Date:, despite it being hard to read.
X# Compensate for V6 Uglix date (no -u) tarted up with all that TZ goo.
Xdate="`
X	set ''\`TZ=GMT0 date\`	# give TZ to see if (Uglix) date responds
X	case \"$5\" in
X	GMT)	echo $* ;;	# Uglix date or V7 date with GMT local time
X	*)	date -u ;;	# must be V7 date command, it ignored TZ
X	esac
X`"				# for defdate, defmsgid
Xcase "$ORGANIZATION" in
X"")	deforg="`sed 1q $NEWSCTL/organi[sz]ation`" ;;	# look in a file
X*)	deforg="$ORGANIZATION" ;;	# look in environment
Xesac
X
X# give defaults and headers to awk
Xcat $* |
X	# strip invisible chars, a la B news; turn tabs to spaces (RFC1036)
X	case "$trversion" in
X	v7)	tr -d '\1-\7\13\14\16-\37';;
X	v6)	tr -d '[\1-\7]\13\14[\16-\37]' ;;
X	esac |
X	sed 's/:	/: /' |
X	awk -f $NEWSBIN/inject/defhdrs.awk \
Xdefpath="$badsites$USER" \
Xdeffrom="$FROM" deforg="$deforg" \
Xdefdate="` set $date; echo $1, $3 $2 \` echo $6 | sed 's/^..//' \` $4 $5`" \
Xdefmsgid="`set $date; echo \<$6$2$3.\`  echo $4 | tr -d : \`.$$@$host\>`" -
!
echo 'relay/sh/ctlrun':
sed 's/^X//' >'relay/sh/ctlrun' <<'!'
X#! /bin/sh
X# ctlrun - run the control messages in control again
X# =()<. ${NEWSCONFIG-@<NEWSCONFIG>@}>()=
X. ${NEWSCONFIG-/usr/lib/news/bin/config}
X# export NEWSCTL NEWSBIN NEWSARTS
XPATH=$NEWSCTL/bin:$NEWSBIN/ctl:$NEWSBIN:$NEWSPATH ; export PATH
Xumask $NEWSUMASK
X
Xcd $NEWSCTL
Xnewslock sys LOCK || exit 1	# lock
X
Xcd $NEWSARTS/control
X
Xfor file in *
Xdo
X	grep '^Control:' $file |
X		sed 's;^Control:[	 ]*;'$NEWSBIN/ctl/';' |
X		grep -v '/cancel ' >/tmp/ctl$$	# cancel needs dbm(3)
X	sh -x /tmp/ctl$$ <$file
Xdone
X
Xrm -f /tmp/ctl$$	
Xrm -f LOCK
!
echo 'relay/sh/defhdrs.awk':
sed 's/^X//' >'relay/sh/defhdrs.awk' <<'!'
X# defhdrs.awk
X# pass 1 - note presence | absence of certain headers
X# a header keyword: remember it and its value
X/^[^\t ]*:/ {
X	hdrval[$1] = $0
X	keyword=$1
X	next
X}
X# a continuation: concatenate this line to the value
X	{ hdrval[keyword] = hdrval[keyword] "\n" $0 }
X
XEND {
X	# pass 2 - cogitate & omit & emit headers
X	emptyhdrre = "^[^\t ]*:[\t ]*$"
X	subjname = "Subject:"
X	ctlname = "Control:"
X	ngname = "Newsgroups:"
X	msgidname = "Message-ID:"
X	typoname =  "Message-Id:"
X	pathname = "Path:"
X	datename = "Date:"
X	fromname = "From:"
X	orgname = "Organization:"
X	distrname = "Distribution:"
X	sendername = "Sender:"
X
X	# fill in missing headers
X	if (hdrval[typoname] != "") {	# spelling hack
X		hdrval[msgidname] = hdrval[typoname]
X		hdrval[typoname] = ""
X		# fix spelling: Message-Id: -> Message-ID:
X		nf = split(hdrval[msgidname], fields);	# bust up
X		fields[1] = msgidname;		# fix spelling
X		hdrval[msgidname] = fields[1];	# reassemble...
X		for (i = 2; i <= nf; i++)
X			hdrval[msgidname] = hdrval[msgidname] " " fields[i]
X	}
X	if (hdrval[msgidname] == "")
X		hdrval[msgidname] = msgidname " " defmsgid
X	if (hdrval[orgname] == "")
X		hdrval[orgname] = orgname " " deforg
X
X	# replace users headers (if any)
X	hdrval[datename] = datename " " defdate
X	hdrval[pathname] = pathname " " defpath
X	if (hdrval[fromname] == "")
X		hdrval[fromname] = fromname " " deffrom
X	else if (hdrval[sendername] == "")
X		hdrval[sendername] = sendername " " deffrom
X
X	# snuff some headers
X	distworld = distrname " world"
X	if (hdrval[distrname] == distworld)
X		hdrval[distrname] = ""
X
X	# the vile cmsg hack, for the sake of the news readers *only*
X	if (hdrval[ctlname] == "" && \
X	    substr(hdrval[subjname], 1, 14) == "Subject: cmsg ")
X		hdrval[ctlname] = ctlname " " substr(hdrval[subjname], 15)
X
X	# warn if no Newsgroups:
X	if (hdrval[ngname] == "")
X		print "no newsgroups header!" | "cat >&2"
X
X	# field the all.all.ctl hack, for the sake of the backward only:
X	# clone Subject: to make Control:
X	if (hdrval[ctlname] == "" && hdrval[ngname] ~ /\.ctl(,|$)/)
X		hdrval[ctlname] = ctlname " " substr(hdrval[subjname], 8)
X
X	# reorder & emit headers
X
X	# favour Control: & Newsgroups: for future benefit of rnews
X	if (hdrval[ctlname] != "") {
X		print hdrval[ctlname]
X		hdrval[ctlname] = ""	# no Control: to print now
X	}
X	if (hdrval[ngname] != "") {
X		print hdrval[ngname]
X		hdrval[ngname] = ""	# no Newsgroups: to print now
X	}
X
X	# B inews kludgery: print Path: before From: to avoid confusing it
X	if (hdrval[pathname] != "") {
X		print hdrval[pathname]
X		hdrval[pathname] = ""	# no Path: to print now
X	}
X	if (hdrval[fromname] != "") {
X		print hdrval[fromname]
X		hdrval[fromname] = ""	# no From: to print now
X	}
X
X	# have pity on readers: put Subject: next
X	if (hdrval[subjname] != "") {
X		print hdrval[subjname]
X		hdrval[subjname] = ""	# no Subject: to print now
X	}
X
X	# print misc. non-empty headers in random order
X	for (i in hdrval)
X		if (hdrval[i] != "" && hdrval[i] !~ /^[^\t ]*:[\t ]*$/)
X			print hdrval[i]
X}
!
echo 'relay/sh/inews':
sed 's/^X//' >'relay/sh/inews' <<'!'
X#! /bin/sh
X# inews [-p] [-debug k] [-x site] [-hMD] [-t subj] [-n ng] [-e exp] [-F ref] \
X#  [-d dist] [-a mod] [-f from] [-o org] [-C ng] [file...] - inject news:
X#	censor locally-posted article and field the "inews -C" kludge;
X#	munge the articles, enforce feeble attempts at Usenet security,
X#	generate lots of silly headers.
X#
X# Yes, it's big, slow and awkward.  The alternative is casting a lot of
X# local policy in C.
X
X# =()<. ${NEWSCONFIG-@<NEWSCONFIG>@}>()=
X. ${NEWSCONFIG-/usr/lib/news/bin/config}
Xexport NEWSCTL NEWSBIN NEWSARTS NEWSPATH NEWSUMASK NEWSMASTER NEWSCONFIG
XPATH=$NEWSCTL/bin:$NEWSBIN/inject:$NEWSBIN/relay:$NEWSBIN:$NEWSPATH; export PATH
XPASSEDFROM='';	export PASSEDFROM	# passed to anne.jones in environ.
X
Xdebug=''			# flags
Xexclusion=''
Xhdrspresent=no
Xautopost=no
Xwaitcmd=''
Xrelayopts=-i			# redirect stdout to log
X
Xwhoami=/tmp/in$$who		# just created to determine effective uid
Xinput=/tmp/in$$in		# uncensored input
Xinhdrs=/tmp/in$$hdr		# generated by tear: headers
Xinbody=/tmp/in$$body		# generated by tear: body
Xcensart=/tmp/in$$cens		# censored input
Xnglist=/tmp/in$$ngs		# newsgroups: list
Xmodroute=/tmp/in$$route		# route to moderator's forwarder
Xexitflag=/tmp/in$$exit		# exit status, if present
Xoutfile=/tmp/in$$out		# relaynews stdout
Xgrpok=/tmp/in$$grp		# flag file: groups okay if present
Xrmlist="$inhdrs $inbody $input $censart $nglist $modroute $exitflag $outfile $grpok"
X
Xumask $NEWSUMASK
X
X# "inews -p": invoke rnews
Xcase "$1" in
X-p)
X	shift
X	exec rnews $*		# rnews, bailing out at or near line 1
X	;;
Xesac
X
X# process arguments: for options, cat headers onto $input; cat files onto $input
X>$input
Xcleanup="test ! -f $HOME/dead.article -o -w $HOME/dead.article &&
X  cat $input >>$HOME/dead.article &&
X  { echo $0: article in $HOME/dead.article >&2; rm -f $rmlist; }; exit 1"
Xtrap "$cleanup" 0 1 2 3 15
Xwhile :
Xdo
X	case $# in
X	0)	break ;;		# arguments exhausted
X	esac
X
X	case "$1" in
X	# peculiar to C news
X	-debug)	shift; debug="$1" ;;
X	-A)	autopost=yes ;;		# wait for free space
X	-V)	relayopts= ;;		# verbose: don't redirect stdout (or stderr)
X	-W)	waitcmd=wait ;;		# wait for completion
X	# useful standard options
X	-h)	hdrspresent=yes ;;
X	-x)	shift; exclusion="-x $1" ;;	# you're welcome, erik (2.11)
X	# silly options supplied by newsreaders
X	-a)	shift; echo "Approved: $1" >>$input ;;
X	-c)	shift; echo "Control: $1" >>$input ;;
X	-d)	shift; echo "Distribution: $1" >>$input ;;
X	-e)	shift; echo "Expires: $1" >>$input ;;
X	-f)	shift; echo "From: $1" >>$input ;;
X	-n)	shift; echo "Newsgroups: $1" >>$input ;;
X	-t)	shift; echo "Subject: $1" >>$input ;;	# aka Title:
X	-D)	# obsolete, undocumented: meant "don't check for recordings".
X		# last present in B 2.10.1, invoked by readnews for followups.
X		;;
X	-F)	# undocumented in B 2.10.1, documented in B 2.11.
X		shift; echo "References: $1" >>$input ;;
X	-M)	# this apparently just sets From: to the author of the article
X		# instead of the poster (moderator), by leaving the From: line
X		# alone (under -h); easy to implement.
X		;;
X
X	# pass next options as environment variables to anne.jones
X	-o)	shift; ORGANIZATION="$1"; export ORGANIZATION ;;
X
X	-C)	# megakludge-o-rama
X		# first, permit only to super-users
X		>$whoami
X		case "`ls -l $whoami | awk '{print $3}'`" in
X		root)	: a winner ;;
X		*)
X			echo "$0: only super-users may create news groups" >&2
X			exit 1
X			;;
X		esac
X		rm -f $whoami
X
X		inewsopt="$1"		# for use in message body
X		shift			# skip -C to get ng as $1
X
X		cat <<! >>$input	# generate a control message
XNewsgroups: $1
XControl: newgroup $1
XSubject: newgroup $1
XApproved: above-user@above-host
X
XThis article generated by inews $inewsopt $1.
X!
X		;;
X	-*)
X		echo "$0: bad option $1" >&2
X		exit 1
X		;;
X	*)					# is a filename; append file
X		# B 2.11 kludge: assume -h if input starts with headers.
X		# apparently the B 2.11 newsreaders assume this.
X		tear /tmp/in$$ <$1
X		if test -s $inhdrs; then
X			hdrspresent=yes
X		fi
X
X		case "$hdrspresent" in
X		no)	echo "" >>$input; hdrspresent=yes ;;
X		esac
X		# capture incoming news in case relaynews fails
X		if cat $inhdrs $inbody >>$input; then
X			: far out
X		else
X			echo "$0: lost news; cat status $?" >&2
X			exit 1
X		fi
X		fileseen=yes
X		;;
X	esac
X	shift		# pass option or filename (any value was done above)
Xdone
X
X# if no files named, read stdin
Xcase "$fileseen" in
Xyes)	;;
X*)
X	# B 2.11 kludge: assume -h if input starts with headers
X	# apparently the B 2.11 newsreaders assume this.
X	tear /tmp/in$$
X	if test -s $inhdrs; then
X		hdrspresent=yes
X	fi
X
X	case "$hdrspresent" in
X	no)	echo "" >>$input; hdrspresent=yes ;;
X	esac
X	# capture incoming news in case relaynews fails
X	if cat $inhdrs $inbody >>$input; then
X		: far out
X	else
X		echo "$0: lost news; cat status $?" >&2
X		exit 1
X	fi
X	;;
Xesac
Xtrap '' 1 2 15			# ignore signals to avoid losing articles
X
X# run the remainder in the background for the benefit of impatient people
X# who lack a window system
X(
Xtrap "$cleanup" 0
Xtear /tmp/in$$ <$input		# output in $inhdrs and $inbody
X# pad zero-line articles, since old B [ir]news are confused by them
X# and the news readers generate zero-line control messages, alas.
Xif test ! -s $inbody; then
X	(echo '';
X	 echo This article was probably generated by a buggy news reader.) \
X	 >$inbody
Xfi
X
X# deduce which tr we have: v6 or v7
Xcase "`echo B | tr A-Z a-z `" in
Xb)	trversion=v7 ;;
XB)	trversion=v6 ;;			# or System V
Xesac
Xexport trversion
X
X# post with new headers and .signature
X(anne.jones <$inhdrs		# bash headers
X # echo "Lines: `		# sop to msb, just uncomment to use
X # if test -r $HOME/.signature; then
X #	(cat $inbody; echo '-- '; sed 4q $HOME/.signature) | wc -l
X # else
X #	wc -l <$inbody
X # fi
X # `"
X
X # strip invisible chars from body, a la B news
X case "$trversion" in
X v7)	tr -d '\1-\7\13\14\16-\37' ;;
X v6)	tr -d '[\1-\7]\13\14[\16-\37]' ;;
X esac <$inbody
X
X if test -r $HOME/.signature; then
X	echo "-- "; sed 4q $HOME/.signature	# glue on first bit of signature
X fi) >$censart
X
X# to post or to mail? that is the question; whether 'tis nobler in the mind
X# to suffer the slings and arrows of outrageous mailers - Bill Shakespeare
Xif grep -s '^Control:' $inhdrs >/dev/null; then
X	echo "control"			# a dreadful hack around all.all.ctl
Xelse
X	sed -n '
X/^Newsgroups:[	 ]/{
Xs/^Newsgroups:[	 ]*\(.*\)$/\1/p
Xq
X}
X' <$inhdrs
Xfi >$nglist
X
Xif test ! -s $nglist; then		# no Newsgroups:
X	exit 1				# anne.jones will have already complained
Xfi
X
X# look up groups in active, to determine disposition of this message.
X# n, x and (unapproved) m flags are dealt with on the spot; if none are
X# seen, the article is posted normally.
X# escape egrep metacharacters.  In theory one could add " ' ` \
Xegreppat="^(` sed -e 's/[.+*()|[]/\\\\&/g' -e 's/,/|/g' <$nglist `) "
Xegrep "$egreppat" $NEWSCTL/active >/dev/null || {
X	echo "$0: `cat $nglist` matches no groups in $NEWSCTL/active" >&2
X	exit 1
X}
Xrm -f $grpok
Xegrep "$egreppat" $NEWSCTL/active |
X	(while read ng high low flag junk	# look at next group's active entry
X	do
X		>>$grpok
X		case "$flag" in
X		[nx])
X			echo "$0: sorry, $ng may not be posted to locally." >&2
X			echo 1 >$exitflag
X			trap 0		# this is a child process - no cleanup
X			exit 1		# dregs in /tmp/in$$*
X			;;
X		m)
X			if grep -s '^Approved:[	 ]' $inhdrs >/dev/null; then
X				rm -f $modroute		# just post normally
X			else
X				# un-Approved: mail it to the moderator(s).
X				echo "%s" >$modroute	# in case no route
X				# look for route for this group
X				cat $NEWSCTL/mailpaths |
X					while read ngpat route junk
X					do
X						# a dreadful B 2.11 hack:
X						# backbone == all
X						case "$ngpat" in
X						backbone) ngpat="all" ;;
X						esac
X						if gngp -a "$ngpat" $nglist >/dev/null; then
X							echo "$route" >$modroute
X							break	# take only 1st match
X						fi
X					done
X			fi
X			# ngpat and route are not set here, damn it!
X			if test -s $modroute; then
X				# an unapproved article in a mod group:
X				# mail the article to this moderator.
X				moderator=`
X				 sed "s/%s/\` echo $ng | tr . - \`/" $modroute
X				`
X				echo "$0: mailing your article to $moderator" >&2
X				mail $moderator <$censart
X				rm -f $rmlist
X				echo 0 >$exitflag
X				trap 0	# this is a child process - did cleanup
X				exit 0
X			fi
X			;;
X
X			# "" matches short active entries,
X			#	to be backward compatible.
X			# * matches garbage flags, to be cautious.
X		y|""|*)
X			# okay so far, but wait until we see all Newsgroups:.
X			;;
X		esac
X	done
X	trap 0					# paranoia - no clean up
X	)
Xif test ! -r $grpok; then
X	echo "$0: no active groups in `cat $nglist`" >&2
X	exit 1			# abnormal exit - cleans up, makes dead.article
Xfi
Xif test -f $exitflag; then
X	exitstatus="`cat $exitflag`"
X	case "$exitstatus" in
X	0)	trap 0 ;;	# normal exit - cleanup done, no dead.article
X	esac
X	exit $exitstatus	# trap 0 will cleanup, make dead.article
Xfi
X
X# deal with inadequate free space
Xcase "$autopost" in
Xno)
X	if test "`spacefor 1 articles`" -le 0; then
X		echo "$0: too little space free on $NEWSARTS" >&2
X		exit 1			# dregs in /tmp/in$$* for trap 0
X	fi
X	;;
X*)
X	iter=0
X	while test "`spacefor 1 articles`" -le 0 -o "`spacefor 1 control`" -le 0
X	do
X		sleep 30
X		iter=`expr $iter + 1`
X		case "$iter" in
X		3)
X			mail "$NEWSMASTER" <<!
XSubject: free space too low on $NEWSARTS
X
XThere is too little free space on $NEWSARTS for inews to run comfortably.
X!
X			;;
X		esac
X	done
X	;;
Xesac
X
X# to get here, we must have seen no n, x, nor (unapproved) m flags.
X# <$censart is used rather than a pipe to work around a bug in the 4.2 sh
X# which made it sometimes return the wrong exit status (that of anne.jones).
X# execute relaynews commands on the server, for the sake of locking.
X# may not use "exec" or sh will leave /tmp/sh* files from here docs in /tmp.
Xme="`hostname`"
Xserver=`cat $NEWSCTL/server 2>/dev/null`
Xcase "$server" in
X"")	server="$me" ;;			# if no server file, assume this is it
Xesac
Xcase "$me" in
X$server)
X	relaynews $relayopts -s $exclusion -d "$debug" <$censart
X	status=$?
X#	echo "status $? from relaynews" >>/tmp/inewsdebug # DEBUG
X	;;
X*)
X	status=`rsh $server \
X"PATH=$PATH relaynews $relayopts -s $exclusion -d \"$debug\"; echo status $?" \
X		<$censart >$outfile; sed -n '/^status /s///p' $outfile `
X	sed '/^status /d' $outfile	# print relaynews's stdout
X	;;
Xesac
Xcase "$status" in
X0)
X	rm -f $rmlist			# far out, it worked: clean up
X	if test ! -f $NEWSCTL/sys; then
X		echo "$0: $NEWSCTL/sys missing; your news can't leave this machine" >&2
X	fi
X	trap 0				# normal exit: cleanup done
X	;;
Xesac
Xexit $status				# trap 0 may cleanup, make dead.article
X) &
X$waitcmd				# wait if -W given
Xtrap 0					# let the background run on unmolested
Xexit
!
echo done


-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.