Add option to prune non-matching entries from cvtsudoers output with -m

option is used.
This commit is contained in:
Todd C. Miller
2018-04-04 09:51:05 -06:00
parent 5c1d9899e1
commit 7663ae7b27
7 changed files with 268 additions and 130 deletions

View File

@@ -4,7 +4,7 @@ NNAAMMEE
ccvvttssuuddooeerrss - convert between sudoers file formats
SSYYNNOOPPSSIISS
ccvvttssuuddooeerrss [--eehhMMVV] [--bb _d_n] [--cc _c_o_n_f___f_i_l_e] [--dd _d_e_f_t_y_p_e_s]
ccvvttssuuddooeerrss [--eehhMMppVV] [--bb _d_n] [--cc _c_o_n_f___f_i_l_e] [--dd _d_e_f_t_y_p_e_s]
[--ff _o_u_t_p_u_t___f_o_r_m_a_t] [--ii _i_n_p_u_t___f_o_r_m_a_t] [--II _i_n_c_r_e_m_e_n_t]
[--mm _f_i_l_t_e_r] [--oo _o_u_t_p_u_t___f_i_l_e] [--OO _s_t_a_r_t___p_o_i_n_t] [--ss _s_e_c_t_i_o_n_s]
[_i_n_p_u_t___f_i_l_e]
@@ -147,6 +147,11 @@ DDEESSCCRRIIPPTTIIOONN
point of 0 will disable the generation of sudoOrder
attributes in the resulting LDIF file.
--pp, ----pprruunnee--mmaattcchheess
When the --mm option is also specified, ccvvttssuuddooeerrss will prune
out non-matching users, groups and hosts from matching
entries.
--ss _s_e_c_t_i_o_n_s, ----ssuupppprreessss=_s_e_c_t_i_o_n_s
Suppress the output of specific _s_e_c_t_i_o_n_s of the security
policy. One or more section names may be specified,
@@ -182,6 +187,9 @@ DDEESSCCRRIIPPTTIIOONN
oouuttppuutt__ffoorrmmaatt == _j_s_o_n | _l_d_i_f | _s_u_d_o_e_r_s
See the description of the --ff command line option.
pprruunnee__mmaattcchheess == _y_e_s | _n_o
See the description of the --pp command line option.
ssuuddooeerrss__bbaassee == _d_n
See the description of the --bb command line option.
@@ -223,4 +231,4 @@ DDIISSCCLLAAIIMMEERR
file distributed with ssuuddoo or https://www.sudo.ws/license.html for
complete details.
Sudo 1.8.23 March 30, 2018 Sudo 1.8.23
Sudo 1.8.23 April 4, 2018 Sudo 1.8.23

View File

@@ -16,7 +16,7 @@
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.TH "CVTSUDOERS" "1" "March 30, 2018" "Sudo @PACKAGE_VERSION@" "General Commands Manual"
.TH "CVTSUDOERS" "1" "April 4, 2018" "Sudo @PACKAGE_VERSION@" "General Commands Manual"
.nh
.if n .ad l
.SH "NAME"
@@ -25,7 +25,7 @@
.SH "SYNOPSIS"
.HP 11n
\fBcvtsudoers\fR
[\fB\-ehMV\fR]
[\fB\-ehMpV\fR]
[\fB\-b\fR\ \fIdn\fR]
[\fB\-c\fR\ \fIconf_file\fR]
[\fB\-d\fR\ \fIdeftypes\fR]
@@ -277,6 +277,14 @@ Defaults to a starting point of 1.
A starting point of 0 will disable the generation of sudoOrder
attributes in the resulting LDIF file.
.TP 12n
\fB\-p\fR, \fB\--prune-matches\fR
When the
\fB\-m\fR
option is also specified,
\fBcvtsudoers\fR
will prune out non-matching users, groups and hosts from
matching entries.
.TP 12n
\fB\-s\fR \fIsections\fR, \fB\--suppress\fR=\fIsections\fR
Suppress the output of specific
\fIsections\fR
@@ -340,6 +348,11 @@ See the description of the
\fB\-f\fR
command line option.
.TP 6n
\fBprune_matches =\fR \fIyes\fR | \fIno\fR
See the description of the
\fB\-p\fR
command line option.
.TP 6n
\fBsudoers_base =\fR \fIdn\fR
See the description of the
\fB\-b\fR

View File

@@ -14,7 +14,7 @@
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.Dd March 30, 2018
.Dd April 4, 2018
.Dt CVTSUDOERS 1
.Os Sudo @PACKAGE_VERSION@
.Sh NAME
@@ -22,7 +22,7 @@
.Nd convert between sudoers file formats
.Sh SYNOPSIS
.Nm cvtsudoers
.Op Fl ehMV
.Op Fl ehMpV
.Op Fl b Ar dn
.Op Fl c Ar conf_file
.Op Fl d Ar deftypes
@@ -228,6 +228,13 @@ option for details.
Defaults to a starting point of 1.
A starting point of 0 will disable the generation of sudoOrder
attributes in the resulting LDIF file.
.It Fl p , Fl -prune-matches
When the
.Fl m
option is also specified,
.Nm
will prune out non-matching users, groups and hosts from
matching entries.
.It Fl s Ar sections , Fl -suppress Ns = Ns Ar sections
Suppress the output of specific
.Ar sections
@@ -284,6 +291,10 @@ command line option.
See the description of the
.Fl f
command line option.
.It Sy prune_matches = Ar yes | no
See the description of the
.Fl p
command line option.
.It Sy sudoers_base = Ar dn
See the description of the
.Fl b

View File

@@ -56,7 +56,7 @@
struct cvtsudoers_filter *filters;
struct sudo_user sudo_user;
struct passwd *list_pw;
static const char short_opts[] = "b:c:d:ef:hi:I:m:Mo:O:s:V";
static const char short_opts[] = "b:c:d:ef:hi:I:m:Mo:O:ps:V";
static struct option long_opts[] = {
{ "base", required_argument, NULL, 'b' },
{ "config", required_argument, NULL, 'c' },
@@ -68,6 +68,7 @@ static struct option long_opts[] = {
{ "increment", required_argument, NULL, 'I' },
{ "match", required_argument, NULL, 'm' },
{ "match-local", no_argument, NULL, 'M' },
{ "prune-matches", no_argument, NULL, 'p' },
{ "order-start", required_argument, NULL, 'O' },
{ "output", required_argument, NULL, 'o' },
{ "suppress", required_argument, NULL, 's' },
@@ -85,7 +86,7 @@ static struct cvtsudoers_config *cvtsudoers_conf_read(const char *conf_file);
static void cvtsudoers_conf_free(struct cvtsudoers_config *conf);
static int cvtsudoers_parse_defaults(char *expression);
static int cvtsudoers_parse_suppression(char *expression);
static void filter_userspecs(void);
static void filter_userspecs(struct cvtsudoers_config *conf);
static void filter_defaults(struct cvtsudoers_config *conf);
static void alias_remove_unused(void);
@@ -207,6 +208,9 @@ main(int argc, char *argv[])
usage(1);
}
break;
case 'p':
conf->prune_matches = true;
break;
case 's':
conf->supstr = optarg;
break;
@@ -317,7 +321,7 @@ main(int argc, char *argv[])
}
/* Apply filters. */
filter_userspecs();
filter_userspecs(conf);
filter_defaults(conf);
if (filters != NULL || conf->defaults != CVT_DEFAULTS_ALL)
alias_remove_unused();
@@ -355,7 +359,8 @@ static struct cvtsudoers_conf_table cvtsudoers_conf_vars[] = {
{ "match", CONF_STR, &cvtsudoers_config.filter },
{ "defaults", CONF_STR, &cvtsudoers_config.defstr },
{ "suppress", CONF_STR, &cvtsudoers_config.supstr },
{ "expand_aliases", CONF_BOOL, &cvtsudoers_config.expand_aliases }
{ "expand_aliases", CONF_BOOL, &cvtsudoers_config.expand_aliases },
{ "prune_matches", CONF_BOOL, &cvtsudoers_config.prune_matches }
};
/*
@@ -669,16 +674,20 @@ open_sudoers(const char *sudoers, bool doedit, bool *keepopen)
}
static bool
userlist_matches_filter(struct member_list *userlist)
userlist_matches_filter(struct member_list *users, bool remove_nonmatching)
{
struct cvtsudoers_string *s;
bool matches = false;
struct member *m, *next;
bool ret = false;
debug_decl(userlist_matches_filter, SUDOERS_DEBUG_UTIL)
if (filters == NULL ||
(STAILQ_EMPTY(&filters->users) && STAILQ_EMPTY(&filters->groups)))
debug_return_bool(true);
TAILQ_FOREACH_REVERSE_SAFE(m, users, member_list, entries, next) {
bool matched = false;
if (STAILQ_EMPTY(&filters->users)) {
struct passwd pw;
@@ -690,12 +699,15 @@ userlist_matches_filter(struct member_list *userlist)
pw.pw_name = "_nobody";
pw.pw_uid = (uid_t)-1;
pw.pw_gid = (gid_t)-1;
if (userlist_matches(&pw, userlist) == true)
matches = true;
}
if (user_matches(&pw, m) == true) {
matched = true;
ret = true;
}
} else {
STAILQ_FOREACH(s, &filters->users, entries) {
struct passwd *pw = NULL;
if (s->str[0] == '#') {
const char *errstr;
uid_t uid = sudo_strtoid(s->str + 1, NULL, NULL, &errstr);
@@ -706,49 +718,100 @@ userlist_matches_filter(struct member_list *userlist)
pw = sudo_getpwnam(s->str);
if (pw == NULL)
continue;
if (userlist_matches(pw, userlist) == true)
matches = true;
if (user_matches(pw, m) == true)
matched = true;
sudo_pw_delref(pw);
if (matches)
/* Only need one user in the filter to match. */
if (matched) {
ret = true;
break;
}
}
}
debug_return_bool(matches);
if (!matched && remove_nonmatching) {
TAILQ_REMOVE(users, m, entries);
free_member(m);
}
}
debug_return_bool(ret);
}
static bool
hostlist_matches_filter(struct member_list *hostlist)
hostlist_matches_filter(struct member_list *hostlist, bool remove_nonmatching)
{
struct cvtsudoers_string *s;
bool matches = false;
struct member *m, *next;
char *lhost, *shost;
bool ret = false;
char **shosts;
int n = 0;
debug_decl(hostlist_matches_filter, SUDOERS_DEBUG_UTIL)
if (filters == NULL || STAILQ_EMPTY(&filters->hosts))
debug_return_bool(true);
/* Create an array of short host names. */
STAILQ_FOREACH(s, &filters->hosts, entries) {
user_runhost = s->str;
if ((user_srunhost = strchr(user_runhost, '.')) != NULL) {
user_srunhost = strndup(user_runhost,
(size_t)(user_srunhost - user_runhost));
if (user_srunhost == NULL) {
n++;
}
shosts = reallocarray(NULL, n, sizeof(char *));
if (shosts == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
n = 0;
STAILQ_FOREACH(s, &filters->hosts, entries) {
lhost = s->str;
if ((shost = strchr(lhost, '.')) != NULL) {
shost = strndup(lhost, (size_t)(shost - lhost));
if (shost == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
} else {
user_srunhost = user_runhost;
shost = lhost;
}
shosts[n++] = shost;
}
TAILQ_FOREACH_REVERSE_SAFE(m, hostlist, member_list, entries, next) {
bool matched = false;
n = 0;
STAILQ_FOREACH(s, &filters->hosts, entries) {
lhost = s->str;
shost = shosts[n++];
/* XXX - can't use netgroup_tuple with NULL pw */
if (hostlist_matches(NULL, hostlist) == true)
matches = true;
if (user_srunhost != user_runhost)
free(user_srunhost);
user_runhost = user_host;
user_srunhost = user_shost;
if (matches)
if (host_matches(NULL, lhost, shost, m) == true)
matched = true;
/* Only need one host in the filter to match. */
if (matched) {
ret = true;
break;
}
debug_return_bool(matches);
}
/* Short-circuit unless removing non-matches. */
if (!matched && remove_nonmatching) {
TAILQ_REMOVE(hostlist, m, entries);
free_member(m);
}
}
/* Free shosts array and its contents. */
n = 0;
STAILQ_FOREACH(s, &filters->hosts, entries) {
lhost = s->str;
shost = shosts[n++];
if (shost != lhost)
free(shost);
}
free(shosts);
debug_return_bool(ret == true);
}
/*
@@ -811,7 +874,7 @@ convert_sudoers_output(const char *buf)
* Apply filters to userspecs, removing non-matching entries.
*/
static void
filter_userspecs(void)
filter_userspecs(struct cvtsudoers_config *conf)
{
struct userspec *us, *next_us;
struct privilege *priv, *next_priv;
@@ -826,13 +889,13 @@ filter_userspecs(void)
* In the future, we may want to add a prune option.
*/
TAILQ_FOREACH_SAFE(us, &userspecs, entries, next_us) {
if (!userlist_matches_filter(&us->users)) {
if (!userlist_matches_filter(&us->users, conf->prune_matches)) {
TAILQ_REMOVE(&userspecs, us, entries);
free_userspec(us);
continue;
}
TAILQ_FOREACH_SAFE(priv, &us->privileges, entries, next_priv) {
if (!hostlist_matches_filter(&priv->hostlist)) {
if (!hostlist_matches_filter(&priv->hostlist, conf->prune_matches)) {
TAILQ_REMOVE(&us->privileges, priv, entries);
free_privilege(priv);
}
@@ -869,7 +932,7 @@ filter_defaults(struct cvtsudoers_config *conf)
break;
case DEFAULTS_USER:
if (!ISSET(conf->defaults, CVT_DEFAULTS_USER) ||
!userlist_matches_filter(def->binding))
!userlist_matches_filter(def->binding, conf->prune_matches))
keep = false;
break;
case DEFAULTS_RUNAS:
@@ -878,7 +941,7 @@ filter_defaults(struct cvtsudoers_config *conf)
break;
case DEFAULTS_HOST:
if (!ISSET(conf->defaults, CVT_DEFAULTS_HOST) ||
!hostlist_matches_filter(def->binding))
!hostlist_matches_filter(def->binding, conf->prune_matches))
keep = false;
break;
case DEFAULTS_CMND:
@@ -998,7 +1061,7 @@ done:
static void
usage(int fatal)
{
(void) fprintf(fatal ? stderr : stdout, "usage: %s [-ehMV] [-b dn] "
(void) fprintf(fatal ? stderr : stdout, "usage: %s [-ehMpV] [-b dn] "
"[-c conf_file ] [-d deftypes] [-f output_format] [-i input_format] "
"[-I increment] [-m filter] [-o output_file] [-O start_point] "
"[-s sections] [input_file]\n", getprogname());
@@ -1023,6 +1086,7 @@ help(void)
" -M, --match-local match filter uses passwd and group databases\n"
" -o, --output=output_file write converted sudoers to output_file\n"
" -O, --order-start=num starting point for first sudoOrder\n"
" -p, --prune-matches prune non-matching users, groups and hosts\n"
" -s, --suppress=sections suppress output of certain sections\n"
" -V, --version display version information and exit"));
exit(0);

View File

@@ -58,6 +58,7 @@ struct cvtsudoers_config {
short suppress;
bool expand_aliases;
bool store_options;
bool prune_matches;
char *sudoers_base;
char *input_format;
char *output_format;
@@ -67,7 +68,7 @@ struct cvtsudoers_config {
};
/* Initial config settings for above. */
#define INITIAL_CONFIG { 1, 1, CVT_DEFAULTS_ALL, 0, false, true }
#define INITIAL_CONFIG { 1, 1, CVT_DEFAULTS_ALL, 0, false, true, false }
#define CONF_BOOL 0
#define CONF_UINT 1

View File

@@ -90,18 +90,16 @@ static bool digest_matches(int fd, const char *file, const struct sudo_digest *s
#define has_meta(s) (strpbrk(s, "\\?*[]") != NULL)
/*
* Check for user described by pw in a list of members.
* Check whether user described by pw matches member.
* Returns ALLOW, DENY or UNSPEC.
*/
int
userlist_matches(const struct passwd *pw, const struct member_list *list)
user_matches(const struct passwd *pw, const struct member *m)
{
struct member *m;
struct alias *a;
int rc, matched = UNSPEC;
debug_decl(userlist_matches, SUDOERS_DEBUG_MATCH)
int matched = UNSPEC;
debug_decl(user_matches, SUDOERS_DEBUG_MATCH)
TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
switch (m->type) {
case ALL:
matched = !m->negated;
@@ -118,7 +116,7 @@ userlist_matches(const struct passwd *pw, const struct member_list *list)
break;
case ALIAS:
if ((a = alias_get(m->name, USERALIAS)) != NULL) {
rc = userlist_matches(pw, &a->members);
int rc = userlist_matches(pw, &a->members);
if (rc != UNSPEC)
matched = m->negated ? !rc : rc;
alias_put(a);
@@ -130,7 +128,22 @@ userlist_matches(const struct passwd *pw, const struct member_list *list)
matched = !m->negated;
break;
}
if (matched != UNSPEC)
debug_return_int(matched);
}
/*
* Check for user described by pw in a list of members.
* Returns ALLOW, DENY or UNSPEC.
*/
int
userlist_matches(const struct passwd *pw, const struct member_list *list)
{
struct member *m;
int matched = UNSPEC;
debug_decl(userlist_matches, SUDOERS_DEBUG_MATCH)
TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
if ((matched = user_matches(pw, m)) != UNSPEC)
break;
}
debug_return_int(matched);
@@ -256,24 +269,53 @@ runaslist_matches(const struct member_list *user_list,
}
/*
* Check for host and shost in a list of members.
* Check for lhost and shost in a list of members.
* Returns ALLOW, DENY or UNSPEC.
*/
static int
hostlist_matches_int(const struct passwd *pw, const char *lhost,
const char *shost, const struct member_list *list)
{
struct member *m;
int matched = UNSPEC;
debug_decl(hostlist_matches, SUDOERS_DEBUG_MATCH)
TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
matched = host_matches(pw, lhost, shost, m);
if (matched != UNSPEC)
break;
}
debug_return_int(matched);
}
/*
* Check for user_runhost and user_srunhost in a list of members.
* Returns ALLOW, DENY or UNSPEC.
*/
int
hostlist_matches(const struct passwd *pw, const struct member_list *list)
{
struct member *m;
struct alias *a;
int rc, matched = UNSPEC;
debug_decl(hostlist_matches, SUDOERS_DEBUG_MATCH)
return hostlist_matches_int(pw, user_runhost, user_srunhost, list);
}
/*
* Check whether host or shost matches member.
* Returns ALLOW, DENY or UNSPEC.
*/
int
host_matches(const struct passwd *pw, const char *lhost, const char *shost,
const struct member *m)
{
struct alias *a;
int matched = UNSPEC;
debug_decl(host_matches, SUDOERS_DEBUG_MATCH)
TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
switch (m->type) {
case ALL:
matched = !m->negated;
break;
case NETGROUP:
if (netgr_matches(m->name, user_runhost, user_srunhost,
if (netgr_matches(m->name, lhost, shost,
def_netgroup_tuple ? pw->pw_name : NULL))
matched = !m->negated;
break;
@@ -283,7 +325,7 @@ hostlist_matches(const struct passwd *pw, const struct member_list *list)
break;
case ALIAS:
if ((a = alias_get(m->name, HOSTALIAS)) != NULL) {
rc = hostlist_matches(pw, &a->members);
int rc = hostlist_matches_int(pw, lhost, shost, &a->members);
if (rc != UNSPEC)
matched = m->negated ? !rc : rc;
alias_put(a);
@@ -291,13 +333,10 @@ hostlist_matches(const struct passwd *pw, const struct member_list *list)
}
/* FALLTHROUGH */
case WORD:
if (hostname_matches(user_srunhost, user_runhost, m->name))
if (hostname_matches(shost, lhost, m->name))
matched = !m->negated;
break;
}
if (matched != UNSPEC)
break;
}
debug_return_int(matched);
}

View File

@@ -286,8 +286,10 @@ bool usergr_matches(const char *group, const char *user, const struct passwd *pw
bool userpw_matches(const char *sudoers_user, const char *user, const struct passwd *pw);
int cmnd_matches(const struct member *m);
int cmndlist_matches(const struct member_list *list);
int host_matches(const struct passwd *pw, const char *host, const char *shost, const struct member *m);
int hostlist_matches(const struct passwd *pw, const struct member_list *list);
int runaslist_matches(const struct member_list *user_list, const struct member_list *group_list, struct member **matching_user, struct member **matching_group);
int user_matches(const struct passwd *pw, const struct member *m);
int userlist_matches(const struct passwd *pw, const struct member_list *list);
const char *sudo_getdomainname(void);