]> granicus.if.org Git - fcron/blob - job.c
Fixed bug in change_user_setup_env() which could make fcron crash when sending a...
[fcron] / job.c
1 /*
2  * FCRON - periodic command scheduler 
3  *
4  *  Copyright 2000-2012 Thibault Godouet <fcron@free.fr>
5  *
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.
10  *
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.
15  * 
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
19  * 
20  *  The GNU General Public License can also be found in the file
21  *  `LICENSE' that comes with the fcron source distribution.
22  */
23
24
25 #include "fcron.h"
26
27 #include "job.h"
28 #include "temp_file.h"
29
30 void sig_dfl(void);
31 void end_job(cl_t * line, int status, FILE * mailf, short mailpos,
32              char **sendmailenv);
33 void end_mailer(cl_t * line, int status);
34 #ifdef HAVE_LIBPAM
35 void die_mail_pame(cl_t * cl, int pamerrno, struct passwd *pas, char *str,
36                    env_list_t * env);
37 #endif
38 #define PIPE_READ 0
39 #define PIPE_WRITE 1
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);
44
45 #ifdef HAVE_LIBPAM
46 void
47 die_mail_pame(cl_t * cl, int pamerrno, struct passwd *pas, char *str,
48               env_list_t * env)
49 /* log an error in syslog, mail user if necessary, and die */
50 {
51     char buf[MAX_MSG];
52
53     snprintf(buf, sizeof(buf), "%s for user '%s'", str, pas->pw_name);
54
55     if (is_mail(cl->cl_option)) {
56         char **envp = env_list_export_envp(env);
57         FILE *mailf =
58             create_mail(cl, "Could not run fcron job", NULL, NULL, envp);
59
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 */
63
64         foreground = 1;
65         error_pame(pamh, pamerrno, buf, cl->cl_shell);
66         error("Job '%s' has *not* run.", cl->cl_shell);
67         foreground = 0;
68
69         pam_end(pamh, pamerrno);
70
71         become_user(cl, pas, "/");
72
73         launch_mailer(cl, mailf, envp);
74         /* launch_mailer() does not return : we never get here */
75     }
76     else
77         die_pame(pamh, pamerrno, buf, cl->cl_shell);
78 }
79 #endif
80
81 void
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. */
85 {
86
87 #ifndef RUN_NON_PRIVILEGED
88     if (pas == NULL)
89         die("become_user() called with a NULL struct passwd");
90
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);
94
95     if (setgid(pas->pw_gid) < 0)
96         die("setgid failed: %s %d", pas->pw_name, pas->pw_gid);
97
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 */
101
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 '/'.",
105                 home);
106         if (chdir("/") < 0)
107             die_e("Could not chdir to HOME dir /");
108     }
109
110 }
111
112 void
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 */
121 {
122     env_list_t *env_list = env_list_init();
123     env_t *e = NULL;
124     char *path = NULL;
125     char *myshell = NULL;
126 #ifdef HAVE_LIBPAM
127     int retcode = 0;
128     char **env;
129 #endif
130
131     if (pas == NULL)
132         die("setup_user_and_env() called with a NULL struct passwd");
133
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,
141                     1);
142
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);
150     else
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);
153
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 */
157
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",
175                       env_list);
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);
179
180     for (env = pam_getenvlist(pamh); env && *env; env++) {
181         env_list_putenv(env_list, *env, 1);
182     }
183
184     /* Close the log here, because PAM calls openlog(3) and
185      * our log messages could go to the wrong facility */
186     xcloselog();
187 #endif                          /* ( ! defined(RUN_NON_PRIVILEGED)) && defined(HAVE_LIBPAM) */
188
189     /* export the environment for sendmail before we apply user customization */
190     if (sendmailenv != NULL)
191         *sendmailenv = env_list_export_envp(env_list);
192
193     /* Now add user customizations to the environment to form jobenv */
194
195     if (jobenv != NULL) {
196
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);
199
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);
203         }
204
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"));
209         }
210
211         /* check that SHELL is valid */
212         myshell = env_list_getenv(env_list, "SHELL");
213         if (myshell == NULL || myshell[0] == '\0') {
214             myshell = shell;
215         }
216         else if (access(myshell, X_OK) != 0) {
217             if (errno == ENOENT)
218                 error("shell \"%s\" : no file or directory. SHELL set to %s",
219                       myshell, shell);
220             else
221                 error_e("shell \"%s\" not valid : SHELL set to %s", myshell,
222                         shell);
223
224             myshell = shell;
225         }
226         env_list_setenv(env_list, "SHELL", myshell, 1);
227         if (curshell != NULL)
228             *curshell = strdup2(myshell);
229
230         *jobenv = env_list_export_envp(env_list);
231
232     }
233
234     if (content_type != NULL) {
235         (*content_type) = strdup2(env_list_getenv(env_list, "CONTENT_TYPE"));
236     }
237     if (encoding != NULL) {
238         (*encoding) =
239             strdup2(env_list_getenv(env_list, "CONTENT_TRANSFER_ENCODING"));
240     }
241
242     env_list_destroy(env_list);
243
244 }
245
246 void
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. */
253 {
254     struct passwd *pas;
255
256     errno = 0;
257     pas = getpwnam(cl->cl_runas);
258     if (pas == NULL)
259         die_e("failed to get passwd fields for user \"%s\"", cl->cl_runas);
260
261     setup_user_and_env(cl, pas, sendmailenv, jobenv, curshell, curhome,
262                        content_type, encoding);
263
264     become_user(cl, pas, (curhome!=NULL)? *curhome : "/");
265 }
266
267 void
268 sig_dfl(void)
269     /* set signals handling to its default */
270 {
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);
277 }
278
279
280 FILE *
281 create_mail(cl_t * line, char *subject, char *content_type, char *encoding,
282             char **env)
283     /* create a temp file and write in it a mail header */
284 {
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;
291     int i = 0;
292
293     if (mailf == NULL)
294         die_e("Could not fdopen() mailfd");
295
296 #ifdef HAVE_GETHOSTNAME
297     if (gethostname(hostname, sizeof(hostname)) != 0) {
298         error_e("Could not get hostname");
299         hostname[0] = '\0';
300     }
301     else {
302         /* it is unspecified whether a truncated hostname is NUL-terminated */
303         hostname[USER_NAME_LEN - 1] = '\0';
304
305         /* check if mailto is a complete mail address */
306         add_hostname = (strchr(line->cl_mailto, '@') == NULL) ? 1 : 0;
307     }
308 #else                           /* HAVE_GETHOSTNAME */
309     hostname[0] = '\0';
310 #endif                          /* HAVE_GETHOSTNAME */
311
312     /* write mail header */
313     if (add_hostname)
314         fprintf(mailf, "To: %s@%s\n", line->cl_mailto, hostname);
315     else
316         fprintf(mailf, "To: %s\n", line->cl_mailto);
317
318     if (subject)
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);
322     else
323         fprintf(mailf, "Subject: fcron <%s@%s> %s\n", line->cl_file->cf_user,
324                 (hostname[0] != '\0') ? hostname : "?", line->cl_shell);
325
326     if (content_type == NULL) {
327         fprintf(mailf, "Content-Type: text/plain; charset=%s\n",
328                 default_mail_charset);
329     }
330     else {
331         /* user specified Content-Type header. */
332         char *c = NULL;
333
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++) {
337             if (*c == '\n')
338                 *c = ' ';
339         }
340         fprintf(mailf, "Content-Type: %s\n", content_type);
341     }
342
343     if (encoding != NULL) {
344         char *c = NULL;
345
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++) {
349             if (*c == '\n')
350                 *c = ' ';
351         }
352         fprintf(mailf, "Content-Transfer-Encoding: %s\n", encoding);
353     }
354
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! */
359
360     /* The Auto-Submitted header is
361      * defined (and suggested by) RFC3834. */
362     fprintf(mailf, "Auto-Submitted: auto-generated\n");
363
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 */
366     if (env != NULL) {
367         for (i = 0; env[i] != NULL; i++) {
368             fprintf(mailf, "X-Cron-Env: <%s>\n", env[i]);
369         }
370     }
371
372     /* Final line return to end the header section: */
373     fprintf(mailf, "\n");
374
375     return mailf;
376 }
377
378
379 int
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 */
386 {
387     int size_processed = 0;
388     int ret;
389     int num_retry = 0;
390
391     while (size_processed < size) {
392         errno = 0;
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);
397         else {
398             error("Invalid action parameter for function read_write_pipe():"
399                   " %d", action);
400             return ERR;
401         }
402         if (ret > 0)
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 */
408             continue;
409         else {
410             /* error */
411
412             if (ret == 0) {
413                 /* is it really an error when writing ? should we continue
414                  * in this case ? */
415                 if (num_retry < 3) {
416                     num_retry++;
417                     error_e
418                         ("read_write_pipe(): read/write returned 0: retrying... (size: %d, size_processed: %d, num_retry: %d)",
419                          size, size_processed, num_retry);
420                     sleep(1);
421                     continue;
422                 }
423                 else
424                     return ERR;
425             }
426             else
427                 return errno;
428         }
429     }
430
431     return OK;
432 }
433
434 int
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 */
440 {
441     return read_write_pipe(fd, buf, size, PIPE_READ);
442 }
443
444 int
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 */
450 {
451     return read_write_pipe(fd, buf, size, PIPE_WRITE);
452 }
453
454 void
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). */
459 {
460
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
475         setlinebuf(stdout);
476         setlinebuf(stderr);
477 #else
478         setvbuf(stdout, NULL, _IONBF, 0);
479         setvbuf(stderr, NULL, _IONBF, 0);
480 #endif
481     }
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");
487     }
488
489 }
490
491 void
492 run_job_grand_child_setup_nice(cl_t * line)
493     /* set the nice value for the job */
494 {
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");
499     }
500 }
501
502 int
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. */
506 {
507
508     pid_t pid;
509     cl_t *line = exeent->e_line;
510     int pipe_pid_fd[2];
511     int ret = 0;
512
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;
518     }
519
520 #ifdef CHECKRUNJOB
521     debug
522         ("run_job(): first pipe created successfully : about to do first fork()");
523 #endif                          /* CHECKRUNJOB */
524
525     switch (pid = fork()) {
526     case -1:
527         error_e("Fork error : could not exec \"%s\"", line->cl_shell);
528         return ERR;
529         break;
530
531     case 0:
532         /* child */
533         {
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;
541             FILE *mailf = NULL;
542             int status = 0;
543             int to_stdout = foreground && is_stdout(line->cl_option);
544             int pipe_fd[2];
545             short int mailpos = 0;      /* 'empty mail file' size */
546 #ifdef WITH_SELINUX
547             int flask_enabled = is_selinux_enabled();
548 #endif
549
550             /* // */
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");
557             /* // */
558
559             errno = 0;
560             pas = getpwnam(line->cl_runas);
561             if (pas == NULL)
562                 die_e("failed to get passwd fields for user \"%s\"",
563                       line->cl_runas);
564
565             setup_user_and_env(line, pas, &sendmailenv, &jobenv, &curshell,
566                                &curhome, &content_type, &encoding);
567
568             /* close unneeded READ fd */
569             if (close(pipe_pid_fd[0]) < 0)
570                 error_e("child: could not close(pipe_pid_fd[0])");
571
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)");
582             }
583
584             become_user(line, pas, curhome);
585             Free_safe(curhome);
586
587             /* restore umask to default */
588             umask(saved_umask);
589
590             sig_dfl();
591
592 #ifdef CHECKRUNJOB
593             debug
594                 ("run_job(): child: change_user() done -- about to do 2nd fork()");
595 #endif                          /* CHECKRUNJOB */
596
597             /* now, run the job */
598             switch (pid = fork()) {
599             case -1:
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])");
609                 exit(EXIT_ERR);
610                 break;
611
612             case 0:
613                 /* grand child (child of the 2nd fork) */
614
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])");
618
619                 if (!to_stdout)
620                     /* note : the following closes the pipe */
621                     run_job_grand_child_setup_stderr_stdout(line, pipe_fd);
622
623                 foreground = 1;
624                 /* now, errors will be mailed to the user (or to /dev/null) */
625
626                 run_job_grand_child_setup_nice(line);
627
628                 xcloselog();
629
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 */
636
637 #ifdef WITH_SELINUX
638                 if (flask_enabled
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);
642 #else
643                 if (setsid() == -1) {
644                     die_e("setsid(): errno %d", errno);
645                 }
646 #endif
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);
650
651                 /* execution never gets here */
652
653             default:
654                 /* child (parent of the 2nd fork) */
655
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])");
659
660 #ifdef CHECKRUNJOB
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 */
664
665                 /* give the pid of the child to the parent (main) fcron process */
666                 ret = write_pipe(pipe_pid_fd[1], &pid, sizeof(pid));
667                 if (ret != OK) {
668                     if (ret == ERR)
669                         error
670                             ("run_job(): child: Could not write job pid to pipe");
671                     else {
672                         errno = ret;
673                         error_e
674                             ("run_job(): child: Could not write job pid to pipe");
675                     }
676                 }
677
678 #ifdef CHECKRUNJOB
679                 debug("run_job(): child: grand-child pid written to pipe");
680 #endif                          /* CHECKRUNJOB */
681
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);
685
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");
690
691                     if (pipef == NULL)
692                         die_e("Could not fdopen() pipe_fd[0]");
693
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)");
701                 }
702
703                 /* FIXME : FOLLOWING HACK USELESS ? */
704                 /* FIXME : HACK
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"
708                  * error messages) */
709                 /* use a select() or similar to know when parent has read
710                  * the pid (with a timeout !) */
711                 /* // */
712                 sleep(2);
713                 /* // */
714 #ifdef CHECKRUNJOB
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])");
719
720                 /* we use a while because of a possible interruption by a signal */
721                 while ((pid = wait3(&status, 0, NULL)) > 0) {
722 #ifdef CHECKRUNJOB
723                     debug("run_job(): child: ending job pid %d", pid);
724 #endif                          /* CHECKRUNJOB */
725                     end_job(line, status, mailf, mailpos, sendmailenv);
726                 }
727
728                 /* execution never gets here */
729
730             }
731
732             /* execution should never gets here, but if it happened we exit with an error */
733             exit(EXIT_ERR);
734         }
735
736     default:
737         /* parent */
738
739         /* close unneeded WRITE fd */
740         if (close(pipe_pid_fd[1]) < 0)
741             error_e("parent: could not close(pipe_pid_fd[1])");
742
743         exeent->e_ctrl_pid = pid;
744
745 #ifdef CHECKRUNJOB
746         debug("run_job(): about to read grand-child pid...");
747 #endif                          /* CHECKRUNJOB */
748
749         /* read the pid of the job */
750         ret = read_pipe(pipe_pid_fd[0], &(exeent->e_job_pid), sizeof(pid_t));
751         if (ret != OK) {
752             if (ret == ERR)
753                 error("Could not read job pid because of closed pipe:"
754                       " setting it to -1");
755             else {
756                 errno = ret;
757                 error_e("Could not read job pid : setting it to -1");
758             }
759
760             exeent->e_job_pid = -1;
761             break;
762         }
763         if (close(pipe_pid_fd[0]) < 0)
764             error_e("parent: could not close(pipe_pid_fd[0])");
765
766 #ifdef CHECKRUNJOB
767         debug
768             ("run_job(): finished reading pid of the job -- end of run_job().");
769 #endif                          /* CHECKRUNJOB */
770
771     }
772
773     return OK;
774
775 }
776
777 void
778 end_job(cl_t * line, int status, FILE * mailf, short mailpos,
779         char **sendmailenv)
780     /* if task have made some output, mail it to user */
781 {
782
783     char mail_output;
784     char *m;
785
786     if (mailf != NULL && (is_mailzerolength(line->cl_option)
787                           || (is_mail(line->cl_option)
788                               && (
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))
793                                      ||
794                                      /* or we want an email only if the job returned an error: */
795                                      !(WIFEXITED(status)
796                                        && WEXITSTATUS(status) == 0)
797                               )
798                           )
799         )
800         )
801         /* an output exit : we will mail it */
802         mail_output = 1;
803     else
804         /* no output */
805         mail_output = 0;
806
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);
811     }
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 */
817         if (mailf != NULL)
818             fprintf(mailf, "Job %s terminated (exit status: %d)%s",
819                     line->cl_shell, WEXITSTATUS(status), m);
820     }
821     else if (WIFSIGNALED(status)) {
822         error("Job %s terminated due to signal %d%s",
823               line->cl_shell, WTERMSIG(status), m);
824         if (mailf != NULL)
825             fprintf(mailf, "Job %s terminated due to signal %d%s",
826                     line->cl_shell, WTERMSIG(status), m);
827     }
828     else {                      /* is this possible? */
829         error("Job %s terminated abnormally %s", line->cl_shell, m);
830         if (mailf != NULL)
831             fprintf(mailf, "Job %s terminated abnormally %s", line->cl_shell,
832                     m);
833     }
834
835 #ifdef HAVE_LIBPAM
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 */
842
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));
848 #endif
849
850     if (mail_output == 1) {
851         launch_mailer(line, mailf, sendmailenv);
852         /* never reached */
853         die_e("Internal error: launch_mailer returned");
854     }
855
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");
859
860     exit(0);
861
862 }
863
864 void
865 launch_mailer(cl_t * line, FILE * mailf, char **sendmailenv)
866     /* mail the output of a job to user */
867 {
868 #ifdef USE_SENDMAIL
869     foreground = 0;
870
871     /* set stdin to the job's output */
872
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))");
884
885     xcloselog();
886
887     if (chdir("/") < 0)
888         die_e("Could not chdir to /");
889
890     /* run sendmail with mail file as standard input */
891     /* // */
892     debug("execle(%s, %s, %s, %s, NULL, sendmailenv)", sendmail, sendmail,
893           SENDMAIL_ARGS, line->cl_mailto);
894     /* // */
895     execle(sendmail, sendmail, SENDMAIL_ARGS, line->cl_mailto, NULL,
896            sendmailenv);
897     die_e("Couldn't exec '%s'", sendmail);
898 #else                           /* defined(USE_SENDMAIL) */
899     exit(EXIT_OK);
900 #endif
901 }