X-Git-Url: https://git.gag.com/?a=blobdiff_plain;ds=sidebyside;f=ldap.c;h=3097af7dabe1b298fc2ee9ce3a17d69cf4f5865f;hb=f3530d8198251b72d01da9a07b1fa518446ec0f0;hp=81d3c408cbfe0173f5e4b4bab9fba3002e2cc090;hpb=a91c5f78701b90c47a08daa15569ef64c6194504;p=debian%2Fsudo diff --git a/ldap.c b/ldap.c index 81d3c40..3097af7 100644 --- a/ldap.c +++ b/ldap.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010 Todd C. Miller + * Copyright (c) 2003-2011 Todd C. Miller * * This code is derived from software contributed by Aaron Spangler. * @@ -37,12 +37,12 @@ #ifdef HAVE_STRINGS_H # include #endif /* HAVE_STRINGS_H */ -#if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS) -# include -#endif /* HAVE_MALLOC_H && !STDC_HEADERS */ #ifdef HAVE_UNISTD_H # include #endif /* HAVE_UNISTD_H */ +#if TIME_WITH_SYS_TIME +# include +#endif #include #include #include @@ -97,8 +97,13 @@ #endif #ifndef HAVE_LDAP_SEARCH_EXT_S -#define ldap_search_ext_s(a, b, c, d, e, f, g, h, i, j, k) \ +# ifdef HAVE_LDAP_SEARCH_ST +# define ldap_search_ext_s(a, b, c, d, e, f, g, h, i, j, k) \ + ldap_search_st(a, b, c, d, e, f, i, k) +# else +# define ldap_search_ext_s(a, b, c, d, e, f, g, h, i, j, k) \ ldap_search_s(a, b, c, d, e, f, k) +# endif #endif #define LDAP_FOREACH(var, ld, res) \ @@ -116,6 +121,53 @@ #define SUDO_LDAP_SSL 1 #define SUDO_LDAP_STARTTLS 2 +/* The TIMEFILTER_LENGTH includes the filter itself plus the global AND + wrapped around the user filter and the time filter when timed entries + are used. The length is computed as follows: + 85 for the filter + + 2 * 13 for the now timestamp + + 3 for the global AND +*/ +#define TIMEFILTER_LENGTH 114 + +/* + * The ldap_search structure implements a linked list of ldap and + * search result pointers, which allows us to remove them after + * all search results have been combined in memory. + * XXX - should probably be a tailq since we do appends + */ +struct ldap_search_list { + LDAP *ldap; + LDAPMessage *searchresult; + struct ldap_search_list *next; +}; + +/* + * The ldap_entry_wrapper structure is used to implement sorted result entries. + * A double is used for the order to allow for insertion of new entries + * without having to renumber everything. + * Note: there is no standard floating point type in LDAP. + * As a result, some LDAP servers will only allow an integer. + */ +struct ldap_entry_wrapper { + LDAPMessage *entry; + double order; +}; + +/* + * The ldap_result structure contains the list of matching searches as + * well as an array of all result entries sorted by the sudoOrder attribute. + */ +struct ldap_result { + struct ldap_search_list *searches; + struct ldap_entry_wrapper *entries; + int allocated_entries; + int nentries; + int user_matches; + int host_matches; +}; +#define ALLOCATION_INCREMENT 100 + struct ldap_config_table { const char *conf_str; /* config file string */ short type; /* CONF_BOOL, CONF_INT, CONF_STR */ @@ -129,7 +181,7 @@ struct ldap_config_list_str { char val[1]; }; -/* ldap configuration structure */ +/* LDAP configuration structure */ static struct ldap_config { int port; int version; @@ -137,16 +189,19 @@ static struct ldap_config { int ldap_debug; int tls_checkpeer; int timelimit; + int timeout; int bind_timelimit; int use_sasl; int rootuse_sasl; int ssl_mode; + int timed; char *host; struct ldap_config_list_str *uri; char *binddn; char *bindpw; char *rootbinddn; struct ldap_config_list_str *base; + char *search_filter; char *ssl; char *tls_cacertfile; char *tls_cacertdir; @@ -213,15 +268,25 @@ static struct ldap_config_table ldap_conf_table[] = { #ifdef LDAP_OPT_NETWORK_TIMEOUT { "bind_timelimit", CONF_INT, TRUE, -1 /* needs timeval, set manually */, &ldap_conf.bind_timelimit }, + { "network_timeout", CONF_INT, TRUE, -1 /* needs timeval, set manually */, + &ldap_conf.bind_timelimit }, #elif defined(LDAP_X_OPT_CONNECT_TIMEOUT) { "bind_timelimit", CONF_INT, TRUE, LDAP_X_OPT_CONNECT_TIMEOUT, &ldap_conf.bind_timelimit }, + { "network_timeout", CONF_INT, TRUE, LDAP_X_OPT_CONNECT_TIMEOUT, + &ldap_conf.bind_timelimit }, #endif { "timelimit", CONF_INT, TRUE, LDAP_OPT_TIMELIMIT, &ldap_conf.timelimit }, +#ifdef LDAP_OPT_TIMEOUT + { "timeout", CONF_INT, TRUE, -1 /* needs timeval, set manually */, + &ldap_conf.timeout }, +#endif { "binddn", CONF_STR, FALSE, -1, &ldap_conf.binddn }, { "bindpw", CONF_STR, FALSE, -1, &ldap_conf.bindpw }, { "rootbinddn", CONF_STR, FALSE, -1, &ldap_conf.rootbinddn }, { "sudoers_base", CONF_LIST_STR, FALSE, -1, &ldap_conf.base }, + { "sudoers_timed", CONF_BOOL, FALSE, -1, &ldap_conf.timed }, + { "sudoers_search_filter", CONF_STR, FALSE, -1, &ldap_conf.search_filter }, #ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S { "use_sasl", CONF_BOOL, FALSE, -1, &ldap_conf.use_sasl }, { "sasl_auth_id", CONF_STR, FALSE, -1, &ldap_conf.sasl_auth_id }, @@ -250,6 +315,22 @@ static int sudo_ldap_display_bound_defaults __P((struct sudo_nss *nss, struct passwd *pw, struct lbuf *lbuf)); static int sudo_ldap_display_privs __P((struct sudo_nss *nss, struct passwd *pw, struct lbuf *lbuf)); +static struct ldap_result *sudo_ldap_result_get __P((struct sudo_nss *nss, + struct passwd *pw)); + +/* + * LDAP sudo_nss handle. + * We store the connection to the LDAP server, the cached ldap_result object + * (if any), and the name of the user the query was performed for. + * If a new query is launched with sudo_ldap_result_get() that specifies a + * different user, the old cached result is freed before the new query is run. + */ +struct sudo_ldap_handle { + LDAP *ld; + struct ldap_result *result; + char *username; + GETGROUPS_T *groups; +}; struct sudo_nss sudo_nss_ldap = { &sudo_nss_ldap, @@ -298,7 +379,7 @@ sudo_ldap_conf_add_ports() } } - free(ldap_conf.host); + efree(ldap_conf.host); ldap_conf.host = estrdup(hostbuf); return; @@ -378,7 +459,7 @@ sudo_ldap_parse_uri(uri_list) ldap_conf.ssl_mode = SUDO_LDAP_SSL; } - free(ldap_conf.host); + efree(ldap_conf.host); ldap_conf.host = estrdup(hostbuf); efree(buf); } while ((uri_list = uri_list->next)); @@ -388,7 +469,7 @@ sudo_ldap_parse_uri(uri_list) done: efree(buf); - return(rc); + return rc; toobig: errorx(1, "sudo_ldap_parse_uri: out of space building hostbuf"); @@ -404,7 +485,7 @@ sudo_ldap_join_uri(uri_list) /* Usually just a single entry. */ if (uri_list->next == NULL) - return(estrdup(uri_list->val)); + return estrdup(uri_list->val); for (uri = uri_list; uri != NULL; uri = uri->next) { len += strlen(uri->val) + 1; @@ -416,7 +497,7 @@ sudo_ldap_join_uri(uri_list) *cp++ = ' '; } cp[-1] = '\0'; - return(buf); + return buf; } #endif /* HAVE_LDAP_INITIALIZE */ @@ -485,7 +566,7 @@ sudo_ldap_init(ldp, host, port) done: *ldp = ld; - return(rc); + return rc; } /* @@ -503,12 +584,12 @@ sudo_ldap_check_user_netgroup(ld, entry, user) int ret = FALSE; if (!entry) - return(ret); + return ret; /* get the values from the entry */ bv = ldap_get_values_len(ld, entry, "sudoUser"); if (bv == NULL) - return(ret); + return ret; /* walk through values */ for (p = bv; *p != NULL && !ret; p++) { @@ -517,12 +598,12 @@ sudo_ldap_check_user_netgroup(ld, entry, user) if (netgr_matches(val, NULL, NULL, user)) ret = TRUE; DPRINTF(("ldap sudoUser netgroup '%s' ... %s", val, - ret ? "MATCH!" : "not"), 2); + ret ? "MATCH!" : "not"), 2 + ((ret) ? 0 : 1)); } ldap_value_free_len(bv); /* cleanup */ - return(ret); + return ret; } /* @@ -539,12 +620,12 @@ sudo_ldap_check_host(ld, entry) int ret = FALSE; if (!entry) - return(ret); + return ret; /* get the values from the entry */ bv = ldap_get_values_len(ld, entry, "sudoHost"); if (bv == NULL) - return(ret); + return ret; /* walk through values */ for (p = bv; *p != NULL && !ret; p++) { @@ -560,7 +641,7 @@ sudo_ldap_check_host(ld, entry) ldap_value_free_len(bv); /* cleanup */ - return(ret); + return ret; } static int @@ -573,7 +654,7 @@ sudo_ldap_check_runas_user(ld, entry) int ret = FALSE; if (!runas_pw) - return(UNSPEC); + return UNSPEC; /* get the runas user from the entry */ bv = ldap_get_values_len(ld, entry, "sudoRunAsUser"); @@ -602,7 +683,7 @@ sudo_ldap_check_runas_user(ld, entry) * what the user specified on the command line. */ if (bv == NULL) - return(!strcasecmp(runas_pw->pw_name, def_runas_default)); + return !strcasecmp(runas_pw->pw_name, def_runas_default); /* walk through values returned, looking for a match */ for (p = bv; *p != NULL && !ret; p++) { @@ -633,7 +714,7 @@ sudo_ldap_check_runas_user(ld, entry) ldap_value_free_len(bv); /* cleanup */ - return(ret); + return ret; } static int @@ -647,12 +728,12 @@ sudo_ldap_check_runas_group(ld, entry) /* runas_gr is only set if the user specified the -g flag */ if (!runas_gr) - return(UNSPEC); + return UNSPEC; /* get the values from the entry */ bv = ldap_get_values_len(ld, entry, "sudoRunAsGroup"); if (bv == NULL) - return(ret); + return ret; /* walk through values returned, looking for a match */ for (p = bv; *p != NULL && !ret; p++) { @@ -665,7 +746,7 @@ sudo_ldap_check_runas_group(ld, entry) ldap_value_free_len(bv); /* cleanup */ - return(ret); + return ret; } /* @@ -680,12 +761,12 @@ sudo_ldap_check_runas(ld, entry) int ret; if (!entry) - return(FALSE); + return FALSE; ret = sudo_ldap_check_runas_user(ld, entry) != FALSE && sudo_ldap_check_runas_group(ld, entry) != FALSE; - return(ret); + return ret; } /* @@ -703,11 +784,11 @@ sudo_ldap_check_command(ld, entry, setenv_implied) int foundbang, ret = UNSPEC; if (!entry) - return(ret); + return ret; bv = ldap_get_values_len(ld, entry, "sudoCommand"); if (bv == NULL) - return(ret); + return ret; for (p = bv; *p != NULL && ret != FALSE; p++) { val = (*p)->bv_val; @@ -750,7 +831,7 @@ sudo_ldap_check_command(ld, entry, setenv_implied) ldap_value_free_len(bv); /* more cleanup */ - return(ret); + return ret; } /* @@ -768,11 +849,11 @@ sudo_ldap_check_bool(ld, entry, option) int ret = UNSPEC; if (entry == NULL) - return(UNSPEC); + return UNSPEC; bv = ldap_get_values_len(ld, entry, "sudoOption"); if (bv == NULL) - return(ret); + return ret; /* walk through options */ for (p = bv; *p != NULL; p++) { @@ -787,7 +868,7 @@ sudo_ldap_check_bool(ld, entry, option) ldap_value_free_len(bv); - return(ret); + return ret; } /* @@ -841,33 +922,128 @@ sudo_ldap_parse_options(ld, entry) } /* - * builds together a filter to check against ldap + * Build an LDAP timefilter. + * + * Stores a filter in the buffer that makes sure only entries + * are selected that have a sudoNotBefore in the past and a + * sudoNotAfter in the future, i.e. a filter of the following + * structure (spaced out a little more for better readability: + * + * (& + * (| + * (!(sudoNotAfter=*)) + * (sudoNotAfter>__now__) + * ) + * (| + * (!(sudoNotBefore=*)) + * (sudoNotBefore<__now__) + * ) + * ) + * + * If either the sudoNotAfter or sudoNotBefore attributes are missing, + * no time restriction shall be imposed. + */ +static int +sudo_ldap_timefilter(buffer, buffersize) + char *buffer; + size_t buffersize; +{ + struct tm *tp; + time_t now; + char timebuffer[16]; + int bytes = 0; + + /* Make sure we have a formatted timestamp for __now__. */ + time(&now); + if ((tp = gmtime(&now)) == NULL) { + warning("unable to get GMT"); + goto done; + } + + /* Format the timestamp according to the RFC. */ + if (strftime(timebuffer, sizeof(timebuffer), "%Y%m%d%H%MZ", tp) == 0) { + warning("unable to format timestamp"); + goto done; + } + + /* Build filter. */ + bytes = snprintf(buffer, buffersize, "(&(|(!(sudoNotAfter=*))(sudoNotAfter>=%s))(|(!(sudoNotBefore=*))(sudoNotBefore<=%s)))", + timebuffer, timebuffer); + if (bytes < 0 || bytes >= buffersize) { + warning("unable to build time filter"); + bytes = 0; + } + +done: + return bytes; +} + +/* + * Builds up a filter to search for default settings + */ +static char * +sudo_ldap_build_default_filter() +{ + char *filt; + + if (ldap_conf.search_filter) + easprintf(&filt, "(&%s(cn=defaults))", ldap_conf.search_filter); + else + filt = estrdup("cn=defaults"); + return filt; +} + +/* + * Builds up a filter to check against LDAP. */ static char * sudo_ldap_build_pass1(pw) struct passwd *pw; { struct group *grp; - size_t sz; - char *buf; + char *buf, timebuffer[TIMEFILTER_LENGTH]; + size_t sz = 0; int i; - /* Start with (|(sudoUser=USERNAME)(sudoUser=ALL)) + NUL */ - sz = 29 + strlen(pw->pw_name); + /* Start with LDAP search filter length + 3 */ + if (ldap_conf.search_filter) + sz += strlen(ldap_conf.search_filter) + 3; + + /* Then add (|(sudoUser=USERNAME)(sudoUser=ALL)) + NUL */ + sz += 29 + strlen(pw->pw_name); /* Add space for groups */ - if ((grp = sudo_getgrgid(pw->pw_gid)) != NULL) + if ((grp = sudo_getgrgid(pw->pw_gid)) != NULL) { sz += 12 + strlen(grp->gr_name); /* primary group */ + gr_delref(grp); + } for (i = 0; i < user_ngroups; i++) { if (user_groups[i] == pw->pw_gid) continue; - if ((grp = sudo_getgrgid(user_groups[i])) != NULL) + if ((grp = sudo_getgrgid(user_groups[i])) != NULL) { sz += 12 + strlen(grp->gr_name); /* supplementary group */ + gr_delref(grp); + } } + + /* If timed, add space for time limits. */ + if (ldap_conf.timed) + sz += TIMEFILTER_LENGTH; buf = emalloc(sz); + *buf = '\0'; + + /* + * If timed or using a search filter, start a global AND clause to + * contain the search filter, search criteria, and time restriction. + */ + if (ldap_conf.timed || ldap_conf.search_filter) + (void) strlcpy(buf, "(&", sz); + + if (ldap_conf.search_filter) + (void) strlcat(buf, ldap_conf.search_filter, sz); /* Global OR + sudoUser=user_name filter */ - (void) strlcpy(buf, "(|(sudoUser=", sz); + (void) strlcat(buf, "(|(sudoUser=", sz); (void) strlcat(buf, pw->pw_name, sz); (void) strlcat(buf, ")", sz); @@ -876,6 +1052,7 @@ sudo_ldap_build_pass1(pw) (void) strlcat(buf, "(sudoUser=%", sz); (void) strlcat(buf, grp->gr_name, sz); (void) strlcat(buf, ")", sz); + gr_delref(grp); } /* Append supplementary groups */ @@ -886,14 +1063,50 @@ sudo_ldap_build_pass1(pw) (void) strlcat(buf, "(sudoUser=%", sz); (void) strlcat(buf, grp->gr_name, sz); (void) strlcat(buf, ")", sz); + gr_delref(grp); } } /* Add ALL to list and end the global OR */ - if (strlcat(buf, "(sudoUser=ALL))", sz) >= sz) + if (strlcat(buf, "(sudoUser=ALL)", sz) >= sz) errorx(1, "sudo_ldap_build_pass1 allocation mismatch"); - return(buf); + /* Add the time restriction, or simply end the global OR. */ + if (ldap_conf.timed) { + strlcat(buf, ")", sz); /* closes the global OR */ + sudo_ldap_timefilter(timebuffer, sizeof(timebuffer)); + strlcat(buf, timebuffer, sz); + } else if (ldap_conf.search_filter) { + strlcat(buf, ")", sz); /* closes the global OR */ + } + strlcat(buf, ")", sz); /* closes the global OR or the global AND */ + + return buf; +} + +/* + * Builds up a filter to check against netgroup entries in LDAP. + */ +static char * +sudo_ldap_build_pass2() +{ + char *filt, timebuffer[TIMEFILTER_LENGTH]; + + if (ldap_conf.timed) + sudo_ldap_timefilter(timebuffer, sizeof(timebuffer)); + + /* + * Match all sudoUsers beginning with a '+'. + * If a search filter or time restriction is specified, + * those get ANDed in to the expression. + */ + easprintf(&filt, "%s%s(sudoUser=+*)%s%s", + (ldap_conf.timed || ldap_conf.search_filter) ? "(&" : "", + ldap_conf.search_filter ? ldap_conf.search_filter : "", + ldap_conf.timed ? timebuffer : "", + (ldap_conf.timed || ldap_conf.search_filter) ? ")" : ""); + + return filt; } /* @@ -907,32 +1120,32 @@ _atobool(s) case 'y': case 'Y': if (strcasecmp(s, "yes") == 0) - return(TRUE); + return TRUE; break; case 't': case 'T': if (strcasecmp(s, "true") == 0) - return(TRUE); + return TRUE; break; case 'o': case 'O': if (strcasecmp(s, "on") == 0) - return(TRUE); + return TRUE; if (strcasecmp(s, "off") == 0) - return(FALSE); + return FALSE; break; case 'n': case 'N': if (strcasecmp(s, "no") == 0) - return(FALSE); + return FALSE; break; case 'f': case 'F': if (strcasecmp(s, "false") == 0) - return(FALSE); + return FALSE; break; } - return(-1); + return -1; } static void @@ -969,12 +1182,13 @@ sudo_ldap_read_config() ldap_conf.port = -1; ldap_conf.tls_checkpeer = -1; ldap_conf.timelimit = -1; + ldap_conf.timeout = -1; ldap_conf.bind_timelimit = -1; ldap_conf.use_sasl = -1; ldap_conf.rootuse_sasl = -1; if ((fp = fopen(_PATH_LDAP_CONF, "r")) == NULL) - return(FALSE); + return FALSE; while ((cp = sudo_parseln(fp)) != NULL) { if (*cp == '\0') @@ -1031,9 +1245,6 @@ sudo_ldap_read_config() if (!ldap_conf.host) ldap_conf.host = estrdup("localhost"); - if (ldap_conf.bind_timelimit > 0) - ldap_conf.bind_timelimit *= 1000; /* convert to ms */ - if (ldap_conf.debug > 1) { fprintf(stderr, "LDAP Config Summary\n"); fprintf(stderr, "===================\n"); @@ -1059,6 +1270,8 @@ sudo_ldap_read_config() fprintf(stderr, "sudoers_base %s\n", "(NONE) <---Sudo will ignore ldap)"); } + if (ldap_conf.search_filter) + fprintf(stderr, "search_filter %s\n", ldap_conf.search_filter); fprintf(stderr, "binddn %s\n", ldap_conf.binddn ? ldap_conf.binddn : "(anonymous)"); fprintf(stderr, "bindpw %s\n", ldap_conf.bindpw ? @@ -1067,6 +1280,8 @@ sudo_ldap_read_config() fprintf(stderr, "bind_timelimit %d\n", ldap_conf.bind_timelimit); if (ldap_conf.timelimit > 0) fprintf(stderr, "timelimit %d\n", ldap_conf.timelimit); + if (ldap_conf.timeout > 0) + fprintf(stderr, "timeout %d\n", ldap_conf.timeout); fprintf(stderr, "ssl %s\n", ldap_conf.ssl ? ldap_conf.ssl : "(no)"); if (ldap_conf.tls_checkpeer != -1) @@ -1102,7 +1317,10 @@ sudo_ldap_read_config() fprintf(stderr, "===================\n"); } if (!ldap_conf.base) - return(FALSE); /* if no base is defined, ignore LDAP */ + return FALSE; /* if no base is defined, ignore LDAP */ + + if (ldap_conf.bind_timelimit > 0) + ldap_conf.bind_timelimit *= 1000; /* convert to ms */ /* * Interpret SSL option @@ -1126,7 +1344,7 @@ sudo_ldap_read_config() if (ldap_conf.uri) { struct ldap_config_list_str *uri = ldap_conf.uri; if (sudo_ldap_parse_uri(uri) != 0) - return(FALSE); + return FALSE; do { ldap_conf.uri = uri->next; efree(uri); @@ -1151,6 +1369,18 @@ sudo_ldap_read_config() #endif } + /* If search filter is not parenthesized, make it so. */ + if (ldap_conf.search_filter && ldap_conf.search_filter[0] != '(') { + size_t len = strlen(ldap_conf.search_filter); + cp = ldap_conf.search_filter; + ldap_conf.search_filter = emalloc(len + 3); + ldap_conf.search_filter[0] = '('; + memcpy(ldap_conf.search_filter + 1, cp, len); + ldap_conf.search_filter[len + 1] = ')'; + ldap_conf.search_filter[len + 2] = '\0'; + efree(cp); + } + /* If rootbinddn set, read in /etc/ldap.secret if it exists. */ if (ldap_conf.rootbinddn) sudo_ldap_read_secret(_PATH_LDAP_SECRET); @@ -1176,7 +1406,7 @@ sudo_ldap_read_config() } } #endif - return(TRUE); + return TRUE; } /* @@ -1192,21 +1422,21 @@ sudo_ldap_get_first_rdn(ld, entry) LDAPDN tmpDN; if ((dn = ldap_get_dn(ld, entry)) == NULL) - return(NULL); + return NULL; if (ldap_str2dn(dn, &tmpDN, LDAP_DN_FORMAT_LDAP) == LDAP_SUCCESS) { ldap_rdn2str(tmpDN[0], &rdn, LDAP_DN_FORMAT_UFN); ldap_dnfree(tmpDN); } ldap_memfree(dn); - return(rdn); + return rdn; #else char *dn, **edn; if ((dn = ldap_get_dn(ld, entry)) == NULL) - return(NULL); + return NULL; edn = ldap_explode_dn(dn, 1); ldap_memfree(dn); - return(edn ? edn[0] : NULL); + return edn ? edn[0] : NULL; #endif } @@ -1220,19 +1450,28 @@ sudo_ldap_display_defaults(nss, pw, lbuf) struct lbuf *lbuf; { struct berval **bv, **p; + struct timeval tv, *tvp = NULL; struct ldap_config_list_str *base; - LDAP *ld = (LDAP *) nss->handle; + struct sudo_ldap_handle *handle = nss->handle; + LDAP *ld; LDAPMessage *entry, *result; - char *prefix; + char *prefix, *filt; int rc, count = 0; - if (ld == NULL) + if (handle == NULL || handle->ld == NULL) goto done; + ld = handle->ld; + filt = sudo_ldap_build_default_filter(); for (base = ldap_conf.base; base != NULL; base = base->next) { + if (ldap_conf.timeout > 0) { + tv.tv_sec = ldap_conf.timeout; + tv.tv_usec = 0; + tvp = &tv; + } result = NULL; rc = ldap_search_ext_s(ld, base->val, LDAP_SCOPE_SUBTREE, - "cn=defaults", NULL, 0, NULL, NULL, NULL, 0, &result); + filt, NULL, 0, NULL, NULL, tvp, 0, &result); if (rc == LDAP_SUCCESS && (entry = ldap_first_entry(ld, result))) { bv = ldap_get_values_len(ld, entry, "sudoOption"); if (bv != NULL) { @@ -1251,8 +1490,9 @@ sudo_ldap_display_defaults(nss, pw, lbuf) if (result) ldap_msgfree(result); } + efree(filt); done: - return(count); + return count; } /* @@ -1264,7 +1504,7 @@ sudo_ldap_display_bound_defaults(nss, pw, lbuf) struct passwd *pw; struct lbuf *lbuf; { - return(0); + return 0; } /* @@ -1329,7 +1569,6 @@ sudo_ldap_display_entry_short(ld, entry, lbuf) "NOSETENV: " : "SETENV: "; if (tag != NULL) lbuf_append(lbuf, tag, NULL); - /* XXX - ignores other options */ } ldap_value_free_len(bv); } @@ -1347,7 +1586,7 @@ sudo_ldap_display_entry_short(ld, entry, lbuf) } lbuf_append(lbuf, "\n", NULL); - return(count); + return count; } /* @@ -1411,7 +1650,19 @@ sudo_ldap_display_entry_long(ld, entry, lbuf) lbuf_append(lbuf, "\n", NULL); } - /* get the Command Values from the entry */ + /* + * Display order attribute if present. This attribute is single valued, + * so there is no need for a loop. + */ + bv = ldap_get_values_len(ld, entry, "sudoOrder"); + if (bv != NULL) { + if (*bv != NULL) { + lbuf_append(lbuf, " Order: ", (*bv)->bv_val, "\n", NULL); + } + ldap_value_free_len(bv); + } + + /* Get the command values from the entry. */ bv = ldap_get_values_len(ld, entry, "sudoCommand"); if (bv != NULL) { lbuf_append(lbuf, " Commands:\n", NULL); @@ -1422,7 +1673,7 @@ sudo_ldap_display_entry_long(ld, entry, lbuf) ldap_value_free_len(bv); } - return(count); + return count; } /* @@ -1434,57 +1685,30 @@ sudo_ldap_display_privs(nss, pw, lbuf) struct passwd *pw; struct lbuf *lbuf; { - struct ldap_config_list_str *base; - LDAP *ld = (LDAP *) nss->handle; - LDAPMessage *entry, *result; - char *filt; - int rc, do_netgr, count = 0; + struct sudo_ldap_handle *handle = nss->handle; + LDAP *ld; + struct ldap_result *lres; + LDAPMessage *entry; + int i, count = 0; - if (ld == NULL) + if (handle == NULL || handle->ld == NULL) goto done; + ld = handle->ld; - /* - * Okay - time to search for anything that matches this user - * Lets limit it to only two queries of the LDAP server - * - * The first pass will look by the username, groups, and - * the keyword ALL. We will then inspect the results that - * came back from the query. We don't need to inspect the - * sudoUser in this pass since the LDAP server already scanned - * it for us. - * - * The second pass will return all the entries that contain - * user netgroups. Then we take the netgroups returned and - * try to match them against the username. - */ - for (do_netgr = 0; do_netgr < 2; do_netgr++) { - filt = do_netgr ? estrdup("sudoUser=+*") : sudo_ldap_build_pass1(pw); - DPRINTF(("ldap search '%s'", filt), 1); - for (base = ldap_conf.base; base != NULL; base = base->next) { - result = NULL; - rc = ldap_search_ext_s(ld, base->val, LDAP_SCOPE_SUBTREE, filt, - NULL, 0, NULL, NULL, NULL, 0, &result); - if (rc != LDAP_SUCCESS) - continue; /* no entries for this pass */ - - /* print each matching entry */ - LDAP_FOREACH(entry, ld, result) { - if ((!do_netgr || - sudo_ldap_check_user_netgroup(ld, entry, pw->pw_name)) && - sudo_ldap_check_host(ld, entry)) { + DPRINTF(("ldap search for command list"), 1); + lres = sudo_ldap_result_get(nss, pw); - if (long_list) - count += sudo_ldap_display_entry_long(ld, entry, lbuf); - else - count += sudo_ldap_display_entry_short(ld, entry, lbuf); - } - } - ldap_msgfree(result); - } - efree(filt); + /* Display all matching entries. */ + for (i = 0; i < lres->nentries; i++) { + entry = lres->entries[i].entry; + if (long_list) + count += sudo_ldap_display_entry_long(ld, entry, lbuf); + else + count += sudo_ldap_display_entry_short(ld, entry, lbuf); } + done: - return(count); + return count; } static int @@ -1492,59 +1716,36 @@ sudo_ldap_display_cmnd(nss, pw) struct sudo_nss *nss; struct passwd *pw; { - struct ldap_config_list_str *base; - LDAP *ld = (LDAP *) nss->handle; - LDAPMessage *entry, *result; /* used for searches */ - char *filt; /* used to parse attributes */ - int rc, found, do_netgr; /* temp/final return values */ + struct sudo_ldap_handle *handle = nss->handle; + LDAP *ld; + struct ldap_result *lres; + LDAPMessage *entry; + int i, found = FALSE; - if (ld == NULL) - return(1); + if (handle == NULL || handle->ld == NULL) + goto done; + ld = handle->ld; /* - * Okay - time to search for anything that matches this user - * Lets limit it to only two queries of the LDAP server - * - * The first pass will look by the username, groups, and - * the keyword ALL. We will then inspect the results that - * came back from the query. We don't need to inspect the - * sudoUser in this pass since the LDAP server already scanned - * it for us. - * - * The second pass will return all the entries that contain - * user netgroups. Then we take the netgroups returned and - * try to match them against the username. + * The sudo_ldap_result_get() function returns all nodes that match + * the user and the host. */ - for (found = FALSE, do_netgr = 0; !found && do_netgr < 2; do_netgr++) { - filt = do_netgr ? estrdup("sudoUser=+*") : sudo_ldap_build_pass1(pw); - DPRINTF(("ldap search '%s'", filt), 1); - for (base = ldap_conf.base; base != NULL; base = base->next) { - result = NULL; - rc = ldap_search_ext_s(ld, base->val, LDAP_SCOPE_SUBTREE, filt, - NULL, 0, NULL, NULL, NULL, 0, &result); - if (rc != LDAP_SUCCESS) - continue; /* no entries for this pass */ - - LDAP_FOREACH(entry, ld, result) { - if ((!do_netgr || - sudo_ldap_check_user_netgroup(ld, entry, pw->pw_name)) && - sudo_ldap_check_host(ld, entry) && - sudo_ldap_check_command(ld, entry, NULL) && - sudo_ldap_check_runas(ld, entry)) { - - found = TRUE; - break; - } - } - ldap_msgfree(result); + DPRINTF(("ldap search for command list"), 1); + lres = sudo_ldap_result_get(nss, pw); + for (i = 0; i < lres->nentries; i++) { + entry = lres->entries[i].entry; + if (sudo_ldap_check_command(ld, entry, NULL) && + sudo_ldap_check_runas(ld, entry)) { + found = TRUE; + goto done; } - efree(filt); } +done: if (found) printf("%s%s%s\n", safe_cmnd ? safe_cmnd : user_cmnd, user_args ? " " : "", user_args ? user_args : ""); - return(!found); + return !found; } #ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S @@ -1560,7 +1761,7 @@ sudo_ldap_sasl_interact(ld, flags, _auth_id, _interact) for (; interact->id != SASL_CB_LIST_END; interact++) { if (interact->id != SASL_CB_USER) - return(LDAP_PARAM_ERROR); + return LDAP_PARAM_ERROR; if (auth_id != NULL) interact->result = auth_id; @@ -1574,7 +1775,7 @@ sudo_ldap_sasl_interact(ld, flags, _auth_id, _interact) interact->result = estrdup(interact->result); #endif /* SASL_VERSION_MAJOR < 2 */ } - return(LDAP_SUCCESS); + return LDAP_SUCCESS; } #endif /* HAVE_LDAP_SASL_INTERACTIVE_BIND_S */ @@ -1613,7 +1814,7 @@ sudo_ldap_set_options(ld) if (rc != LDAP_OPT_SUCCESS) { warningx("ldap_set_option: %s -> %d: %s", cur->conf_str, ival, ldap_err2string(rc)); - return(-1); + return -1; } DPRINTF(("ldap_set_option: %s -> %d", cur->conf_str, ival), 1); } @@ -1625,7 +1826,7 @@ sudo_ldap_set_options(ld) if (rc != LDAP_OPT_SUCCESS) { warningx("ldap_set_option: %s -> %s: %s", cur->conf_str, sval, ldap_err2string(rc)); - return(-1); + return -1; } DPRINTF(("ldap_set_option: %s -> %s", cur->conf_str, sval), 1); } @@ -1633,6 +1834,22 @@ sudo_ldap_set_options(ld) } } +#ifdef LDAP_OPT_TIMEOUT + /* Convert timeout to a timeval */ + if (ldap_conf.timeout > 0) { + struct timeval tv; + tv.tv_sec = ldap_conf.timeout; + tv.tv_usec = 0; + rc = ldap_set_option(ld, LDAP_OPT_TIMEOUT, &tv); + if (rc != LDAP_OPT_SUCCESS) { + warningx("ldap_set_option(TIMEOUT, %ld): %s", + (long)tv.tv_sec, ldap_err2string(rc)); + return -1; + } + DPRINTF(("ldap_set_option(LDAP_OPT_TIMEOUT, %ld)", + (long)tv.tv_sec), 1); + } +#endif #ifdef LDAP_OPT_NETWORK_TIMEOUT /* Convert bind_timelimit to a timeval */ if (ldap_conf.bind_timelimit > 0) { @@ -1643,7 +1860,7 @@ sudo_ldap_set_options(ld) if (rc != LDAP_OPT_SUCCESS) { warningx("ldap_set_option(NETWORK_TIMEOUT, %ld): %s", (long)tv.tv_sec, ldap_err2string(rc)); - return(-1); + return -1; } DPRINTF(("ldap_set_option(LDAP_OPT_NETWORK_TIMEOUT, %ld)", (long)tv.tv_sec), 1); @@ -1657,12 +1874,82 @@ sudo_ldap_set_options(ld) if (rc != LDAP_SUCCESS) { warningx("ldap_set_option(LDAP_OPT_X_TLS, LDAP_OPT_X_TLS_HARD): %s", ldap_err2string(rc)); - return(-1); + return -1; } DPRINTF(("ldap_set_option(LDAP_OPT_X_TLS, LDAP_OPT_X_TLS_HARD)"), 1); } #endif - return(0); + return 0; +} + +/* + * Create a new sudo_ldap_result structure. + */ +static struct ldap_result * +sudo_ldap_result_alloc() +{ + struct ldap_result *result; + + result = emalloc(sizeof(*result)); + result->searches = NULL; + result->nentries = 0; + result->entries = NULL; + result->allocated_entries = 0; + result->user_matches = FALSE; + result->host_matches = FALSE; + return result; +} + +/* + * Free the ldap result structure + */ +static void +sudo_ldap_result_free(lres) + struct ldap_result *lres; +{ + struct ldap_search_list *s; + + if (lres != NULL) { + if (lres->nentries) { + efree(lres->entries); + lres->entries = NULL; + } + if (lres->searches) { + while ((s = lres->searches) != NULL) { + ldap_msgfree(s->searchresult); + lres->searches = s->next; + efree(s); + } + } + efree(lres); + } +} + +/* + * Add a search result to the ldap_result structure. + */ +static struct ldap_search_list * +sudo_ldap_result_add_search(lres, ldap, searchresult) + struct ldap_result *lres; + LDAP *ldap; + LDAPMessage *searchresult; +{ + struct ldap_search_list *s, *news; + + news = emalloc(sizeof(struct ldap_search_list)); + news->next = NULL; + news->ldap = ldap; + news->searchresult = searchresult; + + /* Add entry to the end of the chain (XXX - tailq instead?). */ + if (lres->searches) { + for (s = lres->searches; s->next != NULL; s = s->next) + continue; + s->next = news; + } else { + lres->searches = news; + } + return news; } /* @@ -1712,7 +1999,7 @@ sudo_ldap_bind_s(ld) } if (rc != LDAP_SUCCESS) { warningx("ldap_sasl_interactive_bind_s(): %s", ldap_err2string(rc)); - return(-1); + return -1; } DPRINTF(("ldap_sasl_interactive_bind_s() ok"), 1); } else @@ -1728,7 +2015,7 @@ sudo_ldap_bind_s(ld) NULL, NULL, NULL); if (rc != LDAP_SUCCESS) { warningx("ldap_sasl_bind_s(): %s", ldap_err2string(rc)); - return(-1); + return -1; } DPRINTF(("ldap_sasl_bind_s() ok"), 1); } @@ -1737,12 +2024,12 @@ sudo_ldap_bind_s(ld) rc = ldap_simple_bind_s(ld, ldap_conf.binddn, ldap_conf.bindpw); if (rc != LDAP_SUCCESS) { warningx("ldap_simple_bind_s(): %s", ldap_err2string(rc)); - return(-1); + return -1; } DPRINTF(("ldap_simple_bind_s() ok"), 1); } #endif - return(0); + return 0; } /* @@ -1755,9 +2042,10 @@ sudo_ldap_open(nss) { LDAP *ld; int rc, ldapnoinit = FALSE; + struct sudo_ldap_handle *handle; if (!sudo_ldap_read_config()) - return(-1); + return -1; /* Prevent reading of user ldaprc and system defaults. */ if (getenv("LDAPNOINIT") == NULL) { @@ -1777,7 +2065,7 @@ sudo_ldap_open(nss) rc = sudo_ldap_init(&ld, ldap_conf.host, ldap_conf.port); if (rc != LDAP_SUCCESS) { warningx("unable to initialize LDAP: %s", ldap_err2string(rc)); - return(-1); + return -1; } if (ldapnoinit) @@ -1785,25 +2073,25 @@ sudo_ldap_open(nss) /* Set LDAP options */ if (sudo_ldap_set_options(ld) < 0) - return(-1); + return -1; if (ldap_conf.ssl_mode == SUDO_LDAP_STARTTLS) { #if defined(HAVE_LDAP_START_TLS_S) rc = ldap_start_tls_s(ld, NULL, NULL); if (rc != LDAP_SUCCESS) { warningx("ldap_start_tls_s(): %s", ldap_err2string(rc)); - return(-1); + return -1; } DPRINTF(("ldap_start_tls_s() ok"), 1); #elif defined(HAVE_LDAP_SSL_CLIENT_INIT) && defined(HAVE_LDAP_START_TLS_S_NP) if (ldap_ssl_client_init(NULL, NULL, 0, &rc) != LDAP_SUCCESS) { warningx("ldap_ssl_client_init(): %s", ldap_err2string(rc)); - return(-1); + return -1; } rc = ldap_start_tls_s_np(ld, NULL); if (rc != LDAP_SUCCESS) { warningx("ldap_start_tls_s_np(): %s", ldap_err2string(rc)); - return(-1); + return -1; } DPRINTF(("ldap_start_tls_s_np() ok"), 1); #else @@ -1813,10 +2101,17 @@ sudo_ldap_open(nss) /* Actually connect */ if (sudo_ldap_bind_s(ld) != 0) - return(-1); + return -1; + + /* Create a handle container. */ + handle = emalloc(sizeof(struct sudo_ldap_handle)); + handle->ld = ld; + handle->result = NULL; + handle->username = NULL; + handle->groups = NULL; + nss->handle = handle; - nss->handle = ld; - return(0); + return 0; } static int @@ -1824,17 +2119,29 @@ sudo_ldap_setdefs(nss) struct sudo_nss *nss; { struct ldap_config_list_str *base; - LDAP *ld = (LDAP *) nss->handle; - LDAPMessage *entry, *result; /* used for searches */ - int rc; /* temp return value */ + struct sudo_ldap_handle *handle = nss->handle; + struct timeval tv, *tvp = NULL; + LDAP *ld; + LDAPMessage *entry, *result; + char *filt; + int rc; + + if (handle == NULL || handle->ld == NULL) + return -1; + ld = handle->ld; - if (ld == NULL) - return(-1); + filt = sudo_ldap_build_default_filter(); + DPRINTF(("Looking for cn=defaults: %s", filt), 1); for (base = ldap_conf.base; base != NULL; base = base->next) { + if (ldap_conf.timeout > 0) { + tv.tv_sec = ldap_conf.timeout; + tv.tv_usec = 0; + tvp = &tv; + } result = NULL; rc = ldap_search_ext_s(ld, base->val, LDAP_SCOPE_SUBTREE, - "cn=defaults", NULL, 0, NULL, NULL, NULL, 0, &result); + filt, NULL, 0, NULL, NULL, NULL, 0, &result); if (rc == LDAP_SUCCESS && (entry = ldap_first_entry(ld, result))) { DPRINTF(("found:%s", ldap_get_dn(ld, entry)), 1); sudo_ldap_parse_options(ld, entry); @@ -1844,8 +2151,9 @@ sudo_ldap_setdefs(nss) if (result) ldap_msgfree(result); } + efree(filt); - return(0); + return 0; } /* @@ -1857,55 +2165,42 @@ sudo_ldap_lookup(nss, ret, pwflag) int ret; int pwflag; { - struct ldap_config_list_str *base; - LDAP *ld = (LDAP *) nss->handle; - LDAPMessage *entry, *result; - char *filt; - int do_netgr, rc, matched; - int setenv_implied; - int ldap_user_matches = FALSE, ldap_host_matches = FALSE; - struct passwd *pw = list_pw ? list_pw : sudo_user.pw; + struct sudo_ldap_handle *handle = nss->handle; + LDAP *ld; + LDAPMessage *entry; + int i, rc, setenv_implied, matched = UNSPEC; + struct ldap_result *lres = NULL; + + if (handle == NULL || handle->ld == NULL) + return ret; + ld = handle->ld; - if (ld == NULL) - return(ret); + /* Fetch list of sudoRole entries that match user and host. */ + lres = sudo_ldap_result_get(nss, sudo_user.pw); + /* + * The following queries are only determine whether or not a + * password is required, so the order of the entries doesn't matter. + */ if (pwflag) { + DPRINTF(("perform search for pwflag %d", pwflag), 1); int doauth = UNSPEC; enum def_tupple pwcheck = (pwflag == -1) ? never : sudo_defs_table[pwflag].sd_un.tuple; - for (matched = 0, do_netgr = 0; !matched && do_netgr < 2; do_netgr++) { - filt = do_netgr ? estrdup("sudoUser=+*") : sudo_ldap_build_pass1(pw); - for (base = ldap_conf.base; base != NULL; base = base->next) { - result = NULL; - rc = ldap_search_ext_s(ld, base->val, LDAP_SCOPE_SUBTREE, filt, - NULL, 0, NULL, NULL, NULL, 0, &result); - if (rc != LDAP_SUCCESS) - continue; - - LDAP_FOREACH(entry, ld, result) { - /* only verify netgroup matches in pass 2 */ - if (do_netgr && !sudo_ldap_check_user_netgroup(ld, entry, pw->pw_name)) - continue; - - ldap_user_matches = TRUE; - if (sudo_ldap_check_host(ld, entry)) { - ldap_host_matches = TRUE; - if ((pwcheck == any && doauth != FALSE) || - (pwcheck == all && doauth == FALSE)) - doauth = sudo_ldap_check_bool(ld, entry, "authenticate"); - /* Only check the command when listing another user. */ - if (user_uid == 0 || list_pw == NULL || - user_uid == list_pw->pw_uid || - sudo_ldap_check_command(ld, entry, NULL)) { - matched = 1; - break; /* end foreach */ - } - } - } - ldap_msgfree(result); + for (i = 0; i < lres->nentries; i++) { + entry = lres->entries[i].entry; + if ((pwcheck == any && doauth != FALSE) || + (pwcheck == all && doauth == FALSE)) { + doauth = sudo_ldap_check_bool(ld, entry, "authenticate"); + } + /* Only check the command when listing another user. */ + if (user_uid == 0 || list_pw == NULL || + user_uid == list_pw->pw_uid || + sudo_ldap_check_command(ld, entry, NULL)) { + matched = TRUE; + break; } - efree(filt); } if (matched || user_uid == 0) { SET(ret, VALIDATE_OK); @@ -1931,6 +2226,196 @@ sudo_ldap_lookup(nss, ret, pwflag) goto done; } + DPRINTF(("searching LDAP for sudoers entries"), 1); + + setenv_implied = FALSE; + for (i = 0; i < lres->nentries; i++) { + entry = lres->entries[i].entry; + if (!sudo_ldap_check_runas(ld, entry)) + continue; + rc = sudo_ldap_check_command(ld, entry, &setenv_implied); + if (rc != UNSPEC) { + /* We have a match. */ + DPRINTF(("Command %sallowed", rc == TRUE ? "" : "NOT "), 1); + matched = TRUE; + if (rc == TRUE) { + DPRINTF(("LDAP entry: %p", entry), 1); + /* Apply entry-specific options. */ + if (setenv_implied) + def_setenv = TRUE; + sudo_ldap_parse_options(ld, entry); +#ifdef HAVE_SELINUX + /* Set role and type if not specified on command line. */ + if (user_role == NULL) + user_role = def_role; + if (user_type == NULL) + user_type = def_type; +#endif /* HAVE_SELINUX */ + SET(ret, VALIDATE_OK); + CLR(ret, VALIDATE_NOT_OK); + } else { + SET(ret, VALIDATE_NOT_OK); + CLR(ret, VALIDATE_OK); + } + break; + } + } + +done: + DPRINTF(("done with LDAP searches"), 1); + DPRINTF(("user_matches=%d", lres->user_matches), 1); + DPRINTF(("host_matches=%d", lres->host_matches), 1); + + if (!ISSET(ret, VALIDATE_OK)) { + /* No matching entries. */ + if (pwflag && list_pw == NULL) + SET(ret, FLAG_NO_CHECK); + } + if (lres->user_matches) + CLR(ret, FLAG_NO_USER); + if (lres->host_matches) + CLR(ret, FLAG_NO_HOST); + DPRINTF(("sudo_ldap_lookup(%d)=0x%02x", pwflag, ret), 1); + + return ret; +} + +/* + * Comparison function for ldap_entry_wrapper structures, descending order. + */ +static int +ldap_entry_compare(a, b) + const void *a; + const void *b; +{ + const struct ldap_entry_wrapper *aw = a; + const struct ldap_entry_wrapper *bw = b; + + return bw->order < aw->order ? -1 : + (bw->order > aw->order ? 1 : 0); +} + +/* + * Find the last entry in the list of searches, usually the + * one currently being used to add entries. + * XXX - use a tailq instead? + */ +static struct ldap_search_list * +sudo_ldap_result_last_search(lres) + struct ldap_result *lres; +{ + struct ldap_search_list *result = lres->searches; + + if (result) { + while (result->next) + result = result->next; + } + return result; +} + +/* + * Add an entry to the result structure. + */ +static struct ldap_entry_wrapper * +sudo_ldap_result_add_entry(lres, entry) + struct ldap_result *lres; + LDAPMessage *entry; +{ + struct ldap_search_list *last; + struct berval **bv; + double order = 0.0; + char *ep; + + /* Determine whether the entry has the sudoOrder attribute. */ + last = sudo_ldap_result_last_search(lres); + bv = ldap_get_values_len(last->ldap, entry, "sudoOrder"); + if (bv != NULL) { + if (ldap_count_values_len(bv) > 0) { + /* Get the value of this attribute, 0 if not present. */ + DPRINTF(("order attribute raw: %s", (*bv)->bv_val), 1); + order = strtod((*bv)->bv_val, &ep); + if (ep == (*bv)->bv_val || *ep != '\0') { + warningx("invalid sudoOrder attribute: %s", (*bv)->bv_val); + order = 0.0; + } + DPRINTF(("order attribute: %f", order), 1); + } + ldap_value_free_len(bv); + } + + /* + * Enlarge the array of entry wrappers as needed, preallocating blocks + * of 100 entries to save on allocation time. + */ + if (++lres->nentries > lres->allocated_entries) { + lres->allocated_entries += ALLOCATION_INCREMENT; + lres->entries = erealloc3(lres->entries, lres->allocated_entries, + sizeof(lres->entries[0])); + } + + /* Fill in the new entry and return it. */ + lres->entries[lres->nentries - 1].entry = entry; + lres->entries[lres->nentries - 1].order = order; + + return &lres->entries[lres->nentries - 1]; +} + +/* + * Free the ldap result structure in the sudo_nss handle. + */ +static void +sudo_ldap_result_free_nss(nss) + struct sudo_nss *nss; +{ + struct sudo_ldap_handle *handle = nss->handle; + + if (handle->result != NULL) { + DPRINTF(("removing reusable search result"), 1); + sudo_ldap_result_free(handle->result); + if (handle->username) { + efree(handle->username); + handle->username = NULL; + } + handle->groups = NULL; + handle->result = NULL; + } +} + +/* + * Perform the LDAP query for the user or return a cached query if + * there is one for this user. + */ +static struct ldap_result * +sudo_ldap_result_get(nss, pw) + struct sudo_nss *nss; + struct passwd *pw; +{ + struct sudo_ldap_handle *handle = nss->handle; + struct ldap_config_list_str *base; + struct ldap_result *lres; + struct timeval tv, *tvp = NULL; + LDAPMessage *entry, *result; + LDAP *ld = handle->ld; + int do_netgr, rc; + char *filt; + + /* + * If we already have a cached result, return it so we don't have to + * have to contact the LDAP server again. + */ + if (handle->result) { + if (handle->groups == user_groups && + strcmp(pw->pw_name, handle->username) == 0) { + DPRINTF(("reusing previous result (user %s) with %d entries", + handle->username, handle->result->nentries), 1); + return handle->result; + } + /* User mismatch, cached result cannot be used. */ + DPRINTF(("removing result (user %s), new search (user %s)", + handle->username, pw->pw_name), 1); + sudo_ldap_result_free_nss(nss); + } + /* * Okay - time to search for anything that matches this user * Lets limit it to only two queries of the LDAP server @@ -1944,12 +2429,22 @@ sudo_ldap_lookup(nss, ret, pwflag) * The second pass will return all the entries that contain * user netgroups. Then we take the netgroups returned and * try to match them against the username. + * + * Since we have to sort the possible entries before we make a + * decision, we perform the queries and store all of the results in + * an ldap_result object. The results are then sorted by sudoOrder. */ - setenv_implied = FALSE; - for (matched = 0, do_netgr = 0; !matched && do_netgr < 2; do_netgr++) { - filt = do_netgr ? estrdup("sudoUser=+*") : sudo_ldap_build_pass1(pw); + lres = sudo_ldap_result_alloc(); + for (do_netgr = 0; do_netgr < 2; do_netgr++) { + filt = do_netgr ? sudo_ldap_build_pass2() : sudo_ldap_build_pass1(pw); DPRINTF(("ldap search '%s'", filt), 1); for (base = ldap_conf.base; base != NULL; base = base->next) { + DPRINTF(("searching from base '%s'", base->val), 1); + if (ldap_conf.timeout > 0) { + tv.tv_sec = ldap_conf.timeout; + tv.tv_usec = 0; + tvp = &tv; + } result = NULL; rc = ldap_search_ext_s(ld, base->val, LDAP_SCOPE_SUBTREE, filt, NULL, 0, NULL, NULL, NULL, 0, &result); @@ -1957,85 +2452,61 @@ sudo_ldap_lookup(nss, ret, pwflag) DPRINTF(("nothing found for '%s'", filt), 1); continue; } + lres->user_matches = TRUE; - /* parse each entry returned from this most recent search */ + /* Add the seach result to list of search results. */ + DPRINTF(("adding search result"), 1); + sudo_ldap_result_add_search(lres, ld, result); LDAP_FOREACH(entry, ld, result) { - DPRINTF(("found:%s", ldap_get_dn(ld, entry)), 1); - if ( - /* first verify user netgroup matches - only if in pass 2 */ - (!do_netgr || sudo_ldap_check_user_netgroup(ld, entry, pw->pw_name)) && - /* remember that user matched */ - (ldap_user_matches = TRUE) && - /* verify host match */ - sudo_ldap_check_host(ld, entry) && - /* remember that host matched */ - (ldap_host_matches = TRUE) && - /* verify runas match */ - sudo_ldap_check_runas(ld, entry) && - /* verify command match */ - (rc = sudo_ldap_check_command(ld, entry, &setenv_implied)) != UNSPEC - ) { - /* We have a match! */ - DPRINTF(("Command %sallowed", rc == TRUE ? "" : "NOT "), 1); - matched = TRUE; - if (rc == TRUE) { - /* pick up any options */ - if (setenv_implied) - def_setenv = TRUE; - sudo_ldap_parse_options(ld, entry); -#ifdef HAVE_SELINUX - /* Set role and type if not specified on command line. */ - if (user_role == NULL) - user_role = def_role; - if (user_type == NULL) - user_type = def_type; -#endif /* HAVE_SELINUX */ - /* make sure we don't reenter loop */ - SET(ret, VALIDATE_OK); - CLR(ret, VALIDATE_NOT_OK); - } else { - SET(ret, VALIDATE_NOT_OK); - CLR(ret, VALIDATE_OK); - } - /* break from inside for loop */ - break; + if ((!do_netgr || + sudo_ldap_check_user_netgroup(ld, entry, pw->pw_name)) && + sudo_ldap_check_host(ld, entry)) { + lres->host_matches = TRUE; + sudo_ldap_result_add_entry(lres, entry); } } - ldap_msgfree(result); + DPRINTF(("result now has %d entries", lres->nentries), 1); } efree(filt); } -done: - DPRINTF(("user_matches=%d", ldap_user_matches), 1); - DPRINTF(("host_matches=%d", ldap_host_matches), 1); + /* Sort the entries by the sudoOrder attribute. */ + DPRINTF(("sorting remaining %d entries", lres->nentries), 1); + qsort(lres->entries, lres->nentries, sizeof(lres->entries[0]), + ldap_entry_compare); - if (!ISSET(ret, VALIDATE_OK)) { - /* we do not have a match */ - if (pwflag && list_pw == NULL) - SET(ret, FLAG_NO_CHECK); - } - if (ldap_user_matches) - CLR(ret, FLAG_NO_USER); - if (ldap_host_matches) - CLR(ret, FLAG_NO_HOST); - DPRINTF(("sudo_ldap_lookup(%d)=0x%02x", pwflag, ret), 1); + /* Store everything in the sudo_nss handle. */ + handle->result = lres; + handle->username = estrdup(pw->pw_name); + handle->groups = user_groups; - return(ret); + return lres; } /* - * shut down LDAP connection + * Shut down the LDAP connection. */ static int sudo_ldap_close(nss) struct sudo_nss *nss; { - if (nss->handle != NULL) { - ldap_unbind_ext_s((LDAP *) nss->handle, NULL, NULL); + struct sudo_ldap_handle *handle = nss->handle; + + if (handle != NULL) { + /* Free the result before unbinding; it may use the LDAP connection. */ + sudo_ldap_result_free_nss(nss); + + /* Unbind and close the LDAP connection. */ + if (handle->ld != NULL) { + ldap_unbind_ext_s(handle->ld, NULL, NULL); + handle->ld = NULL; + } + + /* Free the handle container. */ + efree(nss->handle); nss->handle = NULL; } - return(0); + return 0; } /* @@ -2045,5 +2516,46 @@ static int sudo_ldap_parse(nss) struct sudo_nss *nss; { - return(0); + return 0; +} + +#if 0 +/* + * Create an ldap_result from an LDAP search result. + * + * This function is currently not used anywhere, it is left here as + * an example of how to use the cached searches. + */ +static struct ldap_result * +sudo_ldap_result_from_search(ldap, searchresult) + LDAP *ldap; + LDAPMessage *searchresult; +{ + /* + * An ldap_result is built from several search results, which are + * organized in a list. The head of the list is maintained in the + * ldap_result structure, together with the wrappers that point + * to individual entries, this has to be initialized first. + */ + struct ldap_result *result = sudo_ldap_result_alloc(); + + /* + * Build a new list node for the search result, this creates the + * list node. + */ + struct ldap_search_list *last = sudo_ldap_result_add_search(result, + ldap, searchresult); + + /* + * Now add each entry in the search result to the array of of entries + * in the ldap_result object. + */ + LDAPMessage *entry; + LDAP_FOREACH(entry, last->ldap, last->searchresult) { + sudo_ldap_result_add_entry(result, entry); + } + DPRINTF(("sudo_ldap_result_from_search: %d entries found", + result->nentries), 2); + return result; } +#endif