sdap_async_groups.c revision 99c0cfdc5f065ba38f1ee91701d1d27f9e4fdb96
55cf6e01272ec475edea32aa9b7923de2d36cb42Christian Maeder/*
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea SSSD
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea Async LDAP Helper routines - retrieving groups
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder
98890889ffb2e8f6f722b00e265a211f13b5a861Corneliu-Claudiu Prodescu Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009
cb1bcdcebd18280e73151a05cf9846b940674518Codruta Girlea Copyright (C) 2010, Ralf Haferkamp <rhafer@suse.de>, Novell Inc.
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea Copyright (C) Jan Zeleny <jzeleny@redhat.com> - 2011
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea This program is free software; you can redistribute it and/or modify
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea it under the terms of the GNU General Public License as published by
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea the Free Software Foundation; either version 3 of the License, or
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea (at your option) any later version.
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea This program is distributed in the hope that it will be useful,
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea but WITHOUT ANY WARRANTY; without even the implied warranty of
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
7857a35e3af533dfbd0f0e18638ebd211e6358a0Christian Maeder GNU General Public License for more details.
e73f8ff81464981f9ba296a65a459364c0c3c486Codruta Girlea
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea You should have received a copy of the GNU General Public License
cb1bcdcebd18280e73151a05cf9846b940674518Codruta Girlea along with this program. If not, see <http://www.gnu.org/licenses/>.
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu*/
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea#include "util/util.h"
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea#include "db/sysdb.h"
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea#include "providers/ldap/sdap_async_private.h"
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea#include "providers/ldap/ldap_common.h"
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea#include "providers/ldap/sdap_idmap.h"
cb5be4f31df88b8137ef3cabf4e8b0ddec52f351Christian Maeder
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea/* ==Group-Parsing Routines=============================================== */
fd1c864a3dec70aa22ecb2bc85816ec8251c6decCodruta Girlea
fd1c864a3dec70aa22ecb2bc85816ec8251c6decCodruta Girleastatic int sdap_find_entry_by_origDN(TALLOC_CTX *memctx,
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea struct sysdb_ctx *ctx,
cb5be4f31df88b8137ef3cabf4e8b0ddec52f351Christian Maeder struct sss_domain_info *domain,
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea const char *orig_dn,
60be96a6bed14ee4d7625e3d73040a74ca26321eCodruta Girlea char **localdn)
b5fc3c116b803863fe86b55bb75b164d4029c696Christian Maeder{
b5fc3c116b803863fe86b55bb75b164d4029c696Christian Maeder TALLOC_CTX *tmpctx;
cb1bcdcebd18280e73151a05cf9846b940674518Codruta Girlea const char *no_attrs[] = { NULL };
cb1bcdcebd18280e73151a05cf9846b940674518Codruta Girlea struct ldb_dn *base_dn;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder char *filter;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder struct ldb_message **msgs;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder size_t num_msgs;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder int ret;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder char *sanitized_dn;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder tmpctx = talloc_new(NULL);
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder if (!tmpctx) {
cb1bcdcebd18280e73151a05cf9846b940674518Codruta Girlea return ENOMEM;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder }
cb1bcdcebd18280e73151a05cf9846b940674518Codruta Girlea
cb1bcdcebd18280e73151a05cf9846b940674518Codruta Girlea ret = sss_filter_sanitize(tmpctx, orig_dn, &sanitized_dn);
cb1bcdcebd18280e73151a05cf9846b940674518Codruta Girlea if (ret != EOK) {
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder ret = ENOMEM;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder goto done;
cb1bcdcebd18280e73151a05cf9846b940674518Codruta Girlea }
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder
cb5be4f31df88b8137ef3cabf4e8b0ddec52f351Christian Maeder filter = talloc_asprintf(tmpctx, "%s=%s", SYSDB_ORIG_DN, sanitized_dn);
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder if (!filter) {
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder ret = ENOMEM;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder goto done;
cb1bcdcebd18280e73151a05cf9846b940674518Codruta Girlea }
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder base_dn = sysdb_domain_dn(ctx, tmpctx, domain);
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder if (!base_dn) {
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder ret = ENOMEM;
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder goto done;
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder }
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder
cb1bcdcebd18280e73151a05cf9846b940674518Codruta Girlea DEBUG(9, ("Searching cache for [%s].\n", sanitized_dn));
cb1bcdcebd18280e73151a05cf9846b940674518Codruta Girlea ret = sysdb_search_entry(tmpctx, ctx,
cb1bcdcebd18280e73151a05cf9846b940674518Codruta Girlea base_dn, LDB_SCOPE_SUBTREE, filter, no_attrs,
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder &num_msgs, &msgs);
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder if (ret) {
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder goto done;
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder }
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder if (num_msgs != 1) {
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder ret = ENOENT;
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder goto done;
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder }
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder *localdn = talloc_strdup(memctx, ldb_dn_get_linearized(msgs[0]->dn));
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder if (!*localdn) {
b5fc3c116b803863fe86b55bb75b164d4029c696Christian Maeder ret = ENOENT;
b5fc3c116b803863fe86b55bb75b164d4029c696Christian Maeder goto done;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder }
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder ret = EOK;
2025793bdf95b956b34761af691fe9bde01f6d83Christian Maeder
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maederdone:
b5fc3c116b803863fe86b55bb75b164d4029c696Christian Maeder talloc_zfree(tmpctx);
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder return ret;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder}
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maederstatic errno_t
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maedersdap_get_members_with_primary_gid(TALLOC_CTX *mem_ctx, struct sysdb_ctx *sysdb,
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder gid_t gid, char ***_localdn, size_t *_ndn)
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder{
d6c6b2543c509ec7f6213e4cba675d96304a7fd6Christian Maeder static const char *search_attrs[] = { SYSDB_NAME, NULL };
d6c6b2543c509ec7f6213e4cba675d96304a7fd6Christian Maeder char *filter;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder struct ldb_message **msgs;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder size_t count;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder size_t i;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder errno_t ret;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder char **localdn;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder /* Don't search if the group is non-posix */
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder if (!gid) return EOK;
98ca12832943f9b8e6edd28bc59716a2f7798899Christian Maeder
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder filter = talloc_asprintf(mem_ctx, "(%s=%llu)", SYSDB_GIDNUM,
7857a35e3af533dfbd0f0e18638ebd211e6358a0Christian Maeder (unsigned long long) gid);
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder if (!filter) {
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder return ENOMEM;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder }
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder
417437c97b9f3ef102275ebd1e3d015b52ef6201Christian Maeder ret = sysdb_search_users(mem_ctx, sysdb, filter,
417437c97b9f3ef102275ebd1e3d015b52ef6201Christian Maeder search_attrs, &count, &msgs);
417437c97b9f3ef102275ebd1e3d015b52ef6201Christian Maeder talloc_free(filter);
417437c97b9f3ef102275ebd1e3d015b52ef6201Christian Maeder if (ret == ENOENT) {
417437c97b9f3ef102275ebd1e3d015b52ef6201Christian Maeder *_localdn = NULL;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder *_ndn = 0;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder return EOK;
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu } else if (ret != EOK) {
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder return ret;
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder }
f588d2cfbdd1e6d4855df164fce25cf7db1a8e2dChristian Maeder
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu localdn = talloc_array(mem_ctx, char *, count);
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu if (!localdn) {
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu talloc_free(msgs);
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu return ENOMEM;
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu }
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu for (i=0; i < count; i++) {
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu localdn[i] = talloc_strdup(localdn,
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu ldb_dn_get_linearized(msgs[i]->dn));
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu if (!localdn[i]) {
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu talloc_free(localdn);
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu talloc_free(msgs);
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu return ENOMEM;
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu }
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu }
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu talloc_free(msgs);
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu *_localdn = localdn;
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu *_ndn = count;
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu return EOK;
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu}
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcustatic errno_t
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcusdap_dn_by_primary_gid(TALLOC_CTX *mem_ctx, struct sysdb_attrs *ldap_attrs,
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu struct sysdb_ctx *sysdb, struct sdap_options *opts,
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu char ***_dn_list, size_t *_count)
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu{
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu gid_t gid;
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu errno_t ret;
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu ret = sysdb_attrs_get_uint32_t(ldap_attrs,
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu opts->group_map[SDAP_AT_GROUP_GID].sys_name,
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu &gid);
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu if (ret == ENOENT) {
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu /* Non-posix AD group. Skip. */
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu *_dn_list = NULL;
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu *_count = 0;
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu return EOK;
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu } else if (ret && ret != ENOENT) {
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu return ret;
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu }
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu ret = sdap_get_members_with_primary_gid(mem_ctx, sysdb, gid,
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu _dn_list, _count);
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu if (ret) return ret;
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu
9e4febfd2eb81ca73c9b6a6a3c40017e6fb99390Mihaela Turcu return EOK;
}
static int sdap_fill_memberships(struct sysdb_attrs *group_attrs,
struct sysdb_ctx *ctx,
struct sss_domain_info *domain,
hash_table_t *ghosts,
struct ldb_val *values,
int num_values,
char **userdns,
size_t nuserdns)
{
struct ldb_message_element *el;
int i, j;
int ret;
errno_t hret;
hash_key_t key;
hash_value_t value;
ret = sysdb_attrs_get_el(group_attrs, SYSDB_MEMBER, &el);
if (ret) {
goto done;
}
/* Just allocate both big enough to contain all members for now */
el->values = talloc_realloc(group_attrs, el->values, struct ldb_val,
el->num_values + num_values + nuserdns);
if (!el->values) {
ret = ENOMEM;
goto done;
}
j = el->num_values;
for (i = 0; i < num_values; i++) {
if (ghosts == NULL) {
hret = HASH_ERROR_KEY_NOT_FOUND;
} else {
key.type = HASH_KEY_STRING;
key.str = (char *)values[i].data;
hret = hash_lookup(ghosts, &key, &value);
}
if (hret == HASH_ERROR_KEY_NOT_FOUND) {
/* sync search entry with this as origDN */
ret = sdap_find_entry_by_origDN(el->values, ctx, domain,
(char *)values[i].data,
(char **)&el->values[j].data);
if (ret != EOK) {
/* This should never return ENOENT
* -> fail if it does
*/
goto done;
}
DEBUG(7, (" member #%d (%s): [%s]\n",
i, (char *)values[i].data,
(char *)el->values[j].data));
el->values[j].length = strlen((char *)el->values[j].data);
j++;
} else if (hret != HASH_SUCCESS) {
ret = EFAULT;
goto done;
}
/* If the member is in ghost table, it has
* already been processed - just skip it */
}
el->num_values = j;
for (i=0; i < nuserdns; i++) {
el->values[el->num_values + i].data = (uint8_t *) \
talloc_steal(group_attrs, userdns[i]);
el->values[el->num_values + i].length = strlen(userdns[i]);
}
el->num_values += nuserdns;
ret = EOK;
done:
return ret;
}
/* ==Save-Group-Entry===================================================== */
/* FIXME: support non legacy */
/* FIXME: support storing additional attributes */
static errno_t
sdap_store_group_with_gid(struct sysdb_ctx *ctx,
struct sss_domain_info *domain,
const char *name,
gid_t gid,
struct sysdb_attrs *group_attrs,
uint64_t cache_timeout,
bool posix_group,
time_t now)
{
errno_t ret;
/* make sure that non-posix (empty or explicit gid=0) groups have the
* gidNumber set to zero even if updating existing group */
if (!posix_group) {
ret = sysdb_attrs_add_uint32(group_attrs, SYSDB_GIDNUM, 0);
if (ret) {
DEBUG(2, ("Could not set explicit GID 0 for %s\n", name));
return ret;
}
}
ret = sysdb_store_group(ctx, domain, name, gid,
group_attrs, cache_timeout, now);
if (ret) {
DEBUG(2, ("Could not store group %s\n", name));
return ret;
}
return ret;
}
static errno_t
sdap_process_ghost_members(struct sysdb_attrs *attrs,
struct sdap_options *opts,
hash_table_t *ghosts,
bool populate_members,
bool store_original_member,
struct sysdb_attrs *sysdb_attrs)
{
errno_t ret;
struct ldb_message_element *gh;
struct ldb_message_element *memberel;
struct ldb_message_element *sysdb_memberel;
struct ldb_message_element *ghostel;
size_t cnt;
int i;
int hret;
hash_key_t key;
hash_value_t value;
ret = sysdb_attrs_get_el(attrs, SYSDB_GHOST, &gh);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Error reading ghost attributes: [%s]\n",
strerror(ret)));
return ret;
}
ret = sysdb_attrs_get_el(attrs,
opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name,
&memberel);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Error reading members: [%s]\n", strerror(ret)));
return ret;
}
if (store_original_member) {
DEBUG(SSSDBG_TRACE_FUNC, ("The group has %d members\n", memberel->num_values));
for (i = 0; i < memberel->num_values; i++) {
ret = sysdb_attrs_add_string(sysdb_attrs, SYSDB_ORIG_MEMBER,
(const char *) memberel->values[i].data);
if (ret) {
DEBUG(SSSDBG_OP_FAILURE, ("Could not add member [%s]\n",
(const char *) memberel->values[i].data));
return ret;
}
}
}
if (populate_members) {
ret = sysdb_attrs_get_el(sysdb_attrs, SYSDB_MEMBER, &sysdb_memberel);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Error reading group members from group_attrs: [%s]\n",
strerror(ret)));
return ret;
}
sysdb_memberel->values = memberel->values;
sysdb_memberel->num_values = memberel->num_values;
}
ret = sysdb_attrs_get_el(sysdb_attrs, SYSDB_GHOST, &ghostel);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Error getting ghost element: [%s]\n", strerror(ret)));
return ret;
}
ghostel->values = gh->values;
ghostel->num_values = gh->num_values;
cnt = ghostel->num_values + memberel->num_values;
DEBUG(SSSDBG_TRACE_FUNC, ("Group has %d members\n", cnt));
/* Now process RFC2307bis ghost hash table */
if (ghosts && cnt > 0) {
ghostel->values = talloc_realloc(sysdb_attrs, ghostel->values,
struct ldb_val, cnt);
if (ghostel->values == NULL) {
return ENOMEM;
}
for (i = 0; i < memberel->num_values; i++) {
key.type = HASH_KEY_STRING;
key.str = (char *) memberel->values[i].data;
hret = hash_lookup(ghosts, &key, &value);
if (hret == HASH_ERROR_KEY_NOT_FOUND) {
continue;
} else if (hret != HASH_SUCCESS) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Error checking hash table: [%s]\n",
hash_error_string(hret)));
return EFAULT;
}
DEBUG(SSSDBG_TRACE_FUNC,
("Adding ghost member for group [%s]\n", (char *) value.ptr));
ghostel->values[ghostel->num_values].data = \
(uint8_t *) talloc_strdup(ghostel->values, value.ptr);
if (ghostel->values[ghostel->num_values].data == NULL) {
return ENOMEM;
}
ghostel->values[ghostel->num_values].length = strlen(value.ptr);
ghostel->num_values++;
}
}
return EOK;
}
static int sdap_save_group(TALLOC_CTX *memctx,
struct sysdb_ctx *ctx,
struct sdap_options *opts,
struct sss_domain_info *dom,
struct sysdb_attrs *attrs,
bool populate_members,
bool store_original_member,
hash_table_t *ghosts,
char **_usn_value,
time_t now)
{
struct ldb_message_element *el;
struct sysdb_attrs *group_attrs;
const char *name = NULL;
gid_t gid;
errno_t ret;
char *usn_value = NULL;
TALLOC_CTX *tmpctx = NULL;
bool posix_group;
bool use_id_mapping = dp_opt_get_bool(opts->basic, SDAP_ID_MAPPING);
char *sid_str;
tmpctx = talloc_new(NULL);
if (!tmpctx) {
ret = ENOMEM;
goto done;
}
group_attrs = sysdb_new_attrs(tmpctx);
if (group_attrs == NULL) {
ret = ENOMEM;
goto done;
}
ret = sysdb_attrs_primary_name(ctx, attrs,
opts->group_map[SDAP_AT_GROUP_NAME].name,
&name);
if (ret != EOK) {
DEBUG(1, ("Failed to save the group - entry has no name attribute\n"));
goto done;
}
DEBUG(SSSDBG_TRACE_FUNC, ("Processing group %s\n", name));
if (use_id_mapping) {
posix_group = true;
DEBUG(SSSDBG_TRACE_LIBS,
("Mapping group [%s] objectSID to unix ID\n", name));
ret = sdap_attrs_get_sid_str(
tmpctx, opts->idmap_ctx, attrs,
opts->group_map[SDAP_AT_GROUP_OBJECTSID].sys_name,
&sid_str);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Could not identify objectSID: [%s]\n",
strerror(ret)));
goto done;
}
/* Add string representation to the cache for easier
* debugging
*/
ret = sysdb_attrs_add_string(group_attrs, SYSDB_SID_STR, sid_str);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Could not add SID string: [%s]\n",
strerror(ret)));
goto done;
}
/* Convert the SID into a UNIX group ID */
ret = sdap_idmap_sid_to_unix(opts->idmap_ctx, sid_str, &gid);
if (ret == ENOTSUP) {
/* ENOTSUP is returned if built-in SID was provided
* => do not store the group, but return EOK */
DEBUG(SSSDBG_TRACE_FUNC, ("Skipping built-in object.\n"));
ret = EOK;
goto done;
} else if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Could not convert SID string: [%s]\n",
strerror(ret)));
goto done;
}
/* Store the GID in the ldap_attrs so it doesn't get
* treated as a missing attribute from LDAP and removed.
*/
ret = sdap_replace_id(attrs, SYSDB_GIDNUM, gid);
if (ret) {
DEBUG(SSSDBG_OP_FAILURE, ("Cannot set the id-mapped GID\n"));
goto done;
}
} else {
ret = sysdb_attrs_get_bool(attrs, SYSDB_POSIX, &posix_group);
if (ret == ENOENT) {
posix_group = true;
} else if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Error reading posix attribute: [%s]\n",
strerror(ret)));
goto done;
}
DEBUG(8, ("This is%s a posix group\n", (posix_group)?"":" not"));
ret = sysdb_attrs_add_bool(group_attrs, SYSDB_POSIX, posix_group);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Error setting posix attribute: [%s]\n",
strerror(ret)));
goto done;
}
ret = sysdb_attrs_get_uint32_t(attrs,
opts->group_map[SDAP_AT_GROUP_GID].sys_name,
&gid);
if (ret != EOK) {
DEBUG(1, ("no gid provided for [%s] in domain [%s].\n",
name, dom->name));
ret = EINVAL;
goto done;
}
}
/* check that the gid is valid for this domain */
if (posix_group) {
if (OUT_OF_ID_RANGE(gid, dom->id_min, dom->id_max)) {
DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
name));
ret = EINVAL;
goto done;
}
/* Group ID OK */
}
ret = sdap_attrs_add_string(attrs, SYSDB_ORIG_DN, "original DN",
name, group_attrs);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Error setting original DN: [%s]\n",
strerror(ret)));
goto done;
}
ret = sdap_attrs_add_string(attrs,
opts->group_map[SDAP_AT_GROUP_MODSTAMP].sys_name,
"original mod-Timestamp",
name, group_attrs);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Error setting mod timestamp: [%s]\n",
strerror(ret)));
goto done;
}
ret = sysdb_attrs_get_el(attrs,
opts->group_map[SDAP_AT_GROUP_USN].sys_name, &el);
if (ret) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Error looking up group USN: [%s]\n",
strerror(ret)));
goto done;
}
if (el->num_values == 0) {
DEBUG(7, ("Original USN value is not available for [%s].\n",
name));
} else {
ret = sysdb_attrs_add_string(group_attrs,
opts->group_map[SDAP_AT_GROUP_USN].sys_name,
(const char*)el->values[0].data);
if (ret) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Error setting group USN: [%s]\n",
strerror(ret)));
goto done;
}
usn_value = talloc_strdup(tmpctx, (const char*)el->values[0].data);
if (!usn_value) {
ret = ENOMEM;
goto done;
}
}
ret = sdap_process_ghost_members(attrs, opts, ghosts,
populate_members, store_original_member,
group_attrs);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, ("Failed to save ghost members\n"));
goto done;
}
ret = sdap_save_all_names(name, attrs, !dom->case_sensitive, group_attrs);
if (ret != EOK) {
DEBUG(1, ("Failed to save group names\n"));
goto done;
}
DEBUG(6, ("Storing info for group %s\n", name));
ret = sdap_store_group_with_gid(ctx, dom,
name, gid, group_attrs,
dom->group_timeout,
posix_group, now);
if (ret) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Could not store group with GID: [%s]\n",
strerror(ret)));
goto done;
}
if (_usn_value) {
*_usn_value = talloc_steal(memctx, usn_value);
}
talloc_steal(memctx, group_attrs);
ret = EOK;
done:
if (ret) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Failed to save group [%s]: [%s]\n",
name ? name : "Unknown",
strerror(ret)));
}
talloc_free(tmpctx);
return ret;
}
/* ==Save-Group-Memebrs=================================================== */
/* FIXME: support non legacy */
/* FIXME: support storing additional attributes */
static int sdap_save_grpmem(TALLOC_CTX *memctx,
struct sysdb_ctx *ctx,
struct sdap_options *opts,
struct sss_domain_info *dom,
struct sysdb_attrs *attrs,
hash_table_t *ghosts,
time_t now)
{
struct ldb_message_element *el;
struct sysdb_attrs *group_attrs = NULL;
const char *name;
char **userdns = NULL;
size_t nuserdns = 0;
int ret;
ret = sysdb_attrs_primary_name(ctx, attrs,
opts->group_map[SDAP_AT_GROUP_NAME].name,
&name);
if (ret != EOK) {
goto fail;
}
/* With AD we also want to merge in parent groups of primary GID as they
* are reported with tokenGroups, too
*/
if (opts->schema_type == SDAP_SCHEMA_AD) {
ret = sdap_dn_by_primary_gid(memctx, attrs, ctx, opts,
&userdns, &nuserdns);
if (ret != EOK) {
goto fail;
}
}
ret = sysdb_attrs_get_el(attrs,
opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, &el);
if (ret != EOK) {
goto fail;
}
if (el->num_values == 0 && nuserdns == 0) {
DEBUG(7, ("No members for group [%s]\n", name));
} else {
DEBUG(7, ("Adding member users to group [%s]\n", name));
group_attrs = sysdb_new_attrs(memctx);
if (!group_attrs) {
ret = ENOMEM;
goto fail;
}
ret = sdap_fill_memberships(group_attrs, ctx, dom, ghosts,
el->values, el->num_values,
userdns, nuserdns);
if (ret) {
goto fail;
}
}
DEBUG(6, ("Storing members for group %s\n", name));
ret = sysdb_store_group(ctx, dom, name, 0, group_attrs,
dom->group_timeout, now);
if (ret) goto fail;
return EOK;
fail:
DEBUG(2, ("Failed to save user %s\n", name));
return ret;
}
/* ==Generic-Function-to-save-multiple-groups============================= */
static int sdap_save_groups(TALLOC_CTX *memctx,
struct sysdb_ctx *sysdb,
struct sss_domain_info *dom,
struct sdap_options *opts,
struct sysdb_attrs **groups,
int num_groups,
bool populate_members,
hash_table_t *ghosts,
char **_usn_value)
{
TALLOC_CTX *tmpctx;
char *higher_usn = NULL;
char *usn_value;
bool twopass;
bool has_nesting = false;
int ret;
errno_t sret;
int i;
struct sysdb_attrs **saved_groups = NULL;
int nsaved_groups = 0;
time_t now;
bool in_transaction = false;
switch (opts->schema_type) {
case SDAP_SCHEMA_RFC2307:
twopass = false;
break;
case SDAP_SCHEMA_RFC2307BIS:
case SDAP_SCHEMA_IPA_V1:
case SDAP_SCHEMA_AD:
twopass = true;
has_nesting = true;
break;
default:
return EINVAL;
}
tmpctx = talloc_new(memctx);
if (!tmpctx) {
return ENOMEM;
}
ret = sysdb_transaction_start(sysdb);
if (ret) {
DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to start transaction\n"));
goto done;
}
in_transaction = true;
if (twopass && !populate_members) {
saved_groups = talloc_array(tmpctx, struct sysdb_attrs *,
num_groups);
if (!saved_groups) {
ret = ENOMEM;
goto done;
}
}
now = time(NULL);
for (i = 0; i < num_groups; i++) {
usn_value = NULL;
/* if 2 pass savemembers = false */
ret = sdap_save_group(tmpctx, sysdb,
opts, dom, groups[i],
populate_members, has_nesting,
ghosts, &usn_value, now);
/* Do not fail completely on errors.
* Just report the failure to save and go on */
if (ret) {
DEBUG(2, ("Failed to store group %d. Ignoring.\n", i));
} else {
DEBUG(9, ("Group %d processed!\n", i));
if (twopass && !populate_members) {
saved_groups[nsaved_groups] = groups[i];
nsaved_groups++;
}
}
if (usn_value) {
if (higher_usn) {
if ((strlen(usn_value) > strlen(higher_usn)) ||
(strcmp(usn_value, higher_usn) > 0)) {
talloc_zfree(higher_usn);
higher_usn = usn_value;
} else {
talloc_zfree(usn_value);
}
} else {
higher_usn = usn_value;
}
}
}
if (twopass && !populate_members) {
for (i = 0; i < nsaved_groups; i++) {
ret = sdap_save_grpmem(tmpctx, sysdb, opts, dom, saved_groups[i],
ghosts, now);
/* Do not fail completely on errors.
* Just report the failure to save and go on */
if (ret) {
DEBUG(2, ("Failed to store group %d members.\n", i));
} else {
DEBUG(9, ("Group %d members processed!\n", i));
}
}
}
ret = sysdb_transaction_commit(sysdb);
if (ret) {
DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to commit transaction!\n"));
goto done;
}
in_transaction = false;
if (_usn_value) {
*_usn_value = talloc_steal(memctx, higher_usn);
}
done:
if (in_transaction) {
sret = sysdb_transaction_cancel(sysdb);
if (sret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to cancel transaction\n"));
}
}
talloc_zfree(tmpctx);
return ret;
}
/* ==Process-Groups======================================================= */
struct sdap_process_group_state {
struct tevent_context *ev;
struct sdap_options *opts;
struct sdap_handle *sh;
struct sss_domain_info *dom;
struct sysdb_ctx *sysdb;
struct sysdb_attrs *group;
struct ldb_message_element* sysdb_dns;
struct ldb_message_element* ghost_dns;
char **queued_members;
int queue_len;
const char **attrs;
const char *filter;
size_t queue_idx;
size_t count;
size_t check_count;
bool enumeration;
};
#define GROUPMEMBER_REQ_PARALLEL 50
static void sdap_process_group_members(struct tevent_req *subreq);
static int sdap_process_group_members_2307bis(struct tevent_req *req,
struct sdap_process_group_state *state,
struct ldb_message_element *memberel);
static int sdap_process_group_members_2307(struct sdap_process_group_state *state,
struct ldb_message_element *memberel,
struct ldb_message_element *ghostel);
static errno_t sdap_process_group_create_dns(TALLOC_CTX *mem_ctx,
size_t num_values,
struct ldb_message_element **_dns)
{
struct ldb_message_element *dns;
dns = talloc(mem_ctx, struct ldb_message_element);
if (dns == NULL) {
return ENOMEM;
}
dns->num_values = 0;
dns->values = talloc_array(dns, struct ldb_val,
num_values);
if (dns->values == NULL) {
talloc_zfree(dns);
return ENOMEM;
}
*_dns = dns;
return EOK;
}
struct tevent_req *sdap_process_group_send(TALLOC_CTX *memctx,
struct tevent_context *ev,
struct sss_domain_info *dom,
struct sysdb_ctx *sysdb,
struct sdap_options *opts,
struct sdap_handle *sh,
struct sysdb_attrs *group,
bool enumeration)
{
struct ldb_message_element *el;
struct ldb_message_element *ghostel;
struct sdap_process_group_state *grp_state;
struct tevent_req *req = NULL;
const char **attrs;
char* filter;
int ret;
req = tevent_req_create(memctx, &grp_state,
struct sdap_process_group_state);
if (!req) return NULL;
ret = build_attrs_from_map(grp_state, opts->user_map, SDAP_OPTS_USER,
NULL, &attrs, NULL);
if (ret) {
goto done;
}
/* FIXME: we ignore nested rfc2307bis groups for now */
filter = talloc_asprintf(grp_state, "(objectclass=%s)",
opts->user_map[SDAP_OC_USER].name);
if (!filter) {
talloc_zfree(req);
return NULL;
}
grp_state->ev = ev;
grp_state->opts = opts;
grp_state->dom = dom;
grp_state->sh = sh;
grp_state->sysdb = sysdb;
grp_state->group = group;
grp_state->check_count = 0;
grp_state->queue_idx = 0;
grp_state->queued_members = NULL;
grp_state->queue_len = 0;
grp_state->filter = filter;
grp_state->attrs = attrs;
grp_state->enumeration = enumeration;
ret = sysdb_attrs_get_el(group,
opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name,
&el);
if (ret) {
goto done;
}
/* Group without members */
if (el->num_values == 0) {
DEBUG(2, ("No Members. Done!\n"));
ret = EOK;
goto done;
}
ret = sysdb_attrs_get_el(group,
SYSDB_GHOST,
&ghostel);
if (ret) {
goto done;
}
if (ghostel->num_values == 0) {
/* Element was probably newly created, look for "member" again */
ret = sysdb_attrs_get_el(group,
opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name,
&el);
if (ret != EOK) {
goto done;
}
}
ret = sdap_process_group_create_dns(grp_state, el->num_values,
&grp_state->sysdb_dns);
if (ret != EOK) {
goto done;
}
ret = sdap_process_group_create_dns(grp_state, el->num_values,
&grp_state->ghost_dns);
if (ret != EOK) {
goto done;
}
switch (opts->schema_type) {
case SDAP_SCHEMA_RFC2307:
ret = sdap_process_group_members_2307(grp_state, el, ghostel);
break;
case SDAP_SCHEMA_IPA_V1:
case SDAP_SCHEMA_AD:
case SDAP_SCHEMA_RFC2307BIS:
/* Note that this code branch will be used only if
* ldap_nesting_level = 0 is set in config file
*/
ret = sdap_process_group_members_2307bis(req, grp_state, el);
break;
default:
DEBUG(1, ("Unknown schema type %d\n", opts->schema_type));
ret = EINVAL;
break;
}
done:
/* We managed to process all the entries */
/* EBUSY means we need to wait for entries in LDAP */
if (ret == EOK) {
DEBUG(7, ("All group members processed\n"));
tevent_req_done(req);
tevent_req_post(req, ev);
}
if (ret != EOK && ret != EBUSY) {
tevent_req_error(req, ret);
tevent_req_post(req, ev);
}
return req;
}
static int
sdap_process_missing_member_2307bis(struct tevent_req *req,
char *user_dn,
int num_users)
{
struct sdap_process_group_state *grp_state =
tevent_req_data(req, struct sdap_process_group_state);
struct tevent_req *subreq;
/*
* Issue at most GROUPMEMBER_REQ_PARALLEL LDAP searches at once.
* The rest is sent while the results are being processed.
* We limit the number as of request here, as the Server might
* enforce limits on the number of pending operations per
* connection.
*/
if (grp_state->check_count > GROUPMEMBER_REQ_PARALLEL) {
DEBUG(7, (" queueing search for: %s\n", user_dn));
if (!grp_state->queued_members) {
DEBUG(7, ("Allocating queue for %d members\n",
num_users - grp_state->check_count));
grp_state->queued_members = talloc_array(grp_state, char *,
num_users - grp_state->check_count + 1);
if (!grp_state->queued_members) {
return ENOMEM;
}
}
grp_state->queued_members[grp_state->queue_len] = user_dn;
grp_state->queue_len++;
} else {
subreq = sdap_get_generic_send(grp_state,
grp_state->ev,
grp_state->opts,
grp_state->sh,
user_dn,
LDAP_SCOPE_BASE,
grp_state->filter,
grp_state->attrs,
grp_state->opts->user_map,
SDAP_OPTS_USER,
dp_opt_get_int(grp_state->opts->basic,
SDAP_SEARCH_TIMEOUT),
false);
if (!subreq) {
return ENOMEM;
}
tevent_req_set_callback(subreq, sdap_process_group_members, req);
}
grp_state->check_count++;
return EOK;
}
static int
sdap_process_group_members_2307bis(struct tevent_req *req,
struct sdap_process_group_state *state,
struct ldb_message_element *memberel)
{
char *member_dn;
char *strdn;
int ret;
int i;
for (i=0; i < memberel->num_values; i++) {
member_dn = (char *)memberel->values[i].data;
ret = sdap_find_entry_by_origDN(state->sysdb_dns->values,
state->sysdb,
state->dom,
member_dn,
&strdn);
if (ret == EOK) {
/*
* User already cached in sysdb. Remember the sysdb DN for later
* use by sdap_save_groups()
*/
DEBUG(7, ("sysdbdn: %s\n", strdn));
state->sysdb_dns->values[state->sysdb_dns->num_values].data =
(uint8_t*) strdn;
state->sysdb_dns->values[state->sysdb_dns->num_values].length =
strlen(strdn);
state->sysdb_dns->num_values++;
} else if (ret == ENOENT) {
if (!state->enumeration) {
/* The user is not in sysdb, need to add it
* We don't need to do this if we're in an enumeration,
* because all real members should all be populated
* already by the first pass of the enumeration.
* Also, we don't want to be holding the sysdb
* transaction while we're performing LDAP lookups.
*/
DEBUG(7, ("Searching LDAP for missing user entry\n"));
ret = sdap_process_missing_member_2307bis(req,
member_dn,
memberel->num_values);
if (ret != EOK) {
DEBUG(1, ("Error processing missing member #%d (%s):\n",
i, member_dn));
return ret;
}
}
} else {
DEBUG(1, ("Error checking cache for member #%d (%s):\n",
i, (char *)memberel->values[i].data));
return ret;
}
}
if (state->queue_len > 0) {
state->queued_members[state->queue_len]=NULL;
}
if (state->check_count == 0) {
/*
* All group members are already cached in sysdb, we are done
* with this group. To avoid redundant sysdb lookups, populate the
* "member" attribute of the group entry with the sysdb DNs of
* the members.
*/
ret = EOK;
memberel->values = talloc_steal(state->group, state->sysdb_dns->values);
memberel->num_values = state->sysdb_dns->num_values;
} else {
state->count = state->check_count;
ret = EBUSY;
}
return ret;
}
static int
sdap_add_group_member_2307(struct ldb_message_element *sysdb_dns,
struct sss_domain_info *dom,
const char *username)
{
sysdb_dns->values[sysdb_dns->num_values].data =
(uint8_t *) talloc_strdup(sysdb_dns->values, username);
if (sysdb_dns->values[sysdb_dns->num_values].data == NULL) {
return ENOMEM;
}
sysdb_dns->values[sysdb_dns->num_values].length =
strlen(username);
sysdb_dns->num_values++;
return EOK;
}
static int
sdap_process_missing_member_2307(struct sdap_process_group_state *state,
char *member_name, time_t now)
{
int ret;
TALLOC_CTX *tmp_ctx;
const char *filter;
const char *username;
const char *user_dn;
size_t count;
struct ldb_message **msgs = NULL;
static const char *attrs[] = { SYSDB_NAME, NULL };
tmp_ctx = talloc_new(NULL);
if (!tmp_ctx) return ENOMEM;
/* Check for the alias in the sysdb */
filter = talloc_asprintf(tmp_ctx, "(%s=%s)", SYSDB_NAME_ALIAS, member_name);
if (!filter) {
ret = ENOMEM;
goto done;
}
ret = sysdb_search_users(tmp_ctx, state->sysdb, filter,
attrs, &count, &msgs);
if (ret == EOK && count > 0) {
/* Entry exists but the group references it with an alias. */
if (count != 1) {
DEBUG(1, ("More than one entry with this alias?\n"));
ret = EIO;
goto done;
}
/* fill username with primary name */
username = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL);
if (username == NULL) {
ret = EINVAL;
DEBUG(SSSDBG_MINOR_FAILURE, ("Inconsistent sysdb: user "
"without primary name?\n"));
goto done;
}
user_dn = sysdb_user_strdn(tmp_ctx, state->dom->name, username);
if (user_dn == NULL) {
return ENOMEM;
}
ret = sdap_add_group_member_2307(state->sysdb_dns, state->dom, user_dn);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, ("Could not add group member %s\n", username));
}
} else if (ret == ENOENT || count == 0) {
/* The entry really does not exist, add a ghost */
DEBUG(SSSDBG_TRACE_FUNC, ("Adding a ghost entry\n"));
ret = sdap_add_group_member_2307(state->ghost_dns, state->dom, member_name);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, ("Could not add group member %s\n", member_name));
}
} else {
ret = EIO;
}
done:
talloc_free(tmp_ctx);
return ret;
}
static int
sdap_process_group_members_2307(struct sdap_process_group_state *state,
struct ldb_message_element *memberel,
struct ldb_message_element *ghostel)
{
struct ldb_message *msg;
char *member_name;
char *userdn;
int ret;
time_t now;
int i;
now = time(NULL);
for (i=0; i < memberel->num_values; i++) {
member_name = (char *)memberel->values[i].data;
/* We need to skip over zero-length usernames */
if (member_name[0] == '\0') continue;
ret = sysdb_search_user_by_name(state, state->sysdb, state->dom,
member_name, NULL, &msg);
if (ret == EOK) {
/*
* User already cached in sysdb. Remember the sysdb DN for later
* use by sdap_save_groups()
*/
DEBUG(7, ("Member already cached in sysdb: %s\n", member_name));
userdn = sysdb_user_strdn(state->sysdb_dns, state->dom->name, member_name);
if (userdn == NULL) {
return ENOMEM;
}
ret = sdap_add_group_member_2307(state->sysdb_dns, state->dom, userdn);
if (ret != EOK) {
DEBUG(1, ("Could not add member %s into sysdb\n", member_name));
goto done;
}
} else if (ret == ENOENT) {
/* The user is not in sysdb, need to add it */
DEBUG(7, ("member #%d (%s): not found in sysdb\n",
i, member_name));
ret = sdap_process_missing_member_2307(state, member_name, now);
if (ret != EOK) {
DEBUG(1, ("Error processing missing member #%d (%s):\n",
i, member_name));
goto done;
}
} else {
DEBUG(1, ("Error checking cache for member #%d (%s):\n",
i, (char *) memberel->values[i].data));
goto done;
}
}
ret = EOK;
talloc_free(memberel->values);
memberel->values = talloc_steal(state->group, state->sysdb_dns->values);
memberel->num_values = state->sysdb_dns->num_values;
talloc_free(ghostel->values);
ghostel->values = talloc_steal(state->group, state->ghost_dns->values);
ghostel->num_values = state->ghost_dns->num_values;
done:
return ret;
}
static void sdap_process_group_members(struct tevent_req *subreq)
{
struct sysdb_attrs **usr_attrs;
size_t count;
int ret;
struct tevent_req *req =
tevent_req_callback_data(subreq, struct tevent_req);
struct sdap_process_group_state *state =
tevent_req_data(req, struct sdap_process_group_state);
struct ldb_message_element *el;
uint8_t* name_string;
state->check_count--;
DEBUG(9, ("Members remaining: %d\n", state->check_count));
ret = sdap_get_generic_recv(subreq, state, &count, &usr_attrs);
talloc_zfree(subreq);
if (ret) {
goto next;
}
if (count != 1) {
ret = EINVAL;
DEBUG(7, ("Expected one user entry and got %d\n", count));
goto next;
}
ret = sysdb_attrs_get_el(usr_attrs[0],
state->opts->user_map[SDAP_AT_USER_NAME].sys_name, &el);
if (el->num_values == 0) {
ret = EINVAL;
}
if (ret) {
DEBUG(2, ("Failed to get the member's name\n"));
goto next;
}
name_string = el[0].values[0].data;
state->ghost_dns->values[state->ghost_dns->num_values].data =
talloc_steal(state->ghost_dns->values, name_string);
state->ghost_dns->values[state->ghost_dns->num_values].length =
strlen((char *)name_string);
state->ghost_dns->num_values++;
next:
if (ret) {
DEBUG(7, ("Error reading group member. Skipping\n", ret));
state->count--;
}
/* Are there more searches for uncached users to submit ? */
if (state->queued_members && state->queued_members[state->queue_idx]) {
subreq = sdap_get_generic_send(state,
state->ev, state->opts, state->sh,
state->queued_members[state->queue_idx],
LDAP_SCOPE_BASE,
state->filter,
state->attrs,
state->opts->user_map,
SDAP_OPTS_USER,
dp_opt_get_int(state->opts->basic,
SDAP_SEARCH_TIMEOUT),
false);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq,
sdap_process_group_members, req);
state->queue_idx++;
}
if (state->check_count == 0) {
/*
* To avoid redundant sysdb lookups, populate the "member" attribute
* of the group entry with the sysdb DNs of the members.
*/
ret = sysdb_attrs_get_el(state->group,
state->opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name,
&el);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
("Failed to get the group member attribute [%d]: %s\n",
ret, strerror(ret)));
tevent_req_error(req, ret);
return;
}
el->values = talloc_steal(state->group, state->sysdb_dns->values);
el->num_values = state->sysdb_dns->num_values;
ret = sysdb_attrs_get_el(state->group, SYSDB_GHOST, &el);
if (ret != EOK) {
tevent_req_error(req, ret);
return;
}
el->values = talloc_steal(state->group, state->ghost_dns->values);
el->num_values = state->ghost_dns->num_values;
DEBUG(9, ("Processed Group - Done\n"));
tevent_req_done(req);
}
}
static int sdap_process_group_recv(struct tevent_req *req)
{
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}
/* ==Search-Groups-with-filter============================================ */
struct sdap_get_groups_state {
struct tevent_context *ev;
struct sdap_options *opts;
struct sdap_handle *sh;
struct sss_domain_info *dom;
struct sysdb_ctx *sysdb;
const char **attrs;
const char *base_filter;
char *filter;
int timeout;
bool enumeration;
char *higher_usn;
struct sysdb_attrs **groups;
size_t count;
size_t check_count;
hash_table_t *user_hash;
hash_table_t *group_hash;
size_t base_iter;
struct sdap_search_base **search_bases;
};
static errno_t sdap_get_groups_next_base(struct tevent_req *req);
static void sdap_get_groups_process(struct tevent_req *subreq);
static void sdap_get_groups_done(struct tevent_req *subreq);
struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx,
struct tevent_context *ev,
struct sss_domain_info *dom,
struct sysdb_ctx *sysdb,
struct sdap_options *opts,
struct sdap_search_base **search_bases,
struct sdap_handle *sh,
const char **attrs,
const char *filter,
int timeout,
bool enumeration)
{
errno_t ret;
struct tevent_req *req;
struct sdap_get_groups_state *state;
req = tevent_req_create(memctx, &state, struct sdap_get_groups_state);
if (!req) return NULL;
state->ev = ev;
state->opts = opts;
state->dom = dom;
state->sh = sh;
state->sysdb = sysdb;
state->attrs = attrs;
state->higher_usn = NULL;
state->groups = NULL;
state->count = 0;
state->timeout = timeout;
state->enumeration = enumeration;
state->base_filter = filter;
state->base_iter = 0;
state->search_bases = search_bases;
if (!search_bases) {
DEBUG(SSSDBG_CRIT_FAILURE,
("Group lookup request without a search base\n"));
ret = EINVAL;
goto done;
}
ret = sdap_get_groups_next_base(req);
done:
if (ret != EOK) {
tevent_req_error(req, ret);
tevent_req_post(req, ev);
}
return req;
}
static errno_t sdap_get_groups_next_base(struct tevent_req *req)
{
struct tevent_req *subreq;
struct sdap_get_groups_state *state;
state = tevent_req_data(req, struct sdap_get_groups_state);
talloc_zfree(state->filter);
state->filter = sdap_get_id_specific_filter(state,
state->base_filter,
state->search_bases[state->base_iter]->filter);
if (!state->filter) {
return ENOMEM;
}
DEBUG(SSSDBG_TRACE_FUNC,
("Searching for groups with base [%s]\n",
state->search_bases[state->base_iter]->basedn));
subreq = sdap_get_generic_send(
state, state->ev, state->opts, state->sh,
state->search_bases[state->base_iter]->basedn,
state->search_bases[state->base_iter]->scope,
state->filter, state->attrs,
state->opts->group_map, SDAP_OPTS_GROUP,
state->timeout,
state->enumeration); /* If we're enumerating, we need paging */
if (!subreq) {
return ENOMEM;
}
tevent_req_set_callback(subreq, sdap_get_groups_process, req);
return EOK;
}
static struct tevent_req *sdap_nested_group_process_send(
TALLOC_CTX *mem_ctx, struct tevent_context *ev,
struct sss_domain_info *domain,
struct sysdb_ctx *sysdb, struct sysdb_attrs *group,
hash_table_t *users, hash_table_t *groups,
struct sdap_options *opts, struct sdap_handle *sh,
bool enable_deref, uint32_t nesting);
static void sdap_nested_done(struct tevent_req *req);
static errno_t sdap_nested_group_process_recv(struct tevent_req *req);
static void sdap_ad_match_rule_members_process(struct tevent_req *subreq);
static void sdap_get_groups_process(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq, struct tevent_req);
struct sdap_get_groups_state *state =
tevent_req_data(req, struct sdap_get_groups_state);
int ret;
int i;
bool next_base = false;
size_t count;
struct sysdb_attrs **groups;
bool enable_deref = true;
ret = sdap_get_generic_recv(subreq, state,
&count, &groups);
talloc_zfree(subreq);
if (ret) {
tevent_req_error(req, ret);
return;
}
DEBUG(6, ("Search for groups, returned %d results.\n", count));
if (!state->enumeration && count > 1) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Individual group search returned multiple results\n"));
tevent_req_error(req, EINVAL);
return;
}
if (state->enumeration || count == 0) {
next_base = true;
}
/* Add this batch of groups to the list */
if (count > 0) {
state->groups =
talloc_realloc(state,
state->groups,
struct sysdb_attrs *,
state->count + count + 1);
if (!state->groups) {
tevent_req_error(req, ENOMEM);
return;
}
/* Copy the new groups into the list
*/
for (i = 0; i < count; i++) {
state->groups[state->count + i] =
talloc_steal(state->groups, groups[i]);
}
state->count += count;
state->groups[state->count] = NULL;
}
if (next_base) {
state->base_iter++;
if (state->search_bases[state->base_iter]) {
/* There are more search bases to try */
ret = sdap_get_groups_next_base(req);
if (ret != EOK) {
tevent_req_error(req, ret);
}
return;
}
}
/* No more search bases
* Return ENOENT if no groups were found
*/
if (state->count == 0) {
tevent_req_error(req, ENOENT);
return;
}
/* Check whether we need to do nested searches
* for RFC2307bis/FreeIPA/ActiveDirectory
* We don't need to do this for enumeration,
* because all groups will be picked up anyway.
*
* We can also skip this if we're using the
* LDAP_MATCHING_RULE_IN_CHAIN available in
* AD 2008 and later
*/
if (!state->enumeration) {
if ((state->opts->schema_type != SDAP_SCHEMA_RFC2307)
&& (dp_opt_get_int(state->opts->basic, SDAP_NESTING_LEVEL) != 0)
&& !dp_opt_get_bool(state->opts->basic, SDAP_AD_MATCHING_RULE_GROUPS)) {
/* Prepare hashes for nested user processing */
ret = sss_hash_create(state, 32, &state->user_hash);
if (ret != EOK) {
tevent_req_error(req, ret);
return;
}
ret = sss_hash_create(state, 32, &state->group_hash);
if (ret != EOK) {
tevent_req_error(req, ret);
return;
}
/*
* If any search base contains filter, disable dereference.
*/
enable_deref = true;
for (i = 0; state->opts->user_search_bases[i] != NULL; i++) {
if (state->opts->user_search_bases[i]->filter != NULL) {
DEBUG(SSSDBG_TRACE_FUNC,
("User search base contains filter, "
"dereference will be disabled\n"));
enable_deref = false;
break;
}
}
if (enable_deref) {
for (i = 0; state->opts->group_search_bases[i] != NULL; i++) {
if (state->opts->group_search_bases[i]->filter != NULL) {
DEBUG(SSSDBG_TRACE_FUNC,
("Group search base contains filter, "
"dereference will be disabled\n"));
enable_deref = false;
break;
}
}
}
subreq = sdap_nested_group_process_send(state,
state->ev,
state->dom,
state->sysdb,
state->groups[0],
state->user_hash,
state->group_hash,
state->opts,
state->sh,
enable_deref,
0);
if (!subreq) {
tevent_req_error(req, EIO);
return;
}
tevent_req_set_callback(subreq, sdap_nested_done, req);
return;
}
}
/* We have all of the groups. Save them to the sysdb */
state->check_count = state->count;
/* If we're using LDAP_MATCHING_RULE_IN_CHAIN, start a subreq to
* retrieve the members so we can save them in a single step.
*/
if (!state->enumeration
&& (state->opts->schema_type != SDAP_SCHEMA_RFC2307)
&& state->opts->support_matching_rule
&& dp_opt_get_bool(state->opts->basic, SDAP_AD_MATCHING_RULE_GROUPS)) {
subreq = sdap_get_ad_match_rule_members_send(
state, state->ev, state->opts, state->sh,
state->groups[0], state->timeout);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq,
sdap_ad_match_rule_members_process,
req);
return;
}
ret = sysdb_transaction_start(state->sysdb);
if (ret != EOK) {
DEBUG(0, ("Failed to start transaction\n"));
tevent_req_error(req, ret);
return;
}
if (state->enumeration
&& state->opts->schema_type != SDAP_SCHEMA_RFC2307
&& dp_opt_get_int(state->opts->basic, SDAP_NESTING_LEVEL) != 0) {
DEBUG(9, ("Saving groups without members first "
"to allow unrolling of nested groups.\n"));
ret = sdap_save_groups(state, state->sysdb, state->dom, state->opts,
state->groups, state->count, false,
NULL, NULL);
if (ret) {
DEBUG(2, ("Failed to store groups.\n"));
tevent_req_error(req, ret);
return;
}
}
for (i = 0; i < state->count; i++) {
subreq = sdap_process_group_send(state, state->ev, state->dom,
state->sysdb, state->opts,
state->sh, state->groups[i],
state->enumeration);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, sdap_get_groups_done, req);
}
}
static void sdap_get_groups_done(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq, struct tevent_req);
struct sdap_get_groups_state *state =
tevent_req_data(req, struct sdap_get_groups_state);
int ret;
errno_t sysret;
ret = sdap_process_group_recv(subreq);
talloc_zfree(subreq);
if (ret) {
sysret = sysdb_transaction_cancel(state->sysdb);
if (sysret != EOK) {
DEBUG(0, ("Could not cancel sysdb transaction\n"));
}
tevent_req_error(req, ret);
return;
}
state->check_count--;
DEBUG(9, ("Groups remaining: %d\n", state->check_count));
if (state->check_count == 0) {
DEBUG(9, ("All groups processed\n"));
/* If ignore_group_members is set for the domain, don't update
* group memberships in the cache.
*/
ret = sdap_save_groups(state, state->sysdb, state->dom, state->opts,
state->groups, state->count,
!state->dom->ignore_group_members, NULL,
&state->higher_usn);
if (ret) {
DEBUG(2, ("Failed to store groups.\n"));
tevent_req_error(req, ret);
return;
}
DEBUG(9, ("Saving %d Groups - Done\n", state->count));
sysret = sysdb_transaction_commit(state->sysdb);
if (sysret != EOK) {
DEBUG(0, ("Couldn't commit transaction\n"));
tevent_req_error(req, sysret);
} else {
tevent_req_done(req);
}
}
}
static errno_t sdap_nested_group_populate_users(TALLOC_CTX *mem_ctx,
struct sysdb_ctx *sysdb,
struct sss_domain_info *domain,
struct sdap_options *opts,
struct sysdb_attrs **users,
int num_users,
hash_table_t **_ghosts);
static void sdap_ad_match_rule_members_process(struct tevent_req *subreq)
{
errno_t ret;
TALLOC_CTX *tmp_ctx = NULL;
struct tevent_req *req =
tevent_req_callback_data(subreq, struct tevent_req);
struct sdap_get_groups_state *state = tevent_req_data(req,
struct sdap_get_groups_state);
struct sysdb_attrs **users;
struct sysdb_attrs *group = state->groups[0];
struct ldb_message_element *member_el;
struct ldb_message_element *orig_dn_el;
size_t count;
size_t i;
hash_table_t *ghosts;
ret = sdap_get_ad_match_rule_members_recv(subreq, state,
&count, &users);
talloc_zfree(subreq);
if (ret != EOK && ret != ENOENT) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Could not retrieve members using AD match rule. [%s]\n",
strerror(ret)));
goto done;
}
/* Save the group and users to the cache */
/* Truncate the member attribute of the group.
* It will be repopulated below, and it may currently
* be incomplete anyway, thanks to the range extension.
*/
ret = sysdb_attrs_get_el(group, SYSDB_MEMBER, &member_el);
if (ret != EOK) {
goto done;
}
member_el->num_values = 0;
talloc_zfree(member_el->values);
tmp_ctx = talloc_new(NULL);
if (!tmp_ctx) {
ret = ENOMEM;
goto done;
}
/* Figure out which users are already cached in the sysdb and
* which ones need to be added as ghost users.
*/
ret = sdap_nested_group_populate_users(tmp_ctx, state->sysdb, state->dom,
state->opts, users, count,
&ghosts);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Could not determine which users are ghosts: [%s]\n",
strerror(ret)));
goto done;
}
/* Add any entries that aren't in the ghost hash table to the
* member element of the group. This will get converted to a
* native sysdb representation later in sdap_save_groups().
*/
/* Add all of the users as members
*/
member_el->values = talloc_zero_array(tmp_ctx, struct ldb_val, count);
if (!member_el->values) {
ret = ENOMEM;
goto done;
}
/* Copy the origDN values of the users into the member element */
for (i = 0; i < count; i++) {
ret = sysdb_attrs_get_el(users[i], SYSDB_ORIG_DN,
&orig_dn_el);
if (ret != EOK) {
/* This should never happen. Every entry should have
* an originalDN.
*/
DEBUG(SSSDBG_MINOR_FAILURE,
("BUG: Missing originalDN for user?\n"));
goto done;
}
/* These values will have the same lifespan, so instead
* of copying them, just point at the data.
*/
member_el->values[i].data = orig_dn_el->values[0].data;
member_el->values[i].length = orig_dn_el->values[0].length;
}
member_el->num_values = count;
/* Now save the group, users and ghosts to the cache */
ret = sdap_save_groups(tmp_ctx, state->sysdb, state->dom,
state->opts, state->groups, 1,
false, ghosts, NULL);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Could not save group to the cache: [%s]\n",
strerror(ret)));
goto done;
}
ret = EOK;
done:
talloc_free(tmp_ctx);
if (ret == EOK) {
tevent_req_done(req);
} else {
tevent_req_error(req, ret);
}
}
int sdap_get_groups_recv(struct tevent_req *req,
TALLOC_CTX *mem_ctx, char **usn_value)
{
struct sdap_get_groups_state *state = tevent_req_data(req,
struct sdap_get_groups_state);
TEVENT_REQ_RETURN_ON_ERROR(req);
if (usn_value) {
*usn_value = talloc_steal(mem_ctx, state->higher_usn);
}
return EOK;
}
static void sdap_nested_done(struct tevent_req *subreq)
{
errno_t ret, tret;
int hret;
unsigned long i;
unsigned long user_count;
unsigned long group_count;
hash_value_t *values;
bool in_transaction = false;
struct sysdb_attrs **users = NULL;
struct sysdb_attrs **groups = NULL;
hash_table_t *ghosts;
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct sdap_get_groups_state *state = tevent_req_data(req,
struct sdap_get_groups_state);
ret = sdap_nested_group_process_recv(subreq);
talloc_zfree(subreq);
if (ret != EOK) {
DEBUG(1, ("Nested group processing failed: [%d][%s]\n",
ret, strerror(ret)));
goto fail;
}
hret = hash_values(state->user_hash, &user_count, &values);
if (hret != HASH_SUCCESS) {
ret = EIO;
goto fail;
}
if (user_count) {
users = talloc_array(state, struct sysdb_attrs *, user_count);
if (!users) {
talloc_free(values);
ret = ENOMEM;
goto fail;
}
for (i = 0; i < user_count; i++) {
users[i] = talloc_get_type(values[i].ptr, struct sysdb_attrs);
}
talloc_zfree(values);
}
/* Users are all retrieved. Now retrieve groups */
hret = hash_values(state->group_hash, &group_count, &values);
if (hret != HASH_SUCCESS) {
ret = EIO;
goto fail;
}
groups = talloc_array(state, struct sysdb_attrs *, group_count);
if (!groups) {
talloc_free(values);
ret = ENOMEM;
goto fail;
}
for (i = 0; i < group_count; i++) {
groups[i] = talloc_get_type(values[i].ptr, struct sysdb_attrs);
}
talloc_zfree(values);
/* Save all of the users first so that they are in
* place for the groups to add them.
*/
ret = sysdb_transaction_start(state->sysdb);
if (ret != EOK) {
DEBUG(1, ("Failed to start transaction\n"));
goto fail;
}
in_transaction = true;
ret = sdap_nested_group_populate_users(state, state->sysdb,
state->dom, state->opts,
users, user_count, &ghosts);
if (ret != EOK) {
goto fail;
}
ret = sdap_save_groups(state, state->sysdb, state->dom, state->opts,
groups, group_count, false, ghosts,
&state->higher_usn);
if (ret != EOK) {
goto fail;
}
ret = sysdb_transaction_commit(state->sysdb);
if (ret != EOK) {
DEBUG(1, ("Failed to commit transaction\n"));
goto fail;
}
in_transaction = false;
/* Processing complete */
tevent_req_done(req);
return;
fail:
if (in_transaction) {
tret = sysdb_transaction_cancel(state->sysdb);
if (tret != EOK) {
DEBUG(1, ("Failed to cancel transaction\n"));
}
}
tevent_req_error(req, ret);
}
static errno_t sdap_nested_group_populate_users(TALLOC_CTX *mem_ctx,
struct sysdb_ctx *sysdb,
struct sss_domain_info *domain,
struct sdap_options *opts,
struct sysdb_attrs **users,
int num_users,
hash_table_t **_ghosts)
{
int i;
errno_t ret, sret;
struct ldb_message_element *el;
const char *username;
char *clean_orig_dn;
const char *original_dn;
TALLOC_CTX *tmp_ctx;
struct ldb_message **msgs;
char *filter;
const char *sysdb_name;
struct sysdb_attrs *attrs;
static const char *search_attrs[] = { SYSDB_NAME, NULL };
hash_table_t *ghosts;
hash_key_t key;
hash_value_t value;
size_t count;
bool in_transaction = false;
if (_ghosts == NULL) {
return EINVAL;
}
if (num_users == 0) {
/* Nothing to do if there are no users */
*_ghosts = NULL;
return EOK;
}
tmp_ctx = talloc_new(NULL);
if (!tmp_ctx) return ENOMEM;
ret = sss_hash_create(tmp_ctx, num_users, &ghosts);
if (ret != HASH_SUCCESS) {
ret = ENOMEM;
goto done;
}
ret = sysdb_transaction_start(sysdb);
if (ret) {
DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to start transaction!\n"));
goto done;
}
in_transaction = true;
for (i = 0; i < num_users; i++) {
ret = sysdb_attrs_primary_name(sysdb, users[i],
opts->user_map[SDAP_AT_USER_NAME].name,
&username);
if (ret != EOK) {
DEBUG(1, ("User entry %d has no name attribute. Skipping\n", i));
continue;
}
ret = sysdb_attrs_get_el(users[i], SYSDB_ORIG_DN, &el);
if (el->num_values == 0) {
ret = EINVAL;
}
if (ret != EOK) {
DEBUG(1, ("User entry %s has no originalDN attribute\n", i));
goto done;
}
original_dn = (const char *) el->values[0].data;
ret = sss_filter_sanitize(tmp_ctx, original_dn,
&clean_orig_dn);
if (ret != EOK) {
DEBUG(1, ("Cannot sanitize originalDN\n", i));
goto done;
}
/* Check for the specified origDN in the sysdb */
filter = talloc_asprintf(tmp_ctx, "(%s=%s)",
SYSDB_ORIG_DN,
clean_orig_dn);
if (!filter) {
ret = ENOMEM;
goto done;
}
ret = sysdb_search_users(tmp_ctx, sysdb, filter,
search_attrs, &count, &msgs);
talloc_zfree(filter);
talloc_zfree(clean_orig_dn);
if (ret != EOK && ret != ENOENT) {
DEBUG(1, ("Error checking cache for user entry\n"));
goto done;
} else if (ret == EOK) {
/* The entry is cached but expired. Update the username
* if needed. */
if (count != 1) {
DEBUG(1, ("More than one entry with this origDN? Skipping\n"));
continue;
}
sysdb_name = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL);
if (strcmp(sysdb_name, username) == 0) {
/* Username is correct, continue */
continue;
}
attrs = sysdb_new_attrs(tmp_ctx);
if (!attrs) {
ret = ENOMEM;
goto done;
}
ret = sysdb_attrs_add_string(attrs, SYSDB_NAME, username);
if (ret) goto done;
ret = sysdb_set_user_attr(sysdb, domain, sysdb_name,
attrs, SYSDB_MOD_REP);
if (ret != EOK) goto done;
} else {
key.type = HASH_KEY_STRING;
key.str = discard_const(original_dn);
value.type = HASH_VALUE_PTR;
value.ptr = discard_const(username);
ret = hash_enter(ghosts, &key, &value);
if (ret != HASH_SUCCESS) {
ret = ENOMEM;
goto done;
}
}
}
ret = sysdb_transaction_commit(sysdb);
if (ret) {
DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to commit transaction!\n"));
goto done;
}
in_transaction = false;
ret = EOK;
done:
if (in_transaction) {
sret = sysdb_transaction_cancel(sysdb);
if (sret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, ("Could not cancel transaction\n"));
}
}
if (ret != EOK) {
*_ghosts = NULL;
} else {
*_ghosts = talloc_steal(mem_ctx, ghosts);
}
talloc_zfree(tmp_ctx);
return ret;
}
/*
* Get user based on a user DN. Optimized for environments where the containers
* are strictly defined, such as IPA.
*/
static void
sdap_nested_get_user_done(struct tevent_req *subreq);
static errno_t
sdap_nested_get_ipa_user(TALLOC_CTX *mem_ctx, const char *user_dn,
struct sysdb_ctx *sysdb, struct sysdb_attrs ***_reply);
struct sdap_nested_get_user_state {
size_t count;
struct sysdb_attrs **replies;
};
static struct tevent_req *
sdap_nested_get_user_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
struct sss_domain_info *domain,
struct sysdb_ctx *sysdb,
struct sdap_options *opts,
struct sdap_handle *sh,
const char *user_dn,
const char *search_bases_filter)
{
errno_t ret;
struct tevent_req *req;
struct tevent_req *subreq;
const char **sdap_attrs;
const char *filter;
struct sdap_nested_get_user_state *state;
req = tevent_req_create(mem_ctx, &state,
struct sdap_nested_get_user_state);
if (!req) {
return NULL;
}
if (opts->schema_type == SDAP_SCHEMA_IPA_V1) {
/* If the schema is IPA, then just shortcut and guess the name */
ret = sdap_nested_get_ipa_user(state, user_dn, sysdb, &state->replies);
if (ret == EOK) {
state->count = 1;
goto immediate;
} else {
DEBUG(SSSDBG_MINOR_FAILURE, ("Couldn't parse out user information "
"based on DN %s, falling back to an LDAP lookup\n"));
}
}
/* Only pull down username and originalDN */
sdap_attrs = talloc_array(state, const char *, 3);
if (!sdap_attrs) {
ret = ENOMEM;
goto immediate;
}
sdap_attrs[0] = "objectClass";
sdap_attrs[1] = opts->user_map[SDAP_AT_USER_NAME].name;
sdap_attrs[2] = NULL;
if (search_bases_filter != NULL) {
filter = talloc_asprintf(sdap_attrs, "(&%s(objectclass=%s))",
search_bases_filter,
opts->user_map[SDAP_OC_USER].name);
} else {
filter = talloc_asprintf(sdap_attrs, "(objectclass=%s)",
opts->user_map[SDAP_OC_USER].name);
}
if (!filter) {
ret = ENOMEM;
goto immediate;
}
subreq = sdap_get_generic_send(state, ev, opts, sh, user_dn,
LDAP_SCOPE_BASE, filter, sdap_attrs,
opts->user_map, SDAP_OPTS_USER,
dp_opt_get_int(opts->basic,
SDAP_SEARCH_TIMEOUT),
false);
if (!subreq) {
ret = EIO;
goto immediate;
}
talloc_steal(subreq, sdap_attrs);
tevent_req_set_callback(subreq, sdap_nested_get_user_done, req);
return req;
immediate:
if (ret) {
tevent_req_error(req, ret);
} else {
tevent_req_done(req);
}
tevent_req_post(req, ev);
return req;
}
/* This should be a function pointer set from the IPA provider */
static errno_t
sdap_nested_get_ipa_user(TALLOC_CTX *mem_ctx, const char *user_dn,
struct sysdb_ctx *sysdb, struct sysdb_attrs ***_reply)
{
errno_t ret;
struct sysdb_attrs **reply = NULL;
struct sysdb_attrs *user = NULL;
char *name;
struct ldb_dn *dn = NULL;
const char *rdn_name;
const char *users_comp_name;
const char *acct_comp_name;
const struct ldb_val *rdn_val;
const struct ldb_val *users_comp_val;
const struct ldb_val *acct_comp_val;
TALLOC_CTX *tmp_ctx;
tmp_ctx = talloc_new(NULL);
if (!tmp_ctx) return ENOMEM;
dn = ldb_dn_new(tmp_ctx, sysdb_ctx_get_ldb(sysdb), user_dn);
if (dn == NULL) {
ret = ENOMEM;
goto done;
}
if (ldb_dn_get_comp_num(dn) < 4) {
/* RDN, users, accounts, and at least one DC=
* For example:
* uid=admin,cn=users,cn=accounts,dc=example,dc=com */
/* If it's fewer, it's not a user DN */
ret = ENOENT;
goto done;
}
/* If the RDN attribute name is 'uid' */
rdn_name = ldb_dn_get_rdn_name(dn);
if (rdn_name == NULL) {
/* Shouldn't happen if ldb_dn_validate()
* passed, but we'll be careful.
*/
ret = EINVAL;
goto done;
}
if (strcasecmp("uid", rdn_name) != 0) {
/* RDN has the wrong attribute name.
* It's not a service.
*/
ret = ENOENT;
goto done;
}
/* and the second component is "cn=users" */
users_comp_name = ldb_dn_get_component_name(dn, 1);
if (strcasecmp("cn", users_comp_name) != 0) {
/* The second component name is not "cn" */
ret = ENOENT;
goto done;
}
users_comp_val = ldb_dn_get_component_val(dn, 1);
if (strncasecmp("users",
(const char *) users_comp_val->data,
users_comp_val->length) != 0) {
/* The second component value is not "users" */
ret = ENOENT;
goto done;
}
/* and the third component is "cn=accounts" */
acct_comp_name = ldb_dn_get_component_name(dn, 2);
if (strcasecmp("cn", acct_comp_name) != 0) {
/* The third component name is not "cn" */
ret = ENOENT;
goto done;
}
acct_comp_val = ldb_dn_get_component_val(dn, 2);
if (strncasecmp("accounts",
(const char *) acct_comp_val->data,
acct_comp_val->length) != 0) {
/* The third component value is not "accounts" */
ret = ENOENT;
goto done;
}
/* Then the value of the RDN is the group name */
reply = talloc_zero_array(tmp_ctx, struct sysdb_attrs *, 2);
if (!reply) {
ret = ENOMEM;
goto done;
}
reply[0] = sysdb_new_attrs(reply);
if (!reply[0]) {
ret = ENOMEM;
goto done;
}
user = reply[0];
rdn_val = ldb_dn_get_rdn_val(dn);
name = talloc_strndup(user, (const char *)rdn_val->data,
rdn_val->length);
if (name == NULL) {
ret = ENOMEM;
goto done;
}
ret = sysdb_attrs_add_string(user, SYSDB_NAME, name);
if (ret != EOK) {
goto done;
}
ret = sysdb_attrs_add_string(user, SYSDB_ORIG_DN, user_dn);
if (ret != EOK) {
goto done;
}
ret = sysdb_attrs_add_string(user, SYSDB_OBJECTCLASS, SYSDB_USER_CLASS);
if (ret != EOK) {
goto done;
}
ret = EOK;
*_reply = talloc_steal(mem_ctx, reply);
done:
talloc_free(tmp_ctx);
return ret;
}
static void
sdap_nested_get_user_done(struct tevent_req *subreq)
{
errno_t ret;
struct tevent_req *req =
tevent_req_callback_data(subreq, struct tevent_req);
struct sdap_nested_get_user_state *state = tevent_req_data(req,
struct sdap_nested_get_user_state);
ret = sdap_get_generic_recv(subreq, state, &state->count, &state->replies);
talloc_zfree(subreq);
if (ret != EOK && ret != ENOENT) {
tevent_req_error(req, ret);
return;
}
tevent_req_done(req);
}
static errno_t
sdap_nested_get_user_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
size_t *_count, struct sysdb_attrs ***_replies)
{
struct sdap_nested_get_user_state *state = tevent_req_data(req,
struct sdap_nested_get_user_state);
TEVENT_REQ_RETURN_ON_ERROR(req);
if (_count) {
*_count = state->count;
}
if (_replies) {
*_replies = talloc_steal(mem_ctx, state->replies);
}
return EOK;
}
struct sdap_deref_ctx {
const char *orig_dn;
size_t expired_users_num;
uint32_t expired_users_index;
char **expired_users;
size_t expired_groups_num;
uint32_t expired_groups_index;
char **expired_groups;
size_t missing_dns_num;
uint32_t missing_dns_index;
char **missing_dns;
struct sdap_deref_attrs **deref_result;
size_t num_results;
uint32_t result_index;
int deref_threshold;
};
struct sdap_nested_group_ctx {
struct tevent_context *ev;
struct sysdb_ctx *sysdb;
struct sss_domain_info *domain;
hash_table_t *users;
hash_table_t *groups;
struct sdap_options *opts;
struct sdap_handle *sh;
uint32_t nesting_level;
struct ldb_message_element *members;
uint32_t member_index;
char *member_dn;
bool enable_deref;
struct sdap_deref_ctx *derefctx;
};
static errno_t sdap_nested_group_process_deref_step(struct tevent_req *req);
static errno_t sdap_nested_group_process_step(struct tevent_req *req);
static struct tevent_req *sdap_nested_group_process_send(
TALLOC_CTX *mem_ctx, struct tevent_context *ev,
struct sss_domain_info *domain,
struct sysdb_ctx *sysdb, struct sysdb_attrs *group,
hash_table_t *users, hash_table_t *groups,
struct sdap_options *opts, struct sdap_handle *sh,
bool enable_deref, uint32_t nesting)
{
errno_t ret;
int hret;
struct tevent_req *req;
struct sdap_nested_group_ctx *state;
const char *groupname;
hash_key_t key;
hash_value_t value;
gid_t gid;
req = tevent_req_create(mem_ctx, &state, struct sdap_nested_group_ctx);
if (!req) {
return NULL;
}
state->ev = ev;
state->sysdb = sysdb;
state->domain = domain;
state->users = users;
state->groups = groups;
state->opts = opts;
state->sh = sh;
state->enable_deref = enable_deref;
state->nesting_level = nesting;
/* If this is too many levels deep, just return success */
if (nesting > dp_opt_get_int(opts->basic, SDAP_NESTING_LEVEL)) {
ret = EOK;
goto immediate;
}
/* Add the current group to the groups hash so we don't
* look it up more than once
*/
key.type = HASH_KEY_STRING;
ret = sysdb_attrs_primary_name(sysdb, group,
opts->group_map[SDAP_AT_GROUP_NAME].sys_name,
&groupname);
if (ret != EOK) {
goto immediate;
}
key.str = talloc_strdup(state, groupname);
if (!key.str) {
ret = ENOMEM;
goto immediate;
}
if (hash_has_key(groups, &key)) {
/* This group has already been processed
* (or is in progress)
* Skip it and just return success
*/
ret = EOK;
goto immediate;
}
ret = sysdb_attrs_get_uint32_t(group,
opts->group_map[SDAP_AT_GROUP_GID].sys_name,
&gid);
if (ret == ENOENT || (ret == EOK && gid == 0)) {
DEBUG(9, ("The group's gid was %s\n", ret == ENOENT ? "missing" : "zero"));
DEBUG(8, ("Marking group as non-posix and setting GID=0!\n"));
if (ret == ENOENT) {
ret = sysdb_attrs_add_uint32(group,
opts->group_map[SDAP_AT_GROUP_GID].sys_name,
0);
if (ret != EOK) {
DEBUG(1, ("Failed to add a GID to non-posix group!\n"));
goto immediate;
}
}
ret = sysdb_attrs_add_bool(group, SYSDB_POSIX, false);
if (ret != EOK) {
DEBUG(2, ("Error: Failed to mark group as non-posix!\n"));
goto immediate;
}
} else if (ret) {
goto immediate;
}
value.type = HASH_VALUE_PTR;
value.ptr = talloc_steal(groups, group);
hret = hash_enter(groups, &key, &value);
if (hret != HASH_SUCCESS) {
ret = EIO;
goto immediate;
}
talloc_free(key.str);
/* Process group memberships */
/* TODO: future enhancement, check for memberuid as well
* See https://fedorahosted.org/sssd/ticket/445
*/
ret = sysdb_attrs_get_el(
group,
opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name,
&state->members);
if (ret != EOK) {
if (ret == ENOENT) {
/* No members to process */
ret = EOK;
}
goto immediate;
}
state->member_index = 0;
if (enable_deref && sdap_has_deref_support(state->sh, state->opts)) {
state->derefctx = talloc_zero(state, struct sdap_deref_ctx);
if (!state->derefctx) {
ret = ENOMEM;
goto immediate;
}
ret = sysdb_attrs_get_string(group, SYSDB_ORIG_DN,
&state->derefctx->orig_dn);
if (ret != EOK) goto immediate;
ret = sdap_nested_group_process_deref_step(req);
if (ret != EAGAIN) goto immediate;
} else {
ret = sdap_nested_group_process_step(req);
if (ret != EAGAIN) goto immediate;
}
return req;
immediate:
if (ret == EOK) {
tevent_req_done(req);
} else {
tevent_req_error(req, ret);
}
tevent_req_post(req, ev);
return req;
}
static errno_t sdap_nested_group_check_hash(struct sdap_nested_group_ctx *);
static errno_t sdap_nested_group_check_cache(TALLOC_CTX *mem_ctx,
struct sysdb_ctx *sysdb,
struct sss_domain_info *dom,
struct sdap_options *opts,
char *member_dn,
struct ldb_message ***_msgs,
enum sysdb_member_type *_mtype);
static void sdap_nested_group_process_ldap_user(struct tevent_req *subreq);
static void sdap_nested_group_process_user(struct tevent_req *subreq);
static errno_t sdap_nested_group_lookup_user(struct tevent_req *req,
tevent_req_fn fn);
static errno_t sdap_nested_group_lookup_group(struct tevent_req *req);
static errno_t sdap_nested_group_process_deref_call(struct tevent_req *req);
static errno_t sdap_nested_group_process_noderef(struct tevent_req *req);
static errno_t sdap_nested_group_process_deref_step(struct tevent_req *req)
{
errno_t ret;
struct sdap_nested_group_ctx *state =
tevent_req_data(req, struct sdap_nested_group_ctx);
size_t missing = 0;
struct ldb_message **msgs = NULL;
enum sysdb_member_type mtype;
struct sdap_deref_ctx *dctx = state->derefctx;
dctx->deref_threshold = dp_opt_get_int(state->opts->basic,
SDAP_DEREF_THRESHOLD);
dctx->expired_users = talloc_array(dctx, char *,
state->members->num_values + 1);
dctx->expired_groups = talloc_array(dctx, char *,
state->members->num_values + 1);
dctx->missing_dns = talloc_array(dctx, char *,
state->members->num_values + 1);
if (!dctx->expired_users ||
!dctx->expired_groups ||
!dctx->missing_dns) return ENOMEM;
while (true) {
if (state->member_index >= state->members->num_values) {
/* No more entries to check. Return success */
talloc_zfree(state->member_dn);
ret = EOK;
break;
}
/* Continue to loop through until all entries have been
* processed.
*/
ret = sdap_nested_group_check_hash(state);
if (ret == EOK) {
talloc_zfree(state->member_dn);
break; /* All remaining members in hash, check missing */
} else if (ret != ENOENT) {
goto done; /* Unexpected error */
}
ret = sdap_nested_group_check_cache(state, state->sysdb,
state->domain,
state->opts,
state->member_dn,
&msgs, &mtype);
if (ret == EOK) {
/* The entry is cached and valid */
state->member_index++;
talloc_zfree(state->member_dn);
continue;
} else if (ret == EAGAIN) {
/* The entry is cached but needs refresh */
switch(mtype) {
case SYSDB_MEMBER_GROUP:
DEBUG(8, ("Cached LDAP group [%s] needs refresh\n",
state->member_dn));
missing++;
dctx->expired_groups[dctx->expired_groups_num] =
talloc_move(dctx, &state->member_dn);
dctx->expired_groups_num++;
state->member_index++;
continue;
case SYSDB_MEMBER_USER:
DEBUG(8, ("Cached LDAP user [%s] needs refresh\n",
state->member_dn));
missing++;
dctx->expired_users[dctx->expired_users_num] =
talloc_move(dctx, &state->member_dn);
dctx->expired_users_num++;
state->member_index++;
continue;
default:
DEBUG(2, ("Unknown member value\n"));
ret = EINVAL;
goto done;
}
} else if (ret == ENOENT) {
/* The entry is missing. It is unclear whether it
* is a user or a group so we'll need to try looking
* it up */
missing++;
dctx->missing_dns[dctx->missing_dns_num] =
talloc_move(dctx, &state->member_dn);
dctx->missing_dns_num++;
state->member_index++;
continue;
}
/* Unexpected error, skip this entry */
state->member_index++;
continue;
} /* while (true) */
dctx->expired_users[dctx->expired_users_num] = NULL;
dctx->expired_groups[dctx->expired_groups_num] = NULL;
dctx->missing_dns[dctx->missing_dns_num] = NULL;
if (missing == 0) {
ret = EOK;
goto done;
}
if (missing > dctx->deref_threshold) {
DEBUG(6, ("Missing data past threshold, doing a full deref\n"));
ret = sdap_nested_group_process_deref_call(req);
} else {
DEBUG(6, ("Falling back to individual lookups\n"));
ret = sdap_nested_group_process_noderef(req);
}
if (ret != EOK && ret != EAGAIN) goto done;
return EAGAIN;
done:
talloc_zfree(state->member_dn);
return ret;
}
static errno_t sdap_nested_group_process_step(struct tevent_req *req)
{
errno_t ret;
struct sdap_nested_group_ctx *state =
tevent_req_data(req, struct sdap_nested_group_ctx);
struct ldb_message **msgs = NULL;
enum sysdb_member_type mtype;
while (true) {
/* Continue to loop through until all entries have been
* processed.
*/
ret = sdap_nested_group_check_hash(state);
if (ret == EOK) {
talloc_zfree(state->member_dn);
return EOK; /* All members in hash */
} else if (ret != ENOENT) {
goto error; /* Unexpected error */
}
ret = sdap_nested_group_check_cache(state, state->sysdb,
state->domain,
state->opts,
state->member_dn,
&msgs, &mtype);
if (ret == EOK) {
/* The entry is cached and valid */
state->member_index++;
talloc_zfree(state->member_dn);
continue;
} else if (ret == EAGAIN) {
/* The entry is cached but needs refresh */
switch(mtype) {
case SYSDB_MEMBER_GROUP:
DEBUG(6, ("Refreshing cached group from LDAP\n"));
ret = sdap_nested_group_lookup_group(req);
if (ret != EOK) goto error;
break;
case SYSDB_MEMBER_USER:
DEBUG(6, ("Refreshing cached user from LDAP\n"));
ret = sdap_nested_group_lookup_user(
req, sdap_nested_group_process_user);
if (ret != EOK) goto error;
break;
default:
DEBUG(2, ("Unknown member value\n"));
ret = EINVAL;
goto error;
}
return EAGAIN;
} else if (ret == ENOENT) {
/* It wasn't found in the cache either
* We'll have to do a blind lookup in LDAP
*/
/* Try users first */
ret = sdap_nested_group_lookup_user(
req, sdap_nested_group_process_ldap_user);
if (ret != EOK) {
goto error;
}
return EAGAIN;
}
/* Unexpected error, skip this entry */
state->member_index++;
talloc_zfree(state->member_dn);
continue;
} /* while (true) */
error:
talloc_zfree(state->member_dn);
return ret;
}
static errno_t
sdap_nested_group_check_hash(struct sdap_nested_group_ctx *state)
{
hash_key_t key;
bool has_key = false;
uint8_t *data;
do {
if (state->member_index >= state->members->num_values) {
/* No more entries to check. Return success */
return EOK;
}
data = state->members->values[state->member_index].data;
state->member_dn = talloc_strdup(state, (const char *)data);
if (!state->member_dn) {
return ENOMEM;
}
/* Check the user hash
* If it's there, we can save ourselves a trip to the
* sysdb and possibly LDAP as well
*/
key.type = HASH_KEY_STRING;
key.str = state->member_dn;
has_key = hash_has_key(state->users, &key);
if (has_key) {
talloc_zfree(state->member_dn);
state->member_index++;
continue;
}
} while (has_key);
return ENOENT;
}
static errno_t
sdap_nested_group_check_cache(TALLOC_CTX *mem_ctx,
struct sysdb_ctx *sysdb,
struct sss_domain_info *dom,
struct sdap_options *opts,
char *dn,
struct ldb_message ***_msgs,
enum sysdb_member_type *_mtype)
{
TALLOC_CTX *tmp_ctx;
errno_t ret;
struct ldb_message **msgs = NULL;
char *member_dn;
uint64_t expiration;
uid_t user_uid;
time_t now = time(NULL);
static const char *attrs[] = { SYSDB_CACHE_EXPIRE, SYSDB_UIDNUM,
SYSDB_CREATE_TIME, SYSDB_NAME,
NULL };
char *filter;
enum sysdb_member_type mtype;
size_t count;
tmp_ctx = talloc_new(NULL);
if (!tmp_ctx) return ENOMEM;
ret = sss_filter_sanitize(tmp_ctx, dn, &member_dn);
if (ret != EOK) {
goto fail;
}
/* Check for the specified origDN in the sysdb */
filter = talloc_asprintf(tmp_ctx, "(%s=%s)",
SYSDB_ORIG_DN,
member_dn);
if (!filter) {
ret = ENOMEM;
goto fail;
}
/* Try users first */
ret = sysdb_search_users(tmp_ctx, sysdb, filter, attrs, &count, &msgs);
if (ret != EOK && ret != ENOENT) {
ret = EIO;
goto fail;
} else if (ret == EOK && count > 0) {
/* We found a user with this origDN in the sysdb. Check if it is valid
*/
mtype = SYSDB_MEMBER_USER;
/* Check whether the entry is valid */
if (count != 1) {
DEBUG(1, ("More than one entry with this origDN? Skipping\n"));
ret = EIO;
goto fail;
}
user_uid = ldb_msg_find_attr_as_uint64(msgs[0], SYSDB_UIDNUM, 0);
if (!user_uid) {
DEBUG(SSSDBG_OP_FAILURE, ("User with no UID? Skipping\n"));
ret = EIO;
goto fail;
} else {
/* Regular user, check if we need a refresh */
expiration = ldb_msg_find_attr_as_uint64(msgs[0],
SYSDB_CACHE_EXPIRE,
0);
}
if (expiration && expiration > now) {
DEBUG(6, ("Cached values are still valid. Skipping\n"));
ret = EOK;
goto done;
}
/* Refresh the user from LDAP */
ret = EAGAIN;
goto done;
}
/* It wasn't a user. Check whether it's a group */
if (ret == EOK) talloc_zfree(msgs);
ret = sysdb_search_groups(tmp_ctx, sysdb, filter, attrs, &count, &msgs);
if (ret != EOK && ret != ENOENT) {
ret = EIO;
goto fail;
} else if (ret == EOK && count > 0) {
/* We found a group with this origDN in the sysdb */
mtype = SYSDB_MEMBER_GROUP;
/* Check whether the entry is valid */
if (count != 1) {
DEBUG(1, ("More than one entry with this origDN? Skipping\n"));
ret = EIO;
goto fail;
}
expiration = ldb_msg_find_attr_as_uint64(msgs[0],
SYSDB_CACHE_EXPIRE,
0);
if (expiration && expiration > now) {
DEBUG(6, ("Cached values are still valid.\n"));
ret = EOK;
goto done;
}
/* Refresh the group from LDAP */
ret = EAGAIN;
goto done;
}
/* It wasn't found in the groups either */
ret = ENOENT;
done:
if (ret == EOK || ret == EAGAIN) {
*_msgs = talloc_steal(mem_ctx, msgs);
*_mtype = mtype;
}
talloc_zfree(tmp_ctx);
return ret;
fail:
talloc_zfree(tmp_ctx);
return ret;
}
static void sdap_nested_group_process_deref(struct tevent_req *subreq);
static errno_t
sdap_nested_group_process_deref_call(struct tevent_req *req)
{
struct tevent_req *subreq;
struct sdap_attr_map_info *maps;
const char **sdap_attrs;
int ret;
int timeout;
size_t attr_count;
const int num_maps = 2;
struct sdap_nested_group_ctx *state =
tevent_req_data(req, struct sdap_nested_group_ctx);
maps = talloc_array(state, struct sdap_attr_map_info, num_maps+1);
if (!maps) return ENOMEM;
maps[0].map = state->opts->user_map;
maps[0].num_attrs = SDAP_OPTS_USER;
maps[1].map = state->opts->group_map;
maps[1].num_attrs = SDAP_OPTS_GROUP;
maps[2].map = NULL;
/* Pull down the whole group map, but only pull down username
* and originalDN for users. */
ret = build_attrs_from_map(state, state->opts->group_map, SDAP_OPTS_GROUP,
NULL, &sdap_attrs, &attr_count);
if (ret != EOK) goto fail;
sdap_attrs = talloc_realloc(NULL, sdap_attrs, const char *,
attr_count + 2);
if (!sdap_attrs) {
ret = ENOMEM;
goto fail;
}
sdap_attrs[attr_count] = \
state->opts->user_map[SDAP_AT_USER_NAME].name;
sdap_attrs[attr_count + 1] = NULL;
timeout = dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT);
subreq = sdap_deref_search_send(state, state->ev, state->opts,
state->sh, state->derefctx->orig_dn,
state->opts->group_map[SDAP_AT_GROUP_MEMBER].name,
sdap_attrs, num_maps, maps, timeout);
if (!subreq) {
ret = EIO;
goto fail;
}
talloc_steal(subreq, sdap_attrs);
talloc_steal(subreq, maps);
tevent_req_set_callback(subreq, sdap_nested_group_process_deref, req);
return EOK;
fail:
talloc_free(sdap_attrs);
talloc_free(maps);
return ret;
}
static errno_t sdap_nested_group_process_noderef(struct tevent_req *req)
{
struct sdap_nested_group_ctx *state =
tevent_req_data(req, struct sdap_nested_group_ctx);
struct sdap_deref_ctx *dctx = state->derefctx;
errno_t ret;
if (dctx->expired_users_index < dctx->expired_users_num) {
state->member_dn = dctx->expired_users[dctx->expired_users_index];
DEBUG(8, ("Refreshing expired user [%s]\n", state->member_dn));
ret = sdap_nested_group_lookup_user(
req, sdap_nested_group_process_user);
if (ret != EOK) goto done;
return EAGAIN;
}
if (dctx->expired_groups_index < dctx->expired_groups_num) {
state->member_dn = dctx->expired_groups[dctx->expired_groups_index];
DEBUG(8, ("Refreshing expired group [%s]\n", state->member_dn));
ret = sdap_nested_group_lookup_group(req);
if (ret != EOK) goto done;
return EAGAIN;
}
if (dctx->missing_dns_index < dctx->missing_dns_num) {
state->member_dn = dctx->missing_dns[dctx->missing_dns_index];
DEBUG(8, ("Looking up missing DN [%s]\n", state->member_dn));
/* Try users first for generic missing DNs */
ret = sdap_nested_group_lookup_user(
req, sdap_nested_group_process_ldap_user);
if (ret != EOK) goto done;
return EAGAIN;
}
ret = EOK;
done:
return ret;
}
static errno_t sdap_nested_group_lookup_user(struct tevent_req *req,
tevent_req_fn fn)
{
char *search_bases_filter = NULL;
struct tevent_req *subreq;
struct sdap_nested_group_ctx *state =
tevent_req_data(req, struct sdap_nested_group_ctx);
errno_t ret;
/*
* If dn is not in user search base and object may be group
* continue with group lookup. If it can't be group, skip it.
*/
if (!sss_ldap_dn_in_search_bases(state, state->member_dn,
state->opts->user_search_bases,
&search_bases_filter)) {
if (fn == sdap_nested_group_process_ldap_user) {
return sdap_nested_group_lookup_group(req);
} else if (fn == sdap_nested_group_process_user) {
if (state->derefctx) {
state->derefctx->expired_users_index++;
ret = sdap_nested_group_process_noderef(req);
} else {
state->member_index++;
talloc_zfree(state->member_dn);
ret = sdap_nested_group_process_step(req);
}
if (ret != EOK && ret != EAGAIN) {
DEBUG(SSSDBG_OP_FAILURE, ("Nested group processing failed\n"));
return ret;
} else if (ret == EOK) {
DEBUG(SSSDBG_TRACE_FUNC, ("All done.\n"));
tevent_req_done(req);
}
return EOK;
}
/*
* Something else? Continue.
*/
}
subreq = sdap_nested_get_user_send(state, state->ev, state->domain,
state->sysdb, state->opts, state->sh,
state->member_dn, search_bases_filter);
if (!subreq) {
return EIO;
}
talloc_steal(subreq, search_bases_filter);
tevent_req_set_callback(subreq, fn, req);
return EOK;
}
static void sdap_nested_group_process_group(struct tevent_req *subreq);
static errno_t sdap_nested_group_lookup_group(struct tevent_req *req)
{
errno_t ret;
const char **sdap_attrs;
char *filter;
char *search_bases_filter = NULL;
struct tevent_req *subreq;
struct sdap_nested_group_ctx *state =
tevent_req_data(req, struct sdap_nested_group_ctx);
/*
* If dn is not in group search base, skip it.
*/
if (!sss_ldap_dn_in_search_bases(state, state->member_dn,
state->opts->group_search_bases,
&search_bases_filter)) {
if (state->derefctx) {
if (state->derefctx->expired_groups_index <
state->derefctx->expired_groups_num) {
state->derefctx->expired_groups_index++;
} else {
state->derefctx->missing_dns_index++;
}
ret = sdap_nested_group_process_noderef(req);
} else {
state->member_index++;
talloc_zfree(state->member_dn);
ret = sdap_nested_group_process_step(req);
}
if (ret != EOK && ret != EAGAIN) {
DEBUG(SSSDBG_OP_FAILURE, ("Nested group processing failed\n"));
return ret;
} else if (ret == EOK) {
DEBUG(SSSDBG_TRACE_FUNC, ("All done.\n"));
tevent_req_done(req);
}
return EOK;
}
ret = build_attrs_from_map(state, state->opts->group_map, SDAP_OPTS_GROUP,
NULL, &sdap_attrs, NULL);
if (ret != EOK) {
return ret;
}
if (search_bases_filter != NULL) {
filter = talloc_asprintf(sdap_attrs, "(&%s(objectclass=%s)(%s=*))",
search_bases_filter,
state->opts->group_map[SDAP_OC_GROUP].name,
state->opts->group_map[SDAP_AT_GROUP_NAME].name);
} else {
filter = talloc_asprintf(sdap_attrs, "(&(objectclass=%s)(%s=*))",
state->opts->group_map[SDAP_OC_GROUP].name,
state->opts->group_map[SDAP_AT_GROUP_NAME].name);
}
if (!filter) {
talloc_free(sdap_attrs);
return ENOMEM;
}
subreq = sdap_get_generic_send(state, state->ev, state->opts,
state->sh, state->member_dn,
LDAP_SCOPE_BASE,
filter, sdap_attrs,
state->opts->group_map,
SDAP_OPTS_GROUP,
dp_opt_get_int(state->opts->basic,
SDAP_SEARCH_TIMEOUT),
false);
if (!subreq) {
talloc_free(sdap_attrs);
return EIO;
}
talloc_steal(subreq, sdap_attrs);
tevent_req_set_callback(subreq, sdap_nested_group_process_group, req);
return EOK;
}
static void sdap_nested_group_process_user(struct tevent_req *subreq)
{
errno_t ret;
struct tevent_req *req =
tevent_req_callback_data(subreq, struct tevent_req);
struct sdap_nested_group_ctx *state =
tevent_req_data(req, struct sdap_nested_group_ctx);
TALLOC_CTX *tmp_ctx;
size_t count = 0;
struct sysdb_attrs **replies = NULL;
int hret;
hash_key_t key;
hash_value_t value;
tmp_ctx = talloc_new(NULL);
if (!tmp_ctx) {
tevent_req_error(req, ENOMEM);
return;
}
ret = sdap_nested_get_user_recv(subreq, tmp_ctx, &count, &replies);
talloc_zfree(subreq);
if (ret != EOK && ret != ENOENT) {
tevent_req_error(req, ret);
goto done;
} else if (ret == ENOENT || count == 0) {
/* Nothing to do if the user doesn't exist */
goto skip;
}
if (count != 1) {
/* There should only ever be one reply for a
* BASE search. If otherwise, it's a serious
* error.
*/
DEBUG(1,("Received multiple replies for a BASE search!\n"));
tevent_req_error(req, EIO);
goto done;
}
/* Save the user attributes to the user hash so we can store
* them all at once later.
*/
key.type = HASH_KEY_STRING;
key.str = state->member_dn;
value.type = HASH_VALUE_PTR;
value.ptr = replies[0];
hret = hash_enter(state->users, &key, &value);
if (hret != HASH_SUCCESS) {
tevent_req_error(req, EIO);
goto done;
}
talloc_steal(state->users, replies[0]);
skip:
if (state->derefctx) {
state->derefctx->expired_users_index++;
ret = sdap_nested_group_process_noderef(req);
} else {
state->member_index++;
talloc_zfree(state->member_dn);
ret = sdap_nested_group_process_step(req);
}
if (ret == EOK) {
/* EOK means it's complete */
tevent_req_done(req);
} else if (ret != EAGAIN) {
tevent_req_error(req, ret);
}
/* EAGAIN means that we should re-enter
* the mainloop
*/
done:
talloc_free(tmp_ctx);
}
static void sdap_group_internal_nesting_done(struct tevent_req *subreq);
static void sdap_nested_group_process_group(struct tevent_req *subreq)
{
errno_t ret;
struct tevent_req *req =
tevent_req_callback_data(subreq, struct tevent_req);
struct sdap_nested_group_ctx *state =
tevent_req_data(req, struct sdap_nested_group_ctx);
TALLOC_CTX *tmp_ctx;
size_t count;
struct sysdb_attrs **replies;
tmp_ctx = talloc_new(NULL);
if (!tmp_ctx) {
tevent_req_error(req, ENOMEM);
return;
}
ret = sdap_get_generic_recv(subreq, tmp_ctx, &count, &replies);
talloc_zfree(subreq);
if (ret != EOK && ret != ENOENT) {
tevent_req_error(req, ret);
goto done;
} else if (ret == ENOENT || count == 0) {
/* Nothing to do if the group doesn't exist */
goto skip;
}
if (count != 1) {
/* There should only ever be one reply for a
* BASE search. If otherwise, it's a serious
* error.
*/
DEBUG(1,("Received multiple replies for a BASE search!\n"));
tevent_req_error(req, EIO);
goto done;
}
/* Recurse down into the member group */
subreq = sdap_nested_group_process_send(state, state->ev, state->domain,
state->sysdb, replies[0],
state->users, state->groups,
state->opts, state->sh,
state->enable_deref,
state->nesting_level + 1);
if (!subreq) {
tevent_req_error(req, EIO);
goto done;
}
tevent_req_set_callback(subreq, sdap_group_internal_nesting_done, req);
talloc_free(tmp_ctx);
return;
skip:
if (state->derefctx) {
if (state->derefctx->expired_groups_index <
state->derefctx->expired_groups_num) {
state->derefctx->expired_groups_index++;
} else {
state->derefctx->missing_dns_index++;
}
ret = sdap_nested_group_process_noderef(req);
} else {
state->member_index++;
talloc_zfree(state->member_dn);
ret = sdap_nested_group_process_step(req);
}
if (ret == EOK) {
/* EOK means it's complete */
tevent_req_done(req);
} else if (ret != EAGAIN) {
tevent_req_error(req, ret);
}
/* EAGAIN means that we should re-enter
* the mainloop
*/
done:
talloc_free(tmp_ctx);
}
static void sdap_group_internal_nesting_done(struct tevent_req *subreq)
{
errno_t ret;
struct tevent_req *req =
tevent_req_callback_data(subreq, struct tevent_req);
struct sdap_nested_group_ctx *state =
tevent_req_data(req, struct sdap_nested_group_ctx);
ret = sdap_nested_group_process_recv(subreq);
talloc_zfree(subreq);
if (ret != EOK) {
tevent_req_error(req, ret);
return;
}
if (state->derefctx) {
if (state->derefctx->expired_groups_index <
state->derefctx->expired_groups_num) {
state->derefctx->expired_groups_index++;
} else {
state->derefctx->missing_dns_index++;
}
state->derefctx->expired_users_index++;
ret = sdap_nested_group_process_noderef(req);
} else {
state->member_index++;
talloc_zfree(state->member_dn);
ret = sdap_nested_group_process_step(req);
}
if (ret == EOK) {
/* EOK means it's complete */
tevent_req_done(req);
} else if (ret != EAGAIN) {
tevent_req_error(req, ret);
}
/* EAGAIN means that we should re-enter
* the mainloop
*/
}
static void sdap_nested_group_process_ldap_user(struct tevent_req *subreq)
{
errno_t ret;
struct tevent_req *req =
tevent_req_callback_data(subreq, struct tevent_req);
struct sdap_nested_group_ctx *state =
tevent_req_data(req, struct sdap_nested_group_ctx);
TALLOC_CTX *tmp_ctx;
size_t count = 0;
struct sysdb_attrs **replies = NULL;
int hret;
hash_key_t key;
hash_value_t value;
tmp_ctx = talloc_new(NULL);
if (!tmp_ctx) {
tevent_req_error(req, ENOMEM);
return;
}
ret = sdap_nested_get_user_recv(subreq, tmp_ctx, &count, &replies);
talloc_zfree(subreq);
if (ret != EOK && ret != ENOENT) {
tevent_req_error(req, ret);
goto done;
} else if (ret == ENOENT || count == 0) {
/* No user found. Assume it's a group */
ret = sdap_nested_group_lookup_group(req);
if (ret != EOK) {
tevent_req_error(req, ret);
}
goto done;
}
if (count != 1) {
/* There should only ever be one reply for a
* BASE search. If otherwise, it's a serious
* error.
*/
DEBUG(1,("Received multiple replies for a BASE search!\n"));
tevent_req_error(req, EIO);
goto done;
}
/* Save the user attributes to the user hash so we can store
* them all at once later.
*/
key.type = HASH_KEY_STRING;
key.str = state->member_dn;
value.type = HASH_VALUE_PTR;
value.ptr = replies[0];
hret = hash_enter(state->users, &key, &value);
if (hret != HASH_SUCCESS) {
tevent_req_error(req, EIO);
goto done;
}
talloc_steal(state->users, replies[0]);
/* Move on to the next member */
if (state->derefctx) {
state->derefctx->missing_dns_index++;
ret = sdap_nested_group_process_noderef(req);
} else {
state->member_index++;
talloc_zfree(state->member_dn);
ret = sdap_nested_group_process_step(req);
}
if (ret == EOK) {
/* EOK means it's complete */
tevent_req_done(req);
} else if (ret != EAGAIN) {
tevent_req_error(req, ret);
}
/* EAGAIN means that we should re-enter
* the mainloop
*/
done:
talloc_free(tmp_ctx);
}
static errno_t
sdap_nested_group_process_deref_result(struct tevent_req *req);
static void sdap_nested_group_process_deref(struct tevent_req *subreq)
{
errno_t ret;
struct tevent_req *req =
tevent_req_callback_data(subreq, struct tevent_req);
struct sdap_nested_group_ctx *state =
tevent_req_data(req, struct sdap_nested_group_ctx);
ret = sdap_deref_search_recv(subreq, state->derefctx,
&state->derefctx->num_results,
&state->derefctx->deref_result);
talloc_zfree(subreq);
if (ret != EOK && ret != ENOENT) {
tevent_req_error(req, ret);
return;
} else if (ret == ENOENT || state->derefctx->deref_result == NULL) {
/* Nothing could be dereferenced. Done. */
tevent_req_done(req);
return;
}
state->derefctx->result_index = 0;
DEBUG(8, ("Received %d dereference results, about to process them\n",
state->derefctx->num_results));
ret = sdap_nested_group_process_deref_result(req);
if (ret == EOK) {
tevent_req_done(req);
} else if (ret != EAGAIN) {
tevent_req_error(req, ret);
}
/* EAGAIN means a recursive search is in progress */
}
static void
sdap_nested_group_process_deref_recurse_done(struct tevent_req *subreq);
static errno_t
sdap_nested_group_process_deref_result(struct tevent_req *req)
{
struct sdap_nested_group_ctx *state =
tevent_req_data(req, struct sdap_nested_group_ctx);
struct tevent_req *subreq;
hash_key_t key;
hash_value_t value;
int hret;
const char *orig_dn;
errno_t ret;
struct sdap_deref_ctx *dctx = state->derefctx;
const char *tmp_name;
size_t i;
while (dctx->result_index < dctx->num_results) {
/* Add to appropriate hash table */
ret = sysdb_attrs_get_string(
dctx->deref_result[dctx->result_index]->attrs,
SYSDB_ORIG_DN, &orig_dn);
if (ret != EOK) {
DEBUG(2, ("The entry has no originalDN\n"));
return ret;
}
/* Ensure that all members returned from the deref request are included
* in the member processing. Sometimes we will get more results back from
* deref/asq than we got from the initial lookup, as is the case with
* Active Directory and its range retrieval mechanism.
*/
for (i = 0; i < state->members->num_values; i++) {
/* FIXME: This is inefficient for very large sets of groups */
if (strcasecmp((const char *)state->members->values[i].data,
orig_dn) == 0) break;
}
if (i >= state->members->num_values) {
state->members->values = talloc_realloc(state,
state->members->values,
struct ldb_val,
state->members->num_values + 1);
if (!state->members->values) {
return ENOMEM;
}
state->members->values[state->members->num_values].data =
(uint8_t *)talloc_strdup(state->members->values, orig_dn);
if (!state->members->values[state->members->num_values].data) {
return ENOMEM;
}
state->members->values[state->members->num_values].length = strlen(orig_dn);
state->members->num_values++;
}
if (dctx->deref_result[dctx->result_index]->map == \
state->opts->user_map) {
/* check if the user is in search base */
if (!sss_ldap_dn_in_search_bases(state, orig_dn,
state->opts->user_search_bases,
NULL)) {
dctx->result_index++;
continue;
}
DEBUG(9, ("Found member user [%s]\n", orig_dn));
key.type = HASH_KEY_STRING;
key.str = talloc_strdup(state, orig_dn);
value.type = HASH_VALUE_PTR;
value.ptr = dctx->deref_result[dctx->result_index]->attrs;
hret = hash_enter(state->users, &key, &value);
if (hret != HASH_SUCCESS) return EIO;
talloc_steal(state->users,
dctx->deref_result[dctx->result_index]->attrs);
dctx->result_index++;
} else if (dctx->deref_result[dctx->result_index]->map == \
state->opts->group_map) {
ret = sysdb_attrs_get_string(dctx->deref_result[dctx->result_index]->attrs,
state->opts->group_map[SDAP_AT_GROUP_NAME].sys_name,
&tmp_name);
if (ret == ENOENT) {
DEBUG(7, ("Dereferenced a group without name, skipping ...\n"));
} else if (ret) {
return EIO;
}
/* check if the group is in search base */
if (!sss_ldap_dn_in_search_bases(state, orig_dn,
state->opts->group_search_bases,
NULL)) {
dctx->result_index++;
continue;
}
DEBUG(6, ("Recursing down a nested group\n"));
subreq = sdap_nested_group_process_send(state, state->ev,
state->domain, state->sysdb,
dctx->deref_result[dctx->result_index]->attrs,
state->users, state->groups,
state->opts, state->sh,
state->enable_deref,
state->nesting_level + 1);
if (!subreq) return EIO;
tevent_req_set_callback(subreq,
sdap_nested_group_process_deref_recurse_done,
req);
return EAGAIN;
} else {
/* This should never happen, but if it does,
* do not loop forever */
DEBUG(2, ("Entry does not match any known map, skipping\n"));
dctx->result_index++;
continue;
}
}
/* All deref results processed */
DEBUG(8, ("All dereference results processed\n"));
return EOK;
}
static void
sdap_nested_group_process_deref_recurse_done(struct tevent_req *subreq)
{
errno_t ret;
struct tevent_req *req =
tevent_req_callback_data(subreq, struct tevent_req);
struct sdap_nested_group_ctx *state =
tevent_req_data(req, struct sdap_nested_group_ctx);
ret = sdap_nested_group_process_recv(subreq);
talloc_zfree(subreq);
if (ret != EOK) {
tevent_req_error(req, ret);
return;
}
state->derefctx->result_index++;
ret = sdap_nested_group_process_deref_result(req);
if (ret == EOK) {
tevent_req_done(req);
} else if (ret != EAGAIN) {
tevent_req_error(req, ret);
}
/* EAGAIN means a recursive search is in progress */
}
static errno_t sdap_nested_group_process_recv(struct tevent_req *req)
{
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}