/*
   SSSD memberof module

   Copyright (C) Simo Sorce <idra@samba.org> 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 <http://www.gnu.org/licenses/>.
*/

#include <string.h>
#include "ldb_module.h"
#include "util/util.h"
#include "dhash.h"

#define DB_MEMBER "member"
#define DB_MEMBEROF "memberof"
#define DB_MEMBERUID "memberuid"
#define DB_NAME "name"
#define DB_USER_CLASS "user"
#define DB_OC "objectClass"

#ifndef talloc_zfree
#define talloc_zfree(ptr) do { talloc_free(ptr); ptr = NULL; } while(0)
#endif

#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_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_mod_ctx *follow_mod;
    bool is_mod;
};

struct mbof_mod_ctx {
    struct mbof_ctx *ctx;

    const struct ldb_message_element *membel;
    struct ldb_message *entry;

    struct mbof_dn_array *to_add;

    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 int entry_is_user_object(struct ldb_message *entry)
{
    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;
    }

    /* see if this is a user */
    for (i = 0; i < el->num_values; i++) {
        val = &(el->values[i]);
        if (strncasecmp(DB_USER_CLASS, (char *)val->data, val->length) == 0) {
            return LDB_SUCCESS;
        }
    }

    return LDB_ERR_NO_SUCH_ATTRIBUTE;
}

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)
{
    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, DB_MEMBERUID);
        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 indeirect user members names.
 */

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 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_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_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);
            if (ret != LDB_SUCCESS) {
                return ret;
            }
        }

        break;

    case LDB_ERR_NO_SUCH_ATTRIBUTE:
        /* it is not a user object, continue */
        break;

    default:
        /* an error occured, return */
        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_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;
    }

    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
 */

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_muop(struct mbof_del_ctx *ctx);
static int mbof_del_muop_callback(struct ldb_request *req,
                                  struct ldb_reply *ares);


static int memberof_del(struct ldb_module *module, struct ldb_request *req)
{
    static const char *attrs[] = { DB_OC, DB_NAME,
                                   DB_MEMBER, DB_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)(%s=%s))",
                                 dn, DB_MEMBER, 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_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);
        }

        /* 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);
    }
    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);
        }
        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;
    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;
    }
    expression = talloc_asprintf(del_ctx,
                                 "(|(distinguishedName=%s)(%s=%s))",
                                 dn, DB_MEMBER, 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 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_del_operation *parent;
    struct mbof_del_ctx *del_ctx;
    struct mbof_ctx *ctx;
    struct mbof_dn_array *new_list;
    int i;

    del_ctx = delop->del_ctx;
    parent = delop->parent;
    ctx = del_ctx->ctx;

    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 occured, 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->muops, 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);
            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 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);

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;
    }

    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 there are follow functions to run */
    if (del_ctx->follow_mod) {
        return mbof_mod_add(del_ctx->follow_mod,
                            del_ctx->follow_mod->to_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 occured, 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->muops,
                                    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);
        if (ret != LDB_SUCCESS) {
            return ret;
        }
    }

    return LDB_SUCCESS;
}

/* del memberuid attributes to 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 (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:
        del_ctx->cur_muop++;
        if (del_ctx->cur_muop < del_ctx->num_muops) {
            ret = mbof_del_muop(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->to_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;
}



/* mod operation */

/* A modify operation just implements either an add operation, or a delete
 * operation or both (replace) in turn.
 * The only difference between a modify and a pure add or a pure delete is that
 * 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 */

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_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)
{
    struct ldb_message_element *el;
    struct mbof_mod_ctx *mod_ctx;
    struct mbof_ctx *ctx;
    static const char *attrs[] = {DB_MEMBER, DB_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);
    }

    /* 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;
    }

    /* continue with normal ops if there are no members */
    el = ldb_msg_find_element(mod_ctx->msg, DB_MEMBER);
    if (!el) {
        mod_ctx->terminate = true;
        return mbof_orig_mod(mod_ctx);
    }

    mod_ctx->membel = el;

    /* 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_orig_mod(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_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 */
        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)
{
    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, DB_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, DB_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_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, DB_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;
    add_ctx->msg_dn = mod_ctx->msg->dn;

    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 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;

    first->del_ctx = del_ctx;
    first->entry = mod_ctx->entry;
    first->entry_dn = mod_ctx->entry->dn;

    /* 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->follow_mod = mod_ctx;
    }

    /* 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)) {
            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;
}


/*************************
 * 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 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 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,
};