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)
19static 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
41static void		child_process(entry *, user *);
42
43static WAIT_T		wait_on_child(PID_T, const char *);
44
45extern char	*environ;
46
47void
48do_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
88static void
89child_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 overriden.
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
658static WAIT_T
659wait_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