From 69749b61308e4a659fcee4e59fe151ffc71801b0 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Wed, 7 Sep 2016 12:32:14 +0200 Subject: [PATCH] s4-kdc: Add a new kpasswd service implementation This function is intended to be be passed to kdc_add_socket(). The function kpasswd_handle_request() which is called by kpasswd_process() is Kerberos implementation specific and should be implemented in a kpasswd-service-.c file. Signed-off-by: Andreas Schneider Reviewed-by: Jeremy Allison --- source4/kdc/kpasswd-service.c | 348 ++++++++++++++++++++++++++++++++++ source4/kdc/kpasswd-service.h | 43 +++++ source4/kdc/wscript_build | 9 + 3 files changed, 400 insertions(+) create mode 100644 source4/kdc/kpasswd-service.c create mode 100644 source4/kdc/kpasswd-service.h diff --git a/source4/kdc/kpasswd-service.c b/source4/kdc/kpasswd-service.c new file mode 100644 index 000000000000..9cb148235579 --- /dev/null +++ b/source4/kdc/kpasswd-service.c @@ -0,0 +1,348 @@ +/* + Unix SMB/CIFS implementation. + + Samba kpasswd implementation + + Copyright (c) 2005 Andrew Bartlett + 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 "smbd/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" + +#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; + } + + cli_credentials_set_conf(server_credentials, kdc->task->lp_ctx); + + ok = cli_credentials_set_username(server_credentials, + "kadmin/changepw", + CRED_SPECIFIED); + if (!ok) { + goto done; + } + + rv = cli_credentials_set_keytab_name(server_credentials, + kdc->task->lp_ctx, + kdc->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 */ + status = gensec_update_ev(gensec_security, + tmp_ctx, + kdc->task->event_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) { + error_code = code; + goto reply; + } + + status = gensec_wrap(gensec_security, + tmp_ctx, + &kpasswd_dec_reply, + &enc_data_blob); + if (!NT_STATUS_IS_OK(status)) { + 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, + 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 000000000000..845d68096e8a --- /dev/null +++ b/source4/kdc/kpasswd-service.h @@ -0,0 +1,43 @@ +/* + 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 . +*/ + +#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/wscript_build b/source4/kdc/wscript_build index 170cc3499272..99678ea441d2 100755 --- a/source4/kdc/wscript_build +++ b/source4/kdc/wscript_build @@ -54,6 +54,15 @@ bld.SAMBA_SUBSYSTEM('KDC-SERVER', ''', enabled=bld.CONFIG_SET('SAMBA4_USES_HEIMDAL')) +bld.SAMBA_SUBSYSTEM('KPASSWD-SERVICE', + source='kpasswd-service.c kpasswd-helper.c', + includes=kdc_include, + deps=''' + krb5samba + samba_server_gensec + KPASSWD_GLUE + ''') + bld.SAMBA_SUBSYSTEM('KDC-GLUE', source='kdc-glue.c', includes=kdc_include, -- 2.34.1