xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/krb5/send_to_kdc.c (revision c38e7cc395b1472a774ff828e46123de44c628e9)
1 /*	$NetBSD: send_to_kdc.c,v 1.7 2017/01/30 00:25:15 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1997 - 2002 Kungliga Tekniska Högskolan
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     char name[NI_MAXHOST], port[NI_MAXSERV];
320     char *text = NULL;
321     va_list ap;
322     int ret;
323 
324     if (!_krb5_have_debug(context, 5))
325 	return;
326 
327     va_start(ap, fmt);
328     ret = vasprintf(&text, fmt, ap);
329     va_end(ap);
330     if (ret == -1 || text == NULL)
331 	return;
332 
333     if (host->hi->proto == KRB5_KRBHST_HTTP)
334 	proto = "http";
335     else if (host->hi->proto == KRB5_KRBHST_TCP)
336 	proto = "tcp";
337     else if (host->hi->proto == KRB5_KRBHST_UDP)
338 	proto = "udp";
339 
340     if (getnameinfo(host->ai->ai_addr, host->ai->ai_addrlen,
341 		    name, sizeof(name), port, sizeof(port), NI_NUMERICHOST) != 0)
342 	name[0] = '\0';
343 
344     _krb5_debug(context, level, "%s: %s %s:%s (%s) tid: %08x", text,
345 		proto, name, port, host->hi->hostname, host->tid);
346     free(text);
347 }
348 
349 
350 static void
351 deallocate_host(void *ptr)
352 {
353     struct host *host = ptr;
354     if (!rk_IS_BAD_SOCKET(host->fd))
355 	rk_closesocket(host->fd);
356     krb5_data_free(&host->data);
357     host->ai = NULL;
358 }
359 
360 static void
361 host_dead(krb5_context context, struct host *host, const char *msg)
362 {
363     debug_host(context, 5, host, "%s", msg);
364     rk_closesocket(host->fd);
365     host->fd = rk_INVALID_SOCKET;
366     host->state = DEAD;
367 }
368 
369 static krb5_error_code
370 send_stream(krb5_context context, struct host *host)
371 {
372     ssize_t len;
373 
374     len = krb5_net_write(context, &host->fd, host->data.data, host->data.length);
375 
376     if (len < 0)
377 	return errno;
378     else if (len < host->data.length) {
379 	host->data.length -= len;
380 	memmove(host->data.data, ((uint8_t *)host->data.data) + len, host->data.length - len);
381 	return -1;
382     } else {
383 	krb5_data_free(&host->data);
384 	return 0;
385     }
386 }
387 
388 static krb5_error_code
389 recv_stream(krb5_context context, struct host *host)
390 {
391     krb5_error_code ret;
392     size_t oldlen;
393     ssize_t sret;
394     int nbytes;
395 
396     if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
397 	return HEIM_NET_CONN_REFUSED;
398 
399     if (context->max_msg_size - host->data.length < nbytes) {
400 	krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
401 			       N_("TCP message from KDC too large %d", ""),
402 			       (int)(host->data.length + nbytes));
403 	return KRB5KRB_ERR_FIELD_TOOLONG;
404     }
405 
406     oldlen = host->data.length;
407 
408     ret = krb5_data_realloc(&host->data, oldlen + nbytes + 1 /* NUL */);
409     if (ret)
410 	return ret;
411 
412     sret = krb5_net_read(context, &host->fd, ((uint8_t *)host->data.data) + oldlen, nbytes);
413     if (sret <= 0) {
414 	ret = errno;
415 	return ret;
416     }
417     host->data.length = oldlen + sret;
418     /* zero terminate for http transport */
419     ((uint8_t *)host->data.data)[host->data.length] = '\0';
420 
421     return 0;
422 }
423 
424 /*
425  *
426  */
427 
428 static void
429 host_next_timeout(krb5_context context, struct host *host)
430 {
431     host->timeout = context->kdc_timeout / host->fun->ntries;
432     if (host->timeout == 0)
433 	host->timeout = 1;
434 
435     host->timeout += time(NULL);
436 }
437 
438 /*
439  * connected host
440  */
441 
442 static void
443 host_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
444 {
445     krb5_error_code ret;
446 
447     host->state = CONNECTED;
448     /*
449      * Now prepare data to send to host
450      */
451     if (ctx->prexmit_func) {
452 	krb5_data data;
453 
454 	krb5_data_zero(&data);
455 
456 	ret = ctx->prexmit_func(context, host->hi->proto,
457 				ctx->prexmit_ctx, host->fd, &data);
458 	if (ret == 0) {
459 	    if (data.length == 0) {
460 		host_dead(context, host, "prexmit function didn't send data");
461 		return;
462 	    }
463 	    ret = host->fun->prepare(context, host, &data);
464 	    krb5_data_free(&data);
465 	}
466 
467     } else {
468 	ret = host->fun->prepare(context, host, ctx->send_data);
469     }
470     if (ret)
471 	debug_host(context, 5, host, "failed to prexmit/prepare");
472 }
473 
474 /*
475  * connect host
476  */
477 
478 static void
479 host_connect(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
480 {
481     krb5_krbhst_info *hi = host->hi;
482     struct addrinfo *ai = host->ai;
483 
484     debug_host(context, 5, host, "connecting to host");
485 
486     if (connect(host->fd, ai->ai_addr, ai->ai_addrlen) < 0) {
487 #ifdef HAVE_WINSOCK
488 	if (WSAGetLastError() == WSAEWOULDBLOCK)
489 	    errno = EINPROGRESS;
490 #endif /* HAVE_WINSOCK */
491 	if (errno == EINPROGRESS && (hi->proto == KRB5_KRBHST_HTTP || hi->proto == KRB5_KRBHST_TCP)) {
492 	    debug_host(context, 5, host, "connecting to %d", host->fd);
493 	    host->state = CONNECTING;
494 	} else {
495 	    host_dead(context, host, "failed to connect");
496 	}
497     } else {
498 	host_connected(context, ctx, host);
499     }
500 
501     host_next_timeout(context, host);
502 }
503 
504 /*
505  * HTTP transport
506  */
507 
508 static krb5_error_code
509 prepare_http(krb5_context context, struct host *host, const krb5_data *data)
510 {
511     char *str = NULL, *request = NULL;
512     krb5_error_code ret;
513     int len;
514 
515     heim_assert(host->data.length == 0, "prepare_http called twice");
516 
517     len = rk_base64_encode(data->data, data->length, &str);
518     if(len < 0)
519 	return ENOMEM;
520 
521     if (context->http_proxy)
522 	ret = asprintf(&request, "GET http://%s/%s HTTP/1.0\r\n\r\n", host->hi->hostname, str);
523     else
524 	ret = asprintf(&request, "GET /%s HTTP/1.0\r\n\r\n", str);
525     free(str);
526     if(ret < 0 || request == NULL)
527 	return ENOMEM;
528 
529     host->data.data = request;
530     host->data.length = strlen(request);
531 
532     return 0;
533 }
534 
535 static krb5_error_code
536 recv_http(krb5_context context, struct host *host, krb5_data *data)
537 {
538     krb5_error_code ret;
539     unsigned long rep_len;
540     size_t len;
541     char *p;
542 
543     /*
544      * recv_stream returns a NUL terminated stream
545      */
546 
547     ret = recv_stream(context, host);
548     if (ret)
549 	return ret;
550 
551     p = strstr(host->data.data, "\r\n\r\n");
552     if (p == NULL)
553 	return -1;
554     p += 4;
555 
556     len = host->data.length - (p - (char *)host->data.data);
557     if (len < 4)
558 	return -1;
559 
560     _krb5_get_int(p, &rep_len, 4);
561     if (len < rep_len)
562 	return -1;
563 
564     p += 4;
565 
566     memmove(host->data.data, p, rep_len);
567     host->data.length = rep_len;
568 
569     *data = host->data;
570     krb5_data_zero(&host->data);
571 
572     return 0;
573 }
574 
575 /*
576  * TCP transport
577  */
578 
579 static krb5_error_code
580 prepare_tcp(krb5_context context, struct host *host, const krb5_data *data)
581 {
582     krb5_error_code ret;
583     krb5_storage *sp;
584 
585     heim_assert(host->data.length == 0, "prepare_tcp called twice");
586 
587     sp = krb5_storage_emem();
588     if (sp == NULL)
589 	return ENOMEM;
590 
591     ret = krb5_store_data(sp, *data);
592     if (ret) {
593 	krb5_storage_free(sp);
594 	return ret;
595     }
596     ret = krb5_storage_to_data(sp, &host->data);
597     krb5_storage_free(sp);
598 
599     return ret;
600 }
601 
602 static krb5_error_code
603 recv_tcp(krb5_context context, struct host *host, krb5_data *data)
604 {
605     krb5_error_code ret;
606     unsigned long pktlen;
607 
608     ret = recv_stream(context, host);
609     if (ret)
610 	return ret;
611 
612     if (host->data.length < 4)
613 	return -1;
614 
615     _krb5_get_int(host->data.data, &pktlen, 4);
616 
617     if (pktlen > host->data.length - 4)
618 	return -1;
619 
620     memmove(host->data.data, ((uint8_t *)host->data.data) + 4, host->data.length - 4);
621     host->data.length -= 4;
622 
623     *data = host->data;
624     krb5_data_zero(&host->data);
625 
626     return 0;
627 }
628 
629 /*
630  * UDP transport
631  */
632 
633 static krb5_error_code
634 prepare_udp(krb5_context context, struct host *host, const krb5_data *data)
635 {
636     return krb5_data_copy(&host->data, data->data, data->length);
637 }
638 
639 static krb5_error_code
640 send_udp(krb5_context context, struct host *host)
641 {
642     if (send(host->fd, host->data.data, host->data.length, 0) < 0)
643 	return errno;
644     return 0;
645 }
646 
647 static krb5_error_code
648 recv_udp(krb5_context context, struct host *host, krb5_data *data)
649 {
650     krb5_error_code ret;
651     int nbytes;
652 
653 
654     if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
655 	return HEIM_NET_CONN_REFUSED;
656 
657     if (context->max_msg_size < nbytes) {
658 	krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
659 			       N_("UDP message from KDC too large %d", ""),
660 			       (int)nbytes);
661 	return KRB5KRB_ERR_FIELD_TOOLONG;
662     }
663 
664     ret = krb5_data_alloc(data, nbytes);
665     if (ret)
666 	return ret;
667 
668     ret = recv(host->fd, data->data, data->length, 0);
669     if (ret < 0) {
670 	ret = errno;
671 	krb5_data_free(data);
672 	return ret;
673     }
674     data->length = ret;
675 
676     return 0;
677 }
678 
679 static struct host_fun http_fun = {
680     prepare_http,
681     send_stream,
682     recv_http,
683     1
684 };
685 static struct host_fun tcp_fun = {
686     prepare_tcp,
687     send_stream,
688     recv_tcp,
689     1
690 };
691 static struct host_fun udp_fun = {
692     prepare_udp,
693     send_udp,
694     recv_udp,
695     3
696 };
697 
698 
699 /*
700  * Host state machine
701  */
702 
703 static int
704 eval_host_state(krb5_context context,
705 		krb5_sendto_ctx ctx,
706 		struct host *host,
707 		int readable, int writeable)
708 {
709     krb5_error_code ret;
710 
711     if (host->state == CONNECT) {
712 	/* check if its this host time to connect */
713 	if (host->timeout < time(NULL))
714 	    host_connect(context, ctx, host);
715 	return 0;
716     }
717 
718     if (host->state == CONNECTING && writeable)
719 	host_connected(context, ctx, host);
720 
721     if (readable) {
722 
723 	debug_host(context, 5, host, "reading packet");
724 
725 	ret = host->fun->recv_fn(context, host, &ctx->response);
726 	if (ret == -1) {
727 	    /* not done yet */
728 	} else if (ret == 0) {
729 	    /* if recv_foo function returns 0, we have a complete reply */
730 	    debug_host(context, 5, host, "host completed");
731 	    return 1;
732 	} else {
733 	    host_dead(context, host, "host disconnected");
734 	}
735     }
736 
737     /* check if there is anything to send, state might DEAD after read */
738     if (writeable && host->state == CONNECTED) {
739 
740 	ctx->stats.sent_packets++;
741 
742 	debug_host(context, 5, host, "writing packet");
743 
744 	ret = host->fun->send_fn(context, host);
745 	if (ret == -1) {
746 	    /* not done yet */
747 	} else if (ret) {
748 	    host_dead(context, host, "host dead, write failed");
749 	} else
750 	    host->state = WAITING_REPLY;
751     }
752 
753     return 0;
754 }
755 
756 /*
757  *
758  */
759 
760 static krb5_error_code
761 submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi)
762 {
763     unsigned long submitted_host = 0;
764     krb5_boolean freeai = FALSE;
765     struct timeval nrstart, nrstop;
766     krb5_error_code ret;
767     struct addrinfo *ai = NULL, *a;
768     struct host *host;
769 
770     ret = kdc_via_plugin(context, hi, context->kdc_timeout,
771 			 ctx->send_data, &ctx->response);
772     if (ret == 0) {
773 	return 0;
774     } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
775 	_krb5_debug(context, 5, "send via plugin failed %s: %d",
776 		    hi->hostname, ret);
777 	return ret;
778     }
779 
780     /*
781      * If we have a proxy, let use the address of the proxy instead of
782      * the KDC and let the proxy deal with the resolving of the KDC.
783      */
784 
785     gettimeofday(&nrstart, NULL);
786 
787     if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) {
788 	char *proxy2 = strdup(context->http_proxy);
789 	char *el, *proxy  = proxy2;
790 	struct addrinfo hints;
791 	char portstr[NI_MAXSERV];
792 	unsigned short nport;
793 
794 	if (proxy == NULL)
795 	    return ENOMEM;
796 	if (strncmp(proxy, "http://", 7) == 0)
797 	    proxy += 7;
798 
799 	/* check for url terminating slash */
800 	el = strchr(proxy, '/');
801 	if (el != NULL)
802 	    *el = '\0';
803 
804 	/* check for port in hostname, used below as port */
805 	el = strchr(proxy, ':');
806 	if(el != NULL)
807 	    *el++ = '\0';
808 
809 	memset(&hints, 0, sizeof(hints));
810 	hints.ai_family   = PF_UNSPEC;
811 	hints.ai_socktype = SOCK_STREAM;
812 
813 	/* On some systems ntohs(foo(..., htons(...))) causes shadowing */
814 	nport = init_port(el, htons(80));
815 	snprintf(portstr, sizeof(portstr), "%d", ntohs(nport));
816 
817 	ret = getaddrinfo(proxy, portstr, &hints, &ai);
818 	free(proxy2);
819 	if (ret)
820 	    return krb5_eai_to_heim_errno(ret, errno);
821 
822 	freeai = TRUE;
823 
824     } else {
825 	ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
826 	if (ret)
827 	    return ret;
828     }
829 
830     /* add up times */
831     gettimeofday(&nrstop, NULL);
832     timevalsub(&nrstop, &nrstart);
833     timevaladd(&ctx->stats.name_resolution, &nrstop);
834 
835     ctx->stats.num_hosts++;
836 
837     for (a = ai; a != NULL; a = a->ai_next) {
838 	rk_socket_t fd;
839 
840 	fd = socket(a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);
841 	if (rk_IS_BAD_SOCKET(fd))
842 	    continue;
843 	rk_cloexec(fd);
844 
845 #ifndef NO_LIMIT_FD_SETSIZE
846 	if (fd >= FD_SETSIZE) {
847 	    _krb5_debug(context, 0, "fd too large for select");
848 	    rk_closesocket(fd);
849 	    continue;
850 	}
851 #endif
852 	socket_set_nonblocking(fd, 1);
853 
854 	host = heim_alloc(sizeof(*host), "sendto-host", deallocate_host);
855 	if (host == NULL) {
856             if (freeai)
857                 freeaddrinfo(ai);
858 	    rk_closesocket(fd);
859 	    return ENOMEM;
860 	}
861 	host->hi = hi;
862 	host->fd = fd;
863 	host->ai = a;
864 	/* next version of stid */
865 	host->tid = ctx->stid = (ctx->stid & 0xffff0000) | ((ctx->stid & 0xffff) + 1);
866 
867 	host->state = CONNECT;
868 
869 	switch (host->hi->proto) {
870 	case KRB5_KRBHST_HTTP :
871 	    host->fun = &http_fun;
872 	    break;
873 	case KRB5_KRBHST_TCP :
874 	    host->fun = &tcp_fun;
875 	    break;
876 	case KRB5_KRBHST_UDP :
877 	    host->fun = &udp_fun;
878 	    break;
879 	default:
880 	    heim_abort("undefined http transport protocol: %d", (int)host->hi->proto);
881 	}
882 
883 	host->tries = host->fun->ntries;
884 
885 	/*
886 	 * Connect directly next host, wait a host_timeout for each next address
887 	 */
888 	if (submitted_host == 0)
889 	    host_connect(context, ctx, host);
890 	else {
891 	    debug_host(context, 5, host,
892 		       "Queuing host in future (in %ds), its the %lu address on the same name",
893 		       (int)(context->host_timeout * submitted_host), submitted_host + 1);
894 	    host->timeout = time(NULL) + (submitted_host * context->host_timeout);
895 	}
896 
897 	heim_array_append_value(ctx->hosts, host);
898 
899 	heim_release(host);
900 
901 	submitted_host++;
902     }
903 
904     if (freeai)
905 	freeaddrinfo(ai);
906 
907     if (!submitted_host)
908 	return KRB5_KDC_UNREACH;
909 
910     return 0;
911 }
912 
913 struct wait_ctx {
914     krb5_context context;
915     krb5_sendto_ctx ctx;
916     fd_set rfds;
917     fd_set wfds;
918     unsigned max_fd;
919     int got_reply;
920     time_t timenow;
921 };
922 
923 static void
924 wait_setup(heim_object_t obj, void *iter_ctx, int *stop)
925 {
926     struct wait_ctx *wait_ctx = iter_ctx;
927     struct host *h = (struct host *)obj;
928 
929     /* skip dead hosts */
930     if (h->state == DEAD)
931 	return;
932 
933     if (h->state == CONNECT) {
934 	if (h->timeout < wait_ctx->timenow)
935 	    host_connect(wait_ctx->context, wait_ctx->ctx, h);
936 	return;
937     }
938 
939     /* if host timed out, dec tries and (retry or kill host) */
940     if (h->timeout < wait_ctx->timenow) {
941 	heim_assert(h->tries != 0, "tries should not reach 0");
942 	h->tries--;
943 	if (h->tries == 0) {
944 	    host_dead(wait_ctx->context, h, "host timed out");
945 	    return;
946 	} else {
947 	    debug_host(wait_ctx->context, 5, h, "retrying sending to");
948 	    host_next_timeout(wait_ctx->context, h);
949 	    host_connected(wait_ctx->context, wait_ctx->ctx, h);
950 	}
951     }
952 
953 #ifndef NO_LIMIT_FD_SETSIZE
954     heim_assert(h->fd < FD_SETSIZE, "fd too large");
955 #endif
956     switch (h->state) {
957     case WAITING_REPLY:
958 	FD_SET(h->fd, &wait_ctx->rfds);
959 	break;
960     case CONNECTING:
961     case CONNECTED:
962 	FD_SET(h->fd, &wait_ctx->rfds);
963 	FD_SET(h->fd, &wait_ctx->wfds);
964 	break;
965     default:
966 	heim_abort("invalid sendto host state");
967     }
968     if (h->fd > wait_ctx->max_fd)
969 	wait_ctx->max_fd = h->fd;
970 }
971 
972 static int
973 wait_filter_dead(heim_object_t obj, void *ctx)
974 {
975     struct host *h = (struct host *)obj;
976     return (int)((h->state == DEAD) ? true : false);
977 }
978 
979 static void
980 wait_process(heim_object_t obj, void *ctx, int *stop)
981 {
982     struct wait_ctx *wait_ctx = ctx;
983     struct host *h = (struct host *)obj;
984     int readable, writeable;
985     heim_assert(h->state != DEAD, "dead host resurected");
986 
987 #ifndef NO_LIMIT_FD_SETSIZE
988     heim_assert(h->fd < FD_SETSIZE, "fd too large");
989 #endif
990     readable = FD_ISSET(h->fd, &wait_ctx->rfds);
991     writeable = FD_ISSET(h->fd, &wait_ctx->wfds);
992 
993     if (readable || writeable || h->state == CONNECT)
994 	wait_ctx->got_reply |= eval_host_state(wait_ctx->context, wait_ctx->ctx, h, readable, writeable);
995 
996     /* if there is already a reply, just fall though the array */
997     if (wait_ctx->got_reply)
998 	*stop = 1;
999 }
1000 
1001 static krb5_error_code
1002 wait_response(krb5_context context, int *action, krb5_sendto_ctx ctx)
1003 {
1004     struct wait_ctx wait_ctx;
1005     struct timeval tv;
1006     int ret;
1007 
1008     wait_ctx.context = context;
1009     wait_ctx.ctx = ctx;
1010     FD_ZERO(&wait_ctx.rfds);
1011     FD_ZERO(&wait_ctx.wfds);
1012     wait_ctx.max_fd = 0;
1013 
1014     /* oh, we have a reply, it must be a plugin that got it for us */
1015     if (ctx->response.length) {
1016 	*action = KRB5_SENDTO_FILTER;
1017 	return 0;
1018     }
1019 
1020     wait_ctx.timenow = time(NULL);
1021 
1022     heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_setup);
1023     heim_array_filter_f(ctx->hosts, &wait_ctx, wait_filter_dead);
1024 
1025     if (heim_array_get_length(ctx->hosts) == 0) {
1026 	if (ctx->stateflags & KRBHST_COMPLETED) {
1027 	    _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
1028 			 "trying to pulling more hosts");
1029 	    *action = KRB5_SENDTO_FAILED;
1030 	} else {
1031 	    _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
1032 			 "and no more hosts -> failure");
1033 	    *action = KRB5_SENDTO_TIMEOUT;
1034 	}
1035 	return 0;
1036     }
1037 
1038     tv.tv_sec = 1;
1039     tv.tv_usec = 0;
1040 
1041     ret = select(wait_ctx.max_fd + 1, &wait_ctx.rfds, &wait_ctx.wfds, NULL, &tv);
1042     if (ret < 0)
1043 	return errno;
1044     if (ret == 0) {
1045 	*action = KRB5_SENDTO_TIMEOUT;
1046 	return 0;
1047     }
1048 
1049     wait_ctx.got_reply = 0;
1050     heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_process);
1051     if (wait_ctx.got_reply)
1052 	*action = KRB5_SENDTO_FILTER;
1053     else
1054 	*action = KRB5_SENDTO_CONTINUE;
1055 
1056     return 0;
1057 }
1058 
1059 static void
1060 reset_context(krb5_context context, krb5_sendto_ctx ctx)
1061 {
1062     krb5_data_free(&ctx->response);
1063     heim_release(ctx->hosts);
1064     ctx->hosts = heim_array_create();
1065     ctx->stateflags = 0;
1066 }
1067 
1068 
1069 /*
1070  *
1071  */
1072 
1073 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1074 krb5_sendto_context(krb5_context context,
1075 		    krb5_sendto_ctx ctx,
1076 		    const krb5_data *send_data,
1077 		    krb5_const_realm realm,
1078 		    krb5_data *receive)
1079 {
1080     krb5_error_code ret = 0;
1081     krb5_krbhst_handle handle = NULL;
1082     struct timeval nrstart, nrstop, stop_time;
1083     int type, freectx = 0;
1084     int action;
1085     int numreset = 0;
1086 
1087     krb5_data_zero(receive);
1088 
1089     if (ctx == NULL) {
1090 	ret = krb5_sendto_ctx_alloc(context, &ctx);
1091 	if (ret)
1092 	    goto out;
1093 	freectx = 1;
1094     }
1095 
1096     ctx->stid = (context->num_kdc_requests++) << 16;
1097 
1098     memset(&ctx->stats, 0, sizeof(ctx->stats));
1099     gettimeofday(&ctx->stats.start_time, NULL);
1100 
1101     type = ctx->type;
1102     if (type == 0) {
1103 	if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc)
1104 	    type = KRB5_KRBHST_ADMIN;
1105 	else
1106 	    type = KRB5_KRBHST_KDC;
1107     }
1108 
1109     ctx->send_data = send_data;
1110 
1111     if ((int)send_data->length > context->large_msg_size)
1112 	ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG;
1113 
1114     /* loop until we get back a appropriate response */
1115 
1116     action = KRB5_SENDTO_INITIAL;
1117 
1118     while (action != KRB5_SENDTO_DONE && action != KRB5_SENDTO_FAILED) {
1119 	krb5_krbhst_info *hi;
1120 
1121 	switch (action) {
1122 	case KRB5_SENDTO_INITIAL:
1123 	    ret = realm_via_plugin(context, realm, context->kdc_timeout,
1124 				   send_data, &ctx->response);
1125 	    if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) {
1126 		action = KRB5_SENDTO_DONE;
1127 		break;
1128 	    }
1129 	    action = KRB5_SENDTO_KRBHST;
1130 	    /* FALLTHOUGH */
1131 	case KRB5_SENDTO_KRBHST:
1132 	    if (ctx->krbhst == NULL) {
1133 		ret = krb5_krbhst_init_flags(context, realm, type,
1134 					     ctx->flags, &handle);
1135 		if (ret)
1136 		    goto out;
1137 
1138 		if (ctx->hostname) {
1139 		    ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname);
1140 		    if (ret)
1141 			goto out;
1142 		}
1143 
1144 	    } else {
1145 		handle = heim_retain(ctx->krbhst);
1146 	    }
1147 	    action = KRB5_SENDTO_TIMEOUT;
1148 	    /* FALLTHOUGH */
1149 	case KRB5_SENDTO_TIMEOUT:
1150 
1151 	    /*
1152 	     * If we completed, just got to next step
1153 	     */
1154 
1155 	    if (ctx->stateflags & KRBHST_COMPLETED) {
1156 		action = KRB5_SENDTO_CONTINUE;
1157 		break;
1158 	    }
1159 
1160 	    /*
1161 	     * Pull out next host, if there is no more, close the
1162 	     * handle and mark as completed.
1163 	     *
1164 	     * Collect time spent in krbhst (dns, plugin, etc)
1165 	     */
1166 
1167 
1168 	    gettimeofday(&nrstart, NULL);
1169 
1170 	    ret = krb5_krbhst_next(context, handle, &hi);
1171 
1172 	    gettimeofday(&nrstop, NULL);
1173 	    timevalsub(&nrstop, &nrstart);
1174 	    timevaladd(&ctx->stats.krbhst, &nrstop);
1175 
1176 	    action = KRB5_SENDTO_CONTINUE;
1177 	    if (ret == 0) {
1178 		_krb5_debug(context, 5, "submissing new requests to new host");
1179 		if (submit_request(context, ctx, hi) != 0)
1180 		    action = KRB5_SENDTO_TIMEOUT;
1181 	    } else {
1182 		_krb5_debug(context, 5, "out of hosts, waiting for replies");
1183 		ctx->stateflags |= KRBHST_COMPLETED;
1184 	    }
1185 
1186 	    break;
1187 	case KRB5_SENDTO_CONTINUE:
1188 
1189 	    ret = wait_response(context, &action, ctx);
1190 	    if (ret)
1191 		goto out;
1192 
1193 	    break;
1194 	case KRB5_SENDTO_RESET:
1195 	    /* start over */
1196 	    _krb5_debug(context, 5,
1197 			"krb5_sendto trying over again (reset): %d",
1198 			numreset);
1199 	    reset_context(context, ctx);
1200 	    if (handle) {
1201 		krb5_krbhst_free(context, handle);
1202 		handle = NULL;
1203 	    }
1204 	    numreset++;
1205 	    if (numreset >= 3)
1206 		action = KRB5_SENDTO_FAILED;
1207 	    else
1208 		action = KRB5_SENDTO_KRBHST;
1209 
1210 	    break;
1211 	case KRB5_SENDTO_FILTER:
1212 	    /* default to next state, the filter function might modify this */
1213 	    action = KRB5_SENDTO_DONE;
1214 
1215 	    if (ctx->func) {
1216 		ret = (*ctx->func)(context, ctx, ctx->data,
1217 				   &ctx->response, &action);
1218 		if (ret)
1219 		    goto out;
1220 	    }
1221 	    break;
1222 	case KRB5_SENDTO_FAILED:
1223 	    ret = KRB5_KDC_UNREACH;
1224 	    break;
1225 	case KRB5_SENDTO_DONE:
1226 	    ret = 0;
1227 	    break;
1228 	default:
1229 	    heim_abort("invalid krb5_sendto_context state");
1230 	}
1231     }
1232 
1233 out:
1234     gettimeofday(&stop_time, NULL);
1235     timevalsub(&stop_time, &ctx->stats.start_time);
1236     if (ret == 0 && ctx->response.length) {
1237 	*receive = ctx->response;
1238 	krb5_data_zero(&ctx->response);
1239     } else {
1240 	krb5_data_free(&ctx->response);
1241 	krb5_clear_error_message (context);
1242 	ret = KRB5_KDC_UNREACH;
1243 	krb5_set_error_message(context, ret,
1244 			       N_("unable to reach any KDC in realm %s", ""),
1245 			       realm);
1246     }
1247 
1248     _krb5_debug(context, 1,
1249 		"%s %s done: %d hosts %lu packets %lu:"
1250 		" wc: %jd.%06ld nr: %jd.%06ld kh: %jd.%06ld tid: %08x",
1251 		__func__, realm, ret,
1252 		ctx->stats.num_hosts, ctx->stats.sent_packets,
1253 		(intmax_t)stop_time.tv_sec,
1254 		(long)stop_time.tv_usec,
1255 		(intmax_t)ctx->stats.name_resolution.tv_sec,
1256 		(long)ctx->stats.name_resolution.tv_usec,
1257 		(intmax_t)ctx->stats.krbhst.tv_sec,
1258 		(long)ctx->stats.krbhst.tv_usec, ctx->stid);
1259 
1260 
1261     if (freectx)
1262 	krb5_sendto_ctx_free(context, ctx);
1263     else
1264 	reset_context(context, ctx);
1265 
1266     if (handle)
1267 	krb5_krbhst_free(context, handle);
1268 
1269     return ret;
1270 }
1271