Ensure the hdb_method structure is not on the stack.
[kai/samba.git] / source4 / kdc / kdc.c
index 1cb9ed198147077ae55c9642349ef22bf7991dd4..45fa803d0497765b89dbfd3561a11c8261aaec65 100644 (file)
@@ -3,13 +3,13 @@
 
    KDC Server startup
 
-   Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+   Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2008
    Copyright (C) Andrew Tridgell       2005
    Copyright (C) Stefan Metzmacher     2005
 
    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 2 of the License, or
+   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,
@@ -18,8 +18,7 @@
    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, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #include "includes.h"
 #include "smbd/process_model.h"
 #include "lib/events/events.h"
 #include "lib/socket/socket.h"
-#include "kdc/kdc.h"
 #include "system/network.h"
-#include "lib/util/dlinklist.h"
+#include "../lib/util/dlinklist.h"
 #include "lib/messaging/irpc.h"
 #include "lib/stream/packet.h"
 #include "librpc/gen_ndr/samr.h"
+#include "librpc/gen_ndr/ndr_irpc.h"
+#include "librpc/gen_ndr/ndr_krb5pac.h"
 #include "lib/socket/netif.h"
-#include "heimdal/kdc/windc_plugin.h"
-#include "heimdal/lib/krb5/krb5_locl.h"
-#include "heimdal/kdc/kdc_locl.h"
+#include "param/param.h"
+#include "kdc/kdc.h"
+#include "librpc/gen_ndr/ndr_misc.h"
 
 
-/* Disgusting hack to get a mem_ctx into the hdb plugin, when used as a keytab */
+/* Disgusting hack to get a mem_ctx and lp_ctx into the hdb plugin, when 
+ * used as a keytab */
 TALLOC_CTX *kdc_mem_ctx;
+struct loadparm_context *kdc_lp_ctx;
 
 /* hold all the info needed to send a reply */
 struct kdc_reply {
@@ -51,7 +53,7 @@ struct kdc_reply {
        DATA_BLOB packet;
 };
 
-typedef BOOL (*kdc_process_fn_t)(struct kdc_server *kdc,
+typedef bool (*kdc_process_fn_t)(struct kdc_server *kdc,
                                 TALLOC_CTX *mem_ctx, 
                                 DATA_BLOB *input, 
                                 DATA_BLOB *reply,
@@ -311,7 +313,7 @@ static void kdc_tcp_send(struct stream_connection *conn, uint16_t flags)
    calling conventions
 */
 
-static BOOL kdc_process(struct kdc_server *kdc,
+static bool kdc_process(struct kdc_server *kdc,
                        TALLOC_CTX *mem_ctx, 
                        DATA_BLOB *input, 
                        DATA_BLOB *reply,
@@ -323,6 +325,8 @@ static BOOL kdc_process(struct kdc_server *kdc,
        krb5_data k5_reply;
        krb5_data_zero(&k5_reply);
 
+       krb5_kdc_update_time(NULL);
+
        DEBUG(10,("Received KDC packet of length %lu from %s:%d\n", 
                  (long)input->length - 4, peer_addr->addr, peer_addr->port));
 
@@ -335,7 +339,7 @@ static BOOL kdc_process(struct kdc_server *kdc,
                                            datagram_reply);
        if (ret == -1) {
                *reply = data_blob(NULL, 0);
-               return False;
+               return false;
        }
        if (k5_reply.length) {
                *reply = data_blob_talloc(mem_ctx, k5_reply.data, k5_reply.length);
@@ -343,7 +347,7 @@ static BOOL kdc_process(struct kdc_server *kdc,
        } else {
                *reply = data_blob(NULL, 0);    
        }
-       return True;
+       return true;
 }
 
 /*
@@ -406,15 +410,14 @@ static const struct stream_server_ops kpasswdd_tcp_stream_ops = {
 /*
   start listening on the given address
 */
-static NTSTATUS kdc_add_socket(struct kdc_server *kdc, const char *address)
+static NTSTATUS kdc_add_socket(struct kdc_server *kdc, const char *address,
+                              uint16_t kdc_port, uint16_t kpasswd_port)
 {
        const struct model_ops *model_ops;
        struct kdc_socket *kdc_socket;
        struct kdc_socket *kpasswd_socket;
        struct socket_address *kdc_address, *kpasswd_address;
        NTSTATUS status;
-       uint16_t kdc_port = lp_krb5_port();
-       uint16_t kpasswd_port = lp_kpasswd_port();
 
        kdc_socket = talloc(kdc, struct kdc_socket);
        NT_STATUS_HAVE_NO_MEMORY(kdc_socket);
@@ -481,16 +484,20 @@ static NTSTATUS kdc_add_socket(struct kdc_server *kdc, const char *address)
        /* within the kdc task we want to be a single process, so
           ask for the single process model ops and pass these to the
           stream_setup_socket() call. */
-       model_ops = process_model_byname("single");
+       model_ops = process_model_startup(kdc->task->event_ctx, "single");
        if (!model_ops) {
                DEBUG(0,("Can't find 'single' process model_ops\n"));
                talloc_free(kdc_socket);
                return NT_STATUS_INTERNAL_ERROR;
        }
 
-       status = stream_setup_socket(kdc->task->event_ctx, model_ops, 
+       status = stream_setup_socket(kdc->task->event_ctx, 
+                                    kdc->task->lp_ctx,
+                                    model_ops, 
                                     &kdc_tcp_stream_ops, 
-                                    "ip", address, &kdc_port, kdc);
+                                    "ip", address, &kdc_port, 
+                                    lp_socket_options(kdc->task->lp_ctx), 
+                                    kdc);
        if (!NT_STATUS_IS_OK(status)) {
                DEBUG(0,("Failed to bind to %s:%u TCP - %s\n",
                         address, kdc_port, nt_errstr(status)));
@@ -498,9 +505,13 @@ static NTSTATUS kdc_add_socket(struct kdc_server *kdc, const char *address)
                return status;
        }
 
-       status = stream_setup_socket(kdc->task->event_ctx, model_ops, 
+       status = stream_setup_socket(kdc->task->event_ctx, 
+                                    kdc->task->lp_ctx,
+                                    model_ops, 
                                     &kpasswdd_tcp_stream_ops, 
-                                    "ip", address, &kpasswd_port, kdc);
+                                    "ip", address, &kpasswd_port, 
+                                    lp_socket_options(kdc->task->lp_ctx), 
+                                    kdc);
        if (!NT_STATUS_IS_OK(status)) {
                DEBUG(0,("Failed to bind to %s:%u TCP - %s\n",
                         address, kpasswd_port, nt_errstr(status)));
@@ -515,17 +526,20 @@ static NTSTATUS kdc_add_socket(struct kdc_server *kdc, const char *address)
 /*
   setup our listening sockets on the configured network interfaces
 */
-static NTSTATUS kdc_startup_interfaces(struct kdc_server *kdc)
+static NTSTATUS kdc_startup_interfaces(struct kdc_server *kdc, struct loadparm_context *lp_ctx,
+                                      struct interface *ifaces)
 {
-       int num_interfaces = iface_count();
+       int num_interfaces;
        TALLOC_CTX *tmp_ctx = talloc_new(kdc);
        NTSTATUS status;
-       
        int i;
+
+       num_interfaces = iface_count(ifaces);
        
        for (i=0; i<num_interfaces; i++) {
-               const char *address = talloc_strdup(tmp_ctx, iface_n_ip(i));
-               status = kdc_add_socket(kdc, address);
+               const char *address = talloc_strdup(tmp_ctx, iface_n_ip(ifaces, i));
+               status = kdc_add_socket(kdc, address, lp_krb5_port(lp_ctx), 
+                                       lp_kpasswd_port(lp_ctx));
                NT_STATUS_NOT_OK_RETURN(status);
        }
 
@@ -544,6 +558,111 @@ static struct krb5plugin_windc_ftable windc_plugin_table = {
 };
 
 
+static NTSTATUS kdc_check_generic_kerberos(struct irpc_message *msg, 
+                                struct kdc_check_generic_kerberos *r)
+{
+       struct PAC_Validate pac_validate;
+       DATA_BLOB srv_sig;
+       struct PAC_SIGNATURE_DATA kdc_sig;
+       struct kdc_server *kdc = talloc_get_type(msg->private, struct kdc_server);
+       enum ndr_err_code ndr_err;
+       krb5_enctype etype;
+       int ret;
+       hdb_entry_ex ent;
+       krb5_principal principal;
+       krb5_keyblock keyblock;
+       Key *key;
+
+       /* There is no reply to this request */
+       r->out.generic_reply = data_blob(NULL, 0);
+
+       ndr_err = ndr_pull_struct_blob(&r->in.generic_request, msg, 
+                                      lp_iconv_convenience(kdc->task->lp_ctx), 
+                                      &pac_validate,
+                                      (ndr_pull_flags_fn_t)ndr_pull_PAC_Validate);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+       
+       if (pac_validate.MessageType != 3) {
+               /* We don't implement any other message types - such as certificate validation - yet */
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       if (pac_validate.ChecksumAndSignature.length != (pac_validate.ChecksumLength + pac_validate.SignatureLength)
+           || pac_validate.ChecksumAndSignature.length < pac_validate.ChecksumLength
+           || pac_validate.ChecksumAndSignature.length < pac_validate.SignatureLength ) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+       
+       srv_sig = data_blob_const(pac_validate.ChecksumAndSignature.data, 
+                                 pac_validate.ChecksumLength);
+       
+       if (pac_validate.SignatureType == CKSUMTYPE_HMAC_MD5) {
+               etype = ETYPE_ARCFOUR_HMAC_MD5;
+       } else {
+               ret = krb5_cksumtype_to_enctype(kdc->smb_krb5_context->krb5_context, pac_validate.SignatureType,
+                                               &etype);
+               if (ret != 0) {
+                       return NT_STATUS_LOGON_FAILURE;
+               }
+       }
+
+       ret = krb5_make_principal(kdc->smb_krb5_context->krb5_context, &principal, 
+                                 lp_realm(kdc->task->lp_ctx), 
+                                 "krbtgt", lp_realm(kdc->task->lp_ctx), 
+                                 NULL);
+
+       if (ret != 0) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       ret = kdc->config->db[0]->hdb_fetch(kdc->smb_krb5_context->krb5_context, 
+                                           kdc->config->db[0],
+                                           principal,
+                                           HDB_F_GET_KRBTGT | HDB_F_DECRYPT,
+                                           &ent);
+
+       if (ret != 0) {
+               hdb_free_entry(kdc->smb_krb5_context->krb5_context, &ent);
+               krb5_free_principal(kdc->smb_krb5_context->krb5_context, principal);
+       
+               return NT_STATUS_LOGON_FAILURE;
+       }
+       
+       ret = hdb_enctype2key(kdc->smb_krb5_context->krb5_context, &ent.entry, etype, &key);
+
+       if (ret != 0) {
+               hdb_free_entry(kdc->smb_krb5_context->krb5_context, &ent);
+               krb5_free_principal(kdc->smb_krb5_context->krb5_context, principal);
+               return NT_STATUS_LOGON_FAILURE;
+       }
+
+       keyblock = key->key;
+       
+       kdc_sig.type = pac_validate.SignatureType;
+       kdc_sig.signature = data_blob_const(&pac_validate.ChecksumAndSignature.data[pac_validate.ChecksumLength],
+                                           pac_validate.SignatureLength);
+       ret = check_pac_checksum(msg, srv_sig, &kdc_sig, 
+                          kdc->smb_krb5_context->krb5_context, &keyblock);
+
+       hdb_free_entry(kdc->smb_krb5_context->krb5_context, &ent);
+       krb5_free_principal(kdc->smb_krb5_context->krb5_context, principal);
+
+       if (ret != 0) {
+               return NT_STATUS_LOGON_FAILURE;
+       }
+       
+       return NT_STATUS_OK;
+}
+
+
+static struct hdb_method hdb_samba4 = {
+       .interface_version = HDB_INTERFACE_VERSION,
+       .prefix = "samba4:",
+       .create = hdb_samba4_create
+};
+
 /*
   startup the kdc task
 */
@@ -552,8 +671,9 @@ static void kdc_task_init(struct task_server *task)
        struct kdc_server *kdc;
        NTSTATUS status;
        krb5_error_code ret;
+       struct interface *ifaces;
 
-       switch (lp_server_role()) {
+       switch (lp_server_role(task->lp_ctx)) {
        case ROLE_STANDALONE:
                task_server_terminate(task, "kdc: no KDC required in standalone configuration");
                return;
@@ -565,7 +685,9 @@ static void kdc_task_init(struct task_server *task)
                break;
        }
 
-       if (iface_count() == 0) {
+       load_interfaces(task, lp_interfaces(task->lp_ctx), &ifaces);
+
+       if (iface_count(ifaces) == 0) {
                task_server_terminate(task, "kdc: no network interfaces configured");
                return;
        }
@@ -582,7 +704,7 @@ static void kdc_task_init(struct task_server *task)
 
        initialize_krb5_error_table();
 
-       ret = smb_krb5_init_context(kdc, &kdc->smb_krb5_context);
+       ret = smb_krb5_init_context(kdc, task->event_ctx, task->lp_ctx, &kdc->smb_krb5_context);
        if (ret) {
                DEBUG(1,("kdc_task_init: krb5_init_context failed (%s)\n", 
                         error_message(ret)));
@@ -592,70 +714,79 @@ static void kdc_task_init(struct task_server *task)
 
        krb5_add_et_list(kdc->smb_krb5_context->krb5_context, initialize_hdb_error_table_r);
 
-       /* Registar WinDC hooks */
-       ret = _krb5_plugin_register(kdc->smb_krb5_context->krb5_context, 
-                                   PLUGIN_TYPE_DATA, "windc",
-                                   &windc_plugin_table);
+       ret = krb5_kdc_get_config(kdc->smb_krb5_context->krb5_context, 
+                                 &kdc->config);
        if(ret) {
-               task_server_terminate(task, "kdc: failed to register hdb keytab");
-               return;
-       }
-
-       /* Setup the KDC configuration */
-       kdc->config = talloc(kdc, krb5_kdc_configuration);
-       if (!kdc->config) {
-               task_server_terminate(task, "kdc: out of memory");
+               task_server_terminate(task, "kdc: failed to get KDC configuration");
                return;
        }
-       krb5_kdc_default_config(kdc->config);
 
        kdc->config->logf = kdc->smb_krb5_context->logf;
-       kdc->config->db = talloc(kdc->config, struct HDB *);
+       kdc->config->db = talloc(kdc, struct HDB *);
        if (!kdc->config->db) {
                task_server_terminate(task, "kdc: out of memory");
                return;
        }
        kdc->config->num_db = 1;
                
-       status = kdc_hdb_ldb_create(kdc, kdc->smb_krb5_context->krb5_context, 
+       status = kdc_hdb_samba4_create(kdc, task->event_ctx, task->lp_ctx, 
+                                   kdc->smb_krb5_context->krb5_context, 
                                    &kdc->config->db[0], NULL);
        if (!NT_STATUS_IS_OK(status)) {
                task_server_terminate(task, "kdc: hdb_ldb_create (setup KDC database) failed");
                return; 
        }
 
+
+       /* Register hdb-samba4 hooks */
+       ret = krb5_plugin_register(kdc->smb_krb5_context->krb5_context, 
+                                  PLUGIN_TYPE_DATA, "hdb",
+                                  &hdb_samba4);
+       if(ret) {
+               task_server_terminate(task, "kdc: failed to register hdb keytab");
+               return;
+       }
+
        ret = krb5_kt_register(kdc->smb_krb5_context->krb5_context, &hdb_kt_ops);
        if(ret) {
                task_server_terminate(task, "kdc: failed to register hdb keytab");
                return;
        }
 
-       krb5_kdc_configure(kdc->smb_krb5_context->krb5_context, kdc->config);
+       /* Registar WinDC hooks */
+       ret = krb5_plugin_register(kdc->smb_krb5_context->krb5_context, 
+                                  PLUGIN_TYPE_DATA, "windc",
+                                  &windc_plugin_table);
+       if(ret) {
+               task_server_terminate(task, "kdc: failed to register hdb keytab");
+               return;
+       }
+
+       krb5_kdc_windc_init(kdc->smb_krb5_context->krb5_context);
 
        kdc_mem_ctx = kdc->smb_krb5_context;
+       kdc_lp_ctx = task->lp_ctx;
 
        /* start listening on the configured network interfaces */
-       status = kdc_startup_interfaces(kdc);
+       status = kdc_startup_interfaces(kdc, task->lp_ctx, ifaces);
        if (!NT_STATUS_IS_OK(status)) {
                task_server_terminate(task, "kdc failed to setup interfaces");
                return;
        }
 
+       status = IRPC_REGISTER(task->msg_ctx, irpc, KDC_CHECK_GENERIC_KERBEROS, 
+                              kdc_check_generic_kerberos, kdc);
+       if (!NT_STATUS_IS_OK(status)) {
+               task_server_terminate(task, "nbtd failed to setup monitoring");
+               return;
+       }
+
        irpc_add_name(task->msg_ctx, "kdc_server");
 }
 
 
-/*
-  called on startup of the KDC service 
-*/
-static NTSTATUS kdc_init(struct event_context *event_ctx, 
-                        const struct model_ops *model_ops)
-{      
-       return task_server_startup(event_ctx, model_ops, kdc_task_init);
-}
-
 /* called at smbd startup - register ourselves as a server service */
 NTSTATUS server_service_kdc_init(void)
 {
-       return register_server_service("kdc", kdc_init);
+       return register_server_service("kdc", kdc_task_init);
 }