summaryrefslogtreecommitdiffstats
path: root/source4/kdc/kpasswd-service.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/kdc/kpasswd-service.c')
-rw-r--r--source4/kdc/kpasswd-service.c391
1 files changed, 391 insertions, 0 deletions
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;
+}