Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/cron/cron/do_command.c
/* Copyright 1988,1990,1993,1994 by Paul Vixie | /* Copyright 1988,1990,1993,1994 by Paul Vixie | ||||
* All rights reserved | * All rights reserved | ||||
*/ | |||||
/* | |||||
* Copyright (c) 1997 by Internet Software Consortium | |||||
* | * | ||||
* Distribute freely, except: don't remove my name from the source or | * Permission to use, copy, modify, and distribute this software for any | ||||
* documentation (don't take credit for my work), mark your changes (don't | * purpose with or without fee is hereby granted, provided that the above | ||||
* get me blamed for your possible bugs), don't alter or remove this | * copyright notice and this permission notice appear in all copies. | ||||
* 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 | * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS | ||||
* I'll try to keep a version up to date. I can be reached as follows: | * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES | ||||
* Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul | * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE | ||||
* CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||||
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR | |||||
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS | |||||
* ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS | |||||
* SOFTWARE. | |||||
*/ | */ | ||||
#if !defined(lint) && !defined(LINT) | #if !defined(lint) && !defined(LINT) | ||||
static const char rcsid[] = | static const char rcsid[] = | ||||
"$FreeBSD$"; | "$Id: do_command.c,v 1.3 1998/08/14 00:32:39 vixie Exp $"; | ||||
#endif | #endif | ||||
#include "cron.h" | #include "cron.h" | ||||
#include <sys/signal.h> | |||||
#if defined(sequent) | |||||
# include <sys/universe.h> | |||||
#endif | |||||
#if defined(SYSLOG) | |||||
# include <syslog.h> | |||||
#endif | |||||
#if defined(LOGIN_CAP) | #if defined(LOGIN_CAP) | ||||
# include <login_cap.h> | # include <login_cap.h> | ||||
#endif | #endif | ||||
#ifdef PAM | #ifdef PAM | ||||
# include <security/pam_appl.h> | # include <security/pam_appl.h> | ||||
# include <security/openpam.h> | # include <security/openpam.h> | ||||
#endif | #endif | ||||
static void child_process(entry *, user *); | static void child_process(entry *, user *); | ||||
static WAIT_T wait_on_child(PID_T, const char *); | static WAIT_T wait_on_child(PID_T, const char *); | ||||
extern char *environ; | extern char *environ; | ||||
void | void | ||||
do_command(entry *e, user *u) | do_command(entry *e, user *u) | ||||
{ | { | ||||
pid_t pid; | pid_t pid; | ||||
Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", | Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", | ||||
getpid(), e->cmd, u->name, e->uid, e->gid)) | getpid(), e->cmd, u->name, e->uid, e->gid)) | ||||
/* fork to become asynchronous -- parent process is done immediately, | /* fork to become asynchronous -- parent process is done immediately, | ||||
* and continues to run the normal cron code, which means return to | * and continues to run the normal cron code, which means return to | ||||
* tick(). the child and grandchild don't leave this function, alive. | * tick(). the child and grandchild don't leave this function, alive. | ||||
*/ | */ | ||||
switch ((pid = fork())) { | switch ((pid = fork())) { | ||||
case -1: | case -1: | ||||
log_it("CRON",getpid(),"error","can't fork"); | log_it("CRON", getpid(), "error", "can't fork"); | ||||
if (e->flags & INTERVAL) | if (e->flags & INTERVAL) | ||||
e->lastexit = time(NULL); | e->lastexit = time(NULL); | ||||
break; | break; | ||||
case 0: | case 0: | ||||
/* child process */ | /* child process */ | ||||
pidfile_close(pfh); | pidfile_close(pfh); | ||||
child_process(e, u); | child_process(e, u); | ||||
Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) | Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) | ||||
Show All 11 Lines | do_command(entry *e, user *u) | ||||
} | } | ||||
Debug(DPROC, ("[%d] main process returning to work\n", getpid())) | Debug(DPROC, ("[%d] main process returning to work\n", getpid())) | ||||
} | } | ||||
static void | static void | ||||
child_process(entry *e, user *u) | child_process(entry *e, user *u) | ||||
{ | { | ||||
int stdin_pipe[2], stdout_pipe[2]; | int stdin_pipe[2], stdout_pipe[2]; | ||||
register char *input_data; | char *input_data; | ||||
char *usernm, *mailto, *mailfrom; | const char *usernm, *mailto, *mailfrom; | ||||
PID_T jobpid, stdinjob, mailpid; | PID_T jobpid, stdinjob, mailpid; | ||||
register FILE *mail; | FILE *mail; | ||||
register int bytes = 1; | int bytes = 1; | ||||
int status = 0; | int status = 0; | ||||
const char *homedir = NULL; | const char *homedir = NULL; | ||||
# if defined(LOGIN_CAP) | # if defined(LOGIN_CAP) | ||||
struct passwd *pwd; | struct passwd *pwd; | ||||
login_cap_t *lc; | login_cap_t *lc; | ||||
# endif | # endif | ||||
Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd)) | Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd)) | ||||
/* mark ourselves as different to PS command watchers by upshifting | /* mark ourselves as different to PS command watchers by upshifting | ||||
* our program name. This has no effect on some kernels. | * our program name. This has no effect on some kernels. | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD) { | ||||
log_it(usernm, getpid(), "USER", "account unavailable"); | log_it(usernm, getpid(), "USER", "account unavailable"); | ||||
exit(ERROR_EXIT); | exit(ERROR_EXIT); | ||||
} | } | ||||
pam_end(pamh, pam_err); | pam_end(pamh, pam_err); | ||||
} | } | ||||
#endif | #endif | ||||
#ifdef USE_SIGCHLD | |||||
/* our parent is watching for our death by catching SIGCHLD. we | /* our parent is watching for our death by catching SIGCHLD. we | ||||
* do not care to watch for our children's deaths this way -- we | * do not care to watch for our children's deaths this way -- we | ||||
* use wait() explicitly. so we have to disable the signal (which | * use wait() explicitly. so we have to disable the signal (which | ||||
* was inherited from the parent). | * was inherited from the parent). | ||||
*/ | */ | ||||
(void) signal(SIGCHLD, SIG_DFL); | (void) signal(SIGCHLD, SIG_DFL); | ||||
#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 | /* create some pipes to talk to our future child | ||||
*/ | */ | ||||
if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) { | if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) { | ||||
log_it("CRON", getpid(), "error", "can't pipe"); | log_it("CRON", getpid(), "error", "can't pipe"); | ||||
exit(ERROR_EXIT); | exit(ERROR_EXIT); | ||||
} | } | ||||
/* since we are a forked process, we can diddle the command string | /* since we are a forked process, we can diddle the command string | ||||
* we were passed -- nobody else is going to use it again, right? | * we were passed -- nobody else is going to use it again, right? | ||||
* | * | ||||
* if a % is present in the command, previous characters are the | * if a % is present in the command, previous characters are the | ||||
* command, and subsequent characters are the additional input to | * command, and subsequent characters are the additional input to | ||||
* the command. Subsequent %'s will be transformed into newlines, | * the command. Subsequent %'s will be transformed into newlines, | ||||
* but that happens later. | * but that happens later. | ||||
* | * | ||||
* If there are escaped %'s, remove the escape character. | * If there are escaped %'s, remove the escape character. | ||||
*/ | */ | ||||
/*local*/{ | /*local*/{ | ||||
register int escaped = FALSE; | int escaped = FALSE; | ||||
register int ch; | int ch; | ||||
register char *p; | char *p; | ||||
for (input_data = p = e->cmd; (ch = *input_data); | for (input_data = p = e->cmd; | ||||
(ch = *input_data) != '\0'; | |||||
input_data++, p++) { | input_data++, p++) { | ||||
if (p != input_data) | if (p != input_data) | ||||
*p = ch; | *p = ch; | ||||
if (escaped) { | if (escaped) { | ||||
if (ch == '%' || ch == '\\') | if (ch == '%' || ch == '\\') | ||||
*--p = ch; | *--p = ch; | ||||
escaped = FALSE; | escaped = FALSE; | ||||
continue; | continue; | ||||
} | } | ||||
if (ch == '\\') { | if (ch == '\\') { | ||||
escaped = TRUE; | escaped = TRUE; | ||||
continue; | continue; | ||||
} | } | ||||
if (ch == '%') { | if (ch == '%') { | ||||
*input_data++ = '\0'; | *input_data++ = '\0'; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
*p = '\0'; | *p = '\0'; | ||||
} | } | ||||
/* fork again, this time so we can exec the user's command. | /* fork again, this time so we can exec the user's command. | ||||
*/ | */ | ||||
switch (jobpid = fork()) { | switch (jobpid = fork()) { | ||||
case -1: | case -1: | ||||
log_it("CRON",getpid(),"error","can't fork"); | log_it("CRON", getpid(), "error", "can't fork"); | ||||
exit(ERROR_EXIT); | exit(ERROR_EXIT); | ||||
/*NOTREACHED*/ | /*NOTREACHED*/ | ||||
case 0: | case 0: | ||||
Debug(DPROC, ("[%d] grandchild process fork()'ed\n", | Debug(DPROC, ("[%d] grandchild process fork()'ed\n", | ||||
getpid())) | getpid())) | ||||
if (e->uid == ROOT_UID) | if (e->uid == ROOT_UID) | ||||
Jitter = RootJitter; | Jitter = RootJitter; | ||||
▲ Show 20 Lines • Show All 78 Lines • ▼ Show 20 Lines | # endif | ||||
/* set our directory, uid and gid. Set gid first, | /* set our directory, uid and gid. Set gid first, | ||||
* since once we set uid, we've lost root privileges. | * since once we set uid, we've lost root privileges. | ||||
*/ | */ | ||||
if (setgid(e->gid) != 0) { | if (setgid(e->gid) != 0) { | ||||
log_it(usernm, getpid(), | log_it(usernm, getpid(), | ||||
"error", "setgid failed"); | "error", "setgid failed"); | ||||
_exit(ERROR_EXIT); | _exit(ERROR_EXIT); | ||||
} | } | ||||
# if defined(BSD) | |||||
if (initgroups(usernm, e->gid) != 0) { | if (initgroups(usernm, e->gid) != 0) { | ||||
log_it(usernm, getpid(), | log_it(usernm, getpid(), | ||||
"error", "initgroups failed"); | "error", "initgroups failed"); | ||||
_exit(ERROR_EXIT); | _exit(ERROR_EXIT); | ||||
} | } | ||||
# endif | |||||
if (setlogin(usernm) != 0) { | if (setlogin(usernm) != 0) { | ||||
log_it(usernm, getpid(), | log_it(usernm, getpid(), | ||||
"error", "setlogin failed"); | "error", "setlogin failed"); | ||||
_exit(ERROR_EXIT); | _exit(ERROR_EXIT); | ||||
} | } | ||||
if (setuid(e->uid) != 0) { | if (setuid(e->uid) != 0) { | ||||
log_it(usernm, getpid(), | log_it(usernm, getpid(), | ||||
"error", "setuid failed"); | "error", "setuid failed"); | ||||
▲ Show 20 Lines • Show All 102 Lines • ▼ Show 20 Lines | # endif /*DEBUGGING*/ | ||||
* written and the last one wasn't a newline, write a newline. | * 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 | * 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, | * or 4K on most BSD systems), and the child doesn't read its stdin, | ||||
* we would block here. thus we must fork again. | * we would block here. thus we must fork again. | ||||
*/ | */ | ||||
if (*input_data && (stdinjob = fork()) == 0) { | if (*input_data && (stdinjob = fork()) == 0) { | ||||
register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); | FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); | ||||
register int need_newline = FALSE; | int need_newline = FALSE; | ||||
register int escaped = FALSE; | int escaped = FALSE; | ||||
register int ch; | int ch; | ||||
if (out == NULL) { | if (out == NULL) { | ||||
warn("fdopen failed in child2"); | warn("fdopen failed in child2"); | ||||
_exit(ERROR_EXIT); | _exit(ERROR_EXIT); | ||||
} | } | ||||
Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) | Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) | ||||
/* close the pipe we don't use, since we inherited it and | /* close the pipe we don't use, since we inherited it and | ||||
* are part of its reference count now. | * are part of its reference count now. | ||||
*/ | */ | ||||
close(stdout_pipe[READ_PIPE]); | close(stdout_pipe[READ_PIPE]); | ||||
/* translation: | /* translation: | ||||
* \% -> % | * \% -> % | ||||
* % -> \n | * % -> \n | ||||
* \x -> \x for all x != % | * \x -> \x for all x != % | ||||
*/ | */ | ||||
while ((ch = *input_data++)) { | while ((ch = *input_data++) != '\0') { | ||||
if (escaped) { | if (escaped) { | ||||
if (ch != '%') | if (ch != '%') | ||||
putc('\\', out); | putc('\\', out); | ||||
} else { | } else { | ||||
if (ch == '%') | if (ch == '%') | ||||
ch = '\n'; | ch = '\n'; | ||||
} | } | ||||
Show All 26 Lines | # endif /*DEBUGGING*/ | ||||
* it's stdout, which has been redirected to our pipe. if there is any | * 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... | * output, we'll be mailing it to the user whose crontab this is... | ||||
* when the grandchild exits, we'll get EOF. | * when the grandchild exits, we'll get EOF. | ||||
*/ | */ | ||||
Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) | Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) | ||||
/*local*/{ | /*local*/{ | ||||
register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); | FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); | ||||
register int ch; | int ch; | ||||
if (in == NULL) { | if (in == NULL) { | ||||
warn("fdopen failed in child"); | warn("fdopen failed in child"); | ||||
_exit(ERROR_EXIT); | _exit(ERROR_EXIT); | ||||
} | } | ||||
mail = NULL; | mail = NULL; | ||||
Show All 19 Lines | if (ch != EOF) { | ||||
mailto = NULL; | mailto = NULL; | ||||
/* if we are supposed to be mailing, MAILTO will | /* if we are supposed to be mailing, MAILTO will | ||||
* be non-NULL. only in this case should we set | * be non-NULL. only in this case should we set | ||||
* up the mail command and subjects and stuff... | * up the mail command and subjects and stuff... | ||||
*/ | */ | ||||
if (mailto) { | if (mailto) { | ||||
register char **env; | char **env; | ||||
auto char mailcmd[MAX_COMMAND]; | char mailcmd[MAX_COMMAND]; | ||||
auto char hostname[MAXHOSTNAMELEN]; | char hostname[MAXHOSTNAMELEN]; | ||||
if (gethostname(hostname, MAXHOSTNAMELEN) == -1) | if (gethostname(hostname, MAXHOSTNAMELEN) == -1) | ||||
hostname[0] = '\0'; | hostname[0] = '\0'; | ||||
hostname[sizeof(hostname) - 1] = '\0'; | hostname[sizeof(hostname) - 1] = '\0'; | ||||
(void) snprintf(mailcmd, sizeof(mailcmd), | if (snprintf(mailcmd, sizeof(mailcmd), MAILFMT, | ||||
MAILARGS, MAILCMD); | MAILARG) >= sizeof(mailcmd)) { | ||||
warnx("mail command too long"); | |||||
(void) _exit(ERROR_EXIT); | |||||
} | |||||
if (!(mail = cron_popen(mailcmd, "w", e, &mailpid))) { | if (!(mail = cron_popen(mailcmd, "w", e, &mailpid))) { | ||||
warn("%s", MAILCMD); | warn("%s", mailcmd); | ||||
(void) _exit(ERROR_EXIT); | (void) _exit(ERROR_EXIT); | ||||
} | } | ||||
if (mailfrom == NULL || *mailfrom == '\0') | if (mailfrom == NULL || *mailfrom == '\0') | ||||
fprintf(mail, "From: Cron Daemon <%s@%s>\n", | fprintf(mail, "From: Cron Daemon <%s@%s>\n", | ||||
usernm, hostname); | usernm, hostname); | ||||
else | else | ||||
fprintf(mail, "From: Cron Daemon <%s>\n", | fprintf(mail, "From: Cron Daemon <%s>\n", | ||||
mailfrom); | mailfrom); | ||||
fprintf(mail, "To: %s\n", mailto); | fprintf(mail, "To: %s\n", mailto); | ||||
fprintf(mail, "Subject: Cron <%s@%s> %s\n", | fprintf(mail, "Subject: Cron <%s@%s> %s\n", | ||||
usernm, first_word(hostname, "."), | usernm, first_word(hostname, "."), | ||||
e->cmd); | e->cmd); | ||||
# if defined(MAIL_DATE) | #ifdef MAIL_DATE | ||||
fprintf(mail, "Date: %s\n", | fprintf(mail, "Date: %s\n", | ||||
arpadate(&TargetTime)); | arpadate(&TargetTime)); | ||||
# endif /* MAIL_DATE */ | #endif /*MAIL_DATE*/ | ||||
for (env = e->envp; *env; env++) | for (env = e->envp; *env; env++) | ||||
fprintf(mail, "X-Cron-Env: <%s>\n", | fprintf(mail, "X-Cron-Env: <%s>\n", | ||||
*env); | *env); | ||||
fprintf(mail, "\n"); | fprintf(mail, "\n"); | ||||
/* this was the first char from the pipe | /* this was the first char from the pipe | ||||
*/ | */ | ||||
putc(ch, mail); | putc(ch, mail); | ||||
Show All 16 Lines | #endif /*MAIL_DATE*/ | ||||
/* also closes stdout_pipe[READ_PIPE] */ | /* also closes stdout_pipe[READ_PIPE] */ | ||||
fclose(in); | fclose(in); | ||||
} | } | ||||
/* wait for children to die. | /* wait for children to die. | ||||
*/ | */ | ||||
if (jobpid > 0) { | if (jobpid > 0) { | ||||
WAIT_T waiter; | WAIT_T waiter; | ||||
waiter = wait_on_child(jobpid, "grandchild command job"); | waiter = wait_on_child(jobpid, "grandchild command job"); | ||||
/* If everything went well, and -n was set, _and_ we have mail, | /* If everything went well, and -n was set, _and_ we have mail, | ||||
* we won't be mailing... so shoot the messenger! | * we won't be mailing... so shoot the messenger! | ||||
*/ | */ | ||||
if (WIFEXITED(waiter) && WEXITSTATUS(waiter) == 0 | if (WIFEXITED(waiter) && WEXITSTATUS(waiter) == 0 | ||||
&& (e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR | && (e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR | ||||
&& mail) { | && mail) { | ||||
Debug(DPROC, ("[%d] %s executed successfully, mail suppressed\n", | Debug(DPROC, ("[%d] %s executed successfully, mail suppressed\n", | ||||
getpid(), "grandchild command job")) | getpid(), "grandchild command job")) | ||||
kill(mailpid, SIGKILL); | kill(mailpid, SIGKILL); | ||||
(void)fclose(mail); | (void)fclose(mail); | ||||
mail = NULL; | mail = NULL; | ||||
} | } | ||||
/* only close pipe if we opened it -- i.e., we're | /* only close pipe if we opened it -- i.e., we're | ||||
* mailing... | * mailing... | ||||
*/ | */ | ||||
if (mail) { | if (mail) { | ||||
Debug(DPROC, ("[%d] closing pipe to mail\n", | Debug(DPROC, ("[%d] closing pipe to mail\n", | ||||
getpid())) | getpid())) | ||||
/* Note: the pclose will probably see | /* Note: the pclose will probably see | ||||
Show All 20 Lines | if (jobpid > 0) { | ||||
} | } | ||||
} | } | ||||
if (*input_data && stdinjob > 0) | if (*input_data && stdinjob > 0) | ||||
wait_on_child(stdinjob, "grandchild stdinjob"); | wait_on_child(stdinjob, "grandchild stdinjob"); | ||||
} | } | ||||
static WAIT_T | static WAIT_T | ||||
wait_on_child(PID_T childpid, const char *name) { | wait_on_child(PID_T childpid, const char *name) | ||||
{ | |||||
WAIT_T waiter; | WAIT_T waiter; | ||||
PID_T pid; | PID_T pid; | ||||
Debug(DPROC, ("[%d] waiting for %s (%d) to finish\n", | Debug(DPROC, ("[%d] waiting for %s (%d) to finish\n", | ||||
getpid(), name, childpid)) | getpid(), name, childpid)) | ||||
#ifdef POSIX | #ifdef POSIX | ||||
while ((pid = waitpid(childpid, &waiter, 0)) < 0 && errno == EINTR) | while ((pid = waitpid(childpid, &waiter, 0)) < 0 && errno == EINTR) | ||||
#else | #else | ||||
while ((pid = wait4(childpid, &waiter, 0, NULL)) < 0 && errno == EINTR) | while ((pid = wait4(childpid, &waiter, 0, NULL)) < 0 && errno == EINTR) | ||||
Show All 14 Lines |