home *** CD-ROM | disk | FTP | other *** search
/ Unix System Administration Handbook 1997 October / usah_oct97.iso / news / cnews.tar / relay / control.c < prev    next >
C/C++ Source or Header  |  1994-09-08  |  9KB  |  317 lines

  1. /*
  2.  * NCMP (netnews control message protocol).
  3.  * Implement the Usenet control messages, as per RFCs 1036 and 850.
  4.  * These are fairly infrequent and can afford to be done by
  5.  * separate programs.  They are:
  6.  *
  7.  * control messages that (request a) change (in) the local system:
  8.  *    cancel message-ID(s)        restricted to Sender: else From: (or
  9.  *                    root?), in theory
  10.  *    newgroup groupname [moderated]    must be Approved:
  11.  *    rmgroup groupname        must be Approved:;
  12.  *                    allow some local control
  13.  *    checkgroups            harass newsadmin about "deviations"
  14.  *                    in active; incompletely specified
  15.  *
  16.  * control messages that cause mail back to Reply-To: else From:
  17.  *    sendsys [site]
  18.  *    version
  19.  *
  20.  * the "ihave/sendme" protocol to minimise traffic volume and maximise delay
  21.  * between this site and another
  22.  *    ihave [message-ID-list] remotesys    generate a sendme for remotesys
  23.  *                        from message-ID-list
  24.  *    sendme [message-ID-list] remotesys    send articles named to remotesys
  25.  */
  26.  
  27. #include <stdio.h>
  28. #include <stdlib.h>
  29. #include <ctype.h>
  30. #include <string.h>
  31. #include <errno.h>
  32. #include "fixerrno.h"
  33. #include <sys/types.h>
  34.  
  35. #include "libc.h"
  36. #include "news.h"
  37. #include "case.h"
  38. #include "config.h"
  39. #include "headers.h"
  40. #include "relay.h"
  41. #include "active.h"
  42. #include "history.h"
  43.  
  44. #define DEFMSGIDS 10            /* default msgids for awksplit */
  45.  
  46. #define NO_FILES ""
  47. #define SUBDIR binfile("ctl")        /* holds shell scripts */
  48.  
  49. /*
  50.  * These are shell meta-characters, except for /, which is included
  51.  * since it allows people to escape from the control directory.
  52.  */
  53. #define SHELLMETAS "\\<>|^&;\n({$=*?[`'\"/"
  54.  
  55. /* imports from news */
  56. extern statust snufffiles(); 
  57. extern void ihave(), sendme();
  58.  
  59. /*
  60.  * In theory (RFC 1036 nee 850), we should verify that the user issuing the
  61.  * cancel (the Sender: of this article or From: if no Sender) is the
  62.  * Sender: or From: of the original article or the local super-user.
  63.  *
  64.  * In practice, this is a lot of work and since anyone can forge news (and
  65.  * thus cancel anything), not worth the effort.  Furthermore, there is no
  66.  * known precise algorithm for matching addresses (given the address
  67.  * mangling performed by old news systems).
  68.  *
  69.  * Ignore ST_ACCESS while cancelling an already-seen article since the
  70.  * article may have been cancelled before or may have a fake history entry
  71.  * because the cancel arrived before the article.
  72.  *
  73.  * If the article being cancelled has not been seen yet, generate a history
  74.  * file entry for the cancelled article in case it arrives after the cancel
  75.  * control.  The history file entry will cause the cancelled article to be
  76.  * rejected as a duplicate.
  77.  */
  78. STATIC statust
  79. cancelart(msgidstr)
  80. char *msgidstr;
  81. {
  82.     register char *msgid = strsave(msgidstr);
  83.     register statust status = ST_OKAY;
  84.  
  85.     if (msgid[0] == '\0')
  86.         ;
  87.     else if (alreadyseen(msgid)) {
  88.         register char *histent, *filelist;
  89.  
  90.         histent = gethistory(msgid);
  91.         if (histent != NULL && (filelist = findfiles(histent)) != NULL)
  92.             status |= snufffiles(filelist) & ~ST_ACCESS;
  93.     } else {
  94.         status |= fakehist(msgid, DEFEXP, NO_FILES);    /* start log */
  95.         (void) putchar('\n');            /* end log line */
  96.     }
  97.     free(msgid);
  98.     return status;
  99. }
  100.  
  101. STATIC statust
  102. cancel(msgids)
  103. char *msgids;
  104. {
  105.     register int msgidcnt, i;
  106.     register statust status = ST_OKAY;
  107.     char *msgid[DEFMSGIDS];
  108.     char **msgidp = msgid;
  109.     char *msgidcpy = strsave(msgids);
  110.  
  111.     msgidcnt = awksplit(msgidcpy, &msgidp, DEFMSGIDS, " \t\n");
  112.     if (msgidp == NULL) {
  113.         persistent(NOART, 'm', "awksplit failed to allocate memory",
  114.             "");
  115.         status |= ST_DROPPED|ST_NEEDATTN;
  116.     } else {
  117.         for (i = 0; i < msgidcnt; i++)
  118.             status |= cancelart(msgidp[i]);
  119.         if (msgidp != msgid)
  120.             free((char *)msgidp);
  121.     }
  122.     free(msgidcpy);
  123.     return status;
  124. }
  125.  
  126. /*
  127.  * log the failure of cmd with status cmdstat, and _exit with bad status
  128.  * (again avoid stdio buffer flushing in the child).
  129.  */
  130. /* ARGSUSED cmdstat */
  131. STATIC void
  132. bombctlmsg(cmd, cmdstat)
  133. char *cmd;
  134. int cmdstat;
  135. {
  136.     register char *mailcmd;
  137.  
  138.     mailcmd = str3save("PATH=", newspath(), " ; report 'ctl msg failure'");
  139.     if (mailcmd == NULL) {
  140.         persistent(NOART, 'm', "can't allocate memory in bombctlmsg",
  141.                "");
  142.         (void) fflush(stderr);
  143.         _exit(1);
  144.     }
  145.  
  146.     logaudit(NOART, 'c', "control message `%s' failed", cmd);
  147. #ifdef notdef
  148.     {
  149.         /* TODO: don't do this */
  150.         register FILE *mailf = popen(mailcmd, "w");
  151.  
  152.         if (mailf == NULL)
  153.             mailf = stderr;
  154.         (void) fprintf(mailf,
  155.                    "%s: control message `%s' exited with status 0%o\n",
  156.                    progname, cmd, cmdstat);
  157.         (void) fflush(mailf);
  158.         if (mailf != stderr)
  159.             (void) pclose(mailf);
  160.     }
  161. #endif                    /* notdef */
  162.  
  163.     free(mailcmd);
  164.     _exit(1);
  165. }
  166.  
  167. boolean
  168. safecmd(cmd)            /* true if it's safe to system(3) cmd */
  169. register char *cmd;
  170. {
  171.     register char *s;
  172.  
  173.     for (s = cmd; *s != '\0'; s++)
  174.         if (STREQN(s, "..", STRLEN("..")))
  175.             return NO;
  176.     for (s = SHELLMETAS; *s != '\0'; s++)
  177.         if (strchr(cmd, *s) != NULL)
  178.             return NO;
  179.     return YES;
  180. }
  181.  
  182. /*
  183.  * Execute a non-builtin control message by searching $NEWSCTL/bin and
  184.  * $NEWSBIN/ctl for the command named by the control message.
  185.  * runctlmsg is called from a child of relaynews, so it must always
  186.  * call _exit() rather than exit() to avoid flushing stdio buffers.
  187.  *
  188.  * Enforce at least minimal security: the environment was standardised at
  189.  * startup, including PATH and IFS; close non-standard file descriptors;
  190.  * reject shell metacharacters in ctlcmd.
  191.  */
  192. STATIC void
  193. runctlmsg(ctlcmd, inname)            /* child process */
  194. register char *ctlcmd, *inname;
  195. {
  196.     register char *cmd, *s1, *s2, *s3;
  197.     register int cmdstat;
  198.  
  199.     nolock();
  200.     closeall(1);
  201.     if (!safecmd(ctlcmd)) {
  202.         errno = 0;
  203.         logaudit(NOART, 'c', "control `%s' looks unsafe to execute",
  204.              ctlcmd);
  205.         (void) fflush(stderr);
  206.         _exit(0);        /* it's okay; happens all the time */
  207.     }
  208.     s1 = str3save("PATH=", ctlfile("bin"), ":");
  209.     s2 = str3save(SUBDIR, "; ", "");
  210.     s3 = str3save(ctlcmd, " <", inname);
  211.     cmd = str3save(s1, s2, s3);
  212.     free(s1);
  213.     free(s2);
  214.     free(s3);
  215.     /* TODO: use fork, putenv, exec[vl]p here instead of system? */
  216.     cmdstat = system(cmd);
  217.     if (cmdstat != 0)
  218.         bombctlmsg(cmd, cmdstat);
  219.     free(cmd);
  220.     _exit(0);
  221. }
  222.  
  223. STATIC boolean
  224. ismsgnamed(line, msg)
  225. register char *line;
  226. register char *msg;
  227. {
  228.     register int msglen = strlen(msg);
  229.  
  230.     return STREQN(line, msg, msglen) &&
  231.         isascii(line[msglen]) && isspace(line[msglen]);
  232. }
  233.  
  234. /*
  235.  * Implement control message specified in "art".
  236.  * Because newgroup and rmgroup may modify the active file, for example,
  237.  * we must flush in-core caches to disk first and reload them afterward.
  238.  * We handle cancels in this process for speed and dbm read/write access.
  239.  * We handle ihave & sendme in this process for dbm read access and
  240.  * to work around syntax restrictions (<>).
  241.  *
  242.  * In future, one could pass header values to scripts as arguments or
  243.  * in environment, as NEWS* variables, to save time in the scripts.
  244.  */
  245. void
  246. ctlmsg(art)
  247. register struct article *art;
  248. {
  249.     register char *inname = art->a_tmpf, *ctlcmd = art->h.h_ctlcmd;
  250.     int pid, deadpid;
  251.     int wstatus;
  252.     static char nmcancel[] = "cancel";
  253.     static char nmihave[] =  "ihave";
  254.     static char nmsendme[] = "sendme";
  255.  
  256.     /* anything to do? */
  257.     if (ctlcmd == NULL)
  258.         ctlcmd = art->h.h_etctlcmd;
  259.     if (ctlcmd == NULL)
  260.         return;
  261.  
  262.     /* internal ctl msg (cancel, cancel typo, ihave, sendme)? */
  263.     if (ismsgnamed(ctlcmd, nmcancel)) {
  264.         art->a_status |= cancel(ctlcmd + STRLEN(nmcancel));
  265.         return;
  266.     }
  267.     if (ctlcmd[0] == '<' || CISTREQN(ctlcmd, nmcancel, STRLEN(nmcancel)) &&
  268.         (!isascii(ctlcmd[STRLEN(nmcancel)]) ||
  269.          !isalpha(ctlcmd[STRLEN(nmcancel)]))) {
  270.         /* should really just log this */
  271.         errno = 0;
  272.         logaudit(NOART, 'c', "malformed cancel `%s'", ctlcmd);
  273.         /* no need to return bad status; happens all the time */
  274.         return;
  275.     }
  276.     if (ismsgnamed(ctlcmd, nmihave)) {
  277.         ihave(ctlcmd + STRLEN(nmihave), art);
  278.         return;
  279.     }
  280.     if (ismsgnamed(ctlcmd, nmsendme)) {
  281.         sendme(ctlcmd + STRLEN(nmsendme), art);
  282.         return;
  283.     }
  284.  
  285.     /* external ctl msg: flush active and stdio */
  286.     art->a_status |= actsync();
  287.     (void) fflush(stdout);
  288.     (void) fflush(stderr);
  289.  
  290.     pid = fork();
  291.     if (pid == 0)                /* child? */
  292.         runctlmsg(ctlcmd, inname);
  293.     else if (pid == -1)
  294.         persistent(art, 'f', "fork failed", "");
  295.  
  296.     /* lint complains about &wstatus on 4.2+BSD; too bad, lint's wrong. */
  297.     while ((deadpid = wait(&wstatus)) != pid && deadpid != -1)
  298.         ;
  299.  
  300.     /* wrong kid returned, fork failed or child screwed up? */
  301.     if (deadpid == -1 || pid == -1 || wstatus != 0)
  302.         transient(art, '\0', "", "");
  303.             /* ctl msg failed; admin got err.msg. by mail */
  304.     /* let lazy evaluation load the caches */
  305. }
  306.  
  307. char *
  308. hackhybrid(line)
  309. register char *line;
  310. {
  311.     static char stupersedes[] = "Supersedes:";
  312.     static char alsocan[] =     "Also-Control: cancel ";
  313.  
  314.     return CISTREQN(line, stupersedes, STRLEN(stupersedes))?
  315.         str3save(alsocan, "", &line[STRLEN(stupersedes)]): strsave(line);
  316. }
  317.