/*
SSSD
Async LDAP Helper routines - retrieving groups
Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009
Copyright (C) 2010, Ralf Haferkamp <rhafer@suse.de>, Novell Inc.
Copyright (C) Jan Zeleny <jzeleny@redhat.com> - 2011
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "providers/ldap/sdap_async_private.h"
#include "providers/ldap/ldap_common.h"
#include "providers/ldap/sdap_idmap.h"
#include "providers/ad/ad_common.h"
/* ==Group-Parsing Routines=============================================== */
struct sss_domain_info *domain,
const char *orig_dn,
char **_localdn,
bool *_is_group)
{
char *filter;
int ret;
char *sanitized_dn;
const char *objectclass;
if (!tmpctx) {
return ENOMEM;
}
goto done;
}
if (!filter) {
goto done;
}
if (!base_dn) {
goto done;
}
if (ret) {
goto done;
}
if (num_msgs != 1) {
goto done;
}
if (!*_localdn) {
goto done;
}
NULL);
if (objectclass == NULL) {
goto done;
}
}
done:
return ret;
}
static errno_t
struct sss_domain_info *domain,
{
char *filter;
size_t i;
char **localdn;
/* Don't search if the group is non-posix */
(unsigned long long) gid);
if (!filter) {
return ENOMEM;
}
*_ndn = 0;
return EOK;
return ret;
}
if (!localdn) {
return ENOMEM;
}
for (i=0; i < count; i++) {
if (!localdn[i]) {
return ENOMEM;
}
}
return EOK;
}
static errno_t
struct sss_domain_info *domain,
struct sdap_options *opts,
{
&gid);
/* Non-posix AD group. Skip. */
*_count = 0;
return EOK;
return ret;
}
return EOK;
}
char *member)
{
/* This is bad complexity, but the this loop should only be invoked in
* the very rare scenario of AD POSIX group that is primary group of
* some users but has user member attributes at the same time
*/
return true;
}
return false;
}
struct ldb_message_element *member_el,
char **userdns,
{
int i, j;
j = 0;
for (i=0; i < nuserdns; i++) {
"Member %s already included, skipping\n", userdns[i]);
continue;
}
j++;
}
member_el->num_values += j;
}
struct sysdb_attrs *group_attrs,
struct sss_domain_info *domain,
int num_values,
char **userdns,
{
int i, j;
int ret;
if (ret) {
goto done;
}
/* Just allocate both big enough to contain all members for now */
goto done;
}
j = el->num_values;
for (i = 0; i < num_values; i++) {
} else {
}
if (hret == HASH_ERROR_KEY_NOT_FOUND) {
member_sysdb = ctx;
member_dom = domain;
} else {
}
/* sync search entry with this as origDN */
NULL);
/* member may be outside of the configured search bases
* or out of scope of nesting limit */
continue;
}
"'sdap_find_entry_by_origDN' failed for member [%s].\n",
goto done;
}
j++;
} else if (hret != HASH_SUCCESS) {
goto done;
}
/* If the member is in ghost table, it has
* already been processed - just skip it */
}
el->num_values = j;
done:
return ret;
}
/* ==Save-Group-Entry===================================================== */
/* FIXME: support non legacy */
/* FIXME: support storing additional attributes */
static errno_t
const char *name,
struct sysdb_attrs *group_attrs,
bool posix_group,
{
/* 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) {
if (ret) {
"Could not set explicit GID 0 for %s\n", name);
return ret;
}
}
cache_timeout, now);
if (ret) {
return ret;
}
return ret;
}
static errno_t
struct sdap_options *opts,
bool populate_members,
bool store_original_member,
struct sysdb_attrs *sysdb_attrs)
{
int i;
int hret;
"Error reading ghost attributes: [%s]\n",
return ret;
}
false, &memberel);
/* Create a dummy element with no values in order for the loop to just
* fall through and make sure the attrs array is not reallocated.
*/
return ENOMEM;
}
memberel->num_values = 0;
return ret;
}
if (store_original_member) {
for (i = 0; i < memberel->num_values; i++) {
if (ret) {
return ret;
}
}
}
if (populate_members) {
"Error reading group members from group_attrs: [%s]\n",
return ret;
}
}
return ret;
}
/* Now process RFC2307bis ghost hash table */
return ENOMEM;
}
for (i = 0; i < memberel->num_values; i++) {
if (hret == HASH_ERROR_KEY_NOT_FOUND) {
continue;
} else if (hret != HASH_SUCCESS) {
"Error checking hash table: [%s]\n",
return EFAULT;
}
return ENOMEM;
}
ghostel->num_values++;
}
}
return EOK;
}
struct sdap_options *opts,
struct sss_domain_info *dom,
struct sysdb_attrs *attrs,
bool populate_members,
bool store_original_member,
char **_usn_value,
{
bool posix_group;
bool use_id_mapping;
bool need_filter;
char *sid_str;
if (!tmpctx) {
goto done;
}
if (group_attrs == NULL) {
goto done;
}
/* Always store SID string if available */
&sid_str);
sss_strerror(ret));
goto done;
}
} else {
sss_strerror(ret));
}
/* Always store UUID if available */
}
/* If this object has a SID available, we will determine the correct
* domain by its SID. */
sid_str);
if (subdomain) {
} else {
"domain\n", sid_str);
}
}
goto done;
}
posix_group = true;
&need_filter);
goto done;
}
if (need_filter) {
posix_group = false;
gid = 0;
"Error: Failed to mark group as non-posix!\n");
goto done;
}
}
if (posix_group) {
sid_str);
if (use_id_mapping) {
posix_group = true;
"unix ID to group [%s].\n", group_name);
goto done;
}
"Mapping group [%s] objectSID [%s] to unix ID\n",
/* Convert the SID into a UNIX group ID */
/* ENOTSUP is returned if built-in SID was provided
* => do not store the group, but return EOK */
goto done;
"Could not convert SID string: [%s]\n",
sss_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.
*/
if (ret) {
goto done;
}
} else {
posix_group = true;
"Error reading posix attribute: [%s]\n",
sss_strerror(ret));
goto done;
}
"Error setting posix attribute: [%s]\n",
sss_strerror(ret));
goto done;
}
&gid);
"no gid provided for [%s] in domain [%s].\n",
goto done;
}
}
}
/* check that the gid is valid for this domain */
if (posix_group) {
"Group [%s] filtered out! (id out of range)\n", group_name);
goto done;
}
/* Group ID OK */
}
"Error setting original DN: [%s]\n",
sss_strerror(ret));
goto done;
}
"original mod-Timestamp",
"Error setting mod timestamp: [%s]\n",
sss_strerror(ret));
goto done;
}
if (ret) {
"Error looking up group USN: [%s]\n",
sss_strerror(ret));
goto done;
}
if (el->num_values == 0) {
"Original USN value is not available for [%s].\n", group_name);
} else {
if (ret) {
"Error setting group USN: [%s]\n",
sss_strerror(ret));
goto done;
}
if (!usn_value) {
goto done;
}
}
goto done;
}
goto done;
}
posix_group, now);
if (ret) {
"Could not store group with GID: [%s]\n",
sss_strerror(ret));
goto done;
}
if (_usn_value) {
}
done:
if (ret) {
"Failed to save group [%s]: [%s]\n",
sss_strerror(ret));
}
return ret;
}
static errno_t
{
bool result;
return EINVAL;
}
return EINVAL;
}
return EOK;
}
static errno_t
struct sss_domain_info *dom,
const char *group_name,
const char *group_sid,
char ***_userdns,
{
bool same_domain;
size_t i, n;
return ENOMEM;
}
"get_sids_of_members failed: %d [%s]\n",
}
goto done;
}
for (i=0; i < n; i++) {
nuserdns++;
goto done;
}
}
}
done:
return ret;
}
/* ==Save-Group-Memebrs=================================================== */
/* FIXME: support non legacy */
/* FIXME: support storing additional attributes */
struct sdap_options *opts,
struct sss_domain_info *dom,
struct sysdb_attrs *attrs,
{
const char *group_sid;
const char *group_name;
int ret;
NULL};
if (dom->ignore_group_members) {
"Group members are ignored, nothing to do. If you see this " \
"message it might indicate an error in the group processing " \
"logic.\n");
return EOK;
}
/* Try harder. */
}
}
"domain, using [%s].\n", group_sid,
}
}
}
&group_name);
goto fail;
}
/* With AD we also want to merge in parent groups of primary GID as they
* are reported with tokenGroups, too
*/
"sdap_dn_by_primary_gid failed: [%d][%s].\n",
goto fail;
}
}
/* This is a temporal solution until the IPA provider is able to
* resolve external group membership.
*/
"retain_extern_members failed: %d:[%s].\n",
}
}
}
goto fail;
}
"No members for group [%s]\n", group_name);
goto fail;
}
} else {
"Adding member users to group [%s]\n", group_name);
if (!group_attrs) {
goto fail;
}
if (ret) {
"sdap_fill_memberships failed with [%d]: %s\n", ret,
goto fail;
}
}
if (ret) {
goto fail;
}
return EOK;
fail:
"Failed to save members of group %s\n", group_name);
return ret;
}
/* ==Generic-Function-to-save-multiple-groups============================= */
struct sss_domain_info *dom,
struct sdap_options *opts,
struct sysdb_attrs **groups,
int num_groups,
bool populate_members,
bool save_orig_member,
char **_usn_value)
{
char *usn_value;
bool twopass;
bool has_nesting = false;
int ret;
int i;
int nsaved_groups = 0;
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;
}
if (!tmpctx) {
return ENOMEM;
}
if (ret) {
goto done;
}
in_transaction = true;
if (twopass && !populate_members) {
if (!saved_groups) {
goto done;
}
}
for (i = 0; i < num_groups; i++) {
/* if 2 pass savemembers = false */
/* Do not fail completely on errors.
* Just report the failure to save and go on */
if (ret) {
"Failed to store group %d. Ignoring.\n", i);
} else {
if (twopass && !populate_members) {
}
}
if (usn_value) {
if (higher_usn) {
} else {
}
} else {
}
}
}
if (twopass && !populate_members) {
for (i = 0; i < nsaved_groups; i++) {
/* Do not fail completely on errors.
* Just report the failure to save and go on */
if (ret) {
"Failed to store group %d members.\n", i);
} else {
}
}
}
if (ret) {
goto done;
}
in_transaction = false;
if (_usn_value) {
}
done:
if (in_transaction) {
}
}
return ret;
}
/* ==Process-Groups======================================================= */
struct sdap_process_group_state {
char **queued_members;
int queue_len;
const char **attrs;
const char *filter;
bool enumeration;
};
struct sdap_process_group_state *state,
struct ldb_message_element *memberel);
struct ldb_message_element *memberel,
struct ldb_message_element *ghostel);
struct ldb_message_element **_dns)
{
return ENOMEM;
}
dns->num_values = 0;
return ENOMEM;
}
return EOK;
}
static struct tevent_req *
struct tevent_context *ev,
struct sss_domain_info *dom,
struct sdap_options *opts,
struct sdap_handle *sh,
struct sysdb_attrs *group,
bool enumeration)
{
const char **attrs;
char* filter;
int ret;
struct sdap_process_group_state);
if (ret) {
goto done;
}
/* FIXME: we ignore nested rfc2307bis groups for now */
if (!filter) {
return NULL;
}
grp_state->check_count = 0;
&el);
if (ret) {
goto done;
}
/* Group without members */
if (el->num_values == 0) {
goto done;
}
&ghostel);
if (ret) {
goto done;
}
if (ghostel->num_values == 0) {
/* Element was probably newly created, look for "member" again */
&el);
goto done;
}
}
goto done;
}
goto done;
}
switch (opts->schema_type) {
case SDAP_SCHEMA_RFC2307:
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
*/
break;
default:
break;
}
done:
/* We managed to process all the entries */
/* EBUSY means we need to wait for entries in LDAP */
}
}
return req;
}
static int
char *user_dn,
unsigned num_users)
{
/*
* 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->queued_members) {
"Allocating queue for %zu members\n",
if (!grp_state->queued_members) {
return ENOMEM;
}
}
} else {
false);
if (!subreq) {
return ENOMEM;
}
}
grp_state->check_count++;
return EOK;
}
static int
struct sdap_process_group_state *state,
struct ldb_message_element *memberel)
{
char *member_dn;
char *strdn;
int ret;
int i;
int nesting_level;
bool is_group;
for (i=0; i < memberel->num_values; i++) {
&strdn,
&is_group);
if (nesting_level == 0 && is_group) {
/* Ignore group members which are groups themselves. */
continue;
}
/*
* User already cached in sysdb. Remember the sysdb DN for later
* use by sdap_save_groups()
*/
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.
*/
"Searching LDAP for missing user entry\n");
"Error processing missing member #%d (%s):\n",
i, member_dn);
return ret;
}
}
} else {
"Error checking cache for member #%d (%s):\n",
return ret;
}
}
}
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.
*/
} else {
}
return ret;
}
static int
const char *username)
{
return ENOMEM;
}
sysdb_dns->num_values++;
return EOK;
}
static int
char *member_name)
{
int ret;
const char *filter;
const char *username;
const char *user_dn;
char *sanitized_name;
"Failed to sanitize the given name:'%s'.\n", member_name);
goto done;
}
/* Check for the alias in the sysdb */
if (!filter) {
goto done;
}
/* Entry exists but the group references it with an alias. */
if (count != 1) {
"More than one entry with this alias?\n");
goto done;
}
/* fill username with primary name */
"without primary name?\n");
goto done;
}
return ENOMEM;
}
}
/* The entry really does not exist, add a ghost */
}
} else {
}
done:
return ret;
}
static int
struct ldb_message_element *memberel,
struct ldb_message_element *ghostel)
{
char *member_attr_val;
char *member_name;
char *userdn;
int ret;
int i;
for (i=0; i < memberel->num_values; i++) {
/* We need to skip over zero-length usernames */
if (member_attr_val[0] == '\0') continue;
/* RFC2307 stores members as plain usernames in the member attribute.
* Internally, we use fqdns in the cache..
*/
if (member_name == NULL) {
return ENOMEM;
}
/*
* User already cached in sysdb. Remember the sysdb DN for later
* use by sdap_save_groups()
*/
"Member already cached in sysdb: %s\n", member_name);
return ENOMEM;
}
"Could not add member %s into sysdb\n", member_name);
goto done;
}
/* The user is not in sysdb, need to add it */
i, member_name);
"Error processing missing member #%d (%s):\n",
i, member_name);
goto done;
}
} else {
"Error checking cache for member #%d (%s):\n",
goto done;
}
}
done:
return ret;
}
{
int ret;
char *name_string;
state->check_count--;
if (ret) {
goto next;
}
if (count != 1) {
"Expected one user entry and got %zu\n", count);
goto next;
}
if (el->num_values == 0) {
}
if (ret) {
goto next;
}
if (name_string == NULL) {
goto next;
}
next:
if (ret) {
"Error reading group member[%d]: %s. Skipping\n",
}
/* Are there more searches for uncached users to submit ? */
false);
if (!subreq) {
return;
}
}
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.
*/
&el);
"Failed to get the group member attribute [%d]: %s\n",
return;
}
return;
}
}
}
{
return EOK;
}
/* ==Search-Groups-with-filter============================================ */
struct sdap_get_groups_state {
const char **attrs;
const char *base_filter;
char *filter;
int timeout;
bool no_members;
char *higher_usn;
};
struct tevent_context *ev,
struct sdap_domain *sdom,
struct sdap_options *opts,
struct sdap_handle *sh,
const char **attrs,
const char *filter,
int timeout,
bool no_members)
{
if (!state->search_bases) {
"Group lookup request without a search base\n");
goto done;
}
/* With AD by default the Global Catalog is used for lookup. But the GC
* group object might not have full group membership data. To make sure we
* connect to an LDAP server of the group's domain. */
goto done;
}
goto done;
}
req);
return req;
}
done:
}
return req;
}
{
int ret;
int dp_error;
return;
}
}
return;
}
{
bool need_paging = false;
int sizelimit = 0;
return ENOMEM;
}
"Searching for groups with base [%s]\n",
switch (state->lookup_type) {
case SDAP_LOOKUP_SINGLE:
break;
/* Only requests that can return multiple entries should require
* the paging control
*/
case SDAP_LOOKUP_WILDCARD:
need_paging = true;
break;
case SDAP_LOOKUP_ENUMERATE:
need_paging = true;
break;
}
if (!subreq) {
return ENOMEM;
}
return EOK;
}
struct sysdb_attrs **groups,
{
int ret;
int i;
bool next_base = false;
char **sysdb_groupnamelist;
if (ret) {
return;
}
"Search for groups, returned %zu results.\n", count);
count == 0) {
/* No users found in this search or looking up multiple entries */
next_base = true;
}
/* Add this batch of groups to the list */
if (count > 0) {
struct sysdb_attrs *,
return;
}
}
if (next_base) {
/* There are more search bases to try */
}
return;
}
}
/* No more search bases
* Return ENOENT if no groups were found
*/
return;
}
if (state->no_members) {
"sysdb_attrs_primary_name_list failed.\n");
return;
}
"Writing only group data without members was successful.\n");
} else {
}
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 (!subreq) {
return;
}
return;
}
}
/* We have all of the groups. Save them to the sysdb */
/* 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 (!subreq) {
return;
}
req);
return;
}
return;
}
"to allow unrolling of nested groups.\n");
if (ret) {
return;
}
}
if (!subreq) {
return;
}
}
}
struct sysdb_attrs **groups,
{
bool filter;
/* Always copy all objects for wildcard lookups. */
}
{
int ret;
if (ret) {
}
return;
}
state->check_count--;
if (state->check_count == 0) {
/* If ignore_group_members is set for the domain, don't update
* group memberships in the cache.
*
* If enumeration is on, don't overwrite orig_members as they've been
* saved earlier.
*/
&state->higher_usn);
if (ret) {
return;
}
} else {
}
}
}
struct sss_domain_info *domain,
struct sdap_options *opts,
struct sysdb_attrs **users,
int num_users,
hash_table_t **_ghosts);
{
struct sdap_get_groups_state);
size_t i;
"Could not retrieve members using AD match rule. [%s]\n",
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.
*/
goto done;
}
member_el->num_values = 0;
if (!tmp_ctx) {
goto done;
}
/* Figure out which users are already cached in the sysdb and
* which ones need to be added as ghost users.
*/
&ghosts);
"Could not determine which users are ghosts: [%s]\n",
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
*/
goto done;
}
/* Copy the origDN values of the users into the member element */
for (i = 0; i < count; i++) {
&orig_dn_el);
/* This should never happen. Every entry should have
* an originalDN.
*/
"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.
*/
}
/* Now save the group, users and ghosts to the cache */
"Could not save group to the cache: [%s]\n",
goto done;
}
done:
} else {
}
}
{
struct sdap_get_groups_state);
if (usn_value) {
}
return EOK;
}
{
unsigned long user_count;
unsigned long group_count;
bool in_transaction = false;
struct tevent_req);
struct sdap_get_groups_state);
&group_count, &groups,
goto fail;
}
/* Save all of the users first so that they are in
* place for the groups to add them.
*/
goto fail;
}
in_transaction = true;
goto fail;
}
&state->higher_usn);
goto fail;
}
goto fail;
}
in_transaction = false;
/* No external members. Processing complete */
return;
}
/* At the moment, we need to save the direct groups & members in one
* transaction and then query the others in a separate requests
*/
goto fail;
}
return;
fail:
if (in_transaction) {
}
}
}
{
struct tevent_req);
struct sdap_get_groups_state);
"Cannot resolve external members [%d]: %s\n",
return;
}
return;
}
struct sss_domain_info *domain,
struct sdap_options *opts,
struct sysdb_attrs **users,
int num_users,
{
int i;
const char *username;
char *clean_orig_dn;
const char *original_dn;
char *filter;
const char *sysdb_name;
bool in_transaction = false;
return EINVAL;
}
if (num_users == 0) {
/* Nothing to do if there are no users */
return EOK;
}
if (ret != HASH_SUCCESS) {
goto done;
}
if (ret) {
goto done;
}
in_transaction = true;
for (i = 0; i < num_users; i++) {
if (el->num_values == 0) {
}
"User entry %d has no originalDN attribute\n", i);
goto done;
}
"Cannot sanitize originalDN [%s]\n", original_dn);
goto done;
}
"User entry %d has no name attribute. Skipping\n", i);
continue;
}
/* Check for the specified origDN in the sysdb */
if (!filter) {
goto done;
}
goto done;
/* The entry is cached but expired. Update the username
* if needed. */
if (count != 1) {
"More than one entry with this origDN? Skipping\n");
continue;
}
/* Username is correct, continue */
continue;
}
if (!attrs) {
goto done;
}
} else {
/* Already qualified from sdap_get_user_primary_name() */
if (ret != HASH_SUCCESS) {
goto done;
}
}
}
if (ret) {
goto done;
}
in_transaction = false;
done:
if (in_transaction) {
}
}
} else {
}
return ret;
}