do_command.c revision 30895
1243791Sdim/* Copyright 1988,1990,1993,1994 by Paul Vixie
2243791Sdim * All rights reserved
3243791Sdim *
4243791Sdim * Distribute freely, except: don't remove my name from the source or
5243791Sdim * documentation (don't take credit for my work), mark your changes (don't
6243791Sdim * get me blamed for your possible bugs), don't alter or remove this
7243791Sdim * notice.  May be sold if buildable source is provided to buyer.  No
8243791Sdim * warrantee of any kind, express or implied, is included with this
9243791Sdim * software; use at your own risk, responsibility for damages (if any) to
10252723Sdim * anyone resulting from the use of this software rests entirely with the
11243791Sdim * user.
12243791Sdim *
13243791Sdim * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
14243791Sdim * I'll try to keep a version up to date.  I can be reached as follows:
15243791Sdim * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
16243791Sdim */
17252723Sdim
18243791Sdim#if !defined(lint) && !defined(LINT)
19243791Sdimstatic const char rcsid[] =
20243791Sdim	"$Id: do_command.c,v 1.13 1997/09/15 06:39:06 charnier Exp $";
21243791Sdim#endif
22243791Sdim
23243791Sdim
24252723Sdim#include "cron.h"
25252723Sdim#include <sys/signal.h>
26243791Sdim#if defined(sequent)
27243791Sdim# include <sys/universe.h>
28243791Sdim#endif
29243791Sdim#if defined(SYSLOG)
30243791Sdim# include <syslog.h>
31243791Sdim#endif
32243791Sdim#if defined(LOGIN_CAP)
33243791Sdim# include <login_cap.h>
34243791Sdim#endif
35243791Sdim
36243791Sdim
37243791Sdimstatic void		child_process __P((entry *, user *)),
38243791Sdim			do_univ __P((user *));
39243791Sdim
40243791Sdim
41243791Sdimvoid
42243791Sdimdo_command(e, u)
43243791Sdim	entry	*e;
44252723Sdim	user	*u;
45243791Sdim{
46243791Sdim	Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
47243791Sdim		getpid(), e->cmd, u->name, e->uid, e->gid))
48243791Sdim
49243791Sdim	/* fork to become asynchronous -- parent process is done immediately,
50252723Sdim	 * and continues to run the normal cron code, which means return to
51252723Sdim	 * tick().  the child and grandchild don't leave this function, alive.
52252723Sdim	 *
53243791Sdim	 * vfork() is unsuitable, since we have much to do, and the parent
54243791Sdim	 * needs to be able to run off and fork other processes.
55243791Sdim	 */
56243791Sdim	switch (fork()) {
57243791Sdim	case -1:
58243791Sdim		log_it("CRON",getpid(),"error","can't fork");
59243791Sdim		break;
60243791Sdim	case 0:
61243791Sdim		/* child process */
62243791Sdim		acquire_daemonlock(1);
63243791Sdim		child_process(e, u);
64243791Sdim		Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
65243791Sdim		_exit(OK_EXIT);
66243791Sdim		break;
67243791Sdim	default:
68243791Sdim		/* parent process */
69243791Sdim		break;
70243791Sdim	}
71243791Sdim	Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
72243791Sdim}
73243791Sdim
74243791Sdim
75243791Sdimstatic void
76243791Sdimchild_process(e, u)
77252723Sdim	entry	*e;
78252723Sdim	user	*u;
79252723Sdim{
80252723Sdim	int		stdin_pipe[2], stdout_pipe[2];
81252723Sdim	register char	*input_data;
82252723Sdim	char		*usernm, *mailto;
83252723Sdim	int		children = 0;
84252723Sdim# if defined(LOGIN_CAP)
85252723Sdim	struct passwd	*pwd;
86252723Sdim	login_cap_t *lc;
87252723Sdim# endif
88252723Sdim
89252723Sdim	Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
90252723Sdim
91252723Sdim	/* mark ourselves as different to PS command watchers by upshifting
92252723Sdim	 * our program name.  This has no effect on some kernels.
93252723Sdim	 */
94252723Sdim	/*local*/{
95252723Sdim		register char	*pch;
96252723Sdim
97252723Sdim		for (pch = ProgramName;  *pch;  pch++)
98252723Sdim			*pch = MkUpper(*pch);
99252723Sdim	}
100252723Sdim
101252723Sdim	/* discover some useful and important environment settings
102252723Sdim	 */
103252723Sdim	usernm = env_get("LOGNAME", e->envp);
104252723Sdim	mailto = env_get("MAILTO", e->envp);
105252723Sdim
106252723Sdim#ifdef USE_SIGCHLD
107252723Sdim	/* our parent is watching for our death by catching SIGCHLD.  we
108252723Sdim	 * do not care to watch for our children's deaths this way -- we
109252723Sdim	 * use wait() explictly.  so we have to disable the signal (which
110252723Sdim	 * was inherited from the parent).
111252723Sdim	 */
112252723Sdim	(void) signal(SIGCHLD, SIG_IGN);
113252723Sdim#else
114252723Sdim	/* on system-V systems, we are ignoring SIGCLD.  we have to stop
115252723Sdim	 * ignoring it now or the wait() in cron_pclose() won't work.
116252723Sdim	 * because of this, we have to wait() for our children here, as well.
117252723Sdim	 */
118252723Sdim	(void) signal(SIGCLD, SIG_DFL);
119252723Sdim#endif /*BSD*/
120252723Sdim
121252723Sdim	/* create some pipes to talk to our future child
122252723Sdim	 */
123252723Sdim	pipe(stdin_pipe);	/* child's stdin */
124243791Sdim	pipe(stdout_pipe);	/* child's stdout */
125243791Sdim
126	/* since we are a forked process, we can diddle the command string
127	 * we were passed -- nobody else is going to use it again, right?
128	 *
129	 * if a % is present in the command, previous characters are the
130	 * command, and subsequent characters are the additional input to
131	 * the command.  Subsequent %'s will be transformed into newlines,
132	 * but that happens later.
133	 *
134	 * If there are escaped %'s, remove the escape character.
135	 */
136	/*local*/{
137		register int escaped = FALSE;
138		register int ch;
139		register char *p;
140
141		for (input_data = p = e->cmd; (ch = *input_data);
142		     input_data++, p++) {
143			if (p != input_data)
144			    *p = ch;
145			if (escaped) {
146				if (ch == '%' || ch == '\\')
147					*--p = ch;
148				escaped = FALSE;
149				continue;
150			}
151			if (ch == '\\') {
152				escaped = TRUE;
153				continue;
154			}
155			if (ch == '%') {
156				*input_data++ = '\0';
157				break;
158			}
159		}
160		*p = '\0';
161	}
162
163	/* fork again, this time so we can exec the user's command.
164	 */
165	switch (vfork()) {
166	case -1:
167		log_it("CRON",getpid(),"error","can't vfork");
168		exit(ERROR_EXIT);
169		/*NOTREACHED*/
170	case 0:
171		Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n",
172			      getpid()))
173
174		/* write a log message.  we've waited this long to do it
175		 * because it was not until now that we knew the PID that
176		 * the actual user command shell was going to get and the
177		 * PID is part of the log message.
178		 */
179		/*local*/{
180			char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
181
182			log_it(usernm, getpid(), "CMD", x);
183			free(x);
184		}
185
186		/* that's the last thing we'll log.  close the log files.
187		 */
188#ifdef SYSLOG
189		closelog();
190#endif
191
192		/* get new pgrp, void tty, etc.
193		 */
194		(void) setsid();
195
196		/* close the pipe ends that we won't use.  this doesn't affect
197		 * the parent, who has to read and write them; it keeps the
198		 * kernel from recording us as a potential client TWICE --
199		 * which would keep it from sending SIGPIPE in otherwise
200		 * appropriate circumstances.
201		 */
202		close(stdin_pipe[WRITE_PIPE]);
203		close(stdout_pipe[READ_PIPE]);
204
205		/* grandchild process.  make std{in,out} be the ends of
206		 * pipes opened by our daddy; make stderr go to stdout.
207		 */
208		close(STDIN);	dup2(stdin_pipe[READ_PIPE], STDIN);
209		close(STDOUT);	dup2(stdout_pipe[WRITE_PIPE], STDOUT);
210		close(STDERR);	dup2(STDOUT, STDERR);
211
212		/* close the pipes we just dup'ed.  The resources will remain.
213		 */
214		close(stdin_pipe[READ_PIPE]);
215		close(stdout_pipe[WRITE_PIPE]);
216
217		/* set our login universe.  Do this in the grandchild
218		 * so that the child can invoke /usr/lib/sendmail
219		 * without surprises.
220		 */
221		do_univ(u);
222
223# if defined(LOGIN_CAP)
224		/* Set user's entire context, but skip the environment
225		 * as cron provides a separate interface for this
226		 */
227		if ((pwd = getpwnam(usernm)) == NULL)
228			pwd = getpwuid(e->uid);
229		lc = NULL;
230		if (pwd != NULL) {
231			pwd->pw_gid = e->gid;
232			if (e->class != NULL)
233				lc = login_getclass(e->class);
234		}
235		if (pwd &&
236		    setusercontext(lc, pwd, e->uid,
237			    LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0)
238			(void) endpwent();
239		else {
240			/* fall back to the old method */
241			(void) endpwent();
242# endif
243			/* set our directory, uid and gid.  Set gid first,
244			 * since once we set uid, we've lost root privledges.
245			 */
246			setgid(e->gid);
247# if defined(BSD)
248			initgroups(usernm, e->gid);
249# endif
250			setlogin(usernm);
251			setuid(e->uid);		/* we aren't root after this..*/
252#if defined(LOGIN_CAP)
253		}
254#endif
255		chdir(env_get("HOME", e->envp));
256
257		/* exec the command.
258		 */
259		{
260			char	*shell = env_get("SHELL", e->envp);
261
262# if DEBUGGING
263			if (DebugFlags & DTEST) {
264				fprintf(stderr,
265				"debug DTEST is on, not exec'ing command.\n");
266				fprintf(stderr,
267				"\tcmd='%s' shell='%s'\n", e->cmd, shell);
268				_exit(OK_EXIT);
269			}
270# endif /*DEBUGGING*/
271			execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);
272			warn("execl: couldn't exec `%s'", shell);
273			_exit(ERROR_EXIT);
274		}
275		break;
276	default:
277		/* parent process */
278		break;
279	}
280
281	children++;
282
283	/* middle process, child of original cron, parent of process running
284	 * the user's command.
285	 */
286
287	Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
288
289	/* close the ends of the pipe that will only be referenced in the
290	 * grandchild process...
291	 */
292	close(stdin_pipe[READ_PIPE]);
293	close(stdout_pipe[WRITE_PIPE]);
294
295	/*
296	 * write, to the pipe connected to child's stdin, any input specified
297	 * after a % in the crontab entry.  while we copy, convert any
298	 * additional %'s to newlines.  when done, if some characters were
299	 * written and the last one wasn't a newline, write a newline.
300	 *
301	 * Note that if the input data won't fit into one pipe buffer (2K
302	 * or 4K on most BSD systems), and the child doesn't read its stdin,
303	 * we would block here.  thus we must fork again.
304	 */
305
306	if (*input_data && fork() == 0) {
307		register FILE	*out = fdopen(stdin_pipe[WRITE_PIPE], "w");
308		register int	need_newline = FALSE;
309		register int	escaped = FALSE;
310		register int	ch;
311
312		Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
313
314		/* close the pipe we don't use, since we inherited it and
315		 * are part of its reference count now.
316		 */
317		close(stdout_pipe[READ_PIPE]);
318
319		/* translation:
320		 *	\% -> %
321		 *	%  -> \n
322		 *	\x -> \x	for all x != %
323		 */
324		while ((ch = *input_data++)) {
325			if (escaped) {
326				if (ch != '%')
327					putc('\\', out);
328			} else {
329				if (ch == '%')
330					ch = '\n';
331			}
332
333			if (!(escaped = (ch == '\\'))) {
334				putc(ch, out);
335				need_newline = (ch != '\n');
336			}
337		}
338		if (escaped)
339			putc('\\', out);
340		if (need_newline)
341			putc('\n', out);
342
343		/* close the pipe, causing an EOF condition.  fclose causes
344		 * stdin_pipe[WRITE_PIPE] to be closed, too.
345		 */
346		fclose(out);
347
348		Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
349		exit(0);
350	}
351
352	/* close the pipe to the grandkiddie's stdin, since its wicked uncle
353	 * ernie back there has it open and will close it when he's done.
354	 */
355	close(stdin_pipe[WRITE_PIPE]);
356
357	children++;
358
359	/*
360	 * read output from the grandchild.  it's stderr has been redirected to
361	 * it's stdout, which has been redirected to our pipe.  if there is any
362	 * output, we'll be mailing it to the user whose crontab this is...
363	 * when the grandchild exits, we'll get EOF.
364	 */
365
366	Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
367
368	/*local*/{
369		register FILE	*in = fdopen(stdout_pipe[READ_PIPE], "r");
370		register int	ch = getc(in);
371
372		if (ch != EOF) {
373			register FILE	*mail;
374			register int	bytes = 1;
375			int		status = 0;
376
377			Debug(DPROC|DEXT,
378				("[%d] got data (%x:%c) from grandchild\n",
379					getpid(), ch, ch))
380
381			/* get name of recipient.  this is MAILTO if set to a
382			 * valid local username; USER otherwise.
383			 */
384			if (mailto) {
385				/* MAILTO was present in the environment
386				 */
387				if (!*mailto) {
388					/* ... but it's empty. set to NULL
389					 */
390					mailto = NULL;
391				}
392			} else {
393				/* MAILTO not present, set to USER.
394				 */
395				mailto = usernm;
396			}
397
398			/* if we are supposed to be mailing, MAILTO will
399			 * be non-NULL.  only in this case should we set
400			 * up the mail command and subjects and stuff...
401			 */
402
403			if (mailto) {
404				register char	**env;
405				auto char	mailcmd[MAX_COMMAND];
406				auto char	hostname[MAXHOSTNAMELEN];
407
408				(void) gethostname(hostname, MAXHOSTNAMELEN);
409				(void) snprintf(mailcmd, sizeof(mailcmd),
410					       MAILARGS, MAILCMD);
411				if (!(mail = cron_popen(mailcmd, "w"))) {
412					warn("%s", MAILCMD);
413					(void) _exit(ERROR_EXIT);
414				}
415				fprintf(mail, "From: root (Cron Daemon)\n");
416				fprintf(mail, "To: %s\n", mailto);
417				fprintf(mail, "Subject: Cron <%s@%s> %s\n",
418					usernm, first_word(hostname, "."),
419					e->cmd);
420# if defined(MAIL_DATE)
421				fprintf(mail, "Date: %s\n",
422					arpadate(&TargetTime));
423# endif /* MAIL_DATE */
424				for (env = e->envp;  *env;  env++)
425					fprintf(mail, "X-Cron-Env: <%s>\n",
426						*env);
427				fprintf(mail, "\n");
428
429				/* this was the first char from the pipe
430				 */
431				putc(ch, mail);
432			}
433
434			/* we have to read the input pipe no matter whether
435			 * we mail or not, but obviously we only write to
436			 * mail pipe if we ARE mailing.
437			 */
438
439			while (EOF != (ch = getc(in))) {
440				bytes++;
441				if (mailto)
442					putc(ch, mail);
443			}
444
445			/* only close pipe if we opened it -- i.e., we're
446			 * mailing...
447			 */
448
449			if (mailto) {
450				Debug(DPROC, ("[%d] closing pipe to mail\n",
451					getpid()))
452				/* Note: the pclose will probably see
453				 * the termination of the grandchild
454				 * in addition to the mail process, since
455				 * it (the grandchild) is likely to exit
456				 * after closing its stdout.
457				 */
458				status = cron_pclose(mail);
459			}
460
461			/* if there was output and we could not mail it,
462			 * log the facts so the poor user can figure out
463			 * what's going on.
464			 */
465			if (mailto && status) {
466				char buf[MAX_TEMPSTR];
467
468				snprintf(buf, sizeof(buf),
469			"mailed %d byte%s of output but got status 0x%04x\n",
470					bytes, (bytes==1)?"":"s",
471					status);
472				log_it(usernm, getpid(), "MAIL", buf);
473			}
474
475		} /*if data from grandchild*/
476
477		Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
478
479		fclose(in);	/* also closes stdout_pipe[READ_PIPE] */
480	}
481
482	/* wait for children to die.
483	 */
484	for (;  children > 0;  children--)
485	{
486		WAIT_T		waiter;
487		PID_T		pid;
488
489		Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
490			getpid(), children))
491		pid = wait(&waiter);
492		if (pid < OK) {
493			Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
494				getpid()))
495			break;
496		}
497		Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
498			getpid(), pid, WEXITSTATUS(waiter)))
499		if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
500			Debug(DPROC, (", dumped core"))
501		Debug(DPROC, ("\n"))
502	}
503}
504
505
506static void
507do_univ(u)
508	user	*u;
509{
510#if defined(sequent)
511/* Dynix (Sequent) hack to put the user associated with
512 * the passed user structure into the ATT universe if
513 * necessary.  We have to dig the gecos info out of
514 * the user's password entry to see if the magic
515 * "universe(att)" string is present.
516 */
517
518	struct	passwd	*p;
519	char	*s;
520	int	i;
521
522	p = getpwuid(u->uid);
523	(void) endpwent();
524
525	if (p == NULL)
526		return;
527
528	s = p->pw_gecos;
529
530	for (i = 0; i < 4; i++)
531	{
532		if ((s = strchr(s, ',')) == NULL)
533			return;
534		s++;
535	}
536	if (strcmp(s, "universe(att)"))
537		return;
538
539	(void) universe(U_ATT);
540#endif
541}
542