xref: /netbsd-src/external/bsd/cron/dist/do_command.c (revision cd1156524477b27e01412687e65720a208f78651)
1*cd115652Schristos /*	$NetBSD: do_command.c,v 1.15 2020/04/18 19:32:19 christos Exp $	*/
2032a4398Schristos 
30061c6a5Schristos /* Copyright 1988,1990,1993,1994 by Paul Vixie
40061c6a5Schristos  * All rights reserved
50061c6a5Schristos  */
60061c6a5Schristos 
70061c6a5Schristos /*
80061c6a5Schristos  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
90061c6a5Schristos  * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
100061c6a5Schristos  *
110061c6a5Schristos  * Permission to use, copy, modify, and distribute this software for any
120061c6a5Schristos  * purpose with or without fee is hereby granted, provided that the above
130061c6a5Schristos  * copyright notice and this permission notice appear in all copies.
140061c6a5Schristos  *
150061c6a5Schristos  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
160061c6a5Schristos  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
170061c6a5Schristos  * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
180061c6a5Schristos  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
190061c6a5Schristos  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
200061c6a5Schristos  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
210061c6a5Schristos  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
220061c6a5Schristos  */
23032a4398Schristos #include <sys/cdefs.h>
240061c6a5Schristos #if !defined(lint) && !defined(LINT)
25032a4398Schristos #if 0
260061c6a5Schristos static char rcsid[] = "Id: do_command.c,v 1.9 2004/01/23 18:56:42 vixie Exp";
27032a4398Schristos #else
28*cd115652Schristos __RCSID("$NetBSD: do_command.c,v 1.15 2020/04/18 19:32:19 christos Exp $");
29032a4398Schristos #endif
300061c6a5Schristos #endif
310061c6a5Schristos 
320061c6a5Schristos #include "cron.h"
33032a4398Schristos #include <unistd.h>
340061c6a5Schristos 
35065057e6Schristos static int		child_process(entry *);
360061c6a5Schristos static int		safe_p(const char *, const char *);
370061c6a5Schristos 
38*cd115652Schristos pid_t
do_command(entry * e,user * u)390061c6a5Schristos do_command(entry *e, user *u) {
40065057e6Schristos 	int retval;
41065057e6Schristos 
420061c6a5Schristos 	Debug(DPROC, ("[%ld] do_command(%s, (%s,%ld,%ld))\n",
430061c6a5Schristos 		      (long)getpid(), e->cmd, u->name,
44032a4398Schristos 		      (long)e->pwd->pw_uid, (long)e->pwd->pw_gid));
450061c6a5Schristos 
460061c6a5Schristos 	/* fork to become asynchronous -- parent process is done immediately,
470061c6a5Schristos 	 * and continues to run the normal cron code, which means return to
480061c6a5Schristos 	 * tick().  the child and grandchild don't leave this function, alive.
490061c6a5Schristos 	 *
500061c6a5Schristos 	 * vfork() is unsuitable, since we have much to do, and the parent
510061c6a5Schristos 	 * needs to be able to run off and fork other processes.
520061c6a5Schristos 	 */
53fdf2ea13Schristos 
54fdf2ea13Schristos 	pid_t	jobpid;
55fdf2ea13Schristos 	switch (jobpid = fork()) {
560061c6a5Schristos 	case -1:
570061c6a5Schristos 		log_it("CRON", getpid(), "error", "can't fork");
580061c6a5Schristos 		break;
590061c6a5Schristos 	case 0:
600061c6a5Schristos 		/* child process */
610061c6a5Schristos 		acquire_daemonlock(1);
62065057e6Schristos 		retval = child_process(e);
63065057e6Schristos 		Debug(DPROC, ("[%ld] child process done (rc=%d), exiting\n",
64065057e6Schristos 			      (long)getpid(), retval));
65065057e6Schristos 		_exit(retval);
660061c6a5Schristos 		break;
670061c6a5Schristos 	default:
680061c6a5Schristos 		/* parent process */
69*cd115652Schristos 		if ((e->flags & SINGLE_JOB) == 0)
70*cd115652Schristos 			jobpid = -1;
710061c6a5Schristos 		break;
720061c6a5Schristos 	}
73032a4398Schristos 	Debug(DPROC, ("[%ld] main process returning to work\n",(long)getpid()));
74*cd115652Schristos 
75*cd115652Schristos 	/* only return pid if a singleton */
76*cd115652Schristos 	return jobpid;
770061c6a5Schristos }
780061c6a5Schristos 
790061c6a5Schristos static void
sigchld_handler(int signo)80065057e6Schristos sigchld_handler(int signo) {
81065057e6Schristos 	for (;;) {
82065057e6Schristos 		WAIT_T waiter;
83065057e6Schristos 		PID_T pid = waitpid(-1, &waiter, WNOHANG);
84065057e6Schristos 
85065057e6Schristos 		switch (pid) {
86065057e6Schristos 		case -1:
87065057e6Schristos 			if (errno == EINTR)
88065057e6Schristos 				continue;
89065057e6Schristos 		case 0:
90065057e6Schristos 			return;
91065057e6Schristos 		default:
92065057e6Schristos 			break;
93065057e6Schristos 		}
94065057e6Schristos 	}
95065057e6Schristos }
96065057e6Schristos 
977e10eaceSchristos static void
write_data(char * volatile input_data,int * stdin_pipe,int * stdout_pipe)987e10eaceSchristos write_data(char *volatile input_data, int *stdin_pipe, int *stdout_pipe)
997e10eaceSchristos {
1007e10eaceSchristos 	FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
1017e10eaceSchristos 	int need_newline = FALSE;
1027e10eaceSchristos 	int escaped = FALSE;
1037e10eaceSchristos 	int ch;
1047e10eaceSchristos 
1057e10eaceSchristos 	Debug(DPROC, ("[%ld] child2 sending data to grandchild\n",
1067e10eaceSchristos 		      (long)getpid()));
1077e10eaceSchristos 
1087e10eaceSchristos #ifdef USE_PAM
1097e10eaceSchristos 	cron_pam_child_close();
1107e10eaceSchristos #else
1117e10eaceSchristos 	log_close();
1127e10eaceSchristos #endif
1137e10eaceSchristos 
1147e10eaceSchristos 	/* close the pipe we don't use, since we inherited it and
1157e10eaceSchristos 	 * are part of its reference count now.
1167e10eaceSchristos 	 */
1177e10eaceSchristos 	(void)close(stdout_pipe[READ_PIPE]);
1187e10eaceSchristos 
1197e10eaceSchristos 	/* translation:
1207e10eaceSchristos 	 *	\% -> %
1217e10eaceSchristos 	 *	%  -> \n
1227e10eaceSchristos 	 *	\x -> \x	for all x != %
1237e10eaceSchristos 	 */
1247e10eaceSchristos 	while ((ch = *input_data++) != '\0') {
1257e10eaceSchristos 		if (escaped) {
1267e10eaceSchristos 			if (ch != '%')
1277e10eaceSchristos 				(void)putc('\\', out);
1287e10eaceSchristos 		} else {
1297e10eaceSchristos 			if (ch == '%')
1307e10eaceSchristos 				ch = '\n';
1317e10eaceSchristos 		}
1327e10eaceSchristos 
1337e10eaceSchristos 		if (!(escaped = (ch == '\\'))) {
1347e10eaceSchristos 			(void)putc(ch, out);
1357e10eaceSchristos 			need_newline = (ch != '\n');
1367e10eaceSchristos 		}
1377e10eaceSchristos 	}
1387e10eaceSchristos 	if (escaped)
1397e10eaceSchristos 		(void)putc('\\', out);
1407e10eaceSchristos 	if (need_newline)
1417e10eaceSchristos 		(void)putc('\n', out);
1427e10eaceSchristos 
1437e10eaceSchristos 	/* close the pipe, causing an EOF condition.  fclose causes
1447e10eaceSchristos 	 * stdin_pipe[WRITE_PIPE] to be closed, too.
1457e10eaceSchristos 	 */
1467e10eaceSchristos 	(void)fclose(out);
1477e10eaceSchristos 
1487e10eaceSchristos 	Debug(DPROC, ("[%ld] child2 done sending to grandchild\n",
1497e10eaceSchristos 		      (long)getpid()));
1507e10eaceSchristos }
1517e10eaceSchristos 
1527e10eaceSchristos static int
read_data(entry * e,const char * mailto,const char * usernm,char ** envp,int * stdout_pipe,pid_t jobpid)1537e10eaceSchristos read_data(entry *e, const char *mailto, const char *usernm, char **envp,
154fdf2ea13Schristos     int *stdout_pipe, pid_t jobpid)
1557e10eaceSchristos {
1567e10eaceSchristos 	FILE	*in = fdopen(stdout_pipe[READ_PIPE], "r");
1577e10eaceSchristos 	FILE	*mail = NULL;
1587e10eaceSchristos 	int	bytes = 1;
1597e10eaceSchristos 	int	status = 0;
1607e10eaceSchristos 	int	ch = getc(in);
1617e10eaceSchristos 	int	retval = 0;
1627e10eaceSchristos 	sig_t	oldchld = NULL;
1637e10eaceSchristos 
1647e10eaceSchristos 	if (ch == EOF)
1657e10eaceSchristos 		goto out;
1667e10eaceSchristos 
1677e10eaceSchristos 	Debug(DPROC|DEXT, ("[%ld] got data (%x:%c) from grandchild\n",
1687e10eaceSchristos 	    (long)getpid(), ch, ch));
1697e10eaceSchristos 
1707e10eaceSchristos 	/* get name of recipient.  this is MAILTO if set to a
1717e10eaceSchristos 	 * valid local username; USER otherwise.
1727e10eaceSchristos 	 */
1737e10eaceSchristos 	if (mailto) {
1747e10eaceSchristos 		/* MAILTO was present in the environment
1757e10eaceSchristos 		 */
1767e10eaceSchristos 		if (!*mailto) {
1777e10eaceSchristos 			/* ... but it's empty. set to NULL
1787e10eaceSchristos 			 */
1797e10eaceSchristos 			mailto = NULL;
1807e10eaceSchristos 		}
1817e10eaceSchristos 	} else {
1827e10eaceSchristos 		/* MAILTO not present, set to USER.
1837e10eaceSchristos 		 */
1847e10eaceSchristos 		mailto = usernm;
1857e10eaceSchristos 	}
1867e10eaceSchristos 
1877e10eaceSchristos 	/*
1887e10eaceSchristos 	 * Unsafe, disable mailing.
1897e10eaceSchristos 	 */
190558326f7Schristos 	if (mailto && !safe_p(usernm, mailto))
1917e10eaceSchristos 		mailto = NULL;
1927e10eaceSchristos 
1937e10eaceSchristos 	/* if we are supposed to be mailing, MAILTO will
1947e10eaceSchristos 	 * be non-NULL.  only in this case should we set
1957e10eaceSchristos 	 * up the mail command and subjects and stuff...
1967e10eaceSchristos 	 */
1977e10eaceSchristos 
1987e10eaceSchristos 	if (mailto) {
1997e10eaceSchristos 		char	**env;
2007e10eaceSchristos 		char	mailcmd[MAX_COMMAND];
2017e10eaceSchristos 		char	hostname[MAXHOSTNAMELEN + 1];
2027e10eaceSchristos 
2037e10eaceSchristos 		(void)gethostname(hostname, MAXHOSTNAMELEN);
2047e10eaceSchristos 		if (strlens(MAILFMT, MAILARG, NULL) + 1 >= sizeof mailcmd) {
2057e10eaceSchristos 			log_it(usernm, getpid(), "MAIL", "mailcmd too long");
2067e10eaceSchristos 			retval = ERROR_EXIT;
2077e10eaceSchristos 			goto out;
2087e10eaceSchristos 		}
2097e10eaceSchristos 		(void)snprintf(mailcmd, sizeof(mailcmd), MAILFMT, MAILARG);
2107e10eaceSchristos 		oldchld = signal(SIGCHLD, SIG_DFL);
2117e10eaceSchristos 		if (!(mail = cron_popen(mailcmd, "w", e->pwd))) {
2127e10eaceSchristos 			log_itx(usernm, getpid(), "MAIL",
2137e10eaceSchristos 			    "cannot run `%s'", mailcmd);
2147e10eaceSchristos 			(void) signal(SIGCHLD, oldchld);
2157e10eaceSchristos 			retval = ERROR_EXIT;
2167e10eaceSchristos 			goto out;
2177e10eaceSchristos 		}
2187e10eaceSchristos 		(void)fprintf(mail, "From: root (Cron Daemon)\n");
2197e10eaceSchristos 		(void)fprintf(mail, "To: %s\n", mailto);
2207e10eaceSchristos 		(void)fprintf(mail, "Subject: Cron <%s@%s> %s\n",
2217e10eaceSchristos 		    usernm, hostname, e->cmd);
2227e10eaceSchristos 		(void)fprintf(mail, "Auto-Submitted: auto-generated\n");
2237e10eaceSchristos #ifdef MAIL_DATE
2247e10eaceSchristos 		(void)fprintf(mail, "Date: %s\n", arpadate(&StartTime));
2257e10eaceSchristos #endif /*MAIL_DATE*/
2267e10eaceSchristos 		for (env = envp;  *env;  env++)
2277e10eaceSchristos 			(void)fprintf(mail, "X-Cron-Env: <%s>\n", *env);
2287e10eaceSchristos 		(void)fprintf(mail, "\n");
2297e10eaceSchristos 
2307e10eaceSchristos 		/* this was the first char from the pipe
2317e10eaceSchristos 		 */
2327e10eaceSchristos 		(void)putc(ch, mail);
2337e10eaceSchristos 	}
2347e10eaceSchristos 
2357e10eaceSchristos 	/* we have to read the input pipe no matter whether
2367e10eaceSchristos 	 * we mail or not, but obviously we only write to
2377e10eaceSchristos 	 * mail pipe if we ARE mailing.
2387e10eaceSchristos 	 */
2397e10eaceSchristos 
2407e10eaceSchristos 	while (EOF != (ch = getc(in))) {
2417e10eaceSchristos 		bytes++;
2427e10eaceSchristos 		if (mailto)
2437e10eaceSchristos 			(void)putc(ch, mail);
2447e10eaceSchristos 	}
2457e10eaceSchristos 
2467e10eaceSchristos 	/* only close pipe if we opened it -- i.e., we're
2477e10eaceSchristos 	 * mailing...
2487e10eaceSchristos 	 */
2497e10eaceSchristos 
2507e10eaceSchristos 	if (mailto) {
251fdf2ea13Schristos 		if (e->flags & MAIL_WHEN_ERR) {
252fdf2ea13Schristos 			int jstatus = -1;
253fdf2ea13Schristos 			if (jobpid <= 0)
254fdf2ea13Schristos 				log_it("CRON", getpid(), "error",
255fdf2ea13Schristos 				    "no job pid");
256fdf2ea13Schristos 			else {
257fdf2ea13Schristos 				while (waitpid(jobpid, &jstatus, WNOHANG) == -1)
258fdf2ea13Schristos 					if (errno != EINTR) {
259fdf2ea13Schristos 						log_it("CRON", getpid(),
260fdf2ea13Schristos 						    "error", "no job pid");
261fdf2ea13Schristos 						break;
262fdf2ea13Schristos 					}
263fdf2ea13Schristos 			}
264fdf2ea13Schristos 			/* If everything went well, and -n was set, _and_ we
265fdf2ea13Schristos 			 * have mail, we won't be mailing... so shoot the
266fdf2ea13Schristos 			 * messenger!
267fdf2ea13Schristos 			 */
268fdf2ea13Schristos 			if (WIFEXITED(jstatus) && WEXITSTATUS(jstatus) == 0) {
269fdf2ea13Schristos 				Debug(DPROC, ("[%ld] aborting pipe to mail\n",
270fdf2ea13Schristos 				    (long)getpid()));
271fdf2ea13Schristos 				status = cron_pabort(mail);
272fdf2ea13Schristos 				mailto = NULL;
273fdf2ea13Schristos 			}
274fdf2ea13Schristos 		}
275fdf2ea13Schristos 
276fdf2ea13Schristos 		if (mailto) {
277fdf2ea13Schristos 			Debug(DPROC, ("[%ld] closing pipe to mail\n",
278fdf2ea13Schristos 			    (long)getpid()));
2797e10eaceSchristos 			/* Note: the pclose will probably see
2807e10eaceSchristos 			 * the termination of the grandchild
2817e10eaceSchristos 			 * in addition to the mail process, since
2827e10eaceSchristos 			 * it (the grandchild) is likely to exit
2837e10eaceSchristos 			 * after closing its stdout.
2847e10eaceSchristos 			 */
2857e10eaceSchristos 			status = cron_pclose(mail);
286fdf2ea13Schristos 			mail = NULL;
287fdf2ea13Schristos 		}
2887e10eaceSchristos 		(void) signal(SIGCHLD, oldchld);
2897e10eaceSchristos 	}
2907e10eaceSchristos 
2917e10eaceSchristos 	/* if there was output and we could not mail it,
2927e10eaceSchristos 	 * log the facts so the poor user can figure out
2937e10eaceSchristos 	 * what's going on.
2947e10eaceSchristos 	 */
2957e10eaceSchristos 	if (mailto && status) {
2967e10eaceSchristos 		log_itx(usernm, getpid(), "MAIL",
2977e10eaceSchristos 		    "mailed %d byte%s of output to `%s' but"
2987e10eaceSchristos 		    " got status %#04x", bytes,
2997e10eaceSchristos 		    bytes == 1 ? "" : "s", mailto, status);
3007e10eaceSchristos 	}
3017e10eaceSchristos 
3027e10eaceSchristos out:
3037e10eaceSchristos 	Debug(DPROC, ("[%ld] got EOF from grandchild\n", (long)getpid()));
3047e10eaceSchristos 
3057e10eaceSchristos 	(void)fclose(in);	/* also closes stdout_pipe[READ_PIPE] */
3067e10eaceSchristos 	return retval;
3077e10eaceSchristos }
3087e10eaceSchristos 
309065057e6Schristos extern char **environ;
310065057e6Schristos static int
exec_user_command(entry * e,char ** envp,char * usernm,int * stdin_pipe,int * stdout_pipe,pid_t * jobpid)311bdb24028Schristos exec_user_command(entry *e, char **envp, char *usernm, int *stdin_pipe,
312fdf2ea13Schristos     int *stdout_pipe, pid_t *jobpid)
313bdb24028Schristos {
314bdb24028Schristos 	char *homedir;
315fdf2ea13Schristos 	char * volatile *ep;
3160061c6a5Schristos 
317fdf2ea13Schristos 	switch (*jobpid = vfork()) {
3180061c6a5Schristos 	case -1:
319bdb24028Schristos 		return -1;
3200061c6a5Schristos 	case 0:
321fdf2ea13Schristos 		ep = envp;
3220061c6a5Schristos 		Debug(DPROC, ("[%ld] grandchild process vfork()'ed\n",
323032a4398Schristos 			      (long)getpid()));
3240061c6a5Schristos 
3250061c6a5Schristos 		/* write a log message.  we've waited this long to do it
3260061c6a5Schristos 		 * because it was not until now that we knew the PID that
3270061c6a5Schristos 		 * the actual user command shell was going to get and the
3280061c6a5Schristos 		 * PID is part of the log message.
3290061c6a5Schristos 		 */
3300061c6a5Schristos 		if ((e->flags & DONT_LOG) == 0) {
331032a4398Schristos 			char *x = mkprints(e->cmd, strlen(e->cmd));
3320061c6a5Schristos 
333032a4398Schristos 			log_it(usernm, getpid(), "CMD START", x);
3340061c6a5Schristos 			free(x);
3350061c6a5Schristos 		}
3360061c6a5Schristos 
3370061c6a5Schristos 		/* that's the last thing we'll log.  close the log files.
3380061c6a5Schristos 		 */
3390061c6a5Schristos 		log_close();
3400061c6a5Schristos 
3410061c6a5Schristos 		/* get new pgrp, void tty, etc.
3420061c6a5Schristos 		 */
343032a4398Schristos 		if (setsid() == -1)
344032a4398Schristos 			syslog(LOG_ERR, "setsid() failure: %m");
3450061c6a5Schristos 
3460061c6a5Schristos 		/* close the pipe ends that we won't use.  this doesn't affect
3470061c6a5Schristos 		 * the parent, who has to read and write them; it keeps the
3480061c6a5Schristos 		 * kernel from recording us as a potential client TWICE --
3490061c6a5Schristos 		 * which would keep it from sending SIGPIPE in otherwise
3500061c6a5Schristos 		 * appropriate circumstances.
3510061c6a5Schristos 		 */
352032a4398Schristos 		(void)close(stdin_pipe[WRITE_PIPE]);
353032a4398Schristos 		(void)close(stdout_pipe[READ_PIPE]);
3540061c6a5Schristos 
3550061c6a5Schristos 		/* grandchild process.  make std{in,out} be the ends of
3560061c6a5Schristos 		 * pipes opened by our daddy; make stderr go to stdout.
3570061c6a5Schristos 		 */
3580061c6a5Schristos 		if (stdin_pipe[READ_PIPE] != STDIN) {
359032a4398Schristos 			(void)dup2(stdin_pipe[READ_PIPE], STDIN);
360032a4398Schristos 			(void)close(stdin_pipe[READ_PIPE]);
3610061c6a5Schristos 		}
3620061c6a5Schristos 		if (stdout_pipe[WRITE_PIPE] != STDOUT) {
363032a4398Schristos 			(void)dup2(stdout_pipe[WRITE_PIPE], STDOUT);
364032a4398Schristos 			(void)close(stdout_pipe[WRITE_PIPE]);
3650061c6a5Schristos 		}
366032a4398Schristos 		(void)dup2(STDOUT, STDERR);
3670061c6a5Schristos 
3680061c6a5Schristos 		/* set our directory, uid and gid.  Set gid first, since once
3690061c6a5Schristos 		 * we set uid, we've lost root privledges.
3700061c6a5Schristos 		 */
3710061c6a5Schristos #ifdef LOGIN_CAP
3720061c6a5Schristos 		{
3730061c6a5Schristos #ifdef BSD_AUTH
3740061c6a5Schristos 			auth_session_t *as;
3750061c6a5Schristos #endif
3760061c6a5Schristos 			login_cap_t *lc;
377032a4398Schristos 			char *p;
3780061c6a5Schristos 
3790061c6a5Schristos 			if ((lc = login_getclass(e->pwd->pw_class)) == NULL) {
380032a4398Schristos 				warnx("unable to get login class for `%s'",
3810061c6a5Schristos 				    e->pwd->pw_name);
3820061c6a5Schristos 				_exit(ERROR_EXIT);
3830061c6a5Schristos 			}
3840061c6a5Schristos 			if (setusercontext(lc, e->pwd, e->pwd->pw_uid, LOGIN_SETALL) < 0) {
385032a4398Schristos 				warnx("setusercontext failed for `%s'",
3860061c6a5Schristos 				    e->pwd->pw_name);
3870061c6a5Schristos 				_exit(ERROR_EXIT);
3880061c6a5Schristos 			}
3890061c6a5Schristos #ifdef BSD_AUTH
3900061c6a5Schristos 			as = auth_open();
3910061c6a5Schristos 			if (as == NULL || auth_setpwd(as, e->pwd) != 0) {
392032a4398Schristos 				warn("can't malloc");
3930061c6a5Schristos 				_exit(ERROR_EXIT);
3940061c6a5Schristos 			}
3950061c6a5Schristos 			if (auth_approval(as, lc, usernm, "cron") <= 0) {
396032a4398Schristos 				warnx("approval failed for `%s'",
3970061c6a5Schristos 				    e->pwd->pw_name);
3980061c6a5Schristos 				_exit(ERROR_EXIT);
3990061c6a5Schristos 			}
4000061c6a5Schristos 			auth_close(as);
4010061c6a5Schristos #endif /* BSD_AUTH */
4020061c6a5Schristos 			login_close(lc);
4030061c6a5Schristos 
4040061c6a5Schristos 			/* If no PATH specified in crontab file but
4050061c6a5Schristos 			 * we just added one via login.conf, add it to
4060061c6a5Schristos 			 * the crontab environment.
4070061c6a5Schristos 			 */
408065057e6Schristos 			if (env_get("PATH", envp) == NULL && environ != NULL) {
409032a4398Schristos 				if ((p = getenv("PATH")) != NULL)
410bdb24028Schristos 					ep = env_set(envp, p);
4110061c6a5Schristos 			}
4120061c6a5Schristos 		}
4130061c6a5Schristos #else
414032a4398Schristos 		if (setgid(e->pwd->pw_gid) != 0) {
415a362d640Schristos 			syslog(LOG_ERR, "setgid(%d) failed for %s: %m",
416a362d640Schristos 			    e->pwd->pw_gid, e->pwd->pw_name);
417032a4398Schristos 			_exit(ERROR_EXIT);
418032a4398Schristos 		}
419032a4398Schristos 		if (initgroups(usernm, e->pwd->pw_gid) != 0) {
420a362d640Schristos 			syslog(LOG_ERR, "initgroups(%s, %d) failed for %s: %m",
421a362d640Schristos 			    usernm, e->pwd->pw_gid, e->pwd->pw_name);
422032a4398Schristos 			_exit(ERROR_EXIT);
423032a4398Schristos 		}
4240061c6a5Schristos #if (defined(BSD)) && (BSD >= 199103)
425032a4398Schristos 		if (setlogin(usernm) < 0) {
426a362d640Schristos 			syslog(LOG_ERR, "setlogin(%s) failure for %s: %m",
427a362d640Schristos 			    usernm, e->pwd->pw_name);
428032a4398Schristos 			_exit(ERROR_EXIT);
429032a4398Schristos 		}
4300061c6a5Schristos #endif /* BSD */
431065057e6Schristos #ifdef USE_PAM
432065057e6Schristos 		if (!cron_pam_setcred())
433065057e6Schristos 			_exit(ERROR_EXIT);
434065057e6Schristos 		cron_pam_child_close();
435065057e6Schristos #endif
436032a4398Schristos 		if (setuid(e->pwd->pw_uid) != 0) {
437a362d640Schristos 			syslog(LOG_ERR, "setuid(%d) failed for %s: %m",
438a362d640Schristos 			    e->pwd->pw_uid, e->pwd->pw_name);
439032a4398Schristos 			_exit(ERROR_EXIT);
440032a4398Schristos 		}
441032a4398Schristos 		/* we aren't root after this... */
4420061c6a5Schristos #endif /* LOGIN_CAP */
443bdb24028Schristos 		homedir = env_get("HOME", __UNVOLATILE(ep));
444a362d640Schristos 		if (chdir(homedir) != 0) {
445a362d640Schristos 			syslog(LOG_ERR, "chdir(%s) $HOME failed for %s: %m",
446a362d640Schristos 			    homedir, e->pwd->pw_name);
447032a4398Schristos 			_exit(ERROR_EXIT);
448032a4398Schristos 		}
449032a4398Schristos 
450032a4398Schristos #ifdef USE_SIGCHLD
451032a4398Schristos 		/* our grandparent is watching for our death by catching
452032a4398Schristos 		 * SIGCHLD.  the parent is ignoring SIGCHLD's; we want
453032a4398Schristos 		 * to restore default behaviour.
454032a4398Schristos 		 */
455032a4398Schristos 		(void) signal(SIGCHLD, SIG_DFL);
456032a4398Schristos #endif
457065057e6Schristos 		(void) signal(SIGPIPE, SIG_DFL);
458065057e6Schristos 		(void) signal(SIGUSR1, SIG_DFL);
459032a4398Schristos 		(void) signal(SIGHUP, SIG_DFL);
4600061c6a5Schristos 
4610061c6a5Schristos 		/*
4620061c6a5Schristos 		 * Exec the command.
4630061c6a5Schristos 		 */
4640061c6a5Schristos 		{
465bdb24028Schristos 			char	*shell = env_get("SHELL", __UNVOLATILE(ep));
4660061c6a5Schristos 
4670061c6a5Schristos # if DEBUGGING
4680061c6a5Schristos 			if (DebugFlags & DTEST) {
469032a4398Schristos 				(void)fprintf(stderr,
4700061c6a5Schristos 				"debug DTEST is on, not exec'ing command.\n");
471032a4398Schristos 				(void)fprintf(stderr,
4720061c6a5Schristos 				"\tcmd='%s' shell='%s'\n", e->cmd, shell);
4730061c6a5Schristos 				_exit(OK_EXIT);
4740061c6a5Schristos 			}
4750061c6a5Schristos # endif /*DEBUGGING*/
476065057e6Schristos 			(void)execle(shell, shell, "-c", e->cmd, NULL, envp);
477032a4398Schristos 			warn("execl: couldn't exec `%s'", shell);
4780061c6a5Schristos 			_exit(ERROR_EXIT);
4790061c6a5Schristos 		}
480bdb24028Schristos 		return 0;
4810061c6a5Schristos 	default:
4820061c6a5Schristos 		/* parent process */
483bdb24028Schristos 		return 0;
484bdb24028Schristos 	}
485bdb24028Schristos }
486bdb24028Schristos 
487bdb24028Schristos static int
child_process(entry * e)488bdb24028Schristos child_process(entry *e) {
489bdb24028Schristos 	int stdin_pipe[2], stdout_pipe[2];
490bdb24028Schristos 	char * volatile input_data;
491bdb24028Schristos 	char *usernm, * volatile mailto;
492bdb24028Schristos 	struct sigaction sact;
493bdb24028Schristos 	char **envp = e->envp;
494bdb24028Schristos 	int retval = OK_EXIT;
495fdf2ea13Schristos 	pid_t jobpid = 0;
496bdb24028Schristos 
497bdb24028Schristos 	Debug(DPROC, ("[%ld] child_process('%s')\n", (long)getpid(), e->cmd));
498bdb24028Schristos 
499bdb24028Schristos 	setproctitle("running job");
500bdb24028Schristos 
501bdb24028Schristos 	/* discover some useful and important environment settings
502bdb24028Schristos 	 */
503bdb24028Schristos 	usernm = e->pwd->pw_name;
504bdb24028Schristos 	mailto = env_get("MAILTO", envp);
505bdb24028Schristos 
506bdb24028Schristos 	memset(&sact, 0, sizeof(sact));
507bdb24028Schristos 	sigemptyset(&sact.sa_mask);
508bdb24028Schristos 	sact.sa_flags = 0;
509bdb24028Schristos #ifdef SA_RESTART
510bdb24028Schristos 	sact.sa_flags |= SA_RESTART;
511bdb24028Schristos #endif
512bdb24028Schristos 	sact.sa_handler = sigchld_handler;
513bdb24028Schristos 	(void) sigaction(SIGCHLD, &sact, NULL);
514bdb24028Schristos 
515bdb24028Schristos 	/* create some pipes to talk to our future child
516bdb24028Schristos 	 */
517bdb24028Schristos 	if (pipe(stdin_pipe) == -1) 	/* child's stdin */
518bdb24028Schristos 		log_it("CRON", getpid(), "error", "create child stdin pipe");
519bdb24028Schristos 	if (pipe(stdout_pipe) == -1)	/* child's stdout */
520bdb24028Schristos 		log_it("CRON", getpid(), "error", "create child stdout pipe");
521bdb24028Schristos 
522bdb24028Schristos 	/* since we are a forked process, we can diddle the command string
523bdb24028Schristos 	 * we were passed -- nobody else is going to use it again, right?
524bdb24028Schristos 	 *
525bdb24028Schristos 	 * if a % is present in the command, previous characters are the
526bdb24028Schristos 	 * command, and subsequent characters are the additional input to
527bdb24028Schristos 	 * the command.  An escaped % will have the escape character stripped
528bdb24028Schristos 	 * from it.  Subsequent %'s will be transformed into newlines,
529bdb24028Schristos 	 * but that happens later.
530bdb24028Schristos 	 */
531bdb24028Schristos 	/*local*/{
532bdb24028Schristos 		int escaped = FALSE;
533bdb24028Schristos 		int ch;
534bdb24028Schristos 		char *p;
535bdb24028Schristos 
536bdb24028Schristos 		/* translation:
537bdb24028Schristos 		 *	\% -> %
538bdb24028Schristos 		 *	%  -> end of command, following is command input.
539bdb24028Schristos 		 *	\x -> \x	for all x != %
540bdb24028Schristos 		 */
541bdb24028Schristos 		input_data = p = e->cmd;
542bdb24028Schristos 		while ((ch = *input_data++) != '\0') {
543bdb24028Schristos  			if (escaped) {
544bdb24028Schristos 				if (ch != '%')
545bdb24028Schristos 					*p++ = '\\';
546bdb24028Schristos 			} else {
547bdb24028Schristos 				if (ch == '%') {
5480061c6a5Schristos 					break;
5490061c6a5Schristos 				}
550bdb24028Schristos 			}
551bdb24028Schristos 
552bdb24028Schristos 			if (!(escaped = (ch == '\\'))) {
553bdb24028Schristos 				*p++ = (char)ch;
554bdb24028Schristos 			}
555bdb24028Schristos 		}
556bdb24028Schristos 		if (ch == '\0') {
557bdb24028Schristos 			/* move pointer back, so that code below
558bdb24028Schristos 			 * won't think we encountered % sequence */
559bdb24028Schristos 			input_data--;
560bdb24028Schristos 		}
561bdb24028Schristos 		if (escaped)
562bdb24028Schristos 			*p++ = '\\';
563bdb24028Schristos 
564bdb24028Schristos 		*p = '\0';
565bdb24028Schristos 	}
566bdb24028Schristos 
567bdb24028Schristos #ifdef USE_PAM
568bdb24028Schristos 	if (!cron_pam_start(usernm))
569bdb24028Schristos 		return ERROR_EXIT;
570bdb24028Schristos 
571bdb24028Schristos 	if (!(envp = cron_pam_getenvlist(envp))) {
572bdb24028Schristos 		retval = ERROR_EXIT;
573bdb24028Schristos 		goto child_process_end;
574bdb24028Schristos 	}
575bdb24028Schristos #endif
576bdb24028Schristos 
577bdb24028Schristos 	/* fork again, this time so we can exec the user's command.
578bdb24028Schristos 	 */
579fdf2ea13Schristos 	if (exec_user_command(e, envp, usernm, stdin_pipe, stdout_pipe,
580fdf2ea13Schristos 	    &jobpid) == -1) {
581bdb24028Schristos 		retval = ERROR_EXIT;
582bdb24028Schristos 		goto child_process_end;
583bdb24028Schristos 	}
584bdb24028Schristos 
5850061c6a5Schristos 
5860061c6a5Schristos 	/* middle process, child of original cron, parent of process running
5870061c6a5Schristos 	 * the user's command.
5880061c6a5Schristos 	 */
5890061c6a5Schristos 
590032a4398Schristos 	Debug(DPROC, ("[%ld] child continues, closing pipes\n",(long)getpid()));
5910061c6a5Schristos 
5920061c6a5Schristos 	/* close the ends of the pipe that will only be referenced in the
5930061c6a5Schristos 	 * grandchild process...
5940061c6a5Schristos 	 */
595032a4398Schristos 	(void)close(stdin_pipe[READ_PIPE]);
596032a4398Schristos 	(void)close(stdout_pipe[WRITE_PIPE]);
5970061c6a5Schristos 
5980061c6a5Schristos 	/*
5990061c6a5Schristos 	 * write, to the pipe connected to child's stdin, any input specified
6000061c6a5Schristos 	 * after a % in the crontab entry.  while we copy, convert any
6010061c6a5Schristos 	 * additional %'s to newlines.  when done, if some characters were
6020061c6a5Schristos 	 * written and the last one wasn't a newline, write a newline.
6030061c6a5Schristos 	 *
6040061c6a5Schristos 	 * Note that if the input data won't fit into one pipe buffer (2K
6050061c6a5Schristos 	 * or 4K on most BSD systems), and the child doesn't read its stdin,
6060061c6a5Schristos 	 * we would block here.  thus we must fork again.
6070061c6a5Schristos 	 */
6080061c6a5Schristos 
6097e10eaceSchristos 	if (*input_data) {
6107e10eaceSchristos 		switch (fork()) {
6117e10eaceSchristos 		case 0:
6127e10eaceSchristos 			write_data(input_data, stdin_pipe, stdout_pipe);
6137e10eaceSchristos 			exit(EXIT_SUCCESS);
6147e10eaceSchristos 		case -1:
6157e10eaceSchristos 			retval = ERROR_EXIT;
6167e10eaceSchristos 			goto child_process_end;
6177e10eaceSchristos 		default:
6187e10eaceSchristos 			break;
6190061c6a5Schristos 		}
6200061c6a5Schristos 	}
6210061c6a5Schristos 
6220061c6a5Schristos 	/* close the pipe to the grandkiddie's stdin, since its wicked uncle
6230061c6a5Schristos 	 * ernie back there has it open and will close it when he's done.
6240061c6a5Schristos 	 */
625032a4398Schristos 	(void)close(stdin_pipe[WRITE_PIPE]);
6260061c6a5Schristos 
6270061c6a5Schristos 	/*
6280061c6a5Schristos 	 * read output from the grandchild.  it's stderr has been redirected to
6290061c6a5Schristos 	 * it's stdout, which has been redirected to our pipe.  if there is any
6300061c6a5Schristos 	 * output, we'll be mailing it to the user whose crontab this is...
6310061c6a5Schristos 	 * when the grandchild exits, we'll get EOF.
6320061c6a5Schristos 	 */
6330061c6a5Schristos 
6340061c6a5Schristos 	Debug(DPROC, ("[%ld] child reading output from grandchild\n",
635032a4398Schristos 		      (long)getpid()));
6360061c6a5Schristos 
637fdf2ea13Schristos 	retval = read_data(e, mailto, usernm, envp, stdout_pipe, jobpid);
6387e10eaceSchristos 	if (retval)
639065057e6Schristos 		goto child_process_end;
6400061c6a5Schristos 
6410061c6a5Schristos 
6420061c6a5Schristos 	/* wait for children to die.
6430061c6a5Schristos 	 */
644065057e6Schristos 	sigchld_handler(0);
645032a4398Schristos 
646032a4398Schristos 	/* Log the time when we finished deadling with the job */
647032a4398Schristos 	/*local*/{
648032a4398Schristos 		char *x = mkprints(e->cmd, strlen(e->cmd));
649032a4398Schristos 
650032a4398Schristos 		log_it(usernm, getpid(), "CMD FINISH", x);
651032a4398Schristos 		free(x);
6520061c6a5Schristos 	}
653065057e6Schristos 
654065057e6Schristos child_process_end:
655065057e6Schristos #ifdef USE_PAM
656065057e6Schristos 	cron_pam_finish();
657065057e6Schristos #endif
658065057e6Schristos 	return retval;
6590061c6a5Schristos }
6600061c6a5Schristos 
6610061c6a5Schristos static int
safe_p(const char * usernm,const char * s)6620061c6a5Schristos safe_p(const char *usernm, const char *s) {
6630061c6a5Schristos 	static const char safe_delim[] = "@!:%-.,";     /* conservative! */
6640061c6a5Schristos 	const char *t;
6650061c6a5Schristos 	int ch, first;
6660061c6a5Schristos 
6670061c6a5Schristos 	for (t = s, first = 1; (ch = *t++) != '\0'; first = 0) {
6680061c6a5Schristos 		if (isascii(ch) && isprint(ch) &&
6690061c6a5Schristos 		    (isalnum(ch) || (!first && strchr(safe_delim, ch))))
6700061c6a5Schristos 			continue;
6710061c6a5Schristos 		log_it(usernm, getpid(), "UNSAFE", s);
6720061c6a5Schristos 		return (FALSE);
6730061c6a5Schristos 	}
6740061c6a5Schristos 	return (TRUE);
6750061c6a5Schristos }
676