xref: /freebsd/usr.sbin/cron/cron/do_command.c (revision 031beb4e239bfce798af17f5fe8dba8bcaf13d99)
1 /* Copyright 1988,1990,1993,1994 by Paul Vixie
2  * All rights reserved
3  */
4 
5 /*
6  * Copyright (c) 1997 by Internet Software Consortium
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 INTERNET SOFTWARE CONSORTIUM DISCLAIMS
13  * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
14  * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
15  * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
16  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
17  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
18  * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
19  * SOFTWARE.
20  */
21 
22 #if !defined(lint) && !defined(LINT)
23 static const char rcsid[] =
24     "$Id: do_command.c,v 1.3 1998/08/14 00:32:39 vixie Exp $";
25 #endif
26 
27 #include "cron.h"
28 #if defined(LOGIN_CAP)
29 # include <login_cap.h>
30 #endif
31 #ifdef PAM
32 # include <security/pam_appl.h>
33 # include <security/openpam.h>
34 #endif
35 
36 static void		child_process(entry *, user *);
37 static WAIT_T		wait_on_child(PID_T, const char *);
38 
39 extern char	*environ;
40 
41 void
42 do_command(entry *e, user *u)
43 {
44 	pid_t pid;
45 
46 	Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
47 		getpid(), e->cmd, u->name, e->uid, e->gid))
48 
49 	/* fork to become asynchronous -- parent process is done immediately,
50 	 * and continues to run the normal cron code, which means return to
51 	 * tick().  the child and grandchild don't leave this function, alive.
52 	 */
53 	switch ((pid = fork())) {
54 	case -1:
55 		log_it("CRON", getpid(), "error", "can't fork");
56 		if (e->flags & INTERVAL)
57 			e->lastexit = time(NULL);
58 		break;
59 	case 0:
60 		/* child process */
61 		pidfile_close(pfh);
62 		child_process(e, u);
63 		Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
64 		_exit(OK_EXIT);
65 		break;
66 	default:
67 		/* parent process */
68 		Debug(DPROC, ("[%d] main process forked child #%d, "
69 		    "returning to work\n", getpid(), pid))
70 		if (e->flags & INTERVAL) {
71 			e->lastexit = 0;
72 			e->child = pid;
73 		}
74 		break;
75 	}
76 	Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
77 }
78 
79 
80 static void
81 child_process(entry *e, user *u)
82 {
83 	int stdin_pipe[2], stdout_pipe[2];
84 	char *input_data;
85 	const char *usernm, *mailto, *mailfrom;
86 	PID_T jobpid, stdinjob, mailpid;
87 	FILE *mail;
88 	int bytes = 1;
89 	int status = 0;
90 	const char *homedir = NULL;
91 # if defined(LOGIN_CAP)
92 	struct passwd *pwd;
93 	login_cap_t *lc;
94 # endif
95 
96 	Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
97 
98 	/* mark ourselves as different to PS command watchers by upshifting
99 	 * our program name.  This has no effect on some kernels.
100 	 */
101 	setproctitle("running job");
102 
103 	/* discover some useful and important environment settings
104 	 */
105 	usernm = env_get("LOGNAME", e->envp);
106 	mailto = env_get("MAILTO", e->envp);
107 	mailfrom = env_get("MAILFROM", e->envp);
108 
109 #ifdef PAM
110 	/* use PAM to see if the user's account is available,
111 	 * i.e., not locked or expired or whatever.  skip this
112 	 * for system tasks from /etc/crontab -- they can run
113 	 * as any user.
114 	 */
115 	if (strcmp(u->name, SYS_NAME)) {	/* not equal */
116 		pam_handle_t *pamh = NULL;
117 		int pam_err;
118 		struct pam_conv pamc = {
119 			.conv = openpam_nullconv,
120 			.appdata_ptr = NULL
121 		};
122 
123 		Debug(DPROC, ("[%d] checking account with PAM\n", getpid()))
124 
125 		/* u->name keeps crontab owner name while LOGNAME is the name
126 		 * of user to run command on behalf of.  they should be the
127 		 * same for a task from a per-user crontab.
128 		 */
129 		if (strcmp(u->name, usernm)) {
130 			log_it(usernm, getpid(), "username ambiguity", u->name);
131 			exit(ERROR_EXIT);
132 		}
133 
134 		pam_err = pam_start("cron", usernm, &pamc, &pamh);
135 		if (pam_err != PAM_SUCCESS) {
136 			log_it("CRON", getpid(), "error", "can't start PAM");
137 			exit(ERROR_EXIT);
138 		}
139 
140 		pam_err = pam_acct_mgmt(pamh, PAM_SILENT);
141 		/* Expired password shouldn't prevent the job from running. */
142 		if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD) {
143 			log_it(usernm, getpid(), "USER", "account unavailable");
144 			exit(ERROR_EXIT);
145 		}
146 
147 		pam_end(pamh, pam_err);
148 	}
149 #endif
150 
151 	/* our parent is watching for our death by catching SIGCHLD.  we
152 	 * do not care to watch for our children's deaths this way -- we
153 	 * use wait() explicitly.  so we have to disable the signal (which
154 	 * was inherited from the parent).
155 	 */
156 	(void) signal(SIGCHLD, SIG_DFL);
157 
158 	/* create some pipes to talk to our future child
159 	 */
160 	if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) {
161 		log_it("CRON", getpid(), "error", "can't pipe");
162 		exit(ERROR_EXIT);
163 	}
164 
165 	/* since we are a forked process, we can diddle the command string
166 	 * we were passed -- nobody else is going to use it again, right?
167 	 *
168 	 * if a % is present in the command, previous characters are the
169 	 * command, and subsequent characters are the additional input to
170 	 * the command.  Subsequent %'s will be transformed into newlines,
171 	 * but that happens later.
172 	 *
173 	 * If there are escaped %'s, remove the escape character.
174 	 */
175 	/*local*/{
176 		int escaped = FALSE;
177 		int ch;
178 		char *p;
179 
180 		for (input_data = p = e->cmd;
181 		     (ch = *input_data) != '\0';
182 		     input_data++, p++) {
183 			if (p != input_data)
184 				*p = ch;
185 			if (escaped) {
186 				if (ch == '%' || ch == '\\')
187 					*--p = ch;
188 				escaped = FALSE;
189 				continue;
190 			}
191 			if (ch == '\\') {
192 				escaped = TRUE;
193 				continue;
194 			}
195 			if (ch == '%') {
196 				*input_data++ = '\0';
197 				break;
198 			}
199 		}
200 		*p = '\0';
201 	}
202 
203 	/* fork again, this time so we can exec the user's command.
204 	 */
205 	switch (jobpid = fork()) {
206 	case -1:
207 		log_it("CRON", getpid(), "error", "can't fork");
208 		exit(ERROR_EXIT);
209 		/*NOTREACHED*/
210 	case 0:
211 		Debug(DPROC, ("[%d] grandchild process fork()'ed\n",
212 			      getpid()))
213 
214 		if (e->uid == ROOT_UID)
215 			Jitter = RootJitter;
216 		if (Jitter != 0) {
217 			srandom(getpid());
218 			sleep(random() % Jitter);
219 		}
220 
221 		/* write a log message.  we've waited this long to do it
222 		 * because it was not until now that we knew the PID that
223 		 * the actual user command shell was going to get and the
224 		 * PID is part of the log message.
225 		 */
226 		if ((e->flags & DONT_LOG) == 0) {
227 			char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
228 
229 			log_it(usernm, getpid(), "CMD", x);
230 			free(x);
231 		}
232 
233 		/* that's the last thing we'll log.  close the log files.
234 		 */
235 #ifdef SYSLOG
236 		closelog();
237 #endif
238 
239 		/* get new pgrp, void tty, etc.
240 		 */
241 		(void) setsid();
242 
243 		/* close the pipe ends that we won't use.  this doesn't affect
244 		 * the parent, who has to read and write them; it keeps the
245 		 * kernel from recording us as a potential client TWICE --
246 		 * which would keep it from sending SIGPIPE in otherwise
247 		 * appropriate circumstances.
248 		 */
249 		close(stdin_pipe[WRITE_PIPE]);
250 		close(stdout_pipe[READ_PIPE]);
251 
252 		/* grandchild process.  make std{in,out} be the ends of
253 		 * pipes opened by our daddy; make stderr go to stdout.
254 		 */
255 		close(STDIN);	dup2(stdin_pipe[READ_PIPE], STDIN);
256 		close(STDOUT);	dup2(stdout_pipe[WRITE_PIPE], STDOUT);
257 		close(STDERR);	dup2(STDOUT, STDERR);
258 
259 		/* close the pipes we just dup'ed.  The resources will remain.
260 		 */
261 		close(stdin_pipe[READ_PIPE]);
262 		close(stdout_pipe[WRITE_PIPE]);
263 
264 		environ = NULL;
265 
266 # if defined(LOGIN_CAP)
267 		/* Set user's entire context, but note that PATH will
268 		 * be overridden later
269 		 */
270 		if ((pwd = getpwnam(usernm)) == NULL)
271 			pwd = getpwuid(e->uid);
272 		lc = NULL;
273 		if (pwd != NULL) {
274 			if (pwd->pw_dir != NULL
275 			    && pwd->pw_dir[0] != '\0') {
276 				homedir = strdup(pwd->pw_dir);
277 				if (homedir == NULL) {
278 					warn("strdup");
279 					_exit(ERROR_EXIT);
280 				}
281 			}
282 			pwd->pw_gid = e->gid;
283 			if (e->class != NULL)
284 				lc = login_getclass(e->class);
285 		}
286 		if (pwd &&
287 		    setusercontext(lc, pwd, e->uid,
288 			    LOGIN_SETALL) == 0)
289 			(void) endpwent();
290 		else {
291 			/* fall back to the old method */
292 			(void) endpwent();
293 # endif
294 			/* set our directory, uid and gid.  Set gid first,
295 			 * since once we set uid, we've lost root privileges.
296 			 */
297 			if (setgid(e->gid) != 0) {
298 				log_it(usernm, getpid(),
299 				    "error", "setgid failed");
300 				_exit(ERROR_EXIT);
301 			}
302 			if (initgroups(usernm, e->gid) != 0) {
303 				log_it(usernm, getpid(),
304 				    "error", "initgroups failed");
305 				_exit(ERROR_EXIT);
306 			}
307 			if (setlogin(usernm) != 0) {
308 				log_it(usernm, getpid(),
309 				    "error", "setlogin failed");
310 				_exit(ERROR_EXIT);
311 			}
312 			if (setuid(e->uid) != 0) {
313 				log_it(usernm, getpid(),
314 				    "error", "setuid failed");
315 				_exit(ERROR_EXIT);
316 			}
317 			/* we aren't root after this..*/
318 #if defined(LOGIN_CAP)
319 		}
320 		if (lc != NULL)
321 			login_close(lc);
322 #endif
323 
324 		/* For compatibility, we chdir to the value of HOME if it was
325 		 * specified explicitly in the crontab file, but not if it was
326 		 * set in the environment by some other mechanism. We chdir to
327 		 * the homedir given by the pw entry otherwise.
328 		 *
329 		 * If !LOGIN_CAP, then HOME is always set in e->envp.
330 		 *
331 		 * XXX: probably should also consult PAM.
332 		 */
333 		{
334 			char	*new_home = env_get("HOME", e->envp);
335 			if (new_home != NULL && new_home[0] != '\0')
336 				chdir(new_home);
337 			else if (homedir != NULL)
338 				chdir(homedir);
339 			else
340 				chdir("/");
341 		}
342 
343 		/* exec the command. Note that SHELL is not respected from
344 		 * either login.conf or pw_shell, only an explicit setting
345 		 * in the crontab. (default of _PATH_BSHELL is supplied when
346 		 * setting up the entry)
347 		 */
348 		{
349 			char	*shell = env_get("SHELL", e->envp);
350 			char	**p;
351 
352 			/* Apply the environment from the entry, overriding
353 			 * existing values (this will always set LOGNAME and
354 			 * SHELL). putenv should not fail unless malloc does.
355 			 */
356 			for (p = e->envp; *p; ++p) {
357 				if (putenv(*p) != 0) {
358 					warn("putenv");
359 					_exit(ERROR_EXIT);
360 				}
361 			}
362 
363 			/* HOME in login.conf overrides pw, and HOME in the
364 			 * crontab overrides both. So set pw's value only if
365 			 * nothing was already set (overwrite==0).
366 			 */
367 			if (homedir != NULL
368 			    && setenv("HOME", homedir, 0) < 0) {
369 				warn("setenv(HOME)");
370 				_exit(ERROR_EXIT);
371 			}
372 
373 			/* PATH in login.conf is respected, but the crontab
374 			 * overrides; set a default value only if nothing
375 			 * already set.
376 			 */
377 			if (setenv("PATH", _PATH_DEFPATH, 0) < 0) {
378 				warn("setenv(PATH)");
379 				_exit(ERROR_EXIT);
380 			}
381 
382 # if DEBUGGING
383 			if (DebugFlags & DTEST) {
384 				fprintf(stderr,
385 				"debug DTEST is on, not exec'ing command.\n");
386 				fprintf(stderr,
387 				"\tcmd='%s' shell='%s'\n", e->cmd, shell);
388 				_exit(OK_EXIT);
389 			}
390 # endif /*DEBUGGING*/
391 			execl(shell, shell, "-c", e->cmd, (char *)NULL);
392 			warn("execl: couldn't exec `%s'", shell);
393 			_exit(ERROR_EXIT);
394 		}
395 		break;
396 	default:
397 		/* parent process */
398 		break;
399 	}
400 
401 	/* middle process, child of original cron, parent of process running
402 	 * the user's command.
403 	 */
404 
405 	Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
406 
407 	/* close the ends of the pipe that will only be referenced in the
408 	 * grandchild process...
409 	 */
410 	close(stdin_pipe[READ_PIPE]);
411 	close(stdout_pipe[WRITE_PIPE]);
412 
413 	/*
414 	 * write, to the pipe connected to child's stdin, any input specified
415 	 * after a % in the crontab entry.  while we copy, convert any
416 	 * additional %'s to newlines.  when done, if some characters were
417 	 * written and the last one wasn't a newline, write a newline.
418 	 *
419 	 * Note that if the input data won't fit into one pipe buffer (2K
420 	 * or 4K on most BSD systems), and the child doesn't read its stdin,
421 	 * we would block here.  thus we must fork again.
422 	 */
423 
424 	if (*input_data && (stdinjob = fork()) == 0) {
425 		FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
426 		int need_newline = FALSE;
427 		int escaped = FALSE;
428 		int ch;
429 
430 		if (out == NULL) {
431 			warn("fdopen failed in child2");
432 			_exit(ERROR_EXIT);
433 		}
434 
435 		Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
436 
437 		/* close the pipe we don't use, since we inherited it and
438 		 * are part of its reference count now.
439 		 */
440 		close(stdout_pipe[READ_PIPE]);
441 
442 		/* translation:
443 		 *	\% -> %
444 		 *	%  -> \n
445 		 *	\x -> \x	for all x != %
446 		 */
447 		while ((ch = *input_data++) != '\0') {
448 			if (escaped) {
449 				if (ch != '%')
450 					putc('\\', out);
451 			} else {
452 				if (ch == '%')
453 					ch = '\n';
454 			}
455 
456 			if (!(escaped = (ch == '\\'))) {
457 				putc(ch, out);
458 				need_newline = (ch != '\n');
459 			}
460 		}
461 		if (escaped)
462 			putc('\\', out);
463 		if (need_newline)
464 			putc('\n', out);
465 
466 		/* close the pipe, causing an EOF condition.  fclose causes
467 		 * stdin_pipe[WRITE_PIPE] to be closed, too.
468 		 */
469 		fclose(out);
470 
471 		Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
472 		exit(0);
473 	}
474 
475 	/* close the pipe to the grandkiddie's stdin, since its wicked uncle
476 	 * ernie back there has it open and will close it when he's done.
477 	 */
478 	close(stdin_pipe[WRITE_PIPE]);
479 
480 	/*
481 	 * read output from the grandchild.  it's stderr has been redirected to
482 	 * it's stdout, which has been redirected to our pipe.  if there is any
483 	 * output, we'll be mailing it to the user whose crontab this is...
484 	 * when the grandchild exits, we'll get EOF.
485 	 */
486 
487 	Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
488 
489 	/*local*/{
490 		FILE *in = fdopen(stdout_pipe[READ_PIPE], "r");
491 		int ch;
492 
493 		if (in == NULL) {
494 			warn("fdopen failed in child");
495 			_exit(ERROR_EXIT);
496 		}
497 
498 		mail = NULL;
499 
500 		ch = getc(in);
501 		if (ch != EOF) {
502 			Debug(DPROC|DEXT,
503 				("[%d] got data (%x:%c) from grandchild\n",
504 					getpid(), ch, ch))
505 
506 			/* get name of recipient.  this is MAILTO if set to a
507 			 * valid local username; USER otherwise.
508 			 */
509 			if (mailto == NULL) {
510 				/* MAILTO not present, set to USER,
511 				 * unless globally overridden.
512 				 */
513 				if (defmailto)
514 					mailto = defmailto;
515 				else
516 					mailto = usernm;
517 			}
518 			if (mailto && *mailto == '\0')
519 				mailto = NULL;
520 
521 			/* if we are supposed to be mailing, MAILTO will
522 			 * be non-NULL.  only in this case should we set
523 			 * up the mail command and subjects and stuff...
524 			 */
525 
526 			if (mailto) {
527 				char	**env;
528 				char	mailcmd[MAX_COMMAND];
529 				char	hostname[MAXHOSTNAMELEN];
530 
531 				if (gethostname(hostname, MAXHOSTNAMELEN) == -1)
532 					hostname[0] = '\0';
533 				hostname[sizeof(hostname) - 1] = '\0';
534 				if (snprintf(mailcmd, sizeof(mailcmd), MAILFMT,
535 				    MAILARG) >= sizeof(mailcmd)) {
536 					warnx("mail command too long");
537 					(void) _exit(ERROR_EXIT);
538 				}
539 				if (!(mail = cron_popen(mailcmd, "w", e, &mailpid))) {
540 					warn("%s", mailcmd);
541 					(void) _exit(ERROR_EXIT);
542 				}
543 				if (mailfrom == NULL || *mailfrom == '\0')
544 					fprintf(mail, "From: Cron Daemon <%s@%s>\n",
545 					    usernm, hostname);
546 				else
547 					fprintf(mail, "From: Cron Daemon <%s>\n",
548 					    mailfrom);
549 				fprintf(mail, "To: %s\n", mailto);
550 				fprintf(mail, "Subject: Cron <%s@%s> %s\n",
551 					usernm, first_word(hostname, "."),
552 					e->cmd);
553 #ifdef MAIL_DATE
554 				fprintf(mail, "Date: %s\n",
555 					arpadate(&TargetTime));
556 #endif /*MAIL_DATE*/
557 				for (env = e->envp;  *env;  env++)
558 					fprintf(mail, "X-Cron-Env: <%s>\n",
559 						*env);
560 				fprintf(mail, "\n");
561 
562 				/* this was the first char from the pipe
563 				 */
564 				putc(ch, mail);
565 			}
566 
567 			/* we have to read the input pipe no matter whether
568 			 * we mail or not, but obviously we only write to
569 			 * mail pipe if we ARE mailing.
570 			 */
571 
572 			while (EOF != (ch = getc(in))) {
573 				bytes++;
574 				if (mail)
575 					putc(ch, mail);
576 			}
577 		}
578 		/*if data from grandchild*/
579 
580 		Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
581 
582 		/* also closes stdout_pipe[READ_PIPE] */
583 		fclose(in);
584 	}
585 
586 	/* wait for children to die.
587 	 */
588 	if (jobpid > 0) {
589 		WAIT_T waiter;
590 
591 		waiter = wait_on_child(jobpid, "grandchild command job");
592 
593 		/* If everything went well, and -n was set, _and_ we have mail,
594 		 * we won't be mailing... so shoot the messenger!
595 		 */
596 		if (WIFEXITED(waiter) && WEXITSTATUS(waiter) == 0
597 		    && (e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR
598 		    && mail) {
599 			Debug(DPROC, ("[%d] %s executed successfully, mail suppressed\n",
600 				getpid(), "grandchild command job"))
601 			kill(mailpid, SIGKILL);
602 			(void)fclose(mail);
603 			mail = NULL;
604 		}
605 
606 		/* only close pipe if we opened it -- i.e., we're
607 		 * mailing...
608 		 */
609 
610 		if (mail) {
611 			Debug(DPROC, ("[%d] closing pipe to mail\n",
612 				getpid()))
613 			/* Note: the pclose will probably see
614 			 * the termination of the grandchild
615 			 * in addition to the mail process, since
616 			 * it (the grandchild) is likely to exit
617 			 * after closing its stdout.
618 			 */
619 			status = cron_pclose(mail);
620 
621 			/* if there was output and we could not mail it,
622 			 * log the facts so the poor user can figure out
623 			 * what's going on.
624 			 */
625 			if (status) {
626 				char buf[MAX_TEMPSTR];
627 
628 				snprintf(buf, sizeof(buf),
629 			"mailed %d byte%s of output but got status 0x%04x\n",
630 					bytes, (bytes==1)?"":"s",
631 					status);
632 				log_it(usernm, getpid(), "MAIL", buf);
633 			}
634 		}
635 	}
636 
637 	if (*input_data && stdinjob > 0)
638 		wait_on_child(stdinjob, "grandchild stdinjob");
639 }
640 
641 static WAIT_T
642 wait_on_child(PID_T childpid, const char *name)
643 {
644 	WAIT_T waiter;
645 	PID_T pid;
646 
647 	Debug(DPROC, ("[%d] waiting for %s (%d) to finish\n",
648 		getpid(), name, childpid))
649 
650 #ifdef POSIX
651 	while ((pid = waitpid(childpid, &waiter, 0)) < 0 && errno == EINTR)
652 #else
653 	while ((pid = wait4(childpid, &waiter, 0, NULL)) < 0 && errno == EINTR)
654 #endif
655 		;
656 
657 	if (pid < OK)
658 		return waiter;
659 
660 	Debug(DPROC, ("[%d] %s (%d) finished, status=%04x",
661 		getpid(), name, pid, WEXITSTATUS(waiter)))
662 	if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
663 		Debug(DPROC, (", dumped core"))
664 	Debug(DPROC, ("\n"))
665 
666 	return waiter;
667 }
668