lib/gssapi/krb5: implement GSS_C_CHANNEL_BOUND_FLAG for gss_init_sec_context()
[heimdal.git] / lib / krb5 / send_to_kdc.c
blobfdf216cae0fa73739452af6c24e50fe99be1299d
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 const char *const send_to_kdc_plugin_deps[] = { "krb5", NULL };
101 static const struct heim_plugin_data
102 send_to_kdc_plugin_data = {
103 "krb5",
104 KRB5_PLUGIN_SEND_TO_KDC,
105 KRB5_PLUGIN_SEND_TO_KDC_VERSION_0,
106 send_to_kdc_plugin_deps,
107 krb5_get_instance
110 static krb5_error_code
111 kdc_via_plugin(krb5_context context,
112 krb5_krbhst_info *hi,
113 time_t timeout,
114 const krb5_data *send_data,
115 krb5_data *receive)
117 struct send_via_plugin_s userctx;
119 userctx.realm = NULL;
120 userctx.hi = hi;
121 userctx.timeout = timeout;
122 userctx.send_data = send_data;
123 userctx.receive = receive;
125 return _krb5_plugin_run_f(context, &send_to_kdc_plugin_data, 0,
126 &userctx, kdccallback);
129 static krb5_error_code
130 realm_via_plugin(krb5_context context,
131 krb5_const_realm realm,
132 time_t timeout,
133 const krb5_data *send_data,
134 krb5_data *receive)
136 struct send_via_plugin_s userctx;
138 userctx.realm = realm;
139 userctx.hi = NULL;
140 userctx.timeout = timeout;
141 userctx.send_data = send_data;
142 userctx.receive = receive;
144 return _krb5_plugin_run_f(context, &send_to_kdc_plugin_data, 0,
145 &userctx, realmcallback);
148 struct krb5_sendto_ctx_data {
149 int flags;
150 int type;
151 krb5_sendto_ctx_func func;
152 void *data;
153 char *hostname;
154 char *sitename;
155 krb5_krbhst_handle krbhst;
157 /* context2 */
158 const krb5_data *send_data;
159 krb5_data response;
160 heim_array_t hosts;
161 int stateflags;
162 #define KRBHST_COMPLETED 1
164 /* prexmit */
165 krb5_sendto_prexmit prexmit_func;
166 void *prexmit_ctx;
168 /* stats */
169 struct {
170 struct timeval start_time;
171 struct timeval name_resolution;
172 struct timeval krbhst;
173 unsigned long sent_packets;
174 unsigned long num_hosts;
175 } stats;
176 unsigned int stid;
179 static void KRB5_CALLCONV
180 dealloc_sendto_ctx(void *ptr)
182 krb5_sendto_ctx ctx = (krb5_sendto_ctx)ptr;
183 if (ctx->hostname)
184 free(ctx->hostname);
185 if (ctx->sitename)
186 free(ctx->sitename);
187 heim_release(ctx->hosts);
188 heim_release(ctx->krbhst);
191 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
192 krb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx)
194 *ctx = heim_alloc(sizeof(**ctx), "sendto-context", dealloc_sendto_ctx);
195 if (*ctx == NULL)
196 return krb5_enomem(context);
197 (*ctx)->hosts = heim_array_create();
199 return 0;
202 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
203 krb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx, int flags)
205 ctx->flags |= flags;
208 KRB5_LIB_FUNCTION int KRB5_LIB_CALL
209 krb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx)
211 return ctx->flags;
214 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
215 krb5_sendto_ctx_set_type(krb5_sendto_ctx ctx, int type)
217 ctx->type = type;
220 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
221 krb5_sendto_ctx_set_func(krb5_sendto_ctx ctx,
222 krb5_sendto_ctx_func func,
223 void *data)
225 ctx->func = func;
226 ctx->data = data;
229 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
230 _krb5_sendto_ctx_set_prexmit(krb5_sendto_ctx ctx,
231 krb5_sendto_prexmit prexmit,
232 void *data)
234 ctx->prexmit_func = prexmit;
235 ctx->prexmit_ctx = data;
238 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
239 krb5_sendto_set_hostname(krb5_context context,
240 krb5_sendto_ctx ctx,
241 const char *hostname)
243 char *newname;
246 * Handle the case where hostname == ctx->hostname by copying it first, and
247 * disposing of any previous value after.
249 newname = strdup(hostname);
250 if (newname == NULL)
251 return krb5_enomem(context);
252 free(ctx->hostname);
253 ctx->hostname = newname;
254 return 0;
257 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
258 krb5_sendto_set_sitename(krb5_context context,
259 krb5_sendto_ctx ctx,
260 const char *sitename)
262 char *newname;
264 newname = strdup(sitename);
265 if (newname == NULL)
266 return krb5_enomem(context);
267 free(ctx->sitename);
268 ctx->sitename = newname;
269 return 0;
272 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
273 _krb5_sendto_ctx_set_krb5hst(krb5_context context,
274 krb5_sendto_ctx ctx,
275 krb5_krbhst_handle handle)
277 heim_release(ctx->krbhst);
278 ctx->krbhst = heim_retain(handle);
281 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
282 krb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx)
284 heim_release(ctx);
287 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
288 _krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data,
289 const krb5_data *reply, int *action)
291 krb5_error_code ret;
292 KRB_ERROR error;
294 if(krb5_rd_error(context, reply, &error))
295 return 0;
297 ret = krb5_error_from_rd_error(context, &error, NULL);
298 krb5_free_error_contents(context, &error);
300 switch(ret) {
301 case KRB5KRB_ERR_RESPONSE_TOO_BIG: {
302 if (krb5_sendto_ctx_get_flags(ctx) & KRB5_KRBHST_FLAGS_LARGE_MSG)
303 break;
304 krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG);
305 *action = KRB5_SENDTO_RESET;
306 break;
308 case KRB5KDC_ERR_SVC_UNAVAILABLE:
309 *action = KRB5_SENDTO_RESET;
310 break;
312 return 0;
319 struct host;
321 struct host_fun {
322 krb5_error_code (*prepare)(krb5_context, struct host *, const krb5_data *);
323 krb5_error_code (*send_fn)(krb5_context, struct host *);
324 krb5_error_code (*recv_fn)(krb5_context, struct host *, krb5_data *);
325 int ntries;
328 struct host {
329 enum host_state { CONNECT, CONNECTING, CONNECTED, WAITING_REPLY, DEAD } state;
330 krb5_krbhst_info *hi;
331 struct addrinfo *freeai;
332 struct addrinfo *ai;
333 rk_socket_t fd;
334 const struct host_fun *fun;
335 unsigned int tries;
336 time_t timeout;
337 krb5_data data;
338 unsigned int tid;
341 static void
342 debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
343 __attribute__ ((__format__ (__printf__, 4, 5)));
345 static void
346 debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
348 const char *proto = "unknown";
349 const char *state;
350 char name[NI_MAXHOST], port[NI_MAXSERV];
351 char *text = NULL;
352 va_list ap;
353 int ret;
355 if (!_krb5_have_debug(context, 5))
356 return;
358 va_start(ap, fmt);
359 ret = vasprintf(&text, fmt, ap);
360 va_end(ap);
361 if (ret == -1 || text == NULL)
362 return;
364 if (host->hi->proto == KRB5_KRBHST_HTTP)
365 proto = "http";
366 else if (host->hi->proto == KRB5_KRBHST_TCP)
367 proto = "tcp";
368 else if (host->hi->proto == KRB5_KRBHST_UDP)
369 proto = "udp";
371 if (getnameinfo(host->ai->ai_addr, host->ai->ai_addrlen,
372 name, sizeof(name), port, sizeof(port),
373 NI_NUMERICHOST|NI_NUMERICSERV|NI_NUMERICSCOPE) != 0)
374 name[0] = '\0';
376 switch (host->state) {
377 case CONNECT: state = "CONNECT"; break;
378 case CONNECTING: state = "CONNECTING"; break;
379 case CONNECTED: state = "CONNECTED"; break;
380 case WAITING_REPLY: state = "WAITING_REPLY"; break;
381 case DEAD: state = "DEAD"; break;
382 default: state = "unknown"; break;
385 _krb5_debug(context, level, "%s: %s %s:%s (%s) state=%s tid: %08x", text,
386 proto, name, port, host->hi->hostname, state, host->tid);
387 free(text);
391 static void HEIM_CALLCONV
392 deallocate_host(void *ptr)
394 struct host *host = ptr;
395 if (!rk_IS_BAD_SOCKET(host->fd))
396 rk_closesocket(host->fd);
397 krb5_data_free(&host->data);
398 if (host->freeai)
399 freeaddrinfo(host->freeai);
400 host->freeai = NULL;
401 host->ai = NULL;
404 static void
405 host_dead(krb5_context context, struct host *host, const char *msg)
407 debug_host(context, 5, host, "%s", msg);
408 rk_closesocket(host->fd);
409 host->fd = rk_INVALID_SOCKET;
410 host->state = DEAD;
413 static krb5_error_code
414 send_stream(krb5_context context, struct host *host)
416 ssize_t len;
418 len = krb5_net_write(context, &host->fd, host->data.data, host->data.length);
420 if (len < 0)
421 return errno;
422 else if (len < host->data.length) {
423 host->data.length -= len;
424 memmove(host->data.data, ((uint8_t *)host->data.data) + len, host->data.length - len);
425 return -1;
426 } else {
427 krb5_data_free(&host->data);
428 return 0;
432 static krb5_error_code
433 recv_stream(krb5_context context, struct host *host)
435 krb5_error_code ret;
436 size_t oldlen;
437 ssize_t sret;
438 int nbytes;
440 if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
441 return HEIM_NET_CONN_REFUSED;
443 if (context->max_msg_size - host->data.length < nbytes) {
444 krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
445 N_("TCP message from KDC too large %d", ""),
446 (int)(host->data.length + nbytes));
447 return KRB5KRB_ERR_FIELD_TOOLONG;
450 oldlen = host->data.length;
452 ret = krb5_data_realloc(&host->data, oldlen + nbytes + 1 /* NUL */);
453 if (ret)
454 return ret;
456 sret = krb5_net_read(context, &host->fd, ((uint8_t *)host->data.data) + oldlen, nbytes);
457 if (sret <= 0) {
458 ret = errno;
459 return ret;
461 host->data.length = oldlen + sret;
462 /* zero terminate for http transport */
463 ((uint8_t *)host->data.data)[host->data.length] = '\0';
465 return 0;
472 static void
473 host_next_timeout(krb5_context context, struct host *host)
475 host->timeout = context->kdc_timeout / host->fun->ntries;
476 if (host->timeout == 0)
477 host->timeout = 1;
479 host->timeout += time(NULL);
483 * connected host
486 static void
487 host_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
489 krb5_error_code ret;
491 host->state = CONNECTED;
493 * Now prepare data to send to host
495 if (ctx->prexmit_func) {
496 krb5_data data;
498 krb5_data_zero(&data);
500 ret = ctx->prexmit_func(context, host->hi->proto,
501 ctx->prexmit_ctx, host->fd, &data);
502 if (ret == 0) {
503 if (data.length == 0) {
504 host_dead(context, host, "prexmit function didn't send data");
505 return;
507 ret = host->fun->prepare(context, host, &data);
508 krb5_data_free(&data);
511 } else {
512 ret = host->fun->prepare(context, host, ctx->send_data);
514 if (ret)
515 debug_host(context, 5, host, "failed to prexmit/prepare");
519 * connect host
522 static void
523 host_connect(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
525 krb5_krbhst_info *hi = host->hi;
526 struct addrinfo *ai = host->ai;
528 debug_host(context, 5, host, "connecting to host");
530 if (connect(host->fd, ai->ai_addr, ai->ai_addrlen) < 0) {
531 #ifdef HAVE_WINSOCK
532 if (WSAGetLastError() == WSAEWOULDBLOCK)
533 errno = EINPROGRESS;
534 #endif /* HAVE_WINSOCK */
535 if (errno == EINPROGRESS && (hi->proto == KRB5_KRBHST_HTTP || hi->proto == KRB5_KRBHST_TCP)) {
536 debug_host(context, 5, host, "connecting to %d", host->fd);
537 host->state = CONNECTING;
538 } else {
539 host_dead(context, host, "failed to connect");
541 } else {
542 host_connected(context, ctx, host);
545 host_next_timeout(context, host);
549 * HTTP transport
552 static krb5_error_code
553 prepare_http(krb5_context context, struct host *host, const krb5_data *data)
555 char *str = NULL, *request = NULL;
556 krb5_error_code ret;
557 int len;
559 heim_assert(host->data.length == 0, "prepare_http called twice");
561 len = rk_base64_encode(data->data, data->length, &str);
562 if(len < 0)
563 return ENOMEM;
565 if (context->http_proxy)
566 ret = asprintf(&request, "GET http://%s/%s HTTP/1.0\r\n\r\n", host->hi->hostname, str);
567 else
568 ret = asprintf(&request, "GET /%s HTTP/1.0\r\n\r\n", str);
569 free(str);
570 if(ret < 0 || request == NULL)
571 return ENOMEM;
573 host->data.data = request;
574 host->data.length = strlen(request);
576 return 0;
579 static krb5_error_code
580 recv_http(krb5_context context, struct host *host, krb5_data *data)
582 krb5_error_code ret;
583 unsigned long rep_len;
584 size_t len;
585 char *p;
588 * recv_stream returns a NUL terminated stream
591 ret = recv_stream(context, host);
592 if (ret)
593 return ret;
595 p = strstr(host->data.data, "\r\n\r\n");
596 if (p == NULL)
597 return -1;
598 p += 4;
600 len = host->data.length - (p - (char *)host->data.data);
601 if (len < 4)
602 return -1;
604 _krb5_get_int(p, &rep_len, 4);
605 if (len < rep_len)
606 return -1;
608 p += 4;
610 memmove(host->data.data, p, rep_len);
611 host->data.length = rep_len;
613 *data = host->data;
614 krb5_data_zero(&host->data);
616 return 0;
620 * TCP transport
623 static krb5_error_code
624 prepare_tcp(krb5_context context, struct host *host, const krb5_data *data)
626 krb5_error_code ret;
627 krb5_storage *sp;
629 heim_assert(host->data.length == 0, "prepare_tcp called twice");
631 sp = krb5_storage_emem();
632 if (sp == NULL)
633 return ENOMEM;
635 ret = krb5_store_data(sp, *data);
636 if (ret) {
637 krb5_storage_free(sp);
638 return ret;
640 ret = krb5_storage_to_data(sp, &host->data);
641 krb5_storage_free(sp);
643 return ret;
646 static krb5_error_code
647 recv_tcp(krb5_context context, struct host *host, krb5_data *data)
649 krb5_error_code ret;
650 unsigned long pktlen;
652 ret = recv_stream(context, host);
653 if (ret)
654 return ret;
656 if (host->data.length < 4)
657 return -1;
659 _krb5_get_int(host->data.data, &pktlen, 4);
661 if (pktlen > host->data.length - 4)
662 return -1;
664 memmove(host->data.data, ((uint8_t *)host->data.data) + 4, host->data.length - 4);
665 host->data.length -= 4;
667 *data = host->data;
668 krb5_data_zero(&host->data);
670 return 0;
674 * UDP transport
677 static krb5_error_code
678 prepare_udp(krb5_context context, struct host *host, const krb5_data *data)
680 return krb5_data_copy(&host->data, data->data, data->length);
683 static krb5_error_code
684 send_udp(krb5_context context, struct host *host)
686 if (send(host->fd, host->data.data, host->data.length, 0) < 0)
687 return errno;
688 return 0;
691 static krb5_error_code
692 recv_udp(krb5_context context, struct host *host, krb5_data *data)
694 krb5_error_code ret;
695 int nbytes;
698 if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
699 return HEIM_NET_CONN_REFUSED;
701 if (context->max_msg_size < nbytes) {
702 krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
703 N_("UDP message from KDC too large %d", ""),
704 (int)nbytes);
705 return KRB5KRB_ERR_FIELD_TOOLONG;
708 ret = krb5_data_alloc(data, nbytes);
709 if (ret)
710 return ret;
712 ret = recv(host->fd, data->data, data->length, 0);
713 if (ret < 0) {
714 ret = errno;
715 krb5_data_free(data);
716 return ret;
718 data->length = ret;
720 return 0;
723 static const struct host_fun http_fun = {
724 prepare_http,
725 send_stream,
726 recv_http,
729 static const struct host_fun tcp_fun = {
730 prepare_tcp,
731 send_stream,
732 recv_tcp,
735 static const struct host_fun udp_fun = {
736 prepare_udp,
737 send_udp,
738 recv_udp,
744 * Host state machine
747 static int
748 eval_host_state(krb5_context context,
749 krb5_sendto_ctx ctx,
750 struct host *host,
751 int readable, int writeable)
753 krb5_error_code ret;
755 if (host->state == CONNECT) {
756 /* check if its this host time to connect */
757 if (host->timeout < time(NULL))
758 host_connect(context, ctx, host);
759 return 0;
762 if (host->state == CONNECTING && writeable)
763 host_connected(context, ctx, host);
765 if (readable) {
767 debug_host(context, 5, host, "reading packet");
769 ret = host->fun->recv_fn(context, host, &ctx->response);
770 if (ret == -1) {
771 /* not done yet */
772 } else if (ret == 0) {
773 /* if recv_foo function returns 0, we have a complete reply */
774 debug_host(context, 5, host, "host completed");
775 return 1;
776 } else {
777 host_dead(context, host, "host disconnected");
781 /* check if there is anything to send, state might DEAD after read */
782 if (writeable && host->state == CONNECTED) {
784 ctx->stats.sent_packets++;
786 debug_host(context, 5, host, "writing packet");
788 ret = host->fun->send_fn(context, host);
789 if (ret == -1) {
790 /* not done yet */
791 } else if (ret) {
792 host_dead(context, host, "host dead, write failed");
793 } else
794 host->state = WAITING_REPLY;
797 return 0;
804 static krb5_error_code
805 submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi)
807 unsigned long submitted_host = 0;
808 struct addrinfo *freeai = NULL;
809 struct timeval nrstart, nrstop;
810 krb5_error_code ret;
811 struct addrinfo *ai = NULL, *a;
812 struct host *host;
814 ret = kdc_via_plugin(context, hi, context->kdc_timeout,
815 ctx->send_data, &ctx->response);
816 if (ret == 0) {
817 return 0;
818 } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
819 _krb5_debug(context, 5, "send via plugin failed %s: %d",
820 hi->hostname, ret);
821 return ret;
825 * If we have a proxy, let use the address of the proxy instead of
826 * the KDC and let the proxy deal with the resolving of the KDC.
829 gettimeofday(&nrstart, NULL);
831 if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) {
832 char *proxy2 = strdup(context->http_proxy);
833 char *el, *proxy = proxy2;
834 struct addrinfo hints;
835 char portstr[NI_MAXSERV];
836 unsigned short nport;
838 if (proxy == NULL)
839 return ENOMEM;
840 if (strncmp(proxy, "http://", 7) == 0)
841 proxy += 7;
843 /* check for url terminating slash */
844 el = strchr(proxy, '/');
845 if (el != NULL)
846 *el = '\0';
848 /* check for port in hostname, used below as port */
849 el = strchr(proxy, ':');
850 if(el != NULL)
851 *el++ = '\0';
853 memset(&hints, 0, sizeof(hints));
854 hints.ai_family = PF_UNSPEC;
855 hints.ai_socktype = SOCK_STREAM;
857 /* On some systems ntohs(foo(..., htons(...))) causes shadowing */
858 nport = init_port(el, htons(80));
859 snprintf(portstr, sizeof(portstr), "%d", ntohs(nport));
861 if (krb5_config_get_bool(context, NULL, "libdefaults", "block_dns",
862 NULL)) {
863 hints.ai_flags &= ~AI_CANONNAME;
864 hints.ai_flags |= AI_NUMERICHOST|AI_NUMERICSERV;
866 ret = getaddrinfo(proxy, portstr, &hints, &ai);
867 free(proxy2);
868 if (ret)
869 return krb5_eai_to_heim_errno(ret, errno);
871 freeai = ai;
873 } else {
874 ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
875 if (ret)
876 return ret;
879 /* add up times */
880 gettimeofday(&nrstop, NULL);
881 timevalsub(&nrstop, &nrstart);
882 timevaladd(&ctx->stats.name_resolution, &nrstop);
884 ctx->stats.num_hosts++;
886 for (a = ai; a != NULL; a = a->ai_next) {
887 rk_socket_t fd;
889 fd = socket(a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);
890 if (rk_IS_BAD_SOCKET(fd))
891 continue;
892 rk_cloexec(fd);
894 #ifndef NO_LIMIT_FD_SETSIZE
895 if (fd >= FD_SETSIZE) {
896 _krb5_debug(context, 0, "fd too large for select");
897 rk_closesocket(fd);
898 continue;
900 #endif
901 socket_set_nonblocking(fd, 1);
903 host = heim_alloc(sizeof(*host), "sendto-host", deallocate_host);
904 if (host == NULL) {
905 if (freeai)
906 freeaddrinfo(freeai);
907 rk_closesocket(fd);
908 return ENOMEM;
910 host->hi = hi;
911 host->fd = fd;
912 host->ai = a;
913 host->freeai = freeai;
914 freeai = NULL;
915 /* next version of stid */
916 host->tid = ctx->stid = (ctx->stid & 0xffff0000) | ((ctx->stid & 0xffff) + 1);
918 host->state = CONNECT;
920 switch (host->hi->proto) {
921 case KRB5_KRBHST_HTTP :
922 host->fun = &http_fun;
923 break;
924 case KRB5_KRBHST_TCP :
925 host->fun = &tcp_fun;
926 break;
927 case KRB5_KRBHST_UDP :
928 host->fun = &udp_fun;
929 break;
930 default:
931 heim_abort("undefined http transport protocol: %d", (int)host->hi->proto);
934 host->tries = host->fun->ntries;
937 * Connect directly next host, wait a host_timeout for each next address.
938 * We try host_connect() here, checking the return code because as we do
939 * non-blocking connects, any error here indicates that the address is just
940 * offline. That is, it's something like "No route to host" which is not
941 * worth retrying. And so, we fail directly and immediately to the next
942 * address for this host without enqueueing the address for retries.
944 if (submitted_host == 0) {
945 host_connect(context, ctx, host);
946 if (host->state == DEAD)
947 continue;
948 } else {
949 debug_host(context, 5, host,
950 "Queuing host in future (in %ds), its the %lu address on the same name",
951 (int)(context->host_timeout * submitted_host), submitted_host + 1);
952 host->timeout = time(NULL) + (submitted_host * context->host_timeout);
955 heim_array_append_value(ctx->hosts, host);
956 heim_release(host);
957 submitted_host++;
960 if (freeai)
961 freeai = NULL;
963 if (submitted_host == 0)
964 return KRB5_KDC_UNREACH;
966 return 0;
969 struct wait_ctx {
970 krb5_context context;
971 krb5_sendto_ctx ctx;
972 fd_set rfds;
973 fd_set wfds;
974 rk_socket_t max_fd;
975 int got_reply;
976 time_t timenow;
979 static void
980 wait_setup(heim_object_t obj, void *iter_ctx, int *stop)
982 struct wait_ctx *wait_ctx = iter_ctx;
983 struct host *h = (struct host *)obj;
985 if (h->state == CONNECT) {
986 if (h->timeout >= wait_ctx->timenow)
987 return;
988 host_connect(wait_ctx->context, wait_ctx->ctx, h);
991 /* skip dead hosts */
992 if (h->state == DEAD)
993 return;
995 /* if host timed out, dec tries and (retry or kill host) */
996 if (h->timeout < wait_ctx->timenow) {
997 heim_assert(h->tries != 0, "tries should not reach 0");
998 h->tries--;
999 if (h->tries == 0) {
1000 host_dead(wait_ctx->context, h, "host timed out");
1001 return;
1002 } else {
1003 debug_host(wait_ctx->context, 5, h, "retrying sending to");
1004 host_next_timeout(wait_ctx->context, h);
1005 host_connected(wait_ctx->context, wait_ctx->ctx, h);
1009 #ifndef NO_LIMIT_FD_SETSIZE
1010 heim_assert(h->fd < FD_SETSIZE, "fd too large");
1011 #endif
1012 switch (h->state) {
1013 case WAITING_REPLY:
1014 FD_SET(h->fd, &wait_ctx->rfds);
1015 break;
1016 case CONNECTING:
1017 case CONNECTED:
1018 FD_SET(h->fd, &wait_ctx->rfds);
1019 FD_SET(h->fd, &wait_ctx->wfds);
1020 break;
1021 default:
1022 debug_host(wait_ctx->context, 5, h, "invalid sendto host state");
1023 heim_abort("invalid sendto host state");
1025 if (h->fd > wait_ctx->max_fd || wait_ctx->max_fd == rk_INVALID_SOCKET)
1026 wait_ctx->max_fd = h->fd;
1029 static int
1030 wait_filter_dead(heim_object_t obj, void *ctx)
1032 struct host *h = (struct host *)obj;
1033 return (int)((h->state == DEAD) ? true : false);
1036 static void
1037 wait_accelerate(heim_object_t obj, void *ctx, int *stop)
1039 struct host *h = (struct host *)obj;
1041 if (h->state == CONNECT && h->timeout > 0)
1042 h->timeout--;
1045 static void
1046 wait_process(heim_object_t obj, void *ctx, int *stop)
1048 struct wait_ctx *wait_ctx = ctx;
1049 struct host *h = (struct host *)obj;
1050 int readable, writeable;
1051 heim_assert(h->state != DEAD, "dead host resurected");
1053 #ifndef NO_LIMIT_FD_SETSIZE
1054 heim_assert(h->fd < FD_SETSIZE, "fd too large");
1055 #endif
1056 readable = FD_ISSET(h->fd, &wait_ctx->rfds);
1057 writeable = FD_ISSET(h->fd, &wait_ctx->wfds);
1059 if (readable || writeable || h->state == CONNECT)
1060 wait_ctx->got_reply |= eval_host_state(wait_ctx->context, wait_ctx->ctx, h, readable, writeable);
1062 /* if there is already a reply, just fall though the array */
1063 if (wait_ctx->got_reply)
1064 *stop = 1;
1067 static krb5_error_code
1068 wait_response(krb5_context context, int *action, krb5_sendto_ctx ctx)
1070 struct wait_ctx wait_ctx;
1071 struct timeval tv;
1072 int ret;
1074 wait_ctx.context = context;
1075 wait_ctx.ctx = ctx;
1076 FD_ZERO(&wait_ctx.rfds);
1077 FD_ZERO(&wait_ctx.wfds);
1078 wait_ctx.max_fd = rk_INVALID_SOCKET;
1080 /* oh, we have a reply, it must be a plugin that got it for us */
1081 if (ctx->response.length) {
1082 *action = KRB5_SENDTO_FILTER;
1083 return 0;
1086 wait_ctx.timenow = time(NULL);
1088 heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_setup);
1089 heim_array_filter_f(ctx->hosts, &wait_ctx, wait_filter_dead);
1091 if (heim_array_get_length(ctx->hosts) == 0) {
1092 if (ctx->stateflags & KRBHST_COMPLETED) {
1093 _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
1094 "trying to pulling more hosts");
1095 *action = KRB5_SENDTO_FAILED;
1096 } else {
1097 _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
1098 "and no more hosts -> failure");
1099 *action = KRB5_SENDTO_TIMEOUT;
1101 return 0;
1104 if (wait_ctx.max_fd == rk_INVALID_SOCKET) {
1106 * If we don't find a host which can make progress, then
1107 * we accelerate the process by moving all of the contestants
1108 * up by 1s.
1110 _krb5_debug(context, 5, "wait_response: moving the contestants forward");
1111 heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_accelerate);
1112 return 0;
1115 tv.tv_sec = 1;
1116 tv.tv_usec = 0;
1118 ret = select(wait_ctx.max_fd + 1, &wait_ctx.rfds, &wait_ctx.wfds, NULL, &tv);
1119 if (ret < 0)
1120 return errno;
1121 if (ret == 0) {
1122 *action = KRB5_SENDTO_TIMEOUT;
1123 return 0;
1126 wait_ctx.got_reply = 0;
1127 heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_process);
1128 if (wait_ctx.got_reply)
1129 *action = KRB5_SENDTO_FILTER;
1130 else
1131 *action = KRB5_SENDTO_CONTINUE;
1133 return 0;
1136 static void
1137 reset_context(krb5_context context, krb5_sendto_ctx ctx)
1139 krb5_data_free(&ctx->response);
1140 heim_release(ctx->hosts);
1141 ctx->hosts = heim_array_create();
1142 ctx->stateflags = 0;
1150 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1151 krb5_sendto_context(krb5_context context,
1152 krb5_sendto_ctx ctx,
1153 const krb5_data *send_data,
1154 krb5_const_realm realm,
1155 krb5_data *receive)
1157 krb5_error_code ret = 0;
1158 krb5_krbhst_handle handle = NULL;
1159 struct timeval nrstart, nrstop, stop_time;
1160 int type, freectx = 0;
1161 int action;
1162 int numreset = 0;
1164 krb5_data_zero(receive);
1166 if (ctx == NULL) {
1167 ret = krb5_sendto_ctx_alloc(context, &ctx);
1168 if (ret)
1169 goto out;
1170 freectx = 1;
1173 ctx->stid = (context->num_kdc_requests++) << 16;
1175 memset(&ctx->stats, 0, sizeof(ctx->stats));
1176 gettimeofday(&ctx->stats.start_time, NULL);
1178 type = ctx->type;
1179 if (type == 0) {
1180 if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc)
1181 type = KRB5_KRBHST_ADMIN;
1182 else
1183 type = KRB5_KRBHST_KDC;
1186 ctx->send_data = send_data;
1188 if ((int)send_data->length > context->large_msg_size)
1189 ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG;
1191 /* loop until we get back a appropriate response */
1193 action = KRB5_SENDTO_INITIAL;
1195 while (1) {
1196 krb5_krbhst_info *hi;
1198 switch (action) {
1199 case KRB5_SENDTO_INITIAL:
1200 ret = realm_via_plugin(context, realm, context->kdc_timeout,
1201 send_data, &ctx->response);
1202 if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) {
1203 action = KRB5_SENDTO_DONE;
1204 break;
1206 action = KRB5_SENDTO_KRBHST;
1207 HEIM_FALLTHROUGH;
1208 case KRB5_SENDTO_KRBHST:
1209 if (ctx->krbhst == NULL) {
1210 ret = krb5_krbhst_init_flags(context, realm, type,
1211 ctx->flags, &handle);
1212 if (ret)
1213 goto out;
1215 if (ctx->hostname) {
1216 ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname);
1217 if (ret)
1218 goto out;
1220 if (ctx->sitename) {
1221 ret = krb5_krbhst_set_sitename(context, handle, ctx->sitename);
1222 if (ret)
1223 goto out;
1225 } else {
1226 handle = heim_retain(ctx->krbhst);
1228 action = KRB5_SENDTO_TIMEOUT;
1229 HEIM_FALLTHROUGH;
1230 case KRB5_SENDTO_TIMEOUT:
1233 * If we completed, just got to next step
1236 if (ctx->stateflags & KRBHST_COMPLETED) {
1237 action = KRB5_SENDTO_CONTINUE;
1238 break;
1242 * Pull out next host, if there is no more, close the
1243 * handle and mark as completed.
1245 * Collect time spent in krbhst (dns, plugin, etc)
1249 gettimeofday(&nrstart, NULL);
1251 ret = krb5_krbhst_next(context, handle, &hi);
1253 gettimeofday(&nrstop, NULL);
1254 timevalsub(&nrstop, &nrstart);
1255 timevaladd(&ctx->stats.krbhst, &nrstop);
1257 action = KRB5_SENDTO_CONTINUE;
1258 if (ret == 0) {
1259 _krb5_debug(context, 5, "submitting new requests to new host");
1260 if (submit_request(context, ctx, hi) != 0)
1261 action = KRB5_SENDTO_TIMEOUT;
1262 } else {
1263 _krb5_debug(context, 5, "out of hosts, waiting for replies");
1264 ctx->stateflags |= KRBHST_COMPLETED;
1267 break;
1268 case KRB5_SENDTO_CONTINUE:
1270 ret = wait_response(context, &action, ctx);
1271 if (ret)
1272 goto out;
1274 break;
1275 case KRB5_SENDTO_RESET:
1276 /* start over */
1277 _krb5_debug(context, 5,
1278 "krb5_sendto trying over again (reset): %d",
1279 numreset);
1280 reset_context(context, ctx);
1281 if (handle) {
1282 krb5_krbhst_free(context, handle);
1283 handle = NULL;
1285 numreset++;
1286 if (numreset >= 3)
1287 action = KRB5_SENDTO_FAILED;
1288 else
1289 action = KRB5_SENDTO_KRBHST;
1291 break;
1292 case KRB5_SENDTO_FILTER:
1293 /* default to next state, the filter function might modify this */
1294 action = KRB5_SENDTO_DONE;
1296 if (ctx->func) {
1297 ret = (*ctx->func)(context, ctx, ctx->data,
1298 &ctx->response, &action);
1299 if (ret)
1300 goto out;
1303 * If we are not done, ask to continue/reset
1305 switch (action) {
1306 case KRB5_SENDTO_DONE:
1307 break;
1308 case KRB5_SENDTO_RESET:
1309 case KRB5_SENDTO_CONTINUE:
1310 /* free response to clear it out so we don't loop */
1311 krb5_data_free(&ctx->response);
1312 break;
1313 default:
1314 ret = KRB5_KDC_UNREACH;
1315 krb5_set_error_message(context, ret,
1316 "sendto filter funcation return unsupported state: %d", (int)action);
1317 goto out;
1320 break;
1321 case KRB5_SENDTO_FAILED:
1322 ret = KRB5_KDC_UNREACH;
1323 goto out;
1324 case KRB5_SENDTO_DONE:
1325 ret = 0;
1326 goto out;
1327 default:
1328 heim_abort("invalid krb5_sendto_context state");
1332 out:
1333 gettimeofday(&stop_time, NULL);
1334 timevalsub(&stop_time, &ctx->stats.start_time);
1335 if (ret == 0 && ctx->response.length) {
1336 *receive = ctx->response;
1337 krb5_data_zero(&ctx->response);
1338 } else {
1339 krb5_data_free(&ctx->response);
1340 krb5_clear_error_message (context);
1341 ret = KRB5_KDC_UNREACH;
1342 krb5_set_error_message(context, ret,
1343 N_("unable to reach any KDC in realm %s", ""),
1344 realm);
1347 _krb5_debug(context, 1,
1348 "%s %s done: %d hosts: %lu packets: %lu"
1349 " wc: %lld.%06lu nr: %lld.%06lu kh: %lld.%06lu tid: %08x",
1350 __func__, realm, ret,
1351 ctx->stats.num_hosts, ctx->stats.sent_packets,
1352 (long long)stop_time.tv_sec,
1353 (unsigned long)stop_time.tv_usec,
1354 (long long)ctx->stats.name_resolution.tv_sec,
1355 (unsigned long)ctx->stats.name_resolution.tv_usec,
1356 (long long)ctx->stats.krbhst.tv_sec,
1357 (unsigned long)ctx->stats.krbhst.tv_usec, ctx->stid);
1360 if (freectx)
1361 krb5_sendto_ctx_free(context, ctx);
1362 else
1363 reset_context(context, ctx);
1365 if (handle)
1366 krb5_krbhst_free(context, handle);
1368 return ret;