xref: /src/usr.sbin/cron/cron/do_command.c (revision 12444a4da514e91fdf984b31e1691d042d5f88d2)
1 /* Copyright 1988,1990,1993,1994 by Paul Vixie
2  * All rights reserved
3  */
4 
5 /*
6  * Copyright (c) 1997 by Internet Software Consortium
7  *
8  * Permission to use, copy, modify, and distribute this software for any
9  * purpose with or without fee is hereby granted, provided that the above
10  * copyright notice and this permission notice appear in all copies.
11  *
12  * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
13  * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
14  * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
15  * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
16  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
17  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
18  * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
19  * SOFTWARE.
20  */
21 
22 #if !defined(lint) && !defined(LINT)
23 static const char rcsid[] =
24     "$Id: do_command.c,v 1.3 1998/08/14 00:32:39 vixie Exp $";
25 #endif
26 
27 #include "cron.h"
28 #if defined(LOGIN_CAP)
29 # include <login_cap.h>
30 #endif
31 #ifdef PAM
32 # include <security/pam_appl.h>
33 # include <security/openpam.h>
34 #endif
35 
36 static void		child_process(entry *, user *);
37 static WAIT_T		wait_on_child(PID_T, const char *);
38 
39 extern char	*environ;
40 
41 void
do_command(entry * e,user * u)42 do_command(entry *e, user *u)
43 {
44 	pid_t pid;
45 
46 	Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
47 		getpid(), e->cmd, u->name, e->uid, e->gid))
48 
49 	/* fork to become asynchronous -- parent process is done immediately,
50 	 * and continues to run the normal cron code, which means return to
51 	 * tick().  the child and grandchild don't leave this function, alive.
52 	 */
53 	switch ((pid = fork())) {
54 	case -1:
55 		log_it("CRON", getpid(), "error", "can't fork");
56 		if (e->flags & INTERVAL)
57 			e->lastexit = time(NULL);
58 		break;
59 	case 0:
60 		/* child process */
61 		pidfile_close(pfh);
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 		Debug(DPROC, ("[%d] main process forked child #%d, "
69 		    "returning to work\n", getpid(), pid))
70 		if (e->flags & INTERVAL) {
71 			e->lastexit = 0;
72 			e->child = pid;
73 		}
74 		break;
75 	}
76 	Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
77 }
78 
79 #ifdef PAM
80 static void
pam_cleanup(pam_handle_t ** pamhp,int * session_opened,int * cred_established,char *** pam_envp,const char * usernm,pid_t pid,int log_errors,int end_status)81 pam_cleanup(pam_handle_t **pamhp, int *session_opened, int *cred_established,
82     char ***pam_envp, const char *usernm, pid_t pid, int log_errors,
83     int end_status)
84 {
85 	int pam_err;
86 
87 	if (*pamhp == NULL)
88 		return;
89 	if (*session_opened) {
90 		pam_err = pam_close_session(*pamhp, PAM_SILENT);
91 		if (log_errors && pam_err != PAM_SUCCESS) {
92 			log_it(usernm, pid, "SESSION-CLOSE",
93 			    pam_strerror(*pamhp, pam_err));
94 		}
95 		*session_opened = 0;
96 	}
97 	if (*cred_established) {
98 		pam_err = pam_setcred(*pamhp, PAM_DELETE_CRED);
99 		if (log_errors && pam_err != PAM_SUCCESS) {
100 			log_it(usernm, pid, "CRED-DELETE",
101 			    pam_strerror(*pamhp, pam_err));
102 		}
103 		*cred_established = 0;
104 	}
105 	if (*pam_envp != NULL) {
106 		openpam_free_envlist(*pam_envp);
107 		*pam_envp = NULL;
108 	}
109 	pam_end(*pamhp, end_status);
110 	*pamhp = NULL;
111 }
112 #endif
113 
114 
115 static void
child_process(entry * e,user * u)116 child_process(entry *e, user *u)
117 {
118 	int stdin_pipe[2], stdout_pipe[2];
119 	char *input_data;
120 	const char *usernm, *mailto, *mailfrom, *mailcc, *mailbcc;
121 	PID_T jobpid, stdinjob, mailpid;
122 	FILE *mail;
123 	int bytes = 1;
124 	int status = 0;
125 	const char *homedir = NULL;
126 #ifdef PAM
127 	pam_handle_t *pamh = NULL;
128 	int pam_err = PAM_SUCCESS;
129 	int pam_session_opened = 0;
130 	int pam_cred_established = 0;
131 	/* Keep PAM env list in the middle process for the grandchild to use. */
132 	char **pam_envp = NULL;
133 #endif
134 # if defined(LOGIN_CAP)
135 	struct passwd *pwd;
136 	login_cap_t *lc;
137 # endif
138 
139 	Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
140 
141 	/* mark ourselves as different to PS command watchers by upshifting
142 	 * our program name.  This has no effect on some kernels.
143 	 */
144 	setproctitle("running job");
145 
146 	/* discover some useful and important environment settings
147 	 */
148 	usernm = env_get("LOGNAME", e->envp);
149 	mailto = env_get("MAILTO", e->envp);
150 	mailcc = env_get("MAILCC", e->envp);
151 	mailbcc = env_get("MAILBCC", e->envp);
152 	mailfrom = env_get("MAILFROM", e->envp);
153 
154 #ifdef PAM
155 	/* use PAM to see if the user's account is available,
156 	 * i.e., not locked or expired or whatever.  skip this
157 	 * for system tasks from /etc/crontab -- they can run
158 	 * as any user.
159 	 */
160 	if (strcmp(u->name, SYS_NAME)) {	/* not equal */
161 		struct pam_conv pamc = {
162 			.conv = openpam_nullconv,
163 			.appdata_ptr = NULL
164 		};
165 
166 		Debug(DPROC, ("[%d] checking account with PAM\n", getpid()))
167 
168 		/* u->name keeps crontab owner name while LOGNAME is the name
169 		 * of user to run command on behalf of.  they should be the
170 		 * same for a task from a per-user crontab.
171 		 */
172 		if (strcmp(u->name, usernm)) {
173 			log_it(usernm, getpid(), "username ambiguity", u->name);
174 			exit(ERROR_EXIT);
175 		}
176 
177 		pam_err = pam_start("cron", usernm, &pamc, &pamh);
178 		if (pam_err != PAM_SUCCESS) {
179 			log_it("CRON", getpid(), "error", "can't start PAM");
180 			exit(ERROR_EXIT);
181 		}
182 
183 		pam_err = pam_set_item(pamh, PAM_TTY, "cron");
184 		if (pam_err != PAM_SUCCESS) {
185 			log_it("CRON", getpid(), "error", "can't set PAM_TTY");
186 			pam_cleanup(&pamh, &pam_session_opened,
187 			    &pam_cred_established, &pam_envp, usernm,
188 			    getpid(), 0, pam_err);
189 			exit(ERROR_EXIT);
190 		}
191 
192 		pam_err = pam_acct_mgmt(pamh, PAM_SILENT);
193 		/* Expired password shouldn't prevent the job from running. */
194 		if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD) {
195 			log_it(usernm, getpid(), "USER", "account unavailable");
196 			pam_cleanup(&pamh, &pam_session_opened,
197 			    &pam_cred_established, &pam_envp, usernm,
198 			    getpid(), 0, pam_err);
199 			exit(ERROR_EXIT);
200 		}
201 
202 		pam_err = pam_setcred(pamh, PAM_ESTABLISH_CRED);
203 		if (pam_err != PAM_SUCCESS) {
204 			log_it(usernm, getpid(), "CRED",
205 			    pam_strerror(pamh, pam_err));
206 			pam_cleanup(&pamh, &pam_session_opened,
207 			    &pam_cred_established, &pam_envp, usernm,
208 			    getpid(), 0, pam_err);
209 			exit(ERROR_EXIT);
210 		}
211 		pam_cred_established = 1;
212 
213 		/* Establish the session while still root in the middle process. */
214 		pam_err = pam_open_session(pamh, PAM_SILENT);
215 		if (pam_err != PAM_SUCCESS) {
216 			log_it(usernm, getpid(), "SESSION",
217 			    pam_strerror(pamh, pam_err));
218 			pam_cleanup(&pamh, &pam_session_opened,
219 			    &pam_cred_established, &pam_envp, usernm,
220 			    getpid(), 0, pam_err);
221 			exit(ERROR_EXIT);
222 		}
223 		pam_session_opened = 1;
224 
225 		/* Collect PAM env now; apply only in grandchild before exec. */
226 		pam_envp = pam_getenvlist(pamh);
227 	}
228 #endif
229 
230 	/* our parent is watching for our death by catching SIGCHLD.  we
231 	 * do not care to watch for our children's deaths this way -- we
232 	 * use wait() explicitly.  so we have to disable the signal (which
233 	 * was inherited from the parent).
234 	 */
235 	(void) signal(SIGCHLD, SIG_DFL);
236 
237 	/* create some pipes to talk to our future child
238 	 */
239 	if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) {
240 		log_it("CRON", getpid(), "error", "can't pipe");
241 #ifdef PAM
242 		if (pamh != NULL && strcmp(u->name, SYS_NAME)) {
243 			pam_cleanup(&pamh, &pam_session_opened,
244 			    &pam_cred_established, &pam_envp, usernm,
245 			    getpid(), 1, pam_err);
246 		}
247 #endif
248 		exit(ERROR_EXIT);
249 	}
250 
251 	/* since we are a forked process, we can diddle the command string
252 	 * we were passed -- nobody else is going to use it again, right?
253 	 *
254 	 * if a % is present in the command, previous characters are the
255 	 * command, and subsequent characters are the additional input to
256 	 * the command.  Subsequent %'s will be transformed into newlines,
257 	 * but that happens later.
258 	 *
259 	 * If there are escaped %'s, remove the escape character.
260 	 */
261 	/*local*/{
262 		int escaped = FALSE;
263 		int ch;
264 		char *p;
265 
266 		for (input_data = p = e->cmd;
267 		     (ch = *input_data) != '\0';
268 		     input_data++, p++) {
269 			if (p != input_data)
270 				*p = ch;
271 			if (escaped) {
272 				if (ch == '%' || ch == '\\')
273 					*--p = ch;
274 				escaped = FALSE;
275 				continue;
276 			}
277 			if (ch == '\\') {
278 				escaped = TRUE;
279 				continue;
280 			}
281 			if (ch == '%') {
282 				*input_data++ = '\0';
283 				break;
284 			}
285 		}
286 		*p = '\0';
287 	}
288 
289 	/* fork again, this time so we can exec the user's command.
290 	 */
291 	switch (jobpid = fork()) {
292 	case -1:
293 		log_it("CRON", getpid(), "error", "can't fork");
294 #ifdef PAM
295 		if (pamh != NULL && strcmp(u->name, SYS_NAME)) {
296 			pam_cleanup(&pamh, &pam_session_opened,
297 			    &pam_cred_established, &pam_envp, usernm,
298 			    getpid(), 1, pam_err);
299 		}
300 #endif
301 		exit(ERROR_EXIT);
302 		/*NOTREACHED*/
303 	case 0:
304 		Debug(DPROC, ("[%d] grandchild process fork()'ed\n",
305 			      getpid()))
306 
307 #ifdef PAM
308 		/* Grandchild runs the user job; PAM handle remains in parent. */
309 		pamh = NULL;
310 #endif
311 		if (e->uid == ROOT_UID)
312 			Jitter = RootJitter;
313 		if (Jitter != 0) {
314 			srandom(getpid());
315 			sleep(random() % Jitter);
316 		}
317 
318 		/* write a log message.  we've waited this long to do it
319 		 * because it was not until now that we knew the PID that
320 		 * the actual user command shell was going to get and the
321 		 * PID is part of the log message.
322 		 */
323 		if ((e->flags & DONT_LOG) == 0) {
324 			char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
325 
326 			log_it(usernm, getpid(), "CMD", x);
327 			free(x);
328 		}
329 
330 		/* that's the last thing we'll log.  close the log files.
331 		 */
332 #ifdef SYSLOG
333 		closelog();
334 #endif
335 
336 		/* get new pgrp, void tty, etc.
337 		 */
338 		(void) setsid();
339 
340 		/* close the pipe ends that we won't use.  this doesn't affect
341 		 * the parent, who has to read and write them; it keeps the
342 		 * kernel from recording us as a potential client TWICE --
343 		 * which would keep it from sending SIGPIPE in otherwise
344 		 * appropriate circumstances.
345 		 */
346 		close(stdin_pipe[WRITE_PIPE]);
347 		close(stdout_pipe[READ_PIPE]);
348 
349 		/* grandchild process.  make std{in,out} be the ends of
350 		 * pipes opened by our daddy; make stderr go to stdout.
351 		 */
352 		close(STDIN);	dup2(stdin_pipe[READ_PIPE], STDIN);
353 		close(STDOUT);	dup2(stdout_pipe[WRITE_PIPE], STDOUT);
354 		close(STDERR);	dup2(STDOUT, STDERR);
355 
356 		/* close the pipes we just dup'ed.  The resources will remain.
357 		 */
358 		close(stdin_pipe[READ_PIPE]);
359 		close(stdout_pipe[WRITE_PIPE]);
360 
361 		environ = NULL;
362 
363 # if defined(LOGIN_CAP)
364 		/* Set user's entire context, but note that PATH will
365 		 * be overridden later
366 		 */
367 		if ((pwd = getpwnam(usernm)) == NULL)
368 			pwd = getpwuid(e->uid);
369 		lc = NULL;
370 		if (pwd != NULL) {
371 			if (pwd->pw_dir != NULL
372 			    && pwd->pw_dir[0] != '\0') {
373 				homedir = strdup(pwd->pw_dir);
374 				if (homedir == NULL) {
375 					warn("strdup");
376 					_exit(ERROR_EXIT);
377 				}
378 			}
379 			pwd->pw_gid = e->gid;
380 			if (e->class != NULL)
381 				lc = login_getclass(e->class);
382 		}
383 		if (pwd &&
384 		    setusercontext(lc, pwd, e->uid,
385 			    LOGIN_SETALL) == 0)
386 			(void) endpwent();
387 		else {
388 			/* fall back to the old method */
389 			(void) endpwent();
390 # endif
391 			/* set our directory, uid and gid.  Set gid first,
392 			 * since once we set uid, we've lost root privileges.
393 			 */
394 			if (setgid(e->gid) != 0) {
395 				log_it(usernm, getpid(),
396 				    "error", "setgid failed");
397 				_exit(ERROR_EXIT);
398 			}
399 			if (initgroups(usernm, e->gid) != 0) {
400 				log_it(usernm, getpid(),
401 				    "error", "initgroups failed");
402 				_exit(ERROR_EXIT);
403 			}
404 			if (setlogin(usernm) != 0) {
405 				log_it(usernm, getpid(),
406 				    "error", "setlogin failed");
407 				_exit(ERROR_EXIT);
408 			}
409 			if (setuid(e->uid) != 0) {
410 				log_it(usernm, getpid(),
411 				    "error", "setuid failed");
412 				_exit(ERROR_EXIT);
413 			}
414 			/* we aren't root after this..*/
415 #if defined(LOGIN_CAP)
416 		}
417 		if (lc != NULL)
418 			login_close(lc);
419 #endif
420 
421 		/* For compatibility, we chdir to the value of HOME if it was
422 		 * specified explicitly in the crontab file, but not if it was
423 		 * set in the environment by some other mechanism. We chdir to
424 		 * the homedir given by the pw entry otherwise.
425 		 *
426 		 * If !LOGIN_CAP, then HOME is always set in e->envp.
427 		 * PAM environment is applied later for the job; we do not
428 		 * use it for cwd to avoid changing historical behavior.
429 		 */
430 		{
431 			char	*new_home = env_get("HOME", e->envp);
432 			if (new_home != NULL && new_home[0] != '\0')
433 				chdir(new_home);
434 			else if (homedir != NULL)
435 				chdir(homedir);
436 			else
437 				chdir("/");
438 		}
439 
440 		/* exec the command. Note that SHELL is not respected from
441 		 * either login.conf or pw_shell, only an explicit setting
442 		 * in the crontab. (default of _PATH_BSHELL is supplied when
443 		 * setting up the entry)
444 		 */
445 		{
446 			char	*shell = env_get("SHELL", e->envp);
447 			char	**p;
448 
449 #ifdef PAM
450 			if (pam_envp != NULL) {
451 				char **pp;
452 
453 				/* Apply PAM-provided env only to the job process. */
454 				for (pp = pam_envp; *pp != NULL; pp++) {
455 					/*
456 					 * Hand off each PAM string directly to the
457 					 * environment; this process must not free
458 					 * pam_envp after putenv() since the strings
459 					 * must persist until exec. The parent will
460 					 * free its copy after fork.
461 					 */
462 					if (putenv(*pp) != 0) {
463 						warn("putenv");
464 						_exit(ERROR_EXIT);
465 					}
466 				}
467 				/* Free the pointer array; strings stay for exec. */
468 				free(pam_envp);
469 				pam_envp = NULL;
470 			}
471 #endif
472 			/* Apply the environment from the entry, overriding
473 			 * existing values (this will always set LOGNAME and
474 			 * SHELL). putenv should not fail unless malloc does.
475 			 */
476 			for (p = e->envp; *p; ++p) {
477 				if (putenv(*p) != 0) {
478 					warn("putenv");
479 					_exit(ERROR_EXIT);
480 				}
481 			}
482 
483 			/* HOME in login.conf overrides pw, and HOME in the
484 			 * crontab overrides both. So set pw's value only if
485 			 * nothing was already set (overwrite==0).
486 			 */
487 			if (homedir != NULL
488 			    && setenv("HOME", homedir, 0) < 0) {
489 				warn("setenv(HOME)");
490 				_exit(ERROR_EXIT);
491 			}
492 
493 			/* PATH in login.conf is respected, but the crontab
494 			 * overrides; set a default value only if nothing
495 			 * already set.
496 			 */
497 			if (setenv("PATH", _PATH_DEFPATH, 0) < 0) {
498 				warn("setenv(PATH)");
499 				_exit(ERROR_EXIT);
500 			}
501 
502 # if DEBUGGING
503 			if (DebugFlags & DTEST) {
504 				fprintf(stderr,
505 				"debug DTEST is on, not exec'ing command.\n");
506 				fprintf(stderr,
507 				"\tcmd='%s' shell='%s'\n", e->cmd, shell);
508 				_exit(OK_EXIT);
509 			}
510 # endif /*DEBUGGING*/
511 			execl(shell, shell, "-c", e->cmd, (char *)NULL);
512 			warn("execl: couldn't exec `%s'", shell);
513 			_exit(ERROR_EXIT);
514 		}
515 		break;
516 	default:
517 		/* parent process */
518 		break;
519 	}
520 
521 #ifdef PAM
522 	if (jobpid > 0 && pam_envp != NULL) {
523 		/* Parent doesn't need PAM env list after the fork. */
524 		openpam_free_envlist(pam_envp);
525 		pam_envp = NULL;
526 	}
527 #endif
528 
529 	/* middle process, child of original cron, parent of process running
530 	 * the user's command.
531 	 */
532 
533 	Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
534 
535 	/* close the ends of the pipe that will only be referenced in the
536 	 * grandchild process...
537 	 */
538 	close(stdin_pipe[READ_PIPE]);
539 	close(stdout_pipe[WRITE_PIPE]);
540 
541 	/*
542 	 * write, to the pipe connected to child's stdin, any input specified
543 	 * after a % in the crontab entry.  while we copy, convert any
544 	 * additional %'s to newlines.  when done, if some characters were
545 	 * written and the last one wasn't a newline, write a newline.
546 	 *
547 	 * Note that if the input data won't fit into one pipe buffer (2K
548 	 * or 4K on most BSD systems), and the child doesn't read its stdin,
549 	 * we would block here.  thus we must fork again.
550 	 */
551 
552 	if (*input_data && (stdinjob = fork()) == 0) {
553 		FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
554 		int need_newline = FALSE;
555 		int escaped = FALSE;
556 		int ch;
557 
558 		if (out == NULL) {
559 			warn("fdopen failed in child2");
560 			_exit(ERROR_EXIT);
561 		}
562 
563 		Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
564 
565 		/* close the pipe we don't use, since we inherited it and
566 		 * are part of its reference count now.
567 		 */
568 		close(stdout_pipe[READ_PIPE]);
569 
570 		/* translation:
571 		 *	\% -> %
572 		 *	%  -> \n
573 		 *	\x -> \x	for all x != %
574 		 */
575 		while ((ch = *input_data++) != '\0') {
576 			if (escaped) {
577 				if (ch != '%')
578 					putc('\\', out);
579 			} else {
580 				if (ch == '%')
581 					ch = '\n';
582 			}
583 
584 			if (!(escaped = (ch == '\\'))) {
585 				putc(ch, out);
586 				need_newline = (ch != '\n');
587 			}
588 		}
589 		if (escaped)
590 			putc('\\', out);
591 		if (need_newline)
592 			putc('\n', out);
593 
594 		/* close the pipe, causing an EOF condition.  fclose causes
595 		 * stdin_pipe[WRITE_PIPE] to be closed, too.
596 		 */
597 		fclose(out);
598 
599 		Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
600 		exit(0);
601 	}
602 
603 	/* close the pipe to the grandkiddie's stdin, since its wicked uncle
604 	 * ernie back there has it open and will close it when he's done.
605 	 */
606 	close(stdin_pipe[WRITE_PIPE]);
607 
608 	/*
609 	 * read output from the grandchild.  it's stderr has been redirected to
610 	 * it's stdout, which has been redirected to our pipe.  if there is any
611 	 * output, we'll be mailing it to the user whose crontab this is...
612 	 * when the grandchild exits, we'll get EOF.
613 	 */
614 
615 	Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
616 
617 	/*local*/{
618 		FILE *in = fdopen(stdout_pipe[READ_PIPE], "r");
619 		int ch;
620 
621 		if (in == NULL) {
622 			warn("fdopen failed in child");
623 			_exit(ERROR_EXIT);
624 		}
625 
626 		mail = NULL;
627 
628 		ch = getc(in);
629 		if (ch != EOF) {
630 			Debug(DPROC|DEXT,
631 				("[%d] got data (%x:%c) from grandchild\n",
632 					getpid(), ch, ch))
633 
634 			/* get name of recipient.  this is MAILTO if set to a
635 			 * valid local username; USER otherwise.
636 			 */
637 			if (mailto == NULL) {
638 				/* MAILTO not present, set to USER,
639 				 * unless globally overridden.
640 				 */
641 				if (defmailto)
642 					mailto = defmailto;
643 				else
644 					mailto = usernm;
645 			}
646 			if (mailto && *mailto == '\0')
647 				mailto = NULL;
648 
649 			/* if we are supposed to be mailing, MAILTO will
650 			 * be non-NULL.  only in this case should we set
651 			 * up the mail command and subjects and stuff...
652 			 */
653 
654 			if (mailto) {
655 				char	**env;
656 				char	mailcmd[MAX_COMMAND];
657 				char	hostname[MAXHOSTNAMELEN];
658 
659 				if (gethostname(hostname, MAXHOSTNAMELEN) == -1)
660 					hostname[0] = '\0';
661 				hostname[sizeof(hostname) - 1] = '\0';
662 				if (snprintf(mailcmd, sizeof(mailcmd), MAILFMT,
663 				    MAILARG) >= sizeof(mailcmd)) {
664 					warnx("mail command too long");
665 					(void) _exit(ERROR_EXIT);
666 				}
667 				if (!(mail = cron_popen(mailcmd, "w", e, &mailpid))) {
668 					warn("%s", mailcmd);
669 					(void) _exit(ERROR_EXIT);
670 				}
671 				if (mailfrom == NULL || *mailfrom == '\0')
672 					fprintf(mail, "From: Cron Daemon <%s@%s>\n",
673 					    usernm, hostname);
674 				else
675 					fprintf(mail, "From: Cron Daemon <%s>\n",
676 					    mailfrom);
677 				fprintf(mail, "To: %s\n", mailto);
678 				fprintf(mail, "CC: %s\n", mailcc);
679 				fprintf(mail, "BCC: %s\n", mailbcc);
680 				fprintf(mail, "Subject: Cron <%s@%s> %s\n",
681 					usernm, first_word(hostname, "."),
682 					e->cmd);
683 #ifdef MAIL_DATE
684 				fprintf(mail, "Date: %s\n",
685 					arpadate(&TargetTime));
686 #endif /*MAIL_DATE*/
687 				for (env = e->envp;  *env;  env++)
688 					fprintf(mail, "X-Cron-Env: <%s>\n",
689 						*env);
690 				fprintf(mail, "\n");
691 
692 				/* this was the first char from the pipe
693 				 */
694 				putc(ch, mail);
695 			}
696 
697 			/* we have to read the input pipe no matter whether
698 			 * we mail or not, but obviously we only write to
699 			 * mail pipe if we ARE mailing.
700 			 */
701 
702 			while (EOF != (ch = getc(in))) {
703 				bytes++;
704 				if (mail)
705 					putc(ch, mail);
706 			}
707 		}
708 		/*if data from grandchild*/
709 
710 		Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
711 
712 		/* also closes stdout_pipe[READ_PIPE] */
713 		fclose(in);
714 	}
715 
716 	/* wait for children to die.
717 	 */
718 	if (jobpid > 0) {
719 		WAIT_T waiter;
720 
721 		waiter = wait_on_child(jobpid, "grandchild command job");
722 
723 		/* If everything went well, and -n was set, _and_ we have mail,
724 		 * we won't be mailing... so shoot the messenger!
725 		 */
726 		if (WIFEXITED(waiter) && WEXITSTATUS(waiter) == 0
727 		    && (e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR
728 		    && mail) {
729 			Debug(DPROC, ("[%d] %s executed successfully, mail suppressed\n",
730 				getpid(), "grandchild command job"))
731 			kill(mailpid, SIGKILL);
732 			(void)fclose(mail);
733 			mail = NULL;
734 		}
735 
736 		/* only close pipe if we opened it -- i.e., we're
737 		 * mailing...
738 		 */
739 
740 		if (mail) {
741 			Debug(DPROC, ("[%d] closing pipe to mail\n",
742 				getpid()))
743 			/* Note: the pclose will probably see
744 			 * the termination of the grandchild
745 			 * in addition to the mail process, since
746 			 * it (the grandchild) is likely to exit
747 			 * after closing its stdout.
748 			 */
749 			status = cron_pclose(mail);
750 
751 			/* if there was output and we could not mail it,
752 			 * log the facts so the poor user can figure out
753 			 * what's going on.
754 			 */
755 			if (status) {
756 				char buf[MAX_TEMPSTR];
757 
758 				snprintf(buf, sizeof(buf),
759 			"mailed %d byte%s of output but got status 0x%04x\n",
760 					bytes, (bytes==1)?"":"s",
761 					status);
762 				log_it(usernm, getpid(), "MAIL", buf);
763 			}
764 		}
765 	}
766 
767 	if (*input_data && stdinjob > 0)
768 		wait_on_child(stdinjob, "grandchild stdinjob");
769 
770 #ifdef PAM
771 	if (pamh != NULL && strcmp(u->name, SYS_NAME)) {
772 		/* Close the PAM session after the job finishes. */
773 		pam_cleanup(&pamh, &pam_session_opened, &pam_cred_established,
774 		    &pam_envp, usernm, getpid(), 1, PAM_SUCCESS);
775 	}
776 #endif
777 }
778 
779 static WAIT_T
wait_on_child(PID_T childpid,const char * name)780 wait_on_child(PID_T childpid, const char *name)
781 {
782 	WAIT_T waiter;
783 	PID_T pid;
784 
785 	Debug(DPROC, ("[%d] waiting for %s (%d) to finish\n",
786 		getpid(), name, childpid))
787 
788 #ifdef POSIX
789 	while ((pid = waitpid(childpid, &waiter, 0)) < 0 && errno == EINTR)
790 #else
791 	while ((pid = wait4(childpid, &waiter, 0, NULL)) < 0 && errno == EINTR)
792 #endif
793 		;
794 
795 	if (pid < OK)
796 		return waiter;
797 
798 	Debug(DPROC, ("[%d] %s (%d) finished, status=%04x",
799 		getpid(), name, pid, WEXITSTATUS(waiter)))
800 	if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
801 		Debug(DPROC, (", dumped core"))
802 	Debug(DPROC, ("\n"))
803 
804 	return waiter;
805 }
806