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 58static int 59init_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 68struct 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 77static krb5_error_code KRB5_LIB_CALL 78kdccallback(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 89static krb5_error_code KRB5_LIB_CALL 90realmcallback(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 101static krb5_error_code 102kdc_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 121static krb5_error_code 122realm_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 141struct 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 171static void 172dealloc_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 181KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 182krb5_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 192KRB5_LIB_FUNCTION void KRB5_LIB_CALL 193krb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx, int flags) 194{ 195 ctx->flags |= flags; 196} 197 198KRB5_LIB_FUNCTION int KRB5_LIB_CALL 199krb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx) 200{ 201 return ctx->flags; 202} 203 204KRB5_LIB_FUNCTION void KRB5_LIB_CALL 205krb5_sendto_ctx_set_type(krb5_sendto_ctx ctx, int type) 206{ 207 ctx->type = type; 208} 209 210KRB5_LIB_FUNCTION void KRB5_LIB_CALL 211krb5_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 219KRB5_LIB_FUNCTION void KRB5_LIB_CALL 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 228KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 229krb5_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 243KRB5_LIB_FUNCTION void KRB5_LIB_CALL 244_krb5_sendto_ctx_set_krb5hst(krb5_context context, 245 krb5_sendto_ctx ctx, 246 krb5_krbhst_handle handle) 247{ 248 heim_release(ctx->krbhst); 249 ctx->krbhst = heim_retain(handle); 250} 251 252KRB5_LIB_FUNCTION void KRB5_LIB_CALL 253krb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx) 254{ 255 heim_release(ctx); 256} 257 258KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 259_krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data, 260 const krb5_data *reply, int *action) 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 290struct host; 291 292struct 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 299struct 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 311static void 312debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...) 313 __attribute__ ((__format__ (__printf__, 4, 5))); 314 315static void 316debug_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 360static void 361deallocate_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 370static void 371host_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 379static krb5_error_code 380send_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 398static krb5_error_code 399recv_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 438static void 439host_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 452static void 453host_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 488static void 489host_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 518static krb5_error_code 519prepare_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 545static krb5_error_code 546recv_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 589static krb5_error_code 590prepare_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 612static krb5_error_code 613recv_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 643static krb5_error_code 644prepare_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 649static krb5_error_code 650send_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 657static krb5_error_code 658recv_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 689static struct host_fun http_fun = { 690 prepare_http, 691 send_stream, 692 recv_http, 693 1 694}; 695static struct host_fun tcp_fun = { 696 prepare_tcp, 697 send_stream, 698 recv_tcp, 699 1 700}; 701static 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 713static int 714eval_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 770static krb5_error_code 771submit_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 928struct 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 938static void 939wait_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 988static int 989wait_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 995static void 996wait_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 1004static void 1005wait_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 1026static krb5_error_code 1027wait_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 1095static void 1096reset_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 1109KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1110krb5_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 1269out: 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