2 * FCRON - periodic command scheduler
4 * Copyright 2000-2012 Thibault Godouet <fcron@free.fr>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * The GNU General Public License can also be found in the file
21 * `LICENSE' that comes with the fcron source distribution.
28 #include "temp_file.h"
31 void end_job(cl_t * line, int status, FILE * mailf, short mailpos,
33 void end_mailer(cl_t * line, int status);
35 void die_mail_pame(cl_t * cl, int pamerrno, struct passwd *pas, char *str,
40 int read_write_pipe(int fd, void *buf, size_t size, int action);
41 int read_pipe(int fd, void *to, size_t size);
42 int write_pipe(int fd, void *buf, size_t size);
43 void become_user(struct cl_t *cl, struct passwd *pas, char *home);
47 die_mail_pame(cl_t * cl, int pamerrno, struct passwd *pas, char *str,
49 /* log an error in syslog, mail user if necessary, and die */
53 snprintf(buf, sizeof(buf), "%s for user '%s'", str, pas->pw_name);
55 if (is_mail(cl->cl_option)) {
56 char **envp = env_list_export_envp(env);
58 create_mail(cl, "Could not run fcron job", NULL, NULL, envp);
60 /* print the error in both syslog and a file, in order to mail it to user */
61 if (dup2(fileno(mailf), 1) != 1 || dup2(1, 2) != 2)
62 die_e("dup2() error"); /* dup2 also clears close-on-exec flag */
65 error_pame(pamh, pamerrno, buf, cl->cl_shell);
66 error("Job '%s' has *not* run.", cl->cl_shell);
69 pam_end(pamh, pamerrno);
71 become_user(cl, pas, "/");
73 launch_mailer(cl, mailf, envp);
74 /* launch_mailer() does not return : we never get here */
77 die_pame(pamh, pamerrno, buf, cl->cl_shell);
82 become_user(struct cl_t *cl, struct passwd *pas, char *home)
83 /* Become the user who owns the job: change privileges, check PAM authorization,
84 * and change dir to HOME. */
87 #ifndef RUN_NON_PRIVILEGED
89 die("become_user() called with a NULL struct passwd");
91 /* Change running state to the user in question */
92 if (initgroups(pas->pw_name, pas->pw_gid) < 0)
93 die_e("initgroups failed: %s", pas->pw_name);
95 if (setgid(pas->pw_gid) < 0)
96 die("setgid failed: %s %d", pas->pw_name, pas->pw_gid);
98 if (setuid(pas->pw_uid) < 0)
99 die("setuid failed: %s %d", pas->pw_name, pas->pw_uid);
100 #endif /* not RUN_NON_PRIVILEGED */
102 /* make sure HOME is defined and change dir to it */
103 if (chdir(home) != 0) {
104 error_e("Could not chdir to HOME dir '%s'. Trying to chdir to '/'.",
107 die_e("Could not chdir to HOME dir /");
113 setup_user_and_env(struct cl_t *cl, struct passwd *pas,
114 char ***sendmailenv, char ***jobenv, char **curshell,
115 char **curhome, char **content_type, char **encoding)
116 /* Check PAM authorization, and setup the environment variables
117 * to run sendmail and to run the job itself. Change dir to HOME and check if SHELL is ok */
118 /* (*curshell) and (*curhome) will be allocated and should thus be freed
119 * if curshell and curhome are not NULL. */
120 /* Return the the two env var sets, the shell to use to execle() commands and the home dir */
122 env_list_t *env_list = env_list_init();
125 char *myshell = NULL;
132 die("setup_user_and_env() called with a NULL struct passwd");
134 env_list_setenv(env_list, "USER", pas->pw_name, 1);
135 env_list_setenv(env_list, "LOGNAME", pas->pw_name, 1);
136 env_list_setenv(env_list, "HOME", pas->pw_dir, 1);
137 /* inherit fcron's PATH for sendmail. We will later change it to DEFAULT_JOB_PATH
138 * or a user defined PATH for the job itself */
139 path = getenv("PATH");
140 env_list_setenv(env_list, "PATH", (path != NULL) ? path : DEFAULT_JOB_PATH,
143 if (cl->cl_tz != NULL)
144 env_list_setenv(env_list, "TZ", cl->cl_tz, 1);
145 /* To ensure compatibility with Vixie cron, we don't use the shell defined
146 * in /etc/passwd by default, but the default value from fcron.conf instead: */
147 if (shell != NULL && shell[0] != '\0')
148 /* default: use value from fcron.conf */
149 env_list_setenv(env_list, "SHELL", shell, 1);
151 /* shell is empty, ie. not defined: fail back to /etc/passwd's value */
152 env_list_setenv(env_list, "SHELL", pas->pw_shell, 1);
154 #if ( ! defined(RUN_NON_PRIVILEGED)) && defined(HAVE_LIBPAM)
155 /* Open PAM session for the user and obtain any security
156 * credentials we might need */
158 retcode = pam_start("fcron", pas->pw_name, &apamconv, &pamh);
159 if (retcode != PAM_SUCCESS)
160 die_pame(pamh, retcode, "Could not start PAM for %s", cl->cl_shell);
161 /* Some system seem to need that pam_authenticate() call.
162 * Anyway, we have no way to authentificate the user :
163 * we must set auth to pam_permit. */
164 retcode = pam_authenticate(pamh, PAM_SILENT);
165 if (retcode != PAM_SUCCESS)
166 die_mail_pame(cl, retcode, pas,
167 "Could not authenticate PAM user", env_list);
168 retcode = pam_acct_mgmt(pamh, PAM_SILENT); /* permitted access? */
169 if (retcode != PAM_SUCCESS)
170 die_mail_pame(cl, retcode, pas,
171 "Could not init PAM account management", env_list);
172 retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED | PAM_SILENT);
173 if (retcode != PAM_SUCCESS)
174 die_mail_pame(cl, retcode, pas, "Could not set PAM credentials",
176 retcode = pam_open_session(pamh, PAM_SILENT);
177 if (retcode != PAM_SUCCESS)
178 die_mail_pame(cl, retcode, pas, "Could not open PAM session", env_list);
180 for (env = pam_getenvlist(pamh); env && *env; env++) {
181 env_list_putenv(env_list, *env, 1);
184 /* Close the log here, because PAM calls openlog(3) and
185 * our log messages could go to the wrong facility */
187 #endif /* ( ! defined(RUN_NON_PRIVILEGED)) && defined(HAVE_LIBPAM) */
189 /* export the environment for sendmail before we apply user customization */
190 if (sendmailenv != NULL)
191 *sendmailenv = env_list_export_envp(env_list);
193 /* Now add user customizations to the environment to form jobenv */
195 if (jobenv != NULL) {
197 /* Make sure we don't keep fcron daemon's PATH (which we used for sendmail) */
198 env_list_setenv(env_list, "PATH", DEFAULT_JOB_PATH, 1);
200 for (e = env_list_first(cl->cl_file->cf_env_list); e != NULL;
201 e = env_list_next(cl->cl_file->cf_env_list)) {
202 env_list_putenv(env_list, e->e_envvar, 1);
205 /* make sure HOME is defined */
206 env_list_putenv(env_list, "HOME=/", 0); /* don't overwrite if already defined */
207 if (curhome != NULL) {
208 (*curhome) = strdup2(env_list_getenv(env_list, "HOME"));
211 /* check that SHELL is valid */
212 myshell = env_list_getenv(env_list, "SHELL");
213 if (myshell == NULL || myshell[0] == '\0') {
216 else if (access(myshell, X_OK) != 0) {
218 error("shell \"%s\" : no file or directory. SHELL set to %s",
221 error_e("shell \"%s\" not valid : SHELL set to %s", myshell,
226 env_list_setenv(env_list, "SHELL", myshell, 1);
227 if (curshell != NULL)
228 *curshell = strdup2(myshell);
230 *jobenv = env_list_export_envp(env_list);
234 if (content_type != NULL) {
235 (*content_type) = strdup2(env_list_getenv(env_list, "CONTENT_TYPE"));
237 if (encoding != NULL) {
239 strdup2(env_list_getenv(env_list, "CONTENT_TRANSFER_ENCODING"));
242 env_list_destroy(env_list);
247 change_user_setup_env(struct cl_t *cl,
248 char ***sendmailenv, char ***jobenv, char **curshell,
249 char **curhome, char **content_type, char **encoding)
250 /* call setup_user_and_env() and become_user().
251 * As a result, *curshell and *curhome will be allocated and should thus be freed
252 * if curshell and curhome are not NULL. */
257 pas = getpwnam(cl->cl_runas);
259 die_e("failed to get passwd fields for user \"%s\"", cl->cl_runas);
261 setup_user_and_env(cl, pas, sendmailenv, jobenv, curshell, curhome,
262 content_type, encoding);
264 become_user(cl, pas, (curhome!=NULL)? *curhome : "/");
269 /* set signals handling to its default */
271 signal(SIGTERM, SIG_DFL);
272 signal(SIGCHLD, SIG_DFL);
273 signal(SIGHUP, SIG_DFL);
274 signal(SIGUSR1, SIG_DFL);
275 signal(SIGUSR2, SIG_DFL);
276 signal(SIGPIPE, SIG_DFL);
281 create_mail(cl_t * line, char *subject, char *content_type, char *encoding,
283 /* create a temp file and write in it a mail header */
285 /* create temporary file for stdout and stderr of the job */
286 int mailfd = temp_file(NULL);
287 FILE *mailf = fdopen(mailfd, "r+");
288 char hostname[USER_NAME_LEN];
289 /* is this a complete mail address ? (ie. with a "@", not only a username) */
290 char add_hostname = 0;
294 die_e("Could not fdopen() mailfd");
296 #ifdef HAVE_GETHOSTNAME
297 if (gethostname(hostname, sizeof(hostname)) != 0) {
298 error_e("Could not get hostname");
302 /* it is unspecified whether a truncated hostname is NUL-terminated */
303 hostname[USER_NAME_LEN - 1] = '\0';
305 /* check if mailto is a complete mail address */
306 add_hostname = (strchr(line->cl_mailto, '@') == NULL) ? 1 : 0;
308 #else /* HAVE_GETHOSTNAME */
310 #endif /* HAVE_GETHOSTNAME */
312 /* write mail header */
314 fprintf(mailf, "To: %s@%s\n", line->cl_mailto, hostname);
316 fprintf(mailf, "To: %s\n", line->cl_mailto);
319 fprintf(mailf, "Subject: fcron <%s@%s> %s: %s\n",
320 line->cl_file->cf_user, (hostname[0] != '\0') ? hostname : "?",
321 subject, line->cl_shell);
323 fprintf(mailf, "Subject: fcron <%s@%s> %s\n", line->cl_file->cf_user,
324 (hostname[0] != '\0') ? hostname : "?", line->cl_shell);
326 if (content_type == NULL) {
327 fprintf(mailf, "Content-Type: text/plain; charset=%s\n",
328 default_mail_charset);
331 /* user specified Content-Type header. */
334 /* Remove new-lines or users could specify arbitrary mail headers!
335 * (fcrontab should already prevent that, but better safe than sorry) */
336 for (c = content_type; *c != '\0'; c++) {
340 fprintf(mailf, "Content-Type: %s\n", content_type);
343 if (encoding != NULL) {
346 /* Remove new-lines or users could specify arbitrary mail headers!
347 * (fcrontab should already prevent that, but better safe than sorry) */
348 for (c = encoding; *c != '\0'; c++) {
352 fprintf(mailf, "Content-Transfer-Encoding: %s\n", encoding);
355 /* Add headers so as automated systems can identify that this message
356 * is an automated one sent by fcron.
357 * That's useful for example for vacation auto-reply systems: no need
358 * to send such an automated response to fcron! */
360 /* The Auto-Submitted header is
361 * defined (and suggested by) RFC3834. */
362 fprintf(mailf, "Auto-Submitted: auto-generated\n");
364 /* See environ(7) and execle(3) to get documentation on environ:
365 * it is an array of NULL-terminated strings, whose last entry is NULL */
367 for (i = 0; env[i] != NULL; i++) {
368 fprintf(mailf, "X-Cron-Env: <%s>\n", env[i]);
372 /* Final line return to end the header section: */
373 fprintf(mailf, "\n");
380 read_write_pipe(int fd, void *buf, size_t size, int action)
381 /* Read/write data from/to pipe.
382 * action can either be PIPE_WRITE or PIPE_READ.
383 * Handles signal interruptions, and read in several passes.
384 * Returns ERR in case of a closed pipe, the errno from errno
385 * for other errors, and OK if everything was read successfully */
387 int size_processed = 0;
391 while (size_processed < size) {
393 if (action == PIPE_READ)
394 ret = read(fd, (char *)buf + size_processed, size);
395 else if (action == PIPE_WRITE)
396 ret = write(fd, (char *)buf + size_processed, size);
398 error("Invalid action parameter for function read_write_pipe():"
403 /* some data read correctly -- we still may need
404 * one or several calls of read() to read the rest */
405 size_processed += ret;
406 else if (ret < 0 && errno == EINTR)
407 /* interrupted by a signal : let's try again */
413 /* is it really an error when writing ? should we continue
418 ("read_write_pipe(): read/write returned 0: retrying... (size: %d, size_processed: %d, num_retry: %d)",
419 size, size_processed, num_retry);
435 read_pipe(int fd, void *buf, size_t size)
436 /* Read data from pipe.
437 * Handles signal interruptions, and read in several passes.
438 * Returns ERR in case of a closed pipe, the errno from read
439 * for other errors, and OK if everything was read successfully */
441 return read_write_pipe(fd, buf, size, PIPE_READ);
445 write_pipe(int fd, void *buf, size_t size)
446 /* Read data from pipe.
447 * Handles signal interruptions, and read in several passes.
448 * Returns ERR in case of a closed pipe, the errno from write
449 * for other errors, and OK if everything was read successfully */
451 return read_write_pipe(fd, buf, size, PIPE_WRITE);
455 run_job_grand_child_setup_stderr_stdout(cl_t * line, int *pipe_fd)
456 /* setup stderr and stdout correctly so as the mail containing
457 * the output of the job can be send at the end of the job.
458 * Close the pipe (both ways). */
461 if (is_mail(line->cl_option)) {
462 /* we can't dup2 directly to mailfd, since a "cmd > /dev/stderr" in
463 * a script would erase all previously collected message */
464 if (dup2(pipe_fd[1], 1) != 1 || dup2(1, 2) != 2)
465 die_e("dup2() error"); /* dup2 also clears close-on-exec flag */
466 /* we close the pipe_fd[]s : the resources remain, and the pipe will
467 * be effectively close when the job stops */
468 if (close(pipe_fd[0]) < 0)
469 error_e("setup_stderr_stdout: could not close(pipe_fd[0])");
470 if (close(pipe_fd[1]) < 0)
471 error_e("setup_stderr_stdout: could not close(pipe_fd[1])");
472 /* Standard buffering results in unwanted behavior (some messages,
473 * at least error from fcron process itself, are lost) */
474 #ifdef HAVE_SETLINEBUF
478 setvbuf(stdout, NULL, _IONBF, 0);
479 setvbuf(stderr, NULL, _IONBF, 0);
482 else if (foreground) {
483 if (freopen("/dev/null", "w", stdout) == NULL)
484 error_e("could not freopen /dev/null as stdout");
485 if (freopen("/dev/null", "w", stderr) == NULL)
486 error_e("could not freopen /dev/null as stderr");
492 run_job_grand_child_setup_nice(cl_t * line)
493 /* set the nice value for the job */
495 if (line->cl_nice != 0) {
496 errno = 0; /* so that it works with any libc and kernel */
497 if (nice(line->cl_nice) == -1 && errno != 0)
498 error_e("could not set nice value");
503 run_job(struct exe_t *exeent)
504 /* fork(), redirect outputs to a temp file, and execl() the task.
505 * Return ERR if it could not fork() the first time, OK otherwise. */
509 cl_t *line = exeent->e_line;
513 /* prepare the job execution */
514 if (pipe(pipe_pid_fd) != 0) {
515 error_e("pipe(pipe_pid_fd) : setting job_pid to -1");
516 exeent->e_job_pid = -1;
517 pipe_pid_fd[0] = pipe_pid_fd[1] = -1;
522 ("run_job(): first pipe created successfully : about to do first fork()");
523 #endif /* CHECKRUNJOB */
525 switch (pid = fork()) {
527 error_e("Fork error : could not exec \"%s\"", line->cl_shell);
534 struct passwd *pas = NULL;
535 char **jobenv = NULL;
536 char **sendmailenv = NULL;
537 char *curshell = NULL;
538 char *curhome = NULL;
539 char *content_type = NULL;
540 char *encoding = NULL;
543 int to_stdout = foreground && is_stdout(line->cl_option);
545 short int mailpos = 0; /* 'empty mail file' size */
547 int flask_enabled = is_selinux_enabled();
551 debug("run_job(): child: %s, output to %s, %s, %s\n",
552 is_mail(line->cl_option) ? "mail" : "no mail",
553 to_stdout ? "stdout" : "file",
554 foreground ? "running in foreground" :
555 "running in background",
556 is_stdout(line->cl_option) ? "stdout" : "normal");
560 pas = getpwnam(line->cl_runas);
562 die_e("failed to get passwd fields for user \"%s\"",
565 setup_user_and_env(line, pas, &sendmailenv, &jobenv, &curshell,
566 &curhome, &content_type, &encoding);
568 /* close unneeded READ fd */
569 if (close(pipe_pid_fd[0]) < 0)
570 error_e("child: could not close(pipe_pid_fd[0])");
572 pipe_fd[0] = pipe_fd[1] = -1;
573 if (!to_stdout && is_mail(line->cl_option)) {
574 /* we create the temp file (if needed) before change_user(),
575 * as temp_file() needs root privileges */
576 /* if we run in foreground, stdout and stderr point to the console.
577 * Otherwise, stdout and stderr point to /dev/null . */
578 mailf = create_mail(line, NULL, content_type, encoding, jobenv);
579 mailpos = ftell(mailf);
580 if (pipe(pipe_fd) != 0)
581 die_e("could not pipe() (job not executed)");
584 become_user(line, pas, curhome);
587 /* restore umask to default */
594 ("run_job(): child: change_user() done -- about to do 2nd fork()");
595 #endif /* CHECKRUNJOB */
597 /* now, run the job */
598 switch (pid = fork()) {
600 error_e("Fork error : could not exec \"%s\"", line->cl_shell);
601 if (write(pipe_pid_fd[1], &pid, sizeof(pid)) < 0)
602 error_e("could not write child pid to pipe_pid_fd[1]");
603 if (pipe_fd[0] != -1 && close(pipe_fd[0]) < 0)
604 error_e("child: could not close(pipe_fd[0])");
605 if (pipe_fd[1] != -1 && close(pipe_fd[1]) < 0)
606 error_e("child: could not close(pipe_fd[1])");
607 if (close(pipe_pid_fd[1]) < 0)
608 error_e("child: could not close(pipe_pid_fd[1])");
613 /* grand child (child of the 2nd fork) */
615 /* the grand child does not use this pipe: close remaining fd */
616 if (close(pipe_pid_fd[1]) < 0)
617 error_e("grand child: could not close(pipe_pid_fd[1])");
620 /* note : the following closes the pipe */
621 run_job_grand_child_setup_stderr_stdout(line, pipe_fd);
624 /* now, errors will be mailed to the user (or to /dev/null) */
626 run_job_grand_child_setup_nice(line);
630 #if defined(CHECKJOBS) || defined(CHECKRUNJOB)
631 /* this will force to mail a message containing at least the exact
632 * and complete command executed for each execution of all jobs */
633 debug("run_job(): grand-child: Executing \"%s -c %s\"",
634 curshell, line->cl_shell);
635 #endif /* CHECKJOBS OR CHECKRUNJOB */
639 && setexeccon(line->cl_file->cf_user_context) < 0)
640 die_e("Can't set execute context '%s' for user '%s'.",
641 line->cl_file->cf_user_context, line->cl_runas);
643 if (setsid() == -1) {
644 die_e("setsid(): errno %d", errno);
647 execle(curshell, curshell, "-c", line->cl_shell, NULL, jobenv);
648 /* execle returns only on error */
649 die_e("Couldn't exec shell '%s'", curshell);
651 /* execution never gets here */
654 /* child (parent of the 2nd fork) */
656 /* close unneeded WRITE pipe and READ pipe */
657 if (pipe_fd[1] != -1 && close(pipe_fd[1]) < 0)
658 error_e("child: could not close(pipe_fd[1])");
661 debug("run_job(): child: pipe_fd[1] and pipe_pid_fd[0] closed"
662 " -- about to write grand-child pid to pipe");
663 #endif /* CHECKRUNJOB */
665 /* give the pid of the child to the parent (main) fcron process */
666 ret = write_pipe(pipe_pid_fd[1], &pid, sizeof(pid));
670 ("run_job(): child: Could not write job pid to pipe");
674 ("run_job(): child: Could not write job pid to pipe");
679 debug("run_job(): child: grand-child pid written to pipe");
680 #endif /* CHECKRUNJOB */
682 if (!is_nolog(line->cl_option))
683 explain("Job %s started for user %s (pid %d)",
684 line->cl_shell, line->cl_file->cf_user, pid);
686 if (!to_stdout && is_mail(line->cl_option)) {
687 /* user wants a mail : we use the pipe */
688 char mailbuf[TERM_LEN];
689 FILE *pipef = fdopen(pipe_fd[0], "r");
692 die_e("Could not fdopen() pipe_fd[0]");
694 mailbuf[sizeof(mailbuf) - 1] = '\0';
695 while (fgets(mailbuf, sizeof(mailbuf), pipef) != NULL)
696 if (fputs(mailbuf, mailf) < 0)
697 warn("fputs() failed to write to mail file for job %s (pid %d)", line->cl_shell, pid);
698 /* (closes also pipe_fd[0]): */
699 if (fclose(pipef) != 0)
700 error_e("child: Could not fclose(pipef)");
703 /* FIXME : FOLLOWING HACK USELESS ? */
705 * this is a try to fix the bug on sorcerer linux (no jobs
706 * exectued at all, and
707 * "Could not read job pid : setting it to -1: No child processes"
709 /* use a select() or similar to know when parent has read
710 * the pid (with a timeout !) */
715 debug("run_job(): child: closing pipe with parent");
716 #endif /* CHECKRUNJOB */
717 if (close(pipe_pid_fd[1]) < 0)
718 error_e("child: could not close(pipe_pid_fd[1])");
720 /* we use a while because of a possible interruption by a signal */
721 while ((pid = wait3(&status, 0, NULL)) > 0) {
723 debug("run_job(): child: ending job pid %d", pid);
724 #endif /* CHECKRUNJOB */
725 end_job(line, status, mailf, mailpos, sendmailenv);
728 /* execution never gets here */
732 /* execution should never gets here, but if it happened we exit with an error */
739 /* close unneeded WRITE fd */
740 if (close(pipe_pid_fd[1]) < 0)
741 error_e("parent: could not close(pipe_pid_fd[1])");
743 exeent->e_ctrl_pid = pid;
746 debug("run_job(): about to read grand-child pid...");
747 #endif /* CHECKRUNJOB */
749 /* read the pid of the job */
750 ret = read_pipe(pipe_pid_fd[0], &(exeent->e_job_pid), sizeof(pid_t));
753 error("Could not read job pid because of closed pipe:"
754 " setting it to -1");
757 error_e("Could not read job pid : setting it to -1");
760 exeent->e_job_pid = -1;
763 if (close(pipe_pid_fd[0]) < 0)
764 error_e("parent: could not close(pipe_pid_fd[0])");
768 ("run_job(): finished reading pid of the job -- end of run_job().");
769 #endif /* CHECKRUNJOB */
778 end_job(cl_t * line, int status, FILE * mailf, short mailpos,
780 /* if task have made some output, mail it to user */
786 if (mailf != NULL && (is_mailzerolength(line->cl_option)
787 || (is_mail(line->cl_option)
789 /* job wrote some output and we wan't it in any case: */
790 ((fseek(mailf, 0, SEEK_END) == 0
791 && ftell(mailf) > mailpos)
792 && !is_erroronlymail(line->cl_option))
794 /* or we want an email only if the job returned an error: */
796 && WEXITSTATUS(status) == 0)
801 /* an output exit : we will mail it */
807 m = (mail_output == 1) ? " (mailing output)" : "";
808 if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
809 if (!is_nolog(line->cl_option))
810 explain("Job %s completed%s", line->cl_shell, m);
812 else if (WIFEXITED(status)) {
813 warn("Job %s terminated (exit status: %d)%s",
814 line->cl_shell, WEXITSTATUS(status), m);
815 /* there was an error : in order to inform the user by mail, we need
816 * to add some data to mailf */
818 fprintf(mailf, "Job %s terminated (exit status: %d)%s",
819 line->cl_shell, WEXITSTATUS(status), m);
821 else if (WIFSIGNALED(status)) {
822 error("Job %s terminated due to signal %d%s",
823 line->cl_shell, WTERMSIG(status), m);
825 fprintf(mailf, "Job %s terminated due to signal %d%s",
826 line->cl_shell, WTERMSIG(status), m);
828 else { /* is this possible? */
829 error("Job %s terminated abnormally %s", line->cl_shell, m);
831 fprintf(mailf, "Job %s terminated abnormally %s", line->cl_shell,
836 /* we close the PAM session before running the mailer command :
837 * it avoids a fork(), and we use PAM anyway to control whether a user command
838 * should be run or not.
839 * We consider that the administrator can use a PAM compliant mailer to control
840 * whether a mail can be sent or not.
841 * It should be ok like that, otherwise contact me ... -tg */
843 /* Aiee! we may need to be root to do this properly under Linux. Let's
844 * hope we're more l33t than PAM and try it as non-root. If someone
845 * complains, I'll fix this :P -hmh */
846 pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT);
847 pam_end(pamh, pam_close_session(pamh, PAM_SILENT));
850 if (mail_output == 1) {
851 launch_mailer(line, mailf, sendmailenv);
853 die_e("Internal error: launch_mailer returned");
856 /* if mail is sent, execution doesn't get here : close /dev/null */
857 if (mailf != NULL && fclose(mailf) != 0)
858 die_e("Can't close file mailf");
865 launch_mailer(cl_t * line, FILE * mailf, char **sendmailenv)
866 /* mail the output of a job to user */
871 /* set stdin to the job's output */
873 /* fseek() should work, but it seems that it is not always the case
874 * (users have reported problems on gentoo and LFS).
875 * For those users, lseek() works, so I have decided to use both,
876 * as I am not sure that lseek(fileno(...)...) will work as expected
877 * on non linux systems. */
878 if (fseek(mailf, 0, SEEK_SET) != 0)
879 die_e("Can't fseek()");
880 if (lseek(fileno(mailf), 0, SEEK_SET) != 0)
881 die_e("Can't lseek()");
882 if (dup2(fileno(mailf), 0) != 0)
883 die_e("Can't dup2(fileno(mailf))");
888 die_e("Could not chdir to /");
890 /* run sendmail with mail file as standard input */
892 debug("execle(%s, %s, %s, %s, NULL, sendmailenv)", sendmail, sendmail,
893 SENDMAIL_ARGS, line->cl_mailto);
895 execle(sendmail, sendmail, SENDMAIL_ARGS, line->cl_mailto, NULL,
897 die_e("Couldn't exec '%s'", sendmail);
898 #else /* defined(USE_SENDMAIL) */