dsdb: Add tokenGroupsGlobalAndUniversal, tokenGroups, tokenGroupsNoGCAcceptable
[samba.git] / source4 / dsdb / samdb / ldb_modules / operational.c
1 /*
2    ldb database library
3
4    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2010
5    Copyright (C) Andrew Tridgell 2005
6    Copyright (C) Simo Sorce 2006-2008
7    Copyright (C) Matthias Dieter Wallnöfer 2009
8
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 3 of the License, or
12    (at your option) any later version.
13    
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18    
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 /*
24   handle operational attributes
25  */
26
27 /*
28   createTimeStamp: HIDDEN, searchable, ldaptime, alias for whenCreated
29   modifyTimeStamp: HIDDEN, searchable, ldaptime, alias for whenChanged
30
31      for the above two, we do the search as normal, and if
32      createTimeStamp or modifyTimeStamp is asked for, then do
33      additional searches for whenCreated and whenChanged and fill in
34      the resulting values
35
36      we also need to replace these with the whenCreated/whenChanged
37      equivalent in the search expression trees
38
39   whenCreated: not-HIDDEN, CONSTRUCTED, SEARCHABLE
40   whenChanged: not-HIDDEN, CONSTRUCTED, SEARCHABLE
41
42      on init we need to setup attribute handlers for these so
43      comparisons are done correctly. The resolution is 1 second.
44
45      on add we need to add both the above, for current time
46
47      on modify we need to change whenChanged
48
49   structuralObjectClass: HIDDEN, CONSTRUCTED, not-searchable. always same as objectclass?
50
51      for this one we do the search as normal, then if requested ask
52      for objectclass, change the attribute name, and add it
53
54   primaryGroupToken: HIDDEN, CONSTRUCTED, SEARCHABLE
55
56      contains the RID of a certain group object
57     
58
59   attributeTypes: in schema only
60   objectClasses: in schema only
61   matchingRules: in schema only
62   matchingRuleUse: in schema only
63   creatorsName: not supported by w2k3?
64   modifiersName: not supported by w2k3?
65 */
66
67 #include "includes.h"
68 #include <ldb.h>
69 #include <ldb_module.h>
70
71 #include "librpc/gen_ndr/ndr_misc.h"
72 #include "librpc/gen_ndr/ndr_drsblobs.h"
73 #include "param/param.h"
74 #include "dsdb/samdb/samdb.h"
75 #include "dsdb/samdb/ldb_modules/util.h"
76
77 #include "libcli/security/security.h"
78
79 #ifndef ARRAY_SIZE
80 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
81 #endif
82
83 struct operational_data {
84         struct ldb_dn *aggregate_dn;
85 };
86
87 enum search_type {
88         TOKEN_GROUPS,
89         TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL,
90         TOKEN_GROUPS_NO_GC_ACCEPTABLE
91 };
92
93 /*
94   construct a canonical name from a message
95 */
96 static int construct_canonical_name(struct ldb_module *module,
97                                     struct ldb_message *msg, enum ldb_scope scope,
98                                     struct ldb_request *parent)
99 {
100         char *canonicalName;
101         canonicalName = ldb_dn_canonical_string(msg, msg->dn);
102         if (canonicalName == NULL) {
103                 return ldb_operr(ldb_module_get_ctx(module));
104         }
105         return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
106 }
107
108 /*
109   construct a primary group token for groups from a message
110 */
111 static int construct_primary_group_token(struct ldb_module *module,
112                                          struct ldb_message *msg, enum ldb_scope scope,
113                                          struct ldb_request *parent)
114 {
115         struct ldb_context *ldb;
116         uint32_t primary_group_token;
117         
118         ldb = ldb_module_get_ctx(module);
119         if (ldb_match_msg_objectclass(msg, "group") == 1) {
120                 primary_group_token
121                         = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
122                 if (primary_group_token == 0) {
123                         return LDB_SUCCESS;
124                 }
125
126                 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
127                         primary_group_token);
128         } else {
129                 return LDB_SUCCESS;
130         }
131 }
132
133 /*
134   construct the token groups for SAM objects from a message
135 */
136 static int construct_generic_token_groups(struct ldb_module *module,
137                                           struct ldb_message *msg, enum ldb_scope scope,
138                                           struct ldb_request *parent,
139                                           const char *attribute_string,
140                                           enum search_type type)
141 {
142         struct ldb_context *ldb = ldb_module_get_ctx(module);
143         TALLOC_CTX *tmp_ctx = talloc_new(msg);
144         unsigned int i;
145         int ret;
146         const char *filter;
147
148         NTSTATUS status;
149
150         struct dom_sid *primary_group_sid;
151         const char *primary_group_string;
152         const char *primary_group_dn;
153         DATA_BLOB primary_group_blob;
154
155         struct dom_sid *account_sid;
156         const char *account_sid_string;
157         const char *account_sid_dn;
158         DATA_BLOB account_sid_blob;
159         struct dom_sid *groupSIDs = NULL;
160         unsigned int num_groupSIDs = 0;
161
162         struct dom_sid *domain_sid;
163
164         if (scope != LDB_SCOPE_BASE) {
165                 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
166                 return LDB_ERR_OPERATIONS_ERROR;
167         }
168
169         /* If it's not a user, it won't have a primaryGroupID */
170         if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
171                 talloc_free(tmp_ctx);
172                 return LDB_SUCCESS;
173         }
174
175         /* Ensure it has an objectSID too */
176         account_sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid");
177         if (account_sid == NULL) {
178                 talloc_free(tmp_ctx);
179                 return LDB_SUCCESS;
180         }
181
182         status = dom_sid_split_rid(tmp_ctx, account_sid, &domain_sid, NULL);
183         if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
184                 talloc_free(tmp_ctx);
185                 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
186         } else if (!NT_STATUS_IS_OK(status)) {
187                 talloc_free(tmp_ctx);
188                 return LDB_ERR_OPERATIONS_ERROR;
189         }
190
191         primary_group_sid = dom_sid_add_rid(tmp_ctx,
192                                             domain_sid,
193                                             ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
194         if (!primary_group_sid) {
195                 talloc_free(tmp_ctx);
196                 return ldb_oom(ldb);
197         }
198
199         /* only return security groups */
200         switch(type) {
201         case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
202                 filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u)(|(groupType:1.2.840.113556.1.4.803:=%u)(groupType:1.2.840.113556.1.4.803:=%u)))",
203                                          GROUP_TYPE_SECURITY_ENABLED, GROUP_TYPE_ACCOUNT_GROUP, GROUP_TYPE_UNIVERSAL_GROUP);
204                 break;
205         case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
206         case TOKEN_GROUPS:
207                 filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
208                                          GROUP_TYPE_SECURITY_ENABLED);
209                 break;
210         }
211
212         if (!filter) {
213                 talloc_free(tmp_ctx);
214                 return ldb_oom(ldb);
215         }
216
217         primary_group_string = dom_sid_string(tmp_ctx, primary_group_sid);
218         if (!primary_group_string) {
219                 talloc_free(tmp_ctx);
220                 return ldb_oom(ldb);
221         }
222
223         primary_group_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", primary_group_string);
224         if (!primary_group_dn) {
225                 talloc_free(tmp_ctx);
226                 return ldb_oom(ldb);
227         }
228
229         primary_group_blob = data_blob_string_const(primary_group_dn);
230
231         account_sid_string = dom_sid_string(tmp_ctx, account_sid);
232         if (!account_sid_string) {
233                 talloc_free(tmp_ctx);
234                 return ldb_oom(ldb);
235         }
236
237         account_sid_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", account_sid_string);
238         if (!account_sid_dn) {
239                 talloc_free(tmp_ctx);
240                 return ldb_oom(ldb);
241         }
242
243         account_sid_blob = data_blob_string_const(account_sid_dn);
244
245         status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
246                                            true, /* We don't want to add the object's SID itself,
247                                                     it's not returend in this attribute */
248                                            filter,
249                                            tmp_ctx, &groupSIDs, &num_groupSIDs);
250
251         if (!NT_STATUS_IS_OK(status)) {
252                 ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
253                                        account_sid_string, nt_errstr(status));
254                 talloc_free(tmp_ctx);
255                 return LDB_ERR_OPERATIONS_ERROR;
256         }
257
258         /* Expands the primary group - this function takes in
259          * memberOf-like values, so we fake one up with the
260          * <SID=S-...> format of DN and then let it expand
261          * them, as long as they meet the filter - so only
262          * domain groups, not builtin groups
263          */
264         status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
265                                            tmp_ctx, &groupSIDs, &num_groupSIDs);
266         if (!NT_STATUS_IS_OK(status)) {
267                 ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
268                                        account_sid_string, nt_errstr(status));
269                 talloc_free(tmp_ctx);
270                 return LDB_ERR_OPERATIONS_ERROR;
271         }
272
273         for (i=0; i < num_groupSIDs; i++) {
274                 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i]);
275                 if (ret) {
276                         talloc_free(tmp_ctx);
277                         return ret;
278                 }
279         }
280
281         return LDB_SUCCESS;
282 }
283
284 static int construct_token_groups(struct ldb_module *module,
285                                   struct ldb_message *msg, enum ldb_scope scope,
286                                   struct ldb_request *parent)
287 {
288         /**
289          * TODO: Add in a limiting domain when we start to support
290          * trusted domains.
291          */
292         return construct_generic_token_groups(module, msg, scope, parent,
293                                               "tokenGroups",
294                                               TOKEN_GROUPS);
295 }
296
297 static int construct_token_groups_no_gc(struct ldb_module *module,
298                                         struct ldb_message *msg, enum ldb_scope scope,
299                                         struct ldb_request *parent)
300 {
301         /**
302          * TODO: Add in a limiting domain when we start to support
303          * trusted domains.
304          */
305         return construct_generic_token_groups(module, msg, scope, parent,
306                                               "tokenGroupsNoGCAcceptable",
307                                               TOKEN_GROUPS);
308 }
309
310 static int construct_global_universal_token_groups(struct ldb_module *module,
311                                                    struct ldb_message *msg, enum ldb_scope scope,
312                                                    struct ldb_request *parent)
313 {
314         return construct_generic_token_groups(module, msg, scope, parent,
315                                               "tokenGroupsGlobalAndUniversal",
316                                               TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
317 }
318 /*
319   construct the parent GUID for an entry from a message
320 */
321 static int construct_parent_guid(struct ldb_module *module,
322                                  struct ldb_message *msg, enum ldb_scope scope,
323                                  struct ldb_request *parent)
324 {
325         struct ldb_result *res, *parent_res;
326         const struct ldb_val *parent_guid;
327         const char *attrs[] = { "instanceType", NULL };
328         const char *attrs2[] = { "objectGUID", NULL };
329         uint32_t instanceType;
330         int ret;
331         struct ldb_dn *parent_dn;
332         struct ldb_val v;
333
334         /* determine if the object is NC by instance type */
335         ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
336                                     DSDB_FLAG_NEXT_MODULE |
337                                     DSDB_SEARCH_SHOW_RECYCLED, parent);
338         if (ret != LDB_SUCCESS) {
339                 return ret;
340         }
341
342         instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
343                                                  "instanceType", 0);
344         talloc_free(res);
345         if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
346                 DEBUG(4,(__location__ ": Object %s is NC\n",
347                          ldb_dn_get_linearized(msg->dn)));
348                 return LDB_SUCCESS;
349         }
350         parent_dn = ldb_dn_get_parent(msg, msg->dn);
351
352         if (parent_dn == NULL) {
353                 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
354                                          ldb_dn_get_linearized(msg->dn)));
355                 return LDB_SUCCESS;
356         }
357         ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
358                                     DSDB_FLAG_NEXT_MODULE |
359                                     DSDB_SEARCH_SHOW_RECYCLED, parent);
360         talloc_free(parent_dn);
361
362         /* not NC, so the object should have a parent*/
363         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
364                 return ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR, 
365                                  talloc_asprintf(msg, "Parent dn for %s does not exist", 
366                                                  ldb_dn_get_linearized(msg->dn)));
367         } else if (ret != LDB_SUCCESS) {
368                 return ret;
369         }
370
371         parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
372         if (!parent_guid) {
373                 talloc_free(parent_res);
374                 return LDB_SUCCESS;
375         }
376
377         v = data_blob_dup_talloc(parent_res, *parent_guid);
378         if (!v.data) {
379                 talloc_free(parent_res);
380                 return ldb_oom(ldb_module_get_ctx(module));
381         }
382         ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
383         talloc_free(parent_res);
384         return ret;
385 }
386
387 static int construct_modifyTimeStamp(struct ldb_module *module,
388                                         struct ldb_message *msg, enum ldb_scope scope,
389                                         struct ldb_request *parent)
390 {
391         struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
392         struct ldb_context *ldb = ldb_module_get_ctx(module);
393
394         /* We may be being called before the init function has finished */
395         if (!data) {
396                 return LDB_SUCCESS;
397         }
398
399         /* Try and set this value up, if possible.  Don't worry if it
400          * fails, we may not have the DB set up yet.
401          */
402         if (!data->aggregate_dn) {
403                 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
404         }
405
406         if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
407                 /*
408                  * If we have the DN for the object with common name = Aggregate and
409                  * the request is for this DN then let's do the following:
410                  * 1) search the object which changedUSN correspond to the one of the loaded
411                  * schema.
412                  * 2) Get the whenChanged attribute
413                  * 3) Generate the modifyTimestamp out of the whenChanged attribute
414                  */
415                 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
416                 char *value = ldb_timestring(msg, schema->ts_last_change);
417
418                 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
419         }
420         return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
421 }
422
423 /*
424   construct a subSchemaSubEntry
425 */
426 static int construct_subschema_subentry(struct ldb_module *module,
427                                         struct ldb_message *msg, enum ldb_scope scope,
428                                         struct ldb_request *parent)
429 {
430         struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
431         char *subSchemaSubEntry;
432
433         /* We may be being called before the init function has finished */
434         if (!data) {
435                 return LDB_SUCCESS;
436         }
437
438         /* Try and set this value up, if possible.  Don't worry if it
439          * fails, we may not have the DB set up yet, and it's not
440          * really vital anyway */
441         if (!data->aggregate_dn) {
442                 struct ldb_context *ldb = ldb_module_get_ctx(module);
443                 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
444         }
445
446         if (data->aggregate_dn) {
447                 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
448                 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
449         }
450         return LDB_SUCCESS;
451 }
452
453
454 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
455                                          struct ldb_message *msg,
456                                          struct ldb_message_element *object_category)
457 {
458         struct ldb_context *ldb;
459         struct ldb_dn *dn;
460         const struct ldb_val *val;
461
462         ldb = ldb_module_get_ctx(module);
463         if (!ldb) {
464                 DEBUG(4, (__location__ ": Failed to get ldb \n"));
465                 return ldb_operr(ldb);
466         }
467
468         dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
469         if (!dn) {
470                 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
471                           (const char *)object_category->values[0].data));
472                 return ldb_operr(ldb);
473         }
474
475         val = ldb_dn_get_rdn_val(dn);
476         if (!val) {
477                 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
478                           ldb_dn_get_linearized(dn)));
479                 return ldb_operr(ldb);
480         }
481
482         if (strequal((const char *)val->data, "NTDS-DSA")) {
483                 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
484         } else {
485                 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
486         }
487         return LDB_SUCCESS;
488 }
489
490 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
491                                                 struct ldb_message *msg,
492                                                 struct ldb_dn *dn,
493                                                 struct ldb_request *parent)
494 {
495         struct ldb_dn *server_dn;
496         const char *attr_obj_cat[] = { "objectCategory", NULL };
497         struct ldb_result *res;
498         struct ldb_message_element *object_category;
499         int ret;
500
501         server_dn = ldb_dn_copy(msg, dn);
502         if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
503                 DEBUG(4, (__location__ ": Failed to add child to %s \n",
504                           ldb_dn_get_linearized(server_dn)));
505                 return ldb_operr(ldb_module_get_ctx(module));
506         }
507
508         ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
509                                     DSDB_FLAG_NEXT_MODULE, parent);
510         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
511                 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
512                                          ldb_dn_get_linearized(server_dn)));
513                 return LDB_SUCCESS;
514         } else if (ret != LDB_SUCCESS) {
515                 return ret;
516         }
517
518         object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
519         if (!object_category) {
520                 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
521                          ldb_dn_get_linearized(res->msgs[0]->dn)));
522                 return LDB_SUCCESS;
523         }
524         return construct_msds_isrodc_with_dn(module, msg, object_category);
525 }
526
527 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
528                                                   struct ldb_message *msg,
529                                                   struct ldb_request *parent)
530 {
531         int ret;
532         struct ldb_dn *server_dn;
533
534         ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
535                                        &server_dn, parent);
536         if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
537                 /* it's OK if we can't find serverReferenceBL attribute */
538                 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
539                          ldb_dn_get_linearized(msg->dn)));
540                 return LDB_SUCCESS;
541         } else if (ret != LDB_SUCCESS) {
542                 return ret;
543         }
544
545         return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
546 }
547
548 /*
549   construct msDS-isRODC attr
550 */
551 static int construct_msds_isrodc(struct ldb_module *module,
552                                  struct ldb_message *msg, enum ldb_scope scope,
553                                  struct ldb_request *parent)
554 {
555         struct ldb_message_element * object_class;
556         struct ldb_message_element * object_category;
557         unsigned int i;
558
559         object_class = ldb_msg_find_element(msg, "objectClass");
560         if (!object_class) {
561                 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
562                          ldb_dn_get_linearized(msg->dn)));
563                 return ldb_operr(ldb_module_get_ctx(module));
564         }
565
566         for (i=0; i<object_class->num_values; i++) {
567                 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
568                         /* If TO!objectCategory  equals the DN of the classSchema  object for the nTDSDSA
569                          * object class, then TO!msDS-isRODC  is false. Otherwise, TO!msDS-isRODC  is true.
570                          */
571                         object_category = ldb_msg_find_element(msg, "objectCategory");
572                         if (!object_category) {
573                                 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
574                                          ldb_dn_get_linearized(msg->dn)));
575                                 return LDB_SUCCESS;
576                         }
577                         return construct_msds_isrodc_with_dn(module, msg, object_category);
578                 }
579                 if (strequal((const char*)object_class->values[i].data, "server")) {
580                         /* Let TN be the nTDSDSA  object whose DN is "CN=NTDS Settings," prepended to
581                          * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA  object" case,
582                          * substituting TN for TO.
583                          */
584                         return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
585                 }
586                 if (strequal((const char*)object_class->values[i].data, "computer")) {
587                         /* Let TS be the server  object named by TO!serverReferenceBL. Apply the previous
588                          * rule for the "TO is a server  object" case, substituting TS for TO.
589                          */
590                         return construct_msds_isrodc_with_computer_dn(module, msg, parent);
591                 }
592         }
593
594         return LDB_SUCCESS;
595 }
596
597
598 /*
599   construct msDS-keyVersionNumber attr
600
601   TODO:  Make this based on the 'win2k' DS huristics bit...
602
603 */
604 static int construct_msds_keyversionnumber(struct ldb_module *module,
605                                            struct ldb_message *msg,
606                                            enum ldb_scope scope,
607                                            struct ldb_request *parent)
608 {
609         uint32_t i;
610         enum ndr_err_code ndr_err;
611         const struct ldb_val *omd_value;
612         struct replPropertyMetaDataBlob *omd;
613         int ret;
614
615         omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
616         if (!omd_value) {
617                 /* We can't make up a key version number without meta data */
618                 return LDB_SUCCESS;
619         }
620         if (!omd_value) {
621                 return LDB_SUCCESS;
622         }
623
624         omd = talloc(msg, struct replPropertyMetaDataBlob);
625         if (!omd) {
626                 ldb_module_oom(module);
627                 return LDB_SUCCESS;
628         }
629
630         ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
631                                        (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
632         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
633                 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
634                          ldb_dn_get_linearized(msg->dn)));
635                 return ldb_operr(ldb_module_get_ctx(module));
636         }
637
638         if (omd->version != 1) {
639                 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
640                          omd->version, ldb_dn_get_linearized(msg->dn)));
641                 talloc_free(omd);
642                 return LDB_SUCCESS;
643         }
644         for (i=0; i<omd->ctr.ctr1.count; i++) {
645                 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
646                         ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
647                                                  msg, msg,
648                                                  "msDS-KeyVersionNumber",
649                                                  omd->ctr.ctr1.array[i].version);
650                         if (ret != LDB_SUCCESS) {
651                                 talloc_free(omd);
652                                 return ret;
653                         }
654                         break;
655                 }
656         }
657         return LDB_SUCCESS;
658
659 }
660
661 #define _UF_TRUST_ACCOUNTS ( \
662         UF_WORKSTATION_TRUST_ACCOUNT | \
663         UF_SERVER_TRUST_ACCOUNT | \
664         UF_INTERDOMAIN_TRUST_ACCOUNT \
665 )
666 #define _UF_NO_EXPIRY_ACCOUNTS ( \
667         UF_SMARTCARD_REQUIRED | \
668         UF_DONT_EXPIRE_PASSWD | \
669         _UF_TRUST_ACCOUNTS \
670 )
671
672 /*
673   calculate msDS-UserPasswordExpiryTimeComputed
674 */
675 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
676                                                 struct ldb_message *msg,
677                                                 struct ldb_dn *domain_dn)
678 {
679         int64_t pwdLastSet, maxPwdAge;
680         uint32_t userAccountControl;
681         NTTIME ret;
682
683         userAccountControl = ldb_msg_find_attr_as_uint(msg,
684                                         "userAccountControl",
685                                         0);
686         if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
687                 return 0x7FFFFFFFFFFFFFFFULL;
688         }
689
690         pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
691         if (pwdLastSet == 0) {
692                 return 0;
693         }
694
695         if (pwdLastSet <= -1) {
696                 /*
697                  * This can't really happen...
698                  */
699                 return 0x7FFFFFFFFFFFFFFFULL;
700         }
701
702         if (pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL) {
703                 /*
704                  * Somethings wrong with the clock...
705                  */
706                 return 0x7FFFFFFFFFFFFFFFULL;
707         }
708
709         /*
710          * Note that maxPwdAge is a stored as negative value.
711          *
712          * Possible values are in the range of:
713          *
714          * maxPwdAge: -864000000001
715          * to
716          * maxPwdAge: -9223372036854775808 (-0x8000000000000000ULL)
717          *
718          */
719         maxPwdAge = samdb_search_int64(ldb_module_get_ctx(module), msg, 0,
720                                        domain_dn, "maxPwdAge", NULL);
721         if (maxPwdAge >= -864000000000) {
722                 /*
723                  * This is not really possible...
724                  */
725                 return 0x7FFFFFFFFFFFFFFFULL;
726         }
727
728         if (maxPwdAge == -0x8000000000000000ULL) {
729                 return 0x7FFFFFFFFFFFFFFFULL;
730         }
731
732         /*
733          * Note we already catched maxPwdAge == -0x8000000000000000ULL
734          * and pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL above.
735          *
736          * Remember maxPwdAge is a negative number,
737          * so it results in the following.
738          *
739          * 0x7FFFFFFFFFFFFFFEULL + 0x7FFFFFFFFFFFFFFFULL
740          * =
741          * 0xFFFFFFFFFFFFFFFFULL
742          */
743         ret = pwdLastSet - maxPwdAge;
744         if (ret >= 0x7FFFFFFFFFFFFFFFULL) {
745                 return 0x7FFFFFFFFFFFFFFFULL;
746         }
747
748         return ret;
749 }
750
751
752 /*
753   construct msDS-User-Account-Control-Computed attr
754 */
755 static int construct_msds_user_account_control_computed(struct ldb_module *module,
756                                                         struct ldb_message *msg, enum ldb_scope scope,
757                                                         struct ldb_request *parent)
758 {
759         uint32_t userAccountControl;
760         uint32_t msDS_User_Account_Control_Computed = 0;
761         struct ldb_context *ldb = ldb_module_get_ctx(module);
762         NTTIME now;
763         struct ldb_dn *nc_root;
764         int ret;
765
766         ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
767         if (ret != 0) {
768                 ldb_asprintf_errstring(ldb,
769                                        "Failed to find NC root of DN: %s: %s",
770                                        ldb_dn_get_linearized(msg->dn),
771                                        ldb_errstring(ldb_module_get_ctx(module)));
772                 return ret;
773         }
774         if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
775                 /* Only calculate this on our default NC */
776                 return 0;
777         }
778         /* Test account expire time */
779         unix_to_nt_time(&now, time(NULL));
780
781         userAccountControl = ldb_msg_find_attr_as_uint(msg,
782                                                        "userAccountControl",
783                                                        0);
784         if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
785
786                 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
787                 if (lockoutTime != 0) {
788                         int64_t lockoutDuration = samdb_search_int64(ldb,
789                                                                      msg, 0, nc_root,
790                                                                      "lockoutDuration", NULL);
791                         if (lockoutDuration >= 0) {
792                                 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
793                         } else if (lockoutTime - lockoutDuration >= now) {
794                                 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
795                         }
796                 }
797         }
798
799         if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
800                 NTTIME must_change_time
801                         = get_msds_user_password_expiry_time_computed(module,
802                                                                       msg, nc_root);
803                 /* check for expired password */
804                 if (must_change_time < now) {
805                         msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
806                 }
807         }
808
809         return samdb_msg_add_int64(ldb,
810                                    msg->elements, msg,
811                                    "msDS-User-Account-Control-Computed",
812                                    msDS_User_Account_Control_Computed);
813 }
814
815 /*
816   construct msDS-UserPasswordExpiryTimeComputed
817 */
818 static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
819                                                              struct ldb_message *msg, enum ldb_scope scope,
820                                                              struct ldb_request *parent)
821 {
822         struct ldb_context *ldb = ldb_module_get_ctx(module);
823         struct ldb_dn *nc_root;
824         int64_t password_expiry_time;
825         int ret;
826
827         ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
828         if (ret != 0) {
829                 ldb_asprintf_errstring(ldb,
830                                        "Failed to find NC root of DN: %s: %s",
831                                        ldb_dn_get_linearized(msg->dn),
832                                        ldb_errstring(ldb));
833                 return ret;
834         }
835
836         if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
837                 /* Only calculate this on our default NC */
838                 return 0;
839         }
840
841         password_expiry_time
842                 = get_msds_user_password_expiry_time_computed(module, msg,
843                                                               nc_root);
844
845         return samdb_msg_add_int64(ldb,
846                                    msg->elements, msg,
847                                    "msDS-UserPasswordExpiryTimeComputed",
848                                    password_expiry_time);
849 }
850
851
852 struct op_controls_flags {
853         bool sd;
854         bool bypassoperational;
855 };
856
857 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
858         if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
859                 return true;
860         }
861         return false;
862 }
863
864 /*
865   a list of attribute names that should be substituted in the parse
866   tree before the search is done
867 */
868 static const struct {
869         const char *attr;
870         const char *replace;
871 } parse_tree_sub[] = {
872         { "createTimeStamp", "whenCreated" },
873         { "modifyTimeStamp", "whenChanged" }
874 };
875
876
877 struct op_attributes_replace {
878         const char *attr;
879         const char *replace;
880         const char * const *extra_attrs;
881         int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
882 };
883
884
885 static const char *objectSid_attr[] =
886 {
887         "objectSid",
888         NULL
889 };
890
891
892 static const char *objectCategory_attr[] =
893 {
894         "objectCategory",
895         NULL
896 };
897
898
899 static const char *user_account_control_computed_attrs[] =
900 {
901         "lockoutTime",
902         "pwdLastSet",
903         NULL
904 };
905
906
907 static const char *user_password_expiry_time_computed_attrs[] =
908 {
909         "pwdLastSet",
910         NULL
911 };
912
913
914 /*
915   a list of attribute names that are hidden, but can be searched for
916   using another (non-hidden) name to produce the correct result
917 */
918 static const struct op_attributes_replace search_sub[] = {
919         { "createTimeStamp", "whenCreated", NULL , NULL },
920         { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
921         { "structuralObjectClass", "objectClass", NULL , NULL },
922         { "canonicalName", NULL, NULL , construct_canonical_name },
923         { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
924         { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
925         { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
926         { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
927         { "parentGUID", NULL, NULL, construct_parent_guid },
928         { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
929         { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
930         { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
931         { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
932           construct_msds_user_account_control_computed },
933         { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
934           construct_msds_user_password_expiry_time_computed }
935 };
936
937
938 enum op_remove {
939         OPERATIONAL_REMOVE_ALWAYS, /* remove always */
940         OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
941         OPERATIONAL_SD_FLAGS,      /* show if SD_FLAGS_OID set, or asked for */
942         OPERATIONAL_REMOVE_UNLESS_CONTROL        /* remove always unless an adhoc control has been specified */
943 };
944
945 /*
946   a list of attributes that may need to be removed from the
947   underlying db return
948
949   Some of these are attributes that were once stored, but are now calculated
950 */
951 struct op_attributes_operations {
952         const char *attr;
953         enum op_remove op;
954 };
955
956 static const struct op_attributes_operations operational_remove[] = {
957         { "nTSecurityDescriptor",    OPERATIONAL_SD_FLAGS },
958         { "msDS-KeyVersionNumber",   OPERATIONAL_REMOVE_UNLESS_CONTROL  },
959         { "parentGUID",              OPERATIONAL_REMOVE_ALWAYS  },
960         { "replPropertyMetaData",    OPERATIONAL_REMOVE_UNASKED },
961 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
962         { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
963 };
964
965
966 /*
967   post process a search result record. For any search_sub[] attributes that were
968   asked for, we need to call the appropriate copy routine to copy the result
969   into the message, then remove any attributes that we added to the search but
970   were not asked for by the user
971 */
972 static int operational_search_post_process(struct ldb_module *module,
973                                            struct ldb_message *msg,
974                                            enum ldb_scope scope,
975                                            const char * const *attrs_from_user,
976                                            const char * const *attrs_searched_for,
977                                            struct op_controls_flags* controls_flags,
978                                            struct op_attributes_operations *list,
979                                            unsigned int list_size,
980                                            struct op_attributes_replace *list_replace,
981                                            unsigned int list_replace_size,
982                                            struct ldb_request *parent)
983 {
984         struct ldb_context *ldb;
985         unsigned int i, a = 0;
986         bool constructed_attributes = false;
987
988         ldb = ldb_module_get_ctx(module);
989
990         /* removed any attrs that should not be shown to the user */
991         for (i=0; i < list_size; i++) {
992                 ldb_msg_remove_attr(msg, list[i].attr);
993         }
994
995         for (a=0; a < list_replace_size; a++) {
996                 if (check_keep_control_for_attribute(controls_flags,
997                                                      list_replace[a].attr)) {
998                         continue;
999                 }
1000
1001                 /* construct the new attribute, using either a supplied
1002                         constructor or a simple copy */
1003                 constructed_attributes = true;
1004                 if (list_replace[a].constructor != NULL) {
1005                         if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
1006                                 goto failed;
1007                         }
1008                 } else if (ldb_msg_copy_attr(msg,
1009                                              list_replace[a].replace,
1010                                              list_replace[a].attr) != LDB_SUCCESS) {
1011                         goto failed;
1012                 }
1013         }
1014
1015         /* Deletion of the search helper attributes are needed if:
1016          * - we generated constructed attributes and
1017          * - we aren't requesting all attributes
1018          */
1019         if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1020                 for (i=0; i < list_replace_size; i++) {
1021                         /* remove the added search helper attributes, unless
1022                          * they were asked for by the user */
1023                         if (list_replace[i].replace != NULL &&
1024                             !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1025                                 ldb_msg_remove_attr(msg, list_replace[i].replace);
1026                         }
1027                         if (list_replace[i].extra_attrs != NULL) {
1028                                 unsigned int j;
1029                                 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1030                                         if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1031                                                 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1032                                         }
1033                                 }
1034                         }
1035                 }
1036         }
1037
1038         return 0;
1039
1040 failed:
1041         ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1042                       "operational_search_post_process failed for attribute '%s' - %s",
1043                       attrs_from_user[a], ldb_errstring(ldb));
1044         return -1;
1045 }
1046
1047 /*
1048   hook search operations
1049 */
1050
1051 struct operational_context {
1052         struct ldb_module *module;
1053         struct ldb_request *req;
1054         enum ldb_scope scope;
1055         const char * const *attrs;
1056         struct op_controls_flags* controls_flags;
1057         struct op_attributes_operations *list_operations;
1058         unsigned int list_operations_size;
1059         struct op_attributes_replace *attrs_to_replace;
1060         unsigned int attrs_to_replace_size;
1061 };
1062
1063 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1064 {
1065         struct operational_context *ac;
1066         int ret;
1067
1068         ac = talloc_get_type(req->context, struct operational_context);
1069
1070         if (!ares) {
1071                 return ldb_module_done(ac->req, NULL, NULL,
1072                                         LDB_ERR_OPERATIONS_ERROR);
1073         }
1074         if (ares->error != LDB_SUCCESS) {
1075                 return ldb_module_done(ac->req, ares->controls,
1076                                         ares->response, ares->error);
1077         }
1078
1079         switch (ares->type) {
1080         case LDB_REPLY_ENTRY:
1081                 /* for each record returned post-process to add any derived
1082                    attributes that have been asked for */
1083                 ret = operational_search_post_process(ac->module,
1084                                                       ares->message,
1085                                                       ac->scope,
1086                                                       ac->attrs,
1087                                                       req->op.search.attrs,
1088                                                       ac->controls_flags,
1089                                                       ac->list_operations,
1090                                                       ac->list_operations_size,
1091                                                       ac->attrs_to_replace,
1092                                                       ac->attrs_to_replace_size,
1093                                                       req);
1094                 if (ret != 0) {
1095                         return ldb_module_done(ac->req, NULL, NULL,
1096                                                 LDB_ERR_OPERATIONS_ERROR);
1097                 }
1098                 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1099
1100         case LDB_REPLY_REFERRAL:
1101                 return ldb_module_send_referral(ac->req, ares->referral);
1102
1103         case LDB_REPLY_DONE:
1104
1105                 return ldb_module_done(ac->req, ares->controls,
1106                                         ares->response, LDB_SUCCESS);
1107         }
1108
1109         talloc_free(ares);
1110         return LDB_SUCCESS;
1111 }
1112
1113 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1114                                                               const char* const* attrs,
1115                                                               const char* const* searched_attrs,
1116                                                               struct op_controls_flags* controls_flags)
1117 {
1118         int idx = 0;
1119         int i;
1120         struct op_attributes_operations *list = talloc_zero_array(ctx,
1121                                                                   struct op_attributes_operations,
1122                                                                   ARRAY_SIZE(operational_remove) + 1);
1123
1124         if (list == NULL) {
1125                 return NULL;
1126         }
1127
1128         for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1129                 switch (operational_remove[i].op) {
1130                 case OPERATIONAL_REMOVE_UNASKED:
1131                         if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1132                                 continue;
1133                         }
1134                         if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1135                                 continue;
1136                         }
1137                         list[idx].attr = operational_remove[i].attr;
1138                         list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1139                         idx++;
1140                         break;
1141
1142                 case OPERATIONAL_REMOVE_ALWAYS:
1143                         list[idx].attr = operational_remove[i].attr;
1144                         list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1145                         idx++;
1146                         break;
1147
1148                 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1149                         if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1150                                 list[idx].attr = operational_remove[i].attr;
1151                                 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1152                                 idx++;
1153                         }
1154                         break;
1155
1156                 case OPERATIONAL_SD_FLAGS:
1157                         if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1158                                 continue;
1159                         }
1160                         if (controls_flags->sd) {
1161                                 if (attrs == NULL) {
1162                                         continue;
1163                                 }
1164                                 if (attrs[0] == NULL) {
1165                                         continue;
1166                                 }
1167                                 if (ldb_attr_in_list(attrs, "*")) {
1168                                         continue;
1169                                 }
1170                         }
1171                         list[idx].attr = operational_remove[i].attr;
1172                         list[idx].op = OPERATIONAL_SD_FLAGS;
1173                         idx++;
1174                         break;
1175                 }
1176         }
1177
1178         return list;
1179 }
1180
1181 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1182 {
1183         struct ldb_context *ldb;
1184         struct operational_context *ac;
1185         struct ldb_request *down_req;
1186         const char **search_attrs = NULL;
1187         unsigned int i, a;
1188         int ret;
1189
1190         /* There are no operational attributes on special DNs */
1191         if (ldb_dn_is_special(req->op.search.base)) {
1192                 return ldb_next_request(module, req);
1193         }
1194
1195         ldb = ldb_module_get_ctx(module);
1196
1197         ac = talloc(req, struct operational_context);
1198         if (ac == NULL) {
1199                 return ldb_oom(ldb);
1200         }
1201
1202         ac->module = module;
1203         ac->req = req;
1204         ac->scope = req->op.search.scope;
1205         ac->attrs = req->op.search.attrs;
1206
1207         /*  FIXME: We must copy the tree and keep the original
1208          *  unmodified. SSS */
1209         /* replace any attributes in the parse tree that are
1210            searchable, but are stored using a different name in the
1211            backend */
1212         for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1213                 ldb_parse_tree_attr_replace(req->op.search.tree,
1214                                             parse_tree_sub[i].attr,
1215                                             parse_tree_sub[i].replace);
1216         }
1217
1218         ac->controls_flags = talloc(ac, struct op_controls_flags);
1219         /* remember if the SD_FLAGS_OID was set */
1220         ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1221         /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1222         ac->controls_flags->bypassoperational =
1223                 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1224
1225         ac->attrs_to_replace = NULL;
1226         ac->attrs_to_replace_size = 0;
1227         /* in the list of attributes we are looking for, rename any
1228            attributes to the alias for any hidden attributes that can
1229            be fetched directly using non-hidden names */
1230         for (a=0;ac->attrs && ac->attrs[a];a++) {
1231                 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1232                         continue;
1233                 }
1234                 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1235
1236                         if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1237                                 continue;
1238                         }
1239
1240                         ac->attrs_to_replace = talloc_realloc(ac,
1241                                                               ac->attrs_to_replace,
1242                                                               struct op_attributes_replace,
1243                                                               ac->attrs_to_replace_size + 1);
1244
1245                         ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1246                         ac->attrs_to_replace_size++;
1247                         if (!search_sub[i].replace) {
1248                                 continue;
1249                         }
1250
1251                         if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1252                                 unsigned int j;
1253                                 const char **search_attrs2;
1254                                 /* Only adds to the end of the list */
1255                                 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1256                                         search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1257                                                                                ? search_attrs
1258                                                                                : ac->attrs, 
1259                                                                                search_sub[i].extra_attrs[j]);
1260                                         if (search_attrs2 == NULL) {
1261                                                 return ldb_operr(ldb);
1262                                         }
1263                                         /* may be NULL, talloc_free() doesn't mind */
1264                                         talloc_free(search_attrs);
1265                                         search_attrs = search_attrs2;
1266                                 }
1267                         }
1268
1269                         if (!search_attrs) {
1270                                 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1271                                 if (search_attrs == NULL) {
1272                                         return ldb_operr(ldb);
1273                                 }
1274                         }
1275                         /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1276                         search_attrs[a] = search_sub[i].replace;
1277                 }
1278         }
1279         ac->list_operations = operation_get_op_list(ac, ac->attrs,
1280                                                     search_attrs == NULL?req->op.search.attrs:search_attrs,
1281                                                     ac->controls_flags);
1282         ac->list_operations_size = 0;
1283         i = 0;
1284
1285         while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1286                 i++;
1287         }
1288         ac->list_operations_size = i;
1289         ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1290                                         req->op.search.base,
1291                                         req->op.search.scope,
1292                                         req->op.search.tree,
1293                                         /* use new set of attrs if any */
1294                                         search_attrs == NULL?req->op.search.attrs:search_attrs,
1295                                         req->controls,
1296                                         ac, operational_callback,
1297                                         req);
1298         LDB_REQ_SET_LOCATION(down_req);
1299         if (ret != LDB_SUCCESS) {
1300                 return ldb_operr(ldb);
1301         }
1302
1303         /* perform the search */
1304         return ldb_next_request(module, down_req);
1305 }
1306
1307 static int operational_init(struct ldb_module *ctx)
1308 {
1309         struct operational_data *data;
1310         int ret;
1311
1312         ret = ldb_next_init(ctx);
1313
1314         if (ret != LDB_SUCCESS) {
1315                 return ret;
1316         }
1317
1318         data = talloc_zero(ctx, struct operational_data);
1319         if (!data) {
1320                 return ldb_module_oom(ctx);
1321         }
1322
1323         ldb_module_set_private(ctx, data);
1324
1325         return LDB_SUCCESS;
1326 }
1327
1328 static const struct ldb_module_ops ldb_operational_module_ops = {
1329         .name              = "operational",
1330         .search            = operational_search,
1331         .init_context      = operational_init
1332 };
1333
1334 int ldb_operational_module_init(const char *version)
1335 {
1336         LDB_MODULE_CHECK_VERSION(version);
1337         return ldb_register_module(&ldb_operational_module_ops);
1338 }