dsdb: Use attribute-name parameter for error message
[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 = 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 %s: expanding groups of SID %s failed: %s",
253                                        attribute_string, account_sid_string,
254                                        nt_errstr(status));
255                 talloc_free(tmp_ctx);
256                 return LDB_ERR_OPERATIONS_ERROR;
257         }
258
259         /* Expands the primary group - this function takes in
260          * memberOf-like values, so we fake one up with the
261          * <SID=S-...> format of DN and then let it expand
262          * them, as long as they meet the filter - so only
263          * domain groups, not builtin groups
264          */
265         status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
266                                            tmp_ctx, &groupSIDs, &num_groupSIDs);
267         if (!NT_STATUS_IS_OK(status)) {
268                 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
269                                        attribute_string, account_sid_string,
270                                        nt_errstr(status));
271                 talloc_free(tmp_ctx);
272                 return LDB_ERR_OPERATIONS_ERROR;
273         }
274
275         for (i=0; i < num_groupSIDs; i++) {
276                 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i]);
277                 if (ret) {
278                         talloc_free(tmp_ctx);
279                         return ret;
280                 }
281         }
282
283         return LDB_SUCCESS;
284 }
285
286 static int construct_token_groups(struct ldb_module *module,
287                                   struct ldb_message *msg, enum ldb_scope scope,
288                                   struct ldb_request *parent)
289 {
290         /**
291          * TODO: Add in a limiting domain when we start to support
292          * trusted domains.
293          */
294         return construct_generic_token_groups(module, msg, scope, parent,
295                                               "tokenGroups",
296                                               TOKEN_GROUPS);
297 }
298
299 static int construct_token_groups_no_gc(struct ldb_module *module,
300                                         struct ldb_message *msg, enum ldb_scope scope,
301                                         struct ldb_request *parent)
302 {
303         /**
304          * TODO: Add in a limiting domain when we start to support
305          * trusted domains.
306          */
307         return construct_generic_token_groups(module, msg, scope, parent,
308                                               "tokenGroupsNoGCAcceptable",
309                                               TOKEN_GROUPS);
310 }
311
312 static int construct_global_universal_token_groups(struct ldb_module *module,
313                                                    struct ldb_message *msg, enum ldb_scope scope,
314                                                    struct ldb_request *parent)
315 {
316         return construct_generic_token_groups(module, msg, scope, parent,
317                                               "tokenGroupsGlobalAndUniversal",
318                                               TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
319 }
320 /*
321   construct the parent GUID for an entry from a message
322 */
323 static int construct_parent_guid(struct ldb_module *module,
324                                  struct ldb_message *msg, enum ldb_scope scope,
325                                  struct ldb_request *parent)
326 {
327         struct ldb_result *res, *parent_res;
328         const struct ldb_val *parent_guid;
329         const char *attrs[] = { "instanceType", NULL };
330         const char *attrs2[] = { "objectGUID", NULL };
331         uint32_t instanceType;
332         int ret;
333         struct ldb_dn *parent_dn;
334         struct ldb_val v;
335
336         /* determine if the object is NC by instance type */
337         ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
338                                     DSDB_FLAG_NEXT_MODULE |
339                                     DSDB_SEARCH_SHOW_RECYCLED, parent);
340         if (ret != LDB_SUCCESS) {
341                 return ret;
342         }
343
344         instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
345                                                  "instanceType", 0);
346         talloc_free(res);
347         if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
348                 DEBUG(4,(__location__ ": Object %s is NC\n",
349                          ldb_dn_get_linearized(msg->dn)));
350                 return LDB_SUCCESS;
351         }
352         parent_dn = ldb_dn_get_parent(msg, msg->dn);
353
354         if (parent_dn == NULL) {
355                 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
356                                          ldb_dn_get_linearized(msg->dn)));
357                 return LDB_ERR_OTHER;
358         }
359         ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
360                                     DSDB_FLAG_NEXT_MODULE |
361                                     DSDB_SEARCH_SHOW_RECYCLED, parent);
362         /* not NC, so the object should have a parent*/
363         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
364                 ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR, 
365                                  talloc_asprintf(msg, "Parent dn %s for %s does not exist",
366                                                  ldb_dn_get_linearized(parent_dn),
367                                                  ldb_dn_get_linearized(msg->dn)));
368                 talloc_free(parent_dn);
369                 return ret;
370         } else if (ret != LDB_SUCCESS) {
371                 talloc_free(parent_dn);
372                 return ret;
373         }
374         talloc_free(parent_dn);
375
376         parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
377         if (!parent_guid) {
378                 talloc_free(parent_res);
379                 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
380         }
381
382         v = data_blob_dup_talloc(parent_res, *parent_guid);
383         if (!v.data) {
384                 talloc_free(parent_res);
385                 return ldb_oom(ldb_module_get_ctx(module));
386         }
387         ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
388         talloc_free(parent_res);
389         return ret;
390 }
391
392 static int construct_modifyTimeStamp(struct ldb_module *module,
393                                         struct ldb_message *msg, enum ldb_scope scope,
394                                         struct ldb_request *parent)
395 {
396         struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
397         struct ldb_context *ldb = ldb_module_get_ctx(module);
398
399         /* We may be being called before the init function has finished */
400         if (!data) {
401                 return LDB_SUCCESS;
402         }
403
404         /* Try and set this value up, if possible.  Don't worry if it
405          * fails, we may not have the DB set up yet.
406          */
407         if (!data->aggregate_dn) {
408                 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
409         }
410
411         if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
412                 /*
413                  * If we have the DN for the object with common name = Aggregate and
414                  * the request is for this DN then let's do the following:
415                  * 1) search the object which changedUSN correspond to the one of the loaded
416                  * schema.
417                  * 2) Get the whenChanged attribute
418                  * 3) Generate the modifyTimestamp out of the whenChanged attribute
419                  */
420                 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
421                 char *value = ldb_timestring(msg, schema->ts_last_change);
422
423                 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
424         }
425         return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
426 }
427
428 /*
429   construct a subSchemaSubEntry
430 */
431 static int construct_subschema_subentry(struct ldb_module *module,
432                                         struct ldb_message *msg, enum ldb_scope scope,
433                                         struct ldb_request *parent)
434 {
435         struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
436         char *subSchemaSubEntry;
437
438         /* We may be being called before the init function has finished */
439         if (!data) {
440                 return LDB_SUCCESS;
441         }
442
443         /* Try and set this value up, if possible.  Don't worry if it
444          * fails, we may not have the DB set up yet, and it's not
445          * really vital anyway */
446         if (!data->aggregate_dn) {
447                 struct ldb_context *ldb = ldb_module_get_ctx(module);
448                 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
449         }
450
451         if (data->aggregate_dn) {
452                 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
453                 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
454         }
455         return LDB_SUCCESS;
456 }
457
458
459 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
460                                          struct ldb_message *msg,
461                                          struct ldb_message_element *object_category)
462 {
463         struct ldb_context *ldb;
464         struct ldb_dn *dn;
465         const struct ldb_val *val;
466
467         ldb = ldb_module_get_ctx(module);
468         if (!ldb) {
469                 DEBUG(4, (__location__ ": Failed to get ldb \n"));
470                 return LDB_ERR_OPERATIONS_ERROR;
471         }
472
473         dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
474         if (!dn) {
475                 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
476                           (const char *)object_category->values[0].data));
477                 return ldb_operr(ldb);
478         }
479
480         val = ldb_dn_get_rdn_val(dn);
481         if (!val) {
482                 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
483                           ldb_dn_get_linearized(dn)));
484                 return ldb_operr(ldb);
485         }
486
487         if (strequal((const char *)val->data, "NTDS-DSA")) {
488                 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
489         } else {
490                 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
491         }
492         return LDB_SUCCESS;
493 }
494
495 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
496                                                 struct ldb_message *msg,
497                                                 struct ldb_dn *dn,
498                                                 struct ldb_request *parent)
499 {
500         struct ldb_dn *server_dn;
501         const char *attr_obj_cat[] = { "objectCategory", NULL };
502         struct ldb_result *res;
503         struct ldb_message_element *object_category;
504         int ret;
505
506         server_dn = ldb_dn_copy(msg, dn);
507         if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
508                 DEBUG(4, (__location__ ": Failed to add child to %s \n",
509                           ldb_dn_get_linearized(server_dn)));
510                 return ldb_operr(ldb_module_get_ctx(module));
511         }
512
513         ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
514                                     DSDB_FLAG_NEXT_MODULE, parent);
515         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
516                 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
517                                          ldb_dn_get_linearized(server_dn)));
518                 return LDB_SUCCESS;
519         } else if (ret != LDB_SUCCESS) {
520                 return ret;
521         }
522
523         object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
524         if (!object_category) {
525                 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
526                          ldb_dn_get_linearized(res->msgs[0]->dn)));
527                 return LDB_SUCCESS;
528         }
529         return construct_msds_isrodc_with_dn(module, msg, object_category);
530 }
531
532 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
533                                                   struct ldb_message *msg,
534                                                   struct ldb_request *parent)
535 {
536         int ret;
537         struct ldb_dn *server_dn;
538
539         ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
540                                        &server_dn, parent);
541         if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
542                 /* it's OK if we can't find serverReferenceBL attribute */
543                 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
544                          ldb_dn_get_linearized(msg->dn)));
545                 return LDB_SUCCESS;
546         } else if (ret != LDB_SUCCESS) {
547                 return ret;
548         }
549
550         return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
551 }
552
553 /*
554   construct msDS-isRODC attr
555 */
556 static int construct_msds_isrodc(struct ldb_module *module,
557                                  struct ldb_message *msg, enum ldb_scope scope,
558                                  struct ldb_request *parent)
559 {
560         struct ldb_message_element * object_class;
561         struct ldb_message_element * object_category;
562         unsigned int i;
563
564         object_class = ldb_msg_find_element(msg, "objectClass");
565         if (!object_class) {
566                 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
567                          ldb_dn_get_linearized(msg->dn)));
568                 return ldb_operr(ldb_module_get_ctx(module));
569         }
570
571         for (i=0; i<object_class->num_values; i++) {
572                 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
573                         /* If TO!objectCategory  equals the DN of the classSchema  object for the nTDSDSA
574                          * object class, then TO!msDS-isRODC  is false. Otherwise, TO!msDS-isRODC  is true.
575                          */
576                         object_category = ldb_msg_find_element(msg, "objectCategory");
577                         if (!object_category) {
578                                 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
579                                          ldb_dn_get_linearized(msg->dn)));
580                                 return LDB_SUCCESS;
581                         }
582                         return construct_msds_isrodc_with_dn(module, msg, object_category);
583                 }
584                 if (strequal((const char*)object_class->values[i].data, "server")) {
585                         /* Let TN be the nTDSDSA  object whose DN is "CN=NTDS Settings," prepended to
586                          * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA  object" case,
587                          * substituting TN for TO.
588                          */
589                         return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
590                 }
591                 if (strequal((const char*)object_class->values[i].data, "computer")) {
592                         /* Let TS be the server  object named by TO!serverReferenceBL. Apply the previous
593                          * rule for the "TO is a server  object" case, substituting TS for TO.
594                          */
595                         return construct_msds_isrodc_with_computer_dn(module, msg, parent);
596                 }
597         }
598
599         return LDB_SUCCESS;
600 }
601
602
603 /*
604   construct msDS-keyVersionNumber attr
605
606   TODO:  Make this based on the 'win2k' DS huristics bit...
607
608 */
609 static int construct_msds_keyversionnumber(struct ldb_module *module,
610                                            struct ldb_message *msg,
611                                            enum ldb_scope scope,
612                                            struct ldb_request *parent)
613 {
614         uint32_t i;
615         enum ndr_err_code ndr_err;
616         const struct ldb_val *omd_value;
617         struct replPropertyMetaDataBlob *omd;
618         int ret;
619
620         omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
621         if (!omd_value) {
622                 /* We can't make up a key version number without meta data */
623                 return LDB_SUCCESS;
624         }
625
626         omd = talloc(msg, struct replPropertyMetaDataBlob);
627         if (!omd) {
628                 ldb_module_oom(module);
629                 return LDB_SUCCESS;
630         }
631
632         ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
633                                        (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
634         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
635                 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
636                          ldb_dn_get_linearized(msg->dn)));
637                 return ldb_operr(ldb_module_get_ctx(module));
638         }
639
640         if (omd->version != 1) {
641                 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
642                          omd->version, ldb_dn_get_linearized(msg->dn)));
643                 talloc_free(omd);
644                 return LDB_SUCCESS;
645         }
646         for (i=0; i<omd->ctr.ctr1.count; i++) {
647                 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
648                         ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
649                                                  msg, msg,
650                                                  "msDS-KeyVersionNumber",
651                                                  omd->ctr.ctr1.array[i].version);
652                         if (ret != LDB_SUCCESS) {
653                                 talloc_free(omd);
654                                 return ret;
655                         }
656                         break;
657                 }
658         }
659         return LDB_SUCCESS;
660
661 }
662
663 #define _UF_TRUST_ACCOUNTS ( \
664         UF_WORKSTATION_TRUST_ACCOUNT | \
665         UF_SERVER_TRUST_ACCOUNT | \
666         UF_INTERDOMAIN_TRUST_ACCOUNT \
667 )
668 #define _UF_NO_EXPIRY_ACCOUNTS ( \
669         UF_SMARTCARD_REQUIRED | \
670         UF_DONT_EXPIRE_PASSWD | \
671         _UF_TRUST_ACCOUNTS \
672 )
673
674 /*
675   calculate msDS-UserPasswordExpiryTimeComputed
676 */
677 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
678                                                 struct ldb_message *msg,
679                                                 struct ldb_dn *domain_dn)
680 {
681         int64_t pwdLastSet, maxPwdAge;
682         uint32_t userAccountControl;
683         NTTIME ret;
684
685         userAccountControl = ldb_msg_find_attr_as_uint(msg,
686                                         "userAccountControl",
687                                         0);
688         if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
689                 return 0x7FFFFFFFFFFFFFFFULL;
690         }
691
692         pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
693         if (pwdLastSet == 0) {
694                 return 0;
695         }
696
697         if (pwdLastSet <= -1) {
698                 /*
699                  * This can't really happen...
700                  */
701                 return 0x7FFFFFFFFFFFFFFFULL;
702         }
703
704         if (pwdLastSet >= 0x7FFFFFFFFFFFFFFFLL) {
705                 /*
706                  * Somethings wrong with the clock...
707                  */
708                 return 0x7FFFFFFFFFFFFFFFULL;
709         }
710
711         /*
712          * Note that maxPwdAge is a stored as negative value.
713          *
714          * Possible values are in the range of:
715          *
716          * maxPwdAge: -864000000001
717          * to
718          * maxPwdAge: -9223372036854775808 (-0x8000000000000000ULL)
719          *
720          */
721         maxPwdAge = samdb_search_int64(ldb_module_get_ctx(module), msg, 0,
722                                        domain_dn, "maxPwdAge", NULL);
723         if (maxPwdAge >= -864000000000) {
724                 /*
725                  * This is not really possible...
726                  */
727                 return 0x7FFFFFFFFFFFFFFFULL;
728         }
729
730         if (maxPwdAge == -0x8000000000000000LL) {
731                 return 0x7FFFFFFFFFFFFFFFULL;
732         }
733
734         /*
735          * Note we already catched maxPwdAge == -0x8000000000000000ULL
736          * and pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL above.
737          *
738          * Remember maxPwdAge is a negative number,
739          * so it results in the following.
740          *
741          * 0x7FFFFFFFFFFFFFFEULL + 0x7FFFFFFFFFFFFFFFULL
742          * =
743          * 0xFFFFFFFFFFFFFFFFULL
744          */
745         ret = (NTTIME)pwdLastSet - (NTTIME)maxPwdAge;
746         if (ret >= 0x7FFFFFFFFFFFFFFFULL) {
747                 return 0x7FFFFFFFFFFFFFFFULL;
748         }
749
750         return ret;
751 }
752
753
754 /*
755   construct msDS-User-Account-Control-Computed attr
756 */
757 static int construct_msds_user_account_control_computed(struct ldb_module *module,
758                                                         struct ldb_message *msg, enum ldb_scope scope,
759                                                         struct ldb_request *parent)
760 {
761         uint32_t userAccountControl;
762         uint32_t msDS_User_Account_Control_Computed = 0;
763         struct ldb_context *ldb = ldb_module_get_ctx(module);
764         NTTIME now;
765         struct ldb_dn *nc_root;
766         int ret;
767
768         ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
769         if (ret != 0) {
770                 ldb_asprintf_errstring(ldb,
771                                        "Failed to find NC root of DN: %s: %s",
772                                        ldb_dn_get_linearized(msg->dn),
773                                        ldb_errstring(ldb_module_get_ctx(module)));
774                 return ret;
775         }
776         if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
777                 /* Only calculate this on our default NC */
778                 return 0;
779         }
780         /* Test account expire time */
781         unix_to_nt_time(&now, time(NULL));
782
783         userAccountControl = ldb_msg_find_attr_as_uint(msg,
784                                                        "userAccountControl",
785                                                        0);
786         if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
787
788                 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
789                 if (lockoutTime != 0) {
790                         int64_t lockoutDuration = samdb_search_int64(ldb,
791                                                                      msg, 0, nc_root,
792                                                                      "lockoutDuration", NULL);
793                         if (lockoutDuration >= 0) {
794                                 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
795                         } else if (lockoutTime - lockoutDuration >= now) {
796                                 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
797                         }
798                 }
799         }
800
801         if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
802                 NTTIME must_change_time
803                         = get_msds_user_password_expiry_time_computed(module,
804                                                                       msg, nc_root);
805                 /* check for expired password */
806                 if (must_change_time < now) {
807                         msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
808                 }
809         }
810
811         return samdb_msg_add_int64(ldb,
812                                    msg->elements, msg,
813                                    "msDS-User-Account-Control-Computed",
814                                    msDS_User_Account_Control_Computed);
815 }
816
817 /*
818   construct msDS-UserPasswordExpiryTimeComputed
819 */
820 static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
821                                                              struct ldb_message *msg, enum ldb_scope scope,
822                                                              struct ldb_request *parent)
823 {
824         struct ldb_context *ldb = ldb_module_get_ctx(module);
825         struct ldb_dn *nc_root;
826         int64_t password_expiry_time;
827         int ret;
828
829         ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
830         if (ret != 0) {
831                 ldb_asprintf_errstring(ldb,
832                                        "Failed to find NC root of DN: %s: %s",
833                                        ldb_dn_get_linearized(msg->dn),
834                                        ldb_errstring(ldb));
835                 return ret;
836         }
837
838         if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
839                 /* Only calculate this on our default NC */
840                 return 0;
841         }
842
843         password_expiry_time
844                 = get_msds_user_password_expiry_time_computed(module, msg,
845                                                               nc_root);
846
847         return samdb_msg_add_int64(ldb,
848                                    msg->elements, msg,
849                                    "msDS-UserPasswordExpiryTimeComputed",
850                                    password_expiry_time);
851 }
852
853
854 struct op_controls_flags {
855         bool sd;
856         bool bypassoperational;
857 };
858
859 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
860         if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
861                 return true;
862         }
863         return false;
864 }
865
866 /*
867   a list of attribute names that should be substituted in the parse
868   tree before the search is done
869 */
870 static const struct {
871         const char *attr;
872         const char *replace;
873 } parse_tree_sub[] = {
874         { "createTimeStamp", "whenCreated" },
875         { "modifyTimeStamp", "whenChanged" }
876 };
877
878
879 struct op_attributes_replace {
880         const char *attr;
881         const char *replace;
882         const char * const *extra_attrs;
883         int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
884 };
885
886
887 static const char *objectSid_attr[] =
888 {
889         "objectSid",
890         NULL
891 };
892
893
894 static const char *objectCategory_attr[] =
895 {
896         "objectCategory",
897         NULL
898 };
899
900
901 static const char *user_account_control_computed_attrs[] =
902 {
903         "lockoutTime",
904         "pwdLastSet",
905         NULL
906 };
907
908
909 static const char *user_password_expiry_time_computed_attrs[] =
910 {
911         "pwdLastSet",
912         NULL
913 };
914
915
916 /*
917   a list of attribute names that are hidden, but can be searched for
918   using another (non-hidden) name to produce the correct result
919 */
920 static const struct op_attributes_replace search_sub[] = {
921         { "createTimeStamp", "whenCreated", NULL , NULL },
922         { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
923         { "structuralObjectClass", "objectClass", NULL , NULL },
924         { "canonicalName", NULL, NULL , construct_canonical_name },
925         { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
926         { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
927         { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
928         { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
929         { "parentGUID", NULL, NULL, construct_parent_guid },
930         { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
931         { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
932         { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
933         { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
934           construct_msds_user_account_control_computed },
935         { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
936           construct_msds_user_password_expiry_time_computed }
937 };
938
939
940 enum op_remove {
941         OPERATIONAL_REMOVE_ALWAYS, /* remove always */
942         OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
943         OPERATIONAL_SD_FLAGS,      /* show if SD_FLAGS_OID set, or asked for */
944         OPERATIONAL_REMOVE_UNLESS_CONTROL        /* remove always unless an adhoc control has been specified */
945 };
946
947 /*
948   a list of attributes that may need to be removed from the
949   underlying db return
950
951   Some of these are attributes that were once stored, but are now calculated
952 */
953 struct op_attributes_operations {
954         const char *attr;
955         enum op_remove op;
956 };
957
958 static const struct op_attributes_operations operational_remove[] = {
959         { "nTSecurityDescriptor",    OPERATIONAL_SD_FLAGS },
960         { "msDS-KeyVersionNumber",   OPERATIONAL_REMOVE_UNLESS_CONTROL  },
961         { "parentGUID",              OPERATIONAL_REMOVE_ALWAYS  },
962         { "replPropertyMetaData",    OPERATIONAL_REMOVE_UNASKED },
963 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
964         { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
965 };
966
967
968 /*
969   post process a search result record. For any search_sub[] attributes that were
970   asked for, we need to call the appropriate copy routine to copy the result
971   into the message, then remove any attributes that we added to the search but
972   were not asked for by the user
973 */
974 static int operational_search_post_process(struct ldb_module *module,
975                                            struct ldb_message *msg,
976                                            enum ldb_scope scope,
977                                            const char * const *attrs_from_user,
978                                            const char * const *attrs_searched_for,
979                                            struct op_controls_flags* controls_flags,
980                                            struct op_attributes_operations *list,
981                                            unsigned int list_size,
982                                            struct op_attributes_replace *list_replace,
983                                            unsigned int list_replace_size,
984                                            struct ldb_request *parent)
985 {
986         struct ldb_context *ldb;
987         unsigned int i, a = 0;
988         bool constructed_attributes = false;
989
990         ldb = ldb_module_get_ctx(module);
991
992         /* removed any attrs that should not be shown to the user */
993         for (i=0; i < list_size; i++) {
994                 ldb_msg_remove_attr(msg, list[i].attr);
995         }
996
997         for (a=0; a < list_replace_size; a++) {
998                 if (check_keep_control_for_attribute(controls_flags,
999                                                      list_replace[a].attr)) {
1000                         continue;
1001                 }
1002
1003                 /* construct the new attribute, using either a supplied
1004                         constructor or a simple copy */
1005                 constructed_attributes = true;
1006                 if (list_replace[a].constructor != NULL) {
1007                         if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
1008                                 goto failed;
1009                         }
1010                 } else if (ldb_msg_copy_attr(msg,
1011                                              list_replace[a].replace,
1012                                              list_replace[a].attr) != LDB_SUCCESS) {
1013                         goto failed;
1014                 }
1015         }
1016
1017         /* Deletion of the search helper attributes are needed if:
1018          * - we generated constructed attributes and
1019          * - we aren't requesting all attributes
1020          */
1021         if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1022                 for (i=0; i < list_replace_size; i++) {
1023                         /* remove the added search helper attributes, unless
1024                          * they were asked for by the user */
1025                         if (list_replace[i].replace != NULL &&
1026                             !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1027                                 ldb_msg_remove_attr(msg, list_replace[i].replace);
1028                         }
1029                         if (list_replace[i].extra_attrs != NULL) {
1030                                 unsigned int j;
1031                                 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1032                                         if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1033                                                 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1034                                         }
1035                                 }
1036                         }
1037                 }
1038         }
1039
1040         return 0;
1041
1042 failed:
1043         ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1044                       "operational_search_post_process failed for attribute '%s' - %s",
1045                       list_replace[a].attr, ldb_errstring(ldb));
1046         return -1;
1047 }
1048
1049 /*
1050   hook search operations
1051 */
1052
1053 struct operational_context {
1054         struct ldb_module *module;
1055         struct ldb_request *req;
1056         enum ldb_scope scope;
1057         const char * const *attrs;
1058         struct op_controls_flags* controls_flags;
1059         struct op_attributes_operations *list_operations;
1060         unsigned int list_operations_size;
1061         struct op_attributes_replace *attrs_to_replace;
1062         unsigned int attrs_to_replace_size;
1063 };
1064
1065 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1066 {
1067         struct operational_context *ac;
1068         int ret;
1069
1070         ac = talloc_get_type(req->context, struct operational_context);
1071
1072         if (!ares) {
1073                 return ldb_module_done(ac->req, NULL, NULL,
1074                                         LDB_ERR_OPERATIONS_ERROR);
1075         }
1076         if (ares->error != LDB_SUCCESS) {
1077                 return ldb_module_done(ac->req, ares->controls,
1078                                         ares->response, ares->error);
1079         }
1080
1081         switch (ares->type) {
1082         case LDB_REPLY_ENTRY:
1083                 /* for each record returned post-process to add any derived
1084                    attributes that have been asked for */
1085                 ret = operational_search_post_process(ac->module,
1086                                                       ares->message,
1087                                                       ac->scope,
1088                                                       ac->attrs,
1089                                                       req->op.search.attrs,
1090                                                       ac->controls_flags,
1091                                                       ac->list_operations,
1092                                                       ac->list_operations_size,
1093                                                       ac->attrs_to_replace,
1094                                                       ac->attrs_to_replace_size,
1095                                                       req);
1096                 if (ret != 0) {
1097                         return ldb_module_done(ac->req, NULL, NULL,
1098                                                 LDB_ERR_OPERATIONS_ERROR);
1099                 }
1100                 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1101
1102         case LDB_REPLY_REFERRAL:
1103                 return ldb_module_send_referral(ac->req, ares->referral);
1104
1105         case LDB_REPLY_DONE:
1106
1107                 return ldb_module_done(ac->req, ares->controls,
1108                                         ares->response, LDB_SUCCESS);
1109         }
1110
1111         talloc_free(ares);
1112         return LDB_SUCCESS;
1113 }
1114
1115 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1116                                                               const char* const* attrs,
1117                                                               const char* const* searched_attrs,
1118                                                               struct op_controls_flags* controls_flags)
1119 {
1120         int idx = 0;
1121         int i;
1122         struct op_attributes_operations *list = talloc_zero_array(ctx,
1123                                                                   struct op_attributes_operations,
1124                                                                   ARRAY_SIZE(operational_remove) + 1);
1125
1126         if (list == NULL) {
1127                 return NULL;
1128         }
1129
1130         for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1131                 switch (operational_remove[i].op) {
1132                 case OPERATIONAL_REMOVE_UNASKED:
1133                         if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1134                                 continue;
1135                         }
1136                         if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1137                                 continue;
1138                         }
1139                         list[idx].attr = operational_remove[i].attr;
1140                         list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1141                         idx++;
1142                         break;
1143
1144                 case OPERATIONAL_REMOVE_ALWAYS:
1145                         list[idx].attr = operational_remove[i].attr;
1146                         list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1147                         idx++;
1148                         break;
1149
1150                 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1151                         if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1152                                 list[idx].attr = operational_remove[i].attr;
1153                                 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1154                                 idx++;
1155                         }
1156                         break;
1157
1158                 case OPERATIONAL_SD_FLAGS:
1159                         if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1160                                 continue;
1161                         }
1162                         if (controls_flags->sd) {
1163                                 if (attrs == NULL) {
1164                                         continue;
1165                                 }
1166                                 if (attrs[0] == NULL) {
1167                                         continue;
1168                                 }
1169                                 if (ldb_attr_in_list(attrs, "*")) {
1170                                         continue;
1171                                 }
1172                         }
1173                         list[idx].attr = operational_remove[i].attr;
1174                         list[idx].op = OPERATIONAL_SD_FLAGS;
1175                         idx++;
1176                         break;
1177                 }
1178         }
1179
1180         return list;
1181 }
1182
1183 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1184 {
1185         struct ldb_context *ldb;
1186         struct operational_context *ac;
1187         struct ldb_request *down_req;
1188         const char **search_attrs = NULL;
1189         unsigned int i, a;
1190         int ret;
1191
1192         /* There are no operational attributes on special DNs */
1193         if (ldb_dn_is_special(req->op.search.base)) {
1194                 return ldb_next_request(module, req);
1195         }
1196
1197         ldb = ldb_module_get_ctx(module);
1198
1199         ac = talloc(req, struct operational_context);
1200         if (ac == NULL) {
1201                 return ldb_oom(ldb);
1202         }
1203
1204         ac->module = module;
1205         ac->req = req;
1206         ac->scope = req->op.search.scope;
1207         ac->attrs = req->op.search.attrs;
1208
1209         /*  FIXME: We must copy the tree and keep the original
1210          *  unmodified. SSS */
1211         /* replace any attributes in the parse tree that are
1212            searchable, but are stored using a different name in the
1213            backend */
1214         for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1215                 ldb_parse_tree_attr_replace(req->op.search.tree,
1216                                             parse_tree_sub[i].attr,
1217                                             parse_tree_sub[i].replace);
1218         }
1219
1220         ac->controls_flags = talloc(ac, struct op_controls_flags);
1221         /* remember if the SD_FLAGS_OID was set */
1222         ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1223         /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1224         ac->controls_flags->bypassoperational =
1225                 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1226
1227         ac->attrs_to_replace = NULL;
1228         ac->attrs_to_replace_size = 0;
1229         /* in the list of attributes we are looking for, rename any
1230            attributes to the alias for any hidden attributes that can
1231            be fetched directly using non-hidden names */
1232         for (a=0;ac->attrs && ac->attrs[a];a++) {
1233                 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1234                         continue;
1235                 }
1236                 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1237
1238                         if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1239                                 continue;
1240                         }
1241
1242                         ac->attrs_to_replace = talloc_realloc(ac,
1243                                                               ac->attrs_to_replace,
1244                                                               struct op_attributes_replace,
1245                                                               ac->attrs_to_replace_size + 1);
1246
1247                         ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1248                         ac->attrs_to_replace_size++;
1249                         if (!search_sub[i].replace) {
1250                                 continue;
1251                         }
1252
1253                         if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1254                                 unsigned int j;
1255                                 const char **search_attrs2;
1256                                 /* Only adds to the end of the list */
1257                                 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1258                                         search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1259                                                                                ? search_attrs
1260                                                                                : ac->attrs, 
1261                                                                                search_sub[i].extra_attrs[j]);
1262                                         if (search_attrs2 == NULL) {
1263                                                 return ldb_operr(ldb);
1264                                         }
1265                                         /* may be NULL, talloc_free() doesn't mind */
1266                                         talloc_free(search_attrs);
1267                                         search_attrs = search_attrs2;
1268                                 }
1269                         }
1270
1271                         if (!search_attrs) {
1272                                 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1273                                 if (search_attrs == NULL) {
1274                                         return ldb_operr(ldb);
1275                                 }
1276                         }
1277                         /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1278                         search_attrs[a] = search_sub[i].replace;
1279                 }
1280         }
1281         ac->list_operations = operation_get_op_list(ac, ac->attrs,
1282                                                     search_attrs == NULL?req->op.search.attrs:search_attrs,
1283                                                     ac->controls_flags);
1284         ac->list_operations_size = 0;
1285         i = 0;
1286
1287         while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1288                 i++;
1289         }
1290         ac->list_operations_size = i;
1291         ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1292                                         req->op.search.base,
1293                                         req->op.search.scope,
1294                                         req->op.search.tree,
1295                                         /* use new set of attrs if any */
1296                                         search_attrs == NULL?req->op.search.attrs:search_attrs,
1297                                         req->controls,
1298                                         ac, operational_callback,
1299                                         req);
1300         LDB_REQ_SET_LOCATION(down_req);
1301         if (ret != LDB_SUCCESS) {
1302                 return ldb_operr(ldb);
1303         }
1304
1305         /* perform the search */
1306         return ldb_next_request(module, down_req);
1307 }
1308
1309 static int operational_init(struct ldb_module *ctx)
1310 {
1311         struct operational_data *data;
1312         int ret;
1313
1314         ret = ldb_next_init(ctx);
1315
1316         if (ret != LDB_SUCCESS) {
1317                 return ret;
1318         }
1319
1320         data = talloc_zero(ctx, struct operational_data);
1321         if (!data) {
1322                 return ldb_module_oom(ctx);
1323         }
1324
1325         ldb_module_set_private(ctx, data);
1326
1327         return LDB_SUCCESS;
1328 }
1329
1330 static const struct ldb_module_ops ldb_operational_module_ops = {
1331         .name              = "operational",
1332         .search            = operational_search,
1333         .init_context      = operational_init
1334 };
1335
1336 int ldb_operational_module_init(const char *version)
1337 {
1338         LDB_MODULE_CHECK_VERSION(version);
1339         return ldb_register_module(&ldb_operational_module_ops);
1340 }