/*
SSSD memberof module
Copyright (C) Simo Sorce <idra@samba.org> 2008-2011
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 <string.h>
#include <dhash.h>
#include "ldb_module.h"
#include "util/util.h"
#define DB_MEMBER "member"
#define DB_GHOST "ghost"
#define DB_MEMBEROF "memberof"
#define DB_MEMBERUID "memberuid"
#define DB_NAME "name"
#define DB_USER_CLASS "user"
#define DB_GROUP_CLASS "group"
#define DB_CACHE_EXPIRE "dataExpireTimestamp"
#define DB_OC "objectClass"
#ifndef MAX
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
#endif
struct mbof_val_array {
struct ldb_val *vals;
int num;
};
struct mbof_dn_array {
struct ldb_dn **dns;
int num;
};
struct mbof_dn {
struct mbof_dn *next;
struct ldb_dn *dn;
};
struct mbof_ctx {
struct ldb_module *module;
struct ldb_request *req;
struct ldb_control **ret_ctrls;
struct ldb_extended *ret_resp;
};
struct mbof_add_operation {
struct mbof_add_ctx *add_ctx;
struct mbof_add_operation *next;
struct mbof_dn_array *parents;
struct ldb_dn *entry_dn;
struct ldb_message *entry;
};
struct mbof_memberuid_op {
struct ldb_dn *dn;
struct ldb_message_element *el;
};
struct mbof_add_ctx {
struct mbof_ctx *ctx;
struct mbof_add_operation *add_list;
struct mbof_add_operation *current_op;
struct ldb_message *msg;
struct ldb_dn *msg_dn;
bool terminate;
struct mbof_dn *missing;
struct mbof_memberuid_op *muops;
int num_muops;
int cur_muop;
};
struct mbof_del_ancestors_ctx {
struct mbof_dn_array *new_list;
int num_direct;
int cur;
struct ldb_message *entry;
};
struct mbof_del_operation {
struct mbof_del_ctx *del_ctx;
struct mbof_del_operation *parent;
struct mbof_del_operation **children;
int num_children;
int next_child;
struct ldb_dn *entry_dn;
struct ldb_message *entry;
struct ldb_message **parents;
int num_parents;
int cur_parent;
struct mbof_del_ancestors_ctx *anc_ctx;
};
struct mbof_mod_ctx;
struct mbof_del_ctx {
struct mbof_ctx *ctx;
struct mbof_del_operation *first;
struct mbof_dn *history;
struct ldb_message **mus;
int num_mus;
struct mbof_memberuid_op *muops;
int num_muops;
int cur_muop;
struct mbof_memberuid_op *ghops;
int num_ghops;
int cur_ghop;
struct mbof_mod_ctx *follow_mod;
bool is_mod;
};
struct mbof_mod_del_op {
struct mbof_mod_ctx *mod_ctx;
struct ldb_message *mod_msg;
struct ldb_message_element *el;
hash_table_t *inherited_gh;
};
struct mbof_mod_ctx {
struct mbof_ctx *ctx;
const struct ldb_message_element *membel;
const struct ldb_message_element *ghel;
struct ldb_message *entry;
struct mbof_dn_array *mb_add;
struct mbof_dn_array *mb_remove;
struct mbof_val_array *gh_add;
struct mbof_val_array *gh_remove;
struct mbof_mod_del_op *igh;
struct ldb_message *msg;
bool terminate;
};
static struct mbof_ctx *mbof_init(struct ldb_module *module,
struct ldb_request *req)
{
struct mbof_ctx *ctx;
ctx = talloc_zero(req, struct mbof_ctx);
if (!ctx) {
return NULL;
}
ctx->module = module;
ctx->req = req;
return ctx;
}
static void *hash_alloc(const size_t size, void *pvt)
{
return talloc_size(pvt, size);
}
static void hash_free(void *ptr, void *pvt)
{
talloc_free(ptr);
}
static int entry_has_objectclass(struct ldb_message *entry,
const char *objectclass)
{
struct ldb_message_element *el;
struct ldb_val *val;
int i;
el = ldb_msg_find_element(entry, DB_OC);
if (!el) {
return LDB_ERR_OPERATIONS_ERROR;
}
for (i = 0; i < el->num_values; i++) {
val = &(el->values[i]);
if (strncasecmp(objectclass, (char *)val->data, val->length) == 0) {
return LDB_SUCCESS;
}
}
return LDB_ERR_NO_SUCH_ATTRIBUTE;
}
static int entry_is_user_object(struct ldb_message *entry)
{
return entry_has_objectclass(entry, DB_USER_CLASS);
}
static int entry_is_group_object(struct ldb_message *entry)
{
return entry_has_objectclass(entry, DB_GROUP_CLASS);
}
static int mbof_append_muop(TALLOC_CTX *memctx,
struct mbof_memberuid_op **_muops,
int *_num_muops,
int flags,
struct ldb_dn *parent,
const char *name,
const char *element_name)
{
struct mbof_memberuid_op *muops = *_muops;
int num_muops = *_num_muops;
struct mbof_memberuid_op *op;
struct ldb_val *val;
int i;
op = NULL;
if (muops) {
for (i = 0; i < num_muops; i++) {
if (ldb_dn_compare(parent, muops[i].dn) == 0) {
op = &muops[i];
break;
}
}
}
if (!op) {
muops = talloc_realloc(memctx, muops,
struct mbof_memberuid_op,
num_muops + 1);
if (!muops) {
return LDB_ERR_OPERATIONS_ERROR;
}
op = &muops[num_muops];
num_muops++;
*_muops = muops;
*_num_muops = num_muops;
op->dn = parent;
op->el = NULL;
}
if (!op->el) {
op->el = talloc_zero(muops, struct ldb_message_element);
if (!op->el) {
return LDB_ERR_OPERATIONS_ERROR;
}
op->el->name = talloc_strdup(op->el, element_name);
if (!op->el->name) {
return LDB_ERR_OPERATIONS_ERROR;
}
op->el->flags = flags;
}
for (i = 0; i < op->el->num_values; i++) {
if (strcmp((char *)op->el->values[i].data, name) == 0) {
/* we already have this value, get out*/
return LDB_SUCCESS;
}
}
val = talloc_realloc(op->el, op->el->values,
struct ldb_val, op->el->num_values + 1);
if (!val) {
return LDB_ERR_OPERATIONS_ERROR;
}
val[op->el->num_values].data = (uint8_t *)talloc_strdup(val, name);
if (!val[op->el->num_values].data) {
return LDB_ERR_OPERATIONS_ERROR;
}
val[op->el->num_values].length = strlen(name);
op->el->values = val;
op->el->num_values++;
return LDB_SUCCESS;
}
/* add operation */
/* An add operation is quite simple.
* First of all a new object cannot yet have parents, so the only memberof
* attribute that can be added to any member contains just one object DN.
*
* The real add operation is done first, to assure nothing else fails.
* Then we list all members of the object just created, and for each member
* we create an "add operation" and we pass it a parent list of one member
* (the object we just added again).
*
* For each add operation we lookup the object we want to operate on.
* We take the list of memberof attributes and sort out which parents are
* still missing from the parent list we have provided.
* We modify the object memberof attributes to reflect the new memberships.
* Then we list all members of this object, and for each once again we create
* an "add operation" as we did in the initial object.
*
* Processing stops when the target object does not have members or when it
* already has all the parents (can happen if nested groups create loops).
*
* Group cache unrolling:
* Every time we add a memberof attribute to an actual user object,
* we proceed to store the user name.
*
* At the end we will add a memberuid attribute to our new object that
* includes all direct and indirect user members names.
*
* Group objects can also contain a "ghost" attribute. A ghost attribute
* represents a user that is a member of the group but has not yet been
* looked up so there is no real user entry with member/memberof links.
*
* If an object being added contains a "ghost" attribute, the ghost attribute
* is in turn copied to all parents of that object so that retrieving a
* group returns both its direct and indirect members. The ghost attribute is
* similar to the memberuid attribute in many respects. One difference is that
* the memberuid attribute is completely generated and managed by the memberof
* plugin - in contrast, the ghost attribute is added to the entry that "owns"
* it and only propagated to parent groups.
*/
static int mbof_append_addop(struct mbof_add_ctx *add_ctx,
struct mbof_dn_array *parents,
struct ldb_dn *entry_dn)
{
struct mbof_add_operation *lastop = NULL;
struct mbof_add_operation *addop;
/* test if this is a duplicate */
/* FIXME: this is not efficient */
if (add_ctx->add_list) {
do {
if (lastop) {
lastop = lastop->next;
} else {
lastop = add_ctx->add_list;
}
/* FIXME: check if this is right, might have to compare parents */
if (ldb_dn_compare(lastop->entry_dn, entry_dn) == 0) {
/* duplicate found */
return LDB_SUCCESS;
}
} while (lastop->next);
}
addop = talloc_zero(add_ctx, struct mbof_add_operation);
if (!addop) {
return LDB_ERR_OPERATIONS_ERROR;
}
addop->add_ctx = add_ctx;
addop->parents = parents;
addop->entry_dn = entry_dn;
if (add_ctx->add_list) {
lastop->next = addop;
} else {
add_ctx->add_list = addop;
}
return LDB_SUCCESS;
}
static int mbof_add_fill_ghop_ex(struct mbof_add_ctx *add_ctx,
struct ldb_message *entry,
struct mbof_dn_array *parents,
struct ldb_val *ghvals,
unsigned int num_gh_vals)
{
int ret;
int i, j;
if (!parents || parents->num == 0) {
/* no parents attributes ... */
return LDB_SUCCESS;
}
ret = entry_is_group_object(entry);
switch (ret) {
case LDB_SUCCESS:
/* it's a group object, continue */
break;
case LDB_ERR_NO_SUCH_ATTRIBUTE:
/* it is not a group object, just return */
return LDB_SUCCESS;
default:
/* an error occurred, return */
return ret;
}
ldb_debug(ldb_module_get_ctx(add_ctx->ctx->module),
LDB_DEBUG_TRACE,
"will add %d ghost users to %d parents\n",
num_gh_vals, parents->num);
for (i = 0; i < parents->num; i++) {
for (j = 0; j < num_gh_vals; j++) {
ret = mbof_append_muop(add_ctx, &add_ctx->muops,
&add_ctx->num_muops,
LDB_FLAG_MOD_ADD,
parents->dns[i],
(const char *) ghvals[j].data,
DB_GHOST);
if (ret != LDB_SUCCESS) {
return ret;
}
}
}
return LDB_SUCCESS;
}
static int memberof_recompute_task(struct ldb_module *module,
struct ldb_request *req);
static int mbof_add_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_next_add(struct mbof_add_operation *addop);
static int mbof_next_add_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_add_operation(struct mbof_add_operation *addop);
static int mbof_add_fill_ghop(struct mbof_add_ctx *add_ctx,
struct ldb_message *entry,
struct mbof_dn_array *parents);
static int mbof_add_missing(struct mbof_add_ctx *add_ctx, struct ldb_dn *dn);
static int mbof_add_cleanup(struct mbof_add_ctx *add_ctx);
static int mbof_add_cleanup_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_add_muop(struct mbof_add_ctx *add_ctx);
static int mbof_add_muop_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int memberof_add(struct ldb_module *module, struct ldb_request *req)
{
struct ldb_context *ldb = ldb_module_get_ctx(module);
struct mbof_add_ctx *add_ctx;
struct mbof_ctx *ctx;
struct ldb_request *add_req;
struct ldb_message_element *el;
struct mbof_dn_array *parents;
struct ldb_dn *valdn;
int i, ret;
if (ldb_dn_is_special(req->op.add.message->dn)) {
if (strcmp("@MEMBEROF-REBUILD",
ldb_dn_get_linearized(req->op.add.message->dn)) == 0) {
return memberof_recompute_task(module, req);
}
/* do not manipulate other control entries */
return ldb_next_request(module, req);
}
/* check if memberof is specified */
el = ldb_msg_find_element(req->op.add.message, DB_MEMBEROF);
if (el) {
ldb_debug(ldb, LDB_DEBUG_ERROR,
"Error: the memberof attribute is readonly.");
return LDB_ERR_UNWILLING_TO_PERFORM;
}
/* check if memberuid is specified */
el = ldb_msg_find_element(req->op.add.message, DB_MEMBERUID);
if (el) {
ldb_debug(ldb, LDB_DEBUG_ERROR,
"Error: the memberuid attribute is readonly.");
return LDB_ERR_UNWILLING_TO_PERFORM;
}
ctx = mbof_init(module, req);
if (!ctx) {
return LDB_ERR_OPERATIONS_ERROR;
}
add_ctx = talloc_zero(ctx, struct mbof_add_ctx);
if (!add_ctx) {
return LDB_ERR_OPERATIONS_ERROR;
}
add_ctx->ctx = ctx;
add_ctx->msg = ldb_msg_copy(add_ctx, req->op.add.message);
if (!add_ctx->msg) {
return LDB_ERR_OPERATIONS_ERROR;
}
add_ctx->msg_dn = add_ctx->msg->dn;
/* continue with normal ops if there are no members */
el = ldb_msg_find_element(add_ctx->msg, DB_MEMBER);
if (!el) {
add_ctx->terminate = true;
goto done;
}
parents = talloc_zero(add_ctx, struct mbof_dn_array);
if (!parents) {
return LDB_ERR_OPERATIONS_ERROR;
}
parents->dns = talloc_array(parents, struct ldb_dn *, 1);
if (!parents->dns) {
return LDB_ERR_OPERATIONS_ERROR;
}
parents->dns[0] = add_ctx->msg_dn;
parents->num = 1;
/* process new members */
/* check we are not adding ourselves as member as well */
for (i = 0; i < el->num_values; i++) {
valdn = ldb_dn_from_ldb_val(add_ctx, ldb, &el->values[i]);
if (!valdn || !ldb_dn_validate(valdn)) {
ldb_debug(ldb, LDB_DEBUG_ERROR, "Invalid dn value: [%s]",
(const char *)el->values[i].data);
return LDB_ERR_INVALID_DN_SYNTAX;
}
if (ldb_dn_compare(valdn, req->op.add.message->dn) == 0) {
ldb_debug(ldb, LDB_DEBUG_ERROR,
"Adding self as member is not permitted! Skipping");
continue;
}
ret = mbof_append_addop(add_ctx, parents, valdn);
if (ret != LDB_SUCCESS) {
return ret;
}
}
done:
/* add original object */
ret = ldb_build_add_req(&add_req, ldb, add_ctx,
add_ctx->msg, req->controls,
add_ctx, mbof_add_callback,
req);
if (ret != LDB_SUCCESS) {
return ret;
}
return ldb_next_request(module, add_req);
}
static int mbof_add_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct mbof_add_ctx *add_ctx;
struct mbof_ctx *ctx;
int ret;
add_ctx = talloc_get_type(req->context, struct mbof_add_ctx);
ctx = add_ctx->ctx;
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:
/* shouldn't happen */
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
case LDB_REPLY_REFERRAL:
/* ignore */
break;
case LDB_REPLY_DONE:
if (add_ctx->terminate) {
return ldb_module_done(ctx->req,
ctx->ret_ctrls,
ctx->ret_resp,
LDB_SUCCESS);
}
if (add_ctx->current_op == NULL) {
/* first operation */
ctx->ret_ctrls = talloc_steal(ctx, ares->controls);
ctx->ret_resp = talloc_steal(ctx, ares->response);
ret = mbof_next_add(add_ctx->add_list);
}
else if (add_ctx->current_op->next) {
/* next operation */
ret = mbof_next_add(add_ctx->current_op->next);
}
else {
/* no more operations */
if (add_ctx->missing) {
ret = mbof_add_cleanup(add_ctx);
}
else if (add_ctx->muops) {
ret = mbof_add_muop(add_ctx);
}
else {
return ldb_module_done(ctx->req,
ctx->ret_ctrls,
ctx->ret_resp,
LDB_SUCCESS);
}
}
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_next_add(struct mbof_add_operation *addop)
{
static const char *attrs[] = { DB_OC, DB_NAME,
DB_MEMBER, DB_GHOST,
DB_MEMBEROF, NULL };
struct ldb_context *ldb;
struct ldb_request *req;
struct mbof_add_ctx *add_ctx;
struct mbof_ctx *ctx;
int ret;
add_ctx = addop->add_ctx;
ctx = add_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
/* mark the operation as being handled */
add_ctx->current_op = addop;
ret = ldb_build_search_req(&req, ldb, ctx,
addop->entry_dn, LDB_SCOPE_BASE,
NULL, attrs, NULL,
addop, mbof_next_add_callback,
ctx->req);
if (ret != LDB_SUCCESS) {
return ret;
}
return ldb_request(ldb, req);
}
static int mbof_next_add_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct mbof_add_operation *addop;
struct mbof_add_ctx *add_ctx;
struct ldb_context *ldb;
struct mbof_ctx *ctx;
int ret;
addop = talloc_get_type(req->context, struct mbof_add_operation);
add_ctx = addop->add_ctx;
ctx = add_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 (addop->entry != NULL) {
ldb_debug(ldb, LDB_DEBUG_TRACE,
"Found multiple entries for (%s)",
ldb_dn_get_linearized(addop->entry_dn));
/* more than one entry per dn ?? db corrupted ? */
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
addop->entry = talloc_steal(addop, ares->message);
if (addop->entry == NULL) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
break;
case LDB_REPLY_REFERRAL:
/* ignore */
break;
case LDB_REPLY_DONE:
talloc_zfree(ares);
if (addop->entry == NULL) {
ldb_debug(ldb, LDB_DEBUG_TRACE, "Entry not found (%s)",
ldb_dn_get_linearized(addop->entry_dn));
/* this target does not exists, save as missing */
ret = mbof_add_missing(add_ctx, addop->entry_dn);
if (ret != LDB_SUCCESS) {
return ldb_module_done(ctx->req, NULL, NULL, ret);
}
/* now try the next operation */
if (add_ctx->current_op->next) {
ret = mbof_next_add(add_ctx->current_op->next);
}
else {
/* no more operations */
if (add_ctx->missing) {
ret = mbof_add_cleanup(add_ctx);
}
else if (add_ctx->muops) {
ret = mbof_add_muop(add_ctx);
}
else {
return ldb_module_done(ctx->req,
ctx->ret_ctrls,
ctx->ret_resp,
LDB_SUCCESS);
}
}
if (ret != LDB_SUCCESS) {
return ldb_module_done(ctx->req, NULL, NULL, ret);
}
}
else {
ret = mbof_add_operation(addop);
if (ret != LDB_SUCCESS) {
return ldb_module_done(ctx->req, NULL, NULL, ret);
}
}
return LDB_SUCCESS;
}
talloc_zfree(ares);
return LDB_SUCCESS;
}
/* if it is a group, add all members for cascade effect
* add memberof attribute to this entry
*/
static int mbof_add_operation(struct mbof_add_operation *addop)
{
TALLOC_CTX *tmp_ctx;
struct mbof_ctx *ctx;
struct mbof_add_ctx *add_ctx;
struct ldb_context *ldb;
struct ldb_message_element *el;
struct ldb_request *mod_req;
struct ldb_message *msg;
struct ldb_dn *elval_dn;
struct ldb_dn *valdn;
struct mbof_dn_array *parents;
int i, j, ret;
const char *val;
const char *name;
add_ctx = addop->add_ctx;
ctx = add_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
parents = talloc_zero(add_ctx, struct mbof_dn_array);
if (!parents) {
return LDB_ERR_OPERATIONS_ERROR;
}
/* can't be more than the immediate parent */
parents->dns = talloc_array(parents, struct ldb_dn *,
addop->parents->num);
if (!parents->dns) {
return LDB_ERR_OPERATIONS_ERROR;
}
/* create new parent set for this entry */
for (i = 0; i < addop->parents->num; i++) {
/* never add yourself as memberof */
if (ldb_dn_compare(addop->parents->dns[i], addop->entry_dn) == 0) {
continue;
}
parents->dns[parents->num] = addop->parents->dns[i];
parents->num++;
}
/* remove entries that are already there */
el = ldb_msg_find_element(addop->entry, DB_MEMBEROF);
if (el) {
tmp_ctx = talloc_new(addop);
if (!tmp_ctx) return LDB_ERR_OPERATIONS_ERROR;
for (i = 0; i < el->num_values; i++) {
elval_dn = ldb_dn_from_ldb_val(tmp_ctx, ldb, &el->values[i]);
if (!elval_dn) {
ldb_debug(ldb, LDB_DEBUG_TRACE, "Invalid DN in memberof [%s]",
(const char *)el->values[i].data);
talloc_free(tmp_ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
for (j = 0; j < parents->num; j++) {
if (ldb_dn_compare(parents->dns[j], elval_dn) == 0) {
/* duplicate found */
break;
}
}
if (j < parents->num) {
/* remove duplicate */
for (;j+1 < parents->num; j++) {
parents->dns[j] = parents->dns[j+1];
}
parents->num--;
}
}
if (parents->num == 0) {
/* already contains all parents as memberof, skip to next */
talloc_free(tmp_ctx);
talloc_free(addop->entry);
addop->entry = NULL;
if (addop->next) {
return mbof_next_add(addop->next);
}
else if (add_ctx->muops) {
return mbof_add_muop(add_ctx);
}
else {
/* that was the last entry, get out */
return ldb_module_done(ctx->req,
ctx->ret_ctrls,
ctx->ret_resp,
LDB_SUCCESS);
}
}
talloc_free(tmp_ctx);
}
/* if it is a group add all members */
el = ldb_msg_find_element(addop->entry, DB_MEMBER);
if (el) {
for (i = 0; i < el->num_values; i++) {
valdn = ldb_dn_from_ldb_val(add_ctx, ldb, &el->values[i]);
if (!valdn) {
ldb_debug(ldb, LDB_DEBUG_TRACE, "Invalid DN in member [%s]",
(const char *)el->values[i].data);
return LDB_ERR_OPERATIONS_ERROR;
}
if (!ldb_dn_validate(valdn)) {
ldb_debug(ldb, LDB_DEBUG_TRACE,
"Invalid DN syntax for member [%s]",
(const char *)el->values[i].data);
return LDB_ERR_INVALID_DN_SYNTAX;
}
ret = mbof_append_addop(add_ctx, parents, valdn);
if (ret != LDB_SUCCESS) {
return ret;
}
}
}
/* check if we need to store memberuid ops for this entry */
ret = entry_is_user_object(addop->entry);
switch (ret) {
case LDB_SUCCESS:
/* it's a user object */
name = ldb_msg_find_attr_as_string(addop->entry, DB_NAME, NULL);
if (!name) {
return LDB_ERR_OPERATIONS_ERROR;
}
for (i = 0; i < parents->num; i++) {
ret = mbof_append_muop(add_ctx, &add_ctx->muops,
&add_ctx->num_muops,
LDB_FLAG_MOD_ADD,
parents->dns[i], name,
DB_MEMBERUID);
if (ret != LDB_SUCCESS) {
return ret;
}
}
break;
case LDB_ERR_NO_SUCH_ATTRIBUTE:
/* it is not a user object, continue */
break;
default:
/* an error occurred, return */
return ret;
}
ret = mbof_add_fill_ghop(add_ctx, addop->entry, parents);
if (ret != LDB_SUCCESS) {
return ret;
}
/* we are done with the entry now */
talloc_free(addop->entry);
addop->entry = NULL;
/* add memberof to entry */
msg = ldb_msg_new(addop);
if (!msg) return LDB_ERR_OPERATIONS_ERROR;
msg->dn = addop->entry_dn;
ret = ldb_msg_add_empty(msg, DB_MEMBEROF, LDB_FLAG_MOD_ADD, &el);
if (ret != LDB_SUCCESS) {
return ret;
}
el->values = talloc_array(msg, struct ldb_val, parents->num);
if (!el->values) {
return LDB_ERR_OPERATIONS_ERROR;
}
for (i = 0, j = 0; i < parents->num; i++) {
if (ldb_dn_compare(parents->dns[i], msg->dn) == 0) continue;
val = ldb_dn_get_linearized(parents->dns[i]);
el->values[j].length = strlen(val);
el->values[j].data = (uint8_t *)talloc_strdup(el->values, val);
if (!el->values[j].data) {
return LDB_ERR_OPERATIONS_ERROR;
}
j++;
}
el->num_values = j;
ret = ldb_build_mod_req(&mod_req, ldb, add_ctx,
msg, NULL,
add_ctx, mbof_add_callback,
ctx->req);
if (ret != LDB_SUCCESS) {
return ret;
}
talloc_steal(mod_req, msg);
return ldb_next_request(ctx->module, mod_req);
}
static int mbof_add_fill_ghop(struct mbof_add_ctx *add_ctx,
struct ldb_message *entry,
struct mbof_dn_array *parents)
{
struct ldb_message_element *ghel;
ghel = ldb_msg_find_element(entry, DB_GHOST);
if (ghel == NULL || ghel->num_values == 0) {
/* No ghel attribute, just return success */
return LDB_SUCCESS;
}
return mbof_add_fill_ghop_ex(add_ctx, entry, parents,
ghel->values, ghel->num_values);
}
static int mbof_add_missing(struct mbof_add_ctx *add_ctx, struct ldb_dn *dn)
{
struct mbof_dn *mdn;
mdn = talloc(add_ctx, struct mbof_dn);
if (!mdn) {
return LDB_ERR_OPERATIONS_ERROR;
}
mdn->dn = talloc_steal(mdn, dn);
/* add to the list */
mdn->next = add_ctx->missing;
add_ctx->missing = mdn;
return LDB_SUCCESS;
}
/* remove unexisting members and add memberuid attribute */
static int mbof_add_cleanup(struct mbof_add_ctx *add_ctx)
{
struct ldb_context *ldb;
struct ldb_message *msg;
struct ldb_request *mod_req;
struct ldb_message_element *el;
struct mbof_ctx *ctx;
struct mbof_dn *iter;
const char *val;
int ret, i, num;
ctx = add_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
num = 0;
for (iter = add_ctx->missing; iter; iter = iter->next) {
num++;
}
if (num == 0) {
return LDB_ERR_OPERATIONS_ERROR;
}
msg = ldb_msg_new(add_ctx);
if (!msg) return LDB_ERR_OPERATIONS_ERROR;
msg->dn = add_ctx->msg_dn;
ret = ldb_msg_add_empty(msg, DB_MEMBER, LDB_FLAG_MOD_DELETE, &el);
if (ret != LDB_SUCCESS) {
return ret;
}
el->values = talloc_array(msg, struct ldb_val, num);
if (!el->values) {
return LDB_ERR_OPERATIONS_ERROR;
}
el->num_values = num;
for (i = 0, iter = add_ctx->missing; iter; iter = iter->next, i++) {
val = ldb_dn_get_linearized(iter->dn);
el->values[i].length = strlen(val);
el->values[i].data = (uint8_t *)talloc_strdup(el->values, val);
if (!el->values[i].data) {
return LDB_ERR_OPERATIONS_ERROR;
}
}
ret = ldb_build_mod_req(&mod_req, ldb, add_ctx,
msg, NULL,
add_ctx, mbof_add_cleanup_callback,
ctx->req);
if (ret != LDB_SUCCESS) {
return ret;
}
return ldb_next_request(ctx->module, mod_req);
}
static int mbof_add_cleanup_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct mbof_add_ctx *add_ctx;
struct mbof_ctx *ctx;
int ret;
add_ctx = talloc_get_type(req->context, struct mbof_add_ctx);
ctx = add_ctx->ctx;
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:
/* shouldn't happen */
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
case LDB_REPLY_REFERRAL:
/* ignore */
break;
case LDB_REPLY_DONE:
if (add_ctx->muops) {
ret = mbof_add_muop(add_ctx);
}
else {
return ldb_module_done(ctx->req,
ctx->ret_ctrls,
ctx->ret_resp,
LDB_SUCCESS);
}
if (ret != LDB_SUCCESS) {
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL, ret);
}
}
talloc_zfree(ares);
return LDB_SUCCESS;
}
/* add memberuid attributes to parent groups */
static int mbof_add_muop(struct mbof_add_ctx *add_ctx)
{
struct ldb_context *ldb;
struct ldb_message *msg;
struct ldb_request *mod_req;
struct mbof_ctx *ctx;
int ret;
ctx = add_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
msg = ldb_msg_new(add_ctx);
if (!msg) return LDB_ERR_OPERATIONS_ERROR;
msg->dn = add_ctx->muops[add_ctx->cur_muop].dn;
msg->elements = add_ctx->muops[add_ctx->cur_muop].el;
msg->num_elements = 1;
ret = ldb_build_mod_req(&mod_req, ldb, add_ctx,
msg, NULL,
add_ctx, mbof_add_muop_callback,
ctx->req);
if (ret != LDB_SUCCESS) {
return ret;
}
ret = ldb_request_add_control(mod_req, LDB_CONTROL_PERMISSIVE_MODIFY_OID,
false, NULL);
if (ret != LDB_SUCCESS) {
talloc_free(mod_req);
return ret;
}
return ldb_next_request(ctx->module, mod_req);
}
static int mbof_add_muop_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct mbof_add_ctx *add_ctx;
struct mbof_ctx *ctx;
int ret;
add_ctx = talloc_get_type(req->context, struct mbof_add_ctx);
ctx = add_ctx->ctx;
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:
/* shouldn't happen */
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
case LDB_REPLY_REFERRAL:
/* ignore */
break;
case LDB_REPLY_DONE:
add_ctx->cur_muop++;
if (add_ctx->cur_muop < add_ctx->num_muops) {
ret = mbof_add_muop(add_ctx);
}
else {
return ldb_module_done(ctx->req,
ctx->ret_ctrls,
ctx->ret_resp,
LDB_SUCCESS);
}
if (ret != LDB_SUCCESS) {
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL, ret);
}
}
talloc_zfree(ares);
return LDB_SUCCESS;
}
/* delete operations */
/* The implementation of delete operations is a bit more complex than an add
* operation. This is because we need to recompute memberships of potentially
* quite far descendants and we also have to account for loops and how to
* break them without ending in an endless loop ourselves.
* The difficulty is in the fact that while the member -> memberof link is
* direct, memberof -> member is not as membership is transitive.
*
* Ok, first of all, contrary to the add operation, a delete operation
* involves an existing object that may have existing parents. So, first, we
* search the object itself to get the original membership lists (member and
* memberof) for this object, and we also search for any object that has it as
* one of its members.
* Once we have the results, we store object and parents and proceed with the
* original operation to make sure it is valid.
*
* Once the original op returns we proceed fixing parents (parents being each
* object that has the delete operation target object as member), if any.
*
* For each parent we retrieved we proceed to delete the member attribute that
* points to the object we just deleted. Once done for all parents (or if no
* parents exists), we proceed with the children and descendants.
*
* To handle the children we create a first ancestor operation that reflects
* the delete we just made. We set as parents of this object the parents just
* retrieved with the first search. Then we create a remove list.
*
* The remove list contains all objects in the original memberof list and the
* object dn itself of the original delete operation target object (the first
* ancestor).
*
* An operation is identified by an object that contains a tree of
* descendants:
* The remove list for the children, the immediate parent, and the dn and
* entry of the object this operation is about.
*
* We now proceed with adding a new operation for each original member of the
* first ancestor.
*
* In each operation we must first lookup the target object and each immediate
* parent (all the objects in the tree that have target as a "member").
*
* Then we proceed to calculate the new memberof list that we are going to set
* on the target object.
* The new memberof list starts with including all the objects that have the
* target as their direct member.
* Finally for each entry in this provisional new memberof list we add all its
* memberof elements to the new memberof list (taking care of excluding
* duplicates). This way we are certain all direct and indirect membership are
* accounted for.
*
* At this point we have the final new memberof list for this operation and we
* can proceed to modify the entry.
*
* Once the entry has been modified we proceed again to check if there are any
* children of this entry (the entry has "member"s).
* We create a new remove list that is the difference between the original
* entry memberof list and the new memberof list we just stored back in the
* object.
* Then for each member we create a new operation.
*
* We continue to process operations until no new operations need to be
* performed.
*
* Ordering is important here, se the mbof_del_get_next() function to
* understand how we proceed to select which new operation to process.
*
* As a final operation remove any memberuid corresponding to a removal of
* a memberof field from a user entry. Also if the original entry had a ghost
* attribute, we need to remove that attribute from all its parents as well.
*
* There is one catch though - at the memberof level, we can't know if the
* attribute being removed from a parent group is just inherited from the group
* being removed or also a direct member of the parent group. To make sure
* that the attribute is displayed next time the group is requested, we also
* set expire the parent group at the same time.
*/
static int mbof_del_search_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_orig_del(struct mbof_del_ctx *ctx);
static int mbof_orig_del_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_del_cleanup_parents(struct mbof_del_ctx *del_ctx);
static int mbof_del_clean_par_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_del_cleanup_children(struct mbof_del_ctx *del_ctx);
static int mbof_append_delop(struct mbof_del_operation *parent,
struct ldb_dn *entry_dn);
static int mbof_del_execute_op(struct mbof_del_operation *delop);
static int mbof_del_exop_search_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_del_execute_cont(struct mbof_del_operation *delop);
static int mbof_del_ancestors(struct mbof_del_operation *delop);
static int mbof_del_anc_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_del_mod_entry(struct mbof_del_operation *delop);
static int mbof_del_mod_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_del_progeny(struct mbof_del_operation *delop);
static int mbof_del_get_next(struct mbof_del_operation *delop,
struct mbof_del_operation **nextop);
static int mbof_del_fill_muop(struct mbof_del_ctx *del_ctx,
struct ldb_message *entry);
static int mbof_del_fill_ghop(struct mbof_del_ctx *del_ctx,
struct ldb_message *entry);
static int mbof_del_muop(struct mbof_del_ctx *ctx);
static int mbof_del_muop_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_del_ghop(struct mbof_del_ctx *del_ctx);
static int mbof_del_ghop_callback(struct ldb_request *req,
struct ldb_reply *ares);
static void free_delop_contents(struct mbof_del_operation *delop);
static int memberof_del(struct ldb_module *module, struct ldb_request *req)
{
static const char *attrs[] = { DB_OC, DB_NAME,
DB_MEMBER, DB_MEMBEROF,
DB_GHOST, NULL };
struct ldb_context *ldb = ldb_module_get_ctx(module);
struct mbof_del_operation *first;
struct ldb_request *search;
char *expression;
const char *dn;
char *clean_dn;
struct mbof_del_ctx *del_ctx;
struct mbof_ctx *ctx;
int ret;
errno_t sret;
if (ldb_dn_is_special(req->op.del.dn)) {
/* do not manipulate our control entries */
return ldb_next_request(module, req);
}
ctx = mbof_init(module, req);
if (!ctx) {
return LDB_ERR_OPERATIONS_ERROR;
}
del_ctx = talloc_zero(ctx, struct mbof_del_ctx);
if (!del_ctx) {
talloc_free(ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
del_ctx->ctx = ctx;
/* create first entry */
/* the first entry is the parent of all entries and the one where we remove
* member from, it does not get the same treatment as others */
first = talloc_zero(del_ctx, struct mbof_del_operation);
if (!first) {
talloc_free(ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
del_ctx->first = first;
first->del_ctx = del_ctx;
first->entry_dn = req->op.del.dn;
dn = ldb_dn_get_linearized(req->op.del.dn);
if (!dn) {
talloc_free(ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
sret = sss_filter_sanitize(del_ctx, dn, &clean_dn);
if (sret != 0) {
talloc_free(ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
expression = talloc_asprintf(del_ctx,
"(|(distinguishedName=%s)(%s=%s))",
clean_dn, DB_MEMBER, clean_dn);
if (!expression) {
talloc_free(ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
talloc_zfree(clean_dn);
ret = ldb_build_search_req(&search, ldb, del_ctx,
NULL, LDB_SCOPE_SUBTREE,
expression, attrs, NULL,
first, mbof_del_search_callback,
req);
if (ret != LDB_SUCCESS) {
talloc_free(ctx);
return ret;
}
return ldb_request(ldb, search);
}
static int mbof_del_search_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct mbof_del_operation *first;
struct ldb_context *ldb;
struct ldb_message *msg;
struct mbof_del_ctx *del_ctx;
struct mbof_ctx *ctx;
int ret;
first = talloc_get_type(req->context, struct mbof_del_operation);
del_ctx = first->del_ctx;
ctx = del_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:
msg = ares->message;
if (ldb_dn_compare(msg->dn, ctx->req->op.del.dn) == 0) {
if (first->entry != NULL) {
/* more than one entry per dn ?? db corrupted ? */
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
first->entry = talloc_steal(first, msg);
if (first->entry == NULL) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
} else {
first->parents = talloc_realloc(first, first->parents,
struct ldb_message *,
first->num_parents + 1);
if (!first->parents) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
msg = talloc_steal(first->parents, ares->message);
if (!msg) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
first->parents[first->num_parents] = msg;
first->num_parents++;
}
break;
case LDB_REPLY_REFERRAL:
/* ignore */
break;
case LDB_REPLY_DONE:
if (first->entry == NULL) {
/* this target does not exists, too bad! */
ldb_debug(ldb, LDB_DEBUG_TRACE,
"Target entry (%s) not found",
ldb_dn_get_linearized(first->entry_dn));
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_NO_SUCH_OBJECT);
}
/* now perform the requested delete, before proceeding further */
ret = mbof_orig_del(del_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_orig_del(struct mbof_del_ctx *del_ctx)
{
struct ldb_request *del_req;
struct mbof_ctx *ctx;
int ret;
ctx = del_ctx->ctx;
ret = ldb_build_del_req(&del_req, ldb_module_get_ctx(ctx->module),
ctx->req, ctx->req->op.del.dn, NULL,
del_ctx, mbof_orig_del_callback,
ctx->req);
if (ret != LDB_SUCCESS) {
return ret;
}
return ldb_next_request(ctx->module, del_req);
}
static int mbof_orig_del_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct ldb_context *ldb;
struct mbof_del_ctx *del_ctx;
struct mbof_ctx *ctx;
int ret;
del_ctx = talloc_get_type(req->context, struct mbof_del_ctx);
ctx = del_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_set_errstring(ldb, "Invalid reply type!");
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
/* save real call stuff */
ctx->ret_ctrls = talloc_steal(ctx, ares->controls);
ctx->ret_resp = talloc_steal(ctx, ares->response);
/* prep following clean ops */
if (del_ctx->first->num_parents) {
/* if there are parents there may be memberuids to remove */
ret = mbof_del_fill_muop(del_ctx, del_ctx->first->entry);
if (ret != LDB_SUCCESS) {
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL, ret);
}
/* ..or ghost attributes to remove */
ret = mbof_del_fill_ghop(del_ctx, del_ctx->first->entry);
if (ret != LDB_SUCCESS) {
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL, ret);
}
/* if there are any parents, fire a removal sequence */
ret = mbof_del_cleanup_parents(del_ctx);
}
else if (ldb_msg_find_element(del_ctx->first->entry, DB_MEMBER)) {
/* if there are any children, fire a removal sequence */
ret = mbof_del_cleanup_children(del_ctx);
}
/* see if there are memberuid operations to perform */
else if (del_ctx->muops) {
return mbof_del_muop(del_ctx);
}
/* see if we need to remove some ghost users */
else if (del_ctx->ghops) {
return mbof_del_ghop(del_ctx);
}
else {
/* no parents nor children, end ops */
return ldb_module_done(ctx->req,
ares->controls,
ares->response,
LDB_SUCCESS);
}
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_del_cleanup_parents(struct mbof_del_ctx *del_ctx)
{
struct mbof_del_operation *first;
struct mbof_ctx *ctx;
struct ldb_context *ldb;
struct ldb_request *mod_req;
struct ldb_message *msg;
struct ldb_message_element *el;
const char *val;
int ret;
first = del_ctx->first;
ctx = del_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
msg = ldb_msg_new(first->parents);
if (!msg) return LDB_ERR_OPERATIONS_ERROR;
msg->dn = first->parents[first->cur_parent]->dn;
first->cur_parent++;
ret = ldb_msg_add_empty(msg, DB_MEMBER, LDB_FLAG_MOD_DELETE, &el);
if (ret != LDB_SUCCESS) {
return ret;
}
el->values = talloc_array(msg, struct ldb_val, 1);
if (!el->values) {
return LDB_ERR_OPERATIONS_ERROR;
}
val = ldb_dn_get_linearized(first->entry_dn);
el->values[0].length = strlen(val);
el->values[0].data = (uint8_t *)talloc_strdup(el->values, val);
if (!el->values[0].data) {
return LDB_ERR_OPERATIONS_ERROR;
}
el->num_values = 1;
ret = ldb_build_mod_req(&mod_req, ldb, first->parents,
msg, NULL,
del_ctx, mbof_del_clean_par_callback,
ctx->req);
if (ret != LDB_SUCCESS) {
return ret;
}
return ldb_next_request(ctx->module, mod_req);
}
static int mbof_del_clean_par_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct mbof_del_operation *first;
struct ldb_context *ldb;
struct mbof_del_ctx *del_ctx;
struct mbof_ctx *ctx;
int ret;
del_ctx = talloc_get_type(req->context, struct mbof_del_ctx);
first = del_ctx->first;
ctx = del_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_set_errstring(ldb, "Invalid reply type!");
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
if (first->num_parents > first->cur_parent) {
/* still parents to cleanup, go on */
ret = mbof_del_cleanup_parents(del_ctx);
}
else {
/* continue */
if (ldb_msg_find_element(first->entry, DB_MEMBER)) {
/* if there are any children, fire a removal sequence */
ret = mbof_del_cleanup_children(del_ctx);
}
/* see if there are memberuid operations to perform */
else if (del_ctx->muops) {
return mbof_del_muop(del_ctx);
}
/* see if we need to remove some ghost users */
else if (del_ctx->ghops) {
return mbof_del_ghop(del_ctx);
}
else {
/* no children, end ops */
return ldb_module_done(ctx->req,
ctx->ret_ctrls,
ctx->ret_resp,
LDB_SUCCESS);
}
}
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_del_cleanup_children(struct mbof_del_ctx *del_ctx)
{
struct mbof_del_operation *first;
struct mbof_ctx *ctx;
struct ldb_context *ldb;
const struct ldb_message_element *el;
struct ldb_dn *valdn;
int i, ret;
first = del_ctx->first;
ctx = del_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
el = ldb_msg_find_element(first->entry, DB_MEMBER);
/* prepare del sets */
for (i = 0; i < el->num_values; i++) {
valdn = ldb_dn_from_ldb_val(first, ldb, &el->values[i]);
if (!valdn || !ldb_dn_validate(valdn)) {
ldb_debug(ldb, LDB_DEBUG_TRACE,
"Invalid dn syntax for member [%s]",
(const char *)el->values[i].data);
return LDB_ERR_INVALID_DN_SYNTAX;
}
ret = mbof_append_delop(first, valdn);
if (ret != LDB_SUCCESS) {
return ret;
}
}
/* now that sets are built, start processing */
return mbof_del_execute_op(first->children[0]);
}
static int mbof_append_delop(struct mbof_del_operation *parent,
struct ldb_dn *entry_dn)
{
struct mbof_del_operation *delop;
delop = talloc_zero(parent, struct mbof_del_operation);
if (!delop) {
return LDB_ERR_OPERATIONS_ERROR;
}
delop->del_ctx = parent->del_ctx;
delop->parent = parent;
delop->entry_dn = entry_dn;
parent->children = talloc_realloc(parent, parent->children,
struct mbof_del_operation *,
parent->num_children +1);
if (!parent->children) {
talloc_free(delop);
return LDB_ERR_OPERATIONS_ERROR;
}
parent->children[parent->num_children] = delop;
parent->num_children++;
return LDB_SUCCESS;
}
static int mbof_del_execute_op(struct mbof_del_operation *delop)
{
struct mbof_del_ctx *del_ctx;
struct mbof_ctx *ctx;
struct ldb_context *ldb;
struct ldb_request *search;
char *expression;
const char *dn;
char *clean_dn;
static const char *attrs[] = { DB_OC, DB_NAME,
DB_MEMBER, DB_MEMBEROF, NULL };
int ret;
del_ctx = delop->del_ctx;
ctx = del_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
/* load entry */
dn = ldb_dn_get_linearized(delop->entry_dn);
if (!dn) {
return LDB_ERR_OPERATIONS_ERROR;
}
ret = sss_filter_sanitize(del_ctx, dn, &clean_dn);
if (ret != 0) {
return LDB_ERR_OPERATIONS_ERROR;
}
expression = talloc_asprintf(del_ctx,
"(|(distinguishedName=%s)(%s=%s))",
clean_dn, DB_MEMBER, clean_dn);
if (!expression) {
return LDB_ERR_OPERATIONS_ERROR;
}
talloc_zfree(clean_dn);
ret = ldb_build_search_req(&search, ldb, delop,
NULL, LDB_SCOPE_SUBTREE,
expression, attrs, NULL,
delop, mbof_del_exop_search_callback,
ctx->req);
if (ret != LDB_SUCCESS) {
talloc_free(ctx);
return ret;
}
return ldb_request(ldb, search);
}
static int mbof_del_exop_search_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct mbof_del_operation *delop;
struct mbof_del_ctx *del_ctx;
struct ldb_context *ldb;
struct mbof_ctx *ctx;
struct ldb_message *msg;
int ret;
delop = talloc_get_type(req->context, struct mbof_del_operation);
del_ctx = delop->del_ctx;
ctx = del_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:
msg = ares->message;
if (ldb_dn_compare(msg->dn, delop->entry_dn) == 0) {
if (delop->entry != NULL) {
ldb_debug(ldb, LDB_DEBUG_TRACE,
"Found multiple entries for (%s)",
ldb_dn_get_linearized(delop->entry_dn));
/* more than one entry per dn ?? db corrupted ? */
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
delop->entry = talloc_steal(delop, msg);
if (delop->entry == NULL) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
} else {
delop->parents = talloc_realloc(delop, delop->parents,
struct ldb_message *,
delop->num_parents + 1);
if (!delop->parents) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
msg = talloc_steal(delop->parents, msg);
if (!msg) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
delop->parents[delop->num_parents] = msg;
delop->num_parents++;
}
break;
case LDB_REPLY_REFERRAL:
/* ignore */
break;
case LDB_REPLY_DONE:
if (delop->entry == NULL) {
/* no target, no party! */
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
/* ok process the entry */
ret = mbof_del_execute_cont(delop);
if (ret != LDB_SUCCESS) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
}
talloc_zfree(ares);
return LDB_SUCCESS;
}
static int mbof_del_execute_cont(struct mbof_del_operation *delop)
{
struct mbof_del_ancestors_ctx *anc_ctx;
struct mbof_dn_array *new_list;
int i;
anc_ctx = talloc_zero(delop, struct mbof_del_ancestors_ctx);
if (!anc_ctx) {
return LDB_ERR_OPERATIONS_ERROR;
}
delop->anc_ctx = anc_ctx;
new_list = talloc_zero(anc_ctx, struct mbof_dn_array);
if (!new_list) {
return LDB_ERR_OPERATIONS_ERROR;
}
/* at the very least we have a number of memberof elements
* equal to the number of objects that have this entry as
* direct member */
new_list->num = delop->num_parents;
/* attach the list to the operation */
delop->anc_ctx->new_list = new_list;
delop->anc_ctx->num_direct = new_list->num;
/* do we have any direct parent at all ? */
if (new_list->num == 0) {
/* no entries at all, entry ended up being orphaned */
/* skip to directly set the new memberof list for this entry */
return mbof_del_mod_entry(delop);
}
/* fill in the list if we have parents */
new_list->dns = talloc_zero_array(new_list,
struct ldb_dn *,
new_list->num);
if (!new_list->dns) {
return LDB_ERR_OPERATIONS_ERROR;
}
for (i = 0; i < delop->num_parents; i++) {
new_list->dns[i] = delop->parents[i]->dn;
}
/* before proceeding we also need to fetch the ancestors (anew as some may
* have changed by preceeding operations) */
return mbof_del_ancestors(delop);
}
static int mbof_del_ancestors(struct mbof_del_operation *delop)
{
struct mbof_del_ancestors_ctx *anc_ctx;
struct mbof_del_ctx *del_ctx;
struct mbof_ctx *ctx;
struct ldb_context *ldb;
struct mbof_dn_array *new_list;
static const char *attrs[] = { DB_MEMBEROF, NULL };
struct ldb_request *search;
int ret;
del_ctx = delop->del_ctx;
ctx = del_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
anc_ctx = delop->anc_ctx;
new_list = anc_ctx->new_list;
ret = ldb_build_search_req(&search, ldb, anc_ctx,
new_list->dns[anc_ctx->cur],
LDB_SCOPE_BASE, NULL, attrs, NULL,
delop, mbof_del_anc_callback,
ctx->req);
if (ret != LDB_SUCCESS) {
return ret;
}
return ldb_request(ldb, search);
}
static int mbof_del_anc_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct mbof_del_ancestors_ctx *anc_ctx;
struct mbof_del_operation *delop;
struct mbof_del_ctx *del_ctx;
struct mbof_ctx *ctx;
struct ldb_context *ldb;
struct ldb_message *msg;
const struct ldb_message_element *el;
struct mbof_dn_array *new_list;
struct ldb_dn *valdn;
int i, j, ret;
delop = talloc_get_type(req->context, struct mbof_del_operation);
del_ctx = delop->del_ctx;
ctx = del_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
anc_ctx = delop->anc_ctx;
new_list = anc_ctx->new_list;
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:
msg = ares->message;
if (anc_ctx->entry != NULL) {
ldb_debug(ldb, LDB_DEBUG_TRACE,
"Found multiple entries for (%s)",
ldb_dn_get_linearized(anc_ctx->entry->dn));
/* more than one entry per dn ?? db corrupted ? */
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
anc_ctx->entry = talloc_steal(anc_ctx, msg);
if (anc_ctx->entry == NULL) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
break;
case LDB_REPLY_REFERRAL:
/* ignore */
break;
case LDB_REPLY_DONE:
if (anc_ctx->entry == NULL) {
/* no target, no party! */
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
/* check entry */
el = ldb_msg_find_element(anc_ctx->entry, DB_MEMBEROF);
if (el) {
for (i = 0; i < el->num_values; i++) {
valdn = ldb_dn_from_ldb_val(new_list, ldb, &el->values[i]);
if (!valdn) {
ldb_debug(ldb, LDB_DEBUG_TRACE,
"Invalid dn for memberof: (%s)",
(const char *)el->values[i].data);
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
for (j = 0; j < new_list->num; j++) {
if (ldb_dn_compare(valdn, new_list->dns[j]) == 0)
break;
}
if (j < new_list->num) {
talloc_free(valdn);
continue;
}
/* do not re-add the original deleted entry by mistake */
if (ldb_dn_compare(valdn, del_ctx->first->entry_dn) == 0) {
talloc_free(valdn);
continue;
}
new_list->dns = talloc_realloc(new_list,
new_list->dns,
struct ldb_dn *,
new_list->num + 1);
if (!new_list->dns) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
new_list->dns[new_list->num] = valdn;
new_list->num++;
}
}
/* done with this one */
talloc_free(anc_ctx->entry);
anc_ctx->entry = NULL;
anc_ctx->cur++;
/* check if we need to process any more */
if (anc_ctx->cur < anc_ctx->num_direct) {
/* ok process the next one */
ret = mbof_del_ancestors(delop);
} else {
/* ok, end of the story, proceed to modify the entry */
ret = mbof_del_mod_entry(delop);
}
if (ret != LDB_SUCCESS) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
}
talloc_zfree(ares);
return LDB_SUCCESS;
}
static int mbof_del_mod_entry(struct mbof_del_operation *delop)
{
struct mbof_del_ctx *del_ctx;
struct mbof_ctx *ctx;
struct ldb_context *ldb;
struct mbof_dn_array *new_list;
struct ldb_request *mod_req;
struct ldb_message *msg;
struct ldb_message_element *el;
struct ldb_dn **diff = NULL;
const char *name;
const char *val;
int i, j, k;
bool is_user;
int ret;
del_ctx = delop->del_ctx;
ctx = del_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
new_list = delop->anc_ctx->new_list;
/* if this is a user we need to find out which entries have been
* removed so that we can later schedule removal of memberuid
* attributes from these entries */
ret = entry_is_user_object(delop->entry);
switch (ret) {
case LDB_SUCCESS:
/* it's a user object */
is_user = true;
break;
case LDB_ERR_NO_SUCH_ATTRIBUTE:
/* it is not a user object, continue */
is_user = false;
break;
default:
/* an error occurred, return */
return ret;
}
if (is_user) {
/* prepare memberuid delete list */
/* copy all original memberof entries, and then later remove
* the ones that will survive in the entry */
el = ldb_msg_find_element(delop->entry, DB_MEMBEROF);
if (!el || !el->num_values) {
return LDB_ERR_OPERATIONS_ERROR;
}
diff = talloc_array(del_ctx, struct ldb_dn *,
el->num_values + 1);
if (!diff) {
return LDB_ERR_OPERATIONS_ERROR;
}
for (i = 0, j = 0; i < el->num_values; i++) {
diff[j] = ldb_dn_from_ldb_val(diff, ldb, &el->values[i]);
if (!diff[j]) {
return LDB_ERR_OPERATIONS_ERROR;
}
/* skip the deleted entry if this is a delete op */
if (!del_ctx->is_mod) {
if (ldb_dn_compare(del_ctx->first->entry_dn, diff[j]) == 0) {
continue;
}
}
j++;
}
/* zero terminate array */
diff[j] = NULL;
}
/* change memberof on entry */
msg = ldb_msg_new(delop);
if (!msg) return LDB_ERR_OPERATIONS_ERROR;
msg->dn = delop->entry_dn;
if (new_list->num) {
ret = ldb_msg_add_empty(msg, DB_MEMBEROF, LDB_FLAG_MOD_REPLACE, &el);
if (ret != LDB_SUCCESS) {
return ret;
}
el->values = talloc_array(el, struct ldb_val, new_list->num);
if (!el->values) {
return LDB_ERR_OPERATIONS_ERROR;
}
for (i = 0, j = 0; i < new_list->num; i++) {
if (ldb_dn_compare(new_list->dns[i], msg->dn) == 0)
continue;
val = ldb_dn_get_linearized(new_list->dns[i]);
if (!val) {
return LDB_ERR_OPERATIONS_ERROR;
}
el->values[j].length = strlen(val);
el->values[j].data = (uint8_t *)talloc_strdup(el->values, val);
if (!el->values[j].data) {
return LDB_ERR_OPERATIONS_ERROR;
}
j++;
if (is_user) {
/* compare the entry's original memberof list with the new
* one and for each missing entry add a memberuid removal
* operation */
for (k = 0; diff[k]; k++) {
if (ldb_dn_compare(new_list->dns[i], diff[k]) == 0) {
break;
}
}
if (diff[k]) {
talloc_zfree(diff[k]);
for (; diff[k + 1]; k++) {
diff[k] = diff[k + 1];
}
diff[k] = NULL;
}
}
}
el->num_values = j;
}
else {
ret = ldb_msg_add_empty(msg, DB_MEMBEROF, LDB_FLAG_MOD_DELETE, &el);
if (ret != LDB_SUCCESS) {
return ret;
}
}
if (is_user && diff[0]) {
/* file memberuid removal operations */
name = ldb_msg_find_attr_as_string(delop->entry, DB_NAME, NULL);
if (!name) {
return LDB_ERR_OPERATIONS_ERROR;
}
for (i = 0; diff[i]; i++) {
ret = mbof_append_muop(del_ctx, &del_ctx->muops,
&del_ctx->num_muops,
LDB_FLAG_MOD_DELETE,
diff[i], name,
DB_MEMBERUID);
if (ret != LDB_SUCCESS) {
return ret;
}
talloc_steal(del_ctx->muops, diff[i]);
}
}
ret = ldb_build_mod_req(&mod_req, ldb, delop,
msg, NULL,
delop, mbof_del_mod_callback,
ctx->req);
if (ret != LDB_SUCCESS) {
return ret;
}
talloc_steal(mod_req, msg);
return ldb_next_request(ctx->module, mod_req);
}
static int mbof_del_mod_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct mbof_del_operation *delop;
struct mbof_del_ctx *del_ctx;
struct ldb_context *ldb;
struct mbof_ctx *ctx;
int ret;
delop = talloc_get_type(req->context, struct mbof_del_operation);
del_ctx = delop->del_ctx;
ctx = del_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:
ldb_debug(ldb, LDB_DEBUG_TRACE, "Got an entry on a non search op ?!");
/* shouldn't happen */
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
case LDB_REPLY_REFERRAL:
/* ignore */
talloc_zfree(ares);
break;
case LDB_REPLY_DONE:
ret = mbof_del_progeny(delop);
if (ret != LDB_SUCCESS) {
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL, ret);
}
}
return LDB_SUCCESS;
}
static int mbof_mod_add(struct mbof_mod_ctx *mod_ctx,
struct mbof_dn_array *ael,
struct mbof_val_array *addgh);
static int mbof_del_progeny(struct mbof_del_operation *delop)
{
struct mbof_ctx *ctx;
struct mbof_del_ctx *del_ctx;
struct mbof_del_operation *nextop;
const struct ldb_message_element *el;
struct ldb_context *ldb;
struct ldb_dn *valdn;
int i, ret;
del_ctx = delop->del_ctx;
ctx = del_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
/* now verify if this entry is a group and members need to be processed as
* well */
el = ldb_msg_find_element(delop->entry, DB_MEMBER);
if (el) {
for (i = 0; i < el->num_values; i++) {
valdn = ldb_dn_from_ldb_val(delop, ldb, &el->values[i]);
if (!valdn || !ldb_dn_validate(valdn)) {
ldb_debug(ldb, LDB_DEBUG_TRACE,
"Invalid DN for member: (%s)",
(const char *)el->values[i].data);
return LDB_ERR_INVALID_DN_SYNTAX;
}
ret = mbof_append_delop(delop, valdn);
if (ret != LDB_SUCCESS) {
return ret;
}
}
}
/* finally find the next entry to handle */
ret = mbof_del_get_next(delop, &nextop);
if (ret != LDB_SUCCESS) {
return ret;
}
free_delop_contents(delop);
if (nextop) {
return mbof_del_execute_op(nextop);
}
/* see if there are memberuid operations to perform */
if (del_ctx->muops) {
return mbof_del_muop(del_ctx);
}
/* see if we need to remove some ghost users */
else if (del_ctx->ghops) {
return mbof_del_ghop(del_ctx);
}
/* see if there are follow functions to run */
if (del_ctx->follow_mod) {
return mbof_mod_add(del_ctx->follow_mod,
del_ctx->follow_mod->mb_add,
del_ctx->follow_mod->gh_add);
}
/* ok, no more ops, this means our job is done */
return ldb_module_done(ctx->req,
ctx->ret_ctrls,
ctx->ret_resp,
LDB_SUCCESS);
}
static int mbof_del_get_next(struct mbof_del_operation *delop,
struct mbof_del_operation **nextop)
{
struct mbof_del_operation *top, *cop;
struct mbof_del_ctx *del_ctx;
struct mbof_dn *save, *tmp;
del_ctx = delop->del_ctx;
/* first of all, save the current delop in the history */
save = talloc_zero(del_ctx, struct mbof_dn);
if (!save) {
return LDB_ERR_OPERATIONS_ERROR;
}
save->dn = delop->entry_dn;
if (del_ctx->history) {
tmp = del_ctx->history;
while (tmp->next) tmp = tmp->next;
tmp->next = save;
} else {
del_ctx->history = save;
}
/* Find next one */
for (top = delop; top; top = top->parent) {
if (top->num_children == 0 || top->next_child >= top->num_children) {
/* no children, go for next one */
continue;
}
while (top->next_child < top->num_children) {
cop = top->children[top->next_child];
top->next_child++;
/* verify this operation has not already been performed */
for (tmp = del_ctx->history; tmp; tmp = tmp->next) {
if (ldb_dn_compare(tmp->dn, cop->entry_dn) == 0) {
break;
}
}
if (tmp == NULL) {
/* and return the current one */
*nextop = cop;
return LDB_SUCCESS;
}
}
}
/* we have no more ops */
*nextop = NULL;
return LDB_SUCCESS;
}
static int mbof_del_fill_muop(struct mbof_del_ctx *del_ctx,
struct ldb_message *entry)
{
struct ldb_message_element *el;
char *name;
int ret;
int i;
el = ldb_msg_find_element(entry, DB_MEMBEROF);
if (!el || el->num_values == 0) {
/* no memberof attributes ... */
return LDB_SUCCESS;
}
ret = entry_is_user_object(entry);
switch (ret) {
case LDB_SUCCESS:
/* it's a user object, continue */
break;
case LDB_ERR_NO_SUCH_ATTRIBUTE:
/* it is not a user object, just return */
return LDB_SUCCESS;
default:
/* an error occurred, return */
return ret;
}
name = talloc_strdup(del_ctx,
ldb_msg_find_attr_as_string(entry, DB_NAME, NULL));
if (!name) {
return LDB_ERR_OPERATIONS_ERROR;
}
for (i = 0; i < el->num_values; i++) {
struct ldb_dn *valdn;
valdn = ldb_dn_from_ldb_val(del_ctx,
ldb_module_get_ctx(del_ctx->ctx->module),
&el->values[i]);
if (!valdn || !ldb_dn_validate(valdn)) {
ldb_debug(ldb_module_get_ctx(del_ctx->ctx->module),
LDB_DEBUG_ERROR,
"Invalid dn value: [%s]",
(const char *)el->values[i].data);
}
ret = mbof_append_muop(del_ctx, &del_ctx->muops,
&del_ctx->num_muops,
LDB_FLAG_MOD_DELETE,
valdn, name,
DB_MEMBERUID);
if (ret != LDB_SUCCESS) {
return ret;
}
talloc_steal(del_ctx->muops, valdn);
}
return LDB_SUCCESS;
}
static int mbof_del_fill_ghop_ex(struct mbof_del_ctx *del_ctx,
struct ldb_message *entry,
struct ldb_val *ghvals,
unsigned int num_gh_vals)
{
struct ldb_message_element *mbof;
struct ldb_dn *valdn;
int ret;
int i, j;
mbof = ldb_msg_find_element(entry, DB_MEMBEROF);
if (!mbof || mbof->num_values == 0) {
/* no memberof attributes ... */
return LDB_SUCCESS;
}
ret = entry_is_group_object(entry);
switch (ret) {
case LDB_SUCCESS:
/* it's a group object, continue */
break;
case LDB_ERR_NO_SUCH_ATTRIBUTE:
/* it is not a group object, just return */
return LDB_SUCCESS;
default:
/* an error occurred, return */
return ret;
}
ldb_debug(ldb_module_get_ctx(del_ctx->ctx->module),
LDB_DEBUG_TRACE,
"will delete %d ghost users from %d parents\n",
num_gh_vals, mbof->num_values);
for (i = 0; i < mbof->num_values; i++) {
valdn = ldb_dn_from_ldb_val(del_ctx,
ldb_module_get_ctx(del_ctx->ctx->module),
&mbof->values[i]);
if (!valdn || !ldb_dn_validate(valdn)) {
ldb_debug(ldb_module_get_ctx(del_ctx->ctx->module),
LDB_DEBUG_ERROR,
"Invalid dn value: [%s]",
(const char *)mbof->values[i].data);
}
ldb_debug(ldb_module_get_ctx(del_ctx->ctx->module),
LDB_DEBUG_TRACE,
"processing ghosts in parent [%s]\n",
(const char *) mbof->values[i].data);
for (j = 0; j < num_gh_vals; j++) {
ret = mbof_append_muop(del_ctx, &del_ctx->ghops,
&del_ctx->num_ghops,
LDB_FLAG_MOD_DELETE,
valdn,
(const char *) ghvals[j].data,
DB_GHOST);
if (ret != LDB_SUCCESS) {
return ret;
}
talloc_steal(del_ctx->ghops, valdn);
}
}
return LDB_SUCCESS;
}
static int mbof_del_fill_ghop(struct mbof_del_ctx *del_ctx,
struct ldb_message *entry)
{
struct ldb_message_element *ghel;
ghel = ldb_msg_find_element(entry, DB_GHOST);
if (ghel == NULL || ghel->num_values == 0) {
/* No ghel attribute, just return success */
return LDB_SUCCESS;
}
return mbof_del_fill_ghop_ex(del_ctx, entry,
ghel->values, ghel->num_values);
}
/* del memberuid attributes from parent groups */
static int mbof_del_muop(struct mbof_del_ctx *del_ctx)
{
struct ldb_context *ldb;
struct ldb_message *msg;
struct ldb_request *mod_req;
struct mbof_ctx *ctx;
int ret;
ctx = del_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
msg = ldb_msg_new(del_ctx);
if (!msg) return LDB_ERR_OPERATIONS_ERROR;
msg->dn = del_ctx->muops[del_ctx->cur_muop].dn;
msg->elements = del_ctx->muops[del_ctx->cur_muop].el;
msg->num_elements = 1;
ret = ldb_build_mod_req(&mod_req, ldb, del_ctx,
msg, NULL,
del_ctx, mbof_del_muop_callback,
ctx->req);
if (ret != LDB_SUCCESS) {
return ret;
}
return ldb_next_request(ctx->module, mod_req);
}
static int mbof_del_muop_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct mbof_del_ctx *del_ctx;
struct mbof_ctx *ctx;
int ret;
del_ctx = talloc_get_type(req->context, struct mbof_del_ctx);
ctx = del_ctx->ctx;
if (!ares) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
/* if the attribute was not present it means the db is not
* perfectly consistent but failing here is not useful
* anyway and missing entries cause no harm if we are trying
* to remove them anyway */
if (ares->error != LDB_SUCCESS &&
ares->error != LDB_ERR_NO_SUCH_ATTRIBUTE) {
return ldb_module_done(ctx->req,
ares->controls,
ares->response,
ares->error);
}
switch (ares->type) {
case LDB_REPLY_ENTRY:
/* shouldn't happen */
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
case LDB_REPLY_REFERRAL:
/* ignore */
break;
case LDB_REPLY_DONE:
del_ctx->cur_muop++;
if (del_ctx->cur_muop < del_ctx->num_muops) {
ret = mbof_del_muop(del_ctx);
}
/* see if we need to remove some ghost users */
else if (del_ctx->ghops) {
return mbof_del_ghop(del_ctx);
}
/* see if there are follow functions to run */
else if (del_ctx->follow_mod) {
return mbof_mod_add(del_ctx->follow_mod,
del_ctx->follow_mod->mb_add,
del_ctx->follow_mod->gh_add);
}
else {
return ldb_module_done(ctx->req,
ctx->ret_ctrls,
ctx->ret_resp,
LDB_SUCCESS);
}
if (ret != LDB_SUCCESS) {
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL, ret);
}
}
talloc_zfree(ares);
return LDB_SUCCESS;
}
/* del ghost attributes from parent groups */
static int mbof_del_ghop(struct mbof_del_ctx *del_ctx)
{
struct ldb_context *ldb;
struct ldb_message *msg;
struct ldb_request *mod_req;
struct mbof_ctx *ctx;
int ret;
ctx = del_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
msg = ldb_msg_new(del_ctx);
if (!msg) return LDB_ERR_OPERATIONS_ERROR;
msg->dn = del_ctx->ghops[del_ctx->cur_ghop].dn;
ret = ldb_msg_add(msg, del_ctx->ghops[del_ctx->cur_ghop].el,
LDB_FLAG_MOD_DELETE);
if (ret != LDB_SUCCESS) {
return ret;
}
/* Also expire any parent groups to force reloading direct members in
* case the ghost users we remove now were actually *also* direct members
* of the parent groups
*/
ret = ldb_msg_add_empty(msg, DB_CACHE_EXPIRE, LDB_FLAG_MOD_REPLACE, NULL);
if (ret != LDB_SUCCESS) {
return ret;
}
ret = ldb_msg_add_string(msg, DB_CACHE_EXPIRE, "1");
if (ret != LDB_SUCCESS) {
return ret;
}
ret = ldb_build_mod_req(&mod_req, ldb, del_ctx,
msg, NULL,
del_ctx, mbof_del_ghop_callback,
ctx->req);
if (ret != LDB_SUCCESS) {
return ret;
}
return ldb_next_request(ctx->module, mod_req);
}
static int mbof_del_ghop_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct mbof_del_ctx *del_ctx;
struct mbof_ctx *ctx;
int ret;
del_ctx = talloc_get_type(req->context, struct mbof_del_ctx);
ctx = del_ctx->ctx;
if (!ares) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
/* We must treat no such attribute as non-fatal b/c the entry
* might have been directly nested in the parent as well and
* updated with another replace operation.
*/
if (ares->error != LDB_SUCCESS &&
ares->error != LDB_ERR_NO_SUCH_ATTRIBUTE) {
return ldb_module_done(ctx->req,
ares->controls,
ares->response,
ares->error);
}
switch (ares->type) {
case LDB_REPLY_ENTRY:
/* shouldn't happen */
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
case LDB_REPLY_REFERRAL:
/* ignore */
break;
case LDB_REPLY_DONE:
del_ctx->cur_ghop++;
if (del_ctx->cur_ghop < del_ctx->num_ghops) {
ret = mbof_del_ghop(del_ctx);
}
/* see if there are follow functions to run */
else if (del_ctx->follow_mod) {
return mbof_mod_add(del_ctx->follow_mod,
del_ctx->follow_mod->mb_add,
del_ctx->follow_mod->gh_add);
}
else {
return ldb_module_done(ctx->req,
ctx->ret_ctrls,
ctx->ret_resp,
LDB_SUCCESS);
}
if (ret != LDB_SUCCESS) {
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL, ret);
}
}
talloc_zfree(ares);
return LDB_SUCCESS;
}
/* delop may carry on a lot of memory, so we need a function to clean up
* the payload without breaking the delop chain */
static void free_delop_contents(struct mbof_del_operation *delop)
{
talloc_zfree(delop->entry);
talloc_zfree(delop->parents);
talloc_zfree(delop->anc_ctx);
delop->num_parents = 0;
delop->cur_parent = 0;
}
/* mod operation */
/* A modify operation just implements either an add operation, or a delete
* operation or both (replace) in turn.
* 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
*
* 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,
const struct ldb_message_element *membel,
struct mbof_dn_array **_added,
struct mbof_dn_array **_removed);
static int mbof_mod_process_ghel(TALLOC_CTX *mem_ctx,
struct ldb_message *entry,
const struct ldb_message_element *ghel,
const struct ldb_message_element *inherited,
struct mbof_val_array **_added,
struct mbof_val_array **_removed);
static int mbof_mod_delete(struct mbof_mod_ctx *mod_ctx,
struct mbof_dn_array *del,
struct mbof_val_array *delgh);
static int mbof_fill_dn_array(TALLOC_CTX *memctx,
struct ldb_context *ldb,
const struct ldb_message_element *el,
struct mbof_dn_array **dn_array);
static int mbof_fill_vals_array(TALLOC_CTX *memctx,
unsigned int num_values,
struct ldb_val *values,
struct mbof_val_array **val_array);
static int mbof_fill_vals_array_el(TALLOC_CTX *memctx,
const struct ldb_message_element *el,
struct mbof_val_array **val_array);
static int memberof_mod(struct ldb_module *module, struct ldb_request *req)
{
struct ldb_message_element *el;
struct mbof_mod_ctx *mod_ctx;
struct mbof_ctx *ctx;
static const char *attrs[] = { DB_OC, DB_GHOST,
DB_MEMBER, DB_MEMBEROF, NULL};
struct ldb_context *ldb = ldb_module_get_ctx(module);
struct ldb_request *search;
int ret;
if (getenv("SSSD_UPGRADE_DB")) {
/* do not do anything during upgrade */
return ldb_next_request(module, req);
}
if (ldb_dn_is_special(req->op.mod.message->dn)) {
/* do not manipulate our control entries */
return ldb_next_request(module, req);
}
/* check if memberof is specified */
el = ldb_msg_find_element(req->op.mod.message, DB_MEMBEROF);
if (el) {
ldb_debug(ldb, LDB_DEBUG_ERROR,
"Error: the memberof attribute is readonly.");
return LDB_ERR_UNWILLING_TO_PERFORM;
}
/* check if memberuid is specified */
el = ldb_msg_find_element(req->op.mod.message, DB_MEMBERUID);
if (el) {
ldb_debug(ldb, LDB_DEBUG_ERROR,
"Error: the memberuid attribute is readonly.");
return LDB_ERR_UNWILLING_TO_PERFORM;
}
ctx = mbof_init(module, req);
if (!ctx) {
return LDB_ERR_OPERATIONS_ERROR;
}
mod_ctx = talloc_zero(ctx, struct mbof_mod_ctx);
if (!mod_ctx) {
talloc_free(ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
mod_ctx->ctx = ctx;
mod_ctx->msg = ldb_msg_copy(mod_ctx, req->op.mod.message);
if (!mod_ctx->msg) {
return LDB_ERR_OPERATIONS_ERROR;
}
mod_ctx->membel = ldb_msg_find_element(mod_ctx->msg, DB_MEMBER);
mod_ctx->ghel = ldb_msg_find_element(mod_ctx->msg, DB_GHOST);
/* continue with normal ops if there are no members and no ghosts */
if (mod_ctx->membel == NULL && mod_ctx->ghel == NULL) {
mod_ctx->terminate = true;
return mbof_orig_mod(mod_ctx);
}
/* can't do anything,
* must check first what's on the entry */
ret = ldb_build_search_req(&search, ldb, mod_ctx,
mod_ctx->msg->dn, LDB_SCOPE_BASE,
NULL, attrs, NULL,
mod_ctx, mbof_mod_callback,
req);
if (ret != LDB_SUCCESS) {
talloc_free(ctx);
return ret;
}
return ldb_request(ldb, search);
}
static int mbof_mod_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct mbof_mod_ctx *mod_ctx;
struct ldb_context *ldb;
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);
}
switch (ares->type) {
case LDB_REPLY_ENTRY:
if (mod_ctx->entry != NULL) {
ldb_debug(ldb, LDB_DEBUG_TRACE,
"Found multiple entries for (%s)",
ldb_dn_get_linearized(mod_ctx->msg->dn));
/* more than one entry per dn ?? db corrupted ? */
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
mod_ctx->entry = talloc_steal(mod_ctx, ares->message);
if (mod_ctx->entry == NULL) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
break;
case LDB_REPLY_REFERRAL:
/* ignore */
break;
case LDB_REPLY_DONE:
if (mod_ctx->entry == NULL) {
ldb_debug(ldb, LDB_DEBUG_TRACE, "Entry not found (%s)",
ldb_dn_get_linearized(mod_ctx->msg->dn));
/* this target does not exists, too bad! */
return ldb_module_done(ctx->req, NULL, NULL,
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;
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->igh == NULL) {
return LDB_ERR_OPERATIONS_ERROR;
}
mod_ctx->igh->mod_ctx = mod_ctx;
ret = hash_create_ex(1024, &mod_ctx->igh->inherited_gh, 0, 0, 0, 0,
hash_alloc, hash_free, mod_ctx, NULL, NULL);
if (ret != HASH_SUCCESS) {
return LDB_ERR_OPERATIONS_ERROR;
}
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 };
char *expression;
char *clean_dn;
const char *dn;
ctx = igh->mod_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
dn = ldb_dn_get_linearized(igh->mod_ctx->entry->dn);
if (!dn) {
talloc_free(ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
ret = sss_filter_sanitize(igh, dn, &clean_dn);
if (ret != 0) {
return LDB_ERR_OPERATIONS_ERROR;
}
expression = talloc_asprintf(igh,
"(&(%s=%s)(%s=%s))",
DB_OC, DB_GROUP_CLASS,
DB_MEMBEROF, clean_dn);
if (!expression) {
return LDB_ERR_OPERATIONS_ERROR;
}
talloc_zfree(clean_dn);
ret = ldb_build_search_req(&search, ldb, igh,
NULL,
LDB_SCOPE_SUBTREE,
expression, 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_message_element *el;
struct ldb_val *dupval;
int ret;
hash_value_t value;
hash_key_t key;
int i;
igh = talloc_get_type(req->context, struct mbof_mod_del_op);
ctx = igh->mod_ctx->ctx;
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:
el = ldb_msg_find_element(ares->message, DB_GHOST);
if (!el) {
break;
}
for (i=0; i < el->num_values; i++) {
key.type = HASH_KEY_STRING;
key.str = (char *) el->values[i].data;
if (hash_has_key(igh->inherited_gh, &key)) {
/* We already have this user. Don't re-add him */
continue;
}
dupval = talloc_zero(igh->inherited_gh, struct ldb_val);
if (dupval == NULL) {
return LDB_ERR_OPERATIONS_ERROR;
}
*dupval = ldb_val_dup(igh->inherited_gh, &el->values[i]);
if (dupval->data == NULL) {
return LDB_ERR_OPERATIONS_ERROR;
}
value.type = HASH_VALUE_PTR;
value.ptr = dupval;
ret = hash_enter(igh->inherited_gh, &key, &value);
if (ret != HASH_SUCCESS) {
return LDB_ERR_OPERATIONS_ERROR;
}
}
break;
case LDB_REPLY_REFERRAL:
/* ignore */
break;
case LDB_REPLY_DONE:
/* All the children are gathered, let's do the real
* modify operation
*/
ret = mbof_orig_mod(igh->mod_ctx);
if (ret != LDB_SUCCESS) {
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL, ret);
}
break;
}
talloc_zfree(ares);
return LDB_SUCCESS;
}
static int mbof_orig_mod(struct mbof_mod_ctx *mod_ctx)
{
struct ldb_request *mod_req;
struct ldb_context *ldb;
struct mbof_ctx *ctx;
int ret;
ctx = mod_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
ret = ldb_build_mod_req(&mod_req, ldb, ctx->req,
mod_ctx->msg, ctx->req->controls,
mod_ctx, mbof_orig_mod_callback,
ctx->req);
if (ret != LDB_SUCCESS) {
return ret;
}
return ldb_next_request(ctx->module, mod_req);
}
static int mbof_orig_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);
}
/* save real call stuff */
ctx->ret_ctrls = talloc_steal(ctx, ares->controls);
ctx->ret_resp = talloc_steal(ctx, ares->response);
if (!mod_ctx->terminate) {
/* next step */
if (mod_ctx->igh && mod_ctx->igh->inherited_gh &&
hash_count(mod_ctx->igh->inherited_gh) > 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);
}
}
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_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_element *el;
struct ldb_message *msg;
struct ldb_val *val;
struct ldb_val *dupval;
hash_value_t *values;
unsigned long num_values;
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;
}
ret = hash_values(mod_ctx->igh->inherited_gh, &num_values, &values);
if (ret != HASH_SUCCESS) {
return LDB_ERR_OPERATIONS_ERROR;
}
el->values = talloc_array(msg, struct ldb_val, num_values);
if (!el->values) {
return LDB_ERR_OPERATIONS_ERROR;
}
for (i = 0, j = 0; i < num_values; i++) {
val = talloc_get_type(values[i].ptr, struct ldb_val);
dupval = ldb_msg_find_val(mod_ctx->ghel, val);
if (dupval) {
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;
if (el->num_values == 0) {
/* nothing to do */
/* We cannot modify element which has 0 values */
msg->num_elements = 0;
}
mod_ctx->igh->mod_msg = msg;
mod_ctx->igh->el = el;
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;
struct mbof_ctx *ctx;
int ret;
ctx = mod_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
ret = mbof_mod_process_membel(mod_ctx, ldb, mod_ctx->entry, mod_ctx->membel,
&mod_ctx->mb_add, &mod_ctx->mb_remove);
if (ret != LDB_SUCCESS) {
return ret;
}
ret = mbof_mod_process_ghel(mod_ctx, mod_ctx->entry, mod_ctx->ghel,
mod_ctx->igh ? mod_ctx->igh->el : NULL,
&mod_ctx->gh_add, &mod_ctx->gh_remove);
if (ret != LDB_SUCCESS) {
return ret;
}
/* Process the operations */
/* if we have something to remove do it first */
if ((mod_ctx->mb_remove && mod_ctx->mb_remove->num) ||
(mod_ctx->gh_remove && mod_ctx->gh_remove->num)) {
return mbof_mod_delete(mod_ctx, mod_ctx->mb_remove, mod_ctx->gh_remove);
}
/* if there is nothing to remove and we have stuff to add
* do it right away */
if ((mod_ctx->mb_add && mod_ctx->mb_add->num) ||
(mod_ctx->gh_add && mod_ctx->gh_add->num)) {
return mbof_mod_add(mod_ctx, mod_ctx->mb_add, mod_ctx->gh_add);
}
/* the replacement function resulted in a null op,
* nothing to do, return happily */
*done = true;
return LDB_SUCCESS;
}
static int mbof_mod_process_membel(TALLOC_CTX *mem_ctx,
struct ldb_context *ldb,
struct ldb_message *entry,
const struct ldb_message_element *membel,
struct mbof_dn_array **_added,
struct mbof_dn_array **_removed)
{
const struct ldb_message_element *el;
struct mbof_dn_array *removed = NULL;
struct mbof_dn_array *added = NULL;
int i, j, ret;
if (!membel) {
/* Nothing to do.. */
return LDB_SUCCESS;
}
switch (membel->flags) {
case LDB_FLAG_MOD_ADD:
ret = mbof_fill_dn_array(mem_ctx, ldb, membel, &added);
if (ret != LDB_SUCCESS) {
return ret;
}
break;
case LDB_FLAG_MOD_DELETE:
if (membel->num_values == 0) {
el = ldb_msg_find_element(entry, DB_MEMBER);
} else {
el = membel;
}
if (!el) {
/* nothing to do really */
break;
}
ret = mbof_fill_dn_array(mem_ctx, ldb, el, &removed);
if (ret != LDB_SUCCESS) {
return ret;
}
break;
case LDB_FLAG_MOD_REPLACE:
removed = NULL;
el = ldb_msg_find_element(entry, DB_MEMBER);
if (el) {
ret = mbof_fill_dn_array(mem_ctx, ldb, el, &removed);
if (ret != LDB_SUCCESS) {
return ret;
}
}
added = NULL;
el = membel;
if (el) {
ret = mbof_fill_dn_array(mem_ctx, ldb, el, &added);
if (ret != LDB_SUCCESS) {
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++) {
for (j = 0; j < removed->num; j++) {
if (ldb_dn_compare(added->dns[i], removed->dns[j]) == 0) {
break;
}
}
if (j < removed->num) {
/* preexisting one, not removed, nor added */
for (; j+1 < removed->num; j++) {
removed->dns[j] = removed->dns[j+1];
}
removed->num--;
for (j = i; j+1 < added->num; j++) {
added->dns[j] = added->dns[j+1];
}
added->num--;
i--;
}
}
}
break;
default:
return LDB_ERR_OPERATIONS_ERROR;
}
*_added = added;
*_removed = removed;
return LDB_SUCCESS;
}
static int mbof_mod_process_ghel(TALLOC_CTX *mem_ctx,
struct ldb_message *entry,
const struct ldb_message_element *ghel,
const struct ldb_message_element *inherited,
struct mbof_val_array **_added,
struct mbof_val_array **_removed)
{
const struct ldb_message_element *el;
struct mbof_val_array *removed = NULL;
struct mbof_val_array *added = NULL;
int i, j, ret;
if (!ghel) {
/* Nothing to do.. */
return LDB_SUCCESS;
}
el = ldb_msg_find_element(entry, DB_MEMBEROF);
if (!el || el->num_values == 0) {
/* no memberof attributes ... */
return LDB_SUCCESS;
}
switch (ghel->flags) {
case LDB_FLAG_MOD_ADD:
ret = mbof_fill_vals_array_el(mem_ctx, ghel, &added);
if (ret != LDB_SUCCESS) {
return ret;
}
break;
case LDB_FLAG_MOD_DELETE:
if (ghel->num_values == 0) {
el = ldb_msg_find_element(entry, DB_GHOST);
} else {
el = ghel;
}
if (!el) {
/* nothing to do really */
break;
}
ret = mbof_fill_vals_array_el(mem_ctx, ghel, &removed);
if (ret != LDB_SUCCESS) {
return ret;
}
break;
case LDB_FLAG_MOD_REPLACE:
el = ldb_msg_find_element(entry, DB_GHOST);
if (el) {
ret = mbof_fill_vals_array_el(mem_ctx, el, &removed);
if (ret != LDB_SUCCESS) {
return ret;
}
}
el = ghel;
if (el) {
ret = mbof_fill_vals_array_el(mem_ctx, el, &added);
if (ret != LDB_SUCCESS) {
talloc_free(removed);
return ret;
}
}
if (inherited) {
ret = mbof_fill_vals_array_el(mem_ctx, inherited, &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++) {
for (j = 0; j < removed->num; j++) {
if (strcmp((const char *) added->vals[i].data,
(const char *) removed->vals[j].data) == 0) {
break;
}
}
if (j < removed->num) {
/* preexisting one, not removed, nor added */
for (; j+1 < removed->num; j++) {
removed->vals[j] = removed->vals[j+1];
}
removed->num--;
for (j = i; j+1 < added->num; j++) {
added->vals[j] = added->vals[j+1];
}
added->num--;
i--;
}
}
}
break;
default:
return LDB_ERR_OPERATIONS_ERROR;
}
*_added = added;
*_removed = removed;
return LDB_SUCCESS;
}
static int mbof_mod_add(struct mbof_mod_ctx *mod_ctx,
struct mbof_dn_array *ael,
struct mbof_val_array *addgh)
{
const struct ldb_message_element *el;
struct mbof_dn_array *parents;
struct mbof_add_ctx *add_ctx;
struct ldb_context *ldb;
struct mbof_ctx *ctx;
int i, ret;
ctx = mod_ctx->ctx;
ldb = ldb_module_get_ctx(ctx->module);
el = ldb_msg_find_element(mod_ctx->entry, DB_MEMBEROF);
/* all the parents + itself */
ret = mbof_fill_dn_array(mod_ctx, ldb, el, &parents);
if (ret != LDB_SUCCESS) {
return ret;
}
add_ctx = talloc_zero(mod_ctx, struct mbof_add_ctx);
if (!add_ctx) {
return LDB_ERR_OPERATIONS_ERROR;
}
add_ctx->ctx = ctx;
add_ctx->msg_dn = mod_ctx->msg->dn;
if (addgh != NULL) {
/* Build the memberuid add op */
ret = mbof_add_fill_ghop_ex(add_ctx, mod_ctx->entry,
parents, addgh->vals, addgh->num);
if (ret != LDB_SUCCESS) {
return ret;
}
}
if (ael != NULL && ael->num > 0) {
/* Add itself to the list of the parents to also get the memberuid */
parents->dns = talloc_realloc(parents, parents->dns,
struct ldb_dn *, parents->num + 1);
if (!parents->dns) {
return LDB_ERR_OPERATIONS_ERROR;
}
parents->dns[parents->num] = mod_ctx->entry->dn;
parents->num++;
/* Build the member-add array */
for (i = 0; i < ael->num; i++) {
ret = mbof_append_addop(add_ctx, parents, ael->dns[i]);
if (ret != LDB_SUCCESS) {
return ret;
}
}
return mbof_next_add(add_ctx->add_list);
}
return mbof_add_muop(add_ctx);
}
static int mbof_mod_delete(struct mbof_mod_ctx *mod_ctx,
struct mbof_dn_array *del,
struct mbof_val_array *delgh)
{
struct mbof_del_operation *first;
struct mbof_del_ctx *del_ctx;
struct mbof_ctx *ctx;
int i, ret;
ctx = mod_ctx->ctx;
del_ctx = talloc_zero(mod_ctx, struct mbof_del_ctx);
if (!del_ctx) {
return LDB_ERR_OPERATIONS_ERROR;
}
del_ctx->ctx = ctx;
del_ctx->is_mod = true;
/* create first entry */
/* the first entry is the parent of all entries and the one where we
* remove member from, it does not get the same treatment as others */
first = talloc_zero(del_ctx, struct mbof_del_operation);
if (!first) {
return LDB_ERR_OPERATIONS_ERROR;
}
del_ctx->first = first;
/* add followup function if we also have stuff to add */
if ((mod_ctx->mb_add && mod_ctx->mb_add->num > 0) ||
(mod_ctx->gh_add && mod_ctx->gh_add->num > 0)) {
del_ctx->follow_mod = mod_ctx;
}
first->del_ctx = del_ctx;
first->entry = mod_ctx->entry;
first->entry_dn = mod_ctx->entry->dn;
if (delgh != NULL) {
ret = mbof_del_fill_ghop_ex(del_ctx, del_ctx->first->entry,
delgh->vals, delgh->num);
if (ret != LDB_SUCCESS) {
return ret;
}
}
/* prepare del sets */
if (del != NULL && del->num > 0) {
for (i = 0; i < del->num; i++) {
ret = mbof_append_delop(first, del->dns[i]);
if (ret != LDB_SUCCESS) {
return ret;
}
}
/* now that sets are built, start processing */
return mbof_del_execute_op(first->children[0]);
}
/* No member processing, just delete ghosts */
return mbof_del_ghop(del_ctx);
}
static int mbof_fill_dn_array(TALLOC_CTX *memctx,
struct ldb_context *ldb,
const struct ldb_message_element *el,
struct mbof_dn_array **dn_array)
{
struct mbof_dn_array *ar;
struct ldb_dn *valdn;
int i;
ar = talloc_zero(memctx, struct mbof_dn_array);
if (!ar) {
return LDB_ERR_OPERATIONS_ERROR;
}
*dn_array = ar;
if (!el || el->num_values == 0) {
return LDB_SUCCESS;
}
ar->dns = talloc_array(ar, struct ldb_dn *, el->num_values);
if (!ar->dns) {
return LDB_ERR_OPERATIONS_ERROR;
}
ar->num = el->num_values;
for (i = 0; i < ar->num; i++) {
valdn = ldb_dn_from_ldb_val(ar, ldb, &el->values[i]);
if (!valdn || !ldb_dn_validate(valdn)) {
ldb_debug(ldb, LDB_DEBUG_TRACE, "Invalid dn value: [%s]",
(const char *)el->values[i].data);
return LDB_ERR_INVALID_DN_SYNTAX;
}
ar->dns[i] = valdn;
}
return LDB_SUCCESS;
}
static int mbof_fill_vals_array(TALLOC_CTX *memctx,
unsigned int num_values,
struct ldb_val *values,
struct mbof_val_array **val_array)
{
struct mbof_val_array *var = *val_array;
int i, vi;
if (var == NULL) {
var = talloc_zero(memctx, struct mbof_val_array);
if (!var) {
return LDB_ERR_OPERATIONS_ERROR;
}
*val_array = var;
}
if (values == NULL || num_values == 0) {
return LDB_SUCCESS;
}
/* We do not care about duplicate values now.
* They will be filtered later */
vi = 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;
}
/* FIXME - use ldb_val_dup() */
for (i = 0; i < num_values; i++) {
var->vals[vi].length = strlen((const char *) values[i].data);
var->vals[vi].data = (uint8_t *) talloc_strdup(var,
(const char *) values[i].data);
if (var->vals[vi].data == NULL) {
return LDB_ERR_OPERATIONS_ERROR;
}
vi++;
}
return LDB_SUCCESS;
}
static int mbof_fill_vals_array_el(TALLOC_CTX *memctx,
const struct ldb_message_element *el,
struct mbof_val_array **val_array)
{
if (el == NULL) {
return LDB_SUCCESS;
}
return mbof_fill_vals_array(memctx, el->num_values, el->values,
val_array);
}
/*************************
* Cleanup task routines *
*************************/
struct mbof_member {
struct mbof_member *prev;
struct mbof_member *next;
struct ldb_dn *dn;
const char *name;
bool orig_has_memberof;
bool orig_has_memberuid;
struct ldb_message_element *orig_members;
struct mbof_member **members;
hash_table_t *memberofs;
struct ldb_message_element *memuids;
enum { MBOF_GROUP_TO_DO = 0,
MBOF_GROUP_DONE,
MBOF_USER,
MBOF_ITER_ERROR } status;
};
struct mbof_rcmp_context {
struct ldb_module *module;
struct ldb_request *req;
struct mbof_member *user_list;
hash_table_t *user_table;
struct mbof_member *group_list;
hash_table_t *group_table;
};
static int mbof_steal_msg_el(TALLOC_CTX *memctx,
const char *name,
struct ldb_message *msg,
struct ldb_message_element **_dest)
{
struct ldb_message_element *src;
struct ldb_message_element *dest;
src = ldb_msg_find_element(msg, name);
if (!src) {
return LDB_ERR_NO_SUCH_ATTRIBUTE;
}
dest = talloc_zero(memctx, struct ldb_message_element);
if (!dest) {
return LDB_ERR_OPERATIONS_ERROR;
}
*dest = *src;
talloc_steal(dest, dest->name);
talloc_steal(dest, dest->values);
*_dest = dest;
return LDB_SUCCESS;
}
static int mbof_rcmp_usr_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_rcmp_search_groups(struct mbof_rcmp_context *ctx);
static int mbof_rcmp_grp_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_member_update(struct mbof_rcmp_context *ctx,
struct mbof_member *parent,
struct mbof_member *mem);
static bool mbof_member_iter(hash_entry_t *item, void *user_data);
static int mbof_add_memuid(struct mbof_member *grp, const char *user);
static int mbof_rcmp_update(struct mbof_rcmp_context *ctx);
static int mbof_rcmp_mod_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int memberof_recompute_task(struct ldb_module *module,
struct ldb_request *req)
{
struct ldb_context *ldb = ldb_module_get_ctx(module);
static const char *attrs[] = { DB_NAME, DB_MEMBEROF, NULL };
static const char *filter = "(objectclass=user)";
struct mbof_rcmp_context *ctx;
struct ldb_request *src_req;
int ret;
ctx = talloc_zero(req, struct mbof_rcmp_context);
if (!ctx) {
return LDB_ERR_OPERATIONS_ERROR;
}
ctx->module = module;
ctx->req = req;
ret = hash_create_ex(1024, &ctx->user_table, 0, 0, 0, 0,
hash_alloc, hash_free, ctx, NULL, NULL);
if (ret != HASH_SUCCESS) {
return LDB_ERR_OPERATIONS_ERROR;
}
ret = ldb_build_search_req(&src_req, ldb, ctx,
NULL, LDB_SCOPE_SUBTREE,
filter, attrs, NULL,
ctx, mbof_rcmp_usr_callback, ctx->req);
if (ret != LDB_SUCCESS) {
return ret;
}
return ldb_request(ldb, src_req);
}
static int mbof_rcmp_usr_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct mbof_rcmp_context *ctx;
struct mbof_member *usr;
hash_value_t value;
hash_key_t key;
const char *name;
int ret;
ctx = talloc_get_type(req->context, struct mbof_rcmp_context);
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:
usr = talloc_zero(ctx, struct mbof_member);
if (!usr) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
usr->status = MBOF_USER;
usr->dn = talloc_steal(usr, ares->message->dn);
name = ldb_msg_find_attr_as_string(ares->message, DB_NAME, NULL);
if (name) {
usr->name = talloc_steal(usr, name);
}
if (ldb_msg_find_element(ares->message, DB_MEMBEROF)) {
usr->orig_has_memberof = true;
}
DLIST_ADD(ctx->user_list, usr);
key.type = HASH_KEY_STRING;
key.str = discard_const(ldb_dn_get_linearized(usr->dn));
value.type = HASH_VALUE_PTR;
value.ptr = usr;
ret = hash_enter(ctx->user_table, &key, &value);
if (ret != HASH_SUCCESS) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
break;
case LDB_REPLY_REFERRAL:
/* ignore */
break;
case LDB_REPLY_DONE:
talloc_zfree(ares);
/* and now search groups */
return mbof_rcmp_search_groups(ctx);
}
talloc_zfree(ares);
return LDB_SUCCESS;
}
static int mbof_rcmp_search_groups(struct mbof_rcmp_context *ctx)
{
struct ldb_context *ldb = ldb_module_get_ctx(ctx->module);
static const char *attrs[] = { DB_MEMBEROF, DB_MEMBERUID,
DB_NAME, DB_MEMBER, NULL };
static const char *filter = "(objectclass=group)";
struct ldb_request *req;
int ret;
ret = hash_create_ex(1024, &ctx->group_table, 0, 0, 0, 0,
hash_alloc, hash_free, ctx, NULL, NULL);
if (ret != HASH_SUCCESS) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
ret = ldb_build_search_req(&req, ldb, ctx,
NULL, LDB_SCOPE_SUBTREE,
filter, attrs, NULL,
ctx, mbof_rcmp_grp_callback, ctx->req);
if (ret != LDB_SUCCESS) {
return ret;
}
return ldb_request(ldb, req);
}
static int mbof_rcmp_grp_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct ldb_context *ldb;
struct mbof_rcmp_context *ctx;
struct ldb_message_element *el;
struct mbof_member *iter;
struct mbof_member *grp;
hash_value_t value;
hash_key_t key;
const char *name;
int i, j;
int ret;
ctx = talloc_get_type(req->context, struct mbof_rcmp_context);
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:
grp = talloc_zero(ctx, struct mbof_member);
if (!grp) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
grp->status = MBOF_GROUP_TO_DO;
grp->dn = talloc_steal(grp, ares->message->dn);
grp->name = ldb_msg_find_attr_as_string(ares->message, DB_NAME, NULL);
name = ldb_msg_find_attr_as_string(ares->message, DB_NAME, NULL);
if (name) {
grp->name = talloc_steal(grp, name);
}
if (ldb_msg_find_element(ares->message, DB_MEMBEROF)) {
grp->orig_has_memberof = true;
}
if (ldb_msg_find_element(ares->message, DB_MEMBERUID)) {
grp->orig_has_memberuid = true;
}
ret = mbof_steal_msg_el(grp, DB_MEMBER,
ares->message, &grp->orig_members);
if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_ATTRIBUTE) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
DLIST_ADD(ctx->group_list, grp);
key.type = HASH_KEY_STRING;
key.str = discard_const(ldb_dn_get_linearized(grp->dn));
value.type = HASH_VALUE_PTR;
value.ptr = grp;
ret = hash_enter(ctx->group_table, &key, &value);
if (ret != HASH_SUCCESS) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
break;
case LDB_REPLY_REFERRAL:
/* ignore */
break;
case LDB_REPLY_DONE:
talloc_zfree(ares);
if (!ctx->group_list) {
/* no groups ? */
return ldb_module_done(ctx->req, NULL, NULL, LDB_SUCCESS);
}
/* for each group compute the members list */
for (iter = ctx->group_list; iter; iter = iter->next) {
el = iter->orig_members;
if (!el || el->num_values == 0) {
/* no members */
continue;
}
/* we have at most num_values group members */
iter->members = talloc_array(iter, struct mbof_member *,
el->num_values +1);
if (!iter->members) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
for (i = 0, j = 0; i < el->num_values; i++) {
key.type = HASH_KEY_STRING;
key.str = (char *)el->values[i].data;
ret = hash_lookup(ctx->user_table, &key, &value);
switch (ret) {
case HASH_SUCCESS:
iter->members[j] = (struct mbof_member *)value.ptr;
j++;
break;
case HASH_ERROR_KEY_NOT_FOUND:
/* not a user, see if it is a group */
ret = hash_lookup(ctx->group_table, &key, &value);
if (ret != HASH_SUCCESS) {
if (ret != HASH_ERROR_KEY_NOT_FOUND) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
}
if (ret == HASH_ERROR_KEY_NOT_FOUND) {
/* not a known user, nor a known group ?
give a warning an continue */
ldb_debug(ldb, LDB_DEBUG_ERROR,
"member attribute [%s] has no corresponding"
" entry!", key.str);
break;
}
iter->members[j] = (struct mbof_member *)value.ptr;
j++;
break;
default:
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
}
/* terminate */
iter->members[j] = NULL;
talloc_zfree(iter->orig_members);
}
/* now generate correct memberof tables */
while (ctx->group_list->status == MBOF_GROUP_TO_DO) {
grp = ctx->group_list;
/* move to end of list and mark as done.
* NOTE: this is not efficient, but will do for now */
DLIST_DEMOTE(ctx->group_list, grp, struct mbof_member *);
grp->status = MBOF_GROUP_DONE;
/* verify if members need updating */
if (!grp->members) {
continue;
}
for (i = 0; grp->members[i]; i++) {
ret = mbof_member_update(ctx, grp, grp->members[i]);
if (ret != LDB_SUCCESS) {
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
}
}
/* ok all done, now go on and modify the tree */
return mbof_rcmp_update(ctx);
}
talloc_zfree(ares);
return LDB_SUCCESS;
}
static int mbof_member_update(struct mbof_rcmp_context *ctx,
struct mbof_member *parent,
struct mbof_member *mem)
{
hash_value_t value;
hash_key_t key;
int ret;
/* ignore loops */
if (parent == mem) return LDB_SUCCESS;
key.type = HASH_KEY_STRING;
key.str = discard_const(ldb_dn_get_linearized(parent->dn));
if (!mem->memberofs) {
ret = hash_create_ex(32, &mem->memberofs, 0, 0, 0, 0,
hash_alloc, hash_free, mem, NULL, NULL);
if (ret != HASH_SUCCESS) {
return LDB_ERR_OPERATIONS_ERROR;
}
ret = HASH_ERROR_KEY_NOT_FOUND;
} else {
ret = hash_lookup(mem->memberofs, &key, &value);
if (ret != HASH_SUCCESS) {
if (ret != HASH_ERROR_KEY_NOT_FOUND) {
/* fatal error */
return LDB_ERR_OPERATIONS_ERROR;
}
}
}
if (ret == HASH_ERROR_KEY_NOT_FOUND) {
/* it's missing, update member */
value.type = HASH_VALUE_PTR;
value.ptr = parent;
ret = hash_enter(mem->memberofs, &key, &value);
if (ret != HASH_SUCCESS) {
return LDB_ERR_OPERATIONS_ERROR;
}
if (mem->status == MBOF_USER) {
/* add corresponding memuid to the group */
ret = mbof_add_memuid(parent, mem->name);
if (ret != LDB_SUCCESS) {
return ret;
}
}
/* if we updated a group, mark it as TO DO again */
if (mem->status == MBOF_GROUP_DONE) {
mem->status = MBOF_GROUP_TO_DO;
}
}
/* now see if the parent has memberofs to pass down */
if (parent->memberofs) {
ret = hash_iterate(parent->memberofs, mbof_member_iter, mem);
if (ret != HASH_SUCCESS) {
return LDB_ERR_OPERATIONS_ERROR;
}
if (mem->status == MBOF_ITER_ERROR) {
return LDB_ERR_OPERATIONS_ERROR;
}
}
/* finally, if it was made TO DO move it to the head */
if (mem->status == MBOF_GROUP_TO_DO) {
DLIST_PROMOTE(ctx->group_list, mem);
}
return LDB_SUCCESS;
}
static bool mbof_member_iter(hash_entry_t *item, void *user_data)
{
struct mbof_member *parent;
struct mbof_member *mem;
hash_value_t value;
int ret;
mem = talloc_get_type(user_data, struct mbof_member);
/* exclude self */
if (strcmp(item->key.str, ldb_dn_get_linearized(mem->dn)) == 0) {
return true;
}
/* check if we already have it */
ret = hash_lookup(mem->memberofs, &item->key, &value);
if (ret != HASH_SUCCESS) {
if (ret != HASH_ERROR_KEY_NOT_FOUND) {
/* fatal error */
mem->status = MBOF_ITER_ERROR;
return false;
}
/* was not already here, add it and mark group as TO DO */
ret = hash_enter(mem->memberofs, &item->key, &item->value);
if (ret != HASH_SUCCESS) {
return LDB_ERR_OPERATIONS_ERROR;
}
if (mem->status == MBOF_GROUP_DONE) {
mem->status = MBOF_GROUP_TO_DO;
}
if (mem->status == MBOF_USER) {
/* add corresponding memuid to the group */
parent = (struct mbof_member *)item->value.ptr;
ret = mbof_add_memuid(parent, mem->name);
if (ret != LDB_SUCCESS) {
mem->status = MBOF_ITER_ERROR;
return false;
}
}
}
return true;
}
static int mbof_add_memuid(struct mbof_member *grp, const char *user)
{
struct ldb_val *vals;
int n;
if (!grp->memuids) {
grp->memuids = talloc_zero(grp, struct ldb_message_element);
if (!grp->memuids) {
return LDB_ERR_OPERATIONS_ERROR;
}
grp->memuids->name = talloc_strdup(grp->memuids, DB_MEMBERUID);
if (!grp->memuids->name) {
return LDB_ERR_OPERATIONS_ERROR;
}
}
n = grp->memuids->num_values;
vals = talloc_realloc(grp->memuids,
grp->memuids->values,
struct ldb_val, n + 1);
if (!vals) {
return LDB_ERR_OPERATIONS_ERROR;
}
vals[n].data = (uint8_t *)talloc_strdup(vals, user);
vals[n].length = strlen(user);
grp->memuids->values = vals;
grp->memuids->num_values = n + 1;
return LDB_SUCCESS;
}
static int mbof_rcmp_update(struct mbof_rcmp_context *ctx)
{
struct ldb_context *ldb = ldb_module_get_ctx(ctx->module);
struct ldb_message_element *el;
struct ldb_message *msg = NULL;
struct ldb_request *req;
struct mbof_member *x = NULL;
hash_key_t *keys;
unsigned long count;
int flags;
int ret, i;
/* we process all users first and then all groups */
if (ctx->user_list) {
/* take the next entry and remove it from the list */
x = ctx->user_list;
DLIST_REMOVE(ctx->user_list, x);
}
else if (ctx->group_list) {
/* take the next entry and remove it from the list */
x = ctx->group_list;
DLIST_REMOVE(ctx->group_list, x);
}
else {
/* processing terminated, return */
ret = LDB_SUCCESS;
goto done;
}
msg = ldb_msg_new(ctx);
if (!msg) {
ret = LDB_ERR_OPERATIONS_ERROR;
goto done;
}
msg->dn = x->dn;
/* process memberof */
if (x->memberofs) {
ret = hash_keys(x->memberofs, &count, &keys);
if (ret != HASH_SUCCESS) {
ret = LDB_ERR_OPERATIONS_ERROR;
goto done;
}
if (x->orig_has_memberof) {
flags = LDB_FLAG_MOD_REPLACE;
} else {
flags = LDB_FLAG_MOD_ADD;
}
ret = ldb_msg_add_empty(msg, DB_MEMBEROF, flags, &el);
if (ret != LDB_SUCCESS) {
goto done;
}
el->values = talloc_array(el, struct ldb_val, count);
if (!el->values) {
ret = LDB_ERR_OPERATIONS_ERROR;
goto done;
}
el->num_values = count;
for (i = 0; i < count; i++) {
el->values[i].data = (uint8_t *)keys[i].str;
el->values[i].length = strlen(keys[i].str);
}
} else if (x->orig_has_memberof) {
ret = ldb_msg_add_empty(msg, DB_MEMBEROF, LDB_FLAG_MOD_DELETE, NULL);
if (ret != LDB_SUCCESS) {
goto done;
}
}
/* process memberuid */
if (x->memuids) {
if (x->orig_has_memberuid) {
flags = LDB_FLAG_MOD_REPLACE;
} else {
flags = LDB_FLAG_MOD_ADD;
}
ret = ldb_msg_add(msg, x->memuids, flags);
if (ret != LDB_SUCCESS) {
goto done;
}
}
else if (x->orig_has_memberuid) {
ret = ldb_msg_add_empty(msg, DB_MEMBERUID, LDB_FLAG_MOD_DELETE, NULL);
if (ret != LDB_SUCCESS) {
goto done;
}
}
ret = ldb_build_mod_req(&req, ldb, ctx, msg, NULL,
ctx, mbof_rcmp_mod_callback,
ctx->req);
if (ret != LDB_SUCCESS) {
goto done;
}
talloc_steal(req, msg);
/* fire next call */
return ldb_next_request(ctx->module, req);
done:
/* all users and groups have been processed */
return ldb_module_done(ctx->req, NULL, NULL, ret);
}
static int mbof_rcmp_mod_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct ldb_context *ldb;
struct mbof_rcmp_context *ctx;
ctx = talloc_get_type(req->context, struct mbof_rcmp_context);
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:
ldb_debug(ldb, LDB_DEBUG_TRACE, "Got an entry on a non search op ?!");
/* shouldn't happen */
talloc_zfree(ares);
return ldb_module_done(ctx->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
case LDB_REPLY_REFERRAL:
/* ignore */
talloc_zfree(ares);
break;
case LDB_REPLY_DONE:
talloc_zfree(ares);
/* update the next one */
return mbof_rcmp_update(ctx);
}
return LDB_SUCCESS;
}
/* module init code */
static int memberof_init(struct ldb_module *module)
{
struct ldb_context *ldb = ldb_module_get_ctx(module);
int ret;
/* set syntaxes for member and memberof so that comparisons in filters and
* such are done right */
ret = ldb_schema_attribute_add(ldb, DB_MEMBER, 0, LDB_SYNTAX_DN);
if (ret != 0) return LDB_ERR_OPERATIONS_ERROR;
ret = ldb_schema_attribute_add(ldb, DB_MEMBEROF, 0, LDB_SYNTAX_DN);
if (ret != 0) return LDB_ERR_OPERATIONS_ERROR;
return ldb_next_init(module);
}
const struct ldb_module_ops ldb_memberof_module_ops = {
.name = "memberof",
.init_context = memberof_init,
.add = memberof_add,
.modify = memberof_mod,
.del = memberof_del,
};
int ldb_init_module(const char *version)
{
#if defined(SSS_LDB_VERSION_CHECK) && defined(LDB_MODULE_CHECK_VERSION)
LDB_MODULE_CHECK_VERSION(version);
#endif /* SSS_LDB_VERSION_CHECK && LDB_MODULE_CHECK_VERSION */
return ldb_register_module(&ldb_memberof_module_ops);
}