From 1ec8d55e275128f2419fb481f88c7d3d87894506 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 12 Nov 2010 17:23:34 +1100 Subject: s4-kdc: added proxying of kdc requests for RODCs 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 Autobuild-User: Andrew Tridgell Autobuild-Date: Fri Nov 12 08:03:20 UTC 2010 on sn-devel-104 --- source4/kdc/kdc-glue.h | 62 +++++ source4/kdc/kdc.c | 106 ++++---- source4/kdc/kpasswdd.c | 21 +- source4/kdc/proxy.c | 657 ++++++++++++++++++++++++++++++++++++++++++++++ source4/kdc/wscript_build | 2 +- 5 files changed, 782 insertions(+), 66 deletions(-) create mode 100644 source4/kdc/proxy.c diff --git a/source4/kdc/kdc-glue.h b/source4/kdc/kdc-glue.h index 09ae030934..75b6b988fe 100644 --- a/source4/kdc/kdc-glue.h +++ b/source4/kdc/kdc-glue.h @@ -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 diff --git a/source4/kdc/kdc.c b/source4/kdc/kdc.c index 43ac8f458b..2a90ea5a27 100644 --- a/source4/kdc/kdc.c +++ b/source4/kdc/kdc.c @@ -41,41 +41,12 @@ #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); diff --git a/source4/kdc/kpasswdd.c b/source4/kdc/kpasswdd.c index ace8a89371..88d86cd6e4 100644 --- a/source4/kdc/kpasswdd.c +++ b/source4/kdc/kpasswdd.c @@ -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 index 0000000000..3929a127ef --- /dev/null +++ b/source4/kdc/proxy.c @@ -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 . +*/ + +#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; iversion == 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); +} diff --git a/source4/kdc/wscript_build b/source4/kdc/wscript_build index 1ead929314..902868331d 100644 --- a/source4/kdc/wscript_build +++ b/source4/kdc/wscript_build @@ -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', -- cgit