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