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)
23static 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
36static void		child_process(entry *, user *);
37static WAIT_T		wait_on_child(PID_T, const char *);
38
39extern char	*environ;
40
41void
42do_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
80static void
81child_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
641static WAIT_T
642wait_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