resurrect password change support again
[heimdal.git] / lib / krb5 / send_to_kdc.c
blobfd8558f373daed1b23747da331e74de31252b568
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 if (s) {
60 int tmp;
62 sscanf (s, "%d", &tmp);
63 return htons(tmp);
64 } else
65 return fallback;
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;
77 static krb5_error_code KRB5_LIB_CALL
78 kdccallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
80 const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
81 struct send_via_plugin_s *ctx = userctx;
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);
89 static krb5_error_code KRB5_LIB_CALL
90 realmcallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
92 const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
93 struct send_via_plugin_s *ctx = userctx;
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);
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)
108 struct send_via_plugin_s userctx;
110 userctx.realm = NULL;
111 userctx.hi = hi;
112 userctx.timeout = timeout;
113 userctx.send_data = send_data;
114 userctx.receive = receive;
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);
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)
128 struct send_via_plugin_s userctx;
130 userctx.realm = realm;
131 userctx.hi = NULL;
132 userctx.timeout = timeout;
133 userctx.send_data = send_data;
134 userctx.receive = receive;
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);
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;
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
156 /* prexmit */
157 krb5_sendto_prexmit prexmit_func;
158 void *prexmit_ctx;
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;
171 static void
172 dealloc_sendto_ctx(void *ptr)
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);
181 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
182 krb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx)
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();
189 return 0;
192 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
193 krb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx, int flags)
195 ctx->flags |= flags;
198 KRB5_LIB_FUNCTION int KRB5_LIB_CALL
199 krb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx)
201 return ctx->flags;
204 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
205 krb5_sendto_ctx_set_type(krb5_sendto_ctx ctx, int type)
207 ctx->type = type;
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)
215 ctx->func = func;
216 ctx->data = data;
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)
224 ctx->prexmit_func = prexmit;
225 ctx->prexmit_ctx = data;
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)
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;
240 return 0;
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)
248 heim_release(ctx->krbhst);
249 ctx->krbhst = heim_retain(handle);
252 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
253 krb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx)
255 heim_release(ctx);
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)
262 krb5_error_code ret;
263 KRB_ERROR error;
265 if(krb5_rd_error(context, reply, &error))
266 return 0;
268 ret = krb5_error_from_rd_error(context, &error, NULL);
269 krb5_free_error_contents(context, &error);
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;
279 case KRB5KDC_ERR_SVC_UNAVAILABLE:
280 *action = KRB5_SENDTO_CONTINUE;
281 break;
283 return 0;
290 struct host;
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;
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;
311 static void
312 debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
313 __attribute__((__format__(__printf__, 4, 5)));
315 static void
316 debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
318 const char *proto = "unknown";
319 char name[NI_MAXHOST], port[NI_MAXSERV];
320 char *text = NULL;
321 va_list ap;
322 int ret;
324 if (!_krb5_have_debug(context, 5))
325 return;
327 va_start(ap, fmt);
328 ret = vasprintf(&text, fmt, ap);
329 va_end(ap);
330 if (ret == -1 || text == NULL)
331 return;
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";
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';
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);
350 static void
351 deallocate_host(void *ptr)
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;
360 static void
361 host_dead(krb5_context context, struct host *host, const char *msg)
363 debug_host(context, 5, host, "%s", msg);
364 rk_closesocket(host->fd);
365 host->fd = rk_INVALID_SOCKET;
366 host->state = DEAD;
369 static krb5_error_code
370 send_stream(krb5_context context, struct host *host)
372 ssize_t len;
374 len = krb5_net_write(context, &host->fd, host->data.data, host->data.length);
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;
388 static krb5_error_code
389 recv_stream(krb5_context context, struct host *host)
391 krb5_error_code ret;
392 size_t oldlen;
393 ssize_t sret;
394 int nbytes;
396 if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
397 return HEIM_NET_CONN_REFUSED;
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;
406 oldlen = host->data.length;
408 ret = krb5_data_realloc(&host->data, oldlen + nbytes + 1 /* NUL */);
409 if (ret)
410 return ret;
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;
417 host->data.length = oldlen + sret;
418 /* zero terminate for http transport */
419 ((uint8_t *)host->data.data)[host->data.length] = '\0';
421 return 0;
428 static void
429 host_next_timeout(krb5_context context, struct host *host)
431 host->timeout = context->kdc_timeout / host->fun->ntries;
432 if (host->timeout == 0)
433 host->timeout = 1;
435 host->timeout += time(NULL);
439 * connected host
442 static void
443 host_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
445 krb5_error_code ret;
447 host->state = CONNECTED;
449 * Now prepare data to send to host
451 if (ctx->prexmit_func) {
452 krb5_data data;
454 krb5_data_zero(&data);
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;
463 ret = host->fun->prepare(context, host, &data);
464 krb5_data_free(&data);
467 } else {
468 ret = host->fun->prepare(context, host, ctx->send_data);
470 if (ret)
471 debug_host(context, 5, host, "failed to prexmit/prepare");
475 * connect host
478 static void
479 host_connect(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
481 krb5_krbhst_info *hi = host->hi;
482 struct addrinfo *ai = host->ai;
484 debug_host(context, 5, host, "connecting to host");
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");
497 } else {
498 host_connected(context, ctx, host);
501 host_next_timeout(context, host);
505 * HTTP transport
508 static krb5_error_code
509 prepare_http(krb5_context context, struct host *host, const krb5_data *data)
511 char *str = NULL, *request = NULL;
512 krb5_error_code ret;
513 int len;
515 heim_assert(host->data.length == 0, "prepare_http called twice");
517 len = base64_encode(data->data, data->length, &str);
518 if(len < 0)
519 return ENOMEM;
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;
529 host->data.data = request;
530 host->data.length = strlen(request);
532 return 0;
535 static krb5_error_code
536 recv_http(krb5_context context, struct host *host, krb5_data *data)
538 krb5_error_code ret;
539 unsigned long rep_len;
540 size_t len;
541 char *p;
544 * recv_stream returns a NUL terminated stream
547 ret = recv_stream(context, host);
548 if (ret)
549 return ret;
551 p = strstr(host->data.data, "\r\n\r\n");
552 if (p == NULL)
553 return -1;
554 p += 4;
556 len = host->data.length - (p - (char *)host->data.data);
557 if (len < 4)
558 return -1;
560 _krb5_get_int(p, &rep_len, 4);
561 if (len < rep_len)
562 return -1;
564 p += 4;
566 memmove(host->data.data, p, rep_len);
567 host->data.length = rep_len;
569 *data = host->data;
570 krb5_data_zero(&host->data);
572 return 0;
576 * TCP transport
579 static krb5_error_code
580 prepare_tcp(krb5_context context, struct host *host, const krb5_data *data)
582 krb5_error_code ret;
583 krb5_storage *sp;
585 heim_assert(host->data.length == 0, "prepare_tcp called twice");
587 sp = krb5_storage_emem();
588 if (sp == NULL)
589 return ENOMEM;
591 ret = krb5_store_data(sp, *data);
592 if (ret) {
593 krb5_storage_free(sp);
594 return ret;
596 ret = krb5_storage_to_data(sp, &host->data);
597 krb5_storage_free(sp);
599 return ret;
602 static krb5_error_code
603 recv_tcp(krb5_context context, struct host *host, krb5_data *data)
605 krb5_error_code ret;
606 unsigned long pktlen;
608 ret = recv_stream(context, host);
609 if (ret)
610 return ret;
612 if (host->data.length < 4)
613 return -1;
615 _krb5_get_int(host->data.data, &pktlen, 4);
617 if (pktlen > host->data.length - 4)
618 return -1;
620 memmove(host->data.data, ((uint8_t *)host->data.data) + 4, host->data.length - 4);
621 host->data.length -= 4;
623 *data = host->data;
624 krb5_data_zero(&host->data);
626 return 0;
630 * UDP transport
633 static krb5_error_code
634 prepare_udp(krb5_context context, struct host *host, const krb5_data *data)
636 return krb5_data_copy(&host->data, data->data, data->length);
639 static krb5_error_code
640 send_udp(krb5_context context, struct host *host)
642 if (send(host->fd, host->data.data, host->data.length, 0) < 0)
643 return errno;
644 return 0;
647 static krb5_error_code
648 recv_udp(krb5_context context, struct host *host, krb5_data *data)
650 krb5_error_code ret;
651 int nbytes;
654 if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
655 return HEIM_NET_CONN_REFUSED;
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;
664 ret = krb5_data_alloc(data, nbytes);
665 if (ret)
666 return ret;
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;
674 data->length = ret;
676 return 0;
679 static struct host_fun http_fun = {
680 prepare_http,
681 send_stream,
682 recv_http,
685 static struct host_fun tcp_fun = {
686 prepare_tcp,
687 send_stream,
688 recv_tcp,
691 static struct host_fun udp_fun = {
692 prepare_udp,
693 send_udp,
694 recv_udp,
700 * Host state machine
703 static int
704 eval_host_state(krb5_context context,
705 krb5_sendto_ctx ctx,
706 struct host *host,
707 int readable, int writeable)
709 krb5_error_code ret;
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;
718 if (host->state == CONNECTING && writeable)
719 host_connected(context, ctx, host);
721 if (readable) {
723 debug_host(context, 5, host, "reading packet");
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");
737 /* check if there is anything to send, state might DEAD after read */
738 if (writeable && host->state == CONNECTED) {
740 ctx->stats.sent_packets++;
742 debug_host(context, 5, host, "writing packet");
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;
753 return 0;
760 static krb5_error_code
761 submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi)
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;
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;
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.
785 gettimeofday(&nrstart, NULL);
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];
793 if (proxy == NULL)
794 return ENOMEM;
795 if (strncmp(proxy, "http://", 7) == 0)
796 proxy += 7;
798 /* check for url terminating slash */
799 el = strchr(proxy, '/');
800 if (el != NULL)
801 *el = '\0';
803 /* check for port in hostname, used below as port */
804 el = strchr(proxy, ':');
805 if(el != NULL)
806 *el++ = '\0';
808 memset(&hints, 0, sizeof(hints));
809 hints.ai_family = PF_UNSPEC;
810 hints.ai_socktype = SOCK_STREAM;
812 snprintf(portstr, sizeof(portstr), "%d",
813 ntohs(init_port(el, htons(80))));
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 rk_closesocket(fd);
855 return ENOMEM;
857 host->hi = hi;
858 host->fd = fd;
859 host->ai = a;
860 /* next version of stid */
861 host->tid = ctx->stid = (ctx->stid & 0xffff0000) | ((ctx->stid & 0xffff) + 1);
863 host->state = CONNECT;
865 switch (host->hi->proto) {
866 case KRB5_KRBHST_HTTP :
867 host->fun = &http_fun;
868 break;
869 case KRB5_KRBHST_TCP :
870 host->fun = &tcp_fun;
871 break;
872 case KRB5_KRBHST_UDP :
873 host->fun = &udp_fun;
874 break;
875 default:
876 heim_abort("undefined http transport protocol: %d", (int)host->hi->proto);
879 host->tries = host->fun->ntries;
882 * Connect directly next host, wait a host_timeout for each next address
884 if (submitted_host == 0)
885 host_connect(context, ctx, host);
886 else {
887 debug_host(context, 5, host,
888 "Queuing host in future (in %ds), its the %lu address on the same name",
889 (int)(context->host_timeout * submitted_host), submitted_host + 1);
890 host->timeout = time(NULL) + (submitted_host * context->host_timeout);
893 heim_array_append_value(ctx->hosts, host);
895 heim_release(host);
897 submitted_host++;
900 if (freeai)
901 freeaddrinfo(ai);
903 if (!submitted_host)
904 return KRB5_KDC_UNREACH;
906 return 0;
909 struct wait_ctx {
910 krb5_context context;
911 krb5_sendto_ctx ctx;
912 fd_set rfds;
913 fd_set wfds;
914 unsigned max_fd;
915 int got_reply;
916 time_t timenow;
919 static void
920 wait_setup(heim_object_t obj, void *iter_ctx, int *stop)
922 struct wait_ctx *wait_ctx = iter_ctx;
923 struct host *h = (struct host *)obj;
925 /* skip dead hosts */
926 if (h->state == DEAD)
927 return;
929 if (h->state == CONNECT) {
930 if (h->timeout < wait_ctx->timenow)
931 host_connect(wait_ctx->context, wait_ctx->ctx, h);
932 return;
935 /* if host timed out, dec tries and (retry or kill host) */
936 if (h->timeout < wait_ctx->timenow) {
937 heim_assert(h->tries != 0, "tries should not reach 0");
938 h->tries--;
939 if (h->tries == 0) {
940 host_dead(wait_ctx->context, h, "host timed out");
941 return;
942 } else {
943 debug_host(wait_ctx->context, 5, h, "retrying sending to");
944 host_next_timeout(wait_ctx->context, h);
945 host_connected(wait_ctx->context, wait_ctx->ctx, h);
949 #ifndef NO_LIMIT_FD_SETSIZE
950 heim_assert(h->fd < FD_SETSIZE, "fd too large");
951 #endif
952 switch (h->state) {
953 case WAITING_REPLY:
954 FD_SET(h->fd, &wait_ctx->rfds);
955 break;
956 case CONNECTING:
957 case CONNECTED:
958 FD_SET(h->fd, &wait_ctx->rfds);
959 FD_SET(h->fd, &wait_ctx->wfds);
960 break;
961 default:
962 heim_abort("invalid sendto host state");
964 if (h->fd > wait_ctx->max_fd)
965 wait_ctx->max_fd = h->fd;
968 static int
969 wait_filter_dead(heim_object_t obj, void *ctx)
971 struct host *h = (struct host *)obj;
972 return (int)((h->state == DEAD) ? true : false);
975 static void
976 wait_process(heim_object_t obj, void *ctx, int *stop)
978 struct wait_ctx *wait_ctx = ctx;
979 struct host *h = (struct host *)obj;
980 int readable, writeable;
981 heim_assert(h->state != DEAD, "dead host resurected");
983 #ifndef NO_LIMIT_FD_SETSIZE
984 heim_assert(h->fd < FD_SETSIZE, "fd too large");
985 #endif
986 readable = FD_ISSET(h->fd, &wait_ctx->rfds);
987 writeable = FD_ISSET(h->fd, &wait_ctx->wfds);
989 if (readable || writeable || h->state == CONNECT)
990 wait_ctx->got_reply |= eval_host_state(wait_ctx->context, wait_ctx->ctx, h, readable, writeable);
992 /* if there is already a reply, just fall though the array */
993 if (wait_ctx->got_reply)
994 *stop = 1;
997 static krb5_error_code
998 wait_response(krb5_context context, int *action, krb5_sendto_ctx ctx)
1000 struct wait_ctx wait_ctx;
1001 struct timeval tv;
1002 int ret;
1004 wait_ctx.context = context;
1005 wait_ctx.ctx = ctx;
1006 FD_ZERO(&wait_ctx.rfds);
1007 FD_ZERO(&wait_ctx.wfds);
1008 wait_ctx.max_fd = 0;
1010 /* oh, we have a reply, it must be a plugin that got it for us */
1011 if (ctx->response.length) {
1012 *action = KRB5_SENDTO_FILTER;
1013 return 0;
1016 wait_ctx.timenow = time(NULL);
1018 heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_setup);
1019 heim_array_filter_f(ctx->hosts, &wait_ctx, wait_filter_dead);
1021 if (heim_array_get_length(ctx->hosts) == 0) {
1022 if (ctx->stateflags & KRBHST_COMPLETED) {
1023 _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
1024 "trying to pulling more hosts");
1025 *action = KRB5_SENDTO_FAILED;
1026 } else {
1027 _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
1028 "and no more hosts -> failure");
1029 *action = KRB5_SENDTO_TIMEOUT;
1031 return 0;
1034 tv.tv_sec = 1;
1035 tv.tv_usec = 0;
1037 ret = select(wait_ctx.max_fd + 1, &wait_ctx.rfds, &wait_ctx.wfds, NULL, &tv);
1038 if (ret < 0)
1039 return errno;
1040 if (ret == 0) {
1041 *action = KRB5_SENDTO_TIMEOUT;
1042 return 0;
1045 wait_ctx.got_reply = 0;
1046 heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_process);
1047 if (wait_ctx.got_reply)
1048 *action = KRB5_SENDTO_FILTER;
1049 else
1050 *action = KRB5_SENDTO_CONTINUE;
1052 return 0;
1055 static void
1056 reset_context(krb5_context context, krb5_sendto_ctx ctx)
1058 krb5_data_free(&ctx->response);
1059 heim_release(ctx->hosts);
1060 ctx->hosts = heim_array_create();
1061 ctx->stateflags = 0;
1069 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1070 krb5_sendto_context(krb5_context context,
1071 krb5_sendto_ctx ctx,
1072 const krb5_data *send_data,
1073 krb5_const_realm realm,
1074 krb5_data *receive)
1076 krb5_error_code ret = 0;
1077 krb5_krbhst_handle handle = NULL;
1078 struct timeval nrstart, nrstop, stop_time;
1079 int type, freectx = 0;
1080 int action;
1081 int numreset = 0;
1083 krb5_data_zero(receive);
1085 if (ctx == NULL) {
1086 ret = krb5_sendto_ctx_alloc(context, &ctx);
1087 if (ret)
1088 goto out;
1089 freectx = 1;
1092 ctx->stid = (context->num_kdc_requests++) << 16;
1094 memset(&ctx->stats, 0, sizeof(ctx->stats));
1095 gettimeofday(&ctx->stats.start_time, NULL);
1097 type = ctx->type;
1098 if (type == 0) {
1099 if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc)
1100 type = KRB5_KRBHST_ADMIN;
1101 else
1102 type = KRB5_KRBHST_KDC;
1105 ctx->send_data = send_data;
1107 if ((int)send_data->length > context->large_msg_size)
1108 ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG;
1110 /* loop until we get back a appropriate response */
1112 action = KRB5_SENDTO_INITIAL;
1114 while (action != KRB5_SENDTO_DONE && action != KRB5_SENDTO_FAILED) {
1115 krb5_krbhst_info *hi;
1117 switch (action) {
1118 case KRB5_SENDTO_INITIAL:
1119 ret = realm_via_plugin(context, realm, context->kdc_timeout,
1120 send_data, &ctx->response);
1121 if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) {
1122 action = KRB5_SENDTO_DONE;
1123 break;
1125 action = KRB5_SENDTO_KRBHST;
1126 /* FALLTHOUGH */
1127 case KRB5_SENDTO_KRBHST:
1128 if (ctx->krbhst == NULL) {
1129 ret = krb5_krbhst_init_flags(context, realm, type,
1130 ctx->flags, &handle);
1131 if (ret)
1132 goto out;
1134 if (ctx->hostname) {
1135 ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname);
1136 if (ret)
1137 goto out;
1140 } else {
1141 handle = heim_retain(ctx->krbhst);
1143 action = KRB5_SENDTO_TIMEOUT;
1144 /* FALLTHOUGH */
1145 case KRB5_SENDTO_TIMEOUT:
1148 * If we completed, just got to next step
1151 if (ctx->stateflags & KRBHST_COMPLETED) {
1152 action = KRB5_SENDTO_CONTINUE;
1153 break;
1157 * Pull out next host, if there is no more, close the
1158 * handle and mark as completed.
1160 * Collect time spent in krbhst (dns, plugin, etc)
1164 gettimeofday(&nrstart, NULL);
1166 ret = krb5_krbhst_next(context, handle, &hi);
1168 gettimeofday(&nrstop, NULL);
1169 timevalsub(&nrstop, &nrstart);
1170 timevaladd(&ctx->stats.krbhst, &nrstop);
1172 action = KRB5_SENDTO_CONTINUE;
1173 if (ret == 0) {
1174 _krb5_debug(context, 5, "submissing new requests to new host");
1175 if (submit_request(context, ctx, hi) != 0)
1176 action = KRB5_SENDTO_TIMEOUT;
1177 } else {
1178 _krb5_debug(context, 5, "out of hosts, waiting for replies");
1179 ctx->stateflags |= KRBHST_COMPLETED;
1182 break;
1183 case KRB5_SENDTO_CONTINUE:
1185 ret = wait_response(context, &action, ctx);
1186 if (ret)
1187 goto out;
1189 break;
1190 case KRB5_SENDTO_RESET:
1191 /* start over */
1192 _krb5_debug(context, 5,
1193 "krb5_sendto trying over again (reset): %d",
1194 numreset);
1195 reset_context(context, ctx);
1196 if (handle) {
1197 krb5_krbhst_free(context, handle);
1198 handle = NULL;
1200 numreset++;
1201 if (numreset >= 3)
1202 action = KRB5_SENDTO_FAILED;
1203 else
1204 action = KRB5_SENDTO_KRBHST;
1206 break;
1207 case KRB5_SENDTO_FILTER:
1208 /* default to next state, the filter function might modify this */
1209 action = KRB5_SENDTO_DONE;
1211 if (ctx->func) {
1212 ret = (*ctx->func)(context, ctx, ctx->data,
1213 &ctx->response, &action);
1214 if (ret)
1215 goto out;
1217 break;
1218 case KRB5_SENDTO_FAILED:
1219 ret = KRB5_KDC_UNREACH;
1220 break;
1221 case KRB5_SENDTO_DONE:
1222 ret = 0;
1223 break;
1224 default:
1225 heim_abort("invalid krb5_sendto_context state");
1229 gettimeofday(&stop_time, NULL);
1230 timevalsub(&stop_time, &ctx->stats.start_time);
1232 out:
1233 if (ret == 0 && ctx->response.length) {
1234 *receive = ctx->response;
1235 krb5_data_zero(&ctx->response);
1236 } else {
1237 krb5_data_free(&ctx->response);
1238 krb5_clear_error_message (context);
1239 ret = KRB5_KDC_UNREACH;
1240 krb5_set_error_message(context, ret,
1241 N_("unable to reach any KDC in realm %s", ""),
1242 realm);
1245 _krb5_debug(context, 1,
1246 "krb5_sendto_context %s done: %d hosts %lu packets %lu wc: %ld.%06ld nr: %ld.%06ld kh: %ld.%06ld tid: %08x",
1247 realm, ret,
1248 ctx->stats.num_hosts, ctx->stats.sent_packets,
1249 stop_time.tv_sec, (long)stop_time.tv_usec,
1250 ctx->stats.name_resolution.tv_sec, (long)ctx->stats.name_resolution.tv_usec,
1251 ctx->stats.krbhst.tv_sec, (long)ctx->stats.krbhst.tv_usec, ctx->stid);
1254 if (freectx)
1255 krb5_sendto_ctx_free(context, ctx);
1256 else
1257 reset_context(context, ctx);
1259 if (handle)
1260 krb5_krbhst_free(context, handle);
1262 return ret;