diff options
Diffstat (limited to '')
-rw-r--r-- | plugins/sudoers/defaults.c | 1310 |
1 files changed, 1310 insertions, 0 deletions
diff --git a/plugins/sudoers/defaults.c b/plugins/sudoers/defaults.c new file mode 100644 index 0000000..34c5d1d --- /dev/null +++ b/plugins/sudoers/defaults.c @@ -0,0 +1,1310 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 1999-2005, 2007-2023 + * 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. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* + * 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 <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <syslog.h> + +#include <sudoers.h> +#include <sudo_eventlog.h> +#include <sudo_iolog.h> +#include <gram.h> + +static struct early_default early_defaults[] = { + { I_IGNORE_UNKNOWN_DEFAULTS }, +#ifdef FQDN + { I_FQDN, true }, +#else + { I_FQDN }, +#endif + { I_MATCH_GROUP_BY_GID }, + { I_GROUP_PLUGIN }, + { I_RUNAS_DEFAULT }, + { I_SUDOERS_LOCALE }, + { -1 } +}; + +/* + * Local prototypes. + */ +static bool store_int(const char *str, struct sudo_defs_types *def); +static bool store_list(const char *str, struct sudo_defs_types *def, int op); +static bool store_mode(const char *str, struct sudo_defs_types *def); +static int store_str(const char *str, struct sudo_defs_types *def); +static bool store_syslogfac(const char *str, struct sudo_defs_types *def); +static bool store_syslogpri(const char *str, struct sudo_defs_types *def); +static bool store_timeout(const char *str, struct sudo_defs_types *def); +static bool store_tuple(const char *str, struct sudo_defs_types *def, int op); +static bool store_uint(const char *str, struct sudo_defs_types *def); +static bool store_timespec(const char *str, struct sudo_defs_types *def); +static bool store_rlimit(const char *str, struct sudo_defs_types *def); +static bool store_plugin(const char *str, struct sudo_defs_types *def, int op); +static bool list_op(const char *str, size_t, struct list_members *list, enum list_ops op); +static bool valid_path(const struct sudoers_context *ctx, struct sudo_defs_types *def, const char *val, const char *file, int line, int column, bool quiet); + +/* + * Table describing compile-time and run-time options. + */ +#include <def_data.c> + +/* + * Print version and configure info. + */ +void +dump_defaults(void) +{ + struct sudo_defs_types *cur; + struct list_member *item; + struct def_values *def; + const char *desc; + debug_decl(dump_defaults, SUDOERS_DEBUG_DEFAULTS); + + for (cur = sudo_defs_table; cur->name; cur++) { + if (cur->desc) { + desc = _(cur->desc); + switch (cur->type & T_MASK) { + case T_FLAG: + if (cur->sd_un.flag) + sudo_printf(SUDO_CONV_INFO_MSG, "%s\n", desc); + break; + case T_STR: + case T_RLIMIT: + if (cur->sd_un.str) { + sudo_printf(SUDO_CONV_INFO_MSG, desc, cur->sd_un.str); + sudo_printf(SUDO_CONV_INFO_MSG, "\n"); + } + break; + case T_LOGFAC: + if (cur->sd_un.ival) { + sudo_printf(SUDO_CONV_INFO_MSG, desc, + sudo_logfac2str(cur->sd_un.ival)); + sudo_printf(SUDO_CONV_INFO_MSG, "\n"); + } + break; + case T_LOGPRI: + if (cur->sd_un.ival) { + sudo_printf(SUDO_CONV_INFO_MSG, desc, + sudo_logpri2str(cur->sd_un.ival)); + sudo_printf(SUDO_CONV_INFO_MSG, "\n"); + } + break; + case T_INT: + sudo_printf(SUDO_CONV_INFO_MSG, desc, cur->sd_un.ival); + sudo_printf(SUDO_CONV_INFO_MSG, "\n"); + break; + case T_UINT: + sudo_printf(SUDO_CONV_INFO_MSG, desc, cur->sd_un.uival); + sudo_printf(SUDO_CONV_INFO_MSG, "\n"); + break; + case T_TIMESPEC: { + /* display timespec in minutes as a double */ + double d = (double)cur->sd_un.tspec.tv_sec + + ((double)cur->sd_un.tspec.tv_nsec / 1000000000.0); + sudo_printf(SUDO_CONV_INFO_MSG, desc, d / 60.0); + sudo_printf(SUDO_CONV_INFO_MSG, "\n"); + break; + } + case T_MODE: + sudo_printf(SUDO_CONV_INFO_MSG, desc, cur->sd_un.mode); + sudo_printf(SUDO_CONV_INFO_MSG, "\n"); + break; + case T_LIST: + if (!SLIST_EMPTY(&cur->sd_un.list)) { + sudo_printf(SUDO_CONV_INFO_MSG, "%s\n", desc); + SLIST_FOREACH(item, &cur->sd_un.list, entries) { + sudo_printf(SUDO_CONV_INFO_MSG, + "\t%s\n", item->value); + } + } + break; + case T_TIMEOUT: + if (cur->sd_un.ival) { + sudo_printf(SUDO_CONV_INFO_MSG, desc, + cur->sd_un.ival); + sudo_printf(SUDO_CONV_INFO_MSG, "\n"); + } + break; + case T_TUPLE: + for (def = cur->values; def->sval; def++) { + if (cur->sd_un.tuple == def->nval) { + sudo_printf(SUDO_CONV_INFO_MSG, desc, def->sval); + break; + } + } + sudo_printf(SUDO_CONV_INFO_MSG, "\n"); + break; + } + } + } + debug_return; +} + +static bool +defaults_warnx(const struct sudoers_context *ctx, const char *file, int line, + int column, bool quiet, const char * restrict fmt, ...) +{ + va_list ap; + bool ret; + debug_decl(defaults_warnx, SUDOERS_DEBUG_DEFAULTS); + + va_start(ap, fmt); + ret = parser_vwarnx(ctx, file, line, column, true, quiet, fmt, ap); + va_end(ap); + + debug_return_bool(ret); +} + +/* + * Find the index of the specified Defaults name in sudo_defs_table[] + * On success, returns the matching index or -1 on failure. + */ +static int +find_default(const struct sudoers_context *ctx, const char *name, + const char *file, int line, int column, bool quiet) +{ + int i; + debug_decl(find_default, SUDOERS_DEBUG_DEFAULTS); + + for (i = 0; sudo_defs_table[i].name != NULL; i++) { + if (strcmp(name, sudo_defs_table[i].name) == 0) + debug_return_int(i); + } + if (!def_ignore_unknown_defaults) { + defaults_warnx(ctx, file, line, column, quiet, + N_("unknown defaults entry \"%s\""), name); + } + debug_return_int(-1); +} + +/* + * Parse a defaults entry, storing the parsed entry in sd_un. + * Returns true on success or false on failure. + */ +static bool +parse_default_entry(const struct sudoers_context *ctx, + struct sudo_defs_types *def, const char *val, int op, + const char *file, int line, int column, bool quiet) +{ + int rc; + debug_decl(parse_default_entry, SUDOERS_DEBUG_DEFAULTS); + + if (file == NULL) + file = "front-end"; + + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %s:%d:%d: %s=%s op=%d", + __func__, file, line, column, def->name, val ? val : "", op); + + /* + * If no value specified, the boolean flag must be set for non-flags. + * Only flags and tuples support boolean "true". + */ + if (val == NULL) { + switch (def->type & T_MASK) { + case T_LOGFAC: + if (op == true) { + /* Use default syslog facility if none specified. */ + val = LOGFAC; + } + break; + case T_FLAG: + break; + case T_TUPLE: + if (ISSET(def->type, T_BOOL)) + break; + FALLTHROUGH; + default: + if (!ISSET(def->type, T_BOOL) || op != false) { + defaults_warnx(ctx, file, line, column, quiet, + N_("no value specified for \"%s\""), def->name); + debug_return_bool(false); + } + } + } + + /* Only lists support append/remove. */ + if ((op == '+' || op == '-') && (def->type & T_MASK) != T_LIST) { + defaults_warnx(ctx, file, line, column, quiet, + N_("invalid operator \"%c=\" for \"%s\""), op, def->name); + debug_return_bool(false); + } + + switch (def->type & T_MASK) { + case T_LOGFAC: + rc = store_syslogfac(val, def); + break; + case T_LOGPRI: + rc = store_syslogpri(val, def); + break; + case T_STR: + if (val != NULL && ISSET(def->type, T_PATH|T_CHPATH)) { + if (!valid_path(ctx, def, val, file, line, column, quiet)) { + rc = -1; + break; + } + } + rc = store_str(val, def); + break; + case T_INT: + rc = store_int(val, def); + break; + case T_UINT: + rc = store_uint(val, def); + break; + case T_MODE: + rc = store_mode(val, def); + break; + case T_FLAG: + if (val != NULL) { + defaults_warnx(ctx, file, line, column, quiet, + N_("option \"%s\" does not take a value"), def->name); + rc = -1; + break; + } + def->sd_un.flag = (bool)op; + rc = true; + break; + case T_LIST: + rc = store_list(val, def, op); + break; + case T_TIMEOUT: + rc = store_timeout(val, def); + break; + case T_TUPLE: + rc = store_tuple(val, def, op); + break; + case T_TIMESPEC: + rc = store_timespec(val, def); + break; + case T_PLUGIN: + rc = store_plugin(val, def, op); + break; + case T_RLIMIT: + rc = store_rlimit(val, def); + break; + default: + defaults_warnx(ctx, file, line, column, quiet, + N_("invalid Defaults type 0x%x for option \"%s\""), + def->type, def->name); + rc = -1; + break; + } + if (rc == false) { + defaults_warnx(ctx, file, line, column, quiet, + N_("value \"%s\" is invalid for option \"%s\""), val, def->name); + } + + debug_return_bool(rc == true); +} + +static struct early_default * +is_early_default(const char *name) +{ + struct early_default *early; + debug_decl(is_early_default, SUDOERS_DEBUG_DEFAULTS); + + for (early = early_defaults; early->idx != -1; early++) { + if (strcmp(name, sudo_defs_table[early->idx].name) == 0) + debug_return_ptr(early); + } + debug_return_ptr(NULL); +} + +static bool +run_callback(struct sudoers_context *ctx, const char *file, int line, + int column, struct sudo_defs_types *def, int op) +{ + debug_decl(run_callback, SUDOERS_DEBUG_DEFAULTS); + + if (def->callback == NULL) + debug_return_bool(true); + debug_return_bool(def->callback(ctx, file, line, column, &def->sd_un, op)); +} + +/* + * Sets/clears an entry in the defaults structure. + * Runs the callback if present on success. + */ +bool +set_default(struct sudoers_context *ctx, const char *var, const char *val, + int op, const char *file, int line, int column, bool quiet) +{ + int idx; + debug_decl(set_default, SUDOERS_DEBUG_DEFAULTS); + + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "%s: setting Defaults %s -> %s", __func__, var, val ? val : "false"); + + idx = find_default(ctx, var, file, line, column, quiet); + if (idx != -1) { + /* Set parsed value in sudo_defs_table and run callback (if any). */ + struct sudo_defs_types *def = &sudo_defs_table[idx]; + if (parse_default_entry(ctx, def, val, op, file, line, column, quiet)) + debug_return_bool(run_callback(ctx, file, line, column, def, op)); + } + debug_return_bool(false); +} + +/* + * Like set_default() but stores the matching default value + * and does not run callbacks. + */ +static bool +set_early_default(const struct sudoers_context *ctx, const char *var, + const char *val, int op, const char *file, int line, int column, + bool quiet, struct early_default *early) +{ + int idx; + debug_decl(set_early_default, SUDOERS_DEBUG_DEFAULTS); + + idx = find_default(ctx, var, file, line, column, quiet); + if (idx != -1) { + /* Set parsed value in sudo_defs_table but defer callback (if any). */ + struct sudo_defs_types *def = &sudo_defs_table[idx]; + if (parse_default_entry(ctx, def, val, op, file, line, column, quiet)) { + if (early->file != NULL) + sudo_rcstr_delref(early->file); + early->file = sudo_rcstr_addref(file); + early->line = line; + early->column = column; + early->run_callback = true; + debug_return_bool(true); + } + } + debug_return_bool(false); +} + +/* + * Run callbacks for early defaults. + */ +static bool +run_early_defaults(struct sudoers_context *ctx) +{ + struct early_default *early; + bool ret = true; + debug_decl(run_early_defaults, SUDOERS_DEBUG_DEFAULTS); + + for (early = early_defaults; early->idx != -1; early++) { + if (early->run_callback) { + if (!run_callback(ctx, early->file, early->line, early->column, + &sudo_defs_table[early->idx], true)) + ret = false; + early->run_callback = false; + } + } + debug_return_bool(ret); +} + +static void +free_defs_val(int type, union sudo_defs_val *sd_un) +{ + switch (type & T_MASK) { + case T_STR: + case T_RLIMIT: + free(sd_un->str); + break; + case T_LIST: + (void)list_op(NULL, 0, &sd_un->list, freeall); + break; + } + memset(sd_un, 0, sizeof(*sd_un)); +} + +static bool +init_passprompt_regex(void) +{ + struct list_member *lm; + debug_decl(init_passprompt_regex, SUDOERS_DEBUG_DEFAULTS); + + /* Add initial defaults setting. */ + lm = calloc(1, sizeof(struct list_member)); + if (lm == NULL || (lm->value = strdup(PASSPROMPT_REGEX)) == NULL) { + free(lm); + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_bool(false); + } + SLIST_INSERT_HEAD(&def_passprompt_regex, lm, entries); + + debug_return_bool(true); +} + +/* + * Set default options to compiled-in values. + * Any of these may be overridden at runtime by a "Defaults" file. + */ +bool +init_defaults(void) +{ + static bool firsttime = true; + struct sudo_defs_types *def; + debug_decl(init_defaults, SUDOERS_DEBUG_DEFAULTS); + + /* Clear any old settings. */ + if (!firsttime) { + for (def = sudo_defs_table; def->name != NULL; def++) + free_defs_val(def->type, &def->sd_un); + } + + /* First initialize the flags. */ +#ifdef LONG_OTP_PROMPT + def_long_otp_prompt = true; +#endif +#ifdef IGNORE_DOT_PATH + def_ignore_dot = true; +#endif +#ifdef ALWAYS_SEND_MAIL + def_mail_always = true; +#endif +#ifdef SEND_MAIL_WHEN_NO_USER + def_mail_no_user = true; +#endif +#ifdef SEND_MAIL_WHEN_NO_HOST + def_mail_no_host = true; +#endif +#ifdef SEND_MAIL_WHEN_NOT_OK + def_mail_no_perms = true; +#endif +#ifndef NO_LECTURE + def_lecture = once; +#endif +#ifndef NO_AUTHENTICATION + def_authenticate = true; +#endif +#ifndef NO_ROOT_SUDO + def_root_sudo = true; +#endif +#ifdef HOST_IN_LOG + def_log_host = true; +#endif +#ifdef SHELL_IF_NO_ARGS + def_shell_noargs = true; +#endif +#ifdef SHELL_SETS_HOME + def_set_home = true; +#endif +#ifndef DONT_LEAK_PATH_INFO + def_path_info = true; +#endif +#ifdef USE_INSULTS + def_insults = true; +#endif +#ifdef FQDN + def_fqdn = true; +#endif +#ifdef ENV_EDITOR + def_env_editor = true; +#endif +#ifdef UMASK_OVERRIDE + def_umask_override = true; +#endif +#ifdef SUDOERS_NAME_MATCH + def_fast_glob = true; + def_fdexec = never; +#else + def_fdexec = digest_only; +#endif + def_timestamp_type = TIMESTAMP_TYPE; + if ((def_iolog_file = strdup(IOLOG_FILE)) == NULL) + goto oom; + if ((def_iolog_dir = strdup(_PATH_SUDO_IO_LOGDIR)) == NULL) + goto oom; + if ((def_sudoers_locale = strdup("C")) == NULL) + goto oom; + def_env_reset = ENV_RESET; + def_set_logname = true; + def_closefrom = STDERR_FILENO + 1; + def_pam_ruser = true; +#ifdef __sun__ + def_pam_rhost = true; +#endif + if ((def_pam_service = strdup("sudo")) == NULL) + goto oom; +#ifdef HAVE_PAM_LOGIN + if ((def_pam_login_service = strdup("sudo-i")) == NULL) + goto oom; +#else + if ((def_pam_login_service = strdup("sudo")) == NULL) + goto oom; +#endif +#ifdef NO_PAM_SESSION + def_pam_session = false; +#else + def_pam_session = true; +#endif +#ifdef HAVE_SELINUX + def_selinux = true; +#endif +#ifdef _PATH_SUDO_ADMIN_FLAG + if ((def_admin_flag = strdup(_PATH_SUDO_ADMIN_FLAG)) == NULL) + goto oom; +#endif + if ((def_rlimit_core = strdup("0,0")) == NULL) + goto oom; + def_intercept_type = dso; + def_intercept_verify = true; + def_use_netgroups = true; + def_netgroup_tuple = false; + def_sudoedit_checkdir = true; + def_iolog_mode = S_IRUSR|S_IWUSR; + def_log_allowed = true; + def_log_denied = true; + def_log_format = sudo; + def_runas_allow_unknown_id = false; + def_noninteractive_auth = false; + def_use_pty = true; + + /* Syslog options need special care since they both strings and ints */ +#if (LOGGING & SLOG_SYSLOG) + (void) store_syslogfac(LOGFAC, &sudo_defs_table[I_SYSLOG]); + (void) store_syslogpri(PRI_SUCCESS, &sudo_defs_table[I_SYSLOG_GOODPRI]); + (void) store_syslogpri(PRI_FAILURE, &sudo_defs_table[I_SYSLOG_BADPRI]); +#endif + + /* Password flags also have a string and integer component. */ + (void) store_tuple("any", &sudo_defs_table[I_LISTPW], 0); + (void) store_tuple("all", &sudo_defs_table[I_VERIFYPW], 0); + + /* Then initialize the int-like things. */ +#ifdef SUDO_UMASK + def_umask = SUDO_UMASK; +#else + def_umask = ACCESSPERMS; +#endif + def_loglinelen = MAXLOGFILELEN; + def_timestamp_timeout.tv_sec = TIMEOUT * 60; + def_passwd_timeout.tv_sec = PASSWORD_TIMEOUT * 60; + def_passwd_tries = TRIES_FOR_PASSWORD; +#ifdef HAVE_ZLIB_H + def_compress_io = true; +#endif + def_ignore_audit_errors = true; + def_ignore_iolog_errors = false; + def_ignore_logfile_errors = true; + def_log_passwords = true; +#ifdef SUDOERS_LOG_CLIENT + def_log_server_timeout = 30; + def_log_server_verify = true; + def_log_server_keepalive = true; +#endif + + /* Now do the strings */ + if ((def_mailto = strdup(MAILTO)) == NULL) + goto oom; + if ((def_mailsub = strdup(N_(MAILSUBJECT))) == NULL) + goto oom; + if ((def_badpass_message = strdup(_(INCORRECT_PASSWORD))) == NULL) + goto oom; +#ifdef _PATH_SUDO_LECTURE_DIR + if ((def_lecture_status_dir = strdup(_PATH_SUDO_LECTURE_DIR)) == NULL) + goto oom; +#endif +#ifdef _PATH_SUDO_TIMEDIR + if ((def_timestampdir = strdup(_PATH_SUDO_TIMEDIR)) == NULL) + goto oom; +#endif + if ((def_passprompt = strdup(_(PASSPROMPT))) == NULL) + goto oom; + if ((def_runas_default = strdup(RUNAS_DEFAULT)) == NULL) + goto oom; +#ifdef _PATH_SUDO_SENDMAIL + if ((def_mailerpath = strdup(_PATH_SUDO_SENDMAIL)) == NULL) + goto oom; +#endif + if ((def_mailerflags = strdup("-t")) == NULL) + goto oom; +#if (LOGGING & SLOG_FILE) + if ((def_logfile = strdup(_PATH_SUDO_LOGFILE)) == NULL) + goto oom; +#endif +#ifdef EXEMPTGROUP + if ((def_exempt_group = strdup(EXEMPTGROUP)) == NULL) + goto oom; +#endif +#ifdef SECURE_PATH + if ((def_secure_path = strdup(SECURE_PATH)) == NULL) + goto oom; +#endif + if ((def_editor = strdup(EDITOR)) == NULL) + goto oom; + def_set_utmp = true; + def_pam_acct_mgmt = true; + def_pam_setcred = true; + def_syslog_maxlen = MAXSYSLOGLEN; + def_case_insensitive_user = true; + def_case_insensitive_group = true; + + /* Reset the locale. */ + if (!firsttime) { + if (!sudoers_initlocale(NULL, def_sudoers_locale)) + goto oom; + } + + /* Finally do the lists (currently just environment tables). */ + if (!init_envtables()) + goto oom; + + /* Init eventlog config. */ + init_eventlog_config(); + + /* Initial iolog password prompt regex. */ + if (!init_passprompt_regex()) + debug_return_bool(false); + + firsttime = false; + + debug_return_bool(true); +oom: + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_bool(false); +} + +/* + * Check whether a defaults entry matches the specified type. + * Returns true if it matches, else false. + */ +static bool +default_type_matches(struct defaults *d, int what) +{ + debug_decl(default_type_matches, SUDOERS_DEBUG_DEFAULTS); + + switch (d->type) { + case DEFAULTS: + if (ISSET(what, SETDEF_GENERIC)) + debug_return_bool(true); + break; + case DEFAULTS_USER: + if (ISSET(what, SETDEF_USER)) + debug_return_bool(true); + break; + case DEFAULTS_RUNAS: + if (ISSET(what, SETDEF_RUNAS)) + debug_return_bool(true); + break; + case DEFAULTS_HOST: + if (ISSET(what, SETDEF_HOST)) + debug_return_bool(true); + break; + case DEFAULTS_CMND: + if (ISSET(what, SETDEF_CMND)) + debug_return_bool(true); + break; + } + debug_return_bool(false); +} + +/* + * Check whether a defaults entry's binding matches. + * Returns true if it matches, else false. + */ +static bool +default_binding_matches(const struct sudoers_context *ctx, + struct sudoers_parse_tree *parse_tree, struct defaults *d, int what) +{ + debug_decl(default_binding_matches, SUDOERS_DEBUG_DEFAULTS); + + switch (d->type) { + case DEFAULTS: + debug_return_bool(true); + case DEFAULTS_USER: + if (userlist_matches(parse_tree, ctx->user.pw, &d->binding->members) == ALLOW) + debug_return_bool(true); + break; + case DEFAULTS_RUNAS: + if (runaslist_matches(parse_tree, &d->binding->members, NULL, NULL, NULL) == ALLOW) + debug_return_bool(true); + break; + case DEFAULTS_HOST: + if (hostlist_matches(parse_tree, ctx->user.pw, &d->binding->members) == ALLOW) + debug_return_bool(true); + break; + case DEFAULTS_CMND: + if (cmndlist_matches(parse_tree, &d->binding->members, NULL, NULL) == ALLOW) + debug_return_bool(true); + break; + } + debug_return_bool(false); +} + +/* + * Update the global defaults based on the given defaults list. + * Pass in an OR'd list of which default types to update. + */ +bool +update_defaults(struct sudoers_context *ctx, + struct sudoers_parse_tree *parse_tree, + struct defaults_list *defs, int what, bool quiet) +{ + struct defaults *d; + bool global_defaults = false; + bool ret = true; + debug_decl(update_defaults, SUDOERS_DEBUG_DEFAULTS); + + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "what: 0x%02x", what); + + /* If no defaults list specified, use the global one in the parse tree. */ + if (defs == NULL) { + defs = &parse_tree->defaults; + global_defaults = true; + } + + /* + * If using the global defaults list, apply Defaults values marked as early. + */ + if (global_defaults) { + TAILQ_FOREACH(d, defs, entries) { + struct early_default *early = is_early_default(d->var); + if (early == NULL) + continue; + + /* Defaults type and binding must match. */ + if (!default_type_matches(d, what) || + !default_binding_matches(ctx, parse_tree, d, what)) + continue; + + /* Copy the value to sudo_defs_table and mark as early. */ + if (!set_early_default(ctx, d->var, d->val, d->op, d->file, d->line, + d->column, quiet, early)) + ret = false; + } + + /* Run callbacks for early defaults (if any) */ + if (!run_early_defaults(ctx)) + ret = false; + } + + /* + * Set the rest of the defaults and run their callbacks, if any. + */ + TAILQ_FOREACH(d, defs, entries) { + if (global_defaults) { + /* Skip Defaults marked as early, we already did them. */ + if (is_early_default(d->var)) + continue; + } + + /* Defaults type and binding must match. */ + if (!default_type_matches(d, what) || + !default_binding_matches(ctx, parse_tree, d, what)) + continue; + + /* Copy the value to sudo_defs_table and run callback (if any) */ + if (!set_default(ctx, d->var, d->val, d->op, d->file, d->line, d->column, quiet)) + ret = false; + } + + debug_return_bool(ret); +} + +/* + * Check all defaults entries without actually setting them. + */ +bool +check_defaults(const struct sudoers_parse_tree *parse_tree, bool quiet) +{ + struct defaults *d; + bool ret = true; + int idx; + debug_decl(check_defaults, SUDOERS_DEBUG_DEFAULTS); + + TAILQ_FOREACH(d, &parse_tree->defaults, entries) { + idx = find_default(parse_tree->ctx, d->var, d->file, d->line, + d->column, quiet); + if (idx != -1) { + struct sudo_defs_types def = sudo_defs_table[idx]; + memset(&def.sd_un, 0, sizeof(def.sd_un)); + if (parse_default_entry(parse_tree->ctx, &def, d->val, d->op, + d->file, d->line, d->column, quiet)) { + free_defs_val(def.type, &def.sd_un); + continue; + } + } + /* There was an error in the entry. */ + ret = false; + } + debug_return_bool(ret); +} + +static bool +store_int(const char *str, struct sudo_defs_types *def) +{ + const char *errstr; + int i; + debug_decl(store_int, SUDOERS_DEBUG_DEFAULTS); + + if (str == NULL) { + def->sd_un.ival = 0; + } else { + i = (int)sudo_strtonum(str, INT_MIN, INT_MAX, &errstr); + if (errstr != NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: %s", str, errstr); + debug_return_bool(false); + } + def->sd_un.ival = i; + } + debug_return_bool(true); +} + +static bool +store_uint(const char *str, struct sudo_defs_types *def) +{ + const char *errstr; + unsigned int u; + debug_decl(store_uint, SUDOERS_DEBUG_DEFAULTS); + + if (str == NULL) { + def->sd_un.uival = 0; + } else { + u = (unsigned int)sudo_strtonum(str, 0, UINT_MAX, &errstr); + if (errstr != NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: %s", str, errstr); + debug_return_bool(false); + } + def->sd_un.uival = u; + } + debug_return_bool(true); +} + +/* Check resource limit syntax, does not save as rlim_t. */ +static bool +check_rlimit(const char *str, bool soft) +{ + const size_t inflen = sizeof("infinity") - 1; + debug_decl(check_rlimit, SUDOERS_DEBUG_DEFAULTS); + + if (isdigit((unsigned char)*str)) { + unsigned long long ullval; + char *ep; + + errno = 0; +#ifdef HAVE_STRTOULL + ullval = strtoull(str, &ep, 10); + if (str == ep || (errno == ERANGE && ullval == ULLONG_MAX)) + debug_return_bool(false); +#else + ullval = strtoul(str, &ep, 10); + if (str == ep || (errno == ERANGE && ullval == ULONG_MAX)) + debug_return_bool(false); +#endif + if (*ep == '\0' || (soft && *ep == ',')) + debug_return_bool(true); + debug_return_bool(false); + } + if (strncmp(str, "infinity", inflen) == 0) { + if (str[inflen] == '\0' || (soft && str[inflen] == ',')) + debug_return_bool(true); + } + debug_return_bool(false); +} + +static bool +store_rlimit(const char *str, struct sudo_defs_types *def) +{ + debug_decl(store_rlimit, SUDOERS_DEBUG_DEFAULTS); + + /* The special values "user" and "default" are not compound. */ + if (str != NULL && strcmp(str, "user") != 0 && strcmp(str, "default") != 0) { + const char *hard, *soft = str; + /* + * Expect a limit in the form "soft,hard" or "limit" (both soft+hard). + */ + hard = strchr(str, ','); + if (hard != NULL) + hard++; + else + hard = soft; + + if (!check_rlimit(soft, true)) + debug_return_bool(false); + if (!check_rlimit(hard, false)) + debug_return_bool(false); + } + + /* Store as string, front-end will parse it as a limit. */ + debug_return_bool(store_str(str, def)); +} + +static bool +store_timespec(const char *str, struct sudo_defs_types *def) +{ + struct timespec ts; + char sign = '+'; + long i; + debug_decl(store_timespec, SUDOERS_DEBUG_DEFAULTS); + + sudo_timespecclear(&ts); + if (str != NULL) { + /* Convert from minutes to seconds. */ + if (*str == '+' || *str == '-') + sign = *str++; + while (*str != '\0' && *str != '.') { + if (!isdigit((unsigned char)*str)) + debug_return_bool(false); /* invalid number */ + + /* Verify (ts.tv_sec * 10) + (digit * 60) <= TIME_T_MAX. */ + i = (*str++ - '0') * 60L; + if (ts.tv_sec > (TIME_T_MAX - i) / 10) + debug_return_bool(false); /* overflow */ + ts.tv_sec *= 10; + ts.tv_sec += i; + } + if (*str++ == '.') { + long long nsec = 0; + + /* Convert optional fractional component to seconds and nanosecs. */ + for (i = 100000000; i > 0; i /= 10) { + if (*str == '\0') + break; + if (!isdigit((unsigned char)*str)) + debug_return_bool(false); /* invalid number */ + nsec += i * (*str++ - '0') * 60LL; + } + while (nsec >= 1000000000) { + if (ts.tv_sec == TIME_T_MAX) + debug_return_bool(false); /* overflow */ + ts.tv_sec++; + nsec -= 1000000000; + } + ts.tv_nsec = (long)nsec; + } + } + if (sign == '-') { + def->sd_un.tspec.tv_sec = -ts.tv_sec; + def->sd_un.tspec.tv_nsec = -ts.tv_nsec; + } else { + def->sd_un.tspec.tv_sec = ts.tv_sec; + def->sd_un.tspec.tv_nsec = ts.tv_nsec; + } + debug_return_bool(true); +} + +static bool +store_tuple(const char *str, struct sudo_defs_types *def, int op) +{ + struct def_values *v; + debug_decl(store_tuple, SUDOERS_DEBUG_DEFAULTS); + + /* + * Look up tuple value by name to find enum def_tuple value. + * A tuple must have at least two possible values. + */ + if (str == NULL) { + /* + * Boolean context: true maps to values[1], false maps to values[0]. + */ + if (op == true) { + v = &def->values[1]; + def->sd_un.ival = v->nval; + } else if (op == false) { + v = &def->values[0]; + def->sd_un.ival = v->nval; + } else { + debug_return_bool(false); + } + } else { + for (v = def->values; v->sval != NULL; v++) { + if (strcmp(v->sval, str) == 0) { + def->sd_un.tuple = v->nval; + break; + } + } + if (v->sval == NULL) + debug_return_bool(false); + } + debug_return_bool(true); +} + +static int +store_str(const char *str, struct sudo_defs_types *def) +{ + debug_decl(store_str, SUDOERS_DEBUG_DEFAULTS); + + free(def->sd_un.str); + if (str == NULL) { + def->sd_un.str = NULL; + } else { + if ((def->sd_un.str = strdup(str)) == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_int(-1); + } + } + debug_return_int(true); +} + +static bool +store_list(const char *str, struct sudo_defs_types *def, int op) +{ + debug_decl(store_list, SUDOERS_DEBUG_DEFAULTS); + + /* Remove all old members. */ + if (op == false || op == true) + (void)list_op(NULL, 0, &def->sd_un.list, freeall); + + /* Split str into multiple space-separated words and act on each one. */ + if (str != NULL) { + const char *cp, *ep; + const char *end = str + strlen(str); + const enum list_ops lop = op == '-' ? delete : add; + + if (ISSET(def->type, T_SPACE)) { + if (!list_op(str, strlen(str), &def->sd_un.list, lop)) + debug_return_bool(false); + } else { + for (cp = sudo_strsplit(str, end, " \t", &ep); cp != NULL; + cp = sudo_strsplit(NULL, end, " \t", &ep)) { + if (!list_op(cp, (size_t)(ep - cp), &def->sd_un.list, lop)) + debug_return_bool(false); + } + } + } + debug_return_bool(true); +} + +static bool +store_plugin(const char *str, struct sudo_defs_types *def, int op) +{ + const enum list_ops lop = op == '-' ? delete : add; + debug_decl(store_plugin, SUDOERS_DEBUG_DEFAULTS); + + /* Remove all old members. */ + if (op == false || op == true) + (void)list_op(NULL, 0, &def->sd_un.list, freeall); + + if (str != NULL) { + if (!list_op(str, strlen(str), &def->sd_un.list, lop)) + debug_return_bool(false); + } + + debug_return_bool(true); +} + +static bool +store_syslogfac(const char *str, struct sudo_defs_types *def) +{ + debug_decl(store_syslogfac, SUDOERS_DEBUG_DEFAULTS); + + if (str == NULL) { + def->sd_un.ival = false; + debug_return_bool(true); + } + debug_return_bool(sudo_str2logfac(str, &def->sd_un.ival)); +} + +static bool +store_syslogpri(const char *str, struct sudo_defs_types *def) +{ + debug_decl(store_syslogpri, SUDOERS_DEBUG_DEFAULTS); + + if (str == NULL) { + def->sd_un.ival = -1; + debug_return_bool(true); + } + debug_return_bool(sudo_str2logpri(str, &def->sd_un.ival)); +} + +static bool +store_mode(const char *str, struct sudo_defs_types *def) +{ + mode_t mode; + const char *errstr; + debug_decl(store_mode, SUDOERS_DEBUG_DEFAULTS); + + if (str == NULL) { + def->sd_un.mode = ACCESSPERMS; + } else { + mode = sudo_strtomode(str, &errstr); + if (errstr != NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s is %s", str, errstr); + debug_return_bool(false); + } + def->sd_un.mode = mode; + } + debug_return_bool(true); +} + +static bool +store_timeout(const char *str, struct sudo_defs_types *def) +{ + debug_decl(store_mode, SUDOERS_DEBUG_DEFAULTS); + + if (str == NULL) { + def->sd_un.ival = 0; + } else { + int seconds = parse_timeout(str); + if (seconds == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO, + "%s", str); + debug_return_bool(false); + } + def->sd_un.ival = seconds; + } + debug_return_bool(true); +} + +static bool +valid_path(const struct sudoers_context *ctx, struct sudo_defs_types *def, + const char *val, const char *file, int line, int column, bool quiet) +{ + bool ret = true; + debug_decl(valid_path, SUDOERS_DEBUG_DEFAULTS); + + if (strlen(val) >= PATH_MAX) { + defaults_warnx(ctx, file, line, column, quiet, + N_("path name for \"%s\" too long"), def->name); + ret = false; + } + if (ISSET(def->type, T_CHPATH)) { + if (val[0] != '/' && val[0] != '~' && (val[0] != '*' || val[1] != '\0')) { + defaults_warnx(ctx, file, line, column, quiet, + N_("values for \"%s\" must start with a '/', '~', or '*'"), + def->name); + ret = false; + } + } else { + if (val[0] != '/') { + defaults_warnx(ctx, file, line, column, quiet, + N_("values for \"%s\" must start with a '/'"), def->name); + ret = false; + } + + } + debug_return_bool(ret); +} + +static bool +list_op(const char *str, size_t len, struct list_members *list, + enum list_ops op) +{ + struct list_member *cur, *prev = NULL; + debug_decl(list_op, SUDOERS_DEBUG_DEFAULTS); + + if (op == freeall) { + while ((cur = SLIST_FIRST(list)) != NULL) { + SLIST_REMOVE_HEAD(list, entries); + free(cur->value); + free(cur); + } + debug_return_bool(true); + } + + SLIST_FOREACH(cur, list, entries) { + if ((strncmp(cur->value, str, len) == 0 && cur->value[len] == '\0')) { + + if (op == add) + debug_return_bool(true); /* already exists */ + + /* Delete node */ + if (prev == NULL) + SLIST_REMOVE_HEAD(list, entries); + else + SLIST_REMOVE_AFTER(prev, entries); + free(cur->value); + free(cur); + break; + } + prev = cur; + } + + /* Add new node to the head of the list. */ + if (op == add) { + cur = calloc(1, sizeof(struct list_member)); + if (cur == NULL || (cur->value = strndup(str, len)) == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + free(cur); + debug_return_bool(false); + } + SLIST_INSERT_HEAD(list, cur, entries); + } + debug_return_bool(true); +} + +bool +append_default(const char *var, const char *val, int op, + char *source, struct defaults_list *defs) +{ + struct defaults *def; + debug_decl(append_default, SUDOERS_DEBUG_DEFAULTS); + + if ((def = calloc(1, sizeof(*def))) == NULL) + goto oom; + + def->type = DEFAULTS; + def->op = op; + if ((def->var = strdup(var)) == NULL) { + goto oom; + } + if (val != NULL) { + if ((def->val = strdup(val)) == NULL) + goto oom; + } + def->file = source; + sudo_rcstr_addref(source); + TAILQ_INSERT_TAIL(defs, def, entries); + debug_return_bool(true); + +oom: + if (def != NULL) { + free(def->var); + free(def->val); + free(def); + } + debug_return_bool(false); +} + +bool +cb_passprompt_regex(struct sudoers_context *ctx, const char *file, + int line, int column, const union sudo_defs_val *sd_un, int op) +{ + struct list_member *lm; + const char *errstr; + debug_decl(cb_passprompt_regex, SUDOERS_DEBUG_DEFAULTS); + + /* If adding one or more regexps, make sure they are valid. */ + if (op == '+' || op == true) { + SLIST_FOREACH(lm, &sd_un->list, entries) { + if (!sudo_regex_compile(NULL, lm->value, &errstr)) { + defaults_warnx(ctx, file, line, column, false, + U_("invalid regular expression \"%s\": %s"), + lm->value, U_(errstr)); + debug_return_bool(false); + } + } + } + + debug_return_bool(true); +} |