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, struct ldb_message *msg)
383 struct ldb_message_element * object_class;
384 struct ldb_message_element * object_category;
387 object_class = ldb_msg_find_element(msg, "objectClass");
389 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
390 ldb_dn_get_linearized(msg->dn)));
391 return LDB_ERR_OPERATIONS_ERROR;
394 for (i=0; i<object_class->num_values; i++) {
395 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
396 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
397 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
399 object_category = ldb_msg_find_element(msg, "objectCategory");
400 if (!object_category) {
401 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
402 ldb_dn_get_linearized(msg->dn)));
405 return construct_msds_isrodc_with_dn(module, msg, object_category);
407 if (strequal((const char*)object_class->values[i].data, "server")) {
408 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
409 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
410 * substituting TN for TO.
412 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn);
414 if (strequal((const char*)object_class->values[i].data, "computer")) {
415 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
416 * rule for the "TO is a server object" case, substituting TS for TO.
418 return construct_msds_isrodc_with_computer_dn(module, msg);
427 construct msDS-keyVersionNumber attr
429 TODO: Make this based on the 'win2k' DS huristics bit...
432 static int construct_msds_keyversionnumber(struct ldb_module *module, struct ldb_message *msg)
435 enum ndr_err_code ndr_err;
436 const struct ldb_val *omd_value;
437 struct replPropertyMetaDataBlob *omd;
438 struct ldb_context *ldb = ldb_module_get_ctx(module);
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_ERR_OPERATIONS_ERROR;
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);
480 a list of attribute names that should be substituted in the parse
481 tree before the search is done
483 static const struct {
486 } parse_tree_sub[] = {
487 { "createTimestamp", "whenCreated" },
488 { "modifyTimestamp", "whenChanged" }
493 a list of attribute names that are hidden, but can be searched for
494 using another (non-hidden) name to produce the correct result
496 static const struct {
499 const char *extra_attr;
500 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope);
502 { "createTimestamp", "whenCreated", NULL , NULL },
503 { "modifyTimestamp", "whenChanged", NULL , NULL },
504 { "structuralObjectClass", "objectClass", NULL , NULL },
505 { "canonicalName", "distinguishedName", NULL , construct_canonical_name },
506 { "primaryGroupToken", "objectClass", "objectSid", construct_primary_group_token },
507 { "tokenGroups", "objectClass", NULL, construct_token_groups },
508 { "parentGUID", NULL, NULL, construct_parent_guid },
509 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
510 { "msDS-isRODC", "objectClass", "objectCategory", construct_msds_isrodc },
511 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber }
516 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
517 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
518 OPERATIONAL_SD_FLAGS /* show if SD_FLAGS_OID set, or asked for */
522 a list of attributes that may need to be removed from the
525 Some of these are attributes that were once stored, but are now calculated
527 static const struct {
530 } operational_remove[] = {
531 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
532 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_ALWAYS },
533 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
534 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
535 { "unicodePwd", OPERATIONAL_REMOVE_UNASKED },
536 { "dBCSPwd", OPERATIONAL_REMOVE_UNASKED },
537 { "ntPwdHistory", OPERATIONAL_REMOVE_UNASKED },
538 { "lmPwdHistory", OPERATIONAL_REMOVE_UNASKED },
539 { "supplementalCredentials", OPERATIONAL_REMOVE_UNASKED }
544 post process a search result record. For any search_sub[] attributes that were
545 asked for, we need to call the appropriate copy routine to copy the result
546 into the message, then remove any attributes that we added to the search but
547 were not asked for by the user
549 static int operational_search_post_process(struct ldb_module *module,
550 struct ldb_message *msg,
551 enum ldb_scope scope,
552 const char * const *attrs_from_user,
553 const char * const *attrs_searched_for,
556 struct ldb_context *ldb;
557 unsigned int i, a = 0;
558 bool constructed_attributes = false;
560 ldb = ldb_module_get_ctx(module);
562 /* removed any attrs that should not be shown to the user */
563 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
564 switch (operational_remove[i].op) {
565 case OPERATIONAL_REMOVE_UNASKED:
566 if (ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
569 if (ldb_attr_in_list(attrs_searched_for, operational_remove[i].attr)) {
572 case OPERATIONAL_REMOVE_ALWAYS:
573 ldb_msg_remove_attr(msg, operational_remove[i].attr);
575 case OPERATIONAL_SD_FLAGS:
577 ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
580 ldb_msg_remove_attr(msg, operational_remove[i].attr);
585 for (a=0;attrs_from_user && attrs_from_user[a];a++) {
586 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
587 if (ldb_attr_cmp(attrs_from_user[a], search_sub[i].attr) != 0) {
591 /* construct the new attribute, using either a supplied
592 constructor or a simple copy */
593 constructed_attributes = true;
594 if (search_sub[i].constructor != NULL) {
595 if (search_sub[i].constructor(module, msg, scope) != LDB_SUCCESS) {
598 } else if (ldb_msg_copy_attr(msg,
599 search_sub[i].replace,
600 search_sub[i].attr) != LDB_SUCCESS) {
606 /* Deletion of the search helper attributes are needed if:
607 * - we generated constructed attributes and
608 * - we aren't requesting all attributes
610 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
611 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
612 /* remove the added search helper attributes, unless
613 * they were asked for by the user */
614 if (search_sub[i].replace != NULL &&
615 !ldb_attr_in_list(attrs_from_user, search_sub[i].replace)) {
616 ldb_msg_remove_attr(msg, search_sub[i].replace);
618 if (search_sub[i].extra_attr != NULL &&
619 !ldb_attr_in_list(attrs_from_user, search_sub[i].extra_attr)) {
620 ldb_msg_remove_attr(msg, search_sub[i].extra_attr);
628 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
629 "operational_search_post_process failed for attribute '%s'",
636 hook search operations
639 struct operational_context {
640 struct ldb_module *module;
641 struct ldb_request *req;
642 enum ldb_scope scope;
643 const char * const *attrs;
647 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
649 struct operational_context *ac;
652 ac = talloc_get_type(req->context, struct operational_context);
655 return ldb_module_done(ac->req, NULL, NULL,
656 LDB_ERR_OPERATIONS_ERROR);
658 if (ares->error != LDB_SUCCESS) {
659 return ldb_module_done(ac->req, ares->controls,
660 ares->response, ares->error);
663 switch (ares->type) {
664 case LDB_REPLY_ENTRY:
665 /* for each record returned post-process to add any derived
666 attributes that have been asked for */
667 ret = operational_search_post_process(ac->module,
671 req->op.search.attrs,
674 return ldb_module_done(ac->req, NULL, NULL,
675 LDB_ERR_OPERATIONS_ERROR);
677 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
679 case LDB_REPLY_REFERRAL:
680 return ldb_module_send_referral(ac->req, ares->referral);
684 return ldb_module_done(ac->req, ares->controls,
685 ares->response, LDB_SUCCESS);
692 static int operational_search(struct ldb_module *module, struct ldb_request *req)
694 struct ldb_context *ldb;
695 struct operational_context *ac;
696 struct ldb_request *down_req;
697 const char **search_attrs = NULL;
701 /* There are no operational attributes on special DNs */
702 if (ldb_dn_is_special(req->op.search.base)) {
703 return ldb_next_request(module, req);
706 ldb = ldb_module_get_ctx(module);
708 ac = talloc(req, struct operational_context);
710 return LDB_ERR_OPERATIONS_ERROR;
715 ac->scope = req->op.search.scope;
716 ac->attrs = req->op.search.attrs;
718 /* FIXME: We must copy the tree and keep the original
720 /* replace any attributes in the parse tree that are
721 searchable, but are stored using a different name in the
723 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
724 ldb_parse_tree_attr_replace(req->op.search.tree,
725 parse_tree_sub[i].attr,
726 parse_tree_sub[i].replace);
729 /* in the list of attributes we are looking for, rename any
730 attributes to the alias for any hidden attributes that can
731 be fetched directly using non-hidden names */
732 for (a=0;ac->attrs && ac->attrs[a];a++) {
733 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
734 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) == 0 &&
735 search_sub[i].replace) {
737 if (search_sub[i].extra_attr) {
738 const char **search_attrs2;
739 /* Only adds to the end of the list */
740 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
743 search_sub[i].extra_attr);
744 if (search_attrs2 == NULL) {
745 return LDB_ERR_OPERATIONS_ERROR;
747 /* may be NULL, talloc_free() doesn't mind */
748 talloc_free(search_attrs);
749 search_attrs = search_attrs2;
753 search_attrs = ldb_attr_list_copy(req, ac->attrs);
754 if (search_attrs == NULL) {
755 return LDB_ERR_OPERATIONS_ERROR;
758 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
759 search_attrs[a] = search_sub[i].replace;
764 /* remember if the SD_FLAGS_OID was set */
765 ac->sd_flags_set = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
767 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
769 req->op.search.scope,
771 /* use new set of attrs if any */
772 search_attrs == NULL?req->op.search.attrs:search_attrs,
774 ac, operational_callback,
776 if (ret != LDB_SUCCESS) {
777 return LDB_ERR_OPERATIONS_ERROR;
780 /* perform the search */
781 return ldb_next_request(module, down_req);
784 static int operational_init(struct ldb_module *ctx)
786 struct operational_data *data;
790 ret = ldb_next_init(ctx);
792 if (ret != LDB_SUCCESS) {
796 data = talloc_zero(ctx, struct operational_data);
799 return LDB_ERR_OPERATIONS_ERROR;
802 ldb_module_set_private(ctx, data);
807 const struct ldb_module_ops ldb_operational_module_ops = {
808 .name = "operational",
809 .search = operational_search,
810 .init_context = operational_init