From 4dd2a3c6b80c5d6a6c114cf4ed02a14bf250aea8 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Thu, 29 Jan 2015 14:08:30 -0700 Subject: [PATCH] Add support for querying netgroups directly via LDAP since there is no other way to look up all the netgroups for a user (unlike regular groups). This introduces netgroup_base and netgroup_search_filter options to ldap.conf. Based on a diff from Steven Soulen. --- NEWS | 6 + doc/sudoers.ldap.cat | 54 +++++++- doc/sudoers.ldap.man.in | 98 ++++++++++++- doc/sudoers.ldap.mdoc.in | 93 ++++++++++++- plugins/sudoers/ldap.c | 288 +++++++++++++++++++++++++++++++++++---- 5 files changed, 506 insertions(+), 33 deletions(-) diff --git a/NEWS b/NEWS index 9e910b3ad..49828e02a 100644 --- a/NEWS +++ b/NEWS @@ -55,6 +55,12 @@ What's new in Sudo 1.8.12 This limit was introduced when sudo's version of fnmatch() was replaced in sudo 1.8.4. + * LDAP-based sudoers can now query an LDAP server for a user's + netgroups directly. This is often much faster than fetching + every sudoRole object containing a sudoUser that begins with a + `+' prefix and checking whether the user is a member of any of + the returned netgroups. + What's new in Sudo 1.8.11p2 * Fixed a bug where dynamic shared objects loaded from a plugin diff --git a/doc/sudoers.ldap.cat b/doc/sudoers.ldap.cat index 13e821162..23c8ef8af 100644 --- a/doc/sudoers.ldap.cat +++ b/doc/sudoers.ldap.cat @@ -189,13 +189,34 @@ DDEESSCCRRIIPPTTIIOONN The second is to match against the user's name and the groups that the user belongs to. (The special ALL tag is matched in this query too.) If no match is returned for the user's name and groups, a third query - returns all entries containing user netgroups and checks to see if the - user belongs to any of them. + returns all entries containing user netgroups and other non-Unix groups + and checks to see if the user belongs to any of them. If timed entries are enabled with the SSUUDDOOEERRSS__TTIIMMEEDD configuration directive, the LDAP queries include a sub-filter that limits retrieval to entries that satisfy the time constraints, if any. + If the NNEETTGGRROOUUPP__BBAASSEE configuration directive is present, queries are + performed to determine the list of netgroups the user belongs to before + the sudoers query. This makes it possible to include netgroups in the + sudoers query string in the same manner as Unix groups. The third query + mentioned above is not performed unless a group provider plugin is also + configured. The actual LDAP queries performed by ssuuddoo are as follows: + + 1. Match all nisNetgroup records with a nisNetgroupTriple containing + the user and host. The query will match nisNetgroupTriple entries + with either the short or long form of the host name or no host name + specified in the tuple. A wildcard is used to match any domain name + but be aware that the NIS schema used by some LDAP servers may not + support wild cards for nisNetgroupTriple. + + 2. Repeated queries are performed to find any nested nisNetgroup + records with a memberNisNetgroup entry that refers to an already- + matched record. + + For sites with a large number of netgroups, using NNEETTGGRROOUUPP__BBAASSEE can + significantly speed up ssuuddoo's execution time. + DDiiffffeerreenncceess bbeettwweeeenn LLDDAAPP aanndd nnoonn--LLDDAAPP ssuuddooeerrss There are some subtle differences in the way sudoers is handled once in LDAP. Probably the biggest is that according to the RFC, LDAP ordering @@ -337,6 +358,35 @@ DDEESSCCRRIIPPTTIIOONN The version of the LDAP protocol to use when connecting to the server. The default value is protocol version 3. + NNEETTGGRROOUUPP__BBAASSEE _b_a_s_e + The base DN to use when performing LDAP netgroup queries. + Typically this is of the form ou=netgroup,dc=example,dc=com for the + domain example.com. Multiple NNEETTGGRROOUUPP__BBAASSEE lines may be specified, + in which case they are queried in the order specified. + + This option can be used to query a user's netgroups directly via + LDAP which is usually faster than fetching every sudoRole object + containing a sudoUser that begins with a `+' prefix. The NIS + schema used by some LDAP servers need a modificaton to support + querying the nisNetgroup object by its nisNetgroupTriple member. + OpenLDAP's ssllaappdd requires the following change to the + nisNetgroupTriple attribute: + + attributetype ( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple' + DESC 'Netgroup triple' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) + + NNEETTGGRROOUUPP__SSEEAARRCCHH__FFIILLTTEERR _l_d_a_p___f_i_l_t_e_r + An LDAP filter which is used to restrict the set of records + returned when performing an LDAP netgroup query. Typically, this + is of the form attribute=value or + (&(attribute=value)(attribute2=value2)). The default search filter + is: objectClass=nisNetgroup. If _l_d_a_p___f_i_l_t_e_r is omitted, no search + filter will be used. This option is only when querying netgroups + directly via LDAP. + NNEETTWWOORRKK__TTIIMMEEOOUUTT _s_e_c_o_n_d_s An alias for BBIINNDD__TTIIMMEELLIIMMIITT provided for OpenLDAP compatibility. diff --git a/doc/sudoers.ldap.man.in b/doc/sudoers.ldap.man.in index 8e7b842b7..6804b45be 100644 --- a/doc/sudoers.ldap.man.in +++ b/doc/sudoers.ldap.man.in @@ -367,13 +367,52 @@ the user belongs to. \fRALL\fR tag is matched in this query too.) If no match is returned for the user's name and groups, a third -query returns all entries containing user netgroups and checks -to see if the user belongs to any of them. +query returns all entries containing user netgroups and other +non-Unix groups and checks to see if the user belongs to any of them. .PP If timed entries are enabled with the \fBSUDOERS_TIMED\fR configuration directive, the LDAP queries include a sub-filter that limits retrieval to entries that satisfy the time constraints, if any. +.PP +If the +\fBNETGROUP_BASE\fR +configuration directive is present, queries are performed to determine +the list of netgroups the user belongs to before the sudoers query. +This makes it possible to include netgroups in the sudoers query +string in the same manner as Unix groups. +The third query mentioned above is not performed unless a group provider +plugin is also configured. +The actual LDAP queries performed by +\fBsudo\fR +are as follows: +.TP 5n +1.\& +Match all +\fRnisNetgroup\fR +records with a +\fRnisNetgroupTriple\fR +containing the user and host. +The query will match +\fRnisNetgroupTriple\fR +entries with either the short or long form of the host name or +no host name specified in the tuple. +A wildcard is used to match any domain name but be aware that the +NIS schema used by some LDAP servers may not support wild cards for +\fRnisNetgroupTriple\fR. +.TP 5n +2.\& +Repeated queries are performed to find any nested +\fRnisNetgroup\fR +records with a +\fRmemberNisNetgroup\fR +entry that refers to an already-matched record. +.PP +For sites with a large number of netgroups, using +\fBNETGROUP_BASE\fR +can significantly speed up +\fBsudo\fR's +execution time. .SS "Differences between LDAP and non-LDAP sudoers" There are some subtle differences in the way sudoers is handled once in LDAP. @@ -601,6 +640,61 @@ This option is only relevant when using SASL authentication (see below). The version of the LDAP protocol to use when connecting to the server. The default value is protocol version 3. .TP 6n +\fBNETGROUP_BASE\fR \fIbase\fR +The base DN to use when performing LDAP netgroup queries. +Typically this is of the form +\fRou=netgroup,dc=example,dc=com\fR +for the domain +\fRexample.com\fR. +Multiple +\fBNETGROUP_BASE\fR +lines may be specified, in which case they are queried in the order specified. +.sp +This option can be used to query a user's netgroups directly via LDAP +which is usually faster than fetching every +\fRsudoRole\fR +object containing a +\fRsudoUser\fR +that begins with a +\(oq+\(cq +prefix. +The NIS schema used by some LDAP servers need a modificaton to +support querying the +\fRnisNetgroup\fR +object by its +\fRnisNetgroupTriple\fR +member. +OpenLDAP's +\fBslapd\fR +requires the following change to the +\fRnisNetgroupTriple\fR +attribute: +.nf +.sp +.RS 10n +attributetype ( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple' + DESC 'Netgroup triple' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +.RE +.fi +.TP 6n +\fBNETGROUP_SEARCH_FILTER\fR \fIldap_filter\fR +An LDAP filter which is used to restrict the set of records returned +when performing an LDAP netgroup query. +Typically, this is of the +form +\fRattribute=value\fR +or +\fR(&(attribute=value)(attribute2=value2))\fR. +The default search filter is: +\fRobjectClass=nisNetgroup\fR. +If +\fIldap_filter\fR +is omitted, no search filter will be used. +This option is only when querying netgroups directly via LDAP. +.TP 6n \fBNETWORK_TIMEOUT\fR \fIseconds\fR An alias for \fBBIND_TIMELIMIT\fR diff --git a/doc/sudoers.ldap.mdoc.in b/doc/sudoers.ldap.mdoc.in index e2c0c90e4..f16290e90 100644 --- a/doc/sudoers.ldap.mdoc.in +++ b/doc/sudoers.ldap.mdoc.in @@ -345,13 +345,52 @@ the user belongs to. .Li ALL tag is matched in this query too.) If no match is returned for the user's name and groups, a third -query returns all entries containing user netgroups and checks -to see if the user belongs to any of them. +query returns all entries containing user netgroups and other +non-Unix groups and checks to see if the user belongs to any of them. .Pp If timed entries are enabled with the .Sy SUDOERS_TIMED configuration directive, the LDAP queries include a sub-filter that limits retrieval to entries that satisfy the time constraints, if any. +.Pp +If the +.Sy NETGROUP_BASE +configuration directive is present, queries are performed to determine +the list of netgroups the user belongs to before the sudoers query. +This makes it possible to include netgroups in the sudoers query +string in the same manner as Unix groups. +The third query mentioned above is not performed unless a group provider +plugin is also configured. +The actual LDAP queries performed by +.Nm sudo +are as follows: +.Bl -enum +.It +Match all +.Li nisNetgroup +records with a +.Li nisNetgroupTriple +containing the user and host. +The query will match +.Li nisNetgroupTriple +entries with either the short or long form of the host name or +no host name specified in the tuple. +A wildcard is used to match any domain name but be aware that the +NIS schema used by some LDAP servers may not support wild cards for +.Li nisNetgroupTriple . +.It +Repeated queries are performed to find any nested +.Li nisNetgroup +records with a +.Li memberNisNetgroup +entry that refers to an already-matched record. +.El +.Pp +For sites with a large number of netgroups, using +.Sy NETGROUP_BASE +can significantly speed up +.Nm sudo Ns 's +execution time. .Ss Differences between LDAP and non-LDAP sudoers There are some subtle differences in the way sudoers is handled once in LDAP. @@ -561,6 +600,56 @@ This option is only relevant when using SASL authentication (see below). .It Sy LDAP_VERSION Ar number The version of the LDAP protocol to use when connecting to the server. The default value is protocol version 3. +.It Sy NETGROUP_BASE Ar base +The base DN to use when performing LDAP netgroup queries. +Typically this is of the form +.Li ou=netgroup,dc=example,dc=com +for the domain +.Li example.com . +Multiple +.Sy NETGROUP_BASE +lines may be specified, in which case they are queried in the order specified. +.Pp +This option can be used to query a user's netgroups directly via LDAP +which is usually faster than fetching every +.Li sudoRole +object containing a +.Li sudoUser +that begins with a +.Ql + +prefix. +The NIS schema used by some LDAP servers need a modificaton to +support querying the +.Li nisNetgroup +object by its +.Li nisNetgroupTriple +member. +OpenLDAP's +.Sy slapd +requires the following change to the +.Li nisNetgroupTriple +attribute: +.Bd -literal -offset 4n +attributetype ( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple' + DESC 'Netgroup triple' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +.Ed +.It Sy NETGROUP_SEARCH_FILTER Ar ldap_filter +An LDAP filter which is used to restrict the set of records returned +when performing an LDAP netgroup query. +Typically, this is of the +form +.Li attribute=value +or +.Li (&(attribute=value)(attribute2=value2)) . +The default search filter is: +.Li objectClass=nisNetgroup . +If +.Ar ldap_filter +is omitted, no search filter will be used. +This option is only when querying netgroups directly via LDAP. .It Sy NETWORK_TIMEOUT Ar seconds An alias for .Sy BIND_TIMELIMIT diff --git a/plugins/sudoers/ldap.c b/plugins/sudoers/ldap.c index 69f681354..0ede38fb0 100644 --- a/plugins/sudoers/ldap.c +++ b/plugins/sudoers/ldap.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2014 Todd C. Miller + * Copyright (c) 2003-2015 Todd C. Miller * * This code is derived from software contributed by Aaron Spangler. * @@ -162,6 +162,9 @@ extern int ldapssl_set_strength(LDAP *ldap, int strength); /* Default search filter. */ #define DEFAULT_SEARCH_FILTER "(objectClass=sudoRole)" +/* Default netgroup search filter. */ +#define DEFAULT_NETGROUP_SEARCH_FILTER "(objectClass=nisNetgroup)" + /* The TIMEFILTER_LENGTH is the length of the filter when timed entries are used. The length is computed as follows: 81 for the filter itself @@ -207,6 +210,16 @@ struct ldap_result { }; #define ALLOCATION_INCREMENT 100 +/* + * The ldap_netgroup structure implements a singly-linked tail queue of + * netgroups a user is a member of when querying netgroups directly. + */ +struct ldap_netgroup { + STAILQ_ENTRY(ldap_netgroup) entries; + char *name; +}; +STAILQ_HEAD(ldap_netgroup_list, ldap_netgroup); + struct ldap_config_table { const char *conf_str; /* config file string */ int type; /* CONF_BOOL, CONF_INT, CONF_STR */ @@ -218,7 +231,6 @@ struct ldap_config_str { STAILQ_ENTRY(ldap_config_str) entries; char val[1]; }; - STAILQ_HEAD(ldap_config_str_list, ldap_config_str); /* LDAP configuration structure */ @@ -242,7 +254,9 @@ static struct ldap_config { char *bindpw; char *rootbinddn; struct ldap_config_str_list base; + struct ldap_config_str_list netgroup_base; char *search_filter; + char *netgroup_search_filter; char *ssl; char *tls_cacertfile; char *tls_cacertdir; @@ -315,6 +329,8 @@ static struct ldap_config_table ldap_conf_global[] = { { "sudoers_base", CONF_LIST_STR, -1, &ldap_conf.base }, { "sudoers_timed", CONF_BOOL, -1, &ldap_conf.timed }, { "sudoers_search_filter", CONF_STR, -1, &ldap_conf.search_filter }, + { "netgroup_base", CONF_LIST_STR, -1, &ldap_conf.netgroup_base }, + { "netgroup_search_filter", CONF_STR, -1, &ldap_conf.netgroup_search_filter }, #ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S { "use_sasl", CONF_BOOL, -1, &ldap_conf.use_sasl }, { "sasl_auth_id", CONF_STR, -1, &ldap_conf.sasl_auth_id }, @@ -408,7 +424,6 @@ struct sudo_nss sudo_nss_ldap = { static bool sudo_ldap_conf_add_ports(void) { - char *host, *port, defport[13]; char hostbuf[LINE_MAX * 2]; int len; @@ -744,18 +759,18 @@ sudo_ldap_check_runas_user(LDAP *ld, LDAPMessage *entry) /* * BUG: - * + * * if runas is not specified on the command line, the only information * as to which user to run as is in the runas_default option. We should * check to see if we have the local option present. Unfortunately we * don't parse these options until after this routine says yes or no. * The query has already returned, so we could peek at the attribute * values here though. - * + * * For now just require users to always use -u option unless its set * in the global defaults. This behaviour is no different than the global * /etc/sudoers. - * + * * Sigh - maybe add this feature later */ @@ -1227,19 +1242,198 @@ done: return dlen + (s - src); /* count does not include NUL */ } +/* + * Check the netgroups list beginning at "start" for nesting. + * Parent nodes with a memberNisNetgroup that match one of the + * netgroups are added to the list and checked for further nesting. + * Return true on success or false if there was an internal overflow. + */ +static bool +sudo_netgroup_lookup_nested(LDAP *ld, char *base, struct timeval *timeout, + struct ldap_netgroup_list *netgroups, struct ldap_netgroup *start) +{ + struct ldap_netgroup *ng, *old_tail; + LDAPMessage *entry, *result; + size_t filt_len; + char *filt; + int rc; + debug_decl(sudo_netgroup_lookup_nested, SUDOERS_DEBUG_LDAP, sudoers_debug_instance); + + DPRINTF1("Checking for nested netgroups from netgroup_base '%s'", base); + do { + old_tail = STAILQ_LAST(netgroups, ldap_netgroup, entries); + filt_len = strlen(ldap_conf.netgroup_search_filter) + 7; + for (ng = start; ng != NULL; ng = STAILQ_NEXT(ng, entries)) { + filt_len += sudo_ldap_value_len(ng->name) + 20; + } + filt = sudo_emalloc(filt_len); + CHECK_STRLCPY(filt, "(&", filt_len); + CHECK_STRLCAT(filt, ldap_conf.netgroup_search_filter, filt_len); + CHECK_STRLCAT(filt, "(|", filt_len); + for (ng = start; ng != NULL; ng = STAILQ_NEXT(ng, entries)) { + CHECK_STRLCAT(filt, "(memberNisNetgroup=", filt_len); + CHECK_LDAP_VCAT(filt, ng->name, filt_len); + CHECK_STRLCAT(filt, ")", filt_len); + } + CHECK_STRLCAT(filt, "))", filt_len); + DPRINTF1("ldap netgroup search filter: '%s'", filt); + result = NULL; + rc = ldap_search_ext_s(ld, base, LDAP_SCOPE_SUBTREE, filt, + NULL, 0, NULL, NULL, timeout, 0, &result); + if (rc == LDAP_SUCCESS) { + LDAP_FOREACH(entry, ld, result) { + struct berval **bv; + + bv = ldap_get_values_len(ld, entry, "cn"); + if (bv != NULL) { + /* Don't add a netgroup twice. */ + STAILQ_FOREACH(ng, netgroups, entries) { + /* Assumes only one cn per entry. */ + if (strcasecmp(ng->name, (*bv)->bv_val) == 0) + break; + } + if (ng == NULL) { + ng = sudo_emalloc(sizeof(*ng)); + ng->name = sudo_estrdup((*bv)->bv_val); + STAILQ_INSERT_TAIL(netgroups, ng, entries); + DPRINTF1("Found new netgroup %s for %s", ng->name, base); + } + ldap_value_free_len(bv); + } + } + } + if (result) + ldap_msgfree(result); + sudo_efree(filt); + + /* Check for nested netgroups in what we added. */ + start = old_tail ? STAILQ_NEXT(old_tail, entries) : STAILQ_FIRST(netgroups); + } while (start != NULL); + + debug_return_bool(true); +overflow: + sudo_warnx(U_("internal error, %s overflow"), __func__); + debug_return_bool(false); +} + +/* + * Look up netgroups that the specified user is a member of. + * Appends new entries to the netgroups list. + * Return true on success or false if there was an internal overflow. + */ +static bool +sudo_netgroup_lookup(LDAP *ld, struct passwd *pw, + struct ldap_netgroup_list *netgroups) +{ + struct ldap_config_str *base; + struct ldap_netgroup *ng, *old_tail; + struct timeval tv, *tvp = NULL; + LDAPMessage *entry, *result; + size_t filt_len; + char *filt; + int rc; + debug_decl(sudo_netgroup_lookup, SUDOERS_DEBUG_LDAP, sudoers_debug_instance); + + if (ldap_conf.timeout > 0) { + tv.tv_sec = ldap_conf.timeout; + tv.tv_usec = 0; + tvp = &tv; + } + + STAILQ_FOREACH(base, &ldap_conf.netgroup_base, entries) { + /* Build query. */ + filt_len = 2 + strlen(ldap_conf.netgroup_search_filter) + + 24 + (2 * sudo_ldap_value_len(pw->pw_name)) + 26 + + sudo_ldap_value_len(user_shost) + 1 + 7 + 1; + if (user_host != user_shost) { + filt_len += 26 + sudo_ldap_value_len(user_host) + 1 + + sudo_ldap_value_len(pw->pw_name); + } + filt = sudo_emalloc(filt_len); + DPRINTF1("searching from netgroup_base '%s'", base->val); + CHECK_STRLCPY(filt, "(&", filt_len); + CHECK_STRLCAT(filt, ldap_conf.netgroup_search_filter, filt_len); + CHECK_STRLCAT(filt, "(|(nisNetgroupTriple=\\(,", filt_len); + CHECK_LDAP_VCAT(filt, pw->pw_name, filt_len); + CHECK_STRLCAT(filt, ",*\\))(nisNetgroupTriple=\\(", filt_len); + CHECK_LDAP_VCAT(filt, user_shost, filt_len); + CHECK_STRLCAT(filt, ",", filt_len); + CHECK_LDAP_VCAT(filt, pw->pw_name, filt_len); + if (user_host != user_shost) { + CHECK_STRLCAT(filt, ",*\\))(nisNetgroupTriple=\\(", filt_len); + CHECK_LDAP_VCAT(filt, user_host, filt_len); + CHECK_STRLCAT(filt, ",", filt_len); + CHECK_LDAP_VCAT(filt, pw->pw_name, filt_len); + } + CHECK_STRLCAT(filt, ",*\\))))", filt_len); + + DPRINTF1("ldap netgroup search filter: '%s'", filt); + result = NULL; + rc = ldap_search_ext_s(ld, base->val, LDAP_SCOPE_SUBTREE, filt, + NULL, 0, NULL, NULL, tvp, 0, &result); + if (rc != LDAP_SUCCESS) { + DPRINTF1("nothing found for '%s'", filt); + if (result) + ldap_msgfree(result); + sudo_efree(filt); + continue; + } + sudo_efree(filt); + + old_tail = STAILQ_LAST(netgroups, ldap_netgroup, entries); + LDAP_FOREACH(entry, ld, result) { + struct berval **bv; + + bv = ldap_get_values_len(ld, entry, "cn"); + if (bv != NULL) { + /* Don't add a netgroup twice. */ + STAILQ_FOREACH(ng, netgroups, entries) { + /* Assumes only one cn per entry. */ + if (strcasecmp(ng->name, (*bv)->bv_val) == 0) + break; + } + if (ng == NULL) { + ng = sudo_emalloc(sizeof(*ng)); + ng->name = sudo_estrdup((*bv)->bv_val); + STAILQ_INSERT_TAIL(netgroups, ng, entries); + DPRINTF1("Found new netgroup %s for %s", ng->name, + base->val); + } + ldap_value_free_len(bv); + } + } + ldap_msgfree(result); + + /* Check for nested netgroups in what we added. */ + ng = old_tail ? STAILQ_NEXT(old_tail, entries) : STAILQ_FIRST(netgroups); + if (ng != NULL) { + if (!sudo_netgroup_lookup_nested(ld, base->val, tvp, netgroups, ng)) + debug_return_bool(false); + } + } + debug_return_bool(true); +overflow: + sudo_warnx(U_("internal error, %s overflow"), __func__); + debug_return_bool(false); +} + /* * Builds up a filter to check against LDAP. */ static char * -sudo_ldap_build_pass1(struct passwd *pw) +sudo_ldap_build_pass1(LDAP *ld, struct passwd *pw) { - struct group *grp; char *buf, timebuffer[TIMEFILTER_LENGTH + 1], gidbuf[MAX_UID_T_LEN + 1]; + struct ldap_netgroup_list netgroups; + struct ldap_netgroup *ng, *nextng; struct group_list *grlist; + struct group *grp; size_t sz = 0; int i; debug_decl(sudo_ldap_build_pass1, SUDOERS_DEBUG_LDAP, sudoers_debug_instance) + STAILQ_INIT(&netgroups); + /* If there is a filter, allocate space for the global AND. */ if (ldap_conf.timed || ldap_conf.search_filter) sz += 3; @@ -1269,6 +1463,23 @@ sudo_ldap_build_pass1(struct passwd *pw) } } + /* Add space for user netgroups if netgroup_base specified. */ + if (!STAILQ_EMPTY(&ldap_conf.netgroup_base)) { + DPRINTF1("Looking up netgroups for %s", pw->pw_name); + if (sudo_netgroup_lookup(ld, pw, &netgroups)) { + STAILQ_FOREACH(ng, &netgroups, entries) { + sz += 14 + strlen(ng->name); + } + } else { + /* sudo_netgroup_lookup() failed, clean up. */ + STAILQ_FOREACH_SAFE(ng, &netgroups, entries, nextng) { + sudo_efree(ng->name); + sudo_efree(ng); + } + STAILQ_INIT(&netgroups); + } + } + /* If timed, add space for time limits. */ if (ldap_conf.timed) sz += TIMEFILTER_LENGTH; @@ -1327,7 +1538,16 @@ sudo_ldap_build_pass1(struct passwd *pw) if (grp != NULL) sudo_gr_delref(grp); - /* Add ALL to list and end the global OR */ + /* Add netgroups (if any), freeing the list as we go. */ + STAILQ_FOREACH_SAFE(ng, &netgroups, entries, nextng) { + CHECK_STRLCAT(buf, "(sudoUser=+", sz); + CHECK_LDAP_VCAT(buf, ng->name, sz); + CHECK_STRLCAT(buf, ")", sz); + sudo_efree(ng->name); + sudo_efree(ng); + } + + /* Add ALL to list and end the global OR. */ CHECK_STRLCAT(buf, "(sudoUser=ALL)", sz); /* Add the time restriction, or simply end the global OR. */ @@ -1354,32 +1574,36 @@ static char * sudo_ldap_build_pass2(void) { char *filt, timebuffer[TIMEFILTER_LENGTH + 1]; + bool query_netgroups = def_use_netgroups; debug_decl(sudo_ldap_build_pass2, SUDOERS_DEBUG_LDAP, sudoers_debug_instance) - /* Short circuit if no non-Unix group support. */ - if (!def_use_netgroups && !def_group_plugin) { + /* No need to query netgroups if using netgroup_base. */ + if (!STAILQ_EMPTY(&ldap_conf.netgroup_base)) + query_netgroups = false; + + /* Short circuit if no netgroups and no non-Unix groups. */ + if (!query_netgroups && !def_group_plugin) debug_return_str(NULL); - } if (ldap_conf.timed) sudo_ldap_timefilter(timebuffer, sizeof(timebuffer)); /* * Match all sudoUsers beginning with '+' or '%:'. - * If a search filter or time restriction is specified, + * If a search filter or time restriction is specified, * those get ANDed in to the expression. */ - if (def_group_plugin) { - sudo_easprintf(&filt, "%s%s(|(sudoUser=%s*)(sudoUser=%%:*))%s%s", + if (query_netgroups && def_group_plugin) { + sudo_easprintf(&filt, "%s%s(|(sudoUser=+*)(sudoUser=%%:*))%s%s", (ldap_conf.timed || ldap_conf.search_filter) ? "(&" : "", ldap_conf.search_filter ? ldap_conf.search_filter : "", - def_use_netgroups ? "+" : "", ldap_conf.timed ? timebuffer : "", (ldap_conf.timed || ldap_conf.search_filter) ? ")" : ""); } else { - sudo_easprintf(&filt, "%s%s(sudoUser=*)(sudoUser=+*)%s%s", + sudo_easprintf(&filt, "%s%s(sudoUser=*)(sudoUser=%s*)%s%s", (ldap_conf.timed || ldap_conf.search_filter) ? "(&" : "", ldap_conf.search_filter ? ldap_conf.search_filter : "", + query_netgroups ? "+" : "%:", ldap_conf.timed ? timebuffer : "", (ldap_conf.timed || ldap_conf.search_filter) ? ")" : ""); } @@ -1559,9 +1783,10 @@ sudo_check_krb5_ccname(const char *ccname) static bool sudo_ldap_read_config(void) { - FILE *fp; char *cp, *keyword, *value, *line = NULL; + struct ldap_config_str *conf_str; size_t linesize = 0; + FILE *fp; debug_decl(sudo_ldap_read_config, SUDOERS_DEBUG_LDAP, sudoers_debug_instance) /* defaults */ @@ -1575,8 +1800,10 @@ sudo_ldap_read_config(void) ldap_conf.rootuse_sasl = -1; ldap_conf.deref = -1; ldap_conf.search_filter = sudo_estrdup(DEFAULT_SEARCH_FILTER); + ldap_conf.netgroup_search_filter = sudo_estrdup(DEFAULT_NETGROUP_SEARCH_FILTER); STAILQ_INIT(&ldap_conf.uri); STAILQ_INIT(&ldap_conf.base); + STAILQ_INIT(&ldap_conf.netgroup_base); if ((fp = fopen(path_ldap_conf, "r")) == NULL) debug_return_bool(false); @@ -1610,10 +1837,8 @@ sudo_ldap_read_config(void) DPRINTF1("LDAP Config Summary"); DPRINTF1("==================="); if (!STAILQ_EMPTY(&ldap_conf.uri)) { - struct ldap_config_str *uri; - - STAILQ_FOREACH(uri, &ldap_conf.uri, entries) { - DPRINTF1("uri %s", uri->val); + STAILQ_FOREACH(conf_str, &ldap_conf.uri, entries) { + DPRINTF1("uri %s", conf_str->val); } } else { DPRINTF1("host %s", @@ -1623,9 +1848,8 @@ sudo_ldap_read_config(void) DPRINTF1("ldap_version %d", ldap_conf.version); if (!STAILQ_EMPTY(&ldap_conf.base)) { - struct ldap_config_str *base; - STAILQ_FOREACH(base, &ldap_conf.base, entries) { - DPRINTF1("sudoers_base %s", base->val); + STAILQ_FOREACH(conf_str, &ldap_conf.base, entries) { + DPRINTF1("sudoers_base %s", conf_str->val); } } else { DPRINTF1("sudoers_base %s", "(NONE: LDAP disabled)"); @@ -1633,6 +1857,16 @@ sudo_ldap_read_config(void) if (ldap_conf.search_filter) { DPRINTF1("search_filter %s", ldap_conf.search_filter); } + if (!STAILQ_EMPTY(&ldap_conf.netgroup_base)) { + STAILQ_FOREACH(conf_str, &ldap_conf.netgroup_base, entries) { + DPRINTF1("netgroup_base %s", conf_str->val); + } + } else { + DPRINTF1("netgroup_base %s", "(NONE: will use nsswitch)"); + } + if (ldap_conf.netgroup_search_filter) { + DPRINTF1("netgroup_search_filter %s", ldap_conf.netgroup_search_filter); + } DPRINTF1("binddn %s", ldap_conf.binddn ? ldap_conf.binddn : "(anonymous)"); DPRINTF1("bindpw %s", @@ -2706,7 +2940,7 @@ sudo_ldap_lookup(struct sudo_nss *nss, int ret, int pwflag) if (pwflag) { int doauth = UNSPEC; int matched = UNSPEC; - enum def_tuple pwcheck = + enum def_tuple pwcheck = (pwflag == -1) ? never : sudo_defs_table[pwflag].sd_un.tuple; DPRINTF1("perform search for pwflag %d", pwflag); @@ -2949,7 +3183,7 @@ sudo_ldap_result_get(struct sudo_nss *nss, struct passwd *pw) */ lres = sudo_ldap_result_alloc(); for (pass = 0; pass < 2; pass++) { - filt = pass ? sudo_ldap_build_pass2() : sudo_ldap_build_pass1(pw); + filt = pass ? sudo_ldap_build_pass2() : sudo_ldap_build_pass1(ld, pw); if (filt != NULL) { DPRINTF1("ldap search '%s'", filt); STAILQ_FOREACH(base, &ldap_conf.base, entries) { -- 2.49.0