4 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2010
5 Copyright (C) Andrew Tridgell 2005
6 Copyright (C) Simo Sorce 2006-2008
7 Copyright (C) Matthias Dieter Wallnöfer 2009
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 handle operational attributes
28 createTimestamp: HIDDEN, searchable, ldaptime, alias for whenCreated
29 modifyTimestamp: HIDDEN, searchable, ldaptime, alias for whenChanged
31 for the above two, we do the search as normal, and if
32 createTimestamp or modifyTimestamp is asked for, then do
33 additional searches for whenCreated and whenChanged and fill in
36 we also need to replace these with the whenCreated/whenChanged
37 equivalent in the search expression trees
39 whenCreated: not-HIDDEN, CONSTRUCTED, SEARCHABLE
40 whenChanged: not-HIDDEN, CONSTRUCTED, SEARCHABLE
42 on init we need to setup attribute handlers for these so
43 comparisons are done correctly. The resolution is 1 second.
45 on add we need to add both the above, for current time
47 on modify we need to change whenChanged
49 structuralObjectClass: HIDDEN, CONSTRUCTED, not-searchable. always same as objectclass?
51 for this one we do the search as normal, then if requested ask
52 for objectclass, change the attribute name, and add it
54 primaryGroupToken: HIDDEN, CONSTRUCTED, SEARCHABLE
56 contains the RID of a certain group object
59 attributeTypes: in schema only
60 objectClasses: in schema only
61 matchingRules: in schema only
62 matchingRuleUse: in schema only
63 creatorsName: not supported by w2k3?
64 modifiersName: not supported by w2k3?
69 #include <ldb_module.h>
71 #include "librpc/gen_ndr/ndr_misc.h"
72 #include "librpc/gen_ndr/ndr_drsblobs.h"
73 #include "param/param.h"
74 #include "dsdb/samdb/samdb.h"
75 #include "dsdb/samdb/ldb_modules/util.h"
77 #include "auth/auth.h"
78 #include "libcli/security/security.h"
81 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
84 struct operational_data {
85 struct ldb_dn *aggregate_dn;
89 construct a canonical name from a message
91 static int construct_canonical_name(struct ldb_module *module,
92 struct ldb_message *msg, enum ldb_scope scope,
93 struct ldb_request *parent)
96 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
97 if (canonicalName == NULL) {
98 return ldb_operr(ldb_module_get_ctx(module));
100 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
104 construct a primary group token for groups from a message
106 static int construct_primary_group_token(struct ldb_module *module,
107 struct ldb_message *msg, enum ldb_scope scope,
108 struct ldb_request *parent)
110 struct ldb_context *ldb;
111 uint32_t primary_group_token;
113 ldb = ldb_module_get_ctx(module);
114 if (ldb_match_msg_objectclass(msg, "group") == 1) {
116 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
117 if (primary_group_token == 0) {
121 return samdb_msg_add_int(ldb, msg, msg, "primaryGroupToken",
122 primary_group_token);
129 construct the token groups for SAM objects from a message
131 static int construct_token_groups(struct ldb_module *module,
132 struct ldb_message *msg, enum ldb_scope scope,
133 struct ldb_request *parent)
135 struct ldb_context *ldb = ldb_module_get_ctx(module);;
136 TALLOC_CTX *tmp_ctx = talloc_new(msg);
143 struct dom_sid *primary_group_sid;
144 const char *primary_group_string;
145 const char *primary_group_dn;
146 DATA_BLOB primary_group_blob;
148 struct dom_sid *account_sid;
149 const char *account_sid_string;
150 const char *account_sid_dn;
151 DATA_BLOB account_sid_blob;
152 struct dom_sid **groupSIDs = NULL;
153 unsigned int num_groupSIDs = 0;
155 struct dom_sid *domain_sid;
157 if (scope != LDB_SCOPE_BASE) {
158 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
159 return LDB_ERR_OPERATIONS_ERROR;
162 /* If it's not a user, it won't have a primaryGroupID */
163 if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
164 talloc_free(tmp_ctx);
168 /* Ensure it has an objectSID too */
169 account_sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid");
170 if (account_sid == NULL) {
171 talloc_free(tmp_ctx);
175 status = dom_sid_split_rid(tmp_ctx, account_sid, &domain_sid, NULL);
176 if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
177 talloc_free(tmp_ctx);
178 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
179 } else if (!NT_STATUS_IS_OK(status)) {
180 talloc_free(tmp_ctx);
181 return LDB_ERR_OPERATIONS_ERROR;
184 primary_group_sid = dom_sid_add_rid(tmp_ctx,
186 ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
187 if (!primary_group_sid) {
188 talloc_free(tmp_ctx);
192 /* only return security groups */
193 filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
194 GROUP_TYPE_SECURITY_ENABLED);
196 talloc_free(tmp_ctx);
200 primary_group_string = dom_sid_string(tmp_ctx, primary_group_sid);
201 if (!primary_group_string) {
202 talloc_free(tmp_ctx);
206 primary_group_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", primary_group_string);
207 if (!primary_group_dn) {
208 talloc_free(tmp_ctx);
212 primary_group_blob = data_blob_string_const(primary_group_dn);
214 account_sid_string = dom_sid_string(tmp_ctx, account_sid);
215 if (!account_sid_string) {
216 talloc_free(tmp_ctx);
220 account_sid_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", account_sid_string);
221 if (!account_sid_dn) {
222 talloc_free(tmp_ctx);
226 account_sid_blob = data_blob_string_const(account_sid_dn);
228 status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
229 true, /* We don't want to add the object's SID itself,
230 it's not returend in this attribute */
232 tmp_ctx, &groupSIDs, &num_groupSIDs);
234 if (!NT_STATUS_IS_OK(status)) {
235 ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
236 account_sid_string, nt_errstr(status));
237 talloc_free(tmp_ctx);
238 return LDB_ERR_OPERATIONS_ERROR;
241 /* Expands the primary group - this function takes in
242 * memberOf-like values, so we fake one up with the
243 * <SID=S-...> format of DN and then let it expand
244 * them, as long as they meet the filter - so only
245 * domain groups, not builtin groups
247 status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
248 tmp_ctx, &groupSIDs, &num_groupSIDs);
249 if (!NT_STATUS_IS_OK(status)) {
250 ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
251 account_sid_string, nt_errstr(status));
252 talloc_free(tmp_ctx);
253 return LDB_ERR_OPERATIONS_ERROR;
256 for (i=0; i < num_groupSIDs; i++) {
257 ret = samdb_msg_add_dom_sid(ldb, msg, msg, "tokenGroups", groupSIDs[i]);
259 talloc_free(tmp_ctx);
268 construct the parent GUID for an entry from a message
270 static int construct_parent_guid(struct ldb_module *module,
271 struct ldb_message *msg, enum ldb_scope scope,
272 struct ldb_request *parent)
274 struct ldb_result *res, *parent_res;
275 const struct ldb_val *parent_guid;
276 const char *attrs[] = { "instanceType", NULL };
277 const char *attrs2[] = { "objectGUID", NULL };
278 uint32_t instanceType;
280 struct ldb_dn *parent_dn;
283 /* determine if the object is NC by instance type */
284 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
285 DSDB_FLAG_NEXT_MODULE |
286 DSDB_SEARCH_SHOW_RECYCLED, parent);
288 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
291 if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
292 DEBUG(4,(__location__ ": Object %s is NC\n",
293 ldb_dn_get_linearized(msg->dn)));
296 parent_dn = ldb_dn_get_parent(msg, msg->dn);
298 if (parent_dn == NULL) {
299 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
300 ldb_dn_get_linearized(msg->dn)));
303 ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
304 DSDB_FLAG_NEXT_MODULE |
305 DSDB_SEARCH_SHOW_RECYCLED, parent);
306 talloc_free(parent_dn);
308 /* not NC, so the object should have a parent*/
309 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
310 DEBUG(4,(__location__ ": Parent dn for %s does not exist \n",
311 ldb_dn_get_linearized(msg->dn)));
312 return ldb_operr(ldb_module_get_ctx(module));
313 } else if (ret != LDB_SUCCESS) {
317 parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
319 talloc_free(parent_res);
323 v = data_blob_dup_talloc(parent_res, parent_guid);
325 talloc_free(parent_res);
326 return ldb_oom(ldb_module_get_ctx(module));
328 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
329 talloc_free(parent_res);
334 construct a subSchemaSubEntry
336 static int construct_subschema_subentry(struct ldb_module *module,
337 struct ldb_message *msg, enum ldb_scope scope,
338 struct ldb_request *parent)
340 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
341 char *subSchemaSubEntry;
343 /* We may be being called before the init function has finished */
348 /* Try and set this value up, if possible. Don't worry if it
349 * fails, we may not have the DB set up yet, and it's not
350 * really vital anyway */
351 if (!data->aggregate_dn) {
352 struct ldb_context *ldb = ldb_module_get_ctx(module);
353 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
356 if (data->aggregate_dn) {
357 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
358 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
364 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
365 struct ldb_message *msg,
366 struct ldb_message_element *object_category)
368 struct ldb_context *ldb;
370 const struct ldb_val *val;
372 ldb = ldb_module_get_ctx(module);
374 DEBUG(4, (__location__ ": Failed to get ldb \n"));
375 return ldb_operr(ldb);
378 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
380 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
381 (const char *)object_category->values[0].data));
382 return ldb_operr(ldb);
385 val = ldb_dn_get_rdn_val(dn);
387 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
388 ldb_dn_get_linearized(dn)));
389 return ldb_operr(ldb);
392 if (strequal((const char *)val->data, "NTDS-DSA")) {
393 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
395 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
400 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
401 struct ldb_message *msg,
403 struct ldb_request *parent)
405 struct ldb_dn *server_dn;
406 const char *attr_obj_cat[] = { "objectCategory", NULL };
407 struct ldb_result *res;
408 struct ldb_message_element *object_category;
411 server_dn = ldb_dn_copy(msg, dn);
412 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
413 DEBUG(4, (__location__ ": Failed to add child to %s \n",
414 ldb_dn_get_linearized(server_dn)));
415 return ldb_operr(ldb_module_get_ctx(module));
418 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
419 DSDB_FLAG_NEXT_MODULE, parent);
420 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
421 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
422 ldb_dn_get_linearized(server_dn)));
424 } else if (ret != LDB_SUCCESS) {
428 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
429 if (!object_category) {
430 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
431 ldb_dn_get_linearized(res->msgs[0]->dn)));
434 return construct_msds_isrodc_with_dn(module, msg, object_category);
437 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
438 struct ldb_message *msg,
439 struct ldb_request *parent)
441 struct ldb_context *ldb;
442 const char *attr[] = { "serverReferenceBL", NULL };
443 struct ldb_result *res;
445 struct ldb_dn *server_dn;
447 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attr,
448 DSDB_FLAG_NEXT_MODULE, parent);
449 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
450 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
451 ldb_dn_get_linearized(msg->dn)));
453 } else if (ret != LDB_SUCCESS) {
457 ldb = ldb_module_get_ctx(module);
462 server_dn = ldb_msg_find_attr_as_dn(ldb, msg, res->msgs[0], "serverReferenceBL");
464 DEBUG(4,(__location__ ": Can't find serverReferenceBL for %s \n",
465 ldb_dn_get_linearized(res->msgs[0]->dn)));
468 return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
472 construct msDS-isRODC attr
474 static int construct_msds_isrodc(struct ldb_module *module,
475 struct ldb_message *msg, enum ldb_scope scope,
476 struct ldb_request *parent)
478 struct ldb_message_element * object_class;
479 struct ldb_message_element * object_category;
482 object_class = ldb_msg_find_element(msg, "objectClass");
484 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
485 ldb_dn_get_linearized(msg->dn)));
486 return ldb_operr(ldb_module_get_ctx(module));
489 for (i=0; i<object_class->num_values; i++) {
490 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
491 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
492 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
494 object_category = ldb_msg_find_element(msg, "objectCategory");
495 if (!object_category) {
496 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
497 ldb_dn_get_linearized(msg->dn)));
500 return construct_msds_isrodc_with_dn(module, msg, object_category);
502 if (strequal((const char*)object_class->values[i].data, "server")) {
503 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
504 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
505 * substituting TN for TO.
507 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
509 if (strequal((const char*)object_class->values[i].data, "computer")) {
510 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
511 * rule for the "TO is a server object" case, substituting TS for TO.
513 return construct_msds_isrodc_with_computer_dn(module, msg, parent);
522 construct msDS-keyVersionNumber attr
524 TODO: Make this based on the 'win2k' DS huristics bit...
527 static int construct_msds_keyversionnumber(struct ldb_module *module,
528 struct ldb_message *msg,
529 enum ldb_scope scope,
530 struct ldb_request *parent)
533 enum ndr_err_code ndr_err;
534 const struct ldb_val *omd_value;
535 struct replPropertyMetaDataBlob *omd;
538 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
540 /* We can't make up a key version number without meta data */
547 omd = talloc(msg, struct replPropertyMetaDataBlob);
549 ldb_module_oom(module);
553 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
554 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
555 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
556 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
557 ldb_dn_get_linearized(msg->dn)));
558 return ldb_operr(ldb_module_get_ctx(module));
561 if (omd->version != 1) {
562 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
563 omd->version, ldb_dn_get_linearized(msg->dn)));
567 for (i=0; i<omd->ctr.ctr1.count; i++) {
568 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
569 ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
571 "msDS-KeyVersionNumber",
572 omd->ctr.ctr1.array[i].version);
573 if (ret != LDB_SUCCESS) {
584 struct op_controls_flags {
586 bool bypassoperational;
589 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
590 if (ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 && controls_flags->bypassoperational) {
597 a list of attribute names that should be substituted in the parse
598 tree before the search is done
600 static const struct {
603 } parse_tree_sub[] = {
604 { "createTimestamp", "whenCreated" },
605 { "modifyTimestamp", "whenChanged" }
610 a list of attribute names that are hidden, but can be searched for
611 using another (non-hidden) name to produce the correct result
613 static const struct {
616 const char *extra_attr;
617 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
619 { "createTimestamp", "whenCreated", NULL , NULL },
620 { "modifyTimestamp", "whenChanged", NULL , NULL },
621 { "structuralObjectClass", NULL, NULL , NULL },
622 { "canonicalName", NULL, NULL , construct_canonical_name },
623 { "primaryGroupToken", "objectClass", "objectSid", construct_primary_group_token },
624 { "tokenGroups", "primaryGroupID", "objectSid", construct_token_groups },
625 { "parentGUID", NULL, NULL, construct_parent_guid },
626 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
627 { "msDS-isRODC", "objectClass", "objectCategory", construct_msds_isrodc },
628 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber }
633 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
634 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
635 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
636 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an adhoc control has been specified */
640 a list of attributes that may need to be removed from the
643 Some of these are attributes that were once stored, but are now calculated
645 static const struct {
648 } operational_remove[] = {
649 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
650 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
651 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
652 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
653 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
654 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
659 post process a search result record. For any search_sub[] attributes that were
660 asked for, we need to call the appropriate copy routine to copy the result
661 into the message, then remove any attributes that we added to the search but
662 were not asked for by the user
664 static int operational_search_post_process(struct ldb_module *module,
665 struct ldb_message *msg,
666 enum ldb_scope scope,
667 const char * const *attrs_from_user,
668 const char * const *attrs_searched_for,
669 struct op_controls_flags* controls_flags,
670 struct ldb_request *parent)
672 struct ldb_context *ldb;
673 unsigned int i, a = 0;
674 bool constructed_attributes = false;
676 ldb = ldb_module_get_ctx(module);
678 /* removed any attrs that should not be shown to the user */
679 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
680 switch (operational_remove[i].op) {
681 case OPERATIONAL_REMOVE_UNASKED:
682 if (ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
685 if (ldb_attr_in_list(attrs_searched_for, operational_remove[i].attr)) {
688 case OPERATIONAL_REMOVE_ALWAYS:
689 ldb_msg_remove_attr(msg, operational_remove[i].attr);
691 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
692 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
693 ldb_msg_remove_attr(msg, operational_remove[i].attr);
698 case OPERATIONAL_SD_FLAGS:
699 if (controls_flags->sd ||
700 ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
703 ldb_msg_remove_attr(msg, operational_remove[i].attr);
708 for (a=0;attrs_from_user && attrs_from_user[a];a++) {
709 if (check_keep_control_for_attribute(controls_flags, attrs_from_user[a])) {
712 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
713 if (ldb_attr_cmp(attrs_from_user[a], search_sub[i].attr) != 0) {
717 /* construct the new attribute, using either a supplied
718 constructor or a simple copy */
719 constructed_attributes = true;
720 if (search_sub[i].constructor != NULL) {
721 if (search_sub[i].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
724 } else if (ldb_msg_copy_attr(msg,
726 search_sub[i].replace) != LDB_SUCCESS) {
732 /* Deletion of the search helper attributes are needed if:
733 * - we generated constructed attributes and
734 * - we aren't requesting all attributes
736 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
737 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
738 /* remove the added search helper attributes, unless
739 * they were asked for by the user */
740 if (search_sub[i].replace != NULL &&
741 !ldb_attr_in_list(attrs_from_user, search_sub[i].replace)) {
742 ldb_msg_remove_attr(msg, search_sub[i].replace);
744 if (search_sub[i].extra_attr != NULL &&
745 !ldb_attr_in_list(attrs_from_user, search_sub[i].extra_attr)) {
746 ldb_msg_remove_attr(msg, search_sub[i].extra_attr);
754 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
755 "operational_search_post_process failed for attribute '%s' - %s",
756 attrs_from_user[a], ldb_errstring(ldb));
761 hook search operations
764 struct operational_context {
765 struct ldb_module *module;
766 struct ldb_request *req;
767 enum ldb_scope scope;
768 const char * const *attrs;
769 struct op_controls_flags* controls_flags;
772 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
774 struct operational_context *ac;
777 ac = talloc_get_type(req->context, struct operational_context);
780 return ldb_module_done(ac->req, NULL, NULL,
781 LDB_ERR_OPERATIONS_ERROR);
783 if (ares->error != LDB_SUCCESS) {
784 return ldb_module_done(ac->req, ares->controls,
785 ares->response, ares->error);
788 switch (ares->type) {
789 case LDB_REPLY_ENTRY:
790 /* for each record returned post-process to add any derived
791 attributes that have been asked for */
792 ret = operational_search_post_process(ac->module,
796 req->op.search.attrs,
797 ac->controls_flags, req);
799 return ldb_module_done(ac->req, NULL, NULL,
800 LDB_ERR_OPERATIONS_ERROR);
802 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
804 case LDB_REPLY_REFERRAL:
805 return ldb_module_send_referral(ac->req, ares->referral);
809 return ldb_module_done(ac->req, ares->controls,
810 ares->response, LDB_SUCCESS);
817 static int operational_search(struct ldb_module *module, struct ldb_request *req)
819 struct ldb_context *ldb;
820 struct operational_context *ac;
821 struct ldb_request *down_req;
822 const char **search_attrs = NULL;
826 /* There are no operational attributes on special DNs */
827 if (ldb_dn_is_special(req->op.search.base)) {
828 return ldb_next_request(module, req);
831 ldb = ldb_module_get_ctx(module);
833 ac = talloc(req, struct operational_context);
840 ac->scope = req->op.search.scope;
841 ac->attrs = req->op.search.attrs;
843 /* FIXME: We must copy the tree and keep the original
845 /* replace any attributes in the parse tree that are
846 searchable, but are stored using a different name in the
848 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
849 ldb_parse_tree_attr_replace(req->op.search.tree,
850 parse_tree_sub[i].attr,
851 parse_tree_sub[i].replace);
854 ac->controls_flags = talloc(ac, struct op_controls_flags);
855 /* remember if the SD_FLAGS_OID was set */
856 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
857 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
858 ac->controls_flags->bypassoperational =
859 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
861 /* in the list of attributes we are looking for, rename any
862 attributes to the alias for any hidden attributes that can
863 be fetched directly using non-hidden names */
864 for (a=0;ac->attrs && ac->attrs[a];a++) {
865 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
868 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
869 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) == 0 &&
870 search_sub[i].replace) {
872 if (search_sub[i].extra_attr) {
873 const char **search_attrs2;
874 /* Only adds to the end of the list */
875 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
878 search_sub[i].extra_attr);
879 if (search_attrs2 == NULL) {
880 return ldb_operr(ldb);
882 /* may be NULL, talloc_free() doesn't mind */
883 talloc_free(search_attrs);
884 search_attrs = search_attrs2;
888 search_attrs = ldb_attr_list_copy(req, ac->attrs);
889 if (search_attrs == NULL) {
890 return ldb_operr(ldb);
893 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
894 search_attrs[a] = search_sub[i].replace;
899 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
901 req->op.search.scope,
903 /* use new set of attrs if any */
904 search_attrs == NULL?req->op.search.attrs:search_attrs,
906 ac, operational_callback,
908 LDB_REQ_SET_LOCATION(down_req);
909 if (ret != LDB_SUCCESS) {
910 return ldb_operr(ldb);
913 /* perform the search */
914 return ldb_next_request(module, down_req);
917 static int operational_init(struct ldb_module *ctx)
919 struct operational_data *data;
923 ret = ldb_next_init(ctx);
925 if (ret != LDB_SUCCESS) {
929 data = talloc_zero(ctx, struct operational_data);
931 return ldb_module_oom(ctx);
934 ldb_module_set_private(ctx, data);
939 static const struct ldb_module_ops ldb_operational_module_ops = {
940 .name = "operational",
941 .search = operational_search,
942 .init_context = operational_init
945 int ldb_operational_module_init(const char *version)
947 LDB_MODULE_CHECK_VERSION(version);
948 return ldb_register_module(&ldb_operational_module_ops);