summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/Makefile.am14
-rw-r--r--src/db/sysdb.c97
-rw-r--r--src/db/sysdb.h18
-rw-r--r--src/db/sysdb_ops.c191
-rw-r--r--src/providers/ldap/sdap_async_accounts.c212
-rw-r--r--src/tests/sysdb-tests.c199
-rw-r--r--src/tests/util-tests.c227
-rw-r--r--src/util/util.c227
-rw-r--r--src/util/util.h19
9 files changed, 1187 insertions, 17 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 8eea7ac2d..847e97ad3 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -82,7 +82,8 @@ if HAVE_CHECK
find_uid-tests \
auth-tests \
ipa_ldap_opt-tests \
- simple_access-tests
+ simple_access-tests \
+ util-tests
endif
check_PROGRAMS = \
@@ -678,6 +679,17 @@ simple_access_tests_LDADD = \
$(SSSD_LIBS) \
$(CHECK_LIBS)
+util_tests_SOURCES = \
+ tests/util-tests.c \
+ $(SSSD_UTIL_OBJ)
+util_tests_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(CHECK_CFLAGS)
+util_tests_LDADD = \
+ $(SSSD_LIBS) \
+ $(CHECK_LIBS) \
+ libsss_test_common.la
+
endif
stress_tests_SOURCES = \
diff --git a/src/db/sysdb.c b/src/db/sysdb.c
index 41e1756c9..f6996c431 100644
--- a/src/db/sysdb.c
+++ b/src/db/sysdb.c
@@ -52,6 +52,28 @@ struct ldb_dn *sysdb_group_dn(struct sysdb_ctx *ctx, void *memctx,
return ldb_dn_new_fmt(memctx, ctx->ldb, SYSDB_TMPL_GROUP, name, domain);
}
+errno_t sysdb_group_dn_name(struct sysdb_ctx *ctx, void *memctx,
+ const char *_dn, char **_name)
+{
+ struct ldb_dn *dn;
+ *_name = NULL;
+
+ dn = ldb_dn_new_fmt(memctx, ctx->ldb, "%s", _dn);
+ if (dn == NULL) {
+ return ENOMEM;
+ }
+
+ *_name = talloc_strdup(memctx, ldb_dn_get_rdn_name(dn));
+ if (!_name) {
+ talloc_zfree(dn);
+ return ENOMEM;
+ }
+
+ talloc_zfree(dn);
+
+ return EOK;
+}
+
struct ldb_dn *sysdb_domain_dn(struct sysdb_ctx *ctx, void *memctx,
const char *domain)
{
@@ -1924,3 +1946,78 @@ int sysdb_attrs_replace_name(struct sysdb_attrs *attrs, const char *oldname,
return EOK;
}
+
+/* Search for all incidences of attr_name in a list of
+ * sysdb_attrs and add their value to a list
+ *
+ * TODO: Currently only works for single-valued
+ * attributes. Multi-valued attributes will return
+ * only the first entry
+ */
+errno_t sysdb_attrs_to_list(TALLOC_CTX *memctx,
+ struct sysdb_attrs **attrs,
+ int attr_count,
+ const char *attr_name,
+ char ***_list)
+{
+ int attr_idx;
+ int i;
+ char **list;
+ char **tmp_list;
+ int list_idx;
+
+ *_list = NULL;
+
+ /* Assume that every attrs entry contains the attr_name
+ * This may waste a little memory if some entries don't
+ * have the attribute, but it will save us the trouble
+ * of continuously resizing the array.
+ */
+ list = talloc_array(memctx, char *, attr_count+1);
+ if (!list) {
+ return ENOMEM;
+ }
+
+ list_idx = 0;
+ /* Loop through all entries in attrs */
+ for (attr_idx = 0; attr_idx < attr_count; attr_idx++) {
+ /* Examine each attribute within the entry */
+ for (i = 0; i < attrs[attr_idx]->num; i++) {
+ if (strcasecmp(attrs[attr_idx]->a->name, attr_name) == 0) {
+ /* Attribute name matches the requested name
+ * Copy it to the output list
+ */
+ list[list_idx] = talloc_strdup(
+ list,
+ (const char *)attrs[attr_idx]->a->values[0].data);
+ if (!list[list_idx]) {
+ talloc_free(list);
+ return ENOMEM;
+ }
+ list_idx++;
+
+ /* We only support single-valued attributes
+ * Break here and go on to the next entry
+ */
+ break;
+ }
+ }
+ }
+
+ list[list_idx] = NULL;
+
+ /* if list_idx < attr_count, do a realloc to
+ * reclaim unused memory
+ */
+ if (list_idx < attr_count) {
+ tmp_list = talloc_realloc(memctx, list, char *, list_idx+1);
+ if (!tmp_list) {
+ talloc_zfree(list);
+ return ENOMEM;
+ }
+ list = tmp_list;
+ }
+
+ *_list = list;
+ return EOK;
+}
diff --git a/src/db/sysdb.h b/src/db/sysdb.h
index 50427d613..04ce49e66 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -200,6 +200,8 @@ struct ldb_dn *sysdb_user_dn(struct sysdb_ctx *ctx, void *memctx,
const char *domain, const char *name);
struct ldb_dn *sysdb_group_dn(struct sysdb_ctx *ctx, void *memctx,
const char *domain, const char *name);
+errno_t sysdb_group_dn_name(struct sysdb_ctx *ctx, void *memctx,
+ const char *dn_str, char **name);
struct ldb_dn *sysdb_domain_dn(struct sysdb_ctx *ctx, void *memctx,
const char *domain);
struct ldb_dn *sysdb_custom_dn(struct sysdb_ctx *ctx, void *memctx,
@@ -535,6 +537,16 @@ struct tevent_req *sysdb_remove_group_member_send(TALLOC_CTX *mem_ctx,
const char *member);
int sysdb_remove_group_member_recv(struct tevent_req *req);
+
+struct tevent_req * sysdb_update_members_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ char *user,
+ char **add_groups,
+ char **del_groups);
+errno_t sysdb_update_members_recv(struct tevent_req *req);
+
/* Password caching function.
* If you are in a transaction ignore sysdb and pass in the handle.
* If you are not in a transaction pass NULL in handle and provide sysdb,
@@ -652,4 +664,10 @@ struct tevent_req *sysdb_delete_group_send(TALLOC_CTX *mem_ctx,
const char *name, gid_t gid);
int sysdb_delete_group_recv(struct tevent_req *req);
+errno_t sysdb_attrs_to_list(TALLOC_CTX *memctx,
+ struct sysdb_attrs **attrs,
+ int attr_count,
+ const char *attr_name,
+ char ***_list);
+
#endif /* __SYS_DB_H__ */
diff --git a/src/db/sysdb_ops.c b/src/db/sysdb_ops.c
index 6fcc95b19..52102706c 100644
--- a/src/db/sysdb_ops.c
+++ b/src/db/sysdb_ops.c
@@ -5065,3 +5065,194 @@ int sysdb_cache_auth_recv(struct tevent_req *req, time_t *expire_date,
return (state->authentication_successful ? EOK : EINVAL);
}
+
+struct sysdb_update_members_ctx {
+ char *user;
+ struct sss_domain_info *domain;
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+
+ char **add_groups;
+ int add_group_iter;
+
+ char **del_groups;
+ int del_group_iter;
+};
+
+static char **empty_string_list(TALLOC_CTX *mem_ctx)
+{
+ char **empty;
+ empty = talloc_array(mem_ctx, char *, 1);
+ if (!empty) {
+ return NULL;
+ }
+
+ empty[0] = NULL;
+
+ return empty;
+}
+
+static errno_t
+sysdb_update_members_step(struct tevent_req *req);
+
+struct tevent_req *sysdb_update_members_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ char *user,
+ char **add_groups,
+ char **del_groups)
+{
+ errno_t ret;
+ struct tevent_req *req;
+ struct sysdb_update_members_ctx *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_update_members_ctx);
+ if (!req) {
+ return NULL;
+ }
+
+ state->user = talloc_strdup(state, user);
+ if (!state->user) {
+ goto error;
+ }
+
+ state->domain = domain;
+ state->ev = ev;
+ state->handle = handle;
+
+ if (add_groups) {
+ state->add_groups = dup_string_list(state, (const char**)add_groups);
+ }
+ else {
+ state->add_groups = empty_string_list(state);
+ }
+ if (!state->add_groups) {
+ goto error;
+ }
+ state->add_group_iter = 0;
+
+ if (del_groups) {
+ state->del_groups = dup_string_list(state, (const char **)del_groups);
+ }
+ else {
+ state->del_groups = empty_string_list(state);
+ }
+ if (!state->del_groups) {
+ goto error;
+ }
+ state->del_group_iter = 0;
+
+ ret = sysdb_update_members_step(req);
+ if (ret != EOK) {
+ /* Nothing to do. Finish up */
+ tevent_req_error(req, ret);
+ tevent_req_post(req, state->ev);
+ }
+
+ return req;
+
+error:
+ talloc_free(req);
+ return NULL;
+}
+
+static void
+sysdb_update_members_add_done(struct tevent_req *subreq);
+static void
+sysdb_update_members_del_done(struct tevent_req *subreq);
+
+static errno_t
+sysdb_update_members_step(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct sysdb_update_members_ctx *state;
+
+ state = tevent_req_data(req, struct sysdb_update_members_ctx);
+
+ if (state->add_groups[state->add_group_iter]) {
+ subreq = sysdb_add_group_member_send(
+ state, state->ev, state->handle,
+ state->domain,
+ state->add_groups[state->add_group_iter],
+ state->user);
+ if (!subreq) {
+ return EIO;
+ }
+
+ tevent_req_set_callback(subreq, sysdb_update_members_add_done, req);
+ return EOK;
+ }
+
+ if (state->del_groups[state->del_group_iter]) {
+ subreq = sysdb_remove_group_member_send(
+ state, state->ev,
+ state->handle, state->domain,
+ state->del_groups[state->del_group_iter],
+ state->user);
+ if (!subreq) {
+ return EIO;
+ }
+
+ tevent_req_set_callback(subreq, sysdb_update_members_del_done, req);
+ return EOK;
+ }
+
+ /* No more members to handle */
+ tevent_req_done(req);
+ return EOK;
+}
+
+static void
+sysdb_update_members_add_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct sysdb_update_members_ctx *state =
+ tevent_req_data(req, struct sysdb_update_members_ctx);
+
+ ret = sysdb_add_group_member_recv(subreq);
+ talloc_zfree(subreq);
+ if(ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->add_group_iter++;
+ ret = sysdb_update_members_step(req);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+}
+
+static void
+sysdb_update_members_del_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct sysdb_update_members_ctx *state =
+ tevent_req_data(req, struct sysdb_update_members_ctx);
+
+ ret = sysdb_remove_group_member_recv(subreq);
+ talloc_zfree(subreq);
+ if(ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->del_group_iter++;
+ ret = sysdb_update_members_step(req);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+}
+
+errno_t
+sysdb_update_members_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
diff --git a/src/providers/ldap/sdap_async_accounts.c b/src/providers/ldap/sdap_async_accounts.c
index 384936db4..beab326a0 100644
--- a/src/providers/ldap/sdap_async_accounts.c
+++ b/src/providers/ldap/sdap_async_accounts.c
@@ -1485,12 +1485,17 @@ struct sdap_initgr_rfc2307_state {
struct sdap_options *opts;
struct sss_domain_info *dom;
struct sdap_handle *sh;
+ char *name;
+
+ struct sysdb_handle *handle;
+ char **ldap_grouplist;
struct sdap_op *op;
};
static void sdap_initgr_rfc2307_process(struct tevent_req *subreq);
-static void sdap_initgr_rfc2307_done(struct tevent_req *subreq);
+
+static
struct tevent_req *sdap_initgr_rfc2307_send(TALLOC_CTX *memctx,
struct tevent_context *ev,
struct sdap_options *opts,
@@ -1498,12 +1503,12 @@ struct tevent_req *sdap_initgr_rfc2307_send(TALLOC_CTX *memctx,
struct sss_domain_info *dom,
struct sdap_handle *sh,
const char *base_dn,
- const char *name,
- const char **grp_attrs)
+ const char *name)
{
struct tevent_req *req, *subreq;
struct sdap_initgr_rfc2307_state *state;
const char *filter;
+ const char *attrs[2];
req = tevent_req_create(memctx, &state, struct sdap_initgr_rfc2307_state);
if (!req) return NULL;
@@ -1514,6 +1519,18 @@ struct tevent_req *sdap_initgr_rfc2307_send(TALLOC_CTX *memctx,
state->dom = dom;
state->sh = sh;
state->op = NULL;
+ state->name = talloc_strdup(state, name);
+ if (!state->name) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ attrs[0] = talloc_strdup(state, opts->group_map[SDAP_AT_GROUP_NAME].name);
+ if (!attrs[0]) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ attrs[1] = NULL;
filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))",
opts->group_map[SDAP_AT_GROUP_MEMBER].name,
@@ -1525,7 +1542,7 @@ struct tevent_req *sdap_initgr_rfc2307_send(TALLOC_CTX *memctx,
subreq = sdap_get_generic_send(state, state->ev, state->opts,
state->sh, base_dn, LDAP_SCOPE_SUBTREE,
- filter, grp_attrs,
+ filter, attrs,
state->opts->group_map, SDAP_OPTS_GROUP);
if (!subreq) {
talloc_zfree(req);
@@ -1536,6 +1553,7 @@ struct tevent_req *sdap_initgr_rfc2307_send(TALLOC_CTX *memctx,
return req;
}
+static void sdap_initgr_rfc2307_get_sysdb_groups(struct tevent_req *subreq);
static void sdap_initgr_rfc2307_process(struct tevent_req *subreq)
{
struct tevent_req *req;
@@ -1555,34 +1573,196 @@ static void sdap_initgr_rfc2307_process(struct tevent_req *subreq)
}
if (count == 0) {
- tevent_req_done(req);
- return;
+ /* No groups for this user in LDAP
+ * We need to ensure that there are no groups
+ * in the sysdb either.
+ */
+
+ state->ldap_grouplist = NULL;
+ }
+ else {
+ ret = sysdb_attrs_to_list(state, groups, count,
+ state->opts->group_map[SDAP_AT_GROUP_NAME].name,
+ &state->ldap_grouplist);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
}
+ /* Start a transaction to look up the groups in the sysdb
+ * and update them with LDAP data
+ */
- subreq = sdap_save_groups_send(state, state->ev, state->dom,
- state->sysdb, state->opts,
- groups, count);
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
if (!subreq) {
- tevent_req_error(req, ENOMEM);
+ tevent_req_error(req, EIO);
return;
}
- tevent_req_set_callback(subreq, sdap_initgr_rfc2307_done, req);
+ tevent_req_set_callback(subreq,
+ sdap_initgr_rfc2307_get_sysdb_groups,
+ req);
}
-static void sdap_initgr_rfc2307_done(struct tevent_req *subreq)
+static void sdap_initgr_rfc2307_update_sysdb_groups(struct tevent_req *subreq);
+static void sdap_initgr_rfc2307_get_sysdb_groups(struct tevent_req *subreq)
{
- struct tevent_req *req;
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct sdap_initgr_rfc2307_state *state =
+ tevent_req_data(req, struct sdap_initgr_rfc2307_state);
int ret;
+ const char **attrs;
- req = tevent_req_callback_data(subreq, struct tevent_req);
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
- ret = sdap_save_groups_recv(subreq, NULL, NULL);
+ attrs = talloc_array(state, const char *, 2);
+ if (!attrs) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ attrs[0] = SYSDB_MEMBEROF;
+ attrs[1] = NULL;
+
+ /* Search for all groups for which this user is a member */
+ subreq = sysdb_search_user_by_name_send(state, state->ev, state->sysdb,
+ state->handle, state->dom,
+ state->name, attrs);
+ if (!subreq) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ tevent_req_set_callback(subreq,
+ sdap_initgr_rfc2307_update_sysdb_groups,
+ req);
+}
+
+static void
+sdap_initgr_rfc2307_update_sysdb_groups_done(struct tevent_req *subreq);
+static void sdap_initgr_rfc2307_update_sysdb_groups(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct sdap_initgr_rfc2307_state *state =
+ tevent_req_data(req, struct sdap_initgr_rfc2307_state);
+ int ret, i;
+ struct ldb_message *reply;
+ struct ldb_message_element *groups;
+ char **sysdb_grouplist;
+ char **add_groups;
+ char **del_groups;
+
+ ret = sysdb_search_user_recv(subreq, state, &reply);
talloc_zfree(subreq);
if (ret) {
tevent_req_error(req, ret);
return;
}
+ groups = ldb_msg_find_element(reply, SYSDB_MEMBEROF);
+ if (!groups || groups->num_values == 0) {
+ DEBUG(6, ("User is not a member of any groups\n"));
+
+ tevent_req_done(req);
+ return;
+ }
+
+ sysdb_grouplist = talloc_array(state, char *,
+ groups->num_values+1);
+ if (!sysdb_grouplist) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ /* Get a list of the groups by groupname only */
+ for (i=0; i < groups->num_values; i++) {
+ ret = sysdb_group_dn_name(state->sysdb,
+ sysdb_grouplist,
+ (const char *)groups->values[i].data,
+ &sysdb_grouplist[i]);
+ if (ret != EOK) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ }
+
+ sysdb_grouplist[groups->num_values] = NULL;
+
+ /* Find the differences between the sysdb and ldap lists
+ * Groups in ldap only must be added to the sysdb;
+ * groups in the sysdb only must be removed.
+ */
+ ret = diff_string_lists(state,
+ state->ldap_grouplist, sysdb_grouplist,
+ &add_groups, &del_groups, NULL);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_update_members_send(state, state->ev, state->handle,
+ state->dom, state->name,
+ add_groups, del_groups);
+ if (!subreq) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ tevent_req_set_callback(subreq,
+ sdap_initgr_rfc2307_update_sysdb_groups_done,
+ req);
+}
+
+static void
+sdap_initgr_rfc2307_transaction_done(struct tevent_req *subreq);
+static void
+sdap_initgr_rfc2307_update_sysdb_groups_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct sdap_initgr_rfc2307_state *state =
+ tevent_req_data(req, struct sdap_initgr_rfc2307_state);
+
+ ret = sysdb_update_members_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* Commit the transaction */
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ tevent_req_set_callback(subreq,
+ sdap_initgr_rfc2307_transaction_done,
+ req);
+}
+
+static void
+sdap_initgr_rfc2307_transaction_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+
+ ret = sysdb_transaction_commit_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* Processing completed. Return control to sdap_get_initgr_done() */
tevent_req_done(req);
}
@@ -1988,7 +2168,7 @@ static void sdap_get_initgr_process(struct tevent_req *subreq)
state->sysdb, state->dom, state->sh,
dp_opt_get_string(state->opts->basic,
SDAP_GROUP_SEARCH_BASE),
- state->name, state->grp_attrs);
+ state->name);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
diff --git a/src/tests/sysdb-tests.c b/src/tests/sysdb-tests.c
index 8557be5ad..5bdc00547 100644
--- a/src/tests/sysdb-tests.c
+++ b/src/tests/sysdb-tests.c
@@ -3124,6 +3124,200 @@ START_TEST (test_sysdb_memberof_check_memberuid_loop_without_group_5)
}
END_TEST
+START_TEST (test_sysdb_attrs_to_list)
+{
+ struct sysdb_attrs *attrs_list[3];
+ char **list;
+ errno_t ret;
+
+ TALLOC_CTX *test_ctx = talloc_new(NULL);
+
+ attrs_list[0] = sysdb_new_attrs(test_ctx);
+ sysdb_attrs_add_string(attrs_list[0], "test_attr", "attr1");
+ attrs_list[1] = sysdb_new_attrs(test_ctx);
+ sysdb_attrs_add_string(attrs_list[1], "test_attr", "attr2");
+ attrs_list[2] = sysdb_new_attrs(test_ctx);
+ sysdb_attrs_add_string(attrs_list[2], "nottest_attr", "attr3");
+
+ ret = sysdb_attrs_to_list(test_ctx, attrs_list, 3,
+ "test_attr", &list);
+ fail_unless(ret == EOK, "sysdb_attrs_to_list failed with code %d", ret);
+
+ fail_unless(strcmp(list[0],"attr1") == 0, "Expected [attr1], got [%s]",
+ list[0]);
+ fail_unless(strcmp(list[1],"attr2") == 0, "Expected [attr2], got [%s]",
+ list[1]);
+ fail_unless(list[2] == NULL, "List should be NULL-terminated");
+
+ talloc_free(test_ctx);
+}
+END_TEST
+
+static void test_sysdb_update_members_add(struct tevent_req *req);
+START_TEST (test_sysdb_update_members)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+
+ /* Start the transaction */
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_sysdb_update_members_add, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not test sysdb_update_members");
+ talloc_free(test_ctx);
+}
+END_TEST
+
+static void test_sysdb_update_members_add_del(struct tevent_req *req);
+static void test_sysdb_update_members_add(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ char **add_groups;
+ char *user;
+ errno_t ret;
+
+ ret = sysdb_transaction_recv(req, data, &data->handle);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not start transaction\n"));
+ test_return(data, ret);
+ return;
+ }
+
+ /* Add a user to two groups */
+ data->username = talloc_strdup(data, "testuser27000");
+ user = talloc_strdup(data, data->username);
+ add_groups = talloc_array(data, char *, 3);
+ add_groups[0] = talloc_strdup(data, "testgroup28001");
+ add_groups[1] = talloc_strdup(data, "testgroup28002");
+ add_groups[2] = NULL;
+
+ req = sysdb_update_members_send(data, data->ev, data->handle,
+ data->ctx->domain, user,
+ add_groups, NULL);
+ talloc_free(add_groups);
+ talloc_free(user);
+ if (!req) {
+ DEBUG(0, ("Could not add groups\n"));
+ test_return(data, EIO);
+ return;
+ }
+
+ tevent_req_set_callback(req, test_sysdb_update_members_add_del, data);
+}
+
+static void test_sysdb_update_members_del(struct tevent_req *req);
+static void test_sysdb_update_members_add_del(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ errno_t ret;
+ char **add_groups = NULL;
+ char **del_groups = NULL;
+ char *user;
+
+ ret = sysdb_update_members_recv(req);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(0, ("Group addition failed [%d](%s)\n", ret, strerror(ret)));
+ test_return(data, ret);
+ return;
+ }
+
+ /* Remove a user from one group and add to another */
+ user = talloc_strdup(data, data->username);
+ del_groups = talloc_array(data, char *, 2);
+ del_groups[0] = talloc_strdup(del_groups, "testgroup28001");
+ del_groups[1] = NULL;
+ add_groups = talloc_array(data, char *, 2);
+ add_groups[0] = talloc_strdup(add_groups, "testgroup28003");
+ add_groups[1] = NULL;
+
+ req = sysdb_update_members_send(data, data->ev, data->handle,
+ data->ctx->domain, user,
+ add_groups, del_groups);
+ talloc_free(add_groups);
+ talloc_free(del_groups);
+ talloc_free(user);
+ if (!req) {
+ DEBUG(0, ("Could not add/del groups\n"));
+ test_return(data, EIO);
+ return;
+ }
+
+ tevent_req_set_callback(req, test_sysdb_update_members_del, data);
+}
+
+static void test_sysdb_update_members_done(struct tevent_req *req);
+static void test_sysdb_update_members_del(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ errno_t ret;
+ char **del_groups = NULL;
+ char *user;
+
+ ret = sysdb_update_members_recv(req);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(0, ("Group replace failed [%d](%s)\n", ret, strerror(ret)));
+ test_return(data, EIO);
+ return;
+ }
+
+ /* Remove a user from one group and add to another */
+ user = talloc_strdup(data, data->username);
+ del_groups = talloc_array(data, char *, 3);
+ del_groups[0] = talloc_strdup(del_groups, "testgroup28002");
+ del_groups[1] = talloc_strdup(del_groups, "testgroup28003");
+ del_groups[2] = NULL;
+
+ req = sysdb_update_members_send(data, data->ev, data->handle,
+ data->ctx->domain, user,
+ NULL, del_groups);
+ talloc_free(del_groups);
+ talloc_free(user);
+ if (!req) {
+ DEBUG(0, ("Could not del groups\n"));
+ test_return(data, EIO);
+ return;
+ }
+
+ tevent_req_set_callback(req, test_sysdb_update_members_done, data);
+}
+
+static void test_sysdb_update_members_done(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ errno_t ret;
+
+ ret = sysdb_update_members_recv(req);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(0, ("Group delete failed [%d](%s)\n", ret, strerror(ret)));
+ }
+ test_return(data, ret);
+}
+
Suite *create_sysdb_suite(void)
{
Suite *s = suite_create("sysdb");
@@ -3148,6 +3342,9 @@ Suite *create_sysdb_suite(void)
/* test the change */
tcase_add_loop_test(tc_sysdb, test_sysdb_get_user_attr, 27000, 27010);
+ /* Add and remove users in a group with sysdb_update_members */
+ tcase_add_test(tc_sysdb, test_sysdb_update_members);
+
/* Remove the other half by gid */
tcase_add_loop_test(tc_sysdb, test_sysdb_remove_local_group_by_gid, 28000, 28010);
@@ -3233,6 +3430,8 @@ Suite *create_sysdb_suite(void)
tcase_add_test(tc_sysdb, test_sysdb_attrs_replace_name);
+ tcase_add_test(tc_sysdb, test_sysdb_attrs_to_list);
+
/* Add all test cases to the test suite */
suite_add_tcase(s, tc_sysdb);
diff --git a/src/tests/util-tests.c b/src/tests/util-tests.c
new file mode 100644
index 000000000..d8d3800fd
--- /dev/null
+++ b/src/tests/util-tests.c
@@ -0,0 +1,227 @@
+/*
+ SSSD
+
+ util-tests.c
+
+ Authors:
+ Stephen Gallagher <sgallagh@redhat.com>
+
+ Copyright (C) 2010 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; 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 <popt.h>
+#include <talloc.h>
+#include <check.h>
+#include "util/util.h"
+#include "tests/common.h"
+
+START_TEST(test_diff_string_lists)
+{
+ TALLOC_CTX *test_ctx;
+ char **l1;
+ char **l2;
+ char **l3;
+ char **only_l1;
+ char **only_l2;
+ char **both;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+
+ /* Test with all values returned */
+ l1 = talloc_array(test_ctx, char *, 4);
+ l1[0] = talloc_strdup(l1, "a");
+ l1[1] = talloc_strdup(l1, "b");
+ l1[2] = talloc_strdup(l1, "c");
+ l1[3] = NULL;
+
+ l2 = talloc_array(test_ctx, char *, 4);
+ l2[0] = talloc_strdup(l1, "d");
+ l2[1] = talloc_strdup(l1, "c");
+ l2[2] = talloc_strdup(l1, "b");
+ l2[3] = NULL;
+
+ ret = diff_string_lists(test_ctx,
+ l1, l2,
+ &only_l1, &only_l2, &both);
+
+ fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
+ fail_unless(strcmp(only_l1[0], "a") == 0, "Missing \"a\" from only_l1");
+ fail_unless(only_l1[1] == NULL, "only_l1 not NULL-terminated");
+ fail_unless(strcmp(only_l2[0], "d") == 0, "Missing \"d\" from only_l2");
+ fail_unless(only_l2[1] == NULL, "only_l2 not NULL-terminated");
+ fail_unless(strcmp(both[0], "c") == 0, "Missing \"c\" from both");
+ fail_unless(strcmp(both[1], "b") == 0, "Missing \"b\" from both");
+ fail_unless(both[2] == NULL, "both not NULL-terminated");
+
+ talloc_zfree(only_l1);
+ talloc_zfree(only_l2);
+ talloc_zfree(both);
+
+ /* Test with restricted return values */
+ ret = diff_string_lists(test_ctx,
+ l1, l2,
+ &only_l1, &only_l2, NULL);
+
+ fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
+ fail_unless(strcmp(only_l1[0], "a") == 0, "Missing \"a\" from only_l1");
+ fail_unless(only_l1[1] == NULL, "only_l1 not NULL-terminated");
+ fail_unless(strcmp(only_l2[0], "d") == 0, "Missing \"d\" from only_l2");
+ fail_unless(only_l2[1] == NULL, "only_l2 not NULL-terminated");
+ fail_unless(both == NULL, "Nothing returned to both");
+
+ talloc_zfree(only_l1);
+ talloc_zfree(only_l2);
+ talloc_zfree(both);
+
+ ret = diff_string_lists(test_ctx,
+ l1, l2,
+ &only_l1, NULL, NULL);
+
+ fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
+ fail_unless(strcmp(only_l1[0], "a") == 0, "Missing \"a\" from only_l1");
+ fail_unless(only_l1[1] == NULL, "only_l1 not NULL-terminated");
+ fail_unless(only_l2 == NULL, "Nothing returned to only_l2");
+ fail_unless(both == NULL, "Nothing returned to both");
+
+ talloc_zfree(only_l1);
+ talloc_zfree(only_l2);
+ talloc_zfree(both);
+
+ ret = diff_string_lists(test_ctx,
+ l1, l2,
+ NULL, &only_l2, NULL);
+
+ fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
+ fail_unless(strcmp(only_l2[0], "d") == 0, "Missing \"d\" from only_l2");
+ fail_unless(only_l2[1] == NULL, "only_l2 not NULL-terminated");
+ fail_unless(only_l1 == NULL, "Nothing returned to only_l1");
+ fail_unless(both == NULL, "Nothing returned to both");
+
+ talloc_zfree(only_l1);
+ talloc_zfree(only_l2);
+ talloc_zfree(both);
+
+ /* Test with no overlap */
+ l3 = talloc_array(test_ctx, char *, 4);
+ l3[0] = talloc_strdup(l1, "d");
+ l3[1] = talloc_strdup(l1, "e");
+ l3[2] = talloc_strdup(l1, "f");
+ l3[3] = NULL;
+
+ ret = diff_string_lists(test_ctx,
+ l1, l3,
+ &only_l1, &only_l2, &both);
+
+ fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
+ fail_unless(strcmp(only_l1[0], "a") == 0, "Missing \"a\" from only_l1");
+ fail_unless(strcmp(only_l1[1], "b") == 0, "Missing \"b\" from only_l1");
+ fail_unless(strcmp(only_l1[2], "c") == 0, "Missing \"c\" from only_l1");
+ fail_unless(only_l1[3] == NULL, "only_l1 not NULL-terminated");
+ fail_unless(strcmp(only_l2[0], "d") == 0, "Missing \"f\" from only_l2");
+ fail_unless(strcmp(only_l2[1], "e") == 0, "Missing \"e\" from only_l2");
+ fail_unless(strcmp(only_l2[2], "f") == 0, "Missing \"d\" from only_l2");
+ fail_unless(only_l2[3] == NULL, "only_l2 not NULL-terminated");
+ fail_unless(both[0] == NULL, "both should have zero entries");
+
+ talloc_zfree(only_l1);
+ talloc_zfree(only_l2);
+ talloc_zfree(both);
+
+ /* Test with 100% overlap */
+ ret = diff_string_lists(test_ctx,
+ l1, l1,
+ &only_l1, &only_l2, &both);
+
+ fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
+ fail_unless(only_l1[0] == NULL, "only_l1 should have zero entries");
+ fail_unless(only_l2[0] == NULL, "only_l2 should have zero entries");
+ fail_unless(strcmp(both[0], "a") == 0, "Missing \"a\" from both");
+ fail_unless(strcmp(both[1], "b") == 0, "Missing \"b\" from both");
+ fail_unless(strcmp(both[2], "c") == 0, "Missing \"c\" from both");
+ fail_unless(both[3] == NULL, "both is not NULL-terminated");
+
+ talloc_zfree(only_l1);
+ talloc_zfree(only_l2);
+ talloc_zfree(both);
+
+ /* Test with no second list */
+ ret = diff_string_lists(test_ctx,
+ l1, NULL,
+ &only_l1, &only_l2, &both);
+
+ fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
+ fail_unless(strcmp(only_l1[0], "a") == 0, "Missing \"a\" from only_l1");
+ fail_unless(strcmp(only_l1[1], "b") == 0, "Missing \"b\" from only_l1");
+ fail_unless(strcmp(only_l1[2], "c") == 0, "Missing \"c\" from only_l1");
+ fail_unless(only_l1[3] == NULL, "only_l1 not NULL-terminated");
+ fail_unless(only_l2[0] == NULL, "only_l2 should have zero entries");
+ fail_unless(both[0] == NULL, "both should have zero entries");
+
+ talloc_free(test_ctx);
+}
+END_TEST
+
+Suite *util_suite(void)
+{
+ Suite *s = suite_create("util");
+
+ TCase *tc_util = tcase_create("util");
+
+ tcase_add_test (tc_util, test_diff_string_lists);
+ tcase_set_timeout(tc_util, 60);
+
+ suite_add_tcase (s, tc_util);
+
+ return s;
+}
+
+int main(int argc, const char *argv[])
+{
+ int opt;
+ int failure_count;
+ poptContext pc;
+ Suite *s = util_suite();
+ SRunner *sr = srunner_create (s);
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_MAIN_OPTS
+ { NULL }
+ };
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+
+ tests_set_cwd();
+
+ srunner_run_all(sr, CK_ENV);
+ failure_count = srunner_ntests_failed (sr);
+ srunner_free (sr);
+ if (failure_count == 0) {
+ return EXIT_SUCCESS;
+ }
+ return EXIT_FAILURE;
+}
diff --git a/src/util/util.c b/src/util/util.c
index 9a9731222..10bfb647b 100644
--- a/src/util/util.c
+++ b/src/util/util.c
@@ -22,6 +22,7 @@
#include "talloc.h"
#include "util/util.h"
+#include "dhash.h"
/* split a string into an allocated array of strings.
* the separator is a string, and is case-sensitive.
@@ -235,3 +236,229 @@ fail:
free_args(ret);
return NULL;
}
+
+char **dup_string_list(TALLOC_CTX *memctx, const char **str_list)
+{
+ int i = 0;
+ int j = 0;
+ char **dup_list;
+
+ if (!str_list) {
+ return NULL;
+ }
+
+ /* Find the size of the list */
+ while (str_list[i]) i++;
+
+ dup_list = talloc_array(memctx, char *, i+1);
+ if (!dup_list) {
+ return NULL;
+ }
+
+ /* Copy the elements */
+ for (j = 0; j < i; j++) {
+ dup_list[j] = talloc_strdup(dup_list, str_list[j]);
+ if (!dup_list[j]) {
+ talloc_free(dup_list);
+ return NULL;
+ }
+ }
+
+ /* NULL-terminate the list */
+ dup_list[i] = NULL;
+
+ return dup_list;
+}
+
+/* Take two string lists (terminated on a NULL char*)
+ * and return up to three arrays of strings based on
+ * shared ownership.
+ *
+ * Pass NULL to any return type you don't care about
+ */
+errno_t diff_string_lists(TALLOC_CTX *memctx,
+ char **_list1,
+ char **_list2,
+ char ***_list1_only,
+ char ***_list2_only,
+ char ***_both_lists)
+{
+ int error;
+ errno_t ret;
+ int i;
+ int i2 = 0;
+ int i12 = 0;
+ hash_table_t *table;
+ hash_key_t key;
+ hash_value_t value;
+ char **list1 = NULL;
+ char **list2 = NULL;
+ char **list1_only = NULL;
+ char **list2_only = NULL;
+ char **both_lists = NULL;
+ unsigned long count;
+ hash_key_t *keys;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(memctx);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ if (!_list1) {
+ list1 = talloc_array(tmp_ctx, char *, 1);
+ if (!list1) {
+ talloc_free(tmp_ctx);
+ return ENOMEM;
+ }
+ list1[0] = NULL;
+ }
+ else {
+ list1 = _list1;
+ }
+
+ if (!_list2) {
+ list2 = talloc_array(tmp_ctx, char *, 1);
+ if (!list2) {
+ talloc_free(tmp_ctx);
+ return ENOMEM;
+ }
+ list2[0] = NULL;
+ }
+ else {
+ list2 = _list2;
+ }
+
+ error = hash_create(10, &table, NULL, NULL);
+ if (error != HASH_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return EIO;
+ }
+
+ key.type = HASH_KEY_STRING;
+ value.type = HASH_VALUE_UNDEF;
+
+ /* Add all entries from list 1 into a hash table */
+ i = 0;
+ while (list1[i]) {
+ key.str = talloc_strdup(tmp_ctx, list1[i]);
+ error = hash_enter(table, &key, &value);
+ if (error != HASH_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+ i++;
+ }
+
+ /* Iterate through list 2 and remove matching items */
+ i = 0;
+ while (list2[i]) {
+ key.str = talloc_strdup(tmp_ctx, list2[i]);
+ error = hash_delete(table, &key);
+ if (error == HASH_SUCCESS) {
+ if (_both_lists) {
+ /* String was present in both lists */
+ i12++;
+ both_lists = talloc_realloc(tmp_ctx, both_lists, char *, i12+1);
+ if (!both_lists) {
+ ret = ENOMEM;
+ goto done;
+ }
+ both_lists[i12-1] = talloc_strdup(both_lists, list2[i]);
+ if (!both_lists[i12-1]) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ both_lists[i12] = NULL;
+ }
+ }
+ else if (error == HASH_ERROR_KEY_NOT_FOUND) {
+ if (_list2_only) {
+ /* String was present only in list2 */
+ i2++;
+ list2_only = talloc_realloc(tmp_ctx, list2_only,
+ char *, i2+1);
+ if (!list2_only) {
+ ret = ENOMEM;
+ goto done;
+ }
+ list2_only[i2-1] = talloc_strdup(list2_only, list2[i]);
+ if (!list2_only[i2-1]) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ list2_only[i2] = NULL;
+ }
+ }
+ else {
+ /* An error occurred */
+ ret = EIO;
+ goto done;
+ }
+ i++;
+ }
+
+ /* Get the leftover entries in the hash table */
+ if (_list1_only) {
+ error = hash_keys(table, &count, &keys);
+ if (error != HASH_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+
+ list1_only = talloc_array(tmp_ctx, char *, count+1);
+ if (!list1_only) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ for (i = 0; i < count; i++) {
+ list1_only[i] = talloc_strdup(list1_only, keys[i].str);
+ if (!list1_only[i]) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ list1_only[count] = NULL;
+
+ free(keys);
+
+ *_list1_only = talloc_steal(memctx, list1_only);
+ }
+
+ if (_list2_only) {
+ if (list2_only) {
+ *_list2_only = talloc_steal(memctx, list2_only);
+ }
+ else {
+ *_list2_only = talloc_array(memctx, char *, 1);
+ if (!(*_list2_only)) {
+ ret = ENOMEM;
+ goto done;
+ }
+ *_list2_only[0] = NULL;
+ }
+ }
+
+ if (_both_lists) {
+ if (both_lists) {
+ *_both_lists = talloc_steal(memctx, both_lists);
+ }
+ else {
+ *_both_lists = talloc_array(memctx, char *, 1);
+ if (!(*_both_lists)) {
+ ret = ENOMEM;
+ goto done;
+ }
+ *_both_lists[0] = NULL;
+ }
+ }
+
+ ret = EOK;
+
+done:
+ hash_destroy(table);
+ talloc_free(tmp_ctx);
+ return ret;
+}
diff --git a/src/util/util.h b/src/util/util.h
index 3c95f7a20..6bcc9984d 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -331,4 +331,23 @@ int split_on_separator(TALLOC_CTX *mem_ctx, const char *str,
const char sep, bool trim, char ***_list, int *size);
char **parse_args(const char *str);
+
+
+/* Copy a NULL-terminated string list
+ * Returns NULL on out of memory error or invalid input
+ */
+char **dup_string_list(TALLOC_CTX *memctx, const char **str_list);
+
+/* Take two string lists (terminated on a NULL char*)
+ * and return up to three arrays of strings based on
+ * shared ownership.
+ *
+ * Pass NULL to any return type you don't care about
+ */
+errno_t diff_string_lists(TALLOC_CTX *memctx,
+ char **string1,
+ char **string2,
+ char ***string1_only,
+ char ***string2_only,
+ char ***both_strings);
#endif /* __SSSD_UTIL_H__ */