diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:23:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:23:56 +0000 |
commit | 9620f76a210d9d8c1aaff25e99d6dc513f87e6e9 (patch) | |
tree | ceecc90fb95780872c35da764c5163f38e4727c4 /src/sudo.c | |
parent | Initial commit. (diff) | |
download | sudo-9620f76a210d9d8c1aaff25e99d6dc513f87e6e9.tar.xz sudo-9620f76a210d9d8c1aaff25e99d6dc513f87e6e9.zip |
Adding upstream version 1.8.27.upstream/1.8.27upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/sudo.c')
-rw-r--r-- | src/sudo.c | 1450 |
1 files changed, 1450 insertions, 0 deletions
diff --git a/src/sudo.c b/src/sudo.c new file mode 100644 index 0000000..c81f946 --- /dev/null +++ b/src/sudo.c @@ -0,0 +1,1450 @@ +/* + * Copyright (c) 2009-2018 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#ifdef __TANDEM +# include <floss.h> +#endif + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <sys/resource.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <grp.h> +#include <pwd.h> +#include <time.h> +#ifdef HAVE_SELINUX +# include <selinux/selinux.h> /* for is_selinux_enabled() */ +#endif +#ifdef HAVE_SETAUTHDB +# include <usersec.h> +#endif /* HAVE_SETAUTHDB */ +#if defined(HAVE_GETPRPWNAM) && defined(HAVE_SET_AUTH_PARAMETERS) +# ifdef __hpux +# undef MAXINT +# include <hpsecurity.h> +# else +# include <sys/security.h> +# endif /* __hpux */ +# include <prot.h> +#endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */ +#ifdef __linux__ +# include <sys/prctl.h> +#endif + +#include <sudo_usage.h> +#include "sudo.h" +#include "sudo_plugin.h" +#include "sudo_plugin_int.h" + +/* + * Local variables + */ +struct plugin_container policy_plugin; +struct plugin_container_list io_plugins = TAILQ_HEAD_INITIALIZER(io_plugins); +struct user_details user_details; +const char *list_user; /* extern for parse_args.c */ +int sudo_debug_instance = SUDO_DEBUG_INSTANCE_INITIALIZER; +static struct command_details command_details; +static int sudo_mode; + +struct sudo_gc_entry { + SLIST_ENTRY(sudo_gc_entry) entries; + enum sudo_gc_types type; + union { + char **vec; + void *ptr; + } u; +}; +SLIST_HEAD(sudo_gc_list, sudo_gc_entry); +#ifdef NO_LEAKS +static struct sudo_gc_list sudo_gc_list = SLIST_HEAD_INITIALIZER(sudo_gc_list); +#endif + +/* + * Local functions + */ +static void fix_fds(void); +static void sudo_check_suid(const char *path); +static char **get_user_info(struct user_details *); +static void command_info_to_details(char * const info[], + struct command_details *details); +static void gc_init(void); + +/* Policy plugin convenience functions. */ +static int policy_open(struct plugin_container *plugin, + struct sudo_settings *settings, + char * const user_info[], char * const user_env[]); +static void policy_close(struct plugin_container *plugin, int exit_status, + int error); +static int policy_show_version(struct plugin_container *plugin, int verbose); +static int policy_check(struct plugin_container *plugin, int argc, + char * const argv[], char *env_add[], char **command_info[], + char **argv_out[], char **user_env_out[]); +static int policy_list(struct plugin_container *plugin, int argc, + char * const argv[], int verbose, const char *list_user); +static int policy_validate(struct plugin_container *plugin); +static void policy_invalidate(struct plugin_container *plugin, int remove); + +/* I/O log plugin convenience functions. */ +static int iolog_open(struct plugin_container *plugin, + struct sudo_settings *settings, char * const user_info[], + char * const command_details[], int argc, char * const argv[], + char * const user_env[]); +static void iolog_close(struct plugin_container *plugin, int exit_status, + int error); +static int iolog_show_version(struct plugin_container *plugin, int verbose); +static void iolog_unlink(struct plugin_container *plugin); +static void free_plugin_container(struct plugin_container *plugin, bool ioplugin); + +__dso_public int main(int argc, char *argv[], char *envp[]); + +int +main(int argc, char *argv[], char *envp[]) +{ + int nargc, ok, status = 0; + char **nargv, **env_add; + char **user_info, **command_info, **argv_out, **user_env_out; + struct sudo_settings *settings; + struct plugin_container *plugin, *next; + sigset_t mask; + debug_decl_vars(main, SUDO_DEBUG_MAIN) + + /* Make sure fds 0-2 are open and do OS-specific initialization. */ + fix_fds(); + os_init(argc, argv, envp); + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE_NAME, LOCALEDIR); + textdomain(PACKAGE_NAME); + + (void) tzset(); + + /* Must be done before we do any password lookups */ +#if defined(HAVE_GETPRPWNAM) && defined(HAVE_SET_AUTH_PARAMETERS) + (void) set_auth_parameters(argc, argv); +# ifdef HAVE_INITPRIVS + initprivs(); +# endif +#endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */ + + /* Initialize the debug subsystem. */ + if (sudo_conf_read(NULL, SUDO_CONF_DEBUG) == -1) + exit(EXIT_FAILURE); + sudo_debug_instance = sudo_debug_register(getprogname(), + NULL, NULL, sudo_conf_debug_files(getprogname())); + if (sudo_debug_instance == SUDO_DEBUG_INSTANCE_ERROR) + exit(EXIT_FAILURE); + + /* Make sure we are setuid root. */ + sudo_check_suid(argc > 0 ? argv[0] : "sudo"); + + /* Save original signal state and setup default signal handlers. */ + save_signals(); + init_signals(); + + /* Reset signal mask to the default value (unblock). */ + (void) sigemptyset(&mask); + (void) sigprocmask(SIG_SETMASK, &mask, NULL); + + /* Parse the rest of sudo.conf. */ + sudo_conf_read(NULL, SUDO_CONF_ALL & ~SUDO_CONF_DEBUG); + + /* Fill in user_info with user name, uid, cwd, etc. */ + if ((user_info = get_user_info(&user_details)) == NULL) + exit(EXIT_FAILURE); /* get_user_info printed error message */ + + /* Disable core dumps if not enabled in sudo.conf. */ + if (sudo_conf_disable_coredump()) + disable_coredump(false); + + /* Parse command line arguments. */ + sudo_mode = parse_args(argc, argv, &nargc, &nargv, &settings, &env_add); + sudo_debug_printf(SUDO_DEBUG_DEBUG, "sudo_mode %d", sudo_mode); + + /* Print sudo version early, in case of plugin init failure. */ + if (ISSET(sudo_mode, MODE_VERSION)) { + printf(_("Sudo version %s\n"), PACKAGE_VERSION); + if (user_details.uid == ROOT_UID) + (void) printf(_("Configure options: %s\n"), CONFIGURE_ARGS); + } + + /* Use conversation function for sudo_(warn|fatal)x? for plugins. */ + sudo_warn_set_conversation(sudo_conversation); + + /* Load plugins. */ + if (!sudo_load_plugins(&policy_plugin, &io_plugins)) + sudo_fatalx(U_("fatal error, unable to load plugins")); + + /* Open policy plugin. */ + ok = policy_open(&policy_plugin, settings, user_info, envp); + if (ok != 1) { + if (ok == -2) + usage(1); + else + sudo_fatalx(U_("unable to initialize policy plugin")); + } + + switch (sudo_mode & MODE_MASK) { + case MODE_VERSION: + policy_show_version(&policy_plugin, !user_details.uid); + TAILQ_FOREACH(plugin, &io_plugins, entries) { + ok = iolog_open(plugin, settings, user_info, NULL, + nargc, nargv, envp); + if (ok != -1) + iolog_show_version(plugin, !user_details.uid); + } + break; + case MODE_VALIDATE: + case MODE_VALIDATE|MODE_INVALIDATE: + ok = policy_validate(&policy_plugin); + exit(ok != 1); + case MODE_KILL: + case MODE_INVALIDATE: + policy_invalidate(&policy_plugin, sudo_mode == MODE_KILL); + exit(0); + break; + case MODE_CHECK: + case MODE_CHECK|MODE_INVALIDATE: + case MODE_LIST: + case MODE_LIST|MODE_INVALIDATE: + ok = policy_list(&policy_plugin, nargc, nargv, + ISSET(sudo_mode, MODE_LONG_LIST), list_user); + exit(ok != 1); + case MODE_EDIT: + case MODE_RUN: + ok = policy_check(&policy_plugin, nargc, nargv, env_add, + &command_info, &argv_out, &user_env_out); + sudo_debug_printf(SUDO_DEBUG_INFO, "policy plugin returns %d", ok); + if (ok != 1) { + if (ok == -2) + usage(1); + exit(EXIT_FAILURE); /* plugin printed error message */ + } + /* Reset nargv/nargc based on argv_out. */ + /* XXX - leaks old nargv in shell mode */ + for (nargv = argv_out, nargc = 0; nargv[nargc] != NULL; nargc++) + continue; + if (nargc == 0) + sudo_fatalx(U_("plugin did not return a command to execute")); + /* Open I/O plugins once policy plugin succeeds. */ + TAILQ_FOREACH_SAFE(plugin, &io_plugins, entries, next) { + ok = iolog_open(plugin, settings, user_info, + command_info, nargc, nargv, user_env_out); + switch (ok) { + case 1: + break; + case 0: + /* I/O plugin asked to be disabled, remove and free. */ + iolog_unlink(plugin); + break; + case -2: + usage(1); + break; + default: + sudo_fatalx(U_("error initializing I/O plugin %s"), + plugin->name); + } + } + /* Setup command details and run command/edit. */ + command_info_to_details(command_info, &command_details); + command_details.argv = argv_out; + command_details.envp = user_env_out; + if (ISSET(sudo_mode, MODE_LOGIN_SHELL)) + SET(command_details.flags, CD_LOGIN_SHELL); + if (ISSET(sudo_mode, MODE_BACKGROUND)) + SET(command_details.flags, CD_BACKGROUND); + /* Become full root (not just setuid) so user cannot kill us. */ + if (setuid(ROOT_UID) == -1) + sudo_warn("setuid(%d)", ROOT_UID); + if (ISSET(command_details.flags, CD_SUDOEDIT)) { + status = sudo_edit(&command_details); + } else { + status = run_command(&command_details); + } + /* The close method was called by sudo_edit/run_command. */ + break; + default: + sudo_fatalx(U_("unexpected sudo mode 0x%x"), sudo_mode); + } + + /* + * If the command was terminated by a signal, sudo needs to terminated + * the same way. Otherwise, the shell may ignore a keyboard-generated + * signal. However, we want to avoid having sudo dump core itself. + */ + if (WIFSIGNALED(status)) { + struct sigaction sa; + + if (WCOREDUMP(status)) + disable_coredump(false); + + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_DFL; + sigaction(WTERMSIG(status), &sa, NULL); + sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, + WTERMSIG(status) | 128); + kill(getpid(), WTERMSIG(status)); + } + sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, + WEXITSTATUS(status)); + exit(WEXITSTATUS(status)); +} + +int +os_init_common(int argc, char *argv[], char *envp[]) +{ + initprogname(argc > 0 ? argv[0] : "sudo"); +#ifdef STATIC_SUDOERS_PLUGIN + preload_static_symbols(); +#endif + gc_init(); + return 0; +} + +/* + * Ensure that stdin, stdout and stderr are open; set to /dev/null if not. + * Some operating systems do this automatically in the kernel or libc. + */ +static void +fix_fds(void) +{ + int miss[3], devnull = -1; + debug_decl(fix_fds, SUDO_DEBUG_UTIL) + + /* + * stdin, stdout and stderr must be open; set them to /dev/null + * if they are closed. + */ + miss[STDIN_FILENO] = fcntl(STDIN_FILENO, F_GETFL, 0) == -1; + miss[STDOUT_FILENO] = fcntl(STDOUT_FILENO, F_GETFL, 0) == -1; + miss[STDERR_FILENO] = fcntl(STDERR_FILENO, F_GETFL, 0) == -1; + if (miss[STDIN_FILENO] || miss[STDOUT_FILENO] || miss[STDERR_FILENO]) { + devnull = open(_PATH_DEVNULL, O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (devnull == -1) + sudo_fatal(U_("unable to open %s"), _PATH_DEVNULL); + if (miss[STDIN_FILENO] && dup2(devnull, STDIN_FILENO) == -1) + sudo_fatal("dup2"); + if (miss[STDOUT_FILENO] && dup2(devnull, STDOUT_FILENO) == -1) + sudo_fatal("dup2"); + if (miss[STDERR_FILENO] && dup2(devnull, STDERR_FILENO) == -1) + sudo_fatal("dup2"); + if (devnull > STDERR_FILENO) + close(devnull); + } + debug_return; +} + +/* + * Allocate space for groups and fill in using sudo_getgrouplist2() + * for when we cannot (or don't want to) use getgroups(). + * Returns 0 on success and -1 on failure. + */ +static int +fill_group_list(struct user_details *ud) +{ + int ret = -1; + debug_decl(fill_group_list, SUDO_DEBUG_UTIL) + + /* + * If user specified a max number of groups, use it, otherwise let + * sudo_getgrouplist2() allocate the group vector. + */ + ud->ngroups = sudo_conf_max_groups(); + if (ud->ngroups > 0) { + ud->groups = reallocarray(NULL, ud->ngroups, sizeof(GETGROUPS_T)); + if (ud->groups != NULL) { + /* No error on insufficient space if user specified max_groups. */ + (void)sudo_getgrouplist2(ud->username, ud->gid, &ud->groups, + &ud->ngroups); + ret = 0; + } + } else { + ud->groups = NULL; + ret = sudo_getgrouplist2(ud->username, ud->gid, &ud->groups, + &ud->ngroups); + } + if (ret == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: %s: unable to get groups via sudo_getgrouplist2()", + __func__, ud->username); + } else { + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: %s: got %d groups via sudo_getgrouplist2()", + __func__, ud->username, ud->ngroups); + } + debug_return_int(ret); +} + +static char * +get_user_groups(struct user_details *ud) +{ + char *cp, *gid_list = NULL; + size_t glsize; + int i, len, group_source; + debug_decl(get_user_groups, SUDO_DEBUG_UTIL) + + ud->groups = NULL; + group_source = sudo_conf_group_source(); + if (group_source != GROUP_SOURCE_DYNAMIC) { + int maxgroups = (int)sysconf(_SC_NGROUPS_MAX); + if (maxgroups < 0) + maxgroups = NGROUPS_MAX; + + if ((ud->ngroups = getgroups(0, NULL)) > 0) { + /* Use groups from kernel if not too many or source is static. */ + if (ud->ngroups < maxgroups || group_source == GROUP_SOURCE_STATIC) { + ud->groups = reallocarray(NULL, ud->ngroups, sizeof(GETGROUPS_T)); + if (ud->groups == NULL) + goto done; + if (getgroups(ud->ngroups, ud->groups) < 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: %s: unable to get %d groups via getgroups()", + __func__, ud->username, ud->ngroups); + free(ud->groups); + ud->groups = NULL; + } else { + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: %s: got %d groups via getgroups()", + __func__, ud->username, ud->ngroups); + } + } + } + } + if (ud->groups == NULL) { + /* + * Query group database if kernel list is too small or disabled. + * Typically, this is because NFS can only support up to 16 groups. + */ + if (fill_group_list(ud) == -1) + goto done; + } + + /* + * Format group list as a comma-separated string of gids. + */ + glsize = sizeof("groups=") - 1 + (ud->ngroups * (MAX_UID_T_LEN + 1)); + if ((gid_list = malloc(glsize)) == NULL) + goto done; + memcpy(gid_list, "groups=", sizeof("groups=") - 1); + cp = gid_list + sizeof("groups=") - 1; + for (i = 0; i < ud->ngroups; i++) { + len = snprintf(cp, glsize - (cp - gid_list), "%s%u", + i ? "," : "", (unsigned int)ud->groups[i]); + if (len <= 0 || (size_t)len >= glsize - (cp - gid_list)) + sudo_fatalx(U_("internal error, %s overflow"), __func__); + cp += len; + } +done: + debug_return_str(gid_list); +} + +/* + * Return user information as an array of name=value pairs. + * and fill in struct user_details (which shares the same strings). + */ +static char ** +get_user_info(struct user_details *ud) +{ + char *cp, **user_info, path[PATH_MAX]; + unsigned int i = 0; + mode_t mask; + struct passwd *pw; + int fd; + debug_decl(get_user_info, SUDO_DEBUG_UTIL) + + /* + * On BSD systems you can set a hint to keep the password and + * group databases open instead of having to open and close + * them all the time. Since sudo does a lot of password and + * group lookups, keeping the file open can speed things up. + */ +#ifdef HAVE_SETPASSENT + setpassent(1); +#endif /* HAVE_SETPASSENT */ +#ifdef HAVE_SETGROUPENT + setgroupent(1); +#endif /* HAVE_SETGROUPENT */ + + memset(ud, 0, sizeof(*ud)); + + /* XXX - bound check number of entries */ + user_info = reallocarray(NULL, 32, sizeof(char *)); + if (user_info == NULL) + goto oom; + + ud->pid = getpid(); + ud->ppid = getppid(); + ud->pgid = getpgid(0); + ud->tcpgid = -1; + fd = open(_PATH_TTY, O_RDWR); + if (fd != -1) { + ud->tcpgid = tcgetpgrp(fd); + close(fd); + } + ud->sid = getsid(0); + + ud->uid = getuid(); + ud->euid = geteuid(); + ud->gid = getgid(); + ud->egid = getegid(); + +#ifdef HAVE_SETAUTHDB + aix_setauthdb(IDtouser(ud->uid), NULL); +#endif + pw = getpwuid(ud->uid); +#ifdef HAVE_SETAUTHDB + aix_restoreauthdb(); +#endif + if (pw == NULL) + sudo_fatalx(U_("you do not exist in the %s database"), "passwd"); + + user_info[i] = sudo_new_key_val("user", pw->pw_name); + if (user_info[i] == NULL) + goto oom; + ud->username = user_info[i] + sizeof("user=") - 1; + + /* Stash user's shell for use with the -s flag; don't pass to plugin. */ + if ((ud->shell = getenv("SHELL")) == NULL || ud->shell[0] == '\0') { + ud->shell = pw->pw_shell[0] ? pw->pw_shell : _PATH_SUDO_BSHELL; + } + if ((ud->shell = strdup(ud->shell)) == NULL) + goto oom; + + if (asprintf(&user_info[++i], "pid=%d", (int)ud->pid) == -1) + goto oom; + if (asprintf(&user_info[++i], "ppid=%d", (int)ud->ppid) == -1) + goto oom; + if (asprintf(&user_info[++i], "pgid=%d", (int)ud->pgid) == -1) + goto oom; + if (asprintf(&user_info[++i], "tcpgid=%d", (int)ud->tcpgid) == -1) + goto oom; + if (asprintf(&user_info[++i], "sid=%d", (int)ud->sid) == -1) + goto oom; + if (asprintf(&user_info[++i], "uid=%u", (unsigned int)ud->uid) == -1) + goto oom; + if (asprintf(&user_info[++i], "euid=%u", (unsigned int)ud->euid) == -1) + goto oom; + if (asprintf(&user_info[++i], "gid=%u", (unsigned int)ud->gid) == -1) + goto oom; + if (asprintf(&user_info[++i], "egid=%u", (unsigned int)ud->egid) == -1) + goto oom; + + if ((cp = get_user_groups(ud)) == NULL) + goto oom; + user_info[++i] = cp; + + mask = umask(0); + umask(mask); + if (asprintf(&user_info[++i], "umask=0%o", (unsigned int)mask) == -1) + goto oom; + + if (getcwd(path, sizeof(path)) != NULL) { + user_info[++i] = sudo_new_key_val("cwd", path); + if (user_info[i] == NULL) + goto oom; + ud->cwd = user_info[i] + sizeof("cwd=") - 1; + } + + if (get_process_ttyname(path, sizeof(path)) != NULL) { + user_info[++i] = sudo_new_key_val("tty", path); + if (user_info[i] == NULL) + goto oom; + ud->tty = user_info[i] + sizeof("tty=") - 1; + } else { + /* tty may not always be present */ + if (errno != ENOENT) + sudo_warn(U_("unable to determine tty")); + } + + cp = sudo_gethostname(); + user_info[++i] = sudo_new_key_val("host", cp ? cp : "localhost"); + free(cp); + if (user_info[i] == NULL) + goto oom; + ud->host = user_info[i] + sizeof("host=") - 1; + + sudo_get_ttysize(&ud->ts_rows, &ud->ts_cols); + if (asprintf(&user_info[++i], "lines=%d", ud->ts_rows) == -1) + goto oom; + if (asprintf(&user_info[++i], "cols=%d", ud->ts_cols) == -1) + goto oom; + + user_info[++i] = NULL; + + /* Add to list of vectors to be garbage collected at exit. */ + if (!gc_add(GC_VECTOR, user_info)) + goto bad; + + debug_return_ptr(user_info); +oom: + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); +bad: + while (i--) + free(user_info[i]); + free(user_info); + debug_return_ptr(NULL); +} + +/* + * Convert a command_info array into a command_details structure. + */ +static void +command_info_to_details(char * const info[], struct command_details *details) +{ + int i; + id_t id; + char *cp; + const char *errstr; + debug_decl(command_info_to_details, SUDO_DEBUG_PCOMM) + + memset(details, 0, sizeof(*details)); + details->closefrom = -1; + details->execfd = -1; + details->flags = CD_SUDOEDIT_CHECKDIR | CD_SET_GROUPS; + TAILQ_INIT(&details->preserved_fds); + +#define SET_STRING(s, n) \ + if (strncmp(s, info[i], sizeof(s) - 1) == 0 && info[i][sizeof(s) - 1]) { \ + details->n = info[i] + sizeof(s) - 1; \ + break; \ + } +#define SET_FLAG(s, n) \ + if (strncmp(s, info[i], sizeof(s) - 1) == 0) { \ + switch (sudo_strtobool(info[i] + sizeof(s) - 1)) { \ + case true: \ + SET(details->flags, n); \ + break; \ + case false: \ + CLR(details->flags, n); \ + break; \ + default: \ + sudo_debug_printf(SUDO_DEBUG_ERROR, \ + "invalid boolean value for %s", info[i]); \ + break; \ + } \ + break; \ + } + + sudo_debug_printf(SUDO_DEBUG_INFO, "command info from plugin:"); + for (i = 0; info[i] != NULL; i++) { + sudo_debug_printf(SUDO_DEBUG_INFO, " %d: %s", i, info[i]); + switch (info[i][0]) { + case 'c': + SET_STRING("chroot=", chroot) + SET_STRING("command=", command) + SET_STRING("cwd=", cwd) + if (strncmp("closefrom=", info[i], sizeof("closefrom=") - 1) == 0) { + cp = info[i] + sizeof("closefrom=") - 1; + details->closefrom = strtonum(cp, 0, INT_MAX, &errstr); + if (errstr != NULL) + sudo_fatalx(U_("%s: %s"), info[i], U_(errstr)); + break; + } + break; + case 'e': + SET_FLAG("exec_background=", CD_EXEC_BG) + if (strncmp("execfd=", info[i], sizeof("execfd=") - 1) == 0) { + cp = info[i] + sizeof("execfd=") - 1; + details->execfd = strtonum(cp, 0, INT_MAX, &errstr); + if (errstr != NULL) + sudo_fatalx(U_("%s: %s"), info[i], U_(errstr)); +#ifdef HAVE_FEXECVE + /* Must keep fd open during exec. */ + add_preserved_fd(&details->preserved_fds, details->execfd); +#else + /* Plugin thinks we support fexecve() but we don't. */ + (void)fcntl(details->execfd, F_SETFD, FD_CLOEXEC); + details->execfd = -1; +#endif + break; + } + break; + case 'l': + SET_STRING("login_class=", login_class) + break; + case 'n': + if (strncmp("nice=", info[i], sizeof("nice=") - 1) == 0) { + cp = info[i] + sizeof("nice=") - 1; + details->priority = strtonum(cp, INT_MIN, INT_MAX, &errstr); + if (errstr != NULL) + sudo_fatalx(U_("%s: %s"), info[i], U_(errstr)); + SET(details->flags, CD_SET_PRIORITY); + break; + } + SET_FLAG("noexec=", CD_NOEXEC) + break; + case 'p': + SET_FLAG("preserve_groups=", CD_PRESERVE_GROUPS) + if (strncmp("preserve_fds=", info[i], sizeof("preserve_fds=") - 1) == 0) { + parse_preserved_fds(&details->preserved_fds, + info[i] + sizeof("preserve_fds=") - 1); + break; + } + break; + case 'r': + if (strncmp("runas_egid=", info[i], sizeof("runas_egid=") - 1) == 0) { + cp = info[i] + sizeof("runas_egid=") - 1; + id = sudo_strtoid(cp, NULL, NULL, &errstr); + if (errstr != NULL) + sudo_fatalx(U_("%s: %s"), info[i], U_(errstr)); + details->egid = (gid_t)id; + SET(details->flags, CD_SET_EGID); + break; + } + if (strncmp("runas_euid=", info[i], sizeof("runas_euid=") - 1) == 0) { + cp = info[i] + sizeof("runas_euid=") - 1; + id = sudo_strtoid(cp, NULL, NULL, &errstr); + if (errstr != NULL) + sudo_fatalx(U_("%s: %s"), info[i], U_(errstr)); + details->euid = (uid_t)id; + SET(details->flags, CD_SET_EUID); + break; + } + if (strncmp("runas_gid=", info[i], sizeof("runas_gid=") - 1) == 0) { + cp = info[i] + sizeof("runas_gid=") - 1; + id = sudo_strtoid(cp, NULL, NULL, &errstr); + if (errstr != NULL) + sudo_fatalx(U_("%s: %s"), info[i], U_(errstr)); + details->gid = (gid_t)id; + SET(details->flags, CD_SET_GID); + break; + } + if (strncmp("runas_groups=", info[i], sizeof("runas_groups=") - 1) == 0) { + cp = info[i] + sizeof("runas_groups=") - 1; + details->ngroups = sudo_parse_gids(cp, NULL, &details->groups); + /* sudo_parse_gids() will print a warning on error. */ + if (details->ngroups == -1) + exit(EXIT_FAILURE); /* XXX */ + break; + } + if (strncmp("runas_uid=", info[i], sizeof("runas_uid=") - 1) == 0) { + cp = info[i] + sizeof("runas_uid=") - 1; + id = sudo_strtoid(cp, NULL, NULL, &errstr); + if (errstr != NULL) + sudo_fatalx(U_("%s: %s"), info[i], U_(errstr)); + details->uid = (uid_t)id; + SET(details->flags, CD_SET_UID); + break; + } +#ifdef HAVE_PRIV_SET + if (strncmp("runas_privs=", info[i], sizeof("runas_privs=") - 1) == 0) { + const char *endp; + cp = info[i] + sizeof("runas_privs=") - 1; + if (*cp != '\0') { + details->privs = priv_str_to_set(cp, ",", &endp); + if (details->privs == NULL) + sudo_warn("invalid runas_privs %s", endp); + } + break; + } + if (strncmp("runas_limitprivs=", info[i], sizeof("runas_limitprivs=") - 1) == 0) { + const char *endp; + cp = info[i] + sizeof("runas_limitprivs=") - 1; + if (*cp != '\0') { + details->limitprivs = priv_str_to_set(cp, ",", &endp); + if (details->limitprivs == NULL) + sudo_warn("invalid runas_limitprivs %s", endp); + } + break; + } +#endif /* HAVE_PRIV_SET */ + break; + case 's': + SET_STRING("selinux_role=", selinux_role) + SET_STRING("selinux_type=", selinux_type) + SET_FLAG("set_utmp=", CD_SET_UTMP) + SET_FLAG("sudoedit=", CD_SUDOEDIT) + SET_FLAG("sudoedit_checkdir=", CD_SUDOEDIT_CHECKDIR) + SET_FLAG("sudoedit_follow=", CD_SUDOEDIT_FOLLOW) + break; + case 't': + if (strncmp("timeout=", info[i], sizeof("timeout=") - 1) == 0) { + cp = info[i] + sizeof("timeout=") - 1; + details->timeout = strtonum(cp, 0, INT_MAX, &errstr); + if (errstr != NULL) + sudo_fatalx(U_("%s: %s"), info[i], U_(errstr)); + SET(details->flags, CD_SET_TIMEOUT); + break; + } + break; + case 'u': + if (strncmp("umask=", info[i], sizeof("umask=") - 1) == 0) { + cp = info[i] + sizeof("umask=") - 1; + details->umask = sudo_strtomode(cp, &errstr); + if (errstr != NULL) + sudo_fatalx(U_("%s: %s"), info[i], U_(errstr)); + SET(details->flags, CD_SET_UMASK); + break; + } + SET_FLAG("use_pty=", CD_USE_PTY) + SET_STRING("utmp_user=", utmp_user) + break; + } + } + + if (!ISSET(details->flags, CD_SET_EUID)) + details->euid = details->uid; + if (!ISSET(details->flags, CD_SET_EGID)) + details->egid = details->gid; + +#ifdef HAVE_SETAUTHDB + aix_setauthdb(IDtouser(details->euid), NULL); +#endif + details->pw = getpwuid(details->euid); +#ifdef HAVE_SETAUTHDB + aix_restoreauthdb(); +#endif + if (details->pw != NULL && (details->pw = pw_dup(details->pw)) == NULL) + sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + +#ifdef HAVE_SELINUX + if (details->selinux_role != NULL && is_selinux_enabled() > 0) + SET(details->flags, CD_RBAC_ENABLED); +#endif + debug_return; +} + +static void +sudo_check_suid(const char *sudo) +{ + char pathbuf[PATH_MAX]; + struct stat sb; + bool qualified; + debug_decl(sudo_check_suid, SUDO_DEBUG_PCOMM) + + if (geteuid() != ROOT_UID) { + /* Search for sudo binary in PATH if not fully qualified. */ + qualified = strchr(sudo, '/') != NULL; + if (!qualified) { + char *path = getenv_unhooked("PATH"); + if (path != NULL) { + const char *cp, *ep; + const char *pathend = path + strlen(path); + + for (cp = sudo_strsplit(path, pathend, ":", &ep); cp != NULL; + cp = sudo_strsplit(NULL, pathend, ":", &ep)) { + + int len = snprintf(pathbuf, sizeof(pathbuf), "%.*s/%s", + (int)(ep - cp), cp, sudo); + if (len <= 0 || (size_t)len >= sizeof(pathbuf)) + continue; + if (access(pathbuf, X_OK) == 0) { + sudo = pathbuf; + qualified = true; + break; + } + } + } + } + + if (qualified && stat(sudo, &sb) == 0) { + /* Try to determine why sudo was not running as root. */ + if (sb.st_uid != ROOT_UID || !ISSET(sb.st_mode, S_ISUID)) { + sudo_fatalx( + U_("%s must be owned by uid %d and have the setuid bit set"), + sudo, ROOT_UID); + } else { + sudo_fatalx(U_("effective uid is not %d, is %s on a file system " + "with the 'nosuid' option set or an NFS file system without" + " root privileges?"), ROOT_UID, sudo); + } + } else { + sudo_fatalx( + U_("effective uid is not %d, is sudo installed setuid root?"), + ROOT_UID); + } + } + debug_return; +} + +/* + * Disable core dumps to avoid dropping a core with user password in it. + * Called with restore set to true before executing the command. + * Not all operating systems disable core dumps for setuid processes. + */ +void +disable_coredump(bool restore) +{ + struct rlimit rl; + static struct rlimit corelimit; +#ifdef __linux__ + static int dumpflag; +#endif + debug_decl(disable_coredump, SUDO_DEBUG_UTIL) + + if (restore) { + (void) setrlimit(RLIMIT_CORE, &corelimit); +#ifdef __linux__ + (void) prctl(PR_SET_DUMPABLE, dumpflag, 0, 0, 0); +#endif /* __linux__ */ + debug_return; + } + + (void) getrlimit(RLIMIT_CORE, &corelimit); + rl.rlim_cur = 0; + rl.rlim_max = 0; + (void) setrlimit(RLIMIT_CORE, &rl); +#ifdef __linux__ + /* On Linux, also set PR_SET_DUMPABLE to zero (reset by execve). */ + if ((dumpflag = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)) == -1) + dumpflag = 0; + (void) prctl(PR_SET_DUMPABLE, 0, 0, 0, 0); +#endif /* __linux__ */ + + debug_return; +} + +bool +set_user_groups(struct command_details *details) +{ + bool ret = false; + debug_decl(set_user_groups, SUDO_DEBUG_EXEC) + + if (!ISSET(details->flags, CD_PRESERVE_GROUPS)) { + if (details->ngroups >= 0) { + if (sudo_setgroups(details->ngroups, details->groups) < 0) { + sudo_warn(U_("unable to set supplementary group IDs")); + goto done; + } + } + } +#ifdef HAVE_SETEUID + if (ISSET(details->flags, CD_SET_EGID) && setegid(details->egid)) { + sudo_warn(U_("unable to set effective gid to runas gid %u"), + (unsigned int)details->egid); + goto done; + } +#endif + if (ISSET(details->flags, CD_SET_GID) && setgid(details->gid)) { + sudo_warn(U_("unable to set gid to runas gid %u"), + (unsigned int)details->gid); + goto done; + } + ret = true; + +done: + CLR(details->flags, CD_SET_GROUPS); + debug_return_bool(ret); +} + +/* + * Run the command and wait for it to complete. + * Returns wait status suitable for use with the wait(2) macros. + */ +int +run_command(struct command_details *details) +{ + struct plugin_container *plugin; + struct command_status cstat; + int status = W_EXITCODE(1, 0); + debug_decl(run_command, SUDO_DEBUG_EXEC) + + cstat.type = CMD_INVALID; + cstat.val = 0; + + sudo_execute(details, &cstat); + + switch (cstat.type) { + case CMD_ERRNO: + /* exec_setup() or execve() returned an error. */ + sudo_debug_printf(SUDO_DEBUG_DEBUG, + "calling policy close with errno %d", cstat.val); + policy_close(&policy_plugin, 0, cstat.val); + TAILQ_FOREACH(plugin, &io_plugins, entries) { + sudo_debug_printf(SUDO_DEBUG_DEBUG, + "calling I/O close with errno %d", cstat.val); + iolog_close(plugin, 0, cstat.val); + } + break; + case CMD_WSTATUS: + /* Command ran, exited or was killed. */ + status = cstat.val; +#ifdef HAVE_SELINUX + if (ISSET(details->flags, CD_SUDOEDIT_COPY)) + break; +#endif + sudo_debug_printf(SUDO_DEBUG_DEBUG, + "calling policy close with wait status %d", status); + policy_close(&policy_plugin, status, 0); + TAILQ_FOREACH(plugin, &io_plugins, entries) { + sudo_debug_printf(SUDO_DEBUG_DEBUG, + "calling I/O close with wait status %d", status); + iolog_close(plugin, status, 0); + } + break; + default: + sudo_warnx(U_("unexpected child termination condition: %d"), cstat.type); + break; + } + debug_return_int(status); +} + +/* + * Format struct sudo_settings as name=value pairs for the plugin + * to consume. Returns a NULL-terminated plugin-style array of pairs. + */ +static char ** +format_plugin_settings(struct plugin_container *plugin, + struct sudo_settings *sudo_settings) +{ + size_t plugin_settings_size; + struct sudo_debug_file *debug_file; + struct sudo_settings *setting; + char **plugin_settings; + unsigned int i = 0; + debug_decl(format_plugin_settings, SUDO_DEBUG_PCOMM) + + /* Determine sudo_settings array size (including plugin_path and NULL) */ + plugin_settings_size = 2; + for (setting = sudo_settings; setting->name != NULL; setting++) + plugin_settings_size++; + if (plugin->debug_files != NULL) { + TAILQ_FOREACH(debug_file, plugin->debug_files, entries) + plugin_settings_size++; + } + + /* Allocate and fill in. */ + plugin_settings = reallocarray(NULL, plugin_settings_size, sizeof(char *)); + if (plugin_settings == NULL) + goto bad; + plugin_settings[i] = sudo_new_key_val("plugin_path", plugin->path); + if (plugin_settings[i] == NULL) + goto bad; + for (setting = sudo_settings; setting->name != NULL; setting++) { + if (setting->value != NULL) { + sudo_debug_printf(SUDO_DEBUG_INFO, "settings: %s=%s", + setting->name, setting->value); + plugin_settings[++i] = + sudo_new_key_val(setting->name, setting->value); + if (plugin_settings[i] == NULL) + goto bad; + } + } + if (plugin->debug_files != NULL) { + TAILQ_FOREACH(debug_file, plugin->debug_files, entries) { + /* XXX - quote filename? */ + if (asprintf(&plugin_settings[++i], "debug_flags=%s %s", + debug_file->debug_file, debug_file->debug_flags) == -1) + goto bad; + } + } + plugin_settings[++i] = NULL; + + /* Add to list of vectors to be garbage collected at exit. */ + if (!gc_add(GC_VECTOR, plugin_settings)) + sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + + debug_return_ptr(plugin_settings); +bad: + while (i--) + free(plugin_settings[i]); + free(plugin_settings); + debug_return_ptr(NULL); +} + +static int +policy_open(struct plugin_container *plugin, struct sudo_settings *settings, + char * const user_info[], char * const user_env[]) +{ + char **plugin_settings; + int ret; + debug_decl(policy_open, SUDO_DEBUG_PCOMM) + + /* Convert struct sudo_settings to plugin_settings[] */ + plugin_settings = format_plugin_settings(plugin, settings); + if (plugin_settings == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_int(-1); + } + + /* + * Backwards compatibility for older API versions + */ + sudo_debug_set_active_instance(SUDO_DEBUG_INSTANCE_INITIALIZER); + switch (plugin->u.generic->version) { + case SUDO_API_MKVERSION(1, 0): + case SUDO_API_MKVERSION(1, 1): + ret = plugin->u.policy_1_0->open(plugin->u.io_1_0->version, + sudo_conversation_1_7, sudo_conversation_printf, plugin_settings, + user_info, user_env); + break; + default: + ret = plugin->u.policy->open(SUDO_API_VERSION, sudo_conversation, + sudo_conversation_printf, plugin_settings, user_info, user_env, + plugin->options); + } + + /* Stash plugin debug instance ID if set in open() function. */ + plugin->debug_instance = sudo_debug_get_active_instance(); + sudo_debug_set_active_instance(sudo_debug_instance); + + debug_return_int(ret); +} + +static void +policy_close(struct plugin_container *plugin, int exit_status, int error_code) +{ + debug_decl(policy_close, SUDO_DEBUG_PCOMM) + if (plugin->u.policy->close != NULL) { + sudo_debug_set_active_instance(plugin->debug_instance); + plugin->u.policy->close(exit_status, error_code); + sudo_debug_set_active_instance(sudo_debug_instance); + } else if (error_code) { + errno = error_code; + sudo_warn(U_("unable to execute %s"), command_details.command); + } + debug_return; +} + +static int +policy_show_version(struct plugin_container *plugin, int verbose) +{ + int ret; + debug_decl(policy_show_version, SUDO_DEBUG_PCOMM) + + if (plugin->u.policy->show_version == NULL) + debug_return_int(true); + sudo_debug_set_active_instance(plugin->debug_instance); + ret = plugin->u.policy->show_version(verbose); + sudo_debug_set_active_instance(sudo_debug_instance); + debug_return_int(ret); +} + +static int +policy_check(struct plugin_container *plugin, int argc, char * const argv[], + char *env_add[], char **command_info[], char **argv_out[], + char **user_env_out[]) +{ + int ret; + debug_decl(policy_check, SUDO_DEBUG_PCOMM) + + if (plugin->u.policy->check_policy == NULL) { + sudo_fatalx(U_("policy plugin %s is missing the `check_policy' method"), + plugin->name); + } + sudo_debug_set_active_instance(plugin->debug_instance); + ret = plugin->u.policy->check_policy(argc, argv, env_add, command_info, + argv_out, user_env_out); + sudo_debug_set_active_instance(sudo_debug_instance); + debug_return_int(ret); +} + +static int +policy_list(struct plugin_container *plugin, int argc, char * const argv[], + int verbose, const char *list_user) +{ + int ret; + debug_decl(policy_list, SUDO_DEBUG_PCOMM) + + if (plugin->u.policy->list == NULL) { + sudo_warnx(U_("policy plugin %s does not support listing privileges"), + plugin->name); + debug_return_int(false); + } + sudo_debug_set_active_instance(plugin->debug_instance); + ret = plugin->u.policy->list(argc, argv, verbose, list_user); + sudo_debug_set_active_instance(sudo_debug_instance); + debug_return_int(ret); +} + +static int +policy_validate(struct plugin_container *plugin) +{ + int ret; + debug_decl(policy_validate, SUDO_DEBUG_PCOMM) + + if (plugin->u.policy->validate == NULL) { + sudo_warnx(U_("policy plugin %s does not support the -v option"), + plugin->name); + debug_return_int(false); + } + sudo_debug_set_active_instance(plugin->debug_instance); + ret = plugin->u.policy->validate(); + sudo_debug_set_active_instance(sudo_debug_instance); + debug_return_int(ret); +} + +static void +policy_invalidate(struct plugin_container *plugin, int remove) +{ + debug_decl(policy_invalidate, SUDO_DEBUG_PCOMM) + if (plugin->u.policy->invalidate == NULL) { + sudo_fatalx(U_("policy plugin %s does not support the -k/-K options"), + plugin->name); + } + sudo_debug_set_active_instance(plugin->debug_instance); + plugin->u.policy->invalidate(remove); + sudo_debug_set_active_instance(sudo_debug_instance); + debug_return; +} + +int +policy_init_session(struct command_details *details) +{ + int ret = true; + debug_decl(policy_init_session, SUDO_DEBUG_PCOMM) + + /* + * We set groups, including supplementary group vector, + * as part of the session setup. This allows for dynamic + * groups to be set via pam_group(8) in pam_setcred(3). + */ + if (ISSET(details->flags, CD_SET_GROUPS)) { + /* set_user_groups() prints error message on failure. */ + if (!set_user_groups(details)) + goto done; + } + + if (policy_plugin.u.policy->init_session) { + /* + * Backwards compatibility for older API versions + */ + sudo_debug_set_active_instance(policy_plugin.debug_instance); + switch (policy_plugin.u.generic->version) { + case SUDO_API_MKVERSION(1, 0): + case SUDO_API_MKVERSION(1, 1): + ret = policy_plugin.u.policy_1_0->init_session(details->pw); + break; + default: + ret = policy_plugin.u.policy->init_session(details->pw, + &details->envp); + } + sudo_debug_set_active_instance(sudo_debug_instance); + } +done: + debug_return_int(ret); +} + +static int +iolog_open(struct plugin_container *plugin, struct sudo_settings *settings, + char * const user_info[], char * const command_info[], + int argc, char * const argv[], char * const user_env[]) +{ + char **plugin_settings; + int ret; + debug_decl(iolog_open, SUDO_DEBUG_PCOMM) + + /* Convert struct sudo_settings to plugin_settings[] */ + plugin_settings = format_plugin_settings(plugin, settings); + if (plugin_settings == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_int(-1); + } + + /* + * Backwards compatibility for older API versions + */ + sudo_debug_set_active_instance(plugin->debug_instance); + switch (plugin->u.generic->version) { + case SUDO_API_MKVERSION(1, 0): + ret = plugin->u.io_1_0->open(plugin->u.io_1_0->version, + sudo_conversation_1_7, sudo_conversation_printf, plugin_settings, + user_info, argc, argv, user_env); + break; + case SUDO_API_MKVERSION(1, 1): + ret = plugin->u.io_1_1->open(plugin->u.io_1_1->version, + sudo_conversation_1_7, sudo_conversation_printf, plugin_settings, + user_info, command_info, argc, argv, user_env); + break; + default: + ret = plugin->u.io->open(SUDO_API_VERSION, sudo_conversation, + sudo_conversation_printf, plugin_settings, user_info, command_info, + argc, argv, user_env, plugin->options); + } + + /* Stash plugin debug instance ID if set in open() function. */ + plugin->debug_instance = sudo_debug_get_active_instance(); + sudo_debug_set_active_instance(sudo_debug_instance); + + debug_return_int(ret); +} + +static void +iolog_close(struct plugin_container *plugin, int exit_status, int error_code) +{ + debug_decl(iolog_close, SUDO_DEBUG_PCOMM) + + if (plugin->u.io->close != NULL) { + sudo_debug_set_active_instance(plugin->debug_instance); + plugin->u.io->close(exit_status, error_code); + sudo_debug_set_active_instance(sudo_debug_instance); + } + debug_return; +} + +static int +iolog_show_version(struct plugin_container *plugin, int verbose) +{ + int ret; + debug_decl(iolog_show_version, SUDO_DEBUG_PCOMM) + + if (plugin->u.io->show_version == NULL) + debug_return_int(true); + + sudo_debug_set_active_instance(plugin->debug_instance); + ret = plugin->u.io->show_version(verbose); + sudo_debug_set_active_instance(sudo_debug_instance); + debug_return_int(ret); +} + +/* + * Remove the specified I/O logging plugin from the io_plugins list. + * Deregisters any hooks before unlinking, then frees the container. + */ +static void +iolog_unlink(struct plugin_container *plugin) +{ + debug_decl(iolog_unlink, SUDO_DEBUG_PCOMM) + + /* Deregister hooks, if any. */ + if (plugin->u.io->version >= SUDO_API_MKVERSION(1, 2)) { + if (plugin->u.io->deregister_hooks != NULL) { + sudo_debug_set_active_instance(plugin->debug_instance); + plugin->u.io->deregister_hooks(SUDO_HOOK_VERSION, + deregister_hook); + sudo_debug_set_active_instance(sudo_debug_instance); + } + } + /* Remove from io_plugins list and free. */ + TAILQ_REMOVE(&io_plugins, plugin, entries); + free_plugin_container(plugin, true); + + debug_return; +} + +static void +free_plugin_container(struct plugin_container *plugin, bool ioplugin) +{ + debug_decl(free_plugin_container, SUDO_DEBUG_PLUGIN) + + free(plugin->path); + free(plugin->name); + if (plugin->options != NULL) { + int i = 0; + while (plugin->options[i] != NULL) + free(plugin->options[i++]); + free(plugin->options); + } + if (ioplugin) + free(plugin); + + debug_return; +} + +bool +gc_add(enum sudo_gc_types type, void *v) +{ +#ifdef NO_LEAKS + struct sudo_gc_entry *gc; + debug_decl(gc_add, SUDO_DEBUG_MAIN) + + if (v == NULL) + debug_return_bool(false); + + gc = calloc(1, sizeof(*gc)); + if (gc == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_bool(false); + } + switch (type) { + case GC_PTR: + gc->u.ptr = v; + break; + case GC_VECTOR: + gc->u.vec = v; + break; + default: + free(gc); + sudo_warnx("unexpected garbage type %d", type); + debug_return_bool(false); + } + gc->type = type; + SLIST_INSERT_HEAD(&sudo_gc_list, gc, entries); + debug_return_bool(true); +#else + return true; +#endif /* NO_LEAKS */ +} + +#ifdef NO_LEAKS +static void +gc_run(void) +{ + struct plugin_container *plugin; + struct sudo_gc_entry *gc; + char **cur; + debug_decl(gc_run, SUDO_DEBUG_MAIN) + + /* Collect garbage. */ + while ((gc = SLIST_FIRST(&sudo_gc_list))) { + SLIST_REMOVE_HEAD(&sudo_gc_list, entries); + switch (gc->type) { + case GC_PTR: + free(gc->u.ptr); + free(gc); + break; + case GC_VECTOR: + for (cur = gc->u.vec; *cur != NULL; cur++) + free(*cur); + free(gc->u.vec); + free(gc); + break; + default: + sudo_warnx("unexpected garbage type %d", gc->type); + } + } + + /* Free plugin structs. */ + free_plugin_container(&policy_plugin, false); + while ((plugin = TAILQ_FIRST(&io_plugins))) { + TAILQ_REMOVE(&io_plugins, plugin, entries); + free_plugin_container(plugin, true); + } + + debug_return; +} +#endif /* NO_LEAKS */ + +static void +gc_init(void) +{ +#ifdef NO_LEAKS + atexit(gc_run); +#endif +} |