1/*	$NetBSD: do_command.c,v 1.3 2011/07/17 01:14:25 christos Exp $	*/
2
3/* Copyright 1988,1990,1993,1994 by Paul Vixie
4 * All rights reserved
5 */
6
7/*
8 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
9 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
10 *
11 * Permission to use, copy, modify, and distribute this software for any
12 * purpose with or without fee is hereby granted, provided that the above
13 * copyright notice and this permission notice appear in all copies.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
21 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 */
23#include <sys/cdefs.h>
24#if !defined(lint) && !defined(LINT)
25#if 0
26static char rcsid[] = "Id: do_command.c,v 1.9 2004/01/23 18:56:42 vixie Exp";
27#else
28__RCSID("$NetBSD: do_command.c,v 1.3 2011/07/17 01:14:25 christos Exp $");
29#endif
30#endif
31
32#include "cron.h"
33#include <unistd.h>
34
35static void		child_process(entry *);
36static int		safe_p(const char *, const char *);
37
38void
39do_command(entry *e, user *u) {
40	Debug(DPROC, ("[%ld] do_command(%s, (%s,%ld,%ld))\n",
41		      (long)getpid(), e->cmd, u->name,
42		      (long)e->pwd->pw_uid, (long)e->pwd->pw_gid));
43
44	/* fork to become asynchronous -- parent process is done immediately,
45	 * and continues to run the normal cron code, which means return to
46	 * tick().  the child and grandchild don't leave this function, alive.
47	 *
48	 * vfork() is unsuitable, since we have much to do, and the parent
49	 * needs to be able to run off and fork other processes.
50	 */
51	switch (fork()) {
52	case -1:
53		log_it("CRON", getpid(), "error", "can't fork");
54		break;
55	case 0:
56		/* child process */
57		acquire_daemonlock(1);
58		child_process(e);
59		Debug(DPROC, ("[%ld] child process done, exiting\n",
60			      (long)getpid()));
61		_exit(OK_EXIT);
62		break;
63	default:
64		/* parent process */
65		break;
66	}
67	Debug(DPROC, ("[%ld] main process returning to work\n",(long)getpid()));
68}
69
70static void
71child_process(entry *e) {
72	int stdin_pipe[2], stdout_pipe[2];
73	char * volatile input_data;
74	char *usernm, * volatile mailto;
75	int children = 0;
76
77	Debug(DPROC, ("[%ld] child_process('%s')\n", (long)getpid(), e->cmd));
78
79	setproctitle("running job");
80
81	/* discover some useful and important environment settings
82	 */
83	usernm = e->pwd->pw_name;
84	mailto = env_get("MAILTO", e->envp);
85
86	/* our parent is watching for our death by catching SIGCHLD.  we
87	 * do not care to watch for our children's deaths this way -- we
88	 * use wait() explicitly.  so we have to reset the signal (which
89	 * was inherited from the parent).
90	 */
91	(void) signal(SIGCHLD, SIG_DFL);
92
93	/* create some pipes to talk to our future child
94	 */
95	if (pipe(stdin_pipe) == -1) 	/* child's stdin */
96		log_it("CRON", getpid(), "error", "create child stdin pipe");
97	if (pipe(stdout_pipe) == -1)	/* child's stdout */
98		log_it("CRON", getpid(), "error", "create child stdout pipe");
99
100	/* since we are a forked process, we can diddle the command string
101	 * we were passed -- nobody else is going to use it again, right?
102	 *
103	 * if a % is present in the command, previous characters are the
104	 * command, and subsequent characters are the additional input to
105	 * the command.  An escaped % will have the escape character stripped
106	 * from it.  Subsequent %'s will be transformed into newlines,
107	 * but that happens later.
108	 */
109	/*local*/{
110		int escaped = FALSE;
111		int ch;
112		char *p;
113
114		/* translation:
115		 *	\% -> %
116		 *	%  -> end of command, following is command input.
117		 *	\x -> \x	for all x != %
118		 */
119		input_data = p = e->cmd;
120		while ((ch = *input_data++) != '\0') {
121 			if (escaped) {
122				if (ch != '%')
123					*p++ = '\\';
124			} else {
125				if (ch == '%') {
126					break;
127				}
128			}
129
130			if (!(escaped = (ch == '\\'))) {
131				*p++ = ch;
132			}
133		}
134		if (ch == '\0') {
135			/* move pointer back, so that code below
136			 * won't think we encountered % sequence */
137			input_data--;
138		}
139		if (escaped)
140			*p++ = '\\';
141
142		*p = '\0';
143	}
144
145	/* fork again, this time so we can exec the user's command.
146	 */
147	switch (vfork()) {
148	case -1:
149		log_it("CRON", getpid(), "error", "can't vfork");
150		exit(ERROR_EXIT);
151		/*NOTREACHED*/
152	case 0:
153		Debug(DPROC, ("[%ld] grandchild process vfork()'ed\n",
154			      (long)getpid()));
155
156		/* write a log message.  we've waited this long to do it
157		 * because it was not until now that we knew the PID that
158		 * the actual user command shell was going to get and the
159		 * PID is part of the log message.
160		 */
161		if ((e->flags & DONT_LOG) == 0) {
162			char *x = mkprints(e->cmd, strlen(e->cmd));
163
164			log_it(usernm, getpid(), "CMD START", x);
165			free(x);
166		}
167
168		/* that's the last thing we'll log.  close the log files.
169		 */
170		log_close();
171
172		/* get new pgrp, void tty, etc.
173		 */
174		if (setsid() == -1)
175			syslog(LOG_ERR, "setsid() failure: %m");
176
177		/* close the pipe ends that we won't use.  this doesn't affect
178		 * the parent, who has to read and write them; it keeps the
179		 * kernel from recording us as a potential client TWICE --
180		 * which would keep it from sending SIGPIPE in otherwise
181		 * appropriate circumstances.
182		 */
183		(void)close(stdin_pipe[WRITE_PIPE]);
184		(void)close(stdout_pipe[READ_PIPE]);
185
186		/* grandchild process.  make std{in,out} be the ends of
187		 * pipes opened by our daddy; make stderr go to stdout.
188		 */
189		if (stdin_pipe[READ_PIPE] != STDIN) {
190			(void)dup2(stdin_pipe[READ_PIPE], STDIN);
191			(void)close(stdin_pipe[READ_PIPE]);
192		}
193		if (stdout_pipe[WRITE_PIPE] != STDOUT) {
194			(void)dup2(stdout_pipe[WRITE_PIPE], STDOUT);
195			(void)close(stdout_pipe[WRITE_PIPE]);
196		}
197		(void)dup2(STDOUT, STDERR);
198
199		/* set our directory, uid and gid.  Set gid first, since once
200		 * we set uid, we've lost root privledges.
201		 */
202#ifdef LOGIN_CAP
203		{
204#ifdef BSD_AUTH
205			auth_session_t *as;
206#endif
207			login_cap_t *lc;
208			char *p;
209
210			if ((lc = login_getclass(e->pwd->pw_class)) == NULL) {
211				warnx("unable to get login class for `%s'",
212				    e->pwd->pw_name);
213				_exit(ERROR_EXIT);
214			}
215			if (setusercontext(lc, e->pwd, e->pwd->pw_uid, LOGIN_SETALL) < 0) {
216				warnx("setusercontext failed for `%s'",
217				    e->pwd->pw_name);
218				_exit(ERROR_EXIT);
219			}
220#ifdef BSD_AUTH
221			as = auth_open();
222			if (as == NULL || auth_setpwd(as, e->pwd) != 0) {
223				warn("can't malloc");
224				_exit(ERROR_EXIT);
225			}
226			if (auth_approval(as, lc, usernm, "cron") <= 0) {
227				warnx("approval failed for `%s'",
228				    e->pwd->pw_name);
229				_exit(ERROR_EXIT);
230			}
231			auth_close(as);
232#endif /* BSD_AUTH */
233			login_close(lc);
234
235			/* If no PATH specified in crontab file but
236			 * we just added one via login.conf, add it to
237			 * the crontab environment.
238			 */
239			if (env_get("PATH", e->envp) == NULL) {
240				if ((p = getenv("PATH")) != NULL)
241					e->envp = env_set(e->envp, p);
242			}
243		}
244#else
245		if (setgid(e->pwd->pw_gid) != 0) {
246			syslog(LOG_ERR, "setgid failed");
247			_exit(ERROR_EXIT);
248		}
249		if (initgroups(usernm, e->pwd->pw_gid) != 0) {
250			syslog(LOG_ERR, "initgroups failed");
251			_exit(ERROR_EXIT);
252		}
253#if (defined(BSD)) && (BSD >= 199103)
254		if (setlogin(usernm) < 0) {
255			syslog(LOG_ERR, "setlogin() failure: %m");
256			_exit(ERROR_EXIT);
257		}
258#endif /* BSD */
259		if (setuid(e->pwd->pw_uid) != 0) {
260			syslog(LOG_ERR, "setuid failed");
261			_exit(ERROR_EXIT);
262		}
263		/* we aren't root after this... */
264#endif /* LOGIN_CAP */
265
266		if (chdir(env_get("HOME", e->envp)) != 0) {
267			syslog(LOG_ERR, "chdir $HOME failed");
268			_exit(ERROR_EXIT);
269		}
270
271#ifdef USE_SIGCHLD
272		/* our grandparent is watching for our death by catching
273		 * SIGCHLD.  the parent is ignoring SIGCHLD's; we want
274		 * to restore default behaviour.
275		 */
276		(void) signal(SIGCHLD, SIG_DFL);
277#endif
278		(void) signal(SIGHUP, SIG_DFL);
279
280		/*
281		 * Exec the command.
282		 */
283		{
284			char	*shell = env_get("SHELL", e->envp);
285
286# if DEBUGGING
287			if (DebugFlags & DTEST) {
288				(void)fprintf(stderr,
289				"debug DTEST is on, not exec'ing command.\n");
290				(void)fprintf(stderr,
291				"\tcmd='%s' shell='%s'\n", e->cmd, shell);
292				_exit(OK_EXIT);
293			}
294# endif /*DEBUGGING*/
295			(void)execle(shell, shell, "-c", e->cmd, NULL, e->envp);
296			warn("execl: couldn't exec `%s'", shell);
297			_exit(ERROR_EXIT);
298		}
299		break;
300	default:
301		/* parent process */
302		break;
303	}
304
305	children++;
306
307	/* middle process, child of original cron, parent of process running
308	 * the user's command.
309	 */
310
311	Debug(DPROC, ("[%ld] child continues, closing pipes\n",(long)getpid()));
312
313	/* close the ends of the pipe that will only be referenced in the
314	 * grandchild process...
315	 */
316	(void)close(stdin_pipe[READ_PIPE]);
317	(void)close(stdout_pipe[WRITE_PIPE]);
318
319	/*
320	 * write, to the pipe connected to child's stdin, any input specified
321	 * after a % in the crontab entry.  while we copy, convert any
322	 * additional %'s to newlines.  when done, if some characters were
323	 * written and the last one wasn't a newline, write a newline.
324	 *
325	 * Note that if the input data won't fit into one pipe buffer (2K
326	 * or 4K on most BSD systems), and the child doesn't read its stdin,
327	 * we would block here.  thus we must fork again.
328	 */
329
330	if (*input_data && fork() == 0) {
331		FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
332		int need_newline = FALSE;
333		int escaped = FALSE;
334		int ch;
335
336		Debug(DPROC, ("[%ld] child2 sending data to grandchild\n",
337			      (long)getpid()));
338
339		/* close the pipe we don't use, since we inherited it and
340		 * are part of its reference count now.
341		 */
342		(void)close(stdout_pipe[READ_PIPE]);
343
344		/* translation:
345		 *	\% -> %
346		 *	%  -> \n
347		 *	\x -> \x	for all x != %
348		 */
349		while ((ch = *input_data++) != '\0') {
350			if (escaped) {
351				if (ch != '%')
352					(void)putc('\\', out);
353			} else {
354				if (ch == '%')
355					ch = '\n';
356			}
357
358			if (!(escaped = (ch == '\\'))) {
359				(void)putc(ch, out);
360				need_newline = (ch != '\n');
361			}
362		}
363		if (escaped)
364			(void)putc('\\', out);
365		if (need_newline)
366			(void)putc('\n', out);
367
368		/* close the pipe, causing an EOF condition.  fclose causes
369		 * stdin_pipe[WRITE_PIPE] to be closed, too.
370		 */
371		(void)fclose(out);
372
373		Debug(DPROC, ("[%ld] child2 done sending to grandchild\n",
374			      (long)getpid()));
375		exit(0);
376	}
377
378	/* close the pipe to the grandkiddie's stdin, since its wicked uncle
379	 * ernie back there has it open and will close it when he's done.
380	 */
381	(void)close(stdin_pipe[WRITE_PIPE]);
382
383	children++;
384
385	/*
386	 * read output from the grandchild.  it's stderr has been redirected to
387	 * it's stdout, which has been redirected to our pipe.  if there is any
388	 * output, we'll be mailing it to the user whose crontab this is...
389	 * when the grandchild exits, we'll get EOF.
390	 */
391
392	Debug(DPROC, ("[%ld] child reading output from grandchild\n",
393		      (long)getpid()));
394
395	/*local*/{
396		FILE	*in = fdopen(stdout_pipe[READ_PIPE], "r");
397		int	ch = getc(in);
398
399		if (ch != EOF) {
400			FILE	*mail = NULL;
401			int	bytes = 1;
402			int	status = 0;
403
404			Debug(DPROC|DEXT,
405			      ("[%ld] got data (%x:%c) from grandchild\n",
406			       (long)getpid(), ch, ch));
407
408			/* get name of recipient.  this is MAILTO if set to a
409			 * valid local username; USER otherwise.
410			 */
411			if (mailto) {
412				/* MAILTO was present in the environment
413				 */
414				if (!*mailto) {
415					/* ... but it's empty. set to NULL
416					 */
417					mailto = NULL;
418				}
419			} else {
420				/* MAILTO not present, set to USER.
421				 */
422				mailto = usernm;
423			}
424
425			/* if we are supposed to be mailing, MAILTO will
426			 * be non-NULL.  only in this case should we set
427			 * up the mail command and subjects and stuff...
428			 */
429
430			if (mailto && safe_p(usernm, mailto)) {
431				char	**env;
432				char	mailcmd[MAX_COMMAND];
433				char	hostname[MAXHOSTNAMELEN + 1];
434
435				(void)gethostname(hostname, MAXHOSTNAMELEN);
436				if (strlens(MAILFMT, MAILARG, NULL) + 1
437				    >= sizeof mailcmd) {
438					warnx("mailcmd too long");
439					(void) _exit(ERROR_EXIT);
440				}
441				(void)snprintf(mailcmd, sizeof(mailcmd),
442				    MAILFMT, MAILARG);
443				if (!(mail = cron_popen(mailcmd, "w", e->pwd))) {
444					warn("cannot run `%s'", mailcmd);
445					(void) _exit(ERROR_EXIT);
446				}
447				(void)fprintf(mail,
448				    "From: root (Cron Daemon)\n");
449				(void)fprintf(mail, "To: %s\n", mailto);
450				(void)fprintf(mail,
451				    "Subject: Cron <%s@%s> %s\n",
452				    usernm, first_word(hostname, "."), e->cmd);
453				(void)fprintf(mail,
454				    "Auto-Submitted: auto-generated\n");
455#ifdef MAIL_DATE
456				(void)fprintf(mail, "Date: %s\n",
457					arpadate(&StartTime));
458#endif /*MAIL_DATE*/
459				for (env = e->envp;  *env;  env++)
460					(void)fprintf(mail,
461					    "X-Cron-Env: <%s>\n", *env);
462				(void)fprintf(mail, "\n");
463
464				/* this was the first char from the pipe
465				 */
466				(void)putc(ch, mail);
467			}
468
469			/* we have to read the input pipe no matter whether
470			 * we mail or not, but obviously we only write to
471			 * mail pipe if we ARE mailing.
472			 */
473
474			while (EOF != (ch = getc(in))) {
475				bytes++;
476				if (mailto)
477					(void)putc(ch, mail);
478			}
479
480			/* only close pipe if we opened it -- i.e., we're
481			 * mailing...
482			 */
483
484			if (mailto) {
485				Debug(DPROC, ("[%ld] closing pipe to mail\n",
486					      (long)getpid()));
487				/* Note: the pclose will probably see
488				 * the termination of the grandchild
489				 * in addition to the mail process, since
490				 * it (the grandchild) is likely to exit
491				 * after closing its stdout.
492				 */
493				status = cron_pclose(mail);
494			}
495
496			/* if there was output and we could not mail it,
497			 * log the facts so the poor user can figure out
498			 * what's going on.
499			 */
500			if (mailto && status) {
501				char buf[MAX_TEMPSTR];
502
503				(void)snprintf(buf, sizeof(buf),
504			"mailed %d byte%s of output but got status 0x%04x\n",
505					bytes, (bytes==1)?"":"s",
506					status);
507				log_it(usernm, getpid(), "MAIL", buf);
508			}
509
510		} /*if data from grandchild*/
511
512		Debug(DPROC, ("[%ld] got EOF from grandchild\n",
513			      (long)getpid()));
514
515		(void)fclose(in);	/* also closes stdout_pipe[READ_PIPE] */
516	}
517
518	/* wait for children to die.
519	 */
520	for (; children > 0; children--) {
521		WAIT_T waiter;
522		PID_T pid;
523
524		Debug(DPROC, ("[%ld] waiting for grandchild #%d to finish\n",
525			      (long)getpid(), children));
526		while ((pid = wait(&waiter)) < OK && errno == EINTR)
527			;
528		if (pid < OK) {
529			Debug(DPROC,
530			      ("[%ld] no more grandchildren--mail written?\n",
531			       (long)getpid()));
532			break;
533		}
534		Debug(DPROC, ("[%ld] grandchild #%ld finished, status=%04x",
535			      (long)getpid(), (long)pid, WEXITSTATUS(waiter)));
536		if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
537			Debug(DPROC, (", dumped core"));
538		Debug(DPROC, ("\n"));
539	}
540
541	/* Log the time when we finished deadling with the job */
542	/*local*/{
543		char *x = mkprints(e->cmd, strlen(e->cmd));
544
545		log_it(usernm, getpid(), "CMD FINISH", x);
546		free(x);
547	}
548}
549
550static int
551safe_p(const char *usernm, const char *s) {
552	static const char safe_delim[] = "@!:%-.,";     /* conservative! */
553	const char *t;
554	int ch, first;
555
556	for (t = s, first = 1; (ch = *t++) != '\0'; first = 0) {
557		if (isascii(ch) && isprint(ch) &&
558		    (isalnum(ch) || (!first && strchr(safe_delim, ch))))
559			continue;
560		log_it(usernm, getpid(), "UNSAFE", s);
561		return (FALSE);
562	}
563	return (TRUE);
564}
565