summaryrefslogtreecommitdiffstats
path: root/source4/kdc
diff options
context:
space:
mode:
Diffstat (limited to 'source4/kdc')
-rw-r--r--source4/kdc/ad_claims.c1225
-rw-r--r--source4/kdc/ad_claims.h36
-rw-r--r--source4/kdc/authn_policy_util.c1316
-rw-r--r--source4/kdc/authn_policy_util.h228
-rw-r--r--source4/kdc/db-glue.c3872
-rw-r--r--source4/kdc/db-glue.h113
-rw-r--r--source4/kdc/hdb-samba4-plugin.c85
-rw-r--r--source4/kdc/hdb-samba4.c1267
-rw-r--r--source4/kdc/kdc-glue.c92
-rw-r--r--source4/kdc/kdc-glue.h62
-rw-r--r--source4/kdc/kdc-heimdal.c550
-rw-r--r--source4/kdc/kdc-proxy.c596
-rw-r--r--source4/kdc/kdc-proxy.h45
-rw-r--r--source4/kdc/kdc-server.c623
-rw-r--r--source4/kdc/kdc-server.h83
-rw-r--r--source4/kdc/kdc-service-mit.c395
-rw-r--r--source4/kdc/kdc-service-mit.h27
-rw-r--r--source4/kdc/kpasswd-helper.c264
-rw-r--r--source4/kdc/kpasswd-helper.h48
-rw-r--r--source4/kdc/kpasswd-service-heimdal.c326
-rw-r--r--source4/kdc/kpasswd-service-mit.c402
-rw-r--r--source4/kdc/kpasswd-service.c395
-rw-r--r--source4/kdc/kpasswd-service.h43
-rw-r--r--source4/kdc/kpasswd_glue.c88
-rw-r--r--source4/kdc/kpasswd_glue.h31
-rw-r--r--source4/kdc/ktutil.c122
-rw-r--r--source4/kdc/mit-kdb/kdb_samba.c178
-rw-r--r--source4/kdc/mit-kdb/kdb_samba.h182
-rw-r--r--source4/kdc/mit-kdb/kdb_samba_change_pwd.c59
-rw-r--r--source4/kdc/mit-kdb/kdb_samba_common.c103
-rw-r--r--source4/kdc/mit-kdb/kdb_samba_masterkey.c69
-rw-r--r--source4/kdc/mit-kdb/kdb_samba_pac.c115
-rw-r--r--source4/kdc/mit-kdb/kdb_samba_policies.c397
-rw-r--r--source4/kdc/mit-kdb/kdb_samba_principals.c397
-rw-r--r--source4/kdc/mit-kdb/wscript_build22
-rw-r--r--source4/kdc/mit_kdc_irpc.c204
-rw-r--r--source4/kdc/mit_kdc_irpc.h20
-rw-r--r--source4/kdc/mit_samba.c1065
-rw-r--r--source4/kdc/mit_samba.h106
-rw-r--r--source4/kdc/pac-blobs.c253
-rw-r--r--source4/kdc/pac-blobs.h83
-rw-r--r--source4/kdc/pac-glue.c3248
-rw-r--r--source4/kdc/pac-glue.h202
-rw-r--r--source4/kdc/samba_kdc.h83
-rw-r--r--source4/kdc/sdb.c195
-rw-r--r--source4/kdc/sdb.h150
-rw-r--r--source4/kdc/sdb_to_hdb.c359
-rw-r--r--source4/kdc/sdb_to_kdb.c332
-rw-r--r--source4/kdc/wdc-samba4.c937
-rw-r--r--source4/kdc/wscript_build194
50 files changed, 21287 insertions, 0 deletions
diff --git a/source4/kdc/ad_claims.c b/source4/kdc/ad_claims.c
new file mode 100644
index 0000000..b9a417c
--- /dev/null
+++ b/source4/kdc/ad_claims.c
@@ -0,0 +1,1225 @@
+/*
+ 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 "source4/kdc/authn_policy_util.h"
+#include "ldb_module.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/ndr_krb5pac.h"
+#include "lib/util/binsearch.h"
+#include "auth/session.h"
+
+#undef strcasecmp
+
+bool ad_claims_are_issued(struct ldb_context *samdb)
+{
+ /*
+ * Claims aren’t issued by Samba unless the DC is at
+ * FL2012. This is to match Windows, which will offer
+ * this feature as soon as the DC is upgraded.
+ */
+ const int functional_level = dsdb_dc_functional_level(samdb);
+ return functional_level >= DS_DOMAIN_FUNCTION_2012;
+}
+
+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 interpret 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 interpret 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 interpret 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(claim->values,
+ (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 = talloc_strndup(mem_ctx,
+ (const char *)name.data,
+ name.length);
+ if (claim_entry->id == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ 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 whether 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;
+}
+
+struct assigned_silo {
+ const char *name;
+ bool is_initialised;
+ bool is_assigned;
+};
+
+static struct assigned_silo new_assigned_silo(void)
+{
+ return (struct assigned_silo) {
+ .name = NULL,
+ .is_initialised = false,
+ .is_assigned = false,
+ };
+}
+
+static bool silo_is_maybe_assigned(struct assigned_silo silo)
+{
+ return !silo.is_initialised || silo.is_assigned;
+}
+
+static int get_assigned_silo(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *principal,
+ struct assigned_silo *assigned_silo)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ int ret;
+
+ const struct ldb_message *silo_msg = NULL;
+ static const char * const silo_attrs[] = {
+ "msDS-AuthNPolicySiloEnforced",
+ "msDS-AuthNPolicySiloMembers",
+ "name",
+ NULL
+ };
+
+ bool is_silo_enforced = false;
+ const char *silo_name = NULL;
+
+ if (assigned_silo->is_initialised) {
+ return LDB_SUCCESS;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ if (!authn_policy_silos_and_policies_in_effect(ldb)) {
+ /* No assigned silo. */
+ assigned_silo->is_assigned = false;
+ assigned_silo->is_initialised = true;
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ /* Check whether the user is assigned to an enforced silo. */
+ ret = authn_policy_get_assigned_silo(ldb,
+ tmp_ctx,
+ principal,
+ silo_attrs,
+ &silo_msg,
+ &is_silo_enforced);
+ if (ret) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (silo_msg == NULL || !is_silo_enforced) {
+ /* No assigned silo. */
+ assigned_silo->is_assigned = false;
+ assigned_silo->is_initialised = true;
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ /* The user does belong to a silo, so return the name of the silo. */
+ silo_name = ldb_msg_find_attr_as_string(silo_msg,
+ "name",
+ NULL);
+ assigned_silo->name = talloc_steal(mem_ctx, silo_name);
+ assigned_silo->is_assigned = true;
+ assigned_silo->is_initialised = true;
+
+ talloc_free(tmp_ctx);
+ 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 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,
+ const struct ldb_message *principal,
+ uint32_t principal_class_id,
+ struct CLAIMS_SET **claims_set_out)
+{
+ 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 = NULL;
+
+ struct CLAIMS_ARRAY *ad_sourced_constructed = NULL;
+
+ struct assigned_silo assigned_silo = new_assigned_silo();
+
+ *claims_set_out = NULL;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ claims_set = talloc_zero(tmp_ctx, struct CLAIMS_SET);
+ if (claims_set == NULL) {
+ talloc_free(tmp_ctx);
+ 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 an
+ * 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;
+ const char *claim_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");
+
+ claim_source = ldb_msg_find_attr_as_string(res->msgs[i],
+ "msDS-ClaimSource",
+ NULL);
+
+ 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,
+ };
+ } else if (silo_is_maybe_assigned(assigned_silo)
+ && strcasecmp(claim_source_type, "Constructed") == 0)
+ {
+ const struct ldb_val *name = NULL;
+ struct CLAIM_STRING *claim = NULL;
+ struct CLAIM_ENTRY *claim_entry = NULL;
+ const char *claim_value = NULL;
+
+ if (claim_attribute_source != NULL) {
+ continue;
+ }
+
+ if (claim_source != NULL) {
+ continue;
+ }
+
+ name = ldb_msg_find_ldb_val(res->msgs[i], "name");
+ if (name == NULL || name->data == NULL) {
+ continue;
+ }
+ /* Does the claim ID match exactly in case? */
+ if (strcmp((const char *)name->data, "ad://ext/AuthenticationSilo") != 0) {
+ continue;
+ }
+
+ ret = get_assigned_silo(ldb, tmp_ctx, principal, &assigned_silo);
+ if (ret) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ if (!assigned_silo.is_assigned) {
+ continue;
+ }
+
+ if (ad_sourced_constructed == NULL) {
+ claims_set->claims_arrays = talloc_realloc(claims_set,
+ claims_set->claims_arrays,
+ struct CLAIMS_ARRAY,
+ claims_set->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[claims_set->claims_array_count++];
+ *ad_sourced_constructed = (struct CLAIMS_ARRAY) {
+ .claims_source_type = CLAIMS_SOURCE_TYPE_AD,
+ };
+ }
+
+ /* Add the claim to the array. */
+ ad_sourced_constructed->claim_entries = talloc_realloc(
+ claims_set->claims_arrays,
+ 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++];
+
+ /* Fill in the claim details and return the claim. */
+ claim_entry->id = "ad://ext/AuthenticationSilo";
+ claim_entry->type = CLAIM_TYPE_STRING;
+
+ claim = &claim_entry->values.claim_string;
+
+ claim->value_count = 1;
+ claim->values = talloc_array(ad_sourced_constructed->claim_entries,
+ const char *,
+ claim->value_count);
+ if (claim->values == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ claim_value = talloc_strdup(claim->values, assigned_silo.name);
+ if (claim_value == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ claim->values[0] = claim_value;
+ }
+ }
+
+ if (ad_claims_count) {
+ 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 = dsdb_search_one(ldb,
+ tmp_ctx,
+ &principal_msg,
+ principal->dn,
+ LDB_SCOPE_BASE,
+ ad_claim_attrs,
+ 0,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ const char *dn = ldb_dn_get_linearized(principal->dn);
+ DBG_ERR("Failed to find principal %s to construct claims\n",
+ dn != NULL ? dn : "<NULL>");
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /*
+ * 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_msg,
+ 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(claims_set,
+ 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(
+ claims_set->claims_arrays,
+ 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) {
+ *claims_set_out = talloc_steal(mem_ctx, claims_set);
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+int get_claims_set_for_principal(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *principal,
+ struct CLAIMS_SET **claims_set_out)
+{
+ struct ldb_message_element *principal_class_el = NULL;
+ struct dsdb_schema *schema = NULL;
+ const struct dsdb_class *principal_class = NULL;
+
+ *claims_set_out = NULL;
+
+ if (!ad_claims_are_issued(ldb)) {
+ return LDB_SUCCESS;
+ }
+
+ principal_class_el = ldb_msg_find_element(principal,
+ "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,
+ principal_class->governsID_id,
+ claims_set_out);
+}
diff --git a/source4/kdc/ad_claims.h b/source4/kdc/ad_claims.h
new file mode 100644
index 0000000..e54b1da
--- /dev/null
+++ b/source4/kdc/ad_claims.h
@@ -0,0 +1,36 @@
+/*
+ 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/>.
+*/
+
+#ifndef KDC_AD_CLAIMS_H
+#define KDC_AD_CLAIMS_H
+
+#include "lib/util/data_blob.h"
+#include "ldb.h"
+
+struct CLAIMS_SET;
+
+bool ad_claims_are_issued(struct ldb_context *samdb);
+
+int get_claims_set_for_principal(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *principal,
+ struct CLAIMS_SET **claims_set_out);
+
+#endif
diff --git a/source4/kdc/authn_policy_util.c b/source4/kdc/authn_policy_util.c
new file mode 100644
index 0000000..60de61a
--- /dev/null
+++ b/source4/kdc/authn_policy_util.c
@@ -0,0 +1,1316 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba Active Directory authentication policy 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 "source4/kdc/authn_policy_util.h"
+#include "auth/authn_policy_impl.h"
+#include "lib/util/debug.h"
+#include "lib/util/samba_util.h"
+#include "libcli/security/security.h"
+#include "libcli/util/werror.h"
+#include "auth/common_auth.h"
+#include "source4/auth/session.h"
+#include "source4/dsdb/samdb/samdb.h"
+#include "source4/dsdb/samdb/ldb_modules/util.h"
+
+bool authn_policy_silos_and_policies_in_effect(struct ldb_context *samdb)
+{
+ /*
+ * Authentication Silos and Authentication Policies are not
+ * honoured by Samba unless the DC is at FL 2012 R2. This is
+ * to match Windows, which will offer these features as soon
+ * as the DC is upgraded.
+ */
+ const int functional_level = dsdb_dc_functional_level(samdb);
+ return functional_level >= DS_DOMAIN_FUNCTION_2012_R2;
+}
+
+bool authn_policy_allowed_ntlm_network_auth_in_effect(struct ldb_context *samdb)
+{
+ /*
+ * The allowed NTLM network authentication Policies are not
+ * honoured by Samba unless the DC is at FL2016. This
+ * is to match Windows, which will enforce these restrictions
+ * as soon as the DC is upgraded.
+ */
+ const int functional_level = dsdb_dc_functional_level(samdb);
+ return functional_level >= DS_DOMAIN_FUNCTION_2016;
+}
+
+/*
+ * Depending on the type of the account, we need to refer to different
+ * attributes of authentication silo objects. This structure keeps track of the
+ * attributes to use for a certain account type.
+ */
+struct authn_silo_attrs {
+ const char *policy;
+ const char *attrs[];
+};
+
+/*
+ * Depending on the type of the account, we need to refer to different
+ * attributes of authentication policy objects. This structure keeps track of
+ * the attributes to use for a certain account type.
+ */
+struct authn_policy_attrs {
+ /* This applies at FL2016 and up. */
+ const char *allowed_ntlm_network_auth;
+ /* The remainder apply at FL2012_R2 and up. */
+ const char *allowed_to_authenticate_from;
+ const char *allowed_to_authenticate_to;
+ const char *tgt_lifetime;
+ const char *attrs[];
+};
+
+struct authn_attrs {
+ const struct authn_silo_attrs *silo;
+ const struct authn_policy_attrs *policy;
+};
+
+/*
+ * Get the authentication attributes that apply to an account of a certain
+ * class.
+ */
+static const struct authn_attrs authn_policy_get_attrs(const struct ldb_message *msg)
+{
+ const struct authn_attrs null_authn_attrs = {
+ .silo = NULL,
+ .policy = NULL,
+ };
+ const struct ldb_message_element *objectclass_el = NULL;
+ unsigned i;
+
+ objectclass_el = ldb_msg_find_element(msg, "objectClass");
+ if (objectclass_el == NULL || objectclass_el->num_values == 0) {
+ return null_authn_attrs;
+ }
+
+ /*
+ * Iterate over the objectClasses, starting at the most-derived class.
+ */
+ for (i = objectclass_el->num_values; i > 0; --i) {
+ const struct ldb_val *objectclass_val = &objectclass_el->values[i - 1];
+ const char *objectclass = NULL;
+
+ objectclass = (const char *)objectclass_val->data;
+ if (objectclass == NULL) {
+ continue;
+ }
+
+#define COMMON_AUTHN_SILO_ATTRS \
+ "msDS-AuthNPolicySiloEnforced", \
+ "msDS-AuthNPolicySiloMembers", \
+ "name"
+
+#define COMMON_AUTHN_POLICY_ATTRS \
+ "msDS-AuthNPolicyEnforced", \
+ "msDS-StrongNTLMPolicy", \
+ "name"
+
+ /*
+ * See which of three classes this object is most closely
+ * derived from.
+ */
+ if (strcasecmp(objectclass, "user") == 0) {
+ static const struct authn_silo_attrs user_authn_silo_attrs = {
+ .policy = "msDS-UserAuthNPolicy",
+ .attrs = {
+ COMMON_AUTHN_SILO_ATTRS,
+ "msDS-UserAuthNPolicy",
+ NULL,
+ },
+ };
+
+ static const struct authn_policy_attrs user_authn_policy_attrs = {
+ .allowed_ntlm_network_auth = "msDS-UserAllowedNTLMNetworkAuthentication",
+ .allowed_to_authenticate_from = "msDS-UserAllowedToAuthenticateFrom",
+ .allowed_to_authenticate_to = "msDS-UserAllowedToAuthenticateTo",
+ .tgt_lifetime = "msDS-UserTGTLifetime",
+ .attrs = {
+ COMMON_AUTHN_POLICY_ATTRS,
+ "msDS-UserAllowedNTLMNetworkAuthentication",
+ "msDS-UserAllowedToAuthenticateFrom",
+ "msDS-UserAllowedToAuthenticateTo",
+ "msDS-UserTGTLifetime",
+ NULL,
+ },
+ };
+
+ return (struct authn_attrs) {
+ .silo = &user_authn_silo_attrs,
+ .policy = &user_authn_policy_attrs,
+ };
+ }
+
+ if (strcasecmp(objectclass, "computer") == 0) {
+ static const struct authn_silo_attrs computer_authn_silo_attrs = {
+ .policy = "msDS-ComputerAuthNPolicy",
+ .attrs = {
+ COMMON_AUTHN_SILO_ATTRS,
+ "msDS-ComputerAuthNPolicy",
+ NULL,
+ },
+ };
+
+ static const struct authn_policy_attrs computer_authn_policy_attrs = {
+ .allowed_ntlm_network_auth = NULL,
+ .allowed_to_authenticate_from = NULL,
+ .allowed_to_authenticate_to = "msDS-ComputerAllowedToAuthenticateTo",
+ .tgt_lifetime = "msDS-ComputerTGTLifetime",
+ .attrs = {
+ COMMON_AUTHN_POLICY_ATTRS,
+ "msDS-ComputerAllowedToAuthenticateTo",
+ "msDS-ComputerTGTLifetime",
+ NULL,
+ },
+ };
+
+ return (struct authn_attrs) {
+ .silo = &computer_authn_silo_attrs,
+ .policy = &computer_authn_policy_attrs,
+ };
+ }
+
+ if (strcasecmp(objectclass, "msDS-ManagedServiceAccount") == 0) {
+ static const struct authn_silo_attrs service_authn_silo_attrs = {
+ .policy = "msDS-ServiceAuthNPolicy",
+ .attrs = {
+ COMMON_AUTHN_SILO_ATTRS,
+ "msDS-ServiceAuthNPolicy",
+ NULL,
+ },
+ };
+
+ static const struct authn_policy_attrs service_authn_policy_attrs = {
+ .allowed_ntlm_network_auth = "msDS-ServiceAllowedNTLMNetworkAuthentication",
+ .allowed_to_authenticate_from = "msDS-ServiceAllowedToAuthenticateFrom",
+ .allowed_to_authenticate_to = "msDS-ServiceAllowedToAuthenticateTo",
+ .tgt_lifetime = "msDS-ServiceTGTLifetime",
+ .attrs = {
+ COMMON_AUTHN_POLICY_ATTRS,
+ "msDS-ServiceAllowedNTLMNetworkAuthentication",
+ "msDS-ServiceAllowedToAuthenticateFrom",
+ "msDS-ServiceAllowedToAuthenticateTo",
+ "msDS-ServiceTGTLifetime",
+ NULL,
+ },
+ };
+
+ return (struct authn_attrs) {
+ .silo = &service_authn_silo_attrs,
+ .policy = &service_authn_policy_attrs,
+ };
+ }
+ }
+
+#undef COMMON_AUTHN_SILO_ATTRS
+#undef COMMON_AUTHN_POLICY_ATTRS
+
+ /* No match — this object is not a user. */
+ return null_authn_attrs;
+}
+
+/*
+ * Look up the silo assigned to an account. If one exists, returns its details
+ * and whether it is enforced or not. ‘silo_attrs’ comprises the attributes to
+ * include in the search result, the relevant set of which can differ depending
+ * on the account’s objectClass.
+ */
+int authn_policy_get_assigned_silo(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ const char * const *silo_attrs,
+ const struct ldb_message **silo_msg_out,
+ bool *is_enforced)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ int ret = 0;
+ const struct ldb_message_element *authn_silo = NULL;
+ struct ldb_dn *authn_silo_dn = NULL;
+ struct ldb_message *authn_silo_msg = NULL;
+ const struct ldb_message_element *members = NULL;
+ const char *linearized_dn = NULL;
+ struct ldb_val linearized_dn_val;
+
+ *silo_msg_out = NULL;
+ *is_enforced = true;
+
+ if (!authn_policy_silos_and_policies_in_effect(samdb)) {
+ return 0;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ authn_silo = ldb_msg_find_element(msg, "msDS-AssignedAuthNPolicySilo");
+ /* Is the account assigned to a silo? */
+ if (authn_silo == NULL || !authn_silo->num_values) {
+ goto out;
+ }
+
+ authn_silo_dn = ldb_dn_from_ldb_val(tmp_ctx, samdb, &authn_silo->values[0]);
+ if (authn_silo_dn == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ ret = dsdb_search_one(samdb,
+ tmp_ctx,
+ &authn_silo_msg,
+ authn_silo_dn,
+ LDB_SCOPE_BASE,
+ silo_attrs,
+ 0, NULL);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ /* Not found. */
+ ret = 0;
+ goto out;
+ }
+ if (ret) {
+ goto out;
+ }
+
+ members = ldb_msg_find_element(authn_silo_msg,
+ "msDS-AuthNPolicySiloMembers");
+ if (members == NULL) {
+ goto out;
+ }
+
+ linearized_dn = ldb_dn_get_linearized(msg->dn);
+ if (linearized_dn == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ linearized_dn_val = data_blob_string_const(linearized_dn);
+ /* Is the account a member of the silo? */
+ if (!ldb_msg_find_val(members, &linearized_dn_val)) {
+ goto out;
+ }
+
+ /* Is the silo actually enforced? */
+ *is_enforced = ldb_msg_find_attr_as_bool(
+ authn_silo_msg,
+ "msDS-AuthNPolicySiloEnforced",
+ false);
+
+ *silo_msg_out = talloc_move(mem_ctx, &authn_silo_msg);
+
+out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ * Look up the authentication policy assigned to an account, returning its
+ * details if it exists. ‘authn_attrs’ specifies which attributes are relevant,
+ * and should be chosen based on the account’s objectClass.
+ */
+static int samba_kdc_authn_policy_msg(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ const struct authn_attrs authn_attrs,
+ struct ldb_message **authn_policy_msg_out,
+ struct authn_policy *authn_policy_out)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ int ret = 0;
+ const struct ldb_message *authn_silo_msg = NULL;
+ const struct ldb_message_element *authn_policy = NULL;
+ const char *silo_name = NULL;
+ const char *policy_name = NULL;
+ struct ldb_dn *authn_policy_dn = NULL;
+ struct ldb_message *authn_policy_msg = NULL;
+ bool belongs_to_silo = false;
+ bool is_enforced = true;
+
+ *authn_policy_msg_out = NULL;
+ *authn_policy_out = (struct authn_policy) {};
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ /* See whether the account is assigned to a silo. */
+ ret = authn_policy_get_assigned_silo(samdb,
+ tmp_ctx,
+ msg,
+ authn_attrs.silo->attrs,
+ &authn_silo_msg,
+ &is_enforced);
+ if (ret) {
+ goto out;
+ }
+
+ if (authn_silo_msg != NULL) {
+ belongs_to_silo = true;
+
+ silo_name = ldb_msg_find_attr_as_string(authn_silo_msg, "name", NULL);
+
+ /* Get the applicable authentication policy. */
+ authn_policy = ldb_msg_find_element(
+ authn_silo_msg,
+ authn_attrs.silo->policy);
+ } else {
+ /*
+ * If no silo is assigned, take the policy that is directly
+ * assigned to the account.
+ */
+ authn_policy = ldb_msg_find_element(msg, "msDS-AssignedAuthNPolicy");
+ }
+
+ if (authn_policy == NULL || !authn_policy->num_values) {
+ /* No policy applies; we’re done. */
+ goto out;
+ }
+
+ authn_policy_dn = ldb_dn_from_ldb_val(tmp_ctx, samdb, &authn_policy->values[0]);
+ if (authn_policy_dn == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ /* Look up the policy object. */
+ ret = dsdb_search_one(samdb,
+ tmp_ctx,
+ &authn_policy_msg,
+ authn_policy_dn,
+ LDB_SCOPE_BASE,
+ authn_attrs.policy->attrs,
+ 0, NULL);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ /* Not found. */
+ ret = 0;
+ goto out;
+ }
+ if (ret) {
+ goto out;
+ }
+
+ policy_name = ldb_msg_find_attr_as_string(authn_policy_msg, "name", NULL);
+
+ if (!belongs_to_silo) {
+ is_enforced = ldb_msg_find_attr_as_bool(
+ authn_policy_msg,
+ "msDS-AuthNPolicyEnforced",
+ false);
+ }
+
+ authn_policy_out->silo_name = talloc_move(mem_ctx, &silo_name);
+ authn_policy_out->policy_name = talloc_move(mem_ctx, &policy_name);
+ authn_policy_out->enforced = is_enforced;
+
+ *authn_policy_msg_out = talloc_move(mem_ctx, &authn_policy_msg);
+
+out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ * Reference an existing authentication policy onto a talloc context, returning
+ * ‘true’ on success.
+ */
+static bool authn_policy_ref(TALLOC_CTX *mem_ctx,
+ struct authn_policy *policy_out,
+ const struct authn_policy *policy)
+{
+ const char *silo_name = NULL;
+ const char *policy_name = NULL;
+
+ if (policy->silo_name != NULL) {
+ silo_name = talloc_strdup(mem_ctx, policy->silo_name);
+ if (silo_name == NULL) {
+ return false;
+ }
+ }
+
+ if (policy->policy_name != NULL) {
+ policy_name = talloc_strdup(mem_ctx, policy->policy_name);
+ if (policy_name == NULL) {
+ /*
+ * We can’t free ‘silo_name’ here, as it is declared
+ * const. It will be freed with the parent context.
+ */
+ return false;
+ }
+ }
+
+ *policy_out = (struct authn_policy) {
+ .silo_name = silo_name,
+ .policy_name = policy_name,
+ .enforced = policy->enforced,
+ };
+
+ return true;
+}
+
+/* Create a structure containing auditing information. */
+static NTSTATUS _authn_policy_audit_info(TALLOC_CTX *mem_ctx,
+ const struct authn_policy *policy,
+ const struct authn_int64_optional tgt_lifetime_raw,
+ const struct auth_user_info_dc *client_info,
+ const enum authn_audit_event event,
+ const enum authn_audit_reason reason,
+ const NTSTATUS policy_status,
+ const char *location,
+ struct authn_audit_info **audit_info_out)
+{
+ struct authn_audit_info *audit_info = NULL;
+ bool ok;
+
+ if (audit_info_out == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ audit_info = talloc_zero(mem_ctx, struct authn_audit_info);
+ if (audit_info == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (client_info != NULL) {
+ /*
+ * Keep a reference to the client’s user information so that it
+ * is available to be logged later.
+ */
+ audit_info->client_info = talloc_reference(audit_info, client_info);
+ if (audit_info->client_info == NULL) {
+ talloc_free(audit_info);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (policy != NULL) {
+ audit_info->policy = talloc_zero(audit_info, struct authn_policy);
+ if (audit_info->policy == NULL) {
+ talloc_free(audit_info);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = authn_policy_ref(audit_info, audit_info->policy, policy);
+ if (!ok) {
+ talloc_free(audit_info);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ audit_info->event = event;
+ audit_info->reason = reason;
+ audit_info->policy_status = policy_status;
+ audit_info->location = location;
+ audit_info->tgt_lifetime_raw = tgt_lifetime_raw;
+
+ *audit_info_out = audit_info;
+ return NT_STATUS_OK;
+}
+
+/* Create a structure containing auditing information. */
+#define authn_policy_audit_info( \
+ mem_ctx, \
+ policy, \
+ tgt_lifetime_raw, \
+ client_info, \
+ event, \
+ reason, \
+ policy_status, \
+ audit_info_out) \
+ _authn_policy_audit_info( \
+ mem_ctx, \
+ policy, \
+ tgt_lifetime_raw, \
+ client_info, \
+ event, \
+ reason, \
+ policy_status, \
+ __location__, \
+ audit_info_out)
+
+/*
+ * Perform an access check against the security descriptor set in an
+ * authentication policy. ‘client_info’ must be talloc-allocated so that we can
+ * make a reference to it.
+ */
+static NTSTATUS _authn_policy_access_check(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct loadparm_context* lp_ctx,
+ const struct auth_user_info_dc *client_info,
+ const struct auth_user_info_dc *device_info,
+ const struct auth_claims auth_claims,
+ const struct authn_policy *policy,
+ const struct authn_int64_optional tgt_lifetime_raw,
+ const enum authn_audit_event restriction_event,
+ const struct authn_policy_flags authn_policy_flags,
+ const DATA_BLOB *descriptor_blob,
+ const char *location,
+ struct authn_audit_info **audit_info_out)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ NTSTATUS status2;
+ enum ndr_err_code ndr_err;
+ struct security_descriptor *descriptor = NULL;
+ struct security_token *security_token = NULL;
+ uint32_t session_info_flags =
+ AUTH_SESSION_INFO_DEFAULT_GROUPS |
+ AUTH_SESSION_INFO_DEVICE_DEFAULT_GROUPS |
+ AUTH_SESSION_INFO_SIMPLE_PRIVILEGES;
+ const uint32_t access_desired = SEC_ADS_CONTROL_ACCESS;
+ uint32_t access_granted;
+ enum authn_audit_event event = restriction_event;
+ enum authn_audit_reason reason = AUTHN_AUDIT_REASON_NONE;
+
+ if (audit_info_out != NULL) {
+ *audit_info_out = NULL;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ if (!(client_info->info->user_flags & NETLOGON_GUEST)) {
+ session_info_flags |= AUTH_SESSION_INFO_AUTHENTICATED;
+ }
+
+ if (device_info != NULL && !(device_info->info->user_flags & NETLOGON_GUEST)) {
+ session_info_flags |= AUTH_SESSION_INFO_DEVICE_AUTHENTICATED;
+ }
+
+ if (authn_policy_flags.force_compounded_authentication) {
+ session_info_flags |= AUTH_SESSION_INFO_FORCE_COMPOUNDED_AUTHENTICATION;
+ }
+
+ descriptor = talloc(tmp_ctx, struct security_descriptor);
+ if (descriptor == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ndr_err = ndr_pull_struct_blob(descriptor_blob,
+ tmp_ctx,
+ descriptor,
+ (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("Failed to unmarshall "
+ "security descriptor for authentication policy: %s\n",
+ nt_errstr(status));
+ reason = AUTHN_AUDIT_REASON_DESCRIPTOR_INVALID;
+ goto out;
+ }
+
+ /* Require that the security descriptor has an owner set. */
+ if (descriptor->owner_sid == NULL) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ reason = AUTHN_AUDIT_REASON_DESCRIPTOR_NO_OWNER;
+ goto out;
+ }
+
+ status = auth_generate_security_token(tmp_ctx,
+ lp_ctx,
+ samdb,
+ client_info,
+ device_info,
+ auth_claims,
+ session_info_flags,
+ &security_token);
+ if (!NT_STATUS_IS_OK(status)) {
+ reason = AUTHN_AUDIT_REASON_SECURITY_TOKEN_FAILURE;
+ goto out;
+ }
+
+ status = sec_access_check_ds(descriptor, security_token,
+ access_desired, &access_granted,
+ NULL, NULL);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ status = NT_STATUS_AUTHENTICATION_FIREWALL_FAILED;
+ reason = AUTHN_AUDIT_REASON_ACCESS_DENIED;
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ event = AUTHN_AUDIT_EVENT_OK;
+out:
+ /*
+ * Create the structure with auditing information here while we have all
+ * the relevant information to hand. It will contain references to
+ * information regarding the client and the policy, to be consulted
+ * after the referents have possibly been freed.
+ */
+ status2 = _authn_policy_audit_info(mem_ctx,
+ policy,
+ tgt_lifetime_raw,
+ client_info,
+ event,
+ reason,
+ status,
+ location,
+ audit_info_out);
+ if (!NT_STATUS_IS_OK(status2)) {
+ status = status2;
+ } else if (!authn_policy_is_enforced(policy)) {
+ status = NT_STATUS_OK;
+ }
+
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+#define authn_policy_access_check(mem_ctx, \
+ samdb, \
+ lp_ctx, \
+ client_info, \
+ device_info, \
+ auth_claims, \
+ policy, \
+ tgt_lifetime_raw, \
+ restriction_event, \
+ authn_policy_flags, \
+ descriptor_blob, \
+ audit_info_out) \
+ _authn_policy_access_check(mem_ctx, \
+ samdb, \
+ lp_ctx, \
+ client_info, \
+ device_info, \
+ auth_claims, \
+ policy, \
+ tgt_lifetime_raw, \
+ restriction_event, \
+ authn_policy_flags, \
+ descriptor_blob, \
+ __location__, \
+ audit_info_out)
+
+/* Return an authentication policy moved onto a talloc context. */
+static struct authn_policy authn_policy_move(TALLOC_CTX *mem_ctx,
+ struct authn_policy *policy)
+{
+ return (struct authn_policy) {
+ .silo_name = talloc_move(mem_ctx, &policy->silo_name),
+ .policy_name = talloc_move(mem_ctx, &policy->policy_name),
+ .enforced = policy->enforced,
+ };
+}
+
+/* Authentication policies for Kerberos clients. */
+
+/*
+ * Get the applicable authentication policy for an account acting as a Kerberos
+ * client.
+ */
+int authn_policy_kerberos_client(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ const struct authn_kerberos_client_policy **policy_out)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ int ret = 0;
+ struct authn_attrs authn_attrs;
+ struct ldb_message *authn_policy_msg = NULL;
+ struct authn_kerberos_client_policy *client_policy = NULL;
+ struct authn_policy policy;
+
+ *policy_out = NULL;
+
+ if (!authn_policy_silos_and_policies_in_effect(samdb)) {
+ return 0;
+ }
+
+ /*
+ * Get the silo and policy attributes that apply to objects of this
+ * account’s objectclass.
+ */
+ authn_attrs = authn_policy_get_attrs(msg);
+ if (authn_attrs.silo == NULL || authn_attrs.policy == NULL) {
+ /*
+ * No applicable silo or policy attributes (somehow). Either
+ * this account isn’t derived from ‘user’, or the message is
+ * missing an objectClass element.
+ */
+ goto out;
+ }
+
+ if (authn_attrs.policy->allowed_to_authenticate_from == NULL &&
+ authn_attrs.policy->tgt_lifetime == NULL)
+ {
+ /* No relevant policy attributes apply. */
+ goto out;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ ret = samba_kdc_authn_policy_msg(samdb,
+ tmp_ctx,
+ msg,
+ authn_attrs,
+ &authn_policy_msg,
+ &policy);
+ if (ret) {
+ goto out;
+ }
+
+ if (authn_policy_msg == NULL) {
+ /* No policy applies. */
+ goto out;
+ }
+
+ client_policy = talloc_zero(tmp_ctx, struct authn_kerberos_client_policy);
+ if (client_policy == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ client_policy->policy = authn_policy_move(client_policy, &policy);
+
+ if (authn_attrs.policy->allowed_to_authenticate_from != NULL) {
+ const struct ldb_val *allowed_from = ldb_msg_find_ldb_val(
+ authn_policy_msg,
+ authn_attrs.policy->allowed_to_authenticate_from);
+
+ if (allowed_from != NULL && allowed_from->data != NULL) {
+ client_policy->allowed_to_authenticate_from = data_blob_const(
+ talloc_steal(client_policy, allowed_from->data),
+ allowed_from->length);
+ }
+ }
+
+ if (authn_attrs.policy->tgt_lifetime != NULL) {
+ client_policy->tgt_lifetime_raw = ldb_msg_find_attr_as_int64(
+ authn_policy_msg,
+ authn_attrs.policy->tgt_lifetime,
+ 0);
+ }
+
+ *policy_out = talloc_move(mem_ctx, &client_policy);
+
+out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/* Get device restrictions enforced by an authentication policy. */
+static const DATA_BLOB *authn_policy_kerberos_device_restrictions(const struct authn_kerberos_client_policy *policy)
+{
+ const DATA_BLOB *restrictions = NULL;
+
+ if (policy == NULL) {
+ return NULL;
+ }
+
+ restrictions = &policy->allowed_to_authenticate_from;
+ if (restrictions->data == NULL) {
+ return NULL;
+ }
+
+ return restrictions;
+}
+
+/* Return whether an authentication policy enforces device restrictions. */
+bool authn_policy_device_restrictions_present(const struct authn_kerberos_client_policy *policy)
+{
+ return authn_policy_kerberos_device_restrictions(policy) != NULL;
+}
+
+/*
+ * Perform an access check for the device with which the client is
+ * authenticating. ‘device_info’ must be talloc-allocated so that we can make a
+ * reference to it.
+ */
+NTSTATUS authn_policy_authenticate_from_device(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct loadparm_context* lp_ctx,
+ const struct auth_user_info_dc *device_info,
+ const struct auth_claims auth_claims,
+ const struct authn_kerberos_client_policy *client_policy,
+ struct authn_audit_info **client_audit_info_out)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ const DATA_BLOB *restrictions = NULL;
+
+ restrictions = authn_policy_kerberos_device_restrictions(client_policy);
+ if (restrictions == NULL) {
+ goto out;
+ }
+
+ status = authn_policy_access_check(mem_ctx,
+ samdb,
+ lp_ctx,
+ device_info,
+ /* The device itself has no device. */
+ NULL /* device_info */,
+ auth_claims,
+ &client_policy->policy,
+ authn_int64_some(client_policy->tgt_lifetime_raw),
+ AUTHN_AUDIT_EVENT_KERBEROS_DEVICE_RESTRICTION,
+ (struct authn_policy_flags) {},
+ restrictions,
+ client_audit_info_out);
+out:
+ return status;
+}
+
+/* Authentication policies for NTLM clients. */
+
+/*
+ * Get the applicable authentication policy for an account acting as an NTLM
+ * client.
+ */
+int authn_policy_ntlm_client(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ const struct authn_ntlm_client_policy **policy_out)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ int ret = 0;
+ struct authn_attrs authn_attrs;
+ struct ldb_message *authn_policy_msg = NULL;
+ struct authn_ntlm_client_policy *client_policy = NULL;
+ struct authn_policy policy;
+
+ *policy_out = NULL;
+
+ if (!authn_policy_silos_and_policies_in_effect(samdb)) {
+ return 0;
+ }
+
+ /*
+ * Get the silo and policy attributes that apply to objects of this
+ * account’s objectclass.
+ */
+ authn_attrs = authn_policy_get_attrs(msg);
+ if (authn_attrs.silo == NULL || authn_attrs.policy == NULL) {
+ /*
+ * No applicable silo or policy attributes (somehow). Either
+ * this account isn’t derived from ‘user’, or the message is
+ * missing an objectClass element.
+ */
+ goto out;
+ }
+
+ if (authn_attrs.policy->allowed_to_authenticate_from == NULL &&
+ authn_attrs.policy->allowed_ntlm_network_auth == NULL)
+ {
+ /* No relevant policy attributes apply. */
+ goto out;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ ret = samba_kdc_authn_policy_msg(samdb,
+ tmp_ctx,
+ msg,
+ authn_attrs,
+ &authn_policy_msg,
+ &policy);
+ if (ret) {
+ goto out;
+ }
+
+ if (authn_policy_msg == NULL) {
+ /* No policy applies. */
+ goto out;
+ }
+
+ client_policy = talloc_zero(tmp_ctx, struct authn_ntlm_client_policy);
+ if (client_policy == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ client_policy->policy = authn_policy_move(client_policy, &policy);
+
+ if (authn_attrs.policy->allowed_to_authenticate_from != NULL) {
+ const struct ldb_val *allowed_from = ldb_msg_find_ldb_val(
+ authn_policy_msg,
+ authn_attrs.policy->allowed_to_authenticate_from);
+
+ if (allowed_from != NULL && allowed_from->data != NULL) {
+ client_policy->allowed_to_authenticate_from = data_blob_const(
+ talloc_steal(client_policy, allowed_from->data),
+ allowed_from->length);
+ }
+ }
+
+ if (authn_attrs.policy->allowed_ntlm_network_auth != NULL &&
+ authn_policy_allowed_ntlm_network_auth_in_effect(samdb))
+ {
+ client_policy->allowed_ntlm_network_auth = ldb_msg_find_attr_as_bool(
+ authn_policy_msg,
+ authn_attrs.policy->allowed_ntlm_network_auth,
+ false);
+ }
+
+ *policy_out = talloc_move(mem_ctx, &client_policy);
+
+out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/* Return whether an authentication policy enforces device restrictions. */
+static bool authn_policy_ntlm_device_restrictions_present(const struct authn_ntlm_client_policy *policy)
+{
+ if (policy == NULL) {
+ return false;
+ }
+
+ return policy->allowed_to_authenticate_from.data != NULL;
+}
+
+/* Check whether the client is allowed to authenticate using NTLM. */
+NTSTATUS authn_policy_ntlm_apply_device_restriction(TALLOC_CTX *mem_ctx,
+ const struct authn_ntlm_client_policy *client_policy,
+ struct authn_audit_info **client_audit_info_out)
+{
+ NTSTATUS status;
+ NTSTATUS status2;
+
+ if (client_audit_info_out != NULL) {
+ *client_audit_info_out = NULL;
+ }
+
+ if (client_policy == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Access control restrictions cannot be applied to NTLM.
+ *
+ * If NTLM authentication is disallowed and the policy enforces a device
+ * restriction, deny the authentication.
+ */
+
+ if (!authn_policy_ntlm_device_restrictions_present(client_policy)) {
+ return authn_policy_audit_info(mem_ctx,
+ &client_policy->policy,
+ authn_int64_none() /* tgt_lifetime_raw */,
+ NULL /* client_info */,
+ AUTHN_AUDIT_EVENT_OK,
+ AUTHN_AUDIT_REASON_NONE,
+ NT_STATUS_OK,
+ client_audit_info_out);
+ }
+
+ /*
+ * (Although MS-APDS doesn’t state it, AllowedNTLMNetworkAuthentication
+ * applies to interactive logons too.)
+ */
+ if (client_policy->allowed_ntlm_network_auth) {
+ return authn_policy_audit_info(mem_ctx,
+ &client_policy->policy,
+ authn_int64_none() /* tgt_lifetime_raw */,
+ NULL /* client_info */,
+ AUTHN_AUDIT_EVENT_OK,
+ AUTHN_AUDIT_REASON_NONE,
+ NT_STATUS_OK,
+ client_audit_info_out);
+ }
+
+ status = NT_STATUS_ACCOUNT_RESTRICTION;
+ status2 = authn_policy_audit_info(mem_ctx,
+ &client_policy->policy,
+ authn_int64_none() /* tgt_lifetime_raw */,
+ NULL /* client_info */,
+ AUTHN_AUDIT_EVENT_NTLM_DEVICE_RESTRICTION,
+ AUTHN_AUDIT_REASON_NONE,
+ status,
+ client_audit_info_out);
+ if (!NT_STATUS_IS_OK(status2)) {
+ status = status2;
+ } else if (!authn_policy_is_enforced(&client_policy->policy)) {
+ status = NT_STATUS_OK;
+ }
+
+ return status;
+}
+
+/* Authentication policies for servers. */
+
+/*
+ * Get the applicable authentication policy for an account acting as a
+ * server.
+ */
+int authn_policy_server(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ const struct authn_server_policy **policy_out)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ int ret = 0;
+ struct authn_attrs authn_attrs;
+ struct ldb_message *authn_policy_msg = NULL;
+ struct authn_server_policy *server_policy = NULL;
+ struct authn_policy policy;
+
+ *policy_out = NULL;
+
+ if (!authn_policy_silos_and_policies_in_effect(samdb)) {
+ return 0;
+ }
+
+ /*
+ * Get the silo and policy attributes that apply to objects of this
+ * account’s objectclass.
+ */
+ authn_attrs = authn_policy_get_attrs(msg);
+ if (authn_attrs.silo == NULL || authn_attrs.policy == NULL) {
+ /*
+ * No applicable silo or policy attributes (somehow). Either
+ * this account isn’t derived from ‘user’, or the message is
+ * missing an objectClass element.
+ */
+ goto out;
+ }
+
+ if (authn_attrs.policy->allowed_to_authenticate_to == NULL) {
+ /* The relevant policy attribute doesn’t apply. */
+ goto out;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ ret = samba_kdc_authn_policy_msg(samdb,
+ tmp_ctx,
+ msg,
+ authn_attrs,
+ &authn_policy_msg,
+ &policy);
+ if (ret) {
+ goto out;
+ }
+
+ if (authn_policy_msg == NULL) {
+ /* No policy applies. */
+ goto out;
+ }
+
+ server_policy = talloc_zero(tmp_ctx, struct authn_server_policy);
+ if (server_policy == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ server_policy->policy = authn_policy_move(server_policy, &policy);
+
+ if (authn_attrs.policy->allowed_to_authenticate_to != NULL) {
+ const struct ldb_val *allowed_to = ldb_msg_find_ldb_val(
+ authn_policy_msg,
+ authn_attrs.policy->allowed_to_authenticate_to);
+
+ if (allowed_to != NULL && allowed_to->data != NULL) {
+ server_policy->allowed_to_authenticate_to = data_blob_const(
+ talloc_steal(server_policy, allowed_to->data),
+ allowed_to->length);
+ }
+ }
+
+ *policy_out = talloc_move(mem_ctx, &server_policy);
+
+out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/* Get restrictions enforced by an authentication policy. */
+static const DATA_BLOB *authn_policy_restrictions(const struct authn_server_policy *policy)
+{
+ const DATA_BLOB *restrictions = NULL;
+
+ if (policy == NULL) {
+ return NULL;
+ }
+
+ restrictions = &policy->allowed_to_authenticate_to;
+ if (restrictions->data == NULL) {
+ return NULL;
+ }
+
+ return restrictions;
+}
+
+/* Return whether an authentication policy enforces restrictions. */
+bool authn_policy_restrictions_present(const struct authn_server_policy *policy)
+{
+ return authn_policy_restrictions(policy) != NULL;
+}
+
+/*
+ * Perform an access check for the client attempting to authenticate to the
+ * server. ‘user_info’ must be talloc-allocated so that we can make a reference
+ * to it.
+ */
+NTSTATUS authn_policy_authenticate_to_service(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct loadparm_context* lp_ctx,
+ const enum authn_policy_auth_type auth_type,
+ const struct auth_user_info_dc *user_info,
+ const struct auth_user_info_dc *device_info,
+ const struct auth_claims auth_claims,
+ const struct authn_server_policy *server_policy,
+ const struct authn_policy_flags authn_policy_flags,
+ struct authn_audit_info **server_audit_info_out)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ const DATA_BLOB *restrictions = NULL;
+ enum authn_audit_event event;
+
+ restrictions = authn_policy_restrictions(server_policy);
+ if (restrictions == NULL) {
+ return authn_server_policy_audit_info(mem_ctx,
+ server_policy,
+ user_info,
+ AUTHN_AUDIT_EVENT_OK,
+ AUTHN_AUDIT_REASON_NONE,
+ NT_STATUS_OK,
+ server_audit_info_out);
+ }
+
+ switch (auth_type) {
+ case AUTHN_POLICY_AUTH_TYPE_KERBEROS:
+ event = AUTHN_AUDIT_EVENT_KERBEROS_SERVER_RESTRICTION;
+ break;
+ case AUTHN_POLICY_AUTH_TYPE_NTLM:
+ event = AUTHN_AUDIT_EVENT_NTLM_SERVER_RESTRICTION;
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER_4;
+ }
+
+ status = authn_policy_access_check(mem_ctx,
+ samdb,
+ lp_ctx,
+ user_info,
+ device_info,
+ auth_claims,
+ &server_policy->policy,
+ authn_int64_none() /* tgt_lifetime_raw */,
+ event,
+ authn_policy_flags,
+ restrictions,
+ server_audit_info_out);
+ return status;
+}
+
+/* Create a structure containing auditing information. */
+NTSTATUS _authn_kerberos_client_policy_audit_info(
+ TALLOC_CTX *mem_ctx,
+ const struct authn_kerberos_client_policy *client_policy,
+ const struct auth_user_info_dc *client_info,
+ const enum authn_audit_event event,
+ const enum authn_audit_reason reason,
+ const NTSTATUS policy_status,
+ const char *location,
+ struct authn_audit_info **audit_info_out)
+{
+ const struct authn_policy *policy = NULL;
+ struct authn_int64_optional tgt_lifetime_raw = authn_int64_none();
+
+ if (client_policy != NULL) {
+ policy = &client_policy->policy;
+ tgt_lifetime_raw = authn_int64_some(client_policy->tgt_lifetime_raw);
+ }
+
+ return _authn_policy_audit_info(mem_ctx,
+ policy,
+ tgt_lifetime_raw,
+ client_info,
+ event,
+ reason,
+ policy_status,
+ location,
+ audit_info_out);
+}
+
+/* Create a structure containing auditing information. */
+NTSTATUS _authn_ntlm_client_policy_audit_info(
+ TALLOC_CTX *mem_ctx,
+ const struct authn_ntlm_client_policy *client_policy,
+ const struct auth_user_info_dc *client_info,
+ const enum authn_audit_event event,
+ const enum authn_audit_reason reason,
+ const NTSTATUS policy_status,
+ const char *location,
+ struct authn_audit_info **audit_info_out)
+{
+ const struct authn_policy *policy = NULL;
+
+ if (client_policy != NULL) {
+ policy = &client_policy->policy;
+ }
+
+ return _authn_policy_audit_info(mem_ctx,
+ policy,
+ authn_int64_none() /* tgt_lifetime_raw */,
+ client_info,
+ event,
+ reason,
+ policy_status,
+ location,
+ audit_info_out);
+}
+
+/* Create a structure containing auditing information. */
+NTSTATUS _authn_server_policy_audit_info(
+ TALLOC_CTX *mem_ctx,
+ const struct authn_server_policy *server_policy,
+ const struct auth_user_info_dc *client_info,
+ const enum authn_audit_event event,
+ const enum authn_audit_reason reason,
+ const NTSTATUS policy_status,
+ const char *location,
+ struct authn_audit_info **audit_info_out)
+{
+ const struct authn_policy *policy = NULL;
+
+ if (server_policy != NULL) {
+ policy = &server_policy->policy;
+ }
+
+ return _authn_policy_audit_info(mem_ctx,
+ policy,
+ authn_int64_none() /* tgt_lifetime_raw */,
+ client_info,
+ event,
+ reason,
+ policy_status,
+ location,
+ audit_info_out);
+}
diff --git a/source4/kdc/authn_policy_util.h b/source4/kdc/authn_policy_util.h
new file mode 100644
index 0000000..4895803
--- /dev/null
+++ b/source4/kdc/authn_policy_util.h
@@ -0,0 +1,228 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba Active Directory authentication policy 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/>.
+*/
+
+#ifndef KDC_AUTHN_POLICY_UTIL_H
+#define KDC_AUTHN_POLICY_UTIL_H
+
+#include "lib/replace/replace.h"
+#include "auth/authn_policy.h"
+#include "auth/session.h"
+#include <talloc.h>
+
+struct ldb_context;
+struct loadparm_context;
+struct ldb_message;
+
+bool authn_policy_silos_and_policies_in_effect(struct ldb_context *samdb);
+
+bool authn_policy_allowed_ntlm_network_auth_in_effect(struct ldb_context *samdb);
+
+/*
+ * Look up the silo assigned to an account. If one exists, returns its details
+ * and whether it is enforced or not. ‘silo_attrs’ comprises the attributes to
+ * include in the search result, the relevant set of which can differ depending
+ * on the account’s objectClass.
+ */
+int authn_policy_get_assigned_silo(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ const char *const *silo_attrs,
+ const struct ldb_message **silo_msg_out,
+ bool *is_enforced);
+
+struct auth_user_info_dc;
+
+/* Authentication policies for Kerberos clients. */
+
+/*
+ * Get the applicable authentication policy for an account acting as a Kerberos
+ * client.
+ */
+int authn_policy_kerberos_client(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ const struct authn_kerberos_client_policy **policy_out);
+
+/*
+ * Perform an access check for the device with which the client is
+ * authenticating. ‘device_info’ must be talloc-allocated so that we can make a
+ * reference to it.
+ */
+NTSTATUS authn_policy_authenticate_from_device(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct loadparm_context* lp_ctx,
+ const struct auth_user_info_dc *device_info,
+ const struct auth_claims auth_claims,
+ const struct authn_kerberos_client_policy *client_policy,
+ struct authn_audit_info **client_audit_info_out);
+
+/* Return whether an authentication policy enforces device restrictions. */
+bool authn_policy_device_restrictions_present(const struct authn_kerberos_client_policy *policy);
+
+/* Authentication policies for NTLM clients. */
+
+struct authn_ntlm_client_policy;
+
+/*
+ * Get the applicable authentication policy for an account acting as an NTLM
+ * client.
+ */
+int authn_policy_ntlm_client(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ const struct authn_ntlm_client_policy **policy_out);
+
+/* Check whether the client is allowed to authenticate using NTLM. */
+NTSTATUS authn_policy_ntlm_apply_device_restriction(TALLOC_CTX *mem_ctx,
+ const struct authn_ntlm_client_policy *client_policy,
+ struct authn_audit_info **client_audit_info_out);
+
+/* Authentication policies for servers. */
+
+struct authn_server_policy;
+
+/*
+ * Get the applicable authentication policy for an account acting as a
+ * server.
+ */
+int authn_policy_server(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ const struct authn_server_policy **policy_out);
+
+/* Return whether an authentication policy enforces restrictions. */
+bool authn_policy_restrictions_present(const struct authn_server_policy *policy);
+
+enum authn_policy_auth_type {
+ AUTHN_POLICY_AUTH_TYPE_KERBEROS,
+ AUTHN_POLICY_AUTH_TYPE_NTLM,
+};
+
+struct authn_policy_flags {
+ bool force_compounded_authentication : 1;
+};
+
+/*
+ * Perform an access check for the client attempting to authenticate to the
+ * server. ‘user_info’ must be talloc-allocated so that we can make a reference
+ * to it.
+ */
+NTSTATUS authn_policy_authenticate_to_service(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct loadparm_context* lp_ctx,
+ enum authn_policy_auth_type auth_type,
+ const struct auth_user_info_dc *user_info,
+ const struct auth_user_info_dc *device_info,
+ const struct auth_claims auth_claims,
+ const struct authn_server_policy *server_policy,
+ const struct authn_policy_flags authn_policy_flags,
+ struct authn_audit_info **server_audit_info_out);
+
+/* Create a structure containing auditing information. */
+NTSTATUS _authn_kerberos_client_policy_audit_info(
+ TALLOC_CTX *mem_ctx,
+ const struct authn_kerberos_client_policy *client_policy,
+ const struct auth_user_info_dc *client_info,
+ enum authn_audit_event event,
+ enum authn_audit_reason reason,
+ NTSTATUS policy_status,
+ const char *location,
+ struct authn_audit_info **audit_info_out);
+
+/* Create a structure containing auditing information. */
+#define authn_kerberos_client_policy_audit_info( \
+ mem_ctx, \
+ policy, \
+ client_info, \
+ event, \
+ reason, \
+ policy_status, \
+ audit_info_out) \
+ _authn_kerberos_client_policy_audit_info( \
+ mem_ctx, \
+ policy, \
+ client_info, \
+ event, \
+ reason, \
+ policy_status, \
+ __location__, \
+ audit_info_out)
+
+/* Create a structure containing auditing information. */
+NTSTATUS _authn_ntlm_client_policy_audit_info(
+ TALLOC_CTX *mem_ctx,
+ const struct authn_ntlm_client_policy *policy,
+ const struct auth_user_info_dc *client_info,
+ enum authn_audit_event event,
+ enum authn_audit_reason reason,
+ NTSTATUS policy_status,
+ const char *location,
+ struct authn_audit_info **audit_info_out);
+
+/* Create a structure containing auditing information. */
+#define authn_ntlm_client_policy_audit_info( \
+ mem_ctx, \
+ policy, \
+ client_info, \
+ event, \
+ reason, \
+ policy_status, \
+ audit_info_out) \
+ _authn_ntlm_client_policy_audit_info( \
+ mem_ctx, \
+ policy, \
+ client_info, \
+ event, \
+ reason, \
+ policy_status, \
+ __location__, \
+ audit_info_out)
+
+/* Create a structure containing auditing information. */
+NTSTATUS _authn_server_policy_audit_info(
+ TALLOC_CTX *mem_ctx,
+ const struct authn_server_policy *policy,
+ const struct auth_user_info_dc *client_info,
+ enum authn_audit_event event,
+ enum authn_audit_reason reason,
+ NTSTATUS policy_status,
+ const char *location,
+ struct authn_audit_info **audit_info_out);
+
+/* Create a structure containing auditing information. */
+#define authn_server_policy_audit_info( \
+ mem_ctx, \
+ policy, \
+ client_info, \
+ event, \
+ reason, \
+ policy_status, \
+ audit_info_out) \
+ _authn_server_policy_audit_info( \
+ mem_ctx, \
+ policy, \
+ client_info, \
+ event, \
+ reason, \
+ policy_status, \
+ __location__, \
+ audit_info_out)
+
+#endif
diff --git a/source4/kdc/db-glue.c b/source4/kdc/db-glue.c
new file mode 100644
index 0000000..3b2757a
--- /dev/null
+++ b/source4/kdc/db-glue.c
@@ -0,0 +1,3872 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Database Glue between Samba and the KDC
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009
+ Copyright (C) Simo Sorce <idra@samba.org> 2010
+
+ 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 "includes.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "auth/auth.h"
+#include "auth/auth_sam.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/util.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+#include "param/secrets.h"
+#include "../lib/crypto/md4.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include "kdc/authn_policy_util.h"
+#include "kdc/sdb.h"
+#include "kdc/samba_kdc.h"
+#include "kdc/db-glue.h"
+#include "kdc/pac-glue.h"
+#include "librpc/gen_ndr/ndr_irpc_c.h"
+#include "lib/messaging/irpc.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+#undef strcasecmp
+#undef strncasecmp
+
+#define SAMBA_KVNO_GET_KRBTGT(kvno) \
+ ((uint16_t)(((uint32_t)kvno) >> 16))
+
+#define SAMBA_KVNO_GET_VALUE(kvno) \
+ ((uint16_t)(((uint32_t)kvno) & 0xFFFF))
+
+#define SAMBA_KVNO_AND_KRBTGT(kvno, krbtgt) \
+ ((krb5_kvno)((((uint32_t)kvno) & 0xFFFF) | \
+ ((((uint32_t)krbtgt) << 16) & 0xFFFF0000)))
+
+enum trust_direction {
+ UNKNOWN = 0,
+ INBOUND = LSA_TRUST_DIRECTION_INBOUND,
+ OUTBOUND = LSA_TRUST_DIRECTION_OUTBOUND
+};
+
+static const char *trust_attrs[] = {
+ "securityIdentifier",
+ "flatName",
+ "trustPartner",
+ "trustAttributes",
+ "trustDirection",
+ "trustType",
+ "msDS-TrustForestTrustInfo",
+ "trustAuthIncoming",
+ "trustAuthOutgoing",
+ "whenCreated",
+ "msDS-SupportedEncryptionTypes",
+ NULL
+};
+
+/*
+ send a message to the drepl server telling it to initiate a
+ REPL_SECRET getncchanges extended op to fetch the users secrets
+ */
+static void auth_sam_trigger_repl_secret(TALLOC_CTX *mem_ctx,
+ struct imessaging_context *msg_ctx,
+ struct tevent_context *event_ctx,
+ struct ldb_dn *user_dn)
+{
+ struct dcerpc_binding_handle *irpc_handle;
+ struct drepl_trigger_repl_secret r;
+ struct tevent_req *req;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ irpc_handle = irpc_binding_handle_by_name(tmp_ctx, msg_ctx,
+ "dreplsrv",
+ &ndr_table_irpc);
+ if (irpc_handle == NULL) {
+ DBG_WARNING("Unable to get binding handle for dreplsrv\n");
+ TALLOC_FREE(tmp_ctx);
+ return;
+ }
+
+ r.in.user_dn = ldb_dn_get_linearized(user_dn);
+ if (r.in.user_dn == NULL) {
+ DBG_WARNING("Unable to get user DN\n");
+ TALLOC_FREE(tmp_ctx);
+ return;
+ }
+
+ /*
+ * This seem to rely on the current IRPC implementation,
+ * which delivers the message in the _send function.
+ *
+ * TODO: we need a ONE_WAY IRPC handle and register
+ * a callback and wait for it to be triggered!
+ */
+ req = dcerpc_drepl_trigger_repl_secret_r_send(tmp_ctx,
+ event_ctx,
+ irpc_handle,
+ &r);
+
+ /* we aren't interested in a reply */
+ talloc_free(req);
+ TALLOC_FREE(tmp_ctx);
+}
+
+static time_t ldb_msg_find_krb5time_ldap_time(struct ldb_message *msg, const char *attr, time_t default_val)
+{
+ const struct ldb_val *gentime = NULL;
+ time_t t;
+ int ret;
+
+ gentime = ldb_msg_find_ldb_val(msg, attr);
+ ret = ldb_val_to_time(gentime, &t);
+ if (ret) {
+ return default_val;
+ }
+
+ return t;
+}
+
+static struct SDBFlags uf2SDBFlags(krb5_context context, uint32_t userAccountControl, enum samba_kdc_ent_type ent_type)
+{
+ struct SDBFlags flags = {};
+
+ /* we don't allow kadmin deletes */
+ flags.immutable = 1;
+
+ /* mark the principal as invalid to start with */
+ flags.invalid = 1;
+
+ flags.renewable = 1;
+
+ /* All accounts are servers, but this may be disabled again in the caller */
+ flags.server = 1;
+
+ /* Account types - clear the invalid bit if it turns out to be valid */
+ if (userAccountControl & UF_NORMAL_ACCOUNT) {
+ if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT || ent_type == SAMBA_KDC_ENT_TYPE_ANY) {
+ flags.client = 1;
+ }
+ flags.invalid = 0;
+ }
+
+ if (userAccountControl & UF_INTERDOMAIN_TRUST_ACCOUNT) {
+ if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT || ent_type == SAMBA_KDC_ENT_TYPE_ANY) {
+ flags.client = 1;
+ }
+ flags.invalid = 0;
+ }
+ if (userAccountControl & UF_WORKSTATION_TRUST_ACCOUNT) {
+ if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT || ent_type == SAMBA_KDC_ENT_TYPE_ANY) {
+ flags.client = 1;
+ }
+ flags.invalid = 0;
+ }
+ if (userAccountControl & UF_SERVER_TRUST_ACCOUNT) {
+ if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT || ent_type == SAMBA_KDC_ENT_TYPE_ANY) {
+ flags.client = 1;
+ }
+ flags.invalid = 0;
+ }
+
+ /* Not permitted to act as a client if disabled */
+ if (userAccountControl & UF_ACCOUNTDISABLE) {
+ flags.client = 0;
+ }
+ if (userAccountControl & UF_LOCKOUT) {
+ flags.locked_out = 1;
+ }
+/*
+ if (userAccountControl & UF_PASSWD_NOTREQD) {
+ flags.invalid = 1;
+ }
+*/
+/*
+ UF_PASSWD_CANT_CHANGE and UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED are irrelevant
+*/
+ if (userAccountControl & UF_TEMP_DUPLICATE_ACCOUNT) {
+ flags.invalid = 1;
+ }
+
+/* UF_DONT_EXPIRE_PASSWD and UF_USE_DES_KEY_ONLY handled in samba_kdc_message2entry() */
+
+/*
+ if (userAccountControl & UF_MNS_LOGON_ACCOUNT) {
+ flags.invalid = 1;
+ }
+*/
+ if (userAccountControl & UF_SMARTCARD_REQUIRED) {
+ flags.require_hwauth = 1;
+ }
+ if (userAccountControl & UF_TRUSTED_FOR_DELEGATION) {
+ flags.ok_as_delegate = 1;
+ }
+ if (userAccountControl & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION) {
+ /*
+ * this is confusing...
+ *
+ * UF_TRUSTED_FOR_DELEGATION
+ * => ok_as_delegate
+ *
+ * and
+ *
+ * UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION
+ * => trusted_for_delegation
+ */
+ flags.trusted_for_delegation = 1;
+ }
+ if (!(userAccountControl & UF_NOT_DELEGATED)) {
+ flags.forwardable = 1;
+ flags.proxiable = 1;
+ }
+
+ if (userAccountControl & UF_DONT_REQUIRE_PREAUTH) {
+ flags.require_preauth = 0;
+ } else {
+ flags.require_preauth = 1;
+ }
+
+ if (userAccountControl & UF_NO_AUTH_DATA_REQUIRED) {
+ flags.no_auth_data_reqd = 1;
+ }
+
+ return flags;
+}
+
+static int samba_kdc_entry_destructor(struct samba_kdc_entry *p)
+{
+ if (p->db_entry != NULL) {
+ /*
+ * A sdb_entry still has a reference
+ */
+ return -1;
+ }
+
+ if (p->kdc_entry != NULL) {
+ /*
+ * hdb_entry or krb5_db_entry still
+ * have a reference...
+ */
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Sort keys in descending order of strength.
+ *
+ * Explanation from Greg Hudson:
+ *
+ * To encrypt tickets only the first returned key is used by the MIT KDC. The
+ * other keys just communicate support for session key enctypes, and aren't
+ * really used. The encryption key for the ticket enc part doesn't have
+ * to be of a type requested by the client. The session key enctype is chosen
+ * based on the client preference order, limited by the set of enctypes present
+ * in the server keys (unless the string attribute is set on the server
+ * principal overriding that set).
+ */
+
+static int sdb_key_strength_priority(krb5_enctype etype)
+{
+ static const krb5_enctype etype_list[] = {
+ ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+ ENCTYPE_AES128_CTS_HMAC_SHA1_96,
+ ENCTYPE_DES3_CBC_SHA1,
+ ENCTYPE_ARCFOUR_HMAC,
+ ENCTYPE_DES_CBC_MD5,
+ ENCTYPE_DES_CBC_MD4,
+ ENCTYPE_DES_CBC_CRC,
+ ENCTYPE_NULL
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(etype_list); i++) {
+ if (etype == etype_list[i]) {
+ break;
+ }
+ }
+
+ return ARRAY_SIZE(etype_list) - i;
+}
+
+static int sdb_key_strength_cmp(const struct sdb_key *k1, const struct sdb_key *k2)
+{
+ int p1 = sdb_key_strength_priority(KRB5_KEY_TYPE(&k1->key));
+ int p2 = sdb_key_strength_priority(KRB5_KEY_TYPE(&k2->key));
+
+ if (p1 == p2) {
+ return 0;
+ }
+
+ if (p1 > p2) {
+ /*
+ * Higher priority comes first
+ */
+ return -1;
+ } else {
+ return 1;
+ }
+}
+
+static void samba_kdc_sort_keys(struct sdb_keys *keys)
+{
+ if (keys == NULL) {
+ return;
+ }
+
+ TYPESAFE_QSORT(keys->val, keys->len, sdb_key_strength_cmp);
+}
+
+int samba_kdc_set_fixed_keys(krb5_context context,
+ const struct ldb_val *secretbuffer,
+ uint32_t supported_enctypes,
+ struct sdb_keys *keys)
+{
+ uint16_t allocated_keys = 0;
+ int ret;
+
+ allocated_keys = 3;
+ keys->len = 0;
+ keys->val = calloc(allocated_keys, sizeof(struct sdb_key));
+ if (keys->val == NULL) {
+ memset(secretbuffer->data, 0, secretbuffer->length);
+ ret = ENOMEM;
+ goto out;
+ }
+
+ if (supported_enctypes & ENC_HMAC_SHA1_96_AES256) {
+ struct sdb_key key = {};
+
+ ret = smb_krb5_keyblock_init_contents(context,
+ ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+ secretbuffer->data,
+ MIN(secretbuffer->length, 32),
+ &key.key);
+ if (ret) {
+ memset(secretbuffer->data, 0, secretbuffer->length);
+ goto out;
+ }
+
+ keys->val[keys->len] = key;
+ keys->len++;
+ }
+
+ if (supported_enctypes & ENC_HMAC_SHA1_96_AES128) {
+ struct sdb_key key = {};
+
+ ret = smb_krb5_keyblock_init_contents(context,
+ ENCTYPE_AES128_CTS_HMAC_SHA1_96,
+ secretbuffer->data,
+ MIN(secretbuffer->length, 16),
+ &key.key);
+ if (ret) {
+ memset(secretbuffer->data, 0, secretbuffer->length);
+ goto out;
+ }
+
+ keys->val[keys->len] = key;
+ keys->len++;
+ }
+
+ if (supported_enctypes & ENC_RC4_HMAC_MD5) {
+ struct sdb_key key = {};
+
+ ret = smb_krb5_keyblock_init_contents(context,
+ ENCTYPE_ARCFOUR_HMAC,
+ secretbuffer->data,
+ MIN(secretbuffer->length, 16),
+ &key.key);
+ if (ret) {
+ memset(secretbuffer->data, 0, secretbuffer->length);
+ goto out;
+ }
+
+ keys->val[keys->len] = key;
+ keys->len++;
+ }
+ ret = 0;
+out:
+ return ret;
+}
+
+
+static int samba_kdc_set_random_keys(krb5_context context,
+ uint32_t supported_enctypes,
+ struct sdb_keys *keys)
+{
+ struct ldb_val secret_val;
+ uint8_t secretbuffer[32];
+
+ /*
+ * Fake keys until we have a better way to reject
+ * non-pkinit requests.
+ *
+ * We just need to indicate which encryption types are
+ * supported.
+ */
+ generate_secret_buffer(secretbuffer, sizeof(secretbuffer));
+
+ secret_val = data_blob_const(secretbuffer,
+ sizeof(secretbuffer));
+ return samba_kdc_set_fixed_keys(context,
+ &secret_val,
+ supported_enctypes,
+ keys);
+}
+
+struct samba_kdc_user_keys {
+ struct sdb_keys *skeys;
+ uint32_t kvno;
+ uint32_t *returned_kvno;
+ uint32_t supported_enctypes;
+ uint32_t *available_enctypes;
+ const struct samr_Password *nthash;
+ const char *salt_string;
+ uint16_t num_pkeys;
+ const struct package_PrimaryKerberosKey4 *pkeys;
+};
+
+static krb5_error_code samba_kdc_fill_user_keys(krb5_context context,
+ struct samba_kdc_user_keys *p)
+{
+ /*
+ * Make sure we'll never reveal DES keys
+ */
+ uint32_t supported_enctypes = p->supported_enctypes &= ~(ENC_CRC32 | ENC_RSA_MD5);
+ uint32_t _available_enctypes = 0;
+ uint32_t *available_enctypes = p->available_enctypes;
+ uint32_t _returned_kvno = 0;
+ uint32_t *returned_kvno = p->returned_kvno;
+ uint32_t num_pkeys = p->num_pkeys;
+ uint32_t allocated_keys = num_pkeys;
+ uint32_t i;
+ int ret;
+
+ if (available_enctypes == NULL) {
+ available_enctypes = &_available_enctypes;
+ }
+
+ *available_enctypes = 0;
+
+ if (returned_kvno == NULL) {
+ returned_kvno = &_returned_kvno;
+ }
+
+ *returned_kvno = p->kvno;
+
+ if (p->nthash != NULL) {
+ allocated_keys += 1;
+ }
+
+ allocated_keys = MAX(1, allocated_keys);
+
+ /* allocate space to decode into */
+ p->skeys->len = 0;
+ p->skeys->val = calloc(allocated_keys, sizeof(struct sdb_key));
+ if (p->skeys->val == NULL) {
+ return ENOMEM;
+ }
+
+ for (i=0; i < num_pkeys; i++) {
+ struct sdb_key key = {};
+ uint32_t enctype_bit;
+
+ if (p->pkeys[i].value == NULL) {
+ continue;
+ }
+
+ enctype_bit = kerberos_enctype_to_bitmap(p->pkeys[i].keytype);
+ if (!(enctype_bit & supported_enctypes)) {
+ continue;
+ }
+
+ if (p->salt_string != NULL) {
+ DATA_BLOB salt;
+
+ salt = data_blob_string_const(p->salt_string);
+
+ key.salt = calloc(1, sizeof(*key.salt));
+ if (key.salt == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ key.salt->type = KRB5_PW_SALT;
+
+ ret = smb_krb5_copy_data_contents(&key.salt->salt,
+ salt.data,
+ salt.length);
+ if (ret) {
+ *key.salt = (struct sdb_salt) {};
+ sdb_key_free(&key);
+ goto fail;
+ }
+ }
+
+ ret = smb_krb5_keyblock_init_contents(context,
+ p->pkeys[i].keytype,
+ p->pkeys[i].value->data,
+ p->pkeys[i].value->length,
+ &key.key);
+ if (ret == 0) {
+ p->skeys->val[p->skeys->len++] = key;
+ *available_enctypes |= enctype_bit;
+ continue;
+ }
+ ZERO_STRUCT(key.key);
+ sdb_key_free(&key);
+ if (ret == KRB5_PROG_ETYPE_NOSUPP) {
+ DEBUG(2,("Unsupported keytype ignored - type %u\n",
+ p->pkeys[i].keytype));
+ ret = 0;
+ continue;
+ }
+
+ goto fail;
+ }
+
+ if (p->nthash != NULL && (supported_enctypes & ENC_RC4_HMAC_MD5)) {
+ struct sdb_key key = {};
+
+ ret = smb_krb5_keyblock_init_contents(context,
+ ENCTYPE_ARCFOUR_HMAC,
+ p->nthash->hash,
+ sizeof(p->nthash->hash),
+ &key.key);
+ if (ret == 0) {
+ p->skeys->val[p->skeys->len++] = key;
+
+ *available_enctypes |= ENC_RC4_HMAC_MD5;
+ } else if (ret == KRB5_PROG_ETYPE_NOSUPP) {
+ DEBUG(2,("Unsupported keytype ignored - type %u\n",
+ ENCTYPE_ARCFOUR_HMAC));
+ ret = 0;
+ }
+ if (ret != 0) {
+ goto fail;
+ }
+ }
+
+ samba_kdc_sort_keys(p->skeys);
+
+ return 0;
+fail:
+ sdb_keys_free(p->skeys);
+ return ret;
+}
+
+krb5_error_code samba_kdc_message2entry_keys(krb5_context context,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ bool is_krbtgt,
+ bool is_rodc,
+ uint32_t userAccountControl,
+ enum samba_kdc_ent_type ent_type,
+ unsigned flags,
+ krb5_kvno requested_kvno,
+ struct sdb_entry *entry,
+ const uint32_t supported_enctypes_in,
+ uint32_t *supported_enctypes_out)
+{
+ krb5_error_code ret = 0;
+ enum ndr_err_code ndr_err;
+ struct samr_Password *hash;
+ unsigned int num_ntPwdHistory = 0;
+ struct samr_Password *ntPwdHistory = NULL;
+ struct samr_Password *old_hash = NULL;
+ struct samr_Password *older_hash = NULL;
+ const struct ldb_val *sc_val;
+ struct supplementalCredentialsBlob scb;
+ struct supplementalCredentialsPackage *scpk = NULL;
+ struct package_PrimaryKerberosBlob _pkb;
+ struct package_PrimaryKerberosCtr4 *pkb4 = NULL;
+ int krbtgt_number = 0;
+ uint32_t current_kvno;
+ uint32_t old_kvno = 0;
+ uint32_t older_kvno = 0;
+ uint32_t returned_kvno = 0;
+ uint16_t i;
+ struct samba_kdc_user_keys keys = { .num_pkeys = 0, };
+ struct samba_kdc_user_keys old_keys = { .num_pkeys = 0, };
+ struct samba_kdc_user_keys older_keys = { .num_pkeys = 0, };
+ uint32_t available_enctypes = 0;
+ uint32_t supported_enctypes = supported_enctypes_in;
+
+ *supported_enctypes_out = 0;
+
+ /* Is this the krbtgt or a RODC krbtgt */
+ if (is_rodc) {
+ krbtgt_number = ldb_msg_find_attr_as_int(msg, "msDS-SecondaryKrbTgtNumber", -1);
+
+ if (krbtgt_number == -1) {
+ return EINVAL;
+ }
+ if (krbtgt_number == 0) {
+ return EINVAL;
+ }
+ }
+
+ if (flags & SDB_F_USER2USER_PRINCIPAL) {
+ /*
+ * User2User uses the session key
+ * from the additional ticket,
+ * so we just provide random keys
+ * here in order to make sure
+ * we never expose the user password
+ * keys.
+ */
+ ret = samba_kdc_set_random_keys(context,
+ supported_enctypes,
+ &entry->keys);
+
+ *supported_enctypes_out = supported_enctypes & ENC_ALL_TYPES;
+
+ goto out;
+ }
+
+ if ((ent_type == SAMBA_KDC_ENT_TYPE_CLIENT)
+ && (userAccountControl & UF_SMARTCARD_REQUIRED)) {
+ ret = samba_kdc_set_random_keys(context,
+ supported_enctypes,
+ &entry->keys);
+
+ *supported_enctypes_out = supported_enctypes & ENC_ALL_TYPES;
+
+ goto out;
+ }
+
+ current_kvno = ldb_msg_find_attr_as_int(msg, "msDS-KeyVersionNumber", 0);
+ if (current_kvno > 1) {
+ old_kvno = current_kvno - 1;
+ }
+ if (current_kvno > 2) {
+ older_kvno = current_kvno - 2;
+ }
+ if (is_krbtgt) {
+ /*
+ * Even for the main krbtgt account
+ * we have to strictly split the kvno into
+ * two 16-bit parts and the upper 16-bit
+ * need to be all zero, even if
+ * the msDS-KeyVersionNumber has a value
+ * larger than 65535.
+ *
+ * See https://bugzilla.samba.org/show_bug.cgi?id=14951
+ */
+ current_kvno = SAMBA_KVNO_GET_VALUE(current_kvno);
+ old_kvno = SAMBA_KVNO_GET_VALUE(old_kvno);
+ older_kvno = SAMBA_KVNO_GET_VALUE(older_kvno);
+ requested_kvno = SAMBA_KVNO_GET_VALUE(requested_kvno);
+ }
+
+ /* Get keys from the db */
+
+ hash = samdb_result_hash(mem_ctx, msg, "unicodePwd");
+ num_ntPwdHistory = samdb_result_hashes(mem_ctx, msg,
+ "ntPwdHistory",
+ &ntPwdHistory);
+ if (num_ntPwdHistory > 1) {
+ old_hash = &ntPwdHistory[1];
+ }
+ if (num_ntPwdHistory > 2) {
+ older_hash = &ntPwdHistory[1];
+ }
+ sc_val = ldb_msg_find_ldb_val(msg, "supplementalCredentials");
+
+ /* supplementalCredentials if present */
+ if (sc_val) {
+ ndr_err = ndr_pull_struct_blob_all(sc_val, mem_ctx, &scb,
+ (ndr_pull_flags_fn_t)ndr_pull_supplementalCredentialsBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ ret = EINVAL;
+ goto out;
+ }
+
+ if (scb.sub.signature != SUPPLEMENTAL_CREDENTIALS_SIGNATURE) {
+ if (scb.sub.num_packages != 0) {
+ NDR_PRINT_DEBUG(supplementalCredentialsBlob, &scb);
+ ret = EINVAL;
+ goto out;
+ }
+ }
+
+ for (i=0; i < scb.sub.num_packages; i++) {
+ if (scb.sub.packages[i].name != NULL &&
+ strcmp("Primary:Kerberos-Newer-Keys", scb.sub.packages[i].name) == 0)
+ {
+ scpk = &scb.sub.packages[i];
+ if (!scpk->data || !scpk->data[0]) {
+ scpk = NULL;
+ continue;
+ }
+ break;
+ }
+ }
+ }
+ /*
+ * Primary:Kerberos-Newer-Keys element
+ * of supplementalCredentials
+ *
+ * The legacy Primary:Kerberos only contains
+ * single DES keys, which are completely ignored
+ * now.
+ */
+ if (scpk) {
+ DATA_BLOB blob;
+
+ blob = strhex_to_data_blob(mem_ctx, scpk->data);
+ if (!blob.data) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ /* we cannot use ndr_pull_struct_blob_all() here, as w2k and w2k3 add padding bytes */
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &_pkb,
+ (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryKerberosBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "samba_kdc_message2entry_keys: could not parse package_PrimaryKerberosBlob");
+ krb5_warnx(context, "samba_kdc_message2entry_keys: could not parse package_PrimaryKerberosBlob");
+ goto out;
+ }
+
+ if (_pkb.version != 4) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "samba_kdc_message2entry_keys: Primary:Kerberos-Newer-Keys not version 4");
+ krb5_warnx(context, "samba_kdc_message2entry_keys: Primary:Kerberos-Newer-Keys not version 4");
+ goto out;
+ }
+
+ pkb4 = &_pkb.ctr.ctr4;
+ }
+
+ keys = (struct samba_kdc_user_keys) {
+ .kvno = current_kvno,
+ .supported_enctypes = supported_enctypes,
+ .nthash = hash,
+ .salt_string = pkb4 != NULL ? pkb4->salt.string : NULL,
+ .num_pkeys = pkb4 != NULL ? pkb4->num_keys : 0,
+ .pkeys = pkb4 != NULL ? pkb4->keys : NULL,
+ };
+
+ old_keys = (struct samba_kdc_user_keys) {
+ .kvno = old_kvno,
+ .supported_enctypes = supported_enctypes,
+ .nthash = old_hash,
+ .salt_string = pkb4 != NULL ? pkb4->salt.string : NULL,
+ .num_pkeys = pkb4 != NULL ? pkb4->num_old_keys : 0,
+ .pkeys = pkb4 != NULL ? pkb4->old_keys : NULL,
+ };
+ older_keys = (struct samba_kdc_user_keys) {
+ .kvno = older_kvno,
+ .supported_enctypes = supported_enctypes,
+ .nthash = older_hash,
+ .salt_string = pkb4 != NULL ? pkb4->salt.string : NULL,
+ .num_pkeys = pkb4 != NULL ? pkb4->num_older_keys : 0,
+ .pkeys = pkb4 != NULL ? pkb4->older_keys : NULL,
+ };
+
+ if (flags & SDB_F_KVNO_SPECIFIED) {
+ if (requested_kvno == keys.kvno) {
+ /*
+ * The current kvno was requested,
+ * so we return it.
+ */
+ keys.skeys = &entry->keys;
+ keys.available_enctypes = &available_enctypes;
+ keys.returned_kvno = &returned_kvno;
+ } else if (requested_kvno == 0) {
+ /*
+ * don't return any keys
+ */
+ } else if (requested_kvno == old_keys.kvno) {
+ /*
+ * return the old keys as default keys
+ * with the requested kvno.
+ */
+ old_keys.skeys = &entry->keys;
+ old_keys.available_enctypes = &available_enctypes;
+ old_keys.returned_kvno = &returned_kvno;
+ } else if (requested_kvno == older_keys.kvno) {
+ /*
+ * return the older keys as default keys
+ * with the requested kvno.
+ */
+ older_keys.skeys = &entry->keys;
+ older_keys.available_enctypes = &available_enctypes;
+ older_keys.returned_kvno = &returned_kvno;
+ } else {
+ /*
+ * don't return any keys
+ */
+ }
+ } else {
+ bool include_history = false;
+
+ if ((flags & SDB_F_GET_CLIENT) && (flags & SDB_F_FOR_AS_REQ)) {
+ include_history = true;
+ } else if (flags & SDB_F_ADMIN_DATA) {
+ include_history = true;
+ }
+
+ keys.skeys = &entry->keys;
+ keys.available_enctypes = &available_enctypes;
+ keys.returned_kvno = &returned_kvno;
+
+ if (include_history && old_keys.kvno != 0) {
+ old_keys.skeys = &entry->old_keys;
+ }
+ if (include_history && older_keys.kvno != 0) {
+ older_keys.skeys = &entry->older_keys;
+ }
+ }
+
+ if (keys.skeys != NULL) {
+ ret = samba_kdc_fill_user_keys(context, &keys);
+ if (ret != 0) {
+ goto out;
+ }
+ }
+
+ if (old_keys.skeys != NULL) {
+ ret = samba_kdc_fill_user_keys(context, &old_keys);
+ if (ret != 0) {
+ goto out;
+ }
+ }
+
+ if (older_keys.skeys != NULL) {
+ ret = samba_kdc_fill_user_keys(context, &older_keys);
+ if (ret != 0) {
+ goto out;
+ }
+ }
+
+ *supported_enctypes_out |= available_enctypes;
+
+ if (is_krbtgt) {
+ /*
+ * Even for the main krbtgt account
+ * we have to strictly split the kvno into
+ * two 16-bit parts and the upper 16-bit
+ * need to be all zero, even if
+ * the msDS-KeyVersionNumber has a value
+ * larger than 65535.
+ *
+ * See https://bugzilla.samba.org/show_bug.cgi?id=14951
+ */
+ returned_kvno = SAMBA_KVNO_AND_KRBTGT(returned_kvno, krbtgt_number);
+ }
+ entry->kvno = returned_kvno;
+
+out:
+ return ret;
+}
+
+static krb5_error_code is_principal_component_equal_impl(krb5_context context,
+ krb5_const_principal principal,
+ unsigned int component,
+ const char *string,
+ bool do_strcasecmp,
+ bool *eq)
+{
+ const char *p;
+
+#if defined(HAVE_KRB5_PRINCIPAL_GET_COMP_STRING)
+ if (component >= krb5_princ_size(context, principal)) {
+ /* A non‐existent component compares equal to no string. */
+ *eq = false;
+ return 0;
+ }
+ p = krb5_principal_get_comp_string(context, principal, component);
+ if (p == NULL) {
+ return ENOENT;
+ }
+ if (do_strcasecmp) {
+ *eq = strcasecmp(p, string) == 0;
+ } else {
+ *eq = strcmp(p, string) == 0;
+ }
+ return 0;
+#else
+ size_t len;
+ krb5_data d;
+ krb5_error_code ret = 0;
+
+ if (component > INT_MAX) {
+ return EINVAL;
+ }
+
+ if (component >= krb5_princ_size(context, principal)) {
+ /* A non‐existent component compares equal to no string. */
+ *eq = false;
+ return 0;
+ }
+
+ ret = smb_krb5_princ_component(context, principal, component, &d);
+ if (ret) {
+ return ret;
+ }
+
+ p = d.data;
+
+ len = strlen(string);
+ if (d.length != len) {
+ *eq = false;
+ return 0;
+ }
+
+ if (do_strcasecmp) {
+ *eq = strncasecmp(p, string, len) == 0;
+ } else {
+ *eq = memcmp(p, string, len) == 0;
+ }
+ return 0;
+#endif
+}
+
+static krb5_error_code is_principal_component_equal_ignoring_case(krb5_context context,
+ krb5_const_principal principal,
+ unsigned int component,
+ const char *string,
+ bool *eq)
+{
+ return is_principal_component_equal_impl(context,
+ principal,
+ component,
+ string,
+ true /* do_strcasecmp */,
+ eq);
+}
+
+static krb5_error_code is_principal_component_equal(krb5_context context,
+ krb5_const_principal principal,
+ unsigned int component,
+ const char *string,
+ bool *eq)
+{
+ return is_principal_component_equal_impl(context,
+ principal,
+ component,
+ string,
+ false /* do_strcasecmp */,
+ eq);
+}
+
+static krb5_error_code is_kadmin_changepw(krb5_context context,
+ krb5_const_principal principal,
+ bool *is_changepw)
+{
+ krb5_error_code ret = 0;
+ bool eq = false;
+
+ if (krb5_princ_size(context, principal) != 2) {
+ *is_changepw = false;
+ return 0;
+ }
+
+ ret = is_principal_component_equal(context, principal, 0, "kadmin", &eq);
+ if (ret) {
+ return ret;
+ }
+
+ if (!eq) {
+ *is_changepw = false;
+ return 0;
+ }
+
+ ret = is_principal_component_equal(context, principal, 1, "changepw", &eq);
+ if (ret) {
+ return ret;
+ }
+
+ *is_changepw = eq;
+ return 0;
+}
+
+static krb5_error_code samba_kdc_get_entry_principal(
+ krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ const char *samAccountName,
+ enum samba_kdc_ent_type ent_type,
+ unsigned flags,
+ bool is_kadmin_changepw,
+ krb5_const_principal in_princ,
+ krb5_principal *out_princ)
+{
+ struct loadparm_context *lp_ctx = kdc_db_ctx->lp_ctx;
+ krb5_error_code code = 0;
+ bool canon = flags & (SDB_F_CANON|SDB_F_FORCE_CANON);
+
+ /*
+ * If we are set to canonicalize, we get back the fixed UPPER
+ * case realm, and the real username (ie matching LDAP
+ * samAccountName)
+ *
+ * Otherwise, if we are set to enterprise, we
+ * get back the whole principal as-sent
+ *
+ * Finally, if we are not set to canonicalize, we get back the
+ * fixed UPPER case realm, but the as-sent username
+ */
+
+ /*
+ * We need to ensure that the kadmin/changepw principal isn't able to
+ * issue krbtgt tickets, even if canonicalization is turned on.
+ */
+ if (!is_kadmin_changepw) {
+ if (ent_type == SAMBA_KDC_ENT_TYPE_KRBTGT && canon) {
+ /*
+ * When requested to do so, ensure that both
+ * the realm values in the principal are set
+ * to the upper case, canonical realm
+ */
+ code = smb_krb5_make_principal(context,
+ out_princ,
+ lpcfg_realm(lp_ctx),
+ "krbtgt",
+ lpcfg_realm(lp_ctx),
+ NULL);
+ if (code != 0) {
+ return code;
+ }
+ smb_krb5_principal_set_type(context,
+ *out_princ,
+ KRB5_NT_SRV_INST);
+
+ return 0;
+ }
+
+ if ((canon && flags & (SDB_F_FORCE_CANON|SDB_F_FOR_AS_REQ)) ||
+ (ent_type == SAMBA_KDC_ENT_TYPE_ANY && in_princ == NULL)) {
+ /*
+ * SDB_F_CANON maps from the canonicalize flag in the
+ * packet, and has a different meaning between AS-REQ
+ * and TGS-REQ. We only change the principal in the
+ * AS-REQ case.
+ *
+ * The SDB_F_FORCE_CANON if for new MIT KDC code that
+ * wants the canonical name in all lookups, and takes
+ * care to canonicalize only when appropriate.
+ */
+ code = smb_krb5_make_principal(context,
+ out_princ,
+ lpcfg_realm(lp_ctx),
+ samAccountName,
+ NULL);
+ return code;
+ }
+ }
+
+ /*
+ * For a krbtgt entry, this appears to be required regardless of the
+ * canonicalize flag from the client.
+ */
+ code = krb5_copy_principal(context, in_princ, out_princ);
+ if (code != 0) {
+ return code;
+ }
+
+ /*
+ * While we have copied the client principal, tests show that Win2k3
+ * returns the 'corrected' realm, not the client-specified realm. This
+ * code attempts to replace the client principal's realm with the one
+ * we determine from our records
+ */
+ code = smb_krb5_principal_set_realm(context,
+ *out_princ,
+ lpcfg_realm(lp_ctx));
+
+ return code;
+}
+
+/*
+ * Construct an hdb_entry from a directory entry.
+ */
+static krb5_error_code samba_kdc_message2entry(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ TALLOC_CTX *mem_ctx,
+ krb5_const_principal principal,
+ enum samba_kdc_ent_type ent_type,
+ unsigned flags,
+ krb5_kvno kvno,
+ struct ldb_dn *realm_dn,
+ struct ldb_message *msg,
+ struct sdb_entry *entry)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct loadparm_context *lp_ctx = kdc_db_ctx->lp_ctx;
+ uint32_t userAccountControl;
+ uint32_t msDS_User_Account_Control_Computed;
+ krb5_error_code ret = 0;
+ krb5_boolean is_computer = FALSE;
+ struct samba_kdc_entry *p;
+ NTTIME acct_expiry;
+ NTSTATUS status;
+ bool protected_user = false;
+ struct dom_sid sid;
+ uint32_t rid;
+ bool is_krbtgt = false;
+ bool is_rodc = false;
+ bool force_rc4 = lpcfg_kdc_force_enable_rc4_weak_session_keys(lp_ctx);
+ struct ldb_message_element *objectclasses;
+ struct ldb_val computer_val = data_blob_string_const("computer");
+ uint32_t config_default_supported_enctypes = lpcfg_kdc_default_domain_supported_enctypes(lp_ctx);
+ uint32_t default_supported_enctypes =
+ config_default_supported_enctypes != 0 ?
+ config_default_supported_enctypes :
+ ENC_RC4_HMAC_MD5 | ENC_HMAC_SHA1_96_AES256_SK;
+ uint32_t supported_enctypes
+ = ldb_msg_find_attr_as_uint(msg,
+ "msDS-SupportedEncryptionTypes",
+ default_supported_enctypes);
+ uint32_t pa_supported_enctypes;
+ uint32_t supported_session_etypes;
+ uint32_t available_enctypes = 0;
+ /*
+ * also legacy enctypes are announced,
+ * but effectively restricted by kdc_enctypes
+ */
+ uint32_t domain_enctypes = ENC_RC4_HMAC_MD5 | ENC_RSA_MD5 | ENC_CRC32;
+ uint32_t config_kdc_enctypes = lpcfg_kdc_supported_enctypes(lp_ctx);
+ uint32_t kdc_enctypes =
+ config_kdc_enctypes != 0 ?
+ config_kdc_enctypes :
+ ENC_ALL_TYPES;
+ const char *samAccountName = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL);
+
+ const struct authn_kerberos_client_policy *authn_client_policy = NULL;
+ const struct authn_server_policy *authn_server_policy = NULL;
+ int64_t enforced_tgt_lifetime_raw;
+ const bool user2user = (flags & SDB_F_USER2USER_PRINCIPAL);
+
+ *entry = (struct sdb_entry) {};
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ if (supported_enctypes == 0) {
+ supported_enctypes = default_supported_enctypes;
+ }
+
+ if (dsdb_functional_level(kdc_db_ctx->samdb) >= DS_DOMAIN_FUNCTION_2008) {
+ domain_enctypes |= ENC_HMAC_SHA1_96_AES128 | ENC_HMAC_SHA1_96_AES256;
+ }
+
+ if (ldb_msg_find_element(msg, "msDS-SecondaryKrbTgtNumber")) {
+ is_rodc = true;
+ }
+
+ if (!samAccountName) {
+ ret = ENOENT;
+ krb5_set_error_message(context, ret, "samba_kdc_message2entry: no samAccountName present");
+ goto out;
+ }
+
+ objectclasses = ldb_msg_find_element(msg, "objectClass");
+
+ if (objectclasses && ldb_msg_find_val(objectclasses, &computer_val)) {
+ is_computer = TRUE;
+ }
+
+ p = talloc_zero(tmp_ctx, struct samba_kdc_entry);
+ if (!p) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ p->is_rodc = is_rodc;
+ p->kdc_db_ctx = kdc_db_ctx;
+ p->realm_dn = talloc_reference(p, realm_dn);
+ if (!p->realm_dn) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ talloc_set_destructor(p, samba_kdc_entry_destructor);
+
+ entry->skdc_entry = p;
+
+ userAccountControl = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
+
+ msDS_User_Account_Control_Computed
+ = ldb_msg_find_attr_as_uint(msg,
+ "msDS-User-Account-Control-Computed",
+ UF_ACCOUNTDISABLE);
+
+ /*
+ * This brings in the lockout flag, block the account if not
+ * found. We need the weird UF_ACCOUNTDISABLE check because
+ * we do not want to fail open if the value is not returned,
+ * but 0 is a valid value (all OK)
+ */
+ if (msDS_User_Account_Control_Computed == UF_ACCOUNTDISABLE) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "samba_kdc_message2entry: "
+ "no msDS-User-Account-Control-Computed present");
+ goto out;
+ } else {
+ userAccountControl |= msDS_User_Account_Control_Computed;
+ }
+
+ if (ent_type == SAMBA_KDC_ENT_TYPE_KRBTGT) {
+ p->is_krbtgt = true;
+ }
+
+ /* First try and figure out the flags based on the userAccountControl */
+ entry->flags = uf2SDBFlags(context, userAccountControl, ent_type);
+
+ /*
+ * Take control of the returned principal here, rather than
+ * allowing the Heimdal code to do it as we have specific
+ * behaviour around the forced realm to honour
+ */
+ entry->flags.force_canonicalize = true;
+
+ /*
+ * Windows 2008 seems to enforce this (very sensible) rule by
+ * default - don't allow offline attacks on a user's password
+ * by asking for a ticket to them as a service (encrypted with
+ * their probably pathetically insecure password)
+ *
+ * But user2user avoids using the keys based on the password,
+ * so we can allow it.
+ */
+
+ if (entry->flags.server && !user2user
+ && lpcfg_parm_bool(lp_ctx, NULL, "kdc", "require spn for service", true)) {
+ if (!is_computer && !ldb_msg_find_attr_as_string(msg, "servicePrincipalName", NULL)) {
+ entry->flags.server = 0;
+ }
+ }
+
+ /*
+ * We restrict a 3-part SPN ending in my domain/realm to full
+ * domain controllers.
+ *
+ * This avoids any cases where (eg) a demoted DC still has
+ * these more restricted SPNs.
+ */
+ if (krb5_princ_size(context, principal) > 2) {
+ char *third_part = NULL;
+ bool is_our_realm;
+ bool is_dc;
+
+ ret = smb_krb5_principal_get_comp_string(tmp_ctx,
+ context,
+ principal,
+ 2,
+ &third_part);
+ if (ret) {
+ krb5_set_error_message(context, ret, "smb_krb5_principal_get_comp_string: out of memory");
+ goto out;
+ }
+
+ is_our_realm = lpcfg_is_my_domain_or_realm(lp_ctx,
+ third_part);
+ is_dc = userAccountControl &
+ (UF_SERVER_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT);
+ if (is_our_realm && !is_dc) {
+ entry->flags.server = 0;
+ }
+ }
+ /*
+ * To give the correct type of error to the client, we must
+ * not just return the entry without .server set, we must
+ * pretend the principal does not exist. Otherwise we may
+ * return ERR_POLICY instead of
+ * KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN
+ */
+ if (ent_type == SAMBA_KDC_ENT_TYPE_SERVER && entry->flags.server == 0) {
+ ret = SDB_ERR_NOENTRY;
+ krb5_set_error_message(context, ret, "samba_kdc_message2entry: no servicePrincipalName present for this server, refusing with no-such-entry");
+ goto out;
+ }
+ if (flags & SDB_F_ADMIN_DATA) {
+ /* These (created_by, modified_by) parts of the entry are not relevant for Samba4's use
+ * of the Heimdal KDC. They are stored in the traditional
+ * DB for audit purposes, and still form part of the structure
+ * we must return */
+
+ /* use 'whenCreated' */
+ entry->created_by.time = ldb_msg_find_krb5time_ldap_time(msg, "whenCreated", 0);
+ /* use 'kadmin' for now (needed by mit_samba) */
+
+ ret = smb_krb5_make_principal(context,
+ &entry->created_by.principal,
+ lpcfg_realm(lp_ctx), "kadmin", NULL);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+
+ entry->modified_by = calloc(1, sizeof(struct sdb_event));
+ if (entry->modified_by == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "calloc: out of memory");
+ goto out;
+ }
+
+ /* use 'whenChanged' */
+ entry->modified_by->time = ldb_msg_find_krb5time_ldap_time(msg, "whenChanged", 0);
+ /* use 'kadmin' for now (needed by mit_samba) */
+ ret = smb_krb5_make_principal(context,
+ &entry->modified_by->principal,
+ lpcfg_realm(lp_ctx), "kadmin", NULL);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+ }
+
+
+ /* The lack of password controls etc applies to krbtgt by
+ * virtue of being that particular RID */
+ ret = samdb_result_dom_sid_buf(msg, "objectSid", &sid);
+ if (ret) {
+ goto out;
+ }
+ status = dom_sid_split_rid(NULL, &sid, NULL, &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = EINVAL;
+ goto out;
+ }
+
+ if (rid == DOMAIN_RID_KRBTGT) {
+ char *realm = NULL;
+
+ entry->valid_end = NULL;
+ entry->pw_end = NULL;
+
+ entry->flags.invalid = 0;
+ entry->flags.server = 1;
+
+ realm = smb_krb5_principal_get_realm(
+ tmp_ctx, context, principal);
+ if (realm == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ /* Don't mark all requests for the krbtgt/realm as
+ * 'change password', as otherwise we could get into
+ * trouble, and not enforce the password expiry.
+ * Instead, only do it when request is for the kpasswd service */
+ if (ent_type == SAMBA_KDC_ENT_TYPE_SERVER) {
+ bool is_changepw = false;
+
+ ret = is_kadmin_changepw(context, principal, &is_changepw);
+ if (ret) {
+ goto out;
+ }
+
+ if (is_changepw && lpcfg_is_my_domain_or_realm(lp_ctx, realm)) {
+ entry->flags.change_pw = 1;
+ }
+ }
+
+ TALLOC_FREE(realm);
+
+ entry->flags.client = 0;
+ entry->flags.forwardable = 1;
+ entry->flags.ok_as_delegate = 1;
+ } else if (is_rodc) {
+ /* The RODC krbtgt account is like the main krbtgt,
+ * but it does not have a changepw or kadmin
+ * service */
+
+ entry->valid_end = NULL;
+ entry->pw_end = NULL;
+
+ /* Also don't allow the RODC krbtgt to be a client (it should not be needed) */
+ entry->flags.client = 0;
+ entry->flags.invalid = 0;
+ entry->flags.server = 1;
+
+ entry->flags.client = 0;
+ entry->flags.forwardable = 1;
+ entry->flags.ok_as_delegate = 0;
+ } else if (entry->flags.server && ent_type == SAMBA_KDC_ENT_TYPE_SERVER) {
+ /* The account/password expiry only applies when the account is used as a
+ * client (ie password login), not when used as a server */
+
+ /* Make very well sure we don't use this for a client,
+ * it could bypass the password restrictions */
+ entry->flags.client = 0;
+
+ entry->valid_end = NULL;
+ entry->pw_end = NULL;
+
+ } else {
+ NTTIME must_change_time
+ = samdb_result_nttime(msg,
+ "msDS-UserPasswordExpiryTimeComputed",
+ 0);
+ if (must_change_time == 0x7FFFFFFFFFFFFFFFULL) {
+ entry->pw_end = NULL;
+ } else {
+ entry->pw_end = malloc(sizeof(*entry->pw_end));
+ if (entry->pw_end == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+ *entry->pw_end = nt_time_to_unix(must_change_time);
+ }
+
+ acct_expiry = samdb_result_account_expires(msg);
+ if (acct_expiry == 0x7FFFFFFFFFFFFFFFULL) {
+ entry->valid_end = NULL;
+ } else {
+ entry->valid_end = malloc(sizeof(*entry->valid_end));
+ if (entry->valid_end == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+ *entry->valid_end = nt_time_to_unix(acct_expiry);
+ }
+ }
+
+ ret = samba_kdc_get_entry_principal(context,
+ kdc_db_ctx,
+ samAccountName,
+ ent_type,
+ flags,
+ entry->flags.change_pw,
+ principal,
+ &entry->principal);
+ if (ret != 0) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+
+ entry->valid_start = NULL;
+
+ entry->max_life = malloc(sizeof(*entry->max_life));
+ if (entry->max_life == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ if (ent_type == SAMBA_KDC_ENT_TYPE_SERVER) {
+ *entry->max_life = kdc_db_ctx->policy.svc_tkt_lifetime;
+ } else if (ent_type == SAMBA_KDC_ENT_TYPE_KRBTGT || ent_type == SAMBA_KDC_ENT_TYPE_CLIENT) {
+ *entry->max_life = kdc_db_ctx->policy.usr_tkt_lifetime;
+ } else {
+ *entry->max_life = MIN(kdc_db_ctx->policy.svc_tkt_lifetime,
+ kdc_db_ctx->policy.usr_tkt_lifetime);
+ }
+
+ if (entry->flags.change_pw) {
+ /* Limit lifetime of kpasswd tickets to two minutes or less. */
+ *entry->max_life = MIN(*entry->max_life, CHANGEPW_LIFETIME);
+ }
+
+ entry->max_renew = malloc(sizeof(*entry->max_renew));
+ if (entry->max_renew == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ *entry->max_renew = kdc_db_ctx->policy.renewal_lifetime;
+
+ /*
+ * A principal acting as a client that is not being looked up as the
+ * principal of an armor ticket may have an authentication policy apply
+ * to it.
+ *
+ * We won’t get an authentication policy for the client of an S4U2Self
+ * or S4U2Proxy request. Those clients are looked up with
+ * SDB_F_FOR_TGS_REQ instead of with SDB_F_FOR_AS_REQ.
+ */
+ if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT &&
+ (flags & SDB_F_FOR_AS_REQ) &&
+ !(flags & SDB_F_ARMOR_PRINCIPAL))
+ {
+ ret = authn_policy_kerberos_client(kdc_db_ctx->samdb, tmp_ctx, msg,
+ &authn_client_policy);
+ if (ret) {
+ goto out;
+ }
+ }
+
+ /*
+ * A principal acting as a server may have an authentication policy
+ * apply to it.
+ */
+ if (ent_type == SAMBA_KDC_ENT_TYPE_SERVER) {
+ ret = authn_policy_server(kdc_db_ctx->samdb, tmp_ctx, msg,
+ &authn_server_policy);
+ if (ret) {
+ goto out;
+ }
+ }
+
+ enforced_tgt_lifetime_raw = authn_policy_enforced_tgt_lifetime_raw(authn_client_policy);
+ if (enforced_tgt_lifetime_raw != 0) {
+ int64_t lifetime_secs = enforced_tgt_lifetime_raw;
+
+ lifetime_secs /= INT64_C(1000) * 1000 * 10;
+ lifetime_secs = MIN(lifetime_secs, INT_MAX);
+ lifetime_secs = MAX(lifetime_secs, INT_MIN);
+
+ /*
+ * Set both lifetime and renewal time based only on the
+ * configured maximum lifetime — not on the configured renewal
+ * time. Yes, this is what Windows does.
+ */
+ lifetime_secs = MIN(*entry->max_life, lifetime_secs);
+ *entry->max_life = lifetime_secs;
+ *entry->max_renew = lifetime_secs;
+ }
+
+ if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT && (flags & SDB_F_FOR_AS_REQ)) {
+ int result;
+ const struct auth_user_info_dc *user_info_dc = NULL;
+ /*
+ * These protections only apply to clients, so servers in the
+ * Protected Users group may still have service tickets to them
+ * encrypted with RC4. For accounts looked up as servers, note
+ * that 'msg' does not contain the 'memberOf' attribute for
+ * determining whether the account is a member of Protected
+ * Users.
+ *
+ * Additionally, Microsoft advises that accounts for services
+ * and computers should never be members of Protected Users, or
+ * they may fail to authenticate.
+ */
+ ret = samba_kdc_get_user_info_from_db(tmp_ctx,
+ kdc_db_ctx->samdb,
+ p,
+ msg,
+ &user_info_dc);
+ if (ret) {
+ goto out;
+ }
+
+ result = dsdb_is_protected_user(kdc_db_ctx->samdb,
+ user_info_dc->sids,
+ user_info_dc->num_sids);
+ if (result == -1) {
+ ret = EINVAL;
+ goto out;
+ }
+
+ protected_user = result;
+
+ if (protected_user) {
+ entry->flags.forwardable = 0;
+ entry->flags.proxiable = 0;
+
+ if (enforced_tgt_lifetime_raw == 0) {
+ /*
+ * If a TGT lifetime hasn’t been set, Protected
+ * Users enforces a four hour TGT lifetime.
+ */
+ *entry->max_life = MIN(*entry->max_life, 4 * 60 * 60);
+ *entry->max_renew = MIN(*entry->max_renew, 4 * 60 * 60);
+ }
+ }
+ }
+
+ if (rid == DOMAIN_RID_KRBTGT || is_rodc) {
+ bool enable_fast;
+
+ is_krbtgt = true;
+
+ /*
+ * KDCs (and KDCs on RODCs)
+ * ignore msDS-SupportedEncryptionTypes completely
+ * but support all supported enctypes by the domain.
+ */
+ supported_enctypes = domain_enctypes;
+
+ enable_fast = lpcfg_kdc_enable_fast(kdc_db_ctx->lp_ctx);
+ if (enable_fast) {
+ supported_enctypes |= ENC_FAST_SUPPORTED;
+ }
+
+ supported_enctypes |= ENC_CLAIMS_SUPPORTED;
+ supported_enctypes |= ENC_COMPOUND_IDENTITY_SUPPORTED;
+
+ /*
+ * Resource SID compression is enabled implicitly, unless
+ * disabled in msDS-SupportedEncryptionTypes.
+ */
+
+ } else if (userAccountControl & (UF_PARTIAL_SECRETS_ACCOUNT|UF_SERVER_TRUST_ACCOUNT)) {
+ /*
+ * DCs and RODCs computer accounts take
+ * msDS-SupportedEncryptionTypes unmodified, but
+ * force all enctypes supported by the domain.
+ */
+ supported_enctypes |= domain_enctypes;
+
+ } else if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT ||
+ (ent_type == SAMBA_KDC_ENT_TYPE_ANY)) {
+ /*
+ * for AS-REQ the client chooses the enc types it
+ * supports, and this will vary between computers a
+ * user logs in from. Therefore, so that we accept any
+ * of the client's keys for decrypting padata,
+ * supported_enctypes should not restrict etype usage.
+ *
+ * likewise for 'any' return as much as is supported,
+ * to export into a keytab.
+ */
+ supported_enctypes |= ENC_ALL_TYPES;
+ }
+
+ /* If UF_USE_DES_KEY_ONLY has been set, then don't allow use of the newer enc types */
+ if (userAccountControl & UF_USE_DES_KEY_ONLY) {
+ supported_enctypes &= ~ENC_ALL_TYPES;
+ }
+
+ if (protected_user) {
+ supported_enctypes &= ~ENC_RC4_HMAC_MD5;
+ }
+
+ pa_supported_enctypes = supported_enctypes;
+ supported_session_etypes = supported_enctypes;
+ if (supported_session_etypes & ENC_HMAC_SHA1_96_AES256_SK) {
+ supported_session_etypes |= ENC_HMAC_SHA1_96_AES256;
+ supported_session_etypes |= ENC_HMAC_SHA1_96_AES128;
+ }
+ if (force_rc4) {
+ supported_session_etypes |= ENC_RC4_HMAC_MD5;
+ }
+ /*
+ * now that we remembered what to announce in pa_supported_enctypes
+ * and normalized ENC_HMAC_SHA1_96_AES256_SK, we restrict the
+ * rest to the enc types the local kdc supports.
+ */
+ supported_enctypes &= kdc_enctypes;
+ supported_session_etypes &= kdc_enctypes;
+
+ /* Get keys from the db */
+ ret = samba_kdc_message2entry_keys(context, p, msg,
+ is_krbtgt, is_rodc,
+ userAccountControl,
+ ent_type, flags, kvno, entry,
+ supported_enctypes,
+ &available_enctypes);
+ if (ret) {
+ /* Could be bogus data in the entry, or out of memory */
+ goto out;
+ }
+
+ /*
+ * If we only have a nthash stored,
+ * but a better session key would be
+ * available, we fallback to fetching the
+ * RC4_HMAC_MD5, which implicitly also
+ * would allow an RC4_HMAC_MD5 session key.
+ * But only if the kdc actually supports
+ * RC4_HMAC_MD5.
+ */
+ if (available_enctypes == 0 &&
+ (supported_enctypes & ENC_RC4_HMAC_MD5) == 0 &&
+ (supported_enctypes & ~ENC_RC4_HMAC_MD5) != 0 &&
+ (kdc_enctypes & ENC_RC4_HMAC_MD5) != 0)
+ {
+ supported_enctypes = ENC_RC4_HMAC_MD5;
+ ret = samba_kdc_message2entry_keys(context, p, msg,
+ is_krbtgt, is_rodc,
+ userAccountControl,
+ ent_type, flags, kvno, entry,
+ supported_enctypes,
+ &available_enctypes);
+ if (ret) {
+ /* Could be bogus data in the entry, or out of memory */
+ goto out;
+ }
+ }
+
+ /*
+ * We need to support all session keys enctypes for
+ * all keys we provide
+ */
+ supported_session_etypes |= available_enctypes;
+
+ ret = sdb_entry_set_etypes(entry);
+ if (ret) {
+ goto out;
+ }
+
+ if (entry->flags.server) {
+ bool add_aes256 =
+ supported_session_etypes & KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96;
+ bool add_aes128 =
+ supported_session_etypes & KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96;
+ bool add_rc4 =
+ supported_session_etypes & ENC_RC4_HMAC_MD5;
+ ret = sdb_entry_set_session_etypes(entry,
+ add_aes256,
+ add_aes128,
+ add_rc4);
+ if (ret) {
+ goto out;
+ }
+ }
+
+ if (entry->keys.len != 0) {
+ /*
+ * FIXME: Currently limited to Heimdal so as not to
+ * break MIT KDCs, for which no fix is available.
+ */
+#ifdef SAMBA4_USES_HEIMDAL
+ if (is_krbtgt) {
+ /*
+ * The krbtgt account, having no reason to
+ * issue tickets encrypted in weaker keys,
+ * shall only make available its strongest
+ * key. All weaker keys are stripped out. This
+ * makes it impossible for an RC4-encrypted
+ * TGT to be accepted when AES KDC keys exist.
+ *
+ * This controls the ticket key and so the PAC
+ * signature algorithms indirectly, preventing
+ * a weak KDC checksum from being accepted
+ * when we verify the signatures for an
+ * S4U2Proxy evidence ticket. As such, this is
+ * indispensable for addressing
+ * CVE-2022-37966.
+ *
+ * Being strict here also provides protection
+ * against possible future attacks on weak
+ * keys.
+ */
+ entry->keys.len = 1;
+ if (entry->etypes != NULL) {
+ entry->etypes->len = MIN(entry->etypes->len, 1);
+ }
+ entry->old_keys.len = MIN(entry->old_keys.len, 1);
+ entry->older_keys.len = MIN(entry->older_keys.len, 1);
+ }
+#endif
+ } else if (kdc_db_ctx->rodc) {
+ /*
+ * We are on an RODC, but don't have keys for this
+ * account. Signal this to the caller
+ */
+ auth_sam_trigger_repl_secret(kdc_db_ctx,
+ kdc_db_ctx->msg_ctx,
+ kdc_db_ctx->ev_ctx,
+ msg->dn);
+ ret = SDB_ERR_NOT_FOUND_HERE;
+ goto out;
+ } else {
+ /*
+ * oh, no password. Apparently (comment in
+ * hdb-ldap.c) this violates the ASN.1, but this
+ * allows an entry with no keys (yet).
+ */
+ }
+
+ p->msg = talloc_steal(p, msg);
+ p->supported_enctypes = pa_supported_enctypes;
+
+ p->client_policy = talloc_steal(p, authn_client_policy);
+ p->server_policy = talloc_steal(p, authn_server_policy);
+
+ talloc_steal(kdc_db_ctx, p);
+
+out:
+ if (ret != 0) {
+ /* This doesn't free ent itself, that is for the eventual caller to do */
+ sdb_entry_free(entry);
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ * Construct an hdb_entry from a directory entry.
+ * The kvno is what the remote client asked for
+ */
+static krb5_error_code samba_kdc_trust_message2entry(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ TALLOC_CTX *mem_ctx,
+ enum trust_direction direction,
+ struct ldb_dn *realm_dn,
+ unsigned flags,
+ uint32_t kvno,
+ struct ldb_message *msg,
+ struct sdb_entry *entry)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct loadparm_context *lp_ctx = kdc_db_ctx->lp_ctx;
+ const char *our_realm = lpcfg_realm(lp_ctx);
+ char *partner_realm = NULL;
+ const char *realm = NULL;
+ const char *krbtgt_realm = NULL;
+ DATA_BLOB password_utf16 = data_blob_null;
+ DATA_BLOB password_utf8 = data_blob_null;
+ struct samr_Password _password_hash;
+ const struct samr_Password *password_hash = NULL;
+ const struct ldb_val *password_val;
+ struct trustAuthInOutBlob password_blob;
+ struct samba_kdc_entry *p;
+ bool use_previous = false;
+ uint32_t current_kvno;
+ uint32_t previous_kvno;
+ uint32_t num_keys = 0;
+ enum ndr_err_code ndr_err;
+ int ret;
+ unsigned int i;
+ struct AuthenticationInformationArray *auth_array;
+ struct timeval tv;
+ NTTIME an_hour_ago;
+ uint32_t *auth_kvno;
+ bool prefer_current = false;
+ bool force_rc4 = lpcfg_kdc_force_enable_rc4_weak_session_keys(lp_ctx);
+ uint32_t supported_enctypes = ENC_RC4_HMAC_MD5;
+ uint32_t pa_supported_enctypes;
+ uint32_t supported_session_etypes;
+ uint32_t config_kdc_enctypes = lpcfg_kdc_supported_enctypes(lp_ctx);
+ uint32_t kdc_enctypes =
+ config_kdc_enctypes != 0 ?
+ config_kdc_enctypes :
+ ENC_ALL_TYPES;
+ struct lsa_TrustDomainInfoInfoEx *tdo = NULL;
+ NTSTATUS status;
+
+ *entry = (struct sdb_entry) {};
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ if (dsdb_functional_level(kdc_db_ctx->samdb) >= DS_DOMAIN_FUNCTION_2008) {
+ /* If not told otherwise, Windows now assumes that trusts support AES. */
+ supported_enctypes = ldb_msg_find_attr_as_uint(msg,
+ "msDS-SupportedEncryptionTypes",
+ ENC_HMAC_SHA1_96_AES256);
+ }
+
+ pa_supported_enctypes = supported_enctypes;
+ supported_session_etypes = supported_enctypes;
+ if (supported_session_etypes & ENC_HMAC_SHA1_96_AES256_SK) {
+ supported_session_etypes |= ENC_HMAC_SHA1_96_AES256;
+ supported_session_etypes |= ENC_HMAC_SHA1_96_AES128;
+ }
+ if (force_rc4) {
+ supported_session_etypes |= ENC_RC4_HMAC_MD5;
+ }
+ /*
+ * now that we remembered what to announce in pa_supported_enctypes
+ * and normalized ENC_HMAC_SHA1_96_AES256_SK, we restrict the
+ * rest to the enc types the local kdc supports.
+ */
+ supported_enctypes &= kdc_enctypes;
+ supported_session_etypes &= kdc_enctypes;
+
+ status = dsdb_trust_parse_tdo_info(tmp_ctx, msg, &tdo);
+ if (!NT_STATUS_IS_OK(status)) {
+ krb5_clear_error_message(context);
+ ret = ENOMEM;
+ goto out;
+ }
+
+ if (!(tdo->trust_direction & direction)) {
+ krb5_clear_error_message(context);
+ ret = SDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ if (tdo->trust_type != LSA_TRUST_TYPE_UPLEVEL) {
+ /*
+ * Only UPLEVEL domains support kerberos here,
+ * as we don't support LSA_TRUST_TYPE_MIT.
+ */
+ krb5_clear_error_message(context);
+ ret = SDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ if (tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION) {
+ /*
+ * We don't support selective authentication yet.
+ */
+ krb5_clear_error_message(context);
+ ret = SDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ if (tdo->domain_name.string == NULL) {
+ krb5_clear_error_message(context);
+ ret = SDB_ERR_NOENTRY;
+ goto out;
+ }
+ partner_realm = strupper_talloc(tmp_ctx, tdo->domain_name.string);
+ if (partner_realm == NULL) {
+ krb5_clear_error_message(context);
+ ret = ENOMEM;
+ goto out;
+ }
+
+ if (direction == INBOUND) {
+ realm = our_realm;
+ krbtgt_realm = partner_realm;
+
+ password_val = ldb_msg_find_ldb_val(msg, "trustAuthIncoming");
+ } else { /* OUTBOUND */
+ realm = partner_realm;
+ krbtgt_realm = our_realm;
+
+ password_val = ldb_msg_find_ldb_val(msg, "trustAuthOutgoing");
+ }
+
+ if (password_val == NULL) {
+ krb5_clear_error_message(context);
+ ret = SDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ ndr_err = ndr_pull_struct_blob(password_val, tmp_ctx, &password_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ krb5_clear_error_message(context);
+ ret = EINVAL;
+ goto out;
+ }
+
+ p = talloc_zero(tmp_ctx, struct samba_kdc_entry);
+ if (!p) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ p->is_trust = true;
+ p->kdc_db_ctx = kdc_db_ctx;
+ p->realm_dn = realm_dn;
+ p->supported_enctypes = pa_supported_enctypes;
+
+ talloc_set_destructor(p, samba_kdc_entry_destructor);
+
+ entry->skdc_entry = p;
+
+ /* use 'whenCreated' */
+ entry->created_by.time = ldb_msg_find_krb5time_ldap_time(msg, "whenCreated", 0);
+ /* use 'kadmin' for now (needed by mit_samba) */
+ ret = smb_krb5_make_principal(context,
+ &entry->created_by.principal,
+ realm, "kadmin", NULL);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+
+ /*
+ * We always need to generate the canonicalized principal
+ * with the values of our database.
+ */
+ ret = smb_krb5_make_principal(context, &entry->principal, realm,
+ "krbtgt", krbtgt_realm, NULL);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+ smb_krb5_principal_set_type(context, entry->principal,
+ KRB5_NT_SRV_INST);
+
+ entry->valid_start = NULL;
+
+ /* we need to work out if we are going to use the current or
+ * the previous password hash.
+ * We base this on the kvno the client passes in. If the kvno
+ * passed in is equal to the current kvno in our database then
+ * we use the current structure. If it is the current kvno-1,
+ * then we use the previous substructure.
+ */
+
+ /*
+ * Windows prefers the previous key for one hour.
+ */
+ tv = timeval_current();
+ if (tv.tv_sec > 3600) {
+ tv.tv_sec -= 3600;
+ }
+ an_hour_ago = timeval_to_nttime(&tv);
+
+ /* first work out the current kvno */
+ current_kvno = 0;
+ for (i=0; i < password_blob.count; i++) {
+ struct AuthenticationInformation *a =
+ &password_blob.current.array[i];
+
+ if (a->LastUpdateTime <= an_hour_ago) {
+ prefer_current = true;
+ }
+
+ if (a->AuthType == TRUST_AUTH_TYPE_VERSION) {
+ current_kvno = a->AuthInfo.version.version;
+ }
+ }
+ if (current_kvno == 0) {
+ previous_kvno = 255;
+ } else {
+ previous_kvno = current_kvno - 1;
+ }
+ for (i=0; i < password_blob.count; i++) {
+ struct AuthenticationInformation *a =
+ &password_blob.previous.array[i];
+
+ if (a->AuthType == TRUST_AUTH_TYPE_VERSION) {
+ previous_kvno = a->AuthInfo.version.version;
+ }
+ }
+
+ /* work out whether we will use the previous or current
+ password */
+ if (password_blob.previous.count == 0) {
+ /* there is no previous password */
+ use_previous = false;
+ } else if (!(flags & SDB_F_KVNO_SPECIFIED)) {
+ /*
+ * If not specified we use the lowest kvno
+ * for the first hour after an update.
+ */
+ if (prefer_current) {
+ use_previous = false;
+ } else if (previous_kvno < current_kvno) {
+ use_previous = true;
+ } else {
+ use_previous = false;
+ }
+ } else if (kvno == current_kvno) {
+ /*
+ * Exact match ...
+ */
+ use_previous = false;
+ } else if (kvno == previous_kvno) {
+ /*
+ * Exact match ...
+ */
+ use_previous = true;
+ } else {
+ /*
+ * Fallback to the current one for anything else
+ */
+ use_previous = false;
+ }
+
+ if (use_previous) {
+ auth_array = &password_blob.previous;
+ auth_kvno = &previous_kvno;
+ } else {
+ auth_array = &password_blob.current;
+ auth_kvno = &current_kvno;
+ }
+
+ /* use the kvno the client specified, if available */
+ if (flags & SDB_F_KVNO_SPECIFIED) {
+ entry->kvno = kvno;
+ } else {
+ entry->kvno = *auth_kvno;
+ }
+
+ for (i=0; i < auth_array->count; i++) {
+ if (auth_array->array[i].AuthType == TRUST_AUTH_TYPE_CLEAR) {
+ bool ok;
+
+ password_utf16 = data_blob_const(auth_array->array[i].AuthInfo.clear.password,
+ auth_array->array[i].AuthInfo.clear.size);
+ if (password_utf16.length == 0) {
+ break;
+ }
+
+ if (supported_enctypes & ENC_RC4_HMAC_MD5) {
+ mdfour(_password_hash.hash, password_utf16.data, password_utf16.length);
+ if (password_hash == NULL) {
+ num_keys += 1;
+ }
+ password_hash = &_password_hash;
+ }
+
+ if (!(supported_enctypes & (ENC_HMAC_SHA1_96_AES128|ENC_HMAC_SHA1_96_AES256))) {
+ break;
+ }
+
+ ok = convert_string_talloc(tmp_ctx,
+ CH_UTF16MUNGED, CH_UTF8,
+ password_utf16.data,
+ password_utf16.length,
+ &password_utf8.data,
+ &password_utf8.length);
+ if (!ok) {
+ krb5_clear_error_message(context);
+ ret = ENOMEM;
+ goto out;
+ }
+
+ if (supported_enctypes & ENC_HMAC_SHA1_96_AES128) {
+ num_keys += 1;
+ }
+ if (supported_enctypes & ENC_HMAC_SHA1_96_AES256) {
+ num_keys += 1;
+ }
+ break;
+ } else if (auth_array->array[i].AuthType == TRUST_AUTH_TYPE_NT4OWF) {
+ if (supported_enctypes & ENC_RC4_HMAC_MD5) {
+ password_hash = &auth_array->array[i].AuthInfo.nt4owf.password;
+ num_keys += 1;
+ }
+ }
+ }
+
+ /* Must have found a cleartext or MD4 password */
+ if (num_keys == 0) {
+ DBG_WARNING("no usable key found\n");
+ krb5_clear_error_message(context);
+ ret = SDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ entry->keys.val = calloc(num_keys, sizeof(struct sdb_key));
+ if (entry->keys.val == NULL) {
+ krb5_clear_error_message(context);
+ ret = ENOMEM;
+ goto out;
+ }
+
+ if (password_utf8.length != 0) {
+ struct sdb_key key = {};
+ krb5_const_principal salt_principal = entry->principal;
+ krb5_data salt;
+ krb5_data cleartext_data;
+
+ cleartext_data.data = discard_const_p(char, password_utf8.data);
+ cleartext_data.length = password_utf8.length;
+
+ ret = smb_krb5_get_pw_salt(context,
+ salt_principal,
+ &salt);
+ if (ret != 0) {
+ goto out;
+ }
+
+ if (supported_enctypes & ENC_HMAC_SHA1_96_AES256) {
+ ret = smb_krb5_create_key_from_string(context,
+ salt_principal,
+ &salt,
+ &cleartext_data,
+ ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+ &key.key);
+ if (ret != 0) {
+ smb_krb5_free_data_contents(context, &salt);
+ goto out;
+ }
+
+ entry->keys.val[entry->keys.len] = key;
+ entry->keys.len++;
+ }
+
+ if (supported_enctypes & ENC_HMAC_SHA1_96_AES128) {
+ ret = smb_krb5_create_key_from_string(context,
+ salt_principal,
+ &salt,
+ &cleartext_data,
+ ENCTYPE_AES128_CTS_HMAC_SHA1_96,
+ &key.key);
+ if (ret != 0) {
+ smb_krb5_free_data_contents(context, &salt);
+ goto out;
+ }
+
+ entry->keys.val[entry->keys.len] = key;
+ entry->keys.len++;
+ }
+
+ smb_krb5_free_data_contents(context, &salt);
+ }
+
+ if (password_hash != NULL) {
+ struct sdb_key key = {};
+
+ ret = smb_krb5_keyblock_init_contents(context,
+ ENCTYPE_ARCFOUR_HMAC,
+ password_hash->hash,
+ sizeof(password_hash->hash),
+ &key.key);
+ if (ret != 0) {
+ goto out;
+ }
+
+ entry->keys.val[entry->keys.len] = key;
+ entry->keys.len++;
+ }
+
+ entry->flags = (struct SDBFlags) {};
+ entry->flags.immutable = 1;
+ entry->flags.invalid = 0;
+ entry->flags.server = 1;
+ entry->flags.require_preauth = 1;
+
+ entry->pw_end = NULL;
+
+ entry->max_life = NULL;
+
+ entry->max_renew = NULL;
+
+ /* Match Windows behavior and allow forwardable flag in cross-realm. */
+ entry->flags.forwardable = 1;
+
+ samba_kdc_sort_keys(&entry->keys);
+
+ ret = sdb_entry_set_etypes(entry);
+ if (ret) {
+ goto out;
+ }
+
+ {
+ bool add_aes256 =
+ supported_session_etypes & KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96;
+ bool add_aes128 =
+ supported_session_etypes & KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96;
+ bool add_rc4 =
+ supported_session_etypes & ENC_RC4_HMAC_MD5;
+ ret = sdb_entry_set_session_etypes(entry,
+ add_aes256,
+ add_aes128,
+ add_rc4);
+ if (ret) {
+ goto out;
+ }
+ }
+
+ p->msg = talloc_steal(p, msg);
+
+ talloc_steal(kdc_db_ctx, p);
+
+out:
+ TALLOC_FREE(partner_realm);
+
+ if (ret != 0) {
+ /* This doesn't free ent itself, that is for the eventual caller to do */
+ sdb_entry_free(entry);
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+
+}
+
+static krb5_error_code samba_kdc_lookup_trust(krb5_context context, struct ldb_context *ldb_ctx,
+ TALLOC_CTX *mem_ctx,
+ const char *realm,
+ struct ldb_dn *realm_dn,
+ struct ldb_message **pmsg)
+{
+ NTSTATUS status;
+ const char * const *attrs = trust_attrs;
+
+ status = dsdb_trust_search_tdo(ldb_ctx, realm, realm,
+ attrs, mem_ctx, pmsg);
+ if (NT_STATUS_IS_OK(status)) {
+ return 0;
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ return SDB_ERR_NOENTRY;
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
+ int ret = ENOMEM;
+ krb5_set_error_message(context, ret, "samba_kdc_lookup_trust: out of memory");
+ return ret;
+ } else {
+ int ret = EINVAL;
+ krb5_set_error_message(context, ret, "samba_kdc_lookup_trust: %s", nt_errstr(status));
+ return ret;
+ }
+}
+
+static krb5_error_code samba_kdc_lookup_client(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ TALLOC_CTX *mem_ctx,
+ krb5_const_principal principal,
+ const char **attrs,
+ struct ldb_dn **realm_dn,
+ struct ldb_message **msg)
+{
+ NTSTATUS nt_status;
+ char *principal_string = NULL;
+
+ if (smb_krb5_principal_get_type(context, principal) == KRB5_NT_ENTERPRISE_PRINCIPAL) {
+ krb5_error_code ret = 0;
+
+ ret = smb_krb5_principal_get_comp_string(mem_ctx, context,
+ principal, 0, &principal_string);
+ if (ret) {
+ return ret;
+ }
+ } else {
+ char *principal_string_m = NULL;
+ krb5_error_code ret;
+
+ ret = krb5_unparse_name(context, principal, &principal_string_m);
+ if (ret != 0) {
+ return ret;
+ }
+
+ principal_string = talloc_strdup(mem_ctx, principal_string_m);
+ SAFE_FREE(principal_string_m);
+ if (principal_string == NULL) {
+ return ENOMEM;
+ }
+ }
+
+ nt_status = sam_get_results_principal(kdc_db_ctx->samdb,
+ mem_ctx, principal_string, attrs,
+ realm_dn, msg);
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_SUCH_USER)) {
+ krb5_principal fallback_principal = NULL;
+ unsigned int num_comp;
+ char *fallback_realm = NULL;
+ char *fallback_account = NULL;
+ krb5_error_code ret;
+
+ ret = krb5_parse_name(context, principal_string,
+ &fallback_principal);
+ TALLOC_FREE(principal_string);
+ if (ret != 0) {
+ return ret;
+ }
+
+ num_comp = krb5_princ_size(context, fallback_principal);
+ fallback_realm = smb_krb5_principal_get_realm(
+ mem_ctx, context, fallback_principal);
+ if (fallback_realm == NULL) {
+ krb5_free_principal(context, fallback_principal);
+ return ENOMEM;
+ }
+
+ if (num_comp == 1) {
+ size_t len;
+
+ ret = smb_krb5_principal_get_comp_string(mem_ctx,
+ context, fallback_principal, 0, &fallback_account);
+ if (ret) {
+ krb5_free_principal(context, fallback_principal);
+ TALLOC_FREE(fallback_realm);
+ return ret;
+ }
+
+ len = strlen(fallback_account);
+ if (len >= 2 && fallback_account[len - 1] == '$') {
+ TALLOC_FREE(fallback_account);
+ }
+ }
+ krb5_free_principal(context, fallback_principal);
+ fallback_principal = NULL;
+
+ if (fallback_account != NULL) {
+ char *with_dollar;
+
+ with_dollar = talloc_asprintf(mem_ctx, "%s$",
+ fallback_account);
+ if (with_dollar == NULL) {
+ TALLOC_FREE(fallback_realm);
+ return ENOMEM;
+ }
+ TALLOC_FREE(fallback_account);
+
+ ret = smb_krb5_make_principal(context,
+ &fallback_principal,
+ fallback_realm,
+ with_dollar, NULL);
+ TALLOC_FREE(with_dollar);
+ if (ret != 0) {
+ TALLOC_FREE(fallback_realm);
+ return ret;
+ }
+ }
+ TALLOC_FREE(fallback_realm);
+
+ if (fallback_principal != NULL) {
+ char *fallback_string = NULL;
+
+ ret = krb5_unparse_name(context,
+ fallback_principal,
+ &fallback_string);
+ if (ret != 0) {
+ krb5_free_principal(context, fallback_principal);
+ return ret;
+ }
+
+ nt_status = sam_get_results_principal(kdc_db_ctx->samdb,
+ mem_ctx,
+ fallback_string,
+ attrs,
+ realm_dn, msg);
+ SAFE_FREE(fallback_string);
+ }
+ krb5_free_principal(context, fallback_principal);
+ fallback_principal = NULL;
+ }
+ TALLOC_FREE(principal_string);
+
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_SUCH_USER)) {
+ return SDB_ERR_NOENTRY;
+ } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MEMORY)) {
+ return ENOMEM;
+ } else if (!NT_STATUS_IS_OK(nt_status)) {
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+static krb5_error_code samba_kdc_fetch_client(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ TALLOC_CTX *mem_ctx,
+ krb5_const_principal principal,
+ unsigned flags,
+ krb5_kvno kvno,
+ struct sdb_entry *entry)
+{
+ struct ldb_dn *realm_dn;
+ krb5_error_code ret;
+ struct ldb_message *msg = NULL;
+
+ ret = samba_kdc_lookup_client(context, kdc_db_ctx,
+ mem_ctx, principal, user_attrs,
+ &realm_dn, &msg);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = samba_kdc_message2entry(context, kdc_db_ctx, mem_ctx,
+ principal, SAMBA_KDC_ENT_TYPE_CLIENT,
+ flags, kvno,
+ realm_dn, msg, entry);
+ return ret;
+}
+
+static krb5_error_code samba_kdc_fetch_krbtgt(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ TALLOC_CTX *mem_ctx,
+ krb5_const_principal principal,
+ unsigned flags,
+ uint32_t kvno,
+ struct sdb_entry *entry)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct loadparm_context *lp_ctx = kdc_db_ctx->lp_ctx;
+ krb5_error_code ret = 0;
+ int is_krbtgt;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *realm_dn = ldb_get_default_basedn(kdc_db_ctx->samdb);
+ char *realm_from_princ;
+ char *realm_princ_comp = NULL;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ realm_from_princ = smb_krb5_principal_get_realm(
+ tmp_ctx, context, principal);
+ if (realm_from_princ == NULL) {
+ /* can't happen */
+ ret = SDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ is_krbtgt = smb_krb5_principal_is_tgs(context, principal);
+ if (is_krbtgt == -1) {
+ ret = ENOMEM;
+ goto out;
+ } else if (!is_krbtgt) {
+ /* Not a krbtgt */
+ ret = SDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ /* krbtgt case. Either us or a trusted realm */
+
+ ret = smb_krb5_principal_get_comp_string(tmp_ctx, context, principal, 1, &realm_princ_comp);
+ if (ret == ENOENT) {
+ /* OK. */
+ } else if (ret) {
+ goto out;
+ }
+
+ if (lpcfg_is_my_domain_or_realm(lp_ctx, realm_from_princ)
+ && (realm_princ_comp == NULL || lpcfg_is_my_domain_or_realm(lp_ctx, realm_princ_comp))) {
+ /* us, or someone quite like us */
+ /* Kludge, kludge, kludge. If the realm part of krbtgt/realm,
+ * is in our db, then direct the caller at our primary
+ * krbtgt */
+
+ int lret;
+ unsigned int krbtgt_number;
+ /* w2k8r2 sometimes gives us a kvno of 255 for inter-domain
+ trust tickets. We don't yet know what this means, but we do
+ seem to need to treat it as unspecified */
+ if (flags & (SDB_F_KVNO_SPECIFIED|SDB_F_RODC_NUMBER_SPECIFIED)) {
+ krbtgt_number = SAMBA_KVNO_GET_KRBTGT(kvno);
+ if (kdc_db_ctx->rodc) {
+ if (krbtgt_number != kdc_db_ctx->my_krbtgt_number) {
+ ret = SDB_ERR_NOT_FOUND_HERE;
+ goto out;
+ }
+ }
+ } else {
+ krbtgt_number = kdc_db_ctx->my_krbtgt_number;
+ }
+
+ if (krbtgt_number == kdc_db_ctx->my_krbtgt_number) {
+ lret = dsdb_search_one(kdc_db_ctx->samdb, tmp_ctx,
+ &msg, kdc_db_ctx->krbtgt_dn, LDB_SCOPE_BASE,
+ krbtgt_attrs, DSDB_SEARCH_NO_GLOBAL_CATALOG,
+ "(objectClass=user)");
+ } else {
+ /* We need to look up an RODC krbtgt (perhaps
+ * ours, if we are an RODC, perhaps another
+ * RODC if we are a read-write DC */
+ lret = dsdb_search_one(kdc_db_ctx->samdb, tmp_ctx,
+ &msg, realm_dn, LDB_SCOPE_SUBTREE,
+ krbtgt_attrs,
+ DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_NO_GLOBAL_CATALOG,
+ "(&(objectClass=user)(msDS-SecondaryKrbTgtNumber=%u))", (unsigned)(krbtgt_number));
+ }
+
+ if (lret == LDB_ERR_NO_SUCH_OBJECT) {
+ krb5_warnx(context, "samba_kdc_fetch_krbtgt: could not find KRBTGT number %u in DB!",
+ (unsigned)(krbtgt_number));
+ krb5_set_error_message(context, SDB_ERR_NOENTRY,
+ "samba_kdc_fetch_krbtgt: could not find KRBTGT number %u in DB!",
+ (unsigned)(krbtgt_number));
+ ret = SDB_ERR_NOENTRY;
+ goto out;
+ } else if (lret != LDB_SUCCESS) {
+ krb5_warnx(context, "samba_kdc_fetch_krbtgt: could not find KRBTGT number %u in DB!",
+ (unsigned)(krbtgt_number));
+ krb5_set_error_message(context, SDB_ERR_NOENTRY,
+ "samba_kdc_fetch_krbtgt: could not find KRBTGT number %u in DB!",
+ (unsigned)(krbtgt_number));
+ ret = SDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ ret = samba_kdc_message2entry(context, kdc_db_ctx, mem_ctx,
+ principal, SAMBA_KDC_ENT_TYPE_KRBTGT,
+ flags, kvno, realm_dn, msg, entry);
+ if (ret != 0) {
+ krb5_warnx(context, "samba_kdc_fetch_krbtgt: self krbtgt message2entry failed");
+ }
+ } else {
+ enum trust_direction direction = UNKNOWN;
+ const char *realm = NULL;
+
+ /* Either an inbound or outbound trust */
+
+ if (strcasecmp(lpcfg_realm(lp_ctx), realm_from_princ) == 0) {
+ /* look for inbound trust */
+ direction = INBOUND;
+ realm = realm_princ_comp;
+ } else {
+ bool eq = false;
+
+ ret = is_principal_component_equal_ignoring_case(context, principal, 1, lpcfg_realm(lp_ctx), &eq);
+ if (ret) {
+ goto out;
+ }
+
+ if (eq) {
+ /* look for outbound trust */
+ direction = OUTBOUND;
+ realm = realm_from_princ;
+ } else {
+ krb5_warnx(context, "samba_kdc_fetch_krbtgt: not our realm for trusts ('%s', '%s')",
+ realm_from_princ,
+ realm_princ_comp);
+ krb5_set_error_message(context, SDB_ERR_NOENTRY, "samba_kdc_fetch_krbtgt: not our realm for trusts ('%s', '%s')",
+ realm_from_princ,
+ realm_princ_comp);
+ ret = SDB_ERR_NOENTRY;
+ goto out;
+ }
+ }
+
+ /* Trusted domains are under CN=system */
+
+ ret = samba_kdc_lookup_trust(context, kdc_db_ctx->samdb,
+ tmp_ctx,
+ realm, realm_dn, &msg);
+
+ if (ret != 0) {
+ krb5_warnx(context, "samba_kdc_fetch_krbtgt: could not find principal in DB");
+ krb5_set_error_message(context, ret, "samba_kdc_fetch_krbtgt: could not find principal in DB");
+ goto out;
+ }
+
+ ret = samba_kdc_trust_message2entry(context, kdc_db_ctx, mem_ctx,
+ direction,
+ realm_dn, flags, kvno, msg, entry);
+ if (ret != 0) {
+ krb5_warnx(context, "samba_kdc_fetch_krbtgt: trust_message2entry failed for %s",
+ ldb_dn_get_linearized(msg->dn));
+ krb5_set_error_message(context, ret, "samba_kdc_fetch_krbtgt: "
+ "trust_message2entry failed for %s",
+ ldb_dn_get_linearized(msg->dn));
+ }
+ }
+
+out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static krb5_error_code samba_kdc_lookup_server(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ TALLOC_CTX *mem_ctx,
+ krb5_const_principal principal,
+ unsigned flags,
+ const char **attrs,
+ struct ldb_dn **realm_dn,
+ struct ldb_message **msg)
+{
+ krb5_error_code ret;
+ if ((smb_krb5_principal_get_type(context, principal) != KRB5_NT_ENTERPRISE_PRINCIPAL)
+ && krb5_princ_size(context, principal) >= 2) {
+ /* 'normal server' case */
+ int ldb_ret;
+ NTSTATUS nt_status;
+ struct ldb_dn *user_dn;
+ char *principal_string;
+
+ ret = krb5_unparse_name_flags(context, principal,
+ KRB5_PRINCIPAL_UNPARSE_NO_REALM,
+ &principal_string);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* At this point we may find the host is known to be
+ * in a different realm, so we should generate a
+ * referral instead */
+ nt_status = crack_service_principal_name(kdc_db_ctx->samdb,
+ mem_ctx, principal_string,
+ &user_dn, realm_dn);
+ free(principal_string);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return SDB_ERR_NOENTRY;
+ }
+
+ ldb_ret = dsdb_search_one(kdc_db_ctx->samdb,
+ mem_ctx,
+ msg, user_dn, LDB_SCOPE_BASE,
+ attrs,
+ DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_NO_GLOBAL_CATALOG,
+ "(objectClass=*)");
+ if (ldb_ret != LDB_SUCCESS) {
+ return SDB_ERR_NOENTRY;
+ }
+ return 0;
+ } else if (!(flags & SDB_F_FOR_AS_REQ)
+ && smb_krb5_principal_get_type(context, principal) == KRB5_NT_ENTERPRISE_PRINCIPAL) {
+ /*
+ * The behaviour of accepting an
+ * KRB5_NT_ENTERPRISE_PRINCIPAL server principal
+ * containing a UPN only applies to TGS-REQ packets,
+ * not AS-REQ packets.
+ */
+ return samba_kdc_lookup_client(context, kdc_db_ctx,
+ mem_ctx, principal, attrs,
+ realm_dn, msg);
+ } else {
+ /*
+ * This case is for:
+ * - the AS-REQ, where we only accept
+ * samAccountName based lookups for the server, no
+ * matter if the name is an
+ * KRB5_NT_ENTERPRISE_PRINCIPAL or not
+ * - for the TGS-REQ when we are not given an
+ * KRB5_NT_ENTERPRISE_PRINCIPAL, which also must
+ * only lookup samAccountName based names.
+ */
+ int lret;
+ char *short_princ;
+ krb5_principal enterprise_principal = NULL;
+ krb5_const_principal used_principal = NULL;
+ char *name1 = NULL;
+ size_t len1 = 0;
+ char *filter = NULL;
+
+ if (smb_krb5_principal_get_type(context, principal) == KRB5_NT_ENTERPRISE_PRINCIPAL) {
+ char *str = NULL;
+ /* Need to reparse the enterprise principal to find the real target */
+ if (krb5_princ_size(context, principal) != 1) {
+ ret = KRB5_PARSE_MALFORMED;
+ krb5_set_error_message(context, ret, "samba_kdc_lookup_server: request for an "
+ "enterprise principal with wrong (%d) number of components",
+ krb5_princ_size(context, principal));
+ return ret;
+ }
+ ret = smb_krb5_principal_get_comp_string(mem_ctx, context, principal, 0, &str);
+ if (ret) {
+ return KRB5_PARSE_MALFORMED;
+ }
+ ret = krb5_parse_name(context, str,
+ &enterprise_principal);
+ talloc_free(str);
+ if (ret) {
+ return ret;
+ }
+ used_principal = enterprise_principal;
+ } else {
+ used_principal = principal;
+ }
+
+ /* server as client principal case, but we must not lookup userPrincipalNames */
+ *realm_dn = ldb_get_default_basedn(kdc_db_ctx->samdb);
+
+ /* TODO: Check if it is our realm, otherwise give referral */
+
+ ret = krb5_unparse_name_flags(context, used_principal,
+ KRB5_PRINCIPAL_UNPARSE_NO_REALM |
+ KRB5_PRINCIPAL_UNPARSE_DISPLAY,
+ &short_princ);
+ used_principal = NULL;
+ krb5_free_principal(context, enterprise_principal);
+ enterprise_principal = NULL;
+
+ if (ret != 0) {
+ krb5_set_error_message(context, ret, "samba_kdc_lookup_server: could not parse principal");
+ krb5_warnx(context, "samba_kdc_lookup_server: could not parse principal");
+ return ret;
+ }
+
+ name1 = ldb_binary_encode_string(mem_ctx, short_princ);
+ SAFE_FREE(short_princ);
+ if (name1 == NULL) {
+ return ENOMEM;
+ }
+ len1 = strlen(name1);
+ if (len1 >= 1 && name1[len1 - 1] != '$') {
+ filter = talloc_asprintf(mem_ctx,
+ "(&(objectClass=user)(|(samAccountName=%s)(samAccountName=%s$)))",
+ name1, name1);
+ if (filter == NULL) {
+ return ENOMEM;
+ }
+ } else {
+ filter = talloc_asprintf(mem_ctx,
+ "(&(objectClass=user)(samAccountName=%s))",
+ name1);
+ if (filter == NULL) {
+ return ENOMEM;
+ }
+ }
+
+ lret = dsdb_search_one(kdc_db_ctx->samdb, mem_ctx, msg,
+ *realm_dn, LDB_SCOPE_SUBTREE,
+ attrs,
+ DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_NO_GLOBAL_CATALOG,
+ "%s", filter);
+ if (lret == LDB_ERR_NO_SUCH_OBJECT) {
+ DBG_DEBUG("Failed to find an entry for %s filter:%s\n",
+ name1, filter);
+ return SDB_ERR_NOENTRY;
+ }
+ if (lret == LDB_ERR_CONSTRAINT_VIOLATION) {
+ DBG_DEBUG("Failed to find unique entry for %s filter:%s\n",
+ name1, filter);
+ return SDB_ERR_NOENTRY;
+ }
+ if (lret != LDB_SUCCESS) {
+ DBG_ERR("Failed single search for %s - %s\n",
+ name1, ldb_errstring(kdc_db_ctx->samdb));
+ return SDB_ERR_NOENTRY;
+ }
+ return 0;
+ }
+ return SDB_ERR_NOENTRY;
+}
+
+
+
+static krb5_error_code samba_kdc_fetch_server(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ TALLOC_CTX *mem_ctx,
+ krb5_const_principal principal,
+ unsigned flags,
+ krb5_kvno kvno,
+ struct sdb_entry *entry)
+{
+ krb5_error_code ret;
+ struct ldb_dn *realm_dn;
+ struct ldb_message *msg;
+
+ ret = samba_kdc_lookup_server(context, kdc_db_ctx, mem_ctx, principal,
+ flags, server_attrs, &realm_dn, &msg);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = samba_kdc_message2entry(context, kdc_db_ctx, mem_ctx,
+ principal, SAMBA_KDC_ENT_TYPE_SERVER,
+ flags, kvno,
+ realm_dn, msg, entry);
+ if (ret != 0) {
+ char *client_name = NULL;
+ krb5_error_code code;
+
+ code = krb5_unparse_name(context, principal, &client_name);
+ if (code == 0) {
+ krb5_warnx(context,
+ "samba_kdc_fetch_server: message2entry failed for "
+ "%s",
+ client_name);
+ } else {
+ krb5_warnx(context,
+ "samba_kdc_fetch_server: message2entry and "
+ "krb5_unparse_name failed");
+ }
+ SAFE_FREE(client_name);
+ }
+
+ return ret;
+}
+
+static krb5_error_code samba_kdc_lookup_realm(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ krb5_const_principal principal,
+ unsigned flags,
+ struct sdb_entry *entry)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ krb5_error_code ret;
+ bool check_realm = false;
+ const char *realm = NULL;
+ struct dsdb_trust_routing_table *trt = NULL;
+ const struct lsa_TrustDomainInfoInfoEx *tdo = NULL;
+ unsigned int num_comp;
+ bool ok;
+ char *upper = NULL;
+
+ *entry = (struct sdb_entry) {};
+
+ num_comp = krb5_princ_size(context, principal);
+
+ if (flags & SDB_F_GET_CLIENT) {
+ if (flags & SDB_F_FOR_AS_REQ) {
+ check_realm = true;
+ }
+ }
+ if (flags & SDB_F_GET_SERVER) {
+ if (flags & SDB_F_FOR_TGS_REQ) {
+ check_realm = true;
+ }
+ }
+
+ if (!check_realm) {
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ realm = smb_krb5_principal_get_realm(frame, context, principal);
+ if (realm == NULL) {
+ TALLOC_FREE(frame);
+ return ENOMEM;
+ }
+
+ /*
+ * The requested realm needs to be our own
+ */
+ ok = lpcfg_is_my_domain_or_realm(kdc_db_ctx->lp_ctx, realm);
+ if (!ok) {
+ /*
+ * The request is not for us...
+ */
+ TALLOC_FREE(frame);
+ return SDB_ERR_NOENTRY;
+ }
+
+ if (smb_krb5_principal_get_type(context, principal) == KRB5_NT_ENTERPRISE_PRINCIPAL) {
+ char *principal_string = NULL;
+ krb5_principal enterprise_principal = NULL;
+ char *enterprise_realm = NULL;
+
+ if (num_comp != 1) {
+ TALLOC_FREE(frame);
+ return SDB_ERR_NOENTRY;
+ }
+
+ ret = smb_krb5_principal_get_comp_string(frame, context,
+ principal, 0, &principal_string);
+ if (ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ ret = krb5_parse_name(context, principal_string,
+ &enterprise_principal);
+ TALLOC_FREE(principal_string);
+ if (ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ enterprise_realm = smb_krb5_principal_get_realm(
+ frame, context, enterprise_principal);
+ krb5_free_principal(context, enterprise_principal);
+ if (enterprise_realm != NULL) {
+ realm = enterprise_realm;
+ }
+ }
+
+ if (flags & SDB_F_GET_SERVER) {
+ bool is_krbtgt = false;
+
+ ret = is_principal_component_equal(context, principal, 0, KRB5_TGS_NAME, &is_krbtgt);
+ if (ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ if (is_krbtgt) {
+ /*
+ * we need to search krbtgt/ locally
+ */
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ /*
+ * We need to check the last component against the routing table.
+ *
+ * Note this works only with 2 or 3 component principals, e.g:
+ *
+ * servicePrincipalName: ldap/W2K8R2-219.bla.base
+ * servicePrincipalName: ldap/W2K8R2-219.bla.base/bla.base
+ * servicePrincipalName: ldap/W2K8R2-219.bla.base/ForestDnsZones.bla.base
+ * servicePrincipalName: ldap/W2K8R2-219.bla.base/DomainDnsZones.bla.base
+ */
+
+ if (num_comp == 2 || num_comp == 3) {
+ char *service_realm = NULL;
+
+ ret = smb_krb5_principal_get_comp_string(frame,
+ context,
+ principal,
+ num_comp - 1,
+ &service_realm);
+ if (ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ } else {
+ realm = service_realm;
+ }
+ }
+ }
+
+ ok = lpcfg_is_my_domain_or_realm(kdc_db_ctx->lp_ctx, realm);
+ if (ok) {
+ /*
+ * skip the expensive routing lookup
+ */
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ status = dsdb_trust_routing_table_load(kdc_db_ctx->samdb,
+ frame, &trt);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return EINVAL;
+ }
+
+ tdo = dsdb_trust_routing_by_name(trt, realm);
+ if (tdo == NULL) {
+ /*
+ * This principal has to be local
+ */
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ if (tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) {
+ /*
+ * TODO: handle the routing within the forest
+ *
+ * This should likely be handled in
+ * samba_kdc_message2entry() in case we're
+ * a global catalog. We'd need to check
+ * if realm_dn is our own domain and derive
+ * the dns domain name from realm_dn and check that
+ * against the routing table or fallback to
+ * the tdo we found here.
+ *
+ * But for now we don't support multiple domains
+ * in our forest correctly anyway.
+ *
+ * Just search in our local database.
+ */
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ ret = krb5_copy_principal(context, principal,
+ &entry->principal);
+ if (ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ upper = strupper_talloc(frame, tdo->domain_name.string);
+ if (upper == NULL) {
+ TALLOC_FREE(frame);
+ return ENOMEM;
+ }
+
+ ret = smb_krb5_principal_set_realm(context,
+ entry->principal,
+ upper);
+ if (ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ TALLOC_FREE(frame);
+ return SDB_ERR_WRONG_REALM;
+}
+
+krb5_error_code samba_kdc_fetch(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ krb5_const_principal principal,
+ unsigned flags,
+ krb5_kvno kvno,
+ struct sdb_entry *entry)
+{
+ krb5_error_code ret = SDB_ERR_NOENTRY;
+ TALLOC_CTX *mem_ctx;
+
+ mem_ctx = talloc_named(kdc_db_ctx, 0, "samba_kdc_fetch context");
+ if (!mem_ctx) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "samba_kdc_fetch: talloc_named() failed!");
+ return ret;
+ }
+
+ ret = samba_kdc_lookup_realm(context, kdc_db_ctx,
+ principal, flags, entry);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = SDB_ERR_NOENTRY;
+
+ if (flags & SDB_F_GET_CLIENT) {
+ ret = samba_kdc_fetch_client(context, kdc_db_ctx, mem_ctx, principal, flags, kvno, entry);
+ if (ret != SDB_ERR_NOENTRY) goto done;
+ }
+ if (flags & SDB_F_GET_SERVER) {
+ /* krbtgt fits into this situation for trusted realms, and for resolving different versions of our own realm name */
+ ret = samba_kdc_fetch_krbtgt(context, kdc_db_ctx, mem_ctx, principal, flags, kvno, entry);
+ if (ret != SDB_ERR_NOENTRY) goto done;
+
+ /* We return 'no entry' if it does not start with krbtgt/, so move to the common case quickly */
+ ret = samba_kdc_fetch_server(context, kdc_db_ctx, mem_ctx, principal, flags, kvno, entry);
+ if (ret != SDB_ERR_NOENTRY) goto done;
+ }
+ if (flags & SDB_F_GET_KRBTGT) {
+ ret = samba_kdc_fetch_krbtgt(context, kdc_db_ctx, mem_ctx, principal, flags, kvno, entry);
+ if (ret != SDB_ERR_NOENTRY) goto done;
+ }
+
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+struct samba_kdc_seq {
+ unsigned int index;
+ unsigned int count;
+ struct ldb_message **msgs;
+ struct ldb_dn *realm_dn;
+};
+
+static krb5_error_code samba_kdc_seq(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ struct sdb_entry *entry)
+{
+ krb5_error_code ret;
+ struct samba_kdc_seq *priv = kdc_db_ctx->seq_ctx;
+ const char *realm = lpcfg_realm(kdc_db_ctx->lp_ctx);
+ struct ldb_message *msg = NULL;
+ const char *sAMAccountName = NULL;
+ krb5_principal principal = NULL;
+ TALLOC_CTX *mem_ctx;
+
+ if (!priv) {
+ return SDB_ERR_NOENTRY;
+ }
+
+ mem_ctx = talloc_named(priv, 0, "samba_kdc_seq context");
+
+ if (!mem_ctx) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "samba_kdc_seq: talloc_named() failed!");
+ goto out;
+ }
+
+ while (priv->index < priv->count) {
+ msg = priv->msgs[priv->index++];
+
+ sAMAccountName = ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL);
+ if (sAMAccountName != NULL) {
+ break;
+ }
+ }
+
+ if (sAMAccountName == NULL) {
+ ret = SDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ ret = smb_krb5_make_principal(context, &principal,
+ realm, sAMAccountName, NULL);
+ if (ret != 0) {
+ goto out;
+ }
+
+ ret = samba_kdc_message2entry(context, kdc_db_ctx, mem_ctx,
+ principal, SAMBA_KDC_ENT_TYPE_ANY,
+ SDB_F_ADMIN_DATA|SDB_F_GET_ANY,
+ 0 /* kvno */,
+ priv->realm_dn, msg, entry);
+ krb5_free_principal(context, principal);
+
+out:
+ if (ret != 0) {
+ TALLOC_FREE(priv);
+ kdc_db_ctx->seq_ctx = NULL;
+ } else {
+ talloc_free(mem_ctx);
+ }
+
+ return ret;
+}
+
+krb5_error_code samba_kdc_firstkey(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ struct sdb_entry *entry)
+{
+ struct ldb_context *ldb_ctx = kdc_db_ctx->samdb;
+ struct samba_kdc_seq *priv = kdc_db_ctx->seq_ctx;
+ char *realm;
+ struct ldb_result *res = NULL;
+ krb5_error_code ret;
+ int lret;
+
+ if (priv) {
+ TALLOC_FREE(priv);
+ kdc_db_ctx->seq_ctx = NULL;
+ }
+
+ priv = (struct samba_kdc_seq *) talloc(kdc_db_ctx, struct samba_kdc_seq);
+ if (!priv) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "talloc: out of memory");
+ return ret;
+ }
+
+ priv->index = 0;
+ priv->msgs = NULL;
+ priv->realm_dn = ldb_get_default_basedn(ldb_ctx);
+ priv->count = 0;
+
+ ret = krb5_get_default_realm(context, &realm);
+ if (ret != 0) {
+ TALLOC_FREE(priv);
+ return ret;
+ }
+ krb5_free_default_realm(context, realm);
+
+ lret = dsdb_search(ldb_ctx, priv, &res,
+ priv->realm_dn, LDB_SCOPE_SUBTREE, user_attrs,
+ DSDB_SEARCH_NO_GLOBAL_CATALOG,
+ "(objectClass=user)");
+
+ if (lret != LDB_SUCCESS) {
+ TALLOC_FREE(priv);
+ return SDB_ERR_NOENTRY;
+ }
+
+ priv->count = res->count;
+ priv->msgs = talloc_steal(priv, res->msgs);
+ talloc_free(res);
+
+ kdc_db_ctx->seq_ctx = priv;
+
+ ret = samba_kdc_seq(context, kdc_db_ctx, entry);
+
+ if (ret != 0) {
+ TALLOC_FREE(priv);
+ kdc_db_ctx->seq_ctx = NULL;
+ }
+ return ret;
+}
+
+krb5_error_code samba_kdc_nextkey(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ struct sdb_entry *entry)
+{
+ return samba_kdc_seq(context, kdc_db_ctx, entry);
+}
+
+/* Check if a given entry may delegate or do s4u2self to this target principal
+ *
+ * The safest way to determine 'self' is to check the DB record made at
+ * the time the principal was presented to the KDC.
+ */
+krb5_error_code
+samba_kdc_check_client_matches_target_service(krb5_context context,
+ struct samba_kdc_entry *skdc_entry_client,
+ struct samba_kdc_entry *skdc_entry_server_target)
+{
+ struct dom_sid *orig_sid;
+ struct dom_sid *target_sid;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ orig_sid = samdb_result_dom_sid(frame,
+ skdc_entry_client->msg,
+ "objectSid");
+ target_sid = samdb_result_dom_sid(frame,
+ skdc_entry_server_target->msg,
+ "objectSid");
+
+ /*
+ * Allow delegation to the same record (representing a
+ * principal), even if by a different name. The easy and safe
+ * way to prove this is by SID comparison
+ */
+ if (!(orig_sid && target_sid && dom_sid_equal(orig_sid, target_sid))) {
+ talloc_free(frame);
+ return KRB5KRB_AP_ERR_BADMATCH;
+ }
+
+ talloc_free(frame);
+ return 0;
+}
+
+/* Certificates printed by the Certificate Authority might have a
+ * slightly different form of the user principal name to that in the
+ * database. Allow a mismatch where they both refer to the same
+ * SID */
+
+krb5_error_code
+samba_kdc_check_pkinit_ms_upn_match(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ struct samba_kdc_entry *skdc_entry,
+ krb5_const_principal certificate_principal)
+{
+ krb5_error_code ret;
+ struct ldb_dn *realm_dn;
+ struct ldb_message *msg;
+ struct dom_sid *orig_sid;
+ struct dom_sid *target_sid;
+ const char *ms_upn_check_attrs[] = {
+ "objectSid", NULL
+ };
+
+ TALLOC_CTX *mem_ctx = talloc_named(kdc_db_ctx, 0, "samba_kdc_check_pkinit_ms_upn_match");
+
+ if (!mem_ctx) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "samba_kdc_check_pkinit_ms_upn_match: talloc_named() failed!");
+ return ret;
+ }
+
+ ret = samba_kdc_lookup_client(context, kdc_db_ctx,
+ mem_ctx, certificate_principal,
+ ms_upn_check_attrs, &realm_dn, &msg);
+
+ if (ret != 0) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ orig_sid = samdb_result_dom_sid(mem_ctx, skdc_entry->msg, "objectSid");
+ target_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
+
+ /* Consider these to be the same principal, even if by a different
+ * name. The easy and safe way to prove this is by SID
+ * comparison */
+ if (!(orig_sid && target_sid && dom_sid_equal(orig_sid, target_sid))) {
+ talloc_free(mem_ctx);
+#if defined(KRB5KDC_ERR_CLIENT_NAME_MISMATCH) /* MIT */
+ return KRB5KDC_ERR_CLIENT_NAME_MISMATCH;
+#else /* Heimdal (where this is an enum) */
+ return KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
+#endif
+ }
+
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+/*
+ * Check if a given entry may delegate to this target principal
+ * with S4U2Proxy.
+ */
+krb5_error_code
+samba_kdc_check_s4u2proxy(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ struct samba_kdc_entry *skdc_entry,
+ krb5_const_principal target_principal)
+{
+ krb5_error_code ret;
+ char *tmp = NULL;
+ const char *client_dn = NULL;
+ const char *target_principal_name = NULL;
+ struct ldb_message_element *el;
+ struct ldb_val val;
+ unsigned int i;
+ bool found = false;
+
+ TALLOC_CTX *mem_ctx = talloc_named(kdc_db_ctx, 0, "samba_kdc_check_s4u2proxy");
+
+ if (!mem_ctx) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret,
+ "samba_kdc_check_s4u2proxy:"
+ " talloc_named() failed!");
+ return ret;
+ }
+
+ client_dn = ldb_dn_get_linearized(skdc_entry->msg->dn);
+ if (!client_dn) {
+ if (errno == 0) {
+ errno = ENOMEM;
+ }
+ ret = errno;
+ krb5_set_error_message(context, ret,
+ "samba_kdc_check_s4u2proxy:"
+ " ldb_dn_get_linearized() failed!");
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ el = ldb_msg_find_element(skdc_entry->msg, "msDS-AllowedToDelegateTo");
+ if (el == NULL) {
+ ret = ENOENT;
+ goto bad_option;
+ }
+ SMB_ASSERT(el->num_values != 0);
+
+ /*
+ * This is the Microsoft forwardable flag behavior.
+ *
+ * If the proxy (target) principal is NULL, and we have any authorized
+ * delegation target, allow to forward.
+ */
+ if (target_principal == NULL) {
+ talloc_free(mem_ctx);
+ return 0;
+ }
+
+
+ /*
+ * The main heimdal code already checked that the target_principal
+ * belongs to the same realm as the client.
+ *
+ * So we just need the principal without the realm,
+ * as that is what is configured in the "msDS-AllowedToDelegateTo"
+ * attribute.
+ */
+ ret = krb5_unparse_name_flags(context, target_principal,
+ KRB5_PRINCIPAL_UNPARSE_NO_REALM, &tmp);
+ if (ret) {
+ talloc_free(mem_ctx);
+ krb5_set_error_message(context, ret,
+ "samba_kdc_check_s4u2proxy:"
+ " krb5_unparse_name_flags() failed!");
+ return ret;
+ }
+ DBG_DEBUG("client[%s] for target[%s]\n",
+ client_dn, tmp);
+
+ target_principal_name = talloc_strdup(mem_ctx, tmp);
+ SAFE_FREE(tmp);
+ if (target_principal_name == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret,
+ "samba_kdc_check_s4u2proxy:"
+ " talloc_strdup() failed!");
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ val = data_blob_string_const(target_principal_name);
+
+ for (i=0; i<el->num_values; i++) {
+ struct ldb_val *val1 = &val;
+ struct ldb_val *val2 = &el->values[i];
+ int cmp;
+
+ if (val1->length != val2->length) {
+ continue;
+ }
+
+ cmp = strncasecmp((const char *)val1->data,
+ (const char *)val2->data,
+ val1->length);
+ if (cmp != 0) {
+ continue;
+ }
+
+ found = true;
+ break;
+ }
+
+ if (!found) {
+ ret = ENOENT;
+ goto bad_option;
+ }
+
+ DBG_DEBUG("client[%s] allowed target[%s]\n",
+ client_dn, target_principal_name);
+ talloc_free(mem_ctx);
+ return 0;
+
+bad_option:
+ krb5_set_error_message(context, ret,
+ "samba_kdc_check_s4u2proxy: client[%s] "
+ "not allowed for delegation to target[%s]",
+ client_dn,
+ target_principal_name);
+ talloc_free(mem_ctx);
+ return KRB5KDC_ERR_BADOPTION;
+}
+
+/*
+ * This method is called for S4U2Proxy requests and implements the
+ * resource-based constrained delegation variant, which can support
+ * cross-realm delegation.
+ */
+krb5_error_code samba_kdc_check_s4u2proxy_rbcd(
+ krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ krb5_const_principal client_principal,
+ krb5_const_principal server_principal,
+ const struct auth_user_info_dc *user_info_dc,
+ const struct auth_user_info_dc *device_info_dc,
+ const struct auth_claims auth_claims,
+ struct samba_kdc_entry *proxy_skdc_entry)
+{
+ krb5_error_code code;
+ enum ndr_err_code ndr_err;
+ char *client_name = NULL;
+ char *server_name = NULL;
+ const char *proxy_dn = NULL;
+ const DATA_BLOB *data = NULL;
+ struct security_descriptor *rbcd_security_descriptor = NULL;
+ struct security_token *security_token = NULL;
+ uint32_t session_info_flags =
+ AUTH_SESSION_INFO_DEFAULT_GROUPS |
+ AUTH_SESSION_INFO_DEVICE_DEFAULT_GROUPS |
+ AUTH_SESSION_INFO_SIMPLE_PRIVILEGES |
+ AUTH_SESSION_INFO_FORCE_COMPOUNDED_AUTHENTICATION;
+ /*
+ * Testing shows that although Windows grants SEC_ADS_GENERIC_ALL access
+ * in security descriptors it creates for RBCD, its KDC only requires
+ * SEC_ADS_CONTROL_ACCESS for the access check to succeed.
+ */
+ uint32_t access_desired = SEC_ADS_CONTROL_ACCESS;
+ uint32_t access_granted = 0;
+ NTSTATUS nt_status;
+ TALLOC_CTX *mem_ctx = NULL;
+
+ mem_ctx = talloc_named(kdc_db_ctx,
+ 0,
+ "samba_kdc_check_s4u2proxy_rbcd");
+ if (mem_ctx == NULL) {
+ errno = ENOMEM;
+ code = errno;
+
+ return code;
+ }
+
+ proxy_dn = ldb_dn_get_linearized(proxy_skdc_entry->msg->dn);
+ if (proxy_dn == NULL) {
+ DBG_ERR("ldb_dn_get_linearized failed for proxy_dn!\n");
+ if (errno == 0) {
+ errno = ENOMEM;
+ }
+ code = errno;
+
+ goto out;
+ }
+
+ rbcd_security_descriptor = talloc_zero(mem_ctx,
+ struct security_descriptor);
+ if (rbcd_security_descriptor == NULL) {
+ errno = ENOMEM;
+ code = errno;
+
+ goto out;
+ }
+
+ code = krb5_unparse_name_flags(context,
+ client_principal,
+ KRB5_PRINCIPAL_UNPARSE_DISPLAY,
+ &client_name);
+ if (code != 0) {
+ DBG_ERR("Unable to parse client_principal!\n");
+ goto out;
+ }
+
+ code = krb5_unparse_name_flags(context,
+ server_principal,
+ KRB5_PRINCIPAL_UNPARSE_DISPLAY,
+ &server_name);
+ if (code != 0) {
+ DBG_ERR("Unable to parse server_principal!\n");
+ goto out;
+ }
+
+ DBG_INFO("Check delegation from client[%s] to server[%s] via "
+ "proxy[%s]\n",
+ client_name,
+ server_name,
+ proxy_dn);
+
+ if (!(user_info_dc->info->user_flags & NETLOGON_GUEST)) {
+ session_info_flags |= AUTH_SESSION_INFO_AUTHENTICATED;
+ }
+
+ if (device_info_dc != NULL && !(device_info_dc->info->user_flags & NETLOGON_GUEST)) {
+ session_info_flags |= AUTH_SESSION_INFO_DEVICE_AUTHENTICATED;
+ }
+
+ nt_status = auth_generate_security_token(mem_ctx,
+ kdc_db_ctx->lp_ctx,
+ kdc_db_ctx->samdb,
+ user_info_dc,
+ device_info_dc,
+ auth_claims,
+ session_info_flags,
+ &security_token);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ code = map_errno_from_nt_status(nt_status);
+ goto out;
+ }
+
+ data = ldb_msg_find_ldb_val(proxy_skdc_entry->msg,
+ "msDS-AllowedToActOnBehalfOfOtherIdentity");
+ if (data == NULL) {
+ DBG_WARNING("Could not find security descriptor "
+ "msDS-AllowedToActOnBehalfOfOtherIdentity in "
+ "proxy[%s]\n",
+ proxy_dn);
+ code = KRB5KDC_ERR_BADOPTION;
+ goto out;
+ }
+
+ ndr_err = ndr_pull_struct_blob(
+ data,
+ mem_ctx,
+ rbcd_security_descriptor,
+ (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ errno = ndr_map_error2errno(ndr_err);
+ DBG_ERR("Failed to unmarshall "
+ "msDS-AllowedToActOnBehalfOfOtherIdentity "
+ "security descriptor of proxy[%s]\n",
+ proxy_dn);
+ code = KRB5KDC_ERR_BADOPTION;
+ goto out;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(security_token, security_token);
+ NDR_PRINT_DEBUG(security_descriptor, rbcd_security_descriptor);
+ }
+
+ nt_status = sec_access_check_ds(rbcd_security_descriptor,
+ security_token,
+ access_desired,
+ &access_granted,
+ NULL,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_WARNING("RBCD: sec_access_check_ds(access_desired=%#08x, "
+ "access_granted:%#08x) failed with: %s\n",
+ access_desired,
+ access_granted,
+ nt_errstr(nt_status));
+
+ code = KRB5KDC_ERR_BADOPTION;
+ goto out;
+ }
+
+ DBG_NOTICE("RBCD: Access granted for client[%s]\n", client_name);
+
+ code = 0;
+out:
+ SAFE_FREE(client_name);
+ SAFE_FREE(server_name);
+
+ TALLOC_FREE(mem_ctx);
+ return code;
+}
+
+NTSTATUS samba_kdc_setup_db_ctx(TALLOC_CTX *mem_ctx, struct samba_kdc_base_context *base_ctx,
+ struct samba_kdc_db_context **kdc_db_ctx_out)
+{
+ int ldb_ret;
+ struct ldb_message *msg = NULL;
+ struct auth_session_info *session_info = NULL;
+ struct samba_kdc_db_context *kdc_db_ctx = NULL;
+ /* The idea here is very simple. Using Kerberos to
+ * authenticate the KDC to the LDAP server is highly likely to
+ * be circular.
+ *
+ * In future we may set this up to use EXTERNAL and SSL
+ * certificates, for now it will almost certainly be NTLMSSP_SET_USERNAME
+ */
+
+ kdc_db_ctx = talloc_zero(mem_ctx, struct samba_kdc_db_context);
+ if (kdc_db_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ kdc_db_ctx->ev_ctx = base_ctx->ev_ctx;
+ kdc_db_ctx->lp_ctx = base_ctx->lp_ctx;
+ kdc_db_ctx->msg_ctx = base_ctx->msg_ctx;
+
+ /* get default kdc policy */
+ lpcfg_default_kdc_policy(mem_ctx,
+ base_ctx->lp_ctx,
+ &kdc_db_ctx->policy.svc_tkt_lifetime,
+ &kdc_db_ctx->policy.usr_tkt_lifetime,
+ &kdc_db_ctx->policy.renewal_lifetime);
+
+ session_info = system_session(kdc_db_ctx->lp_ctx);
+ if (session_info == NULL) {
+ talloc_free(kdc_db_ctx);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /* Setup the link to LDB */
+ kdc_db_ctx->samdb = samdb_connect(kdc_db_ctx,
+ base_ctx->ev_ctx,
+ base_ctx->lp_ctx,
+ session_info,
+ NULL,
+ 0);
+ if (kdc_db_ctx->samdb == NULL) {
+ DBG_WARNING("Cannot open samdb for KDC backend!\n");
+ talloc_free(kdc_db_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ /* Find out our own krbtgt kvno */
+ ldb_ret = samdb_rodc(kdc_db_ctx->samdb, &kdc_db_ctx->rodc);
+ if (ldb_ret != LDB_SUCCESS) {
+ DBG_WARNING("Cannot determine if we are an RODC in KDC backend: %s\n",
+ ldb_errstring(kdc_db_ctx->samdb));
+ talloc_free(kdc_db_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+ if (kdc_db_ctx->rodc) {
+ int my_krbtgt_number;
+ const char *secondary_keytab[] = { "msDS-SecondaryKrbTgtNumber", NULL };
+ struct ldb_dn *account_dn = NULL;
+ struct ldb_dn *server_dn = samdb_server_dn(kdc_db_ctx->samdb, kdc_db_ctx);
+ if (!server_dn) {
+ DBG_WARNING("Cannot determine server DN in KDC backend: %s\n",
+ ldb_errstring(kdc_db_ctx->samdb));
+ talloc_free(kdc_db_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ ldb_ret = samdb_reference_dn(kdc_db_ctx->samdb, kdc_db_ctx, server_dn,
+ "serverReference", &account_dn);
+ if (ldb_ret != LDB_SUCCESS) {
+ DBG_WARNING("Cannot determine server account in KDC backend: %s\n",
+ ldb_errstring(kdc_db_ctx->samdb));
+ talloc_free(kdc_db_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ ldb_ret = samdb_reference_dn(kdc_db_ctx->samdb, kdc_db_ctx, account_dn,
+ "msDS-KrbTgtLink", &kdc_db_ctx->krbtgt_dn);
+ talloc_free(account_dn);
+ if (ldb_ret != LDB_SUCCESS) {
+ DBG_WARNING("Cannot determine RODC krbtgt account in KDC backend: %s\n",
+ ldb_errstring(kdc_db_ctx->samdb));
+ talloc_free(kdc_db_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ ldb_ret = dsdb_search_one(kdc_db_ctx->samdb, kdc_db_ctx,
+ &msg, kdc_db_ctx->krbtgt_dn, LDB_SCOPE_BASE,
+ secondary_keytab,
+ DSDB_SEARCH_NO_GLOBAL_CATALOG,
+ "(&(objectClass=user)(msDS-SecondaryKrbTgtNumber=*))");
+ if (ldb_ret != LDB_SUCCESS) {
+ DBG_WARNING("Cannot read krbtgt account %s in KDC backend to get msDS-SecondaryKrbTgtNumber: %s: %s\n",
+ ldb_dn_get_linearized(kdc_db_ctx->krbtgt_dn),
+ ldb_errstring(kdc_db_ctx->samdb),
+ ldb_strerror(ldb_ret));
+ talloc_free(kdc_db_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+ my_krbtgt_number = ldb_msg_find_attr_as_int(msg, "msDS-SecondaryKrbTgtNumber", -1);
+ if (my_krbtgt_number == -1) {
+ DBG_WARNING("Cannot read msDS-SecondaryKrbTgtNumber from krbtgt account %s in KDC backend: got %d\n",
+ ldb_dn_get_linearized(kdc_db_ctx->krbtgt_dn),
+ my_krbtgt_number);
+ talloc_free(kdc_db_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+ kdc_db_ctx->my_krbtgt_number = my_krbtgt_number;
+
+ } else {
+ kdc_db_ctx->my_krbtgt_number = 0;
+ ldb_ret = dsdb_search_one(kdc_db_ctx->samdb, kdc_db_ctx,
+ &msg,
+ ldb_get_default_basedn(kdc_db_ctx->samdb),
+ LDB_SCOPE_SUBTREE,
+ krbtgt_attrs,
+ DSDB_SEARCH_NO_GLOBAL_CATALOG,
+ "(&(objectClass=user)(samAccountName=krbtgt))");
+
+ if (ldb_ret != LDB_SUCCESS) {
+ DBG_WARNING("could not find own KRBTGT in DB: %s\n", ldb_errstring(kdc_db_ctx->samdb));
+ talloc_free(kdc_db_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+ kdc_db_ctx->krbtgt_dn = talloc_steal(kdc_db_ctx, msg->dn);
+ kdc_db_ctx->my_krbtgt_number = 0;
+ talloc_free(msg);
+ }
+ *kdc_db_ctx_out = kdc_db_ctx;
+ return NT_STATUS_OK;
+}
+
+krb5_error_code dsdb_extract_aes_256_key(krb5_context context,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ uint32_t user_account_control,
+ const uint32_t *kvno,
+ uint32_t *kvno_out,
+ DATA_BLOB *aes_256_key,
+ DATA_BLOB *salt)
+{
+ krb5_error_code krb5_ret;
+ uint32_t supported_enctypes;
+ unsigned flags = SDB_F_GET_CLIENT;
+ struct sdb_entry sentry = {};
+
+ if (kvno != NULL) {
+ flags |= SDB_F_KVNO_SPECIFIED;
+ }
+
+ krb5_ret = samba_kdc_message2entry_keys(context,
+ mem_ctx,
+ msg,
+ false, /* is_krbtgt */
+ false, /* is_rodc */
+ user_account_control,
+ SAMBA_KDC_ENT_TYPE_CLIENT,
+ flags,
+ (kvno != NULL) ? *kvno : 0,
+ &sentry,
+ ENC_HMAC_SHA1_96_AES256,
+ &supported_enctypes);
+ if (krb5_ret != 0) {
+ const char *krb5_err = krb5_get_error_message(context, krb5_ret);
+
+ DBG_ERR("Failed to parse supplementalCredentials "
+ "of %s with %s kvno using "
+ "ENCTYPE_HMAC_SHA1_96_AES256 "
+ "Kerberos Key: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ (kvno != NULL) ? "previous" : "current",
+ krb5_err != NULL ? krb5_err : "<unknown>");
+
+ krb5_free_error_message(context, krb5_err);
+
+ return krb5_ret;
+ }
+
+ if ((supported_enctypes & ENC_HMAC_SHA1_96_AES256) == 0 ||
+ sentry.keys.len != 1) {
+ DBG_INFO("Failed to find a ENCTYPE_HMAC_SHA1_96_AES256 "
+ "key in supplementalCredentials "
+ "of %s at KVNO %u (got %u keys, expected 1)\n",
+ ldb_dn_get_linearized(msg->dn),
+ sentry.kvno,
+ sentry.keys.len);
+ sdb_entry_free(&sentry);
+ return ENOENT;
+ }
+
+ if (sentry.keys.val[0].salt == NULL) {
+ DBG_INFO("Failed to find a salt in "
+ "supplementalCredentials "
+ "of %s at KVNO %u\n",
+ ldb_dn_get_linearized(msg->dn),
+ sentry.kvno);
+ sdb_entry_free(&sentry);
+ return ENOENT;
+ }
+
+ if (aes_256_key != NULL) {
+ *aes_256_key = data_blob_talloc(mem_ctx,
+ KRB5_KEY_DATA(&sentry.keys.val[0].key),
+ KRB5_KEY_LENGTH(&sentry.keys.val[0].key));
+ if (aes_256_key->data == NULL) {
+ sdb_entry_free(&sentry);
+ return ENOMEM;
+ }
+ talloc_keep_secret(aes_256_key->data);
+ }
+
+ if (salt != NULL) {
+ *salt = data_blob_talloc(mem_ctx,
+ sentry.keys.val[0].salt->salt.data,
+ sentry.keys.val[0].salt->salt.length);
+ if (salt->data == NULL) {
+ sdb_entry_free(&sentry);
+ return ENOMEM;
+ }
+ }
+
+ if (kvno_out != NULL) {
+ *kvno_out = sentry.kvno;
+ }
+
+ sdb_entry_free(&sentry);
+
+ return 0;
+}
diff --git a/source4/kdc/db-glue.h b/source4/kdc/db-glue.h
new file mode 100644
index 0000000..fb74726
--- /dev/null
+++ b/source4/kdc/db-glue.h
@@ -0,0 +1,113 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Database Glue between Samba and the KDC
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009
+ Copyright (C) Simo Sorce <idra@samba.org> 2010
+
+ 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/>.
+*/
+
+struct sdb_keys;
+struct sdb_entry;
+
+struct samba_kdc_base_context;
+struct samba_kdc_db_context;
+struct samba_kdc_entry;
+
+enum samba_kdc_ent_type {
+ SAMBA_KDC_ENT_TYPE_CLIENT,
+ SAMBA_KDC_ENT_TYPE_SERVER,
+ SAMBA_KDC_ENT_TYPE_KRBTGT,
+ SAMBA_KDC_ENT_TYPE_TRUST,
+ SAMBA_KDC_ENT_TYPE_ANY
+};
+
+/*
+ * This allows DSDB to parse Kerberos keys without duplicating this
+ * difficulty
+ */
+krb5_error_code samba_kdc_message2entry_keys(krb5_context context,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ bool is_krbtgt,
+ bool is_rodc,
+ uint32_t userAccountControl,
+ enum samba_kdc_ent_type ent_type,
+ unsigned flags,
+ krb5_kvno requested_kvno,
+ struct sdb_entry *entry,
+ const uint32_t supported_enctypes_in,
+ uint32_t *supported_enctypes_out);
+
+int samba_kdc_set_fixed_keys(krb5_context context,
+ const struct ldb_val *secretbuffer,
+ uint32_t supported_enctypes,
+ struct sdb_keys *keys);
+
+krb5_error_code samba_kdc_fetch(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ krb5_const_principal principal,
+ unsigned flags,
+ krb5_kvno kvno,
+ struct sdb_entry *entry);
+
+krb5_error_code samba_kdc_firstkey(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ struct sdb_entry *entry);
+
+krb5_error_code samba_kdc_nextkey(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ struct sdb_entry *entry);
+
+krb5_error_code
+samba_kdc_check_client_matches_target_service(krb5_context context,
+ struct samba_kdc_entry *skdc_entry_client,
+ struct samba_kdc_entry *skdc_entry_server_target);
+
+krb5_error_code
+samba_kdc_check_pkinit_ms_upn_match(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ struct samba_kdc_entry *skdc_entry,
+ krb5_const_principal certificate_principal);
+
+krb5_error_code
+samba_kdc_check_s4u2proxy(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ struct samba_kdc_entry *skdc_entry,
+ krb5_const_principal target_principal);
+
+krb5_error_code samba_kdc_check_s4u2proxy_rbcd(
+ krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ krb5_const_principal client_principal,
+ krb5_const_principal server_principal,
+ const struct auth_user_info_dc *user_info_dc,
+ const struct auth_user_info_dc *device_info_dc,
+ const struct auth_claims auth_claims,
+ struct samba_kdc_entry *proxy_skdc_entry);
+
+NTSTATUS samba_kdc_setup_db_ctx(TALLOC_CTX *mem_ctx, struct samba_kdc_base_context *base_ctx,
+ struct samba_kdc_db_context **kdc_db_ctx_out);
+
+krb5_error_code dsdb_extract_aes_256_key(krb5_context context,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ uint32_t user_account_control,
+ const uint32_t *kvno,
+ uint32_t *kvno_out,
+ DATA_BLOB *aes_256_key,
+ DATA_BLOB *salt);
diff --git a/source4/kdc/hdb-samba4-plugin.c b/source4/kdc/hdb-samba4-plugin.c
new file mode 100644
index 0000000..be6d243
--- /dev/null
+++ b/source4/kdc/hdb-samba4-plugin.c
@@ -0,0 +1,85 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ KDC Server startup
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-20011
+
+ 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 "includes.h"
+#include "kdc/kdc-glue.h"
+#include "lib/param/param.h"
+
+static krb5_error_code hdb_samba4_create(krb5_context context, struct HDB **db, const char *arg)
+{
+ NTSTATUS nt_status;
+ void *ptr = NULL;
+ struct samba_kdc_base_context *base_ctx = NULL;
+
+ if (sscanf(arg, "&%p", &ptr) != 1) {
+ return EINVAL;
+ }
+
+ base_ctx = talloc_get_type_abort(ptr, struct samba_kdc_base_context);
+
+ /* The global kdc_mem_ctx and kdc_lp_ctx, Disgusting, ugly hack, but it means one less private hook */
+ nt_status = hdb_samba4_kpasswd_create_kdc(base_ctx, context, db);
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ return 0;
+ } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ERROR_DS_INCOMPATIBLE_VERSION)) {
+ return EINVAL;
+ } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) {
+
+ krb5_set_error_message(context, EINVAL, "Failed to open Samba4 LDB at %s", lpcfg_private_path(base_ctx, base_ctx->lp_ctx, "sam.ldb"));
+ } else {
+ krb5_set_error_message(context, EINVAL, "Failed to connect to Samba4 DB: %s (%s)", get_friendly_nt_error_msg(nt_status), nt_errstr(nt_status));
+ }
+
+ return EINVAL;
+}
+
+#if (HDB_INTERFACE_VERSION != 11)
+#error "Unsupported Heimdal HDB version"
+#endif
+
+#if HDB_INTERFACE_VERSION >= 8
+static krb5_error_code hdb_samba4_init(krb5_context context, void **ctx)
+{
+ *ctx = NULL;
+ return 0;
+}
+
+static void hdb_samba4_fini(void *ctx)
+{
+}
+#endif
+
+/* Only used in the hdb-backed keytab code
+ * for a keytab of 'samba4&<address>' or samba4, to find
+ * kpasswd's key in the main DB
+ *
+ * The <address> is the string form of a pointer to a talloced struct hdb_samba_context
+ */
+struct hdb_method hdb_samba4_interface = {
+ HDB_INTERFACE_VERSION,
+#if HDB_INTERFACE_VERSION >= 8
+ .init = hdb_samba4_init,
+ .fini = hdb_samba4_fini,
+#endif
+ .prefix = "samba4",
+ .create = hdb_samba4_create
+};
diff --git a/source4/kdc/hdb-samba4.c b/source4/kdc/hdb-samba4.c
new file mode 100644
index 0000000..40161b5
--- /dev/null
+++ b/source4/kdc/hdb-samba4.c
@@ -0,0 +1,1267 @@
+/*
+ * Copyright (c) 1999-2001, 2003, PADL Software Pty Ltd.
+ * Copyright (c) 2004-2009, Andrew Bartlett <abartlet@samba.org>.
+ * Copyright (c) 2004, Stefan Metzmacher <metze@samba.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of PADL Software nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "includes.h"
+#include "kdc/kdc-glue.h"
+#include "kdc/db-glue.h"
+#include "kdc/pac-glue.h"
+#include "auth/auth_sam.h"
+#include "auth/common_auth.h"
+#include "auth/authn_policy.h"
+#include <ldb.h>
+#include "sdb.h"
+#include "sdb_hdb.h"
+#include "dsdb/samdb/samdb.h"
+#include "param/param.h"
+#include "../lib/tsocket/tsocket.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "lib/messaging/irpc.h"
+#include "hdb.h"
+#include <kdc-audit.h>
+#include <kdc-plugin.h>
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+static krb5_error_code hdb_samba4_open(krb5_context context, HDB *db, int flags, mode_t mode)
+{
+ if (db->hdb_master_key_set) {
+ krb5_error_code ret = HDB_ERR_NOENTRY;
+ krb5_warnx(context, "hdb_samba4_open: use of a master key incompatible with LDB\n");
+ krb5_set_error_message(context, ret, "hdb_samba4_open: use of a master key incompatible with LDB\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static krb5_error_code hdb_samba4_close(krb5_context context, HDB *db)
+{
+ return 0;
+}
+
+static krb5_error_code hdb_samba4_lock(krb5_context context, HDB *db, int operation)
+{
+ return 0;
+}
+
+static krb5_error_code hdb_samba4_unlock(krb5_context context, HDB *db)
+{
+ return 0;
+}
+
+static krb5_error_code hdb_samba4_rename(krb5_context context, HDB *db, const char *new_name)
+{
+ return HDB_ERR_DB_INUSE;
+}
+
+static krb5_error_code hdb_samba4_store(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
+{
+ return HDB_ERR_DB_INUSE;
+}
+
+/*
+ * If we ever want kadmin to work fast, we might try and reopen the
+ * ldb with LDB_NOSYNC
+ */
+static krb5_error_code hdb_samba4_set_sync(krb5_context context, struct HDB *db, int set_sync)
+{
+ return 0;
+}
+
+static void hdb_samba4_free_entry_context(krb5_context context, struct HDB *db, hdb_entry *entry)
+{
+ /*
+ * This function is now called for every HDB entry, not just those with
+ * 'context' set, so we have to check that the context is not NULL.
+ */
+ if (entry->context != NULL) {
+ struct samba_kdc_entry *skdc_entry =
+ talloc_get_type_abort(entry->context,
+ struct samba_kdc_entry);
+
+ /* this function is called only from hdb_free_entry().
+ * Make sure we neutralize the destructor or we will
+ * get a double free later when hdb_free_entry() will
+ * try to call free_hdb_entry() */
+ entry->context = NULL;
+ skdc_entry->kdc_entry = NULL;
+ TALLOC_FREE(skdc_entry);
+ }
+}
+
+static krb5_error_code hdb_samba4_fetch_fast_cookie(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ hdb_entry *entry)
+{
+ DBG_ERR("Looked up HDB entry for unsupported FX-COOKIE.\n");
+ return HDB_ERR_NOENTRY;
+}
+
+static krb5_error_code hdb_samba4_fetch_kvno(krb5_context context, HDB *db,
+ krb5_const_principal principal,
+ unsigned flags,
+ krb5_kvno kvno,
+ hdb_entry *entry)
+{
+ struct samba_kdc_db_context *kdc_db_ctx;
+ struct sdb_entry sentry = {};
+ krb5_error_code code, ret;
+ uint32_t sflags;
+
+ kdc_db_ctx = talloc_get_type_abort(db->hdb_db,
+ struct samba_kdc_db_context);
+
+ if (flags & HDB_F_GET_FAST_COOKIE) {
+ return hdb_samba4_fetch_fast_cookie(context,
+ kdc_db_ctx,
+ entry);
+ }
+
+ sflags = (flags & SDB_F_HDB_MASK);
+
+ ret = samba_kdc_fetch(context,
+ kdc_db_ctx,
+ principal,
+ sflags,
+ kvno,
+ &sentry);
+ switch (ret) {
+ case 0:
+ code = 0;
+ break;
+ case SDB_ERR_WRONG_REALM:
+ /*
+ * If SDB_ERR_WRONG_REALM is returned we need to process the
+ * sdb_entry to fill the principal in the HDB entry.
+ */
+ code = HDB_ERR_WRONG_REALM;
+ break;
+ case SDB_ERR_NOENTRY:
+ return HDB_ERR_NOENTRY;
+ case SDB_ERR_NOT_FOUND_HERE:
+ return HDB_ERR_NOT_FOUND_HERE;
+ default:
+ return ret;
+ }
+
+ ret = sdb_entry_to_hdb_entry(context, &sentry, entry);
+ sdb_entry_free(&sentry);
+
+ if (code == 0) {
+ code = ret;
+ }
+
+ return code;
+}
+
+static krb5_error_code hdb_samba4_kpasswd_fetch_kvno(krb5_context context, HDB *db,
+ krb5_const_principal _principal,
+ unsigned flags,
+ krb5_kvno _kvno,
+ hdb_entry *entry)
+{
+ struct samba_kdc_db_context *kdc_db_ctx = NULL;
+ krb5_error_code ret;
+ krb5_principal kpasswd_principal = NULL;
+
+ kdc_db_ctx = talloc_get_type_abort(db->hdb_db,
+ struct samba_kdc_db_context);
+
+ ret = smb_krb5_make_principal(context, &kpasswd_principal,
+ lpcfg_realm(kdc_db_ctx->lp_ctx),
+ "kadmin", "changepw",
+ NULL);
+ if (ret) {
+ return ret;
+ }
+ smb_krb5_principal_set_type(context, kpasswd_principal, KRB5_NT_SRV_INST);
+
+ /*
+ * For the kpasswd service, always ensure we get the latest kvno. This
+ * also means we (correctly) refuse RODC-issued tickets.
+ */
+ flags &= ~HDB_F_KVNO_SPECIFIED;
+
+ /* Don't bother looking up a client or krbtgt. */
+ flags &= ~(HDB_F_GET_CLIENT|HDB_F_GET_KRBTGT);
+
+ ret = hdb_samba4_fetch_kvno(context, db,
+ kpasswd_principal,
+ flags,
+ 0,
+ entry);
+
+ krb5_free_principal(context, kpasswd_principal);
+ return ret;
+}
+
+static krb5_error_code hdb_samba4_firstkey(krb5_context context, HDB *db, unsigned flags,
+ hdb_entry *entry)
+{
+ struct samba_kdc_db_context *kdc_db_ctx;
+ struct sdb_entry sentry = {};
+ krb5_error_code ret;
+
+ kdc_db_ctx = talloc_get_type_abort(db->hdb_db,
+ struct samba_kdc_db_context);
+
+ ret = samba_kdc_firstkey(context, kdc_db_ctx, &sentry);
+ switch (ret) {
+ case 0:
+ break;
+ case SDB_ERR_WRONG_REALM:
+ return HDB_ERR_WRONG_REALM;
+ case SDB_ERR_NOENTRY:
+ return HDB_ERR_NOENTRY;
+ case SDB_ERR_NOT_FOUND_HERE:
+ return HDB_ERR_NOT_FOUND_HERE;
+ default:
+ return ret;
+ }
+
+ ret = sdb_entry_to_hdb_entry(context, &sentry, entry);
+ sdb_entry_free(&sentry);
+ return ret;
+}
+
+static krb5_error_code hdb_samba4_nextkey(krb5_context context, HDB *db, unsigned flags,
+ hdb_entry *entry)
+{
+ struct samba_kdc_db_context *kdc_db_ctx;
+ struct sdb_entry sentry = {};
+ krb5_error_code ret;
+
+ kdc_db_ctx = talloc_get_type_abort(db->hdb_db,
+ struct samba_kdc_db_context);
+
+ ret = samba_kdc_nextkey(context, kdc_db_ctx, &sentry);
+ switch (ret) {
+ case 0:
+ break;
+ case SDB_ERR_WRONG_REALM:
+ return HDB_ERR_WRONG_REALM;
+ case SDB_ERR_NOENTRY:
+ return HDB_ERR_NOENTRY;
+ case SDB_ERR_NOT_FOUND_HERE:
+ return HDB_ERR_NOT_FOUND_HERE;
+ default:
+ return ret;
+ }
+
+ ret = sdb_entry_to_hdb_entry(context, &sentry, entry);
+ sdb_entry_free(&sentry);
+ return ret;
+}
+
+static krb5_error_code hdb_samba4_nextkey_panic(krb5_context context, HDB *db,
+ unsigned flags,
+ hdb_entry *entry)
+{
+ DBG_ERR("Attempt to iterate kpasswd keytab => PANIC\n");
+ smb_panic("hdb_samba4_nextkey_panic: Attempt to iterate kpasswd keytab");
+}
+
+static krb5_error_code hdb_samba4_destroy(krb5_context context, HDB *db)
+{
+ talloc_free(db);
+ return 0;
+}
+
+static krb5_error_code
+hdb_samba4_check_constrained_delegation(krb5_context context, HDB *db,
+ hdb_entry *entry,
+ krb5_const_principal target_principal)
+{
+ struct samba_kdc_db_context *kdc_db_ctx = NULL;
+ struct samba_kdc_entry *skdc_entry = NULL;
+
+ kdc_db_ctx = talloc_get_type_abort(db->hdb_db,
+ struct samba_kdc_db_context);
+ skdc_entry = talloc_get_type_abort(entry->context,
+ struct samba_kdc_entry);
+
+ return samba_kdc_check_s4u2proxy(context, kdc_db_ctx,
+ skdc_entry,
+ target_principal);
+}
+
+static krb5_error_code
+hdb_samba4_check_rbcd(krb5_context context, HDB *db,
+ const hdb_entry *client_krbtgt,
+ const hdb_entry *client,
+ const hdb_entry *device_krbtgt,
+ const hdb_entry *device,
+ krb5_const_principal server_principal,
+ krb5_const_pac header_pac,
+ krb5_const_pac device_pac,
+ const hdb_entry *proxy)
+{
+ struct samba_kdc_db_context *kdc_db_ctx = NULL;
+ struct samba_kdc_entry *client_skdc_entry = NULL;
+ const struct samba_kdc_entry *client_krbtgt_skdc_entry = NULL;
+ struct samba_kdc_entry *proxy_skdc_entry = NULL;
+ const struct auth_user_info_dc *client_info = NULL;
+ const struct auth_user_info_dc *device_info = NULL;
+ struct samba_kdc_entry_pac client_pac_entry = {};
+ struct auth_claims auth_claims = {};
+ TALLOC_CTX *mem_ctx = NULL;
+ krb5_error_code code;
+
+ kdc_db_ctx = talloc_get_type_abort(db->hdb_db,
+ struct samba_kdc_db_context);
+ client_skdc_entry = talloc_get_type_abort(client->context,
+ struct samba_kdc_entry);
+ client_krbtgt_skdc_entry = talloc_get_type_abort(client_krbtgt->context,
+ struct samba_kdc_entry);
+ proxy_skdc_entry = talloc_get_type_abort(proxy->context,
+ struct samba_kdc_entry);
+
+ mem_ctx = talloc_new(kdc_db_ctx);
+ if (mem_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ client_pac_entry = samba_kdc_entry_pac(header_pac,
+ client_skdc_entry,
+ samba_kdc_entry_is_trust(client_krbtgt_skdc_entry));
+
+ code = samba_kdc_get_user_info_dc(mem_ctx,
+ context,
+ kdc_db_ctx->samdb,
+ client_pac_entry,
+ &client_info,
+ NULL /* resource_groups_out */);
+ if (code != 0) {
+ goto out;
+ }
+
+ code = samba_kdc_get_claims_data(mem_ctx,
+ context,
+ kdc_db_ctx->samdb,
+ client_pac_entry,
+ &auth_claims.user_claims);
+ if (code) {
+ goto out;
+ }
+
+ if (device != NULL) {
+ struct samba_kdc_entry *device_skdc_entry = NULL;
+ const struct samba_kdc_entry *device_krbtgt_skdc_entry = NULL;
+ struct samba_kdc_entry_pac device_pac_entry = {};
+
+ device_skdc_entry = talloc_get_type_abort(device->context,
+ struct samba_kdc_entry);
+
+ if (device_krbtgt != NULL) {
+ device_krbtgt_skdc_entry = talloc_get_type_abort(device_krbtgt->context,
+ struct samba_kdc_entry);
+ }
+
+ device_pac_entry = samba_kdc_entry_pac(device_pac,
+ device_skdc_entry,
+ samba_kdc_entry_is_trust(device_krbtgt_skdc_entry));
+
+ code = samba_kdc_get_user_info_dc(mem_ctx,
+ context,
+ kdc_db_ctx->samdb,
+ device_pac_entry,
+ &device_info,
+ NULL /* resource_groups_out */);
+ if (code) {
+ goto out;
+ }
+
+ code = samba_kdc_get_claims_data(mem_ctx,
+ context,
+ kdc_db_ctx->samdb,
+ device_pac_entry,
+ &auth_claims.device_claims);
+ if (code) {
+ goto out;
+ }
+ }
+
+ code = samba_kdc_check_s4u2proxy_rbcd(context,
+ kdc_db_ctx,
+ client->principal,
+ server_principal,
+ client_info,
+ device_info,
+ auth_claims,
+ proxy_skdc_entry);
+out:
+ talloc_free(mem_ctx);
+ return code;
+}
+
+static krb5_error_code
+hdb_samba4_check_pkinit_ms_upn_match(krb5_context context, HDB *db,
+ hdb_entry *entry,
+ krb5_const_principal certificate_principal)
+{
+ struct samba_kdc_db_context *kdc_db_ctx;
+ struct samba_kdc_entry *skdc_entry;
+ krb5_error_code ret;
+
+ kdc_db_ctx = talloc_get_type_abort(db->hdb_db,
+ struct samba_kdc_db_context);
+ skdc_entry = talloc_get_type_abort(entry->context,
+ struct samba_kdc_entry);
+
+ ret = samba_kdc_check_pkinit_ms_upn_match(context, kdc_db_ctx,
+ skdc_entry,
+ certificate_principal);
+ switch (ret) {
+ case 0:
+ break;
+ case SDB_ERR_WRONG_REALM:
+ ret = HDB_ERR_WRONG_REALM;
+ break;
+ case SDB_ERR_NOENTRY:
+ ret = HDB_ERR_NOENTRY;
+ break;
+ case SDB_ERR_NOT_FOUND_HERE:
+ ret = HDB_ERR_NOT_FOUND_HERE;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static krb5_error_code
+hdb_samba4_check_client_matches_target_service(krb5_context context, HDB *db,
+ hdb_entry *client_entry,
+ hdb_entry *server_target_entry)
+{
+ struct samba_kdc_entry *skdc_client_entry
+ = talloc_get_type_abort(client_entry->context,
+ struct samba_kdc_entry);
+ struct samba_kdc_entry *skdc_server_target_entry
+ = talloc_get_type_abort(server_target_entry->context,
+ struct samba_kdc_entry);
+
+ return samba_kdc_check_client_matches_target_service(context,
+ skdc_client_entry,
+ skdc_server_target_entry);
+}
+
+static void reset_bad_password_netlogon(TALLOC_CTX *mem_ctx,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ struct netr_SendToSamBase *send_to_sam)
+{
+ struct dcerpc_binding_handle *irpc_handle;
+ struct winbind_SendToSam req;
+ struct tevent_req *subreq = NULL;
+
+ irpc_handle = irpc_binding_handle_by_name(mem_ctx, kdc_db_ctx->msg_ctx,
+ "winbind_server",
+ &ndr_table_winbind);
+
+ if (irpc_handle == NULL) {
+ DBG_ERR("No winbind_server running!\n");
+ return;
+ }
+
+ req.in.message = *send_to_sam;
+
+ /*
+ * This seem to rely on the current IRPC implementation,
+ * which delivers the message in the _send function.
+ *
+ * TODO: we need a ONE_WAY IRPC handle and register
+ * a callback and wait for it to be triggered!
+ */
+ subreq = dcerpc_winbind_SendToSam_r_send(mem_ctx, kdc_db_ctx->ev_ctx,
+ irpc_handle, &req);
+
+ /* we aren't interested in a reply */
+ TALLOC_FREE(subreq);
+}
+
+#define SAMBA_HDB_AUTHN_AUDIT_INFO_OBJ "samba:authn_audit_info_obj"
+#define SAMBA_HDB_CLIENT_AUDIT_INFO "samba:client_audit_info"
+#define SAMBA_HDB_SERVER_AUDIT_INFO "samba:server_audit_info"
+
+#define SAMBA_HDB_NT_STATUS_OBJ "samba:nt_status_obj"
+#define SAMBA_HDB_NT_STATUS "samba:nt_status"
+
+struct hdb_audit_info_obj {
+ struct authn_audit_info *audit_info;
+};
+
+static void hdb_audit_info_obj_dealloc(void *ptr)
+{
+ struct hdb_audit_info_obj *audit_info_obj = ptr;
+
+ if (audit_info_obj == NULL) {
+ return;
+ }
+
+ TALLOC_FREE(audit_info_obj->audit_info);
+}
+
+/*
+ * Set talloc-allocated auditing information of the KDC request. On success,
+ * ‘audit_info’ is invalidated and may no longer be used by the caller.
+ */
+static krb5_error_code hdb_samba4_set_steal_audit_info(astgs_request_t r,
+ const char *key,
+ struct authn_audit_info *audit_info)
+{
+ struct hdb_audit_info_obj *audit_info_obj = NULL;
+
+ audit_info_obj = kdc_object_alloc(sizeof (*audit_info_obj),
+ SAMBA_HDB_AUTHN_AUDIT_INFO_OBJ,
+ hdb_audit_info_obj_dealloc);
+ if (audit_info_obj == NULL) {
+ return ENOMEM;
+ }
+
+ /*
+ * Steal a handle to the audit information onto the NULL context —
+ * Heimdal will be responsible for the deallocation of the object.
+ */
+ audit_info_obj->audit_info = talloc_steal(NULL, audit_info);
+
+ heim_audit_setkv_object((heim_svc_req_desc)r, key, audit_info_obj);
+ heim_release(audit_info_obj);
+
+ return 0;
+}
+
+/*
+ * Set talloc-allocated client auditing information of the KDC request. On
+ * success, ‘client_audit_info’ is invalidated and may no longer be used by the
+ * caller.
+ */
+krb5_error_code hdb_samba4_set_steal_client_audit_info(astgs_request_t r,
+ struct authn_audit_info *client_audit_info)
+{
+ return hdb_samba4_set_steal_audit_info(r,
+ SAMBA_HDB_CLIENT_AUDIT_INFO,
+ client_audit_info);
+}
+
+static const struct authn_audit_info *hdb_samba4_get_client_audit_info(hdb_request_t r)
+{
+ const struct hdb_audit_info_obj *audit_info_obj = NULL;
+
+ audit_info_obj = heim_audit_getkv((heim_svc_req_desc)r, SAMBA_HDB_CLIENT_AUDIT_INFO);
+ if (audit_info_obj == NULL) {
+ return NULL;
+ }
+
+ return audit_info_obj->audit_info;
+}
+
+/*
+ * Set talloc-allocated server auditing information of the KDC request. On
+ * success, ‘server_audit_info’ is invalidated and may no longer be used by the
+ * caller.
+ */
+krb5_error_code hdb_samba4_set_steal_server_audit_info(astgs_request_t r,
+ struct authn_audit_info *server_audit_info)
+{
+ return hdb_samba4_set_steal_audit_info(r,
+ SAMBA_HDB_SERVER_AUDIT_INFO,
+ server_audit_info);
+}
+
+static const struct authn_audit_info *hdb_samba4_get_server_audit_info(hdb_request_t r)
+{
+ const struct hdb_audit_info_obj *audit_info_obj = NULL;
+
+ audit_info_obj = heim_audit_getkv((heim_svc_req_desc)r, SAMBA_HDB_SERVER_AUDIT_INFO);
+ if (audit_info_obj == NULL) {
+ return NULL;
+ }
+
+ return audit_info_obj->audit_info;
+}
+
+struct hdb_ntstatus_obj {
+ NTSTATUS status;
+ krb5_error_code current_error;
+};
+
+/*
+ * Add an NTSTATUS code to a Kerberos request. ‘error’ is the error value we
+ * want to return to the client. When it comes time to generating the error
+ * request, we shall compare this error value to whatever error we are about to
+ * return; if the two match, we shall replace the ‘e-data’ field in the reply
+ * with the NTSTATUS code.
+ */
+krb5_error_code hdb_samba4_set_ntstatus(astgs_request_t r,
+ const NTSTATUS status,
+ const krb5_error_code error)
+{
+ struct hdb_ntstatus_obj *status_obj = NULL;
+
+ status_obj = kdc_object_alloc(sizeof (*status_obj),
+ SAMBA_HDB_NT_STATUS_OBJ,
+ NULL);
+ if (status_obj == NULL) {
+ return ENOMEM;
+ }
+
+ *status_obj = (struct hdb_ntstatus_obj) {
+ .status = status,
+ .current_error = error,
+ };
+
+ heim_audit_setkv_object((heim_svc_req_desc)r, SAMBA_HDB_NT_STATUS, status_obj);
+ heim_release(status_obj);
+
+ return 0;
+}
+
+static krb5_error_code hdb_samba4_make_nt_status_edata(const NTSTATUS status,
+ const uint32_t flags,
+ krb5_data *edata_out)
+{
+ const uint32_t status_code = NT_STATUS_V(status);
+ const uint32_t zero = 0;
+ KERB_ERROR_DATA error_data;
+ krb5_data e_data;
+
+ krb5_error_code ret;
+ size_t size;
+
+ /* The raw KERB-ERR-TYPE-EXTENDED structure. */
+ uint8_t data[12];
+
+ PUSH_LE_U32(data, 0, status_code);
+ PUSH_LE_U32(data, 4, zero);
+ PUSH_LE_U32(data, 8, flags);
+
+ e_data = (krb5_data) {
+ .data = &data,
+ .length = sizeof(data),
+ };
+
+ error_data = (KERB_ERROR_DATA) {
+ .data_type = kERB_ERR_TYPE_EXTENDED,
+ .data_value = &e_data,
+ };
+
+ ASN1_MALLOC_ENCODE(KERB_ERROR_DATA,
+ edata_out->data, edata_out->length,
+ &error_data,
+ &size, ret);
+ if (ret) {
+ return ret;
+ }
+ if (size != edata_out->length) {
+ /* Internal ASN.1 encoder error */
+ krb5_data_free(edata_out);
+ return KRB5KRB_ERR_GENERIC;
+ }
+
+ return 0;
+}
+
+static krb5_error_code hdb_samba4_set_edata_from_ntstatus(hdb_request_t r, const NTSTATUS status)
+{
+ const KDC_REQ *req = kdc_request_get_req((astgs_request_t)r);
+ krb5_error_code ret = 0;
+ krb5_data e_data;
+ uint32_t flags = 1;
+
+ if (req->msg_type == krb_tgs_req) {
+ /* This flag is used to indicate a TGS-REQ. */
+ flags |= 2;
+ }
+
+ ret = hdb_samba4_make_nt_status_edata(status, flags, &e_data);
+ if (ret) {
+ return ret;
+ }
+
+ ret = kdc_request_set_e_data((astgs_request_t)r, e_data);
+ if (ret) {
+ krb5_data_free(&e_data);
+ }
+
+ return ret;
+}
+
+static NTSTATUS hdb_samba4_get_ntstatus(hdb_request_t r)
+{
+ struct hdb_ntstatus_obj *status_obj = NULL;
+
+ status_obj = heim_audit_getkv((heim_svc_req_desc)r, SAMBA_HDB_NT_STATUS);
+ if (status_obj == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ if (r->error_code != status_obj->current_error) {
+ /*
+ * The error code has changed from what we expect. Consider the
+ * NTSTATUS to be invalidated.
+ */
+ return NT_STATUS_OK;
+ }
+
+ return status_obj->status;
+}
+
+static krb5_error_code hdb_samba4_tgs_audit(const struct samba_kdc_db_context *kdc_db_ctx,
+ const hdb_entry *entry,
+ hdb_request_t r)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct authn_audit_info *server_audit_info = NULL;
+ struct tsocket_address *remote_host = NULL;
+ struct samba_kdc_entry *client_entry = NULL;
+ struct dom_sid sid_buf = {};
+ const char *account_name = NULL;
+ const char *domain_name = NULL;
+ const struct dom_sid *sid = NULL;
+ size_t sa_socklen = 0;
+ NTSTATUS auth_status = NT_STATUS_OK;
+ krb5_error_code ret = 0;
+ krb5_error_code final_ret = 0;
+
+ /* Have we got a status code indicating an error? */
+ auth_status = hdb_samba4_get_ntstatus(r);
+ if (!NT_STATUS_IS_OK(auth_status)) {
+ /*
+ * Include this status code in the ‘e-data’ field of the reply.
+ */
+ ret = hdb_samba4_set_edata_from_ntstatus(r, auth_status);
+ if (ret) {
+ final_ret = ret;
+ }
+ } else if (entry == NULL) {
+ auth_status = NT_STATUS_NO_SUCH_USER;
+ } else if (r->error_code) {
+ /*
+ * Don’t include a status code in the reply. Just log the
+ * request as being unsuccessful.
+ */
+ auth_status = NT_STATUS_UNSUCCESSFUL;
+ }
+
+ switch (r->addr->sa_family) {
+ case AF_INET:
+ sa_socklen = sizeof(struct sockaddr_in);
+ break;
+#ifdef HAVE_IPV6
+ case AF_INET6:
+ sa_socklen = sizeof(struct sockaddr_in6);
+ break;
+#endif
+ }
+
+ ret = tsocket_address_bsd_from_sockaddr(frame, r->addr,
+ sa_socklen,
+ &remote_host);
+ if (ret != 0) {
+ remote_host = NULL;
+ /* Ignore the error. */
+ }
+
+ server_audit_info = hdb_samba4_get_server_audit_info(r);
+
+ if (entry != NULL) {
+ client_entry = talloc_get_type_abort(entry->context,
+ struct samba_kdc_entry);
+
+ ret = samdb_result_dom_sid_buf(client_entry->msg, "objectSid", &sid_buf);
+ if (ret) {
+ /* Ignore the error. */
+ } else {
+ sid = &sid_buf;
+ }
+
+ account_name = ldb_msg_find_attr_as_string(client_entry->msg, "sAMAccountName", NULL);
+ domain_name = lpcfg_sam_name(kdc_db_ctx->lp_ctx);
+ }
+
+ log_authz_event(kdc_db_ctx->msg_ctx,
+ kdc_db_ctx->lp_ctx,
+ remote_host,
+ NULL /* local */,
+ server_audit_info,
+ r->sname,
+ "TGS-REQ with Ticket-Granting Ticket",
+ domain_name,
+ account_name,
+ sid,
+ lpcfg_netbios_name(kdc_db_ctx->lp_ctx),
+ krb5_kdc_get_time(),
+ auth_status);
+
+ talloc_free(frame);
+ if (final_ret) {
+ r->error_code = final_ret;
+ }
+ return final_ret;
+}
+
+static krb5_error_code hdb_samba4_audit(krb5_context context,
+ HDB *db,
+ hdb_entry *entry,
+ hdb_request_t r)
+{
+ struct samba_kdc_db_context *kdc_db_ctx = talloc_get_type_abort(db->hdb_db,
+ struct samba_kdc_db_context);
+ struct ldb_dn *domain_dn = ldb_get_default_basedn(kdc_db_ctx->samdb);
+ heim_object_t auth_details_obj = NULL;
+ const char *auth_details = NULL;
+ char *etype_str = NULL;
+ heim_object_t hdb_auth_status_obj = NULL;
+ int hdb_auth_status;
+ heim_object_t pa_type_obj = NULL;
+ const char *pa_type = NULL;
+ struct auth_usersupplied_info ui;
+ size_t sa_socklen = 0;
+ const KDC_REQ *req = kdc_request_get_req((astgs_request_t)r);
+ krb5_error_code final_ret = 0;
+ NTSTATUS edata_status;
+
+ if (req->msg_type == krb_tgs_req) {
+ return hdb_samba4_tgs_audit(kdc_db_ctx, entry, r);
+ }
+
+ if (r->error_code == KRB5KDC_ERR_PREAUTH_REQUIRED) {
+ /* Let’s not log PREAUTH_REQUIRED errors. */
+ return 0;
+ }
+
+ edata_status = hdb_samba4_get_ntstatus(r);
+
+ hdb_auth_status_obj = heim_audit_getkv((heim_svc_req_desc)r, KDC_REQUEST_KV_AUTH_EVENT);
+ if (hdb_auth_status_obj == NULL) {
+ /* No status code found, so just return. */
+ return 0;
+ }
+
+ hdb_auth_status = heim_number_get_int(hdb_auth_status_obj);
+
+ pa_type_obj = heim_audit_getkv((heim_svc_req_desc)r, KDC_REQUEST_KV_PA_NAME);
+ if (pa_type_obj != NULL) {
+ pa_type = heim_string_get_utf8(pa_type_obj);
+ }
+
+ auth_details_obj = heim_audit_getkv((heim_svc_req_desc)r, KDC_REQUEST_KV_PKINIT_CLIENT_CERT);
+ if (auth_details_obj != NULL) {
+ auth_details = heim_string_get_utf8(auth_details_obj);
+ } else {
+ auth_details_obj = heim_audit_getkv((heim_svc_req_desc)r, KDC_REQUEST_KV_GSS_INITIATOR);
+ if (auth_details_obj != NULL) {
+ auth_details = heim_string_get_utf8(auth_details_obj);
+ } else {
+ heim_object_t etype_obj = heim_audit_getkv((heim_svc_req_desc)r, KDC_REQUEST_KV_PA_ETYPE);
+ if (etype_obj != NULL) {
+ int etype = heim_number_get_int(etype_obj);
+
+ krb5_error_code ret = krb5_enctype_to_string(r->context, etype, &etype_str);
+ if (ret == 0) {
+ auth_details = etype_str;
+ } else {
+ auth_details = "unknown enctype";
+ }
+ }
+ }
+ }
+
+ /*
+ * Forcing this via the NTLM auth structure is not ideal, but
+ * it is the most practical option right now, and ensures the
+ * logs are consistent, even if some elements are always NULL.
+ */
+ ui = (struct auth_usersupplied_info) {
+ .was_mapped = true,
+ .client = {
+ .account_name = r->cname,
+ .domain_name = NULL,
+ },
+ .service_description = "Kerberos KDC",
+ .auth_description = "Unknown Auth Description",
+ .password_type = auth_details,
+ .logon_id = generate_random_u64(),
+ };
+
+ switch (r->addr->sa_family) {
+ case AF_INET:
+ sa_socklen = sizeof(struct sockaddr_in);
+ break;
+#ifdef HAVE_IPV6
+ case AF_INET6:
+ sa_socklen = sizeof(struct sockaddr_in6);
+ break;
+#endif
+ }
+
+ switch (hdb_auth_status) {
+ default:
+ {
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samba_kdc_entry *p = talloc_get_type_abort(entry->context,
+ struct samba_kdc_entry);
+ struct dom_sid *sid
+ = samdb_result_dom_sid(frame, p->msg, "objectSid");
+ const char *account_name
+ = ldb_msg_find_attr_as_string(p->msg, "sAMAccountName", NULL);
+ const char *domain_name = lpcfg_sam_name(p->kdc_db_ctx->lp_ctx);
+ struct tsocket_address *remote_host;
+ const char *auth_description = NULL;
+ const struct authn_audit_info *client_audit_info = NULL;
+ const struct authn_audit_info *server_audit_info = NULL;
+ NTSTATUS status;
+ int ret;
+ bool rwdc_fallback = false;
+
+ ret = tsocket_address_bsd_from_sockaddr(frame, r->addr,
+ sa_socklen,
+ &remote_host);
+ if (ret != 0) {
+ ui.remote_host = NULL;
+ } else {
+ ui.remote_host = remote_host;
+ }
+
+ ui.mapped.account_name = account_name;
+ ui.mapped.domain_name = domain_name;
+
+ if (pa_type != NULL) {
+ auth_description = talloc_asprintf(frame,
+ "%s Pre-authentication",
+ pa_type);
+ if (auth_description == NULL) {
+ auth_description = pa_type;
+ }
+ } else {
+ auth_description = "Unknown Pre-authentication";
+ }
+ ui.auth_description = auth_description;
+
+ if (hdb_auth_status == KDC_AUTH_EVENT_CLIENT_AUTHORIZED) {
+ struct netr_SendToSamBase *send_to_sam = NULL;
+
+ /*
+ * TODO: We could log the AS-REQ authorization success here as
+ * well. However before we do that, we need to pass
+ * in the PAC here or re-calculate it.
+ */
+ status = authsam_logon_success_accounting(kdc_db_ctx->samdb, p->msg,
+ domain_dn, true, frame, &send_to_sam);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)) {
+ edata_status = status;
+
+ r->error_code = final_ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ rwdc_fallback = kdc_db_ctx->rodc;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ r->error_code = final_ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ rwdc_fallback = kdc_db_ctx->rodc;
+ } else {
+ if (r->error_code == KRB5KDC_ERR_NEVER_VALID) {
+ edata_status = status = NT_STATUS_TIME_DIFFERENCE_AT_DC;
+ } else {
+ status = krb5_to_nt_status(r->error_code);
+ }
+
+ if (kdc_db_ctx->rodc && send_to_sam != NULL) {
+ reset_bad_password_netlogon(frame, kdc_db_ctx, send_to_sam);
+ }
+ }
+
+ /* This is the final success */
+ } else if (hdb_auth_status == KDC_AUTH_EVENT_VALIDATED_LONG_TERM_KEY) {
+ /*
+ * This was only a pre-authentication success,
+ * but we didn't reach the final
+ * KDC_AUTH_EVENT_CLIENT_AUTHORIZED,
+ * so consult the error code.
+ */
+ if (r->error_code == 0) {
+ DBG_ERR("ERROR: VALIDATED_LONG_TERM_KEY "
+ "with error=0 => INTERNAL_ERROR\n");
+ status = NT_STATUS_INTERNAL_ERROR;
+ r->error_code = final_ret = KRB5KRB_ERR_GENERIC;
+ } else if (!NT_STATUS_IS_OK(p->reject_status)) {
+ status = p->reject_status;
+ } else {
+ status = krb5_to_nt_status(r->error_code);
+ }
+ } else if (hdb_auth_status == KDC_AUTH_EVENT_PREAUTH_SUCCEEDED) {
+ /*
+ * This was only a pre-authentication success,
+ * but we didn't reach the final
+ * KDC_AUTH_EVENT_CLIENT_AUTHORIZED,
+ * so consult the error code.
+ */
+ if (r->error_code == 0) {
+ DBG_ERR("ERROR: PREAUTH_SUCCEEDED "
+ "with error=0 => INTERNAL_ERROR\n");
+ status = NT_STATUS_INTERNAL_ERROR;
+ r->error_code = final_ret = KRB5KRB_ERR_GENERIC;
+ } else if (!NT_STATUS_IS_OK(p->reject_status)) {
+ status = p->reject_status;
+ } else {
+ status = krb5_to_nt_status(r->error_code);
+ }
+ } else if (hdb_auth_status == KDC_AUTH_EVENT_CLIENT_FOUND) {
+ /*
+ * We found the client principal,
+ * but we didn’t reach the final
+ * KDC_AUTH_EVENT_CLIENT_AUTHORIZED,
+ * so consult the error code.
+ */
+ if (r->error_code == 0) {
+ DBG_ERR("ERROR: CLIENT_FOUND "
+ "with error=0 => INTERNAL_ERROR\n");
+ status = NT_STATUS_INTERNAL_ERROR;
+ r->error_code = final_ret = KRB5KRB_ERR_GENERIC;
+ } else if (!NT_STATUS_IS_OK(p->reject_status)) {
+ status = p->reject_status;
+ } else {
+ status = krb5_to_nt_status(r->error_code);
+ }
+ } else if (hdb_auth_status == KDC_AUTH_EVENT_CLIENT_TIME_SKEW) {
+ status = NT_STATUS_TIME_DIFFERENCE_AT_DC;
+ } else if (hdb_auth_status == KDC_AUTH_EVENT_WRONG_LONG_TERM_KEY) {
+ status = authsam_update_bad_pwd_count(kdc_db_ctx->samdb, p->msg, domain_dn);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)) {
+ edata_status = status;
+
+ r->error_code = final_ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ } else {
+ status = NT_STATUS_WRONG_PASSWORD;
+ }
+ rwdc_fallback = kdc_db_ctx->rodc;
+ } else if (hdb_auth_status == KDC_AUTH_EVENT_HISTORIC_LONG_TERM_KEY) {
+ /*
+ * The pre-authentication succeeds with a password
+ * from the password history, so we don't
+ * update the badPwdCount, but still return
+ * PREAUTH_FAILED and need to forward to
+ * a RWDC in order to produce an authoritative
+ * response for the client.
+ */
+ status = NT_STATUS_WRONG_PASSWORD;
+ rwdc_fallback = kdc_db_ctx->rodc;
+ } else if (hdb_auth_status == KDC_AUTH_EVENT_CLIENT_LOCKED_OUT) {
+ edata_status = status = NT_STATUS_ACCOUNT_LOCKED_OUT;
+ rwdc_fallback = kdc_db_ctx->rodc;
+ } else if (hdb_auth_status == KDC_AUTH_EVENT_CLIENT_NAME_UNAUTHORIZED) {
+ if (pa_type != NULL && strncmp(pa_type, "PK-INIT", strlen("PK-INIT")) == 0) {
+ status = NT_STATUS_PKINIT_NAME_MISMATCH;
+ } else {
+ status = NT_STATUS_ACCOUNT_RESTRICTION;
+ }
+ rwdc_fallback = kdc_db_ctx->rodc;
+ } else if (hdb_auth_status == KDC_AUTH_EVENT_PREAUTH_FAILED) {
+ if (pa_type != NULL && strncmp(pa_type, "PK-INIT", strlen("PK-INIT")) == 0) {
+ status = NT_STATUS_PKINIT_FAILURE;
+ } else {
+ status = NT_STATUS_GENERIC_COMMAND_FAILED;
+ }
+ rwdc_fallback = kdc_db_ctx->rodc;
+ } else {
+ DBG_ERR("Unhandled hdb_auth_status=%d => INTERNAL_ERROR\n",
+ hdb_auth_status);
+ status = NT_STATUS_INTERNAL_ERROR;
+ r->error_code = final_ret = KRB5KRB_ERR_GENERIC;
+ }
+
+ if (!NT_STATUS_IS_OK(edata_status)) {
+ krb5_error_code code;
+
+ code = hdb_samba4_set_edata_from_ntstatus(r, edata_status);
+ if (code) {
+ r->error_code = final_ret = code;
+ }
+ }
+
+ if (rwdc_fallback) {
+ /*
+ * Forward the request to an RWDC in order
+ * to give an authoritative answer to the client.
+ */
+ auth_description = talloc_asprintf(frame,
+ "%s,Forward-To-RWDC",
+ ui.auth_description);
+ if (auth_description != NULL) {
+ ui.auth_description = auth_description;
+ }
+ final_ret = HDB_ERR_NOT_FOUND_HERE;
+ }
+
+ client_audit_info = hdb_samba4_get_client_audit_info(r);
+ server_audit_info = hdb_samba4_get_server_audit_info(r);
+
+ log_authentication_event(kdc_db_ctx->msg_ctx,
+ kdc_db_ctx->lp_ctx,
+ &r->tv_start,
+ &ui,
+ status,
+ domain_name,
+ account_name,
+ sid,
+ client_audit_info,
+ server_audit_info);
+ if (final_ret == KRB5KRB_ERR_GENERIC && socket_wrapper_enabled()) {
+ /*
+ * If we're running under make test
+ * just panic
+ */
+ DBG_ERR("Unexpected situation => PANIC\n");
+ smb_panic("hdb_samba4_audit: Unexpected situation");
+ }
+ TALLOC_FREE(frame);
+ break;
+ }
+ case KDC_AUTH_EVENT_CLIENT_UNKNOWN:
+ {
+ struct tsocket_address *remote_host;
+ int ret;
+ TALLOC_CTX *frame = talloc_stackframe();
+ ret = tsocket_address_bsd_from_sockaddr(frame, r->addr,
+ sa_socklen,
+ &remote_host);
+ if (ret != 0) {
+ ui.remote_host = NULL;
+ } else {
+ ui.remote_host = remote_host;
+ }
+
+ if (pa_type == NULL) {
+ pa_type = "AS-REQ";
+ }
+
+ ui.auth_description = pa_type;
+
+ /* Note this is not forwarded to an RWDC */
+
+ log_authentication_event(kdc_db_ctx->msg_ctx,
+ kdc_db_ctx->lp_ctx,
+ &r->tv_start,
+ &ui,
+ NT_STATUS_NO_SUCH_USER,
+ NULL, NULL,
+ NULL,
+ NULL /* client_audit_info */,
+ NULL /* server_audit_info */);
+ TALLOC_FREE(frame);
+ break;
+ }
+ }
+
+ free(etype_str);
+
+ return final_ret;
+}
+
+/* This interface is to be called by the KDC and libnet_keytab_dump,
+ * which is expecting Samba calling conventions.
+ * It is also called by a wrapper (hdb_samba4_create) from the
+ * kpasswdd -> krb5 -> keytab_hdb -> hdb code */
+
+NTSTATUS hdb_samba4_create_kdc(struct samba_kdc_base_context *base_ctx,
+ krb5_context context, struct HDB **db)
+{
+ struct samba_kdc_db_context *kdc_db_ctx = NULL;
+ NTSTATUS nt_status;
+
+ if (hdb_interface_version != HDB_INTERFACE_VERSION) {
+ krb5_set_error_message(context, EINVAL, "Heimdal HDB interface version mismatch between build-time and run-time libraries!");
+ return NT_STATUS_ERROR_DS_INCOMPATIBLE_VERSION;
+ }
+
+ *db = talloc_zero(base_ctx, HDB);
+ if (!*db) {
+ krb5_set_error_message(context, ENOMEM, "talloc_zero: out of memory");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ (*db)->hdb_master_key_set = 0;
+ (*db)->hdb_db = NULL;
+ (*db)->hdb_capability_flags = HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL;
+
+ nt_status = samba_kdc_setup_db_ctx(*db, base_ctx, &kdc_db_ctx);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(*db);
+ return nt_status;
+ }
+ (*db)->hdb_db = kdc_db_ctx;
+
+ (*db)->hdb_dbc = NULL;
+ (*db)->hdb_open = hdb_samba4_open;
+ (*db)->hdb_close = hdb_samba4_close;
+ (*db)->hdb_free_entry_context = hdb_samba4_free_entry_context;
+ (*db)->hdb_fetch_kvno = hdb_samba4_fetch_kvno;
+ (*db)->hdb_store = hdb_samba4_store;
+ (*db)->hdb_firstkey = hdb_samba4_firstkey;
+ (*db)->hdb_nextkey = hdb_samba4_nextkey;
+ (*db)->hdb_lock = hdb_samba4_lock;
+ (*db)->hdb_unlock = hdb_samba4_unlock;
+ (*db)->hdb_set_sync = hdb_samba4_set_sync;
+ (*db)->hdb_rename = hdb_samba4_rename;
+ /* we don't implement these, as we are not a lockable database */
+ (*db)->hdb__get = NULL;
+ (*db)->hdb__put = NULL;
+ /* kadmin should not be used for deletes - use other tools instead */
+ (*db)->hdb__del = NULL;
+ (*db)->hdb_destroy = hdb_samba4_destroy;
+
+ (*db)->hdb_audit = hdb_samba4_audit;
+ (*db)->hdb_check_constrained_delegation = hdb_samba4_check_constrained_delegation;
+ (*db)->hdb_check_rbcd = hdb_samba4_check_rbcd;
+ (*db)->hdb_check_pkinit_ms_upn_match = hdb_samba4_check_pkinit_ms_upn_match;
+ (*db)->hdb_check_client_matches_target_service = hdb_samba4_check_client_matches_target_service;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS hdb_samba4_kpasswd_create_kdc(struct samba_kdc_base_context *base_ctx,
+ krb5_context context, struct HDB **db)
+{
+ NTSTATUS nt_status;
+
+ nt_status = hdb_samba4_create_kdc(base_ctx, context, db);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ (*db)->hdb_fetch_kvno = hdb_samba4_kpasswd_fetch_kvno;
+ (*db)->hdb_firstkey = hdb_samba4_nextkey_panic;
+ (*db)->hdb_nextkey = hdb_samba4_nextkey_panic;
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/kdc/kdc-glue.c b/source4/kdc/kdc-glue.c
new file mode 100644
index 0000000..8b98d0f
--- /dev/null
+++ b/source4/kdc/kdc-glue.c
@@ -0,0 +1,92 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ PAC Glue between Samba and the KDC
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009
+ Copyright (C) Simo Sorce <idra@samba.org> 2010
+
+ 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 "includes.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include <hdb.h>
+#include "kdc/samba_kdc.h"
+#include "kdc/pac-glue.h"
+#include "librpc/gen_ndr/ndr_krb5pac.h"
+#include "auth/kerberos/pac_utils.h"
+#include "kdc/kdc-glue.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+int kdc_check_pac(krb5_context context,
+ DATA_BLOB srv_sig,
+ struct PAC_SIGNATURE_DATA *kdc_sig,
+ hdb_entry *ent)
+{
+ krb5_enctype etype;
+ int ret;
+ krb5_keyblock keyblock;
+ Key *key;
+
+ if (kdc_sig->type == CKSUMTYPE_HMAC_MD5) {
+ etype = ENCTYPE_ARCFOUR_HMAC;
+ } else {
+ ret = krb5_cksumtype_to_enctype(context,
+ kdc_sig->type,
+ &etype);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ ret = hdb_enctype2key(context, ent, NULL, etype, &key);
+
+ if (ret != 0) {
+ return ret;
+ }
+
+ keyblock = key->key;
+
+ return check_pac_checksum(srv_sig, kdc_sig,
+ context, &keyblock);
+}
+
+struct samba_kdc_entry_pac samba_kdc_get_device_pac(const astgs_request_t r)
+{
+ const hdb_entry *device = kdc_request_get_armor_client(r);
+ struct samba_kdc_entry *device_skdc_entry = NULL;
+ const hdb_entry *device_krbtgt = NULL;
+ const struct samba_kdc_entry *device_krbtgt_skdc_entry = NULL;
+ const krb5_const_pac device_pac = kdc_request_get_armor_pac(r);
+
+ if (device != NULL) {
+ device_skdc_entry = talloc_get_type_abort(device->context,
+ struct samba_kdc_entry);
+
+ device_krbtgt = kdc_request_get_armor_server(r);
+ if (device_krbtgt != NULL) {
+ device_krbtgt_skdc_entry = talloc_get_type_abort(device_krbtgt->context,
+ struct samba_kdc_entry);
+ }
+ }
+
+ return samba_kdc_entry_pac(device_pac,
+ device_skdc_entry,
+ samba_kdc_entry_is_trust(device_krbtgt_skdc_entry));
+}
diff --git a/source4/kdc/kdc-glue.h b/source4/kdc/kdc-glue.h
new file mode 100644
index 0000000..9497d06
--- /dev/null
+++ b/source4/kdc/kdc-glue.h
@@ -0,0 +1,62 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ KDC structures
+
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+
+ 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/>.
+*/
+
+#ifndef _KDC_KDC_H
+#define _KDC_KDC_H
+
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include <hdb.h>
+#include <heimbase.h>
+#include <kdc.h>
+#include <krb5/kdc-plugin.h>
+#include "kdc/samba_kdc.h"
+#include "kdc/kdc-server.h"
+
+/* from hdb-samba4.c */
+NTSTATUS hdb_samba4_create_kdc(struct samba_kdc_base_context *base_ctx,
+ krb5_context context, struct HDB **db);
+
+NTSTATUS hdb_samba4_kpasswd_create_kdc(struct samba_kdc_base_context *base_ctx,
+ krb5_context context, struct HDB **db);
+
+krb5_error_code hdb_samba4_set_ntstatus(astgs_request_t r,
+ NTSTATUS status,
+ krb5_error_code error);
+
+struct authn_audit_info;
+
+krb5_error_code hdb_samba4_set_steal_client_audit_info(astgs_request_t r,
+ struct authn_audit_info *client_audit_info);
+
+krb5_error_code hdb_samba4_set_steal_server_audit_info(astgs_request_t r,
+ struct authn_audit_info *server_audit_info);
+
+/* from kdc-glue.c */
+int kdc_check_pac(krb5_context krb5_context,
+ DATA_BLOB server_sig,
+ struct PAC_SIGNATURE_DATA *kdc_sig,
+ hdb_entry *ent);
+
+struct samba_kdc_entry_pac samba_kdc_get_device_pac(const astgs_request_t r);
+
+#endif
diff --git a/source4/kdc/kdc-heimdal.c b/source4/kdc/kdc-heimdal.c
new file mode 100644
index 0000000..4f7b80d
--- /dev/null
+++ b/source4/kdc/kdc-heimdal.c
@@ -0,0 +1,550 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ KDC Server startup
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2008
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Stefan Metzmacher 2005
+
+ 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 "includes.h"
+#include "samba/process_model.h"
+#include "lib/tsocket/tsocket.h"
+#include "lib/messaging/irpc.h"
+#include "librpc/gen_ndr/ndr_irpc.h"
+#include "librpc/gen_ndr/ndr_krb5pac.h"
+#include "lib/socket/netif.h"
+#include "param/param.h"
+#include "kdc/kdc-server.h"
+#include "kdc/kdc-proxy.h"
+#include "kdc/kdc-glue.h"
+#include "kdc/pac-glue.h"
+#include "kdc/kpasswd-service.h"
+#include "dsdb/samdb/samdb.h"
+#include "auth/session.h"
+#include "libds/common/roles.h"
+#include <kdc.h>
+#include <hdb.h>
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+NTSTATUS server_service_kdc_init(TALLOC_CTX *);
+
+extern struct krb5plugin_kdc_ftable kdc_plugin_table;
+
+/**
+ Wrapper for krb5_kdc_process_krb5_request, converting to/from Samba
+ calling conventions
+*/
+
+static kdc_code kdc_process(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *input,
+ DATA_BLOB *reply,
+ struct tsocket_address *peer_addr,
+ struct tsocket_address *my_addr,
+ int datagram_reply)
+{
+ int ret;
+ char *pa;
+ struct sockaddr_storage ss;
+ krb5_data k5_reply;
+ krb5_kdc_configuration *kdc_config =
+ (krb5_kdc_configuration *)kdc->private_data;
+
+ krb5_data_zero(&k5_reply);
+
+ krb5_kdc_update_time(NULL);
+
+ ret = tsocket_address_bsd_sockaddr(peer_addr, (struct sockaddr *) &ss,
+ sizeof(struct sockaddr_storage));
+ if (ret < 0) {
+ return KDC_ERROR;
+ }
+ pa = tsocket_address_string(peer_addr, mem_ctx);
+ if (pa == NULL) {
+ return KDC_ERROR;
+ }
+
+ DBG_DEBUG("Received KDC packet of length %zu from %s\n",
+ input->length, pa);
+
+ ret = krb5_kdc_process_krb5_request(kdc->smb_krb5_context->krb5_context,
+ kdc_config,
+ input->data, input->length,
+ &k5_reply,
+ pa,
+ (struct sockaddr *) &ss,
+ datagram_reply);
+ if (ret == -1) {
+ *reply = data_blob(NULL, 0);
+ return KDC_ERROR;
+ }
+
+ if (ret == HDB_ERR_NOT_FOUND_HERE) {
+ *reply = data_blob(NULL, 0);
+ return KDC_PROXY_REQUEST;
+ }
+
+ if (k5_reply.length) {
+ *reply = data_blob_talloc(mem_ctx, k5_reply.data, k5_reply.length);
+ krb5_data_free(&k5_reply);
+ } else {
+ *reply = data_blob(NULL, 0);
+ }
+ return KDC_OK;
+}
+
+/*
+ setup our listening sockets on the configured network interfaces
+*/
+static NTSTATUS kdc_startup_interfaces(struct kdc_server *kdc,
+ struct loadparm_context *lp_ctx,
+ struct interface *ifaces,
+ const struct model_ops *model_ops)
+{
+ int num_interfaces;
+ TALLOC_CTX *tmp_ctx = talloc_new(kdc);
+ NTSTATUS status;
+ int i;
+ uint16_t kdc_port = lpcfg_krb5_port(lp_ctx);
+ uint16_t kpasswd_port = lpcfg_kpasswd_port(lp_ctx);
+ bool done_wildcard = false;
+
+ num_interfaces = iface_list_count(ifaces);
+
+ /* if we are allowing incoming packets from any address, then
+ we need to bind to the wildcard address */
+ if (!lpcfg_bind_interfaces_only(lp_ctx)) {
+ size_t num_binds = 0;
+ char **wcard = iface_list_wildcard(kdc);
+ NT_STATUS_HAVE_NO_MEMORY(wcard);
+ for (i=0; wcard[i]; i++) {
+ if (kdc_port) {
+ status = kdc_add_socket(kdc, model_ops,
+ "kdc", wcard[i], kdc_port,
+ kdc_process, false);
+ if (NT_STATUS_IS_OK(status)) {
+ num_binds++;
+ }
+ }
+
+ if (kpasswd_port) {
+ status = kdc_add_socket(kdc, model_ops,
+ "kpasswd", wcard[i], kpasswd_port,
+ kpasswd_process, false);
+ if (NT_STATUS_IS_OK(status)) {
+ num_binds++;
+ }
+ }
+ }
+ talloc_free(wcard);
+ if (num_binds == 0) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+ done_wildcard = true;
+ }
+
+ for (i=0; i<num_interfaces; i++) {
+ const char *address = talloc_strdup(tmp_ctx, iface_list_n_ip(ifaces, i));
+
+ if (kdc_port) {
+ status = kdc_add_socket(kdc, model_ops,
+ "kdc", address, kdc_port,
+ kdc_process, done_wildcard);
+ NT_STATUS_NOT_OK_RETURN(status);
+ }
+
+ if (kpasswd_port) {
+ status = kdc_add_socket(kdc, model_ops,
+ "kpasswd", address, kpasswd_port,
+ kpasswd_process, done_wildcard);
+ NT_STATUS_NOT_OK_RETURN(status);
+ }
+ }
+
+ talloc_free(tmp_ctx);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS kdc_check_generic_kerberos(struct irpc_message *msg,
+ struct kdc_check_generic_kerberos *r)
+{
+ struct PAC_Validate pac_validate;
+ DATA_BLOB srv_sig;
+ struct PAC_SIGNATURE_DATA kdc_sig;
+ struct kdc_server *kdc = talloc_get_type(msg->private_data, struct kdc_server);
+ krb5_kdc_configuration *kdc_config =
+ (krb5_kdc_configuration *)kdc->private_data;
+ enum ndr_err_code ndr_err;
+ int ret;
+ hdb_entry ent;
+ krb5_principal principal;
+
+
+ /* There is no reply to this request */
+ r->out.generic_reply = data_blob(NULL, 0);
+
+ ndr_err = ndr_pull_struct_blob(&r->in.generic_request, msg, &pac_validate,
+ (ndr_pull_flags_fn_t)ndr_pull_PAC_Validate);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (pac_validate.MessageType != NETLOGON_GENERIC_KRB5_PAC_VALIDATE) {
+ /* We don't implement any other message types - such as certificate validation - yet */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (pac_validate.ChecksumAndSignature.length != (pac_validate.ChecksumLength + pac_validate.SignatureLength)
+ || pac_validate.ChecksumAndSignature.length < pac_validate.ChecksumLength
+ || pac_validate.ChecksumAndSignature.length < pac_validate.SignatureLength ) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ srv_sig = data_blob_const(pac_validate.ChecksumAndSignature.data,
+ pac_validate.ChecksumLength);
+
+ ret = krb5_make_principal(kdc->smb_krb5_context->krb5_context, &principal,
+ lpcfg_realm(kdc->task->lp_ctx),
+ "krbtgt", lpcfg_realm(kdc->task->lp_ctx),
+ NULL);
+
+ if (ret != 0) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = kdc_config->db[0]->hdb_fetch_kvno(kdc->smb_krb5_context->krb5_context,
+ kdc_config->db[0],
+ principal,
+ HDB_F_GET_KRBTGT | HDB_F_DECRYPT,
+ 0,
+ &ent);
+
+ if (ret != 0) {
+ hdb_free_entry(kdc->smb_krb5_context->krb5_context, kdc_config->db[0], &ent);
+ krb5_free_principal(kdc->smb_krb5_context->krb5_context, principal);
+
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ kdc_sig.type = pac_validate.SignatureType;
+ kdc_sig.signature = data_blob_const(&pac_validate.ChecksumAndSignature.data[pac_validate.ChecksumLength],
+ pac_validate.SignatureLength);
+
+ ret = kdc_check_pac(kdc->smb_krb5_context->krb5_context, srv_sig, &kdc_sig, &ent);
+
+ hdb_free_entry(kdc->smb_krb5_context->krb5_context, kdc_config->db[0], &ent);
+ krb5_free_principal(kdc->smb_krb5_context->krb5_context, principal);
+
+ if (ret != 0) {
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ startup the kdc task
+*/
+static NTSTATUS kdc_task_init(struct task_server *task)
+{
+ struct kdc_server *kdc;
+ NTSTATUS status;
+ struct interface *ifaces;
+
+ switch (lpcfg_server_role(task->lp_ctx)) {
+ case ROLE_STANDALONE:
+ task_server_terminate(task, "kdc: no KDC required in standalone configuration", false);
+ return NT_STATUS_INVALID_DOMAIN_ROLE;
+ case ROLE_DOMAIN_MEMBER:
+ task_server_terminate(task, "kdc: no KDC required in member server configuration", false);
+ return NT_STATUS_INVALID_DOMAIN_ROLE;
+ case ROLE_DOMAIN_PDC:
+ case ROLE_DOMAIN_BDC:
+ case ROLE_IPA_DC:
+ task_server_terminate(
+ task, "Cannot start KDC as a 'classic Samba' DC", false);
+ return NT_STATUS_INVALID_DOMAIN_ROLE;
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ /* Yes, we want a KDC */
+ break;
+ }
+
+ load_interface_list(task, task->lp_ctx, &ifaces);
+
+ if (iface_list_count(ifaces) == 0) {
+ task_server_terminate(task, "kdc: no network interfaces configured", false);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ task_server_set_title(task, "task[kdc]");
+
+ kdc = talloc_zero(task, struct kdc_server);
+ if (kdc == NULL) {
+ task_server_terminate(task, "kdc: out of memory", true);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ kdc->task = task;
+ task->private_data = kdc;
+
+ /* start listening on the configured network interfaces */
+ status = kdc_startup_interfaces(kdc, task->lp_ctx, ifaces,
+ task->model_ops);
+ if (!NT_STATUS_IS_OK(status)) {
+ task_server_terminate(task, "kdc failed to setup interfaces", true);
+ return status;
+ }
+
+
+ return NT_STATUS_OK;
+}
+
+/*
+ initialise the kdc task after a fork
+*/
+static void kdc_post_fork(struct task_server *task, struct process_details *pd)
+{
+ struct kdc_server *kdc;
+ krb5_kdc_configuration *kdc_config = NULL;
+ NTSTATUS status;
+ krb5_error_code ret;
+ int ldb_ret;
+
+ if (task == NULL) {
+ task_server_terminate(task, "kdc: Null task", true);
+ return;
+ }
+ if (task->private_data == NULL) {
+ task_server_terminate(task, "kdc: No kdc_server info", true);
+ return;
+ }
+ kdc = talloc_get_type_abort(task->private_data, struct kdc_server);
+
+ /* get a samdb connection */
+ kdc->samdb = samdb_connect(kdc,
+ kdc->task->event_ctx,
+ kdc->task->lp_ctx,
+ system_session(kdc->task->lp_ctx),
+ NULL,
+ 0);
+ if (!kdc->samdb) {
+ DBG_WARNING("kdc_task_init: unable to connect to samdb\n");
+ task_server_terminate(task, "kdc: krb5_init_context samdb connect failed", true);
+ return;
+ }
+
+ ldb_ret = samdb_rodc(kdc->samdb, &kdc->am_rodc);
+ if (ldb_ret != LDB_SUCCESS) {
+ DBG_WARNING("kdc_task_init: "
+ "Cannot determine if we are an RODC: %s\n",
+ ldb_errstring(kdc->samdb));
+ task_server_terminate(task, "kdc: krb5_init_context samdb RODC connect failed", true);
+ return;
+ }
+
+ kdc->proxy_timeout = lpcfg_parm_int(kdc->task->lp_ctx, NULL, "kdc", "proxy timeout", 5);
+
+ initialize_krb5_error_table();
+
+ ret = smb_krb5_init_context(kdc, task->lp_ctx, &kdc->smb_krb5_context);
+ if (ret) {
+ DBG_WARNING("kdc_task_init: krb5_init_context failed (%s)\n",
+ error_message(ret));
+ task_server_terminate(task, "kdc: krb5_init_context failed", true);
+ return;
+ }
+
+ krb5_add_et_list(kdc->smb_krb5_context->krb5_context, initialize_hdb_error_table_r);
+
+ ret = krb5_kdc_get_config(kdc->smb_krb5_context->krb5_context,
+ &kdc_config);
+ if(ret) {
+ task_server_terminate(task, "kdc: failed to get KDC configuration", true);
+ return;
+ }
+
+ kdc_config->logf = (krb5_log_facility *)kdc->smb_krb5_context->pvt_log_data;
+ kdc_config->db = talloc(kdc, struct HDB *);
+ if (!kdc_config->db) {
+ task_server_terminate(task, "kdc: out of memory", true);
+ return;
+ }
+ kdc_config->num_db = 1;
+
+ /*
+ * Note with the CVE-2022-37966 patches,
+ * see https://bugzilla.samba.org/show_bug.cgi?id=15219
+ * and https://bugzilla.samba.org/show_bug.cgi?id=15237
+ * we want to use the strongest keys for everything.
+ *
+ * Some of these don't have any real effect anymore,
+ * but it is better to have them as true...
+ */
+ kdc_config->tgt_use_strongest_session_key = true;
+ kdc_config->preauth_use_strongest_session_key = true;
+ kdc_config->svc_use_strongest_session_key = true;
+ kdc_config->use_strongest_server_key = true;
+
+ kdc_config->force_include_pa_etype_salt = true;
+
+ /*
+ * For Samba CVE-2020-25719 Require PAC to be present
+ * This instructs Heimdal to match AD behaviour,
+ * as seen after Microsoft's CVE-2021-42287 when
+ * PacRequestorEnforcement is set to 2.
+ *
+ * Samba BUG: https://bugzilla.samba.org/show_bug.cgi?id=14686
+ * REF: https://support.microsoft.com/en-au/topic/kb5008380-authentication-updates-cve-2021-42287-9dafac11-e0d0-4cb8-959a-143bd0201041
+ */
+
+ kdc_config->require_pac = true;
+
+ /*
+ * By default we enable RFC6113/FAST support,
+ * but we have an option to disable in order to
+ * test against a KDC with FAST support.
+ */
+ kdc_config->enable_fast = lpcfg_kdc_enable_fast(task->lp_ctx);
+
+ {
+ static const char *dummy_string = "Microsoft";
+
+ /*
+ * The FAST cookie is not cryptographically required,
+ * provided that the non-AD gss-preauth authentication
+ * method is removed (as this is the only multi-step
+ * authentication method).
+ *
+ * gss-preauth has been disabled both by not being
+ * configured and by being made dependent
+ * configuration for a "real" fast cookie.
+ *
+ * The hide_client_names feature in Heimdal is the
+ * only other state that is persisted in the cookie,
+ * and this does not need to be in the cookie for
+ * single-shot authentication protocols such as ENC-TS
+ * and ENC-CHAL, the standard password protocols in
+ * AD.
+ *
+ * Furthermore, the Heimdal KDC does not fail if the
+ * client does not supply a FAST cookie, showing that
+ * the presence of the cookie is not required.
+ */
+ kdc_config->enable_fast_cookie = false;
+ kdc_config->dummy_fast_cookie = smb_krb5_make_data(discard_const_p(char, dummy_string),
+ strlen(dummy_string));
+ }
+
+ /*
+ * Match Windows and RFC6113 and Windows but break older
+ * Heimdal clients.
+ */
+ kdc_config->enable_armored_pa_enc_timestamp = false;
+
+ /* Register hdb-samba4 hooks for use as a keytab */
+
+ kdc->base_ctx = talloc_zero(kdc, struct samba_kdc_base_context);
+ if (!kdc->base_ctx) {
+ task_server_terminate(task, "kdc: out of memory", true);
+ return;
+ }
+
+ kdc->base_ctx->ev_ctx = task->event_ctx;
+ kdc->base_ctx->lp_ctx = task->lp_ctx;
+ kdc->base_ctx->msg_ctx = task->msg_ctx;
+
+ status = hdb_samba4_create_kdc(kdc->base_ctx,
+ kdc->smb_krb5_context->krb5_context,
+ &kdc_config->db[0]);
+ if (!NT_STATUS_IS_OK(status)) {
+ task_server_terminate(task, "kdc: hdb_samba4_create_kdc (setup KDC database) failed", true);
+ return;
+ }
+
+ ret = krb5_plugin_register(kdc->smb_krb5_context->krb5_context,
+ PLUGIN_TYPE_DATA, "hdb_samba4_interface",
+ &hdb_samba4_interface);
+ if(ret) {
+ task_server_terminate(task, "kdc: failed to register hdb plugin", true);
+ return;
+ }
+
+ kdc->kpasswd_keytab_name = talloc_asprintf(kdc, "HDBGET:samba4:&%p", kdc->base_ctx);
+ if (kdc->kpasswd_keytab_name == NULL) {
+ task_server_terminate(task,
+ "kdc: Failed to set keytab name",
+ true);
+ return;
+ }
+
+ ret = krb5_kt_register(kdc->smb_krb5_context->krb5_context, &hdb_get_kt_ops);
+ if(ret) {
+ task_server_terminate(task, "kdc: failed to register keytab plugin", true);
+ return;
+ }
+
+ /* Register KDC hooks */
+ ret = krb5_plugin_register(kdc->smb_krb5_context->krb5_context,
+ PLUGIN_TYPE_DATA, "kdc",
+ &kdc_plugin_table);
+ if(ret) {
+ task_server_terminate(task, "kdc: failed to register kdc plugin", true);
+ return;
+ }
+
+ ret = krb5_kdc_plugin_init(kdc->smb_krb5_context->krb5_context);
+
+ if(ret) {
+ task_server_terminate(task, "kdc: failed to init kdc plugin", true);
+ return;
+ }
+
+ ret = krb5_kdc_pkinit_config(kdc->smb_krb5_context->krb5_context, kdc_config);
+
+ if(ret) {
+ task_server_terminate(task, "kdc: failed to init kdc pkinit subsystem", true);
+ return;
+ }
+ kdc->private_data = kdc_config;
+
+ status = IRPC_REGISTER(task->msg_ctx, irpc, KDC_CHECK_GENERIC_KERBEROS,
+ kdc_check_generic_kerberos, kdc);
+ if (!NT_STATUS_IS_OK(status)) {
+ task_server_terminate(task, "kdc failed to setup monitoring", true);
+ return;
+ }
+
+ irpc_add_name(task->msg_ctx, "kdc_server");
+}
+
+
+/* called at smbd startup - register ourselves as a server service */
+NTSTATUS server_service_kdc_init(TALLOC_CTX *ctx)
+{
+ static const struct service_details details = {
+ .inhibit_fork_on_accept = true,
+ .inhibit_pre_fork = false,
+ .task_init = kdc_task_init,
+ .post_fork = kdc_post_fork
+ };
+ return register_server_service(ctx, "kdc", &details);
+}
diff --git a/source4/kdc/kdc-proxy.c b/source4/kdc/kdc-proxy.c
new file mode 100644
index 0000000..83d552a
--- /dev/null
+++ b/source4/kdc/kdc-proxy.c
@@ -0,0 +1,596 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ KDC Server request proxying
+
+ Copyright (C) Andrew Tridgell 2010
+ Copyright (C) Andrew Bartlett 2010
+ Copyright (C) Stefan Metzmacher 2011
+
+ 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 "includes.h"
+#include "samba/process_model.h"
+#include "lib/tsocket/tsocket.h"
+#include "libcli/util/tstream.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/stream/packet.h"
+#include "kdc/kdc-server.h"
+#include "kdc/kdc-proxy.h"
+#include "dsdb/samdb/samdb.h"
+#include "libcli/composite/composite.h"
+#include "libcli/resolve/resolve.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+/*
+ get a list of our replication partners from repsFrom, returning it in *proxy_list
+ */
+static WERROR kdc_proxy_get_writeable_dcs(struct kdc_server *kdc, TALLOC_CTX *mem_ctx, char ***proxy_list)
+{
+ WERROR werr;
+ uint32_t count, i;
+ struct repsFromToBlob *reps;
+
+ werr = dsdb_loadreps(kdc->samdb, mem_ctx, ldb_get_default_basedn(kdc->samdb), "repsFrom", &reps, &count);
+ W_ERROR_NOT_OK_RETURN(werr);
+
+ if (count == 0) {
+ /* we don't have any DCs to replicate with. Very
+ strange for a RODC */
+ DBG_WARNING("No replication sources for RODC in KDC proxy\n");
+ talloc_free(reps);
+ return WERR_DS_DRA_NO_REPLICA;
+ }
+
+ (*proxy_list) = talloc_array(mem_ctx, char *, count+1);
+ W_ERROR_HAVE_NO_MEMORY_AND_FREE(*proxy_list, reps);
+
+ talloc_steal(*proxy_list, reps);
+
+ for (i=0; i<count; i++) {
+ const char *dns_name = NULL;
+ if (reps->version == 1) {
+ dns_name = reps->ctr.ctr1.other_info->dns_name;
+ } else if (reps->version == 2) {
+ dns_name = reps->ctr.ctr2.other_info->dns_name1;
+ }
+ (*proxy_list)[i] = talloc_strdup(*proxy_list, dns_name);
+ W_ERROR_HAVE_NO_MEMORY_AND_FREE((*proxy_list)[i], *proxy_list);
+ }
+ (*proxy_list)[i] = NULL;
+
+ talloc_free(reps);
+
+ return WERR_OK;
+}
+
+
+struct kdc_udp_proxy_state {
+ struct tevent_context *ev;
+ struct kdc_server *kdc;
+ uint16_t port;
+ DATA_BLOB in;
+ DATA_BLOB out;
+ char **proxy_list;
+ uint32_t next_proxy;
+ struct {
+ struct nbt_name name;
+ const char *ip;
+ struct tdgram_context *dgram;
+ } proxy;
+};
+
+
+static void kdc_udp_next_proxy(struct tevent_req *req);
+
+struct tevent_req *kdc_udp_proxy_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct kdc_server *kdc,
+ uint16_t port,
+ DATA_BLOB in)
+{
+ struct tevent_req *req;
+ struct kdc_udp_proxy_state *state;
+ WERROR werr;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct kdc_udp_proxy_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->kdc = kdc;
+ state->port = port;
+ state->in = in;
+
+ werr = kdc_proxy_get_writeable_dcs(kdc, state, &state->proxy_list);
+ if (!W_ERROR_IS_OK(werr)) {
+ NTSTATUS status = werror_to_ntstatus(werr);
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ kdc_udp_next_proxy(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void kdc_udp_proxy_resolve_done(struct composite_context *csubreq);
+
+/*
+ try the next proxy in the list
+ */
+static void kdc_udp_next_proxy(struct tevent_req *req)
+{
+ struct kdc_udp_proxy_state *state =
+ tevent_req_data(req,
+ struct kdc_udp_proxy_state);
+ const char *proxy_dnsname = state->proxy_list[state->next_proxy];
+ struct composite_context *csubreq;
+
+ if (proxy_dnsname == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_LOGON_SERVERS);
+ return;
+ }
+
+ state->next_proxy++;
+
+ /* make sure we close the socket of the last try */
+ TALLOC_FREE(state->proxy.dgram);
+ ZERO_STRUCT(state->proxy);
+
+ make_nbt_name(&state->proxy.name, proxy_dnsname, 0);
+
+ csubreq = resolve_name_ex_send(lpcfg_resolve_context(state->kdc->task->lp_ctx),
+ state,
+ RESOLVE_NAME_FLAG_FORCE_DNS,
+ 0,
+ &state->proxy.name,
+ state->ev);
+ if (tevent_req_nomem(csubreq, req)) {
+ return;
+ }
+ csubreq->async.fn = kdc_udp_proxy_resolve_done;
+ csubreq->async.private_data = req;
+}
+
+static void kdc_udp_proxy_sendto_done(struct tevent_req *subreq);
+static void kdc_udp_proxy_recvfrom_done(struct tevent_req *subreq);
+
+static void kdc_udp_proxy_resolve_done(struct composite_context *csubreq)
+{
+ struct tevent_req *req =
+ talloc_get_type_abort(csubreq->async.private_data,
+ struct tevent_req);
+ struct kdc_udp_proxy_state *state =
+ tevent_req_data(req,
+ struct kdc_udp_proxy_state);
+ NTSTATUS status;
+ struct tevent_req *subreq;
+ struct tsocket_address *local_addr, *proxy_addr;
+ int ret;
+ bool ok;
+
+ status = resolve_name_recv(csubreq, state, &state->proxy.ip);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Unable to resolve proxy[%s] - %s\n",
+ state->proxy.name.name, nt_errstr(status));
+ kdc_udp_next_proxy(req);
+ return;
+ }
+
+ /* get an address for us to use locally */
+ ret = tsocket_address_inet_from_strings(state, "ip", NULL, 0, &local_addr);
+ if (ret != 0) {
+ kdc_udp_next_proxy(req);
+ return;
+ }
+
+ ret = tsocket_address_inet_from_strings(state, "ip",
+ state->proxy.ip,
+ state->port,
+ &proxy_addr);
+ if (ret != 0) {
+ kdc_udp_next_proxy(req);
+ return;
+ }
+
+ /* create a socket for us to work on */
+ ret = tdgram_inet_udp_socket(local_addr, proxy_addr,
+ state, &state->proxy.dgram);
+ if (ret != 0) {
+ kdc_udp_next_proxy(req);
+ return;
+ }
+
+ subreq = tdgram_sendto_send(state,
+ state->ev,
+ state->proxy.dgram,
+ state->in.data,
+ state->in.length,
+ NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, kdc_udp_proxy_sendto_done, req);
+
+ /* setup to receive the reply from the proxy */
+ subreq = tdgram_recvfrom_send(state, state->ev, state->proxy.dgram);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, kdc_udp_proxy_recvfrom_done, req);
+
+ ok = tevent_req_set_endtime(
+ subreq,
+ state->ev,
+ timeval_current_ofs(state->kdc->proxy_timeout, 0));
+ if (!ok) {
+ DBG_DEBUG("tevent_req_set_endtime failed\n");
+ return;
+ }
+
+ DEBUG(4,("kdc_udp_proxy: proxying request to %s[%s]\n",
+ state->proxy.name.name, state->proxy.ip));
+}
+
+/*
+ called when the send of the call to the proxy is complete
+ this is used to get an errors from the sendto()
+ */
+static void kdc_udp_proxy_sendto_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct kdc_udp_proxy_state *state =
+ tevent_req_data(req,
+ struct kdc_udp_proxy_state);
+ ssize_t ret;
+ int sys_errno;
+
+ ret = tdgram_sendto_recv(subreq, &sys_errno);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ DEBUG(4,("kdc_udp_proxy: sendto for %s[%s] gave %d : %s\n",
+ state->proxy.name.name, state->proxy.ip,
+ sys_errno, strerror(sys_errno)));
+ kdc_udp_next_proxy(req);
+ }
+}
+
+/*
+ called when the proxy replies
+ */
+static void kdc_udp_proxy_recvfrom_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct kdc_udp_proxy_state *state =
+ tevent_req_data(req,
+ struct kdc_udp_proxy_state);
+ int sys_errno;
+ uint8_t *buf;
+ ssize_t len;
+
+ len = tdgram_recvfrom_recv(subreq, &sys_errno,
+ state, &buf, NULL);
+ TALLOC_FREE(subreq);
+ if (len == -1) {
+ DEBUG(4,("kdc_udp_proxy: reply from %s[%s] gave %d : %s\n",
+ state->proxy.name.name, state->proxy.ip,
+ sys_errno, strerror(sys_errno)));
+ kdc_udp_next_proxy(req);
+ return;
+ }
+
+ /*
+ * Check the reply came from the right IP?
+ * As we use connected udp sockets, that should not be needed...
+ */
+
+ state->out.length = len;
+ state->out.data = buf;
+
+ tevent_req_done(req);
+}
+
+NTSTATUS kdc_udp_proxy_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out)
+{
+ struct kdc_udp_proxy_state *state =
+ tevent_req_data(req,
+ struct kdc_udp_proxy_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ out->data = talloc_move(mem_ctx, &state->out.data);
+ out->length = state->out.length;
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct kdc_tcp_proxy_state {
+ struct tevent_context *ev;
+ struct kdc_server *kdc;
+ uint16_t port;
+ DATA_BLOB in;
+ uint8_t in_hdr[4];
+ struct iovec in_iov[2];
+ DATA_BLOB out;
+ char **proxy_list;
+ uint32_t next_proxy;
+ struct {
+ struct nbt_name name;
+ const char *ip;
+ struct tstream_context *stream;
+ } proxy;
+};
+
+static void kdc_tcp_next_proxy(struct tevent_req *req);
+
+struct tevent_req *kdc_tcp_proxy_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct kdc_server *kdc,
+ uint16_t port,
+ DATA_BLOB in)
+{
+ struct tevent_req *req;
+ struct kdc_tcp_proxy_state *state;
+ WERROR werr;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct kdc_tcp_proxy_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->kdc = kdc;
+ state->port = port;
+ state->in = in;
+
+ werr = kdc_proxy_get_writeable_dcs(kdc, state, &state->proxy_list);
+ if (!W_ERROR_IS_OK(werr)) {
+ NTSTATUS status = werror_to_ntstatus(werr);
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ RSIVAL(state->in_hdr, 0, state->in.length);
+ state->in_iov[0].iov_base = (char *)state->in_hdr;
+ state->in_iov[0].iov_len = 4;
+ state->in_iov[1].iov_base = (char *)state->in.data;
+ state->in_iov[1].iov_len = state->in.length;
+
+ kdc_tcp_next_proxy(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void kdc_tcp_proxy_resolve_done(struct composite_context *csubreq);
+
+/*
+ try the next proxy in the list
+ */
+static void kdc_tcp_next_proxy(struct tevent_req *req)
+{
+ struct kdc_tcp_proxy_state *state =
+ tevent_req_data(req,
+ struct kdc_tcp_proxy_state);
+ const char *proxy_dnsname = state->proxy_list[state->next_proxy];
+ struct composite_context *csubreq;
+
+ if (proxy_dnsname == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_LOGON_SERVERS);
+ return;
+ }
+
+ state->next_proxy++;
+
+ /* make sure we close the socket of the last try */
+ TALLOC_FREE(state->proxy.stream);
+ ZERO_STRUCT(state->proxy);
+
+ make_nbt_name(&state->proxy.name, proxy_dnsname, 0);
+
+ csubreq = resolve_name_ex_send(lpcfg_resolve_context(state->kdc->task->lp_ctx),
+ state,
+ RESOLVE_NAME_FLAG_FORCE_DNS,
+ 0,
+ &state->proxy.name,
+ state->ev);
+ if (tevent_req_nomem(csubreq, req)) {
+ return;
+ }
+ csubreq->async.fn = kdc_tcp_proxy_resolve_done;
+ csubreq->async.private_data = req;
+}
+
+static void kdc_tcp_proxy_connect_done(struct tevent_req *subreq);
+
+static void kdc_tcp_proxy_resolve_done(struct composite_context *csubreq)
+{
+ struct tevent_req *req =
+ talloc_get_type_abort(csubreq->async.private_data,
+ struct tevent_req);
+ struct kdc_tcp_proxy_state *state =
+ tevent_req_data(req,
+ struct kdc_tcp_proxy_state);
+ NTSTATUS status;
+ struct tevent_req *subreq;
+ struct tsocket_address *local_addr, *proxy_addr;
+ int ret;
+
+ status = resolve_name_recv(csubreq, state, &state->proxy.ip);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Unable to resolve proxy[%s] - %s\n",
+ state->proxy.name.name, nt_errstr(status));
+ kdc_tcp_next_proxy(req);
+ return;
+ }
+
+ /* get an address for us to use locally */
+ ret = tsocket_address_inet_from_strings(state, "ip", NULL, 0, &local_addr);
+ if (ret != 0) {
+ kdc_tcp_next_proxy(req);
+ return;
+ }
+
+ ret = tsocket_address_inet_from_strings(state, "ip",
+ state->proxy.ip,
+ state->port,
+ &proxy_addr);
+ if (ret != 0) {
+ kdc_tcp_next_proxy(req);
+ return;
+ }
+
+ subreq = tstream_inet_tcp_connect_send(state, state->ev,
+ local_addr, proxy_addr);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, kdc_tcp_proxy_connect_done, req);
+ tevent_req_set_endtime(subreq, state->ev,
+ timeval_current_ofs(state->kdc->proxy_timeout, 0));
+}
+
+static void kdc_tcp_proxy_writev_done(struct tevent_req *subreq);
+static void kdc_tcp_proxy_read_pdu_done(struct tevent_req *subreq);
+
+static void kdc_tcp_proxy_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct kdc_tcp_proxy_state *state =
+ tevent_req_data(req,
+ struct kdc_tcp_proxy_state);
+ int ret, sys_errno;
+
+ ret = tstream_inet_tcp_connect_recv(subreq, &sys_errno,
+ state, &state->proxy.stream, NULL);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ kdc_tcp_next_proxy(req);
+ return;
+ }
+
+ subreq = tstream_writev_send(state,
+ state->ev,
+ state->proxy.stream,
+ state->in_iov, 2);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, kdc_tcp_proxy_writev_done, req);
+
+ subreq = tstream_read_pdu_blob_send(state,
+ state->ev,
+ state->proxy.stream,
+ 4, /* initial_read_size */
+ tstream_full_request_u32,
+ req);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, kdc_tcp_proxy_read_pdu_done, req);
+ tevent_req_set_endtime(subreq, state->kdc->task->event_ctx,
+ timeval_current_ofs(state->kdc->proxy_timeout, 0));
+
+ DEBUG(4,("kdc_tcp_proxy: proxying request to %s[%s]\n",
+ state->proxy.name.name, state->proxy.ip));
+}
+
+static void kdc_tcp_proxy_writev_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret, sys_errno;
+
+ ret = tstream_writev_recv(subreq, &sys_errno);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ kdc_tcp_next_proxy(req);
+ }
+}
+
+static void kdc_tcp_proxy_read_pdu_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct kdc_tcp_proxy_state *state =
+ tevent_req_data(req,
+ struct kdc_tcp_proxy_state);
+ NTSTATUS status;
+ DATA_BLOB raw;
+
+ status = tstream_read_pdu_blob_recv(subreq, state, &raw);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ kdc_tcp_next_proxy(req);
+ return;
+ }
+
+ /*
+ * raw blob has the length in the first 4 bytes,
+ * which we do not need here.
+ */
+ state->out = data_blob_talloc(state, raw.data + 4, raw.length - 4);
+ if (state->out.length != raw.length - 4) {
+ tevent_req_oom(req);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS kdc_tcp_proxy_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out)
+{
+ struct kdc_tcp_proxy_state *state =
+ tevent_req_data(req,
+ struct kdc_tcp_proxy_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ out->data = talloc_move(mem_ctx, &state->out.data);
+ out->length = state->out.length;
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source4/kdc/kdc-proxy.h b/source4/kdc/kdc-proxy.h
new file mode 100644
index 0000000..1ebe8da
--- /dev/null
+++ b/source4/kdc/kdc-proxy.h
@@ -0,0 +1,45 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ KDC related functions
+
+ Copyright (c) 2005-2008 Andrew Bartlett <abartlet@samba.org>
+ Copyright (c) 2005 Andrew Tridgell <tridge@samba.org>
+ Copyright (c) 2005 Stefan Metzmacher <metze@samba.org>
+
+ 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/>.
+*/
+
+#ifndef _KDC_PROXY_H
+#define _KDC_PROXY_H
+
+struct tevent_req *kdc_udp_proxy_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct kdc_server *kdc,
+ uint16_t port,
+ DATA_BLOB in);
+NTSTATUS kdc_udp_proxy_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out);
+
+struct tevent_req *kdc_tcp_proxy_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct kdc_server *kdc,
+ uint16_t port,
+ DATA_BLOB in);
+NTSTATUS kdc_tcp_proxy_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out);
+
+#endif /* _KDC_PROXY_H */
diff --git a/source4/kdc/kdc-server.c b/source4/kdc/kdc-server.c
new file mode 100644
index 0000000..297d91e
--- /dev/null
+++ b/source4/kdc/kdc-server.c
@@ -0,0 +1,623 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ KDC related functions
+
+ Copyright (c) 2005-2008 Andrew Bartlett <abartlet@samba.org>
+ Copyright (c) 2005 Andrew Tridgell <tridge@samba.org>
+ Copyright (c) 2005 Stefan Metzmacher <metze@samba.org>
+
+ 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 "includes.h"
+#include "param/param.h"
+#include "samba/process_model.h"
+#include "lib/tsocket/tsocket.h"
+#include "libcli/util/tstream.h"
+#include "kdc/kdc-server.h"
+#include "kdc/kdc-proxy.h"
+#include "lib/stream/packet.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+/*
+ * State of an open tcp connection
+ */
+struct kdc_tcp_connection {
+ /* stream connection we belong to */
+ struct stream_connection *conn;
+
+ /* the kdc_server the connection belongs to */
+ struct kdc_socket *kdc_socket;
+
+ struct tstream_context *tstream;
+
+ struct tevent_queue *send_queue;
+};
+
+struct kdc_tcp_call {
+ struct kdc_tcp_connection *kdc_conn;
+ DATA_BLOB in;
+ DATA_BLOB out;
+ uint8_t out_hdr[4];
+ struct iovec out_iov[2];
+};
+
+struct kdc_udp_call {
+ struct kdc_udp_socket *sock;
+ struct tsocket_address *src;
+ DATA_BLOB in;
+ DATA_BLOB out;
+};
+
+static void kdc_udp_call_proxy_done(struct tevent_req *subreq);
+static void kdc_udp_call_sendto_done(struct tevent_req *subreq);
+
+static void kdc_tcp_call_writev_done(struct tevent_req *subreq);
+static void kdc_tcp_call_proxy_done(struct tevent_req *subreq);
+
+static void kdc_tcp_terminate_connection(struct kdc_tcp_connection *kdc_conn,
+ const char *reason)
+{
+ stream_terminate_connection(kdc_conn->conn, reason);
+}
+
+static NTSTATUS kdc_proxy_unavailable_error(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out)
+{
+ krb5_error_code code;
+ krb5_data enc_error;
+
+ code = smb_krb5_mk_error(kdc->smb_krb5_context->krb5_context,
+ KRB5KDC_ERR_SVC_UNAVAILABLE,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &enc_error);
+ if (code != 0) {
+ DBG_WARNING("Unable to form krb5 error reply\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ *out = data_blob_talloc(mem_ctx, enc_error.data, enc_error.length);
+ smb_krb5_free_data_contents(kdc->smb_krb5_context->krb5_context,
+ &enc_error);
+ if (!out->data) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void kdc_udp_call_loop(struct tevent_req *subreq)
+{
+ struct kdc_udp_socket *sock = tevent_req_callback_data(subreq,
+ struct kdc_udp_socket);
+ struct kdc_udp_call *call;
+ uint8_t *buf;
+ ssize_t len;
+ int sys_errno;
+ kdc_code ret;
+
+ call = talloc(sock, struct kdc_udp_call);
+ if (call == NULL) {
+ talloc_free(call);
+ goto done;
+ }
+ call->sock = sock;
+
+ len = tdgram_recvfrom_recv(subreq, &sys_errno,
+ call, &buf, &call->src);
+ TALLOC_FREE(subreq);
+ if (len == -1) {
+ talloc_free(call);
+ goto done;
+ }
+
+ call->in.data = buf;
+ call->in.length = len;
+
+ DBG_DEBUG("Received krb5 UDP packet of length %zu from %s\n",
+ call->in.length,
+ tsocket_address_string(call->src, call));
+
+ /* Call krb5 */
+ ret = sock->kdc_socket->process(sock->kdc_socket->kdc,
+ call,
+ &call->in,
+ &call->out,
+ call->src,
+ sock->kdc_socket->local_address,
+ 1 /* Datagram */);
+ if (ret == KDC_ERROR) {
+ talloc_free(call);
+ goto done;
+ }
+
+ if (ret == KDC_PROXY_REQUEST) {
+ uint16_t port;
+
+ if (!sock->kdc_socket->kdc->am_rodc) {
+ DBG_ERR("proxying requested when not RODC\n");
+ talloc_free(call);
+ goto done;
+ }
+
+ port = tsocket_address_inet_port(sock->kdc_socket->local_address);
+
+ subreq = kdc_udp_proxy_send(call,
+ sock->kdc_socket->kdc->task->event_ctx,
+ sock->kdc_socket->kdc,
+ port,
+ call->in);
+ if (subreq == NULL) {
+ talloc_free(call);
+ goto done;
+ }
+ tevent_req_set_callback(subreq, kdc_udp_call_proxy_done, call);
+ goto done;
+ }
+
+ subreq = tdgram_sendto_queue_send(call,
+ sock->kdc_socket->kdc->task->event_ctx,
+ sock->dgram,
+ sock->send_queue,
+ call->out.data,
+ call->out.length,
+ call->src);
+ if (subreq == NULL) {
+ talloc_free(call);
+ goto done;
+ }
+ tevent_req_set_callback(subreq, kdc_udp_call_sendto_done, call);
+
+done:
+ subreq = tdgram_recvfrom_send(sock,
+ sock->kdc_socket->kdc->task->event_ctx,
+ sock->dgram);
+ if (subreq == NULL) {
+ task_server_terminate(sock->kdc_socket->kdc->task,
+ "no memory for tdgram_recvfrom_send",
+ true);
+ return;
+ }
+ tevent_req_set_callback(subreq, kdc_udp_call_loop, sock);
+}
+
+static void kdc_udp_call_proxy_done(struct tevent_req *subreq)
+{
+ struct kdc_udp_call *call =
+ tevent_req_callback_data(subreq,
+ struct kdc_udp_call);
+ NTSTATUS status;
+
+ status = kdc_udp_proxy_recv(subreq, call, &call->out);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* generate an error packet */
+ status = kdc_proxy_unavailable_error(call->sock->kdc_socket->kdc,
+ call, &call->out);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(call);
+ return;
+ }
+
+ subreq = tdgram_sendto_queue_send(call,
+ call->sock->kdc_socket->kdc->task->event_ctx,
+ call->sock->dgram,
+ call->sock->send_queue,
+ call->out.data,
+ call->out.length,
+ call->src);
+ if (subreq == NULL) {
+ talloc_free(call);
+ return;
+ }
+
+ tevent_req_set_callback(subreq, kdc_udp_call_sendto_done, call);
+}
+
+static void kdc_udp_call_sendto_done(struct tevent_req *subreq)
+{
+ struct kdc_udp_call *call = tevent_req_callback_data(subreq,
+ struct kdc_udp_call);
+ int sys_errno;
+
+ tdgram_sendto_queue_recv(subreq, &sys_errno);
+
+ /* We don't care about errors */
+
+ talloc_free(call);
+}
+
+static void kdc_tcp_call_loop(struct tevent_req *subreq)
+{
+ struct kdc_tcp_connection *kdc_conn = tevent_req_callback_data(subreq,
+ struct kdc_tcp_connection);
+ struct kdc_tcp_call *call;
+ NTSTATUS status;
+ kdc_code ret;
+
+ call = talloc(kdc_conn, struct kdc_tcp_call);
+ if (call == NULL) {
+ kdc_tcp_terminate_connection(kdc_conn, "kdc_tcp_call_loop: "
+ "no memory for kdc_tcp_call");
+ return;
+ }
+ call->kdc_conn = kdc_conn;
+
+ status = tstream_read_pdu_blob_recv(subreq,
+ call,
+ &call->in);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ const char *reason;
+
+ reason = talloc_asprintf(call, "kdc_tcp_call_loop: "
+ "tstream_read_pdu_blob_recv() - %s",
+ nt_errstr(status));
+ if (!reason) {
+ reason = nt_errstr(status);
+ }
+
+ kdc_tcp_terminate_connection(kdc_conn, reason);
+ return;
+ }
+
+ DBG_DEBUG("Received krb5 TCP packet of length %zu from %s\n",
+ call->in.length,
+ tsocket_address_string(kdc_conn->conn->remote_address, call));
+
+ /* skip length header */
+ call->in.data +=4;
+ call->in.length -= 4;
+
+ /* Call krb5 */
+ ret = kdc_conn->kdc_socket->process(kdc_conn->kdc_socket->kdc,
+ call,
+ &call->in,
+ &call->out,
+ kdc_conn->conn->remote_address,
+ kdc_conn->conn->local_address,
+ 0 /* Stream */);
+ if (ret == KDC_ERROR) {
+ kdc_tcp_terminate_connection(kdc_conn,
+ "kdc_tcp_call_loop: process function failed");
+ return;
+ }
+
+ if (ret == KDC_PROXY_REQUEST) {
+ uint16_t port;
+
+ if (!kdc_conn->kdc_socket->kdc->am_rodc) {
+ kdc_tcp_terminate_connection(kdc_conn,
+ "kdc_tcp_call_loop: proxying requested when not RODC");
+ return;
+ }
+ port = tsocket_address_inet_port(kdc_conn->conn->local_address);
+
+ subreq = kdc_tcp_proxy_send(call,
+ kdc_conn->conn->event.ctx,
+ kdc_conn->kdc_socket->kdc,
+ port,
+ call->in);
+ if (subreq == NULL) {
+ kdc_tcp_terminate_connection(kdc_conn,
+ "kdc_tcp_call_loop: kdc_tcp_proxy_send failed");
+ return;
+ }
+ tevent_req_set_callback(subreq, kdc_tcp_call_proxy_done, call);
+ return;
+ }
+
+ /* First add the length of the out buffer */
+ RSIVAL(call->out_hdr, 0, call->out.length);
+ call->out_iov[0].iov_base = (char *) call->out_hdr;
+ call->out_iov[0].iov_len = 4;
+
+ call->out_iov[1].iov_base = (char *) call->out.data;
+ call->out_iov[1].iov_len = call->out.length;
+
+ subreq = tstream_writev_queue_send(call,
+ kdc_conn->conn->event.ctx,
+ kdc_conn->tstream,
+ kdc_conn->send_queue,
+ call->out_iov, 2);
+ if (subreq == NULL) {
+ kdc_tcp_terminate_connection(kdc_conn, "kdc_tcp_call_loop: "
+ "no memory for tstream_writev_queue_send");
+ return;
+ }
+ tevent_req_set_callback(subreq, kdc_tcp_call_writev_done, call);
+
+ /*
+ * The krb5 tcp pdu's has the length as 4 byte (initial_read_size),
+ * tstream_full_request_u32 provides the pdu length then.
+ */
+ subreq = tstream_read_pdu_blob_send(kdc_conn,
+ kdc_conn->conn->event.ctx,
+ kdc_conn->tstream,
+ 4, /* initial_read_size */
+ tstream_full_request_u32,
+ kdc_conn);
+ if (subreq == NULL) {
+ kdc_tcp_terminate_connection(kdc_conn, "kdc_tcp_call_loop: "
+ "no memory for tstream_read_pdu_blob_send");
+ return;
+ }
+ tevent_req_set_callback(subreq, kdc_tcp_call_loop, kdc_conn);
+}
+
+static void kdc_tcp_call_proxy_done(struct tevent_req *subreq)
+{
+ struct kdc_tcp_call *call = tevent_req_callback_data(subreq,
+ struct kdc_tcp_call);
+ struct kdc_tcp_connection *kdc_conn = call->kdc_conn;
+ NTSTATUS status;
+
+ status = kdc_tcp_proxy_recv(subreq, call, &call->out);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* generate an error packet */
+ status = kdc_proxy_unavailable_error(kdc_conn->kdc_socket->kdc,
+ call, &call->out);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ const char *reason;
+
+ reason = talloc_asprintf(call, "kdc_tcp_call_proxy_done: "
+ "kdc_proxy_unavailable_error - %s",
+ nt_errstr(status));
+ if (!reason) {
+ reason = "kdc_tcp_call_proxy_done: kdc_proxy_unavailable_error() failed";
+ }
+
+ kdc_tcp_terminate_connection(call->kdc_conn, reason);
+ return;
+ }
+
+ /* First add the length of the out buffer */
+ RSIVAL(call->out_hdr, 0, call->out.length);
+ call->out_iov[0].iov_base = (char *) call->out_hdr;
+ call->out_iov[0].iov_len = 4;
+
+ call->out_iov[1].iov_base = (char *) call->out.data;
+ call->out_iov[1].iov_len = call->out.length;
+
+ subreq = tstream_writev_queue_send(call,
+ kdc_conn->conn->event.ctx,
+ kdc_conn->tstream,
+ kdc_conn->send_queue,
+ call->out_iov, 2);
+ if (subreq == NULL) {
+ kdc_tcp_terminate_connection(kdc_conn, "kdc_tcp_call_proxy_done: "
+ "no memory for tstream_writev_queue_send");
+ return;
+ }
+ tevent_req_set_callback(subreq, kdc_tcp_call_writev_done, call);
+
+ /*
+ * The krb5 tcp pdu's has the length as 4 byte (initial_read_size),
+ * tstream_full_request_u32 provides the pdu length then.
+ */
+ subreq = tstream_read_pdu_blob_send(kdc_conn,
+ kdc_conn->conn->event.ctx,
+ kdc_conn->tstream,
+ 4, /* initial_read_size */
+ tstream_full_request_u32,
+ kdc_conn);
+ if (subreq == NULL) {
+ kdc_tcp_terminate_connection(kdc_conn, "kdc_tcp_call_proxy_done: "
+ "no memory for tstream_read_pdu_blob_send");
+ return;
+ }
+ tevent_req_set_callback(subreq, kdc_tcp_call_loop, kdc_conn);
+}
+
+static void kdc_tcp_call_writev_done(struct tevent_req *subreq)
+{
+ struct kdc_tcp_call *call = tevent_req_callback_data(subreq,
+ struct kdc_tcp_call);
+ int sys_errno;
+ int rc;
+
+ rc = tstream_writev_queue_recv(subreq, &sys_errno);
+ TALLOC_FREE(subreq);
+ if (rc == -1) {
+ const char *reason;
+
+ reason = talloc_asprintf(call, "kdc_tcp_call_writev_done: "
+ "tstream_writev_queue_recv() - %d:%s",
+ sys_errno, strerror(sys_errno));
+ if (!reason) {
+ reason = "kdc_tcp_call_writev_done: tstream_writev_queue_recv() failed";
+ }
+
+ kdc_tcp_terminate_connection(call->kdc_conn, reason);
+ return;
+ }
+
+ /* We don't care about errors */
+
+ talloc_free(call);
+}
+
+/*
+ called when we get a new connection
+*/
+static void kdc_tcp_accept(struct stream_connection *conn)
+{
+ struct kdc_socket *kdc_socket;
+ struct kdc_tcp_connection *kdc_conn;
+ struct tevent_req *subreq;
+ int rc;
+
+ kdc_conn = talloc_zero(conn, struct kdc_tcp_connection);
+ if (kdc_conn == NULL) {
+ stream_terminate_connection(conn,
+ "kdc_tcp_accept: out of memory");
+ return;
+ }
+
+ kdc_conn->send_queue = tevent_queue_create(conn, "kdc_tcp_accept");
+ if (kdc_conn->send_queue == NULL) {
+ stream_terminate_connection(conn,
+ "kdc_tcp_accept: out of memory");
+ return;
+ }
+
+ kdc_socket = talloc_get_type(conn->private_data, struct kdc_socket);
+
+ TALLOC_FREE(conn->event.fde);
+
+ rc = tstream_bsd_existing_socket(kdc_conn,
+ socket_get_fd(conn->socket),
+ &kdc_conn->tstream);
+ if (rc < 0) {
+ stream_terminate_connection(conn,
+ "kdc_tcp_accept: out of memory");
+ return;
+ }
+ /* as server we want to fail early */
+ tstream_bsd_fail_readv_first_error(kdc_conn->tstream, true);
+
+ kdc_conn->conn = conn;
+ kdc_conn->kdc_socket = kdc_socket;
+ conn->private_data = kdc_conn;
+
+ /*
+ * The krb5 tcp pdu's has the length as 4 byte (initial_read_size),
+ * tstream_full_request_u32 provides the pdu length then.
+ */
+ subreq = tstream_read_pdu_blob_send(kdc_conn,
+ kdc_conn->conn->event.ctx,
+ kdc_conn->tstream,
+ 4, /* initial_read_size */
+ tstream_full_request_u32,
+ kdc_conn);
+ if (subreq == NULL) {
+ kdc_tcp_terminate_connection(kdc_conn, "kdc_tcp_accept: "
+ "no memory for tstream_read_pdu_blob_send");
+ return;
+ }
+ tevent_req_set_callback(subreq, kdc_tcp_call_loop, kdc_conn);
+}
+
+static void kdc_tcp_recv(struct stream_connection *conn, uint16_t flags)
+{
+ struct kdc_tcp_connection *kdcconn = talloc_get_type(conn->private_data,
+ struct kdc_tcp_connection);
+ /* this should never be triggered! */
+ kdc_tcp_terminate_connection(kdcconn, "kdc_tcp_recv: called");
+}
+
+static void kdc_tcp_send(struct stream_connection *conn, uint16_t flags)
+{
+ struct kdc_tcp_connection *kdcconn = talloc_get_type(conn->private_data,
+ struct kdc_tcp_connection);
+ /* this should never be triggered! */
+ kdc_tcp_terminate_connection(kdcconn, "kdc_tcp_send: called");
+}
+
+static const struct stream_server_ops kdc_tcp_stream_ops = {
+ .name = "kdc_tcp",
+ .accept_connection = kdc_tcp_accept,
+ .recv_handler = kdc_tcp_recv,
+ .send_handler = kdc_tcp_send
+};
+
+/*
+ * Start listening on the given address
+ */
+NTSTATUS kdc_add_socket(struct kdc_server *kdc,
+ const struct model_ops *model_ops,
+ const char *name,
+ const char *address,
+ uint16_t port,
+ kdc_process_fn_t process,
+ bool udp_only)
+{
+ struct kdc_socket *kdc_socket;
+ struct kdc_udp_socket *kdc_udp_socket;
+ struct tevent_req *udpsubreq;
+ NTSTATUS status;
+ int ret;
+
+ kdc_socket = talloc(kdc, struct kdc_socket);
+ NT_STATUS_HAVE_NO_MEMORY(kdc_socket);
+
+ kdc_socket->kdc = kdc;
+ kdc_socket->process = process;
+
+ ret = tsocket_address_inet_from_strings(kdc_socket, "ip",
+ address, port,
+ &kdc_socket->local_address);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ return status;
+ }
+
+ if (!udp_only) {
+ status = stream_setup_socket(kdc->task,
+ kdc->task->event_ctx,
+ kdc->task->lp_ctx,
+ model_ops,
+ &kdc_tcp_stream_ops,
+ "ip", address, &port,
+ lpcfg_socket_options(kdc->task->lp_ctx),
+ kdc_socket,
+ kdc->task->process_context);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to bind to %s:%u TCP - %s\n",
+ address, port, nt_errstr(status));
+ talloc_free(kdc_socket);
+ return status;
+ }
+ }
+
+ kdc_udp_socket = talloc(kdc_socket, struct kdc_udp_socket);
+ NT_STATUS_HAVE_NO_MEMORY(kdc_udp_socket);
+
+ kdc_udp_socket->kdc_socket = kdc_socket;
+
+ ret = tdgram_inet_udp_socket(kdc_socket->local_address,
+ NULL,
+ kdc_udp_socket,
+ &kdc_udp_socket->dgram);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DBG_ERR("Failed to bind to %s:%u UDP - %s\n",
+ address, port, nt_errstr(status));
+ return status;
+ }
+
+ kdc_udp_socket->send_queue = tevent_queue_create(kdc_udp_socket,
+ "kdc_udp_send_queue");
+ NT_STATUS_HAVE_NO_MEMORY(kdc_udp_socket->send_queue);
+
+ udpsubreq = tdgram_recvfrom_send(kdc_udp_socket,
+ kdc->task->event_ctx,
+ kdc_udp_socket->dgram);
+ NT_STATUS_HAVE_NO_MEMORY(udpsubreq);
+ tevent_req_set_callback(udpsubreq, kdc_udp_call_loop, kdc_udp_socket);
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/kdc/kdc-server.h b/source4/kdc/kdc-server.h
new file mode 100644
index 0000000..89b30f1
--- /dev/null
+++ b/source4/kdc/kdc-server.h
@@ -0,0 +1,83 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ KDC related functions
+
+ Copyright (c) 2005-2008 Andrew Bartlett <abartlet@samba.org>
+ Copyright (c) 2005 Andrew Tridgell <tridge@samba.org>
+ Copyright (c) 2005 Stefan Metzmacher <metze@samba.org>
+
+ 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/>.
+*/
+
+#ifndef _KDC_SERVER_H
+#define _KDC_SERVER_H
+
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+
+struct tsocket_address;
+struct model_ops;
+
+/*
+ * Context structure for the kdc server
+ */
+struct kdc_server {
+ struct task_server *task;
+ struct smb_krb5_context *smb_krb5_context;
+ struct samba_kdc_base_context *base_ctx;
+ struct ldb_context *samdb;
+ bool am_rodc;
+ uint32_t proxy_timeout;
+ const char *kpasswd_keytab_name;
+ void *private_data;
+};
+
+typedef enum kdc_code_e {
+ KDC_OK = 0,
+ KDC_ERROR,
+ KDC_PROXY_REQUEST
+} kdc_code;
+
+typedef kdc_code (*kdc_process_fn_t)(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *request,
+ DATA_BLOB *reply,
+ struct tsocket_address *remote_address,
+ struct tsocket_address *local_address,
+ int datagram);
+
+/* Information about one kdc socket */
+struct kdc_socket {
+ struct kdc_server *kdc;
+ struct tsocket_address *local_address;
+ kdc_process_fn_t process;
+};
+
+/* Information about one kdc/kpasswd udp socket */
+struct kdc_udp_socket {
+ struct kdc_socket *kdc_socket;
+ struct tdgram_context *dgram;
+ struct tevent_queue *send_queue;
+};
+
+NTSTATUS kdc_add_socket(struct kdc_server *kdc,
+ const struct model_ops *model_ops,
+ const char *name,
+ const char *address,
+ uint16_t port,
+ kdc_process_fn_t process,
+ bool udp_only);
+
+#endif /* _KDC_SERVER_H */
diff --git a/source4/kdc/kdc-service-mit.c b/source4/kdc/kdc-service-mit.c
new file mode 100644
index 0000000..5b1240c
--- /dev/null
+++ b/source4/kdc/kdc-service-mit.c
@@ -0,0 +1,395 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Start MIT krb5kdc server within Samba AD
+
+ Copyright (c) 2014-2016 Andreas Schneider <asn@samba.org>
+
+ 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 "includes.h"
+#include "talloc.h"
+#include "tevent.h"
+#include "system/filesys.h"
+#include "lib/param/param.h"
+#include "lib/util/samba_util.h"
+#include "source4/samba/service.h"
+#include "source4/samba/process_model.h"
+#include "kdc/kdc-service-mit.h"
+#include "dynconfig.h"
+#include "libds/common/roles.h"
+#include "lib/socket/netif.h"
+#include "samba/session.h"
+#include "dsdb/samdb/samdb.h"
+#include "kdc/samba_kdc.h"
+#include "kdc/kdc-server.h"
+#include "kdc/kpasswd-service.h"
+#include <kadm5/admin.h>
+#include <kdb.h>
+
+#include "source4/kdc/mit_kdc_irpc.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+/* PROTOTYPES */
+static void mitkdc_server_done(struct tevent_req *subreq);
+
+static int kdc_server_destroy(struct kdc_server *kdc)
+{
+ if (kdc->private_data != NULL) {
+ kadm5_destroy(kdc->private_data);
+ }
+
+ return 0;
+}
+
+static NTSTATUS startup_kpasswd_server(TALLOC_CTX *mem_ctx,
+ struct kdc_server *kdc,
+ struct loadparm_context *lp_ctx,
+ struct interface *ifaces)
+{
+ int num_interfaces;
+ int i;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ uint16_t kpasswd_port;
+ bool done_wildcard = false;
+ bool ok;
+
+ kpasswd_port = lpcfg_kpasswd_port(lp_ctx);
+ if (kpasswd_port == 0) {
+ return NT_STATUS_OK;
+ }
+
+ tmp_ctx = talloc_named_const(mem_ctx, 0, "kpasswd");
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ num_interfaces = iface_list_count(ifaces);
+
+ ok = lpcfg_bind_interfaces_only(lp_ctx);
+ if (!ok) {
+ int num_binds = 0;
+ char **wcard;
+
+ wcard = iface_list_wildcard(tmp_ctx);
+ if (wcard == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ for (i = 0; wcard[i] != NULL; i++) {
+ status = kdc_add_socket(kdc,
+ kdc->task->model_ops,
+ "kpasswd",
+ wcard[i],
+ kpasswd_port,
+ kpasswd_process,
+ false);
+ if (NT_STATUS_IS_OK(status)) {
+ num_binds++;
+ }
+ }
+ talloc_free(wcard);
+
+ if (num_binds == 0) {
+ status = NT_STATUS_INVALID_PARAMETER_MIX;
+ goto out;
+ }
+
+ done_wildcard = true;
+ }
+
+ for (i = 0; i < num_interfaces; i++) {
+ const char *address = talloc_strdup(tmp_ctx, iface_list_n_ip(ifaces, i));
+
+ status = kdc_add_socket(kdc,
+ kdc->task->model_ops,
+ "kpasswd",
+ address,
+ kpasswd_port,
+ kpasswd_process,
+ done_wildcard);
+ if (NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+out:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+/*
+ * Startup a copy of the krb5kdc as a child daemon
+ */
+NTSTATUS mitkdc_task_init(struct task_server *task)
+{
+ struct tevent_req *subreq;
+ const char * const *kdc_cmd;
+ struct interface *ifaces;
+ char *kdc_config = NULL;
+ struct kdc_server *kdc;
+ krb5_error_code code;
+ NTSTATUS status;
+ kadm5_ret_t ret;
+ kadm5_config_params config;
+ void *server_handle;
+ int dbglvl = 0;
+
+ task_server_set_title(task, "task[mitkdc_parent]");
+
+ switch (lpcfg_server_role(task->lp_ctx)) {
+ case ROLE_STANDALONE:
+ task_server_terminate(task,
+ "The KDC is not required in standalone "
+ "server configuration, terminate!",
+ false);
+ return NT_STATUS_INVALID_DOMAIN_ROLE;
+ case ROLE_DOMAIN_MEMBER:
+ task_server_terminate(task,
+ "The KDC is not required in member "
+ "server configuration",
+ false);
+ return NT_STATUS_INVALID_DOMAIN_ROLE;
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ /* Yes, we want to start the KDC */
+ break;
+ }
+
+ /* Load interfaces for kpasswd */
+ load_interface_list(task, task->lp_ctx, &ifaces);
+ if (iface_list_count(ifaces) == 0) {
+ task_server_terminate(task,
+ "KDC: no network interfaces configured",
+ false);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ kdc_config = talloc_asprintf(task,
+ "%s/kdc.conf",
+ lpcfg_private_dir(task->lp_ctx));
+ if (kdc_config == NULL) {
+ task_server_terminate(task,
+ "KDC: no memory",
+ false);
+ return NT_STATUS_NO_MEMORY;
+ }
+ setenv("KRB5_KDC_PROFILE", kdc_config, 0);
+ TALLOC_FREE(kdc_config);
+
+ dbglvl = debuglevel_get_class(DBGC_KERBEROS);
+ if (dbglvl >= 10) {
+ char *kdc_trace_file = talloc_asprintf(task,
+ "%s/mit_kdc_trace.log",
+ get_dyn_LOGFILEBASE());
+ if (kdc_trace_file == NULL) {
+ task_server_terminate(task,
+ "KDC: no memory",
+ false);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ setenv("KRB5_TRACE", kdc_trace_file, 1);
+ }
+
+ /* start it as a child process */
+ kdc_cmd = lpcfg_mit_kdc_command(task->lp_ctx);
+
+ subreq = samba_runcmd_send(task,
+ task->event_ctx,
+ timeval_zero(),
+ 1, /* stdout log level */
+ 0, /* stderr log level */
+ kdc_cmd,
+ "-n", /* Don't go into background */
+#if 0
+ "-w 2", /* Start two workers */
+#endif
+ NULL);
+ if (subreq == NULL) {
+ DBG_ERR("Failed to start MIT KDC as child daemon\n");
+
+ task_server_terminate(task,
+ "Failed to startup mitkdc task",
+ true);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ tevent_req_set_callback(subreq, mitkdc_server_done, task);
+
+ DBG_INFO("Started krb5kdc process\n");
+
+ status = samba_setup_mit_kdc_irpc(task);
+ if (!NT_STATUS_IS_OK(status)) {
+ task_server_terminate(task,
+ "Failed to setup kdc irpc service",
+ true);
+ }
+
+ DBG_INFO("Started irpc service for kdc_server\n");
+
+ kdc = talloc_zero(task, struct kdc_server);
+ if (kdc == NULL) {
+ task_server_terminate(task, "KDC: Out of memory", true);
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_set_destructor(kdc, kdc_server_destroy);
+
+ kdc->task = task;
+
+ kdc->base_ctx = talloc_zero(kdc, struct samba_kdc_base_context);
+ if (kdc->base_ctx == NULL) {
+ task_server_terminate(task, "KDC: Out of memory", true);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ kdc->base_ctx->ev_ctx = task->event_ctx;
+ kdc->base_ctx->lp_ctx = task->lp_ctx;
+
+ initialize_krb5_error_table();
+
+ code = smb_krb5_init_context(kdc,
+ kdc->task->lp_ctx,
+ &kdc->smb_krb5_context);
+ if (code != 0) {
+ task_server_terminate(task,
+ "KDC: Unable to initialize krb5 context",
+ true);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ code = kadm5_init_krb5_context(&kdc->smb_krb5_context->krb5_context);
+ if (code != 0) {
+ task_server_terminate(task,
+ "KDC: Unable to init kadm5 krb5_context",
+ true);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ZERO_STRUCT(config);
+ config.mask = KADM5_CONFIG_REALM;
+ config.realm = discard_const_p(char, lpcfg_realm(kdc->task->lp_ctx));
+
+ ret = kadm5_init(kdc->smb_krb5_context->krb5_context,
+ discard_const_p(char, "kpasswd"),
+ NULL, /* pass */
+ discard_const_p(char, "kpasswd"),
+ &config,
+ KADM5_STRUCT_VERSION,
+ KADM5_API_VERSION_4,
+ NULL,
+ &server_handle);
+ if (ret != 0) {
+ task_server_terminate(task,
+ "KDC: Initialize kadm5",
+ true);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ kdc->private_data = server_handle;
+
+ code = krb5_db_register_keytab(kdc->smb_krb5_context->krb5_context);
+ if (code != 0) {
+ task_server_terminate(task,
+ "KDC: Unable to KDB",
+ true);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ kdc->kpasswd_keytab_name = talloc_asprintf(kdc, "KDB:");
+ if (kdc->kpasswd_keytab_name == NULL) {
+ task_server_terminate(task,
+ "KDC: Out of memory",
+ true);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ kdc->samdb = samdb_connect(kdc,
+ kdc->task->event_ctx,
+ kdc->task->lp_ctx,
+ system_session(kdc->task->lp_ctx),
+ NULL,
+ 0);
+ if (kdc->samdb == NULL) {
+ task_server_terminate(task,
+ "KDC: Unable to connect to samdb",
+ true);
+ return NT_STATUS_CONNECTION_INVALID;
+ }
+
+ status = startup_kpasswd_server(kdc,
+ kdc,
+ task->lp_ctx,
+ ifaces);
+ if (!NT_STATUS_IS_OK(status)) {
+ task_server_terminate(task,
+ "KDC: Unable to start kpasswd server",
+ true);
+ return status;
+ }
+
+ DBG_INFO("Started kpasswd service for kdc_server\n");
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * This gets called the kdc exits.
+ */
+static void mitkdc_server_done(struct tevent_req *subreq)
+{
+ struct task_server *task =
+ tevent_req_callback_data(subreq,
+ struct task_server);
+ int sys_errno;
+ int ret;
+
+ ret = samba_runcmd_recv(subreq, &sys_errno);
+ if (ret != 0) {
+ DBG_ERR("The MIT KDC daemon died with exit status %d\n",
+ sys_errno);
+ } else {
+ DBG_ERR("The MIT KDC daemon exited normally\n");
+ }
+
+ task_server_terminate(task, "mitkdc child process exited", true);
+}
+
+/* Called at MIT KRB5 startup - register ourselves as a server service */
+NTSTATUS server_service_mitkdc_init(TALLOC_CTX *mem_ctx);
+
+NTSTATUS server_service_mitkdc_init(TALLOC_CTX *mem_ctx)
+{
+ static const struct service_details details = {
+ .inhibit_fork_on_accept = true,
+ /*
+ * Need to prevent pre-forking on kdc.
+ * The task_init function is run on the master process only
+ * and the irpc process name is registered in it's event loop.
+ * The child worker processes initialise their event loops on
+ * fork, so are not listening for the irpc event.
+ *
+ * The master process does not wait on that event context
+ * the master process is responsible for managing the worker
+ * processes not performing work.
+ */
+ .inhibit_pre_fork = true,
+ .task_init = mitkdc_task_init,
+ .post_fork = NULL
+ };
+ return register_server_service(mem_ctx, "kdc", &details);
+}
diff --git a/source4/kdc/kdc-service-mit.h b/source4/kdc/kdc-service-mit.h
new file mode 100644
index 0000000..7943933
--- /dev/null
+++ b/source4/kdc/kdc-service-mit.h
@@ -0,0 +1,27 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Start MIT krb5kdc server within Samba AD
+
+ Copyright (c) 2014 Andreas Schneider <asn@samba.org>
+
+ 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/>.
+*/
+
+#ifndef _KDC_SERVICE_MIT_H
+#define _KDC_SERVICE_MIT_H
+
+NTSTATUS mitkdc_task_init(struct task_server *task);
+
+#endif /* _KDC_SERVICE_MIT_H */
diff --git a/source4/kdc/kpasswd-helper.c b/source4/kdc/kpasswd-helper.c
new file mode 100644
index 0000000..3e4ea36
--- /dev/null
+++ b/source4/kdc/kpasswd-helper.c
@@ -0,0 +1,264 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Samba kpasswd implementation
+
+ Copyright (c) 2005 Andrew Bartlett <abartlet@samba.org>
+ Copyright (c) 2016 Andreas Schneider <asn@samba.org>
+
+ 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 "includes.h"
+#include "system/kerberos.h"
+#include "librpc/gen_ndr/samr.h"
+#include "dsdb/samdb/samdb.h"
+#include "auth/auth.h"
+#include "kdc/kpasswd-helper.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+bool kpasswd_make_error_reply(TALLOC_CTX *mem_ctx,
+ krb5_error_code error_code,
+ const char *error_string,
+ DATA_BLOB *error_data)
+{
+ bool ok;
+ char *s;
+ size_t slen;
+
+ if (error_code == 0) {
+ DBG_DEBUG("kpasswd reply - %s\n", error_string);
+ } else {
+ DBG_INFO("kpasswd reply - %s\n", error_string);
+ }
+
+ ok = push_utf8_talloc(mem_ctx, &s, error_string, &slen);
+ if (!ok) {
+ return false;
+ }
+
+ /*
+ * The string 's' has one terminating nul-byte which is also
+ * reflected by 'slen'. We subtract it from the length.
+ */
+ if (slen < 1) {
+ talloc_free(s);
+ return false;
+ }
+ slen--;
+
+ /* Two bytes are added to the length to account for the error code. */
+ if (2 + slen < slen) {
+ talloc_free(s);
+ return false;
+ }
+ error_data->length = 2 + slen;
+ error_data->data = talloc_size(mem_ctx, error_data->length);
+ if (error_data->data == NULL) {
+ talloc_free(s);
+ return false;
+ }
+
+ RSSVAL(error_data->data, 0, error_code);
+ memcpy(error_data->data + 2, s, slen);
+
+ talloc_free(s);
+
+ return true;
+}
+
+bool kpasswd_make_pwchange_reply(TALLOC_CTX *mem_ctx,
+ NTSTATUS status,
+ enum samPwdChangeReason reject_reason,
+ struct samr_DomInfo1 *dominfo,
+ DATA_BLOB *error_blob)
+{
+ const char *reject_string = NULL;
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ return kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_ACCESSDENIED,
+ "No such user when changing password",
+ error_blob);
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ return kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_ACCESSDENIED,
+ "Not permitted to change password",
+ error_blob);
+ }
+ if (dominfo != NULL &&
+ NT_STATUS_EQUAL(status, NT_STATUS_PASSWORD_RESTRICTION)) {
+ switch (reject_reason) {
+ case SAM_PWD_CHANGE_PASSWORD_TOO_SHORT:
+ reject_string =
+ talloc_asprintf(mem_ctx,
+ "Password too short, password "
+ "must be at least %d characters "
+ "long.",
+ dominfo->min_password_length);
+ if (reject_string == NULL) {
+ reject_string = "Password too short";
+ }
+ break;
+ case SAM_PWD_CHANGE_NOT_COMPLEX:
+ reject_string = "Password does not meet complexity "
+ "requirements";
+ break;
+ case SAM_PWD_CHANGE_PWD_IN_HISTORY:
+ reject_string =
+ talloc_asprintf(mem_ctx,
+ "Password is already in password "
+ "history. New password must not "
+ "match any of your %d previous "
+ "passwords.",
+ dominfo->password_history_length);
+ if (reject_string == NULL) {
+ reject_string = "Password is already in password "
+ "history";
+ }
+ break;
+ default:
+ reject_string = "Password change rejected, password "
+ "changes may not be permitted on this "
+ "account, or the minimum password age "
+ "may not have elapsed.";
+ break;
+ }
+
+ return kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_SOFTERROR,
+ reject_string,
+ error_blob);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reject_string = talloc_asprintf(mem_ctx,
+ "Failed to set password: %s",
+ nt_errstr(status));
+ if (reject_string == NULL) {
+ reject_string = "Failed to set password";
+ }
+ return kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ reject_string,
+ error_blob);
+ }
+
+ return kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_SUCCESS,
+ "Password changed",
+ error_blob);
+}
+
+NTSTATUS kpasswd_samdb_set_password(TALLOC_CTX *mem_ctx,
+ struct tevent_context *event_ctx,
+ struct loadparm_context *lp_ctx,
+ struct auth_session_info *session_info,
+ bool is_service_principal,
+ const char *target_principal_name,
+ DATA_BLOB *password,
+ enum samPwdChangeReason *reject_reason,
+ struct samr_DomInfo1 **dominfo)
+{
+ NTSTATUS status;
+ struct ldb_context *samdb;
+ struct ldb_dn *target_dn = NULL;
+ int rc;
+
+ samdb = samdb_connect(mem_ctx,
+ event_ctx,
+ lp_ctx,
+ session_info,
+ NULL,
+ 0);
+ if (samdb == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ DBG_INFO("%s\\%s (%s) is changing password of %s\n",
+ session_info->info->domain_name,
+ session_info->info->account_name,
+ dom_sid_string(mem_ctx,
+ &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]),
+ target_principal_name);
+
+ rc = ldb_transaction_start(samdb);
+ if (rc != LDB_SUCCESS) {
+ return NT_STATUS_TRANSACTION_ABORTED;
+ }
+
+ if (is_service_principal) {
+ status = crack_service_principal_name(samdb,
+ mem_ctx,
+ target_principal_name,
+ &target_dn,
+ NULL);
+ } else {
+ status = crack_user_principal_name(samdb,
+ mem_ctx,
+ target_principal_name,
+ &target_dn,
+ NULL);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_transaction_cancel(samdb);
+ return status;
+ }
+
+ status = samdb_set_password(samdb,
+ mem_ctx,
+ target_dn,
+ NULL, /* domain_dn */
+ password,
+ NULL, /* ntNewHash */
+ DSDB_PASSWORD_RESET,
+ reject_reason,
+ dominfo);
+ if (NT_STATUS_IS_OK(status)) {
+ rc = ldb_transaction_commit(samdb);
+ if (rc != LDB_SUCCESS) {
+ DBG_WARNING("Failed to commit transaction to "
+ "set password on %s: %s\n",
+ ldb_dn_get_linearized(target_dn),
+ ldb_errstring(samdb));
+ return NT_STATUS_TRANSACTION_ABORTED;
+ }
+ } else {
+ ldb_transaction_cancel(samdb);
+ }
+
+ return status;
+}
+
+krb5_error_code kpasswd_check_non_tgt(struct auth_session_info *session_info,
+ const char **error_string)
+{
+ switch(session_info->ticket_type) {
+ case TICKET_TYPE_TGT:
+ /* TGTs are disallowed here. */
+ *error_string = "A TGT may not be used as a ticket to kpasswd";
+ return KRB5_KPASSWD_AUTHERROR;
+ case TICKET_TYPE_NON_TGT:
+ /* Non-TGTs are permitted, and expected. */
+ break;
+ default:
+ /* In case we forgot to set the type. */
+ *error_string = "Failed to ascertain that ticket to kpasswd is not a TGT";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+
+ return 0;
+}
diff --git a/source4/kdc/kpasswd-helper.h b/source4/kdc/kpasswd-helper.h
new file mode 100644
index 0000000..94a6e2a
--- /dev/null
+++ b/source4/kdc/kpasswd-helper.h
@@ -0,0 +1,48 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Samba kpasswd implementation
+
+ Copyright (c) 2016 Andreas Schneider <asn@samba.org>
+
+ 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/>.
+*/
+
+#ifndef _KPASSWD_HELPER_H
+#define _KPASSWD_HELPER_H
+
+bool kpasswd_make_error_reply(TALLOC_CTX *mem_ctx,
+ krb5_error_code error_code,
+ const char *error_string,
+ DATA_BLOB *error_data);
+
+bool kpasswd_make_pwchange_reply(TALLOC_CTX *mem_ctx,
+ NTSTATUS status,
+ enum samPwdChangeReason reject_reason,
+ struct samr_DomInfo1 *dominfo,
+ DATA_BLOB *error_blob);
+
+NTSTATUS kpasswd_samdb_set_password(TALLOC_CTX *mem_ctx,
+ struct tevent_context *event_ctx,
+ struct loadparm_context *lp_ctx,
+ struct auth_session_info *session_info,
+ bool is_service_principal,
+ const char *target_principal_name,
+ DATA_BLOB *password,
+ enum samPwdChangeReason *reject_reason,
+ struct samr_DomInfo1 **dominfo);
+
+krb5_error_code kpasswd_check_non_tgt(struct auth_session_info *session_info,
+ const char **error_string);
+#endif /* _KPASSWD_HELPER_H */
diff --git a/source4/kdc/kpasswd-service-heimdal.c b/source4/kdc/kpasswd-service-heimdal.c
new file mode 100644
index 0000000..b4709de
--- /dev/null
+++ b/source4/kdc/kpasswd-service-heimdal.c
@@ -0,0 +1,326 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Samba kpasswd implementation
+
+ Copyright (c) 2016 Andreas Schneider <asn@samba.org>
+
+ 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 "includes.h"
+#include "samba/service_task.h"
+#include "param/param.h"
+#include "auth/auth.h"
+#include "auth/gensec/gensec.h"
+#include "gensec_krb5_helpers.h"
+#include "kdc/kdc-server.h"
+#include "kdc/kpasswd_glue.h"
+#include "kdc/kpasswd-service.h"
+#include "kdc/kpasswd-helper.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+static krb5_error_code kpasswd_change_password(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ const struct gensec_security *gensec_security,
+ struct auth_session_info *session_info,
+ DATA_BLOB *password,
+ DATA_BLOB *kpasswd_reply,
+ const char **error_string)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ enum samPwdChangeReason reject_reason;
+ const char *reject_string = NULL;
+ struct samr_DomInfo1 *dominfo;
+ bool ok;
+ int ret;
+
+ /*
+ * We're doing a password change (rather than a password set), so check
+ * that we were given an initial ticket.
+ */
+ ret = gensec_krb5_initial_ticket(gensec_security);
+ if (ret != 1) {
+ *error_string = "Expected an initial ticket";
+ return KRB5_KPASSWD_INITIAL_FLAG_NEEDED;
+ }
+
+ status = samdb_kpasswd_change_password(mem_ctx,
+ kdc->task->lp_ctx,
+ kdc->task->event_ctx,
+ session_info,
+ password,
+ &reject_reason,
+ &dominfo,
+ &reject_string,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ ok = kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_ACCESSDENIED,
+ reject_string,
+ kpasswd_reply);
+ if (!ok) {
+ *error_string = "Failed to create reply";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+ /* We want to send an an authenticated packet. */
+ return 0;
+ }
+
+ ok = kpasswd_make_pwchange_reply(mem_ctx,
+ result,
+ reject_reason,
+ dominfo,
+ kpasswd_reply);
+ if (!ok) {
+ *error_string = "Failed to create reply";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+
+ return 0;
+}
+
+static krb5_error_code kpasswd_set_password(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ const struct gensec_security *gensec_security,
+ struct auth_session_info *session_info,
+ DATA_BLOB *decoded_data,
+ DATA_BLOB *kpasswd_reply,
+ const char **error_string)
+{
+ krb5_context context = kdc->smb_krb5_context->krb5_context;
+ krb5_error_code code;
+ krb5_principal target_principal;
+ ChangePasswdDataMS chpw = {};
+ size_t chpw_len = 0;
+ DATA_BLOB password = data_blob_null;
+ enum samPwdChangeReason reject_reason = SAM_PWD_CHANGE_NO_ERROR;
+ struct samr_DomInfo1 *dominfo = NULL;
+ char *target_principal_string = NULL;
+ bool is_service_principal = false;
+ NTSTATUS status;
+ bool ok;
+
+ code = decode_ChangePasswdDataMS(decoded_data->data,
+ decoded_data->length,
+ &chpw,
+ &chpw_len);
+ if (code != 0) {
+ DBG_WARNING("decode_ChangePasswdDataMS failed\n");
+ ok = kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "Failed to decode packet",
+ kpasswd_reply);
+ if (!ok) {
+ *error_string = "Failed to create reply";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+ return 0;
+ }
+
+ ok = convert_string_talloc_handle(mem_ctx,
+ lpcfg_iconv_handle(kdc->task->lp_ctx),
+ CH_UTF8,
+ CH_UTF16,
+ chpw.newpasswd.data,
+ chpw.newpasswd.length,
+ &password.data,
+ &password.length);
+ if (!ok) {
+ free_ChangePasswdDataMS(&chpw);
+ DBG_WARNING("String conversion failed\n");
+ *error_string = "String conversion failed";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+
+ if ((chpw.targname != NULL && chpw.targrealm == NULL) ||
+ (chpw.targname == NULL && chpw.targrealm != NULL)) {
+ free_ChangePasswdDataMS(&chpw);
+ ok = kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "Realm and principal must be "
+ "both present, or neither present",
+ kpasswd_reply);
+ if (!ok) {
+ *error_string = "Failed to create reply";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+ return 0;
+ }
+
+ if (chpw.targname == NULL || chpw.targrealm == NULL) {
+ free_ChangePasswdDataMS(&chpw);
+ return kpasswd_change_password(kdc,
+ mem_ctx,
+ gensec_security,
+ session_info,
+ &password,
+ kpasswd_reply,
+ error_string);
+ }
+ code = krb5_build_principal_ext(context,
+ &target_principal,
+ strlen(*chpw.targrealm),
+ *chpw.targrealm,
+ 0);
+ if (code != 0) {
+ free_ChangePasswdDataMS(&chpw);
+ return kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "Failed to parse principal",
+ kpasswd_reply);
+ }
+ code = copy_PrincipalName(chpw.targname,
+ &target_principal->name);
+ free_ChangePasswdDataMS(&chpw);
+ if (code != 0) {
+ krb5_free_principal(context, target_principal);
+ return kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "Failed to parse principal",
+ kpasswd_reply);
+ }
+
+ if (target_principal->name.name_string.len >= 2) {
+ is_service_principal = true;
+
+ code = krb5_unparse_name_short(context,
+ target_principal,
+ &target_principal_string);
+ } else {
+ code = krb5_unparse_name(context,
+ target_principal,
+ &target_principal_string);
+ }
+ krb5_free_principal(context, target_principal);
+ if (code != 0) {
+ ok = kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "Failed to parse principal",
+ kpasswd_reply);
+ if (!ok) {
+ *error_string = "Failed to create reply";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+ }
+
+ status = kpasswd_samdb_set_password(mem_ctx,
+ kdc->task->event_ctx,
+ kdc->task->lp_ctx,
+ session_info,
+ is_service_principal,
+ target_principal_string,
+ &password,
+ &reject_reason,
+ &dominfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("kpasswd_samdb_set_password failed - %s\n",
+ nt_errstr(status));
+ }
+
+ ok = kpasswd_make_pwchange_reply(mem_ctx,
+ status,
+ reject_reason,
+ dominfo,
+ kpasswd_reply);
+ if (!ok) {
+ *error_string = "Failed to create reply";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+
+ return 0;
+}
+
+krb5_error_code kpasswd_handle_request(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ struct gensec_security *gensec_security,
+ uint16_t verno,
+ DATA_BLOB *decoded_data,
+ DATA_BLOB *kpasswd_reply,
+ const char **error_string)
+{
+ struct auth_session_info *session_info;
+ NTSTATUS status;
+ krb5_error_code code;
+
+ status = gensec_session_info(gensec_security,
+ mem_ctx,
+ &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ *error_string = talloc_asprintf(mem_ctx,
+ "gensec_session_info failed - %s",
+ nt_errstr(status));
+ return KRB5_KPASSWD_HARDERROR;
+ }
+
+ /*
+ * Since the kpasswd service shares its keys with the krbtgt, we might
+ * have received a TGT rather than a kpasswd ticket. We need to check
+ * the ticket type to ensure that TGTs cannot be misused in this manner.
+ */
+ code = kpasswd_check_non_tgt(session_info,
+ error_string);
+ if (code != 0) {
+ DBG_WARNING("%s\n", *error_string);
+ return code;
+ }
+
+ switch(verno) {
+ case KRB5_KPASSWD_VERS_CHANGEPW: {
+ DATA_BLOB password = data_blob_null;
+ bool ok;
+
+ ok = convert_string_talloc_handle(mem_ctx,
+ lpcfg_iconv_handle(kdc->task->lp_ctx),
+ CH_UTF8,
+ CH_UTF16,
+ decoded_data->data,
+ decoded_data->length,
+ &password.data,
+ &password.length);
+ if (!ok) {
+ *error_string = "String conversion failed!";
+ DBG_WARNING("%s\n", *error_string);
+ return KRB5_KPASSWD_HARDERROR;
+ }
+
+ return kpasswd_change_password(kdc,
+ mem_ctx,
+ gensec_security,
+ session_info,
+ &password,
+ kpasswd_reply,
+ error_string);
+ }
+ case KRB5_KPASSWD_VERS_SETPW: {
+ return kpasswd_set_password(kdc,
+ mem_ctx,
+ gensec_security,
+ session_info,
+ decoded_data,
+ kpasswd_reply,
+ error_string);
+ }
+ default:
+ *error_string = talloc_asprintf(mem_ctx,
+ "Protocol version %u not supported",
+ verno);
+ return KRB5_KPASSWD_BAD_VERSION;
+ }
+
+ return 0;
+}
diff --git a/source4/kdc/kpasswd-service-mit.c b/source4/kdc/kpasswd-service-mit.c
new file mode 100644
index 0000000..4c7e871
--- /dev/null
+++ b/source4/kdc/kpasswd-service-mit.c
@@ -0,0 +1,402 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Samba kpasswd implementation
+
+ Copyright (c) 2016 Andreas Schneider <asn@samba.org>
+
+ 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 "includes.h"
+#include "samba/service_task.h"
+#include "param/param.h"
+#include "auth/auth.h"
+#include "auth/gensec/gensec.h"
+#include "gensec_krb5_helpers.h"
+#include "kdc/kdc-server.h"
+#include "kdc/kpasswd_glue.h"
+#include "kdc/kpasswd-service.h"
+#include "kdc/kpasswd-helper.h"
+#include "../lib/util/asn1.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+#define RFC3244_VERSION 0xff80
+
+krb5_error_code decode_krb5_setpw_req(const krb5_data *code,
+ krb5_data **password_out,
+ krb5_principal *target_out);
+
+/*
+ * A fallback for when MIT refuses to parse a setpw structure without the
+ * (optional) target principal and realm
+ */
+static bool decode_krb5_setpw_req_simple(TALLOC_CTX *mem_ctx,
+ const DATA_BLOB *decoded_data,
+ DATA_BLOB *clear_data)
+{
+ struct asn1_data *asn1 = NULL;
+ bool ret;
+
+ asn1 = asn1_init(mem_ctx, 3);
+ if (asn1 == NULL) {
+ return false;
+ }
+
+ ret = asn1_load(asn1, *decoded_data);
+ if (!ret) {
+ goto out;
+ }
+
+ ret = asn1_start_tag(asn1, ASN1_SEQUENCE(0));
+ if (!ret) {
+ goto out;
+ }
+ ret = asn1_start_tag(asn1, ASN1_CONTEXT(0));
+ if (!ret) {
+ goto out;
+ }
+ ret = asn1_read_OctetString(asn1, mem_ctx, clear_data);
+ if (!ret) {
+ goto out;
+ }
+
+ ret = asn1_end_tag(asn1);
+ if (!ret) {
+ goto out;
+ }
+ ret = asn1_end_tag(asn1);
+
+out:
+ asn1_free(asn1);
+
+ return ret;
+}
+
+static krb5_error_code kpasswd_change_password(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ const struct gensec_security *gensec_security,
+ struct auth_session_info *session_info,
+ DATA_BLOB *password,
+ DATA_BLOB *kpasswd_reply,
+ const char **error_string)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ enum samPwdChangeReason reject_reason;
+ const char *reject_string = NULL;
+ struct samr_DomInfo1 *dominfo;
+ bool ok;
+ int ret;
+
+ /*
+ * We're doing a password change (rather than a password set), so check
+ * that we were given an initial ticket.
+ */
+ ret = gensec_krb5_initial_ticket(gensec_security);
+ if (ret != 1) {
+ *error_string = "Expected an initial ticket";
+ return KRB5_KPASSWD_INITIAL_FLAG_NEEDED;
+ }
+
+ status = samdb_kpasswd_change_password(mem_ctx,
+ kdc->task->lp_ctx,
+ kdc->task->event_ctx,
+ session_info,
+ password,
+ &reject_reason,
+ &dominfo,
+ &reject_string,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ ok = kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_ACCESSDENIED,
+ reject_string,
+ kpasswd_reply);
+ if (!ok) {
+ *error_string = "Failed to create reply";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+ /* We want to send an an authenticated packet. */
+ return 0;
+ }
+
+ ok = kpasswd_make_pwchange_reply(mem_ctx,
+ result,
+ reject_reason,
+ dominfo,
+ kpasswd_reply);
+ if (!ok) {
+ *error_string = "Failed to create reply";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+
+ return 0;
+}
+
+static krb5_error_code kpasswd_set_password(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ const struct gensec_security *gensec_security,
+ struct auth_session_info *session_info,
+ DATA_BLOB *decoded_data,
+ DATA_BLOB *kpasswd_reply,
+ const char **error_string)
+{
+ krb5_context context = kdc->smb_krb5_context->krb5_context;
+ DATA_BLOB clear_data;
+ krb5_data k_dec_data;
+ krb5_data *k_clear_data = NULL;
+ krb5_principal target_principal = NULL;
+ krb5_error_code code;
+ DATA_BLOB password;
+ char *target_realm = NULL;
+ char *target_name = NULL;
+ char *target_principal_string = NULL;
+ bool is_service_principal = false;
+ bool ok;
+ size_t num_components;
+ enum samPwdChangeReason reject_reason = SAM_PWD_CHANGE_NO_ERROR;
+ struct samr_DomInfo1 *dominfo = NULL;
+ NTSTATUS status;
+
+ k_dec_data = smb_krb5_data_from_blob(*decoded_data);
+
+ code = decode_krb5_setpw_req(&k_dec_data,
+ &k_clear_data,
+ &target_principal);
+ if (code == 0) {
+ clear_data.data = (uint8_t *)k_clear_data->data;
+ clear_data.length = k_clear_data->length;
+ } else {
+ target_principal = NULL;
+
+ /*
+ * The MIT decode failed, so fall back to trying the simple
+ * case, without target_principal.
+ */
+ ok = decode_krb5_setpw_req_simple(mem_ctx,
+ decoded_data,
+ &clear_data);
+ if (!ok) {
+ DBG_WARNING("decode_krb5_setpw_req failed: %s\n",
+ error_message(code));
+ ok = kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "Failed to decode packet",
+ kpasswd_reply);
+ if (!ok) {
+ *error_string = "Failed to create reply";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+ return 0;
+ }
+ }
+
+ ok = convert_string_talloc_handle(mem_ctx,
+ lpcfg_iconv_handle(kdc->task->lp_ctx),
+ CH_UTF8,
+ CH_UTF16,
+ clear_data.data,
+ clear_data.length,
+ &password.data,
+ &password.length);
+ if (k_clear_data != NULL) {
+ krb5_free_data(context, k_clear_data);
+ }
+ if (!ok) {
+ DBG_WARNING("String conversion failed\n");
+ *error_string = "String conversion failed";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+
+ if (target_principal != NULL) {
+ target_realm = smb_krb5_principal_get_realm(
+ mem_ctx, context, target_principal);
+ code = krb5_unparse_name_flags(context,
+ target_principal,
+ KRB5_PRINCIPAL_UNPARSE_NO_REALM,
+ &target_name);
+ if (code != 0) {
+ DBG_WARNING("Failed to parse principal\n");
+ *error_string = "String conversion failed";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+ }
+
+ if ((target_name != NULL && target_realm == NULL) ||
+ (target_name == NULL && target_realm != NULL)) {
+ krb5_free_principal(context, target_principal);
+ TALLOC_FREE(target_realm);
+ SAFE_FREE(target_name);
+
+ ok = kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "Realm and principal must be "
+ "both present, or neither "
+ "present",
+ kpasswd_reply);
+ if (!ok) {
+ *error_string = "Failed to create reply";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+ return 0;
+ }
+
+ if (target_name != NULL && target_realm != NULL) {
+ TALLOC_FREE(target_realm);
+ SAFE_FREE(target_name);
+ } else {
+ krb5_free_principal(context, target_principal);
+ TALLOC_FREE(target_realm);
+ SAFE_FREE(target_name);
+
+ return kpasswd_change_password(kdc,
+ mem_ctx,
+ gensec_security,
+ session_info,
+ &password,
+ kpasswd_reply,
+ error_string);
+ }
+
+ num_components = krb5_princ_size(context, target_principal);
+ if (num_components >= 2) {
+ is_service_principal = true;
+ code = krb5_unparse_name_flags(context,
+ target_principal,
+ KRB5_PRINCIPAL_UNPARSE_SHORT,
+ &target_principal_string);
+ } else {
+ code = krb5_unparse_name(context,
+ target_principal,
+ &target_principal_string);
+ }
+ krb5_free_principal(context, target_principal);
+ if (code != 0) {
+ ok = kpasswd_make_error_reply(mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "Failed to parse principal",
+ kpasswd_reply);
+ if (!ok) {
+ *error_string = "Failed to create reply";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+ }
+
+ status = kpasswd_samdb_set_password(mem_ctx,
+ kdc->task->event_ctx,
+ kdc->task->lp_ctx,
+ session_info,
+ is_service_principal,
+ target_principal_string,
+ &password,
+ &reject_reason,
+ &dominfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("kpasswd_samdb_set_password failed - %s\n",
+ nt_errstr(status));
+ }
+
+ ok = kpasswd_make_pwchange_reply(mem_ctx,
+ status,
+ reject_reason,
+ dominfo,
+ kpasswd_reply);
+ if (!ok) {
+ *error_string = "Failed to create reply";
+ return KRB5_KPASSWD_HARDERROR;
+ }
+
+ return 0;
+}
+
+krb5_error_code kpasswd_handle_request(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ struct gensec_security *gensec_security,
+ uint16_t verno,
+ DATA_BLOB *decoded_data,
+ DATA_BLOB *kpasswd_reply,
+ const char **error_string)
+{
+ struct auth_session_info *session_info;
+ NTSTATUS status;
+ krb5_error_code code;
+
+ status = gensec_session_info(gensec_security,
+ mem_ctx,
+ &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ *error_string = talloc_asprintf(mem_ctx,
+ "gensec_session_info failed - "
+ "%s",
+ nt_errstr(status));
+ return KRB5_KPASSWD_HARDERROR;
+ }
+
+ /*
+ * Since the kpasswd service shares its keys with the krbtgt, we might
+ * have received a TGT rather than a kpasswd ticket. We need to check
+ * the ticket type to ensure that TGTs cannot be misused in this manner.
+ */
+ code = kpasswd_check_non_tgt(session_info,
+ error_string);
+ if (code != 0) {
+ DBG_WARNING("%s\n", *error_string);
+ return code;
+ }
+
+ switch(verno) {
+ case 1: {
+ DATA_BLOB password;
+ bool ok;
+
+ ok = convert_string_talloc_handle(mem_ctx,
+ lpcfg_iconv_handle(kdc->task->lp_ctx),
+ CH_UTF8,
+ CH_UTF16,
+ decoded_data->data,
+ decoded_data->length,
+ &password.data,
+ &password.length);
+ if (!ok) {
+ *error_string = "String conversion failed!";
+ DBG_WARNING("%s\n", *error_string);
+ return KRB5_KPASSWD_HARDERROR;
+ }
+
+ return kpasswd_change_password(kdc,
+ mem_ctx,
+ gensec_security,
+ session_info,
+ &password,
+ kpasswd_reply,
+ error_string);
+ }
+ case RFC3244_VERSION: {
+ return kpasswd_set_password(kdc,
+ mem_ctx,
+ gensec_security,
+ session_info,
+ decoded_data,
+ kpasswd_reply,
+ error_string);
+ }
+ default:
+ return KRB5_KPASSWD_BAD_VERSION;
+ }
+
+ return 0;
+}
diff --git a/source4/kdc/kpasswd-service.c b/source4/kdc/kpasswd-service.c
new file mode 100644
index 0000000..c671eb4
--- /dev/null
+++ b/source4/kdc/kpasswd-service.c
@@ -0,0 +1,395 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Samba kpasswd implementation
+
+ Copyright (c) 2005 Andrew Bartlett <abartlet@samba.org>
+ Copyright (c) 2016 Andreas Schneider <asn@samba.org>
+
+ 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 "includes.h"
+#include "samba/service_task.h"
+#include "tsocket/tsocket.h"
+#include "auth/credentials/credentials.h"
+#include "auth/auth.h"
+#include "auth/gensec/gensec.h"
+#include "kdc/kdc-server.h"
+#include "kdc/kpasswd-service.h"
+#include "kdc/kpasswd-helper.h"
+#include "param/param.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+#define HEADER_LEN 6
+#ifndef RFC3244_VERSION
+#define RFC3244_VERSION 0xff80
+#endif
+
+kdc_code kpasswd_process(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *request,
+ DATA_BLOB *reply,
+ struct tsocket_address *remote_addr,
+ struct tsocket_address *local_addr,
+ int datagram)
+{
+ uint16_t len;
+ uint16_t verno;
+ uint16_t ap_req_len;
+ uint16_t enc_data_len;
+ DATA_BLOB ap_req_blob = data_blob_null;
+ DATA_BLOB ap_rep_blob = data_blob_null;
+ DATA_BLOB enc_data_blob = data_blob_null;
+ DATA_BLOB dec_data_blob = data_blob_null;
+ DATA_BLOB kpasswd_dec_reply = data_blob_null;
+ const char *error_string = NULL;
+ krb5_error_code error_code = 0;
+ struct cli_credentials *server_credentials;
+ struct gensec_security *gensec_security;
+#ifndef SAMBA4_USES_HEIMDAL
+ struct sockaddr_storage remote_ss;
+#endif
+ struct sockaddr_storage local_ss;
+ ssize_t socklen;
+ TALLOC_CTX *tmp_ctx;
+ kdc_code rc = KDC_ERROR;
+ krb5_error_code code = 0;
+ NTSTATUS status;
+ int rv;
+ bool is_inet;
+ bool ok;
+
+ if (kdc->am_rodc) {
+ return KDC_PROXY_REQUEST;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return KDC_ERROR;
+ }
+
+ is_inet = tsocket_address_is_inet(remote_addr, "ip");
+ if (!is_inet) {
+ DBG_WARNING("Invalid remote IP address\n");
+ goto done;
+ }
+
+#ifndef SAMBA4_USES_HEIMDAL
+ /*
+ * FIXME: Heimdal fails to to do a krb5_rd_req() in gensec_krb5 if we
+ * set the remote address.
+ */
+
+ /* remote_addr */
+ socklen = tsocket_address_bsd_sockaddr(remote_addr,
+ (struct sockaddr *)&remote_ss,
+ sizeof(struct sockaddr_storage));
+ if (socklen < 0) {
+ DBG_WARNING("Invalid remote IP address\n");
+ goto done;
+ }
+#endif
+
+ /* local_addr */
+ socklen = tsocket_address_bsd_sockaddr(local_addr,
+ (struct sockaddr *)&local_ss,
+ sizeof(struct sockaddr_storage));
+ if (socklen < 0) {
+ DBG_WARNING("Invalid local IP address\n");
+ goto done;
+ }
+
+ if (request->length <= HEADER_LEN) {
+ DBG_WARNING("Request truncated\n");
+ goto done;
+ }
+
+ len = RSVAL(request->data, 0);
+ if (request->length != len) {
+ DBG_WARNING("Request length does not match\n");
+ goto done;
+ }
+
+ verno = RSVAL(request->data, 2);
+ if (verno != 1 && verno != RFC3244_VERSION) {
+ DBG_WARNING("Unsupported version: 0x%04x\n", verno);
+ }
+
+ ap_req_len = RSVAL(request->data, 4);
+ if ((ap_req_len >= len) || ((ap_req_len + HEADER_LEN) >= len)) {
+ DBG_WARNING("AP_REQ truncated\n");
+ goto done;
+ }
+
+ ap_req_blob = data_blob_const(&request->data[HEADER_LEN], ap_req_len);
+
+ enc_data_len = len - ap_req_len;
+ enc_data_blob = data_blob_const(&request->data[HEADER_LEN + ap_req_len],
+ enc_data_len);
+
+ server_credentials = cli_credentials_init(tmp_ctx);
+ if (server_credentials == NULL) {
+ DBG_ERR("Failed to initialize server credentials!\n");
+ goto done;
+ }
+
+ /*
+ * We want the credentials subsystem to use the krb5 context we already
+ * have, rather than a new context.
+ *
+ * On this context the KDB plugin has been loaded, so we can access
+ * dsdb.
+ */
+ status = cli_credentials_set_krb5_context(server_credentials,
+ kdc->smb_krb5_context);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ ok = cli_credentials_set_conf(server_credentials, kdc->task->lp_ctx);
+ if (!ok) {
+ goto done;
+ }
+
+ /*
+ * After calling cli_credentials_set_conf(), explicitly set the realm
+ * with CRED_SPECIFIED. We need to do this so the result of
+ * principal_from_credentials() called from the gensec layer is
+ * CRED_SPECIFIED rather than CRED_SMB_CONF, avoiding a fallback to
+ * match-by-key (very undesirable in this case).
+ */
+ ok = cli_credentials_set_realm(server_credentials,
+ lpcfg_realm(kdc->task->lp_ctx),
+ CRED_SPECIFIED);
+ if (!ok) {
+ goto done;
+ }
+
+ ok = cli_credentials_set_username(server_credentials,
+ "kadmin/changepw",
+ CRED_SPECIFIED);
+ if (!ok) {
+ goto done;
+ }
+
+ /* Check that the server principal is indeed CRED_SPECIFIED. */
+ {
+ char *principal = NULL;
+ enum credentials_obtained obtained;
+
+ principal = cli_credentials_get_principal_and_obtained(server_credentials,
+ tmp_ctx,
+ &obtained);
+ if (obtained < CRED_SPECIFIED) {
+ goto done;
+ }
+
+ TALLOC_FREE(principal);
+ }
+
+ rv = cli_credentials_set_keytab_name(server_credentials,
+ kdc->task->lp_ctx,
+ kdc->kpasswd_keytab_name,
+ CRED_SPECIFIED);
+ if (rv != 0) {
+ DBG_ERR("Failed to set credentials keytab name\n");
+ goto done;
+ }
+
+ status = samba_server_gensec_start(tmp_ctx,
+ kdc->task->event_ctx,
+ kdc->task->msg_ctx,
+ kdc->task->lp_ctx,
+ server_credentials,
+ "kpasswd",
+ &gensec_security);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = gensec_set_local_address(gensec_security, local_addr);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+#ifndef SAMBA4_USES_HEIMDAL
+ status = gensec_set_remote_address(gensec_security, remote_addr);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+#endif
+
+ /* We want the GENSEC wrap calls to generate PRIV tokens */
+ gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL);
+
+ /* Use the krb5 gesec mechanism so we can load DB modules */
+ status = gensec_start_mech_by_name(gensec_security, "krb5");
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /*
+ * Accept the AP-REQ and generate the AP-REP we need for the reply
+ *
+ * We only allow KRB5 and make sure the backend to is RPC/IPC free.
+ *
+ * See gensec_krb5_update_internal() as GENSEC_SERVER.
+ *
+ * It allows gensec_update() not to block.
+ *
+ * If that changes in future we need to use
+ * gensec_update_send/recv here!
+ */
+ status = gensec_update(gensec_security, tmp_ctx,
+ ap_req_blob, &ap_rep_blob);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ ap_rep_blob = data_blob_null;
+ error_code = KRB5_KPASSWD_HARDERROR;
+ error_string = talloc_asprintf(tmp_ctx,
+ "gensec_update failed - %s\n",
+ nt_errstr(status));
+ DBG_ERR("%s", error_string);
+ goto reply;
+ }
+
+ status = gensec_unwrap(gensec_security,
+ tmp_ctx,
+ &enc_data_blob,
+ &dec_data_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ ap_rep_blob = data_blob_null;
+ error_code = KRB5_KPASSWD_HARDERROR;
+ error_string = talloc_asprintf(tmp_ctx,
+ "gensec_unwrap failed - %s\n",
+ nt_errstr(status));
+ DBG_ERR("%s", error_string);
+ goto reply;
+ }
+
+ code = kpasswd_handle_request(kdc,
+ tmp_ctx,
+ gensec_security,
+ verno,
+ &dec_data_blob,
+ &kpasswd_dec_reply,
+ &error_string);
+ if (code != 0) {
+ ap_rep_blob = data_blob_null;
+ error_code = code;
+ goto reply;
+ }
+
+ status = gensec_wrap(gensec_security,
+ tmp_ctx,
+ &kpasswd_dec_reply,
+ &enc_data_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ ap_rep_blob = data_blob_null;
+ error_code = KRB5_KPASSWD_HARDERROR;
+ error_string = talloc_asprintf(tmp_ctx,
+ "gensec_wrap failed - %s\n",
+ nt_errstr(status));
+ DBG_ERR("%s", error_string);
+ goto reply;
+ }
+
+reply:
+ if (error_code != 0) {
+ krb5_data k_enc_data;
+ krb5_data k_dec_data;
+ const char *principal_string;
+ krb5_principal server_principal;
+
+ if (error_string == NULL) {
+ DBG_ERR("Invalid error string! This should not happen\n");
+ goto done;
+ }
+
+ ok = kpasswd_make_error_reply(tmp_ctx,
+ error_code,
+ error_string,
+ &dec_data_blob);
+ if (!ok) {
+ DBG_ERR("Failed to create error reply\n");
+ goto done;
+ }
+
+ k_dec_data = smb_krb5_data_from_blob(dec_data_blob);
+
+ principal_string = cli_credentials_get_principal(server_credentials,
+ tmp_ctx);
+ if (principal_string == NULL) {
+ goto done;
+ }
+
+ code = smb_krb5_parse_name(kdc->smb_krb5_context->krb5_context,
+ principal_string,
+ &server_principal);
+ if (code != 0) {
+ DBG_ERR("Failed to create principal: %s\n",
+ error_message(code));
+ goto done;
+ }
+
+ code = smb_krb5_mk_error(kdc->smb_krb5_context->krb5_context,
+ KRB5KDC_ERR_NONE + error_code,
+ NULL, /* e_text */
+ &k_dec_data,
+ NULL, /* client */
+ server_principal,
+ &k_enc_data);
+ krb5_free_principal(kdc->smb_krb5_context->krb5_context,
+ server_principal);
+ if (code != 0) {
+ DBG_ERR("Failed to create krb5 error reply: %s\n",
+ error_message(code));
+ goto done;
+ }
+
+ enc_data_blob = data_blob_talloc(tmp_ctx,
+ k_enc_data.data,
+ k_enc_data.length);
+ if (enc_data_blob.data == NULL) {
+ DBG_ERR("Failed to allocate memory for error reply\n");
+ goto done;
+ }
+ }
+
+ *reply = data_blob_talloc(mem_ctx,
+ NULL,
+ HEADER_LEN + ap_rep_blob.length + enc_data_blob.length);
+ if (reply->data == NULL) {
+ goto done;
+ }
+ RSSVAL(reply->data, 0, reply->length);
+ RSSVAL(reply->data, 2, 1);
+ RSSVAL(reply->data, 4, ap_rep_blob.length);
+ if (ap_rep_blob.data != NULL) {
+ memcpy(reply->data + HEADER_LEN,
+ ap_rep_blob.data,
+ ap_rep_blob.length);
+ }
+ memcpy(reply->data + HEADER_LEN + ap_rep_blob.length,
+ enc_data_blob.data,
+ enc_data_blob.length);
+
+ rc = KDC_OK;
+done:
+ talloc_free(tmp_ctx);
+ return rc;
+}
diff --git a/source4/kdc/kpasswd-service.h b/source4/kdc/kpasswd-service.h
new file mode 100644
index 0000000..845d680
--- /dev/null
+++ b/source4/kdc/kpasswd-service.h
@@ -0,0 +1,43 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Samba kpasswd implementation
+
+ Copyright (c) 2016 Andreas Schneider <asn@samba.org>
+
+ 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/>.
+*/
+
+#ifndef _KPASSWD_SERVICE_H
+#define _KPASSWD_SERVICE_H
+
+struct gensec_security;
+
+krb5_error_code kpasswd_handle_request(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ struct gensec_security *gensec_security,
+ uint16_t verno,
+ DATA_BLOB *decoded_data,
+ DATA_BLOB *kpasswd_reply,
+ const char **error_string);
+
+kdc_code kpasswd_process(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *request,
+ DATA_BLOB *reply,
+ struct tsocket_address *remote_addr,
+ struct tsocket_address *local_addr,
+ int datagram);
+
+#endif /* _KPASSWD_SERVICE_H */
diff --git a/source4/kdc/kpasswd_glue.c b/source4/kdc/kpasswd_glue.c
new file mode 100644
index 0000000..df7668f
--- /dev/null
+++ b/source4/kdc/kpasswd_glue.c
@@ -0,0 +1,88 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ kpasswd Server implementation
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Andrew Tridgell 2005
+
+ 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 "includes.h"
+#include "dsdb/samdb/samdb.h"
+#include "../lib/util/util_ldb.h"
+#include "libcli/security/security.h"
+#include "dsdb/common/util.h"
+#include "auth/auth.h"
+#include "kdc/kpasswd_glue.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+/*
+ A user password change
+
+ Return true if there is a valid error packet (or success) formed in
+ the error_blob
+*/
+NTSTATUS samdb_kpasswd_change_password(TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx,
+ struct tevent_context *event_ctx,
+ struct auth_session_info *session_info,
+ const DATA_BLOB *password,
+ enum samPwdChangeReason *reject_reason,
+ struct samr_DomInfo1 **dominfo,
+ const char **error_string,
+ NTSTATUS *result)
+{
+ NTSTATUS status;
+ struct ldb_context *samdb = NULL;
+
+ /* Start a SAM with user privileges for the password change */
+ samdb = samdb_connect(mem_ctx,
+ event_ctx,
+ lp_ctx,
+ session_info,
+ NULL,
+ 0);
+ if (!samdb) {
+ *error_string = "Failed to open samdb";
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ DBG_NOTICE("Changing password of %s\\%s (%s)\n",
+ session_info->info->domain_name,
+ session_info->info->account_name,
+ dom_sid_string(mem_ctx, &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]));
+
+ /* Performs the password change */
+ status = samdb_set_password_sid(samdb,
+ mem_ctx,
+ &session_info->security_token->sids[PRIMARY_USER_SID_INDEX],
+ NULL,
+ password,
+ NULL,
+ DSDB_PASSWORD_CHECKED_AND_CORRECT,
+ reject_reason,
+ dominfo);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ *error_string = "No such user when changing password";
+ } else if (!NT_STATUS_IS_OK(status)) {
+ *error_string = nt_errstr(status);
+ }
+ *result = status;
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/kdc/kpasswd_glue.h b/source4/kdc/kpasswd_glue.h
new file mode 100644
index 0000000..49246af
--- /dev/null
+++ b/source4/kdc/kpasswd_glue.h
@@ -0,0 +1,31 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ kpasswd Server implementation
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Andrew Tridgell 2005
+
+ 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/>.
+*/
+
+NTSTATUS samdb_kpasswd_change_password(TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx,
+ struct tevent_context *event_ctx,
+ struct auth_session_info *session_info,
+ const DATA_BLOB *password,
+ enum samPwdChangeReason *reject_reason,
+ struct samr_DomInfo1 **dominfo,
+ const char **error_string,
+ NTSTATUS *result);
diff --git a/source4/kdc/ktutil.c b/source4/kdc/ktutil.c
new file mode 100644
index 0000000..d29f4fd
--- /dev/null
+++ b/source4/kdc/ktutil.c
@@ -0,0 +1,122 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Minimal ktutil for selftest
+
+ Copyright (C) Ralph Boehme <slow@samba.org> 2016
+
+ 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 "includes.h"
+#include "krb5_wrap/krb5_samba.h"
+
+static void smb_krb5_err(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ int exit_code,
+ krb5_error_code code,
+ const char *msg)
+{
+ char *krb5_err_str = smb_get_krb5_error_message(context,
+ code,
+ mem_ctx);
+ printf("%s: %s\n", msg, krb5_err_str ? krb5_err_str : "UNKNOWN");
+
+ talloc_free(mem_ctx);
+ exit(exit_code);
+}
+
+int main (int argc, char **argv)
+{
+ TALLOC_CTX *mem_ctx = talloc_init("ktutil");
+ krb5_context context;
+ krb5_keytab keytab;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry entry;
+ krb5_error_code ret;
+ char *keytab_name = NULL;
+
+ if (mem_ctx == NULL) {
+ printf("talloc_init() failed\n");
+ exit(1);
+ }
+
+ if (argc != 2) {
+ printf("Usage: %s KEYTAB\n", argv[0]);
+ exit(1);
+ }
+
+ keytab_name = argv[1];
+
+ ret = smb_krb5_init_context_common(&context);
+ if (ret) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(ret));
+ smb_krb5_err(mem_ctx, context, 1, ret, "krb5_context");
+ }
+
+ ret = smb_krb5_kt_open_relative(context, keytab_name, false, &keytab);
+ if (ret) {
+ smb_krb5_err(mem_ctx, context, 1, ret, "open keytab");
+ }
+
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret) {
+ smb_krb5_err(mem_ctx, context, 1, ret, "krb5_kt_start_seq_get");
+ }
+
+ for (ret = krb5_kt_next_entry(context, keytab, &entry, &cursor);
+ ret == 0;
+ ret = krb5_kt_next_entry(context, keytab, &entry, &cursor))
+ {
+ char *principal = NULL;
+ char *enctype_str = NULL;
+ krb5_enctype enctype = smb_krb5_kt_get_enctype_from_entry(&entry);
+
+ ret = smb_krb5_unparse_name(mem_ctx,
+ context,
+ entry.principal,
+ &principal);
+ if (ret) {
+ smb_krb5_err(mem_ctx, context, 1, ret, "krb5_enctype_to_string");
+ }
+
+ ret = smb_krb5_enctype_to_string(context,
+ enctype,
+ &enctype_str);
+ if (ret) {
+ printf("%s (%d)\n", principal, (int)enctype);
+ } else {
+ printf("%s (%s)\n", principal, enctype_str);
+ }
+
+ TALLOC_FREE(principal);
+ SAFE_FREE(enctype_str);
+ smb_krb5_kt_free_entry(context, &entry);
+ }
+
+ ret = krb5_kt_end_seq_get(context, keytab, &cursor);
+ if (ret) {
+ smb_krb5_err(mem_ctx, context, 1, ret, "krb5_kt_end_seq_get");
+ }
+
+ ret = krb5_kt_close(context, keytab);
+ if (ret) {
+ smb_krb5_err(mem_ctx, context, 1, ret, "krb5_kt_close");
+ }
+
+ krb5_free_context(context);
+ talloc_free(mem_ctx);
+ return 0;
+}
diff --git a/source4/kdc/mit-kdb/kdb_samba.c b/source4/kdc/mit-kdb/kdb_samba.c
new file mode 100644
index 0000000..e8fcfc8
--- /dev/null
+++ b/source4/kdc/mit-kdb/kdb_samba.c
@@ -0,0 +1,178 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Samba KDB plugin for MIT Kerberos
+
+ Copyright (c) 2010 Simo Sorce <idra@samba.org>.
+ Copyright (c) 2014 Andreas Schneider <asn@samba.org>
+
+ 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 "includes.h"
+
+#include "system/kerberos.h"
+
+#include <profile.h>
+#include <kdb.h>
+
+#include "kdc/samba_kdc.h"
+#include "kdc/mit_samba.h"
+#include "kdb_samba.h"
+
+static krb5_error_code kdb_samba_init_library(void)
+{
+ return 0;
+}
+
+static krb5_error_code kdb_samba_fini_library(void)
+{
+ return 0;
+}
+
+static krb5_error_code kdb_samba_init_module(krb5_context context,
+ char *conf_section,
+ char **db_args,
+ int mode)
+{
+ /* TODO mit_samba_context_init */
+ struct mit_samba_context *mit_ctx;
+ krb5_error_code code;
+ int rc;
+
+ rc = mit_samba_context_init(&mit_ctx);
+ if (rc != 0) {
+ return ENOMEM;
+ }
+
+
+ code = krb5_db_set_context(context, mit_ctx);
+
+ return code;
+}
+static krb5_error_code kdb_samba_fini_module(krb5_context context)
+{
+ struct mit_samba_context *mit_ctx;
+
+ mit_ctx = ks_get_context(context);
+ if (mit_ctx == NULL) {
+ return 0;
+ }
+
+ mit_samba_context_free(mit_ctx);
+
+ return 0;
+}
+
+static krb5_error_code kdb_samba_db_create(krb5_context context,
+ char *conf_section,
+ char **db_args)
+{
+ /* NOTE: used only by kadmin */
+ return KRB5_KDB_DBTYPE_NOSUP;
+}
+
+static krb5_error_code kdb_samba_db_destroy(krb5_context context,
+ char *conf_section,
+ char **db_args)
+{
+ /* NOTE: used only by kadmin */
+ return KRB5_KDB_DBTYPE_NOSUP;
+}
+
+static krb5_error_code kdb_samba_db_get_age(krb5_context context,
+ char *db_name,
+ time_t *age)
+{
+ /* TODO: returns last modification time of the db */
+
+ /* NOTE: used by and affects only lookaside cache,
+ * defer implementation until needed as samba doesn't keep this
+ * specific value readily available and it would require a full
+ * database search to get it. */
+
+ *age = time(NULL);
+
+ return 0;
+}
+
+static krb5_error_code kdb_samba_db_lock(krb5_context context, int kmode)
+{
+
+ /* NOTE: important only for kadmin */
+ /* NOTE: deferred as samba's DB cannot be easily locked and doesn't
+ * really make sense to do so anyway as the db is shared and support
+ * transactions */
+ return 0;
+}
+
+static krb5_error_code kdb_samba_db_unlock(krb5_context context)
+{
+
+ /* NOTE: important only for kadmin */
+ /* NOTE: deferred as samba's DB cannot be easily locked and doesn't
+ * really make sense to do so anyway as the db is shared and support
+ * transactions */
+ return 0;
+}
+
+static void kdb_samba_db_free_principal_e_data(krb5_context context,
+ krb5_octet *e_data)
+{
+ struct samba_kdc_entry *skdc_entry;
+
+ skdc_entry = talloc_get_type_abort(e_data,
+ struct samba_kdc_entry);
+ skdc_entry->kdc_entry = NULL;
+ TALLOC_FREE(skdc_entry);
+}
+
+kdb_vftabl kdb_function_table = {
+ .maj_ver = KRB5_KDB_DAL_MAJOR_VERSION,
+ .min_ver = 0,
+
+ .init_library = kdb_samba_init_library,
+ .fini_library = kdb_samba_fini_library,
+ .init_module = kdb_samba_init_module,
+ .fini_module = kdb_samba_fini_module,
+
+ .create = kdb_samba_db_create,
+ .destroy = kdb_samba_db_destroy,
+ .get_age = kdb_samba_db_get_age,
+ .lock = kdb_samba_db_lock,
+ .unlock = kdb_samba_db_unlock,
+
+ .get_principal = kdb_samba_db_get_principal,
+ .put_principal = kdb_samba_db_put_principal,
+ .delete_principal = kdb_samba_db_delete_principal,
+
+ .iterate = kdb_samba_db_iterate,
+
+ .fetch_master_key = kdb_samba_fetch_master_key,
+ .fetch_master_key_list = kdb_samba_fetch_master_key_list,
+
+ .change_pwd = kdb_samba_change_pwd,
+
+ .decrypt_key_data = kdb_samba_dbekd_decrypt_key_data,
+ .encrypt_key_data = kdb_samba_dbekd_encrypt_key_data,
+
+ .check_policy_as = kdb_samba_db_check_policy_as,
+ .audit_as_req = kdb_samba_db_audit_as_req,
+ .check_allowed_to_delegate = kdb_samba_db_check_allowed_to_delegate,
+
+ .free_principal_e_data = kdb_samba_db_free_principal_e_data,
+
+ .allowed_to_delegate_from = kdb_samba_db_allowed_to_delegate_from,
+ .issue_pac = kdb_samba_db_issue_pac,
+};
diff --git a/source4/kdc/mit-kdb/kdb_samba.h b/source4/kdc/mit-kdb/kdb_samba.h
new file mode 100644
index 0000000..acd5a77
--- /dev/null
+++ b/source4/kdc/mit-kdb/kdb_samba.h
@@ -0,0 +1,182 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Samba KDB plugin for MIT Kerberos
+
+ Copyright (c) 2009 Simo Sorce <idra@samba.org>.
+
+ 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/>.
+*/
+
+#ifndef _KDB_SAMBA_H_
+#define _KDB_SAMBA_H_
+
+#include <stdbool.h>
+
+#include <krb5/krb5.h>
+#include <krb5/plugin.h>
+
+#define PAC_LOGON_INFO 1
+
+#ifndef discard_const_p
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+# define discard_const_p(type, ptr) ((type *)((intptr_t)(ptr)))
+#else
+# define discard_const_p(type, ptr) ((type *)(ptr))
+#endif
+#endif
+
+/* from kdb_samba_common.c */
+
+struct mit_samba_context *ks_get_context(krb5_context kcontext);
+
+krb5_error_code ks_get_principal(krb5_context context,
+ krb5_const_principal principal,
+ unsigned int kflags,
+ krb5_db_entry **kentry);
+
+void ks_free_principal(krb5_context context, krb5_db_entry *entry);
+
+bool ks_data_eq_string(krb5_data d, const char *s);
+
+krb5_boolean ks_is_kadmin(krb5_context context,
+ krb5_const_principal princ);
+
+krb5_boolean ks_is_kadmin_history(krb5_context context,
+ krb5_const_principal princ);
+
+krb5_boolean ks_is_kadmin_changepw(krb5_context context,
+ krb5_const_principal princ);
+
+krb5_boolean ks_is_kadmin_admin(krb5_context context,
+ krb5_const_principal princ);
+
+/* from kdb_samba_principals.c */
+
+krb5_error_code kdb_samba_db_get_principal(krb5_context context,
+ krb5_const_principal princ,
+ unsigned int kflags,
+ krb5_db_entry **kentry);
+
+krb5_error_code kdb_samba_db_put_principal(krb5_context context,
+ krb5_db_entry *entry,
+ char **db_args);
+
+krb5_error_code kdb_samba_db_delete_principal(krb5_context context,
+ krb5_const_principal princ);
+
+krb5_error_code kdb_samba_db_iterate(krb5_context context,
+ char *match_entry,
+ int (*func)(krb5_pointer, krb5_db_entry *),
+ krb5_pointer func_arg,
+ krb5_flags iterflags);
+
+/* from kdb_samba_masterkey.c */
+
+krb5_error_code kdb_samba_fetch_master_key(krb5_context context,
+ krb5_principal name,
+ krb5_keyblock *key,
+ krb5_kvno *kvno,
+ char *db_args);
+
+krb5_error_code kdb_samba_fetch_master_key_list(krb5_context context,
+ krb5_principal mname,
+ const krb5_keyblock *key,
+ krb5_keylist_node **mkeys_list);
+
+/* from kdb_samba_pac.c */
+
+krb5_error_code kdb_samba_dbekd_decrypt_key_data(krb5_context context,
+ const krb5_keyblock *mkey,
+ const krb5_key_data *key_data,
+ krb5_keyblock *kkey,
+ krb5_keysalt *keysalt);
+
+krb5_error_code kdb_samba_dbekd_encrypt_key_data(krb5_context context,
+ const krb5_keyblock *mkey,
+ const krb5_keyblock *kkey,
+ const krb5_keysalt *keysalt,
+ int keyver,
+ krb5_key_data *key_data);
+
+/* from kdb_samba_policies.c */
+krb5_error_code kdb_samba_db_issue_pac(krb5_context context,
+ unsigned int flags,
+ krb5_db_entry *client,
+ krb5_keyblock *replaced_reply_key,
+ krb5_db_entry *server,
+ krb5_db_entry *signing_krbtgt,
+ krb5_timestamp authtime,
+ krb5_pac old_pac,
+ krb5_pac new_pac,
+ krb5_data ***auth_indicators);
+
+krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context,
+ unsigned int flags,
+ krb5_const_principal client_princ,
+ krb5_const_principal server_princ,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_db_entry *krbtgt,
+ krb5_db_entry *local_krbtgt,
+ krb5_keyblock *client_key,
+ krb5_keyblock *server_key,
+ krb5_keyblock *krbtgt_key,
+ krb5_keyblock *local_krbtgt_key,
+ krb5_keyblock *session_key,
+ krb5_timestamp authtime,
+ krb5_authdata **tgt_auth_data,
+ void *authdata_info,
+ krb5_data ***auth_indicators,
+ krb5_authdata ***signed_auth_data);
+
+krb5_error_code kdb_samba_db_check_policy_as(krb5_context context,
+ krb5_kdc_req *kdcreq,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_timestamp kdc_time,
+ const char **status,
+ krb5_pa_data ***e_data_out);
+
+krb5_error_code kdb_samba_db_check_allowed_to_delegate(krb5_context context,
+ krb5_const_principal client,
+ const krb5_db_entry *server,
+ krb5_const_principal proxy);
+
+krb5_error_code kdb_samba_db_allowed_to_delegate_from(
+ krb5_context context,
+ krb5_const_principal client,
+ krb5_const_principal server,
+ krb5_pac header_pac,
+ const krb5_db_entry *proxy);
+
+void kdb_samba_db_audit_as_req(krb5_context kcontext,
+ krb5_kdc_req *request,
+ const krb5_address *local_addr,
+ const krb5_address *remote_addr,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_timestamp authtime,
+ krb5_error_code error_code);
+
+/* from kdb_samba_change_pwd.c */
+
+krb5_error_code kdb_samba_change_pwd(krb5_context context,
+ krb5_keyblock *master_key,
+ krb5_key_salt_tuple *ks_tuple,
+ int ks_tuple_count, char *passwd,
+ int new_kvno, krb5_boolean keepold,
+ krb5_db_entry *db_entry);
+
+#endif /* _KDB_SAMBA_H_ */
diff --git a/source4/kdc/mit-kdb/kdb_samba_change_pwd.c b/source4/kdc/mit-kdb/kdb_samba_change_pwd.c
new file mode 100644
index 0000000..ad7bb5d
--- /dev/null
+++ b/source4/kdc/mit-kdb/kdb_samba_change_pwd.c
@@ -0,0 +1,59 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Samba KDB plugin for MIT Kerberos
+
+ Copyright (c) 2010 Simo Sorce <idra@samba.org>.
+ Copyright (c) 2014 Andreas Schneider <asn@samba.org>
+
+ 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 "includes.h"
+
+#include "system/kerberos.h"
+
+#include <profile.h>
+#include <kdb.h>
+
+#include "kdc/mit_samba.h"
+#include "kdb_samba.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+krb5_error_code kdb_samba_change_pwd(krb5_context context,
+ krb5_keyblock *master_key,
+ krb5_key_salt_tuple *ks_tuple,
+ int ks_tuple_count, char *passwd,
+ int new_kvno, krb5_boolean keepold,
+ krb5_db_entry *db_entry)
+{
+ struct mit_samba_context *mit_ctx;
+ krb5_error_code code;
+
+ mit_ctx = ks_get_context(context);
+ if (mit_ctx == NULL) {
+ return KRB5_KDB_DBNOTINITED;
+ }
+
+ code = mit_samba_kpasswd_change_password(mit_ctx, passwd, db_entry);
+ if (code != 0) {
+ goto cleanup;
+ }
+
+cleanup:
+
+ return code;
+}
diff --git a/source4/kdc/mit-kdb/kdb_samba_common.c b/source4/kdc/mit-kdb/kdb_samba_common.c
new file mode 100644
index 0000000..5ba845e
--- /dev/null
+++ b/source4/kdc/mit-kdb/kdb_samba_common.c
@@ -0,0 +1,103 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Samba KDB plugin for MIT Kerberos
+
+ Copyright (c) 2010 Simo Sorce <idra@samba.org>.
+ Copyright (c) 2014 Andreas Schneider <asn@samba.org>
+
+ 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 "includes.h"
+
+#include "system/kerberos.h"
+
+#include <profile.h>
+#include <kdb.h>
+
+#include "kdc/mit_samba.h"
+#include "kdb_samba.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+struct mit_samba_context *ks_get_context(krb5_context kcontext)
+{
+ struct mit_samba_context *mit_ctx = NULL;
+ void *db_ctx = NULL;
+ krb5_error_code code;
+
+ code = krb5_db_get_context(kcontext, &db_ctx);
+ if (code != 0) {
+ return NULL;
+ }
+
+ mit_ctx = talloc_get_type_abort(db_ctx, struct mit_samba_context);
+
+ /*
+ * This is nomrally the starting point for Kerberos operations in
+ * MIT KRB5, so reset errno to 0 for possible com_err debug messages.
+ */
+ errno = 0;
+
+ return mit_ctx;
+}
+
+bool ks_data_eq_string(krb5_data d, const char *s)
+{
+ int rc;
+
+ if (d.length != strlen(s) || d.length == 0) {
+ return false;
+ }
+
+ rc = memcmp(d.data, s, d.length);
+ if (rc != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+krb5_boolean ks_is_kadmin(krb5_context context,
+ krb5_const_principal princ)
+{
+ return krb5_princ_size(context, princ) >= 1 &&
+ ks_data_eq_string(princ->data[0], "kadmin");
+}
+
+krb5_boolean ks_is_kadmin_history(krb5_context context,
+ krb5_const_principal princ)
+{
+ return krb5_princ_size(context, princ) == 2 &&
+ ks_data_eq_string(princ->data[0], "kadmin") &&
+ ks_data_eq_string(princ->data[1], "history");
+}
+
+krb5_boolean ks_is_kadmin_changepw(krb5_context context,
+ krb5_const_principal princ)
+{
+ return krb5_princ_size(context, princ) == 2 &&
+ ks_data_eq_string(princ->data[0], "kadmin") &&
+ ks_data_eq_string(princ->data[1], "changepw");
+}
+
+krb5_boolean ks_is_kadmin_admin(krb5_context context,
+ krb5_const_principal princ)
+{
+ return krb5_princ_size(context, princ) == 2 &&
+ ks_data_eq_string(princ->data[0], "kadmin") &&
+ ks_data_eq_string(princ->data[1], "admin");
+}
diff --git a/source4/kdc/mit-kdb/kdb_samba_masterkey.c b/source4/kdc/mit-kdb/kdb_samba_masterkey.c
new file mode 100644
index 0000000..b068d96
--- /dev/null
+++ b/source4/kdc/mit-kdb/kdb_samba_masterkey.c
@@ -0,0 +1,69 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Samba KDB plugin for MIT Kerberos
+
+ Copyright (c) 2010 Simo Sorce <idra@samba.org>.
+ Copyright (c) 2014 Andreas Schneider <asn@samba.org>
+
+ 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 "includes.h"
+
+#include "system/kerberos.h"
+
+#include <profile.h>
+#include <kdb.h>
+
+#include "kdc/mit_samba.h"
+#include "kdb_samba.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+krb5_error_code kdb_samba_fetch_master_key(krb5_context context,
+ krb5_principal name,
+ krb5_keyblock *key,
+ krb5_kvno *kvno,
+ char *db_args)
+{
+ return 0;
+}
+
+krb5_error_code kdb_samba_fetch_master_key_list(krb5_context context,
+ krb5_principal mname,
+ const krb5_keyblock *key,
+ krb5_keylist_node **mkeys_list)
+{
+ krb5_keylist_node *mkey;
+
+ /*
+ * NOTE: samba does not support master keys
+ * so just return a dummy key
+ */
+ mkey = calloc(1, sizeof(krb5_keylist_node));
+ if (mkey == NULL) {
+ return ENOMEM;
+ }
+
+ mkey->keyblock.magic = KV5M_KEYBLOCK;
+ mkey->keyblock.enctype = ENCTYPE_UNKNOWN;
+ mkey->kvno = 1;
+
+ *mkeys_list = mkey;
+
+ return 0;
+}
diff --git a/source4/kdc/mit-kdb/kdb_samba_pac.c b/source4/kdc/mit-kdb/kdb_samba_pac.c
new file mode 100644
index 0000000..75b05a6
--- /dev/null
+++ b/source4/kdc/mit-kdb/kdb_samba_pac.c
@@ -0,0 +1,115 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Samba KDB plugin for MIT Kerberos
+
+ Copyright (c) 2010 Simo Sorce <idra@samba.org>.
+ Copyright (c) 2014 Andreas Schneider <asn@samba.org>
+
+ 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 "includes.h"
+
+#include "system/kerberos.h"
+
+#include <profile.h>
+#include <kdb.h>
+
+#include "kdc/mit_samba.h"
+#include "kdb_samba.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+krb5_error_code kdb_samba_dbekd_decrypt_key_data(krb5_context context,
+ const krb5_keyblock *mkey,
+ const krb5_key_data *key_data,
+ krb5_keyblock *kkey,
+ krb5_keysalt *keysalt)
+{
+ /*
+ * NOTE: Samba doesn't use a master key, so we will just copy
+ * the contents around untouched.
+ */
+ ZERO_STRUCTP(kkey);
+
+ kkey->magic = KV5M_KEYBLOCK;
+ kkey->enctype = key_data->key_data_type[0];
+ kkey->contents = malloc(key_data->key_data_length[0]);
+ if (kkey->contents == NULL) {
+ return ENOMEM;
+ }
+ memcpy(kkey->contents,
+ key_data->key_data_contents[0],
+ key_data->key_data_length[0]);
+ kkey->length = key_data->key_data_length[0];
+
+ if (keysalt != NULL) {
+ keysalt->type = key_data->key_data_type[1];
+ keysalt->data.data = malloc(key_data->key_data_length[1]);
+ if (keysalt->data.data == NULL) {
+ free(kkey->contents);
+ return ENOMEM;
+ }
+ memcpy(keysalt->data.data,
+ key_data->key_data_contents[1],
+ key_data->key_data_length[1]);
+ keysalt->data.length = key_data->key_data_length[1];
+ }
+
+ return 0;
+}
+
+krb5_error_code kdb_samba_dbekd_encrypt_key_data(krb5_context context,
+ const krb5_keyblock *mkey,
+ const krb5_keyblock *kkey,
+ const krb5_keysalt *keysalt,
+ int keyver,
+ krb5_key_data *key_data)
+{
+ /*
+ * NOTE: samba doesn't use a master key, so we will just copy
+ * the contents around untouched.
+ */
+
+ ZERO_STRUCTP(key_data);
+
+ key_data->key_data_ver = KRB5_KDB_V1_KEY_DATA_ARRAY;
+ key_data->key_data_kvno = keyver;
+ key_data->key_data_type[0] = kkey->enctype;
+ key_data->key_data_contents[0] = malloc(kkey->length);
+ if (key_data->key_data_contents[0] == NULL) {
+ return ENOMEM;
+ }
+ memcpy(key_data->key_data_contents[0],
+ kkey->contents,
+ kkey->length);
+ key_data->key_data_length[0] = kkey->length;
+
+ if (keysalt != NULL) {
+ key_data->key_data_type[1] = keysalt->type;
+ key_data->key_data_contents[1] = malloc(keysalt->data.length);
+ if (key_data->key_data_contents[1] == NULL) {
+ free(key_data->key_data_contents[0]);
+ return ENOMEM;
+ }
+ memcpy(key_data->key_data_contents[1],
+ keysalt->data.data,
+ keysalt->data.length);
+ key_data->key_data_length[1] = keysalt->data.length;
+ }
+
+ return 0;
+}
diff --git a/source4/kdc/mit-kdb/kdb_samba_policies.c b/source4/kdc/mit-kdb/kdb_samba_policies.c
new file mode 100644
index 0000000..56bd0dd
--- /dev/null
+++ b/source4/kdc/mit-kdb/kdb_samba_policies.c
@@ -0,0 +1,397 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Samba KDB plugin for MIT Kerberos
+
+ Copyright (c) 2010 Simo Sorce <idra@samba.org>.
+ Copyright (c) 2014-2021 Andreas Schneider <asn@samba.org>
+
+ 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/replace/system/kerberos.h"
+#include "lib/util/data_blob.h"
+#include "lib/util/debug.h"
+#include "lib/util/fault.h"
+#include "lib/util/memory.h"
+#include "libcli/util/ntstatus.h"
+#include "lib/krb5_wrap/krb5_samba.h"
+
+#include <profile.h>
+#include <kdb.h>
+
+#include "kdc/mit_samba.h"
+#include "kdb_samba.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+/* FIXME: This is a krb5 function which is exported, but in no header */
+extern krb5_error_code decode_krb5_padata_sequence(const krb5_data *output,
+ krb5_pa_data ***rep);
+
+static krb5_error_code ks_get_netbios_name(krb5_address **addrs, char **name)
+{
+ char *nb_name = NULL;
+ int len, i;
+
+ for (i = 0; addrs[i]; i++) {
+ if (addrs[i]->addrtype != ADDRTYPE_NETBIOS) {
+ continue;
+ }
+ len = MIN(addrs[i]->length, 15);
+ nb_name = strndup((const char *)addrs[i]->contents, len);
+ if (!nb_name) {
+ return ENOMEM;
+ }
+ break;
+ }
+
+ if (nb_name) {
+ /* Strip space padding */
+ i = strlen(nb_name) - 1;
+ for (i = strlen(nb_name) - 1;
+ i > 0 && nb_name[i] == ' ';
+ i--) {
+ nb_name[i] = '\0';
+ }
+ }
+
+ *name = nb_name;
+
+ return 0;
+}
+
+krb5_error_code kdb_samba_db_check_policy_as(krb5_context context,
+ krb5_kdc_req *kdcreq,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_timestamp kdc_time,
+ const char **status,
+ krb5_pa_data ***e_data_out)
+{
+ struct mit_samba_context *mit_ctx;
+ krb5_error_code code;
+ char *client_name = NULL;
+ char *server_name = NULL;
+ char *netbios_name = NULL;
+ char *realm = NULL;
+ bool password_change = false;
+ krb5_const_principal client_princ;
+ DATA_BLOB int_data = { NULL, 0 };
+ krb5_data d;
+ krb5_pa_data **e_data;
+
+ mit_ctx = ks_get_context(context);
+ if (mit_ctx == NULL) {
+ return KRB5_KDB_DBNOTINITED;
+ }
+
+ /* Prefer canonicalised name from client entry */
+ client_princ = client ? client->princ : kdcreq->client;
+
+ if (client_princ == NULL || ks_is_kadmin(context, client_princ)) {
+ return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ }
+
+ if (krb5_princ_size(context, kdcreq->server) == 2 &&
+ ks_is_kadmin_changepw(context, kdcreq->server)) {
+ code = krb5_get_default_realm(context, &realm);
+ if (code) {
+ goto done;
+ }
+
+ if (ks_data_eq_string(kdcreq->server->realm, realm)) {
+ password_change = true;
+ }
+ }
+
+ code = krb5_unparse_name(context, kdcreq->server, &server_name);
+ if (code) {
+ goto done;
+ }
+
+ code = krb5_unparse_name(context, client_princ, &client_name);
+ if (code) {
+ goto done;
+ }
+
+ if (kdcreq->addresses) {
+ code = ks_get_netbios_name(kdcreq->addresses, &netbios_name);
+ if (code) {
+ goto done;
+ }
+ }
+
+ code = mit_samba_check_client_access(mit_ctx,
+ client,
+ client_name,
+ server,
+ server_name,
+ netbios_name,
+ password_change,
+ &int_data);
+
+ if (int_data.length && int_data.data) {
+
+ /* make sure the mapped return code is returned - gd */
+ int code_tmp;
+
+ d = smb_krb5_data_from_blob(int_data);
+
+ code_tmp = decode_krb5_padata_sequence(&d, &e_data);
+ if (code_tmp == 0) {
+ *e_data_out = e_data;
+ }
+ }
+done:
+ free(realm);
+ free(server_name);
+ free(client_name);
+ free(netbios_name);
+
+ return code;
+}
+
+static krb5_error_code ks_get_pac(krb5_context context,
+ uint32_t flags,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_keyblock *client_key,
+ krb5_pac *pac)
+{
+ struct mit_samba_context *mit_ctx;
+ krb5_error_code code;
+
+ mit_ctx = ks_get_context(context);
+ if (mit_ctx == NULL) {
+ return KRB5_KDB_DBNOTINITED;
+ }
+
+ code = mit_samba_get_pac(mit_ctx,
+ context,
+ flags,
+ client,
+ server,
+ client_key,
+ pac);
+ if (code != 0) {
+ return code;
+ }
+
+ return code;
+}
+
+static krb5_error_code ks_update_pac(krb5_context context,
+ int flags,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_db_entry *signing_krbtgt,
+ krb5_pac old_pac,
+ krb5_pac new_pac)
+{
+ struct mit_samba_context *mit_ctx = NULL;
+ krb5_error_code code;
+
+ mit_ctx = ks_get_context(context);
+ if (mit_ctx == NULL) {
+ return KRB5_KDB_DBNOTINITED;
+ }
+
+ code = mit_samba_update_pac(mit_ctx,
+ context,
+ flags,
+ client,
+ server,
+ signing_krbtgt,
+ old_pac,
+ new_pac);
+ if (code != 0) {
+ return code;
+ }
+
+ return code;
+}
+
+krb5_error_code kdb_samba_db_issue_pac(krb5_context context,
+ unsigned int flags,
+ krb5_db_entry *client,
+ krb5_keyblock *replaced_reply_key,
+ krb5_db_entry *server,
+ krb5_db_entry *signing_krbtgt,
+ krb5_timestamp authtime,
+ krb5_pac old_pac,
+ krb5_pac new_pac,
+ krb5_data ***auth_indicators)
+{
+ char *client_name = NULL;
+ char *server_name = NULL;
+ krb5_error_code code = EINVAL;
+
+ /* The KDC handles both signing and verification for us. */
+
+ if (client != NULL) {
+ code = krb5_unparse_name(context,
+ client->princ,
+ &client_name);
+ if (code != 0) {
+ return code;
+ }
+ }
+
+ if (server != NULL) {
+ code = krb5_unparse_name(context,
+ server->princ,
+ &server_name);
+ if (code != 0) {
+ SAFE_FREE(client_name);
+ return code;
+ }
+ }
+
+ /*
+ * Get a new PAC for AS-REQ or S4U2Self for our realm.
+ *
+ * For a simple cross-realm S4U2Proxy there will be the following TGS
+ * requests after the client realm is identified:
+ *
+ * 1. server@SREALM to SREALM for krbtgt/CREALM@SREALM -- a regular TGS
+ * request with server's normal TGT and no S4U2Self padata.
+ * 2. server@SREALM to CREALM for server@SREALM (expressed as an
+ * enterprise principal), with the TGT from #1 as header ticket and
+ * S4U2Self padata identifying the client.
+ * 3. server@SREALM to SREALM for server@SREALM with S4U2Self padata,
+ * with the referral TGT from #2 as header ticket
+ *
+ * In request 2 the PROTOCOL_TRANSITION and CROSS_REALM flags are set,
+ * and the request is for a local client (so client != NULL) and we
+ * want to make a new PAC.
+ *
+ * In request 3 the PROTOCOL_TRANSITION and CROSS_REALM flags are also
+ * set, but the request is for a non-local client (so client == NULL)
+ * and we want to copy the subject PAC contained in the referral TGT.
+ */
+ if (old_pac == NULL ||
+ (client != NULL && (flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION))) {
+ DBG_NOTICE("Generate PAC for AS-REQ [client=%s, flags=%#08x]\n",
+ client_name != NULL ? client_name : "<unknown>",
+ flags);
+
+ code = ks_get_pac(context,
+ flags,
+ client,
+ server,
+ replaced_reply_key,
+ &new_pac);
+ } else {
+ DBG_NOTICE("Update PAC for TGS-REQ [client=%s, server=%s, "
+ "flags=%#08x]\n",
+ client_name != NULL ? client_name : "<unknown>",
+ server_name != NULL ? server_name : "<unknown>",
+ flags);
+
+ code = ks_update_pac(context,
+ flags,
+ client,
+ server,
+ signing_krbtgt,
+ old_pac,
+ new_pac);
+ }
+ SAFE_FREE(client_name);
+ SAFE_FREE(server_name);
+
+ return code;
+}
+
+krb5_error_code kdb_samba_db_check_allowed_to_delegate(krb5_context context,
+ krb5_const_principal client,
+ const krb5_db_entry *server,
+ krb5_const_principal proxy)
+{
+ struct mit_samba_context *mit_ctx = NULL;
+
+ mit_ctx = ks_get_context(context);
+ if (mit_ctx == NULL) {
+ return KRB5_KDB_DBNOTINITED;
+ }
+
+ return mit_samba_check_s4u2proxy(mit_ctx,
+ server,
+ proxy);
+
+}
+
+
+krb5_error_code kdb_samba_db_allowed_to_delegate_from(
+ krb5_context context,
+ krb5_const_principal client_principal,
+ krb5_const_principal server_principal,
+ krb5_pac header_pac,
+ const krb5_db_entry *proxy)
+{
+ struct mit_samba_context *mit_ctx = NULL;
+ krb5_error_code code;
+
+ mit_ctx = ks_get_context(context);
+ if (mit_ctx == NULL) {
+ return KRB5_KDB_DBNOTINITED;
+ }
+
+ code = mit_samba_check_allowed_to_delegate_from(mit_ctx,
+ client_principal,
+ server_principal,
+ header_pac,
+ proxy);
+
+ return code;
+}
+
+
+static void samba_bad_password_count(krb5_db_entry *client,
+ krb5_error_code error_code)
+{
+ switch (error_code) {
+ case 0:
+ mit_samba_zero_bad_password_count(client);
+ break;
+ case KRB5KDC_ERR_PREAUTH_FAILED:
+ case KRB5KRB_AP_ERR_BAD_INTEGRITY:
+ mit_samba_update_bad_password_count(client);
+ break;
+ }
+}
+
+void kdb_samba_db_audit_as_req(krb5_context context,
+ krb5_kdc_req *request,
+ const krb5_address *local_addr,
+ const krb5_address *remote_addr,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_timestamp authtime,
+ krb5_error_code error_code)
+{
+ /*
+ * FIXME: This segfaulted with a FAST test
+ * FIND_FAST: <unknown client> for <unknown server>, Unknown FAST armor type 0
+ */
+ if (client == NULL) {
+ return;
+ }
+
+ samba_bad_password_count(client, error_code);
+
+ /* TODO: perform proper audit logging for addresses */
+}
diff --git a/source4/kdc/mit-kdb/kdb_samba_principals.c b/source4/kdc/mit-kdb/kdb_samba_principals.c
new file mode 100644
index 0000000..2726018
--- /dev/null
+++ b/source4/kdc/mit-kdb/kdb_samba_principals.c
@@ -0,0 +1,397 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Samba KDB plugin for MIT Kerberos
+
+ Copyright (c) 2010 Simo Sorce <idra@samba.org>.
+ Copyright (c) 2014 Andreas Schneider <asn@samba.org>
+
+ 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 "includes.h"
+
+#include "system/kerberos.h"
+
+#include <profile.h>
+#include <kdb.h>
+
+#include "kdc/samba_kdc.h"
+#include "kdc/mit_samba.h"
+#include "kdb_samba.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+#define ADMIN_LIFETIME 60*60*3 /* 3 hours */
+
+krb5_error_code ks_get_principal(krb5_context context,
+ krb5_const_principal principal,
+ unsigned int kflags,
+ krb5_db_entry **kentry)
+{
+ struct mit_samba_context *mit_ctx;
+ krb5_error_code code;
+
+ mit_ctx = ks_get_context(context);
+ if (mit_ctx == NULL) {
+ return KRB5_KDB_DBNOTINITED;
+ }
+
+ code = mit_samba_get_principal(mit_ctx,
+ principal,
+ kflags,
+ kentry);
+ if (code != 0) {
+ goto cleanup;
+ }
+
+cleanup:
+
+ return code;
+}
+
+static void ks_free_principal_e_data(krb5_context context, krb5_octet *e_data)
+{
+ struct samba_kdc_entry *skdc_entry;
+
+ skdc_entry = talloc_get_type_abort(e_data,
+ struct samba_kdc_entry);
+ skdc_entry->kdc_entry = NULL;
+ TALLOC_FREE(skdc_entry);
+}
+
+void ks_free_principal(krb5_context context, krb5_db_entry *entry)
+{
+ krb5_tl_data *tl_data_next = NULL;
+ krb5_tl_data *tl_data = NULL;
+ size_t i, j;
+
+ if (entry != NULL) {
+ krb5_free_principal(context, entry->princ);
+
+ for (tl_data = entry->tl_data; tl_data; tl_data = tl_data_next) {
+ tl_data_next = tl_data->tl_data_next;
+ if (tl_data->tl_data_contents != NULL) {
+ free(tl_data->tl_data_contents);
+ }
+ free(tl_data);
+ }
+
+ if (entry->key_data != NULL) {
+ for (i = 0; i < entry->n_key_data; i++) {
+ for (j = 0; j < entry->key_data[i].key_data_ver; j++) {
+ if (entry->key_data[i].key_data_length[j] != 0) {
+ if (entry->key_data[i].key_data_contents[j] != NULL) {
+ memset(entry->key_data[i].key_data_contents[j], 0, entry->key_data[i].key_data_length[j]);
+ free(entry->key_data[i].key_data_contents[j]);
+ }
+ }
+ entry->key_data[i].key_data_contents[j] = NULL;
+ entry->key_data[i].key_data_length[j] = 0;
+ entry->key_data[i].key_data_type[j] = 0;
+ }
+ }
+ free(entry->key_data);
+ }
+
+ if (entry->e_data) {
+ ks_free_principal_e_data(context, entry->e_data);
+ }
+
+ free(entry);
+ }
+}
+
+static krb5_boolean ks_is_master_key_principal(krb5_context context,
+ krb5_const_principal princ)
+{
+ return krb5_princ_size(context, princ) == 2 &&
+ ks_data_eq_string(princ->data[0], "K") &&
+ ks_data_eq_string(princ->data[1], "M");
+}
+
+static krb5_error_code ks_get_master_key_principal(krb5_context context,
+ krb5_const_principal princ,
+ krb5_db_entry **kentry_ptr)
+{
+ krb5_error_code code;
+ krb5_key_data *key_data;
+ krb5_timestamp now;
+ krb5_db_entry *kentry;
+
+ *kentry_ptr = NULL;
+
+ kentry = calloc(1, sizeof(krb5_db_entry));
+ if (kentry == NULL) {
+ return ENOMEM;
+ }
+
+ kentry->magic = KRB5_KDB_MAGIC_NUMBER;
+ kentry->len = KRB5_KDB_V1_BASE_LENGTH;
+ kentry->attributes = KRB5_KDB_DISALLOW_ALL_TIX;
+
+ if (princ == NULL) {
+ code = krb5_parse_name(context, KRB5_KDB_M_NAME, &kentry->princ);
+ } else {
+ code = krb5_copy_principal(context, princ, &kentry->princ);
+ }
+ if (code != 0) {
+ krb5_db_free_principal(context, kentry);
+ return code;
+ }
+
+ now = time(NULL);
+
+ code = krb5_dbe_update_mod_princ_data(context, kentry, now, kentry->princ);
+ if (code != 0) {
+ krb5_db_free_principal(context, kentry);
+ return code;
+ }
+
+ /* Return a dummy key */
+ kentry->n_key_data = 1;
+ kentry->key_data = calloc(1, sizeof(krb5_key_data));
+ if (code != 0) {
+ krb5_db_free_principal(context, kentry);
+ return code;
+ }
+
+ key_data = &kentry->key_data[0];
+
+ key_data->key_data_ver = KRB5_KDB_V1_KEY_DATA_ARRAY;
+ key_data->key_data_kvno = 1;
+ key_data->key_data_type[0] = ENCTYPE_UNKNOWN;
+ if (code != 0) {
+ krb5_db_free_principal(context, kentry);
+ return code;
+ }
+
+ *kentry_ptr = kentry;
+
+ return 0;
+}
+
+static krb5_error_code ks_create_principal(krb5_context context,
+ krb5_const_principal princ,
+ int attributes,
+ int max_life,
+ const char *password,
+ krb5_db_entry **kentry_ptr)
+{
+ krb5_error_code code;
+ krb5_key_data *key_data;
+ krb5_timestamp now;
+ krb5_db_entry *kentry;
+ krb5_keyblock key;
+ krb5_data salt;
+ krb5_data pwd;
+ int enctype = ENCTYPE_AES256_CTS_HMAC_SHA1_96;
+ int sts = KRB5_KDB_SALTTYPE_SPECIAL;
+
+ if (princ == NULL) {
+ return KRB5_KDB_NOENTRY;
+ }
+
+ *kentry_ptr = NULL;
+
+ kentry = calloc(1, sizeof(krb5_db_entry));
+ if (kentry == NULL) {
+ return ENOMEM;
+ }
+
+ kentry->magic = KRB5_KDB_MAGIC_NUMBER;
+ kentry->len = KRB5_KDB_V1_BASE_LENGTH;
+
+ if (attributes > 0) {
+ kentry->attributes = attributes;
+ }
+
+ if (max_life > 0) {
+ kentry->max_life = max_life;
+ }
+
+ code = krb5_copy_principal(context, princ, &kentry->princ);
+ if (code != 0) {
+ krb5_db_free_principal(context, kentry);
+ return code;
+ }
+
+ now = time(NULL);
+
+ code = krb5_dbe_update_mod_princ_data(context, kentry, now, kentry->princ);
+ if (code != 0) {
+ krb5_db_free_principal(context, kentry);
+ return code;
+ }
+
+ code = mit_samba_generate_salt(&salt);
+ if (code != 0) {
+ krb5_db_free_principal(context, kentry);
+ return code;
+ }
+
+ if (password != NULL) {
+ pwd.data = strdup(password);
+ pwd.length = strlen(password);
+ } else {
+ /* create a random password */
+ code = mit_samba_generate_random_password(&pwd);
+ if (code != 0) {
+ krb5_db_free_principal(context, kentry);
+ return code;
+ }
+ }
+
+ code = krb5_c_string_to_key(context, enctype, &pwd, &salt, &key);
+ SAFE_FREE(pwd.data);
+ if (code != 0) {
+ krb5_db_free_principal(context, kentry);
+ return code;
+ }
+
+ kentry->n_key_data = 1;
+ kentry->key_data = calloc(1, sizeof(krb5_key_data));
+ if (code != 0) {
+ krb5_db_free_principal(context, kentry);
+ return code;
+ }
+
+ key_data = &kentry->key_data[0];
+
+ key_data->key_data_ver = KRB5_KDB_V1_KEY_DATA_ARRAY;
+ key_data->key_data_kvno = 1;
+ key_data->key_data_type[0] = key.enctype;
+ key_data->key_data_length[0] = key.length;
+ key_data->key_data_contents[0] = key.contents;
+ key_data->key_data_type[1] = sts;
+ key_data->key_data_length[1] = salt.length;
+ key_data->key_data_contents[1] = (krb5_octet*)salt.data;
+
+ *kentry_ptr = kentry;
+
+ return 0;
+}
+
+static krb5_error_code ks_get_admin_principal(krb5_context context,
+ krb5_const_principal princ,
+ krb5_db_entry **kentry_ptr)
+{
+ krb5_error_code code = EINVAL;
+
+ code = ks_create_principal(context,
+ princ,
+ KRB5_KDB_DISALLOW_TGT_BASED,
+ ADMIN_LIFETIME,
+ NULL,
+ kentry_ptr);
+
+ return code;
+}
+
+krb5_error_code kdb_samba_db_get_principal(krb5_context context,
+ krb5_const_principal princ,
+ unsigned int kflags,
+ krb5_db_entry **kentry)
+{
+ struct mit_samba_context *mit_ctx;
+ krb5_error_code code;
+
+ mit_ctx = ks_get_context(context);
+ if (mit_ctx == NULL) {
+ return KRB5_KDB_DBNOTINITED;
+ }
+
+ if (ks_is_master_key_principal(context, princ)) {
+ return ks_get_master_key_principal(context, princ, kentry);
+ }
+
+ /*
+ * Fake a kadmin/admin and kadmin/history principal so that kadmindd can
+ * start
+ */
+ if (ks_is_kadmin_admin(context, princ) ||
+ ks_is_kadmin_history(context, princ)) {
+ return ks_get_admin_principal(context, princ, kentry);
+ }
+
+ code = ks_get_principal(context, princ, kflags, kentry);
+
+ /*
+ * This restricts the changepw account so it isn't able to request a
+ * service ticket. It also marks the principal as the changepw service.
+ */
+ if (ks_is_kadmin_changepw(context, princ)) {
+ /* FIXME: shouldn't we also set KRB5_KDB_DISALLOW_TGT_BASED ?
+ * testing showed that setpw kpasswd command fails then on the
+ * server though... */
+ (*kentry)->attributes |= KRB5_KDB_PWCHANGE_SERVICE;
+ (*kentry)->max_life = CHANGEPW_LIFETIME;
+ }
+
+ return code;
+}
+
+krb5_error_code kdb_samba_db_put_principal(krb5_context context,
+ krb5_db_entry *entry,
+ char **db_args)
+{
+
+ /* NOTE: deferred, samba does not allow the KDC to store
+ * principals for now. We should not return KRB5_KDB_DB_INUSE as this
+ * would result in confusing error messages after password changes. */
+ return 0;
+}
+
+krb5_error_code kdb_samba_db_delete_principal(krb5_context context,
+ krb5_const_principal princ)
+{
+
+ /* NOTE: deferred, samba does not allow the KDC to delete
+ * principals for now */
+ return KRB5_KDB_DB_INUSE;
+}
+
+krb5_error_code kdb_samba_db_iterate(krb5_context context,
+ char *match_entry,
+ int (*func)(krb5_pointer, krb5_db_entry *),
+ krb5_pointer func_arg,
+ krb5_flags iterflags)
+{
+ struct mit_samba_context *mit_ctx;
+ krb5_db_entry *kentry = NULL;
+ krb5_error_code code;
+
+
+ mit_ctx = ks_get_context(context);
+ if (mit_ctx == NULL) {
+ return KRB5_KDB_DBNOTINITED;
+ }
+
+ code = mit_samba_get_firstkey(mit_ctx, &kentry);
+ while (code == 0) {
+ code = (*func)(func_arg, kentry);
+ if (code != 0) {
+ break;
+ }
+
+ code = mit_samba_get_nextkey(mit_ctx, &kentry);
+ }
+
+ if (code == KRB5_KDB_NOENTRY) {
+ code = 0;
+ }
+
+ return code;
+}
diff --git a/source4/kdc/mit-kdb/wscript_build b/source4/kdc/mit-kdb/wscript_build
new file mode 100644
index 0000000..82cea4a
--- /dev/null
+++ b/source4/kdc/mit-kdb/wscript_build
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+bld.SAMBA_LIBRARY('mit-kdb-samba',
+ source='''
+ kdb_samba.c
+ kdb_samba_common.c
+ kdb_samba_masterkey.c
+ kdb_samba_pac.c
+ kdb_samba_policies.c
+ kdb_samba_principals.c
+ kdb_samba_change_pwd.c
+ ''',
+ private_library=True,
+ realname='samba.so',
+ install_path='${LIBDIR}/krb5/plugins/kdb',
+ deps='''
+ MIT_SAMBA
+ com_err
+ krb5
+ kdb5
+ ''',
+ enabled=bld.CONFIG_SET('HAVE_KDB_H'))
diff --git a/source4/kdc/mit_kdc_irpc.c b/source4/kdc/mit_kdc_irpc.c
new file mode 100644
index 0000000..92fb78d
--- /dev/null
+++ b/source4/kdc/mit_kdc_irpc.c
@@ -0,0 +1,204 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (c) 2015 Andreas Schneider <asn@samba.org>
+ *
+ * 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 "includes.h"
+#include "system/kerberos.h"
+#include "source4/auth/kerberos/kerberos.h"
+#include "auth/kerberos/pac_utils.h"
+
+#include "librpc/gen_ndr/irpc.h"
+#include "lib/messaging/irpc.h"
+#include "source4/librpc/gen_ndr/ndr_irpc.h"
+#include "source4/librpc/gen_ndr/irpc.h"
+
+#include "librpc/gen_ndr/ndr_krb5pac.h"
+
+#include "source4/samba/process_model.h"
+#include "lib/param/param.h"
+
+#include "samba_kdc.h"
+#include "db-glue.h"
+#include "sdb.h"
+#include "mit_kdc_irpc.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+struct mit_kdc_irpc_context {
+ struct task_server *task;
+ krb5_context krb5_context;
+ struct samba_kdc_db_context *db_ctx;
+};
+
+static NTSTATUS netr_samlogon_generic_logon(struct irpc_message *msg,
+ struct kdc_check_generic_kerberos *r)
+{
+ struct PAC_Validate pac_validate;
+ DATA_BLOB pac_chksum;
+ struct PAC_SIGNATURE_DATA pac_kdc_sig;
+ struct mit_kdc_irpc_context *mki_ctx =
+ talloc_get_type(msg->private_data,
+ struct mit_kdc_irpc_context);
+ enum ndr_err_code ndr_err;
+ int code;
+ krb5_principal principal;
+ struct sdb_entry sentry = {};
+ struct sdb_keys skeys;
+ unsigned int i;
+ const uint8_t *d = NULL;
+
+ /* There is no reply to this request */
+ r->out.generic_reply = data_blob(NULL, 0);
+
+ ndr_err =
+ ndr_pull_struct_blob(&r->in.generic_request,
+ msg,
+ &pac_validate,
+ (ndr_pull_flags_fn_t)ndr_pull_PAC_Validate);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (pac_validate.MessageType != NETLOGON_GENERIC_KRB5_PAC_VALIDATE) {
+ /*
+ * We don't implement any other message types - such as
+ * certificate validation - yet
+ */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if ((pac_validate.ChecksumAndSignature.length !=
+ (pac_validate.ChecksumLength + pac_validate.SignatureLength)) ||
+ (pac_validate.ChecksumAndSignature.length <
+ pac_validate.ChecksumLength) ||
+ (pac_validate.ChecksumAndSignature.length <
+ pac_validate.SignatureLength)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* PAC Checksum */
+ pac_chksum = data_blob_const(pac_validate.ChecksumAndSignature.data,
+ pac_validate.ChecksumLength);
+
+ /* Create the krbtgt principal */
+ code = smb_krb5_make_principal(mki_ctx->krb5_context,
+ &principal,
+ lpcfg_realm(mki_ctx->task->lp_ctx),
+ "krbtgt",
+ lpcfg_realm(mki_ctx->task->lp_ctx),
+ NULL);
+ if (code != 0) {
+ DBG_ERR("Failed to create krbtgt@%s principal!\n",
+ lpcfg_realm(mki_ctx->task->lp_ctx));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Get the krbtgt from the DB */
+ code = samba_kdc_fetch(mki_ctx->krb5_context,
+ mki_ctx->db_ctx,
+ principal,
+ SDB_F_GET_KRBTGT | SDB_F_DECRYPT,
+ 0,
+ &sentry);
+ krb5_free_principal(mki_ctx->krb5_context, principal);
+ if (code != 0) {
+ DBG_ERR("Failed to fetch krbtgt@%s principal entry!\n",
+ lpcfg_realm(mki_ctx->task->lp_ctx));
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ /* PAC Signature */
+ pac_kdc_sig.type = pac_validate.SignatureType;
+
+ d = &pac_validate.ChecksumAndSignature.data[pac_validate.ChecksumLength];
+ pac_kdc_sig.signature =
+ data_blob_const(d, pac_validate.SignatureLength);
+
+ /*
+ * Brute force variant because MIT KRB5 doesn't provide a function like
+ * krb5_checksum_to_enctype().
+ */
+ skeys = sentry.keys;
+
+ code = EINVAL;
+ for (i = 0; i < skeys.len; i++) {
+ krb5_keyblock krbtgt_keyblock = skeys.val[i].key;
+
+ code = check_pac_checksum(pac_chksum,
+ &pac_kdc_sig,
+ mki_ctx->krb5_context,
+ &krbtgt_keyblock);
+ if (code == 0) {
+ break;
+ }
+ }
+
+ sdb_entry_free(&sentry);
+
+ if (code != 0) {
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS samba_setup_mit_kdc_irpc(struct task_server *task)
+{
+ struct samba_kdc_base_context base_ctx;
+ struct mit_kdc_irpc_context *mki_ctx;
+ NTSTATUS status;
+ int code;
+
+ mki_ctx = talloc_zero(task, struct mit_kdc_irpc_context);
+ if (mki_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ mki_ctx->task = task;
+
+ base_ctx.ev_ctx = task->event_ctx;
+ base_ctx.lp_ctx = task->lp_ctx;
+
+ /* db-glue.h */
+ status = samba_kdc_setup_db_ctx(mki_ctx,
+ &base_ctx,
+ &mki_ctx->db_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ code = smb_krb5_init_context_basic(mki_ctx,
+ task->lp_ctx,
+ &mki_ctx->krb5_context);
+ if (code != 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ status = IRPC_REGISTER(task->msg_ctx,
+ irpc,
+ KDC_CHECK_GENERIC_KERBEROS,
+ netr_samlogon_generic_logon,
+ mki_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ irpc_add_name(task->msg_ctx, "kdc_server");
+
+ return status;
+}
diff --git a/source4/kdc/mit_kdc_irpc.h b/source4/kdc/mit_kdc_irpc.h
new file mode 100644
index 0000000..943c76c
--- /dev/null
+++ b/source4/kdc/mit_kdc_irpc.h
@@ -0,0 +1,20 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (c) 2015 Andreas Schneider <asn@samba.org>
+ *
+ * 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/>.
+ */
+
+NTSTATUS samba_setup_mit_kdc_irpc(struct task_server *task);
diff --git a/source4/kdc/mit_samba.c b/source4/kdc/mit_samba.c
new file mode 100644
index 0000000..ae8895d
--- /dev/null
+++ b/source4/kdc/mit_samba.c
@@ -0,0 +1,1065 @@
+/*
+ MIT-Samba4 library
+
+ Copyright (c) 2010, Simo Sorce <idra@samba.org>
+ Copyright (c) 2014-2015 Guenther Deschner <gd@samba.org>
+ Copyright (c) 2014-2016 Andreas Schneider <asn@samba.org>
+
+ 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/>.
+ */
+
+#define TEVENT_DEPRECATED 1
+
+#include "includes.h"
+#include "param/param.h"
+#include "dsdb/samdb/samdb.h"
+#include "system/kerberos.h"
+#include "lib/replace/system/filesys.h"
+#include <com_err.h>
+#include <kdb.h>
+#include <kadm5/kadm_err.h>
+#include "kdc/sdb.h"
+#include "kdc/sdb_kdb.h"
+#include "auth/kerberos/kerberos.h"
+#include "auth/kerberos/pac_utils.h"
+#include "kdc/samba_kdc.h"
+#include "kdc/pac-glue.h"
+#include "kdc/db-glue.h"
+#include "auth/auth.h"
+#include "kdc/kpasswd_glue.h"
+#include "auth/auth_sam.h"
+
+#include "mit_samba.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+void mit_samba_context_free(struct mit_samba_context *ctx)
+{
+ /* free MIT's krb5_context */
+ if (ctx->context) {
+ krb5_free_context(ctx->context);
+ }
+
+ /* then free everything else */
+ talloc_free(ctx);
+}
+
+/*
+ * Implement a callback to log to the MIT KDC log facility
+ *
+ * http://web.mit.edu/kerberos/krb5-devel/doc/plugindev/general.html#logging-from-kdc-and-kadmind-plugin-modules
+ */
+static void mit_samba_debug(void *private_ptr, int msg_level, const char *msg)
+{
+ int is_error = errno;
+
+ if (msg_level > 0) {
+ is_error = 0;
+ }
+
+ com_err("mitkdc", is_error, "%s", msg);
+}
+
+krb5_error_code mit_samba_context_init(struct mit_samba_context **_ctx)
+{
+ NTSTATUS status;
+ struct mit_samba_context *ctx;
+ const char *s4_conf_file;
+ krb5_error_code ret;
+ struct samba_kdc_base_context base_ctx;
+
+ ctx = talloc_zero(NULL, struct mit_samba_context);
+ if (!ctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ base_ctx.ev_ctx = tevent_context_init(ctx);
+ if (!base_ctx.ev_ctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_loop_allow_nesting(base_ctx.ev_ctx);
+ base_ctx.lp_ctx = loadparm_init_global(false);
+ if (!base_ctx.lp_ctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ debug_set_callback(NULL, mit_samba_debug);
+
+ /* init s4 configuration */
+ s4_conf_file = lpcfg_configfile(base_ctx.lp_ctx);
+ if (s4_conf_file != NULL) {
+ char *p = talloc_strdup(ctx, s4_conf_file);
+ if (p == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ lpcfg_load(base_ctx.lp_ctx, p);
+ TALLOC_FREE(p);
+ } else {
+ lpcfg_load_default(base_ctx.lp_ctx);
+ }
+
+ status = samba_kdc_setup_db_ctx(ctx, &base_ctx, &ctx->db_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* init MIT's krb_context and log facilities */
+ ret = smb_krb5_init_context_basic(ctx,
+ ctx->db_ctx->lp_ctx,
+ &ctx->context);
+ if (ret) {
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret) {
+ mit_samba_context_free(ctx);
+ } else {
+ *_ctx = ctx;
+ }
+ return ret;
+}
+
+int mit_samba_generate_salt(krb5_data *salt)
+{
+ if (salt == NULL) {
+ return EINVAL;
+ }
+
+ salt->length = 16;
+ salt->data = malloc(salt->length);
+ if (salt->data == NULL) {
+ return ENOMEM;
+ }
+
+ generate_random_buffer((uint8_t *)salt->data, salt->length);
+
+ return 0;
+}
+
+int mit_samba_generate_random_password(krb5_data *pwd)
+{
+ TALLOC_CTX *tmp_ctx;
+ char *password;
+ char *data = NULL;
+ const unsigned length = 24;
+
+ if (pwd == NULL) {
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_named(NULL,
+ 0,
+ "mit_samba_generate_random_password context");
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ password = generate_random_password(tmp_ctx, length, length);
+ if (password == NULL) {
+ talloc_free(tmp_ctx);
+ return ENOMEM;
+ }
+
+ data = strdup(password);
+ talloc_free(tmp_ctx);
+ if (data == NULL) {
+ return ENOMEM;
+ }
+
+ *pwd = smb_krb5_make_data(data, length);
+
+ return 0;
+}
+
+krb5_error_code mit_samba_get_principal(struct mit_samba_context *ctx,
+ krb5_const_principal principal,
+ unsigned int kflags,
+ krb5_db_entry **_kentry)
+{
+ struct sdb_entry sentry = {};
+ krb5_db_entry *kentry;
+ krb5_error_code ret;
+ uint32_t sflags = 0;
+ krb5_principal referral_principal = NULL;
+
+ kentry = calloc(1, sizeof(krb5_db_entry));
+ if (kentry == NULL) {
+ return ENOMEM;
+ }
+
+ /*
+ * The MIT KDC code that wants the canonical name in all lookups, and
+ * takes care to canonicalize only when appropriate.
+ */
+ sflags |= SDB_F_FORCE_CANON;
+
+ if (kflags & KRB5_KDB_FLAG_REFERRAL_OK) {
+ sflags |= SDB_F_CANON;
+ }
+
+ if (kflags & KRB5_KDB_FLAG_CLIENT) {
+ sflags |= SDB_F_GET_CLIENT;
+ sflags |= SDB_F_FOR_AS_REQ;
+ } else {
+ int equal = smb_krb5_principal_is_tgs(ctx->context, principal);
+ if (equal == -1) {
+ return ENOMEM;
+ }
+
+ if (equal) {
+ sflags |= SDB_F_GET_KRBTGT;
+ } else {
+ sflags |= SDB_F_GET_SERVER;
+ sflags |= SDB_F_FOR_TGS_REQ;
+ }
+ }
+
+ /* always set this or the created_by data will not be populated by samba's
+ * backend and we will fail to parse the entry later */
+ sflags |= SDB_F_ADMIN_DATA;
+
+
+fetch_referral_principal:
+ ret = samba_kdc_fetch(ctx->context, ctx->db_ctx,
+ principal, sflags, 0, &sentry);
+ switch (ret) {
+ case 0:
+ break;
+ case SDB_ERR_NOENTRY:
+ ret = KRB5_KDB_NOENTRY;
+ goto done;
+ case SDB_ERR_WRONG_REALM: {
+ char *dest_realm = NULL;
+ const char *our_realm = lpcfg_realm(ctx->db_ctx->lp_ctx);
+
+ if (sflags & SDB_F_FOR_AS_REQ) {
+ /*
+ * If this is a request for a TGT, we are done. The KDC
+ * will return the correct error to the client.
+ */
+ ret = 0;
+ break;
+ }
+
+ if (referral_principal != NULL) {
+ sdb_entry_free(&sentry);
+ ret = KRB5_KDB_NOENTRY;
+ goto done;
+ }
+
+ /*
+ * We get a TGS request
+ *
+ * cifs/dc7.SAMBA2008R2.EXAMPLE.COM@ADDOM.SAMBA.EXAMPLE.COM
+ *
+ * to our DC for the realm
+ *
+ * ADDOM.SAMBA.EXAMPLE.COM
+ *
+ * We look up if we have an entry in the database and get an
+ * entry with the principal:
+ *
+ * cifs/dc7.SAMBA2008R2.EXAMPLE.COM@SAMBA2008R2.EXAMPLE.COM
+ *
+ * and the error: SDB_ERR_WRONG_REALM.
+ *
+ * In the case of a TGS-REQ we need to return a referral ticket
+ * for the next trust hop to the client. This ticket will have
+ * the following principal:
+ *
+ * krbtgt/SAMBA2008R2.EXAMPLE.COM@ADDOM.SAMBA.EXAMPLE.COM
+ *
+ * We just redo the lookup in the database with the referral
+ * principal and return success.
+ */
+ dest_realm = smb_krb5_principal_get_realm(
+ ctx, ctx->context, sentry.principal);
+ sdb_entry_free(&sentry);
+ if (dest_realm == NULL) {
+ ret = KRB5_KDB_NOENTRY;
+ goto done;
+ }
+
+ ret = smb_krb5_make_principal(ctx->context,
+ &referral_principal,
+ our_realm,
+ KRB5_TGS_NAME,
+ dest_realm,
+ NULL);
+ TALLOC_FREE(dest_realm);
+ if (ret != 0) {
+ goto done;
+ }
+
+ principal = referral_principal;
+ goto fetch_referral_principal;
+ }
+ case SDB_ERR_NOT_FOUND_HERE:
+ /* FIXME: RODC support */
+ default:
+ goto done;
+ }
+
+ ret = sdb_entry_to_krb5_db_entry(ctx->context, &sentry, kentry);
+
+ sdb_entry_free(&sentry);
+
+done:
+ krb5_free_principal(ctx->context, referral_principal);
+ referral_principal = NULL;
+
+ if (ret) {
+ free(kentry);
+ } else {
+ *_kentry = kentry;
+ }
+ return ret;
+}
+
+krb5_error_code mit_samba_get_firstkey(struct mit_samba_context *ctx,
+ krb5_db_entry **_kentry)
+{
+ struct sdb_entry sentry = {};
+ krb5_db_entry *kentry;
+ krb5_error_code ret;
+
+ kentry = malloc(sizeof(krb5_db_entry));
+ if (kentry == NULL) {
+ return ENOMEM;
+ }
+
+ ret = samba_kdc_firstkey(ctx->context, ctx->db_ctx, &sentry);
+ switch (ret) {
+ case 0:
+ break;
+ case SDB_ERR_NOENTRY:
+ free(kentry);
+ return KRB5_KDB_NOENTRY;
+ case SDB_ERR_NOT_FOUND_HERE:
+ /* FIXME: RODC support */
+ default:
+ free(kentry);
+ return ret;
+ }
+
+ ret = sdb_entry_to_krb5_db_entry(ctx->context, &sentry, kentry);
+
+ sdb_entry_free(&sentry);
+
+ if (ret) {
+ free(kentry);
+ } else {
+ *_kentry = kentry;
+ }
+ return ret;
+}
+
+krb5_error_code mit_samba_get_nextkey(struct mit_samba_context *ctx,
+ krb5_db_entry **_kentry)
+{
+ struct sdb_entry sentry = {};
+ krb5_db_entry *kentry;
+ krb5_error_code ret;
+
+ kentry = malloc(sizeof(krb5_db_entry));
+ if (kentry == NULL) {
+ return ENOMEM;
+ }
+
+ ret = samba_kdc_nextkey(ctx->context, ctx->db_ctx, &sentry);
+ switch (ret) {
+ case 0:
+ break;
+ case SDB_ERR_NOENTRY:
+ free(kentry);
+ return KRB5_KDB_NOENTRY;
+ case SDB_ERR_NOT_FOUND_HERE:
+ /* FIXME: RODC support */
+ default:
+ free(kentry);
+ return ret;
+ }
+
+ ret = sdb_entry_to_krb5_db_entry(ctx->context, &sentry, kentry);
+
+ sdb_entry_free(&sentry);
+
+ if (ret) {
+ free(kentry);
+ } else {
+ *_kentry = kentry;
+ }
+ return ret;
+}
+
+krb5_error_code mit_samba_get_pac(struct mit_samba_context *smb_ctx,
+ krb5_context context,
+ uint32_t flags,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_keyblock *replaced_reply_key,
+ krb5_pac *pac)
+{
+ TALLOC_CTX *tmp_ctx;
+ const struct auth_user_info_dc *user_info_dc = NULL;
+ struct auth_user_info_dc *user_info_dc_shallow_copy = NULL;
+ DATA_BLOB *logon_info_blob = NULL;
+ DATA_BLOB *upn_dns_info_blob = NULL;
+ DATA_BLOB *cred_ndr = NULL;
+ DATA_BLOB **cred_ndr_ptr = NULL;
+ DATA_BLOB cred_blob = data_blob_null;
+ DATA_BLOB *pcred_blob = NULL;
+ DATA_BLOB *pac_attrs_blob = NULL;
+ DATA_BLOB *requester_sid_blob = NULL;
+ const DATA_BLOB *client_claims_blob = NULL;
+ NTSTATUS nt_status;
+ krb5_error_code code;
+ struct samba_kdc_entry *skdc_entry;
+ struct samba_kdc_entry *server_entry = NULL;
+ bool is_krbtgt;
+ /* Only include resource groups in a service ticket. */
+ enum auth_group_inclusion group_inclusion;
+ enum samba_asserted_identity asserted_identity =
+ (flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION) ?
+ SAMBA_ASSERTED_IDENTITY_SERVICE :
+ SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY;
+
+ if (client == NULL) {
+ return EINVAL;
+ }
+ skdc_entry = talloc_get_type_abort(client->e_data,
+ struct samba_kdc_entry);
+
+ if (server == NULL) {
+ return EINVAL;
+ }
+ {
+ int result = smb_krb5_principal_is_tgs(smb_ctx->context, server->princ);
+ if (result == -1) {
+ return ENOMEM;
+ }
+
+ is_krbtgt = result;
+ }
+ server_entry = talloc_get_type_abort(server->e_data,
+ struct samba_kdc_entry);
+
+ /* Only include resource groups in a service ticket. */
+ if (is_krbtgt) {
+ group_inclusion = AUTH_EXCLUDE_RESOURCE_GROUPS;
+ } else if (server_entry->supported_enctypes & KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED) {
+ group_inclusion = AUTH_INCLUDE_RESOURCE_GROUPS;
+ } else {
+ group_inclusion = AUTH_INCLUDE_RESOURCE_GROUPS_COMPRESSED;
+ }
+
+ tmp_ctx = talloc_named(smb_ctx,
+ 0,
+ "mit_samba_get_pac context");
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ /* Check if we have a PREAUTH key */
+ if (replaced_reply_key != NULL) {
+ cred_ndr_ptr = &cred_ndr;
+ }
+
+ code = samba_kdc_get_user_info_from_db(tmp_ctx,
+ server_entry->kdc_db_ctx->samdb,
+ skdc_entry,
+ skdc_entry->msg,
+ &user_info_dc);
+ if (code) {
+ talloc_free(tmp_ctx);
+ return code;
+ }
+
+ /* Make a shallow copy of the user_info_dc structure. */
+ nt_status = authsam_shallow_copy_user_info_dc(tmp_ctx,
+ user_info_dc,
+ &user_info_dc_shallow_copy);
+ user_info_dc = NULL;
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Failed to allocate shallow copy of user_info_dc: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(tmp_ctx);
+ return map_errno_from_nt_status(nt_status);
+ }
+
+
+ nt_status = samba_kdc_add_asserted_identity(asserted_identity,
+ user_info_dc_shallow_copy);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Failed to add asserted identity: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ nt_status = samba_kdc_add_claims_valid(user_info_dc_shallow_copy);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Failed to add Claims Valid: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ /* We no longer need to modify this, so assign to const variable */
+ user_info_dc = user_info_dc_shallow_copy;
+
+ nt_status = samba_kdc_get_logon_info_blob(tmp_ctx,
+ user_info_dc,
+ group_inclusion,
+ &logon_info_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ if (cred_ndr_ptr != NULL) {
+ nt_status = samba_kdc_get_cred_ndr_blob(tmp_ctx,
+ skdc_entry,
+ cred_ndr_ptr);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+ }
+
+ nt_status = samba_kdc_get_upn_info_blob(tmp_ctx,
+ user_info_dc,
+ &upn_dns_info_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ if (is_krbtgt) {
+ nt_status = samba_kdc_get_pac_attrs_blob(tmp_ctx,
+ PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY,
+ &pac_attrs_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ nt_status = samba_kdc_get_requester_sid_blob(tmp_ctx,
+ user_info_dc,
+ &requester_sid_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+ }
+
+ nt_status = samba_kdc_get_claims_blob(tmp_ctx,
+ skdc_entry,
+ &client_claims_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ if (replaced_reply_key != NULL && cred_ndr != NULL) {
+ code = samba_kdc_encrypt_pac_credentials(context,
+ replaced_reply_key,
+ cred_ndr,
+ tmp_ctx,
+ &cred_blob);
+ if (code != 0) {
+ talloc_free(tmp_ctx);
+ return code;
+ }
+ pcred_blob = &cred_blob;
+ }
+
+ code = samba_make_krb5_pac(context,
+ logon_info_blob,
+ pcred_blob,
+ upn_dns_info_blob,
+ pac_attrs_blob,
+ requester_sid_blob,
+ NULL /* deleg_blob */,
+ client_claims_blob,
+ NULL /* device_info_blob */,
+ NULL /* device_claims_blob */,
+ *pac);
+
+ talloc_free(tmp_ctx);
+ return code;
+}
+
+krb5_error_code mit_samba_update_pac(struct mit_samba_context *ctx,
+ krb5_context context,
+ int kdc_flags,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_db_entry *krbtgt,
+ krb5_pac old_pac,
+ krb5_pac new_pac)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ krb5_error_code code;
+ struct samba_kdc_entry *client_skdc_entry = NULL;
+ struct samba_kdc_entry *server_skdc_entry = NULL;
+ struct samba_kdc_entry *krbtgt_skdc_entry = NULL;
+ struct samba_kdc_entry_pac client_pac_entry = {};
+ bool is_in_db = false;
+ bool is_trusted = false;
+ uint32_t flags = 0;
+
+ /* Create a memory context early so code can use talloc_stackframe() */
+ tmp_ctx = talloc_named(ctx, 0, "mit_samba_update_pac context");
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ if (client != NULL) {
+ client_skdc_entry =
+ talloc_get_type_abort(client->e_data,
+ struct samba_kdc_entry);
+ }
+
+ if (krbtgt == NULL) {
+ code = EINVAL;
+ goto done;
+ }
+ krbtgt_skdc_entry =
+ talloc_get_type_abort(krbtgt->e_data,
+ struct samba_kdc_entry);
+
+ if (server == NULL) {
+ code = EINVAL;
+ goto done;
+ }
+ server_skdc_entry =
+ talloc_get_type_abort(server->e_data,
+ struct samba_kdc_entry);
+
+ /*
+ * If the krbtgt was generated by an RODC, and we are not that
+ * RODC, then we need to regenerate the PAC - we can't trust
+ * it, and confirm that the RODC was permitted to print this ticket
+ *
+ * Because of the samba_kdc_validate_pac_blob() step we can be
+ * sure that the record in 'client' or 'server' matches the SID in the
+ * original PAC.
+ */
+ code = samba_krbtgt_is_in_db(krbtgt_skdc_entry,
+ &is_in_db,
+ &is_trusted);
+ if (code != 0) {
+ goto done;
+ }
+
+ if (kdc_flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION) {
+ flags |= SAMBA_KDC_FLAG_PROTOCOL_TRANSITION;
+ }
+
+ if (kdc_flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) {
+ flags |= SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION;
+ }
+
+ client_pac_entry = samba_kdc_entry_pac_from_trusted(old_pac,
+ client_skdc_entry,
+ samba_kdc_entry_is_trust(krbtgt_skdc_entry),
+ is_trusted);
+
+ code = samba_kdc_verify_pac(tmp_ctx,
+ context,
+ krbtgt_skdc_entry->kdc_db_ctx->samdb,
+ flags,
+ client_pac_entry,
+ krbtgt_skdc_entry);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = samba_kdc_update_pac(tmp_ctx,
+ context,
+ krbtgt_skdc_entry->kdc_db_ctx->samdb,
+ krbtgt_skdc_entry->kdc_db_ctx->lp_ctx,
+ flags,
+ client_pac_entry,
+ server->princ,
+ server_skdc_entry,
+ NULL /* delegated_proxy_principal */,
+ (struct samba_kdc_entry_pac) {} /* delegated_proxy */,
+ (struct samba_kdc_entry_pac) {} /* device */,
+ new_pac,
+ NULL /* server_audit_info_out */,
+ NULL /* status_out */);
+ if (code != 0) {
+ if (code == ENOATTR) {
+ /*
+ * We can't tell the KDC to not issue a PAC. It will
+ * just return the newly allocated empty PAC.
+ */
+ code = 0;
+ }
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return code;
+}
+
+/* provide header, function is exported but there are no public headers */
+
+krb5_error_code encode_krb5_padata_sequence(krb5_pa_data *const *rep, krb5_data **code);
+
+/* this function allocates 'data' using malloc.
+ * The caller is responsible for freeing it */
+static void samba_kdc_build_edata_reply(NTSTATUS nt_status, DATA_BLOB *e_data)
+{
+ krb5_error_code ret = 0;
+ krb5_pa_data pa, *ppa[2];
+ krb5_data *d = NULL;
+
+ if (!e_data)
+ return;
+
+ e_data->data = NULL;
+ e_data->length = 0;
+
+ pa.magic = KV5M_PA_DATA;
+ pa.pa_type = KRB5_PADATA_PW_SALT /* KERB_ERR_TYPE_EXTENDED */;
+ pa.length = 12;
+ pa.contents = malloc(pa.length);
+ if (!pa.contents) {
+ return;
+ }
+
+ SIVAL(pa.contents, 0, NT_STATUS_V(nt_status));
+ SIVAL(pa.contents, 4, 0);
+ SIVAL(pa.contents, 8, 1);
+
+ ppa[0] = &pa;
+ ppa[1] = NULL;
+
+ ret = encode_krb5_padata_sequence(ppa, &d);
+ free(pa.contents);
+ if (ret) {
+ return;
+ }
+
+ e_data->data = (uint8_t *)d->data;
+ e_data->length = d->length;
+
+ /* free d, not d->data - gd */
+ free(d);
+
+ return;
+}
+
+krb5_error_code mit_samba_check_client_access(struct mit_samba_context *ctx,
+ krb5_db_entry *client,
+ const char *client_name,
+ krb5_db_entry *server,
+ const char *server_name,
+ const char *netbios_name,
+ bool password_change,
+ DATA_BLOB *e_data)
+{
+ struct samba_kdc_entry *skdc_entry;
+ NTSTATUS nt_status;
+
+ skdc_entry = talloc_get_type(client->e_data, struct samba_kdc_entry);
+
+ nt_status = samba_kdc_check_client_access(skdc_entry,
+ client_name,
+ netbios_name,
+ password_change);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MEMORY)) {
+ return ENOMEM;
+ }
+
+ samba_kdc_build_edata_reply(nt_status, e_data);
+
+ return samba_kdc_map_policy_err(nt_status);
+ }
+
+ return 0;
+}
+
+krb5_error_code mit_samba_check_s4u2proxy(struct mit_samba_context *ctx,
+ const krb5_db_entry *server,
+ krb5_const_principal target_principal)
+{
+ struct samba_kdc_entry *server_skdc_entry =
+ talloc_get_type_abort(server->e_data, struct samba_kdc_entry);
+ krb5_error_code code;
+
+ code = samba_kdc_check_s4u2proxy(ctx->context,
+ ctx->db_ctx,
+ server_skdc_entry,
+ target_principal);
+
+ return code;
+}
+
+krb5_error_code mit_samba_check_allowed_to_delegate_from(
+ struct mit_samba_context *ctx,
+ krb5_const_principal client_principal,
+ krb5_const_principal server_principal,
+ krb5_pac header_pac,
+ const krb5_db_entry *proxy)
+{
+ struct samba_kdc_entry *proxy_skdc_entry =
+ talloc_get_type_abort(proxy->e_data, struct samba_kdc_entry);
+ struct auth_user_info_dc *user_info_dc = NULL;
+ TALLOC_CTX *mem_ctx = NULL;
+ krb5_error_code code;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ /*
+ * FIXME: If ever we support RODCs, we must check that the PAC has not
+ * been issued by an RODC (other than ourselves) — otherwise the PAC
+ * cannot be trusted. Because the plugin interface does not give us the
+ * client entry, we cannot look up its groups in the database.
+ */
+ code = kerberos_pac_to_user_info_dc(mem_ctx,
+ header_pac,
+ ctx->context,
+ &user_info_dc,
+ AUTH_INCLUDE_RESOURCE_GROUPS,
+ NULL,
+ NULL,
+ NULL);
+ if (code != 0) {
+ goto out;
+ }
+
+ code = samba_kdc_check_s4u2proxy_rbcd(ctx->context,
+ ctx->db_ctx,
+ client_principal,
+ server_principal,
+ user_info_dc,
+ NULL /* device_info_dc */,
+ (struct auth_claims) {},
+ proxy_skdc_entry);
+out:
+ talloc_free(mem_ctx);
+ return code;
+}
+
+static krb5_error_code mit_samba_change_pwd_error(krb5_context context,
+ NTSTATUS result,
+ enum samPwdChangeReason reject_reason,
+ struct samr_DomInfo1 *dominfo)
+{
+ krb5_error_code code = KADM5_PASS_Q_GENERIC;
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_USER)) {
+ code = KADM5_BAD_PRINCIPAL;
+ krb5_set_error_message(context,
+ code,
+ "No such user when changing password");
+ }
+ if (NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)) {
+ code = KADM5_PASS_Q_GENERIC;
+ krb5_set_error_message(context,
+ code,
+ "Not permitted to change password");
+ }
+ if (NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_RESTRICTION) &&
+ dominfo != NULL) {
+ switch (reject_reason) {
+ case SAM_PWD_CHANGE_PASSWORD_TOO_SHORT:
+ code = KADM5_PASS_Q_TOOSHORT;
+ krb5_set_error_message(context,
+ code,
+ "Password too short, password "
+ "must be at least %d characters "
+ "long.",
+ dominfo->min_password_length);
+ break;
+ case SAM_PWD_CHANGE_NOT_COMPLEX:
+ code = KADM5_PASS_Q_DICT;
+ krb5_set_error_message(context,
+ code,
+ "Password does not meet "
+ "complexity requirements");
+ break;
+ case SAM_PWD_CHANGE_PWD_IN_HISTORY:
+ code = KADM5_PASS_TOOSOON;
+ krb5_set_error_message(context,
+ code,
+ "Password is already in password "
+ "history. New password must not "
+ "match any of your %d previous "
+ "passwords.",
+ dominfo->password_history_length);
+ break;
+ default:
+ code = KADM5_PASS_Q_GENERIC;
+ krb5_set_error_message(context,
+ code,
+ "Password change rejected, "
+ "password changes may not be "
+ "permitted on this account, or "
+ "the minimum password age may "
+ "not have elapsed.");
+ break;
+ }
+ }
+
+ return code;
+}
+
+krb5_error_code mit_samba_kpasswd_change_password(struct mit_samba_context *ctx,
+ char *pwd,
+ krb5_db_entry *db_entry)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ TALLOC_CTX *tmp_ctx;
+ DATA_BLOB password;
+ enum samPwdChangeReason reject_reason;
+ struct samr_DomInfo1 *dominfo;
+ const char *error_string = NULL;
+ const struct auth_user_info_dc *user_info_dc = NULL;
+ struct samba_kdc_entry *p =
+ talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry);
+ krb5_error_code code = 0;
+
+#ifdef DEBUG_PASSWORD
+ DBG_WARNING("mit_samba_kpasswd_change_password called with: %s\n", pwd);
+#endif
+
+ tmp_ctx = talloc_named(ctx, 0, "mit_samba_kpasswd_change_password");
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ code = samba_kdc_get_user_info_from_db(tmp_ctx,
+ ctx->db_ctx->samdb,
+ p,
+ p->msg,
+ &user_info_dc);
+ if (code) {
+ const char *krb5err = krb5_get_error_message(ctx->context, code);
+ DBG_WARNING("samba_kdc_get_user_info_from_db failed: %s\n",
+ krb5err != NULL ? krb5err : "<unknown>");
+ krb5_free_error_message(ctx->context, krb5err);
+
+ goto out;
+ }
+
+ status = auth_generate_session_info(tmp_ctx,
+ ctx->db_ctx->lp_ctx,
+ ctx->db_ctx->samdb,
+ user_info_dc,
+ 0, /* session_info_flags */
+ &ctx->session_info);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("auth_generate_session_info failed: %s\n",
+ nt_errstr(status));
+ code = EINVAL;
+ goto out;
+ }
+
+ /* password is expected as UTF16 */
+
+ if (!convert_string_talloc(tmp_ctx, CH_UTF8, CH_UTF16,
+ pwd, strlen(pwd),
+ &password.data, &password.length)) {
+ DBG_WARNING("convert_string_talloc failed\n");
+ code = EINVAL;
+ goto out;
+ }
+
+ status = samdb_kpasswd_change_password(tmp_ctx,
+ ctx->db_ctx->lp_ctx,
+ ctx->db_ctx->ev_ctx,
+ ctx->session_info,
+ &password,
+ &reject_reason,
+ &dominfo,
+ &error_string,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("samdb_kpasswd_change_password failed: %s\n",
+ nt_errstr(status));
+ code = KADM5_PASS_Q_GENERIC;
+ krb5_set_error_message(ctx->context, code, "%s", error_string);
+ goto out;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ code = mit_samba_change_pwd_error(ctx->context,
+ result,
+ reject_reason,
+ dominfo);
+ }
+
+out:
+ talloc_free(tmp_ctx);
+
+ return code;
+}
+
+void mit_samba_zero_bad_password_count(krb5_db_entry *db_entry)
+{
+ /* struct netr_SendToSamBase *send_to_sam = NULL; */
+ struct samba_kdc_entry *p =
+ talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry);
+ struct ldb_dn *domain_dn;
+
+ domain_dn = ldb_get_default_basedn(p->kdc_db_ctx->samdb);
+
+ authsam_logon_success_accounting(p->kdc_db_ctx->samdb,
+ p->msg,
+ domain_dn,
+ true,
+ NULL, NULL);
+ /* TODO: RODC support */
+}
+
+
+void mit_samba_update_bad_password_count(krb5_db_entry *db_entry)
+{
+ struct samba_kdc_entry *p =
+ talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry);
+
+ authsam_update_bad_pwd_count(p->kdc_db_ctx->samdb,
+ p->msg,
+ ldb_get_default_basedn(p->kdc_db_ctx->samdb));
+}
+
+bool mit_samba_princ_needs_pac(krb5_db_entry *db_entry)
+{
+ struct samba_kdc_entry *skdc_entry =
+ talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry);
+
+ return samba_princ_needs_pac(skdc_entry);
+}
diff --git a/source4/kdc/mit_samba.h b/source4/kdc/mit_samba.h
new file mode 100644
index 0000000..0357408
--- /dev/null
+++ b/source4/kdc/mit_samba.h
@@ -0,0 +1,106 @@
+/*
+ MIT-Samba4 library
+
+ Copyright (c) 2010, Simo Sorce <idra@samba.org>
+
+ 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/>.
+ */
+
+#ifndef _MIT_SAMBA_H
+#define _MIT_SAMBA_H
+
+struct mit_samba_context {
+ struct auth_session_info *session_info;
+
+ /* for compat with hdb plugin common code */
+ krb5_context context;
+ struct samba_kdc_db_context *db_ctx;
+};
+
+int mit_samba_context_init(struct mit_samba_context **_ctx);
+
+void mit_samba_context_free(struct mit_samba_context *ctx);
+
+int mit_samba_generate_salt(krb5_data *salt);
+
+int mit_samba_generate_random_password(krb5_data *pwd);
+
+int mit_samba_get_principal(struct mit_samba_context *ctx,
+ krb5_const_principal principal,
+ unsigned int kflags,
+ krb5_db_entry **_kentry);
+
+int mit_samba_get_firstkey(struct mit_samba_context *ctx,
+ krb5_db_entry **_kentry);
+
+int mit_samba_get_nextkey(struct mit_samba_context *ctx,
+ krb5_db_entry **_kentry);
+
+int mit_samba_get_pac(struct mit_samba_context *smb_ctx,
+ krb5_context context,
+ uint32_t flags,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_keyblock *replaced_reply_key,
+ krb5_pac *pac);
+
+krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx,
+ krb5_context context,
+ int flags,
+ krb5_const_principal client_principal,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_db_entry *krbtgt,
+ krb5_keyblock *krbtgt_keyblock,
+ krb5_pac *pac);
+
+krb5_error_code mit_samba_update_pac(struct mit_samba_context *ctx,
+ krb5_context context,
+ int flags,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_db_entry *signing_krbtgt,
+ krb5_pac old_pac,
+ krb5_pac new_pac);
+
+int mit_samba_check_client_access(struct mit_samba_context *ctx,
+ krb5_db_entry *client,
+ const char *client_name,
+ krb5_db_entry *server,
+ const char *server_name,
+ const char *netbios_name,
+ bool password_change,
+ DATA_BLOB *e_data);
+
+int mit_samba_check_s4u2proxy(struct mit_samba_context *ctx,
+ const krb5_db_entry *server,
+ krb5_const_principal target_principal);
+krb5_error_code mit_samba_check_allowed_to_delegate_from(
+ struct mit_samba_context *ctx,
+ krb5_const_principal client,
+ krb5_const_principal server,
+ krb5_pac header_pac,
+ const krb5_db_entry *proxy);
+
+int mit_samba_kpasswd_change_password(struct mit_samba_context *ctx,
+ char *pwd,
+ krb5_db_entry *db_entry);
+
+void mit_samba_zero_bad_password_count(krb5_db_entry *db_entry);
+
+void mit_samba_update_bad_password_count(krb5_db_entry *db_entry);
+
+bool mit_samba_princ_needs_pac(krb5_db_entry *db_entry);
+
+#endif /* _MIT_SAMBA_H */
diff --git a/source4/kdc/pac-blobs.c b/source4/kdc/pac-blobs.c
new file mode 100644
index 0000000..9cf79e7
--- /dev/null
+++ b/source4/kdc/pac-blobs.c
@@ -0,0 +1,253 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ PAC Glue between Samba and the KDC
+
+ 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 "source4/kdc/pac-blobs.h"
+
+#include "lib/util/debug.h"
+#include "lib/util/samba_util.h"
+
+static inline size_t *pac_blobs_get_index(struct pac_blobs *pac_blobs, size_t type)
+{
+ /* Ensure the type is valid. */
+ SMB_ASSERT(type >= PAC_TYPE_BEGIN);
+ SMB_ASSERT(type < PAC_TYPE_END);
+
+ return &pac_blobs->type_index[type - PAC_TYPE_BEGIN];
+}
+
+static inline struct type_data *pac_blobs_get(struct pac_blobs *pac_blobs, size_t type)
+{
+ size_t index = *pac_blobs_get_index(pac_blobs, type);
+ SMB_ASSERT(index < pac_blobs->num_types);
+
+ return &pac_blobs->type_blobs[index];
+}
+
+krb5_error_code pac_blobs_from_krb5_pac(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ const krb5_const_pac pac,
+ struct pac_blobs **pac_blobs)
+{
+ krb5_error_code code = 0;
+ uint32_t *types = NULL;
+ struct pac_blobs *blobs = NULL;
+ size_t i;
+
+ SMB_ASSERT(pac_blobs != NULL);
+ *pac_blobs = NULL;
+
+ blobs = talloc(mem_ctx, struct pac_blobs);
+ if (blobs == NULL) {
+ code = ENOMEM;
+ goto out;
+ }
+
+ *blobs = (struct pac_blobs) {};
+
+ /* Initialize the array indices. */
+ for (i = 0; i < ARRAY_SIZE(blobs->type_index); ++i) {
+ blobs->type_index[i] = SIZE_MAX;
+ }
+
+ code = krb5_pac_get_types(context, pac, &blobs->num_types, &types);
+ if (code != 0) {
+ DBG_ERR("krb5_pac_get_types failed\n");
+ goto out;
+ }
+
+ blobs->type_blobs = talloc_array(blobs, struct type_data, blobs->num_types);
+ if (blobs->type_blobs == NULL) {
+ DBG_ERR("Out of memory\n");
+ code = ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i < blobs->num_types; ++i) {
+ uint32_t type = types[i];
+ size_t *type_index = NULL;
+
+ blobs->type_blobs[i] = (struct type_data) {
+ .type = type,
+ .data = NULL,
+ };
+
+ switch (type) {
+ /* PAC buffer types that we support. */
+ case PAC_TYPE_LOGON_INFO:
+ case PAC_TYPE_CREDENTIAL_INFO:
+ case PAC_TYPE_SRV_CHECKSUM:
+ case PAC_TYPE_KDC_CHECKSUM:
+ case PAC_TYPE_LOGON_NAME:
+ case PAC_TYPE_CONSTRAINED_DELEGATION:
+ case PAC_TYPE_UPN_DNS_INFO:
+ case PAC_TYPE_CLIENT_CLAIMS_INFO:
+ case PAC_TYPE_DEVICE_INFO:
+ case PAC_TYPE_DEVICE_CLAIMS_INFO:
+ case PAC_TYPE_TICKET_CHECKSUM:
+ case PAC_TYPE_ATTRIBUTES_INFO:
+ case PAC_TYPE_REQUESTER_SID:
+ case PAC_TYPE_FULL_CHECKSUM:
+ type_index = pac_blobs_get_index(blobs, type);
+ if (*type_index != SIZE_MAX) {
+ DBG_WARNING("PAC buffer type[%"PRIu32"] twice\n", type);
+ code = EINVAL;
+ goto out;
+ }
+ *type_index = i;
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ *pac_blobs = blobs;
+ blobs = NULL;
+
+out:
+ SAFE_FREE(types);
+ TALLOC_FREE(blobs);
+ return code;
+}
+
+krb5_error_code _pac_blobs_ensure_exists(struct pac_blobs *pac_blobs,
+ const uint32_t type,
+ const char *name,
+ const char *location,
+ const char *function)
+{
+ if (*pac_blobs_get_index(pac_blobs, type) == SIZE_MAX) {
+ DEBUGLF(DBGLVL_ERR, ("%s: %s missing\n", function, name), location, function);
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+krb5_error_code _pac_blobs_replace_existing(struct pac_blobs *pac_blobs,
+ const uint32_t type,
+ const char *name,
+ const DATA_BLOB *blob,
+ const char *location,
+ const char *function)
+{
+ krb5_error_code code;
+
+ code = _pac_blobs_ensure_exists(pac_blobs,
+ type,
+ name,
+ location,
+ function);
+ if (code != 0) {
+ return code;
+ }
+
+ pac_blobs_get(pac_blobs, type)->data = blob;
+
+ return 0;
+}
+
+krb5_error_code pac_blobs_add_blob(struct pac_blobs *pac_blobs,
+ const uint32_t type,
+ const DATA_BLOB *blob)
+{
+ size_t *index = NULL;
+
+ if (blob == NULL) {
+ return 0;
+ }
+
+ index = pac_blobs_get_index(pac_blobs, type);
+ if (*index == SIZE_MAX) {
+ struct type_data *type_blobs = NULL;
+
+ type_blobs = talloc_realloc(pac_blobs,
+ pac_blobs->type_blobs,
+ struct type_data,
+ pac_blobs->num_types + 1);
+ if (type_blobs == NULL) {
+ DBG_ERR("Out of memory\n");
+ return ENOMEM;
+ }
+
+ pac_blobs->type_blobs = type_blobs;
+ *index = pac_blobs->num_types++;
+ }
+
+ *pac_blobs_get(pac_blobs, type) = (struct type_data) {
+ .type = type,
+ .data = blob,
+ };
+
+ return 0;
+}
+
+void pac_blobs_remove_blob(struct pac_blobs *pac_blobs,
+ const uint32_t type)
+{
+ struct type_data *type_blobs = NULL;
+ size_t found_index;
+ size_t i;
+
+ /* Get the index of this PAC buffer type. */
+ found_index = *pac_blobs_get_index(pac_blobs, type);
+ if (found_index == SIZE_MAX) {
+ /* We don't have a PAC buffer of this type, so we're done. */
+ return;
+ }
+
+ /* Since the PAC buffer is present, there will be at least one type in the array. */
+ SMB_ASSERT(pac_blobs->num_types > 0);
+
+ /* The index should be valid. */
+ SMB_ASSERT(found_index < pac_blobs->num_types);
+
+ /*
+ * Even though a consistent ordering of PAC buffers is not to be relied
+ * upon, we must still maintain the ordering we are given.
+ */
+ for (i = found_index; i < pac_blobs->num_types - 1; ++i) {
+ size_t moved_type;
+
+ /* Shift each following element backwards by one. */
+ pac_blobs->type_blobs[i] = pac_blobs->type_blobs[i + 1];
+
+ /* Mark the new position of the moved element in the index. */
+ moved_type = pac_blobs->type_blobs[i].type;
+ if (moved_type >= PAC_TYPE_BEGIN && moved_type < PAC_TYPE_END) {
+ *pac_blobs_get_index(pac_blobs, moved_type) = i;
+ }
+ }
+
+ /* Mark the removed element as no longer present. */
+ *pac_blobs_get_index(pac_blobs, type) = SIZE_MAX;
+
+ /* We do not free the removed data blob, as it may be statically allocated (e.g., a null blob). */
+
+ /* Remove the last element from the array. */
+ type_blobs = talloc_realloc(pac_blobs,
+ pac_blobs->type_blobs,
+ struct type_data,
+ --pac_blobs->num_types);
+ if (type_blobs != NULL) {
+ pac_blobs->type_blobs = type_blobs;
+ }
+}
diff --git a/source4/kdc/pac-blobs.h b/source4/kdc/pac-blobs.h
new file mode 100644
index 0000000..0c5f034
--- /dev/null
+++ b/source4/kdc/pac-blobs.h
@@ -0,0 +1,83 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ PAC Glue between Samba and the KDC
+
+ 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 <stddef.h>
+#include <stdint.h>
+
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include <krb5/krb5.h>
+
+#include "lib/util/data_blob.h"
+#include "librpc/gen_ndr/ndr_krb5pac.h"
+
+struct type_data {
+ uint32_t type;
+ const DATA_BLOB *data;
+};
+
+struct pac_blobs {
+ size_t type_index[PAC_TYPE_COUNT];
+ struct type_data *type_blobs;
+ size_t num_types;
+};
+
+krb5_error_code pac_blobs_from_krb5_pac(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ const krb5_const_pac pac,
+ struct pac_blobs **pac_blobs);
+
+#define pac_blobs_ensure_exists(pac_blobs, type) \
+ _pac_blobs_ensure_exists(pac_blobs, \
+ type, \
+ #type, \
+ __location__, \
+ __func__)
+
+krb5_error_code _pac_blobs_ensure_exists(struct pac_blobs *pac_blobs,
+ const uint32_t type,
+ const char *name,
+ const char *location,
+ const char *function);
+
+#define pac_blobs_replace_existing(pac_blobs, type, blob) \
+ _pac_blobs_replace_existing(pac_blobs, \
+ type, \
+ #type, \
+ blob, \
+ __location__, \
+ __func__)
+
+krb5_error_code _pac_blobs_replace_existing(struct pac_blobs *pac_blobs,
+ const uint32_t type,
+ const char *name,
+ const DATA_BLOB *blob,
+ const char *location,
+ const char *function);
+
+krb5_error_code pac_blobs_add_blob(struct pac_blobs *pac_blobs,
+ const uint32_t type,
+ const DATA_BLOB *blob);
+
+void pac_blobs_remove_blob(struct pac_blobs *pac_blobs,
+ const uint32_t type);
diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c
new file mode 100644
index 0000000..12465b7
--- /dev/null
+++ b/source4/kdc/pac-glue.c
@@ -0,0 +1,3248 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ PAC Glue between Samba and the KDC
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009
+ Copyright (C) Simo Sorce <idra@samba.org> 2010
+
+ 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/replace/system/kerberos.h"
+#include "lib/replace/system/filesys.h"
+#include "lib/util/debug.h"
+#include "lib/util/samba_util.h"
+#include "lib/util/talloc_stack.h"
+
+#include "auth/auth_sam_reply.h"
+#include "auth/kerberos/kerberos.h"
+#include "auth/kerberos/pac_utils.h"
+#include "auth/authn_policy.h"
+#include "libcli/security/security.h"
+#include "libds/common/flags.h"
+#include "librpc/gen_ndr/ndr_krb5pac.h"
+#include "param/param.h"
+#include "source4/auth/auth.h"
+#include "source4/dsdb/common/util.h"
+#include "source4/dsdb/samdb/samdb.h"
+#include "source4/kdc/authn_policy_util.h"
+#include "source4/kdc/samba_kdc.h"
+#include "source4/kdc/pac-glue.h"
+#include "source4/kdc/ad_claims.h"
+#include "source4/kdc/pac-blobs.h"
+
+#include <ldb.h>
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+static
+NTSTATUS samba_get_logon_info_pac_blob(TALLOC_CTX *mem_ctx,
+ const struct auth_user_info_dc *info,
+ const struct PAC_DOMAIN_GROUP_MEMBERSHIP *override_resource_groups,
+ const enum auth_group_inclusion group_inclusion,
+ DATA_BLOB *pac_data)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct netr_SamInfo3 *info3 = NULL;
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP *_resource_groups = NULL;
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP **resource_groups = NULL;
+ union PAC_INFO pac_info = {};
+ enum ndr_err_code ndr_err;
+ NTSTATUS nt_status = NT_STATUS_OK;
+
+ *pac_data = data_blob_null;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (override_resource_groups == NULL) {
+ resource_groups = &_resource_groups;
+ } else if (group_inclusion != AUTH_EXCLUDE_RESOURCE_GROUPS) {
+ /*
+ * It doesn't make sense to override resource groups if we claim
+ * to want resource groups from user_info_dc.
+ */
+ DBG_ERR("supplied resource groups with invalid group inclusion parameter: %u\n",
+ group_inclusion);
+ nt_status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ nt_status = auth_convert_user_info_dc_saminfo3(tmp_ctx, info,
+ group_inclusion,
+ &info3,
+ resource_groups);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_WARNING("Getting Samba info failed: %s\n",
+ nt_errstr(nt_status));
+ goto out;
+ }
+
+ pac_info.logon_info.info = talloc_zero(tmp_ctx, struct PAC_LOGON_INFO);
+ if (!pac_info.logon_info.info) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ pac_info.logon_info.info->info3 = *info3;
+ if (_resource_groups != NULL) {
+ pac_info.logon_info.info->resource_groups = *_resource_groups;
+ }
+
+ if (override_resource_groups != NULL) {
+ pac_info.logon_info.info->resource_groups = *override_resource_groups;
+ }
+
+ if (group_inclusion != AUTH_EXCLUDE_RESOURCE_GROUPS) {
+ /*
+ * Set the resource groups flag based on whether any groups are
+ * present. Otherwise, the flag is propagated from the
+ * originating PAC.
+ */
+ if (pac_info.logon_info.info->resource_groups.groups.count > 0) {
+ pac_info.logon_info.info->info3.base.user_flags |= NETLOGON_RESOURCE_GROUPS;
+ } else {
+ pac_info.logon_info.info->info3.base.user_flags &= ~NETLOGON_RESOURCE_GROUPS;
+ }
+ }
+
+ ndr_err = ndr_push_union_blob(pac_data, mem_ctx, &pac_info,
+ PAC_TYPE_LOGON_INFO,
+ (ndr_push_flags_fn_t)ndr_push_PAC_INFO);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("PAC_LOGON_INFO (presig) push failed: %s\n",
+ nt_errstr(nt_status));
+ goto out;
+ }
+
+out:
+ talloc_free(tmp_ctx);
+ return nt_status;
+}
+
+static
+NTSTATUS samba_get_upn_info_pac_blob(TALLOC_CTX *mem_ctx,
+ const struct auth_user_info_dc *info,
+ DATA_BLOB *upn_data)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ union PAC_INFO pac_upn = {};
+ enum ndr_err_code ndr_err;
+ NTSTATUS nt_status = NT_STATUS_OK;
+ bool ok;
+
+ *upn_data = data_blob_null;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ pac_upn.upn_dns_info.upn_name = info->info->user_principal_name;
+ pac_upn.upn_dns_info.dns_domain_name = strupper_talloc(tmp_ctx,
+ info->info->dns_domain_name);
+ if (pac_upn.upn_dns_info.dns_domain_name == NULL) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ if (info->info->user_principal_constructed) {
+ pac_upn.upn_dns_info.flags |= PAC_UPN_DNS_FLAG_CONSTRUCTED;
+ }
+
+ pac_upn.upn_dns_info.flags |= PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID;
+
+ pac_upn.upn_dns_info.ex.sam_name_and_sid.samaccountname
+ = info->info->account_name;
+
+ pac_upn.upn_dns_info.ex.sam_name_and_sid.objectsid
+ = &info->sids[PRIMARY_USER_SID_INDEX].sid;
+
+ ndr_err = ndr_push_union_blob(upn_data, mem_ctx, &pac_upn,
+ PAC_TYPE_UPN_DNS_INFO,
+ (ndr_push_flags_fn_t)ndr_push_PAC_INFO);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("PAC UPN_DNS_INFO (presig) push failed: %s\n",
+ nt_errstr(nt_status));
+ goto out;
+ }
+
+ ok = data_blob_pad(mem_ctx, upn_data, 8);
+ if (!ok) {
+ talloc_free(upn_data);
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+out:
+ talloc_free(tmp_ctx);
+ return nt_status;
+}
+
+static
+NTSTATUS samba_get_cred_info_ndr_blob(TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ DATA_BLOB *cred_blob)
+{
+ enum ndr_err_code ndr_err;
+ NTSTATUS nt_status;
+ struct samr_Password *lm_hash = NULL;
+ struct samr_Password *nt_hash = NULL;
+ struct PAC_CREDENTIAL_NTLM_SECPKG ntlm_secpkg = {
+ .version = 0,
+ };
+ DATA_BLOB ntlm_blob = data_blob_null;
+ struct PAC_CREDENTIAL_SUPPLEMENTAL_SECPKG secpkgs[1] = {{
+ .credential_size = 0,
+ }};
+ struct PAC_CREDENTIAL_DATA cred_data = {
+ .credential_count = 0,
+ };
+ struct PAC_CREDENTIAL_DATA_NDR cred_ndr = {};
+
+ *cred_blob = data_blob_null;
+
+ lm_hash = samdb_result_hash(mem_ctx, msg, "dBCSPwd");
+ if (lm_hash != NULL) {
+ bool zero = all_zero(lm_hash->hash, 16);
+ if (zero) {
+ lm_hash = NULL;
+ }
+ }
+ if (lm_hash != NULL) {
+ DBG_INFO("Passing LM password hash through credentials set\n");
+ ntlm_secpkg.flags |= PAC_CREDENTIAL_NTLM_HAS_LM_HASH;
+ ntlm_secpkg.lm_password = *lm_hash;
+ ZERO_STRUCTP(lm_hash);
+ TALLOC_FREE(lm_hash);
+ }
+
+ nt_hash = samdb_result_hash(mem_ctx, msg, "unicodePwd");
+ if (nt_hash != NULL) {
+ bool zero = all_zero(nt_hash->hash, 16);
+ if (zero) {
+ nt_hash = NULL;
+ }
+ }
+ if (nt_hash != NULL) {
+ DBG_INFO("Passing NT password hash through credentials set\n");
+ ntlm_secpkg.flags |= PAC_CREDENTIAL_NTLM_HAS_NT_HASH;
+ ntlm_secpkg.nt_password = *nt_hash;
+ ZERO_STRUCTP(nt_hash);
+ TALLOC_FREE(nt_hash);
+ }
+
+ if (ntlm_secpkg.flags == 0) {
+ return NT_STATUS_OK;
+ }
+
+#ifdef DEBUG_PASSWORD
+ if (DEBUGLVL(11)) {
+ NDR_PRINT_DEBUG(PAC_CREDENTIAL_NTLM_SECPKG, &ntlm_secpkg);
+ }
+#endif
+
+ ndr_err = ndr_push_struct_blob(&ntlm_blob, mem_ctx, &ntlm_secpkg,
+ (ndr_push_flags_fn_t)ndr_push_PAC_CREDENTIAL_NTLM_SECPKG);
+ ZERO_STRUCT(ntlm_secpkg);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("PAC_CREDENTIAL_NTLM_SECPKG (presig) push failed: %s\n",
+ nt_errstr(nt_status));
+ return nt_status;
+ }
+
+ DBG_DEBUG("NTLM credential BLOB (len %zu) for user\n",
+ ntlm_blob.length);
+ dump_data_pw("PAC_CREDENTIAL_NTLM_SECPKG",
+ ntlm_blob.data, ntlm_blob.length);
+
+ secpkgs[0].package_name.string = discard_const_p(char, "NTLM");
+ secpkgs[0].credential_size = ntlm_blob.length;
+ secpkgs[0].credential = ntlm_blob.data;
+
+ cred_data.credential_count = ARRAY_SIZE(secpkgs);
+ cred_data.credentials = secpkgs;
+
+#ifdef DEBUG_PASSWORD
+ if (DEBUGLVL(11)) {
+ NDR_PRINT_DEBUG(PAC_CREDENTIAL_DATA, &cred_data);
+ }
+#endif
+
+ cred_ndr.ctr.data = &cred_data;
+
+#ifdef DEBUG_PASSWORD
+ if (DEBUGLVL(11)) {
+ NDR_PRINT_DEBUG(PAC_CREDENTIAL_DATA_NDR, &cred_ndr);
+ }
+#endif
+
+ ndr_err = ndr_push_struct_blob(cred_blob, mem_ctx, &cred_ndr,
+ (ndr_push_flags_fn_t)ndr_push_PAC_CREDENTIAL_DATA_NDR);
+ data_blob_clear(&ntlm_blob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("PAC_CREDENTIAL_DATA_NDR (presig) push failed: %s\n",
+ nt_errstr(nt_status));
+ return nt_status;
+ }
+
+ DBG_DEBUG("Created credential BLOB (len %zu) for user\n",
+ cred_blob->length);
+ dump_data_pw("PAC_CREDENTIAL_DATA_NDR",
+ cred_blob->data, cred_blob->length);
+
+ return NT_STATUS_OK;
+}
+
+krb5_error_code samba_kdc_encrypt_pac_credentials(krb5_context context,
+ const krb5_keyblock *pkreplykey,
+ const DATA_BLOB *cred_ndr_blob,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cred_info_blob)
+{
+#ifdef SAMBA4_USES_HEIMDAL
+ krb5_crypto cred_crypto;
+ krb5_enctype cred_enctype;
+ krb5_data cred_ndr_crypt;
+ struct PAC_CREDENTIAL_INFO pac_cred_info = { .version = 0, };
+ krb5_error_code ret;
+ const char *krb5err;
+ enum ndr_err_code ndr_err;
+ NTSTATUS nt_status;
+
+ *cred_info_blob = data_blob_null;
+
+ ret = krb5_crypto_init(context, pkreplykey, ETYPE_NULL,
+ &cred_crypto);
+ if (ret != 0) {
+ krb5err = krb5_get_error_message(context, ret);
+ DBG_WARNING("Failed initializing cred data crypto: %s\n", krb5err);
+ krb5_free_error_message(context, krb5err);
+ return ret;
+ }
+
+ ret = krb5_crypto_getenctype(context, cred_crypto, &cred_enctype);
+ if (ret != 0) {
+ DBG_WARNING("Failed getting crypto type for key\n");
+ krb5_crypto_destroy(context, cred_crypto);
+ return ret;
+ }
+
+ DBG_DEBUG("Plain cred_ndr_blob (len %zu)\n",
+ cred_ndr_blob->length);
+ dump_data_pw("PAC_CREDENTIAL_DATA_NDR",
+ cred_ndr_blob->data, cred_ndr_blob->length);
+
+ ret = krb5_encrypt(context, cred_crypto,
+ KRB5_KU_OTHER_ENCRYPTED,
+ cred_ndr_blob->data, cred_ndr_blob->length,
+ &cred_ndr_crypt);
+ krb5_crypto_destroy(context, cred_crypto);
+ if (ret != 0) {
+ krb5err = krb5_get_error_message(context, ret);
+ DBG_WARNING("Failed crypt of cred data: %s\n", krb5err);
+ krb5_free_error_message(context, krb5err);
+ return ret;
+ }
+
+ pac_cred_info.encryption_type = cred_enctype;
+ pac_cred_info.encrypted_data.length = cred_ndr_crypt.length;
+ pac_cred_info.encrypted_data.data = (uint8_t *)cred_ndr_crypt.data;
+
+ if (DEBUGLVL(10)) {
+ NDR_PRINT_DEBUG(PAC_CREDENTIAL_INFO, &pac_cred_info);
+ }
+
+ ndr_err = ndr_push_struct_blob(cred_info_blob, mem_ctx, &pac_cred_info,
+ (ndr_push_flags_fn_t)ndr_push_PAC_CREDENTIAL_INFO);
+ krb5_data_free(&cred_ndr_crypt);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("PAC_CREDENTIAL_INFO (presig) push failed: %s\n",
+ nt_errstr(nt_status));
+ return KRB5KDC_ERR_SVC_UNAVAILABLE;
+ }
+
+ DBG_DEBUG("Encrypted credential BLOB (len %zu) with alg %"PRId32"\n",
+ cred_info_blob->length, pac_cred_info.encryption_type);
+ dump_data_pw("PAC_CREDENTIAL_INFO",
+ cred_info_blob->data, cred_info_blob->length);
+
+ return 0;
+#else /* SAMBA4_USES_HEIMDAL */
+ TALLOC_CTX *tmp_ctx = NULL;
+ krb5_key cred_key;
+ krb5_enctype cred_enctype;
+ struct PAC_CREDENTIAL_INFO pac_cred_info = { .version = 0, };
+ krb5_error_code code = 0;
+ const char *krb5err;
+ enum ndr_err_code ndr_err;
+ NTSTATUS nt_status;
+ krb5_data cred_ndr_data;
+ krb5_enc_data cred_ndr_crypt;
+ size_t enc_len = 0;
+
+ *cred_info_blob = data_blob_null;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ code = krb5_k_create_key(context,
+ pkreplykey,
+ &cred_key);
+ if (code != 0) {
+ krb5err = krb5_get_error_message(context, code);
+ DBG_WARNING("Failed initializing cred data crypto: %s\n", krb5err);
+ krb5_free_error_message(context, krb5err);
+ goto out;
+ }
+
+ cred_enctype = krb5_k_key_enctype(context, cred_key);
+
+ DBG_DEBUG("Plain cred_ndr_blob (len %zu)\n",
+ cred_ndr_blob->length);
+ dump_data_pw("PAC_CREDENTIAL_DATA_NDR",
+ cred_ndr_blob->data, cred_ndr_blob->length);
+
+ pac_cred_info.encryption_type = cred_enctype;
+
+ cred_ndr_data = smb_krb5_data_from_blob(*cred_ndr_blob);
+
+ code = krb5_c_encrypt_length(context,
+ cred_enctype,
+ cred_ndr_data.length,
+ &enc_len);
+ if (code != 0) {
+ krb5err = krb5_get_error_message(context, code);
+ DBG_WARNING("Failed initializing cred data crypto: %s\n", krb5err);
+ krb5_free_error_message(context, krb5err);
+ goto out;
+ }
+
+ pac_cred_info.encrypted_data = data_blob_talloc_zero(tmp_ctx, enc_len);
+ if (pac_cred_info.encrypted_data.data == NULL) {
+ DBG_ERR("Out of memory\n");
+ code = ENOMEM;
+ goto out;
+ }
+
+ cred_ndr_crypt.ciphertext = smb_krb5_data_from_blob(pac_cred_info.encrypted_data);
+
+ code = krb5_k_encrypt(context,
+ cred_key,
+ KRB5_KU_OTHER_ENCRYPTED,
+ NULL,
+ &cred_ndr_data,
+ &cred_ndr_crypt);
+ krb5_k_free_key(context, cred_key);
+ if (code != 0) {
+ krb5err = krb5_get_error_message(context, code);
+ DBG_WARNING("Failed crypt of cred data: %s\n", krb5err);
+ krb5_free_error_message(context, krb5err);
+ goto out;
+ }
+
+ if (DEBUGLVL(10)) {
+ NDR_PRINT_DEBUG(PAC_CREDENTIAL_INFO, &pac_cred_info);
+ }
+
+ ndr_err = ndr_push_struct_blob(cred_info_blob, mem_ctx, &pac_cred_info,
+ (ndr_push_flags_fn_t)ndr_push_PAC_CREDENTIAL_INFO);
+ TALLOC_FREE(pac_cred_info.encrypted_data.data);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("PAC_CREDENTIAL_INFO (presig) push failed: %s\n",
+ nt_errstr(nt_status));
+ code = KRB5KDC_ERR_SVC_UNAVAILABLE;
+ goto out;
+ }
+
+ DBG_DEBUG("Encrypted credential BLOB (len %zu) with alg %"PRId32"\n",
+ cred_info_blob->length, pac_cred_info.encryption_type);
+ dump_data_pw("PAC_CREDENTIAL_INFO",
+ cred_info_blob->data, cred_info_blob->length);
+
+out:
+ talloc_free(tmp_ctx);
+ return code;
+#endif /* SAMBA4_USES_HEIMDAL */
+}
+
+
+/**
+ * @brief Create a PAC with the given blobs (logon, credentials, upn and
+ * delegation).
+ *
+ * @param[in] context The KRB5 context to use.
+ *
+ * @param[in] logon_blob Fill the logon info PAC buffer with the given blob,
+ * use NULL to ignore it.
+ *
+ * @param[in] cred_blob Fill the credentials info PAC buffer with the given
+ * blob, use NULL to ignore it.
+ *
+ * @param[in] upn_blob Fill the UPN info PAC buffer with the given blob, use
+ * NULL to ignore it.
+ *
+ * @param[in] deleg_blob Fill the delegation info PAC buffer with the given
+ * blob, use NULL to ignore it.
+ *
+ * @param[in] client_claims_blob Fill the client claims info PAC buffer with the
+ * given blob, use NULL to ignore it.
+ *
+ * @param[in] device_info_blob Fill the device info PAC buffer with the given
+ * blob, use NULL to ignore it.
+ *
+ * @param[in] device_claims_blob Fill the device claims info PAC buffer with the given
+ * blob, use NULL to ignore it.
+ *
+ * @param[in] pac The pac buffer to fill. This should be allocated with
+ * krb5_pac_init() already.
+ *
+ * @returns 0 on success or a corresponding KRB5 error.
+ */
+krb5_error_code samba_make_krb5_pac(krb5_context context,
+ const DATA_BLOB *logon_blob,
+ const DATA_BLOB *cred_blob,
+ const DATA_BLOB *upn_blob,
+ const DATA_BLOB *pac_attrs_blob,
+ const DATA_BLOB *requester_sid_blob,
+ const DATA_BLOB *deleg_blob,
+ const DATA_BLOB *client_claims_blob,
+ const DATA_BLOB *device_info_blob,
+ const DATA_BLOB *device_claims_blob,
+ krb5_pac pac)
+{
+ krb5_data logon_data;
+ krb5_error_code ret;
+ char null_byte = '\0';
+ krb5_data null_data = smb_krb5_make_data(&null_byte, 0);
+
+ /* The user account may be set not to want the PAC */
+ if (logon_blob == NULL) {
+ return 0;
+ }
+
+ logon_data = smb_krb5_data_from_blob(*logon_blob);
+ ret = krb5_pac_add_buffer(context, pac, PAC_TYPE_LOGON_INFO, &logon_data);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if (device_info_blob != NULL) {
+ krb5_data device_info_data = smb_krb5_data_from_blob(*device_info_blob);
+ ret = krb5_pac_add_buffer(context, pac,
+ PAC_TYPE_DEVICE_INFO,
+ &device_info_data);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ if (client_claims_blob != NULL) {
+ krb5_data client_claims_data;
+ krb5_data *data = NULL;
+
+ if (client_claims_blob->length != 0) {
+ client_claims_data = smb_krb5_data_from_blob(*client_claims_blob);
+ data = &client_claims_data;
+ } else {
+ data = &null_data;
+ }
+
+ ret = krb5_pac_add_buffer(context, pac,
+ PAC_TYPE_CLIENT_CLAIMS_INFO,
+ data);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ if (device_claims_blob != NULL) {
+ krb5_data device_claims_data = smb_krb5_data_from_blob(*device_claims_blob);
+ ret = krb5_pac_add_buffer(context, pac,
+ PAC_TYPE_DEVICE_CLAIMS_INFO,
+ &device_claims_data);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ if (cred_blob != NULL) {
+ krb5_data cred_data = smb_krb5_data_from_blob(*cred_blob);
+ ret = krb5_pac_add_buffer(context, pac,
+ PAC_TYPE_CREDENTIAL_INFO,
+ &cred_data);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+#ifdef SAMBA4_USES_HEIMDAL
+ /*
+ * null_data will be filled by the generic KDC code in the caller
+ * here we just add it in order to have it before
+ * PAC_TYPE_UPN_DNS_INFO
+ *
+ * Not needed with MIT Kerberos - asn
+ */
+ ret = krb5_pac_add_buffer(context, pac,
+ PAC_TYPE_LOGON_NAME,
+ &null_data);
+ if (ret != 0) {
+ return ret;
+ }
+#endif
+
+ if (upn_blob != NULL) {
+ krb5_data upn_data = smb_krb5_data_from_blob(*upn_blob);
+ ret = krb5_pac_add_buffer(context, pac,
+ PAC_TYPE_UPN_DNS_INFO,
+ &upn_data);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ if (pac_attrs_blob != NULL) {
+ krb5_data pac_attrs_data = smb_krb5_data_from_blob(*pac_attrs_blob);
+ ret = krb5_pac_add_buffer(context, pac,
+ PAC_TYPE_ATTRIBUTES_INFO,
+ &pac_attrs_data);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ if (requester_sid_blob != NULL) {
+ krb5_data requester_sid_data = smb_krb5_data_from_blob(*requester_sid_blob);
+ ret = krb5_pac_add_buffer(context, pac,
+ PAC_TYPE_REQUESTER_SID,
+ &requester_sid_data);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ if (deleg_blob != NULL) {
+ krb5_data deleg_data = smb_krb5_data_from_blob(*deleg_blob);
+ ret = krb5_pac_add_buffer(context, pac,
+ PAC_TYPE_CONSTRAINED_DELEGATION,
+ &deleg_data);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+bool samba_princ_needs_pac(const struct samba_kdc_entry *skdc_entry)
+{
+
+ uint32_t userAccountControl;
+
+ /* The service account may be set not to want the PAC */
+ userAccountControl = ldb_msg_find_attr_as_uint(skdc_entry->msg, "userAccountControl", 0);
+ if (userAccountControl & UF_NO_AUTH_DATA_REQUIRED) {
+ return false;
+ }
+
+ return true;
+}
+
+static krb5_error_code samba_client_requested_pac(krb5_context context,
+ const krb5_const_pac pac,
+ TALLOC_CTX *mem_ctx,
+ bool *requested_pac)
+{
+ enum ndr_err_code ndr_err;
+ krb5_data k5pac_attrs_in;
+ DATA_BLOB pac_attrs_in;
+ union PAC_INFO pac_attrs;
+ krb5_error_code ret;
+
+ *requested_pac = true;
+
+ ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_ATTRIBUTES_INFO,
+ &k5pac_attrs_in);
+ if (ret != 0) {
+ return ret == ENOENT ? 0 : ret;
+ }
+
+ pac_attrs_in = data_blob_const(k5pac_attrs_in.data,
+ k5pac_attrs_in.length);
+
+ ndr_err = ndr_pull_union_blob(&pac_attrs_in, mem_ctx, &pac_attrs,
+ PAC_TYPE_ATTRIBUTES_INFO,
+ (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO);
+ smb_krb5_free_data_contents(context, &k5pac_attrs_in);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("can't parse the PAC ATTRIBUTES_INFO: %s\n", nt_errstr(nt_status));
+ return map_errno_from_nt_status(nt_status);
+ }
+
+ if (pac_attrs.attributes_info.flags & (PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY
+ | PAC_ATTRIBUTE_FLAG_PAC_WAS_REQUESTED)) {
+ *requested_pac = true;
+ } else {
+ *requested_pac = false;
+ }
+
+ return 0;
+}
+
+/* Was the krbtgt in this DB (ie, should we check the incoming signature) and was it an RODC */
+krb5_error_code samba_krbtgt_is_in_db(const struct samba_kdc_entry *p,
+ bool *is_in_db,
+ bool *is_trusted)
+{
+ NTSTATUS status;
+ krb5_error_code ret;
+ int rodc_krbtgt_number, trust_direction;
+ struct dom_sid sid;
+ uint32_t rid;
+
+ trust_direction = ldb_msg_find_attr_as_int(p->msg, "trustDirection", 0);
+
+ if (trust_direction != 0) {
+ /* Domain trust - we cannot check the sig, but we trust it for a correct PAC
+
+ This is exactly where we should flag for SID
+ validation when we do inter-forest trusts
+ */
+ *is_trusted = true;
+ *is_in_db = false;
+ return 0;
+ }
+
+ /* The lack of password controls etc applies to krbtgt by
+ * virtue of being that particular RID */
+ ret = samdb_result_dom_sid_buf(p->msg, "objectSid", &sid);
+ if (ret) {
+ return ret;
+ }
+
+ status = dom_sid_split_rid(NULL, &sid, NULL, &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return map_errno_from_nt_status(status);
+ }
+
+ rodc_krbtgt_number = ldb_msg_find_attr_as_int(p->msg, "msDS-SecondaryKrbTgtNumber", -1);
+
+ if (p->kdc_db_ctx->my_krbtgt_number == 0) {
+ if (rid == DOMAIN_RID_KRBTGT) {
+ *is_trusted = true;
+ *is_in_db = true;
+ return 0;
+ } else if (rodc_krbtgt_number != -1) {
+ *is_in_db = true;
+ *is_trusted = false;
+ return 0;
+ }
+ } else if ((rid != DOMAIN_RID_KRBTGT) && (rodc_krbtgt_number == p->kdc_db_ctx->my_krbtgt_number)) {
+ *is_trusted = true;
+ *is_in_db = true;
+ return 0;
+ } else if (rid == DOMAIN_RID_KRBTGT) {
+ /* krbtgt viewed from an RODC */
+ *is_trusted = true;
+ *is_in_db = false;
+ return 0;
+ }
+
+ /* Another RODC */
+ *is_trusted = false;
+ *is_in_db = false;
+ return 0;
+}
+
+/*
+ * Because the KDC does not limit protocol transition, two new well-known SIDs
+ * were introduced to give this control to the resource administrator. These
+ * SIDs identify whether protocol transition has occurred, and can be used with
+ * standard access control lists to grant or limit access as needed.
+ *
+ * https://docs.microsoft.com/en-us/windows-server/security/kerberos/kerberos-constrained-delegation-overview
+ */
+NTSTATUS samba_kdc_add_asserted_identity(enum samba_asserted_identity ai,
+ struct auth_user_info_dc *user_info_dc)
+{
+ const struct dom_sid *ai_sid = NULL;
+
+ switch (ai) {
+ case SAMBA_ASSERTED_IDENTITY_SERVICE:
+ ai_sid = &global_sid_Asserted_Identity_Service;
+ break;
+ case SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY:
+ ai_sid = &global_sid_Asserted_Identity_Authentication_Authority;
+ break;
+ case SAMBA_ASSERTED_IDENTITY_IGNORE:
+ return NT_STATUS_OK;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return add_sid_to_array_attrs_unique(
+ user_info_dc,
+ ai_sid,
+ SE_GROUP_DEFAULT_FLAGS,
+ &user_info_dc->sids,
+ &user_info_dc->num_sids);
+}
+
+NTSTATUS samba_kdc_add_claims_valid(struct auth_user_info_dc *user_info_dc)
+{
+ return add_sid_to_array_attrs_unique(
+ user_info_dc,
+ &global_sid_Claims_Valid,
+ SE_GROUP_DEFAULT_FLAGS,
+ &user_info_dc->sids,
+ &user_info_dc->num_sids);
+}
+
+static NTSTATUS samba_kdc_add_compounded_auth(struct auth_user_info_dc *user_info_dc)
+{
+ return add_sid_to_array_attrs_unique(
+ user_info_dc,
+ &global_sid_Compounded_Authentication,
+ SE_GROUP_DEFAULT_FLAGS,
+ &user_info_dc->sids,
+ &user_info_dc->num_sids);
+}
+
+bool samba_kdc_entry_is_trust(const struct samba_kdc_entry *entry)
+{
+ return entry != NULL && entry->is_trust;
+}
+
+/*
+ * Return true if this entry has an associated PAC issued or signed by a KDC
+ * that our KDC trusts. We trust the main krbtgt account, but we don’t trust any
+ * RODC krbtgt besides ourselves.
+ */
+bool samba_krb5_pac_is_trusted(const struct samba_kdc_entry_pac pac)
+{
+ if (pac.pac == NULL) {
+ return false;
+ }
+
+#ifdef HAVE_KRB5_PAC_IS_TRUSTED /* Heimdal */
+ return krb5_pac_is_trusted(pac.pac);
+#else /* MIT */
+ return pac.pac_is_trusted;
+#endif /* HAVE_KRB5_PAC_IS_TRUSTED */
+}
+
+#ifdef HAVE_KRB5_PAC_IS_TRUSTED /* Heimdal */
+struct samba_kdc_entry_pac samba_kdc_entry_pac(krb5_const_pac pac,
+ struct samba_kdc_entry *entry,
+ bool is_from_trust)
+{
+ return (struct samba_kdc_entry_pac) {
+ .entry = entry,
+ .pac = pac,
+ .is_from_trust = is_from_trust,
+ };
+}
+#else /* MIT */
+struct samba_kdc_entry_pac samba_kdc_entry_pac_from_trusted(krb5_const_pac pac,
+ struct samba_kdc_entry *entry,
+ bool is_from_trust,
+ bool is_trusted)
+{
+ return (struct samba_kdc_entry_pac) {
+ .entry = entry,
+ .pac = pac,
+ .is_from_trust = is_from_trust,
+ .pac_is_trusted = is_trusted,
+ };
+}
+#endif /* HAVE_KRB5_PAC_IS_TRUSTED */
+
+static bool samba_kdc_entry_pac_issued_by_trust(const struct samba_kdc_entry_pac entry)
+{
+ return entry.pac != NULL && entry.is_from_trust;
+}
+
+NTSTATUS samba_kdc_get_logon_info_blob(TALLOC_CTX *mem_ctx,
+ const struct auth_user_info_dc *user_info_dc,
+ const enum auth_group_inclusion group_inclusion,
+ DATA_BLOB **_logon_info_blob)
+{
+ DATA_BLOB *logon_blob = NULL;
+ NTSTATUS nt_status;
+
+ *_logon_info_blob = NULL;
+
+ logon_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (logon_blob == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_status = samba_get_logon_info_pac_blob(logon_blob,
+ user_info_dc,
+ NULL,
+ group_inclusion,
+ logon_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Building PAC LOGON INFO failed: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(logon_blob);
+ return nt_status;
+ }
+
+ *_logon_info_blob = logon_blob;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS samba_kdc_get_cred_ndr_blob(TALLOC_CTX *mem_ctx,
+ const struct samba_kdc_entry *p,
+ DATA_BLOB **_cred_ndr_blob)
+{
+ DATA_BLOB *cred_blob = NULL;
+ NTSTATUS nt_status;
+
+ SMB_ASSERT(_cred_ndr_blob != NULL);
+
+ *_cred_ndr_blob = NULL;
+
+ cred_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (cred_blob == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_status = samba_get_cred_info_ndr_blob(cred_blob,
+ p->msg,
+ cred_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Building PAC CRED INFO failed: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(cred_blob);
+ return nt_status;
+ }
+
+ *_cred_ndr_blob = cred_blob;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS samba_kdc_get_upn_info_blob(TALLOC_CTX *mem_ctx,
+ const struct auth_user_info_dc *user_info_dc,
+ DATA_BLOB **_upn_info_blob)
+{
+ DATA_BLOB *upn_blob = NULL;
+ NTSTATUS nt_status;
+
+ *_upn_info_blob = NULL;
+
+ upn_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (upn_blob == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_status = samba_get_upn_info_pac_blob(upn_blob,
+ user_info_dc,
+ upn_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Building PAC UPN INFO failed: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(upn_blob);
+ return nt_status;
+ }
+
+ *_upn_info_blob = upn_blob;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS samba_kdc_get_pac_attrs_blob(TALLOC_CTX *mem_ctx,
+ uint64_t pac_attributes,
+ DATA_BLOB **_pac_attrs_blob)
+{
+ DATA_BLOB *pac_attrs_blob = NULL;
+ union PAC_INFO pac_attrs = {};
+ enum ndr_err_code ndr_err;
+ NTSTATUS nt_status;
+
+ SMB_ASSERT(_pac_attrs_blob != NULL);
+
+ *_pac_attrs_blob = NULL;
+
+ pac_attrs_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (pac_attrs_blob == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Set the length of the flags in bits. */
+ pac_attrs.attributes_info.flags_length = 2;
+ pac_attrs.attributes_info.flags = pac_attributes;
+
+ ndr_err = ndr_push_union_blob(pac_attrs_blob, pac_attrs_blob, &pac_attrs,
+ PAC_TYPE_ATTRIBUTES_INFO,
+ (ndr_push_flags_fn_t)ndr_push_PAC_INFO);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("PAC ATTRIBUTES_INFO (presig) push failed: %s\n",
+ nt_errstr(nt_status));
+ DBG_ERR("Building PAC ATTRIBUTES failed: %s\n",
+ nt_errstr(nt_status));
+
+ talloc_free(pac_attrs_blob);
+ return nt_status;
+ }
+
+ *_pac_attrs_blob = pac_attrs_blob;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS samba_kdc_get_requester_sid_blob(TALLOC_CTX *mem_ctx,
+ const struct auth_user_info_dc *user_info_dc,
+ DATA_BLOB **_requester_sid_blob)
+{
+ DATA_BLOB *requester_sid_blob = NULL;
+ NTSTATUS nt_status;
+
+ SMB_ASSERT(_requester_sid_blob != NULL);
+
+ *_requester_sid_blob = NULL;
+
+ requester_sid_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (requester_sid_blob == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (user_info_dc->num_sids > 0) {
+ union PAC_INFO pac_requester_sid = {};
+ enum ndr_err_code ndr_err;
+
+ pac_requester_sid.requester_sid.sid = user_info_dc->sids[PRIMARY_USER_SID_INDEX].sid;
+
+ ndr_err = ndr_push_union_blob(requester_sid_blob, requester_sid_blob,
+ &pac_requester_sid,
+ PAC_TYPE_REQUESTER_SID,
+ (ndr_push_flags_fn_t)ndr_push_PAC_INFO);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("PAC_REQUESTER_SID (presig) push failed: %s\n",
+ nt_errstr(nt_status));
+ DBG_ERR("Building PAC REQUESTER SID failed: %s\n",
+ nt_errstr(nt_status));
+
+ talloc_free(requester_sid_blob);
+ return nt_status;
+ }
+ }
+
+ *_requester_sid_blob = requester_sid_blob;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS samba_kdc_get_claims_blob(TALLOC_CTX *mem_ctx,
+ struct samba_kdc_entry *p,
+ const DATA_BLOB **_claims_blob)
+{
+ DATA_BLOB *claims_blob = NULL;
+ struct claims_data *claims_data = NULL;
+ NTSTATUS nt_status;
+ int ret;
+
+ SMB_ASSERT(_claims_blob != NULL);
+
+ *_claims_blob = NULL;
+
+ claims_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (claims_blob == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = samba_kdc_get_claims_data_from_db(p->kdc_db_ctx->samdb,
+ p,
+ &claims_data);
+ if (ret != LDB_SUCCESS) {
+ nt_status = dsdb_ldb_err_to_ntstatus(ret);
+ DBG_ERR("Building claims failed: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(claims_blob);
+ return nt_status;
+ }
+
+ nt_status = claims_data_encoded_claims_set(claims_blob,
+ claims_data,
+ claims_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(claims_blob);
+ return nt_status;
+ }
+
+ *_claims_blob = claims_blob;
+
+ return NT_STATUS_OK;
+}
+
+krb5_error_code samba_kdc_get_user_info_from_db(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct samba_kdc_entry *entry,
+ const struct ldb_message *msg,
+ const struct auth_user_info_dc **info_out)
+{
+ NTSTATUS nt_status;
+
+ if (samdb == NULL) {
+ return EINVAL;
+ }
+
+ if (msg == NULL) {
+ return EINVAL;
+ }
+
+ if (info_out == NULL) {
+ return EINVAL;
+ }
+
+ if (entry == NULL) {
+ return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ }
+
+ *info_out = NULL;
+
+ if (entry->info_from_db == NULL) {
+ struct auth_user_info_dc *info_from_db = NULL;
+ struct loadparm_context *lp_ctx = entry->kdc_db_ctx->lp_ctx;
+
+ nt_status = authsam_make_user_info_dc(entry,
+ samdb,
+ lpcfg_netbios_name(lp_ctx),
+ lpcfg_sam_name(lp_ctx),
+ lpcfg_sam_dnsname(lp_ctx),
+ entry->realm_dn,
+ msg,
+ data_blob_null,
+ data_blob_null,
+ &info_from_db);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Getting user info for PAC failed: %s\n",
+ nt_errstr(nt_status));
+ /* NT_STATUS_OBJECT_NAME_NOT_FOUND is mapped to ENOENT. */
+ return map_errno_from_nt_status(nt_status);
+ }
+
+ entry->info_from_db = info_from_db;
+ }
+
+ *info_out = entry->info_from_db;
+
+ return 0;
+}
+
+/*
+ * Check whether a PAC contains the Authentication Authority Asserted Identity
+ * SID.
+ */
+static krb5_error_code samba_kdc_pac_contains_asserted_identity(
+ krb5_context context,
+ const struct samba_kdc_entry_pac entry,
+ bool *contains_out)
+{
+ TALLOC_CTX *frame = NULL;
+ struct auth_user_info_dc *info = NULL;
+ krb5_error_code ret = 0;
+
+ if (contains_out == NULL) {
+ ret = EINVAL;
+ goto out;
+ }
+ *contains_out = false;
+
+ frame = talloc_stackframe();
+
+ /*
+ * Extract our info from the PAC. This does a bit of unnecessary work,
+ * setting up fields we don’t care about — we only want the SIDs.
+ */
+ ret = kerberos_pac_to_user_info_dc(frame,
+ entry.pac,
+ context,
+ &info,
+ AUTH_EXCLUDE_RESOURCE_GROUPS,
+ NULL /* pac_srv_sig */,
+ NULL /* pac_kdc_sig */,
+ /* Ignore the resource groups. */
+ NULL /* resource_groups */);
+ if (ret) {
+ const char *krb5err = krb5_get_error_message(context, ret);
+ DBG_ERR("kerberos_pac_to_user_info_dc failed: %s\n",
+ krb5err != NULL ? krb5err : "?");
+ krb5_free_error_message(context, krb5err);
+
+ goto out;
+ }
+
+ /* Determine whether the PAC contains the Asserted Identity SID. */
+ *contains_out = sid_attrs_contains_sid(
+ info->sids,
+ info->num_sids,
+ &global_sid_Asserted_Identity_Authentication_Authority);
+
+out:
+ talloc_free(frame);
+ return ret;
+}
+
+static krb5_error_code samba_kdc_get_user_info_from_pac(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ const struct samba_kdc_entry_pac entry,
+ const struct auth_user_info_dc **info_out,
+ const struct PAC_DOMAIN_GROUP_MEMBERSHIP **resource_groups_out)
+{
+ TALLOC_CTX *frame = NULL;
+ struct auth_user_info_dc *info = NULL;
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP *resource_groups = NULL;
+ krb5_error_code ret = 0;
+ NTSTATUS nt_status;
+
+ if (samdb == NULL) {
+ ret = EINVAL;
+ goto out;
+ }
+
+ if (!samba_krb5_pac_is_trusted(entry)) {
+ ret = EINVAL;
+ goto out;
+ }
+
+ if (info_out == NULL) {
+ ret = EINVAL;
+ goto out;
+ }
+
+ *info_out = NULL;
+ if (resource_groups_out != NULL) {
+ *resource_groups_out = NULL;
+ }
+
+ if (entry.entry == NULL || entry.entry->info_from_pac == NULL) {
+ frame = talloc_stackframe();
+
+ ret = kerberos_pac_to_user_info_dc(frame,
+ entry.pac,
+ context,
+ &info,
+ AUTH_EXCLUDE_RESOURCE_GROUPS,
+ NULL,
+ NULL,
+ &resource_groups);
+ if (ret) {
+ const char *krb5err = krb5_get_error_message(context, ret);
+ DBG_ERR("kerberos_pac_to_user_info_dc failed: %s\n",
+ krb5err != NULL ? krb5err : "?");
+ krb5_free_error_message(context, krb5err);
+
+ goto out;
+ }
+
+ /*
+ * We need to expand group memberships within our local domain,
+ * as the token might be generated by a trusted domain.
+ */
+ nt_status = authsam_update_user_info_dc(frame,
+ samdb,
+ info);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("authsam_update_user_info_dc failed: %s\n",
+ nt_errstr(nt_status));
+
+ ret = map_errno_from_nt_status(nt_status);
+ goto out;
+ }
+
+ if (entry.entry != NULL) {
+ entry.entry->info_from_pac = talloc_steal(entry.entry, info);
+ entry.entry->resource_groups_from_pac = talloc_steal(entry.entry, resource_groups);
+ }
+ }
+
+
+ if (entry.entry != NULL) {
+ /* Note: the caller does not own this! */
+ *info_out = entry.entry->info_from_pac;
+
+ if (resource_groups_out != NULL) {
+ /* Note: the caller does not own this! */
+ *resource_groups_out = entry.entry->resource_groups_from_pac;
+ }
+ } else {
+ *info_out = talloc_steal(mem_ctx, info);
+
+ if (resource_groups_out != NULL) {
+ *resource_groups_out = talloc_steal(mem_ctx, resource_groups);
+ }
+ }
+
+out:
+ talloc_free(frame);
+ return ret;
+}
+
+krb5_error_code samba_kdc_get_user_info_dc(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ const struct samba_kdc_entry_pac entry,
+ const struct auth_user_info_dc **info_out,
+ const struct PAC_DOMAIN_GROUP_MEMBERSHIP **resource_groups_out)
+{
+ const struct auth_user_info_dc *info = NULL;
+ struct auth_user_info_dc *info_shallow_copy = NULL;
+ bool pac_contains_asserted_identity = false;
+ krb5_error_code ret = 0;
+ NTSTATUS nt_status;
+
+ *info_out = NULL;
+ if (resource_groups_out != NULL) {
+ *resource_groups_out = NULL;
+ }
+
+ if (samba_krb5_pac_is_trusted(entry)) {
+ return samba_kdc_get_user_info_from_pac(mem_ctx,
+ context,
+ samdb,
+ entry,
+ info_out,
+ resource_groups_out);
+ }
+
+ if (entry.entry == NULL) {
+ return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ }
+
+ /*
+ * In this case the RWDC discards the PAC an RODC generated.
+ * Windows adds the asserted_identity in this case too.
+ *
+ * Note that SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION
+ * generates KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN.
+ * So we can always use
+ * SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY
+ * here.
+ */
+ ret = samba_kdc_get_user_info_from_db(mem_ctx,
+ samdb,
+ entry.entry,
+ entry.entry->msg,
+ &info);
+ if (ret) {
+ const char *krb5err = krb5_get_error_message(context, ret);
+ DBG_ERR("samba_kdc_get_user_info_from_db: %s\n",
+ krb5err != NULL ? krb5err : "?");
+ krb5_free_error_message(context, krb5err);
+
+ return KRB5KDC_ERR_TGT_REVOKED;
+ }
+
+ /* Make a shallow copy of the user_info_dc structure. */
+ nt_status = authsam_shallow_copy_user_info_dc(mem_ctx,
+ info,
+ &info_shallow_copy);
+ info = NULL;
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Failed to allocate user_info_dc SIDs: %s\n",
+ nt_errstr(nt_status));
+ return map_errno_from_nt_status(nt_status);
+ }
+
+ /* Determine whether the PAC contains the Asserted Identity SID. */
+ ret = samba_kdc_pac_contains_asserted_identity(
+ context, entry, &pac_contains_asserted_identity);
+ if (ret) {
+ return ret;
+ }
+
+ if (pac_contains_asserted_identity) {
+ nt_status = samba_kdc_add_asserted_identity(
+ SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY,
+ info_shallow_copy);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Failed to add asserted identity: %s\n",
+ nt_errstr(nt_status));
+ TALLOC_FREE(info_shallow_copy);
+ return KRB5KDC_ERR_TGT_REVOKED;
+ }
+ }
+
+ nt_status = samba_kdc_add_claims_valid(info_shallow_copy);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Failed to add Claims Valid: %s\n",
+ nt_errstr(nt_status));
+ TALLOC_FREE(info_shallow_copy);
+ return KRB5KDC_ERR_TGT_REVOKED;
+ }
+
+ *info_out = info_shallow_copy;
+
+ return 0;
+}
+
+static NTSTATUS samba_kdc_update_delegation_info_blob(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ const krb5_const_pac pac,
+ const krb5_const_principal server_principal,
+ const krb5_const_principal proxy_principal,
+ DATA_BLOB *new_blob)
+{
+ krb5_data old_data = {};
+ DATA_BLOB old_blob;
+ krb5_error_code ret;
+ NTSTATUS nt_status = NT_STATUS_OK;
+ enum ndr_err_code ndr_err;
+ union PAC_INFO info = {};
+ struct PAC_CONSTRAINED_DELEGATION _d = {};
+ struct PAC_CONSTRAINED_DELEGATION *d = NULL;
+ char *server = NULL;
+ char *proxy = NULL;
+ uint32_t i;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+
+ if (tmp_ctx == NULL) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_CONSTRAINED_DELEGATION, &old_data);
+ if (ret == ENOENT) {
+ /* OK. */
+ } else if (ret) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+ old_blob.length = old_data.length;
+ old_blob.data = (uint8_t *)old_data.data;
+
+ if (old_blob.length > 0) {
+ ndr_err = ndr_pull_union_blob(&old_blob, tmp_ctx,
+ &info, PAC_TYPE_CONSTRAINED_DELEGATION,
+ (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ smb_krb5_free_data_contents(context, &old_data);
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("can't parse the PAC LOGON_INFO: %s\n", nt_errstr(nt_status));
+ goto out;
+ }
+ } else {
+ info.constrained_delegation.info = &_d;
+ }
+ smb_krb5_free_data_contents(context, &old_data);
+
+ ret = krb5_unparse_name_flags(context, server_principal,
+ KRB5_PRINCIPAL_UNPARSE_NO_REALM, &server);
+ if (ret) {
+ nt_status = NT_STATUS_INTERNAL_ERROR;
+ goto out;
+ }
+
+ ret = krb5_unparse_name(context, proxy_principal, &proxy);
+ if (ret) {
+ SAFE_FREE(server);
+ nt_status = NT_STATUS_INTERNAL_ERROR;
+ goto out;
+ }
+
+ d = info.constrained_delegation.info;
+ i = d->num_transited_services;
+ d->proxy_target.string = server;
+ d->transited_services = talloc_realloc(mem_ctx, d->transited_services,
+ struct lsa_String, i + 1);
+ if (d->transited_services == NULL) {
+ SAFE_FREE(server);
+ SAFE_FREE(proxy);
+ nt_status = NT_STATUS_INTERNAL_ERROR;
+ goto out;
+ }
+ d->transited_services[i].string = proxy;
+ d->num_transited_services = i + 1;
+
+ ndr_err = ndr_push_union_blob(new_blob, mem_ctx,
+ &info, PAC_TYPE_CONSTRAINED_DELEGATION,
+ (ndr_push_flags_fn_t)ndr_push_PAC_INFO);
+ SAFE_FREE(server);
+ SAFE_FREE(proxy);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ smb_krb5_free_data_contents(context, &old_data);
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("can't parse the PAC LOGON_INFO: %s\n", nt_errstr(nt_status));
+ goto out;
+ }
+
+out:
+ talloc_free(tmp_ctx);
+ return nt_status;
+}
+
+/* function to map policy errors */
+krb5_error_code samba_kdc_map_policy_err(NTSTATUS nt_status)
+{
+ krb5_error_code ret;
+
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_PASSWORD_MUST_CHANGE))
+ ret = KRB5KDC_ERR_KEY_EXP;
+ else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_PASSWORD_EXPIRED))
+ ret = KRB5KDC_ERR_KEY_EXP;
+ else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_EXPIRED))
+ ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_DISABLED))
+ ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_LOGON_HOURS))
+ ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_LOCKED_OUT))
+ ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_WORKSTATION))
+ ret = KRB5KDC_ERR_POLICY;
+ else
+ ret = KRB5KDC_ERR_POLICY;
+
+ return ret;
+}
+
+/* Given a kdc entry, consult the account_ok routine in auth/auth_sam.c
+ * for consistency */
+NTSTATUS samba_kdc_check_client_access(struct samba_kdc_entry *kdc_entry,
+ const char *client_name,
+ const char *workstation,
+ bool password_change)
+{
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS nt_status;
+
+ tmp_ctx = talloc_named(NULL, 0, "samba_kdc_check_client_access");
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* we allow all kinds of trusts here */
+ nt_status = authsam_account_ok(tmp_ctx,
+ kdc_entry->kdc_db_ctx->samdb,
+ MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT |
+ MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT,
+ kdc_entry->realm_dn, kdc_entry->msg,
+ workstation, client_name,
+ true, password_change);
+
+ kdc_entry->reject_status = nt_status;
+ talloc_free(tmp_ctx);
+ return nt_status;
+}
+
+static krb5_error_code samba_get_requester_sid(TALLOC_CTX *mem_ctx,
+ krb5_const_pac pac,
+ krb5_context context,
+ struct dom_sid *sid)
+{
+ NTSTATUS nt_status;
+ enum ndr_err_code ndr_err;
+ krb5_error_code ret = 0;
+
+ DATA_BLOB pac_requester_sid_in;
+ krb5_data k5pac_requester_sid_in;
+
+ union PAC_INFO info;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_REQUESTER_SID,
+ &k5pac_requester_sid_in);
+ if (ret != 0) {
+ goto out;
+ }
+
+ pac_requester_sid_in = data_blob_const(k5pac_requester_sid_in.data,
+ k5pac_requester_sid_in.length);
+
+ ndr_err = ndr_pull_union_blob(&pac_requester_sid_in, tmp_ctx, &info,
+ PAC_TYPE_REQUESTER_SID,
+ (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO);
+ smb_krb5_free_data_contents(context, &k5pac_requester_sid_in);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("can't parse the PAC REQUESTER_SID: %s\n", nt_errstr(nt_status));
+ ret = map_errno_from_nt_status(nt_status);
+ goto out;
+ }
+
+ *sid = info.requester_sid.sid;
+
+out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/* Does a parse and SID check, but no crypto. */
+static krb5_error_code samba_kdc_validate_pac_blob(
+ krb5_context context,
+ const struct samba_kdc_entry_pac client)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct auth_user_info_dc *pac_user_info = NULL;
+ struct dom_sid client_sid;
+ struct dom_sid pac_sid;
+ krb5_error_code code;
+ bool ok;
+
+ /*
+ * First, try to get the SID from the requester SID buffer in the PAC.
+ */
+ code = samba_get_requester_sid(frame, client.pac, context, &pac_sid);
+
+ if (code == ENOENT) {
+ /*
+ * If the requester SID buffer isn't present, fall back to the
+ * SID in the LOGON_INFO PAC buffer.
+ */
+ code = kerberos_pac_to_user_info_dc(frame,
+ client.pac,
+ context,
+ &pac_user_info,
+ AUTH_EXCLUDE_RESOURCE_GROUPS,
+ NULL,
+ NULL,
+ NULL);
+ if (code != 0) {
+ goto out;
+ }
+
+ if (pac_user_info->num_sids == 0) {
+ code = EINVAL;
+ goto out;
+ }
+
+ pac_sid = pac_user_info->sids[PRIMARY_USER_SID_INDEX].sid;
+ } else if (code != 0) {
+ goto out;
+ }
+
+ code = samdb_result_dom_sid_buf(client.entry->msg,
+ "objectSid",
+ &client_sid);
+ if (code) {
+ goto out;
+ }
+
+ ok = dom_sid_equal(&pac_sid, &client_sid);
+ if (!ok) {
+ struct dom_sid_buf buf1;
+ struct dom_sid_buf buf2;
+
+ DBG_ERR("SID mismatch between PAC and looked up client: "
+ "PAC[%s] != CLI[%s]\n",
+ dom_sid_str_buf(&pac_sid, &buf1),
+ dom_sid_str_buf(&client_sid, &buf2));
+ code = KRB5KDC_ERR_TGT_REVOKED;
+ goto out;
+ }
+
+ code = 0;
+out:
+ TALLOC_FREE(frame);
+ return code;
+}
+
+
+/*
+ * In the RODC case, to confirm that the returned user is permitted to
+ * be replicated to the KDC (krbgtgt_xxx user) represented by *rodc
+ */
+static WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_object_sids,
+ const struct dom_sid *object_sids,
+ const struct samba_kdc_entry *rodc,
+ const struct samba_kdc_entry *object)
+{
+ int ret;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *rodc_attrs[] = { "msDS-KrbTgtLink",
+ "msDS-NeverRevealGroup",
+ "msDS-RevealOnDemandGroup",
+ "userAccountControl",
+ "objectSid",
+ NULL };
+ struct ldb_result *rodc_machine_account = NULL;
+ struct ldb_dn *rodc_machine_account_dn = samdb_result_dn(rodc->kdc_db_ctx->samdb,
+ frame,
+ rodc->msg,
+ "msDS-KrbTgtLinkBL",
+ NULL);
+ const struct dom_sid *rodc_machine_account_sid = NULL;
+
+ if (rodc_machine_account_dn == NULL) {
+ DBG_ERR("krbtgt account %s has no msDS-KrbTgtLinkBL to find RODC machine account for allow/deny list\n",
+ ldb_dn_get_linearized(rodc->msg->dn));
+ TALLOC_FREE(frame);
+ return WERR_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+
+ /*
+ * Follow the link and get the RODC account (the krbtgt
+ * account is the krbtgt_XXX account, but the
+ * msDS-NeverRevealGroup and msDS-RevealOnDemandGroup is on
+ * the RODC$ account)
+ *
+ * We need DSDB_SEARCH_SHOW_EXTENDED_DN as we get a SID lists
+ * out of the extended DNs
+ */
+
+ ret = dsdb_search_dn(rodc->kdc_db_ctx->samdb,
+ frame,
+ &rodc_machine_account,
+ rodc_machine_account_dn,
+ rodc_attrs,
+ DSDB_SEARCH_SHOW_EXTENDED_DN);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Failed to fetch RODC machine account %s pointed to by %s to check allow/deny list: %s\n",
+ ldb_dn_get_linearized(rodc_machine_account_dn),
+ ldb_dn_get_linearized(rodc->msg->dn),
+ ldb_errstring(rodc->kdc_db_ctx->samdb));
+ TALLOC_FREE(frame);
+ return WERR_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+
+ if (rodc_machine_account->count != 1) {
+ DBG_ERR("Failed to fetch RODC machine account %s pointed to by %s to check allow/deny list: (%d)\n",
+ ldb_dn_get_linearized(rodc_machine_account_dn),
+ ldb_dn_get_linearized(rodc->msg->dn),
+ rodc_machine_account->count);
+ TALLOC_FREE(frame);
+ return WERR_DS_DRA_BAD_DN;
+ }
+
+ /* if the object SID is equal to the user_sid, allow */
+ rodc_machine_account_sid = samdb_result_dom_sid(frame,
+ rodc_machine_account->msgs[0],
+ "objectSid");
+ if (rodc_machine_account_sid == NULL) {
+ TALLOC_FREE(frame);
+ return WERR_DS_DRA_BAD_DN;
+ }
+
+ werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(rodc->kdc_db_ctx->samdb,
+ rodc_machine_account_sid,
+ rodc_machine_account->msgs[0],
+ object->msg,
+ num_object_sids,
+ object_sids);
+
+ TALLOC_FREE(frame);
+ return werr;
+}
+
+/*
+ * Perform an access check for the client attempting to authenticate to the
+ * server. ‘client_info’ must be talloc-allocated so that we can make a
+ * reference to it.
+ */
+krb5_error_code samba_kdc_allowed_to_authenticate_to(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct loadparm_context *lp_ctx,
+ const struct samba_kdc_entry *client,
+ const struct auth_user_info_dc *client_info,
+ const struct auth_user_info_dc *device_info,
+ const struct auth_claims auth_claims,
+ const struct samba_kdc_entry *server,
+ struct authn_audit_info **server_audit_info_out,
+ NTSTATUS *status_out)
+{
+ krb5_error_code ret = 0;
+ NTSTATUS status;
+ _UNUSED_ NTSTATUS _status;
+ struct dom_sid server_sid = {};
+ const struct authn_server_policy *server_policy = server->server_policy;
+
+ if (status_out != NULL) {
+ *status_out = NT_STATUS_OK;
+ }
+
+ ret = samdb_result_dom_sid_buf(server->msg, "objectSid", &server_sid);
+ if (ret) {
+ /*
+ * Ignore the return status — we are already in an error path,
+ * and overwriting the real error code with the audit info
+ * status is unhelpful.
+ */
+ _status = authn_server_policy_audit_info(mem_ctx,
+ server_policy,
+ client_info,
+ AUTHN_AUDIT_EVENT_OTHER_ERROR,
+ AUTHN_AUDIT_REASON_NONE,
+ dsdb_ldb_err_to_ntstatus(ret),
+ server_audit_info_out);
+ goto out;
+ }
+
+ if (dom_sid_equal(&client_info->sids[PRIMARY_USER_SID_INDEX].sid, &server_sid)) {
+ /* Authenticating to ourselves is always allowed. */
+ status = authn_server_policy_audit_info(mem_ctx,
+ server_policy,
+ client_info,
+ AUTHN_AUDIT_EVENT_OK,
+ AUTHN_AUDIT_REASON_NONE,
+ NT_STATUS_OK,
+ server_audit_info_out);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = KRB5KRB_ERR_GENERIC;
+ }
+ goto out;
+ }
+
+ status = authn_policy_authenticate_to_service(mem_ctx,
+ samdb,
+ lp_ctx,
+ AUTHN_POLICY_AUTH_TYPE_KERBEROS,
+ client_info,
+ device_info,
+ auth_claims,
+ server_policy,
+ (struct authn_policy_flags) { .force_compounded_authentication = true },
+ server_audit_info_out);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (status_out != NULL) {
+ *status_out = status;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_AUTHENTICATION_FIREWALL_FAILED)) {
+ ret = KRB5KDC_ERR_POLICY;
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ ret = KRB5KDC_ERR_POLICY;
+ } else {
+ ret = KRB5KRB_ERR_GENERIC;
+ }
+ }
+
+out:
+ return ret;
+}
+
+static krb5_error_code samba_kdc_add_domain_group_sid(struct PAC_DEVICE_INFO *info,
+ const struct netr_SidAttr *sid)
+{
+ uint32_t i;
+ uint32_t rid;
+ NTSTATUS status;
+
+ uint32_t domain_group_count = info->domain_group_count;
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP *domain_group = NULL;
+ struct samr_RidWithAttribute *rids = NULL;
+
+ for (i = 0; i < domain_group_count; ++i) {
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP *this_domain_group
+ = &info->domain_groups[i];
+
+ if (dom_sid_in_domain(this_domain_group->domain_sid, sid->sid)) {
+ domain_group = this_domain_group;
+ break;
+ }
+ }
+
+ if (domain_group == NULL) {
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP *domain_groups = NULL;
+
+ if (domain_group_count == UINT32_MAX) {
+ return EINVAL;
+ }
+
+ domain_groups = talloc_realloc(
+ info,
+ info->domain_groups,
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP,
+ domain_group_count + 1);
+ if (domain_groups == NULL) {
+ return ENOMEM;
+ }
+
+ info->domain_groups = domain_groups;
+
+ domain_group = &info->domain_groups[domain_group_count++];
+ *domain_group = (struct PAC_DOMAIN_GROUP_MEMBERSHIP) {};
+
+ status = dom_sid_split_rid(info->domain_groups,
+ sid->sid,
+ &domain_group->domain_sid,
+ &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return map_errno_from_nt_status(status);
+ }
+ } else {
+ status = dom_sid_split_rid(NULL,
+ sid->sid,
+ NULL,
+ &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return map_errno_from_nt_status(status);
+ }
+ }
+
+ if (domain_group->groups.count == UINT32_MAX) {
+ return EINVAL;
+ }
+
+ rids = talloc_realloc(info->domain_groups,
+ domain_group->groups.rids,
+ struct samr_RidWithAttribute,
+ domain_group->groups.count + 1);
+ if (rids == NULL) {
+ return ENOMEM;
+ }
+
+ domain_group->groups.rids = rids;
+
+ domain_group->groups.rids[domain_group->groups.count] = (struct samr_RidWithAttribute) {
+ .rid = rid,
+ .attributes = sid->attributes,
+ };
+
+ ++domain_group->groups.count;
+
+ info->domain_group_count = domain_group_count;
+
+ return 0;
+}
+
+static krb5_error_code samba_kdc_make_device_info(TALLOC_CTX *mem_ctx,
+ const struct netr_SamInfo3 *info3,
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP *resource_groups,
+ union PAC_INFO *info)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct PAC_DEVICE_INFO *device_info = NULL;
+ uint32_t i;
+ krb5_error_code ret = 0;
+
+ *info = (union PAC_INFO) {};
+
+ info->device_info.info = NULL;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ device_info = talloc(tmp_ctx, struct PAC_DEVICE_INFO);
+ if (device_info == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ device_info->rid = info3->base.rid;
+ device_info->primary_gid = info3->base.primary_gid;
+ device_info->domain_sid = info3->base.domain_sid;
+ device_info->groups = info3->base.groups;
+
+ device_info->sid_count = 0;
+ device_info->sids = NULL;
+
+ if (resource_groups != NULL) {
+ /*
+ * The account's resource groups all belong to the same domain,
+ * so we can add them all in one go.
+ */
+ device_info->domain_group_count = 1;
+ device_info->domain_groups = talloc_move(device_info, &resource_groups);
+ } else {
+ device_info->domain_group_count = 0;
+ device_info->domain_groups = NULL;
+ }
+
+ for (i = 0; i < info3->sidcount; ++i) {
+ const struct netr_SidAttr *device_sid = &info3->sids[i];
+
+ if (dom_sid_has_account_domain(device_sid->sid)) {
+ ret = samba_kdc_add_domain_group_sid(device_info, device_sid);
+ if (ret != 0) {
+ goto out;
+ }
+ } else {
+ device_info->sids = talloc_realloc(device_info, device_info->sids,
+ struct netr_SidAttr,
+ device_info->sid_count + 1);
+ if (device_info->sids == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ device_info->sids[device_info->sid_count].sid = dom_sid_dup(device_info->sids, device_sid->sid);
+ if (device_info->sids[device_info->sid_count].sid == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ device_info->sids[device_info->sid_count].attributes = device_sid->attributes;
+
+ ++device_info->sid_count;
+ }
+ }
+
+ info->device_info.info = talloc_steal(mem_ctx, device_info);
+
+out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static krb5_error_code samba_kdc_update_device_info(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ const union PAC_INFO *logon_info,
+ struct PAC_DEVICE_INFO *device_info)
+{
+ NTSTATUS nt_status;
+ struct auth_user_info_dc *device_info_dc = NULL;
+ union netr_Validation validation;
+ uint32_t i;
+ uint32_t num_existing_sids;
+
+ /*
+ * This does a bit of unnecessary work, setting up fields we don't care
+ * about -- we only want the SIDs.
+ */
+ validation.sam3 = &logon_info->logon_info.info->info3;
+ nt_status = make_user_info_dc_netlogon_validation(mem_ctx, "", 3, &validation,
+ true, /* This user was authenticated */
+ &device_info_dc);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return map_errno_from_nt_status(nt_status);
+ }
+
+ num_existing_sids = device_info_dc->num_sids;
+
+ /*
+ * We need to expand group memberships within our local domain,
+ * as the token might be generated by a trusted domain.
+ */
+ nt_status = authsam_update_user_info_dc(mem_ctx,
+ samdb,
+ device_info_dc);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return map_errno_from_nt_status(nt_status);
+ }
+
+ for (i = num_existing_sids; i < device_info_dc->num_sids; ++i) {
+ struct auth_SidAttr *device_sid = &device_info_dc->sids[i];
+ const struct netr_SidAttr sid = (struct netr_SidAttr) {
+ .sid = &device_sid->sid,
+ .attributes = device_sid->attrs,
+ };
+
+ krb5_error_code ret = samba_kdc_add_domain_group_sid(device_info, &sid);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static krb5_error_code samba_kdc_get_device_info_pac_blob(TALLOC_CTX *mem_ctx,
+ union PAC_INFO *info,
+ DATA_BLOB **_device_info_blob)
+{
+ DATA_BLOB *device_info_blob = NULL;
+ enum ndr_err_code ndr_err;
+
+ *_device_info_blob = NULL;
+
+ device_info_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (device_info_blob == NULL) {
+ DBG_ERR("Out of memory\n");
+ return ENOMEM;
+ }
+
+ ndr_err = ndr_push_union_blob(device_info_blob, device_info_blob,
+ info, PAC_TYPE_DEVICE_INFO,
+ (ndr_push_flags_fn_t)ndr_push_PAC_INFO);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("PAC_DEVICE_INFO (presig) push failed: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(device_info_blob);
+ return map_errno_from_nt_status(nt_status);
+ }
+
+ *_device_info_blob = device_info_blob;
+
+ return 0;
+}
+
+static krb5_error_code samba_kdc_create_device_info_blob(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ const krb5_const_pac device_pac,
+ DATA_BLOB **device_info_blob)
+{
+ TALLOC_CTX *frame = NULL;
+ krb5_data device_logon_info;
+ krb5_error_code code = EINVAL;
+ NTSTATUS nt_status;
+
+ union PAC_INFO info;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB device_logon_info_blob;
+
+ union PAC_INFO logon_info;
+
+ code = krb5_pac_get_buffer(context, device_pac,
+ PAC_TYPE_LOGON_INFO,
+ &device_logon_info);
+ if (code != 0) {
+ if (code == ENOENT) {
+ DBG_ERR("Device PAC is missing LOGON_INFO\n");
+ } else {
+ DBG_ERR("Error getting LOGON_INFO from device PAC\n");
+ }
+ return code;
+ }
+
+ frame = talloc_stackframe();
+
+ device_logon_info_blob = data_blob_const(device_logon_info.data,
+ device_logon_info.length);
+
+ ndr_err = ndr_pull_union_blob(&device_logon_info_blob, frame, &logon_info,
+ PAC_TYPE_LOGON_INFO,
+ (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO);
+ smb_krb5_free_data_contents(context, &device_logon_info);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("can't parse device PAC LOGON_INFO: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(frame);
+ return map_errno_from_nt_status(nt_status);
+ }
+
+ /*
+ * When creating the device info structure, existing resource groups are
+ * discarded.
+ */
+ code = samba_kdc_make_device_info(frame,
+ &logon_info.logon_info.info->info3,
+ NULL, /* resource_groups */
+ &info);
+ if (code != 0) {
+ talloc_free(frame);
+ return code;
+ }
+
+ code = samba_kdc_update_device_info(frame,
+ samdb,
+ &logon_info,
+ info.device_info.info);
+ if (code != 0) {
+ talloc_free(frame);
+ return code;
+ }
+
+ code = samba_kdc_get_device_info_pac_blob(mem_ctx,
+ &info,
+ device_info_blob);
+
+ talloc_free(frame);
+ return code;
+}
+
+static krb5_error_code samba_kdc_get_device_info_blob(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ const struct samba_kdc_entry_pac device,
+ DATA_BLOB **device_info_blob)
+{
+ TALLOC_CTX *frame = NULL;
+ krb5_error_code code = EINVAL;
+ NTSTATUS nt_status;
+
+ const struct auth_user_info_dc *device_info = NULL;
+ struct netr_SamInfo3 *info3 = NULL;
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP *resource_groups = NULL;
+
+ union PAC_INFO info;
+
+ frame = talloc_stackframe();
+
+ code = samba_kdc_get_user_info_dc(frame,
+ context,
+ samdb,
+ device,
+ &device_info,
+ NULL /* resource_groups_out */);
+ if (code) {
+ const char *krb5_err = krb5_get_error_message(context, code);
+ DBG_ERR("samba_kdc_get_user_info_dc failed: %s\n",
+ krb5_err != NULL ? krb5_err : "<unknown>");
+ krb5_free_error_message(context, krb5_err);
+
+ talloc_free(frame);
+ return KRB5KDC_ERR_TGT_REVOKED;
+ }
+
+ nt_status = auth_convert_user_info_dc_saminfo3(frame, device_info,
+ AUTH_INCLUDE_RESOURCE_GROUPS_COMPRESSED,
+ &info3,
+ &resource_groups);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_WARNING("Getting Samba info failed: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(frame);
+ return nt_status_to_krb5(nt_status);
+ }
+
+ code = samba_kdc_make_device_info(frame,
+ info3,
+ resource_groups,
+ &info);
+ if (code != 0) {
+ talloc_free(frame);
+ return code;
+ }
+
+ code = samba_kdc_get_device_info_pac_blob(mem_ctx,
+ &info,
+ device_info_blob);
+
+ talloc_free(frame);
+ return code;
+}
+
+/**
+ * @brief Verify a PAC
+ *
+ * @param mem_ctx A talloc memory context
+ *
+ * @param context A krb5 context
+ *
+ * @param samdb An open samdb connection.
+ *
+ * @param flags Bitwise OR'ed flags
+ *
+ * @param client The client samba kdc PAC entry.
+
+ * @param krbtgt The krbtgt samba kdc entry.
+ *
+ * @return A Kerberos error code.
+ */
+krb5_error_code samba_kdc_verify_pac(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ uint32_t flags,
+ const struct samba_kdc_entry_pac client,
+ const struct samba_kdc_entry *krbtgt)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct pac_blobs *pac_blobs = NULL;
+ krb5_error_code code = EINVAL;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ code = ENOMEM;
+ goto done;
+ }
+
+ if (client.entry != NULL) {
+ /*
+ * Check the objectSID of the client and pac data are the same.
+ * Does a parse and SID check, but no crypto.
+ */
+ code = samba_kdc_validate_pac_blob(context, client);
+ if (code != 0) {
+ goto done;
+ }
+ }
+
+ if (!samba_krb5_pac_is_trusted(client)) {
+ const struct auth_user_info_dc *user_info_dc = NULL;
+ WERROR werr;
+
+ struct dom_sid *object_sids = NULL;
+ uint32_t j;
+
+ if (client.entry == NULL) {
+ code = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ goto done;
+ }
+
+ code = samba_kdc_get_user_info_from_db(tmp_ctx,
+ samdb,
+ client.entry,
+ client.entry->msg,
+ &user_info_dc);
+ if (code) {
+ const char *krb5_err = krb5_get_error_message(context, code);
+ DBG_ERR("Getting user info for PAC failed: %s\n",
+ krb5_err != NULL ? krb5_err : "<unknown>");
+ krb5_free_error_message(context, krb5_err);
+
+ code = KRB5KDC_ERR_TGT_REVOKED;
+ goto done;
+ }
+
+ /*
+ * Check if the SID list in the user_info_dc intersects
+ * correctly with the RODC allow/deny lists.
+ */
+ object_sids = talloc_array(tmp_ctx, struct dom_sid, user_info_dc->num_sids);
+ if (object_sids == NULL) {
+ code = ENOMEM;
+ goto done;
+ }
+
+ for (j = 0; j < user_info_dc->num_sids; ++j) {
+ object_sids[j] = user_info_dc->sids[j].sid;
+ }
+
+ werr = samba_rodc_confirm_user_is_allowed(user_info_dc->num_sids,
+ object_sids,
+ krbtgt,
+ client.entry);
+ if (!W_ERROR_IS_OK(werr)) {
+ code = KRB5KDC_ERR_TGT_REVOKED;
+ if (W_ERROR_EQUAL(werr,
+ WERR_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ code = KRB5KDC_ERR_POLICY;
+ }
+ goto done;
+ }
+
+ /*
+ * The RODC PAC data isn't trusted for authorization as it may
+ * be stale. The only thing meaningful we can do with an RODC
+ * account on a full DC is exchange the RODC TGT for a 'real'
+ * TGT.
+ *
+ * So we match Windows (at least server 2022) and
+ * don't allow S4U2Self.
+ *
+ * https://lists.samba.org/archive/cifs-protocol/2022-April/003673.html
+ */
+ if (flags & SAMBA_KDC_FLAG_PROTOCOL_TRANSITION) {
+ code = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ goto done;
+ }
+ }
+
+ /* Check the types of the given PAC */
+
+ code = pac_blobs_from_krb5_pac(tmp_ctx,
+ context,
+ client.pac,
+ &pac_blobs);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = pac_blobs_ensure_exists(pac_blobs,
+ PAC_TYPE_LOGON_INFO);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = pac_blobs_ensure_exists(pac_blobs,
+ PAC_TYPE_LOGON_NAME);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = pac_blobs_ensure_exists(pac_blobs,
+ PAC_TYPE_SRV_CHECKSUM);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = pac_blobs_ensure_exists(pac_blobs,
+ PAC_TYPE_KDC_CHECKSUM);
+ if (code != 0) {
+ goto done;
+ }
+
+ if (!(flags & SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION)) {
+ code = pac_blobs_ensure_exists(pac_blobs,
+ PAC_TYPE_REQUESTER_SID);
+ if (code != 0) {
+ code = KRB5KDC_ERR_TGT_REVOKED;
+ goto done;
+ }
+ }
+
+ code = 0;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return code;
+}
+
+/**
+ * @brief Update a PAC
+ *
+ * @param mem_ctx A talloc memory context
+ *
+ * @param context A krb5 context
+ *
+ * @param samdb An open samdb connection.
+ *
+ * @param lp_ctx A loadparm context.
+ *
+ * @param flags Bitwise OR'ed flags
+ *
+ * @param device_pac_is_trusted Whether the device's PAC was issued by a trusted server,
+ * as opposed to an RODC.
+ *
+ * @param client The client samba kdc PAC entry.
+ *
+ * @param server_principal The server principal
+ *
+ * @param server The server samba kdc entry.
+ *
+ * @param delegated_proxy_principal The delegated proxy principal used for
+ * updating the constrained delegation PAC
+ * buffer.
+ *
+ * @param delegated_proxy The delegated proxy kdc PAC entry.
+ *
+ * @param device The computer's samba kdc PAC entry; used for compound
+ * authentication.
+ *
+ * @param new_pac The new already allocated PAC
+ *
+ * @return A Kerberos error code. If no PAC should be returned, the code will be
+ * ENOATTR!
+ */
+krb5_error_code samba_kdc_update_pac(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ struct loadparm_context *lp_ctx,
+ uint32_t flags,
+ const struct samba_kdc_entry_pac client,
+ const krb5_const_principal server_principal,
+ const struct samba_kdc_entry *server,
+ const krb5_const_principal delegated_proxy_principal,
+ const struct samba_kdc_entry_pac delegated_proxy,
+ const struct samba_kdc_entry_pac device,
+ krb5_pac new_pac,
+ struct authn_audit_info **server_audit_info_out,
+ NTSTATUS *status_out)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ krb5_error_code code = EINVAL;
+ NTSTATUS nt_status;
+ DATA_BLOB *pac_blob = NULL;
+ DATA_BLOB *upn_blob = NULL;
+ DATA_BLOB *deleg_blob = NULL;
+ DATA_BLOB *requester_sid_blob = NULL;
+ const DATA_BLOB *client_claims_blob = NULL;
+ DATA_BLOB device_claims_blob = {};
+ const DATA_BLOB *device_claims_blob_ptr = NULL;
+ struct auth_claims auth_claims = {};
+ DATA_BLOB *device_info_blob = NULL;
+ bool is_tgs = false;
+ bool server_restrictions_present = false;
+ struct pac_blobs *pac_blobs = NULL;
+ const struct auth_user_info_dc *user_info_dc_const = NULL;
+ struct auth_user_info_dc *user_info_dc_shallow_copy = NULL;
+ const struct PAC_DOMAIN_GROUP_MEMBERSHIP *_resource_groups = NULL;
+ enum auth_group_inclusion group_inclusion;
+ bool compounded_auth;
+ size_t i = 0;
+
+ if (server_audit_info_out != NULL) {
+ *server_audit_info_out = NULL;
+ }
+
+ if (status_out != NULL) {
+ *status_out = NT_STATUS_OK;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ code = ENOMEM;
+ goto done;
+ }
+
+ {
+ int result = smb_krb5_principal_is_tgs(context, server_principal);
+ if (result == -1) {
+ code = ENOMEM;
+ goto done;
+ }
+
+ is_tgs = result;
+ }
+
+ server_restrictions_present = !is_tgs && authn_policy_restrictions_present(server->server_policy);
+
+ /* Only include resource groups in a service ticket. */
+ if (is_tgs) {
+ group_inclusion = AUTH_EXCLUDE_RESOURCE_GROUPS;
+ } else if (server->supported_enctypes & KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED) {
+ group_inclusion = AUTH_INCLUDE_RESOURCE_GROUPS;
+ } else {
+ group_inclusion = AUTH_INCLUDE_RESOURCE_GROUPS_COMPRESSED;
+ }
+
+ compounded_auth = device.entry != NULL && !is_tgs
+ && server->supported_enctypes & KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED;
+
+ if (compounded_auth || (server_restrictions_present && device.entry != NULL)) {
+ /*
+ * [MS-KILE] 3.3.5.7.4 Compound Identity: the client claims from
+ * the device PAC become the device claims in the new PAC.
+ */
+ code = samba_kdc_get_claims_data(tmp_ctx,
+ context,
+ samdb,
+ device,
+ &auth_claims.device_claims);
+ if (code) {
+ goto done;
+ }
+
+ if (compounded_auth) {
+ nt_status = claims_data_encoded_claims_set(tmp_ctx,
+ auth_claims.device_claims,
+ &device_claims_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("claims_data_encoded_claims_set failed: %s\n",
+ nt_errstr(nt_status));
+ code = map_errno_from_nt_status(nt_status);
+ goto done;
+ }
+
+ device_claims_blob_ptr = &device_claims_blob;
+
+ if (samba_krb5_pac_is_trusted(device)) {
+ code = samba_kdc_create_device_info_blob(tmp_ctx,
+ context,
+ samdb,
+ device.pac,
+ &device_info_blob);
+ if (code != 0) {
+ goto done;
+ }
+ } else {
+ /* Don't trust an RODC‐issued PAC; regenerate the device info. */
+ code = samba_kdc_get_device_info_blob(tmp_ctx,
+ context,
+ samdb,
+ device,
+ &device_info_blob);
+ if (code != 0) {
+ goto done;
+ }
+ }
+ }
+ }
+
+ if (delegated_proxy_principal != NULL) {
+ deleg_blob = talloc_zero(tmp_ctx, DATA_BLOB);
+ if (deleg_blob == NULL) {
+ code = ENOMEM;
+ goto done;
+ }
+
+ nt_status = samba_kdc_update_delegation_info_blob(
+ deleg_blob,
+ context,
+ client.pac,
+ server_principal,
+ delegated_proxy_principal,
+ deleg_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("update delegation info blob failed: %s\n",
+ nt_errstr(nt_status));
+ code = map_errno_from_nt_status(nt_status);
+ goto done;
+ }
+ }
+
+ /*
+ * If we are creating a TGT, resource groups from our domain are not to
+ * be put into the PAC. Instead, we take the resource groups directly
+ * from the original PAC and copy them unmodified into the new one.
+ */
+ code = samba_kdc_get_user_info_dc(tmp_ctx,
+ context,
+ samdb,
+ client,
+ &user_info_dc_const,
+ is_tgs ? &_resource_groups : NULL);
+ if (code != 0) {
+ const char *err_str = krb5_get_error_message(context, code);
+ DBG_ERR("samba_kdc_get_user_info_dc failed: %s\n",
+ err_str != NULL ? err_str : "<unknown>");
+ krb5_free_error_message(context, err_str);
+
+ goto done;
+ }
+
+ /*
+ * Enforce the AllowedToAuthenticateTo part of an authentication policy,
+ * if one is present.
+ */
+ if (server_restrictions_present) {
+ struct samba_kdc_entry_pac auth_entry;
+ const struct auth_user_info_dc *auth_user_info_dc = NULL;
+ const struct auth_user_info_dc *device_info = NULL;
+
+ if (delegated_proxy.entry != NULL) {
+ auth_entry = delegated_proxy;
+
+ code = samba_kdc_get_user_info_dc(tmp_ctx,
+ context,
+ samdb,
+ delegated_proxy,
+ &auth_user_info_dc,
+ NULL /* resource_groups_out */);
+ if (code) {
+ goto done;
+ }
+ } else {
+ auth_entry = client;
+ auth_user_info_dc = user_info_dc_const;
+ }
+
+ /* Fetch the user’s claims. */
+ code = samba_kdc_get_claims_data(tmp_ctx,
+ context,
+ samdb,
+ auth_entry,
+ &auth_claims.user_claims);
+ if (code) {
+ goto done;
+ }
+
+ if (device.entry != NULL) {
+ code = samba_kdc_get_user_info_dc(tmp_ctx,
+ context,
+ samdb,
+ device,
+ &device_info,
+ NULL /* resource_groups_out */);
+ if (code) {
+ goto done;
+ }
+ }
+
+ /*
+ * Allocate the audit info and output status on to the parent
+ * mem_ctx, not the temporary context.
+ */
+ code = samba_kdc_allowed_to_authenticate_to(mem_ctx,
+ samdb,
+ lp_ctx,
+ auth_entry.entry,
+ auth_user_info_dc,
+ device_info,
+ auth_claims,
+ server,
+ server_audit_info_out,
+ status_out);
+ if (code) {
+ goto done;
+ }
+ }
+
+ if (compounded_auth) {
+ /* Make a shallow copy of the user_info_dc structure. */
+ nt_status = authsam_shallow_copy_user_info_dc(tmp_ctx,
+ user_info_dc_const,
+ &user_info_dc_shallow_copy);
+ user_info_dc_const = NULL;
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Failed to copy user_info_dc: %s\n",
+ nt_errstr(nt_status));
+
+ code = KRB5KDC_ERR_TGT_REVOKED;
+ goto done;
+ }
+
+ nt_status = samba_kdc_add_compounded_auth(user_info_dc_shallow_copy);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Failed to add Compounded Authentication: %s\n",
+ nt_errstr(nt_status));
+
+ code = KRB5KDC_ERR_TGT_REVOKED;
+ goto done;
+ }
+
+ /* We can now set back to the const, it will not be modified */
+ user_info_dc_const = user_info_dc_shallow_copy;
+ }
+
+ if (samba_krb5_pac_is_trusted(client)) {
+ pac_blob = talloc_zero(tmp_ctx, DATA_BLOB);
+ if (pac_blob == NULL) {
+ code = ENOMEM;
+ goto done;
+ }
+
+ nt_status = samba_get_logon_info_pac_blob(tmp_ctx,
+ user_info_dc_const,
+ _resource_groups,
+ group_inclusion,
+ pac_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("samba_get_logon_info_pac_blob failed: %s\n",
+ nt_errstr(nt_status));
+
+ code = map_errno_from_nt_status(nt_status);
+ goto done;
+ }
+
+ /*
+ * TODO: we need claim translation over trusts,
+ * for now we just clear them...
+ */
+ if (samba_kdc_entry_pac_issued_by_trust(client)) {
+ client_claims_blob = &data_blob_null;
+ }
+ } else {
+ nt_status = samba_kdc_get_logon_info_blob(tmp_ctx,
+ user_info_dc_const,
+ group_inclusion,
+ &pac_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("samba_kdc_get_logon_info_blob failed: %s\n",
+ nt_errstr(nt_status));
+ code = KRB5KDC_ERR_TGT_REVOKED;
+ goto done;
+ }
+
+ nt_status = samba_kdc_get_upn_info_blob(tmp_ctx,
+ user_info_dc_const,
+ &upn_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("samba_kdc_get_upn_info_blob failed: %s\n",
+ nt_errstr(nt_status));
+ code = KRB5KDC_ERR_TGT_REVOKED;
+ goto done;
+ }
+
+ if (is_tgs) {
+ nt_status = samba_kdc_get_requester_sid_blob(tmp_ctx,
+ user_info_dc_const,
+ &requester_sid_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("samba_kdc_get_requester_sid_blob failed: %s\n",
+ nt_errstr(nt_status));
+ code = KRB5KDC_ERR_TGT_REVOKED;
+ goto done;
+ }
+ }
+
+ /* Don't trust RODC-issued claims. Regenerate them. */
+ nt_status = samba_kdc_get_claims_blob(tmp_ctx,
+ client.entry,
+ &client_claims_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("samba_kdc_get_claims_blob failed: %s\n",
+ nt_errstr(nt_status));
+ code = map_errno_from_nt_status(nt_status);
+ goto done;
+ }
+ }
+
+ /* Check the types of the given PAC */
+ code = pac_blobs_from_krb5_pac(tmp_ctx,
+ context,
+ client.pac,
+ &pac_blobs);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = pac_blobs_replace_existing(pac_blobs,
+ PAC_TYPE_LOGON_INFO,
+ pac_blob);
+ if (code != 0) {
+ goto done;
+ }
+
+#ifdef SAMBA4_USES_HEIMDAL
+ /* Not needed with MIT Kerberos */
+ code = pac_blobs_replace_existing(pac_blobs,
+ PAC_TYPE_LOGON_NAME,
+ &data_blob_null);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = pac_blobs_replace_existing(pac_blobs,
+ PAC_TYPE_SRV_CHECKSUM,
+ &data_blob_null);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = pac_blobs_replace_existing(pac_blobs,
+ PAC_TYPE_KDC_CHECKSUM,
+ &data_blob_null);
+ if (code != 0) {
+ goto done;
+ }
+#endif
+
+ code = pac_blobs_add_blob(pac_blobs,
+ PAC_TYPE_CONSTRAINED_DELEGATION,
+ deleg_blob);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = pac_blobs_add_blob(pac_blobs,
+ PAC_TYPE_UPN_DNS_INFO,
+ upn_blob);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = pac_blobs_add_blob(pac_blobs,
+ PAC_TYPE_CLIENT_CLAIMS_INFO,
+ client_claims_blob);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = pac_blobs_add_blob(pac_blobs,
+ PAC_TYPE_DEVICE_INFO,
+ device_info_blob);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = pac_blobs_add_blob(pac_blobs,
+ PAC_TYPE_DEVICE_CLAIMS_INFO,
+ device_claims_blob_ptr);
+ if (code != 0) {
+ goto done;
+ }
+
+ if (!samba_krb5_pac_is_trusted(client) || !is_tgs) {
+ pac_blobs_remove_blob(pac_blobs,
+ PAC_TYPE_ATTRIBUTES_INFO);
+ }
+
+ if (!is_tgs) {
+ pac_blobs_remove_blob(pac_blobs,
+ PAC_TYPE_REQUESTER_SID);
+ }
+
+ code = pac_blobs_add_blob(pac_blobs,
+ PAC_TYPE_REQUESTER_SID,
+ requester_sid_blob);
+ if (code != 0) {
+ goto done;
+ }
+
+ /*
+ * The server account may be set not to want the PAC.
+ *
+ * While this is wasteful if the above calculations were done
+ * and now thrown away, this is cleaner as we do any ticket
+ * signature checking etc always.
+ *
+ * UF_NO_AUTH_DATA_REQUIRED is the rare case and most of the
+ * time (eg not accepting a ticket from the RODC) we do not
+ * need to re-generate anything anyway.
+ */
+ if (!samba_princ_needs_pac(server)) {
+ code = ENOATTR;
+ goto done;
+ }
+
+ if (samba_krb5_pac_is_trusted(client) && !is_tgs) {
+ /*
+ * The client may have requested no PAC when obtaining the
+ * TGT.
+ */
+ bool requested_pac = false;
+
+ code = samba_client_requested_pac(context,
+ client.pac,
+ tmp_ctx,
+ &requested_pac);
+ if (code != 0 || !requested_pac) {
+ if (!requested_pac) {
+ code = ENOATTR;
+ }
+ goto done;
+ }
+ }
+
+ for (i = 0; i < pac_blobs->num_types; ++i) {
+ krb5_data type_data;
+ const DATA_BLOB *type_blob = pac_blobs->type_blobs[i].data;
+ uint32_t type = pac_blobs->type_blobs[i].type;
+
+ static char null_byte = '\0';
+ const krb5_data null_data = smb_krb5_make_data(&null_byte, 0);
+
+#ifndef SAMBA4_USES_HEIMDAL
+ /* Not needed with MIT Kerberos */
+ switch(type) {
+ case PAC_TYPE_LOGON_NAME:
+ case PAC_TYPE_SRV_CHECKSUM:
+ case PAC_TYPE_KDC_CHECKSUM:
+ case PAC_TYPE_FULL_CHECKSUM:
+ continue;
+ default:
+ break;
+ }
+#endif
+
+ if (type_blob != NULL) {
+ type_data = smb_krb5_data_from_blob(*type_blob);
+ /*
+ * Passing a NULL pointer into krb5_pac_add_buffer() is
+ * not allowed, so pass null_data instead if needed.
+ */
+ code = krb5_pac_add_buffer(context,
+ new_pac,
+ type,
+ (type_data.data != NULL) ? &type_data : &null_data);
+ if (code != 0) {
+ goto done;
+ }
+ } else if (samba_krb5_pac_is_trusted(client)) {
+ /*
+ * Convey the buffer from the original PAC if we can
+ * trust it.
+ */
+
+ code = krb5_pac_get_buffer(context,
+ client.pac,
+ type,
+ &type_data);
+ if (code != 0) {
+ goto done;
+ }
+ /*
+ * Passing a NULL pointer into krb5_pac_add_buffer() is
+ * not allowed, so pass null_data instead if needed.
+ */
+ code = krb5_pac_add_buffer(context,
+ new_pac,
+ type,
+ (type_data.data != NULL) ? &type_data : &null_data);
+ smb_krb5_free_data_contents(context, &type_data);
+ if (code != 0) {
+ goto done;
+ }
+ }
+ }
+
+ code = 0;
+done:
+ TALLOC_FREE(tmp_ctx);
+ return code;
+}
+
+krb5_error_code samba_kdc_get_claims_data(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ struct samba_kdc_entry_pac entry,
+ struct claims_data **claims_data_out)
+{
+ if (samba_kdc_entry_pac_issued_by_trust(entry)) {
+ NTSTATUS status;
+
+ /*
+ * TODO: we need claim translation over trusts; for now we just
+ * clear them…
+ */
+ status = claims_data_from_encoded_claims_set(mem_ctx,
+ NULL,
+ claims_data_out);
+ if (!NT_STATUS_IS_OK(status)) {
+ return map_errno_from_nt_status(status);
+ }
+
+ return 0;
+ }
+
+ if (samba_krb5_pac_is_trusted(entry)) {
+ return samba_kdc_get_claims_data_from_pac(mem_ctx,
+ context,
+ entry,
+ claims_data_out);
+ }
+
+ return samba_kdc_get_claims_data_from_db(samdb,
+ entry.entry,
+ claims_data_out);
+}
+
+krb5_error_code samba_kdc_get_claims_data_from_pac(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct samba_kdc_entry_pac entry,
+ struct claims_data **claims_data_out)
+{
+ TALLOC_CTX *frame = NULL;
+ krb5_data claims_info = {};
+ struct claims_data *claims_data = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ krb5_error_code code;
+
+ if (!samba_krb5_pac_is_trusted(entry)) {
+ code = EINVAL;
+ goto out;
+ }
+
+ if (samba_kdc_entry_pac_issued_by_trust(entry)) {
+ code = EINVAL;
+ goto out;
+ }
+
+ if (claims_data_out == NULL) {
+ code = EINVAL;
+ goto out;
+ }
+
+ *claims_data_out = NULL;
+
+ if (entry.entry != NULL && entry.entry->claims_from_pac_are_initialized) {
+ /* Note: the caller does not own this! */
+ *claims_data_out = entry.entry->claims_from_pac;
+ return 0;
+ }
+
+ frame = talloc_stackframe();
+
+ /* Fetch the claims from the PAC. */
+ code = krb5_pac_get_buffer(context, entry.pac,
+ PAC_TYPE_CLIENT_CLAIMS_INFO,
+ &claims_info);
+ if (code == ENOENT) {
+ /* OK. */
+ } else if (code != 0) {
+ DBG_ERR("Error getting CLIENT_CLAIMS_INFO from PAC\n");
+ goto out;
+ } else if (claims_info.length) {
+ DATA_BLOB claims_blob = data_blob_const(claims_info.data,
+ claims_info.length);
+
+ status = claims_data_from_encoded_claims_set(frame,
+ &claims_blob,
+ &claims_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ code = map_errno_from_nt_status(status);
+ goto out;
+ }
+ }
+
+ if (entry.entry != NULL) {
+ /* Note: the caller does not own this! */
+ entry.entry->claims_from_pac = talloc_steal(entry.entry,
+ claims_data);
+ entry.entry->claims_from_pac_are_initialized = true;
+ } else {
+ talloc_steal(mem_ctx, claims_data);
+ }
+
+ *claims_data_out = claims_data;
+
+out:
+ smb_krb5_free_data_contents(context, &claims_info);
+ talloc_free(frame);
+ return code;
+}
+
+krb5_error_code samba_kdc_get_claims_data_from_db(struct ldb_context *samdb,
+ struct samba_kdc_entry *entry,
+ struct claims_data **claims_data_out)
+{
+ TALLOC_CTX *frame = NULL;
+
+ struct claims_data *claims_data = NULL;
+ struct CLAIMS_SET *claims_set = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ krb5_error_code code;
+
+ if (samdb == NULL) {
+ code = EINVAL;
+ goto out;
+ }
+
+ if (claims_data_out == NULL) {
+ code = EINVAL;
+ goto out;
+ }
+
+ if (entry == NULL) {
+ code = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ goto out;
+ }
+
+ *claims_data_out = NULL;
+
+ if (entry->claims_from_db_are_initialized) {
+ /* Note: the caller does not own this! */
+ *claims_data_out = entry->claims_from_db;
+ return 0;
+ }
+
+ frame = talloc_stackframe();
+
+ code = get_claims_set_for_principal(samdb,
+ frame,
+ entry->msg,
+ &claims_set);
+ if (code) {
+ DBG_ERR("Failed to fetch claims\n");
+ goto out;
+ }
+
+ if (claims_set != NULL) {
+ status = claims_data_from_claims_set(claims_data,
+ claims_set,
+ &claims_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ code = map_errno_from_nt_status(status);
+ goto out;
+ }
+ }
+
+ entry->claims_from_db = talloc_steal(entry,
+ claims_data);
+ entry->claims_from_db_are_initialized = true;
+
+ /* Note: the caller does not own this! */
+ *claims_data_out = entry->claims_from_db;
+
+out:
+ talloc_free(frame);
+ return code;
+}
+
+krb5_error_code samba_kdc_check_device(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ struct loadparm_context *lp_ctx,
+ const struct samba_kdc_entry_pac device,
+ const struct authn_kerberos_client_policy *client_policy,
+ struct authn_audit_info **client_audit_info_out,
+ NTSTATUS *status_out)
+{
+ TALLOC_CTX *frame = NULL;
+ krb5_error_code code = 0;
+ NTSTATUS nt_status;
+ const struct auth_user_info_dc *device_info = NULL;
+ struct authn_audit_info *client_audit_info = NULL;
+ struct auth_claims auth_claims = {};
+
+ if (status_out != NULL) {
+ *status_out = NT_STATUS_OK;
+ }
+
+ if (!authn_policy_device_restrictions_present(client_policy)) {
+ return 0;
+ }
+
+ if (device.entry == NULL || device.pac == NULL) {
+ NTSTATUS out_status = NT_STATUS_INVALID_WORKSTATION;
+
+ nt_status = authn_kerberos_client_policy_audit_info(mem_ctx,
+ client_policy,
+ NULL /* client_info */,
+ AUTHN_AUDIT_EVENT_KERBEROS_DEVICE_RESTRICTION,
+ AUTHN_AUDIT_REASON_FAST_REQUIRED,
+ out_status,
+ client_audit_info_out);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ code = KRB5KRB_ERR_GENERIC;
+ } else if (authn_kerberos_client_policy_is_enforced(client_policy)) {
+ code = KRB5KDC_ERR_POLICY;
+
+ if (status_out != NULL) {
+ *status_out = out_status;
+ }
+ } else {
+ /* OK. */
+ code = 0;
+ }
+
+ goto out;
+ }
+
+ frame = talloc_stackframe();
+
+ code = samba_kdc_get_user_info_dc(frame,
+ context,
+ samdb,
+ device,
+ &device_info,
+ NULL);
+ if (code) {
+ goto out;
+ }
+
+ /*
+ * The device claims become the *user* claims for the purpose of
+ * evaluating a conditional ACE expression.
+ */
+ code = samba_kdc_get_claims_data(frame,
+ context,
+ samdb,
+ device,
+ &auth_claims.user_claims);
+ if (code) {
+ goto out;
+ }
+
+ nt_status = authn_policy_authenticate_from_device(frame,
+ samdb,
+ lp_ctx,
+ device_info,
+ auth_claims,
+ client_policy,
+ &client_audit_info);
+ if (client_audit_info != NULL) {
+ *client_audit_info_out = talloc_move(mem_ctx, &client_audit_info);
+ }
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_AUTHENTICATION_FIREWALL_FAILED)) {
+ code = KRB5KDC_ERR_POLICY;
+ } else {
+ code = KRB5KRB_ERR_GENERIC;
+ }
+
+ goto out;
+ }
+
+out:
+ talloc_free(frame);
+ return code;
+}
diff --git a/source4/kdc/pac-glue.h b/source4/kdc/pac-glue.h
new file mode 100644
index 0000000..1b4444a
--- /dev/null
+++ b/source4/kdc/pac-glue.h
@@ -0,0 +1,202 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ PAC Glue between Samba and the KDC
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009
+ Copyright (C) Simo Sorce <idra@samba.org> 2010
+
+ 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 "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include <krb5/krb5.h>
+
+#include "lib/util/data_blob.h"
+#include "lib/util/time.h"
+#include "libcli/util/ntstatus.h"
+#include "libcli/util/werror.h"
+#include "librpc/gen_ndr/auth.h"
+#include "kdc/samba_kdc.h"
+#include "lib/krb5_wrap/krb5_samba.h"
+#include "auth/session.h"
+
+enum samba_asserted_identity {
+ SAMBA_ASSERTED_IDENTITY_IGNORE = 0,
+ SAMBA_ASSERTED_IDENTITY_SERVICE,
+ SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY,
+};
+
+enum {
+ SAMBA_KDC_FLAG_PROTOCOL_TRANSITION = 0x00000001,
+ SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION = 0x00000002,
+};
+
+bool samba_kdc_entry_is_trust(const struct samba_kdc_entry *entry);
+
+struct samba_kdc_entry_pac {
+ struct samba_kdc_entry *entry;
+ krb5_const_pac pac; /* NULL indicates that no PAC is present. */
+ bool is_from_trust : 1;
+#ifndef HAVE_KRB5_PAC_IS_TRUSTED /* MIT */
+ bool pac_is_trusted : 1;
+#endif /* HAVE_KRB5_PAC_IS_TRUSTED */
+};
+
+/*
+ * Return true if this entry has an associated PAC issued or signed by a KDC
+ * that our KDC trusts. We trust the main krbtgt account, but we don’t trust any
+ * RODC krbtgt besides ourselves.
+ */
+bool samba_krb5_pac_is_trusted(const struct samba_kdc_entry_pac pac);
+
+#ifdef HAVE_KRB5_PAC_IS_TRUSTED /* Heimdal */
+struct samba_kdc_entry_pac samba_kdc_entry_pac(krb5_const_pac pac,
+ struct samba_kdc_entry *entry,
+ bool is_from_trust);
+#else /* MIT */
+struct samba_kdc_entry_pac samba_kdc_entry_pac_from_trusted(krb5_const_pac pac,
+ struct samba_kdc_entry *entry,
+ bool is_from_trust,
+ bool is_trusted);
+#endif /* HAVE_KRB5_PAC_IS_TRUSTED */
+
+krb5_error_code samba_kdc_encrypt_pac_credentials(krb5_context context,
+ const krb5_keyblock *pkreplykey,
+ const DATA_BLOB *cred_ndr_blob,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cred_info_blob);
+
+krb5_error_code samba_make_krb5_pac(krb5_context context,
+ const DATA_BLOB *logon_blob,
+ const DATA_BLOB *cred_blob,
+ const DATA_BLOB *upn_blob,
+ const DATA_BLOB *pac_attrs_blob,
+ const DATA_BLOB *requester_sid_blob,
+ const DATA_BLOB *deleg_blob,
+ const DATA_BLOB *client_claims_blob,
+ const DATA_BLOB *device_info_blob,
+ const DATA_BLOB *device_claims_blob,
+ krb5_pac pac);
+
+bool samba_princ_needs_pac(const struct samba_kdc_entry *skdc_entry);
+
+krb5_error_code samba_krbtgt_is_in_db(const struct samba_kdc_entry *skdc_entry,
+ bool *is_in_db,
+ bool *is_trusted);
+
+krb5_error_code samba_kdc_get_user_info_dc(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ const struct samba_kdc_entry_pac entry,
+ const struct auth_user_info_dc **info_out,
+ const struct PAC_DOMAIN_GROUP_MEMBERSHIP **resource_groups_out);
+
+krb5_error_code samba_kdc_get_user_info_from_db(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct samba_kdc_entry *entry,
+ const struct ldb_message *msg,
+ const struct auth_user_info_dc **info_out);
+
+krb5_error_code samba_kdc_map_policy_err(NTSTATUS nt_status);
+
+NTSTATUS samba_kdc_check_client_access(struct samba_kdc_entry *kdc_entry,
+ const char *client_name,
+ const char *workstation,
+ bool password_change);
+
+krb5_error_code samba_kdc_verify_pac(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ uint32_t flags,
+ const struct samba_kdc_entry_pac client,
+ const struct samba_kdc_entry *krbtgt);
+
+struct authn_audit_info;
+krb5_error_code samba_kdc_update_pac(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ struct loadparm_context *lp_ctx,
+ uint32_t flags,
+ const struct samba_kdc_entry_pac client,
+ const krb5_const_principal server_principal,
+ const struct samba_kdc_entry *server,
+ const krb5_const_principal delegated_proxy_principal,
+ const struct samba_kdc_entry_pac delegated_proxy,
+ const struct samba_kdc_entry_pac device,
+ krb5_pac new_pac,
+ struct authn_audit_info **server_audit_info_out,
+ NTSTATUS *status_out);
+
+NTSTATUS samba_kdc_get_logon_info_blob(TALLOC_CTX *mem_ctx,
+ const struct auth_user_info_dc *user_info_dc,
+ enum auth_group_inclusion group_inclusion,
+ DATA_BLOB **_logon_info_blob);
+NTSTATUS samba_kdc_get_cred_ndr_blob(TALLOC_CTX *mem_ctx,
+ const struct samba_kdc_entry *p,
+ DATA_BLOB **_cred_ndr_blob);
+NTSTATUS samba_kdc_get_upn_info_blob(TALLOC_CTX *mem_ctx,
+ const struct auth_user_info_dc *user_info_dc,
+ DATA_BLOB **_upn_info_blob);
+NTSTATUS samba_kdc_get_pac_attrs_blob(TALLOC_CTX *mem_ctx,
+ uint64_t pac_attributes,
+ DATA_BLOB **_pac_attrs_blob);
+NTSTATUS samba_kdc_get_requester_sid_blob(TALLOC_CTX *mem_ctx,
+ const struct auth_user_info_dc *user_info_dc,
+ DATA_BLOB **_requester_sid_blob);
+NTSTATUS samba_kdc_get_claims_blob(TALLOC_CTX *mem_ctx,
+ struct samba_kdc_entry *p,
+ const DATA_BLOB **_claims_blob);
+
+krb5_error_code samba_kdc_allowed_to_authenticate_to(TALLOC_CTX *mem_ctx,
+ struct ldb_context *samdb,
+ struct loadparm_context *lp_ctx,
+ const struct samba_kdc_entry *client,
+ const struct auth_user_info_dc *client_info,
+ const struct auth_user_info_dc *device_info,
+ const struct auth_claims auth_claims,
+ const struct samba_kdc_entry *server,
+ struct authn_audit_info **server_audit_info_out,
+ NTSTATUS *status_out);
+
+krb5_error_code samba_kdc_check_device(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ struct loadparm_context *lp_ctx,
+ const struct samba_kdc_entry_pac device,
+ const struct authn_kerberos_client_policy *client_policy,
+ struct authn_audit_info **client_audit_info_out,
+ NTSTATUS *status_out);
+
+krb5_error_code samba_kdc_get_claims_data(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ struct samba_kdc_entry_pac entry,
+ struct claims_data **claims_data_out);
+
+krb5_error_code samba_kdc_get_claims_data_from_pac(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct samba_kdc_entry_pac entry,
+ struct claims_data **claims_data_out);
+
+krb5_error_code samba_kdc_get_claims_data_from_db(struct ldb_context *samdb,
+ struct samba_kdc_entry *entry,
+ struct claims_data **claims_data_out);
+
+NTSTATUS samba_kdc_add_asserted_identity(enum samba_asserted_identity ai,
+ struct auth_user_info_dc *user_info_dc);
+
+NTSTATUS samba_kdc_add_claims_valid(struct auth_user_info_dc *user_info_dc);
diff --git a/source4/kdc/samba_kdc.h b/source4/kdc/samba_kdc.h
new file mode 100644
index 0000000..d1100f6
--- /dev/null
+++ b/source4/kdc/samba_kdc.h
@@ -0,0 +1,83 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ KDC structures
+
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Simo Sorce <idra@samba.org> 2010
+
+ 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/>.
+*/
+
+#ifndef _SAMBA_KDC_H_
+#define _SAMBA_KDC_H_
+
+#include "lib/replace/replace.h"
+#include "system/time.h"
+#include "libcli/util/ntstatus.h"
+
+struct samba_kdc_policy {
+ time_t svc_tkt_lifetime;
+ time_t usr_tkt_lifetime;
+ time_t renewal_lifetime;
+};
+
+struct samba_kdc_base_context {
+ struct tevent_context *ev_ctx;
+ struct loadparm_context *lp_ctx;
+ struct imessaging_context *msg_ctx;
+};
+
+struct samba_kdc_seq;
+
+struct samba_kdc_db_context {
+ struct tevent_context *ev_ctx;
+ struct loadparm_context *lp_ctx;
+ struct imessaging_context *msg_ctx;
+ struct ldb_context *samdb;
+ struct samba_kdc_seq *seq_ctx;
+ bool rodc;
+ unsigned int my_krbtgt_number;
+ struct ldb_dn *krbtgt_dn;
+ struct samba_kdc_policy policy;
+};
+
+struct samba_kdc_entry {
+ struct samba_kdc_db_context *kdc_db_ctx;
+ const struct sdb_entry *db_entry; /* this is only temporarily valid */
+ const void *kdc_entry; /* this is a reference to hdb_entry/krb5_db_entry */
+ struct ldb_message *msg;
+ struct ldb_dn *realm_dn;
+ struct claims_data *claims_from_pac;
+ struct claims_data *claims_from_db;
+ const struct auth_user_info_dc *info_from_pac;
+ const struct PAC_DOMAIN_GROUP_MEMBERSHIP *resource_groups_from_pac;
+ const struct auth_user_info_dc *info_from_db;
+ const struct authn_kerberos_client_policy *client_policy;
+ const struct authn_server_policy *server_policy;
+ uint32_t supported_enctypes;
+ NTSTATUS reject_status;
+ bool is_krbtgt : 1;
+ bool is_rodc : 1;
+ bool is_trust : 1;
+ bool claims_from_pac_are_initialized : 1;
+ bool claims_from_db_are_initialized : 1;
+};
+
+extern struct hdb_method hdb_samba4_interface;
+
+#define CHANGEPW_LIFETIME (60*2) /* 2 minutes */
+
+#endif /* _SAMBA_KDC_H_ */
diff --git a/source4/kdc/sdb.c b/source4/kdc/sdb.c
new file mode 100644
index 0000000..75f96bb
--- /dev/null
+++ b/source4/kdc/sdb.c
@@ -0,0 +1,195 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Database Glue between Samba and the KDC
+
+ Copyright (C) Guenther Deschner <gd@samba.org> 2014
+ Copyright (C) Andreas Schneider <asn@samba.org> 2014
+
+ 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 "includes.h"
+#include "system/kerberos.h"
+#include "sdb.h"
+#include "samba_kdc.h"
+#include "lib/krb5_wrap/krb5_samba.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+void sdb_key_free(struct sdb_key *k)
+{
+ if (k == NULL) {
+ return;
+ }
+
+ /*
+ * Passing NULL as the Kerberos context is intentional here, as
+ * both Heimdal and MIT libraries don't use the context when
+ * clearing the keyblocks.
+ */
+ krb5_free_keyblock_contents(NULL, &k->key);
+
+ if (k->salt) {
+ smb_krb5_free_data_contents(NULL, &k->salt->salt);
+ SAFE_FREE(k->salt);
+ }
+
+ ZERO_STRUCTP(k);
+}
+
+void sdb_keys_free(struct sdb_keys *keys)
+{
+ unsigned int i;
+
+ if (keys == NULL) {
+ return;
+ }
+
+ for (i=0; i < keys->len; i++) {
+ sdb_key_free(&keys->val[i]);
+ }
+
+ SAFE_FREE(keys->val);
+ ZERO_STRUCTP(keys);
+}
+
+void sdb_entry_free(struct sdb_entry *s)
+{
+ if (s->skdc_entry != NULL) {
+ s->skdc_entry->db_entry = NULL;
+ TALLOC_FREE(s->skdc_entry);
+ }
+
+ /*
+ * Passing NULL as the Kerberos context is intentional here, as both
+ * Heimdal and MIT libraries don't use the context when clearing the
+ * principals.
+ */
+ krb5_free_principal(NULL, s->principal);
+
+ sdb_keys_free(&s->keys);
+ SAFE_FREE(s->etypes);
+ sdb_keys_free(&s->old_keys);
+ sdb_keys_free(&s->older_keys);
+ if (s->session_etypes != NULL) {
+ SAFE_FREE(s->session_etypes->val);
+ }
+ SAFE_FREE(s->session_etypes);
+ krb5_free_principal(NULL, s->created_by.principal);
+ if (s->modified_by) {
+ krb5_free_principal(NULL, s->modified_by->principal);
+ }
+ SAFE_FREE(s->valid_start);
+ SAFE_FREE(s->valid_end);
+ SAFE_FREE(s->pw_end);
+ SAFE_FREE(s->max_life);
+ SAFE_FREE(s->max_renew);
+
+ ZERO_STRUCTP(s);
+}
+
+/* Set the etypes of an sdb_entry based on its available current keys. */
+krb5_error_code sdb_entry_set_etypes(struct sdb_entry *s)
+{
+ if (s->keys.val != NULL) {
+ unsigned i;
+
+ s->etypes = malloc(sizeof(*s->etypes));
+ if (s->etypes == NULL) {
+ return ENOMEM;
+ }
+
+ s->etypes->len = s->keys.len;
+
+ s->etypes->val = calloc(s->etypes->len, sizeof(*s->etypes->val));
+ if (s->etypes->val == NULL) {
+ SAFE_FREE(s->etypes);
+ return ENOMEM;
+ }
+
+ for (i = 0; i < s->etypes->len; i++) {
+ const struct sdb_key *k = &s->keys.val[i];
+
+ s->etypes->val[i] = KRB5_KEY_TYPE(&(k->key));
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Set the session etypes of a server sdb_entry based on its etypes, forcing in
+ * strong etypes as desired.
+ */
+krb5_error_code sdb_entry_set_session_etypes(struct sdb_entry *s,
+ bool add_aes256,
+ bool add_aes128,
+ bool add_rc4)
+{
+ unsigned len = 0;
+
+ if (add_aes256) {
+ /* Reserve space for AES256 */
+ len += 1;
+ }
+
+ if (add_aes128) {
+ /* Reserve space for AES128 */
+ len += 1;
+ }
+
+ if (add_rc4) {
+ /* Reserve space for RC4. */
+ len += 1;
+ }
+
+ if (len != 0) {
+ unsigned j = 0;
+
+ s->session_etypes = malloc(sizeof(*s->session_etypes));
+ if (s->session_etypes == NULL) {
+ return ENOMEM;
+ }
+
+ /* session_etypes must be sorted in order of strength, with preferred etype first. */
+
+ s->session_etypes->val = calloc(len, sizeof(*s->session_etypes->val));
+ if (s->session_etypes->val == NULL) {
+ SAFE_FREE(s->session_etypes);
+ return ENOMEM;
+ }
+
+ if (add_aes256) {
+ /* Add AES256 */
+ s->session_etypes->val[j++] = ENCTYPE_AES256_CTS_HMAC_SHA1_96;
+ }
+
+ if (add_aes128) {
+ /* Add AES128. */
+ s->session_etypes->val[j++] = ENCTYPE_AES128_CTS_HMAC_SHA1_96;
+ }
+
+ if (add_rc4) {
+ /* Add RC4. */
+ s->session_etypes->val[j++] = ENCTYPE_ARCFOUR_HMAC;
+ }
+
+ s->session_etypes->len = j;
+ }
+
+ return 0;
+}
diff --git a/source4/kdc/sdb.h b/source4/kdc/sdb.h
new file mode 100644
index 0000000..820648a
--- /dev/null
+++ b/source4/kdc/sdb.h
@@ -0,0 +1,150 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Database Glue between Samba and the KDC
+
+ Copyright (C) Guenther Deschner <gd@samba.org> 2014
+ Copyright (C) Andreas Schneider <asn@samba.org> 2014
+
+ 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/>.
+*/
+
+#ifndef _KDC_SDB_H_
+#define _KDC_SDB_H_
+
+struct sdb_salt {
+ unsigned int type;
+ krb5_data salt;
+};
+
+struct sdb_key {
+ krb5_keyblock key;
+ struct sdb_salt *salt;
+};
+
+struct sdb_keys {
+ unsigned int len;
+ struct sdb_key *val;
+};
+
+struct sdb_event {
+ krb5_principal principal;
+ time_t time;
+};
+
+struct sdb_etypes {
+ unsigned len;
+ krb5_enctype *val;
+};
+
+struct SDBFlags {
+ unsigned int initial:1;
+ unsigned int forwardable:1;
+ unsigned int proxiable:1;
+ unsigned int renewable:1;
+ unsigned int postdate:1;
+ unsigned int server:1;
+ unsigned int client:1;
+ unsigned int invalid:1;
+ unsigned int require_preauth:1;
+ unsigned int change_pw:1;
+ unsigned int require_hwauth:1;
+ unsigned int ok_as_delegate:1;
+ unsigned int user_to_user:1;
+ unsigned int immutable:1;
+ unsigned int trusted_for_delegation:1;
+ unsigned int allow_kerberos4:1;
+ unsigned int allow_digest:1;
+ unsigned int locked_out:1;
+ unsigned int require_pwchange:1;
+ unsigned int materialize:1;
+ unsigned int virtual_keys:1;
+ unsigned int virtual:1;
+ unsigned int synthetic:1;
+ unsigned int no_auth_data_reqd:1;
+ unsigned int auth_data_reqd:1;
+ unsigned int _unused25:1;
+ unsigned int _unused26:1;
+ unsigned int _unused27:1;
+ unsigned int _unused28:1;
+ unsigned int _unused29:1;
+ unsigned int force_canonicalize:1;
+ unsigned int do_not_store:1;
+};
+
+struct sdb_entry {
+ struct samba_kdc_entry *skdc_entry;
+ krb5_principal principal;
+ unsigned int kvno;
+ struct sdb_keys keys;
+ struct sdb_etypes *etypes;
+ struct sdb_keys old_keys;
+ struct sdb_keys older_keys;
+ struct sdb_etypes *session_etypes;
+ struct sdb_event created_by;
+ struct sdb_event *modified_by;
+ time_t *valid_start;
+ time_t *valid_end;
+ time_t *pw_end;
+ int *max_life;
+ int *max_renew;
+ struct SDBFlags flags;
+};
+
+#define SDB_ERR_NOENTRY 36150275
+#define SDB_ERR_NOT_FOUND_HERE 36150287
+#define SDB_ERR_WRONG_REALM 36150289
+
+/* These must match the values in hdb.h */
+
+#define SDB_F_DECRYPT 1 /* decrypt keys */
+#define SDB_F_GET_CLIENT 4 /* fetch client */
+#define SDB_F_GET_SERVER 8 /* fetch server */
+#define SDB_F_GET_KRBTGT 16 /* fetch krbtgt */
+#define SDB_F_GET_ANY 28 /* fetch any of client,server,krbtgt */
+#define SDB_F_CANON 32 /* want canonicalization */
+#define SDB_F_ADMIN_DATA 64 /* want data that kdc don't use */
+#define SDB_F_KVNO_SPECIFIED 128 /* we want a particular KVNO */
+#define SDB_F_FOR_AS_REQ 4096 /* fetch is for a AS REQ */
+#define SDB_F_FOR_TGS_REQ 8192 /* fetch is for a TGS REQ */
+#define SDB_F_ARMOR_PRINCIPAL 262144 /* fetch is for the client of an armor ticket */
+#define SDB_F_USER2USER_PRINCIPAL 524288/* fetch is for the server of a user2user tgs-req */
+
+#define SDB_F_HDB_MASK (SDB_F_DECRYPT | \
+ SDB_F_GET_CLIENT| \
+ SDB_F_GET_SERVER | \
+ SDB_F_GET_KRBTGT | \
+ SDB_F_CANON | \
+ SDB_F_ADMIN_DATA | \
+ SDB_F_KVNO_SPECIFIED | \
+ SDB_F_FOR_AS_REQ | \
+ SDB_F_FOR_TGS_REQ | \
+ SDB_F_ARMOR_PRINCIPAL| \
+ SDB_F_USER2USER_PRINCIPAL)
+
+/* These are not supported by HDB */
+#define SDB_F_FORCE_CANON 16384 /* force canonicalization */
+#define SDB_F_RODC_NUMBER_SPECIFIED 32768 /* we want a particular RODC number */
+
+void sdb_key_free(struct sdb_key *key);
+void sdb_keys_free(struct sdb_keys *keys);
+void sdb_entry_free(struct sdb_entry *e);
+krb5_error_code sdb_entry_set_etypes(struct sdb_entry *s);
+krb5_error_code sdb_entry_set_session_etypes(struct sdb_entry *s,
+ bool add_aes256,
+ bool add_aes128,
+ bool add_rc4);
+
+#endif /* _KDC_SDB_H_ */
diff --git a/source4/kdc/sdb_to_hdb.c b/source4/kdc/sdb_to_hdb.c
new file mode 100644
index 0000000..4c4f2a5
--- /dev/null
+++ b/source4/kdc/sdb_to_hdb.c
@@ -0,0 +1,359 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Database Glue between Samba and the KDC
+
+ Copyright (C) Guenther Deschner <gd@samba.org> 2014
+ Copyright (C) Andreas Schneider <asn@samba.org> 2014
+
+ 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 "includes.h"
+#include <hdb.h>
+#include "sdb.h"
+#include "sdb_hdb.h"
+#include "lib/krb5_wrap/krb5_samba.h"
+#include "librpc/gen_ndr/security.h"
+#include "kdc/samba_kdc.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+static void sdb_flags_to_hdb_flags(const struct SDBFlags *s,
+ HDBFlags *h)
+{
+ SMB_ASSERT(sizeof(struct SDBFlags) == sizeof(HDBFlags));
+
+ h->initial = s->initial;
+ h->forwardable = s->forwardable;
+ h->proxiable = s->proxiable;
+ h->renewable = s->renewable;
+ h->postdate = s->postdate;
+ h->server = s->server;
+ h->client = s->client;
+ h->invalid = s->invalid;
+ h->require_preauth = s->require_preauth;
+ h->change_pw = s->change_pw;
+ h->require_hwauth = s->require_hwauth;
+ h->ok_as_delegate = s->ok_as_delegate;
+ h->user_to_user = s->user_to_user;
+ h->immutable = s->immutable;
+ h->trusted_for_delegation = s->trusted_for_delegation;
+ h->allow_kerberos4 = s->allow_kerberos4;
+ h->allow_digest = s->allow_digest;
+ h->locked_out = s->locked_out;
+ h->require_pwchange = s->require_pwchange;
+ h->materialize = s->materialize;
+ h->virtual_keys = s->virtual_keys;
+ h->virtual = s->virtual;
+ h->synthetic = s->synthetic;
+ h->no_auth_data_reqd = s->no_auth_data_reqd;
+ h->auth_data_reqd = s->auth_data_reqd;
+ h->_unused25 = s->_unused25;
+ h->_unused26 = s->_unused26;
+ h->_unused27 = s->_unused27;
+ h->_unused28 = s->_unused28;
+ h->_unused29 = s->_unused29;
+ h->force_canonicalize = s->force_canonicalize;
+ h->do_not_store = s->do_not_store;
+}
+
+static int sdb_salt_to_Salt(const struct sdb_salt *s, Salt *h)
+{
+ int ret;
+
+ *h = (struct Salt) {};
+
+ h->type = s->type;
+ ret = smb_krb5_copy_data_contents(&h->salt, s->salt.data, s->salt.length);
+ if (ret != 0) {
+ free_Salt(h);
+ return ENOMEM;
+ }
+
+ return 0;
+}
+
+static int sdb_key_to_Key(const struct sdb_key *s, Key *h)
+{
+ int rc;
+
+ *h = (struct Key) {};
+
+ h->key.keytype = s->key.keytype;
+ rc = smb_krb5_copy_data_contents(&h->key.keyvalue,
+ s->key.keyvalue.data,
+ s->key.keyvalue.length);
+ if (rc != 0) {
+ goto error_nomem;
+ }
+
+ if (s->salt != NULL) {
+ h->salt = malloc(sizeof(Salt));
+ if (h->salt == NULL) {
+ goto error_nomem;
+ }
+
+ rc = sdb_salt_to_Salt(s->salt,
+ h->salt);
+ if (rc != 0) {
+ goto error_nomem;
+ }
+ }
+
+ return 0;
+
+error_nomem:
+ free_Key(h);
+ return ENOMEM;
+}
+
+static int sdb_keys_to_Keys(const struct sdb_keys *s, Keys *h)
+{
+ int ret, i;
+
+ *h = (struct Keys) {};
+
+ if (s->val != NULL) {
+ h->val = malloc(s->len * sizeof(Key));
+ if (h->val == NULL) {
+ return ENOMEM;
+ }
+ for (i = 0; i < s->len; i++) {
+ ret = sdb_key_to_Key(&s->val[i],
+ &h->val[i]);
+ if (ret != 0) {
+ free_Keys(h);
+ return ENOMEM;
+ }
+
+ ++h->len;
+ }
+ }
+
+ return 0;
+}
+
+static int sdb_keys_to_HistKeys(krb5_context context,
+ const struct sdb_keys *s,
+ krb5_kvno kvno,
+ hdb_entry *h)
+{
+ unsigned int i;
+
+ for (i = 0; i < s->len; i++) {
+ Key k = { 0, };
+ int ret;
+
+ ret = sdb_key_to_Key(&s->val[i], &k);
+ if (ret != 0) {
+ return ENOMEM;
+ }
+ ret = hdb_add_history_key(context, h, kvno, &k);
+ free_Key(&k);
+ if (ret != 0) {
+ return ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+static int sdb_event_to_Event(krb5_context context,
+ const struct sdb_event *s, Event *h)
+{
+ int ret;
+
+ *h = (struct Event) {};
+
+ if (s->principal != NULL) {
+ ret = krb5_copy_principal(context,
+ s->principal,
+ &h->principal);
+ if (ret != 0) {
+ free_Event(h);
+ return ret;
+ }
+ }
+ h->time = s->time;
+
+ return 0;
+}
+
+int sdb_entry_to_hdb_entry(krb5_context context,
+ const struct sdb_entry *s,
+ hdb_entry *h)
+{
+ struct samba_kdc_entry *ske = s->skdc_entry;
+ unsigned int i;
+ int rc;
+
+ *h = (hdb_entry) {};
+
+ if (s->principal != NULL) {
+ rc = krb5_copy_principal(context,
+ s->principal,
+ &h->principal);
+ if (rc != 0) {
+ return rc;
+ }
+ }
+
+ h->kvno = s->kvno;
+
+ rc = sdb_keys_to_Keys(&s->keys, &h->keys);
+ if (rc != 0) {
+ goto error;
+ }
+
+ if (h->kvno > 1) {
+ rc = sdb_keys_to_HistKeys(context,
+ &s->old_keys,
+ h->kvno - 1,
+ h);
+ if (rc != 0) {
+ goto error;
+ }
+ }
+
+ if (h->kvno > 2) {
+ rc = sdb_keys_to_HistKeys(context,
+ &s->older_keys,
+ h->kvno - 2,
+ h);
+ if (rc != 0) {
+ goto error;
+ }
+ }
+
+ rc = sdb_event_to_Event(context,
+ &s->created_by,
+ &h->created_by);
+ if (rc != 0) {
+ goto error;
+ }
+
+ if (s->modified_by) {
+ h->modified_by = malloc(sizeof(Event));
+ if (h->modified_by == NULL) {
+ rc = ENOMEM;
+ goto error;
+ }
+
+ rc = sdb_event_to_Event(context,
+ s->modified_by,
+ h->modified_by);
+ if (rc != 0) {
+ goto error;
+ }
+ }
+
+ if (s->valid_start != NULL) {
+ h->valid_start = malloc(sizeof(KerberosTime));
+ if (h->valid_start == NULL) {
+ rc = ENOMEM;
+ goto error;
+ }
+ *h->valid_start = *s->valid_start;
+ }
+
+ if (s->valid_end != NULL) {
+ h->valid_end = malloc(sizeof(KerberosTime));
+ if (h->valid_end == NULL) {
+ rc = ENOMEM;
+ goto error;
+ }
+ *h->valid_end = *s->valid_end;
+ }
+
+ if (s->pw_end != NULL) {
+ h->pw_end = malloc(sizeof(KerberosTime));
+ if (h->pw_end == NULL) {
+ rc = ENOMEM;
+ goto error;
+ }
+ *h->pw_end = *s->pw_end;
+ }
+
+ if (s->max_life != NULL) {
+ h->max_life = malloc(sizeof(*h->max_life));
+ if (h->max_life == NULL) {
+ rc = ENOMEM;
+ goto error;
+ }
+ *h->max_life = *s->max_life;
+ }
+
+ if (s->max_renew != NULL) {
+ h->max_renew = malloc(sizeof(*h->max_renew));
+ if (h->max_renew == NULL) {
+ rc = ENOMEM;
+ goto error;
+ }
+ *h->max_renew = *s->max_renew;
+ }
+
+ sdb_flags_to_hdb_flags(&s->flags, &h->flags);
+
+ if (s->etypes != NULL) {
+ h->etypes = malloc(sizeof(*h->etypes));
+ if (h->etypes == NULL) {
+ rc = ENOMEM;
+ goto error;
+ }
+
+ h->etypes->len = s->etypes->len;
+
+ h->etypes->val = calloc(h->etypes->len, sizeof(int));
+ if (h->etypes->val == NULL) {
+ rc = ENOMEM;
+ goto error;
+ }
+
+ for (i = 0; i < h->etypes->len; i++) {
+ h->etypes->val[i] = s->etypes->val[i];
+ }
+ }
+
+ if (s->session_etypes != NULL) {
+ h->session_etypes = malloc(sizeof(*h->session_etypes));
+ if (h->session_etypes == NULL) {
+ rc = ENOMEM;
+ goto error;
+ }
+
+ h->session_etypes->len = s->session_etypes->len;
+
+ h->session_etypes->val = calloc(h->session_etypes->len, sizeof(*h->session_etypes->val));
+ if (h->session_etypes->val == NULL) {
+ rc = ENOMEM;
+ goto error;
+ }
+
+ for (i = 0; i < h->session_etypes->len; ++i) {
+ h->session_etypes->val[i] = s->session_etypes->val[i];
+ }
+ }
+
+ h->context = ske;
+ if (ske != NULL) {
+ ske->kdc_entry = h;
+ }
+ return 0;
+error:
+ free_hdb_entry(h);
+ return rc;
+}
diff --git a/source4/kdc/sdb_to_kdb.c b/source4/kdc/sdb_to_kdb.c
new file mode 100644
index 0000000..7214cbc
--- /dev/null
+++ b/source4/kdc/sdb_to_kdb.c
@@ -0,0 +1,332 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Database Glue between Samba and the KDC
+
+ Copyright (C) Guenther Deschner <gd@samba.org> 2014
+ Copyright (C) Andreas Schneider <asn@samba.org> 2014
+
+ 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 "includes.h"
+#include <kdb.h>
+#include "sdb.h"
+#include "sdb_kdb.h"
+#include "kdc/samba_kdc.h"
+#include "lib/krb5_wrap/krb5_samba.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+static int SDBFlags_to_kflags(const struct SDBFlags *s,
+ krb5_flags *k)
+{
+ *k = 0;
+
+ if (s->initial) {
+ *k |= KRB5_KDB_DISALLOW_TGT_BASED;
+ }
+ /* The forwardable and proxiable flags are set according to client and
+ * server attributes. */
+ if (!s->forwardable) {
+ *k |= KRB5_KDB_DISALLOW_FORWARDABLE;
+ }
+ if (!s->proxiable) {
+ *k |= KRB5_KDB_DISALLOW_PROXIABLE;
+ }
+ if (s->renewable) {
+ ;
+ }
+ if (s->postdate) {
+ ;
+ }
+ if (s->server) {
+ ;
+ }
+ if (s->client) {
+ ;
+ }
+ if (s->invalid) {
+ *k |= KRB5_KDB_DISALLOW_ALL_TIX;
+ }
+ if (s->require_preauth) {
+ *k |= KRB5_KDB_REQUIRES_PRE_AUTH;
+ }
+ if (s->change_pw) {
+ *k |= KRB5_KDB_PWCHANGE_SERVICE;
+ }
+#if 0
+ /*
+ * Do not set KRB5_KDB_REQUIRES_HW_AUTH as this would tell the client
+ * to enforce hardware authentication. It prevents the use of files
+ * based public key authentication which we use for testing.
+ */
+ if (s->require_hwauth) {
+ *k |= KRB5_KDB_REQUIRES_HW_AUTH;
+ }
+#endif
+ if (s->ok_as_delegate) {
+ *k |= KRB5_KDB_OK_AS_DELEGATE;
+ }
+ if (s->user_to_user) {
+ ;
+ }
+ if (s->immutable) {
+ ;
+ }
+ if (s->trusted_for_delegation) {
+ *k |= KRB5_KDB_OK_TO_AUTH_AS_DELEGATE;
+ }
+ if (s->allow_kerberos4) {
+ ;
+ }
+ if (s->allow_digest) {
+ ;
+ }
+ if (s->no_auth_data_reqd) {
+ *k |= KRB5_KDB_NO_AUTH_DATA_REQUIRED;
+ }
+
+ return 0;
+}
+
+static int sdb_event_to_kmod(krb5_context context,
+ const struct sdb_event *s,
+ krb5_db_entry *k)
+{
+ krb5_error_code ret;
+ krb5_principal principal = NULL;
+
+ if (s->principal != NULL) {
+ ret = krb5_copy_principal(context,
+ s->principal,
+ &principal);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ ret = krb5_dbe_update_mod_princ_data(context,
+ k, s->time,
+ principal);
+
+ krb5_free_principal(context, principal);
+
+ return ret;
+}
+
+/* sets up salt on the 2nd array position */
+
+static int sdb_salt_to_krb5_key_data(const struct sdb_salt *s,
+ krb5_key_data *k)
+{
+ switch (s->type) {
+#if 0
+ /* for now use the special mechanism where the MIT KDC creates the salt
+ * on its own */
+ case 3: /* FIXME KRB5_PW_SALT */
+ k->key_data_type[1] = KRB5_KDB_SALTTYPE_NORMAL;
+ break;
+ /*
+ case hdb_afs3_salt:
+ k->key_data_type[1] = KRB5_KDB_SALTTYPE_AFS3;
+ break;
+ */
+#endif
+ default:
+ k->key_data_type[1] = KRB5_KDB_SALTTYPE_SPECIAL;
+ break;
+ }
+
+ k->key_data_contents[1] = malloc(s->salt.length);
+ if (k->key_data_contents[1] == NULL) {
+ return ENOMEM;
+ }
+ memcpy(k->key_data_contents[1],
+ s->salt.data,
+ s->salt.length);
+ k->key_data_length[1] = s->salt.length;
+
+ return 0;
+}
+
+static int sdb_key_to_krb5_key_data(const struct sdb_key *s,
+ int kvno,
+ krb5_key_data *k)
+{
+ int ret = 0;
+
+ ZERO_STRUCTP(k);
+
+ k->key_data_ver = KRB5_KDB_V1_KEY_DATA_ARRAY;
+ k->key_data_kvno = kvno;
+
+ k->key_data_type[0] = KRB5_KEY_TYPE(&s->key);
+ k->key_data_length[0] = KRB5_KEY_LENGTH(&s->key);
+ k->key_data_contents[0] = malloc(k->key_data_length[0]);
+ if (k->key_data_contents[0] == NULL) {
+ return ENOMEM;
+ }
+
+ memcpy(k->key_data_contents[0],
+ KRB5_KEY_DATA(&s->key),
+ k->key_data_length[0]);
+
+ if (s->salt != NULL) {
+ ret = sdb_salt_to_krb5_key_data(s->salt, k);
+ if (ret) {
+ memset(k->key_data_contents[0], 0, k->key_data_length[0]);
+ free(k->key_data_contents[0]);
+ }
+ }
+
+ return ret;
+}
+
+static void free_krb5_db_entry(krb5_context context,
+ krb5_db_entry *k)
+{
+ krb5_tl_data *tl_data_next = NULL;
+ krb5_tl_data *tl_data = NULL;
+ int i, j;
+
+ if (k == NULL) {
+ return;
+ }
+
+ krb5_free_principal(context, k->princ);
+
+ for (tl_data = k->tl_data; tl_data; tl_data = tl_data_next) {
+ tl_data_next = tl_data->tl_data_next;
+ if (tl_data->tl_data_contents != NULL) {
+ free(tl_data->tl_data_contents);
+ }
+ free(tl_data);
+ }
+
+ if (k->key_data != NULL) {
+ for (i = 0; i < k->n_key_data; i++) {
+ for (j = 0; j < k->key_data[i].key_data_ver; j++) {
+ if (k->key_data[i].key_data_length[j] != 0) {
+ if (k->key_data[i].key_data_contents[j] != NULL) {
+ BURN_PTR_SIZE(k->key_data[i].key_data_contents[j], k->key_data[i].key_data_length[j]);
+ free(k->key_data[i].key_data_contents[j]);
+ }
+ }
+ k->key_data[i].key_data_contents[j] = NULL;
+ k->key_data[i].key_data_length[j] = 0;
+ k->key_data[i].key_data_type[j] = 0;
+ }
+ }
+ free(k->key_data);
+ }
+
+ ZERO_STRUCTP(k);
+}
+
+int sdb_entry_to_krb5_db_entry(krb5_context context,
+ const struct sdb_entry *s,
+ krb5_db_entry *k)
+{
+ struct samba_kdc_entry *ske = s->skdc_entry;
+ krb5_error_code ret;
+ int i;
+
+ ZERO_STRUCTP(k);
+
+ k->magic = KRB5_KDB_MAGIC_NUMBER;
+ k->len = KRB5_KDB_V1_BASE_LENGTH;
+
+ ret = krb5_copy_principal(context,
+ s->principal,
+ &k->princ);
+ if (ret) {
+ free_krb5_db_entry(context, k);
+ return ret;
+ }
+
+ ret = SDBFlags_to_kflags(&s->flags,
+ &k->attributes);
+ if (ret) {
+ free_krb5_db_entry(context, k);
+ return ret;
+ }
+
+ if (s->max_life != NULL) {
+ k->max_life = *s->max_life;
+ }
+ if (s->max_renew != NULL) {
+ k->max_renewable_life = *s->max_renew;
+ }
+ if (s->valid_end != NULL) {
+ k->expiration = *s->valid_end;
+ }
+ if (s->pw_end != NULL) {
+ k->pw_expiration = *s->pw_end;
+ }
+
+ /* last_success */
+ /* last_failed */
+ /* fail_auth_count */
+ /* n_tl_data */
+
+ /*
+ * If we leave early when looking up the realm, we do not have all
+ * information about a principal. We need to construct a db entry
+ * with minimal information, so skip this part.
+ */
+ if (s->created_by.time != 0) {
+ ret = sdb_event_to_kmod(context,
+ s->modified_by ? s->modified_by : &s->created_by,
+ k);
+ if (ret) {
+ free_krb5_db_entry(context, k);
+ return ret;
+ }
+ }
+
+ /* FIXME: TODO HDB Extensions */
+
+ /*
+ * Don't copy keys (allow password auth) if s->flags.require_hwauth is
+ * set which translates to UF_SMARTCARD_REQUIRED.
+ */
+ if (s->keys.len > 0 && s->flags.require_hwauth == 0) {
+ k->key_data = malloc(s->keys.len * sizeof(krb5_key_data));
+ if (k->key_data == NULL) {
+ free_krb5_db_entry(context, k);
+ return ret;
+ }
+
+ for (i=0; i < s->keys.len; i++) {
+ ret = sdb_key_to_krb5_key_data(&s->keys.val[i],
+ s->kvno,
+ &k->key_data[i]);
+ if (ret) {
+ free_krb5_db_entry(context, k);
+ return ret;
+ }
+
+ k->n_key_data++;
+ }
+ }
+
+ k->e_data = (void *)ske;
+ if (ske != NULL) {
+ ske->kdc_entry = k;
+ }
+ return 0;
+}
diff --git a/source4/kdc/wdc-samba4.c b/source4/kdc/wdc-samba4.c
new file mode 100644
index 0000000..dc2fffa
--- /dev/null
+++ b/source4/kdc/wdc-samba4.c
@@ -0,0 +1,937 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ PAC Glue between Samba and the KDC
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009
+ Copyright (C) Simo Sorce <idra@samba.org> 2010
+
+ 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 "includes.h"
+#include "kdc/authn_policy_util.h"
+#include "kdc/kdc-glue.h"
+#include "kdc/db-glue.h"
+#include "kdc/pac-glue.h"
+#include "sdb.h"
+#include "sdb_hdb.h"
+#include "librpc/gen_ndr/auth.h"
+#include <krb5_locl.h>
+#include "lib/replace/system/filesys.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_KERBEROS
+
+static bool samba_wdc_is_s4u2self_req(astgs_request_t r)
+{
+ const KDC_REQ *req = kdc_request_get_req(r);
+ const PA_DATA *pa_for_user = NULL;
+
+ if (req->msg_type != krb_tgs_req) {
+ return false;
+ }
+
+ if (req->padata != NULL) {
+ int idx = 0;
+
+ pa_for_user = krb5_find_padata(req->padata->val,
+ req->padata->len,
+ KRB5_PADATA_FOR_USER,
+ &idx);
+ }
+
+ if (pa_for_user != NULL) {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Given the right private pointer from hdb_samba4,
+ * get a PAC from the attached ldb messages.
+ *
+ * For PKINIT we also get pk_reply_key and can add PAC_CREDENTIAL_INFO.
+ */
+static krb5_error_code samba_wdc_get_pac(void *priv,
+ astgs_request_t r,
+ hdb_entry *client,
+ hdb_entry *server,
+ const krb5_keyblock *pk_reply_key,
+ uint64_t pac_attributes,
+ krb5_pac *pac)
+{
+ krb5_context context = kdc_request_get_context((kdc_request_t)r);
+ TALLOC_CTX *mem_ctx;
+ DATA_BLOB *logon_blob = NULL;
+ DATA_BLOB *cred_ndr = NULL;
+ DATA_BLOB **cred_ndr_ptr = NULL;
+ DATA_BLOB _cred_blob = data_blob_null;
+ DATA_BLOB *cred_blob = NULL;
+ DATA_BLOB *upn_blob = NULL;
+ DATA_BLOB *pac_attrs_blob = NULL;
+ DATA_BLOB *requester_sid_blob = NULL;
+ DATA_BLOB client_claims_blob = {};
+ krb5_error_code ret;
+ NTSTATUS nt_status;
+ struct samba_kdc_entry *skdc_entry =
+ talloc_get_type_abort(client->context,
+ struct samba_kdc_entry);
+ const struct samba_kdc_entry *server_entry =
+ talloc_get_type_abort(server->context,
+ struct samba_kdc_entry);
+ bool is_krbtgt = krb5_principal_is_krbtgt(context, server->principal);
+ enum auth_group_inclusion group_inclusion;
+ bool is_s4u2self = samba_wdc_is_s4u2self_req(r);
+ enum samba_asserted_identity asserted_identity =
+ (is_s4u2self) ?
+ SAMBA_ASSERTED_IDENTITY_SERVICE :
+ SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY;
+ struct authn_audit_info *server_audit_info = NULL;
+ NTSTATUS reply_status = NT_STATUS_OK;
+
+ const struct auth_user_info_dc *user_info_dc_const = NULL;
+ struct auth_user_info_dc *user_info_dc_shallow_copy = NULL;
+ struct auth_claims auth_claims = {};
+
+ /* Only include resource groups in a service ticket. */
+ if (is_krbtgt) {
+ group_inclusion = AUTH_EXCLUDE_RESOURCE_GROUPS;
+ } else if (server_entry->supported_enctypes & KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED) {
+ group_inclusion = AUTH_INCLUDE_RESOURCE_GROUPS;
+ } else {
+ group_inclusion = AUTH_INCLUDE_RESOURCE_GROUPS_COMPRESSED;
+ }
+
+ mem_ctx = talloc_named(client->context, 0, "samba_wdc_get_pac context");
+ if (!mem_ctx) {
+ return ENOMEM;
+ }
+
+ if (pk_reply_key != NULL) {
+ cred_ndr_ptr = &cred_ndr;
+ }
+
+ ret = samba_kdc_get_user_info_from_db(mem_ctx,
+ server_entry->kdc_db_ctx->samdb,
+ skdc_entry,
+ skdc_entry->msg,
+ &user_info_dc_const);
+ if (ret) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* Make a shallow copy of the user_info_dc structure. */
+ nt_status = authsam_shallow_copy_user_info_dc(mem_ctx,
+ user_info_dc_const,
+ &user_info_dc_shallow_copy);
+ user_info_dc_const = NULL;
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Failed to allocate user_info_dc SIDs: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(mem_ctx);
+ return map_errno_from_nt_status(nt_status);
+ }
+
+ nt_status = samba_kdc_add_asserted_identity(asserted_identity,
+ user_info_dc_shallow_copy);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Failed to add asserted identity: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(mem_ctx);
+ return map_errno_from_nt_status(nt_status);
+ }
+
+ nt_status = samba_kdc_add_claims_valid(user_info_dc_shallow_copy);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Failed to add Claims Valid: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(mem_ctx);
+ return map_errno_from_nt_status(nt_status);
+ }
+
+ ret = samba_kdc_get_claims_data_from_db(server_entry->kdc_db_ctx->samdb,
+ skdc_entry,
+ &auth_claims.user_claims);
+ if (ret) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ nt_status = claims_data_encoded_claims_set(mem_ctx,
+ auth_claims.user_claims,
+ &client_claims_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(mem_ctx);
+ return map_errno_from_nt_status(nt_status);
+ }
+
+ /*
+ * For an S4U2Self request, the authentication policy is not enforced.
+ */
+ if (!is_s4u2self && authn_policy_restrictions_present(server_entry->server_policy)) {
+ const hdb_entry *device = kdc_request_get_armor_client(r);
+ const struct auth_user_info_dc *device_info = NULL;
+
+ if (device != NULL) {
+ const hdb_entry *device_krbtgt = NULL;
+ struct samba_kdc_entry *device_skdc_entry = NULL;
+ const struct samba_kdc_entry *device_krbtgt_skdc_entry = NULL;
+ const krb5_const_pac device_pac = kdc_request_get_armor_pac(r);
+ struct samba_kdc_entry_pac device_pac_entry = {};
+
+ device_skdc_entry = talloc_get_type_abort(device->context,
+ struct samba_kdc_entry);
+
+ device_krbtgt = kdc_request_get_armor_server(r);
+ if (device_krbtgt != NULL) {
+ device_krbtgt_skdc_entry = talloc_get_type_abort(device_krbtgt->context,
+ struct samba_kdc_entry);
+ }
+
+ device_pac_entry = samba_kdc_entry_pac(device_pac,
+ device_skdc_entry,
+ samba_kdc_entry_is_trust(device_krbtgt_skdc_entry));
+
+ ret = samba_kdc_get_user_info_dc(mem_ctx,
+ context,
+ server_entry->kdc_db_ctx->samdb,
+ device_pac_entry,
+ &device_info,
+ NULL /* resource_groups_out */);
+ if (ret) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ ret = samba_kdc_get_claims_data(mem_ctx,
+ context,
+ server_entry->kdc_db_ctx->samdb,
+ device_pac_entry,
+ &auth_claims.device_claims);
+ if (ret) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+
+ ret = samba_kdc_allowed_to_authenticate_to(mem_ctx,
+ server_entry->kdc_db_ctx->samdb,
+ server_entry->kdc_db_ctx->lp_ctx,
+ skdc_entry,
+ user_info_dc_shallow_copy,
+ device_info,
+ auth_claims,
+ server_entry,
+ &server_audit_info,
+ &reply_status);
+ if (server_audit_info != NULL) {
+ krb5_error_code ret2;
+
+ ret2 = hdb_samba4_set_steal_server_audit_info(r, server_audit_info);
+ if (ret == 0) {
+ ret = ret2;
+ }
+ }
+ if (!NT_STATUS_IS_OK(reply_status)) {
+ krb5_error_code ret2;
+
+ ret2 = hdb_samba4_set_ntstatus(r, reply_status, ret);
+ if (ret == 0) {
+ ret = ret2;
+ }
+ }
+ if (ret) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+
+ nt_status = samba_kdc_get_logon_info_blob(mem_ctx,
+ user_info_dc_shallow_copy,
+ group_inclusion,
+ &logon_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(mem_ctx);
+ return map_errno_from_nt_status(nt_status);
+ }
+
+ if (cred_ndr_ptr != NULL) {
+ nt_status = samba_kdc_get_cred_ndr_blob(mem_ctx,
+ skdc_entry,
+ cred_ndr_ptr);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(mem_ctx);
+ return map_errno_from_nt_status(nt_status);
+ }
+ }
+
+ nt_status = samba_kdc_get_upn_info_blob(mem_ctx,
+ user_info_dc_shallow_copy,
+ &upn_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(mem_ctx);
+ return map_errno_from_nt_status(nt_status);
+ }
+
+ if (is_krbtgt) {
+ nt_status = samba_kdc_get_pac_attrs_blob(mem_ctx,
+ pac_attributes,
+ &pac_attrs_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(mem_ctx);
+ return map_errno_from_nt_status(nt_status);
+ }
+
+ nt_status = samba_kdc_get_requester_sid_blob(mem_ctx,
+ user_info_dc_shallow_copy,
+ &requester_sid_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(mem_ctx);
+ return map_errno_from_nt_status(nt_status);
+ }
+ }
+
+ if (pk_reply_key != NULL && cred_ndr != NULL) {
+ ret = samba_kdc_encrypt_pac_credentials(context,
+ pk_reply_key,
+ cred_ndr,
+ mem_ctx,
+ &_cred_blob);
+ if (ret != 0) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ cred_blob = &_cred_blob;
+ }
+
+ ret = krb5_pac_init(context, pac);
+ if (ret != 0) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ ret = samba_make_krb5_pac(context, logon_blob, cred_blob,
+ upn_blob, pac_attrs_blob,
+ requester_sid_blob, NULL,
+ &client_claims_blob, NULL, NULL,
+ *pac);
+
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static krb5_error_code samba_wdc_verify_pac2(astgs_request_t r,
+ const hdb_entry *delegated_proxy,
+ const hdb_entry *client,
+ const hdb_entry *krbtgt,
+ const krb5_pac pac,
+ krb5_cksumtype ctype)
+{
+ krb5_context context = kdc_request_get_context((kdc_request_t)r);
+ struct samba_kdc_entry *client_skdc_entry = NULL;
+ struct samba_kdc_entry *krbtgt_skdc_entry =
+ talloc_get_type_abort(krbtgt->context, struct samba_kdc_entry);
+ struct samba_kdc_entry_pac client_pac_entry = {};
+ TALLOC_CTX *mem_ctx = NULL;
+ krb5_error_code ret;
+ bool is_s4u2self = samba_wdc_is_s4u2self_req(r);
+ bool is_in_db = false;
+ bool is_trusted = false;
+ uint32_t flags = 0;
+
+ if (pac == NULL) {
+ return EINVAL;
+ }
+
+ mem_ctx = talloc_named(NULL, 0, "samba_wdc_verify_pac2 context");
+ if (mem_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ if (client != NULL) {
+ client_skdc_entry = talloc_get_type_abort(client->context,
+ struct samba_kdc_entry);
+ }
+
+ /*
+ * If the krbtgt was generated by an RODC, and we are not that
+ * RODC, then we need to regenerate the PAC - we can't trust
+ * it, and confirm that the RODC was permitted to print this ticket
+ *
+ * Because of the samba_kdc_validate_pac_blob() step we can be
+ * sure that the record in 'client' matches the SID in the
+ * original PAC.
+ */
+ ret = samba_krbtgt_is_in_db(krbtgt_skdc_entry, &is_in_db, &is_trusted);
+ if (ret != 0) {
+ goto out;
+ }
+
+ krb5_pac_set_trusted(pac, is_trusted);
+ client_pac_entry = samba_kdc_entry_pac(pac,
+ client_skdc_entry,
+ samba_kdc_entry_is_trust(krbtgt_skdc_entry));
+
+ if (is_s4u2self) {
+ flags |= SAMBA_KDC_FLAG_PROTOCOL_TRANSITION;
+ }
+
+ if (delegated_proxy != NULL) {
+ krb5_enctype etype;
+ Key *key = NULL;
+
+ if (!is_in_db) {
+ /*
+ * The RODC-issued PAC was signed by a KDC entry that we
+ * don't have a key for. The server signature is not
+ * trustworthy, since it could have been created by the
+ * server we got the ticket from. We must not proceed as
+ * otherwise the ticket signature is unchecked.
+ */
+ ret = HDB_ERR_NOT_FOUND_HERE;
+ goto out;
+ }
+
+ /* Fetch the correct key depending on the checksum type. */
+ if (ctype == CKSUMTYPE_HMAC_MD5) {
+ etype = ENCTYPE_ARCFOUR_HMAC;
+ } else {
+ ret = krb5_cksumtype_to_enctype(context,
+ ctype,
+ &etype);
+ if (ret != 0) {
+ goto out;
+ }
+ }
+ ret = hdb_enctype2key(context, krbtgt, NULL, etype, &key);
+ if (ret != 0) {
+ goto out;
+ }
+
+ /* Check the KDC, whole-PAC and ticket signatures. */
+ ret = krb5_pac_verify(context,
+ pac,
+ 0,
+ NULL,
+ NULL,
+ &key->key);
+ if (ret != 0) {
+ DBG_WARNING("PAC KDC signature failed to verify\n");
+ goto out;
+ }
+
+ flags |= SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION;
+ }
+
+ ret = samba_kdc_verify_pac(mem_ctx,
+ context,
+ krbtgt_skdc_entry->kdc_db_ctx->samdb,
+ flags,
+ client_pac_entry,
+ krbtgt_skdc_entry);
+ if (ret != 0) {
+ goto out;
+ }
+
+out:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+/* Re-sign (and reform, including possibly new groups) a PAC */
+
+static krb5_error_code samba_wdc_reget_pac(void *priv, astgs_request_t r,
+ krb5_const_principal _client_principal,
+ hdb_entry *delegated_proxy,
+ krb5_const_pac delegated_proxy_pac,
+ hdb_entry *client,
+ hdb_entry *server,
+ hdb_entry *krbtgt,
+ krb5_pac *pac)
+{
+ krb5_context context = kdc_request_get_context((kdc_request_t)r);
+ struct samba_kdc_entry *delegated_proxy_skdc_entry = NULL;
+ krb5_const_principal delegated_proxy_principal = NULL;
+ struct samba_kdc_entry_pac delegated_proxy_pac_entry = {};
+ struct samba_kdc_entry *client_skdc_entry = NULL;
+ struct samba_kdc_entry_pac client_pac_entry = {};
+ struct samba_kdc_entry_pac device = {};
+ const struct samba_kdc_entry *server_skdc_entry =
+ talloc_get_type_abort(server->context, struct samba_kdc_entry);
+ const struct samba_kdc_entry *krbtgt_skdc_entry =
+ talloc_get_type_abort(krbtgt->context, struct samba_kdc_entry);
+ TALLOC_CTX *mem_ctx = NULL;
+ krb5_pac new_pac = NULL;
+ struct authn_audit_info *server_audit_info = NULL;
+ krb5_error_code ret;
+ NTSTATUS reply_status = NT_STATUS_OK;
+ uint32_t flags = 0;
+
+ if (pac == NULL) {
+ return EINVAL;
+ }
+
+ mem_ctx = talloc_named(NULL, 0, "samba_wdc_reget_pac context");
+ if (mem_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ if (delegated_proxy != NULL) {
+ delegated_proxy_skdc_entry = talloc_get_type_abort(delegated_proxy->context,
+ struct samba_kdc_entry);
+ delegated_proxy_principal = delegated_proxy->principal;
+ }
+
+ delegated_proxy_pac_entry = samba_kdc_entry_pac(delegated_proxy_pac,
+ delegated_proxy_skdc_entry,
+ /* The S4U2Proxy
+ * evidence ticket could
+ * not have been signed
+ * or issued by a krbtgt
+ * trust account. */
+ false /* is_from_trust */);
+
+ if (client != NULL) {
+ client_skdc_entry = talloc_get_type_abort(client->context,
+ struct samba_kdc_entry);
+ }
+
+ device = samba_kdc_get_device_pac(r);
+
+ ret = krb5_pac_init(context, &new_pac);
+ if (ret != 0) {
+ new_pac = NULL;
+ goto out;
+ }
+
+ client_pac_entry = samba_kdc_entry_pac(*pac,
+ client_skdc_entry,
+ samba_kdc_entry_is_trust(krbtgt_skdc_entry));
+
+ ret = samba_kdc_update_pac(mem_ctx,
+ context,
+ krbtgt_skdc_entry->kdc_db_ctx->samdb,
+ krbtgt_skdc_entry->kdc_db_ctx->lp_ctx,
+ flags,
+ client_pac_entry,
+ server->principal,
+ server_skdc_entry,
+ delegated_proxy_principal,
+ delegated_proxy_pac_entry,
+ device,
+ new_pac,
+ &server_audit_info,
+ &reply_status);
+ if (server_audit_info != NULL) {
+ krb5_error_code ret2;
+
+ ret2 = hdb_samba4_set_steal_server_audit_info(r, server_audit_info);
+ if (ret == 0) {
+ ret = ret2;
+ }
+ }
+ if (!NT_STATUS_IS_OK(reply_status)) {
+ krb5_error_code ret2;
+
+ ret2 = hdb_samba4_set_ntstatus(r, reply_status, ret);
+ if (ret == 0) {
+ ret = ret2;
+ }
+ }
+ if (ret != 0) {
+ krb5_pac_free(context, new_pac);
+ if (ret == ENOATTR) {
+ krb5_pac_free(context, *pac);
+ *pac = NULL;
+ ret = 0;
+ }
+ goto out;
+ }
+
+ /* Replace the pac */
+ krb5_pac_free(context, *pac);
+ *pac = new_pac;
+
+out:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+/* Verify a PAC's SID and signatures */
+
+static krb5_error_code samba_wdc_verify_pac(void *priv, astgs_request_t r,
+ krb5_const_principal _client_principal,
+ hdb_entry *delegated_proxy,
+ hdb_entry *client,
+ hdb_entry *_server,
+ hdb_entry *krbtgt,
+ EncTicketPart *ticket,
+ krb5_pac pac)
+{
+ krb5_context context = kdc_request_get_context((kdc_request_t)r);
+ krb5_kdc_configuration *config = kdc_request_get_config((kdc_request_t)r);
+ struct samba_kdc_entry *krbtgt_skdc_entry =
+ talloc_get_type_abort(krbtgt->context,
+ struct samba_kdc_entry);
+ krb5_error_code ret;
+ krb5_cksumtype ctype = CKSUMTYPE_NONE;
+ hdb_entry signing_krbtgt_hdb;
+
+ if (delegated_proxy) {
+ uint16_t pac_kdc_signature_rodc_id;
+ const unsigned int local_tgs_rodc_id = krbtgt_skdc_entry->kdc_db_ctx->my_krbtgt_number;
+ const uint16_t header_ticket_rodc_id = krbtgt->kvno >> 16;
+
+ /*
+ * We're using delegated_proxy for the moment to indicate cases
+ * where the ticket was encrypted with the server key, and not a
+ * krbtgt key. This cannot be trusted, so we need to find a
+ * krbtgt key that signs the PAC in order to trust the ticket.
+ *
+ * The krbtgt passed in to this function refers to the krbtgt
+ * used to decrypt the ticket of the server requesting
+ * S4U2Proxy.
+ *
+ * When we implement service ticket renewal, we need to check
+ * the PAC, and this will need to be updated.
+ */
+ ret = krb5_pac_get_kdc_checksum_info(context,
+ pac,
+ &ctype,
+ &pac_kdc_signature_rodc_id);
+ if (ret != 0) {
+ DBG_WARNING("Failed to get PAC checksum info\n");
+ return ret;
+ }
+
+ /*
+ * We need to check the KDC and ticket signatures, fetching the
+ * correct key based on the enctype.
+ */
+ if (local_tgs_rodc_id != 0) {
+ /*
+ * If we are an RODC, and we are not the KDC that signed
+ * the evidence ticket, then we need to proxy the
+ * request.
+ */
+ if (local_tgs_rodc_id != pac_kdc_signature_rodc_id) {
+ return HDB_ERR_NOT_FOUND_HERE;
+ }
+ } else {
+ /*
+ * If we are a DC, the ticket may have been signed by a
+ * different KDC than the one that issued the header
+ * ticket.
+ */
+ if (pac_kdc_signature_rodc_id != header_ticket_rodc_id) {
+ struct sdb_entry signing_krbtgt_sdb;
+
+ /*
+ * Fetch our key from the database. To support
+ * key rollover, we're going to need to try
+ * multiple keys by trial and error. For now,
+ * krbtgt keys aren't assumed to change.
+ */
+ ret = samba_kdc_fetch(context,
+ krbtgt_skdc_entry->kdc_db_ctx,
+ krbtgt->principal,
+ SDB_F_GET_KRBTGT | SDB_F_RODC_NUMBER_SPECIFIED | SDB_F_CANON,
+ ((uint32_t)pac_kdc_signature_rodc_id) << 16,
+ &signing_krbtgt_sdb);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = sdb_entry_to_hdb_entry(context,
+ &signing_krbtgt_sdb,
+ &signing_krbtgt_hdb);
+ sdb_entry_free(&signing_krbtgt_sdb);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /*
+ * Replace the krbtgt entry with our own entry
+ * for further processing.
+ */
+ krbtgt = &signing_krbtgt_hdb;
+ }
+ }
+ } else if (!krbtgt_skdc_entry->is_trust) {
+ /*
+ * We expect to have received a TGT, so check that we haven't
+ * been given a kpasswd ticket instead. We don't need to do this
+ * check for an incoming trust, as they use a different secret
+ * and can't be confused with a normal TGT.
+ */
+
+ struct timeval now = krb5_kdc_get_time();
+
+ /*
+ * Check if the ticket is in the last two minutes of its
+ * life.
+ */
+ KerberosTime lifetime = rk_time_sub(ticket->endtime, now.tv_sec);
+ if (lifetime <= CHANGEPW_LIFETIME) {
+ /*
+ * This ticket has at most two minutes left to live. It
+ * may be a kpasswd ticket rather than a TGT, so don't
+ * accept it.
+ */
+ kdc_audit_addreason((kdc_request_t)r,
+ "Ticket is not a ticket-granting ticket");
+ return KRB5KRB_AP_ERR_TKT_EXPIRED;
+ }
+ }
+
+ ret = samba_wdc_verify_pac2(r,
+ delegated_proxy,
+ client,
+ krbtgt,
+ pac,
+ ctype);
+
+ if (krbtgt == &signing_krbtgt_hdb) {
+ hdb_free_entry(context, config->db[0], &signing_krbtgt_hdb);
+ }
+
+ return ret;
+}
+
+static char *get_netbios_name(TALLOC_CTX *mem_ctx, HostAddresses *addrs)
+{
+ char *nb_name = NULL;
+ size_t len;
+ unsigned int i;
+
+ for (i = 0; addrs && i < addrs->len; i++) {
+ if (addrs->val[i].addr_type != KRB5_ADDRESS_NETBIOS) {
+ continue;
+ }
+ len = MIN(addrs->val[i].address.length, 15);
+ nb_name = talloc_strndup(mem_ctx,
+ addrs->val[i].address.data, len);
+ if (nb_name) {
+ break;
+ }
+ }
+
+ if ((nb_name == NULL) || (nb_name[0] == '\0')) {
+ return NULL;
+ }
+
+ /* Strip space padding */
+ for (len = strlen(nb_name) - 1;
+ (len > 0) && (nb_name[len] == ' ');
+ --len) {
+ nb_name[len] = '\0';
+ }
+
+ return nb_name;
+}
+
+static krb5_error_code samba_wdc_check_client_access(void *priv,
+ astgs_request_t r)
+{
+ krb5_context context = kdc_request_get_context((kdc_request_t)r);
+ TALLOC_CTX *tmp_ctx = NULL;
+ const hdb_entry *client = NULL;
+ struct samba_kdc_entry *kdc_entry;
+ struct samba_kdc_entry_pac device = {};
+ struct authn_audit_info *client_audit_info = NULL;
+ bool password_change;
+ char *workstation;
+ NTSTATUS nt_status;
+ NTSTATUS check_device_status = NT_STATUS_OK;
+ krb5_error_code ret = 0;
+
+ client = kdc_request_get_client(r);
+
+ tmp_ctx = talloc_named(client->context, 0, "samba_wdc_check_client_access");
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ kdc_entry = talloc_get_type_abort(client->context, struct samba_kdc_entry);
+
+ device = samba_kdc_get_device_pac(r);
+
+ ret = samba_kdc_check_device(tmp_ctx,
+ context,
+ kdc_entry->kdc_db_ctx->samdb,
+ kdc_entry->kdc_db_ctx->lp_ctx,
+ device,
+ kdc_entry->client_policy,
+ &client_audit_info,
+ &check_device_status);
+ if (client_audit_info != NULL) {
+ krb5_error_code ret2;
+
+ ret2 = hdb_samba4_set_steal_client_audit_info(r, client_audit_info);
+ if (ret2) {
+ ret = ret2;
+ }
+ }
+ kdc_entry->reject_status = check_device_status;
+ if (!NT_STATUS_IS_OK(check_device_status)) {
+ krb5_error_code ret2;
+
+ /*
+ * Add the NTSTATUS to the request so we can return it in the
+ * ‘e-data’ field later.
+ */
+ ret2 = hdb_samba4_set_ntstatus(r, check_device_status, ret);
+ if (ret2) {
+ ret = ret2;
+ }
+ }
+
+ if (ret) {
+ /*
+ * As we didn’t get far enough to check the server policy, only
+ * the client policy will be referenced in the authentication
+ * log message.
+ */
+
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ workstation = get_netbios_name(tmp_ctx,
+ kdc_request_get_req(r)->req_body.addresses);
+ password_change = (kdc_request_get_server(r) && kdc_request_get_server(r)->flags.change_pw);
+
+ nt_status = samba_kdc_check_client_access(kdc_entry,
+ kdc_request_get_cname((kdc_request_t)r),
+ workstation,
+ password_change);
+
+ kdc_entry->reject_status = nt_status;
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ krb5_error_code ret2;
+
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MEMORY)) {
+ talloc_free(tmp_ctx);
+ return ENOMEM;
+ }
+
+ ret = samba_kdc_map_policy_err(nt_status);
+
+ /*
+ * Add the NTSTATUS to the request so we can return it in the
+ * ‘e-data’ field later.
+ */
+ ret2 = hdb_samba4_set_ntstatus(r, nt_status, ret);
+ if (ret2) {
+ ret = ret2;
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* Now do the standard Heimdal check */
+ talloc_free(tmp_ctx);
+ return KRB5_PLUGIN_NO_HANDLE;
+}
+
+/* this function allocates 'data' using malloc.
+ * The caller is responsible for freeing it */
+static krb5_error_code samba_kdc_build_supported_etypes(uint32_t supported_etypes,
+ krb5_data *e_data)
+{
+ e_data->data = malloc(4);
+ if (e_data->data == NULL) {
+ return ENOMEM;
+ }
+ e_data->length = 4;
+
+ PUSH_LE_U32(e_data->data, 0, supported_etypes);
+
+ return 0;
+}
+
+static krb5_error_code samba_wdc_finalize_reply(void *priv,
+ astgs_request_t r)
+{
+ struct samba_kdc_entry *server_kdc_entry;
+ uint32_t supported_enctypes;
+
+ server_kdc_entry = talloc_get_type(kdc_request_get_server(r)->context, struct samba_kdc_entry);
+
+ /*
+ * If the canonicalize flag is set, add PA-SUPPORTED-ENCTYPES padata
+ * type to indicate what encryption types the server supports.
+ */
+ supported_enctypes = server_kdc_entry->supported_enctypes;
+ if (kdc_request_get_req(r)->req_body.kdc_options.canonicalize && supported_enctypes != 0) {
+ krb5_error_code ret;
+
+ PA_DATA md;
+
+ ret = samba_kdc_build_supported_etypes(supported_enctypes, &md.padata_value);
+ if (ret != 0) {
+ return ret;
+ }
+
+ md.padata_type = KRB5_PADATA_SUPPORTED_ETYPES;
+
+ ret = kdc_request_add_encrypted_padata(r, &md);
+ if (ret != 0) {
+ /*
+ * So we do not leak the allocated
+ * memory on md in the error case
+ */
+ krb5_data_free(&md.padata_value);
+ }
+ }
+
+ return 0;
+}
+
+static krb5_error_code samba_wdc_plugin_init(krb5_context context, void **ptr)
+{
+ *ptr = NULL;
+ return 0;
+}
+
+static void samba_wdc_plugin_fini(void *ptr)
+{
+ return;
+}
+
+static krb5_error_code samba_wdc_referral_policy(void *priv,
+ astgs_request_t r)
+{
+ return kdc_request_get_error_code((kdc_request_t)r);
+}
+
+struct krb5plugin_kdc_ftable kdc_plugin_table = {
+ .minor_version = KRB5_PLUGIN_KDC_VERSION_11,
+ .init = samba_wdc_plugin_init,
+ .fini = samba_wdc_plugin_fini,
+ .pac_verify = samba_wdc_verify_pac,
+ .pac_update = samba_wdc_reget_pac,
+ .client_access = samba_wdc_check_client_access,
+ .finalize_reply = samba_wdc_finalize_reply,
+ .pac_generate = samba_wdc_get_pac,
+ .referral_policy = samba_wdc_referral_policy,
+};
+
+
diff --git a/source4/kdc/wscript_build b/source4/kdc/wscript_build
new file mode 100644
index 0000000..b15f3e7
--- /dev/null
+++ b/source4/kdc/wscript_build
@@ -0,0 +1,194 @@
+#!/usr/bin/env python
+
+# We do this because we do not want to depend on the KDC, only find and use it's header files. We do not want
+if not bld.CONFIG_SET("USING_SYSTEM_KDC"):
+ kdc_include = "../../third_party/heimdal/kdc ../../third_party/heimdal/lib/gssapi"
+else:
+ kdc_include = getattr(bld.env, "CPPPATH_KDC")
+
+if bld.CONFIG_SET('SAMBA4_USES_HEIMDAL'):
+ bld.SAMBA_MODULE('service_kdc',
+ source='kdc-heimdal.c',
+ subsystem='service',
+ init_function='server_service_kdc_init',
+ deps='''
+ kdc
+ HDB_SAMBA4
+ WDC_SAMBA4
+ samba-hostconfig
+ com_err
+ samba_server_gensec
+ PAC_GLUE
+ KDC-GLUE
+ KDC-SERVER
+ KPASSWD-SERVICE
+ KPASSWD_GLUE
+ ''',
+ internal_module=False)
+
+if bld.CONFIG_GET('SAMBA_USES_MITKDC'):
+ bld.SAMBA_MODULE('service_kdc',
+ source='kdc-service-mit.c',
+ cflags_end='-Wno-strict-prototypes',
+ subsystem='service',
+ init_function='server_service_mitkdc_init',
+ deps='''
+ samba-hostconfig
+ service
+ talloc
+ UTIL_RUNCMD
+ MIT_KDC_IRPC
+ KDC-SERVER
+ KPASSWD-SERVICE
+ com_err
+ kadm5srv_mit
+ kdb5
+ ''',
+ internal_module=False)
+
+bld.SAMBA_LIBRARY('HDB_SAMBA4',
+ source='hdb-samba4.c hdb-samba4-plugin.c',
+ deps='ldb auth4_sam common_auth samba-credentials hdb kdc db-glue samba-hostconfig com_err sdb_hdb RPC_NDR_WINBIND',
+ includes=kdc_include,
+ private_library=True,
+ enabled=bld.CONFIG_SET('SAMBA4_USES_HEIMDAL')
+ )
+
+# A plugin for Heimdal's kadmin for users who need to operate that tool
+bld.SAMBA_LIBRARY('HDB_SAMBA4_PLUGIN',
+ source='hdb-samba4-plugin.c',
+ deps='hdb HDB_SAMBA4 samba-util samba-hostconfig ',
+ link_name='modules/hdb/hdb_samba4.so',
+ realname='hdb_samba4.so',
+ install_path='${MODULESDIR}/hdb',
+ enabled = (bld.CONFIG_SET("USING_SYSTEM_KRB5") and bld.CONFIG_SET("USING_SYSTEM_HDB"))
+ )
+
+bld.SAMBA_SUBSYSTEM('KDC-SERVER',
+ source='kdc-server.c kdc-proxy.c',
+ deps='''
+ krb5samba
+ ldb
+ LIBTSOCKET
+ LIBSAMBA_TSOCKET
+ ''')
+
+kpasswd_flavor_src = 'kpasswd-service.c kpasswd-helper.c'
+if bld.CONFIG_SET('SAMBA4_USES_HEIMDAL'):
+ kpasswd_flavor_src = kpasswd_flavor_src + ' kpasswd-service-heimdal.c'
+elif bld.CONFIG_GET('SAMBA_USES_MITKDC'):
+ kpasswd_flavor_src = kpasswd_flavor_src + ' kpasswd-service-mit.c'
+
+bld.SAMBA_SUBSYSTEM('KPASSWD-SERVICE',
+ source=kpasswd_flavor_src,
+ deps='''
+ krb5samba
+ samba_server_gensec
+ KPASSWD_GLUE
+ gensec_krb5_helpers
+ ''')
+
+bld.SAMBA_SUBSYSTEM('KDC-GLUE',
+ source='kdc-glue.c',
+ includes=kdc_include,
+ deps='hdb PAC_GLUE',
+ enabled=bld.CONFIG_SET('SAMBA4_USES_HEIMDAL')
+ )
+
+bld.SAMBA_SUBSYSTEM('WDC_SAMBA4',
+ source='wdc-samba4.c',
+ includes=kdc_include,
+ deps='ldb auth4_sam common_auth samba-credentials hdb PAC_GLUE samba-hostconfig com_err KDC-GLUE authn_policy_util',
+ enabled=bld.CONFIG_SET('SAMBA4_USES_HEIMDAL')
+ )
+
+bld.SAMBA_SUBSYSTEM('sdb',
+ source='sdb.c',
+ deps='talloc krb5',
+ )
+
+bld.SAMBA_SUBSYSTEM('sdb_hdb',
+ source='sdb_to_hdb.c',
+ deps='talloc sdb hdb',
+ autoproto='sdb_hdb.h',
+ enabled=bld.CONFIG_SET('SAMBA4_USES_HEIMDAL')
+ )
+
+bld.SAMBA_SUBSYSTEM('sdb_kdb',
+ source='sdb_to_kdb.c',
+ deps='sdb kdb5',
+ autoproto='sdb_kdb.h',
+ enabled=bld.CONFIG_SET('HAVE_KDB_H')
+ )
+
+bld.SAMBA_SUBSYSTEM('PAC_GLUE',
+ source='pac-glue.c pac-blobs.c',
+ deps='ldb auth4_sam common_auth samba-credentials samba-hostconfig com_err ad_claims authn_policy authn_policy_util'
+ )
+
+bld.SAMBA_LIBRARY('pac',
+ source=[],
+ deps='PAC_GLUE',
+ private_library=True,
+ grouping_library=True)
+
+
+bld.SAMBA_LIBRARY('db-glue',
+ source='db-glue.c',
+ deps='ldb auth4_sam common_auth samba-credentials sdb samba-hostconfig com_err RPC_NDR_IRPC MESSAGING PAC_GLUE authn_policy_util',
+ private_library=True,
+ )
+
+bld.SAMBA_LIBRARY('ad_claims',
+ source='ad_claims.c',
+ deps='ldb samba-util samdb dsdb-module authn_policy_util',
+ private_library=True,
+ )
+
+bld.SAMBA_LIBRARY('authn_policy_util',
+ source='authn_policy_util.c',
+ deps='authn_policy samdb dsdb-module',
+ private_library=True,
+ )
+
+bld.SAMBA_SUBSYSTEM('KPASSWD_GLUE',
+ source='kpasswd_glue.c',
+ deps='ldb com_err')
+
+bld.SAMBA_SUBSYSTEM('MIT_KDC_IRPC',
+ source='mit_kdc_irpc.c',
+ deps='''
+ ldb
+ auth4_sam
+ samba-credentials
+ db-glue
+ samba-hostconfig
+ com_err
+ kdb5
+ ''',
+ enabled=(bld.CONFIG_SET('SAMBA_USES_MITKDC') and bld.CONFIG_SET('HAVE_KDB_H'))
+ )
+
+bld.SAMBA_SUBSYSTEM('MIT_SAMBA',
+ source='mit_samba.c',
+ deps='''
+ ldb
+ auth4_sam
+ common_auth
+ samba-credentials
+ db-glue
+ PAC_GLUE
+ KPASSWD_GLUE
+ samba-hostconfig
+ com_err
+ sdb_kdb
+ kdb5
+ ''',
+ enabled=(not bld.CONFIG_SET('SAMBA4_USES_HEIMDAL') and bld.CONFIG_SET('HAVE_KDB_H')) )
+
+bld.SAMBA_BINARY('samba4ktutil',
+ 'ktutil.c',
+ deps='krb5samba',
+ install=False)
+
+bld.RECURSE('mit-kdb')