Print this page
NEX-14547 Get UNIX group info. from AD/LDAP with partial RFC2307 schema
NEX-13132 smbd dumping core in nss_ldap.so.1`getbymember
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-13132 smbd dumping core in nss_ldap.so.1`getbymember disables ALL shares
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>

@@ -35,41 +35,46 @@
 
 /* Group attributes filters */
 #define _G_NAME         "cn"
 #define _G_GID          "gidnumber"
 #define _G_PASSWD       "userpassword"
-#define _G_MEM          "memberuid"
+#define _G_MEMUID       "memberuid"
+#define _G_MEM_DN       "member"        /* DN */
 
 #define _F_GETGRNAM     "(&(objectClass=posixGroup)(cn=%s))"
 #define _F_GETGRNAM_SSD "(&(%%s)(cn=%s))"
 #define _F_GETGRGID     "(&(objectClass=posixGroup)(gidNumber=%u))"
 #define _F_GETGRGID_SSD "(&(%%s)(gidNumber=%u))"
-/*
- * Group membership can be defined by either username or DN, so when searching
- * for groups by member we need to consider both. The first parameter in the
- * filter is replaced by username, the second by DN.
- */
-#define _F_GETGRMEM \
-        "(&(objectClass=posixGroup)(|(memberUid=%s)(memberUid=%s)))"
-#define _F_GETGRMEM_SSD "(&(%%s)(|(memberUid=%s)(memberUid=%s)))"
 
 /*
- * Copied from getpwnam.c, needed to look up user DN.
- * Would it be better to move to ldap_common.h rather than duplicate?
+ * When searching for groups in which a specified user is a member,
+ * there are a few different membership schema that might be in use.
+ * We'll use a filter that should work with an of the common ones:
+ * "memberUid=NAME", or "member=DN" (try uniquemember too?)
+ * The first parameter in the filter string is replaced by username,
+ * and the remaining ones by the full DN.
  */
-#define _F_GETPWNAM     "(&(objectClass=posixAccount)(uid=%s))"
-#define _F_GETPWNAM_SSD "(&(%%s)(uid=%s))"
+#define _F_GETGRMEM "(&(objectClass=posixGroup)" \
+        "(|(memberUid=%s)(member=%s)))"
+#define _F_GETGRMEM_SSD "(&(%%s)" \
+        "(|(memberUid=%s)(member=%s)))"
 
 static const char *gr_attrs[] = {
         _G_NAME,
         _G_GID,
         _G_PASSWD,
-        _G_MEM,
+        _G_MEMUID,
+        _G_MEM_DN,
         (char *)NULL
 };
 
+static int
+getmembers_UID(char **bufpp, int *lenp, ns_ldap_attr_t *members);
+static int
+getmembers_DN(char **bufpp, int *lenp, ns_ldap_attr_t *members);
 
+
 /*
  * _nss_ldap_group2str is the data marshaling method for the group getXbyY
  * (e.g., getgrnam(), getgrgid(), getgrent()) backend processes. This method
  * is called after a successful ldap search has been performed. This method
  * will parse the ldap search values into the file format.

@@ -83,17 +88,15 @@
 _nss_ldap_group2str(ldap_backend_ptr be, nss_XbyY_args_t *argp)
 {
         int             i;
         int             nss_result;
         int             buflen = 0, len;
-        int             firstime = 1;
         char            *buffer = NULL;
         ns_ldap_result_t        *result = be->result;
         char            **gname, **passwd, **gid, *password, *end;
         char            gid_nobody[NOBODY_STR_LEN];
         char            *gid_nobody_v[1];
-        char            *member_str, *strtok_state;
         ns_ldap_attr_t  *members;
 
         (void) snprintf(gid_nobody, sizeof (gid_nobody), "%u", GID_NOBODY);
         gid_nobody_v[0] = gid_nobody;
 

@@ -144,57 +147,151 @@
         if (strtoul(gid[0], &end, 10) > MAXUID)
                 gid = gid_nobody_v;
         len = snprintf(buffer, buflen, "%s:%s:%s:", gname[0], password, gid[0]);
         TEST_AND_ADJUST(len, buffer, buflen, result_grp2str);
 
-        members = __ns_ldap_getAttrStruct(result->entry, _G_MEM);
-        if (members == NULL || members->attrvalue == NULL) {
-                /* no member is fine, skip processing the member list */
-                goto nomember;
+        members = __ns_ldap_getAttrStruct(result->entry, _G_MEMUID);
+        if (members != NULL && members->attrvalue != NULL) {
+                nss_result = getmembers_UID(&buffer, &buflen, members);
+                if (nss_result != 0)
+                        goto result_grp2str;
         }
 
-        for (i = 0; i < members->value_count; i++) {
-                if (members->attrvalue[i] == NULL) {
-                        nss_result = NSS_STR_PARSE_PARSE;
+        members = __ns_ldap_getAttrStruct(result->entry, _G_MEM_DN);
+        if (members != NULL && members->attrvalue != NULL) {
+                nss_result = getmembers_DN(&buffer, &buflen, members);
+                if (nss_result != 0)
                         goto result_grp2str;
                 }
-                /*
-                 * If we find an '=' in the member attribute value, treat it as
-                 * a DN, otherwise as a username.
+
+        /* The front end marshaller doesn't need the trailing nulls */
+        if (argp->buf.result != NULL)
+                be->buflen = strlen(be->buffer);
+result_grp2str:
+        (void) __ns_ldap_freeResult(&be->result);
+        return (nss_result);
+}
+
+/*
+ * Process the list values from the "memberUid" attribute of the
+ * current group.  Note that this list is often empty, and we
+ * get the real list of members via getmember_DN (see below).
                  */
-                if (member_str = strchr(members->attrvalue[i], '=')) {
-                        member_str++; /* skip over the '=' */
-                        /* Fail if we can't pull a username out of the RDN */
-                        if (! (member_str = strtok_r(member_str,
-                            ",", &strtok_state))) {
-                                nss_result = NSS_STR_PARSE_PARSE;
-                                goto result_grp2str;
-                        }
-                } else {
+static int
+getmembers_UID(char **bufpp, int *lenp, ns_ldap_attr_t *members)
+{
+        char    *member_str, *strtok_state;
+        char    *buffer;
+        int     buflen;
+        int     i, len;
+        int     nss_result = 0;
+        int     firsttime;
+
+        buffer = *bufpp;
+        buflen = *lenp;
+        firsttime = (buffer[-1] == ':');
+
+        for (i = 0; i < members->value_count; i++) {
                         member_str = members->attrvalue[i];
-                }
-                if (*member_str != '\0') {
-                        if (firstime) {
-                                len = snprintf(buffer, buflen, "%s",
+                if (member_str == NULL)
+                        goto out;
+
+#ifdef DEBUG
+                (void) fprintf(stdout, "getmembers_UID: uid=<%s>\n",
                                     member_str);
-                                TEST_AND_ADJUST(len, buffer, buflen,
-                                    result_grp2str);
-                                firstime = 0;
-                        } else {
-                                len = snprintf(buffer, buflen, ",%s",
-                                    member_str);
-                                TEST_AND_ADJUST(len, buffer, buflen,
-                                    result_grp2str);
+#endif
+                /*
+                 * If not a valid Unix user name, or
+                 * not valid in ldap, just skip.
+                 */
+                if (member_str[0] == '\0' ||
+                    strpbrk(member_str, " ,:=") != NULL)
+                        continue;
+
+                if (firsttime)
+                        len = snprintf(buffer, buflen, "%s", member_str);
+                else
+                        len = snprintf(buffer, buflen, ",%s", member_str);
+                TEST_AND_ADJUST(len, buffer, buflen, out);
                         }
+
+out:
+        *bufpp = buffer;
+        *lenp = buflen;
+        return (nss_result);
+}
+
+/*
+ * Process the list values from the "member" attribute of the
+ * current group.  Note that this list is ONLY one that can be
+ * assumed to be non-empty.  The problem here is that this list
+ * contains the list of members as "distinguished names" (DN),
+ * and we want the Unix names (known here as "uid").  We must
+ * lookup the "uid" for each DN in the member list.  Example:
+ * CN=Doe\, John,OU=Users,DC=contoso,DC=com => john.doe
+ */
+static int
+getmembers_DN(char **bufpp, int *lenp, ns_ldap_attr_t *members)
+{
+        ns_ldap_error_t *error = NULL;
+        char    *member_dn, *member_uid;
+        char    *buffer;
+        int     buflen;
+        int     i, len;
+        int     nss_result = 0;
+        int     firsttime;
+
+        buffer = *bufpp;
+        buflen = *lenp;
+        firsttime = (buffer[-1] == ':');
+
+        for (i = 0; i < members->value_count; i++) {
+                member_dn = members->attrvalue[i];
+                if (member_dn == NULL)
+                        goto out;
+
+                /*
+                 * The attribute name was "member", so these should be
+                 * full distinguisned names (DNs).  We need to loookup
+                 * the Unix UID (name) for each.
+                 */
+#ifdef DEBUG
+                (void) fprintf(stdout, "getmembers_DN: dn=%s\n",
+                    member_dn);
+#endif
+                if (member_dn[0] == '\0')
+                        continue;
+
+                nss_result = __ns_ldap_dn2uid(member_dn,
+                    &member_uid, NULL, &error);
+                if (nss_result != NS_LDAP_SUCCESS) {
+                        (void) __ns_ldap_freeError(&error);
+                        error = NULL;
+                        continue;
                 }
+#ifdef DEBUG
+                (void) fprintf(stdout, "getmembers_DN: uid=<%s>\n",
+                    member_uid);
+#endif
+                /* Skip invalid names. */
+                if (member_uid[0] == '\0' ||
+                    strpbrk(member_uid, " ,:=") != NULL) {
+                        free(member_uid);
+                        continue;
         }
-nomember:
-        /* The front end marshaller doesn't need the trailing nulls */
-        if (argp->buf.result != NULL)
-                be->buflen = strlen(be->buffer);
-result_grp2str:
-        (void) __ns_ldap_freeResult(&be->result);
+
+                if (firsttime)
+                        len = snprintf(buffer, buflen, "%s", member_uid);
+                else
+                        len = snprintf(buffer, buflen, ",%s", member_uid);
+                free(member_uid);
+                TEST_AND_ADJUST(len, buffer, buflen, out);
+        }
+
+out:
+        *bufpp = buffer;
+        *lenp = buflen;
         return (nss_result);
 }
 
 /*
  * getbynam gets a group entry by name. This function constructs an ldap

@@ -265,10 +362,22 @@
 
 }
 
 
 /*
+ * Use a custom attributes list for getbymember, because the LDAP
+ * query for this requests a list of groups, and the result can be
+ * very large if it includes the list of members with each group.
+ * We don't need or want the list of members in this case.
+ */
+static const char *grbymem_attrs[] = {
+        _G_NAME,        /* cn */
+        _G_GID,         /* gidnumber */
+        (char *)NULL
+};
+
+/*
  * getbymember returns all groups a user is defined in. This function
  * uses different architectural procedures than the other group backend
  * system calls because it's a private interface. This function constructs
  * an ldap search filter using the name invocation parameter. Once the
  * filter is constructed, we search for all matching groups counting

@@ -282,127 +391,109 @@
  */
 
 static nss_status_t
 getbymember(ldap_backend_ptr be, void *a)
 {
+        ns_ldap_error_t         *error = NULL;
         int                     i, j, k;
         int                     gcnt = (int)0;
-        char                    **groupvalue, **membervalue, *member_str;
-        char                    *strtok_state;
+        char                    **groupvalue;
         nss_status_t            lstat;
         struct nss_groupsbymem  *argp = (struct nss_groupsbymem *)a;
         char                    searchfilter[SEARCHFILTERLEN];
         char                    userdata[SEARCHFILTERLEN];
         char                    name[SEARCHFILTERLEN];
+        char                    escdn[SEARCHFILTERLEN];
         ns_ldap_result_t        *result;
         ns_ldap_entry_t         *curEntry;
-        char                    *username, **dn_attr, *dn;
+        char                    *dn;
         gid_t                   gid;
-        int                     ret;
+        int                     ret1, ret2;
 
         if (strcmp(argp->username, "") == 0 ||
             strcmp(argp->username, "root") == 0)
                 return ((nss_status_t)NSS_NOTFOUND);
 
         if (_ldap_filter_name(name, argp->username, sizeof (name)) != 0)
                 return ((nss_status_t)NSS_NOTFOUND);
 
-        ret = snprintf(searchfilter, sizeof (searchfilter), _F_GETPWNAM, name);
-        if (ret >= sizeof (searchfilter) || ret < 0)
-                return ((nss_status_t)NSS_NOTFOUND);
-
-        ret = snprintf(userdata, sizeof (userdata), _F_GETPWNAM_SSD, name);
-        if (ret >= sizeof (userdata) || ret < 0)
-                return ((nss_status_t)NSS_NOTFOUND);
-
         /*
          * Look up the user DN in ldap. If it's not found, search solely by
          * username.
          */
-        lstat = (nss_status_t)_nss_ldap_nocb_lookup(be, NULL,
-            _PASSWD, searchfilter, NULL, _merge_SSD_filter, userdata);
-        if (lstat != (nss_status_t)NS_LDAP_SUCCESS)
-                return ((nss_status_t)lstat);
-
-        if (be->result == NULL ||
-            !(dn_attr = __ns_ldap_getAttr(be->result->entry, "dn")))
+        lstat = __ns_ldap_uid2dn(name, &dn, NULL, &error);
+        if (lstat != (nss_status_t)NS_LDAP_SUCCESS) {
+                /* Can't get DN.  Use bare name */
+                (void) __ns_ldap_freeError(&error);
                 dn = name;
-        else
-                dn = dn_attr[0];
+        }
+        /* Note: must free dn if != name */
 
-        ret = snprintf(searchfilter, sizeof (searchfilter), _F_GETGRMEM, name,
-            dn);
-        if (ret >= sizeof (searchfilter) || ret < 0)
+        /*
+         * Compose filter patterns
+         */
+        ret1 = snprintf(searchfilter, sizeof (searchfilter),
+            _F_GETGRMEM, name, dn);
+        ret2 = snprintf(userdata, sizeof (userdata),
+            _F_GETGRMEM_SSD, name, dn);
+        if (dn != name)
+                free(dn);
+        if (ret1 >= sizeof (searchfilter) || ret1 < 0)
                 return ((nss_status_t)NSS_NOTFOUND);
-
-        ret = snprintf(userdata, sizeof (userdata), _F_GETGRMEM_SSD, name,
-            dn);
-        if (ret >= sizeof (userdata) || ret < 0)
+        if (ret2 >= sizeof (userdata) || ret2 < 0)
                 return ((nss_status_t)NSS_NOTFOUND);
 
         /*
-         * Free up resources from user DN search before performing group
-         * search.
+         * Query for groups matching the filter.
          */
-        (void) __ns_ldap_freeResult((ns_ldap_result_t **)&be->result);
-
-        gcnt = (int)argp->numgids;
         lstat = (nss_status_t)_nss_ldap_nocb_lookup(be, NULL,
-            _GROUP, searchfilter, NULL, _merge_SSD_filter, userdata);
+            _GROUP, searchfilter, grbymem_attrs,
+            _merge_SSD_filter, userdata);
         if (lstat != (nss_status_t)NS_LDAP_SUCCESS)
                 return ((nss_status_t)lstat);
         if (be->result == NULL)
                 return (NSS_NOTFOUND);
-        username = (char *)argp->username;
+
+        /*
+         * Walk the query result, collecting GIDs.
+         */
         result = (ns_ldap_result_t *)be->result;
         curEntry = (ns_ldap_entry_t *)result->entry;
-        for (i = 0; i < result->entries_count && curEntry != NULL; i++) {
-                membervalue = __ns_ldap_getAttr(curEntry, "memberUid");
-                if (membervalue == NULL) {
-                        curEntry = curEntry->next;
-                        continue;
-                }
-                for (j = 0; membervalue[j]; j++) {
+        gcnt = (int)argp->numgids;
+        for (i = 0; i < result->entries_count; i++) {
+
                         /*
-                         * If we find an '=' in the member attribute
-                         * value, treat it as a DN, otherwise as a
-                         * username.
+                 * Does this group have a gidNumber attr?
                          */
-                        if (member_str = strchr(membervalue[j], '=')) {
-                                member_str++; /* skip over the '=' */
-                                member_str = strtok_r(member_str, ",",
-                                    &strtok_state);
-                        } else {
-                                member_str = membervalue[j];
-                        }
-                        if (member_str != NULL &&
-                            strcmp(member_str, username) == 0) {
-                                groupvalue = __ns_ldap_getAttr(curEntry,
-                                    "gidnumber");
-                                if (groupvalue == NULL ||
-                                    groupvalue[0] == NULL) {
+                groupvalue = __ns_ldap_getAttr(curEntry, _G_GID);
+                if (groupvalue == NULL || groupvalue[0] == NULL) {
                                         /* Drop this group from the list */
-                                        break;
+                        goto next_group;
                                 }
+
+                /*
+                 * Convert it to a numeric GID
+                 */
                                 errno = 0;
-                                gid = (gid_t)strtol(groupvalue[0],
-                                    (char **)NULL, 10);
+                gid = (gid_t)strtol(groupvalue[0], (char **)NULL, 10);
+                if (errno != 0)
+                        goto next_group;
 
-                                if (errno == 0 &&
-                                    argp->numgids < argp->maxgids) {
+                /*
+                 * If we don't already have this GID, add it.
+                 */
+                if (argp->numgids < argp->maxgids) {
                                         for (k = 0; k < argp->numgids; k++) {
-                                                if (argp->gid_array[k] == gid)
-                                                        /* already exists */
-                                                        break;
+                                if (argp->gid_array[k] == gid) {
+                                        /* already have it */
+                                        goto next_group;
                                         }
-                                        if (k == argp->numgids)
-                                                argp->gid_array[argp->numgids++]
-                                                    = gid;
                                 }
-                                break;
+                        argp->gid_array[argp->numgids++] = gid;
                         }
-                }
+
+        next_group:
                 curEntry = curEntry->next;
         }
 
         (void) __ns_ldap_freeResult((ns_ldap_result_t **)&be->result);
         if (gcnt == argp->numgids)