1171802Sdelphij/* $NetBSD: send_to_kdc.c,v 1.9 2023/06/19 21:41:44 christos Exp $ */ 2170808Sdelphij 3182739Sdelphij/* 4171802Sdelphij * Copyright (c) 1997 - 2002 Kungliga Tekniska H��gskolan 5170808Sdelphij * (Royal Institute of Technology, Stockholm, Sweden). 6170808Sdelphij * All rights reserved. 7170808Sdelphij * 8170808Sdelphij * Portions Copyright (c) 2010 - 2013 Apple Inc. All rights reserved. 9170808Sdelphij * 10170808Sdelphij * Redistribution and use in source and binary forms, with or without 11170808Sdelphij * modification, are permitted provided that the following conditions 12170808Sdelphij * are met: 13170808Sdelphij * 14170808Sdelphij * 1. Redistributions of source code must retain the above copyright 15170808Sdelphij * notice, this list of conditions and the following disclaimer. 16170808Sdelphij * 17170808Sdelphij * 2. Redistributions in binary form must reproduce the above copyright 18170808Sdelphij * notice, this list of conditions and the following disclaimer in the 19170808Sdelphij * documentation and/or other materials provided with the distribution. 20170808Sdelphij * 21170808Sdelphij * 3. Neither the name of the Institute nor the names of its contributors 22170808Sdelphij * may be used to endorse or promote products derived from this software 23170808Sdelphij * without specific prior written permission. 24170808Sdelphij * 25170808Sdelphij * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 26170808Sdelphij * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27170808Sdelphij * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28170808Sdelphij * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 29170808Sdelphij * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30170808Sdelphij * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31170808Sdelphij * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32170808Sdelphij * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33170808Sdelphij * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34170808Sdelphij * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35170808Sdelphij * SUCH DAMAGE. 36170808Sdelphij */ 37170808Sdelphij 38170808Sdelphij#include "krb5_locl.h" 39170808Sdelphij#include "send_to_kdc_plugin.h" 40170808Sdelphij 41170808Sdelphij/** 42170808Sdelphij * @section send_to_kdc Locating and sending packets to the KDC 43170808Sdelphij * 44170808Sdelphij * The send to kdc code is responsible to request the list of KDC from 45197850Sdelphij * the locate-kdc subsystem and then send requests to each of them. 46197850Sdelphij * 47170808Sdelphij * - Each second a new hostname is tried. 48170808Sdelphij * - If the hostname have several addresses, the first will be tried 49233851Sgleb * directly then in turn the other will be tried every 3 seconds 50170808Sdelphij * (host_timeout). 51170808Sdelphij * - UDP requests are tried 3 times, and it tried with a individual timeout of kdc_timeout / 3. 52170808Sdelphij * - TCP and HTTP requests are tried 1 time. 53170808Sdelphij * 54170808Sdelphij * Total wait time shorter then (number of addresses * 3) + kdc_timeout seconds. 55170808Sdelphij * 56170808Sdelphij */ 57188929Salc 58170808Sdelphijstatic int 59170808Sdelphijinit_port(const char *s, int fallback) 60170808Sdelphij{ 61170808Sdelphij int tmp; 62170808Sdelphij 63170808Sdelphij if (s && sscanf(s, "%d", &tmp) == 1) 64233851Sgleb return htons(tmp); 65233851Sgleb return fallback; 66233851Sgleb} 67233851Sgleb 68233851Sglebstruct send_via_plugin_s { 69233851Sgleb krb5_const_realm realm; 70233851Sgleb krb5_krbhst_info *hi; 71170808Sdelphij time_t timeout; 72170808Sdelphij const krb5_data *send_data; 73171069Sdelphij krb5_data *receive; 74170808Sdelphij}; 75170808Sdelphij 76170808Sdelphij 77170808Sdelphijstatic krb5_error_code KRB5_LIB_CALL 78170808Sdelphijkdccallback(krb5_context context, const void *plug, void *plugctx, void *userctx) 79170808Sdelphij{ 80170808Sdelphij const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug; 81170808Sdelphij struct send_via_plugin_s *ctx = userctx; 82170808Sdelphij 83170808Sdelphij if (service->send_to_kdc == NULL) 84170808Sdelphij return KRB5_PLUGIN_NO_HANDLE; 85170808Sdelphij return service->send_to_kdc(context, plugctx, ctx->hi, ctx->timeout, 86170808Sdelphij ctx->send_data, ctx->receive); 87170808Sdelphij} 88191990Sattilio 89170808Sdelphijstatic krb5_error_code KRB5_LIB_CALL 90170808Sdelphijrealmcallback(krb5_context context, const void *plug, void *plugctx, void *userctx) 91170808Sdelphij{ 92170808Sdelphij const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug; 93170808Sdelphij struct send_via_plugin_s *ctx = userctx; 94170808Sdelphij 95170808Sdelphij if (service->send_to_realm == NULL) 96170808Sdelphij return KRB5_PLUGIN_NO_HANDLE; 97197953Sdelphij return service->send_to_realm(context, plugctx, ctx->realm, ctx->timeout, 98197953Sdelphij ctx->send_data, ctx->receive); 99197953Sdelphij} 100197953Sdelphij 101197953Sdelphijstatic krb5_error_code 102170808Sdelphijkdc_via_plugin(krb5_context context, 103171799Sdelphij krb5_krbhst_info *hi, 104171799Sdelphij time_t timeout, 105176559Sattilio const krb5_data *send_data, 106171802Sdelphij krb5_data *receive) 107175294Sattilio{ 108170808Sdelphij struct send_via_plugin_s userctx; 109171799Sdelphij 110191990Sattilio userctx.realm = NULL; 111170808Sdelphij userctx.hi = hi; 112175202Sattilio userctx.timeout = timeout; 113171802Sdelphij userctx.send_data = send_data; 114170808Sdelphij userctx.receive = receive; 115170808Sdelphij 116170808Sdelphij return _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC, 117170808Sdelphij KRB5_PLUGIN_SEND_TO_KDC_VERSION_0, 0, 118170808Sdelphij &userctx, kdccallback); 119188318Skib} 120211598Sed 121211598Sedstatic krb5_error_code 122211598Sedrealm_via_plugin(krb5_context context, 123170808Sdelphij krb5_const_realm realm, 124170808Sdelphij time_t timeout, 125170808Sdelphij const krb5_data *send_data, 126170808Sdelphij krb5_data *receive) 127170808Sdelphij{ 128170808Sdelphij struct send_via_plugin_s userctx; 129211598Sed 130211598Sed userctx.realm = realm; 131211598Sed userctx.hi = NULL; 132211598Sed userctx.timeout = timeout; 133170808Sdelphij userctx.send_data = send_data; 134170808Sdelphij userctx.receive = receive; 135170808Sdelphij 136170808Sdelphij return _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC, 137170808Sdelphij KRB5_PLUGIN_SEND_TO_KDC_VERSION_2, 0, 138170808Sdelphij &userctx, realmcallback); 139170808Sdelphij} 140170808Sdelphij 141170808Sdelphijstruct krb5_sendto_ctx_data { 142170808Sdelphij int flags; 143170808Sdelphij int type; 144170808Sdelphij krb5_sendto_ctx_func func; 145170808Sdelphij void *data; 146170808Sdelphij char *hostname; 147170808Sdelphij krb5_krbhst_handle krbhst; 148170808Sdelphij 149170808Sdelphij /* context2 */ 150170808Sdelphij const krb5_data *send_data; 151170808Sdelphij krb5_data response; 152170808Sdelphij heim_array_t hosts; 153170808Sdelphij int stateflags; 154170808Sdelphij#define KRBHST_COMPLETED 1 155170808Sdelphij 156170808Sdelphij /* prexmit */ 157170808Sdelphij krb5_sendto_prexmit prexmit_func; 158170808Sdelphij void *prexmit_ctx; 159170808Sdelphij 160170808Sdelphij /* stats */ 161170808Sdelphij struct { 162170808Sdelphij struct timeval start_time; 163170808Sdelphij struct timeval name_resolution; 164170808Sdelphij struct timeval krbhst; 165170808Sdelphij unsigned long sent_packets; 166170808Sdelphij unsigned long num_hosts; 167170808Sdelphij } stats; 168170808Sdelphij unsigned int stid; 169170808Sdelphij}; 170170808Sdelphij 171170808Sdelphijstatic void 172170808Sdelphijdealloc_sendto_ctx(void *ptr) 173171070Sdelphij{ 174170808Sdelphij krb5_sendto_ctx ctx = (krb5_sendto_ctx)ptr; 175171799Sdelphij if (ctx->hostname) 176191990Sattilio free(ctx->hostname); 177170808Sdelphij heim_release(ctx->hosts); 178170808Sdelphij heim_release(ctx->krbhst); 179170808Sdelphij} 180170808Sdelphij 181170808SdelphijKRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 182170808Sdelphijkrb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx) 183170808Sdelphij{ 184170808Sdelphij *ctx = heim_alloc(sizeof(**ctx), "sendto-context", dealloc_sendto_ctx); 185170808Sdelphij if (*ctx == NULL) 186170808Sdelphij return krb5_enomem(context); 187171070Sdelphij (*ctx)->hosts = heim_array_create(); 188170808Sdelphij 189171799Sdelphij return 0; 190171799Sdelphij} 191191990Sattilio 192170808SdelphijKRB5_LIB_FUNCTION void KRB5_LIB_CALL 193170808Sdelphijkrb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx, int flags) 194170808Sdelphij{ 195170808Sdelphij ctx->flags |= flags; 196170808Sdelphij} 197170808Sdelphij 198170808SdelphijKRB5_LIB_FUNCTION int KRB5_LIB_CALL 199170808Sdelphijkrb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx) 200170808Sdelphij{ 201170808Sdelphij return ctx->flags; 202170808Sdelphij} 203170808Sdelphij 204170808SdelphijKRB5_LIB_FUNCTION void KRB5_LIB_CALL 205176559Sattiliokrb5_sendto_ctx_set_type(krb5_sendto_ctx ctx, int type) 206170808Sdelphij{ 207170808Sdelphij ctx->type = type; 208170808Sdelphij} 209170808Sdelphij 210170808SdelphijKRB5_LIB_FUNCTION void KRB5_LIB_CALL 211170808Sdelphijkrb5_sendto_ctx_set_func(krb5_sendto_ctx ctx, 212171069Sdelphij krb5_sendto_ctx_func func, 213170808Sdelphij void *data) 214170808Sdelphij{ 215170808Sdelphij ctx->func = func; 216170808Sdelphij ctx->data = data; 217170808Sdelphij} 218170808Sdelphij 219170808SdelphijKRB5_LIB_FUNCTION void KRB5_LIB_CALL 220170808Sdelphij_krb5_sendto_ctx_set_prexmit(krb5_sendto_ctx ctx, 221170808Sdelphij krb5_sendto_prexmit prexmit, 222170808Sdelphij void *data) 223170808Sdelphij{ 224170808Sdelphij ctx->prexmit_func = prexmit; 225170808Sdelphij ctx->prexmit_ctx = data; 226171069Sdelphij} 227170808Sdelphij 228170808SdelphijKRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 229170808Sdelphijkrb5_sendto_set_hostname(krb5_context context, 230170808Sdelphij krb5_sendto_ctx ctx, 231170808Sdelphij const char *hostname) 232170808Sdelphij{ 233170808Sdelphij if (ctx->hostname == NULL) 234170808Sdelphij free(ctx->hostname); 235170808Sdelphij ctx->hostname = strdup(hostname); 236170808Sdelphij if (ctx->hostname == NULL) { 237170808Sdelphij krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", "")); 238170808Sdelphij return ENOMEM; 239170808Sdelphij } 240170808Sdelphij return 0; 241170808Sdelphij} 242170808Sdelphij 243171069SdelphijKRB5_LIB_FUNCTION void KRB5_LIB_CALL 244170808Sdelphij_krb5_sendto_ctx_set_krb5hst(krb5_context context, 245170808Sdelphij krb5_sendto_ctx ctx, 246170808Sdelphij krb5_krbhst_handle handle) 247170808Sdelphij{ 248170808Sdelphij heim_release(ctx->krbhst); 249170808Sdelphij ctx->krbhst = heim_retain(handle); 250170808Sdelphij} 251170808Sdelphij 252176559SattilioKRB5_LIB_FUNCTION void KRB5_LIB_CALL 253170808Sdelphijkrb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx) 254170808Sdelphij{ 255171070Sdelphij heim_release(ctx); 256170808Sdelphij} 257170808Sdelphij 258170808SdelphijKRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 259170808Sdelphij_krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data, 260170808Sdelphij const krb5_data *reply, int *action) 261170808Sdelphij{ 262170808Sdelphij krb5_error_code ret; 263170808Sdelphij KRB_ERROR error; 264170808Sdelphij 265170808Sdelphij if(krb5_rd_error(context, reply, &error)) 266170808Sdelphij return 0; 267171070Sdelphij 268170808Sdelphij ret = krb5_error_from_rd_error(context, &error, NULL); 269170808Sdelphij krb5_free_error_contents(context, &error); 270176559Sattilio 271170808Sdelphij switch(ret) { 272170808Sdelphij case KRB5KRB_ERR_RESPONSE_TOO_BIG: { 273170808Sdelphij if (krb5_sendto_ctx_get_flags(ctx) & KRB5_KRBHST_FLAGS_LARGE_MSG) 274170808Sdelphij break; 275170808Sdelphij krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG); 276171069Sdelphij *action = KRB5_SENDTO_RESET; 277170808Sdelphij break; 278170808Sdelphij } 279170808Sdelphij case KRB5KDC_ERR_SVC_UNAVAILABLE: 280170808Sdelphij *action = KRB5_SENDTO_CONTINUE; 281176559Sattilio break; 282170808Sdelphij } 283218949Salc return 0; 284218949Salc} 285170808Sdelphij 286218949Salc/* 287170808Sdelphij * 288170808Sdelphij */ 289170808Sdelphij 290170808Sdelphijstruct host; 291170808Sdelphij 292170808Sdelphijstruct host_fun { 293170808Sdelphij krb5_error_code (*prepare)(krb5_context, struct host *, const krb5_data *); 294170808Sdelphij krb5_error_code (*send_fn)(krb5_context, struct host *); 295184413Strasz krb5_error_code (*recv_fn)(krb5_context, struct host *, krb5_data *); 296170808Sdelphij int ntries; 297170808Sdelphij}; 298170808Sdelphij 299170808Sdelphijstruct host { 300170808Sdelphij enum host_state { CONNECT, CONNECTING, CONNECTED, WAITING_REPLY, DEAD } state; 301176559Sattilio krb5_krbhst_info *hi; 302170808Sdelphij struct addrinfo *ai; 303170808Sdelphij rk_socket_t fd; 304170808Sdelphij struct host_fun *fun; 305170808Sdelphij unsigned int tries; 306170808Sdelphij time_t timeout; 307170808Sdelphij krb5_data data; 308170808Sdelphij unsigned int tid; 309170808Sdelphij}; 310170808Sdelphij 311184413Straszstatic void 312170808Sdelphijdebug_host(krb5_context context, int level, struct host *host, const char *fmt, ...) 313170808Sdelphij __attribute__ ((__format__ (__printf__, 4, 5))); 314170808Sdelphij 315170808Sdelphijstatic void 316170808Sdelphijdebug_host(krb5_context context, int level, struct host *host, const char *fmt, ...) 317170808Sdelphij{ 318170808Sdelphij const char *proto = "unknown"; 319170808Sdelphij const char *state; 320170808Sdelphij char name[NI_MAXHOST], port[NI_MAXSERV]; 321170808Sdelphij char *text = NULL; 322170808Sdelphij va_list ap; 323170808Sdelphij int ret; 324170808Sdelphij 325170808Sdelphij if (!_krb5_have_debug(context, 5)) 326170808Sdelphij return; 327170808Sdelphij 328170808Sdelphij va_start(ap, fmt); 329170808Sdelphij ret = vasprintf(&text, fmt, ap); 330170808Sdelphij va_end(ap); 331184413Strasz if (ret == -1 || text == NULL) 332170808Sdelphij return; 333170808Sdelphij 334170808Sdelphij if (host->hi->proto == KRB5_KRBHST_HTTP) 335170808Sdelphij proto = "http"; 336170808Sdelphij else if (host->hi->proto == KRB5_KRBHST_TCP) 337184413Strasz proto = "tcp"; 338170808Sdelphij else if (host->hi->proto == KRB5_KRBHST_UDP) 339170808Sdelphij proto = "udp"; 340176559Sattilio 341170808Sdelphij if (getnameinfo(host->ai->ai_addr, host->ai->ai_addrlen, 342170808Sdelphij name, sizeof(name), port, sizeof(port), NI_NUMERICHOST) != 0) 343170808Sdelphij name[0] = '\0'; 344170808Sdelphij 345170808Sdelphij switch (host->state) { 346170808Sdelphij case CONNECT: state = "CONNECT"; break; 347170808Sdelphij case CONNECTING: state = "CONNECTING"; break; 348170808Sdelphij case CONNECTED: state = "CONNECTED"; break; 349170808Sdelphij case WAITING_REPLY: state = "WAITING_REPLY"; break; 350170808Sdelphij case DEAD: state = "DEAD"; break; 351170808Sdelphij default: state = "unknown"; break; 352170808Sdelphij } 353170808Sdelphij 354170808Sdelphij _krb5_debug(context, level, "%s: %s %s:%s (%s) state=%s tid: %08x", text, 355170808Sdelphij proto, name, port, host->hi->hostname, state, host->tid); 356170808Sdelphij free(text); 357170808Sdelphij} 358170808Sdelphij 359170808Sdelphij 360170808Sdelphijstatic void 361170808Sdelphijdeallocate_host(void *ptr) 362170808Sdelphij{ 363170808Sdelphij struct host *host = ptr; 364170808Sdelphij if (!rk_IS_BAD_SOCKET(host->fd)) 365170808Sdelphij rk_closesocket(host->fd); 366170808Sdelphij krb5_data_free(&host->data); 367170808Sdelphij host->ai = NULL; 368170808Sdelphij} 369170808Sdelphij 370170808Sdelphijstatic void 371170808Sdelphijhost_dead(krb5_context context, struct host *host, const char *msg) 372170808Sdelphij{ 373170808Sdelphij debug_host(context, 5, host, "%s", msg); 374170808Sdelphij rk_closesocket(host->fd); 375183214Skib host->fd = rk_INVALID_SOCKET; 376170808Sdelphij host->state = DEAD; 377183212Skib} 378170808Sdelphij 379170808Sdelphijstatic krb5_error_code 380170808Sdelphijsend_stream(krb5_context context, struct host *host) 381170808Sdelphij{ 382170808Sdelphij ssize_t len; 383170808Sdelphij 384170808Sdelphij len = krb5_net_write(context, &host->fd, host->data.data, host->data.length); 385170808Sdelphij 386170808Sdelphij if (len < 0) 387170808Sdelphij return errno; 388170808Sdelphij else if (len < host->data.length) { 389170808Sdelphij host->data.length -= len; 390170808Sdelphij memmove(host->data.data, ((uint8_t *)host->data.data) + len, host->data.length - len); 391170808Sdelphij return -1; 392182371Sattilio } else { 393170808Sdelphij krb5_data_free(&host->data); 394170808Sdelphij return 0; 395170808Sdelphij } 396176559Sattilio} 397170808Sdelphij 398170808Sdelphijstatic krb5_error_code 399170808Sdelphijrecv_stream(krb5_context context, struct host *host) 400170808Sdelphij{ 401170808Sdelphij krb5_error_code ret; 402170808Sdelphij size_t oldlen; 403170808Sdelphij ssize_t sret; 404170808Sdelphij int nbytes; 405170808Sdelphij 406170808Sdelphij if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0) 407170808Sdelphij return HEIM_NET_CONN_REFUSED; 408170808Sdelphij 409170808Sdelphij if (context->max_msg_size - host->data.length < nbytes) { 410170808Sdelphij krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG, 411170808Sdelphij N_("TCP message from KDC too large %d", ""), 412182371Sattilio (int)(host->data.length + nbytes)); 413170808Sdelphij return KRB5KRB_ERR_FIELD_TOOLONG; 414170808Sdelphij } 415182371Sattilio 416170808Sdelphij oldlen = host->data.length; 417170808Sdelphij 418182371Sattilio ret = krb5_data_realloc(&host->data, oldlen + nbytes + 1 /* NUL */); 419170808Sdelphij if (ret) 420170808Sdelphij return ret; 421182371Sattilio 422170808Sdelphij sret = krb5_net_read(context, &host->fd, ((uint8_t *)host->data.data) + oldlen, nbytes); 423170808Sdelphij if (sret <= 0) { 424170808Sdelphij ret = errno; 425170808Sdelphij return ret; 426170808Sdelphij } 427170808Sdelphij host->data.length = oldlen + sret; 428170808Sdelphij /* zero terminate for http transport */ 429171070Sdelphij ((uint8_t *)host->data.data)[host->data.length] = '\0'; 430182371Sattilio 431170808Sdelphij return 0; 432170808Sdelphij} 433170808Sdelphij 434170808Sdelphij/* 435170808Sdelphij * 436170808Sdelphij */ 437176559Sattilio 438170808Sdelphijstatic void 439170808Sdelphijhost_next_timeout(krb5_context context, struct host *host) 440170808Sdelphij{ 441170808Sdelphij host->timeout = context->kdc_timeout / host->fun->ntries; 442170808Sdelphij if (host->timeout == 0) 443197850Sdelphij host->timeout = 1; 444197850Sdelphij 445197850Sdelphij host->timeout += time(NULL); 446197850Sdelphij} 447197850Sdelphij 448197850Sdelphij/* 449171489Sdelphij * connected host 450197850Sdelphij */ 451197850Sdelphij 452197850Sdelphijstatic void 453231775Salchost_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host) 454197850Sdelphij{ 455197850Sdelphij krb5_error_code ret; 456197850Sdelphij 457197850Sdelphij host->state = CONNECTED; 458197850Sdelphij /* 459197850Sdelphij * Now prepare data to send to host 460197850Sdelphij */ 461197850Sdelphij if (ctx->prexmit_func) { 462197850Sdelphij krb5_data data; 463197850Sdelphij 464197850Sdelphij krb5_data_zero(&data); 465197850Sdelphij 466197850Sdelphij ret = ctx->prexmit_func(context, host->hi->proto, 467197850Sdelphij ctx->prexmit_ctx, host->fd, &data); 468207573Salc if (ret == 0) { 469197850Sdelphij if (data.length == 0) { 470207573Salc host_dead(context, host, "prexmit function didn't send data"); 471197850Sdelphij return; 472197850Sdelphij } 473197850Sdelphij ret = host->fun->prepare(context, host, &data); 474197850Sdelphij krb5_data_free(&data); 475197850Sdelphij } 476197850Sdelphij 477197850Sdelphij } else { 478197850Sdelphij ret = host->fun->prepare(context, host, ctx->send_data); 479197850Sdelphij } 480197850Sdelphij if (ret) 481197850Sdelphij debug_host(context, 5, host, "failed to prexmit/prepare"); 482197850Sdelphij} 483197850Sdelphij 484197850Sdelphij/* 485197850Sdelphij * connect host 486197850Sdelphij */ 487197850Sdelphij 488197850Sdelphijstatic void 489197850Sdelphijhost_connect(krb5_context context, krb5_sendto_ctx ctx, struct host *host) 490197850Sdelphij{ 491197850Sdelphij krb5_krbhst_info *hi = host->hi; 492197850Sdelphij struct addrinfo *ai = host->ai; 493197850Sdelphij 494197850Sdelphij debug_host(context, 5, host, "connecting to host"); 495197850Sdelphij 496197850Sdelphij if (connect(host->fd, ai->ai_addr, ai->ai_addrlen) < 0) { 497197850Sdelphij#ifdef HAVE_WINSOCK 498197850Sdelphij if (WSAGetLastError() == WSAEWOULDBLOCK) 499170808Sdelphij errno = EINPROGRESS; 500171489Sdelphij#endif /* HAVE_WINSOCK */ 501170808Sdelphij if (errno == EINPROGRESS && (hi->proto == KRB5_KRBHST_HTTP || hi->proto == KRB5_KRBHST_TCP)) { 502197850Sdelphij debug_host(context, 5, host, "connecting to %d", host->fd); 503171489Sdelphij host->state = CONNECTING; 504171489Sdelphij } else { 505188929Salc host_dead(context, host, "failed to connect"); 506188929Salc } 507171489Sdelphij } else { 508197850Sdelphij host_connected(context, ctx, host); 509171489Sdelphij } 510170808Sdelphij 511171489Sdelphij host_next_timeout(context, host); 512171489Sdelphij} 513171489Sdelphij 514171489Sdelphij/* 515170808Sdelphij * HTTP transport 516197740Sdelphij */ 517197740Sdelphij 518171489Sdelphijstatic krb5_error_code 519170808Sdelphijprepare_http(krb5_context context, struct host *host, const krb5_data *data) 520171489Sdelphij{ 521171489Sdelphij char *str = NULL, *request = NULL; 522171489Sdelphij krb5_error_code ret; 523171489Sdelphij int len; 524207530Salc 525207530Salc heim_assert(host->data.length == 0, "prepare_http called twice"); 526207530Salc 527207530Salc len = rk_base64_encode(data->data, data->length, &str); 528207530Salc if(len < 0) 529225418Skib return ENOMEM; 530207530Salc 531171489Sdelphij if (context->http_proxy) 532207530Salc ret = asprintf(&request, "GET http://%s/%s HTTP/1.0\r\n\r\n", host->hi->hostname, str); 533171489Sdelphij else 534171489Sdelphij ret = asprintf(&request, "GET /%s HTTP/1.0\r\n\r\n", str); 535188929Salc free(str); 536171489Sdelphij if(ret < 0 || request == NULL) 537171489Sdelphij return ENOMEM; 538171489Sdelphij 539171489Sdelphij host->data.data = request; 540197850Sdelphij host->data.length = strlen(request); 541213735Savg 542213735Savg return 0; 543207530Salc} 544207530Salc 545207530Salcstatic krb5_error_code 546207530Salcrecv_http(krb5_context context, struct host *host, krb5_data *data) 547207530Salc{ 548225418Skib krb5_error_code ret; 549207530Salc unsigned long rep_len; 550197850Sdelphij size_t len; 551207530Salc char *p; 552197850Sdelphij 553197850Sdelphij /* 554197850Sdelphij * recv_stream returns a NUL terminated stream 555197850Sdelphij */ 556197850Sdelphij 557213735Savg ret = recv_stream(context, host); 558197850Sdelphij if (ret) 559213735Savg return ret; 560213735Savg 561197850Sdelphij p = strstr(host->data.data, "\r\n\r\n"); 562197850Sdelphij if (p == NULL) 563197850Sdelphij return -1; 564197850Sdelphij p += 4; 565197850Sdelphij 566197850Sdelphij len = host->data.length - (p - (char *)host->data.data); 567212650Savg if (len < 4) 568213735Savg return -1; 569197850Sdelphij 570197850Sdelphij _krb5_get_int(p, &rep_len, 4); 571197850Sdelphij if (len < rep_len) 572171799Sdelphij return -1; 573171489Sdelphij 574171489Sdelphij p += 4; 575197850Sdelphij 576171489Sdelphij memmove(host->data.data, p, rep_len); 577171489Sdelphij host->data.length = rep_len; 578170808Sdelphij 579170808Sdelphij *data = host->data; 580171069Sdelphij krb5_data_zero(&host->data); 581170808Sdelphij 582170808Sdelphij return 0; 583170808Sdelphij} 584170808Sdelphij 585170808Sdelphij/* 586170808Sdelphij * TCP transport 587170808Sdelphij */ 588171489Sdelphij 589171489Sdelphijstatic krb5_error_code 590170808Sdelphijprepare_tcp(krb5_context context, struct host *host, const krb5_data *data) 591174265Swkoszek{ 592170808Sdelphij krb5_error_code ret; 593170808Sdelphij krb5_storage *sp; 594170808Sdelphij 595170808Sdelphij heim_assert(host->data.length == 0, "prepare_tcp called twice"); 596170808Sdelphij 597170808Sdelphij sp = krb5_storage_emem(); 598170808Sdelphij if (sp == NULL) 599170808Sdelphij return ENOMEM; 600170808Sdelphij 601170808Sdelphij ret = krb5_store_data(sp, *data); 602170808Sdelphij if (ret) { 603170808Sdelphij krb5_storage_free(sp); 604170808Sdelphij return ret; 605170808Sdelphij } 606170808Sdelphij ret = krb5_storage_to_data(sp, &host->data); 607170808Sdelphij krb5_storage_free(sp); 608171489Sdelphij 609171489Sdelphij return ret; 610171489Sdelphij} 611171489Sdelphij 612171489Sdelphijstatic krb5_error_code 613171489Sdelphijrecv_tcp(krb5_context context, struct host *host, krb5_data *data) 614171489Sdelphij{ 615171489Sdelphij krb5_error_code ret; 616171489Sdelphij unsigned long pktlen; 617171489Sdelphij 618171489Sdelphij ret = recv_stream(context, host); 619170808Sdelphij if (ret) 620170808Sdelphij return ret; 621170808Sdelphij 622170808Sdelphij if (host->data.length < 4) 623170808Sdelphij return -1; 624170808Sdelphij 625170808Sdelphij _krb5_get_int(host->data.data, &pktlen, 4); 626170808Sdelphij 627171069Sdelphij if (pktlen > host->data.length - 4) 628171489Sdelphij return -1; 629171489Sdelphij 630171489Sdelphij memmove(host->data.data, ((uint8_t *)host->data.data) + 4, host->data.length - 4); 631171489Sdelphij host->data.length -= 4; 632188929Salc 633188929Salc *data = host->data; 634171489Sdelphij krb5_data_zero(&host->data); 635171489Sdelphij 636171489Sdelphij return 0; 637174265Swkoszek} 638174265Swkoszek 639171489Sdelphij/* 640171489Sdelphij * UDP transport 641171489Sdelphij */ 642171489Sdelphij 643171489Sdelphijstatic krb5_error_code 644197740Sdelphijprepare_udp(krb5_context context, struct host *host, const krb5_data *data) 645197740Sdelphij{ 646171489Sdelphij return krb5_data_copy(&host->data, data->data, data->length); 647171489Sdelphij} 648171489Sdelphij 649171489Sdelphijstatic krb5_error_code 650171489Sdelphijsend_udp(krb5_context context, struct host *host) 651171489Sdelphij{ 652171489Sdelphij if (send(host->fd, host->data.data, host->data.length, 0) < 0) 653171489Sdelphij return errno; 654207530Salc return 0; 655207530Salc} 656207530Salc 657207530Salcstatic krb5_error_code 658207530Salcrecv_udp(krb5_context context, struct host *host, krb5_data *data) 659225418Skib{ 660207530Salc krb5_error_code ret; 661171489Sdelphij int nbytes; 662207530Salc 663171489Sdelphij 664171489Sdelphij if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0) 665171489Sdelphij return HEIM_NET_CONN_REFUSED; 666188929Salc 667171489Sdelphij if (context->max_msg_size < nbytes) { 668197740Sdelphij krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG, 669197740Sdelphij N_("UDP message from KDC too large %d", ""), 670171489Sdelphij (int)nbytes); 671171489Sdelphij return KRB5KRB_ERR_FIELD_TOOLONG; 672171489Sdelphij } 673171489Sdelphij 674171489Sdelphij ret = krb5_data_alloc(data, nbytes); 675171489Sdelphij if (ret) 676171489Sdelphij return ret; 677231775Salc 678171489Sdelphij ret = recv(host->fd, data->data, data->length, 0); 679194124Salc if (ret < 0) { 680171489Sdelphij ret = errno; 681171489Sdelphij krb5_data_free(data); 682171489Sdelphij return ret; 683171489Sdelphij } 684171489Sdelphij data->length = ret; 685171489Sdelphij 686171489Sdelphij return 0; 687171489Sdelphij} 688171489Sdelphij 689188929Salcstatic struct host_fun http_fun = { 690188929Salc prepare_http, 691188929Salc send_stream, 692171489Sdelphij recv_http, 693171489Sdelphij 1 694171489Sdelphij}; 695171489Sdelphijstatic struct host_fun tcp_fun = { 696171489Sdelphij prepare_tcp, 697171489Sdelphij send_stream, 698171489Sdelphij recv_tcp, 699171489Sdelphij 1 700192917Salc}; 701192917Salcstatic struct host_fun udp_fun = { 702171489Sdelphij prepare_udp, 703171489Sdelphij send_udp, 704209226Salc recv_udp, 705188921Salc 3 706207573Salc}; 707171489Sdelphij 708171489Sdelphij 709171489Sdelphij/* 710171489Sdelphij * Host state machine 711171489Sdelphij */ 712171489Sdelphij 713171489Sdelphijstatic int 714171489Sdelphijeval_host_state(krb5_context context, 715171489Sdelphij krb5_sendto_ctx ctx, 716171489Sdelphij struct host *host, 717171489Sdelphij int readable, int writeable) 718171489Sdelphij{ 719170808Sdelphij krb5_error_code ret; 720170808Sdelphij 721170808Sdelphij if (host->state == CONNECT) { 722170808Sdelphij /* check if its this host time to connect */ 723170808Sdelphij if (host->timeout < time(NULL)) 724170808Sdelphij host_connect(context, ctx, host); 725170808Sdelphij return 0; 726171489Sdelphij } 727170808Sdelphij 728170808Sdelphij if (host->state == CONNECTING && writeable) 729170808Sdelphij host_connected(context, ctx, host); 730171489Sdelphij 731171489Sdelphij if (readable) { 732170808Sdelphij 733170808Sdelphij debug_host(context, 5, host, "reading packet"); 734170808Sdelphij 735170808Sdelphij ret = host->fun->recv_fn(context, host, &ctx->response); 736170808Sdelphij if (ret == -1) { 737170808Sdelphij /* not done yet */ 738170808Sdelphij } else if (ret == 0) { 739170808Sdelphij /* if recv_foo function returns 0, we have a complete reply */ 740170808Sdelphij debug_host(context, 5, host, "host completed"); 741170808Sdelphij return 1; 742170808Sdelphij } else { 743170808Sdelphij host_dead(context, host, "host disconnected"); 744170808Sdelphij } 745170808Sdelphij } 746170808Sdelphij 747170808Sdelphij /* check if there is anything to send, state might DEAD after read */ 748171070Sdelphij if (writeable && host->state == CONNECTED) { 749171070Sdelphij 750170808Sdelphij ctx->stats.sent_packets++; 751170808Sdelphij 752170808Sdelphij debug_host(context, 5, host, "writing packet"); 753207719Strasz 754207662Strasz ret = host->fun->send_fn(context, host); 755170808Sdelphij if (ret == -1) { 756170808Sdelphij /* not done yet */ 757170808Sdelphij } else if (ret) { 758170808Sdelphij host_dead(context, host, "host dead, write failed"); 759170808Sdelphij } else 760170808Sdelphij host->state = WAITING_REPLY; 761170808Sdelphij } 762170808Sdelphij 763170808Sdelphij return 0; 764171489Sdelphij} 765171489Sdelphij 766171489Sdelphij/* 767171489Sdelphij * 768171489Sdelphij */ 769171489Sdelphij 770171489Sdelphijstatic krb5_error_code 771171489Sdelphijsubmit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi) 772171489Sdelphij{ 773171489Sdelphij unsigned long submitted_host = 0; 774170808Sdelphij krb5_boolean freeai = FALSE; 775170808Sdelphij struct timeval nrstart, nrstop; 776170808Sdelphij krb5_error_code ret; 777170808Sdelphij struct addrinfo *ai = NULL, *a; 778170808Sdelphij struct host *host; 779170808Sdelphij 780170808Sdelphij ret = kdc_via_plugin(context, hi, context->kdc_timeout, 781170808Sdelphij ctx->send_data, &ctx->response); 782170808Sdelphij if (ret == 0) { 783170808Sdelphij return 0; 784170808Sdelphij } else if (ret != KRB5_PLUGIN_NO_HANDLE) { 785170808Sdelphij _krb5_debug(context, 5, "send via plugin failed %s: %d", 786170808Sdelphij hi->hostname, ret); 787170808Sdelphij return ret; 788170808Sdelphij } 789170808Sdelphij 790170808Sdelphij /* 791170808Sdelphij * If we have a proxy, let use the address of the proxy instead of 792170808Sdelphij * the KDC and let the proxy deal with the resolving of the KDC. 793170808Sdelphij */ 794170808Sdelphij 795171069Sdelphij gettimeofday(&nrstart, NULL); 796170808Sdelphij 797170808Sdelphij if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) { 798170808Sdelphij char *proxy2 = strdup(context->http_proxy); 799170808Sdelphij char *el, *proxy = proxy2; 800176559Sattilio struct addrinfo hints; 801170808Sdelphij char portstr[NI_MAXSERV]; 802170808Sdelphij unsigned short nport; 803170808Sdelphij 804170808Sdelphij if (proxy == NULL) 805170808Sdelphij return ENOMEM; 806170808Sdelphij if (strncmp(proxy, "http://", 7) == 0) 807170808Sdelphij proxy += 7; 808170808Sdelphij 809171069Sdelphij /* check for url terminating slash */ 810170808Sdelphij el = strchr(proxy, '/'); 811170808Sdelphij if (el != NULL) 812170808Sdelphij *el = '\0'; 813170808Sdelphij 814170808Sdelphij /* check for port in hostname, used below as port */ 815170808Sdelphij el = strchr(proxy, ':'); 816170808Sdelphij if(el != NULL) 817170808Sdelphij *el++ = '\0'; 818170808Sdelphij 819170808Sdelphij memset(&hints, 0, sizeof(hints)); 820170808Sdelphij hints.ai_family = PF_UNSPEC; 821176559Sattilio hints.ai_socktype = SOCK_STREAM; 822176559Sattilio 823170808Sdelphij /* On some systems ntohs(foo(..., htons(...))) causes shadowing */ 824170808Sdelphij nport = init_port(el, htons(80)); 825170808Sdelphij snprintf(portstr, sizeof(portstr), "%d", ntohs(nport)); 826170808Sdelphij 827170808Sdelphij ret = getaddrinfo(proxy, portstr, &hints, &ai); 828170808Sdelphij free(proxy2); 829170808Sdelphij if (ret) 830170808Sdelphij return krb5_eai_to_heim_errno(ret, errno); 831170808Sdelphij 832188318Skib freeai = TRUE; 833170808Sdelphij 834170808Sdelphij } else { 835170808Sdelphij ret = krb5_krbhst_get_addrinfo(context, hi, &ai); 836170808Sdelphij if (ret) 837170808Sdelphij return ret; 838170808Sdelphij } 839170808Sdelphij 840170808Sdelphij /* add up times */ 841170808Sdelphij gettimeofday(&nrstop, NULL); 842170808Sdelphij timevalsub(&nrstop, &nrstart); 843170808Sdelphij timevaladd(&ctx->stats.name_resolution, &nrstop); 844170808Sdelphij 845211598Sed ctx->stats.num_hosts++; 846211598Sed 847170808Sdelphij for (a = ai; a != NULL; a = a->ai_next) { 848170808Sdelphij rk_socket_t fd; 849170808Sdelphij 850170808Sdelphij fd = socket(a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol); 851170808Sdelphij if (rk_IS_BAD_SOCKET(fd)) 852170808Sdelphij continue; 853218949Salc rk_cloexec(fd); 854170808Sdelphij 855170808Sdelphij#ifndef NO_LIMIT_FD_SETSIZE 856170808Sdelphij if (fd >= FD_SETSIZE) { 857170808Sdelphij _krb5_debug(context, 0, "fd too large for select"); 858170808Sdelphij rk_closesocket(fd); 859170808Sdelphij continue; 860170808Sdelphij } 861170808Sdelphij#endif 862170808Sdelphij socket_set_nonblocking(fd, 1); 863171069Sdelphij 864170808Sdelphij host = heim_alloc(sizeof(*host), "sendto-host", deallocate_host); 865170808Sdelphij if (host == NULL) { 866170808Sdelphij if (freeai) 867170808Sdelphij freeaddrinfo(ai); 868170808Sdelphij rk_closesocket(fd); 869170808Sdelphij return ENOMEM; 870170808Sdelphij } 871170808Sdelphij host->hi = hi; 872170808Sdelphij host->fd = fd; 873170808Sdelphij host->ai = a; 874176559Sattilio /* next version of stid */ 875170808Sdelphij host->tid = ctx->stid = (ctx->stid & 0xffff0000) | ((ctx->stid & 0xffff) + 1); 876170808Sdelphij 877170808Sdelphij host->state = CONNECT; 878170808Sdelphij 879170808Sdelphij switch (host->hi->proto) { 880170808Sdelphij case KRB5_KRBHST_HTTP : 881170808Sdelphij host->fun = &http_fun; 882170808Sdelphij break; 883170808Sdelphij case KRB5_KRBHST_TCP : 884170808Sdelphij host->fun = &tcp_fun; 885170808Sdelphij break; 886170808Sdelphij case KRB5_KRBHST_UDP : 887170808Sdelphij host->fun = &udp_fun; 888170808Sdelphij break; 889170808Sdelphij default: 890170808Sdelphij heim_abort("undefined http transport protocol: %d", (int)host->hi->proto); 891170808Sdelphij } 892170808Sdelphij 893170808Sdelphij host->tries = host->fun->ntries; 894170808Sdelphij 895170808Sdelphij /* 896170808Sdelphij * Connect directly next host, wait a host_timeout for each next address. 897170808Sdelphij * We try host_connect() here, checking the return code because as we do 898170808Sdelphij * non-blocking connects, any error here indicates that the address is just 899170808Sdelphij * offline. That is, it's something like "No route to host" which is not 900170808Sdelphij * worth retrying. And so, we fail directly and immediately to the next 901170808Sdelphij * address for this host without enqueueing the address for retries. 902170808Sdelphij */ 903170808Sdelphij if (submitted_host == 0) { 904170808Sdelphij host_connect(context, ctx, host); 905170808Sdelphij if (host->state == DEAD) 906170808Sdelphij continue; 907170808Sdelphij } else { 908170808Sdelphij debug_host(context, 5, host, 909170808Sdelphij "Queuing host in future (in %ds), its the %lu address on the same name", 910170808Sdelphij (int)(context->host_timeout * submitted_host), submitted_host + 1); 911170808Sdelphij host->timeout = time(NULL) + (submitted_host * context->host_timeout); 912170808Sdelphij } 913170808Sdelphij 914170808Sdelphij heim_array_append_value(ctx->hosts, host); 915211598Sed heim_release(host); 916211598Sed submitted_host++; 917170808Sdelphij } 918170808Sdelphij 919170808Sdelphij if (freeai) 920170808Sdelphij freeaddrinfo(ai); 921170808Sdelphij 922170808Sdelphij if (submitted_host == 0) 923170808Sdelphij return KRB5_KDC_UNREACH; 924171070Sdelphij 925170808Sdelphij return 0; 926170808Sdelphij} 927170808Sdelphij 928170808Sdelphijstruct wait_ctx { 929170808Sdelphij krb5_context context; 930170808Sdelphij krb5_sendto_ctx ctx; 931233851Sgleb fd_set rfds; 932233851Sgleb fd_set wfds; 933233851Sgleb rk_socket_t max_fd; 934233851Sgleb int got_reply; 935233851Sgleb time_t timenow; 936233851Sgleb}; 937233851Sgleb 938233851Sglebstatic void 939171069Sdelphijwait_setup(heim_object_t obj, void *iter_ctx, int *stop) 940233851Sgleb{ 941233851Sgleb struct wait_ctx *wait_ctx = iter_ctx; 942233851Sgleb struct host *h = (struct host *)obj; 943233851Sgleb 944233851Sgleb if (h->state == CONNECT) { 945233851Sgleb if (h->timeout >= wait_ctx->timenow) 946233851Sgleb return; 947233851Sgleb host_connect(wait_ctx->context, wait_ctx->ctx, h); 948233851Sgleb } 949233851Sgleb 950233851Sgleb /* skip dead hosts */ 951233851Sgleb if (h->state == DEAD) 952233851Sgleb return; 953233851Sgleb 954233851Sgleb /* if host timed out, dec tries and (retry or kill host) */ 955233851Sgleb if (h->timeout < wait_ctx->timenow) { 956233851Sgleb heim_assert(h->tries != 0, "tries should not reach 0"); 957233851Sgleb h->tries--; 958233851Sgleb if (h->tries == 0) { 959233851Sgleb host_dead(wait_ctx->context, h, "host timed out"); 960233851Sgleb return; 961233851Sgleb } else { 962233851Sgleb debug_host(wait_ctx->context, 5, h, "retrying sending to"); 963233851Sgleb host_next_timeout(wait_ctx->context, h); 964233851Sgleb host_connected(wait_ctx->context, wait_ctx->ctx, h); 965233851Sgleb } 966233851Sgleb } 967233851Sgleb 968233851Sgleb#ifndef NO_LIMIT_FD_SETSIZE 969233851Sgleb heim_assert(h->fd < FD_SETSIZE, "fd too large"); 970233851Sgleb#endif 971233851Sgleb switch (h->state) { 972233851Sgleb case WAITING_REPLY: 973233851Sgleb FD_SET(h->fd, &wait_ctx->rfds); 974233851Sgleb break; 975233851Sgleb case CONNECTING: 976233851Sgleb case CONNECTED: 977233851Sgleb FD_SET(h->fd, &wait_ctx->rfds); 978233851Sgleb FD_SET(h->fd, &wait_ctx->wfds); 979233851Sgleb break; 980233851Sgleb default: 981233851Sgleb debug_host(wait_ctx->context, 5, h, "invalid sendto host state"); 982233851Sgleb heim_abort("invalid sendto host state"); 983233851Sgleb } 984233851Sgleb if (h->fd > wait_ctx->max_fd || wait_ctx->max_fd == rk_INVALID_SOCKET) 985233851Sgleb wait_ctx->max_fd = h->fd; 986233851Sgleb} 987233851Sgleb 988233851Sglebstatic int 989233851Sglebwait_filter_dead(heim_object_t obj, void *ctx) 990233851Sgleb{ 991233851Sgleb struct host *h = (struct host *)obj; 992233851Sgleb return (int)((h->state == DEAD) ? true : false); 993233851Sgleb} 994233851Sgleb 995233851Sglebstatic void 996233851Sglebwait_accelerate(heim_object_t obj, void *ctx, int *stop) 997233851Sgleb{ 998233851Sgleb struct host *h = (struct host *)obj; 999233851Sgleb 1000233851Sgleb if (h->state == CONNECT && h->timeout > 0) 1001233851Sgleb h->timeout--; 1002233851Sgleb} 1003233851Sgleb 1004233851Sglebstatic void 1005233851Sglebwait_process(heim_object_t obj, void *ctx, int *stop) 1006233851Sgleb{ 1007233851Sgleb struct wait_ctx *wait_ctx = ctx; 1008233851Sgleb struct host *h = (struct host *)obj; 1009233851Sgleb int readable, writeable; 1010233851Sgleb heim_assert(h->state != DEAD, "dead host resurected"); 1011233851Sgleb 1012233851Sgleb#ifndef NO_LIMIT_FD_SETSIZE 1013233851Sgleb heim_assert(h->fd < FD_SETSIZE, "fd too large"); 1014233851Sgleb#endif 1015233851Sgleb readable = FD_ISSET(h->fd, &wait_ctx->rfds); 1016233851Sgleb writeable = FD_ISSET(h->fd, &wait_ctx->wfds); 1017233851Sgleb 1018233851Sgleb if (readable || writeable || h->state == CONNECT) 1019233851Sgleb wait_ctx->got_reply |= eval_host_state(wait_ctx->context, wait_ctx->ctx, h, readable, writeable); 1020233851Sgleb 1021233851Sgleb /* if there is already a reply, just fall though the array */ 1022233851Sgleb if (wait_ctx->got_reply) 1023233851Sgleb *stop = 1; 1024233851Sgleb} 1025233851Sgleb 1026233851Sglebstatic krb5_error_code 1027233851Sglebwait_response(krb5_context context, int *action, krb5_sendto_ctx ctx) 1028233851Sgleb{ 1029233851Sgleb struct wait_ctx wait_ctx; 1030233851Sgleb struct timeval tv; 1031233851Sgleb int ret; 1032233851Sgleb 1033233851Sgleb wait_ctx.context = context; 1034233851Sgleb wait_ctx.ctx = ctx; 1035233851Sgleb FD_ZERO(&wait_ctx.rfds); 1036233851Sgleb FD_ZERO(&wait_ctx.wfds); 1037233851Sgleb wait_ctx.max_fd = rk_INVALID_SOCKET; 1038233851Sgleb 1039233851Sgleb /* oh, we have a reply, it must be a plugin that got it for us */ 1040233851Sgleb if (ctx->response.length) { 1041233851Sgleb *action = KRB5_SENDTO_FILTER; 1042233851Sgleb return 0; 1043233851Sgleb } 1044233851Sgleb 1045233851Sgleb wait_ctx.timenow = time(NULL); 1046233851Sgleb 1047233851Sgleb heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_setup); 1048233851Sgleb heim_array_filter_f(ctx->hosts, &wait_ctx, wait_filter_dead); 1049233851Sgleb 1050233851Sgleb if (heim_array_get_length(ctx->hosts) == 0) { 1051233851Sgleb if (ctx->stateflags & KRBHST_COMPLETED) { 1052233851Sgleb _krb5_debug(context, 5, "no more hosts to send/recv packets to/from " 1053233851Sgleb "trying to pulling more hosts"); 1054233851Sgleb *action = KRB5_SENDTO_FAILED; 1055233851Sgleb } else { 1056233851Sgleb _krb5_debug(context, 5, "no more hosts to send/recv packets to/from " 1057233851Sgleb "and no more hosts -> failure"); 1058233851Sgleb *action = KRB5_SENDTO_TIMEOUT; 1059233851Sgleb } 1060233851Sgleb return 0; 1061233851Sgleb } 1062233851Sgleb 1063233851Sgleb if (wait_ctx.max_fd == rk_INVALID_SOCKET) { 1064233851Sgleb /* 1065170808Sdelphij * If we don't find a host which can make progress, then 1066170808Sdelphij * we accelerate the process by moving all of the contestants 1067170808Sdelphij * up by 1s. 1068170808Sdelphij */ 1069170808Sdelphij _krb5_debug(context, 5, "wait_response: moving the contestants forward"); 1070170808Sdelphij heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_accelerate); 1071170808Sdelphij return 0; 1072170808Sdelphij } 1073233851Sgleb 1074170808Sdelphij tv.tv_sec = 1; 1075170808Sdelphij tv.tv_usec = 0; 1076170808Sdelphij 1077170808Sdelphij ret = select(wait_ctx.max_fd + 1, &wait_ctx.rfds, &wait_ctx.wfds, NULL, &tv); 1078197953Sdelphij if (ret < 0) 1079170808Sdelphij return errno; 1080170808Sdelphij if (ret == 0) { 1081171799Sdelphij *action = KRB5_SENDTO_TIMEOUT; 1082170808Sdelphij return 0; 1083170808Sdelphij } 1084176559Sattilio 1085176559Sattilio wait_ctx.got_reply = 0; 1086170808Sdelphij heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_process); 1087170808Sdelphij if (wait_ctx.got_reply) 1088170808Sdelphij *action = KRB5_SENDTO_FILTER; 1089170808Sdelphij else 1090170808Sdelphij *action = KRB5_SENDTO_CONTINUE; 1091170808Sdelphij 1092170808Sdelphij return 0; 1093170808Sdelphij} 1094170808Sdelphij 1095170808Sdelphijstatic void 1096170808Sdelphijreset_context(krb5_context context, krb5_sendto_ctx ctx) 1097170808Sdelphij{ 1098170808Sdelphij krb5_data_free(&ctx->response); 1099170808Sdelphij heim_release(ctx->hosts); 1100170808Sdelphij ctx->hosts = heim_array_create(); 1101170808Sdelphij ctx->stateflags = 0; 1102170808Sdelphij} 1103173725Sdelphij 1104173725Sdelphij 1105233851Sgleb/* 1106233851Sgleb * 1107233851Sgleb */ 1108233851Sgleb 1109233851SglebKRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1110233851Sglebkrb5_sendto_context(krb5_context context, 1111233851Sgleb krb5_sendto_ctx ctx, 1112233851Sgleb const krb5_data *send_data, 1113233851Sgleb krb5_const_realm realm, 1114233851Sgleb krb5_data *receive) 1115233851Sgleb{ 1116233851Sgleb krb5_error_code ret = 0; 1117233851Sgleb krb5_krbhst_handle handle = NULL; 1118233851Sgleb struct timeval nrstart, nrstop, stop_time; 1119233851Sgleb int type, freectx = 0; 1120233851Sgleb int action; 1121233851Sgleb int numreset = 0; 1122233851Sgleb 1123233851Sgleb krb5_data_zero(receive); 1124233851Sgleb 1125233851Sgleb if (ctx == NULL) { 1126233851Sgleb ret = krb5_sendto_ctx_alloc(context, &ctx); 1127233851Sgleb if (ret) 1128233851Sgleb goto out; 1129233851Sgleb freectx = 1; 1130233851Sgleb } 1131233851Sgleb 1132233851Sgleb ctx->stid = (context->num_kdc_requests++) << 16; 1133233851Sgleb 1134233851Sgleb memset(&ctx->stats, 0, sizeof(ctx->stats)); 1135233851Sgleb gettimeofday(&ctx->stats.start_time, NULL); 1136173725Sdelphij 1137173725Sdelphij type = ctx->type; 1138188318Skib if (type == 0) { 1139173725Sdelphij if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc) 1140212305Sivoras type = KRB5_KRBHST_ADMIN; 1141212305Sivoras else 1142170808Sdelphij type = KRB5_KRBHST_KDC; 1143212305Sivoras } 1144212305Sivoras 1145212305Sivoras ctx->send_data = send_data; 1146212305Sivoras 1147212305Sivoras if ((int)send_data->length > context->large_msg_size) 1148173725Sdelphij ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG; 1149170808Sdelphij 1150170808Sdelphij /* loop until we get back a appropriate response */ 1151170808Sdelphij 1152171070Sdelphij action = KRB5_SENDTO_INITIAL; 1153170808Sdelphij 1154171070Sdelphij while (action != KRB5_SENDTO_DONE && action != KRB5_SENDTO_FAILED) { 1155170808Sdelphij krb5_krbhst_info *hi; 1156170808Sdelphij 1157170808Sdelphij switch (action) { 1158171799Sdelphij case KRB5_SENDTO_INITIAL: 1159171070Sdelphij ret = realm_via_plugin(context, realm, context->kdc_timeout, 1160170808Sdelphij send_data, &ctx->response); 1161170808Sdelphij if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) { 1162170808Sdelphij action = KRB5_SENDTO_DONE; 1163173725Sdelphij break; 1164170808Sdelphij } 1165170808Sdelphij action = KRB5_SENDTO_KRBHST; 1166171799Sdelphij /* FALLTHROUGH */ 1167171799Sdelphij case KRB5_SENDTO_KRBHST: 1168171799Sdelphij if (ctx->krbhst == NULL) { 1169173725Sdelphij ret = krb5_krbhst_init_flags(context, realm, type, 1170171799Sdelphij ctx->flags, &handle); 1171171799Sdelphij if (ret) 1172171799Sdelphij goto out; 1173173725Sdelphij 1174171799Sdelphij if (ctx->hostname) { 1175171799Sdelphij ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname); 1176173725Sdelphij if (ret) 1177171799Sdelphij goto out; 1178171799Sdelphij } 1179171799Sdelphij 1180170808Sdelphij } else { 1181170808Sdelphij handle = heim_retain(ctx->krbhst); 1182170808Sdelphij } 1183170808Sdelphij action = KRB5_SENDTO_TIMEOUT; 1184170808Sdelphij /* FALLTHROUGH */ 1185170808Sdelphij case KRB5_SENDTO_TIMEOUT: 1186170808Sdelphij 1187170808Sdelphij /* 1188170808Sdelphij * If we completed, just got to next step 1189170808Sdelphij */ 1190170808Sdelphij 1191170808Sdelphij if (ctx->stateflags & KRBHST_COMPLETED) { 1192183299Sobrien action = KRB5_SENDTO_CONTINUE; 1193171087Sdelphij break; 1194170808Sdelphij } 1195170808Sdelphij 1196170808Sdelphij /* 1197170808Sdelphij * Pull out next host, if there is no more, close the 1198170808Sdelphij * handle and mark as completed. 1199170808Sdelphij * 1200170808Sdelphij * Collect time spent in krbhst (dns, plugin, etc) 1201170808Sdelphij */ 1202170808Sdelphij 1203170808Sdelphij 1204170808Sdelphij gettimeofday(&nrstart, NULL); 1205170808Sdelphij 1206170808Sdelphij ret = krb5_krbhst_next(context, handle, &hi); 1207170808Sdelphij 1208170808Sdelphij gettimeofday(&nrstop, NULL); 1209197953Sdelphij timevalsub(&nrstop, &nrstart); 1210197953Sdelphij timevaladd(&ctx->stats.krbhst, &nrstop); 1211197953Sdelphij 1212197953Sdelphij action = KRB5_SENDTO_CONTINUE; 1213197953Sdelphij if (ret == 0) { 1214170808Sdelphij _krb5_debug(context, 5, "submitting new requests to new host"); 1215197953Sdelphij if (submit_request(context, ctx, hi) != 0) 1216197953Sdelphij action = KRB5_SENDTO_TIMEOUT; 1217170808Sdelphij } else { 1218197953Sdelphij _krb5_debug(context, 5, "out of hosts, waiting for replies"); 1219197953Sdelphij ctx->stateflags |= KRBHST_COMPLETED; 1220170808Sdelphij } 1221170808Sdelphij 1222171087Sdelphij break; 1223170808Sdelphij case KRB5_SENDTO_CONTINUE: 1224170808Sdelphij 1225197953Sdelphij ret = wait_response(context, &action, ctx); 1226197953Sdelphij if (ret) 1227197953Sdelphij goto out; 1228197953Sdelphij 1229197953Sdelphij break; 1230197953Sdelphij case KRB5_SENDTO_RESET: 1231197953Sdelphij /* start over */ 1232197953Sdelphij _krb5_debug(context, 5, 1233197953Sdelphij "krb5_sendto trying over again (reset): %d", 1234197953Sdelphij numreset); 1235197953Sdelphij reset_context(context, ctx); 1236197953Sdelphij if (handle) { 1237197953Sdelphij krb5_krbhst_free(context, handle); 1238170808Sdelphij handle = NULL; 1239197953Sdelphij } 1240197953Sdelphij numreset++; 1241197953Sdelphij if (numreset >= 3) 1242197953Sdelphij action = KRB5_SENDTO_FAILED; 1243197953Sdelphij else 1244197953Sdelphij action = KRB5_SENDTO_KRBHST; 1245197953Sdelphij 1246197953Sdelphij break; 1247170808Sdelphij case KRB5_SENDTO_FILTER: 1248170808Sdelphij /* default to next state, the filter function might modify this */ 1249170808Sdelphij action = KRB5_SENDTO_DONE; 1250197953Sdelphij 1251170808Sdelphij if (ctx->func) { 1252197953Sdelphij ret = (*ctx->func)(context, ctx, ctx->data, 1253170808Sdelphij &ctx->response, &action); 1254170808Sdelphij if (ret) 1255170808Sdelphij goto out; 1256170808Sdelphij } 1257197953Sdelphij break; 1258197953Sdelphij case KRB5_SENDTO_FAILED: 1259197953Sdelphij ret = KRB5_KDC_UNREACH; 1260197953Sdelphij break; 1261197953Sdelphij case KRB5_SENDTO_DONE: 1262197953Sdelphij ret = 0; 1263197953Sdelphij break; 1264170808Sdelphij default: 1265197953Sdelphij heim_abort("invalid krb5_sendto_context state"); 1266170808Sdelphij } 1267170808Sdelphij } 1268170808Sdelphij 1269170808Sdelphijout: 1270170808Sdelphij gettimeofday(&stop_time, NULL); 1271211598Sed timevalsub(&stop_time, &ctx->stats.start_time); 1272211598Sed if (ret == 0 && ctx->response.length) { 1273211598Sed *receive = ctx->response; 1274211598Sed krb5_data_zero(&ctx->response); 1275170808Sdelphij } else { 1276170808Sdelphij krb5_data_free(&ctx->response); 1277170808Sdelphij krb5_clear_error_message (context); 1278170808Sdelphij ret = KRB5_KDC_UNREACH; 1279170808Sdelphij krb5_set_error_message(context, ret, 1280170808Sdelphij N_("unable to reach any KDC in realm %s", ""), 1281170808Sdelphij realm); 1282170808Sdelphij } 1283171087Sdelphij 1284170808Sdelphij _krb5_debug(context, 1, 1285170808Sdelphij "%s %s done: %d hosts %lu packets %lu:" 1286170808Sdelphij " wc: %jd.%06lu nr: %jd.%06lu kh: %jd.%06lu tid: %08x", 1287170808Sdelphij __func__, realm, ret, 1288170808Sdelphij ctx->stats.num_hosts, ctx->stats.sent_packets, 1289170808Sdelphij (intmax_t)stop_time.tv_sec, 1290170808Sdelphij (unsigned long)stop_time.tv_usec, 1291170808Sdelphij (intmax_t)ctx->stats.name_resolution.tv_sec, 1292170808Sdelphij (unsigned long)ctx->stats.name_resolution.tv_usec, 1293170808Sdelphij (intmax_t)ctx->stats.krbhst.tv_sec, 1294170808Sdelphij (unsigned long)ctx->stats.krbhst.tv_usec, ctx->stid); 1295170808Sdelphij 1296188318Skib if (freectx) 1297170808Sdelphij krb5_sendto_ctx_free(context, ctx); 1298170808Sdelphij else 1299170808Sdelphij reset_context(context, ctx); 1300170808Sdelphij 1301170808Sdelphij if (handle) 1302170808Sdelphij krb5_krbhst_free(context, handle); 1303170808Sdelphij 1304229130Spho return ret; 1305233385Sjhb} 1306233385Sjhb