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