be5dd12d3b84eda491a448b0270270e654689481
[samba.git] / source4 / dsdb / samdb / ldb_modules / linked_attributes.c
1 /* 
2    ldb database library
3
4    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007
5
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.
10    
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.
15    
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/>.
18 */
19
20 /*
21  *  Name: ldb
22  *
23  *  Component: ldb linked_attributes module
24  *
25  *  Description: Module to ensure linked attribute pairs remain in sync
26  *
27  *  Author: Andrew Bartlett
28  */
29
30 #include "includes.h"
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"
35
36 struct linked_attributes_context {
37         struct ldb_module *module;
38         struct ldb_handle *handle;
39         struct ldb_request *orig_req;
40
41         struct ldb_request **down_req;
42         int num_requests;
43         int finished_requests;
44
45         const char **linked_attrs;
46 };
47
48 static struct linked_attributes_context *linked_attributes_init_handle(struct ldb_request *req, 
49                                                                  struct ldb_module *module)
50 {
51         struct linked_attributes_context *ac;
52         struct ldb_handle *h;
53
54         h = talloc_zero(req, struct ldb_handle);
55         if (h == NULL) {
56                 ldb_set_errstring(module->ldb, "Out of Memory");
57                 return NULL;
58         }
59
60         h->module = module;
61
62         ac = talloc_zero(h, struct linked_attributes_context);
63         if (ac == NULL) {
64                 ldb_set_errstring(module->ldb, "Out of Memory");
65                 talloc_free(h);
66                 return NULL;
67         }
68
69         h->private_data = ac;
70
71         ac->module = module;
72         ac->handle = h;
73         ac->orig_req = req;
74
75         req->handle = h;
76
77         return ac;
78 }
79
80 /* Common routine to handle reading the attributes and creating a
81  * series of modify requests */
82
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) 
87 {
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++) {
94                 int otherid;
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);
99                 if (!schema_attr) {
100                         ldb_asprintf_errstring(ldb, 
101                                                "attribute %s is not a valid attribute in schema", el->name);
102                         return LDB_ERR_OBJECT_CLASS_VIOLATION;                  
103                 }
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) {
106                         continue;
107                 }
108                 
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;
112                 } else {
113                         otherid = schema_attr->linkID + 1;
114                 }
115                 
116                 /* Now find the target attribute */
117                 target_attr = dsdb_attribute_by_linkID(schema, otherid);
118                 if (!target_attr) {
119                         ldb_asprintf_errstring(ldb, 
120                                                "attribute %s does not have valid link target", el->name);
121                         return LDB_ERR_OBJECT_CLASS_VIOLATION;                  
122                 }
123                 
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);
130                         if (!new_msg) {
131                                 ldb_oom(ldb);
132                                 return LDB_ERR_OPERATIONS_ERROR;
133                         }
134                         new_msg->dn = ldb_dn_new(new_msg, ldb, (char *)el->values[j].data);
135                         if (!new_msg->dn) {
136                                 ldb_asprintf_errstring(ldb, 
137                                                        "attribute %s value %s was not a valid DN", msg->elements[i].name,
138                                                        el->values[j].data);
139                                 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
140                         }
141                         
142                         if (olddn) {
143                                 ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName, 
144                                                         LDB_FLAG_MOD_DELETE, &ret_el);
145                                 if (ret != LDB_SUCCESS) {
146                                         return ret;
147                                 }       
148                                 ret_el->values = talloc_array(new_msg, struct ldb_val, 1);
149                                 if (!ret_el->values) {
150                                         ldb_oom(ldb);
151                                         return LDB_ERR_OPERATIONS_ERROR;
152                                 }
153                                 ret_el->values[0] = data_blob_string_const(ldb_dn_get_linearized(olddn));
154                                 ret_el->num_values = 1;
155                         }
156                         
157                         if (newdn) {
158                                 ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName, 
159                                                         LDB_FLAG_MOD_ADD, &ret_el);
160                                 if (ret != LDB_SUCCESS) {
161                                         return ret;
162                                 }       
163                                 ret_el->values = talloc_array(new_msg, struct ldb_val, 1);
164                                 if (!ret_el->values) {
165                                         ldb_oom(ldb);
166                                         return LDB_ERR_OPERATIONS_ERROR;
167                                 }
168                                 ret_el->values[0] = data_blob_string_const(ldb_dn_get_linearized(newdn));
169                                 ret_el->num_values = 1;
170                         }
171
172                         ret = ldb_build_mod_req(&new_req, ldb, ac->down_req,
173                                                 new_msg,
174                                                 NULL,
175                                                 NULL,
176                                                 NULL);
177                         if (ret != LDB_SUCCESS) {
178                                 return ret;
179                         }
180                         
181                         talloc_steal(new_req, new_msg);
182                         
183                         ldb_set_timeout_from_prev_req(ldb, ac->orig_req, new_req);
184                         
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);
188                         if (!ac->down_req) {
189                                 ldb_oom(ldb);
190                                 return LDB_ERR_OPERATIONS_ERROR;
191                         }
192                         ac->down_req[ac->num_requests] = new_req;
193                         ac->num_requests++;
194                         
195                         /* Run the new request */
196                         ret = ldb_next_request(ac->module, new_req);
197                         if (ret != LDB_SUCCESS) {
198                                 return ret;
199                         }
200                 }
201         }
202         return ret;
203 }
204
205 /* add */
206 static int linked_attributes_add(struct ldb_module *module, struct ldb_request *req)
207 {
208         int i, ret;
209         struct linked_attributes_context *ac;
210
211         const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
212         if (!schema) {
213                 /* without schema, this doesn't make any sense */
214                 return ldb_next_request(module, req);
215         }
216
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);
220         }
221
222
223         ac = linked_attributes_init_handle(req, module);
224         if (!ac) {
225                 return LDB_ERR_OPERATIONS_ERROR;
226         }
227         
228         /* prepare the first operation */
229         ac->down_req = talloc_realloc(ac, ac->down_req, 
230                                       struct ldb_request *, 1);
231         if (!ac->down_req) {
232                 ldb_oom(ac->module->ldb);
233                 return LDB_ERR_OPERATIONS_ERROR;
234         }
235         
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;
240         }
241         *(ac->down_req[0]) = *req; /* copy the request */
242         
243         ac->num_requests++;
244         
245         /* Run the original request */
246         ret = ldb_next_request(module, ac->down_req[0]);
247         if (ret != LDB_SUCCESS) {
248                 return ret;
249         }
250
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);
256                 if (!schema_attr) {
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;                  
260                 }
261                 /* We have a valid attribute, not find out if it is linked */
262                 if (schema_attr->linkID == 0) {
263                         continue;
264                 }
265                 
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;
271                 }
272                 
273                 /* Even link IDs are for the originating attribute */
274         }
275         
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);
278 }
279
280 /* modify */
281 static int linked_attributes_modify(struct ldb_module *module, struct ldb_request *req)
282 {
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 */
287
288         int i, j, ret;
289         struct linked_attributes_context *ac;
290
291         const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
292         if (!schema) {
293                 /* without schema, this doesn't make any sense */
294                 return ldb_next_request(module, req);
295         }
296
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);
300         }
301
302
303         ac = linked_attributes_init_handle(req, module);
304         if (!ac) {
305                 return LDB_ERR_OPERATIONS_ERROR;
306         }
307         
308         /* prepare the first operation */
309         ac->down_req = talloc_realloc(ac, ac->down_req, 
310                                       struct ldb_request *, 1);
311         if (!ac->down_req) {
312                 ldb_oom(ac->module->ldb);
313                 return LDB_ERR_OPERATIONS_ERROR;
314         }
315         
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;
320         }
321         *(ac->down_req[0]) = *req; /* copy the request */
322         
323         ac->num_requests++;
324         
325         /* Run the original request */
326         ret = ldb_next_request(module, ac->down_req[0]);
327         if (ret != LDB_SUCCESS) {
328                 return ret;
329         }
330
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);
336                 if (!schema_attr) {
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;                  
340                 }
341                 /* We have a valid attribute, not find out if it is linked */
342                 if (schema_attr->linkID == 0) {
343                         continue;
344                 }
345                 
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;
351                 }
352                 
353                 /* Even link IDs are for the originating attribute */
354                 
355                 /* Now find the target attribute */
356                 target_attr = dsdb_attribute_by_linkID(schema, schema_attr->linkID + 1);
357                 if (!target_attr) {
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;                  
361                 }
362
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;
367                 }
368                 /* Prepare the modify (mod element) on the targets */
369
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);
375                         if (!new_msg) {
376                                 ldb_oom(module->ldb);
377                                 return LDB_ERR_OPERATIONS_ERROR;
378                         }
379                         new_msg->dn = ldb_dn_new(new_msg, module->ldb, (char *)el->values[j].data);
380                         if (!new_msg->dn) {
381                                 ldb_asprintf_errstring(module->ldb, 
382                                                "attribute %s value %s was not a valid DN", req->op.mod.message->elements[i].name,
383                                                        el->values[j].data);
384                                 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
385                         }
386
387                         ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName, 
388                                                 el->flags & LDB_FLAG_MOD_MASK, NULL);
389                         if (ret != LDB_SUCCESS) {
390                                 return ret;
391                         }
392                         
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) {
396                                 return ret;
397                         }
398
399                         ret = ldb_build_mod_req(&new_req, module->ldb, ac->down_req,
400                                                 new_msg,
401                                                 NULL,
402                                                 NULL,
403                                                 NULL);
404                         if (ret != LDB_SUCCESS) {
405                                 return ret;
406                         }
407                         
408                         talloc_steal(new_req, new_msg);
409                         
410                         ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
411                         
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);
415                         if (!ac->down_req) {
416                                 ldb_oom(ac->module->ldb);
417                                 return LDB_ERR_OPERATIONS_ERROR;
418                         }
419                         ac->down_req[ac->num_requests] = new_req;
420                         ac->num_requests++;
421
422                         /* Run the new request */
423                         ret = ldb_next_request(module, new_req);
424                         if (ret != LDB_SUCCESS) {
425                                 return ret;
426                         }
427                 }
428         }
429         return ret;
430 }
431
432 static int linked_attributes_rename_del_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares) 
433 {
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;
437     
438         switch (ac->orig_req->operation) {
439         case LDB_DELETE:
440         {
441                 olddn = ac->orig_req->op.del.dn;
442                 newdn = NULL;
443                 break;
444         } 
445         case LDB_RENAME:
446         {
447                 olddn = ac->orig_req->op.rename.olddn;
448                 newdn = ac->orig_req->op.rename.newdn;
449                 break;
450         }       
451         default:
452                 return LDB_ERR_OPERATIONS_ERROR;
453         }
454         
455
456         /* OK, we have one search result here: */
457
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);
465                 }
466                 talloc_free(ares);
467                 return LDB_SUCCESS;
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;
474                 talloc_free(ares);
475
476                 ac->down_req = talloc_realloc(ac, ac->down_req, 
477                                               struct ldb_request *, ac->num_requests + 1);
478                 if (!ac->down_req) {
479                         ldb_oom(ldb);
480                         return LDB_ERR_OPERATIONS_ERROR;
481                 }
482                 ac->down_req[ac->num_requests] = req;
483                 ac->num_requests++;
484                 
485                 return ldb_next_request(ac->module, req);
486
487         } else {
488                 talloc_free(ares);
489                 return LDB_SUCCESS;
490         }
491         
492         
493 }
494 /* rename */
495 static int linked_attributes_rename(struct ldb_module *module, struct ldb_request *req)
496 {
497         /* Look up list of linked attributes */
498         const char **attrs;
499         WERROR werr;
500         int ret;
501         struct linked_attributes_context *ac;
502         struct ldb_request *new_req;
503         const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
504         if (!schema) {
505                 /* without schema, this doesn't make any sense */
506                 return ldb_next_request(module, req);
507         }
508
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
514            - Regain our sainity 
515         */
516
517         ac = linked_attributes_init_handle(req, module);
518         if (!ac) {
519                 return LDB_ERR_OPERATIONS_ERROR;
520         }
521         
522         werr = dsdb_linked_attribute_lDAPDisplayName_list(schema, ac, &attrs);
523         if (!W_ERROR_IS_OK(werr)) {
524                 return LDB_ERR_OPERATIONS_ERROR;
525         }
526         
527         ret = ldb_build_search_req(&new_req, module->ldb, req,
528                                    req->op.rename.olddn, 
529                                    LDB_SCOPE_BASE,
530                                    "(objectClass=*)",
531                                    attrs,
532                                    NULL, 
533                                    ac, 
534                                    linked_attributes_rename_del_search_callback);
535
536         if (ret != LDB_SUCCESS) {
537                 return ret;
538         }
539
540         talloc_steal(new_req, attrs);
541
542         ac->down_req = talloc_realloc(ac, ac->down_req, 
543                                         struct ldb_request *, ac->num_requests + 1);
544         if (!ac->down_req) {
545                 ldb_oom(ac->module->ldb);
546                 return LDB_ERR_OPERATIONS_ERROR;
547         }
548         ac->down_req[ac->num_requests] = new_req;
549         if (req == NULL) {
550                 ldb_oom(ac->module->ldb);
551                 return LDB_ERR_OPERATIONS_ERROR;
552         }
553         ac->num_requests++;
554         return ldb_next_request(module, new_req);
555 }
556
557 /* delete */
558 static int linked_attributes_delete(struct ldb_module *module, struct ldb_request *req)
559 {
560         /* Look up list of linked attributes */
561         const char **attrs;
562         WERROR werr;
563         int ret;
564         struct ldb_request *new_req;
565         struct linked_attributes_context *ac;
566         const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
567         if (!schema) {
568                 /* without schema, this doesn't make any sense */
569                 return ldb_next_request(module, req);
570         }
571
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
577            - Regain our sainity 
578         */
579
580         ac = linked_attributes_init_handle(req, module);
581         if (!ac) {
582                 return LDB_ERR_OPERATIONS_ERROR;
583         }
584         
585         werr = dsdb_linked_attribute_lDAPDisplayName_list(schema, ac, &attrs);
586         if (!W_ERROR_IS_OK(werr)) {
587                 return LDB_ERR_OPERATIONS_ERROR;
588         };
589         
590         ret = ldb_build_search_req(&new_req, module->ldb, req,
591                                    req->op.del.dn, 
592                                    LDB_SCOPE_BASE,
593                                    "(objectClass=*)",
594                                    attrs,
595                                    NULL, 
596                                    ac, 
597                                    linked_attributes_rename_del_search_callback);
598
599         if (ret != LDB_SUCCESS) {
600                 return ret;
601         }
602
603         talloc_steal(new_req, attrs);
604
605         ac->down_req = talloc_realloc(ac, ac->down_req, 
606                                         struct ldb_request *, ac->num_requests + 1);
607         if (!ac->down_req) {
608                 ldb_oom(ac->module->ldb);
609                 return LDB_ERR_OPERATIONS_ERROR;
610         }
611         ac->down_req[ac->num_requests] = new_req;
612         if (req == NULL) {
613                 ldb_oom(ac->module->ldb);
614                 return LDB_ERR_OPERATIONS_ERROR;
615         }
616         ac->num_requests++;
617         return ldb_next_request(module, new_req);
618 }
619
620
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;
626         }
627
628         if (handle->state == LDB_ASYNC_DONE) {
629                 return handle->status;
630         }
631
632         handle->state = LDB_ASYNC_PENDING;
633         handle->status = LDB_SUCCESS;
634
635         ac = talloc_get_type(handle->private_data, struct linked_attributes_context);
636
637         for (i=0; i < ac->num_requests; i++) {
638                 ret = ldb_wait(ac->down_req[i]->handle, LDB_WAIT_NONE);
639                 
640                 if (ret != LDB_SUCCESS) {
641                         handle->status = ret;
642                         goto done;
643                 }
644                 if (ac->down_req[i]->handle->status != LDB_SUCCESS) {
645                         handle->status = ac->down_req[i]->handle->status;
646                         goto done;
647                 }
648                 
649                 if (ac->down_req[i]->handle->state != LDB_ASYNC_DONE) {
650                         return LDB_SUCCESS;
651                 }
652         }
653
654 done:
655         handle->state = LDB_ASYNC_DONE;
656         return ret;
657
658 }
659
660 static int linked_attributes_wait_all(struct ldb_handle *handle) {
661
662         int ret;
663
664         while (handle->state != LDB_ASYNC_DONE) {
665                 ret = linked_attributes_wait_none(handle);
666                 if (ret != LDB_SUCCESS) {
667                         return ret;
668                 }
669         }
670
671         return handle->status;
672 }
673
674 static int linked_attributes_wait(struct ldb_handle *handle, enum ldb_wait_type type)
675 {
676         if (type == LDB_WAIT_ALL) {
677                 return linked_attributes_wait_all(handle);
678         } else {
679                 return linked_attributes_wait_none(handle);
680         }
681 }
682
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,
690 };
691
692 int ldb_linked_attributes_init(void)
693 {
694         return ldb_register_module(&linked_attributes_ops);
695 }