diff options
| author | svn2git <svn2git@FreeBSD.org> | 1994-05-01 08:00:00 +0000 |
|---|---|---|
| committer | svn2git <svn2git@FreeBSD.org> | 1994-05-01 08:00:00 +0000 |
| commit | a16f65c7d117419bd266c28a1901ef129a337569 (patch) | |
| tree | 2626602f66dc3551e7a7c7bc9ad763c3bc7ab40a /usr.sbin/cron/do_command.c | |
| parent | 8503f4f13f77abf7adc8f7e329c6f9c1d52b6a20 (diff) | |
Diffstat (limited to 'usr.sbin/cron/do_command.c')
| -rw-r--r-- | usr.sbin/cron/do_command.c | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/usr.sbin/cron/do_command.c b/usr.sbin/cron/do_command.c new file mode 100644 index 000000000000..51a5dba919af --- /dev/null +++ b/usr.sbin/cron/do_command.c @@ -0,0 +1,502 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + * from Id: do_command.c,v 2.12 1994/01/15 20:43:43 vixie Exp + */ + +#if !defined(lint) && !defined(LINT) +static char rcsid[] = "$Header: /home/cvs/386BSD/src/usr.sbin/cron/do_command.c,v 1.1 1994/01/22 20:39:02 guido Exp $"; +#endif + + +#include "cron.h" +#include <sys/signal.h> +#if defined(sequent) +# include <sys/universe.h> +#endif +#if defined(SYSLOG) +# include <syslog.h> +#endif + + +static void child_process __P((entry *, user *)), + do_univ __P((user *)); + + +void +do_command(e, u) + entry *e; + user *u; +{ + Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", + getpid(), e->cmd, u->name, e->uid, e->gid)) + + /* fork to become asynchronous -- parent process is done immediately, + * and continues to run the normal cron code, which means return to + * tick(). the child and grandchild don't leave this function, alive. + * + * vfork() is unsuitable, since we have much to do, and the parent + * needs to be able to run off and fork other processes. + */ + switch (fork()) { + case -1: + log_it("CRON",getpid(),"error","can't fork"); + break; + case 0: + /* child process */ + acquire_daemonlock(1); + child_process(e, u); + Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) + _exit(OK_EXIT); + break; + default: + /* parent process */ + break; + } + Debug(DPROC, ("[%d] main process returning to work\n", getpid())) +} + + +static void +child_process(e, u) + entry *e; + user *u; +{ + int stdin_pipe[2], stdout_pipe[2]; + register char *input_data; + char *usernm, *mailto; + int children = 0; + + Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd)) + + /* mark ourselves as different to PS command watchers by upshifting + * our program name. This has no effect on some kernels. + */ + /*local*/{ + register char *pch; + + for (pch = ProgramName; *pch; pch++) + *pch = MkUpper(*pch); + } + + /* discover some useful and important environment settings + */ + usernm = env_get("LOGNAME", e->envp); + mailto = env_get("MAILTO", e->envp); + +#ifdef USE_SIGCHLD + /* our parent is watching for our death by catching SIGCHLD. we + * do not care to watch for our children's deaths this way -- we + * use wait() explictly. so we have to disable the signal (which + * was inherited from the parent). + */ + (void) signal(SIGCHLD, SIG_IGN); +#else + /* on system-V systems, we are ignoring SIGCLD. we have to stop + * ignoring it now or the wait() in cron_pclose() won't work. + * because of this, we have to wait() for our children here, as well. + */ + (void) signal(SIGCLD, SIG_DFL); +#endif /*BSD*/ + + /* create some pipes to talk to our future child + */ + pipe(stdin_pipe); /* child's stdin */ + pipe(stdout_pipe); /* child's stdout */ + + /* since we are a forked process, we can diddle the command string + * we were passed -- nobody else is going to use it again, right? + * + * if a % is present in the command, previous characters are the + * command, and subsequent characters are the additional input to + * the command. Subsequent %'s will be transformed into newlines, + * but that happens later. + */ + /*local*/{ + register int escaped = FALSE; + register int ch; + + for (input_data = e->cmd; ch = *input_data; input_data++) { + if (escaped) { + escaped = FALSE; + continue; + } + if (ch == '\\') { + escaped = TRUE; + continue; + } + if (ch == '%') { + *input_data++ = '\0'; + break; + } + } + } + + /* fork again, this time so we can exec the user's command. + */ + switch (vfork()) { + case -1: + log_it("CRON",getpid(),"error","can't vfork"); + exit(ERROR_EXIT); + /*NOTREACHED*/ + case 0: + Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n", + getpid())) + + /* write a log message. we've waited this long to do it + * because it was not until now that we knew the PID that + * the actual user command shell was going to get and the + * PID is part of the log message. + */ + /*local*/{ + char *x = mkprints((u_char *)e->cmd, strlen(e->cmd)); + + log_it(usernm, getpid(), "CMD", x); + free(x); + } + + /* that's the last thing we'll log. close the log files. + */ +#ifdef SYSLOG + closelog(); +#endif + + /* get new pgrp, void tty, etc. + */ + (void) setsid(); + + /* close the pipe ends that we won't use. this doesn't affect + * the parent, who has to read and write them; it keeps the + * kernel from recording us as a potential client TWICE -- + * which would keep it from sending SIGPIPE in otherwise + * appropriate circumstances. + */ + close(stdin_pipe[WRITE_PIPE]); + close(stdout_pipe[READ_PIPE]); + + /* grandchild process. make std{in,out} be the ends of + * pipes opened by our daddy; make stderr go to stdout. + */ + close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN); + close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT); + close(STDERR); dup2(STDOUT, STDERR); + + /* close the pipes we just dup'ed. The resources will remain. + */ + close(stdin_pipe[READ_PIPE]); + close(stdout_pipe[WRITE_PIPE]); + + /* set our login universe. Do this in the grandchild + * so that the child can invoke /usr/lib/sendmail + * without surprises. + */ + do_univ(u); + + /* set our directory, uid and gid. Set gid first, since once + * we set uid, we've lost root privledges. + */ + setgid(e->gid); +# if defined(BSD) + initgroups(env_get("LOGNAME", e->envp), e->gid); +# endif + setuid(e->uid); /* we aren't root after this... */ + chdir(env_get("HOME", e->envp)); + + /* exec the command. + */ + { + char *shell = env_get("SHELL", e->envp); + +# if DEBUGGING + if (DebugFlags & DTEST) { + fprintf(stderr, + "debug DTEST is on, not exec'ing command.\n"); + fprintf(stderr, + "\tcmd='%s' shell='%s'\n", e->cmd, shell); + _exit(OK_EXIT); + } +# endif /*DEBUGGING*/ + execle(shell, shell, "-c", e->cmd, (char *)0, e->envp); + fprintf(stderr, "execl: couldn't exec `%s'\n", shell); + perror("execl"); + _exit(ERROR_EXIT); + } + break; + default: + /* parent process */ + break; + } + + children++; + + /* middle process, child of original cron, parent of process running + * the user's command. + */ + + Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid())) + + /* close the ends of the pipe that will only be referenced in the + * grandchild process... + */ + close(stdin_pipe[READ_PIPE]); + close(stdout_pipe[WRITE_PIPE]); + + /* + * write, to the pipe connected to child's stdin, any input specified + * after a % in the crontab entry. while we copy, convert any + * additional %'s to newlines. when done, if some characters were + * written and the last one wasn't a newline, write a newline. + * + * Note that if the input data won't fit into one pipe buffer (2K + * or 4K on most BSD systems), and the child doesn't read its stdin, + * we would block here. thus we must fork again. + */ + + if (*input_data && fork() == 0) { + register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); + register int need_newline = FALSE; + register int escaped = FALSE; + register int ch; + + Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) + + /* close the pipe we don't use, since we inherited it and + * are part of its reference count now. + */ + close(stdout_pipe[READ_PIPE]); + + /* translation: + * \% -> % + * % -> \n + * \x -> \x for all x != % + */ + while (ch = *input_data++) { + if (escaped) { + if (ch != '%') + putc('\\', out); + } else { + if (ch == '%') + ch = '\n'; + } + + if (!(escaped = (ch == '\\'))) { + putc(ch, out); + need_newline = (ch != '\n'); + } + } + if (escaped) + putc('\\', out); + if (need_newline) + putc('\n', out); + + /* close the pipe, causing an EOF condition. fclose causes + * stdin_pipe[WRITE_PIPE] to be closed, too. + */ + fclose(out); + + Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid())) + exit(0); + } + + /* close the pipe to the grandkiddie's stdin, since its wicked uncle + * ernie back there has it open and will close it when he's done. + */ + close(stdin_pipe[WRITE_PIPE]); + + children++; + + /* + * read output from the grandchild. it's stderr has been redirected to + * it's stdout, which has been redirected to our pipe. if there is any + * output, we'll be mailing it to the user whose crontab this is... + * when the grandchild exits, we'll get EOF. + */ + + Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) + + /*local*/{ + register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); + register int ch = getc(in); + + if (ch != EOF) { + register FILE *mail; + register int bytes = 1; + int status = 0; + + Debug(DPROC|DEXT, + ("[%d] got data (%x:%c) from grandchild\n", + getpid(), ch, ch)) + + /* get name of recipient. this is MAILTO if set to a + * valid local username; USER otherwise. + */ + if (mailto) { + /* MAILTO was present in the environment + */ + if (!*mailto) { + /* ... but it's empty. set to NULL + */ + mailto = NULL; + } + } else { + /* MAILTO not present, set to USER. + */ + mailto = usernm; + } + + /* if we are supposed to be mailing, MAILTO will + * be non-NULL. only in this case should we set + * up the mail command and subjects and stuff... + */ + + if (mailto) { + register char **env; + auto char mailcmd[MAX_COMMAND]; + auto char hostname[MAXHOSTNAMELEN]; + + (void) gethostname(hostname, MAXHOSTNAMELEN); + (void) sprintf(mailcmd, MAILARGS, + MAILCMD, mailto); + if (!(mail = cron_popen(mailcmd, "w"))) { + perror(MAILCMD); + (void) _exit(ERROR_EXIT); + } + fprintf(mail, "From: root (Cron Daemon)\n"); + fprintf(mail, "To: %s\n", mailto); + fprintf(mail, "Subject: Cron <%s@%s> %s\n", + usernm, first_word(hostname, "."), + e->cmd); +# if defined(MAIL_DATE) + fprintf(mail, "Date: %s\n", + arpadate(&TargetTime)); +# endif /* MAIL_DATE */ + for (env = e->envp; *env; env++) + fprintf(mail, "X-Cron-Env: <%s>\n", + *env); + fprintf(mail, "\n"); + + /* this was the first char from the pipe + */ + putc(ch, mail); + } + + /* we have to read the input pipe no matter whether + * we mail or not, but obviously we only write to + * mail pipe if we ARE mailing. + */ + + while (EOF != (ch = getc(in))) { + bytes++; + if (mailto) + putc(ch, mail); + } + + /* only close pipe if we opened it -- i.e., we're + * mailing... + */ + + if (mailto) { + Debug(DPROC, ("[%d] closing pipe to mail\n", + getpid())) + /* Note: the pclose will probably see + * the termination of the grandchild + * in addition to the mail process, since + * it (the grandchild) is likely to exit + * after closing its stdout. + */ + status = cron_pclose(mail); + } + + /* if there was output and we could not mail it, + * log the facts so the poor user can figure out + * what's going on. + */ + if (mailto && status) { + char buf[MAX_TEMPSTR]; + + sprintf(buf, + "mailed %d byte%s of output but got status 0x%04x\n", + bytes, (bytes==1)?"":"s", + status); + log_it(usernm, getpid(), "MAIL", buf); + } + + } /*if data from grandchild*/ + + Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid())) + + fclose(in); /* also closes stdout_pipe[READ_PIPE] */ + } + + /* wait for children to die. + */ + for (; children > 0; children--) + { + WAIT_T waiter; + PID_T pid; + + Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n", + getpid(), children)) + pid = wait(&waiter); + if (pid < OK) { + Debug(DPROC, ("[%d] no more grandchildren--mail written?\n", + getpid())) + break; + } + Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x", + getpid(), pid, WEXITSTATUS(waiter))) + if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) + Debug(DPROC, (", dumped core")) + Debug(DPROC, ("\n")) + } +} + + +static void +do_univ(u) + user *u; +{ +#if defined(sequent) +/* Dynix (Sequent) hack to put the user associated with + * the passed user structure into the ATT universe if + * necessary. We have to dig the gecos info out of + * the user's password entry to see if the magic + * "universe(att)" string is present. + */ + + struct passwd *p; + char *s; + int i; + + p = getpwuid(u->uid); + (void) endpwent(); + + if (p == NULL) + return; + + s = p->pw_gecos; + + for (i = 0; i < 4; i++) + { + if ((s = strchr(s, ',')) == NULL) + return; + s++; + } + if (strcmp(s, "universe(att)")) + return; + + (void) universe(U_ATT); +#endif +} |
