--- /dev/null
+/*
+ Unix SMB/CIFS implementation.
+ Samba Active Directory claims utility functions
+
+ Copyright (C) Catalyst.Net Ltd 2023
+
+ 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/>.
+*/
+
+#include "lib/replace/replace.h"
+#include "lib/util/debug.h"
+#include "lib/util/samba_util.h"
+#include "source4/kdc/ad_claims.h"
+#include "ldb_module.h"
+#include "libcli/security/security.h"
+#include "libcli/util/werror.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "librpc/gen_ndr/claims.h"
+#include "librpc/gen_ndr/ndr_claims.h"
+#include "librpc/gen_ndr/krb5pac.h"
+#include "librpc/gen_ndr/ndr_krb5pac.h"
+#include "lzxpress_huffman.h"
+#include "lib/util/binsearch.h"
+
+#undef strcasecmp
+
+static int acl_attr_cmp_fn(const char *a, const char * const *b)
+{
+ return ldb_attr_cmp(a, *b);
+}
+
+/*
+ * Add a single attribute to a list of attributes if it is not already
+ * present. The list is maintained in case-insensitive sorted order.
+ */
+static int add_attr_unique(TALLOC_CTX *mem_ctx,
+ const char **attrs,
+ unsigned *ad_claim_attrs_count,
+ const char *attr)
+{
+ const unsigned count = *ad_claim_attrs_count;
+ const char * const *exact = NULL;
+ const char * const *next = NULL;
+
+ BINARY_ARRAY_SEARCH_GTE(attrs,
+ count,
+ attr,
+ acl_attr_cmp_fn,
+ exact,
+ next);
+ if (exact != NULL) {
+ /* The attribute is already present; there's nothing to do. */
+ return LDB_SUCCESS;
+ }
+
+ /* Make sure we don't overflow the array. */
+ SMB_ASSERT(count < talloc_array_length(attrs));
+ *ad_claim_attrs_count = count + 1;
+
+ if (next == NULL) {
+ /* Just add the new element on the end. */
+ attrs[count] = attr;
+ } else {
+ /* Shift all following elements over to make room. */
+ size_t next_idx = next - attrs;
+ size_t bytes_to_move = (count - next_idx) * sizeof (attrs[0]);
+ memmove(&attrs[next_idx + 1],
+ &attrs[next_idx],
+ bytes_to_move);
+
+ attrs[next_idx] = attr;
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * Return true if a data_blob, interpreted as a string, is equal to another
+ * string. This is more efficient than strcmp(), particularly when comparing
+ * against a string constant. This assumes the data_blob's length does not
+ * include the zero-terminator.
+ */
+static inline bool data_blob_equals_str(const DATA_BLOB val, const char *str)
+{
+ size_t len = strlen(str);
+ if (val.length != len) {
+ return false;
+ }
+
+ return memcmp(val.data, str, len) == 0;
+}
+
+static int fill_claim_int64(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ const struct ldb_message_element *principal_attribute,
+ const struct ldb_val name,
+ struct CLAIM_INT64 *claim)
+{
+ uint32_t i;
+
+ claim->value_count = 0;
+ claim->values = talloc_array(mem_ctx,
+ int64_t,
+ principal_attribute->num_values);
+ if (claim->values == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ for (i = 0; i < principal_attribute->num_values; ++i) {
+ const struct ldb_val *value = &principal_attribute->values[i];
+ int ret = ldb_val_as_int64(value, &claim->values[i]);
+ if (ret) {
+ char buf[1024];
+ const char *reason = NULL;
+ int err = strerror_r(ret, buf, sizeof(buf));
+ if (err == 0) {
+ reason = buf;
+ } else {
+ reason = "Unknown error";
+ }
+ DBG_WARNING("Failed to intepret value %s as INT64 "
+ "while creating claim %s for attribute %s (%s); "
+ "skipping value\n",
+ (value->data != NULL) ? (const char *)value->data : "<unknown>",
+ name.data, principal_attribute->name,
+ reason);
+ continue;
+ }
+
+ ++claim->value_count;
+ }
+
+ /* Shrink the array to fit. */
+ claim->values = talloc_realloc(mem_ctx,
+ claim->values,
+ int64_t,
+ claim->value_count);
+ if (claim->value_count && claim->values == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int fill_claim_uint64(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ const struct ldb_message_element *principal_attribute,
+ const struct ldb_val name,
+ struct CLAIM_UINT64 *claim)
+{
+ uint32_t i;
+
+ claim->value_count = 0;
+ claim->values = talloc_array(mem_ctx,
+ uint64_t,
+ principal_attribute->num_values);
+ if (claim->values == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ for (i = 0; i < principal_attribute->num_values; ++i) {
+ const struct ldb_val *value = &principal_attribute->values[i];
+ int ret = ldb_val_as_uint64(value, &claim->values[i]);
+ if (ret) {
+ char buf[1024];
+ const char *reason = NULL;
+ int err = strerror_r(ret, buf, sizeof(buf));
+ if (err == 0) {
+ reason = buf;
+ } else {
+ reason = "Unknown error";
+ }
+ DBG_WARNING("Failed to intepret value %s as UINT64 "
+ "while creating claim %s for attribute %s (%s); "
+ "skipping value\n",
+ (value->data != NULL) ? (const char *)value->data : "<unknown>",
+ name.data, principal_attribute->name,
+ reason);
+ continue;
+ }
+
+ ++claim->value_count;
+ }
+
+ /* Shrink the array to fit. */
+ claim->values = talloc_realloc(mem_ctx,
+ claim->values,
+ uint64_t,
+ claim->value_count);
+ if (claim->value_count && claim->values == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int fill_claim_uint64_oid_syntax(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ const struct dsdb_schema *schema,
+ const struct ldb_message_element *principal_attribute,
+ const struct ldb_val name,
+ struct CLAIM_UINT64 *claim)
+{
+ uint32_t i;
+
+ claim->value_count = 0;
+ claim->values = talloc_array(mem_ctx,
+ uint64_t,
+ principal_attribute->num_values);
+ if (claim->values == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ for (i = 0; i < principal_attribute->num_values; ++i) {
+ const struct dsdb_class *class_val = NULL;
+
+ /*
+ * OID values for objectClass
+ * are presented in reverse
+ * order.
+ */
+ const struct ldb_val *display_name = &principal_attribute->values[
+ principal_attribute->num_values - 1 - i];
+
+ class_val = dsdb_class_by_lDAPDisplayName_ldb_val(schema, display_name);
+ if (class_val == NULL) {
+ DBG_WARNING("Failed to look up OID for value %s "
+ "while creating claim %s for attribute %s; "
+ "skipping value\n",
+ (display_name->data != NULL) ? (const char *)display_name->data : "<unknown>",
+ name.data, principal_attribute->name);
+ continue;
+ }
+
+ claim->values[i] = class_val->governsID_id;
+ ++claim->value_count;
+ }
+
+ /* Shrink the array to fit. */
+ claim->values = talloc_realloc(mem_ctx,
+ claim->values,
+ uint64_t,
+ claim->value_count);
+ if (claim->value_count && claim->values == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int fill_claim_boolean(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ const struct ldb_message_element *principal_attribute,
+ const struct ldb_val name,
+ struct CLAIM_UINT64 *claim)
+{
+ uint32_t i;
+
+ claim->value_count = 0;
+ claim->values = talloc_array(mem_ctx,
+ uint64_t,
+ principal_attribute->num_values);
+ if (claim->values == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ for (i = 0; i < principal_attribute->num_values; ++i) {
+ const struct ldb_val *value = &principal_attribute->values[i];
+ bool val = false;
+ int ret = ldb_val_as_bool(value, &val);
+ if (ret) {
+ char buf[1024];
+ const char *reason = NULL;
+ int err = strerror_r(ret, buf, sizeof(buf));
+ if (err == 0) {
+ reason = buf;
+ } else {
+ reason = "Unknown error";
+ }
+ DBG_WARNING("Failed to intepret value %s as BOOL "
+ "while creating claim %s for attribute %s (%s); "
+ "skipping value\n",
+ (value->data != NULL) ? (const char *)value->data : "<unknown>",
+ name.data, principal_attribute->name,
+ reason);
+ continue;
+ }
+
+ claim->values[i] = val;
+ ++claim->value_count;
+ }
+
+ /* Shrink the array to fit. */
+ claim->values = talloc_realloc(mem_ctx,
+ claim->values,
+ uint64_t,
+ claim->value_count);
+ if (claim->value_count && claim->values == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int fill_claim_string(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ const struct ldb_message_element *principal_attribute,
+ struct CLAIM_STRING *claim)
+{
+ uint32_t i;
+
+ claim->value_count = 0;
+ claim->values = talloc_array(mem_ctx,
+ const char *,
+ principal_attribute->num_values);
+ if (claim->values == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ for (i = 0; i < principal_attribute->num_values; ++i) {
+ const char *val = NULL;
+ const struct ldb_val *v = &principal_attribute->values[i];
+
+ if (v == NULL || v->data == NULL) {
+ continue;
+ }
+
+ val = talloc_strndup(mem_ctx,
+ (const char *)v->data,
+ v->length);
+ if (val == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ claim->values[i] = val;
+ ++claim->value_count;
+ }
+
+ /* Shrink the array to fit. */
+ claim->values = talloc_realloc(mem_ctx,
+ claim->values,
+ const char *,
+ claim->value_count);
+ if (claim->value_count && claim->values == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int fill_claim_string_sec_desc_syntax(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ const struct ldb_message_element *principal_attribute,
+ struct CLAIM_STRING *claim)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ const struct dom_sid *domain_sid = NULL;
+ uint32_t i;
+
+ claim->value_count = 0;
+ claim->values = talloc_array(mem_ctx,
+ const char *,
+ principal_attribute->num_values);
+ if (claim->values == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ domain_sid = samdb_domain_sid(ldb);
+ if (domain_sid == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ for (i = 0; i < principal_attribute->num_values; ++i) {
+ const struct ldb_val *v = &principal_attribute->values[i];
+
+ enum ndr_err_code ndr_err;
+ struct security_descriptor desc = {};
+ const char *sddl = NULL;
+
+ if (v == NULL || v->data == NULL) {
+ continue;
+ }
+
+ ndr_err = ndr_pull_struct_blob(v,
+ tmp_ctx,
+ &desc,
+ (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("security_descriptor pull failed: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ sddl = sddl_encode(mem_ctx,
+ &desc,
+ domain_sid);
+ if (sddl == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ claim->values[i] = sddl;
+ ++claim->value_count;
+ }
+
+ talloc_free(tmp_ctx);
+
+ /* Shrink the array to fit. */
+ claim->values = talloc_realloc(mem_ctx,
+ claim->values,
+ const char *,
+ claim->value_count);
+ if (claim->value_count && claim->values == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int fill_claim_entry(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ const struct dsdb_schema *schema,
+ const struct ldb_message_element *principal_attribute,
+ const struct ldb_val name,
+ const DATA_BLOB syntax,
+ enum CLAIM_TYPE claim_type,
+ struct CLAIM_ENTRY *claim_entry)
+{
+
+ claim_entry->id = (const char *)name.data;
+ claim_entry->type = claim_type;
+
+ switch (claim_type) {
+ case CLAIM_TYPE_INT64:
+ return fill_claim_int64(mem_ctx,
+ ldb,
+ principal_attribute,
+ name,
+ &claim_entry->values.claim_int64);
+ case CLAIM_TYPE_UINT64:
+ if (syntax.data != NULL && data_blob_equals_str(syntax, "2.5.5.2")) {
+ return fill_claim_uint64_oid_syntax(mem_ctx,
+ ldb,
+ schema,
+ principal_attribute,
+ name,
+ &claim_entry->values.claim_uint64);
+ } else {
+ return fill_claim_uint64(mem_ctx,
+ ldb,
+ principal_attribute,
+ name,
+ &claim_entry->values.claim_uint64);
+ }
+ case CLAIM_TYPE_BOOLEAN:
+ return fill_claim_boolean(mem_ctx,
+ ldb,
+ principal_attribute,
+ name,
+ &claim_entry->values.claim_boolean);
+ case CLAIM_TYPE_STRING:
+ default:
+ if (syntax.data != NULL && data_blob_equals_str(syntax, "2.5.5.15")) {
+ return fill_claim_string_sec_desc_syntax(mem_ctx,
+ ldb,
+ principal_attribute,
+ &claim_entry->values.claim_string);
+ } else {
+ return fill_claim_string(mem_ctx,
+ ldb,
+ principal_attribute,
+ &claim_entry->values.claim_string);
+ }
+ }
+}
+
+/*
+ * Determine wheter a claim applies to the most specific objectClass of the
+ * principal.
+ */
+static int claim_applies_to_class(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ const struct dsdb_schema *schema,
+ const struct ldb_message *claim_msg,
+ const uint32_t principal_class_id,
+ bool *applies)
+{
+ struct ldb_message_element *applies_to_class = NULL;
+ unsigned i;
+
+ applies_to_class = ldb_msg_find_element(claim_msg,
+ "msDS-ClaimTypeAppliesToClass");
+ if (applies_to_class == NULL) {
+ *applies = false;
+ return LDB_SUCCESS;
+ }
+
+ for (i = 0; i < applies_to_class->num_values; ++i) {
+ struct ldb_dn *class_dn = NULL;
+ const struct dsdb_class *class_val = NULL;
+ const struct ldb_val *class_rdn = NULL;
+
+ class_dn = ldb_dn_from_ldb_val(mem_ctx,
+ ldb,
+ &applies_to_class->values[i]);
+ if (class_dn == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ class_rdn = ldb_dn_get_rdn_val(class_dn);
+ if (class_rdn == NULL) {
+ TALLOC_FREE(class_dn);
+ continue;
+ }
+
+ class_val = dsdb_class_by_cn_ldb_val(schema, class_rdn);
+ TALLOC_FREE(class_dn);
+ if (class_val == NULL) {
+ continue;
+ }
+
+ if (class_val->governsID_id == principal_class_id) {
+ *applies = true;
+ return LDB_SUCCESS;
+ }
+ }
+
+ *applies = false;
+ return LDB_SUCCESS;
+}
+
+static inline struct ldb_val talloc_steal_ldb_val(TALLOC_CTX *mem_ctx, struct ldb_val val)
+{
+ val.data = talloc_steal(mem_ctx, val.data);
+ return val;
+}
+
+static uint32_t claim_get_value_count(const struct CLAIM_ENTRY *claim)
+{
+ switch (claim->type) {
+ case CLAIM_TYPE_INT64:
+ return claim->values.claim_int64.value_count;
+ case CLAIM_TYPE_UINT64:
+ return claim->values.claim_uint64.value_count;
+ case CLAIM_TYPE_STRING:
+ return claim->values.claim_string.value_count;
+ case CLAIM_TYPE_BOOLEAN:
+ return claim->values.claim_boolean.value_count;
+ }
+
+ smb_panic(__location__ ": unknown claim type");
+ return 0;
+}
+
+static int encode_claims_set(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ struct CLAIMS_SET *claims_set,
+ DATA_BLOB *claims_blob)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ enum ndr_err_code ndr_err;
+ struct CLAIMS_SET_NDR *claims_set_info = NULL;
+ struct CLAIMS_SET_METADATA *metadata = NULL;
+ struct CLAIMS_SET_METADATA_NDR *metadata_ndr = NULL;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ metadata_ndr = talloc_zero(tmp_ctx, struct CLAIMS_SET_METADATA_NDR);
+ if (metadata_ndr == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ metadata = talloc_zero(metadata_ndr, struct CLAIMS_SET_METADATA);
+ if (metadata == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ claims_set_info = talloc_zero(metadata, struct CLAIMS_SET_NDR);
+ if (claims_set_info == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ metadata_ndr->claims.metadata = metadata;
+
+ metadata->claims_set = claims_set_info;
+ metadata->compression_format = CLAIMS_COMPRESSION_FORMAT_XPRESS_HUFF;
+
+ claims_set_info->claims.claims = claims_set;
+
+ ndr_err = ndr_push_struct_blob(claims_blob, mem_ctx, metadata_ndr,
+ (ndr_push_flags_fn_t)ndr_push_CLAIMS_SET_METADATA_NDR);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("CLAIMS_SET_METADATA_NDR push failed: %s\n",
+ nt_errstr(nt_status));
+
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+static bool is_schema_dn(struct ldb_dn *dn,
+ struct ldb_dn *schema_dn)
+{
+ if (ldb_dn_get_comp_num(dn) != (ldb_dn_get_comp_num(schema_dn) + 1)) {
+ return false;
+ }
+
+ return ldb_dn_compare_base(schema_dn, dn) == 0;
+}
+
+static bool is_valid_claim_attribute_syntax(const DATA_BLOB source_syntax,
+ uint64_t claim_value_type)
+{
+ switch (claim_value_type) {
+ case CLAIM_TYPE_STRING:
+ if (data_blob_equals_str(source_syntax, "2.5.5.1")) {
+ return true;
+ }
+ if (data_blob_equals_str(source_syntax, "2.5.5.12")) {
+ return true;
+ }
+ if (data_blob_equals_str(source_syntax, "2.5.5.15")) {
+ return true;
+ }
+ break;
+ case CLAIM_TYPE_UINT64:
+ if (data_blob_equals_str(source_syntax, "2.5.5.2")) {
+ return true;
+ }
+ break;
+ case CLAIM_TYPE_INT64:
+ if (data_blob_equals_str(source_syntax, "2.5.5.9")) {
+ return true;
+ }
+ if (data_blob_equals_str(source_syntax, "2.5.5.16")) {
+ return true;
+ }
+ break;
+ case CLAIM_TYPE_BOOLEAN:
+ /* Note: MS-ADTS has a typo (2.2.5.8 instead of 2.5.5.8) */
+ if (data_blob_equals_str(source_syntax, "2.5.5.8")) {
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static int get_all_claims(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *principal_dn,
+ uint32_t principal_class_id,
+ DATA_BLOB *claims_blob)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ const struct dsdb_schema *schema = NULL;
+
+ struct ldb_dn *claim_config_container = NULL;
+ struct ldb_dn *claim_types_child = NULL;
+ struct ldb_dn *config_dn = ldb_get_config_basedn(ldb);
+ struct ldb_dn *schema_dn = ldb_get_schema_basedn(ldb);
+ bool ok;
+ int ret;
+ struct ldb_result *res = NULL;
+ static const char * const attrs[] = {
+ "Enabled",
+ "msDS-ClaimAttributeSource",
+ "msDS-ClaimSource",
+ "msDS-ClaimSourceType",
+ "msDS-ClaimTypeAppliesToClass",
+ "msDS-ClaimValueType",
+ "name",
+ NULL
+ };
+
+ const char **ad_claim_attrs = NULL;
+ unsigned int ad_claim_attrs_count;
+ struct ad_claim_info {
+ struct ldb_val name;
+ DATA_BLOB syntax;
+ const char *attribute;
+ enum CLAIM_TYPE claim_type;
+ } *ad_claims = NULL;
+ unsigned ad_claims_count;
+
+ unsigned i;
+
+ /* The structure which we'll use to build up the claims. */
+ struct CLAIMS_SET claims_set = {};
+
+ struct CLAIMS_ARRAY *ad_sourced_constructed = NULL;
+
+ *claims_blob = data_blob_null;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ schema = dsdb_get_schema(ldb, tmp_ctx);
+ if (schema == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ /* Get the DN of the claims container. */
+ claim_config_container = ldb_dn_copy(tmp_ctx, config_dn);
+ if (claim_config_container == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ claim_types_child = ldb_dn_new(tmp_ctx, ldb,
+ "CN=Claim Types,CN=Claims Configuration,CN=Services");
+ if (claim_types_child == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ ok = ldb_dn_add_child(claim_config_container, claim_types_child);
+ TALLOC_FREE(claim_types_child);
+ if (!ok) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ /* Search for the claims container's children. */
+ ret = ldb_search(ldb, tmp_ctx, &res,
+ claim_config_container,
+ LDB_SCOPE_ONELEVEL,
+ attrs, NULL);
+ if (ret) {
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ret = LDB_SUCCESS;
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /*
+ * Allocate enough space for all AD claim attributes, followed by space
+ * for a NULL marker (so it can be passed as the attributes filter to
+ * ldb_search()).
+ */
+ ad_claim_attrs = talloc_array(tmp_ctx,
+ const char *,
+ res->count + 1);
+ if (ad_claim_attrs == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+ ad_claims = talloc_array(tmp_ctx,
+ struct ad_claim_info,
+ res->count);
+ if (ad_claims == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+ ad_claims_count = ad_claim_attrs_count = 0;
+
+ /* Loop through each child of the claims container. */
+ for (i = 0; i < res->count; ++i) {
+ bool claim_applies = false;
+
+ int enabled;
+ uint64_t claim_value_type;
+
+ const char *claim_source_type = NULL;
+ const struct ldb_val *claim_attribute_source = NULL;
+
+ /*
+ * Does this claim apply to the most specific objectClass of the
+ * principal?
+ */
+ ret = claim_applies_to_class(tmp_ctx,
+ ldb,
+ schema,
+ res->msgs[i],
+ principal_class_id,
+ &claim_applies);
+ if (ret) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ if (!claim_applies) {
+ /* If the claim doesn't apply, skip it. */
+ continue;
+ }
+
+ enabled = ldb_msg_find_attr_as_bool(res->msgs[i], "Enabled", 0);
+ if (!enabled) {
+ /* If the claim isn't enabled, skip it. */
+ continue;
+ }
+
+ claim_value_type = ldb_msg_find_attr_as_uint64(res->msgs[i],
+ "msDS-ClaimValueType",
+ 0);
+ if (!claim_value_type) {
+ continue;
+ }
+
+ claim_source_type = ldb_msg_find_attr_as_string(res->msgs[i],
+ "msDS-ClaimSourceType",
+ "");
+
+ /* Get the attribute used by the claim. */
+ claim_attribute_source = ldb_msg_find_ldb_val(res->msgs[i],
+ "msDS-ClaimAttributeSource");
+
+ if (strcasecmp(claim_source_type, "AD") == 0) {
+ struct ldb_dn *claim_attribute_source_dn = NULL;
+ const struct ldb_val *claim_attribute_source_rdn = NULL;
+ const struct dsdb_attribute *claim_attribute_source_class = NULL;
+
+ DATA_BLOB source_syntax;
+ const char *attribute = NULL;
+ const struct ldb_val *name = NULL;
+
+ if (claim_attribute_source == NULL) {
+ continue;
+ }
+
+ claim_attribute_source_dn = ldb_val_as_dn(ldb,
+ tmp_ctx,
+ claim_attribute_source);
+ if (claim_attribute_source_dn == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ if (!is_schema_dn(claim_attribute_source_dn, schema_dn)) {
+ /* This DN doesn't belong to the schema. */
+ continue;
+ }
+
+ claim_attribute_source_rdn = ldb_dn_get_rdn_val(claim_attribute_source_dn);
+ if (claim_attribute_source_rdn == NULL) {
+ /* No RDN, skip it. */
+ continue;
+ }
+
+ claim_attribute_source_class = dsdb_attribute_by_cn_ldb_val(schema,
+ claim_attribute_source_rdn);
+ claim_attribute_source_rdn = NULL;
+ TALLOC_FREE(claim_attribute_source_dn);
+ if (claim_attribute_source_class == NULL) {
+ continue;
+ }
+
+ source_syntax = data_blob_string_const(claim_attribute_source_class->attributeSyntax_oid);
+ if (source_syntax.data == NULL) {
+ continue;
+ }
+
+ if (!is_valid_claim_attribute_syntax(source_syntax, claim_value_type)) {
+ continue;
+ }
+
+ attribute = claim_attribute_source_class->lDAPDisplayName;
+ if (attribute == NULL) {
+ continue;
+ }
+
+ ret = add_attr_unique(tmp_ctx,
+ ad_claim_attrs,
+ &ad_claim_attrs_count,
+ attribute);
+ if (ret) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ name = ldb_msg_find_ldb_val(res->msgs[i], "name");
+ if (name == NULL) {
+ name = &data_blob_null;
+ }
+
+ ad_claims[ad_claims_count++] = (struct ad_claim_info) {
+ .name = *name,
+ .syntax = source_syntax,
+ .attribute = attribute,
+ .claim_type = claim_value_type,
+ };
+ }
+ }
+
+ if (ad_claims_count) {
+ struct ldb_result *principal_res = NULL;
+ const struct ldb_message *principal_msg = NULL;
+
+ /* Shrink the arrays to remove any unused space. */
+ ad_claim_attrs = talloc_realloc(tmp_ctx,
+ ad_claim_attrs,
+ const char *,
+ ad_claim_attrs_count + 1);
+ if (ad_claim_attrs == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+ ad_claim_attrs[ad_claim_attrs_count] = NULL;
+
+ ad_claims = talloc_realloc(tmp_ctx,
+ ad_claims,
+ struct ad_claim_info,
+ ad_claims_count);
+ if (ad_claims == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ ret = ldb_search(ldb, tmp_ctx, &principal_res,
+ principal_dn,
+ LDB_SCOPE_BASE,
+ ad_claim_attrs, NULL);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Failed to find principal %s to construct claims\n",
+ ldb_dn_get_linearized(principal_dn));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ principal_msg = principal_res->msgs[0];
+
+ /*
+ * Ensure that only the attrs we asked for end up in the results
+ * (it's fine if some are missing)
+ */
+ SMB_ASSERT(principal_msg->num_elements <= ad_claim_attrs_count);
+
+ for (i = 0; i < ad_claims_count; ++i) {
+ const struct ldb_message_element *principal_attribute = NULL;
+ struct CLAIM_ENTRY *claim_entry = NULL;
+ uint32_t new_claims_array_count = claims_set.claims_array_count;
+
+ /* Get the value of the claim attribute for the principal. */
+ principal_attribute = ldb_msg_find_element(principal_res->msgs[0],
+ ad_claims[i].attribute);
+ if (principal_attribute == NULL) {
+ continue;
+ }
+
+ /* Add the claim to the array. */
+
+ if (ad_sourced_constructed == NULL) {
+ claims_set.claims_arrays = talloc_realloc(tmp_ctx,
+ claims_set.claims_arrays,
+ struct CLAIMS_ARRAY,
+ new_claims_array_count + 1);
+ if (claims_set.claims_arrays == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ ad_sourced_constructed = &claims_set.claims_arrays[new_claims_array_count++];
+ *ad_sourced_constructed = (struct CLAIMS_ARRAY) {
+ .claims_source_type = CLAIMS_SOURCE_TYPE_AD,
+ };
+ }
+
+ ad_sourced_constructed->claim_entries = talloc_realloc(
+ tmp_ctx,
+ ad_sourced_constructed->claim_entries,
+ struct CLAIM_ENTRY,
+ ad_sourced_constructed->claims_count + 1);
+ if (ad_sourced_constructed->claim_entries == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ claim_entry = &ad_sourced_constructed->claim_entries[
+ ad_sourced_constructed->claims_count];
+
+ ret = fill_claim_entry(ad_sourced_constructed->claim_entries,
+ ldb,
+ schema,
+ principal_attribute,
+ ad_claims[i].name,
+ ad_claims[i].syntax,
+ ad_claims[i].claim_type,
+ claim_entry);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (claim_get_value_count(claim_entry) > 0) {
+ /*
+ * If the claim contains values, add it to the
+ * array(s).
+ */
+ ++ad_sourced_constructed->claims_count;
+ claims_set.claims_array_count = new_claims_array_count;
+ }
+ }
+ }
+
+ if (claims_set.claims_array_count == 0) {
+ /* If we have no claims, we're done. */
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ /* Encode the claims ready to go into a PAC buffer. */
+ ret = encode_claims_set(ldb, mem_ctx, &claims_set, claims_blob);
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+int get_claims_for_principal(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *principal_dn,
+ DATA_BLOB *claims_blob)
+{
+ struct ldb_result *principal_res = NULL;
+ static const char * const principal_attrs[] = {
+ "objectClass",
+ NULL
+ };
+
+ struct ldb_message_element *principal_class_el = NULL;
+ struct dsdb_schema *schema = NULL;
+ const struct dsdb_class *principal_class = NULL;
+
+ int ret;
+
+ *claims_blob = data_blob_null;
+
+ ret = ldb_search(ldb, mem_ctx, &principal_res,
+ principal_dn,
+ LDB_SCOPE_BASE,
+ principal_attrs, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ principal_class_el = ldb_msg_find_element(principal_res->msgs[0],
+ "objectClass");
+ if (principal_class_el == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ schema = dsdb_get_schema(ldb, mem_ctx);
+ if (schema == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ principal_class = dsdb_get_last_structural_class(schema, principal_class_el);
+ if (principal_class == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ return get_all_claims(ldb,
+ mem_ctx,
+ principal_dn,
+ principal_class->governsID_id,
+ claims_blob);
+}