#include "includes.h"
#include "ldb_module.h"
-#include "dlinklist.h"
+#include "util/dlinklist.h"
#include "dsdb/samdb/samdb.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+struct la_private {
+ struct la_context *la_list;
+};
struct la_op_store {
struct la_op_store *next;
struct la_op_store *prev;
enum la_op {LA_OP_ADD, LA_OP_DEL} op;
- struct ldb_dn *dn;
+ struct GUID guid;
char *name;
char *value;
};
};
struct la_context {
+ struct la_context *next, *prev;
const struct dsdb_schema *schema;
struct ldb_module *module;
struct ldb_request *req;
return NULL;
}
- ac->schema = dsdb_get_schema(ldb);
+ ac->schema = dsdb_get_schema(ldb, ac);
ac->module = module;
ac->req = req;
return ac;
}
+/*
+ turn a DN into a GUID
+ */
+static int la_guid_from_dn(struct la_context *ac, struct ldb_dn *dn, struct GUID *guid)
+{
+ int ret;
+ NTSTATUS status;
+
+ status = dsdb_get_extended_dn_guid(dn, guid, "GUID");
+ if (NT_STATUS_IS_OK(status)) {
+ return LDB_SUCCESS;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ DEBUG(4,(__location__ ": Unable to parse GUID for dn %s\n",
+ ldb_dn_get_linearized(dn)));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = dsdb_find_guid_by_dn(ldb_module_get_ctx(ac->module), dn, guid);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(4,(__location__ ": Failed to find GUID for dn %s\n",
+ ldb_dn_get_linearized(dn)));
+ return ret;
+ }
+ return LDB_SUCCESS;
+}
+
+
/* Common routine to handle reading the attributes and creating a
* series of modify requests */
static int la_store_op(struct la_context *ac,
enum la_op op, struct ldb_val *dn,
- const char *name)
+ const char *name)
{
struct ldb_context *ldb;
struct la_op_store *os;
struct ldb_dn *op_dn;
+ int ret;
ldb = ldb_module_get_ctx(ac->module);
os->op = op;
- os->dn = talloc_steal(os, op_dn);
+ ret = la_guid_from_dn(ac, op_dn, &os->guid);
+ talloc_free(op_dn);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT && ac->req->operation == LDB_DELETE) {
+ /* we are deleting an object, and we've found it has a
+ * forward link to a target that no longer
+ * exists. This is not an error in the delete, and we
+ * should just not do the deferred delete of the
+ * target attribute
+ */
+ talloc_free(os);
+ return LDB_SUCCESS;
+ }
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
os->name = talloc_strdup(os, name);
if (!os->name) {
return LDB_SUCCESS;
}
-static int la_op_search_callback(struct ldb_request *req,
- struct ldb_reply *ares);
-static int la_do_mod_request(struct la_context *ac);
-static int la_mod_callback(struct ldb_request *req,
- struct ldb_reply *ares);
+static int la_queue_mod_request(struct la_context *ac);
static int la_down_req(struct la_context *ac);
const struct dsdb_attribute *target_attr;
struct la_context *ac;
const char *attr_name;
+ struct ldb_control *ctrl;
int ret;
int i, j;
return ldb_next_request(module, req);
}
+ if (!(ctrl = ldb_request_get_control(req, DSDB_CONTROL_APPLY_LINKS))) {
+ /* don't do anything special for linked attributes, repl_meta_data has done it */
+ return ldb_next_request(module, req);
+ }
+ ctrl->critical = false;
+
ac = linked_attributes_init(module, req);
if (!ac) {
return LDB_ERR_OPERATIONS_ERROR;
"attribute %s is not a valid attribute in schema", el->name);
return LDB_ERR_OBJECT_CLASS_VIOLATION;
}
- /* We have a valid attribute, now find out if it is linked */
- if (schema_attr->linkID == 0) {
+ /* We have a valid attribute, now find out if it is a forward link */
+ if ((schema_attr->linkID == 0) || ((schema_attr->linkID & 1) == 1)) {
continue;
}
- if ((schema_attr->linkID & 1) == 1) {
- /* Odd is for the target. Illigal to modify */
- ldb_asprintf_errstring(ldb,
- "attribute %s must not be modified directly, it is a linked attribute", el->name);
- return LDB_ERR_UNWILLING_TO_PERFORM;
- }
-
/* Even link IDs are for the originating attribute */
target_attr = dsdb_attribute_by_linkID(ac->schema, schema_attr->linkID + 1);
if (!target_attr) {
if (ldb_dn_compare(ares->message->dn, ac->req->op.mod.message->dn) != 0) {
ldb_asprintf_errstring(ldb,
- "linked_attributes: %s is not the DN we were looking for", ldb_dn_get_linearized(ares->message->dn));
+ "linked_attributes: %s is not the DN we were looking for",
+ ldb_dn_get_linearized(ares->message->dn));
/* Guh? We only asked for this DN */
talloc_free(ares);
return ldb_module_done(ac->req, NULL, NULL,
if (ac->req->operation == LDB_ADD) {
/* Start the modifies to the backlinks */
- ret = la_do_mod_request(ac);
+ ret = la_queue_mod_request(ac);
if (ret != LDB_SUCCESS) {
return ldb_module_done(ac->req, NULL, NULL,
struct la_context *ac;
struct ldb_request *search_req;
const char **attrs;
+ struct ldb_control *ctrl;
int ret;
return ldb_next_request(module, req);
}
+ if (!(ctrl = ldb_request_get_control(req, DSDB_CONTROL_APPLY_LINKS))) {
+ /* don't do anything special for linked attributes, repl_meta_data has done it */
+ return ldb_next_request(module, req);
+ }
+ ctrl->critical = false;
+
ac = linked_attributes_init(module, req);
if (!ac) {
return LDB_ERR_OPERATIONS_ERROR;
"attribute %s is not a valid attribute in schema", el->name);
return LDB_ERR_OBJECT_CLASS_VIOLATION;
}
- /* We have a valid attribute, now find out if it is linked */
- if (schema_attr->linkID == 0) {
+ /* We have a valid attribute, now find out if it is a forward link
+ (Even link IDs are for the originating attribute) */
+ if ((schema_attr->linkID == 0) || ((schema_attr->linkID & 1) == 1)) {
continue;
}
-
- if ((schema_attr->linkID & 1) == 1) {
- /* Odd is for the target. Illegal to modify */
- ldb_asprintf_errstring(ldb,
- "attribute %s must not be modified directly, it is a linked attribute", el->name);
- return LDB_ERR_UNWILLING_TO_PERFORM;
- }
-
- /* Even link IDs are for the originating attribute */
-
+
/* Now find the target attribute */
target_attr = dsdb_attribute_by_linkID(ac->schema, schema_attr->linkID + 1);
if (!target_attr) {
return ret;
}
-/* delete, rename */
-static int linked_attributes_del(struct ldb_module *module, struct ldb_request *req)
-{
- struct ldb_context *ldb;
- struct ldb_request *search_req;
- struct la_context *ac;
- const char **attrs;
- WERROR werr;
- int ret;
-
- /* This gets complex: We need to:
- - Do a search for the entry
- - Wait for these result to appear
- - In the callback for the result, issue a modify
- request based on the linked attributes found
- - Wait for each modify result
- - Regain our sainity
- */
-
- ldb = ldb_module_get_ctx(module);
-
- ac = linked_attributes_init(module, req);
- if (!ac) {
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
- if (!ac->schema) {
- /* without schema, this doesn't make any sense */
- return ldb_next_request(module, req);
- }
-
- werr = dsdb_linked_attribute_lDAPDisplayName_list(ac->schema, ac, &attrs);
- if (!W_ERROR_IS_OK(werr)) {
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
- ret = ldb_build_search_req(&search_req, ldb, req,
- req->op.del.dn, LDB_SCOPE_BASE,
- "(objectClass=*)", attrs,
- NULL,
- ac, la_op_search_callback,
- req);
-
- if (ret != LDB_SUCCESS) {
- return ret;
- }
-
- talloc_steal(search_req, attrs);
-
- return ldb_next_request(module, search_req);
-}
-
-/* delete, rename */
-static int linked_attributes_rename(struct ldb_module *module, struct ldb_request *req)
+static int linked_attributes_fix_links(struct ldb_module *module,
+ struct ldb_dn *old_dn, struct ldb_dn *new_dn,
+ struct ldb_message_element *el, struct dsdb_schema *schema,
+ const struct dsdb_attribute *schema_attr)
{
- struct la_context *ac;
-
- /* This gets complex: We need to:
- - Do a search for the entry
- - Wait for these result to appear
- - In the callback for the result, issue a modify
- request based on the linked attributes found
- - Wait for each modify result
- - Regain our sainity
- */
-
- ac = linked_attributes_init(module, req);
- if (!ac) {
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
- if (!ac->schema) {
- /* without schema, this doesn't make any sense */
- return ldb_next_request(module, req);
- }
-
- /* start with the original request */
- return la_down_req(ac);
-}
-
-
-static int la_op_search_callback(struct ldb_request *req,
- struct ldb_reply *ares)
-{
- struct ldb_context *ldb;
- struct la_context *ac;
- const struct dsdb_attribute *schema_attr;
- const struct dsdb_attribute *target_attr;
- const struct ldb_message_element *el;
- const char *attr_name;
- int i, j;
- int ret;
-
- ac = talloc_get_type(req->context, struct la_context);
- ldb = ldb_module_get_ctx(ac->module);
-
- if (!ares) {
- return ldb_module_done(ac->req, NULL, NULL,
- LDB_ERR_OPERATIONS_ERROR);
- }
- if (ares->error != LDB_SUCCESS) {
- return ldb_module_done(ac->req, ares->controls,
- ares->response, ares->error);
+ unsigned int i;
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const struct dsdb_attribute *target;
+ const char *attrs[2];
+
+ target = dsdb_attribute_by_linkID(schema, schema_attr->linkID ^ 1);
+ if (target == NULL) {
+ /* there is no counterpart link to change */
+ return LDB_SUCCESS;
}
- /* Only entries are interesting, and we only want the olddn */
- switch (ares->type) {
- case LDB_REPLY_ENTRY:
- ret = ldb_dn_compare(ares->message->dn, req->op.search.base);
- if (ret != 0) {
- /* Guh? We only asked for this DN */
- talloc_free(ares);
- return ldb_module_done(ac->req, NULL, NULL,
- LDB_ERR_OPERATIONS_ERROR);
+ attrs[0] = target->lDAPDisplayName;
+ attrs[1] = NULL;
+
+ for (i=0; i<el->num_values; i++) {
+ struct dsdb_dn *dsdb_dn;
+ unsigned int j;
+ int ret;
+ struct ldb_result *res;
+ struct ldb_message *msg;
+ struct ldb_message_element *el2;
+
+ dsdb_dn = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], schema_attr->syntax->ldap_oid);
+ if (dsdb_dn == NULL) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_INVALID_DN_SYNTAX;
}
- if (ares->message->num_elements == 0) {
- /* only bother at all if there were some
- * linked attributes found */
- talloc_free(ares);
- return LDB_SUCCESS;
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res, dsdb_dn->dn,
+ attrs,
+ DSDB_SEARCH_SHOW_DELETED |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
+ DSDB_SEARCH_REVEAL_INTERNALS);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Linked attribute %s->%s between %s and %s - remote not found - %s",
+ el->name, target->lDAPDisplayName,
+ ldb_dn_get_linearized(old_dn),
+ ldb_dn_get_linearized(dsdb_dn->dn),
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
}
+ msg = res->msgs[0];
- switch (ac->req->operation) {
- case LDB_DELETE:
- ac->del_dn = talloc_steal(ac, ares->message->dn);
- break;
- case LDB_RENAME:
- ac->add_dn = talloc_steal(ac, ares->message->dn);
- ac->del_dn = talloc_steal(ac, ac->req->op.rename.olddn);
- break;
- default:
- talloc_free(ares);
- ldb_set_errstring(ldb,
- "operations must be delete or rename");
- return ldb_module_done(ac->req, NULL, NULL,
- LDB_ERR_OPERATIONS_ERROR);
+ if (msg->num_elements != 1 ||
+ ldb_attr_cmp(msg->elements[0].name, target->lDAPDisplayName) != 0) {
+ ldb_set_errstring(ldb, "Bad msg elements in linked_attributes_fix_links");
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
}
+ el2 = &msg->elements[0];
- for (i = 0; i < ares->message->num_elements; i++) {
- el = &ares->message->elements[i];
+ el2->flags = LDB_FLAG_MOD_REPLACE;
- schema_attr = dsdb_attribute_by_lDAPDisplayName(ac->schema, el->name);
- if (!schema_attr) {
- ldb_asprintf_errstring(ldb,
- "attribute %s is not a valid attribute"
- " in schema", el->name);
- talloc_free(ares);
- return ldb_module_done(ac->req, NULL, NULL,
- LDB_ERR_OBJECT_CLASS_VIOLATION);
+ /* find our DN in the values */
+ for (j=0; j<el2->num_values; j++) {
+ struct dsdb_dn *dsdb_dn2;
+ dsdb_dn2 = dsdb_dn_parse(msg, ldb, &el2->values[j], target->syntax->ldap_oid);
+ if (dsdb_dn2 == NULL) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_INVALID_DN_SYNTAX;
}
-
- /* Valid attribute, now find out if it is linked */
- if (schema_attr->linkID == 0) {
- /* Not a linked attribute, skip */
+ if (ldb_dn_compare(old_dn, dsdb_dn2->dn) != 0) {
continue;
}
-
- if ((schema_attr->linkID & 1) == 0) {
- /* Odd is for the target. */
- target_attr = dsdb_attribute_by_linkID(ac->schema, schema_attr->linkID + 1);
- if (!target_attr) {
- continue;
- }
- attr_name = target_attr->lDAPDisplayName;
- } else {
- target_attr = dsdb_attribute_by_linkID(ac->schema, schema_attr->linkID - 1);
- if (!target_attr) {
- continue;
- }
- attr_name = target_attr->lDAPDisplayName;
+ ret = ldb_dn_update_components(dsdb_dn2->dn, new_dn);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
}
- for (j = 0; j < el->num_values; j++) {
- ret = la_store_op(ac, LA_OP_DEL,
- &el->values[j],
- attr_name);
- /* for renames, ensure we add it back */
- if (ret == LDB_SUCCESS
- && ac->req->operation == LDB_RENAME) {
- ret = la_store_op(ac, LA_OP_ADD,
- &el->values[j],
- attr_name);
- }
- if (ret != LDB_SUCCESS) {
- talloc_free(ares);
- return ldb_module_done(ac->req,
- NULL, NULL, ret);
- }
- }
+ el2->values[j] = data_blob_string_const(
+ dsdb_dn_get_extended_linearized(el2->values, dsdb_dn2, 1));
}
- break;
-
- case LDB_REPLY_REFERRAL:
- /* ignore */
- break;
-
- case LDB_REPLY_DONE:
-
- talloc_free(ares);
-
+ ret = dsdb_check_single_valued_link(target, el2);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
- switch (ac->req->operation) {
- case LDB_DELETE:
- /* start the mod requests chain */
- ret = la_down_req(ac);
- if (ret != LDB_SUCCESS) {
- return ldb_module_done(ac->req, NULL, NULL, ret);
- }
- break;
- case LDB_RENAME:
-
- ret = la_do_mod_request(ac);
- if (ret != LDB_SUCCESS) {
- return ldb_module_done(ac->req, NULL, NULL,
- ret);
- }
-
+ ret = dsdb_module_modify(module, msg, DSDB_MODIFY_RELAX);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Linked attribute %s->%s between %s and %s - update failed - %s",
+ el->name, target->lDAPDisplayName,
+ ldb_dn_get_linearized(old_dn),
+ ldb_dn_get_linearized(dsdb_dn->dn),
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
return ret;
-
- default:
- talloc_free(ares);
- ldb_set_errstring(ldb,
- "operations must be delete or rename");
- return ldb_module_done(ac->req, NULL, NULL,
- LDB_ERR_OPERATIONS_ERROR);
}
- return LDB_SUCCESS;
}
- talloc_free(ares);
+ talloc_free(tmp_ctx);
return LDB_SUCCESS;
}
-/* do a linked attributes modify request */
-static int la_do_mod_request(struct la_context *ac)
+
+/* rename */
+static int linked_attributes_rename(struct ldb_module *module, struct ldb_request *req)
{
- struct ldb_message_element *ret_el;
- struct ldb_request *mod_req;
- struct ldb_message *new_msg;
- struct ldb_context *ldb;
+ struct ldb_result *res;
+ struct ldb_message *msg;
+ unsigned int i;
int ret;
-
- /* If we have no modifies in the queue, we are done! */
- if (!ac->ops) {
- return ldb_module_done(ac->req, ac->op_controls,
- ac->op_response, LDB_SUCCESS);
- }
-
- ldb = ldb_module_get_ctx(ac->module);
-
- /* Create the modify request */
- new_msg = ldb_msg_new(ac);
- if (!new_msg) {
- ldb_oom(ldb);
- return LDB_ERR_OPERATIONS_ERROR;
- }
- new_msg->dn = ac->ops->dn;
-
- if (ac->ops->op == LA_OP_ADD) {
- ret = ldb_msg_add_empty(new_msg, ac->ops->name,
- LDB_FLAG_MOD_ADD, &ret_el);
- } else {
- ret = ldb_msg_add_empty(new_msg, ac->ops->name,
- LDB_FLAG_MOD_DELETE, &ret_el);
- }
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct dsdb_schema *schema;
+ /*
+ - load the current msg
+ - find any linked attributes
+ - if its a link then find the target object
+ - modify the target linked attributes with the new DN
+ */
+ ret = dsdb_module_search_dn(module, req, &res, req->op.rename.olddn,
+ NULL, DSDB_SEARCH_SHOW_DELETED);
if (ret != LDB_SUCCESS) {
return ret;
}
- ret_el->values = talloc_array(new_msg, struct ldb_val, 1);
- if (!ret_el->values) {
+
+ schema = dsdb_get_schema(ldb, res);
+ if (!schema) {
ldb_oom(ldb);
return LDB_ERR_OPERATIONS_ERROR;
}
- ret_el->num_values = 1;
- if (ac->ops->op == LA_OP_ADD) {
- ret_el->values[0] = data_blob_string_const(ldb_dn_get_extended_linearized(new_msg, ac->add_dn, 1));
- } else {
- ret_el->values[0] = data_blob_string_const(ldb_dn_get_extended_linearized(new_msg, ac->del_dn, 1));
- }
-#if 0
- ldb_debug(ldb, LDB_DEBUG_WARNING,
- "link on %s %s: %s %s\n",
- ldb_dn_get_linearized(new_msg->dn), ret_el->name,
- ret_el->values[0].data, ac->ops->op == LA_OP_ADD ? "added" : "deleted");
-#endif
+ msg = res->msgs[0];
- /* use ac->ops as the mem_ctx so that the request will be freed
- * in the callback as soon as completed */
- ret = ldb_build_mod_req(&mod_req, ldb, ac->ops,
- new_msg,
- NULL,
- ac, la_mod_callback,
- ac->req);
- if (ret != LDB_SUCCESS) {
- return ret;
+ for (i=0; i<msg->num_elements; i++) {
+ struct ldb_message_element *el = &msg->elements[i];
+ const struct dsdb_attribute *schema_attr
+ = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ if (!schema_attr || schema_attr->linkID == 0) {
+ continue;
+ }
+ ret = linked_attributes_fix_links(module, msg->dn, req->op.rename.newdn, el,
+ schema, schema_attr);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(res);
+ return ret;
+ }
}
- talloc_steal(mod_req, new_msg);
- /* Run the new request */
- return ldb_next_request(ac->module, mod_req);
-}
+ talloc_free(res);
-static int la_mod_callback(struct ldb_request *req, struct ldb_reply *ares)
-{
- struct la_context *ac;
- struct ldb_context *ldb;
- struct la_op_store *os;
+ return ldb_next_request(module, req);
+}
- ac = talloc_get_type(req->context, struct la_context);
- ldb = ldb_module_get_ctx(ac->module);
- if (!ares) {
- return ldb_module_done(ac->req, NULL, NULL,
- LDB_ERR_OPERATIONS_ERROR);
- }
- if (ares->error != LDB_SUCCESS) {
- return ldb_module_done(ac->req, ares->controls,
- ares->response, ares->error);
- }
+/* queue a linked attributes modify request in the la_private
+ structure */
+static int la_queue_mod_request(struct la_context *ac)
+{
+ struct la_private *la_private =
+ talloc_get_type(ldb_module_get_private(ac->module), struct la_private);
- if (ares->type != LDB_REPLY_DONE) {
- ldb_set_errstring(ldb,
- "invalid ldb_reply_type in callback");
- talloc_free(ares);
- return ldb_module_done(ac->req, NULL, NULL,
- LDB_ERR_OPERATIONS_ERROR);
+ if (la_private == NULL) {
+ ldb_debug(ldb_module_get_ctx(ac->module), LDB_DEBUG_ERROR, __location__ ": No la_private transaction setup\n");
+ return LDB_ERR_OPERATIONS_ERROR;
}
- talloc_free(ares);
-
- os = ac->ops;
- DLIST_REMOVE(ac->ops, os);
+ talloc_steal(la_private, ac);
+ DLIST_ADD(la_private->la_list, ac);
- /* this frees the request too
- * DO NOT access 'req' after this point */
- talloc_free(os);
-
- return la_do_mod_request(ac);
+ return ldb_module_done(ac->req, ac->op_controls,
+ ac->op_response, LDB_SUCCESS);
}
/* Having done the original operation, then try to fix up all the linked attributes for modify and delete */
ac->op_response = talloc_steal(ac, ares->response);
/* If we have modfies to make, this is the time to do them for modify and delete */
- ret = la_do_mod_request(ac);
+ ret = la_queue_mod_request(ac);
if (ret != LDB_SUCCESS) {
return ldb_module_done(ac->req, NULL, NULL, ret);
}
talloc_free(ares);
- /* la_do_mod_request has already sent the callbacks */
+ /* la_queue_mod_request has already sent the callbacks */
return LDB_SUCCESS;
}
-/* Having done the original rename try to fix up all the linked attributes */
-static int la_rename_callback(struct ldb_request *req, struct ldb_reply *ares)
-{
- int ret;
- struct la_context *ac;
- struct ldb_request *search_req;
- const char **attrs;
- WERROR werr;
- struct ldb_context *ldb;
-
- ac = talloc_get_type(req->context, struct la_context);
- ldb = ldb_module_get_ctx(ac->module);
-
- if (!ares) {
- return ldb_module_done(ac->req, NULL, NULL,
- LDB_ERR_OPERATIONS_ERROR);
- }
- if (ares->error != LDB_SUCCESS) {
- return ldb_module_done(ac->req, ares->controls,
- ares->response, ares->error);
- }
-
- if (ares->type != LDB_REPLY_DONE) {
- ldb_set_errstring(ldb,
- "invalid ldb_reply_type in callback");
- talloc_free(ares);
- return ldb_module_done(ac->req, NULL, NULL,
- LDB_ERR_OPERATIONS_ERROR);
- }
-
- werr = dsdb_linked_attribute_lDAPDisplayName_list(ac->schema, ac, &attrs);
- if (!W_ERROR_IS_OK(werr)) {
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
- ret = ldb_build_search_req(&search_req, ldb, req,
- ac->req->op.rename.newdn, LDB_SCOPE_BASE,
- "(objectClass=*)", attrs,
- NULL,
- ac, la_op_search_callback,
- req);
-
- if (ret != LDB_SUCCESS) {
- return ret;
- }
-
- talloc_steal(search_req, attrs);
-
- if (ret == LDB_SUCCESS) {
- ret = ldb_request_add_control(search_req,
- LDB_CONTROL_EXTENDED_DN_OID,
- false, NULL);
- }
- if (ret != LDB_SUCCESS) {
- return ldb_module_done(ac->req, NULL, NULL,
- ret);
- }
-
- ac->op_controls = talloc_steal(ac, ares->controls);
- ac->op_response = talloc_steal(ac, ares->response);
-
- return ldb_next_request(ac->module, search_req);
-}
-
/* Having done the original add, then try to fix up all the linked attributes
This is done after the add so the links can get the extended DNs correctly.
ac, la_mod_del_callback,
ac->req);
break;
- case LDB_DELETE:
- ret = ldb_build_del_req(&down_req, ldb, ac,
- ac->req->op.del.dn,
- ac->req->controls,
- ac, la_mod_del_callback,
- ac->req);
- break;
- case LDB_RENAME:
- ret = ldb_build_rename_req(&down_req, ldb, ac,
- ac->req->op.rename.olddn,
- ac->req->op.rename.newdn,
- ac->req->controls,
- ac, la_rename_callback,
- ac->req);
- break;
default:
ret = LDB_ERR_OPERATIONS_ERROR;
}
return ldb_next_request(ac->module, down_req);
}
+/*
+ use the GUID part of an extended DN to find the target DN, in case
+ it has moved
+ */
+static int la_find_dn_target(struct ldb_module *module, struct la_context *ac,
+ struct GUID *guid, struct ldb_dn **dn)
+{
+ return dsdb_find_dn_by_guid(ldb_module_get_ctx(ac->module), ac, guid, dn);
+}
+
+/* apply one la_context op change */
+static int la_do_op_request(struct ldb_module *module, struct la_context *ac, struct la_op_store *op)
+{
+ struct ldb_message_element *ret_el;
+ struct ldb_request *mod_req;
+ struct ldb_message *new_msg;
+ struct ldb_context *ldb;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ /* Create the modify request */
+ new_msg = ldb_msg_new(ac);
+ if (!new_msg) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = la_find_dn_target(module, ac, &op->guid, &new_msg->dn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (op->op == LA_OP_ADD) {
+ ret = ldb_msg_add_empty(new_msg, op->name,
+ LDB_FLAG_MOD_ADD, &ret_el);
+ } else {
+ ret = ldb_msg_add_empty(new_msg, op->name,
+ LDB_FLAG_MOD_DELETE, &ret_el);
+ }
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret_el->values = talloc_array(new_msg, struct ldb_val, 1);
+ if (!ret_el->values) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret_el->num_values = 1;
+ if (op->op == LA_OP_ADD) {
+ ret_el->values[0] = data_blob_string_const(ldb_dn_get_extended_linearized(new_msg, ac->add_dn, 1));
+ } else {
+ ret_el->values[0] = data_blob_string_const(ldb_dn_get_extended_linearized(new_msg, ac->del_dn, 1));
+ }
+
+#if 0
+ ldb_debug(ldb, LDB_DEBUG_WARNING,
+ "link on %s %s: %s %s\n",
+ ldb_dn_get_linearized(new_msg->dn), ret_el->name,
+ ret_el->values[0].data, ac->ops->op == LA_OP_ADD ? "added" : "deleted");
+#endif
+
+ ret = ldb_build_mod_req(&mod_req, ldb, op,
+ new_msg,
+ NULL,
+ NULL,
+ ldb_op_default_callback,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ talloc_steal(mod_req, new_msg);
+
+ if (DEBUGLVL(4)) {
+ DEBUG(4,("Applying linked attribute change:\n%s\n",
+ ldb_ldif_message_string(ldb, op, LDB_CHANGETYPE_MODIFY, new_msg)));
+ }
+
+ /* Run the new request */
+ ret = ldb_next_request(module, mod_req);
+
+ /* we need to wait for this to finish, as we are being called
+ from the synchronous end_transaction hook of this module */
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(mod_req->handle, LDB_WAIT_ALL);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_WARNING, "Failed to apply linked attribute change '%s' %s\n",
+ ldb_errstring(ldb),
+ ldb_ldif_message_string(ldb, op, LDB_CHANGETYPE_MODIFY, new_msg));
+ }
+
+ return ret;
+}
+
+/* apply one set of la_context changes */
+static int la_do_mod_request(struct ldb_module *module, struct la_context *ac)
+{
+ struct la_op_store *op;
+
+ for (op = ac->ops; op; op=op->next) {
+ int ret = la_do_op_request(module, ac, op);
+ if (ret != LDB_SUCCESS) {
+ if (ret != LDB_ERR_NO_SUCH_OBJECT) {
+ return ret;
+ }
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+/*
+ we hook into the transaction operations to allow us to
+ perform the linked attribute updates at the end of the whole
+ transaction. This allows a forward linked attribute to be created
+ before the target is created, as long as the target is created
+ in the same transaction
+ */
+static int linked_attributes_start_transaction(struct ldb_module *module)
+{
+ /* create our private structure for this transaction */
+ struct la_private *la_private = talloc_get_type(ldb_module_get_private(module),
+ struct la_private);
+ talloc_free(la_private);
+ la_private = talloc(module, struct la_private);
+ if (la_private == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ la_private->la_list = NULL;
+ ldb_module_set_private(module, la_private);
+ return ldb_next_start_trans(module);
+}
+
+/*
+ on prepare commit we loop over our queued la_context structures
+ and apply each of them
+ */
+static int linked_attributes_prepare_commit(struct ldb_module *module)
+{
+ struct la_private *la_private =
+ talloc_get_type(ldb_module_get_private(module), struct la_private);
+ struct la_context *ac;
+
+ if (!la_private) {
+ /* prepare commit without begin_transaction - let someone else return the error, just don't segfault */
+ return ldb_next_prepare_commit(module);
+ }
+ /* walk the list backwards, to do the first entry first, as we
+ * added the entries with DLIST_ADD() which puts them at the
+ * start of the list */
+ for (ac = la_private->la_list; ac && ac->next; ac=ac->next) ;
+
+ for (; ac; ac=ac->prev) {
+ int ret;
+ ac->req = NULL;
+ ret = la_do_mod_request(module, ac);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed mod request ret=%d\n", ret));
+ talloc_free(la_private);
+ ldb_module_set_private(module, NULL);
+ return ret;
+ }
+ }
+
+ talloc_free(la_private);
+ ldb_module_set_private(module, NULL);
+
+ return ldb_next_prepare_commit(module);
+}
+
+static int linked_attributes_del_transaction(struct ldb_module *module)
+{
+ struct la_private *la_private =
+ talloc_get_type(ldb_module_get_private(module), struct la_private);
+ talloc_free(la_private);
+ ldb_module_set_private(module, NULL);
+ return ldb_next_del_trans(module);
+}
+
_PUBLIC_ const struct ldb_module_ops ldb_linked_attributes_module_ops = {
.name = "linked_attributes",
.add = linked_attributes_add,
.modify = linked_attributes_modify,
- .del = linked_attributes_del,
.rename = linked_attributes_rename,
+ .start_transaction = linked_attributes_start_transaction,
+ .prepare_commit = linked_attributes_prepare_commit,
+ .del_transaction = linked_attributes_del_transaction,
};