/* group.c - group entry lookup routines Parts of this file were part of the nss_ldap library (as ldap-grp.c) which has been forked into the nss-pam-ldapd library. Copyright (C) 1997-2006 Luke Howard Copyright (C) 2006 West Consulting Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Arthur de Jong Copyright (C) 2013 Steve Hill 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 /* for gid_t */ #include #include "common/set.h" #include "common.h" #include "log.h" #include "myldap.h" #include "cfg.h" #include "attmap.h" #include "compat/strndup.h" /* ( nisSchema.2.2 NAME 'posixGroup' SUP top STRUCTURAL * DESC 'Abstraction of a group of accounts' * MUST ( cn $ gidNumber ) * MAY ( userPassword $ memberUid $ description ) ) * * apart from the above a member attribute is also supported that * may contains a DN of a user * * nested groups (groups that are member of a group) are currently * not supported */ /* the search base for searches */ const char *group_bases[NSS_LDAP_CONFIG_MAX_BASES] = { NULL }; /* the search scope for searches */ int group_scope = LDAP_SCOPE_DEFAULT; /* the basic search filter for searches */ const char *group_filter = "(objectClass=posixGroup)"; /* the attributes to request with searches */ const char *attmap_group_cn = "cn"; const char *attmap_group_userPassword = "\"*\""; const char *attmap_group_gidNumber = "gidNumber"; const char *attmap_group_memberUid = "memberUid"; const char *attmap_group_member = "member"; /* special property for objectSid-based searches (these are already LDAP-escaped strings) */ static char *gidSid = NULL; /* default values for attributes */ static const char *default_group_userPassword = "*"; /* unmatchable */ /* the attribute list to request with searches */ static const char **group_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, char *buffer, size_t buflen) { char safename[300]; /* escape attribute */ if (myldap_escape(name, safename, sizeof(safename))) return -1; /* build filter */ return mysnprintf(buffer, buflen, "(&%s(%s=%s))", group_filter, attmap_group_cn, safename); } /* create a search filter for searching a group entry by gid, return -1 on errors */ static int mkfilter_group_bygid(gid_t gid, char *buffer, size_t buflen) { if (gidSid != NULL) { return mysnprintf(buffer, buflen, "(&%s(%s=%s\\%02x\\%02x\\%02x\\%02x))", group_filter, attmap_group_gidNumber, gidSid, (int)(gid & 0xff), (int)((gid >> 8) & 0xff), (int)((gid >> 16) & 0xff), (int)((gid >> 24) & 0xff)); } else { return mysnprintf(buffer, buflen, "(&%s(%s=%d))", group_filter, attmap_group_gidNumber, (int)gid); } } /* create a search filter for searching a group entry by member uid, return -1 on errors */ static int mkfilter_group_bymember(MYLDAP_SESSION *session, const char *uid, char *buffer, size_t buflen) { char dn[256]; char safeuid[300]; char safedn[300]; /* escape attribute */ if (myldap_escape(uid, safeuid, sizeof(safeuid))) return -1; /* try to translate uid to DN */ if (uid2dn(session, uid, dn, sizeof(dn)) == NULL) return mysnprintf(buffer, buflen, "(&%s(%s=%s))", group_filter, attmap_group_memberUid, safeuid); /* escape DN */ if (myldap_escape(dn, safedn, sizeof(safedn))) return -1; /* also lookup using user DN */ return mysnprintf(buffer, buflen, "(&%s(|(%s=%s)(%s=%s)))", group_filter, attmap_group_memberUid, safeuid, attmap_group_member, safedn); } static int mkfilter_group_bymemberdn(MYLDAP_SESSION *session, const char *dn, char *buffer, size_t buflen) { char safedn[300]; /* escape DN */ if (myldap_escape(dn, safedn, sizeof(safedn))) return -1; return mysnprintf(buffer, buflen, "(&%s(%s=%s))", group_filter, attmap_group_member, safedn); } void group_init(void) { int i; SET *set; /* set up search bases */ if (group_bases[0] == NULL) for (i = 0; i < NSS_LDAP_CONFIG_MAX_BASES; i++) group_bases[i] = nslcd_cfg->bases[i]; /* set up scope */ if (group_scope == LDAP_SCOPE_DEFAULT) group_scope = nslcd_cfg->scope; /* special case when gidNumber references objectSid */ if (strncasecmp(attmap_group_gidNumber, "objectSid:", 10) == 0) { gidSid = sid2search(attmap_group_gidNumber + 10); attmap_group_gidNumber = strndup(attmap_group_gidNumber, 9); } /* set up 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_memberUid); attmap_add_attributes(set, attmap_group_gidNumber); attmap_add_attributes(set, attmap_group_member); group_attrs = set_tolist(set); if (group_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, const char **names, gid_t gids[], int numgids, const char *passwd, const char **members, const char *reqname) { int32_t tmpint32, tmp2int32, tmp3int32; int i, j; /* write entries for all names and gids */ for (i = 0; names[i] != NULL; i++) { if (!isvalidname(names[i])) { log_log(LOG_WARNING, "%s: %s: denied by validnames option", myldap_get_dn(entry), attmap_group_cn); } else if ((reqname == NULL) || (STR_CMP(reqname, names[i]) == 0)) { for (j = 0; j < numgids; j++) { WRITE_INT32(fp, NSLCD_RESULT_BEGIN); WRITE_STRING(fp, names[i]); WRITE_STRING(fp, passwd); WRITE_INT32(fp, gids[j]); WRITE_STRINGLIST(fp, members); } } } return 0; } static void getmembers(MYLDAP_ENTRY *entry, MYLDAP_SESSION *session, SET *members, SET *seen, SET *subgroups) { char buf[256]; int i; const char **values; /* add the memberUid values */ values = myldap_get_values(entry, attmap_group_memberUid); if (values != NULL) for (i = 0; values[i] != NULL; i++) { /* only add valid usernames */ if (isvalidname(values[i])) set_add(members, values[i]); } /* add the member values */ values = myldap_get_values(entry, attmap_group_member); if (values != NULL) for (i = 0; values[i] != NULL; i++) { if ((seen == NULL) || (!set_contains(seen, values[i]))) { if (seen != NULL) set_add(seen, values[i]); /* transform the DN into a uid (dn2uid() already checks validity) */ if (dn2uid(session, values[i], buf, sizeof(buf)) != NULL) set_add(members, buf); /* wasn't a UID - try handling it as a nested group */ else if (subgroups != NULL) set_add(subgroups, values[i]); } } } /* the maximum number of gidNumber attributes per entry */ #define MAXGIDS_PER_ENTRY 5 static int write_group(TFILE *fp, MYLDAP_ENTRY *entry, const char *reqname, const gid_t *reqgid, int wantmembers, MYLDAP_SESSION *session) { const char **names, **gidvalues; const char *passwd; const char **members = NULL; SET *set, *seen=NULL, *subgroups=NULL; gid_t gids[MAXGIDS_PER_ENTRY]; int numgids; char *tmp; char passbuffer[64]; MYLDAP_SEARCH *search; MYLDAP_ENTRY *entry2; int rc; /* get group name (cn) */ names = myldap_get_values(entry, attmap_group_cn); if ((names == NULL) || (names[0] == NULL)) { log_log(LOG_WARNING, "%s: %s: missing", myldap_get_dn(entry), attmap_group_cn); return 0; } /* get the group id(s) */ if (reqgid != NULL) { gids[0] = *reqgid; numgids = 1; } else { gidvalues = myldap_get_values_len(entry, attmap_group_gidNumber); if ((gidvalues == NULL) || (gidvalues[0] == NULL)) { log_log(LOG_WARNING, "%s: %s: missing", myldap_get_dn(entry), attmap_group_gidNumber); return 0; } for (numgids = 0; (numgids < MAXGIDS_PER_ENTRY) && (gidvalues[numgids] != NULL); numgids++) { if (gidSid != NULL) gids[numgids] = (gid_t)binsid2id(gidvalues[numgids]); else { errno = 0; gids[numgids] = strtogid(gidvalues[numgids], &tmp, 10); if ((*(gidvalues[numgids]) == '\0') || (*tmp != '\0')) { log_log(LOG_WARNING, "%s: %s: non-numeric", myldap_get_dn(entry), attmap_group_gidNumber); return 0; } else if ((errno != 0) || (strchr(gidvalues[numgids], '-') != NULL)) { log_log(LOG_WARNING, "%s: %s: out of range", myldap_get_dn(entry), attmap_group_gidNumber); return 0; } } } } /* get group passwd (userPassword) (use only first entry) */ passwd = get_userpassword(entry, attmap_group_userPassword, passbuffer, sizeof(passbuffer)); if (passwd == NULL) passwd = default_group_userPassword; /* get group memebers (memberUid&member) */ if (wantmembers) { set = set_new(); if (set != NULL) { if (nslcd_cfg->nss_nested_groups) { seen = set_new(); subgroups = set_new(); } /* collect the members from this group */ getmembers(entry, session, set, seen, subgroups); /* add the members of any nested groups */ if (subgroups != NULL) { while ((tmp = set_pop(subgroups)) != NULL) { search = myldap_search(session, tmp, LDAP_SCOPE_BASE, group_filter, group_attrs, NULL); if (search != NULL) while ((entry2 = myldap_get_entry(search, NULL)) != NULL) getmembers(entry2, session, set, seen, subgroups); } } members = set_tolist(set); set_free(set); if (seen != NULL) set_free(seen); if (subgroups != NULL) set_free(subgroups); } } /* write entries (split to a separate function so we can ensure the call to free() below in case a write fails) */ rc = do_write_group(fp, entry, names, gids, numgids, passwd, members, reqname); /* free and return */ if (members != NULL) free(members); return rc; } NSLCD_HANDLE( group, byname, NSLCD_ACTION_GROUP_BYNAME, char name[256]; char filter[4096]; READ_STRING(fp, name); log_setrequest("group=\"%s\"", name); if (!isvalidname(name)) { log_log(LOG_WARNING, "request denied by validnames option"); return -1; }, mkfilter_group_byname(name, filter, sizeof(filter)), write_group(fp, entry, name, NULL, 1, session) ) NSLCD_HANDLE( group, bygid, NSLCD_ACTION_GROUP_BYGID, gid_t gid; char filter[4096]; READ_INT32(fp, gid); log_setrequest("group=%lu", (unsigned long int)gid);, mkfilter_group_bygid(gid, filter, sizeof(filter)), write_group(fp, entry, NULL, &gid, 1, session) ) int nslcd_group_bymember(TFILE *fp, MYLDAP_SESSION *session) { /* define common variables */ int32_t tmpint32; MYLDAP_SEARCH *search; MYLDAP_ENTRY *entry; const char *dn; const char *base; int rc, i; char name[256]; char filter[4096]; SET *seen=NULL, *tocheck=NULL; /* read request parameters */ READ_STRING(fp, name); log_setrequest("group/member=\"%s\"", name); /* validate request */ if (!isvalidname(name)) { log_log(LOG_WARNING, "request denied by validnames option"); return -1; } if ((nslcd_cfg->nss_initgroups_ignoreusers != NULL) && set_contains(nslcd_cfg->nss_initgroups_ignoreusers, name)) { log_log(LOG_DEBUG, "ignored group member"); /* just end the request, returning no results */ WRITE_INT32(fp, NSLCD_VERSION); WRITE_INT32(fp, NSLCD_ACTION_GROUP_BYMEMBER); WRITE_INT32(fp, NSLCD_RESULT_END); return 0; } /* write the response header */ WRITE_INT32(fp, NSLCD_VERSION); WRITE_INT32(fp, NSLCD_ACTION_GROUP_BYMEMBER); /* prepare the search filter */ if (mkfilter_group_bymember(session, name, filter, sizeof(filter))) { log_log(LOG_WARNING, "nslcd_group_bymember(): filter buffer too small"); return -1; } if (nslcd_cfg->nss_nested_groups) { seen = set_new(); tocheck = set_new(); if ((seen != NULL) && (tocheck == NULL)) { set_free(seen); seen = NULL; } else if ((tocheck != NULL) && (seen == NULL)) { set_free(tocheck); tocheck = NULL; } } /* perform a search for each search base */ for (i = 0; (base = group_bases[i]) != NULL; i++) { /* do the LDAP search */ search = myldap_search(session, base, group_scope, filter, group_attrs, NULL); if (search == NULL) { if (seen != NULL) { set_free(seen); set_free(tocheck); } return -1; } /* go over results */ while ((entry = myldap_get_entry(search, &rc)) != NULL) { if ((seen == NULL) || (!set_contains(seen, dn = myldap_get_dn(entry)))) { if (seen != NULL) { set_add(seen, dn); set_add(tocheck, dn); } if (write_group(fp, entry, NULL, NULL, 0, session)) { if (seen != NULL) { set_free(seen); set_free(tocheck); } return -1; } } } } /* write possible parent groups */ if (tocheck != NULL) { while ((dn = set_pop(tocheck)) != NULL) { /* make filter for finding groups with our group as member */ if (mkfilter_group_bymemberdn(session, dn, filter, sizeof(filter))) { log_log(LOG_WARNING, "nslcd_group_bymember(): filter buffer too small"); set_free(seen); set_free(tocheck); return -1; } /* do the LDAP searches */ for (i = 0; (base = group_bases[i]) != NULL; i++) { search = myldap_search(session, base, group_scope, filter, group_attrs, NULL); if (search != NULL) { while ((entry = myldap_get_entry(search, NULL)) != NULL) { if (!set_contains(seen, dn = myldap_get_dn(entry))) { set_add(seen, dn); set_add(tocheck, dn); if (write_group(fp, entry, NULL, NULL, 0, session)) { set_free(seen); set_free(tocheck); return -1; } } } } } } set_free(seen); set_free(tocheck); } /* write the final result code */ if (rc == LDAP_SUCCESS) { WRITE_INT32(fp, NSLCD_RESULT_END); } return 0; } NSLCD_HANDLE( group, all, NSLCD_ACTION_GROUP_ALL, const char *filter; log_setrequest("group(all)");, (filter = group_filter, 0), write_group(fp, entry, NULL, NULL, 1, session) )