4 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 * Component: ldb linked_attributes module
25 * Description: Module to ensure linked attribute pairs remain in sync
27 * Author: Andrew Bartlett
31 #include "ldb/include/ldb.h"
32 #include "ldb/include/ldb_errors.h"
33 #include "ldb/include/ldb_private.h"
34 #include "dsdb/samdb/samdb.h"
36 struct linked_attributes_context {
37 struct ldb_module *module;
38 struct ldb_handle *handle;
39 struct ldb_request *orig_req;
41 struct ldb_request **down_req;
43 int finished_requests;
45 const char **linked_attrs;
48 static struct linked_attributes_context *linked_attributes_init_handle(struct ldb_request *req,
49 struct ldb_module *module)
51 struct linked_attributes_context *ac;
54 h = talloc_zero(req, struct ldb_handle);
56 ldb_set_errstring(module->ldb, "Out of Memory");
62 ac = talloc_zero(h, struct linked_attributes_context);
64 ldb_set_errstring(module->ldb, "Out of Memory");
80 /* Common routine to handle reading the attributes and creating a
81 * series of modify requests */
83 static int setup_modifies(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
84 struct linked_attributes_context *ac,
85 struct ldb_message *msg,
86 struct ldb_dn *olddn, struct ldb_dn *newdn)
88 int i, j, ret = LDB_SUCCESS;
89 const struct dsdb_schema *schema = dsdb_get_schema(ldb);
90 /* Look up each of the returned attributes */
91 /* Find their schema */
92 /* And it is an actual entry: now create a series of modify requests */
93 for (i=0; i < msg->num_elements; i++) {
95 const struct dsdb_attribute *target_attr;
96 const struct ldb_message_element *el = &msg->elements[i];
97 const struct dsdb_attribute *schema_attr
98 = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
100 ldb_asprintf_errstring(ldb,
101 "attribute %s is not a valid attribute in schema", el->name);
102 return LDB_ERR_OBJECT_CLASS_VIOLATION;
104 /* We have a valid attribute, but if it's not linked they maybe we just got an extra return on our search... */
105 if (schema_attr->linkID == 0) {
109 /* Depending on which direction this link is in, we need to find it's partner */
110 if ((schema_attr->linkID & 1) == 1) {
111 otherid = schema_attr->linkID - 1;
113 otherid = schema_attr->linkID + 1;
116 /* Now find the target attribute */
117 target_attr = dsdb_attribute_by_linkID(schema, otherid);
119 ldb_asprintf_errstring(ldb,
120 "attribute %s does not have valid link target", el->name);
121 return LDB_ERR_OBJECT_CLASS_VIOLATION;
124 /* For each value being moded, we need to setup the modify */
125 for (j=0; j < el->num_values; j++) {
126 struct ldb_message_element *ret_el;
127 struct ldb_request *new_req;
128 /* Create the modify request */
129 struct ldb_message *new_msg = ldb_msg_new(ac->down_req);
132 return LDB_ERR_OPERATIONS_ERROR;
134 new_msg->dn = ldb_dn_new(new_msg, ldb, (char *)el->values[j].data);
136 ldb_asprintf_errstring(ldb,
137 "attribute %s value %s was not a valid DN", msg->elements[i].name,
139 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
143 ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
144 LDB_FLAG_MOD_DELETE, &ret_el);
145 if (ret != LDB_SUCCESS) {
148 ret_el->values = talloc_array(new_msg, struct ldb_val, 1);
149 if (!ret_el->values) {
151 return LDB_ERR_OPERATIONS_ERROR;
153 ret_el->values[0] = data_blob_string_const(ldb_dn_get_linearized(olddn));
154 ret_el->num_values = 1;
158 ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
159 LDB_FLAG_MOD_ADD, &ret_el);
160 if (ret != LDB_SUCCESS) {
163 ret_el->values = talloc_array(new_msg, struct ldb_val, 1);
164 if (!ret_el->values) {
166 return LDB_ERR_OPERATIONS_ERROR;
168 ret_el->values[0] = data_blob_string_const(ldb_dn_get_linearized(newdn));
169 ret_el->num_values = 1;
172 ret = ldb_build_mod_req(&new_req, ldb, ac->down_req,
177 if (ret != LDB_SUCCESS) {
181 talloc_steal(new_req, new_msg);
183 ldb_set_timeout_from_prev_req(ldb, ac->orig_req, new_req);
185 /* Now add it to the list */
186 ac->down_req = talloc_realloc(ac, ac->down_req,
187 struct ldb_request *, ac->num_requests + 1);
190 return LDB_ERR_OPERATIONS_ERROR;
192 ac->down_req[ac->num_requests] = new_req;
195 /* Run the new request */
196 ret = ldb_next_request(ac->module, new_req);
197 if (ret != LDB_SUCCESS) {
206 static int linked_attributes_add(struct ldb_module *module, struct ldb_request *req)
209 struct linked_attributes_context *ac;
211 const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
213 /* without schema, this doesn't make any sense */
214 return ldb_next_request(module, req);
217 if (ldb_dn_is_special(req->op.mod.message->dn)) {
218 /* do not manipulate our control entries */
219 return ldb_next_request(module, req);
223 ac = linked_attributes_init_handle(req, module);
225 return LDB_ERR_OPERATIONS_ERROR;
228 /* prepare the first operation */
229 ac->down_req = talloc_realloc(ac, ac->down_req,
230 struct ldb_request *, 1);
232 ldb_oom(ac->module->ldb);
233 return LDB_ERR_OPERATIONS_ERROR;
236 ac->down_req[0] = talloc(ac->down_req, struct ldb_request);
237 if (!ac->down_req[0]) {
238 ldb_oom(ac->module->ldb);
239 return LDB_ERR_OPERATIONS_ERROR;
241 *(ac->down_req[0]) = *req; /* copy the request */
245 /* Run the original request */
246 ret = ldb_next_request(module, ac->down_req[0]);
247 if (ret != LDB_SUCCESS) {
251 /* Need to ensure we only have forward links being specified */
252 for (i=0; i < req->op.add.message->num_elements; i++) {
253 const struct ldb_message_element *el = &req->op.add.message->elements[i];
254 const struct dsdb_attribute *schema_attr
255 = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
257 ldb_asprintf_errstring(module->ldb,
258 "attribute %s is not a valid attribute in schema", req->op.add.message->elements[i].name);
259 return LDB_ERR_OBJECT_CLASS_VIOLATION;
261 /* We have a valid attribute, not find out if it is linked */
262 if (schema_attr->linkID == 0) {
266 if ((schema_attr->linkID & 1) == 1) {
267 /* Odd is for the target. Illigal to modify */
268 ldb_asprintf_errstring(module->ldb,
269 "attribute %s must not be modified directly, it is a linked attribute", req->op.add.message->elements[i].name);
270 return LDB_ERR_UNWILLING_TO_PERFORM;
273 /* Even link IDs are for the originating attribute */
276 /* Now call the common routine to setup the modifies across all the attributes */
277 return setup_modifies(module->ldb, ac, ac, req->op.add.message, NULL, req->op.add.message->dn);
281 static int linked_attributes_modify(struct ldb_module *module, struct ldb_request *req)
283 /* Look over list of modifications */
284 /* Find if any are for linked attributes */
285 /* Determine the effect of the modification */
286 /* Apply the modify to the linked entry */
289 struct linked_attributes_context *ac;
291 const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
293 /* without schema, this doesn't make any sense */
294 return ldb_next_request(module, req);
297 if (ldb_dn_is_special(req->op.mod.message->dn)) {
298 /* do not manipulate our control entries */
299 return ldb_next_request(module, req);
303 ac = linked_attributes_init_handle(req, module);
305 return LDB_ERR_OPERATIONS_ERROR;
308 /* prepare the first operation */
309 ac->down_req = talloc_realloc(ac, ac->down_req,
310 struct ldb_request *, 1);
312 ldb_oom(ac->module->ldb);
313 return LDB_ERR_OPERATIONS_ERROR;
316 ac->down_req[0] = talloc(ac->down_req, struct ldb_request);
317 if (!ac->down_req[0]) {
318 ldb_oom(ac->module->ldb);
319 return LDB_ERR_OPERATIONS_ERROR;
321 *(ac->down_req[0]) = *req; /* copy the request */
325 /* Run the original request */
326 ret = ldb_next_request(module, ac->down_req[0]);
327 if (ret != LDB_SUCCESS) {
331 for (i=0; i < req->op.mod.message->num_elements; i++) {
332 const struct dsdb_attribute *target_attr;
333 const struct ldb_message_element *el = &req->op.mod.message->elements[i];
334 const struct dsdb_attribute *schema_attr
335 = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
337 ldb_asprintf_errstring(module->ldb,
338 "attribute %s is not a valid attribute in schema", req->op.mod.message->elements[i].name);
339 return LDB_ERR_OBJECT_CLASS_VIOLATION;
341 /* We have a valid attribute, not find out if it is linked */
342 if (schema_attr->linkID == 0) {
346 if ((schema_attr->linkID & 1) == 1) {
347 /* Odd is for the target. Illigal to modify */
348 ldb_asprintf_errstring(module->ldb,
349 "attribute %s must not be modified directly, it is a linked attribute", req->op.mod.message->elements[i].name);
350 return LDB_ERR_UNWILLING_TO_PERFORM;
353 /* Even link IDs are for the originating attribute */
355 /* Now find the target attribute */
356 target_attr = dsdb_attribute_by_linkID(schema, schema_attr->linkID + 1);
358 ldb_asprintf_errstring(module->ldb,
359 "attribute %s does not have valid link target", req->op.mod.message->elements[i].name);
360 return LDB_ERR_OBJECT_CLASS_VIOLATION;
363 if ((el->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_REPLACE) {
364 ldb_asprintf_errstring(module->ldb,
365 "attribute %s may not be replaced, only added or deleted", req->op.mod.message->elements[i].name);
366 return LDB_ERR_UNWILLING_TO_PERFORM;
368 /* Prepare the modify (mod element) on the targets */
370 /* For each value being moded, we need to setup the modify */
371 for (j=0; j < el->num_values; j++) {
372 struct ldb_request *new_req;
373 /* Create the modify request */
374 struct ldb_message *new_msg = ldb_msg_new(ac->down_req);
376 ldb_oom(module->ldb);
377 return LDB_ERR_OPERATIONS_ERROR;
379 new_msg->dn = ldb_dn_new(new_msg, module->ldb, (char *)el->values[j].data);
381 ldb_asprintf_errstring(module->ldb,
382 "attribute %s value %s was not a valid DN", req->op.mod.message->elements[i].name,
384 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
387 ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
388 el->flags & LDB_FLAG_MOD_MASK, NULL);
389 if (ret != LDB_SUCCESS) {
393 ret = ldb_msg_add_string(new_msg, target_attr->lDAPDisplayName,
394 ldb_dn_get_linearized(ac->orig_req->op.add.message->dn));
395 if (ret != LDB_SUCCESS) {
399 ret = ldb_build_mod_req(&new_req, module->ldb, ac->down_req,
404 if (ret != LDB_SUCCESS) {
408 talloc_steal(new_req, new_msg);
410 ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
412 /* Now add it to the list */
413 ac->down_req = talloc_realloc(ac, ac->down_req,
414 struct ldb_request *, ac->num_requests + 1);
416 ldb_oom(ac->module->ldb);
417 return LDB_ERR_OPERATIONS_ERROR;
419 ac->down_req[ac->num_requests] = new_req;
422 /* Run the new request */
423 ret = ldb_next_request(module, new_req);
424 if (ret != LDB_SUCCESS) {
432 static int linked_attributes_rename_del_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
434 struct ldb_request *req;
435 struct linked_attributes_context *ac = talloc_get_type(context, struct linked_attributes_context);
436 struct ldb_dn *olddn, *newdn;
438 switch (ac->orig_req->operation) {
441 olddn = ac->orig_req->op.del.dn;
447 olddn = ac->orig_req->op.rename.olddn;
448 newdn = ac->orig_req->op.rename.newdn;
452 return LDB_ERR_OPERATIONS_ERROR;
456 /* OK, we have one search result here: */
458 /* Only entries are interesting, and we only want the olddn */
459 if (ares->type == LDB_REPLY_ENTRY
460 && ldb_dn_compare(ares->message->dn, olddn) == 0) {
461 /* only bother at all if there were some linked attributes found */
462 if (ares->message->num_elements > 0) {
463 return setup_modifies(ldb, ac, ac,
464 ares->message, olddn, newdn);
468 } else if (ares->type == LDB_REPLY_ENTRY) {
469 /* Guh? We only asked for this DN */
470 return LDB_ERR_OPERATIONS_ERROR;
471 } else if (ares->type == LDB_REPLY_DONE) {
472 req = talloc(ac, struct ldb_request);
473 *req = *ac->orig_req;
476 ac->down_req = talloc_realloc(ac, ac->down_req,
477 struct ldb_request *, ac->num_requests + 1);
480 return LDB_ERR_OPERATIONS_ERROR;
482 ac->down_req[ac->num_requests] = req;
485 return ldb_next_request(ac->module, req);
495 static int linked_attributes_rename(struct ldb_module *module, struct ldb_request *req)
497 /* Look up list of linked attributes */
501 struct linked_attributes_context *ac;
502 struct ldb_request *new_req;
503 const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
505 /* without schema, this doesn't make any sense */
506 return ldb_next_request(module, req);
509 /* This gets complex: We need to:
510 - Do a search for the entry
511 - Wait for these result to appear
512 - In the callback for the result, issue a modify request based on the linked attributes found
513 - Wait for each modify result
517 ac = linked_attributes_init_handle(req, module);
519 return LDB_ERR_OPERATIONS_ERROR;
522 werr = dsdb_linked_attribute_lDAPDisplayName_list(schema, ac, &attrs);
523 if (!W_ERROR_IS_OK(werr)) {
524 return LDB_ERR_OPERATIONS_ERROR;
527 ret = ldb_build_search_req(&new_req, module->ldb, req,
528 req->op.rename.olddn,
534 linked_attributes_rename_del_search_callback);
536 if (ret != LDB_SUCCESS) {
540 talloc_steal(new_req, attrs);
542 ac->down_req = talloc_realloc(ac, ac->down_req,
543 struct ldb_request *, ac->num_requests + 1);
545 ldb_oom(ac->module->ldb);
546 return LDB_ERR_OPERATIONS_ERROR;
548 ac->down_req[ac->num_requests] = new_req;
550 ldb_oom(ac->module->ldb);
551 return LDB_ERR_OPERATIONS_ERROR;
554 return ldb_next_request(module, new_req);
558 static int linked_attributes_delete(struct ldb_module *module, struct ldb_request *req)
560 /* Look up list of linked attributes */
564 struct ldb_request *new_req;
565 struct linked_attributes_context *ac;
566 const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
568 /* without schema, this doesn't make any sense */
569 return ldb_next_request(module, req);
572 /* This gets complex: We need to:
573 - Do a search for the entry
574 - Wait for these result to appear
575 - In the callback for the result, issue a modify request based on the linked attributes found
576 - Wait for each modify result
580 ac = linked_attributes_init_handle(req, module);
582 return LDB_ERR_OPERATIONS_ERROR;
585 werr = dsdb_linked_attribute_lDAPDisplayName_list(schema, ac, &attrs);
586 if (!W_ERROR_IS_OK(werr)) {
587 return LDB_ERR_OPERATIONS_ERROR;
590 ret = ldb_build_search_req(&new_req, module->ldb, req,
597 linked_attributes_rename_del_search_callback);
599 if (ret != LDB_SUCCESS) {
603 talloc_steal(new_req, attrs);
605 ac->down_req = talloc_realloc(ac, ac->down_req,
606 struct ldb_request *, ac->num_requests + 1);
608 ldb_oom(ac->module->ldb);
609 return LDB_ERR_OPERATIONS_ERROR;
611 ac->down_req[ac->num_requests] = new_req;
613 ldb_oom(ac->module->ldb);
614 return LDB_ERR_OPERATIONS_ERROR;
617 return ldb_next_request(module, new_req);
621 static int linked_attributes_wait_none(struct ldb_handle *handle) {
622 struct linked_attributes_context *ac;
623 int i, ret = LDB_ERR_OPERATIONS_ERROR;
624 if (!handle || !handle->private_data) {
625 return LDB_ERR_OPERATIONS_ERROR;
628 if (handle->state == LDB_ASYNC_DONE) {
629 return handle->status;
632 handle->state = LDB_ASYNC_PENDING;
633 handle->status = LDB_SUCCESS;
635 ac = talloc_get_type(handle->private_data, struct linked_attributes_context);
637 for (i=0; i < ac->num_requests; i++) {
638 ret = ldb_wait(ac->down_req[i]->handle, LDB_WAIT_NONE);
640 if (ret != LDB_SUCCESS) {
641 handle->status = ret;
644 if (ac->down_req[i]->handle->status != LDB_SUCCESS) {
645 handle->status = ac->down_req[i]->handle->status;
649 if (ac->down_req[i]->handle->state != LDB_ASYNC_DONE) {
655 handle->state = LDB_ASYNC_DONE;
660 static int linked_attributes_wait_all(struct ldb_handle *handle) {
664 while (handle->state != LDB_ASYNC_DONE) {
665 ret = linked_attributes_wait_none(handle);
666 if (ret != LDB_SUCCESS) {
671 return handle->status;
674 static int linked_attributes_wait(struct ldb_handle *handle, enum ldb_wait_type type)
676 if (type == LDB_WAIT_ALL) {
677 return linked_attributes_wait_all(handle);
679 return linked_attributes_wait_none(handle);
683 static const struct ldb_module_ops linked_attributes_ops = {
684 .name = "linked_attributes",
685 .add = linked_attributes_add,
686 .modify = linked_attributes_modify,
687 .del = linked_attributes_delete,
688 .rename = linked_attributes_rename,
689 .wait = linked_attributes_wait,
692 int ldb_linked_attributes_init(void)
694 return ldb_register_module(&linked_attributes_ops);