b1122b3d4294ea2092938e0c54682c290522c670
[metze/samba/wip.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 /*
88   construct a canonical name from a message
89 */
90 static int construct_canonical_name(struct ldb_module *module,
91                                     struct ldb_message *msg, enum ldb_scope scope,
92                                     struct ldb_request *parent)
93 {
94         char *canonicalName;
95         canonicalName = ldb_dn_canonical_string(msg, msg->dn);
96         if (canonicalName == NULL) {
97                 return ldb_operr(ldb_module_get_ctx(module));
98         }
99         return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
100 }
101
102 /*
103   construct a primary group token for groups from a message
104 */
105 static int construct_primary_group_token(struct ldb_module *module,
106                                          struct ldb_message *msg, enum ldb_scope scope,
107                                          struct ldb_request *parent)
108 {
109         struct ldb_context *ldb;
110         uint32_t primary_group_token;
111         
112         ldb = ldb_module_get_ctx(module);
113         if (ldb_match_msg_objectclass(msg, "group") == 1) {
114                 primary_group_token
115                         = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
116                 if (primary_group_token == 0) {
117                         return LDB_SUCCESS;
118                 }
119
120                 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
121                         primary_group_token);
122         } else {
123                 return LDB_SUCCESS;
124         }
125 }
126
127 /*
128   construct the token groups for SAM objects from a message
129 */
130 static int construct_token_groups(struct ldb_module *module,
131                                   struct ldb_message *msg, enum ldb_scope scope,
132                                   struct ldb_request *parent)
133 {
134         struct ldb_context *ldb = ldb_module_get_ctx(module);;
135         TALLOC_CTX *tmp_ctx = talloc_new(msg);
136         unsigned int i;
137         int ret;
138         const char *filter;
139
140         NTSTATUS status;
141
142         struct dom_sid *primary_group_sid;
143         const char *primary_group_string;
144         const char *primary_group_dn;
145         DATA_BLOB primary_group_blob;
146
147         struct dom_sid *account_sid;
148         const char *account_sid_string;
149         const char *account_sid_dn;
150         DATA_BLOB account_sid_blob;
151         struct dom_sid *groupSIDs = NULL;
152         unsigned int num_groupSIDs = 0;
153
154         struct dom_sid *domain_sid;
155
156         if (scope != LDB_SCOPE_BASE) {
157                 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
158                 return LDB_ERR_OPERATIONS_ERROR;
159         }
160
161         /* If it's not a user, it won't have a primaryGroupID */
162         if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
163                 talloc_free(tmp_ctx);
164                 return LDB_SUCCESS;
165         }
166
167         /* Ensure it has an objectSID too */
168         account_sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid");
169         if (account_sid == NULL) {
170                 talloc_free(tmp_ctx);
171                 return LDB_SUCCESS;
172         }
173
174         status = dom_sid_split_rid(tmp_ctx, account_sid, &domain_sid, NULL);
175         if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
176                 talloc_free(tmp_ctx);
177                 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
178         } else if (!NT_STATUS_IS_OK(status)) {
179                 talloc_free(tmp_ctx);
180                 return LDB_ERR_OPERATIONS_ERROR;
181         }
182
183         primary_group_sid = dom_sid_add_rid(tmp_ctx,
184                                             domain_sid,
185                                             ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
186         if (!primary_group_sid) {
187                 talloc_free(tmp_ctx);
188                 return ldb_oom(ldb);
189         }
190
191         /* only return security groups */
192         filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
193                                  GROUP_TYPE_SECURITY_ENABLED);
194         if (!filter) {
195                 talloc_free(tmp_ctx);
196                 return ldb_oom(ldb);
197         }
198
199         primary_group_string = dom_sid_string(tmp_ctx, primary_group_sid);
200         if (!primary_group_string) {
201                 talloc_free(tmp_ctx);
202                 return ldb_oom(ldb);
203         }
204
205         primary_group_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", primary_group_string);
206         if (!primary_group_dn) {
207                 talloc_free(tmp_ctx);
208                 return ldb_oom(ldb);
209         }
210
211         primary_group_blob = data_blob_string_const(primary_group_dn);
212
213         account_sid_string = dom_sid_string(tmp_ctx, account_sid);
214         if (!account_sid_string) {
215                 talloc_free(tmp_ctx);
216                 return ldb_oom(ldb);
217         }
218
219         account_sid_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", account_sid_string);
220         if (!account_sid_dn) {
221                 talloc_free(tmp_ctx);
222                 return ldb_oom(ldb);
223         }
224
225         account_sid_blob = data_blob_string_const(account_sid_dn);
226
227         status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
228                                            true, /* We don't want to add the object's SID itself,
229                                                     it's not returend in this attribute */
230                                            filter,
231                                            tmp_ctx, &groupSIDs, &num_groupSIDs);
232
233         if (!NT_STATUS_IS_OK(status)) {
234                 ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
235                                        account_sid_string, nt_errstr(status));
236                 talloc_free(tmp_ctx);
237                 return LDB_ERR_OPERATIONS_ERROR;
238         }
239
240         /* Expands the primary group - this function takes in
241          * memberOf-like values, so we fake one up with the
242          * <SID=S-...> format of DN and then let it expand
243          * them, as long as they meet the filter - so only
244          * domain groups, not builtin groups
245          */
246         status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
247                                            tmp_ctx, &groupSIDs, &num_groupSIDs);
248         if (!NT_STATUS_IS_OK(status)) {
249                 ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s",
250                                        account_sid_string, nt_errstr(status));
251                 talloc_free(tmp_ctx);
252                 return LDB_ERR_OPERATIONS_ERROR;
253         }
254
255         for (i=0; i < num_groupSIDs; i++) {
256                 ret = samdb_msg_add_dom_sid(ldb, msg, msg, "tokenGroups", &groupSIDs[i]);
257                 if (ret) {
258                         talloc_free(tmp_ctx);
259                         return ret;
260                 }
261         }
262
263         return LDB_SUCCESS;
264 }
265
266 /*
267   construct the parent GUID for an entry from a message
268 */
269 static int construct_parent_guid(struct ldb_module *module,
270                                  struct ldb_message *msg, enum ldb_scope scope,
271                                  struct ldb_request *parent)
272 {
273         struct ldb_result *res, *parent_res;
274         const struct ldb_val *parent_guid;
275         const char *attrs[] = { "instanceType", NULL };
276         const char *attrs2[] = { "objectGUID", NULL };
277         uint32_t instanceType;
278         int ret;
279         struct ldb_dn *parent_dn;
280         struct ldb_val v;
281
282         /* determine if the object is NC by instance type */
283         ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
284                                     DSDB_FLAG_NEXT_MODULE |
285                                     DSDB_SEARCH_SHOW_RECYCLED, parent);
286         if (ret != LDB_SUCCESS) {
287                 return ret;
288         }
289
290         instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
291                                                  "instanceType", 0);
292         talloc_free(res);
293         if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
294                 DEBUG(4,(__location__ ": Object %s is NC\n",
295                          ldb_dn_get_linearized(msg->dn)));
296                 return LDB_SUCCESS;
297         }
298         parent_dn = ldb_dn_get_parent(msg, msg->dn);
299
300         if (parent_dn == NULL) {
301                 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
302                                          ldb_dn_get_linearized(msg->dn)));
303                 return LDB_SUCCESS;
304         }
305         ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
306                                     DSDB_FLAG_NEXT_MODULE |
307                                     DSDB_SEARCH_SHOW_RECYCLED, parent);
308         talloc_free(parent_dn);
309
310         /* not NC, so the object should have a parent*/
311         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
312                 return ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR, 
313                                  talloc_asprintf(msg, "Parent dn for %s does not exist", 
314                                                  ldb_dn_get_linearized(msg->dn)));
315         } else if (ret != LDB_SUCCESS) {
316                 return ret;
317         }
318
319         parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
320         if (!parent_guid) {
321                 talloc_free(parent_res);
322                 return LDB_SUCCESS;
323         }
324
325         v = data_blob_dup_talloc(parent_res, *parent_guid);
326         if (!v.data) {
327                 talloc_free(parent_res);
328                 return ldb_oom(ldb_module_get_ctx(module));
329         }
330         ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
331         talloc_free(parent_res);
332         return ret;
333 }
334
335 static int construct_modifyTimeStamp(struct ldb_module *module,
336                                         struct ldb_message *msg, enum ldb_scope scope,
337                                         struct ldb_request *parent)
338 {
339         struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
340         struct ldb_context *ldb = ldb_module_get_ctx(module);
341
342         /* We may be being called before the init function has finished */
343         if (!data) {
344                 return LDB_SUCCESS;
345         }
346
347         /* Try and set this value up, if possible.  Don't worry if it
348          * fails, we may not have the DB set up yet.
349          */
350         if (!data->aggregate_dn) {
351                 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
352         }
353
354         if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
355                 /*
356                  * If we have the DN for the object with common name = Aggregate and
357                  * the request is for this DN then let's do the following:
358                  * 1) search the object which changedUSN correspond to the one of the loaded
359                  * schema.
360                  * 2) Get the whenChanged attribute
361                  * 3) Generate the modifyTimestamp out of the whenChanged attribute
362                  */
363                 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
364                 char *value = ldb_timestring(msg, schema->ts_last_change);
365
366                 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
367         }
368         return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
369 }
370
371 /*
372   construct a subSchemaSubEntry
373 */
374 static int construct_subschema_subentry(struct ldb_module *module,
375                                         struct ldb_message *msg, enum ldb_scope scope,
376                                         struct ldb_request *parent)
377 {
378         struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
379         char *subSchemaSubEntry;
380
381         /* We may be being called before the init function has finished */
382         if (!data) {
383                 return LDB_SUCCESS;
384         }
385
386         /* Try and set this value up, if possible.  Don't worry if it
387          * fails, we may not have the DB set up yet, and it's not
388          * really vital anyway */
389         if (!data->aggregate_dn) {
390                 struct ldb_context *ldb = ldb_module_get_ctx(module);
391                 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
392         }
393
394         if (data->aggregate_dn) {
395                 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
396                 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
397         }
398         return LDB_SUCCESS;
399 }
400
401
402 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
403                                          struct ldb_message *msg,
404                                          struct ldb_message_element *object_category)
405 {
406         struct ldb_context *ldb;
407         struct ldb_dn *dn;
408         const struct ldb_val *val;
409
410         ldb = ldb_module_get_ctx(module);
411         if (!ldb) {
412                 DEBUG(4, (__location__ ": Failed to get ldb \n"));
413                 return ldb_operr(ldb);
414         }
415
416         dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
417         if (!dn) {
418                 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
419                           (const char *)object_category->values[0].data));
420                 return ldb_operr(ldb);
421         }
422
423         val = ldb_dn_get_rdn_val(dn);
424         if (!val) {
425                 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
426                           ldb_dn_get_linearized(dn)));
427                 return ldb_operr(ldb);
428         }
429
430         if (strequal((const char *)val->data, "NTDS-DSA")) {
431                 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
432         } else {
433                 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
434         }
435         return LDB_SUCCESS;
436 }
437
438 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
439                                                 struct ldb_message *msg,
440                                                 struct ldb_dn *dn,
441                                                 struct ldb_request *parent)
442 {
443         struct ldb_dn *server_dn;
444         const char *attr_obj_cat[] = { "objectCategory", NULL };
445         struct ldb_result *res;
446         struct ldb_message_element *object_category;
447         int ret;
448
449         server_dn = ldb_dn_copy(msg, dn);
450         if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
451                 DEBUG(4, (__location__ ": Failed to add child to %s \n",
452                           ldb_dn_get_linearized(server_dn)));
453                 return ldb_operr(ldb_module_get_ctx(module));
454         }
455
456         ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
457                                     DSDB_FLAG_NEXT_MODULE, parent);
458         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
459                 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
460                                          ldb_dn_get_linearized(server_dn)));
461                 return LDB_SUCCESS;
462         } else if (ret != LDB_SUCCESS) {
463                 return ret;
464         }
465
466         object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
467         if (!object_category) {
468                 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
469                          ldb_dn_get_linearized(res->msgs[0]->dn)));
470                 return LDB_SUCCESS;
471         }
472         return construct_msds_isrodc_with_dn(module, msg, object_category);
473 }
474
475 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
476                                                   struct ldb_message *msg,
477                                                   struct ldb_request *parent)
478 {
479         int ret;
480         struct ldb_dn *server_dn;
481
482         ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
483                                        &server_dn, parent);
484         if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
485                 /* it's OK if we can't find serverReferenceBL attribute */
486                 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
487                          ldb_dn_get_linearized(msg->dn)));
488                 return LDB_SUCCESS;
489         } else if (ret != LDB_SUCCESS) {
490                 return ret;
491         }
492
493         return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
494 }
495
496 /*
497   construct msDS-isRODC attr
498 */
499 static int construct_msds_isrodc(struct ldb_module *module,
500                                  struct ldb_message *msg, enum ldb_scope scope,
501                                  struct ldb_request *parent)
502 {
503         struct ldb_message_element * object_class;
504         struct ldb_message_element * object_category;
505         unsigned int i;
506
507         object_class = ldb_msg_find_element(msg, "objectClass");
508         if (!object_class) {
509                 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
510                          ldb_dn_get_linearized(msg->dn)));
511                 return ldb_operr(ldb_module_get_ctx(module));
512         }
513
514         for (i=0; i<object_class->num_values; i++) {
515                 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
516                         /* If TO!objectCategory  equals the DN of the classSchema  object for the nTDSDSA
517                          * object class, then TO!msDS-isRODC  is false. Otherwise, TO!msDS-isRODC  is true.
518                          */
519                         object_category = ldb_msg_find_element(msg, "objectCategory");
520                         if (!object_category) {
521                                 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
522                                          ldb_dn_get_linearized(msg->dn)));
523                                 return LDB_SUCCESS;
524                         }
525                         return construct_msds_isrodc_with_dn(module, msg, object_category);
526                 }
527                 if (strequal((const char*)object_class->values[i].data, "server")) {
528                         /* Let TN be the nTDSDSA  object whose DN is "CN=NTDS Settings," prepended to
529                          * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA  object" case,
530                          * substituting TN for TO.
531                          */
532                         return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
533                 }
534                 if (strequal((const char*)object_class->values[i].data, "computer")) {
535                         /* Let TS be the server  object named by TO!serverReferenceBL. Apply the previous
536                          * rule for the "TO is a server  object" case, substituting TS for TO.
537                          */
538                         return construct_msds_isrodc_with_computer_dn(module, msg, parent);
539                 }
540         }
541
542         return LDB_SUCCESS;
543 }
544
545
546 /*
547   construct msDS-keyVersionNumber attr
548
549   TODO:  Make this based on the 'win2k' DS huristics bit...
550
551 */
552 static int construct_msds_keyversionnumber(struct ldb_module *module,
553                                            struct ldb_message *msg,
554                                            enum ldb_scope scope,
555                                            struct ldb_request *parent)
556 {
557         uint32_t i;
558         enum ndr_err_code ndr_err;
559         const struct ldb_val *omd_value;
560         struct replPropertyMetaDataBlob *omd;
561         int ret;
562
563         omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
564         if (!omd_value) {
565                 /* We can't make up a key version number without meta data */
566                 return LDB_SUCCESS;
567         }
568         if (!omd_value) {
569                 return LDB_SUCCESS;
570         }
571
572         omd = talloc(msg, struct replPropertyMetaDataBlob);
573         if (!omd) {
574                 ldb_module_oom(module);
575                 return LDB_SUCCESS;
576         }
577
578         ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
579                                        (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
580         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
581                 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
582                          ldb_dn_get_linearized(msg->dn)));
583                 return ldb_operr(ldb_module_get_ctx(module));
584         }
585
586         if (omd->version != 1) {
587                 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
588                          omd->version, ldb_dn_get_linearized(msg->dn)));
589                 talloc_free(omd);
590                 return LDB_SUCCESS;
591         }
592         for (i=0; i<omd->ctr.ctr1.count; i++) {
593                 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
594                         ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
595                                                  msg, msg,
596                                                  "msDS-KeyVersionNumber",
597                                                  omd->ctr.ctr1.array[i].version);
598                         if (ret != LDB_SUCCESS) {
599                                 talloc_free(omd);
600                                 return ret;
601                         }
602                         break;
603                 }
604         }
605         return LDB_SUCCESS;
606
607 }
608
609 struct op_controls_flags {
610         bool sd;
611         bool bypassoperational;
612 };
613
614 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
615         if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
616                 return true;
617         }
618         return false;
619 }
620
621 /*
622   a list of attribute names that should be substituted in the parse
623   tree before the search is done
624 */
625 static const struct {
626         const char *attr;
627         const char *replace;
628 } parse_tree_sub[] = {
629         { "createTimeStamp", "whenCreated" },
630         { "modifyTimeStamp", "whenChanged" }
631 };
632
633
634 /*
635   a list of attribute names that are hidden, but can be searched for
636   using another (non-hidden) name to produce the correct result
637 */
638 static const struct {
639         const char *attr;
640         const char *replace;
641         const char *extra_attr;
642         int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
643 } search_sub[] = {
644         { "createTimeStamp", "whenCreated", NULL , NULL },
645         { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
646         { "structuralObjectClass", "objectClass", NULL , NULL },
647         { "canonicalName", NULL, NULL , construct_canonical_name },
648         { "primaryGroupToken", "objectClass", "objectSid", construct_primary_group_token },
649         { "tokenGroups", "primaryGroupID", "objectSid", construct_token_groups },
650         { "parentGUID", NULL, NULL, construct_parent_guid },
651         { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
652         { "msDS-isRODC", "objectClass", "objectCategory", construct_msds_isrodc },
653         { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber }
654 };
655
656
657 enum op_remove {
658         OPERATIONAL_REMOVE_ALWAYS, /* remove always */
659         OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
660         OPERATIONAL_SD_FLAGS,      /* show if SD_FLAGS_OID set, or asked for */
661         OPERATIONAL_REMOVE_UNLESS_CONTROL        /* remove always unless an adhoc control has been specified */
662 };
663
664 /*
665   a list of attributes that may need to be removed from the
666   underlying db return
667
668   Some of these are attributes that were once stored, but are now calculated
669 */
670 static const struct {
671         const char *attr;
672         enum op_remove op;
673 } operational_remove[] = {
674         { "nTSecurityDescriptor",    OPERATIONAL_SD_FLAGS },
675         { "msDS-KeyVersionNumber",   OPERATIONAL_REMOVE_UNLESS_CONTROL  },
676         { "parentGUID",              OPERATIONAL_REMOVE_ALWAYS  },
677         { "replPropertyMetaData",    OPERATIONAL_REMOVE_UNASKED },
678 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
679         { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
680 };
681
682
683 /*
684   post process a search result record. For any search_sub[] attributes that were
685   asked for, we need to call the appropriate copy routine to copy the result
686   into the message, then remove any attributes that we added to the search but
687   were not asked for by the user
688 */
689 static int operational_search_post_process(struct ldb_module *module,
690                                            struct ldb_message *msg,
691                                            enum ldb_scope scope,
692                                            const char * const *attrs_from_user,
693                                            const char * const *attrs_searched_for,
694                                            struct op_controls_flags* controls_flags,
695                                            struct ldb_request *parent)
696 {
697         struct ldb_context *ldb;
698         unsigned int i, a = 0;
699         bool constructed_attributes = false;
700
701         ldb = ldb_module_get_ctx(module);
702
703         /* removed any attrs that should not be shown to the user */
704         for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
705                 switch (operational_remove[i].op) {
706                 case OPERATIONAL_REMOVE_UNASKED:
707                         if (ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
708                                 continue;
709                         }
710                         if (ldb_attr_in_list(attrs_searched_for, operational_remove[i].attr)) {
711                                 continue;
712                         }
713                 case OPERATIONAL_REMOVE_ALWAYS:
714                         ldb_msg_remove_attr(msg, operational_remove[i].attr);
715                         break;
716                 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
717                         if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
718                                 ldb_msg_remove_attr(msg, operational_remove[i].attr);
719                                 break;
720                         } else {
721                                 continue;
722                         }
723                 case OPERATIONAL_SD_FLAGS:
724                         if (ldb_attr_in_list(attrs_from_user, operational_remove[i].attr)) {
725                                 continue;
726                         }
727                         if (controls_flags->sd) {
728                                 if (attrs_from_user == NULL) {
729                                         continue;
730                                 }
731                                 if (attrs_from_user[0] == NULL) {
732                                         continue;
733                                 }
734                                 if (ldb_attr_in_list(attrs_from_user, "*")) {
735                                         continue;
736                                 }
737                         }
738                         ldb_msg_remove_attr(msg, operational_remove[i].attr);
739                         break;
740                 }
741         }
742
743         for (a=0;attrs_from_user && attrs_from_user[a];a++) {
744                 if (check_keep_control_for_attribute(controls_flags, attrs_from_user[a])) {
745                         continue;
746                 }
747                 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
748                         if (ldb_attr_cmp(attrs_from_user[a], search_sub[i].attr) != 0) {
749                                 continue;
750                         }
751
752                         /* construct the new attribute, using either a supplied
753                            constructor or a simple copy */
754                         constructed_attributes = true;
755                         if (search_sub[i].constructor != NULL) {
756                                 if (search_sub[i].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
757                                         goto failed;
758                                 }
759                         } else if (ldb_msg_copy_attr(msg,
760                                                      search_sub[i].replace,
761                                                      search_sub[i].attr) != LDB_SUCCESS) {
762                                 goto failed;
763                         }
764                 }
765         }
766
767         /* Deletion of the search helper attributes are needed if:
768          * - we generated constructed attributes and
769          * - we aren't requesting all attributes
770          */
771         if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
772                 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
773                         /* remove the added search helper attributes, unless
774                          * they were asked for by the user */
775                         if (search_sub[i].replace != NULL && 
776                             !ldb_attr_in_list(attrs_from_user, search_sub[i].replace)) {
777                                 ldb_msg_remove_attr(msg, search_sub[i].replace);
778                         }
779                         if (search_sub[i].extra_attr != NULL && 
780                             !ldb_attr_in_list(attrs_from_user, search_sub[i].extra_attr)) {
781                                 ldb_msg_remove_attr(msg, search_sub[i].extra_attr);
782                         }
783                 }
784         }
785
786         return 0;
787
788 failed:
789         ldb_debug_set(ldb, LDB_DEBUG_WARNING,
790                       "operational_search_post_process failed for attribute '%s' - %s",
791                       attrs_from_user[a], ldb_errstring(ldb));
792         return -1;
793 }
794
795 /*
796   hook search operations
797 */
798
799 struct operational_context {
800         struct ldb_module *module;
801         struct ldb_request *req;
802         enum ldb_scope scope;
803         const char * const *attrs;
804         struct op_controls_flags* controls_flags;
805 };
806
807 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
808 {
809         struct operational_context *ac;
810         int ret;
811
812         ac = talloc_get_type(req->context, struct operational_context);
813
814         if (!ares) {
815                 return ldb_module_done(ac->req, NULL, NULL,
816                                         LDB_ERR_OPERATIONS_ERROR);
817         }
818         if (ares->error != LDB_SUCCESS) {
819                 return ldb_module_done(ac->req, ares->controls,
820                                         ares->response, ares->error);
821         }
822
823         switch (ares->type) {
824         case LDB_REPLY_ENTRY:
825                 /* for each record returned post-process to add any derived
826                    attributes that have been asked for */
827                 ret = operational_search_post_process(ac->module,
828                                                       ares->message,
829                                                       ac->scope,
830                                                       ac->attrs,
831                                                       req->op.search.attrs,
832                                                       ac->controls_flags, req);
833                 if (ret != 0) {
834                         return ldb_module_done(ac->req, NULL, NULL,
835                                                 LDB_ERR_OPERATIONS_ERROR);
836                 }
837                 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
838
839         case LDB_REPLY_REFERRAL:
840                 return ldb_module_send_referral(ac->req, ares->referral);
841
842         case LDB_REPLY_DONE:
843
844                 return ldb_module_done(ac->req, ares->controls,
845                                         ares->response, LDB_SUCCESS);
846         }
847
848         talloc_free(ares);
849         return LDB_SUCCESS;
850 }
851
852 static int operational_search(struct ldb_module *module, struct ldb_request *req)
853 {
854         struct ldb_context *ldb;
855         struct operational_context *ac;
856         struct ldb_request *down_req;
857         const char **search_attrs = NULL;
858         unsigned int i, a;
859         int ret;
860
861         /* There are no operational attributes on special DNs */
862         if (ldb_dn_is_special(req->op.search.base)) {
863                 return ldb_next_request(module, req);
864         }
865
866         ldb = ldb_module_get_ctx(module);
867
868         ac = talloc(req, struct operational_context);
869         if (ac == NULL) {
870                 return ldb_oom(ldb);
871         }
872
873         ac->module = module;
874         ac->req = req;
875         ac->scope = req->op.search.scope;
876         ac->attrs = req->op.search.attrs;
877
878         /*  FIXME: We must copy the tree and keep the original
879          *  unmodified. SSS */
880         /* replace any attributes in the parse tree that are
881            searchable, but are stored using a different name in the
882            backend */
883         for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
884                 ldb_parse_tree_attr_replace(req->op.search.tree,
885                                             parse_tree_sub[i].attr,
886                                             parse_tree_sub[i].replace);
887         }
888
889         ac->controls_flags = talloc(ac, struct op_controls_flags);
890         /* remember if the SD_FLAGS_OID was set */
891         ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
892         /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
893         ac->controls_flags->bypassoperational =
894                 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
895
896         /* in the list of attributes we are looking for, rename any
897            attributes to the alias for any hidden attributes that can
898            be fetched directly using non-hidden names */
899         for (a=0;ac->attrs && ac->attrs[a];a++) {
900                 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
901                         continue;
902                 }
903                 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
904                         if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) == 0 &&
905                             search_sub[i].replace) {
906
907                                 if (search_sub[i].extra_attr) {
908                                         const char **search_attrs2;
909                                         /* Only adds to the end of the list */
910                                         search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
911                                                                                ? search_attrs
912                                                                                : ac->attrs, 
913                                                                                search_sub[i].extra_attr);
914                                         if (search_attrs2 == NULL) {
915                                                 return ldb_operr(ldb);
916                                         }
917                                         /* may be NULL, talloc_free() doesn't mind */
918                                         talloc_free(search_attrs);
919                                         search_attrs = search_attrs2;
920                                 }
921
922                                 if (!search_attrs) {
923                                         search_attrs = ldb_attr_list_copy(req, ac->attrs);
924                                         if (search_attrs == NULL) {
925                                                 return ldb_operr(ldb);
926                                         }
927                                 }
928                                 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
929                                 search_attrs[a] = search_sub[i].replace;
930                         }
931                 }
932         }
933
934         ret = ldb_build_search_req_ex(&down_req, ldb, ac,
935                                         req->op.search.base,
936                                         req->op.search.scope,
937                                         req->op.search.tree,
938                                         /* use new set of attrs if any */
939                                         search_attrs == NULL?req->op.search.attrs:search_attrs,
940                                         req->controls,
941                                         ac, operational_callback,
942                                         req);
943         LDB_REQ_SET_LOCATION(down_req);
944         if (ret != LDB_SUCCESS) {
945                 return ldb_operr(ldb);
946         }
947
948         /* perform the search */
949         return ldb_next_request(module, down_req);
950 }
951
952 static int operational_init(struct ldb_module *ctx)
953 {
954         struct operational_data *data;
955         int ret;
956
957         ret = ldb_next_init(ctx);
958
959         if (ret != LDB_SUCCESS) {
960                 return ret;
961         }
962
963         data = talloc_zero(ctx, struct operational_data);
964         if (!data) {
965                 return ldb_module_oom(ctx);
966         }
967
968         ldb_module_set_private(ctx, data);
969
970         return LDB_SUCCESS;
971 }
972
973 static const struct ldb_module_ops ldb_operational_module_ops = {
974         .name              = "operational",
975         .search            = operational_search,
976         .init_context      = operational_init
977 };
978
979 int ldb_operational_module_init(const char *version)
980 {
981         LDB_MODULE_CHECK_VERSION(version);
982         return ldb_register_module(&ldb_operational_module_ops);
983 }