xref: /freebsd/usr.sbin/cron/cron/do_command.c (revision fe590ffe40f49fe09d8275fbf29f0d46c5b99dc7)
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