diff options
Diffstat (limited to '')
44 files changed, 16191 insertions, 0 deletions
diff --git a/source4/kdc/db-glue.c b/source4/kdc/db-glue.c new file mode 100644 index 0000000..83d5c90 --- /dev/null +++ b/source4/kdc/db-glue.c @@ -0,0 +1,3668 @@ +/* + Unix SMB/CIFS implementation. + + Database Glue between Samba and the KDC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009 + Copyright (C) Simo Sorce <idra@samba.org> 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "auth/auth.h" +#include "auth/auth_sam.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/common/util.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "param/param.h" +#include "param/secrets.h" +#include "../lib/crypto/md4.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include "kdc/sdb.h" +#include "kdc/samba_kdc.h" +#include "kdc/db-glue.h" +#include "kdc/pac-glue.h" +#include "librpc/gen_ndr/ndr_irpc_c.h" +#include "lib/messaging/irpc.h" + +#undef strcasecmp +#undef strncasecmp + +#define SAMBA_KVNO_GET_KRBTGT(kvno) \ + ((uint16_t)(((uint32_t)kvno) >> 16)) + +#define SAMBA_KVNO_GET_VALUE(kvno) \ + ((uint16_t)(((uint32_t)kvno) & 0xFFFF)) + +#define SAMBA_KVNO_AND_KRBTGT(kvno, krbtgt) \ + ((krb5_kvno)((((uint32_t)kvno) & 0xFFFF) | \ + ((((uint32_t)krbtgt) << 16) & 0xFFFF0000))) + +enum trust_direction { + UNKNOWN = 0, + INBOUND = LSA_TRUST_DIRECTION_INBOUND, + OUTBOUND = LSA_TRUST_DIRECTION_OUTBOUND +}; + +static const char *trust_attrs[] = { + "securityIdentifier", + "flatName", + "trustPartner", + "trustAttributes", + "trustDirection", + "trustType", + "msDS-TrustForestTrustInfo", + "trustAuthIncoming", + "trustAuthOutgoing", + "whenCreated", + "msDS-SupportedEncryptionTypes", + NULL +}; + +/* + send a message to the drepl server telling it to initiate a + REPL_SECRET getncchanges extended op to fetch the users secrets + */ +static void auth_sam_trigger_repl_secret(TALLOC_CTX *mem_ctx, + struct imessaging_context *msg_ctx, + struct tevent_context *event_ctx, + struct ldb_dn *user_dn) +{ + struct dcerpc_binding_handle *irpc_handle; + struct drepl_trigger_repl_secret r; + struct tevent_req *req; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return; + } + + irpc_handle = irpc_binding_handle_by_name(tmp_ctx, msg_ctx, + "dreplsrv", + &ndr_table_irpc); + if (irpc_handle == NULL) { + DEBUG(1,(__location__ ": Unable to get binding handle for dreplsrv\n")); + TALLOC_FREE(tmp_ctx); + return; + } + + r.in.user_dn = ldb_dn_get_linearized(user_dn); + + /* + * This seem to rely on the current IRPC implementation, + * which delivers the message in the _send function. + * + * TODO: we need a ONE_WAY IRPC handle and register + * a callback and wait for it to be triggered! + */ + req = dcerpc_drepl_trigger_repl_secret_r_send(tmp_ctx, + event_ctx, + irpc_handle, + &r); + + /* we aren't interested in a reply */ + talloc_free(req); + TALLOC_FREE(tmp_ctx); +} + +static time_t ldb_msg_find_krb5time_ldap_time(struct ldb_message *msg, const char *attr, time_t default_val) +{ + const char *tmp; + const char *gentime; + struct tm tm; + + gentime = ldb_msg_find_attr_as_string(msg, attr, NULL); + if (!gentime) + return default_val; + + tmp = strptime(gentime, "%Y%m%d%H%M%SZ", &tm); + if (tmp == NULL) { + return default_val; + } + + return timegm(&tm); +} + +static struct SDBFlags uf2SDBFlags(krb5_context context, uint32_t userAccountControl, enum samba_kdc_ent_type ent_type) +{ + struct SDBFlags flags = int2SDBFlags(0); + + /* we don't allow kadmin deletes */ + flags.immutable = 1; + + /* mark the principal as invalid to start with */ + flags.invalid = 1; + + flags.renewable = 1; + + /* All accounts are servers, but this may be disabled again in the caller */ + flags.server = 1; + + /* Account types - clear the invalid bit if it turns out to be valid */ + if (userAccountControl & UF_NORMAL_ACCOUNT) { + if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT || ent_type == SAMBA_KDC_ENT_TYPE_ANY) { + flags.client = 1; + } + flags.invalid = 0; + } + + if (userAccountControl & UF_INTERDOMAIN_TRUST_ACCOUNT) { + if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT || ent_type == SAMBA_KDC_ENT_TYPE_ANY) { + flags.client = 1; + } + flags.invalid = 0; + } + if (userAccountControl & UF_WORKSTATION_TRUST_ACCOUNT) { + if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT || ent_type == SAMBA_KDC_ENT_TYPE_ANY) { + flags.client = 1; + } + flags.invalid = 0; + } + if (userAccountControl & UF_SERVER_TRUST_ACCOUNT) { + if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT || ent_type == SAMBA_KDC_ENT_TYPE_ANY) { + flags.client = 1; + } + flags.invalid = 0; + } + + /* Not permitted to act as a client if disabled */ + if (userAccountControl & UF_ACCOUNTDISABLE) { + flags.client = 0; + } + if (userAccountControl & UF_LOCKOUT) { + flags.locked_out = 1; + } +/* + if (userAccountControl & UF_PASSWORD_NOTREQD) { + flags.invalid = 1; + } +*/ +/* + UF_PASSWORD_CANT_CHANGE and UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED are irrelevent +*/ + if (userAccountControl & UF_TEMP_DUPLICATE_ACCOUNT) { + flags.invalid = 1; + } + +/* UF_DONT_EXPIRE_PASSWD and UF_USE_DES_KEY_ONLY handled in samba_kdc_message2entry() */ + +/* + if (userAccountControl & UF_MNS_LOGON_ACCOUNT) { + flags.invalid = 1; + } +*/ + if (userAccountControl & UF_SMARTCARD_REQUIRED) { + flags.require_hwauth = 1; + } + if (userAccountControl & UF_TRUSTED_FOR_DELEGATION) { + flags.ok_as_delegate = 1; + } + if (userAccountControl & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION) { + /* + * this is confusing... + * + * UF_TRUSTED_FOR_DELEGATION + * => ok_as_delegate + * + * and + * + * UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION + * => trusted_for_delegation + */ + flags.trusted_for_delegation = 1; + } + if (!(userAccountControl & UF_NOT_DELEGATED)) { + flags.forwardable = 1; + flags.proxiable = 1; + } + + if (userAccountControl & UF_DONT_REQUIRE_PREAUTH) { + flags.require_preauth = 0; + } else { + flags.require_preauth = 1; + } + + if (userAccountControl & UF_NO_AUTH_DATA_REQUIRED) { + flags.no_auth_data_reqd = 1; + } + + return flags; +} + +static int samba_kdc_entry_destructor(struct samba_kdc_entry *p) +{ + if (p->db_entry != NULL) { + /* + * A sdb_entry still has a reference + */ + return -1; + } + + if (p->kdc_entry != NULL) { + /* + * hdb_entry or krb5_db_entry still + * have a reference... + */ + return -1; + } + + return 0; +} + +/* + * Sort keys in descending order of strength. + * + * Explanaton from Greg Hudson: + * + * To encrypt tickets only the first returned key is used by the MIT KDC. The + * other keys just communicate support for session key enctypes, and aren't + * really used. The encryption key for the ticket enc part doesn't have + * to be of a type requested by the client. The session key enctype is chosen + * based on the client preference order, limited by the set of enctypes present + * in the server keys (unless the string attribute is set on the server + * principal overriding that set). + */ + +static int sdb_key_strength_priority(krb5_enctype etype) +{ + static const krb5_enctype etype_list[] = { + ENCTYPE_AES256_CTS_HMAC_SHA1_96, + ENCTYPE_AES128_CTS_HMAC_SHA1_96, + ENCTYPE_DES3_CBC_SHA1, + ENCTYPE_ARCFOUR_HMAC, + ENCTYPE_DES_CBC_MD5, + ENCTYPE_DES_CBC_MD4, + ENCTYPE_DES_CBC_CRC, + ENCTYPE_NULL + }; + int i; + + for (i = 0; i < ARRAY_SIZE(etype_list); i++) { + if (etype == etype_list[i]) { + break; + } + } + + return ARRAY_SIZE(etype_list) - i; +} + +static int sdb_key_strength_cmp(const struct sdb_key *k1, const struct sdb_key *k2) +{ + int p1 = sdb_key_strength_priority(KRB5_KEY_TYPE(&k1->key)); + int p2 = sdb_key_strength_priority(KRB5_KEY_TYPE(&k2->key)); + + if (p1 == p2) { + return 0; + } + + if (p1 > p2) { + /* + * Higher priority comes first + */ + return -1; + } else { + return 1; + } +} + +static void samba_kdc_sort_keys(struct sdb_keys *keys) +{ + if (keys == NULL) { + return; + } + + TYPESAFE_QSORT(keys->val, keys->len, sdb_key_strength_cmp); +} + +int samba_kdc_set_fixed_keys(krb5_context context, + const struct ldb_val *secretbuffer, + uint32_t supported_enctypes, + struct sdb_keys *keys) +{ + uint16_t allocated_keys = 0; + int ret; + + allocated_keys = 3; + keys->len = 0; + keys->val = calloc(allocated_keys, sizeof(struct sdb_key)); + if (keys->val == NULL) { + memset(secretbuffer->data, 0, secretbuffer->length); + ret = ENOMEM; + goto out; + } + + if (supported_enctypes & ENC_HMAC_SHA1_96_AES256) { + struct sdb_key key = {}; + + ret = smb_krb5_keyblock_init_contents(context, + ENCTYPE_AES256_CTS_HMAC_SHA1_96, + secretbuffer->data, + MIN(secretbuffer->length, 32), + &key.key); + if (ret) { + memset(secretbuffer->data, 0, secretbuffer->length); + goto out; + } + + keys->val[keys->len] = key; + keys->len++; + } + + if (supported_enctypes & ENC_HMAC_SHA1_96_AES128) { + struct sdb_key key = {}; + + ret = smb_krb5_keyblock_init_contents(context, + ENCTYPE_AES128_CTS_HMAC_SHA1_96, + secretbuffer->data, + MIN(secretbuffer->length, 16), + &key.key); + if (ret) { + memset(secretbuffer->data, 0, secretbuffer->length); + goto out; + } + + keys->val[keys->len] = key; + keys->len++; + } + + if (supported_enctypes & ENC_RC4_HMAC_MD5) { + struct sdb_key key = {}; + + ret = smb_krb5_keyblock_init_contents(context, + ENCTYPE_ARCFOUR_HMAC, + secretbuffer->data, + MIN(secretbuffer->length, 16), + &key.key); + if (ret) { + memset(secretbuffer->data, 0, secretbuffer->length); + goto out; + } + + keys->val[keys->len] = key; + keys->len++; + } + ret = 0; +out: + return ret; +} + + +static int samba_kdc_set_random_keys(krb5_context context, + uint32_t supported_enctypes, + struct sdb_keys *keys) +{ + struct ldb_val secret_val; + uint8_t secretbuffer[32]; + + /* + * Fake keys until we have a better way to reject + * non-pkinit requests. + * + * We just need to indicate which encryption types are + * supported. + */ + generate_secret_buffer(secretbuffer, sizeof(secretbuffer)); + + secret_val = data_blob_const(secretbuffer, + sizeof(secretbuffer)); + return samba_kdc_set_fixed_keys(context, + &secret_val, + supported_enctypes, + keys); +} + +struct samba_kdc_user_keys { + struct sdb_keys *skeys; + uint32_t kvno; + uint32_t *returned_kvno; + uint32_t supported_enctypes; + uint32_t *available_enctypes; + const struct samr_Password *nthash; + const char *salt_string; + uint16_t num_pkeys; + const struct package_PrimaryKerberosKey4 *pkeys; +}; + +static krb5_error_code samba_kdc_fill_user_keys(krb5_context context, + struct samba_kdc_user_keys *p) +{ + /* + * Make sure we'll never reveal DES keys + */ + uint32_t supported_enctypes = p->supported_enctypes &= ~(ENC_CRC32 | ENC_RSA_MD5); + uint32_t _available_enctypes = 0; + uint32_t *available_enctypes = p->available_enctypes; + uint32_t _returned_kvno = 0; + uint32_t *returned_kvno = p->returned_kvno; + uint32_t num_pkeys = p->num_pkeys; + uint32_t allocated_keys = num_pkeys; + uint32_t i; + int ret; + + if (available_enctypes == NULL) { + available_enctypes = &_available_enctypes; + } + + *available_enctypes = 0; + + if (returned_kvno == NULL) { + returned_kvno = &_returned_kvno; + } + + *returned_kvno = p->kvno; + + if (p->nthash != NULL) { + allocated_keys += 1; + } + + allocated_keys = MAX(1, allocated_keys); + + /* allocate space to decode into */ + p->skeys->len = 0; + p->skeys->val = calloc(allocated_keys, sizeof(struct sdb_key)); + if (p->skeys->val == NULL) { + return ENOMEM; + } + + for (i=0; i < num_pkeys; i++) { + struct sdb_key key = {}; + uint32_t enctype_bit; + + if (p->pkeys[i].value == NULL) { + continue; + } + + enctype_bit = kerberos_enctype_to_bitmap(p->pkeys[i].keytype); + if (!(enctype_bit & supported_enctypes)) { + continue; + } + + if (p->salt_string != NULL) { + DATA_BLOB salt; + + salt = data_blob_string_const(p->salt_string); + + key.salt = calloc(1, sizeof(*key.salt)); + if (key.salt == NULL) { + ret = ENOMEM; + goto fail; + } + + key.salt->type = KRB5_PW_SALT; + + ret = smb_krb5_copy_data_contents(&key.salt->salt, + salt.data, + salt.length); + if (ret) { + ZERO_STRUCTP(key.salt); + sdb_key_free(&key); + goto fail; + } + } + + ret = smb_krb5_keyblock_init_contents(context, + p->pkeys[i].keytype, + p->pkeys[i].value->data, + p->pkeys[i].value->length, + &key.key); + if (ret == 0) { + p->skeys->val[p->skeys->len++] = key; + *available_enctypes |= enctype_bit; + continue; + } + ZERO_STRUCT(key.key); + sdb_key_free(&key); + if (ret == KRB5_PROG_ETYPE_NOSUPP) { + DEBUG(2,("Unsupported keytype ignored - type %u\n", + p->pkeys[i].keytype)); + ret = 0; + continue; + } + + goto fail; + } + + if (p->nthash != NULL && (supported_enctypes & ENC_RC4_HMAC_MD5)) { + struct sdb_key key = {}; + + ret = smb_krb5_keyblock_init_contents(context, + ENCTYPE_ARCFOUR_HMAC, + p->nthash->hash, + sizeof(p->nthash->hash), + &key.key); + if (ret == 0) { + p->skeys->val[p->skeys->len++] = key; + + *available_enctypes |= ENC_RC4_HMAC_MD5; + } else if (ret == KRB5_PROG_ETYPE_NOSUPP) { + DEBUG(2,("Unsupported keytype ignored - type %u\n", + ENCTYPE_ARCFOUR_HMAC)); + ret = 0; + } + if (ret != 0) { + goto fail; + } + } + + samba_kdc_sort_keys(p->skeys); + + return 0; +fail: + sdb_keys_free(p->skeys); + return ret; +} + +krb5_error_code samba_kdc_message2entry_keys(krb5_context context, + TALLOC_CTX *mem_ctx, + const struct ldb_message *msg, + bool is_krbtgt, + bool is_rodc, + uint32_t userAccountControl, + enum samba_kdc_ent_type ent_type, + unsigned flags, + krb5_kvno requested_kvno, + struct sdb_entry *entry, + const uint32_t supported_enctypes_in, + uint32_t *supported_enctypes_out) +{ + krb5_error_code ret = 0; + enum ndr_err_code ndr_err; + struct samr_Password *hash; + unsigned int num_ntPwdHistory = 0; + struct samr_Password *ntPwdHistory = NULL; + struct samr_Password *old_hash = NULL; + struct samr_Password *older_hash = NULL; + const struct ldb_val *sc_val; + struct supplementalCredentialsBlob scb; + struct supplementalCredentialsPackage *scpk = NULL; + struct package_PrimaryKerberosBlob _pkb; + struct package_PrimaryKerberosCtr4 *pkb4 = NULL; + int krbtgt_number = 0; + uint32_t current_kvno; + uint32_t old_kvno = 0; + uint32_t older_kvno = 0; + uint32_t returned_kvno = 0; + uint16_t i; + struct samba_kdc_user_keys keys = { .num_pkeys = 0, }; + struct samba_kdc_user_keys old_keys = { .num_pkeys = 0, }; + struct samba_kdc_user_keys older_keys = { .num_pkeys = 0, }; + uint32_t available_enctypes = 0; + uint32_t supported_enctypes = supported_enctypes_in; + + *supported_enctypes_out = 0; + + /* Is this the krbtgt or a RODC krbtgt */ + if (is_rodc) { + krbtgt_number = ldb_msg_find_attr_as_int(msg, "msDS-SecondaryKrbTgtNumber", -1); + + if (krbtgt_number == -1) { + return EINVAL; + } + if (krbtgt_number == 0) { + return EINVAL; + } + } + + if ((ent_type == SAMBA_KDC_ENT_TYPE_CLIENT) + && (userAccountControl & UF_SMARTCARD_REQUIRED)) { + ret = samba_kdc_set_random_keys(context, + supported_enctypes, + &entry->keys); + + *supported_enctypes_out = supported_enctypes & ENC_ALL_TYPES; + + goto out; + } + + current_kvno = ldb_msg_find_attr_as_int(msg, "msDS-KeyVersionNumber", 0); + if (current_kvno > 1) { + old_kvno = current_kvno - 1; + } + if (current_kvno > 2) { + older_kvno = current_kvno - 2; + } + if (is_krbtgt) { + /* + * Even for the main krbtgt account + * we have to strictly split the kvno into + * two 16-bit parts and the upper 16-bit + * need to be all zero, even if + * the msDS-KeyVersionNumber has a value + * larger than 65535. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14951 + */ + current_kvno = SAMBA_KVNO_GET_VALUE(current_kvno); + old_kvno = SAMBA_KVNO_GET_VALUE(old_kvno); + older_kvno = SAMBA_KVNO_GET_VALUE(older_kvno); + requested_kvno = SAMBA_KVNO_GET_VALUE(requested_kvno); + } + + /* Get keys from the db */ + + hash = samdb_result_hash(mem_ctx, msg, "unicodePwd"); + num_ntPwdHistory = samdb_result_hashes(mem_ctx, msg, + "ntPwdHistory", + &ntPwdHistory); + if (num_ntPwdHistory > 1) { + old_hash = &ntPwdHistory[1]; + } + if (num_ntPwdHistory > 2) { + older_hash = &ntPwdHistory[1]; + } + sc_val = ldb_msg_find_ldb_val(msg, "supplementalCredentials"); + + /* supplementalCredentials if present */ + if (sc_val) { + ndr_err = ndr_pull_struct_blob_all(sc_val, mem_ctx, &scb, + (ndr_pull_flags_fn_t)ndr_pull_supplementalCredentialsBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + dump_data(0, sc_val->data, sc_val->length); + ret = EINVAL; + goto out; + } + + if (scb.sub.signature != SUPPLEMENTAL_CREDENTIALS_SIGNATURE) { + if (scb.sub.num_packages != 0) { + NDR_PRINT_DEBUG(supplementalCredentialsBlob, &scb); + ret = EINVAL; + goto out; + } + } + + for (i=0; i < scb.sub.num_packages; i++) { + if (strcmp("Primary:Kerberos-Newer-Keys", scb.sub.packages[i].name) == 0) { + scpk = &scb.sub.packages[i]; + if (!scpk->data || !scpk->data[0]) { + scpk = NULL; + continue; + } + break; + } + } + } + /* + * Primary:Kerberos-Newer-Keys element + * of supplementalCredentials + * + * The legacy Primary:Kerberos only contains + * single DES keys, which are completely ignored + * now. + */ + if (scpk) { + DATA_BLOB blob; + + blob = strhex_to_data_blob(mem_ctx, scpk->data); + if (!blob.data) { + ret = ENOMEM; + goto out; + } + + /* we cannot use ndr_pull_struct_blob_all() here, as w2k and w2k3 add padding bytes */ + ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &_pkb, + (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryKerberosBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + ret = EINVAL; + krb5_set_error_message(context, ret, "samba_kdc_message2entry_keys: could not parse package_PrimaryKerberosBlob"); + krb5_warnx(context, "samba_kdc_message2entry_keys: could not parse package_PrimaryKerberosBlob"); + goto out; + } + + if (_pkb.version != 4) { + ret = EINVAL; + krb5_set_error_message(context, ret, "samba_kdc_message2entry_keys: Primary:Kerberos-Newer-Keys not version 4"); + krb5_warnx(context, "samba_kdc_message2entry_keys: Primary:Kerberos-Newer-Keys not version 4"); + goto out; + } + + pkb4 = &_pkb.ctr.ctr4; + } + + keys = (struct samba_kdc_user_keys) { + .kvno = current_kvno, + .supported_enctypes = supported_enctypes, + .nthash = hash, + .salt_string = pkb4 != NULL ? pkb4->salt.string : NULL, + .num_pkeys = pkb4 != NULL ? pkb4->num_keys : 0, + .pkeys = pkb4 != NULL ? pkb4->keys : NULL, + }; + + old_keys = (struct samba_kdc_user_keys) { + .kvno = old_kvno, + .supported_enctypes = supported_enctypes, + .nthash = old_hash, + .salt_string = pkb4 != NULL ? pkb4->salt.string : NULL, + .num_pkeys = pkb4 != NULL ? pkb4->num_old_keys : 0, + .pkeys = pkb4 != NULL ? pkb4->old_keys : NULL, + }; + older_keys = (struct samba_kdc_user_keys) { + .kvno = older_kvno, + .supported_enctypes = supported_enctypes, + .nthash = older_hash, + .salt_string = pkb4 != NULL ? pkb4->salt.string : NULL, + .num_pkeys = pkb4 != NULL ? pkb4->num_older_keys : 0, + .pkeys = pkb4 != NULL ? pkb4->older_keys : NULL, + }; + + if (flags & SDB_F_KVNO_SPECIFIED) { + if (requested_kvno == keys.kvno) { + /* + * The current kvno was requested, + * so we return it. + */ + keys.skeys = &entry->keys; + keys.available_enctypes = &available_enctypes; + keys.returned_kvno = &returned_kvno; + } else if (requested_kvno == 0) { + /* + * don't return any keys + */ + } else if (requested_kvno == old_keys.kvno) { + /* + * return the old keys as default keys + * with the requested kvno. + */ + old_keys.skeys = &entry->keys; + old_keys.available_enctypes = &available_enctypes; + old_keys.returned_kvno = &returned_kvno; + } else if (requested_kvno == older_keys.kvno) { + /* + * return the older keys as default keys + * with the requested kvno. + */ + older_keys.skeys = &entry->keys; + older_keys.available_enctypes = &available_enctypes; + older_keys.returned_kvno = &returned_kvno; + } else { + /* + * don't return any keys + */ + } + } else { + bool include_history = false; + + if ((flags & SDB_F_GET_CLIENT) && (flags & SDB_F_FOR_AS_REQ)) { + include_history = true; + } else if (flags & SDB_F_ADMIN_DATA) { + include_history = true; + } + + keys.skeys = &entry->keys; + keys.available_enctypes = &available_enctypes; + keys.returned_kvno = &returned_kvno; + + if (include_history && old_keys.kvno != 0) { + old_keys.skeys = &entry->old_keys; + } + if (include_history && older_keys.kvno != 0) { + older_keys.skeys = &entry->older_keys; + } + } + + if (keys.skeys != NULL) { + ret = samba_kdc_fill_user_keys(context, &keys); + if (ret != 0) { + goto out; + } + } + + if (old_keys.skeys != NULL) { + ret = samba_kdc_fill_user_keys(context, &old_keys); + if (ret != 0) { + goto out; + } + } + + if (older_keys.skeys != NULL) { + ret = samba_kdc_fill_user_keys(context, &older_keys); + if (ret != 0) { + goto out; + } + } + + *supported_enctypes_out |= available_enctypes; + + if (is_krbtgt) { + /* + * Even for the main krbtgt account + * we have to strictly split the kvno into + * two 16-bit parts and the upper 16-bit + * need to be all zero, even if + * the msDS-KeyVersionNumber has a value + * larger than 65535. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14951 + */ + returned_kvno = SAMBA_KVNO_AND_KRBTGT(returned_kvno, krbtgt_number); + } + entry->kvno = returned_kvno; + +out: + return ret; +} + +static int principal_comp_strcmp_int(krb5_context context, + krb5_const_principal principal, + unsigned int component, + const char *string, + bool do_strcasecmp) +{ + const char *p; + +#if defined(HAVE_KRB5_PRINCIPAL_GET_COMP_STRING) + p = krb5_principal_get_comp_string(context, principal, component); + if (p == NULL) { + return -1; + } + if (do_strcasecmp) { + return strcasecmp(p, string); + } else { + return strcmp(p, string); + } +#else + size_t len; + krb5_data *d; + if (component >= krb5_princ_size(context, principal)) { + return -1; + } + + d = krb5_princ_component(context, principal, component); + if (d == NULL) { + return -1; + } + + p = d->data; + + len = strlen(string); + + /* + * We explicitly return -1 or 1. Subtracting of the two lengths might + * give the wrong result if the result overflows or loses data when + * narrowed to int. + */ + if (d->length < len) { + return -1; + } else if (d->length > len) { + return 1; + } + + if (do_strcasecmp) { + return strncasecmp(p, string, len); + } else { + return memcmp(p, string, len); + } +#endif +} + +static int principal_comp_strcasecmp(krb5_context context, + krb5_const_principal principal, + unsigned int component, + const char *string) +{ + return principal_comp_strcmp_int(context, principal, + component, string, true); +} + +static int principal_comp_strcmp(krb5_context context, + krb5_const_principal principal, + unsigned int component, + const char *string) +{ + return principal_comp_strcmp_int(context, principal, + component, string, false); +} + +static bool is_kadmin_changepw(krb5_context context, + krb5_const_principal principal) +{ + return krb5_princ_size(context, principal) == 2 && + (principal_comp_strcmp(context, principal, 0, "kadmin") == 0) && + (principal_comp_strcmp(context, principal, 1, "changepw") == 0); +} + +static krb5_error_code samba_kdc_get_entry_principal( + krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + const char *samAccountName, + enum samba_kdc_ent_type ent_type, + unsigned flags, + bool is_kadmin_changepw, + krb5_const_principal in_princ, + krb5_principal *out_princ) +{ + struct loadparm_context *lp_ctx = kdc_db_ctx->lp_ctx; + krb5_error_code code = 0; + bool canon = flags & (SDB_F_CANON|SDB_F_FORCE_CANON); + + /* + * If we are set to canonicalize, we get back the fixed UPPER + * case realm, and the real username (ie matching LDAP + * samAccountName) + * + * Otherwise, if we are set to enterprise, we + * get back the whole principal as-sent + * + * Finally, if we are not set to canonicalize, we get back the + * fixed UPPER case realm, but the as-sent username + */ + + /* + * We need to ensure that the kadmin/changepw principal isn't able to + * issue krbtgt tickets, even if canonicalization is turned on. + */ + if (!is_kadmin_changepw) { + if (ent_type == SAMBA_KDC_ENT_TYPE_KRBTGT && canon) { + /* + * When requested to do so, ensure that the + * both realm values in the principal are set + * to the upper case, canonical realm + */ + code = smb_krb5_make_principal(context, + out_princ, + lpcfg_realm(lp_ctx), + "krbtgt", + lpcfg_realm(lp_ctx), + NULL); + if (code != 0) { + return code; + } + smb_krb5_principal_set_type(context, + *out_princ, + KRB5_NT_SRV_INST); + + return 0; + } + + if ((canon && flags & (SDB_F_FORCE_CANON|SDB_F_FOR_AS_REQ)) || + (ent_type == SAMBA_KDC_ENT_TYPE_ANY && in_princ == NULL)) { + /* + * SDB_F_CANON maps from the canonicalize flag in the + * packet, and has a different meaning between AS-REQ + * and TGS-REQ. We only change the principal in the + * AS-REQ case. + * + * The SDB_F_FORCE_CANON if for new MIT KDC code that + * wants the canonical name in all lookups, and takes + * care to canonicalize only when appropriate. + */ + code = smb_krb5_make_principal(context, + out_princ, + lpcfg_realm(lp_ctx), + samAccountName, + NULL); + return code; + } + } + + /* + * For a krbtgt entry, this appears to be required regardless of the + * canonicalize flag from the client. + */ + code = krb5_copy_principal(context, in_princ, out_princ); + if (code != 0) { + return code; + } + + /* + * While we have copied the client principal, tests show that Win2k3 + * returns the 'corrected' realm, not the client-specified realm. This + * code attempts to replace the client principal's realm with the one + * we determine from our records + */ + code = smb_krb5_principal_set_realm(context, + *out_princ, + lpcfg_realm(lp_ctx)); + + return code; +} + +/* + * Construct an hdb_entry from a directory entry. + */ +static krb5_error_code samba_kdc_message2entry(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + TALLOC_CTX *mem_ctx, + krb5_const_principal principal, + enum samba_kdc_ent_type ent_type, + unsigned flags, + krb5_kvno kvno, + struct ldb_dn *realm_dn, + struct ldb_message *msg, + struct sdb_entry *entry) +{ + struct loadparm_context *lp_ctx = kdc_db_ctx->lp_ctx; + uint32_t userAccountControl; + uint32_t msDS_User_Account_Control_Computed; + krb5_error_code ret = 0; + krb5_boolean is_computer = FALSE; + struct samba_kdc_entry *p; + NTTIME acct_expiry; + NTSTATUS status; + bool protected_user = false; + uint32_t rid; + bool is_krbtgt = false; + bool is_rodc = false; + bool force_rc4 = lpcfg_kdc_force_enable_rc4_weak_session_keys(lp_ctx); + struct ldb_message_element *objectclasses; + struct ldb_val computer_val = data_blob_string_const("computer"); + uint32_t config_default_supported_enctypes = lpcfg_kdc_default_domain_supported_enctypes(lp_ctx); + uint32_t default_supported_enctypes = + config_default_supported_enctypes != 0 ? + config_default_supported_enctypes : + ENC_RC4_HMAC_MD5 | ENC_HMAC_SHA1_96_AES256_SK; + uint32_t supported_enctypes + = ldb_msg_find_attr_as_uint(msg, + "msDS-SupportedEncryptionTypes", + default_supported_enctypes); + uint32_t pa_supported_enctypes; + uint32_t supported_session_etypes; + uint32_t available_enctypes = 0; + /* + * also lagacy enctypes are announced, + * but effectively restricted by kdc_enctypes + */ + uint32_t domain_enctypes = ENC_RC4_HMAC_MD5 | ENC_RSA_MD5 | ENC_CRC32; + uint32_t config_kdc_enctypes = lpcfg_kdc_supported_enctypes(lp_ctx); + uint32_t kdc_enctypes = + config_kdc_enctypes != 0 ? + config_kdc_enctypes : + ENC_ALL_TYPES; + const char *samAccountName = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL); + + ZERO_STRUCTP(entry); + + if (supported_enctypes == 0) { + supported_enctypes = default_supported_enctypes; + } + + if (dsdb_functional_level(kdc_db_ctx->samdb) >= DS_DOMAIN_FUNCTION_2008) { + domain_enctypes |= ENC_HMAC_SHA1_96_AES128 | ENC_HMAC_SHA1_96_AES256; + } + + if (ldb_msg_find_element(msg, "msDS-SecondaryKrbTgtNumber")) { + is_rodc = true; + } + + if (!samAccountName) { + ret = ENOENT; + krb5_set_error_message(context, ret, "samba_kdc_message2entry: no samAccountName present"); + goto out; + } + + objectclasses = ldb_msg_find_element(msg, "objectClass"); + + if (objectclasses && ldb_msg_find_val(objectclasses, &computer_val)) { + is_computer = TRUE; + } + + p = talloc_zero(mem_ctx, struct samba_kdc_entry); + if (!p) { + ret = ENOMEM; + goto out; + } + + p->is_rodc = is_rodc; + p->kdc_db_ctx = kdc_db_ctx; + p->realm_dn = talloc_reference(p, realm_dn); + if (!p->realm_dn) { + ret = ENOMEM; + goto out; + } + + talloc_set_destructor(p, samba_kdc_entry_destructor); + + entry->skdc_entry = p; + + userAccountControl = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0); + + msDS_User_Account_Control_Computed + = ldb_msg_find_attr_as_uint(msg, + "msDS-User-Account-Control-Computed", + UF_ACCOUNTDISABLE); + + /* + * This brings in the lockout flag, block the account if not + * found. We need the weird UF_ACCOUNTDISABLE check because + * we do not want to fail open if the value is not returned, + * but 0 is a valid value (all OK) + */ + if (msDS_User_Account_Control_Computed == UF_ACCOUNTDISABLE) { + ret = EINVAL; + krb5_set_error_message(context, ret, "samba_kdc_message2entry: " + "no msDS-User-Account-Control-Computed present"); + goto out; + } else { + userAccountControl |= msDS_User_Account_Control_Computed; + } + + if (ent_type == SAMBA_KDC_ENT_TYPE_KRBTGT) { + p->is_krbtgt = true; + } + + /* First try and figure out the flags based on the userAccountControl */ + entry->flags = uf2SDBFlags(context, userAccountControl, ent_type); + + /* + * Take control of the returned principal here, rather than + * allowing the Heimdal code to do it as we have specific + * behaviour around the forced realm to honour + */ + entry->flags.force_canonicalize = true; + + /* Windows 2008 seems to enforce this (very sensible) rule by + * default - don't allow offline attacks on a user's password + * by asking for a ticket to them as a service (encrypted with + * their probably patheticly insecure password) */ + + if (entry->flags.server + && lpcfg_parm_bool(lp_ctx, NULL, "kdc", "require spn for service", true)) { + if (!is_computer && !ldb_msg_find_attr_as_string(msg, "servicePrincipalName", NULL)) { + entry->flags.server = 0; + } + } + + /* + * We restrict a 3-part SPN ending in my domain/realm to full + * domain controllers. + * + * This avoids any cases where (eg) a demoted DC still has + * these more restricted SPNs. + */ + if (krb5_princ_size(context, principal) > 2) { + char *third_part + = smb_krb5_principal_get_comp_string(mem_ctx, + context, + principal, + 2); + bool is_our_realm = + lpcfg_is_my_domain_or_realm(lp_ctx, + third_part); + bool is_dc = userAccountControl & + (UF_SERVER_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT); + if (is_our_realm && !is_dc) { + entry->flags.server = 0; + } + } + /* + * To give the correct type of error to the client, we must + * not just return the entry without .server set, we must + * pretend the principal does not exist. Otherwise we may + * return ERR_POLICY instead of + * KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN + */ + if (ent_type == SAMBA_KDC_ENT_TYPE_SERVER && entry->flags.server == 0) { + ret = SDB_ERR_NOENTRY; + krb5_set_error_message(context, ret, "samba_kdc_message2entry: no servicePrincipalName present for this server, refusing with no-such-entry"); + goto out; + } + if (flags & SDB_F_ADMIN_DATA) { + /* These (created_by, modified_by) parts of the entry are not relevant for Samba4's use + * of the Heimdal KDC. They are stored in a the traditional + * DB for audit purposes, and still form part of the structure + * we must return */ + + /* use 'whenCreated' */ + entry->created_by.time = ldb_msg_find_krb5time_ldap_time(msg, "whenCreated", 0); + /* use 'kadmin' for now (needed by mit_samba) */ + + ret = smb_krb5_make_principal(context, + &entry->created_by.principal, + lpcfg_realm(lp_ctx), "kadmin", NULL); + if (ret) { + krb5_clear_error_message(context); + goto out; + } + + entry->modified_by = (struct sdb_event *) malloc(sizeof(struct sdb_event)); + if (entry->modified_by == NULL) { + ret = ENOMEM; + krb5_set_error_message(context, ret, "malloc: out of memory"); + goto out; + } + + /* use 'whenChanged' */ + entry->modified_by->time = ldb_msg_find_krb5time_ldap_time(msg, "whenChanged", 0); + /* use 'kadmin' for now (needed by mit_samba) */ + ret = smb_krb5_make_principal(context, + &entry->modified_by->principal, + lpcfg_realm(lp_ctx), "kadmin", NULL); + if (ret) { + krb5_clear_error_message(context); + goto out; + } + } + + + /* The lack of password controls etc applies to krbtgt by + * virtue of being that particular RID */ + status = dom_sid_split_rid(NULL, samdb_result_dom_sid(mem_ctx, msg, "objectSid"), NULL, &rid); + + if (!NT_STATUS_IS_OK(status)) { + ret = EINVAL; + goto out; + } + + if (rid == DOMAIN_RID_KRBTGT) { + char *realm = NULL; + + entry->valid_end = NULL; + entry->pw_end = NULL; + + entry->flags.invalid = 0; + entry->flags.server = 1; + + realm = smb_krb5_principal_get_realm( + mem_ctx, context, principal); + if (realm == NULL) { + ret = ENOMEM; + goto out; + } + + /* Don't mark all requests for the krbtgt/realm as + * 'change password', as otherwise we could get into + * trouble, and not enforce the password expirty. + * Instead, only do it when request is for the kpasswd service */ + if (ent_type == SAMBA_KDC_ENT_TYPE_SERVER && + is_kadmin_changepw(context, principal) && + lpcfg_is_my_domain_or_realm(lp_ctx, realm)) { + entry->flags.change_pw = 1; + } + + TALLOC_FREE(realm); + + entry->flags.client = 0; + entry->flags.forwardable = 1; + entry->flags.ok_as_delegate = 1; + } else if (is_rodc) { + /* The RODC krbtgt account is like the main krbtgt, + * but it does not have a changepw or kadmin + * service */ + + entry->valid_end = NULL; + entry->pw_end = NULL; + + /* Also don't allow the RODC krbtgt to be a client (it should not be needed) */ + entry->flags.client = 0; + entry->flags.invalid = 0; + entry->flags.server = 1; + + entry->flags.client = 0; + entry->flags.forwardable = 1; + entry->flags.ok_as_delegate = 0; + } else if (entry->flags.server && ent_type == SAMBA_KDC_ENT_TYPE_SERVER) { + /* The account/password expiry only applies when the account is used as a + * client (ie password login), not when used as a server */ + + /* Make very well sure we don't use this for a client, + * it could bypass the password restrictions */ + entry->flags.client = 0; + + entry->valid_end = NULL; + entry->pw_end = NULL; + + } else { + NTTIME must_change_time + = samdb_result_nttime(msg, + "msDS-UserPasswordExpiryTimeComputed", + 0); + if (must_change_time == 0x7FFFFFFFFFFFFFFFULL) { + entry->pw_end = NULL; + } else { + entry->pw_end = malloc(sizeof(*entry->pw_end)); + if (entry->pw_end == NULL) { + ret = ENOMEM; + goto out; + } + *entry->pw_end = nt_time_to_unix(must_change_time); + } + + acct_expiry = samdb_result_account_expires(msg); + if (acct_expiry == 0x7FFFFFFFFFFFFFFFULL) { + entry->valid_end = NULL; + } else { + entry->valid_end = malloc(sizeof(*entry->valid_end)); + if (entry->valid_end == NULL) { + ret = ENOMEM; + goto out; + } + *entry->valid_end = nt_time_to_unix(acct_expiry); + } + } + + ret = samba_kdc_get_entry_principal(context, + kdc_db_ctx, + samAccountName, + ent_type, + flags, + entry->flags.change_pw, + principal, + &entry->principal); + if (ret != 0) { + krb5_clear_error_message(context); + goto out; + } + + entry->valid_start = NULL; + + entry->max_life = malloc(sizeof(*entry->max_life)); + if (entry->max_life == NULL) { + ret = ENOMEM; + goto out; + } + + if (ent_type == SAMBA_KDC_ENT_TYPE_SERVER) { + *entry->max_life = kdc_db_ctx->policy.svc_tkt_lifetime; + } else if (ent_type == SAMBA_KDC_ENT_TYPE_KRBTGT || ent_type == SAMBA_KDC_ENT_TYPE_CLIENT) { + *entry->max_life = kdc_db_ctx->policy.usr_tkt_lifetime; + } else { + *entry->max_life = MIN(kdc_db_ctx->policy.svc_tkt_lifetime, + kdc_db_ctx->policy.usr_tkt_lifetime); + } + + if (entry->flags.change_pw) { + /* Limit lifetime of kpasswd tickets to two minutes or less. */ + *entry->max_life = MIN(*entry->max_life, CHANGEPW_LIFETIME); + } + + entry->max_renew = malloc(sizeof(*entry->max_renew)); + if (entry->max_renew == NULL) { + ret = ENOMEM; + goto out; + } + + *entry->max_renew = kdc_db_ctx->policy.renewal_lifetime; + + if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT && (flags & SDB_F_FOR_AS_REQ)) { + int result; + struct auth_user_info_dc *user_info_dc = NULL; + /* + * These protections only apply to clients, so servers in the + * Protected Users group may still have service tickets to them + * encrypted with RC4. For accounts looked up as servers, note + * that 'msg' does not contain the 'memberOf' attribute for + * determining whether the account is a member of Protected + * Users. + * + * Additionally, Microsoft advises that accounts for services + * and computers should never be members of Protected Users, or + * they may fail to authenticate. + */ + status = samba_kdc_get_user_info_from_db(p, msg, &user_info_dc); + if (!NT_STATUS_IS_OK(status)) { + ret = EINVAL; + goto out; + } + + result = dsdb_is_protected_user(kdc_db_ctx->samdb, + user_info_dc->sids, + user_info_dc->num_sids); + if (result == -1) { + ret = EINVAL; + goto out; + } + + protected_user = result; + + if (protected_user) { + *entry->max_life = MIN(*entry->max_life, 4 * 60 * 60); + *entry->max_renew = MIN(*entry->max_renew, 4 * 60 * 60); + + entry->flags.forwardable = 0; + entry->flags.proxiable = 0; + } + } + + if (rid == DOMAIN_RID_KRBTGT || is_rodc) { + bool enable_fast; + + is_krbtgt = true; + + /* + * KDCs (and KDCs on RODCs) + * ignore msDS-SupportedEncryptionTypes completely + * but support all supported enctypes by the domain. + */ + supported_enctypes = domain_enctypes; + + enable_fast = lpcfg_kdc_enable_fast(kdc_db_ctx->lp_ctx); + if (enable_fast) { + supported_enctypes |= ENC_FAST_SUPPORTED; + } + } else if (userAccountControl & (UF_PARTIAL_SECRETS_ACCOUNT|UF_SERVER_TRUST_ACCOUNT)) { + /* + * DCs and RODCs computer accounts take + * msDS-SupportedEncryptionTypes unmodified, but + * force all enctypes supported by the domain. + */ + supported_enctypes |= domain_enctypes; + + } else if (ent_type == SAMBA_KDC_ENT_TYPE_CLIENT || + (ent_type == SAMBA_KDC_ENT_TYPE_ANY)) { + /* + * for AS-REQ the client chooses the enc types it + * supports, and this will vary between computers a + * user logs in from. Therefore, so that we accept any + * of the client's keys for decrypting padata, + * supported_enctypes should not restrict etype usage. + * + * likewise for 'any' return as much as is supported, + * to export into a keytab. + */ + supported_enctypes |= ENC_ALL_TYPES; + } + + /* If UF_USE_DES_KEY_ONLY has been set, then don't allow use of the newer enc types */ + if (userAccountControl & UF_USE_DES_KEY_ONLY) { + supported_enctypes &= ~ENC_ALL_TYPES; + } + + if (protected_user) { + supported_enctypes &= ~ENC_RC4_HMAC_MD5; + } + + pa_supported_enctypes = supported_enctypes; + supported_session_etypes = supported_enctypes; + if (supported_session_etypes & ENC_HMAC_SHA1_96_AES256_SK) { + supported_session_etypes |= ENC_HMAC_SHA1_96_AES256; + supported_session_etypes |= ENC_HMAC_SHA1_96_AES128; + } + if (force_rc4) { + supported_session_etypes |= ENC_RC4_HMAC_MD5; + } + /* + * now that we remembered what to announce in pa_supported_enctypes + * and normalized ENC_HMAC_SHA1_96_AES256_SK, we restrict the + * rest to the enc types the local kdc supports. + */ + supported_enctypes &= kdc_enctypes; + supported_session_etypes &= kdc_enctypes; + + /* Get keys from the db */ + ret = samba_kdc_message2entry_keys(context, p, msg, + is_krbtgt, is_rodc, + userAccountControl, + ent_type, flags, kvno, entry, + supported_enctypes, + &available_enctypes); + if (ret) { + /* Could be bogus data in the entry, or out of memory */ + goto out; + } + + /* + * If we only have a nthash stored, + * but a better session key would be + * available, we fallback to fetching the + * RC4_HMAC_MD5, which implicitly also + * would allow an RC4_HMAC_MD5 session key. + * But only if the kdc actually supports + * RC4_HMAC_MD5. + */ + if (available_enctypes == 0 && + (supported_enctypes & ENC_RC4_HMAC_MD5) == 0 && + (supported_enctypes & ~ENC_RC4_HMAC_MD5) != 0 && + (kdc_enctypes & ENC_RC4_HMAC_MD5) != 0) + { + supported_enctypes = ENC_RC4_HMAC_MD5; + ret = samba_kdc_message2entry_keys(context, p, msg, + is_krbtgt, is_rodc, + userAccountControl, + ent_type, flags, kvno, entry, + supported_enctypes, + &available_enctypes); + if (ret) { + /* Could be bogus data in the entry, or out of memory */ + goto out; + } + } + + /* + * We need to support all session keys enctypes for + * all keys we provide + */ + supported_session_etypes |= available_enctypes; + + ret = sdb_entry_set_etypes(entry); + if (ret) { + goto out; + } + + if (entry->flags.server) { + bool add_aes256 = + supported_session_etypes & KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96; + bool add_aes128 = + supported_session_etypes & KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96; + bool add_rc4 = + supported_session_etypes & ENC_RC4_HMAC_MD5; + ret = sdb_entry_set_session_etypes(entry, + add_aes256, + add_aes128, + add_rc4); + if (ret) { + goto out; + } + } + + if (entry->keys.len != 0) { + /* + * FIXME: Currently limited to Heimdal so as not to + * break MIT KDCs, for which no fix is available. + */ +#ifdef SAMBA4_USES_HEIMDAL + if (is_krbtgt) { + /* + * The krbtgt account, having no reason to + * issue tickets encrypted in weaker keys, + * shall only make available its strongest + * key. All weaker keys are stripped out. This + * makes it impossible for an RC4-encrypted + * TGT to be accepted when AES KDC keys exist. + * + * This controls the ticket key and so the PAC + * signature algorithms indirectly, preventing + * a weak KDC checksum from being accepted + * when we verify the signatures for an + * S4U2Proxy evidence ticket. As such, this is + * indispensable for addressing + * CVE-2022-37966. + * + * Being strict here also provides protection + * against possible future attacks on weak + * keys. + */ + entry->keys.len = 1; + if (entry->etypes != NULL) { + entry->etypes->len = 1; + } + entry->old_keys.len = MIN(entry->old_keys.len, 1); + entry->older_keys.len = MIN(entry->older_keys.len, 1); + } +#endif + } else if (kdc_db_ctx->rodc) { + /* + * We are on an RODC, but don't have keys for this + * account. Signal this to the caller + */ + auth_sam_trigger_repl_secret(kdc_db_ctx, + kdc_db_ctx->msg_ctx, + kdc_db_ctx->ev_ctx, + msg->dn); + return SDB_ERR_NOT_FOUND_HERE; + } else { + /* + * oh, no password. Apparently (comment in + * hdb-ldap.c) this violates the ASN.1, but this + * allows an entry with no keys (yet). + */ + } + + p->msg = talloc_steal(p, msg); + p->supported_enctypes = pa_supported_enctypes; + +out: + if (ret != 0) { + /* This doesn't free ent itself, that is for the eventual caller to do */ + sdb_entry_free(entry); + } else { + talloc_steal(kdc_db_ctx, p); + } + + return ret; +} + +/* + * Construct an hdb_entry from a directory entry. + * The kvno is what the remote client asked for + */ +static krb5_error_code samba_kdc_trust_message2entry(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + TALLOC_CTX *mem_ctx, + enum trust_direction direction, + struct ldb_dn *realm_dn, + unsigned flags, + uint32_t kvno, + struct ldb_message *msg, + struct sdb_entry *entry) +{ + struct loadparm_context *lp_ctx = kdc_db_ctx->lp_ctx; + const char *our_realm = lpcfg_realm(lp_ctx); + char *partner_realm = NULL; + const char *realm = NULL; + const char *krbtgt_realm = NULL; + DATA_BLOB password_utf16 = data_blob_null; + DATA_BLOB password_utf8 = data_blob_null; + struct samr_Password _password_hash; + const struct samr_Password *password_hash = NULL; + const struct ldb_val *password_val; + struct trustAuthInOutBlob password_blob; + struct samba_kdc_entry *p; + bool use_previous = false; + uint32_t current_kvno; + uint32_t previous_kvno; + uint32_t num_keys = 0; + enum ndr_err_code ndr_err; + int ret; + unsigned int i; + struct AuthenticationInformationArray *auth_array; + struct timeval tv; + NTTIME an_hour_ago; + uint32_t *auth_kvno; + bool preferr_current = false; + bool force_rc4 = lpcfg_kdc_force_enable_rc4_weak_session_keys(lp_ctx); + uint32_t supported_enctypes = ENC_RC4_HMAC_MD5; + uint32_t pa_supported_enctypes; + uint32_t supported_session_etypes; + uint32_t config_kdc_enctypes = lpcfg_kdc_supported_enctypes(lp_ctx); + uint32_t kdc_enctypes = + config_kdc_enctypes != 0 ? + config_kdc_enctypes : + ENC_ALL_TYPES; + struct lsa_TrustDomainInfoInfoEx *tdo = NULL; + NTSTATUS status; + + ZERO_STRUCTP(entry); + + if (dsdb_functional_level(kdc_db_ctx->samdb) >= DS_DOMAIN_FUNCTION_2008) { + /* If not told otherwise, Windows now assumes that trusts support AES. */ + supported_enctypes = ldb_msg_find_attr_as_uint(msg, + "msDS-SupportedEncryptionTypes", + ENC_HMAC_SHA1_96_AES256); + } + + pa_supported_enctypes = supported_enctypes; + supported_session_etypes = supported_enctypes; + if (supported_session_etypes & ENC_HMAC_SHA1_96_AES256_SK) { + supported_session_etypes |= ENC_HMAC_SHA1_96_AES256; + supported_session_etypes |= ENC_HMAC_SHA1_96_AES128; + } + if (force_rc4) { + supported_session_etypes |= ENC_RC4_HMAC_MD5; + } + /* + * now that we remembered what to announce in pa_supported_enctypes + * and normalized ENC_HMAC_SHA1_96_AES256_SK, we restrict the + * rest to the enc types the local kdc supports. + */ + supported_enctypes &= kdc_enctypes; + supported_session_etypes &= kdc_enctypes; + + status = dsdb_trust_parse_tdo_info(mem_ctx, msg, &tdo); + if (!NT_STATUS_IS_OK(status)) { + krb5_clear_error_message(context); + ret = ENOMEM; + goto out; + } + + if (!(tdo->trust_direction & direction)) { + krb5_clear_error_message(context); + ret = SDB_ERR_NOENTRY; + goto out; + } + + if (tdo->trust_type != LSA_TRUST_TYPE_UPLEVEL) { + /* + * Only UPLEVEL domains support kerberos here, + * as we don't support LSA_TRUST_TYPE_MIT. + */ + krb5_clear_error_message(context); + ret = SDB_ERR_NOENTRY; + goto out; + } + + if (tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION) { + /* + * We don't support selective authentication yet. + */ + krb5_clear_error_message(context); + ret = SDB_ERR_NOENTRY; + goto out; + } + + if (tdo->domain_name.string == NULL) { + krb5_clear_error_message(context); + ret = SDB_ERR_NOENTRY; + goto out; + } + partner_realm = strupper_talloc(mem_ctx, tdo->domain_name.string); + if (partner_realm == NULL) { + krb5_clear_error_message(context); + ret = ENOMEM; + goto out; + } + + if (direction == INBOUND) { + realm = our_realm; + krbtgt_realm = partner_realm; + + password_val = ldb_msg_find_ldb_val(msg, "trustAuthIncoming"); + } else { /* OUTBOUND */ + realm = partner_realm; + krbtgt_realm = our_realm; + + password_val = ldb_msg_find_ldb_val(msg, "trustAuthOutgoing"); + } + + if (password_val == NULL) { + krb5_clear_error_message(context); + ret = SDB_ERR_NOENTRY; + goto out; + } + + ndr_err = ndr_pull_struct_blob(password_val, mem_ctx, &password_blob, + (ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + krb5_clear_error_message(context); + ret = EINVAL; + goto out; + } + + p = talloc_zero(mem_ctx, struct samba_kdc_entry); + if (!p) { + ret = ENOMEM; + goto out; + } + + p->is_trust = true; + p->kdc_db_ctx = kdc_db_ctx; + p->realm_dn = realm_dn; + p->supported_enctypes = pa_supported_enctypes; + + talloc_set_destructor(p, samba_kdc_entry_destructor); + + entry->skdc_entry = p; + + /* use 'whenCreated' */ + entry->created_by.time = ldb_msg_find_krb5time_ldap_time(msg, "whenCreated", 0); + /* use 'kadmin' for now (needed by mit_samba) */ + ret = smb_krb5_make_principal(context, + &entry->created_by.principal, + realm, "kadmin", NULL); + if (ret) { + krb5_clear_error_message(context); + goto out; + } + + /* + * We always need to generate the canonicalized principal + * with the values of our database. + */ + ret = smb_krb5_make_principal(context, &entry->principal, realm, + "krbtgt", krbtgt_realm, NULL); + if (ret) { + krb5_clear_error_message(context); + goto out; + } + smb_krb5_principal_set_type(context, entry->principal, + KRB5_NT_SRV_INST); + + entry->valid_start = NULL; + + /* we need to work out if we are going to use the current or + * the previous password hash. + * We base this on the kvno the client passes in. If the kvno + * passed in is equal to the current kvno in our database then + * we use the current structure. If it is the current kvno-1, + * then we use the previous substrucure. + */ + + /* + * Windows preferrs the previous key for one hour. + */ + tv = timeval_current(); + if (tv.tv_sec > 3600) { + tv.tv_sec -= 3600; + } + an_hour_ago = timeval_to_nttime(&tv); + + /* first work out the current kvno */ + current_kvno = 0; + for (i=0; i < password_blob.count; i++) { + struct AuthenticationInformation *a = + &password_blob.current.array[i]; + + if (a->LastUpdateTime <= an_hour_ago) { + preferr_current = true; + } + + if (a->AuthType == TRUST_AUTH_TYPE_VERSION) { + current_kvno = a->AuthInfo.version.version; + } + } + if (current_kvno == 0) { + previous_kvno = 255; + } else { + previous_kvno = current_kvno - 1; + } + for (i=0; i < password_blob.count; i++) { + struct AuthenticationInformation *a = + &password_blob.previous.array[i]; + + if (a->AuthType == TRUST_AUTH_TYPE_VERSION) { + previous_kvno = a->AuthInfo.version.version; + } + } + + /* work out whether we will use the previous or current + password */ + if (password_blob.previous.count == 0) { + /* there is no previous password */ + use_previous = false; + } else if (!(flags & SDB_F_KVNO_SPECIFIED)) { + /* + * If not specified we use the lowest kvno + * for the first hour after an update. + */ + if (preferr_current) { + use_previous = false; + } else if (previous_kvno < current_kvno) { + use_previous = true; + } else { + use_previous = false; + } + } else if (kvno == current_kvno) { + /* + * Exact match ... + */ + use_previous = false; + } else if (kvno == previous_kvno) { + /* + * Exact match ... + */ + use_previous = true; + } else { + /* + * Fallback to the current one for anything else + */ + use_previous = false; + } + + if (use_previous) { + auth_array = &password_blob.previous; + auth_kvno = &previous_kvno; + } else { + auth_array = &password_blob.current; + auth_kvno = ¤t_kvno; + } + + /* use the kvno the client specified, if available */ + if (flags & SDB_F_KVNO_SPECIFIED) { + entry->kvno = kvno; + } else { + entry->kvno = *auth_kvno; + } + + for (i=0; i < auth_array->count; i++) { + if (auth_array->array[i].AuthType == TRUST_AUTH_TYPE_CLEAR) { + bool ok; + + password_utf16 = data_blob_const(auth_array->array[i].AuthInfo.clear.password, + auth_array->array[i].AuthInfo.clear.size); + if (password_utf16.length == 0) { + break; + } + + if (supported_enctypes & ENC_RC4_HMAC_MD5) { + mdfour(_password_hash.hash, password_utf16.data, password_utf16.length); + if (password_hash == NULL) { + num_keys += 1; + } + password_hash = &_password_hash; + } + + if (!(supported_enctypes & (ENC_HMAC_SHA1_96_AES128|ENC_HMAC_SHA1_96_AES256))) { + break; + } + + ok = convert_string_talloc(mem_ctx, + CH_UTF16MUNGED, CH_UTF8, + password_utf16.data, + password_utf16.length, + (void *)&password_utf8.data, + &password_utf8.length); + if (!ok) { + krb5_clear_error_message(context); + ret = ENOMEM; + goto out; + } + + if (supported_enctypes & ENC_HMAC_SHA1_96_AES128) { + num_keys += 1; + } + if (supported_enctypes & ENC_HMAC_SHA1_96_AES256) { + num_keys += 1; + } + break; + } else if (auth_array->array[i].AuthType == TRUST_AUTH_TYPE_NT4OWF) { + if (supported_enctypes & ENC_RC4_HMAC_MD5) { + password_hash = &auth_array->array[i].AuthInfo.nt4owf.password; + num_keys += 1; + } + } + } + + /* Must have found a cleartext or MD4 password */ + if (num_keys == 0) { + DEBUG(1,(__location__ ": no usable key found\n")); + krb5_clear_error_message(context); + ret = SDB_ERR_NOENTRY; + goto out; + } + + entry->keys.val = calloc(num_keys, sizeof(struct sdb_key)); + if (entry->keys.val == NULL) { + krb5_clear_error_message(context); + ret = ENOMEM; + goto out; + } + + if (password_utf8.length != 0) { + struct sdb_key key = {}; + krb5_const_principal salt_principal = entry->principal; + krb5_data salt; + krb5_data cleartext_data; + + cleartext_data.data = discard_const_p(char, password_utf8.data); + cleartext_data.length = password_utf8.length; + + ret = smb_krb5_get_pw_salt(context, + salt_principal, + &salt); + if (ret != 0) { + goto out; + } + + if (supported_enctypes & ENC_HMAC_SHA1_96_AES256) { + ret = smb_krb5_create_key_from_string(context, + salt_principal, + &salt, + &cleartext_data, + ENCTYPE_AES256_CTS_HMAC_SHA1_96, + &key.key); + if (ret != 0) { + smb_krb5_free_data_contents(context, &salt); + goto out; + } + + entry->keys.val[entry->keys.len] = key; + entry->keys.len++; + } + + if (supported_enctypes & ENC_HMAC_SHA1_96_AES128) { + ret = smb_krb5_create_key_from_string(context, + salt_principal, + &salt, + &cleartext_data, + ENCTYPE_AES128_CTS_HMAC_SHA1_96, + &key.key); + if (ret != 0) { + smb_krb5_free_data_contents(context, &salt); + goto out; + } + + entry->keys.val[entry->keys.len] = key; + entry->keys.len++; + } + + smb_krb5_free_data_contents(context, &salt); + } + + if (password_hash != NULL) { + struct sdb_key key = {}; + + ret = smb_krb5_keyblock_init_contents(context, + ENCTYPE_ARCFOUR_HMAC, + password_hash->hash, + sizeof(password_hash->hash), + &key.key); + if (ret != 0) { + goto out; + } + + entry->keys.val[entry->keys.len] = key; + entry->keys.len++; + } + + entry->flags = int2SDBFlags(0); + entry->flags.immutable = 1; + entry->flags.invalid = 0; + entry->flags.server = 1; + entry->flags.require_preauth = 1; + + entry->pw_end = NULL; + + entry->max_life = NULL; + + entry->max_renew = NULL; + + /* Match Windows behavior and allow forwardable flag in cross-realm. */ + entry->flags.forwardable = 1; + + samba_kdc_sort_keys(&entry->keys); + + ret = sdb_entry_set_etypes(entry); + if (ret) { + goto out; + } + + { + bool add_aes256 = + supported_session_etypes & KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96; + bool add_aes128 = + supported_session_etypes & KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96; + bool add_rc4 = + supported_session_etypes & ENC_RC4_HMAC_MD5; + ret = sdb_entry_set_session_etypes(entry, + add_aes256, + add_aes128, + add_rc4); + if (ret) { + goto out; + } + } + + p->msg = talloc_steal(p, msg); + +out: + TALLOC_FREE(partner_realm); + + if (ret != 0) { + /* This doesn't free ent itself, that is for the eventual caller to do */ + sdb_entry_free(entry); + } else { + talloc_steal(kdc_db_ctx, p); + } + + return ret; + +} + +static krb5_error_code samba_kdc_lookup_trust(krb5_context context, struct ldb_context *ldb_ctx, + TALLOC_CTX *mem_ctx, + const char *realm, + struct ldb_dn *realm_dn, + struct ldb_message **pmsg) +{ + NTSTATUS status; + const char * const *attrs = trust_attrs; + + status = dsdb_trust_search_tdo(ldb_ctx, realm, realm, + attrs, mem_ctx, pmsg); + if (NT_STATUS_IS_OK(status)) { + return 0; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + return SDB_ERR_NOENTRY; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) { + int ret = ENOMEM; + krb5_set_error_message(context, ret, "get_sam_result_trust: out of memory"); + return ret; + } else { + int ret = EINVAL; + krb5_set_error_message(context, ret, "get_sam_result_trust: %s", nt_errstr(status)); + return ret; + } +} + +static krb5_error_code samba_kdc_lookup_client(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + TALLOC_CTX *mem_ctx, + krb5_const_principal principal, + const char **attrs, + struct ldb_dn **realm_dn, + struct ldb_message **msg) +{ + NTSTATUS nt_status; + char *principal_string = NULL; + + if (smb_krb5_principal_get_type(context, principal) == KRB5_NT_ENTERPRISE_PRINCIPAL) { + principal_string = smb_krb5_principal_get_comp_string(mem_ctx, context, + principal, 0); + if (principal_string == NULL) { + return ENOMEM; + } + } else { + char *principal_string_m = NULL; + krb5_error_code ret; + + ret = krb5_unparse_name(context, principal, &principal_string_m); + if (ret != 0) { + return ret; + } + + principal_string = talloc_strdup(mem_ctx, principal_string_m); + SAFE_FREE(principal_string_m); + if (principal_string == NULL) { + return ENOMEM; + } + } + + nt_status = sam_get_results_principal(kdc_db_ctx->samdb, + mem_ctx, principal_string, attrs, + realm_dn, msg); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_SUCH_USER)) { + krb5_principal fallback_principal = NULL; + unsigned int num_comp; + char *fallback_realm = NULL; + char *fallback_account = NULL; + krb5_error_code ret; + + ret = krb5_parse_name(context, principal_string, + &fallback_principal); + TALLOC_FREE(principal_string); + if (ret != 0) { + return ret; + } + + num_comp = krb5_princ_size(context, fallback_principal); + fallback_realm = smb_krb5_principal_get_realm( + mem_ctx, context, fallback_principal); + if (fallback_realm == NULL) { + krb5_free_principal(context, fallback_principal); + return ENOMEM; + } + + if (num_comp == 1) { + size_t len; + + fallback_account = smb_krb5_principal_get_comp_string(mem_ctx, + context, fallback_principal, 0); + if (fallback_account == NULL) { + krb5_free_principal(context, fallback_principal); + TALLOC_FREE(fallback_realm); + return ENOMEM; + } + + len = strlen(fallback_account); + if (len >= 2 && fallback_account[len - 1] == '$') { + TALLOC_FREE(fallback_account); + } + } + krb5_free_principal(context, fallback_principal); + fallback_principal = NULL; + + if (fallback_account != NULL) { + char *with_dollar; + + with_dollar = talloc_asprintf(mem_ctx, "%s$", + fallback_account); + if (with_dollar == NULL) { + TALLOC_FREE(fallback_realm); + return ENOMEM; + } + TALLOC_FREE(fallback_account); + + ret = smb_krb5_make_principal(context, + &fallback_principal, + fallback_realm, + with_dollar, NULL); + TALLOC_FREE(with_dollar); + if (ret != 0) { + TALLOC_FREE(fallback_realm); + return ret; + } + } + TALLOC_FREE(fallback_realm); + + if (fallback_principal != NULL) { + char *fallback_string = NULL; + + ret = krb5_unparse_name(context, + fallback_principal, + &fallback_string); + if (ret != 0) { + krb5_free_principal(context, fallback_principal); + return ret; + } + + nt_status = sam_get_results_principal(kdc_db_ctx->samdb, + mem_ctx, + fallback_string, + attrs, + realm_dn, msg); + SAFE_FREE(fallback_string); + } + krb5_free_principal(context, fallback_principal); + fallback_principal = NULL; + } + TALLOC_FREE(principal_string); + + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_SUCH_USER)) { + return SDB_ERR_NOENTRY; + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MEMORY)) { + return ENOMEM; + } else if (!NT_STATUS_IS_OK(nt_status)) { + return EINVAL; + } + + return 0; +} + +static krb5_error_code samba_kdc_fetch_client(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + TALLOC_CTX *mem_ctx, + krb5_const_principal principal, + unsigned flags, + krb5_kvno kvno, + struct sdb_entry *entry) +{ + struct ldb_dn *realm_dn; + krb5_error_code ret; + struct ldb_message *msg = NULL; + + ret = samba_kdc_lookup_client(context, kdc_db_ctx, + mem_ctx, principal, user_attrs, + &realm_dn, &msg); + if (ret != 0) { + return ret; + } + + ret = samba_kdc_message2entry(context, kdc_db_ctx, mem_ctx, + principal, SAMBA_KDC_ENT_TYPE_CLIENT, + flags, kvno, + realm_dn, msg, entry); + return ret; +} + +static krb5_error_code samba_kdc_fetch_krbtgt(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + TALLOC_CTX *mem_ctx, + krb5_const_principal principal, + unsigned flags, + uint32_t kvno, + struct sdb_entry *entry) +{ + struct loadparm_context *lp_ctx = kdc_db_ctx->lp_ctx; + krb5_error_code ret; + struct ldb_message *msg = NULL; + struct ldb_dn *realm_dn = ldb_get_default_basedn(kdc_db_ctx->samdb); + char *realm_from_princ; + char *realm_princ_comp = smb_krb5_principal_get_comp_string(mem_ctx, context, principal, 1); + + realm_from_princ = smb_krb5_principal_get_realm( + mem_ctx, context, principal); + if (realm_from_princ == NULL) { + /* can't happen */ + return SDB_ERR_NOENTRY; + } + + if (krb5_princ_size(context, principal) != 2 + || (principal_comp_strcmp(context, principal, 0, KRB5_TGS_NAME) != 0)) { + /* Not a krbtgt */ + return SDB_ERR_NOENTRY; + } + + /* krbtgt case. Either us or a trusted realm */ + + if (lpcfg_is_my_domain_or_realm(lp_ctx, realm_from_princ) + && lpcfg_is_my_domain_or_realm(lp_ctx, realm_princ_comp)) { + /* us, or someone quite like us */ + /* Cludge, cludge cludge. If the realm part of krbtgt/realm, + * is in our db, then direct the caller at our primary + * krbtgt */ + + int lret; + unsigned int krbtgt_number; + /* w2k8r2 sometimes gives us a kvno of 255 for inter-domain + trust tickets. We don't yet know what this means, but we do + seem to need to treat it as unspecified */ + if (flags & SDB_F_KVNO_SPECIFIED) { + krbtgt_number = SAMBA_KVNO_GET_KRBTGT(kvno); + if (kdc_db_ctx->rodc) { + if (krbtgt_number != kdc_db_ctx->my_krbtgt_number) { + return SDB_ERR_NOT_FOUND_HERE; + } + } + } else { + krbtgt_number = kdc_db_ctx->my_krbtgt_number; + } + + if (krbtgt_number == kdc_db_ctx->my_krbtgt_number) { + lret = dsdb_search_one(kdc_db_ctx->samdb, mem_ctx, + &msg, kdc_db_ctx->krbtgt_dn, LDB_SCOPE_BASE, + krbtgt_attrs, DSDB_SEARCH_NO_GLOBAL_CATALOG, + "(objectClass=user)"); + } else { + /* We need to look up an RODC krbtgt (perhaps + * ours, if we are an RODC, perhaps another + * RODC if we are a read-write DC */ + lret = dsdb_search_one(kdc_db_ctx->samdb, mem_ctx, + &msg, realm_dn, LDB_SCOPE_SUBTREE, + krbtgt_attrs, + DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_NO_GLOBAL_CATALOG, + "(&(objectClass=user)(msDS-SecondaryKrbTgtNumber=%u))", (unsigned)(krbtgt_number)); + } + + if (lret == LDB_ERR_NO_SUCH_OBJECT) { + krb5_warnx(context, "samba_kdc_fetch: could not find KRBTGT number %u in DB!", + (unsigned)(krbtgt_number)); + krb5_set_error_message(context, SDB_ERR_NOENTRY, + "samba_kdc_fetch: could not find KRBTGT number %u in DB!", + (unsigned)(krbtgt_number)); + return SDB_ERR_NOENTRY; + } else if (lret != LDB_SUCCESS) { + krb5_warnx(context, "samba_kdc_fetch: could not find KRBTGT number %u in DB!", + (unsigned)(krbtgt_number)); + krb5_set_error_message(context, SDB_ERR_NOENTRY, + "samba_kdc_fetch: could not find KRBTGT number %u in DB!", + (unsigned)(krbtgt_number)); + return SDB_ERR_NOENTRY; + } + + ret = samba_kdc_message2entry(context, kdc_db_ctx, mem_ctx, + principal, SAMBA_KDC_ENT_TYPE_KRBTGT, + flags, kvno, realm_dn, msg, entry); + if (ret != 0) { + krb5_warnx(context, "samba_kdc_fetch: self krbtgt message2entry failed"); + } + return ret; + + } else { + enum trust_direction direction = UNKNOWN; + const char *realm = NULL; + + /* Either an inbound or outbound trust */ + + if (strcasecmp(lpcfg_realm(lp_ctx), realm_from_princ) == 0) { + /* look for inbound trust */ + direction = INBOUND; + realm = realm_princ_comp; + } else if (principal_comp_strcasecmp(context, principal, 1, lpcfg_realm(lp_ctx)) == 0) { + /* look for outbound trust */ + direction = OUTBOUND; + realm = realm_from_princ; + } else { + krb5_warnx(context, "samba_kdc_fetch: not our realm for trusts ('%s', '%s')", + realm_from_princ, + realm_princ_comp); + krb5_set_error_message(context, SDB_ERR_NOENTRY, "samba_kdc_fetch: not our realm for trusts ('%s', '%s')", + realm_from_princ, + realm_princ_comp); + return SDB_ERR_NOENTRY; + } + + /* Trusted domains are under CN=system */ + + ret = samba_kdc_lookup_trust(context, kdc_db_ctx->samdb, + mem_ctx, + realm, realm_dn, &msg); + + if (ret != 0) { + krb5_warnx(context, "samba_kdc_fetch: could not find principal in DB"); + krb5_set_error_message(context, ret, "samba_kdc_fetch: could not find principal in DB"); + return ret; + } + + ret = samba_kdc_trust_message2entry(context, kdc_db_ctx, mem_ctx, + direction, + realm_dn, flags, kvno, msg, entry); + if (ret != 0) { + krb5_warnx(context, "samba_kdc_fetch: trust_message2entry failed for %s", + ldb_dn_get_linearized(msg->dn)); + krb5_set_error_message(context, ret, "samba_kdc_fetch: " + "trust_message2entry failed for %s", + ldb_dn_get_linearized(msg->dn)); + } + return ret; + } + +} + +static krb5_error_code samba_kdc_lookup_server(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + TALLOC_CTX *mem_ctx, + krb5_const_principal principal, + unsigned flags, + const char **attrs, + struct ldb_dn **realm_dn, + struct ldb_message **msg) +{ + krb5_error_code ret; + if ((smb_krb5_principal_get_type(context, principal) != KRB5_NT_ENTERPRISE_PRINCIPAL) + && krb5_princ_size(context, principal) >= 2) { + /* 'normal server' case */ + int ldb_ret; + NTSTATUS nt_status; + struct ldb_dn *user_dn; + char *principal_string; + + ret = krb5_unparse_name_flags(context, principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, + &principal_string); + if (ret != 0) { + return ret; + } + + /* At this point we may find the host is known to be + * in a different realm, so we should generate a + * referral instead */ + nt_status = crack_service_principal_name(kdc_db_ctx->samdb, + mem_ctx, principal_string, + &user_dn, realm_dn); + free(principal_string); + + if (!NT_STATUS_IS_OK(nt_status)) { + return SDB_ERR_NOENTRY; + } + + ldb_ret = dsdb_search_one(kdc_db_ctx->samdb, + mem_ctx, + msg, user_dn, LDB_SCOPE_BASE, + attrs, + DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_NO_GLOBAL_CATALOG, + "(objectClass=*)"); + if (ldb_ret != LDB_SUCCESS) { + return SDB_ERR_NOENTRY; + } + return 0; + } else if (!(flags & SDB_F_FOR_AS_REQ) + && smb_krb5_principal_get_type(context, principal) == KRB5_NT_ENTERPRISE_PRINCIPAL) { + /* + * The behaviour of accepting an + * KRB5_NT_ENTERPRISE_PRINCIPAL server principal + * containing a UPN only applies to TGS-REQ packets, + * not AS-REQ packets. + */ + return samba_kdc_lookup_client(context, kdc_db_ctx, + mem_ctx, principal, attrs, + realm_dn, msg); + } else { + /* + * This case is for: + * - the AS-REQ, where we only accept + * samAccountName based lookups for the server, no + * matter if the name is an + * KRB5_NT_ENTERPRISE_PRINCIPAL or not + * - for the TGS-REQ when we are not given an + * KRB5_NT_ENTERPRISE_PRINCIPAL, which also must + * only lookup samAccountName based names. + */ + int lret; + char *short_princ; + krb5_principal enterprise_principal = NULL; + krb5_const_principal used_principal = NULL; + char *name1 = NULL; + size_t len1 = 0; + char *filter = NULL; + + if (smb_krb5_principal_get_type(context, principal) == KRB5_NT_ENTERPRISE_PRINCIPAL) { + char *str = NULL; + /* Need to reparse the enterprise principal to find the real target */ + if (krb5_princ_size(context, principal) != 1) { + ret = KRB5_PARSE_MALFORMED; + krb5_set_error_message(context, ret, "samba_kdc_lookup_server: request for an " + "enterprise principal with wrong (%d) number of components", + krb5_princ_size(context, principal)); + return ret; + } + str = smb_krb5_principal_get_comp_string(mem_ctx, context, principal, 0); + if (str == NULL) { + return KRB5_PARSE_MALFORMED; + } + ret = krb5_parse_name(context, str, + &enterprise_principal); + talloc_free(str); + if (ret) { + return ret; + } + used_principal = enterprise_principal; + } else { + used_principal = principal; + } + + /* server as client principal case, but we must not lookup userPrincipalNames */ + *realm_dn = ldb_get_default_basedn(kdc_db_ctx->samdb); + + /* TODO: Check if it is our realm, otherwise give referral */ + + ret = krb5_unparse_name_flags(context, used_principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM | + KRB5_PRINCIPAL_UNPARSE_DISPLAY, + &short_princ); + used_principal = NULL; + krb5_free_principal(context, enterprise_principal); + enterprise_principal = NULL; + + if (ret != 0) { + krb5_set_error_message(context, ret, "samba_kdc_lookup_principal: could not parse principal"); + krb5_warnx(context, "samba_kdc_lookup_principal: could not parse principal"); + return ret; + } + + name1 = ldb_binary_encode_string(mem_ctx, short_princ); + SAFE_FREE(short_princ); + if (name1 == NULL) { + return ENOMEM; + } + len1 = strlen(name1); + if (len1 >= 1 && name1[len1 - 1] != '$') { + filter = talloc_asprintf(mem_ctx, + "(&(objectClass=user)(|(samAccountName=%s)(samAccountName=%s$)))", + name1, name1); + if (filter == NULL) { + return ENOMEM; + } + } else { + filter = talloc_asprintf(mem_ctx, + "(&(objectClass=user)(samAccountName=%s))", + name1); + if (filter == NULL) { + return ENOMEM; + } + } + + lret = dsdb_search_one(kdc_db_ctx->samdb, mem_ctx, msg, + *realm_dn, LDB_SCOPE_SUBTREE, + attrs, + DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_NO_GLOBAL_CATALOG, + "%s", filter); + if (lret == LDB_ERR_NO_SUCH_OBJECT) { + DEBUG(10, ("Failed to find an entry for %s filter:%s\n", + name1, filter)); + return SDB_ERR_NOENTRY; + } + if (lret == LDB_ERR_CONSTRAINT_VIOLATION) { + DEBUG(10, ("Failed to find unique entry for %s filter:%s\n", + name1, filter)); + return SDB_ERR_NOENTRY; + } + if (lret != LDB_SUCCESS) { + DEBUG(0, ("Failed single search for %s - %s\n", + name1, ldb_errstring(kdc_db_ctx->samdb))); + return SDB_ERR_NOENTRY; + } + return 0; + } + return SDB_ERR_NOENTRY; +} + + + +static krb5_error_code samba_kdc_fetch_server(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + TALLOC_CTX *mem_ctx, + krb5_const_principal principal, + unsigned flags, + krb5_kvno kvno, + struct sdb_entry *entry) +{ + krb5_error_code ret; + struct ldb_dn *realm_dn; + struct ldb_message *msg; + + ret = samba_kdc_lookup_server(context, kdc_db_ctx, mem_ctx, principal, + flags, server_attrs, &realm_dn, &msg); + if (ret != 0) { + return ret; + } + + ret = samba_kdc_message2entry(context, kdc_db_ctx, mem_ctx, + principal, SAMBA_KDC_ENT_TYPE_SERVER, + flags, kvno, + realm_dn, msg, entry); + if (ret != 0) { + char *client_name = NULL; + krb5_error_code code; + + code = krb5_unparse_name(context, principal, &client_name); + if (code == 0) { + krb5_warnx(context, + "samba_kdc_fetch: message2entry failed for " + "%s", + client_name); + } else { + krb5_warnx(context, + "samba_kdc_fetch: message2entry and " + "krb5_unparse_name failed"); + } + SAFE_FREE(client_name); + } + + return ret; +} + +static krb5_error_code samba_kdc_lookup_realm(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + TALLOC_CTX *mem_ctx, + krb5_const_principal principal, + unsigned flags, + struct sdb_entry *entry) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status; + krb5_error_code ret; + bool check_realm = false; + const char *realm = NULL; + struct dsdb_trust_routing_table *trt = NULL; + const struct lsa_TrustDomainInfoInfoEx *tdo = NULL; + unsigned int num_comp; + bool ok; + char *upper = NULL; + + num_comp = krb5_princ_size(context, principal); + + if (flags & SDB_F_GET_CLIENT) { + if (flags & SDB_F_FOR_AS_REQ) { + check_realm = true; + } + } + if (flags & SDB_F_GET_SERVER) { + if (flags & SDB_F_FOR_TGS_REQ) { + check_realm = true; + } + } + + if (!check_realm) { + TALLOC_FREE(frame); + return 0; + } + + realm = smb_krb5_principal_get_realm(frame, context, principal); + if (realm == NULL) { + TALLOC_FREE(frame); + return ENOMEM; + } + + /* + * The requested realm needs to be our own + */ + ok = lpcfg_is_my_domain_or_realm(kdc_db_ctx->lp_ctx, realm); + if (!ok) { + /* + * The request is not for us... + */ + TALLOC_FREE(frame); + return SDB_ERR_NOENTRY; + } + + if (smb_krb5_principal_get_type(context, principal) == KRB5_NT_ENTERPRISE_PRINCIPAL) { + char *principal_string = NULL; + krb5_principal enterprise_principal = NULL; + char *enterprise_realm = NULL; + + if (num_comp != 1) { + TALLOC_FREE(frame); + return SDB_ERR_NOENTRY; + } + + principal_string = smb_krb5_principal_get_comp_string(frame, context, + principal, 0); + if (principal_string == NULL) { + TALLOC_FREE(frame); + return ENOMEM; + } + + ret = krb5_parse_name(context, principal_string, + &enterprise_principal); + TALLOC_FREE(principal_string); + if (ret) { + TALLOC_FREE(frame); + return ret; + } + + enterprise_realm = smb_krb5_principal_get_realm( + frame, context, enterprise_principal); + krb5_free_principal(context, enterprise_principal); + if (enterprise_realm != NULL) { + realm = enterprise_realm; + } + } + + if (flags & SDB_F_GET_SERVER) { + char *service_realm = NULL; + + ret = principal_comp_strcmp(context, principal, 0, KRB5_TGS_NAME); + if (ret == 0) { + /* + * we need to search krbtgt/ locally + */ + TALLOC_FREE(frame); + return 0; + } + + /* + * We need to check the last component against the routing table. + * + * Note this works only with 2 or 3 component principals, e.g: + * + * servicePrincipalName: ldap/W2K8R2-219.bla.base + * servicePrincipalName: ldap/W2K8R2-219.bla.base/bla.base + * servicePrincipalName: ldap/W2K8R2-219.bla.base/ForestDnsZones.bla.base + * servicePrincipalName: ldap/W2K8R2-219.bla.base/DomainDnsZones.bla.base + */ + + if (num_comp == 2 || num_comp == 3) { + service_realm = smb_krb5_principal_get_comp_string(frame, + context, + principal, + num_comp - 1); + } + + if (service_realm != NULL) { + realm = service_realm; + } + } + + ok = lpcfg_is_my_domain_or_realm(kdc_db_ctx->lp_ctx, realm); + if (ok) { + /* + * skip the expensive routing lookup + */ + TALLOC_FREE(frame); + return 0; + } + + status = dsdb_trust_routing_table_load(kdc_db_ctx->samdb, + frame, &trt); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return EINVAL; + } + + tdo = dsdb_trust_routing_by_name(trt, realm); + if (tdo == NULL) { + /* + * This principal has to be local + */ + TALLOC_FREE(frame); + return 0; + } + + if (tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) { + /* + * TODO: handle the routing within the forest + * + * This should likely be handled in + * samba_kdc_message2entry() in case we're + * a global catalog. We'd need to check + * if realm_dn is our own domain and derive + * the dns domain name from realm_dn and check that + * against the routing table or fallback to + * the tdo we found here. + * + * But for now we don't support multiple domains + * in our forest correctly anyway. + * + * Just search in our local database. + */ + TALLOC_FREE(frame); + return 0; + } + + ZERO_STRUCTP(entry); + + ret = krb5_copy_principal(context, principal, + &entry->principal); + if (ret) { + TALLOC_FREE(frame); + return ret; + } + + upper = strupper_talloc(frame, tdo->domain_name.string); + if (upper == NULL) { + TALLOC_FREE(frame); + return ENOMEM; + } + + ret = smb_krb5_principal_set_realm(context, + entry->principal, + upper); + if (ret) { + TALLOC_FREE(frame); + return ret; + } + + TALLOC_FREE(frame); + return SDB_ERR_WRONG_REALM; +} + +krb5_error_code samba_kdc_fetch(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + krb5_const_principal principal, + unsigned flags, + krb5_kvno kvno, + struct sdb_entry *entry) +{ + krb5_error_code ret = SDB_ERR_NOENTRY; + TALLOC_CTX *mem_ctx; + + mem_ctx = talloc_named(kdc_db_ctx, 0, "samba_kdc_fetch context"); + if (!mem_ctx) { + ret = ENOMEM; + krb5_set_error_message(context, ret, "samba_kdc_fetch: talloc_named() failed!"); + return ret; + } + + ret = samba_kdc_lookup_realm(context, kdc_db_ctx, mem_ctx, + principal, flags, entry); + if (ret != 0) { + goto done; + } + + ret = SDB_ERR_NOENTRY; + + if (flags & SDB_F_GET_CLIENT) { + ret = samba_kdc_fetch_client(context, kdc_db_ctx, mem_ctx, principal, flags, kvno, entry); + if (ret != SDB_ERR_NOENTRY) goto done; + } + if (flags & SDB_F_GET_SERVER) { + /* krbtgt fits into this situation for trusted realms, and for resolving different versions of our own realm name */ + ret = samba_kdc_fetch_krbtgt(context, kdc_db_ctx, mem_ctx, principal, flags, kvno, entry); + if (ret != SDB_ERR_NOENTRY) goto done; + + /* We return 'no entry' if it does not start with krbtgt/, so move to the common case quickly */ + ret = samba_kdc_fetch_server(context, kdc_db_ctx, mem_ctx, principal, flags, kvno, entry); + if (ret != SDB_ERR_NOENTRY) goto done; + } + if (flags & SDB_F_GET_KRBTGT) { + ret = samba_kdc_fetch_krbtgt(context, kdc_db_ctx, mem_ctx, principal, flags, kvno, entry); + if (ret != SDB_ERR_NOENTRY) goto done; + } + +done: + talloc_free(mem_ctx); + return ret; +} + +struct samba_kdc_seq { + unsigned int index; + unsigned int count; + struct ldb_message **msgs; + struct ldb_dn *realm_dn; +}; + +static krb5_error_code samba_kdc_seq(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + struct sdb_entry *entry) +{ + krb5_error_code ret; + struct samba_kdc_seq *priv = kdc_db_ctx->seq_ctx; + const char *realm = lpcfg_realm(kdc_db_ctx->lp_ctx); + struct ldb_message *msg = NULL; + const char *sAMAccountName = NULL; + krb5_principal principal = NULL; + TALLOC_CTX *mem_ctx; + + if (!priv) { + return SDB_ERR_NOENTRY; + } + + mem_ctx = talloc_named(priv, 0, "samba_kdc_seq context"); + + if (!mem_ctx) { + ret = ENOMEM; + krb5_set_error_message(context, ret, "samba_kdc_seq: talloc_named() failed!"); + return ret; + } + + while (priv->index < priv->count) { + msg = priv->msgs[priv->index++]; + + sAMAccountName = ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL); + if (sAMAccountName != NULL) { + break; + } + } + + if (sAMAccountName == NULL) { + ret = SDB_ERR_NOENTRY; + goto out; + } + + ret = smb_krb5_make_principal(context, &principal, + realm, sAMAccountName, NULL); + if (ret != 0) { + goto out; + } + + ret = samba_kdc_message2entry(context, kdc_db_ctx, mem_ctx, + principal, SAMBA_KDC_ENT_TYPE_ANY, + SDB_F_ADMIN_DATA|SDB_F_GET_ANY, + 0 /* kvno */, + priv->realm_dn, msg, entry); + +out: + if (principal != NULL) { + krb5_free_principal(context, principal); + } + + if (ret != 0) { + TALLOC_FREE(priv); + kdc_db_ctx->seq_ctx = NULL; + } else { + talloc_free(mem_ctx); + } + + return ret; +} + +krb5_error_code samba_kdc_firstkey(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + struct sdb_entry *entry) +{ + struct ldb_context *ldb_ctx = kdc_db_ctx->samdb; + struct samba_kdc_seq *priv = kdc_db_ctx->seq_ctx; + char *realm; + struct ldb_result *res = NULL; + krb5_error_code ret; + TALLOC_CTX *mem_ctx; + int lret; + + if (priv) { + TALLOC_FREE(priv); + kdc_db_ctx->seq_ctx = NULL; + } + + priv = (struct samba_kdc_seq *) talloc(kdc_db_ctx, struct samba_kdc_seq); + if (!priv) { + ret = ENOMEM; + krb5_set_error_message(context, ret, "talloc: out of memory"); + return ret; + } + + priv->index = 0; + priv->msgs = NULL; + priv->realm_dn = ldb_get_default_basedn(ldb_ctx); + priv->count = 0; + + mem_ctx = talloc_named(priv, 0, "samba_kdc_firstkey context"); + + if (!mem_ctx) { + ret = ENOMEM; + krb5_set_error_message(context, ret, "samba_kdc_firstkey: talloc_named() failed!"); + return ret; + } + + ret = krb5_get_default_realm(context, &realm); + if (ret != 0) { + TALLOC_FREE(priv); + return ret; + } + krb5_free_default_realm(context, realm); + + lret = dsdb_search(ldb_ctx, priv, &res, + priv->realm_dn, LDB_SCOPE_SUBTREE, user_attrs, + DSDB_SEARCH_NO_GLOBAL_CATALOG, + "(objectClass=user)"); + + if (lret != LDB_SUCCESS) { + TALLOC_FREE(priv); + return SDB_ERR_NOENTRY; + } + + priv->count = res->count; + priv->msgs = talloc_steal(priv, res->msgs); + talloc_free(res); + + kdc_db_ctx->seq_ctx = priv; + + ret = samba_kdc_seq(context, kdc_db_ctx, entry); + + if (ret != 0) { + TALLOC_FREE(priv); + kdc_db_ctx->seq_ctx = NULL; + } else { + talloc_free(mem_ctx); + } + return ret; +} + +krb5_error_code samba_kdc_nextkey(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + struct sdb_entry *entry) +{ + return samba_kdc_seq(context, kdc_db_ctx, entry); +} + +/* Check if a given entry may delegate or do s4u2self to this target principal + * + * The safest way to determine 'self' is to check the DB record made at + * the time the principal was presented to the KDC. + */ +krb5_error_code +samba_kdc_check_client_matches_target_service(krb5_context context, + struct samba_kdc_entry *skdc_entry_client, + struct samba_kdc_entry *skdc_entry_server_target) +{ + struct dom_sid *orig_sid; + struct dom_sid *target_sid; + TALLOC_CTX *frame = talloc_stackframe(); + + orig_sid = samdb_result_dom_sid(frame, + skdc_entry_client->msg, + "objectSid"); + target_sid = samdb_result_dom_sid(frame, + skdc_entry_server_target->msg, + "objectSid"); + + /* + * Allow delegation to the same record (representing a + * principal), even if by a different name. The easy and safe + * way to prove this is by SID comparison + */ + if (!(orig_sid && target_sid && dom_sid_equal(orig_sid, target_sid))) { + talloc_free(frame); + return KRB5KRB_AP_ERR_BADMATCH; + } + + talloc_free(frame); + return 0; +} + +/* Certificates printed by a the Certificate Authority might have a + * slightly different form of the user principal name to that in the + * database. Allow a mismatch where they both refer to the same + * SID */ + +krb5_error_code +samba_kdc_check_pkinit_ms_upn_match(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + struct samba_kdc_entry *skdc_entry, + krb5_const_principal certificate_principal) +{ + krb5_error_code ret; + struct ldb_dn *realm_dn; + struct ldb_message *msg; + struct dom_sid *orig_sid; + struct dom_sid *target_sid; + const char *ms_upn_check_attrs[] = { + "objectSid", NULL + }; + + TALLOC_CTX *mem_ctx = talloc_named(kdc_db_ctx, 0, "samba_kdc_check_pkinit_ms_upn_match"); + + if (!mem_ctx) { + ret = ENOMEM; + krb5_set_error_message(context, ret, "samba_kdc_fetch: talloc_named() failed!"); + return ret; + } + + ret = samba_kdc_lookup_client(context, kdc_db_ctx, + mem_ctx, certificate_principal, + ms_upn_check_attrs, &realm_dn, &msg); + + if (ret != 0) { + talloc_free(mem_ctx); + return ret; + } + + orig_sid = samdb_result_dom_sid(mem_ctx, skdc_entry->msg, "objectSid"); + target_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid"); + + /* Consider these to be the same principal, even if by a different + * name. The easy and safe way to prove this is by SID + * comparison */ + if (!(orig_sid && target_sid && dom_sid_equal(orig_sid, target_sid))) { + talloc_free(mem_ctx); +#if defined(KRB5KDC_ERR_CLIENT_NAME_MISMATCH) /* MIT */ + return KRB5KDC_ERR_CLIENT_NAME_MISMATCH; +#else /* Heimdal (where this is an enum) */ + return KRB5_KDC_ERR_CLIENT_NAME_MISMATCH; +#endif + } + + talloc_free(mem_ctx); + return ret; +} + +/* + * Check if a given entry may delegate to this target principal + * with S4U2Proxy. + */ +krb5_error_code +samba_kdc_check_s4u2proxy(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + struct samba_kdc_entry *skdc_entry, + krb5_const_principal target_principal) +{ + krb5_error_code ret; + char *tmp = NULL; + const char *client_dn = NULL; + const char *target_principal_name = NULL; + struct ldb_message_element *el; + struct ldb_val val; + unsigned int i; + bool found = false; + + TALLOC_CTX *mem_ctx = talloc_named(kdc_db_ctx, 0, "samba_kdc_check_s4u2proxy"); + + if (!mem_ctx) { + ret = ENOMEM; + krb5_set_error_message(context, ret, + "samba_kdc_check_s4u2proxy:" + " talloc_named() failed!"); + return ret; + } + + client_dn = ldb_dn_get_linearized(skdc_entry->msg->dn); + if (!client_dn) { + if (errno == 0) { + errno = ENOMEM; + } + ret = errno; + krb5_set_error_message(context, ret, + "samba_kdc_check_s4u2proxy:" + " ldb_dn_get_linearized() failed!"); + return ret; + } + + el = ldb_msg_find_element(skdc_entry->msg, "msDS-AllowedToDelegateTo"); + if (el == NULL) { + ret = ENOENT; + goto bad_option; + } + SMB_ASSERT(el->num_values != 0); + + /* + * This is the Microsoft forwardable flag behavior. + * + * If the proxy (target) principal is NULL, and we have any authorized + * delegation target, allow to forward. + */ + if (target_principal == NULL) { + return 0; + } + + + /* + * The main heimdal code already checked that the target_principal + * belongs to the same realm as the client. + * + * So we just need the principal without the realm, + * as that is what is configured in the "msDS-AllowedToDelegateTo" + * attribute. + */ + ret = krb5_unparse_name_flags(context, target_principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, &tmp); + if (ret) { + talloc_free(mem_ctx); + krb5_set_error_message(context, ret, + "samba_kdc_check_s4u2proxy:" + " krb5_unparse_name() failed!"); + return ret; + } + DEBUG(10,("samba_kdc_check_s4u2proxy: client[%s] for target[%s]\n", + client_dn, tmp)); + + target_principal_name = talloc_strdup(mem_ctx, tmp); + SAFE_FREE(tmp); + if (target_principal_name == NULL) { + ret = ENOMEM; + krb5_set_error_message(context, ret, + "samba_kdc_check_s4u2proxy:" + " talloc_strdup() failed!"); + return ret; + } + + val = data_blob_string_const(target_principal_name); + + for (i=0; i<el->num_values; i++) { + struct ldb_val *val1 = &val; + struct ldb_val *val2 = &el->values[i]; + int cmp; + + if (val1->length != val2->length) { + continue; + } + + cmp = strncasecmp((const char *)val1->data, + (const char *)val2->data, + val1->length); + if (cmp != 0) { + continue; + } + + found = true; + break; + } + + if (!found) { + ret = ENOENT; + goto bad_option; + } + + DEBUG(10,("samba_kdc_check_s4u2proxy: client[%s] allowed target[%s]\n", + client_dn, tmp)); + talloc_free(mem_ctx); + return 0; + +bad_option: + krb5_set_error_message(context, ret, + "samba_kdc_check_s4u2proxy: client[%s] " + "not allowed for delegation to target[%s]", + client_dn, + target_principal_name); + talloc_free(mem_ctx); + return KRB5KDC_ERR_BADOPTION; +} + +/* + * This method is called for S4U2Proxy requests and implements the + * resource-based constrained delegation variant, which can support + * cross-realm delegation. + */ +krb5_error_code samba_kdc_check_s4u2proxy_rbcd( + krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + krb5_const_principal client_principal, + krb5_const_principal server_principal, + krb5_pac header_pac, + struct samba_kdc_entry *proxy_skdc_entry) +{ + krb5_error_code code; + enum ndr_err_code ndr_err; + char *client_name = NULL; + char *server_name = NULL; + const char *proxy_dn = NULL; + const DATA_BLOB *data = NULL; + struct security_descriptor *rbcd_security_descriptor = NULL; + struct auth_user_info_dc *user_info_dc = NULL; + struct auth_session_info *session_info = NULL; + uint32_t session_info_flags = AUTH_SESSION_INFO_SIMPLE_PRIVILEGES; + /* + * Testing shows that although Windows grants SEC_ADS_GENERIC_ALL access + * in security descriptors it creates for RBCD, its KDC only requires + * SEC_ADS_CONTROL_ACCESS for the access check to succeed. + */ + uint32_t access_desired = SEC_ADS_CONTROL_ACCESS; + uint32_t access_granted = 0; + NTSTATUS nt_status; + TALLOC_CTX *mem_ctx = NULL; + + mem_ctx = talloc_named(kdc_db_ctx, + 0, + "samba_kdc_check_s4u2proxy_rbcd"); + if (mem_ctx == NULL) { + errno = ENOMEM; + code = errno; + + return code; + } + + proxy_dn = ldb_dn_get_linearized(proxy_skdc_entry->msg->dn); + if (proxy_dn == NULL) { + DBG_ERR("ldb_dn_get_linearized failed for proxy_dn!\n"); + TALLOC_FREE(mem_ctx); + if (errno == 0) { + errno = ENOMEM; + } + code = errno; + + goto out; + } + + rbcd_security_descriptor = talloc_zero(mem_ctx, + struct security_descriptor); + if (rbcd_security_descriptor == NULL) { + errno = ENOMEM; + code = errno; + + goto out; + } + + code = krb5_unparse_name_flags(context, + client_principal, + KRB5_PRINCIPAL_UNPARSE_DISPLAY, + &client_name); + if (code != 0) { + DBG_ERR("Unable to parse client_principal!\n"); + goto out; + } + + code = krb5_unparse_name_flags(context, + server_principal, + KRB5_PRINCIPAL_UNPARSE_DISPLAY, + &server_name); + if (code != 0) { + DBG_ERR("Unable to parse server_principal!\n"); + SAFE_FREE(client_name); + goto out; + } + + DBG_INFO("Check delegation from client[%s] to server[%s] via " + "proxy[%s]\n", + client_name, + server_name, + proxy_dn); + + code = kerberos_pac_to_user_info_dc(mem_ctx, + header_pac, + context, + &user_info_dc, + NULL, + NULL); + if (code != 0) { + goto out; + } + + if (user_info_dc->info->authenticated) { + session_info_flags |= AUTH_SESSION_INFO_AUTHENTICATED; + } + + nt_status = auth_generate_session_info(mem_ctx, + kdc_db_ctx->lp_ctx, + kdc_db_ctx->samdb, + user_info_dc, + session_info_flags, + &session_info); + if (!NT_STATUS_IS_OK(nt_status)) { + code = map_errno_from_nt_status(nt_status); + goto out; + } + + data = ldb_msg_find_ldb_val(proxy_skdc_entry->msg, + "msDS-AllowedToActOnBehalfOfOtherIdentity"); + if (data == NULL) { + DBG_ERR("Could not find security descriptor " + "msDS-AllowedToActOnBehalfOfOtherIdentity in " + "proxy[%s]\n", + proxy_dn); + code = KRB5KDC_ERR_BADOPTION; + goto out; + } + + ndr_err = ndr_pull_struct_blob( + data, + mem_ctx, + rbcd_security_descriptor, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + errno = ndr_map_error2errno(ndr_err); + DBG_ERR("Failed to unmarshall " + "msDS-AllowedToActOnBehalfOfOtherIdentity " + "security descriptor of proxy[%s]\n", + proxy_dn); + code = KRB5KDC_ERR_BADOPTION; + goto out; + } + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(security_token, session_info->security_token); + NDR_PRINT_DEBUG(security_descriptor, rbcd_security_descriptor); + } + + nt_status = sec_access_check_ds(rbcd_security_descriptor, + session_info->security_token, + access_desired, + &access_granted, + NULL, + NULL); + + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_WARNING("RBCD: sec_access_check_ds(access_desired=%#08x, " + "access_granted:%#08x) failed with: %s\n", + access_desired, + access_granted, + nt_errstr(nt_status)); + + code = KRB5KDC_ERR_BADOPTION; + goto out; + } + + DBG_NOTICE("RBCD: Access granted for client[%s]\n", client_name); + + code = 0; +out: + SAFE_FREE(client_name); + SAFE_FREE(server_name); + + TALLOC_FREE(mem_ctx); + return code; +} + +NTSTATUS samba_kdc_setup_db_ctx(TALLOC_CTX *mem_ctx, struct samba_kdc_base_context *base_ctx, + struct samba_kdc_db_context **kdc_db_ctx_out) +{ + int ldb_ret; + struct ldb_message *msg; + struct auth_session_info *session_info; + struct samba_kdc_db_context *kdc_db_ctx; + /* The idea here is very simple. Using Kerberos to + * authenticate the KDC to the LDAP server is higly likely to + * be circular. + * + * In future we may set this up to use EXERNAL and SSL + * certificates, for now it will almost certainly be NTLMSSP_SET_USERNAME + */ + + kdc_db_ctx = talloc_zero(mem_ctx, struct samba_kdc_db_context); + if (kdc_db_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + kdc_db_ctx->ev_ctx = base_ctx->ev_ctx; + kdc_db_ctx->lp_ctx = base_ctx->lp_ctx; + kdc_db_ctx->msg_ctx = base_ctx->msg_ctx; + + /* get default kdc policy */ + lpcfg_default_kdc_policy(mem_ctx, + base_ctx->lp_ctx, + &kdc_db_ctx->policy.svc_tkt_lifetime, + &kdc_db_ctx->policy.usr_tkt_lifetime, + &kdc_db_ctx->policy.renewal_lifetime); + + session_info = system_session(kdc_db_ctx->lp_ctx); + if (session_info == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + /* Setup the link to secrets.ldb */ + + kdc_db_ctx->secrets_db = secrets_db_connect(kdc_db_ctx, + base_ctx->lp_ctx); + if (kdc_db_ctx->secrets_db == NULL) { + DEBUG(1, ("samba_kdc_setup_db_ctx: " + "Cannot open secrets.ldb for KDC backend!")); + talloc_free(kdc_db_ctx); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + kdc_db_ctx->fx_cookie_dn = ldb_dn_new(kdc_db_ctx, + kdc_db_ctx->secrets_db, + "CN=FX Cookie"); + + /* Setup the link to LDB */ + kdc_db_ctx->samdb = samdb_connect(kdc_db_ctx, + base_ctx->ev_ctx, + base_ctx->lp_ctx, + session_info, + NULL, + 0); + if (kdc_db_ctx->samdb == NULL) { + DEBUG(1, ("samba_kdc_setup_db_ctx: Cannot open samdb for KDC backend!")); + talloc_free(kdc_db_ctx); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + /* Find out our own krbtgt kvno */ + ldb_ret = samdb_rodc(kdc_db_ctx->samdb, &kdc_db_ctx->rodc); + if (ldb_ret != LDB_SUCCESS) { + DEBUG(1, ("samba_kdc_setup_db_ctx: Cannot determine if we are an RODC in KDC backend: %s\n", + ldb_errstring(kdc_db_ctx->samdb))); + talloc_free(kdc_db_ctx); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + if (kdc_db_ctx->rodc) { + int my_krbtgt_number; + const char *secondary_keytab[] = { "msDS-SecondaryKrbTgtNumber", NULL }; + struct ldb_dn *account_dn; + struct ldb_dn *server_dn = samdb_server_dn(kdc_db_ctx->samdb, kdc_db_ctx); + if (!server_dn) { + DEBUG(1, ("samba_kdc_setup_db_ctx: Cannot determine server DN in KDC backend: %s\n", + ldb_errstring(kdc_db_ctx->samdb))); + talloc_free(kdc_db_ctx); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + ldb_ret = samdb_reference_dn(kdc_db_ctx->samdb, kdc_db_ctx, server_dn, + "serverReference", &account_dn); + if (ldb_ret != LDB_SUCCESS) { + DEBUG(1, ("samba_kdc_setup_db_ctx: Cannot determine server account in KDC backend: %s\n", + ldb_errstring(kdc_db_ctx->samdb))); + talloc_free(kdc_db_ctx); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + ldb_ret = samdb_reference_dn(kdc_db_ctx->samdb, kdc_db_ctx, account_dn, + "msDS-KrbTgtLink", &kdc_db_ctx->krbtgt_dn); + talloc_free(account_dn); + if (ldb_ret != LDB_SUCCESS) { + DEBUG(1, ("samba_kdc_setup_db_ctx: Cannot determine RODC krbtgt account in KDC backend: %s\n", + ldb_errstring(kdc_db_ctx->samdb))); + talloc_free(kdc_db_ctx); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + ldb_ret = dsdb_search_one(kdc_db_ctx->samdb, kdc_db_ctx, + &msg, kdc_db_ctx->krbtgt_dn, LDB_SCOPE_BASE, + secondary_keytab, + DSDB_SEARCH_NO_GLOBAL_CATALOG, + "(&(objectClass=user)(msDS-SecondaryKrbTgtNumber=*))"); + if (ldb_ret != LDB_SUCCESS) { + DEBUG(1, ("samba_kdc_setup_db_ctx: Cannot read krbtgt account %s in KDC backend to get msDS-SecondaryKrbTgtNumber: %s: %s\n", + ldb_dn_get_linearized(kdc_db_ctx->krbtgt_dn), + ldb_errstring(kdc_db_ctx->samdb), + ldb_strerror(ldb_ret))); + talloc_free(kdc_db_ctx); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + my_krbtgt_number = ldb_msg_find_attr_as_int(msg, "msDS-SecondaryKrbTgtNumber", -1); + if (my_krbtgt_number == -1) { + DEBUG(1, ("samba_kdc_setup_db_ctx: Cannot read msDS-SecondaryKrbTgtNumber from krbtgt account %s in KDC backend: got %d\n", + ldb_dn_get_linearized(kdc_db_ctx->krbtgt_dn), + my_krbtgt_number)); + talloc_free(kdc_db_ctx); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + kdc_db_ctx->my_krbtgt_number = my_krbtgt_number; + + } else { + kdc_db_ctx->my_krbtgt_number = 0; + ldb_ret = dsdb_search_one(kdc_db_ctx->samdb, kdc_db_ctx, + &msg, + ldb_get_default_basedn(kdc_db_ctx->samdb), + LDB_SCOPE_SUBTREE, + krbtgt_attrs, + DSDB_SEARCH_NO_GLOBAL_CATALOG, + "(&(objectClass=user)(samAccountName=krbtgt))"); + + if (ldb_ret != LDB_SUCCESS) { + DEBUG(1, ("samba_kdc_fetch: could not find own KRBTGT in DB: %s\n", ldb_errstring(kdc_db_ctx->samdb))); + talloc_free(kdc_db_ctx); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + kdc_db_ctx->krbtgt_dn = talloc_steal(kdc_db_ctx, msg->dn); + kdc_db_ctx->my_krbtgt_number = 0; + talloc_free(msg); + } + *kdc_db_ctx_out = kdc_db_ctx; + return NT_STATUS_OK; +} + +krb5_error_code dsdb_extract_aes_256_key(krb5_context context, + TALLOC_CTX *mem_ctx, + const struct ldb_message *msg, + uint32_t user_account_control, + const uint32_t *kvno, + uint32_t *kvno_out, + DATA_BLOB *aes_256_key, + DATA_BLOB *salt) +{ + krb5_error_code krb5_ret; + uint32_t supported_enctypes; + unsigned flags = SDB_F_GET_CLIENT; + struct sdb_entry sentry = {}; + + if (kvno != NULL) { + flags |= SDB_F_KVNO_SPECIFIED; + } + + krb5_ret = samba_kdc_message2entry_keys(context, + mem_ctx, + msg, + false, /* is_krbtgt */ + false, /* is_rodc */ + user_account_control, + SAMBA_KDC_ENT_TYPE_CLIENT, + flags, + (kvno != NULL) ? *kvno : 0, + &sentry, + ENC_HMAC_SHA1_96_AES256, + &supported_enctypes); + if (krb5_ret != 0) { + DBG_ERR("Failed to parse supplementalCredentials " + "of %s with %s kvno using " + "ENCTYPE_HMAC_SHA1_96_AES256 " + "Kerberos Key: %s\n", + ldb_dn_get_linearized(msg->dn), + (kvno != NULL) ? "previous" : "current", + krb5_get_error_message(context, + krb5_ret)); + return krb5_ret; + } + + if ((supported_enctypes & ENC_HMAC_SHA1_96_AES256) == 0 || + sentry.keys.len != 1) { + DBG_INFO("Failed to find a ENCTYPE_HMAC_SHA1_96_AES256 " + "key in supplementalCredentials " + "of %s at KVNO %u (got %u keys, expected 1)\n", + ldb_dn_get_linearized(msg->dn), + sentry.kvno, + sentry.keys.len); + sdb_entry_free(&sentry); + return ENOENT; + } + + if (sentry.keys.val[0].salt == NULL) { + DBG_INFO("Failed to find a salt in " + "supplementalCredentials " + "of %s at KVNO %u\n", + ldb_dn_get_linearized(msg->dn), + sentry.kvno); + sdb_entry_free(&sentry); + return ENOENT; + } + + if (aes_256_key != NULL) { + *aes_256_key = data_blob_talloc(mem_ctx, + KRB5_KEY_DATA(&sentry.keys.val[0].key), + KRB5_KEY_LENGTH(&sentry.keys.val[0].key)); + if (aes_256_key->data == NULL) { + sdb_entry_free(&sentry); + return ENOMEM; + } + talloc_keep_secret(aes_256_key->data); + } + + if (salt != NULL) { + *salt = data_blob_talloc(mem_ctx, + sentry.keys.val[0].salt->salt.data, + sentry.keys.val[0].salt->salt.length); + if (salt->data == NULL) { + sdb_entry_free(&sentry); + return ENOMEM; + } + } + + if (kvno_out != NULL) { + *kvno_out = sentry.kvno; + } + + sdb_entry_free(&sentry); + + return 0; +} diff --git a/source4/kdc/db-glue.h b/source4/kdc/db-glue.h new file mode 100644 index 0000000..3a59d6a --- /dev/null +++ b/source4/kdc/db-glue.h @@ -0,0 +1,111 @@ +/* + Unix SMB/CIFS implementation. + + Database Glue between Samba and the KDC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009 + Copyright (C) Simo Sorce <idra@samba.org> 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +struct sdb_keys; +struct sdb_entry; + +struct samba_kdc_base_context; +struct samba_kdc_db_context; +struct samba_kdc_entry; + +enum samba_kdc_ent_type { + SAMBA_KDC_ENT_TYPE_CLIENT, + SAMBA_KDC_ENT_TYPE_SERVER, + SAMBA_KDC_ENT_TYPE_KRBTGT, + SAMBA_KDC_ENT_TYPE_TRUST, + SAMBA_KDC_ENT_TYPE_ANY +}; + +/* + * This allows DSDB to parse Kerberos keys without duplicating this + * difficulty + */ +krb5_error_code samba_kdc_message2entry_keys(krb5_context context, + TALLOC_CTX *mem_ctx, + const struct ldb_message *msg, + bool is_krbtgt, + bool is_rodc, + uint32_t userAccountControl, + enum samba_kdc_ent_type ent_type, + unsigned flags, + krb5_kvno requested_kvno, + struct sdb_entry *entry, + const uint32_t supported_enctypes_in, + uint32_t *supported_enctypes_out); + +int samba_kdc_set_fixed_keys(krb5_context context, + const struct ldb_val *secretbuffer, + uint32_t supported_enctypes, + struct sdb_keys *keys); + +krb5_error_code samba_kdc_fetch(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + krb5_const_principal principal, + unsigned flags, + krb5_kvno kvno, + struct sdb_entry *entry); + +krb5_error_code samba_kdc_firstkey(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + struct sdb_entry *entry); + +krb5_error_code samba_kdc_nextkey(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + struct sdb_entry *entry); + +krb5_error_code +samba_kdc_check_client_matches_target_service(krb5_context context, + struct samba_kdc_entry *skdc_entry_client, + struct samba_kdc_entry *skdc_entry_server_target); + +krb5_error_code +samba_kdc_check_pkinit_ms_upn_match(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + struct samba_kdc_entry *skdc_entry, + krb5_const_principal certificate_principal); + +krb5_error_code +samba_kdc_check_s4u2proxy(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + struct samba_kdc_entry *skdc_entry, + krb5_const_principal target_principal); + +krb5_error_code samba_kdc_check_s4u2proxy_rbcd( + krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + krb5_const_principal client_principal, + krb5_const_principal server_principal, + krb5_pac header_pac, + struct samba_kdc_entry *proxy_skdc_entry); + +NTSTATUS samba_kdc_setup_db_ctx(TALLOC_CTX *mem_ctx, struct samba_kdc_base_context *base_ctx, + struct samba_kdc_db_context **kdc_db_ctx_out); + +krb5_error_code dsdb_extract_aes_256_key(krb5_context context, + TALLOC_CTX *mem_ctx, + const struct ldb_message *msg, + uint32_t user_account_control, + const uint32_t *kvno, + uint32_t *kvno_out, + DATA_BLOB *aes_256_key, + DATA_BLOB *salt); diff --git a/source4/kdc/hdb-samba4-plugin.c b/source4/kdc/hdb-samba4-plugin.c new file mode 100644 index 0000000..be6d243 --- /dev/null +++ b/source4/kdc/hdb-samba4-plugin.c @@ -0,0 +1,85 @@ +/* + Unix SMB/CIFS implementation. + + KDC Server startup + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-20011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "kdc/kdc-glue.h" +#include "lib/param/param.h" + +static krb5_error_code hdb_samba4_create(krb5_context context, struct HDB **db, const char *arg) +{ + NTSTATUS nt_status; + void *ptr = NULL; + struct samba_kdc_base_context *base_ctx = NULL; + + if (sscanf(arg, "&%p", &ptr) != 1) { + return EINVAL; + } + + base_ctx = talloc_get_type_abort(ptr, struct samba_kdc_base_context); + + /* The global kdc_mem_ctx and kdc_lp_ctx, Disgusting, ugly hack, but it means one less private hook */ + nt_status = hdb_samba4_kpasswd_create_kdc(base_ctx, context, db); + + if (NT_STATUS_IS_OK(nt_status)) { + return 0; + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ERROR_DS_INCOMPATIBLE_VERSION)) { + return EINVAL; + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) { + + krb5_set_error_message(context, EINVAL, "Failed to open Samba4 LDB at %s", lpcfg_private_path(base_ctx, base_ctx->lp_ctx, "sam.ldb")); + } else { + krb5_set_error_message(context, EINVAL, "Failed to connect to Samba4 DB: %s (%s)", get_friendly_nt_error_msg(nt_status), nt_errstr(nt_status)); + } + + return EINVAL; +} + +#if (HDB_INTERFACE_VERSION != 11) +#error "Unsupported Heimdal HDB version" +#endif + +#if HDB_INTERFACE_VERSION >= 8 +static krb5_error_code hdb_samba4_init(krb5_context context, void **ctx) +{ + *ctx = NULL; + return 0; +} + +static void hdb_samba4_fini(void *ctx) +{ +} +#endif + +/* Only used in the hdb-backed keytab code + * for a keytab of 'samba4&<address>' or samba4, to find + * kpasswd's key in the main DB + * + * The <address> is the string form of a pointer to a talloced struct hdb_samba_context + */ +struct hdb_method hdb_samba4_interface = { + HDB_INTERFACE_VERSION, +#if HDB_INTERFACE_VERSION >= 8 + .init = hdb_samba4_init, + .fini = hdb_samba4_fini, +#endif + .prefix = "samba4", + .create = hdb_samba4_create +}; diff --git a/source4/kdc/hdb-samba4.c b/source4/kdc/hdb-samba4.c new file mode 100644 index 0000000..e465a28 --- /dev/null +++ b/source4/kdc/hdb-samba4.c @@ -0,0 +1,886 @@ +/* + * Copyright (c) 1999-2001, 2003, PADL Software Pty Ltd. + * Copyright (c) 2004-2009, Andrew Bartlett <abartlet@samba.org>. + * Copyright (c) 2004, Stefan Metzmacher <metze@samba.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of PADL Software nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "includes.h" +#include "kdc/kdc-glue.h" +#include "kdc/db-glue.h" +#include "auth/auth_sam.h" +#include "auth/common_auth.h" +#include <ldb.h> +#include "sdb.h" +#include "sdb_hdb.h" +#include "dsdb/samdb/samdb.h" +#include "param/param.h" +#include "../lib/tsocket/tsocket.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "lib/messaging/irpc.h" +#include "hdb.h" +#include <kdc-audit.h> + +static krb5_error_code hdb_samba4_open(krb5_context context, HDB *db, int flags, mode_t mode) +{ + if (db->hdb_master_key_set) { + krb5_error_code ret = HDB_ERR_NOENTRY; + krb5_warnx(context, "hdb_samba4_open: use of a master key incompatible with LDB\n"); + krb5_set_error_message(context, ret, "hdb_samba4_open: use of a master key incompatible with LDB\n"); + return ret; + } + + return 0; +} + +static krb5_error_code hdb_samba4_close(krb5_context context, HDB *db) +{ + return 0; +} + +static krb5_error_code hdb_samba4_lock(krb5_context context, HDB *db, int operation) +{ + return 0; +} + +static krb5_error_code hdb_samba4_unlock(krb5_context context, HDB *db) +{ + return 0; +} + +static krb5_error_code hdb_samba4_rename(krb5_context context, HDB *db, const char *new_name) +{ + return HDB_ERR_DB_INUSE; +} + +static krb5_error_code hdb_samba4_store(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry) +{ + return HDB_ERR_DB_INUSE; +} + +/* + * If we ever want kadmin to work fast, we might try and reopen the + * ldb with LDB_NOSYNC + */ +static krb5_error_code hdb_samba4_set_sync(krb5_context context, struct HDB *db, int set_sync) +{ + return 0; +} + +static void hdb_samba4_free_entry_context(krb5_context context, struct HDB *db, hdb_entry *entry) +{ + /* + * This function is now called for every HDB entry, not just those with + * 'context' set, so we have to check that the context is not NULL. + */ + if (entry->context != NULL) { + struct samba_kdc_entry *skdc_entry = + talloc_get_type_abort(entry->context, + struct samba_kdc_entry); + + /* this function is called only from hdb_free_entry(). + * Make sure we neutralize the destructor or we will + * get a double free later when hdb_free_entry() will + * try to call free_hdb_entry() */ + entry->context = NULL; + skdc_entry->kdc_entry = NULL; + TALLOC_FREE(skdc_entry); + } +} + +static int hdb_samba4_fill_fast_cookie(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx) +{ + struct ldb_message *msg = ldb_msg_new(kdc_db_ctx); + int ldb_ret; + + uint8_t secretbuffer[32]; + struct ldb_val val = data_blob_const(secretbuffer, + sizeof(secretbuffer)); + + if (msg == NULL) { + DBG_ERR("Failed to allocate msg for new fast cookie\n"); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* Fill in all the keys with the same secret */ + generate_secret_buffer(secretbuffer, + sizeof(secretbuffer)); + + msg->dn = kdc_db_ctx->fx_cookie_dn; + + ldb_ret = ldb_msg_add_value(msg, "secret", &val, NULL); + + if (ldb_ret != LDB_SUCCESS) { + return ldb_ret; + } + + ldb_ret = ldb_add(kdc_db_ctx->secrets_db, + msg); + if (ldb_ret != LDB_SUCCESS) { + DBG_ERR("Failed to add fast cookie to ldb: %s\n", + ldb_errstring(kdc_db_ctx->secrets_db)); + } + return ldb_ret; +} + +static krb5_error_code hdb_samba4_fetch_fast_cookie(krb5_context context, + struct samba_kdc_db_context *kdc_db_ctx, + hdb_entry *entry) +{ + krb5_error_code ret = SDB_ERR_NOENTRY; + TALLOC_CTX *mem_ctx; + struct ldb_result *res; + int ldb_ret; + struct sdb_entry sentry = {}; + const char *attrs[] = { + "secret", + NULL + }; + const struct ldb_val *val; + + mem_ctx = talloc_named(kdc_db_ctx, 0, "samba_kdc_fetch context"); + if (!mem_ctx) { + ret = ENOMEM; + krb5_set_error_message(context, ret, "samba_kdc_fetch: talloc_named() failed!"); + return ret; + } + + /* search for CN=FX-COOKIE */ + ldb_ret = ldb_search(kdc_db_ctx->secrets_db, + mem_ctx, + &res, + kdc_db_ctx->fx_cookie_dn, + LDB_SCOPE_BASE, + attrs, NULL); + + if (ldb_ret == LDB_ERR_NO_SUCH_OBJECT || res->count == 0) { + + ldb_ret = hdb_samba4_fill_fast_cookie(context, + kdc_db_ctx); + + if (ldb_ret != LDB_SUCCESS) { + TALLOC_FREE(mem_ctx); + return HDB_ERR_NO_WRITE_SUPPORT; + } + + /* search for CN=FX-COOKIE */ + ldb_ret = ldb_search(kdc_db_ctx->secrets_db, + mem_ctx, + &res, + kdc_db_ctx->fx_cookie_dn, + LDB_SCOPE_BASE, + attrs, NULL); + + if (ldb_ret != LDB_SUCCESS || res->count != 1) { + TALLOC_FREE(mem_ctx); + return HDB_ERR_NOENTRY; + } + } + + val = ldb_msg_find_ldb_val(res->msgs[0], + "secret"); + if (val == NULL || val->length != 32) { + TALLOC_FREE(mem_ctx); + return HDB_ERR_NOENTRY; + } + + + ret = krb5_make_principal(context, + &sentry.principal, + KRB5_WELLKNOWN_ORG_H5L_REALM, + KRB5_WELLKNOWN_NAME, "org.h5l.fast-cookie", + NULL); + if (ret) { + TALLOC_FREE(mem_ctx); + return ret; + } + + ret = samba_kdc_set_fixed_keys(context, val, ENC_ALL_TYPES, + &sentry.keys); + if (ret != 0) { + return ret; + } + + ret = sdb_entry_to_hdb_entry(context, &sentry, entry); + sdb_entry_free(&sentry); + TALLOC_FREE(mem_ctx); + + return ret; +} + +static krb5_error_code hdb_samba4_fetch_kvno(krb5_context context, HDB *db, + krb5_const_principal principal, + unsigned flags, + krb5_kvno kvno, + hdb_entry *entry) +{ + struct samba_kdc_db_context *kdc_db_ctx; + struct sdb_entry sentry = {}; + krb5_error_code code, ret; + uint32_t sflags; + + kdc_db_ctx = talloc_get_type_abort(db->hdb_db, + struct samba_kdc_db_context); + + if (flags & HDB_F_GET_FAST_COOKIE) { + return hdb_samba4_fetch_fast_cookie(context, + kdc_db_ctx, + entry); + } + + sflags = (flags & SDB_F_HDB_MASK); + + ret = samba_kdc_fetch(context, + kdc_db_ctx, + principal, + sflags, + kvno, + &sentry); + switch (ret) { + case 0: + code = 0; + break; + case SDB_ERR_WRONG_REALM: + /* + * If SDB_ERR_WRONG_REALM is returned we need to process the + * sdb_entry to fill the principal in the HDB entry. + */ + code = HDB_ERR_WRONG_REALM; + break; + case SDB_ERR_NOENTRY: + return HDB_ERR_NOENTRY; + case SDB_ERR_NOT_FOUND_HERE: + return HDB_ERR_NOT_FOUND_HERE; + default: + return ret; + } + + ret = sdb_entry_to_hdb_entry(context, &sentry, entry); + sdb_entry_free(&sentry); + + if (code != 0 && ret != 0) { + code = ret; + } + + return code; +} + +static krb5_error_code hdb_samba4_kpasswd_fetch_kvno(krb5_context context, HDB *db, + krb5_const_principal _principal, + unsigned flags, + krb5_kvno _kvno, + hdb_entry *entry) +{ + struct samba_kdc_db_context *kdc_db_ctx = NULL; + krb5_error_code ret; + krb5_principal kpasswd_principal = NULL; + + kdc_db_ctx = talloc_get_type_abort(db->hdb_db, + struct samba_kdc_db_context); + + ret = smb_krb5_make_principal(context, &kpasswd_principal, + lpcfg_realm(kdc_db_ctx->lp_ctx), + "kadmin", "changepw", + NULL); + if (ret) { + return ret; + } + smb_krb5_principal_set_type(context, kpasswd_principal, KRB5_NT_SRV_INST); + + /* + * For the kpasswd service, always ensure we get the latest kvno. This + * also means we (correctly) refuse RODC-issued tickets. + */ + flags &= ~HDB_F_KVNO_SPECIFIED; + + /* Don't bother looking up a client or krbtgt. */ + flags &= ~(SDB_F_GET_CLIENT|SDB_F_GET_KRBTGT); + + ret = hdb_samba4_fetch_kvno(context, db, + kpasswd_principal, + flags, + 0, + entry); + + krb5_free_principal(context, kpasswd_principal); + return ret; +} + +static krb5_error_code hdb_samba4_firstkey(krb5_context context, HDB *db, unsigned flags, + hdb_entry *entry) +{ + struct samba_kdc_db_context *kdc_db_ctx; + struct sdb_entry sentry = {}; + krb5_error_code ret; + + kdc_db_ctx = talloc_get_type_abort(db->hdb_db, + struct samba_kdc_db_context); + + ret = samba_kdc_firstkey(context, kdc_db_ctx, &sentry); + switch (ret) { + case 0: + break; + case SDB_ERR_WRONG_REALM: + return HDB_ERR_WRONG_REALM; + case SDB_ERR_NOENTRY: + return HDB_ERR_NOENTRY; + case SDB_ERR_NOT_FOUND_HERE: + return HDB_ERR_NOT_FOUND_HERE; + default: + return ret; + } + + ret = sdb_entry_to_hdb_entry(context, &sentry, entry); + sdb_entry_free(&sentry); + return ret; +} + +static krb5_error_code hdb_samba4_nextkey(krb5_context context, HDB *db, unsigned flags, + hdb_entry *entry) +{ + struct samba_kdc_db_context *kdc_db_ctx; + struct sdb_entry sentry = {}; + krb5_error_code ret; + + kdc_db_ctx = talloc_get_type_abort(db->hdb_db, + struct samba_kdc_db_context); + + ret = samba_kdc_nextkey(context, kdc_db_ctx, &sentry); + switch (ret) { + case 0: + break; + case SDB_ERR_WRONG_REALM: + return HDB_ERR_WRONG_REALM; + case SDB_ERR_NOENTRY: + return HDB_ERR_NOENTRY; + case SDB_ERR_NOT_FOUND_HERE: + return HDB_ERR_NOT_FOUND_HERE; + default: + return ret; + } + + ret = sdb_entry_to_hdb_entry(context, &sentry, entry); + sdb_entry_free(&sentry); + return ret; +} + +static krb5_error_code hdb_samba4_nextkey_panic(krb5_context context, HDB *db, + unsigned flags, + hdb_entry *entry) +{ + DBG_ERR("Attempt to iterate kpasswd keytab => PANIC\n"); + smb_panic("hdb_samba4_nextkey_panic: Attempt to iterate kpasswd keytab"); +} + +static krb5_error_code hdb_samba4_destroy(krb5_context context, HDB *db) +{ + talloc_free(db); + return 0; +} + +static krb5_error_code +hdb_samba4_check_constrained_delegation(krb5_context context, HDB *db, + hdb_entry *entry, + krb5_const_principal target_principal) +{ + struct samba_kdc_db_context *kdc_db_ctx; + struct samba_kdc_entry *skdc_entry; + krb5_error_code ret; + + kdc_db_ctx = talloc_get_type_abort(db->hdb_db, + struct samba_kdc_db_context); + skdc_entry = talloc_get_type_abort(entry->context, + struct samba_kdc_entry); + + ret = samba_kdc_check_s4u2proxy(context, kdc_db_ctx, + skdc_entry, + target_principal); + switch (ret) { + case 0: + break; + case SDB_ERR_WRONG_REALM: + ret = HDB_ERR_WRONG_REALM; + break; + case SDB_ERR_NOENTRY: + ret = HDB_ERR_NOENTRY; + break; + case SDB_ERR_NOT_FOUND_HERE: + ret = HDB_ERR_NOT_FOUND_HERE; + break; + default: + break; + } + + return ret; +} + +static krb5_error_code +hdb_samba4_check_pkinit_ms_upn_match(krb5_context context, HDB *db, + hdb_entry *entry, + krb5_const_principal certificate_principal) +{ + struct samba_kdc_db_context *kdc_db_ctx; + struct samba_kdc_entry *skdc_entry; + krb5_error_code ret; + + kdc_db_ctx = talloc_get_type_abort(db->hdb_db, + struct samba_kdc_db_context); + skdc_entry = talloc_get_type_abort(entry->context, + struct samba_kdc_entry); + + ret = samba_kdc_check_pkinit_ms_upn_match(context, kdc_db_ctx, + skdc_entry, + certificate_principal); + switch (ret) { + case 0: + break; + case SDB_ERR_WRONG_REALM: + ret = HDB_ERR_WRONG_REALM; + break; + case SDB_ERR_NOENTRY: + ret = HDB_ERR_NOENTRY; + break; + case SDB_ERR_NOT_FOUND_HERE: + ret = HDB_ERR_NOT_FOUND_HERE; + break; + default: + break; + } + + return ret; +} + +static krb5_error_code +hdb_samba4_check_client_matches_target_service(krb5_context context, HDB *db, + hdb_entry *client_entry, + hdb_entry *server_target_entry) +{ + struct samba_kdc_entry *skdc_client_entry + = talloc_get_type_abort(client_entry->context, + struct samba_kdc_entry); + struct samba_kdc_entry *skdc_server_target_entry + = talloc_get_type_abort(server_target_entry->context, + struct samba_kdc_entry); + + return samba_kdc_check_client_matches_target_service(context, + skdc_client_entry, + skdc_server_target_entry); +} + +static void reset_bad_password_netlogon(TALLOC_CTX *mem_ctx, + struct samba_kdc_db_context *kdc_db_ctx, + struct netr_SendToSamBase *send_to_sam) +{ + struct dcerpc_binding_handle *irpc_handle; + struct winbind_SendToSam req; + + irpc_handle = irpc_binding_handle_by_name(mem_ctx, kdc_db_ctx->msg_ctx, + "winbind_server", + &ndr_table_winbind); + + if (irpc_handle == NULL) { + DEBUG(0, ("No winbind_server running!\n")); + return; + } + + req.in.message = *send_to_sam; + + dcerpc_winbind_SendToSam_r_send(mem_ctx, kdc_db_ctx->ev_ctx, + irpc_handle, &req); +} + +static krb5_error_code hdb_samba4_audit(krb5_context context, + HDB *db, + hdb_entry *entry, + hdb_request_t r) +{ + struct samba_kdc_db_context *kdc_db_ctx = talloc_get_type_abort(db->hdb_db, + struct samba_kdc_db_context); + struct ldb_dn *domain_dn = ldb_get_default_basedn(kdc_db_ctx->samdb); + uint64_t logon_id = generate_random_u64(); + heim_object_t auth_details_obj = NULL; + const char *auth_details = NULL; + char *etype_str = NULL; + heim_object_t hdb_auth_status_obj = NULL; + int hdb_auth_status; + heim_object_t pa_type_obj = NULL; + const char *pa_type = NULL; + struct auth_usersupplied_info ui; + size_t sa_socklen = 0; + int final_ret = 0; + + hdb_auth_status_obj = heim_audit_getkv((heim_svc_req_desc)r, KDC_REQUEST_KV_AUTH_EVENT); + if (hdb_auth_status_obj == NULL) { + /* No status code found, so just return. */ + return 0; + } + + hdb_auth_status = heim_number_get_int(hdb_auth_status_obj); + + pa_type_obj = heim_audit_getkv((heim_svc_req_desc)r, "pa"); + if (pa_type_obj != NULL) { + pa_type = heim_string_get_utf8(pa_type_obj); + } + + auth_details_obj = heim_audit_getkv((heim_svc_req_desc)r, KDC_REQUEST_KV_PKINIT_CLIENT_CERT); + if (auth_details_obj != NULL) { + auth_details = heim_string_get_utf8(auth_details_obj); + } else { + auth_details_obj = heim_audit_getkv((heim_svc_req_desc)r, KDC_REQUEST_KV_GSS_INITIATOR); + if (auth_details_obj != NULL) { + auth_details = heim_string_get_utf8(auth_details_obj); + } else { + heim_object_t etype_obj = heim_audit_getkv((heim_svc_req_desc)r, KDC_REQUEST_KV_PA_ETYPE); + if (etype_obj != NULL) { + int etype = heim_number_get_int(etype_obj); + + krb5_error_code ret = krb5_enctype_to_string(r->context, etype, &etype_str); + if (ret == 0) { + auth_details = etype_str; + } else { + auth_details = "unknown enctype"; + } + } + } + } + + /* + * Forcing this via the NTLM auth structure is not ideal, but + * it is the most practical option right now, and ensures the + * logs are consistent, even if some elements are always NULL. + */ + ui = (struct auth_usersupplied_info) { + .was_mapped = true, + .client = { + .account_name = r->cname, + .domain_name = NULL, + }, + .service_description = "Kerberos KDC", + .auth_description = "Unknown Auth Description", + .password_type = auth_details, + .logon_id = logon_id + }; + + switch (r->addr->sa_family) { + case AF_INET: + sa_socklen = sizeof(struct sockaddr_in); + break; +#ifdef HAVE_IPV6 + case AF_INET6: + sa_socklen = sizeof(struct sockaddr_in6); + break; +#endif + } + + switch (hdb_auth_status) { + default: + { + TALLOC_CTX *frame = talloc_stackframe(); + struct samba_kdc_entry *p = talloc_get_type(entry->context, + struct samba_kdc_entry); + struct dom_sid *sid + = samdb_result_dom_sid(frame, p->msg, "objectSid"); + const char *account_name + = ldb_msg_find_attr_as_string(p->msg, "sAMAccountName", NULL); + const char *domain_name = lpcfg_sam_name(p->kdc_db_ctx->lp_ctx); + struct tsocket_address *remote_host; + const char *auth_description = NULL; + NTSTATUS status; + int ret; + bool rwdc_fallback = false; + + ret = tsocket_address_bsd_from_sockaddr(frame, r->addr, + sa_socklen, + &remote_host); + if (ret != 0) { + ui.remote_host = NULL; + } else { + ui.remote_host = remote_host; + } + + ui.mapped.account_name = account_name; + ui.mapped.domain_name = domain_name; + + if (pa_type != NULL) { + auth_description = talloc_asprintf(frame, + "%s Pre-authentication", + pa_type); + if (auth_description == NULL) { + auth_description = pa_type; + } + } else { + auth_description = "Unknown Pre-authentication"; + } + ui.auth_description = auth_description; + + if (hdb_auth_status == KDC_AUTH_EVENT_CLIENT_AUTHORIZED) { + struct netr_SendToSamBase *send_to_sam = NULL; + + /* + * TODO: We could log the AS-REQ authorization success here as + * well. However before we do that, we need to pass + * in the PAC here or re-calculate it. + */ + status = authsam_logon_success_accounting(kdc_db_ctx->samdb, p->msg, + domain_dn, true, frame, &send_to_sam); + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)) { + final_ret = KRB5KDC_ERR_CLIENT_REVOKED; + r->error_code = final_ret; + rwdc_fallback = kdc_db_ctx->rodc; + } else if (!NT_STATUS_IS_OK(status)) { + final_ret = KRB5KRB_ERR_GENERIC; + r->error_code = final_ret; + rwdc_fallback = kdc_db_ctx->rodc; + } else if (kdc_db_ctx->rodc && send_to_sam != NULL) { + reset_bad_password_netlogon(frame, kdc_db_ctx, send_to_sam); + } + + /* This is the final sucess */ + } else if (hdb_auth_status == KDC_AUTH_EVENT_VALIDATED_LONG_TERM_KEY) { + /* + * This was only a pre-authentication success, + * but we didn't reach the final + * KDC_AUTH_EVENT_CLIENT_AUTHORIZED, + * so consult the error code. + */ + if (r->error_code == 0) { + DBG_ERR("ERROR: VALIDATED_LONG_TERM_KEY " + "with error=0 => INTERNAL_ERROR\n"); + status = NT_STATUS_INTERNAL_ERROR; + final_ret = KRB5KRB_ERR_GENERIC; + r->error_code = final_ret; + } else if (!NT_STATUS_IS_OK(p->reject_status)) { + status = p->reject_status; + } else { + status = krb5_to_nt_status(r->error_code); + } + } else if (hdb_auth_status == KDC_AUTH_EVENT_PREAUTH_SUCCEEDED) { + /* + * This was only a pre-authentication success, + * but we didn't reach the final + * KDC_AUTH_EVENT_CLIENT_AUTHORIZED, + * so consult the error code. + */ + if (r->error_code == 0) { + DBG_ERR("ERROR: PREAUTH_SUCCEEDED " + "with error=0 => INTERNAL_ERROR\n"); + status = NT_STATUS_INTERNAL_ERROR; + final_ret = KRB5KRB_ERR_GENERIC; + r->error_code = final_ret; + } else if (!NT_STATUS_IS_OK(p->reject_status)) { + status = p->reject_status; + } else { + status = krb5_to_nt_status(r->error_code); + } + } else if (hdb_auth_status == KDC_AUTH_EVENT_CLIENT_TIME_SKEW) { + status = NT_STATUS_TIME_DIFFERENCE_AT_DC; + } else if (hdb_auth_status == KDC_AUTH_EVENT_WRONG_LONG_TERM_KEY) { + status = authsam_update_bad_pwd_count(kdc_db_ctx->samdb, p->msg, domain_dn); + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)) { + final_ret = KRB5KDC_ERR_CLIENT_REVOKED; + r->error_code = final_ret; + } else { + status = NT_STATUS_WRONG_PASSWORD; + } + rwdc_fallback = kdc_db_ctx->rodc; + } else if (hdb_auth_status == KDC_AUTH_EVENT_CLIENT_LOCKED_OUT) { + status = NT_STATUS_ACCOUNT_LOCKED_OUT; + rwdc_fallback = kdc_db_ctx->rodc; + } else if (hdb_auth_status == KDC_AUTH_EVENT_CLIENT_NAME_UNAUTHORIZED) { + if (pa_type != NULL && strncmp(pa_type, "PK-INIT", strlen("PK-INIT")) == 0) { + status = NT_STATUS_PKINIT_NAME_MISMATCH; + } else { + status = NT_STATUS_ACCOUNT_RESTRICTION; + } + rwdc_fallback = kdc_db_ctx->rodc; + } else if (hdb_auth_status == KDC_AUTH_EVENT_PREAUTH_FAILED) { + if (pa_type != NULL && strncmp(pa_type, "PK-INIT", strlen("PK-INIT")) == 0) { + status = NT_STATUS_PKINIT_FAILURE; + } else { + status = NT_STATUS_GENERIC_COMMAND_FAILED; + } + rwdc_fallback = kdc_db_ctx->rodc; + } else { + DBG_ERR("Unhandled hdb_auth_status=%d => INTERNAL_ERROR\n", + hdb_auth_status); + status = NT_STATUS_INTERNAL_ERROR; + final_ret = KRB5KRB_ERR_GENERIC; + r->error_code = final_ret; + } + + if (rwdc_fallback) { + /* + * Forward the request to an RWDC in order + * to give an authoritative answer to the client. + */ + auth_description = talloc_asprintf(frame, + "%s,Forward-To-RWDC", + ui.auth_description); + if (auth_description != NULL) { + ui.auth_description = auth_description; + } + final_ret = HDB_ERR_NOT_FOUND_HERE; + } + + log_authentication_event(kdc_db_ctx->msg_ctx, + kdc_db_ctx->lp_ctx, + &r->tv_start, + &ui, + status, + domain_name, + account_name, + sid); + if (final_ret == KRB5KRB_ERR_GENERIC && socket_wrapper_enabled()) { + /* + * If we're running under make test + * just panic + */ + DBG_ERR("Unexpected situation => PANIC\n"); + smb_panic("hdb_samba4_audit: Unexpected situation"); + } + TALLOC_FREE(frame); + break; + } + case KDC_AUTH_EVENT_CLIENT_UNKNOWN: + { + struct tsocket_address *remote_host; + int ret; + TALLOC_CTX *frame = talloc_stackframe(); + ret = tsocket_address_bsd_from_sockaddr(frame, r->addr, + sa_socklen, + &remote_host); + if (ret != 0) { + ui.remote_host = NULL; + } else { + ui.remote_host = remote_host; + } + + if (pa_type == NULL) { + pa_type = "AS-REQ"; + } + + ui.auth_description = pa_type; + + /* Note this is not forwarded to an RWDC */ + + log_authentication_event(kdc_db_ctx->msg_ctx, + kdc_db_ctx->lp_ctx, + &r->tv_start, + &ui, + NT_STATUS_NO_SUCH_USER, + NULL, NULL, + NULL); + TALLOC_FREE(frame); + break; + } + } + + free(etype_str); + + return final_ret; +} + +/* This interface is to be called by the KDC and libnet_keytab_dump, + * which is expecting Samba calling conventions. + * It is also called by a wrapper (hdb_samba4_create) from the + * kpasswdd -> krb5 -> keytab_hdb -> hdb code */ + +NTSTATUS hdb_samba4_create_kdc(struct samba_kdc_base_context *base_ctx, + krb5_context context, struct HDB **db) +{ + struct samba_kdc_db_context *kdc_db_ctx; + NTSTATUS nt_status; + + if (hdb_interface_version != HDB_INTERFACE_VERSION) { + krb5_set_error_message(context, EINVAL, "Heimdal HDB interface version mismatch between build-time and run-time libraries!"); + return NT_STATUS_ERROR_DS_INCOMPATIBLE_VERSION; + } + + *db = talloc_zero(base_ctx, HDB); + if (!*db) { + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return NT_STATUS_NO_MEMORY; + } + + (*db)->hdb_master_key_set = 0; + (*db)->hdb_db = NULL; + (*db)->hdb_capability_flags = HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL; + + nt_status = samba_kdc_setup_db_ctx(*db, base_ctx, &kdc_db_ctx); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(*db); + return nt_status; + } + (*db)->hdb_db = kdc_db_ctx; + + (*db)->hdb_dbc = NULL; + (*db)->hdb_open = hdb_samba4_open; + (*db)->hdb_close = hdb_samba4_close; + (*db)->hdb_free_entry_context = hdb_samba4_free_entry_context; + (*db)->hdb_fetch_kvno = hdb_samba4_fetch_kvno; + (*db)->hdb_store = hdb_samba4_store; + (*db)->hdb_firstkey = hdb_samba4_firstkey; + (*db)->hdb_nextkey = hdb_samba4_nextkey; + (*db)->hdb_lock = hdb_samba4_lock; + (*db)->hdb_unlock = hdb_samba4_unlock; + (*db)->hdb_set_sync = hdb_samba4_set_sync; + (*db)->hdb_rename = hdb_samba4_rename; + /* we don't implement these, as we are not a lockable database */ + (*db)->hdb__get = NULL; + (*db)->hdb__put = NULL; + /* kadmin should not be used for deletes - use other tools instead */ + (*db)->hdb__del = NULL; + (*db)->hdb_destroy = hdb_samba4_destroy; + + (*db)->hdb_audit = hdb_samba4_audit; + (*db)->hdb_check_constrained_delegation = hdb_samba4_check_constrained_delegation; + (*db)->hdb_check_pkinit_ms_upn_match = hdb_samba4_check_pkinit_ms_upn_match; + (*db)->hdb_check_client_matches_target_service = hdb_samba4_check_client_matches_target_service; + + return NT_STATUS_OK; +} + +NTSTATUS hdb_samba4_kpasswd_create_kdc(struct samba_kdc_base_context *base_ctx, + krb5_context context, struct HDB **db) +{ + NTSTATUS nt_status; + + nt_status = hdb_samba4_create_kdc(base_ctx, context, db); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + (*db)->hdb_fetch_kvno = hdb_samba4_kpasswd_fetch_kvno; + (*db)->hdb_firstkey = hdb_samba4_nextkey_panic; + (*db)->hdb_nextkey = hdb_samba4_nextkey_panic; + + return NT_STATUS_OK; +} diff --git a/source4/kdc/kdc-glue.c b/source4/kdc/kdc-glue.c new file mode 100644 index 0000000..671e506 --- /dev/null +++ b/source4/kdc/kdc-glue.c @@ -0,0 +1,65 @@ +/* + Unix SMB/CIFS implementation. + + PAC Glue between Samba and the KDC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009 + Copyright (C) Simo Sorce <idra@samba.org> 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include <hdb.h> +#include "kdc/samba_kdc.h" +#include "kdc/pac-glue.h" +#include "librpc/gen_ndr/ndr_krb5pac.h" +#include "auth/kerberos/pac_utils.h" +#include "kdc/kdc-glue.h" + +int kdc_check_pac(krb5_context context, + DATA_BLOB srv_sig, + struct PAC_SIGNATURE_DATA *kdc_sig, + hdb_entry *ent) +{ + krb5_enctype etype; + int ret; + krb5_keyblock keyblock; + Key *key; + + if (kdc_sig->type == CKSUMTYPE_HMAC_MD5) { + etype = ENCTYPE_ARCFOUR_HMAC; + } else { + ret = krb5_cksumtype_to_enctype(context, + kdc_sig->type, + &etype); + if (ret != 0) { + return ret; + } + } + + ret = hdb_enctype2key(context, ent, NULL, etype, &key); + + if (ret != 0) { + return ret; + } + + keyblock = key->key; + + return check_pac_checksum(srv_sig, kdc_sig, + context, &keyblock); +} diff --git a/source4/kdc/kdc-glue.h b/source4/kdc/kdc-glue.h new file mode 100644 index 0000000..7a0184c --- /dev/null +++ b/source4/kdc/kdc-glue.h @@ -0,0 +1,57 @@ +/* + Unix SMB/CIFS implementation. + + KDC structures + + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _KDC_KDC_H +#define _KDC_KDC_H + +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include <hdb.h> +#include <heimbase.h> +#include <kdc.h> +#include <krb5/kdc-plugin.h> +#include "kdc/samba_kdc.h" +#include "kdc/kdc-server.h" + +struct tsocket_address; + +kdc_code kpasswdd_process(struct kdc_server *kdc, + TALLOC_CTX *mem_ctx, + DATA_BLOB *input, + DATA_BLOB *reply, + struct tsocket_address *peer_addr, + struct tsocket_address *my_addr, + int datagram_reply); + +/* from hdb-samba4.c */ +NTSTATUS hdb_samba4_create_kdc(struct samba_kdc_base_context *base_ctx, + krb5_context context, struct HDB **db); + +NTSTATUS hdb_samba4_kpasswd_create_kdc(struct samba_kdc_base_context *base_ctx, + krb5_context context, struct HDB **db); + +/* from kdc-glue.c */ +int kdc_check_pac(krb5_context krb5_context, + DATA_BLOB server_sig, + struct PAC_SIGNATURE_DATA *kdc_sig, + hdb_entry *ent); +#endif diff --git a/source4/kdc/kdc-heimdal.c b/source4/kdc/kdc-heimdal.c new file mode 100644 index 0000000..e936d4b --- /dev/null +++ b/source4/kdc/kdc-heimdal.c @@ -0,0 +1,518 @@ +/* + Unix SMB/CIFS implementation. + + KDC Server startup + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2008 + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Stefan Metzmacher 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "samba/process_model.h" +#include "lib/tsocket/tsocket.h" +#include "lib/messaging/irpc.h" +#include "librpc/gen_ndr/ndr_irpc.h" +#include "librpc/gen_ndr/ndr_krb5pac.h" +#include "lib/socket/netif.h" +#include "param/param.h" +#include "kdc/kdc-server.h" +#include "kdc/kdc-proxy.h" +#include "kdc/kdc-glue.h" +#include "kdc/pac-glue.h" +#include "kdc/kpasswd-service.h" +#include "dsdb/samdb/samdb.h" +#include "auth/session.h" +#include "libds/common/roles.h" +#include <kdc.h> +#include <hdb.h> + +NTSTATUS server_service_kdc_init(TALLOC_CTX *); + +extern struct krb5plugin_kdc_ftable kdc_plugin_table; + +/** + Wrapper for krb5_kdc_process_krb5_request, converting to/from Samba + calling conventions +*/ + +static kdc_code kdc_process(struct kdc_server *kdc, + TALLOC_CTX *mem_ctx, + DATA_BLOB *input, + DATA_BLOB *reply, + struct tsocket_address *peer_addr, + struct tsocket_address *my_addr, + int datagram_reply) +{ + int ret; + char *pa; + struct sockaddr_storage ss; + krb5_data k5_reply; + krb5_kdc_configuration *kdc_config = + (krb5_kdc_configuration *)kdc->private_data; + + krb5_data_zero(&k5_reply); + + krb5_kdc_update_time(NULL); + + ret = tsocket_address_bsd_sockaddr(peer_addr, (struct sockaddr *) &ss, + sizeof(struct sockaddr_storage)); + if (ret < 0) { + return KDC_ERROR; + } + pa = tsocket_address_string(peer_addr, mem_ctx); + if (pa == NULL) { + return KDC_ERROR; + } + + DBG_DEBUG("Received KDC packet of length %lu from %s\n", + (long)input->length - 4, pa); + + ret = krb5_kdc_process_krb5_request(kdc->smb_krb5_context->krb5_context, + kdc_config, + input->data, input->length, + &k5_reply, + pa, + (struct sockaddr *) &ss, + datagram_reply); + if (ret == -1) { + *reply = data_blob(NULL, 0); + return KDC_ERROR; + } + + if (ret == HDB_ERR_NOT_FOUND_HERE) { + *reply = data_blob(NULL, 0); + return KDC_PROXY_REQUEST; + } + + if (k5_reply.length) { + *reply = data_blob_talloc(mem_ctx, k5_reply.data, k5_reply.length); + krb5_data_free(&k5_reply); + } else { + *reply = data_blob(NULL, 0); + } + return KDC_OK; +} + +/* + setup our listening sockets on the configured network interfaces +*/ +static NTSTATUS kdc_startup_interfaces(struct kdc_server *kdc, + struct loadparm_context *lp_ctx, + struct interface *ifaces, + const struct model_ops *model_ops) +{ + int num_interfaces; + TALLOC_CTX *tmp_ctx = talloc_new(kdc); + NTSTATUS status; + int i; + uint16_t kdc_port = lpcfg_krb5_port(lp_ctx); + uint16_t kpasswd_port = lpcfg_kpasswd_port(lp_ctx); + bool done_wildcard = false; + + num_interfaces = iface_list_count(ifaces); + + /* if we are allowing incoming packets from any address, then + we need to bind to the wildcard address */ + if (!lpcfg_bind_interfaces_only(lp_ctx)) { + size_t num_binds = 0; + char **wcard = iface_list_wildcard(kdc); + NT_STATUS_HAVE_NO_MEMORY(wcard); + for (i=0; wcard[i]; i++) { + if (kdc_port) { + status = kdc_add_socket(kdc, model_ops, + "kdc", wcard[i], kdc_port, + kdc_process, false); + if (NT_STATUS_IS_OK(status)) { + num_binds++; + } + } + + if (kpasswd_port) { + status = kdc_add_socket(kdc, model_ops, + "kpasswd", wcard[i], kpasswd_port, + kpasswd_process, false); + if (NT_STATUS_IS_OK(status)) { + num_binds++; + } + } + } + talloc_free(wcard); + if (num_binds == 0) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + done_wildcard = true; + } + + for (i=0; i<num_interfaces; i++) { + const char *address = talloc_strdup(tmp_ctx, iface_list_n_ip(ifaces, i)); + + if (kdc_port) { + status = kdc_add_socket(kdc, model_ops, + "kdc", address, kdc_port, + kdc_process, done_wildcard); + NT_STATUS_NOT_OK_RETURN(status); + } + + if (kpasswd_port) { + status = kdc_add_socket(kdc, model_ops, + "kpasswd", address, kpasswd_port, + kpasswd_process, done_wildcard); + NT_STATUS_NOT_OK_RETURN(status); + } + } + + talloc_free(tmp_ctx); + + return NT_STATUS_OK; +} + +static NTSTATUS kdc_check_generic_kerberos(struct irpc_message *msg, + struct kdc_check_generic_kerberos *r) +{ + struct PAC_Validate pac_validate; + DATA_BLOB srv_sig; + struct PAC_SIGNATURE_DATA kdc_sig; + struct kdc_server *kdc = talloc_get_type(msg->private_data, struct kdc_server); + krb5_kdc_configuration *kdc_config = + (krb5_kdc_configuration *)kdc->private_data; + enum ndr_err_code ndr_err; + int ret; + hdb_entry ent; + krb5_principal principal; + + + /* There is no reply to this request */ + r->out.generic_reply = data_blob(NULL, 0); + + ndr_err = ndr_pull_struct_blob(&r->in.generic_request, msg, &pac_validate, + (ndr_pull_flags_fn_t)ndr_pull_PAC_Validate); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (pac_validate.MessageType != NETLOGON_GENERIC_KRB5_PAC_VALIDATE) { + /* We don't implement any other message types - such as certificate validation - yet */ + return NT_STATUS_INVALID_PARAMETER; + } + + if (pac_validate.ChecksumAndSignature.length != (pac_validate.ChecksumLength + pac_validate.SignatureLength) + || pac_validate.ChecksumAndSignature.length < pac_validate.ChecksumLength + || pac_validate.ChecksumAndSignature.length < pac_validate.SignatureLength ) { + return NT_STATUS_INVALID_PARAMETER; + } + + srv_sig = data_blob_const(pac_validate.ChecksumAndSignature.data, + pac_validate.ChecksumLength); + + ret = krb5_make_principal(kdc->smb_krb5_context->krb5_context, &principal, + lpcfg_realm(kdc->task->lp_ctx), + "krbtgt", lpcfg_realm(kdc->task->lp_ctx), + NULL); + + if (ret != 0) { + return NT_STATUS_NO_MEMORY; + } + + ret = kdc_config->db[0]->hdb_fetch_kvno(kdc->smb_krb5_context->krb5_context, + kdc_config->db[0], + principal, + HDB_F_GET_KRBTGT | HDB_F_DECRYPT, + 0, + &ent); + + if (ret != 0) { + hdb_free_entry(kdc->smb_krb5_context->krb5_context, kdc_config->db[0], &ent); + krb5_free_principal(kdc->smb_krb5_context->krb5_context, principal); + + return NT_STATUS_LOGON_FAILURE; + } + + kdc_sig.type = pac_validate.SignatureType; + kdc_sig.signature = data_blob_const(&pac_validate.ChecksumAndSignature.data[pac_validate.ChecksumLength], + pac_validate.SignatureLength); + + ret = kdc_check_pac(kdc->smb_krb5_context->krb5_context, srv_sig, &kdc_sig, &ent); + + hdb_free_entry(kdc->smb_krb5_context->krb5_context, kdc_config->db[0], &ent); + krb5_free_principal(kdc->smb_krb5_context->krb5_context, principal); + + if (ret != 0) { + return NT_STATUS_LOGON_FAILURE; + } + + return NT_STATUS_OK; +} + + +/* + startup the kdc task +*/ +static NTSTATUS kdc_task_init(struct task_server *task) +{ + struct kdc_server *kdc; + NTSTATUS status; + struct interface *ifaces; + + switch (lpcfg_server_role(task->lp_ctx)) { + case ROLE_STANDALONE: + task_server_terminate(task, "kdc: no KDC required in standalone configuration", false); + return NT_STATUS_INVALID_DOMAIN_ROLE; + case ROLE_DOMAIN_MEMBER: + task_server_terminate(task, "kdc: no KDC required in member server configuration", false); + return NT_STATUS_INVALID_DOMAIN_ROLE; + case ROLE_DOMAIN_PDC: + case ROLE_DOMAIN_BDC: + case ROLE_IPA_DC: + task_server_terminate( + task, "Cannot start KDC as a 'classic Samba' DC", false); + return NT_STATUS_INVALID_DOMAIN_ROLE; + case ROLE_ACTIVE_DIRECTORY_DC: + /* Yes, we want a KDC */ + break; + } + + load_interface_list(task, task->lp_ctx, &ifaces); + + if (iface_list_count(ifaces) == 0) { + task_server_terminate(task, "kdc: no network interfaces configured", false); + return NT_STATUS_UNSUCCESSFUL; + } + + task_server_set_title(task, "task[kdc]"); + + kdc = talloc_zero(task, struct kdc_server); + if (kdc == NULL) { + task_server_terminate(task, "kdc: out of memory", true); + return NT_STATUS_NO_MEMORY; + } + + kdc->task = task; + task->private_data = kdc; + + /* start listening on the configured network interfaces */ + status = kdc_startup_interfaces(kdc, task->lp_ctx, ifaces, + task->model_ops); + if (!NT_STATUS_IS_OK(status)) { + task_server_terminate(task, "kdc failed to setup interfaces", true); + return status; + } + + + return NT_STATUS_OK; +} + +/* + initialise the kdc task after a fork +*/ +static void kdc_post_fork(struct task_server *task, struct process_details *pd) +{ + struct kdc_server *kdc; + krb5_kdc_configuration *kdc_config = NULL; + NTSTATUS status; + krb5_error_code ret; + int ldb_ret; + + if (task == NULL) { + task_server_terminate(task, "kdc: Null task", true); + return; + } + if (task->private_data == NULL) { + task_server_terminate(task, "kdc: No kdc_server info", true); + return; + } + kdc = talloc_get_type_abort(task->private_data, struct kdc_server); + + /* get a samdb connection */ + kdc->samdb = samdb_connect(kdc, + kdc->task->event_ctx, + kdc->task->lp_ctx, + system_session(kdc->task->lp_ctx), + NULL, + 0); + if (!kdc->samdb) { + DBG_WARNING("kdc_task_init: unable to connect to samdb\n"); + task_server_terminate(task, "kdc: krb5_init_context samdb connect failed", true); + return; + } + + ldb_ret = samdb_rodc(kdc->samdb, &kdc->am_rodc); + if (ldb_ret != LDB_SUCCESS) { + DBG_WARNING("kdc_task_init: " + "Cannot determine if we are an RODC: %s\n", + ldb_errstring(kdc->samdb)); + task_server_terminate(task, "kdc: krb5_init_context samdb RODC connect failed", true); + return; + } + + kdc->proxy_timeout = lpcfg_parm_int(kdc->task->lp_ctx, NULL, "kdc", "proxy timeout", 5); + + initialize_krb5_error_table(); + + ret = smb_krb5_init_context(kdc, task->lp_ctx, &kdc->smb_krb5_context); + if (ret) { + DBG_WARNING("kdc_task_init: krb5_init_context failed (%s)\n", + error_message(ret)); + task_server_terminate(task, "kdc: krb5_init_context failed", true); + return; + } + + krb5_add_et_list(kdc->smb_krb5_context->krb5_context, initialize_hdb_error_table_r); + + ret = krb5_kdc_get_config(kdc->smb_krb5_context->krb5_context, + &kdc_config); + if(ret) { + task_server_terminate(task, "kdc: failed to get KDC configuration", true); + return; + } + + kdc_config->logf = (krb5_log_facility *)kdc->smb_krb5_context->pvt_log_data; + kdc_config->db = talloc(kdc, struct HDB *); + if (!kdc_config->db) { + task_server_terminate(task, "kdc: out of memory", true); + return; + } + kdc_config->num_db = 1; + + /* + * Note with the CVE-2022-37966 patches, + * see https://bugzilla.samba.org/show_bug.cgi?id=15219 + * and https://bugzilla.samba.org/show_bug.cgi?id=15237 + * we want to use the strongest keys for everything. + * + * Some of these don't have any real effect anymore, + * but it is better to have them as true... + */ + kdc_config->tgt_use_strongest_session_key = true; + kdc_config->preauth_use_strongest_session_key = true; + kdc_config->svc_use_strongest_session_key = true; + kdc_config->use_strongest_server_key = true; + + kdc_config->force_include_pa_etype_salt = true; + + /* + * For Samba CVE-2020-25719 Require PAC to be present + * This instructs Heimdal to match AD behaviour, + * as seen after Microsoft's CVE-2021-42287 when + * PacRequestorEnforcement is set to 2. + * + * Samba BUG: https://bugzilla.samba.org/show_bug.cgi?id=14686 + * REF: https://support.microsoft.com/en-au/topic/kb5008380-authentication-updates-cve-2021-42287-9dafac11-e0d0-4cb8-959a-143bd0201041 + */ + + kdc_config->require_pac = true; + + /* + * By default we enable RFC6113/FAST support, + * but we have an option to disable in order to + * test against a KDC with FAST support. + */ + kdc_config->enable_fast = lpcfg_kdc_enable_fast(task->lp_ctx); + + /* + * Match Windows and RFC6113 and Windows but break older + * Heimdal clients. + */ + kdc_config->enable_armored_pa_enc_timestamp = false; + + /* Register hdb-samba4 hooks for use as a keytab */ + + kdc->base_ctx = talloc_zero(kdc, struct samba_kdc_base_context); + if (!kdc->base_ctx) { + task_server_terminate(task, "kdc: out of memory", true); + return; + } + + kdc->base_ctx->ev_ctx = task->event_ctx; + kdc->base_ctx->lp_ctx = task->lp_ctx; + kdc->base_ctx->msg_ctx = task->msg_ctx; + + status = hdb_samba4_create_kdc(kdc->base_ctx, + kdc->smb_krb5_context->krb5_context, + &kdc_config->db[0]); + if (!NT_STATUS_IS_OK(status)) { + task_server_terminate(task, "kdc: hdb_samba4_create_kdc (setup KDC database) failed", true); + return; + } + + ret = krb5_plugin_register(kdc->smb_krb5_context->krb5_context, + PLUGIN_TYPE_DATA, "hdb_samba4_interface", + &hdb_samba4_interface); + if(ret) { + task_server_terminate(task, "kdc: failed to register hdb plugin", true); + return; + } + + kdc->kpasswd_keytab_name = talloc_asprintf(kdc, "HDBGET:samba4:&%p", kdc->base_ctx); + if (kdc->kpasswd_keytab_name == NULL) { + task_server_terminate(task, + "kdc: Failed to set keytab name", + true); + return; + } + + ret = krb5_kt_register(kdc->smb_krb5_context->krb5_context, &hdb_get_kt_ops); + if(ret) { + task_server_terminate(task, "kdc: failed to register keytab plugin", true); + return; + } + + /* Register KDC hooks */ + ret = krb5_plugin_register(kdc->smb_krb5_context->krb5_context, + PLUGIN_TYPE_DATA, "kdc", + &kdc_plugin_table); + if(ret) { + task_server_terminate(task, "kdc: failed to register kdc plugin", true); + return; + } + + ret = krb5_kdc_plugin_init(kdc->smb_krb5_context->krb5_context); + + if(ret) { + task_server_terminate(task, "kdc: failed to init kdc plugin", true); + return; + } + + ret = krb5_kdc_pkinit_config(kdc->smb_krb5_context->krb5_context, kdc_config); + + if(ret) { + task_server_terminate(task, "kdc: failed to init kdc pkinit subsystem", true); + return; + } + kdc->private_data = kdc_config; + + status = IRPC_REGISTER(task->msg_ctx, irpc, KDC_CHECK_GENERIC_KERBEROS, + kdc_check_generic_kerberos, kdc); + if (!NT_STATUS_IS_OK(status)) { + task_server_terminate(task, "kdc failed to setup monitoring", true); + return; + } + + irpc_add_name(task->msg_ctx, "kdc_server"); +} + + +/* called at smbd startup - register ourselves as a server service */ +NTSTATUS server_service_kdc_init(TALLOC_CTX *ctx) +{ + static const struct service_details details = { + .inhibit_fork_on_accept = true, + .inhibit_pre_fork = false, + .task_init = kdc_task_init, + .post_fork = kdc_post_fork + }; + return register_server_service(ctx, "kdc", &details); +} diff --git a/source4/kdc/kdc-proxy.c b/source4/kdc/kdc-proxy.c new file mode 100644 index 0000000..64160b3 --- /dev/null +++ b/source4/kdc/kdc-proxy.c @@ -0,0 +1,594 @@ +/* + Unix SMB/CIFS implementation. + + KDC Server request proxying + + Copyright (C) Andrew Tridgell 2010 + Copyright (C) Andrew Bartlett 2010 + Copyright (C) Stefan Metzmacher 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "samba/process_model.h" +#include "lib/tsocket/tsocket.h" +#include "libcli/util/tstream.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/stream/packet.h" +#include "kdc/kdc-server.h" +#include "kdc/kdc-proxy.h" +#include "dsdb/samdb/samdb.h" +#include "libcli/composite/composite.h" +#include "libcli/resolve/resolve.h" + + +/* + get a list of our replication partners from repsFrom, returning it in *proxy_list + */ +static WERROR kdc_proxy_get_writeable_dcs(struct kdc_server *kdc, TALLOC_CTX *mem_ctx, char ***proxy_list) +{ + WERROR werr; + uint32_t count, i; + struct repsFromToBlob *reps; + + werr = dsdb_loadreps(kdc->samdb, mem_ctx, ldb_get_default_basedn(kdc->samdb), "repsFrom", &reps, &count); + W_ERROR_NOT_OK_RETURN(werr); + + if (count == 0) { + /* we don't have any DCs to replicate with. Very + strange for a RODC */ + DEBUG(1,(__location__ ": No replication sources for RODC in KDC proxy\n")); + talloc_free(reps); + return WERR_DS_DRA_NO_REPLICA; + } + + (*proxy_list) = talloc_array(mem_ctx, char *, count+1); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(*proxy_list, reps); + + talloc_steal(*proxy_list, reps); + + for (i=0; i<count; i++) { + const char *dns_name = NULL; + if (reps->version == 1) { + dns_name = reps->ctr.ctr1.other_info->dns_name; + } else if (reps->version == 2) { + dns_name = reps->ctr.ctr2.other_info->dns_name1; + } + (*proxy_list)[i] = talloc_strdup(*proxy_list, dns_name); + W_ERROR_HAVE_NO_MEMORY_AND_FREE((*proxy_list)[i], *proxy_list); + } + (*proxy_list)[i] = NULL; + + talloc_free(reps); + + return WERR_OK; +} + + +struct kdc_udp_proxy_state { + struct tevent_context *ev; + struct kdc_server *kdc; + uint16_t port; + DATA_BLOB in; + DATA_BLOB out; + char **proxy_list; + uint32_t next_proxy; + struct { + struct nbt_name name; + const char *ip; + struct tdgram_context *dgram; + } proxy; +}; + + +static void kdc_udp_next_proxy(struct tevent_req *req); + +struct tevent_req *kdc_udp_proxy_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kdc_server *kdc, + uint16_t port, + DATA_BLOB in) +{ + struct tevent_req *req; + struct kdc_udp_proxy_state *state; + WERROR werr; + + req = tevent_req_create(mem_ctx, &state, + struct kdc_udp_proxy_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->kdc = kdc; + state->port = port; + state->in = in; + + werr = kdc_proxy_get_writeable_dcs(kdc, state, &state->proxy_list); + if (!W_ERROR_IS_OK(werr)) { + NTSTATUS status = werror_to_ntstatus(werr); + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + kdc_udp_next_proxy(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void kdc_udp_proxy_resolve_done(struct composite_context *csubreq); + +/* + try the next proxy in the list + */ +static void kdc_udp_next_proxy(struct tevent_req *req) +{ + struct kdc_udp_proxy_state *state = + tevent_req_data(req, + struct kdc_udp_proxy_state); + const char *proxy_dnsname = state->proxy_list[state->next_proxy]; + struct composite_context *csubreq; + + if (proxy_dnsname == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_LOGON_SERVERS); + return; + } + + state->next_proxy++; + + /* make sure we close the socket of the last try */ + TALLOC_FREE(state->proxy.dgram); + ZERO_STRUCT(state->proxy); + + make_nbt_name(&state->proxy.name, proxy_dnsname, 0); + + csubreq = resolve_name_ex_send(lpcfg_resolve_context(state->kdc->task->lp_ctx), + state, + RESOLVE_NAME_FLAG_FORCE_DNS, + 0, + &state->proxy.name, + state->ev); + if (tevent_req_nomem(csubreq, req)) { + return; + } + csubreq->async.fn = kdc_udp_proxy_resolve_done; + csubreq->async.private_data = req; +} + +static void kdc_udp_proxy_sendto_done(struct tevent_req *subreq); +static void kdc_udp_proxy_recvfrom_done(struct tevent_req *subreq); + +static void kdc_udp_proxy_resolve_done(struct composite_context *csubreq) +{ + struct tevent_req *req = + talloc_get_type_abort(csubreq->async.private_data, + struct tevent_req); + struct kdc_udp_proxy_state *state = + tevent_req_data(req, + struct kdc_udp_proxy_state); + NTSTATUS status; + struct tevent_req *subreq; + struct tsocket_address *local_addr, *proxy_addr; + int ret; + bool ok; + + status = resolve_name_recv(csubreq, state, &state->proxy.ip); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to resolve proxy[%s] - %s\n", + state->proxy.name.name, nt_errstr(status))); + kdc_udp_next_proxy(req); + return; + } + + /* get an address for us to use locally */ + ret = tsocket_address_inet_from_strings(state, "ip", NULL, 0, &local_addr); + if (ret != 0) { + kdc_udp_next_proxy(req); + return; + } + + ret = tsocket_address_inet_from_strings(state, "ip", + state->proxy.ip, + state->port, + &proxy_addr); + if (ret != 0) { + kdc_udp_next_proxy(req); + return; + } + + /* create a socket for us to work on */ + ret = tdgram_inet_udp_socket(local_addr, proxy_addr, + state, &state->proxy.dgram); + if (ret != 0) { + kdc_udp_next_proxy(req); + return; + } + + subreq = tdgram_sendto_send(state, + state->ev, + state->proxy.dgram, + state->in.data, + state->in.length, + NULL); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, kdc_udp_proxy_sendto_done, req); + + /* setup to receive the reply from the proxy */ + subreq = tdgram_recvfrom_send(state, state->ev, state->proxy.dgram); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, kdc_udp_proxy_recvfrom_done, req); + + ok = tevent_req_set_endtime( + subreq, + state->ev, + timeval_current_ofs(state->kdc->proxy_timeout, 0)); + if (!ok) { + DBG_DEBUG("tevent_req_set_endtime failed\n"); + return; + } + + DEBUG(4,("kdc_udp_proxy: proxying request to %s[%s]\n", + state->proxy.name.name, state->proxy.ip)); +} + +/* + called when the send of the call to the proxy is complete + this is used to get an errors from the sendto() + */ +static void kdc_udp_proxy_sendto_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct kdc_udp_proxy_state *state = + tevent_req_data(req, + struct kdc_udp_proxy_state); + ssize_t ret; + int sys_errno; + + ret = tdgram_sendto_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + DEBUG(4,("kdc_udp_proxy: sendto for %s[%s] gave %d : %s\n", + state->proxy.name.name, state->proxy.ip, + sys_errno, strerror(sys_errno))); + kdc_udp_next_proxy(req); + } +} + +/* + called when the proxy replies + */ +static void kdc_udp_proxy_recvfrom_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct kdc_udp_proxy_state *state = + tevent_req_data(req, + struct kdc_udp_proxy_state); + int sys_errno; + uint8_t *buf; + ssize_t len; + + len = tdgram_recvfrom_recv(subreq, &sys_errno, + state, &buf, NULL); + TALLOC_FREE(subreq); + if (len == -1) { + DEBUG(4,("kdc_udp_proxy: reply from %s[%s] gave %d : %s\n", + state->proxy.name.name, state->proxy.ip, + sys_errno, strerror(sys_errno))); + kdc_udp_next_proxy(req); + return; + } + + /* + * Check the reply came from the right IP? + * As we use connected udp sockets, that should not be needed... + */ + + state->out.length = len; + state->out.data = buf; + + tevent_req_done(req); +} + +NTSTATUS kdc_udp_proxy_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + DATA_BLOB *out) +{ + struct kdc_udp_proxy_state *state = + tevent_req_data(req, + struct kdc_udp_proxy_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + out->data = talloc_move(mem_ctx, &state->out.data); + out->length = state->out.length; + + tevent_req_received(req); + return NT_STATUS_OK; +} + +struct kdc_tcp_proxy_state { + struct tevent_context *ev; + struct kdc_server *kdc; + uint16_t port; + DATA_BLOB in; + uint8_t in_hdr[4]; + struct iovec in_iov[2]; + DATA_BLOB out; + char **proxy_list; + uint32_t next_proxy; + struct { + struct nbt_name name; + const char *ip; + struct tstream_context *stream; + } proxy; +}; + +static void kdc_tcp_next_proxy(struct tevent_req *req); + +struct tevent_req *kdc_tcp_proxy_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kdc_server *kdc, + uint16_t port, + DATA_BLOB in) +{ + struct tevent_req *req; + struct kdc_tcp_proxy_state *state; + WERROR werr; + + req = tevent_req_create(mem_ctx, &state, + struct kdc_tcp_proxy_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->kdc = kdc; + state->port = port; + state->in = in; + + werr = kdc_proxy_get_writeable_dcs(kdc, state, &state->proxy_list); + if (!W_ERROR_IS_OK(werr)) { + NTSTATUS status = werror_to_ntstatus(werr); + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + RSIVAL(state->in_hdr, 0, state->in.length); + state->in_iov[0].iov_base = (char *)state->in_hdr; + state->in_iov[0].iov_len = 4; + state->in_iov[1].iov_base = (char *)state->in.data; + state->in_iov[1].iov_len = state->in.length; + + kdc_tcp_next_proxy(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void kdc_tcp_proxy_resolve_done(struct composite_context *csubreq); + +/* + try the next proxy in the list + */ +static void kdc_tcp_next_proxy(struct tevent_req *req) +{ + struct kdc_tcp_proxy_state *state = + tevent_req_data(req, + struct kdc_tcp_proxy_state); + const char *proxy_dnsname = state->proxy_list[state->next_proxy]; + struct composite_context *csubreq; + + if (proxy_dnsname == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_LOGON_SERVERS); + return; + } + + state->next_proxy++; + + /* make sure we close the socket of the last try */ + TALLOC_FREE(state->proxy.stream); + ZERO_STRUCT(state->proxy); + + make_nbt_name(&state->proxy.name, proxy_dnsname, 0); + + csubreq = resolve_name_ex_send(lpcfg_resolve_context(state->kdc->task->lp_ctx), + state, + RESOLVE_NAME_FLAG_FORCE_DNS, + 0, + &state->proxy.name, + state->ev); + if (tevent_req_nomem(csubreq, req)) { + return; + } + csubreq->async.fn = kdc_tcp_proxy_resolve_done; + csubreq->async.private_data = req; +} + +static void kdc_tcp_proxy_connect_done(struct tevent_req *subreq); + +static void kdc_tcp_proxy_resolve_done(struct composite_context *csubreq) +{ + struct tevent_req *req = + talloc_get_type_abort(csubreq->async.private_data, + struct tevent_req); + struct kdc_tcp_proxy_state *state = + tevent_req_data(req, + struct kdc_tcp_proxy_state); + NTSTATUS status; + struct tevent_req *subreq; + struct tsocket_address *local_addr, *proxy_addr; + int ret; + + status = resolve_name_recv(csubreq, state, &state->proxy.ip); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to resolve proxy[%s] - %s\n", + state->proxy.name.name, nt_errstr(status))); + kdc_tcp_next_proxy(req); + return; + } + + /* get an address for us to use locally */ + ret = tsocket_address_inet_from_strings(state, "ip", NULL, 0, &local_addr); + if (ret != 0) { + kdc_tcp_next_proxy(req); + return; + } + + ret = tsocket_address_inet_from_strings(state, "ip", + state->proxy.ip, + state->port, + &proxy_addr); + if (ret != 0) { + kdc_tcp_next_proxy(req); + return; + } + + subreq = tstream_inet_tcp_connect_send(state, state->ev, + local_addr, proxy_addr); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, kdc_tcp_proxy_connect_done, req); + tevent_req_set_endtime(subreq, state->ev, + timeval_current_ofs(state->kdc->proxy_timeout, 0)); +} + +static void kdc_tcp_proxy_writev_done(struct tevent_req *subreq); +static void kdc_tcp_proxy_read_pdu_done(struct tevent_req *subreq); + +static void kdc_tcp_proxy_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct kdc_tcp_proxy_state *state = + tevent_req_data(req, + struct kdc_tcp_proxy_state); + int ret, sys_errno; + + ret = tstream_inet_tcp_connect_recv(subreq, &sys_errno, + state, &state->proxy.stream, NULL); + TALLOC_FREE(subreq); + if (ret != 0) { + kdc_tcp_next_proxy(req); + return; + } + + subreq = tstream_writev_send(state, + state->ev, + state->proxy.stream, + state->in_iov, 2); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, kdc_tcp_proxy_writev_done, req); + + subreq = tstream_read_pdu_blob_send(state, + state->ev, + state->proxy.stream, + 4, /* initial_read_size */ + packet_full_request_u32, + req); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, kdc_tcp_proxy_read_pdu_done, req); + tevent_req_set_endtime(subreq, state->kdc->task->event_ctx, + timeval_current_ofs(state->kdc->proxy_timeout, 0)); + + DEBUG(4,("kdc_tcp_proxy: proxying request to %s[%s]\n", + state->proxy.name.name, state->proxy.ip)); +} + +static void kdc_tcp_proxy_writev_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + int ret, sys_errno; + + ret = tstream_writev_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + kdc_tcp_next_proxy(req); + } +} + +static void kdc_tcp_proxy_read_pdu_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct kdc_tcp_proxy_state *state = + tevent_req_data(req, + struct kdc_tcp_proxy_state); + NTSTATUS status; + DATA_BLOB raw; + + status = tstream_read_pdu_blob_recv(subreq, state, &raw); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + kdc_tcp_next_proxy(req); + return; + } + + /* + * raw blob has the length in the first 4 bytes, + * which we do not need here. + */ + state->out = data_blob_talloc(state, raw.data + 4, raw.length - 4); + if (state->out.length != raw.length - 4) { + tevent_req_oom(req); + return; + } + + tevent_req_done(req); +} + +NTSTATUS kdc_tcp_proxy_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + DATA_BLOB *out) +{ + struct kdc_tcp_proxy_state *state = + tevent_req_data(req, + struct kdc_tcp_proxy_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + out->data = talloc_move(mem_ctx, &state->out.data); + out->length = state->out.length; + + tevent_req_received(req); + return NT_STATUS_OK; +} diff --git a/source4/kdc/kdc-proxy.h b/source4/kdc/kdc-proxy.h new file mode 100644 index 0000000..1ebe8da --- /dev/null +++ b/source4/kdc/kdc-proxy.h @@ -0,0 +1,45 @@ +/* + Unix SMB/CIFS implementation. + + KDC related functions + + Copyright (c) 2005-2008 Andrew Bartlett <abartlet@samba.org> + Copyright (c) 2005 Andrew Tridgell <tridge@samba.org> + Copyright (c) 2005 Stefan Metzmacher <metze@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _KDC_PROXY_H +#define _KDC_PROXY_H + +struct tevent_req *kdc_udp_proxy_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kdc_server *kdc, + uint16_t port, + DATA_BLOB in); +NTSTATUS kdc_udp_proxy_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + DATA_BLOB *out); + +struct tevent_req *kdc_tcp_proxy_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kdc_server *kdc, + uint16_t port, + DATA_BLOB in); +NTSTATUS kdc_tcp_proxy_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + DATA_BLOB *out); + +#endif /* _KDC_PROXY_H */ diff --git a/source4/kdc/kdc-server.c b/source4/kdc/kdc-server.c new file mode 100644 index 0000000..de0ae8d --- /dev/null +++ b/source4/kdc/kdc-server.c @@ -0,0 +1,618 @@ +/* + Unix SMB/CIFS implementation. + + KDC related functions + + Copyright (c) 2005-2008 Andrew Bartlett <abartlet@samba.org> + Copyright (c) 2005 Andrew Tridgell <tridge@samba.org> + Copyright (c) 2005 Stefan Metzmacher <metze@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "param/param.h" +#include "samba/process_model.h" +#include "lib/tsocket/tsocket.h" +#include "libcli/util/tstream.h" +#include "kdc/kdc-server.h" +#include "kdc/kdc-proxy.h" +#include "lib/stream/packet.h" + +/* + * State of an open tcp connection + */ +struct kdc_tcp_connection { + /* stream connection we belong to */ + struct stream_connection *conn; + + /* the kdc_server the connection belongs to */ + struct kdc_socket *kdc_socket; + + struct tstream_context *tstream; + + struct tevent_queue *send_queue; +}; + +struct kdc_tcp_call { + struct kdc_tcp_connection *kdc_conn; + DATA_BLOB in; + DATA_BLOB out; + uint8_t out_hdr[4]; + struct iovec out_iov[2]; +}; + +struct kdc_udp_call { + struct kdc_udp_socket *sock; + struct tsocket_address *src; + DATA_BLOB in; + DATA_BLOB out; +}; + +static void kdc_udp_call_proxy_done(struct tevent_req *subreq); +static void kdc_udp_call_sendto_done(struct tevent_req *subreq); + +static void kdc_tcp_call_writev_done(struct tevent_req *subreq); +static void kdc_tcp_call_proxy_done(struct tevent_req *subreq); + +static void kdc_tcp_terminate_connection(struct kdc_tcp_connection *kdc_conn, + const char *reason) +{ + stream_terminate_connection(kdc_conn->conn, reason); +} + +static NTSTATUS kdc_proxy_unavailable_error(struct kdc_server *kdc, + TALLOC_CTX *mem_ctx, + DATA_BLOB *out) +{ + krb5_error_code code; + krb5_data enc_error; + + code = smb_krb5_mk_error(kdc->smb_krb5_context->krb5_context, + KRB5KDC_ERR_SVC_UNAVAILABLE, + NULL, + NULL, + NULL, + NULL, + &enc_error); + if (code != 0) { + DBG_WARNING("Unable to form krb5 error reply\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + *out = data_blob_talloc(mem_ctx, enc_error.data, enc_error.length); + smb_krb5_free_data_contents(kdc->smb_krb5_context->krb5_context, + &enc_error); + if (!out->data) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static void kdc_udp_call_loop(struct tevent_req *subreq) +{ + struct kdc_udp_socket *sock = tevent_req_callback_data(subreq, + struct kdc_udp_socket); + struct kdc_udp_call *call; + uint8_t *buf; + ssize_t len; + int sys_errno; + kdc_code ret; + + call = talloc(sock, struct kdc_udp_call); + if (call == NULL) { + talloc_free(call); + goto done; + } + call->sock = sock; + + len = tdgram_recvfrom_recv(subreq, &sys_errno, + call, &buf, &call->src); + TALLOC_FREE(subreq); + if (len == -1) { + talloc_free(call); + goto done; + } + + call->in.data = buf; + call->in.length = len; + + DEBUG(10,("Received krb5 UDP packet of length %lu from %s\n", + (long)call->in.length, + tsocket_address_string(call->src, call))); + + /* Call krb5 */ + ret = sock->kdc_socket->process(sock->kdc_socket->kdc, + call, + &call->in, + &call->out, + call->src, + sock->kdc_socket->local_address, + 1 /* Datagram */); + if (ret == KDC_ERROR) { + talloc_free(call); + goto done; + } + + if (ret == KDC_PROXY_REQUEST) { + uint16_t port; + + if (!sock->kdc_socket->kdc->am_rodc) { + DEBUG(0,("kdc_udp_call_loop: proxying requested when not RODC")); + talloc_free(call); + goto done; + } + + port = tsocket_address_inet_port(sock->kdc_socket->local_address); + + subreq = kdc_udp_proxy_send(call, + sock->kdc_socket->kdc->task->event_ctx, + sock->kdc_socket->kdc, + port, + call->in); + if (subreq == NULL) { + talloc_free(call); + goto done; + } + tevent_req_set_callback(subreq, kdc_udp_call_proxy_done, call); + goto done; + } + + subreq = tdgram_sendto_queue_send(call, + sock->kdc_socket->kdc->task->event_ctx, + sock->dgram, + sock->send_queue, + call->out.data, + call->out.length, + call->src); + if (subreq == NULL) { + talloc_free(call); + goto done; + } + tevent_req_set_callback(subreq, kdc_udp_call_sendto_done, call); + +done: + subreq = tdgram_recvfrom_send(sock, + sock->kdc_socket->kdc->task->event_ctx, + sock->dgram); + if (subreq == NULL) { + task_server_terminate(sock->kdc_socket->kdc->task, + "no memory for tdgram_recvfrom_send", + true); + return; + } + tevent_req_set_callback(subreq, kdc_udp_call_loop, sock); +} + +static void kdc_udp_call_proxy_done(struct tevent_req *subreq) +{ + struct kdc_udp_call *call = + tevent_req_callback_data(subreq, + struct kdc_udp_call); + NTSTATUS status; + + status = kdc_udp_proxy_recv(subreq, call, &call->out); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + /* generate an error packet */ + status = kdc_proxy_unavailable_error(call->sock->kdc_socket->kdc, + call, &call->out); + } + + if (!NT_STATUS_IS_OK(status)) { + talloc_free(call); + return; + } + + subreq = tdgram_sendto_queue_send(call, + call->sock->kdc_socket->kdc->task->event_ctx, + call->sock->dgram, + call->sock->send_queue, + call->out.data, + call->out.length, + call->src); + if (subreq == NULL) { + talloc_free(call); + return; + } + + tevent_req_set_callback(subreq, kdc_udp_call_sendto_done, call); +} + +static void kdc_udp_call_sendto_done(struct tevent_req *subreq) +{ + struct kdc_udp_call *call = tevent_req_callback_data(subreq, + struct kdc_udp_call); + int sys_errno; + + tdgram_sendto_queue_recv(subreq, &sys_errno); + + /* We don't care about errors */ + + talloc_free(call); +} + +static void kdc_tcp_call_loop(struct tevent_req *subreq) +{ + struct kdc_tcp_connection *kdc_conn = tevent_req_callback_data(subreq, + struct kdc_tcp_connection); + struct kdc_tcp_call *call; + NTSTATUS status; + kdc_code ret; + + call = talloc(kdc_conn, struct kdc_tcp_call); + if (call == NULL) { + kdc_tcp_terminate_connection(kdc_conn, "kdc_tcp_call_loop: " + "no memory for kdc_tcp_call"); + return; + } + call->kdc_conn = kdc_conn; + + status = tstream_read_pdu_blob_recv(subreq, + call, + &call->in); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + const char *reason; + + reason = talloc_asprintf(call, "kdc_tcp_call_loop: " + "tstream_read_pdu_blob_recv() - %s", + nt_errstr(status)); + if (!reason) { + reason = nt_errstr(status); + } + + kdc_tcp_terminate_connection(kdc_conn, reason); + return; + } + + DEBUG(10,("Received krb5 TCP packet of length %lu from %s\n", + (long) call->in.length, + tsocket_address_string(kdc_conn->conn->remote_address, call))); + + /* skip length header */ + call->in.data +=4; + call->in.length -= 4; + + /* Call krb5 */ + ret = kdc_conn->kdc_socket->process(kdc_conn->kdc_socket->kdc, + call, + &call->in, + &call->out, + kdc_conn->conn->remote_address, + kdc_conn->conn->local_address, + 0 /* Stream */); + if (ret == KDC_ERROR) { + kdc_tcp_terminate_connection(kdc_conn, + "kdc_tcp_call_loop: process function failed"); + return; + } + + if (ret == KDC_PROXY_REQUEST) { + uint16_t port; + + if (!kdc_conn->kdc_socket->kdc->am_rodc) { + kdc_tcp_terminate_connection(kdc_conn, + "kdc_tcp_call_loop: proxying requested when not RODC"); + return; + } + port = tsocket_address_inet_port(kdc_conn->conn->local_address); + + subreq = kdc_tcp_proxy_send(call, + kdc_conn->conn->event.ctx, + kdc_conn->kdc_socket->kdc, + port, + call->in); + if (subreq == NULL) { + kdc_tcp_terminate_connection(kdc_conn, + "kdc_tcp_call_loop: kdc_tcp_proxy_send failed"); + return; + } + tevent_req_set_callback(subreq, kdc_tcp_call_proxy_done, call); + return; + } + + /* First add the length of the out buffer */ + RSIVAL(call->out_hdr, 0, call->out.length); + call->out_iov[0].iov_base = (char *) call->out_hdr; + call->out_iov[0].iov_len = 4; + + call->out_iov[1].iov_base = (char *) call->out.data; + call->out_iov[1].iov_len = call->out.length; + + subreq = tstream_writev_queue_send(call, + kdc_conn->conn->event.ctx, + kdc_conn->tstream, + kdc_conn->send_queue, + call->out_iov, 2); + if (subreq == NULL) { + kdc_tcp_terminate_connection(kdc_conn, "kdc_tcp_call_loop: " + "no memory for tstream_writev_queue_send"); + return; + } + tevent_req_set_callback(subreq, kdc_tcp_call_writev_done, call); + + /* + * The krb5 tcp pdu's has the length as 4 byte (initial_read_size), + * packet_full_request_u32 provides the pdu length then. + */ + subreq = tstream_read_pdu_blob_send(kdc_conn, + kdc_conn->conn->event.ctx, + kdc_conn->tstream, + 4, /* initial_read_size */ + packet_full_request_u32, + kdc_conn); + if (subreq == NULL) { + kdc_tcp_terminate_connection(kdc_conn, "kdc_tcp_call_loop: " + "no memory for tstream_read_pdu_blob_send"); + return; + } + tevent_req_set_callback(subreq, kdc_tcp_call_loop, kdc_conn); +} + +static void kdc_tcp_call_proxy_done(struct tevent_req *subreq) +{ + struct kdc_tcp_call *call = tevent_req_callback_data(subreq, + struct kdc_tcp_call); + struct kdc_tcp_connection *kdc_conn = call->kdc_conn; + NTSTATUS status; + + status = kdc_tcp_proxy_recv(subreq, call, &call->out); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + /* generate an error packet */ + status = kdc_proxy_unavailable_error(kdc_conn->kdc_socket->kdc, + call, &call->out); + } + + if (!NT_STATUS_IS_OK(status)) { + const char *reason; + + reason = talloc_asprintf(call, "kdc_tcp_call_proxy_done: " + "kdc_proxy_unavailable_error - %s", + nt_errstr(status)); + if (!reason) { + reason = "kdc_tcp_call_proxy_done: kdc_proxy_unavailable_error() failed"; + } + + kdc_tcp_terminate_connection(call->kdc_conn, reason); + return; + } + + /* First add the length of the out buffer */ + RSIVAL(call->out_hdr, 0, call->out.length); + call->out_iov[0].iov_base = (char *) call->out_hdr; + call->out_iov[0].iov_len = 4; + + call->out_iov[1].iov_base = (char *) call->out.data; + call->out_iov[1].iov_len = call->out.length; + + subreq = tstream_writev_queue_send(call, + kdc_conn->conn->event.ctx, + kdc_conn->tstream, + kdc_conn->send_queue, + call->out_iov, 2); + if (subreq == NULL) { + kdc_tcp_terminate_connection(kdc_conn, "kdc_tcp_call_loop: " + "no memory for tstream_writev_queue_send"); + return; + } + tevent_req_set_callback(subreq, kdc_tcp_call_writev_done, call); + + /* + * The krb5 tcp pdu's has the length as 4 byte (initial_read_size), + * packet_full_request_u32 provides the pdu length then. + */ + subreq = tstream_read_pdu_blob_send(kdc_conn, + kdc_conn->conn->event.ctx, + kdc_conn->tstream, + 4, /* initial_read_size */ + packet_full_request_u32, + kdc_conn); + if (subreq == NULL) { + kdc_tcp_terminate_connection(kdc_conn, "kdc_tcp_call_loop: " + "no memory for tstream_read_pdu_blob_send"); + return; + } + tevent_req_set_callback(subreq, kdc_tcp_call_loop, kdc_conn); +} + +static void kdc_tcp_call_writev_done(struct tevent_req *subreq) +{ + struct kdc_tcp_call *call = tevent_req_callback_data(subreq, + struct kdc_tcp_call); + int sys_errno; + int rc; + + rc = tstream_writev_queue_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (rc == -1) { + const char *reason; + + reason = talloc_asprintf(call, "kdc_tcp_call_writev_done: " + "tstream_writev_queue_recv() - %d:%s", + sys_errno, strerror(sys_errno)); + if (!reason) { + reason = "kdc_tcp_call_writev_done: tstream_writev_queue_recv() failed"; + } + + kdc_tcp_terminate_connection(call->kdc_conn, reason); + return; + } + + /* We don't care about errors */ + + talloc_free(call); +} + +/* + called when we get a new connection +*/ +static void kdc_tcp_accept(struct stream_connection *conn) +{ + struct kdc_socket *kdc_socket; + struct kdc_tcp_connection *kdc_conn; + struct tevent_req *subreq; + int rc; + + kdc_conn = talloc_zero(conn, struct kdc_tcp_connection); + if (kdc_conn == NULL) { + stream_terminate_connection(conn, + "kdc_tcp_accept: out of memory"); + return; + } + + kdc_conn->send_queue = tevent_queue_create(conn, "kdc_tcp_accept"); + if (kdc_conn->send_queue == NULL) { + stream_terminate_connection(conn, + "kdc_tcp_accept: out of memory"); + return; + } + + kdc_socket = talloc_get_type(conn->private_data, struct kdc_socket); + + TALLOC_FREE(conn->event.fde); + + rc = tstream_bsd_existing_socket(kdc_conn, + socket_get_fd(conn->socket), + &kdc_conn->tstream); + if (rc < 0) { + stream_terminate_connection(conn, + "kdc_tcp_accept: out of memory"); + return; + } + + kdc_conn->conn = conn; + kdc_conn->kdc_socket = kdc_socket; + conn->private_data = kdc_conn; + + /* + * The krb5 tcp pdu's has the length as 4 byte (initial_read_size), + * packet_full_request_u32 provides the pdu length then. + */ + subreq = tstream_read_pdu_blob_send(kdc_conn, + kdc_conn->conn->event.ctx, + kdc_conn->tstream, + 4, /* initial_read_size */ + packet_full_request_u32, + kdc_conn); + if (subreq == NULL) { + kdc_tcp_terminate_connection(kdc_conn, "kdc_tcp_accept: " + "no memory for tstream_read_pdu_blob_send"); + return; + } + tevent_req_set_callback(subreq, kdc_tcp_call_loop, kdc_conn); +} + +static void kdc_tcp_recv(struct stream_connection *conn, uint16_t flags) +{ + struct kdc_tcp_connection *kdcconn = talloc_get_type(conn->private_data, + struct kdc_tcp_connection); + /* this should never be triggered! */ + kdc_tcp_terminate_connection(kdcconn, "kdc_tcp_recv: called"); +} + +static void kdc_tcp_send(struct stream_connection *conn, uint16_t flags) +{ + struct kdc_tcp_connection *kdcconn = talloc_get_type(conn->private_data, + struct kdc_tcp_connection); + /* this should never be triggered! */ + kdc_tcp_terminate_connection(kdcconn, "kdc_tcp_send: called"); +} + +static const struct stream_server_ops kdc_tcp_stream_ops = { + .name = "kdc_tcp", + .accept_connection = kdc_tcp_accept, + .recv_handler = kdc_tcp_recv, + .send_handler = kdc_tcp_send +}; + +/* + * Start listening on the given address + */ +NTSTATUS kdc_add_socket(struct kdc_server *kdc, + const struct model_ops *model_ops, + const char *name, + const char *address, + uint16_t port, + kdc_process_fn_t process, + bool udp_only) +{ + struct kdc_socket *kdc_socket; + struct kdc_udp_socket *kdc_udp_socket; + struct tevent_req *udpsubreq; + NTSTATUS status; + int ret; + + kdc_socket = talloc(kdc, struct kdc_socket); + NT_STATUS_HAVE_NO_MEMORY(kdc_socket); + + kdc_socket->kdc = kdc; + kdc_socket->process = process; + + ret = tsocket_address_inet_from_strings(kdc_socket, "ip", + address, port, + &kdc_socket->local_address); + if (ret != 0) { + status = map_nt_error_from_unix_common(errno); + return status; + } + + if (!udp_only) { + status = stream_setup_socket(kdc->task, + kdc->task->event_ctx, + kdc->task->lp_ctx, + model_ops, + &kdc_tcp_stream_ops, + "ip", address, &port, + lpcfg_socket_options(kdc->task->lp_ctx), + kdc_socket, + kdc->task->process_context); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Failed to bind to %s:%u TCP - %s\n", + address, port, nt_errstr(status))); + talloc_free(kdc_socket); + return status; + } + } + + kdc_udp_socket = talloc(kdc_socket, struct kdc_udp_socket); + NT_STATUS_HAVE_NO_MEMORY(kdc_udp_socket); + + kdc_udp_socket->kdc_socket = kdc_socket; + + ret = tdgram_inet_udp_socket(kdc_socket->local_address, + NULL, + kdc_udp_socket, + &kdc_udp_socket->dgram); + if (ret != 0) { + status = map_nt_error_from_unix_common(errno); + DEBUG(0,("Failed to bind to %s:%u UDP - %s\n", + address, port, nt_errstr(status))); + return status; + } + + kdc_udp_socket->send_queue = tevent_queue_create(kdc_udp_socket, + "kdc_udp_send_queue"); + NT_STATUS_HAVE_NO_MEMORY(kdc_udp_socket->send_queue); + + udpsubreq = tdgram_recvfrom_send(kdc_udp_socket, + kdc->task->event_ctx, + kdc_udp_socket->dgram); + NT_STATUS_HAVE_NO_MEMORY(udpsubreq); + tevent_req_set_callback(udpsubreq, kdc_udp_call_loop, kdc_udp_socket); + + return NT_STATUS_OK; +} diff --git a/source4/kdc/kdc-server.h b/source4/kdc/kdc-server.h new file mode 100644 index 0000000..89b30f1 --- /dev/null +++ b/source4/kdc/kdc-server.h @@ -0,0 +1,83 @@ +/* + Unix SMB/CIFS implementation. + + KDC related functions + + Copyright (c) 2005-2008 Andrew Bartlett <abartlet@samba.org> + Copyright (c) 2005 Andrew Tridgell <tridge@samba.org> + Copyright (c) 2005 Stefan Metzmacher <metze@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _KDC_SERVER_H +#define _KDC_SERVER_H + +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" + +struct tsocket_address; +struct model_ops; + +/* + * Context structure for the kdc server + */ +struct kdc_server { + struct task_server *task; + struct smb_krb5_context *smb_krb5_context; + struct samba_kdc_base_context *base_ctx; + struct ldb_context *samdb; + bool am_rodc; + uint32_t proxy_timeout; + const char *kpasswd_keytab_name; + void *private_data; +}; + +typedef enum kdc_code_e { + KDC_OK = 0, + KDC_ERROR, + KDC_PROXY_REQUEST +} kdc_code; + +typedef kdc_code (*kdc_process_fn_t)(struct kdc_server *kdc, + TALLOC_CTX *mem_ctx, + DATA_BLOB *request, + DATA_BLOB *reply, + struct tsocket_address *remote_address, + struct tsocket_address *local_address, + int datagram); + +/* Information about one kdc socket */ +struct kdc_socket { + struct kdc_server *kdc; + struct tsocket_address *local_address; + kdc_process_fn_t process; +}; + +/* Information about one kdc/kpasswd udp socket */ +struct kdc_udp_socket { + struct kdc_socket *kdc_socket; + struct tdgram_context *dgram; + struct tevent_queue *send_queue; +}; + +NTSTATUS kdc_add_socket(struct kdc_server *kdc, + const struct model_ops *model_ops, + const char *name, + const char *address, + uint16_t port, + kdc_process_fn_t process, + bool udp_only); + +#endif /* _KDC_SERVER_H */ diff --git a/source4/kdc/kdc-service-mit.c b/source4/kdc/kdc-service-mit.c new file mode 100644 index 0000000..31cfef2 --- /dev/null +++ b/source4/kdc/kdc-service-mit.c @@ -0,0 +1,392 @@ +/* + Unix SMB/CIFS implementation. + + Start MIT krb5kdc server within Samba AD + + Copyright (c) 2014-2016 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "talloc.h" +#include "tevent.h" +#include "system/filesys.h" +#include "lib/param/param.h" +#include "lib/util/samba_util.h" +#include "source4/samba/service.h" +#include "source4/samba/process_model.h" +#include "kdc/kdc-service-mit.h" +#include "dynconfig.h" +#include "libds/common/roles.h" +#include "lib/socket/netif.h" +#include "samba/session.h" +#include "dsdb/samdb/samdb.h" +#include "kdc/samba_kdc.h" +#include "kdc/kdc-server.h" +#include "kdc/kpasswd-service.h" +#include <kadm5/admin.h> +#include <kdb.h> + +#include "source4/kdc/mit_kdc_irpc.h" + +/* PROTOTYPES */ +static void mitkdc_server_done(struct tevent_req *subreq); + +static int kdc_server_destroy(struct kdc_server *kdc) +{ + if (kdc->private_data != NULL) { + kadm5_destroy(kdc->private_data); + } + + return 0; +} + +static NTSTATUS startup_kpasswd_server(TALLOC_CTX *mem_ctx, + struct kdc_server *kdc, + struct loadparm_context *lp_ctx, + struct interface *ifaces) +{ + int num_interfaces; + int i; + TALLOC_CTX *tmp_ctx; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + uint16_t kpasswd_port; + bool done_wildcard = false; + bool ok; + + kpasswd_port = lpcfg_kpasswd_port(lp_ctx); + if (kpasswd_port == 0) { + return NT_STATUS_OK; + } + + tmp_ctx = talloc_named_const(mem_ctx, 0, "kpasswd"); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + num_interfaces = iface_list_count(ifaces); + + ok = lpcfg_bind_interfaces_only(lp_ctx); + if (!ok) { + int num_binds = 0; + char **wcard; + + wcard = iface_list_wildcard(tmp_ctx); + if (wcard == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + for (i = 0; wcard[i] != NULL; i++) { + status = kdc_add_socket(kdc, + kdc->task->model_ops, + "kpasswd", + wcard[i], + kpasswd_port, + kpasswd_process, + false); + if (NT_STATUS_IS_OK(status)) { + num_binds++; + } + } + talloc_free(wcard); + + if (num_binds == 0) { + status = NT_STATUS_INVALID_PARAMETER_MIX; + goto out; + } + + done_wildcard = true; + } + + for (i = 0; i < num_interfaces; i++) { + const char *address = talloc_strdup(tmp_ctx, iface_list_n_ip(ifaces, i)); + + status = kdc_add_socket(kdc, + kdc->task->model_ops, + "kpasswd", + address, + kpasswd_port, + kpasswd_process, + done_wildcard); + if (NT_STATUS_IS_OK(status)) { + goto out; + } + } + +out: + talloc_free(tmp_ctx); + return status; +} + +/* + * Startup a copy of the krb5kdc as a child daemon + */ +NTSTATUS mitkdc_task_init(struct task_server *task) +{ + struct tevent_req *subreq; + const char * const *kdc_cmd; + struct interface *ifaces; + char *kdc_config = NULL; + struct kdc_server *kdc; + krb5_error_code code; + NTSTATUS status; + kadm5_ret_t ret; + kadm5_config_params config; + void *server_handle; + int dbglvl = 0; + + task_server_set_title(task, "task[mitkdc_parent]"); + + switch (lpcfg_server_role(task->lp_ctx)) { + case ROLE_STANDALONE: + task_server_terminate(task, + "The KDC is not required in standalone " + "server configuration, terminate!", + false); + return NT_STATUS_INVALID_DOMAIN_ROLE; + case ROLE_DOMAIN_MEMBER: + task_server_terminate(task, + "The KDC is not required in member " + "server configuration", + false); + return NT_STATUS_INVALID_DOMAIN_ROLE; + case ROLE_ACTIVE_DIRECTORY_DC: + /* Yes, we want to start the KDC */ + break; + } + + /* Load interfaces for kpasswd */ + load_interface_list(task, task->lp_ctx, &ifaces); + if (iface_list_count(ifaces) == 0) { + task_server_terminate(task, + "KDC: no network interfaces configured", + false); + return NT_STATUS_UNSUCCESSFUL; + } + + kdc_config = talloc_asprintf(task, + "%s/kdc.conf", + lpcfg_private_dir(task->lp_ctx)); + if (kdc_config == NULL) { + task_server_terminate(task, + "KDC: no memory", + false); + return NT_STATUS_NO_MEMORY; + } + setenv("KRB5_KDC_PROFILE", kdc_config, 0); + TALLOC_FREE(kdc_config); + + dbglvl = debuglevel_get_class(DBGC_KERBEROS); + if (dbglvl >= 10) { + char *kdc_trace_file = talloc_asprintf(task, + "%s/mit_kdc_trace.log", + get_dyn_LOGFILEBASE()); + if (kdc_trace_file == NULL) { + task_server_terminate(task, + "KDC: no memory", + false); + return NT_STATUS_NO_MEMORY; + } + + setenv("KRB5_TRACE", kdc_trace_file, 1); + } + + /* start it as a child process */ + kdc_cmd = lpcfg_mit_kdc_command(task->lp_ctx); + + subreq = samba_runcmd_send(task, + task->event_ctx, + timeval_zero(), + 1, /* stdout log level */ + 0, /* stderr log level */ + kdc_cmd, + "-n", /* Don't go into background */ +#if 0 + "-w 2", /* Start two workers */ +#endif + NULL); + if (subreq == NULL) { + DEBUG(0, ("Failed to start MIT KDC as child daemon\n")); + + task_server_terminate(task, + "Failed to startup mitkdc task", + true); + return NT_STATUS_INTERNAL_ERROR; + } + + tevent_req_set_callback(subreq, mitkdc_server_done, task); + + DEBUG(5,("Started krb5kdc process\n")); + + status = samba_setup_mit_kdc_irpc(task); + if (!NT_STATUS_IS_OK(status)) { + task_server_terminate(task, + "Failed to setup kdc irpc service", + true); + } + + DEBUG(5,("Started irpc service for kdc_server\n")); + + kdc = talloc_zero(task, struct kdc_server); + if (kdc == NULL) { + task_server_terminate(task, "KDC: Out of memory", true); + return NT_STATUS_NO_MEMORY; + } + talloc_set_destructor(kdc, kdc_server_destroy); + + kdc->task = task; + + kdc->base_ctx = talloc_zero(kdc, struct samba_kdc_base_context); + if (kdc->base_ctx == NULL) { + task_server_terminate(task, "KDC: Out of memory", true); + return NT_STATUS_NO_MEMORY; + } + + kdc->base_ctx->ev_ctx = task->event_ctx; + kdc->base_ctx->lp_ctx = task->lp_ctx; + + initialize_krb5_error_table(); + + code = smb_krb5_init_context(kdc, + kdc->task->lp_ctx, + &kdc->smb_krb5_context); + if (code != 0) { + task_server_terminate(task, + "KDC: Unable to initialize krb5 context", + true); + return NT_STATUS_INTERNAL_ERROR; + } + + code = kadm5_init_krb5_context(&kdc->smb_krb5_context->krb5_context); + if (code != 0) { + task_server_terminate(task, + "KDC: Unable to init kadm5 krb5_context", + true); + return NT_STATUS_INTERNAL_ERROR; + } + + ZERO_STRUCT(config); + config.mask = KADM5_CONFIG_REALM; + config.realm = discard_const_p(char, lpcfg_realm(kdc->task->lp_ctx)); + + ret = kadm5_init(kdc->smb_krb5_context->krb5_context, + discard_const_p(char, "kpasswd"), + NULL, /* pass */ + discard_const_p(char, "kpasswd"), + &config, + KADM5_STRUCT_VERSION, + KADM5_API_VERSION_4, + NULL, + &server_handle); + if (ret != 0) { + task_server_terminate(task, + "KDC: Initialize kadm5", + true); + return NT_STATUS_INTERNAL_ERROR; + } + kdc->private_data = server_handle; + + code = krb5_db_register_keytab(kdc->smb_krb5_context->krb5_context); + if (code != 0) { + task_server_terminate(task, + "KDC: Unable to KDB", + true); + return NT_STATUS_INTERNAL_ERROR; + } + + kdc->kpasswd_keytab_name = talloc_asprintf(kdc, "KDB:"); + if (kdc->kpasswd_keytab_name == NULL) { + task_server_terminate(task, + "KDC: Out of memory", + true); + return NT_STATUS_NO_MEMORY; + } + + kdc->samdb = samdb_connect(kdc, + kdc->task->event_ctx, + kdc->task->lp_ctx, + system_session(kdc->task->lp_ctx), + NULL, + 0); + if (kdc->samdb == NULL) { + task_server_terminate(task, + "KDC: Unable to connect to samdb", + true); + return NT_STATUS_CONNECTION_INVALID; + } + + status = startup_kpasswd_server(kdc, + kdc, + task->lp_ctx, + ifaces); + if (!NT_STATUS_IS_OK(status)) { + task_server_terminate(task, + "KDC: Unable to start kpasswd server", + true); + return status; + } + + DEBUG(5,("Started kpasswd service for kdc_server\n")); + + return NT_STATUS_OK; +} + +/* + * This gets called the kdc exits. + */ +static void mitkdc_server_done(struct tevent_req *subreq) +{ + struct task_server *task = + tevent_req_callback_data(subreq, + struct task_server); + int sys_errno; + int ret; + + ret = samba_runcmd_recv(subreq, &sys_errno); + if (ret != 0) { + DEBUG(0, ("The MIT KDC daemon died with exit status %d\n", + sys_errno)); + } else { + DEBUG(0,("The MIT KDC daemon exited normally\n")); + } + + task_server_terminate(task, "mitkdc child process exited", true); +} + +/* Called at MIT KRB5 startup - register ourselves as a server service */ +NTSTATUS server_service_mitkdc_init(TALLOC_CTX *mem_ctx); + +NTSTATUS server_service_mitkdc_init(TALLOC_CTX *mem_ctx) +{ + static const struct service_details details = { + .inhibit_fork_on_accept = true, + /* + * Need to prevent pre-forking on kdc. + * The task_init function is run on the master process only + * and the irpc process name is registered in it's event loop. + * The child worker processes initialise their event loops on + * fork, so are not listening for the irpc event. + * + * The master process does not wait on that event context + * the master process is responsible for managing the worker + * processes not performing work. + */ + .inhibit_pre_fork = true, + .task_init = mitkdc_task_init, + .post_fork = NULL + }; + return register_server_service(mem_ctx, "kdc", &details); +} diff --git a/source4/kdc/kdc-service-mit.h b/source4/kdc/kdc-service-mit.h new file mode 100644 index 0000000..7943933 --- /dev/null +++ b/source4/kdc/kdc-service-mit.h @@ -0,0 +1,27 @@ +/* + Unix SMB/CIFS implementation. + + Start MIT krb5kdc server within Samba AD + + Copyright (c) 2014 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _KDC_SERVICE_MIT_H +#define _KDC_SERVICE_MIT_H + +NTSTATUS mitkdc_task_init(struct task_server *task); + +#endif /* _KDC_SERVICE_MIT_H */ diff --git a/source4/kdc/kpasswd-helper.c b/source4/kdc/kpasswd-helper.c new file mode 100644 index 0000000..645f9c9 --- /dev/null +++ b/source4/kdc/kpasswd-helper.c @@ -0,0 +1,261 @@ +/* + Unix SMB/CIFS implementation. + + Samba kpasswd implementation + + Copyright (c) 2005 Andrew Bartlett <abartlet@samba.org> + Copyright (c) 2016 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include "librpc/gen_ndr/samr.h" +#include "dsdb/samdb/samdb.h" +#include "auth/auth.h" +#include "kdc/kpasswd-helper.h" + +bool kpasswd_make_error_reply(TALLOC_CTX *mem_ctx, + krb5_error_code error_code, + const char *error_string, + DATA_BLOB *error_data) +{ + bool ok; + char *s; + size_t slen; + + if (error_code == 0) { + DBG_DEBUG("kpasswd reply - %s\n", error_string); + } else { + DBG_INFO("kpasswd reply - %s\n", error_string); + } + + ok = push_utf8_talloc(mem_ctx, &s, error_string, &slen); + if (!ok) { + return false; + } + + /* + * The string 's' has one terminating nul-byte which is also + * reflected by 'slen'. We subtract it from the length. + */ + if (slen < 1) { + talloc_free(s); + return false; + } + slen--; + + /* Two bytes are added to the length to account for the error code. */ + if (2 + slen < slen) { + talloc_free(s); + return false; + } + error_data->length = 2 + slen; + error_data->data = talloc_size(mem_ctx, error_data->length); + if (error_data->data == NULL) { + talloc_free(s); + return false; + } + + RSSVAL(error_data->data, 0, error_code); + memcpy(error_data->data + 2, s, slen); + + talloc_free(s); + + return true; +} + +bool kpasswd_make_pwchange_reply(TALLOC_CTX *mem_ctx, + NTSTATUS status, + enum samPwdChangeReason reject_reason, + struct samr_DomInfo1 *dominfo, + DATA_BLOB *error_blob) +{ + const char *reject_string = NULL; + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { + return kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_ACCESSDENIED, + "No such user when changing password", + error_blob); + } else if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + return kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_ACCESSDENIED, + "Not permitted to change password", + error_blob); + } + if (dominfo != NULL && + NT_STATUS_EQUAL(status, NT_STATUS_PASSWORD_RESTRICTION)) { + switch (reject_reason) { + case SAM_PWD_CHANGE_PASSWORD_TOO_SHORT: + reject_string = + talloc_asprintf(mem_ctx, + "Password too short, password " + "must be at least %d characters " + "long.", + dominfo->min_password_length); + if (reject_string == NULL) { + reject_string = "Password too short"; + } + break; + case SAM_PWD_CHANGE_NOT_COMPLEX: + reject_string = "Password does not meet complexity " + "requirements"; + break; + case SAM_PWD_CHANGE_PWD_IN_HISTORY: + reject_string = + talloc_asprintf(mem_ctx, + "Password is already in password " + "history. New password must not " + "match any of your %d previous " + "passwords.", + dominfo->password_history_length); + if (reject_string == NULL) { + reject_string = "Password is already in password " + "history"; + } + break; + default: + reject_string = "Password change rejected, password " + "changes may not be permitted on this " + "account, or the minimum password age " + "may not have elapsed."; + break; + } + + return kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_SOFTERROR, + reject_string, + error_blob); + } + + if (!NT_STATUS_IS_OK(status)) { + reject_string = talloc_asprintf(mem_ctx, + "Failed to set password: %s", + nt_errstr(status)); + if (reject_string == NULL) { + reject_string = "Failed to set password"; + } + return kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_HARDERROR, + reject_string, + error_blob); + } + + return kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_SUCCESS, + "Password changed", + error_blob); +} + +NTSTATUS kpasswd_samdb_set_password(TALLOC_CTX *mem_ctx, + struct tevent_context *event_ctx, + struct loadparm_context *lp_ctx, + struct auth_session_info *session_info, + bool is_service_principal, + const char *target_principal_name, + DATA_BLOB *password, + enum samPwdChangeReason *reject_reason, + struct samr_DomInfo1 **dominfo) +{ + NTSTATUS status; + struct ldb_context *samdb; + struct ldb_dn *target_dn = NULL; + int rc; + + samdb = samdb_connect(mem_ctx, + event_ctx, + lp_ctx, + session_info, + NULL, + 0); + if (samdb == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + DBG_INFO("%s\\%s (%s) is changing password of %s\n", + session_info->info->domain_name, + session_info->info->account_name, + dom_sid_string(mem_ctx, + &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]), + target_principal_name); + + rc = ldb_transaction_start(samdb); + if (rc != LDB_SUCCESS) { + return NT_STATUS_TRANSACTION_ABORTED; + } + + if (is_service_principal) { + status = crack_service_principal_name(samdb, + mem_ctx, + target_principal_name, + &target_dn, + NULL); + } else { + status = crack_user_principal_name(samdb, + mem_ctx, + target_principal_name, + &target_dn, + NULL); + } + if (!NT_STATUS_IS_OK(status)) { + ldb_transaction_cancel(samdb); + return status; + } + + status = samdb_set_password(samdb, + mem_ctx, + target_dn, + NULL, /* domain_dn */ + password, + NULL, /* ntNewHash */ + DSDB_PASSWORD_RESET, + reject_reason, + dominfo); + if (NT_STATUS_IS_OK(status)) { + rc = ldb_transaction_commit(samdb); + if (rc != LDB_SUCCESS) { + DBG_WARNING("Failed to commit transaction to " + "set password on %s: %s\n", + ldb_dn_get_linearized(target_dn), + ldb_errstring(samdb)); + return NT_STATUS_TRANSACTION_ABORTED; + } + } else { + ldb_transaction_cancel(samdb); + } + + return status; +} + +krb5_error_code kpasswd_check_non_tgt(struct auth_session_info *session_info, + const char **error_string) +{ + switch(session_info->ticket_type) { + case TICKET_TYPE_TGT: + /* TGTs are disallowed here. */ + *error_string = "A TGT may not be used as a ticket to kpasswd"; + return KRB5_KPASSWD_AUTHERROR; + case TICKET_TYPE_NON_TGT: + /* Non-TGTs are permitted, and expected. */ + break; + default: + /* In case we forgot to set the type. */ + *error_string = "Failed to ascertain that ticket to kpasswd is not a TGT"; + return KRB5_KPASSWD_HARDERROR; + } + + return 0; +} diff --git a/source4/kdc/kpasswd-helper.h b/source4/kdc/kpasswd-helper.h new file mode 100644 index 0000000..94a6e2a --- /dev/null +++ b/source4/kdc/kpasswd-helper.h @@ -0,0 +1,48 @@ +/* + Unix SMB/CIFS implementation. + + Samba kpasswd implementation + + Copyright (c) 2016 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _KPASSWD_HELPER_H +#define _KPASSWD_HELPER_H + +bool kpasswd_make_error_reply(TALLOC_CTX *mem_ctx, + krb5_error_code error_code, + const char *error_string, + DATA_BLOB *error_data); + +bool kpasswd_make_pwchange_reply(TALLOC_CTX *mem_ctx, + NTSTATUS status, + enum samPwdChangeReason reject_reason, + struct samr_DomInfo1 *dominfo, + DATA_BLOB *error_blob); + +NTSTATUS kpasswd_samdb_set_password(TALLOC_CTX *mem_ctx, + struct tevent_context *event_ctx, + struct loadparm_context *lp_ctx, + struct auth_session_info *session_info, + bool is_service_principal, + const char *target_principal_name, + DATA_BLOB *password, + enum samPwdChangeReason *reject_reason, + struct samr_DomInfo1 **dominfo); + +krb5_error_code kpasswd_check_non_tgt(struct auth_session_info *session_info, + const char **error_string); +#endif /* _KPASSWD_HELPER_H */ diff --git a/source4/kdc/kpasswd-service-heimdal.c b/source4/kdc/kpasswd-service-heimdal.c new file mode 100644 index 0000000..c92b13d --- /dev/null +++ b/source4/kdc/kpasswd-service-heimdal.c @@ -0,0 +1,323 @@ +/* + Unix SMB/CIFS implementation. + + Samba kpasswd implementation + + Copyright (c) 2016 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "samba/service_task.h" +#include "param/param.h" +#include "auth/auth.h" +#include "auth/gensec/gensec.h" +#include "gensec_krb5_helpers.h" +#include "kdc/kdc-server.h" +#include "kdc/kpasswd_glue.h" +#include "kdc/kpasswd-service.h" +#include "kdc/kpasswd-helper.h" + +static krb5_error_code kpasswd_change_password(struct kdc_server *kdc, + TALLOC_CTX *mem_ctx, + const struct gensec_security *gensec_security, + struct auth_session_info *session_info, + DATA_BLOB *password, + DATA_BLOB *kpasswd_reply, + const char **error_string) +{ + NTSTATUS status; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + enum samPwdChangeReason reject_reason; + const char *reject_string = NULL; + struct samr_DomInfo1 *dominfo; + bool ok; + int ret; + + /* + * We're doing a password change (rather than a password set), so check + * that we were given an initial ticket. + */ + ret = gensec_krb5_initial_ticket(gensec_security); + if (ret != 1) { + *error_string = "Expected an initial ticket"; + return KRB5_KPASSWD_INITIAL_FLAG_NEEDED; + } + + status = samdb_kpasswd_change_password(mem_ctx, + kdc->task->lp_ctx, + kdc->task->event_ctx, + session_info, + password, + &reject_reason, + &dominfo, + &reject_string, + &result); + if (!NT_STATUS_IS_OK(status)) { + ok = kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_ACCESSDENIED, + reject_string, + kpasswd_reply); + if (!ok) { + *error_string = "Failed to create reply"; + return KRB5_KPASSWD_HARDERROR; + } + /* We want to send an an authenticated packet. */ + return 0; + } + + ok = kpasswd_make_pwchange_reply(mem_ctx, + result, + reject_reason, + dominfo, + kpasswd_reply); + if (!ok) { + *error_string = "Failed to create reply"; + return KRB5_KPASSWD_HARDERROR; + } + + return 0; +} + +static krb5_error_code kpasswd_set_password(struct kdc_server *kdc, + TALLOC_CTX *mem_ctx, + const struct gensec_security *gensec_security, + struct auth_session_info *session_info, + DATA_BLOB *decoded_data, + DATA_BLOB *kpasswd_reply, + const char **error_string) +{ + krb5_context context = kdc->smb_krb5_context->krb5_context; + krb5_error_code code; + krb5_principal target_principal; + ChangePasswdDataMS chpw = {}; + size_t chpw_len = 0; + DATA_BLOB password = data_blob_null; + enum samPwdChangeReason reject_reason = SAM_PWD_CHANGE_NO_ERROR; + struct samr_DomInfo1 *dominfo = NULL; + char *target_principal_string = NULL; + bool is_service_principal = false; + NTSTATUS status; + bool ok; + + code = decode_ChangePasswdDataMS(decoded_data->data, + decoded_data->length, + &chpw, + &chpw_len); + if (code != 0) { + DBG_WARNING("decode_ChangePasswdDataMS failed\n"); + ok = kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_MALFORMED, + "Failed to decode packet", + kpasswd_reply); + if (!ok) { + *error_string = "Failed to create reply"; + return KRB5_KPASSWD_HARDERROR; + } + return 0; + } + + ok = convert_string_talloc_handle(mem_ctx, + lpcfg_iconv_handle(kdc->task->lp_ctx), + CH_UTF8, + CH_UTF16, + (const char *)chpw.newpasswd.data, + chpw.newpasswd.length, + (void **)&password.data, + &password.length); + if (!ok) { + free_ChangePasswdDataMS(&chpw); + DBG_WARNING("String conversion failed\n"); + *error_string = "String conversion failed"; + return KRB5_KPASSWD_HARDERROR; + } + + if ((chpw.targname != NULL && chpw.targrealm == NULL) || + (chpw.targname == NULL && chpw.targrealm != NULL)) { + free_ChangePasswdDataMS(&chpw); + ok = kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_MALFORMED, + "Realm and principal must be " + "both present, or neither present", + kpasswd_reply); + if (!ok) { + *error_string = "Failed to create reply"; + return KRB5_KPASSWD_HARDERROR; + } + return 0; + } + + if (chpw.targname == NULL || chpw.targrealm == NULL) { + free_ChangePasswdDataMS(&chpw); + return kpasswd_change_password(kdc, + mem_ctx, + gensec_security, + session_info, + &password, + kpasswd_reply, + error_string); + } + code = krb5_build_principal_ext(context, + &target_principal, + strlen(*chpw.targrealm), + *chpw.targrealm, + 0); + if (code != 0) { + free_ChangePasswdDataMS(&chpw); + return kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_MALFORMED, + "Failed to parse principal", + kpasswd_reply); + } + code = copy_PrincipalName(chpw.targname, + &target_principal->name); + free_ChangePasswdDataMS(&chpw); + if (code != 0) { + krb5_free_principal(context, target_principal); + return kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_MALFORMED, + "Failed to parse principal", + kpasswd_reply); + } + + if (target_principal->name.name_string.len >= 2) { + is_service_principal = true; + + code = krb5_unparse_name_short(context, + target_principal, + &target_principal_string); + } else { + code = krb5_unparse_name(context, + target_principal, + &target_principal_string); + } + krb5_free_principal(context, target_principal); + if (code != 0) { + ok = kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_MALFORMED, + "Failed to parse principal", + kpasswd_reply); + if (!ok) { + *error_string = "Failed to create reply"; + return KRB5_KPASSWD_HARDERROR; + } + } + + status = kpasswd_samdb_set_password(mem_ctx, + kdc->task->event_ctx, + kdc->task->lp_ctx, + session_info, + is_service_principal, + target_principal_string, + &password, + &reject_reason, + &dominfo); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("kpasswd_samdb_set_password failed - %s\n", + nt_errstr(status)); + } + + ok = kpasswd_make_pwchange_reply(mem_ctx, + status, + reject_reason, + dominfo, + kpasswd_reply); + if (!ok) { + *error_string = "Failed to create reply"; + return KRB5_KPASSWD_HARDERROR; + } + + return 0; +} + +krb5_error_code kpasswd_handle_request(struct kdc_server *kdc, + TALLOC_CTX *mem_ctx, + struct gensec_security *gensec_security, + uint16_t verno, + DATA_BLOB *decoded_data, + DATA_BLOB *kpasswd_reply, + const char **error_string) +{ + struct auth_session_info *session_info; + NTSTATUS status; + krb5_error_code code; + + status = gensec_session_info(gensec_security, + mem_ctx, + &session_info); + if (!NT_STATUS_IS_OK(status)) { + *error_string = talloc_asprintf(mem_ctx, + "gensec_session_info failed - %s", + nt_errstr(status)); + return KRB5_KPASSWD_HARDERROR; + } + + /* + * Since the kpasswd service shares its keys with the krbtgt, we might + * have received a TGT rather than a kpasswd ticket. We need to check + * the ticket type to ensure that TGTs cannot be misused in this manner. + */ + code = kpasswd_check_non_tgt(session_info, + error_string); + if (code != 0) { + DBG_WARNING("%s\n", *error_string); + return code; + } + + switch(verno) { + case KRB5_KPASSWD_VERS_CHANGEPW: { + DATA_BLOB password = data_blob_null; + bool ok; + + ok = convert_string_talloc_handle(mem_ctx, + lpcfg_iconv_handle(kdc->task->lp_ctx), + CH_UTF8, + CH_UTF16, + (const char *)decoded_data->data, + decoded_data->length, + (void **)&password.data, + &password.length); + if (!ok) { + *error_string = "String conversion failed!"; + DBG_WARNING("%s\n", *error_string); + return KRB5_KPASSWD_HARDERROR; + } + + return kpasswd_change_password(kdc, + mem_ctx, + gensec_security, + session_info, + &password, + kpasswd_reply, + error_string); + } + case KRB5_KPASSWD_VERS_SETPW: { + return kpasswd_set_password(kdc, + mem_ctx, + gensec_security, + session_info, + decoded_data, + kpasswd_reply, + error_string); + } + default: + *error_string = talloc_asprintf(mem_ctx, + "Protocol version %u not supported", + verno); + return KRB5_KPASSWD_BAD_VERSION; + } + + return 0; +} diff --git a/source4/kdc/kpasswd-service-mit.c b/source4/kdc/kpasswd-service-mit.c new file mode 100644 index 0000000..053b1f2 --- /dev/null +++ b/source4/kdc/kpasswd-service-mit.c @@ -0,0 +1,400 @@ +/* + Unix SMB/CIFS implementation. + + Samba kpasswd implementation + + Copyright (c) 2016 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "samba/service_task.h" +#include "param/param.h" +#include "auth/auth.h" +#include "auth/gensec/gensec.h" +#include "gensec_krb5_helpers.h" +#include "kdc/kdc-server.h" +#include "kdc/kpasswd_glue.h" +#include "kdc/kpasswd-service.h" +#include "kdc/kpasswd-helper.h" +#include "../lib/util/asn1.h" + +#define RFC3244_VERSION 0xff80 + +krb5_error_code decode_krb5_setpw_req(const krb5_data *code, + krb5_data **password_out, + krb5_principal *target_out); + +/* + * A fallback for when MIT refuses to parse a setpw structure without the + * (optional) target principal and realm + */ +static bool decode_krb5_setpw_req_simple(TALLOC_CTX *mem_ctx, + const DATA_BLOB *decoded_data, + DATA_BLOB *clear_data) +{ + struct asn1_data *asn1 = NULL; + bool ret; + + asn1 = asn1_init(mem_ctx, 3); + if (asn1 == NULL) { + return false; + } + + ret = asn1_load(asn1, *decoded_data); + if (!ret) { + goto out; + } + + ret = asn1_start_tag(asn1, ASN1_SEQUENCE(0)); + if (!ret) { + goto out; + } + ret = asn1_start_tag(asn1, ASN1_CONTEXT(0)); + if (!ret) { + goto out; + } + ret = asn1_read_OctetString(asn1, mem_ctx, clear_data); + if (!ret) { + goto out; + } + + ret = asn1_end_tag(asn1); + if (!ret) { + goto out; + } + ret = asn1_end_tag(asn1); + +out: + asn1_free(asn1); + + return ret; +} + +static krb5_error_code kpasswd_change_password(struct kdc_server *kdc, + TALLOC_CTX *mem_ctx, + const struct gensec_security *gensec_security, + struct auth_session_info *session_info, + DATA_BLOB *password, + DATA_BLOB *kpasswd_reply, + const char **error_string) +{ + NTSTATUS status; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + enum samPwdChangeReason reject_reason; + const char *reject_string = NULL; + struct samr_DomInfo1 *dominfo; + bool ok; + int ret; + + /* + * We're doing a password change (rather than a password set), so check + * that we were given an initial ticket. + */ + ret = gensec_krb5_initial_ticket(gensec_security); + if (ret != 1) { + *error_string = "Expected an initial ticket"; + return KRB5_KPASSWD_INITIAL_FLAG_NEEDED; + } + + status = samdb_kpasswd_change_password(mem_ctx, + kdc->task->lp_ctx, + kdc->task->event_ctx, + session_info, + password, + &reject_reason, + &dominfo, + &reject_string, + &result); + if (!NT_STATUS_IS_OK(status)) { + ok = kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_ACCESSDENIED, + reject_string, + kpasswd_reply); + if (!ok) { + *error_string = "Failed to create reply"; + return KRB5_KPASSWD_HARDERROR; + } + /* We want to send an an authenticated packet. */ + return 0; + } + + ok = kpasswd_make_pwchange_reply(mem_ctx, + result, + reject_reason, + dominfo, + kpasswd_reply); + if (!ok) { + *error_string = "Failed to create reply"; + return KRB5_KPASSWD_HARDERROR; + } + + return 0; +} + +static krb5_error_code kpasswd_set_password(struct kdc_server *kdc, + TALLOC_CTX *mem_ctx, + const struct gensec_security *gensec_security, + struct auth_session_info *session_info, + DATA_BLOB *decoded_data, + DATA_BLOB *kpasswd_reply, + const char **error_string) +{ + krb5_context context = kdc->smb_krb5_context->krb5_context; + DATA_BLOB clear_data; + krb5_data k_dec_data; + krb5_data *k_clear_data = NULL; + krb5_principal target_principal = NULL; + krb5_error_code code; + DATA_BLOB password; + char *target_realm = NULL; + char *target_name = NULL; + char *target_principal_string = NULL; + bool is_service_principal = false; + bool ok; + size_t num_components; + enum samPwdChangeReason reject_reason = SAM_PWD_CHANGE_NO_ERROR; + struct samr_DomInfo1 *dominfo = NULL; + NTSTATUS status; + + k_dec_data.length = decoded_data->length; + k_dec_data.data = (char *)decoded_data->data; + + code = decode_krb5_setpw_req(&k_dec_data, + &k_clear_data, + &target_principal); + if (code == 0) { + clear_data.data = (uint8_t *)k_clear_data->data; + clear_data.length = k_clear_data->length; + } else { + target_principal = NULL; + + /* + * The MIT decode failed, so fall back to trying the simple + * case, without target_principal. + */ + ok = decode_krb5_setpw_req_simple(mem_ctx, + decoded_data, + &clear_data); + if (!ok) { + DBG_WARNING("decode_krb5_setpw_req failed: %s\n", + error_message(code)); + ok = kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_MALFORMED, + "Failed to decode packet", + kpasswd_reply); + if (!ok) { + *error_string = "Failed to create reply"; + return KRB5_KPASSWD_HARDERROR; + } + return 0; + } + } + + ok = convert_string_talloc_handle(mem_ctx, + lpcfg_iconv_handle(kdc->task->lp_ctx), + CH_UTF8, + CH_UTF16, + clear_data.data, + clear_data.length, + (void **)&password.data, + &password.length); + if (k_clear_data != NULL) { + krb5_free_data(context, k_clear_data); + } + if (!ok) { + DBG_WARNING("String conversion failed\n"); + *error_string = "String conversion failed"; + return KRB5_KPASSWD_HARDERROR; + } + + if (target_principal != NULL) { + target_realm = smb_krb5_principal_get_realm( + mem_ctx, context, target_principal); + code = krb5_unparse_name_flags(context, + target_principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, + &target_name); + if (code != 0) { + DBG_WARNING("Failed to parse principal\n"); + *error_string = "String conversion failed"; + return KRB5_KPASSWD_HARDERROR; + } + } + + if ((target_name != NULL && target_realm == NULL) || + (target_name == NULL && target_realm != NULL)) { + krb5_free_principal(context, target_principal); + TALLOC_FREE(target_realm); + SAFE_FREE(target_name); + + ok = kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_MALFORMED, + "Realm and principal must be " + "both present, or neither " + "present", + kpasswd_reply); + if (!ok) { + *error_string = "Failed to create reply"; + return KRB5_KPASSWD_HARDERROR; + } + return 0; + } + + if (target_name != NULL && target_realm != NULL) { + TALLOC_FREE(target_realm); + SAFE_FREE(target_name); + } else { + krb5_free_principal(context, target_principal); + TALLOC_FREE(target_realm); + SAFE_FREE(target_name); + + return kpasswd_change_password(kdc, + mem_ctx, + gensec_security, + session_info, + &password, + kpasswd_reply, + error_string); + } + + num_components = krb5_princ_size(context, target_principal); + if (num_components >= 2) { + is_service_principal = true; + code = krb5_unparse_name_flags(context, + target_principal, + KRB5_PRINCIPAL_UNPARSE_SHORT, + &target_principal_string); + } else { + code = krb5_unparse_name(context, + target_principal, + &target_principal_string); + } + krb5_free_principal(context, target_principal); + if (code != 0) { + ok = kpasswd_make_error_reply(mem_ctx, + KRB5_KPASSWD_MALFORMED, + "Failed to parse principal", + kpasswd_reply); + if (!ok) { + *error_string = "Failed to create reply"; + return KRB5_KPASSWD_HARDERROR; + } + } + + status = kpasswd_samdb_set_password(mem_ctx, + kdc->task->event_ctx, + kdc->task->lp_ctx, + session_info, + is_service_principal, + target_principal_string, + &password, + &reject_reason, + &dominfo); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("kpasswd_samdb_set_password failed - %s\n", + nt_errstr(status)); + } + + ok = kpasswd_make_pwchange_reply(mem_ctx, + status, + reject_reason, + dominfo, + kpasswd_reply); + if (!ok) { + *error_string = "Failed to create reply"; + return KRB5_KPASSWD_HARDERROR; + } + + return 0; +} + +krb5_error_code kpasswd_handle_request(struct kdc_server *kdc, + TALLOC_CTX *mem_ctx, + struct gensec_security *gensec_security, + uint16_t verno, + DATA_BLOB *decoded_data, + DATA_BLOB *kpasswd_reply, + const char **error_string) +{ + struct auth_session_info *session_info; + NTSTATUS status; + krb5_error_code code; + + status = gensec_session_info(gensec_security, + mem_ctx, + &session_info); + if (!NT_STATUS_IS_OK(status)) { + *error_string = talloc_asprintf(mem_ctx, + "gensec_session_info failed - " + "%s", + nt_errstr(status)); + return KRB5_KPASSWD_HARDERROR; + } + + /* + * Since the kpasswd service shares its keys with the krbtgt, we might + * have received a TGT rather than a kpasswd ticket. We need to check + * the ticket type to ensure that TGTs cannot be misused in this manner. + */ + code = kpasswd_check_non_tgt(session_info, + error_string); + if (code != 0) { + DBG_WARNING("%s\n", *error_string); + return code; + } + + switch(verno) { + case 1: { + DATA_BLOB password; + bool ok; + + ok = convert_string_talloc_handle(mem_ctx, + lpcfg_iconv_handle(kdc->task->lp_ctx), + CH_UTF8, + CH_UTF16, + (const char *)decoded_data->data, + decoded_data->length, + (void **)&password.data, + &password.length); + if (!ok) { + *error_string = "String conversion failed!"; + DBG_WARNING("%s\n", *error_string); + return KRB5_KPASSWD_HARDERROR; + } + + return kpasswd_change_password(kdc, + mem_ctx, + gensec_security, + session_info, + &password, + kpasswd_reply, + error_string); + } + case RFC3244_VERSION: { + return kpasswd_set_password(kdc, + mem_ctx, + gensec_security, + session_info, + decoded_data, + kpasswd_reply, + error_string); + } + default: + return KRB5_KPASSWD_BAD_VERSION; + } + + return 0; +} diff --git a/source4/kdc/kpasswd-service.c b/source4/kdc/kpasswd-service.c new file mode 100644 index 0000000..d2f1bb0 --- /dev/null +++ b/source4/kdc/kpasswd-service.c @@ -0,0 +1,391 @@ +/* + Unix SMB/CIFS implementation. + + Samba kpasswd implementation + + Copyright (c) 2005 Andrew Bartlett <abartlet@samba.org> + Copyright (c) 2016 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "samba/service_task.h" +#include "tsocket/tsocket.h" +#include "auth/credentials/credentials.h" +#include "auth/auth.h" +#include "auth/gensec/gensec.h" +#include "kdc/kdc-server.h" +#include "kdc/kpasswd-service.h" +#include "kdc/kpasswd-helper.h" +#include "param/param.h" + +#define HEADER_LEN 6 +#ifndef RFC3244_VERSION +#define RFC3244_VERSION 0xff80 +#endif + +kdc_code kpasswd_process(struct kdc_server *kdc, + TALLOC_CTX *mem_ctx, + DATA_BLOB *request, + DATA_BLOB *reply, + struct tsocket_address *remote_addr, + struct tsocket_address *local_addr, + int datagram) +{ + uint16_t len; + uint16_t verno; + uint16_t ap_req_len; + uint16_t enc_data_len; + DATA_BLOB ap_req_blob = data_blob_null; + DATA_BLOB ap_rep_blob = data_blob_null; + DATA_BLOB enc_data_blob = data_blob_null; + DATA_BLOB dec_data_blob = data_blob_null; + DATA_BLOB kpasswd_dec_reply = data_blob_null; + const char *error_string = NULL; + krb5_error_code error_code = 0; + struct cli_credentials *server_credentials; + struct gensec_security *gensec_security; +#ifndef SAMBA4_USES_HEIMDAL + struct sockaddr_storage remote_ss; +#endif + struct sockaddr_storage local_ss; + ssize_t socklen; + TALLOC_CTX *tmp_ctx; + kdc_code rc = KDC_ERROR; + krb5_error_code code = 0; + NTSTATUS status; + int rv; + bool is_inet; + bool ok; + + if (kdc->am_rodc) { + return KDC_PROXY_REQUEST; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return KDC_ERROR; + } + + is_inet = tsocket_address_is_inet(remote_addr, "ip"); + if (!is_inet) { + DBG_WARNING("Invalid remote IP address"); + goto done; + } + +#ifndef SAMBA4_USES_HEIMDAL + /* + * FIXME: Heimdal fails to to do a krb5_rd_req() in gensec_krb5 if we + * set the remote address. + */ + + /* remote_addr */ + socklen = tsocket_address_bsd_sockaddr(remote_addr, + (struct sockaddr *)&remote_ss, + sizeof(struct sockaddr_storage)); + if (socklen < 0) { + DBG_WARNING("Invalid remote IP address"); + goto done; + } +#endif + + /* local_addr */ + socklen = tsocket_address_bsd_sockaddr(local_addr, + (struct sockaddr *)&local_ss, + sizeof(struct sockaddr_storage)); + if (socklen < 0) { + DBG_WARNING("Invalid local IP address"); + goto done; + } + + if (request->length <= HEADER_LEN) { + DBG_WARNING("Request truncated\n"); + goto done; + } + + len = RSVAL(request->data, 0); + if (request->length != len) { + DBG_WARNING("Request length does not match\n"); + goto done; + } + + verno = RSVAL(request->data, 2); + if (verno != 1 && verno != RFC3244_VERSION) { + DBG_WARNING("Unsupported version: 0x%04x\n", verno); + } + + ap_req_len = RSVAL(request->data, 4); + if ((ap_req_len >= len) || ((ap_req_len + HEADER_LEN) >= len)) { + DBG_WARNING("AP_REQ truncated\n"); + goto done; + } + + ap_req_blob = data_blob_const(&request->data[HEADER_LEN], ap_req_len); + + enc_data_len = len - ap_req_len; + enc_data_blob = data_blob_const(&request->data[HEADER_LEN + ap_req_len], + enc_data_len); + + server_credentials = cli_credentials_init(tmp_ctx); + if (server_credentials == NULL) { + DBG_ERR("Failed to initialize server credentials!\n"); + goto done; + } + + /* + * We want the credentials subsystem to use the krb5 context we already + * have, rather than a new context. + * + * On this context the KDB plugin has been loaded, so we can access + * dsdb. + */ + status = cli_credentials_set_krb5_context(server_credentials, + kdc->smb_krb5_context); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + ok = cli_credentials_set_conf(server_credentials, kdc->task->lp_ctx); + if (!ok) { + goto done; + } + + /* + * After calling cli_credentials_set_conf(), explicitly set the realm + * with CRED_SPECIFIED. We need to do this so the result of + * principal_from_credentials() called from the gensec layer is + * CRED_SPECIFIED rather than CRED_SMB_CONF, avoiding a fallback to + * match-by-key (very undesirable in this case). + */ + ok = cli_credentials_set_realm(server_credentials, + lpcfg_realm(kdc->task->lp_ctx), + CRED_SPECIFIED); + if (!ok) { + goto done; + } + + ok = cli_credentials_set_username(server_credentials, + "kadmin/changepw", + CRED_SPECIFIED); + if (!ok) { + goto done; + } + + /* Check that the server principal is indeed CRED_SPECIFIED. */ + { + char *principal = NULL; + enum credentials_obtained obtained; + + principal = cli_credentials_get_principal_and_obtained(server_credentials, + tmp_ctx, + &obtained); + if (obtained < CRED_SPECIFIED) { + goto done; + } + + TALLOC_FREE(principal); + } + + rv = cli_credentials_set_keytab_name(server_credentials, + kdc->task->lp_ctx, + kdc->kpasswd_keytab_name, + CRED_SPECIFIED); + if (rv != 0) { + DBG_ERR("Failed to set credentials keytab name\n"); + goto done; + } + + status = samba_server_gensec_start(tmp_ctx, + kdc->task->event_ctx, + kdc->task->msg_ctx, + kdc->task->lp_ctx, + server_credentials, + "kpasswd", + &gensec_security); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = gensec_set_local_address(gensec_security, local_addr); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + +#ifndef SAMBA4_USES_HEIMDAL + status = gensec_set_remote_address(gensec_security, remote_addr); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } +#endif + + /* We want the GENSEC wrap calls to generate PRIV tokens */ + gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL); + + /* Use the krb5 gesec mechanism so we can load DB modules */ + status = gensec_start_mech_by_name(gensec_security, "krb5"); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + /* + * Accept the AP-REQ and generate the AP-REP we need for the reply + * + * We only allow KRB5 and make sure the backend to is RPC/IPC free. + * + * See gensec_krb5_update_internal() as GENSEC_SERVER. + * + * It allows gensec_update() not to block. + * + * If that changes in future we need to use + * gensec_update_send/recv here! + */ + status = gensec_update(gensec_security, tmp_ctx, + ap_req_blob, &ap_rep_blob); + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + ap_rep_blob = data_blob_null; + error_code = KRB5_KPASSWD_HARDERROR; + error_string = talloc_asprintf(tmp_ctx, + "gensec_update failed - %s\n", + nt_errstr(status)); + DBG_ERR("%s", error_string); + goto reply; + } + + status = gensec_unwrap(gensec_security, + tmp_ctx, + &enc_data_blob, + &dec_data_blob); + if (!NT_STATUS_IS_OK(status)) { + ap_rep_blob = data_blob_null; + error_code = KRB5_KPASSWD_HARDERROR; + error_string = talloc_asprintf(tmp_ctx, + "gensec_unwrap failed - %s\n", + nt_errstr(status)); + DBG_ERR("%s", error_string); + goto reply; + } + + code = kpasswd_handle_request(kdc, + tmp_ctx, + gensec_security, + verno, + &dec_data_blob, + &kpasswd_dec_reply, + &error_string); + if (code != 0) { + ap_rep_blob = data_blob_null; + error_code = code; + goto reply; + } + + status = gensec_wrap(gensec_security, + tmp_ctx, + &kpasswd_dec_reply, + &enc_data_blob); + if (!NT_STATUS_IS_OK(status)) { + ap_rep_blob = data_blob_null; + error_code = KRB5_KPASSWD_HARDERROR; + error_string = talloc_asprintf(tmp_ctx, + "gensec_wrap failed - %s\n", + nt_errstr(status)); + DBG_ERR("%s", error_string); + goto reply; + } + +reply: + if (error_code != 0) { + krb5_data k_enc_data; + krb5_data k_dec_data; + const char *principal_string; + krb5_principal server_principal; + + if (error_string == NULL) { + DBG_ERR("Invalid error string! This should not happen\n"); + goto done; + } + + ok = kpasswd_make_error_reply(tmp_ctx, + error_code, + error_string, + &dec_data_blob); + if (!ok) { + DBG_ERR("Failed to create error reply\n"); + goto done; + } + + k_dec_data.length = dec_data_blob.length; + k_dec_data.data = (char *)dec_data_blob.data; + + principal_string = cli_credentials_get_principal(server_credentials, + tmp_ctx); + if (principal_string == NULL) { + goto done; + } + + code = smb_krb5_parse_name(kdc->smb_krb5_context->krb5_context, + principal_string, + &server_principal); + if (code != 0) { + DBG_ERR("Failed to create principal: %s\n", + error_message(code)); + goto done; + } + + code = smb_krb5_mk_error(kdc->smb_krb5_context->krb5_context, + KRB5KDC_ERR_NONE + error_code, + NULL, /* e_text */ + &k_dec_data, + NULL, /* client */ + server_principal, + &k_enc_data); + krb5_free_principal(kdc->smb_krb5_context->krb5_context, + server_principal); + if (code != 0) { + DBG_ERR("Failed to create krb5 error reply: %s\n", + error_message(code)); + goto done; + } + + enc_data_blob = data_blob_talloc(tmp_ctx, + k_enc_data.data, + k_enc_data.length); + if (enc_data_blob.data == NULL) { + DBG_ERR("Failed to allocate memory for error reply\n"); + goto done; + } + } + + *reply = data_blob_talloc(mem_ctx, + NULL, + HEADER_LEN + ap_rep_blob.length + enc_data_blob.length); + if (reply->data == NULL) { + goto done; + } + RSSVAL(reply->data, 0, reply->length); + RSSVAL(reply->data, 2, 1); + RSSVAL(reply->data, 4, ap_rep_blob.length); + memcpy(reply->data + HEADER_LEN, + ap_rep_blob.data, + ap_rep_blob.length); + memcpy(reply->data + HEADER_LEN + ap_rep_blob.length, + enc_data_blob.data, + enc_data_blob.length); + + rc = KDC_OK; +done: + talloc_free(tmp_ctx); + return rc; +} diff --git a/source4/kdc/kpasswd-service.h b/source4/kdc/kpasswd-service.h new file mode 100644 index 0000000..845d680 --- /dev/null +++ b/source4/kdc/kpasswd-service.h @@ -0,0 +1,43 @@ +/* + Unix SMB/CIFS implementation. + + Samba kpasswd implementation + + Copyright (c) 2016 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _KPASSWD_SERVICE_H +#define _KPASSWD_SERVICE_H + +struct gensec_security; + +krb5_error_code kpasswd_handle_request(struct kdc_server *kdc, + TALLOC_CTX *mem_ctx, + struct gensec_security *gensec_security, + uint16_t verno, + DATA_BLOB *decoded_data, + DATA_BLOB *kpasswd_reply, + const char **error_string); + +kdc_code kpasswd_process(struct kdc_server *kdc, + TALLOC_CTX *mem_ctx, + DATA_BLOB *request, + DATA_BLOB *reply, + struct tsocket_address *remote_addr, + struct tsocket_address *local_addr, + int datagram); + +#endif /* _KPASSWD_SERVICE_H */ diff --git a/source4/kdc/kpasswd_glue.c b/source4/kdc/kpasswd_glue.c new file mode 100644 index 0000000..68f347c --- /dev/null +++ b/source4/kdc/kpasswd_glue.c @@ -0,0 +1,85 @@ +/* + Unix SMB/CIFS implementation. + + kpasswd Server implementation + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Andrew Tridgell 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "dsdb/samdb/samdb.h" +#include "../lib/util/util_ldb.h" +#include "libcli/security/security.h" +#include "dsdb/common/util.h" +#include "auth/auth.h" +#include "kdc/kpasswd_glue.h" + +/* + A user password change + + Return true if there is a valid error packet (or success) formed in + the error_blob +*/ +NTSTATUS samdb_kpasswd_change_password(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct tevent_context *event_ctx, + struct auth_session_info *session_info, + const DATA_BLOB *password, + enum samPwdChangeReason *reject_reason, + struct samr_DomInfo1 **dominfo, + const char **error_string, + NTSTATUS *result) +{ + NTSTATUS status; + struct ldb_context *samdb = NULL; + + /* Start a SAM with user privileges for the password change */ + samdb = samdb_connect(mem_ctx, + event_ctx, + lp_ctx, + session_info, + NULL, + 0); + if (!samdb) { + *error_string = "Failed to open samdb"; + return NT_STATUS_ACCESS_DENIED; + } + + DEBUG(3, ("Changing password of %s\\%s (%s)\n", + session_info->info->domain_name, + session_info->info->account_name, + dom_sid_string(mem_ctx, &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]))); + + /* Performs the password change */ + status = samdb_set_password_sid(samdb, + mem_ctx, + &session_info->security_token->sids[PRIMARY_USER_SID_INDEX], + NULL, + password, + NULL, + DSDB_PASSWORD_CHECKED_AND_CORRECT, + reject_reason, + dominfo); + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { + *error_string = "No such user when changing password"; + } else if (!NT_STATUS_IS_OK(status)) { + *error_string = nt_errstr(status); + } + *result = status; + + return NT_STATUS_OK; +} diff --git a/source4/kdc/kpasswd_glue.h b/source4/kdc/kpasswd_glue.h new file mode 100644 index 0000000..49246af --- /dev/null +++ b/source4/kdc/kpasswd_glue.h @@ -0,0 +1,31 @@ +/* + Unix SMB/CIFS implementation. + + kpasswd Server implementation + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Andrew Tridgell 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +NTSTATUS samdb_kpasswd_change_password(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct tevent_context *event_ctx, + struct auth_session_info *session_info, + const DATA_BLOB *password, + enum samPwdChangeReason *reject_reason, + struct samr_DomInfo1 **dominfo, + const char **error_string, + NTSTATUS *result); diff --git a/source4/kdc/ktutil.c b/source4/kdc/ktutil.c new file mode 100644 index 0000000..732d247 --- /dev/null +++ b/source4/kdc/ktutil.c @@ -0,0 +1,122 @@ +/* + Unix SMB/CIFS implementation. + + Minimal ktutil for selftest + + Copyright (C) Ralph Boehme <slow@samba.org> 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "krb5_wrap/krb5_samba.h" + +static void smb_krb5_err(TALLOC_CTX *mem_ctx, + krb5_context context, + int exit_code, + krb5_error_code code, + const char *msg) +{ + char *krb5_err_str = smb_get_krb5_error_message(context, + code, + mem_ctx); + printf("%s: %s\n", msg, krb5_err_str ? krb5_err_str : "UNKOWN"); + + talloc_free(mem_ctx); + exit(exit_code); +} + +int main (int argc, char **argv) +{ + TALLOC_CTX *mem_ctx = talloc_init("ktutil"); + krb5_context context; + krb5_keytab keytab; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + krb5_error_code ret; + char *keytab_name = NULL; + + if (mem_ctx == NULL) { + printf("talloc_init() failed\n"); + exit(1); + } + + if (argc != 2) { + printf("Usage: %s KEYTAB\n", argv[0]); + exit(1); + } + + keytab_name = argv[1]; + + ret = smb_krb5_init_context_common(&context); + if (ret) { + DBG_ERR("kerberos init context failed (%s)\n", + error_message(ret)); + smb_krb5_err(mem_ctx, context, 1, ret, "krb5_context"); + } + + ret = smb_krb5_kt_open_relative(context, keytab_name, false, &keytab); + if (ret) { + smb_krb5_err(mem_ctx, context, 1, ret, "open keytab"); + } + + ret = krb5_kt_start_seq_get(context, keytab, &cursor); + if (ret) { + smb_krb5_err(mem_ctx, context, 1, ret, "krb5_kt_start_seq_get"); + } + + for (ret = krb5_kt_next_entry(context, keytab, &entry, &cursor); + ret == 0; + ret = krb5_kt_next_entry(context, keytab, &entry, &cursor)) + { + char *principal = NULL; + char *enctype_str = NULL; + krb5_enctype enctype = smb_krb5_kt_get_enctype_from_entry(&entry); + + ret = smb_krb5_unparse_name(mem_ctx, + context, + entry.principal, + &principal); + if (ret) { + smb_krb5_err(mem_ctx, context, 1, ret, "krb5_enctype_to_string"); + } + + ret = smb_krb5_enctype_to_string(context, + enctype, + &enctype_str); + if (ret) { + printf("%s (%d)\n", principal, (int)enctype); + } else { + printf("%s (%s)\n", principal, enctype_str); + } + + TALLOC_FREE(principal); + SAFE_FREE(enctype_str); + smb_krb5_kt_free_entry(context, &entry); + } + + ret = krb5_kt_end_seq_get(context, keytab, &cursor); + if (ret) { + smb_krb5_err(mem_ctx, context, 1, ret, "krb5_kt_end_seq_get"); + } + + ret = krb5_kt_close(context, keytab); + if (ret) { + smb_krb5_err(mem_ctx, context, 1, ret, "krb5_kt_close"); + } + + krb5_free_context(context); + talloc_free(mem_ctx); + return 0; +} diff --git a/source4/kdc/mit-kdb/kdb_samba.c b/source4/kdc/mit-kdb/kdb_samba.c new file mode 100644 index 0000000..0ff1bfe --- /dev/null +++ b/source4/kdc/mit-kdb/kdb_samba.c @@ -0,0 +1,182 @@ +/* + Unix SMB/CIFS implementation. + + Samba KDB plugin for MIT Kerberos + + Copyright (c) 2010 Simo Sorce <idra@samba.org>. + Copyright (c) 2014 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +#include "system/kerberos.h" + +#include <profile.h> +#include <kdb.h> + +#include "kdc/samba_kdc.h" +#include "kdc/mit_samba.h" +#include "kdb_samba.h" + +static krb5_error_code kdb_samba_init_library(void) +{ + return 0; +} + +static krb5_error_code kdb_samba_fini_library(void) +{ + return 0; +} + +static krb5_error_code kdb_samba_init_module(krb5_context context, + char *conf_section, + char **db_args, + int mode) +{ + /* TODO mit_samba_context_init */ + struct mit_samba_context *mit_ctx; + krb5_error_code code; + int rc; + + rc = mit_samba_context_init(&mit_ctx); + if (rc != 0) { + return ENOMEM; + } + + + code = krb5_db_set_context(context, mit_ctx); + + return code; +} +static krb5_error_code kdb_samba_fini_module(krb5_context context) +{ + struct mit_samba_context *mit_ctx; + + mit_ctx = ks_get_context(context); + if (mit_ctx == NULL) { + return 0; + } + + mit_samba_context_free(mit_ctx); + + return 0; +} + +static krb5_error_code kdb_samba_db_create(krb5_context context, + char *conf_section, + char **db_args) +{ + /* NOTE: used only by kadmin */ + return KRB5_KDB_DBTYPE_NOSUP; +} + +static krb5_error_code kdb_samba_db_destroy(krb5_context context, + char *conf_section, + char **db_args) +{ + /* NOTE: used only by kadmin */ + return KRB5_KDB_DBTYPE_NOSUP; +} + +static krb5_error_code kdb_samba_db_get_age(krb5_context context, + char *db_name, + time_t *age) +{ + /* TODO: returns last modification time of the db */ + + /* NOTE: used by and affects only lookaside cache, + * defer implementation until needed as samba doesn't keep this + * specific value readily available and it would require a full + * database search to get it. */ + + *age = time(NULL); + + return 0; +} + +static krb5_error_code kdb_samba_db_lock(krb5_context context, int kmode) +{ + + /* NOTE: important only for kadmin */ + /* NOTE: deferred as samba's DB cannot be easily locked and doesn't + * really make sense to do so anyway as the db is shared and support + * transactions */ + return 0; +} + +static krb5_error_code kdb_samba_db_unlock(krb5_context context) +{ + + /* NOTE: important only for kadmin */ + /* NOTE: deferred as samba's DB cannot be easily locked and doesn't + * really make sense to do so anyway as the db is shared and support + * transactions */ + return 0; +} + +static void kdb_samba_db_free_principal_e_data(krb5_context context, + krb5_octet *e_data) +{ + struct samba_kdc_entry *skdc_entry; + + skdc_entry = talloc_get_type_abort(e_data, + struct samba_kdc_entry); + skdc_entry->kdc_entry = NULL; + TALLOC_FREE(skdc_entry); +} + +kdb_vftabl kdb_function_table = { + .maj_ver = KRB5_KDB_DAL_MAJOR_VERSION, + .min_ver = KRB5_KDB_DAL_MAJOR_VERSION == 6 ? 1 : 0, + + .init_library = kdb_samba_init_library, + .fini_library = kdb_samba_fini_library, + .init_module = kdb_samba_init_module, + .fini_module = kdb_samba_fini_module, + + .create = kdb_samba_db_create, + .destroy = kdb_samba_db_destroy, + .get_age = kdb_samba_db_get_age, + .lock = kdb_samba_db_lock, + .unlock = kdb_samba_db_unlock, + + .get_principal = kdb_samba_db_get_principal, + .put_principal = kdb_samba_db_put_principal, + .delete_principal = kdb_samba_db_delete_principal, + + .iterate = kdb_samba_db_iterate, + + .fetch_master_key = kdb_samba_fetch_master_key, + .fetch_master_key_list = kdb_samba_fetch_master_key_list, + + .change_pwd = kdb_samba_change_pwd, + + .decrypt_key_data = kdb_samba_dbekd_decrypt_key_data, + .encrypt_key_data = kdb_samba_dbekd_encrypt_key_data, + + .check_policy_as = kdb_samba_db_check_policy_as, + .audit_as_req = kdb_samba_db_audit_as_req, + .check_allowed_to_delegate = kdb_samba_db_check_allowed_to_delegate, + + .free_principal_e_data = kdb_samba_db_free_principal_e_data, + +#if KRB5_KDB_DAL_MAJOR_VERSION >= 9 + .allowed_to_delegate_from = kdb_samba_db_allowed_to_delegate_from, + .issue_pac = kdb_samba_db_issue_pac, +#else + .sign_authdata = kdb_samba_db_sign_auth_data, +#endif +}; diff --git a/source4/kdc/mit-kdb/kdb_samba.h b/source4/kdc/mit-kdb/kdb_samba.h new file mode 100644 index 0000000..138105e --- /dev/null +++ b/source4/kdc/mit-kdb/kdb_samba.h @@ -0,0 +1,184 @@ +/* + Unix SMB/CIFS implementation. + + Samba KDB plugin for MIT Kerberos + + Copyright (c) 2009 Simo Sorce <idra@samba.org>. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _KDB_SAMBA_H_ +#define _KDB_SAMBA_H_ + +#include <stdbool.h> + +#include <krb5/krb5.h> +#include <krb5/plugin.h> + +#define PAC_LOGON_INFO 1 + +#ifndef discard_const_p +#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T) +# define discard_const_p(type, ptr) ((type *)((intptr_t)(ptr))) +#else +# define discard_const_p(type, ptr) ((type *)(ptr)) +#endif +#endif + +/* from kdb_samba_common.c */ + +struct mit_samba_context *ks_get_context(krb5_context kcontext); + +krb5_error_code ks_get_principal(krb5_context context, + krb5_const_principal principal, + unsigned int kflags, + krb5_db_entry **kentry); + +void ks_free_principal(krb5_context context, krb5_db_entry *entry); + +bool ks_data_eq_string(krb5_data d, const char *s); + +krb5_data ks_make_data(void *data, unsigned int len); + +krb5_boolean ks_is_kadmin(krb5_context context, + krb5_const_principal princ); + +krb5_boolean ks_is_kadmin_history(krb5_context context, + krb5_const_principal princ); + +krb5_boolean ks_is_kadmin_changepw(krb5_context context, + krb5_const_principal princ); + +krb5_boolean ks_is_kadmin_admin(krb5_context context, + krb5_const_principal princ); + +/* from kdb_samba_principals.c */ + +krb5_error_code kdb_samba_db_get_principal(krb5_context context, + krb5_const_principal princ, + unsigned int kflags, + krb5_db_entry **kentry); + +krb5_error_code kdb_samba_db_put_principal(krb5_context context, + krb5_db_entry *entry, + char **db_args); + +krb5_error_code kdb_samba_db_delete_principal(krb5_context context, + krb5_const_principal princ); + +krb5_error_code kdb_samba_db_iterate(krb5_context context, + char *match_entry, + int (*func)(krb5_pointer, krb5_db_entry *), + krb5_pointer func_arg, + krb5_flags iterflags); + +/* from kdb_samba_masterkey.c */ + +krb5_error_code kdb_samba_fetch_master_key(krb5_context context, + krb5_principal name, + krb5_keyblock *key, + krb5_kvno *kvno, + char *db_args); + +krb5_error_code kdb_samba_fetch_master_key_list(krb5_context context, + krb5_principal mname, + const krb5_keyblock *key, + krb5_keylist_node **mkeys_list); + +/* from kdb_samba_pac.c */ + +krb5_error_code kdb_samba_dbekd_decrypt_key_data(krb5_context context, + const krb5_keyblock *mkey, + const krb5_key_data *key_data, + krb5_keyblock *kkey, + krb5_keysalt *keysalt); + +krb5_error_code kdb_samba_dbekd_encrypt_key_data(krb5_context context, + const krb5_keyblock *mkey, + const krb5_keyblock *kkey, + const krb5_keysalt *keysalt, + int keyver, + krb5_key_data *key_data); + +/* from kdb_samba_policies.c */ +krb5_error_code kdb_samba_db_issue_pac(krb5_context context, + unsigned int flags, + krb5_db_entry *client, + krb5_keyblock *replaced_reply_key, + krb5_db_entry *server, + krb5_db_entry *signing_krbtgt, + krb5_timestamp authtime, + krb5_pac old_pac, + krb5_pac new_pac, + krb5_data ***auth_indicators); + +krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, + unsigned int flags, + krb5_const_principal client_princ, + krb5_const_principal server_princ, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_db_entry *krbtgt, + krb5_db_entry *local_krbtgt, + krb5_keyblock *client_key, + krb5_keyblock *server_key, + krb5_keyblock *krbtgt_key, + krb5_keyblock *local_krbtgt_key, + krb5_keyblock *session_key, + krb5_timestamp authtime, + krb5_authdata **tgt_auth_data, + void *authdata_info, + krb5_data ***auth_indicators, + krb5_authdata ***signed_auth_data); + +krb5_error_code kdb_samba_db_check_policy_as(krb5_context context, + krb5_kdc_req *kdcreq, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_timestamp kdc_time, + const char **status, + krb5_pa_data ***e_data_out); + +krb5_error_code kdb_samba_db_check_allowed_to_delegate(krb5_context context, + krb5_const_principal client, + const krb5_db_entry *server, + krb5_const_principal proxy); + +krb5_error_code kdb_samba_db_allowed_to_delegate_from( + krb5_context context, + krb5_const_principal client, + krb5_const_principal server, + krb5_pac header_pac, + const krb5_db_entry *proxy); + +void kdb_samba_db_audit_as_req(krb5_context kcontext, + krb5_kdc_req *request, + const krb5_address *local_addr, + const krb5_address *remote_addr, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_timestamp authtime, + krb5_error_code error_code); + +/* from kdb_samba_change_pwd.c */ + +krb5_error_code kdb_samba_change_pwd(krb5_context context, + krb5_keyblock *master_key, + krb5_key_salt_tuple *ks_tuple, + int ks_tuple_count, char *passwd, + int new_kvno, krb5_boolean keepold, + krb5_db_entry *db_entry); + +#endif /* _KDB_SAMBA_H_ */ diff --git a/source4/kdc/mit-kdb/kdb_samba_change_pwd.c b/source4/kdc/mit-kdb/kdb_samba_change_pwd.c new file mode 100644 index 0000000..ad7bb5d --- /dev/null +++ b/source4/kdc/mit-kdb/kdb_samba_change_pwd.c @@ -0,0 +1,59 @@ +/* + Unix SMB/CIFS implementation. + + Samba KDB plugin for MIT Kerberos + + Copyright (c) 2010 Simo Sorce <idra@samba.org>. + Copyright (c) 2014 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +#include "system/kerberos.h" + +#include <profile.h> +#include <kdb.h> + +#include "kdc/mit_samba.h" +#include "kdb_samba.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_KERBEROS + +krb5_error_code kdb_samba_change_pwd(krb5_context context, + krb5_keyblock *master_key, + krb5_key_salt_tuple *ks_tuple, + int ks_tuple_count, char *passwd, + int new_kvno, krb5_boolean keepold, + krb5_db_entry *db_entry) +{ + struct mit_samba_context *mit_ctx; + krb5_error_code code; + + mit_ctx = ks_get_context(context); + if (mit_ctx == NULL) { + return KRB5_KDB_DBNOTINITED; + } + + code = mit_samba_kpasswd_change_password(mit_ctx, passwd, db_entry); + if (code != 0) { + goto cleanup; + } + +cleanup: + + return code; +} diff --git a/source4/kdc/mit-kdb/kdb_samba_common.c b/source4/kdc/mit-kdb/kdb_samba_common.c new file mode 100644 index 0000000..1ad1c14 --- /dev/null +++ b/source4/kdc/mit-kdb/kdb_samba_common.c @@ -0,0 +1,114 @@ +/* + Unix SMB/CIFS implementation. + + Samba KDB plugin for MIT Kerberos + + Copyright (c) 2010 Simo Sorce <idra@samba.org>. + Copyright (c) 2014 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +#include "system/kerberos.h" + +#include <profile.h> +#include <kdb.h> + +#include "kdc/mit_samba.h" +#include "kdb_samba.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_KERBEROS + +struct mit_samba_context *ks_get_context(krb5_context kcontext) +{ + struct mit_samba_context *mit_ctx = NULL; + void *db_ctx = NULL; + krb5_error_code code; + + code = krb5_db_get_context(kcontext, &db_ctx); + if (code != 0) { + return NULL; + } + + mit_ctx = talloc_get_type_abort(db_ctx, struct mit_samba_context); + + /* + * This is nomrally the starting point for Kerberos operations in + * MIT KRB5, so reset errno to 0 for possible com_err debug messages. + */ + errno = 0; + + return mit_ctx; +} + +bool ks_data_eq_string(krb5_data d, const char *s) +{ + int rc; + + if (d.length != strlen(s) || d.length == 0) { + return false; + } + + rc = memcmp(d.data, s, d.length); + if (rc != 0) { + return false; + } + + return true; +} + +krb5_data ks_make_data(void *data, unsigned int len) +{ + krb5_data d; + + d.magic = KV5M_DATA; + d.data = data; + d.length = len; + + return d; +} + +krb5_boolean ks_is_kadmin(krb5_context context, + krb5_const_principal princ) +{ + return krb5_princ_size(context, princ) >= 1 && + ks_data_eq_string(princ->data[0], "kadmin"); +} + +krb5_boolean ks_is_kadmin_history(krb5_context context, + krb5_const_principal princ) +{ + return krb5_princ_size(context, princ) == 2 && + ks_data_eq_string(princ->data[0], "kadmin") && + ks_data_eq_string(princ->data[1], "history"); +} + +krb5_boolean ks_is_kadmin_changepw(krb5_context context, + krb5_const_principal princ) +{ + return krb5_princ_size(context, princ) == 2 && + ks_data_eq_string(princ->data[0], "kadmin") && + ks_data_eq_string(princ->data[1], "changepw"); +} + +krb5_boolean ks_is_kadmin_admin(krb5_context context, + krb5_const_principal princ) +{ + return krb5_princ_size(context, princ) == 2 && + ks_data_eq_string(princ->data[0], "kadmin") && + ks_data_eq_string(princ->data[1], "admin"); +} diff --git a/source4/kdc/mit-kdb/kdb_samba_masterkey.c b/source4/kdc/mit-kdb/kdb_samba_masterkey.c new file mode 100644 index 0000000..b068d96 --- /dev/null +++ b/source4/kdc/mit-kdb/kdb_samba_masterkey.c @@ -0,0 +1,69 @@ +/* + Unix SMB/CIFS implementation. + + Samba KDB plugin for MIT Kerberos + + Copyright (c) 2010 Simo Sorce <idra@samba.org>. + Copyright (c) 2014 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#include "includes.h" + +#include "system/kerberos.h" + +#include <profile.h> +#include <kdb.h> + +#include "kdc/mit_samba.h" +#include "kdb_samba.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_KERBEROS + +krb5_error_code kdb_samba_fetch_master_key(krb5_context context, + krb5_principal name, + krb5_keyblock *key, + krb5_kvno *kvno, + char *db_args) +{ + return 0; +} + +krb5_error_code kdb_samba_fetch_master_key_list(krb5_context context, + krb5_principal mname, + const krb5_keyblock *key, + krb5_keylist_node **mkeys_list) +{ + krb5_keylist_node *mkey; + + /* + * NOTE: samba does not support master keys + * so just return a dummy key + */ + mkey = calloc(1, sizeof(krb5_keylist_node)); + if (mkey == NULL) { + return ENOMEM; + } + + mkey->keyblock.magic = KV5M_KEYBLOCK; + mkey->keyblock.enctype = ENCTYPE_UNKNOWN; + mkey->kvno = 1; + + *mkeys_list = mkey; + + return 0; +} diff --git a/source4/kdc/mit-kdb/kdb_samba_pac.c b/source4/kdc/mit-kdb/kdb_samba_pac.c new file mode 100644 index 0000000..75b05a6 --- /dev/null +++ b/source4/kdc/mit-kdb/kdb_samba_pac.c @@ -0,0 +1,115 @@ +/* + Unix SMB/CIFS implementation. + + Samba KDB plugin for MIT Kerberos + + Copyright (c) 2010 Simo Sorce <idra@samba.org>. + Copyright (c) 2014 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +#include "system/kerberos.h" + +#include <profile.h> +#include <kdb.h> + +#include "kdc/mit_samba.h" +#include "kdb_samba.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_KERBEROS + +krb5_error_code kdb_samba_dbekd_decrypt_key_data(krb5_context context, + const krb5_keyblock *mkey, + const krb5_key_data *key_data, + krb5_keyblock *kkey, + krb5_keysalt *keysalt) +{ + /* + * NOTE: Samba doesn't use a master key, so we will just copy + * the contents around untouched. + */ + ZERO_STRUCTP(kkey); + + kkey->magic = KV5M_KEYBLOCK; + kkey->enctype = key_data->key_data_type[0]; + kkey->contents = malloc(key_data->key_data_length[0]); + if (kkey->contents == NULL) { + return ENOMEM; + } + memcpy(kkey->contents, + key_data->key_data_contents[0], + key_data->key_data_length[0]); + kkey->length = key_data->key_data_length[0]; + + if (keysalt != NULL) { + keysalt->type = key_data->key_data_type[1]; + keysalt->data.data = malloc(key_data->key_data_length[1]); + if (keysalt->data.data == NULL) { + free(kkey->contents); + return ENOMEM; + } + memcpy(keysalt->data.data, + key_data->key_data_contents[1], + key_data->key_data_length[1]); + keysalt->data.length = key_data->key_data_length[1]; + } + + return 0; +} + +krb5_error_code kdb_samba_dbekd_encrypt_key_data(krb5_context context, + const krb5_keyblock *mkey, + const krb5_keyblock *kkey, + const krb5_keysalt *keysalt, + int keyver, + krb5_key_data *key_data) +{ + /* + * NOTE: samba doesn't use a master key, so we will just copy + * the contents around untouched. + */ + + ZERO_STRUCTP(key_data); + + key_data->key_data_ver = KRB5_KDB_V1_KEY_DATA_ARRAY; + key_data->key_data_kvno = keyver; + key_data->key_data_type[0] = kkey->enctype; + key_data->key_data_contents[0] = malloc(kkey->length); + if (key_data->key_data_contents[0] == NULL) { + return ENOMEM; + } + memcpy(key_data->key_data_contents[0], + kkey->contents, + kkey->length); + key_data->key_data_length[0] = kkey->length; + + if (keysalt != NULL) { + key_data->key_data_type[1] = keysalt->type; + key_data->key_data_contents[1] = malloc(keysalt->data.length); + if (key_data->key_data_contents[1] == NULL) { + free(key_data->key_data_contents[0]); + return ENOMEM; + } + memcpy(key_data->key_data_contents[1], + keysalt->data.data, + keysalt->data.length); + key_data->key_data_length[1] = keysalt->data.length; + } + + return 0; +} diff --git a/source4/kdc/mit-kdb/kdb_samba_policies.c b/source4/kdc/mit-kdb/kdb_samba_policies.c new file mode 100644 index 0000000..0004854 --- /dev/null +++ b/source4/kdc/mit-kdb/kdb_samba_policies.c @@ -0,0 +1,769 @@ +/* + Unix SMB/CIFS implementation. + + Samba KDB plugin for MIT Kerberos + + Copyright (c) 2010 Simo Sorce <idra@samba.org>. + Copyright (c) 2014-2021 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "lib/replace/replace.h" +#include "lib/replace/system/kerberos.h" +#include "lib/util/data_blob.h" +#include "lib/util/debug.h" +#include "lib/util/fault.h" +#include "lib/util/memory.h" + +#include <profile.h> +#include <kdb.h> + +#include "kdc/mit_samba.h" +#include "kdb_samba.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_KERBEROS + +/* FIXME: This is a krb5 function which is exported, but in no header */ +extern krb5_error_code decode_krb5_padata_sequence(const krb5_data *output, + krb5_pa_data ***rep); + +static krb5_error_code ks_get_netbios_name(krb5_address **addrs, char **name) +{ + char *nb_name = NULL; + int len, i; + + for (i = 0; addrs[i]; i++) { + if (addrs[i]->addrtype != ADDRTYPE_NETBIOS) { + continue; + } + len = MIN(addrs[i]->length, 15); + nb_name = strndup((const char *)addrs[i]->contents, len); + if (!nb_name) { + return ENOMEM; + } + break; + } + + if (nb_name) { + /* Strip space padding */ + i = strlen(nb_name) - 1; + for (i = strlen(nb_name) - 1; + i > 0 && nb_name[i] == ' '; + i--) { + nb_name[i] = '\0'; + } + } + + *name = nb_name; + + return 0; +} + +krb5_error_code kdb_samba_db_check_policy_as(krb5_context context, + krb5_kdc_req *kdcreq, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_timestamp kdc_time, + const char **status, + krb5_pa_data ***e_data_out) +{ + struct mit_samba_context *mit_ctx; + krb5_error_code code; + char *client_name = NULL; + char *server_name = NULL; + char *netbios_name = NULL; + char *realm = NULL; + bool password_change = false; + krb5_const_principal client_princ; + DATA_BLOB int_data = { NULL, 0 }; + krb5_data d; + krb5_pa_data **e_data; + + mit_ctx = ks_get_context(context); + if (mit_ctx == NULL) { + return KRB5_KDB_DBNOTINITED; + } + + /* Prefer canonicalised name from client entry */ + client_princ = client ? client->princ : kdcreq->client; + + if (client_princ == NULL || ks_is_kadmin(context, client_princ)) { + return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; + } + + if (krb5_princ_size(context, kdcreq->server) == 2 && + ks_is_kadmin_changepw(context, kdcreq->server)) { + code = krb5_get_default_realm(context, &realm); + if (code) { + goto done; + } + + if (ks_data_eq_string(kdcreq->server->realm, realm)) { + password_change = true; + } + } + + code = krb5_unparse_name(context, kdcreq->server, &server_name); + if (code) { + goto done; + } + + code = krb5_unparse_name(context, client_princ, &client_name); + if (code) { + goto done; + } + + if (kdcreq->addresses) { + code = ks_get_netbios_name(kdcreq->addresses, &netbios_name); + if (code) { + goto done; + } + } + + code = mit_samba_check_client_access(mit_ctx, + client, + client_name, + server, + server_name, + netbios_name, + password_change, + &int_data); + + if (int_data.length && int_data.data) { + + /* make sure the mapped return code is returned - gd */ + int code_tmp; + + d = ks_make_data(int_data.data, int_data.length); + + code_tmp = decode_krb5_padata_sequence(&d, &e_data); + if (code_tmp == 0) { + *e_data_out = e_data; + } + } +done: + free(realm); + free(server_name); + free(client_name); + free(netbios_name); + + return code; +} + +static krb5_error_code ks_get_pac(krb5_context context, + uint32_t flags, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_keyblock *client_key, + krb5_pac *pac) +{ + struct mit_samba_context *mit_ctx; + krb5_error_code code; + + mit_ctx = ks_get_context(context); + if (mit_ctx == NULL) { + return KRB5_KDB_DBNOTINITED; + } + + code = mit_samba_get_pac(mit_ctx, + context, + flags, + client, + server, + client_key, + pac); + if (code != 0) { + return code; + } + + return code; +} + +#if KRB5_KDB_DAL_MAJOR_VERSION < 9 +static krb5_error_code ks_verify_pac(krb5_context context, + unsigned int flags, + krb5_const_principal client_princ, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_db_entry *krbtgt, + krb5_keyblock *server_key, + krb5_keyblock *krbtgt_key, + krb5_timestamp authtime, + krb5_authdata **tgt_auth_data, + krb5_pac *pac) +{ + struct mit_samba_context *mit_ctx; + krb5_authdata **authdata = NULL; + krb5_pac ipac = NULL; + DATA_BLOB logon_data = { NULL, 0 }; + krb5_error_code code; + + mit_ctx = ks_get_context(context); + if (mit_ctx == NULL) { + return KRB5_KDB_DBNOTINITED; + } + + /* find the existing PAC, if present */ + code = krb5_find_authdata(context, + tgt_auth_data, + NULL, + KRB5_AUTHDATA_WIN2K_PAC, + &authdata); + if (code != 0) { + return code; + } + + /* no pac data */ + if (authdata == NULL) { + return 0; + } + + SMB_ASSERT(authdata[0] != NULL); + + if (authdata[1] != NULL) { + code = KRB5KDC_ERR_BADOPTION; /* XXX */ + goto done; + } + + code = krb5_pac_parse(context, + authdata[0]->contents, + authdata[0]->length, + &ipac); + if (code != 0) { + goto done; + } + + /* TODO: verify this is correct + * + * In the constrained delegation case, the PAC is from a service + * ticket rather than a TGT; we must verify the server and KDC + * signatures to assert that the server did not forge the PAC. + */ + if (flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) { + code = krb5_pac_verify(context, + ipac, + authtime, + client_princ, + server_key, + krbtgt_key); + } else { + code = krb5_pac_verify(context, + ipac, + authtime, + client_princ, + krbtgt_key, + NULL); + } + if (code != 0) { + goto done; + } + + /* check and update PAC */ + code = krb5_pac_parse(context, + authdata[0]->contents, + authdata[0]->length, + pac); + if (code != 0) { + goto done; + } + + code = mit_samba_reget_pac(mit_ctx, + context, + flags, + client_princ, + client, + server, + krbtgt, + krbtgt_key, + pac); + +done: + krb5_free_authdata(context, authdata); + krb5_pac_free(context, ipac); + free(logon_data.data); + + return code; +} + +krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, + unsigned int flags, + krb5_const_principal client_princ, + krb5_const_principal server_princ, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_db_entry *krbtgt, + krb5_db_entry *local_krbtgt, + krb5_keyblock *client_key, + krb5_keyblock *server_key, + krb5_keyblock *krbtgt_key, + krb5_keyblock *local_krbtgt_key, + krb5_keyblock *session_key, + krb5_timestamp authtime, + krb5_authdata **tgt_auth_data, + void *authdata_info, + krb5_data ***auth_indicators, + krb5_authdata ***signed_auth_data) +{ + krb5_const_principal ks_client_princ = NULL; + krb5_db_entry *client_entry = NULL; + krb5_authdata **pac_auth_data = NULL; + krb5_authdata **authdata = NULL; + krb5_boolean is_as_req; + krb5_error_code code; + krb5_pac pac = NULL; + krb5_data pac_data; + bool with_pac = false; + bool generate_pac = false; + char *client_name = NULL; + + + krbtgt = krbtgt == NULL ? local_krbtgt : krbtgt; + krbtgt_key = krbtgt_key == NULL ? local_krbtgt_key : krbtgt_key; + + /* FIXME: We don't support S4U yet */ + if (flags & KRB5_KDB_FLAGS_S4U) { + return KRB5_KDB_DBTYPE_NOSUP; + } + + is_as_req = ((flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) != 0); + + /* + * When using s4u2proxy client_princ actually refers to the proxied user + * while client->princ to the proxy service asking for the TGS on behalf + * of the proxied user. So always use client_princ in preference. + * + * Note that when client principal is not NULL, client entry might be + * NULL for cross-realm case, so we need to make sure to not + * dereference NULL pointer here. + */ + if (client_princ != NULL) { + ks_client_princ = client_princ; + if (!is_as_req) { + krb5_boolean is_equal = false; + + if (client != NULL && client->princ != NULL) { + is_equal = + krb5_principal_compare(context, + client_princ, + client->princ); + } + + /* + * When client principal is the same as supplied client + * entry, don't fetch it. + */ + if (!is_equal) { + code = ks_get_principal(context, + ks_client_princ, + 0, + &client_entry); + if (code != 0) { + (void)krb5_unparse_name(context, + ks_client_princ, + &client_name); + + DBG_DEBUG("We didn't find the client " + "principal [%s] in our " + "database.\n", + client_name); + SAFE_FREE(client_name); + + /* + * If we didn't find client_princ in our + * database it might be from another + * realm. + */ + client_entry = NULL; + } + } + } + } else { + if (client == NULL) { + *signed_auth_data = NULL; + return 0; + } + ks_client_princ = client->princ; + } + + if (client_entry == NULL) { + client_entry = client; + } + + if (is_as_req) { + with_pac = mit_samba_princ_needs_pac(client_entry); + } else { + with_pac = mit_samba_princ_needs_pac(server); + } + + code = krb5_unparse_name(context, + client_princ, + &client_name); + if (code != 0) { + goto done; + } + + if (is_as_req && (flags & KRB5_KDB_FLAG_INCLUDE_PAC) != 0) { + generate_pac = true; + } + + DBG_DEBUG("*** Sign data for client principal: %s [%s %s%s]\n", + client_name, + is_as_req ? "AS-REQ" : "TGS_REQ", + with_pac ? is_as_req ? "WITH_PAC" : "FIND_PAC" : "NO_PAC", + generate_pac ? " GENERATE_PAC" : ""); + + /* + * Generate PAC for the AS-REQ or check or generate one for the TGS if + * needed. + */ + if (with_pac && generate_pac) { + DBG_DEBUG("Generate PAC for AS-REQ [%s]\n", client_name); + + code = krb5_pac_init(context, &pac); + if (code != 0) { + goto done; + } + + code = ks_get_pac(context, + flags, + client_entry, + server, + NULL, + &pac); + if (code != 0) { + goto done; + } + } else if (with_pac && !is_as_req) { + /* + * Find the PAC in the TGS, if one exists. + */ + code = krb5_find_authdata(context, + tgt_auth_data, + NULL, + KRB5_AUTHDATA_WIN2K_PAC, + &pac_auth_data); + if (code != 0) { + DBG_ERR("krb5_find_authdata failed: %d\n", code); + goto done; + } + DBG_DEBUG("Found PAC data for TGS-REQ [%s]\n", client_name); + + if (pac_auth_data != NULL && pac_auth_data[0] != NULL) { + if (pac_auth_data[1] != NULL) { + DBG_ERR("Invalid PAC data!\n"); + code = KRB5KDC_ERR_BADOPTION; + goto done; + } + + DBG_DEBUG("Verify PAC for TGS [%s]\n", + client_name); + + code = ks_verify_pac(context, + flags, + ks_client_princ, + client_entry, + server, + krbtgt, + server_key, + krbtgt_key, + authtime, + tgt_auth_data, + &pac); + if (code != 0) { + goto done; + } + } else { + if (flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) { + DBG_DEBUG("Generate PAC for constrained" + "delegation TGS [%s]\n", + client_name); + + code = krb5_pac_init(context, &pac); + if (code != 0) { + goto done; + } + + code = ks_get_pac(context, + flags, + client_entry, + server, + NULL, + &pac); + if (code != 0 && code != ENOENT) { + goto done; + } + } + } + } + + if (pac == NULL) { + DBG_DEBUG("No PAC data - we're done [%s]\n", client_name); + *signed_auth_data = NULL; + code = 0; + goto done; + } + + DBG_DEBUG("Signing PAC for %s [%s]\n", + is_as_req ? "AS-REQ" : "TGS-REQ", + client_name); + code = krb5_pac_sign(context, pac, authtime, ks_client_princ, + server_key, krbtgt_key, &pac_data); + if (code != 0) { + DBG_ERR("krb5_pac_sign failed: %d\n", code); + goto done; + } + + authdata = calloc(2, sizeof(krb5_authdata *)); + if (authdata == NULL) { + goto done; + } + + authdata[0] = malloc(sizeof(krb5_authdata)); + if (authdata[0] == NULL) { + goto done; + } + + /* put in signed data */ + authdata[0]->magic = KV5M_AUTHDATA; + authdata[0]->ad_type = KRB5_AUTHDATA_WIN2K_PAC; + authdata[0]->contents = (krb5_octet *)pac_data.data; + authdata[0]->length = pac_data.length; + + code = krb5_encode_authdata_container(context, + KRB5_AUTHDATA_IF_RELEVANT, + authdata, + signed_auth_data); + if (code != 0) { + goto done; + } + + code = 0; + +done: + if (client_entry != NULL && client_entry != client) { + ks_free_principal(context, client_entry); + } + SAFE_FREE(client_name); + krb5_free_authdata(context, authdata); + krb5_pac_free(context, pac); + + return code; +} +#else /* KRB5_KDB_DAL_MAJOR_VERSION >= 9 */ +static krb5_error_code ks_update_pac(krb5_context context, + int flags, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_db_entry *signing_krbtgt, + krb5_pac old_pac, + krb5_pac new_pac) +{ + struct mit_samba_context *mit_ctx = NULL; + krb5_error_code code; + + mit_ctx = ks_get_context(context); + if (mit_ctx == NULL) { + return KRB5_KDB_DBNOTINITED; + } + + code = mit_samba_update_pac(mit_ctx, + context, + flags, + client, + server, + signing_krbtgt, + old_pac, + new_pac); + if (code != 0) { + return code; + } + + return code; +} + +krb5_error_code kdb_samba_db_issue_pac(krb5_context context, + unsigned int flags, + krb5_db_entry *client, + krb5_keyblock *replaced_reply_key, + krb5_db_entry *server, + krb5_db_entry *signing_krbtgt, + krb5_timestamp authtime, + krb5_pac old_pac, + krb5_pac new_pac, + krb5_data ***auth_indicators) +{ + char *client_name = NULL; + char *server_name = NULL; + krb5_error_code code = EINVAL; + + /* The KDC handles both signing and verification for us. */ + + if (client != NULL) { + code = krb5_unparse_name(context, + client->princ, + &client_name); + if (code != 0) { + return code; + } + } + + if (server != NULL) { + code = krb5_unparse_name(context, + server->princ, + &server_name); + if (code != 0) { + SAFE_FREE(client_name); + return code; + } + } + + /* + * Get a new PAC for AS-REQ or S4U2Self for our realm. + * + * For a simple cross-realm S4U2Proxy there will be the following TGS + * requests after the client realm is identified: + * + * 1. server@SREALM to SREALM for krbtgt/CREALM@SREALM -- a regular TGS + * request with server's normal TGT and no S4U2Self padata. + * 2. server@SREALM to CREALM for server@SREALM (expressed as an + * enterprise principal), with the TGT from #1 as header ticket and + * S4U2Self padata identifying the client. + * 3. server@SREALM to SREALM for server@SREALM with S4U2Self padata, + * with the referral TGT from #2 as header ticket + * + * In request 2 the PROTOCOL_TRANSITION and CROSS_REALM flags are set, + * and the request is for a local client (so client != NULL) and we + * want to make a new PAC. + * + * In request 3 the PROTOCOL_TRANSITION and CROSS_REALM flags are also + * set, but the request is for a non-local client (so client == NULL) + * and we want to copy the subject PAC contained in the referral TGT. + */ + if (old_pac == NULL || + (client != NULL && (flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION))) { + DBG_NOTICE("Generate PAC for AS-REQ [client=%s, flags=%#08x]\n", + client_name != NULL ? client_name : "<unknown>", + flags); + + code = ks_get_pac(context, + flags, + client, + server, + replaced_reply_key, + &new_pac); + } else { + DBG_NOTICE("Update PAC for TGS-REQ [client=%s, server=%s, " + "flags=%#08x]\n", + client_name != NULL ? client_name : "<unknown>", + server_name != NULL ? server_name : "<unknown>", + flags); + + code = ks_update_pac(context, + flags, + client, + server, + signing_krbtgt, + old_pac, + new_pac); + } + SAFE_FREE(client_name); + SAFE_FREE(server_name); + + return code; +} +#endif /* KRB5_KDB_DAL_MAJOR_VERSION */ + +krb5_error_code kdb_samba_db_check_allowed_to_delegate(krb5_context context, + krb5_const_principal client, + const krb5_db_entry *server, + krb5_const_principal proxy) +{ + struct mit_samba_context *mit_ctx = NULL; + + mit_ctx = ks_get_context(context); + if (mit_ctx == NULL) { + return KRB5_KDB_DBNOTINITED; + } + + return mit_samba_check_s4u2proxy(mit_ctx, + server, + proxy); + +} + + +#if KRB5_KDB_DAL_MAJOR_VERSION >= 9 +krb5_error_code kdb_samba_db_allowed_to_delegate_from( + krb5_context context, + krb5_const_principal client_principal, + krb5_const_principal server_principal, + krb5_pac header_pac, + const krb5_db_entry *proxy) +{ + struct mit_samba_context *mit_ctx = NULL; + krb5_error_code code; + + mit_ctx = ks_get_context(context); + if (mit_ctx == NULL) { + return KRB5_KDB_DBNOTINITED; + } + + code = mit_samba_check_allowed_to_delegate_from(mit_ctx, + client_principal, + server_principal, + header_pac, + proxy); + + return code; +} +#endif + + +static void samba_bad_password_count(krb5_db_entry *client, + krb5_error_code error_code) +{ + switch (error_code) { + case 0: + mit_samba_zero_bad_password_count(client); + break; + case KRB5KDC_ERR_PREAUTH_FAILED: + case KRB5KRB_AP_ERR_BAD_INTEGRITY: + mit_samba_update_bad_password_count(client); + break; + } +} + +void kdb_samba_db_audit_as_req(krb5_context context, + krb5_kdc_req *request, + const krb5_address *local_addr, + const krb5_address *remote_addr, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_timestamp authtime, + krb5_error_code error_code) +{ + /* + * FIXME: This segfaulted with a FAST test + * FIND_FAST: <unknown client> for <unknown server>, Unknown FAST armor type 0 + */ + if (client == NULL) { + return; + } + + samba_bad_password_count(client, error_code); + + /* TODO: perform proper audit logging for addresses */ +} diff --git a/source4/kdc/mit-kdb/kdb_samba_principals.c b/source4/kdc/mit-kdb/kdb_samba_principals.c new file mode 100644 index 0000000..2726018 --- /dev/null +++ b/source4/kdc/mit-kdb/kdb_samba_principals.c @@ -0,0 +1,397 @@ +/* + Unix SMB/CIFS implementation. + + Samba KDB plugin for MIT Kerberos + + Copyright (c) 2010 Simo Sorce <idra@samba.org>. + Copyright (c) 2014 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +#include "system/kerberos.h" + +#include <profile.h> +#include <kdb.h> + +#include "kdc/samba_kdc.h" +#include "kdc/mit_samba.h" +#include "kdb_samba.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_KERBEROS + +#define ADMIN_LIFETIME 60*60*3 /* 3 hours */ + +krb5_error_code ks_get_principal(krb5_context context, + krb5_const_principal principal, + unsigned int kflags, + krb5_db_entry **kentry) +{ + struct mit_samba_context *mit_ctx; + krb5_error_code code; + + mit_ctx = ks_get_context(context); + if (mit_ctx == NULL) { + return KRB5_KDB_DBNOTINITED; + } + + code = mit_samba_get_principal(mit_ctx, + principal, + kflags, + kentry); + if (code != 0) { + goto cleanup; + } + +cleanup: + + return code; +} + +static void ks_free_principal_e_data(krb5_context context, krb5_octet *e_data) +{ + struct samba_kdc_entry *skdc_entry; + + skdc_entry = talloc_get_type_abort(e_data, + struct samba_kdc_entry); + skdc_entry->kdc_entry = NULL; + TALLOC_FREE(skdc_entry); +} + +void ks_free_principal(krb5_context context, krb5_db_entry *entry) +{ + krb5_tl_data *tl_data_next = NULL; + krb5_tl_data *tl_data = NULL; + size_t i, j; + + if (entry != NULL) { + krb5_free_principal(context, entry->princ); + + for (tl_data = entry->tl_data; tl_data; tl_data = tl_data_next) { + tl_data_next = tl_data->tl_data_next; + if (tl_data->tl_data_contents != NULL) { + free(tl_data->tl_data_contents); + } + free(tl_data); + } + + if (entry->key_data != NULL) { + for (i = 0; i < entry->n_key_data; i++) { + for (j = 0; j < entry->key_data[i].key_data_ver; j++) { + if (entry->key_data[i].key_data_length[j] != 0) { + if (entry->key_data[i].key_data_contents[j] != NULL) { + memset(entry->key_data[i].key_data_contents[j], 0, entry->key_data[i].key_data_length[j]); + free(entry->key_data[i].key_data_contents[j]); + } + } + entry->key_data[i].key_data_contents[j] = NULL; + entry->key_data[i].key_data_length[j] = 0; + entry->key_data[i].key_data_type[j] = 0; + } + } + free(entry->key_data); + } + + if (entry->e_data) { + ks_free_principal_e_data(context, entry->e_data); + } + + free(entry); + } +} + +static krb5_boolean ks_is_master_key_principal(krb5_context context, + krb5_const_principal princ) +{ + return krb5_princ_size(context, princ) == 2 && + ks_data_eq_string(princ->data[0], "K") && + ks_data_eq_string(princ->data[1], "M"); +} + +static krb5_error_code ks_get_master_key_principal(krb5_context context, + krb5_const_principal princ, + krb5_db_entry **kentry_ptr) +{ + krb5_error_code code; + krb5_key_data *key_data; + krb5_timestamp now; + krb5_db_entry *kentry; + + *kentry_ptr = NULL; + + kentry = calloc(1, sizeof(krb5_db_entry)); + if (kentry == NULL) { + return ENOMEM; + } + + kentry->magic = KRB5_KDB_MAGIC_NUMBER; + kentry->len = KRB5_KDB_V1_BASE_LENGTH; + kentry->attributes = KRB5_KDB_DISALLOW_ALL_TIX; + + if (princ == NULL) { + code = krb5_parse_name(context, KRB5_KDB_M_NAME, &kentry->princ); + } else { + code = krb5_copy_principal(context, princ, &kentry->princ); + } + if (code != 0) { + krb5_db_free_principal(context, kentry); + return code; + } + + now = time(NULL); + + code = krb5_dbe_update_mod_princ_data(context, kentry, now, kentry->princ); + if (code != 0) { + krb5_db_free_principal(context, kentry); + return code; + } + + /* Return a dummy key */ + kentry->n_key_data = 1; + kentry->key_data = calloc(1, sizeof(krb5_key_data)); + if (code != 0) { + krb5_db_free_principal(context, kentry); + return code; + } + + key_data = &kentry->key_data[0]; + + key_data->key_data_ver = KRB5_KDB_V1_KEY_DATA_ARRAY; + key_data->key_data_kvno = 1; + key_data->key_data_type[0] = ENCTYPE_UNKNOWN; + if (code != 0) { + krb5_db_free_principal(context, kentry); + return code; + } + + *kentry_ptr = kentry; + + return 0; +} + +static krb5_error_code ks_create_principal(krb5_context context, + krb5_const_principal princ, + int attributes, + int max_life, + const char *password, + krb5_db_entry **kentry_ptr) +{ + krb5_error_code code; + krb5_key_data *key_data; + krb5_timestamp now; + krb5_db_entry *kentry; + krb5_keyblock key; + krb5_data salt; + krb5_data pwd; + int enctype = ENCTYPE_AES256_CTS_HMAC_SHA1_96; + int sts = KRB5_KDB_SALTTYPE_SPECIAL; + + if (princ == NULL) { + return KRB5_KDB_NOENTRY; + } + + *kentry_ptr = NULL; + + kentry = calloc(1, sizeof(krb5_db_entry)); + if (kentry == NULL) { + return ENOMEM; + } + + kentry->magic = KRB5_KDB_MAGIC_NUMBER; + kentry->len = KRB5_KDB_V1_BASE_LENGTH; + + if (attributes > 0) { + kentry->attributes = attributes; + } + + if (max_life > 0) { + kentry->max_life = max_life; + } + + code = krb5_copy_principal(context, princ, &kentry->princ); + if (code != 0) { + krb5_db_free_principal(context, kentry); + return code; + } + + now = time(NULL); + + code = krb5_dbe_update_mod_princ_data(context, kentry, now, kentry->princ); + if (code != 0) { + krb5_db_free_principal(context, kentry); + return code; + } + + code = mit_samba_generate_salt(&salt); + if (code != 0) { + krb5_db_free_principal(context, kentry); + return code; + } + + if (password != NULL) { + pwd.data = strdup(password); + pwd.length = strlen(password); + } else { + /* create a random password */ + code = mit_samba_generate_random_password(&pwd); + if (code != 0) { + krb5_db_free_principal(context, kentry); + return code; + } + } + + code = krb5_c_string_to_key(context, enctype, &pwd, &salt, &key); + SAFE_FREE(pwd.data); + if (code != 0) { + krb5_db_free_principal(context, kentry); + return code; + } + + kentry->n_key_data = 1; + kentry->key_data = calloc(1, sizeof(krb5_key_data)); + if (code != 0) { + krb5_db_free_principal(context, kentry); + return code; + } + + key_data = &kentry->key_data[0]; + + key_data->key_data_ver = KRB5_KDB_V1_KEY_DATA_ARRAY; + key_data->key_data_kvno = 1; + key_data->key_data_type[0] = key.enctype; + key_data->key_data_length[0] = key.length; + key_data->key_data_contents[0] = key.contents; + key_data->key_data_type[1] = sts; + key_data->key_data_length[1] = salt.length; + key_data->key_data_contents[1] = (krb5_octet*)salt.data; + + *kentry_ptr = kentry; + + return 0; +} + +static krb5_error_code ks_get_admin_principal(krb5_context context, + krb5_const_principal princ, + krb5_db_entry **kentry_ptr) +{ + krb5_error_code code = EINVAL; + + code = ks_create_principal(context, + princ, + KRB5_KDB_DISALLOW_TGT_BASED, + ADMIN_LIFETIME, + NULL, + kentry_ptr); + + return code; +} + +krb5_error_code kdb_samba_db_get_principal(krb5_context context, + krb5_const_principal princ, + unsigned int kflags, + krb5_db_entry **kentry) +{ + struct mit_samba_context *mit_ctx; + krb5_error_code code; + + mit_ctx = ks_get_context(context); + if (mit_ctx == NULL) { + return KRB5_KDB_DBNOTINITED; + } + + if (ks_is_master_key_principal(context, princ)) { + return ks_get_master_key_principal(context, princ, kentry); + } + + /* + * Fake a kadmin/admin and kadmin/history principal so that kadmindd can + * start + */ + if (ks_is_kadmin_admin(context, princ) || + ks_is_kadmin_history(context, princ)) { + return ks_get_admin_principal(context, princ, kentry); + } + + code = ks_get_principal(context, princ, kflags, kentry); + + /* + * This restricts the changepw account so it isn't able to request a + * service ticket. It also marks the principal as the changepw service. + */ + if (ks_is_kadmin_changepw(context, princ)) { + /* FIXME: shouldn't we also set KRB5_KDB_DISALLOW_TGT_BASED ? + * testing showed that setpw kpasswd command fails then on the + * server though... */ + (*kentry)->attributes |= KRB5_KDB_PWCHANGE_SERVICE; + (*kentry)->max_life = CHANGEPW_LIFETIME; + } + + return code; +} + +krb5_error_code kdb_samba_db_put_principal(krb5_context context, + krb5_db_entry *entry, + char **db_args) +{ + + /* NOTE: deferred, samba does not allow the KDC to store + * principals for now. We should not return KRB5_KDB_DB_INUSE as this + * would result in confusing error messages after password changes. */ + return 0; +} + +krb5_error_code kdb_samba_db_delete_principal(krb5_context context, + krb5_const_principal princ) +{ + + /* NOTE: deferred, samba does not allow the KDC to delete + * principals for now */ + return KRB5_KDB_DB_INUSE; +} + +krb5_error_code kdb_samba_db_iterate(krb5_context context, + char *match_entry, + int (*func)(krb5_pointer, krb5_db_entry *), + krb5_pointer func_arg, + krb5_flags iterflags) +{ + struct mit_samba_context *mit_ctx; + krb5_db_entry *kentry = NULL; + krb5_error_code code; + + + mit_ctx = ks_get_context(context); + if (mit_ctx == NULL) { + return KRB5_KDB_DBNOTINITED; + } + + code = mit_samba_get_firstkey(mit_ctx, &kentry); + while (code == 0) { + code = (*func)(func_arg, kentry); + if (code != 0) { + break; + } + + code = mit_samba_get_nextkey(mit_ctx, &kentry); + } + + if (code == KRB5_KDB_NOENTRY) { + code = 0; + } + + return code; +} diff --git a/source4/kdc/mit-kdb/wscript_build b/source4/kdc/mit-kdb/wscript_build new file mode 100644 index 0000000..82cea4a --- /dev/null +++ b/source4/kdc/mit-kdb/wscript_build @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +bld.SAMBA_LIBRARY('mit-kdb-samba', + source=''' + kdb_samba.c + kdb_samba_common.c + kdb_samba_masterkey.c + kdb_samba_pac.c + kdb_samba_policies.c + kdb_samba_principals.c + kdb_samba_change_pwd.c + ''', + private_library=True, + realname='samba.so', + install_path='${LIBDIR}/krb5/plugins/kdb', + deps=''' + MIT_SAMBA + com_err + krb5 + kdb5 + ''', + enabled=bld.CONFIG_SET('HAVE_KDB_H')) diff --git a/source4/kdc/mit_kdc_irpc.c b/source4/kdc/mit_kdc_irpc.c new file mode 100644 index 0000000..9113cf8 --- /dev/null +++ b/source4/kdc/mit_kdc_irpc.c @@ -0,0 +1,200 @@ +/* + * Unix SMB/CIFS implementation. + * + * Copyright (c) 2015 Andreas Schneider <asn@samba.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "includes.h" +#include "system/kerberos.h" +#include "source4/auth/kerberos/kerberos.h" +#include "auth/kerberos/pac_utils.h" + +#include "librpc/gen_ndr/irpc.h" +#include "lib/messaging/irpc.h" +#include "source4/librpc/gen_ndr/ndr_irpc.h" +#include "source4/librpc/gen_ndr/irpc.h" + +#include "librpc/gen_ndr/ndr_krb5pac.h" + +#include "source4/samba/process_model.h" +#include "lib/param/param.h" + +#include "samba_kdc.h" +#include "db-glue.h" +#include "sdb.h" +#include "mit_kdc_irpc.h" + +struct mit_kdc_irpc_context { + struct task_server *task; + krb5_context krb5_context; + struct samba_kdc_db_context *db_ctx; +}; + +static NTSTATUS netr_samlogon_generic_logon(struct irpc_message *msg, + struct kdc_check_generic_kerberos *r) +{ + struct PAC_Validate pac_validate; + DATA_BLOB pac_chksum; + struct PAC_SIGNATURE_DATA pac_kdc_sig; + struct mit_kdc_irpc_context *mki_ctx = + talloc_get_type(msg->private_data, + struct mit_kdc_irpc_context); + enum ndr_err_code ndr_err; + int code; + krb5_principal principal; + struct sdb_entry sentry = {}; + struct sdb_keys skeys; + unsigned int i; + const uint8_t *d = NULL; + + /* There is no reply to this request */ + r->out.generic_reply = data_blob(NULL, 0); + + ndr_err = + ndr_pull_struct_blob(&r->in.generic_request, + msg, + &pac_validate, + (ndr_pull_flags_fn_t)ndr_pull_PAC_Validate); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (pac_validate.MessageType != NETLOGON_GENERIC_KRB5_PAC_VALIDATE) { + /* + * We don't implement any other message types - such as + * certificate validation - yet + */ + return NT_STATUS_INVALID_PARAMETER; + } + + if ((pac_validate.ChecksumAndSignature.length != + (pac_validate.ChecksumLength + pac_validate.SignatureLength)) || + (pac_validate.ChecksumAndSignature.length < + pac_validate.ChecksumLength) || + (pac_validate.ChecksumAndSignature.length < + pac_validate.SignatureLength)) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* PAC Checksum */ + pac_chksum = data_blob_const(pac_validate.ChecksumAndSignature.data, + pac_validate.ChecksumLength); + + /* Create the krbtgt principal */ + code = smb_krb5_make_principal(mki_ctx->krb5_context, + &principal, + lpcfg_realm(mki_ctx->task->lp_ctx), + "krbtgt", + lpcfg_realm(mki_ctx->task->lp_ctx), + NULL); + if (code != 0) { + DEBUG(0, ("Failed to create krbtgt@%s principal!\n", + lpcfg_realm(mki_ctx->task->lp_ctx))); + return NT_STATUS_NO_MEMORY; + } + + /* Get the krbtgt from the DB */ + code = samba_kdc_fetch(mki_ctx->krb5_context, + mki_ctx->db_ctx, + principal, + SDB_F_GET_KRBTGT | SDB_F_DECRYPT, + 0, + &sentry); + krb5_free_principal(mki_ctx->krb5_context, principal); + if (code != 0) { + DEBUG(0, ("Failed to fetch krbtgt@%s principal entry!\n", + lpcfg_realm(mki_ctx->task->lp_ctx))); + return NT_STATUS_LOGON_FAILURE; + } + + /* PAC Signature */ + pac_kdc_sig.type = pac_validate.SignatureType; + + d = &pac_validate.ChecksumAndSignature.data[pac_validate.ChecksumLength]; + pac_kdc_sig.signature = + data_blob_const(d, pac_validate.SignatureLength); + + /* + * Brute force variant because MIT KRB5 doesn't provide a function like + * krb5_checksum_to_enctype(). + */ + skeys = sentry.keys; + + for (i = 0; i < skeys.len; i++) { + krb5_keyblock krbtgt_keyblock = skeys.val[i].key; + + code = check_pac_checksum(pac_chksum, + &pac_kdc_sig, + mki_ctx->krb5_context, + &krbtgt_keyblock); + if (code == 0) { + break; + } + } + + sdb_entry_free(&sentry); + + if (code != 0) { + return NT_STATUS_LOGON_FAILURE; + } + + return NT_STATUS_OK; +} + +NTSTATUS samba_setup_mit_kdc_irpc(struct task_server *task) +{ + struct samba_kdc_base_context base_ctx; + struct mit_kdc_irpc_context *mki_ctx; + NTSTATUS status; + int code; + + mki_ctx = talloc_zero(task, struct mit_kdc_irpc_context); + if (mki_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + mki_ctx->task = task; + + base_ctx.ev_ctx = task->event_ctx; + base_ctx.lp_ctx = task->lp_ctx; + + /* db-glue.h */ + status = samba_kdc_setup_db_ctx(mki_ctx, + &base_ctx, + &mki_ctx->db_ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + code = smb_krb5_init_context_basic(mki_ctx, + task->lp_ctx, + &mki_ctx->krb5_context); + if (code != 0) { + return NT_STATUS_INTERNAL_ERROR; + } + + status = IRPC_REGISTER(task->msg_ctx, + irpc, + KDC_CHECK_GENERIC_KERBEROS, + netr_samlogon_generic_logon, + mki_ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + irpc_add_name(task->msg_ctx, "kdc_server"); + + return status; +} diff --git a/source4/kdc/mit_kdc_irpc.h b/source4/kdc/mit_kdc_irpc.h new file mode 100644 index 0000000..943c76c --- /dev/null +++ b/source4/kdc/mit_kdc_irpc.h @@ -0,0 +1,20 @@ +/* + * Unix SMB/CIFS implementation. + * + * Copyright (c) 2015 Andreas Schneider <asn@samba.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +NTSTATUS samba_setup_mit_kdc_irpc(struct task_server *task); diff --git a/source4/kdc/mit_samba.c b/source4/kdc/mit_samba.c new file mode 100644 index 0000000..bfe9748 --- /dev/null +++ b/source4/kdc/mit_samba.c @@ -0,0 +1,1066 @@ +/* + MIT-Samba4 library + + Copyright (c) 2010, Simo Sorce <idra@samba.org> + Copyright (c) 2014-2015 Guenther Deschner <gd@samba.org> + Copyright (c) 2014-2016 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define TEVENT_DEPRECATED 1 + +#include "includes.h" +#include "param/param.h" +#include "dsdb/samdb/samdb.h" +#include "system/kerberos.h" +#include <com_err.h> +#include <kdb.h> +#include <kadm5/kadm_err.h> +#include "kdc/sdb.h" +#include "kdc/sdb_kdb.h" +#include "auth/kerberos/kerberos.h" +#include "auth/kerberos/pac_utils.h" +#include "kdc/samba_kdc.h" +#include "kdc/pac-glue.h" +#include "kdc/db-glue.h" +#include "auth/auth.h" +#include "kdc/kpasswd_glue.h" +#include "auth/auth_sam.h" + +#include "mit_samba.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_KERBEROS + +void mit_samba_context_free(struct mit_samba_context *ctx) +{ + /* free heimdal's krb5_context */ + if (ctx->context) { + krb5_free_context(ctx->context); + } + + /* then free everything else */ + talloc_free(ctx); +} + +/* + * Implemant a callback to log to the MIT KDC log facility + * + * http://web.mit.edu/kerberos/krb5-devel/doc/plugindev/general.html#logging-from-kdc-and-kadmind-plugin-modules + */ +static void mit_samba_debug(void *private_ptr, int msg_level, const char *msg) +{ + int is_error = errno; + + if (msg_level > 0) { + is_error = 0; + } + + com_err("", is_error, "%s", msg); +} + +int mit_samba_context_init(struct mit_samba_context **_ctx) +{ + NTSTATUS status; + struct mit_samba_context *ctx; + const char *s4_conf_file; + int ret; + struct samba_kdc_base_context base_ctx; + + ctx = talloc_zero(NULL, struct mit_samba_context); + if (!ctx) { + ret = ENOMEM; + goto done; + } + + base_ctx.ev_ctx = tevent_context_init(ctx); + if (!base_ctx.ev_ctx) { + ret = ENOMEM; + goto done; + } + tevent_loop_allow_nesting(base_ctx.ev_ctx); + base_ctx.lp_ctx = loadparm_init_global(false); + if (!base_ctx.lp_ctx) { + ret = ENOMEM; + goto done; + } + + debug_set_callback(NULL, mit_samba_debug); + + /* init s4 configuration */ + s4_conf_file = lpcfg_configfile(base_ctx.lp_ctx); + if (s4_conf_file != NULL) { + char *p = talloc_strdup(ctx, s4_conf_file); + if (p == NULL) { + ret = ENOMEM; + goto done; + } + lpcfg_load(base_ctx.lp_ctx, p); + TALLOC_FREE(p); + } else { + lpcfg_load_default(base_ctx.lp_ctx); + } + + status = samba_kdc_setup_db_ctx(ctx, &base_ctx, &ctx->db_ctx); + if (!NT_STATUS_IS_OK(status)) { + ret = EINVAL; + goto done; + } + + /* init heimdal's krb_context and log facilities */ + ret = smb_krb5_init_context_basic(ctx, + ctx->db_ctx->lp_ctx, + &ctx->context); + if (ret) { + goto done; + } + + ret = 0; + +done: + if (ret) { + mit_samba_context_free(ctx); + } else { + *_ctx = ctx; + } + return ret; +} + +static krb5_error_code ks_is_tgs_principal(struct mit_samba_context *ctx, + krb5_const_principal principal) +{ + char *p; + int eq = -1; + + p = smb_krb5_principal_get_comp_string(ctx, ctx->context, principal, 0); + + eq = krb5_princ_size(ctx->context, principal) == 2 && + (strcmp(p, KRB5_TGS_NAME) == 0); + + talloc_free(p); + + return eq; +} + +int mit_samba_generate_salt(krb5_data *salt) +{ + if (salt == NULL) { + return EINVAL; + } + + salt->length = 16; + salt->data = malloc(salt->length); + if (salt->data == NULL) { + return ENOMEM; + } + + generate_random_buffer((uint8_t *)salt->data, salt->length); + + return 0; +} + +int mit_samba_generate_random_password(krb5_data *pwd) +{ + TALLOC_CTX *tmp_ctx; + char *password; + + if (pwd == NULL) { + return EINVAL; + } + pwd->length = 24; + + tmp_ctx = talloc_named(NULL, + 0, + "mit_samba_create_principal_password context"); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + password = generate_random_password(tmp_ctx, pwd->length, pwd->length); + if (password == NULL) { + talloc_free(tmp_ctx); + return ENOMEM; + } + + pwd->data = strdup(password); + talloc_free(tmp_ctx); + if (pwd->data == NULL) { + return ENOMEM; + } + + return 0; +} + +int mit_samba_get_principal(struct mit_samba_context *ctx, + krb5_const_principal principal, + unsigned int kflags, + krb5_db_entry **_kentry) +{ + struct sdb_entry sentry = {}; + krb5_db_entry *kentry; + int ret; + uint32_t sflags = 0; + krb5_principal referral_principal = NULL; + + kentry = calloc(1, sizeof(krb5_db_entry)); + if (kentry == NULL) { + return ENOMEM; + } + +#if KRB5_KDB_API_VERSION >= 10 + /* + * The MIT KDC code that wants the canonical name in all lookups, and + * takes care to canonicalize only when appropriate. + */ + sflags |= SDB_F_FORCE_CANON; +#endif + +#if KRB5_KDB_DAL_MAJOR_VERSION >= 9 + if (kflags & KRB5_KDB_FLAG_REFERRAL_OK) { + sflags |= SDB_F_CANON; + } + + if (kflags & KRB5_KDB_FLAG_CLIENT) { + sflags |= SDB_F_GET_CLIENT; + sflags |= SDB_F_FOR_AS_REQ; + } else { + int equal = smb_krb5_principal_is_tgs(ctx->context, principal); + if (equal == -1) { + return ENOMEM; + } + + if (equal) { + sflags |= SDB_F_GET_KRBTGT; + } else { + sflags |= SDB_F_GET_SERVER; + sflags |= SDB_F_FOR_TGS_REQ; + } + } +#else /* KRB5_KDB_DAL_MAJOR_VERSION < 9 */ + if (kflags & KRB5_KDB_FLAG_CANONICALIZE) { + sflags |= SDB_F_CANON; + } + if (kflags & (KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY | + KRB5_KDB_FLAG_INCLUDE_PAC)) { + /* + * KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY is equal to + * SDB_F_FOR_AS_REQ + * + * We use ANY to also allow AS_REQ for service principal names + * This is supported by Windows. + */ + sflags |= SDB_F_GET_ANY|SDB_F_FOR_AS_REQ; + } else { + int equal = smb_krb5_principal_is_tgs(ctx->context, principal); + if (equal == -1) { + return ENOMEM; + } + + if (equal) { + sflags |= SDB_F_GET_KRBTGT; + } else { + sflags |= SDB_F_GET_SERVER|SDB_F_FOR_TGS_REQ; + } + } +#endif /* KRB5_KDB_DAL_MAJOR_VERSION */ + + /* always set this or the created_by data will not be populated by samba's + * backend and we will fail to parse the entry later */ + sflags |= SDB_F_ADMIN_DATA; + + +fetch_referral_principal: + ret = samba_kdc_fetch(ctx->context, ctx->db_ctx, + principal, sflags, 0, &sentry); + switch (ret) { + case 0: + break; + case SDB_ERR_NOENTRY: + ret = KRB5_KDB_NOENTRY; + goto done; + case SDB_ERR_WRONG_REALM: { + char *dest_realm = NULL; + const char *our_realm = lpcfg_realm(ctx->db_ctx->lp_ctx); + + if (sflags & SDB_F_FOR_AS_REQ) { + /* + * If this is a request for a TGT, we are done. The KDC + * will return the correct error to the client. + */ + ret = 0; + break; + } + + if (referral_principal != NULL) { + sdb_entry_free(&sentry); + ret = KRB5_KDB_NOENTRY; + goto done; + } + + /* + * We get a TGS request + * + * cifs/dc7.SAMBA2008R2.EXAMPLE.COM@ADDOM.SAMBA.EXAMPLE.COM + * + * to our DC for the realm + * + * ADDOM.SAMBA.EXAMPLE.COM + * + * We look up if we have and entry in the database and get an + * entry with the pricipal: + * + * cifs/dc7.SAMBA2008R2.EXAMPLE.COM@SAMBA2008R2.EXAMPLE.COM + * + * and the error: SDB_ERR_WRONG_REALM. + * + * In the case of a TGS-REQ we need to return a referral ticket + * fo the next trust hop to the client. This ticket will have + * the following principal: + * + * krbtgt/SAMBA2008R2.EXAMPLE.COM@ADDOM.SAMBA.EXAMPLE.COM + * + * We just redo the lookup in the database with the referral + * principal and return success. + */ + dest_realm = smb_krb5_principal_get_realm( + ctx, ctx->context, sentry.principal); + sdb_entry_free(&sentry); + if (dest_realm == NULL) { + ret = KRB5_KDB_NOENTRY; + goto done; + } + + ret = smb_krb5_make_principal(ctx->context, + &referral_principal, + our_realm, + KRB5_TGS_NAME, + dest_realm, + NULL); + TALLOC_FREE(dest_realm); + if (ret != 0) { + goto done; + } + + principal = referral_principal; + goto fetch_referral_principal; + } + case SDB_ERR_NOT_FOUND_HERE: + /* FIXME: RODC support */ + default: + goto done; + } + + ret = sdb_entry_to_krb5_db_entry(ctx->context, &sentry, kentry); + + sdb_entry_free(&sentry); + +done: + krb5_free_principal(ctx->context, referral_principal); + referral_principal = NULL; + + if (ret) { + free(kentry); + } else { + *_kentry = kentry; + } + return ret; +} + +int mit_samba_get_firstkey(struct mit_samba_context *ctx, + krb5_db_entry **_kentry) +{ + struct sdb_entry sentry = {}; + krb5_db_entry *kentry; + int ret; + + kentry = malloc(sizeof(krb5_db_entry)); + if (kentry == NULL) { + return ENOMEM; + } + + ret = samba_kdc_firstkey(ctx->context, ctx->db_ctx, &sentry); + switch (ret) { + case 0: + break; + case SDB_ERR_NOENTRY: + free(kentry); + return KRB5_KDB_NOENTRY; + case SDB_ERR_NOT_FOUND_HERE: + /* FIXME: RODC support */ + default: + free(kentry); + return ret; + } + + ret = sdb_entry_to_krb5_db_entry(ctx->context, &sentry, kentry); + + sdb_entry_free(&sentry); + + if (ret) { + free(kentry); + } else { + *_kentry = kentry; + } + return ret; +} + +int mit_samba_get_nextkey(struct mit_samba_context *ctx, + krb5_db_entry **_kentry) +{ + struct sdb_entry sentry = {}; + krb5_db_entry *kentry; + int ret; + + kentry = malloc(sizeof(krb5_db_entry)); + if (kentry == NULL) { + return ENOMEM; + } + + ret = samba_kdc_nextkey(ctx->context, ctx->db_ctx, &sentry); + switch (ret) { + case 0: + break; + case SDB_ERR_NOENTRY: + free(kentry); + return KRB5_KDB_NOENTRY; + case SDB_ERR_NOT_FOUND_HERE: + /* FIXME: RODC support */ + default: + free(kentry); + return ret; + } + + ret = sdb_entry_to_krb5_db_entry(ctx->context, &sentry, kentry); + + sdb_entry_free(&sentry); + + if (ret) { + free(kentry); + } else { + *_kentry = kentry; + } + return ret; +} + +int mit_samba_get_pac(struct mit_samba_context *smb_ctx, + krb5_context context, + uint32_t flags, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_keyblock *replaced_reply_key, + krb5_pac *pac) +{ + TALLOC_CTX *tmp_ctx; + DATA_BLOB *logon_info_blob = NULL; + DATA_BLOB *upn_dns_info_blob = NULL; + DATA_BLOB *cred_ndr = NULL; + DATA_BLOB **cred_ndr_ptr = NULL; + DATA_BLOB cred_blob = data_blob_null; + DATA_BLOB *pcred_blob = NULL; + DATA_BLOB *pac_attrs_blob = NULL; + DATA_BLOB *requester_sid_blob = NULL; + NTSTATUS nt_status; + krb5_error_code code; + struct samba_kdc_entry *skdc_entry; + bool is_krbtgt; + enum samba_asserted_identity asserted_identity = + (flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION) ? + SAMBA_ASSERTED_IDENTITY_SERVICE : + SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY; + + skdc_entry = talloc_get_type_abort(client->e_data, + struct samba_kdc_entry); + + tmp_ctx = talloc_named(smb_ctx, + 0, + "mit_samba_get_pac_data_blobs context"); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Check if we have a PREAUTH key */ + if (replaced_reply_key != NULL) { + cred_ndr_ptr = &cred_ndr; + } + + is_krbtgt = ks_is_tgs_principal(smb_ctx, server->princ); + + nt_status = samba_kdc_get_pac_blobs(tmp_ctx, + skdc_entry, + asserted_identity, + &logon_info_blob, + cred_ndr_ptr, + &upn_dns_info_blob, + is_krbtgt ? &pac_attrs_blob : NULL, + PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY, + is_krbtgt ? &requester_sid_blob : NULL); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(tmp_ctx); + if (NT_STATUS_EQUAL(nt_status, + NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + return ENOENT; + } + return EINVAL; + } + + if (replaced_reply_key != NULL && cred_ndr != NULL) { + code = samba_kdc_encrypt_pac_credentials(context, + replaced_reply_key, + cred_ndr, + tmp_ctx, + &cred_blob); + if (code != 0) { + talloc_free(tmp_ctx); + return code; + } + pcred_blob = &cred_blob; + } + + code = samba_make_krb5_pac(context, + logon_info_blob, + pcred_blob, + upn_dns_info_blob, + pac_attrs_blob, + requester_sid_blob, + NULL, + *pac); + + talloc_free(tmp_ctx); + return code; +} + +#if KRB5_KDB_DAL_MAJOR_VERSION < 9 +krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx, + krb5_context context, + int kdc_flags, + krb5_const_principal client_principal, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_db_entry *krbtgt, + krb5_keyblock *krbtgt_keyblock, + krb5_pac *pac) +{ + TALLOC_CTX *tmp_ctx; + krb5_error_code code; + struct samba_kdc_entry *client_skdc_entry = NULL; + struct samba_kdc_entry *krbtgt_skdc_entry = NULL; + struct samba_kdc_entry *server_skdc_entry = NULL; + krb5_principal delegated_proxy_principal = NULL; + krb5_pac new_pac = NULL; + bool is_in_db = false; + bool is_untrusted = false; + uint32_t flags = SAMBA_KDC_FLAG_SKIP_PAC_BUFFER; + + /* Create a memory context early so code can use talloc_stackframe() */ + tmp_ctx = talloc_named(ctx, 0, "mit_samba_reget_pac context"); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + if (client != NULL) { + client_skdc_entry = + talloc_get_type_abort(client->e_data, + struct samba_kdc_entry); + } + + if (server == NULL) { + code = EINVAL; + goto done; + } + + server_skdc_entry = + talloc_get_type_abort(server->e_data, + struct samba_kdc_entry); + + if (krbtgt == NULL) { + code = EINVAL; + goto done; + } + krbtgt_skdc_entry = + talloc_get_type_abort(krbtgt->e_data, + struct samba_kdc_entry); + + code = samba_krbtgt_is_in_db(krbtgt_skdc_entry, + &is_in_db, + &is_untrusted); + if (code != 0) { + goto done; + } + + if (is_untrusted) { + flags |= SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED; + } + + if (is_in_db) { + flags |= SAMBA_KDC_FLAG_KRBTGT_IN_DB; + + } + + if (kdc_flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION) { + flags |= SAMBA_KDC_FLAG_PROTOCOL_TRANSITION; + } + + if (kdc_flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) { + flags |= SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION; + delegated_proxy_principal = discard_const(client_principal); + } + + /* Build an updated PAC */ + code = krb5_pac_init(context, &new_pac); + if (code != 0) { + goto done; + } + + code = samba_kdc_update_pac(tmp_ctx, + context, + krbtgt_skdc_entry->kdc_db_ctx->samdb, + flags, + client_skdc_entry, + server->princ, + server_skdc_entry, + krbtgt_skdc_entry, + delegated_proxy_principal, + *pac, + new_pac); + if (code != 0) { + krb5_pac_free(context, new_pac); + if (code == ENODATA) { + krb5_pac_free(context, *pac); + *pac = NULL; + code = 0; + } + goto done; + } + + /* We now replace the pac */ + krb5_pac_free(context, *pac); + *pac = new_pac; + +done: + talloc_free(tmp_ctx); + return code; +} +#else +krb5_error_code mit_samba_update_pac(struct mit_samba_context *ctx, + krb5_context context, + int kdc_flags, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_db_entry *krbtgt, + krb5_pac old_pac, + krb5_pac new_pac) +{ + TALLOC_CTX *tmp_ctx = NULL; + krb5_error_code code; + struct samba_kdc_entry *client_skdc_entry = NULL; + struct samba_kdc_entry *server_skdc_entry = NULL; + struct samba_kdc_entry *krbtgt_skdc_entry = NULL; + bool is_in_db = false; + bool is_untrusted = false; + uint32_t flags = SAMBA_KDC_FLAG_SKIP_PAC_BUFFER; + + /* Create a memory context early so code can use talloc_stackframe() */ + tmp_ctx = talloc_named(ctx, 0, "mit_samba_update_pac context"); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + if (client != NULL) { + client_skdc_entry = + talloc_get_type_abort(client->e_data, + struct samba_kdc_entry); + } + + if (krbtgt == NULL) { + code = EINVAL; + goto done; + } + krbtgt_skdc_entry = + talloc_get_type_abort(krbtgt->e_data, + struct samba_kdc_entry); + + server_skdc_entry = + talloc_get_type_abort(server->e_data, + struct samba_kdc_entry); + + /* + * If the krbtgt was generated by an RODC, and we are not that + * RODC, then we need to regenerate the PAC - we can't trust + * it, and confirm that the RODC was permitted to print this ticket + * + * Because of the samba_kdc_validate_pac_blob() step we can be + * sure that the record in 'client' or 'server' matches the SID in the + * original PAC. + */ + code = samba_krbtgt_is_in_db(krbtgt_skdc_entry, + &is_in_db, + &is_untrusted); + if (code != 0) { + goto done; + } + + if (is_untrusted) { + flags |= SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED; + } + + if (is_in_db) { + flags |= SAMBA_KDC_FLAG_KRBTGT_IN_DB; + + } + + if (kdc_flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) { + flags |= SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION; + } + + code = samba_kdc_update_pac(tmp_ctx, + context, + krbtgt_skdc_entry->kdc_db_ctx->samdb, + flags, + client_skdc_entry, + server->princ, + server_skdc_entry, + krbtgt_skdc_entry, + NULL, + old_pac, + new_pac); + if (code != 0) { + if (code == ENODATA) { + /* + * We can't tell the KDC to not issue a PAC. It will + * just return the newly allocated empty PAC. + */ + code = 0; + } + } + +done: + talloc_free(tmp_ctx); + return code; +} +#endif + +/* provide header, function is exported but there are no public headers */ + +krb5_error_code encode_krb5_padata_sequence(krb5_pa_data *const *rep, krb5_data **code); + +/* this function allocates 'data' using malloc. + * The caller is responsible for freeing it */ +static void samba_kdc_build_edata_reply(NTSTATUS nt_status, DATA_BLOB *e_data) +{ + krb5_error_code ret = 0; + krb5_pa_data pa, *ppa[2]; + krb5_data *d = NULL; + + if (!e_data) + return; + + e_data->data = NULL; + e_data->length = 0; + + pa.magic = KV5M_PA_DATA; + pa.pa_type = KRB5_PADATA_PW_SALT; + pa.length = 12; + pa.contents = malloc(pa.length); + if (!pa.contents) { + return; + } + + SIVAL(pa.contents, 0, NT_STATUS_V(nt_status)); + SIVAL(pa.contents, 4, 0); + SIVAL(pa.contents, 8, 1); + + ppa[0] = &pa; + ppa[1] = NULL; + + ret = encode_krb5_padata_sequence(ppa, &d); + free(pa.contents); + if (ret) { + return; + } + + e_data->data = (uint8_t *)d->data; + e_data->length = d->length; + + /* free d, not d->data - gd */ + free(d); + + return; +} + +int mit_samba_check_client_access(struct mit_samba_context *ctx, + krb5_db_entry *client, + const char *client_name, + krb5_db_entry *server, + const char *server_name, + const char *netbios_name, + bool password_change, + DATA_BLOB *e_data) +{ + struct samba_kdc_entry *skdc_entry; + NTSTATUS nt_status; + + skdc_entry = talloc_get_type(client->e_data, struct samba_kdc_entry); + + nt_status = samba_kdc_check_client_access(skdc_entry, + client_name, + netbios_name, + password_change); + + if (!NT_STATUS_IS_OK(nt_status)) { + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MEMORY)) { + return ENOMEM; + } + + samba_kdc_build_edata_reply(nt_status, e_data); + + return samba_kdc_map_policy_err(nt_status); + } + + return 0; +} + +int mit_samba_check_s4u2proxy(struct mit_samba_context *ctx, + const krb5_db_entry *server, + krb5_const_principal target_principal) +{ +#if KRB5_KDB_DAL_MAJOR_VERSION < 9 + return KRB5KDC_ERR_BADOPTION; +#else + struct samba_kdc_entry *server_skdc_entry = + talloc_get_type_abort(server->e_data, struct samba_kdc_entry); + krb5_error_code code; + + code = samba_kdc_check_s4u2proxy(ctx->context, + ctx->db_ctx, + server_skdc_entry, + target_principal); + + return code; +#endif +} + +krb5_error_code mit_samba_check_allowed_to_delegate_from( + struct mit_samba_context *ctx, + krb5_const_principal client_principal, + krb5_const_principal server_principal, + krb5_pac header_pac, + const krb5_db_entry *proxy) +{ +#if KRB5_KDB_DAL_MAJOR_VERSION < 8 + return KRB5KDC_ERR_POLICY; +#else + struct samba_kdc_entry *proxy_skdc_entry = + talloc_get_type_abort(proxy->e_data, struct samba_kdc_entry); + krb5_error_code code; + + code = samba_kdc_check_s4u2proxy_rbcd(ctx->context, + ctx->db_ctx, + client_principal, + server_principal, + header_pac, + proxy_skdc_entry); + + return code; +#endif +} + +static krb5_error_code mit_samba_change_pwd_error(krb5_context context, + NTSTATUS result, + enum samPwdChangeReason reject_reason, + struct samr_DomInfo1 *dominfo) +{ + krb5_error_code code = KADM5_PASS_Q_GENERIC; + + if (NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_USER)) { + code = KADM5_BAD_PRINCIPAL; + krb5_set_error_message(context, + code, + "No such user when changing password"); + } + if (NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)) { + code = KADM5_PASS_Q_GENERIC; + krb5_set_error_message(context, + code, + "Not permitted to change password"); + } + if (NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_RESTRICTION) && + dominfo != NULL) { + switch (reject_reason) { + case SAM_PWD_CHANGE_PASSWORD_TOO_SHORT: + code = KADM5_PASS_Q_TOOSHORT; + krb5_set_error_message(context, + code, + "Password too short, password " + "must be at least %d characters " + "long.", + dominfo->min_password_length); + break; + case SAM_PWD_CHANGE_NOT_COMPLEX: + code = KADM5_PASS_Q_DICT; + krb5_set_error_message(context, + code, + "Password does not meet " + "complexity requirements"); + break; + case SAM_PWD_CHANGE_PWD_IN_HISTORY: + code = KADM5_PASS_TOOSOON; + krb5_set_error_message(context, + code, + "Password is already in password " + "history. New password must not " + "match any of your %d previous " + "passwords.", + dominfo->password_history_length); + break; + default: + code = KADM5_PASS_Q_GENERIC; + krb5_set_error_message(context, + code, + "Password change rejected, " + "password changes may not be " + "permitted on this account, or " + "the minimum password age may " + "not have elapsed."); + break; + } + } + + return code; +} + +int mit_samba_kpasswd_change_password(struct mit_samba_context *ctx, + char *pwd, + krb5_db_entry *db_entry) +{ + NTSTATUS status; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + TALLOC_CTX *tmp_ctx; + DATA_BLOB password; + enum samPwdChangeReason reject_reason; + struct samr_DomInfo1 *dominfo; + const char *error_string = NULL; + struct auth_user_info_dc *user_info_dc; + struct samba_kdc_entry *p = + talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry); + krb5_error_code code = 0; + +#ifdef DEBUG_PASSWORD + DEBUG(1,("mit_samba_kpasswd_change_password called with: %s\n", pwd)); +#endif + + tmp_ctx = talloc_named(ctx, 0, "mit_samba_kpasswd_change_password"); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + status = samba_kdc_get_user_info_from_db(p, + p->msg, + &user_info_dc); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("samba_kdc_get_user_info_from_db failed: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return EINVAL; + } + + status = auth_generate_session_info(tmp_ctx, + ctx->db_ctx->lp_ctx, + ctx->db_ctx->samdb, + user_info_dc, + 0, /* session_info_flags */ + &ctx->session_info); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("auth_generate_session_info failed: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return EINVAL; + } + + /* password is expected as UTF16 */ + + if (!convert_string_talloc(tmp_ctx, CH_UTF8, CH_UTF16, + pwd, strlen(pwd), + &password.data, &password.length)) { + DEBUG(1,("convert_string_talloc failed\n")); + talloc_free(tmp_ctx); + return EINVAL; + } + + status = samdb_kpasswd_change_password(tmp_ctx, + ctx->db_ctx->lp_ctx, + ctx->db_ctx->ev_ctx, + ctx->session_info, + &password, + &reject_reason, + &dominfo, + &error_string, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("samdb_kpasswd_change_password failed: %s\n", + nt_errstr(status))); + code = KADM5_PASS_Q_GENERIC; + krb5_set_error_message(ctx->context, code, "%s", error_string); + goto out; + } + + if (!NT_STATUS_IS_OK(result)) { + code = mit_samba_change_pwd_error(ctx->context, + result, + reject_reason, + dominfo); + } + +out: + talloc_free(tmp_ctx); + + return code; +} + +void mit_samba_zero_bad_password_count(krb5_db_entry *db_entry) +{ + /* struct netr_SendToSamBase *send_to_sam = NULL; */ + struct samba_kdc_entry *p = + talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry); + struct ldb_dn *domain_dn; + + domain_dn = ldb_get_default_basedn(p->kdc_db_ctx->samdb); + + authsam_logon_success_accounting(p->kdc_db_ctx->samdb, + p->msg, + domain_dn, + true, + NULL, NULL); + /* TODO: RODC support */ +} + + +void mit_samba_update_bad_password_count(krb5_db_entry *db_entry) +{ + struct samba_kdc_entry *p = + talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry); + + authsam_update_bad_pwd_count(p->kdc_db_ctx->samdb, + p->msg, + ldb_get_default_basedn(p->kdc_db_ctx->samdb)); +} + +bool mit_samba_princ_needs_pac(krb5_db_entry *db_entry) +{ + struct samba_kdc_entry *skdc_entry = + talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry); + + return samba_princ_needs_pac(skdc_entry); +} diff --git a/source4/kdc/mit_samba.h b/source4/kdc/mit_samba.h new file mode 100644 index 0000000..0357408 --- /dev/null +++ b/source4/kdc/mit_samba.h @@ -0,0 +1,106 @@ +/* + MIT-Samba4 library + + Copyright (c) 2010, Simo Sorce <idra@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _MIT_SAMBA_H +#define _MIT_SAMBA_H + +struct mit_samba_context { + struct auth_session_info *session_info; + + /* for compat with hdb plugin common code */ + krb5_context context; + struct samba_kdc_db_context *db_ctx; +}; + +int mit_samba_context_init(struct mit_samba_context **_ctx); + +void mit_samba_context_free(struct mit_samba_context *ctx); + +int mit_samba_generate_salt(krb5_data *salt); + +int mit_samba_generate_random_password(krb5_data *pwd); + +int mit_samba_get_principal(struct mit_samba_context *ctx, + krb5_const_principal principal, + unsigned int kflags, + krb5_db_entry **_kentry); + +int mit_samba_get_firstkey(struct mit_samba_context *ctx, + krb5_db_entry **_kentry); + +int mit_samba_get_nextkey(struct mit_samba_context *ctx, + krb5_db_entry **_kentry); + +int mit_samba_get_pac(struct mit_samba_context *smb_ctx, + krb5_context context, + uint32_t flags, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_keyblock *replaced_reply_key, + krb5_pac *pac); + +krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx, + krb5_context context, + int flags, + krb5_const_principal client_principal, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_db_entry *krbtgt, + krb5_keyblock *krbtgt_keyblock, + krb5_pac *pac); + +krb5_error_code mit_samba_update_pac(struct mit_samba_context *ctx, + krb5_context context, + int flags, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_db_entry *signing_krbtgt, + krb5_pac old_pac, + krb5_pac new_pac); + +int mit_samba_check_client_access(struct mit_samba_context *ctx, + krb5_db_entry *client, + const char *client_name, + krb5_db_entry *server, + const char *server_name, + const char *netbios_name, + bool password_change, + DATA_BLOB *e_data); + +int mit_samba_check_s4u2proxy(struct mit_samba_context *ctx, + const krb5_db_entry *server, + krb5_const_principal target_principal); +krb5_error_code mit_samba_check_allowed_to_delegate_from( + struct mit_samba_context *ctx, + krb5_const_principal client, + krb5_const_principal server, + krb5_pac header_pac, + const krb5_db_entry *proxy); + +int mit_samba_kpasswd_change_password(struct mit_samba_context *ctx, + char *pwd, + krb5_db_entry *db_entry); + +void mit_samba_zero_bad_password_count(krb5_db_entry *db_entry); + +void mit_samba_update_bad_password_count(krb5_db_entry *db_entry); + +bool mit_samba_princ_needs_pac(krb5_db_entry *db_entry); + +#endif /* _MIT_SAMBA_H */ diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c new file mode 100644 index 0000000..6692619 --- /dev/null +++ b/source4/kdc/pac-glue.c @@ -0,0 +1,2006 @@ +/* + Unix SMB/CIFS implementation. + + PAC Glue between Samba and the KDC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009 + Copyright (C) Simo Sorce <idra@samba.org> 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "lib/replace/replace.h" +#include "lib/replace/system/kerberos.h" +#include "lib/util/debug.h" +#include "lib/util/samba_util.h" +#include "lib/util/talloc_stack.h" + +#include "auth/auth_sam_reply.h" +#include "auth/kerberos/kerberos.h" +#include "auth/kerberos/pac_utils.h" +#include "libcli/security/security.h" +#include "libds/common/flags.h" +#include "librpc/gen_ndr/ndr_krb5pac.h" +#include "param/param.h" +#include "source4/auth/auth.h" +#include "source4/dsdb/common/util.h" +#include "source4/dsdb/samdb/samdb.h" +#include "source4/kdc/samba_kdc.h" +#include "source4/kdc/pac-glue.h" + +#include <ldb.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_KERBEROS + +static +NTSTATUS samba_get_logon_info_pac_blob(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *info, + DATA_BLOB *pac_data, + DATA_BLOB *requester_sid_blob) +{ + struct netr_SamInfo3 *info3; + union PAC_INFO pac_info; + enum ndr_err_code ndr_err; + NTSTATUS nt_status; + + ZERO_STRUCT(pac_info); + + *pac_data = data_blob_null; + if (requester_sid_blob != NULL) { + *requester_sid_blob = data_blob_null; + } + + nt_status = auth_convert_user_info_dc_saminfo3(mem_ctx, info, &info3); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(1, ("Getting Samba info failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + + pac_info.logon_info.info = talloc_zero(mem_ctx, struct PAC_LOGON_INFO); + if (!pac_info.logon_info.info) { + return NT_STATUS_NO_MEMORY; + } + + pac_info.logon_info.info->info3 = *info3; + + ndr_err = ndr_push_union_blob(pac_data, mem_ctx, &pac_info, + PAC_TYPE_LOGON_INFO, + (ndr_push_flags_fn_t)ndr_push_PAC_INFO); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(1, ("PAC_LOGON_INFO (presig) push failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + + if (requester_sid_blob != NULL && info->num_sids > 0) { + union PAC_INFO pac_requester_sid; + + ZERO_STRUCT(pac_requester_sid); + + pac_requester_sid.requester_sid.sid = info->sids[0]; + + ndr_err = ndr_push_union_blob(requester_sid_blob, mem_ctx, + &pac_requester_sid, + PAC_TYPE_REQUESTER_SID, + (ndr_push_flags_fn_t)ndr_push_PAC_INFO); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(1, ("PAC_REQUESTER_SID (presig) push failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + } + + return NT_STATUS_OK; +} + +static +NTSTATUS samba_get_upn_info_pac_blob(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *info, + DATA_BLOB *upn_data) +{ + union PAC_INFO pac_upn; + enum ndr_err_code ndr_err; + NTSTATUS nt_status; + bool ok; + + ZERO_STRUCT(pac_upn); + + *upn_data = data_blob_null; + + pac_upn.upn_dns_info.upn_name = info->info->user_principal_name; + pac_upn.upn_dns_info.dns_domain_name = strupper_talloc(mem_ctx, + info->info->dns_domain_name); + if (pac_upn.upn_dns_info.dns_domain_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + if (info->info->user_principal_constructed) { + pac_upn.upn_dns_info.flags |= PAC_UPN_DNS_FLAG_CONSTRUCTED; + } + + pac_upn.upn_dns_info.flags |= PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID; + + pac_upn.upn_dns_info.ex.sam_name_and_sid.samaccountname + = info->info->account_name; + + pac_upn.upn_dns_info.ex.sam_name_and_sid.objectsid + = &info->sids[0]; + + ndr_err = ndr_push_union_blob(upn_data, mem_ctx, &pac_upn, + PAC_TYPE_UPN_DNS_INFO, + (ndr_push_flags_fn_t)ndr_push_PAC_INFO); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(1, ("PAC UPN_DNS_INFO (presig) push failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + + ok = data_blob_pad(mem_ctx, upn_data, 8); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static +NTSTATUS samba_get_pac_attrs_blob(TALLOC_CTX *mem_ctx, + uint64_t pac_attributes, + DATA_BLOB *pac_attrs_data) +{ + union PAC_INFO pac_attrs; + enum ndr_err_code ndr_err; + NTSTATUS nt_status; + + ZERO_STRUCT(pac_attrs); + + *pac_attrs_data = data_blob_null; + + /* Set the length of the flags in bits. */ + pac_attrs.attributes_info.flags_length = 2; + pac_attrs.attributes_info.flags = pac_attributes; + + ndr_err = ndr_push_union_blob(pac_attrs_data, mem_ctx, &pac_attrs, + PAC_TYPE_ATTRIBUTES_INFO, + (ndr_push_flags_fn_t)ndr_push_PAC_INFO); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(1, ("PAC ATTRIBUTES_INFO (presig) push failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + + return NT_STATUS_OK; +} + +static +NTSTATUS samba_get_cred_info_ndr_blob(TALLOC_CTX *mem_ctx, + const struct ldb_message *msg, + DATA_BLOB *cred_blob) +{ + enum ndr_err_code ndr_err; + NTSTATUS nt_status; + struct samr_Password *lm_hash = NULL; + struct samr_Password *nt_hash = NULL; + struct PAC_CREDENTIAL_NTLM_SECPKG ntlm_secpkg = { + .version = 0, + }; + DATA_BLOB ntlm_blob = data_blob_null; + struct PAC_CREDENTIAL_SUPPLEMENTAL_SECPKG secpkgs[1] = {{ + .credential_size = 0, + }}; + struct PAC_CREDENTIAL_DATA cred_data = { + .credential_count = 0, + }; + struct PAC_CREDENTIAL_DATA_NDR cred_ndr; + + ZERO_STRUCT(cred_ndr); + + *cred_blob = data_blob_null; + + lm_hash = samdb_result_hash(mem_ctx, msg, "dBCSPwd"); + if (lm_hash != NULL) { + bool zero = all_zero(lm_hash->hash, 16); + if (zero) { + lm_hash = NULL; + } + } + if (lm_hash != NULL) { + DEBUG(5, ("Passing LM password hash through credentials set\n")); + ntlm_secpkg.flags |= PAC_CREDENTIAL_NTLM_HAS_LM_HASH; + ntlm_secpkg.lm_password = *lm_hash; + ZERO_STRUCTP(lm_hash); + TALLOC_FREE(lm_hash); + } + + nt_hash = samdb_result_hash(mem_ctx, msg, "unicodePwd"); + if (nt_hash != NULL) { + bool zero = all_zero(nt_hash->hash, 16); + if (zero) { + nt_hash = NULL; + } + } + if (nt_hash != NULL) { + DEBUG(5, ("Passing LM password hash through credentials set\n")); + ntlm_secpkg.flags |= PAC_CREDENTIAL_NTLM_HAS_NT_HASH; + ntlm_secpkg.nt_password = *nt_hash; + ZERO_STRUCTP(nt_hash); + TALLOC_FREE(nt_hash); + } + + if (ntlm_secpkg.flags == 0) { + return NT_STATUS_OK; + } + +#ifdef DEBUG_PASSWORD + if (DEBUGLVL(11)) { + NDR_PRINT_DEBUG(PAC_CREDENTIAL_NTLM_SECPKG, &ntlm_secpkg); + } +#endif + + ndr_err = ndr_push_struct_blob(&ntlm_blob, mem_ctx, &ntlm_secpkg, + (ndr_push_flags_fn_t)ndr_push_PAC_CREDENTIAL_NTLM_SECPKG); + ZERO_STRUCT(ntlm_secpkg); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(1, ("PAC_CREDENTIAL_NTLM_SECPKG (presig) push failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + + DEBUG(10, ("NTLM credential BLOB (len %zu) for user\n", + ntlm_blob.length)); + dump_data_pw("PAC_CREDENTIAL_NTLM_SECPKG", + ntlm_blob.data, ntlm_blob.length); + + secpkgs[0].package_name.string = discard_const_p(char, "NTLM"); + secpkgs[0].credential_size = ntlm_blob.length; + secpkgs[0].credential = ntlm_blob.data; + + cred_data.credential_count = ARRAY_SIZE(secpkgs); + cred_data.credentials = secpkgs; + +#ifdef DEBUG_PASSWORD + if (DEBUGLVL(11)) { + NDR_PRINT_DEBUG(PAC_CREDENTIAL_DATA, &cred_data); + } +#endif + + cred_ndr.ctr.data = &cred_data; + +#ifdef DEBUG_PASSWORD + if (DEBUGLVL(11)) { + NDR_PRINT_DEBUG(PAC_CREDENTIAL_DATA_NDR, &cred_ndr); + } +#endif + + ndr_err = ndr_push_struct_blob(cred_blob, mem_ctx, &cred_ndr, + (ndr_push_flags_fn_t)ndr_push_PAC_CREDENTIAL_DATA_NDR); + data_blob_clear(&ntlm_blob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(1, ("PAC_CREDENTIAL_DATA_NDR (presig) push failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + + DEBUG(10, ("Created credential BLOB (len %zu) for user\n", + cred_blob->length)); + dump_data_pw("PAC_CREDENTIAL_DATA_NDR", + cred_blob->data, cred_blob->length); + + return NT_STATUS_OK; +} + +#ifdef SAMBA4_USES_HEIMDAL +krb5_error_code samba_kdc_encrypt_pac_credentials(krb5_context context, + const krb5_keyblock *pkreplykey, + const DATA_BLOB *cred_ndr_blob, + TALLOC_CTX *mem_ctx, + DATA_BLOB *cred_info_blob) +{ + krb5_crypto cred_crypto; + krb5_enctype cred_enctype; + krb5_data cred_ndr_crypt; + struct PAC_CREDENTIAL_INFO pac_cred_info = { .version = 0, }; + krb5_error_code ret; + const char *krb5err; + enum ndr_err_code ndr_err; + NTSTATUS nt_status; + + *cred_info_blob = data_blob_null; + + ret = krb5_crypto_init(context, pkreplykey, ETYPE_NULL, + &cred_crypto); + if (ret != 0) { + krb5err = krb5_get_error_message(context, ret); + DEBUG(1, ("Failed initializing cred data crypto: %s\n", krb5err)); + krb5_free_error_message(context, krb5err); + return ret; + } + + ret = krb5_crypto_getenctype(context, cred_crypto, &cred_enctype); + if (ret != 0) { + DEBUG(1, ("Failed getting crypto type for key\n")); + krb5_crypto_destroy(context, cred_crypto); + return ret; + } + + DEBUG(10, ("Plain cred_ndr_blob (len %zu)\n", + cred_ndr_blob->length)); + dump_data_pw("PAC_CREDENTIAL_DATA_NDR", + cred_ndr_blob->data, cred_ndr_blob->length); + + ret = krb5_encrypt(context, cred_crypto, + KRB5_KU_OTHER_ENCRYPTED, + cred_ndr_blob->data, cred_ndr_blob->length, + &cred_ndr_crypt); + krb5_crypto_destroy(context, cred_crypto); + if (ret != 0) { + krb5err = krb5_get_error_message(context, ret); + DEBUG(1, ("Failed crypt of cred data: %s\n", krb5err)); + krb5_free_error_message(context, krb5err); + return ret; + } + + pac_cred_info.encryption_type = cred_enctype; + pac_cred_info.encrypted_data.length = cred_ndr_crypt.length; + pac_cred_info.encrypted_data.data = (uint8_t *)cred_ndr_crypt.data; + + if (DEBUGLVL(10)) { + NDR_PRINT_DEBUG(PAC_CREDENTIAL_INFO, &pac_cred_info); + } + + ndr_err = ndr_push_struct_blob(cred_info_blob, mem_ctx, &pac_cred_info, + (ndr_push_flags_fn_t)ndr_push_PAC_CREDENTIAL_INFO); + krb5_data_free(&cred_ndr_crypt); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(1, ("PAC_CREDENTIAL_INFO (presig) push failed: %s\n", + nt_errstr(nt_status))); + return KRB5KDC_ERR_SVC_UNAVAILABLE; + } + + DEBUG(10, ("Encrypted credential BLOB (len %zu) with alg %d\n", + cred_info_blob->length, (int)pac_cred_info.encryption_type)); + dump_data_pw("PAC_CREDENTIAL_INFO", + cred_info_blob->data, cred_info_blob->length); + + return 0; +} +#else /* SAMBA4_USES_HEIMDAL */ +krb5_error_code samba_kdc_encrypt_pac_credentials(krb5_context context, + const krb5_keyblock *pkreplykey, + const DATA_BLOB *cred_ndr_blob, + TALLOC_CTX *mem_ctx, + DATA_BLOB *cred_info_blob) +{ + krb5_key cred_key; + krb5_enctype cred_enctype; + struct PAC_CREDENTIAL_INFO pac_cred_info = { .version = 0, }; + krb5_error_code code; + const char *krb5err; + enum ndr_err_code ndr_err; + NTSTATUS nt_status; + krb5_data cred_ndr_data; + krb5_enc_data cred_ndr_crypt; + size_t enc_len = 0; + + *cred_info_blob = data_blob_null; + + code = krb5_k_create_key(context, + pkreplykey, + &cred_key); + if (code != 0) { + krb5err = krb5_get_error_message(context, code); + DEBUG(1, ("Failed initializing cred data crypto: %s\n", krb5err)); + krb5_free_error_message(context, krb5err); + return code; + } + + cred_enctype = krb5_k_key_enctype(context, cred_key); + + DEBUG(10, ("Plain cred_ndr_blob (len %zu)\n", + cred_ndr_blob->length)); + dump_data_pw("PAC_CREDENTIAL_DATA_NDR", + cred_ndr_blob->data, cred_ndr_blob->length); + + pac_cred_info.encryption_type = cred_enctype; + + cred_ndr_data.magic = 0; + cred_ndr_data.data = (char *)cred_ndr_blob->data; + cred_ndr_data.length = cred_ndr_blob->length; + + code = krb5_c_encrypt_length(context, + cred_enctype, + cred_ndr_data.length, + &enc_len); + if (code != 0) { + krb5err = krb5_get_error_message(context, code); + DEBUG(1, ("Failed initializing cred data crypto: %s\n", krb5err)); + krb5_free_error_message(context, krb5err); + return code; + } + + pac_cred_info.encrypted_data = data_blob_talloc_zero(mem_ctx, enc_len); + if (pac_cred_info.encrypted_data.data == NULL) { + DBG_ERR("Out of memory\n"); + return ENOMEM; + } + + cred_ndr_crypt.ciphertext.length = enc_len; + cred_ndr_crypt.ciphertext.data = (char *)pac_cred_info.encrypted_data.data; + + code = krb5_k_encrypt(context, + cred_key, + KRB5_KU_OTHER_ENCRYPTED, + NULL, + &cred_ndr_data, + &cred_ndr_crypt); + krb5_k_free_key(context, cred_key); + if (code != 0) { + krb5err = krb5_get_error_message(context, code); + DEBUG(1, ("Failed crypt of cred data: %s\n", krb5err)); + krb5_free_error_message(context, krb5err); + return code; + } + + if (DEBUGLVL(10)) { + NDR_PRINT_DEBUG(PAC_CREDENTIAL_INFO, &pac_cred_info); + } + + ndr_err = ndr_push_struct_blob(cred_info_blob, mem_ctx, &pac_cred_info, + (ndr_push_flags_fn_t)ndr_push_PAC_CREDENTIAL_INFO); + TALLOC_FREE(pac_cred_info.encrypted_data.data); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(1, ("PAC_CREDENTIAL_INFO (presig) push failed: %s\n", + nt_errstr(nt_status))); + return KRB5KDC_ERR_SVC_UNAVAILABLE; + } + + DEBUG(10, ("Encrypted credential BLOB (len %zu) with alg %d\n", + cred_info_blob->length, (int)pac_cred_info.encryption_type)); + dump_data_pw("PAC_CREDENTIAL_INFO", + cred_info_blob->data, cred_info_blob->length); + + return 0; +} +#endif /* SAMBA4_USES_HEIMDAL */ + + +/** + * @brief Create a PAC with the given blobs (logon, credentials, upn and + * delegation). + * + * @param[in] context The KRB5 context to use. + * + * @param[in] logon_blob Fill the logon info PAC buffer with the given blob, + * use NULL to ignore it. + * + * @param[in] cred_blob Fill the credentials info PAC buffer with the given + * blob, use NULL to ignore it. + * + * @param[in] upn_blob Fill the UPN info PAC buffer with the given blob, use + * NULL to ignore it. + * + * @param[in] deleg_blob Fill the delegation info PAC buffer with the given + * blob, use NULL to ignore it. + * + * @param[in] pac The pac buffer to fill. This should be allocated with + * krb5_pac_init() already. + * + * @returns 0 on success or a corresponding KRB5 error. + */ +krb5_error_code samba_make_krb5_pac(krb5_context context, + const DATA_BLOB *logon_blob, + const DATA_BLOB *cred_blob, + const DATA_BLOB *upn_blob, + const DATA_BLOB *pac_attrs_blob, + const DATA_BLOB *requester_sid_blob, + const DATA_BLOB *deleg_blob, + krb5_pac pac) +{ + krb5_data logon_data; + krb5_data cred_data; + krb5_data upn_data; + krb5_data pac_attrs_data; + krb5_data requester_sid_data; + krb5_data deleg_data; + krb5_error_code ret; +#ifdef SAMBA4_USES_HEIMDAL + char null_byte = '\0'; + krb5_data null_data = { + .length = 1, + .data = &null_byte, + }; +#endif + + /* The user account may be set not to want the PAC */ + if (logon_blob == NULL) { + return 0; + } + + ret = smb_krb5_copy_data_contents(&logon_data, + logon_blob->data, + logon_blob->length); + if (ret != 0) { + return ret; + } + + ZERO_STRUCT(cred_data); + if (cred_blob != NULL) { + ret = smb_krb5_copy_data_contents(&cred_data, + cred_blob->data, + cred_blob->length); + if (ret != 0) { + smb_krb5_free_data_contents(context, &logon_data); + return ret; + } + } + + ZERO_STRUCT(upn_data); + if (upn_blob != NULL) { + ret = smb_krb5_copy_data_contents(&upn_data, + upn_blob->data, + upn_blob->length); + if (ret != 0) { + smb_krb5_free_data_contents(context, &logon_data); + smb_krb5_free_data_contents(context, &cred_data); + return ret; + } + } + + ZERO_STRUCT(pac_attrs_data); + if (pac_attrs_blob != NULL) { + ret = smb_krb5_copy_data_contents(&pac_attrs_data, + pac_attrs_blob->data, + pac_attrs_blob->length); + if (ret != 0) { + smb_krb5_free_data_contents(context, &logon_data); + smb_krb5_free_data_contents(context, &cred_data); + smb_krb5_free_data_contents(context, &upn_data); + return ret; + } + } + + ZERO_STRUCT(requester_sid_data); + if (requester_sid_blob != NULL) { + ret = smb_krb5_copy_data_contents(&requester_sid_data, + requester_sid_blob->data, + requester_sid_blob->length); + if (ret != 0) { + smb_krb5_free_data_contents(context, &logon_data); + smb_krb5_free_data_contents(context, &cred_data); + smb_krb5_free_data_contents(context, &upn_data); + smb_krb5_free_data_contents(context, &pac_attrs_data); + return ret; + } + } + + ZERO_STRUCT(deleg_data); + if (deleg_blob != NULL) { + ret = smb_krb5_copy_data_contents(&deleg_data, + deleg_blob->data, + deleg_blob->length); + if (ret != 0) { + smb_krb5_free_data_contents(context, &logon_data); + smb_krb5_free_data_contents(context, &cred_data); + smb_krb5_free_data_contents(context, &upn_data); + smb_krb5_free_data_contents(context, &pac_attrs_data); + smb_krb5_free_data_contents(context, &requester_sid_data); + return ret; + } + } + + ret = krb5_pac_add_buffer(context, pac, PAC_TYPE_LOGON_INFO, &logon_data); + smb_krb5_free_data_contents(context, &logon_data); + if (ret != 0) { + smb_krb5_free_data_contents(context, &cred_data); + smb_krb5_free_data_contents(context, &upn_data); + smb_krb5_free_data_contents(context, &pac_attrs_data); + smb_krb5_free_data_contents(context, &requester_sid_data); + smb_krb5_free_data_contents(context, &deleg_data); + return ret; + } + + if (cred_blob != NULL) { + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_CREDENTIAL_INFO, + &cred_data); + smb_krb5_free_data_contents(context, &cred_data); + if (ret != 0) { + smb_krb5_free_data_contents(context, &upn_data); + smb_krb5_free_data_contents(context, &pac_attrs_data); + smb_krb5_free_data_contents(context, &requester_sid_data); + smb_krb5_free_data_contents(context, &deleg_data); + return ret; + } + } + +#ifdef SAMBA4_USES_HEIMDAL + /* + * null_data will be filled by the generic KDC code in the caller + * here we just add it in order to have it before + * PAC_TYPE_UPN_DNS_INFO + * + * Not needed with MIT Kerberos - asn + */ + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_LOGON_NAME, + &null_data); + if (ret != 0) { + smb_krb5_free_data_contents(context, &upn_data); + smb_krb5_free_data_contents(context, &pac_attrs_data); + smb_krb5_free_data_contents(context, &requester_sid_data); + smb_krb5_free_data_contents(context, &deleg_data); + return ret; + } +#endif + + if (upn_blob != NULL) { + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_UPN_DNS_INFO, + &upn_data); + smb_krb5_free_data_contents(context, &upn_data); + if (ret != 0) { + smb_krb5_free_data_contents(context, &pac_attrs_data); + smb_krb5_free_data_contents(context, &requester_sid_data); + smb_krb5_free_data_contents(context, &deleg_data); + return ret; + } + } + + if (pac_attrs_blob != NULL) { + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_ATTRIBUTES_INFO, + &pac_attrs_data); + smb_krb5_free_data_contents(context, &pac_attrs_data); + if (ret != 0) { + smb_krb5_free_data_contents(context, &requester_sid_data); + smb_krb5_free_data_contents(context, &deleg_data); + return ret; + } + } + + if (requester_sid_blob != NULL) { + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_REQUESTER_SID, + &requester_sid_data); + smb_krb5_free_data_contents(context, &requester_sid_data); + if (ret != 0) { + smb_krb5_free_data_contents(context, &deleg_data); + return ret; + } + } + + if (deleg_blob != NULL) { + ret = krb5_pac_add_buffer(context, pac, + PAC_TYPE_CONSTRAINED_DELEGATION, + &deleg_data); + smb_krb5_free_data_contents(context, &deleg_data); + if (ret != 0) { + return ret; + } + } + + return ret; +} + +bool samba_princ_needs_pac(struct samba_kdc_entry *skdc_entry) +{ + + uint32_t userAccountControl; + + /* The service account may be set not to want the PAC */ + userAccountControl = ldb_msg_find_attr_as_uint(skdc_entry->msg, "userAccountControl", 0); + if (userAccountControl & UF_NO_AUTH_DATA_REQUIRED) { + return false; + } + + return true; +} + +int samba_client_requested_pac(krb5_context context, + const krb5_pac *pac, + TALLOC_CTX *mem_ctx, + bool *requested_pac) +{ + enum ndr_err_code ndr_err; + krb5_data k5pac_attrs_in; + DATA_BLOB pac_attrs_in; + union PAC_INFO pac_attrs; + int ret; + + *requested_pac = true; + + ret = krb5_pac_get_buffer(context, *pac, PAC_TYPE_ATTRIBUTES_INFO, + &k5pac_attrs_in); + if (ret != 0) { + return ret == ENOENT ? 0 : ret; + } + + pac_attrs_in = data_blob_const(k5pac_attrs_in.data, + k5pac_attrs_in.length); + + ndr_err = ndr_pull_union_blob(&pac_attrs_in, mem_ctx, &pac_attrs, + PAC_TYPE_ATTRIBUTES_INFO, + (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO); + smb_krb5_free_data_contents(context, &k5pac_attrs_in); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC ATTRIBUTES_INFO: %s\n", nt_errstr(nt_status))); + return EINVAL; + } + + if (pac_attrs.attributes_info.flags & (PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY + | PAC_ATTRIBUTE_FLAG_PAC_WAS_REQUESTED)) { + *requested_pac = true; + } else { + *requested_pac = false; + } + + return 0; +} + +/* Was the krbtgt in this DB (ie, should we check the incoming signature) and was it an RODC */ +int samba_krbtgt_is_in_db(struct samba_kdc_entry *p, + bool *is_in_db, + bool *is_untrusted) +{ + NTSTATUS status; + int rodc_krbtgt_number, trust_direction; + uint32_t rid; + + TALLOC_CTX *mem_ctx = talloc_new(NULL); + if (!mem_ctx) { + return ENOMEM; + } + + trust_direction = ldb_msg_find_attr_as_int(p->msg, "trustDirection", 0); + + if (trust_direction != 0) { + /* Domain trust - we cannot check the sig, but we trust it for a correct PAC + + This is exactly where we should flag for SID + validation when we do inter-foreest trusts + */ + talloc_free(mem_ctx); + *is_untrusted = false; + *is_in_db = false; + return 0; + } + + /* The lack of password controls etc applies to krbtgt by + * virtue of being that particular RID */ + status = dom_sid_split_rid(NULL, samdb_result_dom_sid(mem_ctx, p->msg, "objectSid"), NULL, &rid); + + if (!NT_STATUS_IS_OK(status)) { + talloc_free(mem_ctx); + return EINVAL; + } + + rodc_krbtgt_number = ldb_msg_find_attr_as_int(p->msg, "msDS-SecondaryKrbTgtNumber", -1); + + if (p->kdc_db_ctx->my_krbtgt_number == 0) { + if (rid == DOMAIN_RID_KRBTGT) { + *is_untrusted = false; + *is_in_db = true; + talloc_free(mem_ctx); + return 0; + } else if (rodc_krbtgt_number != -1) { + *is_in_db = true; + *is_untrusted = true; + talloc_free(mem_ctx); + return 0; + } + } else if ((rid != DOMAIN_RID_KRBTGT) && (rodc_krbtgt_number == p->kdc_db_ctx->my_krbtgt_number)) { + talloc_free(mem_ctx); + *is_untrusted = false; + *is_in_db = true; + return 0; + } else if (rid == DOMAIN_RID_KRBTGT) { + /* krbtgt viewed from an RODC */ + talloc_free(mem_ctx); + *is_untrusted = false; + *is_in_db = false; + return 0; + } + + /* Another RODC */ + talloc_free(mem_ctx); + *is_untrusted = true; + *is_in_db = false; + return 0; +} + +/* + * Because the KDC does not limit protocol transition, two new well-known SIDs + * were introduced to give this control to the resource administrator. These + * SIDs identify whether protocol transition has occurred, and can be used with + * standard access control lists to grant or limit access as needed. + * + * https://docs.microsoft.com/en-us/windows-server/security/kerberos/kerberos-constrained-delegation-overview + */ +static NTSTATUS samba_add_asserted_identity(TALLOC_CTX *mem_ctx, + enum samba_asserted_identity ai, + struct auth_user_info_dc *user_info_dc) +{ + struct dom_sid ai_sid; + const char *sid_str = NULL; + + switch (ai) { + case SAMBA_ASSERTED_IDENTITY_SERVICE: + sid_str = SID_SERVICE_ASSERTED_IDENTITY; + break; + case SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY: + sid_str = SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY; + break; + case SAMBA_ASSERTED_IDENTITY_IGNORE: + return NT_STATUS_OK; + } + + dom_sid_parse(sid_str, &ai_sid); + + return add_sid_to_array_unique(user_info_dc, + &ai_sid, + &user_info_dc->sids, + &user_info_dc->num_sids); +} + +/* + * Look up the user's info in the database and create a auth_user_info_dc + * structure. If the resulting structure is not talloc_free()d, it will be + * reused on future calls to this function. + */ +NTSTATUS samba_kdc_get_user_info_from_db(struct samba_kdc_entry *skdc_entry, + struct ldb_message *msg, + struct auth_user_info_dc **user_info_dc) +{ + if (skdc_entry->user_info_dc == NULL) { + NTSTATUS nt_status; + struct loadparm_context *lp_ctx = skdc_entry->kdc_db_ctx->lp_ctx; + + nt_status = authsam_make_user_info_dc(skdc_entry, + skdc_entry->kdc_db_ctx->samdb, + lpcfg_netbios_name(lp_ctx), + lpcfg_sam_name(lp_ctx), + lpcfg_sam_dnsname(lp_ctx), + skdc_entry->realm_dn, + msg, + data_blob_null, + data_blob_null, + &skdc_entry->user_info_dc); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } + + *user_info_dc = skdc_entry->user_info_dc; + return NT_STATUS_OK; +} + +NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, + struct samba_kdc_entry *p, + enum samba_asserted_identity asserted_identity, + DATA_BLOB **_logon_info_blob, + DATA_BLOB **_cred_ndr_blob, + DATA_BLOB **_upn_info_blob, + DATA_BLOB **_pac_attrs_blob, + uint64_t pac_attributes, + DATA_BLOB **_requester_sid_blob) +{ + struct auth_user_info_dc *user_info_dc = NULL; + DATA_BLOB *logon_blob = NULL; + DATA_BLOB *cred_blob = NULL; + DATA_BLOB *upn_blob = NULL; + DATA_BLOB *pac_attrs_blob = NULL; + DATA_BLOB *requester_sid_blob = NULL; + NTSTATUS nt_status; + + *_logon_info_blob = NULL; + if (_cred_ndr_blob != NULL) { + *_cred_ndr_blob = NULL; + } + *_upn_info_blob = NULL; + if (_pac_attrs_blob != NULL) { + *_pac_attrs_blob = NULL; + } + if (_requester_sid_blob != NULL) { + *_requester_sid_blob = NULL; + } + + logon_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (logon_blob == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (_cred_ndr_blob != NULL) { + cred_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (cred_blob == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + upn_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (upn_blob == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (_pac_attrs_blob != NULL) { + pac_attrs_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (pac_attrs_blob == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + if (_requester_sid_blob != NULL) { + requester_sid_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (requester_sid_blob == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + nt_status = samba_kdc_get_user_info_from_db(p, + p->msg, + &user_info_dc); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0, ("Getting user info for PAC failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + + nt_status = samba_add_asserted_identity(mem_ctx, + asserted_identity, + user_info_dc); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("Failed to add assertied identity!\n"); + return nt_status; + } + + nt_status = samba_get_logon_info_pac_blob(logon_blob, + user_info_dc, + logon_blob, + requester_sid_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0, ("Building PAC LOGON INFO failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + + if (cred_blob != NULL) { + nt_status = samba_get_cred_info_ndr_blob(cred_blob, + p->msg, + cred_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0, ("Building PAC CRED INFO failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + } + + nt_status = samba_get_upn_info_pac_blob(upn_blob, + user_info_dc, + upn_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0, ("Building PAC UPN INFO failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + + if (pac_attrs_blob != NULL) { + nt_status = samba_get_pac_attrs_blob(pac_attrs_blob, + pac_attributes, + pac_attrs_blob); + + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0, ("Building PAC ATTRIBUTES failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + } + + *_logon_info_blob = logon_blob; + if (_cred_ndr_blob != NULL) { + *_cred_ndr_blob = cred_blob; + } + *_upn_info_blob = upn_blob; + if (_pac_attrs_blob != NULL) { + *_pac_attrs_blob = pac_attrs_blob; + } + if (_requester_sid_blob != NULL) { + *_requester_sid_blob = requester_sid_blob; + } + return NT_STATUS_OK; +} + +NTSTATUS samba_kdc_update_pac_blob(TALLOC_CTX *mem_ctx, + krb5_context context, + struct ldb_context *samdb, + const krb5_pac pac, DATA_BLOB *pac_blob, + struct PAC_SIGNATURE_DATA *pac_srv_sig, + struct PAC_SIGNATURE_DATA *pac_kdc_sig) +{ + struct auth_user_info_dc *user_info_dc; + krb5_error_code ret; + NTSTATUS nt_status; + + ret = kerberos_pac_to_user_info_dc(mem_ctx, pac, + context, &user_info_dc, pac_srv_sig, pac_kdc_sig); + if (ret) { + return NT_STATUS_UNSUCCESSFUL; + } + + /* + * We need to expand group memberships within our local domain, + * as the token might be generated by a trusted domain. + */ + nt_status = authsam_update_user_info_dc(mem_ctx, + samdb, + user_info_dc); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + nt_status = samba_get_logon_info_pac_blob(mem_ctx, + user_info_dc, pac_blob, NULL); + + return nt_status; +} + +NTSTATUS samba_kdc_update_delegation_info_blob(TALLOC_CTX *mem_ctx, + krb5_context context, + const krb5_pac pac, + const krb5_principal server_principal, + const krb5_principal proxy_principal, + DATA_BLOB *new_blob) +{ + krb5_data old_data; + DATA_BLOB old_blob; + krb5_error_code ret; + NTSTATUS nt_status; + enum ndr_err_code ndr_err; + union PAC_INFO info; + struct PAC_CONSTRAINED_DELEGATION _d; + struct PAC_CONSTRAINED_DELEGATION *d = NULL; + char *server = NULL; + char *proxy = NULL; + uint32_t i; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_CONSTRAINED_DELEGATION, &old_data); + if (ret == ENOENT) { + ZERO_STRUCT(old_data); + } else if (ret) { + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + old_blob.length = old_data.length; + old_blob.data = (uint8_t *)old_data.data; + + ZERO_STRUCT(info); + if (old_blob.length > 0) { + ndr_err = ndr_pull_union_blob(&old_blob, mem_ctx, + &info, PAC_TYPE_CONSTRAINED_DELEGATION, + (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + smb_krb5_free_data_contents(context, &old_data); + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC LOGON_INFO: %s\n", nt_errstr(nt_status))); + talloc_free(tmp_ctx); + return nt_status; + } + } else { + ZERO_STRUCT(_d); + info.constrained_delegation.info = &_d; + } + smb_krb5_free_data_contents(context, &old_data); + + ret = krb5_unparse_name_flags(context, server_principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, &server); + if (ret) { + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_ERROR; + } + + ret = krb5_unparse_name(context, proxy_principal, &proxy); + if (ret) { + SAFE_FREE(server); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_ERROR; + } + + d = info.constrained_delegation.info; + i = d->num_transited_services; + d->proxy_target.string = server; + d->transited_services = talloc_realloc(mem_ctx, d->transited_services, + struct lsa_String, i + 1); + d->transited_services[i].string = proxy; + d->num_transited_services = i + 1; + + ndr_err = ndr_push_union_blob(new_blob, mem_ctx, + &info, PAC_TYPE_CONSTRAINED_DELEGATION, + (ndr_push_flags_fn_t)ndr_push_PAC_INFO); + SAFE_FREE(server); + SAFE_FREE(proxy); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + smb_krb5_free_data_contents(context, &old_data); + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC LOGON_INFO: %s\n", nt_errstr(nt_status))); + talloc_free(tmp_ctx); + return nt_status; + } + + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} + +/* function to map policy errors */ +krb5_error_code samba_kdc_map_policy_err(NTSTATUS nt_status) +{ + krb5_error_code ret; + + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_PASSWORD_MUST_CHANGE)) + ret = KRB5KDC_ERR_KEY_EXP; + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_PASSWORD_EXPIRED)) + ret = KRB5KDC_ERR_KEY_EXP; + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_EXPIRED)) + ret = KRB5KDC_ERR_CLIENT_REVOKED; + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_DISABLED)) + ret = KRB5KDC_ERR_CLIENT_REVOKED; + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_LOGON_HOURS)) + ret = KRB5KDC_ERR_CLIENT_REVOKED; + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_LOCKED_OUT)) + ret = KRB5KDC_ERR_CLIENT_REVOKED; + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_WORKSTATION)) + ret = KRB5KDC_ERR_POLICY; + else + ret = KRB5KDC_ERR_POLICY; + + return ret; +} + +/* Given a kdc entry, consult the account_ok routine in auth/auth_sam.c + * for consistency */ +NTSTATUS samba_kdc_check_client_access(struct samba_kdc_entry *kdc_entry, + const char *client_name, + const char *workstation, + bool password_change) +{ + TALLOC_CTX *tmp_ctx; + NTSTATUS nt_status; + + tmp_ctx = talloc_named(NULL, 0, "samba_kdc_check_client_access"); + if (!tmp_ctx) { + return NT_STATUS_NO_MEMORY; + } + + /* we allow all kinds of trusts here */ + nt_status = authsam_account_ok(tmp_ctx, + kdc_entry->kdc_db_ctx->samdb, + MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT | + MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT, + kdc_entry->realm_dn, kdc_entry->msg, + workstation, client_name, + true, password_change); + + kdc_entry->reject_status = nt_status; + talloc_free(tmp_ctx); + return nt_status; +} + +static krb5_error_code samba_get_requester_sid(TALLOC_CTX *mem_ctx, + krb5_pac pac, + krb5_context context, + struct dom_sid *sid) +{ + NTSTATUS nt_status; + enum ndr_err_code ndr_err; + krb5_error_code ret; + + DATA_BLOB pac_requester_sid_in; + krb5_data k5pac_requester_sid_in; + + union PAC_INFO info; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_REQUESTER_SID, + &k5pac_requester_sid_in); + if (ret != 0) { + talloc_free(tmp_ctx); + return ret; + } + + pac_requester_sid_in = data_blob_const(k5pac_requester_sid_in.data, + k5pac_requester_sid_in.length); + + ndr_err = ndr_pull_union_blob(&pac_requester_sid_in, tmp_ctx, &info, + PAC_TYPE_REQUESTER_SID, + (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO); + smb_krb5_free_data_contents(context, &k5pac_requester_sid_in); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC REQUESTER_SID: %s\n", nt_errstr(nt_status))); + talloc_free(tmp_ctx); + return EINVAL; + } + + *sid = info.requester_sid.sid; + + talloc_free(tmp_ctx); + return 0; +} + +/* Does a parse and SID check, but no crypto. */ +krb5_error_code samba_kdc_validate_pac_blob( + krb5_context context, + struct samba_kdc_entry *client_skdc_entry, + const krb5_pac pac) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct auth_user_info_dc *pac_user_info = NULL; + struct dom_sid *client_sid = NULL; + struct dom_sid pac_sid; + krb5_error_code code; + bool ok; + + /* + * First, try to get the SID from the requester SID buffer in the PAC. + */ + code = samba_get_requester_sid(frame, pac, context, &pac_sid); + + if (code == ENOENT) { + /* + * If the requester SID buffer isn't present, fall back to the + * SID in the LOGON_INFO PAC buffer. + */ + code = kerberos_pac_to_user_info_dc(frame, + pac, + context, + &pac_user_info, + NULL, + NULL); + if (code != 0) { + goto out; + } + + if (pac_user_info->num_sids == 0) { + code = EINVAL; + goto out; + } + + pac_sid = pac_user_info->sids[0]; + } else if (code != 0) { + goto out; + } + + client_sid = samdb_result_dom_sid(frame, + client_skdc_entry->msg, + "objectSid"); + + ok = dom_sid_equal(&pac_sid, client_sid); + if (!ok) { + struct dom_sid_buf buf1; + struct dom_sid_buf buf2; + + DBG_ERR("SID mismatch between PAC and looked up client: " + "PAC[%s] != CLI[%s]\n", + dom_sid_str_buf(&pac_sid, &buf1), + dom_sid_str_buf(client_sid, &buf2)); + code = KRB5KDC_ERR_TGT_REVOKED; + goto out; + } + + code = 0; +out: + TALLOC_FREE(frame); + return code; +} + + +/* + * In the RODC case, to confirm that the returned user is permitted to + * be replicated to the KDC (krbgtgt_xxx user) represented by *rodc + */ +WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_object_sids, + struct dom_sid *object_sids, + struct samba_kdc_entry *rodc, + struct samba_kdc_entry *object) +{ + int ret; + WERROR werr; + TALLOC_CTX *frame = talloc_stackframe(); + const char *rodc_attrs[] = { "msDS-KrbTgtLink", + "msDS-NeverRevealGroup", + "msDS-RevealOnDemandGroup", + "userAccountControl", + "objectSid", + NULL }; + struct ldb_result *rodc_machine_account = NULL; + struct ldb_dn *rodc_machine_account_dn = samdb_result_dn(rodc->kdc_db_ctx->samdb, + frame, + rodc->msg, + "msDS-KrbTgtLinkBL", + NULL); + const struct dom_sid *rodc_machine_account_sid = NULL; + + if (rodc_machine_account_dn == NULL) { + DBG_ERR("krbtgt account %s has no msDS-KrbTgtLinkBL to find RODC machine account for allow/deny list\n", + ldb_dn_get_linearized(rodc->msg->dn)); + TALLOC_FREE(frame); + return WERR_DOMAIN_CONTROLLER_NOT_FOUND; + } + + /* + * Follow the link and get the RODC account (the krbtgt + * account is the krbtgt_XXX account, but the + * msDS-NeverRevealGroup and msDS-RevealOnDemandGroup is on + * the RODC$ account) + * + * We need DSDB_SEARCH_SHOW_EXTENDED_DN as we get a SID lists + * out of the extended DNs + */ + + ret = dsdb_search_dn(rodc->kdc_db_ctx->samdb, + frame, + &rodc_machine_account, + rodc_machine_account_dn, + rodc_attrs, + DSDB_SEARCH_SHOW_EXTENDED_DN); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to fetch RODC machine account %s pointed to by %s to check allow/deny list: %s\n", + ldb_dn_get_linearized(rodc_machine_account_dn), + ldb_dn_get_linearized(rodc->msg->dn), + ldb_errstring(rodc->kdc_db_ctx->samdb)); + TALLOC_FREE(frame); + return WERR_DOMAIN_CONTROLLER_NOT_FOUND; + } + + if (rodc_machine_account->count != 1) { + DBG_ERR("Failed to fetch RODC machine account %s pointed to by %s to check allow/deny list: (%d)\n", + ldb_dn_get_linearized(rodc_machine_account_dn), + ldb_dn_get_linearized(rodc->msg->dn), + rodc_machine_account->count); + TALLOC_FREE(frame); + return WERR_DS_DRA_BAD_DN; + } + + /* if the object SID is equal to the user_sid, allow */ + rodc_machine_account_sid = samdb_result_dom_sid(frame, + rodc_machine_account->msgs[0], + "objectSid"); + if (rodc_machine_account_sid == NULL) { + return WERR_DS_DRA_BAD_DN; + } + + werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(rodc->kdc_db_ctx->samdb, + rodc_machine_account_sid, + rodc_machine_account->msgs[0], + object->msg, + num_object_sids, + object_sids); + + TALLOC_FREE(frame); + return werr; +} + +/** + * @brief Update a PAC + * + * @param mem_ctx A talloc memory context + * + * @param context A krb5 context + * + * @param samdb An open samdb connection. + * + * @param flags Bitwise OR'ed flags + * + * @param client The client samba kdc entry. + + * @param server_principal The server principal + + * @param server The server samba kdc entry. + + * @param krbtgt The krbtgt samba kdc entry. + * + * @param delegated_proxy_principal The delegated proxy principal used for + * updating the constrained delegation PAC + * buffer. + + * @param old_pac The old PAC + + * @param new_pac The new already allocated PAC + + * @return A Kerberos error code. If no PAC should be returned, the code will be + * ENODATA! + */ +krb5_error_code samba_kdc_update_pac(TALLOC_CTX *mem_ctx, + krb5_context context, + struct ldb_context *samdb, + uint32_t flags, + struct samba_kdc_entry *client, + const krb5_principal server_principal, + struct samba_kdc_entry *server, + struct samba_kdc_entry *krbtgt, + const krb5_principal delegated_proxy_principal, + const krb5_pac old_pac, + krb5_pac new_pac) +{ + krb5_error_code code = EINVAL; + NTSTATUS nt_status; + DATA_BLOB *pac_blob = NULL; + DATA_BLOB *upn_blob = NULL; + DATA_BLOB *deleg_blob = NULL; + DATA_BLOB *requester_sid_blob = NULL; + bool is_untrusted = flags & SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED; + int is_tgs = false; + size_t num_types = 0; + uint32_t *types = NULL; + /* + * FIXME: Do we really still need forced_next_type? With MIT Kerberos + * the PAC buffers do not get ordered and it works just fine. We are + * not aware of any issues in this regard. This might be just ancient + * code. + */ + uint32_t forced_next_type = 0; + size_t i = 0; + ssize_t logon_info_idx = -1; + ssize_t delegation_idx = -1; + ssize_t logon_name_idx = -1; + ssize_t upn_dns_info_idx = -1; + ssize_t srv_checksum_idx = -1; + ssize_t kdc_checksum_idx = -1; + ssize_t tkt_checksum_idx = -1; + ssize_t attrs_info_idx = -1; + ssize_t requester_sid_idx = -1; + ssize_t full_checksum_idx = -1; + + if (client != NULL) { + /* + * Check the objectSID of the client and pac data are the same. + * Does a parse and SID check, but no crypto. + */ + code = samba_kdc_validate_pac_blob(context, + client, + old_pac); + if (code != 0) { + goto done; + } + } + + if (delegated_proxy_principal != NULL) { + deleg_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (deleg_blob == NULL) { + code = ENOMEM; + goto done; + } + + nt_status = samba_kdc_update_delegation_info_blob( + mem_ctx, + context, + old_pac, + server_principal, + delegated_proxy_principal, + deleg_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("update delegation info blob failed: %s\n", + nt_errstr(nt_status)); + code = EINVAL; + goto done; + } + } + + if (is_untrusted) { + struct auth_user_info_dc *user_info_dc = NULL; + WERROR werr; + /* + * In this case the RWDC discards the PAC an RODC generated. + * Windows adds the asserted_identity in this case too. + * + * Note that SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION + * generates KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN. + * So we can always use + * SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY + * here. + */ + enum samba_asserted_identity asserted_identity = + SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY; + + if (client == NULL) { + code = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; + goto done; + } + + nt_status = samba_kdc_get_pac_blobs(mem_ctx, + client, + asserted_identity, + &pac_blob, + NULL, + &upn_blob, + NULL, + PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY, + &requester_sid_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("samba_kdc_get_pac_blobs failed: %s\n", + nt_errstr(nt_status)); + code = KRB5KDC_ERR_TGT_REVOKED; + goto done; + } + + nt_status = samba_kdc_get_user_info_from_db(client, + client->msg, + &user_info_dc); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("samba_kdc_get_user_info_from_db failed: %s\n", + nt_errstr(nt_status)); + code = KRB5KDC_ERR_TGT_REVOKED; + goto done; + } + + /* + * Check if the SID list in the user_info_dc intersects + * correctly with the RODC allow/deny lists. + */ + werr = samba_rodc_confirm_user_is_allowed(user_info_dc->num_sids, + user_info_dc->sids, + krbtgt, + client); + TALLOC_FREE(user_info_dc); + if (!W_ERROR_IS_OK(werr)) { + code = KRB5KDC_ERR_TGT_REVOKED; + if (W_ERROR_EQUAL(werr, + WERR_DOMAIN_CONTROLLER_NOT_FOUND)) { + code = KRB5KDC_ERR_POLICY; + } + goto done; + } + + /* + * The RODC PAC data isn't trusted for authorization as it may + * be stale. The only thing meaningful we can do with an RODC + * account on a full DC is exchange the RODC TGT for a 'real' + * TGT. + * + * So we match Windows (at least server 2022) and + * don't allow S4U2Self. + * + * https://lists.samba.org/archive/cifs-protocol/2022-April/003673.html + */ + if (flags & SAMBA_KDC_FLAG_PROTOCOL_TRANSITION) { + code = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; + goto done; + } + } else { + pac_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (pac_blob == NULL) { + code = ENOMEM; + goto done; + } + + nt_status = samba_kdc_update_pac_blob(mem_ctx, + context, + samdb, + old_pac, + pac_blob, + NULL, + NULL); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("samba_kdc_update_pac_blob failed: %s\n", + nt_errstr(nt_status)); + code = EINVAL; + goto done; + } + } + + /* Check the types of the given PAC */ + code = krb5_pac_get_types(context, old_pac, &num_types, &types); + if (code != 0) { + DBG_ERR("krb5_pac_get_types failed\n"); + goto done; + } + + for (i = 0; i < num_types; i++) { + switch (types[i]) { + case PAC_TYPE_LOGON_INFO: + if (logon_info_idx != -1) { + DBG_WARNING("logon info type[%u] twice [%zd] " + "and [%zu]: \n", + types[i], + logon_info_idx, + i); + code = EINVAL; + goto done; + } + logon_info_idx = i; + break; + case PAC_TYPE_CONSTRAINED_DELEGATION: + if (delegation_idx != -1) { + DBG_WARNING("constrained delegation type[%u] " + "twice [%zd] and [%zu]: \n", + types[i], + delegation_idx, + i); + code = EINVAL; + goto done; + } + delegation_idx = i; + break; + case PAC_TYPE_LOGON_NAME: + if (logon_name_idx != -1) { + DBG_WARNING("logon name type[%u] twice [%zd] " + "and [%zu]: \n", + types[i], + logon_name_idx, + i); + code = EINVAL; + goto done; + } + logon_name_idx = i; + break; + case PAC_TYPE_UPN_DNS_INFO: + if (upn_dns_info_idx != -1) { + DBG_WARNING("upn dns info type[%u] twice [%zd] " + "and [%zu]: \n", + types[i], + upn_dns_info_idx, + i); + code = EINVAL; + goto done; + } + upn_dns_info_idx = i; + break; + case PAC_TYPE_SRV_CHECKSUM: + if (srv_checksum_idx != -1) { + DBG_WARNING("srv checksum type[%u] twice [%zd] " + "and [%zu]: \n", + types[i], + srv_checksum_idx, + i); + code = EINVAL; + goto done; + } + srv_checksum_idx = i; + break; + case PAC_TYPE_KDC_CHECKSUM: + if (kdc_checksum_idx != -1) { + DBG_WARNING("kdc checksum type[%u] twice [%zd] " + "and [%zu]: \n", + types[i], + kdc_checksum_idx, + i); + code = EINVAL; + goto done; + } + kdc_checksum_idx = i; + break; + case PAC_TYPE_TICKET_CHECKSUM: + if (tkt_checksum_idx != -1) { + DBG_WARNING("ticket checksum type[%u] twice " + "[%zd] and [%zu]: \n", + types[i], + tkt_checksum_idx, + i); + code = EINVAL; + goto done; + } + tkt_checksum_idx = i; + break; + case PAC_TYPE_ATTRIBUTES_INFO: + if (attrs_info_idx != -1) { + DBG_WARNING("attributes info type[%u] twice " + "[%zd] and [%zu]: \n", + types[i], + attrs_info_idx, + i); + code = EINVAL; + goto done; + } + attrs_info_idx = i; + break; + case PAC_TYPE_REQUESTER_SID: + if (requester_sid_idx != -1) { + DBG_WARNING("requester sid type[%u] twice" + "[%zd] and [%zu]: \n", + types[i], + requester_sid_idx, + i); + code = EINVAL; + goto done; + } + requester_sid_idx = i; + break; + case PAC_TYPE_FULL_CHECKSUM: + if (full_checksum_idx != -1) { + DBG_WARNING("full checksum type[%u] twice " + "[%zd] and [%zu]: \n", + types[i], + full_checksum_idx, + i); + code = EINVAL; + goto done; + } + full_checksum_idx = i; + break; + default: + continue; + } + } + + if (logon_info_idx == -1) { + DBG_WARNING("PAC_TYPE_LOGON_INFO missing\n"); + code = EINVAL; + goto done; + } + if (logon_name_idx == -1) { + DBG_WARNING("PAC_TYPE_LOGON_NAME missing\n"); + code = EINVAL; + goto done; + } + if (srv_checksum_idx == -1) { + DBG_WARNING("PAC_TYPE_SRV_CHECKSUM missing\n"); + code = EINVAL; + goto done; + } + if (kdc_checksum_idx == -1) { + DBG_WARNING("PAC_TYPE_KDC_CHECKSUM missing\n"); + code = EINVAL; + goto done; + } + if (!(flags & SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION) && + requester_sid_idx == -1) { + DBG_WARNING("PAC_TYPE_REQUESTER_SID missing\n"); + code = KRB5KDC_ERR_TGT_REVOKED; + goto done; + } + + /* + * The server account may be set not to want the PAC. + * + * While this is wasteful if the above cacluations were done + * and now thrown away, this is cleaner as we do any ticket + * signature checking etc always. + * + * UF_NO_AUTH_DATA_REQUIRED is the rare case and most of the + * time (eg not accepting a ticket from the RODC) we do not + * need to re-generate anything anyway. + */ + if (!samba_princ_needs_pac(server)) { + code = ENODATA; + goto done; + } + + is_tgs = smb_krb5_principal_is_tgs(context, server_principal); + if (is_tgs == -1) { + code = ENOMEM; + goto done; + } + + if (!is_untrusted && !is_tgs) { + /* + * The client may have requested no PAC when obtaining the + * TGT. + */ + bool requested_pac = false; + + code = samba_client_requested_pac(context, + &old_pac, + mem_ctx, + &requested_pac); + if (code != 0 || !requested_pac) { + if (!requested_pac) { + code = ENODATA; + } + goto done; + } + } + +#define MAX_PAC_BUFFERS 128 /* Avoid infinite loops */ + + for (i = 0; i < MAX_PAC_BUFFERS;) { + const uint8_t zero_byte = 0; + krb5_data type_data; + DATA_BLOB type_blob = data_blob_null; + uint32_t type; + + if (forced_next_type != 0) { + /* + * We need to inject possible missing types + */ + type = forced_next_type; + forced_next_type = 0; + } else if (i < num_types) { + type = types[i]; + i++; + } else { + break; + } + + switch (type) { + case PAC_TYPE_LOGON_INFO: + type_blob = *pac_blob; + + if (delegation_idx == -1 && deleg_blob != NULL) { + /* inject CONSTRAINED_DELEGATION behind */ + forced_next_type = + PAC_TYPE_CONSTRAINED_DELEGATION; + } + break; + case PAC_TYPE_CONSTRAINED_DELEGATION: + /* + * This is generated in the main KDC code + */ + if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) { + continue; + } + + if (deleg_blob != NULL) { + type_blob = *deleg_blob; + } + break; + case PAC_TYPE_CREDENTIAL_INFO: + /* + * Note that we copy the credential blob, + * as it's only usable with the PKINIT based + * AS-REP reply key, it's only available on the + * host which did the AS-REQ/AS-REP exchange. + * + * This matches Windows 2008R2... + */ + break; + case PAC_TYPE_LOGON_NAME: + /* + * This is generated in the main KDC code + */ + if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) { + continue; + } + + type_blob = data_blob_const(&zero_byte, 1); + + if (upn_dns_info_idx == -1 && upn_blob != NULL) { + /* inject UPN_DNS_INFO behind */ + forced_next_type = PAC_TYPE_UPN_DNS_INFO; + } + break; + case PAC_TYPE_UPN_DNS_INFO: + /* + * Replace in the RODC case, otherwise + * upn_blob is NULL and we just copy. + */ + if (upn_blob != NULL) { + type_blob = *upn_blob; + } + break; + case PAC_TYPE_SRV_CHECKSUM: + /* + * This is generated in the main KDC code + */ + if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) { + continue; + } + + type_blob = data_blob_const(&zero_byte, 1); + + if (requester_sid_idx == -1 && requester_sid_blob != NULL) { + /* inject REQUESTER_SID behind */ + forced_next_type = PAC_TYPE_REQUESTER_SID; + } + break; + case PAC_TYPE_KDC_CHECKSUM: + /* + * This is generated in the main KDC code + */ + if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) { + continue; + } + + type_blob = data_blob_const(&zero_byte, 1); + + break; + case PAC_TYPE_TICKET_CHECKSUM: + /* + * This is generated in the main KDC code + */ + if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) { + continue; + } + + type_blob = data_blob_const(&zero_byte, 1); + + break; + case PAC_TYPE_ATTRIBUTES_INFO: + if (!is_untrusted && is_tgs) { + /* just copy... */ + break; + } + + continue; + case PAC_TYPE_REQUESTER_SID: + if (!is_tgs) { + continue; + } + + /* + * Replace in the RODC case, otherwise + * requester_sid_blob is NULL and we just copy. + */ + if (requester_sid_blob != NULL) { + type_blob = *requester_sid_blob; + } + break; + case PAC_TYPE_FULL_CHECKSUM: + /* + * This is generated in the main KDC code + */ + if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) { + continue; + } + + type_blob = data_blob_const(&zero_byte, 1); + + break; + default: + /* just copy... */ + break; + } + + if (type_blob.length != 0) { + code = smb_krb5_copy_data_contents(&type_data, + type_blob.data, + type_blob.length); + if (code != 0) { + goto done; + } + } else { + code = krb5_pac_get_buffer(context, + old_pac, + type, + &type_data); + if (code != 0) { + goto done; + } + } + + code = krb5_pac_add_buffer(context, + new_pac, + type, + &type_data); + smb_krb5_free_data_contents(context, &type_data); + if (code != 0) { + goto done; + } + } + + code = 0; +done: + TALLOC_FREE(pac_blob); + TALLOC_FREE(upn_blob); + TALLOC_FREE(deleg_blob); + SAFE_FREE(types); + return code; +} diff --git a/source4/kdc/pac-glue.h b/source4/kdc/pac-glue.h new file mode 100644 index 0000000..7b7c489 --- /dev/null +++ b/source4/kdc/pac-glue.h @@ -0,0 +1,122 @@ +/* + Unix SMB/CIFS implementation. + + PAC Glue between Samba and the KDC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009 + Copyright (C) Simo Sorce <idra@samba.org> 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +enum samba_asserted_identity { + SAMBA_ASSERTED_IDENTITY_IGNORE = 0, + SAMBA_ASSERTED_IDENTITY_SERVICE, + SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY, +}; + +enum { + SAMBA_KDC_FLAG_PROTOCOL_TRANSITION = 0x00000001, + SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION = 0x00000002, + SAMBA_KDC_FLAG_KRBTGT_IN_DB = 0x00000004, + SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED = 0x00000008, + SAMBA_KDC_FLAG_SKIP_PAC_BUFFER = 0x00000010, +}; + +krb5_error_code samba_kdc_encrypt_pac_credentials(krb5_context context, + const krb5_keyblock *pkreplykey, + const DATA_BLOB *cred_ndr_blob, + TALLOC_CTX *mem_ctx, + DATA_BLOB *cred_info_blob); + +krb5_error_code samba_make_krb5_pac(krb5_context context, + const DATA_BLOB *logon_blob, + const DATA_BLOB *cred_blob, + const DATA_BLOB *upn_blob, + const DATA_BLOB *pac_attrs_blob, + const DATA_BLOB *requester_sid_blob, + const DATA_BLOB *deleg_blob, + krb5_pac pac); + +bool samba_princ_needs_pac(struct samba_kdc_entry *skdc_entry); + +int samba_client_requested_pac(krb5_context context, + const krb5_pac *pac, + TALLOC_CTX *mem_ctx, + bool *requested_pac); + +int samba_krbtgt_is_in_db(struct samba_kdc_entry *skdc_entry, + bool *is_in_db, + bool *is_untrusted); + +NTSTATUS samba_kdc_get_user_info_from_db(struct samba_kdc_entry *skdc_entry, + struct ldb_message *msg, + struct auth_user_info_dc **user_info_dc); + +NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, + struct samba_kdc_entry *skdc_entry, + enum samba_asserted_identity asserted_identity, + DATA_BLOB **_logon_info_blob, + DATA_BLOB **_cred_ndr_blob, + DATA_BLOB **_upn_info_blob, + DATA_BLOB **_pac_attrs_blob, + uint64_t pac_attributes, + DATA_BLOB **_requester_sid_blob); +NTSTATUS samba_kdc_update_pac_blob(TALLOC_CTX *mem_ctx, + krb5_context context, + struct ldb_context *samdb, + const krb5_pac pac, DATA_BLOB *pac_blob, + struct PAC_SIGNATURE_DATA *pac_srv_sig, + struct PAC_SIGNATURE_DATA *pac_kdc_sig); + +NTSTATUS samba_kdc_update_delegation_info_blob(TALLOC_CTX *mem_ctx, + krb5_context context, + const krb5_pac pac, + const krb5_principal server_principal, + const krb5_principal proxy_principal, + DATA_BLOB *pac_blob); + +krb5_error_code samba_kdc_map_policy_err(NTSTATUS nt_status); + +NTSTATUS samba_kdc_check_client_access(struct samba_kdc_entry *kdc_entry, + const char *client_name, + const char *workstation, + bool password_change); + +krb5_error_code samba_kdc_validate_pac_blob( + krb5_context context, + struct samba_kdc_entry *client_skdc_entry, + const krb5_pac pac); + +/* + * In the RODC case, to confirm that the returned user is permitted to + * be replicated to the KDC (krbgtgt_xxx user) represented by *rodc + */ +WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_sids, + struct dom_sid *sids, + struct samba_kdc_entry *rodc, + struct samba_kdc_entry *object); + +krb5_error_code samba_kdc_update_pac(TALLOC_CTX *mem_ctx, + krb5_context context, + struct ldb_context *samdb, + uint32_t flags, + struct samba_kdc_entry *client, + const krb5_principal server_principal, + struct samba_kdc_entry *server, + struct samba_kdc_entry *krbtgt, + const krb5_principal delegated_proxy_principal, + const krb5_pac old_pac, + krb5_pac new_pac); diff --git a/source4/kdc/samba_kdc.h b/source4/kdc/samba_kdc.h new file mode 100644 index 0000000..5d73c5b --- /dev/null +++ b/source4/kdc/samba_kdc.h @@ -0,0 +1,73 @@ +/* + Unix SMB/CIFS implementation. + + KDC structures + + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Simo Sorce <idra@samba.org> 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SAMBA_KDC_H_ +#define _SAMBA_KDC_H_ + +struct samba_kdc_policy { + time_t svc_tkt_lifetime; + time_t usr_tkt_lifetime; + time_t renewal_lifetime; +}; + +struct samba_kdc_base_context { + struct tevent_context *ev_ctx; + struct loadparm_context *lp_ctx; + struct imessaging_context *msg_ctx; +}; + +struct samba_kdc_seq; + +struct samba_kdc_db_context { + struct tevent_context *ev_ctx; + struct loadparm_context *lp_ctx; + struct imessaging_context *msg_ctx; + struct ldb_context *samdb; + struct samba_kdc_seq *seq_ctx; + bool rodc; + unsigned int my_krbtgt_number; + struct ldb_dn *krbtgt_dn; + struct samba_kdc_policy policy; + struct ldb_dn *fx_cookie_dn; + struct ldb_context *secrets_db; +}; + +struct samba_kdc_entry { + struct samba_kdc_db_context *kdc_db_ctx; + const struct sdb_entry *db_entry; /* this is only temporary valid */ + const void *kdc_entry; /* this is a reference to hdb_entry/krb5_db_entry */ + struct ldb_message *msg; + struct ldb_dn *realm_dn; + struct auth_user_info_dc *user_info_dc; + bool is_krbtgt; + bool is_rodc; + bool is_trust; + uint32_t supported_enctypes; + NTSTATUS reject_status; +}; + +extern struct hdb_method hdb_samba4_interface; + +#define CHANGEPW_LIFETIME 60*2 /* 2 minutes */ + +#endif /* _SAMBA_KDC_H_ */ diff --git a/source4/kdc/sdb.c b/source4/kdc/sdb.c new file mode 100644 index 0000000..5fb1146 --- /dev/null +++ b/source4/kdc/sdb.c @@ -0,0 +1,212 @@ +/* + Unix SMB/CIFS implementation. + + Database Glue between Samba and the KDC + + Copyright (C) Guenther Deschner <gd@samba.org> 2014 + Copyright (C) Andreas Schneider <asn@samba.org> 2014 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include "sdb.h" +#include "samba_kdc.h" +#include "lib/krb5_wrap/krb5_samba.h" + +void sdb_key_free(struct sdb_key *k) +{ + if (k == NULL) { + return; + } + + /* + * Passing NULL as the Kerberos context is intentional here, as + * both Heimdal and MIT libraries don't use the context when + * clearing the keyblocks. + */ + krb5_free_keyblock_contents(NULL, &k->key); + + if (k->salt) { + smb_krb5_free_data_contents(NULL, &k->salt->salt); + SAFE_FREE(k->salt); + } + + ZERO_STRUCTP(k); +} + +void sdb_keys_free(struct sdb_keys *keys) +{ + unsigned int i; + + if (keys == NULL) { + return; + } + + for (i=0; i < keys->len; i++) { + sdb_key_free(&keys->val[i]); + } + + SAFE_FREE(keys->val); + ZERO_STRUCTP(keys); +} + +void sdb_entry_free(struct sdb_entry *s) +{ + if (s->skdc_entry != NULL) { + s->skdc_entry->db_entry = NULL; + TALLOC_FREE(s->skdc_entry); + } + + /* + * Passing NULL as the Kerberos context is intentional here, as both + * Heimdal and MIT libraries don't use the context when clearing the + * principals. + */ + krb5_free_principal(NULL, s->principal); + + sdb_keys_free(&s->keys); + sdb_keys_free(&s->old_keys); + sdb_keys_free(&s->older_keys); + krb5_free_principal(NULL, s->created_by.principal); + if (s->modified_by) { + krb5_free_principal(NULL, s->modified_by->principal); + } + SAFE_FREE(s->valid_start); + SAFE_FREE(s->valid_end); + SAFE_FREE(s->pw_end); + + ZERO_STRUCTP(s); +} + +struct SDBFlags int2SDBFlags(unsigned n) +{ + struct SDBFlags flags; + + memset(&flags, 0, sizeof(flags)); + + flags.initial = (n >> 0) & 1; + flags.forwardable = (n >> 1) & 1; + flags.proxiable = (n >> 2) & 1; + flags.renewable = (n >> 3) & 1; + flags.postdate = (n >> 4) & 1; + flags.server = (n >> 5) & 1; + flags.client = (n >> 6) & 1; + flags.invalid = (n >> 7) & 1; + flags.require_preauth = (n >> 8) & 1; + flags.change_pw = (n >> 9) & 1; + flags.require_hwauth = (n >> 10) & 1; + flags.ok_as_delegate = (n >> 11) & 1; + flags.user_to_user = (n >> 12) & 1; + flags.immutable = (n >> 13) & 1; + flags.trusted_for_delegation = (n >> 14) & 1; + flags.allow_kerberos4 = (n >> 15) & 1; + flags.allow_digest = (n >> 16) & 1; + flags.locked_out = (n >> 17) & 1; + flags.do_not_store = (n >> 31) & 1; + return flags; +} + +/* Set the etypes of an sdb_entry based on its available current keys. */ +krb5_error_code sdb_entry_set_etypes(struct sdb_entry *s) +{ + if (s->keys.val != NULL) { + unsigned i; + + s->etypes = malloc(sizeof(*s->etypes)); + if (s->etypes == NULL) { + return ENOMEM; + } + + s->etypes->len = s->keys.len; + + s->etypes->val = calloc(s->etypes->len, sizeof(*s->etypes->val)); + if (s->etypes->val == NULL) { + return ENOMEM; + } + + for (i = 0; i < s->etypes->len; i++) { + const struct sdb_key *k = &s->keys.val[i]; + + s->etypes->val[i] = KRB5_KEY_TYPE(&(k->key)); + } + } + + return 0; +} + +/* + * Set the session etypes of a server sdb_entry based on its etypes, forcing in + * strong etypes as desired. + */ +krb5_error_code sdb_entry_set_session_etypes(struct sdb_entry *s, + bool add_aes256, + bool add_aes128, + bool add_rc4) +{ + unsigned len = 0; + + if (add_aes256) { + /* Reserve space for AES256 */ + len += 1; + } + + if (add_aes128) { + /* Reserve space for AES128 */ + len += 1; + } + + if (add_rc4) { + /* Reserve space for RC4. */ + len += 1; + } + + if (len != 0) { + unsigned j = 0; + + s->session_etypes = malloc(sizeof(*s->session_etypes)); + if (s->session_etypes == NULL) { + return ENOMEM; + } + + /* session_etypes must be sorted in order of strength, with preferred etype first. */ + + s->session_etypes->val = calloc(len, sizeof(*s->session_etypes->val)); + if (s->session_etypes->val == NULL) { + SAFE_FREE(s->session_etypes); + return ENOMEM; + } + + if (add_aes256) { + /* Add AES256 */ + s->session_etypes->val[j++] = ENCTYPE_AES256_CTS_HMAC_SHA1_96; + } + + if (add_aes128) { + /* Add AES128. */ + s->session_etypes->val[j++] = ENCTYPE_AES128_CTS_HMAC_SHA1_96; + } + + if (add_rc4) { + /* Add RC4. */ + s->session_etypes->val[j++] = ENCTYPE_ARCFOUR_HMAC; + } + + s->session_etypes->len = j; + } + + return 0; +} diff --git a/source4/kdc/sdb.h b/source4/kdc/sdb.h new file mode 100644 index 0000000..ec1ce59 --- /dev/null +++ b/source4/kdc/sdb.h @@ -0,0 +1,146 @@ +/* + Unix SMB/CIFS implementation. + + Database Glue between Samba and the KDC + + Copyright (C) Guenther Deschner <gd@samba.org> 2014 + Copyright (C) Andreas Schneider <asn@samba.org> 2014 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _KDC_SDB_H_ +#define _KDC_SDB_H_ + +struct sdb_salt { + unsigned int type; + krb5_data salt; +}; + +struct sdb_key { + krb5_keyblock key; + struct sdb_salt *salt; +}; + +struct sdb_keys { + unsigned int len; + struct sdb_key *val; +}; + +struct sdb_event { + krb5_principal principal; + time_t time; +}; + +struct sdb_etypes { + unsigned len; + krb5_enctype *val; +}; + +struct SDBFlags { + unsigned int initial:1; + unsigned int forwardable:1; + unsigned int proxiable:1; + unsigned int renewable:1; + unsigned int postdate:1; + unsigned int server:1; + unsigned int client:1; + unsigned int invalid:1; + unsigned int require_preauth:1; + unsigned int change_pw:1; + unsigned int require_hwauth:1; + unsigned int ok_as_delegate:1; + unsigned int user_to_user:1; + unsigned int immutable:1; + unsigned int trusted_for_delegation:1; + unsigned int allow_kerberos4:1; + unsigned int allow_digest:1; + unsigned int locked_out:1; + unsigned int require_pwchange:1; + unsigned int materialize:1; + unsigned int virtual_keys:1; + unsigned int virtual:1; + unsigned int synthetic:1; + unsigned int no_auth_data_reqd:1; + unsigned int _unused24:1; + unsigned int _unused25:1; + unsigned int _unused26:1; + unsigned int _unused27:1; + unsigned int _unused28:1; + unsigned int _unused29:1; + unsigned int force_canonicalize:1; + unsigned int do_not_store:1; +}; + +struct sdb_entry { + struct samba_kdc_entry *skdc_entry; + krb5_principal principal; + unsigned int kvno; + struct sdb_keys keys; + struct sdb_etypes *etypes; + struct sdb_keys old_keys; + struct sdb_keys older_keys; + struct sdb_etypes *session_etypes; + struct sdb_event created_by; + struct sdb_event *modified_by; + time_t *valid_start; + time_t *valid_end; + time_t *pw_end; + unsigned int *max_life; + unsigned int *max_renew; + struct SDBFlags flags; +}; + +#define SDB_ERR_NOENTRY 36150275 +#define SDB_ERR_NOT_FOUND_HERE 36150287 +#define SDB_ERR_WRONG_REALM 36150289 + +/* These must match the values in hdb.h */ + +#define SDB_F_DECRYPT 1 /* decrypt keys */ +#define SDB_F_GET_CLIENT 4 /* fetch client */ +#define SDB_F_GET_SERVER 8 /* fetch server */ +#define SDB_F_GET_KRBTGT 16 /* fetch krbtgt */ +#define SDB_F_GET_ANY 28 /* fetch any of client,server,krbtgt */ +#define SDB_F_CANON 32 /* want canonicalition */ +#define SDB_F_ADMIN_DATA 64 /* want data that kdc don't use */ +#define SDB_F_KVNO_SPECIFIED 128 /* we want a particular KVNO */ +#define SDB_F_FOR_AS_REQ 4096 /* fetch is for a AS REQ */ +#define SDB_F_FOR_TGS_REQ 8192 /* fetch is for a TGS REQ */ + +#define SDB_F_HDB_MASK (SDB_F_DECRYPT | \ + SDB_F_GET_CLIENT| \ + SDB_F_GET_SERVER | \ + SDB_F_GET_KRBTGT | \ + SDB_F_CANON | \ + SDB_F_ADMIN_DATA | \ + SDB_F_KVNO_SPECIFIED | \ + SDB_F_FOR_AS_REQ | \ + SDB_F_FOR_TGS_REQ) + +/* This is not supported by HDB */ +#define SDB_F_FORCE_CANON 16384 /* force canonicalition */ + +void sdb_key_free(struct sdb_key *key); +void sdb_keys_free(struct sdb_keys *keys); +void sdb_entry_free(struct sdb_entry *e); +struct SDBFlags int2SDBFlags(unsigned n); +krb5_error_code sdb_entry_set_etypes(struct sdb_entry *s); +krb5_error_code sdb_entry_set_session_etypes(struct sdb_entry *s, + bool add_aes256, + bool add_aes128, + bool add_rc4); + +#endif /* _KDC_SDB_H_ */ diff --git a/source4/kdc/sdb_to_hdb.c b/source4/kdc/sdb_to_hdb.c new file mode 100644 index 0000000..06e20ae --- /dev/null +++ b/source4/kdc/sdb_to_hdb.c @@ -0,0 +1,323 @@ +/* + Unix SMB/CIFS implementation. + + Database Glue between Samba and the KDC + + Copyright (C) Guenther Deschner <gd@samba.org> 2014 + Copyright (C) Andreas Schneider <asn@samba.org> 2014 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <hdb.h> +#include "sdb.h" +#include "sdb_hdb.h" +#include "lib/krb5_wrap/krb5_samba.h" +#include "librpc/gen_ndr/security.h" +#include "kdc/samba_kdc.h" + +static void sdb_flags_to_hdb_flags(const struct SDBFlags *s, + HDBFlags *h) +{ + SMB_ASSERT(sizeof(struct SDBFlags) == sizeof(HDBFlags)); + + h->initial = s->initial; + h->forwardable = s->forwardable; + h->proxiable = s->proxiable; + h->renewable = s->renewable; + h->postdate = s->postdate; + h->server = s->server; + h->client = s->client; + h->invalid = s->invalid; + h->require_preauth = s->require_preauth; + h->change_pw = s->change_pw; + h->require_hwauth = s->require_hwauth; + h->ok_as_delegate = s->ok_as_delegate; + h->user_to_user = s->user_to_user; + h->immutable = s->immutable; + h->trusted_for_delegation = s->trusted_for_delegation; + h->allow_kerberos4 = s->allow_kerberos4; + h->allow_digest = s->allow_digest; + h->locked_out = s->locked_out; + h->require_pwchange = s->require_pwchange; + h->materialize = s->materialize; + h->virtual_keys = s->virtual_keys; + h->virtual = s->virtual; + h->synthetic = s->synthetic; + h->no_auth_data_reqd = s->no_auth_data_reqd; + h->_unused24 = s->_unused24; + h->_unused25 = s->_unused25; + h->_unused26 = s->_unused26; + h->_unused27 = s->_unused27; + h->_unused28 = s->_unused28; + h->_unused29 = s->_unused29; + h->force_canonicalize = s->force_canonicalize; + h->do_not_store = s->do_not_store; +} + +static int sdb_salt_to_Salt(const struct sdb_salt *s, Salt *h) +{ + int ret; + + h->type = s->type; + ret = smb_krb5_copy_data_contents(&h->salt, s->salt.data, s->salt.length); + if (ret != 0) { + free_Salt(h); + return ENOMEM; + } + h->opaque = NULL; + + return 0; +} + +static int sdb_key_to_Key(const struct sdb_key *s, Key *h) +{ + int rc; + + ZERO_STRUCTP(h); + + h->key.keytype = s->key.keytype; + rc = smb_krb5_copy_data_contents(&h->key.keyvalue, + s->key.keyvalue.data, + s->key.keyvalue.length); + if (rc != 0) { + goto error_nomem; + } + + if (s->salt != NULL) { + h->salt = malloc(sizeof(Salt)); + if (h->salt == NULL) { + goto error_nomem; + } + + rc = sdb_salt_to_Salt(s->salt, + h->salt); + if (rc != 0) { + goto error_nomem; + } + } else { + h->salt = NULL; + } + + return 0; + +error_nomem: + free_Key(h); + return ENOMEM; +} + +static int sdb_keys_to_Keys(const struct sdb_keys *s, Keys *h) +{ + int ret, i; + + h->len = s->len; + if (s->val != NULL) { + h->val = malloc(h->len * sizeof(Key)); + if (h->val == NULL) { + return ENOMEM; + } + for (i = 0; i < h->len; i++) { + ret = sdb_key_to_Key(&s->val[i], + &h->val[i]); + if (ret != 0) { + free_Keys(h); + return ENOMEM; + } + } + } else { + h->val = NULL; + } + + return 0; +} + +static int sdb_event_to_Event(krb5_context context, + const struct sdb_event *s, Event *h) +{ + int ret; + + if (s->principal != NULL) { + ret = krb5_copy_principal(context, + s->principal, + &h->principal); + if (ret != 0) { + free_Event(h); + return ret; + } + } else { + h->principal = NULL; + } + h->time = s->time; + + return 0; +} + +int sdb_entry_to_hdb_entry(krb5_context context, + const struct sdb_entry *s, + hdb_entry *h) +{ + struct samba_kdc_entry *ske = s->skdc_entry; + unsigned int i; + int rc; + + ZERO_STRUCTP(h); + + rc = krb5_copy_principal(context, + s->principal, + &h->principal); + if (rc != 0) { + return rc; + } + + h->kvno = s->kvno; + + rc = sdb_keys_to_Keys(&s->keys, &h->keys); + if (rc != 0) { + goto error; + } + + rc = sdb_event_to_Event(context, + &s->created_by, + &h->created_by); + if (rc != 0) { + goto error; + } + + if (s->modified_by) { + h->modified_by = malloc(sizeof(Event)); + if (h->modified_by == NULL) { + rc = ENOMEM; + goto error; + } + + rc = sdb_event_to_Event(context, + s->modified_by, + h->modified_by); + if (rc != 0) { + goto error; + } + } else { + h->modified_by = NULL; + } + + if (s->valid_start != NULL) { + h->valid_start = malloc(sizeof(KerberosTime)); + if (h->valid_start == NULL) { + rc = ENOMEM; + goto error; + } + *h->valid_start = *s->valid_start; + } else { + h->valid_start = NULL; + } + + if (s->valid_end != NULL) { + h->valid_end = malloc(sizeof(KerberosTime)); + if (h->valid_end == NULL) { + rc = ENOMEM; + goto error; + } + *h->valid_end = *s->valid_end; + } else { + h->valid_end = NULL; + } + + if (s->pw_end != NULL) { + h->pw_end = malloc(sizeof(KerberosTime)); + if (h->pw_end == NULL) { + rc = ENOMEM; + goto error; + } + *h->pw_end = *s->pw_end; + } else { + h->pw_end = NULL; + } + + if (s->max_life != NULL) { + h->max_life = malloc(sizeof(unsigned int)); + if (h->max_life == NULL) { + rc = ENOMEM; + goto error; + } + *h->max_life = *s->max_life; + } else { + h->max_life = NULL; + } + + if (s->max_renew != NULL) { + h->max_renew = malloc(sizeof(unsigned int)); + if (h->max_renew == NULL) { + rc = ENOMEM; + goto error; + } + *h->max_renew = *s->max_renew; + } else { + h->max_renew = NULL; + } + + sdb_flags_to_hdb_flags(&s->flags, &h->flags); + + h->etypes = NULL; + if (s->etypes != NULL) { + h->etypes = malloc(sizeof(*h->etypes)); + if (h->etypes == NULL) { + rc = ENOMEM; + goto error; + } + + h->etypes->len = s->etypes->len; + + h->etypes->val = calloc(h->etypes->len, sizeof(int)); + if (h->etypes->val == NULL) { + rc = ENOMEM; + goto error; + } + + for (i = 0; i < h->etypes->len; i++) { + h->etypes->val[i] = s->etypes->val[i]; + } + } + + h->session_etypes = NULL; + if (s->session_etypes != NULL) { + h->session_etypes = malloc(sizeof(*h->session_etypes)); + if (h->session_etypes == NULL) { + rc = ENOMEM; + goto error; + } + + h->session_etypes->len = s->session_etypes->len; + + h->session_etypes->val = calloc(h->session_etypes->len, sizeof(*h->session_etypes->val)); + if (h->session_etypes->val == NULL) { + rc = ENOMEM; + goto error; + } + + for (i = 0; i < h->session_etypes->len; ++i) { + h->session_etypes->val[i] = s->session_etypes->val[i]; + } + } + + h->context = ske; + if (ske != NULL) { + ske->kdc_entry = h; + } + return 0; +error: + free_hdb_entry(h); + return rc; +} diff --git a/source4/kdc/sdb_to_kdb.c b/source4/kdc/sdb_to_kdb.c new file mode 100644 index 0000000..c24fd73 --- /dev/null +++ b/source4/kdc/sdb_to_kdb.c @@ -0,0 +1,329 @@ +/* + Unix SMB/CIFS implementation. + + Database Glue between Samba and the KDC + + Copyright (C) Guenther Deschner <gd@samba.org> 2014 + Copyright (C) Andreas Schneider <asn@samba.org> 2014 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <kdb.h> +#include "sdb.h" +#include "sdb_kdb.h" +#include "kdc/samba_kdc.h" +#include "lib/krb5_wrap/krb5_samba.h" + +static int SDBFlags_to_kflags(const struct SDBFlags *s, + krb5_flags *k) +{ + *k = 0; + + if (s->initial) { + *k |= KRB5_KDB_DISALLOW_TGT_BASED; + } + /* The forwardable and proxiable flags are set according to client and + * server attributes. */ + if (!s->forwardable) { + *k |= KRB5_KDB_DISALLOW_FORWARDABLE; + } + if (!s->proxiable) { + *k |= KRB5_KDB_DISALLOW_PROXIABLE; + } + if (s->renewable) { + ; + } + if (s->postdate) { + ; + } + if (s->server) { + ; + } + if (s->client) { + ; + } + if (s->invalid) { + *k |= KRB5_KDB_DISALLOW_ALL_TIX; + } + if (s->require_preauth) { + *k |= KRB5_KDB_REQUIRES_PRE_AUTH; + } + if (s->change_pw) { + *k |= KRB5_KDB_PWCHANGE_SERVICE; + } +#if 0 + /* + * Do not set KRB5_KDB_REQUIRES_HW_AUTH as this would tell the client + * to enforce hardware authentication. It prevents the use of files + * based public key authentication which we use for testing. + */ + if (s->require_hwauth) { + *k |= KRB5_KDB_REQUIRES_HW_AUTH; + } +#endif + if (s->ok_as_delegate) { + *k |= KRB5_KDB_OK_AS_DELEGATE; + } + if (s->user_to_user) { + ; + } + if (s->immutable) { + ; + } + if (s->trusted_for_delegation) { + *k |= KRB5_KDB_OK_TO_AUTH_AS_DELEGATE; + } + if (s->allow_kerberos4) { + ; + } + if (s->allow_digest) { + ; + } + if (s->no_auth_data_reqd) { + *k |= KRB5_KDB_NO_AUTH_DATA_REQUIRED; + } + + return 0; +} + +static int sdb_event_to_kmod(krb5_context context, + const struct sdb_event *s, + krb5_db_entry *k) +{ + krb5_error_code ret; + krb5_principal principal = NULL; + + if (s->principal != NULL) { + ret = krb5_copy_principal(context, + s->principal, + &principal); + if (ret != 0) { + return ret; + } + } + + ret = krb5_dbe_update_mod_princ_data(context, + k, s->time, + principal); + + krb5_free_principal(context, principal); + + return ret; +} + +/* sets up salt on the 2nd array position */ + +static int sdb_salt_to_krb5_key_data(const struct sdb_salt *s, + krb5_key_data *k) +{ + switch (s->type) { +#if 0 + /* for now use the special mechanism where the MIT KDC creates the salt + * on its own */ + case 3: /* FIXME KRB5_PW_SALT */ + k->key_data_type[1] = KRB5_KDB_SALTTYPE_NORMAL; + break; + /* + case hdb_afs3_salt: + k->key_data_type[1] = KRB5_KDB_SALTTYPE_AFS3; + break; + */ +#endif + default: + k->key_data_type[1] = KRB5_KDB_SALTTYPE_SPECIAL; + break; + } + + k->key_data_contents[1] = malloc(s->salt.length); + if (k->key_data_contents[1] == NULL) { + return ENOMEM; + } + memcpy(k->key_data_contents[1], + s->salt.data, + s->salt.length); + k->key_data_length[1] = s->salt.length; + + return 0; +} + +static int sdb_key_to_krb5_key_data(const struct sdb_key *s, + int kvno, + krb5_key_data *k) +{ + int ret = 0; + + ZERO_STRUCTP(k); + + k->key_data_ver = KRB5_KDB_V1_KEY_DATA_ARRAY; + k->key_data_kvno = kvno; + + k->key_data_type[0] = KRB5_KEY_TYPE(&s->key); + k->key_data_length[0] = KRB5_KEY_LENGTH(&s->key); + k->key_data_contents[0] = malloc(k->key_data_length[0]); + if (k->key_data_contents[0] == NULL) { + return ENOMEM; + } + + memcpy(k->key_data_contents[0], + KRB5_KEY_DATA(&s->key), + k->key_data_length[0]); + + if (s->salt != NULL) { + ret = sdb_salt_to_krb5_key_data(s->salt, k); + if (ret) { + memset(k->key_data_contents[0], 0, k->key_data_length[0]); + free(k->key_data_contents[0]); + } + } + + return ret; +} + +static void free_krb5_db_entry(krb5_context context, + krb5_db_entry *k) +{ + krb5_tl_data *tl_data_next = NULL; + krb5_tl_data *tl_data = NULL; + int i, j; + + if (k == NULL) { + return; + } + + krb5_free_principal(context, k->princ); + + for (tl_data = k->tl_data; tl_data; tl_data = tl_data_next) { + tl_data_next = tl_data->tl_data_next; + if (tl_data->tl_data_contents != NULL) { + free(tl_data->tl_data_contents); + } + free(tl_data); + } + + if (k->key_data != NULL) { + for (i = 0; i < k->n_key_data; i++) { + for (j = 0; j < k->key_data[i].key_data_ver; j++) { + if (k->key_data[i].key_data_length[j] != 0) { + if (k->key_data[i].key_data_contents[j] != NULL) { + memset(k->key_data[i].key_data_contents[j], 0, k->key_data[i].key_data_length[j]); + free(k->key_data[i].key_data_contents[j]); + } + } + k->key_data[i].key_data_contents[j] = NULL; + k->key_data[i].key_data_length[j] = 0; + k->key_data[i].key_data_type[j] = 0; + } + } + free(k->key_data); + } + + ZERO_STRUCTP(k); +} + +int sdb_entry_to_krb5_db_entry(krb5_context context, + const struct sdb_entry *s, + krb5_db_entry *k) +{ + struct samba_kdc_entry *ske = s->skdc_entry; + krb5_error_code ret; + int i; + + ZERO_STRUCTP(k); + + k->magic = KRB5_KDB_MAGIC_NUMBER; + k->len = KRB5_KDB_V1_BASE_LENGTH; + + ret = krb5_copy_principal(context, + s->principal, + &k->princ); + if (ret) { + free_krb5_db_entry(context, k); + return ret; + } + + ret = SDBFlags_to_kflags(&s->flags, + &k->attributes); + if (ret) { + free_krb5_db_entry(context, k); + return ret; + } + + if (s->max_life != NULL) { + k->max_life = *s->max_life; + } + if (s->max_renew != NULL) { + k->max_renewable_life = *s->max_renew; + } + if (s->valid_end != NULL) { + k->expiration = *s->valid_end; + } + if (s->pw_end != NULL) { + k->pw_expiration = *s->pw_end; + } + + /* last_success */ + /* last_failed */ + /* fail_auth_count */ + /* n_tl_data */ + + /* + * If we leave early when looking up the realm, we do not have all + * information about a principal. We need to construct a db entry + * with minimal information, so skip this part. + */ + if (s->created_by.time != 0) { + ret = sdb_event_to_kmod(context, + s->modified_by ? s->modified_by : &s->created_by, + k); + if (ret) { + free_krb5_db_entry(context, k); + return ret; + } + } + + /* FIXME: TODO HDB Extensions */ + + /* + * Don't copy keys (allow password auth) if s->flags.require_hwauth is + * set which translates to UF_SMARTCARD_REQUIRED. + */ + if (s->keys.len > 0 && s->flags.require_hwauth == 0) { + k->key_data = malloc(s->keys.len * sizeof(krb5_key_data)); + if (k->key_data == NULL) { + free_krb5_db_entry(context, k); + return ret; + } + + for (i=0; i < s->keys.len; i++) { + ret = sdb_key_to_krb5_key_data(&s->keys.val[i], + s->kvno, + &k->key_data[i]); + if (ret) { + free_krb5_db_entry(context, k); + return ret; + } + + k->n_key_data++; + } + } + + k->e_data = (void *)ske; + if (ske != NULL) { + ske->kdc_entry = k; + } + return 0; +} diff --git a/source4/kdc/wdc-samba4.c b/source4/kdc/wdc-samba4.c new file mode 100644 index 0000000..06025cc --- /dev/null +++ b/source4/kdc/wdc-samba4.c @@ -0,0 +1,642 @@ +/* + Unix SMB/CIFS implementation. + + PAC Glue between Samba and the KDC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009 + Copyright (C) Simo Sorce <idra@samba.org> 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "kdc/kdc-glue.h" +#include "kdc/db-glue.h" +#include "kdc/pac-glue.h" +#include "sdb.h" +#include "sdb_hdb.h" +#include "librpc/gen_ndr/auth.h" +#include <krb5_locl.h> + +static bool samba_wdc_is_s4u2self_req(astgs_request_t r) +{ + krb5_kdc_configuration *config = kdc_request_get_config((kdc_request_t)r); + const KDC_REQ *req = kdc_request_get_req(r); + const PA_DATA *pa_for_user = NULL; + + if (req->msg_type != krb_tgs_req) { + return false; + } + + if (config->enable_fast && req->padata != NULL) { + const PA_DATA *pa_fx_fast = NULL; + int idx = 0; + + pa_fx_fast = krb5_find_padata(req->padata->val, + req->padata->len, + KRB5_PADATA_FX_FAST, + &idx); + if (pa_fx_fast != NULL) { + /* + * We're in the outer request + * with KRB5_PADATA_FX_FAST + * if fast is enabled we'll + * process the s4u2self + * request only in the + * inner request. + */ + return false; + } + } + + if (req->padata != NULL) { + int idx = 0; + + pa_for_user = krb5_find_padata(req->padata->val, + req->padata->len, + KRB5_PADATA_FOR_USER, + &idx); + } + + if (pa_for_user != NULL) { + return true; + } + + return false; +} + +/* + * Given the right private pointer from hdb_samba4, + * get a PAC from the attached ldb messages. + * + * For PKINIT we also get pk_reply_key and can add PAC_CREDENTIAL_INFO. + */ +static krb5_error_code samba_wdc_get_pac(void *priv, + astgs_request_t r, + hdb_entry *client, + hdb_entry *server, + const krb5_keyblock *pk_reply_key, + uint64_t pac_attributes, + krb5_pac *pac) +{ + krb5_context context = kdc_request_get_context((kdc_request_t)r); + TALLOC_CTX *mem_ctx; + DATA_BLOB *logon_blob = NULL; + DATA_BLOB *cred_ndr = NULL; + DATA_BLOB **cred_ndr_ptr = NULL; + DATA_BLOB _cred_blob = data_blob_null; + DATA_BLOB *cred_blob = NULL; + DATA_BLOB *upn_blob = NULL; + DATA_BLOB *pac_attrs_blob = NULL; + DATA_BLOB *requester_sid_blob = NULL; + krb5_error_code ret; + NTSTATUS nt_status; + struct samba_kdc_entry *skdc_entry = + talloc_get_type_abort(client->context, + struct samba_kdc_entry); + bool is_krbtgt; + bool is_s4u2self = samba_wdc_is_s4u2self_req(r); + enum samba_asserted_identity asserted_identity = + (is_s4u2self) ? + SAMBA_ASSERTED_IDENTITY_SERVICE : + SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY; + + mem_ctx = talloc_named(client->context, 0, "samba_get_pac context"); + if (!mem_ctx) { + return ENOMEM; + } + + if (pk_reply_key != NULL) { + cred_ndr_ptr = &cred_ndr; + } + + is_krbtgt = krb5_principal_is_krbtgt(context, server->principal); + + nt_status = samba_kdc_get_pac_blobs(mem_ctx, skdc_entry, + asserted_identity, + &logon_blob, + cred_ndr_ptr, + &upn_blob, + is_krbtgt ? &pac_attrs_blob : NULL, + pac_attributes, + is_krbtgt ? &requester_sid_blob : NULL); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(mem_ctx); + return EINVAL; + } + + if (pk_reply_key != NULL && cred_ndr != NULL) { + ret = samba_kdc_encrypt_pac_credentials(context, + pk_reply_key, + cred_ndr, + mem_ctx, + &_cred_blob); + if (ret != 0) { + talloc_free(mem_ctx); + return ret; + } + cred_blob = &_cred_blob; + } + + ret = krb5_pac_init(context, pac); + if (ret != 0) { + talloc_free(mem_ctx); + return ret; + } + + ret = samba_make_krb5_pac(context, logon_blob, cred_blob, + upn_blob, pac_attrs_blob, + requester_sid_blob, NULL, *pac); + + talloc_free(mem_ctx); + return ret; +} + +static krb5_error_code samba_wdc_reget_pac2(astgs_request_t r, + const krb5_principal delegated_proxy_principal, + hdb_entry *client, + hdb_entry *server, + hdb_entry *krbtgt, + krb5_pac *pac, + krb5_cksumtype ctype) +{ + krb5_context context = kdc_request_get_context((kdc_request_t)r); + struct samba_kdc_entry *client_skdc_entry = NULL; + struct samba_kdc_entry *server_skdc_entry = + talloc_get_type_abort(server->context, struct samba_kdc_entry); + struct samba_kdc_entry *krbtgt_skdc_entry = + talloc_get_type_abort(krbtgt->context, struct samba_kdc_entry); + TALLOC_CTX *mem_ctx = NULL; + krb5_pac new_pac = NULL; + krb5_error_code ret; + bool is_s4u2self = samba_wdc_is_s4u2self_req(r); + bool is_in_db = false; + bool is_untrusted = false; + uint32_t flags = 0; + + mem_ctx = talloc_named(NULL, 0, "samba_kdc_reget_pac2 context"); + if (mem_ctx == NULL) { + return ENOMEM; + } + + if (client != NULL) { + client_skdc_entry = talloc_get_type_abort(client->context, + struct samba_kdc_entry); + } + + /* + * If the krbtgt was generated by an RODC, and we are not that + * RODC, then we need to regenerate the PAC - we can't trust + * it, and confirm that the RODC was permitted to print this ticket + * + * Becasue of the samba_kdc_validate_pac_blob() step we can be + * sure that the record in 'client' matches the SID in the + * original PAC. + */ + ret = samba_krbtgt_is_in_db(krbtgt_skdc_entry, &is_in_db, &is_untrusted); + if (ret != 0) { + goto out; + } + + if (is_s4u2self) { + flags |= SAMBA_KDC_FLAG_PROTOCOL_TRANSITION; + } + + if (delegated_proxy_principal != NULL) { + krb5_enctype etype; + Key *key = NULL; + + if (!is_in_db) { + /* + * The RODC-issued PAC was signed by a KDC entry that we + * don't have a key for. The server signature is not + * trustworthy, since it could have been created by the + * server we got the ticket from. We must not proceed as + * otherwise the ticket signature is unchecked. + */ + ret = HDB_ERR_NOT_FOUND_HERE; + goto out; + } + + /* Fetch the correct key depending on the checksum type. */ + if (ctype == CKSUMTYPE_HMAC_MD5) { + etype = ENCTYPE_ARCFOUR_HMAC; + } else { + ret = krb5_cksumtype_to_enctype(context, + ctype, + &etype); + if (ret != 0) { + goto out; + } + } + ret = hdb_enctype2key(context, krbtgt, NULL, etype, &key); + if (ret != 0) { + goto out; + } + + /* Check the KDC, whole-PAC and ticket signatures. */ + ret = krb5_pac_verify(context, + *pac, + 0, + NULL, + NULL, + &key->key); + if (ret != 0) { + DEBUG(1, ("PAC KDC signature failed to verify\n")); + goto out; + } + + flags |= SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION; + } + + if (is_untrusted) { + flags |= SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED; + } + + if (is_in_db) { + flags |= SAMBA_KDC_FLAG_KRBTGT_IN_DB; + } + + ret = krb5_pac_init(context, &new_pac); + if (ret != 0) { + new_pac = NULL; + goto out; + } + + ret = samba_kdc_update_pac(mem_ctx, + context, + krbtgt_skdc_entry->kdc_db_ctx->samdb, + flags, + client_skdc_entry, + server->principal, + server_skdc_entry, + krbtgt_skdc_entry, + delegated_proxy_principal, + *pac, + new_pac); + if (ret != 0) { + krb5_pac_free(context, new_pac); + if (ret == ENODATA) { + krb5_pac_free(context, *pac); + *pac = NULL; + ret = 0; + } + goto out; + } + + /* Replace the pac */ + krb5_pac_free(context, *pac); + *pac = new_pac; + +out: + talloc_free(mem_ctx); + return ret; +} + +/* Resign (and reform, including possibly new groups) a PAC */ + +static krb5_error_code samba_wdc_reget_pac(void *priv, astgs_request_t r, + const krb5_principal client_principal, + const krb5_principal delegated_proxy_principal, + hdb_entry *client, + hdb_entry *server, + hdb_entry *krbtgt, + krb5_pac *pac) +{ + krb5_context context = kdc_request_get_context((kdc_request_t)r); + krb5_kdc_configuration *config = kdc_request_get_config((kdc_request_t)r); + struct samba_kdc_entry *krbtgt_skdc_entry = + talloc_get_type_abort(krbtgt->context, + struct samba_kdc_entry); + krb5_error_code ret; + krb5_cksumtype ctype = CKSUMTYPE_NONE; + hdb_entry signing_krbtgt_hdb; + + if (delegated_proxy_principal) { + uint16_t rodc_id; + unsigned int my_krbtgt_number; + + /* + * We're using delegated_proxy_principal for the moment to + * indicate cases where the ticket was encrypted with the server + * key, and not a krbtgt key. This cannot be trusted, so we need + * to find a krbtgt key that signs the PAC in order to trust the + * ticket. + * + * The krbtgt passed in to this function refers to the krbtgt + * used to decrypt the ticket of the server requesting + * S4U2Proxy. + * + * When we implement service ticket renewal, we need to check + * the PAC, and this will need to be updated. + */ + ret = krb5_pac_get_kdc_checksum_info(context, + *pac, + &ctype, + &rodc_id); + if (ret != 0) { + DEBUG(1, ("Failed to get PAC checksum info\n")); + return ret; + } + + /* + * We need to check the KDC and ticket signatures, fetching the + * correct key based on the enctype. + */ + + my_krbtgt_number = krbtgt_skdc_entry->kdc_db_ctx->my_krbtgt_number; + + if (my_krbtgt_number != 0) { + /* + * If we are an RODC, and we are not the KDC that signed + * the evidence ticket, then we need to proxy the + * request. + */ + if (rodc_id != my_krbtgt_number) { + return HDB_ERR_NOT_FOUND_HERE; + } + } else { + /* + * If we are a DC, the ticket may have been signed by a + * different KDC than the one that issued the header + * ticket. + */ + if (rodc_id != krbtgt->kvno >> 16) { + struct sdb_entry signing_krbtgt_sdb; + + /* + * If we didn't sign the ticket, then return an + * error. + */ + if (rodc_id != 0) { + return KRB5KRB_AP_ERR_MODIFIED; + } + + /* + * Fetch our key from the database. To support + * key rollover, we're going to need to try + * multiple keys by trial and error. For now, + * krbtgt keys aren't assumed to change. + */ + ret = samba_kdc_fetch(context, + krbtgt_skdc_entry->kdc_db_ctx, + krbtgt->principal, + SDB_F_GET_KRBTGT | SDB_F_CANON, + 0, + &signing_krbtgt_sdb); + if (ret != 0) { + return ret; + } + + ret = sdb_entry_to_hdb_entry(context, + &signing_krbtgt_sdb, + &signing_krbtgt_hdb); + sdb_entry_free(&signing_krbtgt_sdb); + if (ret != 0) { + return ret; + } + + /* + * Replace the krbtgt entry with our own entry + * for further processing. + */ + krbtgt = &signing_krbtgt_hdb; + } + } + } else if (!krbtgt_skdc_entry->is_trust) { + /* + * We expect to have received a TGT, so check that we haven't + * been given a kpasswd ticket instead. We don't need to do this + * check for an incoming trust, as they use a different secret + * and can't be confused with a normal TGT. + */ + krb5_ticket *tgt = kdc_request_get_ticket(r); + + struct timeval now = krb5_kdc_get_time(); + + /* + * Check if the ticket is in the last two minutes of its + * life. + */ + KerberosTime lifetime = rk_time_sub(tgt->ticket.endtime, now.tv_sec); + if (lifetime <= CHANGEPW_LIFETIME) { + /* + * This ticket has at most two minutes left to live. It + * may be a kpasswd ticket rather than a TGT, so don't + * accept it. + */ + kdc_audit_addreason((kdc_request_t)r, + "Ticket is not a ticket-granting ticket"); + return KRB5KRB_AP_ERR_TKT_EXPIRED; + } + } + + ret = samba_wdc_reget_pac2(r, + delegated_proxy_principal, + client, + server, + krbtgt, + pac, + ctype); + + if (krbtgt == &signing_krbtgt_hdb) { + hdb_free_entry(context, config->db[0], &signing_krbtgt_hdb); + } + + return ret; +} + +static char *get_netbios_name(TALLOC_CTX *mem_ctx, HostAddresses *addrs) +{ + char *nb_name = NULL; + size_t len; + unsigned int i; + + for (i = 0; addrs && i < addrs->len; i++) { + if (addrs->val[i].addr_type != KRB5_ADDRESS_NETBIOS) { + continue; + } + len = MIN(addrs->val[i].address.length, 15); + nb_name = talloc_strndup(mem_ctx, + addrs->val[i].address.data, len); + if (nb_name) { + break; + } + } + + if ((nb_name == NULL) || (nb_name[0] == '\0')) { + return NULL; + } + + /* Strip space padding */ + for (len = strlen(nb_name) - 1; + (len > 0) && (nb_name[len] == ' '); + --len) { + nb_name[len] = '\0'; + } + + return nb_name; +} + +/* this function allocates 'data' using malloc. + * The caller is responsible for freeing it */ +static void samba_kdc_build_edata_reply(NTSTATUS nt_status, krb5_data *e_data) +{ + e_data->data = malloc(12); + if (e_data->data == NULL) { + e_data->length = 0; + e_data->data = NULL; + return; + } + e_data->length = 12; + + SIVAL(e_data->data, 0, NT_STATUS_V(nt_status)); + SIVAL(e_data->data, 4, 0); + SIVAL(e_data->data, 8, 1); + + return; +} + +static krb5_error_code samba_wdc_check_client_access(void *priv, + astgs_request_t r) +{ + struct samba_kdc_entry *kdc_entry; + bool password_change; + char *workstation; + NTSTATUS nt_status; + + + kdc_entry = talloc_get_type(kdc_request_get_client(r)->context, struct samba_kdc_entry); + password_change = (kdc_request_get_server(r) && kdc_request_get_server(r)->flags.change_pw); + workstation = get_netbios_name((TALLOC_CTX *)kdc_request_get_client(r)->context, + kdc_request_get_req(r)->req_body.addresses); + + nt_status = samba_kdc_check_client_access(kdc_entry, + kdc_request_get_cname((kdc_request_t)r), + workstation, + password_change); + + if (!NT_STATUS_IS_OK(nt_status)) { + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MEMORY)) { + return ENOMEM; + } + + if (kdc_request_get_rep(r)->padata) { + int ret; + krb5_data kd; + + samba_kdc_build_edata_reply(nt_status, &kd); + ret = krb5_padata_add(kdc_request_get_context((kdc_request_t)r), kdc_request_get_rep(r)->padata, + KRB5_PADATA_PW_SALT, + kd.data, kd.length); + if (ret != 0) { + /* + * So we do not leak the allocated + * memory on kd in the error case + */ + krb5_data_free(&kd); + } + } + + return samba_kdc_map_policy_err(nt_status); + } + + /* Now do the standard Heimdal check */ + return KRB5_PLUGIN_NO_HANDLE; +} + +/* this function allocates 'data' using malloc. + * The caller is responsible for freeing it */ +static krb5_error_code samba_kdc_build_supported_etypes(uint32_t supported_etypes, + krb5_data *e_data) +{ + e_data->data = malloc(4); + if (e_data->data == NULL) { + return ENOMEM; + } + e_data->length = 4; + + PUSH_LE_U32(e_data->data, 0, supported_etypes); + + return 0; +} + +static krb5_error_code samba_wdc_finalize_reply(void *priv, + astgs_request_t r) +{ + struct samba_kdc_entry *server_kdc_entry; + uint32_t supported_enctypes; + + server_kdc_entry = talloc_get_type(kdc_request_get_server(r)->context, struct samba_kdc_entry); + + /* + * If the canonicalize flag is set, add PA-SUPPORTED-ENCTYPES padata + * type to indicate what encryption types the server supports. + */ + supported_enctypes = server_kdc_entry->supported_enctypes; + if (kdc_request_get_req(r)->req_body.kdc_options.canonicalize && supported_enctypes != 0) { + krb5_error_code ret; + + PA_DATA md; + + ret = samba_kdc_build_supported_etypes(supported_enctypes, &md.padata_value); + if (ret != 0) { + return ret; + } + + md.padata_type = KRB5_PADATA_SUPPORTED_ETYPES; + + ret = kdc_request_add_encrypted_padata(r, &md); + if (ret != 0) { + /* + * So we do not leak the allocated + * memory on kd in the error case + */ + krb5_data_free(&md.padata_value); + } + } + + return 0; +} + +static krb5_error_code samba_wdc_plugin_init(krb5_context context, void **ptr) +{ + *ptr = NULL; + return 0; +} + +static void samba_wdc_plugin_fini(void *ptr) +{ + return; +} + +static krb5_error_code samba_wdc_referral_policy(void *priv, + astgs_request_t r) +{ + return kdc_request_get_error_code((kdc_request_t)r); +} + +struct krb5plugin_kdc_ftable kdc_plugin_table = { + .minor_version = KRB5_PLUGIN_KDC_VERSION_10, + .init = samba_wdc_plugin_init, + .fini = samba_wdc_plugin_fini, + .pac_verify = samba_wdc_reget_pac, + .client_access = samba_wdc_check_client_access, + .finalize_reply = samba_wdc_finalize_reply, + .pac_generate = samba_wdc_get_pac, + .referral_policy = samba_wdc_referral_policy, +}; + + diff --git a/source4/kdc/wscript_build b/source4/kdc/wscript_build new file mode 100644 index 0000000..0c902f5 --- /dev/null +++ b/source4/kdc/wscript_build @@ -0,0 +1,182 @@ +#!/usr/bin/env python + +# We do this because we do not want to depend on the KDC, only find and use it's header files. We do not want +if not bld.CONFIG_SET("USING_SYSTEM_KDC"): + kdc_include = "../../third_party/heimdal/kdc ../../third_party/heimdal/lib/gssapi" +else: + kdc_include = getattr(bld.env, "CPPPATH_KDC") + +if bld.CONFIG_SET('SAMBA4_USES_HEIMDAL'): + bld.SAMBA_MODULE('service_kdc', + source='kdc-heimdal.c', + subsystem='service', + init_function='server_service_kdc_init', + deps=''' + kdc + HDB_SAMBA4 + WDC_SAMBA4 + samba-hostconfig + com_err + samba_server_gensec + PAC_GLUE + KDC-GLUE + KDC-SERVER + KPASSWD-SERVICE + KPASSWD_GLUE + ''', + internal_module=False) + +if bld.CONFIG_GET('SAMBA_USES_MITKDC'): + bld.SAMBA_MODULE('service_kdc', + source='kdc-service-mit.c', + cflags_end='-Wno-strict-prototypes', + subsystem='service', + init_function='server_service_mitkdc_init', + deps=''' + samba-hostconfig + service + talloc + UTIL_RUNCMD + MIT_KDC_IRPC + KDC-SERVER + KPASSWD-SERVICE + com_err + kadm5srv_mit + kdb5 + ''', + internal_module=False) + +bld.SAMBA_LIBRARY('HDB_SAMBA4', + source='hdb-samba4.c hdb-samba4-plugin.c', + deps='ldb auth4_sam common_auth samba-credentials hdb kdc db-glue samba-hostconfig com_err sdb_hdb RPC_NDR_WINBIND', + includes=kdc_include, + private_library=True, + enabled=bld.CONFIG_SET('SAMBA4_USES_HEIMDAL') + ) + +# A plugin for Heimdal's kadmin for users who need to operate that tool +bld.SAMBA_LIBRARY('HDB_SAMBA4_PLUGIN', + source='hdb-samba4-plugin.c', + deps='hdb HDB_SAMBA4 samba-util samba-hostconfig ', + link_name='modules/hdb/hdb_samba4.so', + realname='hdb_samba4.so', + install_path='${MODULESDIR}/hdb', + enabled = (bld.CONFIG_SET("USING_SYSTEM_KRB5") and bld.CONFIG_SET("USING_SYSTEM_HDB")) + ) + +bld.SAMBA_SUBSYSTEM('KDC-SERVER', + source='kdc-server.c kdc-proxy.c', + deps=''' + krb5samba + ldb + LIBTSOCKET + LIBSAMBA_TSOCKET + ''') + +kpasswd_flavor_src = 'kpasswd-service.c kpasswd-helper.c' +if bld.CONFIG_SET('SAMBA4_USES_HEIMDAL'): + kpasswd_flavor_src = kpasswd_flavor_src + ' kpasswd-service-heimdal.c' +elif bld.CONFIG_GET('SAMBA_USES_MITKDC'): + kpasswd_flavor_src = kpasswd_flavor_src + ' kpasswd-service-mit.c' + +bld.SAMBA_SUBSYSTEM('KPASSWD-SERVICE', + source=kpasswd_flavor_src, + deps=''' + krb5samba + samba_server_gensec + KPASSWD_GLUE + gensec_krb5_helpers + ''') + +bld.SAMBA_SUBSYSTEM('KDC-GLUE', + source='kdc-glue.c', + includes=kdc_include, + deps='hdb PAC_GLUE', + enabled=bld.CONFIG_SET('SAMBA4_USES_HEIMDAL') + ) + +bld.SAMBA_SUBSYSTEM('WDC_SAMBA4', + source='wdc-samba4.c', + includes=kdc_include, + deps='ldb auth4_sam common_auth samba-credentials hdb PAC_GLUE samba-hostconfig com_err KDC-GLUE', + enabled=bld.CONFIG_SET('SAMBA4_USES_HEIMDAL') + ) + +bld.SAMBA_SUBSYSTEM('sdb', + source='sdb.c', + deps='talloc krb5', + ) + +bld.SAMBA_SUBSYSTEM('sdb_hdb', + source='sdb_to_hdb.c', + deps='talloc sdb hdb', + autoproto='sdb_hdb.h', + enabled=bld.CONFIG_SET('SAMBA4_USES_HEIMDAL') + ) + +bld.SAMBA_SUBSYSTEM('sdb_kdb', + source='sdb_to_kdb.c', + deps='sdb kdb5', + autoproto='sdb_kdb.h', + enabled=bld.CONFIG_SET('HAVE_KDB_H') + ) + +bld.SAMBA_SUBSYSTEM('PAC_GLUE', + source='pac-glue.c', + deps='ldb auth4_sam common_auth samba-credentials samba-hostconfig com_err' + ) + +bld.SAMBA_LIBRARY('pac', + source=[], + deps='PAC_GLUE', + private_library=True, + grouping_library=True) + + +bld.SAMBA_LIBRARY('db-glue', + source='db-glue.c', + deps='ldb auth4_sam common_auth samba-credentials sdb samba-hostconfig com_err RPC_NDR_IRPC MESSAGING PAC_GLUE', + private_library=True, + ) + +bld.SAMBA_SUBSYSTEM('KPASSWD_GLUE', + source='kpasswd_glue.c', + deps='ldb com_err') + +bld.SAMBA_SUBSYSTEM('MIT_KDC_IRPC', + source='mit_kdc_irpc.c', + deps=''' + ldb + auth4_sam + samba-credentials + db-glue + samba-hostconfig + com_err + kdb5 + ''', + enabled=(bld.CONFIG_SET('SAMBA_USES_MITKDC') and bld.CONFIG_SET('HAVE_KDB_H')) + ) + +bld.SAMBA_SUBSYSTEM('MIT_SAMBA', + source='mit_samba.c', + deps=''' + ldb + auth4_sam + common_auth + samba-credentials + db-glue + PAC_GLUE + KPASSWD_GLUE + samba-hostconfig + com_err + sdb_kdb + kdb5 + ''', + enabled=(not bld.CONFIG_SET('SAMBA4_USES_HEIMDAL') and bld.CONFIG_SET('HAVE_KDB_H')) ) + +bld.SAMBA_BINARY('samba4ktutil', + 'ktutil.c', + deps='krb5samba', + install=False) + +bld.RECURSE('mit-kdb') |