| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| uid_t default_script_uid; |
| gid_t default_script_gid; |
| |
| |
| static bool default_script_uid_set = false; |
| static bool default_user_fail = false; |
| |
| |
| |
| bool script_security = false; |
| |
| |
| static size_t getpwnam_buf_len; |
| |
| static char *path; |
| static bool path_is_malloced; |
| |
| |
| static int cur_prio = INT_MAX; |
| |
| |
| static char cmd_str_buf[MAXBUF]; |
| |
| static bool |
| set_privileges(uid_t uid, gid_t gid) |
| { |
| int retval; |
| |
| |
| prctl(PR_SET_PDEATHSIG, SIGTERM); |
| |
| |
| if (cur_prio != INT_MAX) |
| cur_prio = getpriority(PRIO_PROCESS, 0); |
| if (cur_prio < 0) |
| setpriority(PRIO_PROCESS, 0, 0); |
| |
| |
| if (gid) { |
| retval = setgid(gid); |
| if (retval < 0) { |
| log_message(LOG_ALERT, "Couldn't setgid: %u (%m)", gid); |
| return true; |
| } |
| |
| |
| retval = setgroups(1, &gid); |
| if (retval < 0) { |
| log_message(LOG_ALERT, "Couldn't setgroups: %u (%m)", gid); |
| return true; |
| } |
| } |
| |
| if (uid) { |
| retval = setuid(uid); |
| if (retval < 0) { |
| log_message(LOG_ALERT, "Couldn't setuid: %u (%m)", uid); |
| return true; |
| } |
| } |
| |
| |
| signal_handler_script(); |
| set_std_fd(false); |
| |
| return false; |
| } |
| |
| const char * |
| cmd_str_r(const notify_script_t *script, char *buf, size_t len) |
| { |
| char *str_p; |
| int i; |
| size_t str_len; |
| |
| str_p = buf; |
| |
| for (i = 0; i < script->num_args; i++) { |
| |
| str_len = strlen(script->args[i]); |
| if (str_p + str_len + 2 + (i ? 1 : 0) >= buf + len) |
| return NULL; |
| |
| if (i) |
| *str_p++ = ' '; |
| |
| |
| |
| if (i || (script->args[i][0] != '<' && script->args[i][0] != '>')) |
| *str_p++ = '\''; |
| |
| strcpy(str_p, script->args[i]); |
| str_p += str_len; |
| |
| |
| if (i || (script->args[i][0] != '<' && script->args[i][0] != '>')) |
| *str_p++ = '\''; |
| } |
| *str_p = '\0'; |
| |
| return buf; |
| } |
| |
| const char * |
| cmd_str(const notify_script_t *script) |
| { |
| size_t len; |
| int i; |
| |
| for (i = 0, len = 0; i < script->num_args; i++) |
| len += strlen(script->args[i]) + 3; |
| |
| if (len > sizeof cmd_str_buf) |
| return NULL; |
| |
| return cmd_str_r(script, cmd_str_buf, sizeof cmd_str_buf); |
| } |
| |
| |
| static pid_t |
| notify_fifo_exec(thread_master_t *m, thread_func_t func, void *arg, notify_script_t *script) |
| { |
| pid_t pid; |
| int retval; |
| const char *scr; |
| union non_const_args args; |
| |
| pid = local_fork(); |
| |
| |
| if (pid < 0) { |
| log_message(LOG_INFO, "Failed fork process"); |
| return -1; |
| } |
| |
| |
| if (pid) { |
| thread_add_child(m, func, arg, pid, TIMER_NEVER); |
| return 0; |
| } |
| |
| |
| skip_mem_dump(); |
| |
| |
| setpgid(0, 0); |
| set_privileges(script->uid, script->gid); |
| |
| if (script->flags & SC_EXECABLE) { |
| |
| prctl(PR_SET_PDEATHSIG, SIGTERM); |
| |
| args.args = script->args; |
| execve(script->args[0], args.execve_args, environ); |
| |
| if (errno == EACCES) |
| log_message(LOG_INFO, "FIFO notify script %s is not executable", script->args[0]); |
| else |
| log_message(LOG_INFO, "Unable to execute FIFO notify script %s - errno %d - %m", script->args[0], errno); |
| } |
| else { |
| retval = system(scr = cmd_str(script)); |
| |
| if (retval == 127) { |
| |
| log_message(LOG_ALERT, "Couldn't exec FIFO command: %s", scr); |
| } |
| else if (retval == -1) |
| log_message(LOG_ALERT, "Error exec-ing FIFO command: %s", scr); |
| |
| exit(0); |
| } |
| |
| |
| exit(0); |
| } |
| |
| static void |
| fifo_open(notify_fifo_t* fifo, thread_func_t script_exit, const char *type) |
| { |
| int ret; |
| int sav_errno; |
| |
| if (fifo->name) { |
| sav_errno = 0; |
| |
| if (!(ret = mkfifo(fifo->name, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) { |
| fifo->created_fifo = true; |
| |
| if (chown(fifo->name, fifo->uid, fifo->gid)) |
| log_message(LOG_INFO, "Failed to set uid:gid for fifo %s", fifo->name); |
| } else { |
| sav_errno = errno; |
| |
| if (sav_errno != EEXIST) |
| log_message(LOG_INFO, "Unable to create %snotify fifo %s", type, fifo->name); |
| } |
| |
| if (!sav_errno || sav_errno == EEXIST) { |
| |
| if (fifo->script) |
| notify_fifo_exec(master, script_exit, fifo, fifo->script); |
| |
| |
| if ((fifo->fd = open(fifo->name, O_RDWR | O_CLOEXEC | O_NONBLOCK | O_NOFOLLOW)) == -1) { |
| log_message(LOG_INFO, "Unable to open %snotify fifo %s - errno %d", type, fifo->name, errno); |
| if (fifo->created_fifo) { |
| unlink(fifo->name); |
| fifo->created_fifo = false; |
| } |
| } |
| } |
| |
| if (fifo->fd == -1) { |
| FREE_CONST(fifo->name); |
| fifo->name = NULL; |
| } |
| } |
| } |
| |
| void |
| notify_fifo_open(notify_fifo_t* global_fifo, notify_fifo_t* fifo, thread_func_t script_exit, const char *type) |
| { |
| |
| if (global_fifo->name) |
| fifo_open(global_fifo, script_exit, ""); |
| |
| |
| if (fifo->name) |
| fifo_open(fifo, script_exit, type); |
| } |
| |
| static void |
| fifo_close(notify_fifo_t* fifo) |
| { |
| if (fifo->fd != -1) { |
| close(fifo->fd); |
| fifo->fd = -1; |
| } |
| if (fifo->created_fifo) |
| unlink(fifo->name); |
| } |
| |
| void |
| notify_fifo_close(notify_fifo_t* global_fifo, notify_fifo_t* fifo) |
| { |
| if (global_fifo->fd != -1) |
| fifo_close(global_fifo); |
| |
| fifo_close(fifo); |
| } |
| |
| |
| static void __attribute__ ((noreturn)) |
| system_call(const notify_script_t* script) |
| { |
| char *command_line = NULL; |
| const char *str; |
| int retval; |
| union non_const_args args; |
| |
| if (set_privileges(script->uid, script->gid)) |
| exit(0); |
| |
| |
| |
| setpgid(0, 0); |
| |
| if (script->flags & SC_EXECABLE) { |
| |
| prctl(PR_SET_PDEATHSIG, SIGTERM); |
| |
| args.args = script->args; |
| execve(script->args[0], args.execve_args, environ); |
| |
| |
| log_message(LOG_ALERT, "Error exec-ing command '%s', error %d: %m", script->args[0], errno); |
| } |
| else { |
| retval = system(str = cmd_str(script)); |
| |
| if (retval == -1) |
| log_message(LOG_ALERT, "Error exec-ing command: %s", str); |
| else if (WIFEXITED(retval)) { |
| if (WEXITSTATUS(retval) == 127) { |
| |
| log_message(LOG_ALERT, "Couldn't find command: %s", str); |
| } |
| else if (WEXITSTATUS(retval) == 126) { |
| |
| log_message(LOG_ALERT, "Couldn't execute command: %s", str); |
| } |
| } |
| |
| if (command_line) |
| FREE(command_line); |
| |
| if (retval == -1 || |
| (WIFEXITED(retval) && (WEXITSTATUS(retval) == 126 || WEXITSTATUS(retval) == 127))) |
| exit(0); |
| if (WIFEXITED(retval)) |
| exit(WEXITSTATUS(retval)); |
| if (WIFSIGNALED(retval)) |
| kill(getpid(), WTERMSIG(retval)); |
| exit(0); |
| } |
| |
| exit(0); |
| } |
| |
| |
| int |
| notify_exec(const notify_script_t *script) |
| { |
| pid_t pid; |
| |
| |
| if (log_file_name) |
| flush_log_file(); |
| |
| |
| pid = local_fork(); |
| |
| if (pid < 0) { |
| |
| log_message(LOG_INFO, "Failed fork process"); |
| return -1; |
| } |
| |
| if (pid) { |
| |
| return 0; |
| } |
| |
| |
| skip_mem_dump(); |
| |
| |
| system_call(script); |
| |
| |
| exit(0); |
| } |
| |
| int |
| system_call_script(thread_master_t *m, thread_func_t func, void * arg, unsigned long timer, notify_script_t* script) |
| { |
| pid_t pid; |
| |
| |
| |
| if (log_file_name) |
| flush_log_file(); |
| |
| |
| pid = local_fork(); |
| |
| if (pid < 0) { |
| |
| log_message(LOG_INFO, "Failed fork process"); |
| return -1; |
| } |
| |
| if (pid) { |
| |
| thread_add_child(m, func, arg, pid, timer); |
| |
| if (do_script_debug) |
| log_message(LOG_INFO, "Running script with pid %d, timer %lu.%6.6lu", pid, timer / TIMER_HZ, timer % TIMER_HZ); |
| |
| return 0; |
| } |
| |
| |
| |
| skip_mem_dump(); |
| |
| |
| system_call(script); |
| |
| exit(0); |
| } |
| |
| void |
| child_killed_thread(thread_ref_t thread) |
| { |
| thread_master_t *m = thread->master; |
| |
| |
| if (thread->type == THREAD_CHILD_TIMEOUT) |
| kill(-getpgid(thread->u.c.pid), SIGKILL); |
| |
| |
| |
| if (!&m->child.rb_root.rb_node && !m->shutdown_timer_running) |
| thread_add_terminate_event(m); |
| } |
| |
| void |
| script_killall(thread_master_t *m, int signo, bool requeue) |
| { |
| thread_t *thread; |
| pid_t p_pgid, c_pgid; |
| |
| sigset_t old_set, child_wait; |
| |
| sigmask_func(0, NULL, &old_set); |
| if (!sigismember(&old_set, SIGCHLD)) { |
| sigemptyset(&child_wait); |
| sigaddset(&child_wait, SIGCHLD); |
| sigmask_func(SIG_BLOCK, &child_wait, NULL); |
| } |
| |
| |
| p_pgid = getpgid(0); |
| |
| rb_for_each_entry_cached(thread, &m->child, n) { |
| c_pgid = getpgid(thread->u.c.pid); |
| if (c_pgid != p_pgid) |
| kill(-c_pgid, signo); |
| else { |
| log_message(LOG_INFO, "Child process %d in our process group %d", c_pgid, p_pgid); |
| kill(thread->u.c.pid, signo); |
| } |
| } |
| |
| |
| if (requeue && signo != SIGKILL) |
| thread_children_reschedule(m, child_killed_thread, TIMER_HZ); |
| |
| |
| if (!sigismember(&old_set, SIGCHLD)) |
| sigmask_func(SIG_UNBLOCK, &child_wait, NULL); |
| |
| } |
| |
| static bool |
| is_executable(const struct stat *buf, uid_t uid, gid_t gid) |
| { |
| return (uid == 0 && buf->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) || |
| (uid == buf->st_uid && buf->st_mode & S_IXUSR) || |
| (uid != buf->st_uid && |
| ((gid == buf->st_gid && buf->st_mode & S_IXGRP) || |
| (gid != buf->st_gid && buf->st_mode & S_IXOTH))); |
| } |
| |
| static void |
| replace_cmd_name(notify_script_t *script, const char *new_path) |
| { |
| size_t len; |
| const char * const *wp = &script->args[1]; |
| size_t num_words = 1; |
| char **params; |
| char **word_ptrs; |
| char *words; |
| |
| |
| |
| |
| |
| |
| union { |
| char **params; |
| const char **cparams; |
| } args; |
| |
| len = strlen(new_path) + 1; |
| while (*wp) |
| len += strlen(*wp++) + 1; |
| num_words = (script->args[0] - (const char *)&script->args[0]) - 1; |
| |
| params = word_ptrs = MALLOC((num_words + 1) * sizeof(char *) + len); |
| words = (char *)¶ms[num_words + 1]; |
| |
| strcpy(words, new_path); |
| *(word_ptrs++) = words; |
| words += strlen(words) + 1; |
| |
| wp = &script->args[1]; |
| while (*wp) { |
| strcpy(words, *wp); |
| *(word_ptrs++) = words; |
| words += strlen(*wp) + 1; |
| wp++; |
| } |
| *word_ptrs = NULL; |
| |
| FREE_CONST(script->args); |
| args.params = params; |
| script->args = args.cparams; |
| } |
| |
| |
| static int |
| find_path(notify_script_t *script) |
| { |
| size_t filename_len; |
| size_t file_len; |
| size_t path_len; |
| const char *file = script->args[0]; |
| struct stat buf; |
| int ret; |
| int ret_val = ENOENT; |
| int sgid_num; |
| gid_t *sgid_list = NULL; |
| const char *subp; |
| bool got_eacces = false; |
| const char *p; |
| |
| |
| if (*file == '\0') |
| return ENOENT; |
| |
| filename_len = strlen(file); |
| if (filename_len >= PATH_MAX) { |
| ret_val = ENAMETOOLONG; |
| goto exit1; |
| } |
| |
| |
| if (strchr (file, '/') != NULL) { |
| ret_val = 0; |
| goto exit1; |
| } |
| |
| |
| |
| if (!path) { |
| path = getenv ("PATH"); |
| |
| if (!path) { |
| size_t cs_path_len; |
| path = MALLOC(cs_path_len = confstr(_CS_PATH, NULL, 0)); |
| confstr(_CS_PATH, path, cs_path_len); |
| path_is_malloced = true; |
| } |
| } |
| |
| |
| |
| |
| file_len = strnlen (file, NAME_MAX + 1); |
| path_len = strnlen (path, PATH_MAX - 1) + 1; |
| |
| if (file_len > NAME_MAX) { |
| ret_val = ENAMETOOLONG; |
| goto exit1; |
| } |
| |
| |
| if (script->gid) { |
| if (setegid(script->gid)) { |
| log_message(LOG_INFO, "Unable to set egid to %u (%m)", script->gid); |
| ret_val = EACCES; |
| goto exit1; |
| } |
| |
| |
| sgid_num = getgroups(0, NULL); |
| if (sgid_num == -1) { |
| log_message(LOG_INFO, "Unable to get number of supplementary gids (%m)"); |
| ret_val = EACCES; |
| goto exit; |
| } |
| sgid_list = MALLOC(((size_t)sgid_num + 1) * sizeof(gid_t)); |
| sgid_num = getgroups(sgid_num, sgid_list); |
| sgid_list[sgid_num++] = 0; |
| |
| |
| if (setgroups(1, &script->gid)) { |
| log_message(LOG_INFO, "Unable to set supplementary gids (%m)"); |
| ret_val = EACCES; |
| goto exit; |
| } |
| } |
| if (script->uid && seteuid(script->uid)) { |
| log_message(LOG_INFO, "Unable to set euid to %u (%m)", script->uid); |
| ret_val = EACCES; |
| goto exit; |
| } |
| |
| for (p = path; ; p = subp) |
| { |
| subp = strchrnul (p, ':'); |
| |
| |
| |
| if (subp >= p + path_len) { |
| |
| if (*subp == '\0') { |
| ret_val = ENOENT; |
| goto exit; |
| } |
| |
| |
| continue; |
| } |
| |
| |
| char *buffer = MALLOC(path_len + file_len + 1); |
| char *pend = mempcpy (buffer, p, (size_t)(subp - p)); |
| *pend = '/'; |
| memcpy (pend + (p < subp), file, file_len + 1); |
| |
| ret = stat (buffer, &buf); |
| if (!ret) { |
| if (!S_ISREG(buf.st_mode)) |
| errno = EACCES; |
| else if (!is_executable(&buf, script->uid, script->gid)) { |
| errno = EACCES; |
| } else { |
| |
| log_message(LOG_INFO, "WARNING - script `%s` resolved by path search to `%s`. Please specify full path.", script->args[0], buffer); |
| |
| |
| replace_cmd_name(script, buffer); |
| |
| ret_val = 0; |
| got_eacces = false; |
| FREE(buffer); |
| goto exit; |
| } |
| } |
| FREE(buffer); |
| |
| switch (errno) |
| { |
| case ENOEXEC: |
| case EACCES: |
| |
| |
| |
| if (!ret) |
| got_eacces = true; |
| case ENOENT: |
| case ESTALE: |
| case ENOTDIR: |
| |
| |
| |
| case ENODEV: |
| case ETIMEDOUT: |
| |
| |
| |
| break; |
| |
| default: |
| |
| |
| |
| ret_val = -1; |
| goto exit; |
| } |
| |
| if (*subp++ == '\0') |
| break; |
| } |
| |
| exit: |
| |
| if (script->uid && seteuid(0)) |
| log_message(LOG_INFO, "Unable to restore euid after script search (%m)"); |
| if (script->gid) { |
| if (setegid(0)) |
| log_message(LOG_INFO, "Unable to restore egid after script search (%m)"); |
| |
| |
| if (sgid_list) { |
| if (setgroups((size_t)sgid_num, sgid_list)) |
| log_message(LOG_INFO, "Unable to restore supplementary groups after script search (%m)"); |
| FREE(sgid_list); |
| } |
| } |
| |
| exit1: |
| |
| if (got_eacces) { |
| |
| return EACCES; |
| } |
| |
| return ret_val; |
| } |
| |
| static int |
| check_security(const char *filename, bool using_script_security) |
| { |
| const char *next; |
| char *slash; |
| int ret; |
| struct stat buf; |
| int flags = 0; |
| char *filename_copy; |
| |
| next = filename; |
| while (next) { |
| slash = strchrnul(next, '/'); |
| if (*slash) |
| next = slash + 1; |
| else { |
| slash = NULL; |
| next = NULL; |
| } |
| |
| if (slash) { |
| |
| if (slash == filename) |
| slash++; |
| filename_copy = STRNDUP(filename, slash - filename); |
| } |
| |
| ret = fstatat(0, slash ? filename_copy : filename, &buf, AT_SYMLINK_NOFOLLOW); |
| |
| |
| if (slash) |
| FREE(filename_copy); |
| |
| if (ret) { |
| if (errno == EACCES || errno == ELOOP || errno == ENOENT || errno == ENOTDIR) |
| log_message(LOG_INFO, "check_script_secure could not find script '%s' - disabling", filename); |
| else |
| log_message(LOG_INFO, "check_script_secure('%s') returned errno %d - %s - disabling", filename, errno, strerror(errno)); |
| return flags | SC_NOTFOUND; |
| } |
| |
| |
| |
| if ((slash && !S_ISDIR(buf.st_mode)) || |
| (!slash && |
| !S_ISREG(buf.st_mode) && |
| !S_ISLNK(buf.st_mode))) { |
| log_message(LOG_INFO, "Wrong file type found in script path '%s'.", filename); |
| return flags | SC_INHIBIT; |
| } |
| |
| if (buf.st_uid || |
| (((S_ISDIR(buf.st_mode) && |
| !(buf.st_mode & S_ISVTX)) || |
| S_ISREG(buf.st_mode)) && |
| ((buf.st_gid && buf.st_mode & S_IWGRP) || |
| buf.st_mode & S_IWOTH))) { |
| log_message(LOG_INFO, "Unsafe permissions found for script '%s'%s.", filename, using_script_security ? " - disabling" : ""); |
| flags |= SC_INSECURE; |
| return flags | (using_script_security ? SC_INHIBIT : 0); |
| } |
| } |
| |
| return flags; |
| } |
| |
| unsigned |
| check_script_secure(notify_script_t *script, |
| |
| __attribute__((unused)) |
| |
| magic_t magic) |
| { |
| unsigned flags; |
| int ret, ret_real, ret_new; |
| struct stat file_buf, real_buf; |
| bool need_script_protection = false; |
| uid_t old_uid = 0; |
| gid_t old_gid = 0; |
| char *new_path; |
| char *sav_path; |
| int sav_errno; |
| char *real_file_path; |
| char *orig_file_part, *new_file_part; |
| |
| if (!script) |
| return 0; |
| |
| |
| |
| |
| if (script->args[0][0] == '<' && |
| script->args[0][strspn(script->args[0] + 1, " \t") + 1] == '/') |
| return SC_SYSTEM; |
| |
| if (!strchr(script->args[0], '/')) { |
| |
| if ((ret = find_path(script))) { |
| if (ret == EACCES) |
| log_message(LOG_INFO, "Permissions failure for script %s in path - disabling", script->args[0]); |
| else |
| log_message(LOG_INFO, "Cannot find script %s in path - disabling", script->args[0]); |
| return SC_NOTFOUND; |
| } |
| } |
| |
| |
| if (script->uid) |
| old_uid = geteuid(); |
| if (script->gid) |
| old_gid = getegid(); |
| |
| if ((script->gid && setegid(script->gid)) || |
| (script->uid && seteuid(script->uid))) { |
| log_message(LOG_INFO, "Unable to set uid:gid %u:%u for script %s - disabling", script->uid, script->gid, script->args[0]); |
| |
| if ((script->uid && seteuid(old_uid)) || |
| (script->gid && setegid(old_gid))) |
| log_message(LOG_INFO, "Unable to restore uid:gid %u:%u after script %s", script->uid, script->gid, script->args[0]); |
| |
| return SC_INHIBIT; |
| } |
| |
| |
| new_path = realpath(script->args[0], NULL); |
| sav_errno = errno; |
| |
| if ((script->gid && setegid(old_gid)) || |
| (script->uid && seteuid(old_uid))) |
| log_message(LOG_INFO, "Unable to restore uid:gid %u:%u after checking script %s", script->uid, script->gid, script->args[0]); |
| |
| if (!new_path) |
| { |
| log_message(LOG_INFO, "Script %s cannot be accessed - %s", script->args[0], strerror(sav_errno)); |
| |
| return SC_NOTFOUND; |
| } |
| |
| |
| |
| sav_path = new_path; |
| new_path = STRDUP(new_path); |
| free(sav_path); |
| |
| real_file_path = NULL; |
| |
| orig_file_part = strrchr(script->args[0], '/'); |
| new_file_part = strrchr(new_path, '/'); |
| if (strcmp(script->args[0], new_path)) { |
| |
| |
| |
| |
| |
| if (strcmp(orig_file_part + 1, new_file_part + 1)) { |
| real_file_path = new_path; |
| new_path = MALLOC(new_file_part - real_file_path + 1 + strlen(orig_file_part) - 1 + 1); |
| strncpy(new_path, real_file_path, new_file_part + 1 - real_file_path); |
| strcpy(new_path + (new_file_part + 1 - real_file_path), orig_file_part + 1); |
| |
| |
| ret_real = stat(real_file_path, &real_buf); |
| ret_new = stat(new_path, &file_buf); |
| if (!ret_real && |
| (ret_new || |
| real_buf.st_dev != file_buf.st_dev || |
| real_buf.st_ino != file_buf.st_ino)) { |
| |
| FREE(new_path); |
| new_path = real_file_path; |
| real_file_path = NULL; |
| } |
| } |
| |
| if (strcmp(script->args[0], new_path)) { |
| |
| replace_cmd_name(script, new_path); |
| } |
| } |
| |
| FREE(new_path); |
| |
| |
| if (stat(real_file_path ? real_file_path : script->args[0], &file_buf)) { |
| log_message(LOG_INFO, "Unable to access script `%s` - disabling", script->args[0]); |
| return SC_NOTFOUND; |
| } |
| |
| flags = SC_ISSCRIPT; |
| |
| |
| if (is_executable(&file_buf, script->uid, script->gid)) { |
| flags |= SC_EXECUTABLE; |
| if (script->uid == 0 || script->gid == 0 || |
| (file_buf.st_uid == 0 && (file_buf.st_mode & S_IXUSR) && (file_buf.st_mode & S_ISUID)) || |
| (file_buf.st_gid == 0 && (file_buf.st_mode & S_IXGRP) && (file_buf.st_mode & S_ISGID))) |
| need_script_protection = true; |
| } else |
| log_message(LOG_INFO, "WARNING - script '%s' is not executable for uid:gid %u:%u - disabling.", script->args[0], script->uid, script->gid); |
| |
| |
| script->flags |= SC_EXECABLE; |
| |
| if (magic && flags & SC_EXECUTABLE) { |
| const char *magic_desc = magic_file(magic, real_file_path ? real_file_path : script->args[0]); |
| if (!strstr(magic_desc, " executable") && |
| !strstr(magic_desc, " shared object")) { |
| log_message(LOG_INFO, "Please add a #! shebang to script %s", script->args[0]); |
| script->flags &= ~SC_EXECABLE; |
| } |
| } |
| |
| |
| if (!need_script_protection) { |
| if (real_file_path) |
| FREE(real_file_path); |
| |
| return flags; |
| } |
| |
| |
| flags |= check_security(script->args[0], script_security); |
| |
| if (real_file_path) { |
| flags |= check_security(real_file_path, script_security); |
| FREE(real_file_path); |
| } |
| |
| return flags; |
| } |
| |
| unsigned |
| check_notify_script_secure(notify_script_t **script_p, magic_t magic) |
| { |
| unsigned flags; |
| notify_script_t *script = *script_p; |
| |
| if (!script) |
| return 0; |
| |
| flags = check_script_secure(script, magic); |
| |
| |
| if ((flags & (SC_INHIBIT | SC_NOTFOUND)) || |
| !(flags & SC_EXECUTABLE)) |
| free_notify_script(script_p); |
| |
| return flags; |
| } |
| |
| static void |
| set_pwnam_buf_len(void) |
| { |
| long buf_len; |
| |
| |
| if ((buf_len = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1) |
| getpwnam_buf_len = 1024; |
| else |
| getpwnam_buf_len = (size_t)buf_len; |
| if ((buf_len = sysconf(_SC_GETGR_R_SIZE_MAX)) != -1 && |
| (size_t)buf_len > getpwnam_buf_len) |
| getpwnam_buf_len = (size_t)buf_len; |
| } |
| |
| static bool |
| set_uid_gid(const char *username, const char *groupname, uid_t *uid_p, gid_t *gid_p, bool default_user) |
| { |
| uid_t uid; |
| gid_t gid; |
| struct passwd pwd; |
| struct passwd *pwd_p; |
| struct group grp; |
| struct group *grp_p; |
| int ret; |
| bool using_default_default_user = false; |
| char *buf; |
| |
| if (!getpwnam_buf_len) |
| set_pwnam_buf_len(); |
| |
| buf = MALLOC(getpwnam_buf_len); |
| |
| if (default_user && !username) { |
| using_default_default_user = true; |
| username = "keepalived_script"; |
| } |
| |
| if ((ret = getpwnam_r(username, &pwd, buf, getpwnam_buf_len, &pwd_p))) { |
| log_message(LOG_INFO, "Unable to resolve %sscript username '%s' - ignoring", default_user ? "default " : "", username); |
| FREE(buf); |
| return true; |
| } |
| if (!pwd_p) { |
| if (using_default_default_user) |
| log_message(LOG_INFO, "WARNING - default user '%s' for script execution does not exist - please create.", username); |
| else |
| log_message(LOG_INFO, "%script user '%s' does not exist", default_user ? "Default s" : "S", username); |
| FREE(buf); |
| return true; |
| } |
| |
| uid = pwd.pw_uid; |
| gid = pwd.pw_gid; |
| |
| if (groupname) { |
| if ((ret = getgrnam_r(groupname, &grp, buf, getpwnam_buf_len, &grp_p))) { |
| log_message(LOG_INFO, "Unable to resolve %sscript group name '%s' - ignoring", default_user ? "default " : "", groupname); |
| FREE(buf); |
| return true; |
| } |
| if (!grp_p) { |
| log_message(LOG_INFO, "%script group '%s' does not exist", default_user ? "Default s" : "S", groupname); |
| FREE(buf); |
| return true; |
| } |
| gid = grp.gr_gid; |
| } |
| |
| *uid_p = uid; |
| *gid_p = gid; |
| |
| FREE(buf); |
| |
| return false; |
| } |
| |
| |
| bool |
| set_default_script_user(const char *username, const char *groupname) |
| { |
| if (!default_script_uid_set || username) { |
| |
| default_script_uid_set = true; |
| |
| if (set_uid_gid(username, groupname, &default_script_uid, &default_script_gid, true)) { |
| if (username || script_security) |
| default_user_fail = true; |
| } |
| else |
| default_user_fail = false; |
| } |
| |
| return default_user_fail; |
| } |
| |
| bool |
| set_script_uid_gid(const vector_t *strvec, unsigned keyword_offset, uid_t *uid_p, gid_t *gid_p) |
| { |
| const char *username; |
| const char *groupname; |
| |
| username = strvec_slot(strvec, keyword_offset); |
| if (vector_size(strvec) > keyword_offset + 1) |
| groupname = strvec_slot(strvec, keyword_offset + 1); |
| else |
| groupname = NULL; |
| |
| return set_uid_gid(username, groupname, uid_p, gid_p, false); |
| } |
| |
| void |
| set_script_params_array(const vector_t *strvec, notify_script_t *script, unsigned extra_params) |
| { |
| unsigned num_words = 0; |
| size_t len = 0; |
| char **word_ptrs; |
| char *words; |
| const vector_t *strvec_qe = NULL; |
| unsigned i; |
| union { |
| char **params; |
| const char **cparams; |
| } args; |
| |
| |
| if (vector_size(strvec) >= 2) |
| strvec_qe = alloc_strvec_quoted_escaped(strvec_slot(strvec, 1)); |
| |
| if (!strvec_qe) |
| return; |
| |
| num_words = vector_size(strvec_qe); |
| for (i = 0; i < num_words; i++) |
| len += strlen(strvec_slot(strvec_qe, i)) + 1; |
| |
| |
| word_ptrs = MALLOC((num_words + extra_params + 1) * sizeof(char *) + len); |
| words = (char *)word_ptrs + (num_words + extra_params + 1) * sizeof(char *); |
| args.params = word_ptrs; |
| script->args = args.cparams; |
| |
| |
| for (i = 0; i < num_words; i++) { |
| strcpy(words, strvec_slot(strvec_qe, i)); |
| *(word_ptrs++) = words; |
| words += strlen(words) + 1; |
| } |
| *word_ptrs = NULL; |
| |
| script->num_args = num_words; |
| |
| free_strvec(strvec_qe); |
| } |
| |
| notify_script_t* |
| notify_script_init(int extra_params, const char *type) |
| { |
| notify_script_t *script = MALLOC(sizeof(notify_script_t)); |
| const vector_t *strvec_qe; |
| |
| |
| strvec_qe = alloc_strvec_quoted_escaped(NULL); |
| |
| if (!strvec_qe) { |
| log_message(LOG_INFO, "Unable to parse notify script"); |
| FREE(script); |
| return NULL; |
| } |
| |
| set_script_params_array(strvec_qe, script, extra_params); |
| if (!script->args) { |
| log_message(LOG_INFO, "Unable to parse script '%s' - ignoring", strvec_slot(strvec_qe, 1)); |
| FREE(script); |
| free_strvec(strvec_qe); |
| return NULL; |
| } |
| |
| script->flags = 0; |
| |
| if (vector_size(strvec_qe) > 2) { |
| if (set_script_uid_gid(strvec_qe, 2, &script->uid, &script->gid)) { |
| log_message(LOG_INFO, "Invalid user/group for %s script %s - ignoring", type, script->args[0]); |
| FREE_CONST(script->args); |
| FREE(script); |
| free_strvec(strvec_qe); |
| return NULL; |
| } |
| } |
| else { |
| if (set_default_script_user(NULL, NULL)) { |
| log_message(LOG_INFO, "Failed to set default user for %s script %s - ignoring", type, script->args[0]); |
| FREE_CONST(script->args); |
| FREE(script); |
| free_strvec(strvec_qe); |
| return NULL; |
| } |
| |
| script->uid = default_script_uid; |
| script->gid = default_script_gid; |
| } |
| |
| free_strvec(strvec_qe); |
| |
| return script; |
| } |
| |
| void |
| add_script_param(notify_script_t *script, const char *param) |
| { |
| |
| |
| |
| |
| if (script->args[script->num_args + 1]) { |
| log_message(LOG_INFO, "notify_fifo_script %s no room to add parameter %s", script->args[0], param); |
| return; |
| } |
| |
| |
| script->args[script->num_args++] = param; |
| } |
| |
| void |
| notify_resource_release(void) |
| { |
| if (path_is_malloced) { |
| FREE(path); |
| path_is_malloced = false; |
| path = NULL; |
| } |
| } |
| |
| bool __attribute__ ((pure)) |
| notify_script_compare(const notify_script_t *a, const notify_script_t *b) |
| { |
| int i; |
| |
| if (a->num_args != b->num_args) |
| return false; |
| for (i = 0; i < a->num_args; i++) { |
| if (strcmp(a->args[i], b->args[i])) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| |
| void |
| register_notify_addresses(void) |
| { |
| register_thread_address("child_killed_thread", child_killed_thread); |
| } |
| |