dsdb: Remember the last ACL we read during a search and what it expanded to
[samba.git] / source4 / dsdb / samdb / ldb_modules / acl_read.c
1 /*
2   ldb database library
3
4   Copyright (C) Simo Sorce 2006-2008
5   Copyright (C) Nadezhda Ivanova 2010
6
7   This program is free software; you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation; either version 3 of the License, or
10   (at your option) any later version.
11
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 /*
22  *  Name: ldb
23  *
24  *  Component: ldb ACL Read module
25  *
26  *  Description: Module that performs authorisation access checks on read requests
27  *               Only DACL checks implemented at this point
28  *
29  *  Author: Nadezhda Ivanova
30  */
31
32 #include "includes.h"
33 #include "ldb_module.h"
34 #include "auth/auth.h"
35 #include "libcli/security/security.h"
36 #include "dsdb/samdb/samdb.h"
37 #include "librpc/gen_ndr/ndr_security.h"
38 #include "param/param.h"
39 #include "dsdb/samdb/ldb_modules/util.h"
40
41
42 struct aclread_context {
43         struct ldb_module *module;
44         struct ldb_request *req;
45         const char * const *attrs;
46         const struct dsdb_schema *schema;
47         uint32_t sd_flags;
48         bool added_nTSecurityDescriptor;
49         bool added_instanceType;
50         bool added_objectSid;
51         bool added_objectClass;
52         bool indirsync;
53
54         /* cache on the last parent we checked in this search */
55         struct ldb_dn *last_parent_dn;
56         int last_parent_check_ret;
57 };
58
59 struct aclread_private {
60         bool enabled;
61
62         /* cache of the last SD we read during any search */
63         struct security_descriptor *sd_cached;
64         struct ldb_val sd_cached_blob;
65 };
66
67 static void aclread_mark_inaccesslible(struct ldb_message_element *el) {
68         el->flags |= LDB_FLAG_INTERNAL_INACCESSIBLE_ATTRIBUTE;
69 }
70
71 static bool aclread_is_inaccessible(struct ldb_message_element *el) {
72         return el->flags & LDB_FLAG_INTERNAL_INACCESSIBLE_ATTRIBUTE;
73 }
74
75 /*
76  * the object has a parent, so we have to check for visibility
77  *
78  * This helper function uses a per-search cache to avoid checking the
79  * parent object for each of many possible children.  This is likely
80  * to help on SCOPE_ONE searches and on typical tree structures for
81  * SCOPE_SUBTREE, where an OU has many users as children.
82  *
83  * We rely for safety on the DB being locked for reads during the full
84  * search.
85  */
86 static int aclread_check_parent(struct aclread_context *ac,
87                                 struct ldb_message *msg,
88                                 struct ldb_request *req)
89 {
90         int ret;
91         struct ldb_dn *parent_dn = NULL;
92
93         /* We may have a cached result from earlier in this search */
94         if (ac->last_parent_dn != NULL) {
95                 /*
96                  * We try the no-allocation ldb_dn_compare_base()
97                  * first however it will not tell parents and
98                  * grand-parents apart
99                  */
100                 int cmp_base = ldb_dn_compare_base(ac->last_parent_dn,
101                                                    msg->dn);
102                 if (cmp_base == 0) {
103                         /* Now check if it is a direct parent */
104                         parent_dn = ldb_dn_get_parent(ac, msg->dn);
105                         if (parent_dn == NULL) {
106                                 return ldb_oom(ldb_module_get_ctx(ac->module));
107                         }
108                         if (ldb_dn_compare(ac->last_parent_dn,
109                                            parent_dn) == 0) {
110                                 TALLOC_FREE(parent_dn);
111
112                                 /*
113                                  * If we checked the same parent last
114                                  * time, then return the cached
115                                  * result.
116                                  *
117                                  * The cache is valid as long as the
118                                  * search as the DB is read locked and
119                                  * the session_info (connected user)
120                                  * is constant.
121                                  */
122                                 return ac->last_parent_check_ret;
123                         }
124                 }
125         }
126
127         {
128                 TALLOC_CTX *frame = NULL;
129                 frame = talloc_stackframe();
130
131                 /*
132                  * This may have been set in the block above, don't
133                  * re-parse
134                  */
135                 if (parent_dn == NULL) {
136                         parent_dn = ldb_dn_get_parent(ac, msg->dn);
137                         if (parent_dn == NULL) {
138                                 TALLOC_FREE(frame);
139                                 return ldb_oom(ldb_module_get_ctx(ac->module));
140                         }
141                 }
142                 ret = dsdb_module_check_access_on_dn(ac->module,
143                                                      frame,
144                                                      parent_dn,
145                                                      SEC_ADS_LIST,
146                                                      NULL, req);
147                 talloc_unlink(ac, ac->last_parent_dn);
148                 ac->last_parent_dn = parent_dn;
149                 ac->last_parent_check_ret = ret;
150
151                 TALLOC_FREE(frame);
152         }
153         return ret;
154 }
155
156 /*
157  * The sd returned from this function is valid until the next call on
158  * this module context
159  *
160  * This helper function uses a cache on the module private data to
161  * speed up repeated use of the same SD.
162  */
163
164 static int aclread_get_sd_from_ldb_message(struct aclread_context *ac,
165                                            struct ldb_message *acl_res,
166                                            struct security_descriptor **sd)
167 {
168         struct ldb_message_element *sd_element;
169         struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
170         struct aclread_private *private_data
171                 = talloc_get_type(ldb_module_get_private(ac->module),
172                                   struct aclread_private);
173         enum ndr_err_code ndr_err;
174
175         sd_element = ldb_msg_find_element(acl_res, "nTSecurityDescriptor");
176         if (sd_element == NULL) {
177                 return ldb_error(ldb, LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS,
178                                  "nTSecurityDescriptor is missing");
179         }
180
181         if (sd_element->num_values != 1) {
182                 return ldb_operr(ldb);
183         }
184
185         /*
186          * The time spent in ndr_pull_security_descriptor() is quite
187          * expensive, so we check if this is the same binary blob as last
188          * time, and if so return the memory tree from that previous parse.
189          */
190
191         if (private_data->sd_cached != NULL &&
192             private_data->sd_cached_blob.data != NULL &&
193             ldb_val_equal_exact(&sd_element->values[0],
194                                 &private_data->sd_cached_blob)) {
195                 *sd = private_data->sd_cached;
196                 return LDB_SUCCESS;
197         }
198
199         *sd = talloc(private_data, struct security_descriptor);
200         if(!*sd) {
201                 return ldb_oom(ldb);
202         }
203         ndr_err = ndr_pull_struct_blob(&sd_element->values[0], *sd, *sd,
204                              (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
205
206         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
207                 TALLOC_FREE(*sd);
208                 return ldb_operr(ldb);
209         }
210
211         talloc_unlink(private_data, private_data->sd_cached_blob.data);
212         if (ac->added_nTSecurityDescriptor) {
213                 private_data->sd_cached_blob = sd_element->values[0];
214                 talloc_steal(private_data, sd_element->values[0].data);
215         } else {
216                 private_data->sd_cached_blob = ldb_val_dup(private_data,
217                                                            &sd_element->values[0]);
218                 if (private_data->sd_cached_blob.data == NULL) {
219                         TALLOC_FREE(*sd);
220                         return ldb_operr(ldb);
221                 }
222         }
223
224         talloc_unlink(private_data, private_data->sd_cached);
225         private_data->sd_cached = *sd;
226
227         return LDB_SUCCESS;
228 }
229
230
231 static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares)
232 {
233         struct ldb_context *ldb;
234         struct aclread_context *ac;
235         struct ldb_message *ret_msg;
236         struct ldb_message *msg;
237         int ret, num_of_attrs = 0;
238         unsigned int i, k = 0;
239         struct security_descriptor *sd = NULL;
240         struct dom_sid *sid = NULL;
241         TALLOC_CTX *tmp_ctx;
242         uint32_t instanceType;
243         const struct dsdb_class *objectclass;
244
245         ac = talloc_get_type(req->context, struct aclread_context);
246         ldb = ldb_module_get_ctx(ac->module);
247         if (!ares) {
248                 return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR );
249         }
250         if (ares->error != LDB_SUCCESS) {
251                 return ldb_module_done(ac->req, ares->controls,
252                                        ares->response, ares->error);
253         }
254         tmp_ctx = talloc_new(ac);
255         switch (ares->type) {
256         case LDB_REPLY_ENTRY:
257                 msg = ares->message;
258                 ret = aclread_get_sd_from_ldb_message(ac, msg, &sd);
259                 if (ret != LDB_SUCCESS) {
260                         ldb_debug_set(ldb, LDB_DEBUG_FATAL,
261                                       "acl_read: cannot get descriptor of %s: %s\n",
262                                       ldb_dn_get_linearized(msg->dn), ldb_strerror(ret));
263                         ret = LDB_ERR_OPERATIONS_ERROR;
264                         goto fail;
265                 } else if (sd == NULL) {
266                         ldb_debug_set(ldb, LDB_DEBUG_FATAL,
267                                       "acl_read: cannot get descriptor of %s (attribute not found)\n",
268                                       ldb_dn_get_linearized(msg->dn));
269                         ret = LDB_ERR_OPERATIONS_ERROR;
270                         goto fail;
271                 }
272                 /*
273                  * Get the most specific structural object class for the ACL check
274                  */
275                 objectclass = dsdb_get_structural_oc_from_msg(ac->schema, msg);
276                 if (objectclass == NULL) {
277                         ldb_asprintf_errstring(ldb, "acl_read: Failed to find a structural class for %s",
278                                                ldb_dn_get_linearized(msg->dn));
279                         ret = LDB_ERR_OPERATIONS_ERROR;
280                         goto fail;
281                 }
282
283                 sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid");
284                 /* get the object instance type */
285                 instanceType = ldb_msg_find_attr_as_uint(msg,
286                                                          "instanceType", 0);
287                 if (!ldb_dn_is_null(msg->dn) && !(instanceType & INSTANCE_TYPE_IS_NC_HEAD))
288                 {
289                         /* the object has a parent, so we have to check for visibility */
290                         ret = aclread_check_parent(ac, msg, req);
291                         
292                         if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
293                                 talloc_free(tmp_ctx);
294                                 return LDB_SUCCESS;
295                         } else if (ret != LDB_SUCCESS) {
296                                 ldb_debug_set(ldb, LDB_DEBUG_FATAL,
297                                               "acl_read: %s check parent %s - %s\n",
298                                               ldb_dn_get_linearized(msg->dn),
299                                               ldb_strerror(ret),
300                                               ldb_errstring(ldb));
301                                 goto fail;
302                         }
303                 }
304
305                 /* for every element in the message check RP */
306                 for (i=0; i < msg->num_elements; i++) {
307                         const struct dsdb_attribute *attr;
308                         bool is_sd, is_objectsid, is_instancetype, is_objectclass;
309                         uint32_t access_mask;
310                         attr = dsdb_attribute_by_lDAPDisplayName(ac->schema,
311                                                                  msg->elements[i].name);
312                         if (!attr) {
313                                 ldb_debug_set(ldb, LDB_DEBUG_FATAL,
314                                               "acl_read: %s cannot find attr[%s] in of schema\n",
315                                               ldb_dn_get_linearized(msg->dn),
316                                               msg->elements[i].name);
317                                 ret = LDB_ERR_OPERATIONS_ERROR;
318                                 goto fail;
319                         }
320                         is_sd = ldb_attr_cmp("nTSecurityDescriptor",
321                                               msg->elements[i].name) == 0;
322                         is_objectsid = ldb_attr_cmp("objectSid",
323                                                     msg->elements[i].name) == 0;
324                         is_instancetype = ldb_attr_cmp("instanceType",
325                                                        msg->elements[i].name) == 0;
326                         is_objectclass = ldb_attr_cmp("objectClass",
327                                                       msg->elements[i].name) == 0;
328                         /* these attributes were added to perform access checks and must be removed */
329                         if (is_objectsid && ac->added_objectSid) {
330                                 aclread_mark_inaccesslible(&msg->elements[i]);
331                                 continue;
332                         }
333                         if (is_instancetype && ac->added_instanceType) {
334                                 aclread_mark_inaccesslible(&msg->elements[i]);
335                                 continue;
336                         }
337                         if (is_objectclass && ac->added_objectClass) {
338                                 aclread_mark_inaccesslible(&msg->elements[i]);
339                                 continue;
340                         }
341                         if (is_sd && ac->added_nTSecurityDescriptor) {
342                                 aclread_mark_inaccesslible(&msg->elements[i]);
343                                 continue;
344                         }
345                         /* nTSecurityDescriptor is a special case */
346                         if (is_sd) {
347                                 access_mask = 0;
348
349                                 if (ac->sd_flags & (SECINFO_OWNER|SECINFO_GROUP)) {
350                                         access_mask |= SEC_STD_READ_CONTROL;
351                                 }
352                                 if (ac->sd_flags & SECINFO_DACL) {
353                                         access_mask |= SEC_STD_READ_CONTROL;
354                                 }
355                                 if (ac->sd_flags & SECINFO_SACL) {
356                                         access_mask |= SEC_FLAG_SYSTEM_SECURITY;
357                                 }
358                         } else {
359                                 access_mask = SEC_ADS_READ_PROP;
360                         }
361
362                         if (attr->searchFlags & SEARCH_FLAG_CONFIDENTIAL) {
363                                 access_mask |= SEC_ADS_CONTROL_ACCESS;
364                         }
365
366                         if (access_mask == 0) {
367                                 aclread_mark_inaccesslible(&msg->elements[i]);
368                                 continue;
369                         }
370
371                         ret = acl_check_access_on_attribute(ac->module,
372                                                             tmp_ctx,
373                                                             sd,
374                                                             sid,
375                                                             access_mask,
376                                                             attr,
377                                                             objectclass);
378
379                         /*
380                          * Dirsync control needs the replpropertymetadata attribute
381                          * so return it as it will be removed by the control
382                          * in anycase.
383                          */
384                         if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
385                                 if (!ac->indirsync) {
386                                         /*
387                                          * do not return this entry if attribute is
388                                          * part of the search filter
389                                          */
390                                         if (dsdb_attr_in_parse_tree(ac->req->op.search.tree,
391                                                                 msg->elements[i].name)) {
392                                                 talloc_free(tmp_ctx);
393                                                 return LDB_SUCCESS;
394                                         }
395                                         aclread_mark_inaccesslible(&msg->elements[i]);
396                                 } else {
397                                         /*
398                                          * We are doing dirysnc answers
399                                          * and the object shouldn't be returned (normally)
400                                          * but we will return it without replPropertyMetaData
401                                          * so that the dirysync module will do what is needed
402                                          * (remove the object if it is not deleted, or return
403                                          * just the objectGUID if it's deleted).
404                                          */
405                                         if (dsdb_attr_in_parse_tree(ac->req->op.search.tree,
406                                                                 msg->elements[i].name)) {
407                                                 ldb_msg_remove_attr(msg, "replPropertyMetaData");
408                                                 break;
409                                         } else {
410                                                 aclread_mark_inaccesslible(&msg->elements[i]);
411                                         }
412                                 }
413                         } else if (ret != LDB_SUCCESS) {
414                                 ldb_debug_set(ldb, LDB_DEBUG_FATAL,
415                                               "acl_read: %s check attr[%s] gives %s - %s\n",
416                                               ldb_dn_get_linearized(msg->dn),
417                                               msg->elements[i].name,
418                                               ldb_strerror(ret),
419                                               ldb_errstring(ldb));
420                                 goto fail;
421                         }
422                 }
423                 for (i=0; i < msg->num_elements; i++) {
424                         if (!aclread_is_inaccessible(&msg->elements[i])) {
425                                 num_of_attrs++;
426                         }
427                 }
428                 /*create a new message to return*/
429                 ret_msg = ldb_msg_new(ac->req);
430                 ret_msg->dn = msg->dn;
431                 talloc_steal(ret_msg, msg->dn);
432                 ret_msg->num_elements = num_of_attrs;
433                 if (num_of_attrs > 0) {
434                         ret_msg->elements = talloc_array(ret_msg,
435                                                          struct ldb_message_element,
436                                                          num_of_attrs);
437                         if (ret_msg->elements == NULL) {
438                                 return ldb_oom(ldb);
439                         }
440                         for (i=0; i < msg->num_elements; i++) {
441                                 bool to_remove = aclread_is_inaccessible(&msg->elements[i]);
442                                 if (!to_remove) {
443                                         ret_msg->elements[k] = msg->elements[i];
444                                         talloc_steal(ret_msg->elements, msg->elements[i].name);
445                                         talloc_steal(ret_msg->elements, msg->elements[i].values);
446                                         k++;
447                                 }
448                         }
449                         /*
450                          * This should not be needed, but some modules
451                          * may allocate values on the wrong context...
452                          */
453                         talloc_steal(ret_msg->elements, msg);
454                 } else {
455                         ret_msg->elements = NULL;
456                 }
457                 talloc_free(tmp_ctx);
458
459                 return ldb_module_send_entry(ac->req, ret_msg, ares->controls);
460         case LDB_REPLY_REFERRAL:
461                 return ldb_module_send_referral(ac->req, ares->referral);
462         case LDB_REPLY_DONE:
463                 return ldb_module_done(ac->req, ares->controls,
464                                         ares->response, LDB_SUCCESS);
465
466         }
467         return LDB_SUCCESS;
468 fail:
469         talloc_free(tmp_ctx);
470         return ldb_module_done(ac->req, NULL, NULL, ret);
471 }
472
473
474 static int aclread_search(struct ldb_module *module, struct ldb_request *req)
475 {
476         struct ldb_context *ldb;
477         int ret;
478         struct aclread_context *ac;
479         struct ldb_request *down_req;
480         struct ldb_control *as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID);
481         uint32_t flags = ldb_req_get_custom_flags(req);
482         struct ldb_result *res;
483         struct aclread_private *p;
484         bool need_sd = false;
485         bool explicit_sd_flags = false;
486         bool is_untrusted = ldb_req_is_untrusted(req);
487         static const char * const _all_attrs[] = { "*", NULL };
488         bool all_attrs = false;
489         const char * const *attrs = NULL;
490         uint32_t instanceType;
491         static const char *acl_attrs[] = {
492                 "instanceType",
493                 NULL
494         };
495
496         ldb = ldb_module_get_ctx(module);
497         p = talloc_get_type(ldb_module_get_private(module), struct aclread_private);
498
499         /* skip access checks if we are system or system control is supplied
500          * or this is not LDAP server request */
501         if (!p || !p->enabled ||
502             dsdb_module_am_system(module)
503             || as_system || !is_untrusted) {
504                 return ldb_next_request(module, req);
505         }
506         /* no checks on special dn */
507         if (ldb_dn_is_special(req->op.search.base)) {
508                 return ldb_next_request(module, req);
509         }
510
511         /* check accessibility of base */
512         if (!ldb_dn_is_null(req->op.search.base)) {
513                 ret = dsdb_module_search_dn(module, req, &res, req->op.search.base,
514                                             acl_attrs,
515                                             DSDB_FLAG_NEXT_MODULE |
516                                             DSDB_FLAG_AS_SYSTEM |
517                                             DSDB_SEARCH_SHOW_RECYCLED,
518                                             req);
519                 if (ret != LDB_SUCCESS) {
520                         return ldb_error(ldb, ret,
521                                         "acl_read: Error retrieving instanceType for base.");
522                 }
523                 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
524                                                         "instanceType", 0);
525                 if (instanceType != 0 && !(instanceType & INSTANCE_TYPE_IS_NC_HEAD))
526                 {
527                         /* the object has a parent, so we have to check for visibility */
528                         struct ldb_dn *parent_dn = ldb_dn_get_parent(req, req->op.search.base);
529                         ret = dsdb_module_check_access_on_dn(module,
530                                                              req,
531                                                              parent_dn,
532                                                              SEC_ADS_LIST,
533                                                              NULL, req);
534                         if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
535                                 return ldb_module_done(req, NULL, NULL, LDB_ERR_NO_SUCH_OBJECT);
536                         } else if (ret != LDB_SUCCESS) {
537                                 return ldb_module_done(req, NULL, NULL, ret);
538                         }
539                 }
540         }
541         ac = talloc_zero(req, struct aclread_context);
542         if (ac == NULL) {
543                 return ldb_oom(ldb);
544         }
545         ac->module = module;
546         ac->req = req;
547         ac->schema = dsdb_get_schema(ldb, req);
548         if (flags & DSDB_ACL_CHECKS_DIRSYNC_FLAG) {
549                 ac->indirsync = true;
550         } else {
551                 ac->indirsync = false;
552         }
553         if (!ac->schema) {
554                 return ldb_operr(ldb);
555         }
556
557         attrs = req->op.search.attrs;
558         if (attrs == NULL) {
559                 all_attrs = true;
560                 attrs = _all_attrs;
561         } else if (attrs[0] == NULL) {
562                 all_attrs = true;
563                 attrs = _all_attrs;
564         } else if (ldb_attr_in_list(attrs, "*")) {
565                 all_attrs = true;
566         }
567
568         /*
569          * In theory we should also check for the SD control but control verification is
570          * expensive so we'd better had the ntsecuritydescriptor to the list of
571          * searched attribute and then remove it !
572          */
573         ac->sd_flags = dsdb_request_sd_flags(ac->req, &explicit_sd_flags);
574
575         if (ldb_attr_in_list(attrs, "nTSecurityDescriptor")) {
576                 need_sd = false;
577         } else if (explicit_sd_flags && all_attrs) {
578                 need_sd = false;
579         } else {
580                 need_sd = true;
581         }
582
583         if (!all_attrs) {
584                 if (!ldb_attr_in_list(attrs, "instanceType")) {
585                         attrs = ldb_attr_list_copy_add(ac, attrs, "instanceType");
586                         if (attrs == NULL) {
587                                 return ldb_oom(ldb);
588                         }
589                         ac->added_instanceType = true;
590                 }
591                 if (!ldb_attr_in_list(req->op.search.attrs, "objectSid")) {
592                         attrs = ldb_attr_list_copy_add(ac, attrs, "objectSid");
593                         if (attrs == NULL) {
594                                 return ldb_oom(ldb);
595                         }
596                         ac->added_objectSid = true;
597                 }
598                 if (!ldb_attr_in_list(req->op.search.attrs, "objectClass")) {
599                         attrs = ldb_attr_list_copy_add(ac, attrs, "objectClass");
600                         if (attrs == NULL) {
601                                 return ldb_oom(ldb);
602                         }
603                         ac->added_objectClass = true;
604                 }
605         }
606
607         if (need_sd) {
608                 attrs = ldb_attr_list_copy_add(ac, attrs, "nTSecurityDescriptor");
609                 if (attrs == NULL) {
610                         return ldb_oom(ldb);
611                 }
612                 ac->added_nTSecurityDescriptor = true;
613         }
614
615         ac->attrs = req->op.search.attrs;
616         ret = ldb_build_search_req_ex(&down_req,
617                                       ldb, ac,
618                                       req->op.search.base,
619                                       req->op.search.scope,
620                                       req->op.search.tree,
621                                       attrs,
622                                       req->controls,
623                                       ac, aclread_callback,
624                                       req);
625
626         if (ret != LDB_SUCCESS) {
627                 return LDB_ERR_OPERATIONS_ERROR;
628         }
629
630         return ldb_next_request(module, down_req);
631 }
632
633 static int aclread_init(struct ldb_module *module)
634 {
635         struct ldb_context *ldb = ldb_module_get_ctx(module);
636         struct aclread_private *p = talloc_zero(module, struct aclread_private);
637         if (p == NULL) {
638                 return ldb_module_oom(module);
639         }
640         p->enabled = lpcfg_parm_bool(ldb_get_opaque(ldb, "loadparm"), NULL, "acl", "search", true);
641         ldb_module_set_private(module, p);
642         return ldb_next_init(module);
643 }
644
645 static const struct ldb_module_ops ldb_aclread_module_ops = {
646         .name              = "aclread",
647         .search            = aclread_search,
648         .init_context      = aclread_init
649 };
650
651 int ldb_aclread_module_init(const char *version)
652 {
653         LDB_MODULE_CHECK_VERSION(version);
654         return ldb_register_module(&ldb_aclread_module_ops);
655 }