home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Gold Fish 1
/
GoldFishApril1994_CD2.img
/
d4xx
/
d473
/
cnewssrc
/
cnews_src.lzh
/
relay
/
relaynews.c
< prev
next >
Wrap
C/C++ Source or Header
|
1990-12-22
|
14KB
|
560 lines
/* :ts=4
* relaynews - relay Usenet news (version C)
* See the file COPYRIGHT for the copyright notice.
*
* relaynews should be setuid-news, setgid-news. You'll need to install
* setnewsids setuid-root if setuid(geteuid()) doesn't work on your
* machine (e.g. on V7 and possibly SystemIII).
*
* Written by Geoff Collyer, 15-20 November 1985 and revised periodically
* since.
*
* relaynews parses article headers, rejects articles by newsgroup &
* message-id, files articles, updates the active & history files,
* transmits articles, and honours (infrequent) control messages, which do
* all sorts of varied and rococo things. Control messages are implemented
* by separate programs. relaynews reads a "sys" file to control the
* transmission of articles but can function as a promiscuous leaf node
* without one. See ARPA Internet RFC 1036 nee 850 for the whole story.
*
* A truly radical notion: people may over-ride via environment variables
* the compiled-in default directories so IHCC kludges are not needed and
* testing is possible (and encouraged) in alternate directories. This
* does cause a loss of privilege, to avoid spoofing.
*
* The disused old unbatched ihave/sendme protocol is gone because it was
* too wasteful; use the batched form instead (see the ihave sys flag
* ("I") instead).
*
* $Log$
*/
#include <stdio.h>
#include <errno.h>
#include "fixerrno.h"
#include <ctype.h>
#include <signal.h> /* to make locking safe */
#ifdef unix
# include <sys/types.h>
#endif /* unix */
#include "libc.h"
#include "news.h"
#include "config.h"
#include "fgetmfs.h"
#include "active.h"
#include "caches.h"
#include "fileart.h"
#include "headers.h"
#include "history.h"
#include "transmit.h"
/*
* setuid-root program to set ids to news/news & rexec rnews with
* NEWSPERMS in the environment to break loops.
*/
#ifndef SETNEWSIDS
#define SETNEWSIDS "setnewsids"
#endif
/* exports */
char *progname;
boolean okrefusal = YES; /* okay to refuse articles? */
char *exclude = NULL; /* site to exclude, for erik */
boolean histreject = NO; /* keep history of rejects? */
/* internal */
static boolean userealids = NO;
/* imports */
extern int optind; /* set by getopt */
extern char *optarg;
extern statust cpinsart(); /* from procart.c */
/* forwards */
extern void prelude(), setids(), procopts(), redirectlogs(), logfile();
extern void getwdandcd();
extern statust procargs(), relnmprocess(), process(), unbatch();
extern boolean batchln();
FORWARD boolean debugon();
FORWARD long maxlong();
#ifdef AMIGA
size_t input_lines;
char *curdir = NULL;
void cleanup(void)
{
if (curdir && *curdir) /* Malloc'd space is still available */
chdir(curdir);
}
# ifdef AZTEC_C
#define SIG_IGN ((void (*)(int))1)
void (*signal(int _sig, void (*_func)(int)))(int);
void (*signal(int x, void (*func)(int)))(int)
{
return( (void *)0 );
}
# endif /* AZTEC_C */
#endif /* AMIGA */
/*
* main - take setuid precautions, switch to "news" ids, ignore signals,
* handle options, lock news system, process files & unlock news system.
*/
int
main(argc, argv)
int argc;
char *argv[];
{
statust status = ST_OKAY;
int redirlogs = 0; /* redirect n std output streams to logs */
char *origdir = NULL; /* current directory at start */
progname = argv[0];
#ifdef CSRIMALLOC
mal_debug(0); /* was 2; 3 is too slow */
mal_leaktrace(0); /* was 1 */
#endif
prelude(argv); /* various precautions; switch to "news" */
/* ignore signals (for locking). relaynews runs quickly, so don't worry. */
#ifdef SIGINT
(void) signal(SIGINT, SIG_IGN);
#endif
#ifdef SIGQUIT
(void) signal(SIGQUIT, SIG_IGN);
#endif
#ifdef SIGHUP
(void) signal(SIGHUP, SIG_IGN);
#endif
#ifdef SIGTERM
(void) signal(SIGTERM, SIG_IGN);
#endif
procopts(argc, argv, &redirlogs, &okrefusal);
newslock(); /* done here due to dbm internal caching */
if (redirlogs > 0) {
redirectlogs(redirlogs); /* redirect std output streams to logs */
#ifdef MANYERRORS
(void) putc('\n', stderr); /* leave a blank line */
/* prints "Jun 5 12:34:56" */
timestamp(stderr, (time_t *)NULL);
(void) putc('\n', stderr);
#endif
}
#ifdef AMIGA
curdir = getcwd( NULL, 0 ); /* curdir is malloc'd space */
atexit(cleanup);
#endif /* AMIGA */
getwdandcd(argc, argv, &origdir);
status |= procargs(argc, argv, &origdir);
status |= synccaches(); /* being cautious: write & close caches */
status |= closehist();
(void) fflush(stdout); /* log file */
(void) fflush(stderr); /* errlog file */
#ifdef notdef
#ifdef CSRIMALLOC
mal_dumpleaktrace(fileno(stderr));
#endif
#endif
newsunlock();
exit(status);
/* NOTREACHED */
}
/*
* reset various environmental things for safety: umask, alarm,
* environment variables (PATH, IFS), standard file descriptors,
* user & group ids.
*/
void
prelude(argv) /* setuid daemon prelude */
char **argv;
{
register char *newpath;
(void) umask(newsumask());
(void) alarm(0); /* cancel any pending alarm */
newpath = malloc((unsigned)(strlen("PATH=")+strlen(newspath())+SIZENUL));
if (newpath == NULL)
exit(1); /* no chatter until stdfdopen */
(void) strcpy(newpath, "PATH=");
(void) strcat(newpath, newspath());
#ifndef AMIGA
if (putenv(newpath) ||
putenv("SHELL=/bin/sh") ||
putenv("IFS= \t\n"))
exit(1); /* no chatter until stdfdopen */
#endif
closeall(1); /* closes all but std descriptors */
stdfdopen(); /* ensure standard descriptors are open */
setids(argv); /* change of real and effective ids */
}
/*
* change real and effective ids to real ids if unprivileged() is called,
* else to effective ("news") ids. ctlfile((char *)0) will trigger a call
* to unprivileged() if any environment variables override the default
* path names. unprivileged() in turn sets userealids.
*
* If setuid(geteuid()) fails, try execing a small, setuid-root program
* to just do "getpwnam(), getgrnam() (with NEWSPERMS set), setgid(),
* setuid()," and exec this program again. If NEWSPERMS is set,
* the failure is a fatal error (recursive loop).
* This program (relaynews) can be setuid-news.
*
* The peculiar tests for failure (getuid() != newsuid) are to work
* around a Xenix bug which returns 0 from setuid() upon failure.
*/
void
setids(argv)
char **argv;
{
#ifndef AMIGA
int newsuid, newsgid;
(void) ctlfile((char *)NULL);
if (userealids)
newsuid = getuid(), newsgid = getgid();
else
newsuid = geteuid(), newsgid = getegid();
if (setgid(newsgid) < 0 || setuid(newsuid) < 0 ||
getgid() != newsgid || getuid() != newsuid) {
if (getenv("NEWSPERMS") != 0)
error("recursive loop setting ids", "");
execv(ctlfile(SETNEWSIDS), argv);
error("can't exec `%s' to set ids", ctlfile(SETNEWSIDS));
/* NOTREACHED */
}
/* we are now running as news, so you can all relax */
#endif /* !AMIGA */
}
/*
* parse options and set flags
*/
void
procopts(argc, argv, redirlogsp, okrefusalp)
int argc;
char **argv;
int *redirlogsp;
boolean *okrefusalp;
{
int c, errflg = 0;
while ((c = getopt(argc, argv, "d:inrsx:")) != EOF)
switch (c) {
case 'd': /* -d debug-options; thanks, henry */
if (!debugon(optarg))
errflg++; /* debugon has complained */
break;
case 'i': /* redirect stdout to log (inews) */
*redirlogsp = 1; /* just stdout */
break;
case 'n': /* nntp mode: keep history of rejects */
histreject = YES;
break;
case 'r': /* redirect std. ostreams to logs (rnews) */
*redirlogsp = 2; /* stdout & stderr */
break;
case 's': /* dropping input is serious (inews) */
*okrefusalp = NO;
break;
case 'x': /* -x site: don't send to site */
/* you're welcome, erik */
/* erik says he only needs one -x per inews */
if (exclude != NULL) {
(void) fprintf(stderr,
"%s: more than one -x site (%s)\n", progname, optarg);
errflg++;
} else
exclude = optarg;
break;
default:
errflg++;
break;
}
if (errflg) {
(void) fprintf(stderr, "Usage: %s [-inrs] [-d fhlmt] [-x site]\n",
progname);
exit(1);
}
}
/*
* called if NEWSARTS, NEWSCTL, NEWSBIN, etc. are non-standard.
* this may be due to legitimate testing, but we can't tell.
* the error message will at least be seen by a human trying to
* track down a problem, even if stderr isn't normally seen.
*/
void
unprivileged(reason)
char *reason;
{
userealids = YES;
(void) fprintf(stderr,
"%s: warning: renouncing setuid due to nonstandard `%s' in environment\n",
progname, reason);
}
STATIC boolean
debugon(dbopt)
register char *dbopt;
{
statust status = YES;
for (; *dbopt != '\0'; dbopt++)
switch (*dbopt) {
#ifdef AMIGA
case 'd':
if (dbzdebug(1) < 0)
printf("Dbz debugging not available\n");
break;
#endif /* AMIGA */
case 'f':
filedebug(YES);
break;
case 'h':
hdrdebug(YES);
break;
case 'l':
lockdebug(YES);
break;
case 'm':
matchdebug(YES);
break;
case 't':
transdebug(YES);
break;
default:
status = NO; /* unknown debugging option */
(void) fprintf(stderr, "%s: bad -d %c\n",
progname, *dbopt);
break;
}
return status;
}
/*
* Redirect stdout or stderr into log files at known locations.
*/
void
redirectlogs(count)
int count;
{
if (count > 0)
logfile(stdout, ctlfile("log"));
if (count > 1)
logfile(stderr, ctlfile("errlog"));
}
void
logfile(stream, name) /* redirect stream into name */
FILE *stream;
char *name;
{
if (freopen(name, "a", stream) == NULL)
errunlock("can't redirect standard stream to `%s'", name);
}
/*
* if argv contains relative file name arguments, save current directory name
* in malloced memory, through origdirp.
* then change directory to the spool directory ($NEWSARTS).
*/
void
getwdandcd(argc, argv, origdirp)
int argc;
char **argv;
char **origdirp;
{
register int argind;
boolean needpwd = NO;
static char dirtmp[MAXPATH]; /* much bigger than needed */
for (argind = optind; argind < argc; argind++)
if (argv[argind][0] != FNDELIM)
needpwd = YES;
*origdirp = "/???"; /* pessimism */
if (needpwd && getcwd(dirtmp, sizeof dirtmp) != 0)
*origdirp = dirtmp;
*origdirp = strsave(*origdirp); /* save a smaller copy */
cd(fullartfile((char *)NULL)); /* move to spool directory */
}
/*
* process files named as arguments (or implied)
*/
statust
procargs(argc, argv, origdirp)
int argc;
char **argv;
char **origdirp;
{
register statust status = ST_OKAY;
if (optind == argc)
status |= process(stdin, "stdin");
else {
for (; optind < argc; optind++) {
if (isdigit(*argv[optind]))
input_lines = atol(argv[optind]);
else
status |= relnmprocess(argv[optind], *origdirp);
}
if (input_lines)
status |= process(stdin, "stdin");
}
nnfree(origdirp);
return status;
}
statust
relnmprocess(name, origdir) /* process a (relative) file name */
char *name, *origdir;
{
register statust status = ST_OKAY;
register FILE *in;
register char *fullname;
fullname = nemalloc((unsigned)strlen(origdir) + STRLEN(SFNDELIM) +
strlen(name) + SIZENUL);
fullname[0] = '\0';
#ifdef AMIGA
mkfilename(fullname, origdir, name);
#else
if (name[0] != FNDELIM) { /* relative path */
(void) strcat(fullname, origdir);
(void) strcat(fullname, SFNDELIM);
}
(void) strcat(fullname, name);
#endif /* AMIGA */
in = fopenwclex(fullname, "r");
if (in != NULL) {
status |= process(in, fullname);
(void) nfclose(in);
}
free(fullname);
return status;
}
/*
* process - process input file
* If it starts with '#', assume it's a batch and unravel it,
* else it's a single article, so just inject it.
*/
statust
process(in, inname)
FILE *in;
char *inname;
{
register int c;
if ((c = getc(in)) == EOF)
return ST_OKAY; /* normal EOF */
(void) ungetc(c, in);
if (c == '#')
return unbatch(in, inname);
else
/* -SIZENUL is to avoid overflow later during +SIZENUL */
return cpinsart(in, inname, maxlong() - SIZENUL, NO);
}
/*
* compute the largest number that can be stored in a long. in theory,
* #define MAXLONG ((long)(~(unsigned long)0 >> 1))
* will do the job, but old compilers don't have "unsigned long", don't
* like casts in initialisers, or otherwise miscompute.
*/
STATIC long
maxlong()
{
register int bits = 0;
register unsigned word = 1; /* "unsigned" avoids overflow */
static long savemaxlong = 0;
if (savemaxlong > 0)
return savemaxlong;
for (bits = 0, word = 1; word != 0; word <<= 1)
bits++;
/* bits/sizeof word = bits per char; all bits on but the sign bit */
savemaxlong = ~(1L << (bits/sizeof word * sizeof savemaxlong - 1));
if (savemaxlong <= 0) { /* sanity check */
errno = 0;
errunlock("maxlong is non-positive; your compiler is broken", "");
}
return savemaxlong;
}
/*
* Unwind "in" and insert each article.
* For each article, call cpinsart to copy the article from "in" into
* a (temporary) file in the news spool directory and rename the temp file
* to the correct final name if it isn't right already.
*
* If the unbatcher gets out of sync with the input batch, the unbatcher
* will print and discard each input line until it gets back in sync.
*/
statust
unbatch(in, inname)
register FILE *in;
char *inname;
{
register int c;
/* register */ char *line;
register statust status = ST_OKAY;
long charcnt;
while (!(status&ST_NEEDATTN) && (c = getc(in)) != EOF) {
(void) ungetc(c, in);
while ((line = fgetms(in)) != NULL && !batchln(line, &charcnt)) {
/* returns charcnt */
status |= ST_DROPPED;
(void) fprintf(stderr,
"%s: unbatcher out of synch, tossing: ", progname);
(void) fputs(line, stderr);
free(line);
}
nnfree(&line); /* free "#! rnews n" */
if (!feof(in))
status |= cpinsart(in, inname, charcnt, YES);
}
if (ferror(in))
errunlock("error reading `%s'", inname);
return status;
}
/*
* Is line a batcher-produced line (#! rnews count)?
* If so, return the count through charcntp.
* This is slightly less convenient than sscanf, but a lot smaller.
*/
boolean
batchln(line, charcntp)
register char *line;
register long *charcntp;
{
register char *countp;
static char batchtext[] = "#! rnews ";
countp = line + STRLEN(batchtext);
if (STREQN(line, batchtext, STRLEN(batchtext)) &&
isascii(*countp) && isdigit(*countp)) {
*charcntp = atol(countp);
return YES;
} else {
*charcntp = 0;
return NO;
}
}