summaryrefslogtreecommitdiffstats
path: root/source4/kdc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--source4/kdc/db-glue.c3668
-rw-r--r--source4/kdc/db-glue.h111
-rw-r--r--source4/kdc/hdb-samba4-plugin.c85
-rw-r--r--source4/kdc/hdb-samba4.c886
-rw-r--r--source4/kdc/kdc-glue.c65
-rw-r--r--source4/kdc/kdc-glue.h57
-rw-r--r--source4/kdc/kdc-heimdal.c518
-rw-r--r--source4/kdc/kdc-proxy.c594
-rw-r--r--source4/kdc/kdc-proxy.h45
-rw-r--r--source4/kdc/kdc-server.c618
-rw-r--r--source4/kdc/kdc-server.h83
-rw-r--r--source4/kdc/kdc-service-mit.c392
-rw-r--r--source4/kdc/kdc-service-mit.h27
-rw-r--r--source4/kdc/kpasswd-helper.c261
-rw-r--r--source4/kdc/kpasswd-helper.h48
-rw-r--r--source4/kdc/kpasswd-service-heimdal.c323
-rw-r--r--source4/kdc/kpasswd-service-mit.c400
-rw-r--r--source4/kdc/kpasswd-service.c391
-rw-r--r--source4/kdc/kpasswd-service.h43
-rw-r--r--source4/kdc/kpasswd_glue.c85
-rw-r--r--source4/kdc/kpasswd_glue.h31
-rw-r--r--source4/kdc/ktutil.c122
-rw-r--r--source4/kdc/mit-kdb/kdb_samba.c182
-rw-r--r--source4/kdc/mit-kdb/kdb_samba.h184
-rw-r--r--source4/kdc/mit-kdb/kdb_samba_change_pwd.c59
-rw-r--r--source4/kdc/mit-kdb/kdb_samba_common.c114
-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.c769
-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.c200
-rw-r--r--source4/kdc/mit_kdc_irpc.h20
-rw-r--r--source4/kdc/mit_samba.c1066
-rw-r--r--source4/kdc/mit_samba.h106
-rw-r--r--source4/kdc/pac-glue.c2006
-rw-r--r--source4/kdc/pac-glue.h122
-rw-r--r--source4/kdc/samba_kdc.h73
-rw-r--r--source4/kdc/sdb.c212
-rw-r--r--source4/kdc/sdb.h146
-rw-r--r--source4/kdc/sdb_to_hdb.c323
-rw-r--r--source4/kdc/sdb_to_kdb.c329
-rw-r--r--source4/kdc/wdc-samba4.c642
-rw-r--r--source4/kdc/wscript_build182
44 files changed, 16191 insertions, 0 deletions
diff --git a/source4/kdc/db-glue.c b/source4/kdc/db-glue.c
new file mode 100644
index 0000000..83d5c90
--- /dev/null
+++ b/source4/kdc/db-glue.c
@@ -0,0 +1,3668 @@
+/*
+ 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/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 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) {
+ DEBUG(1,(__location__ ": Unable to get binding handle for dreplsrv\n"));
+ TALLOC_FREE(tmp_ctx);
+ return;
+ }
+
+ r.in.user_dn = ldb_dn_get_linearized(user_dn);
+
+ /*
+ * 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 char *tmp;
+ const char *gentime;
+ struct tm tm;
+
+ gentime = ldb_msg_find_attr_as_string(msg, attr, NULL);
+ if (!gentime)
+ return default_val;
+
+ tmp = strptime(gentime, "%Y%m%d%H%M%SZ", &tm);
+ if (tmp == NULL) {
+ return default_val;
+ }
+
+ return timegm(&tm);
+}
+
+static struct SDBFlags uf2SDBFlags(krb5_context context, uint32_t userAccountControl, enum samba_kdc_ent_type ent_type)
+{
+ struct SDBFlags flags = int2SDBFlags(0);
+
+ /* 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_PASSWORD_NOTREQD) {
+ flags.invalid = 1;
+ }
+*/
+/*
+ UF_PASSWORD_CANT_CHANGE and UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED are irrelevent
+*/
+ 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.
+ *
+ * Explanaton 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) {
+ ZERO_STRUCTP(key.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 ((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)) {
+ dump_data(0, sc_val->data, sc_val->length);
+ 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 (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 int principal_comp_strcmp_int(krb5_context context,
+ krb5_const_principal principal,
+ unsigned int component,
+ const char *string,
+ bool do_strcasecmp)
+{
+ const char *p;
+
+#if defined(HAVE_KRB5_PRINCIPAL_GET_COMP_STRING)
+ p = krb5_principal_get_comp_string(context, principal, component);
+ if (p == NULL) {
+ return -1;
+ }
+ if (do_strcasecmp) {
+ return strcasecmp(p, string);
+ } else {
+ return strcmp(p, string);
+ }
+#else
+ size_t len;
+ krb5_data *d;
+ if (component >= krb5_princ_size(context, principal)) {
+ return -1;
+ }
+
+ d = krb5_princ_component(context, principal, component);
+ if (d == NULL) {
+ return -1;
+ }
+
+ p = d->data;
+
+ len = strlen(string);
+
+ /*
+ * We explicitly return -1 or 1. Subtracting of the two lengths might
+ * give the wrong result if the result overflows or loses data when
+ * narrowed to int.
+ */
+ if (d->length < len) {
+ return -1;
+ } else if (d->length > len) {
+ return 1;
+ }
+
+ if (do_strcasecmp) {
+ return strncasecmp(p, string, len);
+ } else {
+ return memcmp(p, string, len);
+ }
+#endif
+}
+
+static int principal_comp_strcasecmp(krb5_context context,
+ krb5_const_principal principal,
+ unsigned int component,
+ const char *string)
+{
+ return principal_comp_strcmp_int(context, principal,
+ component, string, true);
+}
+
+static int principal_comp_strcmp(krb5_context context,
+ krb5_const_principal principal,
+ unsigned int component,
+ const char *string)
+{
+ return principal_comp_strcmp_int(context, principal,
+ component, string, false);
+}
+
+static bool is_kadmin_changepw(krb5_context context,
+ krb5_const_principal principal)
+{
+ return krb5_princ_size(context, principal) == 2 &&
+ (principal_comp_strcmp(context, principal, 0, "kadmin") == 0) &&
+ (principal_comp_strcmp(context, principal, 1, "changepw") == 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 the
+ * both 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)
+{
+ 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;
+ 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 lagacy 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);
+
+ ZERO_STRUCTP(entry);
+
+ 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(mem_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 patheticly insecure password) */
+
+ if (entry->flags.server
+ && 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
+ = smb_krb5_principal_get_comp_string(mem_ctx,
+ context,
+ principal,
+ 2);
+ bool is_our_realm =
+ lpcfg_is_my_domain_or_realm(lp_ctx,
+ third_part);
+ bool 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 a 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 = (struct sdb_event *) malloc(sizeof(struct sdb_event));
+ if (entry->modified_by == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: 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 */
+ status = dom_sid_split_rid(NULL, samdb_result_dom_sid(mem_ctx, msg, "objectSid"), 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(
+ mem_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 expirty.
+ * Instead, only do it when request is for the kpasswd service */
+ if (ent_type == SAMBA_KDC_ENT_TYPE_SERVER &&
+ is_kadmin_changepw(context, principal) &&
+ 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;
+
+ if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT && (flags & SDB_F_FOR_AS_REQ)) {
+ int result;
+ 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.
+ */
+ status = samba_kdc_get_user_info_from_db(p, msg, &user_info_dc);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = EINVAL;
+ 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->max_life = MIN(*entry->max_life, 4 * 60 * 60);
+ *entry->max_renew = MIN(*entry->max_renew, 4 * 60 * 60);
+
+ entry->flags.forwardable = 0;
+ entry->flags.proxiable = 0;
+ }
+ }
+
+ 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;
+ }
+ } 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 = 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);
+ return SDB_ERR_NOT_FOUND_HERE;
+ } 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;
+
+out:
+ if (ret != 0) {
+ /* This doesn't free ent itself, that is for the eventual caller to do */
+ sdb_entry_free(entry);
+ } else {
+ talloc_steal(kdc_db_ctx, p);
+ }
+
+ 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)
+{
+ 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 preferr_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;
+
+ ZERO_STRUCTP(entry);
+
+ 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(mem_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(mem_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, mem_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(mem_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 substrucure.
+ */
+
+ /*
+ * Windows preferrs 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) {
+ preferr_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 (preferr_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(mem_ctx,
+ CH_UTF16MUNGED, CH_UTF8,
+ password_utf16.data,
+ password_utf16.length,
+ (void *)&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) {
+ DEBUG(1,(__location__ ": 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 = int2SDBFlags(0);
+ 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);
+
+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);
+ } else {
+ talloc_steal(kdc_db_ctx, p);
+ }
+
+ 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, "get_sam_result_trust: out of memory");
+ return ret;
+ } else {
+ int ret = EINVAL;
+ krb5_set_error_message(context, ret, "get_sam_result_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) {
+ principal_string = smb_krb5_principal_get_comp_string(mem_ctx, context,
+ principal, 0);
+ if (principal_string == NULL) {
+ return ENOMEM;
+ }
+ } 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;
+
+ fallback_account = smb_krb5_principal_get_comp_string(mem_ctx,
+ context, fallback_principal, 0);
+ if (fallback_account == NULL) {
+ krb5_free_principal(context, fallback_principal);
+ TALLOC_FREE(fallback_realm);
+ return ENOMEM;
+ }
+
+ 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)
+{
+ struct loadparm_context *lp_ctx = kdc_db_ctx->lp_ctx;
+ krb5_error_code ret;
+ 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 = smb_krb5_principal_get_comp_string(mem_ctx, context, principal, 1);
+
+ realm_from_princ = smb_krb5_principal_get_realm(
+ mem_ctx, context, principal);
+ if (realm_from_princ == NULL) {
+ /* can't happen */
+ return SDB_ERR_NOENTRY;
+ }
+
+ if (krb5_princ_size(context, principal) != 2
+ || (principal_comp_strcmp(context, principal, 0, KRB5_TGS_NAME) != 0)) {
+ /* Not a krbtgt */
+ return SDB_ERR_NOENTRY;
+ }
+
+ /* krbtgt case. Either us or a trusted realm */
+
+ if (lpcfg_is_my_domain_or_realm(lp_ctx, realm_from_princ)
+ && lpcfg_is_my_domain_or_realm(lp_ctx, realm_princ_comp)) {
+ /* us, or someone quite like us */
+ /* Cludge, cludge cludge. 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) {
+ krbtgt_number = SAMBA_KVNO_GET_KRBTGT(kvno);
+ if (kdc_db_ctx->rodc) {
+ if (krbtgt_number != kdc_db_ctx->my_krbtgt_number) {
+ return SDB_ERR_NOT_FOUND_HERE;
+ }
+ }
+ } 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, mem_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, mem_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: could not find KRBTGT number %u in DB!",
+ (unsigned)(krbtgt_number));
+ krb5_set_error_message(context, SDB_ERR_NOENTRY,
+ "samba_kdc_fetch: could not find KRBTGT number %u in DB!",
+ (unsigned)(krbtgt_number));
+ return SDB_ERR_NOENTRY;
+ } else if (lret != LDB_SUCCESS) {
+ krb5_warnx(context, "samba_kdc_fetch: could not find KRBTGT number %u in DB!",
+ (unsigned)(krbtgt_number));
+ krb5_set_error_message(context, SDB_ERR_NOENTRY,
+ "samba_kdc_fetch: could not find KRBTGT number %u in DB!",
+ (unsigned)(krbtgt_number));
+ return SDB_ERR_NOENTRY;
+ }
+
+ 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: self krbtgt message2entry failed");
+ }
+ return ret;
+
+ } 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 if (principal_comp_strcasecmp(context, principal, 1, lpcfg_realm(lp_ctx)) == 0) {
+ /* look for outbound trust */
+ direction = OUTBOUND;
+ realm = realm_from_princ;
+ } else {
+ krb5_warnx(context, "samba_kdc_fetch: not our realm for trusts ('%s', '%s')",
+ realm_from_princ,
+ realm_princ_comp);
+ krb5_set_error_message(context, SDB_ERR_NOENTRY, "samba_kdc_fetch: not our realm for trusts ('%s', '%s')",
+ realm_from_princ,
+ realm_princ_comp);
+ return SDB_ERR_NOENTRY;
+ }
+
+ /* Trusted domains are under CN=system */
+
+ ret = samba_kdc_lookup_trust(context, kdc_db_ctx->samdb,
+ mem_ctx,
+ realm, realm_dn, &msg);
+
+ if (ret != 0) {
+ krb5_warnx(context, "samba_kdc_fetch: could not find principal in DB");
+ krb5_set_error_message(context, ret, "samba_kdc_fetch: could not find principal in DB");
+ return ret;
+ }
+
+ 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: trust_message2entry failed for %s",
+ ldb_dn_get_linearized(msg->dn));
+ krb5_set_error_message(context, ret, "samba_kdc_fetch: "
+ "trust_message2entry failed for %s",
+ ldb_dn_get_linearized(msg->dn));
+ }
+ 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;
+ }
+ str = smb_krb5_principal_get_comp_string(mem_ctx, context, principal, 0);
+ if (str == NULL) {
+ 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_principal: could not parse principal");
+ krb5_warnx(context, "samba_kdc_lookup_principal: 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) {
+ DEBUG(10, ("Failed to find an entry for %s filter:%s\n",
+ name1, filter));
+ return SDB_ERR_NOENTRY;
+ }
+ if (lret == LDB_ERR_CONSTRAINT_VIOLATION) {
+ DEBUG(10, ("Failed to find unique entry for %s filter:%s\n",
+ name1, filter));
+ return SDB_ERR_NOENTRY;
+ }
+ if (lret != LDB_SUCCESS) {
+ DEBUG(0, ("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: message2entry failed for "
+ "%s",
+ client_name);
+ } else {
+ krb5_warnx(context,
+ "samba_kdc_fetch: 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,
+ TALLOC_CTX *mem_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;
+
+ 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;
+ }
+
+ principal_string = smb_krb5_principal_get_comp_string(frame, context,
+ principal, 0);
+ if (principal_string == NULL) {
+ TALLOC_FREE(frame);
+ return ENOMEM;
+ }
+
+ 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) {
+ char *service_realm = NULL;
+
+ ret = principal_comp_strcmp(context, principal, 0, KRB5_TGS_NAME);
+ if (ret == 0) {
+ /*
+ * 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) {
+ service_realm = smb_krb5_principal_get_comp_string(frame,
+ context,
+ principal,
+ num_comp - 1);
+ }
+
+ if (service_realm != NULL) {
+ 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;
+ }
+
+ ZERO_STRUCTP(entry);
+
+ 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, mem_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!");
+ return ret;
+ }
+
+ 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);
+
+out:
+ if (principal != NULL) {
+ krb5_free_principal(context, principal);
+ }
+
+ 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;
+ TALLOC_CTX *mem_ctx;
+ 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;
+
+ mem_ctx = talloc_named(priv, 0, "samba_kdc_firstkey context");
+
+ if (!mem_ctx) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "samba_kdc_firstkey: talloc_named() failed!");
+ return ret;
+ }
+
+ 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;
+ } else {
+ talloc_free(mem_ctx);
+ }
+ 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 a 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_fetch: 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!");
+ 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) {
+ 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() failed!");
+ return ret;
+ }
+ DEBUG(10,("samba_kdc_check_s4u2proxy: 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!");
+ 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;
+ }
+
+ DEBUG(10,("samba_kdc_check_s4u2proxy: client[%s] allowed target[%s]\n",
+ client_dn, tmp));
+ 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,
+ krb5_pac header_pac,
+ 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 auth_user_info_dc *user_info_dc = NULL;
+ struct auth_session_info *session_info = NULL;
+ uint32_t session_info_flags = AUTH_SESSION_INFO_SIMPLE_PRIVILEGES;
+ /*
+ * 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");
+ TALLOC_FREE(mem_ctx);
+ 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");
+ SAFE_FREE(client_name);
+ goto out;
+ }
+
+ DBG_INFO("Check delegation from client[%s] to server[%s] via "
+ "proxy[%s]\n",
+ client_name,
+ server_name,
+ proxy_dn);
+
+ code = kerberos_pac_to_user_info_dc(mem_ctx,
+ header_pac,
+ context,
+ &user_info_dc,
+ NULL,
+ NULL);
+ if (code != 0) {
+ goto out;
+ }
+
+ if (user_info_dc->info->authenticated) {
+ session_info_flags |= AUTH_SESSION_INFO_AUTHENTICATED;
+ }
+
+ nt_status = auth_generate_session_info(mem_ctx,
+ kdc_db_ctx->lp_ctx,
+ kdc_db_ctx->samdb,
+ user_info_dc,
+ session_info_flags,
+ &session_info);
+ 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_ERR("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, session_info->security_token);
+ NDR_PRINT_DEBUG(security_descriptor, rbcd_security_descriptor);
+ }
+
+ nt_status = sec_access_check_ds(rbcd_security_descriptor,
+ session_info->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;
+ struct auth_session_info *session_info;
+ struct samba_kdc_db_context *kdc_db_ctx;
+ /* The idea here is very simple. Using Kerberos to
+ * authenticate the KDC to the LDAP server is higly likely to
+ * be circular.
+ *
+ * In future we may set this up to use EXERNAL 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) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /* Setup the link to secrets.ldb */
+
+ kdc_db_ctx->secrets_db = secrets_db_connect(kdc_db_ctx,
+ base_ctx->lp_ctx);
+ if (kdc_db_ctx->secrets_db == NULL) {
+ DEBUG(1, ("samba_kdc_setup_db_ctx: "
+ "Cannot open secrets.ldb for KDC backend!"));
+ talloc_free(kdc_db_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ kdc_db_ctx->fx_cookie_dn = ldb_dn_new(kdc_db_ctx,
+ kdc_db_ctx->secrets_db,
+ "CN=FX Cookie");
+
+ /* 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) {
+ DEBUG(1, ("samba_kdc_setup_db_ctx: Cannot open samdb for KDC backend!"));
+ 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) {
+ DEBUG(1, ("samba_kdc_setup_db_ctx: 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;
+ struct ldb_dn *server_dn = samdb_server_dn(kdc_db_ctx->samdb, kdc_db_ctx);
+ if (!server_dn) {
+ DEBUG(1, ("samba_kdc_setup_db_ctx: 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) {
+ DEBUG(1, ("samba_kdc_setup_db_ctx: 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) {
+ DEBUG(1, ("samba_kdc_setup_db_ctx: 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) {
+ DEBUG(1, ("samba_kdc_setup_db_ctx: 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) {
+ DEBUG(1, ("samba_kdc_setup_db_ctx: 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) {
+ DEBUG(1, ("samba_kdc_fetch: 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) {
+ 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_get_error_message(context,
+ krb5_ret));
+ 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..3a59d6a
--- /dev/null
+++ b/source4/kdc/db-glue.h
@@ -0,0 +1,111 @@
+/*
+ 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,
+ krb5_pac header_pac,
+ 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..e465a28
--- /dev/null
+++ b/source4/kdc/hdb-samba4.c
@@ -0,0 +1,886 @@
+/*
+ * 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 "auth/auth_sam.h"
+#include "auth/common_auth.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>
+
+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 int hdb_samba4_fill_fast_cookie(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx)
+{
+ struct ldb_message *msg = ldb_msg_new(kdc_db_ctx);
+ int ldb_ret;
+
+ uint8_t secretbuffer[32];
+ struct ldb_val val = data_blob_const(secretbuffer,
+ sizeof(secretbuffer));
+
+ if (msg == NULL) {
+ DBG_ERR("Failed to allocate msg for new fast cookie\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Fill in all the keys with the same secret */
+ generate_secret_buffer(secretbuffer,
+ sizeof(secretbuffer));
+
+ msg->dn = kdc_db_ctx->fx_cookie_dn;
+
+ ldb_ret = ldb_msg_add_value(msg, "secret", &val, NULL);
+
+ if (ldb_ret != LDB_SUCCESS) {
+ return ldb_ret;
+ }
+
+ ldb_ret = ldb_add(kdc_db_ctx->secrets_db,
+ msg);
+ if (ldb_ret != LDB_SUCCESS) {
+ DBG_ERR("Failed to add fast cookie to ldb: %s\n",
+ ldb_errstring(kdc_db_ctx->secrets_db));
+ }
+ return ldb_ret;
+}
+
+static krb5_error_code hdb_samba4_fetch_fast_cookie(krb5_context context,
+ struct samba_kdc_db_context *kdc_db_ctx,
+ hdb_entry *entry)
+{
+ krb5_error_code ret = SDB_ERR_NOENTRY;
+ TALLOC_CTX *mem_ctx;
+ struct ldb_result *res;
+ int ldb_ret;
+ struct sdb_entry sentry = {};
+ const char *attrs[] = {
+ "secret",
+ NULL
+ };
+ const struct ldb_val *val;
+
+ 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;
+ }
+
+ /* search for CN=FX-COOKIE */
+ ldb_ret = ldb_search(kdc_db_ctx->secrets_db,
+ mem_ctx,
+ &res,
+ kdc_db_ctx->fx_cookie_dn,
+ LDB_SCOPE_BASE,
+ attrs, NULL);
+
+ if (ldb_ret == LDB_ERR_NO_SUCH_OBJECT || res->count == 0) {
+
+ ldb_ret = hdb_samba4_fill_fast_cookie(context,
+ kdc_db_ctx);
+
+ if (ldb_ret != LDB_SUCCESS) {
+ TALLOC_FREE(mem_ctx);
+ return HDB_ERR_NO_WRITE_SUPPORT;
+ }
+
+ /* search for CN=FX-COOKIE */
+ ldb_ret = ldb_search(kdc_db_ctx->secrets_db,
+ mem_ctx,
+ &res,
+ kdc_db_ctx->fx_cookie_dn,
+ LDB_SCOPE_BASE,
+ attrs, NULL);
+
+ if (ldb_ret != LDB_SUCCESS || res->count != 1) {
+ TALLOC_FREE(mem_ctx);
+ return HDB_ERR_NOENTRY;
+ }
+ }
+
+ val = ldb_msg_find_ldb_val(res->msgs[0],
+ "secret");
+ if (val == NULL || val->length != 32) {
+ TALLOC_FREE(mem_ctx);
+ return HDB_ERR_NOENTRY;
+ }
+
+
+ ret = krb5_make_principal(context,
+ &sentry.principal,
+ KRB5_WELLKNOWN_ORG_H5L_REALM,
+ KRB5_WELLKNOWN_NAME, "org.h5l.fast-cookie",
+ NULL);
+ if (ret) {
+ TALLOC_FREE(mem_ctx);
+ return ret;
+ }
+
+ ret = samba_kdc_set_fixed_keys(context, val, ENC_ALL_TYPES,
+ &sentry.keys);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = sdb_entry_to_hdb_entry(context, &sentry, entry);
+ sdb_entry_free(&sentry);
+ TALLOC_FREE(mem_ctx);
+
+ return ret;
+}
+
+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 && ret != 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 &= ~(SDB_F_GET_CLIENT|SDB_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;
+ 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_s4u2proxy(context, kdc_db_ctx,
+ skdc_entry,
+ target_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_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;
+
+ irpc_handle = irpc_binding_handle_by_name(mem_ctx, kdc_db_ctx->msg_ctx,
+ "winbind_server",
+ &ndr_table_winbind);
+
+ if (irpc_handle == NULL) {
+ DEBUG(0, ("No winbind_server running!\n"));
+ return;
+ }
+
+ req.in.message = *send_to_sam;
+
+ dcerpc_winbind_SendToSam_r_send(mem_ctx, kdc_db_ctx->ev_ctx,
+ irpc_handle, &req);
+}
+
+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);
+ uint64_t logon_id = generate_random_u64();
+ 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;
+ int final_ret = 0;
+
+ 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, "pa");
+ 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 = logon_id
+ };
+
+ 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(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;
+ 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)) {
+ final_ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ r->error_code = final_ret;
+ rwdc_fallback = kdc_db_ctx->rodc;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ final_ret = KRB5KRB_ERR_GENERIC;
+ r->error_code = final_ret;
+ rwdc_fallback = kdc_db_ctx->rodc;
+ } else if (kdc_db_ctx->rodc && send_to_sam != NULL) {
+ reset_bad_password_netlogon(frame, kdc_db_ctx, send_to_sam);
+ }
+
+ /* This is the final sucess */
+ } 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;
+ final_ret = KRB5KRB_ERR_GENERIC;
+ r->error_code = final_ret;
+ } 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;
+ final_ret = KRB5KRB_ERR_GENERIC;
+ r->error_code = final_ret;
+ } 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)) {
+ final_ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ r->error_code = final_ret;
+ } else {
+ status = NT_STATUS_WRONG_PASSWORD;
+ }
+ rwdc_fallback = kdc_db_ctx->rodc;
+ } else if (hdb_auth_status == KDC_AUTH_EVENT_CLIENT_LOCKED_OUT) {
+ 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;
+ final_ret = KRB5KRB_ERR_GENERIC;
+ r->error_code = final_ret;
+ }
+
+ 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;
+ }
+
+ log_authentication_event(kdc_db_ctx->msg_ctx,
+ kdc_db_ctx->lp_ctx,
+ &r->tv_start,
+ &ui,
+ status,
+ domain_name,
+ account_name,
+ sid);
+ 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);
+ 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;
+ 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, "malloc: 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_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..671e506
--- /dev/null
+++ b/source4/kdc/kdc-glue.c
@@ -0,0 +1,65 @@
+/*
+ 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"
+
+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);
+}
diff --git a/source4/kdc/kdc-glue.h b/source4/kdc/kdc-glue.h
new file mode 100644
index 0000000..7a0184c
--- /dev/null
+++ b/source4/kdc/kdc-glue.h
@@ -0,0 +1,57 @@
+/*
+ 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"
+
+struct tsocket_address;
+
+kdc_code kpasswdd_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);
+
+/* 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);
+
+/* 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);
+#endif
diff --git a/source4/kdc/kdc-heimdal.c b/source4/kdc/kdc-heimdal.c
new file mode 100644
index 0000000..e936d4b
--- /dev/null
+++ b/source4/kdc/kdc-heimdal.c
@@ -0,0 +1,518 @@
+/*
+ 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>
+
+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 %lu from %s\n",
+ (long)input->length - 4, 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);
+
+ /*
+ * 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..64160b3
--- /dev/null
+++ b/source4/kdc/kdc-proxy.c
@@ -0,0 +1,594 @@
+/*
+ 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"
+
+
+/*
+ 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 */
+ DEBUG(1,(__location__ ": 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)) {
+ DEBUG(0,("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)) {
+ DEBUG(0,("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 */
+ packet_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..de0ae8d
--- /dev/null
+++ b/source4/kdc/kdc-server.c
@@ -0,0 +1,618 @@
+/*
+ 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"
+
+/*
+ * 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;
+
+ DEBUG(10,("Received krb5 UDP packet of length %lu from %s\n",
+ (long)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) {
+ DEBUG(0,("kdc_udp_call_loop: proxying requested when not RODC"));
+ 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;
+ }
+
+ DEBUG(10,("Received krb5 TCP packet of length %lu from %s\n",
+ (long) 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),
+ * packet_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 */
+ packet_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_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),
+ * packet_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 */
+ packet_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_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;
+ }
+
+ 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),
+ * packet_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 */
+ packet_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)) {
+ DEBUG(0,("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);
+ DEBUG(0,("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..31cfef2
--- /dev/null
+++ b/source4/kdc/kdc-service-mit.c
@@ -0,0 +1,392 @@
+/*
+ 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"
+
+/* 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) {
+ DEBUG(0, ("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);
+
+ DEBUG(5,("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);
+ }
+
+ DEBUG(5,("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;
+ }
+
+ DEBUG(5,("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) {
+ DEBUG(0, ("The MIT KDC daemon died with exit status %d\n",
+ sys_errno));
+ } else {
+ DEBUG(0,("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..645f9c9
--- /dev/null
+++ b/source4/kdc/kpasswd-helper.c
@@ -0,0 +1,261 @@
+/*
+ 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"
+
+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..c92b13d
--- /dev/null
+++ b/source4/kdc/kpasswd-service-heimdal.c
@@ -0,0 +1,323 @@
+/*
+ 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"
+
+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,
+ (const char *)chpw.newpasswd.data,
+ chpw.newpasswd.length,
+ (void **)&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,
+ (const char *)decoded_data->data,
+ decoded_data->length,
+ (void **)&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..053b1f2
--- /dev/null
+++ b/source4/kdc/kpasswd-service-mit.c
@@ -0,0 +1,400 @@
+/*
+ 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"
+
+#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.length = decoded_data->length;
+ k_dec_data.data = (char *)decoded_data->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,
+ (void **)&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,
+ (const char *)decoded_data->data,
+ decoded_data->length,
+ (void **)&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..d2f1bb0
--- /dev/null
+++ b/source4/kdc/kpasswd-service.c
@@ -0,0 +1,391 @@
+/*
+ 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"
+
+#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");
+ 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");
+ 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");
+ 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.length = dec_data_blob.length;
+ k_dec_data.data = (char *)dec_data_blob.data;
+
+ 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);
+ 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..68f347c
--- /dev/null
+++ b/source4/kdc/kpasswd_glue.c
@@ -0,0 +1,85 @@
+/*
+ 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"
+
+/*
+ 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;
+ }
+
+ DEBUG(3, ("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..732d247
--- /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 : "UNKOWN");
+
+ 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..0ff1bfe
--- /dev/null
+++ b/source4/kdc/mit-kdb/kdb_samba.c
@@ -0,0 +1,182 @@
+/*
+ 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 = KRB5_KDB_DAL_MAJOR_VERSION == 6 ? 1 : 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,
+
+#if KRB5_KDB_DAL_MAJOR_VERSION >= 9
+ .allowed_to_delegate_from = kdb_samba_db_allowed_to_delegate_from,
+ .issue_pac = kdb_samba_db_issue_pac,
+#else
+ .sign_authdata = kdb_samba_db_sign_auth_data,
+#endif
+};
diff --git a/source4/kdc/mit-kdb/kdb_samba.h b/source4/kdc/mit-kdb/kdb_samba.h
new file mode 100644
index 0000000..138105e
--- /dev/null
+++ b/source4/kdc/mit-kdb/kdb_samba.h
@@ -0,0 +1,184 @@
+/*
+ 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_data ks_make_data(void *data, unsigned int len);
+
+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..1ad1c14
--- /dev/null
+++ b/source4/kdc/mit-kdb/kdb_samba_common.c
@@ -0,0 +1,114 @@
+/*
+ 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_data ks_make_data(void *data, unsigned int len)
+{
+ krb5_data d;
+
+ d.magic = KV5M_DATA;
+ d.data = data;
+ d.length = len;
+
+ return d;
+}
+
+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..0004854
--- /dev/null
+++ b/source4/kdc/mit-kdb/kdb_samba_policies.c
@@ -0,0 +1,769 @@
+/*
+ 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 <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 = ks_make_data(int_data.data, int_data.length);
+
+ 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;
+}
+
+#if KRB5_KDB_DAL_MAJOR_VERSION < 9
+static krb5_error_code ks_verify_pac(krb5_context context,
+ unsigned int flags,
+ krb5_const_principal client_princ,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ krb5_db_entry *krbtgt,
+ krb5_keyblock *server_key,
+ krb5_keyblock *krbtgt_key,
+ krb5_timestamp authtime,
+ krb5_authdata **tgt_auth_data,
+ krb5_pac *pac)
+{
+ struct mit_samba_context *mit_ctx;
+ krb5_authdata **authdata = NULL;
+ krb5_pac ipac = NULL;
+ DATA_BLOB logon_data = { NULL, 0 };
+ krb5_error_code code;
+
+ mit_ctx = ks_get_context(context);
+ if (mit_ctx == NULL) {
+ return KRB5_KDB_DBNOTINITED;
+ }
+
+ /* find the existing PAC, if present */
+ code = krb5_find_authdata(context,
+ tgt_auth_data,
+ NULL,
+ KRB5_AUTHDATA_WIN2K_PAC,
+ &authdata);
+ if (code != 0) {
+ return code;
+ }
+
+ /* no pac data */
+ if (authdata == NULL) {
+ return 0;
+ }
+
+ SMB_ASSERT(authdata[0] != NULL);
+
+ if (authdata[1] != NULL) {
+ code = KRB5KDC_ERR_BADOPTION; /* XXX */
+ goto done;
+ }
+
+ code = krb5_pac_parse(context,
+ authdata[0]->contents,
+ authdata[0]->length,
+ &ipac);
+ if (code != 0) {
+ goto done;
+ }
+
+ /* TODO: verify this is correct
+ *
+ * In the constrained delegation case, the PAC is from a service
+ * ticket rather than a TGT; we must verify the server and KDC
+ * signatures to assert that the server did not forge the PAC.
+ */
+ if (flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) {
+ code = krb5_pac_verify(context,
+ ipac,
+ authtime,
+ client_princ,
+ server_key,
+ krbtgt_key);
+ } else {
+ code = krb5_pac_verify(context,
+ ipac,
+ authtime,
+ client_princ,
+ krbtgt_key,
+ NULL);
+ }
+ if (code != 0) {
+ goto done;
+ }
+
+ /* check and update PAC */
+ code = krb5_pac_parse(context,
+ authdata[0]->contents,
+ authdata[0]->length,
+ pac);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = mit_samba_reget_pac(mit_ctx,
+ context,
+ flags,
+ client_princ,
+ client,
+ server,
+ krbtgt,
+ krbtgt_key,
+ pac);
+
+done:
+ krb5_free_authdata(context, authdata);
+ krb5_pac_free(context, ipac);
+ free(logon_data.data);
+
+ return code;
+}
+
+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_const_principal ks_client_princ = NULL;
+ krb5_db_entry *client_entry = NULL;
+ krb5_authdata **pac_auth_data = NULL;
+ krb5_authdata **authdata = NULL;
+ krb5_boolean is_as_req;
+ krb5_error_code code;
+ krb5_pac pac = NULL;
+ krb5_data pac_data;
+ bool with_pac = false;
+ bool generate_pac = false;
+ char *client_name = NULL;
+
+
+ krbtgt = krbtgt == NULL ? local_krbtgt : krbtgt;
+ krbtgt_key = krbtgt_key == NULL ? local_krbtgt_key : krbtgt_key;
+
+ /* FIXME: We don't support S4U yet */
+ if (flags & KRB5_KDB_FLAGS_S4U) {
+ return KRB5_KDB_DBTYPE_NOSUP;
+ }
+
+ is_as_req = ((flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) != 0);
+
+ /*
+ * When using s4u2proxy client_princ actually refers to the proxied user
+ * while client->princ to the proxy service asking for the TGS on behalf
+ * of the proxied user. So always use client_princ in preference.
+ *
+ * Note that when client principal is not NULL, client entry might be
+ * NULL for cross-realm case, so we need to make sure to not
+ * dereference NULL pointer here.
+ */
+ if (client_princ != NULL) {
+ ks_client_princ = client_princ;
+ if (!is_as_req) {
+ krb5_boolean is_equal = false;
+
+ if (client != NULL && client->princ != NULL) {
+ is_equal =
+ krb5_principal_compare(context,
+ client_princ,
+ client->princ);
+ }
+
+ /*
+ * When client principal is the same as supplied client
+ * entry, don't fetch it.
+ */
+ if (!is_equal) {
+ code = ks_get_principal(context,
+ ks_client_princ,
+ 0,
+ &client_entry);
+ if (code != 0) {
+ (void)krb5_unparse_name(context,
+ ks_client_princ,
+ &client_name);
+
+ DBG_DEBUG("We didn't find the client "
+ "principal [%s] in our "
+ "database.\n",
+ client_name);
+ SAFE_FREE(client_name);
+
+ /*
+ * If we didn't find client_princ in our
+ * database it might be from another
+ * realm.
+ */
+ client_entry = NULL;
+ }
+ }
+ }
+ } else {
+ if (client == NULL) {
+ *signed_auth_data = NULL;
+ return 0;
+ }
+ ks_client_princ = client->princ;
+ }
+
+ if (client_entry == NULL) {
+ client_entry = client;
+ }
+
+ if (is_as_req) {
+ with_pac = mit_samba_princ_needs_pac(client_entry);
+ } else {
+ with_pac = mit_samba_princ_needs_pac(server);
+ }
+
+ code = krb5_unparse_name(context,
+ client_princ,
+ &client_name);
+ if (code != 0) {
+ goto done;
+ }
+
+ if (is_as_req && (flags & KRB5_KDB_FLAG_INCLUDE_PAC) != 0) {
+ generate_pac = true;
+ }
+
+ DBG_DEBUG("*** Sign data for client principal: %s [%s %s%s]\n",
+ client_name,
+ is_as_req ? "AS-REQ" : "TGS_REQ",
+ with_pac ? is_as_req ? "WITH_PAC" : "FIND_PAC" : "NO_PAC",
+ generate_pac ? " GENERATE_PAC" : "");
+
+ /*
+ * Generate PAC for the AS-REQ or check or generate one for the TGS if
+ * needed.
+ */
+ if (with_pac && generate_pac) {
+ DBG_DEBUG("Generate PAC for AS-REQ [%s]\n", client_name);
+
+ code = krb5_pac_init(context, &pac);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = ks_get_pac(context,
+ flags,
+ client_entry,
+ server,
+ NULL,
+ &pac);
+ if (code != 0) {
+ goto done;
+ }
+ } else if (with_pac && !is_as_req) {
+ /*
+ * Find the PAC in the TGS, if one exists.
+ */
+ code = krb5_find_authdata(context,
+ tgt_auth_data,
+ NULL,
+ KRB5_AUTHDATA_WIN2K_PAC,
+ &pac_auth_data);
+ if (code != 0) {
+ DBG_ERR("krb5_find_authdata failed: %d\n", code);
+ goto done;
+ }
+ DBG_DEBUG("Found PAC data for TGS-REQ [%s]\n", client_name);
+
+ if (pac_auth_data != NULL && pac_auth_data[0] != NULL) {
+ if (pac_auth_data[1] != NULL) {
+ DBG_ERR("Invalid PAC data!\n");
+ code = KRB5KDC_ERR_BADOPTION;
+ goto done;
+ }
+
+ DBG_DEBUG("Verify PAC for TGS [%s]\n",
+ client_name);
+
+ code = ks_verify_pac(context,
+ flags,
+ ks_client_princ,
+ client_entry,
+ server,
+ krbtgt,
+ server_key,
+ krbtgt_key,
+ authtime,
+ tgt_auth_data,
+ &pac);
+ if (code != 0) {
+ goto done;
+ }
+ } else {
+ if (flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) {
+ DBG_DEBUG("Generate PAC for constrained"
+ "delegation TGS [%s]\n",
+ client_name);
+
+ code = krb5_pac_init(context, &pac);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = ks_get_pac(context,
+ flags,
+ client_entry,
+ server,
+ NULL,
+ &pac);
+ if (code != 0 && code != ENOENT) {
+ goto done;
+ }
+ }
+ }
+ }
+
+ if (pac == NULL) {
+ DBG_DEBUG("No PAC data - we're done [%s]\n", client_name);
+ *signed_auth_data = NULL;
+ code = 0;
+ goto done;
+ }
+
+ DBG_DEBUG("Signing PAC for %s [%s]\n",
+ is_as_req ? "AS-REQ" : "TGS-REQ",
+ client_name);
+ code = krb5_pac_sign(context, pac, authtime, ks_client_princ,
+ server_key, krbtgt_key, &pac_data);
+ if (code != 0) {
+ DBG_ERR("krb5_pac_sign failed: %d\n", code);
+ goto done;
+ }
+
+ authdata = calloc(2, sizeof(krb5_authdata *));
+ if (authdata == NULL) {
+ goto done;
+ }
+
+ authdata[0] = malloc(sizeof(krb5_authdata));
+ if (authdata[0] == NULL) {
+ goto done;
+ }
+
+ /* put in signed data */
+ authdata[0]->magic = KV5M_AUTHDATA;
+ authdata[0]->ad_type = KRB5_AUTHDATA_WIN2K_PAC;
+ authdata[0]->contents = (krb5_octet *)pac_data.data;
+ authdata[0]->length = pac_data.length;
+
+ code = krb5_encode_authdata_container(context,
+ KRB5_AUTHDATA_IF_RELEVANT,
+ authdata,
+ signed_auth_data);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = 0;
+
+done:
+ if (client_entry != NULL && client_entry != client) {
+ ks_free_principal(context, client_entry);
+ }
+ SAFE_FREE(client_name);
+ krb5_free_authdata(context, authdata);
+ krb5_pac_free(context, pac);
+
+ return code;
+}
+#else /* KRB5_KDB_DAL_MAJOR_VERSION >= 9 */
+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;
+}
+#endif /* KRB5_KDB_DAL_MAJOR_VERSION */
+
+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);
+
+}
+
+
+#if KRB5_KDB_DAL_MAJOR_VERSION >= 9
+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;
+}
+#endif
+
+
+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..9113cf8
--- /dev/null
+++ b/source4/kdc/mit_kdc_irpc.c
@@ -0,0 +1,200 @@
+/*
+ * 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"
+
+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) {
+ DEBUG(0, ("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) {
+ DEBUG(0, ("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;
+
+ 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..bfe9748
--- /dev/null
+++ b/source4/kdc/mit_samba.c
@@ -0,0 +1,1066 @@
+/*
+ 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 <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 heimdal's krb5_context */
+ if (ctx->context) {
+ krb5_free_context(ctx->context);
+ }
+
+ /* then free everything else */
+ talloc_free(ctx);
+}
+
+/*
+ * Implemant 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("", is_error, "%s", msg);
+}
+
+int mit_samba_context_init(struct mit_samba_context **_ctx)
+{
+ NTSTATUS status;
+ struct mit_samba_context *ctx;
+ const char *s4_conf_file;
+ int 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 heimdal'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;
+}
+
+static krb5_error_code ks_is_tgs_principal(struct mit_samba_context *ctx,
+ krb5_const_principal principal)
+{
+ char *p;
+ int eq = -1;
+
+ p = smb_krb5_principal_get_comp_string(ctx, ctx->context, principal, 0);
+
+ eq = krb5_princ_size(ctx->context, principal) == 2 &&
+ (strcmp(p, KRB5_TGS_NAME) == 0);
+
+ talloc_free(p);
+
+ return eq;
+}
+
+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;
+
+ if (pwd == NULL) {
+ return EINVAL;
+ }
+ pwd->length = 24;
+
+ tmp_ctx = talloc_named(NULL,
+ 0,
+ "mit_samba_create_principal_password context");
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ password = generate_random_password(tmp_ctx, pwd->length, pwd->length);
+ if (password == NULL) {
+ talloc_free(tmp_ctx);
+ return ENOMEM;
+ }
+
+ pwd->data = strdup(password);
+ talloc_free(tmp_ctx);
+ if (pwd->data == NULL) {
+ return ENOMEM;
+ }
+
+ return 0;
+}
+
+int 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;
+ int ret;
+ uint32_t sflags = 0;
+ krb5_principal referral_principal = NULL;
+
+ kentry = calloc(1, sizeof(krb5_db_entry));
+ if (kentry == NULL) {
+ return ENOMEM;
+ }
+
+#if KRB5_KDB_API_VERSION >= 10
+ /*
+ * 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;
+#endif
+
+#if KRB5_KDB_DAL_MAJOR_VERSION >= 9
+ 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;
+ }
+ }
+#else /* KRB5_KDB_DAL_MAJOR_VERSION < 9 */
+ if (kflags & KRB5_KDB_FLAG_CANONICALIZE) {
+ sflags |= SDB_F_CANON;
+ }
+ if (kflags & (KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY |
+ KRB5_KDB_FLAG_INCLUDE_PAC)) {
+ /*
+ * KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY is equal to
+ * SDB_F_FOR_AS_REQ
+ *
+ * We use ANY to also allow AS_REQ for service principal names
+ * This is supported by Windows.
+ */
+ sflags |= SDB_F_GET_ANY|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|SDB_F_FOR_TGS_REQ;
+ }
+ }
+#endif /* KRB5_KDB_DAL_MAJOR_VERSION */
+
+ /* 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 and entry in the database and get an
+ * entry with the pricipal:
+ *
+ * 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
+ * fo 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;
+}
+
+int mit_samba_get_firstkey(struct mit_samba_context *ctx,
+ krb5_db_entry **_kentry)
+{
+ struct sdb_entry sentry = {};
+ krb5_db_entry *kentry;
+ int 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;
+}
+
+int mit_samba_get_nextkey(struct mit_samba_context *ctx,
+ krb5_db_entry **_kentry)
+{
+ struct sdb_entry sentry = {};
+ krb5_db_entry *kentry;
+ int 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;
+}
+
+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)
+{
+ TALLOC_CTX *tmp_ctx;
+ 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;
+ NTSTATUS nt_status;
+ krb5_error_code code;
+ struct samba_kdc_entry *skdc_entry;
+ bool is_krbtgt;
+ enum samba_asserted_identity asserted_identity =
+ (flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION) ?
+ SAMBA_ASSERTED_IDENTITY_SERVICE :
+ SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY;
+
+ skdc_entry = talloc_get_type_abort(client->e_data,
+ struct samba_kdc_entry);
+
+ tmp_ctx = talloc_named(smb_ctx,
+ 0,
+ "mit_samba_get_pac_data_blobs context");
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ /* Check if we have a PREAUTH key */
+ if (replaced_reply_key != NULL) {
+ cred_ndr_ptr = &cred_ndr;
+ }
+
+ is_krbtgt = ks_is_tgs_principal(smb_ctx, server->princ);
+
+ nt_status = samba_kdc_get_pac_blobs(tmp_ctx,
+ skdc_entry,
+ asserted_identity,
+ &logon_info_blob,
+ cred_ndr_ptr,
+ &upn_dns_info_blob,
+ is_krbtgt ? &pac_attrs_blob : NULL,
+ PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY,
+ is_krbtgt ? &requester_sid_blob : NULL);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ if (NT_STATUS_EQUAL(nt_status,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ return ENOENT;
+ }
+ 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,
+ *pac);
+
+ talloc_free(tmp_ctx);
+ return code;
+}
+
+#if KRB5_KDB_DAL_MAJOR_VERSION < 9
+krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx,
+ krb5_context context,
+ int kdc_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)
+{
+ TALLOC_CTX *tmp_ctx;
+ krb5_error_code code;
+ struct samba_kdc_entry *client_skdc_entry = NULL;
+ struct samba_kdc_entry *krbtgt_skdc_entry = NULL;
+ struct samba_kdc_entry *server_skdc_entry = NULL;
+ krb5_principal delegated_proxy_principal = NULL;
+ krb5_pac new_pac = NULL;
+ bool is_in_db = false;
+ bool is_untrusted = false;
+ uint32_t flags = SAMBA_KDC_FLAG_SKIP_PAC_BUFFER;
+
+ /* Create a memory context early so code can use talloc_stackframe() */
+ tmp_ctx = talloc_named(ctx, 0, "mit_samba_reget_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 (server == NULL) {
+ code = EINVAL;
+ goto done;
+ }
+
+ server_skdc_entry =
+ talloc_get_type_abort(server->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);
+
+ code = samba_krbtgt_is_in_db(krbtgt_skdc_entry,
+ &is_in_db,
+ &is_untrusted);
+ if (code != 0) {
+ goto done;
+ }
+
+ if (is_untrusted) {
+ flags |= SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED;
+ }
+
+ if (is_in_db) {
+ flags |= SAMBA_KDC_FLAG_KRBTGT_IN_DB;
+
+ }
+
+ 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;
+ delegated_proxy_principal = discard_const(client_principal);
+ }
+
+ /* Build an updated PAC */
+ code = krb5_pac_init(context, &new_pac);
+ if (code != 0) {
+ goto done;
+ }
+
+ code = samba_kdc_update_pac(tmp_ctx,
+ context,
+ krbtgt_skdc_entry->kdc_db_ctx->samdb,
+ flags,
+ client_skdc_entry,
+ server->princ,
+ server_skdc_entry,
+ krbtgt_skdc_entry,
+ delegated_proxy_principal,
+ *pac,
+ new_pac);
+ if (code != 0) {
+ krb5_pac_free(context, new_pac);
+ if (code == ENODATA) {
+ krb5_pac_free(context, *pac);
+ *pac = NULL;
+ code = 0;
+ }
+ goto done;
+ }
+
+ /* We now replace the pac */
+ krb5_pac_free(context, *pac);
+ *pac = new_pac;
+
+done:
+ talloc_free(tmp_ctx);
+ return code;
+}
+#else
+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;
+ bool is_in_db = false;
+ bool is_untrusted = false;
+ uint32_t flags = SAMBA_KDC_FLAG_SKIP_PAC_BUFFER;
+
+ /* 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);
+
+ 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_untrusted);
+ if (code != 0) {
+ goto done;
+ }
+
+ if (is_untrusted) {
+ flags |= SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED;
+ }
+
+ if (is_in_db) {
+ flags |= SAMBA_KDC_FLAG_KRBTGT_IN_DB;
+
+ }
+
+ if (kdc_flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) {
+ flags |= SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION;
+ }
+
+ code = samba_kdc_update_pac(tmp_ctx,
+ context,
+ krbtgt_skdc_entry->kdc_db_ctx->samdb,
+ flags,
+ client_skdc_entry,
+ server->princ,
+ server_skdc_entry,
+ krbtgt_skdc_entry,
+ NULL,
+ old_pac,
+ new_pac);
+ if (code != 0) {
+ if (code == ENODATA) {
+ /*
+ * 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;
+}
+#endif
+
+/* 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;
+ 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;
+}
+
+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)
+{
+ 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;
+}
+
+int mit_samba_check_s4u2proxy(struct mit_samba_context *ctx,
+ const krb5_db_entry *server,
+ krb5_const_principal target_principal)
+{
+#if KRB5_KDB_DAL_MAJOR_VERSION < 9
+ return KRB5KDC_ERR_BADOPTION;
+#else
+ 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;
+#endif
+}
+
+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)
+{
+#if KRB5_KDB_DAL_MAJOR_VERSION < 8
+ return KRB5KDC_ERR_POLICY;
+#else
+ struct samba_kdc_entry *proxy_skdc_entry =
+ talloc_get_type_abort(proxy->e_data, struct samba_kdc_entry);
+ krb5_error_code code;
+
+ code = samba_kdc_check_s4u2proxy_rbcd(ctx->context,
+ ctx->db_ctx,
+ client_principal,
+ server_principal,
+ header_pac,
+ proxy_skdc_entry);
+
+ return code;
+#endif
+}
+
+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;
+}
+
+int 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;
+ struct auth_user_info_dc *user_info_dc;
+ 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
+ DEBUG(1,("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;
+ }
+
+ status = samba_kdc_get_user_info_from_db(p,
+ p->msg,
+ &user_info_dc);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("samba_kdc_get_user_info_from_db failed: %s\n",
+ nt_errstr(status)));
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ 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)) {
+ DEBUG(1,("auth_generate_session_info failed: %s\n",
+ nt_errstr(status)));
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ /* password is expected as UTF16 */
+
+ if (!convert_string_talloc(tmp_ctx, CH_UTF8, CH_UTF16,
+ pwd, strlen(pwd),
+ &password.data, &password.length)) {
+ DEBUG(1,("convert_string_talloc failed\n"));
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ 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)) {
+ DEBUG(1,("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-glue.c b/source4/kdc/pac-glue.c
new file mode 100644
index 0000000..6692619
--- /dev/null
+++ b/source4/kdc/pac-glue.c
@@ -0,0 +1,2006 @@
+/*
+ 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/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 "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/samba_kdc.h"
+#include "source4/kdc/pac-glue.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,
+ DATA_BLOB *pac_data,
+ DATA_BLOB *requester_sid_blob)
+{
+ struct netr_SamInfo3 *info3;
+ union PAC_INFO pac_info;
+ enum ndr_err_code ndr_err;
+ NTSTATUS nt_status;
+
+ ZERO_STRUCT(pac_info);
+
+ *pac_data = data_blob_null;
+ if (requester_sid_blob != NULL) {
+ *requester_sid_blob = data_blob_null;
+ }
+
+ nt_status = auth_convert_user_info_dc_saminfo3(mem_ctx, info, &info3);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(1, ("Getting Samba info failed: %s\n",
+ nt_errstr(nt_status)));
+ return nt_status;
+ }
+
+ pac_info.logon_info.info = talloc_zero(mem_ctx, struct PAC_LOGON_INFO);
+ if (!pac_info.logon_info.info) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ pac_info.logon_info.info->info3 = *info3;
+
+ 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);
+ DEBUG(1, ("PAC_LOGON_INFO (presig) push failed: %s\n",
+ nt_errstr(nt_status)));
+ return nt_status;
+ }
+
+ if (requester_sid_blob != NULL && info->num_sids > 0) {
+ union PAC_INFO pac_requester_sid;
+
+ ZERO_STRUCT(pac_requester_sid);
+
+ pac_requester_sid.requester_sid.sid = info->sids[0];
+
+ ndr_err = ndr_push_union_blob(requester_sid_blob, mem_ctx,
+ &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);
+ DEBUG(1, ("PAC_REQUESTER_SID (presig) push failed: %s\n",
+ nt_errstr(nt_status)));
+ return nt_status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static
+NTSTATUS samba_get_upn_info_pac_blob(TALLOC_CTX *mem_ctx,
+ const struct auth_user_info_dc *info,
+ DATA_BLOB *upn_data)
+{
+ union PAC_INFO pac_upn;
+ enum ndr_err_code ndr_err;
+ NTSTATUS nt_status;
+ bool ok;
+
+ ZERO_STRUCT(pac_upn);
+
+ *upn_data = data_blob_null;
+
+ pac_upn.upn_dns_info.upn_name = info->info->user_principal_name;
+ pac_upn.upn_dns_info.dns_domain_name = strupper_talloc(mem_ctx,
+ info->info->dns_domain_name);
+ if (pac_upn.upn_dns_info.dns_domain_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ 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[0];
+
+ 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);
+ DEBUG(1, ("PAC UPN_DNS_INFO (presig) push failed: %s\n",
+ nt_errstr(nt_status)));
+ return nt_status;
+ }
+
+ ok = data_blob_pad(mem_ctx, upn_data, 8);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static
+NTSTATUS samba_get_pac_attrs_blob(TALLOC_CTX *mem_ctx,
+ uint64_t pac_attributes,
+ DATA_BLOB *pac_attrs_data)
+{
+ union PAC_INFO pac_attrs;
+ enum ndr_err_code ndr_err;
+ NTSTATUS nt_status;
+
+ ZERO_STRUCT(pac_attrs);
+
+ *pac_attrs_data = data_blob_null;
+
+ /* 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_data, mem_ctx, &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);
+ DEBUG(1, ("PAC ATTRIBUTES_INFO (presig) push failed: %s\n",
+ nt_errstr(nt_status)));
+ return nt_status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+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;
+
+ ZERO_STRUCT(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) {
+ DEBUG(5, ("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) {
+ DEBUG(5, ("Passing LM 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);
+ DEBUG(1, ("PAC_CREDENTIAL_NTLM_SECPKG (presig) push failed: %s\n",
+ nt_errstr(nt_status)));
+ return nt_status;
+ }
+
+ DEBUG(10, ("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);
+ DEBUG(1, ("PAC_CREDENTIAL_DATA_NDR (presig) push failed: %s\n",
+ nt_errstr(nt_status)));
+ return nt_status;
+ }
+
+ DEBUG(10, ("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;
+}
+
+#ifdef SAMBA4_USES_HEIMDAL
+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_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);
+ DEBUG(1, ("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) {
+ DEBUG(1, ("Failed getting crypto type for key\n"));
+ krb5_crypto_destroy(context, cred_crypto);
+ return ret;
+ }
+
+ DEBUG(10, ("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);
+ DEBUG(1, ("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);
+ DEBUG(1, ("PAC_CREDENTIAL_INFO (presig) push failed: %s\n",
+ nt_errstr(nt_status)));
+ return KRB5KDC_ERR_SVC_UNAVAILABLE;
+ }
+
+ DEBUG(10, ("Encrypted credential BLOB (len %zu) with alg %d\n",
+ cred_info_blob->length, (int)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 */
+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_key cred_key;
+ krb5_enctype cred_enctype;
+ struct PAC_CREDENTIAL_INFO pac_cred_info = { .version = 0, };
+ krb5_error_code code;
+ 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;
+
+ code = krb5_k_create_key(context,
+ pkreplykey,
+ &cred_key);
+ if (code != 0) {
+ krb5err = krb5_get_error_message(context, code);
+ DEBUG(1, ("Failed initializing cred data crypto: %s\n", krb5err));
+ krb5_free_error_message(context, krb5err);
+ return code;
+ }
+
+ cred_enctype = krb5_k_key_enctype(context, cred_key);
+
+ DEBUG(10, ("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.magic = 0;
+ cred_ndr_data.data = (char *)cred_ndr_blob->data;
+ cred_ndr_data.length = cred_ndr_blob->length;
+
+ code = krb5_c_encrypt_length(context,
+ cred_enctype,
+ cred_ndr_data.length,
+ &enc_len);
+ if (code != 0) {
+ krb5err = krb5_get_error_message(context, code);
+ DEBUG(1, ("Failed initializing cred data crypto: %s\n", krb5err));
+ krb5_free_error_message(context, krb5err);
+ return code;
+ }
+
+ pac_cred_info.encrypted_data = data_blob_talloc_zero(mem_ctx, enc_len);
+ if (pac_cred_info.encrypted_data.data == NULL) {
+ DBG_ERR("Out of memory\n");
+ return ENOMEM;
+ }
+
+ cred_ndr_crypt.ciphertext.length = enc_len;
+ cred_ndr_crypt.ciphertext.data = (char *)pac_cred_info.encrypted_data.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);
+ DEBUG(1, ("Failed crypt of cred data: %s\n", krb5err));
+ krb5_free_error_message(context, krb5err);
+ return code;
+ }
+
+ 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);
+ DEBUG(1, ("PAC_CREDENTIAL_INFO (presig) push failed: %s\n",
+ nt_errstr(nt_status)));
+ return KRB5KDC_ERR_SVC_UNAVAILABLE;
+ }
+
+ DEBUG(10, ("Encrypted credential BLOB (len %zu) with alg %d\n",
+ cred_info_blob->length, (int)pac_cred_info.encryption_type));
+ dump_data_pw("PAC_CREDENTIAL_INFO",
+ cred_info_blob->data, cred_info_blob->length);
+
+ return 0;
+}
+#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] 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,
+ krb5_pac pac)
+{
+ krb5_data logon_data;
+ krb5_data cred_data;
+ krb5_data upn_data;
+ krb5_data pac_attrs_data;
+ krb5_data requester_sid_data;
+ krb5_data deleg_data;
+ krb5_error_code ret;
+#ifdef SAMBA4_USES_HEIMDAL
+ char null_byte = '\0';
+ krb5_data null_data = {
+ .length = 1,
+ .data = &null_byte,
+ };
+#endif
+
+ /* The user account may be set not to want the PAC */
+ if (logon_blob == NULL) {
+ return 0;
+ }
+
+ ret = smb_krb5_copy_data_contents(&logon_data,
+ logon_blob->data,
+ logon_blob->length);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ZERO_STRUCT(cred_data);
+ if (cred_blob != NULL) {
+ ret = smb_krb5_copy_data_contents(&cred_data,
+ cred_blob->data,
+ cred_blob->length);
+ if (ret != 0) {
+ smb_krb5_free_data_contents(context, &logon_data);
+ return ret;
+ }
+ }
+
+ ZERO_STRUCT(upn_data);
+ if (upn_blob != NULL) {
+ ret = smb_krb5_copy_data_contents(&upn_data,
+ upn_blob->data,
+ upn_blob->length);
+ if (ret != 0) {
+ smb_krb5_free_data_contents(context, &logon_data);
+ smb_krb5_free_data_contents(context, &cred_data);
+ return ret;
+ }
+ }
+
+ ZERO_STRUCT(pac_attrs_data);
+ if (pac_attrs_blob != NULL) {
+ ret = smb_krb5_copy_data_contents(&pac_attrs_data,
+ pac_attrs_blob->data,
+ pac_attrs_blob->length);
+ if (ret != 0) {
+ smb_krb5_free_data_contents(context, &logon_data);
+ smb_krb5_free_data_contents(context, &cred_data);
+ smb_krb5_free_data_contents(context, &upn_data);
+ return ret;
+ }
+ }
+
+ ZERO_STRUCT(requester_sid_data);
+ if (requester_sid_blob != NULL) {
+ ret = smb_krb5_copy_data_contents(&requester_sid_data,
+ requester_sid_blob->data,
+ requester_sid_blob->length);
+ if (ret != 0) {
+ smb_krb5_free_data_contents(context, &logon_data);
+ smb_krb5_free_data_contents(context, &cred_data);
+ smb_krb5_free_data_contents(context, &upn_data);
+ smb_krb5_free_data_contents(context, &pac_attrs_data);
+ return ret;
+ }
+ }
+
+ ZERO_STRUCT(deleg_data);
+ if (deleg_blob != NULL) {
+ ret = smb_krb5_copy_data_contents(&deleg_data,
+ deleg_blob->data,
+ deleg_blob->length);
+ if (ret != 0) {
+ smb_krb5_free_data_contents(context, &logon_data);
+ smb_krb5_free_data_contents(context, &cred_data);
+ smb_krb5_free_data_contents(context, &upn_data);
+ smb_krb5_free_data_contents(context, &pac_attrs_data);
+ smb_krb5_free_data_contents(context, &requester_sid_data);
+ return ret;
+ }
+ }
+
+ ret = krb5_pac_add_buffer(context, pac, PAC_TYPE_LOGON_INFO, &logon_data);
+ smb_krb5_free_data_contents(context, &logon_data);
+ if (ret != 0) {
+ smb_krb5_free_data_contents(context, &cred_data);
+ smb_krb5_free_data_contents(context, &upn_data);
+ smb_krb5_free_data_contents(context, &pac_attrs_data);
+ smb_krb5_free_data_contents(context, &requester_sid_data);
+ smb_krb5_free_data_contents(context, &deleg_data);
+ return ret;
+ }
+
+ if (cred_blob != NULL) {
+ ret = krb5_pac_add_buffer(context, pac,
+ PAC_TYPE_CREDENTIAL_INFO,
+ &cred_data);
+ smb_krb5_free_data_contents(context, &cred_data);
+ if (ret != 0) {
+ smb_krb5_free_data_contents(context, &upn_data);
+ smb_krb5_free_data_contents(context, &pac_attrs_data);
+ smb_krb5_free_data_contents(context, &requester_sid_data);
+ smb_krb5_free_data_contents(context, &deleg_data);
+ 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) {
+ smb_krb5_free_data_contents(context, &upn_data);
+ smb_krb5_free_data_contents(context, &pac_attrs_data);
+ smb_krb5_free_data_contents(context, &requester_sid_data);
+ smb_krb5_free_data_contents(context, &deleg_data);
+ return ret;
+ }
+#endif
+
+ if (upn_blob != NULL) {
+ ret = krb5_pac_add_buffer(context, pac,
+ PAC_TYPE_UPN_DNS_INFO,
+ &upn_data);
+ smb_krb5_free_data_contents(context, &upn_data);
+ if (ret != 0) {
+ smb_krb5_free_data_contents(context, &pac_attrs_data);
+ smb_krb5_free_data_contents(context, &requester_sid_data);
+ smb_krb5_free_data_contents(context, &deleg_data);
+ return ret;
+ }
+ }
+
+ if (pac_attrs_blob != NULL) {
+ ret = krb5_pac_add_buffer(context, pac,
+ PAC_TYPE_ATTRIBUTES_INFO,
+ &pac_attrs_data);
+ smb_krb5_free_data_contents(context, &pac_attrs_data);
+ if (ret != 0) {
+ smb_krb5_free_data_contents(context, &requester_sid_data);
+ smb_krb5_free_data_contents(context, &deleg_data);
+ return ret;
+ }
+ }
+
+ if (requester_sid_blob != NULL) {
+ ret = krb5_pac_add_buffer(context, pac,
+ PAC_TYPE_REQUESTER_SID,
+ &requester_sid_data);
+ smb_krb5_free_data_contents(context, &requester_sid_data);
+ if (ret != 0) {
+ smb_krb5_free_data_contents(context, &deleg_data);
+ return ret;
+ }
+ }
+
+ if (deleg_blob != NULL) {
+ ret = krb5_pac_add_buffer(context, pac,
+ PAC_TYPE_CONSTRAINED_DELEGATION,
+ &deleg_data);
+ smb_krb5_free_data_contents(context, &deleg_data);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+bool samba_princ_needs_pac(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;
+}
+
+int samba_client_requested_pac(krb5_context context,
+ const krb5_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;
+ int 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);
+ DEBUG(0,("can't parse the PAC ATTRIBUTES_INFO: %s\n", nt_errstr(nt_status)));
+ return EINVAL;
+ }
+
+ 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 */
+int samba_krbtgt_is_in_db(struct samba_kdc_entry *p,
+ bool *is_in_db,
+ bool *is_untrusted)
+{
+ NTSTATUS status;
+ int rodc_krbtgt_number, trust_direction;
+ uint32_t rid;
+
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ if (!mem_ctx) {
+ return ENOMEM;
+ }
+
+ 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-foreest trusts
+ */
+ talloc_free(mem_ctx);
+ *is_untrusted = false;
+ *is_in_db = false;
+ return 0;
+ }
+
+ /* The lack of password controls etc applies to krbtgt by
+ * virtue of being that particular RID */
+ status = dom_sid_split_rid(NULL, samdb_result_dom_sid(mem_ctx, p->msg, "objectSid"), NULL, &rid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(mem_ctx);
+ return EINVAL;
+ }
+
+ 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_untrusted = false;
+ *is_in_db = true;
+ talloc_free(mem_ctx);
+ return 0;
+ } else if (rodc_krbtgt_number != -1) {
+ *is_in_db = true;
+ *is_untrusted = true;
+ talloc_free(mem_ctx);
+ return 0;
+ }
+ } else if ((rid != DOMAIN_RID_KRBTGT) && (rodc_krbtgt_number == p->kdc_db_ctx->my_krbtgt_number)) {
+ talloc_free(mem_ctx);
+ *is_untrusted = false;
+ *is_in_db = true;
+ return 0;
+ } else if (rid == DOMAIN_RID_KRBTGT) {
+ /* krbtgt viewed from an RODC */
+ talloc_free(mem_ctx);
+ *is_untrusted = false;
+ *is_in_db = false;
+ return 0;
+ }
+
+ /* Another RODC */
+ talloc_free(mem_ctx);
+ *is_untrusted = true;
+ *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
+ */
+static NTSTATUS samba_add_asserted_identity(TALLOC_CTX *mem_ctx,
+ enum samba_asserted_identity ai,
+ struct auth_user_info_dc *user_info_dc)
+{
+ struct dom_sid ai_sid;
+ const char *sid_str = NULL;
+
+ switch (ai) {
+ case SAMBA_ASSERTED_IDENTITY_SERVICE:
+ sid_str = SID_SERVICE_ASSERTED_IDENTITY;
+ break;
+ case SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY:
+ sid_str = SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY;
+ break;
+ case SAMBA_ASSERTED_IDENTITY_IGNORE:
+ return NT_STATUS_OK;
+ }
+
+ dom_sid_parse(sid_str, &ai_sid);
+
+ return add_sid_to_array_unique(user_info_dc,
+ &ai_sid,
+ &user_info_dc->sids,
+ &user_info_dc->num_sids);
+}
+
+/*
+ * Look up the user's info in the database and create a auth_user_info_dc
+ * structure. If the resulting structure is not talloc_free()d, it will be
+ * reused on future calls to this function.
+ */
+NTSTATUS samba_kdc_get_user_info_from_db(struct samba_kdc_entry *skdc_entry,
+ struct ldb_message *msg,
+ struct auth_user_info_dc **user_info_dc)
+{
+ if (skdc_entry->user_info_dc == NULL) {
+ NTSTATUS nt_status;
+ struct loadparm_context *lp_ctx = skdc_entry->kdc_db_ctx->lp_ctx;
+
+ nt_status = authsam_make_user_info_dc(skdc_entry,
+ skdc_entry->kdc_db_ctx->samdb,
+ lpcfg_netbios_name(lp_ctx),
+ lpcfg_sam_name(lp_ctx),
+ lpcfg_sam_dnsname(lp_ctx),
+ skdc_entry->realm_dn,
+ msg,
+ data_blob_null,
+ data_blob_null,
+ &skdc_entry->user_info_dc);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ }
+
+ *user_info_dc = skdc_entry->user_info_dc;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx,
+ struct samba_kdc_entry *p,
+ enum samba_asserted_identity asserted_identity,
+ DATA_BLOB **_logon_info_blob,
+ DATA_BLOB **_cred_ndr_blob,
+ DATA_BLOB **_upn_info_blob,
+ DATA_BLOB **_pac_attrs_blob,
+ uint64_t pac_attributes,
+ DATA_BLOB **_requester_sid_blob)
+{
+ struct auth_user_info_dc *user_info_dc = NULL;
+ DATA_BLOB *logon_blob = NULL;
+ DATA_BLOB *cred_blob = NULL;
+ DATA_BLOB *upn_blob = NULL;
+ DATA_BLOB *pac_attrs_blob = NULL;
+ DATA_BLOB *requester_sid_blob = NULL;
+ NTSTATUS nt_status;
+
+ *_logon_info_blob = NULL;
+ if (_cred_ndr_blob != NULL) {
+ *_cred_ndr_blob = NULL;
+ }
+ *_upn_info_blob = NULL;
+ if (_pac_attrs_blob != NULL) {
+ *_pac_attrs_blob = NULL;
+ }
+ if (_requester_sid_blob != NULL) {
+ *_requester_sid_blob = NULL;
+ }
+
+ logon_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (logon_blob == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (_cred_ndr_blob != NULL) {
+ cred_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (cred_blob == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ upn_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (upn_blob == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (_pac_attrs_blob != NULL) {
+ pac_attrs_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (pac_attrs_blob == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (_requester_sid_blob != NULL) {
+ requester_sid_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (requester_sid_blob == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ nt_status = samba_kdc_get_user_info_from_db(p,
+ p->msg,
+ &user_info_dc);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Getting user info for PAC failed: %s\n",
+ nt_errstr(nt_status)));
+ return nt_status;
+ }
+
+ nt_status = samba_add_asserted_identity(mem_ctx,
+ asserted_identity,
+ user_info_dc);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("Failed to add assertied identity!\n");
+ return nt_status;
+ }
+
+ nt_status = samba_get_logon_info_pac_blob(logon_blob,
+ user_info_dc,
+ logon_blob,
+ requester_sid_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Building PAC LOGON INFO failed: %s\n",
+ nt_errstr(nt_status)));
+ return nt_status;
+ }
+
+ if (cred_blob != NULL) {
+ nt_status = samba_get_cred_info_ndr_blob(cred_blob,
+ p->msg,
+ cred_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Building PAC CRED INFO failed: %s\n",
+ nt_errstr(nt_status)));
+ return nt_status;
+ }
+ }
+
+ nt_status = samba_get_upn_info_pac_blob(upn_blob,
+ user_info_dc,
+ upn_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Building PAC UPN INFO failed: %s\n",
+ nt_errstr(nt_status)));
+ return nt_status;
+ }
+
+ if (pac_attrs_blob != NULL) {
+ nt_status = samba_get_pac_attrs_blob(pac_attrs_blob,
+ pac_attributes,
+ pac_attrs_blob);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Building PAC ATTRIBUTES failed: %s\n",
+ nt_errstr(nt_status)));
+ return nt_status;
+ }
+ }
+
+ *_logon_info_blob = logon_blob;
+ if (_cred_ndr_blob != NULL) {
+ *_cred_ndr_blob = cred_blob;
+ }
+ *_upn_info_blob = upn_blob;
+ if (_pac_attrs_blob != NULL) {
+ *_pac_attrs_blob = pac_attrs_blob;
+ }
+ if (_requester_sid_blob != NULL) {
+ *_requester_sid_blob = requester_sid_blob;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS samba_kdc_update_pac_blob(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ const krb5_pac pac, DATA_BLOB *pac_blob,
+ struct PAC_SIGNATURE_DATA *pac_srv_sig,
+ struct PAC_SIGNATURE_DATA *pac_kdc_sig)
+{
+ struct auth_user_info_dc *user_info_dc;
+ krb5_error_code ret;
+ NTSTATUS nt_status;
+
+ ret = kerberos_pac_to_user_info_dc(mem_ctx, pac,
+ context, &user_info_dc, pac_srv_sig, pac_kdc_sig);
+ if (ret) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /*
+ * 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,
+ user_info_dc);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ nt_status = samba_get_logon_info_pac_blob(mem_ctx,
+ user_info_dc, pac_blob, NULL);
+
+ return nt_status;
+}
+
+NTSTATUS samba_kdc_update_delegation_info_blob(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ const krb5_pac pac,
+ const krb5_principal server_principal,
+ const krb5_principal proxy_principal,
+ DATA_BLOB *new_blob)
+{
+ krb5_data old_data;
+ DATA_BLOB old_blob;
+ krb5_error_code ret;
+ NTSTATUS nt_status;
+ 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) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_CONSTRAINED_DELEGATION, &old_data);
+ if (ret == ENOENT) {
+ ZERO_STRUCT(old_data);
+ } else if (ret) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ old_blob.length = old_data.length;
+ old_blob.data = (uint8_t *)old_data.data;
+
+ ZERO_STRUCT(info);
+ if (old_blob.length > 0) {
+ ndr_err = ndr_pull_union_blob(&old_blob, mem_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);
+ DEBUG(0,("can't parse the PAC LOGON_INFO: %s\n", nt_errstr(nt_status)));
+ talloc_free(tmp_ctx);
+ return nt_status;
+ }
+ } else {
+ ZERO_STRUCT(_d);
+ 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) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ret = krb5_unparse_name(context, proxy_principal, &proxy);
+ if (ret) {
+ SAFE_FREE(server);
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ 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);
+ 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);
+ DEBUG(0,("can't parse the PAC LOGON_INFO: %s\n", nt_errstr(nt_status)));
+ talloc_free(tmp_ctx);
+ return nt_status;
+ }
+
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+/* 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_pac pac,
+ krb5_context context,
+ struct dom_sid *sid)
+{
+ NTSTATUS nt_status;
+ enum ndr_err_code ndr_err;
+ krb5_error_code ret;
+
+ 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) {
+ return ENOMEM;
+ }
+
+ ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_REQUESTER_SID,
+ &k5pac_requester_sid_in);
+ if (ret != 0) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ 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);
+ DEBUG(0,("can't parse the PAC REQUESTER_SID: %s\n", nt_errstr(nt_status)));
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ *sid = info.requester_sid.sid;
+
+ talloc_free(tmp_ctx);
+ return 0;
+}
+
+/* Does a parse and SID check, but no crypto. */
+krb5_error_code samba_kdc_validate_pac_blob(
+ krb5_context context,
+ struct samba_kdc_entry *client_skdc_entry,
+ const krb5_pac pac)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct auth_user_info_dc *pac_user_info = NULL;
+ struct dom_sid *client_sid = NULL;
+ 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, 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,
+ pac,
+ context,
+ &pac_user_info,
+ NULL,
+ NULL);
+ if (code != 0) {
+ goto out;
+ }
+
+ if (pac_user_info->num_sids == 0) {
+ code = EINVAL;
+ goto out;
+ }
+
+ pac_sid = pac_user_info->sids[0];
+ } else if (code != 0) {
+ goto out;
+ }
+
+ client_sid = samdb_result_dom_sid(frame,
+ client_skdc_entry->msg,
+ "objectSid");
+
+ 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
+ */
+WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_object_sids,
+ struct dom_sid *object_sids,
+ struct samba_kdc_entry *rodc,
+ 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) {
+ 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;
+}
+
+/**
+ * @brief Update 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 entry.
+
+ * @param server_principal The server principal
+
+ * @param server The server samba kdc entry.
+
+ * @param krbtgt The krbtgt samba kdc entry.
+ *
+ * @param delegated_proxy_principal The delegated proxy principal used for
+ * updating the constrained delegation PAC
+ * buffer.
+
+ * @param old_pac The old PAC
+
+ * @param new_pac The new already allocated PAC
+
+ * @return A Kerberos error code. If no PAC should be returned, the code will be
+ * ENODATA!
+ */
+krb5_error_code samba_kdc_update_pac(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ uint32_t flags,
+ struct samba_kdc_entry *client,
+ const krb5_principal server_principal,
+ struct samba_kdc_entry *server,
+ struct samba_kdc_entry *krbtgt,
+ const krb5_principal delegated_proxy_principal,
+ const krb5_pac old_pac,
+ krb5_pac new_pac)
+{
+ 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;
+ bool is_untrusted = flags & SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED;
+ int is_tgs = false;
+ size_t num_types = 0;
+ uint32_t *types = NULL;
+ /*
+ * FIXME: Do we really still need forced_next_type? With MIT Kerberos
+ * the PAC buffers do not get ordered and it works just fine. We are
+ * not aware of any issues in this regard. This might be just ancient
+ * code.
+ */
+ uint32_t forced_next_type = 0;
+ size_t i = 0;
+ ssize_t logon_info_idx = -1;
+ ssize_t delegation_idx = -1;
+ ssize_t logon_name_idx = -1;
+ ssize_t upn_dns_info_idx = -1;
+ ssize_t srv_checksum_idx = -1;
+ ssize_t kdc_checksum_idx = -1;
+ ssize_t tkt_checksum_idx = -1;
+ ssize_t attrs_info_idx = -1;
+ ssize_t requester_sid_idx = -1;
+ ssize_t full_checksum_idx = -1;
+
+ if (client != 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,
+ old_pac);
+ if (code != 0) {
+ goto done;
+ }
+ }
+
+ if (delegated_proxy_principal != NULL) {
+ deleg_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (deleg_blob == NULL) {
+ code = ENOMEM;
+ goto done;
+ }
+
+ nt_status = samba_kdc_update_delegation_info_blob(
+ mem_ctx,
+ context,
+ old_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 = EINVAL;
+ goto done;
+ }
+ }
+
+ if (is_untrusted) {
+ struct auth_user_info_dc *user_info_dc = NULL;
+ WERROR werr;
+ /*
+ * 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.
+ */
+ enum samba_asserted_identity asserted_identity =
+ SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY;
+
+ if (client == NULL) {
+ code = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ goto done;
+ }
+
+ nt_status = samba_kdc_get_pac_blobs(mem_ctx,
+ client,
+ asserted_identity,
+ &pac_blob,
+ NULL,
+ &upn_blob,
+ NULL,
+ PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY,
+ &requester_sid_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("samba_kdc_get_pac_blobs failed: %s\n",
+ nt_errstr(nt_status));
+ code = KRB5KDC_ERR_TGT_REVOKED;
+ goto done;
+ }
+
+ nt_status = samba_kdc_get_user_info_from_db(client,
+ client->msg,
+ &user_info_dc);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("samba_kdc_get_user_info_from_db failed: %s\n",
+ nt_errstr(nt_status));
+ 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.
+ */
+ werr = samba_rodc_confirm_user_is_allowed(user_info_dc->num_sids,
+ user_info_dc->sids,
+ krbtgt,
+ client);
+ TALLOC_FREE(user_info_dc);
+ 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;
+ }
+ } else {
+ pac_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (pac_blob == NULL) {
+ code = ENOMEM;
+ goto done;
+ }
+
+ nt_status = samba_kdc_update_pac_blob(mem_ctx,
+ context,
+ samdb,
+ old_pac,
+ pac_blob,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("samba_kdc_update_pac_blob failed: %s\n",
+ nt_errstr(nt_status));
+ code = EINVAL;
+ goto done;
+ }
+ }
+
+ /* Check the types of the given PAC */
+ code = krb5_pac_get_types(context, old_pac, &num_types, &types);
+ if (code != 0) {
+ DBG_ERR("krb5_pac_get_types failed\n");
+ goto done;
+ }
+
+ for (i = 0; i < num_types; i++) {
+ switch (types[i]) {
+ case PAC_TYPE_LOGON_INFO:
+ if (logon_info_idx != -1) {
+ DBG_WARNING("logon info type[%u] twice [%zd] "
+ "and [%zu]: \n",
+ types[i],
+ logon_info_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ logon_info_idx = i;
+ break;
+ case PAC_TYPE_CONSTRAINED_DELEGATION:
+ if (delegation_idx != -1) {
+ DBG_WARNING("constrained delegation type[%u] "
+ "twice [%zd] and [%zu]: \n",
+ types[i],
+ delegation_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ delegation_idx = i;
+ break;
+ case PAC_TYPE_LOGON_NAME:
+ if (logon_name_idx != -1) {
+ DBG_WARNING("logon name type[%u] twice [%zd] "
+ "and [%zu]: \n",
+ types[i],
+ logon_name_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ logon_name_idx = i;
+ break;
+ case PAC_TYPE_UPN_DNS_INFO:
+ if (upn_dns_info_idx != -1) {
+ DBG_WARNING("upn dns info type[%u] twice [%zd] "
+ "and [%zu]: \n",
+ types[i],
+ upn_dns_info_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ upn_dns_info_idx = i;
+ break;
+ case PAC_TYPE_SRV_CHECKSUM:
+ if (srv_checksum_idx != -1) {
+ DBG_WARNING("srv checksum type[%u] twice [%zd] "
+ "and [%zu]: \n",
+ types[i],
+ srv_checksum_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ srv_checksum_idx = i;
+ break;
+ case PAC_TYPE_KDC_CHECKSUM:
+ if (kdc_checksum_idx != -1) {
+ DBG_WARNING("kdc checksum type[%u] twice [%zd] "
+ "and [%zu]: \n",
+ types[i],
+ kdc_checksum_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ kdc_checksum_idx = i;
+ break;
+ case PAC_TYPE_TICKET_CHECKSUM:
+ if (tkt_checksum_idx != -1) {
+ DBG_WARNING("ticket checksum type[%u] twice "
+ "[%zd] and [%zu]: \n",
+ types[i],
+ tkt_checksum_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ tkt_checksum_idx = i;
+ break;
+ case PAC_TYPE_ATTRIBUTES_INFO:
+ if (attrs_info_idx != -1) {
+ DBG_WARNING("attributes info type[%u] twice "
+ "[%zd] and [%zu]: \n",
+ types[i],
+ attrs_info_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ attrs_info_idx = i;
+ break;
+ case PAC_TYPE_REQUESTER_SID:
+ if (requester_sid_idx != -1) {
+ DBG_WARNING("requester sid type[%u] twice"
+ "[%zd] and [%zu]: \n",
+ types[i],
+ requester_sid_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ requester_sid_idx = i;
+ break;
+ case PAC_TYPE_FULL_CHECKSUM:
+ if (full_checksum_idx != -1) {
+ DBG_WARNING("full checksum type[%u] twice "
+ "[%zd] and [%zu]: \n",
+ types[i],
+ full_checksum_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ full_checksum_idx = i;
+ break;
+ default:
+ continue;
+ }
+ }
+
+ if (logon_info_idx == -1) {
+ DBG_WARNING("PAC_TYPE_LOGON_INFO missing\n");
+ code = EINVAL;
+ goto done;
+ }
+ if (logon_name_idx == -1) {
+ DBG_WARNING("PAC_TYPE_LOGON_NAME missing\n");
+ code = EINVAL;
+ goto done;
+ }
+ if (srv_checksum_idx == -1) {
+ DBG_WARNING("PAC_TYPE_SRV_CHECKSUM missing\n");
+ code = EINVAL;
+ goto done;
+ }
+ if (kdc_checksum_idx == -1) {
+ DBG_WARNING("PAC_TYPE_KDC_CHECKSUM missing\n");
+ code = EINVAL;
+ goto done;
+ }
+ if (!(flags & SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION) &&
+ requester_sid_idx == -1) {
+ DBG_WARNING("PAC_TYPE_REQUESTER_SID missing\n");
+ code = KRB5KDC_ERR_TGT_REVOKED;
+ goto done;
+ }
+
+ /*
+ * The server account may be set not to want the PAC.
+ *
+ * While this is wasteful if the above cacluations 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 = ENODATA;
+ goto done;
+ }
+
+ is_tgs = smb_krb5_principal_is_tgs(context, server_principal);
+ if (is_tgs == -1) {
+ code = ENOMEM;
+ goto done;
+ }
+
+ if (!is_untrusted && !is_tgs) {
+ /*
+ * The client may have requested no PAC when obtaining the
+ * TGT.
+ */
+ bool requested_pac = false;
+
+ code = samba_client_requested_pac(context,
+ &old_pac,
+ mem_ctx,
+ &requested_pac);
+ if (code != 0 || !requested_pac) {
+ if (!requested_pac) {
+ code = ENODATA;
+ }
+ goto done;
+ }
+ }
+
+#define MAX_PAC_BUFFERS 128 /* Avoid infinite loops */
+
+ for (i = 0; i < MAX_PAC_BUFFERS;) {
+ const uint8_t zero_byte = 0;
+ krb5_data type_data;
+ DATA_BLOB type_blob = data_blob_null;
+ uint32_t type;
+
+ if (forced_next_type != 0) {
+ /*
+ * We need to inject possible missing types
+ */
+ type = forced_next_type;
+ forced_next_type = 0;
+ } else if (i < num_types) {
+ type = types[i];
+ i++;
+ } else {
+ break;
+ }
+
+ switch (type) {
+ case PAC_TYPE_LOGON_INFO:
+ type_blob = *pac_blob;
+
+ if (delegation_idx == -1 && deleg_blob != NULL) {
+ /* inject CONSTRAINED_DELEGATION behind */
+ forced_next_type =
+ PAC_TYPE_CONSTRAINED_DELEGATION;
+ }
+ break;
+ case PAC_TYPE_CONSTRAINED_DELEGATION:
+ /*
+ * This is generated in the main KDC code
+ */
+ if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+ continue;
+ }
+
+ if (deleg_blob != NULL) {
+ type_blob = *deleg_blob;
+ }
+ break;
+ case PAC_TYPE_CREDENTIAL_INFO:
+ /*
+ * Note that we copy the credential blob,
+ * as it's only usable with the PKINIT based
+ * AS-REP reply key, it's only available on the
+ * host which did the AS-REQ/AS-REP exchange.
+ *
+ * This matches Windows 2008R2...
+ */
+ break;
+ case PAC_TYPE_LOGON_NAME:
+ /*
+ * This is generated in the main KDC code
+ */
+ if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+ continue;
+ }
+
+ type_blob = data_blob_const(&zero_byte, 1);
+
+ if (upn_dns_info_idx == -1 && upn_blob != NULL) {
+ /* inject UPN_DNS_INFO behind */
+ forced_next_type = PAC_TYPE_UPN_DNS_INFO;
+ }
+ break;
+ case PAC_TYPE_UPN_DNS_INFO:
+ /*
+ * Replace in the RODC case, otherwise
+ * upn_blob is NULL and we just copy.
+ */
+ if (upn_blob != NULL) {
+ type_blob = *upn_blob;
+ }
+ break;
+ case PAC_TYPE_SRV_CHECKSUM:
+ /*
+ * This is generated in the main KDC code
+ */
+ if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+ continue;
+ }
+
+ type_blob = data_blob_const(&zero_byte, 1);
+
+ if (requester_sid_idx == -1 && requester_sid_blob != NULL) {
+ /* inject REQUESTER_SID behind */
+ forced_next_type = PAC_TYPE_REQUESTER_SID;
+ }
+ break;
+ case PAC_TYPE_KDC_CHECKSUM:
+ /*
+ * This is generated in the main KDC code
+ */
+ if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+ continue;
+ }
+
+ type_blob = data_blob_const(&zero_byte, 1);
+
+ break;
+ case PAC_TYPE_TICKET_CHECKSUM:
+ /*
+ * This is generated in the main KDC code
+ */
+ if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+ continue;
+ }
+
+ type_blob = data_blob_const(&zero_byte, 1);
+
+ break;
+ case PAC_TYPE_ATTRIBUTES_INFO:
+ if (!is_untrusted && is_tgs) {
+ /* just copy... */
+ break;
+ }
+
+ continue;
+ case PAC_TYPE_REQUESTER_SID:
+ if (!is_tgs) {
+ continue;
+ }
+
+ /*
+ * Replace in the RODC case, otherwise
+ * requester_sid_blob is NULL and we just copy.
+ */
+ if (requester_sid_blob != NULL) {
+ type_blob = *requester_sid_blob;
+ }
+ break;
+ case PAC_TYPE_FULL_CHECKSUM:
+ /*
+ * This is generated in the main KDC code
+ */
+ if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+ continue;
+ }
+
+ type_blob = data_blob_const(&zero_byte, 1);
+
+ break;
+ default:
+ /* just copy... */
+ break;
+ }
+
+ if (type_blob.length != 0) {
+ code = smb_krb5_copy_data_contents(&type_data,
+ type_blob.data,
+ type_blob.length);
+ if (code != 0) {
+ goto done;
+ }
+ } else {
+ code = krb5_pac_get_buffer(context,
+ old_pac,
+ type,
+ &type_data);
+ if (code != 0) {
+ goto done;
+ }
+ }
+
+ code = krb5_pac_add_buffer(context,
+ new_pac,
+ type,
+ &type_data);
+ smb_krb5_free_data_contents(context, &type_data);
+ if (code != 0) {
+ goto done;
+ }
+ }
+
+ code = 0;
+done:
+ TALLOC_FREE(pac_blob);
+ TALLOC_FREE(upn_blob);
+ TALLOC_FREE(deleg_blob);
+ SAFE_FREE(types);
+ return code;
+}
diff --git a/source4/kdc/pac-glue.h b/source4/kdc/pac-glue.h
new file mode 100644
index 0000000..7b7c489
--- /dev/null
+++ b/source4/kdc/pac-glue.h
@@ -0,0 +1,122 @@
+/*
+ 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/>.
+*/
+
+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,
+ SAMBA_KDC_FLAG_KRBTGT_IN_DB = 0x00000004,
+ SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED = 0x00000008,
+ SAMBA_KDC_FLAG_SKIP_PAC_BUFFER = 0x00000010,
+};
+
+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,
+ krb5_pac pac);
+
+bool samba_princ_needs_pac(struct samba_kdc_entry *skdc_entry);
+
+int samba_client_requested_pac(krb5_context context,
+ const krb5_pac *pac,
+ TALLOC_CTX *mem_ctx,
+ bool *requested_pac);
+
+int samba_krbtgt_is_in_db(struct samba_kdc_entry *skdc_entry,
+ bool *is_in_db,
+ bool *is_untrusted);
+
+NTSTATUS samba_kdc_get_user_info_from_db(struct samba_kdc_entry *skdc_entry,
+ struct ldb_message *msg,
+ struct auth_user_info_dc **user_info_dc);
+
+NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx,
+ struct samba_kdc_entry *skdc_entry,
+ enum samba_asserted_identity asserted_identity,
+ DATA_BLOB **_logon_info_blob,
+ DATA_BLOB **_cred_ndr_blob,
+ DATA_BLOB **_upn_info_blob,
+ DATA_BLOB **_pac_attrs_blob,
+ uint64_t pac_attributes,
+ DATA_BLOB **_requester_sid_blob);
+NTSTATUS samba_kdc_update_pac_blob(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ const krb5_pac pac, DATA_BLOB *pac_blob,
+ struct PAC_SIGNATURE_DATA *pac_srv_sig,
+ struct PAC_SIGNATURE_DATA *pac_kdc_sig);
+
+NTSTATUS samba_kdc_update_delegation_info_blob(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ const krb5_pac pac,
+ const krb5_principal server_principal,
+ const krb5_principal proxy_principal,
+ DATA_BLOB *pac_blob);
+
+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_validate_pac_blob(
+ krb5_context context,
+ struct samba_kdc_entry *client_skdc_entry,
+ const krb5_pac pac);
+
+/*
+ * In the RODC case, to confirm that the returned user is permitted to
+ * be replicated to the KDC (krbgtgt_xxx user) represented by *rodc
+ */
+WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_sids,
+ struct dom_sid *sids,
+ struct samba_kdc_entry *rodc,
+ struct samba_kdc_entry *object);
+
+krb5_error_code samba_kdc_update_pac(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ uint32_t flags,
+ struct samba_kdc_entry *client,
+ const krb5_principal server_principal,
+ struct samba_kdc_entry *server,
+ struct samba_kdc_entry *krbtgt,
+ const krb5_principal delegated_proxy_principal,
+ const krb5_pac old_pac,
+ krb5_pac new_pac);
diff --git a/source4/kdc/samba_kdc.h b/source4/kdc/samba_kdc.h
new file mode 100644
index 0000000..5d73c5b
--- /dev/null
+++ b/source4/kdc/samba_kdc.h
@@ -0,0 +1,73 @@
+/*
+ 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_
+
+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 ldb_dn *fx_cookie_dn;
+ struct ldb_context *secrets_db;
+};
+
+struct samba_kdc_entry {
+ struct samba_kdc_db_context *kdc_db_ctx;
+ const struct sdb_entry *db_entry; /* this is only temporary 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 auth_user_info_dc *user_info_dc;
+ bool is_krbtgt;
+ bool is_rodc;
+ bool is_trust;
+ uint32_t supported_enctypes;
+ NTSTATUS reject_status;
+};
+
+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..5fb1146
--- /dev/null
+++ b/source4/kdc/sdb.c
@@ -0,0 +1,212 @@
+/*
+ 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"
+
+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);
+ sdb_keys_free(&s->old_keys);
+ sdb_keys_free(&s->older_keys);
+ 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);
+
+ ZERO_STRUCTP(s);
+}
+
+struct SDBFlags int2SDBFlags(unsigned n)
+{
+ struct SDBFlags flags;
+
+ memset(&flags, 0, sizeof(flags));
+
+ flags.initial = (n >> 0) & 1;
+ flags.forwardable = (n >> 1) & 1;
+ flags.proxiable = (n >> 2) & 1;
+ flags.renewable = (n >> 3) & 1;
+ flags.postdate = (n >> 4) & 1;
+ flags.server = (n >> 5) & 1;
+ flags.client = (n >> 6) & 1;
+ flags.invalid = (n >> 7) & 1;
+ flags.require_preauth = (n >> 8) & 1;
+ flags.change_pw = (n >> 9) & 1;
+ flags.require_hwauth = (n >> 10) & 1;
+ flags.ok_as_delegate = (n >> 11) & 1;
+ flags.user_to_user = (n >> 12) & 1;
+ flags.immutable = (n >> 13) & 1;
+ flags.trusted_for_delegation = (n >> 14) & 1;
+ flags.allow_kerberos4 = (n >> 15) & 1;
+ flags.allow_digest = (n >> 16) & 1;
+ flags.locked_out = (n >> 17) & 1;
+ flags.do_not_store = (n >> 31) & 1;
+ return flags;
+}
+
+/* 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) {
+ 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..ec1ce59
--- /dev/null
+++ b/source4/kdc/sdb.h
@@ -0,0 +1,146 @@
+/*
+ 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 _unused24: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;
+ unsigned int *max_life;
+ unsigned 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 canonicalition */
+#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_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)
+
+/* This is not supported by HDB */
+#define SDB_F_FORCE_CANON 16384 /* force canonicalition */
+
+void sdb_key_free(struct sdb_key *key);
+void sdb_keys_free(struct sdb_keys *keys);
+void sdb_entry_free(struct sdb_entry *e);
+struct SDBFlags int2SDBFlags(unsigned n);
+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..06e20ae
--- /dev/null
+++ b/source4/kdc/sdb_to_hdb.c
@@ -0,0 +1,323 @@
+/*
+ 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"
+
+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->_unused24 = s->_unused24;
+ 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->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;
+ }
+ h->opaque = NULL;
+
+ return 0;
+}
+
+static int sdb_key_to_Key(const struct sdb_key *s, Key *h)
+{
+ int rc;
+
+ ZERO_STRUCTP(h);
+
+ 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;
+ }
+ } else {
+ h->salt = NULL;
+ }
+
+ 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->len = s->len;
+ if (s->val != NULL) {
+ h->val = malloc(h->len * sizeof(Key));
+ if (h->val == NULL) {
+ return ENOMEM;
+ }
+ for (i = 0; i < h->len; i++) {
+ ret = sdb_key_to_Key(&s->val[i],
+ &h->val[i]);
+ if (ret != 0) {
+ free_Keys(h);
+ return ENOMEM;
+ }
+ }
+ } else {
+ h->val = NULL;
+ }
+
+ return 0;
+}
+
+static int sdb_event_to_Event(krb5_context context,
+ const struct sdb_event *s, Event *h)
+{
+ int ret;
+
+ if (s->principal != NULL) {
+ ret = krb5_copy_principal(context,
+ s->principal,
+ &h->principal);
+ if (ret != 0) {
+ free_Event(h);
+ return ret;
+ }
+ } else {
+ h->principal = NULL;
+ }
+ 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;
+
+ ZERO_STRUCTP(h);
+
+ 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;
+ }
+
+ 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;
+ }
+ } else {
+ h->modified_by = NULL;
+ }
+
+ 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;
+ } else {
+ h->valid_start = NULL;
+ }
+
+ 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;
+ } else {
+ h->valid_end = NULL;
+ }
+
+ 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;
+ } else {
+ h->pw_end = NULL;
+ }
+
+ if (s->max_life != NULL) {
+ h->max_life = malloc(sizeof(unsigned int));
+ if (h->max_life == NULL) {
+ rc = ENOMEM;
+ goto error;
+ }
+ *h->max_life = *s->max_life;
+ } else {
+ h->max_life = NULL;
+ }
+
+ if (s->max_renew != NULL) {
+ h->max_renew = malloc(sizeof(unsigned int));
+ if (h->max_renew == NULL) {
+ rc = ENOMEM;
+ goto error;
+ }
+ *h->max_renew = *s->max_renew;
+ } else {
+ h->max_renew = NULL;
+ }
+
+ sdb_flags_to_hdb_flags(&s->flags, &h->flags);
+
+ h->etypes = NULL;
+ 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];
+ }
+ }
+
+ h->session_etypes = NULL;
+ 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..c24fd73
--- /dev/null
+++ b/source4/kdc/sdb_to_kdb.c
@@ -0,0 +1,329 @@
+/*
+ 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"
+
+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) {
+ memset(k->key_data[i].key_data_contents[j], 0, 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..06025cc
--- /dev/null
+++ b/source4/kdc/wdc-samba4.c
@@ -0,0 +1,642 @@
+/*
+ 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/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>
+
+static bool samba_wdc_is_s4u2self_req(astgs_request_t r)
+{
+ krb5_kdc_configuration *config = kdc_request_get_config((kdc_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 (config->enable_fast && req->padata != NULL) {
+ const PA_DATA *pa_fx_fast = NULL;
+ int idx = 0;
+
+ pa_fx_fast = krb5_find_padata(req->padata->val,
+ req->padata->len,
+ KRB5_PADATA_FX_FAST,
+ &idx);
+ if (pa_fx_fast != NULL) {
+ /*
+ * We're in the outer request
+ * with KRB5_PADATA_FX_FAST
+ * if fast is enabled we'll
+ * process the s4u2self
+ * request only in the
+ * inner request.
+ */
+ 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;
+ krb5_error_code ret;
+ NTSTATUS nt_status;
+ struct samba_kdc_entry *skdc_entry =
+ talloc_get_type_abort(client->context,
+ struct samba_kdc_entry);
+ bool is_krbtgt;
+ 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;
+
+ mem_ctx = talloc_named(client->context, 0, "samba_get_pac context");
+ if (!mem_ctx) {
+ return ENOMEM;
+ }
+
+ if (pk_reply_key != NULL) {
+ cred_ndr_ptr = &cred_ndr;
+ }
+
+ is_krbtgt = krb5_principal_is_krbtgt(context, server->principal);
+
+ nt_status = samba_kdc_get_pac_blobs(mem_ctx, skdc_entry,
+ asserted_identity,
+ &logon_blob,
+ cred_ndr_ptr,
+ &upn_blob,
+ is_krbtgt ? &pac_attrs_blob : NULL,
+ pac_attributes,
+ is_krbtgt ? &requester_sid_blob : NULL);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(mem_ctx);
+ return EINVAL;
+ }
+
+ 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, *pac);
+
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static krb5_error_code samba_wdc_reget_pac2(astgs_request_t r,
+ const krb5_principal delegated_proxy_principal,
+ hdb_entry *client,
+ hdb_entry *server,
+ hdb_entry *krbtgt,
+ 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 *server_skdc_entry =
+ talloc_get_type_abort(server->context, struct samba_kdc_entry);
+ 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;
+ krb5_error_code ret;
+ bool is_s4u2self = samba_wdc_is_s4u2self_req(r);
+ bool is_in_db = false;
+ bool is_untrusted = false;
+ uint32_t flags = 0;
+
+ mem_ctx = talloc_named(NULL, 0, "samba_kdc_reget_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
+ *
+ * Becasue 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_untrusted);
+ if (ret != 0) {
+ goto out;
+ }
+
+ if (is_s4u2self) {
+ flags |= SAMBA_KDC_FLAG_PROTOCOL_TRANSITION;
+ }
+
+ if (delegated_proxy_principal != 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) {
+ DEBUG(1, ("PAC KDC signature failed to verify\n"));
+ goto out;
+ }
+
+ flags |= SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION;
+ }
+
+ if (is_untrusted) {
+ flags |= SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED;
+ }
+
+ if (is_in_db) {
+ flags |= SAMBA_KDC_FLAG_KRBTGT_IN_DB;
+ }
+
+ ret = krb5_pac_init(context, &new_pac);
+ if (ret != 0) {
+ new_pac = NULL;
+ goto out;
+ }
+
+ ret = samba_kdc_update_pac(mem_ctx,
+ context,
+ krbtgt_skdc_entry->kdc_db_ctx->samdb,
+ flags,
+ client_skdc_entry,
+ server->principal,
+ server_skdc_entry,
+ krbtgt_skdc_entry,
+ delegated_proxy_principal,
+ *pac,
+ new_pac);
+ if (ret != 0) {
+ krb5_pac_free(context, new_pac);
+ if (ret == ENODATA) {
+ 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;
+}
+
+/* Resign (and reform, including possibly new groups) a PAC */
+
+static krb5_error_code samba_wdc_reget_pac(void *priv, astgs_request_t r,
+ const krb5_principal client_principal,
+ const krb5_principal delegated_proxy_principal,
+ hdb_entry *client,
+ hdb_entry *server,
+ hdb_entry *krbtgt,
+ 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_principal) {
+ uint16_t rodc_id;
+ unsigned int my_krbtgt_number;
+
+ /*
+ * We're using delegated_proxy_principal 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,
+ &rodc_id);
+ if (ret != 0) {
+ DEBUG(1, ("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.
+ */
+
+ my_krbtgt_number = krbtgt_skdc_entry->kdc_db_ctx->my_krbtgt_number;
+
+ if (my_krbtgt_number != 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 (rodc_id != my_krbtgt_number) {
+ 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 (rodc_id != krbtgt->kvno >> 16) {
+ struct sdb_entry signing_krbtgt_sdb;
+
+ /*
+ * If we didn't sign the ticket, then return an
+ * error.
+ */
+ if (rodc_id != 0) {
+ return KRB5KRB_AP_ERR_MODIFIED;
+ }
+
+ /*
+ * 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_CANON,
+ 0,
+ &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.
+ */
+ krb5_ticket *tgt = kdc_request_get_ticket(r);
+
+ 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(tgt->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_reget_pac2(r,
+ delegated_proxy_principal,
+ client,
+ server,
+ 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;
+}
+
+/* this function allocates 'data' using malloc.
+ * The caller is responsible for freeing it */
+static void samba_kdc_build_edata_reply(NTSTATUS nt_status, krb5_data *e_data)
+{
+ e_data->data = malloc(12);
+ if (e_data->data == NULL) {
+ e_data->length = 0;
+ e_data->data = NULL;
+ return;
+ }
+ e_data->length = 12;
+
+ SIVAL(e_data->data, 0, NT_STATUS_V(nt_status));
+ SIVAL(e_data->data, 4, 0);
+ SIVAL(e_data->data, 8, 1);
+
+ return;
+}
+
+static krb5_error_code samba_wdc_check_client_access(void *priv,
+ astgs_request_t r)
+{
+ struct samba_kdc_entry *kdc_entry;
+ bool password_change;
+ char *workstation;
+ NTSTATUS nt_status;
+
+
+ kdc_entry = talloc_get_type(kdc_request_get_client(r)->context, struct samba_kdc_entry);
+ password_change = (kdc_request_get_server(r) && kdc_request_get_server(r)->flags.change_pw);
+ workstation = get_netbios_name((TALLOC_CTX *)kdc_request_get_client(r)->context,
+ kdc_request_get_req(r)->req_body.addresses);
+
+ nt_status = samba_kdc_check_client_access(kdc_entry,
+ kdc_request_get_cname((kdc_request_t)r),
+ workstation,
+ password_change);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MEMORY)) {
+ return ENOMEM;
+ }
+
+ if (kdc_request_get_rep(r)->padata) {
+ int ret;
+ krb5_data kd;
+
+ samba_kdc_build_edata_reply(nt_status, &kd);
+ ret = krb5_padata_add(kdc_request_get_context((kdc_request_t)r), kdc_request_get_rep(r)->padata,
+ KRB5_PADATA_PW_SALT,
+ kd.data, kd.length);
+ if (ret != 0) {
+ /*
+ * So we do not leak the allocated
+ * memory on kd in the error case
+ */
+ krb5_data_free(&kd);
+ }
+ }
+
+ return samba_kdc_map_policy_err(nt_status);
+ }
+
+ /* Now do the standard Heimdal check */
+ 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 kd 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_10,
+ .init = samba_wdc_plugin_init,
+ .fini = samba_wdc_plugin_fini,
+ .pac_verify = 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..0c902f5
--- /dev/null
+++ b/source4/kdc/wscript_build
@@ -0,0 +1,182 @@
+#!/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',
+ 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',
+ deps='ldb auth4_sam common_auth samba-credentials samba-hostconfig com_err'
+ )
+
+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',
+ 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')