4 Copyright (C) Andrew Tridgell 2005
5 Copyright (C) Simo Sorce 2006-2008
6 Copyright (C) Matthias Dieter Wallnöfer 2009
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 handle operational attributes
27 createTimestamp: HIDDEN, searchable, ldaptime, alias for whenCreated
28 modifyTimestamp: HIDDEN, searchable, ldaptime, alias for whenChanged
30 for the above two, we do the search as normal, and if
31 createTimestamp or modifyTimestamp is asked for, then do
32 additional searches for whenCreated and whenChanged and fill in
35 we also need to replace these with the whenCreated/whenChanged
36 equivalent in the search expression trees
38 whenCreated: not-HIDDEN, CONSTRUCTED, SEARCHABLE
39 whenChanged: not-HIDDEN, CONSTRUCTED, SEARCHABLE
41 on init we need to setup attribute handlers for these so
42 comparisons are done correctly. The resolution is 1 second.
44 on add we need to add both the above, for current time
46 on modify we need to change whenChanged
48 structuralObjectClass: HIDDEN, CONSTRUCTED, not-searchable. always same as objectclass?
50 for this one we do the search as normal, then if requested ask
51 for objectclass, change the attribute name, and add it
53 primaryGroupToken: HIDDEN, CONSTRUCTED, SEARCHABLE
55 contains the RID of a certain group object
58 attributeTypes: in schema only
59 objectClasses: in schema only
60 matchingRules: in schema only
61 matchingRuleUse: in schema only
62 creatorsName: not supported by w2k3?
63 modifiersName: not supported by w2k3?
68 #include <ldb_module.h>
70 #include "librpc/gen_ndr/ndr_misc.h"
71 #include "librpc/gen_ndr/ndr_drsblobs.h"
72 #include "param/param.h"
73 #include "dsdb/samdb/samdb.h"
74 #include "dsdb/samdb/ldb_modules/util.h"
76 #include "auth/auth.h"
77 #include "libcli/security/dom_sid.h"
80 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
83 struct operational_data {
84 struct ldb_dn *aggregate_dn;
88 construct a canonical name from a message
90 static int construct_canonical_name(struct ldb_module *module,
91 struct ldb_message *msg, enum ldb_scope scope)
94 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
95 if (canonicalName == NULL) {
96 return ldb_operr(ldb_module_get_ctx(module));
98 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
102 construct a primary group token for groups from a message
104 static int construct_primary_group_token(struct ldb_module *module,
105 struct ldb_message *msg, enum ldb_scope scope)
107 struct ldb_context *ldb;
108 uint32_t primary_group_token;
110 ldb = ldb_module_get_ctx(module);
111 if (ldb_match_msg_objectclass(msg, "group") == 1) {
113 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
114 if (primary_group_token == 0) {
118 return samdb_msg_add_int(ldb, msg, msg, "primaryGroupToken",
119 primary_group_token);
126 construct the token groups for SAM objects from a message
128 static int construct_token_groups(struct ldb_module *module,
129 struct ldb_message *msg, enum ldb_scope scope)
131 struct ldb_context *ldb = ldb_module_get_ctx(module);;
132 struct auth_context *auth_context;
133 struct auth_serversupplied_info *server_info;
134 struct auth_session_info *session_info;
135 TALLOC_CTX *tmp_ctx = talloc_new(msg);
141 if (scope != LDB_SCOPE_BASE) {
142 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
143 return LDB_ERR_OPERATIONS_ERROR;
146 status = auth_context_create_from_ldb(tmp_ctx, ldb, &auth_context);
147 if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
148 talloc_free(tmp_ctx);
149 return ldb_module_oom(module);
150 } else if (!NT_STATUS_IS_OK(status)) {
151 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, could not create authContext");
152 talloc_free(tmp_ctx);
153 return LDB_ERR_OPERATIONS_ERROR;
156 status = auth_get_server_info_principal(tmp_ctx, auth_context, NULL, msg->dn, &server_info);
157 if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
158 talloc_free(tmp_ctx);
159 return ldb_module_oom(module);
160 } else if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
161 /* Not a user, we have no tokenGroups */
162 talloc_free(tmp_ctx);
164 } else if (!NT_STATUS_IS_OK(status)) {
165 talloc_free(tmp_ctx);
166 ldb_asprintf_errstring(ldb, "Cannot provide tokenGroups attribute: auth_get_server_info_principal failed: %s", nt_errstr(status));
167 return LDB_ERR_OPERATIONS_ERROR;
170 status = auth_generate_session_info(tmp_ctx, auth_context, server_info, 0, &session_info);
171 if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
172 talloc_free(tmp_ctx);
173 return ldb_module_oom(module);
174 } else if (!NT_STATUS_IS_OK(status)) {
175 talloc_free(tmp_ctx);
176 ldb_asprintf_errstring(ldb, "Cannot provide tokenGroups attribute: auth_generate_session_info failed: %s", nt_errstr(status));
177 return LDB_ERR_OPERATIONS_ERROR;
180 /* We start at 1, as the first SID is the user's SID, not included in the tokenGroups */
181 for (i = 1; i < session_info->security_token->num_sids; i++) {
182 ret = samdb_msg_add_dom_sid(ldb, msg, msg,
184 session_info->security_token->sids[i]);
185 if (ret != LDB_SUCCESS) {
186 talloc_free(tmp_ctx);
195 construct the parent GUID for an entry from a message
197 static int construct_parent_guid(struct ldb_module *module,
198 struct ldb_message *msg, enum ldb_scope scope)
200 struct ldb_result *res;
201 const struct ldb_val *parent_guid;
202 const char *attrs[] = { "objectGUID", NULL };
206 /* TODO: In the future, this needs to honour the partition boundaries */
207 struct ldb_dn *parent_dn = ldb_dn_get_parent(msg, msg->dn);
209 if (parent_dn == NULL) {
210 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
211 ldb_dn_get_linearized(msg->dn)));
215 ret = dsdb_module_search_dn(module, msg, &res, parent_dn, attrs, DSDB_SEARCH_SHOW_DELETED);
216 talloc_free(parent_dn);
218 /* if there is no parent for this object, then return */
219 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
220 DEBUG(4,(__location__ ": Parent dn for %s does not exist \n",
221 ldb_dn_get_linearized(msg->dn)));
223 } else if (ret != LDB_SUCCESS) {
227 parent_guid = ldb_msg_find_ldb_val(res->msgs[0], "objectGUID");
233 v = data_blob_dup_talloc(res, parent_guid);
236 return ldb_oom(ldb_module_get_ctx(module));
238 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
244 construct a subSchemaSubEntry
246 static int construct_subschema_subentry(struct ldb_module *module,
247 struct ldb_message *msg, enum ldb_scope scope)
249 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
250 char *subSchemaSubEntry;
252 /* We may be being called before the init function has finished */
257 /* Try and set this value up, if possible. Don't worry if it
258 * fails, we may not have the DB set up yet, and it's not
259 * really vital anyway */
260 if (!data->aggregate_dn) {
261 struct ldb_context *ldb = ldb_module_get_ctx(module);
262 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
265 if (data->aggregate_dn) {
266 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
267 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
273 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
274 struct ldb_message *msg,
275 struct ldb_message_element *object_category)
277 struct ldb_context *ldb;
279 const struct ldb_val *val;
281 ldb = ldb_module_get_ctx(module);
283 DEBUG(4, (__location__ ": Failed to get ldb \n"));
284 return ldb_operr(ldb);
287 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
289 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
290 (const char *)object_category->values[0].data));
291 return ldb_operr(ldb);
294 val = ldb_dn_get_rdn_val(dn);
296 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
297 ldb_dn_get_linearized(dn)));
298 return ldb_operr(ldb);
301 if (strequal((const char *)val->data, "NTDS-DSA")) {
302 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
304 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
309 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
310 struct ldb_message *msg,
313 struct ldb_dn *server_dn;
314 const char *attr_obj_cat[] = { "objectCategory", NULL };
315 struct ldb_result *res;
316 struct ldb_message_element *object_category;
319 server_dn = ldb_dn_copy(msg, dn);
320 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
321 DEBUG(4, (__location__ ": Failed to add child to %s \n",
322 ldb_dn_get_linearized(server_dn)));
323 return ldb_operr(ldb_module_get_ctx(module));
326 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat, 0);
327 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
328 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
329 ldb_dn_get_linearized(server_dn)));
331 } else if (ret != LDB_SUCCESS) {
335 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
336 if (!object_category) {
337 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
338 ldb_dn_get_linearized(res->msgs[0]->dn)));
341 return construct_msds_isrodc_with_dn(module, msg, object_category);
344 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
345 struct ldb_message *msg)
347 struct ldb_context *ldb;
348 const char *attr[] = { "serverReferenceBL", NULL };
349 struct ldb_result *res;
351 struct ldb_dn *server_dn;
353 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attr, 0);
354 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
355 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
356 ldb_dn_get_linearized(msg->dn)));
358 } else if (ret != LDB_SUCCESS) {
362 ldb = ldb_module_get_ctx(module);
367 server_dn = ldb_msg_find_attr_as_dn(ldb, msg, res->msgs[0], "serverReferenceBL");
369 DEBUG(4,(__location__ ": Can't find serverReferenceBL for %s \n",
370 ldb_dn_get_linearized(res->msgs[0]->dn)));
373 return construct_msds_isrodc_with_server_dn(module, msg, server_dn);
377 construct msDS-isRODC attr
379 static int construct_msds_isrodc(struct ldb_module *module,
380 struct ldb_message *msg, enum ldb_scope scope)
382 struct ldb_message_element * object_class;
383 struct ldb_message_element * object_category;
386 object_class = ldb_msg_find_element(msg, "objectClass");
388 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
389 ldb_dn_get_linearized(msg->dn)));
390 return ldb_operr(ldb_module_get_ctx(module));
393 for (i=0; i<object_class->num_values; i++) {
394 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
395 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
396 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
398 object_category = ldb_msg_find_element(msg, "objectCategory");
399 if (!object_category) {
400 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
401 ldb_dn_get_linearized(msg->dn)));
404 return construct_msds_isrodc_with_dn(module, msg, object_category);
406 if (strequal((const char*)object_class->values[i].data, "server")) {
407 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
408 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
409 * substituting TN for TO.
411 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn);
413 if (strequal((const char*)object_class->values[i].data, "computer")) {
414 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
415 * rule for the "TO is a server object" case, substituting TS for TO.
417 return construct_msds_isrodc_with_computer_dn(module, msg);
426 construct msDS-keyVersionNumber attr
428 TODO: Make this based on the 'win2k' DS huristics bit...
431 static int construct_msds_keyversionnumber(struct ldb_module *module,
432 struct ldb_message *msg,
433 enum ldb_scope scope)
436 enum ndr_err_code ndr_err;
437 const struct ldb_val *omd_value;
438 struct replPropertyMetaDataBlob *omd;
440 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
442 /* We can't make up a key version number without meta data */
449 omd = talloc(msg, struct replPropertyMetaDataBlob);
451 ldb_module_oom(module);
455 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
456 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
457 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
458 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
459 ldb_dn_get_linearized(msg->dn)));
460 return ldb_operr(ldb_module_get_ctx(module));
463 if (omd->version != 1) {
464 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
465 omd->version, ldb_dn_get_linearized(msg->dn)));
469 for (i=0; i<omd->ctr.ctr1.count; i++) {
470 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTRIBUTE_unicodePwd) {
471 ldb_msg_add_fmt(msg, "msDS-KeyVersionNumber", "%u", omd->ctr.ctr1.array[i].version);
479 struct op_controls_flags {
481 bool bypassoperational;
484 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
485 if (ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 && controls_flags->bypassoperational) {
492 a list of attribute names that should be substituted in the parse
493 tree before the search is done
495 static const struct {
498 } parse_tree_sub[] = {
499 { "createTimestamp", "whenCreated" },
500 { "modifyTimestamp", "whenChanged" }
505 a list of attribute names that are hidden, but can be searched for
506 using another (non-hidden) name to produce the correct result
508 static const struct {
511 const char *extra_attr;
512 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope);
514 { "createTimestamp", "whenCreated", NULL , NULL },
515 { "modifyTimestamp", "whenChanged", NULL , NULL },
516 { "structuralObjectClass", "objectClass", NULL , NULL },
517 { "canonicalName", "distinguishedName", NULL , construct_canonical_name },
518 { "primaryGroupToken", "objectClass", "objectSid", construct_primary_group_token },
519 { "tokenGroups", "objectClass", NULL, construct_token_groups },
520 { "parentGUID", NULL, NULL, construct_parent_guid },
521 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
522 { "msDS-isRODC", "objectClass", "objectCategory", construct_msds_isrodc },
523 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber }
528 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
529 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
530 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
531 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an adhoc control has been specified */
535 a list of attributes that may need to be removed from the
538 Some of these are attributes that were once stored, but are now calculated
540 static const struct {
543 } operational_remove[] = {
544 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
545 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
546 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
547 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
548 { "unicodePwd", OPERATIONAL_REMOVE_UNASKED },
549 { "dBCSPwd", OPERATIONAL_REMOVE_UNASKED },
550 { "ntPwdHistory", OPERATIONAL_REMOVE_UNASKED },
551 { "lmPwdHistory", OPERATIONAL_REMOVE_UNASKED },
552 { "supplementalCredentials", OPERATIONAL_REMOVE_UNASKED }
557 post process a search result record. For any search_sub[] attributes that were
558 asked for, we need to call the appropriate copy routine to copy the result
559 into the message, then remove any attributes that we added to the search but
560 were not asked for by the user
562 static int operational_search_post_process(struct ldb_module *module,
563 struct ldb_message *msg,
564 enum ldb_scope scope,
565 const char * const *attrs_from_user,
566 const char * const *attrs_searched_for,
567 struct op_controls_flags* controls_flags)
569 struct ldb_context *ldb;
570 unsigned int i, a = 0;
571 bool constructed_attributes = false;
573 ldb = ldb_module_get_ctx(module);
575 /* removed any attrs that should not be shown to the user */
576 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
577 switch (operational_remove[i].op) {
578 case OPERATIONAL_REMOVE_UNASKED:
579 if (ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
582 if (ldb_attr_in_list(attrs_searched_for, operational_remove[i].attr)) {
585 case OPERATIONAL_REMOVE_ALWAYS:
586 ldb_msg_remove_attr(msg, operational_remove[i].attr);
588 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
589 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
590 ldb_msg_remove_attr(msg, operational_remove[i].attr);
595 case OPERATIONAL_SD_FLAGS:
596 if (controls_flags->sd ||
597 ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
600 ldb_msg_remove_attr(msg, operational_remove[i].attr);
605 for (a=0;attrs_from_user && attrs_from_user[a];a++) {
606 if (check_keep_control_for_attribute(controls_flags, attrs_from_user[a])) {
609 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
610 if (ldb_attr_cmp(attrs_from_user[a], search_sub[i].attr) != 0) {
614 /* construct the new attribute, using either a supplied
615 constructor or a simple copy */
616 constructed_attributes = true;
617 if (search_sub[i].constructor != NULL) {
618 if (search_sub[i].constructor(module, msg, scope) != LDB_SUCCESS) {
621 } else if (ldb_msg_copy_attr(msg,
622 search_sub[i].replace,
623 search_sub[i].attr) != LDB_SUCCESS) {
629 /* Deletion of the search helper attributes are needed if:
630 * - we generated constructed attributes and
631 * - we aren't requesting all attributes
633 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
634 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
635 /* remove the added search helper attributes, unless
636 * they were asked for by the user */
637 if (search_sub[i].replace != NULL &&
638 !ldb_attr_in_list(attrs_from_user, search_sub[i].replace)) {
639 ldb_msg_remove_attr(msg, search_sub[i].replace);
641 if (search_sub[i].extra_attr != NULL &&
642 !ldb_attr_in_list(attrs_from_user, search_sub[i].extra_attr)) {
643 ldb_msg_remove_attr(msg, search_sub[i].extra_attr);
651 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
652 "operational_search_post_process failed for attribute '%s'",
658 hook search operations
661 struct operational_context {
662 struct ldb_module *module;
663 struct ldb_request *req;
664 enum ldb_scope scope;
665 const char * const *attrs;
666 struct op_controls_flags* controls_flags;
669 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
671 struct operational_context *ac;
674 ac = talloc_get_type(req->context, struct operational_context);
677 return ldb_module_done(ac->req, NULL, NULL,
678 LDB_ERR_OPERATIONS_ERROR);
680 if (ares->error != LDB_SUCCESS) {
681 return ldb_module_done(ac->req, ares->controls,
682 ares->response, ares->error);
685 switch (ares->type) {
686 case LDB_REPLY_ENTRY:
687 /* for each record returned post-process to add any derived
688 attributes that have been asked for */
689 ret = operational_search_post_process(ac->module,
693 req->op.search.attrs,
696 return ldb_module_done(ac->req, NULL, NULL,
697 LDB_ERR_OPERATIONS_ERROR);
699 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
701 case LDB_REPLY_REFERRAL:
702 return ldb_module_send_referral(ac->req, ares->referral);
706 return ldb_module_done(ac->req, ares->controls,
707 ares->response, LDB_SUCCESS);
714 static int operational_search(struct ldb_module *module, struct ldb_request *req)
716 struct ldb_context *ldb;
717 struct operational_context *ac;
718 struct ldb_request *down_req;
719 const char **search_attrs = NULL;
723 /* There are no operational attributes on special DNs */
724 if (ldb_dn_is_special(req->op.search.base)) {
725 return ldb_next_request(module, req);
728 ldb = ldb_module_get_ctx(module);
730 ac = talloc(req, struct operational_context);
737 ac->scope = req->op.search.scope;
738 ac->attrs = req->op.search.attrs;
740 /* FIXME: We must copy the tree and keep the original
742 /* replace any attributes in the parse tree that are
743 searchable, but are stored using a different name in the
745 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
746 ldb_parse_tree_attr_replace(req->op.search.tree,
747 parse_tree_sub[i].attr,
748 parse_tree_sub[i].replace);
751 ac->controls_flags = talloc(ac, struct op_controls_flags);
752 /* remember if the SD_FLAGS_OID was set */
753 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
754 /* remember if the LDB_CONTROL_BYPASSOPERATIONAL_OID */
755 ac->controls_flags->bypassoperational = (ldb_request_get_control(req,
756 LDB_CONTROL_BYPASSOPERATIONAL_OID) != NULL);
758 /* in the list of attributes we are looking for, rename any
759 attributes to the alias for any hidden attributes that can
760 be fetched directly using non-hidden names */
761 for (a=0;ac->attrs && ac->attrs[a];a++) {
762 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
765 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
766 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) == 0 &&
767 search_sub[i].replace) {
769 if (search_sub[i].extra_attr) {
770 const char **search_attrs2;
771 /* Only adds to the end of the list */
772 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
775 search_sub[i].extra_attr);
776 if (search_attrs2 == NULL) {
777 return ldb_operr(ldb);
779 /* may be NULL, talloc_free() doesn't mind */
780 talloc_free(search_attrs);
781 search_attrs = search_attrs2;
785 search_attrs = ldb_attr_list_copy(req, ac->attrs);
786 if (search_attrs == NULL) {
787 return ldb_operr(ldb);
790 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
791 search_attrs[a] = search_sub[i].replace;
796 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
798 req->op.search.scope,
800 /* use new set of attrs if any */
801 search_attrs == NULL?req->op.search.attrs:search_attrs,
803 ac, operational_callback,
805 if (ret != LDB_SUCCESS) {
806 return ldb_operr(ldb);
809 /* perform the search */
810 return ldb_next_request(module, down_req);
813 static int operational_init(struct ldb_module *ctx)
815 struct operational_data *data;
819 ret = ldb_next_init(ctx);
821 if (ret != LDB_SUCCESS) {
825 data = talloc_zero(ctx, struct operational_data);
827 return ldb_module_oom(ctx);
830 ldb_module_set_private(ctx, data);
835 _PUBLIC_ const struct ldb_module_ops ldb_operational_module_ops = {
836 .name = "operational",
837 .search = operational_search,
838 .init_context = operational_init