home *** CD-ROM | disk | FTP | other *** search
/ Gold Fish 1 / GoldFishApril1994_CD2.img / d4xx / d473 / cnewssrc / cnews_src.lzh / relay / relaynews.c < prev    next >
C/C++ Source or Header  |  1990-12-22  |  14KB  |  560 lines

  1. /* :ts=4
  2.  * relaynews - relay Usenet news (version C)
  3.  * See the file COPYRIGHT for the copyright notice.
  4.  *
  5.  * relaynews should be setuid-news, setgid-news.  You'll need to install
  6.  * setnewsids setuid-root if setuid(geteuid()) doesn't work on your
  7.  * machine (e.g. on V7 and possibly SystemIII).
  8.  *
  9.  * Written by Geoff Collyer, 15-20 November 1985 and revised periodically
  10.  * since.
  11.  *
  12.  * relaynews parses article headers, rejects articles by newsgroup &
  13.  * message-id, files articles, updates the active & history files,
  14.  * transmits articles, and honours (infrequent) control messages, which do
  15.  * all sorts of varied and rococo things.  Control messages are implemented
  16.  * by separate programs.  relaynews reads a "sys" file to control the
  17.  * transmission of articles but can function as a promiscuous leaf node
  18.  * without one.  See ARPA Internet RFC 1036 nee 850 for the whole story.
  19.  *
  20.  * A truly radical notion: people may over-ride via environment variables
  21.  * the compiled-in default directories so IHCC kludges are not needed and
  22.  * testing is possible (and encouraged) in alternate directories.  This
  23.  * does cause a loss of privilege, to avoid spoofing.
  24.  *
  25.  * The disused old unbatched ihave/sendme protocol is gone because it was
  26.  * too wasteful; use the batched form instead (see the ihave sys flag
  27.  * ("I") instead).
  28.  *
  29.  *    $Log$
  30.  */
  31.  
  32. #include <stdio.h>
  33. #include <errno.h>
  34. #include "fixerrno.h"
  35. #include <ctype.h>
  36. #include <signal.h>        /* to make locking safe */
  37. #ifdef unix
  38. # include <sys/types.h>
  39. #endif /* unix */
  40.  
  41. #include "libc.h"
  42. #include "news.h"
  43. #include "config.h"
  44. #include "fgetmfs.h"
  45. #include "active.h"
  46. #include "caches.h"
  47. #include "fileart.h"
  48. #include "headers.h"
  49. #include "history.h"
  50. #include "transmit.h"
  51.  
  52. /*
  53.  * setuid-root program to set ids to news/news & rexec rnews with
  54.  * NEWSPERMS in the environment to break loops.
  55.  */
  56. #ifndef SETNEWSIDS
  57. #define SETNEWSIDS "setnewsids"
  58. #endif
  59.  
  60. /* exports */
  61. char *progname;
  62. boolean okrefusal = YES;            /* okay to refuse articles? */
  63. char *exclude = NULL;                /* site to exclude, for erik */
  64. boolean histreject = NO;            /* keep history of rejects? */
  65.  
  66. /* internal */
  67. static boolean userealids = NO;
  68.  
  69. /* imports */
  70. extern int optind;            /* set by getopt */
  71. extern char *optarg;
  72. extern statust cpinsart();        /* from procart.c */
  73.  
  74. /* forwards */
  75. extern void prelude(), setids(), procopts(), redirectlogs(), logfile();
  76. extern void getwdandcd();
  77. extern statust procargs(), relnmprocess(), process(), unbatch();
  78. extern boolean batchln();
  79. FORWARD boolean debugon();
  80. FORWARD long maxlong();
  81.  
  82. #ifdef AMIGA
  83. size_t input_lines;
  84. char *curdir = NULL;
  85.  
  86. void cleanup(void)
  87. {
  88.     if (curdir && *curdir)        /* Malloc'd space is still available */
  89.         chdir(curdir);
  90. }
  91.  
  92. # ifdef AZTEC_C
  93. #define SIG_IGN    ((void (*)(int))1)
  94. void (*signal(int _sig, void (*_func)(int)))(int);
  95.  
  96. void (*signal(int x, void (*func)(int)))(int)
  97. {
  98.     return( (void *)0 );
  99. }
  100. # endif /* AZTEC_C */
  101. #endif /* AMIGA */
  102.  
  103. /*
  104.  * main - take setuid precautions, switch to "news" ids, ignore signals,
  105.  * handle options, lock news system, process files & unlock news system.
  106.  */
  107. int
  108. main(argc, argv)
  109. int argc;
  110. char *argv[];
  111. {
  112.     statust status = ST_OKAY;
  113.     int redirlogs = 0;        /* redirect n std output streams to logs */
  114.     char *origdir = NULL;        /* current directory at start */
  115.  
  116.     progname = argv[0];
  117. #ifdef CSRIMALLOC
  118.     mal_debug(0);    /* was 2; 3 is too slow */
  119.     mal_leaktrace(0);    /* was 1 */
  120. #endif
  121.     prelude(argv);        /* various precautions; switch to "news" */
  122.  
  123.     /* ignore signals (for locking). relaynews runs quickly, so don't worry. */
  124. #ifdef SIGINT
  125.     (void) signal(SIGINT, SIG_IGN);
  126. #endif
  127. #ifdef SIGQUIT
  128.     (void) signal(SIGQUIT, SIG_IGN);
  129. #endif
  130. #ifdef SIGHUP
  131.     (void) signal(SIGHUP, SIG_IGN);
  132. #endif
  133. #ifdef SIGTERM
  134.     (void) signal(SIGTERM, SIG_IGN);
  135. #endif
  136.  
  137.     procopts(argc, argv, &redirlogs, &okrefusal);
  138.  
  139.     newslock();            /* done here due to dbm internal caching */
  140.     if (redirlogs > 0) {
  141.         redirectlogs(redirlogs); /* redirect std output streams to logs */
  142. #ifdef MANYERRORS
  143.         (void) putc('\n', stderr);    /* leave a blank line */
  144.         /* prints "Jun  5 12:34:56" */
  145.         timestamp(stderr, (time_t *)NULL);
  146.         (void) putc('\n', stderr);
  147. #endif
  148.     }
  149.  
  150. #ifdef AMIGA
  151.     curdir = getcwd( NULL, 0 );        /* curdir is malloc'd space */
  152.     atexit(cleanup);
  153. #endif /* AMIGA */
  154.     getwdandcd(argc, argv, &origdir);
  155.     status |= procargs(argc, argv, &origdir);
  156.  
  157.     status |= synccaches();        /* being cautious: write & close caches */
  158.     status |= closehist();
  159.     (void) fflush(stdout);        /* log file */
  160.     (void) fflush(stderr);        /* errlog file */
  161.  
  162. #ifdef notdef
  163. #ifdef CSRIMALLOC
  164.     mal_dumpleaktrace(fileno(stderr));
  165. #endif
  166. #endif
  167.     newsunlock();
  168.     exit(status);
  169.     /* NOTREACHED */
  170. }
  171.  
  172. /*
  173.  * reset various environmental things for safety: umask, alarm,
  174.  * environment variables (PATH, IFS), standard file descriptors,
  175.  * user & group ids.
  176.  */
  177. void
  178. prelude(argv)                /* setuid daemon prelude */
  179. char **argv;
  180. {
  181.     register char *newpath;
  182.  
  183.     (void) umask(newsumask());
  184.     (void) alarm(0);        /* cancel any pending alarm */
  185.     newpath = malloc((unsigned)(strlen("PATH=")+strlen(newspath())+SIZENUL));
  186.     if (newpath == NULL)
  187.         exit(1);        /* no chatter until stdfdopen */
  188.     (void) strcpy(newpath, "PATH=");
  189.     (void) strcat(newpath, newspath());
  190. #ifndef AMIGA
  191.     if (putenv(newpath) ||
  192.         putenv("SHELL=/bin/sh") ||
  193.         putenv("IFS= \t\n"))
  194.         exit(1);        /* no chatter until stdfdopen */
  195. #endif
  196.     closeall(1);            /* closes all but std descriptors */
  197.     stdfdopen();            /* ensure standard descriptors are open */
  198.     setids(argv);            /* change of real and effective ids */
  199. }
  200.  
  201. /*
  202.  * change real and effective ids to real ids if unprivileged() is called,
  203.  * else to effective ("news") ids.  ctlfile((char *)0) will trigger a call
  204.  * to unprivileged() if any environment variables override the default
  205.  * path names.  unprivileged() in turn sets userealids.
  206.  *
  207.  * If setuid(geteuid()) fails, try execing a small, setuid-root program
  208.  * to just do "getpwnam(), getgrnam() (with NEWSPERMS set), setgid(),
  209.  * setuid()," and exec this program again.  If NEWSPERMS is set,
  210.  * the failure is a fatal error (recursive loop).
  211.  * This program (relaynews) can be setuid-news.
  212.  *
  213.  * The peculiar tests for failure (getuid() != newsuid) are to work
  214.  * around a Xenix bug which returns 0 from setuid() upon failure.
  215.  */
  216. void
  217. setids(argv)
  218. char **argv;
  219. {
  220. #ifndef AMIGA
  221.     int newsuid, newsgid;
  222.  
  223.     (void) ctlfile((char *)NULL);
  224.     if (userealids)
  225.         newsuid = getuid(), newsgid = getgid();
  226.     else
  227.         newsuid = geteuid(), newsgid = getegid();
  228.     if (setgid(newsgid) < 0 || setuid(newsuid) < 0 ||
  229.         getgid() != newsgid || getuid() != newsuid) {
  230.         if (getenv("NEWSPERMS") != 0)
  231.             error("recursive loop setting ids", "");
  232.         execv(ctlfile(SETNEWSIDS), argv);
  233.         error("can't exec `%s' to set ids", ctlfile(SETNEWSIDS));
  234.         /* NOTREACHED */
  235.     }
  236.     /* we are now running as news, so you can all relax */
  237. #endif /* !AMIGA */
  238. }
  239.  
  240. /*
  241.  * parse options and set flags
  242.  */
  243. void
  244. procopts(argc, argv, redirlogsp, okrefusalp)
  245. int argc;
  246. char **argv;
  247. int *redirlogsp;
  248. boolean *okrefusalp;
  249. {
  250.     int c, errflg = 0;
  251.  
  252.     while ((c = getopt(argc, argv, "d:inrsx:")) != EOF)
  253.         switch (c) {
  254.         case 'd':        /* -d debug-options; thanks, henry */
  255.             if (!debugon(optarg))
  256.                 errflg++;    /* debugon has complained */
  257.             break;
  258.         case 'i':        /* redirect stdout to log (inews) */
  259.             *redirlogsp = 1; /* just stdout */
  260.             break;
  261.         case 'n':        /* nntp mode: keep history of rejects */
  262.             histreject = YES;
  263.             break;
  264.         case 'r':        /* redirect std. ostreams to logs (rnews) */
  265.             *redirlogsp = 2; /* stdout & stderr */
  266.             break;
  267.         case 's':        /* dropping input is serious (inews) */
  268.             *okrefusalp = NO;
  269.             break;
  270.         case 'x':        /* -x site: don't send to site */
  271.             /* you're welcome, erik */
  272.             /* erik says he only needs one -x per inews */
  273.             if (exclude != NULL) {
  274.                 (void) fprintf(stderr,
  275.                     "%s: more than one -x site (%s)\n", progname, optarg);
  276.                 errflg++;
  277.             } else
  278.                 exclude = optarg;
  279.             break;
  280.         default:
  281.             errflg++;
  282.             break;
  283.         }
  284.     if (errflg) {
  285.         (void) fprintf(stderr, "Usage: %s [-inrs] [-d fhlmt] [-x site]\n",
  286.             progname);
  287.         exit(1);
  288.     }
  289. }
  290.  
  291. /*
  292.  * called if NEWSARTS, NEWSCTL, NEWSBIN, etc. are non-standard.
  293.  * this may be due to legitimate testing, but we can't tell.
  294.  * the error message will at least be seen by a human trying to
  295.  * track down a problem, even if stderr isn't normally seen.
  296.  */
  297. void
  298. unprivileged(reason)
  299. char *reason;
  300. {
  301.     userealids = YES;
  302.     (void) fprintf(stderr,
  303. "%s: warning: renouncing setuid due to nonstandard `%s' in environment\n",
  304.             progname, reason);
  305. }
  306.  
  307. STATIC boolean
  308. debugon(dbopt)
  309. register char *dbopt;
  310. {
  311.     statust status = YES;
  312.  
  313.     for (; *dbopt != '\0'; dbopt++)
  314.         switch (*dbopt) {
  315. #ifdef AMIGA
  316.         case 'd':
  317.             if (dbzdebug(1) < 0)
  318.                 printf("Dbz debugging not available\n");
  319.             break;
  320. #endif /* AMIGA */
  321.         case 'f':
  322.             filedebug(YES);
  323.             break;
  324.         case 'h':
  325.             hdrdebug(YES);
  326.             break;
  327.         case 'l':
  328.             lockdebug(YES);
  329.             break;
  330.         case 'm':
  331.             matchdebug(YES);
  332.             break;
  333.         case 't':
  334.             transdebug(YES);
  335.             break;
  336.         default:
  337.             status = NO;    /* unknown debugging option */
  338.             (void) fprintf(stderr, "%s: bad -d %c\n",
  339.                 progname, *dbopt);
  340.             break;
  341.         }
  342.     return status;
  343. }
  344.  
  345. /*
  346.  * Redirect stdout or stderr into log files at known locations.
  347.  */
  348. void
  349. redirectlogs(count)
  350. int count;
  351. {
  352.     if (count > 0)
  353.         logfile(stdout, ctlfile("log"));
  354.     if (count > 1)
  355.         logfile(stderr, ctlfile("errlog"));
  356. }
  357.  
  358. void
  359. logfile(stream, name)            /* redirect stream into name */
  360. FILE *stream;
  361. char *name;
  362. {
  363.     if (freopen(name, "a", stream) == NULL)
  364.         errunlock("can't redirect standard stream to `%s'", name);
  365. }
  366.  
  367. /*
  368.  * if argv contains relative file name arguments, save current directory name
  369.  * in malloced memory, through origdirp.
  370.  * then change directory to the spool directory ($NEWSARTS).
  371.  */
  372. void
  373. getwdandcd(argc, argv, origdirp)
  374. int argc;
  375. char **argv;
  376. char **origdirp;
  377. {
  378.     register int argind;
  379.     boolean needpwd = NO;
  380.     static char dirtmp[MAXPATH];            /* much bigger than needed */
  381.  
  382.     for (argind = optind; argind < argc; argind++)
  383.         if (argv[argind][0] != FNDELIM)
  384.             needpwd = YES;
  385.  
  386.     *origdirp = "/???";            /* pessimism */
  387.     if (needpwd && getcwd(dirtmp, sizeof dirtmp) != 0)
  388.         *origdirp = dirtmp;
  389.     *origdirp = strsave(*origdirp);        /* save a smaller copy */
  390.     cd(fullartfile((char *)NULL));        /* move to spool directory */
  391. }
  392.  
  393. /*
  394.  * process files named as arguments (or implied)
  395.  */
  396. statust
  397. procargs(argc, argv, origdirp)
  398. int argc;
  399. char **argv;
  400. char **origdirp;
  401. {
  402.     register statust status = ST_OKAY;
  403.  
  404.     if (optind == argc)
  405.         status |= process(stdin, "stdin");
  406.     else {
  407.         for (; optind < argc; optind++) {
  408.             if (isdigit(*argv[optind]))
  409.                 input_lines = atol(argv[optind]);
  410.             else
  411.                 status |= relnmprocess(argv[optind], *origdirp);
  412.         }
  413.         if (input_lines)
  414.             status |= process(stdin, "stdin");
  415.     }
  416.     nnfree(origdirp);
  417.     return status;
  418. }
  419.  
  420. statust
  421. relnmprocess(name, origdir)        /* process a (relative) file name */
  422. char *name, *origdir;
  423. {
  424.     register statust status = ST_OKAY;
  425.     register FILE *in;
  426.     register char *fullname;
  427.  
  428.     fullname = nemalloc((unsigned)strlen(origdir) + STRLEN(SFNDELIM) +
  429.         strlen(name) + SIZENUL);
  430.     fullname[0] = '\0';
  431.  
  432. #ifdef AMIGA
  433.     mkfilename(fullname, origdir, name);
  434. #else
  435.     if (name[0] != FNDELIM) {    /* relative path */
  436.         (void) strcat(fullname, origdir);
  437.         (void) strcat(fullname, SFNDELIM);
  438.     }
  439.     (void) strcat(fullname, name);
  440. #endif /* AMIGA */
  441.  
  442.     in = fopenwclex(fullname, "r");
  443.     if (in != NULL) {
  444.         status |= process(in, fullname);
  445.         (void) nfclose(in);
  446.     }
  447.     free(fullname);
  448.     return status;
  449. }
  450.  
  451. /*
  452.  * process - process input file
  453.  * If it starts with '#', assume it's a batch and unravel it,
  454.  * else it's a single article, so just inject it.
  455.  */
  456. statust
  457. process(in, inname)
  458. FILE *in;
  459. char *inname;
  460. {
  461.     register int c;
  462.  
  463.     if ((c = getc(in)) == EOF)
  464.         return ST_OKAY;         /* normal EOF */
  465.     (void) ungetc(c, in);
  466.     if (c == '#')
  467.         return unbatch(in, inname);
  468.     else
  469.         /* -SIZENUL is to avoid overflow later during +SIZENUL */
  470.         return cpinsart(in, inname, maxlong() - SIZENUL, NO);
  471. }
  472.  
  473. /*
  474.  * compute the largest number that can be stored in a long.  in theory, 
  475.  * #define MAXLONG ((long)(~(unsigned long)0 >> 1))
  476.  * will do the job, but old compilers don't have "unsigned long", don't
  477.  * like casts in initialisers, or otherwise miscompute.
  478.  */
  479. STATIC long
  480. maxlong()
  481. {
  482.     register int bits = 0;
  483.     register unsigned word = 1;        /* "unsigned" avoids overflow */
  484.     static long savemaxlong = 0;
  485.  
  486.     if (savemaxlong > 0)
  487.         return savemaxlong;
  488.     for (bits = 0, word = 1; word != 0; word <<= 1)
  489.         bits++;
  490.     /* bits/sizeof word = bits per char; all bits on but the sign bit */
  491.     savemaxlong = ~(1L << (bits/sizeof word * sizeof savemaxlong - 1));
  492.     if (savemaxlong <= 0) {            /* sanity check */
  493.         errno = 0;
  494.         errunlock("maxlong is non-positive; your compiler is broken", "");
  495.     }
  496.     return savemaxlong;
  497. }
  498.  
  499. /*
  500.  * Unwind "in" and insert each article.
  501.  * For each article, call cpinsart to copy the article from "in" into
  502.  * a (temporary) file in the news spool directory and rename the temp file
  503.  * to the correct final name if it isn't right already.
  504.  *
  505.  * If the unbatcher gets out of sync with the input batch, the unbatcher
  506.  * will print and discard each input line until it gets back in sync.
  507.  */
  508. statust
  509. unbatch(in, inname)
  510. register FILE *in;
  511. char *inname;
  512. {
  513.     register int c;
  514.     /* register */ char *line;
  515.     register statust status = ST_OKAY;
  516.     long charcnt;
  517.  
  518.     while (!(status&ST_NEEDATTN) && (c = getc(in)) != EOF) {
  519.         (void) ungetc(c, in);
  520.         while ((line = fgetms(in)) != NULL && !batchln(line, &charcnt)) {
  521.                                                 /* returns charcnt */
  522.             status |= ST_DROPPED;
  523.             (void) fprintf(stderr,
  524.                 "%s: unbatcher out of synch, tossing: ", progname);
  525.             (void) fputs(line, stderr);
  526.             free(line);
  527.         }
  528.         nnfree(&line);            /* free "#! rnews n" */
  529.         if (!feof(in))
  530.             status |= cpinsart(in, inname, charcnt, YES);
  531.     }
  532.     if (ferror(in))
  533.         errunlock("error reading `%s'", inname);
  534.     return status;
  535. }
  536.  
  537. /*
  538.  * Is line a batcher-produced line (#! rnews count)?
  539.  * If so, return the count through charcntp.
  540.  * This is slightly less convenient than sscanf, but a lot smaller.
  541.  */
  542. boolean
  543. batchln(line, charcntp)
  544. register char *line;
  545. register long *charcntp;
  546. {
  547.     register char *countp;
  548.     static char batchtext[] = "#! rnews ";
  549.  
  550.     countp = line + STRLEN(batchtext);
  551.     if (STREQN(line, batchtext, STRLEN(batchtext)) &&
  552.         isascii(*countp) && isdigit(*countp)) {
  553.         *charcntp = atol(countp);
  554.         return YES;
  555.     } else {
  556.         *charcntp = 0;
  557.         return NO;
  558.     }
  559. }
  560.