1/*	$OpenBSD: do_command.c,v 1.63 2022/05/21 01:21:29 deraadt Exp $	*/
2
3/* Copyright 1988,1990,1993,1994 by Paul Vixie
4 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
5 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
6 * Copyright (c) 2018 Job Snijders <job@openbsd.org>
7 *
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
18 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 */
20
21#include <sys/types.h>
22#include <sys/wait.h>
23
24#include <bitstring.h>		/* for structs.h */
25#include <bsd_auth.h>
26#include <ctype.h>
27#include <err.h>
28#include <errno.h>
29#include <fcntl.h>
30#include <limits.h>
31#include <login_cap.h>
32#include <pwd.h>
33#include <signal.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <syslog.h>
38#include <time.h>		/* for structs.h */
39#include <unistd.h>
40#include <vis.h>
41
42#include "config.h"
43#include "pathnames.h"
44#include "macros.h"
45#include "structs.h"
46#include "funcs.h"
47
48static void		child_process(entry *, user *);
49
50pid_t
51do_command(entry *e, user *u)
52{
53	pid_t pid;
54
55	/* fork to become asynchronous -- parent process is done immediately,
56	 * and continues to run the normal cron code, which means return to
57	 * tick().  the child and grandchild don't leave this function, alive.
58	 *
59	 * vfork() is unsuitable, since we have much to do, and the parent
60	 * needs to be able to run off and fork other processes.
61	 */
62	switch ((pid = fork())) {
63	case -1:
64		syslog(LOG_ERR, "(CRON) CAN'T FORK (%m)");
65		break;
66	case 0:
67		/* child process */
68		child_process(e, u);
69		_exit(EXIT_SUCCESS);
70		break;
71	default:
72		/* parent process */
73		if ((e->flags & SINGLE_JOB) == 0)
74			pid = -1;
75		break;
76	}
77
78	/* only return pid if a singleton */
79	return (pid);
80}
81
82static void
83child_process(entry *e, user *u)
84{
85	FILE *in;
86	int stdin_pipe[2], stdout_pipe[2];
87	char **p, *input_data, *usernm;
88	auth_session_t *as;
89	login_cap_t *lc;
90	extern char **environ;
91
92	/* mark ourselves as different to PS command watchers */
93	setproctitle("running job");
94
95	/* close sockets from parent (i.e. cronSock) */
96	closefrom(3);
97
98	/* discover some useful and important environment settings
99	 */
100	usernm = e->pwd->pw_name;
101
102	/* our parent is watching for our death by catching SIGCHLD.  we
103	 * do not care to watch for our children's deaths this way -- we
104	 * use wait() explicitly.  so we have to reset the signal (which
105	 * was inherited from the parent).
106	 */
107	(void) signal(SIGCHLD, SIG_DFL);
108
109	/* create some pipes to talk to our future child
110	 */
111	if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) {
112		syslog(LOG_ERR, "(CRON) PIPE (%m)");
113		_exit(EXIT_FAILURE);
114	}
115
116	/* since we are a forked process, we can diddle the command string
117	 * we were passed -- nobody else is going to use it again, right?
118	 *
119	 * if a % is present in the command, previous characters are the
120	 * command, and subsequent characters are the additional input to
121	 * the command.  An escaped % will have the escape character stripped
122	 * from it.  Subsequent %'s will be transformed into newlines,
123	 * but that happens later.
124	 */
125	/*local*/{
126		int escaped = FALSE;
127		int ch;
128		char *p;
129
130		for (input_data = p = e->cmd;
131		    (ch = *input_data) != '\0';
132		    input_data++, p++) {
133			if (p != input_data)
134				*p = ch;
135			if (escaped) {
136				if (ch == '%')
137					*--p = ch;
138				escaped = FALSE;
139				continue;
140			}
141			if (ch == '\\') {
142				escaped = TRUE;
143				continue;
144			}
145			if (ch == '%') {
146				*input_data++ = '\0';
147				break;
148			}
149		}
150		*p = '\0';
151	}
152
153	/* fork again, this time so we can exec the user's command.
154	 */
155
156	pid_t	jobpid;
157	switch (jobpid = fork()) {
158	case -1:
159		syslog(LOG_ERR, "(CRON) CAN'T FORK (%m)");
160		_exit(EXIT_FAILURE);
161		/*NOTREACHED*/
162	case 0:
163		/* write a log message.  we've waited this long to do it
164		 * because it was not until now that we knew the PID that
165		 * the actual user command shell was going to get and the
166		 * PID is part of the log message.
167		 */
168		if ((e->flags & DONT_LOG) == 0) {
169			char *x;
170			if (stravis(&x, e->cmd, 0) != -1) {
171				syslog(LOG_INFO, "(%s) CMD (%s)", usernm, x);
172				free(x);
173			}
174		}
175
176		/* get new pgrp, void tty, etc.
177		 */
178		(void) setsid();
179
180		/* close the pipe ends that we won't use.  this doesn't affect
181		 * the parent, who has to read and write them; it keeps the
182		 * kernel from recording us as a potential client TWICE --
183		 * which would keep it from sending SIGPIPE in otherwise
184		 * appropriate circumstances.
185		 */
186		close(stdin_pipe[WRITE_PIPE]);
187		close(stdout_pipe[READ_PIPE]);
188
189		/* grandchild process.  make std{in,out} be the ends of
190		 * pipes opened by our daddy; make stderr go to stdout.
191		 */
192		if (stdin_pipe[READ_PIPE] != STDIN_FILENO) {
193			dup2(stdin_pipe[READ_PIPE], STDIN_FILENO);
194			close(stdin_pipe[READ_PIPE]);
195		}
196		if (stdout_pipe[WRITE_PIPE] != STDOUT_FILENO) {
197			dup2(stdout_pipe[WRITE_PIPE], STDOUT_FILENO);
198			close(stdout_pipe[WRITE_PIPE]);
199		}
200		dup2(STDOUT_FILENO, STDERR_FILENO);
201
202		/*
203		 * From this point on, anything written to stderr will be
204		 * mailed to the user as output.
205		 */
206
207		/* XXX - should just pass in a login_cap_t * */
208		if ((lc = login_getclass(e->pwd->pw_class)) == NULL) {
209			warnx("unable to get login class for %s",
210			    e->pwd->pw_name);
211			syslog(LOG_ERR, "(CRON) CAN'T GET LOGIN CLASS (%s)",
212			    e->pwd->pw_name);
213			_exit(EXIT_FAILURE);
214		}
215		if (setusercontext(lc, e->pwd, e->pwd->pw_uid, LOGIN_SETALL) == -1) {
216			warn("setusercontext failed for %s", e->pwd->pw_name);
217			syslog(LOG_ERR, "(%s) SETUSERCONTEXT FAILED (%m)",
218			    e->pwd->pw_name);
219			_exit(EXIT_FAILURE);
220		}
221		as = auth_open();
222		if (as == NULL || auth_setpwd(as, e->pwd) != 0) {
223			warn("auth_setpwd");
224			syslog(LOG_ERR, "(%s) AUTH_SETPWD FAILED (%m)",
225			    e->pwd->pw_name);
226			_exit(EXIT_FAILURE);
227		}
228		if (auth_approval(as, lc, usernm, "cron") <= 0) {
229			warnx("approval failed for %s", e->pwd->pw_name);
230			syslog(LOG_ERR, "(%s) APPROVAL FAILED (cron)",
231			    e->pwd->pw_name);
232			_exit(EXIT_FAILURE);
233		}
234		auth_close(as);
235		login_close(lc);
236
237		/* If no PATH specified in crontab file but
238		 * we just added one via login.conf, add it to
239		 * the crontab environment.
240		 */
241		if (env_get("PATH", e->envp) == NULL && environ != NULL) {
242			for (p = environ; *p; p++) {
243				if (strncmp(*p, "PATH=", 5) == 0) {
244					e->envp = env_set(e->envp, *p);
245					break;
246				}
247			}
248		}
249		chdir(env_get("HOME", e->envp));
250
251		(void) signal(SIGPIPE, SIG_DFL);
252
253		/*
254		 * Exec the command.
255		 */
256		{
257			char	*shell = env_get("SHELL", e->envp);
258
259			execle(shell, shell, "-c", e->cmd, (char *)NULL, e->envp);
260			warn("unable to execute %s", shell);
261			syslog(LOG_ERR, "(%s) CAN'T EXEC (%s: %m)",
262			    e->pwd->pw_name, shell);
263			_exit(EXIT_FAILURE);
264		}
265		break;
266	default:
267		/* parent process */
268		break;
269	}
270
271	/* middle process, child of original cron, parent of process running
272	 * the user's command.
273	 */
274
275	/* close the ends of the pipe that will only be referenced in the
276	 * grandchild process...
277	 */
278	close(stdin_pipe[READ_PIPE]);
279	close(stdout_pipe[WRITE_PIPE]);
280
281	/*
282	 * write, to the pipe connected to child's stdin, any input specified
283	 * after a % in the crontab entry.  while we copy, convert any
284	 * additional %'s to newlines.  when done, if some characters were
285	 * written and the last one wasn't a newline, write a newline.
286	 *
287	 * Note that if the input data won't fit into one pipe buffer (2K
288	 * or 4K on most BSD systems), and the child doesn't read its stdin,
289	 * we would block here.  thus we must fork again.
290	 */
291
292	pid_t	stdinjob;
293	if (*input_data && (stdinjob = fork()) == 0) {
294		FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
295		int need_newline = FALSE;
296		int escaped = FALSE;
297		int ch;
298
299		/* close the pipe we don't use, since we inherited it and
300		 * are part of its reference count now.
301		 */
302		close(stdout_pipe[READ_PIPE]);
303
304		/* translation:
305		 *	\% -> %
306		 *	%  -> \n
307		 *	\x -> \x	for all x != %
308		 */
309		while ((ch = *input_data++) != '\0') {
310			if (escaped) {
311				if (ch != '%')
312					putc('\\', out);
313			} else {
314				if (ch == '%')
315					ch = '\n';
316			}
317
318			if (!(escaped = (ch == '\\'))) {
319				putc(ch, out);
320				need_newline = (ch != '\n');
321			}
322		}
323		if (escaped)
324			putc('\\', out);
325		if (need_newline)
326			putc('\n', out);
327
328		/* close the pipe, causing an EOF condition.  fclose causes
329		 * stdin_pipe[WRITE_PIPE] to be closed, too.
330		 */
331		fclose(out);
332
333		_exit(EXIT_SUCCESS);
334	}
335
336	/* close the pipe to the grandkiddie's stdin, since its wicked uncle
337	 * ernie back there has it open and will close it when he's done.
338	 */
339	close(stdin_pipe[WRITE_PIPE]);
340
341	/*
342	 * read output from the grandchild.  Its stderr has been redirected to
343	 * its stdout, which has been redirected to our pipe.  if there is any
344	 * output, we'll be mailing it to the user whose crontab this is...
345	 * when the grandchild exits, we'll get EOF.
346	 */
347
348	(void) signal(SIGPIPE, SIG_IGN);
349	in = fdopen(stdout_pipe[READ_PIPE], "r");
350
351	char	*mailto;
352	FILE	*mail = NULL;
353	int	status = 0;
354	pid_t	mailpid;
355	size_t	bytes = 1;
356
357	if (in != NULL) {
358		int	ch = getc(in);
359
360		if (ch != EOF) {
361
362			/* get name of recipient.  this is MAILTO if set to a
363			 * valid local username; USER otherwise.
364			 */
365			mailto = env_get("MAILTO", e->envp);
366			if (!mailto) {
367				/* MAILTO not present, set to USER.
368				 */
369				mailto = usernm;
370			} else if (!*mailto || !safe_p(usernm, mailto)) {
371				mailto = NULL;
372			}
373
374			/* if we are supposed to be mailing, MAILTO will
375			 * be non-NULL.  only in this case should we set
376			 * up the mail command and subjects and stuff...
377			 */
378
379			if (mailto) {
380				char	**env;
381				char	mailcmd[MAX_COMMAND];
382				char	hostname[HOST_NAME_MAX + 1];
383
384				gethostname(hostname, sizeof(hostname));
385				if (snprintf(mailcmd, sizeof mailcmd,  MAILFMT,
386				    MAILARG) >= sizeof mailcmd) {
387					syslog(LOG_ERR,
388					    "(%s) ERROR (mailcmd too long)",
389					    e->pwd->pw_name);
390					(void) _exit(EXIT_FAILURE);
391				}
392				if (!(mail = cron_popen(mailcmd, "w", e->pwd,
393				    &mailpid))) {
394					syslog(LOG_ERR, "(%s) POPEN (%s)",
395					    e->pwd->pw_name, mailcmd);
396					(void) _exit(EXIT_FAILURE);
397				}
398				fprintf(mail, "From: root (Cron Daemon)\n");
399				fprintf(mail, "To: %s\n", mailto);
400				fprintf(mail, "Subject: Cron <%s@%s> %s\n",
401					usernm, first_word(hostname, "."),
402					e->cmd);
403				fprintf(mail, "Auto-Submitted: auto-generated\n");
404				for (env = e->envp;  *env;  env++)
405					fprintf(mail, "X-Cron-Env: <%s>\n",
406						*env);
407				fprintf(mail, "\n");
408
409				/* this was the first char from the pipe
410				 */
411				fputc(ch, mail);
412			}
413
414			/* we have to read the input pipe no matter whether
415			 * we mail or not, but obviously we only write to
416			 * mail pipe if we ARE mailing.
417			 */
418
419			while ((ch = getc(in)) != EOF) {
420				bytes++;
421				if (mail)
422					fputc(ch, mail);
423			}
424
425		} /*if data from grandchild*/
426
427		fclose(in);	/* also closes stdout_pipe[READ_PIPE] */
428	}
429
430	/* wait for children to die.
431	 */
432	int waiter;
433	if (jobpid > 0) {
434		while (waitpid(jobpid, &waiter, 0) == -1 && errno == EINTR)
435			;
436
437		/* If everything went well, and -n was set, _and_ we have mail,
438		 * we won't be mailing... so shoot the messenger!
439		 */
440		if (WIFEXITED(waiter) && WEXITSTATUS(waiter) == 0
441		    && (e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR
442		    && mail) {
443			kill(mailpid, SIGKILL);
444			(void)fclose(mail);
445			mail = NULL;
446		}
447
448		/* only close pipe if we opened it -- i.e., we're mailing... */
449		if (mail) {
450			/*
451			 * Note: the pclose will probably see the termination
452			 * of the grandchild in addition to the mail process,
453			 * since it (the grandchild) is likely to exit after
454			 * closing its stdout.
455			 */
456			status = cron_pclose(mail, mailpid);
457		}
458
459		/* if there was output and we could not mail it,
460		 * log the facts so the poor user can figure out
461		 * what's going on.
462		 */
463		if (mail && status) {
464			syslog(LOG_NOTICE, "(%s) MAIL (mailed %zu byte"
465			    "%s of output but got status 0x%04x)", usernm,
466			    bytes, (bytes == 1) ? "" : "s", status);
467		}
468	}
469
470	if (stdinjob > 0)
471		while (waitpid(stdinjob, &waiter, 0) == -1 && errno == EINTR)
472			;
473}
474
475int
476safe_p(const char *usernm, const char *s)
477{
478	static const char safe_delim[] = "@!:%+-.,";	/* conservative! */
479	const char *t;
480	int ch, first;
481
482	for (t = s, first = 1; (ch = (unsigned char)*t++) != '\0'; first = 0) {
483		if (isascii(ch) && isprint(ch) &&
484		    (isalnum(ch) || ch == '_' ||
485		    (!first && strchr(safe_delim, ch))))
486			continue;
487		syslog(LOG_WARNING, "(%s) UNSAFE (%s)", usernm, s);
488		return (FALSE);
489	}
490	return (TRUE);
491}
492