/* * 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; }; static int bvstrcasecmp(const struct berval *bval, const char *s) { size_t len; int c; len = strlen(s); if (len == bval->bv_len) { return strncasecmp(bval->bv_val, s, len); } c = strncasecmp(bval->bv_val, s, MIN(bval->bv_len, len)); if (c != 0) { return c; } return bval->bv_len - strlen(s); } /* 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 == bvstrcasecmp(bval, "posixGroup"))) { config->search_group = TRUE; } else if ((0 == strcasecmp(filter_type, "objectClass")) && (0 == bvstrcasecmp(bval, "shadowAccount"))) { config->wrong_search = TRUE; } if ((NULL == config->name) && config->name_set) { config->name = slapi_ch_malloc(bval->bv_len + 1); if (config->name != NULL) { memcpy(config->name, bval->bv_val, bval->bv_len); config->name[bval->bv_len] = '\0'; } } } 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 char * backend_build_dn(const char *attribute, const char *value, const char *container_sdn) { Slapi_RDN *rdn; Slapi_DN *sdn; char *val, *dn = NULL; const char *ndn, *hexchars = "0123456789ABCDEF"; int i; val = malloc(strlen(value) * 3 + 1); if (val == NULL) { return NULL; } rdn = slapi_rdn_new(); if (rdn == NULL) { free(val); return NULL; } for (i = 0; value[i] != '\0'; i++) { val[i * 3] = '\\'; val[i * 3 + 1] = hexchars[(value[i] & 0xf0) >> 4]; val[i * 3 + 2] = hexchars[value[i] & 0xf]; } val[i * 3] = '\0'; if (slapi_rdn_add(rdn, attribute, val) == 1) { sdn = slapi_sdn_new_dn_byval(container_sdn); if (sdn != NULL) { sdn = slapi_sdn_add_rdn(sdn, rdn); ndn = slapi_sdn_get_ndn(sdn); if (ndn != NULL) { dn = slapi_ch_strdup(ndn); } slapi_sdn_free(&sdn); } } free(val); slapi_rdn_free(&rdn); return dn; } static Slapi_Entry * backend_make_user_entry_from_nsswitch_passwd(struct passwd *pwd, char *container_sdn, struct backend_search_cbdata *cbdata) { Slapi_Entry *entry; int rc; char *name; char *dn = NULL; #ifdef HAVE_SSS_NSS_IDMAP enum sss_id_type id_type; char *sid_str; #endif entry = slapi_entry_alloc(); if (entry == NULL) { return NULL; } dn = backend_build_dn("uid", pwd->pw_name, container_sdn); if (dn == NULL) { slapi_log_error(SLAPI_LOG_FATAL, cbdata->state->plugin_desc->spd_id, "error building DN for uid=%s,%s skipping\n", pwd->pw_name, container_sdn); 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); #ifdef HAVE_SSS_NSS_IDMAP 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); } #endif return entry; } static Slapi_Entry ** backend_retrieve_user_entry_from_nsswitch(char *user_name, bool_t is_uid, char *container_sdn, struct backend_search_cbdata *cbdata, int *count) { struct passwd pwd, *result; Slapi_Entry *entry, **entries; int rc; 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 = backend_make_user_entry_from_nsswitch_passwd(&pwd, container_sdn, cbdata); entries = malloc(sizeof(entries[0]) * 2); if (entries != NULL) { entries[0] = entry; entries[1] = NULL; *count = 1; } else { slapi_entry_free(entry); } return entries; } static Slapi_Entry * backend_make_group_entry_from_nsswitch_group(struct group *grp, char *container_sdn, struct backend_search_cbdata *cbdata) { Slapi_Entry *entry; int rc, i; char *dn = NULL; #ifdef HAVE_SSS_NSS_IDMAP enum sss_id_type id_type; char *sid_str; #endif entry = slapi_entry_alloc(); if (entry == NULL) { return NULL; } dn = backend_build_dn("cn", grp->gr_name, container_sdn); if (dn == NULL) { slapi_log_error(SLAPI_LOG_FATAL, cbdata->state->plugin_desc->spd_id, "error building DN for cn=%s,%s skipping\n", grp->gr_name, container_sdn); 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, int *count) { struct group grp, *result; Slapi_Entry *entry, **entries; 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_make_group_entry_from_nsswitch_group(&grp, container_sdn, cbdata); entries = malloc(sizeof(entries[0]) * 2); if (entries != NULL) { entries[0] = entry; entries[1] = NULL; *count = 1; } else { slapi_entry_free(entry); } return entries; } 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_make_group_entry_from_nsswitch_group(&grp, container_sdn, cbdata); 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 = calloc(ngroups + 1, sizeof(entries[0])); 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++; entries[idx] = NULL; } } if (idx != ngroups) { tmp = realloc(entries, (idx + 1) * sizeof(entries[0])); if (tmp != NULL) { entries = tmp; } } *count = 0; if (entries != NULL) { *count = idx; } free(grouplist); return entries; } const char * nsswitch_type_to_name(enum sch_search_nsswitch_t type) { switch (type) { case SCH_NSSWITCH_USER: return "user"; break; case SCH_NSSWITCH_GROUP: return "group"; break; case SCH_NSSWITCH_NONE: return "none(?)"; break; } return "(unknown)"; } /* Check if the filter is one (like uid=) that should trigger an * nsswitch lookup, and if it is, make a note that we should perform such a * lookup. */ 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_search *staged = NULL; char *idptr = NULL; unsigned long id; /* First, we search the filter to see if it includes a cn|uid= test. */ 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(*staged)); 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 = 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; /* takes ownership */ staged->is_id = config.search_gid || config.search_uid; staged->search_members = config.search_members; staged->next = cbdata->staged; cbdata->staged = staged; slapi_log_error(SLAPI_LOG_PLUGIN, cbdata->state->plugin_desc->spd_id, "staged nsswitch %s search for %s/%s/%s\n", nsswitch_type_to_name(staged->type), staged->map_group, staged->map_set, staged->name); return; fail: slapi_ch_free_string(&config.name); return; } /* Actually look up the information that we previously noted that we should, * then convert whatever we find into one or more Slapi_Entry pointers. */ bool_t backend_retrieve_from_nsswitch(struct backend_staged_search *staged, struct backend_search_cbdata *cbdata) { Slapi_Entry **entries; 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; return TRUE; } return FALSE; } if ((staged->type == SCH_NSSWITCH_GROUP) && (NULL != staged->name)) { entries = backend_retrieve_group_entry_from_nsswitch(staged->name, staged->is_id, staged->container_sdn, cbdata, &staged->count); if (entries != NULL) { staged->entries = entries; return TRUE; } return FALSE; } if ((staged->type == SCH_NSSWITCH_USER) && (NULL != staged->name)) { entries = backend_retrieve_user_entry_from_nsswitch(staged->name, staged->is_id, staged->container_sdn, cbdata, &staged->count); if (entries != NULL) { staged->entries = entries; return TRUE; } return FALSE; } return FALSE; }