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 xclose_check(&(pipe_fd[0]), "pipe_fd[0] in setup_stderr_stdout");
469 xclose_check(&(pipe_fd[1]), "pipe_fd[1] in setup_stderr_stdout");
470 /* Standard buffering results in unwanted behavior (some messages,
471 * at least error from fcron process itself, are lost) */
472 #ifdef HAVE_SETLINEBUF
476 setvbuf(stdout, NULL, _IONBF, 0);
477 setvbuf(stderr, NULL, _IONBF, 0);
480 else if (foreground) {
481 if (freopen("/dev/null", "w", stdout) == NULL)
482 error_e("could not freopen /dev/null as stdout");
483 if (freopen("/dev/null", "w", stderr) == NULL)
484 error_e("could not freopen /dev/null as stderr");
490 run_job_grand_child_setup_nice(cl_t * line)
491 /* set the nice value for the job */
493 if (line->cl_nice != 0) {
494 errno = 0; /* so that it works with any libc and kernel */
495 if (nice(line->cl_nice) == -1 && errno != 0)
496 error_e("could not set nice value");
501 run_job(struct exe_t *exeent)
502 /* fork(), redirect outputs to a temp file, and execl() the task.
503 * Return ERR if it could not fork() the first time, OK otherwise. */
507 cl_t *line = exeent->e_line;
511 /* prepare the job execution */
512 if (pipe(pipe_pid_fd) != 0) {
513 error_e("pipe(pipe_pid_fd) : setting job_pid to -1");
514 exeent->e_job_pid = -1;
515 pipe_pid_fd[0] = pipe_pid_fd[1] = -1;
520 ("run_job(): first pipe created successfully : about to do first fork()");
521 #endif /* CHECKRUNJOB */
523 switch (pid = fork()) {
525 error_e("Fork error : could not exec \"%s\"", line->cl_shell);
532 struct passwd *pas = NULL;
533 char **jobenv = NULL;
534 char **sendmailenv = NULL;
535 char *curshell = NULL;
536 char *curhome = NULL;
537 char *content_type = NULL;
538 char *encoding = NULL;
541 int to_stdout = foreground && is_stdout(line->cl_option);
543 short int mailpos = 0; /* 'empty mail file' size */
545 int flask_enabled = is_selinux_enabled();
549 debug("run_job(): child: %s, output to %s, %s, %s\n",
550 is_mail(line->cl_option) ? "mail" : "no mail",
551 to_stdout ? "stdout" : "file",
552 foreground ? "running in foreground" :
553 "running in background",
554 is_stdout(line->cl_option) ? "stdout" : "normal");
558 pas = getpwnam(line->cl_runas);
560 die_e("failed to get passwd fields for user \"%s\"",
563 setup_user_and_env(line, pas, &sendmailenv, &jobenv, &curshell,
564 &curhome, &content_type, &encoding);
566 /* close unneeded READ fd */
567 xclose_check(&(pipe_pid_fd[0]), "child's pipe_pid_fd[0]");
569 pipe_fd[0] = pipe_fd[1] = -1;
570 if (!to_stdout && is_mail(line->cl_option)) {
571 /* we create the temp file (if needed) before change_user(),
572 * as temp_file() needs root privileges */
573 /* if we run in foreground, stdout and stderr point to the console.
574 * Otherwise, stdout and stderr point to /dev/null . */
575 mailf = create_mail(line, NULL, content_type, encoding, jobenv);
576 mailpos = ftell(mailf);
577 if (pipe(pipe_fd) != 0)
578 die_e("could not pipe() (job not executed)");
581 become_user(line, pas, curhome);
584 /* restore umask to default */
591 ("run_job(): child: change_user() done -- about to do 2nd fork()");
592 #endif /* CHECKRUNJOB */
594 /* now, run the job */
595 switch (pid = fork()) {
597 error_e("Fork error : could not exec \"%s\"", line->cl_shell);
598 if (write(pipe_pid_fd[1], &pid, sizeof(pid)) < 0)
599 error_e("could not write child pid to pipe_pid_fd[1]");
600 xclose_check(&(pipe_fd[0]), "child's pipe_fd[0]");
601 xclose_check(&(pipe_fd[1]), "child's pipe_fd[1]");
602 xclose_check(&(pipe_pid_fd[1]), "child's pipe_pid_fd[1]");
607 /* grand child (child of the 2nd fork) */
609 /* the grand child does not use this pipe: close remaining fd */
610 xclose_check(&(pipe_pid_fd[1]), "grand child's pipe_pid_fd[1]");
613 /* note : the following closes the pipe */
614 run_job_grand_child_setup_stderr_stdout(line, pipe_fd);
617 /* now, errors will be mailed to the user (or to /dev/null) */
619 run_job_grand_child_setup_nice(line);
623 #if defined(CHECKJOBS) || defined(CHECKRUNJOB)
624 /* this will force to mail a message containing at least the exact
625 * and complete command executed for each execution of all jobs */
626 debug("run_job(): grand-child: Executing \"%s -c %s\"",
627 curshell, line->cl_shell);
628 #endif /* CHECKJOBS OR CHECKRUNJOB */
632 && setexeccon(line->cl_file->cf_user_context) < 0)
633 die_e("Can't set execute context '%s' for user '%s'.",
634 line->cl_file->cf_user_context, line->cl_runas);
636 if (setsid() == -1) {
637 die_e("setsid(): errno %d", errno);
640 execle(curshell, curshell, "-c", line->cl_shell, NULL, jobenv);
641 /* execle returns only on error */
642 die_e("Couldn't exec shell '%s'", curshell);
644 /* execution never gets here */
647 /* child (parent of the 2nd fork) */
649 /* close unneeded WRITE pipe and READ pipe */
650 xclose_check(&(pipe_fd[1]), "child's pipe_fd[1]");
653 debug("run_job(): child: pipe_fd[1] and pipe_pid_fd[0] closed"
654 " -- about to write grand-child pid to pipe");
655 #endif /* CHECKRUNJOB */
657 /* give the pid of the child to the parent (main) fcron process */
658 ret = write_pipe(pipe_pid_fd[1], &pid, sizeof(pid));
662 ("run_job(): child: Could not write job pid to pipe");
666 ("run_job(): child: Could not write job pid to pipe");
671 debug("run_job(): child: grand-child pid written to pipe");
672 #endif /* CHECKRUNJOB */
674 if (!is_nolog(line->cl_option))
675 explain("Job %s started for user %s (pid %d)",
676 line->cl_shell, line->cl_file->cf_user, pid);
678 if (!to_stdout && is_mail(line->cl_option)) {
679 /* user wants a mail : we use the pipe */
680 char mailbuf[TERM_LEN];
681 FILE *pipef = fdopen(pipe_fd[0], "r");
684 die_e("Could not fdopen() pipe_fd[0]");
686 mailbuf[sizeof(mailbuf) - 1] = '\0';
687 while (fgets(mailbuf, sizeof(mailbuf), pipef) != NULL)
688 if (fputs(mailbuf, mailf) < 0)
689 warn("fputs() failed to write to mail file for job %s (pid %d)", line->cl_shell, pid);
690 /* (closes also pipe_fd[0]): */
691 xfclose_check(&pipef, "child's pipef");
694 /* FIXME : FOLLOWING HACK USELESS ? */
696 * this is a try to fix the bug on sorcerer linux (no jobs
697 * exectued at all, and
698 * "Could not read job pid : setting it to -1: No child processes"
700 /* use a select() or similar to know when parent has read
701 * the pid (with a timeout !) */
706 debug("run_job(): child: closing pipe with parent");
707 #endif /* CHECKRUNJOB */
708 xclose_check(&(pipe_pid_fd[1]), "child's pipe_pid_fd[1]");
710 /* we use a while because of a possible interruption by a signal */
711 while ((pid = wait3(&status, 0, NULL)) > 0) {
713 debug("run_job(): child: ending job pid %d", pid);
714 #endif /* CHECKRUNJOB */
715 end_job(line, status, mailf, mailpos, sendmailenv);
718 /* execution never gets here */
722 /* execution should never gets here, but if it happened we exit with an error */
729 /* close unneeded WRITE fd */
730 xclose_check(&(pipe_pid_fd[1]), "parent's pipe_pid_fd[1]");
732 exeent->e_ctrl_pid = pid;
735 debug("run_job(): about to read grand-child pid...");
736 #endif /* CHECKRUNJOB */
738 /* read the pid of the job */
739 ret = read_pipe(pipe_pid_fd[0], &(exeent->e_job_pid), sizeof(pid_t));
742 error("Could not read job pid because of closed pipe:"
743 " setting it to -1");
746 error_e("Could not read job pid : setting it to -1");
749 exeent->e_job_pid = -1;
752 xclose_check(&(pipe_pid_fd[0]), "parent's pipe_pid_fd[0]");
756 ("run_job(): finished reading pid of the job -- end of run_job().");
757 #endif /* CHECKRUNJOB */
766 end_job(cl_t * line, int status, FILE * mailf, short mailpos,
768 /* if task have made some output, mail it to user */
774 if (mailf != NULL && (is_mailzerolength(line->cl_option)
775 || (is_mail(line->cl_option)
777 /* job wrote some output and we wan't it in any case: */
778 ((fseek(mailf, 0, SEEK_END) == 0
779 && ftell(mailf) > mailpos)
780 && !is_erroronlymail(line->cl_option))
782 /* or we want an email only if the job returned an error: */
784 && WEXITSTATUS(status) == 0)
789 /* an output exit : we will mail it */
795 m = (mail_output == 1) ? " (mailing output)" : "";
796 if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
797 if (!is_nolog(line->cl_option))
798 explain("Job %s completed%s", line->cl_shell, m);
800 else if (WIFEXITED(status)) {
801 warn("Job %s terminated (exit status: %d)%s",
802 line->cl_shell, WEXITSTATUS(status), m);
803 /* there was an error : in order to inform the user by mail, we need
804 * to add some data to mailf */
806 fprintf(mailf, "Job %s terminated (exit status: %d)%s",
807 line->cl_shell, WEXITSTATUS(status), m);
809 else if (WIFSIGNALED(status)) {
810 error("Job %s terminated due to signal %d%s",
811 line->cl_shell, WTERMSIG(status), m);
813 fprintf(mailf, "Job %s terminated due to signal %d%s",
814 line->cl_shell, WTERMSIG(status), m);
816 else { /* is this possible? */
817 error("Job %s terminated abnormally %s", line->cl_shell, m);
819 fprintf(mailf, "Job %s terminated abnormally %s", line->cl_shell,
824 /* we close the PAM session before running the mailer command :
825 * it avoids a fork(), and we use PAM anyway to control whether a user command
826 * should be run or not.
827 * We consider that the administrator can use a PAM compliant mailer to control
828 * whether a mail can be sent or not.
829 * It should be ok like that, otherwise contact me ... -tg */
831 /* Aiee! we may need to be root to do this properly under Linux. Let's
832 * hope we're more l33t than PAM and try it as non-root. If someone
833 * complains, I'll fix this :P -hmh */
834 pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT);
835 pam_end(pamh, pam_close_session(pamh, PAM_SILENT));
838 if (mail_output == 1) {
839 launch_mailer(line, mailf, sendmailenv);
841 die_e("Internal error: launch_mailer returned");
844 /* if mail is sent, execution doesn't get here : close /dev/null */
845 xfclose_check(&mailf, "Can't close file mailf");
852 launch_mailer(cl_t * line, FILE * mailf, char **sendmailenv)
853 /* mail the output of a job to user */
858 /* set stdin to the job's output */
860 /* fseek() should work, but it seems that it is not always the case
861 * (users have reported problems on gentoo and LFS).
862 * For those users, lseek() works, so I have decided to use both,
863 * as I am not sure that lseek(fileno(...)...) will work as expected
864 * on non linux systems. */
865 if (fseek(mailf, 0, SEEK_SET) != 0)
866 die_e("Can't fseek()");
867 if (lseek(fileno(mailf), 0, SEEK_SET) != 0)
868 die_e("Can't lseek()");
869 if (dup2(fileno(mailf), 0) != 0)
870 die_e("Can't dup2(fileno(mailf))");
875 die_e("Could not chdir to /");
877 /* run sendmail with mail file as standard input */
879 debug("execle(%s, %s, %s, %s, NULL, sendmailenv)", sendmail, sendmail,
880 SENDMAIL_ARGS, line->cl_mailto);
882 execle(sendmail, sendmail, SENDMAIL_ARGS, line->cl_mailto, NULL,
884 die_e("Couldn't exec '%s'", sendmail);
885 #else /* defined(USE_SENDMAIL) */