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 /"); } }
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 list_file(char *file) { FILE *f = NULL; int c; explain("listing %s's fcrontab", user); if ( (f = fopen(file, "r")) == NULL ) { if ( errno == ENOENT ) { explain("user %s has no fcrontab.", user); return ; } else die_e("User %s could not read file \"%s\"", user, file); } else { while ( (c = getc(f)) != EOF ) putchar(c); fclose(f); } }
void die_mail_pame(cl_t * cl, int pamerrno, struct passwd *pas, char *str, env_list_t * env) /* log an error in syslog, mail user if necessary, and die */ { char buf[MAX_MSG]; snprintf(buf, sizeof(buf), "%s for user '%s'", str, pas->pw_name); if (is_mail(cl->cl_option)) { char **envp = env_list_export_envp(env); FILE *mailf = create_mail(cl, "Could not run fcron job", NULL, NULL, envp); /* print the error in both syslog and a file, in order to mail it to user */ if (dup2(fileno(mailf), 1) != 1 || dup2(1, 2) != 2) die_e("dup2() error"); /* dup2 also clears close-on-exec flag */ foreground = 1; error_pame(pamh, pamerrno, buf, cl->cl_shell); error("Job '%s' has *not* run.", cl->cl_shell); foreground = 0; pam_end(pamh, pamerrno); become_user(cl, pas, "/"); launch_mailer(cl, mailf, envp); /* launch_mailer() does not return : we never get here */ } else die_pame(pamh, pamerrno, buf, cl->cl_shell); }
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 main(int argc, char **argv) { struct passwd *pass = NULL; char *cur_user = NULL; rootuid = get_user_uid_safe(ROOTNAME); rootgid = get_group_gid_safe(ROOTGROUP); if (strrchr(argv[0], '/') == NULL) prog_name = argv[0]; else prog_name = strrchr(argv[0], '/') + 1; fcrontab_uid = get_user_uid_safe(USERNAME); #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 */ if (argc == 2) fcronconf = argv[1]; else if (argc > 2) usage(); /* read fcron.conf and update global parameters */ /* We deactivate output to console, because otherwise it may be used * by a malicious user to read some data it is not allow to read * (fcronsighup is suid root) */ foreground = 0; read_conf(); foreground = 1; uid = getuid(); /* check if user is allowed to use this program */ if (!(pass = getpwuid(uid))) die("user \"%s\" is not in passwd file. Aborting.", USERNAME); cur_user = strdup2(pass->pw_name); if (is_allowed(cur_user)) { /* check if daemon is running */ if ((daemon_pid = read_pid()) != 0) sig_daemon(); else fprintf(stderr, "fcron is not running :\n modifications will" " be taken into account at its next execution.\n"); } else die("User \"%s\" is not allowed to use %s. Aborting.", cur_user, prog_name); if (cur_user) free(cur_user); return EXIT_OK; }
int connect_fcron(void) /* connect to fcron through a socket, and identify user */ { int fd = -1; struct sockaddr_un addr; int len = 0; int sun_len = 0; if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) die_e("could not create socket"); addr.sun_family = AF_UNIX; len = strlen(fifofile); if (len > sizeof(addr.sun_path) - 1) die("Error : fifo file path too long (max is %d)", sizeof(addr.sun_path) - 1); /* length(fifofile) < sizeof(add.sun_path), so strncpy will terminate * the string with at least one \0 (not necessarily required by the OS, * but still a good idea) */ strncpy(addr.sun_path, fifofile, sizeof(addr.sun_path)); addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; sun_len = (addr.sun_path - (char *)&addr) + len; #if HAVE_SA_LEN addr.sun_len = sun_len; #endif if (connect(fd, (struct sockaddr *)&addr, sun_len) < 0) die_e("Cannot connect() to fcron (check if fcron is running)"); /* Nothing to do on the client side if we use SO_PASSCRED */ #if !defined(SO_PASSCRED) && !defined(HAVE_GETPEERUCRED) && !defined(HAVE_GETPEEREID) if (authenticate_user_password(fd) == ERR) { fprintf(stderr, "Invalid password or too many authentication failures" " (try to connect later).\n(In the later case, fcron rejects all" " new authentication during %d secs)\n", AUTH_WAIT); die("Unable to authenticate user"); } #endif /* SO_PASSCRED HAVE_GETPEERUCRED HAVE_GETPEEREID */ return fd; }
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 create_spooldir(char *dir) /* create a new spool dir for fcron : set correctly its mode and owner */ { int dir_fd = -1; struct stat st; uid_t useruid = get_user_uid_safe(USERNAME); gid_t usergid = get_group_gid_safe(GROUPNAME); if (mkdir(dir, 0) != 0 && errno != EEXIST) die_e("Cannot create dir %s", dir); if ((dir_fd = open(dir, 0)) < 0) die_e("Cannot open dir %s", dir); if (fstat(dir_fd, &st) != 0) { xclose_check(&dir_fd, "spooldir"); die_e("Cannot fstat %s", dir); } if (!S_ISDIR(st.st_mode)) { xclose_check(&dir_fd, "spooldir"); die("%s exists and is not a directory", dir); } if (fchown(dir_fd, useruid, usergid) != 0) { xclose_check(&dir_fd, "spooldir"); die_e("Cannot fchown dir %s to %s:%s", dir, USERNAME, GROUPNAME); } if (fchmod (dir_fd, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP) != 0) { xclose_check(&dir_fd, "spooldir"); die_e("Cannot change dir %s's mode to 770", dir); } xclose_check(&dir_fd, "spooldir"); exit(EXIT_OK); }
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. */ }
static char *get_socket() { Atom a; char *s; dpy=XOpenDisplay(NULL); if(dpy==NULL) die_e("Unable to open display."); a=XInternAtom(dpy, "_NOTION_MOD_NOTIONFLUX_SOCKET", True); if(a==None) die_e("Missing atom. Notion not running?"); s=xwindow_get_string_property(DefaultRootWindow(dpy), a, NULL); XCloseDisplay(dpy); return s; }
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; }
static void mywrite(int fd, const char *buf, int n) { while(n>0){ int k; k=write(fd, buf, n); if(k<0 && (errno!=EAGAIN && errno!=EINTR)) die_e("Writing"); if(k>0){ n-=k; buf+=k; } } }
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 }
void xexit(int exit_val) /* launch signal if needed and exit */ { pid_t pid = 0; if ( need_sig == 1 ) { /* fork and exec fcronsighup */ switch ( pid = fork() ) { case 0: /* child */ execl(BINDIREX "/fcronsighup", BINDIREX "/fcronsighup", fcronconf, NULL); die_e("Could not exec " BINDIREX " fcronsighup"); break; case -1: die_e("Could not fork (fcron has not been signaled)"); break; default: /* parent */ waitpid(pid, NULL, 0); break; } } #ifdef HAVE_LIBPAM pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT); pam_end(pamh, pam_close_session(pamh, PAM_SILENT)); #endif exit(exit_val); }
static int myread(int fd, char *buf, int n) { int left=n; while(left>0){ int k; k=read(fd, buf, left); if(k<0 && (errno!=EAGAIN && errno!=EINTR)) die_e("Writing"); if(k==0) break; if(k>0){ left-=k; buf+=k; } } return n-left; }
void change_user_setup_env(struct cl_t *cl, char ***sendmailenv, char ***jobenv, char **curshell, char **curhome, char **content_type, char **encoding) /* call setup_user_and_env() and become_user(). * As a result, *curshell and *curhome will be allocated and should thus be freed * if curshell and curhome are not NULL. */ { struct passwd *pas; errno = 0; pas = getpwnam(cl->cl_runas); if (pas == NULL) die_e("failed to get passwd fields for user \"%s\"", cl->cl_runas); setup_user_and_env(cl, pas, sendmailenv, jobenv, curshell, curhome, content_type, encoding); become_user(cl, pas, (curhome != NULL) ? *curhome : "/"); }
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 end_job(cl_t * line, int status, FILE * mailf, short mailpos, char **sendmailenv) /* if task have made some output, mail it to user */ { char mail_output = 0; char *m = NULL; #ifdef USE_SENDMAIL if (mailf != NULL && (is_mailzerolength(line->cl_option) || (is_mail(line->cl_option) && ( /* job wrote some output and we wan't it in any case: */ ((fseek(mailf, 0, SEEK_END) == 0 && ftell(mailf) > mailpos) && !is_erroronlymail(line->cl_option)) || /* or we want an email only if the job returned an error: */ !(WIFEXITED(status) && WEXITSTATUS(status) == 0) ) ) ) ) { /* an output exit : we will mail it */ mail_output = 1; } /* or else there is no output to email -- mail_output is already set to 0 */ #endif /* USE_SENDMAIL */ m = (mail_output == 1) ? " (mailing output)" : ""; if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { if (!is_nolog(line->cl_option)) explain("Job '%s' completed%s", line->cl_shell, m); } else if (WIFEXITED(status)) { warn("Job '%s' terminated (exit status: %d)%s", line->cl_shell, WEXITSTATUS(status), m); /* there was an error : in order to inform the user by mail, we need * to add some data to mailf */ if (mailf != NULL) fprintf(mailf, "Job '%s' terminated (exit status: %d)%s", line->cl_shell, WEXITSTATUS(status), m); } else if (WIFSIGNALED(status)) { error("Job '%s' terminated due to signal %d%s", line->cl_shell, WTERMSIG(status), m); if (mailf != NULL) fprintf(mailf, "Job '%s' terminated due to signal %d%s", line->cl_shell, WTERMSIG(status), m); } else { /* is this possible? */ error("Job '%s' terminated abnormally %s", line->cl_shell, m); if (mailf != NULL) fprintf(mailf, "Job '%s' terminated abnormally %s", line->cl_shell, m); } #ifdef HAVE_LIBPAM /* we close the PAM session before running the mailer command : * it avoids a fork(), and we use PAM anyway to control whether a user command * should be run or not. * We consider that the administrator can use a PAM compliant mailer to control * whether a mail can be sent or not. * It should be ok like that, otherwise contact me ... -tg */ /* Aiee! we may need to be root to do this properly under Linux. Let's * hope we're more l33t than PAM and try it as non-root. If someone * complains, I'll fix this :P -hmh */ pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT); pam_end(pamh, pam_close_session(pamh, PAM_SILENT)); #endif if (mail_output == 1) { launch_mailer(line, mailf, sendmailenv); /* never reached */ die_e("Internal error: launch_mailer returned"); } /* if mail is sent, execution doesn't get here : close /dev/null */ xfclose_check(&mailf, "Can't close file mailf"); exit(0); }
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 main_loop() /* main loop - get the time to sleep until next job execution, * sleep, and then test all jobs and execute if needed. */ { time_t save; /* time remaining until next save */ time_t stime; /* time to sleep until next job * execution */ #ifdef HAVE_GETTIMEOFDAY struct timeval tv; /* we use usec field to get more precision */ #endif #ifdef FCRONDYN int retcode = 0; #endif debug("Entering main loop"); now = time(NULL); synchronize_dir(".", is_system_startup()); /* synchronize save with jobs execution */ save = now + save_time; if (serial_num > 0 || once) stime = first_sleep; else if ((stime = time_to_sleep(save)) < first_sleep) /* force first execution after first_sleep sec : execution of jobs * during system boot time is not what we want */ stime = first_sleep; debug("initial sleep time : %ld", stime); for (;;) { #ifdef HAVE_GETTIMEOFDAY #ifdef FCRONDYN gettimeofday(&tv, NULL); tv.tv_sec = (stime > 1) ? stime - 1 : 0; /* we set tv_usec to slightly more than necessary so as * we don't wake up too early, in which case we would * have to sleep again for some time */ tv.tv_usec = 1001000 - tv.tv_usec; /* On some systems (BSD, etc), tv_usec cannot be greater than 999999 */ if (tv.tv_usec > 999999) tv.tv_usec = 999999; /* note: read_set is set in socket.c */ if ((retcode = select(set_max_fd + 1, &read_set, NULL, NULL, &tv)) < 0 && errno != EINTR) die_e("select returned %d", errno); #else if (stime > 1) sleep(stime - 1); gettimeofday(&tv, NULL); /* we set tv_usec to slightly more than necessary to avoid * infinite loop */ usleep(1001000 - tv.tv_usec); #endif /* FCRONDYN */ #else sleep(stime); #endif /* HAVE_GETTIMEOFDAY */ now = time(NULL); check_signal(); debug("\n"); test_jobs(); while (serial_num > 0 && serial_running < serial_max_running) run_serial_job(); if (once) { explain("Running with option once : exiting ... "); xexit(EXIT_OK); } if (save <= now) { save = now + save_time; /* save all files */ save_file(NULL); } #ifdef FCRONDYN /* check if there's a new connection, a new command to answer, etc ... */ /* we do that *after* other checks, to avoid Denial Of Service attacks */ check_socket(retcode); #endif stime = check_lavg(save); debug("next sleep time : %ld", stime); check_signal(); } }
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; }
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) { int return_code = 0; int fd = (-1); /* fd == -1 means connection to fcron is not currently open */ struct passwd *pass = NULL; rootuid = get_user_uid_safe(ROOTNAME); rootgid = get_group_gid_safe(ROOTGROUP); if (strrchr(argv[0], '/') == NULL) prog_name = argv[0]; else prog_name = strrchr(argv[0], '/') + 1; user_uid = getuid(); user_gid = getgid(); if ((pass = getpwuid(user_uid)) == NULL) die("user \"%s\" is not in passwd file. Aborting.", USERNAME); user_str = strdup2(pass->pw_name); /* drop suid rights that we don't need, but keep the sgid rights * for now as we will need them for read_conf() and is_allowed() */ #ifdef USE_SETE_ID seteuid_safe(user_uid); #endif if (setuid(user_uid) < 0) die_e("could not setuid() to %d", user_uid); /* interpret command line options */ parseopt(argc, argv); /* read fcron.conf and update global parameters */ read_conf(); if (!is_allowed(user_str)) { die("User \"%s\" is not allowed to use %s. Aborting.", user_str, prog_name); } /* we don't need anymore special rights : drop remaining ones */ #ifdef USE_SETE_ID setegid_safe(user_gid); #endif if (setgid(user_gid) < 0) die_e("could not setgid() to %d", user_gid); /* check for broken pipes ... */ signal(SIGPIPE, sigpipe_handler); if (cmd_str == NULL) return_code = interactive_mode(fd); else return_code = talk_fcron(cmd_str, fd); xexit((return_code == OK) ? EXIT_OK : EXIT_ERR); /* never reached */ return EXIT_OK; }
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 */ }
int main(int argc, char *argv[]) { int sock; struct sockaddr_un serv; const char *sockname; int use_stdin=1; char res; int n; if(argc>1){ if(argc!=3 || strcmp(argv[1], "-e")!=0) die("Usage: notionflux [-e code]"); if(strlen(argv[2])>=MAX_DATA) die("Too much data."); use_stdin=0; } sockname=get_socket(); if(sockname==NULL) die("No socket."); if(strlen(sockname)>SOCK_MAX) die("Socket name too long."); sock=socket(AF_UNIX, SOCK_STREAM, 0); if(sock<0) die_e("Opening socket"); serv.sun_family=AF_UNIX; strcpy(serv.sun_path, sockname); if(connect(sock, (struct sockaddr*)&serv, sizeof(struct sockaddr_un))<0) die_e("Connecting socket"); if(!use_stdin){ mywrite(sock, argv[2], strlen(argv[2])+1); }else{ char c='\0'; while(1){ if(fgets(buf, MAX_DATA, stdin)==NULL) break; mywrite(sock, buf, strlen(buf)); } mywrite(sock, &c, 1); } n=myread(sock, &res, 1); if(n!=1 || (res!='E' && res!='S')) die("Invalid response"); while(1){ n=myread(sock, buf, MAX_DATA); if(n==0) break; if(res=='S') mywrite(1, buf, n); else /* res=='E' */ mywrite(2, buf, n); if(n<MAX_DATA) break; } return 0; }