do_command.c revision 20573
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 char rcsid[] = "$Id: do_command.c,v 1.6 1995/09/10 13:02:56 joerg Exp $";
20#endif
21
22
23#include "cron.h"
24#include <sys/signal.h>
25#if defined(sequent)
26# include <sys/universe.h>
27#endif
28#if defined(SYSLOG)
29# include <syslog.h>
30#endif
31
32
33static void		child_process __P((entry *, user *)),
34			do_univ __P((user *));
35
36
37void
38do_command(e, u)
39	entry	*e;
40	user	*u;
41{
42	Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
43		getpid(), e->cmd, u->name, e->uid, e->gid))
44
45	/* fork to become asynchronous -- parent process is done immediately,
46	 * and continues to run the normal cron code, which means return to
47	 * tick().  the child and grandchild don't leave this function, alive.
48	 *
49	 * vfork() is unsuitable, since we have much to do, and the parent
50	 * needs to be able to run off and fork other processes.
51	 */
52	switch (fork()) {
53	case -1:
54		log_it("CRON",getpid(),"error","can't fork");
55		break;
56	case 0:
57		/* child process */
58		acquire_daemonlock(1);
59		child_process(e, u);
60		Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
61		_exit(OK_EXIT);
62		break;
63	default:
64		/* parent process */
65		break;
66	}
67	Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
68}
69
70
71static void
72child_process(e, u)
73	entry	*e;
74	user	*u;
75{
76	int		stdin_pipe[2], stdout_pipe[2];
77	register char	*input_data;
78	char		*usernm, *mailto;
79	int		children = 0;
80
81	Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
82
83	/* mark ourselves as different to PS command watchers by upshifting
84	 * our program name.  This has no effect on some kernels.
85	 */
86	/*local*/{
87		register char	*pch;
88
89		for (pch = ProgramName;  *pch;  pch++)
90			*pch = MkUpper(*pch);
91	}
92
93	/* discover some useful and important environment settings
94	 */
95	usernm = env_get("LOGNAME", e->envp);
96	mailto = env_get("MAILTO", e->envp);
97
98#ifdef USE_SIGCHLD
99	/* our parent is watching for our death by catching SIGCHLD.  we
100	 * do not care to watch for our children's deaths this way -- we
101	 * use wait() explictly.  so we have to disable the signal (which
102	 * was inherited from the parent).
103	 */
104	(void) signal(SIGCHLD, SIG_IGN);
105#else
106	/* on system-V systems, we are ignoring SIGCLD.  we have to stop
107	 * ignoring it now or the wait() in cron_pclose() won't work.
108	 * because of this, we have to wait() for our children here, as well.
109	 */
110	(void) signal(SIGCLD, SIG_DFL);
111#endif /*BSD*/
112
113	/* create some pipes to talk to our future child
114	 */
115	pipe(stdin_pipe);	/* child's stdin */
116	pipe(stdout_pipe);	/* child's stdout */
117
118	/* since we are a forked process, we can diddle the command string
119	 * we were passed -- nobody else is going to use it again, right?
120	 *
121	 * if a % is present in the command, previous characters are the
122	 * command, and subsequent characters are the additional input to
123	 * the command.  Subsequent %'s will be transformed into newlines,
124	 * but that happens later.
125	 *
126	 * If there are escaped %'s, remove the escape character.
127	 */
128	/*local*/{
129		register int escaped = FALSE;
130		register int ch;
131		register char *p;
132
133		for (input_data = p = e->cmd; ch = *input_data;
134		     input_data++, p++) {
135			if (p != input_data)
136			    *p = ch;
137			if (escaped) {
138				if (ch == '%' || ch == '\\')
139					*--p = ch;
140				escaped = FALSE;
141				continue;
142			}
143			if (ch == '\\') {
144				escaped = TRUE;
145				continue;
146			}
147			if (ch == '%') {
148				*input_data++ = '\0';
149				break;
150			}
151		}
152		*p = '\0';
153	}
154
155	/* fork again, this time so we can exec the user's command.
156	 */
157	switch (vfork()) {
158	case -1:
159		log_it("CRON",getpid(),"error","can't vfork");
160		exit(ERROR_EXIT);
161		/*NOTREACHED*/
162	case 0:
163		Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n",
164			      getpid()))
165
166		/* write a log message.  we've waited this long to do it
167		 * because it was not until now that we knew the PID that
168		 * the actual user command shell was going to get and the
169		 * PID is part of the log message.
170		 */
171		/*local*/{
172			char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
173
174			log_it(usernm, getpid(), "CMD", x);
175			free(x);
176		}
177
178		/* that's the last thing we'll log.  close the log files.
179		 */
180#ifdef SYSLOG
181		closelog();
182#endif
183
184		/* get new pgrp, void tty, etc.
185		 */
186		(void) setsid();
187
188		/* close the pipe ends that we won't use.  this doesn't affect
189		 * the parent, who has to read and write them; it keeps the
190		 * kernel from recording us as a potential client TWICE --
191		 * which would keep it from sending SIGPIPE in otherwise
192		 * appropriate circumstances.
193		 */
194		close(stdin_pipe[WRITE_PIPE]);
195		close(stdout_pipe[READ_PIPE]);
196
197		/* grandchild process.  make std{in,out} be the ends of
198		 * pipes opened by our daddy; make stderr go to stdout.
199		 */
200		close(STDIN);	dup2(stdin_pipe[READ_PIPE], STDIN);
201		close(STDOUT);	dup2(stdout_pipe[WRITE_PIPE], STDOUT);
202		close(STDERR);	dup2(STDOUT, STDERR);
203
204		/* close the pipes we just dup'ed.  The resources will remain.
205		 */
206		close(stdin_pipe[READ_PIPE]);
207		close(stdout_pipe[WRITE_PIPE]);
208
209		/* set our login universe.  Do this in the grandchild
210		 * so that the child can invoke /usr/lib/sendmail
211		 * without surprises.
212		 */
213		do_univ(u);
214
215		/* set our directory, uid and gid.  Set gid first, since once
216		 * we set uid, we've lost root privledges.
217		 */
218		setgid(e->gid);
219# if defined(BSD)
220		initgroups(env_get("LOGNAME", e->envp), e->gid);
221# endif
222		setlogin(usernm);
223		setuid(e->uid);		/* we aren't root after this... */
224		chdir(env_get("HOME", e->envp));
225
226		/* exec the command.
227		 */
228		{
229			char	*shell = env_get("SHELL", e->envp);
230
231# if DEBUGGING
232			if (DebugFlags & DTEST) {
233				fprintf(stderr,
234				"debug DTEST is on, not exec'ing command.\n");
235				fprintf(stderr,
236				"\tcmd='%s' shell='%s'\n", e->cmd, shell);
237				_exit(OK_EXIT);
238			}
239# endif /*DEBUGGING*/
240			execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);
241			fprintf(stderr, "execl: couldn't exec `%s'\n", shell);
242			perror("execl");
243			_exit(ERROR_EXIT);
244		}
245		break;
246	default:
247		/* parent process */
248		break;
249	}
250
251	children++;
252
253	/* middle process, child of original cron, parent of process running
254	 * the user's command.
255	 */
256
257	Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
258
259	/* close the ends of the pipe that will only be referenced in the
260	 * grandchild process...
261	 */
262	close(stdin_pipe[READ_PIPE]);
263	close(stdout_pipe[WRITE_PIPE]);
264
265	/*
266	 * write, to the pipe connected to child's stdin, any input specified
267	 * after a % in the crontab entry.  while we copy, convert any
268	 * additional %'s to newlines.  when done, if some characters were
269	 * written and the last one wasn't a newline, write a newline.
270	 *
271	 * Note that if the input data won't fit into one pipe buffer (2K
272	 * or 4K on most BSD systems), and the child doesn't read its stdin,
273	 * we would block here.  thus we must fork again.
274	 */
275
276	if (*input_data && fork() == 0) {
277		register FILE	*out = fdopen(stdin_pipe[WRITE_PIPE], "w");
278		register int	need_newline = FALSE;
279		register int	escaped = FALSE;
280		register int	ch;
281
282		Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
283
284		/* close the pipe we don't use, since we inherited it and
285		 * are part of its reference count now.
286		 */
287		close(stdout_pipe[READ_PIPE]);
288
289		/* translation:
290		 *	\% -> %
291		 *	%  -> \n
292		 *	\x -> \x	for all x != %
293		 */
294		while (ch = *input_data++) {
295			if (escaped) {
296				if (ch != '%')
297					putc('\\', out);
298			} else {
299				if (ch == '%')
300					ch = '\n';
301			}
302
303			if (!(escaped = (ch == '\\'))) {
304				putc(ch, out);
305				need_newline = (ch != '\n');
306			}
307		}
308		if (escaped)
309			putc('\\', out);
310		if (need_newline)
311			putc('\n', out);
312
313		/* close the pipe, causing an EOF condition.  fclose causes
314		 * stdin_pipe[WRITE_PIPE] to be closed, too.
315		 */
316		fclose(out);
317
318		Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
319		exit(0);
320	}
321
322	/* close the pipe to the grandkiddie's stdin, since its wicked uncle
323	 * ernie back there has it open and will close it when he's done.
324	 */
325	close(stdin_pipe[WRITE_PIPE]);
326
327	children++;
328
329	/*
330	 * read output from the grandchild.  it's stderr has been redirected to
331	 * it's stdout, which has been redirected to our pipe.  if there is any
332	 * output, we'll be mailing it to the user whose crontab this is...
333	 * when the grandchild exits, we'll get EOF.
334	 */
335
336	Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
337
338	/*local*/{
339		register FILE	*in = fdopen(stdout_pipe[READ_PIPE], "r");
340		register int	ch = getc(in);
341
342		if (ch != EOF) {
343			register FILE	*mail;
344			register int	bytes = 1;
345			int		status = 0;
346
347			Debug(DPROC|DEXT,
348				("[%d] got data (%x:%c) from grandchild\n",
349					getpid(), ch, ch))
350
351			/* get name of recipient.  this is MAILTO if set to a
352			 * valid local username; USER otherwise.
353			 */
354			if (mailto) {
355				/* MAILTO was present in the environment
356				 */
357				if (!*mailto) {
358					/* ... but it's empty. set to NULL
359					 */
360					mailto = NULL;
361				}
362			} else {
363				/* MAILTO not present, set to USER.
364				 */
365				mailto = usernm;
366			}
367
368			/* if we are supposed to be mailing, MAILTO will
369			 * be non-NULL.  only in this case should we set
370			 * up the mail command and subjects and stuff...
371			 */
372
373			if (mailto) {
374				register char	**env;
375				auto char	mailcmd[MAX_COMMAND];
376				auto char	hostname[MAXHOSTNAMELEN];
377
378				(void) gethostname(hostname, MAXHOSTNAMELEN);
379				(void) snprintf(mailcmd, sizeof(mailcmd),
380					       MAILARGS, MAILCMD);
381				if (!(mail = cron_popen(mailcmd, "w"))) {
382					perror(MAILCMD);
383					(void) _exit(ERROR_EXIT);
384				}
385				fprintf(mail, "From: root (Cron Daemon)\n");
386				fprintf(mail, "To: %s\n", mailto);
387				fprintf(mail, "Subject: Cron <%s@%s> %s\n",
388					usernm, first_word(hostname, "."),
389					e->cmd);
390# if defined(MAIL_DATE)
391				fprintf(mail, "Date: %s\n",
392					arpadate(&TargetTime));
393# endif /* MAIL_DATE */
394				for (env = e->envp;  *env;  env++)
395					fprintf(mail, "X-Cron-Env: <%s>\n",
396						*env);
397				fprintf(mail, "\n");
398
399				/* this was the first char from the pipe
400				 */
401				putc(ch, mail);
402			}
403
404			/* we have to read the input pipe no matter whether
405			 * we mail or not, but obviously we only write to
406			 * mail pipe if we ARE mailing.
407			 */
408
409			while (EOF != (ch = getc(in))) {
410				bytes++;
411				if (mailto)
412					putc(ch, mail);
413			}
414
415			/* only close pipe if we opened it -- i.e., we're
416			 * mailing...
417			 */
418
419			if (mailto) {
420				Debug(DPROC, ("[%d] closing pipe to mail\n",
421					getpid()))
422				/* Note: the pclose will probably see
423				 * the termination of the grandchild
424				 * in addition to the mail process, since
425				 * it (the grandchild) is likely to exit
426				 * after closing its stdout.
427				 */
428				status = cron_pclose(mail);
429			}
430
431			/* if there was output and we could not mail it,
432			 * log the facts so the poor user can figure out
433			 * what's going on.
434			 */
435			if (mailto && status) {
436				char buf[MAX_TEMPSTR];
437
438				snprintf(buf, sizeof(buf),
439			"mailed %d byte%s of output but got status 0x%04x\n",
440					bytes, (bytes==1)?"":"s",
441					status);
442				log_it(usernm, getpid(), "MAIL", buf);
443			}
444
445		} /*if data from grandchild*/
446
447		Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
448
449		fclose(in);	/* also closes stdout_pipe[READ_PIPE] */
450	}
451
452	/* wait for children to die.
453	 */
454	for (;  children > 0;  children--)
455	{
456		WAIT_T		waiter;
457		PID_T		pid;
458
459		Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
460			getpid(), children))
461		pid = wait(&waiter);
462		if (pid < OK) {
463			Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
464				getpid()))
465			break;
466		}
467		Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
468			getpid(), pid, WEXITSTATUS(waiter)))
469		if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
470			Debug(DPROC, (", dumped core"))
471		Debug(DPROC, ("\n"))
472	}
473}
474
475
476static void
477do_univ(u)
478	user	*u;
479{
480#if defined(sequent)
481/* Dynix (Sequent) hack to put the user associated with
482 * the passed user structure into the ATT universe if
483 * necessary.  We have to dig the gecos info out of
484 * the user's password entry to see if the magic
485 * "universe(att)" string is present.
486 */
487
488	struct	passwd	*p;
489	char	*s;
490	int	i;
491
492	p = getpwuid(u->uid);
493	(void) endpwent();
494
495	if (p == NULL)
496		return;
497
498	s = p->pw_gecos;
499
500	for (i = 0; i < 4; i++)
501	{
502		if ((s = strchr(s, ',')) == NULL)
503			return;
504		s++;
505	}
506	if (strcmp(s, "universe(att)"))
507		return;
508
509	(void) universe(U_ATT);
510#endif
511}
512