s4:kdc: Add KDC support for Protected Users group
[samba.git] / source4 / kdc / db-glue.c
index 023ae7b580d672377ea127866d54e378b9b36508..d384ecea811d05f231d214cbbd778670107143c8 100644 (file)
 
 #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))
@@ -233,8 +238,12 @@ static struct SDBFlags uf2SDBFlags(krb5_context context, uint32_t userAccountCon
                flags.require_preauth = 0;
        } else {
                flags.require_preauth = 1;
+       }
 
+       if (userAccountControl & UF_NO_AUTH_DATA_REQUIRED) {
+               flags.no_auth_data_reqd = 1;
        }
+
        return flags;
 }
 
@@ -311,6 +320,111 @@ static int samba_kdc_sort_encryption_keys(struct sdb_entry_ex *entry_ex)
        return 0;
 }
 
+int samba_kdc_set_fixed_keys(krb5_context context,
+                            struct samba_kdc_db_context *kdc_db_ctx,
+                            const struct ldb_val *secretbuffer,
+                            bool is_protected,
+                            struct sdb_entry_ex *entry_ex)
+{
+       uint32_t supported_enctypes = ENC_ALL_TYPES;
+       uint16_t allocated_keys = 0;
+       int ret;
+
+       allocated_keys = 3;
+       entry_ex->entry.keys.len = 0;
+       entry_ex->entry.keys.val = calloc(allocated_keys, sizeof(struct sdb_key));
+       if (entry_ex->entry.keys.val == NULL) {
+               memset(secretbuffer->data, 0, secretbuffer->length);
+               ret = ENOMEM;
+               goto out;
+       }
+
+       if (is_protected) {
+               supported_enctypes &= ~ENC_RC4_HMAC_MD5;
+       }
+
+       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;
+               }
+
+               entry_ex->entry.keys.val[entry_ex->entry.keys.len] = key;
+               entry_ex->entry.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;
+               }
+
+               entry_ex->entry.keys.val[entry_ex->entry.keys.len] = key;
+               entry_ex->entry.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;
+               }
+
+               entry_ex->entry.keys.val[entry_ex->entry.keys.len] = key;
+               entry_ex->entry.keys.len++;
+       }
+       ret = 0;
+out:
+       return ret;
+}
+
+
+static int samba_kdc_set_random_keys(krb5_context context,
+                                    struct samba_kdc_db_context *kdc_db_ctx,
+                                    struct sdb_entry_ex *entry_ex,
+                                    bool is_protected)
+{
+       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, kdc_db_ctx,
+                                       &secret_val,
+                                       is_protected,
+                                       entry_ex);
+}
+
+
 static krb5_error_code samba_kdc_message2entry_keys(krb5_context context,
                                                    struct samba_kdc_db_context *kdc_db_ctx,
                                                    TALLOC_CTX *mem_ctx,
@@ -319,7 +433,9 @@ static krb5_error_code samba_kdc_message2entry_keys(krb5_context context,
                                                    bool is_rodc,
                                                    uint32_t userAccountControl,
                                                    enum samba_kdc_ent_type ent_type,
-                                                   struct sdb_entry_ex *entry_ex)
+                                                   struct sdb_entry_ex *entry_ex,
+                                                   bool is_protected,
+                                                   uint32_t *supported_enctypes_out)
 {
        krb5_error_code ret = 0;
        enum ndr_err_code ndr_err;
@@ -339,10 +455,18 @@ static krb5_error_code samba_kdc_message2entry_keys(krb5_context context,
                = ldb_msg_find_attr_as_uint(msg,
                                            "msDS-SupportedEncryptionTypes",
                                            0);
+       *supported_enctypes_out = 0;
 
        if (rid == DOMAIN_RID_KRBTGT || is_rodc) {
+               bool enable_fast;
+
                /* KDCs (and KDCs on RODCs) use AES */
                supported_enctypes |= ENC_HMAC_SHA1_96_AES128 | ENC_HMAC_SHA1_96_AES256;
+
+               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 comptuer accounts use AES */
                supported_enctypes |= ENC_HMAC_SHA1_96_AES128 | ENC_HMAC_SHA1_96_AES256;
@@ -365,6 +489,10 @@ static krb5_error_code samba_kdc_message2entry_keys(krb5_context context,
                supported_enctypes |= ENC_RC4_HMAC_MD5;
        }
 
+       if (is_protected) {
+               supported_enctypes &= ~ENC_RC4_HMAC_MD5;
+       }
+
        /* Is this the krbtgt or a RODC krbtgt */
        if (is_rodc) {
                rodc_krbtgt_number = ldb_msg_find_attr_as_int(msg, "msDS-SecondaryKrbTgtNumber", -1);
@@ -380,75 +508,13 @@ static krb5_error_code samba_kdc_message2entry_keys(krb5_context context,
 
        if ((ent_type == SAMBA_KDC_ENT_TYPE_CLIENT)
            && (userAccountControl & UF_SMARTCARD_REQUIRED)) {
-               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));
-
-               allocated_keys = 3;
-               entry_ex->entry.keys.len = 0;
-               entry_ex->entry.keys.val = calloc(allocated_keys, sizeof(struct sdb_key));
-               if (entry_ex->entry.keys.val == NULL) {
-                       ZERO_STRUCT(secretbuffer);
-                       ret = ENOMEM;
-                       goto out;
-               }
+               ret = samba_kdc_set_random_keys(context,
+                                               kdc_db_ctx,
+                                               entry_ex,
+                                               is_protected);
 
-               if (supported_enctypes & ENC_HMAC_SHA1_96_AES256) {
-                       struct sdb_key key = {};
+               *supported_enctypes_out = supported_enctypes;
 
-                       ret = smb_krb5_keyblock_init_contents(context,
-                                                             ENCTYPE_AES256_CTS_HMAC_SHA1_96,
-                                                             secretbuffer, 32,
-                                                             &key.key);
-                       if (ret) {
-                               ZERO_STRUCT(secretbuffer);
-                               goto out;
-                       }
-
-                       entry_ex->entry.keys.val[entry_ex->entry.keys.len] = key;
-                       entry_ex->entry.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, 16,
-                                                             &key.key);
-                       if (ret) {
-                               ZERO_STRUCT(secretbuffer);
-                               goto out;
-                       }
-
-                       entry_ex->entry.keys.val[entry_ex->entry.keys.len] = key;
-                       entry_ex->entry.keys.len++;
-               }
-
-               if (supported_enctypes & ENC_RC4_HMAC_MD5) {
-                       struct sdb_key key = {};
-
-                       ret = smb_krb5_keyblock_init_contents(context,
-                                                             ENCTYPE_ARCFOUR_HMAC,
-                                                             secretbuffer, 16,
-                                                             &key.key);
-                       if (ret) {
-                               ZERO_STRUCT(secretbuffer);
-                               goto out;
-                       }
-
-                       entry_ex->entry.keys.val[entry_ex->entry.keys.len] = key;
-                       entry_ex->entry.keys.len++;
-               }
-
-               ret = 0;
                goto out;
        }
 
@@ -589,15 +655,19 @@ static krb5_error_code samba_kdc_message2entry_keys(krb5_context context,
 
                entry_ex->entry.keys.val[entry_ex->entry.keys.len] = key;
                entry_ex->entry.keys.len++;
+
+               *supported_enctypes_out |= ENC_RC4_HMAC_MD5;
        }
 
        if (pkb4) {
                for (i=0; i < pkb4->num_keys; i++) {
                        struct sdb_key key = {};
+                       uint32_t enctype_bit;
 
                        if (!pkb4->keys[i].value) continue;
 
-                       if (!(kerberos_enctype_to_bitmap(pkb4->keys[i].keytype) & supported_enctypes)) {
+                       enctype_bit = kerberos_enctype_to_bitmap(pkb4->keys[i].keytype);
+                       if (!(enctype_bit & supported_enctypes)) {
                                continue;
                        }
 
@@ -631,31 +701,35 @@ static krb5_error_code samba_kdc_message2entry_keys(krb5_context context,
                                                              pkb4->keys[i].value->data,
                                                              pkb4->keys[i].value->length,
                                                              &key.key);
-                       if (ret == KRB5_PROG_ETYPE_NOSUPP) {
-                               DEBUG(2,("Unsupported keytype ignored - type %u\n",
-                                        pkb4->keys[i].keytype));
-                               ret = 0;
-                               continue;
-                       }
                        if (ret) {
                                if (key.salt) {
                                        smb_krb5_free_data_contents(context, &key.salt->salt);
                                        free(key.salt);
                                        key.salt = NULL;
                                }
+                               if (ret == KRB5_PROG_ETYPE_NOSUPP) {
+                                       DEBUG(2,("Unsupported keytype ignored - type %u\n",
+                                                pkb4->keys[i].keytype));
+                                       ret = 0;
+                                       continue;
+                               }
                                goto out;
                        }
 
                        entry_ex->entry.keys.val[entry_ex->entry.keys.len] = key;
                        entry_ex->entry.keys.len++;
+
+                       *supported_enctypes_out |= enctype_bit;
                }
        } else if (pkb3) {
                for (i=0; i < pkb3->num_keys; i++) {
                        struct sdb_key key = {};
+                       uint32_t enctype_bit;
 
                        if (!pkb3->keys[i].value) continue;
 
-                       if (!(kerberos_enctype_to_bitmap(pkb3->keys[i].keytype) & supported_enctypes)) {
+                       enctype_bit = kerberos_enctype_to_bitmap(pkb3->keys[i].keytype);
+                       if (!(enctype_bit & supported_enctypes)) {
                                continue;
                        }
 
@@ -693,14 +767,27 @@ static krb5_error_code samba_kdc_message2entry_keys(krb5_context context,
                                        free(key.salt);
                                        key.salt = NULL;
                                }
+                               if (ret == KRB5_PROG_ETYPE_NOSUPP) {
+                                       DEBUG(2,("Unsupported keytype ignored - type %u\n",
+                                                pkb3->keys[i].keytype));
+                                       ret = 0;
+                                       continue;
+                               }
                                goto out;
                        }
 
                        entry_ex->entry.keys.val[entry_ex->entry.keys.len] = key;
                        entry_ex->entry.keys.len++;
+
+                       *supported_enctypes_out |= enctype_bit;
                }
        }
 
+       /* Set FAST support bits */
+       *supported_enctypes_out |= supported_enctypes & (ENC_FAST_SUPPORTED |
+                                                        ENC_COMPOUND_IDENTITY_SUPPORTED |
+                                                        ENC_CLAIMS_SUPPORTED);
+
 out:
        if (ret != 0) {
                entry_ex->entry.keys.len = 0;
@@ -791,18 +878,16 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
        uint32_t msDS_User_Account_Control_Computed;
        krb5_error_code ret = 0;
        krb5_boolean is_computer = FALSE;
-
        struct samba_kdc_entry *p;
+       uint32_t supported_enctypes = 0;
        NTTIME acct_expiry;
        NTSTATUS status;
-
+       bool protected_user = false;
        uint32_t rid;
        bool is_rodc = false;
        struct ldb_message_element *objectclasses;
-       struct ldb_val computer_val;
+       struct ldb_val computer_val = data_blob_string_const("computer");
        const char *samAccountName = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL);
-       computer_val.data = discard_const_p(uint8_t,"computer");
-       computer_val.length = strlen((const char *)computer_val.data);
 
        if (ldb_msg_find_element(msg, "msDS-SecondaryKrbTgtNumber")) {
                is_rodc = true;
@@ -862,13 +947,13 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
                userAccountControl |= msDS_User_Account_Control_Computed;
        }
 
-       /* 
+       /*
         * If we are set to canonicalize, we get back the fixed UPPER
         * case realm, and the real username (ie matching LDAP
-        * samAccountName) 
+        * samAccountName)
         *
         * Otherwise, if we are set to enterprise, we
-        * get back the whole principal as-sent 
+        * 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
@@ -877,7 +962,7 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
        if (ent_type == SAMBA_KDC_ENT_TYPE_KRBTGT) {
                p->is_krbtgt = true;
 
-               if (flags & (SDB_F_CANON)) {
+               if (flags & (SDB_F_CANON|SDB_F_FORCE_CANON)) {
                        /*
                         * When requested to do so, ensure that the
                         * both realm values in the principal are set
@@ -914,11 +999,16 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
                        krb5_clear_error_message(context);
                        goto out;
                }
-       } else if ((flags & SDB_F_CANON) && (flags & SDB_F_FOR_AS_REQ)) {
+       } else if ((flags & SDB_F_FORCE_CANON) ||
+                  ((flags & SDB_F_CANON) && (flags & SDB_F_FOR_AS_REQ))) {
                /*
                 * 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.
                 */
                ret = smb_krb5_make_principal(context, &entry_ex->entry.principal, lpcfg_realm(lp_ctx), samAccountName, NULL);
                if (ret) {
@@ -932,25 +1022,30 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
                        goto out;
                }
 
-               if (smb_krb5_principal_get_type(context, principal) != KRB5_NT_ENTERPRISE_PRINCIPAL) {
-                       /* 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 */
-                       
-                       /* this has to be with malloc() */
-                       ret = smb_krb5_principal_set_realm(context, entry_ex->entry.principal, lpcfg_realm(lp_ctx));
-                       if (ret) {
-                               krb5_clear_error_message(context);
-                               goto out;
-                       }
+               /* 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 */
+
+               /* this has to be with malloc() */
+               ret = smb_krb5_principal_set_realm(context, entry_ex->entry.principal, lpcfg_realm(lp_ctx));
+               if (ret) {
+                       krb5_clear_error_message(context);
+                       goto out;
                }
        }
 
        /* First try and figure out the flags based on the userAccountControl */
        entry_ex->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_ex->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
@@ -962,6 +1057,29 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
                        entry_ex->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_ex->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
@@ -1127,7 +1245,7 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
                                                kdc_db_ctx->policy.usr_tkt_lifetime);
        }
 
-       entry_ex->entry.max_renew = malloc(sizeof(*entry_ex->entry.max_life));
+       entry_ex->entry.max_renew = malloc(sizeof(*entry_ex->entry.max_renew));
        if (entry_ex->entry.max_renew == NULL) {
                ret = ENOMEM;
                goto out;
@@ -1135,16 +1253,57 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
 
        *entry_ex->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_ex->entry.max_life = MIN(*entry_ex->entry.max_life, 4 * 60 * 60);
+                       *entry_ex->entry.max_renew = MIN(*entry_ex->entry.max_renew, 4 * 60 * 60);
+
+                       entry_ex->entry.flags.forwardable = 0;
+                       entry_ex->entry.flags.proxiable = 0;
+               }
+       }
+
        /* Get keys from the db */
        ret = samba_kdc_message2entry_keys(context, kdc_db_ctx, p, msg,
                                           rid, is_rodc, userAccountControl,
-                                          ent_type, entry_ex);
+                                          ent_type, entry_ex, protected_user, &supported_enctypes);
        if (ret) {
                /* Could be bogus data in the entry, or out of memory */
                goto out;
        }
 
        p->msg = talloc_steal(p, msg);
+       p->supported_enctypes = supported_enctypes;
 
 out:
        if (ret != 0) {
@@ -1285,6 +1444,7 @@ static krb5_error_code samba_kdc_trust_message2entry(krb5_context context,
        p->is_trust = true;
        p->kdc_db_ctx = kdc_db_ctx;
        p->realm_dn = realm_dn;
+       p->supported_enctypes = supported_enctypes;
 
        talloc_set_destructor(p, samba_kdc_entry_destructor);
 
@@ -1556,6 +1716,9 @@ static krb5_error_code samba_kdc_trust_message2entry(krb5_context context,
 
        entry_ex->entry.max_renew = NULL;
 
+       /* Match Windows behavior and allow forwardable flag in cross-realm. */
+       entry_ex->entry.flags.forwardable = 1;
+
        ret = samba_kdc_sort_encryption_keys(entry_ex);
        if (ret != 0) {
                krb5_clear_error_message(context);
@@ -2499,53 +2662,37 @@ krb5_error_code samba_kdc_nextkey(krb5_context context,
 
 /* Check if a given entry may delegate or do s4u2self to this target principal
  *
- * This is currently a very nasty hack - allowing only delegation to itself.
+ * 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_s4u2self(krb5_context context,
-                        struct samba_kdc_db_context *kdc_db_ctx,
-                        struct samba_kdc_entry *skdc_entry,
-                        krb5_const_principal target_principal)
+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 ret;
-       struct ldb_dn *realm_dn;
-       struct ldb_message *msg;
        struct dom_sid *orig_sid;
        struct dom_sid *target_sid;
-       const char *delegation_check_attrs[] = {
-               "objectSid", NULL
-       };
-
-       TALLOC_CTX *mem_ctx = talloc_named(kdc_db_ctx, 0, "samba_kdc_check_s4u2self");
-
-       if (!mem_ctx) {
-               ret = ENOMEM;
-               krb5_set_error_message(context, ret, "samba_kdc_check_s4u2self: talloc_named() failed!");
-               return ret;
-       }
-
-       ret = samba_kdc_lookup_server(context, kdc_db_ctx, mem_ctx, target_principal,
-                                     SDB_F_GET_CLIENT|SDB_F_GET_SERVER,
-                                     delegation_check_attrs, &realm_dn, &msg);
-
-       if (ret != 0) {
-               talloc_free(mem_ctx);
-               return ret;
-       }
+       TALLOC_CTX *frame = talloc_stackframe();
 
-       orig_sid = samdb_result_dom_sid(mem_ctx, skdc_entry->msg, "objectSid");
-       target_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
+       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 principal, even if by a different
-        * name.  The easy and safe way to prove this is by SID
-        * comparison */
+       /*
+        * 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(mem_ctx);
-               return KRB5KDC_ERR_BADOPTION;
+               talloc_free(frame);
+               return KRB5KRB_AP_ERR_BADMATCH;
        }
 
-       talloc_free(mem_ctx);
-       return ret;
+       talloc_free(frame);
+       return 0;
 }
 
 /* Certificates printed by a the Certificate Authority might have a
@@ -2645,6 +2792,24 @@ samba_kdc_check_s4u2proxy(krb5_context context,
                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.
@@ -2675,11 +2840,6 @@ samba_kdc_check_s4u2proxy(krb5_context context,
                return ret;
        }
 
-       el = ldb_msg_find_element(skdc_entry->msg, "msDS-AllowedToDelegateTo");
-       if (el == NULL) {
-               goto bad_option;
-       }
-
        val = data_blob_string_const(target_principal_name);
 
        for (i=0; i<el->num_values; i++) {
@@ -2703,6 +2863,7 @@ samba_kdc_check_s4u2proxy(krb5_context context,
        }
 
        if (!found) {
+               ret = ENOENT;
                goto bad_option;
        }
 
@@ -2721,6 +2882,175 @@ bad_option:
        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;
+       uint32_t access_desired = SEC_ADS_GENERIC_ALL; /* => 0x000f01ff */
+       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)
 {
@@ -2756,6 +3086,21 @@ NTSTATUS samba_kdc_setup_db_ctx(TALLOC_CTX *mem_ctx, struct samba_kdc_base_conte
                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,
@@ -2852,4 +3197,4 @@ NTSTATUS samba_kdc_setup_db_ctx(TALLOC_CTX *mem_ctx, struct samba_kdc_base_conte
        }
        *kdc_db_ctx_out = kdc_db_ctx;
        return NT_STATUS_OK;
-}
+}
\ No newline at end of file