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
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?
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/util.h"
76 #include "auth/auth.h"
79 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
84 struct operational_data {
85 struct ldb_dn *aggregate_dn;
90 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL,
91 TOKEN_GROUPS_NO_GC_ACCEPTABLE,
94 * MS-DRSR 4.1.8.1.3 RevMembGetAccountGroups: Transitive membership in
95 * all account groups in a given domain, excluding built-in groups.
96 * (Used internally for msDS-ResultantPSO support)
101 static int get_pso_for_user(struct ldb_module *module,
102 struct ldb_message *user_msg,
103 struct ldb_request *parent,
104 struct ldb_message **pso_msg);
107 construct a canonical name from a message
109 static int construct_canonical_name(struct ldb_module *module,
110 struct ldb_message *msg, enum ldb_scope scope,
111 struct ldb_request *parent, struct ldb_reply *ares)
114 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
115 if (canonicalName == NULL) {
116 return ldb_operr(ldb_module_get_ctx(module));
118 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
122 construct a primary group token for groups from a message
124 static int construct_primary_group_token(struct ldb_module *module,
125 struct ldb_message *msg, enum ldb_scope scope,
126 struct ldb_request *parent, struct ldb_reply *ares)
128 struct ldb_context *ldb;
129 uint32_t primary_group_token;
131 ldb = ldb_module_get_ctx(module);
132 if (ldb_match_msg_objectclass(msg, "group") == 1) {
134 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
135 if (primary_group_token == 0) {
139 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
140 primary_group_token);
147 * Returns the group SIDs for the user in the given LDB message
149 static int get_group_sids(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
150 struct ldb_message *msg, const char *attribute_string,
151 enum search_type type, struct auth_SidAttr **groupSIDs,
152 uint32_t *num_groupSIDs)
154 const char *filter = NULL;
156 struct dom_sid *primary_group_sid;
157 const char *primary_group_string;
158 const char *primary_group_dn;
159 DATA_BLOB primary_group_blob;
160 struct dom_sid *account_sid;
161 const char *account_sid_string;
162 const char *account_sid_dn;
163 DATA_BLOB account_sid_blob;
164 struct dom_sid *domain_sid;
166 /* If it's not a user, it won't have a primaryGroupID */
167 if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
171 /* Ensure it has an objectSID too */
172 account_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
173 if (account_sid == NULL) {
177 status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
178 if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
179 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
180 } else if (!NT_STATUS_IS_OK(status)) {
181 return LDB_ERR_OPERATIONS_ERROR;
184 primary_group_sid = dom_sid_add_rid(mem_ctx,
186 ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
187 if (!primary_group_sid) {
191 /* only return security groups */
193 case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
194 filter = talloc_asprintf(mem_ctx,
195 "(&(objectClass=group)"
196 "(groupType:"LDB_OID_COMPARATOR_AND":=%u)"
197 "(groupType:"LDB_OID_COMPARATOR_OR":=%u))",
198 GROUP_TYPE_SECURITY_ENABLED,
199 GROUP_TYPE_ACCOUNT_GROUP | GROUP_TYPE_UNIVERSAL_GROUP);
201 case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
203 filter = talloc_asprintf(mem_ctx,
204 "(&(objectClass=group)"
205 "(groupType:"LDB_OID_COMPARATOR_AND":=%u))",
206 GROUP_TYPE_SECURITY_ENABLED);
209 /* for RevMembGetAccountGroups, exclude built-in groups */
211 filter = talloc_asprintf(mem_ctx,
212 "(&(objectClass=group)"
213 "(!(groupType:"LDB_OID_COMPARATOR_AND":=%u))"
214 "(groupType:"LDB_OID_COMPARATOR_AND":=%u))",
215 GROUP_TYPE_BUILTIN_LOCAL_GROUP, GROUP_TYPE_SECURITY_ENABLED);
223 primary_group_string = dom_sid_string(mem_ctx, primary_group_sid);
224 if (!primary_group_string) {
228 primary_group_dn = talloc_asprintf(mem_ctx, "<SID=%s>", primary_group_string);
229 if (!primary_group_dn) {
233 primary_group_blob = data_blob_string_const(primary_group_dn);
235 account_sid_string = dom_sid_string(mem_ctx, account_sid);
236 if (!account_sid_string) {
240 account_sid_dn = talloc_asprintf(mem_ctx, "<SID=%s>", account_sid_string);
241 if (!account_sid_dn) {
245 account_sid_blob = data_blob_string_const(account_sid_dn);
247 status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
248 true, /* We don't want to add the object's SID itself,
249 it's not returned in this attribute */
251 mem_ctx, groupSIDs, num_groupSIDs);
253 if (!NT_STATUS_IS_OK(status)) {
254 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
255 attribute_string, account_sid_string,
257 return LDB_ERR_OPERATIONS_ERROR;
260 /* Expands the primary group - this function takes in
261 * memberOf-like values, so we fake one up with the
262 * <SID=S-...> format of DN and then let it expand
263 * them, as long as they meet the filter - so only
264 * domain groups, not builtin groups
266 status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
267 mem_ctx, groupSIDs, num_groupSIDs);
268 if (!NT_STATUS_IS_OK(status)) {
269 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
270 attribute_string, account_sid_string,
272 return LDB_ERR_OPERATIONS_ERROR;
279 construct the token groups for SAM objects from a message
281 static int construct_generic_token_groups(struct ldb_module *module,
282 struct ldb_message *msg, enum ldb_scope scope,
283 struct ldb_request *parent,
284 const char *attribute_string,
285 enum search_type type)
287 struct ldb_context *ldb = ldb_module_get_ctx(module);
288 TALLOC_CTX *tmp_ctx = talloc_new(msg);
291 struct auth_SidAttr *groupSIDs = NULL;
292 uint32_t num_groupSIDs = 0;
294 if (scope != LDB_SCOPE_BASE) {
295 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
296 return LDB_ERR_OPERATIONS_ERROR;
299 /* calculate the group SIDs for this object */
300 ret = get_group_sids(ldb, tmp_ctx, msg, attribute_string, type,
301 &groupSIDs, &num_groupSIDs);
303 if (ret != LDB_SUCCESS) {
304 talloc_free(tmp_ctx);
305 return LDB_ERR_OPERATIONS_ERROR;
308 /* add these SIDs to the search result */
309 for (i=0; i < num_groupSIDs; i++) {
310 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i].sid);
312 talloc_free(tmp_ctx);
320 static int construct_token_groups(struct ldb_module *module,
321 struct ldb_message *msg, enum ldb_scope scope,
322 struct ldb_request *parent, struct ldb_reply *ares)
325 * TODO: Add in a limiting domain when we start to support
328 return construct_generic_token_groups(module, msg, scope, parent,
333 static int construct_token_groups_no_gc(struct ldb_module *module,
334 struct ldb_message *msg, enum ldb_scope scope,
335 struct ldb_request *parent, struct ldb_reply *ares)
338 * TODO: Add in a limiting domain when we start to support
341 return construct_generic_token_groups(module, msg, scope, parent,
342 "tokenGroupsNoGCAcceptable",
346 static int construct_global_universal_token_groups(struct ldb_module *module,
347 struct ldb_message *msg, enum ldb_scope scope,
348 struct ldb_request *parent, struct ldb_reply *ares)
350 return construct_generic_token_groups(module, msg, scope, parent,
351 "tokenGroupsGlobalAndUniversal",
352 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
355 construct the parent GUID for an entry from a message
357 static int construct_parent_guid(struct ldb_module *module,
358 struct ldb_message *msg, enum ldb_scope scope,
359 struct ldb_request *parent, struct ldb_reply *ares)
361 struct ldb_result *res, *parent_res;
362 const struct ldb_val *parent_guid;
363 const char *attrs[] = { "instanceType", NULL };
364 const char *attrs2[] = { "objectGUID", NULL };
365 uint32_t instanceType;
367 struct ldb_dn *parent_dn;
370 /* determine if the object is NC by instance type */
371 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
372 DSDB_FLAG_NEXT_MODULE |
373 DSDB_SEARCH_SHOW_RECYCLED, parent);
374 if (ret != LDB_SUCCESS) {
378 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
381 if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
382 DEBUG(4,(__location__ ": Object %s is NC\n",
383 ldb_dn_get_linearized(msg->dn)));
386 parent_dn = ldb_dn_get_parent(msg, msg->dn);
388 if (parent_dn == NULL) {
389 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
390 ldb_dn_get_linearized(msg->dn)));
391 return LDB_ERR_OTHER;
393 ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
394 DSDB_FLAG_NEXT_MODULE |
395 DSDB_SEARCH_SHOW_RECYCLED, parent);
396 /* not NC, so the object should have a parent*/
397 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
398 ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
399 talloc_asprintf(msg, "Parent dn %s for %s does not exist",
400 ldb_dn_get_linearized(parent_dn),
401 ldb_dn_get_linearized(msg->dn)));
402 talloc_free(parent_dn);
404 } else if (ret != LDB_SUCCESS) {
405 talloc_free(parent_dn);
408 talloc_free(parent_dn);
410 parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
412 talloc_free(parent_res);
413 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
416 v = data_blob_dup_talloc(parent_res, *parent_guid);
418 talloc_free(parent_res);
419 return ldb_oom(ldb_module_get_ctx(module));
421 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
422 talloc_free(parent_res);
426 static int construct_modifyTimeStamp(struct ldb_module *module,
427 struct ldb_message *msg, enum ldb_scope scope,
428 struct ldb_request *parent, struct ldb_reply *ares)
430 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
431 struct ldb_context *ldb = ldb_module_get_ctx(module);
433 /* We may be being called before the init function has finished */
438 /* Try and set this value up, if possible. Don't worry if it
439 * fails, we may not have the DB set up yet.
441 if (!data->aggregate_dn) {
442 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
445 if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
447 * If we have the DN for the object with common name = Aggregate and
448 * the request is for this DN then let's do the following:
449 * 1) search the object which changedUSN correspond to the one of the loaded
451 * 2) Get the whenChanged attribute
452 * 3) Generate the modifyTimestamp out of the whenChanged attribute
454 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
455 char *value = ldb_timestring(msg, schema->ts_last_change);
458 return ldb_oom(ldb_module_get_ctx(module));
461 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
463 return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
467 construct a subSchemaSubEntry
469 static int construct_subschema_subentry(struct ldb_module *module,
470 struct ldb_message *msg, enum ldb_scope scope,
471 struct ldb_request *parent, struct ldb_reply *ares)
473 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
474 char *subSchemaSubEntry;
476 /* We may be being called before the init function has finished */
481 /* Try and set this value up, if possible. Don't worry if it
482 * fails, we may not have the DB set up yet, and it's not
483 * really vital anyway */
484 if (!data->aggregate_dn) {
485 struct ldb_context *ldb = ldb_module_get_ctx(module);
486 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
489 if (data->aggregate_dn) {
490 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
491 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
497 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
498 struct ldb_message *msg,
499 struct ldb_message_element *object_category)
501 struct ldb_context *ldb;
503 const struct ldb_val *val;
505 ldb = ldb_module_get_ctx(module);
507 DEBUG(4, (__location__ ": Failed to get ldb \n"));
508 return LDB_ERR_OPERATIONS_ERROR;
511 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
513 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
514 (const char *)object_category->values[0].data));
515 return ldb_operr(ldb);
518 val = ldb_dn_get_rdn_val(dn);
520 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
521 ldb_dn_get_linearized(dn)));
522 return ldb_operr(ldb);
525 if (strequal((const char *)val->data, "NTDS-DSA")) {
526 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
528 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
533 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
534 struct ldb_message *msg,
536 struct ldb_request *parent)
538 struct ldb_dn *server_dn;
539 const char *attr_obj_cat[] = { "objectCategory", NULL };
540 struct ldb_result *res;
541 struct ldb_message_element *object_category;
544 server_dn = ldb_dn_copy(msg, dn);
545 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
546 DEBUG(4, (__location__ ": Failed to add child to %s \n",
547 ldb_dn_get_linearized(server_dn)));
548 return ldb_operr(ldb_module_get_ctx(module));
551 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
552 DSDB_FLAG_NEXT_MODULE, parent);
553 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
554 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
555 ldb_dn_get_linearized(server_dn)));
557 } else if (ret != LDB_SUCCESS) {
561 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
562 if (!object_category) {
563 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
564 ldb_dn_get_linearized(res->msgs[0]->dn)));
567 return construct_msds_isrodc_with_dn(module, msg, object_category);
570 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
571 struct ldb_message *msg,
572 struct ldb_request *parent)
575 struct ldb_dn *server_dn;
577 ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
579 if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
580 /* it's OK if we can't find serverReferenceBL attribute */
581 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
582 ldb_dn_get_linearized(msg->dn)));
584 } else if (ret != LDB_SUCCESS) {
588 return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
592 construct msDS-isRODC attr
594 static int construct_msds_isrodc(struct ldb_module *module,
595 struct ldb_message *msg, enum ldb_scope scope,
596 struct ldb_request *parent, struct ldb_reply *ares)
598 struct ldb_message_element * object_class;
599 struct ldb_message_element * object_category;
602 object_class = ldb_msg_find_element(msg, "objectClass");
604 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
605 ldb_dn_get_linearized(msg->dn)));
606 return ldb_operr(ldb_module_get_ctx(module));
609 for (i=0; i<object_class->num_values; i++) {
610 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
611 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
612 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
614 object_category = ldb_msg_find_element(msg, "objectCategory");
615 if (!object_category) {
616 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
617 ldb_dn_get_linearized(msg->dn)));
620 return construct_msds_isrodc_with_dn(module, msg, object_category);
622 if (strequal((const char*)object_class->values[i].data, "server")) {
623 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
624 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
625 * substituting TN for TO.
627 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
629 if (strequal((const char*)object_class->values[i].data, "computer")) {
630 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
631 * rule for the "TO is a server object" case, substituting TS for TO.
633 return construct_msds_isrodc_with_computer_dn(module, msg, parent);
642 construct msDS-keyVersionNumber attr
644 TODO: Make this based on the 'win2k' DS heuristics bit...
647 static int construct_msds_keyversionnumber(struct ldb_module *module,
648 struct ldb_message *msg,
649 enum ldb_scope scope,
650 struct ldb_request *parent,
651 struct ldb_reply *ares)
654 enum ndr_err_code ndr_err;
655 const struct ldb_val *omd_value;
656 struct replPropertyMetaDataBlob *omd;
659 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
661 /* We can't make up a key version number without meta data */
665 omd = talloc(msg, struct replPropertyMetaDataBlob);
667 ldb_module_oom(module);
671 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
672 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
673 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
674 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
675 ldb_dn_get_linearized(msg->dn)));
676 return ldb_operr(ldb_module_get_ctx(module));
679 if (omd->version != 1) {
680 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
681 omd->version, ldb_dn_get_linearized(msg->dn)));
685 for (i=0; i<omd->ctr.ctr1.count; i++) {
686 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
687 ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
689 "msDS-KeyVersionNumber",
690 omd->ctr.ctr1.array[i].version);
691 if (ret != LDB_SUCCESS) {
702 #define _UF_TRUST_ACCOUNTS ( \
703 UF_WORKSTATION_TRUST_ACCOUNT | \
704 UF_SERVER_TRUST_ACCOUNT | \
705 UF_INTERDOMAIN_TRUST_ACCOUNT \
707 #define _UF_NO_EXPIRY_ACCOUNTS ( \
708 UF_SMARTCARD_REQUIRED | \
709 UF_DONT_EXPIRE_PASSWD | \
715 * Returns the Effective-MaximumPasswordAge for a user
717 static int64_t get_user_max_pwd_age(struct ldb_module *module,
718 struct ldb_message *user_msg,
719 struct ldb_request *parent,
720 struct ldb_dn *nc_root)
723 struct ldb_message *pso = NULL;
724 struct ldb_context *ldb = ldb_module_get_ctx(module);
726 /* if a PSO applies to the user, use its maxPwdAge */
727 ret = get_pso_for_user(module, user_msg, parent, &pso);
728 if (ret != LDB_SUCCESS) {
730 /* log the error, but fallback to the domain default */
731 DBG_ERR("Error retrieving PSO for %s\n",
732 ldb_dn_get_linearized(user_msg->dn));
736 return ldb_msg_find_attr_as_int64(pso,
737 "msDS-MaximumPasswordAge", 0);
740 /* otherwise return the default domain value */
741 return samdb_search_int64(ldb, user_msg, 0, nc_root, "maxPwdAge", NULL);
745 calculate msDS-UserPasswordExpiryTimeComputed
747 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
748 struct ldb_message *msg,
749 struct ldb_request *parent,
750 struct ldb_dn *domain_dn)
752 int64_t pwdLastSet, maxPwdAge;
753 uint32_t userAccountControl;
756 userAccountControl = ldb_msg_find_attr_as_uint(msg,
757 "userAccountControl",
759 if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
763 pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
764 if (pwdLastSet == 0) {
768 if (pwdLastSet <= -1) {
770 * This can't really happen...
775 if (pwdLastSet >= INT64_MAX) {
777 * Somethings wrong with the clock...
783 * Note that maxPwdAge is a stored as negative value.
785 * Possible values are in the range of:
787 * maxPwdAge: -864000000001
789 * maxPwdAge: -9223372036854775808 (INT64_MIN)
792 maxPwdAge = get_user_max_pwd_age(module, msg, parent, domain_dn);
793 if (maxPwdAge >= -864000000000) {
795 * This is not really possible...
800 if (maxPwdAge == INT64_MIN) {
805 * Note we already caught maxPwdAge == INT64_MIN
806 * and pwdLastSet >= INT64_MAX above.
808 * Remember maxPwdAge is a negative number,
809 * so it results in the following.
811 * 0x7FFFFFFFFFFFFFFEULL + INT64_MAX
813 * 0xFFFFFFFFFFFFFFFDULL
815 * or to put it another way, adding two numbers less than 1<<63 can't
816 * ever be more than 1<<64, therefore this result can't wrap.
818 ret = (NTTIME)pwdLastSet - (NTTIME)maxPwdAge;
819 if (ret >= INT64_MAX) {
827 * Returns the Effective-LockoutDuration for a user
829 static int64_t get_user_lockout_duration(struct ldb_module *module,
830 struct ldb_message *user_msg,
831 struct ldb_request *parent,
832 struct ldb_dn *nc_root)
835 struct ldb_message *pso = NULL;
836 struct ldb_context *ldb = ldb_module_get_ctx(module);
838 /* if a PSO applies to the user, use its lockoutDuration */
839 ret = get_pso_for_user(module, user_msg, parent, &pso);
840 if (ret != LDB_SUCCESS) {
842 /* log the error, but fallback to the domain default */
843 DBG_ERR("Error retrieving PSO for %s\n",
844 ldb_dn_get_linearized(user_msg->dn));
848 return ldb_msg_find_attr_as_int64(pso,
849 "msDS-LockoutDuration", 0);
852 /* otherwise return the default domain value */
853 return samdb_search_int64(ldb, user_msg, 0, nc_root, "lockoutDuration",
858 construct msDS-User-Account-Control-Computed attr
860 static int construct_msds_user_account_control_computed(struct ldb_module *module,
861 struct ldb_message *msg, enum ldb_scope scope,
862 struct ldb_request *parent, struct ldb_reply *ares)
864 uint32_t userAccountControl;
865 uint32_t msDS_User_Account_Control_Computed = 0;
866 struct ldb_context *ldb = ldb_module_get_ctx(module);
868 struct ldb_dn *nc_root;
871 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
873 ldb_asprintf_errstring(ldb,
874 "Failed to find NC root of DN: %s: %s",
875 ldb_dn_get_linearized(msg->dn),
876 ldb_errstring(ldb_module_get_ctx(module)));
879 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
880 /* Only calculate this on our default NC */
883 /* Test account expire time */
884 unix_to_nt_time(&now, time(NULL));
886 userAccountControl = ldb_msg_find_attr_as_uint(msg,
887 "userAccountControl",
889 if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
891 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
892 if (lockoutTime != 0) {
893 int64_t lockoutDuration;
895 lockoutDuration = get_user_lockout_duration(module, msg,
899 /* zero locks out until the administrator intervenes */
900 if (lockoutDuration >= 0) {
901 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
902 } else if (lockoutTime - lockoutDuration >= now) {
903 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
908 if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
909 NTTIME must_change_time
910 = get_msds_user_password_expiry_time_computed(module,
914 /* check for expired password */
915 if (must_change_time < now) {
916 msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
920 return samdb_msg_add_int64(ldb,
922 "msDS-User-Account-Control-Computed",
923 msDS_User_Account_Control_Computed);
927 construct msDS-UserPasswordExpiryTimeComputed
929 static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
930 struct ldb_message *msg, enum ldb_scope scope,
931 struct ldb_request *parent, struct ldb_reply *ares)
933 struct ldb_context *ldb = ldb_module_get_ctx(module);
934 struct ldb_dn *nc_root;
935 int64_t password_expiry_time;
938 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
940 ldb_asprintf_errstring(ldb,
941 "Failed to find NC root of DN: %s: %s",
942 ldb_dn_get_linearized(msg->dn),
947 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
948 /* Only calculate this on our default NC */
953 = get_msds_user_password_expiry_time_computed(module, msg,
956 return samdb_msg_add_int64(ldb,
958 "msDS-UserPasswordExpiryTimeComputed",
959 password_expiry_time);
963 * Checks whether the msDS-ResultantPSO attribute is supported for a given
964 * user object. As per MS-ADTS, section 3.1.1.4.5.36 msDS-ResultantPSO.
966 static bool pso_is_supported(struct ldb_context *ldb, struct ldb_message *msg)
968 int functional_level;
972 functional_level = dsdb_functional_level(ldb);
973 if (functional_level < DS_DOMAIN_FUNCTION_2008) {
977 /* msDS-ResultantPSO is only supported for user objects */
978 if (!ldb_match_msg_objectclass(msg, "user")) {
982 /* ...and only if the ADS_UF_NORMAL_ACCOUNT bit is set */
983 uac = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
984 if (!(uac & UF_NORMAL_ACCOUNT)) {
988 /* skip it if it's the special KRBTGT default account */
989 user_rid = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
990 if (user_rid == DOMAIN_RID_KRBTGT) {
994 /* ...or if it's a special KRBTGT account for an RODC KDC */
995 if (ldb_msg_find_ldb_val(msg, "msDS-SecondaryKrbTgtNumber") != NULL) {
1003 * Returns the number of PSO objects that exist in the DB
1005 static int get_pso_count(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1006 struct ldb_request *parent, int *pso_count)
1008 static const char * const attrs[] = { NULL };
1010 struct ldb_dn *psc_dn = NULL;
1011 struct ldb_result *res = NULL;
1012 struct ldb_context *ldb = ldb_module_get_ctx(module);
1016 psc_dn = samdb_system_container_dn(ldb, mem_ctx);
1017 if (psc_dn == NULL) {
1018 return ldb_oom(ldb);
1020 psc_ok = ldb_dn_add_child_fmt(psc_dn, "CN=Password Settings Container");
1021 if (psc_ok == false) {
1022 return ldb_oom(ldb);
1025 /* get the number of PSO children */
1026 ret = dsdb_module_search(module, mem_ctx, &res, psc_dn,
1027 LDB_SCOPE_ONELEVEL, attrs,
1028 DSDB_FLAG_NEXT_MODULE, parent,
1029 "(objectClass=msDS-PasswordSettings)");
1032 * Just ignore PSOs if the container doesn't exist. This is a weird
1033 * corner-case where the AD DB was created from a pre-2008 base schema,
1034 * and then the FL was manually upgraded.
1036 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
1037 DBG_NOTICE("No Password Settings Container exists\n");
1041 if (ret != LDB_SUCCESS) {
1045 *pso_count = res->count;
1047 talloc_free(psc_dn);
1053 * Compares two PSO objects returned by a search, to work out the better PSO.
1054 * The PSO with the lowest precedence is better, otherwise (if the precedence
1055 * is equal) the PSO with the lower GUID wins.
1057 static int pso_compare(struct ldb_message **m1, struct ldb_message **m2)
1062 prec1 = ldb_msg_find_attr_as_uint(*m1, "msDS-PasswordSettingsPrecedence",
1064 prec2 = ldb_msg_find_attr_as_uint(*m2, "msDS-PasswordSettingsPrecedence",
1067 /* if precedence is equal, use the lowest GUID */
1068 if (prec1 == prec2) {
1069 struct GUID guid1 = samdb_result_guid(*m1, "objectGUID");
1070 struct GUID guid2 = samdb_result_guid(*m2, "objectGUID");
1072 return ndr_guid_compare(&guid1, &guid2);
1074 return NUMERIC_CMP(prec1, prec2);
1079 * Search for PSO objects that apply to the object SIDs specified
1081 static int pso_search_by_sids(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1082 struct ldb_request *parent,
1083 struct auth_SidAttr *sid_array, unsigned int num_sids,
1084 struct ldb_result **result)
1088 struct ldb_context *ldb = ldb_module_get_ctx(module);
1089 char *sid_filter = NULL;
1090 struct ldb_dn *psc_dn = NULL;
1092 const char *attrs[] = {
1093 "msDS-PasswordSettingsPrecedence",
1095 "msDS-LockoutDuration",
1096 "msDS-MaximumPasswordAge",
1100 /* build a query for PSO objects that apply to any of the SIDs given */
1101 sid_filter = talloc_strdup(mem_ctx, "");
1102 if (sid_filter == NULL) {
1103 return ldb_oom(ldb);
1106 for (i = 0; sid_filter && i < num_sids; i++) {
1107 struct dom_sid_buf sid_buf;
1109 sid_filter = talloc_asprintf_append(
1111 "(msDS-PSOAppliesTo=<SID=%s>)",
1112 dom_sid_str_buf(&sid_array[i].sid, &sid_buf));
1113 if (sid_filter == NULL) {
1114 return ldb_oom(ldb);
1118 /* only PSOs located in the Password Settings Container are valid */
1119 psc_dn = samdb_system_container_dn(ldb, mem_ctx);
1120 if (psc_dn == NULL) {
1121 return ldb_oom(ldb);
1123 psc_ok = ldb_dn_add_child_fmt(psc_dn, "CN=Password Settings Container");
1124 if (psc_ok == false) {
1125 return ldb_oom(ldb);
1128 ret = dsdb_module_search(module, mem_ctx, result, psc_dn,
1129 LDB_SCOPE_ONELEVEL, attrs,
1130 DSDB_FLAG_NEXT_MODULE, parent,
1131 "(&(objectClass=msDS-PasswordSettings)(|%s))",
1133 talloc_free(sid_filter);
1138 * Returns the best PSO object that applies to the object SID(s) specified
1140 static int pso_find_best(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1141 struct ldb_request *parent, struct auth_SidAttr *sid_array,
1142 unsigned int num_sids, struct ldb_message **best_pso)
1144 struct ldb_result *res = NULL;
1149 /* find any PSOs that apply to the SIDs specified */
1150 ret = pso_search_by_sids(module, mem_ctx, parent, sid_array, num_sids,
1152 if (ret != LDB_SUCCESS) {
1153 DBG_ERR("Error %d retrieving PSO for SID(s)\n", ret);
1157 /* sort the list so that the best PSO is first */
1158 TYPESAFE_QSORT(res->msgs, res->count, pso_compare);
1160 if (res->count > 0) {
1161 *best_pso = res->msgs[0];
1168 * Determines the Password Settings Object (PSO) that applies to the given user
1170 static int get_pso_for_user(struct ldb_module *module,
1171 struct ldb_message *user_msg,
1172 struct ldb_request *parent,
1173 struct ldb_message **pso_msg)
1176 struct auth_SidAttr *groupSIDs = NULL;
1177 uint32_t num_groupSIDs = 0;
1178 struct ldb_context *ldb = ldb_module_get_ctx(module);
1179 struct ldb_message *best_pso = NULL;
1180 struct ldb_dn *pso_dn = NULL;
1182 struct ldb_message_element *el = NULL;
1183 TALLOC_CTX *tmp_ctx = NULL;
1185 struct ldb_result *res = NULL;
1186 static const char *attrs[] = {
1187 "msDS-LockoutDuration",
1188 "msDS-MaximumPasswordAge",
1194 /* first, check msDS-ResultantPSO is supported for this object */
1195 pso_supported = pso_is_supported(ldb, user_msg);
1197 if (!pso_supported) {
1201 tmp_ctx = talloc_new(user_msg);
1204 * Several different constructed attributes try to use the PSO info. If
1205 * we've already constructed the msDS-ResultantPSO for this user, we can
1206 * just re-use the result, rather than calculating it from scratch again
1208 pso_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, user_msg,
1209 "msDS-ResultantPSO");
1211 if (pso_dn != NULL) {
1212 ret = dsdb_module_search_dn(module, tmp_ctx, &res, pso_dn,
1213 attrs, DSDB_FLAG_NEXT_MODULE,
1215 if (ret != LDB_SUCCESS) {
1216 DBG_ERR("Error %d retrieving PSO %s\n", ret,
1217 ldb_dn_get_linearized(pso_dn));
1218 talloc_free(tmp_ctx);
1222 if (res->count == 1) {
1223 *pso_msg = res->msgs[0];
1229 * if any PSOs apply directly to the user, they are considered first
1230 * before we check group membership PSOs
1232 el = ldb_msg_find_element(user_msg, "msDS-PSOApplied");
1234 if (el != NULL && el->num_values > 0) {
1235 struct auth_SidAttr *user_sid = NULL;
1237 /* lookup the best PSO object, based on the user's SID */
1238 user_sid = samdb_result_dom_sid_attrs(
1239 tmp_ctx, user_msg, "objectSid",
1240 SE_GROUP_DEFAULT_FLAGS);
1242 ret = pso_find_best(module, tmp_ctx, parent, user_sid, 1,
1244 if (ret != LDB_SUCCESS) {
1245 talloc_free(tmp_ctx);
1249 if (best_pso != NULL) {
1250 *pso_msg = best_pso;
1256 * If no valid PSO applies directly to the user, then try its groups.
1257 * The group expansion is expensive, so check there are actually
1258 * PSOs in the DB first (which is a quick search). Note in the above
1259 * cases we could tell that a PSO applied to the user, based on info
1260 * already retrieved by the user search.
1262 ret = get_pso_count(module, tmp_ctx, parent, &pso_count);
1263 if (ret != LDB_SUCCESS) {
1264 DBG_ERR("Error %d determining PSOs in system\n", ret);
1265 talloc_free(tmp_ctx);
1269 if (pso_count == 0) {
1270 talloc_free(tmp_ctx);
1274 /* Work out the SIDs of any account groups the user is a member of */
1275 ret = get_group_sids(ldb, tmp_ctx, user_msg,
1276 "msDS-ResultantPSO", ACCOUNT_GROUPS,
1277 &groupSIDs, &num_groupSIDs);
1278 if (ret != LDB_SUCCESS) {
1279 DBG_ERR("Error %d determining group SIDs for %s\n", ret,
1280 ldb_dn_get_linearized(user_msg->dn));
1281 talloc_free(tmp_ctx);
1285 /* lookup the best PSO that applies to any of these groups */
1286 ret = pso_find_best(module, tmp_ctx, parent, groupSIDs,
1287 num_groupSIDs, &best_pso);
1288 if (ret != LDB_SUCCESS) {
1289 talloc_free(tmp_ctx);
1293 *pso_msg = best_pso;
1298 * Constructs the msDS-ResultantPSO attribute, which is the DN of the Password
1299 * Settings Object (PSO) that applies to that user.
1301 static int construct_resultant_pso(struct ldb_module *module,
1302 struct ldb_message *msg,
1303 enum ldb_scope scope,
1304 struct ldb_request *parent,
1305 struct ldb_reply *ares)
1307 struct ldb_message *pso = NULL;
1310 /* work out the PSO (if any) that applies to this user */
1311 ret = get_pso_for_user(module, msg, parent, &pso);
1312 if (ret != LDB_SUCCESS) {
1313 DBG_ERR("Couldn't determine PSO for %s\n",
1314 ldb_dn_get_linearized(msg->dn));
1319 DBG_INFO("%s is resultant PSO for user %s\n",
1320 ldb_dn_get_linearized(pso->dn),
1321 ldb_dn_get_linearized(msg->dn));
1322 return ldb_msg_add_string(msg, "msDS-ResultantPSO",
1323 ldb_dn_get_linearized(pso->dn));
1326 /* no PSO applies to this user */
1330 struct op_controls_flags {
1332 bool bypassoperational;
1335 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
1336 if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
1343 a list of attribute names that should be substituted in the parse
1344 tree before the search is done
1346 static const struct {
1348 const char *replace;
1349 } parse_tree_sub[] = {
1350 { "createTimeStamp", "whenCreated" },
1351 { "modifyTimeStamp", "whenChanged" }
1355 struct op_attributes_replace {
1357 const char *replace;
1358 const char * const *extra_attrs;
1359 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *, struct ldb_reply *);
1362 /* the 'extra_attrs' required for msDS-ResultantPSO */
1363 #define RESULTANT_PSO_COMPUTED_ATTRS \
1364 "msDS-PSOApplied", \
1365 "userAccountControl", \
1367 "msDS-SecondaryKrbTgtNumber", \
1371 * any other constructed attributes that want to work out the PSO also need to
1372 * include objectClass (this gets included via 'replace' for msDS-ResultantPSO)
1374 #define PSO_ATTR_DEPENDENCIES \
1375 RESULTANT_PSO_COMPUTED_ATTRS, \
1378 static const char *objectSid_attr[] =
1385 static const char *objectCategory_attr[] =
1392 static const char *user_account_control_computed_attrs[] =
1396 PSO_ATTR_DEPENDENCIES,
1401 static const char *user_password_expiry_time_computed_attrs[] =
1404 PSO_ATTR_DEPENDENCIES,
1408 static const char *resultant_pso_computed_attrs[] =
1410 RESULTANT_PSO_COMPUTED_ATTRS,
1415 a list of attribute names that are hidden, but can be searched for
1416 using another (non-hidden) name to produce the correct result
1418 static const struct op_attributes_replace search_sub[] = {
1419 { "createTimeStamp", "whenCreated", NULL , NULL },
1420 { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
1421 { "structuralObjectClass", "objectClass", NULL , NULL },
1422 { "canonicalName", NULL, NULL , construct_canonical_name },
1423 { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
1424 { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
1425 { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
1426 { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
1427 { "parentGUID", "objectGUID", NULL, construct_parent_guid },
1428 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
1429 { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
1430 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
1431 { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
1432 construct_msds_user_account_control_computed },
1433 { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
1434 construct_msds_user_password_expiry_time_computed },
1435 { "msDS-ResultantPSO", "objectClass", resultant_pso_computed_attrs,
1436 construct_resultant_pso }
1441 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
1442 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
1443 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
1444 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an ad hoc control has been specified */
1448 a list of attributes that may need to be removed from the
1449 underlying db return
1451 Some of these are attributes that were once stored, but are now calculated
1453 struct op_attributes_operations {
1458 static const struct op_attributes_operations operational_remove[] = {
1459 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
1460 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
1461 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
1462 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
1463 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
1464 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
1469 post process a search result record. For any search_sub[] attributes that were
1470 asked for, we need to call the appropriate copy routine to copy the result
1471 into the message, then remove any attributes that we added to the search but
1472 were not asked for by the user
1474 static int operational_search_post_process(struct ldb_module *module,
1475 struct ldb_message *msg,
1476 enum ldb_scope scope,
1477 const char * const *attrs_from_user,
1478 const char * const *attrs_searched_for,
1479 struct op_controls_flags* controls_flags,
1480 struct op_attributes_operations *list,
1481 unsigned int list_size,
1482 struct op_attributes_replace *list_replace,
1483 unsigned int list_replace_size,
1484 struct ldb_request *parent,
1485 struct ldb_reply *ares)
1487 struct ldb_context *ldb;
1488 unsigned int i, a = 0;
1489 bool constructed_attributes = false;
1491 ldb = ldb_module_get_ctx(module);
1493 /* removed any attrs that should not be shown to the user */
1494 for (i=0; i < list_size; i++) {
1495 ldb_msg_remove_attr(msg, list[i].attr);
1498 for (a=0; a < list_replace_size; a++) {
1499 if (check_keep_control_for_attribute(controls_flags,
1500 list_replace[a].attr)) {
1504 /* construct the new attribute, using either a supplied
1505 constructor or a simple copy */
1506 constructed_attributes = true;
1507 if (list_replace[a].constructor != NULL) {
1508 if (list_replace[a].constructor(module, msg, scope, parent, ares) != LDB_SUCCESS) {
1511 } else if (ldb_msg_copy_attr(msg,
1512 list_replace[a].replace,
1513 list_replace[a].attr) != LDB_SUCCESS) {
1518 /* Deletion of the search helper attributes are needed if:
1519 * - we generated constructed attributes and
1520 * - we aren't requesting all attributes
1522 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1523 for (i=0; i < list_replace_size; i++) {
1524 /* remove the added search helper attributes, unless
1525 * they were asked for by the user */
1526 if (list_replace[i].replace != NULL &&
1527 !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1528 ldb_msg_remove_attr(msg, list_replace[i].replace);
1530 if (list_replace[i].extra_attrs != NULL) {
1532 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1533 if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1534 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1544 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1545 "operational_search_post_process failed for attribute '%s' - %s",
1546 list_replace[a].attr, ldb_errstring(ldb));
1551 hook search operations
1554 struct operational_context {
1555 struct ldb_module *module;
1556 struct ldb_request *req;
1557 enum ldb_scope scope;
1558 const char * const *attrs;
1559 struct ldb_parse_tree *tree;
1560 struct op_controls_flags* controls_flags;
1561 struct op_attributes_operations *list_operations;
1562 unsigned int list_operations_size;
1563 struct op_attributes_replace *attrs_to_replace;
1564 unsigned int attrs_to_replace_size;
1567 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1569 struct operational_context *ac;
1572 ac = talloc_get_type(req->context, struct operational_context);
1575 return ldb_module_done(ac->req, NULL, NULL,
1576 LDB_ERR_OPERATIONS_ERROR);
1578 if (ares->error != LDB_SUCCESS) {
1579 return ldb_module_done(ac->req, ares->controls,
1580 ares->response, ares->error);
1583 switch (ares->type) {
1584 case LDB_REPLY_ENTRY:
1585 /* for each record returned post-process to add any derived
1586 attributes that have been asked for */
1587 ret = operational_search_post_process(ac->module,
1591 req->op.search.attrs,
1593 ac->list_operations,
1594 ac->list_operations_size,
1595 ac->attrs_to_replace,
1596 ac->attrs_to_replace_size,
1600 return ldb_module_done(ac->req, NULL, NULL,
1601 LDB_ERR_OPERATIONS_ERROR);
1603 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1605 case LDB_REPLY_REFERRAL:
1606 return ldb_module_send_referral(ac->req, ares->referral);
1608 case LDB_REPLY_DONE:
1610 return ldb_module_done(ac->req, ares->controls,
1611 ares->response, LDB_SUCCESS);
1618 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1619 const char* const* attrs,
1620 const char* const* searched_attrs,
1621 struct op_controls_flags* controls_flags)
1625 struct op_attributes_operations *list = talloc_zero_array(ctx,
1626 struct op_attributes_operations,
1627 ARRAY_SIZE(operational_remove) + 1);
1633 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1634 switch (operational_remove[i].op) {
1635 case OPERATIONAL_REMOVE_UNASKED:
1636 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1639 if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1642 list[idx].attr = operational_remove[i].attr;
1643 list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1647 case OPERATIONAL_REMOVE_ALWAYS:
1648 list[idx].attr = operational_remove[i].attr;
1649 list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1653 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1654 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1655 list[idx].attr = operational_remove[i].attr;
1656 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1661 case OPERATIONAL_SD_FLAGS:
1662 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1665 if (controls_flags->sd) {
1666 if (attrs == NULL) {
1669 if (attrs[0] == NULL) {
1672 if (ldb_attr_in_list(attrs, "*")) {
1676 list[idx].attr = operational_remove[i].attr;
1677 list[idx].op = OPERATIONAL_SD_FLAGS;
1686 struct operational_present_ctx {
1688 bool found_operational;
1692 callback to determine if an operational attribute (needing
1693 replacement) is in use at all
1695 static int operational_present(struct ldb_parse_tree *tree, void *private_context)
1697 struct operational_present_ctx *ctx = private_context;
1698 switch (tree->operation) {
1699 case LDB_OP_EQUALITY:
1700 if (ldb_attr_cmp(tree->u.equality.attr, ctx->attr) == 0) {
1701 ctx->found_operational = true;
1704 case LDB_OP_GREATER:
1707 if (ldb_attr_cmp(tree->u.comparison.attr, ctx->attr) == 0) {
1708 ctx->found_operational = true;
1711 case LDB_OP_SUBSTRING:
1712 if (ldb_attr_cmp(tree->u.substring.attr, ctx->attr) == 0) {
1713 ctx->found_operational = true;
1716 case LDB_OP_PRESENT:
1717 if (ldb_attr_cmp(tree->u.present.attr, ctx->attr) == 0) {
1718 ctx->found_operational = true;
1721 case LDB_OP_EXTENDED:
1722 if (tree->u.extended.attr &&
1723 ldb_attr_cmp(tree->u.extended.attr, ctx->attr) == 0) {
1724 ctx->found_operational = true;
1734 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1736 struct ldb_context *ldb;
1737 struct operational_context *ac;
1738 struct ldb_request *down_req;
1739 const char **search_attrs = NULL;
1740 struct operational_present_ctx ctx;
1744 /* There are no operational attributes on special DNs */
1745 if (ldb_dn_is_special(req->op.search.base)) {
1746 return ldb_next_request(module, req);
1749 ldb = ldb_module_get_ctx(module);
1751 ac = talloc(req, struct operational_context);
1753 return ldb_oom(ldb);
1756 ac->module = module;
1758 ac->scope = req->op.search.scope;
1759 ac->attrs = req->op.search.attrs;
1761 ctx.found_operational = false;
1764 * find any attributes in the parse tree that are searchable,
1765 * but are stored using a different name in the backend, so we
1766 * only duplicate the memory when needed
1768 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1769 ctx.attr = parse_tree_sub[i].attr;
1771 ldb_parse_tree_walk(req->op.search.tree,
1772 operational_present,
1774 if (ctx.found_operational) {
1779 if (ctx.found_operational) {
1781 ac->tree = ldb_parse_tree_copy_shallow(ac,
1782 req->op.search.tree);
1784 if (ac->tree == NULL) {
1785 return ldb_operr(ldb);
1788 /* replace any attributes in the parse tree that are
1789 searchable, but are stored using a different name in the
1791 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1792 ldb_parse_tree_attr_replace(ac->tree,
1793 parse_tree_sub[i].attr,
1794 parse_tree_sub[i].replace);
1797 /* Avoid allocating a copy if we do not need to */
1798 ac->tree = req->op.search.tree;
1801 ac->controls_flags = talloc(ac, struct op_controls_flags);
1802 /* remember if the SD_FLAGS_OID was set */
1803 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1804 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1805 ac->controls_flags->bypassoperational =
1806 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1808 ac->attrs_to_replace = NULL;
1809 ac->attrs_to_replace_size = 0;
1810 /* in the list of attributes we are looking for, rename any
1811 attributes to the alias for any hidden attributes that can
1812 be fetched directly using non-hidden names.
1813 Note that order here can affect performance, e.g. we should process
1814 msDS-ResultantPSO before msDS-User-Account-Control-Computed (as the
1815 latter is also dependent on the PSO information) */
1816 for (a=0;ac->attrs && ac->attrs[a];a++) {
1817 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1820 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1822 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1826 ac->attrs_to_replace = talloc_realloc(ac,
1827 ac->attrs_to_replace,
1828 struct op_attributes_replace,
1829 ac->attrs_to_replace_size + 1);
1831 ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1832 ac->attrs_to_replace_size++;
1834 if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1836 const char **search_attrs2;
1837 /* Only adds to the end of the list */
1838 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1839 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1842 search_sub[i].extra_attrs[j]);
1843 if (search_attrs2 == NULL) {
1844 return ldb_operr(ldb);
1846 /* may be NULL, talloc_free() doesn't mind */
1847 talloc_free(search_attrs);
1848 search_attrs = search_attrs2;
1852 if (!search_sub[i].replace) {
1856 if (!search_attrs) {
1857 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1858 if (search_attrs == NULL) {
1859 return ldb_operr(ldb);
1862 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1863 search_attrs[a] = search_sub[i].replace;
1866 ac->list_operations = operation_get_op_list(ac, ac->attrs,
1867 search_attrs == NULL?req->op.search.attrs:search_attrs,
1868 ac->controls_flags);
1869 ac->list_operations_size = 0;
1872 while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1875 ac->list_operations_size = i;
1876 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1877 req->op.search.base,
1878 req->op.search.scope,
1880 /* use new set of attrs if any */
1881 search_attrs == NULL?req->op.search.attrs:search_attrs,
1883 ac, operational_callback,
1885 LDB_REQ_SET_LOCATION(down_req);
1886 if (ret != LDB_SUCCESS) {
1887 return ldb_operr(ldb);
1890 /* perform the search */
1891 return ldb_next_request(module, down_req);
1894 static int operational_init(struct ldb_module *ctx)
1896 struct operational_data *data;
1899 ret = ldb_next_init(ctx);
1901 if (ret != LDB_SUCCESS) {
1905 data = talloc_zero(ctx, struct operational_data);
1907 return ldb_module_oom(ctx);
1910 ldb_module_set_private(ctx, data);
1915 static const struct ldb_module_ops ldb_operational_module_ops = {
1916 .name = "operational",
1917 .search = operational_search,
1918 .init_context = operational_init
1921 int ldb_operational_module_init(const char *version)
1923 LDB_MODULE_CHECK_VERSION(version);
1924 return ldb_register_module(&ldb_operational_module_ops);