provision: Ignore duplicate attid and governsID check
[sfrench/samba-autobuild/.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 = NULL;
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_ERR_OTHER;
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         /* not NC, so the object should have a parent*/
361         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
362                 ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR, 
363                                  talloc_asprintf(msg, "Parent dn %s for %s does not exist",
364                                                  ldb_dn_get_linearized(parent_dn),
365                                                  ldb_dn_get_linearized(msg->dn)));
366                 talloc_free(parent_dn);
367                 return ret;
368         } else if (ret != LDB_SUCCESS) {
369                 talloc_free(parent_dn);
370                 return ret;
371         }
372         talloc_free(parent_dn);
373
374         parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
375         if (!parent_guid) {
376                 talloc_free(parent_res);
377                 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
378         }
379
380         v = data_blob_dup_talloc(parent_res, *parent_guid);
381         if (!v.data) {
382                 talloc_free(parent_res);
383                 return ldb_oom(ldb_module_get_ctx(module));
384         }
385         ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
386         talloc_free(parent_res);
387         return ret;
388 }
389
390 static int construct_modifyTimeStamp(struct ldb_module *module,
391                                         struct ldb_message *msg, enum ldb_scope scope,
392                                         struct ldb_request *parent)
393 {
394         struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
395         struct ldb_context *ldb = ldb_module_get_ctx(module);
396
397         /* We may be being called before the init function has finished */
398         if (!data) {
399                 return LDB_SUCCESS;
400         }
401
402         /* Try and set this value up, if possible.  Don't worry if it
403          * fails, we may not have the DB set up yet.
404          */
405         if (!data->aggregate_dn) {
406                 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
407         }
408
409         if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
410                 /*
411                  * If we have the DN for the object with common name = Aggregate and
412                  * the request is for this DN then let's do the following:
413                  * 1) search the object which changedUSN correspond to the one of the loaded
414                  * schema.
415                  * 2) Get the whenChanged attribute
416                  * 3) Generate the modifyTimestamp out of the whenChanged attribute
417                  */
418                 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
419                 char *value = ldb_timestring(msg, schema->ts_last_change);
420
421                 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
422         }
423         return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
424 }
425
426 /*
427   construct a subSchemaSubEntry
428 */
429 static int construct_subschema_subentry(struct ldb_module *module,
430                                         struct ldb_message *msg, enum ldb_scope scope,
431                                         struct ldb_request *parent)
432 {
433         struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
434         char *subSchemaSubEntry;
435
436         /* We may be being called before the init function has finished */
437         if (!data) {
438                 return LDB_SUCCESS;
439         }
440
441         /* Try and set this value up, if possible.  Don't worry if it
442          * fails, we may not have the DB set up yet, and it's not
443          * really vital anyway */
444         if (!data->aggregate_dn) {
445                 struct ldb_context *ldb = ldb_module_get_ctx(module);
446                 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
447         }
448
449         if (data->aggregate_dn) {
450                 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
451                 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
452         }
453         return LDB_SUCCESS;
454 }
455
456
457 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
458                                          struct ldb_message *msg,
459                                          struct ldb_message_element *object_category)
460 {
461         struct ldb_context *ldb;
462         struct ldb_dn *dn;
463         const struct ldb_val *val;
464
465         ldb = ldb_module_get_ctx(module);
466         if (!ldb) {
467                 DEBUG(4, (__location__ ": Failed to get ldb \n"));
468                 return LDB_ERR_OPERATIONS_ERROR;
469         }
470
471         dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
472         if (!dn) {
473                 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
474                           (const char *)object_category->values[0].data));
475                 return ldb_operr(ldb);
476         }
477
478         val = ldb_dn_get_rdn_val(dn);
479         if (!val) {
480                 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
481                           ldb_dn_get_linearized(dn)));
482                 return ldb_operr(ldb);
483         }
484
485         if (strequal((const char *)val->data, "NTDS-DSA")) {
486                 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
487         } else {
488                 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
489         }
490         return LDB_SUCCESS;
491 }
492
493 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
494                                                 struct ldb_message *msg,
495                                                 struct ldb_dn *dn,
496                                                 struct ldb_request *parent)
497 {
498         struct ldb_dn *server_dn;
499         const char *attr_obj_cat[] = { "objectCategory", NULL };
500         struct ldb_result *res;
501         struct ldb_message_element *object_category;
502         int ret;
503
504         server_dn = ldb_dn_copy(msg, dn);
505         if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
506                 DEBUG(4, (__location__ ": Failed to add child to %s \n",
507                           ldb_dn_get_linearized(server_dn)));
508                 return ldb_operr(ldb_module_get_ctx(module));
509         }
510
511         ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
512                                     DSDB_FLAG_NEXT_MODULE, parent);
513         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
514                 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
515                                          ldb_dn_get_linearized(server_dn)));
516                 return LDB_SUCCESS;
517         } else if (ret != LDB_SUCCESS) {
518                 return ret;
519         }
520
521         object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
522         if (!object_category) {
523                 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
524                          ldb_dn_get_linearized(res->msgs[0]->dn)));
525                 return LDB_SUCCESS;
526         }
527         return construct_msds_isrodc_with_dn(module, msg, object_category);
528 }
529
530 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
531                                                   struct ldb_message *msg,
532                                                   struct ldb_request *parent)
533 {
534         int ret;
535         struct ldb_dn *server_dn;
536
537         ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
538                                        &server_dn, parent);
539         if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
540                 /* it's OK if we can't find serverReferenceBL attribute */
541                 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
542                          ldb_dn_get_linearized(msg->dn)));
543                 return LDB_SUCCESS;
544         } else if (ret != LDB_SUCCESS) {
545                 return ret;
546         }
547
548         return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
549 }
550
551 /*
552   construct msDS-isRODC attr
553 */
554 static int construct_msds_isrodc(struct ldb_module *module,
555                                  struct ldb_message *msg, enum ldb_scope scope,
556                                  struct ldb_request *parent)
557 {
558         struct ldb_message_element * object_class;
559         struct ldb_message_element * object_category;
560         unsigned int i;
561
562         object_class = ldb_msg_find_element(msg, "objectClass");
563         if (!object_class) {
564                 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
565                          ldb_dn_get_linearized(msg->dn)));
566                 return ldb_operr(ldb_module_get_ctx(module));
567         }
568
569         for (i=0; i<object_class->num_values; i++) {
570                 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
571                         /* If TO!objectCategory  equals the DN of the classSchema  object for the nTDSDSA
572                          * object class, then TO!msDS-isRODC  is false. Otherwise, TO!msDS-isRODC  is true.
573                          */
574                         object_category = ldb_msg_find_element(msg, "objectCategory");
575                         if (!object_category) {
576                                 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
577                                          ldb_dn_get_linearized(msg->dn)));
578                                 return LDB_SUCCESS;
579                         }
580                         return construct_msds_isrodc_with_dn(module, msg, object_category);
581                 }
582                 if (strequal((const char*)object_class->values[i].data, "server")) {
583                         /* Let TN be the nTDSDSA  object whose DN is "CN=NTDS Settings," prepended to
584                          * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA  object" case,
585                          * substituting TN for TO.
586                          */
587                         return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
588                 }
589                 if (strequal((const char*)object_class->values[i].data, "computer")) {
590                         /* Let TS be the server  object named by TO!serverReferenceBL. Apply the previous
591                          * rule for the "TO is a server  object" case, substituting TS for TO.
592                          */
593                         return construct_msds_isrodc_with_computer_dn(module, msg, parent);
594                 }
595         }
596
597         return LDB_SUCCESS;
598 }
599
600
601 /*
602   construct msDS-keyVersionNumber attr
603
604   TODO:  Make this based on the 'win2k' DS huristics bit...
605
606 */
607 static int construct_msds_keyversionnumber(struct ldb_module *module,
608                                            struct ldb_message *msg,
609                                            enum ldb_scope scope,
610                                            struct ldb_request *parent)
611 {
612         uint32_t i;
613         enum ndr_err_code ndr_err;
614         const struct ldb_val *omd_value;
615         struct replPropertyMetaDataBlob *omd;
616         int ret;
617
618         omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
619         if (!omd_value) {
620                 /* We can't make up a key version number without meta data */
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                       list_replace[a].attr, 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 }