Date: Tue, 17 Jul 2007 12:31:05 GMT From: Michael Hanselmann <freebsd@hansmi.ch> To: freebsd-gnats-submit@FreeBSD.org Subject: ports/114655: Implement getgroupmembership in nss_ldap Message-ID: <200707171231.l6HCV5kn090877@www.freebsd.org> Resent-Message-ID: <200707171240.l6HCe30w081338@freefall.freebsd.org>
next in thread | raw e-mail | index | archive | help
>Number: 114655 >Category: ports >Synopsis: Implement getgroupmembership in nss_ldap >Confidential: no >Severity: non-critical >Priority: medium >Responsible: freebsd-ports-bugs >State: open >Quarter: >Keywords: >Date-Required: >Class: update >Submitter-Id: current-users >Arrival-Date: Tue Jul 17 12:40:03 GMT 2007 >Closed-Date: >Last-Modified: >Originator: Michael Hanselmann >Release: 6.x, 7 >Organization: >Environment: >Description: (Copied from freebsd-current@) I was working with a company which plans to migrate its FreeBSD servers from using /etc/{passwd,group} to LDAP. They will have about 45'000 users and as much groups in the directory. Tests showed that any function retrieving the groups a user is member of, for example getgrouplist(3) or initgroups(3), is very slow. In our case, it was about 7 seconds per invocation. Further investigation showed how inefficient these functions are implemented through getgrouplist(3). FreeBSD's implementation loops through all groups and their members to check whether a user is member of it, in which case it adds the group to a list. In our case, this means retrieving 45'000 search results from the LDAP server. Directory services like LDAP or Winbind allow queries to have filters, enabling us to write a much more efficient implementation. The attached patches (nss-getgroupmembership-try9.diff for FreeBSD 6, nss-getgroupmembership-fbsd7-try3.diff for FreeBSD 7) use an nss module's getgroupmembership(3) function if available. Otherwise it uses a fallback which then uses the old algorithm with some modifications. After applying it, getgrouplist(3) takes only a few milliseconds to retrieve all groups of a user. Another patch, attached as bsdnss.diff, is needed for nss_ldap. It applies to the ports/net/nss_ldap/files/bsdnss.c file and exports the required getgroupmembership function. Most of the code there is from NetBSD. >How-To-Repeat: >Fix: Apply the patch. Patch attached with submission follows: --- bsdnss.c.orig Mon May 14 14:25:05 2007 +++ bsdnss.c Mon May 14 15:01:06 2007 @@ -1,9 +1,11 @@ +#include <stdlib.h> #include <errno.h> #include <sys/param.h> #include <netinet/in.h> #include <pwd.h> #include <grp.h> #include <nss.h> +#include <nsswitch.h> #include <netdb.h> extern enum nss_status _nss_ldap_getgrent_r(struct group *, char *, size_t, @@ -34,12 +36,15 @@ extern enum nss_status _nss_ldap_gethost extern enum nss_status _nss_ldap_gethostbyaddr_r (struct in_addr * addr, int len, int type, struct hostent * result, char *buffer, size_t buflen, int *errnop, int *h_errnop); +extern enum nss_status _nss_ldap_initgroups_dyn(const char *, gid_t, long int *, + long int *, gid_t **, long int, int *); NSS_METHOD_PROTOTYPE(__nss_compat_getgrnam_r); NSS_METHOD_PROTOTYPE(__nss_compat_getgrgid_r); NSS_METHOD_PROTOTYPE(__nss_compat_getgrent_r); NSS_METHOD_PROTOTYPE(__nss_compat_setgrent); NSS_METHOD_PROTOTYPE(__nss_compat_endgrent); +static NSS_METHOD_PROTOTYPE(__freebsd_getgroupmembership); NSS_METHOD_PROTOTYPE(__nss_compat_getpwnam_r); NSS_METHOD_PROTOTYPE(__nss_compat_getpwuid_r); @@ -57,6 +62,7 @@ static ns_mtab methods[] = { { NSDB_GROUP, "getgrent_r", __nss_compat_getgrent_r, _nss_ldap_getgrent_r }, { NSDB_GROUP, "setgrent", __nss_compat_setgrent, _nss_ldap_setgrent }, { NSDB_GROUP, "endgrent", __nss_compat_endgrent, _nss_ldap_endgrent }, +{ NSDB_GROUP, "getgroupmembership", __freebsd_getgroupmembership, NULL }, { NSDB_PASSWD, "getpwnam_r", __nss_compat_getpwnam_r, _nss_ldap_getpwnam_r }, { NSDB_PASSWD, "getpwuid_r", __nss_compat_getpwuid_r, _nss_ldap_getpwuid_r }, @@ -155,4 +161,62 @@ int __nss_compat_gethostbyaddr(void *ret status = __nss_compat_result(status,errnop); h_errno = h_errnop; return (status); +} + +static int +__gr_addgid(gid_t gid, gid_t *groups, int maxgrp, int *groupc) +{ + int ret, dupc; + + /* skip duplicates */ + for (dupc = 0; dupc < MIN(maxgrp, *groupc); dupc++) { + if (groups[dupc] == gid) + return 1; + } + + ret = 1; + if (*groupc < maxgrp) /* add this gid */ + groups[*groupc] = gid; + else + ret = 0; + (*groupc)++; + return ret; +} + +static int +__freebsd_getgroupmembership(void *retval, void *mdata, va_list ap) +{ + int err; + enum nss_status s; + int *result = va_arg(ap, int *); + const char *user = va_arg(ap, const char *); + gid_t group = va_arg(ap, gid_t); + gid_t *groups = va_arg(ap, gid_t *); + int limit = va_arg(ap, int); + int *size = va_arg(ap, int*); + gid_t *tmpgroups; + long int lstart, lsize; + int i; + + tmpgroups = malloc(limit * sizeof(gid_t)); + if (tmpgroups == NULL) + return NS_TRYAGAIN; + + /* insert primary membership */ + __gr_addgid(group, groups, limit, size); + + lstart = 0; + lsize = limit; + s = _nss_ldap_initgroups_dyn(user, group, &lstart, &lsize, + &tmpgroups, 0, &err); + if (s == NSS_STATUS_SUCCESS) { + for (i = 0; i < lstart; i++) + if (! __gr_addgid(tmpgroups[i], groups, limit, size)) + *result = -1; + s = NSS_STATUS_NOTFOUND; + } + + free(tmpgroups); + + return __nss_compat_result(s, 0); } >Release-Note: >Audit-Trail: >Unformatted:
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200707171231.l6HCV5kn090877>