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