do_command.c revision 1.25
1/*	$OpenBSD: do_command.c,v 1.25 2003/07/30 20:20:01 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.25 2003/07/30 20:20:01 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		setgid(e->pwd->pw_gid);
237		initgroups(usernm, e->pwd->pw_gid);
238#if (defined(BSD)) && (BSD >= 199103)
239		setlogin(usernm);
240#endif /* BSD */
241		setuid(e->pwd->pw_uid);	/* we aren't root after this... */
242
243#endif /* LOGIN_CAP */
244		chdir(env_get("HOME", e->envp));
245
246		/*
247		 * Exec the command.
248		 */
249		{
250			char	*shell = env_get("SHELL", e->envp);
251
252# if DEBUGGING
253			if (DebugFlags & DTEST) {
254				fprintf(stderr,
255				"debug DTEST is on, not exec'ing command.\n");
256				fprintf(stderr,
257				"\tcmd='%s' shell='%s'\n", e->cmd, shell);
258				_exit(OK_EXIT);
259			}
260# endif /*DEBUGGING*/
261			execle(shell, shell, "-c", e->cmd, (char *)NULL, e->envp);
262			fprintf(stderr, "execle: couldn't exec `%s'\n", shell);
263			perror("execle");
264			_exit(ERROR_EXIT);
265		}
266		break;
267	default:
268		/* parent process */
269		break;
270	}
271
272	children++;
273
274	/* middle process, child of original cron, parent of process running
275	 * the user's command.
276	 */
277
278	Debug(DPROC, ("[%ld] child continues, closing pipes\n",(long)getpid()))
279
280	/* close the ends of the pipe that will only be referenced in the
281	 * grandchild process...
282	 */
283	close(stdin_pipe[READ_PIPE]);
284	close(stdout_pipe[WRITE_PIPE]);
285
286	/*
287	 * write, to the pipe connected to child's stdin, any input specified
288	 * after a % in the crontab entry.  while we copy, convert any
289	 * additional %'s to newlines.  when done, if some characters were
290	 * written and the last one wasn't a newline, write a newline.
291	 *
292	 * Note that if the input data won't fit into one pipe buffer (2K
293	 * or 4K on most BSD systems), and the child doesn't read its stdin,
294	 * we would block here.  thus we must fork again.
295	 */
296
297	if (*input_data && fork() == 0) {
298		FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
299		int need_newline = FALSE;
300		int escaped = FALSE;
301		int ch;
302
303		Debug(DPROC, ("[%ld] child2 sending data to grandchild\n",
304			      (long)getpid()))
305
306		/* close the pipe we don't use, since we inherited it and
307		 * are part of its reference count now.
308		 */
309		close(stdout_pipe[READ_PIPE]);
310
311		/* translation:
312		 *	\% -> %
313		 *	%  -> \n
314		 *	\x -> \x	for all x != %
315		 */
316		while ((ch = *input_data++) != '\0') {
317			if (escaped) {
318				if (ch != '%')
319					putc('\\', out);
320			} else {
321				if (ch == '%')
322					ch = '\n';
323			}
324
325			if (!(escaped = (ch == '\\'))) {
326				putc(ch, out);
327				need_newline = (ch != '\n');
328			}
329		}
330		if (escaped)
331			putc('\\', out);
332		if (need_newline)
333			putc('\n', out);
334
335		/* close the pipe, causing an EOF condition.  fclose causes
336		 * stdin_pipe[WRITE_PIPE] to be closed, too.
337		 */
338		fclose(out);
339
340		Debug(DPROC, ("[%ld] child2 done sending to grandchild\n",
341			      (long)getpid()))
342		exit(0);
343	}
344
345	/* close the pipe to the grandkiddie's stdin, since its wicked uncle
346	 * ernie back there has it open and will close it when he's done.
347	 */
348	close(stdin_pipe[WRITE_PIPE]);
349
350	children++;
351
352	/*
353	 * read output from the grandchild.  it's stderr has been redirected to
354	 * it's stdout, which has been redirected to our pipe.  if there is any
355	 * output, we'll be mailing it to the user whose crontab this is...
356	 * when the grandchild exits, we'll get EOF.
357	 */
358
359	Debug(DPROC, ("[%ld] child reading output from grandchild\n",
360		      (long)getpid()))
361
362	/*local*/{
363		FILE	*in = fdopen(stdout_pipe[READ_PIPE], "r");
364		int	ch = getc(in);
365
366		if (ch != EOF) {
367			FILE	*mail;
368			int	bytes = 1;
369			int	status = 0;
370
371			Debug(DPROC|DEXT,
372			      ("[%ld] got data (%x:%c) from grandchild\n",
373			       (long)getpid(), ch, ch))
374
375			/* get name of recipient.  this is MAILTO if set to a
376			 * valid local username; USER otherwise.
377			 */
378			if (mailto) {
379				/* MAILTO was present in the environment
380				 */
381				if (!*mailto) {
382					/* ... but it's empty. set to NULL
383					 */
384					mailto = NULL;
385				}
386			} else {
387				/* MAILTO not present, set to USER.
388				 */
389				mailto = usernm;
390			}
391
392			/* if we are supposed to be mailing, MAILTO will
393			 * be non-NULL.  only in this case should we set
394			 * up the mail command and subjects and stuff...
395			 */
396
397			if (mailto && safe_p(usernm, mailto)) {
398				char	**env;
399				char	mailcmd[MAX_COMMAND];
400				char	hostname[MAXHOSTNAMELEN];
401
402				gethostname(hostname, sizeof(hostname));
403				if (snprintf(mailcmd, sizeof mailcmd,  MAILFMT,
404				    MAILARG) >= sizeof mailcmd) {
405					fprintf(stderr, "mailcmd too long\n");
406					(void) _exit(ERROR_EXIT);
407				}
408				if (!(mail = cron_popen(mailcmd, "w", e->pwd))) {
409					perror(mailcmd);
410					(void) _exit(ERROR_EXIT);
411				}
412				fprintf(mail, "From: root (Cron Daemon)\n");
413				fprintf(mail, "To: %s\n", mailto);
414				fprintf(mail, "Subject: Cron <%s@%s> %s\n",
415					usernm, first_word(hostname, "."),
416					e->cmd);
417#ifdef MAIL_DATE
418				fprintf(mail, "Date: %s\n",
419					arpadate(&StartTime));
420#endif /*MAIL_DATE*/
421				for (env = e->envp;  *env;  env++)
422					fprintf(mail, "X-Cron-Env: <%s>\n",
423						*env);
424				fprintf(mail, "\n");
425
426				/* this was the first char from the pipe
427				 */
428				fputc(ch, mail);
429			}
430
431			/* we have to read the input pipe no matter whether
432			 * we mail or not, but obviously we only write to
433			 * mail pipe if we ARE mailing.
434			 */
435
436			while (EOF != (ch = getc(in))) {
437				bytes++;
438				if (mailto)
439					fputc(ch, mail);
440			}
441
442			/* only close pipe if we opened it -- i.e., we're
443			 * mailing...
444			 */
445
446			if (mailto) {
447				Debug(DPROC, ("[%ld] closing pipe to mail\n",
448					      (long)getpid()))
449				/* Note: the pclose will probably see
450				 * the termination of the grandchild
451				 * in addition to the mail process, since
452				 * it (the grandchild) is likely to exit
453				 * after closing its stdout.
454				 */
455				status = cron_pclose(mail);
456			}
457
458			/* if there was output and we could not mail it,
459			 * log the facts so the poor user can figure out
460			 * what's going on.
461			 */
462			if (mailto && status) {
463				char buf[MAX_TEMPSTR];
464
465				snprintf(buf, sizeof buf,
466			"mailed %d byte%s of output but got status 0x%04x\n",
467					bytes, (bytes==1)?"":"s",
468					status);
469				log_it(usernm, getpid(), "MAIL", buf);
470			}
471
472		} /*if data from grandchild*/
473
474		Debug(DPROC, ("[%ld] got EOF from grandchild\n",
475			      (long)getpid()))
476
477		fclose(in);	/* also closes stdout_pipe[READ_PIPE] */
478	}
479
480	/* wait for children to die.
481	 */
482	for (; children > 0; children--) {
483		WAIT_T waiter;
484		PID_T pid;
485
486		Debug(DPROC, ("[%ld] waiting for grandchild #%d to finish\n",
487			      (long)getpid(), children))
488		while ((pid = wait(&waiter)) < OK && errno == EINTR)
489			;
490		if (pid < OK) {
491			Debug(DPROC,
492			      ("[%ld] no more grandchildren--mail written?\n",
493			       (long)getpid()))
494			break;
495		}
496		Debug(DPROC, ("[%ld] grandchild #%ld finished, status=%04x",
497			      (long)getpid(), (long)pid, WEXITSTATUS(waiter)))
498		if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
499			Debug(DPROC, (", dumped core"))
500		Debug(DPROC, ("\n"))
501	}
502}
503
504int
505safe_p(const char *usernm, const char *s) {
506	static const char safe_delim[] = "@!:%-.,";     /* conservative! */
507	const char *t;
508	int ch, first;
509
510	for (t = s, first = 1; (ch = *t++) != '\0'; first = 0) {
511		if (isascii(ch) && isprint(ch) &&
512		    (isalnum(ch) || (!first && strchr(safe_delim, ch))))
513			continue;
514		log_it(usernm, getpid(), "UNSAFE", s);
515		return (FALSE);
516	}
517	return (TRUE);
518}
519