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 "libcli/security/security.h"
80 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
83 struct operational_data {
84 struct ldb_dn *aggregate_dn;
89 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL,
90 TOKEN_GROUPS_NO_GC_ACCEPTABLE
94 construct a canonical name from a message
96 static int construct_canonical_name(struct ldb_module *module,
97 struct ldb_message *msg, enum ldb_scope scope,
98 struct ldb_request *parent)
101 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
102 if (canonicalName == NULL) {
103 return ldb_operr(ldb_module_get_ctx(module));
105 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
109 construct a primary group token for groups from a message
111 static int construct_primary_group_token(struct ldb_module *module,
112 struct ldb_message *msg, enum ldb_scope scope,
113 struct ldb_request *parent)
115 struct ldb_context *ldb;
116 uint32_t primary_group_token;
118 ldb = ldb_module_get_ctx(module);
119 if (ldb_match_msg_objectclass(msg, "group") == 1) {
121 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
122 if (primary_group_token == 0) {
126 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
127 primary_group_token);
134 construct the token groups for SAM objects from a message
136 static int construct_generic_token_groups(struct ldb_module *module,
137 struct ldb_message *msg, enum ldb_scope scope,
138 struct ldb_request *parent,
139 const char *attribute_string,
140 enum search_type type)
142 struct ldb_context *ldb = ldb_module_get_ctx(module);;
143 TALLOC_CTX *tmp_ctx = talloc_new(msg);
150 struct dom_sid *primary_group_sid;
151 const char *primary_group_string;
152 const char *primary_group_dn;
153 DATA_BLOB primary_group_blob;
155 struct dom_sid *account_sid;
156 const char *account_sid_string;
157 const char *account_sid_dn;
158 DATA_BLOB account_sid_blob;
159 struct dom_sid *groupSIDs = NULL;
160 unsigned int num_groupSIDs = 0;
162 struct dom_sid *domain_sid;
164 if (scope != LDB_SCOPE_BASE) {
165 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
166 return LDB_ERR_OPERATIONS_ERROR;
169 /* If it's not a user, it won't have a primaryGroupID */
170 if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
171 talloc_free(tmp_ctx);
175 /* Ensure it has an objectSID too */
176 account_sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid");
177 if (account_sid == NULL) {
178 talloc_free(tmp_ctx);
182 status = dom_sid_split_rid(tmp_ctx, account_sid, &domain_sid, NULL);
183 if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
184 talloc_free(tmp_ctx);
185 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
186 } else if (!NT_STATUS_IS_OK(status)) {
187 talloc_free(tmp_ctx);
188 return LDB_ERR_OPERATIONS_ERROR;
191 primary_group_sid = dom_sid_add_rid(tmp_ctx,
193 ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
194 if (!primary_group_sid) {
195 talloc_free(tmp_ctx);
199 /* only return security groups */
201 case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
202 filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u)(|(groupType:1.2.840.113556.1.4.803:=%u)(groupType:1.2.840.113556.1.4.803:=%u)))",
203 GROUP_TYPE_SECURITY_ENABLED, GROUP_TYPE_ACCOUNT_GROUP, GROUP_TYPE_UNIVERSAL_GROUP);
205 case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
207 filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
208 GROUP_TYPE_SECURITY_ENABLED);
213 talloc_free(tmp_ctx);
217 primary_group_string = dom_sid_string(tmp_ctx, primary_group_sid);
218 if (!primary_group_string) {
219 talloc_free(tmp_ctx);
223 primary_group_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", primary_group_string);
224 if (!primary_group_dn) {
225 talloc_free(tmp_ctx);
229 primary_group_blob = data_blob_string_const(primary_group_dn);
231 account_sid_string = dom_sid_string(tmp_ctx, account_sid);
232 if (!account_sid_string) {
233 talloc_free(tmp_ctx);
237 account_sid_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", account_sid_string);
238 if (!account_sid_dn) {
239 talloc_free(tmp_ctx);
243 account_sid_blob = data_blob_string_const(account_sid_dn);
245 status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
246 true, /* We don't want to add the object's SID itself,
247 it's not returend in this attribute */
249 tmp_ctx, &groupSIDs, &num_groupSIDs);
251 if (!NT_STATUS_IS_OK(status)) {
252 ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
253 account_sid_string, nt_errstr(status));
254 talloc_free(tmp_ctx);
255 return LDB_ERR_OPERATIONS_ERROR;
258 /* Expands the primary group - this function takes in
259 * memberOf-like values, so we fake one up with the
260 * <SID=S-...> format of DN and then let it expand
261 * them, as long as they meet the filter - so only
262 * domain groups, not builtin groups
264 status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
265 tmp_ctx, &groupSIDs, &num_groupSIDs);
266 if (!NT_STATUS_IS_OK(status)) {
267 ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
268 account_sid_string, nt_errstr(status));
269 talloc_free(tmp_ctx);
270 return LDB_ERR_OPERATIONS_ERROR;
273 for (i=0; i < num_groupSIDs; i++) {
274 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i]);
276 talloc_free(tmp_ctx);
284 static int construct_token_groups(struct ldb_module *module,
285 struct ldb_message *msg, enum ldb_scope scope,
286 struct ldb_request *parent)
289 * TODO: Add in a limiting domain when we start to support
292 return construct_generic_token_groups(module, msg, scope, parent,
297 static int construct_token_groups_no_gc(struct ldb_module *module,
298 struct ldb_message *msg, enum ldb_scope scope,
299 struct ldb_request *parent)
302 * TODO: Add in a limiting domain when we start to support
305 return construct_generic_token_groups(module, msg, scope, parent,
306 "tokenGroupsNoGCAcceptable",
310 static int construct_global_universal_token_groups(struct ldb_module *module,
311 struct ldb_message *msg, enum ldb_scope scope,
312 struct ldb_request *parent)
314 return construct_generic_token_groups(module, msg, scope, parent,
315 "tokenGroupsGlobalAndUniversal",
316 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
319 construct the parent GUID for an entry from a message
321 static int construct_parent_guid(struct ldb_module *module,
322 struct ldb_message *msg, enum ldb_scope scope,
323 struct ldb_request *parent)
325 struct ldb_result *res, *parent_res;
326 const struct ldb_val *parent_guid;
327 const char *attrs[] = { "instanceType", NULL };
328 const char *attrs2[] = { "objectGUID", NULL };
329 uint32_t instanceType;
331 struct ldb_dn *parent_dn;
334 /* determine if the object is NC by instance type */
335 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
336 DSDB_FLAG_NEXT_MODULE |
337 DSDB_SEARCH_SHOW_RECYCLED, parent);
338 if (ret != LDB_SUCCESS) {
342 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
345 if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
346 DEBUG(4,(__location__ ": Object %s is NC\n",
347 ldb_dn_get_linearized(msg->dn)));
350 parent_dn = ldb_dn_get_parent(msg, msg->dn);
352 if (parent_dn == NULL) {
353 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
354 ldb_dn_get_linearized(msg->dn)));
357 ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
358 DSDB_FLAG_NEXT_MODULE |
359 DSDB_SEARCH_SHOW_RECYCLED, parent);
360 talloc_free(parent_dn);
362 /* not NC, so the object should have a parent*/
363 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
364 return ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
365 talloc_asprintf(msg, "Parent dn for %s does not exist",
366 ldb_dn_get_linearized(msg->dn)));
367 } else if (ret != LDB_SUCCESS) {
371 parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
373 talloc_free(parent_res);
377 v = data_blob_dup_talloc(parent_res, *parent_guid);
379 talloc_free(parent_res);
380 return ldb_oom(ldb_module_get_ctx(module));
382 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
383 talloc_free(parent_res);
387 static int construct_modifyTimeStamp(struct ldb_module *module,
388 struct ldb_message *msg, enum ldb_scope scope,
389 struct ldb_request *parent)
391 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
392 struct ldb_context *ldb = ldb_module_get_ctx(module);
394 /* We may be being called before the init function has finished */
399 /* Try and set this value up, if possible. Don't worry if it
400 * fails, we may not have the DB set up yet.
402 if (!data->aggregate_dn) {
403 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
406 if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
408 * If we have the DN for the object with common name = Aggregate and
409 * the request is for this DN then let's do the following:
410 * 1) search the object which changedUSN correspond to the one of the loaded
412 * 2) Get the whenChanged attribute
413 * 3) Generate the modifyTimestamp out of the whenChanged attribute
415 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
416 char *value = ldb_timestring(msg, schema->ts_last_change);
418 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
420 return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
424 construct a subSchemaSubEntry
426 static int construct_subschema_subentry(struct ldb_module *module,
427 struct ldb_message *msg, enum ldb_scope scope,
428 struct ldb_request *parent)
430 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
431 char *subSchemaSubEntry;
433 /* We may be being called before the init function has finished */
438 /* Try and set this value up, if possible. Don't worry if it
439 * fails, we may not have the DB set up yet, and it's not
440 * really vital anyway */
441 if (!data->aggregate_dn) {
442 struct ldb_context *ldb = ldb_module_get_ctx(module);
443 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
446 if (data->aggregate_dn) {
447 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
448 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
454 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
455 struct ldb_message *msg,
456 struct ldb_message_element *object_category)
458 struct ldb_context *ldb;
460 const struct ldb_val *val;
462 ldb = ldb_module_get_ctx(module);
464 DEBUG(4, (__location__ ": Failed to get ldb \n"));
465 return ldb_operr(ldb);
468 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
470 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
471 (const char *)object_category->values[0].data));
472 return ldb_operr(ldb);
475 val = ldb_dn_get_rdn_val(dn);
477 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
478 ldb_dn_get_linearized(dn)));
479 return ldb_operr(ldb);
482 if (strequal((const char *)val->data, "NTDS-DSA")) {
483 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
485 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
490 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
491 struct ldb_message *msg,
493 struct ldb_request *parent)
495 struct ldb_dn *server_dn;
496 const char *attr_obj_cat[] = { "objectCategory", NULL };
497 struct ldb_result *res;
498 struct ldb_message_element *object_category;
501 server_dn = ldb_dn_copy(msg, dn);
502 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
503 DEBUG(4, (__location__ ": Failed to add child to %s \n",
504 ldb_dn_get_linearized(server_dn)));
505 return ldb_operr(ldb_module_get_ctx(module));
508 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
509 DSDB_FLAG_NEXT_MODULE, parent);
510 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
511 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
512 ldb_dn_get_linearized(server_dn)));
514 } else if (ret != LDB_SUCCESS) {
518 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
519 if (!object_category) {
520 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
521 ldb_dn_get_linearized(res->msgs[0]->dn)));
524 return construct_msds_isrodc_with_dn(module, msg, object_category);
527 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
528 struct ldb_message *msg,
529 struct ldb_request *parent)
532 struct ldb_dn *server_dn;
534 ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
536 if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
537 /* it's OK if we can't find serverReferenceBL attribute */
538 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
539 ldb_dn_get_linearized(msg->dn)));
541 } else if (ret != LDB_SUCCESS) {
545 return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
549 construct msDS-isRODC attr
551 static int construct_msds_isrodc(struct ldb_module *module,
552 struct ldb_message *msg, enum ldb_scope scope,
553 struct ldb_request *parent)
555 struct ldb_message_element * object_class;
556 struct ldb_message_element * object_category;
559 object_class = ldb_msg_find_element(msg, "objectClass");
561 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
562 ldb_dn_get_linearized(msg->dn)));
563 return ldb_operr(ldb_module_get_ctx(module));
566 for (i=0; i<object_class->num_values; i++) {
567 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
568 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
569 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
571 object_category = ldb_msg_find_element(msg, "objectCategory");
572 if (!object_category) {
573 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
574 ldb_dn_get_linearized(msg->dn)));
577 return construct_msds_isrodc_with_dn(module, msg, object_category);
579 if (strequal((const char*)object_class->values[i].data, "server")) {
580 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
581 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
582 * substituting TN for TO.
584 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
586 if (strequal((const char*)object_class->values[i].data, "computer")) {
587 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
588 * rule for the "TO is a server object" case, substituting TS for TO.
590 return construct_msds_isrodc_with_computer_dn(module, msg, parent);
599 construct msDS-keyVersionNumber attr
601 TODO: Make this based on the 'win2k' DS huristics bit...
604 static int construct_msds_keyversionnumber(struct ldb_module *module,
605 struct ldb_message *msg,
606 enum ldb_scope scope,
607 struct ldb_request *parent)
610 enum ndr_err_code ndr_err;
611 const struct ldb_val *omd_value;
612 struct replPropertyMetaDataBlob *omd;
615 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
617 /* We can't make up a key version number without meta data */
624 omd = talloc(msg, struct replPropertyMetaDataBlob);
626 ldb_module_oom(module);
630 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
631 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
632 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
633 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
634 ldb_dn_get_linearized(msg->dn)));
635 return ldb_operr(ldb_module_get_ctx(module));
638 if (omd->version != 1) {
639 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
640 omd->version, ldb_dn_get_linearized(msg->dn)));
644 for (i=0; i<omd->ctr.ctr1.count; i++) {
645 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
646 ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
648 "msDS-KeyVersionNumber",
649 omd->ctr.ctr1.array[i].version);
650 if (ret != LDB_SUCCESS) {
661 struct op_controls_flags {
663 bool bypassoperational;
666 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
667 if (ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 && controls_flags->bypassoperational) {
674 a list of attribute names that should be substituted in the parse
675 tree before the search is done
677 static const struct {
680 } parse_tree_sub[] = {
681 { "createTimeStamp", "whenCreated" },
682 { "modifyTimeStamp", "whenChanged" }
687 a list of attribute names that are hidden, but can be searched for
688 using another (non-hidden) name to produce the correct result
690 static const struct {
693 const char *extra_attr;
694 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
696 { "createTimeStamp", "whenCreated", NULL , NULL },
697 { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
698 { "structuralObjectClass", "objectClass", NULL , NULL },
699 { "canonicalName", NULL, NULL , construct_canonical_name },
700 { "primaryGroupToken", "objectClass", "objectSid", construct_primary_group_token },
701 { "tokenGroups", "primaryGroupID", "objectSid", construct_token_groups },
702 { "tokenGroupsNoGCAcceptable", "primaryGroupID", "objectSid", construct_token_groups_no_gc},
703 { "tokenGroupsGlobalAndUniversal", "primaryGroupID", "objectSid", construct_global_universal_token_groups },
704 { "parentGUID", NULL, NULL, construct_parent_guid },
705 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
706 { "msDS-isRODC", "objectClass", "objectCategory", construct_msds_isrodc },
707 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber }
712 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
713 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
714 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
715 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an adhoc control has been specified */
719 a list of attributes that may need to be removed from the
722 Some of these are attributes that were once stored, but are now calculated
724 static const struct {
727 } operational_remove[] = {
728 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
729 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
730 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
731 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
732 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
733 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
738 post process a search result record. For any search_sub[] attributes that were
739 asked for, we need to call the appropriate copy routine to copy the result
740 into the message, then remove any attributes that we added to the search but
741 were not asked for by the user
743 static int operational_search_post_process(struct ldb_module *module,
744 struct ldb_message *msg,
745 enum ldb_scope scope,
746 const char * const *attrs_from_user,
747 const char * const *attrs_searched_for,
748 struct op_controls_flags* controls_flags,
749 struct ldb_request *parent)
751 struct ldb_context *ldb;
752 unsigned int i, a = 0;
753 bool constructed_attributes = false;
755 ldb = ldb_module_get_ctx(module);
757 /* removed any attrs that should not be shown to the user */
758 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
759 switch (operational_remove[i].op) {
760 case OPERATIONAL_REMOVE_UNASKED:
761 if (ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
764 if (ldb_attr_in_list(attrs_searched_for, operational_remove[i].attr)) {
767 case OPERATIONAL_REMOVE_ALWAYS:
768 ldb_msg_remove_attr(msg, operational_remove[i].attr);
770 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
771 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
772 ldb_msg_remove_attr(msg, operational_remove[i].attr);
777 case OPERATIONAL_SD_FLAGS:
778 if (ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
781 if (controls_flags->sd) {
782 if (attrs_from_user == NULL) {
785 if (attrs_from_user[0] == NULL) {
788 if (ldb_attr_in_list(attrs_from_user, "*")) {
792 ldb_msg_remove_attr(msg, operational_remove[i].attr);
797 for (a=0;attrs_from_user && attrs_from_user[a];a++) {
798 if (check_keep_control_for_attribute(controls_flags, attrs_from_user[a])) {
801 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
802 if (ldb_attr_cmp(attrs_from_user[a], search_sub[i].attr) != 0) {
806 /* construct the new attribute, using either a supplied
807 constructor or a simple copy */
808 constructed_attributes = true;
809 if (search_sub[i].constructor != NULL) {
810 if (search_sub[i].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
813 } else if (ldb_msg_copy_attr(msg,
814 search_sub[i].replace,
815 search_sub[i].attr) != LDB_SUCCESS) {
821 /* Deletion of the search helper attributes are needed if:
822 * - we generated constructed attributes and
823 * - we aren't requesting all attributes
825 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
826 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
827 /* remove the added search helper attributes, unless
828 * they were asked for by the user */
829 if (search_sub[i].replace != NULL &&
830 !ldb_attr_in_list(attrs_from_user, search_sub[i].replace)) {
831 ldb_msg_remove_attr(msg, search_sub[i].replace);
833 if (search_sub[i].extra_attr != NULL &&
834 !ldb_attr_in_list(attrs_from_user, search_sub[i].extra_attr)) {
835 ldb_msg_remove_attr(msg, search_sub[i].extra_attr);
843 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
844 "operational_search_post_process failed for attribute '%s' - %s",
845 attrs_from_user[a], ldb_errstring(ldb));
850 hook search operations
853 struct operational_context {
854 struct ldb_module *module;
855 struct ldb_request *req;
856 enum ldb_scope scope;
857 const char * const *attrs;
858 struct op_controls_flags* controls_flags;
861 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
863 struct operational_context *ac;
866 ac = talloc_get_type(req->context, struct operational_context);
869 return ldb_module_done(ac->req, NULL, NULL,
870 LDB_ERR_OPERATIONS_ERROR);
872 if (ares->error != LDB_SUCCESS) {
873 return ldb_module_done(ac->req, ares->controls,
874 ares->response, ares->error);
877 switch (ares->type) {
878 case LDB_REPLY_ENTRY:
879 /* for each record returned post-process to add any derived
880 attributes that have been asked for */
881 ret = operational_search_post_process(ac->module,
885 req->op.search.attrs,
886 ac->controls_flags, req);
888 return ldb_module_done(ac->req, NULL, NULL,
889 LDB_ERR_OPERATIONS_ERROR);
891 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
893 case LDB_REPLY_REFERRAL:
894 return ldb_module_send_referral(ac->req, ares->referral);
898 return ldb_module_done(ac->req, ares->controls,
899 ares->response, LDB_SUCCESS);
906 static int operational_search(struct ldb_module *module, struct ldb_request *req)
908 struct ldb_context *ldb;
909 struct operational_context *ac;
910 struct ldb_request *down_req;
911 const char **search_attrs = NULL;
915 /* There are no operational attributes on special DNs */
916 if (ldb_dn_is_special(req->op.search.base)) {
917 return ldb_next_request(module, req);
920 ldb = ldb_module_get_ctx(module);
922 ac = talloc(req, struct operational_context);
929 ac->scope = req->op.search.scope;
930 ac->attrs = req->op.search.attrs;
932 /* FIXME: We must copy the tree and keep the original
934 /* replace any attributes in the parse tree that are
935 searchable, but are stored using a different name in the
937 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
938 ldb_parse_tree_attr_replace(req->op.search.tree,
939 parse_tree_sub[i].attr,
940 parse_tree_sub[i].replace);
943 ac->controls_flags = talloc(ac, struct op_controls_flags);
944 /* remember if the SD_FLAGS_OID was set */
945 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
946 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
947 ac->controls_flags->bypassoperational =
948 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
950 /* in the list of attributes we are looking for, rename any
951 attributes to the alias for any hidden attributes that can
952 be fetched directly using non-hidden names */
953 for (a=0;ac->attrs && ac->attrs[a];a++) {
954 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
957 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
958 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) == 0 &&
959 search_sub[i].replace) {
961 if (search_sub[i].extra_attr) {
962 const char **search_attrs2;
963 /* Only adds to the end of the list */
964 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
967 search_sub[i].extra_attr);
968 if (search_attrs2 == NULL) {
969 return ldb_operr(ldb);
971 /* may be NULL, talloc_free() doesn't mind */
972 talloc_free(search_attrs);
973 search_attrs = search_attrs2;
977 search_attrs = ldb_attr_list_copy(req, ac->attrs);
978 if (search_attrs == NULL) {
979 return ldb_operr(ldb);
982 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
983 search_attrs[a] = search_sub[i].replace;
988 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
990 req->op.search.scope,
992 /* use new set of attrs if any */
993 search_attrs == NULL?req->op.search.attrs:search_attrs,
995 ac, operational_callback,
997 LDB_REQ_SET_LOCATION(down_req);
998 if (ret != LDB_SUCCESS) {
999 return ldb_operr(ldb);
1002 /* perform the search */
1003 return ldb_next_request(module, down_req);
1006 static int operational_init(struct ldb_module *ctx)
1008 struct operational_data *data;
1011 ret = ldb_next_init(ctx);
1013 if (ret != LDB_SUCCESS) {
1017 data = talloc_zero(ctx, struct operational_data);
1019 return ldb_module_oom(ctx);
1022 ldb_module_set_private(ctx, data);
1027 static const struct ldb_module_ops ldb_operational_module_ops = {
1028 .name = "operational",
1029 .search = operational_search,
1030 .init_context = operational_init
1033 int ldb_operational_module_init(const char *version)
1035 LDB_MODULE_CHECK_VERSION(version);
1036 return ldb_register_module(&ldb_operational_module_ops);