1 /* $NetBSD: send_to_kdc.c,v 1.9 2023/06/19 21:41:44 christos Exp $ */
2
3 /*
4 * Copyright (c) 1997 - 2002 Kungliga Tekniska Högskolan
5 * (Royal Institute of Technology, Stockholm, Sweden).
6 * All rights reserved.
7 *
8 * Portions Copyright (c) 2010 - 2013 Apple Inc. All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 *
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 *
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 *
21 * 3. Neither the name of the Institute nor the names of its contributors
22 * may be used to endorse or promote products derived from this software
23 * without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
36 */
37
38 #include "krb5_locl.h"
39 #include "send_to_kdc_plugin.h"
40
41 /**
42 * @section send_to_kdc Locating and sending packets to the KDC
43 *
44 * The send to kdc code is responsible to request the list of KDC from
45 * the locate-kdc subsystem and then send requests to each of them.
46 *
47 * - Each second a new hostname is tried.
48 * - If the hostname have several addresses, the first will be tried
49 * directly then in turn the other will be tried every 3 seconds
50 * (host_timeout).
51 * - UDP requests are tried 3 times, and it tried with a individual timeout of kdc_timeout / 3.
52 * - TCP and HTTP requests are tried 1 time.
53 *
54 * Total wait time shorter then (number of addresses * 3) + kdc_timeout seconds.
55 *
56 */
57
58 static int
init_port(const char * s,int fallback)59 init_port(const char *s, int fallback)
60 {
61 int tmp;
62
63 if (s && sscanf(s, "%d", &tmp) == 1)
64 return htons(tmp);
65 return fallback;
66 }
67
68 struct send_via_plugin_s {
69 krb5_const_realm realm;
70 krb5_krbhst_info *hi;
71 time_t timeout;
72 const krb5_data *send_data;
73 krb5_data *receive;
74 };
75
76
77 static krb5_error_code KRB5_LIB_CALL
kdccallback(krb5_context context,const void * plug,void * plugctx,void * userctx)78 kdccallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
79 {
80 const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
81 struct send_via_plugin_s *ctx = userctx;
82
83 if (service->send_to_kdc == NULL)
84 return KRB5_PLUGIN_NO_HANDLE;
85 return service->send_to_kdc(context, plugctx, ctx->hi, ctx->timeout,
86 ctx->send_data, ctx->receive);
87 }
88
89 static krb5_error_code KRB5_LIB_CALL
realmcallback(krb5_context context,const void * plug,void * plugctx,void * userctx)90 realmcallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
91 {
92 const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
93 struct send_via_plugin_s *ctx = userctx;
94
95 if (service->send_to_realm == NULL)
96 return KRB5_PLUGIN_NO_HANDLE;
97 return service->send_to_realm(context, plugctx, ctx->realm, ctx->timeout,
98 ctx->send_data, ctx->receive);
99 }
100
101 static krb5_error_code
kdc_via_plugin(krb5_context context,krb5_krbhst_info * hi,time_t timeout,const krb5_data * send_data,krb5_data * receive)102 kdc_via_plugin(krb5_context context,
103 krb5_krbhst_info *hi,
104 time_t timeout,
105 const krb5_data *send_data,
106 krb5_data *receive)
107 {
108 struct send_via_plugin_s userctx;
109
110 userctx.realm = NULL;
111 userctx.hi = hi;
112 userctx.timeout = timeout;
113 userctx.send_data = send_data;
114 userctx.receive = receive;
115
116 return _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC,
117 KRB5_PLUGIN_SEND_TO_KDC_VERSION_0, 0,
118 &userctx, kdccallback);
119 }
120
121 static krb5_error_code
realm_via_plugin(krb5_context context,krb5_const_realm realm,time_t timeout,const krb5_data * send_data,krb5_data * receive)122 realm_via_plugin(krb5_context context,
123 krb5_const_realm realm,
124 time_t timeout,
125 const krb5_data *send_data,
126 krb5_data *receive)
127 {
128 struct send_via_plugin_s userctx;
129
130 userctx.realm = realm;
131 userctx.hi = NULL;
132 userctx.timeout = timeout;
133 userctx.send_data = send_data;
134 userctx.receive = receive;
135
136 return _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC,
137 KRB5_PLUGIN_SEND_TO_KDC_VERSION_2, 0,
138 &userctx, realmcallback);
139 }
140
141 struct krb5_sendto_ctx_data {
142 int flags;
143 int type;
144 krb5_sendto_ctx_func func;
145 void *data;
146 char *hostname;
147 krb5_krbhst_handle krbhst;
148
149 /* context2 */
150 const krb5_data *send_data;
151 krb5_data response;
152 heim_array_t hosts;
153 int stateflags;
154 #define KRBHST_COMPLETED 1
155
156 /* prexmit */
157 krb5_sendto_prexmit prexmit_func;
158 void *prexmit_ctx;
159
160 /* stats */
161 struct {
162 struct timeval start_time;
163 struct timeval name_resolution;
164 struct timeval krbhst;
165 unsigned long sent_packets;
166 unsigned long num_hosts;
167 } stats;
168 unsigned int stid;
169 };
170
171 static void
dealloc_sendto_ctx(void * ptr)172 dealloc_sendto_ctx(void *ptr)
173 {
174 krb5_sendto_ctx ctx = (krb5_sendto_ctx)ptr;
175 if (ctx->hostname)
176 free(ctx->hostname);
177 heim_release(ctx->hosts);
178 heim_release(ctx->krbhst);
179 }
180
181 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_sendto_ctx_alloc(krb5_context context,krb5_sendto_ctx * ctx)182 krb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx)
183 {
184 *ctx = heim_alloc(sizeof(**ctx), "sendto-context", dealloc_sendto_ctx);
185 if (*ctx == NULL)
186 return krb5_enomem(context);
187 (*ctx)->hosts = heim_array_create();
188
189 return 0;
190 }
191
192 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
krb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx,int flags)193 krb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx, int flags)
194 {
195 ctx->flags |= flags;
196 }
197
198 KRB5_LIB_FUNCTION int KRB5_LIB_CALL
krb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx)199 krb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx)
200 {
201 return ctx->flags;
202 }
203
204 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
krb5_sendto_ctx_set_type(krb5_sendto_ctx ctx,int type)205 krb5_sendto_ctx_set_type(krb5_sendto_ctx ctx, int type)
206 {
207 ctx->type = type;
208 }
209
210 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
krb5_sendto_ctx_set_func(krb5_sendto_ctx ctx,krb5_sendto_ctx_func func,void * data)211 krb5_sendto_ctx_set_func(krb5_sendto_ctx ctx,
212 krb5_sendto_ctx_func func,
213 void *data)
214 {
215 ctx->func = func;
216 ctx->data = data;
217 }
218
219 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
_krb5_sendto_ctx_set_prexmit(krb5_sendto_ctx ctx,krb5_sendto_prexmit prexmit,void * data)220 _krb5_sendto_ctx_set_prexmit(krb5_sendto_ctx ctx,
221 krb5_sendto_prexmit prexmit,
222 void *data)
223 {
224 ctx->prexmit_func = prexmit;
225 ctx->prexmit_ctx = data;
226 }
227
228 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_sendto_set_hostname(krb5_context context,krb5_sendto_ctx ctx,const char * hostname)229 krb5_sendto_set_hostname(krb5_context context,
230 krb5_sendto_ctx ctx,
231 const char *hostname)
232 {
233 if (ctx->hostname == NULL)
234 free(ctx->hostname);
235 ctx->hostname = strdup(hostname);
236 if (ctx->hostname == NULL) {
237 krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
238 return ENOMEM;
239 }
240 return 0;
241 }
242
243 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
_krb5_sendto_ctx_set_krb5hst(krb5_context context,krb5_sendto_ctx ctx,krb5_krbhst_handle handle)244 _krb5_sendto_ctx_set_krb5hst(krb5_context context,
245 krb5_sendto_ctx ctx,
246 krb5_krbhst_handle handle)
247 {
248 heim_release(ctx->krbhst);
249 ctx->krbhst = heim_retain(handle);
250 }
251
252 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
krb5_sendto_ctx_free(krb5_context context,krb5_sendto_ctx ctx)253 krb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx)
254 {
255 heim_release(ctx);
256 }
257
258 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_kdc_retry(krb5_context context,krb5_sendto_ctx ctx,void * data,const krb5_data * reply,int * action)259 _krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data,
260 const krb5_data *reply, int *action)
261 {
262 krb5_error_code ret;
263 KRB_ERROR error;
264
265 if(krb5_rd_error(context, reply, &error))
266 return 0;
267
268 ret = krb5_error_from_rd_error(context, &error, NULL);
269 krb5_free_error_contents(context, &error);
270
271 switch(ret) {
272 case KRB5KRB_ERR_RESPONSE_TOO_BIG: {
273 if (krb5_sendto_ctx_get_flags(ctx) & KRB5_KRBHST_FLAGS_LARGE_MSG)
274 break;
275 krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG);
276 *action = KRB5_SENDTO_RESET;
277 break;
278 }
279 case KRB5KDC_ERR_SVC_UNAVAILABLE:
280 *action = KRB5_SENDTO_CONTINUE;
281 break;
282 }
283 return 0;
284 }
285
286 /*
287 *
288 */
289
290 struct host;
291
292 struct host_fun {
293 krb5_error_code (*prepare)(krb5_context, struct host *, const krb5_data *);
294 krb5_error_code (*send_fn)(krb5_context, struct host *);
295 krb5_error_code (*recv_fn)(krb5_context, struct host *, krb5_data *);
296 int ntries;
297 };
298
299 struct host {
300 enum host_state { CONNECT, CONNECTING, CONNECTED, WAITING_REPLY, DEAD } state;
301 krb5_krbhst_info *hi;
302 struct addrinfo *ai;
303 rk_socket_t fd;
304 struct host_fun *fun;
305 unsigned int tries;
306 time_t timeout;
307 krb5_data data;
308 unsigned int tid;
309 };
310
311 static void
312 debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
313 __attribute__ ((__format__ (__printf__, 4, 5)));
314
315 static void
debug_host(krb5_context context,int level,struct host * host,const char * fmt,...)316 debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
317 {
318 const char *proto = "unknown";
319 const char *state;
320 char name[NI_MAXHOST], port[NI_MAXSERV];
321 char *text = NULL;
322 va_list ap;
323 int ret;
324
325 if (!_krb5_have_debug(context, 5))
326 return;
327
328 va_start(ap, fmt);
329 ret = vasprintf(&text, fmt, ap);
330 va_end(ap);
331 if (ret == -1 || text == NULL)
332 return;
333
334 if (host->hi->proto == KRB5_KRBHST_HTTP)
335 proto = "http";
336 else if (host->hi->proto == KRB5_KRBHST_TCP)
337 proto = "tcp";
338 else if (host->hi->proto == KRB5_KRBHST_UDP)
339 proto = "udp";
340
341 if (getnameinfo(host->ai->ai_addr, host->ai->ai_addrlen,
342 name, sizeof(name), port, sizeof(port), NI_NUMERICHOST) != 0)
343 name[0] = '\0';
344
345 switch (host->state) {
346 case CONNECT: state = "CONNECT"; break;
347 case CONNECTING: state = "CONNECTING"; break;
348 case CONNECTED: state = "CONNECTED"; break;
349 case WAITING_REPLY: state = "WAITING_REPLY"; break;
350 case DEAD: state = "DEAD"; break;
351 default: state = "unknown"; break;
352 }
353
354 _krb5_debug(context, level, "%s: %s %s:%s (%s) state=%s tid: %08x", text,
355 proto, name, port, host->hi->hostname, state, host->tid);
356 free(text);
357 }
358
359
360 static void
deallocate_host(void * ptr)361 deallocate_host(void *ptr)
362 {
363 struct host *host = ptr;
364 if (!rk_IS_BAD_SOCKET(host->fd))
365 rk_closesocket(host->fd);
366 krb5_data_free(&host->data);
367 host->ai = NULL;
368 }
369
370 static void
host_dead(krb5_context context,struct host * host,const char * msg)371 host_dead(krb5_context context, struct host *host, const char *msg)
372 {
373 debug_host(context, 5, host, "%s", msg);
374 rk_closesocket(host->fd);
375 host->fd = rk_INVALID_SOCKET;
376 host->state = DEAD;
377 }
378
379 static krb5_error_code
send_stream(krb5_context context,struct host * host)380 send_stream(krb5_context context, struct host *host)
381 {
382 ssize_t len;
383
384 len = krb5_net_write(context, &host->fd, host->data.data, host->data.length);
385
386 if (len < 0)
387 return errno;
388 else if (len < host->data.length) {
389 host->data.length -= len;
390 memmove(host->data.data, ((uint8_t *)host->data.data) + len, host->data.length - len);
391 return -1;
392 } else {
393 krb5_data_free(&host->data);
394 return 0;
395 }
396 }
397
398 static krb5_error_code
recv_stream(krb5_context context,struct host * host)399 recv_stream(krb5_context context, struct host *host)
400 {
401 krb5_error_code ret;
402 size_t oldlen;
403 ssize_t sret;
404 int nbytes;
405
406 if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
407 return HEIM_NET_CONN_REFUSED;
408
409 if (context->max_msg_size - host->data.length < nbytes) {
410 krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
411 N_("TCP message from KDC too large %d", ""),
412 (int)(host->data.length + nbytes));
413 return KRB5KRB_ERR_FIELD_TOOLONG;
414 }
415
416 oldlen = host->data.length;
417
418 ret = krb5_data_realloc(&host->data, oldlen + nbytes + 1 /* NUL */);
419 if (ret)
420 return ret;
421
422 sret = krb5_net_read(context, &host->fd, ((uint8_t *)host->data.data) + oldlen, nbytes);
423 if (sret <= 0) {
424 ret = errno;
425 return ret;
426 }
427 host->data.length = oldlen + sret;
428 /* zero terminate for http transport */
429 ((uint8_t *)host->data.data)[host->data.length] = '\0';
430
431 return 0;
432 }
433
434 /*
435 *
436 */
437
438 static void
host_next_timeout(krb5_context context,struct host * host)439 host_next_timeout(krb5_context context, struct host *host)
440 {
441 host->timeout = context->kdc_timeout / host->fun->ntries;
442 if (host->timeout == 0)
443 host->timeout = 1;
444
445 host->timeout += time(NULL);
446 }
447
448 /*
449 * connected host
450 */
451
452 static void
host_connected(krb5_context context,krb5_sendto_ctx ctx,struct host * host)453 host_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
454 {
455 krb5_error_code ret;
456
457 host->state = CONNECTED;
458 /*
459 * Now prepare data to send to host
460 */
461 if (ctx->prexmit_func) {
462 krb5_data data;
463
464 krb5_data_zero(&data);
465
466 ret = ctx->prexmit_func(context, host->hi->proto,
467 ctx->prexmit_ctx, host->fd, &data);
468 if (ret == 0) {
469 if (data.length == 0) {
470 host_dead(context, host, "prexmit function didn't send data");
471 return;
472 }
473 ret = host->fun->prepare(context, host, &data);
474 krb5_data_free(&data);
475 }
476
477 } else {
478 ret = host->fun->prepare(context, host, ctx->send_data);
479 }
480 if (ret)
481 debug_host(context, 5, host, "failed to prexmit/prepare");
482 }
483
484 /*
485 * connect host
486 */
487
488 static void
host_connect(krb5_context context,krb5_sendto_ctx ctx,struct host * host)489 host_connect(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
490 {
491 krb5_krbhst_info *hi = host->hi;
492 struct addrinfo *ai = host->ai;
493
494 debug_host(context, 5, host, "connecting to host");
495
496 if (connect(host->fd, ai->ai_addr, ai->ai_addrlen) < 0) {
497 #ifdef HAVE_WINSOCK
498 if (WSAGetLastError() == WSAEWOULDBLOCK)
499 errno = EINPROGRESS;
500 #endif /* HAVE_WINSOCK */
501 if (errno == EINPROGRESS && (hi->proto == KRB5_KRBHST_HTTP || hi->proto == KRB5_KRBHST_TCP)) {
502 debug_host(context, 5, host, "connecting to %d", host->fd);
503 host->state = CONNECTING;
504 } else {
505 host_dead(context, host, "failed to connect");
506 }
507 } else {
508 host_connected(context, ctx, host);
509 }
510
511 host_next_timeout(context, host);
512 }
513
514 /*
515 * HTTP transport
516 */
517
518 static krb5_error_code
prepare_http(krb5_context context,struct host * host,const krb5_data * data)519 prepare_http(krb5_context context, struct host *host, const krb5_data *data)
520 {
521 char *str = NULL, *request = NULL;
522 krb5_error_code ret;
523 int len;
524
525 heim_assert(host->data.length == 0, "prepare_http called twice");
526
527 len = rk_base64_encode(data->data, data->length, &str);
528 if(len < 0)
529 return ENOMEM;
530
531 if (context->http_proxy)
532 ret = asprintf(&request, "GET http://%s/%s HTTP/1.0\r\n\r\n", host->hi->hostname, str);
533 else
534 ret = asprintf(&request, "GET /%s HTTP/1.0\r\n\r\n", str);
535 free(str);
536 if(ret < 0 || request == NULL)
537 return ENOMEM;
538
539 host->data.data = request;
540 host->data.length = strlen(request);
541
542 return 0;
543 }
544
545 static krb5_error_code
recv_http(krb5_context context,struct host * host,krb5_data * data)546 recv_http(krb5_context context, struct host *host, krb5_data *data)
547 {
548 krb5_error_code ret;
549 unsigned long rep_len;
550 size_t len;
551 char *p;
552
553 /*
554 * recv_stream returns a NUL terminated stream
555 */
556
557 ret = recv_stream(context, host);
558 if (ret)
559 return ret;
560
561 p = strstr(host->data.data, "\r\n\r\n");
562 if (p == NULL)
563 return -1;
564 p += 4;
565
566 len = host->data.length - (p - (char *)host->data.data);
567 if (len < 4)
568 return -1;
569
570 _krb5_get_int(p, &rep_len, 4);
571 if (len < rep_len)
572 return -1;
573
574 p += 4;
575
576 memmove(host->data.data, p, rep_len);
577 host->data.length = rep_len;
578
579 *data = host->data;
580 krb5_data_zero(&host->data);
581
582 return 0;
583 }
584
585 /*
586 * TCP transport
587 */
588
589 static krb5_error_code
prepare_tcp(krb5_context context,struct host * host,const krb5_data * data)590 prepare_tcp(krb5_context context, struct host *host, const krb5_data *data)
591 {
592 krb5_error_code ret;
593 krb5_storage *sp;
594
595 heim_assert(host->data.length == 0, "prepare_tcp called twice");
596
597 sp = krb5_storage_emem();
598 if (sp == NULL)
599 return ENOMEM;
600
601 ret = krb5_store_data(sp, *data);
602 if (ret) {
603 krb5_storage_free(sp);
604 return ret;
605 }
606 ret = krb5_storage_to_data(sp, &host->data);
607 krb5_storage_free(sp);
608
609 return ret;
610 }
611
612 static krb5_error_code
recv_tcp(krb5_context context,struct host * host,krb5_data * data)613 recv_tcp(krb5_context context, struct host *host, krb5_data *data)
614 {
615 krb5_error_code ret;
616 unsigned long pktlen;
617
618 ret = recv_stream(context, host);
619 if (ret)
620 return ret;
621
622 if (host->data.length < 4)
623 return -1;
624
625 _krb5_get_int(host->data.data, &pktlen, 4);
626
627 if (pktlen > host->data.length - 4)
628 return -1;
629
630 memmove(host->data.data, ((uint8_t *)host->data.data) + 4, host->data.length - 4);
631 host->data.length -= 4;
632
633 *data = host->data;
634 krb5_data_zero(&host->data);
635
636 return 0;
637 }
638
639 /*
640 * UDP transport
641 */
642
643 static krb5_error_code
prepare_udp(krb5_context context,struct host * host,const krb5_data * data)644 prepare_udp(krb5_context context, struct host *host, const krb5_data *data)
645 {
646 return krb5_data_copy(&host->data, data->data, data->length);
647 }
648
649 static krb5_error_code
send_udp(krb5_context context,struct host * host)650 send_udp(krb5_context context, struct host *host)
651 {
652 if (send(host->fd, host->data.data, host->data.length, 0) < 0)
653 return errno;
654 return 0;
655 }
656
657 static krb5_error_code
recv_udp(krb5_context context,struct host * host,krb5_data * data)658 recv_udp(krb5_context context, struct host *host, krb5_data *data)
659 {
660 krb5_error_code ret;
661 int nbytes;
662
663
664 if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
665 return HEIM_NET_CONN_REFUSED;
666
667 if (context->max_msg_size < nbytes) {
668 krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
669 N_("UDP message from KDC too large %d", ""),
670 (int)nbytes);
671 return KRB5KRB_ERR_FIELD_TOOLONG;
672 }
673
674 ret = krb5_data_alloc(data, nbytes);
675 if (ret)
676 return ret;
677
678 ret = recv(host->fd, data->data, data->length, 0);
679 if (ret < 0) {
680 ret = errno;
681 krb5_data_free(data);
682 return ret;
683 }
684 data->length = ret;
685
686 return 0;
687 }
688
689 static struct host_fun http_fun = {
690 prepare_http,
691 send_stream,
692 recv_http,
693 1
694 };
695 static struct host_fun tcp_fun = {
696 prepare_tcp,
697 send_stream,
698 recv_tcp,
699 1
700 };
701 static struct host_fun udp_fun = {
702 prepare_udp,
703 send_udp,
704 recv_udp,
705 3
706 };
707
708
709 /*
710 * Host state machine
711 */
712
713 static int
eval_host_state(krb5_context context,krb5_sendto_ctx ctx,struct host * host,int readable,int writeable)714 eval_host_state(krb5_context context,
715 krb5_sendto_ctx ctx,
716 struct host *host,
717 int readable, int writeable)
718 {
719 krb5_error_code ret;
720
721 if (host->state == CONNECT) {
722 /* check if its this host time to connect */
723 if (host->timeout < time(NULL))
724 host_connect(context, ctx, host);
725 return 0;
726 }
727
728 if (host->state == CONNECTING && writeable)
729 host_connected(context, ctx, host);
730
731 if (readable) {
732
733 debug_host(context, 5, host, "reading packet");
734
735 ret = host->fun->recv_fn(context, host, &ctx->response);
736 if (ret == -1) {
737 /* not done yet */
738 } else if (ret == 0) {
739 /* if recv_foo function returns 0, we have a complete reply */
740 debug_host(context, 5, host, "host completed");
741 return 1;
742 } else {
743 host_dead(context, host, "host disconnected");
744 }
745 }
746
747 /* check if there is anything to send, state might DEAD after read */
748 if (writeable && host->state == CONNECTED) {
749
750 ctx->stats.sent_packets++;
751
752 debug_host(context, 5, host, "writing packet");
753
754 ret = host->fun->send_fn(context, host);
755 if (ret == -1) {
756 /* not done yet */
757 } else if (ret) {
758 host_dead(context, host, "host dead, write failed");
759 } else
760 host->state = WAITING_REPLY;
761 }
762
763 return 0;
764 }
765
766 /*
767 *
768 */
769
770 static krb5_error_code
submit_request(krb5_context context,krb5_sendto_ctx ctx,krb5_krbhst_info * hi)771 submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi)
772 {
773 unsigned long submitted_host = 0;
774 krb5_boolean freeai = FALSE;
775 struct timeval nrstart, nrstop;
776 krb5_error_code ret;
777 struct addrinfo *ai = NULL, *a;
778 struct host *host;
779
780 ret = kdc_via_plugin(context, hi, context->kdc_timeout,
781 ctx->send_data, &ctx->response);
782 if (ret == 0) {
783 return 0;
784 } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
785 _krb5_debug(context, 5, "send via plugin failed %s: %d",
786 hi->hostname, ret);
787 return ret;
788 }
789
790 /*
791 * If we have a proxy, let use the address of the proxy instead of
792 * the KDC and let the proxy deal with the resolving of the KDC.
793 */
794
795 gettimeofday(&nrstart, NULL);
796
797 if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) {
798 char *proxy2 = strdup(context->http_proxy);
799 char *el, *proxy = proxy2;
800 struct addrinfo hints;
801 char portstr[NI_MAXSERV];
802 unsigned short nport;
803
804 if (proxy == NULL)
805 return ENOMEM;
806 if (strncmp(proxy, "http://", 7) == 0)
807 proxy += 7;
808
809 /* check for url terminating slash */
810 el = strchr(proxy, '/');
811 if (el != NULL)
812 *el = '\0';
813
814 /* check for port in hostname, used below as port */
815 el = strchr(proxy, ':');
816 if(el != NULL)
817 *el++ = '\0';
818
819 memset(&hints, 0, sizeof(hints));
820 hints.ai_family = PF_UNSPEC;
821 hints.ai_socktype = SOCK_STREAM;
822
823 /* On some systems ntohs(foo(..., htons(...))) causes shadowing */
824 nport = init_port(el, htons(80));
825 snprintf(portstr, sizeof(portstr), "%d", ntohs(nport));
826
827 ret = getaddrinfo(proxy, portstr, &hints, &ai);
828 free(proxy2);
829 if (ret)
830 return krb5_eai_to_heim_errno(ret, errno);
831
832 freeai = TRUE;
833
834 } else {
835 ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
836 if (ret)
837 return ret;
838 }
839
840 /* add up times */
841 gettimeofday(&nrstop, NULL);
842 timevalsub(&nrstop, &nrstart);
843 timevaladd(&ctx->stats.name_resolution, &nrstop);
844
845 ctx->stats.num_hosts++;
846
847 for (a = ai; a != NULL; a = a->ai_next) {
848 rk_socket_t fd;
849
850 fd = socket(a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);
851 if (rk_IS_BAD_SOCKET(fd))
852 continue;
853 rk_cloexec(fd);
854
855 #ifndef NO_LIMIT_FD_SETSIZE
856 if (fd >= FD_SETSIZE) {
857 _krb5_debug(context, 0, "fd too large for select");
858 rk_closesocket(fd);
859 continue;
860 }
861 #endif
862 socket_set_nonblocking(fd, 1);
863
864 host = heim_alloc(sizeof(*host), "sendto-host", deallocate_host);
865 if (host == NULL) {
866 if (freeai)
867 freeaddrinfo(ai);
868 rk_closesocket(fd);
869 return ENOMEM;
870 }
871 host->hi = hi;
872 host->fd = fd;
873 host->ai = a;
874 /* next version of stid */
875 host->tid = ctx->stid = (ctx->stid & 0xffff0000) | ((ctx->stid & 0xffff) + 1);
876
877 host->state = CONNECT;
878
879 switch (host->hi->proto) {
880 case KRB5_KRBHST_HTTP :
881 host->fun = &http_fun;
882 break;
883 case KRB5_KRBHST_TCP :
884 host->fun = &tcp_fun;
885 break;
886 case KRB5_KRBHST_UDP :
887 host->fun = &udp_fun;
888 break;
889 default:
890 heim_abort("undefined http transport protocol: %d", (int)host->hi->proto);
891 }
892
893 host->tries = host->fun->ntries;
894
895 /*
896 * Connect directly next host, wait a host_timeout for each next address.
897 * We try host_connect() here, checking the return code because as we do
898 * non-blocking connects, any error here indicates that the address is just
899 * offline. That is, it's something like "No route to host" which is not
900 * worth retrying. And so, we fail directly and immediately to the next
901 * address for this host without enqueueing the address for retries.
902 */
903 if (submitted_host == 0) {
904 host_connect(context, ctx, host);
905 if (host->state == DEAD)
906 continue;
907 } else {
908 debug_host(context, 5, host,
909 "Queuing host in future (in %ds), its the %lu address on the same name",
910 (int)(context->host_timeout * submitted_host), submitted_host + 1);
911 host->timeout = time(NULL) + (submitted_host * context->host_timeout);
912 }
913
914 heim_array_append_value(ctx->hosts, host);
915 heim_release(host);
916 submitted_host++;
917 }
918
919 if (freeai)
920 freeaddrinfo(ai);
921
922 if (submitted_host == 0)
923 return KRB5_KDC_UNREACH;
924
925 return 0;
926 }
927
928 struct wait_ctx {
929 krb5_context context;
930 krb5_sendto_ctx ctx;
931 fd_set rfds;
932 fd_set wfds;
933 rk_socket_t max_fd;
934 int got_reply;
935 time_t timenow;
936 };
937
938 static void
wait_setup(heim_object_t obj,void * iter_ctx,int * stop)939 wait_setup(heim_object_t obj, void *iter_ctx, int *stop)
940 {
941 struct wait_ctx *wait_ctx = iter_ctx;
942 struct host *h = (struct host *)obj;
943
944 if (h->state == CONNECT) {
945 if (h->timeout >= wait_ctx->timenow)
946 return;
947 host_connect(wait_ctx->context, wait_ctx->ctx, h);
948 }
949
950 /* skip dead hosts */
951 if (h->state == DEAD)
952 return;
953
954 /* if host timed out, dec tries and (retry or kill host) */
955 if (h->timeout < wait_ctx->timenow) {
956 heim_assert(h->tries != 0, "tries should not reach 0");
957 h->tries--;
958 if (h->tries == 0) {
959 host_dead(wait_ctx->context, h, "host timed out");
960 return;
961 } else {
962 debug_host(wait_ctx->context, 5, h, "retrying sending to");
963 host_next_timeout(wait_ctx->context, h);
964 host_connected(wait_ctx->context, wait_ctx->ctx, h);
965 }
966 }
967
968 #ifndef NO_LIMIT_FD_SETSIZE
969 heim_assert(h->fd < FD_SETSIZE, "fd too large");
970 #endif
971 switch (h->state) {
972 case WAITING_REPLY:
973 FD_SET(h->fd, &wait_ctx->rfds);
974 break;
975 case CONNECTING:
976 case CONNECTED:
977 FD_SET(h->fd, &wait_ctx->rfds);
978 FD_SET(h->fd, &wait_ctx->wfds);
979 break;
980 default:
981 debug_host(wait_ctx->context, 5, h, "invalid sendto host state");
982 heim_abort("invalid sendto host state");
983 }
984 if (h->fd > wait_ctx->max_fd || wait_ctx->max_fd == rk_INVALID_SOCKET)
985 wait_ctx->max_fd = h->fd;
986 }
987
988 static int
wait_filter_dead(heim_object_t obj,void * ctx)989 wait_filter_dead(heim_object_t obj, void *ctx)
990 {
991 struct host *h = (struct host *)obj;
992 return (int)((h->state == DEAD) ? true : false);
993 }
994
995 static void
wait_accelerate(heim_object_t obj,void * ctx,int * stop)996 wait_accelerate(heim_object_t obj, void *ctx, int *stop)
997 {
998 struct host *h = (struct host *)obj;
999
1000 if (h->state == CONNECT && h->timeout > 0)
1001 h->timeout--;
1002 }
1003
1004 static void
wait_process(heim_object_t obj,void * ctx,int * stop)1005 wait_process(heim_object_t obj, void *ctx, int *stop)
1006 {
1007 struct wait_ctx *wait_ctx = ctx;
1008 struct host *h = (struct host *)obj;
1009 int readable, writeable;
1010 heim_assert(h->state != DEAD, "dead host resurected");
1011
1012 #ifndef NO_LIMIT_FD_SETSIZE
1013 heim_assert(h->fd < FD_SETSIZE, "fd too large");
1014 #endif
1015 readable = FD_ISSET(h->fd, &wait_ctx->rfds);
1016 writeable = FD_ISSET(h->fd, &wait_ctx->wfds);
1017
1018 if (readable || writeable || h->state == CONNECT)
1019 wait_ctx->got_reply |= eval_host_state(wait_ctx->context, wait_ctx->ctx, h, readable, writeable);
1020
1021 /* if there is already a reply, just fall though the array */
1022 if (wait_ctx->got_reply)
1023 *stop = 1;
1024 }
1025
1026 static krb5_error_code
wait_response(krb5_context context,int * action,krb5_sendto_ctx ctx)1027 wait_response(krb5_context context, int *action, krb5_sendto_ctx ctx)
1028 {
1029 struct wait_ctx wait_ctx;
1030 struct timeval tv;
1031 int ret;
1032
1033 wait_ctx.context = context;
1034 wait_ctx.ctx = ctx;
1035 FD_ZERO(&wait_ctx.rfds);
1036 FD_ZERO(&wait_ctx.wfds);
1037 wait_ctx.max_fd = rk_INVALID_SOCKET;
1038
1039 /* oh, we have a reply, it must be a plugin that got it for us */
1040 if (ctx->response.length) {
1041 *action = KRB5_SENDTO_FILTER;
1042 return 0;
1043 }
1044
1045 wait_ctx.timenow = time(NULL);
1046
1047 heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_setup);
1048 heim_array_filter_f(ctx->hosts, &wait_ctx, wait_filter_dead);
1049
1050 if (heim_array_get_length(ctx->hosts) == 0) {
1051 if (ctx->stateflags & KRBHST_COMPLETED) {
1052 _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
1053 "trying to pulling more hosts");
1054 *action = KRB5_SENDTO_FAILED;
1055 } else {
1056 _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
1057 "and no more hosts -> failure");
1058 *action = KRB5_SENDTO_TIMEOUT;
1059 }
1060 return 0;
1061 }
1062
1063 if (wait_ctx.max_fd == rk_INVALID_SOCKET) {
1064 /*
1065 * If we don't find a host which can make progress, then
1066 * we accelerate the process by moving all of the contestants
1067 * up by 1s.
1068 */
1069 _krb5_debug(context, 5, "wait_response: moving the contestants forward");
1070 heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_accelerate);
1071 return 0;
1072 }
1073
1074 tv.tv_sec = 1;
1075 tv.tv_usec = 0;
1076
1077 ret = select(wait_ctx.max_fd + 1, &wait_ctx.rfds, &wait_ctx.wfds, NULL, &tv);
1078 if (ret < 0)
1079 return errno;
1080 if (ret == 0) {
1081 *action = KRB5_SENDTO_TIMEOUT;
1082 return 0;
1083 }
1084
1085 wait_ctx.got_reply = 0;
1086 heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_process);
1087 if (wait_ctx.got_reply)
1088 *action = KRB5_SENDTO_FILTER;
1089 else
1090 *action = KRB5_SENDTO_CONTINUE;
1091
1092 return 0;
1093 }
1094
1095 static void
reset_context(krb5_context context,krb5_sendto_ctx ctx)1096 reset_context(krb5_context context, krb5_sendto_ctx ctx)
1097 {
1098 krb5_data_free(&ctx->response);
1099 heim_release(ctx->hosts);
1100 ctx->hosts = heim_array_create();
1101 ctx->stateflags = 0;
1102 }
1103
1104
1105 /*
1106 *
1107 */
1108
1109 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_sendto_context(krb5_context context,krb5_sendto_ctx ctx,const krb5_data * send_data,krb5_const_realm realm,krb5_data * receive)1110 krb5_sendto_context(krb5_context context,
1111 krb5_sendto_ctx ctx,
1112 const krb5_data *send_data,
1113 krb5_const_realm realm,
1114 krb5_data *receive)
1115 {
1116 krb5_error_code ret = 0;
1117 krb5_krbhst_handle handle = NULL;
1118 struct timeval nrstart, nrstop, stop_time;
1119 int type, freectx = 0;
1120 int action;
1121 int numreset = 0;
1122
1123 krb5_data_zero(receive);
1124
1125 if (ctx == NULL) {
1126 ret = krb5_sendto_ctx_alloc(context, &ctx);
1127 if (ret)
1128 goto out;
1129 freectx = 1;
1130 }
1131
1132 ctx->stid = (context->num_kdc_requests++) << 16;
1133
1134 memset(&ctx->stats, 0, sizeof(ctx->stats));
1135 gettimeofday(&ctx->stats.start_time, NULL);
1136
1137 type = ctx->type;
1138 if (type == 0) {
1139 if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc)
1140 type = KRB5_KRBHST_ADMIN;
1141 else
1142 type = KRB5_KRBHST_KDC;
1143 }
1144
1145 ctx->send_data = send_data;
1146
1147 if ((int)send_data->length > context->large_msg_size)
1148 ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG;
1149
1150 /* loop until we get back a appropriate response */
1151
1152 action = KRB5_SENDTO_INITIAL;
1153
1154 while (action != KRB5_SENDTO_DONE && action != KRB5_SENDTO_FAILED) {
1155 krb5_krbhst_info *hi;
1156
1157 switch (action) {
1158 case KRB5_SENDTO_INITIAL:
1159 ret = realm_via_plugin(context, realm, context->kdc_timeout,
1160 send_data, &ctx->response);
1161 if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) {
1162 action = KRB5_SENDTO_DONE;
1163 break;
1164 }
1165 action = KRB5_SENDTO_KRBHST;
1166 /* FALLTHROUGH */
1167 case KRB5_SENDTO_KRBHST:
1168 if (ctx->krbhst == NULL) {
1169 ret = krb5_krbhst_init_flags(context, realm, type,
1170 ctx->flags, &handle);
1171 if (ret)
1172 goto out;
1173
1174 if (ctx->hostname) {
1175 ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname);
1176 if (ret)
1177 goto out;
1178 }
1179
1180 } else {
1181 handle = heim_retain(ctx->krbhst);
1182 }
1183 action = KRB5_SENDTO_TIMEOUT;
1184 /* FALLTHROUGH */
1185 case KRB5_SENDTO_TIMEOUT:
1186
1187 /*
1188 * If we completed, just got to next step
1189 */
1190
1191 if (ctx->stateflags & KRBHST_COMPLETED) {
1192 action = KRB5_SENDTO_CONTINUE;
1193 break;
1194 }
1195
1196 /*
1197 * Pull out next host, if there is no more, close the
1198 * handle and mark as completed.
1199 *
1200 * Collect time spent in krbhst (dns, plugin, etc)
1201 */
1202
1203
1204 gettimeofday(&nrstart, NULL);
1205
1206 ret = krb5_krbhst_next(context, handle, &hi);
1207
1208 gettimeofday(&nrstop, NULL);
1209 timevalsub(&nrstop, &nrstart);
1210 timevaladd(&ctx->stats.krbhst, &nrstop);
1211
1212 action = KRB5_SENDTO_CONTINUE;
1213 if (ret == 0) {
1214 _krb5_debug(context, 5, "submitting new requests to new host");
1215 if (submit_request(context, ctx, hi) != 0)
1216 action = KRB5_SENDTO_TIMEOUT;
1217 } else {
1218 _krb5_debug(context, 5, "out of hosts, waiting for replies");
1219 ctx->stateflags |= KRBHST_COMPLETED;
1220 }
1221
1222 break;
1223 case KRB5_SENDTO_CONTINUE:
1224
1225 ret = wait_response(context, &action, ctx);
1226 if (ret)
1227 goto out;
1228
1229 break;
1230 case KRB5_SENDTO_RESET:
1231 /* start over */
1232 _krb5_debug(context, 5,
1233 "krb5_sendto trying over again (reset): %d",
1234 numreset);
1235 reset_context(context, ctx);
1236 if (handle) {
1237 krb5_krbhst_free(context, handle);
1238 handle = NULL;
1239 }
1240 numreset++;
1241 if (numreset >= 3)
1242 action = KRB5_SENDTO_FAILED;
1243 else
1244 action = KRB5_SENDTO_KRBHST;
1245
1246 break;
1247 case KRB5_SENDTO_FILTER:
1248 /* default to next state, the filter function might modify this */
1249 action = KRB5_SENDTO_DONE;
1250
1251 if (ctx->func) {
1252 ret = (*ctx->func)(context, ctx, ctx->data,
1253 &ctx->response, &action);
1254 if (ret)
1255 goto out;
1256 }
1257 break;
1258 case KRB5_SENDTO_FAILED:
1259 ret = KRB5_KDC_UNREACH;
1260 break;
1261 case KRB5_SENDTO_DONE:
1262 ret = 0;
1263 break;
1264 default:
1265 heim_abort("invalid krb5_sendto_context state");
1266 }
1267 }
1268
1269 out:
1270 gettimeofday(&stop_time, NULL);
1271 timevalsub(&stop_time, &ctx->stats.start_time);
1272 if (ret == 0 && ctx->response.length) {
1273 *receive = ctx->response;
1274 krb5_data_zero(&ctx->response);
1275 } else {
1276 krb5_data_free(&ctx->response);
1277 krb5_clear_error_message (context);
1278 ret = KRB5_KDC_UNREACH;
1279 krb5_set_error_message(context, ret,
1280 N_("unable to reach any KDC in realm %s", ""),
1281 realm);
1282 }
1283
1284 _krb5_debug(context, 1,
1285 "%s %s done: %d hosts %lu packets %lu:"
1286 " wc: %jd.%06lu nr: %jd.%06lu kh: %jd.%06lu tid: %08x",
1287 __func__, realm, ret,
1288 ctx->stats.num_hosts, ctx->stats.sent_packets,
1289 (intmax_t)stop_time.tv_sec,
1290 (unsigned long)stop_time.tv_usec,
1291 (intmax_t)ctx->stats.name_resolution.tv_sec,
1292 (unsigned long)ctx->stats.name_resolution.tv_usec,
1293 (intmax_t)ctx->stats.krbhst.tv_sec,
1294 (unsigned long)ctx->stats.krbhst.tv_usec, ctx->stid);
1295
1296 if (freectx)
1297 krb5_sendto_ctx_free(context, ctx);
1298 else
1299 reset_context(context, ctx);
1300
1301 if (handle)
1302 krb5_krbhst_free(context, handle);
1303
1304 return ret;
1305 }
1306