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