4 Copyright (C) Simo Sorce 2005-2008
5 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007-2009
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 * Component: ldb extended dn control module
26 * Description: this module builds a special dn for returned search
27 * results nad creates the special DN in the backend store for new
30 * This also has the curious result that we convert <SID=S-1-2-345>
31 * in an attribute value into a normal DN for the rest of the stack
40 #include <ldb_errors.h>
41 #include <ldb_module.h>
42 #include "librpc/gen_ndr/ndr_misc.h"
43 #include "dsdb/samdb/samdb.h"
44 #include "libcli/security/security.h"
45 #include "dsdb/samdb/ldb_modules/util.h"
48 struct extended_dn_replace_list {
49 struct extended_dn_replace_list *next;
50 struct dsdb_dn *dsdb_dn;
52 struct ldb_val *replace_dn;
53 struct extended_dn_context *ac;
54 struct ldb_request *search_req;
61 struct extended_dn_context {
62 const struct dsdb_schema *schema;
63 struct ldb_module *module;
64 struct ldb_context *ldb;
65 struct ldb_request *req;
66 struct ldb_request *new_req;
68 struct extended_dn_replace_list *ops;
69 struct extended_dn_replace_list *cur;
73 static struct extended_dn_context *extended_dn_context_init(struct ldb_module *module,
74 struct ldb_request *req)
76 struct extended_dn_context *ac;
77 struct ldb_context *ldb = ldb_module_get_ctx(module);
78 ac = talloc_zero(req, struct extended_dn_context);
84 ac->schema = dsdb_get_schema(ldb_module_get_ctx(module), ac);
92 static int extended_replace_dn(struct extended_dn_replace_list *os,
95 struct dsdb_dn *dsdb_dn = NULL;
96 const char *str = NULL;
99 * Rebuild with the string or binary 'extra part' the
100 * DN may have had as a prefix
102 dsdb_dn = dsdb_dn_construct(os, dn,
103 os->dsdb_dn->extra_part,
105 if (dsdb_dn == NULL) {
106 return ldb_module_operr(os->ac->module);
109 str = dsdb_dn_get_extended_linearized(os->mem_ctx,
112 return ldb_module_operr(os->ac->module);
116 * Replace the DN with the extended version of the DN
117 * (ie, add SID and GUID)
119 *os->replace_dn = data_blob_string_const(str);
120 os->got_entry = true;
124 /* An extra layer of indirection because LDB does not allow the original request to be altered */
126 static int extended_final_callback(struct ldb_request *req, struct ldb_reply *ares)
128 int ret = LDB_ERR_OPERATIONS_ERROR;
129 struct extended_dn_context *ac;
130 ac = talloc_get_type(req->context, struct extended_dn_context);
132 if (ares->error != LDB_SUCCESS) {
133 ret = ldb_module_done(ac->req, ares->controls,
134 ares->response, ares->error);
136 switch (ares->type) {
137 case LDB_REPLY_ENTRY:
139 ret = ldb_module_send_entry(ac->req, ares->message, ares->controls);
141 case LDB_REPLY_REFERRAL:
143 ret = ldb_module_send_referral(ac->req, ares->referral);
147 ret = ldb_module_done(ac->req, ares->controls,
148 ares->response, ares->error);
155 static int extended_replace_callback(struct ldb_request *req, struct ldb_reply *ares)
157 struct extended_dn_replace_list *os = talloc_get_type(req->context,
158 struct extended_dn_replace_list);
161 return ldb_module_done(os->ac->req, NULL, NULL,
162 LDB_ERR_OPERATIONS_ERROR);
164 if (ares->error == LDB_ERR_NO_SUCH_OBJECT) {
166 /* This is in internal error... */
167 int ret = ldb_module_operr(os->ac->module);
168 return ldb_module_done(os->ac->req, NULL, NULL, ret);
171 if (os->require_object && os->fpo_enabled) {
175 * It's an error if the target doesn't exist,
176 * unless it's a delete.
178 * Note FPO-enabled attributes generate
181 ret = dsdb_module_werror(os->ac->module,
182 LDB_ERR_NO_SUCH_OBJECT,
184 "specified dn doesn't exist");
186 return ldb_module_done(os->ac->req, NULL, NULL,
190 if (!os->got_entry && os->require_object) {
192 * It's an error if the target doesn't exist,
193 * unless it's a delete.
195 int ret = dsdb_module_werror(os->ac->module,
196 LDB_ERR_CONSTRAINT_VIOLATION,
197 WERR_DS_NAME_REFERENCE_INVALID,
198 "Referenced object not found");
199 return ldb_module_done(os->ac->req, NULL, NULL, ret);
202 /* Don't worry too much about dangling references */
204 ldb_reset_err_string(os->ac->ldb);
206 struct extended_dn_replace_list *next;
213 return ldb_next_request(os->ac->module, next->search_req);
215 /* Otherwise, we are done - let's run the
216 * request now we have swapped the DNs for the
218 return ldb_next_request(os->ac->module, os->ac->new_req);
221 if (ares->error != LDB_SUCCESS) {
222 return ldb_module_done(os->ac->req, ares->controls,
223 ares->response, ares->error);
226 /* Only entries are interesting, and we only want the olddn */
227 switch (ares->type) {
228 case LDB_REPLY_ENTRY:
230 /* This *must* be the right DN, as this is a base
231 * search. We can't check, as it could be an extended
232 * DN, so a module below will resolve it */
235 ret = extended_replace_dn(os, ares->message->dn);
236 if (ret != LDB_SUCCESS) {
237 return ldb_module_done(os->ac->req, NULL, NULL, ret);
239 /* os->got_entry is true at this point */
242 case LDB_REPLY_REFERRAL:
250 if (!os->got_entry && os->require_object && os->fpo_enabled) {
254 * It's an error if the target doesn't exist,
255 * unless it's a delete.
257 * Note FPO-enabled attributes generate
260 ret = dsdb_module_werror(os->ac->module,
261 LDB_ERR_NO_SUCH_OBJECT,
263 "specified dn doesn't exist");
265 return ldb_module_done(os->ac->req, NULL, NULL,
269 if (!os->got_entry && os->require_object) {
271 * It's an error if the target doesn't exist,
272 * unless it's a delete.
274 int ret = dsdb_module_werror(os->ac->module,
275 LDB_ERR_CONSTRAINT_VIOLATION,
276 WERR_DS_NAME_REFERENCE_INVALID,
277 "Referenced object not found");
278 return ldb_module_done(os->ac->req, NULL, NULL, ret);
281 /* Run the next search */
284 struct extended_dn_replace_list *next;
291 return ldb_next_request(os->ac->module, next->search_req);
293 /* Otherwise, we are done - let's run the
294 * request now we have swapped the DNs for the
296 return ldb_next_request(os->ac->module, os->ac->new_req);
304 /* We have a 'normal' DN in the inbound request. We need to find out
305 * what the GUID and SID are on the DN it points to, so we can
306 * construct an extended DN for storage.
308 * This creates a list of DNs to look up, and the plain DN to replace
311 static int extended_store_replace(struct extended_dn_context *ac,
312 TALLOC_CTX *callback_mem_ctx,
313 struct ldb_dn *self_dn,
314 struct ldb_val *plain_dn,
316 const struct dsdb_attribute *schema_attr)
318 const char *oid = schema_attr->syntax->ldap_oid;
320 struct extended_dn_replace_list *os;
321 static const char *attrs[] = {
326 uint32_t ctrl_flags = 0;
327 bool is_untrusted = ldb_req_is_untrusted(ac->req);
329 os = talloc_zero(ac, struct extended_dn_replace_list);
331 return ldb_oom(ac->ldb);
336 os->mem_ctx = callback_mem_ctx;
338 os->dsdb_dn = dsdb_dn_parse(os, ac->ldb, plain_dn, oid);
339 if (!os->dsdb_dn || !ldb_dn_validate(os->dsdb_dn->dn)) {
341 ldb_asprintf_errstring(ac->ldb,
342 "could not parse %.*s as a %s DN", (int)plain_dn->length, plain_dn->data,
344 return LDB_ERR_INVALID_DN_SYNTAX;
347 if (self_dn != NULL) {
348 ret = ldb_dn_compare(self_dn, os->dsdb_dn->dn);
351 * If this is a reference to the object
352 * itself during an 'add', we won't
353 * be able to find the object.
360 if (is_delete && !ldb_dn_has_extended(os->dsdb_dn->dn)) {
361 /* NO need to figure this DN out, this element is
362 * going to be deleted anyway, and becuase it's not
363 * extended, we have enough information to do the
370 os->replace_dn = plain_dn;
372 /* The search request here might happen to be for an
373 * 'extended' style DN, such as <GUID=abced...>. The next
374 * module in the stack will convert this into a normal DN for
376 ret = ldb_build_search_req(&os->search_req,
377 ac->ldb, os, os->dsdb_dn->dn, LDB_SCOPE_BASE, NULL,
378 attrs, NULL, os, extended_replace_callback,
380 LDB_REQ_SET_LOCATION(os->search_req);
381 if (ret != LDB_SUCCESS) {
387 * By default we require the presence of the target.
389 os->require_object = true;
392 * Handle FPO-enabled attributes cause a different
395 switch (schema_attr->attributeID_id) {
396 case DRSUAPI_ATTID_member:
397 case DRSUAPI_ATTID_msDS_NonMembers:
398 case DRSUAPI_ATTID_msDS_MembersForAzRole:
399 case DRSUAPI_ATTID_msDS_NeverRevealGroup:
400 case DRSUAPI_ATTID_msDS_RevealOnDemandGroup:
401 case DRSUAPI_ATTID_msDS_HostServiceAccount:
402 os->fpo_enabled = true;
406 if (schema_attr->linkID == 0) {
408 * None linked attributes allow references
409 * to deleted objects.
411 ctrl_flags |= DSDB_SEARCH_SHOW_RECYCLED;
416 * On delete want to be able to
417 * find a deleted object, but
418 * it's not a problem if they doesn't
421 ctrl_flags |= DSDB_SEARCH_SHOW_RECYCLED;
422 os->require_object = false;
426 struct ldb_control *ctrl = NULL;
429 * During provision or dbcheck we may not find
433 ctrl = ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID);
435 os->require_object = false;
437 ctrl = ldb_request_get_control(ac->req, DSDB_CONTROL_DBCHECK);
439 os->require_object = false;
443 ret = dsdb_request_add_controls(os->search_req,
444 DSDB_FLAG_AS_SYSTEM |
446 DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT);
447 if (ret != LDB_SUCCESS) {
464 static int extended_dn_add(struct ldb_module *module, struct ldb_request *req)
466 struct extended_dn_context *ac;
470 if (ldb_dn_is_special(req->op.add.message->dn)) {
471 /* do not manipulate our control entries */
472 return ldb_next_request(module, req);
475 ac = extended_dn_context_init(module, req);
477 return ldb_operr(ldb_module_get_ctx(module));
481 /* without schema, this doesn't make any sense */
483 return ldb_next_request(module, req);
486 for (i=0; i < req->op.add.message->num_elements; i++) {
487 const struct ldb_message_element *el = &req->op.add.message->elements[i];
488 const struct dsdb_attribute *schema_attr
489 = dsdb_attribute_by_lDAPDisplayName(ac->schema, el->name);
494 /* We only setup an extended DN GUID on DN elements */
495 if (schema_attr->dn_format == DSDB_INVALID_DN) {
499 if (schema_attr->attributeID_id == DRSUAPI_ATTID_distinguishedName) {
500 /* distinguishedName values are ignored */
504 /* Before we setup a procedure to modify the incoming message, we must copy it */
506 struct ldb_message *msg = ldb_msg_copy(ac, req->op.add.message);
508 return ldb_oom(ldb_module_get_ctx(module));
511 ret = ldb_build_add_req(&ac->new_req, ac->ldb, ac, msg, req->controls, ac, extended_final_callback, req);
512 LDB_REQ_SET_LOCATION(ac->new_req);
513 if (ret != LDB_SUCCESS) {
517 /* Re-calculate el */
518 el = &ac->new_req->op.add.message->elements[i];
519 for (j = 0; j < el->num_values; j++) {
520 ret = extended_store_replace(ac, ac->new_req,
521 req->op.add.message->dn,
524 if (ret != LDB_SUCCESS) {
530 /* if no DNs were set continue */
531 if (ac->ops == NULL) {
533 return ldb_next_request(module, req);
536 /* start with the searches */
537 return ldb_next_request(module, ac->ops->search_req);
541 static int extended_dn_modify(struct ldb_module *module, struct ldb_request *req)
543 /* Look over list of modifications */
544 /* Find if any are for linked attributes */
545 /* Determine the effect of the modification */
546 /* Apply the modify to the linked entry */
549 struct extended_dn_context *ac;
550 struct ldb_control *fix_links_control = NULL;
553 if (ldb_dn_is_special(req->op.mod.message->dn)) {
554 /* do not manipulate our control entries */
555 return ldb_next_request(module, req);
558 ac = extended_dn_context_init(module, req);
560 return ldb_operr(ldb_module_get_ctx(module));
565 /* without schema, this doesn't make any sense */
566 return ldb_next_request(module, req);
569 fix_links_control = ldb_request_get_control(req,
570 DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS);
571 if (fix_links_control != NULL) {
572 return ldb_next_request(module, req);
575 for (i=0; i < req->op.mod.message->num_elements; i++) {
576 const struct ldb_message_element *el = &req->op.mod.message->elements[i];
577 const struct dsdb_attribute *schema_attr
578 = dsdb_attribute_by_lDAPDisplayName(ac->schema, el->name);
583 /* We only setup an extended DN GUID on these particular DN objects */
584 if (schema_attr->dn_format == DSDB_INVALID_DN) {
588 if (schema_attr->attributeID_id == DRSUAPI_ATTID_distinguishedName) {
589 /* distinguishedName values are ignored */
593 /* Before we setup a procedure to modify the incoming message, we must copy it */
595 struct ldb_message *msg = ldb_msg_copy(ac, req->op.mod.message);
598 return ldb_oom(ac->ldb);
601 ret = ldb_build_mod_req(&ac->new_req, ac->ldb, ac, msg, req->controls, ac, extended_final_callback, req);
602 LDB_REQ_SET_LOCATION(ac->new_req);
603 if (ret != LDB_SUCCESS) {
608 /* Re-calculate el */
609 el = &ac->new_req->op.mod.message->elements[i];
610 /* For each value being added, we need to setup the lookups to fill in the extended DN */
611 for (j = 0; j < el->num_values; j++) {
612 /* If we are just going to delete this
613 * element, only do a lookup if
614 * extended_store_replace determines it's an
615 * input of an extended DN */
616 bool is_delete = (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE);
618 ret = extended_store_replace(ac, ac->new_req,
619 NULL, /* self_dn to be ignored */
621 is_delete, schema_attr);
622 if (ret != LDB_SUCCESS) {
629 /* if DNs were set continue */
630 if (ac->ops == NULL) {
632 return ldb_next_request(module, req);
635 /* start with the searches */
636 return ldb_next_request(module, ac->ops->search_req);
639 static const struct ldb_module_ops ldb_extended_dn_store_module_ops = {
640 .name = "extended_dn_store",
641 .add = extended_dn_add,
642 .modify = extended_dn_modify,
645 int ldb_extended_dn_store_module_init(const char *version)
647 LDB_MODULE_CHECK_VERSION(version);
648 return ldb_register_module(&ldb_extended_dn_store_module_ops);