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 enum la_step {LA_SEARCH, LA_DO_OPS, LA_DO_ORIG} step;
38 struct ldb_module *module;
39 struct ldb_handle *handle;
40 struct ldb_request *orig_req;
42 struct ldb_request *search_req;
43 struct ldb_request **down_req;
44 struct ldb_request *orig_down_req;
47 int finished_requests;
49 const char **linked_attrs;
52 struct replace_context {
53 struct linked_attributes_context *ac;
54 struct ldb_message_element *el;
57 static int linked_attributes_rename_del_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares);
59 static struct linked_attributes_context *linked_attributes_init_handle(struct ldb_request *req,
60 struct ldb_module *module)
62 struct linked_attributes_context *ac;
65 h = talloc_zero(req, struct ldb_handle);
67 ldb_set_errstring(module->ldb, "Out of Memory");
73 ac = talloc_zero(h, struct linked_attributes_context);
75 ldb_set_errstring(module->ldb, "Out of Memory");
86 ac->orig_down_req = talloc(ac, struct ldb_request);
87 if (!ac->orig_down_req) {
88 ldb_oom(ac->module->ldb);
92 *ac->orig_down_req = *req;
99 /* Common routine to handle reading the attributes and creating a
100 * series of modify requests */
102 static int setup_modifies(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
103 struct linked_attributes_context *ac,
104 const struct ldb_message *msg,
105 struct ldb_dn *olddn, struct ldb_dn *newdn)
107 int i, j, ret = LDB_SUCCESS;
108 const struct dsdb_schema *schema = dsdb_get_schema(ldb);
109 /* Look up each of the returned attributes */
110 /* Find their schema */
111 /* And it is an actual entry: now create a series of modify requests */
112 for (i=0; i < msg->num_elements; i++) {
114 const struct dsdb_attribute *target_attr;
115 const struct ldb_message_element *el = &msg->elements[i];
116 const struct dsdb_attribute *schema_attr
117 = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
119 ldb_asprintf_errstring(ldb,
120 "attribute %s is not a valid attribute in schema", el->name);
121 return LDB_ERR_OBJECT_CLASS_VIOLATION;
123 /* We have a valid attribute, but if it's not linked they maybe we just got an extra return on our search... */
124 if (schema_attr->linkID == 0) {
128 /* Depending on which direction this link is in, we need to find it's partner */
129 if ((schema_attr->linkID & 1) == 1) {
130 otherid = schema_attr->linkID - 1;
132 otherid = schema_attr->linkID + 1;
135 /* Now find the target attribute */
136 target_attr = dsdb_attribute_by_linkID(schema, otherid);
138 ldb_asprintf_errstring(ldb,
139 "attribute %s does not have valid link target", el->name);
140 return LDB_ERR_OBJECT_CLASS_VIOLATION;
143 /* For each value being moded, we need to setup the modify */
144 for (j=0; j < el->num_values; j++) {
145 struct ldb_message_element *ret_el;
146 struct ldb_request *new_req;
147 struct ldb_message *new_msg;
149 /* Create a spot in the list for the requests */
150 ac->down_req = talloc_realloc(ac, ac->down_req,
151 struct ldb_request *, ac->num_requests + 1);
154 return LDB_ERR_OPERATIONS_ERROR;
157 /* Create the modify request */
158 new_msg = ldb_msg_new(ac->down_req);
161 return LDB_ERR_OPERATIONS_ERROR;
163 new_msg->dn = ldb_dn_new(new_msg, ldb, (char *)el->values[j].data);
165 ldb_asprintf_errstring(ldb,
166 "attribute %s value %s was not a valid DN", msg->elements[i].name,
168 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
172 ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
173 LDB_FLAG_MOD_DELETE, &ret_el);
174 if (ret != LDB_SUCCESS) {
177 ret_el->values = talloc_array(new_msg, struct ldb_val, 1);
178 if (!ret_el->values) {
180 return LDB_ERR_OPERATIONS_ERROR;
182 ret_el->values[0] = data_blob_string_const(ldb_dn_get_linearized(olddn));
183 ret_el->num_values = 1;
187 ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
188 LDB_FLAG_MOD_ADD, &ret_el);
189 if (ret != LDB_SUCCESS) {
192 ret_el->values = talloc_array(new_msg, struct ldb_val, 1);
193 if (!ret_el->values) {
195 return LDB_ERR_OPERATIONS_ERROR;
197 ret_el->values[0] = data_blob_string_const(ldb_dn_get_linearized(newdn));
198 ret_el->num_values = 1;
201 ret = ldb_build_mod_req(&new_req, ldb, ac->down_req,
206 if (ret != LDB_SUCCESS) {
210 talloc_steal(new_req, new_msg);
212 ldb_set_timeout_from_prev_req(ldb, ac->orig_req, new_req);
214 ac->down_req[ac->num_requests] = new_req;
218 /* Run the new request */
219 ret = ldb_next_request(ac->module, new_req);
220 if (ret != LDB_SUCCESS) {
229 static int linked_attributes_add(struct ldb_module *module, struct ldb_request *req)
232 struct linked_attributes_context *ac;
234 const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
236 /* without schema, this doesn't make any sense */
237 return ldb_next_request(module, req);
240 if (ldb_dn_is_special(req->op.mod.message->dn)) {
241 /* do not manipulate our control entries */
242 return ldb_next_request(module, req);
246 ac = linked_attributes_init_handle(req, module);
248 return LDB_ERR_OPERATIONS_ERROR;
251 ac->step = LA_DO_OPS;
253 /* Need to ensure we only have forward links being specified */
254 for (i=0; i < req->op.add.message->num_elements; i++) {
255 const struct ldb_message_element *el = &req->op.add.message->elements[i];
256 const struct dsdb_attribute *schema_attr
257 = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
259 ldb_asprintf_errstring(module->ldb,
260 "attribute %s is not a valid attribute in schema", req->op.add.message->elements[i].name);
261 return LDB_ERR_OBJECT_CLASS_VIOLATION;
263 /* We have a valid attribute, not find out if it is linked */
264 if (schema_attr->linkID == 0) {
268 if ((schema_attr->linkID & 1) == 1) {
269 /* Odd is for the target. Illigal to modify */
270 ldb_asprintf_errstring(module->ldb,
271 "attribute %s must not be modified directly, it is a linked attribute", req->op.add.message->elements[i].name);
272 return LDB_ERR_UNWILLING_TO_PERFORM;
275 /* Even link IDs are for the originating attribute */
278 /* Now call the common routine to setup the modifies across all the attributes */
279 return setup_modifies(module->ldb, ac, ac, req->op.add.message, NULL, req->op.add.message->dn);
282 static int linked_attributes_mod_replace_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
284 struct replace_context *ac2 = talloc_get_type(context, struct replace_context);
285 struct linked_attributes_context *ac = ac2->ac;
287 /* OK, we have one search result here: */
289 /* Only entries are interesting, and we only want the olddn */
290 if (ares->type == LDB_REPLY_ENTRY
291 && ldb_dn_compare(ares->message->dn, ac->orig_req->op.mod.message->dn) == 0) {
292 /* only bother at all if there were some linked attributes found */
293 struct ldb_message_element *search_el
294 = ldb_msg_find_element(ares->message,
297 /* See if this element already exists */
300 struct ldb_message *msg = ldb_msg_new(ac);
302 ldb_oom(ac->module->ldb);
303 return LDB_ERR_OPERATIONS_ERROR;
306 /* Lazy option: Delete and add the elements on all members */
307 msg->num_elements = 1;
308 msg->elements = search_el;
309 msg->dn = ac->orig_req->op.mod.message->dn;
311 ret = setup_modifies(ac->module->ldb, ac2, ac, msg, ares->message->dn, NULL);
312 if (ret != LDB_SUCCESS) {
316 msg->elements = ac2->el;
318 ret = setup_modifies(ac->module->ldb, ac2, ac, msg, NULL, ares->message->dn);
319 if (ret != LDB_SUCCESS) {
324 /* Looks like it doesn't exist, process like an 'add' */
325 struct ldb_message *msg = ldb_msg_new(ac);
327 ldb_oom(ac->module->ldb);
328 return LDB_ERR_OPERATIONS_ERROR;
330 msg->num_elements = 1;
331 msg->elements = ac2->el;
332 msg->dn = ac->orig_req->op.mod.message->dn;
334 return setup_modifies(ac->module->ldb, ac2, ac, msg, NULL, ac->orig_req->op.mod.message->dn);
338 } else if (ares->type == LDB_REPLY_ENTRY) {
339 /* Guh? We only asked for this DN */
340 return LDB_ERR_OPERATIONS_ERROR;
350 static int linked_attributes_modify(struct ldb_module *module, struct ldb_request *req)
352 /* Look over list of modifications */
353 /* Find if any are for linked attributes */
354 /* Determine the effect of the modification */
355 /* Apply the modify to the linked entry */
358 struct linked_attributes_context *ac;
360 const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
362 /* without schema, this doesn't make any sense */
363 return ldb_next_request(module, req);
366 if (ldb_dn_is_special(req->op.mod.message->dn)) {
367 /* do not manipulate our control entries */
368 return ldb_next_request(module, req);
372 ac = linked_attributes_init_handle(req, module);
374 return LDB_ERR_OPERATIONS_ERROR;
377 /* prepare the first operation */
378 ac->step = LA_DO_OPS;
380 for (i=0; i < req->op.mod.message->num_elements; i++) {
382 struct ldb_request *new_req;
383 const struct dsdb_attribute *target_attr;
384 const struct ldb_message_element *el = &req->op.mod.message->elements[i];
385 const struct dsdb_attribute *schema_attr
386 = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
388 ldb_asprintf_errstring(module->ldb,
389 "attribute %s is not a valid attribute in schema", req->op.mod.message->elements[i].name);
390 return LDB_ERR_OBJECT_CLASS_VIOLATION;
392 /* We have a valid attribute, not find out if it is linked */
393 if (schema_attr->linkID == 0) {
397 if ((schema_attr->linkID & 1) == 1) {
398 /* Odd is for the target. Illigal to modify */
399 ldb_asprintf_errstring(module->ldb,
400 "attribute %s must not be modified directly, it is a linked attribute", req->op.mod.message->elements[i].name);
401 return LDB_ERR_UNWILLING_TO_PERFORM;
404 /* Even link IDs are for the originating attribute */
406 /* Now find the target attribute */
407 target_attr = dsdb_attribute_by_linkID(schema, schema_attr->linkID + 1);
409 ldb_asprintf_errstring(module->ldb,
410 "attribute %s does not have valid link target", req->op.mod.message->elements[i].name);
411 return LDB_ERR_OBJECT_CLASS_VIOLATION;
414 if (((el->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_REPLACE)
415 && el->num_values > 0) {
416 struct replace_context *ac2 = talloc(ac, struct replace_context);
417 const char **attrs = talloc_array(ac, const char *, 2);
418 if (!attrs || !ac2) {
419 ldb_oom(ac->module->ldb);
420 return LDB_ERR_OPERATIONS_ERROR;
428 /* We need to setup a search, compare with the list, and then setup add/del as required */
430 /* The callback does all the hard work here */
431 ret = ldb_build_search_req(&new_req, module->ldb, req,
432 req->op.mod.message->dn,
438 linked_attributes_mod_replace_search_callback);
440 if (ret != LDB_SUCCESS) {
444 talloc_steal(new_req, attrs);
446 /* Create a spot in the list for the requests */
447 ac->down_req = talloc_realloc(ac, ac->down_req,
448 struct ldb_request *, ac->num_requests + 1);
450 ldb_oom(ac->module->ldb);
451 return LDB_ERR_OPERATIONS_ERROR;
454 ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
457 ret = ldb_next_request(module, new_req);
459 if (ret != LDB_SUCCESS) {
464 } else if (((el->flags & LDB_FLAG_MOD_MASK) & (LDB_FLAG_MOD_DELETE|LDB_FLAG_MOD_REPLACE))
465 && el->num_values == 0) {
466 const char **attrs = talloc_array(ac, const char *, 2);
468 ldb_oom(ac->module->ldb);
469 return LDB_ERR_OPERATIONS_ERROR;
474 /* We need to setup a search, and then setup del as required */
476 /* The callback does all the hard work here, acting identically to if we had delted the whole entry */
477 ret = ldb_build_search_req(&new_req, module->ldb, req,
478 req->op.mod.message->dn,
484 linked_attributes_rename_del_search_callback);
486 if (ret != LDB_SUCCESS) {
490 talloc_steal(new_req, attrs);
492 /* Create a spot in the list for the requests */
493 ac->down_req = talloc_realloc(ac, ac->down_req,
494 struct ldb_request *, ac->num_requests + 1);
496 ldb_oom(ac->module->ldb);
497 return LDB_ERR_OPERATIONS_ERROR;
500 ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
503 ret = ldb_next_request(module, new_req);
505 if (ret != LDB_SUCCESS) {
511 /* Prepare the modify (mod element) on the targets */
513 /* For each value being moded, we need to setup the modify */
514 for (j=0; j < el->num_values; j++) {
515 /* Create the modify request */
516 struct ldb_message *new_msg = ldb_msg_new(ac);
518 ldb_oom(module->ldb);
519 return LDB_ERR_OPERATIONS_ERROR;
521 new_msg->dn = ldb_dn_new(new_msg, module->ldb, (char *)el->values[j].data);
523 ldb_asprintf_errstring(module->ldb,
524 "attribute %s value %s was not a valid DN", req->op.mod.message->elements[i].name,
526 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
529 ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
530 el->flags & LDB_FLAG_MOD_MASK, NULL);
531 if (ret != LDB_SUCCESS) {
535 ret = ldb_msg_add_string(new_msg, target_attr->lDAPDisplayName,
536 ldb_dn_get_linearized(ac->orig_req->op.add.message->dn));
537 if (ret != LDB_SUCCESS) {
541 ret = ldb_build_mod_req(&new_req, module->ldb, ac,
546 if (ret != LDB_SUCCESS) {
550 talloc_steal(new_req, new_msg);
552 ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
554 /* Now add it to the list */
555 ac->down_req = talloc_realloc(ac, ac->down_req,
556 struct ldb_request *, ac->num_requests + 1);
558 ldb_oom(ac->module->ldb);
559 return LDB_ERR_OPERATIONS_ERROR;
561 ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
564 /* Run the new request */
565 ret = ldb_next_request(module, new_req);
566 if (ret != LDB_SUCCESS) {
574 static int linked_attributes_rename_del_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
576 struct linked_attributes_context *ac = talloc_get_type(context, struct linked_attributes_context);
577 struct ldb_dn *olddn, *newdn;
579 switch (ac->orig_req->operation) {
582 olddn = ac->orig_req->op.del.dn;
586 /* This isn't the general modify case, just the modify when we are asked to delete all values */
589 olddn = ac->orig_req->op.mod.message->dn;
595 olddn = ac->orig_req->op.rename.olddn;
596 newdn = ac->orig_req->op.rename.newdn;
600 return LDB_ERR_OPERATIONS_ERROR;
604 /* OK, we have one search result here: */
606 /* Only entries are interesting, and we only want the olddn */
607 if (ares->type == LDB_REPLY_ENTRY
608 && ldb_dn_compare(ares->message->dn, olddn) == 0) {
609 /* only bother at all if there were some linked attributes found */
610 if (ares->message->num_elements > 0) {
611 return setup_modifies(ldb, ac, ac,
612 ares->message, olddn, newdn);
616 } else if (ares->type == LDB_REPLY_ENTRY) {
617 /* Guh? We only asked for this DN */
618 return LDB_ERR_OPERATIONS_ERROR;
628 static int linked_attributes_rename(struct ldb_module *module, struct ldb_request *req)
630 /* Look up list of linked attributes */
634 struct linked_attributes_context *ac;
635 struct ldb_request *new_req;
636 const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
638 /* without schema, this doesn't make any sense */
639 return ldb_next_request(module, req);
642 /* This gets complex: We need to:
643 - Do a search for the entry
644 - Wait for these result to appear
645 - In the callback for the result, issue a modify request based on the linked attributes found
646 - Wait for each modify result
650 ac = linked_attributes_init_handle(req, module);
652 return LDB_ERR_OPERATIONS_ERROR;
655 werr = dsdb_linked_attribute_lDAPDisplayName_list(schema, ac, &attrs);
656 if (!W_ERROR_IS_OK(werr)) {
657 return LDB_ERR_OPERATIONS_ERROR;
660 ret = ldb_build_search_req(&new_req, module->ldb, req,
661 req->op.rename.olddn,
667 linked_attributes_rename_del_search_callback);
669 if (ret != LDB_SUCCESS) {
673 talloc_steal(new_req, attrs);
675 ac->search_req = new_req;
676 ac->step = LA_SEARCH;
677 return ldb_next_request(module, new_req);
681 static int linked_attributes_delete(struct ldb_module *module, struct ldb_request *req)
683 /* Look up list of linked attributes */
687 struct ldb_request *new_req;
688 struct linked_attributes_context *ac;
689 const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
691 /* without schema, this doesn't make any sense */
692 return ldb_next_request(module, req);
695 /* This gets complex: We need to:
696 - Do a search for the entry
697 - Wait for these result to appear
698 - In the callback for the result, issue a modify request based on the linked attributes found
699 - Wait for each modify result
703 ac = linked_attributes_init_handle(req, module);
705 return LDB_ERR_OPERATIONS_ERROR;
708 werr = dsdb_linked_attribute_lDAPDisplayName_list(schema, ac, &attrs);
709 if (!W_ERROR_IS_OK(werr)) {
710 return LDB_ERR_OPERATIONS_ERROR;
713 ret = ldb_build_search_req(&new_req, module->ldb, req,
720 linked_attributes_rename_del_search_callback);
722 if (ret != LDB_SUCCESS) {
726 talloc_steal(new_req, attrs);
728 ac->search_req = new_req;
729 ac->step = LA_SEARCH;
730 return ldb_next_request(module, new_req);
734 static int linked_attributes_wait_none(struct ldb_handle *handle) {
735 struct linked_attributes_context *ac;
736 int i, ret = LDB_ERR_OPERATIONS_ERROR;
737 if (!handle || !handle->private_data) {
738 return LDB_ERR_OPERATIONS_ERROR;
741 if (handle->state == LDB_ASYNC_DONE) {
742 return handle->status;
745 handle->state = LDB_ASYNC_PENDING;
746 handle->status = LDB_SUCCESS;
748 ac = talloc_get_type(handle->private_data, struct linked_attributes_context);
752 ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
754 if (ret != LDB_SUCCESS) {
755 handle->status = ret;
758 if (ac->search_req->handle->status != LDB_SUCCESS) {
759 handle->status = ac->search_req->handle->status;
763 if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
766 ac->step = LA_DO_OPS;
770 for (i=0; i < ac->num_requests; i++) {
771 ret = ldb_wait(ac->down_req[i]->handle, LDB_WAIT_NONE);
773 if (ret != LDB_SUCCESS) {
774 handle->status = ret;
777 if (ac->down_req[i]->handle->status != LDB_SUCCESS) {
778 handle->status = ac->down_req[i]->handle->status;
782 if (ac->down_req[i]->handle->state != LDB_ASYNC_DONE) {
787 /* Now run the original request */
788 ac->step = LA_DO_ORIG;
789 return ldb_next_request(ac->module, ac->orig_down_req);
792 ret = ldb_wait(ac->orig_down_req->handle, LDB_WAIT_NONE);
794 if (ret != LDB_SUCCESS) {
795 handle->status = ret;
798 if (ac->orig_down_req->handle->status != LDB_SUCCESS) {
799 handle->status = ac->orig_down_req->handle->status;
803 if (ac->orig_down_req->handle->state != LDB_ASYNC_DONE) {
810 handle->state = LDB_ASYNC_DONE;
815 static int linked_attributes_wait_all(struct ldb_handle *handle) {
819 while (handle->state != LDB_ASYNC_DONE) {
820 ret = linked_attributes_wait_none(handle);
821 if (ret != LDB_SUCCESS) {
826 return handle->status;
829 static int linked_attributes_wait(struct ldb_handle *handle, enum ldb_wait_type type)
831 if (type == LDB_WAIT_ALL) {
832 return linked_attributes_wait_all(handle);
834 return linked_attributes_wait_none(handle);
838 static const struct ldb_module_ops linked_attributes_ops = {
839 .name = "linked_attributes",
840 .add = linked_attributes_add,
841 .modify = linked_attributes_modify,
842 .del = linked_attributes_delete,
843 .rename = linked_attributes_rename,
844 .wait = linked_attributes_wait,
847 int ldb_linked_attributes_init(void)
849 return ldb_register_module(&linked_attributes_ops);