Update copyright
[kai/samba.git] / source4 / kdc / kdc.c
index 77ed9b6f15c8ccfe8ce3cbb4cf15d8f2bd75e93f..b7009b030f1678b73c15e910e10fb50ea3a9a098 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,
    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/service_task.h"
+#include "smbd/service.h"
 #include "smbd/service_stream.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 "dlinklist.h"
+#include "lib/util/dlinklist.h"
 #include "lib/messaging/irpc.h"
 #include "lib/stream/packet.h"
 #include "librpc/gen_ndr/samr.h"
-#include "netif/netif.h"
+#include "librpc/gen_ndr/ndr_irpc.h"
+#include "librpc/gen_ndr/ndr_krb5pac.h"
+#include "lib/socket/netif.h"
+#include "param/param.h"
+#include "kdc/kdc.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+
+
+/* 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 {
@@ -43,12 +53,13 @@ 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,
                                 struct socket_address *peer_addr, 
-                                struct socket_address *my_addr);
+                                struct socket_address *my_addr, 
+                                int datagram);
 
 /* hold information about one kdc socket */
 struct kdc_socket {
@@ -86,7 +97,7 @@ static void kdc_send_handler(struct kdc_socket *kdc_socket)
                NTSTATUS status;
                size_t sendlen;
 
-               status = socket_sendto(kdc_socket->sock, &rep->packet, &sendlen, 0,
+               status = socket_sendto(kdc_socket->sock, &rep->packet, &sendlen,
                                       rep->dest);
                if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
                        break;
@@ -126,14 +137,14 @@ static void kdc_recv_handler(struct kdc_socket *kdc_socket)
                return;
        }
 
-       blob = data_blob_talloc(kdc_socket, NULL, dsize);
+       blob = data_blob_talloc(tmp_ctx, NULL, dsize);
        if (blob.data == NULL) {
                /* hope this is a temporary low memory condition */
                talloc_free(tmp_ctx);
                return;
        }
 
-       status = socket_recvfrom(kdc_socket->sock, blob.data, blob.length, &nread, 0,
+       status = socket_recvfrom(kdc_socket->sock, blob.data, blob.length, &nread,
                                 tmp_ctx, &src);
        if (!NT_STATUS_IS_OK(status)) {
                talloc_free(tmp_ctx);
@@ -156,7 +167,8 @@ static void kdc_recv_handler(struct kdc_socket *kdc_socket)
                                  tmp_ctx, 
                                  &blob,  
                                  &reply,
-                                 src, my_addr);
+                                 src, my_addr,
+                                 1 /* Datagram */);
        if (!ret) {
                talloc_free(tmp_ctx);
                return;
@@ -239,7 +251,8 @@ static NTSTATUS kdc_tcp_recv(void *private, DATA_BLOB blob)
                               &input,
                               &reply,
                               src_addr,
-                              my_addr);
+                              my_addr,
+                              0 /* Not datagram */);
        if (!ret) {
                talloc_free(tmp_ctx);
                return NT_STATUS_INTERNAL_ERROR;
@@ -300,15 +313,19 @@ 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,
                        struct socket_address *peer_addr, 
-                       struct socket_address *my_addr)
+                       struct socket_address *my_addr,
+                       int datagram_reply)
 {
        int ret;        
        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));
@@ -318,14 +335,19 @@ static BOOL kdc_process(struct kdc_server *kdc,
                                            input->data, input->length,
                                            &k5_reply,
                                            peer_addr->addr,
-                                           peer_addr->sockaddr);
+                                           peer_addr->sockaddr,
+                                           datagram_reply);
        if (ret == -1) {
                *reply = data_blob(NULL, 0);
-               return False;
+               return false;
        }
-       *reply = data_blob_talloc(mem_ctx, k5_reply.data, k5_reply.length);
-       krb5_data_free(&k5_reply);
-       return True;
+       if (k5_reply.length) {
+               *reply = data_blob_talloc(mem_ctx, k5_reply.data, k5_reply.length);
+               krb5_free_data_contents(kdc->smb_krb5_context->krb5_context, &k5_reply);
+       } else {
+               *reply = data_blob(NULL, 0);    
+       }
+       return true;
 }
 
 /*
@@ -388,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);
@@ -470,9 +491,13 @@ static NTSTATUS kdc_add_socket(struct kdc_server *kdc, const char *address)
                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)));
@@ -480,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)));
@@ -497,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);
        }
 
@@ -516,6 +548,118 @@ static NTSTATUS kdc_startup_interfaces(struct kdc_server *kdc)
        return NT_STATUS_OK;
 }
 
+static struct krb5plugin_windc_ftable windc_plugin_table = {
+       .minor_version = KRB5_WINDC_PLUGING_MINOR,
+       .init = samba_kdc_plugin_init,
+       .fini = samba_kdc_plugin_fini,
+       .pac_generate = samba_kdc_get_pac,
+       .pac_verify = samba_kdc_reget_pac,
+       .client_access = samba_kdc_check_client_access,
+};
+
+
+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 0
+       /* Windows does not check this */
+       if (pac_validate.MessageType != 3) {
+               /* We don't implement any other message types - such as certificate validation - yet */
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+#endif 
+       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;
+}
+
+
+
 /*
   startup the kdc task
 */
@@ -524,25 +668,29 @@ 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;
        case ROLE_DOMAIN_MEMBER:
                task_server_terminate(task, "kdc: no KDC required in member server configuration");
                return;
-       case ROLE_DOMAIN_PDC:
-       case ROLE_DOMAIN_BDC:
+       case ROLE_DOMAIN_CONTROLLER:
                /* Yes, we want a KDC */
                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;
        }
 
+       task_server_set_title(task, "task[kdc]");
+
        kdc = talloc(task, struct kdc_server);
        if (kdc == NULL) {
                task_server_terminate(task, "kdc: out of memory");
@@ -551,17 +699,9 @@ static void kdc_task_init(struct task_server *task)
 
        kdc->task = task;
 
-       /* Setup the KDC configuration */
-       kdc->config = talloc(kdc, krb5_kdc_configuration);
-       if (!kdc->config) {
-               task_server_terminate(task, "kdc: out of memory");
-               return;
-       }
-       krb5_kdc_default_config(kdc->config);
-
        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)));
@@ -571,15 +711,23 @@ static void kdc_task_init(struct task_server *task)
 
        krb5_add_et_list(kdc->smb_krb5_context->krb5_context, initialize_hdb_error_table_r);
 
+       ret = krb5_kdc_get_config(kdc->smb_krb5_context->krb5_context, 
+                                 &kdc->config);
+       if(ret) {
+               task_server_terminate(task, "kdc: failed to get KDC configuration");
+               return;
+       }
+
        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_ldb_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");
@@ -591,28 +739,41 @@ static void kdc_task_init(struct task_server *task)
                task_server_terminate(task, "kdc: failed to register hdb keytab");
                return;
        }
+
+       /* 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);
 }