home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume23 / vixie-cron / part03 / do_command.c next >
Encoding:
C/C++ Source or Header  |  1990-10-09  |  14.6 KB  |  573 lines

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