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");
81 static int linked_attributes_add(struct ldb_module *module, struct ldb_request *req)
84 struct linked_attributes_context *ac;
86 const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
88 /* without schema, this doesn't make any sense */
89 return ldb_next_request(module, req);
92 if (ldb_dn_is_special(req->op.mod.message->dn)) {
93 /* do not manipulate our control entries */
94 return ldb_next_request(module, req);
98 ac = linked_attributes_init_handle(req, module);
100 return LDB_ERR_OPERATIONS_ERROR;
103 /* prepare the first operation */
104 ac->down_req = talloc_realloc(ac, ac->down_req,
105 struct ldb_request *, 1);
107 ldb_oom(ac->module->ldb);
108 return LDB_ERR_OPERATIONS_ERROR;
111 ac->down_req[0] = talloc(ac->down_req, struct ldb_request);
112 if (!ac->down_req[0]) {
113 ldb_oom(ac->module->ldb);
114 return LDB_ERR_OPERATIONS_ERROR;
116 *(ac->down_req[0]) = *req; /* copy the request */
120 /* Run the original request */
121 ret = ldb_next_request(module, req);
122 if (ret != LDB_SUCCESS) {
126 for (i=0; i < req->op.add.message->num_elements; i++) {
127 const struct dsdb_attribute *target_attr;
128 const struct ldb_message_element *el = &req->op.add.message->elements[i];
129 const struct dsdb_attribute *schema_attr
130 = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
132 ldb_asprintf_errstring(module->ldb,
133 "attribute %s is not a valid attribute in schema", req->op.add.message->elements[i].name);
134 return LDB_ERR_OBJECT_CLASS_VIOLATION;
136 /* We have a valid attribute, not find out if it is linked */
137 if (schema_attr->linkID == 0) {
141 if ((schema_attr->linkID & 1) == 1) {
142 /* Odd is for the target. Illigal to modify */
143 ldb_asprintf_errstring(module->ldb,
144 "attribute %s must not be modified directly, it is a linked attribute", req->op.add.message->elements[i].name);
145 return LDB_ERR_UNWILLING_TO_PERFORM;
148 /* Even link IDs are for the originating attribute */
150 /* Now find the target attribute */
151 target_attr = dsdb_attribute_by_linkID(schema, schema_attr->linkID + 1);
153 ldb_asprintf_errstring(module->ldb,
154 "attribute %s does not have valid link target", req->op.add.message->elements[i].name);
155 return LDB_ERR_OBJECT_CLASS_VIOLATION;
158 /* Prepare the modify (add element) on the targets */
160 /* For each value being added, we need to setup the modify */
161 for (j=0; j < el->num_values; j++) {
162 struct ldb_request *new_req;
163 /* Create the modify request */
164 struct ldb_message *new_msg = ldb_msg_new(ac->down_req);
166 ldb_oom(module->ldb);
167 return LDB_ERR_OPERATIONS_ERROR;
169 new_msg->dn = ldb_dn_new(new_msg, module->ldb, (char *)el->values[j].data);
171 ldb_asprintf_errstring(module->ldb,
172 "attribute %s value %s was not a valid DN", req->op.add.message->elements[i].name,
174 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
177 ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
178 LDB_FLAG_MOD_ADD, NULL);
179 if (ret != LDB_SUCCESS) {
183 ret = ldb_msg_add_string(new_msg, target_attr->lDAPDisplayName,
184 ldb_dn_get_linearized(ac->orig_req->op.add.message->dn));
185 if (ret != LDB_SUCCESS) {
189 ret = ldb_build_mod_req(&new_req, module->ldb, ac->down_req,
194 if (ret != LDB_SUCCESS) {
198 talloc_steal(new_req, new_msg);
200 ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
202 /* Now add it to the list */
203 ac->down_req = talloc_realloc(ac, ac->down_req,
204 struct ldb_request *, ac->num_requests + 1);
206 ldb_oom(ac->module->ldb);
207 return LDB_ERR_OPERATIONS_ERROR;
209 ac->down_req[ac->num_requests] = new_req;
212 /* Run the new request */
213 ret = ldb_next_request(module, new_req);
214 if (ret != LDB_SUCCESS) {
223 static int linked_attributes_modify(struct ldb_module *module, struct ldb_request *req)
225 /* Look over list of modifications */
226 /* Find if any are for linked attributes */
227 /* Determine the effect of the modification */
228 /* Apply the modify to the linked entry */
231 struct linked_attributes_context *ac;
233 const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
235 /* without schema, this doesn't make any sense */
236 return ldb_next_request(module, req);
239 if (ldb_dn_is_special(req->op.mod.message->dn)) {
240 /* do not manipulate our control entries */
241 return ldb_next_request(module, req);
245 ac = linked_attributes_init_handle(req, module);
247 return LDB_ERR_OPERATIONS_ERROR;
250 /* prepare the first operation */
251 ac->down_req = talloc_realloc(ac, ac->down_req,
252 struct ldb_request *, 1);
254 ldb_oom(ac->module->ldb);
255 return LDB_ERR_OPERATIONS_ERROR;
258 ac->down_req[0] = talloc(ac->down_req, struct ldb_request);
259 if (!ac->down_req[0]) {
260 ldb_oom(ac->module->ldb);
261 return LDB_ERR_OPERATIONS_ERROR;
263 *(ac->down_req[0]) = *req; /* copy the request */
267 /* Run the original request */
268 ret = ldb_next_request(module, req);
269 if (ret != LDB_SUCCESS) {
273 for (i=0; i < req->op.mod.message->num_elements; i++) {
274 const struct dsdb_attribute *target_attr;
275 const struct ldb_message_element *el = &req->op.mod.message->elements[i];
276 const struct dsdb_attribute *schema_attr
277 = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
279 ldb_asprintf_errstring(module->ldb,
280 "attribute %s is not a valid attribute in schema", req->op.mod.message->elements[i].name);
281 return LDB_ERR_OBJECT_CLASS_VIOLATION;
283 /* We have a valid attribute, not find out if it is linked */
284 if (schema_attr->linkID == 0) {
288 if ((schema_attr->linkID & 1) == 1) {
289 /* Odd is for the target. Illigal to modify */
290 ldb_asprintf_errstring(module->ldb,
291 "attribute %s must not be modified directly, it is a linked attribute", req->op.mod.message->elements[i].name);
292 return LDB_ERR_UNWILLING_TO_PERFORM;
295 /* Even link IDs are for the originating attribute */
297 /* Now find the target attribute */
298 target_attr = dsdb_attribute_by_linkID(schema, schema_attr->linkID + 1);
300 ldb_asprintf_errstring(module->ldb,
301 "attribute %s does not have valid link target", req->op.mod.message->elements[i].name);
302 return LDB_ERR_OBJECT_CLASS_VIOLATION;
305 if ((el->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_REPLACE) {
306 ldb_asprintf_errstring(module->ldb,
307 "attribute %s may not be replaced, only added or deleted", req->op.mod.message->elements[i].name);
308 return LDB_ERR_UNWILLING_TO_PERFORM;
310 /* Prepare the modify (mod element) on the targets */
312 /* For each value being moded, we need to setup the modify */
313 for (j=0; j < el->num_values; j++) {
314 struct ldb_request *new_req;
315 /* Create the modify request */
316 struct ldb_message *new_msg = ldb_msg_new(ac->down_req);
318 ldb_oom(module->ldb);
319 return LDB_ERR_OPERATIONS_ERROR;
321 new_msg->dn = ldb_dn_new(new_msg, module->ldb, (char *)el->values[j].data);
323 ldb_asprintf_errstring(module->ldb,
324 "attribute %s value %s was not a valid DN", req->op.mod.message->elements[i].name,
326 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
329 ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
330 el->flags & LDB_FLAG_MOD_MASK, NULL);
331 if (ret != LDB_SUCCESS) {
335 ret = ldb_msg_add_string(new_msg, target_attr->lDAPDisplayName,
336 ldb_dn_get_linearized(ac->orig_req->op.add.message->dn));
337 if (ret != LDB_SUCCESS) {
341 ret = ldb_build_mod_req(&new_req, module->ldb, ac->down_req,
346 if (ret != LDB_SUCCESS) {
350 talloc_steal(new_req, new_msg);
352 ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
354 /* Now add it to the list */
355 ac->down_req = talloc_realloc(ac, ac->down_req,
356 struct ldb_request *, ac->num_requests + 1);
358 ldb_oom(ac->module->ldb);
359 return LDB_ERR_OPERATIONS_ERROR;
361 ac->down_req[ac->num_requests] = new_req;
364 /* Run the new request */
365 ret = ldb_next_request(module, new_req);
366 if (ret != LDB_SUCCESS) {
374 static int setup_modifies(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
375 struct linked_attributes_context *ac,
376 struct ldb_message *msg,
377 struct ldb_dn *olddn, struct ldb_dn *newdn)
379 int i, j, ret = LDB_SUCCESS;
380 const struct dsdb_schema *schema = dsdb_get_schema(ldb);
381 /* Look up each of the returned attributes */
382 /* Find their schema */
383 /* And it is an actual entry: now create a series of modify requests */
384 for (i=0; i < msg->num_elements; i++) {
386 const struct dsdb_attribute *target_attr;
387 const struct ldb_message_element *el = &msg->elements[i];
388 const struct dsdb_attribute *schema_attr
389 = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
391 ldb_asprintf_errstring(ldb,
392 "attribute %s is not a valid attribute in schema", el->name);
393 return LDB_ERR_OBJECT_CLASS_VIOLATION;
395 /* We have a valid attribute, but if it's not linked they maybe we just got an extra return on our search... */
396 if (schema_attr->linkID == 0) {
400 /* Depending on which direction this link is in, we need to find it's partner */
401 if ((schema_attr->linkID & 1) == 1) {
402 otherid = schema_attr->linkID - 1;
404 otherid = schema_attr->linkID + 1;
407 /* Now find the target attribute */
408 target_attr = dsdb_attribute_by_linkID(schema, otherid);
410 ldb_asprintf_errstring(ldb,
411 "attribute %s does not have valid link target", el->name);
412 return LDB_ERR_OBJECT_CLASS_VIOLATION;
415 /* For each value being moded, we need to setup the modify */
416 for (j=0; j < el->num_values; j++) {
417 struct ldb_message_element *ret_el;
418 struct ldb_request *new_req;
419 /* Create the modify request */
420 struct ldb_message *new_msg = ldb_msg_new(ac->down_req);
423 return LDB_ERR_OPERATIONS_ERROR;
425 new_msg->dn = ldb_dn_new(new_msg, ldb, (char *)el->values[j].data);
427 ldb_asprintf_errstring(ldb,
428 "attribute %s value %s was not a valid DN", msg->elements[i].name,
430 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
434 ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
435 LDB_FLAG_MOD_DELETE, &ret_el);
436 if (ret != LDB_SUCCESS) {
439 ret_el->values = talloc_array(new_msg, struct ldb_val, 1);
440 if (!ret_el->values) {
442 return LDB_ERR_OPERATIONS_ERROR;
444 ret_el->values[0] = data_blob_string_const(ldb_dn_get_linearized(olddn));
445 ret_el->num_values = 1;
449 ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
450 LDB_FLAG_MOD_ADD, &ret_el);
451 if (ret != LDB_SUCCESS) {
454 ret_el->values = talloc_array(new_msg, struct ldb_val, 1);
455 if (!ret_el->values) {
457 return LDB_ERR_OPERATIONS_ERROR;
459 ret_el->values[0] = data_blob_string_const(ldb_dn_get_linearized(newdn));
460 ret_el->num_values = 1;
463 ret = ldb_build_mod_req(&new_req, ldb, ac->down_req,
468 if (ret != LDB_SUCCESS) {
472 talloc_steal(new_req, new_msg);
474 ldb_set_timeout_from_prev_req(ldb, ac->orig_req, new_req);
476 /* Now add it to the list */
477 ac->down_req = talloc_realloc(ac, ac->down_req,
478 struct ldb_request *, ac->num_requests + 1);
481 return LDB_ERR_OPERATIONS_ERROR;
483 ac->down_req[ac->num_requests] = new_req;
486 /* Run the new request */
487 ret = ldb_next_request(ac->module, new_req);
488 if (ret != LDB_SUCCESS) {
496 static int linked_attributes_rename_del_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
498 struct ldb_request *req;
499 struct linked_attributes_context *ac = talloc_get_type(context, struct linked_attributes_context);
500 struct ldb_dn *olddn, *newdn;
501 TALLOC_CTX *mem_ctx = talloc_new(ac);
505 return LDB_ERR_OPERATIONS_ERROR;
508 switch (ac->orig_req->operation) {
511 olddn = ac->orig_req->op.del.dn;
517 olddn = ac->orig_req->op.rename.olddn;
518 newdn = ac->orig_req->op.rename.newdn;
522 return LDB_ERR_OPERATIONS_ERROR;
526 /* OK, we have one search result here: */
528 /* Only entries are interesting, and we only want the olddn */
529 if (ares->type == LDB_REPLY_ENTRY
530 && ldb_dn_compare(ares->message->dn, olddn) == 0) {
531 /* only bother at all if there were some linked attributes found */
532 if (ares->message->num_elements > 0) {
533 return setup_modifies(ldb, mem_ctx, ac,
534 ares->message, olddn, newdn);
538 } else if (ares->type == LDB_REPLY_ENTRY) {
539 /* Guh? We only asked for this DN */
540 return LDB_ERR_OPERATIONS_ERROR;
541 } else if (ares->type == LDB_REPLY_DONE) {
542 req = talloc(mem_ctx, struct ldb_request);
543 *req = *ac->orig_req;
546 ac->down_req = talloc_realloc(ac, ac->down_req,
547 struct ldb_request *, ac->num_requests + 1);
550 return LDB_ERR_OPERATIONS_ERROR;
552 ac->down_req[ac->num_requests] = req;
555 return ldb_next_request(ac->module, req);
565 static int linked_attributes_rename(struct ldb_module *module, struct ldb_request *req)
567 /* Look up list of linked attributes */
571 struct linked_attributes_context *ac;
572 struct ldb_request *new_req;
573 const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
575 /* without schema, this doesn't make any sense */
576 return ldb_next_request(module, req);
579 /* This gets complex: We need to:
580 - Do a search for the entry
581 - Wait for these result to appear
582 - In the callback for the result, issue a modify request based on the linked attributes found
583 - Wait for each modify result
587 ac = linked_attributes_init_handle(req, module);
589 return LDB_ERR_OPERATIONS_ERROR;
592 werr = dsdb_linked_attribute_lDAPDisplayName_list(schema, ac, &attrs);
593 if (!W_ERROR_IS_OK(werr)) {
594 return LDB_ERR_OPERATIONS_ERROR;
597 ret = ldb_build_search_req(&new_req, module->ldb, req,
598 req->op.rename.olddn,
604 linked_attributes_rename_del_search_callback);
606 if (ret != LDB_SUCCESS) {
610 talloc_steal(new_req, attrs);
612 ac->down_req = talloc_realloc(ac, ac->down_req,
613 struct ldb_request *, ac->num_requests + 1);
615 ldb_oom(ac->module->ldb);
616 return LDB_ERR_OPERATIONS_ERROR;
618 ac->down_req[ac->num_requests] = new_req;
620 ldb_oom(ac->module->ldb);
621 return LDB_ERR_OPERATIONS_ERROR;
624 return ldb_next_request(module, new_req);
628 static int linked_attributes_delete(struct ldb_module *module, struct ldb_request *req)
630 /* Look up list of linked attributes */
634 struct ldb_request *new_req;
635 struct linked_attributes_context *ac;
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,
667 linked_attributes_rename_del_search_callback);
669 if (ret != LDB_SUCCESS) {
673 talloc_steal(new_req, attrs);
675 ac->down_req = talloc_realloc(ac, ac->down_req,
676 struct ldb_request *, ac->num_requests + 1);
678 ldb_oom(ac->module->ldb);
679 return LDB_ERR_OPERATIONS_ERROR;
681 ac->down_req[ac->num_requests] = new_req;
683 ldb_oom(ac->module->ldb);
684 return LDB_ERR_OPERATIONS_ERROR;
687 return ldb_next_request(module, new_req);
691 static int linked_attributes_wait_none(struct ldb_handle *handle) {
692 struct linked_attributes_context *ac;
693 int i, ret = LDB_ERR_OPERATIONS_ERROR;
694 if (!handle || !handle->private_data) {
695 return LDB_ERR_OPERATIONS_ERROR;
698 if (handle->state == LDB_ASYNC_DONE) {
699 return handle->status;
702 handle->state = LDB_ASYNC_PENDING;
703 handle->status = LDB_SUCCESS;
705 ac = talloc_get_type(handle->private_data, struct linked_attributes_context);
707 for (i=0; i < ac->num_requests; i++) {
708 ret = ldb_wait(ac->down_req[i]->handle, LDB_WAIT_NONE);
710 if (ret != LDB_SUCCESS) {
711 handle->status = ret;
714 if (ac->down_req[i]->handle->status != LDB_SUCCESS) {
715 handle->status = ac->down_req[i]->handle->status;
719 if (ac->down_req[i]->handle->state != LDB_ASYNC_DONE) {
725 handle->state = LDB_ASYNC_DONE;
730 static int linked_attributes_wait_all(struct ldb_handle *handle) {
734 while (handle->state != LDB_ASYNC_DONE) {
735 ret = linked_attributes_wait_none(handle);
736 if (ret != LDB_SUCCESS) {
741 return handle->status;
744 static int linked_attributes_wait(struct ldb_handle *handle, enum ldb_wait_type type)
746 if (type == LDB_WAIT_ALL) {
747 return linked_attributes_wait_all(handle);
749 return linked_attributes_wait_none(handle);
753 static const struct ldb_module_ops linked_attributes_ops = {
754 .name = "linked_attributes",
755 .add = linked_attributes_add,
756 .modify = linked_attributes_modify,
757 .del = linked_attributes_delete,
758 .rename = linked_attributes_rename,
759 .wait = linked_attributes_wait,
762 int ldb_linked_attributes_init(void)
764 return ldb_register_module(&linked_attributes_ops);