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