diff options
Diffstat (limited to '')
-rw-r--r-- | source4/kdc/pac-glue.c | 3248 |
1 files changed, 3248 insertions, 0 deletions
diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c new file mode 100644 index 0000000..12465b7 --- /dev/null +++ b/source4/kdc/pac-glue.c @@ -0,0 +1,3248 @@ +/* + Unix SMB/CIFS implementation. + + PAC Glue between Samba and the KDC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009 + Copyright (C) Simo Sorce <idra@samba.org> 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "lib/replace/replace.h" +#include "lib/replace/system/kerberos.h" +#include "lib/replace/system/filesys.h" +#include "lib/util/debug.h" +#include "lib/util/samba_util.h" +#include "lib/util/talloc_stack.h" + +#include "auth/auth_sam_reply.h" +#include "auth/kerberos/kerberos.h" +#include "auth/kerberos/pac_utils.h" +#include "auth/authn_policy.h" +#include "libcli/security/security.h" +#include "libds/common/flags.h" +#include "librpc/gen_ndr/ndr_krb5pac.h" +#include "param/param.h" +#include "source4/auth/auth.h" +#include "source4/dsdb/common/util.h" +#include "source4/dsdb/samdb/samdb.h" +#include "source4/kdc/authn_policy_util.h" +#include "source4/kdc/samba_kdc.h" +#include "source4/kdc/pac-glue.h" +#include "source4/kdc/ad_claims.h" +#include "source4/kdc/pac-blobs.h" + +#include <ldb.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_KERBEROS + +static +NTSTATUS samba_get_logon_info_pac_blob(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *info, + const struct PAC_DOMAIN_GROUP_MEMBERSHIP *override_resource_groups, + const enum auth_group_inclusion group_inclusion, + DATA_BLOB *pac_data) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct netr_SamInfo3 *info3 = NULL; + struct PAC_DOMAIN_GROUP_MEMBERSHIP *_resource_groups = NULL; + struct PAC_DOMAIN_GROUP_MEMBERSHIP **resource_groups = NULL; + union PAC_INFO pac_info = {}; + enum ndr_err_code ndr_err; + NTSTATUS nt_status = NT_STATUS_OK; + + *pac_data = data_blob_null; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (override_resource_groups == NULL) { + resource_groups = &_resource_groups; + } else if (group_inclusion != AUTH_EXCLUDE_RESOURCE_GROUPS) { + /* + * It doesn't make sense to override resource groups if we claim + * to want resource groups from user_info_dc. + */ + DBG_ERR("supplied resource groups with invalid group inclusion parameter: %u\n", + group_inclusion); + nt_status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + nt_status = auth_convert_user_info_dc_saminfo3(tmp_ctx, info, + group_inclusion, + &info3, + resource_groups); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_WARNING("Getting Samba info failed: %s\n", + nt_errstr(nt_status)); + goto out; + } + + pac_info.logon_info.info = talloc_zero(tmp_ctx, struct PAC_LOGON_INFO); + if (!pac_info.logon_info.info) { + nt_status = NT_STATUS_NO_MEMORY; + goto out; + } + + pac_info.logon_info.info->info3 = *info3; + if (_resource_groups != NULL) { + pac_info.logon_info.info->resource_groups = *_resource_groups; + } + + if (override_resource_groups != NULL) { + pac_info.logon_info.info->resource_groups = *override_resource_groups; + } + + if (group_inclusion != AUTH_EXCLUDE_RESOURCE_GROUPS) { + /* + * Set the resource groups flag based on whether any groups are + * present. Otherwise, the flag is propagated from the + * originating PAC. + */ + if (pac_info.logon_info.info->resource_groups.groups.count > 0) { + pac_info.logon_info.info->info3.base.user_flags |= NETLOGON_RESOURCE_GROUPS; + } else { + pac_info.logon_info.info->info3.base.user_flags &= ~NETLOGON_RESOURCE_GROUPS; + } + } + + ndr_err = ndr_push_union_blob(pac_data, mem_ctx, &pac_info, + PAC_TYPE_LOGON_INFO, + (ndr_push_flags_fn_t)ndr_push_PAC_INFO); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_WARNING("PAC_LOGON_INFO (presig) push failed: %s\n", + nt_errstr(nt_status)); + goto out; + } + +out: + talloc_free(tmp_ctx); + return nt_status; +} + +static +NTSTATUS samba_get_upn_info_pac_blob(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *info, + DATA_BLOB *upn_data) +{ + TALLOC_CTX *tmp_ctx = NULL; + union PAC_INFO pac_upn = {}; + enum ndr_err_code ndr_err; + NTSTATUS nt_status = NT_STATUS_OK; + bool ok; + + *upn_data = data_blob_null; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + pac_upn.upn_dns_info.upn_name = info->info->user_principal_name; + pac_upn.upn_dns_info.dns_domain_name = strupper_talloc(tmp_ctx, + info->info->dns_domain_name); + if (pac_upn.upn_dns_info.dns_domain_name == NULL) { + nt_status = NT_STATUS_NO_MEMORY; + goto out; + } + if (info->info->user_principal_constructed) { + pac_upn.upn_dns_info.flags |= PAC_UPN_DNS_FLAG_CONSTRUCTED; + } + + pac_upn.upn_dns_info.flags |= PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID; + + pac_upn.upn_dns_info.ex.sam_name_and_sid.samaccountname + = info->info->account_name; + + pac_upn.upn_dns_info.ex.sam_name_and_sid.objectsid + = &info->sids[PRIMARY_USER_SID_INDEX].sid; + + ndr_err = ndr_push_union_blob(upn_data, mem_ctx, &pac_upn, + PAC_TYPE_UPN_DNS_INFO, + (ndr_push_flags_fn_t)ndr_push_PAC_INFO); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_WARNING("PAC UPN_DNS_INFO (presig) push failed: %s\n", + nt_errstr(nt_status)); + goto out; + } + + ok = data_blob_pad(mem_ctx, upn_data, 8); + if (!ok) { + talloc_free(upn_data); + nt_status = NT_STATUS_NO_MEMORY; + goto out; + } + +out: + talloc_free(tmp_ctx); + return nt_status; +} + +static +NTSTATUS samba_get_cred_info_ndr_blob(TALLOC_CTX *mem_ctx, + const struct ldb_message *msg, + DATA_BLOB *cred_blob) +{ + enum ndr_err_code ndr_err; + NTSTATUS nt_status; + struct samr_Password *lm_hash = NULL; + struct samr_Password *nt_hash = NULL; + struct PAC_CREDENTIAL_NTLM_SECPKG ntlm_secpkg = { + .version = 0, + }; + DATA_BLOB ntlm_blob = data_blob_null; + struct PAC_CREDENTIAL_SUPPLEMENTAL_SECPKG secpkgs[1] = {{ + .credential_size = 0, + }}; + struct PAC_CREDENTIAL_DATA cred_data = { + .credential_count = 0, + }; + struct PAC_CREDENTIAL_DATA_NDR cred_ndr = {}; + + *cred_blob = data_blob_null; + + lm_hash = samdb_result_hash(mem_ctx, msg, "dBCSPwd"); + if (lm_hash != NULL) { + bool zero = all_zero(lm_hash->hash, 16); + if (zero) { + lm_hash = NULL; + } + } + if (lm_hash != NULL) { + DBG_INFO("Passing LM password hash through credentials set\n"); + ntlm_secpkg.flags |= PAC_CREDENTIAL_NTLM_HAS_LM_HASH; + ntlm_secpkg.lm_password = *lm_hash; + ZERO_STRUCTP(lm_hash); + TALLOC_FREE(lm_hash); + } + + nt_hash = samdb_result_hash(mem_ctx, msg, "unicodePwd"); + if (nt_hash != NULL) { + bool zero = all_zero(nt_hash->hash, 16); + if (zero) { + nt_hash = NULL; + } + } + if (nt_hash != NULL) { + DBG_INFO("Passing NT password hash through credentials set\n"); + ntlm_secpkg.flags |= PAC_CREDENTIAL_NTLM_HAS_NT_HASH; + ntlm_secpkg.nt_password = *nt_hash; + ZERO_STRUCTP(nt_hash); + TALLOC_FREE(nt_hash); + } + + if (ntlm_secpkg.flags == 0) { + return NT_STATUS_OK; + } + +#ifdef DEBUG_PASSWORD + if (DEBUGLVL(11)) { + NDR_PRINT_DEBUG(PAC_CREDENTIAL_NTLM_SECPKG, &ntlm_secpkg); + } +#endif + + ndr_err = ndr_push_struct_blob(&ntlm_blob, mem_ctx, &ntlm_secpkg, + (ndr_push_flags_fn_t)ndr_push_PAC_CREDENTIAL_NTLM_SECPKG); + ZERO_STRUCT(ntlm_secpkg); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_WARNING("PAC_CREDENTIAL_NTLM_SECPKG (presig) push failed: %s\n", + nt_errstr(nt_status)); + return nt_status; + } + + DBG_DEBUG("NTLM credential BLOB (len %zu) for user\n", + ntlm_blob.length); + dump_data_pw("PAC_CREDENTIAL_NTLM_SECPKG", + ntlm_blob.data, ntlm_blob.length); + + secpkgs[0].package_name.string = discard_const_p(char, "NTLM"); + secpkgs[0].credential_size = ntlm_blob.length; + secpkgs[0].credential = ntlm_blob.data; + + cred_data.credential_count = ARRAY_SIZE(secpkgs); + cred_data.credentials = secpkgs; + +#ifdef DEBUG_PASSWORD + if (DEBUGLVL(11)) { + NDR_PRINT_DEBUG(PAC_CREDENTIAL_DATA, &cred_data); + } +#endif + + cred_ndr.ctr.data = &cred_data; + +#ifdef DEBUG_PASSWORD + if (DEBUGLVL(11)) { + NDR_PRINT_DEBUG(PAC_CREDENTIAL_DATA_NDR, &cred_ndr); + } +#endif + + ndr_err = ndr_push_struct_blob(cred_blob, mem_ctx, &cred_ndr, + (ndr_push_flags_fn_t)ndr_push_PAC_CREDENTIAL_DATA_NDR); + data_blob_clear(&ntlm_blob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_WARNING("PAC_CREDENTIAL_DATA_NDR (presig) push failed: %s\n", + nt_errstr(nt_status)); + return nt_status; + } + + DBG_DEBUG("Created credential BLOB (len %zu) for user\n", + cred_blob->length); + dump_data_pw("PAC_CREDENTIAL_DATA_NDR", + cred_blob->data, cred_blob->length); + + return NT_STATUS_OK; +} + +krb5_error_code samba_kdc_encrypt_pac_credentials(krb5_context context, + const krb5_keyblock *pkreplykey, + const DATA_BLOB *cred_ndr_blob, + TALLOC_CTX *mem_ctx, + DATA_BLOB *cred_info_blob) +{ +#ifdef SAMBA4_USES_HEIMDAL + krb5_crypto cred_crypto; + krb5_enctype cred_enctype; + krb5_data cred_ndr_crypt; + struct PAC_CREDENTIAL_INFO pac_cred_info = { .version = 0, }; + krb5_error_code ret; + const char *krb5err; + enum ndr_err_code ndr_err; + NTSTATUS nt_status; + + *cred_info_blob = data_blob_null; + + ret = krb5_crypto_init(context, pkreplykey, ETYPE_NULL, + &cred_crypto); + if (ret != 0) { + krb5err = krb5_get_error_message(context, ret); + DBG_WARNING("Failed initializing cred data crypto: %s\n", krb5err); + krb5_free_error_message(context, krb5err); + return ret; + } + + ret = krb5_crypto_getenctype(context, cred_crypto, &cred_enctype); + if (ret != 0) { + DBG_WARNING("Failed getting crypto type for key\n"); + krb5_crypto_destroy(context, cred_crypto); + return ret; + } + + DBG_DEBUG("Plain cred_ndr_blob (len %zu)\n", + cred_ndr_blob->length); + dump_data_pw("PAC_CREDENTIAL_DATA_NDR", + cred_ndr_blob->data, cred_ndr_blob->length); + + ret = krb5_encrypt(context, cred_crypto, + KRB5_KU_OTHER_ENCRYPTED, + cred_ndr_blob->data, cred_ndr_blob->length, + &cred_ndr_crypt); + krb5_crypto_destroy(context, cred_crypto); + if (ret != 0) { + krb5err = krb5_get_error_message(context, ret); + DBG_WARNING("Failed crypt of cred data: %s\n", krb5err); + krb5_free_error_message(context, krb5err); + return ret; + } + + pac_cred_info.encryption_type = cred_enctype; + pac_cred_info.encrypted_data.length = cred_ndr_crypt.length; + pac_cred_info.encrypted_data.data = (uint8_t *)cred_ndr_crypt.data; + + if (DEBUGLVL(10)) { + NDR_PRINT_DEBUG(PAC_CREDENTIAL_INFO, &pac_cred_info); + } + + ndr_err = ndr_push_struct_blob(cred_info_blob, mem_ctx, &pac_cred_info, + (ndr_push_flags_fn_t)ndr_push_PAC_CREDENTIAL_INFO); + krb5_data_free(&cred_ndr_crypt); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_WARNING("PAC_CREDENTIAL_INFO (presig) push failed: %s\n", + nt_errstr(nt_status)); + return KRB5KDC_ERR_SVC_UNAVAILABLE; + } + + DBG_DEBUG("Encrypted credential BLOB (len %zu) with alg %"PRId32"\n", + cred_info_blob->length, pac_cred_info.encryption_type); + dump_data_pw("PAC_CREDENTIAL_INFO", + cred_info_blob->data, cred_info_blob->length); + + return 0; +#else /* SAMBA4_USES_HEIMDAL */ + TALLOC_CTX *tmp_ctx = NULL; + krb5_key cred_key; + krb5_enctype cred_enctype; + struct PAC_CREDENTIAL_INFO pac_cred_info = { .version = 0, }; + krb5_error_code code = 0; + const char *krb5err; + enum ndr_err_code ndr_err; + NTSTATUS nt_status; + krb5_data cred_ndr_data; + krb5_enc_data cred_ndr_crypt; + size_t enc_len = 0; + + *cred_info_blob = data_blob_null; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + code = krb5_k_create_key(context, + pkreplykey, + &cred_key); + if (code != 0) { + krb5err = krb5_get_error_message(context, code); + DBG_WARNING("Failed initializing cred data crypto: %s\n", krb5err); + krb5_free_error_message(context, krb5err); + goto out; + } + + cred_enctype = krb5_k_key_enctype(context, cred_key); + + DBG_DEBUG("Plain cred_ndr_blob (len %zu)\n", + cred_ndr_blob->length); + dump_data_pw("PAC_CREDENTIAL_DATA_NDR", + cred_ndr_blob->data, cred_ndr_blob->length); + + pac_cred_info.encryption_type = cred_enctype; + + cred_ndr_data = smb_krb5_data_from_blob(*cred_ndr_blob); + + code = krb5_c_encrypt_length(context, + cred_enctype, + cred_ndr_data.length, + &enc_len); + if (code != 0) { + krb5err = krb5_get_error_message(context, code); + DBG_WARNING("Failed initializing cred data crypto: %s\n", krb5err); + krb5_free_error_message(context, krb5err); + goto out; + } + + pac_cred_info.encrypted_data = data_blob_talloc_zero(tmp_ctx, enc_len); + if (pac_cred_info.encrypted_data.data == NULL) { + DBG_ERR("Out of memory\n"); + code = ENOMEM; + goto out; + } + + cred_ndr_crypt.ciphertext = smb_krb5_data_from_blob(pac_cred_info.encrypted_data); + + code = krb5_k_encrypt(context, + cred_key, + KRB5_KU_OTHER_ENCRYPTED, + NULL, + &cred_ndr_data, + &cred_ndr_crypt); + krb5_k_free_key(context, cred_key); + if (code != 0) { + krb5err = krb5_get_error_message(context, code); + DBG_WARNING("Failed crypt of cred data: %s\n", krb5err); + krb5_free_error_message(context, krb5err); + goto out; + } + + if (DEBUGLVL(10)) { + NDR_PRINT_DEBUG(PAC_CREDENTIAL_INFO, &pac_cred_info); + } + + ndr_err = ndr_push_struct_blob(cred_info_blob, mem_ctx, &pac_cred_info, + (ndr_push_flags_fn_t)ndr_push_PAC_CREDENTIAL_INFO); + TALLOC_FREE(pac_cred_info.encrypted_data.data); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_WARNING("PAC_CREDENTIAL_INFO (presig) push failed: %s\n", + nt_errstr(nt_status)); + code = KRB5KDC_ERR_SVC_UNAVAILABLE; + goto out; + } + + DBG_DEBUG("Encrypted credential BLOB (len %zu) with alg %"PRId32"\n", + cred_info_blob->length, pac_cred_info.encryption_type); + dump_data_pw("PAC_CREDENTIAL_INFO", + cred_info_blob->data, cred_info_blob->length); + +out: + talloc_free(tmp_ctx); + return code; +#endif /* SAMBA4_USES_HEIMDAL */ +} + + +/** + * @brief Create a PAC with the given blobs (logon, credentials, upn and + * delegation). + * + * @param[in] context The KRB5 context to use. + * + * @param[in] logon_blob Fill the logon info PAC buffer with the given blob, + * use NULL to ignore it. + * + * @param[in] cred_blob Fill the credentials info PAC buffer with the given + * blob, use NULL to ignore it. + * + * @param[in] upn_blob Fill the UPN info PAC buffer with the given blob, use + * NULL to ignore it. + * + * @param[in] deleg_blob Fill the delegation info PAC buffer with the given + * blob, use NULL to ignore it. + * + * @param[in] client_claims_blob Fill the client claims info PAC buffer with the + * given blob, use NULL to ignore it. + * + * @param[in] device_info_blob Fill the device info PAC buffer with the given + * blob, use NULL to ignore it. + * + * @param[in] device_claims_blob Fill the device claims info PAC buffer with the given + * blob, use NULL to ignore it. + * + * @param[in] pac The pac buffer to fill. This should be allocated with + * krb5_pac_init() already. + * + * @returns 0 on success or a corresponding KRB5 error. + */ +krb5_error_code samba_make_krb5_pac(krb5_context context, + const DATA_BLOB *logon_blob, + const DATA_BLOB *cred_blob, + const DATA_BLOB *upn_blob, + const DATA_BLOB *pac_attrs_blob, + const DATA_BLOB *requester_sid_blob, + const DATA_BLOB *deleg_blob, + const DATA_BLOB *client_claims_blob, + const DATA_BLOB *device_info_blob, + const DATA_BLOB *device_claims_blob, + krb5_pac pac) +{ + krb5_data logon_data; + krb5_error_code ret; + char null_byte = '\0'; + krb5_data null_data = smb_krb5_make_data(&null_byte, 0); + + /* The user account may be set not to want the PAC */ + if (logon_blob == NULL) { + return 0; + } + + logon_data = smb_krb5_data_from_blob(*logon_blob); + ret = krb5_pac_add_buffer(context, pac, PAC_TYPE_LOGON_INFO, &logon_data); + if (ret != 0) { + return ret; + } + + if (device_info_blob != NULL) { + krb5_data device_info_data = smb_krb5_data_from_blob(*device_info_blob); + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_DEVICE_INFO, + &device_info_data); + if (ret != 0) { + return ret; + } + } + + if (client_claims_blob != NULL) { + krb5_data client_claims_data; + krb5_data *data = NULL; + + if (client_claims_blob->length != 0) { + client_claims_data = smb_krb5_data_from_blob(*client_claims_blob); + data = &client_claims_data; + } else { + data = &null_data; + } + + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_CLIENT_CLAIMS_INFO, + data); + if (ret != 0) { + return ret; + } + } + + if (device_claims_blob != NULL) { + krb5_data device_claims_data = smb_krb5_data_from_blob(*device_claims_blob); + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_DEVICE_CLAIMS_INFO, + &device_claims_data); + if (ret != 0) { + return ret; + } + } + + if (cred_blob != NULL) { + krb5_data cred_data = smb_krb5_data_from_blob(*cred_blob); + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_CREDENTIAL_INFO, + &cred_data); + if (ret != 0) { + return ret; + } + } + +#ifdef SAMBA4_USES_HEIMDAL + /* + * null_data will be filled by the generic KDC code in the caller + * here we just add it in order to have it before + * PAC_TYPE_UPN_DNS_INFO + * + * Not needed with MIT Kerberos - asn + */ + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_LOGON_NAME, + &null_data); + if (ret != 0) { + return ret; + } +#endif + + if (upn_blob != NULL) { + krb5_data upn_data = smb_krb5_data_from_blob(*upn_blob); + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_UPN_DNS_INFO, + &upn_data); + if (ret != 0) { + return ret; + } + } + + if (pac_attrs_blob != NULL) { + krb5_data pac_attrs_data = smb_krb5_data_from_blob(*pac_attrs_blob); + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_ATTRIBUTES_INFO, + &pac_attrs_data); + if (ret != 0) { + return ret; + } + } + + if (requester_sid_blob != NULL) { + krb5_data requester_sid_data = smb_krb5_data_from_blob(*requester_sid_blob); + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_REQUESTER_SID, + &requester_sid_data); + if (ret != 0) { + return ret; + } + } + + if (deleg_blob != NULL) { + krb5_data deleg_data = smb_krb5_data_from_blob(*deleg_blob); + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_CONSTRAINED_DELEGATION, + &deleg_data); + if (ret != 0) { + return ret; + } + } + + return ret; +} + +bool samba_princ_needs_pac(const struct samba_kdc_entry *skdc_entry) +{ + + uint32_t userAccountControl; + + /* The service account may be set not to want the PAC */ + userAccountControl = ldb_msg_find_attr_as_uint(skdc_entry->msg, "userAccountControl", 0); + if (userAccountControl & UF_NO_AUTH_DATA_REQUIRED) { + return false; + } + + return true; +} + +static krb5_error_code samba_client_requested_pac(krb5_context context, + const krb5_const_pac pac, + TALLOC_CTX *mem_ctx, + bool *requested_pac) +{ + enum ndr_err_code ndr_err; + krb5_data k5pac_attrs_in; + DATA_BLOB pac_attrs_in; + union PAC_INFO pac_attrs; + krb5_error_code ret; + + *requested_pac = true; + + ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_ATTRIBUTES_INFO, + &k5pac_attrs_in); + if (ret != 0) { + return ret == ENOENT ? 0 : ret; + } + + pac_attrs_in = data_blob_const(k5pac_attrs_in.data, + k5pac_attrs_in.length); + + ndr_err = ndr_pull_union_blob(&pac_attrs_in, mem_ctx, &pac_attrs, + PAC_TYPE_ATTRIBUTES_INFO, + (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO); + smb_krb5_free_data_contents(context, &k5pac_attrs_in); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_ERR("can't parse the PAC ATTRIBUTES_INFO: %s\n", nt_errstr(nt_status)); + return map_errno_from_nt_status(nt_status); + } + + if (pac_attrs.attributes_info.flags & (PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY + | PAC_ATTRIBUTE_FLAG_PAC_WAS_REQUESTED)) { + *requested_pac = true; + } else { + *requested_pac = false; + } + + return 0; +} + +/* Was the krbtgt in this DB (ie, should we check the incoming signature) and was it an RODC */ +krb5_error_code samba_krbtgt_is_in_db(const struct samba_kdc_entry *p, + bool *is_in_db, + bool *is_trusted) +{ + NTSTATUS status; + krb5_error_code ret; + int rodc_krbtgt_number, trust_direction; + struct dom_sid sid; + uint32_t rid; + + trust_direction = ldb_msg_find_attr_as_int(p->msg, "trustDirection", 0); + + if (trust_direction != 0) { + /* Domain trust - we cannot check the sig, but we trust it for a correct PAC + + This is exactly where we should flag for SID + validation when we do inter-forest trusts + */ + *is_trusted = true; + *is_in_db = false; + return 0; + } + + /* The lack of password controls etc applies to krbtgt by + * virtue of being that particular RID */ + ret = samdb_result_dom_sid_buf(p->msg, "objectSid", &sid); + if (ret) { + return ret; + } + + status = dom_sid_split_rid(NULL, &sid, NULL, &rid); + if (!NT_STATUS_IS_OK(status)) { + return map_errno_from_nt_status(status); + } + + rodc_krbtgt_number = ldb_msg_find_attr_as_int(p->msg, "msDS-SecondaryKrbTgtNumber", -1); + + if (p->kdc_db_ctx->my_krbtgt_number == 0) { + if (rid == DOMAIN_RID_KRBTGT) { + *is_trusted = true; + *is_in_db = true; + return 0; + } else if (rodc_krbtgt_number != -1) { + *is_in_db = true; + *is_trusted = false; + return 0; + } + } else if ((rid != DOMAIN_RID_KRBTGT) && (rodc_krbtgt_number == p->kdc_db_ctx->my_krbtgt_number)) { + *is_trusted = true; + *is_in_db = true; + return 0; + } else if (rid == DOMAIN_RID_KRBTGT) { + /* krbtgt viewed from an RODC */ + *is_trusted = true; + *is_in_db = false; + return 0; + } + + /* Another RODC */ + *is_trusted = false; + *is_in_db = false; + return 0; +} + +/* + * Because the KDC does not limit protocol transition, two new well-known SIDs + * were introduced to give this control to the resource administrator. These + * SIDs identify whether protocol transition has occurred, and can be used with + * standard access control lists to grant or limit access as needed. + * + * https://docs.microsoft.com/en-us/windows-server/security/kerberos/kerberos-constrained-delegation-overview + */ +NTSTATUS samba_kdc_add_asserted_identity(enum samba_asserted_identity ai, + struct auth_user_info_dc *user_info_dc) +{ + const struct dom_sid *ai_sid = NULL; + + switch (ai) { + case SAMBA_ASSERTED_IDENTITY_SERVICE: + ai_sid = &global_sid_Asserted_Identity_Service; + break; + case SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY: + ai_sid = &global_sid_Asserted_Identity_Authentication_Authority; + break; + case SAMBA_ASSERTED_IDENTITY_IGNORE: + return NT_STATUS_OK; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + return add_sid_to_array_attrs_unique( + user_info_dc, + ai_sid, + SE_GROUP_DEFAULT_FLAGS, + &user_info_dc->sids, + &user_info_dc->num_sids); +} + +NTSTATUS samba_kdc_add_claims_valid(struct auth_user_info_dc *user_info_dc) +{ + return add_sid_to_array_attrs_unique( + user_info_dc, + &global_sid_Claims_Valid, + SE_GROUP_DEFAULT_FLAGS, + &user_info_dc->sids, + &user_info_dc->num_sids); +} + +static NTSTATUS samba_kdc_add_compounded_auth(struct auth_user_info_dc *user_info_dc) +{ + return add_sid_to_array_attrs_unique( + user_info_dc, + &global_sid_Compounded_Authentication, + SE_GROUP_DEFAULT_FLAGS, + &user_info_dc->sids, + &user_info_dc->num_sids); +} + +bool samba_kdc_entry_is_trust(const struct samba_kdc_entry *entry) +{ + return entry != NULL && entry->is_trust; +} + +/* + * Return true if this entry has an associated PAC issued or signed by a KDC + * that our KDC trusts. We trust the main krbtgt account, but we don’t trust any + * RODC krbtgt besides ourselves. + */ +bool samba_krb5_pac_is_trusted(const struct samba_kdc_entry_pac pac) +{ + if (pac.pac == NULL) { + return false; + } + +#ifdef HAVE_KRB5_PAC_IS_TRUSTED /* Heimdal */ + return krb5_pac_is_trusted(pac.pac); +#else /* MIT */ + return pac.pac_is_trusted; +#endif /* HAVE_KRB5_PAC_IS_TRUSTED */ +} + +#ifdef HAVE_KRB5_PAC_IS_TRUSTED /* Heimdal */ +struct samba_kdc_entry_pac samba_kdc_entry_pac(krb5_const_pac pac, + struct samba_kdc_entry *entry, + bool is_from_trust) +{ + return (struct samba_kdc_entry_pac) { + .entry = entry, + .pac = pac, + .is_from_trust = is_from_trust, + }; +} +#else /* MIT */ +struct samba_kdc_entry_pac samba_kdc_entry_pac_from_trusted(krb5_const_pac pac, + struct samba_kdc_entry *entry, + bool is_from_trust, + bool is_trusted) +{ + return (struct samba_kdc_entry_pac) { + .entry = entry, + .pac = pac, + .is_from_trust = is_from_trust, + .pac_is_trusted = is_trusted, + }; +} +#endif /* HAVE_KRB5_PAC_IS_TRUSTED */ + +static bool samba_kdc_entry_pac_issued_by_trust(const struct samba_kdc_entry_pac entry) +{ + return entry.pac != NULL && entry.is_from_trust; +} + +NTSTATUS samba_kdc_get_logon_info_blob(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *user_info_dc, + const enum auth_group_inclusion group_inclusion, + DATA_BLOB **_logon_info_blob) +{ + DATA_BLOB *logon_blob = NULL; + NTSTATUS nt_status; + + *_logon_info_blob = NULL; + + logon_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (logon_blob == NULL) { + return NT_STATUS_NO_MEMORY; + } + + nt_status = samba_get_logon_info_pac_blob(logon_blob, + user_info_dc, + NULL, + group_inclusion, + logon_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("Building PAC LOGON INFO failed: %s\n", + nt_errstr(nt_status)); + talloc_free(logon_blob); + return nt_status; + } + + *_logon_info_blob = logon_blob; + + return NT_STATUS_OK; +} + +NTSTATUS samba_kdc_get_cred_ndr_blob(TALLOC_CTX *mem_ctx, + const struct samba_kdc_entry *p, + DATA_BLOB **_cred_ndr_blob) +{ + DATA_BLOB *cred_blob = NULL; + NTSTATUS nt_status; + + SMB_ASSERT(_cred_ndr_blob != NULL); + + *_cred_ndr_blob = NULL; + + cred_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (cred_blob == NULL) { + return NT_STATUS_NO_MEMORY; + } + + nt_status = samba_get_cred_info_ndr_blob(cred_blob, + p->msg, + cred_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("Building PAC CRED INFO failed: %s\n", + nt_errstr(nt_status)); + talloc_free(cred_blob); + return nt_status; + } + + *_cred_ndr_blob = cred_blob; + + return NT_STATUS_OK; +} + +NTSTATUS samba_kdc_get_upn_info_blob(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *user_info_dc, + DATA_BLOB **_upn_info_blob) +{ + DATA_BLOB *upn_blob = NULL; + NTSTATUS nt_status; + + *_upn_info_blob = NULL; + + upn_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (upn_blob == NULL) { + return NT_STATUS_NO_MEMORY; + } + + nt_status = samba_get_upn_info_pac_blob(upn_blob, + user_info_dc, + upn_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("Building PAC UPN INFO failed: %s\n", + nt_errstr(nt_status)); + talloc_free(upn_blob); + return nt_status; + } + + *_upn_info_blob = upn_blob; + + return NT_STATUS_OK; +} + +NTSTATUS samba_kdc_get_pac_attrs_blob(TALLOC_CTX *mem_ctx, + uint64_t pac_attributes, + DATA_BLOB **_pac_attrs_blob) +{ + DATA_BLOB *pac_attrs_blob = NULL; + union PAC_INFO pac_attrs = {}; + enum ndr_err_code ndr_err; + NTSTATUS nt_status; + + SMB_ASSERT(_pac_attrs_blob != NULL); + + *_pac_attrs_blob = NULL; + + pac_attrs_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (pac_attrs_blob == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* Set the length of the flags in bits. */ + pac_attrs.attributes_info.flags_length = 2; + pac_attrs.attributes_info.flags = pac_attributes; + + ndr_err = ndr_push_union_blob(pac_attrs_blob, pac_attrs_blob, &pac_attrs, + PAC_TYPE_ATTRIBUTES_INFO, + (ndr_push_flags_fn_t)ndr_push_PAC_INFO); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_WARNING("PAC ATTRIBUTES_INFO (presig) push failed: %s\n", + nt_errstr(nt_status)); + DBG_ERR("Building PAC ATTRIBUTES failed: %s\n", + nt_errstr(nt_status)); + + talloc_free(pac_attrs_blob); + return nt_status; + } + + *_pac_attrs_blob = pac_attrs_blob; + + return NT_STATUS_OK; +} + +NTSTATUS samba_kdc_get_requester_sid_blob(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *user_info_dc, + DATA_BLOB **_requester_sid_blob) +{ + DATA_BLOB *requester_sid_blob = NULL; + NTSTATUS nt_status; + + SMB_ASSERT(_requester_sid_blob != NULL); + + *_requester_sid_blob = NULL; + + requester_sid_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (requester_sid_blob == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (user_info_dc->num_sids > 0) { + union PAC_INFO pac_requester_sid = {}; + enum ndr_err_code ndr_err; + + pac_requester_sid.requester_sid.sid = user_info_dc->sids[PRIMARY_USER_SID_INDEX].sid; + + ndr_err = ndr_push_union_blob(requester_sid_blob, requester_sid_blob, + &pac_requester_sid, + PAC_TYPE_REQUESTER_SID, + (ndr_push_flags_fn_t)ndr_push_PAC_INFO); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_WARNING("PAC_REQUESTER_SID (presig) push failed: %s\n", + nt_errstr(nt_status)); + DBG_ERR("Building PAC REQUESTER SID failed: %s\n", + nt_errstr(nt_status)); + + talloc_free(requester_sid_blob); + return nt_status; + } + } + + *_requester_sid_blob = requester_sid_blob; + + return NT_STATUS_OK; +} + +NTSTATUS samba_kdc_get_claims_blob(TALLOC_CTX *mem_ctx, + struct samba_kdc_entry *p, + const DATA_BLOB **_claims_blob) +{ + DATA_BLOB *claims_blob = NULL; + struct claims_data *claims_data = NULL; + NTSTATUS nt_status; + int ret; + + SMB_ASSERT(_claims_blob != NULL); + + *_claims_blob = NULL; + + claims_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (claims_blob == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ret = samba_kdc_get_claims_data_from_db(p->kdc_db_ctx->samdb, + p, + &claims_data); + if (ret != LDB_SUCCESS) { + nt_status = dsdb_ldb_err_to_ntstatus(ret); + DBG_ERR("Building claims failed: %s\n", + nt_errstr(nt_status)); + talloc_free(claims_blob); + return nt_status; + } + + nt_status = claims_data_encoded_claims_set(claims_blob, + claims_data, + claims_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(claims_blob); + return nt_status; + } + + *_claims_blob = claims_blob; + + return NT_STATUS_OK; +} + +krb5_error_code samba_kdc_get_user_info_from_db(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct samba_kdc_entry *entry, + const struct ldb_message *msg, + const struct auth_user_info_dc **info_out) +{ + NTSTATUS nt_status; + + if (samdb == NULL) { + return EINVAL; + } + + if (msg == NULL) { + return EINVAL; + } + + if (info_out == NULL) { + return EINVAL; + } + + if (entry == NULL) { + return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; + } + + *info_out = NULL; + + if (entry->info_from_db == NULL) { + struct auth_user_info_dc *info_from_db = NULL; + struct loadparm_context *lp_ctx = entry->kdc_db_ctx->lp_ctx; + + nt_status = authsam_make_user_info_dc(entry, + samdb, + lpcfg_netbios_name(lp_ctx), + lpcfg_sam_name(lp_ctx), + lpcfg_sam_dnsname(lp_ctx), + entry->realm_dn, + msg, + data_blob_null, + data_blob_null, + &info_from_db); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("Getting user info for PAC failed: %s\n", + nt_errstr(nt_status)); + /* NT_STATUS_OBJECT_NAME_NOT_FOUND is mapped to ENOENT. */ + return map_errno_from_nt_status(nt_status); + } + + entry->info_from_db = info_from_db; + } + + *info_out = entry->info_from_db; + + return 0; +} + +/* + * Check whether a PAC contains the Authentication Authority Asserted Identity + * SID. + */ +static krb5_error_code samba_kdc_pac_contains_asserted_identity( + krb5_context context, + const struct samba_kdc_entry_pac entry, + bool *contains_out) +{ + TALLOC_CTX *frame = NULL; + struct auth_user_info_dc *info = NULL; + krb5_error_code ret = 0; + + if (contains_out == NULL) { + ret = EINVAL; + goto out; + } + *contains_out = false; + + frame = talloc_stackframe(); + + /* + * Extract our info from the PAC. This does a bit of unnecessary work, + * setting up fields we don’t care about — we only want the SIDs. + */ + ret = kerberos_pac_to_user_info_dc(frame, + entry.pac, + context, + &info, + AUTH_EXCLUDE_RESOURCE_GROUPS, + NULL /* pac_srv_sig */, + NULL /* pac_kdc_sig */, + /* Ignore the resource groups. */ + NULL /* resource_groups */); + if (ret) { + const char *krb5err = krb5_get_error_message(context, ret); + DBG_ERR("kerberos_pac_to_user_info_dc failed: %s\n", + krb5err != NULL ? krb5err : "?"); + krb5_free_error_message(context, krb5err); + + goto out; + } + + /* Determine whether the PAC contains the Asserted Identity SID. */ + *contains_out = sid_attrs_contains_sid( + info->sids, + info->num_sids, + &global_sid_Asserted_Identity_Authentication_Authority); + +out: + talloc_free(frame); + return ret; +} + +static krb5_error_code samba_kdc_get_user_info_from_pac(TALLOC_CTX *mem_ctx, + krb5_context context, + struct ldb_context *samdb, + const struct samba_kdc_entry_pac entry, + const struct auth_user_info_dc **info_out, + const struct PAC_DOMAIN_GROUP_MEMBERSHIP **resource_groups_out) +{ + TALLOC_CTX *frame = NULL; + struct auth_user_info_dc *info = NULL; + struct PAC_DOMAIN_GROUP_MEMBERSHIP *resource_groups = NULL; + krb5_error_code ret = 0; + NTSTATUS nt_status; + + if (samdb == NULL) { + ret = EINVAL; + goto out; + } + + if (!samba_krb5_pac_is_trusted(entry)) { + ret = EINVAL; + goto out; + } + + if (info_out == NULL) { + ret = EINVAL; + goto out; + } + + *info_out = NULL; + if (resource_groups_out != NULL) { + *resource_groups_out = NULL; + } + + if (entry.entry == NULL || entry.entry->info_from_pac == NULL) { + frame = talloc_stackframe(); + + ret = kerberos_pac_to_user_info_dc(frame, + entry.pac, + context, + &info, + AUTH_EXCLUDE_RESOURCE_GROUPS, + NULL, + NULL, + &resource_groups); + if (ret) { + const char *krb5err = krb5_get_error_message(context, ret); + DBG_ERR("kerberos_pac_to_user_info_dc failed: %s\n", + krb5err != NULL ? krb5err : "?"); + krb5_free_error_message(context, krb5err); + + goto out; + } + + /* + * We need to expand group memberships within our local domain, + * as the token might be generated by a trusted domain. + */ + nt_status = authsam_update_user_info_dc(frame, + samdb, + info); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("authsam_update_user_info_dc failed: %s\n", + nt_errstr(nt_status)); + + ret = map_errno_from_nt_status(nt_status); + goto out; + } + + if (entry.entry != NULL) { + entry.entry->info_from_pac = talloc_steal(entry.entry, info); + entry.entry->resource_groups_from_pac = talloc_steal(entry.entry, resource_groups); + } + } + + + if (entry.entry != NULL) { + /* Note: the caller does not own this! */ + *info_out = entry.entry->info_from_pac; + + if (resource_groups_out != NULL) { + /* Note: the caller does not own this! */ + *resource_groups_out = entry.entry->resource_groups_from_pac; + } + } else { + *info_out = talloc_steal(mem_ctx, info); + + if (resource_groups_out != NULL) { + *resource_groups_out = talloc_steal(mem_ctx, resource_groups); + } + } + +out: + talloc_free(frame); + return ret; +} + +krb5_error_code samba_kdc_get_user_info_dc(TALLOC_CTX *mem_ctx, + krb5_context context, + struct ldb_context *samdb, + const struct samba_kdc_entry_pac entry, + const struct auth_user_info_dc **info_out, + const struct PAC_DOMAIN_GROUP_MEMBERSHIP **resource_groups_out) +{ + const struct auth_user_info_dc *info = NULL; + struct auth_user_info_dc *info_shallow_copy = NULL; + bool pac_contains_asserted_identity = false; + krb5_error_code ret = 0; + NTSTATUS nt_status; + + *info_out = NULL; + if (resource_groups_out != NULL) { + *resource_groups_out = NULL; + } + + if (samba_krb5_pac_is_trusted(entry)) { + return samba_kdc_get_user_info_from_pac(mem_ctx, + context, + samdb, + entry, + info_out, + resource_groups_out); + } + + if (entry.entry == NULL) { + return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; + } + + /* + * In this case the RWDC discards the PAC an RODC generated. + * Windows adds the asserted_identity in this case too. + * + * Note that SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION + * generates KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN. + * So we can always use + * SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY + * here. + */ + ret = samba_kdc_get_user_info_from_db(mem_ctx, + samdb, + entry.entry, + entry.entry->msg, + &info); + if (ret) { + const char *krb5err = krb5_get_error_message(context, ret); + DBG_ERR("samba_kdc_get_user_info_from_db: %s\n", + krb5err != NULL ? krb5err : "?"); + krb5_free_error_message(context, krb5err); + + return KRB5KDC_ERR_TGT_REVOKED; + } + + /* Make a shallow copy of the user_info_dc structure. */ + nt_status = authsam_shallow_copy_user_info_dc(mem_ctx, + info, + &info_shallow_copy); + info = NULL; + + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("Failed to allocate user_info_dc SIDs: %s\n", + nt_errstr(nt_status)); + return map_errno_from_nt_status(nt_status); + } + + /* Determine whether the PAC contains the Asserted Identity SID. */ + ret = samba_kdc_pac_contains_asserted_identity( + context, entry, &pac_contains_asserted_identity); + if (ret) { + return ret; + } + + if (pac_contains_asserted_identity) { + nt_status = samba_kdc_add_asserted_identity( + SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY, + info_shallow_copy); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("Failed to add asserted identity: %s\n", + nt_errstr(nt_status)); + TALLOC_FREE(info_shallow_copy); + return KRB5KDC_ERR_TGT_REVOKED; + } + } + + nt_status = samba_kdc_add_claims_valid(info_shallow_copy); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("Failed to add Claims Valid: %s\n", + nt_errstr(nt_status)); + TALLOC_FREE(info_shallow_copy); + return KRB5KDC_ERR_TGT_REVOKED; + } + + *info_out = info_shallow_copy; + + return 0; +} + +static NTSTATUS samba_kdc_update_delegation_info_blob(TALLOC_CTX *mem_ctx, + krb5_context context, + const krb5_const_pac pac, + const krb5_const_principal server_principal, + const krb5_const_principal proxy_principal, + DATA_BLOB *new_blob) +{ + krb5_data old_data = {}; + DATA_BLOB old_blob; + krb5_error_code ret; + NTSTATUS nt_status = NT_STATUS_OK; + enum ndr_err_code ndr_err; + union PAC_INFO info = {}; + struct PAC_CONSTRAINED_DELEGATION _d = {}; + struct PAC_CONSTRAINED_DELEGATION *d = NULL; + char *server = NULL; + char *proxy = NULL; + uint32_t i; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + + if (tmp_ctx == NULL) { + nt_status = NT_STATUS_NO_MEMORY; + goto out; + } + + ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_CONSTRAINED_DELEGATION, &old_data); + if (ret == ENOENT) { + /* OK. */ + } else if (ret) { + nt_status = NT_STATUS_UNSUCCESSFUL; + goto out; + } + + old_blob.length = old_data.length; + old_blob.data = (uint8_t *)old_data.data; + + if (old_blob.length > 0) { + ndr_err = ndr_pull_union_blob(&old_blob, tmp_ctx, + &info, PAC_TYPE_CONSTRAINED_DELEGATION, + (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + smb_krb5_free_data_contents(context, &old_data); + nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_ERR("can't parse the PAC LOGON_INFO: %s\n", nt_errstr(nt_status)); + goto out; + } + } else { + info.constrained_delegation.info = &_d; + } + smb_krb5_free_data_contents(context, &old_data); + + ret = krb5_unparse_name_flags(context, server_principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, &server); + if (ret) { + nt_status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + + ret = krb5_unparse_name(context, proxy_principal, &proxy); + if (ret) { + SAFE_FREE(server); + nt_status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + + d = info.constrained_delegation.info; + i = d->num_transited_services; + d->proxy_target.string = server; + d->transited_services = talloc_realloc(mem_ctx, d->transited_services, + struct lsa_String, i + 1); + if (d->transited_services == NULL) { + SAFE_FREE(server); + SAFE_FREE(proxy); + nt_status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + d->transited_services[i].string = proxy; + d->num_transited_services = i + 1; + + ndr_err = ndr_push_union_blob(new_blob, mem_ctx, + &info, PAC_TYPE_CONSTRAINED_DELEGATION, + (ndr_push_flags_fn_t)ndr_push_PAC_INFO); + SAFE_FREE(server); + SAFE_FREE(proxy); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + smb_krb5_free_data_contents(context, &old_data); + nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_ERR("can't parse the PAC LOGON_INFO: %s\n", nt_errstr(nt_status)); + goto out; + } + +out: + talloc_free(tmp_ctx); + return nt_status; +} + +/* function to map policy errors */ +krb5_error_code samba_kdc_map_policy_err(NTSTATUS nt_status) +{ + krb5_error_code ret; + + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_PASSWORD_MUST_CHANGE)) + ret = KRB5KDC_ERR_KEY_EXP; + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_PASSWORD_EXPIRED)) + ret = KRB5KDC_ERR_KEY_EXP; + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_EXPIRED)) + ret = KRB5KDC_ERR_CLIENT_REVOKED; + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_DISABLED)) + ret = KRB5KDC_ERR_CLIENT_REVOKED; + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_LOGON_HOURS)) + ret = KRB5KDC_ERR_CLIENT_REVOKED; + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_LOCKED_OUT)) + ret = KRB5KDC_ERR_CLIENT_REVOKED; + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_WORKSTATION)) + ret = KRB5KDC_ERR_POLICY; + else + ret = KRB5KDC_ERR_POLICY; + + return ret; +} + +/* Given a kdc entry, consult the account_ok routine in auth/auth_sam.c + * for consistency */ +NTSTATUS samba_kdc_check_client_access(struct samba_kdc_entry *kdc_entry, + const char *client_name, + const char *workstation, + bool password_change) +{ + TALLOC_CTX *tmp_ctx; + NTSTATUS nt_status; + + tmp_ctx = talloc_named(NULL, 0, "samba_kdc_check_client_access"); + if (!tmp_ctx) { + return NT_STATUS_NO_MEMORY; + } + + /* we allow all kinds of trusts here */ + nt_status = authsam_account_ok(tmp_ctx, + kdc_entry->kdc_db_ctx->samdb, + MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT | + MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT, + kdc_entry->realm_dn, kdc_entry->msg, + workstation, client_name, + true, password_change); + + kdc_entry->reject_status = nt_status; + talloc_free(tmp_ctx); + return nt_status; +} + +static krb5_error_code samba_get_requester_sid(TALLOC_CTX *mem_ctx, + krb5_const_pac pac, + krb5_context context, + struct dom_sid *sid) +{ + NTSTATUS nt_status; + enum ndr_err_code ndr_err; + krb5_error_code ret = 0; + + DATA_BLOB pac_requester_sid_in; + krb5_data k5pac_requester_sid_in; + + union PAC_INFO info; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto out; + } + + ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_REQUESTER_SID, + &k5pac_requester_sid_in); + if (ret != 0) { + goto out; + } + + pac_requester_sid_in = data_blob_const(k5pac_requester_sid_in.data, + k5pac_requester_sid_in.length); + + ndr_err = ndr_pull_union_blob(&pac_requester_sid_in, tmp_ctx, &info, + PAC_TYPE_REQUESTER_SID, + (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO); + smb_krb5_free_data_contents(context, &k5pac_requester_sid_in); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_ERR("can't parse the PAC REQUESTER_SID: %s\n", nt_errstr(nt_status)); + ret = map_errno_from_nt_status(nt_status); + goto out; + } + + *sid = info.requester_sid.sid; + +out: + talloc_free(tmp_ctx); + return ret; +} + +/* Does a parse and SID check, but no crypto. */ +static krb5_error_code samba_kdc_validate_pac_blob( + krb5_context context, + const struct samba_kdc_entry_pac client) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct auth_user_info_dc *pac_user_info = NULL; + struct dom_sid client_sid; + struct dom_sid pac_sid; + krb5_error_code code; + bool ok; + + /* + * First, try to get the SID from the requester SID buffer in the PAC. + */ + code = samba_get_requester_sid(frame, client.pac, context, &pac_sid); + + if (code == ENOENT) { + /* + * If the requester SID buffer isn't present, fall back to the + * SID in the LOGON_INFO PAC buffer. + */ + code = kerberos_pac_to_user_info_dc(frame, + client.pac, + context, + &pac_user_info, + AUTH_EXCLUDE_RESOURCE_GROUPS, + NULL, + NULL, + NULL); + if (code != 0) { + goto out; + } + + if (pac_user_info->num_sids == 0) { + code = EINVAL; + goto out; + } + + pac_sid = pac_user_info->sids[PRIMARY_USER_SID_INDEX].sid; + } else if (code != 0) { + goto out; + } + + code = samdb_result_dom_sid_buf(client.entry->msg, + "objectSid", + &client_sid); + if (code) { + goto out; + } + + ok = dom_sid_equal(&pac_sid, &client_sid); + if (!ok) { + struct dom_sid_buf buf1; + struct dom_sid_buf buf2; + + DBG_ERR("SID mismatch between PAC and looked up client: " + "PAC[%s] != CLI[%s]\n", + dom_sid_str_buf(&pac_sid, &buf1), + dom_sid_str_buf(&client_sid, &buf2)); + code = KRB5KDC_ERR_TGT_REVOKED; + goto out; + } + + code = 0; +out: + TALLOC_FREE(frame); + return code; +} + + +/* + * In the RODC case, to confirm that the returned user is permitted to + * be replicated to the KDC (krbgtgt_xxx user) represented by *rodc + */ +static WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_object_sids, + const struct dom_sid *object_sids, + const struct samba_kdc_entry *rodc, + const struct samba_kdc_entry *object) +{ + int ret; + WERROR werr; + TALLOC_CTX *frame = talloc_stackframe(); + const char *rodc_attrs[] = { "msDS-KrbTgtLink", + "msDS-NeverRevealGroup", + "msDS-RevealOnDemandGroup", + "userAccountControl", + "objectSid", + NULL }; + struct ldb_result *rodc_machine_account = NULL; + struct ldb_dn *rodc_machine_account_dn = samdb_result_dn(rodc->kdc_db_ctx->samdb, + frame, + rodc->msg, + "msDS-KrbTgtLinkBL", + NULL); + const struct dom_sid *rodc_machine_account_sid = NULL; + + if (rodc_machine_account_dn == NULL) { + DBG_ERR("krbtgt account %s has no msDS-KrbTgtLinkBL to find RODC machine account for allow/deny list\n", + ldb_dn_get_linearized(rodc->msg->dn)); + TALLOC_FREE(frame); + return WERR_DOMAIN_CONTROLLER_NOT_FOUND; + } + + /* + * Follow the link and get the RODC account (the krbtgt + * account is the krbtgt_XXX account, but the + * msDS-NeverRevealGroup and msDS-RevealOnDemandGroup is on + * the RODC$ account) + * + * We need DSDB_SEARCH_SHOW_EXTENDED_DN as we get a SID lists + * out of the extended DNs + */ + + ret = dsdb_search_dn(rodc->kdc_db_ctx->samdb, + frame, + &rodc_machine_account, + rodc_machine_account_dn, + rodc_attrs, + DSDB_SEARCH_SHOW_EXTENDED_DN); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to fetch RODC machine account %s pointed to by %s to check allow/deny list: %s\n", + ldb_dn_get_linearized(rodc_machine_account_dn), + ldb_dn_get_linearized(rodc->msg->dn), + ldb_errstring(rodc->kdc_db_ctx->samdb)); + TALLOC_FREE(frame); + return WERR_DOMAIN_CONTROLLER_NOT_FOUND; + } + + if (rodc_machine_account->count != 1) { + DBG_ERR("Failed to fetch RODC machine account %s pointed to by %s to check allow/deny list: (%d)\n", + ldb_dn_get_linearized(rodc_machine_account_dn), + ldb_dn_get_linearized(rodc->msg->dn), + rodc_machine_account->count); + TALLOC_FREE(frame); + return WERR_DS_DRA_BAD_DN; + } + + /* if the object SID is equal to the user_sid, allow */ + rodc_machine_account_sid = samdb_result_dom_sid(frame, + rodc_machine_account->msgs[0], + "objectSid"); + if (rodc_machine_account_sid == NULL) { + TALLOC_FREE(frame); + return WERR_DS_DRA_BAD_DN; + } + + werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(rodc->kdc_db_ctx->samdb, + rodc_machine_account_sid, + rodc_machine_account->msgs[0], + object->msg, + num_object_sids, + object_sids); + + TALLOC_FREE(frame); + return werr; +} + +/* + * Perform an access check for the client attempting to authenticate to the + * server. ‘client_info’ must be talloc-allocated so that we can make a + * reference to it. + */ +krb5_error_code samba_kdc_allowed_to_authenticate_to(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct loadparm_context *lp_ctx, + const struct samba_kdc_entry *client, + const struct auth_user_info_dc *client_info, + const struct auth_user_info_dc *device_info, + const struct auth_claims auth_claims, + const struct samba_kdc_entry *server, + struct authn_audit_info **server_audit_info_out, + NTSTATUS *status_out) +{ + krb5_error_code ret = 0; + NTSTATUS status; + _UNUSED_ NTSTATUS _status; + struct dom_sid server_sid = {}; + const struct authn_server_policy *server_policy = server->server_policy; + + if (status_out != NULL) { + *status_out = NT_STATUS_OK; + } + + ret = samdb_result_dom_sid_buf(server->msg, "objectSid", &server_sid); + if (ret) { + /* + * Ignore the return status — we are already in an error path, + * and overwriting the real error code with the audit info + * status is unhelpful. + */ + _status = authn_server_policy_audit_info(mem_ctx, + server_policy, + client_info, + AUTHN_AUDIT_EVENT_OTHER_ERROR, + AUTHN_AUDIT_REASON_NONE, + dsdb_ldb_err_to_ntstatus(ret), + server_audit_info_out); + goto out; + } + + if (dom_sid_equal(&client_info->sids[PRIMARY_USER_SID_INDEX].sid, &server_sid)) { + /* Authenticating to ourselves is always allowed. */ + status = authn_server_policy_audit_info(mem_ctx, + server_policy, + client_info, + AUTHN_AUDIT_EVENT_OK, + AUTHN_AUDIT_REASON_NONE, + NT_STATUS_OK, + server_audit_info_out); + if (!NT_STATUS_IS_OK(status)) { + ret = KRB5KRB_ERR_GENERIC; + } + goto out; + } + + status = authn_policy_authenticate_to_service(mem_ctx, + samdb, + lp_ctx, + AUTHN_POLICY_AUTH_TYPE_KERBEROS, + client_info, + device_info, + auth_claims, + server_policy, + (struct authn_policy_flags) { .force_compounded_authentication = true }, + server_audit_info_out); + if (!NT_STATUS_IS_OK(status)) { + if (status_out != NULL) { + *status_out = status; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_AUTHENTICATION_FIREWALL_FAILED)) { + ret = KRB5KDC_ERR_POLICY; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + ret = KRB5KDC_ERR_POLICY; + } else { + ret = KRB5KRB_ERR_GENERIC; + } + } + +out: + return ret; +} + +static krb5_error_code samba_kdc_add_domain_group_sid(struct PAC_DEVICE_INFO *info, + const struct netr_SidAttr *sid) +{ + uint32_t i; + uint32_t rid; + NTSTATUS status; + + uint32_t domain_group_count = info->domain_group_count; + struct PAC_DOMAIN_GROUP_MEMBERSHIP *domain_group = NULL; + struct samr_RidWithAttribute *rids = NULL; + + for (i = 0; i < domain_group_count; ++i) { + struct PAC_DOMAIN_GROUP_MEMBERSHIP *this_domain_group + = &info->domain_groups[i]; + + if (dom_sid_in_domain(this_domain_group->domain_sid, sid->sid)) { + domain_group = this_domain_group; + break; + } + } + + if (domain_group == NULL) { + struct PAC_DOMAIN_GROUP_MEMBERSHIP *domain_groups = NULL; + + if (domain_group_count == UINT32_MAX) { + return EINVAL; + } + + domain_groups = talloc_realloc( + info, + info->domain_groups, + struct PAC_DOMAIN_GROUP_MEMBERSHIP, + domain_group_count + 1); + if (domain_groups == NULL) { + return ENOMEM; + } + + info->domain_groups = domain_groups; + + domain_group = &info->domain_groups[domain_group_count++]; + *domain_group = (struct PAC_DOMAIN_GROUP_MEMBERSHIP) {}; + + status = dom_sid_split_rid(info->domain_groups, + sid->sid, + &domain_group->domain_sid, + &rid); + if (!NT_STATUS_IS_OK(status)) { + return map_errno_from_nt_status(status); + } + } else { + status = dom_sid_split_rid(NULL, + sid->sid, + NULL, + &rid); + if (!NT_STATUS_IS_OK(status)) { + return map_errno_from_nt_status(status); + } + } + + if (domain_group->groups.count == UINT32_MAX) { + return EINVAL; + } + + rids = talloc_realloc(info->domain_groups, + domain_group->groups.rids, + struct samr_RidWithAttribute, + domain_group->groups.count + 1); + if (rids == NULL) { + return ENOMEM; + } + + domain_group->groups.rids = rids; + + domain_group->groups.rids[domain_group->groups.count] = (struct samr_RidWithAttribute) { + .rid = rid, + .attributes = sid->attributes, + }; + + ++domain_group->groups.count; + + info->domain_group_count = domain_group_count; + + return 0; +} + +static krb5_error_code samba_kdc_make_device_info(TALLOC_CTX *mem_ctx, + const struct netr_SamInfo3 *info3, + struct PAC_DOMAIN_GROUP_MEMBERSHIP *resource_groups, + union PAC_INFO *info) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct PAC_DEVICE_INFO *device_info = NULL; + uint32_t i; + krb5_error_code ret = 0; + + *info = (union PAC_INFO) {}; + + info->device_info.info = NULL; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + device_info = talloc(tmp_ctx, struct PAC_DEVICE_INFO); + if (device_info == NULL) { + ret = ENOMEM; + goto out; + } + + device_info->rid = info3->base.rid; + device_info->primary_gid = info3->base.primary_gid; + device_info->domain_sid = info3->base.domain_sid; + device_info->groups = info3->base.groups; + + device_info->sid_count = 0; + device_info->sids = NULL; + + if (resource_groups != NULL) { + /* + * The account's resource groups all belong to the same domain, + * so we can add them all in one go. + */ + device_info->domain_group_count = 1; + device_info->domain_groups = talloc_move(device_info, &resource_groups); + } else { + device_info->domain_group_count = 0; + device_info->domain_groups = NULL; + } + + for (i = 0; i < info3->sidcount; ++i) { + const struct netr_SidAttr *device_sid = &info3->sids[i]; + + if (dom_sid_has_account_domain(device_sid->sid)) { + ret = samba_kdc_add_domain_group_sid(device_info, device_sid); + if (ret != 0) { + goto out; + } + } else { + device_info->sids = talloc_realloc(device_info, device_info->sids, + struct netr_SidAttr, + device_info->sid_count + 1); + if (device_info->sids == NULL) { + ret = ENOMEM; + goto out; + } + + device_info->sids[device_info->sid_count].sid = dom_sid_dup(device_info->sids, device_sid->sid); + if (device_info->sids[device_info->sid_count].sid == NULL) { + ret = ENOMEM; + goto out; + } + + device_info->sids[device_info->sid_count].attributes = device_sid->attributes; + + ++device_info->sid_count; + } + } + + info->device_info.info = talloc_steal(mem_ctx, device_info); + +out: + talloc_free(tmp_ctx); + return ret; +} + +static krb5_error_code samba_kdc_update_device_info(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + const union PAC_INFO *logon_info, + struct PAC_DEVICE_INFO *device_info) +{ + NTSTATUS nt_status; + struct auth_user_info_dc *device_info_dc = NULL; + union netr_Validation validation; + uint32_t i; + uint32_t num_existing_sids; + + /* + * This does a bit of unnecessary work, setting up fields we don't care + * about -- we only want the SIDs. + */ + validation.sam3 = &logon_info->logon_info.info->info3; + nt_status = make_user_info_dc_netlogon_validation(mem_ctx, "", 3, &validation, + true, /* This user was authenticated */ + &device_info_dc); + if (!NT_STATUS_IS_OK(nt_status)) { + return map_errno_from_nt_status(nt_status); + } + + num_existing_sids = device_info_dc->num_sids; + + /* + * We need to expand group memberships within our local domain, + * as the token might be generated by a trusted domain. + */ + nt_status = authsam_update_user_info_dc(mem_ctx, + samdb, + device_info_dc); + if (!NT_STATUS_IS_OK(nt_status)) { + return map_errno_from_nt_status(nt_status); + } + + for (i = num_existing_sids; i < device_info_dc->num_sids; ++i) { + struct auth_SidAttr *device_sid = &device_info_dc->sids[i]; + const struct netr_SidAttr sid = (struct netr_SidAttr) { + .sid = &device_sid->sid, + .attributes = device_sid->attrs, + }; + + krb5_error_code ret = samba_kdc_add_domain_group_sid(device_info, &sid); + if (ret != 0) { + return ret; + } + } + + return 0; +} + +static krb5_error_code samba_kdc_get_device_info_pac_blob(TALLOC_CTX *mem_ctx, + union PAC_INFO *info, + DATA_BLOB **_device_info_blob) +{ + DATA_BLOB *device_info_blob = NULL; + enum ndr_err_code ndr_err; + + *_device_info_blob = NULL; + + device_info_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (device_info_blob == NULL) { + DBG_ERR("Out of memory\n"); + return ENOMEM; + } + + ndr_err = ndr_push_union_blob(device_info_blob, device_info_blob, + info, PAC_TYPE_DEVICE_INFO, + (ndr_push_flags_fn_t)ndr_push_PAC_INFO); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_WARNING("PAC_DEVICE_INFO (presig) push failed: %s\n", + nt_errstr(nt_status)); + talloc_free(device_info_blob); + return map_errno_from_nt_status(nt_status); + } + + *_device_info_blob = device_info_blob; + + return 0; +} + +static krb5_error_code samba_kdc_create_device_info_blob(TALLOC_CTX *mem_ctx, + krb5_context context, + struct ldb_context *samdb, + const krb5_const_pac device_pac, + DATA_BLOB **device_info_blob) +{ + TALLOC_CTX *frame = NULL; + krb5_data device_logon_info; + krb5_error_code code = EINVAL; + NTSTATUS nt_status; + + union PAC_INFO info; + enum ndr_err_code ndr_err; + DATA_BLOB device_logon_info_blob; + + union PAC_INFO logon_info; + + code = krb5_pac_get_buffer(context, device_pac, + PAC_TYPE_LOGON_INFO, + &device_logon_info); + if (code != 0) { + if (code == ENOENT) { + DBG_ERR("Device PAC is missing LOGON_INFO\n"); + } else { + DBG_ERR("Error getting LOGON_INFO from device PAC\n"); + } + return code; + } + + frame = talloc_stackframe(); + + device_logon_info_blob = data_blob_const(device_logon_info.data, + device_logon_info.length); + + ndr_err = ndr_pull_union_blob(&device_logon_info_blob, frame, &logon_info, + PAC_TYPE_LOGON_INFO, + (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO); + smb_krb5_free_data_contents(context, &device_logon_info); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_ERR("can't parse device PAC LOGON_INFO: %s\n", + nt_errstr(nt_status)); + talloc_free(frame); + return map_errno_from_nt_status(nt_status); + } + + /* + * When creating the device info structure, existing resource groups are + * discarded. + */ + code = samba_kdc_make_device_info(frame, + &logon_info.logon_info.info->info3, + NULL, /* resource_groups */ + &info); + if (code != 0) { + talloc_free(frame); + return code; + } + + code = samba_kdc_update_device_info(frame, + samdb, + &logon_info, + info.device_info.info); + if (code != 0) { + talloc_free(frame); + return code; + } + + code = samba_kdc_get_device_info_pac_blob(mem_ctx, + &info, + device_info_blob); + + talloc_free(frame); + return code; +} + +static krb5_error_code samba_kdc_get_device_info_blob(TALLOC_CTX *mem_ctx, + krb5_context context, + struct ldb_context *samdb, + const struct samba_kdc_entry_pac device, + DATA_BLOB **device_info_blob) +{ + TALLOC_CTX *frame = NULL; + krb5_error_code code = EINVAL; + NTSTATUS nt_status; + + const struct auth_user_info_dc *device_info = NULL; + struct netr_SamInfo3 *info3 = NULL; + struct PAC_DOMAIN_GROUP_MEMBERSHIP *resource_groups = NULL; + + union PAC_INFO info; + + frame = talloc_stackframe(); + + code = samba_kdc_get_user_info_dc(frame, + context, + samdb, + device, + &device_info, + NULL /* resource_groups_out */); + if (code) { + const char *krb5_err = krb5_get_error_message(context, code); + DBG_ERR("samba_kdc_get_user_info_dc failed: %s\n", + krb5_err != NULL ? krb5_err : "<unknown>"); + krb5_free_error_message(context, krb5_err); + + talloc_free(frame); + return KRB5KDC_ERR_TGT_REVOKED; + } + + nt_status = auth_convert_user_info_dc_saminfo3(frame, device_info, + AUTH_INCLUDE_RESOURCE_GROUPS_COMPRESSED, + &info3, + &resource_groups); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_WARNING("Getting Samba info failed: %s\n", + nt_errstr(nt_status)); + talloc_free(frame); + return nt_status_to_krb5(nt_status); + } + + code = samba_kdc_make_device_info(frame, + info3, + resource_groups, + &info); + if (code != 0) { + talloc_free(frame); + return code; + } + + code = samba_kdc_get_device_info_pac_blob(mem_ctx, + &info, + device_info_blob); + + talloc_free(frame); + return code; +} + +/** + * @brief Verify a PAC + * + * @param mem_ctx A talloc memory context + * + * @param context A krb5 context + * + * @param samdb An open samdb connection. + * + * @param flags Bitwise OR'ed flags + * + * @param client The client samba kdc PAC entry. + + * @param krbtgt The krbtgt samba kdc entry. + * + * @return A Kerberos error code. + */ +krb5_error_code samba_kdc_verify_pac(TALLOC_CTX *mem_ctx, + krb5_context context, + struct ldb_context *samdb, + uint32_t flags, + const struct samba_kdc_entry_pac client, + const struct samba_kdc_entry *krbtgt) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct pac_blobs *pac_blobs = NULL; + krb5_error_code code = EINVAL; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + code = ENOMEM; + goto done; + } + + if (client.entry != NULL) { + /* + * Check the objectSID of the client and pac data are the same. + * Does a parse and SID check, but no crypto. + */ + code = samba_kdc_validate_pac_blob(context, client); + if (code != 0) { + goto done; + } + } + + if (!samba_krb5_pac_is_trusted(client)) { + const struct auth_user_info_dc *user_info_dc = NULL; + WERROR werr; + + struct dom_sid *object_sids = NULL; + uint32_t j; + + if (client.entry == NULL) { + code = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; + goto done; + } + + code = samba_kdc_get_user_info_from_db(tmp_ctx, + samdb, + client.entry, + client.entry->msg, + &user_info_dc); + if (code) { + const char *krb5_err = krb5_get_error_message(context, code); + DBG_ERR("Getting user info for PAC failed: %s\n", + krb5_err != NULL ? krb5_err : "<unknown>"); + krb5_free_error_message(context, krb5_err); + + code = KRB5KDC_ERR_TGT_REVOKED; + goto done; + } + + /* + * Check if the SID list in the user_info_dc intersects + * correctly with the RODC allow/deny lists. + */ + object_sids = talloc_array(tmp_ctx, struct dom_sid, user_info_dc->num_sids); + if (object_sids == NULL) { + code = ENOMEM; + goto done; + } + + for (j = 0; j < user_info_dc->num_sids; ++j) { + object_sids[j] = user_info_dc->sids[j].sid; + } + + werr = samba_rodc_confirm_user_is_allowed(user_info_dc->num_sids, + object_sids, + krbtgt, + client.entry); + if (!W_ERROR_IS_OK(werr)) { + code = KRB5KDC_ERR_TGT_REVOKED; + if (W_ERROR_EQUAL(werr, + WERR_DOMAIN_CONTROLLER_NOT_FOUND)) { + code = KRB5KDC_ERR_POLICY; + } + goto done; + } + + /* + * The RODC PAC data isn't trusted for authorization as it may + * be stale. The only thing meaningful we can do with an RODC + * account on a full DC is exchange the RODC TGT for a 'real' + * TGT. + * + * So we match Windows (at least server 2022) and + * don't allow S4U2Self. + * + * https://lists.samba.org/archive/cifs-protocol/2022-April/003673.html + */ + if (flags & SAMBA_KDC_FLAG_PROTOCOL_TRANSITION) { + code = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; + goto done; + } + } + + /* Check the types of the given PAC */ + + code = pac_blobs_from_krb5_pac(tmp_ctx, + context, + client.pac, + &pac_blobs); + if (code != 0) { + goto done; + } + + code = pac_blobs_ensure_exists(pac_blobs, + PAC_TYPE_LOGON_INFO); + if (code != 0) { + goto done; + } + + code = pac_blobs_ensure_exists(pac_blobs, + PAC_TYPE_LOGON_NAME); + if (code != 0) { + goto done; + } + + code = pac_blobs_ensure_exists(pac_blobs, + PAC_TYPE_SRV_CHECKSUM); + if (code != 0) { + goto done; + } + + code = pac_blobs_ensure_exists(pac_blobs, + PAC_TYPE_KDC_CHECKSUM); + if (code != 0) { + goto done; + } + + if (!(flags & SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION)) { + code = pac_blobs_ensure_exists(pac_blobs, + PAC_TYPE_REQUESTER_SID); + if (code != 0) { + code = KRB5KDC_ERR_TGT_REVOKED; + goto done; + } + } + + code = 0; + +done: + talloc_free(tmp_ctx); + + return code; +} + +/** + * @brief Update a PAC + * + * @param mem_ctx A talloc memory context + * + * @param context A krb5 context + * + * @param samdb An open samdb connection. + * + * @param lp_ctx A loadparm context. + * + * @param flags Bitwise OR'ed flags + * + * @param device_pac_is_trusted Whether the device's PAC was issued by a trusted server, + * as opposed to an RODC. + * + * @param client The client samba kdc PAC entry. + * + * @param server_principal The server principal + * + * @param server The server samba kdc entry. + * + * @param delegated_proxy_principal The delegated proxy principal used for + * updating the constrained delegation PAC + * buffer. + * + * @param delegated_proxy The delegated proxy kdc PAC entry. + * + * @param device The computer's samba kdc PAC entry; used for compound + * authentication. + * + * @param new_pac The new already allocated PAC + * + * @return A Kerberos error code. If no PAC should be returned, the code will be + * ENOATTR! + */ +krb5_error_code samba_kdc_update_pac(TALLOC_CTX *mem_ctx, + krb5_context context, + struct ldb_context *samdb, + struct loadparm_context *lp_ctx, + uint32_t flags, + const struct samba_kdc_entry_pac client, + const krb5_const_principal server_principal, + const struct samba_kdc_entry *server, + const krb5_const_principal delegated_proxy_principal, + const struct samba_kdc_entry_pac delegated_proxy, + const struct samba_kdc_entry_pac device, + krb5_pac new_pac, + struct authn_audit_info **server_audit_info_out, + NTSTATUS *status_out) +{ + TALLOC_CTX *tmp_ctx = NULL; + krb5_error_code code = EINVAL; + NTSTATUS nt_status; + DATA_BLOB *pac_blob = NULL; + DATA_BLOB *upn_blob = NULL; + DATA_BLOB *deleg_blob = NULL; + DATA_BLOB *requester_sid_blob = NULL; + const DATA_BLOB *client_claims_blob = NULL; + DATA_BLOB device_claims_blob = {}; + const DATA_BLOB *device_claims_blob_ptr = NULL; + struct auth_claims auth_claims = {}; + DATA_BLOB *device_info_blob = NULL; + bool is_tgs = false; + bool server_restrictions_present = false; + struct pac_blobs *pac_blobs = NULL; + const struct auth_user_info_dc *user_info_dc_const = NULL; + struct auth_user_info_dc *user_info_dc_shallow_copy = NULL; + const struct PAC_DOMAIN_GROUP_MEMBERSHIP *_resource_groups = NULL; + enum auth_group_inclusion group_inclusion; + bool compounded_auth; + size_t i = 0; + + if (server_audit_info_out != NULL) { + *server_audit_info_out = NULL; + } + + if (status_out != NULL) { + *status_out = NT_STATUS_OK; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + code = ENOMEM; + goto done; + } + + { + int result = smb_krb5_principal_is_tgs(context, server_principal); + if (result == -1) { + code = ENOMEM; + goto done; + } + + is_tgs = result; + } + + server_restrictions_present = !is_tgs && authn_policy_restrictions_present(server->server_policy); + + /* Only include resource groups in a service ticket. */ + if (is_tgs) { + group_inclusion = AUTH_EXCLUDE_RESOURCE_GROUPS; + } else if (server->supported_enctypes & KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED) { + group_inclusion = AUTH_INCLUDE_RESOURCE_GROUPS; + } else { + group_inclusion = AUTH_INCLUDE_RESOURCE_GROUPS_COMPRESSED; + } + + compounded_auth = device.entry != NULL && !is_tgs + && server->supported_enctypes & KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED; + + if (compounded_auth || (server_restrictions_present && device.entry != NULL)) { + /* + * [MS-KILE] 3.3.5.7.4 Compound Identity: the client claims from + * the device PAC become the device claims in the new PAC. + */ + code = samba_kdc_get_claims_data(tmp_ctx, + context, + samdb, + device, + &auth_claims.device_claims); + if (code) { + goto done; + } + + if (compounded_auth) { + nt_status = claims_data_encoded_claims_set(tmp_ctx, + auth_claims.device_claims, + &device_claims_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("claims_data_encoded_claims_set failed: %s\n", + nt_errstr(nt_status)); + code = map_errno_from_nt_status(nt_status); + goto done; + } + + device_claims_blob_ptr = &device_claims_blob; + + if (samba_krb5_pac_is_trusted(device)) { + code = samba_kdc_create_device_info_blob(tmp_ctx, + context, + samdb, + device.pac, + &device_info_blob); + if (code != 0) { + goto done; + } + } else { + /* Don't trust an RODC‐issued PAC; regenerate the device info. */ + code = samba_kdc_get_device_info_blob(tmp_ctx, + context, + samdb, + device, + &device_info_blob); + if (code != 0) { + goto done; + } + } + } + } + + if (delegated_proxy_principal != NULL) { + deleg_blob = talloc_zero(tmp_ctx, DATA_BLOB); + if (deleg_blob == NULL) { + code = ENOMEM; + goto done; + } + + nt_status = samba_kdc_update_delegation_info_blob( + deleg_blob, + context, + client.pac, + server_principal, + delegated_proxy_principal, + deleg_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("update delegation info blob failed: %s\n", + nt_errstr(nt_status)); + code = map_errno_from_nt_status(nt_status); + goto done; + } + } + + /* + * If we are creating a TGT, resource groups from our domain are not to + * be put into the PAC. Instead, we take the resource groups directly + * from the original PAC and copy them unmodified into the new one. + */ + code = samba_kdc_get_user_info_dc(tmp_ctx, + context, + samdb, + client, + &user_info_dc_const, + is_tgs ? &_resource_groups : NULL); + if (code != 0) { + const char *err_str = krb5_get_error_message(context, code); + DBG_ERR("samba_kdc_get_user_info_dc failed: %s\n", + err_str != NULL ? err_str : "<unknown>"); + krb5_free_error_message(context, err_str); + + goto done; + } + + /* + * Enforce the AllowedToAuthenticateTo part of an authentication policy, + * if one is present. + */ + if (server_restrictions_present) { + struct samba_kdc_entry_pac auth_entry; + const struct auth_user_info_dc *auth_user_info_dc = NULL; + const struct auth_user_info_dc *device_info = NULL; + + if (delegated_proxy.entry != NULL) { + auth_entry = delegated_proxy; + + code = samba_kdc_get_user_info_dc(tmp_ctx, + context, + samdb, + delegated_proxy, + &auth_user_info_dc, + NULL /* resource_groups_out */); + if (code) { + goto done; + } + } else { + auth_entry = client; + auth_user_info_dc = user_info_dc_const; + } + + /* Fetch the user’s claims. */ + code = samba_kdc_get_claims_data(tmp_ctx, + context, + samdb, + auth_entry, + &auth_claims.user_claims); + if (code) { + goto done; + } + + if (device.entry != NULL) { + code = samba_kdc_get_user_info_dc(tmp_ctx, + context, + samdb, + device, + &device_info, + NULL /* resource_groups_out */); + if (code) { + goto done; + } + } + + /* + * Allocate the audit info and output status on to the parent + * mem_ctx, not the temporary context. + */ + code = samba_kdc_allowed_to_authenticate_to(mem_ctx, + samdb, + lp_ctx, + auth_entry.entry, + auth_user_info_dc, + device_info, + auth_claims, + server, + server_audit_info_out, + status_out); + if (code) { + goto done; + } + } + + if (compounded_auth) { + /* Make a shallow copy of the user_info_dc structure. */ + nt_status = authsam_shallow_copy_user_info_dc(tmp_ctx, + user_info_dc_const, + &user_info_dc_shallow_copy); + user_info_dc_const = NULL; + + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("Failed to copy user_info_dc: %s\n", + nt_errstr(nt_status)); + + code = KRB5KDC_ERR_TGT_REVOKED; + goto done; + } + + nt_status = samba_kdc_add_compounded_auth(user_info_dc_shallow_copy); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("Failed to add Compounded Authentication: %s\n", + nt_errstr(nt_status)); + + code = KRB5KDC_ERR_TGT_REVOKED; + goto done; + } + + /* We can now set back to the const, it will not be modified */ + user_info_dc_const = user_info_dc_shallow_copy; + } + + if (samba_krb5_pac_is_trusted(client)) { + pac_blob = talloc_zero(tmp_ctx, DATA_BLOB); + if (pac_blob == NULL) { + code = ENOMEM; + goto done; + } + + nt_status = samba_get_logon_info_pac_blob(tmp_ctx, + user_info_dc_const, + _resource_groups, + group_inclusion, + pac_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("samba_get_logon_info_pac_blob failed: %s\n", + nt_errstr(nt_status)); + + code = map_errno_from_nt_status(nt_status); + goto done; + } + + /* + * TODO: we need claim translation over trusts, + * for now we just clear them... + */ + if (samba_kdc_entry_pac_issued_by_trust(client)) { + client_claims_blob = &data_blob_null; + } + } else { + nt_status = samba_kdc_get_logon_info_blob(tmp_ctx, + user_info_dc_const, + group_inclusion, + &pac_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("samba_kdc_get_logon_info_blob failed: %s\n", + nt_errstr(nt_status)); + code = KRB5KDC_ERR_TGT_REVOKED; + goto done; + } + + nt_status = samba_kdc_get_upn_info_blob(tmp_ctx, + user_info_dc_const, + &upn_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("samba_kdc_get_upn_info_blob failed: %s\n", + nt_errstr(nt_status)); + code = KRB5KDC_ERR_TGT_REVOKED; + goto done; + } + + if (is_tgs) { + nt_status = samba_kdc_get_requester_sid_blob(tmp_ctx, + user_info_dc_const, + &requester_sid_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("samba_kdc_get_requester_sid_blob failed: %s\n", + nt_errstr(nt_status)); + code = KRB5KDC_ERR_TGT_REVOKED; + goto done; + } + } + + /* Don't trust RODC-issued claims. Regenerate them. */ + nt_status = samba_kdc_get_claims_blob(tmp_ctx, + client.entry, + &client_claims_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("samba_kdc_get_claims_blob failed: %s\n", + nt_errstr(nt_status)); + code = map_errno_from_nt_status(nt_status); + goto done; + } + } + + /* Check the types of the given PAC */ + code = pac_blobs_from_krb5_pac(tmp_ctx, + context, + client.pac, + &pac_blobs); + if (code != 0) { + goto done; + } + + code = pac_blobs_replace_existing(pac_blobs, + PAC_TYPE_LOGON_INFO, + pac_blob); + if (code != 0) { + goto done; + } + +#ifdef SAMBA4_USES_HEIMDAL + /* Not needed with MIT Kerberos */ + code = pac_blobs_replace_existing(pac_blobs, + PAC_TYPE_LOGON_NAME, + &data_blob_null); + if (code != 0) { + goto done; + } + + code = pac_blobs_replace_existing(pac_blobs, + PAC_TYPE_SRV_CHECKSUM, + &data_blob_null); + if (code != 0) { + goto done; + } + + code = pac_blobs_replace_existing(pac_blobs, + PAC_TYPE_KDC_CHECKSUM, + &data_blob_null); + if (code != 0) { + goto done; + } +#endif + + code = pac_blobs_add_blob(pac_blobs, + PAC_TYPE_CONSTRAINED_DELEGATION, + deleg_blob); + if (code != 0) { + goto done; + } + + code = pac_blobs_add_blob(pac_blobs, + PAC_TYPE_UPN_DNS_INFO, + upn_blob); + if (code != 0) { + goto done; + } + + code = pac_blobs_add_blob(pac_blobs, + PAC_TYPE_CLIENT_CLAIMS_INFO, + client_claims_blob); + if (code != 0) { + goto done; + } + + code = pac_blobs_add_blob(pac_blobs, + PAC_TYPE_DEVICE_INFO, + device_info_blob); + if (code != 0) { + goto done; + } + + code = pac_blobs_add_blob(pac_blobs, + PAC_TYPE_DEVICE_CLAIMS_INFO, + device_claims_blob_ptr); + if (code != 0) { + goto done; + } + + if (!samba_krb5_pac_is_trusted(client) || !is_tgs) { + pac_blobs_remove_blob(pac_blobs, + PAC_TYPE_ATTRIBUTES_INFO); + } + + if (!is_tgs) { + pac_blobs_remove_blob(pac_blobs, + PAC_TYPE_REQUESTER_SID); + } + + code = pac_blobs_add_blob(pac_blobs, + PAC_TYPE_REQUESTER_SID, + requester_sid_blob); + if (code != 0) { + goto done; + } + + /* + * The server account may be set not to want the PAC. + * + * While this is wasteful if the above calculations were done + * and now thrown away, this is cleaner as we do any ticket + * signature checking etc always. + * + * UF_NO_AUTH_DATA_REQUIRED is the rare case and most of the + * time (eg not accepting a ticket from the RODC) we do not + * need to re-generate anything anyway. + */ + if (!samba_princ_needs_pac(server)) { + code = ENOATTR; + goto done; + } + + if (samba_krb5_pac_is_trusted(client) && !is_tgs) { + /* + * The client may have requested no PAC when obtaining the + * TGT. + */ + bool requested_pac = false; + + code = samba_client_requested_pac(context, + client.pac, + tmp_ctx, + &requested_pac); + if (code != 0 || !requested_pac) { + if (!requested_pac) { + code = ENOATTR; + } + goto done; + } + } + + for (i = 0; i < pac_blobs->num_types; ++i) { + krb5_data type_data; + const DATA_BLOB *type_blob = pac_blobs->type_blobs[i].data; + uint32_t type = pac_blobs->type_blobs[i].type; + + static char null_byte = '\0'; + const krb5_data null_data = smb_krb5_make_data(&null_byte, 0); + +#ifndef SAMBA4_USES_HEIMDAL + /* Not needed with MIT Kerberos */ + switch(type) { + case PAC_TYPE_LOGON_NAME: + case PAC_TYPE_SRV_CHECKSUM: + case PAC_TYPE_KDC_CHECKSUM: + case PAC_TYPE_FULL_CHECKSUM: + continue; + default: + break; + } +#endif + + if (type_blob != NULL) { + type_data = smb_krb5_data_from_blob(*type_blob); + /* + * Passing a NULL pointer into krb5_pac_add_buffer() is + * not allowed, so pass null_data instead if needed. + */ + code = krb5_pac_add_buffer(context, + new_pac, + type, + (type_data.data != NULL) ? &type_data : &null_data); + if (code != 0) { + goto done; + } + } else if (samba_krb5_pac_is_trusted(client)) { + /* + * Convey the buffer from the original PAC if we can + * trust it. + */ + + code = krb5_pac_get_buffer(context, + client.pac, + type, + &type_data); + if (code != 0) { + goto done; + } + /* + * Passing a NULL pointer into krb5_pac_add_buffer() is + * not allowed, so pass null_data instead if needed. + */ + code = krb5_pac_add_buffer(context, + new_pac, + type, + (type_data.data != NULL) ? &type_data : &null_data); + smb_krb5_free_data_contents(context, &type_data); + if (code != 0) { + goto done; + } + } + } + + code = 0; +done: + TALLOC_FREE(tmp_ctx); + return code; +} + +krb5_error_code samba_kdc_get_claims_data(TALLOC_CTX *mem_ctx, + krb5_context context, + struct ldb_context *samdb, + struct samba_kdc_entry_pac entry, + struct claims_data **claims_data_out) +{ + if (samba_kdc_entry_pac_issued_by_trust(entry)) { + NTSTATUS status; + + /* + * TODO: we need claim translation over trusts; for now we just + * clear them… + */ + status = claims_data_from_encoded_claims_set(mem_ctx, + NULL, + claims_data_out); + if (!NT_STATUS_IS_OK(status)) { + return map_errno_from_nt_status(status); + } + + return 0; + } + + if (samba_krb5_pac_is_trusted(entry)) { + return samba_kdc_get_claims_data_from_pac(mem_ctx, + context, + entry, + claims_data_out); + } + + return samba_kdc_get_claims_data_from_db(samdb, + entry.entry, + claims_data_out); +} + +krb5_error_code samba_kdc_get_claims_data_from_pac(TALLOC_CTX *mem_ctx, + krb5_context context, + struct samba_kdc_entry_pac entry, + struct claims_data **claims_data_out) +{ + TALLOC_CTX *frame = NULL; + krb5_data claims_info = {}; + struct claims_data *claims_data = NULL; + NTSTATUS status = NT_STATUS_OK; + krb5_error_code code; + + if (!samba_krb5_pac_is_trusted(entry)) { + code = EINVAL; + goto out; + } + + if (samba_kdc_entry_pac_issued_by_trust(entry)) { + code = EINVAL; + goto out; + } + + if (claims_data_out == NULL) { + code = EINVAL; + goto out; + } + + *claims_data_out = NULL; + + if (entry.entry != NULL && entry.entry->claims_from_pac_are_initialized) { + /* Note: the caller does not own this! */ + *claims_data_out = entry.entry->claims_from_pac; + return 0; + } + + frame = talloc_stackframe(); + + /* Fetch the claims from the PAC. */ + code = krb5_pac_get_buffer(context, entry.pac, + PAC_TYPE_CLIENT_CLAIMS_INFO, + &claims_info); + if (code == ENOENT) { + /* OK. */ + } else if (code != 0) { + DBG_ERR("Error getting CLIENT_CLAIMS_INFO from PAC\n"); + goto out; + } else if (claims_info.length) { + DATA_BLOB claims_blob = data_blob_const(claims_info.data, + claims_info.length); + + status = claims_data_from_encoded_claims_set(frame, + &claims_blob, + &claims_data); + if (!NT_STATUS_IS_OK(status)) { + code = map_errno_from_nt_status(status); + goto out; + } + } + + if (entry.entry != NULL) { + /* Note: the caller does not own this! */ + entry.entry->claims_from_pac = talloc_steal(entry.entry, + claims_data); + entry.entry->claims_from_pac_are_initialized = true; + } else { + talloc_steal(mem_ctx, claims_data); + } + + *claims_data_out = claims_data; + +out: + smb_krb5_free_data_contents(context, &claims_info); + talloc_free(frame); + return code; +} + +krb5_error_code samba_kdc_get_claims_data_from_db(struct ldb_context *samdb, + struct samba_kdc_entry *entry, + struct claims_data **claims_data_out) +{ + TALLOC_CTX *frame = NULL; + + struct claims_data *claims_data = NULL; + struct CLAIMS_SET *claims_set = NULL; + NTSTATUS status = NT_STATUS_OK; + krb5_error_code code; + + if (samdb == NULL) { + code = EINVAL; + goto out; + } + + if (claims_data_out == NULL) { + code = EINVAL; + goto out; + } + + if (entry == NULL) { + code = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; + goto out; + } + + *claims_data_out = NULL; + + if (entry->claims_from_db_are_initialized) { + /* Note: the caller does not own this! */ + *claims_data_out = entry->claims_from_db; + return 0; + } + + frame = talloc_stackframe(); + + code = get_claims_set_for_principal(samdb, + frame, + entry->msg, + &claims_set); + if (code) { + DBG_ERR("Failed to fetch claims\n"); + goto out; + } + + if (claims_set != NULL) { + status = claims_data_from_claims_set(claims_data, + claims_set, + &claims_data); + if (!NT_STATUS_IS_OK(status)) { + code = map_errno_from_nt_status(status); + goto out; + } + } + + entry->claims_from_db = talloc_steal(entry, + claims_data); + entry->claims_from_db_are_initialized = true; + + /* Note: the caller does not own this! */ + *claims_data_out = entry->claims_from_db; + +out: + talloc_free(frame); + return code; +} + +krb5_error_code samba_kdc_check_device(TALLOC_CTX *mem_ctx, + krb5_context context, + struct ldb_context *samdb, + struct loadparm_context *lp_ctx, + const struct samba_kdc_entry_pac device, + const struct authn_kerberos_client_policy *client_policy, + struct authn_audit_info **client_audit_info_out, + NTSTATUS *status_out) +{ + TALLOC_CTX *frame = NULL; + krb5_error_code code = 0; + NTSTATUS nt_status; + const struct auth_user_info_dc *device_info = NULL; + struct authn_audit_info *client_audit_info = NULL; + struct auth_claims auth_claims = {}; + + if (status_out != NULL) { + *status_out = NT_STATUS_OK; + } + + if (!authn_policy_device_restrictions_present(client_policy)) { + return 0; + } + + if (device.entry == NULL || device.pac == NULL) { + NTSTATUS out_status = NT_STATUS_INVALID_WORKSTATION; + + nt_status = authn_kerberos_client_policy_audit_info(mem_ctx, + client_policy, + NULL /* client_info */, + AUTHN_AUDIT_EVENT_KERBEROS_DEVICE_RESTRICTION, + AUTHN_AUDIT_REASON_FAST_REQUIRED, + out_status, + client_audit_info_out); + if (!NT_STATUS_IS_OK(nt_status)) { + code = KRB5KRB_ERR_GENERIC; + } else if (authn_kerberos_client_policy_is_enforced(client_policy)) { + code = KRB5KDC_ERR_POLICY; + + if (status_out != NULL) { + *status_out = out_status; + } + } else { + /* OK. */ + code = 0; + } + + goto out; + } + + frame = talloc_stackframe(); + + code = samba_kdc_get_user_info_dc(frame, + context, + samdb, + device, + &device_info, + NULL); + if (code) { + goto out; + } + + /* + * The device claims become the *user* claims for the purpose of + * evaluating a conditional ACE expression. + */ + code = samba_kdc_get_claims_data(frame, + context, + samdb, + device, + &auth_claims.user_claims); + if (code) { + goto out; + } + + nt_status = authn_policy_authenticate_from_device(frame, + samdb, + lp_ctx, + device_info, + auth_claims, + client_policy, + &client_audit_info); + if (client_audit_info != NULL) { + *client_audit_info_out = talloc_move(mem_ctx, &client_audit_info); + } + if (!NT_STATUS_IS_OK(nt_status)) { + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_AUTHENTICATION_FIREWALL_FAILED)) { + code = KRB5KDC_ERR_POLICY; + } else { + code = KRB5KRB_ERR_GENERIC; + } + + goto out; + } + +out: + talloc_free(frame); + return code; +} |