dsdb: PSO support for msDS-User-Account-Control-Computed
[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          * MS-DRSR 4.1.8.1.3 RevMembGetAccountGroups: Transitive membership in
94          * all account groups in a given domain, excluding built-in groups.
95          * (Used internally for msDS-ResultantPSO support)
96          */
97         ACCOUNT_GROUPS
98 };
99
100 static int get_pso_for_user(struct ldb_module *module,
101                             struct ldb_message *user_msg,
102                             struct ldb_request *parent,
103                             struct ldb_message **pso_msg);
104
105 /*
106   construct a canonical name from a message
107 */
108 static int construct_canonical_name(struct ldb_module *module,
109                                     struct ldb_message *msg, enum ldb_scope scope,
110                                     struct ldb_request *parent)
111 {
112         char *canonicalName;
113         canonicalName = ldb_dn_canonical_string(msg, msg->dn);
114         if (canonicalName == NULL) {
115                 return ldb_operr(ldb_module_get_ctx(module));
116         }
117         return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
118 }
119
120 /*
121   construct a primary group token for groups from a message
122 */
123 static int construct_primary_group_token(struct ldb_module *module,
124                                          struct ldb_message *msg, enum ldb_scope scope,
125                                          struct ldb_request *parent)
126 {
127         struct ldb_context *ldb;
128         uint32_t primary_group_token;
129         
130         ldb = ldb_module_get_ctx(module);
131         if (ldb_match_msg_objectclass(msg, "group") == 1) {
132                 primary_group_token
133                         = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
134                 if (primary_group_token == 0) {
135                         return LDB_SUCCESS;
136                 }
137
138                 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
139                         primary_group_token);
140         } else {
141                 return LDB_SUCCESS;
142         }
143 }
144
145 /*
146  * Returns the group SIDs for the user in the given LDB message
147  */
148 static int get_group_sids(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
149                           struct ldb_message *msg, const char *attribute_string,
150                           enum search_type type, struct dom_sid **groupSIDs,
151                           unsigned int *num_groupSIDs)
152 {
153         const char *filter = NULL;
154         NTSTATUS status;
155         struct dom_sid *primary_group_sid;
156         const char *primary_group_string;
157         const char *primary_group_dn;
158         DATA_BLOB primary_group_blob;
159         struct dom_sid *account_sid;
160         const char *account_sid_string;
161         const char *account_sid_dn;
162         DATA_BLOB account_sid_blob;
163         struct dom_sid *domain_sid;
164
165         /* If it's not a user, it won't have a primaryGroupID */
166         if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
167                 return LDB_SUCCESS;
168         }
169
170         /* Ensure it has an objectSID too */
171         account_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
172         if (account_sid == NULL) {
173                 return LDB_SUCCESS;
174         }
175
176         status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
177         if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
178                 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
179         } else if (!NT_STATUS_IS_OK(status)) {
180                 return LDB_ERR_OPERATIONS_ERROR;
181         }
182
183         primary_group_sid = dom_sid_add_rid(mem_ctx,
184                                             domain_sid,
185                                             ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
186         if (!primary_group_sid) {
187                 return ldb_oom(ldb);
188         }
189
190         /* only return security groups */
191         switch(type) {
192         case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
193                 filter = talloc_asprintf(mem_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)))",
194                                          GROUP_TYPE_SECURITY_ENABLED, GROUP_TYPE_ACCOUNT_GROUP, GROUP_TYPE_UNIVERSAL_GROUP);
195                 break;
196         case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
197         case TOKEN_GROUPS:
198                 filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
199                                          GROUP_TYPE_SECURITY_ENABLED);
200                 break;
201
202         /* for RevMembGetAccountGroups, exclude built-in groups */
203         case ACCOUNT_GROUPS:
204                 filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(!(groupType:1.2.840.113556.1.4.803:=%u))(groupType:1.2.840.113556.1.4.803:=%u))",
205                                 GROUP_TYPE_BUILTIN_LOCAL_GROUP, GROUP_TYPE_SECURITY_ENABLED);
206                 break;
207         }
208
209         if (!filter) {
210                 return ldb_oom(ldb);
211         }
212
213         primary_group_string = dom_sid_string(mem_ctx, primary_group_sid);
214         if (!primary_group_string) {
215                 return ldb_oom(ldb);
216         }
217
218         primary_group_dn = talloc_asprintf(mem_ctx, "<SID=%s>", primary_group_string);
219         if (!primary_group_dn) {
220                 return ldb_oom(ldb);
221         }
222
223         primary_group_blob = data_blob_string_const(primary_group_dn);
224
225         account_sid_string = dom_sid_string(mem_ctx, account_sid);
226         if (!account_sid_string) {
227                 return ldb_oom(ldb);
228         }
229
230         account_sid_dn = talloc_asprintf(mem_ctx, "<SID=%s>", account_sid_string);
231         if (!account_sid_dn) {
232                 return ldb_oom(ldb);
233         }
234
235         account_sid_blob = data_blob_string_const(account_sid_dn);
236
237         status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
238                                            true, /* We don't want to add the object's SID itself,
239                                                     it's not returend in this attribute */
240                                            filter,
241                                            mem_ctx, groupSIDs, num_groupSIDs);
242
243         if (!NT_STATUS_IS_OK(status)) {
244                 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
245                                        attribute_string, account_sid_string,
246                                        nt_errstr(status));
247                 return LDB_ERR_OPERATIONS_ERROR;
248         }
249
250         /* Expands the primary group - this function takes in
251          * memberOf-like values, so we fake one up with the
252          * <SID=S-...> format of DN and then let it expand
253          * them, as long as they meet the filter - so only
254          * domain groups, not builtin groups
255          */
256         status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
257                                            mem_ctx, groupSIDs, num_groupSIDs);
258         if (!NT_STATUS_IS_OK(status)) {
259                 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
260                                        attribute_string, account_sid_string,
261                                        nt_errstr(status));
262                 return LDB_ERR_OPERATIONS_ERROR;
263         }
264
265         return LDB_SUCCESS;
266 }
267
268 /*
269   construct the token groups for SAM objects from a message
270 */
271 static int construct_generic_token_groups(struct ldb_module *module,
272                                           struct ldb_message *msg, enum ldb_scope scope,
273                                           struct ldb_request *parent,
274                                           const char *attribute_string,
275                                           enum search_type type)
276 {
277         struct ldb_context *ldb = ldb_module_get_ctx(module);
278         TALLOC_CTX *tmp_ctx = talloc_new(msg);
279         unsigned int i;
280         int ret;
281         struct dom_sid *groupSIDs = NULL;
282         unsigned int num_groupSIDs = 0;
283
284         if (scope != LDB_SCOPE_BASE) {
285                 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
286                 return LDB_ERR_OPERATIONS_ERROR;
287         }
288
289         /* calculate the group SIDs for this object */
290         ret = get_group_sids(ldb, tmp_ctx, msg, attribute_string, type,
291                              &groupSIDs, &num_groupSIDs);
292
293         if (ret != LDB_SUCCESS) {
294                 talloc_free(tmp_ctx);
295                 return LDB_ERR_OPERATIONS_ERROR;
296         }
297
298         /* add these SIDs to the search result */
299         for (i=0; i < num_groupSIDs; i++) {
300                 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i]);
301                 if (ret) {
302                         talloc_free(tmp_ctx);
303                         return ret;
304                 }
305         }
306
307         return LDB_SUCCESS;
308 }
309
310 static int construct_token_groups(struct ldb_module *module,
311                                   struct ldb_message *msg, enum ldb_scope scope,
312                                   struct ldb_request *parent)
313 {
314         /**
315          * TODO: Add in a limiting domain when we start to support
316          * trusted domains.
317          */
318         return construct_generic_token_groups(module, msg, scope, parent,
319                                               "tokenGroups",
320                                               TOKEN_GROUPS);
321 }
322
323 static int construct_token_groups_no_gc(struct ldb_module *module,
324                                         struct ldb_message *msg, enum ldb_scope scope,
325                                         struct ldb_request *parent)
326 {
327         /**
328          * TODO: Add in a limiting domain when we start to support
329          * trusted domains.
330          */
331         return construct_generic_token_groups(module, msg, scope, parent,
332                                               "tokenGroupsNoGCAcceptable",
333                                               TOKEN_GROUPS);
334 }
335
336 static int construct_global_universal_token_groups(struct ldb_module *module,
337                                                    struct ldb_message *msg, enum ldb_scope scope,
338                                                    struct ldb_request *parent)
339 {
340         return construct_generic_token_groups(module, msg, scope, parent,
341                                               "tokenGroupsGlobalAndUniversal",
342                                               TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
343 }
344 /*
345   construct the parent GUID for an entry from a message
346 */
347 static int construct_parent_guid(struct ldb_module *module,
348                                  struct ldb_message *msg, enum ldb_scope scope,
349                                  struct ldb_request *parent)
350 {
351         struct ldb_result *res, *parent_res;
352         const struct ldb_val *parent_guid;
353         const char *attrs[] = { "instanceType", NULL };
354         const char *attrs2[] = { "objectGUID", NULL };
355         uint32_t instanceType;
356         int ret;
357         struct ldb_dn *parent_dn;
358         struct ldb_val v;
359
360         /* determine if the object is NC by instance type */
361         ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
362                                     DSDB_FLAG_NEXT_MODULE |
363                                     DSDB_SEARCH_SHOW_RECYCLED, parent);
364         if (ret != LDB_SUCCESS) {
365                 return ret;
366         }
367
368         instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
369                                                  "instanceType", 0);
370         talloc_free(res);
371         if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
372                 DEBUG(4,(__location__ ": Object %s is NC\n",
373                          ldb_dn_get_linearized(msg->dn)));
374                 return LDB_SUCCESS;
375         }
376         parent_dn = ldb_dn_get_parent(msg, msg->dn);
377
378         if (parent_dn == NULL) {
379                 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
380                                          ldb_dn_get_linearized(msg->dn)));
381                 return LDB_ERR_OTHER;
382         }
383         ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
384                                     DSDB_FLAG_NEXT_MODULE |
385                                     DSDB_SEARCH_SHOW_RECYCLED, parent);
386         /* not NC, so the object should have a parent*/
387         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
388                 ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR, 
389                                  talloc_asprintf(msg, "Parent dn %s for %s does not exist",
390                                                  ldb_dn_get_linearized(parent_dn),
391                                                  ldb_dn_get_linearized(msg->dn)));
392                 talloc_free(parent_dn);
393                 return ret;
394         } else if (ret != LDB_SUCCESS) {
395                 talloc_free(parent_dn);
396                 return ret;
397         }
398         talloc_free(parent_dn);
399
400         parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
401         if (!parent_guid) {
402                 talloc_free(parent_res);
403                 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
404         }
405
406         v = data_blob_dup_talloc(parent_res, *parent_guid);
407         if (!v.data) {
408                 talloc_free(parent_res);
409                 return ldb_oom(ldb_module_get_ctx(module));
410         }
411         ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
412         talloc_free(parent_res);
413         return ret;
414 }
415
416 static int construct_modifyTimeStamp(struct ldb_module *module,
417                                         struct ldb_message *msg, enum ldb_scope scope,
418                                         struct ldb_request *parent)
419 {
420         struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
421         struct ldb_context *ldb = ldb_module_get_ctx(module);
422
423         /* We may be being called before the init function has finished */
424         if (!data) {
425                 return LDB_SUCCESS;
426         }
427
428         /* Try and set this value up, if possible.  Don't worry if it
429          * fails, we may not have the DB set up yet.
430          */
431         if (!data->aggregate_dn) {
432                 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
433         }
434
435         if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
436                 /*
437                  * If we have the DN for the object with common name = Aggregate and
438                  * the request is for this DN then let's do the following:
439                  * 1) search the object which changedUSN correspond to the one of the loaded
440                  * schema.
441                  * 2) Get the whenChanged attribute
442                  * 3) Generate the modifyTimestamp out of the whenChanged attribute
443                  */
444                 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
445                 char *value = ldb_timestring(msg, schema->ts_last_change);
446
447                 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
448         }
449         return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
450 }
451
452 /*
453   construct a subSchemaSubEntry
454 */
455 static int construct_subschema_subentry(struct ldb_module *module,
456                                         struct ldb_message *msg, enum ldb_scope scope,
457                                         struct ldb_request *parent)
458 {
459         struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
460         char *subSchemaSubEntry;
461
462         /* We may be being called before the init function has finished */
463         if (!data) {
464                 return LDB_SUCCESS;
465         }
466
467         /* Try and set this value up, if possible.  Don't worry if it
468          * fails, we may not have the DB set up yet, and it's not
469          * really vital anyway */
470         if (!data->aggregate_dn) {
471                 struct ldb_context *ldb = ldb_module_get_ctx(module);
472                 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
473         }
474
475         if (data->aggregate_dn) {
476                 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
477                 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
478         }
479         return LDB_SUCCESS;
480 }
481
482
483 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
484                                          struct ldb_message *msg,
485                                          struct ldb_message_element *object_category)
486 {
487         struct ldb_context *ldb;
488         struct ldb_dn *dn;
489         const struct ldb_val *val;
490
491         ldb = ldb_module_get_ctx(module);
492         if (!ldb) {
493                 DEBUG(4, (__location__ ": Failed to get ldb \n"));
494                 return LDB_ERR_OPERATIONS_ERROR;
495         }
496
497         dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
498         if (!dn) {
499                 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
500                           (const char *)object_category->values[0].data));
501                 return ldb_operr(ldb);
502         }
503
504         val = ldb_dn_get_rdn_val(dn);
505         if (!val) {
506                 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
507                           ldb_dn_get_linearized(dn)));
508                 return ldb_operr(ldb);
509         }
510
511         if (strequal((const char *)val->data, "NTDS-DSA")) {
512                 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
513         } else {
514                 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
515         }
516         return LDB_SUCCESS;
517 }
518
519 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
520                                                 struct ldb_message *msg,
521                                                 struct ldb_dn *dn,
522                                                 struct ldb_request *parent)
523 {
524         struct ldb_dn *server_dn;
525         const char *attr_obj_cat[] = { "objectCategory", NULL };
526         struct ldb_result *res;
527         struct ldb_message_element *object_category;
528         int ret;
529
530         server_dn = ldb_dn_copy(msg, dn);
531         if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
532                 DEBUG(4, (__location__ ": Failed to add child to %s \n",
533                           ldb_dn_get_linearized(server_dn)));
534                 return ldb_operr(ldb_module_get_ctx(module));
535         }
536
537         ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
538                                     DSDB_FLAG_NEXT_MODULE, parent);
539         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
540                 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
541                                          ldb_dn_get_linearized(server_dn)));
542                 return LDB_SUCCESS;
543         } else if (ret != LDB_SUCCESS) {
544                 return ret;
545         }
546
547         object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
548         if (!object_category) {
549                 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
550                          ldb_dn_get_linearized(res->msgs[0]->dn)));
551                 return LDB_SUCCESS;
552         }
553         return construct_msds_isrodc_with_dn(module, msg, object_category);
554 }
555
556 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
557                                                   struct ldb_message *msg,
558                                                   struct ldb_request *parent)
559 {
560         int ret;
561         struct ldb_dn *server_dn;
562
563         ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
564                                        &server_dn, parent);
565         if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
566                 /* it's OK if we can't find serverReferenceBL attribute */
567                 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
568                          ldb_dn_get_linearized(msg->dn)));
569                 return LDB_SUCCESS;
570         } else if (ret != LDB_SUCCESS) {
571                 return ret;
572         }
573
574         return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
575 }
576
577 /*
578   construct msDS-isRODC attr
579 */
580 static int construct_msds_isrodc(struct ldb_module *module,
581                                  struct ldb_message *msg, enum ldb_scope scope,
582                                  struct ldb_request *parent)
583 {
584         struct ldb_message_element * object_class;
585         struct ldb_message_element * object_category;
586         unsigned int i;
587
588         object_class = ldb_msg_find_element(msg, "objectClass");
589         if (!object_class) {
590                 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
591                          ldb_dn_get_linearized(msg->dn)));
592                 return ldb_operr(ldb_module_get_ctx(module));
593         }
594
595         for (i=0; i<object_class->num_values; i++) {
596                 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
597                         /* If TO!objectCategory  equals the DN of the classSchema  object for the nTDSDSA
598                          * object class, then TO!msDS-isRODC  is false. Otherwise, TO!msDS-isRODC  is true.
599                          */
600                         object_category = ldb_msg_find_element(msg, "objectCategory");
601                         if (!object_category) {
602                                 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
603                                          ldb_dn_get_linearized(msg->dn)));
604                                 return LDB_SUCCESS;
605                         }
606                         return construct_msds_isrodc_with_dn(module, msg, object_category);
607                 }
608                 if (strequal((const char*)object_class->values[i].data, "server")) {
609                         /* Let TN be the nTDSDSA  object whose DN is "CN=NTDS Settings," prepended to
610                          * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA  object" case,
611                          * substituting TN for TO.
612                          */
613                         return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
614                 }
615                 if (strequal((const char*)object_class->values[i].data, "computer")) {
616                         /* Let TS be the server  object named by TO!serverReferenceBL. Apply the previous
617                          * rule for the "TO is a server  object" case, substituting TS for TO.
618                          */
619                         return construct_msds_isrodc_with_computer_dn(module, msg, parent);
620                 }
621         }
622
623         return LDB_SUCCESS;
624 }
625
626
627 /*
628   construct msDS-keyVersionNumber attr
629
630   TODO:  Make this based on the 'win2k' DS huristics bit...
631
632 */
633 static int construct_msds_keyversionnumber(struct ldb_module *module,
634                                            struct ldb_message *msg,
635                                            enum ldb_scope scope,
636                                            struct ldb_request *parent)
637 {
638         uint32_t i;
639         enum ndr_err_code ndr_err;
640         const struct ldb_val *omd_value;
641         struct replPropertyMetaDataBlob *omd;
642         int ret;
643
644         omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
645         if (!omd_value) {
646                 /* We can't make up a key version number without meta data */
647                 return LDB_SUCCESS;
648         }
649
650         omd = talloc(msg, struct replPropertyMetaDataBlob);
651         if (!omd) {
652                 ldb_module_oom(module);
653                 return LDB_SUCCESS;
654         }
655
656         ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
657                                        (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
658         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
659                 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
660                          ldb_dn_get_linearized(msg->dn)));
661                 return ldb_operr(ldb_module_get_ctx(module));
662         }
663
664         if (omd->version != 1) {
665                 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
666                          omd->version, ldb_dn_get_linearized(msg->dn)));
667                 talloc_free(omd);
668                 return LDB_SUCCESS;
669         }
670         for (i=0; i<omd->ctr.ctr1.count; i++) {
671                 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
672                         ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
673                                                  msg, msg,
674                                                  "msDS-KeyVersionNumber",
675                                                  omd->ctr.ctr1.array[i].version);
676                         if (ret != LDB_SUCCESS) {
677                                 talloc_free(omd);
678                                 return ret;
679                         }
680                         break;
681                 }
682         }
683         return LDB_SUCCESS;
684
685 }
686
687 #define _UF_TRUST_ACCOUNTS ( \
688         UF_WORKSTATION_TRUST_ACCOUNT | \
689         UF_SERVER_TRUST_ACCOUNT | \
690         UF_INTERDOMAIN_TRUST_ACCOUNT \
691 )
692 #define _UF_NO_EXPIRY_ACCOUNTS ( \
693         UF_SMARTCARD_REQUIRED | \
694         UF_DONT_EXPIRE_PASSWD | \
695         _UF_TRUST_ACCOUNTS \
696 )
697
698 /*
699   calculate msDS-UserPasswordExpiryTimeComputed
700 */
701 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
702                                                 struct ldb_message *msg,
703                                                 struct ldb_dn *domain_dn)
704 {
705         int64_t pwdLastSet, maxPwdAge;
706         uint32_t userAccountControl;
707         NTTIME ret;
708
709         userAccountControl = ldb_msg_find_attr_as_uint(msg,
710                                         "userAccountControl",
711                                         0);
712         if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
713                 return 0x7FFFFFFFFFFFFFFFULL;
714         }
715
716         pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
717         if (pwdLastSet == 0) {
718                 return 0;
719         }
720
721         if (pwdLastSet <= -1) {
722                 /*
723                  * This can't really happen...
724                  */
725                 return 0x7FFFFFFFFFFFFFFFULL;
726         }
727
728         if (pwdLastSet >= 0x7FFFFFFFFFFFFFFFLL) {
729                 /*
730                  * Somethings wrong with the clock...
731                  */
732                 return 0x7FFFFFFFFFFFFFFFULL;
733         }
734
735         /*
736          * Note that maxPwdAge is a stored as negative value.
737          *
738          * Possible values are in the range of:
739          *
740          * maxPwdAge: -864000000001
741          * to
742          * maxPwdAge: -9223372036854775808 (-0x8000000000000000ULL)
743          *
744          */
745         maxPwdAge = samdb_search_int64(ldb_module_get_ctx(module), msg, 0,
746                                        domain_dn, "maxPwdAge", NULL);
747         if (maxPwdAge >= -864000000000) {
748                 /*
749                  * This is not really possible...
750                  */
751                 return 0x7FFFFFFFFFFFFFFFULL;
752         }
753
754         if (maxPwdAge == -0x8000000000000000LL) {
755                 return 0x7FFFFFFFFFFFFFFFULL;
756         }
757
758         /*
759          * Note we already catched maxPwdAge == -0x8000000000000000ULL
760          * and pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL above.
761          *
762          * Remember maxPwdAge is a negative number,
763          * so it results in the following.
764          *
765          * 0x7FFFFFFFFFFFFFFEULL + 0x7FFFFFFFFFFFFFFFULL
766          * =
767          * 0xFFFFFFFFFFFFFFFFULL
768          */
769         ret = (NTTIME)pwdLastSet - (NTTIME)maxPwdAge;
770         if (ret >= 0x7FFFFFFFFFFFFFFFULL) {
771                 return 0x7FFFFFFFFFFFFFFFULL;
772         }
773
774         return ret;
775 }
776
777 /*
778  * Returns the Effective-LockoutDuration for a user
779  */
780 static int64_t get_user_lockout_duration(struct ldb_module *module,
781                                          struct ldb_message *user_msg,
782                                          struct ldb_request *parent,
783                                          struct ldb_dn *nc_root)
784 {
785         int ret;
786         struct ldb_message *pso = NULL;
787         struct ldb_context *ldb = ldb_module_get_ctx(module);
788
789         /* if a PSO applies to the user, use its lockoutDuration */
790         ret = get_pso_for_user(module, user_msg, parent, &pso);
791         if (ret != LDB_SUCCESS) {
792
793                 /* log the error, but fallback to the domain default */
794                 DBG_ERR("Error retrieving PSO for %s\n",
795                         ldb_dn_get_linearized(user_msg->dn));
796         }
797
798         if (pso != NULL) {
799                 return ldb_msg_find_attr_as_int64(pso,
800                                                   "msDS-LockoutDuration", 0);
801         }
802
803         /* otherwise return the default domain value */
804         return samdb_search_int64(ldb, user_msg, 0, nc_root, "lockoutDuration",
805                                   NULL);
806 }
807
808 /*
809   construct msDS-User-Account-Control-Computed attr
810 */
811 static int construct_msds_user_account_control_computed(struct ldb_module *module,
812                                                         struct ldb_message *msg, enum ldb_scope scope,
813                                                         struct ldb_request *parent)
814 {
815         uint32_t userAccountControl;
816         uint32_t msDS_User_Account_Control_Computed = 0;
817         struct ldb_context *ldb = ldb_module_get_ctx(module);
818         NTTIME now;
819         struct ldb_dn *nc_root;
820         int ret;
821
822         ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
823         if (ret != 0) {
824                 ldb_asprintf_errstring(ldb,
825                                        "Failed to find NC root of DN: %s: %s",
826                                        ldb_dn_get_linearized(msg->dn),
827                                        ldb_errstring(ldb_module_get_ctx(module)));
828                 return ret;
829         }
830         if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
831                 /* Only calculate this on our default NC */
832                 return 0;
833         }
834         /* Test account expire time */
835         unix_to_nt_time(&now, time(NULL));
836
837         userAccountControl = ldb_msg_find_attr_as_uint(msg,
838                                                        "userAccountControl",
839                                                        0);
840         if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
841
842                 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
843                 if (lockoutTime != 0) {
844                         int64_t lockoutDuration;
845
846                         lockoutDuration = get_user_lockout_duration(module, msg,
847                                                                     parent,
848                                                                     nc_root);
849
850                         /* zero locks out until the administrator intervenes */
851                         if (lockoutDuration >= 0) {
852                                 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
853                         } else if (lockoutTime - lockoutDuration >= now) {
854                                 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
855                         }
856                 }
857         }
858
859         if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
860                 NTTIME must_change_time
861                         = get_msds_user_password_expiry_time_computed(module,
862                                                                       msg, nc_root);
863                 /* check for expired password */
864                 if (must_change_time < now) {
865                         msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
866                 }
867         }
868
869         return samdb_msg_add_int64(ldb,
870                                    msg->elements, msg,
871                                    "msDS-User-Account-Control-Computed",
872                                    msDS_User_Account_Control_Computed);
873 }
874
875 /*
876   construct msDS-UserPasswordExpiryTimeComputed
877 */
878 static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
879                                                              struct ldb_message *msg, enum ldb_scope scope,
880                                                              struct ldb_request *parent)
881 {
882         struct ldb_context *ldb = ldb_module_get_ctx(module);
883         struct ldb_dn *nc_root;
884         int64_t password_expiry_time;
885         int ret;
886
887         ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
888         if (ret != 0) {
889                 ldb_asprintf_errstring(ldb,
890                                        "Failed to find NC root of DN: %s: %s",
891                                        ldb_dn_get_linearized(msg->dn),
892                                        ldb_errstring(ldb));
893                 return ret;
894         }
895
896         if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
897                 /* Only calculate this on our default NC */
898                 return 0;
899         }
900
901         password_expiry_time
902                 = get_msds_user_password_expiry_time_computed(module, msg,
903                                                               nc_root);
904
905         return samdb_msg_add_int64(ldb,
906                                    msg->elements, msg,
907                                    "msDS-UserPasswordExpiryTimeComputed",
908                                    password_expiry_time);
909 }
910
911 /*
912  * Checks whether the msDS-ResultantPSO attribute is supported for a given
913  * user object. As per MS-ADTS, section 3.1.1.4.5.36 msDS-ResultantPSO.
914  */
915 static bool pso_is_supported(struct ldb_context *ldb, struct ldb_message *msg)
916 {
917         int functional_level;
918         uint32_t uac;
919         uint32_t user_rid;
920
921         functional_level = dsdb_functional_level(ldb);
922         if (functional_level < DS_DOMAIN_FUNCTION_2008) {
923                 return false;
924         }
925
926         /* msDS-ResultantPSO is only supported for user objects */
927         if (!ldb_match_msg_objectclass(msg, "user")) {
928                 return false;
929         }
930
931         /* ...and only if the ADS_UF_NORMAL_ACCOUNT bit is set */
932         uac = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
933         if (!(uac & UF_NORMAL_ACCOUNT)) {
934                 return false;
935         }
936
937         /* skip it if it's the special KRBTGT default account */
938         user_rid = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
939         if (user_rid == DOMAIN_RID_KRBTGT) {
940                 return false;
941         }
942
943         /* ...or if it's a special KRBTGT account for an RODC KDC */
944         if (ldb_msg_find_ldb_val(msg, "msDS-SecondaryKrbTgtNumber") != NULL) {
945                 return false;
946         }
947
948         return true;
949 }
950
951 /*
952  * Compares two PSO objects returned by a search, to work out the better PSO.
953  * The PSO with the lowest precedence is better, otherwise (if the precedence
954  * is equal) the PSO with the lower GUID wins.
955  */
956 static int pso_compare(struct ldb_message **m1, struct ldb_message **m2,
957                        TALLOC_CTX *mem_ctx)
958 {
959         uint32_t prec1;
960         uint32_t prec2;
961
962         prec1 = ldb_msg_find_attr_as_uint(*m1, "msDS-PasswordSettingsPrecedence",
963                                           0xffffffff);
964         prec2 = ldb_msg_find_attr_as_uint(*m2, "msDS-PasswordSettingsPrecedence",
965                                           0xffffffff);
966
967         /* if precedence is equal, use the lowest GUID */
968         if (prec1 == prec2) {
969                 struct GUID guid1 = samdb_result_guid(*m1, "objectGUID");
970                 struct GUID guid2 = samdb_result_guid(*m2, "objectGUID");
971
972                 return ndr_guid_compare(&guid1, &guid2);
973         } else {
974                 return prec1 - prec2;
975         }
976 }
977
978 /*
979  * Search for PSO objects that apply to the object SIDs specified
980  */
981 static int pso_search_by_sids(struct ldb_module *module, TALLOC_CTX *mem_ctx,
982                               struct ldb_request *parent,
983                               struct dom_sid *sid_array, unsigned int num_sids,
984                               struct ldb_result **result)
985 {
986         int ret;
987         int i;
988         struct ldb_context *ldb = ldb_module_get_ctx(module);
989         char *sid_filter = NULL;
990         struct ldb_dn *domain_dn = NULL;
991         struct ldb_dn *psc_dn = NULL;
992         const char *attrs[] = {
993                 "msDS-PasswordSettingsPrecedence",
994                 "objectGUID",
995                 "msDS-LockoutDuration",
996                 NULL
997         };
998
999         /* build a query for PSO objects that apply to any of the SIDs given */
1000         sid_filter = talloc_strdup(mem_ctx, "");
1001
1002         for (i = 0; sid_filter && i < num_sids; i++) {
1003                 char sid_buf[DOM_SID_STR_BUFLEN] = {0,};
1004
1005                 dom_sid_string_buf(&sid_array[i], sid_buf, sizeof(sid_buf));
1006
1007                 sid_filter = talloc_asprintf_append(sid_filter,
1008                                                     "(msDS-PSOAppliesTo=<SID=%s>)",
1009                                                     sid_buf);
1010         }
1011
1012         if (sid_filter == NULL) {
1013                 return ldb_oom(ldb);
1014         }
1015
1016         /* only PSOs located in the Password Settings Container are valid */
1017         domain_dn = ldb_get_default_basedn(ldb);
1018         psc_dn = ldb_dn_new_fmt(mem_ctx, ldb,
1019                                 "CN=Password Settings Container,CN=System,%s",
1020                                 ldb_dn_get_linearized(domain_dn));
1021         if (psc_dn == NULL) {
1022                 return ldb_oom(ldb);
1023         }
1024
1025         ret = dsdb_module_search(module, mem_ctx, result, psc_dn,
1026                                  LDB_SCOPE_ONELEVEL, attrs,
1027                                  DSDB_FLAG_NEXT_MODULE, parent,
1028                                  "(&(objectClass=msDS-PasswordSettings)(|%s))",
1029                                  sid_filter);
1030         talloc_free(sid_filter);
1031         return ret;
1032 }
1033
1034 /*
1035  * Returns the best PSO object that applies to the object SID(s) specified
1036  */
1037 static int pso_find_best(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1038                          struct ldb_request *parent, struct dom_sid *sid_array,
1039                          unsigned int num_sids, struct ldb_message **best_pso)
1040 {
1041         struct ldb_result *res = NULL;
1042         int ret;
1043
1044         *best_pso = NULL;
1045
1046         /* find any PSOs that apply to the SIDs specified */
1047         ret = pso_search_by_sids(module, mem_ctx, parent, sid_array, num_sids,
1048                                  &res);
1049         if (ret != LDB_SUCCESS) {
1050                 DBG_ERR("Error %d retrieving PSO for SID(s)\n", ret);
1051                 return ret;
1052         }
1053
1054         /* sort the list so that the best PSO is first */
1055         LDB_TYPESAFE_QSORT(res->msgs, res->count, mem_ctx, pso_compare);
1056
1057         if (res->count > 0) {
1058                 *best_pso = res->msgs[0];
1059         }
1060
1061         return LDB_SUCCESS;
1062 }
1063
1064 /*
1065  * Determines the Password Settings Object (PSO) that applies to the given user
1066  */
1067 static int get_pso_for_user(struct ldb_module *module,
1068                             struct ldb_message *user_msg,
1069                             struct ldb_request *parent,
1070                             struct ldb_message **pso_msg)
1071 {
1072         bool pso_supported;
1073         struct dom_sid *groupSIDs = NULL;
1074         unsigned int num_groupSIDs = 0;
1075         struct ldb_context *ldb = ldb_module_get_ctx(module);
1076         struct ldb_message *best_pso = NULL;
1077         int ret;
1078         struct ldb_message_element *el = NULL;
1079         TALLOC_CTX *tmp_ctx = NULL;
1080
1081         *pso_msg = NULL;
1082
1083         /* first, check msDS-ResultantPSO is supported for this object */
1084         pso_supported = pso_is_supported(ldb, user_msg);
1085
1086         if (!pso_supported) {
1087                 return LDB_SUCCESS;
1088         }
1089
1090         tmp_ctx = talloc_new(user_msg);
1091
1092         /*
1093          * if any PSOs apply directly to the user, they are considered first
1094          * before we check group membership PSOs
1095          */
1096         el = ldb_msg_find_element(user_msg, "msDS-PSOApplied");
1097
1098         if (el != NULL && el->num_values > 0) {
1099                 struct dom_sid *user_sid = NULL;
1100
1101                 /* lookup the best PSO object, based on the user's SID */
1102                 user_sid = samdb_result_dom_sid(tmp_ctx, user_msg, "objectSid");
1103
1104                 ret = pso_find_best(module, tmp_ctx, parent, user_sid, 1,
1105                                     &best_pso);
1106                 if (ret != LDB_SUCCESS) {
1107                         talloc_free(tmp_ctx);
1108                         return ret;
1109                 }
1110
1111                 if (best_pso != NULL) {
1112                         *pso_msg = best_pso;
1113                         return LDB_SUCCESS;
1114                 }
1115         }
1116
1117         /*
1118          * If no valid PSO applies directly to the user, then try its groups.
1119          * Work out the SIDs of any account groups the user is a member of
1120          */
1121         ret = get_group_sids(ldb, tmp_ctx, user_msg,
1122                              "msDS-ResultantPSO", ACCOUNT_GROUPS,
1123                              &groupSIDs, &num_groupSIDs);
1124         if (ret != LDB_SUCCESS) {
1125                 DBG_ERR("Error %d determining group SIDs for %s\n", ret,
1126                         ldb_dn_get_linearized(user_msg->dn));
1127                 talloc_free(tmp_ctx);
1128                 return ret;
1129         }
1130
1131         /* lookup the best PSO that applies to any of these groups */
1132         ret = pso_find_best(module, tmp_ctx, parent, groupSIDs,
1133                             num_groupSIDs, &best_pso);
1134         if (ret != LDB_SUCCESS) {
1135                 talloc_free(tmp_ctx);
1136                 return ret;
1137         }
1138
1139         *pso_msg = best_pso;
1140         return LDB_SUCCESS;
1141 }
1142
1143 /*
1144  * Constructs the msDS-ResultantPSO attribute, which is the DN of the Password
1145  * Settings Object (PSO) that applies to that user.
1146  */
1147 static int construct_resultant_pso(struct ldb_module *module,
1148                                    struct ldb_message *msg,
1149                                    enum ldb_scope scope,
1150                                    struct ldb_request *parent)
1151 {
1152         struct ldb_message *pso = NULL;
1153         int ret;
1154
1155         /* work out the PSO (if any) that applies to this user */
1156         ret = get_pso_for_user(module, msg, parent, &pso);
1157         if (ret != LDB_SUCCESS) {
1158                 DBG_ERR("Couldn't determine PSO for %s\n",
1159                         ldb_dn_get_linearized(msg->dn));
1160                 return ret;
1161         }
1162
1163         if (pso != NULL) {
1164                 DBG_INFO("%s is resultant PSO for user %s\n",
1165                          ldb_dn_get_linearized(pso->dn),
1166                          ldb_dn_get_linearized(msg->dn));
1167                 return ldb_msg_add_string(msg, "msDS-ResultantPSO",
1168                                           ldb_dn_get_linearized(pso->dn));
1169         }
1170
1171         /* no PSO applies to this user */
1172         return LDB_SUCCESS;
1173 }
1174
1175 struct op_controls_flags {
1176         bool sd;
1177         bool bypassoperational;
1178 };
1179
1180 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
1181         if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
1182                 return true;
1183         }
1184         return false;
1185 }
1186
1187 /*
1188   a list of attribute names that should be substituted in the parse
1189   tree before the search is done
1190 */
1191 static const struct {
1192         const char *attr;
1193         const char *replace;
1194 } parse_tree_sub[] = {
1195         { "createTimeStamp", "whenCreated" },
1196         { "modifyTimeStamp", "whenChanged" }
1197 };
1198
1199
1200 struct op_attributes_replace {
1201         const char *attr;
1202         const char *replace;
1203         const char * const *extra_attrs;
1204         int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
1205 };
1206
1207 /* the 'extra_attrs' required for msDS-ResultantPSO */
1208 #define RESULTANT_PSO_COMPUTED_ATTRS \
1209         "msDS-PSOApplied", \
1210         "userAccountControl", \
1211         "objectSid", \
1212         "msDS-SecondaryKrbTgtNumber", \
1213         "primaryGroupID"
1214
1215 /*
1216  * any other constructed attributes that want to work out the PSO also need to
1217  * include objectClass (this gets included via 'replace' for msDS-ResultantPSO)
1218  */
1219 #define PSO_ATTR_DEPENDENCIES \
1220         RESULTANT_PSO_COMPUTED_ATTRS, \
1221         "objectClass"
1222
1223 static const char *objectSid_attr[] =
1224 {
1225         "objectSid",
1226         NULL
1227 };
1228
1229
1230 static const char *objectCategory_attr[] =
1231 {
1232         "objectCategory",
1233         NULL
1234 };
1235
1236
1237 static const char *user_account_control_computed_attrs[] =
1238 {
1239         "lockoutTime",
1240         "pwdLastSet",
1241         PSO_ATTR_DEPENDENCIES,
1242         NULL
1243 };
1244
1245
1246 static const char *user_password_expiry_time_computed_attrs[] =
1247 {
1248         "pwdLastSet",
1249         NULL
1250 };
1251
1252 static const char *resultant_pso_computed_attrs[] =
1253 {
1254         RESULTANT_PSO_COMPUTED_ATTRS,
1255         NULL
1256 };
1257
1258 /*
1259   a list of attribute names that are hidden, but can be searched for
1260   using another (non-hidden) name to produce the correct result
1261 */
1262 static const struct op_attributes_replace search_sub[] = {
1263         { "createTimeStamp", "whenCreated", NULL , NULL },
1264         { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
1265         { "structuralObjectClass", "objectClass", NULL , NULL },
1266         { "canonicalName", NULL, NULL , construct_canonical_name },
1267         { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
1268         { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
1269         { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
1270         { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
1271         { "parentGUID", NULL, NULL, construct_parent_guid },
1272         { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
1273         { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
1274         { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
1275         { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
1276           construct_msds_user_account_control_computed },
1277         { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
1278           construct_msds_user_password_expiry_time_computed },
1279         { "msDS-ResultantPSO", "objectClass", resultant_pso_computed_attrs,
1280           construct_resultant_pso }
1281 };
1282
1283
1284 enum op_remove {
1285         OPERATIONAL_REMOVE_ALWAYS, /* remove always */
1286         OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
1287         OPERATIONAL_SD_FLAGS,      /* show if SD_FLAGS_OID set, or asked for */
1288         OPERATIONAL_REMOVE_UNLESS_CONTROL        /* remove always unless an adhoc control has been specified */
1289 };
1290
1291 /*
1292   a list of attributes that may need to be removed from the
1293   underlying db return
1294
1295   Some of these are attributes that were once stored, but are now calculated
1296 */
1297 struct op_attributes_operations {
1298         const char *attr;
1299         enum op_remove op;
1300 };
1301
1302 static const struct op_attributes_operations operational_remove[] = {
1303         { "nTSecurityDescriptor",    OPERATIONAL_SD_FLAGS },
1304         { "msDS-KeyVersionNumber",   OPERATIONAL_REMOVE_UNLESS_CONTROL  },
1305         { "parentGUID",              OPERATIONAL_REMOVE_ALWAYS  },
1306         { "replPropertyMetaData",    OPERATIONAL_REMOVE_UNASKED },
1307 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
1308         { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
1309 };
1310
1311
1312 /*
1313   post process a search result record. For any search_sub[] attributes that were
1314   asked for, we need to call the appropriate copy routine to copy the result
1315   into the message, then remove any attributes that we added to the search but
1316   were not asked for by the user
1317 */
1318 static int operational_search_post_process(struct ldb_module *module,
1319                                            struct ldb_message *msg,
1320                                            enum ldb_scope scope,
1321                                            const char * const *attrs_from_user,
1322                                            const char * const *attrs_searched_for,
1323                                            struct op_controls_flags* controls_flags,
1324                                            struct op_attributes_operations *list,
1325                                            unsigned int list_size,
1326                                            struct op_attributes_replace *list_replace,
1327                                            unsigned int list_replace_size,
1328                                            struct ldb_request *parent)
1329 {
1330         struct ldb_context *ldb;
1331         unsigned int i, a = 0;
1332         bool constructed_attributes = false;
1333
1334         ldb = ldb_module_get_ctx(module);
1335
1336         /* removed any attrs that should not be shown to the user */
1337         for (i=0; i < list_size; i++) {
1338                 ldb_msg_remove_attr(msg, list[i].attr);
1339         }
1340
1341         for (a=0; a < list_replace_size; a++) {
1342                 if (check_keep_control_for_attribute(controls_flags,
1343                                                      list_replace[a].attr)) {
1344                         continue;
1345                 }
1346
1347                 /* construct the new attribute, using either a supplied
1348                         constructor or a simple copy */
1349                 constructed_attributes = true;
1350                 if (list_replace[a].constructor != NULL) {
1351                         if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
1352                                 goto failed;
1353                         }
1354                 } else if (ldb_msg_copy_attr(msg,
1355                                              list_replace[a].replace,
1356                                              list_replace[a].attr) != LDB_SUCCESS) {
1357                         goto failed;
1358                 }
1359         }
1360
1361         /* Deletion of the search helper attributes are needed if:
1362          * - we generated constructed attributes and
1363          * - we aren't requesting all attributes
1364          */
1365         if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1366                 for (i=0; i < list_replace_size; i++) {
1367                         /* remove the added search helper attributes, unless
1368                          * they were asked for by the user */
1369                         if (list_replace[i].replace != NULL &&
1370                             !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1371                                 ldb_msg_remove_attr(msg, list_replace[i].replace);
1372                         }
1373                         if (list_replace[i].extra_attrs != NULL) {
1374                                 unsigned int j;
1375                                 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1376                                         if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1377                                                 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1378                                         }
1379                                 }
1380                         }
1381                 }
1382         }
1383
1384         return 0;
1385
1386 failed:
1387         ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1388                       "operational_search_post_process failed for attribute '%s' - %s",
1389                       list_replace[a].attr, ldb_errstring(ldb));
1390         return -1;
1391 }
1392
1393 /*
1394   hook search operations
1395 */
1396
1397 struct operational_context {
1398         struct ldb_module *module;
1399         struct ldb_request *req;
1400         enum ldb_scope scope;
1401         const char * const *attrs;
1402         struct op_controls_flags* controls_flags;
1403         struct op_attributes_operations *list_operations;
1404         unsigned int list_operations_size;
1405         struct op_attributes_replace *attrs_to_replace;
1406         unsigned int attrs_to_replace_size;
1407 };
1408
1409 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1410 {
1411         struct operational_context *ac;
1412         int ret;
1413
1414         ac = talloc_get_type(req->context, struct operational_context);
1415
1416         if (!ares) {
1417                 return ldb_module_done(ac->req, NULL, NULL,
1418                                         LDB_ERR_OPERATIONS_ERROR);
1419         }
1420         if (ares->error != LDB_SUCCESS) {
1421                 return ldb_module_done(ac->req, ares->controls,
1422                                         ares->response, ares->error);
1423         }
1424
1425         switch (ares->type) {
1426         case LDB_REPLY_ENTRY:
1427                 /* for each record returned post-process to add any derived
1428                    attributes that have been asked for */
1429                 ret = operational_search_post_process(ac->module,
1430                                                       ares->message,
1431                                                       ac->scope,
1432                                                       ac->attrs,
1433                                                       req->op.search.attrs,
1434                                                       ac->controls_flags,
1435                                                       ac->list_operations,
1436                                                       ac->list_operations_size,
1437                                                       ac->attrs_to_replace,
1438                                                       ac->attrs_to_replace_size,
1439                                                       req);
1440                 if (ret != 0) {
1441                         return ldb_module_done(ac->req, NULL, NULL,
1442                                                 LDB_ERR_OPERATIONS_ERROR);
1443                 }
1444                 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1445
1446         case LDB_REPLY_REFERRAL:
1447                 return ldb_module_send_referral(ac->req, ares->referral);
1448
1449         case LDB_REPLY_DONE:
1450
1451                 return ldb_module_done(ac->req, ares->controls,
1452                                         ares->response, LDB_SUCCESS);
1453         }
1454
1455         talloc_free(ares);
1456         return LDB_SUCCESS;
1457 }
1458
1459 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1460                                                               const char* const* attrs,
1461                                                               const char* const* searched_attrs,
1462                                                               struct op_controls_flags* controls_flags)
1463 {
1464         int idx = 0;
1465         int i;
1466         struct op_attributes_operations *list = talloc_zero_array(ctx,
1467                                                                   struct op_attributes_operations,
1468                                                                   ARRAY_SIZE(operational_remove) + 1);
1469
1470         if (list == NULL) {
1471                 return NULL;
1472         }
1473
1474         for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1475                 switch (operational_remove[i].op) {
1476                 case OPERATIONAL_REMOVE_UNASKED:
1477                         if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1478                                 continue;
1479                         }
1480                         if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1481                                 continue;
1482                         }
1483                         list[idx].attr = operational_remove[i].attr;
1484                         list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1485                         idx++;
1486                         break;
1487
1488                 case OPERATIONAL_REMOVE_ALWAYS:
1489                         list[idx].attr = operational_remove[i].attr;
1490                         list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1491                         idx++;
1492                         break;
1493
1494                 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1495                         if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1496                                 list[idx].attr = operational_remove[i].attr;
1497                                 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1498                                 idx++;
1499                         }
1500                         break;
1501
1502                 case OPERATIONAL_SD_FLAGS:
1503                         if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1504                                 continue;
1505                         }
1506                         if (controls_flags->sd) {
1507                                 if (attrs == NULL) {
1508                                         continue;
1509                                 }
1510                                 if (attrs[0] == NULL) {
1511                                         continue;
1512                                 }
1513                                 if (ldb_attr_in_list(attrs, "*")) {
1514                                         continue;
1515                                 }
1516                         }
1517                         list[idx].attr = operational_remove[i].attr;
1518                         list[idx].op = OPERATIONAL_SD_FLAGS;
1519                         idx++;
1520                         break;
1521                 }
1522         }
1523
1524         return list;
1525 }
1526
1527 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1528 {
1529         struct ldb_context *ldb;
1530         struct operational_context *ac;
1531         struct ldb_request *down_req;
1532         const char **search_attrs = NULL;
1533         unsigned int i, a;
1534         int ret;
1535
1536         /* There are no operational attributes on special DNs */
1537         if (ldb_dn_is_special(req->op.search.base)) {
1538                 return ldb_next_request(module, req);
1539         }
1540
1541         ldb = ldb_module_get_ctx(module);
1542
1543         ac = talloc(req, struct operational_context);
1544         if (ac == NULL) {
1545                 return ldb_oom(ldb);
1546         }
1547
1548         ac->module = module;
1549         ac->req = req;
1550         ac->scope = req->op.search.scope;
1551         ac->attrs = req->op.search.attrs;
1552
1553         /*  FIXME: We must copy the tree and keep the original
1554          *  unmodified. SSS */
1555         /* replace any attributes in the parse tree that are
1556            searchable, but are stored using a different name in the
1557            backend */
1558         for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1559                 ldb_parse_tree_attr_replace(req->op.search.tree,
1560                                             parse_tree_sub[i].attr,
1561                                             parse_tree_sub[i].replace);
1562         }
1563
1564         ac->controls_flags = talloc(ac, struct op_controls_flags);
1565         /* remember if the SD_FLAGS_OID was set */
1566         ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1567         /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1568         ac->controls_flags->bypassoperational =
1569                 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1570
1571         ac->attrs_to_replace = NULL;
1572         ac->attrs_to_replace_size = 0;
1573         /* in the list of attributes we are looking for, rename any
1574            attributes to the alias for any hidden attributes that can
1575            be fetched directly using non-hidden names */
1576         for (a=0;ac->attrs && ac->attrs[a];a++) {
1577                 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1578                         continue;
1579                 }
1580                 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1581
1582                         if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1583                                 continue;
1584                         }
1585
1586                         ac->attrs_to_replace = talloc_realloc(ac,
1587                                                               ac->attrs_to_replace,
1588                                                               struct op_attributes_replace,
1589                                                               ac->attrs_to_replace_size + 1);
1590
1591                         ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1592                         ac->attrs_to_replace_size++;
1593                         if (!search_sub[i].replace) {
1594                                 continue;
1595                         }
1596
1597                         if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1598                                 unsigned int j;
1599                                 const char **search_attrs2;
1600                                 /* Only adds to the end of the list */
1601                                 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1602                                         search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1603                                                                                ? search_attrs
1604                                                                                : ac->attrs, 
1605                                                                                search_sub[i].extra_attrs[j]);
1606                                         if (search_attrs2 == NULL) {
1607                                                 return ldb_operr(ldb);
1608                                         }
1609                                         /* may be NULL, talloc_free() doesn't mind */
1610                                         talloc_free(search_attrs);
1611                                         search_attrs = search_attrs2;
1612                                 }
1613                         }
1614
1615                         if (!search_attrs) {
1616                                 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1617                                 if (search_attrs == NULL) {
1618                                         return ldb_operr(ldb);
1619                                 }
1620                         }
1621                         /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1622                         search_attrs[a] = search_sub[i].replace;
1623                 }
1624         }
1625         ac->list_operations = operation_get_op_list(ac, ac->attrs,
1626                                                     search_attrs == NULL?req->op.search.attrs:search_attrs,
1627                                                     ac->controls_flags);
1628         ac->list_operations_size = 0;
1629         i = 0;
1630
1631         while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1632                 i++;
1633         }
1634         ac->list_operations_size = i;
1635         ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1636                                         req->op.search.base,
1637                                         req->op.search.scope,
1638                                         req->op.search.tree,
1639                                         /* use new set of attrs if any */
1640                                         search_attrs == NULL?req->op.search.attrs:search_attrs,
1641                                         req->controls,
1642                                         ac, operational_callback,
1643                                         req);
1644         LDB_REQ_SET_LOCATION(down_req);
1645         if (ret != LDB_SUCCESS) {
1646                 return ldb_operr(ldb);
1647         }
1648
1649         /* perform the search */
1650         return ldb_next_request(module, down_req);
1651 }
1652
1653 static int operational_init(struct ldb_module *ctx)
1654 {
1655         struct operational_data *data;
1656         int ret;
1657
1658         ret = ldb_next_init(ctx);
1659
1660         if (ret != LDB_SUCCESS) {
1661                 return ret;
1662         }
1663
1664         data = talloc_zero(ctx, struct operational_data);
1665         if (!data) {
1666                 return ldb_module_oom(ctx);
1667         }
1668
1669         ldb_module_set_private(ctx, data);
1670
1671         return LDB_SUCCESS;
1672 }
1673
1674 static const struct ldb_module_ops ldb_operational_module_ops = {
1675         .name              = "operational",
1676         .search            = operational_search,
1677         .init_context      = operational_init
1678 };
1679
1680 int ldb_operational_module_init(const char *version)
1681 {
1682         LDB_MODULE_CHECK_VERSION(version);
1683         return ldb_register_module(&ldb_operational_module_ops);
1684 }