diff options
Diffstat (limited to 'src/load_plugins.c')
-rw-r--r-- | src/load_plugins.c | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/src/load_plugins.c b/src/load_plugins.c new file mode 100644 index 0000000..df08b95 --- /dev/null +++ b/src/load_plugins.c @@ -0,0 +1,488 @@ +/* + * SPDX-License-Identifier: ISC + * + * 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 + */ + +#include <config.h> + +#include <sys/stat.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <sudo.h> +#include <sudo_plugin.h> +#include <sudo_plugin_int.h> +#include <sudo_dso.h> + +#ifdef ENABLE_SUDO_PLUGIN_API +static bool +sudo_qualify_plugin(struct plugin_info *info, char *fullpath, size_t pathsize) +{ + const char *plugin_dir = sudo_conf_plugin_dir_path(); + int len; + debug_decl(sudo_stat_plugin, SUDO_DEBUG_PLUGIN); + + if (info->path[0] == '/') { + if (strlcpy(fullpath, info->path, pathsize) >= pathsize) { + errno = ENAMETOOLONG; + goto bad; + } + } else { +#ifdef STATIC_SUDOERS_PLUGIN + /* Check static symbols. */ + if (strcmp(info->path, _PATH_SUDOERS_PLUGIN) == 0) { + if (strlcpy(fullpath, info->path, pathsize) >= pathsize) { + errno = ENAMETOOLONG; + goto bad; + } + /* Plugin is static, do not fully-qualify. */ + debug_return_bool(true); + } +#endif /* STATIC_SUDOERS_PLUGIN */ + + if (plugin_dir == NULL) { + errno = ENOENT; + goto bad; + } + len = snprintf(fullpath, pathsize, "%s%s", plugin_dir, info->path); + if (len < 0 || (size_t)len >= pathsize) { + errno = ENAMETOOLONG; + goto bad; + } + } + debug_return_bool(true); +bad: + sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""), + _PATH_SUDO_CONF, info->lineno, info->symbol_name); + if (info->path[0] != '/' && plugin_dir != NULL) + sudo_warn("%s%s", plugin_dir, info->path); + else + sudo_warn("%s", info->path); + debug_return_bool(false); +} +#else +static bool +sudo_qualify_plugin(struct plugin_info *info, char *fullpath, size_t pathsize) +{ + debug_decl(sudo_qualify_plugin, SUDO_DEBUG_PLUGIN); + (void)strlcpy(fullpath, info->path, pathsize); + debug_return_bool(true); +} +#endif /* ENABLE_SUDO_PLUGIN_API */ + +static bool +fill_container(struct plugin_container *container, void *handle, + const char *path, struct generic_plugin *plugin, struct plugin_info *info) +{ + debug_decl(fill_container, SUDO_DEBUG_PLUGIN); + + if ((container->path = strdup(path)) == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_bool(false); + } + container->handle = handle; + container->name = info->symbol_name; + container->options = info->options; + container->debug_instance = SUDO_DEBUG_INSTANCE_INITIALIZER; + container->u.generic = plugin; + container->debug_files = sudo_conf_debug_files(path); + + /* Zero out info strings that the container now owns. */ + info->symbol_name = NULL; + info->options = NULL; + + debug_return_bool(true); +} + +static struct plugin_container * +new_container(void *handle, const char *path, struct generic_plugin *plugin, + struct plugin_info *info) +{ + struct plugin_container *container; + debug_decl(new_container, SUDO_DEBUG_PLUGIN); + + if ((container = calloc(1, sizeof(*container))) == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + goto bad; + } + if (!fill_container(container, handle, path, plugin, info)) + goto bad; + + debug_return_ptr(container); +bad: + free(container); + debug_return_ptr(NULL); +} + +static bool +plugin_exists(struct plugin_container_list *plugins, const char *symbol_name) +{ + struct plugin_container *container; + debug_decl(plugin_exists, SUDO_DEBUG_PLUGIN); + + TAILQ_FOREACH(container, plugins, entries) { + if (strcmp(container->name, symbol_name) == 0) + debug_return_bool(true); + } + debug_return_bool(false); +} + +typedef struct generic_plugin * (plugin_clone_func)(void); + +static struct generic_plugin * +sudo_plugin_try_to_clone(void *so_handle, const char *symbol_name) +{ + debug_decl(sudo_plugin_try_to_clone, SUDO_DEBUG_PLUGIN); + struct generic_plugin *plugin = NULL; + plugin_clone_func *clone_func; + char *clone_func_name = NULL; + + if (asprintf(&clone_func_name, "%s_clone", symbol_name) < 0) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + goto cleanup; + } + + clone_func = (plugin_clone_func *)sudo_dso_findsym(so_handle, + clone_func_name); + if (clone_func) { + plugin = (*clone_func)(); + } + +cleanup: + free(clone_func_name); + debug_return_ptr(plugin); +} + +static bool +sudo_insert_plugin(struct plugin_container_list *plugin_list, void *handle, + const char *path, struct generic_plugin *plugin, struct plugin_info *info) +{ + struct plugin_container *container; + debug_decl(sudo_insert_plugin, SUDO_DEBUG_PLUGIN); + + if (plugin_exists(plugin_list, info->symbol_name)) { + plugin = sudo_plugin_try_to_clone(handle, info->symbol_name); + if (plugin == NULL) { + sudo_warnx(U_("ignoring duplicate plugin \"%s\" in %s, line %d"), + info->symbol_name, _PATH_SUDO_CONF, info->lineno); + sudo_dso_unload(handle); + goto done; + } + } + + if ((container = new_container(handle, path, plugin, info)) == NULL) + debug_return_bool(false); + TAILQ_INSERT_TAIL(plugin_list, container, entries); + +done: + debug_return_bool(true); +} + +/* + * Load the plugin specified by "info". + */ +static bool +sudo_load_plugin(struct plugin_info *info, bool quiet) +{ + struct generic_plugin *plugin; + char path[PATH_MAX]; + void *handle = NULL; + bool ret = false; + debug_decl(sudo_load_plugin, SUDO_DEBUG_PLUGIN); + + /* Fill in path from info and plugin dir. */ + if (!sudo_qualify_plugin(info, path, sizeof(path))) + goto done; + + /* Open plugin and map in symbol */ + handle = sudo_dso_load(path, SUDO_DSO_LAZY|SUDO_DSO_GLOBAL); + if (!handle) { + if (!quiet) { + const char *errstr = sudo_dso_strerror(); + sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""), + _PATH_SUDO_CONF, info->lineno, info->symbol_name); + sudo_warnx(U_("unable to load %s: %s"), path, + errstr ? errstr : "unknown error"); + } + goto done; + } + plugin = sudo_dso_findsym(handle, info->symbol_name); + if (!plugin) { + if (!quiet) { + sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""), + _PATH_SUDO_CONF, info->lineno, info->symbol_name); + sudo_warnx(U_("unable to find symbol \"%s\" in %s"), + info->symbol_name, path); + } + goto done; + } + + if (SUDO_API_VERSION_GET_MAJOR(plugin->version) != SUDO_API_VERSION_MAJOR) { + if (!quiet) { + sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""), + _PATH_SUDO_CONF, info->lineno, info->symbol_name); + sudo_warnx(U_("incompatible plugin major version %d (expected %d) found in %s"), + SUDO_API_VERSION_GET_MAJOR(plugin->version), + SUDO_API_VERSION_MAJOR, path); + } + goto done; + } + + switch (plugin->type) { + case SUDO_POLICY_PLUGIN: + if (policy_plugin.handle != NULL) { + /* Ignore duplicate entries. */ + if (strcmp(policy_plugin.name, info->symbol_name) == 0) { + if (!quiet) { + sudo_warnx(U_("ignoring duplicate plugin \"%s\" in %s, line %d"), + info->symbol_name, _PATH_SUDO_CONF, info->lineno); + } + } else { + if (!quiet) { + sudo_warnx(U_("ignoring policy plugin \"%s\" in %s, line %d"), + info->symbol_name, _PATH_SUDO_CONF, info->lineno); + sudo_warnx("%s", + U_("only a single policy plugin may be specified")); + } + goto done; + } + ret = true; + goto done; + } + if (!fill_container(&policy_plugin, handle, path, plugin, info)) + goto done; + break; + case SUDO_IO_PLUGIN: + if (!sudo_insert_plugin(&io_plugins, handle, path, plugin, info)) + goto done; + break; + case SUDO_AUDIT_PLUGIN: + if (!sudo_insert_plugin(&audit_plugins, handle, path, plugin, info)) + goto done; + break; + case SUDO_APPROVAL_PLUGIN: + if (!sudo_insert_plugin(&approval_plugins, handle, path, plugin, info)) + goto done; + break; + default: + if (!quiet) { + sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""), + _PATH_SUDO_CONF, info->lineno, info->symbol_name); + sudo_warnx(U_("unknown plugin type %d found in %s"), plugin->type, path); + } + goto done; + } + + /* Handle is either in use or has been closed. */ + handle = NULL; + + ret = true; + +done: + if (handle != NULL) + sudo_dso_unload(handle); + debug_return_bool(ret); +} + +static void +free_plugin_info(struct plugin_info *info) +{ + free(info->path); + free(info->symbol_name); + if (info->options != NULL) { + int i = 0; + while (info->options[i] != NULL) + free(info->options[i++]); + free(info->options); + } + free(info); +} + +static void +sudo_register_hooks(void) +{ + struct plugin_container *container; + debug_decl(sudo_register_hooks, SUDO_DEBUG_PLUGIN); + + if (policy_plugin.u.policy->version >= SUDO_API_MKVERSION(1, 2)) { + if (policy_plugin.u.policy->register_hooks != NULL) { + sudo_debug_set_active_instance(policy_plugin.debug_instance); + policy_plugin.u.policy->register_hooks(SUDO_HOOK_VERSION, + register_hook); + sudo_debug_set_active_instance(sudo_debug_instance); + } + } + + TAILQ_FOREACH(container, &io_plugins, entries) { + if (container->u.io->version >= SUDO_API_MKVERSION(1, 2)) { + if (container->u.io->register_hooks != NULL) { + sudo_debug_set_active_instance(container->debug_instance); + container->u.io->register_hooks(SUDO_HOOK_VERSION, + register_hook); + sudo_debug_set_active_instance(sudo_debug_instance); + } + } + } + + TAILQ_FOREACH(container, &audit_plugins, entries) { + if (container->u.audit->register_hooks != NULL) { + sudo_debug_set_active_instance(container->debug_instance); + container->u.audit->register_hooks(SUDO_HOOK_VERSION, + register_hook); + sudo_debug_set_active_instance(sudo_debug_instance); + } + } + + debug_return; +} + +static void +sudo_init_event_alloc(void) +{ + struct plugin_container *container; + debug_decl(sudo_init_event_alloc, SUDO_DEBUG_PLUGIN); + + if (policy_plugin.u.policy->version >= SUDO_API_MKVERSION(1, 15)) + policy_plugin.u.policy->event_alloc = sudo_plugin_event_alloc; + + TAILQ_FOREACH(container, &io_plugins, entries) { + if (container->u.io->version >= SUDO_API_MKVERSION(1, 15)) + container->u.io->event_alloc = sudo_plugin_event_alloc; + } + TAILQ_FOREACH(container, &audit_plugins, entries) { + if (container->u.audit->version >= SUDO_API_MKVERSION(1, 17)) + container->u.audit->event_alloc = sudo_plugin_event_alloc; + } + + debug_return; +} + +/* + * Load the specified symbol from the sudoers plugin. + * Used to provide a default plugin when none are specified in sudo.conf. + */ +static bool +sudo_load_sudoers_plugin(const char *symbol_name, bool optional) +{ + struct plugin_info *info; + bool ret = false; + debug_decl(sudo_load_sudoers_plugin, SUDO_DEBUG_PLUGIN); + + /* Default policy plugin */ + info = calloc(1, sizeof(*info)); + if (info == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + goto done; + } + info->symbol_name = strdup(symbol_name); + info->path = strdup(_PATH_SUDOERS_PLUGIN); + if (info->symbol_name == NULL || info->path == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + free_plugin_info(info); + goto done; + } + /* info->options = NULL; */ + ret = sudo_load_plugin(info, optional); + free_plugin_info(info); + +done: + debug_return_bool(ret); +} + +/* + * Load the plugins listed in sudo.conf. + */ +bool +sudo_load_plugins(void) +{ + struct plugin_info_list *plugins; + struct plugin_info *info, *next; + bool ret = false; + debug_decl(sudo_load_plugins, SUDO_DEBUG_PLUGIN); + + /* Walk the plugin list from sudo.conf, if any and free it. */ + plugins = sudo_conf_plugins(); + TAILQ_FOREACH_SAFE(info, plugins, entries, next) { + ret = sudo_load_plugin(info, false); + if (!ret) + goto done; + free_plugin_info(info); + } + TAILQ_INIT(plugins); + + /* + * If no policy plugin, fall back to the default (sudoers). + * If there is also no I/O log plugin, use sudoers for that too. + */ + if (policy_plugin.handle == NULL) { + /* Default policy plugin */ + ret = sudo_load_sudoers_plugin("sudoers_policy", false); + if (!ret) + goto done; + + /* Default audit plugin, optional (sudoers < 1.9.1 lack this) */ + (void)sudo_load_sudoers_plugin("sudoers_audit", true); + + /* Default I/O plugin */ + if (TAILQ_EMPTY(&io_plugins)) { + ret = sudo_load_sudoers_plugin("sudoers_io", false); + if (!ret) + goto done; + } + } else if (strcmp(policy_plugin.name, "sudoers_policy") == 0) { + /* + * If policy plugin is sudoers_policy but there is no sudoers_audit + * loaded, load it too, if possible. + */ + if (!plugin_exists(&audit_plugins, "sudoers_audit")) { + if (sudo_load_sudoers_plugin("sudoers_audit", true)) { + /* + * Move the plugin options from sudoers_policy to sudoers_audit + * since the audit module is now what actually opens sudoers. + */ + if (policy_plugin.options != NULL) { + TAILQ_LAST(&audit_plugins, plugin_container_list)->options = + policy_plugin.options; + policy_plugin.options = NULL; + } + } + } + } + + /* TODO: check all plugins for open function too */ + if (policy_plugin.u.policy->check_policy == NULL) { + sudo_warnx(U_("policy plugin %s does not include a check_policy method"), + policy_plugin.name); + ret = false; + goto done; + } + + /* Set event_alloc() in plugins. */ + sudo_init_event_alloc(); + + /* Install hooks (XXX - later, after open). */ + sudo_register_hooks(); + +done: + debug_return_bool(ret); +} |