do_command.c revision 129280
1251881Speter/* Copyright 1988,1990,1993,1994 by Paul Vixie
2251881Speter * All rights reserved
3251881Speter *
4251881Speter * Distribute freely, except: don't remove my name from the source or
5251881Speter * documentation (don't take credit for my work), mark your changes (don't
6251881Speter * get me blamed for your possible bugs), don't alter or remove this
7251881Speter * notice.  May be sold if buildable source is provided to buyer.  No
8251881Speter * warrantee of any kind, express or implied, is included with this
9251881Speter * software; use at your own risk, responsibility for damages (if any) to
10251881Speter * anyone resulting from the use of this software rests entirely with the
11251881Speter * user.
12251881Speter *
13251881Speter * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
14251881Speter * I'll try to keep a version up to date.  I can be reached as follows:
15251881Speter * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
16251881Speter */
17251881Speter
18251881Speter#if !defined(lint) && !defined(LINT)
19251881Speterstatic const char rcsid[] =
20251881Speter  "$FreeBSD: head/usr.sbin/cron/cron/do_command.c 129280 2004-05-16 19:29:33Z yar $";
21251881Speter#endif
22251881Speter
23251881Speter
24251881Speter#include "cron.h"
25251881Speter#include <sys/signal.h>
26251881Speter#if defined(sequent)
27251881Speter# include <sys/universe.h>
28251881Speter#endif
29251881Speter#if defined(SYSLOG)
30251881Speter# include <syslog.h>
31251881Speter#endif
32251881Speter#if defined(LOGIN_CAP)
33251881Speter# include <login_cap.h>
34251881Speter#endif
35251881Speter
36251881Speter
37251881Speterstatic void		child_process __P((entry *, user *)),
38251881Speter			do_univ __P((user *));
39251881Speter
40251881Speter
41251881Spetervoid
42289180Speterdo_command(e, u)
43251881Speter	entry	*e;
44251881Speter	user	*u;
45251881Speter{
46251881Speter	Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
47289180Speter		getpid(), e->cmd, u->name, e->uid, e->gid))
48251881Speter
49289180Speter	/* fork to become asynchronous -- parent process is done immediately,
50251881Speter	 * and continues to run the normal cron code, which means return to
51251881Speter	 * tick().  the child and grandchild don't leave this function, alive.
52251881Speter	 *
53251881Speter	 * vfork() is unsuitable, since we have much to do, and the parent
54251881Speter	 * needs to be able to run off and fork other processes.
55251881Speter	 */
56251881Speter	switch (fork()) {
57289180Speter	case -1:
58289180Speter		log_it("CRON",getpid(),"error","can't fork");
59289180Speter		break;
60289180Speter	case 0:
61289180Speter		/* child process */
62251881Speter		acquire_daemonlock(1);
63251881Speter		child_process(e, u);
64251881Speter		Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
65251881Speter		_exit(OK_EXIT);
66251881Speter		break;
67251881Speter	default:
68251881Speter		/* parent process */
69251881Speter		break;
70251881Speter	}
71251881Speter	Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
72251881Speter}
73251881Speter
74251881Speter
75251881Speterstatic void
76251881Speterchild_process(e, u)
77251881Speter	entry	*e;
78251881Speter	user	*u;
79251881Speter{
80251881Speter	int		stdin_pipe[2], stdout_pipe[2];
81251881Speter	register char	*input_data;
82251881Speter	char		*usernm, *mailto;
83251881Speter	int		children = 0;
84251881Speter# if defined(LOGIN_CAP)
85251881Speter	struct passwd	*pwd;
86251881Speter	login_cap_t *lc;
87251881Speter# endif
88251881Speter
89251881Speter	Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
90251881Speter
91251881Speter	/* mark ourselves as different to PS command watchers by upshifting
92251881Speter	 * our program name.  This has no effect on some kernels.
93251881Speter	 */
94251881Speter	setproctitle("running job");
95251881Speter
96251881Speter	/* discover some useful and important environment settings
97251881Speter	 */
98251881Speter	usernm = env_get("LOGNAME", e->envp);
99251881Speter	mailto = env_get("MAILTO", e->envp);
100251881Speter
101251881Speter#ifdef USE_SIGCHLD
102251881Speter	/* our parent is watching for our death by catching SIGCHLD.  we
103251881Speter	 * do not care to watch for our children's deaths this way -- we
104251881Speter	 * use wait() explictly.  so we have to disable the signal (which
105251881Speter	 * was inherited from the parent).
106251881Speter	 */
107251881Speter	(void) signal(SIGCHLD, SIG_DFL);
108251881Speter#else
109251881Speter	/* on system-V systems, we are ignoring SIGCLD.  we have to stop
110289180Speter	 * ignoring it now or the wait() in cron_pclose() won't work.
111251881Speter	 * because of this, we have to wait() for our children here, as well.
112251881Speter	 */
113251881Speter	(void) signal(SIGCLD, SIG_DFL);
114251881Speter#endif /*BSD*/
115251881Speter
116251881Speter	/* create some pipes to talk to our future child
117251881Speter	 */
118251881Speter	pipe(stdin_pipe);	/* child's stdin */
119251881Speter	pipe(stdout_pipe);	/* child's stdout */
120251881Speter
121289180Speter	/* since we are a forked process, we can diddle the command string
122289180Speter	 * we were passed -- nobody else is going to use it again, right?
123289180Speter	 *
124289180Speter	 * if a % is present in the command, previous characters are the
125251881Speter	 * command, and subsequent characters are the additional input to
126251881Speter	 * the command.  Subsequent %'s will be transformed into newlines,
127251881Speter	 * but that happens later.
128251881Speter	 *
129251881Speter	 * If there are escaped %'s, remove the escape character.
130251881Speter	 */
131251881Speter	/*local*/{
132289180Speter		register int escaped = FALSE;
133289180Speter		register int ch;
134251881Speter		register char *p;
135251881Speter
136289180Speter		for (input_data = p = e->cmd; (ch = *input_data);
137251881Speter		     input_data++, p++) {
138251881Speter			if (p != input_data)
139251881Speter			    *p = ch;
140251881Speter			if (escaped) {
141251881Speter				if (ch == '%' || ch == '\\')
142251881Speter					*--p = ch;
143251881Speter				escaped = FALSE;
144251881Speter				continue;
145251881Speter			}
146251881Speter			if (ch == '\\') {
147251881Speter				escaped = TRUE;
148251881Speter				continue;
149251881Speter			}
150251881Speter			if (ch == '%') {
151251881Speter				*input_data++ = '\0';
152251881Speter				break;
153251881Speter			}
154251881Speter		}
155251881Speter		*p = '\0';
156257936Speter	}
157251881Speter
158251881Speter	/* fork again, this time so we can exec the user's command.
159251881Speter	 */
160251881Speter	switch (vfork()) {
161251881Speter	case -1:
162251881Speter		log_it("CRON",getpid(),"error","can't vfork");
163251881Speter		exit(ERROR_EXIT);
164251881Speter		/*NOTREACHED*/
165251881Speter	case 0:
166289180Speter		Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n",
167251881Speter			      getpid()))
168251881Speter
169251881Speter		if (e->uid == ROOT_UID)
170251881Speter			Jitter = RootJitter;
171251881Speter		if (Jitter != 0) {
172289180Speter			srandom(getpid());
173251881Speter			sleep(random() % Jitter);
174251881Speter		}
175251881Speter
176251881Speter		/* write a log message.  we've waited this long to do it
177251881Speter		 * because it was not until now that we knew the PID that
178251881Speter		 * the actual user command shell was going to get and the
179251881Speter		 * PID is part of the log message.
180251881Speter		 */
181251881Speter		/*local*/{
182251881Speter			char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
183251881Speter
184251881Speter			log_it(usernm, getpid(), "CMD", x);
185251881Speter			free(x);
186251881Speter		}
187251881Speter
188251881Speter		/* that's the last thing we'll log.  close the log files.
189251881Speter		 */
190251881Speter#ifdef SYSLOG
191251881Speter		closelog();
192251881Speter#endif
193251881Speter
194289180Speter		/* get new pgrp, void tty, etc.
195251881Speter		 */
196251881Speter		(void) setsid();
197251881Speter
198251881Speter		/* close the pipe ends that we won't use.  this doesn't affect
199251881Speter		 * the parent, who has to read and write them; it keeps the
200251881Speter		 * kernel from recording us as a potential client TWICE --
201251881Speter		 * which would keep it from sending SIGPIPE in otherwise
202251881Speter		 * appropriate circumstances.
203251881Speter		 */
204251881Speter		close(stdin_pipe[WRITE_PIPE]);
205289180Speter		close(stdout_pipe[READ_PIPE]);
206251881Speter
207251881Speter		/* grandchild process.  make std{in,out} be the ends of
208251881Speter		 * pipes opened by our daddy; make stderr go to stdout.
209251881Speter		 */
210251881Speter		close(STDIN);	dup2(stdin_pipe[READ_PIPE], STDIN);
211251881Speter		close(STDOUT);	dup2(stdout_pipe[WRITE_PIPE], STDOUT);
212251881Speter		close(STDERR);	dup2(STDOUT, STDERR);
213251881Speter
214251881Speter		/* close the pipes we just dup'ed.  The resources will remain.
215289180Speter		 */
216289180Speter		close(stdin_pipe[READ_PIPE]);
217289180Speter		close(stdout_pipe[WRITE_PIPE]);
218251881Speter
219251881Speter		/* set our login universe.  Do this in the grandchild
220251881Speter		 * so that the child can invoke /usr/lib/sendmail
221251881Speter		 * without surprises.
222251881Speter		 */
223251881Speter		do_univ(u);
224251881Speter
225251881Speter# if defined(LOGIN_CAP)
226251881Speter		/* Set user's entire context, but skip the environment
227251881Speter		 * as cron provides a separate interface for this
228251881Speter		 */
229251881Speter		if ((pwd = getpwnam(usernm)) == NULL)
230251881Speter			pwd = getpwuid(e->uid);
231251881Speter		lc = NULL;
232251881Speter		if (pwd != NULL) {
233251881Speter			pwd->pw_gid = e->gid;
234251881Speter			if (e->class != NULL)
235251881Speter				lc = login_getclass(e->class);
236251881Speter		}
237251881Speter		if (pwd &&
238251881Speter		    setusercontext(lc, pwd, e->uid,
239251881Speter			    LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0)
240251881Speter			(void) endpwent();
241251881Speter		else {
242251881Speter			/* fall back to the old method */
243251881Speter			(void) endpwent();
244251881Speter# endif
245251881Speter			/* set our directory, uid and gid.  Set gid first,
246251881Speter			 * since once we set uid, we've lost root privledges.
247251881Speter			 */
248251881Speter			setgid(e->gid);
249251881Speter# if defined(BSD)
250251881Speter			initgroups(usernm, e->gid);
251251881Speter# endif
252251881Speter			setlogin(usernm);
253289180Speter			setuid(e->uid);		/* we aren't root after this..*/
254289180Speter#if defined(LOGIN_CAP)
255289180Speter		}
256251881Speter		if (lc != NULL)
257289180Speter			login_close(lc);
258251881Speter#endif
259251881Speter		chdir(env_get("HOME", e->envp));
260251881Speter
261251881Speter		/* exec the command.
262251881Speter		 */
263251881Speter		{
264251881Speter			char	*shell = env_get("SHELL", e->envp);
265251881Speter
266289180Speter# if DEBUGGING
267289180Speter			if (DebugFlags & DTEST) {
268289180Speter				fprintf(stderr,
269251881Speter				"debug DTEST is on, not exec'ing command.\n");
270251881Speter				fprintf(stderr,
271251881Speter				"\tcmd='%s' shell='%s'\n", e->cmd, shell);
272251881Speter				_exit(OK_EXIT);
273251881Speter			}
274251881Speter# endif /*DEBUGGING*/
275251881Speter			execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);
276251881Speter			warn("execl: couldn't exec `%s'", shell);
277251881Speter			_exit(ERROR_EXIT);
278251881Speter		}
279251881Speter		break;
280251881Speter	default:
281251881Speter		/* parent process */
282251881Speter		break;
283251881Speter	}
284251881Speter
285251881Speter	children++;
286251881Speter
287251881Speter	/* middle process, child of original cron, parent of process running
288251881Speter	 * the user's command.
289251881Speter	 */
290251881Speter
291251881Speter	Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
292251881Speter
293251881Speter	/* close the ends of the pipe that will only be referenced in the
294251881Speter	 * grandchild process...
295251881Speter	 */
296251881Speter	close(stdin_pipe[READ_PIPE]);
297251881Speter	close(stdout_pipe[WRITE_PIPE]);
298251881Speter
299251881Speter	/*
300251881Speter	 * write, to the pipe connected to child's stdin, any input specified
301251881Speter	 * after a % in the crontab entry.  while we copy, convert any
302251881Speter	 * additional %'s to newlines.  when done, if some characters were
303251881Speter	 * written and the last one wasn't a newline, write a newline.
304251881Speter	 *
305251881Speter	 * Note that if the input data won't fit into one pipe buffer (2K
306251881Speter	 * or 4K on most BSD systems), and the child doesn't read its stdin,
307251881Speter	 * we would block here.  thus we must fork again.
308251881Speter	 */
309251881Speter
310251881Speter	if (*input_data && fork() == 0) {
311289180Speter		register FILE	*out = fdopen(stdin_pipe[WRITE_PIPE], "w");
312289180Speter		register int	need_newline = FALSE;
313289180Speter		register int	escaped = FALSE;
314251881Speter		register int	ch;
315251881Speter
316251881Speter		if (out == NULL) {
317251881Speter			warn("fdopen failed in child2");
318251881Speter			_exit(ERROR_EXIT);
319251881Speter		}
320251881Speter
321251881Speter		Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
322251881Speter
323251881Speter		/* close the pipe we don't use, since we inherited it and
324251881Speter		 * are part of its reference count now.
325289180Speter		 */
326289180Speter		close(stdout_pipe[READ_PIPE]);
327289180Speter
328289180Speter		/* translation:
329289180Speter		 *	\% -> %
330289180Speter		 *	%  -> \n
331289180Speter		 *	\x -> \x	for all x != %
332289180Speter		 */
333289180Speter		while ((ch = *input_data++)) {
334289180Speter			if (escaped) {
335289180Speter				if (ch != '%')
336251881Speter					putc('\\', out);
337251881Speter			} else {
338251881Speter				if (ch == '%')
339251881Speter					ch = '\n';
340251881Speter			}
341251881Speter
342251881Speter			if (!(escaped = (ch == '\\'))) {
343251881Speter				putc(ch, out);
344251881Speter				need_newline = (ch != '\n');
345251881Speter			}
346251881Speter		}
347251881Speter		if (escaped)
348251881Speter			putc('\\', out);
349251881Speter		if (need_newline)
350251881Speter			putc('\n', out);
351251881Speter
352251881Speter		/* close the pipe, causing an EOF condition.  fclose causes
353251881Speter		 * stdin_pipe[WRITE_PIPE] to be closed, too.
354251881Speter		 */
355251881Speter		fclose(out);
356251881Speter
357251881Speter		Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
358251881Speter		exit(0);
359251881Speter	}
360289180Speter
361289180Speter	/* close the pipe to the grandkiddie's stdin, since its wicked uncle
362289180Speter	 * ernie back there has it open and will close it when he's done.
363289180Speter	 */
364289180Speter	close(stdin_pipe[WRITE_PIPE]);
365289180Speter
366289180Speter	children++;
367289180Speter
368289180Speter	/*
369289180Speter	 * read output from the grandchild.  it's stderr has been redirected to
370289180Speter	 * it's stdout, which has been redirected to our pipe.  if there is any
371289180Speter	 * output, we'll be mailing it to the user whose crontab this is...
372289180Speter	 * when the grandchild exits, we'll get EOF.
373251881Speter	 */
374251881Speter
375251881Speter	Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
376251881Speter
377251881Speter	/*local*/{
378251881Speter		register FILE	*in = fdopen(stdout_pipe[READ_PIPE], "r");
379251881Speter		register int	ch;
380251881Speter
381251881Speter		if (in == NULL) {
382251881Speter			warn("fdopen failed in child");
383251881Speter			_exit(ERROR_EXIT);
384251881Speter		}
385251881Speter
386251881Speter		ch = getc(in);
387251881Speter		if (ch != EOF) {
388251881Speter			register FILE	*mail;
389251881Speter			register int	bytes = 1;
390251881Speter			int		status = 0;
391251881Speter
392251881Speter			Debug(DPROC|DEXT,
393251881Speter				("[%d] got data (%x:%c) from grandchild\n",
394251881Speter					getpid(), ch, ch))
395251881Speter
396251881Speter			/* get name of recipient.  this is MAILTO if set to a
397251881Speter			 * valid local username; USER otherwise.
398251881Speter			 */
399251881Speter			if (mailto) {
400251881Speter				/* MAILTO was present in the environment
401251881Speter				 */
402251881Speter				if (!*mailto) {
403251881Speter					/* ... but it's empty. set to NULL
404251881Speter					 */
405251881Speter					mailto = NULL;
406251881Speter				}
407251881Speter			} else {
408251881Speter				/* MAILTO not present, set to USER.
409251881Speter				 */
410251881Speter				mailto = usernm;
411251881Speter			}
412251881Speter
413251881Speter			/* if we are supposed to be mailing, MAILTO will
414251881Speter			 * be non-NULL.  only in this case should we set
415289180Speter			 * up the mail command and subjects and stuff...
416251881Speter			 */
417289180Speter
418289180Speter			if (mailto) {
419289180Speter				register char	**env;
420289180Speter				auto char	mailcmd[MAX_COMMAND];
421289180Speter				auto char	hostname[MAXHOSTNAMELEN];
422251881Speter
423251881Speter				(void) gethostname(hostname, MAXHOSTNAMELEN);
424251881Speter				(void) snprintf(mailcmd, sizeof(mailcmd),
425251881Speter					       MAILARGS, MAILCMD);
426251881Speter				if (!(mail = cron_popen(mailcmd, "w", e))) {
427251881Speter					warn("%s", MAILCMD);
428251881Speter					(void) _exit(ERROR_EXIT);
429251881Speter				}
430251881Speter				fprintf(mail, "From: %s (Cron Daemon)\n", usernm);
431251881Speter				fprintf(mail, "To: %s\n", mailto);
432251881Speter				fprintf(mail, "Subject: Cron <%s@%s> %s\n",
433251881Speter					usernm, first_word(hostname, "."),
434251881Speter					e->cmd);
435251881Speter# if defined(MAIL_DATE)
436251881Speter				fprintf(mail, "Date: %s\n",
437251881Speter					arpadate(&TargetTime));
438251881Speter# endif /* MAIL_DATE */
439251881Speter				for (env = e->envp;  *env;  env++)
440251881Speter					fprintf(mail, "X-Cron-Env: <%s>\n",
441251881Speter						*env);
442251881Speter				fprintf(mail, "\n");
443289180Speter
444251881Speter				/* this was the first char from the pipe
445251881Speter				 */
446251881Speter				putc(ch, mail);
447251881Speter			}
448251881Speter
449251881Speter			/* we have to read the input pipe no matter whether
450251881Speter			 * we mail or not, but obviously we only write to
451251881Speter			 * mail pipe if we ARE mailing.
452251881Speter			 */
453251881Speter
454251881Speter			while (EOF != (ch = getc(in))) {
455251881Speter				bytes++;
456251881Speter				if (mailto)
457251881Speter					putc(ch, mail);
458251881Speter			}
459251881Speter
460251881Speter			/* only close pipe if we opened it -- i.e., we're
461251881Speter			 * mailing...
462251881Speter			 */
463251881Speter
464251881Speter			if (mailto) {
465251881Speter				Debug(DPROC, ("[%d] closing pipe to mail\n",
466251881Speter					getpid()))
467251881Speter				/* Note: the pclose will probably see
468251881Speter				 * the termination of the grandchild
469289180Speter				 * in addition to the mail process, since
470251881Speter				 * it (the grandchild) is likely to exit
471251881Speter				 * after closing its stdout.
472251881Speter				 */
473251881Speter				status = cron_pclose(mail);
474251881Speter			}
475251881Speter
476251881Speter			/* if there was output and we could not mail it,
477251881Speter			 * log the facts so the poor user can figure out
478251881Speter			 * what's going on.
479251881Speter			 */
480251881Speter			if (mailto && status) {
481251881Speter				char buf[MAX_TEMPSTR];
482251881Speter
483251881Speter				snprintf(buf, sizeof(buf),
484251881Speter			"mailed %d byte%s of output but got status 0x%04x\n",
485251881Speter					bytes, (bytes==1)?"":"s",
486251881Speter					status);
487251881Speter				log_it(usernm, getpid(), "MAIL", buf);
488251881Speter			}
489251881Speter
490251881Speter		} /*if data from grandchild*/
491251881Speter
492251881Speter		Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
493251881Speter
494251881Speter		fclose(in);	/* also closes stdout_pipe[READ_PIPE] */
495251881Speter	}
496251881Speter
497251881Speter	/* wait for children to die.
498251881Speter	 */
499251881Speter	for (;  children > 0;  children--)
500251881Speter	{
501251881Speter		WAIT_T		waiter;
502289180Speter		PID_T		pid;
503289180Speter
504289180Speter		Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
505289180Speter			getpid(), children))
506289180Speter		pid = wait(&waiter);
507289180Speter		if (pid < OK) {
508251881Speter			Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
509289180Speter				getpid()))
510289180Speter			break;
511289180Speter		}
512251881Speter		Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
513251881Speter			getpid(), pid, WEXITSTATUS(waiter)))
514251881Speter		if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
515251881Speter			Debug(DPROC, (", dumped core"))
516251881Speter		Debug(DPROC, ("\n"))
517251881Speter	}
518251881Speter}
519251881Speter
520251881Speter
521251881Speterstatic void
522251881Speterdo_univ(u)
523251881Speter	user	*u;
524251881Speter{
525251881Speter#if defined(sequent)
526251881Speter/* Dynix (Sequent) hack to put the user associated with
527251881Speter * the passed user structure into the ATT universe if
528251881Speter * necessary.  We have to dig the gecos info out of
529251881Speter * the user's password entry to see if the magic
530251881Speter * "universe(att)" string is present.
531251881Speter */
532251881Speter
533251881Speter	struct	passwd	*p;
534251881Speter	char	*s;
535251881Speter	int	i;
536251881Speter
537251881Speter	p = getpwuid(u->uid);
538251881Speter	(void) endpwent();
539251881Speter
540251881Speter	if (p == NULL)
541251881Speter		return;
542251881Speter
543251881Speter	s = p->pw_gecos;
544289180Speter
545289180Speter	for (i = 0; i < 4; i++)
546251881Speter	{
547251881Speter		if ((s = strchr(s, ',')) == NULL)
548251881Speter			return;
549251881Speter		s++;
550251881Speter	}
551251881Speter	if (strcmp(s, "universe(att)"))
552251881Speter		return;
553251881Speter
554251881Speter	(void) universe(U_ATT);
555251881Speter#endif
556251881Speter}
557251881Speter