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: src/usr.sbin/cron/cron/do_command.c,v 1.26 2006/06/11 21:13:49 maxim Exp $";
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
36#ifdef __APPLE__
37#include <stdlib.h>
38#include <IOKit/IOKitLib.h>
39#include <IOKit/pwr_mgt/IOPMLib.h>
40#include <IOKit/pwr_mgt/IOPM.h>
41#include <IOKit/IOReturn.h>
42#include <CoreFoundation/CFArray.h>
43#include <CoreFoundation/CFBase.h>
44#include <CoreFoundation/CFNumber.h>
45#include <CoreFoundation/CFData.h>
46#include <CoreFoundation/CoreFoundation.h>
47#include <mach/mach_init.h>        /* for bootstrap_port */
48#include <vproc.h>
49#include <vproc_priv.h>
50#include <bootstrap_priv.h>
51#endif /* __APPLE__ */
52
53static void		child_process __P((entry *, user *)),
54			do_univ __P((user *));
55
56#ifdef __APPLE__
57extern vproc_err_t _vproc_post_fork_ping(void);
58#endif
59
60void
61do_command(e, u)
62	entry	*e;
63	user	*u;
64{
65#ifdef __APPLE__
66	CFArrayRef cfarray;
67	static mach_port_t master = 0;
68	static io_connect_t pmcon = 0;
69
70	Debug(DPROC, ("[%d] do_command(%s, (%s,%s,%s))\n",
71		getpid(), e->cmd, u->name, e->uname, e->gname))
72
73	if( e->flags & NOT_BATTERY ) {
74		if( master == 0 ) {
75			IOMasterPort(bootstrap_port, &master);
76			pmcon = IOPMFindPowerManagement(master);
77		}
78
79		if( IOPMCopyBatteryInfo(master, &cfarray) == kIOReturnSuccess) {
80			CFDictionaryRef dict;
81			CFNumberRef cfnum;
82			int flags;
83
84			dict = CFArrayGetValueAtIndex(cfarray, 0);
85			cfnum = CFDictionaryGetValue(dict, CFSTR(kIOBatteryFlagsKey));
86			CFNumberGetValue(cfnum, kCFNumberLongType, &flags);
87
88			if( !(flags & kIOBatteryChargerConnect) ) {
89				return;
90			}
91		}
92	}
93#else
94	Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
95		getpid(), e->cmd, u->name, e->uid, e->gid))
96#endif
97
98	/* fork to become asynchronous -- parent process is done immediately,
99	 * and continues to run the normal cron code, which means return to
100	 * tick().  the child and grandchild don't leave this function, alive.
101	 *
102	 * vfork() is unsuitable, since we have much to do, and the parent
103	 * needs to be able to run off and fork other processes.
104	 */
105	switch (fork()) {
106	case -1:
107		log_it("CRON",getpid(),"error","can't fork");
108		break;
109	case 0:
110		/* child process */
111		pidfile_close(pfh);
112		child_process(e, u);
113		Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
114		_exit(OK_EXIT);
115		break;
116	default:
117		/* parent process */
118		break;
119	}
120	Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
121}
122
123
124static void
125child_process(e, u)
126	entry	*e;
127	user	*u;
128{
129	int		stdin_pipe[2], stdout_pipe[2];
130	register char	*input_data;
131	char		*usernm, *mailto;
132#ifdef __APPLE__
133	uid_t		uid = -1;
134	gid_t		gid = -1;
135	struct passwd	*pwd;
136#endif
137	int		children = 0;
138# if defined(LOGIN_CAP)
139	struct passwd	*pwd;
140	login_cap_t *lc;
141# endif
142
143	Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
144
145	/* mark ourselves as different to PS command watchers by upshifting
146	 * our program name.  This has no effect on some kernels.
147	 */
148#ifdef __APPLE__
149	setprogname("running job");
150#else
151	setproctitle("running job");
152#endif
153
154	/* discover some useful and important environment settings
155	 */
156#ifdef __APPLE__
157	usernm = e->uname;
158#else
159	usernm = env_get("LOGNAME", e->envp);
160#endif
161	mailto = env_get("MAILTO", e->envp);
162
163#ifdef USE_SIGCHLD
164	/* our parent is watching for our death by catching SIGCHLD.  we
165	 * do not care to watch for our children's deaths this way -- we
166	 * use wait() explictly.  so we have to disable the signal (which
167	 * was inherited from the parent).
168	 */
169	(void) signal(SIGCHLD, SIG_DFL);
170#else
171	/* on system-V systems, we are ignoring SIGCLD.  we have to stop
172	 * ignoring it now or the wait() in cron_pclose() won't work.
173	 * because of this, we have to wait() for our children here, as well.
174	 */
175	(void) signal(SIGCLD, SIG_DFL);
176#endif /*BSD*/
177
178	/* create some pipes to talk to our future child
179	 */
180	pipe(stdin_pipe);	/* child's stdin */
181	pipe(stdout_pipe);	/* child's stdout */
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#ifdef __APPLE__
223	switch (fork()) {
224#else
225	switch (vfork()) {
226#endif
227	case -1:
228		log_it("CRON",getpid(),"error","can't vfork");
229		exit(ERROR_EXIT);
230		/*NOTREACHED*/
231	case 0:
232		Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n",
233			      getpid()))
234
235#ifndef __APPLE__
236		if (e->uid == ROOT_UID)
237			Jitter = RootJitter;
238		if (Jitter != 0) {
239			srandom(getpid());
240			sleep(random() % Jitter);
241		}
242#endif
243
244		/* write a log message.  we've waited this long to do it
245		 * because it was not until now that we knew the PID that
246		 * the actual user command shell was going to get and the
247		 * PID is part of the log message.
248		 */
249		/*local*/{
250			char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
251
252			log_it(usernm, getpid(), "CMD", x);
253			free(x);
254		}
255
256		/* that's the last thing we'll log.  close the log files.
257		 */
258#ifdef SYSLOG
259		closelog();
260#endif
261
262		/* get new pgrp, void tty, etc.
263		 */
264		(void) setsid();
265
266		/* close the pipe ends that we won't use.  this doesn't affect
267		 * the parent, who has to read and write them; it keeps the
268		 * kernel from recording us as a potential client TWICE --
269		 * which would keep it from sending SIGPIPE in otherwise
270		 * appropriate circumstances.
271		 */
272		close(stdin_pipe[WRITE_PIPE]);
273		close(stdout_pipe[READ_PIPE]);
274
275		/* grandchild process.  make std{in,out} be the ends of
276		 * pipes opened by our daddy; make stderr go to stdout.
277		 */
278		close(STDIN);	dup2(stdin_pipe[READ_PIPE], STDIN);
279		close(STDOUT);	dup2(stdout_pipe[WRITE_PIPE], STDOUT);
280		close(STDERR);	dup2(STDOUT, STDERR);
281
282		/* close the pipes we just dup'ed.  The resources will remain.
283		 */
284		close(stdin_pipe[READ_PIPE]);
285		close(stdout_pipe[WRITE_PIPE]);
286
287		/* set our login universe.  Do this in the grandchild
288		 * so that the child can invoke /usr/lib/sendmail
289		 * without surprises.
290		 */
291		do_univ(u);
292
293#ifdef __APPLE__
294		/* Set user's entire context, but skip the environment
295		 * as cron provides a separate interface for this
296		 */
297		if ((pwd = getpwnam(e->uname))) {
298			char envstr[MAXPATHLEN + sizeof "HOME="];
299
300			uid = pwd->pw_uid;
301			gid = pwd->pw_gid;
302
303			if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) {
304				warn("user account expired: %s", e->uname);
305				_exit(ERROR_EXIT);
306			}
307
308			sprintf(envstr, "HOME=%s", pwd->pw_dir);
309			e->envp = env_set(e->envp, envstr);
310			if (e->envp == NULL) {
311				warn("env_set(%s)", envstr);
312				_exit(ERROR_EXIT);
313			}
314		} else {
315			warn("getpwnam(\"%s\")", e->uname);
316			_exit(ERROR_EXIT);
317		}
318
319		if (strlen(e->gname) > 0) {
320			struct group *gr = getgrnam(e->gname);
321			if (gr) {
322				gid = gr->gr_gid;
323			} else {
324				warn("getgrnam(\"%s\")", e->gname);
325				_exit(ERROR_EXIT);
326			}
327		}
328
329		/* move to the correct bootstrap */
330		/* similar to but simpler than pam_launchd */
331		mach_port_t puc = MACH_PORT_NULL;
332		kern_return_t kr = bootstrap_look_up_per_user(bootstrap_port, NULL, uid, &puc);
333		if (kr != BOOTSTRAP_SUCCESS) {
334			warnx("could not look up per-user bootstrap for uid %u", uid);
335			_exit(ERROR_EXIT);
336		}
337		mach_port_mod_refs(mach_task_self(), bootstrap_port, MACH_PORT_RIGHT_SEND, -1);
338		task_set_bootstrap_port(mach_task_self(), puc);
339		bootstrap_port = puc;
340		if (_vproc_post_fork_ping() != NULL) {
341			warnx("failed to setup exception ports");
342			_exit(ERROR_EXIT);
343		}
344#endif /* __APPLE__ */
345# if defined(LOGIN_CAP)
346		/* Set user's entire context, but skip the environment
347		 * as cron provides a separate interface for this
348		 */
349		if ((pwd = getpwnam(usernm)) == NULL)
350			pwd = getpwuid(e->uid);
351		lc = NULL;
352		if (pwd != NULL) {
353			pwd->pw_gid = e->gid;
354			if (e->class != NULL)
355				lc = login_getclass(e->class);
356		}
357		if (pwd &&
358		    setusercontext(lc, pwd, e->uid,
359			    LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0)
360			(void) endpwent();
361		else {
362			/* fall back to the old method */
363			(void) endpwent();
364# endif
365			/* set our directory, uid and gid.  Set gid first,
366			 * since once we set uid, we've lost root privileges.
367			 */
368#ifdef __APPLE__
369			if (setgid(gid) != 0) {
370#else
371			if (setgid(e->gid) != 0) {
372#endif
373				log_it(usernm, getpid(),
374				    "error", "setgid failed");
375				exit(ERROR_EXIT);
376			}
377# if defined(BSD)
378#ifdef __APPLE__
379			if (initgroups(usernm, gid) != 0) {
380#else
381			if (initgroups(usernm, e->gid) != 0) {
382#endif
383				log_it(usernm, getpid(),
384				    "error", "initgroups failed");
385				exit(ERROR_EXIT);
386			}
387# endif
388			if (setlogin(usernm) != 0) {
389				log_it(usernm, getpid(),
390				    "error", "setlogin failed");
391				exit(ERROR_EXIT);
392			}
393#ifdef __APPLE__
394			if (setuid(uid) != 0) {
395#else
396			if (setuid(e->uid) != 0) {
397#endif
398				log_it(usernm, getpid(),
399				    "error", "setuid failed");
400				exit(ERROR_EXIT);
401			}
402			/* we aren't root after this..*/
403#if defined(LOGIN_CAP)
404		}
405		if (lc != NULL)
406			login_close(lc);
407#endif
408		chdir(env_get("HOME", e->envp));
409
410		/* exec the command.
411		 */
412		{
413			char	*shell = env_get("SHELL", e->envp);
414
415# if DEBUGGING
416			if (DebugFlags & DTEST) {
417				fprintf(stderr,
418				"debug DTEST is on, not exec'ing command.\n");
419				fprintf(stderr,
420				"\tcmd='%s' shell='%s'\n", e->cmd, shell);
421				_exit(OK_EXIT);
422			}
423# endif /*DEBUGGING*/
424			execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);
425			warn("execl: couldn't exec `%s'", shell);
426			_exit(ERROR_EXIT);
427		}
428		break;
429	default:
430		/* parent process */
431		break;
432	}
433
434	children++;
435
436	/* middle process, child of original cron, parent of process running
437	 * the user's command.
438	 */
439
440	Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
441
442	/* close the ends of the pipe that will only be referenced in the
443	 * grandchild process...
444	 */
445	close(stdin_pipe[READ_PIPE]);
446	close(stdout_pipe[WRITE_PIPE]);
447
448	/*
449	 * write, to the pipe connected to child's stdin, any input specified
450	 * after a % in the crontab entry.  while we copy, convert any
451	 * additional %'s to newlines.  when done, if some characters were
452	 * written and the last one wasn't a newline, write a newline.
453	 *
454	 * Note that if the input data won't fit into one pipe buffer (2K
455	 * or 4K on most BSD systems), and the child doesn't read its stdin,
456	 * we would block here.  thus we must fork again.
457	 */
458
459	if (*input_data && fork() == 0) {
460		register FILE	*out = fdopen(stdin_pipe[WRITE_PIPE], "w");
461		register int	need_newline = FALSE;
462		register int	escaped = FALSE;
463		register int	ch;
464
465		if (out == NULL) {
466			warn("fdopen failed in child2");
467			_exit(ERROR_EXIT);
468		}
469
470		Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
471
472		/* close the pipe we don't use, since we inherited it and
473		 * are part of its reference count now.
474		 */
475		close(stdout_pipe[READ_PIPE]);
476
477		/* translation:
478		 *	\% -> %
479		 *	%  -> \n
480		 *	\x -> \x	for all x != %
481		 */
482		while ((ch = *input_data++)) {
483			if (escaped) {
484				if (ch != '%')
485					putc('\\', out);
486			} else {
487				if (ch == '%')
488					ch = '\n';
489			}
490
491			if (!(escaped = (ch == '\\'))) {
492				putc(ch, out);
493				need_newline = (ch != '\n');
494			}
495		}
496		if (escaped)
497			putc('\\', out);
498		if (need_newline)
499			putc('\n', out);
500
501		/* close the pipe, causing an EOF condition.  fclose causes
502		 * stdin_pipe[WRITE_PIPE] to be closed, too.
503		 */
504		fclose(out);
505
506		Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
507		exit(0);
508	}
509
510	/* close the pipe to the grandkiddie's stdin, since its wicked uncle
511	 * ernie back there has it open and will close it when he's done.
512	 */
513	close(stdin_pipe[WRITE_PIPE]);
514
515	children++;
516
517	/*
518	 * read output from the grandchild.  it's stderr has been redirected to
519	 * it's stdout, which has been redirected to our pipe.  if there is any
520	 * output, we'll be mailing it to the user whose crontab this is...
521	 * when the grandchild exits, we'll get EOF.
522	 */
523
524	Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
525
526	/*local*/{
527		register FILE	*in = fdopen(stdout_pipe[READ_PIPE], "r");
528		register int	ch;
529
530		if (in == NULL) {
531			warn("fdopen failed in child");
532			_exit(ERROR_EXIT);
533		}
534
535		ch = getc(in);
536		if (ch != EOF) {
537			register FILE	*mail = NULL;
538			register int	bytes = 1;
539			int		status = 0;
540
541			Debug(DPROC|DEXT,
542				("[%d] got data (%x:%c) from grandchild\n",
543					getpid(), ch, ch))
544
545			/* get name of recipient.  this is MAILTO if set to a
546			 * valid local username; USER otherwise.
547			 */
548			if (mailto) {
549				/* MAILTO was present in the environment
550				 */
551				if (!*mailto) {
552					/* ... but it's empty. set to NULL
553					 */
554					mailto = NULL;
555				}
556			} else {
557				/* MAILTO not present, set to USER.
558				 */
559				mailto = usernm;
560			}
561
562			/* if we are supposed to be mailing, MAILTO will
563			 * be non-NULL.  only in this case should we set
564			 * up the mail command and subjects and stuff...
565			 */
566
567			if (mailto) {
568				register char	**env;
569				auto char	mailcmd[MAX_COMMAND];
570				auto char	hostname[MAXHOSTNAMELEN];
571
572				(void) gethostname(hostname, MAXHOSTNAMELEN);
573				(void) snprintf(mailcmd, sizeof(mailcmd),
574					       MAILARGS, MAILCMD);
575				if (!(mail = cron_popen(mailcmd, "w", e))) {
576					warn("%s", MAILCMD);
577					(void) _exit(ERROR_EXIT);
578				}
579				fprintf(mail, "From: %s (Cron Daemon)\n", usernm);
580				fprintf(mail, "To: %s\n", mailto);
581				fprintf(mail, "Subject: Cron <%s@%s> %s\n",
582					usernm, first_word(hostname, "."),
583					e->cmd);
584# if defined(MAIL_DATE)
585				fprintf(mail, "Date: %s\n",
586					arpadate(&TargetTime));
587# endif /* MAIL_DATE */
588				for (env = e->envp;  *env;  env++)
589					fprintf(mail, "X-Cron-Env: <%s>\n",
590						*env);
591				fprintf(mail, "\n");
592
593				/* this was the first char from the pipe
594				 */
595				putc(ch, mail);
596			}
597
598			/* we have to read the input pipe no matter whether
599			 * we mail or not, but obviously we only write to
600			 * mail pipe if we ARE mailing.
601			 */
602
603			while (EOF != (ch = getc(in))) {
604				bytes++;
605				if (mailto)
606					putc(ch, mail);
607			}
608
609			/* only close pipe if we opened it -- i.e., we're
610			 * mailing...
611			 */
612
613			if (mailto) {
614				Debug(DPROC, ("[%d] closing pipe to mail\n",
615					getpid()))
616				/* Note: the pclose will probably see
617				 * the termination of the grandchild
618				 * in addition to the mail process, since
619				 * it (the grandchild) is likely to exit
620				 * after closing its stdout.
621				 */
622				status = cron_pclose(mail);
623			}
624
625			/* if there was output and we could not mail it,
626			 * log the facts so the poor user can figure out
627			 * what's going on.
628			 */
629			if (mailto && status) {
630				char buf[MAX_TEMPSTR];
631
632				snprintf(buf, sizeof(buf),
633			"mailed %d byte%s of output but got status 0x%04x\n",
634					bytes, (bytes==1)?"":"s",
635					status);
636				log_it(usernm, getpid(), "MAIL", buf);
637			}
638
639		} /*if data from grandchild*/
640
641		Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
642
643		fclose(in);	/* also closes stdout_pipe[READ_PIPE] */
644	}
645
646	/* wait for children to die.
647	 */
648	for (;  children > 0;  children--)
649	{
650		WAIT_T		waiter;
651		PID_T		pid;
652
653		Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
654			getpid(), children))
655		pid = wait(&waiter);
656		if (pid < OK) {
657			Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
658				getpid()))
659			break;
660		}
661		Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
662			getpid(), pid, WEXITSTATUS(waiter)))
663		if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
664			Debug(DPROC, (", dumped core"))
665		Debug(DPROC, ("\n"))
666	}
667}
668
669
670static void
671do_univ(u)
672	user	*u;
673{
674#ifdef __APPLE__
675	u = u; // avoid unused argument warning
676#endif
677#if defined(sequent)
678/* Dynix (Sequent) hack to put the user associated with
679 * the passed user structure into the ATT universe if
680 * necessary.  We have to dig the gecos info out of
681 * the user's password entry to see if the magic
682 * "universe(att)" string is present.
683 */
684
685	struct	passwd	*p;
686	char	*s;
687	int	i;
688
689	p = getpwuid(u->uid);
690	(void) endpwent();
691
692	if (p == NULL)
693		return;
694
695	s = p->pw_gecos;
696
697	for (i = 0; i < 4; i++)
698	{
699		if ((s = strchr(s, ',')) == NULL)
700			return;
701		s++;
702	}
703	if (strcmp(s, "universe(att)"))
704		return;
705
706	(void) universe(U_ATT);
707#endif
708}
709