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' },
{ "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' },
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);
usage(1);
}
break;
+ case 'p':
+ conf->prune_matches = true;
+ break;
case 's':
conf->supstr = optarg;
break;
}
/* Apply filters. */
- filter_userspecs();
+ filter_userspecs(conf);
filter_defaults(conf);
if (filters != NULL || conf->defaults != CVT_DEFAULTS_ALL)
alias_remove_unused();
{ "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 }
};
/*
}
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);
- if (STAILQ_EMPTY(&filters->users)) {
- struct passwd pw;
+ TAILQ_FOREACH_REVERSE_SAFE(m, users, member_list, entries, next) {
+ bool matched = false;
- /*
- * Only groups in filter, make a dummy user so userlist_matches()
- * can do its thing.
- */
- memset(&pw, 0, sizeof(pw));
- 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 (STAILQ_EMPTY(&filters->users)) {
+ struct passwd pw;
+
+ /*
+ * Only groups in filter, make a dummy user so userlist_matches()
+ * can do its thing.
+ */
+ memset(&pw, 0, sizeof(pw));
+ pw.pw_name = "_nobody";
+ pw.pw_uid = (uid_t)-1;
+ pw.pw_gid = (gid_t)-1;
- 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);
- if (errstr == NULL)
- pw = sudo_getpwuid(uid);
+ 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);
+ if (errstr == NULL)
+ pw = sudo_getpwuid(uid);
+ }
+ if (pw == NULL)
+ pw = sudo_getpwnam(s->str);
+ if (pw == NULL)
+ continue;
+
+ if (user_matches(pw, m) == true)
+ matched = true;
+ sudo_pw_delref(pw);
+
+ /* Only need one user in the filter to match. */
+ if (matched) {
+ ret = true;
+ break;
+ }
+ }
+ }
+
+ if (!matched && remove_nonmatching) {
+ TAILQ_REMOVE(users, m, entries);
+ free_member(m);
}
- if (pw == NULL)
- pw = sudo_getpwnam(s->str);
- if (pw == NULL)
- continue;
- if (userlist_matches(pw, userlist) == true)
- matches = true;
- sudo_pw_delref(pw);
- if (matches)
- break;
}
- debug_return_bool(matches);
+ 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 (host_matches(NULL, lhost, shost, m) == true)
+ matched = true;
+
+ /* Only need one host in the filter to match. */
+ if (matched) {
+ ret = true;
+ break;
+ }
+ }
+
+ /* Short-circuit unless removing non-matches. */
+ if (!matched && remove_nonmatching) {
+ TAILQ_REMOVE(hostlist, m, entries);
+ free_member(m);
}
- /* 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)
- break;
}
- debug_return_bool(matches);
+
+ /* 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);
}
/*
* 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;
* 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);
}
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:
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:
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());
" -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);
*/
#define has_meta(s) (strpbrk(s, "\\?*[]") != NULL)
+/*
+ * Check whether user described by pw matches member.
+ * Returns ALLOW, DENY or UNSPEC.
+ */
+int
+user_matches(const struct passwd *pw, const struct member *m)
+{
+ struct alias *a;
+ int matched = UNSPEC;
+ debug_decl(user_matches, SUDOERS_DEBUG_MATCH)
+
+ switch (m->type) {
+ case ALL:
+ matched = !m->negated;
+ break;
+ case NETGROUP:
+ if (netgr_matches(m->name,
+ def_netgroup_tuple ? user_runhost : NULL,
+ def_netgroup_tuple ? user_srunhost : NULL, pw->pw_name))
+ matched = !m->negated;
+ break;
+ case USERGROUP:
+ if (usergr_matches(m->name, pw->pw_name, pw))
+ matched = !m->negated;
+ break;
+ case ALIAS:
+ if ((a = alias_get(m->name, USERALIAS)) != NULL) {
+ int rc = userlist_matches(pw, &a->members);
+ if (rc != UNSPEC)
+ matched = m->negated ? !rc : rc;
+ alias_put(a);
+ break;
+ }
+ /* FALLTHROUGH */
+ case WORD:
+ if (userpw_matches(m->name, pw->pw_name, pw))
+ matched = !m->negated;
+ break;
+ }
+ debug_return_int(matched);
+}
+
/*
* Check for user described by pw in a list of members.
* Returns ALLOW, DENY or UNSPEC.
userlist_matches(const struct passwd *pw, const struct member_list *list)
{
struct member *m;
- struct alias *a;
- int rc, matched = UNSPEC;
+ int matched = UNSPEC;
debug_decl(userlist_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,
- def_netgroup_tuple ? user_runhost : NULL,
- def_netgroup_tuple ? user_srunhost : NULL, pw->pw_name))
- matched = !m->negated;
- break;
- case USERGROUP:
- if (usergr_matches(m->name, pw->pw_name, pw))
- matched = !m->negated;
- break;
- case ALIAS:
- if ((a = alias_get(m->name, USERALIAS)) != NULL) {
- rc = userlist_matches(pw, &a->members);
- if (rc != UNSPEC)
- matched = m->negated ? !rc : rc;
- alias_put(a);
- break;
- }
- /* FALLTHROUGH */
- case WORD:
- if (userpw_matches(m->name, pw->pw_name, pw))
- matched = !m->negated;
- break;
- }
- if (matched != UNSPEC)
+ if ((matched = user_matches(pw, m)) != UNSPEC)
break;
}
debug_return_int(matched);
}
/*
- * 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.
*/
-int
-hostlist_matches(const struct passwd *pw, const struct member_list *list)
+static int
+hostlist_matches_int(const struct passwd *pw, const char *lhost,
+ const char *shost, const struct member_list *list)
{
struct member *m;
- struct alias *a;
- int rc, matched = UNSPEC;
+ int matched = UNSPEC;
debug_decl(hostlist_matches, SUDOERS_DEBUG_MATCH)
TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
- switch (m->type) {
- case ALL:
+ 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)
+{
+ 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)
+
+ switch (m->type) {
+ case ALL:
+ matched = !m->negated;
+ break;
+ case NETGROUP:
+ if (netgr_matches(m->name, lhost, shost,
+ def_netgroup_tuple ? pw->pw_name : NULL))
matched = !m->negated;
+ break;
+ case NTWKADDR:
+ if (addr_matches(m->name))
+ matched = !m->negated;
+ break;
+ case ALIAS:
+ if ((a = alias_get(m->name, HOSTALIAS)) != NULL) {
+ int rc = hostlist_matches_int(pw, lhost, shost, &a->members);
+ if (rc != UNSPEC)
+ matched = m->negated ? !rc : rc;
+ alias_put(a);
break;
- case NETGROUP:
- if (netgr_matches(m->name, user_runhost, user_srunhost,
- def_netgroup_tuple ? pw->pw_name : NULL))
- matched = !m->negated;
- break;
- case NTWKADDR:
- if (addr_matches(m->name))
- matched = !m->negated;
- break;
- case ALIAS:
- if ((a = alias_get(m->name, HOSTALIAS)) != NULL) {
- rc = hostlist_matches(pw, &a->members);
- if (rc != UNSPEC)
- matched = m->negated ? !rc : rc;
- alias_put(a);
- break;
- }
- /* FALLTHROUGH */
- case WORD:
- if (hostname_matches(user_srunhost, user_runhost, m->name))
- matched = !m->negated;
- break;
- }
- if (matched != UNSPEC)
+ }
+ /* FALLTHROUGH */
+ case WORD:
+ if (hostname_matches(shost, lhost, m->name))
+ matched = !m->negated;
break;
}
debug_return_int(matched);