do_command.c revision 8857
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.4 1995/04/14 21:54:18 ache 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	/*local*/{
127		register int escaped = FALSE;
128		register int ch;
129
130		for (input_data = e->cmd;  ch = *input_data;  input_data++) {
131			if (escaped) {
132				escaped = FALSE;
133				continue;
134			}
135			if (ch == '\\') {
136				escaped = TRUE;
137				continue;
138			}
139			if (ch == '%') {
140				*input_data++ = '\0';
141				break;
142			}
143		}
144	}
145
146	/* fork again, this time so we can exec the user's command.
147	 */
148	switch (vfork()) {
149	case -1:
150		log_it("CRON",getpid(),"error","can't vfork");
151		exit(ERROR_EXIT);
152		/*NOTREACHED*/
153	case 0:
154		Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n",
155			      getpid()))
156
157		/* write a log message.  we've waited this long to do it
158		 * because it was not until now that we knew the PID that
159		 * the actual user command shell was going to get and the
160		 * PID is part of the log message.
161		 */
162		/*local*/{
163			char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
164
165			log_it(usernm, getpid(), "CMD", x);
166			free(x);
167		}
168
169		/* that's the last thing we'll log.  close the log files.
170		 */
171#ifdef SYSLOG
172		closelog();
173#endif
174
175		/* get new pgrp, void tty, etc.
176		 */
177		(void) setsid();
178
179		/* close the pipe ends that we won't use.  this doesn't affect
180		 * the parent, who has to read and write them; it keeps the
181		 * kernel from recording us as a potential client TWICE --
182		 * which would keep it from sending SIGPIPE in otherwise
183		 * appropriate circumstances.
184		 */
185		close(stdin_pipe[WRITE_PIPE]);
186		close(stdout_pipe[READ_PIPE]);
187
188		/* grandchild process.  make std{in,out} be the ends of
189		 * pipes opened by our daddy; make stderr go to stdout.
190		 */
191		close(STDIN);	dup2(stdin_pipe[READ_PIPE], STDIN);
192		close(STDOUT);	dup2(stdout_pipe[WRITE_PIPE], STDOUT);
193		close(STDERR);	dup2(STDOUT, STDERR);
194
195		/* close the pipes we just dup'ed.  The resources will remain.
196		 */
197		close(stdin_pipe[READ_PIPE]);
198		close(stdout_pipe[WRITE_PIPE]);
199
200		/* set our login universe.  Do this in the grandchild
201		 * so that the child can invoke /usr/lib/sendmail
202		 * without surprises.
203		 */
204		do_univ(u);
205
206		/* set our directory, uid and gid.  Set gid first, since once
207		 * we set uid, we've lost root privledges.
208		 */
209		chdir(env_get("HOME", e->envp));
210# if defined(BSD)
211		initgroups(env_get("LOGNAME", e->envp), e->gid);
212# endif
213		setgid(e->gid);
214		setuid(e->uid);		/* we aren't root after this... */
215
216		/* exec the command.
217		 */
218		{
219			char	*shell = env_get("SHELL", e->envp);
220
221# if DEBUGGING
222			if (DebugFlags & DTEST) {
223				fprintf(stderr,
224				"debug DTEST is on, not exec'ing command.\n");
225				fprintf(stderr,
226				"\tcmd='%s' shell='%s'\n", e->cmd, shell);
227				_exit(OK_EXIT);
228			}
229# endif /*DEBUGGING*/
230			execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);
231			fprintf(stderr, "execl: couldn't exec `%s'\n", shell);
232			perror("execl");
233			_exit(ERROR_EXIT);
234		}
235		break;
236	default:
237		/* parent process */
238		break;
239	}
240
241	children++;
242
243	/* middle process, child of original cron, parent of process running
244	 * the user's command.
245	 */
246
247	Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
248
249	/* close the ends of the pipe that will only be referenced in the
250	 * grandchild process...
251	 */
252	close(stdin_pipe[READ_PIPE]);
253	close(stdout_pipe[WRITE_PIPE]);
254
255	/*
256	 * write, to the pipe connected to child's stdin, any input specified
257	 * after a % in the crontab entry.  while we copy, convert any
258	 * additional %'s to newlines.  when done, if some characters were
259	 * written and the last one wasn't a newline, write a newline.
260	 *
261	 * Note that if the input data won't fit into one pipe buffer (2K
262	 * or 4K on most BSD systems), and the child doesn't read its stdin,
263	 * we would block here.  thus we must fork again.
264	 */
265
266	if (*input_data && fork() == 0) {
267		register FILE	*out = fdopen(stdin_pipe[WRITE_PIPE], "w");
268		register int	need_newline = FALSE;
269		register int	escaped = FALSE;
270		register int	ch;
271
272		Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
273
274		/* close the pipe we don't use, since we inherited it and
275		 * are part of its reference count now.
276		 */
277		close(stdout_pipe[READ_PIPE]);
278
279		/* translation:
280		 *	\% -> %
281		 *	%  -> \n
282		 *	\x -> \x	for all x != %
283		 */
284		while (ch = *input_data++) {
285			if (escaped) {
286				if (ch != '%')
287					putc('\\', out);
288			} else {
289				if (ch == '%')
290					ch = '\n';
291			}
292
293			if (!(escaped = (ch == '\\'))) {
294				putc(ch, out);
295				need_newline = (ch != '\n');
296			}
297		}
298		if (escaped)
299			putc('\\', out);
300		if (need_newline)
301			putc('\n', out);
302
303		/* close the pipe, causing an EOF condition.  fclose causes
304		 * stdin_pipe[WRITE_PIPE] to be closed, too.
305		 */
306		fclose(out);
307
308		Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
309		exit(0);
310	}
311
312	/* close the pipe to the grandkiddie's stdin, since its wicked uncle
313	 * ernie back there has it open and will close it when he's done.
314	 */
315	close(stdin_pipe[WRITE_PIPE]);
316
317	children++;
318
319	/*
320	 * read output from the grandchild.  it's stderr has been redirected to
321	 * it's stdout, which has been redirected to our pipe.  if there is any
322	 * output, we'll be mailing it to the user whose crontab this is...
323	 * when the grandchild exits, we'll get EOF.
324	 */
325
326	Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
327
328	/*local*/{
329		register FILE	*in = fdopen(stdout_pipe[READ_PIPE], "r");
330		register int	ch = getc(in);
331
332		if (ch != EOF) {
333			register FILE	*mail;
334			register int	bytes = 1;
335			int		status = 0;
336
337			Debug(DPROC|DEXT,
338				("[%d] got data (%x:%c) from grandchild\n",
339					getpid(), ch, ch))
340
341			/* get name of recipient.  this is MAILTO if set to a
342			 * valid local username; USER otherwise.
343			 */
344			if (mailto) {
345				/* MAILTO was present in the environment
346				 */
347				if (!*mailto) {
348					/* ... but it's empty. set to NULL
349					 */
350					mailto = NULL;
351				}
352			} else {
353				/* MAILTO not present, set to USER.
354				 */
355				mailto = usernm;
356			}
357
358			/* if we are supposed to be mailing, MAILTO will
359			 * be non-NULL.  only in this case should we set
360			 * up the mail command and subjects and stuff...
361			 */
362
363			if (mailto) {
364				register char	**env;
365				auto char	mailcmd[MAX_COMMAND];
366				auto char	hostname[MAXHOSTNAMELEN];
367
368				(void) gethostname(hostname, MAXHOSTNAMELEN);
369				(void) sprintf(mailcmd, MAILARGS,
370					       MAILCMD);
371				if (!(mail = cron_popen(mailcmd, "w"))) {
372					perror(MAILCMD);
373					(void) _exit(ERROR_EXIT);
374				}
375				fprintf(mail, "From: root (Cron Daemon)\n");
376				fprintf(mail, "To: %s\n", mailto);
377				fprintf(mail, "Subject: Cron <%s@%s> %s\n",
378					usernm, first_word(hostname, "."),
379					e->cmd);
380# if defined(MAIL_DATE)
381				fprintf(mail, "Date: %s\n",
382					arpadate(&TargetTime));
383# endif /* MAIL_DATE */
384				for (env = e->envp;  *env;  env++)
385					fprintf(mail, "X-Cron-Env: <%s>\n",
386						*env);
387				fprintf(mail, "\n");
388
389				/* this was the first char from the pipe
390				 */
391				putc(ch, mail);
392			}
393
394			/* we have to read the input pipe no matter whether
395			 * we mail or not, but obviously we only write to
396			 * mail pipe if we ARE mailing.
397			 */
398
399			while (EOF != (ch = getc(in))) {
400				bytes++;
401				if (mailto)
402					putc(ch, mail);
403			}
404
405			/* only close pipe if we opened it -- i.e., we're
406			 * mailing...
407			 */
408
409			if (mailto) {
410				Debug(DPROC, ("[%d] closing pipe to mail\n",
411					getpid()))
412				/* Note: the pclose will probably see
413				 * the termination of the grandchild
414				 * in addition to the mail process, since
415				 * it (the grandchild) is likely to exit
416				 * after closing its stdout.
417				 */
418				status = cron_pclose(mail);
419			}
420
421			/* if there was output and we could not mail it,
422			 * log the facts so the poor user can figure out
423			 * what's going on.
424			 */
425			if (mailto && status) {
426				char buf[MAX_TEMPSTR];
427
428				sprintf(buf,
429			"mailed %d byte%s of output but got status 0x%04x\n",
430					bytes, (bytes==1)?"":"s",
431					status);
432				log_it(usernm, getpid(), "MAIL", buf);
433			}
434
435		} /*if data from grandchild*/
436
437		Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
438
439		fclose(in);	/* also closes stdout_pipe[READ_PIPE] */
440	}
441
442	/* wait for children to die.
443	 */
444	for (;  children > 0;  children--)
445	{
446		WAIT_T		waiter;
447		PID_T		pid;
448
449		Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
450			getpid(), children))
451		pid = wait(&waiter);
452		if (pid < OK) {
453			Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
454				getpid()))
455			break;
456		}
457		Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
458			getpid(), pid, WEXITSTATUS(waiter)))
459		if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
460			Debug(DPROC, (", dumped core"))
461		Debug(DPROC, ("\n"))
462	}
463}
464
465
466static void
467do_univ(u)
468	user	*u;
469{
470#if defined(sequent)
471/* Dynix (Sequent) hack to put the user associated with
472 * the passed user structure into the ATT universe if
473 * necessary.  We have to dig the gecos info out of
474 * the user's password entry to see if the magic
475 * "universe(att)" string is present.
476 */
477
478	struct	passwd	*p;
479	char	*s;
480	int	i;
481
482	p = getpwuid(u->uid);
483	(void) endpwent();
484
485	if (p == NULL)
486		return;
487
488	s = p->pw_gecos;
489
490	for (i = 0; i < 4; i++)
491	{
492		if ((s = strchr(s, ',')) == NULL)
493			return;
494		s++;
495	}
496	if (strcmp(s, "universe(att)"))
497		return;
498
499	(void) universe(U_ATT);
500#endif
501}
502