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);
288 static int merge_cmp(struct merge *merge1, struct merge *merge2) {
290 ret = ldb_dn_compare(merge1->dn, merge2->dn);
292 if (merge1->add == merge2->add) {
295 if (merge1->add == true) {
303 static int linked_attributes_mod_replace_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
305 struct replace_context *ac2 = talloc_get_type(context, struct replace_context);
306 struct linked_attributes_context *ac = ac2->ac;
308 /* OK, we have one search result here: */
310 /* Only entries are interesting, and we only want the olddn */
311 if (ares->type == LDB_REPLY_ENTRY
312 && ldb_dn_compare(ares->message->dn, ac->orig_req->op.mod.message->dn) == 0) {
313 /* only bother at all if there were some linked attributes found */
314 struct ldb_message_element *search_el
315 = ldb_msg_find_element(ares->message,
318 /* See if this element already exists */
321 struct merge *merged_list = NULL;
323 int ret, size = 0, i;
324 struct ldb_message *msg = ldb_msg_new(ac);
326 ldb_oom(ac->module->ldb);
327 return LDB_ERR_OPERATIONS_ERROR;
330 /* Add all the existing elements, marking as 'proposed for delete' by setting .add = false */
331 for (i=0; i < search_el->num_values; i++) {
332 merged_list = talloc_realloc(ares, merged_list, struct merge, size + 1);
333 merged_list[size].dn = ldb_dn_new(merged_list, ldb, (char *)search_el->values[i].data);
334 merged_list[size].add = false;
335 merged_list[size].ignore = false;
339 /* Add all the new replacement elements, marking as 'proposed for add' by setting .add = true */
340 for (i=0; i < ac2->el->num_values; i++) {
341 merged_list = talloc_realloc(ares, merged_list, struct merge, size + 1);
342 merged_list[size].dn = ldb_dn_new(merged_list, ldb, (char *)ac2->el->values[i].data);
343 merged_list[size].add = true;
344 merged_list[size].ignore = false;
348 /* Sort the list, so we can pick out an add and delete for the same DN, and eliminate them */
349 qsort(merged_list, size,
350 sizeof(*merged_list),
351 (comparison_fn_t)merge_cmp);
353 /* Now things are sorted, it is trivial to mark pairs of DNs as 'ignore' */
354 for (i=0; i + 1 < size; i++) {
355 if (ldb_dn_compare(merged_list[i].dn,
356 merged_list[i+1].dn) == 0
357 /* Fortunetly the sort also sorts 'add == false' first */
358 && merged_list[i].add == false
359 && merged_list[i+1].add == true) {
361 /* Mark as ignore, so we include neither in the actual operations */
362 merged_list[i].ignore = true;
363 merged_list[i+1].ignore = true;
367 /* Arrange to delete anything the search found that we don't re-add */
368 for (i=0; i < size; i++) {
369 if (merged_list[i].ignore == false
370 && merged_list[i].add == false) {
371 ldb_msg_add_steal_string(msg, search_el->name,
372 ldb_dn_get_linearized(merged_list[i].dn));
376 /* The DN to set on the linked attributes is the original DN of the modify message */
377 msg->dn = ac->orig_req->op.mod.message->dn;
379 ret = setup_modifies(ac->module->ldb, ac2, ac, msg, ares->message->dn, NULL);
380 if (ret != LDB_SUCCESS) {
384 /* Now add links for all the actually new elements */
385 for (i=0; i < size; i++) {
386 if (merged_list[i].ignore == false && merged_list[i].add == true) {
387 ldb_msg_add_steal_string(msg, search_el->name,
388 ldb_dn_get_linearized(merged_list[i].dn));
392 ret = setup_modifies(ac->module->ldb, ac2, ac, msg, NULL, ares->message->dn);
393 if (ret != LDB_SUCCESS) {
397 talloc_free(merged_list);
400 /* Looks like it doesn't exist, process like an 'add' */
401 struct ldb_message *msg = ldb_msg_new(ac);
403 ldb_oom(ac->module->ldb);
404 return LDB_ERR_OPERATIONS_ERROR;
406 msg->num_elements = 1;
407 msg->elements = ac2->el;
408 msg->dn = ac->orig_req->op.mod.message->dn;
410 return setup_modifies(ac->module->ldb, ac2, ac, msg, NULL, ac->orig_req->op.mod.message->dn);
414 } else if (ares->type == LDB_REPLY_ENTRY) {
415 /* Guh? We only asked for this DN */
416 return LDB_ERR_OPERATIONS_ERROR;
426 static int linked_attributes_modify(struct ldb_module *module, struct ldb_request *req)
428 /* Look over list of modifications */
429 /* Find if any are for linked attributes */
430 /* Determine the effect of the modification */
431 /* Apply the modify to the linked entry */
434 struct linked_attributes_context *ac;
436 const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
438 /* without schema, this doesn't make any sense */
439 return ldb_next_request(module, req);
442 if (ldb_dn_is_special(req->op.mod.message->dn)) {
443 /* do not manipulate our control entries */
444 return ldb_next_request(module, req);
448 ac = linked_attributes_init_handle(req, module);
450 return LDB_ERR_OPERATIONS_ERROR;
453 /* prepare the first operation */
454 ac->step = LA_DO_OPS;
456 for (i=0; i < req->op.mod.message->num_elements; i++) {
458 struct ldb_request *new_req;
459 const struct dsdb_attribute *target_attr;
460 const struct ldb_message_element *el = &req->op.mod.message->elements[i];
461 const struct dsdb_attribute *schema_attr
462 = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
464 ldb_asprintf_errstring(module->ldb,
465 "attribute %s is not a valid attribute in schema", req->op.mod.message->elements[i].name);
466 return LDB_ERR_OBJECT_CLASS_VIOLATION;
468 /* We have a valid attribute, not find out if it is linked */
469 if (schema_attr->linkID == 0) {
473 if ((schema_attr->linkID & 1) == 1) {
474 /* Odd is for the target. Illigal to modify */
475 ldb_asprintf_errstring(module->ldb,
476 "attribute %s must not be modified directly, it is a linked attribute", req->op.mod.message->elements[i].name);
477 return LDB_ERR_UNWILLING_TO_PERFORM;
480 /* Even link IDs are for the originating attribute */
482 /* Now find the target attribute */
483 target_attr = dsdb_attribute_by_linkID(schema, schema_attr->linkID + 1);
485 ldb_asprintf_errstring(module->ldb,
486 "attribute %s does not have valid link target", req->op.mod.message->elements[i].name);
487 return LDB_ERR_OBJECT_CLASS_VIOLATION;
490 /* Replace with new set of values */
491 if (((el->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_REPLACE)
492 && el->num_values > 0) {
493 struct replace_context *ac2 = talloc(ac, struct replace_context);
494 const char **attrs = talloc_array(ac, const char *, 2);
495 if (!attrs || !ac2) {
496 ldb_oom(ac->module->ldb);
497 return LDB_ERR_OPERATIONS_ERROR;
505 /* We need to setup a search, compare with the list, and then setup add/del as required */
507 /* The callback does all the hard work here */
508 ret = ldb_build_search_req(&new_req, module->ldb, req,
509 req->op.mod.message->dn,
515 linked_attributes_mod_replace_search_callback);
517 if (ret != LDB_SUCCESS) {
521 talloc_steal(new_req, attrs);
523 /* Create a spot in the list for the requests */
524 ac->down_req = talloc_realloc(ac, ac->down_req,
525 struct ldb_request *, ac->num_requests + 1);
527 ldb_oom(ac->module->ldb);
528 return LDB_ERR_OPERATIONS_ERROR;
531 ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
534 ret = ldb_next_request(module, new_req);
536 if (ret != LDB_SUCCESS) {
542 /* Delete all values case */
543 } else if (((el->flags & LDB_FLAG_MOD_MASK) & (LDB_FLAG_MOD_DELETE|LDB_FLAG_MOD_REPLACE))
544 && el->num_values == 0) {
545 const char **attrs = talloc_array(ac, const char *, 2);
547 ldb_oom(ac->module->ldb);
548 return LDB_ERR_OPERATIONS_ERROR;
553 /* We need to setup a search, and then setup del as required */
555 /* The callback does all the hard work here, acting identically to if we had delted the whole entry */
556 ret = ldb_build_search_req(&new_req, module->ldb, req,
557 req->op.mod.message->dn,
563 linked_attributes_rename_del_search_callback);
565 if (ret != LDB_SUCCESS) {
569 talloc_steal(new_req, attrs);
571 /* Create a spot in the list for the requests */
572 ac->down_req = talloc_realloc(ac, ac->down_req,
573 struct ldb_request *, ac->num_requests + 1);
575 ldb_oom(ac->module->ldb);
576 return LDB_ERR_OPERATIONS_ERROR;
579 ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
582 ret = ldb_next_request(module, new_req);
584 if (ret != LDB_SUCCESS) {
591 /* Prepare the modify (mod element) on the targets, for a normal modify request */
593 /* For each value being moded, we need to setup the modify */
594 for (j=0; j < el->num_values; j++) {
595 /* Create the modify request */
596 struct ldb_message *new_msg = ldb_msg_new(ac);
598 ldb_oom(module->ldb);
599 return LDB_ERR_OPERATIONS_ERROR;
601 new_msg->dn = ldb_dn_new(new_msg, module->ldb, (char *)el->values[j].data);
603 ldb_asprintf_errstring(module->ldb,
604 "attribute %s value %s was not a valid DN", req->op.mod.message->elements[i].name,
606 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
609 ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
610 el->flags & LDB_FLAG_MOD_MASK, NULL);
611 if (ret != LDB_SUCCESS) {
615 ret = ldb_msg_add_string(new_msg, target_attr->lDAPDisplayName,
616 ldb_dn_get_linearized(ac->orig_req->op.add.message->dn));
617 if (ret != LDB_SUCCESS) {
621 ret = ldb_build_mod_req(&new_req, module->ldb, ac,
626 if (ret != LDB_SUCCESS) {
630 talloc_steal(new_req, new_msg);
632 ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
634 /* Now add it to the list */
635 ac->down_req = talloc_realloc(ac, ac->down_req,
636 struct ldb_request *, ac->num_requests + 1);
638 ldb_oom(ac->module->ldb);
639 return LDB_ERR_OPERATIONS_ERROR;
641 ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
644 /* Run the new request */
645 ret = ldb_next_request(module, new_req);
646 if (ret != LDB_SUCCESS) {
654 static int linked_attributes_rename_del_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
656 struct linked_attributes_context *ac = talloc_get_type(context, struct linked_attributes_context);
657 struct ldb_dn *olddn, *newdn;
659 switch (ac->orig_req->operation) {
662 olddn = ac->orig_req->op.del.dn;
666 /* This isn't the general modify case, just the modify when we are asked to delete all values */
669 olddn = ac->orig_req->op.mod.message->dn;
675 olddn = ac->orig_req->op.rename.olddn;
676 newdn = ac->orig_req->op.rename.newdn;
680 return LDB_ERR_OPERATIONS_ERROR;
684 /* OK, we have one search result here: */
686 /* Only entries are interesting, and we only want the olddn */
687 if (ares->type == LDB_REPLY_ENTRY
688 && ldb_dn_compare(ares->message->dn, olddn) == 0) {
689 /* only bother at all if there were some linked attributes found */
690 if (ares->message->num_elements > 0) {
691 return setup_modifies(ldb, ac, ac,
692 ares->message, olddn, newdn);
696 } else if (ares->type == LDB_REPLY_ENTRY) {
697 /* Guh? We only asked for this DN */
698 return LDB_ERR_OPERATIONS_ERROR;
708 static int linked_attributes_rename(struct ldb_module *module, struct ldb_request *req)
710 /* Look up list of linked attributes */
714 struct linked_attributes_context *ac;
715 struct ldb_request *new_req;
716 const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
718 /* without schema, this doesn't make any sense */
719 return ldb_next_request(module, req);
722 /* This gets complex: We need to:
723 - Do a search for the entry
724 - Wait for these result to appear
725 - In the callback for the result, issue a modify request based on the linked attributes found
726 - Wait for each modify result
730 ac = linked_attributes_init_handle(req, module);
732 return LDB_ERR_OPERATIONS_ERROR;
735 werr = dsdb_linked_attribute_lDAPDisplayName_list(schema, ac, &attrs);
736 if (!W_ERROR_IS_OK(werr)) {
737 return LDB_ERR_OPERATIONS_ERROR;
740 ret = ldb_build_search_req(&new_req, module->ldb, req,
741 req->op.rename.olddn,
747 linked_attributes_rename_del_search_callback);
749 if (ret != LDB_SUCCESS) {
753 talloc_steal(new_req, attrs);
755 ac->search_req = new_req;
756 ac->step = LA_SEARCH;
757 return ldb_next_request(module, new_req);
761 static int linked_attributes_delete(struct ldb_module *module, struct ldb_request *req)
763 /* Look up list of linked attributes */
767 struct ldb_request *new_req;
768 struct linked_attributes_context *ac;
769 const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
771 /* without schema, this doesn't make any sense */
772 return ldb_next_request(module, req);
775 /* This gets complex: We need to:
776 - Do a search for the entry
777 - Wait for these result to appear
778 - In the callback for the result, issue a modify request based on the linked attributes found
779 - Wait for each modify result
783 ac = linked_attributes_init_handle(req, module);
785 return LDB_ERR_OPERATIONS_ERROR;
788 werr = dsdb_linked_attribute_lDAPDisplayName_list(schema, ac, &attrs);
789 if (!W_ERROR_IS_OK(werr)) {
790 return LDB_ERR_OPERATIONS_ERROR;
793 ret = ldb_build_search_req(&new_req, module->ldb, req,
800 linked_attributes_rename_del_search_callback);
802 if (ret != LDB_SUCCESS) {
806 talloc_steal(new_req, attrs);
808 ac->search_req = new_req;
809 ac->step = LA_SEARCH;
810 return ldb_next_request(module, new_req);
814 static int linked_attributes_wait_none(struct ldb_handle *handle) {
815 struct linked_attributes_context *ac;
816 int i, ret = LDB_ERR_OPERATIONS_ERROR;
817 if (!handle || !handle->private_data) {
818 return LDB_ERR_OPERATIONS_ERROR;
821 if (handle->state == LDB_ASYNC_DONE) {
822 return handle->status;
825 handle->state = LDB_ASYNC_PENDING;
826 handle->status = LDB_SUCCESS;
828 ac = talloc_get_type(handle->private_data, struct linked_attributes_context);
832 ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
834 if (ret != LDB_SUCCESS) {
835 handle->status = ret;
838 if (ac->search_req->handle->status != LDB_SUCCESS) {
839 handle->status = ac->search_req->handle->status;
843 if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
846 ac->step = LA_DO_OPS;
850 for (i=0; i < ac->num_requests; i++) {
851 ret = ldb_wait(ac->down_req[i]->handle, LDB_WAIT_NONE);
853 if (ret != LDB_SUCCESS) {
854 handle->status = ret;
857 if (ac->down_req[i]->handle->status != LDB_SUCCESS) {
858 handle->status = ac->down_req[i]->handle->status;
862 if (ac->down_req[i]->handle->state != LDB_ASYNC_DONE) {
867 /* Now run the original request */
868 ac->step = LA_DO_ORIG;
869 return ldb_next_request(ac->module, ac->orig_down_req);
872 ret = ldb_wait(ac->orig_down_req->handle, LDB_WAIT_NONE);
874 if (ret != LDB_SUCCESS) {
875 handle->status = ret;
878 if (ac->orig_down_req->handle->status != LDB_SUCCESS) {
879 handle->status = ac->orig_down_req->handle->status;
883 if (ac->orig_down_req->handle->state != LDB_ASYNC_DONE) {
890 handle->state = LDB_ASYNC_DONE;
895 static int linked_attributes_wait_all(struct ldb_handle *handle) {
899 while (handle->state != LDB_ASYNC_DONE) {
900 ret = linked_attributes_wait_none(handle);
901 if (ret != LDB_SUCCESS) {
906 return handle->status;
909 static int linked_attributes_wait(struct ldb_handle *handle, enum ldb_wait_type type)
911 if (type == LDB_WAIT_ALL) {
912 return linked_attributes_wait_all(handle);
914 return linked_attributes_wait_none(handle);
918 static const struct ldb_module_ops linked_attributes_ops = {
919 .name = "linked_attributes",
920 .add = linked_attributes_add,
921 .modify = linked_attributes_modify,
922 .del = linked_attributes_delete,
923 .rename = linked_attributes_rename,
924 .wait = linked_attributes_wait,
927 int ldb_linked_attributes_init(void)
929 return ldb_register_module(&linked_attributes_ops);