s4-kdc: added proxying of kdc requests for RODCs
authorAndrew Tridgell <tridge@samba.org>
Fri, 12 Nov 2010 06:23:34 +0000 (17:23 +1100)
committerAndrew Tridgell <tridge@samba.org>
Fri, 12 Nov 2010 08:03:20 +0000 (08:03 +0000)
when we are an RODC and we get a request for a principal that we don't
have the right secrets for, we need to proxy the request to a
writeable DC. This happens for both TCP and UDP requests, for both
krb5 and kpasswd

Pair-Programmed-With: Andrew Bartlett <abartlet@samba.org>

Autobuild-User: Andrew Tridgell <tridge@samba.org>
Autobuild-Date: Fri Nov 12 08:03:20 UTC 2010 on sn-devel-104

source4/kdc/kdc-glue.h
source4/kdc/kdc.c
source4/kdc/kpasswdd.c
source4/kdc/proxy.c [new file with mode: 0644]
source4/kdc/wscript_build

index 09ae030934f6730ec63c732b937604a22e27d244..75b6b988fe0e81edebbf9562c7f00d1b63a64e48 100644 (file)
@@ -40,6 +40,9 @@ struct kdc_server {
        krb5_kdc_configuration *config;
        struct smb_krb5_context *smb_krb5_context;
        struct samba_kdc_base_context *base_ctx;
+       struct ldb_context *samdb;
+       bool am_rodc;
+       uint32_t proxy_timeout;
 };
 
 enum kdc_process_ret {
@@ -47,6 +50,58 @@ enum kdc_process_ret {
        KDC_PROCESS_FAILED,
        KDC_PROCESS_PROXY};
 
+struct kdc_udp_call {
+       struct tsocket_address *src;
+       DATA_BLOB in;
+       DATA_BLOB out;
+};
+
+/* hold information about one kdc/kpasswd udp socket */
+struct kdc_udp_socket {
+       struct kdc_socket *kdc_socket;
+       struct tdgram_context *dgram;
+       struct tevent_queue *send_queue;
+};
+
+struct kdc_tcp_call {
+       struct kdc_tcp_connection *kdc_conn;
+       DATA_BLOB in;
+       DATA_BLOB out;
+       uint8_t out_hdr[4];
+       struct iovec out_iov[2];
+};
+
+typedef enum kdc_process_ret (*kdc_process_fn_t)(struct kdc_server *kdc,
+                                                TALLOC_CTX *mem_ctx,
+                                                DATA_BLOB *input,
+                                                DATA_BLOB *reply,
+                                                struct tsocket_address *peer_addr,
+                                                struct tsocket_address *my_addr,
+                                                int datagram);
+
+
+/* hold information about one kdc socket */
+struct kdc_socket {
+       struct kdc_server *kdc;
+       struct tsocket_address *local_address;
+       kdc_process_fn_t process;
+};
+
+/*
+  state of an open tcp connection
+*/
+struct kdc_tcp_connection {
+       /* stream connection we belong to */
+       struct stream_connection *conn;
+
+       /* the kdc_server the connection belongs to */
+       struct kdc_socket *kdc_socket;
+
+       struct tstream_context *tstream;
+
+       struct tevent_queue *send_queue;
+};
+
 
 enum kdc_process_ret kpasswdd_process(struct kdc_server *kdc,
                                      TALLOC_CTX *mem_ctx,
@@ -60,4 +115,11 @@ enum kdc_process_ret kpasswdd_process(struct kdc_server *kdc,
 NTSTATUS hdb_samba4_create_kdc(struct samba_kdc_base_context *base_ctx,
                               krb5_context context, struct HDB **db);
 
+/* from proxy.c */
+void kdc_udp_proxy(struct kdc_server *kdc, struct kdc_udp_socket *sock,
+                  struct kdc_udp_call *call, uint16_t port);
+
+void kdc_tcp_proxy(struct kdc_server *kdc, struct kdc_tcp_connection *kdc_conn,
+                  struct kdc_tcp_call *call, uint16_t port);
+
 #endif
index 43ac8f458b82e67759d2468517e3f86201aa62cb..2a90ea5a273d2bdf7306b7b216a9710cdc5da4f4 100644 (file)
 #include "param/param.h"
 #include "kdc/kdc-glue.h"
 #include "librpc/gen_ndr/ndr_misc.h"
-
+#include "dsdb/samdb/samdb.h"
+#include "auth/session.h"
 
 extern struct krb5plugin_windc_ftable windc_plugin_table;
 extern struct hdb_method hdb_samba4;
 
-typedef enum kdc_process_ret (*kdc_process_fn_t)(struct kdc_server *kdc,
-                                                TALLOC_CTX *mem_ctx,
-                                                DATA_BLOB *input,
-                                                DATA_BLOB *reply,
-                                                struct tsocket_address *peer_addr,
-                                                struct tsocket_address *my_addr,
-                                                int datagram);
-
-/* hold information about one kdc socket */
-struct kdc_socket {
-       struct kdc_server *kdc;
-       struct tsocket_address *local_address;
-       kdc_process_fn_t process;
-};
-
-/*
-  state of an open tcp connection
-*/
-struct kdc_tcp_connection {
-       /* stream connection we belong to */
-       struct stream_connection *conn;
-
-       /* the kdc_server the connection belongs to */
-       struct kdc_socket *kdc_socket;
-
-       struct tstream_context *tstream;
-
-       struct tevent_queue *send_queue;
-};
-
 static void kdc_tcp_terminate_connection(struct kdc_tcp_connection *kdcconn, const char *reason)
 {
        stream_terminate_connection(kdcconn->conn, reason);
@@ -142,6 +113,12 @@ static enum kdc_process_ret kdc_process(struct kdc_server *kdc,
                *reply = data_blob(NULL, 0);
                return KDC_PROCESS_FAILED;
        }
+
+       if (ret == HDB_ERR_NOT_FOUND_HERE) {
+               *reply = data_blob(NULL, 0);
+               return KDC_PROCESS_PROXY;
+       }
+
        if (k5_reply.length) {
                *reply = data_blob_talloc(mem_ctx, k5_reply.data, k5_reply.length);
                krb5_data_free(&k5_reply);
@@ -151,14 +128,6 @@ static enum kdc_process_ret kdc_process(struct kdc_server *kdc,
        return KDC_PROCESS_OK;
 }
 
-struct kdc_tcp_call {
-       struct kdc_tcp_connection *kdc_conn;
-       DATA_BLOB in;
-       DATA_BLOB out;
-       uint8_t out_hdr[4];
-       struct iovec out_iov[2];
-};
-
 static void kdc_tcp_call_writev_done(struct tevent_req *subreq);
 
 static void kdc_tcp_call_loop(struct tevent_req *subreq)
@@ -217,6 +186,17 @@ static void kdc_tcp_call_loop(struct tevent_req *subreq)
                return;
        }
 
+       if (ret == KDC_PROCESS_PROXY) {
+               if (!kdc_conn->kdc_socket->kdc->am_rodc) {
+                       kdc_tcp_terminate_connection(kdc_conn,
+                                                    "kdc_tcp_call_loop: proxying requested when not RODC");
+                       return;
+               }
+               kdc_tcp_proxy(kdc_conn->kdc_socket->kdc, kdc_conn, call,
+                             tsocket_address_inet_port(kdc_conn->conn->local_address));
+               goto done;
+       }
+
        /* First add the length of the out buffer */
        RSIVAL(call->out_hdr, 0, call->out.length);
        call->out_iov[0].iov_base = (char *) call->out_hdr;
@@ -237,6 +217,7 @@ static void kdc_tcp_call_loop(struct tevent_req *subreq)
        }
        tevent_req_set_callback(subreq, kdc_tcp_call_writev_done, call);
 
+done:
        /*
         * The krb5 tcp pdu's has the length as 4 byte (initial_read_size),
         * packet_full_request_u32 provides the pdu length then.
@@ -349,19 +330,6 @@ static const struct stream_server_ops kdc_tcp_stream_ops = {
        .send_handler           = kdc_tcp_send
 };
 
-/* hold information about one kdc/kpasswd udp socket */
-struct kdc_udp_socket {
-       struct kdc_socket *kdc_socket;
-       struct tdgram_context *dgram;
-       struct tevent_queue *send_queue;
-};
-
-struct kdc_udp_call {
-       struct tsocket_address *src;
-       DATA_BLOB in;
-       DATA_BLOB out;
-};
-
 static void kdc_udp_call_sendto_done(struct tevent_req *subreq);
 
 static void kdc_udp_call_loop(struct tevent_req *subreq)
@@ -408,6 +376,17 @@ static void kdc_udp_call_loop(struct tevent_req *subreq)
                goto done;
        }
 
+       if (ret == KDC_PROCESS_PROXY) {
+               if (!sock->kdc_socket->kdc->am_rodc) {
+                       DEBUG(0,("kdc_udp_call_loop: proxying requested when not RODC"));
+                       talloc_free(call);
+                       goto done;
+               }
+               kdc_udp_proxy(sock->kdc_socket->kdc, sock, call,
+                             tsocket_address_inet_port(sock->kdc_socket->local_address));
+               goto done;
+       }
+
        subreq = tdgram_sendto_queue_send(call,
                                          sock->kdc_socket->kdc->task->event_ctx,
                                          sock->dgram,
@@ -677,6 +656,7 @@ static void kdc_task_init(struct task_server *task)
        NTSTATUS status;
        krb5_error_code ret;
        struct interface *ifaces;
+       int ldb_ret;
 
        switch (lpcfg_server_role(task->lp_ctx)) {
        case ROLE_STANDALONE:
@@ -699,7 +679,7 @@ static void kdc_task_init(struct task_server *task)
 
        task_server_set_title(task, "task[kdc]");
 
-       kdc = talloc(task, struct kdc_server);
+       kdc = talloc_zero(task, struct kdc_server);
        if (kdc == NULL) {
                task_server_terminate(task, "kdc: out of memory", true);
                return;
@@ -707,6 +687,26 @@ static void kdc_task_init(struct task_server *task)
 
        kdc->task = task;
 
+
+       /* get a samdb connection */
+       kdc->samdb = samdb_connect(kdc, kdc->task->event_ctx, kdc->task->lp_ctx,
+                                  system_session(kdc->task->lp_ctx), 0);
+       if (!kdc->samdb) {
+               DEBUG(1,("kdc_task_init: unable to connect to samdb\n"));
+               task_server_terminate(task, "kdc: krb5_init_context samdb connect failed", true);
+               return;
+       }
+
+       ldb_ret = samdb_rodc(kdc->samdb, &kdc->am_rodc);
+       if (ldb_ret != LDB_SUCCESS) {
+               DEBUG(1, ("kdc_task_init: Cannot determine if we are an RODC: %s\n",
+                         ldb_errstring(kdc->samdb)));
+               task_server_terminate(task, "kdc: krb5_init_context samdb RODC connect failed", true);
+               return;
+       }
+
+       kdc->proxy_timeout = lpcfg_parm_int(kdc->task->lp_ctx, NULL, "kdc", "proxy timeout", 5);
+
        initialize_krb5_error_table();
 
        ret = smb_krb5_init_context(kdc, task->event_ctx, task->lp_ctx, &kdc->smb_krb5_context);
index ace8a89371d0c20c5b6001ded103ef6e586583e0..88d86cd6e40b676345f47d6f46354636f08e191a 100644 (file)
@@ -177,22 +177,11 @@ static bool kpasswdd_change_password(struct kdc_server *kdc,
        struct ldb_message **res;
        int ret;
 
-       /* Connect to a SAMDB with system privileges for fetching the old pw
-        * hashes. */
-       samdb = samdb_connect(mem_ctx, kdc->task->event_ctx, kdc->task->lp_ctx,
-                             system_session(kdc->task->lp_ctx), 0);
-       if (!samdb) {
-               return kpasswdd_make_error_reply(kdc, mem_ctx,
-                                               KRB5_KPASSWD_HARDERROR,
-                                               "Failed to open samdb",
-                                               reply);
-       }
-
        /* Fetch the old hashes to get the old password in order to perform
         * the password change operation. Naturally it would be much better to
         * have a password hash from an authentication around but this doesn't
         * seem to be the case here. */
-       ret = gendb_search(samdb, mem_ctx, NULL, &res, attrs,
+       ret = gendb_search(kdc->samdb, mem_ctx, NULL, &res, attrs,
                           "(&(objectClass=user)(sAMAccountName=%s))",
                           session_info->server_info->account_name);
        if (ret != 1) {
@@ -478,6 +467,11 @@ enum kdc_process_ret kpasswdd_process(struct kdc_server *kdc,
                return KDC_PROCESS_FAILED;
        }
 
+       if (kdc->am_rodc) {
+               talloc_free(tmp_ctx);
+               return KDC_PROCESS_PROXY;
+       }
+
        /* Be parinoid.  We need to ensure we don't just let the
         * caller lead us into a buffer overflow */
        if (input->length <= header_len) {
@@ -508,6 +502,7 @@ enum kdc_process_ret kpasswdd_process(struct kdc_server *kdc,
        server_credentials = cli_credentials_init(tmp_ctx);
        if (!server_credentials) {
                DEBUG(1, ("Failed to init server credentials\n"));
+               talloc_free(tmp_ctx);
                return KDC_PROCESS_FAILED;
        }
 
@@ -622,6 +617,7 @@ enum kdc_process_ret kpasswdd_process(struct kdc_server *kdc,
                                      &kpasswd_req, &kpasswd_rep);
        if (!ret) {
                /* Argh! */
+               talloc_free(tmp_ctx);
                return KDC_PROCESS_FAILED;
        }
 
@@ -647,6 +643,7 @@ enum kdc_process_ret kpasswdd_process(struct kdc_server *kdc,
 reply:
        *reply = data_blob_talloc(mem_ctx, NULL, krb_priv_rep.length + ap_rep.length + header_len);
        if (!reply->data) {
+               talloc_free(tmp_ctx);
                return KDC_PROCESS_FAILED;
        }
 
diff --git a/source4/kdc/proxy.c b/source4/kdc/proxy.c
new file mode 100644 (file)
index 0000000..3929a12
--- /dev/null
@@ -0,0 +1,657 @@
+/*
+   Unix SMB/CIFS implementation.
+
+   KDC Server request proxying
+
+   Copyright (C) Andrew Tridgell       2010
+   Copyright (C) Andrew Bartlett        2010
+
+   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/process_model.h"
+#include "lib/tsocket/tsocket.h"
+#include "libcli/util/tstream.h"
+#include "system/network.h"
+#include "param/param.h"
+#include "lib/stream/packet.h"
+#include "kdc/kdc-glue.h"
+#include "ldb.h"
+#include "librpc/gen_ndr/drsblobs.h"
+#include "dsdb/schema/schema.h"
+#include "dsdb/common/proto.h"
+#include "libcli/composite/composite.h"
+#include "libcli/resolve/resolve.h"
+
+
+/*
+  get a list of our replication partners from repsFrom, returning it in *proxy_list
+ */
+static WERROR kdc_proxy_get_writeable_dcs(struct kdc_server *kdc, TALLOC_CTX *mem_ctx, char ***proxy_list)
+{
+       WERROR werr;
+       uint32_t count, i;
+       struct repsFromToBlob *reps;
+
+       werr = dsdb_loadreps(kdc->samdb, mem_ctx, ldb_get_default_basedn(kdc->samdb), "repsFrom", &reps, &count);
+       W_ERROR_NOT_OK_RETURN(werr);
+
+       if (count == 0) {
+               /* we don't have any DCs to replicate with. Very
+                  strange for a RODC */
+               DEBUG(1,(__location__ ": No replication sources for RODC in KDC proxy\n"));
+               talloc_free(reps);
+               return WERR_DS_DRA_NO_REPLICA;
+       }
+
+       (*proxy_list) = talloc_array(mem_ctx, char *, count+1);
+       W_ERROR_HAVE_NO_MEMORY_AND_FREE(*proxy_list, reps);
+
+       talloc_steal(*proxy_list, reps);
+
+       for (i=0; i<count; i++) {
+               const char *dns_name = NULL;
+               if (reps->version == 1) {
+                       dns_name = reps->ctr.ctr1.other_info->dns_name;
+               } else if (reps->version == 2) {
+                       dns_name = reps->ctr.ctr2.other_info->dns_name1;
+               }
+               (*proxy_list)[i] = talloc_strdup(*proxy_list, dns_name);
+               W_ERROR_HAVE_NO_MEMORY_AND_FREE((*proxy_list)[i], *proxy_list);
+       }
+       (*proxy_list)[i] = NULL;
+
+       talloc_free(reps);
+
+       return WERR_OK;
+}
+
+
+struct kdc_udp_proxy_state {
+       struct kdc_udp_call *call;
+       struct kdc_udp_socket *sock;
+       struct kdc_server *kdc;
+       char **proxy_list;
+       uint32_t next_proxy;
+       const char *proxy_ip;
+       uint16_t port;
+};
+
+
+static void kdc_udp_next_proxy(struct kdc_udp_proxy_state *state);
+
+/*
+  called when the send of the call to the proxy is complete
+  this is used to get an errors from the sendto()
+ */
+static void kdc_udp_proxy_sendto_done(struct tevent_req *req)
+{
+       struct kdc_udp_proxy_state *state = tevent_req_callback_data(req,
+                                                                    struct kdc_udp_proxy_state);
+       ssize_t ret;
+       int sys_errno;
+
+       ret = tdgram_sendto_queue_recv(req, &sys_errno);
+       talloc_free(req);
+
+       if (ret == -1) {
+               DEBUG(4,("kdc_udp_proxy: sendto for %s gave %d : %s\n",
+                        state->proxy_ip, sys_errno, strerror(sys_errno)));
+               kdc_udp_next_proxy(state);
+       }
+}
+
+/*
+  called when the send of the reply to the client is complete
+  this is used to get an errors from the sendto()
+ */
+static void kdc_udp_proxy_reply_done(struct tevent_req *req)
+{
+       struct kdc_udp_proxy_state *state = tevent_req_callback_data(req,
+                                                                    struct kdc_udp_proxy_state);
+       ssize_t ret;
+       int sys_errno;
+
+       ret = tdgram_sendto_queue_recv(req, &sys_errno);
+       if (ret == -1) {
+               DEBUG(3,("kdc_udp_proxy: reply sendto gave %d : %s\n",
+                        sys_errno, strerror(sys_errno)));
+       }
+
+       /* all done - we can destroy the proxy state */
+       talloc_free(req);
+       talloc_free(state);
+}
+
+
+/*
+  called when the proxy replies
+ */
+static void kdc_udp_proxy_reply(struct tevent_req *req)
+{
+       struct kdc_udp_proxy_state *state = tevent_req_callback_data(req,
+                                                                    struct kdc_udp_proxy_state);
+       int sys_errno;
+       uint8_t *buf;
+       struct tsocket_address *src;
+       ssize_t len;
+
+       len = tdgram_recvfrom_recv(req, &sys_errno,
+                                  state, &buf, &src);
+       talloc_free(req);
+       if (len == -1) {
+               DEBUG(4,("kdc_udp_proxy: reply from %s gave %d : %s\n",
+                        state->proxy_ip, sys_errno, strerror(sys_errno)));
+               kdc_udp_next_proxy(state);
+               return;
+       }
+
+       state->call->out.length = len;
+       state->call->out.data = buf;
+
+       /* TODO: check the reply came from the right IP? */
+
+       req = tdgram_sendto_queue_send(state,
+                                      state->kdc->task->event_ctx,
+                                      state->sock->dgram,
+                                      state->sock->send_queue,
+                                      state->call->out.data,
+                                      state->call->out.length,
+                                      state->call->src);
+       if (req == NULL) {
+               kdc_udp_next_proxy(state);
+               return;
+       }
+
+       tevent_req_set_callback(req, kdc_udp_proxy_reply_done, state);
+}
+
+
+/*
+  called when we've resolved the name of a proxy
+ */
+static void kdc_udp_proxy_resolve_done(struct composite_context *c)
+{
+       struct kdc_udp_proxy_state *state;
+       NTSTATUS status;
+       struct tevent_req *req;
+       struct tsocket_address *local_addr, *proxy_addr;
+       int ret;
+       struct tdgram_context *dgram;
+       struct tevent_queue *send_queue;
+
+       state = talloc_get_type(c->async.private_data, struct kdc_udp_proxy_state);
+
+       status = resolve_name_recv(c, state, &state->proxy_ip);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(0,("Unable to resolve proxy\n"));
+               kdc_udp_next_proxy(state);
+               return;
+       }
+
+       /* get an address for us to use locally */
+       ret = tsocket_address_inet_from_strings(state, "ip", NULL, 0, &local_addr);
+       if (ret != 0) {
+               kdc_udp_next_proxy(state);
+               return;
+       }
+
+       ret = tsocket_address_inet_from_strings(state, "ip",
+                                               state->proxy_ip, state->port, &proxy_addr);
+       if (ret != 0) {
+               kdc_udp_next_proxy(state);
+               return;
+       }
+
+       /* create a socket for us to work on */
+       ret = tdgram_inet_udp_socket(local_addr, proxy_addr, state, &dgram);
+       if (ret != 0) {
+               kdc_udp_next_proxy(state);
+               return;
+       }
+
+       send_queue = tevent_queue_create(state, "kdc_udp_proxy");
+       if (send_queue == NULL) {
+               kdc_udp_next_proxy(state);
+               return;
+       }
+
+       req = tdgram_sendto_queue_send(state,
+                                      state->kdc->task->event_ctx,
+                                      dgram,
+                                      send_queue,
+                                      state->call->in.data,
+                                      state->call->in.length,
+                                      proxy_addr);
+       if (req == NULL) {
+               kdc_udp_next_proxy(state);
+               return;
+       }
+
+       tevent_req_set_callback(req, kdc_udp_proxy_sendto_done, state);
+
+       /* setup to receive the reply from the proxy */
+       req = tdgram_recvfrom_send(state, state->kdc->task->event_ctx, dgram);
+       if (req == NULL) {
+               kdc_udp_next_proxy(state);
+               return;
+       }
+
+       tevent_req_set_callback(req, kdc_udp_proxy_reply, state);
+
+       tevent_req_set_endtime(req, state->kdc->task->event_ctx,
+                              timeval_current_ofs(state->kdc->proxy_timeout, 0));
+
+       DEBUG(4,("kdc_udp_proxy: proxying request to %s\n", state->proxy_ip));
+}
+
+
+/*
+  called when our proxies are not available
+ */
+static void kdc_udp_proxy_unavailable(struct kdc_udp_proxy_state *state)
+{
+       int kret;
+       krb5_data k5_error_blob;
+       struct tevent_req *req;
+
+       kret = krb5_mk_error(state->kdc->smb_krb5_context->krb5_context,
+                            KRB5KDC_ERR_SVC_UNAVAILABLE, NULL, NULL,
+                            NULL, NULL, NULL, NULL, &k5_error_blob);
+       if (kret != 0) {
+               DEBUG(2,(__location__ ": Unable to form krb5 error reply\n"));
+               talloc_free(state);
+               return;
+       }
+
+       state->call->out = data_blob_talloc(state, k5_error_blob.data, k5_error_blob.length);
+       krb5_data_free(&k5_error_blob);
+       if (!state->call->out.data) {
+               talloc_free(state);
+               return;
+       }
+
+       req = tdgram_sendto_queue_send(state,
+                                      state->kdc->task->event_ctx,
+                                      state->sock->dgram,
+                                      state->sock->send_queue,
+                                      state->call->out.data,
+                                      state->call->out.length,
+                                      state->call->src);
+       if (!req) {
+               talloc_free(state);
+               return;
+       }
+
+       tevent_req_set_callback(req, kdc_udp_proxy_reply_done, state);
+}
+
+/*
+  try the next proxy in the list
+ */
+static void kdc_udp_next_proxy(struct kdc_udp_proxy_state *state)
+{
+       const char *proxy_dnsname = state->proxy_list[state->next_proxy];
+       struct nbt_name name;
+       struct composite_context *c;
+
+       if (proxy_dnsname == NULL) {
+               kdc_udp_proxy_unavailable(state);
+               return;
+       }
+
+       state->next_proxy++;
+
+       make_nbt_name(&name, proxy_dnsname, 0);
+
+       c = resolve_name_ex_send(lpcfg_resolve_context(state->kdc->task->lp_ctx),
+                                state,
+                                RESOLVE_NAME_FLAG_FORCE_DNS,
+                                0,
+                                &name,
+                                state->kdc->task->event_ctx);
+       if (c == NULL) {
+               kdc_udp_next_proxy(state);
+               return;
+       }
+       c->async.fn = kdc_udp_proxy_resolve_done;
+       c->async.private_data = state;
+}
+
+
+/*
+  proxy a UDP kdc request to a writeable DC
+ */
+void kdc_udp_proxy(struct kdc_server *kdc, struct kdc_udp_socket *sock,
+                  struct kdc_udp_call *call, uint16_t port)
+{
+       struct kdc_udp_proxy_state *state;
+       WERROR werr;
+
+       state = talloc_zero(kdc, struct kdc_udp_proxy_state);
+       if (state == NULL) {
+               talloc_free(call);
+               return;
+       }
+
+       state->call = talloc_steal(state, call);
+       state->sock = sock;
+       state->kdc  = kdc;
+       state->port = port;
+
+       werr = kdc_proxy_get_writeable_dcs(kdc, state, &state->proxy_list);
+       if (!W_ERROR_IS_OK(werr)) {
+               kdc_udp_proxy_unavailable(state);
+               return;
+       }
+
+       kdc_udp_next_proxy(state);
+}
+
+
+struct kdc_tcp_proxy_state {
+       struct kdc_tcp_call *call;
+       struct kdc_tcp_connection *kdc_conn;
+       struct kdc_server *kdc;
+       uint16_t port;
+       uint32_t next_proxy;
+       char **proxy_list;
+       const char *proxy_ip;
+};
+
+static void kdc_tcp_next_proxy(struct kdc_tcp_proxy_state *state);
+
+/*
+  called when the send of the proxied reply to the client is done
+ */
+static void kdc_tcp_proxy_reply_done(struct tevent_req *req)
+{
+       struct kdc_tcp_proxy_state *state = tevent_req_callback_data(req,
+                                                                    struct kdc_tcp_proxy_state);
+       int ret, sys_errno;
+
+       ret = tstream_writev_queue_recv(req, &sys_errno);
+       if (ret == -1) {
+               DEBUG(4,("kdc_tcp_proxy: writev of reply gave %d : %s\n",
+                        sys_errno, strerror(sys_errno)));
+       }
+       talloc_free(req);
+       talloc_free(state);
+}
+
+/*
+  called when the recv of the proxied reply is done
+ */
+static void kdc_tcp_proxy_recv_done(struct tevent_req *req)
+{
+       struct kdc_tcp_proxy_state *state = tevent_req_callback_data(req,
+                                                                    struct kdc_tcp_proxy_state);
+       NTSTATUS status;
+
+       status = tstream_read_pdu_blob_recv(req,
+                                           state,
+                                           &state->call->out);
+       talloc_free(req);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               kdc_tcp_next_proxy(state);
+               return;
+       }
+
+
+       /* send the reply to the original caller */
+
+       state->call->out_iov[0].iov_base = (char *)state->call->out.data;
+       state->call->out_iov[0].iov_len = state->call->out.length;
+
+       req = tstream_writev_queue_send(state,
+                                       state->kdc_conn->conn->event.ctx,
+                                       state->kdc_conn->tstream,
+                                       state->kdc_conn->send_queue,
+                                       state->call->out_iov, 1);
+       if (req == NULL) {
+               kdc_tcp_next_proxy(state);
+               return;
+       }
+
+       tevent_req_set_callback(req, kdc_tcp_proxy_reply_done, state);
+}
+
+/*
+  called when the send of the proxied packet is done
+ */
+static void kdc_tcp_proxy_send_done(struct tevent_req *req)
+{
+       struct kdc_tcp_proxy_state *state = tevent_req_callback_data(req,
+                                                                    struct kdc_tcp_proxy_state);
+       int ret, sys_errno;
+
+       ret = tstream_writev_queue_recv(req, &sys_errno);
+       talloc_free(req);
+       if (ret == -1) {
+               kdc_tcp_next_proxy(state);
+       }
+}
+
+/*
+  called when we've connected to the proxy
+ */
+static void kdc_tcp_proxy_connect_done(struct tevent_req *req)
+{
+       struct kdc_tcp_proxy_state *state = tevent_req_callback_data(req,
+                                                                    struct kdc_tcp_proxy_state);
+       int ret, sys_errno;
+       struct tstream_context *stream;
+       struct tevent_queue *send_queue;
+
+
+       ret = tstream_inet_tcp_connect_recv(req, &sys_errno, state, &stream, NULL);
+       talloc_free(req);
+
+       if (ret != 0) {
+               kdc_tcp_next_proxy(state);
+               return;
+       }
+
+       RSIVAL(state->call->out_hdr, 0, state->call->in.length);
+       state->call->out_iov[0].iov_base = (char *)state->call->out_hdr;
+       state->call->out_iov[0].iov_len = 4;
+       state->call->out_iov[1].iov_base = (char *) state->call->in.data;
+       state->call->out_iov[1].iov_len = state->call->in.length;
+
+       send_queue = tevent_queue_create(state, "kdc_tcp_proxy");
+       if (send_queue == NULL) {
+               kdc_tcp_next_proxy(state);
+               return;
+       }
+
+       req = tstream_writev_queue_send(state,
+                                       state->kdc_conn->conn->event.ctx,
+                                       stream,
+                                       send_queue,
+                                       state->call->out_iov, 2);
+       if (req == NULL) {
+               kdc_tcp_next_proxy(state);
+               return;
+       }
+
+       tevent_req_set_callback(req, kdc_tcp_proxy_send_done, state);
+
+       req = tstream_read_pdu_blob_send(state,
+                                        state->kdc_conn->conn->event.ctx,
+                                        stream,
+                                        4, /* initial_read_size */
+                                        packet_full_request_u32,
+                                        state);
+       if (req == NULL) {
+               kdc_tcp_next_proxy(state);
+               return;
+       }
+
+       tevent_req_set_callback(req, kdc_tcp_proxy_recv_done, state);
+       tevent_req_set_endtime(req, state->kdc->task->event_ctx,
+                              timeval_current_ofs(state->kdc->proxy_timeout, 0));
+
+}
+
+
+/*
+  called when name resolution for a proxy is done
+ */
+static void kdc_tcp_proxy_resolve_done(struct composite_context *c)
+{
+       struct kdc_tcp_proxy_state *state;
+       NTSTATUS status;
+       struct tevent_req *req;
+       struct tsocket_address *local_addr, *proxy_addr;
+       int ret;
+
+       state = talloc_get_type(c->async.private_data, struct kdc_tcp_proxy_state);
+
+       status = resolve_name_recv(c, state, &state->proxy_ip);
+       if (!NT_STATUS_IS_OK(status)) {
+               kdc_tcp_next_proxy(state);
+               return;
+       }
+
+       /* get an address for us to use locally */
+       ret = tsocket_address_inet_from_strings(state, "ip", NULL, 0, &local_addr);
+       if (ret != 0) {
+               kdc_tcp_next_proxy(state);
+               return;
+       }
+
+       ret = tsocket_address_inet_from_strings(state, "ip",
+                                               state->proxy_ip, state->port, &proxy_addr);
+       if (ret != 0) {
+               kdc_tcp_next_proxy(state);
+               return;
+       }
+
+       /* connect to the proxy */
+       req = tstream_inet_tcp_connect_send(state, state->kdc->task->event_ctx, local_addr, proxy_addr);
+       if (req == NULL) {
+               kdc_tcp_next_proxy(state);
+               return;
+       }
+
+       tevent_req_set_callback(req, kdc_tcp_proxy_connect_done, state);
+
+       tevent_req_set_endtime(req, state->kdc->task->event_ctx,
+                              timeval_current_ofs(state->kdc->proxy_timeout, 0));
+
+       DEBUG(4,("kdc_tcp_proxy: proxying request to %s\n", state->proxy_ip));
+}
+
+
+/*
+  called when our proxies are not available
+ */
+static void kdc_tcp_proxy_unavailable(struct kdc_tcp_proxy_state *state)
+{
+       int kret;
+       krb5_data k5_error_blob;
+       struct tevent_req *req;
+
+       kret = krb5_mk_error(state->kdc->smb_krb5_context->krb5_context,
+                            KRB5KDC_ERR_SVC_UNAVAILABLE, NULL, NULL,
+                            NULL, NULL, NULL, NULL, &k5_error_blob);
+       if (kret != 0) {
+               DEBUG(2,(__location__ ": Unable to form krb5 error reply\n"));
+               talloc_free(state);
+               return;
+       }
+
+
+       state->call->out = data_blob_talloc(state, k5_error_blob.data, k5_error_blob.length);
+       krb5_data_free(&k5_error_blob);
+       if (!state->call->out.data) {
+               talloc_free(state);
+               return;
+       }
+
+       state->call->out_iov[0].iov_base = (char *)state->call->out.data;
+       state->call->out_iov[0].iov_len = state->call->out.length;
+
+       req = tstream_writev_queue_send(state,
+                                       state->kdc_conn->conn->event.ctx,
+                                       state->kdc_conn->tstream,
+                                       state->kdc_conn->send_queue,
+                                       state->call->out_iov, 1);
+       if (!req) {
+               talloc_free(state);
+               return;
+       }
+
+       tevent_req_set_callback(req, kdc_tcp_proxy_reply_done, state);
+}
+
+/*
+  try the next proxy in the list
+ */
+static void kdc_tcp_next_proxy(struct kdc_tcp_proxy_state *state)
+{
+       const char *proxy_dnsname = state->proxy_list[state->next_proxy];
+       struct nbt_name name;
+       struct composite_context *c;
+
+       if (proxy_dnsname == NULL) {
+               kdc_tcp_proxy_unavailable(state);
+               return;
+       }
+
+       state->next_proxy++;
+
+       make_nbt_name(&name, proxy_dnsname, 0);
+
+       c = resolve_name_ex_send(lpcfg_resolve_context(state->kdc->task->lp_ctx),
+                                state,
+                                RESOLVE_NAME_FLAG_FORCE_DNS,
+                                0,
+                                &name,
+                                state->kdc->task->event_ctx);
+       if (c == NULL) {
+               kdc_tcp_next_proxy(state);
+               return;
+       }
+       c->async.fn = kdc_tcp_proxy_resolve_done;
+       c->async.private_data = state;
+}
+
+
+/*
+  proxy a TCP kdc request to a writeable DC
+ */
+void kdc_tcp_proxy(struct kdc_server *kdc, struct kdc_tcp_connection *kdc_conn,
+                  struct kdc_tcp_call *call, uint16_t port)
+{
+       struct kdc_tcp_proxy_state *state;
+       WERROR werr;
+
+       state = talloc_zero(kdc_conn, struct kdc_tcp_proxy_state);
+
+       state->call = talloc_steal(state, call);
+       state->kdc_conn = kdc_conn;
+       state->kdc  = kdc;
+       state->port = port;
+
+       werr = kdc_proxy_get_writeable_dcs(kdc, state, &state->proxy_list);
+       if (!W_ERROR_IS_OK(werr)) {
+               kdc_tcp_proxy_unavailable(state);
+               return;
+       }
+
+       kdc_tcp_next_proxy(state);
+}
index 1ead929314b63af97a5126627ffc5bb2a29a7866..902868331d54d6dd2cd6d0322151b0bf49408875 100644 (file)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 bld.SAMBA_MODULE('KDC',
-       source='kdc.c kpasswdd.c',
+       source='kdc.c kpasswdd.c proxy.c',
        subsystem='service',
        init_function='server_service_kdc_init',
        deps='kdc HDB_SAMBA4 WDC_SAMBA4 samba-hostconfig LIBTSOCKET LIBSAMBA_TSOCKET com_err samba_server_gensec',