1 /* $NetBSD: send_to_kdc.c,v 1.7 2017/01/30 00:25:15 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 316 debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...) 317 { 318 const char *proto = "unknown"; 319 char name[NI_MAXHOST], port[NI_MAXSERV]; 320 char *text = NULL; 321 va_list ap; 322 int ret; 323 324 if (!_krb5_have_debug(context, 5)) 325 return; 326 327 va_start(ap, fmt); 328 ret = vasprintf(&text, fmt, ap); 329 va_end(ap); 330 if (ret == -1 || text == NULL) 331 return; 332 333 if (host->hi->proto == KRB5_KRBHST_HTTP) 334 proto = "http"; 335 else if (host->hi->proto == KRB5_KRBHST_TCP) 336 proto = "tcp"; 337 else if (host->hi->proto == KRB5_KRBHST_UDP) 338 proto = "udp"; 339 340 if (getnameinfo(host->ai->ai_addr, host->ai->ai_addrlen, 341 name, sizeof(name), port, sizeof(port), NI_NUMERICHOST) != 0) 342 name[0] = '\0'; 343 344 _krb5_debug(context, level, "%s: %s %s:%s (%s) tid: %08x", text, 345 proto, name, port, host->hi->hostname, host->tid); 346 free(text); 347 } 348 349 350 static void 351 deallocate_host(void *ptr) 352 { 353 struct host *host = ptr; 354 if (!rk_IS_BAD_SOCKET(host->fd)) 355 rk_closesocket(host->fd); 356 krb5_data_free(&host->data); 357 host->ai = NULL; 358 } 359 360 static void 361 host_dead(krb5_context context, struct host *host, const char *msg) 362 { 363 debug_host(context, 5, host, "%s", msg); 364 rk_closesocket(host->fd); 365 host->fd = rk_INVALID_SOCKET; 366 host->state = DEAD; 367 } 368 369 static krb5_error_code 370 send_stream(krb5_context context, struct host *host) 371 { 372 ssize_t len; 373 374 len = krb5_net_write(context, &host->fd, host->data.data, host->data.length); 375 376 if (len < 0) 377 return errno; 378 else if (len < host->data.length) { 379 host->data.length -= len; 380 memmove(host->data.data, ((uint8_t *)host->data.data) + len, host->data.length - len); 381 return -1; 382 } else { 383 krb5_data_free(&host->data); 384 return 0; 385 } 386 } 387 388 static krb5_error_code 389 recv_stream(krb5_context context, struct host *host) 390 { 391 krb5_error_code ret; 392 size_t oldlen; 393 ssize_t sret; 394 int nbytes; 395 396 if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0) 397 return HEIM_NET_CONN_REFUSED; 398 399 if (context->max_msg_size - host->data.length < nbytes) { 400 krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG, 401 N_("TCP message from KDC too large %d", ""), 402 (int)(host->data.length + nbytes)); 403 return KRB5KRB_ERR_FIELD_TOOLONG; 404 } 405 406 oldlen = host->data.length; 407 408 ret = krb5_data_realloc(&host->data, oldlen + nbytes + 1 /* NUL */); 409 if (ret) 410 return ret; 411 412 sret = krb5_net_read(context, &host->fd, ((uint8_t *)host->data.data) + oldlen, nbytes); 413 if (sret <= 0) { 414 ret = errno; 415 return ret; 416 } 417 host->data.length = oldlen + sret; 418 /* zero terminate for http transport */ 419 ((uint8_t *)host->data.data)[host->data.length] = '\0'; 420 421 return 0; 422 } 423 424 /* 425 * 426 */ 427 428 static void 429 host_next_timeout(krb5_context context, struct host *host) 430 { 431 host->timeout = context->kdc_timeout / host->fun->ntries; 432 if (host->timeout == 0) 433 host->timeout = 1; 434 435 host->timeout += time(NULL); 436 } 437 438 /* 439 * connected host 440 */ 441 442 static void 443 host_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host) 444 { 445 krb5_error_code ret; 446 447 host->state = CONNECTED; 448 /* 449 * Now prepare data to send to host 450 */ 451 if (ctx->prexmit_func) { 452 krb5_data data; 453 454 krb5_data_zero(&data); 455 456 ret = ctx->prexmit_func(context, host->hi->proto, 457 ctx->prexmit_ctx, host->fd, &data); 458 if (ret == 0) { 459 if (data.length == 0) { 460 host_dead(context, host, "prexmit function didn't send data"); 461 return; 462 } 463 ret = host->fun->prepare(context, host, &data); 464 krb5_data_free(&data); 465 } 466 467 } else { 468 ret = host->fun->prepare(context, host, ctx->send_data); 469 } 470 if (ret) 471 debug_host(context, 5, host, "failed to prexmit/prepare"); 472 } 473 474 /* 475 * connect host 476 */ 477 478 static void 479 host_connect(krb5_context context, krb5_sendto_ctx ctx, struct host *host) 480 { 481 krb5_krbhst_info *hi = host->hi; 482 struct addrinfo *ai = host->ai; 483 484 debug_host(context, 5, host, "connecting to host"); 485 486 if (connect(host->fd, ai->ai_addr, ai->ai_addrlen) < 0) { 487 #ifdef HAVE_WINSOCK 488 if (WSAGetLastError() == WSAEWOULDBLOCK) 489 errno = EINPROGRESS; 490 #endif /* HAVE_WINSOCK */ 491 if (errno == EINPROGRESS && (hi->proto == KRB5_KRBHST_HTTP || hi->proto == KRB5_KRBHST_TCP)) { 492 debug_host(context, 5, host, "connecting to %d", host->fd); 493 host->state = CONNECTING; 494 } else { 495 host_dead(context, host, "failed to connect"); 496 } 497 } else { 498 host_connected(context, ctx, host); 499 } 500 501 host_next_timeout(context, host); 502 } 503 504 /* 505 * HTTP transport 506 */ 507 508 static krb5_error_code 509 prepare_http(krb5_context context, struct host *host, const krb5_data *data) 510 { 511 char *str = NULL, *request = NULL; 512 krb5_error_code ret; 513 int len; 514 515 heim_assert(host->data.length == 0, "prepare_http called twice"); 516 517 len = rk_base64_encode(data->data, data->length, &str); 518 if(len < 0) 519 return ENOMEM; 520 521 if (context->http_proxy) 522 ret = asprintf(&request, "GET http://%s/%s HTTP/1.0\r\n\r\n", host->hi->hostname, str); 523 else 524 ret = asprintf(&request, "GET /%s HTTP/1.0\r\n\r\n", str); 525 free(str); 526 if(ret < 0 || request == NULL) 527 return ENOMEM; 528 529 host->data.data = request; 530 host->data.length = strlen(request); 531 532 return 0; 533 } 534 535 static krb5_error_code 536 recv_http(krb5_context context, struct host *host, krb5_data *data) 537 { 538 krb5_error_code ret; 539 unsigned long rep_len; 540 size_t len; 541 char *p; 542 543 /* 544 * recv_stream returns a NUL terminated stream 545 */ 546 547 ret = recv_stream(context, host); 548 if (ret) 549 return ret; 550 551 p = strstr(host->data.data, "\r\n\r\n"); 552 if (p == NULL) 553 return -1; 554 p += 4; 555 556 len = host->data.length - (p - (char *)host->data.data); 557 if (len < 4) 558 return -1; 559 560 _krb5_get_int(p, &rep_len, 4); 561 if (len < rep_len) 562 return -1; 563 564 p += 4; 565 566 memmove(host->data.data, p, rep_len); 567 host->data.length = rep_len; 568 569 *data = host->data; 570 krb5_data_zero(&host->data); 571 572 return 0; 573 } 574 575 /* 576 * TCP transport 577 */ 578 579 static krb5_error_code 580 prepare_tcp(krb5_context context, struct host *host, const krb5_data *data) 581 { 582 krb5_error_code ret; 583 krb5_storage *sp; 584 585 heim_assert(host->data.length == 0, "prepare_tcp called twice"); 586 587 sp = krb5_storage_emem(); 588 if (sp == NULL) 589 return ENOMEM; 590 591 ret = krb5_store_data(sp, *data); 592 if (ret) { 593 krb5_storage_free(sp); 594 return ret; 595 } 596 ret = krb5_storage_to_data(sp, &host->data); 597 krb5_storage_free(sp); 598 599 return ret; 600 } 601 602 static krb5_error_code 603 recv_tcp(krb5_context context, struct host *host, krb5_data *data) 604 { 605 krb5_error_code ret; 606 unsigned long pktlen; 607 608 ret = recv_stream(context, host); 609 if (ret) 610 return ret; 611 612 if (host->data.length < 4) 613 return -1; 614 615 _krb5_get_int(host->data.data, &pktlen, 4); 616 617 if (pktlen > host->data.length - 4) 618 return -1; 619 620 memmove(host->data.data, ((uint8_t *)host->data.data) + 4, host->data.length - 4); 621 host->data.length -= 4; 622 623 *data = host->data; 624 krb5_data_zero(&host->data); 625 626 return 0; 627 } 628 629 /* 630 * UDP transport 631 */ 632 633 static krb5_error_code 634 prepare_udp(krb5_context context, struct host *host, const krb5_data *data) 635 { 636 return krb5_data_copy(&host->data, data->data, data->length); 637 } 638 639 static krb5_error_code 640 send_udp(krb5_context context, struct host *host) 641 { 642 if (send(host->fd, host->data.data, host->data.length, 0) < 0) 643 return errno; 644 return 0; 645 } 646 647 static krb5_error_code 648 recv_udp(krb5_context context, struct host *host, krb5_data *data) 649 { 650 krb5_error_code ret; 651 int nbytes; 652 653 654 if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0) 655 return HEIM_NET_CONN_REFUSED; 656 657 if (context->max_msg_size < nbytes) { 658 krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG, 659 N_("UDP message from KDC too large %d", ""), 660 (int)nbytes); 661 return KRB5KRB_ERR_FIELD_TOOLONG; 662 } 663 664 ret = krb5_data_alloc(data, nbytes); 665 if (ret) 666 return ret; 667 668 ret = recv(host->fd, data->data, data->length, 0); 669 if (ret < 0) { 670 ret = errno; 671 krb5_data_free(data); 672 return ret; 673 } 674 data->length = ret; 675 676 return 0; 677 } 678 679 static struct host_fun http_fun = { 680 prepare_http, 681 send_stream, 682 recv_http, 683 1 684 }; 685 static struct host_fun tcp_fun = { 686 prepare_tcp, 687 send_stream, 688 recv_tcp, 689 1 690 }; 691 static struct host_fun udp_fun = { 692 prepare_udp, 693 send_udp, 694 recv_udp, 695 3 696 }; 697 698 699 /* 700 * Host state machine 701 */ 702 703 static int 704 eval_host_state(krb5_context context, 705 krb5_sendto_ctx ctx, 706 struct host *host, 707 int readable, int writeable) 708 { 709 krb5_error_code ret; 710 711 if (host->state == CONNECT) { 712 /* check if its this host time to connect */ 713 if (host->timeout < time(NULL)) 714 host_connect(context, ctx, host); 715 return 0; 716 } 717 718 if (host->state == CONNECTING && writeable) 719 host_connected(context, ctx, host); 720 721 if (readable) { 722 723 debug_host(context, 5, host, "reading packet"); 724 725 ret = host->fun->recv_fn(context, host, &ctx->response); 726 if (ret == -1) { 727 /* not done yet */ 728 } else if (ret == 0) { 729 /* if recv_foo function returns 0, we have a complete reply */ 730 debug_host(context, 5, host, "host completed"); 731 return 1; 732 } else { 733 host_dead(context, host, "host disconnected"); 734 } 735 } 736 737 /* check if there is anything to send, state might DEAD after read */ 738 if (writeable && host->state == CONNECTED) { 739 740 ctx->stats.sent_packets++; 741 742 debug_host(context, 5, host, "writing packet"); 743 744 ret = host->fun->send_fn(context, host); 745 if (ret == -1) { 746 /* not done yet */ 747 } else if (ret) { 748 host_dead(context, host, "host dead, write failed"); 749 } else 750 host->state = WAITING_REPLY; 751 } 752 753 return 0; 754 } 755 756 /* 757 * 758 */ 759 760 static krb5_error_code 761 submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi) 762 { 763 unsigned long submitted_host = 0; 764 krb5_boolean freeai = FALSE; 765 struct timeval nrstart, nrstop; 766 krb5_error_code ret; 767 struct addrinfo *ai = NULL, *a; 768 struct host *host; 769 770 ret = kdc_via_plugin(context, hi, context->kdc_timeout, 771 ctx->send_data, &ctx->response); 772 if (ret == 0) { 773 return 0; 774 } else if (ret != KRB5_PLUGIN_NO_HANDLE) { 775 _krb5_debug(context, 5, "send via plugin failed %s: %d", 776 hi->hostname, ret); 777 return ret; 778 } 779 780 /* 781 * If we have a proxy, let use the address of the proxy instead of 782 * the KDC and let the proxy deal with the resolving of the KDC. 783 */ 784 785 gettimeofday(&nrstart, NULL); 786 787 if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) { 788 char *proxy2 = strdup(context->http_proxy); 789 char *el, *proxy = proxy2; 790 struct addrinfo hints; 791 char portstr[NI_MAXSERV]; 792 unsigned short nport; 793 794 if (proxy == NULL) 795 return ENOMEM; 796 if (strncmp(proxy, "http://", 7) == 0) 797 proxy += 7; 798 799 /* check for url terminating slash */ 800 el = strchr(proxy, '/'); 801 if (el != NULL) 802 *el = '\0'; 803 804 /* check for port in hostname, used below as port */ 805 el = strchr(proxy, ':'); 806 if(el != NULL) 807 *el++ = '\0'; 808 809 memset(&hints, 0, sizeof(hints)); 810 hints.ai_family = PF_UNSPEC; 811 hints.ai_socktype = SOCK_STREAM; 812 813 /* On some systems ntohs(foo(..., htons(...))) causes shadowing */ 814 nport = init_port(el, htons(80)); 815 snprintf(portstr, sizeof(portstr), "%d", ntohs(nport)); 816 817 ret = getaddrinfo(proxy, portstr, &hints, &ai); 818 free(proxy2); 819 if (ret) 820 return krb5_eai_to_heim_errno(ret, errno); 821 822 freeai = TRUE; 823 824 } else { 825 ret = krb5_krbhst_get_addrinfo(context, hi, &ai); 826 if (ret) 827 return ret; 828 } 829 830 /* add up times */ 831 gettimeofday(&nrstop, NULL); 832 timevalsub(&nrstop, &nrstart); 833 timevaladd(&ctx->stats.name_resolution, &nrstop); 834 835 ctx->stats.num_hosts++; 836 837 for (a = ai; a != NULL; a = a->ai_next) { 838 rk_socket_t fd; 839 840 fd = socket(a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol); 841 if (rk_IS_BAD_SOCKET(fd)) 842 continue; 843 rk_cloexec(fd); 844 845 #ifndef NO_LIMIT_FD_SETSIZE 846 if (fd >= FD_SETSIZE) { 847 _krb5_debug(context, 0, "fd too large for select"); 848 rk_closesocket(fd); 849 continue; 850 } 851 #endif 852 socket_set_nonblocking(fd, 1); 853 854 host = heim_alloc(sizeof(*host), "sendto-host", deallocate_host); 855 if (host == NULL) { 856 if (freeai) 857 freeaddrinfo(ai); 858 rk_closesocket(fd); 859 return ENOMEM; 860 } 861 host->hi = hi; 862 host->fd = fd; 863 host->ai = a; 864 /* next version of stid */ 865 host->tid = ctx->stid = (ctx->stid & 0xffff0000) | ((ctx->stid & 0xffff) + 1); 866 867 host->state = CONNECT; 868 869 switch (host->hi->proto) { 870 case KRB5_KRBHST_HTTP : 871 host->fun = &http_fun; 872 break; 873 case KRB5_KRBHST_TCP : 874 host->fun = &tcp_fun; 875 break; 876 case KRB5_KRBHST_UDP : 877 host->fun = &udp_fun; 878 break; 879 default: 880 heim_abort("undefined http transport protocol: %d", (int)host->hi->proto); 881 } 882 883 host->tries = host->fun->ntries; 884 885 /* 886 * Connect directly next host, wait a host_timeout for each next address 887 */ 888 if (submitted_host == 0) 889 host_connect(context, ctx, host); 890 else { 891 debug_host(context, 5, host, 892 "Queuing host in future (in %ds), its the %lu address on the same name", 893 (int)(context->host_timeout * submitted_host), submitted_host + 1); 894 host->timeout = time(NULL) + (submitted_host * context->host_timeout); 895 } 896 897 heim_array_append_value(ctx->hosts, host); 898 899 heim_release(host); 900 901 submitted_host++; 902 } 903 904 if (freeai) 905 freeaddrinfo(ai); 906 907 if (!submitted_host) 908 return KRB5_KDC_UNREACH; 909 910 return 0; 911 } 912 913 struct wait_ctx { 914 krb5_context context; 915 krb5_sendto_ctx ctx; 916 fd_set rfds; 917 fd_set wfds; 918 unsigned max_fd; 919 int got_reply; 920 time_t timenow; 921 }; 922 923 static void 924 wait_setup(heim_object_t obj, void *iter_ctx, int *stop) 925 { 926 struct wait_ctx *wait_ctx = iter_ctx; 927 struct host *h = (struct host *)obj; 928 929 /* skip dead hosts */ 930 if (h->state == DEAD) 931 return; 932 933 if (h->state == CONNECT) { 934 if (h->timeout < wait_ctx->timenow) 935 host_connect(wait_ctx->context, wait_ctx->ctx, h); 936 return; 937 } 938 939 /* if host timed out, dec tries and (retry or kill host) */ 940 if (h->timeout < wait_ctx->timenow) { 941 heim_assert(h->tries != 0, "tries should not reach 0"); 942 h->tries--; 943 if (h->tries == 0) { 944 host_dead(wait_ctx->context, h, "host timed out"); 945 return; 946 } else { 947 debug_host(wait_ctx->context, 5, h, "retrying sending to"); 948 host_next_timeout(wait_ctx->context, h); 949 host_connected(wait_ctx->context, wait_ctx->ctx, h); 950 } 951 } 952 953 #ifndef NO_LIMIT_FD_SETSIZE 954 heim_assert(h->fd < FD_SETSIZE, "fd too large"); 955 #endif 956 switch (h->state) { 957 case WAITING_REPLY: 958 FD_SET(h->fd, &wait_ctx->rfds); 959 break; 960 case CONNECTING: 961 case CONNECTED: 962 FD_SET(h->fd, &wait_ctx->rfds); 963 FD_SET(h->fd, &wait_ctx->wfds); 964 break; 965 default: 966 heim_abort("invalid sendto host state"); 967 } 968 if (h->fd > wait_ctx->max_fd) 969 wait_ctx->max_fd = h->fd; 970 } 971 972 static int 973 wait_filter_dead(heim_object_t obj, void *ctx) 974 { 975 struct host *h = (struct host *)obj; 976 return (int)((h->state == DEAD) ? true : false); 977 } 978 979 static void 980 wait_process(heim_object_t obj, void *ctx, int *stop) 981 { 982 struct wait_ctx *wait_ctx = ctx; 983 struct host *h = (struct host *)obj; 984 int readable, writeable; 985 heim_assert(h->state != DEAD, "dead host resurected"); 986 987 #ifndef NO_LIMIT_FD_SETSIZE 988 heim_assert(h->fd < FD_SETSIZE, "fd too large"); 989 #endif 990 readable = FD_ISSET(h->fd, &wait_ctx->rfds); 991 writeable = FD_ISSET(h->fd, &wait_ctx->wfds); 992 993 if (readable || writeable || h->state == CONNECT) 994 wait_ctx->got_reply |= eval_host_state(wait_ctx->context, wait_ctx->ctx, h, readable, writeable); 995 996 /* if there is already a reply, just fall though the array */ 997 if (wait_ctx->got_reply) 998 *stop = 1; 999 } 1000 1001 static krb5_error_code 1002 wait_response(krb5_context context, int *action, krb5_sendto_ctx ctx) 1003 { 1004 struct wait_ctx wait_ctx; 1005 struct timeval tv; 1006 int ret; 1007 1008 wait_ctx.context = context; 1009 wait_ctx.ctx = ctx; 1010 FD_ZERO(&wait_ctx.rfds); 1011 FD_ZERO(&wait_ctx.wfds); 1012 wait_ctx.max_fd = 0; 1013 1014 /* oh, we have a reply, it must be a plugin that got it for us */ 1015 if (ctx->response.length) { 1016 *action = KRB5_SENDTO_FILTER; 1017 return 0; 1018 } 1019 1020 wait_ctx.timenow = time(NULL); 1021 1022 heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_setup); 1023 heim_array_filter_f(ctx->hosts, &wait_ctx, wait_filter_dead); 1024 1025 if (heim_array_get_length(ctx->hosts) == 0) { 1026 if (ctx->stateflags & KRBHST_COMPLETED) { 1027 _krb5_debug(context, 5, "no more hosts to send/recv packets to/from " 1028 "trying to pulling more hosts"); 1029 *action = KRB5_SENDTO_FAILED; 1030 } else { 1031 _krb5_debug(context, 5, "no more hosts to send/recv packets to/from " 1032 "and no more hosts -> failure"); 1033 *action = KRB5_SENDTO_TIMEOUT; 1034 } 1035 return 0; 1036 } 1037 1038 tv.tv_sec = 1; 1039 tv.tv_usec = 0; 1040 1041 ret = select(wait_ctx.max_fd + 1, &wait_ctx.rfds, &wait_ctx.wfds, NULL, &tv); 1042 if (ret < 0) 1043 return errno; 1044 if (ret == 0) { 1045 *action = KRB5_SENDTO_TIMEOUT; 1046 return 0; 1047 } 1048 1049 wait_ctx.got_reply = 0; 1050 heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_process); 1051 if (wait_ctx.got_reply) 1052 *action = KRB5_SENDTO_FILTER; 1053 else 1054 *action = KRB5_SENDTO_CONTINUE; 1055 1056 return 0; 1057 } 1058 1059 static void 1060 reset_context(krb5_context context, krb5_sendto_ctx ctx) 1061 { 1062 krb5_data_free(&ctx->response); 1063 heim_release(ctx->hosts); 1064 ctx->hosts = heim_array_create(); 1065 ctx->stateflags = 0; 1066 } 1067 1068 1069 /* 1070 * 1071 */ 1072 1073 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1074 krb5_sendto_context(krb5_context context, 1075 krb5_sendto_ctx ctx, 1076 const krb5_data *send_data, 1077 krb5_const_realm realm, 1078 krb5_data *receive) 1079 { 1080 krb5_error_code ret = 0; 1081 krb5_krbhst_handle handle = NULL; 1082 struct timeval nrstart, nrstop, stop_time; 1083 int type, freectx = 0; 1084 int action; 1085 int numreset = 0; 1086 1087 krb5_data_zero(receive); 1088 1089 if (ctx == NULL) { 1090 ret = krb5_sendto_ctx_alloc(context, &ctx); 1091 if (ret) 1092 goto out; 1093 freectx = 1; 1094 } 1095 1096 ctx->stid = (context->num_kdc_requests++) << 16; 1097 1098 memset(&ctx->stats, 0, sizeof(ctx->stats)); 1099 gettimeofday(&ctx->stats.start_time, NULL); 1100 1101 type = ctx->type; 1102 if (type == 0) { 1103 if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc) 1104 type = KRB5_KRBHST_ADMIN; 1105 else 1106 type = KRB5_KRBHST_KDC; 1107 } 1108 1109 ctx->send_data = send_data; 1110 1111 if ((int)send_data->length > context->large_msg_size) 1112 ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG; 1113 1114 /* loop until we get back a appropriate response */ 1115 1116 action = KRB5_SENDTO_INITIAL; 1117 1118 while (action != KRB5_SENDTO_DONE && action != KRB5_SENDTO_FAILED) { 1119 krb5_krbhst_info *hi; 1120 1121 switch (action) { 1122 case KRB5_SENDTO_INITIAL: 1123 ret = realm_via_plugin(context, realm, context->kdc_timeout, 1124 send_data, &ctx->response); 1125 if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) { 1126 action = KRB5_SENDTO_DONE; 1127 break; 1128 } 1129 action = KRB5_SENDTO_KRBHST; 1130 /* FALLTHOUGH */ 1131 case KRB5_SENDTO_KRBHST: 1132 if (ctx->krbhst == NULL) { 1133 ret = krb5_krbhst_init_flags(context, realm, type, 1134 ctx->flags, &handle); 1135 if (ret) 1136 goto out; 1137 1138 if (ctx->hostname) { 1139 ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname); 1140 if (ret) 1141 goto out; 1142 } 1143 1144 } else { 1145 handle = heim_retain(ctx->krbhst); 1146 } 1147 action = KRB5_SENDTO_TIMEOUT; 1148 /* FALLTHOUGH */ 1149 case KRB5_SENDTO_TIMEOUT: 1150 1151 /* 1152 * If we completed, just got to next step 1153 */ 1154 1155 if (ctx->stateflags & KRBHST_COMPLETED) { 1156 action = KRB5_SENDTO_CONTINUE; 1157 break; 1158 } 1159 1160 /* 1161 * Pull out next host, if there is no more, close the 1162 * handle and mark as completed. 1163 * 1164 * Collect time spent in krbhst (dns, plugin, etc) 1165 */ 1166 1167 1168 gettimeofday(&nrstart, NULL); 1169 1170 ret = krb5_krbhst_next(context, handle, &hi); 1171 1172 gettimeofday(&nrstop, NULL); 1173 timevalsub(&nrstop, &nrstart); 1174 timevaladd(&ctx->stats.krbhst, &nrstop); 1175 1176 action = KRB5_SENDTO_CONTINUE; 1177 if (ret == 0) { 1178 _krb5_debug(context, 5, "submissing new requests to new host"); 1179 if (submit_request(context, ctx, hi) != 0) 1180 action = KRB5_SENDTO_TIMEOUT; 1181 } else { 1182 _krb5_debug(context, 5, "out of hosts, waiting for replies"); 1183 ctx->stateflags |= KRBHST_COMPLETED; 1184 } 1185 1186 break; 1187 case KRB5_SENDTO_CONTINUE: 1188 1189 ret = wait_response(context, &action, ctx); 1190 if (ret) 1191 goto out; 1192 1193 break; 1194 case KRB5_SENDTO_RESET: 1195 /* start over */ 1196 _krb5_debug(context, 5, 1197 "krb5_sendto trying over again (reset): %d", 1198 numreset); 1199 reset_context(context, ctx); 1200 if (handle) { 1201 krb5_krbhst_free(context, handle); 1202 handle = NULL; 1203 } 1204 numreset++; 1205 if (numreset >= 3) 1206 action = KRB5_SENDTO_FAILED; 1207 else 1208 action = KRB5_SENDTO_KRBHST; 1209 1210 break; 1211 case KRB5_SENDTO_FILTER: 1212 /* default to next state, the filter function might modify this */ 1213 action = KRB5_SENDTO_DONE; 1214 1215 if (ctx->func) { 1216 ret = (*ctx->func)(context, ctx, ctx->data, 1217 &ctx->response, &action); 1218 if (ret) 1219 goto out; 1220 } 1221 break; 1222 case KRB5_SENDTO_FAILED: 1223 ret = KRB5_KDC_UNREACH; 1224 break; 1225 case KRB5_SENDTO_DONE: 1226 ret = 0; 1227 break; 1228 default: 1229 heim_abort("invalid krb5_sendto_context state"); 1230 } 1231 } 1232 1233 out: 1234 gettimeofday(&stop_time, NULL); 1235 timevalsub(&stop_time, &ctx->stats.start_time); 1236 if (ret == 0 && ctx->response.length) { 1237 *receive = ctx->response; 1238 krb5_data_zero(&ctx->response); 1239 } else { 1240 krb5_data_free(&ctx->response); 1241 krb5_clear_error_message (context); 1242 ret = KRB5_KDC_UNREACH; 1243 krb5_set_error_message(context, ret, 1244 N_("unable to reach any KDC in realm %s", ""), 1245 realm); 1246 } 1247 1248 _krb5_debug(context, 1, 1249 "%s %s done: %d hosts %lu packets %lu:" 1250 " wc: %jd.%06ld nr: %jd.%06ld kh: %jd.%06ld tid: %08x", 1251 __func__, realm, ret, 1252 ctx->stats.num_hosts, ctx->stats.sent_packets, 1253 (intmax_t)stop_time.tv_sec, 1254 (long)stop_time.tv_usec, 1255 (intmax_t)ctx->stats.name_resolution.tv_sec, 1256 (long)ctx->stats.name_resolution.tv_usec, 1257 (intmax_t)ctx->stats.krbhst.tv_sec, 1258 (long)ctx->stats.krbhst.tv_usec, ctx->stid); 1259 1260 1261 if (freectx) 1262 krb5_sendto_ctx_free(context, ctx); 1263 else 1264 reset_context(context, ctx); 1265 1266 if (handle) 1267 krb5_krbhst_free(context, handle); 1268 1269 return ret; 1270 } 1271