diff options
Diffstat (limited to 'plugins/sudoers/check_aliases.c')
-rw-r--r-- | plugins/sudoers/check_aliases.c | 226 |
1 files changed, 226 insertions, 0 deletions
diff --git a/plugins/sudoers/check_aliases.c b/plugins/sudoers/check_aliases.c new file mode 100644 index 0000000..1b3d030 --- /dev/null +++ b/plugins/sudoers/check_aliases.c @@ -0,0 +1,226 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2004-2005, 2007-2018, 2021-2022 + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "sudoers.h" +#include <gram.h> + +struct alias_warned { + SLIST_ENTRY(alias_warned) entries; + const char *name; +}; +SLIST_HEAD(alias_warned_list, alias_warned); + +static bool alias_warnx(const char *file, int line, int column, bool strict, bool quiet, const char *fmt, ...) sudo_printflike(6, 7); + +static bool +alias_warned(struct alias_warned_list *warned, char *name) +{ + struct alias_warned *w; + debug_decl(alias_warned, SUDOERS_DEBUG_ALIAS); + + SLIST_FOREACH(w, warned, entries) { + if (strcmp(w->name, name) == 0) + debug_return_bool(true); + } + + debug_return_bool(false); +} + +static void +alias_warned_add(struct alias_warned_list *warned, char *name) +{ + struct alias_warned *w; + debug_decl(alias_warned_add, SUDOERS_DEBUG_ALIAS); + + w = malloc(sizeof(*w)); + if (w != NULL) { + w->name = name; + SLIST_INSERT_HEAD(warned, w, entries); + } + + debug_return; +} + +static bool +alias_warnx(const char *file, int line, int column, bool strict, bool quiet, + const char *fmt, ...) +{ + bool ret = true; + va_list ap; + debug_decl(alias_warnx, SUDOERS_DEBUG_ALIAS); + + if (strict && sudoers_error_hook != NULL) { + va_start(ap, fmt); + ret = sudoers_error_hook(file, line, column, fmt, ap); + va_end(ap); + } + + if (!quiet) { + int oldlocale; + char *errstr; + + sudoers_setlocale(SUDOERS_LOCALE_USER, &oldlocale); + va_start(ap, fmt); + if (vasprintf(&errstr, _(fmt), ap) == -1) { + errstr = NULL; + ret = false; + } else if (line > 0) { + sudo_printf(SUDO_CONV_ERROR_MSG, _("%s:%d:%d: %s\n"), file, + line, column, errstr); + } else { + sudo_printf(SUDO_CONV_ERROR_MSG, _("%s: %s\n"), file, errstr); + } + va_end(ap); + sudoers_setlocale(oldlocale, NULL); + + free(errstr); + } + + debug_return_bool(ret); +} + +static int +check_alias(struct sudoers_parse_tree *parse_tree, + struct alias_warned_list *warned, char *name, int type, + char *file, int line, int column, bool strict, bool quiet) +{ + struct member *m; + struct alias *a; + int errors = 0; + debug_decl(check_alias, SUDOERS_DEBUG_ALIAS); + + if ((a = alias_get(parse_tree, name, type)) != NULL) { + /* check alias contents */ + TAILQ_FOREACH(m, &a->members, entries) { + if (m->type != ALIAS) + continue; + errors += check_alias(parse_tree, warned, m->name, type, + a->file, a->line, a->column, strict, quiet); + } + alias_put(a); + } else { + if (!alias_warned(warned, name)) { + if (errno == ELOOP) { + alias_warnx(file, line, column, strict, quiet, + N_("cycle in %s \"%s\""), alias_type_to_string(type), name); + } else { + alias_warnx(file, line, column, strict, quiet, + N_("%s \"%s\" referenced but not defined"), + alias_type_to_string(type), name); + } + alias_warned_add(warned, name); + } + errors++; + } + + debug_return_int(errors); +} + +/* + * Iterate through the sudoers datastructures looking for undefined + * aliases or unused aliases. + * In strict mode, returns the number of errors, else 0. + */ +int +check_aliases(struct sudoers_parse_tree *parse_tree, bool strict, bool quiet, + int (*cb_unused)(struct sudoers_parse_tree *, struct alias *, void *)) +{ + struct alias_warned_list warned = SLIST_HEAD_INITIALIZER(warned); + struct rbtree *used_aliases; + struct alias_warned *w; + struct cmndspec *cs; + struct member *m; + struct privilege *priv; + struct userspec *us; + int errors = 0; + debug_decl(check_aliases, SUDOERS_DEBUG_ALIAS); + + used_aliases = alloc_aliases(); + if (used_aliases == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_int(-1); + } + + /* Forward check. */ + TAILQ_FOREACH(us, &parse_tree->userspecs, entries) { + TAILQ_FOREACH(m, &us->users, entries) { + if (m->type == ALIAS) { + errors += check_alias(parse_tree, &warned, m->name, USERALIAS, + us->file, us->line, us->column, strict, quiet); + } + } + TAILQ_FOREACH(priv, &us->privileges, entries) { + TAILQ_FOREACH(m, &priv->hostlist, entries) { + if (m->type == ALIAS) { + errors += check_alias(parse_tree, &warned, m->name, HOSTALIAS, + us->file, us->line, us->column, strict, quiet); + } + } + TAILQ_FOREACH(cs, &priv->cmndlist, entries) { + if (cs->runasuserlist != NULL) { + TAILQ_FOREACH(m, cs->runasuserlist, entries) { + if (m->type == ALIAS) { + errors += check_alias(parse_tree, &warned, m->name, RUNASALIAS, + us->file, us->line, us->column, strict, quiet); + } + } + } + if (cs->runasgrouplist != NULL) { + TAILQ_FOREACH(m, cs->runasgrouplist, entries) { + if (m->type == ALIAS) { + errors += check_alias(parse_tree, &warned, m->name, RUNASALIAS, + us->file, us->line, us->column, strict, quiet); + } + } + } + if ((m = cs->cmnd)->type == ALIAS) { + errors += check_alias(parse_tree, &warned, m->name, CMNDALIAS, + us->file, us->line, us->column, strict, quiet); + } + } + } + } + while ((w = SLIST_FIRST(&warned)) != NULL) { + SLIST_REMOVE_HEAD(&warned, entries); + free(w); + } + + /* Reverse check (destructive) */ + if (!alias_find_used(parse_tree, used_aliases)) + errors++; + free_aliases(used_aliases); + + /* If all aliases were referenced we will have an empty tree. */ + if (!no_aliases(parse_tree)) + alias_apply(parse_tree, cb_unused, &quiet); + + debug_return_int(strict ? errors : 0); +} |