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