From 4f6dfdd636a6d5db649a70c781ef9b3901a1b760 Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Fri, 3 Jan 2014 22:08:25 +0100 Subject: Use do_try_search() also for paged searches This also changes do_try_search() to support building continued paged controls and lays the groundwork for adding more search controls. --- nslcd/myldap.c | 70 ++++++++++++++++++++-------------------------------------- 1 file changed, 24 insertions(+), 46 deletions(-) diff --git a/nslcd/myldap.c b/nslcd/myldap.c index 620a3b7..e2daf17 100644 --- a/nslcd/myldap.c +++ b/nslcd/myldap.c @@ -4,8 +4,8 @@ which has been forked into the nss-pam-ldapd library. Copyright (C) 1997-2006 Luke Howard - Copyright (C) 2006, 2007 West Consulting - Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Arthur de Jong + Copyright (C) 2006-2007 West Consulting + Copyright (C) 2006-2014 Arthur de Jong This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -1053,9 +1053,9 @@ void myldap_get_policy_response(MYLDAP_SESSION *session, int *response, static int do_try_search(MYLDAP_SEARCH *search) { + int ctrlidx = 0; int rc; - LDAPControl *serverCtrls[2]; - LDAPControl **pServerCtrls; + LDAPControl *serverctrls[2]; int msgid; /* ensure that we have an open connection */ rc = do_open(search->session); @@ -1065,35 +1065,36 @@ static int do_try_search(MYLDAP_SEARCH *search) if ((nslcd_cfg->pagesize > 0) && (search->scope != LDAP_SCOPE_BASE)) { rc = ldap_create_page_control(search->session->ld, nslcd_cfg->pagesize, - NULL, 0, &serverCtrls[0]); + search->cookie, 0, &serverctrls[ctrlidx]); if (rc == LDAP_SUCCESS) - { - serverCtrls[1] = NULL; - pServerCtrls = serverCtrls; - } + ctrlidx++; else { myldap_err(LOG_WARNING, search->session->ld, rc, "ldap_create_page_control() failed"); - /* clear error flag */ - rc = LDAP_SUCCESS; - if (ldap_set_option(search->session->ld, LDAP_OPT_ERROR_NUMBER, &rc) != LDAP_SUCCESS) - log_log(LOG_WARNING, "failed to clear the error flag"); - pServerCtrls = NULL; + serverctrls[ctrlidx] = NULL; + /* if we were paging, failure building the second control is fatal */ + if (search->cookie != NULL) + return rc; } } - else - pServerCtrls = NULL; + /* NULL terminate control list */ + serverctrls[ctrlidx] = NULL; + /* clear error flag (perhaps control setting failed) */ + if (ctrlidx > 0) + { + rc = LDAP_SUCCESS; + if (ldap_set_option(search->session->ld, LDAP_OPT_ERROR_NUMBER, &rc) != LDAP_SUCCESS) + log_log(LOG_WARNING, "failed to clear the error flag"); + } /* perform the search */ rc = ldap_search_ext(search->session->ld, search->base, search->scope, search->filter, (char **)(search->attrs), - 0, pServerCtrls, NULL, NULL, LDAP_NO_LIMIT, &msgid); + 0, serverctrls[0] == NULL ? NULL : serverctrls, + NULL, NULL, LDAP_NO_LIMIT, &msgid); /* free the controls if we had them */ - if (pServerCtrls != NULL) - { - ldap_control_free(serverCtrls[0]); - serverCtrls[0] = NULL; - } + for (ctrlidx = 0; serverctrls[ctrlidx] != NULL; ctrlidx++) + ldap_control_free(serverctrls[ctrlidx]); /* handle errors */ if (rc != LDAP_SUCCESS) { @@ -1381,10 +1382,8 @@ MYLDAP_ENTRY *myldap_get_entry(MYLDAP_SEARCH *search, int *rcp) { int rc; int parserc; - int msgid; struct timeval tv, *tvp; LDAPControl **resultcontrols; - LDAPControl *serverctrls[2]; ber_int_t count; /* check parameters */ if ((search == NULL) || (search->session == NULL) || (search->session->ld == NULL)) @@ -1526,29 +1525,9 @@ MYLDAP_ENTRY *myldap_get_entry(MYLDAP_SEARCH *search, int *rcp) return NULL; } /* try the next page */ - serverctrls[0] = NULL; - serverctrls[1] = NULL; - rc = ldap_create_page_control(search->session->ld, nslcd_cfg->pagesize, - search->cookie, 0, &serverctrls[0]); - if (rc != LDAP_SUCCESS) - { - if (serverctrls[0] != NULL) - ldap_control_free(serverctrls[0]); - myldap_err(LOG_WARNING, search->session->ld, rc, "ldap_create_page_control() failed"); - myldap_search_close(search); - if (rcp != NULL) - *rcp = rc; - return NULL; - } - /* set up a new search for the next page */ - rc = ldap_search_ext(search->session->ld, - search->base, search->scope, search->filter, - search->attrs, 0, serverctrls, NULL, NULL, - LDAP_NO_LIMIT, &msgid); - ldap_control_free(serverctrls[0]); + rc = do_try_search(search); if (rc != LDAP_SUCCESS) { - myldap_err(LOG_WARNING, search->session->ld, rc, "ldap_search_ext() failed"); /* close connection on connection problems */ if ((rc == LDAP_UNAVAILABLE) || (rc == LDAP_SERVER_DOWN)) do_close(search->session); @@ -1557,7 +1536,6 @@ MYLDAP_ENTRY *myldap_get_entry(MYLDAP_SEARCH *search, int *rcp) *rcp = rc; return NULL; } - search->msgid = msgid; /* we continue with another pass */ break; case LDAP_RES_SEARCH_REFERENCE: -- cgit v1.2.3 From f009c96b36f3bf00c091097cec9496752845e7c6 Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Fri, 3 Jan 2014 21:25:32 +0100 Subject: Ignore missing page controls Since we could get arbitrray controls and are only interested in page controls we ignore failures to find page controls. --- nslcd/myldap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nslcd/myldap.c b/nslcd/myldap.c index e2daf17..57c420c 100644 --- a/nslcd/myldap.c +++ b/nslcd/myldap.c @@ -1499,7 +1499,8 @@ MYLDAP_ENTRY *myldap_get_entry(MYLDAP_SEARCH *search, int *rcp) &count, &(search->cookie)); if (rc != LDAP_SUCCESS) { - myldap_err(LOG_WARNING, search->session->ld, rc, "ldap_parse_page_control() failed"); + if (rc != LDAP_CONTROL_NOT_FOUND) + myldap_err(LOG_WARNING, search->session->ld, rc, "ldap_parse_page_control() failed"); /* clear error flag */ rc = LDAP_SUCCESS; if (ldap_set_option(search->session->ld, LDAP_OPT_ERROR_NUMBER, -- cgit v1.2.3 From c22eb0891e9174e43114b9dfa6b9240f98dbf489 Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Sat, 4 Jan 2014 19:47:58 +0100 Subject: Rename entry property to indicate storage type This changes entrye->rangedattributevalues to entry->buffers because the propery is not only used for ranged attribute values but for anything that can be freed with free(). --- nslcd/myldap.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/nslcd/myldap.c b/nslcd/myldap.c index 57c420c..a57f2f9 100644 --- a/nslcd/myldap.c +++ b/nslcd/myldap.c @@ -130,9 +130,9 @@ struct myldap_search { done per returned entry. */ #define MAX_ATTRIBUTES_PER_ENTRY 16 -/* The maximum number of ranged attribute values that may be stoted - per entry. */ -#define MAX_RANGED_ATTRIBUTES_PER_ENTRY 8 +/* The maximum number of buffers (used for ranged attribute values and + values returned by bervalues_to_values()) that may be stored per entry. */ +#define MAX_BUFFERS_PER_ENTRY 8 /* A single entry from the LDAP database as returned by myldap_get_entry(). */ @@ -146,8 +146,8 @@ struct myldap_entry { char **exploded_rdn; /* a cache of attribute to value list */ char **attributevalues[MAX_ATTRIBUTES_PER_ENTRY]; - /* a reference to ranged attribute values so we can free() them later on */ - char **rangedattributevalues[MAX_RANGED_ATTRIBUTES_PER_ENTRY]; + /* a reference to buffers so we can free() them later on */ + char **buffers[MAX_BUFFERS_PER_ENTRY]; }; /* Flag to record first search operation */ @@ -206,8 +206,8 @@ static MYLDAP_ENTRY *myldap_entry_new(MYLDAP_SEARCH *search) entry->exploded_rdn = NULL; for (i = 0; i < MAX_ATTRIBUTES_PER_ENTRY; i++) entry->attributevalues[i] = NULL; - for (i = 0; i < MAX_RANGED_ATTRIBUTES_PER_ENTRY; i++) - entry->rangedattributevalues[i] = NULL; + for (i = 0; i < MAX_BUFFERS_PER_ENTRY; i++) + entry->buffers[i] = NULL; /* return the fresh entry */ return entry; } @@ -225,10 +225,10 @@ static void myldap_entry_free(MYLDAP_ENTRY *entry) for (i = 0; i < MAX_ATTRIBUTES_PER_ENTRY; i++) if (entry->attributevalues[i] != NULL) ldap_value_free(entry->attributevalues[i]); - /* free all ranged attribute values */ - for (i = 0; i < MAX_RANGED_ATTRIBUTES_PER_ENTRY; i++) - if (entry->rangedattributevalues[i] != NULL) - free(entry->rangedattributevalues[i]); + /* free all buffers */ + for (i = 0; i < MAX_BUFFERS_PER_ENTRY; i++) + if (entry->buffers[i] != NULL) + free(entry->buffers[i]); /* we don't need the result anymore, ditch it. */ ldap_msgfree(entry->search->msg); entry->search->msg = NULL; @@ -1769,14 +1769,14 @@ const char **myldap_get_values(MYLDAP_ENTRY *entry, const char *attr) if (values == NULL) return NULL; /* store values entry so we can free it later on */ - for (i = 0; i < MAX_RANGED_ATTRIBUTES_PER_ENTRY; i++) - if (entry->rangedattributevalues[i] == NULL) + for (i = 0; i < MAX_BUFFERS_PER_ENTRY; i++) + if (entry->buffers[i] == NULL) { - entry->rangedattributevalues[i] = values; - return (const char **)entry->rangedattributevalues[i]; + entry->buffers[i] = values; + return (const char **)entry->buffers[i]; } /* we found no room to store the values */ - log_log(LOG_ERR, "ldap_get_values() couldn't store results, increase MAX_RANGED_ATTRIBUTES_PER_ENTRY"); + log_log(LOG_ERR, "ldap_get_values() couldn't store results, increase MAX_BUFFERS_PER_ENTRY"); free(values); return NULL; } @@ -1893,14 +1893,14 @@ const char **myldap_get_values_len(MYLDAP_ENTRY *entry, const char *attr) if (values == NULL) return NULL; /* store values entry so we can free it later on */ - for (i = 0; i < MAX_RANGED_ATTRIBUTES_PER_ENTRY; i++) - if (entry->rangedattributevalues[i] == NULL) + for (i = 0; i < MAX_BUFFERS_PER_ENTRY; i++) + if (entry->buffers[i] == NULL) { - entry->rangedattributevalues[i] = (char **)values; + entry->buffers[i] = (char **)values; return values; } /* we found no room to store the values */ - log_log(LOG_ERR, "myldap_get_values_len() couldn't store results, increase MAX_RANGED_ATTRIBUTES_PER_ENTRY"); + log_log(LOG_ERR, "myldap_get_values_len() couldn't store results, increase MAX_BUFFERS_PER_ENTRY"); free(values); return NULL; } -- cgit v1.2.3 From 547e4792c580b67ec14595e23a08836825424171 Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Sun, 5 Jan 2014 18:16:58 +0100 Subject: Request attribute deref via search control This uses the LDAP_CONTROL_X_DEREF control as descibed in draft-masarati-ldap-deref-00 to request the LDAP server to dereference member attribute values to uid attribute values in order to avoid doing extra searches. This control is currently only added for group search by looking for the member attribute in the search. --- configure.ac | 1 + nslcd/myldap.c | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 411c9e5..cecb358 100644 --- a/configure.ac +++ b/configure.ac @@ -806,6 +806,7 @@ then AC_CHECK_FUNCS(ldap_create_control ldap_control_find) AC_CHECK_FUNCS(ldap_controls_free ldap_control_free) AC_CHECK_FUNCS(ldap_parse_passwordpolicy_control ldap_passwordpolicy_err2txt) + AC_CHECK_FUNCS(ldap_create_deref_control) # replace ldap_create_page_control() and ldap_parse_page_control() AC_CHECK_FUNCS(ldap_create_page_control ldap_parse_page_control,, [AC_LIBOBJ(pagectrl)]) diff --git a/nslcd/myldap.c b/nslcd/myldap.c index a57f2f9..722109d 100644 --- a/nslcd/myldap.c +++ b/nslcd/myldap.c @@ -72,6 +72,7 @@ #include "cfg.h" #include "common/set.h" #include "compat/ldap_compat.h" +#include "attmap.h" /* the maximum number of searches per session */ #define MAX_SEARCHES_IN_SESSION 4 @@ -1055,7 +1056,12 @@ static int do_try_search(MYLDAP_SEARCH *search) { int ctrlidx = 0; int rc; - LDAPControl *serverctrls[2]; + LDAPControl *serverctrls[3]; +#ifdef HAVE_LDAP_CREATE_DEREF_CONTROL + int i; + struct LDAPDerefSpec ds[2]; + char *deref_attrs[2]; +#endif /* HAVE_LDAP_CREATE_DEREF_CONTROL */ int msgid; /* ensure that we have an open connection */ rc = do_open(search->session); @@ -1078,6 +1084,33 @@ static int do_try_search(MYLDAP_SEARCH *search) return rc; } } +#ifdef HAVE_LDAP_CREATE_DEREF_CONTROL + /* if doing group searches, add deref control to search request + (this is currently a bit of a hack and hard-coded for group searches + which are detected by requesting the attmap_group_member member + attribute) */ + for (i = 0; search->attrs[i] != NULL; i++) + if (strcasecmp(search->attrs[i], attmap_group_member) == 0) + { + /* attributes from dereff'd entries */ + deref_attrs[0] = (void *)attmap_passwd_uid; + deref_attrs[1] = NULL; + /* build deref control */ + ds[0].derefAttr = (void *)attmap_group_member; + ds[0].attributes = deref_attrs; + ds[1].derefAttr = NULL; + ds[1].attributes = NULL; + rc = ldap_create_deref_control(search->session->ld, ds, 0, &serverctrls[ctrlidx]); + if (rc == LDAP_SUCCESS) + ctrlidx++; + else + { + myldap_err(LOG_WARNING, search->session->ld, rc, + "ldap_create_deref_control() failed"); + serverctrls[ctrlidx] = NULL; + } + } +#endif /* HAVE_LDAP_CREATE_DEREF_CONTROL */ /* NULL terminate control list */ serverctrls[ctrlidx] = NULL; /* clear error flag (perhaps control setting failed) */ -- cgit v1.2.3 From 15ee2fce08794ec82d2a08b9c01339c0db0a4725 Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Sat, 28 Dec 2013 10:51:06 +0100 Subject: Provide replacement ldap_create_deref_control() This adds a test for a bug in OpenLDAP that allocated a LDAP_CONTROL_PAGEDRESULTS control instead of a LDAP_CONTROL_X_DEREF control. --- compat/Makefile.am | 1 + compat/derefctrl.c | 50 ++++++++++++++++++++++++++++++++++++++++++ compat/ldap_compat.h | 13 ++++++++++- configure.ac | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 compat/derefctrl.c diff --git a/compat/Makefile.am b/compat/Makefile.am index 361c9be..bc8e4cc 100644 --- a/compat/Makefile.am +++ b/compat/Makefile.am @@ -29,6 +29,7 @@ EXTRA_DIST = getopt_long.c getopt_long.h \ nss_compat.h socket.h \ ldap_compat.h pagectrl.c ldap_passwd_s.c ldap_initialize.c \ ldap_parse_passwordpolicy_control.c ldap_passwordpolicy_err2txt.c \ + derefctrl.c \ pam_compat.h pam_get_authtok.c pam_prompt.c libcompat_a_SOURCES = getpeercred.c getpeercred.h diff --git a/compat/derefctrl.c b/compat/derefctrl.c new file mode 100644 index 0000000..9676c55 --- /dev/null +++ b/compat/derefctrl.c @@ -0,0 +1,50 @@ +/* + derefctrl.c - replacement function + + Copyright (C) 2013 Arthur de Jong + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "config.h" + +#include +#include +#include +#include + +#include "compat/ldap_compat.h" +#include "compat/attrs.h" + +#ifdef REPLACE_LDAP_CREATE_DEREF_CONTROL +int replacement_ldap_create_deref_control(LDAP *ld, LDAPDerefSpec *ds, + int iscritical, LDAPControl **ctrlp) +{ + int rc; + struct berval value; + if (ctrlp == NULL) + return LDAP_PARAM_ERROR; + rc = ldap_create_deref_control_value(ld, ds, &value); + if (rc != LDAP_SUCCESS) + return rc; + rc = ldap_control_create(LDAP_CONTROL_X_DEREF, iscritical, &value, 0, ctrlp); + if (rc != LDAP_SUCCESS) + { + ber_memfree(value.bv_val); + } + return rc; +} +#endif /* REPLACE_LDAP_CREATE_DEREF_CONTROL */ diff --git a/compat/ldap_compat.h b/compat/ldap_compat.h index 6e9c6b1..b69974f 100644 --- a/compat/ldap_compat.h +++ b/compat/ldap_compat.h @@ -1,7 +1,7 @@ /* ldap_compat.h - provide a replacement definitions for some ldap functions - Copyright (C) 2009, 2010, 2012, 2013 Arthur de Jong + Copyright (C) 2009-2013 Arthur de Jong This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -80,6 +80,14 @@ int ldap_parse_passwordpolicy_control(LDAP *ld, LDAPControl *ctrl, const char *ldap_passwordpolicy_err2txt(LDAPPasswordPolicyError error); #endif /* HAVE_LDAP_PASSWORDPOLICY_ERR2TXT */ +#ifdef REPLACE_LDAP_CREATE_DEREF_CONTROL +/* provide a replacement implementation of ldap_create_deref_control() */ +int replacement_ldap_create_deref_control(LDAP *ld, LDAPDerefSpec *ds, + int iscritical, LDAPControl **ctrlp); +#define ldap_create_deref_control(ld, dc, iscritical, ctrlp) \ + replacement_ldap_create_deref_control(ld, dc, iscritical, ctrlp) +#endif /* REPLACE_LDAP_CREATE_DEREF_CONTROL */ + /* compatibility definition */ #ifndef LDAP_SASL_QUIET #define LDAP_SASL_QUIET 2U @@ -106,5 +114,8 @@ const char *ldap_passwordpolicy_err2txt(LDAPPasswordPolicyError error); #ifndef LDAP_CONTROL_PASSWORDPOLICYRESPONSE #define LDAP_CONTROL_PASSWORDPOLICYRESPONSE "1.3.6.1.4.1.42.2.27.8.5.1" #endif /* LDAP_CONTROL_PASSWORDPOLICYRESPONSE */ +#ifndef LDAP_CONTROL_X_DEREF +#define LDAP_CONTROL_X_DEREF "1.3.6.1.4.1.4203.666.5.16" +#endif /* LDAP_CONTROL_X_DEREF */ #endif /* COMPAT__LDAP_COMPAT_H */ diff --git a/configure.ac b/configure.ac index cecb358..907446e 100644 --- a/configure.ac +++ b/configure.ac @@ -803,10 +803,10 @@ then AC_CHECK_FUNCS(ldap_get_values ldap_value_free) AC_CHECK_FUNCS(ldap_get_values_len ldap_count_values_len ldap_value_free_len) AC_CHECK_FUNCS(ldap_err2string ldap_abandon) - AC_CHECK_FUNCS(ldap_create_control ldap_control_find) + AC_CHECK_FUNCS(ldap_control_create ldap_create_control ldap_control_find) AC_CHECK_FUNCS(ldap_controls_free ldap_control_free) AC_CHECK_FUNCS(ldap_parse_passwordpolicy_control ldap_passwordpolicy_err2txt) - AC_CHECK_FUNCS(ldap_create_deref_control) + AC_CHECK_FUNCS(ldap_create_deref_control ldap_create_deref_control_value) # replace ldap_create_page_control() and ldap_parse_page_control() AC_CHECK_FUNCS(ldap_create_page_control ldap_parse_page_control,, [AC_LIBOBJ(pagectrl)]) @@ -861,6 +861,64 @@ then [Define to 1 if ldap_set_rebind_proc() returns void.]) fi + # check for broken implementations of ldap_create_deref_control() + if test "x$ac_cv_func_ldap_create_deref_control" = "xyes" + then + # this bug cannot be determined on compile time so we run a + # small test program + AC_CACHE_CHECK( + [ldap_create_deref_control() implementation], + nslcd_cv_ldap_create_deref_control_working, + [AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ + #include + #include + #include + ]], [[ + int rc; + LDAP *ld; + LDAPControl *ctrls[2] = {NULL, NULL}; + struct LDAPDerefSpec ds[2]; + char *attrs[2] = {"uid", NULL}; + ld = ldap_init("localhost", LDAP_PORT); + if (ld == NULL) + { + fprintf(stderr, "ldap_init() failed\n"); + return 2; + } + ds[0].derefAttr = "member"; + ds[0].attributes = attrs; + ds[1].derefAttr = NULL; + rc = ldap_create_deref_control(ld, ds, 0, &ctrls[0]); + if (rc != LDAP_SUCCESS) + { + fprintf(stderr, "ldap_create_deref_control() failed: %s\n", + ldap_err2string(rc)); + return 2; + } + if (ldap_control_find(LDAP_CONTROL_X_DEREF, ctrls, NULL) != NULL) + return 0; + if (ldap_control_find(LDAP_CONTROL_PAGEDRESULTS, ctrls, NULL) != NULL) + { + fprintf(stderr, "ldap_create_deref_control() created LDAP_CONTROL_PAGEDRESULTS control\n"); + return 3; + } + fprintf(stderr, "ldap_create_deref_control() created unknown control\n"); + return 2; + ]])], + [nslcd_cv_ldap_create_deref_control_working=ok], + [if test "$?" -eq 3; then nslcd_cv_ldap_create_deref_control_working=broken + else nslcd_cv_ldap_create_deref_control_working=unknown; fi], + [nslcd_cv_ldap_create_deref_control_working=cross])]) + if test "x$nslcd_cv_ldap_create_deref_control_working" != "xok" + then + AC_MSG_NOTICE([using replacement ldap_create_deref_control()]) + AC_LIBOBJ(derefctrl) + AC_DEFINE(REPLACE_LDAP_CREATE_DEREF_CONTROL, 1, + [Define to 1 if ldap_create_deref_control() is broken.]) + fi + fi + # save nslcd LIBS and CFLAGS and restore originals nslcd_CFLAGS="$CFLAGS" nslcd_LIBS="$LIBS" -- cgit v1.2.3 From 3992e15ffd0a4f0d8130b6ac25163ab90d064c27 Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Sun, 5 Jan 2014 18:32:21 +0100 Subject: Skip member attributes in bymember search This changes the group by member searches to not request the member attributes. This will speed up result parsing by a fraction because less data is transferred but will also cause the deref control not to be added to these searches. --- nslcd/group.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/nslcd/group.c b/nslcd/group.c index 1455930..da0653d 100644 --- a/nslcd/group.c +++ b/nslcd/group.c @@ -78,6 +78,9 @@ static const char *default_group_userPassword = "*"; /* unmatchable */ /* the attribute list to request with searches */ static const char **group_attrs = NULL; +/* the attribute list for bymember searches (without member attributes) */ +static const char **group_bymember_attrs = NULL; + /* create a search filter for searching a group entry by name, return -1 on errors */ static int mkfilter_group_byname(const char *name, @@ -181,6 +184,18 @@ void group_init(void) exit(EXIT_FAILURE); } set_free(set); + /* set up bymember attribute list */ + set = set_new(); + attmap_add_attributes(set, attmap_group_cn); + attmap_add_attributes(set, attmap_group_userPassword); + attmap_add_attributes(set, attmap_group_gidNumber); + group_bymember_attrs = set_tolist(set); + if (group_bymember_attrs == NULL) + { + log_log(LOG_CRIT, "malloc() failed to allocate memory"); + exit(EXIT_FAILURE); + } + set_free(set); } static int do_write_group(TFILE *fp, MYLDAP_ENTRY *entry, @@ -447,7 +462,7 @@ int nslcd_group_bymember(TFILE *fp, MYLDAP_SESSION *session) { /* do the LDAP search */ search = myldap_search(session, base, group_scope, filter, - group_attrs, NULL); + group_bymember_attrs, NULL); if (search == NULL) { if (seen != NULL) @@ -497,7 +512,7 @@ int nslcd_group_bymember(TFILE *fp, MYLDAP_SESSION *session) /* do the LDAP searches */ for (i = 0; (base = group_bases[i]) != NULL; i++) { - search = myldap_search(session, base, group_scope, filter, group_attrs, NULL); + search = myldap_search(session, base, group_scope, filter, group_bymember_attrs, NULL); if (search != NULL) { while ((entry = myldap_get_entry(search, NULL)) != NULL) -- cgit v1.2.3 From c973834328baa69dbd3352182431421b2b9a2319 Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Sun, 5 Jan 2014 14:15:21 +0100 Subject: Provide a myldap_get_deref_values() function This function looks for deref response controls (LDAP_CONTROL_X_DEREF) in the entry and returns the information from the dereferenced attribute in two lists: dereferenced values and attribute values that could not be dereferenced. --- configure.ac | 3 +- nslcd/myldap.c | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ nslcd/myldap.h | 9 +++- 3 files changed, 158 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 907446e..cd776e0 100644 --- a/configure.ac +++ b/configure.ac @@ -804,9 +804,10 @@ then AC_CHECK_FUNCS(ldap_get_values_len ldap_count_values_len ldap_value_free_len) AC_CHECK_FUNCS(ldap_err2string ldap_abandon) AC_CHECK_FUNCS(ldap_control_create ldap_create_control ldap_control_find) - AC_CHECK_FUNCS(ldap_controls_free ldap_control_free) + AC_CHECK_FUNCS(ldap_controls_free ldap_control_free ldap_get_entry_controls) AC_CHECK_FUNCS(ldap_parse_passwordpolicy_control ldap_passwordpolicy_err2txt) AC_CHECK_FUNCS(ldap_create_deref_control ldap_create_deref_control_value) + AC_CHECK_FUNCS(ldap_parse_deref_control ldap_derefresponse_free) # replace ldap_create_page_control() and ldap_parse_page_control() AC_CHECK_FUNCS(ldap_create_page_control ldap_parse_page_control,, [AC_LIBOBJ(pagectrl)]) diff --git a/nslcd/myldap.c b/nslcd/myldap.c index 722109d..9e0bc6e 100644 --- a/nslcd/myldap.c +++ b/nslcd/myldap.c @@ -2080,6 +2080,154 @@ int myldap_has_objectclass(MYLDAP_ENTRY *entry, const char *objectclass) return 0; } +#ifdef HAVE_LDAP_PARSE_DEREF_CONTROL +const char ***myldap_get_deref_values(MYLDAP_ENTRY *entry, + const char *derefattr, const char *getattr) +{ + LDAPControl **entryctrls; + LDAPDerefRes *deref, *d; + LDAPDerefVal *a; + int i, pass; + int rc; + int found; + int counts[2]; + size_t sizes[2], size; + char *buffer = NULL; + char ***results = NULL; + rc = ldap_get_entry_controls(entry->search->session->ld, entry->search->msg, + &entryctrls); + if (rc != LDAP_SUCCESS) + { + myldap_err(LOG_WARNING, entry->search->session->ld, rc, + "ldap_get_entry_controls() failed"); + return NULL; + } + if (entryctrls == NULL) + return NULL; + /* see if we can find a deref control */ + rc = ldap_parse_deref_control(entry->search->session->ld, entryctrls, + &deref); + if ((rc != LDAP_SUCCESS) || (deref == NULL)) + { + if ((rc != LDAP_SUCCESS) && (rc != LDAP_CONTROL_NOT_FOUND)) + myldap_err(LOG_WARNING, entry->search->session->ld, rc, + "ldap_parse_deref_control() failed"); + /* clear error flag */ + rc = LDAP_SUCCESS; + if (ldap_set_option(entry->search->session->ld, LDAP_OPT_ERROR_NUMBER, + &rc) != LDAP_SUCCESS) + log_log(LOG_WARNING, "failed to clear the error flag"); + ldap_controls_free(entryctrls); + return NULL; + } + /* two passes: one to calculate size, one to store data */ + for (pass=0; pass < 2; pass++) + { + /* reset counters and size */ + for (i = 0; i < 2; i++) + { + counts[i] = 0; + sizes[i] = 0; + } + /* go over all deref'd attributes and find the one we're looking for */ + for (d = deref; d != NULL; d = d->next) + if ((d->derefAttr != NULL) && (d->derefVal.bv_val != NULL) && + (strcasecmp(derefattr, d->derefAttr) == 0)) + { + /* we should have one d per original attribute value */ + found = 0; + /* go over deref'd attribute values to find the ones we're looking for */ + for (a = d->attrVals; a != NULL; a = a->next) + if ((a->type != NULL) && (a->vals != NULL) && + (strcasecmp(getattr, a->type) == 0)) + for (i=0; a->vals[i].bv_val != NULL; i++) + { + found = 1; + if (results == NULL) + { + log_log(LOG_DEBUG, "deref %s %s=%s -> %s=%s", + myldap_get_dn(entry), d->derefAttr, d->derefVal.bv_val, + a->type, a->vals[i].bv_val); + counts[0]++; + sizes[0] += strlen(a->vals[i].bv_val) + 1; + } + else + { + strcpy(buffer, a->vals[i].bv_val); + results[0][counts[0]++] = buffer; + buffer += strlen(buffer) + 1; + } + } + if (!found) + { + if (results == NULL) + { + log_log(LOG_DEBUG, "no %s deref %s %s=%s", getattr, + myldap_get_dn(entry), d->derefAttr, d->derefVal.bv_val); + counts[1]++; + sizes[1] += strlen(d->derefVal.bv_val) + 1; + } + else + { + strcpy(buffer, d->derefVal.bv_val); + results[1][counts[1]++] = buffer; + buffer += strlen(buffer) + 1; + } + } + } + /* allocate memory after first pass */ + if (results == NULL) + { + size = sizeof(char **) * 3; + for (i = 0; i < 2; i++) + size += sizeof(char *) * (counts[i] + 1); + for (i = 0; i < 2; i++) + size += sizeof(char) * sizes[i]; + buffer = (char *)malloc(size); + if (buffer == NULL) + { + log_log(LOG_CRIT, "myldap_get_deref_values(): malloc() failed to allocate memory"); + return NULL; + } + /* allocate the list of lists */ + results = (void *)buffer; + buffer += sizeof(char **) * 3; + /* allocate the lists */ + for (i = 0; i < 2; i++) + { + results[i] = (char **)buffer; + buffer += sizeof(char *) * (counts[i] + 1); + } + results[i] = NULL; + } + } + /* NULL terminate the lists */ + results[0][counts[0]] = NULL; + results[1][counts[1]] = NULL; + /* free control data */ + ldap_derefresponse_free(deref); + ldap_controls_free(entryctrls); + /* store results so we can free it later on */ + for (i = 0; i < MAX_BUFFERS_PER_ENTRY; i++) + if (entry->buffers[i] == NULL) + { + entry->buffers[i] = (void *)results; + return (const char ***)results; + } + /* we found no room to store the values */ + log_log(LOG_ERR, "myldap_get_deref_values() couldn't store results, " + "increase MAX_BUFFERS_PER_ENTRY"); + free(results); + return NULL; +} +#else /* not HAVE_LDAP_PARSE_DEREF_CONTROL */ +const char ***myldap_get_deref_values(MYLDAP_ENTRY UNUSED(*entry), + const char UNUSED(*derefattr), const char UNUSED(*getattr)) +{ + return NULL; +} +#endif /* not HAVE_LDAP_PARSE_DEREF_CONTROL */ + int myldap_escape(const char *src, char *buffer, size_t buflen) { size_t pos = 0; diff --git a/nslcd/myldap.h b/nslcd/myldap.h index 8c4551a..c7358af 100644 --- a/nslcd/myldap.h +++ b/nslcd/myldap.h @@ -2,7 +2,7 @@ myldap.h - simple interface to do LDAP requests This file is part of the nss-pam-ldapd library. - Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Arthur de Jong + Copyright (C) 2007-2014 Arthur de Jong This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -133,6 +133,13 @@ MUST_USE const char **myldap_get_values_len(MYLDAP_ENTRY *entry, const char *att /* Checks to see if the entry has the specified object class. */ MUST_USE int myldap_has_objectclass(MYLDAP_ENTRY *entry, const char *objectclass); +/* See if the entry has any deref controls attached to it and deref attr + derefattr to get the getattr values. Will return two lists of attribute + values. One list of deref'ed attribute values and one list of original + attribute values that could not be deref'ed. */ +MUST_USE const char ***myldap_get_deref_values(MYLDAP_ENTRY *entry, + const char *derefattr, const char *getattr); + /* Get the RDN's value: eg. if the DN was cn=lukeh, ou=People, dc=example, dc=com getrdnvalue(entry, cn) would return lukeh. If the attribute was not found in the DN or if some error occurs NULL is returned. This method may -- cgit v1.2.3 From cecc02451efa40f0b6418b3fd6bca39448fb99a8 Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Sun, 5 Jan 2014 14:59:11 +0100 Subject: Use myldap_get_deref_values() to get member uids This uses information from the deref control (if available) to get the username for each of the members of the group. Any missing deref member attribute values will be seen as nested groups and will be traversed if nested group support is enabled. --- nslcd/group.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/nslcd/group.c b/nslcd/group.c index da0653d..ffaeb80 100644 --- a/nslcd/group.c +++ b/nslcd/group.c @@ -234,6 +234,7 @@ static void getmembers(MYLDAP_ENTRY *entry, MYLDAP_SESSION *session, char buf[BUFLEN_NAME]; int i; const char **values; + const char ***derefs; /* add the memberUid values */ values = myldap_get_values(entry, attmap_group_memberUid); if (values != NULL) @@ -246,6 +247,26 @@ static void getmembers(MYLDAP_ENTRY *entry, MYLDAP_SESSION *session, /* skip rest if attmap_group_member is blank */ if (strcasecmp(attmap_group_member, "\"\"") == 0) return; + /* add deref'd entries if we have them*/ + derefs = myldap_get_deref_values(entry, attmap_group_member, attmap_passwd_uid); + if (derefs != NULL) + { + /* add deref'd uid attributes */ + for (i = 0; derefs[0][i] != NULL; i++) + set_add(members, derefs[0][i]); + /* add non-deref'd attribute values as subgroups */ + for (i = 0; derefs[1][i] != NULL; i++) + { + if ((seen == NULL) || (!set_contains(seen, derefs[1][i]))) + { + if (seen != NULL) + set_add(seen, derefs[1][i]); + if (subgroups != NULL) + set_add(subgroups, derefs[1][i]); + } + } + return; /* no need to parse the member attribute ourselves */ + } /* add the member values */ values = myldap_get_values(entry, attmap_group_member); if (values != NULL) -- cgit v1.2.3 From 309b4bbbc040ce9f37ccf25399eacc5294bfc34f Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Sun, 5 Jan 2014 18:02:44 +0100 Subject: Update documentation This documents the way the deref controls are used. --- README | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/README b/README index d5a996a..62184b9 100644 --- a/README +++ b/README @@ -15,7 +15,7 @@ Copyright (C) 1997-2006 Luke Howard Copyright (C) 2006-2007 West Consulting - Copyright (C) 2006-2013 Arthur de Jong + Copyright (C) 2006-2014 Arthur de Jong Copyright (C) 2009 Howard Chu Copyright (C) 2010 Symas Corporation @@ -344,18 +344,25 @@ group membership Currently, two ways of specifying group membership are supported. The first, by using the memberUid attribute, is the simplest and by far the fastest -(takes the least number of lookups). This attribute maps to user names with -the same values as the uid attribute would hold for posixAccount entries. +(takes the least number of lookups). The attribute values are user names with +the format as the uid attribute for posixAccount entries and are returned +without further processing. -The second method is to use DN values in the member attribute (attribute -names can be changed by using the attribute mapping options as described in -the manual page). This is potentially a lot slower because in the worst case -every DN has to be looked up in the LDAP server to find the proper value for -the uid attribute. +The second method is to use DN values in the member attribute (attribute names +can be changed by using the attribute mapping options as described in the +manual page). This is potentially a lot slower because in the worst case every +DN has to be looked up in the LDAP server to find the proper value for the uid +attribute. + +If the LDAP server supports the deref control (provided by the deref overlay +in OpenLDAP) the DN to uid expansing is performed by the LDAP server. If the DN value already contains a uid value (e.g. uid=arthur, dc=example, -dc=com) the lookup is skipped and the value from the DN is used. A cache is -maintained that saves the DN to uid translations for 15 minutes. +dc=com) a further lookup is skipped and the uid value from the DN is used. + +For other DN values an extra lookup is performed to expand it to a uid. These +lookups are cached and are configurable with the cache dn2uid configuration +option. The member attribute may also contain the DN of another group entry. These nested groups are parsed recursively depending on the nss_nested_groups -- cgit v1.2.3