summaryrefslogtreecommitdiffstats
path: root/source4/dsdb/samdb/ldb_modules/operational.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/dsdb/samdb/ldb_modules/operational.c')
-rw-r--r--source4/dsdb/samdb/ldb_modules/operational.c1920
1 files changed, 1920 insertions, 0 deletions
diff --git a/source4/dsdb/samdb/ldb_modules/operational.c b/source4/dsdb/samdb/ldb_modules/operational.c
new file mode 100644
index 0000000..1317b58
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/operational.c
@@ -0,0 +1,1920 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2010
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Simo Sorce 2006-2008
+ Copyright (C) Matthias Dieter Wallnöfer 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ handle operational attributes
+ */
+
+/*
+ createTimeStamp: HIDDEN, searchable, ldaptime, alias for whenCreated
+ modifyTimeStamp: HIDDEN, searchable, ldaptime, alias for whenChanged
+
+ for the above two, we do the search as normal, and if
+ createTimeStamp or modifyTimeStamp is asked for, then do
+ additional searches for whenCreated and whenChanged and fill in
+ the resulting values
+
+ we also need to replace these with the whenCreated/whenChanged
+ equivalent in the search expression trees
+
+ whenCreated: not-HIDDEN, CONSTRUCTED, SEARCHABLE
+ whenChanged: not-HIDDEN, CONSTRUCTED, SEARCHABLE
+
+ on init we need to setup attribute handlers for these so
+ comparisons are done correctly. The resolution is 1 second.
+
+ on add we need to add both the above, for current time
+
+ on modify we need to change whenChanged
+
+ structuralObjectClass: HIDDEN, CONSTRUCTED, not-searchable. always same as objectclass?
+
+ for this one we do the search as normal, then if requested ask
+ for objectclass, change the attribute name, and add it
+
+ primaryGroupToken: HIDDEN, CONSTRUCTED, SEARCHABLE
+
+ contains the RID of a certain group object
+
+
+ attributeTypes: in schema only
+ objectClasses: in schema only
+ matchingRules: in schema only
+ matchingRuleUse: in schema only
+ creatorsName: not supported by w2k3?
+ modifiersName: not supported by w2k3?
+*/
+
+#include "includes.h"
+#include <ldb.h>
+#include <ldb_module.h>
+
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+#include "auth/auth.h"
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
+#endif
+
+#undef strcasecmp
+
+struct operational_data {
+ struct ldb_dn *aggregate_dn;
+};
+
+enum search_type {
+ TOKEN_GROUPS,
+ TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL,
+ TOKEN_GROUPS_NO_GC_ACCEPTABLE,
+
+ /*
+ * MS-DRSR 4.1.8.1.3 RevMembGetAccountGroups: Transitive membership in
+ * all account groups in a given domain, excluding built-in groups.
+ * (Used internally for msDS-ResultantPSO support)
+ */
+ ACCOUNT_GROUPS
+};
+
+static int get_pso_for_user(struct ldb_module *module,
+ struct ldb_message *user_msg,
+ struct ldb_request *parent,
+ struct ldb_message **pso_msg);
+
+/*
+ construct a canonical name from a message
+*/
+static int construct_canonical_name(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ char *canonicalName;
+ canonicalName = ldb_dn_canonical_string(msg, msg->dn);
+ if (canonicalName == NULL) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+ return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
+}
+
+/*
+ construct a primary group token for groups from a message
+*/
+static int construct_primary_group_token(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ struct ldb_context *ldb;
+ uint32_t primary_group_token;
+
+ ldb = ldb_module_get_ctx(module);
+ if (ldb_match_msg_objectclass(msg, "group") == 1) {
+ primary_group_token
+ = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
+ if (primary_group_token == 0) {
+ return LDB_SUCCESS;
+ }
+
+ return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
+ primary_group_token);
+ } else {
+ return LDB_SUCCESS;
+ }
+}
+
+/*
+ * Returns the group SIDs for the user in the given LDB message
+ */
+static int get_group_sids(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
+ struct ldb_message *msg, const char *attribute_string,
+ enum search_type type, struct auth_SidAttr **groupSIDs,
+ uint32_t *num_groupSIDs)
+{
+ const char *filter = NULL;
+ NTSTATUS status;
+ struct dom_sid *primary_group_sid;
+ const char *primary_group_string;
+ const char *primary_group_dn;
+ DATA_BLOB primary_group_blob;
+ struct dom_sid *account_sid;
+ const char *account_sid_string;
+ const char *account_sid_dn;
+ DATA_BLOB account_sid_blob;
+ struct dom_sid *domain_sid;
+
+ /* If it's not a user, it won't have a primaryGroupID */
+ if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
+ return LDB_SUCCESS;
+ }
+
+ /* Ensure it has an objectSID too */
+ account_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
+ if (account_sid == NULL) {
+ return LDB_SUCCESS;
+ }
+
+ status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ primary_group_sid = dom_sid_add_rid(mem_ctx,
+ domain_sid,
+ ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
+ if (!primary_group_sid) {
+ return ldb_oom(ldb);
+ }
+
+ /* only return security groups */
+ switch(type) {
+ case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
+ filter = talloc_asprintf(mem_ctx,
+ "(&(objectClass=group)"
+ "(groupType:"LDB_OID_COMPARATOR_AND":=%u)"
+ "(groupType:"LDB_OID_COMPARATOR_OR":=%u))",
+ GROUP_TYPE_SECURITY_ENABLED,
+ GROUP_TYPE_ACCOUNT_GROUP | GROUP_TYPE_UNIVERSAL_GROUP);
+ break;
+ case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
+ case TOKEN_GROUPS:
+ filter = talloc_asprintf(mem_ctx,
+ "(&(objectClass=group)"
+ "(groupType:"LDB_OID_COMPARATOR_AND":=%u))",
+ GROUP_TYPE_SECURITY_ENABLED);
+ break;
+
+ /* for RevMembGetAccountGroups, exclude built-in groups */
+ case ACCOUNT_GROUPS:
+ filter = talloc_asprintf(mem_ctx,
+ "(&(objectClass=group)"
+ "(!(groupType:"LDB_OID_COMPARATOR_AND":=%u))"
+ "(groupType:"LDB_OID_COMPARATOR_AND":=%u))",
+ GROUP_TYPE_BUILTIN_LOCAL_GROUP, GROUP_TYPE_SECURITY_ENABLED);
+ break;
+ }
+
+ if (!filter) {
+ return ldb_oom(ldb);
+ }
+
+ primary_group_string = dom_sid_string(mem_ctx, primary_group_sid);
+ if (!primary_group_string) {
+ return ldb_oom(ldb);
+ }
+
+ primary_group_dn = talloc_asprintf(mem_ctx, "<SID=%s>", primary_group_string);
+ if (!primary_group_dn) {
+ return ldb_oom(ldb);
+ }
+
+ primary_group_blob = data_blob_string_const(primary_group_dn);
+
+ account_sid_string = dom_sid_string(mem_ctx, account_sid);
+ if (!account_sid_string) {
+ return ldb_oom(ldb);
+ }
+
+ account_sid_dn = talloc_asprintf(mem_ctx, "<SID=%s>", account_sid_string);
+ if (!account_sid_dn) {
+ return ldb_oom(ldb);
+ }
+
+ account_sid_blob = data_blob_string_const(account_sid_dn);
+
+ status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
+ true, /* We don't want to add the object's SID itself,
+ it's not returned in this attribute */
+ filter,
+ mem_ctx, groupSIDs, num_groupSIDs);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
+ attribute_string, account_sid_string,
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Expands the primary group - this function takes in
+ * memberOf-like values, so we fake one up with the
+ * <SID=S-...> format of DN and then let it expand
+ * them, as long as they meet the filter - so only
+ * domain groups, not builtin groups
+ */
+ status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
+ mem_ctx, groupSIDs, num_groupSIDs);
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
+ attribute_string, account_sid_string,
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ construct the token groups for SAM objects from a message
+*/
+static int construct_generic_token_groups(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent,
+ const char *attribute_string,
+ enum search_type type)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx = talloc_new(msg);
+ uint32_t i;
+ int ret;
+ struct auth_SidAttr *groupSIDs = NULL;
+ uint32_t num_groupSIDs = 0;
+
+ if (scope != LDB_SCOPE_BASE) {
+ ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* calculate the group SIDs for this object */
+ ret = get_group_sids(ldb, tmp_ctx, msg, attribute_string, type,
+ &groupSIDs, &num_groupSIDs);
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* add these SIDs to the search result */
+ for (i=0; i < num_groupSIDs; i++) {
+ ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i].sid);
+ if (ret) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int construct_token_groups(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ /**
+ * TODO: Add in a limiting domain when we start to support
+ * trusted domains.
+ */
+ return construct_generic_token_groups(module, msg, scope, parent,
+ "tokenGroups",
+ TOKEN_GROUPS);
+}
+
+static int construct_token_groups_no_gc(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ /**
+ * TODO: Add in a limiting domain when we start to support
+ * trusted domains.
+ */
+ return construct_generic_token_groups(module, msg, scope, parent,
+ "tokenGroupsNoGCAcceptable",
+ TOKEN_GROUPS);
+}
+
+static int construct_global_universal_token_groups(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ return construct_generic_token_groups(module, msg, scope, parent,
+ "tokenGroupsGlobalAndUniversal",
+ TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
+}
+/*
+ construct the parent GUID for an entry from a message
+*/
+static int construct_parent_guid(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ struct ldb_result *res, *parent_res;
+ const struct ldb_val *parent_guid;
+ const char *attrs[] = { "instanceType", NULL };
+ const char *attrs2[] = { "objectGUID", NULL };
+ uint32_t instanceType;
+ int ret;
+ struct ldb_dn *parent_dn;
+ struct ldb_val v;
+
+ /* determine if the object is NC by instance type */
+ ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED, parent);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
+ "instanceType", 0);
+ talloc_free(res);
+ if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
+ DEBUG(4,(__location__ ": Object %s is NC\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_SUCCESS;
+ }
+ parent_dn = ldb_dn_get_parent(msg, msg->dn);
+
+ if (parent_dn == NULL) {
+ DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_ERR_OTHER;
+ }
+ ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED, parent);
+ /* not NC, so the object should have a parent*/
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
+ talloc_asprintf(msg, "Parent dn %s for %s does not exist",
+ ldb_dn_get_linearized(parent_dn),
+ ldb_dn_get_linearized(msg->dn)));
+ talloc_free(parent_dn);
+ return ret;
+ } else if (ret != LDB_SUCCESS) {
+ talloc_free(parent_dn);
+ return ret;
+ }
+ talloc_free(parent_dn);
+
+ parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
+ if (!parent_guid) {
+ talloc_free(parent_res);
+ return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+ }
+
+ v = data_blob_dup_talloc(parent_res, *parent_guid);
+ if (!v.data) {
+ talloc_free(parent_res);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+ ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
+ talloc_free(parent_res);
+ return ret;
+}
+
+static int construct_modifyTimeStamp(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ /* We may be being called before the init function has finished */
+ if (!data) {
+ return LDB_SUCCESS;
+ }
+
+ /* Try and set this value up, if possible. Don't worry if it
+ * fails, we may not have the DB set up yet.
+ */
+ if (!data->aggregate_dn) {
+ data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
+ }
+
+ if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
+ /*
+ * If we have the DN for the object with common name = Aggregate and
+ * the request is for this DN then let's do the following:
+ * 1) search the object which changedUSN correspond to the one of the loaded
+ * schema.
+ * 2) Get the whenChanged attribute
+ * 3) Generate the modifyTimestamp out of the whenChanged attribute
+ */
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
+ char *value = ldb_timestring(msg, schema->ts_last_change);
+
+ if (value == NULL) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ return ldb_msg_add_string(msg, "modifyTimeStamp", value);
+ }
+ return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
+}
+
+/*
+ construct a subSchemaSubEntry
+*/
+static int construct_subschema_subentry(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
+ char *subSchemaSubEntry;
+
+ /* We may be being called before the init function has finished */
+ if (!data) {
+ return LDB_SUCCESS;
+ }
+
+ /* Try and set this value up, if possible. Don't worry if it
+ * fails, we may not have the DB set up yet, and it's not
+ * really vital anyway */
+ if (!data->aggregate_dn) {
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
+ }
+
+ if (data->aggregate_dn) {
+ subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
+ return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
+ }
+ return LDB_SUCCESS;
+}
+
+
+static int construct_msds_isrodc_with_dn(struct ldb_module *module,
+ struct ldb_message *msg,
+ struct ldb_message_element *object_category)
+{
+ struct ldb_context *ldb;
+ struct ldb_dn *dn;
+ const struct ldb_val *val;
+
+ ldb = ldb_module_get_ctx(module);
+ if (!ldb) {
+ DEBUG(4, (__location__ ": Failed to get ldb \n"));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
+ if (!dn) {
+ DEBUG(4, (__location__ ": Failed to create dn from %s \n",
+ (const char *)object_category->values[0].data));
+ return ldb_operr(ldb);
+ }
+
+ val = ldb_dn_get_rdn_val(dn);
+ if (!val) {
+ DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
+ ldb_dn_get_linearized(dn)));
+ return ldb_operr(ldb);
+ }
+
+ if (strequal((const char *)val->data, "NTDS-DSA")) {
+ ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
+ } else {
+ ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
+ }
+ return LDB_SUCCESS;
+}
+
+static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
+ struct ldb_message *msg,
+ struct ldb_dn *dn,
+ struct ldb_request *parent)
+{
+ struct ldb_dn *server_dn;
+ const char *attr_obj_cat[] = { "objectCategory", NULL };
+ struct ldb_result *res;
+ struct ldb_message_element *object_category;
+ int ret;
+
+ server_dn = ldb_dn_copy(msg, dn);
+ if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
+ DEBUG(4, (__location__ ": Failed to add child to %s \n",
+ ldb_dn_get_linearized(server_dn)));
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
+ DSDB_FLAG_NEXT_MODULE, parent);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
+ ldb_dn_get_linearized(server_dn)));
+ return LDB_SUCCESS;
+ } else if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
+ if (!object_category) {
+ DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
+ ldb_dn_get_linearized(res->msgs[0]->dn)));
+ return LDB_SUCCESS;
+ }
+ return construct_msds_isrodc_with_dn(module, msg, object_category);
+}
+
+static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
+ struct ldb_message *msg,
+ struct ldb_request *parent)
+{
+ int ret;
+ struct ldb_dn *server_dn;
+
+ ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
+ &server_dn, parent);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
+ /* it's OK if we can't find serverReferenceBL attribute */
+ DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_SUCCESS;
+ } else if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
+}
+
+/*
+ construct msDS-isRODC attr
+*/
+static int construct_msds_isrodc(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ struct ldb_message_element * object_class;
+ struct ldb_message_element * object_category;
+ unsigned int i;
+
+ object_class = ldb_msg_find_element(msg, "objectClass");
+ if (!object_class) {
+ DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
+ ldb_dn_get_linearized(msg->dn)));
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ for (i=0; i<object_class->num_values; i++) {
+ if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
+ /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
+ * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
+ */
+ object_category = ldb_msg_find_element(msg, "objectCategory");
+ if (!object_category) {
+ DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_SUCCESS;
+ }
+ return construct_msds_isrodc_with_dn(module, msg, object_category);
+ }
+ if (strequal((const char*)object_class->values[i].data, "server")) {
+ /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
+ * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
+ * substituting TN for TO.
+ */
+ return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
+ }
+ if (strequal((const char*)object_class->values[i].data, "computer")) {
+ /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
+ * rule for the "TO is a server object" case, substituting TS for TO.
+ */
+ return construct_msds_isrodc_with_computer_dn(module, msg, parent);
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+/*
+ construct msDS-keyVersionNumber attr
+
+ TODO: Make this based on the 'win2k' DS heuristics bit...
+
+*/
+static int construct_msds_keyversionnumber(struct ldb_module *module,
+ struct ldb_message *msg,
+ enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ uint32_t i;
+ enum ndr_err_code ndr_err;
+ const struct ldb_val *omd_value;
+ struct replPropertyMetaDataBlob *omd;
+ int ret;
+
+ omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
+ if (!omd_value) {
+ /* We can't make up a key version number without meta data */
+ return LDB_SUCCESS;
+ }
+
+ omd = talloc(msg, struct replPropertyMetaDataBlob);
+ if (!omd) {
+ ldb_module_oom(module);
+ return LDB_SUCCESS;
+ }
+
+ ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
+ (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ if (omd->version != 1) {
+ DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
+ omd->version, ldb_dn_get_linearized(msg->dn)));
+ talloc_free(omd);
+ return LDB_SUCCESS;
+ }
+ for (i=0; i<omd->ctr.ctr1.count; i++) {
+ if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
+ ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
+ msg, msg,
+ "msDS-KeyVersionNumber",
+ omd->ctr.ctr1.array[i].version);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(omd);
+ return ret;
+ }
+ break;
+ }
+ }
+ return LDB_SUCCESS;
+
+}
+
+#define _UF_TRUST_ACCOUNTS ( \
+ UF_WORKSTATION_TRUST_ACCOUNT | \
+ UF_SERVER_TRUST_ACCOUNT | \
+ UF_INTERDOMAIN_TRUST_ACCOUNT \
+)
+#define _UF_NO_EXPIRY_ACCOUNTS ( \
+ UF_SMARTCARD_REQUIRED | \
+ UF_DONT_EXPIRE_PASSWD | \
+ _UF_TRUST_ACCOUNTS \
+)
+
+
+/*
+ * Returns the Effective-MaximumPasswordAge for a user
+ */
+static int64_t get_user_max_pwd_age(struct ldb_module *module,
+ struct ldb_message *user_msg,
+ struct ldb_request *parent,
+ struct ldb_dn *nc_root)
+{
+ int ret;
+ struct ldb_message *pso = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ /* if a PSO applies to the user, use its maxPwdAge */
+ ret = get_pso_for_user(module, user_msg, parent, &pso);
+ if (ret != LDB_SUCCESS) {
+
+ /* log the error, but fallback to the domain default */
+ DBG_ERR("Error retrieving PSO for %s\n",
+ ldb_dn_get_linearized(user_msg->dn));
+ }
+
+ if (pso != NULL) {
+ return ldb_msg_find_attr_as_int64(pso,
+ "msDS-MaximumPasswordAge", 0);
+ }
+
+ /* otherwise return the default domain value */
+ return samdb_search_int64(ldb, user_msg, 0, nc_root, "maxPwdAge", NULL);
+}
+
+/*
+ calculate msDS-UserPasswordExpiryTimeComputed
+*/
+static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
+ struct ldb_message *msg,
+ struct ldb_request *parent,
+ struct ldb_dn *domain_dn)
+{
+ int64_t pwdLastSet, maxPwdAge;
+ uint32_t userAccountControl;
+ NTTIME ret;
+
+ userAccountControl = ldb_msg_find_attr_as_uint(msg,
+ "userAccountControl",
+ 0);
+ if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
+ return INT64_MAX;
+ }
+
+ pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
+ if (pwdLastSet == 0) {
+ return 0;
+ }
+
+ if (pwdLastSet <= -1) {
+ /*
+ * This can't really happen...
+ */
+ return INT64_MAX;
+ }
+
+ if (pwdLastSet >= INT64_MAX) {
+ /*
+ * Somethings wrong with the clock...
+ */
+ return INT64_MAX;
+ }
+
+ /*
+ * Note that maxPwdAge is a stored as negative value.
+ *
+ * Possible values are in the range of:
+ *
+ * maxPwdAge: -864000000001
+ * to
+ * maxPwdAge: -9223372036854775808 (INT64_MIN)
+ *
+ */
+ maxPwdAge = get_user_max_pwd_age(module, msg, parent, domain_dn);
+ if (maxPwdAge >= -864000000000) {
+ /*
+ * This is not really possible...
+ */
+ return INT64_MAX;
+ }
+
+ if (maxPwdAge == INT64_MIN) {
+ return INT64_MAX;
+ }
+
+ /*
+ * Note we already caught maxPwdAge == INT64_MIN
+ * and pwdLastSet >= INT64_MAX above.
+ *
+ * Remember maxPwdAge is a negative number,
+ * so it results in the following.
+ *
+ * 0x7FFFFFFFFFFFFFFEULL + INT64_MAX
+ * =
+ * 0xFFFFFFFFFFFFFFFDULL
+ *
+ * or to put it another way, adding two numbers less than 1<<63 can't
+ * ever be more than 1<<64, therefore this result can't wrap.
+ */
+ ret = (NTTIME)pwdLastSet - (NTTIME)maxPwdAge;
+ if (ret >= INT64_MAX) {
+ return INT64_MAX;
+ }
+
+ return ret;
+}
+
+/*
+ * Returns the Effective-LockoutDuration for a user
+ */
+static int64_t get_user_lockout_duration(struct ldb_module *module,
+ struct ldb_message *user_msg,
+ struct ldb_request *parent,
+ struct ldb_dn *nc_root)
+{
+ int ret;
+ struct ldb_message *pso = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ /* if a PSO applies to the user, use its lockoutDuration */
+ ret = get_pso_for_user(module, user_msg, parent, &pso);
+ if (ret != LDB_SUCCESS) {
+
+ /* log the error, but fallback to the domain default */
+ DBG_ERR("Error retrieving PSO for %s\n",
+ ldb_dn_get_linearized(user_msg->dn));
+ }
+
+ if (pso != NULL) {
+ return ldb_msg_find_attr_as_int64(pso,
+ "msDS-LockoutDuration", 0);
+ }
+
+ /* otherwise return the default domain value */
+ return samdb_search_int64(ldb, user_msg, 0, nc_root, "lockoutDuration",
+ NULL);
+}
+
+/*
+ construct msDS-User-Account-Control-Computed attr
+*/
+static int construct_msds_user_account_control_computed(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ uint32_t userAccountControl;
+ uint32_t msDS_User_Account_Control_Computed = 0;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ NTTIME now;
+ struct ldb_dn *nc_root;
+ int ret;
+
+ ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
+ if (ret != 0) {
+ ldb_asprintf_errstring(ldb,
+ "Failed to find NC root of DN: %s: %s",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb_module_get_ctx(module)));
+ return ret;
+ }
+ if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
+ /* Only calculate this on our default NC */
+ return 0;
+ }
+ /* Test account expire time */
+ unix_to_nt_time(&now, time(NULL));
+
+ userAccountControl = ldb_msg_find_attr_as_uint(msg,
+ "userAccountControl",
+ 0);
+ if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
+
+ int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
+ if (lockoutTime != 0) {
+ int64_t lockoutDuration;
+
+ lockoutDuration = get_user_lockout_duration(module, msg,
+ parent,
+ nc_root);
+
+ /* zero locks out until the administrator intervenes */
+ if (lockoutDuration >= 0) {
+ msDS_User_Account_Control_Computed |= UF_LOCKOUT;
+ } else if (lockoutTime - lockoutDuration >= now) {
+ msDS_User_Account_Control_Computed |= UF_LOCKOUT;
+ }
+ }
+ }
+
+ if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
+ NTTIME must_change_time
+ = get_msds_user_password_expiry_time_computed(module,
+ msg,
+ parent,
+ nc_root);
+ /* check for expired password */
+ if (must_change_time < now) {
+ msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
+ }
+ }
+
+ return samdb_msg_add_int64(ldb,
+ msg->elements, msg,
+ "msDS-User-Account-Control-Computed",
+ msDS_User_Account_Control_Computed);
+}
+
+/*
+ construct msDS-UserPasswordExpiryTimeComputed
+*/
+static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_dn *nc_root;
+ int64_t password_expiry_time;
+ int ret;
+
+ ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
+ if (ret != 0) {
+ ldb_asprintf_errstring(ldb,
+ "Failed to find NC root of DN: %s: %s",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb));
+ return ret;
+ }
+
+ if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
+ /* Only calculate this on our default NC */
+ return 0;
+ }
+
+ password_expiry_time
+ = get_msds_user_password_expiry_time_computed(module, msg,
+ parent, nc_root);
+
+ return samdb_msg_add_int64(ldb,
+ msg->elements, msg,
+ "msDS-UserPasswordExpiryTimeComputed",
+ password_expiry_time);
+}
+
+/*
+ * Checks whether the msDS-ResultantPSO attribute is supported for a given
+ * user object. As per MS-ADTS, section 3.1.1.4.5.36 msDS-ResultantPSO.
+ */
+static bool pso_is_supported(struct ldb_context *ldb, struct ldb_message *msg)
+{
+ int functional_level;
+ uint32_t uac;
+ uint32_t user_rid;
+
+ functional_level = dsdb_functional_level(ldb);
+ if (functional_level < DS_DOMAIN_FUNCTION_2008) {
+ return false;
+ }
+
+ /* msDS-ResultantPSO is only supported for user objects */
+ if (!ldb_match_msg_objectclass(msg, "user")) {
+ return false;
+ }
+
+ /* ...and only if the ADS_UF_NORMAL_ACCOUNT bit is set */
+ uac = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
+ if (!(uac & UF_NORMAL_ACCOUNT)) {
+ return false;
+ }
+
+ /* skip it if it's the special KRBTGT default account */
+ user_rid = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
+ if (user_rid == DOMAIN_RID_KRBTGT) {
+ return false;
+ }
+
+ /* ...or if it's a special KRBTGT account for an RODC KDC */
+ if (ldb_msg_find_ldb_val(msg, "msDS-SecondaryKrbTgtNumber") != NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Returns the number of PSO objects that exist in the DB
+ */
+static int get_pso_count(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_request *parent, int *pso_count)
+{
+ static const char * const attrs[] = { NULL };
+ int ret;
+ struct ldb_dn *psc_dn = NULL;
+ struct ldb_result *res = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ bool psc_ok;
+
+ *pso_count = 0;
+ psc_dn = samdb_system_container_dn(ldb, mem_ctx);
+ if (psc_dn == NULL) {
+ return ldb_oom(ldb);
+ }
+ psc_ok = ldb_dn_add_child_fmt(psc_dn, "CN=Password Settings Container");
+ if (psc_ok == false) {
+ return ldb_oom(ldb);
+ }
+
+ /* get the number of PSO children */
+ ret = dsdb_module_search(module, mem_ctx, &res, psc_dn,
+ LDB_SCOPE_ONELEVEL, attrs,
+ DSDB_FLAG_NEXT_MODULE, parent,
+ "(objectClass=msDS-PasswordSettings)");
+
+ /*
+ * Just ignore PSOs if the container doesn't exist. This is a weird
+ * corner-case where the AD DB was created from a pre-2008 base schema,
+ * and then the FL was manually upgraded.
+ */
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ DBG_NOTICE("No Password Settings Container exists\n");
+ return LDB_SUCCESS;
+ }
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ *pso_count = res->count;
+ talloc_free(res);
+ talloc_free(psc_dn);
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * Compares two PSO objects returned by a search, to work out the better PSO.
+ * The PSO with the lowest precedence is better, otherwise (if the precedence
+ * is equal) the PSO with the lower GUID wins.
+ */
+static int pso_compare(struct ldb_message **m1, struct ldb_message **m2)
+{
+ uint32_t prec1;
+ uint32_t prec2;
+
+ prec1 = ldb_msg_find_attr_as_uint(*m1, "msDS-PasswordSettingsPrecedence",
+ 0xffffffff);
+ prec2 = ldb_msg_find_attr_as_uint(*m2, "msDS-PasswordSettingsPrecedence",
+ 0xffffffff);
+
+ /* if precedence is equal, use the lowest GUID */
+ if (prec1 == prec2) {
+ struct GUID guid1 = samdb_result_guid(*m1, "objectGUID");
+ struct GUID guid2 = samdb_result_guid(*m2, "objectGUID");
+
+ return ndr_guid_compare(&guid1, &guid2);
+ } else {
+ return prec1 - prec2;
+ }
+}
+
+/*
+ * Search for PSO objects that apply to the object SIDs specified
+ */
+static int pso_search_by_sids(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_request *parent,
+ struct auth_SidAttr *sid_array, unsigned int num_sids,
+ struct ldb_result **result)
+{
+ int ret;
+ int i;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ char *sid_filter = NULL;
+ struct ldb_dn *psc_dn = NULL;
+ bool psc_ok;
+ const char *attrs[] = {
+ "msDS-PasswordSettingsPrecedence",
+ "objectGUID",
+ "msDS-LockoutDuration",
+ "msDS-MaximumPasswordAge",
+ NULL
+ };
+
+ /* build a query for PSO objects that apply to any of the SIDs given */
+ sid_filter = talloc_strdup(mem_ctx, "");
+ if (sid_filter == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ for (i = 0; sid_filter && i < num_sids; i++) {
+ struct dom_sid_buf sid_buf;
+
+ sid_filter = talloc_asprintf_append(
+ sid_filter,
+ "(msDS-PSOAppliesTo=<SID=%s>)",
+ dom_sid_str_buf(&sid_array[i].sid, &sid_buf));
+ if (sid_filter == NULL) {
+ return ldb_oom(ldb);
+ }
+ }
+
+ /* only PSOs located in the Password Settings Container are valid */
+ psc_dn = samdb_system_container_dn(ldb, mem_ctx);
+ if (psc_dn == NULL) {
+ return ldb_oom(ldb);
+ }
+ psc_ok = ldb_dn_add_child_fmt(psc_dn, "CN=Password Settings Container");
+ if (psc_ok == false) {
+ return ldb_oom(ldb);
+ }
+
+ ret = dsdb_module_search(module, mem_ctx, result, psc_dn,
+ LDB_SCOPE_ONELEVEL, attrs,
+ DSDB_FLAG_NEXT_MODULE, parent,
+ "(&(objectClass=msDS-PasswordSettings)(|%s))",
+ sid_filter);
+ talloc_free(sid_filter);
+ return ret;
+}
+
+/*
+ * Returns the best PSO object that applies to the object SID(s) specified
+ */
+static int pso_find_best(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_request *parent, struct auth_SidAttr *sid_array,
+ unsigned int num_sids, struct ldb_message **best_pso)
+{
+ struct ldb_result *res = NULL;
+ int ret;
+
+ *best_pso = NULL;
+
+ /* find any PSOs that apply to the SIDs specified */
+ ret = pso_search_by_sids(module, mem_ctx, parent, sid_array, num_sids,
+ &res);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Error %d retrieving PSO for SID(s)\n", ret);
+ return ret;
+ }
+
+ /* sort the list so that the best PSO is first */
+ TYPESAFE_QSORT(res->msgs, res->count, pso_compare);
+
+ if (res->count > 0) {
+ *best_pso = res->msgs[0];
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * Determines the Password Settings Object (PSO) that applies to the given user
+ */
+static int get_pso_for_user(struct ldb_module *module,
+ struct ldb_message *user_msg,
+ struct ldb_request *parent,
+ struct ldb_message **pso_msg)
+{
+ bool pso_supported;
+ struct auth_SidAttr *groupSIDs = NULL;
+ uint32_t num_groupSIDs = 0;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_message *best_pso = NULL;
+ struct ldb_dn *pso_dn = NULL;
+ int ret;
+ struct ldb_message_element *el = NULL;
+ TALLOC_CTX *tmp_ctx = NULL;
+ int pso_count = 0;
+ struct ldb_result *res = NULL;
+ static const char *attrs[] = {
+ "msDS-LockoutDuration",
+ "msDS-MaximumPasswordAge",
+ NULL
+ };
+
+ *pso_msg = NULL;
+
+ /* first, check msDS-ResultantPSO is supported for this object */
+ pso_supported = pso_is_supported(ldb, user_msg);
+
+ if (!pso_supported) {
+ return LDB_SUCCESS;
+ }
+
+ tmp_ctx = talloc_new(user_msg);
+
+ /*
+ * Several different constructed attributes try to use the PSO info. If
+ * we've already constructed the msDS-ResultantPSO for this user, we can
+ * just re-use the result, rather than calculating it from scratch again
+ */
+ pso_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, user_msg,
+ "msDS-ResultantPSO");
+
+ if (pso_dn != NULL) {
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res, pso_dn,
+ attrs, DSDB_FLAG_NEXT_MODULE,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Error %d retrieving PSO %s\n", ret,
+ ldb_dn_get_linearized(pso_dn));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (res->count == 1) {
+ *pso_msg = res->msgs[0];
+ return LDB_SUCCESS;
+ }
+ }
+
+ /*
+ * if any PSOs apply directly to the user, they are considered first
+ * before we check group membership PSOs
+ */
+ el = ldb_msg_find_element(user_msg, "msDS-PSOApplied");
+
+ if (el != NULL && el->num_values > 0) {
+ struct auth_SidAttr *user_sid = NULL;
+
+ /* lookup the best PSO object, based on the user's SID */
+ user_sid = samdb_result_dom_sid_attrs(
+ tmp_ctx, user_msg, "objectSid",
+ SE_GROUP_DEFAULT_FLAGS);
+
+ ret = pso_find_best(module, tmp_ctx, parent, user_sid, 1,
+ &best_pso);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (best_pso != NULL) {
+ *pso_msg = best_pso;
+ return LDB_SUCCESS;
+ }
+ }
+
+ /*
+ * If no valid PSO applies directly to the user, then try its groups.
+ * The group expansion is expensive, so check there are actually
+ * PSOs in the DB first (which is a quick search). Note in the above
+ * cases we could tell that a PSO applied to the user, based on info
+ * already retrieved by the user search.
+ */
+ ret = get_pso_count(module, tmp_ctx, parent, &pso_count);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Error %d determining PSOs in system\n", ret);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (pso_count == 0) {
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ /* Work out the SIDs of any account groups the user is a member of */
+ ret = get_group_sids(ldb, tmp_ctx, user_msg,
+ "msDS-ResultantPSO", ACCOUNT_GROUPS,
+ &groupSIDs, &num_groupSIDs);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Error %d determining group SIDs for %s\n", ret,
+ ldb_dn_get_linearized(user_msg->dn));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* lookup the best PSO that applies to any of these groups */
+ ret = pso_find_best(module, tmp_ctx, parent, groupSIDs,
+ num_groupSIDs, &best_pso);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ *pso_msg = best_pso;
+ return LDB_SUCCESS;
+}
+
+/*
+ * Constructs the msDS-ResultantPSO attribute, which is the DN of the Password
+ * Settings Object (PSO) that applies to that user.
+ */
+static int construct_resultant_pso(struct ldb_module *module,
+ struct ldb_message *msg,
+ enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ struct ldb_message *pso = NULL;
+ int ret;
+
+ /* work out the PSO (if any) that applies to this user */
+ ret = get_pso_for_user(module, msg, parent, &pso);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Couldn't determine PSO for %s\n",
+ ldb_dn_get_linearized(msg->dn));
+ return ret;
+ }
+
+ if (pso != NULL) {
+ DBG_INFO("%s is resultant PSO for user %s\n",
+ ldb_dn_get_linearized(pso->dn),
+ ldb_dn_get_linearized(msg->dn));
+ return ldb_msg_add_string(msg, "msDS-ResultantPSO",
+ ldb_dn_get_linearized(pso->dn));
+ }
+
+ /* no PSO applies to this user */
+ return LDB_SUCCESS;
+}
+
+struct op_controls_flags {
+ bool sd;
+ bool bypassoperational;
+};
+
+static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
+ if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
+ return true;
+ }
+ return false;
+}
+
+/*
+ a list of attribute names that should be substituted in the parse
+ tree before the search is done
+*/
+static const struct {
+ const char *attr;
+ const char *replace;
+} parse_tree_sub[] = {
+ { "createTimeStamp", "whenCreated" },
+ { "modifyTimeStamp", "whenChanged" }
+};
+
+
+struct op_attributes_replace {
+ const char *attr;
+ const char *replace;
+ const char * const *extra_attrs;
+ int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
+};
+
+/* the 'extra_attrs' required for msDS-ResultantPSO */
+#define RESULTANT_PSO_COMPUTED_ATTRS \
+ "msDS-PSOApplied", \
+ "userAccountControl", \
+ "objectSid", \
+ "msDS-SecondaryKrbTgtNumber", \
+ "primaryGroupID"
+
+/*
+ * any other constructed attributes that want to work out the PSO also need to
+ * include objectClass (this gets included via 'replace' for msDS-ResultantPSO)
+ */
+#define PSO_ATTR_DEPENDENCIES \
+ RESULTANT_PSO_COMPUTED_ATTRS, \
+ "objectClass"
+
+static const char *objectSid_attr[] =
+{
+ "objectSid",
+ NULL
+};
+
+
+static const char *objectCategory_attr[] =
+{
+ "objectCategory",
+ NULL
+};
+
+
+static const char *user_account_control_computed_attrs[] =
+{
+ "lockoutTime",
+ "pwdLastSet",
+ PSO_ATTR_DEPENDENCIES,
+ NULL
+};
+
+
+static const char *user_password_expiry_time_computed_attrs[] =
+{
+ "pwdLastSet",
+ PSO_ATTR_DEPENDENCIES,
+ NULL
+};
+
+static const char *resultant_pso_computed_attrs[] =
+{
+ RESULTANT_PSO_COMPUTED_ATTRS,
+ NULL
+};
+
+/*
+ a list of attribute names that are hidden, but can be searched for
+ using another (non-hidden) name to produce the correct result
+*/
+static const struct op_attributes_replace search_sub[] = {
+ { "createTimeStamp", "whenCreated", NULL , NULL },
+ { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
+ { "structuralObjectClass", "objectClass", NULL , NULL },
+ { "canonicalName", NULL, NULL , construct_canonical_name },
+ { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
+ { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
+ { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
+ { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
+ { "parentGUID", "objectGUID", NULL, construct_parent_guid },
+ { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
+ { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
+ { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
+ { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
+ construct_msds_user_account_control_computed },
+ { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
+ construct_msds_user_password_expiry_time_computed },
+ { "msDS-ResultantPSO", "objectClass", resultant_pso_computed_attrs,
+ construct_resultant_pso }
+};
+
+
+enum op_remove {
+ OPERATIONAL_REMOVE_ALWAYS, /* remove always */
+ OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
+ OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
+ OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an ad hoc control has been specified */
+};
+
+/*
+ a list of attributes that may need to be removed from the
+ underlying db return
+
+ Some of these are attributes that were once stored, but are now calculated
+*/
+struct op_attributes_operations {
+ const char *attr;
+ enum op_remove op;
+};
+
+static const struct op_attributes_operations operational_remove[] = {
+ { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
+ { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
+ { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
+ { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
+#define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
+ { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
+};
+
+
+/*
+ post process a search result record. For any search_sub[] attributes that were
+ asked for, we need to call the appropriate copy routine to copy the result
+ into the message, then remove any attributes that we added to the search but
+ were not asked for by the user
+*/
+static int operational_search_post_process(struct ldb_module *module,
+ struct ldb_message *msg,
+ enum ldb_scope scope,
+ const char * const *attrs_from_user,
+ const char * const *attrs_searched_for,
+ struct op_controls_flags* controls_flags,
+ struct op_attributes_operations *list,
+ unsigned int list_size,
+ struct op_attributes_replace *list_replace,
+ unsigned int list_replace_size,
+ struct ldb_request *parent)
+{
+ struct ldb_context *ldb;
+ unsigned int i, a = 0;
+ bool constructed_attributes = false;
+
+ ldb = ldb_module_get_ctx(module);
+
+ /* removed any attrs that should not be shown to the user */
+ for (i=0; i < list_size; i++) {
+ ldb_msg_remove_attr(msg, list[i].attr);
+ }
+
+ for (a=0; a < list_replace_size; a++) {
+ if (check_keep_control_for_attribute(controls_flags,
+ list_replace[a].attr)) {
+ continue;
+ }
+
+ /* construct the new attribute, using either a supplied
+ constructor or a simple copy */
+ constructed_attributes = true;
+ if (list_replace[a].constructor != NULL) {
+ if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
+ goto failed;
+ }
+ } else if (ldb_msg_copy_attr(msg,
+ list_replace[a].replace,
+ list_replace[a].attr) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+
+ /* Deletion of the search helper attributes are needed if:
+ * - we generated constructed attributes and
+ * - we aren't requesting all attributes
+ */
+ if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
+ for (i=0; i < list_replace_size; i++) {
+ /* remove the added search helper attributes, unless
+ * they were asked for by the user */
+ if (list_replace[i].replace != NULL &&
+ !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
+ ldb_msg_remove_attr(msg, list_replace[i].replace);
+ }
+ if (list_replace[i].extra_attrs != NULL) {
+ unsigned int j;
+ for (j=0; list_replace[i].extra_attrs[j]; j++) {
+ if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
+ ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+
+failed:
+ ldb_debug_set(ldb, LDB_DEBUG_WARNING,
+ "operational_search_post_process failed for attribute '%s' - %s",
+ list_replace[a].attr, ldb_errstring(ldb));
+ return -1;
+}
+
+/*
+ hook search operations
+*/
+
+struct operational_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+ enum ldb_scope scope;
+ const char * const *attrs;
+ struct ldb_parse_tree *tree;
+ struct op_controls_flags* controls_flags;
+ struct op_attributes_operations *list_operations;
+ unsigned int list_operations_size;
+ struct op_attributes_replace *attrs_to_replace;
+ unsigned int attrs_to_replace_size;
+};
+
+static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct operational_context *ac;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct operational_context);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ /* for each record returned post-process to add any derived
+ attributes that have been asked for */
+ ret = operational_search_post_process(ac->module,
+ ares->message,
+ ac->scope,
+ ac->attrs,
+ req->op.search.attrs,
+ ac->controls_flags,
+ ac->list_operations,
+ ac->list_operations_size,
+ ac->attrs_to_replace,
+ ac->attrs_to_replace_size,
+ req);
+ if (ret != 0) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ return ldb_module_send_entry(ac->req, ares->message, ares->controls);
+
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(ac->req, ares->referral);
+
+ case LDB_REPLY_DONE:
+
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, LDB_SUCCESS);
+ }
+
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
+ const char* const* attrs,
+ const char* const* searched_attrs,
+ struct op_controls_flags* controls_flags)
+{
+ int idx = 0;
+ int i;
+ struct op_attributes_operations *list = talloc_zero_array(ctx,
+ struct op_attributes_operations,
+ ARRAY_SIZE(operational_remove) + 1);
+
+ if (list == NULL) {
+ return NULL;
+ }
+
+ for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
+ switch (operational_remove[i].op) {
+ case OPERATIONAL_REMOVE_UNASKED:
+ if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
+ continue;
+ }
+ if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
+ continue;
+ }
+ list[idx].attr = operational_remove[i].attr;
+ list[idx].op = OPERATIONAL_REMOVE_UNASKED;
+ idx++;
+ break;
+
+ case OPERATIONAL_REMOVE_ALWAYS:
+ list[idx].attr = operational_remove[i].attr;
+ list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
+ idx++;
+ break;
+
+ case OPERATIONAL_REMOVE_UNLESS_CONTROL:
+ if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
+ list[idx].attr = operational_remove[i].attr;
+ list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
+ idx++;
+ }
+ break;
+
+ case OPERATIONAL_SD_FLAGS:
+ if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
+ continue;
+ }
+ if (controls_flags->sd) {
+ if (attrs == NULL) {
+ continue;
+ }
+ if (attrs[0] == NULL) {
+ continue;
+ }
+ if (ldb_attr_in_list(attrs, "*")) {
+ continue;
+ }
+ }
+ list[idx].attr = operational_remove[i].attr;
+ list[idx].op = OPERATIONAL_SD_FLAGS;
+ idx++;
+ break;
+ }
+ }
+
+ return list;
+}
+
+struct operational_present_ctx {
+ const char *attr;
+ bool found_operational;
+};
+
+/*
+ callback to determine if an operational attribute (needing
+ replacement) is in use at all
+ */
+static int operational_present(struct ldb_parse_tree *tree, void *private_context)
+{
+ struct operational_present_ctx *ctx = private_context;
+ switch (tree->operation) {
+ case LDB_OP_EQUALITY:
+ if (ldb_attr_cmp(tree->u.equality.attr, ctx->attr) == 0) {
+ ctx->found_operational = true;
+ }
+ break;
+ case LDB_OP_GREATER:
+ case LDB_OP_LESS:
+ case LDB_OP_APPROX:
+ if (ldb_attr_cmp(tree->u.comparison.attr, ctx->attr) == 0) {
+ ctx->found_operational = true;
+ }
+ break;
+ case LDB_OP_SUBSTRING:
+ if (ldb_attr_cmp(tree->u.substring.attr, ctx->attr) == 0) {
+ ctx->found_operational = true;
+ }
+ break;
+ case LDB_OP_PRESENT:
+ if (ldb_attr_cmp(tree->u.present.attr, ctx->attr) == 0) {
+ ctx->found_operational = true;
+ }
+ break;
+ case LDB_OP_EXTENDED:
+ if (tree->u.extended.attr &&
+ ldb_attr_cmp(tree->u.extended.attr, ctx->attr) == 0) {
+ ctx->found_operational = true;
+ }
+ break;
+ default:
+ break;
+ }
+ return LDB_SUCCESS;
+}
+
+
+static int operational_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct operational_context *ac;
+ struct ldb_request *down_req;
+ const char **search_attrs = NULL;
+ struct operational_present_ctx ctx;
+ unsigned int i, a;
+ int ret;
+
+ /* There are no operational attributes on special DNs */
+ if (ldb_dn_is_special(req->op.search.base)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = talloc(req, struct operational_context);
+ if (ac == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ac->module = module;
+ ac->req = req;
+ ac->scope = req->op.search.scope;
+ ac->attrs = req->op.search.attrs;
+
+ ctx.found_operational = false;
+
+ /*
+ * find any attributes in the parse tree that are searchable,
+ * but are stored using a different name in the backend, so we
+ * only duplicate the memory when needed
+ */
+ for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
+ ctx.attr = parse_tree_sub[i].attr;
+
+ ldb_parse_tree_walk(req->op.search.tree,
+ operational_present,
+ &ctx);
+ if (ctx.found_operational) {
+ break;
+ }
+ }
+
+ if (ctx.found_operational) {
+
+ ac->tree = ldb_parse_tree_copy_shallow(ac,
+ req->op.search.tree);
+
+ if (ac->tree == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* replace any attributes in the parse tree that are
+ searchable, but are stored using a different name in the
+ backend */
+ for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
+ ldb_parse_tree_attr_replace(ac->tree,
+ parse_tree_sub[i].attr,
+ parse_tree_sub[i].replace);
+ }
+ } else {
+ /* Avoid allocating a copy if we do not need to */
+ ac->tree = req->op.search.tree;
+ }
+
+ ac->controls_flags = talloc(ac, struct op_controls_flags);
+ /* remember if the SD_FLAGS_OID was set */
+ ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
+ /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
+ ac->controls_flags->bypassoperational =
+ (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
+
+ ac->attrs_to_replace = NULL;
+ ac->attrs_to_replace_size = 0;
+ /* in the list of attributes we are looking for, rename any
+ attributes to the alias for any hidden attributes that can
+ be fetched directly using non-hidden names.
+ Note that order here can affect performance, e.g. we should process
+ msDS-ResultantPSO before msDS-User-Account-Control-Computed (as the
+ latter is also dependent on the PSO information) */
+ for (a=0;ac->attrs && ac->attrs[a];a++) {
+ if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
+ continue;
+ }
+ for (i=0;i<ARRAY_SIZE(search_sub);i++) {
+
+ if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
+ continue;
+ }
+
+ ac->attrs_to_replace = talloc_realloc(ac,
+ ac->attrs_to_replace,
+ struct op_attributes_replace,
+ ac->attrs_to_replace_size + 1);
+
+ ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
+ ac->attrs_to_replace_size++;
+ if (!search_sub[i].replace) {
+ continue;
+ }
+
+ if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
+ unsigned int j;
+ const char **search_attrs2;
+ /* Only adds to the end of the list */
+ for (j = 0; search_sub[i].extra_attrs[j]; j++) {
+ search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
+ ? search_attrs
+ : ac->attrs,
+ search_sub[i].extra_attrs[j]);
+ if (search_attrs2 == NULL) {
+ return ldb_operr(ldb);
+ }
+ /* may be NULL, talloc_free() doesn't mind */
+ talloc_free(search_attrs);
+ search_attrs = search_attrs2;
+ }
+ }
+
+ if (!search_attrs) {
+ search_attrs = ldb_attr_list_copy(req, ac->attrs);
+ if (search_attrs == NULL) {
+ return ldb_operr(ldb);
+ }
+ }
+ /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
+ search_attrs[a] = search_sub[i].replace;
+ }
+ }
+ ac->list_operations = operation_get_op_list(ac, ac->attrs,
+ search_attrs == NULL?req->op.search.attrs:search_attrs,
+ ac->controls_flags);
+ ac->list_operations_size = 0;
+ i = 0;
+
+ while (ac->list_operations && ac->list_operations[i].attr != NULL) {
+ i++;
+ }
+ ac->list_operations_size = i;
+ ret = ldb_build_search_req_ex(&down_req, ldb, ac,
+ req->op.search.base,
+ req->op.search.scope,
+ ac->tree,
+ /* use new set of attrs if any */
+ search_attrs == NULL?req->op.search.attrs:search_attrs,
+ req->controls,
+ ac, operational_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ /* perform the search */
+ return ldb_next_request(module, down_req);
+}
+
+static int operational_init(struct ldb_module *ctx)
+{
+ struct operational_data *data;
+ int ret;
+
+ ret = ldb_next_init(ctx);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ data = talloc_zero(ctx, struct operational_data);
+ if (!data) {
+ return ldb_module_oom(ctx);
+ }
+
+ ldb_module_set_private(ctx, data);
+
+ return LDB_SUCCESS;
+}
+
+static const struct ldb_module_ops ldb_operational_module_ops = {
+ .name = "operational",
+ .search = operational_search,
+ .init_context = operational_init
+};
+
+int ldb_operational_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_operational_module_ops);
+}