Use __attribute__ ((__name__)) form
[heimdal.git] / lib / krb5 / send_to_kdc.c
blob614e88d876c90c30ef2291795738dd9140a6c07a
1 /*
2 * Copyright (c) 1997 - 2002 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Portions Copyright (c) 2010 - 2013 Apple Inc. All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the Institute nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
36 #include "krb5_locl.h"
37 #include "send_to_kdc_plugin.h"
39 /**
40 * @section send_to_kdc Locating and sending packets to the KDC
42 * The send to kdc code is responsible to request the list of KDC from
43 * the locate-kdc subsystem and then send requests to each of them.
45 * - Each second a new hostname is tried.
46 * - If the hostname have several addresses, the first will be tried
47 * directly then in turn the other will be tried every 3 seconds
48 * (host_timeout).
49 * - UDP requests are tried 3 times, and it tried with a individual timeout of kdc_timeout / 3.
50 * - TCP and HTTP requests are tried 1 time.
52 * Total wait time shorter then (number of addresses * 3) + kdc_timeout seconds.
56 static int
57 init_port(const char *s, int fallback)
59 int tmp;
61 if (s && sscanf(s, "%d", &tmp) == 1)
62 return htons(tmp);
63 return fallback;
66 struct send_via_plugin_s {
67 krb5_const_realm realm;
68 krb5_krbhst_info *hi;
69 time_t timeout;
70 const krb5_data *send_data;
71 krb5_data *receive;
75 static krb5_error_code KRB5_LIB_CALL
76 kdccallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
78 const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
79 struct send_via_plugin_s *ctx = userctx;
81 if (service->send_to_kdc == NULL)
82 return KRB5_PLUGIN_NO_HANDLE;
83 return service->send_to_kdc(context, plugctx, ctx->hi, ctx->timeout,
84 ctx->send_data, ctx->receive);
87 static krb5_error_code KRB5_LIB_CALL
88 realmcallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
90 const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
91 struct send_via_plugin_s *ctx = userctx;
93 if (service->send_to_realm == NULL)
94 return KRB5_PLUGIN_NO_HANDLE;
95 return service->send_to_realm(context, plugctx, ctx->realm, ctx->timeout,
96 ctx->send_data, ctx->receive);
99 static krb5_error_code
100 kdc_via_plugin(krb5_context context,
101 krb5_krbhst_info *hi,
102 time_t timeout,
103 const krb5_data *send_data,
104 krb5_data *receive)
106 struct send_via_plugin_s userctx;
108 userctx.realm = NULL;
109 userctx.hi = hi;
110 userctx.timeout = timeout;
111 userctx.send_data = send_data;
112 userctx.receive = receive;
114 return _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC,
115 KRB5_PLUGIN_SEND_TO_KDC_VERSION_0, 0,
116 &userctx, kdccallback);
119 static krb5_error_code
120 realm_via_plugin(krb5_context context,
121 krb5_const_realm realm,
122 time_t timeout,
123 const krb5_data *send_data,
124 krb5_data *receive)
126 struct send_via_plugin_s userctx;
128 userctx.realm = realm;
129 userctx.hi = NULL;
130 userctx.timeout = timeout;
131 userctx.send_data = send_data;
132 userctx.receive = receive;
134 return _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC,
135 KRB5_PLUGIN_SEND_TO_KDC_VERSION_2, 0,
136 &userctx, realmcallback);
139 struct krb5_sendto_ctx_data {
140 int flags;
141 int type;
142 krb5_sendto_ctx_func func;
143 void *data;
144 char *hostname;
145 krb5_krbhst_handle krbhst;
147 /* context2 */
148 const krb5_data *send_data;
149 krb5_data response;
150 heim_array_t hosts;
151 int stateflags;
152 #define KRBHST_COMPLETED 1
154 /* prexmit */
155 krb5_sendto_prexmit prexmit_func;
156 void *prexmit_ctx;
158 /* stats */
159 struct {
160 struct timeval start_time;
161 struct timeval name_resolution;
162 struct timeval krbhst;
163 unsigned long sent_packets;
164 unsigned long num_hosts;
165 } stats;
166 unsigned int stid;
169 static void
170 dealloc_sendto_ctx(void *ptr)
172 krb5_sendto_ctx ctx = (krb5_sendto_ctx)ptr;
173 if (ctx->hostname)
174 free(ctx->hostname);
175 heim_release(ctx->hosts);
176 heim_release(ctx->krbhst);
179 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
180 krb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx)
182 *ctx = heim_alloc(sizeof(**ctx), "sendto-context", dealloc_sendto_ctx);
183 if (*ctx == NULL)
184 return krb5_enomem(context);
185 (*ctx)->hosts = heim_array_create();
187 return 0;
190 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
191 krb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx, int flags)
193 ctx->flags |= flags;
196 KRB5_LIB_FUNCTION int KRB5_LIB_CALL
197 krb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx)
199 return ctx->flags;
202 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
203 krb5_sendto_ctx_set_type(krb5_sendto_ctx ctx, int type)
205 ctx->type = type;
208 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
209 krb5_sendto_ctx_set_func(krb5_sendto_ctx ctx,
210 krb5_sendto_ctx_func func,
211 void *data)
213 ctx->func = func;
214 ctx->data = data;
217 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
218 _krb5_sendto_ctx_set_prexmit(krb5_sendto_ctx ctx,
219 krb5_sendto_prexmit prexmit,
220 void *data)
222 ctx->prexmit_func = prexmit;
223 ctx->prexmit_ctx = data;
226 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
227 krb5_sendto_set_hostname(krb5_context context,
228 krb5_sendto_ctx ctx,
229 const char *hostname)
231 if (ctx->hostname == NULL)
232 free(ctx->hostname);
233 ctx->hostname = strdup(hostname);
234 if (ctx->hostname == NULL) {
235 krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
236 return ENOMEM;
238 return 0;
241 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
242 _krb5_sendto_ctx_set_krb5hst(krb5_context context,
243 krb5_sendto_ctx ctx,
244 krb5_krbhst_handle handle)
246 heim_release(ctx->krbhst);
247 ctx->krbhst = heim_retain(handle);
250 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
251 krb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx)
253 heim_release(ctx);
256 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
257 _krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data,
258 const krb5_data *reply, int *action)
260 krb5_error_code ret;
261 KRB_ERROR error;
263 if(krb5_rd_error(context, reply, &error))
264 return 0;
266 ret = krb5_error_from_rd_error(context, &error, NULL);
267 krb5_free_error_contents(context, &error);
269 switch(ret) {
270 case KRB5KRB_ERR_RESPONSE_TOO_BIG: {
271 if (krb5_sendto_ctx_get_flags(ctx) & KRB5_KRBHST_FLAGS_LARGE_MSG)
272 break;
273 krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG);
274 *action = KRB5_SENDTO_RESET;
275 break;
277 case KRB5KDC_ERR_SVC_UNAVAILABLE:
278 *action = KRB5_SENDTO_CONTINUE;
279 break;
281 return 0;
288 struct host;
290 struct host_fun {
291 krb5_error_code (*prepare)(krb5_context, struct host *, const krb5_data *);
292 krb5_error_code (*send_fn)(krb5_context, struct host *);
293 krb5_error_code (*recv_fn)(krb5_context, struct host *, krb5_data *);
294 int ntries;
297 struct host {
298 enum host_state { CONNECT, CONNECTING, CONNECTED, WAITING_REPLY, DEAD } state;
299 krb5_krbhst_info *hi;
300 struct addrinfo *ai;
301 rk_socket_t fd;
302 struct host_fun *fun;
303 unsigned int tries;
304 time_t timeout;
305 krb5_data data;
306 unsigned int tid;
309 static void
310 debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
311 __attribute__ ((__format__ (__printf__, 4, 5)));
313 static void
314 debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
316 const char *proto = "unknown";
317 char name[NI_MAXHOST], port[NI_MAXSERV];
318 char *text = NULL;
319 va_list ap;
320 int ret;
322 if (!_krb5_have_debug(context, 5))
323 return;
325 va_start(ap, fmt);
326 ret = vasprintf(&text, fmt, ap);
327 va_end(ap);
328 if (ret == -1 || text == NULL)
329 return;
331 if (host->hi->proto == KRB5_KRBHST_HTTP)
332 proto = "http";
333 else if (host->hi->proto == KRB5_KRBHST_TCP)
334 proto = "tcp";
335 else if (host->hi->proto == KRB5_KRBHST_UDP)
336 proto = "udp";
338 if (getnameinfo(host->ai->ai_addr, host->ai->ai_addrlen,
339 name, sizeof(name), port, sizeof(port), NI_NUMERICHOST) != 0)
340 name[0] = '\0';
342 _krb5_debug(context, level, "%s: %s %s:%s (%s) tid: %08x", text,
343 proto, name, port, host->hi->hostname, host->tid);
344 free(text);
348 static void
349 deallocate_host(void *ptr)
351 struct host *host = ptr;
352 if (!rk_IS_BAD_SOCKET(host->fd))
353 rk_closesocket(host->fd);
354 krb5_data_free(&host->data);
355 host->ai = NULL;
358 static void
359 host_dead(krb5_context context, struct host *host, const char *msg)
361 debug_host(context, 5, host, "%s", msg);
362 rk_closesocket(host->fd);
363 host->fd = rk_INVALID_SOCKET;
364 host->state = DEAD;
367 static krb5_error_code
368 send_stream(krb5_context context, struct host *host)
370 ssize_t len;
372 len = krb5_net_write(context, &host->fd, host->data.data, host->data.length);
374 if (len < 0)
375 return errno;
376 else if (len < host->data.length) {
377 host->data.length -= len;
378 memmove(host->data.data, ((uint8_t *)host->data.data) + len, host->data.length - len);
379 return -1;
380 } else {
381 krb5_data_free(&host->data);
382 return 0;
386 static krb5_error_code
387 recv_stream(krb5_context context, struct host *host)
389 krb5_error_code ret;
390 size_t oldlen;
391 ssize_t sret;
392 int nbytes;
394 if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
395 return HEIM_NET_CONN_REFUSED;
397 if (context->max_msg_size - host->data.length < nbytes) {
398 krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
399 N_("TCP message from KDC too large %d", ""),
400 (int)(host->data.length + nbytes));
401 return KRB5KRB_ERR_FIELD_TOOLONG;
404 oldlen = host->data.length;
406 ret = krb5_data_realloc(&host->data, oldlen + nbytes + 1 /* NUL */);
407 if (ret)
408 return ret;
410 sret = krb5_net_read(context, &host->fd, ((uint8_t *)host->data.data) + oldlen, nbytes);
411 if (sret <= 0) {
412 ret = errno;
413 return ret;
415 host->data.length = oldlen + sret;
416 /* zero terminate for http transport */
417 ((uint8_t *)host->data.data)[host->data.length] = '\0';
419 return 0;
426 static void
427 host_next_timeout(krb5_context context, struct host *host)
429 host->timeout = context->kdc_timeout / host->fun->ntries;
430 if (host->timeout == 0)
431 host->timeout = 1;
433 host->timeout += time(NULL);
437 * connected host
440 static void
441 host_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
443 krb5_error_code ret;
445 host->state = CONNECTED;
447 * Now prepare data to send to host
449 if (ctx->prexmit_func) {
450 krb5_data data;
452 krb5_data_zero(&data);
454 ret = ctx->prexmit_func(context, host->hi->proto,
455 ctx->prexmit_ctx, host->fd, &data);
456 if (ret == 0) {
457 if (data.length == 0) {
458 host_dead(context, host, "prexmit function didn't send data");
459 return;
461 ret = host->fun->prepare(context, host, &data);
462 krb5_data_free(&data);
465 } else {
466 ret = host->fun->prepare(context, host, ctx->send_data);
468 if (ret)
469 debug_host(context, 5, host, "failed to prexmit/prepare");
473 * connect host
476 static void
477 host_connect(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
479 krb5_krbhst_info *hi = host->hi;
480 struct addrinfo *ai = host->ai;
482 debug_host(context, 5, host, "connecting to host");
484 if (connect(host->fd, ai->ai_addr, ai->ai_addrlen) < 0) {
485 #ifdef HAVE_WINSOCK
486 if (WSAGetLastError() == WSAEWOULDBLOCK)
487 errno = EINPROGRESS;
488 #endif /* HAVE_WINSOCK */
489 if (errno == EINPROGRESS && (hi->proto == KRB5_KRBHST_HTTP || hi->proto == KRB5_KRBHST_TCP)) {
490 debug_host(context, 5, host, "connecting to %d", host->fd);
491 host->state = CONNECTING;
492 } else {
493 host_dead(context, host, "failed to connect");
495 } else {
496 host_connected(context, ctx, host);
499 host_next_timeout(context, host);
503 * HTTP transport
506 static krb5_error_code
507 prepare_http(krb5_context context, struct host *host, const krb5_data *data)
509 char *str = NULL, *request = NULL;
510 krb5_error_code ret;
511 int len;
513 heim_assert(host->data.length == 0, "prepare_http called twice");
515 len = rk_base64_encode(data->data, data->length, &str);
516 if(len < 0)
517 return ENOMEM;
519 if (context->http_proxy)
520 ret = asprintf(&request, "GET http://%s/%s HTTP/1.0\r\n\r\n", host->hi->hostname, str);
521 else
522 ret = asprintf(&request, "GET /%s HTTP/1.0\r\n\r\n", str);
523 free(str);
524 if(ret < 0 || request == NULL)
525 return ENOMEM;
527 host->data.data = request;
528 host->data.length = strlen(request);
530 return 0;
533 static krb5_error_code
534 recv_http(krb5_context context, struct host *host, krb5_data *data)
536 krb5_error_code ret;
537 unsigned long rep_len;
538 size_t len;
539 char *p;
542 * recv_stream returns a NUL terminated stream
545 ret = recv_stream(context, host);
546 if (ret)
547 return ret;
549 p = strstr(host->data.data, "\r\n\r\n");
550 if (p == NULL)
551 return -1;
552 p += 4;
554 len = host->data.length - (p - (char *)host->data.data);
555 if (len < 4)
556 return -1;
558 _krb5_get_int(p, &rep_len, 4);
559 if (len < rep_len)
560 return -1;
562 p += 4;
564 memmove(host->data.data, p, rep_len);
565 host->data.length = rep_len;
567 *data = host->data;
568 krb5_data_zero(&host->data);
570 return 0;
574 * TCP transport
577 static krb5_error_code
578 prepare_tcp(krb5_context context, struct host *host, const krb5_data *data)
580 krb5_error_code ret;
581 krb5_storage *sp;
583 heim_assert(host->data.length == 0, "prepare_tcp called twice");
585 sp = krb5_storage_emem();
586 if (sp == NULL)
587 return ENOMEM;
589 ret = krb5_store_data(sp, *data);
590 if (ret) {
591 krb5_storage_free(sp);
592 return ret;
594 ret = krb5_storage_to_data(sp, &host->data);
595 krb5_storage_free(sp);
597 return ret;
600 static krb5_error_code
601 recv_tcp(krb5_context context, struct host *host, krb5_data *data)
603 krb5_error_code ret;
604 unsigned long pktlen;
606 ret = recv_stream(context, host);
607 if (ret)
608 return ret;
610 if (host->data.length < 4)
611 return -1;
613 _krb5_get_int(host->data.data, &pktlen, 4);
615 if (pktlen > host->data.length - 4)
616 return -1;
618 memmove(host->data.data, ((uint8_t *)host->data.data) + 4, host->data.length - 4);
619 host->data.length -= 4;
621 *data = host->data;
622 krb5_data_zero(&host->data);
624 return 0;
628 * UDP transport
631 static krb5_error_code
632 prepare_udp(krb5_context context, struct host *host, const krb5_data *data)
634 return krb5_data_copy(&host->data, data->data, data->length);
637 static krb5_error_code
638 send_udp(krb5_context context, struct host *host)
640 if (send(host->fd, host->data.data, host->data.length, 0) < 0)
641 return errno;
642 return 0;
645 static krb5_error_code
646 recv_udp(krb5_context context, struct host *host, krb5_data *data)
648 krb5_error_code ret;
649 int nbytes;
652 if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
653 return HEIM_NET_CONN_REFUSED;
655 if (context->max_msg_size < nbytes) {
656 krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
657 N_("UDP message from KDC too large %d", ""),
658 (int)nbytes);
659 return KRB5KRB_ERR_FIELD_TOOLONG;
662 ret = krb5_data_alloc(data, nbytes);
663 if (ret)
664 return ret;
666 ret = recv(host->fd, data->data, data->length, 0);
667 if (ret < 0) {
668 ret = errno;
669 krb5_data_free(data);
670 return ret;
672 data->length = ret;
674 return 0;
677 static struct host_fun http_fun = {
678 prepare_http,
679 send_stream,
680 recv_http,
683 static struct host_fun tcp_fun = {
684 prepare_tcp,
685 send_stream,
686 recv_tcp,
689 static struct host_fun udp_fun = {
690 prepare_udp,
691 send_udp,
692 recv_udp,
698 * Host state machine
701 static int
702 eval_host_state(krb5_context context,
703 krb5_sendto_ctx ctx,
704 struct host *host,
705 int readable, int writeable)
707 krb5_error_code ret;
709 if (host->state == CONNECT) {
710 /* check if its this host time to connect */
711 if (host->timeout < time(NULL))
712 host_connect(context, ctx, host);
713 return 0;
716 if (host->state == CONNECTING && writeable)
717 host_connected(context, ctx, host);
719 if (readable) {
721 debug_host(context, 5, host, "reading packet");
723 ret = host->fun->recv_fn(context, host, &ctx->response);
724 if (ret == -1) {
725 /* not done yet */
726 } else if (ret == 0) {
727 /* if recv_foo function returns 0, we have a complete reply */
728 debug_host(context, 5, host, "host completed");
729 return 1;
730 } else {
731 host_dead(context, host, "host disconnected");
735 /* check if there is anything to send, state might DEAD after read */
736 if (writeable && host->state == CONNECTED) {
738 ctx->stats.sent_packets++;
740 debug_host(context, 5, host, "writing packet");
742 ret = host->fun->send_fn(context, host);
743 if (ret == -1) {
744 /* not done yet */
745 } else if (ret) {
746 host_dead(context, host, "host dead, write failed");
747 } else
748 host->state = WAITING_REPLY;
751 return 0;
758 static krb5_error_code
759 submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi)
761 unsigned long submitted_host = 0;
762 krb5_boolean freeai = FALSE;
763 struct timeval nrstart, nrstop;
764 krb5_error_code ret;
765 struct addrinfo *ai = NULL, *a;
766 struct host *host;
768 ret = kdc_via_plugin(context, hi, context->kdc_timeout,
769 ctx->send_data, &ctx->response);
770 if (ret == 0) {
771 return 0;
772 } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
773 _krb5_debug(context, 5, "send via plugin failed %s: %d",
774 hi->hostname, ret);
775 return ret;
779 * If we have a proxy, let use the address of the proxy instead of
780 * the KDC and let the proxy deal with the resolving of the KDC.
783 gettimeofday(&nrstart, NULL);
785 if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) {
786 char *proxy2 = strdup(context->http_proxy);
787 char *el, *proxy = proxy2;
788 struct addrinfo hints;
789 char portstr[NI_MAXSERV];
790 unsigned short nport;
792 if (proxy == NULL)
793 return ENOMEM;
794 if (strncmp(proxy, "http://", 7) == 0)
795 proxy += 7;
797 /* check for url terminating slash */
798 el = strchr(proxy, '/');
799 if (el != NULL)
800 *el = '\0';
802 /* check for port in hostname, used below as port */
803 el = strchr(proxy, ':');
804 if(el != NULL)
805 *el++ = '\0';
807 memset(&hints, 0, sizeof(hints));
808 hints.ai_family = PF_UNSPEC;
809 hints.ai_socktype = SOCK_STREAM;
811 /* On some systems ntohs(foo(..., htons(...))) causes shadowing */
812 nport = init_port(el, htons(80));
813 snprintf(portstr, sizeof(portstr), "%d", ntohs(nport));
815 ret = getaddrinfo(proxy, portstr, &hints, &ai);
816 free(proxy2);
817 if (ret)
818 return krb5_eai_to_heim_errno(ret, errno);
820 freeai = TRUE;
822 } else {
823 ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
824 if (ret)
825 return ret;
828 /* add up times */
829 gettimeofday(&nrstop, NULL);
830 timevalsub(&nrstop, &nrstart);
831 timevaladd(&ctx->stats.name_resolution, &nrstop);
833 ctx->stats.num_hosts++;
835 for (a = ai; a != NULL; a = a->ai_next) {
836 rk_socket_t fd;
838 fd = socket(a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);
839 if (rk_IS_BAD_SOCKET(fd))
840 continue;
841 rk_cloexec(fd);
843 #ifndef NO_LIMIT_FD_SETSIZE
844 if (fd >= FD_SETSIZE) {
845 _krb5_debug(context, 0, "fd too large for select");
846 rk_closesocket(fd);
847 continue;
849 #endif
850 socket_set_nonblocking(fd, 1);
852 host = heim_alloc(sizeof(*host), "sendto-host", deallocate_host);
853 if (host == NULL) {
854 if (freeai)
855 freeaddrinfo(ai);
856 rk_closesocket(fd);
857 return ENOMEM;
859 host->hi = hi;
860 host->fd = fd;
861 host->ai = a;
862 /* next version of stid */
863 host->tid = ctx->stid = (ctx->stid & 0xffff0000) | ((ctx->stid & 0xffff) + 1);
865 host->state = CONNECT;
867 switch (host->hi->proto) {
868 case KRB5_KRBHST_HTTP :
869 host->fun = &http_fun;
870 break;
871 case KRB5_KRBHST_TCP :
872 host->fun = &tcp_fun;
873 break;
874 case KRB5_KRBHST_UDP :
875 host->fun = &udp_fun;
876 break;
877 default:
878 heim_abort("undefined http transport protocol: %d", (int)host->hi->proto);
881 host->tries = host->fun->ntries;
884 * Connect directly next host, wait a host_timeout for each next address
886 if (submitted_host == 0)
887 host_connect(context, ctx, host);
888 else {
889 debug_host(context, 5, host,
890 "Queuing host in future (in %ds), its the %lu address on the same name",
891 (int)(context->host_timeout * submitted_host), submitted_host + 1);
892 host->timeout = time(NULL) + (submitted_host * context->host_timeout);
895 heim_array_append_value(ctx->hosts, host);
897 heim_release(host);
899 submitted_host++;
902 if (freeai)
903 freeaddrinfo(ai);
905 if (!submitted_host)
906 return KRB5_KDC_UNREACH;
908 return 0;
911 struct wait_ctx {
912 krb5_context context;
913 krb5_sendto_ctx ctx;
914 fd_set rfds;
915 fd_set wfds;
916 unsigned max_fd;
917 int got_reply;
918 time_t timenow;
921 static void
922 wait_setup(heim_object_t obj, void *iter_ctx, int *stop)
924 struct wait_ctx *wait_ctx = iter_ctx;
925 struct host *h = (struct host *)obj;
927 /* skip dead hosts */
928 if (h->state == DEAD)
929 return;
931 if (h->state == CONNECT) {
932 if (h->timeout < wait_ctx->timenow)
933 host_connect(wait_ctx->context, wait_ctx->ctx, h);
934 return;
937 /* if host timed out, dec tries and (retry or kill host) */
938 if (h->timeout < wait_ctx->timenow) {
939 heim_assert(h->tries != 0, "tries should not reach 0");
940 h->tries--;
941 if (h->tries == 0) {
942 host_dead(wait_ctx->context, h, "host timed out");
943 return;
944 } else {
945 debug_host(wait_ctx->context, 5, h, "retrying sending to");
946 host_next_timeout(wait_ctx->context, h);
947 host_connected(wait_ctx->context, wait_ctx->ctx, h);
951 #ifndef NO_LIMIT_FD_SETSIZE
952 heim_assert(h->fd < FD_SETSIZE, "fd too large");
953 #endif
954 switch (h->state) {
955 case WAITING_REPLY:
956 FD_SET(h->fd, &wait_ctx->rfds);
957 break;
958 case CONNECTING:
959 case CONNECTED:
960 FD_SET(h->fd, &wait_ctx->rfds);
961 FD_SET(h->fd, &wait_ctx->wfds);
962 break;
963 default:
964 heim_abort("invalid sendto host state");
966 if (h->fd > wait_ctx->max_fd)
967 wait_ctx->max_fd = h->fd;
970 static int
971 wait_filter_dead(heim_object_t obj, void *ctx)
973 struct host *h = (struct host *)obj;
974 return (int)((h->state == DEAD) ? true : false);
977 static void
978 wait_process(heim_object_t obj, void *ctx, int *stop)
980 struct wait_ctx *wait_ctx = ctx;
981 struct host *h = (struct host *)obj;
982 int readable, writeable;
983 heim_assert(h->state != DEAD, "dead host resurected");
985 #ifndef NO_LIMIT_FD_SETSIZE
986 heim_assert(h->fd < FD_SETSIZE, "fd too large");
987 #endif
988 readable = FD_ISSET(h->fd, &wait_ctx->rfds);
989 writeable = FD_ISSET(h->fd, &wait_ctx->wfds);
991 if (readable || writeable || h->state == CONNECT)
992 wait_ctx->got_reply |= eval_host_state(wait_ctx->context, wait_ctx->ctx, h, readable, writeable);
994 /* if there is already a reply, just fall though the array */
995 if (wait_ctx->got_reply)
996 *stop = 1;
999 static krb5_error_code
1000 wait_response(krb5_context context, int *action, krb5_sendto_ctx ctx)
1002 struct wait_ctx wait_ctx;
1003 struct timeval tv;
1004 int ret;
1006 wait_ctx.context = context;
1007 wait_ctx.ctx = ctx;
1008 FD_ZERO(&wait_ctx.rfds);
1009 FD_ZERO(&wait_ctx.wfds);
1010 wait_ctx.max_fd = 0;
1012 /* oh, we have a reply, it must be a plugin that got it for us */
1013 if (ctx->response.length) {
1014 *action = KRB5_SENDTO_FILTER;
1015 return 0;
1018 wait_ctx.timenow = time(NULL);
1020 heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_setup);
1021 heim_array_filter_f(ctx->hosts, &wait_ctx, wait_filter_dead);
1023 if (heim_array_get_length(ctx->hosts) == 0) {
1024 if (ctx->stateflags & KRBHST_COMPLETED) {
1025 _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
1026 "trying to pulling more hosts");
1027 *action = KRB5_SENDTO_FAILED;
1028 } else {
1029 _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
1030 "and no more hosts -> failure");
1031 *action = KRB5_SENDTO_TIMEOUT;
1033 return 0;
1036 tv.tv_sec = 1;
1037 tv.tv_usec = 0;
1039 ret = select(wait_ctx.max_fd + 1, &wait_ctx.rfds, &wait_ctx.wfds, NULL, &tv);
1040 if (ret < 0)
1041 return errno;
1042 if (ret == 0) {
1043 *action = KRB5_SENDTO_TIMEOUT;
1044 return 0;
1047 wait_ctx.got_reply = 0;
1048 heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_process);
1049 if (wait_ctx.got_reply)
1050 *action = KRB5_SENDTO_FILTER;
1051 else
1052 *action = KRB5_SENDTO_CONTINUE;
1054 return 0;
1057 static void
1058 reset_context(krb5_context context, krb5_sendto_ctx ctx)
1060 krb5_data_free(&ctx->response);
1061 heim_release(ctx->hosts);
1062 ctx->hosts = heim_array_create();
1063 ctx->stateflags = 0;
1071 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1072 krb5_sendto_context(krb5_context context,
1073 krb5_sendto_ctx ctx,
1074 const krb5_data *send_data,
1075 krb5_const_realm realm,
1076 krb5_data *receive)
1078 krb5_error_code ret = 0;
1079 krb5_krbhst_handle handle = NULL;
1080 struct timeval nrstart, nrstop, stop_time;
1081 int type, freectx = 0;
1082 int action;
1083 int numreset = 0;
1085 krb5_data_zero(receive);
1087 if (ctx == NULL) {
1088 ret = krb5_sendto_ctx_alloc(context, &ctx);
1089 if (ret)
1090 goto out;
1091 freectx = 1;
1094 ctx->stid = (context->num_kdc_requests++) << 16;
1096 memset(&ctx->stats, 0, sizeof(ctx->stats));
1097 gettimeofday(&ctx->stats.start_time, NULL);
1099 type = ctx->type;
1100 if (type == 0) {
1101 if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc)
1102 type = KRB5_KRBHST_ADMIN;
1103 else
1104 type = KRB5_KRBHST_KDC;
1107 ctx->send_data = send_data;
1109 if ((int)send_data->length > context->large_msg_size)
1110 ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG;
1112 /* loop until we get back a appropriate response */
1114 action = KRB5_SENDTO_INITIAL;
1116 while (action != KRB5_SENDTO_DONE && action != KRB5_SENDTO_FAILED) {
1117 krb5_krbhst_info *hi;
1119 switch (action) {
1120 case KRB5_SENDTO_INITIAL:
1121 ret = realm_via_plugin(context, realm, context->kdc_timeout,
1122 send_data, &ctx->response);
1123 if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) {
1124 action = KRB5_SENDTO_DONE;
1125 break;
1127 action = KRB5_SENDTO_KRBHST;
1128 /* FALLTHOUGH */
1129 case KRB5_SENDTO_KRBHST:
1130 if (ctx->krbhst == NULL) {
1131 ret = krb5_krbhst_init_flags(context, realm, type,
1132 ctx->flags, &handle);
1133 if (ret)
1134 goto out;
1136 if (ctx->hostname) {
1137 ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname);
1138 if (ret)
1139 goto out;
1142 } else {
1143 handle = heim_retain(ctx->krbhst);
1145 action = KRB5_SENDTO_TIMEOUT;
1146 /* FALLTHOUGH */
1147 case KRB5_SENDTO_TIMEOUT:
1150 * If we completed, just got to next step
1153 if (ctx->stateflags & KRBHST_COMPLETED) {
1154 action = KRB5_SENDTO_CONTINUE;
1155 break;
1159 * Pull out next host, if there is no more, close the
1160 * handle and mark as completed.
1162 * Collect time spent in krbhst (dns, plugin, etc)
1166 gettimeofday(&nrstart, NULL);
1168 ret = krb5_krbhst_next(context, handle, &hi);
1170 gettimeofday(&nrstop, NULL);
1171 timevalsub(&nrstop, &nrstart);
1172 timevaladd(&ctx->stats.krbhst, &nrstop);
1174 action = KRB5_SENDTO_CONTINUE;
1175 if (ret == 0) {
1176 _krb5_debug(context, 5, "submissing new requests to new host");
1177 if (submit_request(context, ctx, hi) != 0)
1178 action = KRB5_SENDTO_TIMEOUT;
1179 } else {
1180 _krb5_debug(context, 5, "out of hosts, waiting for replies");
1181 ctx->stateflags |= KRBHST_COMPLETED;
1184 break;
1185 case KRB5_SENDTO_CONTINUE:
1187 ret = wait_response(context, &action, ctx);
1188 if (ret)
1189 goto out;
1191 break;
1192 case KRB5_SENDTO_RESET:
1193 /* start over */
1194 _krb5_debug(context, 5,
1195 "krb5_sendto trying over again (reset): %d",
1196 numreset);
1197 reset_context(context, ctx);
1198 if (handle) {
1199 krb5_krbhst_free(context, handle);
1200 handle = NULL;
1202 numreset++;
1203 if (numreset >= 3)
1204 action = KRB5_SENDTO_FAILED;
1205 else
1206 action = KRB5_SENDTO_KRBHST;
1208 break;
1209 case KRB5_SENDTO_FILTER:
1210 /* default to next state, the filter function might modify this */
1211 action = KRB5_SENDTO_DONE;
1213 if (ctx->func) {
1214 ret = (*ctx->func)(context, ctx, ctx->data,
1215 &ctx->response, &action);
1216 if (ret)
1217 goto out;
1219 break;
1220 case KRB5_SENDTO_FAILED:
1221 ret = KRB5_KDC_UNREACH;
1222 break;
1223 case KRB5_SENDTO_DONE:
1224 ret = 0;
1225 break;
1226 default:
1227 heim_abort("invalid krb5_sendto_context state");
1231 out:
1232 gettimeofday(&stop_time, NULL);
1233 timevalsub(&stop_time, &ctx->stats.start_time);
1234 if (ret == 0 && ctx->response.length) {
1235 *receive = ctx->response;
1236 krb5_data_zero(&ctx->response);
1237 } else {
1238 krb5_data_free(&ctx->response);
1239 krb5_clear_error_message (context);
1240 ret = KRB5_KDC_UNREACH;
1241 krb5_set_error_message(context, ret,
1242 N_("unable to reach any KDC in realm %s", ""),
1243 realm);
1246 _krb5_debug(context, 1,
1247 "krb5_sendto_context %s done: %d hosts %lu packets %lu wc: %ld.%06ld nr: %ld.%06ld kh: %ld.%06ld tid: %08x",
1248 realm, ret,
1249 ctx->stats.num_hosts, ctx->stats.sent_packets,
1250 stop_time.tv_sec, (long)stop_time.tv_usec,
1251 ctx->stats.name_resolution.tv_sec, (long)ctx->stats.name_resolution.tv_usec,
1252 ctx->stats.krbhst.tv_sec, (long)ctx->stats.krbhst.tv_usec, ctx->stid);
1255 if (freectx)
1256 krb5_sendto_ctx_free(context, ctx);
1257 else
1258 reset_context(context, ctx);
1260 if (handle)
1261 krb5_krbhst_free(context, handle);
1263 return ret;