void run_job_grand_child_setup_stderr_stdout(cl_t * line, int *pipe_fd) /* setup stderr and stdout correctly so as the mail containing * the output of the job can be send at the end of the job. * Close the pipe (both ways). */ { if (is_mail(line->cl_option)) { /* we can't dup2 directly to mailfd, since a "cmd > /dev/stderr" in * a script would erase all previously collected message */ if (dup2(pipe_fd[1], 1) != 1 || dup2(1, 2) != 2) die_e("dup2() error"); /* dup2 also clears close-on-exec flag */ /* we close the pipe_fd[]s : the resources remain, and the pipe will * be effectively close when the job stops */ xclose_check(&(pipe_fd[0]), "pipe_fd[0] in setup_stderr_stdout"); xclose_check(&(pipe_fd[1]), "pipe_fd[1] in setup_stderr_stdout"); /* Standard buffering results in unwanted behavior (some messages, * at least error from fcron process itself, are lost) */ #ifdef HAVE_SETLINEBUF setlinebuf(stdout); setlinebuf(stderr); #else setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); #endif } else if (foreground) { if (freopen("/dev/null", "w", stdout) == NULL) error_e("could not freopen /dev/null as stdout"); if (freopen("/dev/null", "w", stderr) == NULL) error_e("could not freopen /dev/null as stderr"); } }
int remove_fcrontab(char rm_orig) /* remove user's fcrontab and tell daemon to update his conf */ /* note : the binary fcrontab is removed by fcron */ { int return_val = OK; int fd; if ( rm_orig ) explain("removing %s's fcrontab", user); /* remove source and formated file */ if ( (rm_orig && remove(buf)) != 0 ) { if ( errno == ENOENT ) return_val = ENOENT; else error_e("could not remove %s", buf); } #ifdef USE_SETE_ID if (seteuid(fcrontab_uid) != 0) error_e("seteuid(fcrontab_uid[%d])", fcrontab_uid); #endif /* try to remove the temp file in case he has not * been read by fcron daemon */ snprintf(buf, sizeof(buf), "new.%s", user); remove(buf); /* finally create a file in order to tell the daemon * a file was removed, and launch a signal to daemon */ snprintf(buf, sizeof(buf), "rm.%s", user); fd = open(buf, O_CREAT | O_TRUNC | O_EXCL, S_IRUSR | S_IWUSR); #ifdef USE_SETE_ID if (seteuid(uid) != 0) die_e("seteuid(uid[%d])", uid); #endif if ( fd == -1 ) { if ( errno != EEXIST ) error_e("Can't create file %s", buf); } else if ( asuid == ROOTUID && fchown(fd, ROOTUID, fcrontab_gid) != 0 ) error_e("Could not fchown %s to root", buf); close(fd); need_sig = 1; return return_val; }
int install_stdin(void) /* install what we get through stdin */ { int tmp_fd = 0; FILE *tmp_file = NULL; char *tmp_str = NULL; int c; short return_val = EXIT_OK; tmp_fd = temp_file(&tmp_str); if( (tmp_file = fdopen(tmp_fd, "w")) == NULL ) die_e("Could not fdopen file %s", tmp_str); while ( (c = getc(stdin)) != EOF ) putc(c, tmp_file); fclose(tmp_file); close(tmp_fd); if ( make_file(tmp_str) == ERR ) goto exiterr; else goto exit; exiterr: return_val = EXIT_ERR; exit: if ( remove(tmp_str) != 0 ) error_e("Could not remove %s", tmp_str); free(tmp_str); return return_val; }
void become_user(struct cl_t *cl, struct passwd *pas, char *home) /* Become the user who owns the job: change privileges, check PAM authorization, * and change dir to HOME. */ { #ifndef RUN_NON_PRIVILEGED if (pas == NULL) die("become_user() called with a NULL struct passwd"); /* Change running state to the user in question */ if (initgroups(pas->pw_name, pas->pw_gid) < 0) die_e("initgroups failed: %s", pas->pw_name); if (setgid(pas->pw_gid) < 0) die("setgid failed: %s %d", pas->pw_name, pas->pw_gid); if (setuid(pas->pw_uid) < 0) die("setuid failed: %s %d", pas->pw_name, pas->pw_uid); #endif /* not RUN_NON_PRIVILEGED */ /* make sure HOME is defined and change dir to it */ if (chdir(home) != 0) { error_e("Could not chdir to HOME dir '%s'. Trying to chdir to '/'.", home); if (chdir("/") < 0) die_e("Could not chdir to HOME dir /"); } }
void cmd_renice(struct fcrondyn_cl *client, long int *cmd, int fd, int exe_index, int is_root) /* change nice value of a running job */ { #ifdef HAVE_SETPRIORITY /* check if arguments are valid */ if ( exe_array[exe_index].e_job_pid <= 0 || ( (int)cmd[1] < 0 && ! is_root ) || (int)cmd[1] > 20 || (int)cmd[1] < -20 ) { warn("renice: invalid args : pid: %d nice_value: %d user: %s.", exe_array[exe_index].e_job_pid, (int)cmd[1], client->fcl_user); Send_err_msg_end(fd, err_invalid_args_str); } /* ok, now setpriority() the job */ if ( setpriority(PRIO_PROCESS, exe_array[exe_index].e_job_pid, (int)cmd[1]) != 0) { error_e("could not setpriority(PRIO_PROCESS, %d, %d)", exe_array[exe_index].e_job_pid, (int)cmd[1]); Send_err_msg_end(fd, err_unknown_str); } else Send_err_msg_end(fd, err_no_err_str); #else /* HAVE_SETPRIORITY */ warn("System has no setpriority() : cannot renice. pid: %d nice_value: %d user: %s.", exe_array[exe_index].e_job_pid, (int)cmd[1], client->fcl_user); Send_err_msg_end(fd, err_cmd_unknown_str); #endif /* HAVE_SETPRIORITY */ }
int getloadavg(double *result, int n) /* return the current load average as a floating point number, * the number of load averages read, or <0 for error */ { FILE *fp; int i; if (n > 3) n = 3; if ((fp = fopen(PROC "/loadavg", "r")) == NULL) { error_e("could not open '"PROC"/loadavg'" " (make sure procfs is mounted)"); i = -1; } else { for (i = 0; i < n; i++) { if (fscanf(fp, "%lf", result) != 1) goto end; result++; } } end: fclose(fp); return (i<0) ? i : i; }
void run_job_grand_child_setup_nice(cl_t * line) /* set the nice value for the job */ { if (line->cl_nice != 0) { errno = 0; /* so that it works with any libc and kernel */ if (nice(line->cl_nice) == -1 && errno != 0) error_e("could not set nice value"); } }
int read_write_pipe(int fd, void *buf, size_t size, int action) /* Read/write data from/to pipe. * action can either be PIPE_WRITE or PIPE_READ. * Handles signal interruptions, and read in several passes. * Returns ERR in case of a closed pipe, the errno from errno * for other errors, and OK if everything was read successfully */ { int size_processed = 0; int ret; int num_retry = 0; while (size_processed < size) { errno = 0; if (action == PIPE_READ) ret = read(fd, (char *)buf + size_processed, size); else if (action == PIPE_WRITE) ret = write(fd, (char *)buf + size_processed, size); else { error("Invalid action parameter for function read_write_pipe():" " %d", action); return ERR; } if (ret > 0) /* some data read correctly -- we still may need * one or several calls of read() to read the rest */ size_processed += ret; else if (ret < 0 && errno == EINTR) /* interrupted by a signal : let's try again */ continue; else { /* error */ if (ret == 0) { /* is it really an error when writing ? should we continue * in this case ? */ if (num_retry < 3) { num_retry++; error_e ("read_write_pipe(): read/write returned 0: retrying... (size: %d, size_processed: %d, num_retry: %d)", size, size_processed, num_retry); sleep(1); continue; } else return ERR; } else return errno; } } return OK; }
int save_one_file(cf_t *file, char *filename, uid_t own_uid, gid_t own_gid, time_t save_date) /* save a given file to disk */ { int fd; /* open file */ #ifdef CONFIG_FLASK if ( is_flask_enabled() ) fd = open_secure(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR, file->cf_file_sid); else #endif fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR|S_IWUSR); if ( fd == -1 ) { error_e("Could not open %s", filename); return ERR; } if (fchown(fd, own_uid, own_gid) != 0) { error_e("Could not fchown %s to uid:%d gid:%d", filename, own_uid, own_gid); close(fd); remove(filename); return ERR; } /* save file : */ if ( write_file_to_disk(fd, file, save_date) == ERR ) { close(fd); remove(filename); return ERR; } close(fd); return OK; }
int copy(char *orig, char *dest) /* copy orig file to dest */ { int from; int to_fd; int nb; char *copy_buf[LINE_LEN]; if ( (from = open(orig, O_RDONLY)) == -1) { error_e("copy: open(orig)"); return ERR; } /* create it as fcrontab_uid (to avoid problem if user's uid changed) * except for root. Root requires filesystem uid root for security * reasons */ #ifdef USE_SETE_ID if (asuid == ROOTUID) { if (seteuid(ROOTUID) != 0) error_e("seteuid(ROOTUID)"); } else { if (seteuid(fcrontab_uid) != 0) error_e("seteuid(fcrontab_uid[%d])", fcrontab_uid); } #endif to_fd = open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR|S_IWUSR|S_IRGRP); if (to_fd == -1) { error_e("copy: dest"); return ERR; } #ifdef USE_SETE_ID if (asuid != ROOTUID && seteuid(uid) != 0) die_e("seteuid(uid[%d])", uid); #endif if (asuid == ROOTUID ) { if ( fchmod(to_fd, S_IWUSR | S_IRUSR) != 0 ) error_e("Could not fchmod %s to 600", dest); if ( fchown(to_fd, ROOTUID, fcrontab_gid) != 0 ) error_e("Could not fchown %s to root", dest); } while ( (nb = read(from, copy_buf, sizeof(copy_buf))) != -1 && nb != 0 ) if ( write(to_fd, copy_buf, nb) != nb ) { error("Error while copying file. Aborting.\n"); close(from); close(to_fd); return ERR; } close(from); close(to_fd); return OK; }
void cmd_send_signal(struct fcrondyn_cl *client, long int *cmd, int fd, int exe_index) /* send a signal to a running job */ { if ( exe_array[exe_index].e_job_pid <= 0 || (int)cmd[1] <= 0 ) { warn("send_signal: invalid args : pid: %d signal: %d user: %s", exe_array[exe_index].e_job_pid, (int)cmd[1], client->fcl_user); Send_err_msg_end(fd, err_invalid_args_str); } /* ok, now kill() the job */ if ( kill(exe_array[exe_index].e_job_pid, (int)cmd[1]) != 0) { error_e("could not kill(%d, %d)", exe_array[exe_index].e_job_pid, (int)cmd[1]); Send_err_msg_end(fd, err_unknown_str); } else Send_err_msg_end(fd, err_no_err_str); }
void get_lock() /* check if another fcron daemon is running with the same config (-c option) : * in this case, die. if not, write our pid to /var/run/fcron.pid in order to lock, * and to permit fcrontab to read our pid and signal us */ { int otherpid = 0; FILE *daemon_lockfp = NULL; int fd; if (((fd = open(pidfile, O_RDWR | O_CREAT, 0644)) == -1) || ((daemon_lockfp = fdopen(fd, "r+"))) == NULL) die_e("can't open or create %s", pidfile); #ifdef HAVE_FLOCK if (flock(fd, LOCK_EX | LOCK_NB) != 0) #else /* HAVE_FLOCK */ if (lockf(fileno(daemon_lockfp), F_TLOCK, 0) != 0) #endif /* ! HAVE_FLOCK */ { if (fscanf(daemon_lockfp, "%d", &otherpid) >= 1) die_e("can't lock %s, running daemon's pid may be %d", pidfile, otherpid); else die_e("can't lock %s, and unable to read running" " daemon's pid", pidfile); } fcntl(fd, F_SETFD, 1); rewind(daemon_lockfp); fprintf(daemon_lockfp, "%d\n", (int)daemon_pid); fflush(daemon_lockfp); if (ftruncate(fileno(daemon_lockfp), ftell(daemon_lockfp)) < 0) error_e ("Unable to ftruncate(fileno(daemon_lockfp), ftell(daemon_lockfp))"); /* abandon fd and daemon_lockfp even though the file is open. we need to * keep it open and locked, but we don't need the handles elsewhere. */ }
int is_system_startup(void) { int reboot = 0; /* lock exist - skip reboot jobs */ if (access(REBOOT_LOCK, F_OK) == 0) { explain("@reboot jobs will only be run at computer's startup."); /* don't run @reboot jobs */ return 0; } /* lock doesn't exist - create lock, run reboot jobs */ if ((reboot = creat(REBOOT_LOCK, S_IRUSR & S_IWUSR)) < 0) error_e("Can't create lock for reboot jobs."); else xclose_check(&reboot, REBOOT_LOCK); /* run @reboot jobs */ return 1; }
int authenticate_user_password(int fd) /* authenticate user */ { char *password = NULL; char buf[USER_NAME_LEN + 16]; int len = 0; fd_set read_set; /* needed to use select to check if some data is waiting */ struct timeval tv; snprintf(buf, sizeof(buf), "password for %s :", user_str); if ((password = read_string(CONV_ECHO_OFF, buf)) == NULL) return ERR; len = snprintf(buf, sizeof(buf), "%s", user_str) + 1; len += snprintf(buf + len, sizeof(buf) - len, "%s", password) + 1; send(fd, buf, len, 0); Overwrite(buf); Overwrite(password); Free_safe(password); tv.tv_sec = MAX_WAIT_TIME; tv.tv_usec = 0; FD_ZERO(&read_set); FD_SET(fd, &read_set); if (select(fd + 1, &read_set, NULL, NULL, &tv) <= 0) { error_e("Couldn't get data from socket during %d seconds.", MAX_WAIT_TIME); return ERR; } while (recv(fd, buf, sizeof(buf), 0) < 0 && errno == EINTR) if (errno == EINTR && debug_opt) fprintf(stderr, "Got EINTR ..."); if (strncmp(buf, "1", sizeof("1")) != 0) return ERR; return OK; }
int write_buf_to_disk(int fd, char *write_buf, int *buf_used) /* write the buffer to disk */ { ssize_t to_write = *buf_used; ssize_t written = 0; ssize_t return_val; int num_retries = 0; while ( written < to_write ) { if ( num_retries++ > (int)(to_write / 2) ) { error("too many retries (%d) to write buf to disk : giving up.",num_retries); return ERR; } return_val = write(fd, (write_buf+written), to_write - written); if ( return_val == -1 ) { error_e("could not write() buf to disk"); return ERR; } written += return_val; } /* */ debug("write_buf_to_disk() : written %d/%d, %d (re)try(ies)", written, to_write, num_retries); /* */ if ( written == to_write ) { *buf_used = 0; return OK; } else { error("write_buf_to_disk() : written %d bytes for %d requested.", written, to_write); return ERR; } }
void print_fields(int fd, unsigned char *details) /* print a line describing the field types used in print_line() */ { char fields[TERM_LEN]; char field_user[] = " USER "; char field_id[] = "ID "; char field_rq[] = " R&Q "; char field_options[] = " OPTIONS "; char field_schedule[] = " SCHEDULE "; char field_until[] = " LAVG 1,5,15 UNTIL STRICT"; char field_pid[] = " PID "; char field_index[] = " INDEX"; char field_cmd[] = " CMD"; char field_endline[] = "\n"; int len = 0; fields[0] = '\0'; Add_field(field_id); Test_add_field(FIELD_USER, field_user); Test_add_field(FIELD_PID, field_pid); Test_add_field(FIELD_INDEX, field_index); Test_add_field(FIELD_RQ, field_rq); Test_add_field(FIELD_OPTIONS, field_options); Test_add_field(FIELD_LAVG, field_until); Test_add_field(FIELD_SCHEDULE, field_schedule); Add_field(field_cmd); Add_field(field_endline); fields[TERM_LEN-1] = '\0'; if ( send(fd, fields, (len < sizeof(fields)) ? len : sizeof(fields), 0) < 0 ) error_e("error in send()"); }
int save_file_safe(cf_t *file, char *final_path, char *prog_name, uid_t own_uid, gid_t own_gid, time_t save_date) /* save a file to a temp path, and then rename it (safely) to avoid loss of data * if a system crash, hardware failure, etc happens. */ { char temp_path[PATH_LEN+4]; int final_path_len, temp_path_index; char *tmp_str = ".tmp"; final_path_len = strlen(final_path); strncpy(temp_path, final_path, sizeof(temp_path)-sizeof(tmp_str)); temp_path_index = ( final_path_len > sizeof(temp_path)-sizeof(tmp_str) ) ? sizeof(temp_path)-sizeof(tmp_str) : final_path_len; strcpy(&temp_path[temp_path_index], ".tmp"); if ( save_one_file(file, temp_path, own_uid, own_gid, save_date) == OK ) { if ( rename(temp_path, final_path) != 0 ) { error_e("Cannot rename %s to %s", temp_path, final_path); error("%s will try to save the name to its definitive filename " "directly.", prog_name); error("If there is an error, root may consider to replace %s (which is " "a valid copy) by %s manually.", final_path, temp_path); if ( save_one_file(file, final_path, own_uid, own_gid, save_date) == ERR ) return ERR; } } else { error("Since %s has not been able to save %s's file, it will keep " "the previous version (if any) of %s.", prog_name, final_path, final_path); return ERR; } return 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; }
void print_line(int fd, struct cl_t *line, unsigned char *details, pid_t pid, int index, time_t until) /* print some basic fields of a line, and some more if details == 1 */ { char buf[TERM_LEN]; int len = 0; struct tm *ftime; len = snprintf(buf, sizeof(buf), "%-5ld", line->cl_id); if ( bit_test(details, FIELD_USER) ) len += snprintf(buf+len, sizeof(buf)-len, " %-6s", line->cl_file->cf_user); if ( bit_test(details, FIELD_PID) ) len += snprintf(buf+len, sizeof(buf)-len, " %-7d", pid); if ( bit_test(details, FIELD_INDEX) ) len += snprintf(buf+len, sizeof(buf)-len, " %-5d", index); if ( bit_test(details, FIELD_RQ) ) len += snprintf(buf+len, sizeof(buf)-len, " %-4d", line->cl_numexe); if ( bit_test(details, FIELD_OPTIONS) ) { char opt[9]; int i = 0; opt[0] = '\0'; if ( is_lavg(line->cl_option) ) i += snprintf(opt+i, sizeof(opt)-i, "L%.*s", (is_lavg_sev(line->cl_option)) ? 0:1, "O"); if ( is_serial(line->cl_option) ) i += snprintf(opt+i, sizeof(opt)-i, "%.*sS%.*s", i, ",", (is_serial_sev(line->cl_option)) ? 0:1, "O"); if ( is_exe_sev(line->cl_option) ) i += snprintf(opt+i, sizeof(opt)-i, "%.*sES", i, ","); len += snprintf(buf+len, sizeof(buf)-len, " %-9s", opt); } if ( bit_test(details, FIELD_LAVG) ) { len += snprintf(buf+len, sizeof(buf)-len, " %.1f,%.1f,%.1f", ((double)((line->cl_lavg)[0]))/10, ((double)((line->cl_lavg)[1]))/10, ((double)((line->cl_lavg)[2]))/10); if ( until > 0 ) { ftime = localtime( &until ); len += snprintf(buf+len, sizeof(buf)-len, " %02d/%02d/%d %02d:%02d %s", (ftime->tm_mon + 1), ftime->tm_mday, (ftime->tm_year + 1900), ftime->tm_hour, ftime->tm_min, (is_strict(line->cl_option)) ? "Y":"N"); } else len += snprintf(buf+len, sizeof(buf)-len, " %18s", " (no until set) "); } if ( bit_test(details, FIELD_SCHEDULE) ) { ftime = localtime( &(line->cl_nextexe) ); len += snprintf(buf+len, sizeof(buf)-len, " %02d/%02d/%d %02d:%02d", (ftime->tm_mon + 1), ftime->tm_mday, (ftime->tm_year + 1900), ftime->tm_hour, ftime->tm_min ); } len += snprintf(buf+len, sizeof(buf)-len, " %s\n", line->cl_shell); if ( send(fd, buf, (len < sizeof(buf)) ? len : sizeof(buf), 0) < 0 ) error_e("error in send()"); }
void auth_client(struct fcrondyn_cl *client) /* check client identity */ { char *pass_cry = NULL; char *pass_sys = NULL; char *pass_str = NULL; #ifdef HAVE_LIBSHADOW struct spwd *pass_sp = NULL; if ( (pass_sp = getspnam((char *) client->fcl_cmd )) == NULL ) { error_e("could not getspnam %s", (char *) client->fcl_cmd); send(client->fcl_sock_fd, "0", sizeof("0"), 0); return; } pass_sys = pass_sp->sp_pwdp; #else struct passwd *pass = NULL; if ( (pass = getpwnam((char *) client->fcl_cmd )) == NULL ) { error_e("could not getpwnam %s", (char *) client->fcl_cmd); send(client->fcl_sock_fd, "0", sizeof("0"), 0); return; } pass_sys = pass->pw_passwd; #endif /* */ debug("auth_client() : socket : %d", client->fcl_sock_fd); /* */ /* we need to limit auth failures : otherwise fcron may be used to "read" * shadow password !!! (or to crack it using a test-all-possible-password attack) */ if (auth_fail > 0 && auth_nofail_since + AUTH_WAIT <= now ) /* no auth time exceeded : set counter to 0 */ auth_fail = 0; if (auth_fail >= MAX_AUTH_FAIL) { error("Too many authentication failures : try to connect later."); send(client->fcl_sock_fd, "0", sizeof("0"), 0); auth_fail = auth_nofail_since = 0; return; } /* password is stored after user name */ pass_str = &( (char *)client->fcl_cmd ) [ strlen( (char*)client->fcl_cmd ) + 1 ]; if ( (pass_cry = crypt(pass_str, pass_sys)) == NULL ) { error_e("could not crypt()"); send(client->fcl_sock_fd, "0", sizeof("0"), 0); return; } /* debug("pass_sp->sp_pwdp : %s", pass_sp->sp_pwdp); */ /* debug("pass_cry : %s", pass_cry); */ if (strcmp(pass_cry, pass_sys) == 0) { client->fcl_user = strdup2( (char *) client->fcl_cmd ); send(client->fcl_sock_fd, "1", sizeof("1"), 0); } else { auth_fail++; auth_nofail_since = now; error("Invalid passwd for %s from socket %d", (char *) client->fcl_cmd, client->fcl_sock_fd); send(client->fcl_sock_fd, "0", sizeof("0"), 0); } }
void sig_daemon(void) /* send SIGHUP to daemon to tell him configuration has changed */ /* SIGHUP is sent once 10s before the next minute to avoid * some bad users to block daemon by sending it SIGHUP all the time */ { /* we don't need to make root wait */ if (uid != rootuid) { time_t t = 0; int sl = 0; FILE *fp = NULL; int fd = 0; struct tm *tm = NULL; char sigfile[PATH_LEN]; char buf[PATH_LEN]; sigfile[0] = '\0'; t = time(NULL); tm = localtime(&t); if ((sl = 60 - (t % 60) - 10) < 0) { if ((tm->tm_min = tm->tm_min + 2) >= 60) { tm->tm_hour++; tm->tm_min -= 60; } snprintf(buf, sizeof(buf), "%02d:%02d", tm->tm_hour, tm->tm_min); sl = 60 - (t % 60) + 50; } else { if (++tm->tm_min >= 60) { tm->tm_hour++; tm->tm_min -= 60; } snprintf(buf, sizeof(buf), "%02d:%02d", tm->tm_hour, tm->tm_min); } fprintf(stderr, "Modifications will be taken into account" " at %s.\n", buf); /* if fcrontabs is too long, snprintf will not be able to add "/fcrontab.sig" * string at the end of sigfile */ if (strlen(fcrontabs) > (sizeof(sigfile) - sizeof("/fcrontab.sig"))) die("fcrontabs string too long (more than %d characters)", (sizeof(sigfile) - sizeof("/fcrontab.sig"))); snprintf(sigfile, sizeof(sigfile), "%s/fcrontab.sig", fcrontabs); switch (fork()) { case -1: remove(sigfile); die_e("could not fork : daemon has not been signaled"); break; case 0: /* child */ break; default: /* parent */ return; } foreground = 0; /* try to create a lock file */ /* // */ debug("uid: %d, euid: %d, gid: %d, egid: %d", getuid(), geteuid(), getgid(), getegid()); /* // */ fd = open(sigfile, O_RDWR | O_CREAT, 0644); if (fd == -1) die_e("can't open or create %s", sigfile); fp = fdopen(fd, "r+"); if (fp == NULL) die_e("can't fdopen %s", sigfile); #ifdef HAVE_FLOCK if (flock(fd, LOCK_EX | LOCK_NB) != 0) { debug("fcrontab is already waiting for signalling the daemon :" " exiting."); return; } #else /* HAVE_FLOCK */ if (lockf(fd, F_TLOCK, 0) != 0) { debug("fcrontab is already waiting for signalling the daemon :" " exiting."); return; } #endif /* ! HAVE_FLOCK */ sleep(sl); /* also closes the underlying file descriptor fd: */ xfclose_check(&fp, sigfile); /* also reset fd, now closed by xfclose_check(), to make it clear it is closed */ fd = -1; if (remove(sigfile) < 0) error_e("Could not remove %s"); } else /* we are root */ fprintf(stderr, "Modifications will be taken into account" " right now.\n"); if ((daemon_pid = read_pid()) == 0) /* daemon is not running any longer : we exit */ return; foreground = 1; #ifdef USE_SETE_ID if (seteuid(rootuid) != 0) error_e("seteuid(rootuid)"); #endif /* USE_SETE_ID */ if (kill(daemon_pid, SIGHUP) != 0) die_e("could not send SIGHUP to daemon (pid %d)", daemon_pid); #ifdef USE_SETE_ID /* get user's permissions */ if (seteuid(fcrontab_uid) != 0) die_e("Could not change euid to " USERNAME "[%d]", uid); #endif /* USE_SETE_ID */ }
void edit_file(char *buf) /* copy file to a temp file, edit that file, and install it if necessary */ { char *cureditor = NULL; char editorcmd[PATH_LEN]; pid_t pid; int status; struct stat st; time_t mtime = 0; char *tmp_str; FILE *f, *fi; int file = 0; int c; char correction = 0; short return_val = EXIT_OK; explain("fcrontab : editing %s's fcrontab", user); if ((cureditor=getenv("VISUAL")) == NULL || strcmp(cureditor, "\0") == 0 ) if((cureditor=getenv("EDITOR"))==NULL || strcmp(cureditor, "\0") == 0 ) cureditor = editor; file = temp_file(&tmp_str); if ( (fi = fdopen(file, "w")) == NULL ) { error_e("could not fdopen"); goto exiterr; } #ifndef USE_SETE_ID if (fchown(file, asuid, asgid) != 0) { error_e("Could not fchown %s to asuid and asgid", tmp_str); goto exiterr; } #endif /* copy user's fcrontab (if any) to a temp file */ if ( (f = fopen(buf, "r")) == NULL ) { if ( errno != ENOENT ) { error_e("could not open file %s", buf); goto exiterr; } else fprintf(stderr, "no fcrontab for %s - using an empty one\n", user); } else { /* copy original file to temp file */ while ( (c=getc(f)) != EOF ) putc(c, fi); fclose(f); } fclose(fi); close(file); do { if ( stat(tmp_str, &st) == 0 ) mtime = st.st_mtime; else { error_e("could not stat \"%s\"", buf); goto exiterr; } switch ( pid = fork() ) { case 0: /* child */ if ( uid != ROOTUID ) { if (setgid(asgid) < 0) { error_e("setgid(asgid)"); goto exiterr; } if (setuid(asuid) < 0) { error_e("setuid(asuid)"); goto exiterr; } } else { /* Some programs, like perl, require gid=egid : */ if ( setgid(getgid()) < 0 ) { error_e("setgid(getgid())"); goto exiterr; } } snprintf(editorcmd, sizeof(editorcmd), "%s %s", cureditor, tmp_str); execlp(shell, shell, "-c", editorcmd, tmp_str, NULL); error_e("Error while running \"%s\"", cureditor); goto exiterr; case -1: error_e("fork"); goto exiterr; default: /* parent */ break ; } /* only reached by parent */ waitpid(pid, &status, 0); if ( ! WIFEXITED(status) ) { fprintf(stderr, "Editor exited abnormally:" " fcrontab is unchanged.\n"); goto exiterr; } #ifndef USE_SETE_ID /* we have chown the tmp file to user's name : user may have * linked the tmp file to a file owned by root. In that case, as * fcrontab is setuid root, user may read some informations he is not * allowed to read : * we *must* check that the tmp file is not a link. */ /* open the tmp file, chown it to root and chmod it to avoid race * conditions */ /* make sure that the tmp file is not a link */ { int fd = 0; if ( (fd = open(tmp_str, O_RDONLY)) <= 0 || fstat(fd, &st) != 0 || ! S_ISREG(st.st_mode) || S_ISLNK(st.st_mode) || st.st_uid != asuid || st.st_nlink > 1){ fprintf(stderr, "%s is not a valid regular file.\n", tmp_str); close(fd); goto exiterr; } if ( fchown(fd, ROOTUID, ROOTGID) != 0 || fchmod(fd, S_IRUSR|S_IWUSR) != 0 ){ fprintf(stderr, "Can't chown or chmod %s.\n", tmp_str); close(fd); goto exiterr; } close(fd); } #endif /* check if file has been modified */ if ( stat(tmp_str, &st) != 0 ) { error_e("could not stat %s", tmp_str); goto exiterr; } else if ( st.st_mtime > mtime || correction == 1) { correction = 0; switch ( read_file(tmp_str) ) { case ERR: goto exiterr; case 2: fprintf(stderr, "\nFile contains some errors. " "Ignore [i] or Correct [c] ? "); /* the 2nd getchar() is for the newline char (\n) */ while ( (c = getchar()) && c != 'i' && c != 'c' ) { fprintf(stderr, "Please press c to correct, " "or i to ignore: "); while (c != '\n') c = getchar(); } if ( c == 'c' ) { /* free memory used to store the list */ delete_file(user); correction = 1; } break; default: break; } } else { fprintf(stderr, "Fcrontab is unchanged :" " no need to install it.\n"); goto end; } } while ( correction == 1); if ( write_file(tmp_str) != OK ) return_val = EXIT_ERR; /* free memory used to store the list */ delete_file(user); /* tell daemon to update the conf */ need_sig = 1; end: if ( remove(tmp_str) != 0 ) error_e("could not remove %s", tmp_str); free(tmp_str); xexit (return_val); exiterr: if ( remove(tmp_str) != 0 ) error_e("could not remove %s", tmp_str); free(tmp_str); xexit (EXIT_ERR); }
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; }
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; }
FILE * create_mail(cl_t * line, char *subject, char *content_type, char *encoding, char **env) /* create a temp file and write in it a mail header */ { /* create temporary file for stdout and stderr of the job */ int mailfd = temp_file(NULL); FILE *mailf = fdopen(mailfd, "r+"); char hostname[USER_NAME_LEN]; /* is this a complete mail address ? (ie. with a "@", not only a username) */ char add_hostname = 0; int i = 0; if (mailf == NULL) die_e("Could not fdopen() mailfd"); #ifdef HAVE_GETHOSTNAME if (gethostname(hostname, sizeof(hostname)) != 0) { error_e("Could not get hostname"); hostname[0] = '\0'; } else { /* it is unspecified whether a truncated hostname is NUL-terminated */ hostname[USER_NAME_LEN - 1] = '\0'; /* check if mailto is a complete mail address */ add_hostname = (strchr(line->cl_mailto, '@') == NULL) ? 1 : 0; } #else /* HAVE_GETHOSTNAME */ hostname[0] = '\0'; #endif /* HAVE_GETHOSTNAME */ /* write mail header */ if (add_hostname) fprintf(mailf, "To: %s@%s\n", line->cl_mailto, hostname); else fprintf(mailf, "To: %s\n", line->cl_mailto); if (subject) fprintf(mailf, "Subject: fcron <%s@%s> %s: %s\n", line->cl_file->cf_user, (hostname[0] != '\0') ? hostname : "?", subject, line->cl_shell); else fprintf(mailf, "Subject: fcron <%s@%s> %s\n", line->cl_file->cf_user, (hostname[0] != '\0') ? hostname : "?", line->cl_shell); if (content_type == NULL) { fprintf(mailf, "Content-Type: text/plain; charset=%s\n", default_mail_charset); } else { /* user specified Content-Type header. */ char *c = NULL; /* Remove new-lines or users could specify arbitrary mail headers! * (fcrontab should already prevent that, but better safe than sorry) */ for (c = content_type; *c != '\0'; c++) { if (*c == '\n') *c = ' '; } fprintf(mailf, "Content-Type: %s\n", content_type); } if (encoding != NULL) { char *c = NULL; /* Remove new-lines or users could specify arbitrary mail headers! * (fcrontab should already prevent that, but better safe than sorry) */ for (c = encoding; *c != '\0'; c++) { if (*c == '\n') *c = ' '; } fprintf(mailf, "Content-Transfer-Encoding: %s\n", encoding); } /* Add headers so as automated systems can identify that this message * is an automated one sent by fcron. * That's useful for example for vacation auto-reply systems: no need * to send such an automated response to fcron! */ /* The Auto-Submitted header is * defined (and suggested by) RFC3834. */ fprintf(mailf, "Auto-Submitted: auto-generated\n"); /* See environ(7) and execle(3) to get documentation on environ: * it is an array of NULL-terminated strings, whose last entry is NULL */ if (env != NULL) { for (i = 0; env[i] != NULL; i++) { fprintf(mailf, "X-Cron-Env: <%s>\n", env[i]); } } /* Final line return to end the header section: */ fprintf(mailf, "\n"); return mailf; }
void check_socket(int num) /* check for new connection, command, connection closed */ { int fd = -1, avoid_fd = -1, addr_len = sizeof(struct sockaddr_un); struct sockaddr_un client_addr; long int buf_int[SOCKET_MSG_LEN]; int read_len = 0; struct fcrondyn_cl *client = NULL, *prev_client = NULL; if ( num <= 0 ) /* no socket to check : go directly to the end of that function */ goto final_settings; debug("Checking socket ..."); if ( FD_ISSET(listen_fd, &read_set) ) { debug("got new connection ..."); if ((fd = accept(listen_fd, (struct sockaddr *)&client_addr, &addr_len)) == -1) { error_e("could not accept new connection : isset(listen_fd = %d) = %d", listen_fd, FD_ISSET(listen_fd, &read_set)); } else { fcntl(fd, F_SETFD, 1); /* set fd to O_NONBLOCK : we do not want fcron to be stopped on error, etc */ if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) { error_e("Could not set fd attribute O_NONBLOCK : connection rejected."); shutdown(fd, SHUT_RDWR); close(fd); } else { Alloc(client, fcrondyn_cl); client->fcl_sock_fd = fd; /* means : not authenticated yet : */ client->fcl_user = NULL; client->fcl_cmd = NULL; /* include new entry in client list */ client->fcl_next = fcrondyn_cl_base; fcrondyn_cl_base = client; client->fcl_idle_since = now; /* to avoid trying to read from it in this call */ avoid_fd = fd; FD_SET(fd, &master_set); if ( fd > set_max_fd ) set_max_fd = fd; fcrondyn_cl_num += 1; debug("Added connection fd : %d - %d connections", fd, fcrondyn_cl_num); } } } client = fcrondyn_cl_base; while ( client != NULL ) { if (! FD_ISSET(client->fcl_sock_fd, &read_set) || client->fcl_sock_fd==avoid_fd){ /* check if the connection has not been idle for too long ... */ if (client->fcl_user==NULL && now - client->fcl_idle_since > MAX_AUTH_TIME ){ warn("Connection with no auth for more than %ds : closing it.", MAX_AUTH_TIME); remove_connection(&client, prev_client); } else if ( now - client->fcl_idle_since > MAX_IDLE_TIME ) { warn("Connection of %s is idle for more than %ds : closing it.", client->fcl_user, MAX_IDLE_TIME); remove_connection(&client, prev_client); } else { /* nothing to do on this one ... check the next one */ prev_client = client; client = client->fcl_next; } continue; } if ( (read_len = recv(client->fcl_sock_fd, buf_int, sizeof(buf_int), 0)) <= 0 ) { if (read_len == 0) { /* connection closed by client */ remove_connection(&client, prev_client); } else { error_e("error recv() from sock fd %d", client->fcl_sock_fd); prev_client = client; client = client->fcl_next; } } else { client->fcl_cmd_len = read_len; client->fcl_cmd = buf_int; if ( client->fcl_user == NULL ) /* not authenticated yet */ auth_client(client); else { /* we've just read a command ... */ client->fcl_idle_since = now; exe_cmd(client); } prev_client = client; client = client->fcl_next; } } final_settings: /* copy master_set in read_set, because read_set is modified by select() */ read_set = master_set; }
int talk_fcron(char *cmd_str, int fd) /* read a string command, check if it is valid and translate it, * send it to fcron, and print its answer */ { long int *cmd = NULL; int cmd_len = 0; char buf[LINE_LEN]; size_t read_len = 0; char existing_connection = (fd < 0) ? 0 : 1; fd_set read_set; /* needed to use select to check if some data is waiting */ struct timeval tv; switch (parse_cmd(cmd_str, &cmd, &cmd_len)) { case OK: break; case HELP_CMD: { int i, j, len; printf("Command recognized by fcrondyn :\n"); printf("------------------------------\n"); for (i = 0; i < cmd_list_len; i++) { len = printf("%s ", cmd_list[i].cmd_name); /* print args : */ for (j = 0; j < cmd_list[i].cmd_numopt; j++) { if (cmd_list[i].cmd_default[j] != ARG_REQUIRED) len += printf("["); switch (cmd_list[i].cmd_opt[j]) { case USER: len += printf("user"); break; case JOBID: len += printf("jobid"); break; case TIME_AND_DATE: len += printf("time"); break; case NICE_VALUE: len += printf("niceval"); break; case SIGNAL: len += printf("sig"); break; case BOOLEAN: len += printf("bool"); break; default: len += printf("unknown_arg!"); } if (cmd_list[i].cmd_default[j] != ARG_REQUIRED) len += printf("]"); len += printf(" "); } /* Align correctly the descriptions : */ printf("%*s%s", 24 - len, "", cmd_list[i].cmd_desc); /* print alias list (if any) */ if (cmd_list[i].cmd_alias[0] != NULL) { printf(" (aliases:"); for (j = 0; j < MAX_NUM_ALIAS && cmd_list[i].cmd_alias[j] != NULL; j++) { printf(" %s", cmd_list[i].cmd_alias[j]); } printf(")"); } printf("\n"); } } return HELP_CMD; case QUIT_CMD: return QUIT_CMD; case CMD_NOT_FOUND: return CMD_NOT_FOUND; case INVALID_ARG: return INVALID_ARG; case ZEROLEN_CMD: return ZEROLEN_CMD; default: return ERR; } /* This is a valid command (so we'll have to free() it) ... */ if (!existing_connection && (fd = connect_fcron()) == ERR) return ERR; send(fd, cmd, cmd_len * sizeof(long int), 0); Free_safe(cmd); cmd_len = 0; tv.tv_sec = MAX_WAIT_TIME; tv.tv_usec = 0; FD_ZERO(&read_set); FD_SET(fd, &read_set); if (select(fd + 1, &read_set, NULL, NULL, &tv) <= 0) { error_e("Couldn't get data from socket during %d seconds.", MAX_WAIT_TIME); return ERR; } while ((read_len = (size_t) recv(fd, buf, sizeof(buf) - 1, 0)) >= 0 || errno == EINTR) { if (errno == EINTR && debug_opt) fprintf(stderr, "got EINTR ...\n"); else if (read_len > sizeof(buf)) { /* weird ... no data yet ? */ if (debug_opt) fprintf(stderr, "no data yet ?"); } else if (read_len <= 0) { if (debug_opt) fprintf(stderr, "read_len = %d\n", (int)read_len); fprintf(stderr, "connection closed by fcron\n"); shutdown(fd, SHUT_RDWR); return ERR; } else { /* ensure the string is terminated by a '\0' for when we'll printf() it */ buf[read_len] = '\0'; printf("%s", buf); /* check for the end of command output marker */ if (read_len >= sizeof(END_STR) && strncmp(&buf[read_len - sizeof(END_STR)], END_STR, sizeof(END_STR)) == 0) break; } } if (read_len < 0) { error_e("error in recv()"); } if (!existing_connection) xclose_check(&fd, "unix socket"); return OK; }
void init_socket(void) /* do everything needed to get a working listening socket */ { struct sockaddr_un addr; int len = 0; /* used in fcron.c:main_loop():select() */ FD_ZERO(&read_set); FD_ZERO(&master_set); if ( (listen_fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1 ) { error_e("Could not create socket : fcrondyn won't work"); return; } addr.sun_family = AF_UNIX; if ( (len = strlen(fifofile)) > sizeof(addr.sun_path) ) { error("Error : fifo file path too long (max is %d)", sizeof(addr.sun_path)); goto err; } strncpy(addr.sun_path, fifofile, sizeof(addr.sun_path) - 1); addr.sun_path[sizeof(addr.sun_path) -1 ] = '\0'; unlink(fifofile); if (bind(listen_fd, (struct sockaddr *) &addr, sizeof(addr.sun_family)+len) != 0) { error_e("Cannot bind socket to '%s'", fifofile); goto err; } if ( listen(listen_fd, MAX_CONNECTION) != 0 ) { error_e("Cannot set socket in listen mode"); goto err; } /* */ if ( chmod(fifofile, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) != 0 ) error_e("Cannot chmod() socket file"); /* */ fcntl(listen_fd, F_SETFD, 1); /* set listen_fd to O_NONBLOCK : we do not want fcron to be stopped on error, etc */ if ( fcntl(listen_fd, F_SETFL, fcntl(listen_fd, F_GETFL) | O_NONBLOCK) == -1 ) { error_e("Could not set listen_fd attribute O_NONBLOCK : no fcrondyn support"); goto err; } /* no error */ FD_SET(listen_fd, &master_set); if ( listen_fd > set_max_fd ) set_max_fd = listen_fd; /* copy master in read_fs, because read_fs will be modified by select() */ read_set = master_set; debug("Socket initialized : listen_fd : %d set_max_fd : %d ", listen_fd, set_max_fd); return; err: close_socket(); }
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); }