diff --git a/plugins/sudoers/logging.c b/plugins/sudoers/logging.c index 4518fcb55..f276739b0 100644 --- a/plugins/sudoers/logging.c +++ b/plugins/sudoers/logging.c @@ -59,6 +59,14 @@ # include "strlist.h" #endif +struct parse_error { + STAILQ_ENTRY(parse_error) entries; + char *errstr; +}; +STAILQ_HEAD(parse_error_list, parse_error); +static struct parse_error_list parse_error_list = + STAILQ_HEAD_INITIALIZER(parse_error_list); + static bool should_mail(int); static bool warned = false; @@ -770,15 +778,91 @@ gai_log_warning(int flags, int errnum, const char *fmt, ...) } /* - * Log and mail a parse error using log_warningx(). + * Send mail about accumulated parser errors. + * Frees the list of parse errors when done. + */ +bool +mail_parse_errors(void) +{ + const int evl_flags = EVLOG_MAIL|EVLOG_MAIL_ONLY|EVLOG_RAW; + struct parse_error *pe; + struct eventlog evlog; + char *cp, *mailbody = NULL; + struct timespec now; + size_t len, n; + bool ret; + debug_decl(mail_parse_errors, SUDOERS_DEBUG_LOGGING); + + if (STAILQ_EMPTY(&parse_error_list)) + debug_return_bool(true); + + if (sudo_gettime_real(&now) == -1) { + sudo_warn("%s", U_("unable to get time of day")); + ret = false; + goto done; + } + sudoers_to_eventlog(&evlog, NewArgv, env_get(), sudo_user.uuid_str); + + len = sizeof(_("problem parsing sudoers")); + STAILQ_FOREACH(pe, &parse_error_list, entries) { + len += strlen(_(pe->errstr)) + 1; + } + mailbody = malloc(len); + if (mailbody == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + ret = false; + goto done; + } + cp = mailbody; + + n = strlcpy(cp, _("problem parsing sudoers"), len); + if (n >= len) { + sudo_warnx(U_("internal error, %s overflow"), __func__); + ret = false; + goto done; + } + cp += n; + len -= n; + + STAILQ_FOREACH(pe, &parse_error_list, entries) { + n = snprintf(cp, len, "\n%s", _(pe->errstr)); + if (n >= len) { + sudo_warnx(U_("internal error, %s overflow"), __func__); + ret = false; + goto done; + } + cp += n; + len -= n; + } + + ret = eventlog_alert(&evlog, evl_flags, &now, mailbody, NULL); + if (!log_server_alert(&evlog, &now, mailbody, NULL, + sudoers_policy.event_alloc)) { + ret = false; + } + +done: + free(mailbody); + while ((pe = STAILQ_FIRST(&parse_error_list)) != NULL) { + STAILQ_REMOVE_HEAD(&parse_error_list, entries); + free(pe->errstr); + free(pe); + } + debug_return_bool(ret); +} + +/* + * Log a parse error using log_warningx(). + * Journals the message to be mailed after parsing is complete. * Does not write the message to stderr. */ bool log_parse_error(const char *file, int line, int column, const char *fmt, va_list args) { - const int flags = SLOG_SEND_MAIL|SLOG_NO_STDERR; + const int flags = SLOG_RAW_MSG|SLOG_NO_STDERR; char *errstr, *tofree = NULL; + struct parse_error *pe; bool ret; debug_decl(log_parse_error, SUDOERS_DEBUG_LOGGING); @@ -791,10 +875,31 @@ log_parse_error(const char *file, int line, int column, const char *fmt, tofree = errstr; } - if (line > 0) - ret = log_warningx(flags, _("%s:%d:%d: %s"), file, line, column, errstr); - else - ret = log_warningx(flags, _("%s: %s"), file, errstr); + if (line > 0) { + ret = log_warningx(flags, N_("%s:%d:%d: %s"), file, line, column, + errstr); + } else { + ret = log_warningx(flags, N_("%s: %s"), file, errstr); + } + + /* Journal parse error for later mailing. */ + pe = malloc(sizeof(*pe)); + if (pe != NULL) { + int len; + + if (line > 0) { + len = asprintf(&pe->errstr, _("%s:%d:%d: %s"), file, line, column, + errstr); + } else { + len = asprintf(&pe->errstr, _("%s: %s"), file, errstr); + } + if (len != -1) { + STAILQ_INSERT_TAIL(&parse_error_list, pe, entries); + } else { + free(pe); + } + } + free(tofree); debug_return_bool(ret); diff --git a/plugins/sudoers/logging.h b/plugins/sudoers/logging.h index 79d0cb283..10599d643 100644 --- a/plugins/sudoers/logging.h +++ b/plugins/sudoers/logging.h @@ -89,5 +89,6 @@ void sudoers_to_eventlog(struct eventlog *evlog, char * const argv[], char *cons void init_eventlog_config(void); bool init_log_details(struct log_details *details, struct eventlog *evlog); bool log_parse_error(const char *file, int line, int column, const char *fmt, va_list ap) __printflike(4, 0); +bool mail_parse_errors(void); #endif /* SUDOERS_LOGGING_H */ diff --git a/plugins/sudoers/parse.c b/plugins/sudoers/parse.c index 80b685ff4..b2f09f540 100644 --- a/plugins/sudoers/parse.c +++ b/plugins/sudoers/parse.c @@ -434,7 +434,7 @@ sudoers_lookup(struct sudo_nss_list *snl, struct passwd *pw, int *cmnd_status, *cmnd_status = info.status; } if (defs != NULL) - update_defaults(parse_tree, defs, SETDEF_GENERIC, false); + (void)update_defaults(parse_tree, defs, SETDEF_GENERIC, false); if (!apply_cmndspec(cs)) SET(validated, VALIDATE_ERROR); else if (match == ALLOW) diff --git a/plugins/sudoers/regress/fuzz/fuzz_policy.c b/plugins/sudoers/regress/fuzz/fuzz_policy.c index 3a5f0478b..9b0c8ea3e 100644 --- a/plugins/sudoers/regress/fuzz/fuzz_policy.c +++ b/plugins/sudoers/regress/fuzz/fuzz_policy.c @@ -742,6 +742,13 @@ log_exit_status(int exit_status) return true; } +/* STUB */ +bool +mail_parse_errors(void) +{ + return true; +} + /* STUB */ bool log_parse_error(const char *file, int line, int column, const char *fmt, diff --git a/plugins/sudoers/regress/fuzz/fuzz_sudoers.c b/plugins/sudoers/regress/fuzz/fuzz_sudoers.c index 336b0c08f..8a7caeaae 100644 --- a/plugins/sudoers/regress/fuzz/fuzz_sudoers.c +++ b/plugins/sudoers/regress/fuzz/fuzz_sudoers.c @@ -108,6 +108,13 @@ set_cmnd_path(const char *runchroot) return NOT_FOUND; } +/* STUB */ +bool +mail_parse_errors(void) +{ + return true; +} + /* STUB */ bool log_warningx(int flags, const char *fmt, ...) diff --git a/plugins/sudoers/sudoers.c b/plugins/sudoers/sudoers.c index 7c2a9d84d..6036505b4 100644 --- a/plugins/sudoers/sudoers.c +++ b/plugins/sudoers/sudoers.c @@ -149,6 +149,12 @@ restore_nproc(void) #endif /* __linux__ */ } +/* + * Re-initialize Defaults settings. + * We do not send mail for errors when reinitializing, mail would have + * already been sent the first time. + * TODO: prevent Defaults error logging too + */ static bool sudoers_reinit_defaults(void) { @@ -161,21 +167,21 @@ sudoers_reinit_defaults(void) } if (!update_defaults(NULL, &initial_defaults, - SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER|SETDEF_RUNAS, false)) { + SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER|SETDEF_RUNAS, false)) debug_return_bool(false); - } TAILQ_FOREACH_SAFE(nss, snl, entries, nss_next) { + /* Missing/invalid defaults is not a fatal error. */ if (nss->getdefs(nss) == -1) { log_warningx(SLOG_SEND_MAIL|SLOG_NO_STDERR, N_("unable to get defaults from %s"), nss->source); + } else { + (void)update_defaults(nss->parse_tree, NULL, + SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER|SETDEF_RUNAS, false); } - /* Not a fatal error. */ - (void)update_defaults(nss->parse_tree, NULL, - SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER|SETDEF_RUNAS, false); } - debug_return_int(true); + debug_return_bool(true); } int @@ -223,33 +229,33 @@ sudoers_init(void *info, sudoers_logger_t logger, char * const envp[]) if (!set_perms(PERM_ROOT)) debug_return_int(-1); + /* Use the C locale unless another is specified in sudoers. */ + sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale); + sudo_warn_set_locale_func(sudoers_warn_setlocale); + /* Update defaults set by front-end. */ if (!update_defaults(NULL, &initial_defaults, SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER|SETDEF_RUNAS, false)) { - debug_return_int(-1); + goto cleanup; } - /* - * Open and parse sudoers, set global defaults. - * Uses the C locale unless another is specified in sudoers. - */ - sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale); - sudo_warn_set_locale_func(sudoers_warn_setlocale); + /* Open and parse sudoers, set global defaults. */ init_parser(sudoers_file, false, false); TAILQ_FOREACH_SAFE(nss, snl, entries, nss_next) { if (nss->open(nss) == -1 || (nss->parse_tree = nss->parse(nss)) == NULL) { TAILQ_REMOVE(snl, nss, entries); continue; } - sources++; + + /* Missing/invalid defaults is not a fatal error. */ if (nss->getdefs(nss) == -1) { log_warningx(SLOG_SEND_MAIL|SLOG_NO_STDERR, N_("unable to get defaults from %s"), nss->source); + } else { + (void)update_defaults(nss->parse_tree, NULL, + SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER|SETDEF_RUNAS, false); } - /* Not a fatal error. */ - (void)update_defaults(nss->parse_tree, NULL, - SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER|SETDEF_RUNAS, false); } if (sources == 0) { sudo_warnx("%s", U_("no valid sudoers sources found, quitting")); @@ -261,6 +267,8 @@ sudoers_init(void *info, sudoers_logger_t logger, char * const envp[]) ret = true; cleanup: + mail_parse_errors(); + if (!restore_perms()) ret = -1; @@ -806,6 +814,8 @@ bad: ret = false; done: + mail_parse_errors(); + if (def_group_plugin) group_plugin_unload(); init_parser(NULL, false, false); @@ -1046,7 +1056,7 @@ set_cmnd(void) } TAILQ_FOREACH(nss, snl, entries) { - /* Not a fatal error. */ + /* Missing/invalid defaults is not a fatal error. */ (void)update_defaults(nss->parse_tree, NULL, SETDEF_CMND, false); }