/*
SSSD memberof module
Copyright (C) Simo Sorce <idra@samba.org> 2008-2011
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* 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_OC "objectClass"
#ifndef talloc_zfree
#define talloc_zfree(ptr) do { talloc_free(ptr); ptr = NULL; } while(0)
#endif
#ifndef MAX
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
#endif
struct mbof_dn_array {
struct ldb_dn **dns;
int num;
};
struct mbof_dn {
struct mbof_dn *next;
struct ldb_dn *dn;
};
struct mbof_ctx {
struct ldb_module *module;
struct ldb_request *req;
struct ldb_control **ret_ctrls;
struct ldb_extended *ret_resp;
};
struct mbof_add_operation {
struct mbof_add_ctx *add_ctx;
struct mbof_add_operation *next;
struct mbof_dn_array *parents;
struct ldb_dn *entry_dn;
struct ldb_message *entry;
};
struct mbof_memberuid_op {
struct ldb_dn *dn;
struct ldb_message_element *el;
};
struct mbof_add_ctx {
struct mbof_ctx *ctx;
struct mbof_add_operation *add_list;
struct mbof_add_operation *current_op;
struct ldb_message *msg;
struct ldb_dn *msg_dn;
bool terminate;
struct mbof_dn *missing;
struct mbof_memberuid_op *muops;
int num_muops;
int cur_muop;
};
struct mbof_del_ancestors_ctx {
struct mbof_dn_array *new_list;
int num_direct;
int cur;
struct ldb_message *entry;
};
struct mbof_del_operation {
struct mbof_del_ctx *del_ctx;
struct mbof_del_operation *parent;
struct mbof_del_operation **children;
int num_children;
int next_child;
struct ldb_dn *entry_dn;
struct ldb_message *entry;
struct ldb_message **parents;
int num_parents;
int cur_parent;
struct mbof_del_ancestors_ctx *anc_ctx;
};
struct mbof_mod_ctx;
struct mbof_del_ctx {
struct mbof_ctx *ctx;
struct mbof_del_operation *first;
struct mbof_dn *history;
struct ldb_message **mus;
int num_mus;
struct mbof_memberuid_op *muops;
int num_muops;
int cur_muop;
struct mbof_mod_ctx *follow_mod;
bool is_mod;
};
struct mbof_mod_ctx {
struct mbof_ctx *ctx;
const struct ldb_message_element *membel;
struct ldb_message *entry;
struct mbof_dn_array *to_add;
struct ldb_message *msg;
bool terminate;
};
static struct mbof_ctx *mbof_init(struct ldb_module *module,
struct ldb_request *req)
{
struct mbof_ctx *ctx;
ctx = talloc_zero(req, struct mbof_ctx);
if (!ctx) {
return NULL;
}
ctx->module = module;
ctx->req = req;
return ctx;
}
static int entry_is_user_object(struct ldb_message *entry)
{
struct ldb_message_element *el;
struct ldb_val *val;
int i;
el = ldb_msg_find_element(entry, DB_OC);
if (!el) {
return LDB_ERR_OPERATIONS_ERROR;
}
/* see if this is a user */
for (i = 0; i < el->num_values; i++) {
val = &(el->values[i]);
if (strncasecmp(DB_USER_CLASS, (char *)val->data, val->length) == 0) {
return LDB_SUCCESS;
}
}
return LDB_ERR_NO_SUCH_ATTRIBUTE;
}
static int mbof_append_muop(TALLOC_CTX *memctx,
struct mbof_memberuid_op **_muops,
int *_num_muops,
int flags,
struct ldb_dn *parent,
const char *name,
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 indeirect user members names.
*/
static int mbof_append_addop(struct mbof_add_ctx *add_ctx,
struct mbof_dn_array *parents,
struct ldb_dn *entry_dn)
{
struct mbof_add_operation *lastop = NULL;
struct mbof_add_operation *addop;
/* test if this is a duplicate */
/* FIXME: this is not efficient */
if (add_ctx->add_list) {
do {
if (lastop) {
lastop = lastop->next;
} else {
lastop = add_ctx->add_list;
}
/* FIXME: check if this is right, might have to compare parents */
if (ldb_dn_compare(lastop->entry_dn, entry_dn) == 0) {
/* duplicate found */
return LDB_SUCCESS;
}
} while (lastop->next);
}
addop = talloc_zero(add_ctx, struct mbof_add_operation);
if (!addop) {
return LDB_ERR_OPERATIONS_ERROR;
}
addop->add_ctx = add_ctx;
addop->parents = parents;
addop->entry_dn = entry_dn;
if (add_ctx->add_list) {
lastop->next = addop;
} else {
add_ctx->add_list = addop;
}
return LDB_SUCCESS;
}
static int memberof_recompute_task(struct ldb_module *module,
struct ldb_request *req);
static int mbof_add_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_next_add(struct mbof_add_operation *addop);
static int mbof_next_add_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_add_operation(struct mbof_add_operation *addop);
static int mbof_add_missing(struct mbof_add_ctx *add_ctx, struct ldb_dn *dn);
static int mbof_add_cleanup(struct mbof_add_ctx *add_ctx);
static int mbof_add_cleanup_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int mbof_add_muop(struct mbof_add_ctx *add_ctx);
static int mbof_add_muop_callback(struct ldb_request *req,
struct ldb_reply *ares);
static int memberof_add(struct ldb_module *module, struct ldb_request *req)
{
struct ldb_context *ldb = ldb_module_get_ctx(module);
struct mbof_add_ctx *add_ctx;
struct mbof_ctx *ctx;
struct ldb_request *add_req;
struct ldb_message_element *el;
struct mbof_dn_array *parents;
struct ldb_dn *valdn;
int i, ret;
if (ldb_dn_is_special(req->op.add.message->dn)) {
if (strcmp("@MEMBEROF-REBUILD",
ldb_dn_get_linearized(req->op.add.message->dn)) == 0) {
return memberof_recompute_task(module, req);
}
/* do not manipulate other control entries */
return ldb_next_request(module, req);
}
/* check if memberof is specified */
el = ldb_msg_find_element(req->op.add.message, DB_MEMBEROF);
if (el) {
ldb_debug(ldb, LDB_DEBUG_ERROR,
"Error: the memberof attribute is readonly.");
return LDB_ERR_UNWILLING_TO_PERFORM;
}
/* check if memberuid is specified */
el = ldb_msg_find_element(req->op.add.message, DB_MEMBERUID);
if (el) {
ldb_debug(ldb, LDB_DEBUG_ERROR,
"Error: the memberuid attribute is readonly.");
return LDB_ERR_UNWILLING_TO_PERFORM;
}
ctx = mbof_init(module, req);
if (!ctx) {
return LDB_ERR_OPERATIONS_ERROR;
}
add_ctx = talloc_zero(ctx, struct mbof_add_ctx);
if (!add_ctx) {
return LDB_ERR_OPERATIONS_ERROR;
}
add_ctx->ctx = ctx;
add_ctx->msg = ldb_msg_copy(add_ctx, req->op.add.message);
if (!add_ctx->msg) {
return LDB_ERR_OPERATIONS_ERROR;
}
add_ctx->msg_dn = add_ctx->msg->dn;
/* continue with normal ops if there are no members */
el = ldb_msg_find_element(add_ctx->msg, DB_MEMBER);
if (!el) {
add_ctx->terminate = true;
goto done;
}
parents = talloc_zero(add_ctx, struct mbof_dn_array);
if (!parents) {
return LDB_ERR_OPERATIONS_ERROR;
}
parents->dns = talloc_array(parents, struct ldb_dn *, 1);
if (!parents->dns) {
return LDB_ERR_OPERATIONS_ERROR;
}
parents->dns[0] = add_ctx->msg_dn;
parents->num = 1;
/* process new members */
/* check we are not adding ourselves as member as well */
for (i = 0; i < el->num_values; i++) {
valdn = ldb_dn_from_ldb_val(add_ctx, ldb, &el->values[i]);
if (!valdn || !ldb_dn_validate(valdn)) {
ldb_debug(ldb, LDB_DEBUG_ERROR, "Invalid dn value: [%s]",
(const char *)el->values[i].data);
return LDB_ERR_INVALID_DN_SYNTAX;
}
if (ldb_dn_compare(valdn, req->op.add.message->dn) == 0) {
ldb_debug(ldb, LDB_DEBUG_ERROR,
"Adding self as member is not permitted! Skipping");
continue;
}
ret = mbof_append_addop(add_ctx, parents, valdn);
|