static long MS_CALLBACK slg_ctrl(BIO *b, int cmd, long num, void *ptr) { switch (cmd) { case BIO_CTRL_SET: xcloselog(b); xopenlog(b, ptr, num); break; default: break; } return (0); }
void launch_mailer(cl_t * line, FILE * mailf, char **sendmailenv) /* mail the output of a job to user */ { #ifdef USE_SENDMAIL foreground = 0; /* set stdin to the job's output */ /* fseek() should work, but it seems that it is not always the case * (users have reported problems on gentoo and LFS). * For those users, lseek() works, so I have decided to use both, * as I am not sure that lseek(fileno(...)...) will work as expected * on non linux systems. */ if (fseek(mailf, 0, SEEK_SET) != 0) die_e("Can't fseek()"); if (lseek(fileno(mailf), 0, SEEK_SET) != 0) die_e("Can't lseek()"); if (dup2(fileno(mailf), 0) != 0) die_e("Can't dup2(fileno(mailf))"); xcloselog(); if (chdir("/") < 0) die_e("Could not chdir to /"); /* run sendmail with mail file as standard input */ /* // */ debug("execle(%s, %s, %s, %s, NULL, sendmailenv)", sendmail, sendmail, SENDMAIL_ARGS, line->cl_mailto); /* // */ execle(sendmail, sendmail, SENDMAIL_ARGS, line->cl_mailto, NULL, sendmailenv); die_e("Couldn't exec '%s'", sendmail); #else /* defined(USE_SENDMAIL) */ exit(EXIT_OK); #endif }
static int MS_CALLBACK slg_free(BIO *a) { if (a == NULL) return(0); xcloselog(a); return(1); }
int run_job(struct exe_t *exeent) /* fork(), redirect outputs to a temp file, and execl() the task. * Return ERR if it could not fork() the first time, OK otherwise. */ { pid_t pid; cl_t *line = exeent->e_line; int pipe_pid_fd[2]; int ret = 0; /* prepare the job execution */ if (pipe(pipe_pid_fd) != 0) { error_e("pipe(pipe_pid_fd) : setting job_pid to -1"); exeent->e_job_pid = -1; pipe_pid_fd[0] = pipe_pid_fd[1] = -1; } #ifdef CHECKRUNJOB debug ("run_job(): first pipe created successfully : about to do first fork()"); #endif /* CHECKRUNJOB */ switch (pid = fork()) { case -1: error_e("Fork error : could not exec '%s'", line->cl_shell); return ERR; break; case 0: /* child */ { struct passwd *pas = NULL; char **jobenv = NULL; char **sendmailenv = NULL; char *curshell = NULL; char *curhome = NULL; char *content_type = NULL; char *encoding = NULL; FILE *mailf = NULL; int status = 0; int to_stdout = foreground && is_stdout(line->cl_option); int pipe_fd[2]; short int mailpos = 0; /* 'empty mail file' size */ #ifdef WITH_SELINUX int flask_enabled = is_selinux_enabled(); #endif /* // */ debug("run_job(): child: %s, output to %s, %s, %s\n", is_mail(line->cl_option) ? "mail" : "no mail", to_stdout ? "stdout" : "file", foreground ? "running in foreground" : "running in background", is_stdout(line->cl_option) ? "stdout" : "normal"); /* // */ errno = 0; pas = getpwnam(line->cl_runas); if (pas == NULL) die_e("failed to get passwd fields for user \"%s\"", line->cl_runas); setup_user_and_env(line, pas, &sendmailenv, &jobenv, &curshell, &curhome, &content_type, &encoding); /* close unneeded READ fd */ xclose_check(&(pipe_pid_fd[0]), "child's pipe_pid_fd[0]"); pipe_fd[0] = pipe_fd[1] = -1; if (!to_stdout && is_mail(line->cl_option)) { /* we create the temp file (if needed) before change_user(), * as temp_file() needs root privileges */ /* if we run in foreground, stdout and stderr point to the console. * Otherwise, stdout and stderr point to /dev/null . */ mailf = create_mail(line, NULL, content_type, encoding, jobenv); mailpos = ftell(mailf); if (pipe(pipe_fd) != 0) die_e("could not pipe() (job not executed)"); } become_user(line, pas, curhome); Free_safe(curhome); /* restore umask to default */ umask(saved_umask); sig_dfl(); #ifdef CHECKRUNJOB debug ("run_job(): child: change_user() done -- about to do 2nd fork()"); #endif /* CHECKRUNJOB */ /* now, run the job */ switch (pid = fork()) { case -1: error_e("Fork error : could not exec '%s'", line->cl_shell); if (write(pipe_pid_fd[1], &pid, sizeof(pid)) < 0) error_e("could not write child pid to pipe_pid_fd[1]"); xclose_check(&(pipe_fd[0]), "child's pipe_fd[0]"); xclose_check(&(pipe_fd[1]), "child's pipe_fd[1]"); xclose_check(&(pipe_pid_fd[1]), "child's pipe_pid_fd[1]"); exit(EXIT_ERR); break; case 0: /* grand child (child of the 2nd fork) */ /* the grand child does not use this pipe: close remaining fd */ xclose_check(&(pipe_pid_fd[1]), "grand child's pipe_pid_fd[1]"); if (!to_stdout) /* note : the following closes the pipe */ run_job_grand_child_setup_stderr_stdout(line, pipe_fd); foreground = 1; /* now, errors will be mailed to the user (or to /dev/null) */ run_job_grand_child_setup_nice(line); xcloselog(); #if defined(CHECKJOBS) || defined(CHECKRUNJOB) /* this will force to mail a message containing at least the exact * and complete command executed for each execution of all jobs */ debug("run_job(): grand-child: Executing \"%s -c %s\"", curshell, line->cl_shell); #endif /* CHECKJOBS OR CHECKRUNJOB */ #ifdef WITH_SELINUX if (flask_enabled && setexeccon(line->cl_file->cf_user_context) < 0) die_e("Can't set execute context '%s' for user '%s'.", line->cl_file->cf_user_context, line->cl_runas); #else if (setsid() == -1) { die_e("setsid(): errno %d", errno); } #endif execle(curshell, curshell, "-c", line->cl_shell, NULL, jobenv); /* execle returns only on error */ die_e("Couldn't exec shell '%s'", curshell); /* execution never gets here */ default: /* child (parent of the 2nd fork) */ /* close unneeded WRITE pipe and READ pipe */ xclose_check(&(pipe_fd[1]), "child's pipe_fd[1]"); #ifdef CHECKRUNJOB debug("run_job(): child: pipe_fd[1] and pipe_pid_fd[0] closed" " -- about to write grand-child pid to pipe"); #endif /* CHECKRUNJOB */ /* give the pid of the child to the parent (main) fcron process */ ret = write_pipe(pipe_pid_fd[1], &pid, sizeof(pid)); if (ret != OK) { if (ret == ERR) error ("run_job(): child: Could not write job pid to pipe"); else { errno = ret; error_e ("run_job(): child: Could not write job pid to pipe"); } } #ifdef CHECKRUNJOB debug("run_job(): child: grand-child pid written to pipe"); #endif /* CHECKRUNJOB */ if (!is_nolog(line->cl_option)) explain("Job '%s' started for user %s (pid %d)", line->cl_shell, line->cl_file->cf_user, pid); if (!to_stdout && is_mail(line->cl_option)) { /* user wants a mail : we use the pipe */ char mailbuf[TERM_LEN]; FILE *pipef = fdopen(pipe_fd[0], "r"); if (pipef == NULL) die_e("Could not fdopen() pipe_fd[0]"); mailbuf[sizeof(mailbuf) - 1] = '\0'; while (fgets(mailbuf, sizeof(mailbuf), pipef) != NULL) if (fputs(mailbuf, mailf) < 0) warn("fputs() failed to write to mail file for job '%s' (pid %d)", line->cl_shell, pid); /* (closes also pipe_fd[0]): */ xfclose_check(&pipef, "child's pipef"); } /* FIXME : FOLLOWING HACK USELESS ? */ /* FIXME : HACK * this is a try to fix the bug on sorcerer linux (no jobs * exectued at all, and * "Could not read job pid : setting it to -1: No child processes" * error messages) */ /* use a select() or similar to know when parent has read * the pid (with a timeout !) */ /* // */ sleep(2); /* // */ #ifdef CHECKRUNJOB debug("run_job(): child: closing pipe with parent"); #endif /* CHECKRUNJOB */ xclose_check(&(pipe_pid_fd[1]), "child's pipe_pid_fd[1]"); /* we use a while because of a possible interruption by a signal */ while ((pid = wait3(&status, 0, NULL)) > 0) { #ifdef CHECKRUNJOB debug("run_job(): child: ending job pid %d", pid); #endif /* CHECKRUNJOB */ end_job(line, status, mailf, mailpos, sendmailenv); } /* execution never gets here */ } /* execution should never gets here, but if it happened we exit with an error */ exit(EXIT_ERR); } default: /* parent */ /* close unneeded WRITE fd */ xclose_check(&(pipe_pid_fd[1]), "parent's pipe_pid_fd[1]"); exeent->e_ctrl_pid = pid; #ifdef CHECKRUNJOB debug("run_job(): about to read grand-child pid..."); #endif /* CHECKRUNJOB */ /* read the pid of the job */ ret = read_pipe(pipe_pid_fd[0], &(exeent->e_job_pid), sizeof(pid_t)); if (ret != OK) { if (ret == ERR) { error("Could not read job pid because of closed pipe:" " setting it to -1"); } else { errno = ret; error_e("Could not read job pid : setting it to -1"); } exeent->e_job_pid = -1; } xclose_check(&(pipe_pid_fd[0]), "parent's pipe_pid_fd[0]"); #ifdef CHECKRUNJOB debug ("run_job(): finished reading pid of the job -- end of run_job()."); #endif /* CHECKRUNJOB */ } return OK; }
void setup_user_and_env(struct cl_t *cl, struct passwd *pas, char ***sendmailenv, char ***jobenv, char **curshell, char **curhome, char **content_type, char **encoding) /* Check PAM authorization, and setup the environment variables * to run sendmail and to run the job itself. Change dir to HOME and check if SHELL is ok */ /* (*curshell) and (*curhome) will be allocated and should thus be freed * if curshell and curhome are not NULL. */ /* Return the the two env var sets, the shell to use to execle() commands and the home dir */ { env_list_t *env_list = env_list_init(); env_t *e = NULL; char *path = NULL; char *myshell = NULL; #ifdef HAVE_LIBPAM int retcode = 0; char **env; #endif if (pas == NULL) die("setup_user_and_env() called with a NULL struct passwd"); env_list_setenv(env_list, "USER", pas->pw_name, 1); env_list_setenv(env_list, "LOGNAME", pas->pw_name, 1); env_list_setenv(env_list, "HOME", pas->pw_dir, 1); /* inherit fcron's PATH for sendmail. We will later change it to DEFAULT_JOB_PATH * or a user defined PATH for the job itself */ path = getenv("PATH"); env_list_setenv(env_list, "PATH", (path != NULL) ? path : DEFAULT_JOB_PATH, 1); if (cl->cl_tz != NULL) env_list_setenv(env_list, "TZ", cl->cl_tz, 1); /* To ensure compatibility with Vixie cron, we don't use the shell defined * in /etc/passwd by default, but the default value from fcron.conf instead: */ if (shell != NULL && shell[0] != '\0') /* default: use value from fcron.conf */ env_list_setenv(env_list, "SHELL", shell, 1); else /* shell is empty, ie. not defined: fail back to /etc/passwd's value */ env_list_setenv(env_list, "SHELL", pas->pw_shell, 1); #if ( ! defined(RUN_NON_PRIVILEGED)) && defined(HAVE_LIBPAM) /* Open PAM session for the user and obtain any security * credentials we might need */ retcode = pam_start("fcron", pas->pw_name, &apamconv, &pamh); if (retcode != PAM_SUCCESS) die_pame(pamh, retcode, "Could not start PAM for '%s'", cl->cl_shell); /* Some system seem to need that pam_authenticate() call. * Anyway, we have no way to authentificate the user : * we must set auth to pam_permit. */ retcode = pam_authenticate(pamh, PAM_SILENT); if (retcode != PAM_SUCCESS) die_mail_pame(cl, retcode, pas, "Could not authenticate PAM user", env_list); retcode = pam_acct_mgmt(pamh, PAM_SILENT); /* permitted access? */ if (retcode != PAM_SUCCESS) die_mail_pame(cl, retcode, pas, "Could not init PAM account management", env_list); retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED | PAM_SILENT); if (retcode != PAM_SUCCESS) die_mail_pame(cl, retcode, pas, "Could not set PAM credentials", env_list); retcode = pam_open_session(pamh, PAM_SILENT); if (retcode != PAM_SUCCESS) die_mail_pame(cl, retcode, pas, "Could not open PAM session", env_list); for (env = pam_getenvlist(pamh); env && *env; env++) { env_list_putenv(env_list, *env, 1); } /* Close the log here, because PAM calls openlog(3) and * our log messages could go to the wrong facility */ xcloselog(); #endif /* ( ! defined(RUN_NON_PRIVILEGED)) && defined(HAVE_LIBPAM) */ /* export the environment for sendmail before we apply user customization */ if (sendmailenv != NULL) *sendmailenv = env_list_export_envp(env_list); /* Now add user customizations to the environment to form jobenv */ if (jobenv != NULL) { /* Make sure we don't keep fcron daemon's PATH (which we used for sendmail) */ env_list_setenv(env_list, "PATH", DEFAULT_JOB_PATH, 1); for (e = env_list_first(cl->cl_file->cf_env_list); e != NULL; e = env_list_next(cl->cl_file->cf_env_list)) { env_list_putenv(env_list, e->e_envvar, 1); } /* make sure HOME is defined */ env_list_putenv(env_list, "HOME=/", 0); /* don't overwrite if already defined */ if (curhome != NULL) { (*curhome) = strdup2(env_list_getenv(env_list, "HOME")); } /* check that SHELL is valid */ myshell = env_list_getenv(env_list, "SHELL"); if (myshell == NULL || myshell[0] == '\0') { myshell = shell; } else if (access(myshell, X_OK) != 0) { if (errno == ENOENT) error("shell \"%s\" : no file or directory. SHELL set to %s", myshell, shell); else error_e("shell \"%s\" not valid : SHELL set to %s", myshell, shell); myshell = shell; } env_list_setenv(env_list, "SHELL", myshell, 1); if (curshell != NULL) *curshell = strdup2(myshell); *jobenv = env_list_export_envp(env_list); } if (content_type != NULL) { (*content_type) = strdup2(env_list_getenv(env_list, "CONTENT_TYPE")); } if (encoding != NULL) { (*encoding) = strdup2(env_list_getenv(env_list, "CONTENT_TRANSFER_ENCODING")); } env_list_destroy(env_list); }
int main(int argc, char **argv) { #ifdef HAVE_LIBPAM int retcode = 0; const char * const * env; #endif #ifdef USE_SETE_ID struct passwd *pass; #endif memset(buf, 0, sizeof(buf)); memset(file, 0, sizeof(file)); if (strrchr(argv[0],'/')==NULL) prog_name = argv[0]; else prog_name = strrchr(argv[0],'/')+1; uid = getuid(); /* get current dir */ if ( getcwd(orig_dir, sizeof(orig_dir)) == NULL ) die_e("getcwd"); /* interpret command line options */ parseopt(argc, argv); #ifdef USE_SETE_ID if ( ! (pass = getpwnam(USERNAME)) ) die("user \"%s\" is not in passwd file. Aborting.", USERNAME); fcrontab_uid = pass->pw_uid; fcrontab_gid = pass->pw_gid; #ifdef HAVE_LIBPAM /* Open PAM session for the user and obtain any security credentials we might need */ debug("username: %s", user); retcode = pam_start("fcrontab", user, &apamconv, &pamh); if (retcode != PAM_SUCCESS) die_pame(pamh, retcode, "Could not start PAM"); retcode = pam_authenticate(pamh, 0); /* is user really user? */ if (retcode != PAM_SUCCESS) die_pame(pamh, retcode, "Could not authenticate user using PAM (%d)", retcode); retcode = pam_acct_mgmt(pamh, 0); /* permitted access? */ if (retcode != PAM_SUCCESS) die_pame(pamh, retcode, "Could not init PAM account management (%d)", retcode); retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED); if (retcode != PAM_SUCCESS) die_pame(pamh, retcode, "Could not set PAM credentials"); retcode = pam_open_session(pamh, 0); if (retcode != PAM_SUCCESS) die_pame(pamh, retcode, "Could not open PAM session"); env = (const char * const *) pam_getenvlist(pamh); while (env && *env) { if (putenv((char*) *env)) die_e("Could not copy PAM environment"); env++; } /* Close the log here, because PAM calls openlog(3) and our log messages could go to the wrong facility */ xcloselog(); #endif /* USE_PAM */ if (uid != fcrontab_uid) if (seteuid(fcrontab_uid) != 0) die_e("Couldn't change euid to fcrontab_uid[%d]",fcrontab_uid); /* change directory */ if (chdir(fcrontabs) != 0) { error_e("Could not chdir to %s", fcrontabs); xexit (EXIT_ERR); } /* get user's permissions */ if (seteuid(uid) != 0) die_e("Could not change euid to %d", uid); if (setegid(fcrontab_gid) != 0) die_e("Could not change egid to " GROUPNAME "[%d]", fcrontab_gid); #else /* USE_SETE_ID */ if (setuid(ROOTUID) != 0 ) die_e("Could not change uid to ROOTUID"); if (setgid(ROOTGID) != 0) die_e("Could not change gid to ROOTGID"); /* change directory */ if (chdir(fcrontabs) != 0) { error_e("Could not chdir to %s", fcrontabs); xexit (EXIT_ERR); } #endif /* USE_SETE_ID */ /* this program is seteuid : we set default permission mode * to 640 for a normal user, 600 for root, for security reasons */ if ( asuid == ROOTUID ) umask(066); /* octal : '0' + number in octal notation */ else umask(026); snprintf(buf, sizeof(buf), "%s.orig", user); /* determine what action should be taken */ if ( file_opt ) { if ( strcmp(argv[file_opt], "-") == 0 ) xexit(install_stdin()); else { if ( *argv[file_opt] != '/' ) /* this is just the file name, not the path : complete it */ snprintf(file, sizeof(file), "%s/%s", orig_dir, argv[file_opt]); else { strncpy(file, argv[file_opt], sizeof(file) - 1); file[sizeof(file)-1] = '\0'; } if (make_file(file) == OK) xexit(EXIT_OK); else xexit(EXIT_ERR); } } /* remove user's entries */ if ( rm_opt == 1 ) { if ( remove_fcrontab(1) == ENOENT ) fprintf(stderr, "no fcrontab for %s\n", user); xexit (EXIT_OK); } /* list user's entries */ if ( list_opt == 1 ) { list_file(buf); xexit(EXIT_OK); } /* edit user's entries */ if ( edit_opt == 1 ) { edit_file(buf); xexit(EXIT_OK); } /* reinstall user's entries */ if ( reinstall_opt == 1 ) { reinstall(buf); xexit(EXIT_OK); } /* never reached */ return EXIT_OK; }
int main(int argc, char **argv) { char *codeset = NULL; rootuid = get_user_uid_safe(ROOTNAME); rootgid = get_group_gid_safe(ROOTGROUP); /* we set it to 022 in order to get a pidfile readable by fcrontab * (will be set to 066 later) */ saved_umask = umask(022); /* parse options */ if (strrchr(argv[0], '/') == NULL) prog_name = argv[0]; else prog_name = strrchr(argv[0], '/') + 1; { uid_t daemon_uid; if ((daemon_uid = getuid()) != rootuid) die("Fcron must be executed as root"); } /* we have to set daemon_pid before the fork because it's * used in die() and die_e() functions */ daemon_pid = getpid(); /* save the value of the TZ env variable (used for option timezone) */ orig_tz_envvar = strdup2(getenv("TZ")); parseopt(argc, argv); /* read fcron.conf and update global parameters */ read_conf(); /* initialize the logs before we become a daemon */ xopenlog(); /* change directory */ if (chdir(fcrontabs) != 0) die_e("Could not change dir to %s", fcrontabs); /* Get the default locale character set for the mail * "Content-Type: ...; charset=" header */ setlocale(LC_ALL, ""); /* set locale to system defaults or to * that specified by any LC_* env vars */ /* Except that "US-ASCII" is preferred to "ANSI_x3.4-1968" in MIME, * even though "ANSI_x3.4-1968" is the official charset name. */ if ((codeset = nl_langinfo(CODESET)) != 0L && strcmp(codeset, "ANSI_x3.4-1968") != 0) strncpy(default_mail_charset, codeset, sizeof(default_mail_charset)); else strcpy(default_mail_charset, "US-ASCII"); if (freopen("/dev/null", "r", stdin) == NULL) error_e("Could not open /dev/null as stdin"); if (foreground == 0) { /* close stdout and stderr. * close unused descriptors * optional detach from controlling terminal */ int fd; pid_t pid; switch (pid = fork()) { case -1: die_e("fork"); break; case 0: /* child */ break; default: /* parent */ /* printf("%s[%d] " VERSION_QUOTED " : started.\n", */ /* prog_name, pid); */ exit(0); } daemon_pid = getpid(); if ((fd = open("/dev/tty", O_RDWR)) >= 0) { #ifndef _HPUX_SOURCE ioctl(fd, TIOCNOTTY, 0); #endif xclose_check(&fd, "/dev/tty"); } if (freopen("/dev/null", "w", stdout) == NULL) error_e("Could not open /dev/null as stdout"); if (freopen("/dev/null", "w", stderr) == NULL) error_e("Could not open /dev/null as stderr"); /* close most other open fds */ xcloselog(); for (fd = 3; fd < 250; fd++) /* don't use xclose_check() as we do expect most of them to fail */ (void)close(fd); /* finally, create a new session */ if (setsid() == -1) error("Could not setsid()"); } /* check if another fcron daemon is running, create pid file and lock it */ get_lock(); /* this program belongs to root : we set default permission mode * to 600 for security reasons, but we reset them to the saved * umask just before we run a job */ umask(066); explain("%s[%d] " VERSION_QUOTED " started", prog_name, daemon_pid); #ifdef HAVE_SIGNAL signal(SIGTERM, sigterm_handler); signal(SIGHUP, sighup_handler); siginterrupt(SIGHUP, 0); signal(SIGCHLD, sigchild_handler); siginterrupt(SIGCHLD, 0); signal(SIGUSR1, sigusr1_handler); siginterrupt(SIGUSR1, 0); signal(SIGUSR2, sigusr2_handler); siginterrupt(SIGUSR2, 0); /* we don't want SIGPIPE to kill fcron, and don't need to handle it */ signal(SIGPIPE, SIG_IGN); #elif HAVE_SIGSET sigset(SIGTERM, sigterm_handler); sigset(SIGHUP, sighup_handler); sigset(SIGCHLD, sigchild_handler); sigset(SIGUSR1, sigusr1_handler); sigset(SIGUSR2, sigusr2_handler); sigset(SIGPIPE, SIG_IGN); #endif /* initialize job database */ next_id = 0; /* initialize exe_array */ exe_list = exe_list_init(); /* initialize serial_array */ serial_running = 0; serial_array_index = 0; serial_num = 0; serial_array_size = SERIAL_INITIAL_SIZE; serial_array = alloc_safe(serial_array_size * sizeof(cl_t *), "serial_array"); /* initialize lavg_array */ lavg_list = lavg_list_init(); lavg_list->max_entries = lavg_queue_max; lavg_serial_running = 0; #ifdef FCRONDYN /* initialize socket */ init_socket(); #endif /* initialize random number generator : * WARNING : easy to guess !!! */ /* we use the hostname and tv_usec in order to get different seeds * on two different machines starting fcron at the same moment */ { char hostname[50]; int i; unsigned int seed; #ifdef HAVE_GETTIMEOFDAY struct timeval tv; /* we use usec field to get more precision */ gettimeofday(&tv, NULL); seed = ((unsigned int)tv.tv_usec) ^ ((unsigned int)tv.tv_sec); #else seed = (unsigned int)time(NULL); #endif gethostname(hostname, sizeof(hostname)); for (i = 0; i < sizeof(hostname) - sizeof(seed); i += sizeof(seed)) seed ^= (unsigned int)*(hostname + i); srand(seed); } main_loop(); /* never reached */ return EXIT_OK; }