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