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?
67 #include "ldb_includes.h"
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_ERR_OPERATIONS_ERROR;
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 ldb_module_oom(module);
150 return LDB_ERR_OPERATIONS_ERROR;
151 } else if (!NT_STATUS_IS_OK(status)) {
152 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, could not create authContext");
153 talloc_free(tmp_ctx);
154 return LDB_ERR_OPERATIONS_ERROR;
157 status = auth_get_server_info_principal(tmp_ctx, auth_context, NULL, msg->dn, &server_info);
158 if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
159 talloc_free(tmp_ctx);
160 ldb_module_oom(module);
161 return LDB_ERR_OPERATIONS_ERROR;
162 } else if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
163 /* Not a user, we have no tokenGroups */
164 talloc_free(tmp_ctx);
166 } else if (!NT_STATUS_IS_OK(status)) {
167 talloc_free(tmp_ctx);
168 ldb_asprintf_errstring(ldb, "Cannot provide tokenGroups attribute: auth_get_server_info_principal failed: %s", nt_errstr(status));
169 return LDB_ERR_OPERATIONS_ERROR;
172 status = auth_generate_session_info(tmp_ctx, auth_context, server_info, 0, &session_info);
173 if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
174 talloc_free(tmp_ctx);
175 ldb_module_oom(module);
176 return LDB_ERR_OPERATIONS_ERROR;
177 } else if (!NT_STATUS_IS_OK(status)) {
178 talloc_free(tmp_ctx);
179 ldb_asprintf_errstring(ldb, "Cannot provide tokenGroups attribute: auth_generate_session_info failed: %s", nt_errstr(status));
180 return LDB_ERR_OPERATIONS_ERROR;
183 /* We start at 1, as the first SID is the user's SID, not included in the tokenGroups */
184 for (i = 1; i < session_info->security_token->num_sids; i++) {
185 ret = samdb_msg_add_dom_sid(ldb, msg, msg,
187 session_info->security_token->sids[i]);
188 if (ret != LDB_SUCCESS) {
189 talloc_free(tmp_ctx);
198 construct the parent GUID for an entry from a message
200 static int construct_parent_guid(struct ldb_module *module,
201 struct ldb_message *msg, enum ldb_scope scope)
203 struct ldb_result *res;
204 const struct ldb_val *parent_guid;
205 const char *attrs[] = { "objectGUID", NULL };
209 /* TODO: In the future, this needs to honour the partition boundaries */
210 struct ldb_dn *parent_dn = ldb_dn_get_parent(msg, msg->dn);
212 if (parent_dn == NULL) {
213 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
214 ldb_dn_get_linearized(msg->dn)));
218 ret = dsdb_module_search_dn(module, msg, &res, parent_dn, attrs, DSDB_SEARCH_SHOW_DELETED);
219 talloc_free(parent_dn);
220 /* if there is no parentGUID for this object, then return */
221 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
222 DEBUG(4,(__location__ ": Parent dn for %s does not exist \n",
223 ldb_dn_get_linearized(msg->dn)));
225 } else if (ret != LDB_SUCCESS) {
229 parent_guid = ldb_msg_find_ldb_val(res->msgs[0], "objectGUID");
235 v = data_blob_dup_talloc(res, parent_guid);
238 return LDB_ERR_OPERATIONS_ERROR;
240 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
246 construct a subSchemaSubEntry
248 static int construct_subschema_subentry(struct ldb_module *module,
249 struct ldb_message *msg, enum ldb_scope scope)
251 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
252 char *subSchemaSubEntry;
254 /* We may be being called before the init function has finished */
259 /* Try and set this value up, if possible. Don't worry if it
260 * fails, we may not have the DB set up yet, and it's not
261 * really vital anyway */
262 if (!data->aggregate_dn) {
263 struct ldb_context *ldb = ldb_module_get_ctx(module);
264 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
267 if (data->aggregate_dn) {
268 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
269 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
275 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
276 struct ldb_message *msg,
277 struct ldb_message_element *object_category)
279 struct ldb_context *ldb;
281 const struct ldb_val *val;
283 ldb = ldb_module_get_ctx(module);
285 DEBUG(4, (__location__ ": Failed to get ldb \n"));
286 return LDB_ERR_OPERATIONS_ERROR;
289 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
291 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
292 (const char *)object_category->values[0].data));
293 return LDB_ERR_OPERATIONS_ERROR;
296 val = ldb_dn_get_rdn_val(dn);
298 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
299 ldb_dn_get_linearized(dn)));
300 return LDB_ERR_OPERATIONS_ERROR;
303 if (strequal((const char *)val->data, "NTDS-DSA")) {
304 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
306 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
311 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
312 struct ldb_message *msg,
315 struct ldb_dn *server_dn;
316 const char *attr_obj_cat[] = { "objectCategory", NULL };
317 struct ldb_result *res;
318 struct ldb_message_element *object_category;
321 server_dn = ldb_dn_copy(msg, dn);
322 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
323 DEBUG(4, (__location__ ": Failed to add child to %s \n",
324 ldb_dn_get_linearized(server_dn)));
325 return LDB_ERR_OPERATIONS_ERROR;
328 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat, 0);
329 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
330 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
331 ldb_dn_get_linearized(server_dn)));
333 } else if (ret != LDB_SUCCESS) {
337 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
338 if (!object_category) {
339 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
340 ldb_dn_get_linearized(res->msgs[0]->dn)));
343 return construct_msds_isrodc_with_dn(module, msg, object_category);
346 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
347 struct ldb_message *msg)
349 struct ldb_context *ldb;
350 const char *attr[] = { "serverReferenceBL", NULL };
351 struct ldb_result *res;
353 struct ldb_dn *server_dn;
355 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attr, 0);
356 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
357 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
358 ldb_dn_get_linearized(msg->dn)));
360 } else if (ret != LDB_SUCCESS) {
364 ldb = ldb_module_get_ctx(module);
369 server_dn = ldb_msg_find_attr_as_dn(ldb, msg, res->msgs[0], "serverReferenceBL");
371 DEBUG(4,(__location__ ": Can't find serverReferenceBL for %s \n",
372 ldb_dn_get_linearized(res->msgs[0]->dn)));
375 return construct_msds_isrodc_with_server_dn(module, msg, server_dn);
379 construct msDS-isRODC attr
381 static int construct_msds_isrodc(struct ldb_module *module,
382 struct ldb_message *msg, enum ldb_scope scope)
384 struct ldb_message_element * object_class;
385 struct ldb_message_element * object_category;
388 object_class = ldb_msg_find_element(msg, "objectClass");
390 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
391 ldb_dn_get_linearized(msg->dn)));
392 return LDB_ERR_OPERATIONS_ERROR;
395 for (i=0; i<object_class->num_values; i++) {
396 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
397 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
398 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
400 object_category = ldb_msg_find_element(msg, "objectCategory");
401 if (!object_category) {
402 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
403 ldb_dn_get_linearized(msg->dn)));
406 return construct_msds_isrodc_with_dn(module, msg, object_category);
408 if (strequal((const char*)object_class->values[i].data, "server")) {
409 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
410 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
411 * substituting TN for TO.
413 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn);
415 if (strequal((const char*)object_class->values[i].data, "computer")) {
416 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
417 * rule for the "TO is a server object" case, substituting TS for TO.
419 return construct_msds_isrodc_with_computer_dn(module, msg);
428 construct msDS-keyVersionNumber attr
430 TODO: Make this based on the 'win2k' DS huristics bit...
433 static int construct_msds_keyversionnumber(struct ldb_module *module,
434 struct ldb_message *msg,
435 enum ldb_scope scope)
438 enum ndr_err_code ndr_err;
439 const struct ldb_val *omd_value;
440 struct replPropertyMetaDataBlob *omd;
442 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
444 /* We can't make up a key version number without meta data */
451 omd = talloc(msg, struct replPropertyMetaDataBlob);
453 ldb_module_oom(module);
457 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
458 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
459 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
460 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
461 ldb_dn_get_linearized(msg->dn)));
462 return LDB_ERR_OPERATIONS_ERROR;
465 if (omd->version != 1) {
466 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
467 omd->version, ldb_dn_get_linearized(msg->dn)));
471 for (i=0; i<omd->ctr.ctr1.count; i++) {
472 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTRIBUTE_unicodePwd) {
473 ldb_msg_add_fmt(msg, "msDS-KeyVersionNumber", "%u", omd->ctr.ctr1.array[i].version);
482 a list of attribute names that should be substituted in the parse
483 tree before the search is done
485 static const struct {
488 } parse_tree_sub[] = {
489 { "createTimestamp", "whenCreated" },
490 { "modifyTimestamp", "whenChanged" }
495 a list of attribute names that are hidden, but can be searched for
496 using another (non-hidden) name to produce the correct result
498 static const struct {
501 const char *extra_attr;
502 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope);
504 { "createTimestamp", "whenCreated", NULL , NULL },
505 { "modifyTimestamp", "whenChanged", NULL , NULL },
506 { "structuralObjectClass", "objectClass", NULL , NULL },
507 { "canonicalName", "distinguishedName", NULL , construct_canonical_name },
508 { "primaryGroupToken", "objectClass", "objectSid", construct_primary_group_token },
509 { "tokenGroups", "objectClass", NULL, construct_token_groups },
510 { "parentGUID", NULL, NULL, construct_parent_guid },
511 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
512 { "msDS-isRODC", "objectClass", "objectCategory", construct_msds_isrodc },
513 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber }
518 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
519 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
520 OPERATIONAL_SD_FLAGS /* show if SD_FLAGS_OID set, or asked for */
524 a list of attributes that may need to be removed from the
527 Some of these are attributes that were once stored, but are now calculated
529 static const struct {
532 } operational_remove[] = {
533 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
534 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_ALWAYS },
535 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
536 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
537 { "unicodePwd", OPERATIONAL_REMOVE_UNASKED },
538 { "dBCSPwd", OPERATIONAL_REMOVE_UNASKED },
539 { "ntPwdHistory", OPERATIONAL_REMOVE_UNASKED },
540 { "lmPwdHistory", OPERATIONAL_REMOVE_UNASKED },
541 { "supplementalCredentials", OPERATIONAL_REMOVE_UNASKED }
546 post process a search result record. For any search_sub[] attributes that were
547 asked for, we need to call the appropriate copy routine to copy the result
548 into the message, then remove any attributes that we added to the search but
549 were not asked for by the user
551 static int operational_search_post_process(struct ldb_module *module,
552 struct ldb_message *msg,
553 enum ldb_scope scope,
554 const char * const *attrs_from_user,
555 const char * const *attrs_searched_for,
558 struct ldb_context *ldb;
559 unsigned int i, a = 0;
560 bool constructed_attributes = false;
562 ldb = ldb_module_get_ctx(module);
564 /* removed any attrs that should not be shown to the user */
565 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
566 switch (operational_remove[i].op) {
567 case OPERATIONAL_REMOVE_UNASKED:
568 if (ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
571 if (ldb_attr_in_list(attrs_searched_for, operational_remove[i].attr)) {
574 case OPERATIONAL_REMOVE_ALWAYS:
575 ldb_msg_remove_attr(msg, operational_remove[i].attr);
577 case OPERATIONAL_SD_FLAGS:
579 ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
582 ldb_msg_remove_attr(msg, operational_remove[i].attr);
587 for (a=0;attrs_from_user && attrs_from_user[a];a++) {
588 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
589 if (ldb_attr_cmp(attrs_from_user[a], search_sub[i].attr) != 0) {
593 /* construct the new attribute, using either a supplied
594 constructor or a simple copy */
595 constructed_attributes = true;
596 if (search_sub[i].constructor != NULL) {
597 if (search_sub[i].constructor(module, msg, scope) != LDB_SUCCESS) {
600 } else if (ldb_msg_copy_attr(msg,
601 search_sub[i].replace,
602 search_sub[i].attr) != LDB_SUCCESS) {
608 /* Deletion of the search helper attributes are needed if:
609 * - we generated constructed attributes and
610 * - we aren't requesting all attributes
612 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
613 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
614 /* remove the added search helper attributes, unless
615 * they were asked for by the user */
616 if (search_sub[i].replace != NULL &&
617 !ldb_attr_in_list(attrs_from_user, search_sub[i].replace)) {
618 ldb_msg_remove_attr(msg, search_sub[i].replace);
620 if (search_sub[i].extra_attr != NULL &&
621 !ldb_attr_in_list(attrs_from_user, search_sub[i].extra_attr)) {
622 ldb_msg_remove_attr(msg, search_sub[i].extra_attr);
630 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
631 "operational_search_post_process failed for attribute '%s'",
638 hook search operations
641 struct operational_context {
642 struct ldb_module *module;
643 struct ldb_request *req;
644 enum ldb_scope scope;
645 const char * const *attrs;
649 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
651 struct operational_context *ac;
654 ac = talloc_get_type(req->context, struct operational_context);
657 return ldb_module_done(ac->req, NULL, NULL,
658 LDB_ERR_OPERATIONS_ERROR);
660 if (ares->error != LDB_SUCCESS) {
661 return ldb_module_done(ac->req, ares->controls,
662 ares->response, ares->error);
665 switch (ares->type) {
666 case LDB_REPLY_ENTRY:
667 /* for each record returned post-process to add any derived
668 attributes that have been asked for */
669 ret = operational_search_post_process(ac->module,
673 req->op.search.attrs,
676 return ldb_module_done(ac->req, NULL, NULL,
677 LDB_ERR_OPERATIONS_ERROR);
679 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
681 case LDB_REPLY_REFERRAL:
682 return ldb_module_send_referral(ac->req, ares->referral);
686 return ldb_module_done(ac->req, ares->controls,
687 ares->response, LDB_SUCCESS);
694 static int operational_search(struct ldb_module *module, struct ldb_request *req)
696 struct ldb_context *ldb;
697 struct operational_context *ac;
698 struct ldb_request *down_req;
699 const char **search_attrs = NULL;
703 /* There are no operational attributes on special DNs */
704 if (ldb_dn_is_special(req->op.search.base)) {
705 return ldb_next_request(module, req);
708 ldb = ldb_module_get_ctx(module);
710 ac = talloc(req, struct operational_context);
712 return LDB_ERR_OPERATIONS_ERROR;
717 ac->scope = req->op.search.scope;
718 ac->attrs = req->op.search.attrs;
720 /* FIXME: We must copy the tree and keep the original
722 /* replace any attributes in the parse tree that are
723 searchable, but are stored using a different name in the
725 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
726 ldb_parse_tree_attr_replace(req->op.search.tree,
727 parse_tree_sub[i].attr,
728 parse_tree_sub[i].replace);
731 /* in the list of attributes we are looking for, rename any
732 attributes to the alias for any hidden attributes that can
733 be fetched directly using non-hidden names */
734 for (a=0;ac->attrs && ac->attrs[a];a++) {
735 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
736 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) == 0 &&
737 search_sub[i].replace) {
739 if (search_sub[i].extra_attr) {
740 const char **search_attrs2;
741 /* Only adds to the end of the list */
742 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
745 search_sub[i].extra_attr);
746 if (search_attrs2 == NULL) {
747 return LDB_ERR_OPERATIONS_ERROR;
749 /* may be NULL, talloc_free() doesn't mind */
750 talloc_free(search_attrs);
751 search_attrs = search_attrs2;
755 search_attrs = ldb_attr_list_copy(req, ac->attrs);
756 if (search_attrs == NULL) {
757 return LDB_ERR_OPERATIONS_ERROR;
760 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
761 search_attrs[a] = search_sub[i].replace;
766 /* remember if the SD_FLAGS_OID was set */
767 ac->sd_flags_set = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
769 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
771 req->op.search.scope,
773 /* use new set of attrs if any */
774 search_attrs == NULL?req->op.search.attrs:search_attrs,
776 ac, operational_callback,
778 if (ret != LDB_SUCCESS) {
779 return LDB_ERR_OPERATIONS_ERROR;
782 /* perform the search */
783 return ldb_next_request(module, down_req);
786 static int operational_init(struct ldb_module *ctx)
788 struct operational_data *data;
792 ret = ldb_next_init(ctx);
794 if (ret != LDB_SUCCESS) {
798 data = talloc_zero(ctx, struct operational_data);
801 return LDB_ERR_OPERATIONS_ERROR;
804 ldb_module_set_private(ctx, data);
809 const struct ldb_module_ops ldb_operational_module_ops = {
810 .name = "operational",
811 .search = operational_search,
812 .init_context = operational_init