2 * Copyright (c) 1997 - 2002 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
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
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
36 #include "krb5_locl.h"
37 #include "send_to_kdc_plugin.h"
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
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.
57 init_port(const char *s
, int fallback
)
61 if (s
&& sscanf(s
, "%d", &tmp
) == 1)
66 struct send_via_plugin_s
{
67 krb5_const_realm realm
;
70 const krb5_data
*send_data
;
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
= {
104 KRB5_PLUGIN_SEND_TO_KDC
,
105 KRB5_PLUGIN_SEND_TO_KDC_VERSION_0
,
106 send_to_kdc_plugin_deps
,
110 static krb5_error_code
111 kdc_via_plugin(krb5_context context
,
112 krb5_krbhst_info
*hi
,
114 const krb5_data
*send_data
,
117 struct send_via_plugin_s userctx
;
119 userctx
.realm
= NULL
;
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
,
133 const krb5_data
*send_data
,
136 struct send_via_plugin_s userctx
;
138 userctx
.realm
= realm
;
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
{
151 krb5_sendto_ctx_func func
;
155 krb5_krbhst_handle krbhst
;
158 const krb5_data
*send_data
;
162 #define KRBHST_COMPLETED 1
165 krb5_sendto_prexmit prexmit_func
;
170 struct timeval start_time
;
171 struct timeval name_resolution
;
172 struct timeval krbhst
;
173 unsigned long sent_packets
;
174 unsigned long num_hosts
;
179 static void KRB5_CALLCONV
180 dealloc_sendto_ctx(void *ptr
)
182 krb5_sendto_ctx ctx
= (krb5_sendto_ctx
)ptr
;
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
);
196 return krb5_enomem(context
);
197 (*ctx
)->hosts
= heim_array_create();
202 KRB5_LIB_FUNCTION
void KRB5_LIB_CALL
203 krb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx
, int flags
)
208 KRB5_LIB_FUNCTION
int KRB5_LIB_CALL
209 krb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx
)
214 KRB5_LIB_FUNCTION
void KRB5_LIB_CALL
215 krb5_sendto_ctx_set_type(krb5_sendto_ctx ctx
, int 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
,
229 KRB5_LIB_FUNCTION
void KRB5_LIB_CALL
230 _krb5_sendto_ctx_set_prexmit(krb5_sendto_ctx ctx
,
231 krb5_sendto_prexmit prexmit
,
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
,
241 const char *hostname
)
246 * Handle the case where hostname == ctx->hostname by copying it first, and
247 * disposing of any previous value after.
249 newname
= strdup(hostname
);
251 return krb5_enomem(context
);
253 ctx
->hostname
= newname
;
257 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
258 krb5_sendto_set_sitename(krb5_context context
,
260 const char *sitename
)
264 newname
= strdup(sitename
);
266 return krb5_enomem(context
);
268 ctx
->sitename
= newname
;
272 KRB5_LIB_FUNCTION
void KRB5_LIB_CALL
273 _krb5_sendto_ctx_set_krb5hst(krb5_context context
,
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
)
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
)
294 if(krb5_rd_error(context
, reply
, &error
))
297 ret
= krb5_error_from_rd_error(context
, &error
, NULL
);
298 krb5_free_error_contents(context
, &error
);
301 case KRB5KRB_ERR_RESPONSE_TOO_BIG
: {
302 if (krb5_sendto_ctx_get_flags(ctx
) & KRB5_KRBHST_FLAGS_LARGE_MSG
)
304 krb5_sendto_ctx_add_flags(ctx
, KRB5_KRBHST_FLAGS_LARGE_MSG
);
305 *action
= KRB5_SENDTO_RESET
;
308 case KRB5KDC_ERR_SVC_UNAVAILABLE
:
309 *action
= KRB5_SENDTO_RESET
;
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
*);
329 enum host_state
{ CONNECT
, CONNECTING
, CONNECTED
, WAITING_REPLY
, DEAD
} state
;
330 krb5_krbhst_info
*hi
;
331 struct addrinfo
*freeai
;
334 const struct host_fun
*fun
;
342 debug_host(krb5_context context
, int level
, struct host
*host
, const char *fmt
, ...)
343 __attribute__ ((__format__ (__printf__
, 4, 5)));
346 debug_host(krb5_context context
, int level
, struct host
*host
, const char *fmt
, ...)
348 const char *proto
= "unknown";
350 char name
[NI_MAXHOST
], port
[NI_MAXSERV
];
355 if (!_krb5_have_debug(context
, 5))
359 ret
= vasprintf(&text
, fmt
, ap
);
361 if (ret
== -1 || text
== NULL
)
364 if (host
->hi
->proto
== KRB5_KRBHST_HTTP
)
366 else if (host
->hi
->proto
== KRB5_KRBHST_TCP
)
368 else if (host
->hi
->proto
== KRB5_KRBHST_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)
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
);
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
);
399 freeaddrinfo(host
->freeai
);
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
;
413 static krb5_error_code
414 send_stream(krb5_context context
, struct host
*host
)
418 len
= krb5_net_write(context
, &host
->fd
, host
->data
.data
, host
->data
.length
);
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
);
427 krb5_data_free(&host
->data
);
432 static krb5_error_code
433 recv_stream(krb5_context context
, struct host
*host
)
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 */);
456 sret
= krb5_net_read(context
, &host
->fd
, ((uint8_t *)host
->data
.data
) + oldlen
, nbytes
);
461 host
->data
.length
= oldlen
+ sret
;
462 /* zero terminate for http transport */
463 ((uint8_t *)host
->data
.data
)[host
->data
.length
] = '\0';
473 host_next_timeout(krb5_context context
, struct host
*host
)
475 host
->timeout
= context
->kdc_timeout
/ host
->fun
->ntries
;
476 if (host
->timeout
== 0)
479 host
->timeout
+= time(NULL
);
487 host_connected(krb5_context context
, krb5_sendto_ctx ctx
, struct host
*host
)
491 host
->state
= CONNECTED
;
493 * Now prepare data to send to host
495 if (ctx
->prexmit_func
) {
498 krb5_data_zero(&data
);
500 ret
= ctx
->prexmit_func(context
, host
->hi
->proto
,
501 ctx
->prexmit_ctx
, host
->fd
, &data
);
503 if (data
.length
== 0) {
504 host_dead(context
, host
, "prexmit function didn't send data");
507 ret
= host
->fun
->prepare(context
, host
, &data
);
508 krb5_data_free(&data
);
512 ret
= host
->fun
->prepare(context
, host
, ctx
->send_data
);
515 debug_host(context
, 5, host
, "failed to prexmit/prepare");
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) {
532 if (WSAGetLastError() == WSAEWOULDBLOCK
)
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
;
539 host_dead(context
, host
, "failed to connect");
542 host_connected(context
, ctx
, host
);
545 host_next_timeout(context
, host
);
552 static krb5_error_code
553 prepare_http(krb5_context context
, struct host
*host
, const krb5_data
*data
)
555 char *str
= NULL
, *request
= NULL
;
559 heim_assert(host
->data
.length
== 0, "prepare_http called twice");
561 len
= rk_base64_encode(data
->data
, data
->length
, &str
);
565 if (context
->http_proxy
)
566 ret
= asprintf(&request
, "GET http://%s/%s HTTP/1.0\r\n\r\n", host
->hi
->hostname
, str
);
568 ret
= asprintf(&request
, "GET /%s HTTP/1.0\r\n\r\n", str
);
570 if(ret
< 0 || request
== NULL
)
573 host
->data
.data
= request
;
574 host
->data
.length
= strlen(request
);
579 static krb5_error_code
580 recv_http(krb5_context context
, struct host
*host
, krb5_data
*data
)
583 unsigned long rep_len
;
588 * recv_stream returns a NUL terminated stream
591 ret
= recv_stream(context
, host
);
595 p
= strstr(host
->data
.data
, "\r\n\r\n");
600 len
= host
->data
.length
- (p
- (char *)host
->data
.data
);
604 _krb5_get_int(p
, &rep_len
, 4);
610 memmove(host
->data
.data
, p
, rep_len
);
611 host
->data
.length
= rep_len
;
614 krb5_data_zero(&host
->data
);
623 static krb5_error_code
624 prepare_tcp(krb5_context context
, struct host
*host
, const krb5_data
*data
)
629 heim_assert(host
->data
.length
== 0, "prepare_tcp called twice");
631 sp
= krb5_storage_emem();
635 ret
= krb5_store_data(sp
, *data
);
637 krb5_storage_free(sp
);
640 ret
= krb5_storage_to_data(sp
, &host
->data
);
641 krb5_storage_free(sp
);
646 static krb5_error_code
647 recv_tcp(krb5_context context
, struct host
*host
, krb5_data
*data
)
650 unsigned long pktlen
;
652 ret
= recv_stream(context
, host
);
656 if (host
->data
.length
< 4)
659 _krb5_get_int(host
->data
.data
, &pktlen
, 4);
661 if (pktlen
> host
->data
.length
- 4)
664 memmove(host
->data
.data
, ((uint8_t *)host
->data
.data
) + 4, host
->data
.length
- 4);
665 host
->data
.length
-= 4;
668 krb5_data_zero(&host
->data
);
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)
691 static krb5_error_code
692 recv_udp(krb5_context context
, struct host
*host
, krb5_data
*data
)
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", ""),
705 return KRB5KRB_ERR_FIELD_TOOLONG
;
708 ret
= krb5_data_alloc(data
, nbytes
);
712 ret
= recv(host
->fd
, data
->data
, data
->length
, 0);
715 krb5_data_free(data
);
723 static const struct host_fun http_fun
= {
729 static const struct host_fun tcp_fun
= {
735 static const struct host_fun udp_fun
= {
748 eval_host_state(krb5_context context
,
751 int readable
, int writeable
)
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
);
762 if (host
->state
== CONNECTING
&& writeable
)
763 host_connected(context
, ctx
, host
);
767 debug_host(context
, 5, host
, "reading packet");
769 ret
= host
->fun
->recv_fn(context
, host
, &ctx
->response
);
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");
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
);
792 host_dead(context
, host
, "host dead, write failed");
794 host
->state
= WAITING_REPLY
;
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
;
811 struct addrinfo
*ai
= NULL
, *a
;
814 ret
= kdc_via_plugin(context
, hi
, context
->kdc_timeout
,
815 ctx
->send_data
, &ctx
->response
);
818 } else if (ret
!= KRB5_PLUGIN_NO_HANDLE
) {
819 _krb5_debug(context
, 5, "send via plugin failed %s: %d",
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
;
840 if (strncmp(proxy
, "http://", 7) == 0)
843 /* check for url terminating slash */
844 el
= strchr(proxy
, '/');
848 /* check for port in hostname, used below as port */
849 el
= strchr(proxy
, ':');
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",
863 hints
.ai_flags
&= ~AI_CANONNAME
;
864 hints
.ai_flags
|= AI_NUMERICHOST
|AI_NUMERICSERV
;
866 ret
= getaddrinfo(proxy
, portstr
, &hints
, &ai
);
869 return krb5_eai_to_heim_errno(ret
, errno
);
874 ret
= krb5_krbhst_get_addrinfo(context
, hi
, &ai
);
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
) {
889 fd
= socket(a
->ai_family
, a
->ai_socktype
| SOCK_CLOEXEC
, a
->ai_protocol
);
890 if (rk_IS_BAD_SOCKET(fd
))
894 #ifndef NO_LIMIT_FD_SETSIZE
895 if (fd
>= FD_SETSIZE
) {
896 _krb5_debug(context
, 0, "fd too large for select");
901 socket_set_nonblocking(fd
, 1);
903 host
= heim_alloc(sizeof(*host
), "sendto-host", deallocate_host
);
906 freeaddrinfo(freeai
);
913 host
->freeai
= freeai
;
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
;
924 case KRB5_KRBHST_TCP
:
925 host
->fun
= &tcp_fun
;
927 case KRB5_KRBHST_UDP
:
928 host
->fun
= &udp_fun
;
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
)
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
);
963 if (submitted_host
== 0)
964 return KRB5_KDC_UNREACH
;
970 krb5_context context
;
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
)
988 host_connect(wait_ctx
->context
, wait_ctx
->ctx
, h
);
991 /* skip dead hosts */
992 if (h
->state
== DEAD
)
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");
1000 host_dead(wait_ctx
->context
, h
, "host timed out");
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");
1014 FD_SET(h
->fd
, &wait_ctx
->rfds
);
1018 FD_SET(h
->fd
, &wait_ctx
->rfds
);
1019 FD_SET(h
->fd
, &wait_ctx
->wfds
);
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
;
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);
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)
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");
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
)
1067 static krb5_error_code
1068 wait_response(krb5_context context
, int *action
, krb5_sendto_ctx ctx
)
1070 struct wait_ctx wait_ctx
;
1074 wait_ctx
.context
= context
;
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
;
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
;
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
;
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
1110 _krb5_debug(context
, 5, "wait_response: moving the contestants forward");
1111 heim_array_iterate_f(ctx
->hosts
, &wait_ctx
, wait_accelerate
);
1118 ret
= select(wait_ctx
.max_fd
+ 1, &wait_ctx
.rfds
, &wait_ctx
.wfds
, NULL
, &tv
);
1122 *action
= KRB5_SENDTO_TIMEOUT
;
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
;
1131 *action
= KRB5_SENDTO_CONTINUE
;
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
,
1157 krb5_error_code ret
= 0;
1158 krb5_krbhst_handle handle
= NULL
;
1159 struct timeval nrstart
, nrstop
, stop_time
;
1160 int type
, freectx
= 0;
1164 krb5_data_zero(receive
);
1167 ret
= krb5_sendto_ctx_alloc(context
, &ctx
);
1173 ctx
->stid
= (context
->num_kdc_requests
++) << 16;
1175 memset(&ctx
->stats
, 0, sizeof(ctx
->stats
));
1176 gettimeofday(&ctx
->stats
.start_time
, NULL
);
1180 if ((ctx
->flags
& KRB5_KRBHST_FLAGS_MASTER
) || context
->use_admin_kdc
)
1181 type
= KRB5_KRBHST_ADMIN
;
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
;
1196 krb5_krbhst_info
*hi
;
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
;
1206 action
= KRB5_SENDTO_KRBHST
;
1208 case KRB5_SENDTO_KRBHST
:
1209 if (ctx
->krbhst
== NULL
) {
1210 ret
= krb5_krbhst_init_flags(context
, realm
, type
,
1211 ctx
->flags
, &handle
);
1215 if (ctx
->hostname
) {
1216 ret
= krb5_krbhst_set_hostname(context
, handle
, ctx
->hostname
);
1220 if (ctx
->sitename
) {
1221 ret
= krb5_krbhst_set_sitename(context
, handle
, ctx
->sitename
);
1226 handle
= heim_retain(ctx
->krbhst
);
1228 action
= KRB5_SENDTO_TIMEOUT
;
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
;
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
;
1259 _krb5_debug(context
, 5, "submitting new requests to new host");
1260 if (submit_request(context
, ctx
, hi
) != 0)
1261 action
= KRB5_SENDTO_TIMEOUT
;
1263 _krb5_debug(context
, 5, "out of hosts, waiting for replies");
1264 ctx
->stateflags
|= KRBHST_COMPLETED
;
1268 case KRB5_SENDTO_CONTINUE
:
1270 ret
= wait_response(context
, &action
, ctx
);
1275 case KRB5_SENDTO_RESET
:
1277 _krb5_debug(context
, 5,
1278 "krb5_sendto trying over again (reset): %d",
1280 reset_context(context
, ctx
);
1282 krb5_krbhst_free(context
, handle
);
1287 action
= KRB5_SENDTO_FAILED
;
1289 action
= KRB5_SENDTO_KRBHST
;
1292 case KRB5_SENDTO_FILTER
:
1293 /* default to next state, the filter function might modify this */
1294 action
= KRB5_SENDTO_DONE
;
1297 ret
= (*ctx
->func
)(context
, ctx
, ctx
->data
,
1298 &ctx
->response
, &action
);
1303 * If we are not done, ask to continue/reset
1306 case KRB5_SENDTO_DONE
:
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
);
1314 ret
= KRB5_KDC_UNREACH
;
1315 krb5_set_error_message(context
, ret
,
1316 "sendto filter funcation return unsupported state: %d", (int)action
);
1321 case KRB5_SENDTO_FAILED
:
1322 ret
= KRB5_KDC_UNREACH
;
1324 case KRB5_SENDTO_DONE
:
1328 heim_abort("invalid krb5_sendto_context state");
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
);
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", ""),
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
);
1361 krb5_sendto_ctx_free(context
, ctx
);
1363 reset_context(context
, ctx
);
1366 krb5_krbhst_free(context
, handle
);