Home | History | Annotate | Line # | Download | only in krb5
      1 /*	$NetBSD: send_to_kdc.c,v 1.9 2023/06/19 21:41:44 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1997 - 2002 Kungliga Tekniska Hgskolan
      5  * (Royal Institute of Technology, Stockholm, Sweden).
      6  * All rights reserved.
      7  *
      8  * Portions Copyright (c) 2010 - 2013 Apple Inc. All rights reserved.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  *
     14  * 1. Redistributions of source code must retain the above copyright
     15  *    notice, this list of conditions and the following disclaimer.
     16  *
     17  * 2. Redistributions in binary form must reproduce the above copyright
     18  *    notice, this list of conditions and the following disclaimer in the
     19  *    documentation and/or other materials provided with the distribution.
     20  *
     21  * 3. Neither the name of the Institute nor the names of its contributors
     22  *    may be used to endorse or promote products derived from this software
     23  *    without specific prior written permission.
     24  *
     25  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
     26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
     29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     35  * SUCH DAMAGE.
     36  */
     37 
     38 #include "krb5_locl.h"
     39 #include "send_to_kdc_plugin.h"
     40 
     41 /**
     42  * @section send_to_kdc Locating and sending packets to the KDC
     43  *
     44  * The send to kdc code is responsible to request the list of KDC from
     45  * the locate-kdc subsystem and then send requests to each of them.
     46  *
     47  * - Each second a new hostname is tried.
     48  * - If the hostname have several addresses, the first will be tried
     49  *   directly then in turn the other will be tried every 3 seconds
     50  *   (host_timeout).
     51  * - UDP requests are tried 3 times, and it tried with a individual timeout of kdc_timeout / 3.
     52  * - TCP and HTTP requests are tried 1 time.
     53  *
     54  *  Total wait time shorter then (number of addresses * 3) + kdc_timeout seconds.
     55  *
     56  */
     57 
     58 static int
     59 init_port(const char *s, int fallback)
     60 {
     61     int tmp;
     62 
     63     if (s && sscanf(s, "%d", &tmp) == 1)
     64         return htons(tmp);
     65     return fallback;
     66 }
     67 
     68 struct send_via_plugin_s {
     69     krb5_const_realm realm;
     70     krb5_krbhst_info *hi;
     71     time_t timeout;
     72     const krb5_data *send_data;
     73     krb5_data *receive;
     74 };
     75 
     76 
     77 static krb5_error_code KRB5_LIB_CALL
     78 kdccallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
     79 {
     80     const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
     81     struct send_via_plugin_s *ctx = userctx;
     82 
     83     if (service->send_to_kdc == NULL)
     84 	return KRB5_PLUGIN_NO_HANDLE;
     85     return service->send_to_kdc(context, plugctx, ctx->hi, ctx->timeout,
     86 				ctx->send_data, ctx->receive);
     87 }
     88 
     89 static krb5_error_code KRB5_LIB_CALL
     90 realmcallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
     91 {
     92     const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
     93     struct send_via_plugin_s *ctx = userctx;
     94 
     95     if (service->send_to_realm == NULL)
     96 	return KRB5_PLUGIN_NO_HANDLE;
     97     return service->send_to_realm(context, plugctx, ctx->realm, ctx->timeout,
     98 				  ctx->send_data, ctx->receive);
     99 }
    100 
    101 static krb5_error_code
    102 kdc_via_plugin(krb5_context context,
    103 	       krb5_krbhst_info *hi,
    104 	       time_t timeout,
    105 	       const krb5_data *send_data,
    106 	       krb5_data *receive)
    107 {
    108     struct send_via_plugin_s userctx;
    109 
    110     userctx.realm = NULL;
    111     userctx.hi = hi;
    112     userctx.timeout = timeout;
    113     userctx.send_data = send_data;
    114     userctx.receive = receive;
    115 
    116     return _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC,
    117 			      KRB5_PLUGIN_SEND_TO_KDC_VERSION_0, 0,
    118 			      &userctx, kdccallback);
    119 }
    120 
    121 static krb5_error_code
    122 realm_via_plugin(krb5_context context,
    123 		 krb5_const_realm realm,
    124 		 time_t timeout,
    125 		 const krb5_data *send_data,
    126 		 krb5_data *receive)
    127 {
    128     struct send_via_plugin_s userctx;
    129 
    130     userctx.realm = realm;
    131     userctx.hi = NULL;
    132     userctx.timeout = timeout;
    133     userctx.send_data = send_data;
    134     userctx.receive = receive;
    135 
    136     return _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC,
    137 			      KRB5_PLUGIN_SEND_TO_KDC_VERSION_2, 0,
    138 			      &userctx, realmcallback);
    139 }
    140 
    141 struct krb5_sendto_ctx_data {
    142     int flags;
    143     int type;
    144     krb5_sendto_ctx_func func;
    145     void *data;
    146     char *hostname;
    147     krb5_krbhst_handle krbhst;
    148 
    149     /* context2 */
    150     const krb5_data *send_data;
    151     krb5_data response;
    152     heim_array_t hosts;
    153     int stateflags;
    154 #define KRBHST_COMPLETED	1
    155 
    156     /* prexmit */
    157     krb5_sendto_prexmit prexmit_func;
    158     void *prexmit_ctx;
    159 
    160     /* stats */
    161     struct {
    162 	struct timeval start_time;
    163 	struct timeval name_resolution;
    164 	struct timeval krbhst;
    165 	unsigned long sent_packets;
    166 	unsigned long num_hosts;
    167     } stats;
    168     unsigned int stid;
    169 };
    170 
    171 static void
    172 dealloc_sendto_ctx(void *ptr)
    173 {
    174     krb5_sendto_ctx ctx = (krb5_sendto_ctx)ptr;
    175     if (ctx->hostname)
    176 	free(ctx->hostname);
    177     heim_release(ctx->hosts);
    178     heim_release(ctx->krbhst);
    179 }
    180 
    181 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    182 krb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx)
    183 {
    184     *ctx = heim_alloc(sizeof(**ctx), "sendto-context", dealloc_sendto_ctx);
    185     if (*ctx == NULL)
    186 	return krb5_enomem(context);
    187     (*ctx)->hosts = heim_array_create();
    188 
    189     return 0;
    190 }
    191 
    192 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
    193 krb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx, int flags)
    194 {
    195     ctx->flags |= flags;
    196 }
    197 
    198 KRB5_LIB_FUNCTION int KRB5_LIB_CALL
    199 krb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx)
    200 {
    201     return ctx->flags;
    202 }
    203 
    204 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
    205 krb5_sendto_ctx_set_type(krb5_sendto_ctx ctx, int type)
    206 {
    207     ctx->type = type;
    208 }
    209 
    210 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
    211 krb5_sendto_ctx_set_func(krb5_sendto_ctx ctx,
    212 			 krb5_sendto_ctx_func func,
    213 			 void *data)
    214 {
    215     ctx->func = func;
    216     ctx->data = data;
    217 }
    218 
    219 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
    220 _krb5_sendto_ctx_set_prexmit(krb5_sendto_ctx ctx,
    221 			     krb5_sendto_prexmit prexmit,
    222 			     void *data)
    223 {
    224     ctx->prexmit_func = prexmit;
    225     ctx->prexmit_ctx = data;
    226 }
    227 
    228 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    229 krb5_sendto_set_hostname(krb5_context context,
    230 			 krb5_sendto_ctx ctx,
    231 			 const char *hostname)
    232 {
    233     if (ctx->hostname == NULL)
    234 	free(ctx->hostname);
    235     ctx->hostname = strdup(hostname);
    236     if (ctx->hostname == NULL) {
    237 	krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
    238 	return ENOMEM;
    239     }
    240     return 0;
    241 }
    242 
    243 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
    244 _krb5_sendto_ctx_set_krb5hst(krb5_context context,
    245 			     krb5_sendto_ctx ctx,
    246 			     krb5_krbhst_handle handle)
    247 {
    248     heim_release(ctx->krbhst);
    249     ctx->krbhst = heim_retain(handle);
    250 }
    251 
    252 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
    253 krb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx)
    254 {
    255     heim_release(ctx);
    256 }
    257 
    258 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    259 _krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data,
    260 		const krb5_data *reply, int *action)
    261 {
    262     krb5_error_code ret;
    263     KRB_ERROR error;
    264 
    265     if(krb5_rd_error(context, reply, &error))
    266 	return 0;
    267 
    268     ret = krb5_error_from_rd_error(context, &error, NULL);
    269     krb5_free_error_contents(context, &error);
    270 
    271     switch(ret) {
    272     case KRB5KRB_ERR_RESPONSE_TOO_BIG: {
    273 	if (krb5_sendto_ctx_get_flags(ctx) & KRB5_KRBHST_FLAGS_LARGE_MSG)
    274 	    break;
    275 	krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG);
    276 	*action = KRB5_SENDTO_RESET;
    277 	break;
    278     }
    279     case KRB5KDC_ERR_SVC_UNAVAILABLE:
    280 	*action = KRB5_SENDTO_CONTINUE;
    281 	break;
    282     }
    283     return 0;
    284 }
    285 
    286 /*
    287  *
    288  */
    289 
    290 struct host;
    291 
    292 struct host_fun {
    293     krb5_error_code (*prepare)(krb5_context, struct host *, const krb5_data *);
    294     krb5_error_code (*send_fn)(krb5_context, struct host *);
    295     krb5_error_code (*recv_fn)(krb5_context, struct host *, krb5_data *);
    296     int ntries;
    297 };
    298 
    299 struct host {
    300     enum host_state { CONNECT, CONNECTING, CONNECTED, WAITING_REPLY, DEAD } state;
    301     krb5_krbhst_info *hi;
    302     struct addrinfo *ai;
    303     rk_socket_t fd;
    304     struct host_fun *fun;
    305     unsigned int tries;
    306     time_t timeout;
    307     krb5_data data;
    308     unsigned int tid;
    309 };
    310 
    311 static void
    312 debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
    313 	__attribute__ ((__format__ (__printf__, 4, 5)));
    314 
    315 static void
    316 debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
    317 {
    318     const char *proto = "unknown";
    319     const char *state;
    320     char name[NI_MAXHOST], port[NI_MAXSERV];
    321     char *text = NULL;
    322     va_list ap;
    323     int ret;
    324 
    325     if (!_krb5_have_debug(context, 5))
    326 	return;
    327 
    328     va_start(ap, fmt);
    329     ret = vasprintf(&text, fmt, ap);
    330     va_end(ap);
    331     if (ret == -1 || text == NULL)
    332 	return;
    333 
    334     if (host->hi->proto == KRB5_KRBHST_HTTP)
    335 	proto = "http";
    336     else if (host->hi->proto == KRB5_KRBHST_TCP)
    337 	proto = "tcp";
    338     else if (host->hi->proto == KRB5_KRBHST_UDP)
    339 	proto = "udp";
    340 
    341     if (getnameinfo(host->ai->ai_addr, host->ai->ai_addrlen,
    342 		    name, sizeof(name), port, sizeof(port), NI_NUMERICHOST) != 0)
    343 	name[0] = '\0';
    344 
    345     switch (host->state) {
    346     case CONNECT:	state = "CONNECT";		break;
    347     case CONNECTING:	state = "CONNECTING";		break;
    348     case CONNECTED:	state = "CONNECTED";		break;
    349     case WAITING_REPLY:	state = "WAITING_REPLY";	break;
    350     case DEAD:		state = "DEAD";			break;
    351     default:		state = "unknown";		break;
    352     }
    353 
    354     _krb5_debug(context, level, "%s: %s %s:%s (%s) state=%s tid: %08x", text,
    355 		proto, name, port, host->hi->hostname, state, host->tid);
    356     free(text);
    357 }
    358 
    359 
    360 static void
    361 deallocate_host(void *ptr)
    362 {
    363     struct host *host = ptr;
    364     if (!rk_IS_BAD_SOCKET(host->fd))
    365 	rk_closesocket(host->fd);
    366     krb5_data_free(&host->data);
    367     host->ai = NULL;
    368 }
    369 
    370 static void
    371 host_dead(krb5_context context, struct host *host, const char *msg)
    372 {
    373     debug_host(context, 5, host, "%s", msg);
    374     rk_closesocket(host->fd);
    375     host->fd = rk_INVALID_SOCKET;
    376     host->state = DEAD;
    377 }
    378 
    379 static krb5_error_code
    380 send_stream(krb5_context context, struct host *host)
    381 {
    382     ssize_t len;
    383 
    384     len = krb5_net_write(context, &host->fd, host->data.data, host->data.length);
    385 
    386     if (len < 0)
    387 	return errno;
    388     else if (len < host->data.length) {
    389 	host->data.length -= len;
    390 	memmove(host->data.data, ((uint8_t *)host->data.data) + len, host->data.length - len);
    391 	return -1;
    392     } else {
    393 	krb5_data_free(&host->data);
    394 	return 0;
    395     }
    396 }
    397 
    398 static krb5_error_code
    399 recv_stream(krb5_context context, struct host *host)
    400 {
    401     krb5_error_code ret;
    402     size_t oldlen;
    403     ssize_t sret;
    404     int nbytes;
    405 
    406     if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
    407 	return HEIM_NET_CONN_REFUSED;
    408 
    409     if (context->max_msg_size - host->data.length < nbytes) {
    410 	krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
    411 			       N_("TCP message from KDC too large %d", ""),
    412 			       (int)(host->data.length + nbytes));
    413 	return KRB5KRB_ERR_FIELD_TOOLONG;
    414     }
    415 
    416     oldlen = host->data.length;
    417 
    418     ret = krb5_data_realloc(&host->data, oldlen + nbytes + 1 /* NUL */);
    419     if (ret)
    420 	return ret;
    421 
    422     sret = krb5_net_read(context, &host->fd, ((uint8_t *)host->data.data) + oldlen, nbytes);
    423     if (sret <= 0) {
    424 	ret = errno;
    425 	return ret;
    426     }
    427     host->data.length = oldlen + sret;
    428     /* zero terminate for http transport */
    429     ((uint8_t *)host->data.data)[host->data.length] = '\0';
    430 
    431     return 0;
    432 }
    433 
    434 /*
    435  *
    436  */
    437 
    438 static void
    439 host_next_timeout(krb5_context context, struct host *host)
    440 {
    441     host->timeout = context->kdc_timeout / host->fun->ntries;
    442     if (host->timeout == 0)
    443 	host->timeout = 1;
    444 
    445     host->timeout += time(NULL);
    446 }
    447 
    448 /*
    449  * connected host
    450  */
    451 
    452 static void
    453 host_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
    454 {
    455     krb5_error_code ret;
    456 
    457     host->state = CONNECTED;
    458     /*
    459      * Now prepare data to send to host
    460      */
    461     if (ctx->prexmit_func) {
    462 	krb5_data data;
    463 
    464 	krb5_data_zero(&data);
    465 
    466 	ret = ctx->prexmit_func(context, host->hi->proto,
    467 				ctx->prexmit_ctx, host->fd, &data);
    468 	if (ret == 0) {
    469 	    if (data.length == 0) {
    470 		host_dead(context, host, "prexmit function didn't send data");
    471 		return;
    472 	    }
    473 	    ret = host->fun->prepare(context, host, &data);
    474 	    krb5_data_free(&data);
    475 	}
    476 
    477     } else {
    478 	ret = host->fun->prepare(context, host, ctx->send_data);
    479     }
    480     if (ret)
    481 	debug_host(context, 5, host, "failed to prexmit/prepare");
    482 }
    483 
    484 /*
    485  * connect host
    486  */
    487 
    488 static void
    489 host_connect(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
    490 {
    491     krb5_krbhst_info *hi = host->hi;
    492     struct addrinfo *ai = host->ai;
    493 
    494     debug_host(context, 5, host, "connecting to host");
    495 
    496     if (connect(host->fd, ai->ai_addr, ai->ai_addrlen) < 0) {
    497 #ifdef HAVE_WINSOCK
    498 	if (WSAGetLastError() == WSAEWOULDBLOCK)
    499 	    errno = EINPROGRESS;
    500 #endif /* HAVE_WINSOCK */
    501 	if (errno == EINPROGRESS && (hi->proto == KRB5_KRBHST_HTTP || hi->proto == KRB5_KRBHST_TCP)) {
    502 	    debug_host(context, 5, host, "connecting to %d", host->fd);
    503 	    host->state = CONNECTING;
    504 	} else {
    505 	    host_dead(context, host, "failed to connect");
    506 	}
    507     } else {
    508 	host_connected(context, ctx, host);
    509     }
    510 
    511     host_next_timeout(context, host);
    512 }
    513 
    514 /*
    515  * HTTP transport
    516  */
    517 
    518 static krb5_error_code
    519 prepare_http(krb5_context context, struct host *host, const krb5_data *data)
    520 {
    521     char *str = NULL, *request = NULL;
    522     krb5_error_code ret;
    523     int len;
    524 
    525     heim_assert(host->data.length == 0, "prepare_http called twice");
    526 
    527     len = rk_base64_encode(data->data, data->length, &str);
    528     if(len < 0)
    529 	return ENOMEM;
    530 
    531     if (context->http_proxy)
    532 	ret = asprintf(&request, "GET http://%s/%s HTTP/1.0\r\n\r\n", host->hi->hostname, str);
    533     else
    534 	ret = asprintf(&request, "GET /%s HTTP/1.0\r\n\r\n", str);
    535     free(str);
    536     if(ret < 0 || request == NULL)
    537 	return ENOMEM;
    538 
    539     host->data.data = request;
    540     host->data.length = strlen(request);
    541 
    542     return 0;
    543 }
    544 
    545 static krb5_error_code
    546 recv_http(krb5_context context, struct host *host, krb5_data *data)
    547 {
    548     krb5_error_code ret;
    549     unsigned long rep_len;
    550     size_t len;
    551     char *p;
    552 
    553     /*
    554      * recv_stream returns a NUL terminated stream
    555      */
    556 
    557     ret = recv_stream(context, host);
    558     if (ret)
    559 	return ret;
    560 
    561     p = strstr(host->data.data, "\r\n\r\n");
    562     if (p == NULL)
    563 	return -1;
    564     p += 4;
    565 
    566     len = host->data.length - (p - (char *)host->data.data);
    567     if (len < 4)
    568 	return -1;
    569 
    570     _krb5_get_int(p, &rep_len, 4);
    571     if (len < rep_len)
    572 	return -1;
    573 
    574     p += 4;
    575 
    576     memmove(host->data.data, p, rep_len);
    577     host->data.length = rep_len;
    578 
    579     *data = host->data;
    580     krb5_data_zero(&host->data);
    581 
    582     return 0;
    583 }
    584 
    585 /*
    586  * TCP transport
    587  */
    588 
    589 static krb5_error_code
    590 prepare_tcp(krb5_context context, struct host *host, const krb5_data *data)
    591 {
    592     krb5_error_code ret;
    593     krb5_storage *sp;
    594 
    595     heim_assert(host->data.length == 0, "prepare_tcp called twice");
    596 
    597     sp = krb5_storage_emem();
    598     if (sp == NULL)
    599 	return ENOMEM;
    600 
    601     ret = krb5_store_data(sp, *data);
    602     if (ret) {
    603 	krb5_storage_free(sp);
    604 	return ret;
    605     }
    606     ret = krb5_storage_to_data(sp, &host->data);
    607     krb5_storage_free(sp);
    608 
    609     return ret;
    610 }
    611 
    612 static krb5_error_code
    613 recv_tcp(krb5_context context, struct host *host, krb5_data *data)
    614 {
    615     krb5_error_code ret;
    616     unsigned long pktlen;
    617 
    618     ret = recv_stream(context, host);
    619     if (ret)
    620 	return ret;
    621 
    622     if (host->data.length < 4)
    623 	return -1;
    624 
    625     _krb5_get_int(host->data.data, &pktlen, 4);
    626 
    627     if (pktlen > host->data.length - 4)
    628 	return -1;
    629 
    630     memmove(host->data.data, ((uint8_t *)host->data.data) + 4, host->data.length - 4);
    631     host->data.length -= 4;
    632 
    633     *data = host->data;
    634     krb5_data_zero(&host->data);
    635 
    636     return 0;
    637 }
    638 
    639 /*
    640  * UDP transport
    641  */
    642 
    643 static krb5_error_code
    644 prepare_udp(krb5_context context, struct host *host, const krb5_data *data)
    645 {
    646     return krb5_data_copy(&host->data, data->data, data->length);
    647 }
    648 
    649 static krb5_error_code
    650 send_udp(krb5_context context, struct host *host)
    651 {
    652     if (send(host->fd, host->data.data, host->data.length, 0) < 0)
    653 	return errno;
    654     return 0;
    655 }
    656 
    657 static krb5_error_code
    658 recv_udp(krb5_context context, struct host *host, krb5_data *data)
    659 {
    660     krb5_error_code ret;
    661     int nbytes;
    662 
    663 
    664     if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
    665 	return HEIM_NET_CONN_REFUSED;
    666 
    667     if (context->max_msg_size < nbytes) {
    668 	krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
    669 			       N_("UDP message from KDC too large %d", ""),
    670 			       (int)nbytes);
    671 	return KRB5KRB_ERR_FIELD_TOOLONG;
    672     }
    673 
    674     ret = krb5_data_alloc(data, nbytes);
    675     if (ret)
    676 	return ret;
    677 
    678     ret = recv(host->fd, data->data, data->length, 0);
    679     if (ret < 0) {
    680 	ret = errno;
    681 	krb5_data_free(data);
    682 	return ret;
    683     }
    684     data->length = ret;
    685 
    686     return 0;
    687 }
    688 
    689 static struct host_fun http_fun = {
    690     prepare_http,
    691     send_stream,
    692     recv_http,
    693     1
    694 };
    695 static struct host_fun tcp_fun = {
    696     prepare_tcp,
    697     send_stream,
    698     recv_tcp,
    699     1
    700 };
    701 static struct host_fun udp_fun = {
    702     prepare_udp,
    703     send_udp,
    704     recv_udp,
    705     3
    706 };
    707 
    708 
    709 /*
    710  * Host state machine
    711  */
    712 
    713 static int
    714 eval_host_state(krb5_context context,
    715 		krb5_sendto_ctx ctx,
    716 		struct host *host,
    717 		int readable, int writeable)
    718 {
    719     krb5_error_code ret;
    720 
    721     if (host->state == CONNECT) {
    722 	/* check if its this host time to connect */
    723 	if (host->timeout < time(NULL))
    724 	    host_connect(context, ctx, host);
    725 	return 0;
    726     }
    727 
    728     if (host->state == CONNECTING && writeable)
    729 	host_connected(context, ctx, host);
    730 
    731     if (readable) {
    732 
    733 	debug_host(context, 5, host, "reading packet");
    734 
    735 	ret = host->fun->recv_fn(context, host, &ctx->response);
    736 	if (ret == -1) {
    737 	    /* not done yet */
    738 	} else if (ret == 0) {
    739 	    /* if recv_foo function returns 0, we have a complete reply */
    740 	    debug_host(context, 5, host, "host completed");
    741 	    return 1;
    742 	} else {
    743 	    host_dead(context, host, "host disconnected");
    744 	}
    745     }
    746 
    747     /* check if there is anything to send, state might DEAD after read */
    748     if (writeable && host->state == CONNECTED) {
    749 
    750 	ctx->stats.sent_packets++;
    751 
    752 	debug_host(context, 5, host, "writing packet");
    753 
    754 	ret = host->fun->send_fn(context, host);
    755 	if (ret == -1) {
    756 	    /* not done yet */
    757 	} else if (ret) {
    758 	    host_dead(context, host, "host dead, write failed");
    759 	} else
    760 	    host->state = WAITING_REPLY;
    761     }
    762 
    763     return 0;
    764 }
    765 
    766 /*
    767  *
    768  */
    769 
    770 static krb5_error_code
    771 submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi)
    772 {
    773     unsigned long submitted_host = 0;
    774     krb5_boolean freeai = FALSE;
    775     struct timeval nrstart, nrstop;
    776     krb5_error_code ret;
    777     struct addrinfo *ai = NULL, *a;
    778     struct host *host;
    779 
    780     ret = kdc_via_plugin(context, hi, context->kdc_timeout,
    781 			 ctx->send_data, &ctx->response);
    782     if (ret == 0) {
    783 	return 0;
    784     } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
    785 	_krb5_debug(context, 5, "send via plugin failed %s: %d",
    786 		    hi->hostname, ret);
    787 	return ret;
    788     }
    789 
    790     /*
    791      * If we have a proxy, let use the address of the proxy instead of
    792      * the KDC and let the proxy deal with the resolving of the KDC.
    793      */
    794 
    795     gettimeofday(&nrstart, NULL);
    796 
    797     if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) {
    798 	char *proxy2 = strdup(context->http_proxy);
    799 	char *el, *proxy  = proxy2;
    800 	struct addrinfo hints;
    801 	char portstr[NI_MAXSERV];
    802 	unsigned short nport;
    803 
    804 	if (proxy == NULL)
    805 	    return ENOMEM;
    806 	if (strncmp(proxy, "http://", 7) == 0)
    807 	    proxy += 7;
    808 
    809 	/* check for url terminating slash */
    810 	el = strchr(proxy, '/');
    811 	if (el != NULL)
    812 	    *el = '\0';
    813 
    814 	/* check for port in hostname, used below as port */
    815 	el = strchr(proxy, ':');
    816 	if(el != NULL)
    817 	    *el++ = '\0';
    818 
    819 	memset(&hints, 0, sizeof(hints));
    820 	hints.ai_family   = PF_UNSPEC;
    821 	hints.ai_socktype = SOCK_STREAM;
    822 
    823 	/* On some systems ntohs(foo(..., htons(...))) causes shadowing */
    824 	nport = init_port(el, htons(80));
    825 	snprintf(portstr, sizeof(portstr), "%d", ntohs(nport));
    826 
    827 	ret = getaddrinfo(proxy, portstr, &hints, &ai);
    828 	free(proxy2);
    829 	if (ret)
    830 	    return krb5_eai_to_heim_errno(ret, errno);
    831 
    832 	freeai = TRUE;
    833 
    834     } else {
    835 	ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
    836 	if (ret)
    837 	    return ret;
    838     }
    839 
    840     /* add up times */
    841     gettimeofday(&nrstop, NULL);
    842     timevalsub(&nrstop, &nrstart);
    843     timevaladd(&ctx->stats.name_resolution, &nrstop);
    844 
    845     ctx->stats.num_hosts++;
    846 
    847     for (a = ai; a != NULL; a = a->ai_next) {
    848 	rk_socket_t fd;
    849 
    850 	fd = socket(a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);
    851 	if (rk_IS_BAD_SOCKET(fd))
    852 	    continue;
    853 	rk_cloexec(fd);
    854 
    855 #ifndef NO_LIMIT_FD_SETSIZE
    856 	if (fd >= FD_SETSIZE) {
    857 	    _krb5_debug(context, 0, "fd too large for select");
    858 	    rk_closesocket(fd);
    859 	    continue;
    860 	}
    861 #endif
    862 	socket_set_nonblocking(fd, 1);
    863 
    864 	host = heim_alloc(sizeof(*host), "sendto-host", deallocate_host);
    865 	if (host == NULL) {
    866             if (freeai)
    867                 freeaddrinfo(ai);
    868 	    rk_closesocket(fd);
    869 	    return ENOMEM;
    870 	}
    871 	host->hi = hi;
    872 	host->fd = fd;
    873 	host->ai = a;
    874 	/* next version of stid */
    875 	host->tid = ctx->stid = (ctx->stid & 0xffff0000) | ((ctx->stid & 0xffff) + 1);
    876 
    877 	host->state = CONNECT;
    878 
    879 	switch (host->hi->proto) {
    880 	case KRB5_KRBHST_HTTP :
    881 	    host->fun = &http_fun;
    882 	    break;
    883 	case KRB5_KRBHST_TCP :
    884 	    host->fun = &tcp_fun;
    885 	    break;
    886 	case KRB5_KRBHST_UDP :
    887 	    host->fun = &udp_fun;
    888 	    break;
    889 	default:
    890 	    heim_abort("undefined http transport protocol: %d", (int)host->hi->proto);
    891 	}
    892 
    893 	host->tries = host->fun->ntries;
    894 
    895 	/*
    896 	 * Connect directly next host, wait a host_timeout for each next address.
    897 	 * We try host_connect() here, checking the return code because as we do
    898 	 * non-blocking connects, any error here indicates that the address is just
    899 	 * offline.  That is, it's something like "No route to host" which is not
    900 	 * worth retrying.  And so, we fail directly and immediately to the next
    901 	 * address for this host without enqueueing the address for retries.
    902 	 */
    903 	if (submitted_host == 0) {
    904 	    host_connect(context, ctx, host);
    905 	    if (host->state == DEAD)
    906 		continue;
    907 	} else {
    908 	    debug_host(context, 5, host,
    909 		       "Queuing host in future (in %ds), its the %lu address on the same name",
    910 		       (int)(context->host_timeout * submitted_host), submitted_host + 1);
    911 	    host->timeout = time(NULL) + (submitted_host * context->host_timeout);
    912 	}
    913 
    914 	heim_array_append_value(ctx->hosts, host);
    915 	heim_release(host);
    916 	submitted_host++;
    917     }
    918 
    919     if (freeai)
    920 	freeaddrinfo(ai);
    921 
    922     if (submitted_host == 0)
    923 	return KRB5_KDC_UNREACH;
    924 
    925     return 0;
    926 }
    927 
    928 struct wait_ctx {
    929     krb5_context context;
    930     krb5_sendto_ctx ctx;
    931     fd_set rfds;
    932     fd_set wfds;
    933     rk_socket_t max_fd;
    934     int got_reply;
    935     time_t timenow;
    936 };
    937 
    938 static void
    939 wait_setup(heim_object_t obj, void *iter_ctx, int *stop)
    940 {
    941     struct wait_ctx *wait_ctx = iter_ctx;
    942     struct host *h = (struct host *)obj;
    943 
    944     if (h->state == CONNECT) {
    945 	if (h->timeout >= wait_ctx->timenow)
    946 	    return;
    947 	host_connect(wait_ctx->context, wait_ctx->ctx, h);
    948     }
    949 
    950     /* skip dead hosts */
    951     if (h->state == DEAD)
    952 	return;
    953 
    954     /* if host timed out, dec tries and (retry or kill host) */
    955     if (h->timeout < wait_ctx->timenow) {
    956 	heim_assert(h->tries != 0, "tries should not reach 0");
    957 	h->tries--;
    958 	if (h->tries == 0) {
    959 	    host_dead(wait_ctx->context, h, "host timed out");
    960 	    return;
    961 	} else {
    962 	    debug_host(wait_ctx->context, 5, h, "retrying sending to");
    963 	    host_next_timeout(wait_ctx->context, h);
    964 	    host_connected(wait_ctx->context, wait_ctx->ctx, h);
    965 	}
    966     }
    967 
    968 #ifndef NO_LIMIT_FD_SETSIZE
    969     heim_assert(h->fd < FD_SETSIZE, "fd too large");
    970 #endif
    971     switch (h->state) {
    972     case WAITING_REPLY:
    973 	FD_SET(h->fd, &wait_ctx->rfds);
    974 	break;
    975     case CONNECTING:
    976     case CONNECTED:
    977 	FD_SET(h->fd, &wait_ctx->rfds);
    978 	FD_SET(h->fd, &wait_ctx->wfds);
    979 	break;
    980     default:
    981 	debug_host(wait_ctx->context, 5, h, "invalid sendto host state");
    982 	heim_abort("invalid sendto host state");
    983     }
    984     if (h->fd > wait_ctx->max_fd || wait_ctx->max_fd == rk_INVALID_SOCKET)
    985 	wait_ctx->max_fd = h->fd;
    986 }
    987 
    988 static int
    989 wait_filter_dead(heim_object_t obj, void *ctx)
    990 {
    991     struct host *h = (struct host *)obj;
    992     return (int)((h->state == DEAD) ? true : false);
    993 }
    994 
    995 static void
    996 wait_accelerate(heim_object_t obj, void *ctx, int *stop)
    997 {
    998     struct host *h = (struct host *)obj;
    999 
   1000     if (h->state == CONNECT && h->timeout > 0)
   1001 	h->timeout--;
   1002 }
   1003 
   1004 static void
   1005 wait_process(heim_object_t obj, void *ctx, int *stop)
   1006 {
   1007     struct wait_ctx *wait_ctx = ctx;
   1008     struct host *h = (struct host *)obj;
   1009     int readable, writeable;
   1010     heim_assert(h->state != DEAD, "dead host resurected");
   1011 
   1012 #ifndef NO_LIMIT_FD_SETSIZE
   1013     heim_assert(h->fd < FD_SETSIZE, "fd too large");
   1014 #endif
   1015     readable = FD_ISSET(h->fd, &wait_ctx->rfds);
   1016     writeable = FD_ISSET(h->fd, &wait_ctx->wfds);
   1017 
   1018     if (readable || writeable || h->state == CONNECT)
   1019 	wait_ctx->got_reply |= eval_host_state(wait_ctx->context, wait_ctx->ctx, h, readable, writeable);
   1020 
   1021     /* if there is already a reply, just fall though the array */
   1022     if (wait_ctx->got_reply)
   1023 	*stop = 1;
   1024 }
   1025 
   1026 static krb5_error_code
   1027 wait_response(krb5_context context, int *action, krb5_sendto_ctx ctx)
   1028 {
   1029     struct wait_ctx wait_ctx;
   1030     struct timeval tv;
   1031     int ret;
   1032 
   1033     wait_ctx.context = context;
   1034     wait_ctx.ctx = ctx;
   1035     FD_ZERO(&wait_ctx.rfds);
   1036     FD_ZERO(&wait_ctx.wfds);
   1037     wait_ctx.max_fd = rk_INVALID_SOCKET;
   1038 
   1039     /* oh, we have a reply, it must be a plugin that got it for us */
   1040     if (ctx->response.length) {
   1041 	*action = KRB5_SENDTO_FILTER;
   1042 	return 0;
   1043     }
   1044 
   1045     wait_ctx.timenow = time(NULL);
   1046 
   1047     heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_setup);
   1048     heim_array_filter_f(ctx->hosts, &wait_ctx, wait_filter_dead);
   1049 
   1050     if (heim_array_get_length(ctx->hosts) == 0) {
   1051 	if (ctx->stateflags & KRBHST_COMPLETED) {
   1052 	    _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
   1053 			 "trying to pulling more hosts");
   1054 	    *action = KRB5_SENDTO_FAILED;
   1055 	} else {
   1056 	    _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
   1057 			 "and no more hosts -> failure");
   1058 	    *action = KRB5_SENDTO_TIMEOUT;
   1059 	}
   1060 	return 0;
   1061     }
   1062 
   1063     if (wait_ctx.max_fd == rk_INVALID_SOCKET) {
   1064 	/*
   1065 	 * If we don't find a host which can make progress, then
   1066 	 * we accelerate the process by moving all of the contestants
   1067 	 * up by 1s.
   1068 	 */
   1069 	_krb5_debug(context, 5, "wait_response: moving the contestants forward");
   1070 	heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_accelerate);
   1071 	return 0;
   1072     }
   1073 
   1074     tv.tv_sec = 1;
   1075     tv.tv_usec = 0;
   1076 
   1077     ret = select(wait_ctx.max_fd + 1, &wait_ctx.rfds, &wait_ctx.wfds, NULL, &tv);
   1078     if (ret < 0)
   1079 	return errno;
   1080     if (ret == 0) {
   1081 	*action = KRB5_SENDTO_TIMEOUT;
   1082 	return 0;
   1083     }
   1084 
   1085     wait_ctx.got_reply = 0;
   1086     heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_process);
   1087     if (wait_ctx.got_reply)
   1088 	*action = KRB5_SENDTO_FILTER;
   1089     else
   1090 	*action = KRB5_SENDTO_CONTINUE;
   1091 
   1092     return 0;
   1093 }
   1094 
   1095 static void
   1096 reset_context(krb5_context context, krb5_sendto_ctx ctx)
   1097 {
   1098     krb5_data_free(&ctx->response);
   1099     heim_release(ctx->hosts);
   1100     ctx->hosts = heim_array_create();
   1101     ctx->stateflags = 0;
   1102 }
   1103 
   1104 
   1105 /*
   1106  *
   1107  */
   1108 
   1109 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
   1110 krb5_sendto_context(krb5_context context,
   1111 		    krb5_sendto_ctx ctx,
   1112 		    const krb5_data *send_data,
   1113 		    krb5_const_realm realm,
   1114 		    krb5_data *receive)
   1115 {
   1116     krb5_error_code ret = 0;
   1117     krb5_krbhst_handle handle = NULL;
   1118     struct timeval nrstart, nrstop, stop_time;
   1119     int type, freectx = 0;
   1120     int action;
   1121     int numreset = 0;
   1122 
   1123     krb5_data_zero(receive);
   1124 
   1125     if (ctx == NULL) {
   1126 	ret = krb5_sendto_ctx_alloc(context, &ctx);
   1127 	if (ret)
   1128 	    goto out;
   1129 	freectx = 1;
   1130     }
   1131 
   1132     ctx->stid = (context->num_kdc_requests++) << 16;
   1133 
   1134     memset(&ctx->stats, 0, sizeof(ctx->stats));
   1135     gettimeofday(&ctx->stats.start_time, NULL);
   1136 
   1137     type = ctx->type;
   1138     if (type == 0) {
   1139 	if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc)
   1140 	    type = KRB5_KRBHST_ADMIN;
   1141 	else
   1142 	    type = KRB5_KRBHST_KDC;
   1143     }
   1144 
   1145     ctx->send_data = send_data;
   1146 
   1147     if ((int)send_data->length > context->large_msg_size)
   1148 	ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG;
   1149 
   1150     /* loop until we get back a appropriate response */
   1151 
   1152     action = KRB5_SENDTO_INITIAL;
   1153 
   1154     while (action != KRB5_SENDTO_DONE && action != KRB5_SENDTO_FAILED) {
   1155 	krb5_krbhst_info *hi;
   1156 
   1157 	switch (action) {
   1158 	case KRB5_SENDTO_INITIAL:
   1159 	    ret = realm_via_plugin(context, realm, context->kdc_timeout,
   1160 				   send_data, &ctx->response);
   1161 	    if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) {
   1162 		action = KRB5_SENDTO_DONE;
   1163 		break;
   1164 	    }
   1165 	    action = KRB5_SENDTO_KRBHST;
   1166 	    /* FALLTHROUGH */
   1167 	case KRB5_SENDTO_KRBHST:
   1168 	    if (ctx->krbhst == NULL) {
   1169 		ret = krb5_krbhst_init_flags(context, realm, type,
   1170 					     ctx->flags, &handle);
   1171 		if (ret)
   1172 		    goto out;
   1173 
   1174 		if (ctx->hostname) {
   1175 		    ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname);
   1176 		    if (ret)
   1177 			goto out;
   1178 		}
   1179 
   1180 	    } else {
   1181 		handle = heim_retain(ctx->krbhst);
   1182 	    }
   1183 	    action = KRB5_SENDTO_TIMEOUT;
   1184 	    /* FALLTHROUGH */
   1185 	case KRB5_SENDTO_TIMEOUT:
   1186 
   1187 	    /*
   1188 	     * If we completed, just got to next step
   1189 	     */
   1190 
   1191 	    if (ctx->stateflags & KRBHST_COMPLETED) {
   1192 		action = KRB5_SENDTO_CONTINUE;
   1193 		break;
   1194 	    }
   1195 
   1196 	    /*
   1197 	     * Pull out next host, if there is no more, close the
   1198 	     * handle and mark as completed.
   1199 	     *
   1200 	     * Collect time spent in krbhst (dns, plugin, etc)
   1201 	     */
   1202 
   1203 
   1204 	    gettimeofday(&nrstart, NULL);
   1205 
   1206 	    ret = krb5_krbhst_next(context, handle, &hi);
   1207 
   1208 	    gettimeofday(&nrstop, NULL);
   1209 	    timevalsub(&nrstop, &nrstart);
   1210 	    timevaladd(&ctx->stats.krbhst, &nrstop);
   1211 
   1212 	    action = KRB5_SENDTO_CONTINUE;
   1213 	    if (ret == 0) {
   1214 		_krb5_debug(context, 5, "submitting new requests to new host");
   1215 		if (submit_request(context, ctx, hi) != 0)
   1216 		    action = KRB5_SENDTO_TIMEOUT;
   1217 	    } else {
   1218 		_krb5_debug(context, 5, "out of hosts, waiting for replies");
   1219 		ctx->stateflags |= KRBHST_COMPLETED;
   1220 	    }
   1221 
   1222 	    break;
   1223 	case KRB5_SENDTO_CONTINUE:
   1224 
   1225 	    ret = wait_response(context, &action, ctx);
   1226 	    if (ret)
   1227 		goto out;
   1228 
   1229 	    break;
   1230 	case KRB5_SENDTO_RESET:
   1231 	    /* start over */
   1232 	    _krb5_debug(context, 5,
   1233 			"krb5_sendto trying over again (reset): %d",
   1234 			numreset);
   1235 	    reset_context(context, ctx);
   1236 	    if (handle) {
   1237 		krb5_krbhst_free(context, handle);
   1238 		handle = NULL;
   1239 	    }
   1240 	    numreset++;
   1241 	    if (numreset >= 3)
   1242 		action = KRB5_SENDTO_FAILED;
   1243 	    else
   1244 		action = KRB5_SENDTO_KRBHST;
   1245 
   1246 	    break;
   1247 	case KRB5_SENDTO_FILTER:
   1248 	    /* default to next state, the filter function might modify this */
   1249 	    action = KRB5_SENDTO_DONE;
   1250 
   1251 	    if (ctx->func) {
   1252 		ret = (*ctx->func)(context, ctx, ctx->data,
   1253 				   &ctx->response, &action);
   1254 		if (ret)
   1255 		    goto out;
   1256 	    }
   1257 	    break;
   1258 	case KRB5_SENDTO_FAILED:
   1259 	    ret = KRB5_KDC_UNREACH;
   1260 	    break;
   1261 	case KRB5_SENDTO_DONE:
   1262 	    ret = 0;
   1263 	    break;
   1264 	default:
   1265 	    heim_abort("invalid krb5_sendto_context state");
   1266 	}
   1267     }
   1268 
   1269 out:
   1270     gettimeofday(&stop_time, NULL);
   1271     timevalsub(&stop_time, &ctx->stats.start_time);
   1272     if (ret == 0 && ctx->response.length) {
   1273 	*receive = ctx->response;
   1274 	krb5_data_zero(&ctx->response);
   1275     } else {
   1276 	krb5_data_free(&ctx->response);
   1277 	krb5_clear_error_message (context);
   1278 	ret = KRB5_KDC_UNREACH;
   1279 	krb5_set_error_message(context, ret,
   1280 			       N_("unable to reach any KDC in realm %s", ""),
   1281 			       realm);
   1282     }
   1283 
   1284     _krb5_debug(context, 1,
   1285 		"%s %s done: %d hosts %lu packets %lu:"
   1286 		" wc: %jd.%06lu nr: %jd.%06lu kh: %jd.%06lu tid: %08x",
   1287 		__func__, realm, ret,
   1288 		ctx->stats.num_hosts, ctx->stats.sent_packets,
   1289 		(intmax_t)stop_time.tv_sec,
   1290 		(unsigned long)stop_time.tv_usec,
   1291 		(intmax_t)ctx->stats.name_resolution.tv_sec,
   1292 		(unsigned long)ctx->stats.name_resolution.tv_usec,
   1293 		(intmax_t)ctx->stats.krbhst.tv_sec,
   1294 		(unsigned long)ctx->stats.krbhst.tv_usec, ctx->stid);
   1295 
   1296     if (freectx)
   1297 	krb5_sendto_ctx_free(context, ctx);
   1298     else
   1299 	reset_context(context, ctx);
   1300 
   1301     if (handle)
   1302 	krb5_krbhst_free(context, handle);
   1303 
   1304     return ret;
   1305 }
   1306