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