/* Unix SMB/CIFS implementation. Samba kpasswd implementation Copyright (c) 2016 Andreas Schneider 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 . */ #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" #undef DBGC_CLASS #define DBGC_CLASS DBGC_KERBEROS 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 = SAM_PWD_CHANGE_NO_ERROR; const char *reject_string = NULL; struct samr_DomInfo1 *dominfo = NULL; 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, chpw.newpasswd.data, chpw.newpasswd.length, &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) { krb5_xfree(target_principal_string); *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); krb5_xfree(target_principal_string); 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, decoded_data->data, decoded_data->length, &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; }