
sudoers now supports an APPARMOR_PROFILE option, which can be specified as e.g. alice ALL=(ALL:ALL) APPARMOR_PROFILE=foo ALL The line above says "user alice can run any command as any user/group, under confinement by the AppArmor profile 'foo'." Profiles can be specified in any way that complies with the rules of aa_change_profile(2). For instance, the sudoers configuration alice ALL=(ALL:ALL) APPARMOR_PROFILE=unconfined ALL allows alice to run any command unconfined (i.e., without an AppArmor profile), while alice ALL=(ALL:ALL) APPARMOR_PROFILE=foo//&bar ALL tells sudoers that alice can run any command under the stacked AppArmor profiles 'foo' and 'bar'. The intention of this option is to give sysadmins on Linux distros supporting AppArmor better options for fine-grained access control. Among other things, this option can enforce mandatory access control (MAC) over the operations that a privileged user is able to perform to ensure that they cannot privesc past the boundaries of a specified profile. It can also be used to limit which users are able to get unconfined system access, by enforcing a default AppArmor profile on all users and then specifying 'APPARMOR_PROFILE=unconfined' for a privileged subset of users.
1017 lines
30 KiB
C
1017 lines
30 KiB
C
/*
|
|
* SPDX-License-Identifier: ISC
|
|
*
|
|
* Copyright (c) 2004-2005, 2007-2021 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 <unistd.h>
|
|
#include <ctype.h>
|
|
#include <pwd.h>
|
|
|
|
#include "sudoers.h"
|
|
#include "sudo_lbuf.h"
|
|
#include <gram.h>
|
|
|
|
/*
|
|
* Look up the user in the sudoers parse tree for pseudo-commands like
|
|
* list, verify and kill.
|
|
*/
|
|
static int
|
|
sudoers_lookup_pseudo(struct sudo_nss_list *snl, struct passwd *pw,
|
|
int validated, int pwflag)
|
|
{
|
|
struct passwd *root_pw = NULL;
|
|
struct sudo_nss *nss;
|
|
struct cmndspec *cs;
|
|
struct privilege *priv;
|
|
struct userspec *us;
|
|
struct defaults *def;
|
|
int nopass, match = DENY;
|
|
enum def_tuple pwcheck;
|
|
debug_decl(sudoers_lookup_pseudo, SUDOERS_DEBUG_PARSER);
|
|
|
|
pwcheck = (pwflag == -1) ? never : sudo_defs_table[pwflag].sd_un.tuple;
|
|
nopass = (pwcheck == never || pwcheck == all) ? true : false;
|
|
|
|
CLR(validated, FLAG_NO_USER);
|
|
CLR(validated, FLAG_NO_HOST);
|
|
if (list_pw != NULL) {
|
|
root_pw = sudo_getpwuid(ROOT_UID);
|
|
if (root_pw == NULL)
|
|
log_warningx(SLOG_SEND_MAIL, N_("unknown uid %u"), ROOT_UID);
|
|
} else {
|
|
SET(validated, FLAG_NO_CHECK);
|
|
}
|
|
TAILQ_FOREACH(nss, snl, entries) {
|
|
if (nss->query(nss, pw) == -1) {
|
|
/* The query function should have printed an error message. */
|
|
SET(validated, VALIDATE_ERROR);
|
|
break;
|
|
}
|
|
TAILQ_FOREACH(us, &nss->parse_tree->userspecs, entries) {
|
|
if (userlist_matches(nss->parse_tree, pw, &us->users) != ALLOW)
|
|
continue;
|
|
TAILQ_FOREACH(priv, &us->privileges, entries) {
|
|
int priv_nopass = UNSPEC;
|
|
|
|
if (hostlist_matches(nss->parse_tree, pw, &priv->hostlist) != ALLOW)
|
|
continue;
|
|
TAILQ_FOREACH(def, &priv->defaults, entries) {
|
|
if (strcmp(def->var, "authenticate") == 0)
|
|
priv_nopass = !def->op;
|
|
}
|
|
TAILQ_FOREACH(cs, &priv->cmndlist, entries) {
|
|
if (pwcheck == any) {
|
|
if (cs->tags.nopasswd == true || priv_nopass == true)
|
|
nopass = true;
|
|
} else if (pwcheck == all) {
|
|
if (cs->tags.nopasswd != true && priv_nopass != true)
|
|
nopass = false;
|
|
}
|
|
if (match == ALLOW)
|
|
continue;
|
|
|
|
/* Only check runas/command when listing another user. */
|
|
if (user_uid == 0 || list_pw == NULL ||
|
|
user_uid == list_pw->pw_uid) {
|
|
match = ALLOW;
|
|
continue;
|
|
}
|
|
/* Runas user must match list user or root. */
|
|
if (userlist_matches(nss->parse_tree, list_pw,
|
|
cs->runasuserlist) == DENY) {
|
|
continue;
|
|
}
|
|
if (root_pw == NULL || userlist_matches(nss->parse_tree,
|
|
root_pw, cs->runasuserlist) != ALLOW) {
|
|
continue;
|
|
}
|
|
if (cmnd_matches(nss->parse_tree, cs->cmnd, cs->runchroot,
|
|
NULL) == ALLOW) {
|
|
match = ALLOW;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (root_pw != NULL)
|
|
sudo_pw_delref(root_pw);
|
|
if (match == ALLOW || user_uid == 0) {
|
|
/* User has an entry for this host. */
|
|
SET(validated, VALIDATE_SUCCESS);
|
|
} else if (match == DENY)
|
|
SET(validated, VALIDATE_FAILURE);
|
|
if (pwcheck == always && def_authenticate)
|
|
SET(validated, FLAG_CHECK_USER);
|
|
else if (nopass == true)
|
|
def_authenticate = false;
|
|
debug_return_int(validated);
|
|
}
|
|
|
|
static void
|
|
init_cmnd_info(struct cmnd_info *info)
|
|
{
|
|
memset(info, 0, sizeof(*info));
|
|
if (def_intercept || ISSET(sudo_mode, MODE_POLICY_INTERCEPTED))
|
|
info->intercepted = true;
|
|
}
|
|
|
|
static int
|
|
sudoers_lookup_check(struct sudo_nss *nss, struct passwd *pw,
|
|
int *validated, struct cmnd_info *info, struct cmndspec **matching_cs,
|
|
struct defaults_list **defs, time_t now)
|
|
{
|
|
int host_match, runas_match, cmnd_match;
|
|
struct cmndspec *cs;
|
|
struct privilege *priv;
|
|
struct userspec *us;
|
|
struct member *matching_user;
|
|
debug_decl(sudoers_lookup_check, SUDOERS_DEBUG_PARSER);
|
|
|
|
init_cmnd_info(info);
|
|
|
|
TAILQ_FOREACH_REVERSE(us, &nss->parse_tree->userspecs, userspec_list, entries) {
|
|
if (userlist_matches(nss->parse_tree, pw, &us->users) != ALLOW)
|
|
continue;
|
|
CLR(*validated, FLAG_NO_USER);
|
|
TAILQ_FOREACH_REVERSE(priv, &us->privileges, privilege_list, entries) {
|
|
host_match = hostlist_matches(nss->parse_tree, pw, &priv->hostlist);
|
|
if (host_match == ALLOW)
|
|
CLR(*validated, FLAG_NO_HOST);
|
|
else
|
|
continue;
|
|
TAILQ_FOREACH_REVERSE(cs, &priv->cmndlist, cmndspec_list, entries) {
|
|
if (cs->notbefore != UNSPEC) {
|
|
if (now < cs->notbefore)
|
|
continue;
|
|
}
|
|
if (cs->notafter != UNSPEC) {
|
|
if (now > cs->notafter)
|
|
continue;
|
|
}
|
|
matching_user = NULL;
|
|
runas_match = runaslist_matches(nss->parse_tree,
|
|
cs->runasuserlist, cs->runasgrouplist, &matching_user,
|
|
NULL);
|
|
if (runas_match == ALLOW) {
|
|
cmnd_match = cmnd_matches(nss->parse_tree, cs->cmnd,
|
|
cs->runchroot, info);
|
|
if (cmnd_match != UNSPEC) {
|
|
/*
|
|
* If user is running command as himself,
|
|
* set runas_pw = sudo_user.pw.
|
|
* XXX - hack, want more general solution
|
|
*/
|
|
if (matching_user && matching_user->type == MYSELF) {
|
|
sudo_pw_delref(runas_pw);
|
|
sudo_pw_addref(sudo_user.pw);
|
|
runas_pw = sudo_user.pw;
|
|
}
|
|
*matching_cs = cs;
|
|
*defs = &priv->defaults;
|
|
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
|
|
"userspec matched @ %s:%d:%d: %s",
|
|
us->file ? us->file : "???", us->line, us->column,
|
|
cmnd_match ? "allowed" : "denied");
|
|
debug_return_int(cmnd_match);
|
|
}
|
|
free(info->cmnd_path);
|
|
init_cmnd_info(info);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
debug_return_int(UNSPEC);
|
|
}
|
|
|
|
/*
|
|
* Apply cmndspec-specific settngs including SELinux role/type,
|
|
* Solaris privs, and command tags.
|
|
*/
|
|
static bool
|
|
apply_cmndspec(struct cmndspec *cs)
|
|
{
|
|
debug_decl(apply_cmndspec, SUDOERS_DEBUG_PARSER);
|
|
|
|
if (cs != NULL) {
|
|
#ifdef HAVE_SELINUX
|
|
/* Set role and type if not specified on command line. */
|
|
if (user_role == NULL) {
|
|
if (cs->role != NULL) {
|
|
user_role = strdup(cs->role);
|
|
if (user_role == NULL) {
|
|
sudo_warnx(U_("%s: %s"), __func__,
|
|
U_("unable to allocate memory"));
|
|
debug_return_bool(false);
|
|
}
|
|
} else {
|
|
user_role = def_role;
|
|
def_role = NULL;
|
|
}
|
|
if (user_role != NULL) {
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"user_role -> %s", user_role);
|
|
}
|
|
}
|
|
if (user_type == NULL) {
|
|
if (cs->type != NULL) {
|
|
user_type = strdup(cs->type);
|
|
if (user_type == NULL) {
|
|
sudo_warnx(U_("%s: %s"), __func__,
|
|
U_("unable to allocate memory"));
|
|
debug_return_bool(false);
|
|
}
|
|
} else {
|
|
user_type = def_type;
|
|
def_type = NULL;
|
|
}
|
|
if (user_type != NULL) {
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"user_type -> %s", user_type);
|
|
}
|
|
}
|
|
#endif /* HAVE_SELINUX */
|
|
#ifdef HAVE_APPARMOR
|
|
/* Set AppArmor profile, if specified */
|
|
if (cs->apparmor_profile != NULL) {
|
|
user_apparmor_profile = strdup(cs->apparmor_profile);
|
|
if (user_apparmor_profile == NULL) {
|
|
sudo_warnx(U_("%s: %s"), __func__,
|
|
U_("unable to allocate memory"));
|
|
debug_return_bool(false);
|
|
}
|
|
} else {
|
|
user_apparmor_profile = def_apparmor_profile;
|
|
def_apparmor_profile = NULL;
|
|
}
|
|
if (user_apparmor_profile != NULL) {
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"user_apparmor_profile -> %s", user_apparmor_profile);
|
|
}
|
|
#endif
|
|
#ifdef HAVE_PRIV_SET
|
|
/* Set Solaris privilege sets */
|
|
if (runas_privs == NULL) {
|
|
if (cs->privs != NULL) {
|
|
runas_privs = strdup(cs->privs);
|
|
if (runas_privs == NULL) {
|
|
sudo_warnx(U_("%s: %s"), __func__,
|
|
U_("unable to allocate memory"));
|
|
debug_return_bool(false);
|
|
}
|
|
} else {
|
|
runas_privs = def_privs;
|
|
def_privs = NULL;
|
|
}
|
|
if (runas_privs != NULL) {
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"runas_privs -> %s", runas_privs);
|
|
}
|
|
}
|
|
if (runas_limitprivs == NULL) {
|
|
if (cs->limitprivs != NULL) {
|
|
runas_limitprivs = strdup(cs->limitprivs);
|
|
if (runas_limitprivs == NULL) {
|
|
sudo_warnx(U_("%s: %s"), __func__,
|
|
U_("unable to allocate memory"));
|
|
debug_return_bool(false);
|
|
}
|
|
} else {
|
|
runas_limitprivs = def_limitprivs;
|
|
def_limitprivs = NULL;
|
|
}
|
|
if (runas_limitprivs != NULL) {
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"runas_limitprivs -> %s", runas_limitprivs);
|
|
}
|
|
}
|
|
#endif /* HAVE_PRIV_SET */
|
|
if (cs->timeout > 0) {
|
|
def_command_timeout = cs->timeout;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_command_timeout -> %d", def_command_timeout);
|
|
}
|
|
if (cs->runcwd != NULL) {
|
|
free(def_runcwd);
|
|
def_runcwd = strdup(cs->runcwd);
|
|
if (def_runcwd == NULL) {
|
|
sudo_warnx(U_("%s: %s"), __func__,
|
|
U_("unable to allocate memory"));
|
|
debug_return_bool(false);
|
|
}
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_runcwd -> %s", def_runcwd);
|
|
}
|
|
if (cs->runchroot != NULL) {
|
|
free(def_runchroot);
|
|
def_runchroot = strdup(cs->runchroot);
|
|
if (def_runchroot == NULL) {
|
|
sudo_warnx(U_("%s: %s"), __func__,
|
|
U_("unable to allocate memory"));
|
|
debug_return_bool(false);
|
|
}
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_runchroot -> %s", def_runchroot);
|
|
}
|
|
if (cs->tags.nopasswd != UNSPEC) {
|
|
def_authenticate = !cs->tags.nopasswd;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_authenticate -> %s", def_authenticate ? "true" : "false");
|
|
}
|
|
if (cs->tags.noexec != UNSPEC) {
|
|
def_noexec = cs->tags.noexec;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_noexec -> %s", def_noexec ? "true" : "false");
|
|
}
|
|
if (cs->tags.intercept != UNSPEC) {
|
|
def_intercept = cs->tags.intercept;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_intercept -> %s", def_intercept ? "true" : "false");
|
|
}
|
|
if (cs->tags.setenv != UNSPEC) {
|
|
def_setenv = cs->tags.setenv;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_setenv -> %s", def_setenv ? "true" : "false");
|
|
}
|
|
if (cs->tags.log_input != UNSPEC) {
|
|
def_log_input = cs->tags.log_input;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_log_input -> %s", def_log_input ? "true" : "false");
|
|
}
|
|
if (cs->tags.log_output != UNSPEC) {
|
|
def_log_output = cs->tags.log_output;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_log_output -> %s", def_log_output ? "true" : "false");
|
|
}
|
|
if (cs->tags.send_mail != UNSPEC) {
|
|
if (cs->tags.send_mail) {
|
|
def_mail_all_cmnds = true;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_mail_all_cmnds -> true");
|
|
} else {
|
|
def_mail_all_cmnds = false;
|
|
def_mail_always = false;
|
|
def_mail_no_perms = false;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_mail_all_cmnds -> false, def_mail_always -> false, "
|
|
"def_mail_no_perms -> false");
|
|
}
|
|
}
|
|
if (cs->tags.follow != UNSPEC) {
|
|
def_sudoedit_follow = cs->tags.follow;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_sudoedit_follow -> %s", def_sudoedit_follow ? "true" : "false");
|
|
}
|
|
}
|
|
|
|
debug_return_bool(true);
|
|
}
|
|
|
|
/*
|
|
* Look up the user in the sudoers parse tree and check to see if they are
|
|
* allowed to run the specified command on this host as the target user.
|
|
*/
|
|
int
|
|
sudoers_lookup(struct sudo_nss_list *snl, struct passwd *pw, int *cmnd_status,
|
|
int pwflag)
|
|
{
|
|
struct defaults_list *defs = NULL;
|
|
struct sudoers_parse_tree *parse_tree = NULL;
|
|
struct cmndspec *cs = NULL;
|
|
struct sudo_nss *nss;
|
|
struct cmnd_info info;
|
|
int validated = FLAG_NO_USER | FLAG_NO_HOST;
|
|
int m, match = UNSPEC;
|
|
time_t now;
|
|
debug_decl(sudoers_lookup, SUDOERS_DEBUG_PARSER);
|
|
|
|
/*
|
|
* Special case checking the "validate", "list" and "kill" pseudo-commands.
|
|
*/
|
|
if (pwflag)
|
|
debug_return_int(sudoers_lookup_pseudo(snl, pw, validated, pwflag));
|
|
|
|
/* Need to be runas user while stat'ing things. */
|
|
if (!set_perms(PERM_RUNAS))
|
|
debug_return_int(validated);
|
|
|
|
/* Query each sudoers source and check the user. */
|
|
time(&now);
|
|
TAILQ_FOREACH(nss, snl, entries) {
|
|
if (nss->query(nss, pw) == -1) {
|
|
/* The query function should have printed an error message. */
|
|
SET(validated, VALIDATE_ERROR);
|
|
break;
|
|
}
|
|
|
|
m = sudoers_lookup_check(nss, pw, &validated, &info, &cs, &defs, now);
|
|
if (m != UNSPEC) {
|
|
match = m;
|
|
parse_tree = nss->parse_tree;
|
|
}
|
|
|
|
if (!sudo_nss_can_continue(nss, m))
|
|
break;
|
|
}
|
|
if (match != UNSPEC) {
|
|
if (info.cmnd_path != NULL) {
|
|
/* Update user_cmnd, user_stat, cmnd_status from matching entry. */
|
|
free(user_cmnd);
|
|
user_cmnd = info.cmnd_path;
|
|
if (user_stat != NULL)
|
|
*user_stat = info.cmnd_stat;
|
|
*cmnd_status = info.status;
|
|
}
|
|
if (defs != NULL)
|
|
(void)update_defaults(parse_tree, defs, SETDEF_GENERIC, false);
|
|
if (!apply_cmndspec(cs))
|
|
SET(validated, VALIDATE_ERROR);
|
|
else if (match == ALLOW)
|
|
SET(validated, VALIDATE_SUCCESS);
|
|
else
|
|
SET(validated, VALIDATE_FAILURE);
|
|
}
|
|
if (!restore_perms())
|
|
SET(validated, VALIDATE_ERROR);
|
|
debug_return_int(validated);
|
|
}
|
|
|
|
static int
|
|
display_priv_short(struct sudoers_parse_tree *parse_tree, struct passwd *pw,
|
|
struct userspec *us, struct sudo_lbuf *lbuf)
|
|
{
|
|
struct privilege *priv;
|
|
int nfound = 0;
|
|
debug_decl(display_priv_short, SUDOERS_DEBUG_PARSER);
|
|
|
|
TAILQ_FOREACH(priv, &us->privileges, entries) {
|
|
struct cmndspec *cs, *prev_cs = NULL;
|
|
struct cmndtag tags;
|
|
|
|
if (hostlist_matches(parse_tree, pw, &priv->hostlist) != ALLOW)
|
|
continue;
|
|
|
|
sudoers_defaults_list_to_tags(&priv->defaults, &tags);
|
|
TAILQ_FOREACH(cs, &priv->cmndlist, entries) {
|
|
/* Start a new line if RunAs changes. */
|
|
if (prev_cs == NULL || RUNAS_CHANGED(cs, prev_cs)) {
|
|
struct member *m;
|
|
|
|
if (cs != TAILQ_FIRST(&priv->cmndlist))
|
|
sudo_lbuf_append(lbuf, "\n");
|
|
sudo_lbuf_append(lbuf, " (");
|
|
if (cs->runasuserlist != NULL) {
|
|
TAILQ_FOREACH(m, cs->runasuserlist, entries) {
|
|
if (m != TAILQ_FIRST(cs->runasuserlist))
|
|
sudo_lbuf_append(lbuf, ", ");
|
|
sudoers_format_member(lbuf, parse_tree, m, ", ",
|
|
RUNASALIAS);
|
|
}
|
|
} else if (cs->runasgrouplist == NULL) {
|
|
sudo_lbuf_append(lbuf, "%s", def_runas_default);
|
|
} else {
|
|
sudo_lbuf_append(lbuf, "%s", pw->pw_name);
|
|
}
|
|
if (cs->runasgrouplist != NULL) {
|
|
sudo_lbuf_append(lbuf, " : ");
|
|
TAILQ_FOREACH(m, cs->runasgrouplist, entries) {
|
|
if (m != TAILQ_FIRST(cs->runasgrouplist))
|
|
sudo_lbuf_append(lbuf, ", ");
|
|
sudoers_format_member(lbuf, parse_tree, m, ", ",
|
|
RUNASALIAS);
|
|
}
|
|
}
|
|
sudo_lbuf_append(lbuf, ") ");
|
|
} else if (cs != TAILQ_FIRST(&priv->cmndlist)) {
|
|
sudo_lbuf_append(lbuf, ", ");
|
|
}
|
|
sudoers_format_cmndspec(lbuf, parse_tree, cs, prev_cs, tags, true);
|
|
prev_cs = cs;
|
|
nfound++;
|
|
}
|
|
sudo_lbuf_append(lbuf, "\n");
|
|
}
|
|
debug_return_int(nfound);
|
|
}
|
|
|
|
/*
|
|
* Compare the current cmndspec with the previous one to determine
|
|
* whether we need to start a new long entry for "sudo -ll".
|
|
* Returns true if we should start a new long entry, else false.
|
|
*/
|
|
static bool
|
|
new_long_entry(struct cmndspec *cs, struct cmndspec *prev_cs)
|
|
{
|
|
debug_decl(new_long_entry, SUDOERS_DEBUG_PARSER);
|
|
|
|
if (prev_cs == NULL)
|
|
debug_return_bool(true);
|
|
if (RUNAS_CHANGED(cs, prev_cs) || TAGS_CHANGED(prev_cs->tags, cs->tags))
|
|
debug_return_bool(true);
|
|
#ifdef HAVE_PRIV_SET
|
|
if (cs->privs && (!prev_cs->privs || strcmp(cs->privs, prev_cs->privs) != 0))
|
|
debug_return_bool(true);
|
|
if (cs->limitprivs && (!prev_cs->limitprivs || strcmp(cs->limitprivs, prev_cs->limitprivs) != 0))
|
|
debug_return_bool(true);
|
|
#endif /* HAVE_PRIV_SET */
|
|
#ifdef HAVE_SELINUX
|
|
if (cs->role && (!prev_cs->role || strcmp(cs->role, prev_cs->role) != 0))
|
|
debug_return_bool(true);
|
|
if (cs->type && (!prev_cs->type || strcmp(cs->type, prev_cs->type) != 0))
|
|
debug_return_bool(true);
|
|
#endif /* HAVE_SELINUX */
|
|
#ifdef HAVE_APPARMOR
|
|
if (cs->apparmor_profile && (!prev_cs->apparmor_profile || strcmp(cs->apparmor_profile, prev_cs->apparmor_profile) != 0))
|
|
debug_return_bool(true);
|
|
#endif /* HAVE_APPARMOR */
|
|
if (cs->runchroot && (!prev_cs->runchroot || strcmp(cs->runchroot, prev_cs->runchroot) != 0))
|
|
debug_return_bool(true);
|
|
if (cs->runcwd && (!prev_cs->runcwd || strcmp(cs->runcwd, prev_cs->runcwd) != 0))
|
|
debug_return_bool(true);
|
|
if (cs->timeout != prev_cs->timeout)
|
|
debug_return_bool(true);
|
|
if (cs->notbefore != prev_cs->notbefore)
|
|
debug_return_bool(true);
|
|
if (cs->notafter != prev_cs->notafter)
|
|
debug_return_bool(true);
|
|
debug_return_bool(false);
|
|
}
|
|
|
|
static int
|
|
display_priv_long(struct sudoers_parse_tree *parse_tree, struct passwd *pw,
|
|
struct userspec *us, struct sudo_lbuf *lbuf)
|
|
{
|
|
struct privilege *priv;
|
|
int nfound = 0;
|
|
debug_decl(display_priv_long, SUDOERS_DEBUG_PARSER);
|
|
|
|
TAILQ_FOREACH(priv, &us->privileges, entries) {
|
|
struct cmndspec *cs, *prev_cs;
|
|
|
|
if (hostlist_matches(parse_tree, pw, &priv->hostlist) != ALLOW)
|
|
continue;
|
|
prev_cs = NULL;
|
|
TAILQ_FOREACH(cs, &priv->cmndlist, entries) {
|
|
struct defaults *d;
|
|
struct member *m;
|
|
|
|
if (new_long_entry(cs, prev_cs)) {
|
|
int olen;
|
|
|
|
if (priv->ldap_role != NULL) {
|
|
sudo_lbuf_append(lbuf, _("\nLDAP Role: %s\n"),
|
|
priv->ldap_role);
|
|
} else {
|
|
sudo_lbuf_append(lbuf, "%s", _("\nSudoers entry:\n"));
|
|
}
|
|
sudo_lbuf_append(lbuf, "%s", _(" RunAsUsers: "));
|
|
if (cs->runasuserlist != NULL) {
|
|
TAILQ_FOREACH(m, cs->runasuserlist, entries) {
|
|
if (m != TAILQ_FIRST(cs->runasuserlist))
|
|
sudo_lbuf_append(lbuf, ", ");
|
|
sudoers_format_member(lbuf, parse_tree, m, ", ",
|
|
RUNASALIAS);
|
|
}
|
|
} else if (cs->runasgrouplist == NULL) {
|
|
sudo_lbuf_append(lbuf, "%s", def_runas_default);
|
|
} else {
|
|
sudo_lbuf_append(lbuf, "%s", pw->pw_name);
|
|
}
|
|
sudo_lbuf_append(lbuf, "\n");
|
|
if (cs->runasgrouplist != NULL) {
|
|
sudo_lbuf_append(lbuf, "%s", _(" RunAsGroups: "));
|
|
TAILQ_FOREACH(m, cs->runasgrouplist, entries) {
|
|
if (m != TAILQ_FIRST(cs->runasgrouplist))
|
|
sudo_lbuf_append(lbuf, ", ");
|
|
sudoers_format_member(lbuf, parse_tree, m, ", ",
|
|
RUNASALIAS);
|
|
}
|
|
sudo_lbuf_append(lbuf, "\n");
|
|
}
|
|
olen = lbuf->len;
|
|
sudo_lbuf_append(lbuf, "%s", _(" Options: "));
|
|
TAILQ_FOREACH(d, &priv->defaults, entries) {
|
|
sudoers_format_default(lbuf, d);
|
|
sudo_lbuf_append(lbuf, ", ");
|
|
}
|
|
if (TAG_SET(cs->tags.setenv))
|
|
sudo_lbuf_append(lbuf, "%ssetenv, ", cs->tags.setenv ? "" : "!");
|
|
if (TAG_SET(cs->tags.noexec))
|
|
sudo_lbuf_append(lbuf, "%snoexec, ", cs->tags.noexec ? "" : "!");
|
|
if (TAG_SET(cs->tags.intercept))
|
|
sudo_lbuf_append(lbuf, "%sintercept, ", cs->tags.intercept ? "" : "!");
|
|
if (TAG_SET(cs->tags.nopasswd))
|
|
sudo_lbuf_append(lbuf, "%sauthenticate, ", cs->tags.nopasswd ? "!" : "");
|
|
if (TAG_SET(cs->tags.log_input))
|
|
sudo_lbuf_append(lbuf, "%slog_input, ", cs->tags.log_input ? "" : "!");
|
|
if (TAG_SET(cs->tags.log_output))
|
|
sudo_lbuf_append(lbuf, "%slog_output, ", cs->tags.log_output ? "" : "!");
|
|
if (lbuf->buf[lbuf->len - 2] == ',') {
|
|
lbuf->len -= 2; /* remove trailing ", " */
|
|
sudo_lbuf_append(lbuf, "\n");
|
|
} else {
|
|
lbuf->len = olen; /* no options */
|
|
}
|
|
#ifdef HAVE_PRIV_SET
|
|
if (cs->privs)
|
|
sudo_lbuf_append(lbuf, " Privs: %s\n", cs->privs);
|
|
if (cs->limitprivs)
|
|
sudo_lbuf_append(lbuf, " Limitprivs: %s\n", cs->limitprivs);
|
|
#endif /* HAVE_PRIV_SET */
|
|
#ifdef HAVE_SELINUX
|
|
if (cs->role)
|
|
sudo_lbuf_append(lbuf, " Role: %s\n", cs->role);
|
|
if (cs->type)
|
|
sudo_lbuf_append(lbuf, " Type: %s\n", cs->type);
|
|
#endif /* HAVE_SELINUX */
|
|
if (cs->runchroot != NULL)
|
|
sudo_lbuf_append(lbuf, " Chroot: %s\n", cs->runchroot);
|
|
if (cs->runcwd != NULL)
|
|
sudo_lbuf_append(lbuf, " Cwd: %s\n", cs->runcwd);
|
|
if (cs->timeout > 0) {
|
|
char numbuf[(((sizeof(int) * 8) + 2) / 3) + 2];
|
|
(void)snprintf(numbuf, sizeof(numbuf), "%d", cs->timeout);
|
|
sudo_lbuf_append(lbuf, " Timeout: %s\n", numbuf);
|
|
}
|
|
if (cs->notbefore != UNSPEC) {
|
|
char buf[sizeof("CCYYMMDDHHMMSSZ")] = "";
|
|
struct tm gmt;
|
|
int len;
|
|
if (gmtime_r(&cs->notbefore, &gmt) != NULL) {
|
|
len = strftime(buf, sizeof(buf), "%Y%m%d%H%M%SZ", &gmt);
|
|
if (len != 0 && buf[sizeof(buf) - 1] == '\0')
|
|
sudo_lbuf_append(lbuf, " NotBefore: %s\n", buf);
|
|
}
|
|
}
|
|
if (cs->notafter != UNSPEC) {
|
|
char buf[sizeof("CCYYMMDDHHMMSSZ")] = "";
|
|
struct tm gmt;
|
|
int len;
|
|
if (gmtime_r(&cs->notafter, &gmt) != NULL) {
|
|
len = strftime(buf, sizeof(buf), "%Y%m%d%H%M%SZ", &gmt);
|
|
if (len != 0 && buf[sizeof(buf) - 1] == '\0')
|
|
sudo_lbuf_append(lbuf, " NotAfter: %s\n", buf);
|
|
}
|
|
}
|
|
sudo_lbuf_append(lbuf, "%s", _(" Commands:\n"));
|
|
}
|
|
sudo_lbuf_append(lbuf, "\t");
|
|
sudoers_format_member(lbuf, parse_tree, cs->cmnd, "\n\t",
|
|
CMNDALIAS);
|
|
sudo_lbuf_append(lbuf, "\n");
|
|
prev_cs = cs;
|
|
nfound++;
|
|
}
|
|
}
|
|
debug_return_int(nfound);
|
|
}
|
|
|
|
static int
|
|
sudo_display_userspecs(struct sudoers_parse_tree *parse_tree, struct passwd *pw,
|
|
struct sudo_lbuf *lbuf, bool verbose)
|
|
{
|
|
struct userspec *us;
|
|
int nfound = 0;
|
|
debug_decl(sudo_display_userspecs, SUDOERS_DEBUG_PARSER);
|
|
|
|
TAILQ_FOREACH(us, &parse_tree->userspecs, entries) {
|
|
if (userlist_matches(parse_tree, pw, &us->users) != ALLOW)
|
|
continue;
|
|
|
|
if (verbose)
|
|
nfound += display_priv_long(parse_tree, pw, us, lbuf);
|
|
else
|
|
nfound += display_priv_short(parse_tree, pw, us, lbuf);
|
|
}
|
|
if (sudo_lbuf_error(lbuf))
|
|
debug_return_int(-1);
|
|
debug_return_int(nfound);
|
|
}
|
|
|
|
/*
|
|
* Display matching Defaults entries for the given user on this host.
|
|
*/
|
|
static int
|
|
display_defaults(struct sudoers_parse_tree *parse_tree, struct passwd *pw,
|
|
struct sudo_lbuf *lbuf)
|
|
{
|
|
struct defaults *d;
|
|
char *prefix;
|
|
int nfound = 0;
|
|
debug_decl(display_defaults, SUDOERS_DEBUG_PARSER);
|
|
|
|
if (lbuf->len == 0 || isspace((unsigned char)lbuf->buf[lbuf->len - 1]))
|
|
prefix = " ";
|
|
else
|
|
prefix = ", ";
|
|
|
|
TAILQ_FOREACH(d, &parse_tree->defaults, entries) {
|
|
switch (d->type) {
|
|
case DEFAULTS_HOST:
|
|
if (hostlist_matches(parse_tree, pw, &d->binding->members) != ALLOW)
|
|
continue;
|
|
break;
|
|
case DEFAULTS_USER:
|
|
if (userlist_matches(parse_tree, pw, &d->binding->members) != ALLOW)
|
|
continue;
|
|
break;
|
|
case DEFAULTS_RUNAS:
|
|
case DEFAULTS_CMND:
|
|
continue;
|
|
}
|
|
sudo_lbuf_append(lbuf, "%s", prefix);
|
|
sudoers_format_default(lbuf, d);
|
|
prefix = ", ";
|
|
nfound++;
|
|
}
|
|
if (sudo_lbuf_error(lbuf))
|
|
debug_return_int(-1);
|
|
debug_return_int(nfound);
|
|
}
|
|
|
|
/*
|
|
* Display Defaults entries of the given type.
|
|
*/
|
|
static int
|
|
display_bound_defaults_by_type(struct sudoers_parse_tree *parse_tree,
|
|
int deftype, struct sudo_lbuf *lbuf)
|
|
{
|
|
struct defaults *d;
|
|
struct defaults_binding *binding = NULL;
|
|
struct member *m;
|
|
char *dsep;
|
|
int atype, nfound = 0;
|
|
debug_decl(display_bound_defaults_by_type, SUDOERS_DEBUG_PARSER);
|
|
|
|
switch (deftype) {
|
|
case DEFAULTS_HOST:
|
|
atype = HOSTALIAS;
|
|
dsep = "@";
|
|
break;
|
|
case DEFAULTS_USER:
|
|
atype = USERALIAS;
|
|
dsep = ":";
|
|
break;
|
|
case DEFAULTS_RUNAS:
|
|
atype = RUNASALIAS;
|
|
dsep = ">";
|
|
break;
|
|
case DEFAULTS_CMND:
|
|
atype = CMNDALIAS;
|
|
dsep = "!";
|
|
break;
|
|
default:
|
|
debug_return_int(-1);
|
|
}
|
|
TAILQ_FOREACH(d, &parse_tree->defaults, entries) {
|
|
if (d->type != deftype)
|
|
continue;
|
|
|
|
nfound++;
|
|
if (binding != d->binding) {
|
|
binding = d->binding;
|
|
if (nfound != 1)
|
|
sudo_lbuf_append(lbuf, "\n");
|
|
sudo_lbuf_append(lbuf, " Defaults%s", dsep);
|
|
TAILQ_FOREACH(m, &binding->members, entries) {
|
|
if (m != TAILQ_FIRST(&binding->members))
|
|
sudo_lbuf_append(lbuf, ", ");
|
|
sudoers_format_member(lbuf, parse_tree, m, ", ", atype);
|
|
}
|
|
sudo_lbuf_append(lbuf, " ");
|
|
} else
|
|
sudo_lbuf_append(lbuf, ", ");
|
|
sudoers_format_default(lbuf, d);
|
|
}
|
|
|
|
if (sudo_lbuf_error(lbuf))
|
|
debug_return_int(-1);
|
|
debug_return_int(nfound);
|
|
}
|
|
|
|
/*
|
|
* Display Defaults entries that are per-runas or per-command
|
|
*/
|
|
static int
|
|
display_bound_defaults(struct sudoers_parse_tree *parse_tree,
|
|
struct passwd *pw, struct sudo_lbuf *lbuf)
|
|
{
|
|
int nfound = 0;
|
|
debug_decl(display_bound_defaults, SUDOERS_DEBUG_PARSER);
|
|
|
|
/* XXX - should only print ones that match what the user can do. */
|
|
nfound += display_bound_defaults_by_type(parse_tree, DEFAULTS_RUNAS, lbuf);
|
|
nfound += display_bound_defaults_by_type(parse_tree, DEFAULTS_CMND, lbuf);
|
|
|
|
if (sudo_lbuf_error(lbuf))
|
|
debug_return_int(-1);
|
|
debug_return_int(nfound);
|
|
}
|
|
|
|
static int
|
|
output(const char *buf)
|
|
{
|
|
struct sudo_conv_message msg;
|
|
struct sudo_conv_reply repl;
|
|
debug_decl(output, SUDOERS_DEBUG_NSS);
|
|
|
|
/* Call conversation function */
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.msg_type = SUDO_CONV_INFO_MSG;
|
|
msg.msg = buf;
|
|
memset(&repl, 0, sizeof(repl));
|
|
if (sudo_conv(1, &msg, &repl, NULL) == -1)
|
|
debug_return_int(0);
|
|
debug_return_int(strlen(buf));
|
|
}
|
|
|
|
/*
|
|
* Print out privileges for the specified user.
|
|
* Returns true on success or -1 on error.
|
|
*/
|
|
int
|
|
display_privs(struct sudo_nss_list *snl, struct passwd *pw, bool verbose)
|
|
{
|
|
struct sudo_nss *nss;
|
|
struct sudo_lbuf def_buf, priv_buf;
|
|
struct stat sb;
|
|
int cols, count, olen, n;
|
|
debug_decl(display_privs, SUDOERS_DEBUG_PARSER);
|
|
|
|
cols = sudo_user.cols;
|
|
if (fstat(STDOUT_FILENO, &sb) == 0 && S_ISFIFO(sb.st_mode))
|
|
cols = 0;
|
|
sudo_lbuf_init(&def_buf, output, 4, NULL, cols);
|
|
sudo_lbuf_init(&priv_buf, output, 8, NULL, cols);
|
|
|
|
sudo_lbuf_append(&def_buf, _("Matching Defaults entries for %s on %s:\n"),
|
|
pw->pw_name, user_srunhost);
|
|
count = 0;
|
|
TAILQ_FOREACH(nss, snl, entries) {
|
|
n = display_defaults(nss->parse_tree, pw, &def_buf);
|
|
if (n == -1)
|
|
goto bad;
|
|
count += n;
|
|
}
|
|
if (count != 0) {
|
|
sudo_lbuf_append(&def_buf, "\n\n");
|
|
} else {
|
|
/* Undo Defaults header. */
|
|
def_buf.len = 0;
|
|
}
|
|
|
|
/* Display Runas and Cmnd-specific defaults. */
|
|
olen = def_buf.len;
|
|
sudo_lbuf_append(&def_buf, _("Runas and Command-specific defaults for %s:\n"),
|
|
pw->pw_name);
|
|
count = 0;
|
|
TAILQ_FOREACH(nss, snl, entries) {
|
|
n = display_bound_defaults(nss->parse_tree, pw, &def_buf);
|
|
if (n == -1)
|
|
goto bad;
|
|
count += n;
|
|
}
|
|
if (count != 0) {
|
|
sudo_lbuf_append(&def_buf, "\n\n");
|
|
} else {
|
|
/* Undo Defaults header. */
|
|
def_buf.len = olen;
|
|
}
|
|
|
|
/* Display privileges from all sources. */
|
|
sudo_lbuf_append(&priv_buf,
|
|
_("User %s may run the following commands on %s:\n"),
|
|
pw->pw_name, user_srunhost);
|
|
count = 0;
|
|
TAILQ_FOREACH(nss, snl, entries) {
|
|
if (nss->query(nss, pw) != -1) {
|
|
n = sudo_display_userspecs(nss->parse_tree, pw, &priv_buf, verbose);
|
|
if (n == -1)
|
|
goto bad;
|
|
count += n;
|
|
}
|
|
}
|
|
if (count == 0) {
|
|
def_buf.len = 0;
|
|
priv_buf.len = 0;
|
|
sudo_lbuf_append(&priv_buf,
|
|
_("User %s is not allowed to run sudo on %s.\n"),
|
|
pw->pw_name, user_srunhost);
|
|
}
|
|
if (sudo_lbuf_error(&def_buf) || sudo_lbuf_error(&priv_buf))
|
|
goto bad;
|
|
|
|
sudo_lbuf_print(&def_buf);
|
|
sudo_lbuf_print(&priv_buf);
|
|
|
|
sudo_lbuf_destroy(&def_buf);
|
|
sudo_lbuf_destroy(&priv_buf);
|
|
|
|
debug_return_int(true);
|
|
bad:
|
|
sudo_lbuf_destroy(&def_buf);
|
|
sudo_lbuf_destroy(&priv_buf);
|
|
|
|
debug_return_int(-1);
|
|
}
|
|
|
|
static int
|
|
display_cmnd_check(struct sudoers_parse_tree *parse_tree, struct passwd *pw,
|
|
time_t now)
|
|
{
|
|
int host_match, runas_match, cmnd_match;
|
|
struct cmndspec *cs;
|
|
struct privilege *priv;
|
|
struct userspec *us;
|
|
debug_decl(display_cmnd_check, SUDOERS_DEBUG_PARSER);
|
|
|
|
TAILQ_FOREACH_REVERSE(us, &parse_tree->userspecs, userspec_list, entries) {
|
|
if (userlist_matches(parse_tree, pw, &us->users) != ALLOW)
|
|
continue;
|
|
TAILQ_FOREACH_REVERSE(priv, &us->privileges, privilege_list, entries) {
|
|
host_match = hostlist_matches(parse_tree, pw, &priv->hostlist);
|
|
if (host_match != ALLOW)
|
|
continue;
|
|
TAILQ_FOREACH_REVERSE(cs, &priv->cmndlist, cmndspec_list, entries) {
|
|
if (cs->notbefore != UNSPEC) {
|
|
if (now < cs->notbefore)
|
|
continue;
|
|
}
|
|
if (cs->notafter != UNSPEC) {
|
|
if (now > cs->notafter)
|
|
continue;
|
|
}
|
|
runas_match = runaslist_matches(parse_tree, cs->runasuserlist,
|
|
cs->runasgrouplist, NULL, NULL);
|
|
if (runas_match == ALLOW) {
|
|
cmnd_match = cmnd_matches(parse_tree, cs->cmnd,
|
|
cs->runchroot, NULL);
|
|
if (cmnd_match != UNSPEC)
|
|
debug_return_int(cmnd_match);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
debug_return_int(UNSPEC);
|
|
}
|
|
|
|
/*
|
|
* Check user_cmnd against sudoers and print the matching entry if the
|
|
* command is allowed.
|
|
* Returns true if the command is allowed, false if not or -1 on error.
|
|
*/
|
|
int
|
|
display_cmnd(struct sudo_nss_list *snl, struct passwd *pw)
|
|
{
|
|
struct sudo_nss *nss;
|
|
int m, match = UNSPEC;
|
|
int ret = false;
|
|
time_t now;
|
|
debug_decl(display_cmnd, SUDOERS_DEBUG_PARSER);
|
|
|
|
/* Iterate over each source, checking for the command. */
|
|
time(&now);
|
|
TAILQ_FOREACH(nss, snl, entries) {
|
|
if (nss->query(nss, pw) == -1) {
|
|
/* The query function should have printed an error message. */
|
|
debug_return_int(-1);
|
|
}
|
|
|
|
m = display_cmnd_check(nss->parse_tree, pw, now);
|
|
if (m != UNSPEC)
|
|
match = m;
|
|
|
|
if (!sudo_nss_can_continue(nss, m))
|
|
break;
|
|
}
|
|
if (match == ALLOW) {
|
|
const int len = sudo_printf(SUDO_CONV_INFO_MSG, "%s%s%s\n",
|
|
safe_cmnd, user_args ? " " : "", user_args ? user_args : "");
|
|
ret = len < 0 ? -1 : true;
|
|
}
|
|
debug_return_int(ret);
|
|
}
|