s4-kdc: Add a new kpasswd service implementation
authorAndreas Schneider <asn@samba.org>
Wed, 7 Sep 2016 10:32:14 +0000 (12:32 +0200)
committerJeremy Allison <jra@samba.org>
Mon, 12 Sep 2016 22:19:25 +0000 (00:19 +0200)
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-<kerberos flavour>.c file.

Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
source4/kdc/kpasswd-service.c [new file with mode: 0644]
source4/kdc/kpasswd-service.h [new file with mode: 0644]
source4/kdc/wscript_build

diff --git a/source4/kdc/kpasswd-service.c b/source4/kdc/kpasswd-service.c
new file mode 100644 (file)
index 0000000..9cb1482
--- /dev/null
@@ -0,0 +1,348 @@
+/*
+   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 "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 (file)
index 0000000..845d680
--- /dev/null
@@ -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 */
index 170cc349927200167c26d14aebed61f06bff4b2e..99678ea441d2fc6c1f86d75fb41d7f74780d0205 100755 (executable)
@@ -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,