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