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