From 2e38e59b2fca5ec46ebd5eceffb2fc0f79a9d25a Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Wed, 31 Jul 2013 14:35:15 +0300 Subject: schema-compat: add support for querying users and groups through NSSWITCH src/back-sch-nss.c implements interface to query users and groups on FreeIPA master server via getpwnam_r(), getgrnam_r(), and libsss_idmap. --- src/back-sch-nss.c | 574 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 574 insertions(+) create mode 100644 src/back-sch-nss.c (limited to 'src/back-sch-nss.c') diff --git a/src/back-sch-nss.c b/src/back-sch-nss.c new file mode 100644 index 0000000..affed01 --- /dev/null +++ b/src/back-sch-nss.c @@ -0,0 +1,574 @@ +/* + * Copyright 2013 Red Hat, Inc. + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This Program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; if not, write to the + * + * Free Software Foundation, Inc. + * 59 Temple Place, Suite 330 + * Boston, MA 02111-1307 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_DIRSRV_SLAPI_PLUGIN_H +#include +#include +#include +#else +#include +#endif + +#include +#ifdef HAVE_SSS_NSS_IDMAP +#include +#endif + +#include "backend.h" +#include "back-shr.h" +#include "plugin.h" +#include "map.h" +#include "back-sch.h" +#include "format.h" + +struct backend_search_filter_config { + bool_t search_user; + bool_t search_group; + bool_t search_uid; + bool_t search_gid; + bool_t search_members; + bool_t name_set; + bool_t wrong_search; + char *name; +}; + +/* Check simple filter to see if it has + * (cn|uid|uidNumber|gidNumber|memberUid=) or (objectClass=posixGroup|shadowAccount) + * Called by slapi_filter_apply(). */ +static int +backend_search_filter_has_cn_uid(Slapi_Filter *filter, void *arg) +{ + struct backend_search_filter_config *config = arg; + struct berval *bval; + char *filter_type; + int f_choice, rc; + + f_choice = slapi_filter_get_choice(filter); + rc = slapi_filter_get_ava(filter, &filter_type, &bval); + if ((LDAP_FILTER_EQUALITY == f_choice) && (0 == rc)) { + if (0 == strcasecmp(filter_type, "uidNumber")) { + config->search_uid = TRUE; + config->name_set = TRUE; + } else if (0 == strcasecmp(filter_type, "gidNumber")) { + config->search_gid = TRUE; + config->name_set = TRUE; + } else if (0 == strcasecmp(filter_type, "uid")) { + config->search_user = TRUE; + config->name_set = TRUE; + } else if (0 == strcasecmp(filter_type, "cn")) { + config->name_set = TRUE; + } else if (0 == strcasecmp(filter_type, "memberUid")) { + config->name_set = TRUE; + config->search_members = TRUE; + } else if ((0 == strcasecmp(filter_type, "objectClass")) && + (0 == strncasecmp(bval->bv_val, "posixGroup", bval->bv_len))) { + config->search_group = TRUE; + } else if ((0 == strcasecmp(filter_type, "objectClass")) && + (0 == strncasecmp(bval->bv_val, "shadowAccount", bval->bv_len))) { + config->wrong_search = TRUE; + } + + if ((NULL == config->name) && config->name_set) { + config->name = format_strdupbv(bval); + } + } + + if ((config->search_uid || + config->search_gid || + config->search_user || + config->search_group) && (config->name != NULL)) { + return SLAPI_FILTER_SCAN_STOP; + } + return SLAPI_FILTER_SCAN_CONTINUE; +} + +static Slapi_Entry * +backend_retrieve_user_entry_from_nsswitch(char *user_name, bool_t is_uid, + char *container_sdn, + struct backend_search_cbdata *cbdata) +{ + struct passwd pwd, *result; + Slapi_Entry *entry; + int rc; + enum sss_id_type id_type; + char *sid_str, *name; + char *dn = NULL; + char *buf = NULL; + +repeat: + if (cbdata->nsswitch_buffer == NULL) { + return NULL; + } + + if (is_uid) { + rc = getpwuid_r((uid_t) atoll(user_name), &pwd, + cbdata->nsswitch_buffer, + cbdata->nsswitch_buffer_len, &result); + } else { + rc = getpwnam_r(user_name, &pwd, + cbdata->nsswitch_buffer, + cbdata->nsswitch_buffer_len, &result); + } + if ((result == NULL) || (rc != 0)) { + if (rc == ERANGE) { + buf = realloc(cbdata->nsswitch_buffer, cbdata->nsswitch_buffer_len * 2); + if (buf != NULL) { + cbdata->nsswitch_buffer = buf; + goto repeat; + } + } + return NULL; + } + + if (pwd.pw_uid < cbdata->nsswitch_min_id) { + return NULL; + } + + entry = slapi_entry_alloc(); + if (entry == NULL) { + return NULL; + } + + name = slapi_escape_filter_value(pwd.pw_name, -1); + if (name != NULL) { + dn = slapi_ch_smprintf("uid=%s,%s", name, container_sdn); + slapi_ch_free_string(&name); + } + if (dn == NULL) { + slapi_entry_free(entry); + return NULL; + } + + slapi_entry_add_string(entry, + "objectClass", "top"); + slapi_entry_add_string(entry, + "objectClass", "posixAccount"); + slapi_entry_add_string(entry, + "objectClass", "extensibleObject"); + slapi_entry_add_string(entry, + "uid", pwd.pw_name); + slapi_entry_attr_set_uint(entry, + "uidNumber", pwd.pw_uid); + slapi_entry_attr_set_uint(entry, + "gidNumber", pwd.pw_gid); + slapi_entry_add_string(entry, + "gecos", pwd.pw_gecos); + if (strlen(pwd.pw_gecos) > 0) { + slapi_entry_add_string(entry, + "cn", pwd.pw_gecos); + } else { + slapi_entry_add_string(entry, + "cn", pwd.pw_name); + } + + slapi_entry_add_string(entry, + "homeDirectory", pwd.pw_dir); + if ((pwd.pw_shell != NULL) && (strlen(pwd.pw_shell) > 0)) { + slapi_entry_add_string(entry, + "loginShell", pwd.pw_shell); + } + + slapi_entry_set_dn(entry, dn); + + rc = sss_nss_getsidbyid(pwd.pw_uid, &sid_str, &id_type); + if ((rc == 0) && (sid_str != NULL)) { + slapi_entry_add_string(entry, "ipaNTSecurityIdentifier", sid_str); + free(sid_str); + } + + return entry; +} + +static Slapi_Entry * +backend_retrieve_group_entry_from_nsswitch_helper(struct group *grp, + char *container_sdn) +{ + Slapi_Entry *entry; + int rc, i; + enum sss_id_type id_type; + char *sid_str, *name; + char *dn = NULL; + + entry = slapi_entry_alloc(); + if (entry == NULL) { + return NULL; + } + + name = slapi_escape_filter_value(grp->gr_name, -1); + if (name != NULL) { + dn = slapi_ch_smprintf("cn=%s,%s", name, container_sdn); + slapi_ch_free_string(&name); + } + if (dn == NULL) { + slapi_entry_free(entry); + return NULL; + } + + slapi_entry_add_string(entry, + "objectClass", "top"); + slapi_entry_add_string(entry, + "objectClass", "posixGroup"); + slapi_entry_add_string(entry, + "objectClass", "extensibleObject"); + slapi_entry_add_string(entry, + "cn", grp->gr_name); + slapi_entry_attr_set_uint(entry, + "gidNumber", grp->gr_gid); + + if (grp->gr_mem) { + for (i=0; grp->gr_mem[i]; i++) { + slapi_entry_add_string(entry, "memberUid", grp->gr_mem[i]); + } + } + + slapi_entry_set_dn(entry, dn); + +#ifdef HAVE_SSS_NSS_IDMAP + rc = sss_nss_getsidbyid(grp->gr_gid, &sid_str, &id_type); + if ((rc == 0) && (sid_str != NULL)) { + slapi_entry_add_string(entry, "ipaNTSecurityIdentifier", sid_str); + free(sid_str); + } +#endif + return entry; +} + +static Slapi_Entry * +backend_retrieve_group_entry_from_nsswitch(char *group_name, bool_t is_gid, + char *container_sdn, + struct backend_search_cbdata *cbdata) +{ + struct group grp, *result; + Slapi_Entry *entry; + int rc; + char *buf = NULL; + +repeat: + if (cbdata->nsswitch_buffer == NULL) { + return NULL; + } + + if (is_gid) { + rc = getgrgid_r((gid_t) atoll(group_name), &grp, + cbdata->nsswitch_buffer, + cbdata->nsswitch_buffer_len, &result); + } else { + rc = getgrnam_r(group_name, &grp, + cbdata->nsswitch_buffer, + cbdata->nsswitch_buffer_len, &result); + } + if ((result == NULL) || (rc != 0)) { + if (rc == ERANGE) { + buf = realloc(cbdata->nsswitch_buffer, cbdata->nsswitch_buffer_len * 2); + if (buf != NULL) { + cbdata->nsswitch_buffer = buf; + goto repeat; + } + } + return NULL; + } + + if (grp.gr_gid < cbdata->nsswitch_min_id) { + return NULL; + } + + entry = backend_retrieve_group_entry_from_nsswitch_helper(&grp, container_sdn); + + return entry; +} + +static Slapi_Entry * +backend_retrieve_group_entry_from_nsswitch_by_gid(gid_t gid, + char *container_sdn, + struct backend_search_cbdata *cbdata) +{ + struct group grp, *result; + Slapi_Entry *entry; + int rc; + char *buf = NULL; + +repeat: + if (cbdata->nsswitch_buffer == NULL) { + return NULL; + } + + rc = getgrgid_r(gid, &grp, + cbdata->nsswitch_buffer, + cbdata->nsswitch_buffer_len, &result); + + if ((result == NULL) || (rc != 0)) { + if (rc == ERANGE) { + buf = realloc(cbdata->nsswitch_buffer, cbdata->nsswitch_buffer_len * 2); + if (buf != NULL) { + cbdata->nsswitch_buffer = buf; + goto repeat; + } + } + return NULL; + } + + if (grp.gr_gid < cbdata->nsswitch_min_id) { + return NULL; + } + + entry = backend_retrieve_group_entry_from_nsswitch_helper(&grp, container_sdn); + + return entry; +} + +static Slapi_Entry ** +backend_retrieve_group_list_from_nsswitch(char *user_name, char *container_sdn, + struct backend_search_cbdata *cbdata, + int *count) +{ + struct passwd pwd, *pwd_result; + gid_t *grouplist, *tmp_list; + Slapi_Entry **entries, *entry, **tmp; + char *buf = NULL; + int rc, ngroups, i, idx; + +repeat: + if (cbdata->nsswitch_buffer == NULL) { + return NULL; + } + + rc = getpwnam_r(user_name, &pwd, + cbdata->nsswitch_buffer, + cbdata->nsswitch_buffer_len, &pwd_result); + + if ((pwd_result == NULL) || (rc != 0)) { + if (rc == ERANGE) { + buf = realloc(cbdata->nsswitch_buffer, cbdata->nsswitch_buffer_len * 2); + if (buf != NULL) { + cbdata->nsswitch_buffer = buf; + goto repeat; + } + } + return NULL; + } + + if (pwd.pw_uid < cbdata->nsswitch_min_id) { + return NULL; + } + + ngroups = 32; + grouplist = malloc(sizeof(gid_t) * ngroups); + if (grouplist == NULL) { + return NULL; + } + + do { + rc = getgrouplist(user_name, pwd.pw_gid, grouplist, &ngroups); + if (rc < ngroups) { + tmp_list = realloc(grouplist, ngroups * sizeof(gid_t)); + if (tmp_list == NULL) { + free(grouplist); + return NULL; + } + grouplist = tmp_list; + } + } while (rc != ngroups); + + entries = malloc(sizeof(Slapi_Entry *) * ngroups); + if (entries == NULL) { + free(grouplist); + return NULL; + } + + idx = 0; + /* At this point we are not interested in the buffer used in pwd anymore + * so the next function can take it over for getgrid_r() */ + for (i = 0; i < ngroups; i++) { + entry = backend_retrieve_group_entry_from_nsswitch_by_gid(grouplist[i], container_sdn, cbdata); + if (entry != NULL) { + entries[idx] = entry; + idx++; + } + } + + if (idx != ngroups) { + tmp = realloc(entries, idx * sizeof(Slapi_Entry *)); + if (tmp != NULL) { + entries = tmp; + } + } + + *count = 0; + if (entries != NULL) { + *count = idx; + } + + free(grouplist); + + return entries; +} + +/* Check filter for a component like (uid=) and if found, + * stage NSSWITCH lookup. Lookup will be performed later, with call to backend_retrieve_from_nsswitch */ +void +backend_search_nsswitch(struct backend_set_data *set_data, + struct backend_search_cbdata *cbdata) +{ + int result, rc; + struct backend_search_filter_config config = {FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL}; + struct backend_staged_data *staged = NULL; + char *idptr = NULL; + unsigned long id; + + /* There was no match but we asked to check NSSWITCH */ + /* First, we search the filter to see if it includes cn|uid= check */ + result = slapi_filter_apply(cbdata->filter, + backend_search_filter_has_cn_uid, &config, &rc); + if ((result != SLAPI_FILTER_SCAN_STOP)) { + return; + } + + if (NULL == config.name) { + return; + } + + if (config.wrong_search) { + goto fail; + } + + /* Drop irrelevant requests. Each set only works with a single type */ + if ((cbdata->check_nsswitch == SCH_NSSWITCH_GROUP) && + (config.search_uid || config.search_user)) { + goto fail; + } + + if ((cbdata->check_nsswitch == SCH_NSSWITCH_USER) && + (config.search_gid || config.search_group)) { + goto fail; + } + + if ((config.search_gid || config.search_uid)) { + errno = 0; + id = strtoul(config.name, &idptr, 10); + if ((errno != 0) || ((idptr != NULL) && (*idptr != '\0'))) { + goto fail; + } + if (id < cbdata->nsswitch_min_id) { + goto fail; + } + } + + + staged = malloc(sizeof(struct backend_staged_data)); + if (staged == NULL) { + goto fail; + } + + staged->map_group = slapi_ch_strdup(set_data->common.group); + staged->map_set = slapi_ch_strdup(set_data->common.set); + staged->set_data_fixup = NULL; + staged->count = 0; + staged->entries = NULL; + + staged->container_sdn = slapi_ch_strdup(slapi_sdn_get_dn(set_data->container_sdn)); + + staged->type = cbdata->check_nsswitch; + staged->name = config.name; + staged->is_id = config.search_gid || config.search_uid; + staged->search_members = config.search_members; + + staged->next = cbdata->staged; + cbdata->staged = staged; + return; + +fail: + slapi_ch_free_string(&config.name); + return; +} + + /* perform look up against NSSWITCH and create entry based on that */ +bool_t +backend_retrieve_from_nsswitch(struct backend_staged_data *staged, + struct backend_search_cbdata *cbdata) +{ + Slapi_Entry *entry, **entries; + int i; + + if (((staged->type == SCH_NSSWITCH_GROUP) && staged->search_members) && + (NULL != staged->name)) { + entries = backend_retrieve_group_list_from_nsswitch(staged->name, staged->container_sdn, + cbdata, &staged->count); + if (entries != NULL) { + staged->entries = entries; + for (i = 0; i < staged->count; i++) { + slapi_entry_add_string(entries[i], "schema-compat-origin", "nsswitch"); + } + return TRUE; + } + return FALSE; + } + + if ((staged->type == SCH_NSSWITCH_GROUP) && (NULL != staged->name)) { + entry = backend_retrieve_group_entry_from_nsswitch(staged->name, staged->is_id, + staged->container_sdn, + cbdata); + if (entry) { + slapi_entry_add_string(entry, "schema-compat-origin", "nsswitch"); + staged->entries = malloc(sizeof(Slapi_Entry *)); + if (staged->entries != NULL) { + staged->entries[0] = entry; + staged->count = 1; + return TRUE; + } else { + slapi_entry_free(entry); + } + } + return FALSE; + } + + if ((staged->type == SCH_NSSWITCH_USER) && (NULL != staged->name)) { + entry = backend_retrieve_user_entry_from_nsswitch(staged->name, staged->is_id, + staged->container_sdn, + cbdata); + if (entry) { + slapi_entry_add_string(entry, "schema-compat-origin", "nsswitch"); + staged->entries = malloc(sizeof(Slapi_Entry *)); + if (staged->entries != NULL) { + staged->entries[0] = entry; + staged->count = 1; + return TRUE; + } else { + slapi_entry_free(entry); + } + } + return FALSE; + } + + return FALSE; +} -- cgit