184f33deaSJordan K. Hubbard /* Copyright 1988,1990,1993,1994 by Paul Vixie
284f33deaSJordan K. Hubbard * All rights reserved
3*fe590ffeSEric van Gyzen */
4*fe590ffeSEric van Gyzen
5*fe590ffeSEric van Gyzen /*
6*fe590ffeSEric van Gyzen * Copyright (c) 1997 by Internet Software Consortium
784f33deaSJordan K. Hubbard *
8*fe590ffeSEric van Gyzen * Permission to use, copy, modify, and distribute this software for any
9*fe590ffeSEric van Gyzen * purpose with or without fee is hereby granted, provided that the above
10*fe590ffeSEric van Gyzen * copyright notice and this permission notice appear in all copies.
1184f33deaSJordan K. Hubbard *
12*fe590ffeSEric van Gyzen * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
13*fe590ffeSEric van Gyzen * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
14*fe590ffeSEric van Gyzen * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
15*fe590ffeSEric van Gyzen * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
16*fe590ffeSEric van Gyzen * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
17*fe590ffeSEric van Gyzen * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
18*fe590ffeSEric van Gyzen * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
19*fe590ffeSEric van Gyzen * SOFTWARE.
2084f33deaSJordan K. Hubbard */
2184f33deaSJordan K. Hubbard
2284f33deaSJordan K. Hubbard #if !defined(lint) && !defined(LINT)
23401e6468SPhilippe Charnier static const char rcsid[] =
24*fe590ffeSEric van Gyzen "$Id: do_command.c,v 1.3 1998/08/14 00:32:39 vixie Exp $";
2584f33deaSJordan K. Hubbard #endif
2684f33deaSJordan K. Hubbard
2784f33deaSJordan K. Hubbard #include "cron.h"
28b25b7bc1SDavid Nugent #if defined(LOGIN_CAP)
29b25b7bc1SDavid Nugent # include <login_cap.h>
30b25b7bc1SDavid Nugent #endif
31997c6eefSYaroslav Tykhiy #ifdef PAM
32997c6eefSYaroslav Tykhiy # include <security/pam_appl.h>
33997c6eefSYaroslav Tykhiy # include <security/openpam.h>
34997c6eefSYaroslav Tykhiy #endif
3584f33deaSJordan K. Hubbard
361709a13cSKyle Evans static void child_process(entry *, user *);
375b80de23SKyle Evans static WAIT_T wait_on_child(PID_T, const char *);
3884f33deaSJordan K. Hubbard
397466dbd6SKyle Evans extern char *environ;
407466dbd6SKyle Evans
4184f33deaSJordan K. Hubbard void
do_command(entry * e,user * u)42e93f27e3SJohn Baldwin do_command(entry *e, user *u)
4384f33deaSJordan K. Hubbard {
44a08d12d3SGleb Smirnoff pid_t pid;
45a08d12d3SGleb Smirnoff
4684f33deaSJordan K. Hubbard Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
4784f33deaSJordan K. Hubbard getpid(), e->cmd, u->name, e->uid, e->gid))
4884f33deaSJordan K. Hubbard
4984f33deaSJordan K. Hubbard /* fork to become asynchronous -- parent process is done immediately,
5084f33deaSJordan K. Hubbard * and continues to run the normal cron code, which means return to
5184f33deaSJordan K. Hubbard * tick(). the child and grandchild don't leave this function, alive.
5284f33deaSJordan K. Hubbard */
53a08d12d3SGleb Smirnoff switch ((pid = fork())) {
5484f33deaSJordan K. Hubbard case -1:
5584f33deaSJordan K. Hubbard log_it("CRON", getpid(), "error", "can't fork");
56a08d12d3SGleb Smirnoff if (e->flags & INTERVAL)
57a08d12d3SGleb Smirnoff e->lastexit = time(NULL);
5884f33deaSJordan K. Hubbard break;
5984f33deaSJordan K. Hubbard case 0:
6084f33deaSJordan K. Hubbard /* child process */
6178735592SPawel Jakub Dawidek pidfile_close(pfh);
6284f33deaSJordan K. Hubbard child_process(e, u);
6384f33deaSJordan K. Hubbard Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
6484f33deaSJordan K. Hubbard _exit(OK_EXIT);
6584f33deaSJordan K. Hubbard break;
6684f33deaSJordan K. Hubbard default:
6784f33deaSJordan K. Hubbard /* parent process */
68a08d12d3SGleb Smirnoff Debug(DPROC, ("[%d] main process forked child #%d, "
69a08d12d3SGleb Smirnoff "returning to work\n", getpid(), pid))
70a08d12d3SGleb Smirnoff if (e->flags & INTERVAL) {
71a08d12d3SGleb Smirnoff e->lastexit = 0;
72a08d12d3SGleb Smirnoff e->child = pid;
73a08d12d3SGleb Smirnoff }
7484f33deaSJordan K. Hubbard break;
7584f33deaSJordan K. Hubbard }
7684f33deaSJordan K. Hubbard Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
7784f33deaSJordan K. Hubbard }
7884f33deaSJordan K. Hubbard
7984f33deaSJordan K. Hubbard
8084f33deaSJordan K. Hubbard static void
child_process(entry * e,user * u)81e93f27e3SJohn Baldwin child_process(entry *e, user *u)
8284f33deaSJordan K. Hubbard {
8384f33deaSJordan K. Hubbard int stdin_pipe[2], stdout_pipe[2];
84*fe590ffeSEric van Gyzen char *input_data;
85*fe590ffeSEric van Gyzen const char *usernm, *mailto, *mailfrom;
865b80de23SKyle Evans PID_T jobpid, stdinjob, mailpid;
87*fe590ffeSEric van Gyzen FILE *mail;
88*fe590ffeSEric van Gyzen int bytes = 1;
895b80de23SKyle Evans int status = 0;
9089c7bb56SKyle Evans const char *homedir = NULL;
91b25b7bc1SDavid Nugent # if defined(LOGIN_CAP)
92c00d650fSPeter Wemm struct passwd *pwd;
930435c150SAndrey A. Chernov login_cap_t *lc;
94b25b7bc1SDavid Nugent # endif
9584f33deaSJordan K. Hubbard
9684f33deaSJordan K. Hubbard Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
9784f33deaSJordan K. Hubbard
9884f33deaSJordan K. Hubbard /* mark ourselves as different to PS command watchers by upshifting
9984f33deaSJordan K. Hubbard * our program name. This has no effect on some kernels.
10084f33deaSJordan K. Hubbard */
101c7517d5aSPeter Wemm setproctitle("running job");
10284f33deaSJordan K. Hubbard
10384f33deaSJordan K. Hubbard /* discover some useful and important environment settings
10484f33deaSJordan K. Hubbard */
10584f33deaSJordan K. Hubbard usernm = env_get("LOGNAME", e->envp);
10684f33deaSJordan K. Hubbard mailto = env_get("MAILTO", e->envp);
10712455a9eSKyle Evans mailfrom = env_get("MAILFROM", e->envp);
10884f33deaSJordan K. Hubbard
109997c6eefSYaroslav Tykhiy #ifdef PAM
110997c6eefSYaroslav Tykhiy /* use PAM to see if the user's account is available,
111997c6eefSYaroslav Tykhiy * i.e., not locked or expired or whatever. skip this
112997c6eefSYaroslav Tykhiy * for system tasks from /etc/crontab -- they can run
113997c6eefSYaroslav Tykhiy * as any user.
114997c6eefSYaroslav Tykhiy */
115997c6eefSYaroslav Tykhiy if (strcmp(u->name, SYS_NAME)) { /* not equal */
116997c6eefSYaroslav Tykhiy pam_handle_t *pamh = NULL;
117997c6eefSYaroslav Tykhiy int pam_err;
118997c6eefSYaroslav Tykhiy struct pam_conv pamc = {
119997c6eefSYaroslav Tykhiy .conv = openpam_nullconv,
120997c6eefSYaroslav Tykhiy .appdata_ptr = NULL
121587d674eSPedro F. Giffuni };
122997c6eefSYaroslav Tykhiy
123997c6eefSYaroslav Tykhiy Debug(DPROC, ("[%d] checking account with PAM\n", getpid()))
124997c6eefSYaroslav Tykhiy
125997c6eefSYaroslav Tykhiy /* u->name keeps crontab owner name while LOGNAME is the name
126997c6eefSYaroslav Tykhiy * of user to run command on behalf of. they should be the
127997c6eefSYaroslav Tykhiy * same for a task from a per-user crontab.
128997c6eefSYaroslav Tykhiy */
129997c6eefSYaroslav Tykhiy if (strcmp(u->name, usernm)) {
130997c6eefSYaroslav Tykhiy log_it(usernm, getpid(), "username ambiguity", u->name);
131997c6eefSYaroslav Tykhiy exit(ERROR_EXIT);
132997c6eefSYaroslav Tykhiy }
133997c6eefSYaroslav Tykhiy
134997c6eefSYaroslav Tykhiy pam_err = pam_start("cron", usernm, &pamc, &pamh);
135997c6eefSYaroslav Tykhiy if (pam_err != PAM_SUCCESS) {
136997c6eefSYaroslav Tykhiy log_it("CRON", getpid(), "error", "can't start PAM");
137997c6eefSYaroslav Tykhiy exit(ERROR_EXIT);
138997c6eefSYaroslav Tykhiy }
139997c6eefSYaroslav Tykhiy
140997c6eefSYaroslav Tykhiy pam_err = pam_acct_mgmt(pamh, PAM_SILENT);
141997c6eefSYaroslav Tykhiy /* Expired password shouldn't prevent the job from running. */
142997c6eefSYaroslav Tykhiy if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD) {
143997c6eefSYaroslav Tykhiy log_it(usernm, getpid(), "USER", "account unavailable");
144997c6eefSYaroslav Tykhiy exit(ERROR_EXIT);
145997c6eefSYaroslav Tykhiy }
146997c6eefSYaroslav Tykhiy
147997c6eefSYaroslav Tykhiy pam_end(pamh, pam_err);
148997c6eefSYaroslav Tykhiy }
149997c6eefSYaroslav Tykhiy #endif
150997c6eefSYaroslav Tykhiy
15184f33deaSJordan K. Hubbard /* our parent is watching for our death by catching SIGCHLD. we
15284f33deaSJordan K. Hubbard * do not care to watch for our children's deaths this way -- we
1533df5ecacSUlrich Spörlein * use wait() explicitly. so we have to disable the signal (which
15484f33deaSJordan K. Hubbard * was inherited from the parent).
15584f33deaSJordan K. Hubbard */
1565e4a74ecSAndrey A. Chernov (void) signal(SIGCHLD, SIG_DFL);
15784f33deaSJordan K. Hubbard
15884f33deaSJordan K. Hubbard /* create some pipes to talk to our future child
15984f33deaSJordan K. Hubbard */
160d9850281SPedro F. Giffuni if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) {
161d9850281SPedro F. Giffuni log_it("CRON", getpid(), "error", "can't pipe");
162d9850281SPedro F. Giffuni exit(ERROR_EXIT);
163d9850281SPedro F. Giffuni }
16484f33deaSJordan K. Hubbard
16584f33deaSJordan K. Hubbard /* since we are a forked process, we can diddle the command string
16684f33deaSJordan K. Hubbard * we were passed -- nobody else is going to use it again, right?
16784f33deaSJordan K. Hubbard *
16884f33deaSJordan K. Hubbard * if a % is present in the command, previous characters are the
16984f33deaSJordan K. Hubbard * command, and subsequent characters are the additional input to
17084f33deaSJordan K. Hubbard * the command. Subsequent %'s will be transformed into newlines,
17184f33deaSJordan K. Hubbard * but that happens later.
17286ed6de3SJoerg Wunsch *
17386ed6de3SJoerg Wunsch * If there are escaped %'s, remove the escape character.
17484f33deaSJordan K. Hubbard */
17584f33deaSJordan K. Hubbard /*local*/{
176*fe590ffeSEric van Gyzen int escaped = FALSE;
177*fe590ffeSEric van Gyzen int ch;
178*fe590ffeSEric van Gyzen char *p;
17984f33deaSJordan K. Hubbard
180*fe590ffeSEric van Gyzen for (input_data = p = e->cmd;
181*fe590ffeSEric van Gyzen (ch = *input_data) != '\0';
18286ed6de3SJoerg Wunsch input_data++, p++) {
18386ed6de3SJoerg Wunsch if (p != input_data)
18486ed6de3SJoerg Wunsch *p = ch;
18584f33deaSJordan K. Hubbard if (escaped) {
18686ed6de3SJoerg Wunsch if (ch == '%' || ch == '\\')
18786ed6de3SJoerg Wunsch *--p = ch;
18884f33deaSJordan K. Hubbard escaped = FALSE;
18984f33deaSJordan K. Hubbard continue;
19084f33deaSJordan K. Hubbard }
19184f33deaSJordan K. Hubbard if (ch == '\\') {
19284f33deaSJordan K. Hubbard escaped = TRUE;
19384f33deaSJordan K. Hubbard continue;
19484f33deaSJordan K. Hubbard }
19584f33deaSJordan K. Hubbard if (ch == '%') {
19684f33deaSJordan K. Hubbard *input_data++ = '\0';
19784f33deaSJordan K. Hubbard break;
19884f33deaSJordan K. Hubbard }
19984f33deaSJordan K. Hubbard }
20086ed6de3SJoerg Wunsch *p = '\0';
20184f33deaSJordan K. Hubbard }
20284f33deaSJordan K. Hubbard
20384f33deaSJordan K. Hubbard /* fork again, this time so we can exec the user's command.
20484f33deaSJordan K. Hubbard */
2059b367233SKyle Evans switch (jobpid = fork()) {
20684f33deaSJordan K. Hubbard case -1:
2079b367233SKyle Evans log_it("CRON", getpid(), "error", "can't fork");
20884f33deaSJordan K. Hubbard exit(ERROR_EXIT);
20984f33deaSJordan K. Hubbard /*NOTREACHED*/
21084f33deaSJordan K. Hubbard case 0:
2119b367233SKyle Evans Debug(DPROC, ("[%d] grandchild process fork()'ed\n",
21284f33deaSJordan K. Hubbard getpid()))
21384f33deaSJordan K. Hubbard
214f5896bafSYaroslav Tykhiy if (e->uid == ROOT_UID)
215f5896bafSYaroslav Tykhiy Jitter = RootJitter;
216f5896bafSYaroslav Tykhiy if (Jitter != 0) {
217f5896bafSYaroslav Tykhiy srandom(getpid());
218f5896bafSYaroslav Tykhiy sleep(random() % Jitter);
219f5896bafSYaroslav Tykhiy }
220f5896bafSYaroslav Tykhiy
22184f33deaSJordan K. Hubbard /* write a log message. we've waited this long to do it
22284f33deaSJordan K. Hubbard * because it was not until now that we knew the PID that
22384f33deaSJordan K. Hubbard * the actual user command shell was going to get and the
22484f33deaSJordan K. Hubbard * PID is part of the log message.
22584f33deaSJordan K. Hubbard */
2265b80de23SKyle Evans if ((e->flags & DONT_LOG) == 0) {
22784f33deaSJordan K. Hubbard char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
22884f33deaSJordan K. Hubbard
22984f33deaSJordan K. Hubbard log_it(usernm, getpid(), "CMD", x);
23084f33deaSJordan K. Hubbard free(x);
23184f33deaSJordan K. Hubbard }
23284f33deaSJordan K. Hubbard
23384f33deaSJordan K. Hubbard /* that's the last thing we'll log. close the log files.
23484f33deaSJordan K. Hubbard */
23584f33deaSJordan K. Hubbard #ifdef SYSLOG
23684f33deaSJordan K. Hubbard closelog();
23784f33deaSJordan K. Hubbard #endif
23884f33deaSJordan K. Hubbard
23984f33deaSJordan K. Hubbard /* get new pgrp, void tty, etc.
24084f33deaSJordan K. Hubbard */
24184f33deaSJordan K. Hubbard (void) setsid();
24284f33deaSJordan K. Hubbard
24384f33deaSJordan K. Hubbard /* close the pipe ends that we won't use. this doesn't affect
24484f33deaSJordan K. Hubbard * the parent, who has to read and write them; it keeps the
24584f33deaSJordan K. Hubbard * kernel from recording us as a potential client TWICE --
24684f33deaSJordan K. Hubbard * which would keep it from sending SIGPIPE in otherwise
24784f33deaSJordan K. Hubbard * appropriate circumstances.
24884f33deaSJordan K. Hubbard */
24984f33deaSJordan K. Hubbard close(stdin_pipe[WRITE_PIPE]);
25084f33deaSJordan K. Hubbard close(stdout_pipe[READ_PIPE]);
25184f33deaSJordan K. Hubbard
25284f33deaSJordan K. Hubbard /* grandchild process. make std{in,out} be the ends of
25384f33deaSJordan K. Hubbard * pipes opened by our daddy; make stderr go to stdout.
25484f33deaSJordan K. Hubbard */
25584f33deaSJordan K. Hubbard close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN);
25684f33deaSJordan K. Hubbard close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT);
25784f33deaSJordan K. Hubbard close(STDERR); dup2(STDOUT, STDERR);
25884f33deaSJordan K. Hubbard
25984f33deaSJordan K. Hubbard /* close the pipes we just dup'ed. The resources will remain.
26084f33deaSJordan K. Hubbard */
26184f33deaSJordan K. Hubbard close(stdin_pipe[READ_PIPE]);
26284f33deaSJordan K. Hubbard close(stdout_pipe[WRITE_PIPE]);
26384f33deaSJordan K. Hubbard
2647466dbd6SKyle Evans environ = NULL;
2657466dbd6SKyle Evans
266b25b7bc1SDavid Nugent # if defined(LOGIN_CAP)
2677466dbd6SKyle Evans /* Set user's entire context, but note that PATH will
2687466dbd6SKyle Evans * be overridden later
269b25b7bc1SDavid Nugent */
2700435c150SAndrey A. Chernov if ((pwd = getpwnam(usernm)) == NULL)
271c00d650fSPeter Wemm pwd = getpwuid(e->uid);
2720435c150SAndrey A. Chernov lc = NULL;
2730435c150SAndrey A. Chernov if (pwd != NULL) {
27489c7bb56SKyle Evans if (pwd->pw_dir != NULL
27589c7bb56SKyle Evans && pwd->pw_dir[0] != '\0') {
27689c7bb56SKyle Evans homedir = strdup(pwd->pw_dir);
27789c7bb56SKyle Evans if (homedir == NULL) {
27889c7bb56SKyle Evans warn("strdup");
27989c7bb56SKyle Evans _exit(ERROR_EXIT);
28089c7bb56SKyle Evans }
28189c7bb56SKyle Evans }
2820435c150SAndrey A. Chernov pwd->pw_gid = e->gid;
2830435c150SAndrey A. Chernov if (e->class != NULL)
2840435c150SAndrey A. Chernov lc = login_getclass(e->class);
2850435c150SAndrey A. Chernov }
2869efe2eccSPeter Wemm if (pwd &&
2870435c150SAndrey A. Chernov setusercontext(lc, pwd, e->uid,
2887466dbd6SKyle Evans LOGIN_SETALL) == 0)
2899efe2eccSPeter Wemm (void) endpwent();
2909efe2eccSPeter Wemm else {
291c00d650fSPeter Wemm /* fall back to the old method */
2929efe2eccSPeter Wemm (void) endpwent();
293c00d650fSPeter Wemm # endif
294c00d650fSPeter Wemm /* set our directory, uid and gid. Set gid first,
295708f27a1SMaxim Konovalov * since once we set uid, we've lost root privileges.
29684f33deaSJordan K. Hubbard */
297bb0aa1a5SMaxim Konovalov if (setgid(e->gid) != 0) {
298bb0aa1a5SMaxim Konovalov log_it(usernm, getpid(),
299bb0aa1a5SMaxim Konovalov "error", "setgid failed");
3009b367233SKyle Evans _exit(ERROR_EXIT);
301bb0aa1a5SMaxim Konovalov }
302bb0aa1a5SMaxim Konovalov if (initgroups(usernm, e->gid) != 0) {
303bb0aa1a5SMaxim Konovalov log_it(usernm, getpid(),
304bb0aa1a5SMaxim Konovalov "error", "initgroups failed");
3059b367233SKyle Evans _exit(ERROR_EXIT);
306bb0aa1a5SMaxim Konovalov }
307bb0aa1a5SMaxim Konovalov if (setlogin(usernm) != 0) {
308bb0aa1a5SMaxim Konovalov log_it(usernm, getpid(),
309bb0aa1a5SMaxim Konovalov "error", "setlogin failed");
3109b367233SKyle Evans _exit(ERROR_EXIT);
311bb0aa1a5SMaxim Konovalov }
312bb0aa1a5SMaxim Konovalov if (setuid(e->uid) != 0) {
313bb0aa1a5SMaxim Konovalov log_it(usernm, getpid(),
314bb0aa1a5SMaxim Konovalov "error", "setuid failed");
3159b367233SKyle Evans _exit(ERROR_EXIT);
316bb0aa1a5SMaxim Konovalov }
317bb0aa1a5SMaxim Konovalov /* we aren't root after this..*/
318c00d650fSPeter Wemm #if defined(LOGIN_CAP)
319c00d650fSPeter Wemm }
3203d99cebfSAndrey A. Chernov if (lc != NULL)
3213d99cebfSAndrey A. Chernov login_close(lc);
322b25b7bc1SDavid Nugent #endif
32384f33deaSJordan K. Hubbard
32489c7bb56SKyle Evans /* For compatibility, we chdir to the value of HOME if it was
32589c7bb56SKyle Evans * specified explicitly in the crontab file, but not if it was
32689c7bb56SKyle Evans * set in the environment by some other mechanism. We chdir to
32789c7bb56SKyle Evans * the homedir given by the pw entry otherwise.
32889c7bb56SKyle Evans *
32989c7bb56SKyle Evans * If !LOGIN_CAP, then HOME is always set in e->envp.
33089c7bb56SKyle Evans *
33189c7bb56SKyle Evans * XXX: probably should also consult PAM.
33289c7bb56SKyle Evans */
33389c7bb56SKyle Evans {
33489c7bb56SKyle Evans char *new_home = env_get("HOME", e->envp);
33589c7bb56SKyle Evans if (new_home != NULL && new_home[0] != '\0')
33689c7bb56SKyle Evans chdir(new_home);
33789c7bb56SKyle Evans else if (homedir != NULL)
33889c7bb56SKyle Evans chdir(homedir);
33989c7bb56SKyle Evans else
34089c7bb56SKyle Evans chdir("/");
34189c7bb56SKyle Evans }
34289c7bb56SKyle Evans
34389c7bb56SKyle Evans /* exec the command. Note that SHELL is not respected from
34489c7bb56SKyle Evans * either login.conf or pw_shell, only an explicit setting
34589c7bb56SKyle Evans * in the crontab. (default of _PATH_BSHELL is supplied when
34689c7bb56SKyle Evans * setting up the entry)
34784f33deaSJordan K. Hubbard */
34884f33deaSJordan K. Hubbard {
34984f33deaSJordan K. Hubbard char *shell = env_get("SHELL", e->envp);
3507466dbd6SKyle Evans char **p;
3517466dbd6SKyle Evans
35289c7bb56SKyle Evans /* Apply the environment from the entry, overriding
35389c7bb56SKyle Evans * existing values (this will always set LOGNAME and
35489c7bb56SKyle Evans * SHELL). putenv should not fail unless malloc does.
3557466dbd6SKyle Evans */
3567466dbd6SKyle Evans for (p = e->envp; *p; ++p) {
3577466dbd6SKyle Evans if (putenv(*p) != 0) {
3587466dbd6SKyle Evans warn("putenv");
3597466dbd6SKyle Evans _exit(ERROR_EXIT);
3607466dbd6SKyle Evans }
3617466dbd6SKyle Evans }
36284f33deaSJordan K. Hubbard
36389c7bb56SKyle Evans /* HOME in login.conf overrides pw, and HOME in the
36489c7bb56SKyle Evans * crontab overrides both. So set pw's value only if
36589c7bb56SKyle Evans * nothing was already set (overwrite==0).
36689c7bb56SKyle Evans */
36789c7bb56SKyle Evans if (homedir != NULL
36889c7bb56SKyle Evans && setenv("HOME", homedir, 0) < 0) {
36989c7bb56SKyle Evans warn("setenv(HOME)");
37089c7bb56SKyle Evans _exit(ERROR_EXIT);
37189c7bb56SKyle Evans }
37289c7bb56SKyle Evans
37389c7bb56SKyle Evans /* PATH in login.conf is respected, but the crontab
37489c7bb56SKyle Evans * overrides; set a default value only if nothing
37589c7bb56SKyle Evans * already set.
37689c7bb56SKyle Evans */
37789c7bb56SKyle Evans if (setenv("PATH", _PATH_DEFPATH, 0) < 0) {
37889c7bb56SKyle Evans warn("setenv(PATH)");
37989c7bb56SKyle Evans _exit(ERROR_EXIT);
38089c7bb56SKyle Evans }
38189c7bb56SKyle Evans
38284f33deaSJordan K. Hubbard # if DEBUGGING
38384f33deaSJordan K. Hubbard if (DebugFlags & DTEST) {
38484f33deaSJordan K. Hubbard fprintf(stderr,
38584f33deaSJordan K. Hubbard "debug DTEST is on, not exec'ing command.\n");
38684f33deaSJordan K. Hubbard fprintf(stderr,
38784f33deaSJordan K. Hubbard "\tcmd='%s' shell='%s'\n", e->cmd, shell);
38884f33deaSJordan K. Hubbard _exit(OK_EXIT);
38984f33deaSJordan K. Hubbard }
39084f33deaSJordan K. Hubbard # endif /*DEBUGGING*/
3917466dbd6SKyle Evans execl(shell, shell, "-c", e->cmd, (char *)NULL);
3927466dbd6SKyle Evans warn("execl: couldn't exec `%s'", shell);
39384f33deaSJordan K. Hubbard _exit(ERROR_EXIT);
39484f33deaSJordan K. Hubbard }
39584f33deaSJordan K. Hubbard break;
39684f33deaSJordan K. Hubbard default:
39784f33deaSJordan K. Hubbard /* parent process */
39884f33deaSJordan K. Hubbard break;
39984f33deaSJordan K. Hubbard }
40084f33deaSJordan K. Hubbard
40184f33deaSJordan K. Hubbard /* middle process, child of original cron, parent of process running
40284f33deaSJordan K. Hubbard * the user's command.
40384f33deaSJordan K. Hubbard */
40484f33deaSJordan K. Hubbard
40584f33deaSJordan K. Hubbard Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
40684f33deaSJordan K. Hubbard
40784f33deaSJordan K. Hubbard /* close the ends of the pipe that will only be referenced in the
40884f33deaSJordan K. Hubbard * grandchild process...
40984f33deaSJordan K. Hubbard */
41084f33deaSJordan K. Hubbard close(stdin_pipe[READ_PIPE]);
41184f33deaSJordan K. Hubbard close(stdout_pipe[WRITE_PIPE]);
41284f33deaSJordan K. Hubbard
41384f33deaSJordan K. Hubbard /*
41484f33deaSJordan K. Hubbard * write, to the pipe connected to child's stdin, any input specified
41584f33deaSJordan K. Hubbard * after a % in the crontab entry. while we copy, convert any
41684f33deaSJordan K. Hubbard * additional %'s to newlines. when done, if some characters were
41784f33deaSJordan K. Hubbard * written and the last one wasn't a newline, write a newline.
41884f33deaSJordan K. Hubbard *
41984f33deaSJordan K. Hubbard * Note that if the input data won't fit into one pipe buffer (2K
42084f33deaSJordan K. Hubbard * or 4K on most BSD systems), and the child doesn't read its stdin,
42184f33deaSJordan K. Hubbard * we would block here. thus we must fork again.
42284f33deaSJordan K. Hubbard */
42384f33deaSJordan K. Hubbard
4245b80de23SKyle Evans if (*input_data && (stdinjob = fork()) == 0) {
425*fe590ffeSEric van Gyzen FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
426*fe590ffeSEric van Gyzen int need_newline = FALSE;
427*fe590ffeSEric van Gyzen int escaped = FALSE;
428*fe590ffeSEric van Gyzen int ch;
42984f33deaSJordan K. Hubbard
43064ae78cbSGuy Helmer if (out == NULL) {
43164ae78cbSGuy Helmer warn("fdopen failed in child2");
43264ae78cbSGuy Helmer _exit(ERROR_EXIT);
43364ae78cbSGuy Helmer }
43464ae78cbSGuy Helmer
43584f33deaSJordan K. Hubbard Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
43684f33deaSJordan K. Hubbard
43784f33deaSJordan K. Hubbard /* close the pipe we don't use, since we inherited it and
43884f33deaSJordan K. Hubbard * are part of its reference count now.
43984f33deaSJordan K. Hubbard */
44084f33deaSJordan K. Hubbard close(stdout_pipe[READ_PIPE]);
44184f33deaSJordan K. Hubbard
44284f33deaSJordan K. Hubbard /* translation:
44384f33deaSJordan K. Hubbard * \% -> %
44484f33deaSJordan K. Hubbard * % -> \n
44584f33deaSJordan K. Hubbard * \x -> \x for all x != %
44684f33deaSJordan K. Hubbard */
447*fe590ffeSEric van Gyzen while ((ch = *input_data++) != '\0') {
44884f33deaSJordan K. Hubbard if (escaped) {
44984f33deaSJordan K. Hubbard if (ch != '%')
45084f33deaSJordan K. Hubbard putc('\\', out);
45184f33deaSJordan K. Hubbard } else {
45284f33deaSJordan K. Hubbard if (ch == '%')
45384f33deaSJordan K. Hubbard ch = '\n';
45484f33deaSJordan K. Hubbard }
45584f33deaSJordan K. Hubbard
45684f33deaSJordan K. Hubbard if (!(escaped = (ch == '\\'))) {
45784f33deaSJordan K. Hubbard putc(ch, out);
45884f33deaSJordan K. Hubbard need_newline = (ch != '\n');
45984f33deaSJordan K. Hubbard }
46084f33deaSJordan K. Hubbard }
46184f33deaSJordan K. Hubbard if (escaped)
46284f33deaSJordan K. Hubbard putc('\\', out);
46384f33deaSJordan K. Hubbard if (need_newline)
46484f33deaSJordan K. Hubbard putc('\n', out);
46584f33deaSJordan K. Hubbard
46684f33deaSJordan K. Hubbard /* close the pipe, causing an EOF condition. fclose causes
46784f33deaSJordan K. Hubbard * stdin_pipe[WRITE_PIPE] to be closed, too.
46884f33deaSJordan K. Hubbard */
46984f33deaSJordan K. Hubbard fclose(out);
47084f33deaSJordan K. Hubbard
47184f33deaSJordan K. Hubbard Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
47284f33deaSJordan K. Hubbard exit(0);
47384f33deaSJordan K. Hubbard }
47484f33deaSJordan K. Hubbard
47584f33deaSJordan K. Hubbard /* close the pipe to the grandkiddie's stdin, since its wicked uncle
47684f33deaSJordan K. Hubbard * ernie back there has it open and will close it when he's done.
47784f33deaSJordan K. Hubbard */
47884f33deaSJordan K. Hubbard close(stdin_pipe[WRITE_PIPE]);
47984f33deaSJordan K. Hubbard
48084f33deaSJordan K. Hubbard /*
48184f33deaSJordan K. Hubbard * read output from the grandchild. it's stderr has been redirected to
48284f33deaSJordan K. Hubbard * it's stdout, which has been redirected to our pipe. if there is any
48384f33deaSJordan K. Hubbard * output, we'll be mailing it to the user whose crontab this is...
48484f33deaSJordan K. Hubbard * when the grandchild exits, we'll get EOF.
48584f33deaSJordan K. Hubbard */
48684f33deaSJordan K. Hubbard
48784f33deaSJordan K. Hubbard Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
48884f33deaSJordan K. Hubbard
48984f33deaSJordan K. Hubbard /*local*/{
490*fe590ffeSEric van Gyzen FILE *in = fdopen(stdout_pipe[READ_PIPE], "r");
491*fe590ffeSEric van Gyzen int ch;
49284f33deaSJordan K. Hubbard
49364ae78cbSGuy Helmer if (in == NULL) {
49464ae78cbSGuy Helmer warn("fdopen failed in child");
49564ae78cbSGuy Helmer _exit(ERROR_EXIT);
49664ae78cbSGuy Helmer }
49764ae78cbSGuy Helmer
4986795e26bSKyle Evans mail = NULL;
4996795e26bSKyle Evans
5009be756b5SMike Silbersack ch = getc(in);
50184f33deaSJordan K. Hubbard if (ch != EOF) {
50284f33deaSJordan K. Hubbard Debug(DPROC|DEXT,
50384f33deaSJordan K. Hubbard ("[%d] got data (%x:%c) from grandchild\n",
50484f33deaSJordan K. Hubbard getpid(), ch, ch))
50584f33deaSJordan K. Hubbard
50684f33deaSJordan K. Hubbard /* get name of recipient. this is MAILTO if set to a
50784f33deaSJordan K. Hubbard * valid local username; USER otherwise.
50884f33deaSJordan K. Hubbard */
509b75634d2SDmitry Morozovsky if (mailto == NULL) {
510b75634d2SDmitry Morozovsky /* MAILTO not present, set to USER,
511b82cbe46SGordon Bergling * unless globally overridden.
51284f33deaSJordan K. Hubbard */
513b75634d2SDmitry Morozovsky if (defmailto)
514b75634d2SDmitry Morozovsky mailto = defmailto;
515b75634d2SDmitry Morozovsky else
51684f33deaSJordan K. Hubbard mailto = usernm;
51784f33deaSJordan K. Hubbard }
5181168e5f1SDmitry Morozovsky if (mailto && *mailto == '\0')
5191168e5f1SDmitry Morozovsky mailto = NULL;
52084f33deaSJordan K. Hubbard
52184f33deaSJordan K. Hubbard /* if we are supposed to be mailing, MAILTO will
52284f33deaSJordan K. Hubbard * be non-NULL. only in this case should we set
52384f33deaSJordan K. Hubbard * up the mail command and subjects and stuff...
52484f33deaSJordan K. Hubbard */
52584f33deaSJordan K. Hubbard
5261168e5f1SDmitry Morozovsky if (mailto) {
527*fe590ffeSEric van Gyzen char **env;
528*fe590ffeSEric van Gyzen char mailcmd[MAX_COMMAND];
529*fe590ffeSEric van Gyzen char hostname[MAXHOSTNAMELEN];
53084f33deaSJordan K. Hubbard
531713c03d5SPeter Wemm if (gethostname(hostname, MAXHOSTNAMELEN) == -1)
532713c03d5SPeter Wemm hostname[0] = '\0';
533713c03d5SPeter Wemm hostname[sizeof(hostname) - 1] = '\0';
534*fe590ffeSEric van Gyzen if (snprintf(mailcmd, sizeof(mailcmd), MAILFMT,
535*fe590ffeSEric van Gyzen MAILARG) >= sizeof(mailcmd)) {
536*fe590ffeSEric van Gyzen warnx("mail command too long");
537*fe590ffeSEric van Gyzen (void) _exit(ERROR_EXIT);
538*fe590ffeSEric van Gyzen }
5395b80de23SKyle Evans if (!(mail = cron_popen(mailcmd, "w", e, &mailpid))) {
540*fe590ffeSEric van Gyzen warn("%s", mailcmd);
54184f33deaSJordan K. Hubbard (void) _exit(ERROR_EXIT);
54284f33deaSJordan K. Hubbard }
54312455a9eSKyle Evans if (mailfrom == NULL || *mailfrom == '\0')
544713c03d5SPeter Wemm fprintf(mail, "From: Cron Daemon <%s@%s>\n",
545713c03d5SPeter Wemm usernm, hostname);
54612455a9eSKyle Evans else
54712455a9eSKyle Evans fprintf(mail, "From: Cron Daemon <%s>\n",
54812455a9eSKyle Evans mailfrom);
54984f33deaSJordan K. Hubbard fprintf(mail, "To: %s\n", mailto);
55084f33deaSJordan K. Hubbard fprintf(mail, "Subject: Cron <%s@%s> %s\n",
55184f33deaSJordan K. Hubbard usernm, first_word(hostname, "."),
55284f33deaSJordan K. Hubbard e->cmd);
553*fe590ffeSEric van Gyzen #ifdef MAIL_DATE
55484f33deaSJordan K. Hubbard fprintf(mail, "Date: %s\n",
55584f33deaSJordan K. Hubbard arpadate(&TargetTime));
55684f33deaSJordan K. Hubbard #endif /*MAIL_DATE*/
55784f33deaSJordan K. Hubbard for (env = e->envp; *env; env++)
55884f33deaSJordan K. Hubbard fprintf(mail, "X-Cron-Env: <%s>\n",
55984f33deaSJordan K. Hubbard *env);
56084f33deaSJordan K. Hubbard fprintf(mail, "\n");
56184f33deaSJordan K. Hubbard
56284f33deaSJordan K. Hubbard /* this was the first char from the pipe
56384f33deaSJordan K. Hubbard */
56484f33deaSJordan K. Hubbard putc(ch, mail);
56584f33deaSJordan K. Hubbard }
56684f33deaSJordan K. Hubbard
56784f33deaSJordan K. Hubbard /* we have to read the input pipe no matter whether
56884f33deaSJordan K. Hubbard * we mail or not, but obviously we only write to
56984f33deaSJordan K. Hubbard * mail pipe if we ARE mailing.
57084f33deaSJordan K. Hubbard */
57184f33deaSJordan K. Hubbard
57284f33deaSJordan K. Hubbard while (EOF != (ch = getc(in))) {
57384f33deaSJordan K. Hubbard bytes++;
5746795e26bSKyle Evans if (mail)
57584f33deaSJordan K. Hubbard putc(ch, mail);
57684f33deaSJordan K. Hubbard }
5775b80de23SKyle Evans }
5785b80de23SKyle Evans /*if data from grandchild*/
5795b80de23SKyle Evans
5805b80de23SKyle Evans Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
5815b80de23SKyle Evans
5825b80de23SKyle Evans /* also closes stdout_pipe[READ_PIPE] */
5835b80de23SKyle Evans fclose(in);
5845b80de23SKyle Evans }
5855b80de23SKyle Evans
5865b80de23SKyle Evans /* wait for children to die.
5875b80de23SKyle Evans */
5885b80de23SKyle Evans if (jobpid > 0) {
5895b80de23SKyle Evans WAIT_T waiter;
5905b80de23SKyle Evans
5915b80de23SKyle Evans waiter = wait_on_child(jobpid, "grandchild command job");
5925b80de23SKyle Evans
5935b80de23SKyle Evans /* If everything went well, and -n was set, _and_ we have mail,
5945b80de23SKyle Evans * we won't be mailing... so shoot the messenger!
5955b80de23SKyle Evans */
5965b80de23SKyle Evans if (WIFEXITED(waiter) && WEXITSTATUS(waiter) == 0
5975b80de23SKyle Evans && (e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR
5986795e26bSKyle Evans && mail) {
5995b80de23SKyle Evans Debug(DPROC, ("[%d] %s executed successfully, mail suppressed\n",
6005b80de23SKyle Evans getpid(), "grandchild command job"))
6015b80de23SKyle Evans kill(mailpid, SIGKILL);
6025b80de23SKyle Evans (void)fclose(mail);
6036795e26bSKyle Evans mail = NULL;
6045b80de23SKyle Evans }
6055b80de23SKyle Evans
60684f33deaSJordan K. Hubbard /* only close pipe if we opened it -- i.e., we're
60784f33deaSJordan K. Hubbard * mailing...
60884f33deaSJordan K. Hubbard */
60984f33deaSJordan K. Hubbard
6106795e26bSKyle Evans if (mail) {
61184f33deaSJordan K. Hubbard Debug(DPROC, ("[%d] closing pipe to mail\n",
61284f33deaSJordan K. Hubbard getpid()))
61384f33deaSJordan K. Hubbard /* Note: the pclose will probably see
61484f33deaSJordan K. Hubbard * the termination of the grandchild
61584f33deaSJordan K. Hubbard * in addition to the mail process, since
61684f33deaSJordan K. Hubbard * it (the grandchild) is likely to exit
61784f33deaSJordan K. Hubbard * after closing its stdout.
61884f33deaSJordan K. Hubbard */
61984f33deaSJordan K. Hubbard status = cron_pclose(mail);
62084f33deaSJordan K. Hubbard
62184f33deaSJordan K. Hubbard /* if there was output and we could not mail it,
62284f33deaSJordan K. Hubbard * log the facts so the poor user can figure out
62384f33deaSJordan K. Hubbard * what's going on.
62484f33deaSJordan K. Hubbard */
6255b80de23SKyle Evans if (status) {
62684f33deaSJordan K. Hubbard char buf[MAX_TEMPSTR];
62784f33deaSJordan K. Hubbard
628bdddbd2fSPaul Traina snprintf(buf, sizeof(buf),
62984f33deaSJordan K. Hubbard "mailed %d byte%s of output but got status 0x%04x\n",
63084f33deaSJordan K. Hubbard bytes, (bytes==1)?"":"s",
63184f33deaSJordan K. Hubbard status);
63284f33deaSJordan K. Hubbard log_it(usernm, getpid(), "MAIL", buf);
63384f33deaSJordan K. Hubbard }
6345b80de23SKyle Evans }
63584f33deaSJordan K. Hubbard }
63684f33deaSJordan K. Hubbard
6375b80de23SKyle Evans if (*input_data && stdinjob > 0)
6385b80de23SKyle Evans wait_on_child(stdinjob, "grandchild stdinjob");
6395b80de23SKyle Evans }
6405b80de23SKyle Evans
6415b80de23SKyle Evans static WAIT_T
wait_on_child(PID_T childpid,const char * name)642*fe590ffeSEric van Gyzen wait_on_child(PID_T childpid, const char *name)
643*fe590ffeSEric van Gyzen {
64484f33deaSJordan K. Hubbard WAIT_T waiter;
64584f33deaSJordan K. Hubbard PID_T pid;
64684f33deaSJordan K. Hubbard
6475b80de23SKyle Evans Debug(DPROC, ("[%d] waiting for %s (%d) to finish\n",
6485b80de23SKyle Evans getpid(), name, childpid))
6495b80de23SKyle Evans
6505b80de23SKyle Evans #ifdef POSIX
6515b80de23SKyle Evans while ((pid = waitpid(childpid, &waiter, 0)) < 0 && errno == EINTR)
6525b80de23SKyle Evans #else
6535b80de23SKyle Evans while ((pid = wait4(childpid, &waiter, 0, NULL)) < 0 && errno == EINTR)
6545b80de23SKyle Evans #endif
6555b80de23SKyle Evans ;
6565b80de23SKyle Evans
6575b80de23SKyle Evans if (pid < OK)
6585b80de23SKyle Evans return waiter;
6595b80de23SKyle Evans
6605b80de23SKyle Evans Debug(DPROC, ("[%d] %s (%d) finished, status=%04x",
6615b80de23SKyle Evans getpid(), name, pid, WEXITSTATUS(waiter)))
66284f33deaSJordan K. Hubbard if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
66384f33deaSJordan K. Hubbard Debug(DPROC, (", dumped core"))
66484f33deaSJordan K. Hubbard Debug(DPROC, ("\n"))
6655b80de23SKyle Evans
6665b80de23SKyle Evans return waiter;
66784f33deaSJordan K. Hubbard }
668