From 11e7110cee5c95bebe5f746287e7792e88cf6e83 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Wed, 28 Jan 2009 08:43:12 -0500 Subject: Add a memberof plugin so that we can properly apply the same group memberships as in IPA if necessary. This patch slightly modifies ldb to split out a modules header file without exposing the private headers. --- server/ldb_modules/ldb_module.h | 111 ++ server/ldb_modules/memberof.c | 2187 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 2298 insertions(+) create mode 100644 server/ldb_modules/ldb_module.h create mode 100644 server/ldb_modules/memberof.c (limited to 'server/ldb_modules') diff --git a/server/ldb_modules/ldb_module.h b/server/ldb_modules/ldb_module.h new file mode 100644 index 00000000..f41941da --- /dev/null +++ b/server/ldb_modules/ldb_module.h @@ -0,0 +1,111 @@ +/* + ldb database library + + Copyright (C) Simo Sorce 2008 + + ** NOTE! The following LGPL license applies to the ldb + ** library. This does NOT imply that all of Samba is released + ** under the LGPL + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ + +/* + * Name: ldb + * + * Component: ldb module header + * + * Description: defines ldb modules structures and helpers + * + */ + +#ifndef _LDB_MODULE_H_ +#define _LDB_MODULE_H_ + +struct ldb_context; +struct ldb_module; + +/* + these function pointers define the operations that a ldb module can intercept +*/ +struct ldb_module_ops { + const char *name; + int (*init_context) (struct ldb_module *); + int (*search)(struct ldb_module *, struct ldb_request *); /* search */ + int (*add)(struct ldb_module *, struct ldb_request *); /* add */ + int (*modify)(struct ldb_module *, struct ldb_request *); /* modify */ + int (*del)(struct ldb_module *, struct ldb_request *); /* delete */ + int (*rename)(struct ldb_module *, struct ldb_request *); /* rename */ + int (*request)(struct ldb_module *, struct ldb_request *); /* match any other operation */ + int (*extended)(struct ldb_module *, struct ldb_request *); /* extended operations */ + int (*start_transaction)(struct ldb_module *); + int (*end_transaction)(struct ldb_module *); + int (*del_transaction)(struct ldb_module *); + int (*sequence_number)(struct ldb_module *, struct ldb_request *); + void *private_data; +}; + + +/* MODULES specific headers -- SSS */ + +/* The following definitions come from lib/ldb/common/ldb_modules.c */ + +struct ldb_context *ldb_module_get_ctx(struct ldb_module *module); + +int ldb_next_request(struct ldb_module *module, struct ldb_request *request); +int ldb_next_start_trans(struct ldb_module *module); +int ldb_next_end_trans(struct ldb_module *module); +int ldb_next_del_trans(struct ldb_module *module); +int ldb_next_init(struct ldb_module *module); + +void ldb_set_errstring(struct ldb_context *ldb, const char *err_string); +void ldb_asprintf_errstring(struct ldb_context *ldb, const char *format, ...) PRINTF_ATTRIBUTE(2,3); +void ldb_reset_err_string(struct ldb_context *ldb); + +const char *ldb_default_modules_dir(void); + +int ldb_register_module(const struct ldb_module_ops *); + +typedef int (*ldb_connect_fn)(struct ldb_context *ldb, const char *url, + unsigned int flags, const char *options[], + struct ldb_module **module); + +struct ldb_backend_ops { + const char *name; + ldb_connect_fn connect_fn; +}; + +const char *ldb_default_modules_dir(void); + +int ldb_register_backend(const char *url_prefix, ldb_connect_fn); + +void *ldb_dso_load_symbol(struct ldb_context *ldb, const char *name, + const char *symbol); + +struct ldb_handle *ldb_handle_new(TALLOC_CTX *mem_ctx, struct ldb_context *ldb); + +int ldb_module_send_entry(struct ldb_request *req, + struct ldb_message *msg); + +int ldb_module_send_referral(struct ldb_request *req, + char *ref); + +int ldb_module_done(struct ldb_request *req, + struct ldb_control **ctrls, + struct ldb_extended *response, + int error); + +int ldb_mod_register_control(struct ldb_module *module, const char *oid); + +#endif diff --git a/server/ldb_modules/memberof.c b/server/ldb_modules/memberof.c new file mode 100644 index 00000000..f3942728 --- /dev/null +++ b/server/ldb_modules/memberof.c @@ -0,0 +1,2187 @@ +/* + SSSD memberof module + + Copyright (C) Simo Sorce 2008 + + 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 . +*/ + +#include "replace.h" +#include "talloc.h" +#include "ldb.h" +#include "ldb_errors.h" +#include "ldb_modules/ldb_module.h" + +#ifndef MAX +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +struct mbof_dn_array { + struct ldb_dn **dns; + int num; +}; + +struct mbof_dn { + struct mbof_dn *next; + struct ldb_dn *dn; +}; + +struct mbof_array_entry { + struct ldb_dn *dn; + struct ldb_message *pmsg; +}; + +struct mbof_array { + struct mbof_array_entry *entries; + int num; +}; + +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_add_ctx { + struct mbof_ctx *ctx; + + struct mbof_add_operation *add_list; + struct mbof_add_operation *current_op; +}; + +struct mbof_del_ancestors_ctx { + struct mbof_dn_array *orig_list; + struct mbof_array *new_list; + + struct ldb_message *entry; + int cur; +}; + +struct mbof_del_operation { + struct mbof_del_ctx *del_ctx; + struct mbof_del_operation *parent; + struct mbof_del_operation **children; + int num_children; + int cur_child; + + struct ldb_dn *entry_dn; + struct mbof_dn_array *rm_list; + + struct ldb_message *entry; + struct ldb_message **parents; + int num_parents; + int cur_parent; + + struct mbof_del_ancestors_ctx *anc_ctx; +}; + +struct mbof_del_ctx { + struct mbof_ctx *ctx; + + struct mbof_del_operation *first; + struct mbof_dn *history; + + void *followup_ctx; + int (*followup_fn)(void *); +}; + +struct mbof_mod_ctx { + struct mbof_ctx *ctx; + + const struct ldb_message_element *membel; + struct ldb_message *entry; + + struct mbof_dn_array *to_add; +}; + +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; +} + + + + +/* add operation */ + +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_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 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; + const 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)) { + /* do not manipulate our control entries */ + return ldb_next_request(module, req); + } + + /* fail operation if memberof is ever specified */ + el = ldb_msg_find_element(req->op.add.message, "memberof"); + if (el) { + /* FIXME: should we simply filter it instead ? */ + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + /* continue with normal ops if there are no members */ + el = ldb_msg_find_element(req->op.add.message, "member"); + if (!el) { + return ldb_next_request(module, req); + } + + 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; + + 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] = req->op.add.message->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)) { + return LDB_ERR_INVALID_DN_SYNTAX; + } + if (ldb_dn_compare(valdn, req->op.add.message->dn) == 0) { + ldb_set_errstring(ldb, "Adding self as member is not permitted!"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + ret = mbof_append_addop(add_ctx, parents, valdn); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + /* add original object */ + ret = ldb_build_add_req(&add_req, ldb, add_ctx, + req->op.add.message, 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_free(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->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 */ + talloc_free(ares); + return ldb_module_done(ctx->req, + ctx->ret_ctrls, + ctx->ret_resp, + LDB_SUCCESS); + } + + if (ret != LDB_SUCCESS) { + talloc_free(ares); + return ldb_module_done(ctx->req, NULL, NULL, ret); + } + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +static int mbof_next_add(struct mbof_add_operation *addop) +{ + static const char *attrs[] = {"member", "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 mbof_ctx *ctx; + int ret; + + addop = talloc_get_type(req->context, struct mbof_add_operation); + add_ctx = addop->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: + if (addop->entry != NULL) { + /* 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: + if (addop->entry == NULL) { + /* this target does not exists, too bad! */ + return ldb_module_done(ctx->req, NULL, NULL, + LDB_ERR_CONSTRAINT_VIOLATION); + } + + ret = mbof_add_operation(addop); + if (ret != LDB_SUCCESS) { + talloc_free(ares); + return ldb_module_done(ctx->req, NULL, NULL, ret); + } + } + + talloc_free(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; + + 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, "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) { + 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 { + /* 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, "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) { + return LDB_ERR_OPERATIONS_ERROR; + } + if (!ldb_dn_validate(valdn)) { + return LDB_ERR_INVALID_DN_SYNTAX; + } + ret = mbof_append_addop(add_ctx, parents, valdn); + 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, "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); +} + + + + +/* delete operations */ + +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_build_first_rm_list(struct ldb_context *ldb, + struct mbof_del_operation *first); +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 memberof_del(struct ldb_module *module, struct ldb_request *req) +{ + static const char *attrs[] = {"member", "memberof", NULL}; + struct ldb_context *ldb = ldb_module_get_ctx(module); + struct mbof_del_operation *first; + struct ldb_request *search; + char *expression; + const char *dn; + struct mbof_del_ctx *del_ctx; + struct mbof_ctx *ctx; + int ret; + + 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; + } + expression = talloc_asprintf(del_ctx, + "(|(distinguishedName=%s)(member=%s))", + dn, dn); + if (!expression) { + talloc_free(ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + 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_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; + + 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! */ + 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_free(ares); + return ldb_module_done(ctx->req, NULL, NULL, ret); + } + } + + talloc_free(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_free(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 any parents, fire a removal sequence */ + ret = mbof_del_cleanup_parents(del_ctx); + } + else if (ldb_msg_find_element(del_ctx->first->entry, "member")) { + /* if there are any children, fire a removal sequence */ + ret = mbof_del_cleanup_children(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_free(ares); + return ldb_module_done(ctx->req, NULL, NULL, ret); + } + + talloc_free(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, "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_free(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, "member")) { + /* if there are any children, fire a removal sequence */ + ret = mbof_del_cleanup_children(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_free(ares); + return ldb_module_done(ctx->req, NULL, NULL, ret); + } + + talloc_free(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); + + ret = mbof_build_first_rm_list(ldb, first); + if (ret != LDB_SUCCESS) { + return ret; + } + + el = ldb_msg_find_element(first->entry, "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)) { + 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_build_first_rm_list(struct ldb_context *ldb, + struct mbof_del_operation *first) +{ + const struct ldb_message_element *el; + struct mbof_dn_array *rm_list; + struct ldb_dn *valdn; + int i; + + rm_list = talloc_zero(first, struct mbof_dn_array); + if (!rm_list) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* create memberof removal list if part of any group */ + el = ldb_msg_find_element(first->entry, "memberof"); + if (el) { + rm_list->dns = talloc_array(rm_list, struct ldb_dn *, + el->num_values + 1); + if (!rm_list->dns) { + return LDB_ERR_OPERATIONS_ERROR; + } + rm_list->num = el->num_values + 1; + + for (i = 0; i < el->num_values; i++) { + valdn = ldb_dn_from_ldb_val(rm_list, ldb, &el->values[i]); + if (!valdn || !ldb_dn_validate(valdn)) { + return LDB_ERR_OPERATIONS_ERROR; + } + + rm_list->dns[i] = valdn; + } + } + + if (!rm_list->dns) { + rm_list->dns = talloc_array(rm_list, struct ldb_dn *, 1); + if (!rm_list->dns) { + return LDB_ERR_OPERATIONS_ERROR; + } + rm_list->num = 1; + i = 0; + } + + rm_list->dns[i] = first->entry_dn; + if (!rm_list->dns[i]) { + return LDB_ERR_OPERATIONS_ERROR; + } + + first->rm_list = rm_list; + + return LDB_SUCCESS; +} + +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; + static const char *attrs[] = {"member", "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; + } + expression = talloc_asprintf(del_ctx, + "(|(distinguishedName=%s)(member=%s))", + dn, dn); + if (!expression) { + return LDB_ERR_OPERATIONS_ERROR; + } + + 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 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; + + 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) { + /* 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_free(ares); + return LDB_SUCCESS; +} + +/* FIXME: very inefficient */ +static int mbof_del_execute_cont(struct mbof_del_operation *delop) +{ + struct mbof_del_ancestors_ctx *anc_ctx; + struct mbof_del_operation *parent; + struct mbof_del_ctx *del_ctx; + struct mbof_ctx *ctx; + struct ldb_context *ldb; + const struct ldb_message_element *el; + struct mbof_dn_array *orig_list; + struct mbof_array *new_list; + struct ldb_dn *valdn; + int i, j; + bool found; + + del_ctx = delop->del_ctx; + parent = delop->parent; + ctx = del_ctx->ctx; + ldb = ldb_module_get_ctx(ctx->module); + + anc_ctx = talloc_zero(delop, struct mbof_del_ancestors_ctx); + if (!anc_ctx) { + return LDB_ERR_OPERATIONS_ERROR; + } + delop->anc_ctx = anc_ctx; + + orig_list = talloc_zero(anc_ctx, struct mbof_dn_array); + if (!orig_list) { + return LDB_ERR_OPERATIONS_ERROR; + } + anc_ctx->orig_list = orig_list; + + new_list = talloc_zero(anc_ctx, struct mbof_array); + if (!new_list) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* create new memberof list */ + el = ldb_msg_find_element(delop->entry, "memberof"); + if (!el) { + /* can't be, if we get there it is because this entry + * had at least one parent */ + return LDB_ERR_OPERATIONS_ERROR; + } + + orig_list->num = el->num_values; + orig_list->dns = talloc_zero_array(orig_list, + struct ldb_dn *, + orig_list->num); + if (!orig_list->dns) { + return LDB_ERR_OPERATIONS_ERROR; + } + new_list->entries = talloc_zero_array(new_list, + struct mbof_array_entry, + orig_list->num); + if (!new_list->entries) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* add to the list only the values that are not on the rm_list */ + for (i = 0; i < el->num_values; i++) { + valdn = ldb_dn_from_ldb_val(orig_list, ldb, &el->values[i]); + if (!valdn) { + return LDB_ERR_OPERATIONS_ERROR; + } + orig_list->dns[i] = valdn; + for (j = 0; j < parent->rm_list->num; j++) { + if (ldb_dn_compare(valdn, parent->rm_list->dns[j]) == 0) + break; + } + if (j < parent->rm_list->num) { + continue; + } + new_list->entries[new_list->num].dn = valdn; + new_list->num++; + } + + /* now check that we have the right memberof for all direct parents */ + for (i = 0; i < delop->num_parents; i++) { + found = false; + for (j = 0; j < orig_list->num; j++) { + if (new_list->entries[j].pmsg) { + /* parent has already been paired, try next */ + continue; + } + if (new_list->entries[j].dn == NULL) { + /* we reached the end of current entries, pair new parent */ + new_list->entries[j].dn = delop->parents[i]->dn; + new_list->entries[j].pmsg = delop->parents[i]; + /* update also the number tracking the entries */ + new_list->num++; + /* mark we found an entry; */ + found = true; + break; + } + if (ldb_dn_compare(delop->parents[i]->dn, + new_list->entries[j].dn) == 0) { + /* pair parent and move to next */ + new_list->entries[j].pmsg = delop->parents[i]; + /* mark we found an entry; */ + found = true; + break; + } + } + if (!found && j == orig_list->num) { + /* houston we have a problem here */ + /* it seem we have more parents then allotted slots which is + * impossible because we have slots for a number of parents equal to + * the original number minus 1 and we are only potentially removing + * parents not adding. Return a bogus error, this must be an error + * somewhere else or the database has been corrupted */ + return LDB_ERR_UNWILLING_TO_PERFORM; + } + } + + /* attach the list to the operation */ + delop->anc_ctx->new_list = new_list; + + /* do we have any member in the list 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); + } + + /* 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_array *new_list; + static const char *attrs[] = {"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->entries[anc_ctx->cur].dn, + 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_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) { + /* 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, "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) { + 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->entries[j].dn) == 0) + break; + } + if (j < new_list->num) { + talloc_free(valdn); + continue; + } + new_list->entries = talloc_realloc(new_list, + new_list->entries, + struct mbof_array_entry, + new_list->num + 1); + if (!new_list->entries) { + return ldb_module_done(ctx->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + new_list->entries[new_list->num].dn = 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 >= new_list->num) { + /* ok, end of the story, proceed to modify the entry */ + ret = mbof_del_mod_entry(delop); + } else { + /* ok process the next one */ + ret = mbof_del_ancestors(delop); + } + + if (ret != LDB_SUCCESS) { + return ldb_module_done(ctx->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + } + + talloc_free(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_array *new_list; + struct ldb_request *mod_req; + struct ldb_message *msg; + struct ldb_message_element *el; + const char *val; + int i, j, ret; + + del_ctx = delop->del_ctx; + ctx = del_ctx->ctx; + ldb = ldb_module_get_ctx(ctx->module); + new_list = delop->anc_ctx->new_list; + + /* 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, "memberof", LDB_FLAG_MOD_REPLACE, &el); + if (ret != LDB_SUCCESS) { + return ret; + } + + el->values = talloc_array(msg, 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->entries[i].dn, msg->dn) == 0) + continue; + val = ldb_dn_get_linearized(new_list->entries[i].dn); + 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++; + } + el->num_values = j; + } + else { + ret = ldb_msg_add_empty(msg, "memberof", LDB_FLAG_MOD_DELETE, &el); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + 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 mbof_ctx *ctx; + int ret; + + delop = talloc_get_type(req->context, struct mbof_del_operation); + del_ctx = delop->del_ctx; + ctx = del_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_free(ares); + return ldb_module_done(ctx->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + case LDB_REPLY_REFERRAL: + /* ignore */ + break; + + case LDB_REPLY_DONE: + + ret = mbof_del_progeny(delop); + + if (ret != LDB_SUCCESS) { + talloc_free(ares); + return ldb_module_done(ctx->req, NULL, NULL, ret); + } + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +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 mbof_dn_array *rm_list; + struct mbof_dn_array *orig_list; + struct mbof_array *new_list; + struct ldb_context *ldb; + struct ldb_dn *valdn; + int i, j, ret; + + orig_list = delop->anc_ctx->orig_list; + new_list = delop->anc_ctx->new_list; + 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, "member"); + if (el) { + /* build rm_list for children */ + rm_list = talloc_zero(delop, struct mbof_dn_array); + if (!rm_list) { + return LDB_ERR_OPERATIONS_ERROR; + } + + rm_list->dns = talloc_zero_array(rm_list, + struct ldb_dn *, + orig_list->num); + if (!rm_list->dns) { + return LDB_ERR_OPERATIONS_ERROR; + } + + for (i = 0; i < orig_list->num; i++) { + for (j = 0; j < new_list->num; j++) { + if (ldb_dn_compare(orig_list->dns[i], + new_list->entries[j].dn) == 0) + break; + } + /* present in both, therefoire not deleted */ + if (j < new_list->num) continue; + + /* this was removed, add to the rm_list */ + rm_list->dns[rm_list->num] = orig_list->dns[i]; + rm_list->num++; + } + + delop->rm_list = rm_list; + + 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)) { + 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; + } + + if (nextop) { + return mbof_del_execute_op(nextop); + } + + /* see if there are follow functions to run */ + if (del_ctx->followup_fn) { + return del_ctx->followup_fn(del_ctx->followup_ctx); + } + + /* 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_ctx *ctx; + struct mbof_dn *save, *tmp; + + del_ctx = delop->del_ctx; + ctx = del_ctx->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 && + top->cur_child < top->num_children) { + + cop = top->children[top->cur_child]; + top->cur_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) { + /* it's a dup */ + continue; + } + + *nextop = cop; + return LDB_SUCCESS; + } + } + + /* we have no more ops */ + *nextop = NULL; + return LDB_SUCCESS; +} + + + +/* mod operation */ + +static int mbof_mod_callback(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_mod_process(struct mbof_mod_ctx *mod_ctx, bool *done); +static int mbof_mod_add(struct mbof_mod_ctx *mod_ctx, + struct mbof_dn_array *ael); +static int mbof_mod_followup(void *ctx); +static int mbof_mod_delete(struct mbof_mod_ctx *mod_ctx, + struct mbof_dn_array *del); +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 memberof_mod(struct ldb_module *module, struct ldb_request *req) +{ + const struct ldb_message_element *el; + struct mbof_mod_ctx *mod_ctx; + struct mbof_ctx *ctx; + static const char *attrs[] = {"member", "memberof", NULL}; + struct ldb_context *ldb = ldb_module_get_ctx(module); + struct ldb_request *search; + int ret; + + if (ldb_dn_is_special(req->op.mod.message->dn)) { + /* do not manipulate our control entries */ + return ldb_next_request(module, req); + } + + /* fail operation if memberof is ever specified */ + el = ldb_msg_find_element(req->op.mod.message, "memberof"); + if (el) { + /* FIXME: should we simply filter it instead ? */ + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + /* continue with normal ops if there are no members */ + el = ldb_msg_find_element(req->op.mod.message, "member"); + if (!el) { + return ldb_next_request(module, req); + } + + /* TODO: fail if this is not a group ? */ + + ctx = mbof_init(module, req); + if (!ctx) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* can't do anything, + * must check first what's on the entry */ + 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->membel = el; + + ret = ldb_build_search_req(&search, ldb, mod_ctx, + req->op.mod.message->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 mbof_ctx *ctx; + int ret; + + mod_ctx = talloc_get_type(req->context, struct mbof_mod_ctx); + ctx = 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: + if (mod_ctx->entry != NULL) { + /* 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) { + /* this target does not exists, too bad! */ + return ldb_module_done(ctx->req, NULL, NULL, + LDB_ERR_NO_SUCH_OBJECT); + } + + ret = mbof_orig_mod(mod_ctx); + if (ret != LDB_SUCCESS) { + talloc_free(ares); + return ldb_module_done(ctx->req, NULL, NULL, ret); + } + } + + talloc_free(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, + ctx->req->op.mod.message, 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; + bool done = false; + 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_free(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); + + /* next step */ + ret = mbof_mod_process(mod_ctx, &done); + if (ret != LDB_SUCCESS) { + talloc_free(ares); + return ldb_module_done(ctx->req, NULL, NULL, ret); + } + if (done) { + talloc_free(ares); + return ldb_module_done(ctx->req, + ctx->ret_ctrls, + ctx->ret_resp, + LDB_SUCCESS); + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +static int mbof_mod_process(struct mbof_mod_ctx *mod_ctx, bool *done) +{ + const struct ldb_message_element *el; + struct ldb_context *ldb; + struct mbof_ctx *ctx; + struct mbof_dn_array *removed; + struct mbof_dn_array *added; + int i, j, ret; + + ctx = mod_ctx->ctx; + ldb = ldb_module_get_ctx(ctx->module); + + switch (mod_ctx->membel->flags) { + case LDB_FLAG_MOD_ADD: + + ret = mbof_fill_dn_array(mod_ctx, ldb, mod_ctx->membel, &added); + if (ret != LDB_SUCCESS) { + return ret; + } + + return mbof_mod_add(mod_ctx, added); + + case LDB_FLAG_MOD_DELETE: + + if (mod_ctx->membel->num_values == 0) { + el = ldb_msg_find_element(mod_ctx->entry, "member"); + } else { + el = mod_ctx->membel; + } + + if (!el) { + /* nothing to do really */ + *done = true; + return LDB_SUCCESS; + } + + ret = mbof_fill_dn_array(mod_ctx, ldb, el, &removed); + if (ret != LDB_SUCCESS) { + return ret; + } + + return mbof_mod_delete(mod_ctx, removed); + + case LDB_FLAG_MOD_REPLACE: + + removed = NULL; + el = ldb_msg_find_element(mod_ctx->entry, "member"); + if (el) { + ret = mbof_fill_dn_array(mod_ctx, ldb, el, &removed); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + added = NULL; + el = mod_ctx->membel; + if (el) { + ret = mbof_fill_dn_array(mod_ctx, ldb, el, &added); + if (ret != LDB_SUCCESS) { + 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--; + } + } + } + + /* if we need to add something put it away so that it + * can be done after all delete operations are over */ + if (added && added->num) { + mod_ctx->to_add = added; + } + + /* if we have something to remove do it first */ + if (removed && removed->num) { + return mbof_mod_delete(mod_ctx, removed); + } + + /* if there is nothing to remove and we have stuff to add + * do it right away */ + if (mod_ctx->to_add) { + return mbof_mod_add(mod_ctx, added); + } + + /* the replacement function resulted in a null op, + * nothing to do, return happily */ + *done = true; + return LDB_SUCCESS; + } + + return LDB_ERR_OPERATIONS_ERROR; +} + +static int mbof_mod_followup(void *ctx) +{ + struct mbof_mod_ctx *mod_ctx; + + mod_ctx = talloc_get_type(ctx, struct mbof_mod_ctx); + + return mbof_mod_add(mod_ctx, mod_ctx->to_add); +} + +static int mbof_mod_add(struct mbof_mod_ctx *mod_ctx, + struct mbof_dn_array *ael) +{ + 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, "memberof"); + + /* all the parents + itself */ + ret = mbof_fill_dn_array(mod_ctx, ldb, el, &parents); + if (ret != LDB_SUCCESS) { + return ret; + } + + 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++; + + add_ctx = talloc_zero(mod_ctx, struct mbof_add_ctx); + if (!add_ctx) { + return LDB_ERR_OPERATIONS_ERROR; + } + add_ctx->ctx = ctx; + + 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); +} + +static int mbof_mod_delete(struct mbof_mod_ctx *mod_ctx, + struct mbof_dn_array *del) +{ + struct mbof_del_operation *first; + struct mbof_del_ctx *del_ctx; + struct ldb_context *ldb; + struct mbof_ctx *ctx; + int i, ret; + + ctx = mod_ctx->ctx; + ldb = ldb_module_get_ctx(ctx->module); + + del_ctx = talloc_zero(mod_ctx, struct mbof_del_ctx); + if (!del_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) { + return LDB_ERR_OPERATIONS_ERROR; + } + del_ctx->first = first; + + first->del_ctx = del_ctx; + first->entry = mod_ctx->entry; + first->entry_dn = mod_ctx->entry->dn; + + ret = mbof_build_first_rm_list(ldb, first); + if (ret != LDB_SUCCESS) { + return ret; + } + + /* prepare del sets */ + for (i = 0; i < del->num; i++) { + ret = mbof_append_delop(first, del->dns[i]); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + /* add followup function if we also have stuff to add */ + if (mod_ctx->to_add) { + del_ctx->followup_ctx = mod_ctx; + del_ctx->followup_fn = mbof_mod_followup; + } + + /* now that sets are built, start processing */ + return mbof_del_execute_op(first->children[0]); +} + +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)) { + return LDB_ERR_INVALID_DN_SYNTAX; + } + ar->dns[i] = valdn; + } + + return LDB_SUCCESS; +} + + + + +/* module init code */ + +static int memberof_init(struct ldb_module *module) +{ + 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, +}; -- cgit