xref: /openbsd-src/usr.sbin/cron/do_command.c (revision 62a742911104f98b9185b2c6b6007d9b1c36396c)
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.5 1998/05/08 20:17:18 deraadt 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 	char		*input_data;
78 	char		*usernm, *mailto;
79 	int		children = 0;
80 
81 #ifdef __GNUC__
82 	(void) &input_data;	/* Avoid vfork clobbering */
83 	(void) &mailto;
84 	(void) &children;
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 		/* set our directory, uid and gid.  Set gid first, since once
222 		 * we set uid, we've lost root privledges.
223 		 */
224 		setgid(e->gid);
225 # if defined(BSD)
226 		initgroups(env_get("LOGNAME", e->envp), e->gid);
227 # endif
228 		setlogin(usernm);
229 		setuid(e->uid);		/* we aren't root after this... */
230 		chdir(env_get("HOME", e->envp));
231 
232 		/* exec the command.
233 		 */
234 		{
235 			char	*shell = env_get("SHELL", e->envp);
236 
237 # if DEBUGGING
238 			if (DebugFlags & DTEST) {
239 				fprintf(stderr,
240 				"debug DTEST is on, not exec'ing command.\n");
241 				fprintf(stderr,
242 				"\tcmd='%s' shell='%s'\n", e->cmd, shell);
243 				_exit(OK_EXIT);
244 			}
245 # endif /*DEBUGGING*/
246 			execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);
247 			fprintf(stderr, "execl: couldn't exec `%s'\n", shell);
248 			perror("execl");
249 			_exit(ERROR_EXIT);
250 		}
251 		break;
252 	default:
253 		/* parent process */
254 		break;
255 	}
256 
257 	children++;
258 
259 	/* middle process, child of original cron, parent of process running
260 	 * the user's command.
261 	 */
262 
263 	Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
264 
265 	/* close the ends of the pipe that will only be referenced in the
266 	 * grandchild process...
267 	 */
268 	close(stdin_pipe[READ_PIPE]);
269 	close(stdout_pipe[WRITE_PIPE]);
270 
271 	/*
272 	 * write, to the pipe connected to child's stdin, any input specified
273 	 * after a % in the crontab entry.  while we copy, convert any
274 	 * additional %'s to newlines.  when done, if some characters were
275 	 * written and the last one wasn't a newline, write a newline.
276 	 *
277 	 * Note that if the input data won't fit into one pipe buffer (2K
278 	 * or 4K on most BSD systems), and the child doesn't read its stdin,
279 	 * we would block here.  thus we must fork again.
280 	 */
281 
282 	if (*input_data && fork() == 0) {
283 		register FILE	*out = fdopen(stdin_pipe[WRITE_PIPE], "w");
284 		register int	need_newline = FALSE;
285 		register int	escaped = FALSE;
286 		register int	ch;
287 
288 		Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
289 
290 		/* close the pipe we don't use, since we inherited it and
291 		 * are part of its reference count now.
292 		 */
293 		close(stdout_pipe[READ_PIPE]);
294 
295 		/* translation:
296 		 *	\% -> %
297 		 *	%  -> \n
298 		 *	\x -> \x	for all x != %
299 		 */
300 		while ((ch = *input_data++) != '\0') {
301 			if (escaped) {
302 				if (ch != '%')
303 					putc('\\', out);
304 			} else {
305 				if (ch == '%')
306 					ch = '\n';
307 			}
308 
309 			if (!(escaped = (ch == '\\'))) {
310 				putc(ch, out);
311 				need_newline = (ch != '\n');
312 			}
313 		}
314 		if (escaped)
315 			putc('\\', out);
316 		if (need_newline)
317 			putc('\n', out);
318 
319 		/* close the pipe, causing an EOF condition.  fclose causes
320 		 * stdin_pipe[WRITE_PIPE] to be closed, too.
321 		 */
322 		fclose(out);
323 
324 		Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
325 		exit(0);
326 	}
327 
328 	/* close the pipe to the grandkiddie's stdin, since its wicked uncle
329 	 * ernie back there has it open and will close it when he's done.
330 	 */
331 	close(stdin_pipe[WRITE_PIPE]);
332 
333 	children++;
334 
335 	/*
336 	 * read output from the grandchild.  it's stderr has been redirected to
337 	 * it's stdout, which has been redirected to our pipe.  if there is any
338 	 * output, we'll be mailing it to the user whose crontab this is...
339 	 * when the grandchild exits, we'll get EOF.
340 	 */
341 
342 	Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
343 
344 	/*local*/{
345 		register FILE	*in = fdopen(stdout_pipe[READ_PIPE], "r");
346 		register int	ch = getc(in);
347 
348 		if (ch != EOF) {
349 			FILE		*mail;
350 			register int	bytes = 1;
351 			int		status = 0;
352 
353 #ifdef __GNUC__
354 			(void) &mail;
355 #endif
356 			Debug(DPROC|DEXT,
357 				("[%d] got data (%x:%c) from grandchild\n",
358 					getpid(), ch, ch))
359 
360 			/* get name of recipient.  this is MAILTO if set to a
361 			 * valid local username; USER otherwise.
362 			 */
363 			if (mailto) {
364 				/* MAILTO was present in the environment
365 				 */
366 				if (!*mailto) {
367 					/* ... but it's empty. set to NULL
368 					 */
369 					mailto = NULL;
370 				}
371 			} else {
372 				/* MAILTO not present, set to USER.
373 				 */
374 				mailto = usernm;
375 			}
376 
377 			/* if we are supposed to be mailing, MAILTO will
378 			 * be non-NULL.  only in this case should we set
379 			 * up the mail command and subjects and stuff...
380 			 */
381 
382 			if (mailto) {
383 				register char	**env;
384 				auto char	mailcmd[MAX_COMMAND];
385 				auto char	hostname[MAXHOSTNAMELEN];
386 
387 				(void) gethostname(hostname, MAXHOSTNAMELEN);
388 				(void) snprintf(mailcmd, sizeof(mailcmd),
389 				    MAILARGS, MAILCMD);
390 				if (!(mail = cron_popen(mailcmd, "w"))) {
391 					perror(MAILCMD);
392 					(void) _exit(ERROR_EXIT);
393 				}
394 				fprintf(mail, "From: root (Cron Daemon)\n");
395 				fprintf(mail, "To: %s\n", mailto);
396 				fprintf(mail, "Subject: Cron <%s@%s> %s\n",
397 					usernm, first_word(hostname, "."),
398 					e->cmd);
399 # if defined(MAIL_DATE)
400 				fprintf(mail, "Date: %s\n",
401 					arpadate(&StartTime));
402 # endif /* MAIL_DATE */
403 				for (env = e->envp;  *env;  env++)
404 					fprintf(mail, "X-Cron-Env: <%s>\n",
405 						*env);
406 				fprintf(mail, "\n");
407 
408 				/* this was the first char from the pipe
409 				 */
410 				putc(ch, mail);
411 			}
412 
413 			/* we have to read the input pipe no matter whether
414 			 * we mail or not, but obviously we only write to
415 			 * mail pipe if we ARE mailing.
416 			 */
417 
418 			while (EOF != (ch = getc(in))) {
419 				bytes++;
420 				if (mailto)
421 					putc(ch, mail);
422 			}
423 
424 			/* only close pipe if we opened it -- i.e., we're
425 			 * mailing...
426 			 */
427 
428 			if (mailto) {
429 				Debug(DPROC, ("[%d] closing pipe to mail\n",
430 					getpid()))
431 				/* Note: the pclose will probably see
432 				 * the termination of the grandchild
433 				 * in addition to the mail process, since
434 				 * it (the grandchild) is likely to exit
435 				 * after closing its stdout.
436 				 */
437 				status = cron_pclose(mail);
438 			}
439 
440 			/* if there was output and we could not mail it,
441 			 * log the facts so the poor user can figure out
442 			 * what's going on.
443 			 */
444 			if (mailto && status) {
445 				char buf[MAX_TEMPSTR];
446 
447 				sprintf(buf,
448 			"mailed %d byte%s of output but got status 0x%04x\n",
449 					bytes, (bytes==1)?"":"s",
450 					status);
451 				log_it(usernm, getpid(), "MAIL", buf);
452 			}
453 
454 		} /*if data from grandchild*/
455 
456 		Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
457 
458 		fclose(in);	/* also closes stdout_pipe[READ_PIPE] */
459 	}
460 
461 	/* wait for children to die.
462 	 */
463 	for (;  children > 0;  children--)
464 	{
465 		WAIT_T		waiter;
466 		PID_T		pid;
467 
468 		Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
469 			getpid(), children))
470 		pid = wait(&waiter);
471 		if (pid < OK) {
472 			Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
473 				getpid()))
474 			break;
475 		}
476 		Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
477 			getpid(), pid, WEXITSTATUS(waiter)))
478 		if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
479 			Debug(DPROC, (", dumped core"))
480 		Debug(DPROC, ("\n"))
481 	}
482 }
483 
484 
485 static void
486 do_univ(u)
487 	user	*u;
488 {
489 #if defined(sequent)
490 /* Dynix (Sequent) hack to put the user associated with
491  * the passed user structure into the ATT universe if
492  * necessary.  We have to dig the gecos info out of
493  * the user's password entry to see if the magic
494  * "universe(att)" string is present.
495  */
496 
497 	struct	passwd	*p;
498 	char	*s;
499 	int	i;
500 
501 	p = getpwuid(u->uid);
502 	(void) endpwent();
503 
504 	if (p == NULL)
505 		return;
506 
507 	s = p->pw_gecos;
508 
509 	for (i = 0; i < 4; i++)
510 	{
511 		if ((s = strchr(s, ',')) == NULL)
512 			return;
513 		s++;
514 	}
515 	if (strcmp(s, "universe(att)"))
516 		return;
517 
518 	(void) universe(U_ATT);
519 #endif
520 }
521