diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
commit | 8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch) | |
tree | 4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source4/kdc/mit_samba.c | |
parent | Initial commit. (diff) | |
download | samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip |
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/kdc/mit_samba.c')
-rw-r--r-- | source4/kdc/mit_samba.c | 1065 |
1 files changed, 1065 insertions, 0 deletions
diff --git a/source4/kdc/mit_samba.c b/source4/kdc/mit_samba.c new file mode 100644 index 0000000..ae8895d --- /dev/null +++ b/source4/kdc/mit_samba.c @@ -0,0 +1,1065 @@ +/* + MIT-Samba4 library + + Copyright (c) 2010, Simo Sorce <idra@samba.org> + Copyright (c) 2014-2015 Guenther Deschner <gd@samba.org> + Copyright (c) 2014-2016 Andreas Schneider <asn@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define TEVENT_DEPRECATED 1 + +#include "includes.h" +#include "param/param.h" +#include "dsdb/samdb/samdb.h" +#include "system/kerberos.h" +#include "lib/replace/system/filesys.h" +#include <com_err.h> +#include <kdb.h> +#include <kadm5/kadm_err.h> +#include "kdc/sdb.h" +#include "kdc/sdb_kdb.h" +#include "auth/kerberos/kerberos.h" +#include "auth/kerberos/pac_utils.h" +#include "kdc/samba_kdc.h" +#include "kdc/pac-glue.h" +#include "kdc/db-glue.h" +#include "auth/auth.h" +#include "kdc/kpasswd_glue.h" +#include "auth/auth_sam.h" + +#include "mit_samba.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_KERBEROS + +void mit_samba_context_free(struct mit_samba_context *ctx) +{ + /* free MIT's krb5_context */ + if (ctx->context) { + krb5_free_context(ctx->context); + } + + /* then free everything else */ + talloc_free(ctx); +} + +/* + * Implement a callback to log to the MIT KDC log facility + * + * http://web.mit.edu/kerberos/krb5-devel/doc/plugindev/general.html#logging-from-kdc-and-kadmind-plugin-modules + */ +static void mit_samba_debug(void *private_ptr, int msg_level, const char *msg) +{ + int is_error = errno; + + if (msg_level > 0) { + is_error = 0; + } + + com_err("mitkdc", is_error, "%s", msg); +} + +krb5_error_code mit_samba_context_init(struct mit_samba_context **_ctx) +{ + NTSTATUS status; + struct mit_samba_context *ctx; + const char *s4_conf_file; + krb5_error_code ret; + struct samba_kdc_base_context base_ctx; + + ctx = talloc_zero(NULL, struct mit_samba_context); + if (!ctx) { + ret = ENOMEM; + goto done; + } + + base_ctx.ev_ctx = tevent_context_init(ctx); + if (!base_ctx.ev_ctx) { + ret = ENOMEM; + goto done; + } + tevent_loop_allow_nesting(base_ctx.ev_ctx); + base_ctx.lp_ctx = loadparm_init_global(false); + if (!base_ctx.lp_ctx) { + ret = ENOMEM; + goto done; + } + + debug_set_callback(NULL, mit_samba_debug); + + /* init s4 configuration */ + s4_conf_file = lpcfg_configfile(base_ctx.lp_ctx); + if (s4_conf_file != NULL) { + char *p = talloc_strdup(ctx, s4_conf_file); + if (p == NULL) { + ret = ENOMEM; + goto done; + } + lpcfg_load(base_ctx.lp_ctx, p); + TALLOC_FREE(p); + } else { + lpcfg_load_default(base_ctx.lp_ctx); + } + + status = samba_kdc_setup_db_ctx(ctx, &base_ctx, &ctx->db_ctx); + if (!NT_STATUS_IS_OK(status)) { + ret = EINVAL; + goto done; + } + + /* init MIT's krb_context and log facilities */ + ret = smb_krb5_init_context_basic(ctx, + ctx->db_ctx->lp_ctx, + &ctx->context); + if (ret) { + goto done; + } + + ret = 0; + +done: + if (ret) { + mit_samba_context_free(ctx); + } else { + *_ctx = ctx; + } + return ret; +} + +int mit_samba_generate_salt(krb5_data *salt) +{ + if (salt == NULL) { + return EINVAL; + } + + salt->length = 16; + salt->data = malloc(salt->length); + if (salt->data == NULL) { + return ENOMEM; + } + + generate_random_buffer((uint8_t *)salt->data, salt->length); + + return 0; +} + +int mit_samba_generate_random_password(krb5_data *pwd) +{ + TALLOC_CTX *tmp_ctx; + char *password; + char *data = NULL; + const unsigned length = 24; + + if (pwd == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_named(NULL, + 0, + "mit_samba_generate_random_password context"); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + password = generate_random_password(tmp_ctx, length, length); + if (password == NULL) { + talloc_free(tmp_ctx); + return ENOMEM; + } + + data = strdup(password); + talloc_free(tmp_ctx); + if (data == NULL) { + return ENOMEM; + } + + *pwd = smb_krb5_make_data(data, length); + + return 0; +} + +krb5_error_code mit_samba_get_principal(struct mit_samba_context *ctx, + krb5_const_principal principal, + unsigned int kflags, + krb5_db_entry **_kentry) +{ + struct sdb_entry sentry = {}; + krb5_db_entry *kentry; + krb5_error_code ret; + uint32_t sflags = 0; + krb5_principal referral_principal = NULL; + + kentry = calloc(1, sizeof(krb5_db_entry)); + if (kentry == NULL) { + return ENOMEM; + } + + /* + * The MIT KDC code that wants the canonical name in all lookups, and + * takes care to canonicalize only when appropriate. + */ + sflags |= SDB_F_FORCE_CANON; + + if (kflags & KRB5_KDB_FLAG_REFERRAL_OK) { + sflags |= SDB_F_CANON; + } + + if (kflags & KRB5_KDB_FLAG_CLIENT) { + sflags |= SDB_F_GET_CLIENT; + sflags |= SDB_F_FOR_AS_REQ; + } else { + int equal = smb_krb5_principal_is_tgs(ctx->context, principal); + if (equal == -1) { + return ENOMEM; + } + + if (equal) { + sflags |= SDB_F_GET_KRBTGT; + } else { + sflags |= SDB_F_GET_SERVER; + sflags |= SDB_F_FOR_TGS_REQ; + } + } + + /* always set this or the created_by data will not be populated by samba's + * backend and we will fail to parse the entry later */ + sflags |= SDB_F_ADMIN_DATA; + + +fetch_referral_principal: + ret = samba_kdc_fetch(ctx->context, ctx->db_ctx, + principal, sflags, 0, &sentry); + switch (ret) { + case 0: + break; + case SDB_ERR_NOENTRY: + ret = KRB5_KDB_NOENTRY; + goto done; + case SDB_ERR_WRONG_REALM: { + char *dest_realm = NULL; + const char *our_realm = lpcfg_realm(ctx->db_ctx->lp_ctx); + + if (sflags & SDB_F_FOR_AS_REQ) { + /* + * If this is a request for a TGT, we are done. The KDC + * will return the correct error to the client. + */ + ret = 0; + break; + } + + if (referral_principal != NULL) { + sdb_entry_free(&sentry); + ret = KRB5_KDB_NOENTRY; + goto done; + } + + /* + * We get a TGS request + * + * cifs/dc7.SAMBA2008R2.EXAMPLE.COM@ADDOM.SAMBA.EXAMPLE.COM + * + * to our DC for the realm + * + * ADDOM.SAMBA.EXAMPLE.COM + * + * We look up if we have an entry in the database and get an + * entry with the principal: + * + * cifs/dc7.SAMBA2008R2.EXAMPLE.COM@SAMBA2008R2.EXAMPLE.COM + * + * and the error: SDB_ERR_WRONG_REALM. + * + * In the case of a TGS-REQ we need to return a referral ticket + * for the next trust hop to the client. This ticket will have + * the following principal: + * + * krbtgt/SAMBA2008R2.EXAMPLE.COM@ADDOM.SAMBA.EXAMPLE.COM + * + * We just redo the lookup in the database with the referral + * principal and return success. + */ + dest_realm = smb_krb5_principal_get_realm( + ctx, ctx->context, sentry.principal); + sdb_entry_free(&sentry); + if (dest_realm == NULL) { + ret = KRB5_KDB_NOENTRY; + goto done; + } + + ret = smb_krb5_make_principal(ctx->context, + &referral_principal, + our_realm, + KRB5_TGS_NAME, + dest_realm, + NULL); + TALLOC_FREE(dest_realm); + if (ret != 0) { + goto done; + } + + principal = referral_principal; + goto fetch_referral_principal; + } + case SDB_ERR_NOT_FOUND_HERE: + /* FIXME: RODC support */ + default: + goto done; + } + + ret = sdb_entry_to_krb5_db_entry(ctx->context, &sentry, kentry); + + sdb_entry_free(&sentry); + +done: + krb5_free_principal(ctx->context, referral_principal); + referral_principal = NULL; + + if (ret) { + free(kentry); + } else { + *_kentry = kentry; + } + return ret; +} + +krb5_error_code mit_samba_get_firstkey(struct mit_samba_context *ctx, + krb5_db_entry **_kentry) +{ + struct sdb_entry sentry = {}; + krb5_db_entry *kentry; + krb5_error_code ret; + + kentry = malloc(sizeof(krb5_db_entry)); + if (kentry == NULL) { + return ENOMEM; + } + + ret = samba_kdc_firstkey(ctx->context, ctx->db_ctx, &sentry); + switch (ret) { + case 0: + break; + case SDB_ERR_NOENTRY: + free(kentry); + return KRB5_KDB_NOENTRY; + case SDB_ERR_NOT_FOUND_HERE: + /* FIXME: RODC support */ + default: + free(kentry); + return ret; + } + + ret = sdb_entry_to_krb5_db_entry(ctx->context, &sentry, kentry); + + sdb_entry_free(&sentry); + + if (ret) { + free(kentry); + } else { + *_kentry = kentry; + } + return ret; +} + +krb5_error_code mit_samba_get_nextkey(struct mit_samba_context *ctx, + krb5_db_entry **_kentry) +{ + struct sdb_entry sentry = {}; + krb5_db_entry *kentry; + krb5_error_code ret; + + kentry = malloc(sizeof(krb5_db_entry)); + if (kentry == NULL) { + return ENOMEM; + } + + ret = samba_kdc_nextkey(ctx->context, ctx->db_ctx, &sentry); + switch (ret) { + case 0: + break; + case SDB_ERR_NOENTRY: + free(kentry); + return KRB5_KDB_NOENTRY; + case SDB_ERR_NOT_FOUND_HERE: + /* FIXME: RODC support */ + default: + free(kentry); + return ret; + } + + ret = sdb_entry_to_krb5_db_entry(ctx->context, &sentry, kentry); + + sdb_entry_free(&sentry); + + if (ret) { + free(kentry); + } else { + *_kentry = kentry; + } + return ret; +} + +krb5_error_code mit_samba_get_pac(struct mit_samba_context *smb_ctx, + krb5_context context, + uint32_t flags, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_keyblock *replaced_reply_key, + krb5_pac *pac) +{ + TALLOC_CTX *tmp_ctx; + const struct auth_user_info_dc *user_info_dc = NULL; + struct auth_user_info_dc *user_info_dc_shallow_copy = NULL; + DATA_BLOB *logon_info_blob = NULL; + DATA_BLOB *upn_dns_info_blob = NULL; + DATA_BLOB *cred_ndr = NULL; + DATA_BLOB **cred_ndr_ptr = NULL; + DATA_BLOB cred_blob = data_blob_null; + DATA_BLOB *pcred_blob = NULL; + DATA_BLOB *pac_attrs_blob = NULL; + DATA_BLOB *requester_sid_blob = NULL; + const DATA_BLOB *client_claims_blob = NULL; + NTSTATUS nt_status; + krb5_error_code code; + struct samba_kdc_entry *skdc_entry; + struct samba_kdc_entry *server_entry = NULL; + bool is_krbtgt; + /* Only include resource groups in a service ticket. */ + enum auth_group_inclusion group_inclusion; + enum samba_asserted_identity asserted_identity = + (flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION) ? + SAMBA_ASSERTED_IDENTITY_SERVICE : + SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY; + + if (client == NULL) { + return EINVAL; + } + skdc_entry = talloc_get_type_abort(client->e_data, + struct samba_kdc_entry); + + if (server == NULL) { + return EINVAL; + } + { + int result = smb_krb5_principal_is_tgs(smb_ctx->context, server->princ); + if (result == -1) { + return ENOMEM; + } + + is_krbtgt = result; + } + server_entry = talloc_get_type_abort(server->e_data, + struct samba_kdc_entry); + + /* Only include resource groups in a service ticket. */ + if (is_krbtgt) { + group_inclusion = AUTH_EXCLUDE_RESOURCE_GROUPS; + } else if (server_entry->supported_enctypes & KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED) { + group_inclusion = AUTH_INCLUDE_RESOURCE_GROUPS; + } else { + group_inclusion = AUTH_INCLUDE_RESOURCE_GROUPS_COMPRESSED; + } + + tmp_ctx = talloc_named(smb_ctx, + 0, + "mit_samba_get_pac context"); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Check if we have a PREAUTH key */ + if (replaced_reply_key != NULL) { + cred_ndr_ptr = &cred_ndr; + } + + code = samba_kdc_get_user_info_from_db(tmp_ctx, + server_entry->kdc_db_ctx->samdb, + skdc_entry, + skdc_entry->msg, + &user_info_dc); + if (code) { + talloc_free(tmp_ctx); + return code; + } + + /* Make a shallow copy of the user_info_dc structure. */ + nt_status = authsam_shallow_copy_user_info_dc(tmp_ctx, + user_info_dc, + &user_info_dc_shallow_copy); + user_info_dc = NULL; + + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("Failed to allocate shallow copy of user_info_dc: %s\n", + nt_errstr(nt_status)); + talloc_free(tmp_ctx); + return map_errno_from_nt_status(nt_status); + } + + + nt_status = samba_kdc_add_asserted_identity(asserted_identity, + user_info_dc_shallow_copy); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("Failed to add asserted identity: %s\n", + nt_errstr(nt_status)); + talloc_free(tmp_ctx); + return EINVAL; + } + + nt_status = samba_kdc_add_claims_valid(user_info_dc_shallow_copy); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("Failed to add Claims Valid: %s\n", + nt_errstr(nt_status)); + talloc_free(tmp_ctx); + return EINVAL; + } + + /* We no longer need to modify this, so assign to const variable */ + user_info_dc = user_info_dc_shallow_copy; + + nt_status = samba_kdc_get_logon_info_blob(tmp_ctx, + user_info_dc, + group_inclusion, + &logon_info_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(tmp_ctx); + return EINVAL; + } + + if (cred_ndr_ptr != NULL) { + nt_status = samba_kdc_get_cred_ndr_blob(tmp_ctx, + skdc_entry, + cred_ndr_ptr); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(tmp_ctx); + return EINVAL; + } + } + + nt_status = samba_kdc_get_upn_info_blob(tmp_ctx, + user_info_dc, + &upn_dns_info_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(tmp_ctx); + return EINVAL; + } + + if (is_krbtgt) { + nt_status = samba_kdc_get_pac_attrs_blob(tmp_ctx, + PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY, + &pac_attrs_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(tmp_ctx); + return EINVAL; + } + + nt_status = samba_kdc_get_requester_sid_blob(tmp_ctx, + user_info_dc, + &requester_sid_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(tmp_ctx); + return EINVAL; + } + } + + nt_status = samba_kdc_get_claims_blob(tmp_ctx, + skdc_entry, + &client_claims_blob); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(tmp_ctx); + return EINVAL; + } + + if (replaced_reply_key != NULL && cred_ndr != NULL) { + code = samba_kdc_encrypt_pac_credentials(context, + replaced_reply_key, + cred_ndr, + tmp_ctx, + &cred_blob); + if (code != 0) { + talloc_free(tmp_ctx); + return code; + } + pcred_blob = &cred_blob; + } + + code = samba_make_krb5_pac(context, + logon_info_blob, + pcred_blob, + upn_dns_info_blob, + pac_attrs_blob, + requester_sid_blob, + NULL /* deleg_blob */, + client_claims_blob, + NULL /* device_info_blob */, + NULL /* device_claims_blob */, + *pac); + + talloc_free(tmp_ctx); + return code; +} + +krb5_error_code mit_samba_update_pac(struct mit_samba_context *ctx, + krb5_context context, + int kdc_flags, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_db_entry *krbtgt, + krb5_pac old_pac, + krb5_pac new_pac) +{ + TALLOC_CTX *tmp_ctx = NULL; + krb5_error_code code; + struct samba_kdc_entry *client_skdc_entry = NULL; + struct samba_kdc_entry *server_skdc_entry = NULL; + struct samba_kdc_entry *krbtgt_skdc_entry = NULL; + struct samba_kdc_entry_pac client_pac_entry = {}; + bool is_in_db = false; + bool is_trusted = false; + uint32_t flags = 0; + + /* Create a memory context early so code can use talloc_stackframe() */ + tmp_ctx = talloc_named(ctx, 0, "mit_samba_update_pac context"); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + if (client != NULL) { + client_skdc_entry = + talloc_get_type_abort(client->e_data, + struct samba_kdc_entry); + } + + if (krbtgt == NULL) { + code = EINVAL; + goto done; + } + krbtgt_skdc_entry = + talloc_get_type_abort(krbtgt->e_data, + struct samba_kdc_entry); + + if (server == NULL) { + code = EINVAL; + goto done; + } + server_skdc_entry = + talloc_get_type_abort(server->e_data, + struct samba_kdc_entry); + + /* + * If the krbtgt was generated by an RODC, and we are not that + * RODC, then we need to regenerate the PAC - we can't trust + * it, and confirm that the RODC was permitted to print this ticket + * + * Because of the samba_kdc_validate_pac_blob() step we can be + * sure that the record in 'client' or 'server' matches the SID in the + * original PAC. + */ + code = samba_krbtgt_is_in_db(krbtgt_skdc_entry, + &is_in_db, + &is_trusted); + if (code != 0) { + goto done; + } + + if (kdc_flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION) { + flags |= SAMBA_KDC_FLAG_PROTOCOL_TRANSITION; + } + + if (kdc_flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) { + flags |= SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION; + } + + client_pac_entry = samba_kdc_entry_pac_from_trusted(old_pac, + client_skdc_entry, + samba_kdc_entry_is_trust(krbtgt_skdc_entry), + is_trusted); + + code = samba_kdc_verify_pac(tmp_ctx, + context, + krbtgt_skdc_entry->kdc_db_ctx->samdb, + flags, + client_pac_entry, + krbtgt_skdc_entry); + if (code != 0) { + goto done; + } + + code = samba_kdc_update_pac(tmp_ctx, + context, + krbtgt_skdc_entry->kdc_db_ctx->samdb, + krbtgt_skdc_entry->kdc_db_ctx->lp_ctx, + flags, + client_pac_entry, + server->princ, + server_skdc_entry, + NULL /* delegated_proxy_principal */, + (struct samba_kdc_entry_pac) {} /* delegated_proxy */, + (struct samba_kdc_entry_pac) {} /* device */, + new_pac, + NULL /* server_audit_info_out */, + NULL /* status_out */); + if (code != 0) { + if (code == ENOATTR) { + /* + * We can't tell the KDC to not issue a PAC. It will + * just return the newly allocated empty PAC. + */ + code = 0; + } + } + +done: + talloc_free(tmp_ctx); + return code; +} + +/* provide header, function is exported but there are no public headers */ + +krb5_error_code encode_krb5_padata_sequence(krb5_pa_data *const *rep, krb5_data **code); + +/* this function allocates 'data' using malloc. + * The caller is responsible for freeing it */ +static void samba_kdc_build_edata_reply(NTSTATUS nt_status, DATA_BLOB *e_data) +{ + krb5_error_code ret = 0; + krb5_pa_data pa, *ppa[2]; + krb5_data *d = NULL; + + if (!e_data) + return; + + e_data->data = NULL; + e_data->length = 0; + + pa.magic = KV5M_PA_DATA; + pa.pa_type = KRB5_PADATA_PW_SALT /* KERB_ERR_TYPE_EXTENDED */; + pa.length = 12; + pa.contents = malloc(pa.length); + if (!pa.contents) { + return; + } + + SIVAL(pa.contents, 0, NT_STATUS_V(nt_status)); + SIVAL(pa.contents, 4, 0); + SIVAL(pa.contents, 8, 1); + + ppa[0] = &pa; + ppa[1] = NULL; + + ret = encode_krb5_padata_sequence(ppa, &d); + free(pa.contents); + if (ret) { + return; + } + + e_data->data = (uint8_t *)d->data; + e_data->length = d->length; + + /* free d, not d->data - gd */ + free(d); + + return; +} + +krb5_error_code mit_samba_check_client_access(struct mit_samba_context *ctx, + krb5_db_entry *client, + const char *client_name, + krb5_db_entry *server, + const char *server_name, + const char *netbios_name, + bool password_change, + DATA_BLOB *e_data) +{ + struct samba_kdc_entry *skdc_entry; + NTSTATUS nt_status; + + skdc_entry = talloc_get_type(client->e_data, struct samba_kdc_entry); + + nt_status = samba_kdc_check_client_access(skdc_entry, + client_name, + netbios_name, + password_change); + + if (!NT_STATUS_IS_OK(nt_status)) { + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MEMORY)) { + return ENOMEM; + } + + samba_kdc_build_edata_reply(nt_status, e_data); + + return samba_kdc_map_policy_err(nt_status); + } + + return 0; +} + +krb5_error_code mit_samba_check_s4u2proxy(struct mit_samba_context *ctx, + const krb5_db_entry *server, + krb5_const_principal target_principal) +{ + struct samba_kdc_entry *server_skdc_entry = + talloc_get_type_abort(server->e_data, struct samba_kdc_entry); + krb5_error_code code; + + code = samba_kdc_check_s4u2proxy(ctx->context, + ctx->db_ctx, + server_skdc_entry, + target_principal); + + return code; +} + +krb5_error_code mit_samba_check_allowed_to_delegate_from( + struct mit_samba_context *ctx, + krb5_const_principal client_principal, + krb5_const_principal server_principal, + krb5_pac header_pac, + const krb5_db_entry *proxy) +{ + struct samba_kdc_entry *proxy_skdc_entry = + talloc_get_type_abort(proxy->e_data, struct samba_kdc_entry); + struct auth_user_info_dc *user_info_dc = NULL; + TALLOC_CTX *mem_ctx = NULL; + krb5_error_code code; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + return ENOMEM; + } + + /* + * FIXME: If ever we support RODCs, we must check that the PAC has not + * been issued by an RODC (other than ourselves) — otherwise the PAC + * cannot be trusted. Because the plugin interface does not give us the + * client entry, we cannot look up its groups in the database. + */ + code = kerberos_pac_to_user_info_dc(mem_ctx, + header_pac, + ctx->context, + &user_info_dc, + AUTH_INCLUDE_RESOURCE_GROUPS, + NULL, + NULL, + NULL); + if (code != 0) { + goto out; + } + + code = samba_kdc_check_s4u2proxy_rbcd(ctx->context, + ctx->db_ctx, + client_principal, + server_principal, + user_info_dc, + NULL /* device_info_dc */, + (struct auth_claims) {}, + proxy_skdc_entry); +out: + talloc_free(mem_ctx); + return code; +} + +static krb5_error_code mit_samba_change_pwd_error(krb5_context context, + NTSTATUS result, + enum samPwdChangeReason reject_reason, + struct samr_DomInfo1 *dominfo) +{ + krb5_error_code code = KADM5_PASS_Q_GENERIC; + + if (NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_USER)) { + code = KADM5_BAD_PRINCIPAL; + krb5_set_error_message(context, + code, + "No such user when changing password"); + } + if (NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)) { + code = KADM5_PASS_Q_GENERIC; + krb5_set_error_message(context, + code, + "Not permitted to change password"); + } + if (NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_RESTRICTION) && + dominfo != NULL) { + switch (reject_reason) { + case SAM_PWD_CHANGE_PASSWORD_TOO_SHORT: + code = KADM5_PASS_Q_TOOSHORT; + krb5_set_error_message(context, + code, + "Password too short, password " + "must be at least %d characters " + "long.", + dominfo->min_password_length); + break; + case SAM_PWD_CHANGE_NOT_COMPLEX: + code = KADM5_PASS_Q_DICT; + krb5_set_error_message(context, + code, + "Password does not meet " + "complexity requirements"); + break; + case SAM_PWD_CHANGE_PWD_IN_HISTORY: + code = KADM5_PASS_TOOSOON; + krb5_set_error_message(context, + code, + "Password is already in password " + "history. New password must not " + "match any of your %d previous " + "passwords.", + dominfo->password_history_length); + break; + default: + code = KADM5_PASS_Q_GENERIC; + krb5_set_error_message(context, + code, + "Password change rejected, " + "password changes may not be " + "permitted on this account, or " + "the minimum password age may " + "not have elapsed."); + break; + } + } + + return code; +} + +krb5_error_code mit_samba_kpasswd_change_password(struct mit_samba_context *ctx, + char *pwd, + krb5_db_entry *db_entry) +{ + NTSTATUS status; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + TALLOC_CTX *tmp_ctx; + DATA_BLOB password; + enum samPwdChangeReason reject_reason; + struct samr_DomInfo1 *dominfo; + const char *error_string = NULL; + const struct auth_user_info_dc *user_info_dc = NULL; + struct samba_kdc_entry *p = + talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry); + krb5_error_code code = 0; + +#ifdef DEBUG_PASSWORD + DBG_WARNING("mit_samba_kpasswd_change_password called with: %s\n", pwd); +#endif + + tmp_ctx = talloc_named(ctx, 0, "mit_samba_kpasswd_change_password"); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + code = samba_kdc_get_user_info_from_db(tmp_ctx, + ctx->db_ctx->samdb, + p, + p->msg, + &user_info_dc); + if (code) { + const char *krb5err = krb5_get_error_message(ctx->context, code); + DBG_WARNING("samba_kdc_get_user_info_from_db failed: %s\n", + krb5err != NULL ? krb5err : "<unknown>"); + krb5_free_error_message(ctx->context, krb5err); + + goto out; + } + + status = auth_generate_session_info(tmp_ctx, + ctx->db_ctx->lp_ctx, + ctx->db_ctx->samdb, + user_info_dc, + 0, /* session_info_flags */ + &ctx->session_info); + + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("auth_generate_session_info failed: %s\n", + nt_errstr(status)); + code = EINVAL; + goto out; + } + + /* password is expected as UTF16 */ + + if (!convert_string_talloc(tmp_ctx, CH_UTF8, CH_UTF16, + pwd, strlen(pwd), + &password.data, &password.length)) { + DBG_WARNING("convert_string_talloc failed\n"); + code = EINVAL; + goto out; + } + + status = samdb_kpasswd_change_password(tmp_ctx, + ctx->db_ctx->lp_ctx, + ctx->db_ctx->ev_ctx, + ctx->session_info, + &password, + &reject_reason, + &dominfo, + &error_string, + &result); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("samdb_kpasswd_change_password failed: %s\n", + nt_errstr(status)); + code = KADM5_PASS_Q_GENERIC; + krb5_set_error_message(ctx->context, code, "%s", error_string); + goto out; + } + + if (!NT_STATUS_IS_OK(result)) { + code = mit_samba_change_pwd_error(ctx->context, + result, + reject_reason, + dominfo); + } + +out: + talloc_free(tmp_ctx); + + return code; +} + +void mit_samba_zero_bad_password_count(krb5_db_entry *db_entry) +{ + /* struct netr_SendToSamBase *send_to_sam = NULL; */ + struct samba_kdc_entry *p = + talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry); + struct ldb_dn *domain_dn; + + domain_dn = ldb_get_default_basedn(p->kdc_db_ctx->samdb); + + authsam_logon_success_accounting(p->kdc_db_ctx->samdb, + p->msg, + domain_dn, + true, + NULL, NULL); + /* TODO: RODC support */ +} + + +void mit_samba_update_bad_password_count(krb5_db_entry *db_entry) +{ + struct samba_kdc_entry *p = + talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry); + + authsam_update_bad_pwd_count(p->kdc_db_ctx->samdb, + p->msg, + ldb_get_default_basedn(p->kdc_db_ctx->samdb)); +} + +bool mit_samba_princ_needs_pac(krb5_db_entry *db_entry) +{ + struct samba_kdc_entry *skdc_entry = + talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry); + + return samba_princ_needs_pac(skdc_entry); +} |