>From 119238f878a7dd84f9ca964dae7625c53b6bbccb Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Mon, 3 Dec 2012 23:03:35 +0100 Subject: [PATCH 6/6] MEMBEROF: Keep inherited ghost users around on modify operation https://fedorahosted.org/sssd/ticket/1652 It is possible to simply reset the list of ghost users to a different one during a modify operation. It is also actually how we update entries that are expired in the SSSD cache. In this case, we must be careful and retain the ghost users that are not native to the group we are processing but are rather inherited from child groups. The intention of the replace operation after all is to set the list of direct members of that group, not direct and indirect. --- src/ldb_modules/memberof.c | 376 ++++++++++++++++++++++++++++++++++++++++++--- src/tests/sysdb-tests.c | 248 ++++++++++++++++++++++++++++++ 2 files changed, 600 insertions(+), 24 deletions(-) diff --git a/src/ldb_modules/memberof.c b/src/ldb_modules/memberof.c index cb6f8113f2b863cc6f8758f7de3baa5b38e053e8..131e2075579ead8237390b0b5207e1dbdb71a443 100644 --- a/src/ldb_modules/memberof.c +++ b/src/ldb_modules/memberof.c @@ -144,6 +144,18 @@ struct mbof_del_ctx { bool is_mod; }; +struct mbof_mod_del_op { + struct mbof_mod_ctx *mod_ctx; + struct ldb_message *mod_msg; + + struct mbof_dn_array *children; + int cur_child; + + struct ldb_message *msg; + + struct mbof_val_array *inherited_gh; +}; + struct mbof_mod_ctx { struct mbof_ctx *ctx; @@ -156,6 +168,7 @@ struct mbof_mod_ctx { struct mbof_val_array *gh_add; struct mbof_val_array *gh_remove; + struct mbof_mod_del_op *igh; struct ldb_message *msg; bool terminate; @@ -2774,19 +2787,37 @@ static void free_delop_contents(struct mbof_del_operation *delop) /* A modify operation just implements either an add operation, or a delete * operation or both (replace) in turn. - * The only difference between a modify and a pure add or a pure delete is that + * One difference between a modify and a pure add or a pure delete is that * the object is not created a new or not completely removed, but the setup just * treats it in the same way children objects are treated in a pure add or delete * operation. A list of appropriate parents and objects to modify is built, then * we jump directly in the add or delete code. * If both add and delete are necessary, delete operations are performed first - * and then a followup add operation is concatenated */ + * and then a followup add operation is concatenated + * + * Another difference is the ghost users. Because of its semi-managed nature, + * the ghost attribute requires some special care. During a modify operation, the + * ghost attribute can be set to a new list. That list coming, from an + * application, would typically only include the direct ghost + * members. However, we want to keep both direct and indirect ghost members + * in the cache to be able to return them all in a single call. To solve + * that problem, we also iterate over members of the group being modified, + * collect all ghost entries and add them back in case the original modify + * operation wiped them out. + */ static int mbof_mod_callback(struct ldb_request *req, struct ldb_reply *ares); +static int mbof_collect_child_ghosts(struct mbof_mod_ctx *mod_ctx); +static int mbof_get_ghost_from_parent(struct mbof_mod_del_op *igh); +static int mbof_get_ghost_from_parent_cb(struct ldb_request *req, + struct ldb_reply *ares); static int mbof_orig_mod(struct mbof_mod_ctx *mod_ctx); static int mbof_orig_mod_callback(struct ldb_request *req, struct ldb_reply *ares); +static int mbof_inherited_mod(struct mbof_mod_ctx *mod_ctx); +static int mbof_inherited_mod_callback(struct ldb_request *req, + struct ldb_reply *ares); static int mbof_mod_process(struct mbof_mod_ctx *mod_ctx, bool *done); static int mbof_mod_process_membel(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, struct ldb_message *entry, @@ -2795,7 +2826,8 @@ static int mbof_mod_process_membel(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, struct mbof_dn_array **_removed); static int mbof_mod_process_ghel(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, struct ldb_message *entry, - const struct ldb_message_element *membel, + const struct ldb_message_element *ghel, + struct mbof_val_array *inherited, struct mbof_val_array **_added, struct mbof_val_array **_removed); static int mbof_mod_delete(struct mbof_mod_ctx *mod_ctx, @@ -2807,8 +2839,13 @@ static int mbof_fill_dn_array(TALLOC_CTX *memctx, struct mbof_dn_array **dn_array); static int mbof_fill_vals_array(TALLOC_CTX *memctx, struct ldb_context *ldb, - const struct ldb_message_element *el, + unsigned int num_values, + struct ldb_val *values, struct mbof_val_array **val_array); +static int mbof_fill_vals_array_el(TALLOC_CTX *memctx, + struct ldb_context *ldb, + const struct ldb_message_element *el, + struct mbof_val_array **val_array); static int memberof_mod(struct ldb_module *module, struct ldb_request *req) { @@ -2937,11 +2974,150 @@ static int mbof_mod_callback(struct ldb_request *req, LDB_ERR_NO_SUCH_OBJECT); } + ret = mbof_collect_child_ghosts(mod_ctx); + if (ret != LDB_SUCCESS) { + talloc_zfree(ares); + return ldb_module_done(ctx->req, NULL, NULL, ret); + } + } + + talloc_zfree(ares); + return LDB_SUCCESS; +} + +static int mbof_collect_child_ghosts(struct mbof_mod_ctx *mod_ctx) +{ + int ret; + const struct ldb_message_element *member; + struct mbof_ctx *ctx; + struct ldb_context *ldb; + struct mbof_dn_array *children; + + ctx = mod_ctx->ctx; + ldb = ldb_module_get_ctx(ctx->module); + + member = ldb_msg_find_element(mod_ctx->entry, DB_MEMBER); + + if (member == NULL || member->num_values == 0 || + mod_ctx->ghel == NULL || mod_ctx->ghel->flags != LDB_FLAG_MOD_REPLACE) { ret = mbof_orig_mod(mod_ctx); if (ret != LDB_SUCCESS) { + return ret; + } + + return LDB_SUCCESS; + } + + mod_ctx->igh = talloc_zero(mod_ctx, struct mbof_mod_del_op); + if (mod_ctx == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + mod_ctx->igh->mod_ctx = mod_ctx; + mod_ctx->igh->mod_msg = mod_ctx->msg; + + ret = mbof_fill_dn_array(mod_ctx->igh, ldb, member, &children); + if (ret != LDB_SUCCESS) { + talloc_free(children); + return ret; + } + mod_ctx->igh->children = children; + mod_ctx->igh->cur_child = 0; + + return mbof_get_ghost_from_parent(mod_ctx->igh); +} + +static int mbof_get_ghost_from_parent(struct mbof_mod_del_op *igh) +{ + struct ldb_request *search; + struct ldb_context *ldb; + struct mbof_ctx *ctx; + int ret; + static const char *attrs[] = { DB_GHOST, NULL }; + + ctx = igh->mod_ctx->ctx; + ldb = ldb_module_get_ctx(ctx->module); + + if (igh->cur_child >= igh->children->num) { + /* Start the real mod */ + return mbof_orig_mod(igh->mod_ctx); + } + + ret = ldb_build_search_req(&search, ldb, igh, + igh->children->dns[igh->cur_child], + LDB_SCOPE_BASE, + NULL, attrs, NULL, + igh, mbof_get_ghost_from_parent_cb, + ctx->req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_request(ldb, search); +} + +static int mbof_get_ghost_from_parent_cb(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct mbof_mod_del_op *igh; + struct mbof_ctx *ctx; + struct ldb_context *ldb; + struct ldb_message_element *el; + int ret; + + igh = talloc_get_type(req->context, struct mbof_mod_del_op); + ctx = igh->mod_ctx->ctx; + ldb = ldb_module_get_ctx(ctx->module); + + if (!ares) { + return ldb_module_done(ctx->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ctx->req, + ares->controls, + ares->response, + ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + if (igh->msg != NULL) { + ldb_debug(ldb, LDB_DEBUG_TRACE, + "Found multiple entries for (%s)", + ldb_dn_get_linearized(igh->children->dns[igh->cur_child])); + /* more than one entry per dn ?? db corrupted ? */ + return ldb_module_done(ctx->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + igh->msg = ares->message; + + el = ldb_msg_find_element(igh->msg, DB_GHOST); + if (!el) { + break; + } + + /* gather ghosts from child and extend the add list */ + ret = mbof_fill_vals_array_el(igh, ldb, el, &igh->inherited_gh); + if (ret != LDB_SUCCESS) { + return ret; + } + break; + + case LDB_REPLY_REFERRAL: + /* ignore */ + break; + + case LDB_REPLY_DONE: + igh->msg = NULL; + igh->cur_child++; + + ret = mbof_get_ghost_from_parent(igh); + if (ret != LDB_SUCCESS) { talloc_zfree(ares); return ldb_module_done(ctx->req, NULL, NULL, ret); } + break; } talloc_zfree(ares); @@ -3006,7 +3182,13 @@ static int mbof_orig_mod_callback(struct ldb_request *req, if (!mod_ctx->terminate) { /* next step */ - ret = mbof_mod_process(mod_ctx, &mod_ctx->terminate); + if (mod_ctx->igh && mod_ctx->igh->inherited_gh && + mod_ctx->igh->inherited_gh->num > 0) { + ret = mbof_inherited_mod(mod_ctx); + } else { + ret = mbof_mod_process(mod_ctx, &mod_ctx->terminate); + } + if (ret != LDB_SUCCESS) { talloc_zfree(ares); return ldb_module_done(ctx->req, NULL, NULL, ret); @@ -3025,6 +3207,120 @@ static int mbof_orig_mod_callback(struct ldb_request *req, return LDB_SUCCESS; } +static int mbof_inherited_mod(struct mbof_mod_ctx *mod_ctx) +{ + struct ldb_request *mod_req; + struct ldb_context *ldb; + struct mbof_ctx *ctx; + int ret; + struct ldb_message *msg; + struct ldb_message_element *el; + struct ldb_val *val; + struct ldb_val *dup; + int i, j; + + ctx = mod_ctx->ctx; + ldb = ldb_module_get_ctx(ctx->module); + + /* add back the inherited children to entry */ + msg = ldb_msg_new(mod_ctx); + if (!msg) return LDB_ERR_OPERATIONS_ERROR; + + msg->dn = mod_ctx->entry->dn; + + /* We only inherit during replaces, so it's safe to only look + * at the replaced set + */ + ret = ldb_msg_add_empty(msg, DB_GHOST, LDB_FLAG_MOD_ADD, &el); + if (ret != LDB_SUCCESS) { + return ret; + } + + el->values = talloc_array(msg, struct ldb_val, + mod_ctx->igh->inherited_gh->num); + if (!el->values) { + return LDB_ERR_OPERATIONS_ERROR; + } + + for (i = 0, j = 0; i < mod_ctx->igh->inherited_gh->num; i++) { + /* if this value was already present in the replaced set, + * don't re-add it */ + val = &mod_ctx->igh->inherited_gh->vals[i]; + dup = ldb_msg_find_val(mod_ctx->ghel, val); + if (dup) { + continue; + } + + el->values[j].length = strlen((const char *) val->data); + el->values[j].data = (uint8_t *) talloc_strdup(el->values, + (const char *) val->data); + if (!el->values[j].data) { + return LDB_ERR_OPERATIONS_ERROR; + } + j++; + } + el->num_values = j; + + ret = ldb_build_mod_req(&mod_req, ldb, ctx->req, + msg, ctx->req->controls, + mod_ctx, mbof_inherited_mod_callback, + ctx->req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(ctx->module, mod_req); +} + +static int mbof_inherited_mod_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_context *ldb; + struct mbof_mod_ctx *mod_ctx; + struct mbof_ctx *ctx; + int ret; + + mod_ctx = talloc_get_type(req->context, struct mbof_mod_ctx); + ctx = mod_ctx->ctx; + ldb = ldb_module_get_ctx(ctx->module); + + if (!ares) { + return ldb_module_done(ctx->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ctx->req, + ares->controls, + ares->response, + ares->error); + } + + if (ares->type != LDB_REPLY_DONE) { + talloc_zfree(ares); + ldb_debug(ldb, LDB_DEBUG_TRACE, "Invalid reply type!"); + ldb_set_errstring(ldb, "Invalid reply type!"); + return ldb_module_done(ctx->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + ret = mbof_mod_process(mod_ctx, &mod_ctx->terminate); + if (ret != LDB_SUCCESS) { + talloc_zfree(ares); + return ldb_module_done(ctx->req, NULL, NULL, ret); + } + + if (mod_ctx->terminate) { + talloc_zfree(ares); + return ldb_module_done(ctx->req, + ctx->ret_ctrls, + ctx->ret_resp, + LDB_SUCCESS); + } + + talloc_zfree(ares); + return LDB_SUCCESS; +} + static int mbof_mod_process(struct mbof_mod_ctx *mod_ctx, bool *done) { struct ldb_context *ldb; @@ -3041,6 +3337,7 @@ static int mbof_mod_process(struct mbof_mod_ctx *mod_ctx, bool *done) } ret = mbof_mod_process_ghel(mod_ctx, ldb, mod_ctx->entry, mod_ctx->ghel, + mod_ctx->igh ? mod_ctx->igh->inherited_gh : NULL, &mod_ctx->gh_add, &mod_ctx->gh_remove); if (ret != LDB_SUCCESS) { return ret; @@ -3168,6 +3465,7 @@ static int mbof_mod_process_membel(TALLOC_CTX *mem_ctx, static int mbof_mod_process_ghel(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, struct ldb_message *entry, const struct ldb_message_element *ghel, + struct mbof_val_array *inherited, struct mbof_val_array **_added, struct mbof_val_array **_removed) { @@ -3189,7 +3487,7 @@ static int mbof_mod_process_ghel(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, switch (ghel->flags) { case LDB_FLAG_MOD_ADD: - ret = mbof_fill_vals_array(mem_ctx, ldb, ghel, &added); + ret = mbof_fill_vals_array_el(mem_ctx, ldb, ghel, &added); if (ret != LDB_SUCCESS) { return ret; } @@ -3207,7 +3505,7 @@ static int mbof_mod_process_ghel(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, break; } - ret = mbof_fill_vals_array(mem_ctx, ldb, ghel, &removed); + ret = mbof_fill_vals_array_el(mem_ctx, ldb, ghel, &removed); if (ret != LDB_SUCCESS) { return ret; } @@ -3216,7 +3514,7 @@ static int mbof_mod_process_ghel(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, case LDB_FLAG_MOD_REPLACE: el = ldb_msg_find_element(entry, DB_GHOST); if (el) { - ret = mbof_fill_vals_array(mem_ctx, ldb, el, &removed); + ret = mbof_fill_vals_array_el(mem_ctx, ldb, el, &removed); if (ret != LDB_SUCCESS) { return ret; } @@ -3224,13 +3522,23 @@ static int mbof_mod_process_ghel(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, el = ghel; if (el) { - ret = mbof_fill_vals_array(mem_ctx, ldb, el, &added); + ret = mbof_fill_vals_array_el(mem_ctx, ldb, el, &added); if (ret != LDB_SUCCESS) { talloc_free(removed); return ret; } } + if (inherited) { + ret = mbof_fill_vals_array(mem_ctx, ldb, inherited->num, + inherited->vals, &added); + if (ret != LDB_SUCCESS) { + talloc_free(added); + talloc_free(removed); + return ret; + } + } + /* remove from arrays values that ended up unchanged */ if (removed && removed->num && added && added->num) { for (i = 0; i < added->num; i++) { @@ -3429,40 +3737,60 @@ static int mbof_fill_dn_array(TALLOC_CTX *memctx, static int mbof_fill_vals_array(TALLOC_CTX *memctx, struct ldb_context *ldb, - const struct ldb_message_element *el, + unsigned int num_values, + struct ldb_val *values, struct mbof_val_array **val_array) { - struct mbof_val_array *var; - int i; + struct mbof_val_array *var = *val_array; + int i, index; - var = talloc_zero(memctx, struct mbof_val_array); - if (!var) { - return LDB_ERR_OPERATIONS_ERROR; + if (var == NULL) { + var = talloc_zero(memctx, struct mbof_val_array); + if (!var) { + return LDB_ERR_OPERATIONS_ERROR; + } + *val_array = var; } - *val_array = var; - if (!el || el->num_values == 0) { + if (values == NULL || num_values == 0) { return LDB_SUCCESS; } - var->vals = talloc_array(var, struct ldb_val, el->num_values); + /* We do not care about duplicate values now. + * They will be filtered later */ + index = var->num; + var->num += num_values; + var->vals = talloc_realloc(memctx, var->vals, struct ldb_val, var->num); if (!var->vals) { return LDB_ERR_OPERATIONS_ERROR; } - var->num = el->num_values; - for (i = 0; i < var->num; i++) { - var->vals[i].length = strlen((const char *) el->values[i].data); - var->vals[i].data = (uint8_t *) talloc_strdup(var, - (const char *) el->values[i].data); - if (var->vals[i].data == NULL) { + /* FIXME - use ldb_val_dup() */ + for (i = 0; i < num_values; i++) { + var->vals[index].length = strlen((const char *) values[i].data); + var->vals[index].data = (uint8_t *) talloc_strdup(var, + (const char *) values[i].data); + if (var->vals[index].data == NULL) { return LDB_ERR_OPERATIONS_ERROR; } + index++; } return LDB_SUCCESS; } +static int mbof_fill_vals_array_el(TALLOC_CTX *memctx, + struct ldb_context *ldb, + const struct ldb_message_element *el, + struct mbof_val_array **val_array) +{ + if (el == NULL) { + return LDB_SUCCESS; + } + + return mbof_fill_vals_array(memctx, ldb, el->num_values, el->values, + val_array); +} /************************* * Cleanup task routines * diff --git a/src/tests/sysdb-tests.c b/src/tests/sysdb-tests.c index d513c30f9b370d030bea72901eacdec8eaeb0e87..1db907b5a74bd7cd9a258fc8634a5c271a0386ca 100644 --- a/src/tests/sysdb-tests.c +++ b/src/tests/sysdb-tests.c @@ -1996,6 +1996,46 @@ START_TEST (test_sysdb_memberof_store_group_with_ghosts) } END_TEST +START_TEST (test_sysdb_memberof_store_group_with_double_ghosts) +{ + struct sysdb_test_ctx *test_ctx; + struct test_data *data; + 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; + data->gid = _i; + data->groupname = talloc_asprintf(data, "testgroup%d", data->gid); + + if (_i == 0) { + data->attrlist = NULL; + } else { + data->attrlist = talloc_array(data, const char *, 2); + fail_unless(data->attrlist != NULL, "talloc_array failed."); + data->attrlist[0] = talloc_asprintf(data, "testgroup%d", data->gid - 1); + data->attrlist[1] = NULL; + } + + data->memberlist = talloc_array(data, char *, 3); + fail_unless(data->memberlist != NULL, "talloc_array failed."); + data->memberlist[0] = talloc_asprintf(data, "testusera%d", data->gid); + data->memberlist[1] = talloc_asprintf(data, "testuserb%d", data->gid); + data->memberlist[2] = NULL; + + ret = test_memberof_store_group_with_ghosts(data); + + fail_if(ret != EOK, "Could not store POSIX group #%d", data->gid); + talloc_free(test_ctx); +} +END_TEST START_TEST (test_sysdb_memberof_mod_add) { @@ -2180,6 +2220,164 @@ START_TEST (test_sysdb_memberof_mod_replace) } END_TEST +START_TEST (test_sysdb_memberof_mod_replace_keep) +{ + struct sysdb_test_ctx *test_ctx; + struct test_data *data; + char *ghostname_rep; + char *ghostname_del; + char *ghostname_check; + int ret; + struct ldb_message_element *el; + struct ldb_val gv, *test_gv; + gid_t itergid; + uid_t iteruid; + + /* 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; + data->gid = MBO_GROUP_BASE + 10 - _i; + data->groupname = talloc_asprintf(data, "testgroup%d", data->gid); + + data->attrs = sysdb_new_attrs(data); + if (ret != EOK) { + fail("Could not create the changeset"); + return; + } + + /* The test replaces the attributes (testusera$gid, testuserb$gid) with + * just testusera$gid. The result should be not only testusera, but also + * all ghost users inherited from child groups + */ + ghostname_rep = talloc_asprintf(data, "testusera%d", data->gid); + fail_unless(ghostname_rep != NULL, "Out of memory\n"); + ret = sysdb_attrs_steal_string(data->attrs, SYSDB_GHOST, ghostname_rep); + fail_unless(ret == EOK, "Cannot add attr\n"); + + ghostname_del = talloc_asprintf(data, "testuserb%d", data->gid); + fail_unless(ghostname_del != NULL, "Out of memory\n"); + + data->attrlist = talloc_array(data, const char *, 2); + fail_unless(data->attrlist != NULL, "talloc_array failed."); + data->attrlist[0] = SYSDB_GHOST; + data->attrlist[1] = NULL; + + /* Before the replace, all groups with gid >= _i have both testuser a + * and testuserb as a member + */ + for (itergid = data->gid ; itergid < MBO_GROUP_BASE + NUM_GHOSTS; itergid++) { + ret = sysdb_search_group_by_gid(data, test_ctx->sysdb, + itergid, + data->attrlist, &data->msg); + fail_if(ret != EOK, "Cannot retrieve group %llu\n", + (unsigned long long) data->gid); + + gv.data = (uint8_t *) ghostname_rep; + gv.length = strlen(ghostname_rep); + + el = ldb_msg_find_element(data->msg, SYSDB_GHOST); + fail_if(el == NULL, "Cannot find ghost element\n"); + + test_gv = ldb_msg_find_val(el, &gv); + fail_if(test_gv == NULL, "Cannot find ghost user %s\n", ghostname_rep); + + gv.data = (uint8_t *) ghostname_del; + gv.length = strlen(ghostname_rep); + + test_gv = ldb_msg_find_val(el, &gv); + fail_if(test_gv == NULL, "Cannot find ghost user %s\n", ghostname_del); + + /* inherited users must be there */ + for (iteruid = MBO_GROUP_BASE ; iteruid < itergid ; iteruid++) { + ghostname_check = talloc_asprintf(data, "testusera%d", iteruid); + gv.data = (uint8_t *) ghostname_check; + gv.length = strlen(ghostname_check); + + test_gv = ldb_msg_find_val(el, &gv); + fail_if(test_gv == NULL, "Cannot find inherited ghost user %s\n", + ghostname_check); + + if (iteruid < data->gid) { + /* Also check the B user if it hasn't been deleted yet */ + ghostname_check = talloc_asprintf(data, "testuserb%d", iteruid); + gv.data = (uint8_t *) ghostname_check; + gv.length = strlen(ghostname_check); + + test_gv = ldb_msg_find_val(el, &gv); + fail_if(test_gv == NULL, "Cannot find inherited ghost user %s\n", + ghostname_check); + } + talloc_zfree(ghostname_check); + } + } + + /* Perform the replace operation */ + ret = sysdb_set_group_attr(test_ctx->sysdb, data->groupname, + data->attrs, SYSDB_MOD_REP); + fail_unless(ret == EOK, "Cannot set group attrs\n"); + + /* After the replace, testusera should still be there, but we also need + * to keep ghost users inherited from other groups + */ + for (itergid = data->gid ; itergid < MBO_GROUP_BASE + NUM_GHOSTS; itergid++) { + ret = sysdb_search_group_by_gid(data, test_ctx->sysdb, + itergid, + data->attrlist, &data->msg); + fail_if(ret != EOK, "Cannot retrieve group %llu\n", + (unsigned long long) data->gid); + + gv.data = (uint8_t *) ghostname_rep; + gv.length = strlen(ghostname_rep); + + /* testusera must still be there */ + el = ldb_msg_find_element(data->msg, SYSDB_GHOST); + fail_if(el == NULL, "Cannot find ghost element\n"); + + test_gv = ldb_msg_find_val(el, &gv); + fail_if(test_gv == NULL, "Cannot find ghost user %s\n", ghostname_rep); + + /* testuserb must be gone */ + gv.data = (uint8_t *) ghostname_del; + gv.length = strlen(ghostname_rep); + + test_gv = ldb_msg_find_val(el, &gv); + fail_unless(test_gv == NULL, "Cannot find ghost user %s\n", ghostname_del); + + /* inherited users must still be there */ + for (iteruid = MBO_GROUP_BASE ; iteruid < itergid ; iteruid++) { + ghostname_check = talloc_asprintf(data, "testusera%d", iteruid); + gv.data = (uint8_t *) ghostname_check; + gv.length = strlen(ghostname_check); + + test_gv = ldb_msg_find_val(el, &gv); + fail_if(test_gv == NULL, "Cannot find inherited ghost user %s\n", + ghostname_check); + + if (iteruid < data->gid) { + /* Also check the B user if it hasn't been deleted yet */ + ghostname_check = talloc_asprintf(data, "testuserb%d", iteruid); + gv.data = (uint8_t *) ghostname_check; + gv.length = strlen(ghostname_check); + + test_gv = ldb_msg_find_val(el, &gv); + fail_if(test_gv == NULL, "Cannot find inherited ghost user %s\n", + ghostname_check); + } + talloc_zfree(ghostname_check); + } + } + + talloc_free(test_ctx); +} +END_TEST + START_TEST (test_sysdb_memberof_close_loop) { struct sysdb_test_ctx *test_ctx; @@ -2489,6 +2687,45 @@ START_TEST (test_sysdb_memberof_check_nested_ghosts) } END_TEST +START_TEST (test_sysdb_memberof_check_nested_double_ghosts) +{ + struct sysdb_test_ctx *test_ctx; + struct test_data *data; + 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; + data->gid = _i; + + data->attrlist = talloc_array(data, const char *, 2); + fail_unless(data->attrlist != NULL, "talloc_array failed."); + data->attrlist[0] = SYSDB_GHOST; + data->attrlist[1] = NULL; + + ret = sysdb_search_group_by_gid(data, test_ctx->sysdb, + data->gid, + data->attrlist, &data->msg); + fail_if(ret != EOK, "Cannot retrieve group %llu\n", (unsigned long long) data->gid); + + fail_unless(strcmp(data->msg->elements[0].name, SYSDB_GHOST) == 0, + "Wrong attribute name"); + fail_unless(data->msg->elements[0].num_values == (_i - MBO_GROUP_BASE + 1)*2, + "Wrong number of attribute values, expected [%d] got [%d]", + (_i - MBO_GROUP_BASE + 1)*2, + data->msg->elements[0].num_values); + + talloc_free(test_ctx); +} +END_TEST + START_TEST (test_sysdb_memberof_remove_child_group_and_check_ghost) { struct sysdb_test_ctx *test_ctx; @@ -4942,6 +5179,17 @@ Suite *create_sysdb_suite(void) tcase_add_loop_test(tc_memberof, test_sysdb_remove_local_group_by_gid, MBO_GROUP_BASE , MBO_GROUP_BASE + 10); + /* ghost users - replace but retain inherited */ + tcase_add_loop_test(tc_memberof, test_sysdb_memberof_store_group_with_double_ghosts, + MBO_GROUP_BASE , MBO_GROUP_BASE + 10); + tcase_add_loop_test(tc_memberof, test_sysdb_memberof_check_nested_double_ghosts, + MBO_GROUP_BASE , MBO_GROUP_BASE + 10); + /* This loop counts backwards so the indexing is a little odd */ + tcase_add_loop_test(tc_memberof, test_sysdb_memberof_mod_replace_keep, + 1 , 11); + tcase_add_loop_test(tc_memberof, test_sysdb_remove_local_group_by_gid, + MBO_GROUP_BASE , MBO_GROUP_BASE + 10); + suite_add_tcase(s, tc_memberof); TCase *tc_subdomain = tcase_create("SYSDB sub-domain Tests"); -- 1.8.0.1