xref: /freebsd/usr.sbin/cron/cron/do_command.c (revision 17ee9d00bc1ae1e598c38f25826f861e4bc6c3ce)
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)
19 static char rcsid[] = "$Id: do_command.c,v 2.12 1994/01/15 20:43:43 vixie 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 
33 static void		child_process __P((entry *, user *)),
34 			do_univ __P((user *));
35 
36 
37 void
38 do_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 
71 static void
72 child_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 		setgid(e->gid);
210 # if defined(BSD)
211 		initgroups(env_get("LOGNAME", e->envp), e->gid);
212 # endif
213 		setuid(e->uid);		/* we aren't root after this... */
214 		chdir(env_get("HOME", e->envp));
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, mailto);
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 
466 static void
467 do_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