s4:dsdb: Implement msDS-ManagedPassword attribute
[Samba.git] / source4 / dsdb / samdb / ldb_modules / operational.c
blob4e9ece53643410783537ae865e9eabe8ee05ea68
1 /*
2 ldb database library
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
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.
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.
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/>.
24 handle operational attributes
28 createTimeStamp: HIDDEN, searchable, ldaptime, alias for whenCreated
29 modifyTimeStamp: HIDDEN, searchable, ldaptime, alias for whenChanged
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
36 we also need to replace these with the whenCreated/whenChanged
37 equivalent in the search expression trees
39 whenCreated: not-HIDDEN, CONSTRUCTED, SEARCHABLE
40 whenChanged: not-HIDDEN, CONSTRUCTED, SEARCHABLE
42 on init we need to setup attribute handlers for these so
43 comparisons are done correctly. The resolution is 1 second.
45 on add we need to add both the above, for current time
47 on modify we need to change whenChanged
49 structuralObjectClass: HIDDEN, CONSTRUCTED, not-searchable. always same as objectclass?
51 for this one we do the search as normal, then if requested ask
52 for objectclass, change the attribute name, and add it
54 primaryGroupToken: HIDDEN, CONSTRUCTED, SEARCHABLE
56 contains the RID of a certain group object
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?
67 #include "includes.h"
68 #include <ldb.h>
69 #include <ldb_module.h>
71 #include "librpc/gen_ndr/ndr_misc.h"
72 #include "librpc/gen_ndr/ndr_drsblobs.h"
73 #include "dsdb/samdb/samdb.h"
74 #include "dsdb/samdb/ldb_modules/managed_pwd.h"
75 #include "dsdb/samdb/ldb_modules/util.h"
77 #include "auth/auth.h"
79 #ifndef ARRAY_SIZE
80 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
81 #endif
83 #undef strcasecmp
85 struct operational_data {
86 struct ldb_dn *aggregate_dn;
89 enum search_type {
90 TOKEN_GROUPS,
91 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL,
92 TOKEN_GROUPS_NO_GC_ACCEPTABLE,
95 * MS-DRSR 4.1.8.1.3 RevMembGetAccountGroups: Transitive membership in
96 * all account groups in a given domain, excluding built-in groups.
97 * (Used internally for msDS-ResultantPSO support)
99 ACCOUNT_GROUPS
102 static int get_pso_for_user(struct ldb_module *module,
103 struct ldb_message *user_msg,
104 struct ldb_request *parent,
105 struct ldb_message **pso_msg);
108 construct a canonical name from a message
110 static int construct_canonical_name(struct ldb_module *module,
111 struct ldb_message *msg, enum ldb_scope scope,
112 struct ldb_request *parent, struct ldb_reply *ares)
114 char *canonicalName;
115 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
116 if (canonicalName == NULL) {
117 return ldb_operr(ldb_module_get_ctx(module));
119 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
123 construct a primary group token for groups from a message
125 static int construct_primary_group_token(struct ldb_module *module,
126 struct ldb_message *msg, enum ldb_scope scope,
127 struct ldb_request *parent, struct ldb_reply *ares)
129 struct ldb_context *ldb;
130 uint32_t primary_group_token;
132 ldb = ldb_module_get_ctx(module);
133 if (ldb_match_msg_objectclass(msg, "group") == 1) {
134 primary_group_token
135 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
136 if (primary_group_token == 0) {
137 return LDB_SUCCESS;
140 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
141 primary_group_token);
142 } else {
143 return LDB_SUCCESS;
148 * Returns the group SIDs for the user in the given LDB message
150 static int get_group_sids(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
151 struct ldb_message *msg, const char *attribute_string,
152 enum search_type type, struct auth_SidAttr **groupSIDs,
153 uint32_t *num_groupSIDs)
155 const char *filter = NULL;
156 NTSTATUS status;
157 struct dom_sid *primary_group_sid;
158 const char *primary_group_string;
159 const char *primary_group_dn;
160 DATA_BLOB primary_group_blob;
161 struct dom_sid *account_sid;
162 const char *account_sid_string;
163 const char *account_sid_dn;
164 DATA_BLOB account_sid_blob;
165 struct dom_sid *domain_sid;
167 /* If it's not a user, it won't have a primaryGroupID */
168 if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
169 return LDB_SUCCESS;
172 /* Ensure it has an objectSID too */
173 account_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
174 if (account_sid == NULL) {
175 return LDB_SUCCESS;
178 status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
179 if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
180 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
181 } else if (!NT_STATUS_IS_OK(status)) {
182 return LDB_ERR_OPERATIONS_ERROR;
185 primary_group_sid = dom_sid_add_rid(mem_ctx,
186 domain_sid,
187 ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
188 if (!primary_group_sid) {
189 return ldb_oom(ldb);
192 /* only return security groups */
193 switch(type) {
194 case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
195 filter = talloc_asprintf(mem_ctx,
196 "(&(objectClass=group)"
197 "(groupType:"LDB_OID_COMPARATOR_AND":=%u)"
198 "(groupType:"LDB_OID_COMPARATOR_OR":=%u))",
199 GROUP_TYPE_SECURITY_ENABLED,
200 GROUP_TYPE_ACCOUNT_GROUP | GROUP_TYPE_UNIVERSAL_GROUP);
201 break;
202 case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
203 case TOKEN_GROUPS:
204 filter = talloc_asprintf(mem_ctx,
205 "(&(objectClass=group)"
206 "(groupType:"LDB_OID_COMPARATOR_AND":=%u))",
207 GROUP_TYPE_SECURITY_ENABLED);
208 break;
210 /* for RevMembGetAccountGroups, exclude built-in groups */
211 case ACCOUNT_GROUPS:
212 filter = talloc_asprintf(mem_ctx,
213 "(&(objectClass=group)"
214 "(!(groupType:"LDB_OID_COMPARATOR_AND":=%u))"
215 "(groupType:"LDB_OID_COMPARATOR_AND":=%u))",
216 GROUP_TYPE_BUILTIN_LOCAL_GROUP, GROUP_TYPE_SECURITY_ENABLED);
217 break;
220 if (!filter) {
221 return ldb_oom(ldb);
224 primary_group_string = dom_sid_string(mem_ctx, primary_group_sid);
225 if (!primary_group_string) {
226 return ldb_oom(ldb);
229 primary_group_dn = talloc_asprintf(mem_ctx, "<SID=%s>", primary_group_string);
230 if (!primary_group_dn) {
231 return ldb_oom(ldb);
234 primary_group_blob = data_blob_string_const(primary_group_dn);
236 account_sid_string = dom_sid_string(mem_ctx, account_sid);
237 if (!account_sid_string) {
238 return ldb_oom(ldb);
241 account_sid_dn = talloc_asprintf(mem_ctx, "<SID=%s>", account_sid_string);
242 if (!account_sid_dn) {
243 return ldb_oom(ldb);
246 account_sid_blob = data_blob_string_const(account_sid_dn);
248 status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
249 true, /* We don't want to add the object's SID itself,
250 it's not returned in this attribute */
251 filter,
252 mem_ctx, groupSIDs, num_groupSIDs);
254 if (!NT_STATUS_IS_OK(status)) {
255 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
256 attribute_string, account_sid_string,
257 nt_errstr(status));
258 return LDB_ERR_OPERATIONS_ERROR;
261 /* Expands the primary group - this function takes in
262 * memberOf-like values, so we fake one up with the
263 * <SID=S-...> format of DN and then let it expand
264 * them, as long as they meet the filter - so only
265 * domain groups, not builtin groups
267 status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
268 mem_ctx, groupSIDs, num_groupSIDs);
269 if (!NT_STATUS_IS_OK(status)) {
270 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
271 attribute_string, account_sid_string,
272 nt_errstr(status));
273 return LDB_ERR_OPERATIONS_ERROR;
276 return LDB_SUCCESS;
280 construct the token groups for SAM objects from a message
282 static int construct_generic_token_groups(struct ldb_module *module,
283 struct ldb_message *msg, enum ldb_scope scope,
284 struct ldb_request *parent,
285 const char *attribute_string,
286 enum search_type type)
288 struct ldb_context *ldb = ldb_module_get_ctx(module);
289 TALLOC_CTX *tmp_ctx = talloc_new(msg);
290 uint32_t i;
291 int ret;
292 struct auth_SidAttr *groupSIDs = NULL;
293 uint32_t num_groupSIDs = 0;
295 if (scope != LDB_SCOPE_BASE) {
296 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
297 return LDB_ERR_OPERATIONS_ERROR;
300 /* calculate the group SIDs for this object */
301 ret = get_group_sids(ldb, tmp_ctx, msg, attribute_string, type,
302 &groupSIDs, &num_groupSIDs);
304 if (ret != LDB_SUCCESS) {
305 talloc_free(tmp_ctx);
306 return LDB_ERR_OPERATIONS_ERROR;
309 /* add these SIDs to the search result */
310 for (i=0; i < num_groupSIDs; i++) {
311 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i].sid);
312 if (ret) {
313 talloc_free(tmp_ctx);
314 return ret;
318 return LDB_SUCCESS;
321 static int construct_token_groups(struct ldb_module *module,
322 struct ldb_message *msg, enum ldb_scope scope,
323 struct ldb_request *parent, struct ldb_reply *ares)
326 * TODO: Add in a limiting domain when we start to support
327 * trusted domains.
329 return construct_generic_token_groups(module, msg, scope, parent,
330 "tokenGroups",
331 TOKEN_GROUPS);
334 static int construct_token_groups_no_gc(struct ldb_module *module,
335 struct ldb_message *msg, enum ldb_scope scope,
336 struct ldb_request *parent, struct ldb_reply *ares)
339 * TODO: Add in a limiting domain when we start to support
340 * trusted domains.
342 return construct_generic_token_groups(module, msg, scope, parent,
343 "tokenGroupsNoGCAcceptable",
344 TOKEN_GROUPS);
347 static int construct_global_universal_token_groups(struct ldb_module *module,
348 struct ldb_message *msg, enum ldb_scope scope,
349 struct ldb_request *parent, struct ldb_reply *ares)
351 return construct_generic_token_groups(module, msg, scope, parent,
352 "tokenGroupsGlobalAndUniversal",
353 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
356 construct the parent GUID for an entry from a message
358 static int construct_parent_guid(struct ldb_module *module,
359 struct ldb_message *msg, enum ldb_scope scope,
360 struct ldb_request *parent, struct ldb_reply *ares)
362 struct ldb_result *res, *parent_res;
363 const struct ldb_val *parent_guid;
364 const char *attrs[] = { "instanceType", NULL };
365 const char *attrs2[] = { "objectGUID", NULL };
366 uint32_t instanceType;
367 int ret;
368 struct ldb_dn *parent_dn;
369 struct ldb_val v;
371 /* determine if the object is NC by instance type */
372 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
373 DSDB_FLAG_NEXT_MODULE |
374 DSDB_SEARCH_SHOW_RECYCLED, parent);
375 if (ret != LDB_SUCCESS) {
376 return ret;
379 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
380 "instanceType", 0);
381 talloc_free(res);
382 if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
383 DEBUG(4,(__location__ ": Object %s is NC\n",
384 ldb_dn_get_linearized(msg->dn)));
385 return LDB_SUCCESS;
387 parent_dn = ldb_dn_get_parent(msg, msg->dn);
389 if (parent_dn == NULL) {
390 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
391 ldb_dn_get_linearized(msg->dn)));
392 return LDB_ERR_OTHER;
394 ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
395 DSDB_FLAG_NEXT_MODULE |
396 DSDB_SEARCH_SHOW_RECYCLED, parent);
397 /* not NC, so the object should have a parent*/
398 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
399 ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
400 talloc_asprintf(msg, "Parent dn %s for %s does not exist",
401 ldb_dn_get_linearized(parent_dn),
402 ldb_dn_get_linearized(msg->dn)));
403 talloc_free(parent_dn);
404 return ret;
405 } else if (ret != LDB_SUCCESS) {
406 talloc_free(parent_dn);
407 return ret;
409 talloc_free(parent_dn);
411 parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
412 if (!parent_guid) {
413 talloc_free(parent_res);
414 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
417 v = data_blob_dup_talloc(parent_res, *parent_guid);
418 if (!v.data) {
419 talloc_free(parent_res);
420 return ldb_oom(ldb_module_get_ctx(module));
422 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
423 talloc_free(parent_res);
424 return ret;
427 static int construct_modifyTimeStamp(struct ldb_module *module,
428 struct ldb_message *msg, enum ldb_scope scope,
429 struct ldb_request *parent, struct ldb_reply *ares)
431 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
432 struct ldb_context *ldb = ldb_module_get_ctx(module);
434 /* We may be being called before the init function has finished */
435 if (!data) {
436 return LDB_SUCCESS;
439 /* Try and set this value up, if possible. Don't worry if it
440 * fails, we may not have the DB set up yet.
442 if (!data->aggregate_dn) {
443 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
446 if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
448 * If we have the DN for the object with common name = Aggregate and
449 * the request is for this DN then let's do the following:
450 * 1) search the object which changedUSN correspond to the one of the loaded
451 * schema.
452 * 2) Get the whenChanged attribute
453 * 3) Generate the modifyTimestamp out of the whenChanged attribute
455 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
456 char *value = ldb_timestring(msg, schema->ts_last_change);
458 if (value == NULL) {
459 return ldb_oom(ldb_module_get_ctx(module));
462 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
464 return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
468 construct a subSchemaSubEntry
470 static int construct_subschema_subentry(struct ldb_module *module,
471 struct ldb_message *msg, enum ldb_scope scope,
472 struct ldb_request *parent, struct ldb_reply *ares)
474 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
475 char *subSchemaSubEntry;
477 /* We may be being called before the init function has finished */
478 if (!data) {
479 return LDB_SUCCESS;
482 /* Try and set this value up, if possible. Don't worry if it
483 * fails, we may not have the DB set up yet, and it's not
484 * really vital anyway */
485 if (!data->aggregate_dn) {
486 struct ldb_context *ldb = ldb_module_get_ctx(module);
487 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
490 if (data->aggregate_dn) {
491 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
492 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
494 return LDB_SUCCESS;
498 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
499 struct ldb_message *msg,
500 struct ldb_message_element *object_category)
502 struct ldb_context *ldb;
503 struct ldb_dn *dn;
504 const struct ldb_val *val;
506 ldb = ldb_module_get_ctx(module);
507 if (!ldb) {
508 DEBUG(4, (__location__ ": Failed to get ldb \n"));
509 return LDB_ERR_OPERATIONS_ERROR;
512 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
513 if (!dn) {
514 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
515 (const char *)object_category->values[0].data));
516 return ldb_operr(ldb);
519 val = ldb_dn_get_rdn_val(dn);
520 if (!val) {
521 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
522 ldb_dn_get_linearized(dn)));
523 return ldb_operr(ldb);
526 if (strequal((const char *)val->data, "NTDS-DSA")) {
527 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
528 } else {
529 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
531 return LDB_SUCCESS;
534 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
535 struct ldb_message *msg,
536 struct ldb_dn *dn,
537 struct ldb_request *parent)
539 struct ldb_dn *server_dn;
540 const char *attr_obj_cat[] = { "objectCategory", NULL };
541 struct ldb_result *res;
542 struct ldb_message_element *object_category;
543 int ret;
545 server_dn = ldb_dn_copy(msg, dn);
546 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
547 DEBUG(4, (__location__ ": Failed to add child to %s \n",
548 ldb_dn_get_linearized(server_dn)));
549 return ldb_operr(ldb_module_get_ctx(module));
552 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
553 DSDB_FLAG_NEXT_MODULE, parent);
554 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
555 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
556 ldb_dn_get_linearized(server_dn)));
557 return LDB_SUCCESS;
558 } else if (ret != LDB_SUCCESS) {
559 return ret;
562 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
563 if (!object_category) {
564 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
565 ldb_dn_get_linearized(res->msgs[0]->dn)));
566 return LDB_SUCCESS;
568 return construct_msds_isrodc_with_dn(module, msg, object_category);
571 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
572 struct ldb_message *msg,
573 struct ldb_request *parent)
575 int ret;
576 struct ldb_dn *server_dn;
578 ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
579 &server_dn, parent);
580 if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
581 /* it's OK if we can't find serverReferenceBL attribute */
582 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
583 ldb_dn_get_linearized(msg->dn)));
584 return LDB_SUCCESS;
585 } else if (ret != LDB_SUCCESS) {
586 return ret;
589 return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
593 construct msDS-isRODC attr
595 static int construct_msds_isrodc(struct ldb_module *module,
596 struct ldb_message *msg, enum ldb_scope scope,
597 struct ldb_request *parent, struct ldb_reply *ares)
599 struct ldb_message_element * object_class;
600 struct ldb_message_element * object_category;
601 unsigned int i;
603 object_class = ldb_msg_find_element(msg, "objectClass");
604 if (!object_class) {
605 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
606 ldb_dn_get_linearized(msg->dn)));
607 return ldb_operr(ldb_module_get_ctx(module));
610 for (i=0; i<object_class->num_values; i++) {
611 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
612 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
613 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
615 object_category = ldb_msg_find_element(msg, "objectCategory");
616 if (!object_category) {
617 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
618 ldb_dn_get_linearized(msg->dn)));
619 return LDB_SUCCESS;
621 return construct_msds_isrodc_with_dn(module, msg, object_category);
623 if (strequal((const char*)object_class->values[i].data, "server")) {
624 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
625 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
626 * substituting TN for TO.
628 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
630 if (strequal((const char*)object_class->values[i].data, "computer")) {
631 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
632 * rule for the "TO is a server object" case, substituting TS for TO.
634 return construct_msds_isrodc_with_computer_dn(module, msg, parent);
638 return LDB_SUCCESS;
643 construct msDS-keyVersionNumber attr
645 TODO: Make this based on the 'win2k' DS heuristics bit...
648 static int construct_msds_keyversionnumber(struct ldb_module *module,
649 struct ldb_message *msg,
650 enum ldb_scope scope,
651 struct ldb_request *parent,
652 struct ldb_reply *ares)
654 uint32_t i;
655 enum ndr_err_code ndr_err;
656 const struct ldb_val *omd_value;
657 struct replPropertyMetaDataBlob *omd;
658 int ret;
660 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
661 if (!omd_value) {
662 /* We can't make up a key version number without meta data */
663 return LDB_SUCCESS;
666 omd = talloc(msg, struct replPropertyMetaDataBlob);
667 if (!omd) {
668 ldb_module_oom(module);
669 return LDB_SUCCESS;
672 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
673 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
674 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
675 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
676 ldb_dn_get_linearized(msg->dn)));
677 return ldb_operr(ldb_module_get_ctx(module));
680 if (omd->version != 1) {
681 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
682 omd->version, ldb_dn_get_linearized(msg->dn)));
683 talloc_free(omd);
684 return LDB_SUCCESS;
686 for (i=0; i<omd->ctr.ctr1.count; i++) {
687 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
688 ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
689 msg, msg,
690 "msDS-KeyVersionNumber",
691 omd->ctr.ctr1.array[i].version);
692 if (ret != LDB_SUCCESS) {
693 talloc_free(omd);
694 return ret;
696 break;
699 return LDB_SUCCESS;
703 #define _UF_TRUST_ACCOUNTS ( \
704 UF_WORKSTATION_TRUST_ACCOUNT | \
705 UF_SERVER_TRUST_ACCOUNT | \
706 UF_INTERDOMAIN_TRUST_ACCOUNT \
708 #define _UF_NO_EXPIRY_ACCOUNTS ( \
709 UF_SMARTCARD_REQUIRED | \
710 UF_DONT_EXPIRE_PASSWD | \
711 _UF_TRUST_ACCOUNTS \
716 * Returns the Effective-MaximumPasswordAge for a user
718 static int64_t get_user_max_pwd_age(struct ldb_module *module,
719 struct ldb_message *user_msg,
720 struct ldb_request *parent,
721 struct ldb_dn *nc_root)
723 int ret;
724 struct ldb_message *pso = NULL;
725 struct ldb_context *ldb = ldb_module_get_ctx(module);
727 /* if a PSO applies to the user, use its maxPwdAge */
728 ret = get_pso_for_user(module, user_msg, parent, &pso);
729 if (ret != LDB_SUCCESS) {
731 /* log the error, but fallback to the domain default */
732 DBG_ERR("Error retrieving PSO for %s\n",
733 ldb_dn_get_linearized(user_msg->dn));
736 if (pso != NULL) {
737 return ldb_msg_find_attr_as_int64(pso,
738 "msDS-MaximumPasswordAge", 0);
741 /* otherwise return the default domain value */
742 return samdb_search_int64(ldb, user_msg, 0, nc_root, "maxPwdAge", NULL);
746 calculate msDS-UserPasswordExpiryTimeComputed
748 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
749 struct ldb_message *msg,
750 struct ldb_request *parent,
751 struct ldb_dn *domain_dn)
753 int64_t pwdLastSet, maxPwdAge;
754 uint32_t userAccountControl;
755 NTTIME ret;
757 userAccountControl = ldb_msg_find_attr_as_uint(msg,
758 "userAccountControl",
760 if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
761 return INT64_MAX;
764 pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
765 if (pwdLastSet == 0) {
766 return 0;
769 if (pwdLastSet <= -1) {
771 * This can't really happen...
773 return INT64_MAX;
776 if (pwdLastSet >= INT64_MAX) {
778 * Somethings wrong with the clock...
780 return INT64_MAX;
784 * Note that maxPwdAge is a stored as negative value.
786 * Possible values are in the range of:
788 * maxPwdAge: -864000000001
789 * to
790 * maxPwdAge: -9223372036854775808 (INT64_MIN)
793 maxPwdAge = get_user_max_pwd_age(module, msg, parent, domain_dn);
794 if (maxPwdAge >= -864000000000) {
796 * This is not really possible...
798 return INT64_MAX;
801 if (maxPwdAge == INT64_MIN) {
802 return INT64_MAX;
806 * Note we already caught maxPwdAge == INT64_MIN
807 * and pwdLastSet >= INT64_MAX above.
809 * Remember maxPwdAge is a negative number,
810 * so it results in the following.
812 * 0x7FFFFFFFFFFFFFFEULL + INT64_MAX
814 * 0xFFFFFFFFFFFFFFFDULL
816 * or to put it another way, adding two numbers less than 1<<63 can't
817 * ever be more than 1<<64, therefore this result can't wrap.
819 ret = (NTTIME)pwdLastSet - (NTTIME)maxPwdAge;
820 if (ret >= INT64_MAX) {
821 return INT64_MAX;
824 return ret;
828 * Returns the Effective-LockoutDuration for a user
830 static int64_t get_user_lockout_duration(struct ldb_module *module,
831 struct ldb_message *user_msg,
832 struct ldb_request *parent,
833 struct ldb_dn *nc_root)
835 int ret;
836 struct ldb_message *pso = NULL;
837 struct ldb_context *ldb = ldb_module_get_ctx(module);
839 /* if a PSO applies to the user, use its lockoutDuration */
840 ret = get_pso_for_user(module, user_msg, parent, &pso);
841 if (ret != LDB_SUCCESS) {
843 /* log the error, but fallback to the domain default */
844 DBG_ERR("Error retrieving PSO for %s\n",
845 ldb_dn_get_linearized(user_msg->dn));
848 if (pso != NULL) {
849 return ldb_msg_find_attr_as_int64(pso,
850 "msDS-LockoutDuration", 0);
853 /* otherwise return the default domain value */
854 return samdb_search_int64(ldb, user_msg, 0, nc_root, "lockoutDuration",
855 NULL);
859 construct msDS-User-Account-Control-Computed attr
861 static int construct_msds_user_account_control_computed(struct ldb_module *module,
862 struct ldb_message *msg, enum ldb_scope scope,
863 struct ldb_request *parent, struct ldb_reply *ares)
865 uint32_t userAccountControl;
866 uint32_t msDS_User_Account_Control_Computed = 0;
867 struct ldb_context *ldb = ldb_module_get_ctx(module);
868 NTTIME now;
869 struct ldb_dn *nc_root;
870 int ret;
872 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
873 if (ret != 0) {
874 ldb_asprintf_errstring(ldb,
875 "Failed to find NC root of DN: %s: %s",
876 ldb_dn_get_linearized(msg->dn),
877 ldb_errstring(ldb_module_get_ctx(module)));
878 return ret;
880 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
881 /* Only calculate this on our default NC */
882 return 0;
884 /* Test account expire time */
885 unix_to_nt_time(&now, time(NULL));
887 userAccountControl = ldb_msg_find_attr_as_uint(msg,
888 "userAccountControl",
890 if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
892 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
893 if (lockoutTime != 0) {
894 int64_t lockoutDuration;
896 lockoutDuration = get_user_lockout_duration(module, msg,
897 parent,
898 nc_root);
900 /* zero locks out until the administrator intervenes */
901 if (lockoutDuration >= 0) {
902 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
903 } else if (lockoutTime - lockoutDuration >= now) {
904 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
909 if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
910 NTTIME must_change_time
911 = get_msds_user_password_expiry_time_computed(module,
912 msg,
913 parent,
914 nc_root);
915 /* check for expired password */
916 if (must_change_time < now) {
917 msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
921 return samdb_msg_add_int64(ldb,
922 msg->elements, msg,
923 "msDS-User-Account-Control-Computed",
924 msDS_User_Account_Control_Computed);
928 construct msDS-UserPasswordExpiryTimeComputed
930 static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
931 struct ldb_message *msg, enum ldb_scope scope,
932 struct ldb_request *parent, struct ldb_reply *ares)
934 struct ldb_context *ldb = ldb_module_get_ctx(module);
935 struct ldb_dn *nc_root;
936 int64_t password_expiry_time;
937 int ret;
939 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
940 if (ret != 0) {
941 ldb_asprintf_errstring(ldb,
942 "Failed to find NC root of DN: %s: %s",
943 ldb_dn_get_linearized(msg->dn),
944 ldb_errstring(ldb));
945 return ret;
948 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
949 /* Only calculate this on our default NC */
950 return 0;
953 password_expiry_time
954 = get_msds_user_password_expiry_time_computed(module, msg,
955 parent, nc_root);
957 return samdb_msg_add_int64(ldb,
958 msg->elements, msg,
959 "msDS-UserPasswordExpiryTimeComputed",
960 password_expiry_time);
964 * Checks whether the msDS-ResultantPSO attribute is supported for a given
965 * user object. As per MS-ADTS, section 3.1.1.4.5.36 msDS-ResultantPSO.
967 static bool pso_is_supported(struct ldb_context *ldb, struct ldb_message *msg)
969 int functional_level;
970 uint32_t uac;
971 uint32_t user_rid;
973 functional_level = dsdb_functional_level(ldb);
974 if (functional_level < DS_DOMAIN_FUNCTION_2008) {
975 return false;
978 /* msDS-ResultantPSO is only supported for user objects */
979 if (!ldb_match_msg_objectclass(msg, "user")) {
980 return false;
983 /* ...and only if the ADS_UF_NORMAL_ACCOUNT bit is set */
984 uac = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
985 if (!(uac & UF_NORMAL_ACCOUNT)) {
986 return false;
989 /* skip it if it's the special KRBTGT default account */
990 user_rid = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
991 if (user_rid == DOMAIN_RID_KRBTGT) {
992 return false;
995 /* ...or if it's a special KRBTGT account for an RODC KDC */
996 if (ldb_msg_find_ldb_val(msg, "msDS-SecondaryKrbTgtNumber") != NULL) {
997 return false;
1000 return true;
1004 * Returns the number of PSO objects that exist in the DB
1006 static int get_pso_count(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1007 struct ldb_request *parent, int *pso_count)
1009 static const char * const attrs[] = { NULL };
1010 int ret;
1011 struct ldb_dn *psc_dn = NULL;
1012 struct ldb_result *res = NULL;
1013 struct ldb_context *ldb = ldb_module_get_ctx(module);
1014 bool psc_ok;
1016 *pso_count = 0;
1017 psc_dn = samdb_system_container_dn(ldb, mem_ctx);
1018 if (psc_dn == NULL) {
1019 return ldb_oom(ldb);
1021 psc_ok = ldb_dn_add_child_fmt(psc_dn, "CN=Password Settings Container");
1022 if (psc_ok == false) {
1023 return ldb_oom(ldb);
1026 /* get the number of PSO children */
1027 ret = dsdb_module_search(module, mem_ctx, &res, psc_dn,
1028 LDB_SCOPE_ONELEVEL, attrs,
1029 DSDB_FLAG_NEXT_MODULE, parent,
1030 "(objectClass=msDS-PasswordSettings)");
1033 * Just ignore PSOs if the container doesn't exist. This is a weird
1034 * corner-case where the AD DB was created from a pre-2008 base schema,
1035 * and then the FL was manually upgraded.
1037 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
1038 DBG_NOTICE("No Password Settings Container exists\n");
1039 return LDB_SUCCESS;
1042 if (ret != LDB_SUCCESS) {
1043 return ret;
1046 *pso_count = res->count;
1047 talloc_free(res);
1048 talloc_free(psc_dn);
1050 return LDB_SUCCESS;
1054 * Compares two PSO objects returned by a search, to work out the better PSO.
1055 * The PSO with the lowest precedence is better, otherwise (if the precedence
1056 * is equal) the PSO with the lower GUID wins.
1058 static int pso_compare(struct ldb_message **m1, struct ldb_message **m2)
1060 uint32_t prec1;
1061 uint32_t prec2;
1063 prec1 = ldb_msg_find_attr_as_uint(*m1, "msDS-PasswordSettingsPrecedence",
1064 0xffffffff);
1065 prec2 = ldb_msg_find_attr_as_uint(*m2, "msDS-PasswordSettingsPrecedence",
1066 0xffffffff);
1068 /* if precedence is equal, use the lowest GUID */
1069 if (prec1 == prec2) {
1070 struct GUID guid1 = samdb_result_guid(*m1, "objectGUID");
1071 struct GUID guid2 = samdb_result_guid(*m2, "objectGUID");
1073 return ndr_guid_compare(&guid1, &guid2);
1074 } else {
1075 return NUMERIC_CMP(prec1, prec2);
1080 * Search for PSO objects that apply to the object SIDs specified
1082 static int pso_search_by_sids(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1083 struct ldb_request *parent,
1084 struct auth_SidAttr *sid_array, unsigned int num_sids,
1085 struct ldb_result **result)
1087 int ret;
1088 int i;
1089 struct ldb_context *ldb = ldb_module_get_ctx(module);
1090 char *sid_filter = NULL;
1091 struct ldb_dn *psc_dn = NULL;
1092 bool psc_ok;
1093 const char *attrs[] = {
1094 "msDS-PasswordSettingsPrecedence",
1095 "objectGUID",
1096 "msDS-LockoutDuration",
1097 "msDS-MaximumPasswordAge",
1098 NULL
1101 /* build a query for PSO objects that apply to any of the SIDs given */
1102 sid_filter = talloc_strdup(mem_ctx, "");
1103 if (sid_filter == NULL) {
1104 return ldb_oom(ldb);
1107 for (i = 0; sid_filter && i < num_sids; i++) {
1108 struct dom_sid_buf sid_buf;
1110 sid_filter = talloc_asprintf_append(
1111 sid_filter,
1112 "(msDS-PSOAppliesTo=<SID=%s>)",
1113 dom_sid_str_buf(&sid_array[i].sid, &sid_buf));
1114 if (sid_filter == NULL) {
1115 return ldb_oom(ldb);
1119 /* only PSOs located in the Password Settings Container are valid */
1120 psc_dn = samdb_system_container_dn(ldb, mem_ctx);
1121 if (psc_dn == NULL) {
1122 return ldb_oom(ldb);
1124 psc_ok = ldb_dn_add_child_fmt(psc_dn, "CN=Password Settings Container");
1125 if (psc_ok == false) {
1126 return ldb_oom(ldb);
1129 ret = dsdb_module_search(module, mem_ctx, result, psc_dn,
1130 LDB_SCOPE_ONELEVEL, attrs,
1131 DSDB_FLAG_NEXT_MODULE, parent,
1132 "(&(objectClass=msDS-PasswordSettings)(|%s))",
1133 sid_filter);
1134 talloc_free(sid_filter);
1135 return ret;
1139 * Returns the best PSO object that applies to the object SID(s) specified
1141 static int pso_find_best(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1142 struct ldb_request *parent, struct auth_SidAttr *sid_array,
1143 unsigned int num_sids, struct ldb_message **best_pso)
1145 struct ldb_result *res = NULL;
1146 int ret;
1148 *best_pso = NULL;
1150 /* find any PSOs that apply to the SIDs specified */
1151 ret = pso_search_by_sids(module, mem_ctx, parent, sid_array, num_sids,
1152 &res);
1153 if (ret != LDB_SUCCESS) {
1154 DBG_ERR("Error %d retrieving PSO for SID(s)\n", ret);
1155 return ret;
1158 /* sort the list so that the best PSO is first */
1159 TYPESAFE_QSORT(res->msgs, res->count, pso_compare);
1161 if (res->count > 0) {
1162 *best_pso = res->msgs[0];
1165 return LDB_SUCCESS;
1169 * Determines the Password Settings Object (PSO) that applies to the given user
1171 static int get_pso_for_user(struct ldb_module *module,
1172 struct ldb_message *user_msg,
1173 struct ldb_request *parent,
1174 struct ldb_message **pso_msg)
1176 bool pso_supported;
1177 struct auth_SidAttr *groupSIDs = NULL;
1178 uint32_t num_groupSIDs = 0;
1179 struct ldb_context *ldb = ldb_module_get_ctx(module);
1180 struct ldb_message *best_pso = NULL;
1181 struct ldb_dn *pso_dn = NULL;
1182 int ret;
1183 struct ldb_message_element *el = NULL;
1184 TALLOC_CTX *tmp_ctx = NULL;
1185 int pso_count = 0;
1186 struct ldb_result *res = NULL;
1187 static const char *attrs[] = {
1188 "msDS-LockoutDuration",
1189 "msDS-MaximumPasswordAge",
1190 NULL
1193 *pso_msg = NULL;
1195 /* first, check msDS-ResultantPSO is supported for this object */
1196 pso_supported = pso_is_supported(ldb, user_msg);
1198 if (!pso_supported) {
1199 return LDB_SUCCESS;
1202 tmp_ctx = talloc_new(user_msg);
1205 * Several different constructed attributes try to use the PSO info. If
1206 * we've already constructed the msDS-ResultantPSO for this user, we can
1207 * just re-use the result, rather than calculating it from scratch again
1209 pso_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, user_msg,
1210 "msDS-ResultantPSO");
1212 if (pso_dn != NULL) {
1213 ret = dsdb_module_search_dn(module, tmp_ctx, &res, pso_dn,
1214 attrs, DSDB_FLAG_NEXT_MODULE,
1215 parent);
1216 if (ret != LDB_SUCCESS) {
1217 DBG_ERR("Error %d retrieving PSO %s\n", ret,
1218 ldb_dn_get_linearized(pso_dn));
1219 talloc_free(tmp_ctx);
1220 return ret;
1223 if (res->count == 1) {
1224 *pso_msg = res->msgs[0];
1225 return LDB_SUCCESS;
1230 * if any PSOs apply directly to the user, they are considered first
1231 * before we check group membership PSOs
1233 el = ldb_msg_find_element(user_msg, "msDS-PSOApplied");
1235 if (el != NULL && el->num_values > 0) {
1236 struct auth_SidAttr *user_sid = NULL;
1238 /* lookup the best PSO object, based on the user's SID */
1239 user_sid = samdb_result_dom_sid_attrs(
1240 tmp_ctx, user_msg, "objectSid",
1241 SE_GROUP_DEFAULT_FLAGS);
1243 ret = pso_find_best(module, tmp_ctx, parent, user_sid, 1,
1244 &best_pso);
1245 if (ret != LDB_SUCCESS) {
1246 talloc_free(tmp_ctx);
1247 return ret;
1250 if (best_pso != NULL) {
1251 *pso_msg = best_pso;
1252 return LDB_SUCCESS;
1257 * If no valid PSO applies directly to the user, then try its groups.
1258 * The group expansion is expensive, so check there are actually
1259 * PSOs in the DB first (which is a quick search). Note in the above
1260 * cases we could tell that a PSO applied to the user, based on info
1261 * already retrieved by the user search.
1263 ret = get_pso_count(module, tmp_ctx, parent, &pso_count);
1264 if (ret != LDB_SUCCESS) {
1265 DBG_ERR("Error %d determining PSOs in system\n", ret);
1266 talloc_free(tmp_ctx);
1267 return ret;
1270 if (pso_count == 0) {
1271 talloc_free(tmp_ctx);
1272 return LDB_SUCCESS;
1275 /* Work out the SIDs of any account groups the user is a member of */
1276 ret = get_group_sids(ldb, tmp_ctx, user_msg,
1277 "msDS-ResultantPSO", ACCOUNT_GROUPS,
1278 &groupSIDs, &num_groupSIDs);
1279 if (ret != LDB_SUCCESS) {
1280 DBG_ERR("Error %d determining group SIDs for %s\n", ret,
1281 ldb_dn_get_linearized(user_msg->dn));
1282 talloc_free(tmp_ctx);
1283 return ret;
1286 /* lookup the best PSO that applies to any of these groups */
1287 ret = pso_find_best(module, tmp_ctx, parent, groupSIDs,
1288 num_groupSIDs, &best_pso);
1289 if (ret != LDB_SUCCESS) {
1290 talloc_free(tmp_ctx);
1291 return ret;
1294 *pso_msg = best_pso;
1295 return LDB_SUCCESS;
1299 * Constructs the msDS-ResultantPSO attribute, which is the DN of the Password
1300 * Settings Object (PSO) that applies to that user.
1302 static int construct_resultant_pso(struct ldb_module *module,
1303 struct ldb_message *msg,
1304 enum ldb_scope scope,
1305 struct ldb_request *parent,
1306 struct ldb_reply *ares)
1308 struct ldb_message *pso = NULL;
1309 int ret;
1311 /* work out the PSO (if any) that applies to this user */
1312 ret = get_pso_for_user(module, msg, parent, &pso);
1313 if (ret != LDB_SUCCESS) {
1314 DBG_ERR("Couldn't determine PSO for %s\n",
1315 ldb_dn_get_linearized(msg->dn));
1316 return ret;
1319 if (pso != NULL) {
1320 DBG_INFO("%s is resultant PSO for user %s\n",
1321 ldb_dn_get_linearized(pso->dn),
1322 ldb_dn_get_linearized(msg->dn));
1323 return ldb_msg_add_string(msg, "msDS-ResultantPSO",
1324 ldb_dn_get_linearized(pso->dn));
1327 /* no PSO applies to this user */
1328 return LDB_SUCCESS;
1331 struct op_controls_flags {
1332 bool sd;
1333 bool bypassoperational;
1336 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
1337 if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
1338 return true;
1340 return false;
1344 a list of attribute names that should be substituted in the parse
1345 tree before the search is done
1347 static const struct {
1348 const char *attr;
1349 const char *replace;
1350 } parse_tree_sub[] = {
1351 { "createTimeStamp", "whenCreated" },
1352 { "modifyTimeStamp", "whenChanged" }
1356 struct op_attributes_replace {
1357 const char *attr;
1358 const char *replace;
1359 const char * const *extra_attrs;
1360 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *, struct ldb_reply *);
1363 /* the 'extra_attrs' required for msDS-ResultantPSO */
1364 #define RESULTANT_PSO_COMPUTED_ATTRS \
1365 "msDS-PSOApplied", \
1366 "userAccountControl", \
1367 "objectSid", \
1368 "msDS-SecondaryKrbTgtNumber", \
1369 "primaryGroupID"
1372 * any other constructed attributes that want to work out the PSO also need to
1373 * include objectClass (this gets included via 'replace' for msDS-ResultantPSO)
1375 #define PSO_ATTR_DEPENDENCIES \
1376 RESULTANT_PSO_COMPUTED_ATTRS, \
1377 "objectClass"
1379 static const char *objectSid_attr[] =
1381 "objectSid",
1382 NULL
1386 static const char *objectCategory_attr[] =
1388 "objectCategory",
1389 NULL
1393 static const char *user_account_control_computed_attrs[] =
1395 "lockoutTime",
1396 "pwdLastSet",
1397 PSO_ATTR_DEPENDENCIES,
1398 NULL
1402 static const char *user_password_expiry_time_computed_attrs[] =
1404 "pwdLastSet",
1405 PSO_ATTR_DEPENDENCIES,
1406 NULL
1409 static const char *resultant_pso_computed_attrs[] =
1411 RESULTANT_PSO_COMPUTED_ATTRS,
1412 NULL
1415 static const char *managed_password_computed_attrs[] = {
1416 "msDS-GroupMSAMembership",
1417 "msDS-ManagedPasswordId",
1418 "msDS-ManagedPasswordInterval",
1419 "msDS-ManagedPasswordPreviousId",
1420 "objectClass",
1421 "objectSid",
1422 "whenCreated",
1423 NULL,
1427 a list of attribute names that are hidden, but can be searched for
1428 using another (non-hidden) name to produce the correct result
1430 static const struct op_attributes_replace search_sub[] = {
1431 { "createTimeStamp", "whenCreated", NULL , NULL },
1432 { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
1433 { "structuralObjectClass", "objectClass", NULL , NULL },
1434 { "canonicalName", NULL, NULL , construct_canonical_name },
1435 { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
1436 { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
1437 { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
1438 { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
1439 { "parentGUID", "objectGUID", NULL, construct_parent_guid },
1440 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
1441 { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
1442 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
1443 { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
1444 construct_msds_user_account_control_computed },
1445 { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
1446 construct_msds_user_password_expiry_time_computed },
1447 { "msDS-ResultantPSO", "objectClass", resultant_pso_computed_attrs,
1448 construct_resultant_pso },
1449 {"msDS-ManagedPassword",
1450 NULL,
1451 managed_password_computed_attrs,
1452 constructed_msds_managed_password},
1456 enum op_remove {
1457 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
1458 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
1459 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
1460 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an ad hoc control has been specified */
1464 a list of attributes that may need to be removed from the
1465 underlying db return
1467 Some of these are attributes that were once stored, but are now calculated
1469 struct op_attributes_operations {
1470 const char *attr;
1471 enum op_remove op;
1474 static const struct op_attributes_operations operational_remove[] = {
1475 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
1476 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
1477 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
1478 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
1479 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
1480 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
1485 post process a search result record. For any search_sub[] attributes that were
1486 asked for, we need to call the appropriate copy routine to copy the result
1487 into the message, then remove any attributes that we added to the search but
1488 were not asked for by the user
1490 static int operational_search_post_process(struct ldb_module *module,
1491 struct ldb_message *msg,
1492 enum ldb_scope scope,
1493 const char * const *attrs_from_user,
1494 const char * const *attrs_searched_for,
1495 struct op_controls_flags* controls_flags,
1496 struct op_attributes_operations *list,
1497 unsigned int list_size,
1498 struct op_attributes_replace *list_replace,
1499 unsigned int list_replace_size,
1500 struct ldb_request *parent,
1501 struct ldb_reply *ares)
1503 struct ldb_context *ldb;
1504 unsigned int i, a = 0;
1505 bool constructed_attributes = false;
1507 ldb = ldb_module_get_ctx(module);
1509 /* removed any attrs that should not be shown to the user */
1510 for (i=0; i < list_size; i++) {
1511 ldb_msg_remove_attr(msg, list[i].attr);
1514 for (a=0; a < list_replace_size; a++) {
1515 if (check_keep_control_for_attribute(controls_flags,
1516 list_replace[a].attr)) {
1517 continue;
1520 /* construct the new attribute, using either a supplied
1521 constructor or a simple copy */
1522 constructed_attributes = true;
1523 if (list_replace[a].constructor != NULL) {
1524 if (list_replace[a].constructor(module, msg, scope, parent, ares) != LDB_SUCCESS) {
1525 goto failed;
1527 } else if (ldb_msg_copy_attr(msg,
1528 list_replace[a].replace,
1529 list_replace[a].attr) != LDB_SUCCESS) {
1530 goto failed;
1534 /* Deletion of the search helper attributes are needed if:
1535 * - we generated constructed attributes and
1536 * - we aren't requesting all attributes
1538 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1539 for (i=0; i < list_replace_size; i++) {
1540 /* remove the added search helper attributes, unless
1541 * they were asked for by the user */
1542 if (list_replace[i].replace != NULL &&
1543 !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1544 ldb_msg_remove_attr(msg, list_replace[i].replace);
1546 if (list_replace[i].extra_attrs != NULL) {
1547 unsigned int j;
1548 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1549 if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1550 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1557 return 0;
1559 failed:
1560 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1561 "operational_search_post_process failed for attribute '%s' - %s",
1562 list_replace[a].attr, ldb_errstring(ldb));
1563 return -1;
1567 hook search operations
1570 struct operational_context {
1571 struct ldb_module *module;
1572 struct ldb_request *req;
1573 enum ldb_scope scope;
1574 const char * const *attrs;
1575 struct ldb_parse_tree *tree;
1576 struct op_controls_flags* controls_flags;
1577 struct op_attributes_operations *list_operations;
1578 unsigned int list_operations_size;
1579 struct op_attributes_replace *attrs_to_replace;
1580 unsigned int attrs_to_replace_size;
1583 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1585 struct operational_context *ac;
1586 int ret;
1588 ac = talloc_get_type(req->context, struct operational_context);
1590 if (!ares) {
1591 return ldb_module_done(ac->req, NULL, NULL,
1592 LDB_ERR_OPERATIONS_ERROR);
1594 if (ares->error != LDB_SUCCESS) {
1595 return ldb_module_done(ac->req, ares->controls,
1596 ares->response, ares->error);
1599 switch (ares->type) {
1600 case LDB_REPLY_ENTRY:
1601 /* for each record returned post-process to add any derived
1602 attributes that have been asked for */
1603 ret = operational_search_post_process(ac->module,
1604 ares->message,
1605 ac->scope,
1606 ac->attrs,
1607 req->op.search.attrs,
1608 ac->controls_flags,
1609 ac->list_operations,
1610 ac->list_operations_size,
1611 ac->attrs_to_replace,
1612 ac->attrs_to_replace_size,
1613 req,
1614 ares);
1615 if (ret != 0) {
1616 return ldb_module_done(ac->req, NULL, NULL,
1617 LDB_ERR_OPERATIONS_ERROR);
1619 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1621 case LDB_REPLY_REFERRAL:
1622 return ldb_module_send_referral(ac->req, ares->referral);
1624 case LDB_REPLY_DONE:
1626 return ldb_module_done(ac->req, ares->controls,
1627 ares->response, LDB_SUCCESS);
1630 talloc_free(ares);
1631 return LDB_SUCCESS;
1634 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1635 const char* const* attrs,
1636 const char* const* searched_attrs,
1637 struct op_controls_flags* controls_flags)
1639 int idx = 0;
1640 int i;
1641 struct op_attributes_operations *list = talloc_zero_array(ctx,
1642 struct op_attributes_operations,
1643 ARRAY_SIZE(operational_remove) + 1);
1645 if (list == NULL) {
1646 return NULL;
1649 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1650 switch (operational_remove[i].op) {
1651 case OPERATIONAL_REMOVE_UNASKED:
1652 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1653 continue;
1655 if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1656 continue;
1658 list[idx].attr = operational_remove[i].attr;
1659 list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1660 idx++;
1661 break;
1663 case OPERATIONAL_REMOVE_ALWAYS:
1664 list[idx].attr = operational_remove[i].attr;
1665 list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1666 idx++;
1667 break;
1669 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1670 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1671 list[idx].attr = operational_remove[i].attr;
1672 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1673 idx++;
1675 break;
1677 case OPERATIONAL_SD_FLAGS:
1678 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1679 continue;
1681 if (controls_flags->sd) {
1682 if (attrs == NULL) {
1683 continue;
1685 if (attrs[0] == NULL) {
1686 continue;
1688 if (ldb_attr_in_list(attrs, "*")) {
1689 continue;
1692 list[idx].attr = operational_remove[i].attr;
1693 list[idx].op = OPERATIONAL_SD_FLAGS;
1694 idx++;
1695 break;
1699 return list;
1702 struct operational_present_ctx {
1703 const char *attr;
1704 bool found_operational;
1708 callback to determine if an operational attribute (needing
1709 replacement) is in use at all
1711 static int operational_present(struct ldb_parse_tree *tree, void *private_context)
1713 struct operational_present_ctx *ctx = private_context;
1714 switch (tree->operation) {
1715 case LDB_OP_EQUALITY:
1716 if (ldb_attr_cmp(tree->u.equality.attr, ctx->attr) == 0) {
1717 ctx->found_operational = true;
1719 break;
1720 case LDB_OP_GREATER:
1721 case LDB_OP_LESS:
1722 case LDB_OP_APPROX:
1723 if (ldb_attr_cmp(tree->u.comparison.attr, ctx->attr) == 0) {
1724 ctx->found_operational = true;
1726 break;
1727 case LDB_OP_SUBSTRING:
1728 if (ldb_attr_cmp(tree->u.substring.attr, ctx->attr) == 0) {
1729 ctx->found_operational = true;
1731 break;
1732 case LDB_OP_PRESENT:
1733 if (ldb_attr_cmp(tree->u.present.attr, ctx->attr) == 0) {
1734 ctx->found_operational = true;
1736 break;
1737 case LDB_OP_EXTENDED:
1738 if (tree->u.extended.attr &&
1739 ldb_attr_cmp(tree->u.extended.attr, ctx->attr) == 0) {
1740 ctx->found_operational = true;
1742 break;
1743 default:
1744 break;
1746 return LDB_SUCCESS;
1750 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1752 struct ldb_context *ldb;
1753 struct operational_context *ac;
1754 struct ldb_request *down_req;
1755 const char **search_attrs = NULL;
1756 struct operational_present_ctx ctx;
1757 unsigned int i, a;
1758 int ret;
1760 /* There are no operational attributes on special DNs */
1761 if (ldb_dn_is_special(req->op.search.base)) {
1762 return ldb_next_request(module, req);
1765 ldb = ldb_module_get_ctx(module);
1767 ac = talloc(req, struct operational_context);
1768 if (ac == NULL) {
1769 return ldb_oom(ldb);
1772 ac->module = module;
1773 ac->req = req;
1774 ac->scope = req->op.search.scope;
1775 ac->attrs = req->op.search.attrs;
1777 ctx.found_operational = false;
1780 * find any attributes in the parse tree that are searchable,
1781 * but are stored using a different name in the backend, so we
1782 * only duplicate the memory when needed
1784 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1785 ctx.attr = parse_tree_sub[i].attr;
1787 ldb_parse_tree_walk(req->op.search.tree,
1788 operational_present,
1789 &ctx);
1790 if (ctx.found_operational) {
1791 break;
1795 if (ctx.found_operational) {
1797 ac->tree = ldb_parse_tree_copy_shallow(ac,
1798 req->op.search.tree);
1800 if (ac->tree == NULL) {
1801 return ldb_operr(ldb);
1804 /* replace any attributes in the parse tree that are
1805 searchable, but are stored using a different name in the
1806 backend */
1807 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1808 ldb_parse_tree_attr_replace(ac->tree,
1809 parse_tree_sub[i].attr,
1810 parse_tree_sub[i].replace);
1812 } else {
1813 /* Avoid allocating a copy if we do not need to */
1814 ac->tree = req->op.search.tree;
1817 ac->controls_flags = talloc(ac, struct op_controls_flags);
1818 /* remember if the SD_FLAGS_OID was set */
1819 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1820 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1821 ac->controls_flags->bypassoperational =
1822 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1824 ac->attrs_to_replace = NULL;
1825 ac->attrs_to_replace_size = 0;
1826 /* in the list of attributes we are looking for, rename any
1827 attributes to the alias for any hidden attributes that can
1828 be fetched directly using non-hidden names.
1829 Note that order here can affect performance, e.g. we should process
1830 msDS-ResultantPSO before msDS-User-Account-Control-Computed (as the
1831 latter is also dependent on the PSO information) */
1832 for (a=0;ac->attrs && ac->attrs[a];a++) {
1833 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1834 continue;
1836 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1838 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1839 continue;
1842 ac->attrs_to_replace = talloc_realloc(ac,
1843 ac->attrs_to_replace,
1844 struct op_attributes_replace,
1845 ac->attrs_to_replace_size + 1);
1847 ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1848 ac->attrs_to_replace_size++;
1850 if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1851 unsigned int j;
1852 const char **search_attrs2;
1853 /* Only adds to the end of the list */
1854 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1855 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1856 ? search_attrs
1857 : ac->attrs,
1858 search_sub[i].extra_attrs[j]);
1859 if (search_attrs2 == NULL) {
1860 return ldb_operr(ldb);
1862 /* may be NULL, talloc_free() doesn't mind */
1863 talloc_free(search_attrs);
1864 search_attrs = search_attrs2;
1868 if (!search_sub[i].replace) {
1869 continue;
1872 if (!search_attrs) {
1873 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1874 if (search_attrs == NULL) {
1875 return ldb_operr(ldb);
1878 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1879 search_attrs[a] = search_sub[i].replace;
1882 ac->list_operations = operation_get_op_list(ac, ac->attrs,
1883 search_attrs == NULL?req->op.search.attrs:search_attrs,
1884 ac->controls_flags);
1885 ac->list_operations_size = 0;
1886 i = 0;
1888 while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1889 i++;
1891 ac->list_operations_size = i;
1892 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1893 req->op.search.base,
1894 req->op.search.scope,
1895 ac->tree,
1896 /* use new set of attrs if any */
1897 search_attrs == NULL?req->op.search.attrs:search_attrs,
1898 req->controls,
1899 ac, operational_callback,
1900 req);
1901 LDB_REQ_SET_LOCATION(down_req);
1902 if (ret != LDB_SUCCESS) {
1903 return ldb_operr(ldb);
1906 /* perform the search */
1907 return ldb_next_request(module, down_req);
1910 static int operational_init(struct ldb_module *ctx)
1912 struct operational_data *data;
1913 int ret;
1915 ret = ldb_next_init(ctx);
1917 if (ret != LDB_SUCCESS) {
1918 return ret;
1921 data = talloc_zero(ctx, struct operational_data);
1922 if (!data) {
1923 return ldb_module_oom(ctx);
1926 ldb_module_set_private(ctx, data);
1928 return LDB_SUCCESS;
1931 static const struct ldb_module_ops ldb_operational_module_ops = {
1932 .name = "operational",
1933 .search = operational_search,
1934 .init_context = operational_init
1937 int ldb_operational_module_init(const char *version)
1939 LDB_MODULE_CHECK_VERSION(version);
1940 return ldb_register_module(&ldb_operational_module_ops);