home *** CD-ROM | disk | FTP | other *** search
/ Nebula / nebula.bin / SourceCode / CronVixie2.1 / do_command.c < prev    next >
C/C++ Source or Header  |  1992-01-14  |  15KB  |  574 lines

  1. #if !defined(lint) && !defined(LINT)
  2. static char rcsid[] = "$Header: /private/Net/beauty/src/vixie-cron-2.1/RCS/do_command.c,v 2.1 90/07/18 00:23:38 vixie Exp Locker: irving $";
  3. #endif
  4.  
  5. /* $Source: /private/Net/beauty/src/vixie-cron-2.1/RCS/do_command.c,v $
  6.  * $Revision: 2.1 $
  7.  * $Log:    do_command.c,v $
  8.  * Revision 2.1  90/07/18  00:23:38  vixie
  9.  * Baseline for 4.4BSD release
  10.  * 
  11.  * Revision 2.0  88/12/10  04:57:44  vixie
  12.  * V2 Beta
  13.  * 
  14.  * Revision 1.5  88/11/29  13:06:06  vixie
  15.  * seems to work on Ultrix 3.0 FT1
  16.  * 
  17.  * Revision 1.4  87/05/02  17:33:35  paul
  18.  * baseline for mod.sources release
  19.  * 
  20.  * Revision 1.3  87/04/09  00:03:58  paul
  21.  * improved data hiding, locality of declaration/references
  22.  * fixed a rs@mirror bug by redesigning the mailto stuff completely
  23.  * 
  24.  * Revision 1.2  87/03/19  12:46:24  paul
  25.  * implemented suggestions from rs@mirror (Rich $alz):
  26.  *    MAILTO="" means no mail should be sent
  27.  *    various fixes of bugs or lint complaints
  28.  *    put a To: line in the mail message
  29.  * 
  30.  * Revision 1.1  87/01/26  23:47:00  paul
  31.  * Initial revision
  32.  */
  33.  
  34. /* Copyright 1988,1990 by Paul Vixie
  35.  * All rights reserved
  36.  *
  37.  * Distribute freely, except: don't remove my name from the source or
  38.  * documentation (don't take credit for my work), mark your changes (don't
  39.  * get me blamed for your possible bugs), don't alter or remove this
  40.  * notice.  May be sold if buildable source is provided to buyer.  No
  41.  * warrantee of any kind, express or implied, is included with this
  42.  * software; use at your own risk, responsibility for damages (if any) to
  43.  * anyone resulting from the use of this software rests entirely with the
  44.  * user.
  45.  *
  46.  * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
  47.  * I'll try to keep a version up to date.  I can be reached as follows:
  48.  * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
  49.  * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
  50.  */
  51.  
  52.  
  53. #include "cron.h"
  54. #include <signal.h>
  55. #include <pwd.h>
  56. #if defined(BSD)
  57. # include <sys/wait.h>
  58. #endif /*BSD*/
  59. #if defined(sequent)
  60. # include <strings.h>
  61. # include <sys/universe.h>
  62. #endif
  63.  
  64.  
  65. void
  66. do_command(cmd, u)
  67.     char    *cmd;
  68.     user    *u;
  69. {
  70.     extern int    fork(), _exit();
  71.     extern void    log_it();
  72.     static void    child_process();
  73.     extern char    *env_get();
  74.  
  75.     Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
  76.         getpid(), cmd, env_get(USERENV, u->envp), u->uid, u->gid))
  77.  
  78.     /* fork to become asynchronous -- parent process is done immediately,
  79.      * and continues to run the normal cron code, which means return to
  80.      * tick().  the child and grandchild don't leave this function, alive.
  81.      *
  82.      * vfork() is unsuitable, since we have much to do, and the parent
  83.      * needs to be able to run off and fork other processes.
  84.      */
  85.     switch (fork())
  86.     {
  87.     case -1:
  88.         log_it("CROND",getpid(),"error","can't fork");
  89.         break;
  90.     case 0:
  91.         /* child process */
  92.         child_process(cmd, u);
  93.         Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
  94.         _exit(OK_EXIT);
  95.         break;
  96.     }
  97.     Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
  98. }
  99.  
  100.  
  101. static void
  102. child_process(cmd, u)
  103.     char    *cmd;
  104.     user    *u;
  105. {
  106.     extern struct passwd    *getpwnam();
  107.     extern void    sigpipe_func(), be_different(), log_it();
  108.     extern int    VFORK();
  109.     extern char    *index(), *env_get();
  110.  
  111.     auto int    stdin_pipe[2], stdout_pipe[2];
  112.     register char    *input_data, *usernm, *mailto;
  113.     auto int    children = 0;
  114. #if defined(sequent)
  115.     extern void    do_univ();
  116. #endif
  117.  
  118.     Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), cmd))
  119.  
  120.     /* mark ourselves as different to PS command watchers by upshifting
  121.      * our program name.  This has no effect on some kernels.
  122.      */
  123.     {
  124.         register char    *pch;
  125.  
  126.         for (pch = ProgramName;  *pch;  pch++)
  127.             *pch = MkUpper(*pch);
  128.     }
  129.  
  130.     /* discover some useful and important environment settings
  131.      */
  132.     usernm = env_get(USERENV, u->envp);
  133.     mailto = env_get("MAILTO", u->envp);
  134.  
  135. #if defined(BSD)
  136.     /* our parent is watching for our death by catching SIGCHLD.  we
  137.      * do not care to watch for our children's deaths this way -- we
  138.      * use wait() explictly.  so we have to disable the signal (which
  139.      * was inherited from the parent).
  140.      *
  141.      * this isn't needed for system V, since our parent is already
  142.      * SIG_IGN on SIGCLD -- which, hopefully, will cause children to
  143.      * simply vanish when they die.
  144.      */
  145.     (void) signal(SIGCHLD, SIG_IGN);
  146. #endif /*BSD*/
  147.  
  148.     /* create some pipes to talk to our future child
  149.      */
  150.     pipe(stdin_pipe);    /* child's stdin */
  151.     pipe(stdout_pipe);    /* child's stdout */
  152.     
  153.     /* since we are a forked process, we can diddle the command string
  154.      * we were passed -- nobody else is going to use it again, right?
  155.      *
  156.      * if a % is present in the command, previous characters are the
  157.      * command, and subsequent characters are the additional input to
  158.      * the command.  Subsequent %'s will be transformed into newlines,
  159.      * but that happens later.
  160.      */
  161.     if (NULL == (input_data = index(cmd, '%')))
  162.     {
  163.         /* no %.  point input_data at a null string.
  164.          */
  165.         input_data = "";
  166.     }
  167.     else
  168.     {
  169.         /* % found.  replace with a null (remember, we're a forked
  170.          * process and the string won't be reused), and increment
  171.          * input_data to point at the following character.
  172.          */
  173.         *input_data++ = '\0';
  174.     }
  175.  
  176.     /* fork again, this time so we can exec the user's command.  Vfork()
  177.      * is okay this time, since we are going to exec() pretty quickly.
  178.      * I'm assuming that closing pipe ends &whatnot will not affect our
  179.      * suspended pseudo-parent/alter-ego.
  180.      */
  181.     if (VFORK() == 0)
  182.     {
  183.         Debug(DPROC, ("[%d] grandchild process VFORK()'ed\n", getpid()))
  184.  
  185.         /* write a log message.  we've waited this long to do it
  186.          * because it was not until now that we knew the PID that
  187.          * the actual user command shell was going to get and the
  188.          * PID is part of the log message.
  189.          */
  190. #ifdef LOG_FILE
  191.         {
  192.             extern char *mkprints();
  193.             char *x = mkprints(cmd, strlen(cmd));
  194.  
  195.             log_it(usernm, getpid(), "CMD", x);
  196.             free(x);
  197.         }
  198. #endif
  199.  
  200.         /* get new pgrp, void tty, etc.
  201.          */
  202.         be_different();
  203.  
  204.         /* close the pipe ends that we won't use.  this doesn't affect
  205.          * the parent, who has to read and write them; it keeps the
  206.          * kernel from recording us as a potential client TWICE --
  207.          * which would keep it from sending SIGPIPE in otherwise
  208.          * appropriate circumstances.
  209.          */
  210.         close(stdin_pipe[WRITE_PIPE]);
  211.         close(stdout_pipe[READ_PIPE]);
  212.  
  213.         /* grandchild process.  make std{in,out} be the ends of
  214.          * pipes opened by our daddy; make stderr go to stdout.
  215.          */
  216.         close(STDIN);    dup2(stdin_pipe[READ_PIPE], STDIN);
  217.         close(STDOUT);    dup2(stdout_pipe[WRITE_PIPE], STDOUT);
  218.         close(STDERR);    dup2(STDOUT, STDERR);
  219.  
  220.         /* close the pipes we just dup'ed.  The resources will remain,
  221.          * since they've been dup'ed... :-)...
  222.          */
  223.         close(stdin_pipe[READ_PIPE]);
  224.         close(stdout_pipe[WRITE_PIPE]);
  225.  
  226. # if defined(sequent)
  227.         /* set our login universe.  Do this in the grandchild
  228.          * so that the child can invoke /usr/lib/sendmail
  229.          * without surprises.
  230.          */
  231.         do_univ(u);
  232. # endif
  233.  
  234.         /* set our directory, uid and gid.  Set gid first, since once
  235.          * we set uid, we've lost root privledges.  (oops!)
  236.          */
  237.         setgid(u->gid);
  238. # if defined(BSD)
  239.         initgroups(env_get(USERENV, u->envp), u->gid);
  240. # endif
  241.         setuid(u->uid);        /* you aren't root after this... */
  242.         chdir(env_get("HOME", u->envp));
  243.  
  244.         /* exec the command.
  245.          */
  246.         {
  247.             char    *shell = env_get("SHELL", u->envp);
  248.  
  249. # if DEBUGGING
  250.             if (DebugFlags & DTEST) {
  251.                 fprintf(stderr,
  252.                 "debug DTEST is on, not exec'ing command.\n");
  253.                 fprintf(stderr,
  254.                 "\tcmd='%s' shell='%s'\n", cmd, shell);
  255.                 _exit(OK_EXIT);
  256.             }
  257. # endif /*DEBUGGING*/
  258.             /* normally you can't put debugging stuff here because
  259.              * it gets mailed with the command output.
  260.              */
  261.             /*
  262.             Debug(DPROC, ("[%d] execle('%s', '%s', -c, '%s')\n",
  263.                     getpid(), shell, shell, cmd))
  264.              */
  265.  
  266. # ifdef bad_idea
  267.             /* files writable by non-owner are a no-no
  268.              */
  269.             {
  270.                 struct stat sb;
  271.  
  272.                 if (0 != stat(cmd, &sb)) {
  273.                     fputs("crond: stat(2): ", stderr);
  274.                     perror(cmd);
  275.                     _exit(ERROR_EXIT);
  276.                 } else if (sb.st_mode & 022) {
  277.                     fprintf(stderr,
  278.                     "crond: %s writable by nonowner\n",
  279.                         cmd);
  280.                     _exit(ERROR_EXIT);
  281.                 } else if (sb.st_uid & 022) {
  282.                     fprintf(stderr,
  283.                     "crond: %s owned by uid %d\n",
  284.                         cmd, sb.st_uid);
  285.                     _exit(ERROR_EXIT);
  286.                 }
  287.             }
  288. # endif /*bad_idea*/
  289.  
  290.             execle(shell, shell, "-c", cmd, (char *)0, u->envp);
  291.             fprintf(stderr, "execl: couldn't exec `%s'\n", shell);
  292.             perror("execl");
  293.             _exit(ERROR_EXIT);
  294.         }
  295.     }
  296.  
  297.     children++;
  298.  
  299.     /* middle process, child of original cron, parent of process running
  300.      * the user's command.
  301.      */
  302.  
  303.     Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
  304.  
  305.     /* close the ends of the pipe that will only be referenced in the
  306.      * grandchild process...
  307.      */
  308.     close(stdin_pipe[READ_PIPE]);
  309.     close(stdout_pipe[WRITE_PIPE]);
  310.  
  311.     /*
  312.      * write, to the pipe connected to child's stdin, any input specified
  313.      * after a % in the crontab entry.  while we copy, convert any
  314.      * additional %'s to newlines.  when done, if some characters were
  315.      * written and the last one wasn't a newline, write a newline.
  316.      *
  317.      * Note that if the input data won't fit into one pipe buffer (2K
  318.      * or 4K on most BSD systems), and the child doesn't read its stdin,
  319.      * we would block here.  the solution, of course, is to fork again.
  320.      */
  321.  
  322.     if (*input_data && fork() == 0) {
  323.         register FILE    *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
  324.         register int    need_newline = FALSE;
  325.         register int    escaped = FALSE;
  326.         register int    ch;
  327.  
  328.         Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
  329.  
  330.         /* close the pipe we don't use, since we inherited it and
  331.          * are part of its reference count now.
  332.          */
  333.         close(stdout_pipe[READ_PIPE]);
  334.  
  335.         /* translation:
  336.          *    \% -> %
  337.          *    %  -> \n
  338.          *    \x -> \x    for all x != %
  339.          */
  340.         while (ch = *input_data++)
  341.         {
  342.             if (escaped) {
  343.                 if (ch != '%')
  344.                     putc('\\', out);
  345.             } else {
  346.                 if (ch == '%')
  347.                     ch = '\n';
  348.             }
  349.  
  350.             if (!(escaped = (ch == '\\'))) {
  351.                 putc(ch, out);
  352.                 need_newline = (ch != '\n');
  353.             }
  354.         }
  355.         if (escaped)
  356.             putc('\\', out);
  357.         if (need_newline)
  358.             putc('\n', out);
  359.  
  360.         /* close the pipe, causing an EOF condition.  fclose causes
  361.          * stdin_pipe[WRITE_PIPE] to be closed, too.
  362.          */
  363.         fclose(out);
  364.  
  365.         Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
  366.         exit(0);
  367.     }
  368.  
  369.     /* close the pipe to the grandkiddie's stdin, since its wicked uncle
  370.      * ernie back there has it open and will close it when he's done.
  371.      */
  372.     close(stdin_pipe[WRITE_PIPE]);
  373.  
  374.     children++;
  375.  
  376.     /*
  377.      * read output from the grandchild.  it's stderr has been redirected to
  378.      * it's stdout, which has been redirected to our pipe.  if there is any
  379.      * output, we'll be mailing it to the user whose crontab this is...
  380.      * when the grandchild exits, we'll get EOF.
  381.      */
  382.  
  383.     Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
  384.  
  385.     {
  386.         register FILE    *in = fdopen(stdout_pipe[READ_PIPE], "r");
  387.         register int    ch = getc(in);
  388.  
  389.         if (ch != EOF)
  390.         {
  391.             register FILE    *mail;
  392.             register int    bytes = 1;
  393.             union wait    status;
  394.  
  395.             Debug(DPROC|DEXT,
  396.                 ("[%d] got data (%x:%c) from grandchild\n",
  397.                     getpid(), ch, ch))
  398.  
  399.             /* get name of recipient.  this is MAILTO if set to a
  400.              * valid local username; USER otherwise.
  401.              */
  402.             if (mailto)
  403.             {
  404.                 /* MAILTO was present in the environment
  405.                  */
  406.                 if (!*mailto)
  407.                 {
  408.                     /* ... but it's empty. set to NULL
  409.                      */
  410.                     mailto = NULL;
  411.                 }
  412.             }
  413.             else
  414.             {
  415.                 /* MAILTO not present, set to USER.
  416.                  */
  417.                 mailto = usernm;
  418.             }
  419.         
  420.             /* if we are supposed to be mailing, MAILTO will
  421.              * be non-NULL.  only in this case should we set
  422.              * up the mail command and subjects and stuff...
  423.              */
  424.  
  425.             if (mailto)
  426.             {
  427.                 extern FILE    *popen();
  428.                 extern char    *sprintf(), *print_cmd();
  429.                 register char    **env;
  430.                 auto char    mailcmd[MAX_COMMAND];
  431.                 auto char    hostname[MAXHOSTNAMELEN];
  432.  
  433.                 (void) gethostname(hostname, MAXHOSTNAMELEN);
  434.                 (void) sprintf(mailcmd, MAILCMD, mailto);
  435.                 if (!(mail = popen(mailcmd, "w")))
  436.                 {
  437.                     perror(MAILCMD);
  438.                     (void) _exit(ERROR_EXIT);
  439.                 }
  440.                 fprintf(mail, "From: root (Cron Daemon)\n");
  441.                 fprintf(mail, "To: %s\n", mailto);
  442.                 fprintf(mail,
  443.                 "Subject: cron for %s@%s said this\n",
  444.                     usernm, first_word(hostname, ".")
  445.                 );
  446.                 fprintf(mail, "Date: %s", ctime(&TargetTime));
  447.                 fprintf(mail, "X-Cron-Cmd: <%s>\n", cmd);
  448.                 for (env = u->envp;  *env;  env++)
  449.                     fprintf(mail, "X-Cron-Env: <%s>\n",
  450.                         *env);
  451.                 fprintf(mail, "\n");
  452.  
  453.                 /* this was the first char from the pipe
  454.                  */
  455.                 putc(ch, mail);
  456.             }
  457.  
  458.             /* we have to read the input pipe no matter whether
  459.              * we mail or not, but obviously we only write to
  460.              * mail pipe if we ARE mailing.
  461.              */
  462.  
  463.             while (EOF != (ch = getc(in)))
  464.             {
  465.                 bytes++;
  466.                 if (mailto)
  467.                     putc(ch, mail);
  468.             }
  469.  
  470.             /* only close pipe if we opened it -- i.e., we're
  471.              * mailing...
  472.              */
  473.  
  474.             if (mailto) {
  475.                 Debug(DPROC, ("[%d] closing pipe to mail\n",
  476.                     getpid()))
  477.                 /* Note: the pclose will probably see
  478.                  * the termination of the grandchild
  479.                  * in addition to the mail process, since
  480.                  * it (the grandchild) is likely to exit
  481.                  * after closing its stdout.
  482.                  */
  483.                 status.w_status = pclose(mail);
  484.             }
  485.  
  486.             /* if there was output and we could not mail it,
  487.              * log the facts so the poor user can figure out
  488.              * what's going on.
  489.              */
  490.             if (mailto && status.w_status) {
  491.                 char buf[MAX_TEMPSTR];
  492.  
  493.                 sprintf(buf,
  494.             "mailed %d byte%s of output but got status 0x%04x\n",
  495.                     bytes, (bytes==1)?"":"s",
  496.                     status.w_status);
  497.                 log_it(usernm, getpid(), "MAIL", buf);
  498.             }
  499.  
  500.         } /*if data from grandchild*/
  501.  
  502.         Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
  503.  
  504.         fclose(in);    /* also closes stdout_pipe[READ_PIPE] */
  505.     }
  506.  
  507. #if defined(BSD)
  508.     /* wait for children to die.
  509.      */
  510.     for (;  children > 0;  children--)
  511.     {
  512.         int        pid;
  513.         union wait    waiter;
  514.  
  515.         Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
  516.             getpid(), children))
  517.         pid = wait(&waiter);
  518.         if (pid < OK) {
  519.             Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
  520.                 getpid()))
  521.             break;
  522.         }
  523.         Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
  524.             getpid(), pid, waiter.w_status))
  525.         if (waiter.w_coredump)
  526.             Debug(DPROC, (", dumped core"))
  527.         Debug(DPROC, ("\n"))
  528.     }
  529. #endif /*BSD*/
  530. }
  531.  
  532.  
  533. #if defined(sequent)
  534. /* Dynix (Sequent) hack to put the user associated with
  535.  * the passed user structure into the ATT universe if
  536.  * necessary.  We have to dig the gecos info out of
  537.  * the user's password entry to see if the magic
  538.  * "universe(att)" string is present.  If we do change
  539.  * the universe, also set "LOGNAME".
  540.  */
  541.  
  542. void
  543. do_univ(u)
  544.     user    *u;
  545. {
  546.     struct    passwd    *p;
  547.     char    *s;
  548.     int    i;
  549.     char    envstr[MAX_ENVSTR], **env_set();
  550.  
  551.     p = getpwuid(u->uid);
  552.     (void) endpwent();
  553.  
  554.     if (p == NULL)
  555.         return;
  556.  
  557.     s = p->pw_gecos;
  558.  
  559.     for (i = 0; i < 4; i++)
  560.     {
  561.         if ((s = index(s, ',')) == NULL)
  562.             return;
  563.         s++;
  564.     }
  565.     if (strcmp(s, "universe(att)"))
  566.         return;
  567.  
  568.     (void) sprintf(envstr, "LOGNAME=%s", p->pw_name);
  569.     u->envp = env_set(u->envp, envstr);
  570.  
  571.     (void) universe(U_ATT);
  572. }
  573. #endif
  574.