1/* 2 * Copyright (c) 1997 - 2002 Kungliga Tekniska Högskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Portions Copyright (c) 2010 - 2013 Apple Inc. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * 3. Neither the name of the Institute nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36#include "krb5_locl.h" 37#include "send_to_kdc_plugin.h" 38 39/** 40 * @section send_to_kdc Locating and sending packets to the KDC 41 * 42 * The send to kdc code is responsible to request the list of KDC from 43 * the locate-kdc subsystem and then send requests to each of them. 44 * 45 * - Each second a new hostname is tried. 46 * - If the hostname have several addresses, the first will be tried 47 * directly then in turn the other will be tried every 3 seconds 48 * (host_timeout). 49 * - UDP requests are tried 3 times (ntries), and it tried with a individual timeout of kdc_timeout / ntries. 50 * - TCP and HTTP requests are tried 1 time. 51 * 52 * Total wait time is (number of addresses * 3) + kdc_timeout seconds. 53 * 54 */ 55 56static int 57init_port(const char *s, int fallback) 58{ 59 if (s) { 60 int tmp; 61 62 sscanf (s, "%d", &tmp); 63 return htons(tmp); 64 } else 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 struct heim_base_uniq base; 143 int flags; 144 int type; 145 krb5_sendto_ctx_func func; 146 void *data; 147 char *hostname; 148 krb5_krbhst_handle krbhst; 149 150 /* context2 */ 151 const krb5_data *send_data; 152 krb5_data response; 153 heim_array_t hosts; 154 const char *realm; 155 int stateflags; 156#define KRBHST_COMPLETED 1 157 158 /* prexmit */ 159 krb5_sendto_prexmit prexmit_func; 160 void *prexmit_ctx; 161 162 /* stats */ 163 struct { 164 struct timeval start_time; 165 struct timeval name_resolution; 166 struct timeval krbhst; 167 unsigned long sent_packets; 168 unsigned long num_hosts; 169 } stats; 170 unsigned int stid; 171}; 172 173static void 174dealloc_sendto_ctx(void *ptr) 175{ 176 krb5_sendto_ctx ctx = (krb5_sendto_ctx)ptr; 177 if (ctx->hostname) 178 free(ctx->hostname); 179 heim_release(ctx->hosts); 180 heim_release(ctx->krbhst); 181} 182 183KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 184krb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx) 185{ 186 *ctx = heim_uniq_alloc(sizeof(**ctx), "sendto-context", dealloc_sendto_ctx); 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_error_code 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)(krb5_context, struct host *); 295 krb5_error_code (*recv)(krb5_context, struct host *, krb5_data *); 296 int ntries; 297}; 298 299struct host { 300 struct heim_base_uniq base; 301 krb5_sendto_ctx ctx; 302 enum host_state { CONNECT, CONNECTING, CONNECTED, WAITING_REPLY, DEAD } state; 303 krb5_krbhst_info *hi; 304 struct addrinfo *ai; 305 rk_socket_t fd; 306 rk_socket_t fd2; 307 struct host_fun *fun; 308 unsigned int tries; 309 time_t timeout; 310 krb5_data data; 311 unsigned int tid; 312}; 313 314static void 315debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...) 316 __attribute__((__format__(__printf__, 4, 5))); 317 318static void 319debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...) 320{ 321 const char *proto = "unknown"; 322 char name[NI_MAXHOST], port[NI_MAXSERV]; 323 char *text = NULL; 324 va_list ap; 325 int ret; 326 327 if (!_krb5_have_debug(context, 5)) 328 return; 329 330 va_start(ap, fmt); 331 ret = vasprintf(&text, fmt, ap); 332 va_end(ap); 333 if (ret == -1 || text == NULL) 334 return; 335 336 if (host->hi->proto == KRB5_KRBHST_HTTP) 337 proto = "http"; 338 else if (host->hi->proto == KRB5_KRBHST_TCP) 339 proto = "tcp"; 340 else if (host->hi->proto == KRB5_KRBHST_UDP) 341 proto = "udp"; 342 else if (host->hi->proto == KRB5_KRBHST_KKDCP) 343 proto = "kkdcp"; 344 345 if (host->ai == NULL || 346 getnameinfo(host->ai->ai_addr, host->ai->ai_addrlen, 347 name, sizeof(name), port, sizeof(port), NI_NUMERICHOST) != 0) 348 { 349 name[0] = '\0'; 350 port[0] = '\0'; 351 } 352 353 _krb5_debugx(context, level, "%s: %s %s:%s (%s) tid: %08x", 354 text, proto, name, port, host->hi->hostname, host->tid); 355 free(text); 356} 357 358 359static void 360deallocate_host(void *ptr) 361{ 362 struct host *host = ptr; 363 if (!rk_IS_BAD_SOCKET(host->fd)) 364 rk_closesocket(host->fd); 365 if (!rk_IS_BAD_SOCKET(host->fd2)) 366 rk_closesocket(host->fd2); 367 krb5_data_free(&host->data); 368 host->ai = NULL; 369} 370 371static void 372host_dead(krb5_context context, struct host *host, const char *msg) 373{ 374 debug_host(context, 5, host, "%s", msg); 375 rk_closesocket(host->fd); 376 host->fd = rk_INVALID_SOCKET; 377 host->state = DEAD; 378} 379 380static krb5_error_code 381send_stream(krb5_context context, struct host *host) 382{ 383 ssize_t len; 384 385 len = write(host->fd, host->data.data, host->data.length); 386 387 if (len < 0) 388 return errno; 389 else if ((size_t)len < host->data.length) { 390 host->data.length -= len; 391 memmove(host->data.data, ((uint8_t *)host->data.data) + len, host->data.length - len); 392 return -1; 393 } else { 394 krb5_data_free(&host->data); 395 return 0; 396 } 397} 398 399static krb5_error_code 400recv_stream(krb5_context context, struct host *host) 401{ 402 krb5_error_code ret; 403 size_t oldlen; 404 ssize_t sret; 405 int nbytes; 406 407 if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0) { 408 debug_host(context, 5, host, "failed to get nbytes from socket, no bytes there?"); 409 return HEIM_NET_CONN_REFUSED; 410 } 411 412 if (context->max_msg_size - host->data.length < (size_t)nbytes) { 413 krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG, 414 N_("TCP message from KDC too large %d", ""), 415 (int)(host->data.length + nbytes)); 416 return KRB5KRB_ERR_FIELD_TOOLONG; 417 } 418 419 oldlen = host->data.length; 420 421 ret = krb5_data_realloc(&host->data, oldlen + nbytes + 1 /* NUL */); 422 if (ret) 423 return ret; 424 425 sret = read(host->fd, ((uint8_t *)host->data.data) + oldlen, nbytes); 426 if (sret <= 0) { 427 ret = errno; 428 debug_host(context, 5, host, "failed to read bytes from stream: %d", ret); 429 return ret; 430 } 431 host->data.length = oldlen + sret; 432 /* zero terminate for http transport */ 433 ((uint8_t *)host->data.data)[host->data.length] = '\0'; 434 435 return 0; 436} 437 438/* 439 * 440 */ 441 442static krb5_error_code 443send_kkdcp(krb5_context context, struct host *host) 444{ 445#ifdef __APPLE__ 446 dispatch_queue_t q; 447 char *url = NULL; 448 char *path = host->hi->path; 449 __block krb5_data data; 450 451 q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 452 453 heim_retain(host); 454 heim_retain(host->ctx); 455 456 if (path == NULL) 457 path = ""; 458 459 if (host->hi->def_port != host->hi->port) 460 asprintf(&url, "https://%s:%d/%s", host->hi->hostname, host->hi->port, path); 461 else 462 asprintf(&url, "https://%s/%s", host->hi->hostname, path); 463 if (url == NULL) 464 return ENOMEM; 465 466 data = host->data; 467 krb5_data_zero(&host->data); 468 469 debug_host(context, 5, host, "sending request to: %s", url); 470 471 heim_retain(context); 472 473 dispatch_async(q, ^{ 474 krb5_error_code ret; 475 krb5_data retdata; 476 477 krb5_data_zero(&retdata); 478 479 ret = _krb5_kkdcp_request(context, host->ctx->realm, url, 480 &data, &retdata); 481 krb5_data_free(&data); 482 free(url); 483 if (ret == 0) { 484 uint8_t length[4]; 485 486 debug_host(context, 5, host, "kkdcp: got %d bytes, feeding them back", (int)retdata.length); 487 488 _krb5_put_int(length, retdata.length, 4); 489 krb5_net_write_block(context, &host->fd2, length, sizeof(length), 2); 490 krb5_net_write_block(context, &host->fd2, retdata.data, retdata.length, 2); 491 } 492 493 close(host->fd2); 494 host->fd2 = -1; 495 heim_release(host->ctx); 496 heim_release(host); 497 heim_release(context); 498 }); 499 return 0; 500#else 501 close(host->fd2); 502 host->fd2 = -1; 503#endif 504} 505 506/* 507 * 508 */ 509 510static void 511host_next_timeout(krb5_context context, struct host *host) 512{ 513 host->timeout = context->kdc_timeout / host->fun->ntries; 514 if (host->timeout == 0) 515 host->timeout = 1; 516 517 host->timeout += time(NULL); 518} 519 520/* 521 * connected host 522 */ 523 524static void 525host_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host) 526{ 527 krb5_error_code ret; 528 529 host->state = CONNECTED; 530 /* 531 * Now prepare data to send to host 532 */ 533 if (ctx->prexmit_func) { 534 krb5_data data; 535 536 krb5_data_zero(&data); 537 538 ret = ctx->prexmit_func(context, host->hi->proto, 539 ctx->prexmit_ctx, host->fd, &data); 540 if (ret == 0) { 541 if (data.length == 0) { 542 host_dead(context, host, "prexmit function didn't send data"); 543 return; 544 } 545 ret = host->fun->prepare(context, host, &data); 546 krb5_data_free(&data); 547 } 548 549 } else { 550 ret = host->fun->prepare(context, host, ctx->send_data); 551 } 552 if (ret) 553 debug_host(context, 5, host, "failed to prexmit/prepare"); 554} 555 556/* 557 * connect host 558 */ 559 560static void 561host_connect(krb5_context context, krb5_sendto_ctx ctx, struct host *host) 562{ 563 krb5_krbhst_info *hi = host->hi; 564 struct addrinfo *ai = host->ai; 565 566 debug_host(context, 5, host, "connecting to host"); 567 568 if (connect(host->fd, ai->ai_addr, ai->ai_addrlen) < 0) { 569 if (errno == EINPROGRESS && (hi->proto == KRB5_KRBHST_HTTP || hi->proto == KRB5_KRBHST_TCP)) { 570 debug_host(context, 5, host, "connecting to %d", host->fd); 571 host->state = CONNECTING; 572 } else { 573 host_dead(context, host, "failed to connect"); 574 } 575 } else { 576 host_connected(context, ctx, host); 577 } 578 579 host_next_timeout(context, host); 580} 581 582/* 583 * HTTP transport 584 */ 585 586static krb5_error_code 587prepare_http(krb5_context context, struct host *host, const krb5_data *data) 588{ 589 char *str = NULL, *request = NULL; 590 krb5_error_code ret; 591 int len; 592 593 heim_assert(host->data.length == 0, "prepare_http called twice"); 594 595 len = base64_encode(data->data, (int)data->length, &str); 596 if(len < 0) 597 return ENOMEM; 598 599 if (context->http_proxy) 600 ret = asprintf(&request, "GET http://%s/%s HTTP/1.0\r\n\r\n", host->hi->hostname, str); 601 else 602 ret = asprintf(&request, "GET /%s HTTP/1.0\r\n\r\n", str); 603 free(str); 604 if(ret < 0 || request == NULL) 605 return ENOMEM; 606 607 host->data.data = request; 608 host->data.length = strlen(request); 609 610 return 0; 611} 612 613static krb5_error_code 614recv_http(krb5_context context, struct host *host, krb5_data *data) 615{ 616 krb5_error_code ret; 617 unsigned long rep_len; 618 size_t len; 619 char *p; 620 621 /* 622 * recv_stream returns a NUL terminated stream 623 */ 624 625 ret = recv_stream(context, host); 626 if (ret) 627 return ret; 628 629 p = strstr(host->data.data, "\r\n\r\n"); 630 if (p == NULL) 631 return -1; 632 p += 4; 633 634 len = host->data.length - (p - (char *)host->data.data); 635 if (len < 4) 636 return KRB5KRB_ERR_FIELD_TOOLONG; 637 638 _krb5_get_int(p, &rep_len, 4); 639 if (len < rep_len) 640 return -1; 641 642 p += 4; 643 644 memmove(host->data.data, p, rep_len); 645 host->data.length = rep_len; 646 647 *data = host->data; 648 krb5_data_zero(&host->data); 649 650 return 0; 651} 652 653/* 654 * TCP transport 655 */ 656 657static krb5_error_code 658prepare_tcp(krb5_context context, struct host *host, const krb5_data *data) 659{ 660 krb5_error_code ret; 661 krb5_storage *sp; 662 663 heim_assert(host->data.length == 0, "prepare_tcp called twice"); 664 665 sp = krb5_storage_emem(); 666 if (sp == NULL) 667 return ENOMEM; 668 669 ret = krb5_store_data(sp, *data); 670 if (ret) { 671 krb5_storage_free(sp); 672 return ret; 673 } 674 ret = krb5_storage_to_data(sp, &host->data); 675 krb5_storage_free(sp); 676 677 return ret; 678} 679 680static krb5_error_code 681recv_tcp(krb5_context context, struct host *host, krb5_data *data) 682{ 683 krb5_error_code ret; 684 unsigned long pktlen; 685 686 ret = recv_stream(context, host); 687 if (ret) 688 return ret; 689 690 if (host->data.length < 4) 691 return -1; 692 693 _krb5_get_int(host->data.data, &pktlen, 4); 694 695 if (pktlen > host->data.length - 4) 696 return -1; 697 698 memmove(host->data.data, ((uint8_t *)host->data.data) + 4, host->data.length - 4); 699 host->data.length -= 4; 700 701 *data = host->data; 702 krb5_data_zero(&host->data); 703 704 return 0; 705} 706 707/* 708 * UDP transport 709 */ 710 711static krb5_error_code 712prepare_udp(krb5_context context, struct host *host, const krb5_data *data) 713{ 714 return krb5_data_copy(&host->data, data->data, data->length); 715} 716 717static krb5_error_code 718send_udp(krb5_context context, struct host *host) 719{ 720 if (send(host->fd, host->data.data, host->data.length, 0) < 0) 721 return errno; 722 return 0; 723} 724 725static krb5_error_code 726recv_udp(krb5_context context, struct host *host, krb5_data *data) 727{ 728 krb5_error_code ret; 729 int nbytes; 730 ssize_t sret; 731 732 if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0) { 733 debug_host(context, 5, host, "failed to get nbytes from socket, no bytes there?"); 734 return HEIM_NET_CONN_REFUSED; 735 } 736 737 if (nbytes > context->max_msg_size) { 738 debug_host(context, 5, host, "server sent too large message %d (max is %d)", 739 (int)nbytes, (int)context->max_msg_size); 740 krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG, 741 N_("UDP message from KDC too large %d", ""), 742 (int)nbytes); 743 return KRB5KRB_ERR_FIELD_TOOLONG; 744 } 745 746 ret = krb5_data_alloc(data, nbytes); 747 if (ret) 748 return ret; 749 750 sret = recv(host->fd, data->data, data->length, 0); 751 if (sret < 0) { 752 debug_host(context, 5, host, "read data from nbytes from host: %d", errno); 753 ret = errno; 754 krb5_data_free(data); 755 return ret; 756 } 757 data->length = sret; 758 759 return 0; 760} 761 762static struct host_fun http_fun = { 763 prepare_http, 764 send_stream, 765 recv_http, 766 1 767}; 768static struct host_fun kkdcp_fun = { 769 prepare_udp, 770 send_kkdcp, 771 recv_tcp, 772 1 773}; 774static struct host_fun tcp_fun = { 775 prepare_tcp, 776 send_stream, 777 recv_tcp, 778 1 779}; 780static struct host_fun udp_fun = { 781 prepare_udp, 782 send_udp, 783 recv_udp, 784 3 785}; 786 787 788/* 789 * Host state machine 790 */ 791 792static int 793eval_host_state(krb5_context context, 794 krb5_sendto_ctx ctx, 795 struct host *host, 796 int readable, int writeable) 797{ 798 krb5_error_code ret; 799 800 if (host->state == CONNECTING && writeable) 801 host_connected(context, ctx, host); 802 803 if (readable) { 804 805 debug_host(context, 5, host, "reading packet"); 806 807 ret = host->fun->recv(context, host, &ctx->response); 808 if (ret == -1) { 809 /* not done yet */ 810 } else if (ret == 0) { 811 /* if recv_foo function returns 0, we have a complete reply */ 812 debug_host(context, 5, host, "host completed"); 813 return 1; 814 } else { 815 host_dead(context, host, "host disconnected"); 816 } 817 } 818 819 /* check if there is anything to send, state might DEAD after read */ 820 if (writeable && host->state == CONNECTED) { 821 822 ctx->stats.sent_packets++; 823 824 debug_host(context, 5, host, "writing packet"); 825 826 ret = host->fun->send(context, host); 827 if (ret == -1) { 828 /* not done yet */ 829 } else if (ret) { 830 host_dead(context, host, "host dead, write failed"); 831 } else 832 host->state = WAITING_REPLY; 833 } 834 835 return 0; 836} 837 838/* 839 * 840 */ 841 842static struct host * 843host_create(krb5_context context, 844 krb5_sendto_ctx ctx, 845 krb5_krbhst_info *hi, 846 struct addrinfo *ai, 847 int fd) 848{ 849 struct host *host; 850 851 host = heim_uniq_alloc(sizeof(*host), "sendto-host", deallocate_host); 852 if (host == NULL) 853 return ENOMEM; 854 855 host->hi = hi; 856 host->fd = fd; 857 host->fd2 = -1; 858 host->ai = ai; 859 host->ctx = ctx; 860 /* next version of stid */ 861 host->tid = ctx->stid = (ctx->stid & 0xffff0000) | ((ctx->stid & 0xffff) + 1); 862 863 host->state = CONNECT; 864 865 switch (host->hi->proto) { 866 case KRB5_KRBHST_HTTP : 867 host->fun = &http_fun; 868 break; 869 case KRB5_KRBHST_KKDCP : 870 host->fun = &kkdcp_fun; 871 break; 872 case KRB5_KRBHST_TCP : 873 host->fun = &tcp_fun; 874 break; 875 case KRB5_KRBHST_UDP : 876 host->fun = &udp_fun; 877 break; 878 } 879 880 host->tries = host->fun->ntries; 881 882 heim_array_append_value(ctx->hosts, host); 883 884 return host; 885} 886 887 888/* 889 * 890 */ 891 892static krb5_error_code 893submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi) 894{ 895 unsigned long submitted_host = 0; 896 krb5_boolean freeai = FALSE; 897 struct timeval nrstart, nrstop; 898 krb5_error_code ret; 899 struct addrinfo *ai = NULL, *a; 900 struct host *host; 901 902 ret = kdc_via_plugin(context, hi, context->kdc_timeout, 903 ctx->send_data, &ctx->response); 904 if (ret == 0) { 905 return 0; 906 } else if (ret != KRB5_PLUGIN_NO_HANDLE) { 907 _krb5_debugx(context, 5, "send via plugin failed %s: %d", 908 hi->hostname, ret); 909 return ret; 910 } 911 912 /* 913 * If we have a proxy, let use the address of the proxy instead of 914 * the KDC and let the proxy deal with the resolving of the KDC. 915 */ 916 917 gettimeofday(&nrstart, NULL); 918 919 if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) { 920 char *proxy2 = strdup(context->http_proxy); 921 char *el, *proxy = proxy2; 922 struct addrinfo hints; 923 char portstr[NI_MAXSERV]; 924 925 if (proxy == NULL) 926 return ENOMEM; 927 if (strncmp(proxy, "http://", 7) == 0) 928 proxy += 7; 929 930 /* check for url terminating slash */ 931 el = strchr(proxy, '/'); 932 if (el != NULL) 933 *el = '\0'; 934 935 /* check for port in hostname, used below as port */ 936 el = strchr(proxy, ':'); 937 if(el != NULL) 938 *el++ = '\0'; 939 940 memset(&hints, 0, sizeof(hints)); 941 hints.ai_family = PF_UNSPEC; 942 hints.ai_socktype = SOCK_STREAM; 943 944 snprintf(portstr, sizeof(portstr), "%d", 945 ntohs(init_port(el, htons(80)))); 946 947 ret = getaddrinfo(proxy, portstr, &hints, &ai); 948 free(proxy2); 949 if (ret) 950 return krb5_eai_to_heim_errno(ret, errno); 951 952 freeai = TRUE; 953 } else if (hi->proto == KRB5_KRBHST_KKDCP) { 954 ai = NULL; 955 } else { 956 ret = krb5_krbhst_get_addrinfo(context, hi, &ai); 957 if (ret) 958 return ret; 959 } 960 961 /* add up times */ 962 gettimeofday(&nrstop, NULL); 963 timevalsub(&nrstop, &nrstart); 964 timevaladd(&ctx->stats.name_resolution, &nrstop); 965 966 ctx->stats.num_hosts++; 967 968 for (a = ai; a != NULL; a = a->ai_next) { 969 rk_socket_t fd; 970 971 fd = socket(a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol); 972 if (rk_IS_BAD_SOCKET(fd)) 973 continue; 974 rk_cloexec(fd); 975 socket_set_nopipe(fd, 1); 976 socket_set_nonblocking(fd, 1); 977 978#ifndef NO_LIMIT_FD_SETSIZE 979 if (fd >= FD_SETSIZE) { 980 _krb5_debugx(context, 0, "fd too large for select"); 981 rk_closesocket(fd); 982 continue; 983 } 984#endif 985 host = host_create(context, ctx, hi, a, fd); 986 if (host == NULL) { 987 rk_closesocket(fd); 988 continue; 989 } 990 991 /* 992 * Connect directly next host, wait a host_timeout for each next address 993 */ 994 if (submitted_host == 0) 995 host_connect(context, ctx, host); 996 else { 997 debug_host(context, 5, host, 998 "Queuing host in future (in %ds), " 999 "its the %lu address on the same name", 1000 (int)(context->host_timeout * submitted_host), 1001 submitted_host + 1); 1002 host->timeout = time(NULL) + (submitted_host * context->host_timeout); 1003 } 1004 1005 submitted_host++; 1006 heim_release(host); 1007 } 1008 1009 if (hi->proto == KRB5_KRBHST_KKDCP) { 1010 int fds[2]; 1011 1012 heim_assert(ai == NULL, "kkdcp host with ai ?"); 1013 1014 if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) < 0) 1015 return KRB5_KDC_UNREACH; 1016 1017 socket_set_nopipe(fds[0], 1); 1018 socket_set_nopipe(fds[1], 1); 1019 socket_set_nonblocking(fds[0], 1); 1020 socket_set_nonblocking(fds[1], 1); 1021 1022 host = host_create(context, ctx, hi, NULL, fds[0]); 1023 if (host == NULL) { 1024 close(fds[0]); 1025 close(fds[1]); 1026 return ENOMEM; 1027 } 1028 host->fd2 = fds[1]; 1029 1030 host_next_timeout(context, host); 1031 host_connected(context, ctx, host); 1032 1033 submitted_host++; 1034 heim_release(host); 1035 } 1036 1037 1038 if (freeai) 1039 freeaddrinfo(ai); 1040 1041 if (!submitted_host) 1042 return KRB5_KDC_UNREACH; 1043 1044 return 0; 1045} 1046 1047static void 1048set_fd_status(struct host *h, fd_set *rfds, fd_set *wfds, int *max_fd) 1049{ 1050#ifndef NO_LIMIT_FD_SETSIZE 1051 heim_assert(h->fd < FD_SETSIZE, "fd too large"); 1052#endif 1053 switch (h->state) { 1054 case WAITING_REPLY: 1055 FD_SET(h->fd, rfds); 1056 break; 1057 case CONNECTING: 1058 case CONNECTED: 1059 FD_SET(h->fd, rfds); 1060 FD_SET(h->fd, wfds); 1061 break; 1062 case DEAD: 1063 case CONNECT: 1064 break; 1065 } 1066 if (h->fd > *max_fd) 1067 *max_fd = h->fd + 1; 1068} 1069 1070 1071 1072static krb5_error_code 1073wait_response(krb5_context context, int *action, krb5_sendto_ctx ctx) 1074{ 1075 __block struct host *next_pending = NULL; 1076 __block fd_set rfds, wfds; 1077 __block int max_fd = 0; 1078 __block int ret; 1079 struct timeval tv; 1080 time_t timenow; 1081 1082 FD_ZERO(&rfds); 1083 FD_ZERO(&wfds); 1084 1085 /* oh, we have a reply, it must be a plugin that got it for us */ 1086 if (ctx->response.length) { 1087 *action = KRB5_SENDTO_FILTER; 1088 return 0; 1089 } 1090 1091 timenow = time(NULL); 1092 1093 heim_array_iterate(ctx->hosts, ^(heim_object_t obj, int *stop) { 1094 struct host *h = (struct host *)obj; 1095 1096 /* skip dead hosts */ 1097 if (h->state == DEAD) 1098 return; 1099 1100 /* 1101 * process submitted by pending hosts here 1102 */ 1103 if (h->state == CONNECT) { 1104 if (h->timeout < timenow) { 1105 host_connect(context, ctx, h); 1106 } else if (next_pending == NULL || next_pending->timeout > h->timeout) { 1107 next_pending = h; 1108 return; 1109 } else { 1110 return; 1111 } 1112 } 1113 1114 /* if host timed out, dec tries and (retry or kill host) */ 1115 if (h->timeout < timenow) { 1116 heim_assert(h->tries != 0, "tries should not reach 0"); 1117 h->tries--; 1118 if (h->tries == 0) { 1119 host_dead(context, h, "host timed out"); 1120 return; 1121 } else { 1122 debug_host(context, 5, h, "retrying sending to"); 1123 host_next_timeout(context, h); 1124 host_connected(context, ctx, h); 1125 } 1126 } 1127 1128 set_fd_status(h, &rfds, &wfds, &max_fd); 1129 }); 1130 1131 /* 1132 * We have no host to wait for, but there is one pending, lets 1133 * kick that one off. 1134 */ 1135 if (max_fd == 0 && next_pending) { 1136 time_t forward = next_pending->timeout - timenow; 1137 1138 host_connect(context, ctx, next_pending); 1139 set_fd_status(next_pending, &rfds, &wfds, &max_fd); 1140 1141 /* 1142 * Move all waiting host forward in time too, only if the next 1143 * host didn't happen the same about the same time as the last 1144 * expiration 1145 */ 1146 if (forward > 0) { 1147 heim_array_iterate(ctx->hosts, ^(heim_object_t obj, int *stop) { 1148 struct host *h = (struct host *)obj; 1149 1150 if (h->state != CONNECT) 1151 return; 1152 h->timeout -= forward; 1153 if (h->timeout < timenow) 1154 h->timeout = timenow; 1155 }); 1156 } 1157 } 1158 1159 heim_array_filter(ctx->hosts, ^(heim_object_t obj) { 1160 struct host *h = (struct host *)obj; 1161 return (int)((h->state == DEAD) ? true : false); 1162 }); 1163 1164 if (heim_array_get_length(ctx->hosts) == 0) { 1165 if (ctx->stateflags & KRBHST_COMPLETED) { 1166 _krb5_debugx(context, 5, "no more hosts to send/recv packets to/from " 1167 "trying to pulling more hosts"); 1168 *action = KRB5_SENDTO_FAILED; 1169 } else { 1170 _krb5_debugx(context, 5, "no more hosts to send/recv packets to/from " 1171 "and no more hosts -> failure"); 1172 *action = KRB5_SENDTO_TIMEOUT; 1173 } 1174 return 0; 1175 } 1176 1177 tv.tv_sec = 1; 1178 tv.tv_usec = 0; 1179 1180 ret = select(max_fd + 1, &rfds, &wfds, NULL, &tv); 1181 if (ret < 0) { 1182 if (errno != EAGAIN || errno != EINTR) 1183 return errno; 1184 ret = 0; 1185 } 1186 if (ret == 0) { 1187 *action = KRB5_SENDTO_TIMEOUT; 1188 return 0; 1189 } 1190 1191 ret = 0; 1192 heim_array_iterate(ctx->hosts, ^(heim_object_t obj, int *stop) { 1193 struct host *h = (struct host *)obj; 1194 int readable, writeable; 1195 heim_assert(h->state != DEAD, "dead host resurected"); 1196 1197#ifndef NO_LIMIT_FD_SETSIZE 1198 heim_assert(h->fd < FD_SETSIZE, "fd too large"); 1199#endif 1200 readable = FD_ISSET(h->fd, &rfds); 1201 writeable = FD_ISSET(h->fd, &wfds); 1202 1203 if (readable || writeable) 1204 ret |= eval_host_state(context, ctx, h, readable, writeable); 1205 1206 /* if there is already a reply, just fall though the array */ 1207 if (ret) 1208 *stop = 1; 1209 }); 1210 if (ret) 1211 *action = KRB5_SENDTO_FILTER; 1212 else 1213 *action = KRB5_SENDTO_CONTINUE; 1214 1215 return 0; 1216} 1217 1218static void 1219reset_context(krb5_context context, krb5_sendto_ctx ctx) 1220{ 1221 krb5_data_free(&ctx->response); 1222 heim_release(ctx->hosts); 1223 ctx->hosts = heim_array_create(); 1224 ctx->stateflags = 0; 1225} 1226 1227 1228/* 1229 * 1230 */ 1231 1232KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1233krb5_sendto_context(krb5_context context, 1234 krb5_sendto_ctx ctx, 1235 const krb5_data *send_data, 1236 krb5_const_realm realm, 1237 krb5_data *receive) 1238{ 1239 krb5_error_code ret = KRB5_KDC_UNREACH; 1240 krb5_krbhst_handle handle = NULL; 1241 struct timeval nrstart, nrstop, stop_time; 1242 int type, freectx = 0; 1243 int action; 1244 int numreset = 0; 1245 1246 krb5_data_zero(receive); 1247 1248 HEIM_WARN_BLOCKING("krb5_sendto_context", warn_once); 1249 1250 if (ctx == NULL) { 1251 ret = krb5_sendto_ctx_alloc(context, &ctx); 1252 if (ret) 1253 goto out; 1254 freectx = 1; 1255 } 1256 1257 memset(&ctx->stats, 0, sizeof(ctx->stats)); 1258 gettimeofday(&ctx->stats.start_time, NULL); 1259 1260 ctx->realm = realm; 1261 ctx->stid = (context->num_kdc_requests++) << 16; 1262 1263 type = ctx->type; 1264 if (type == 0) { 1265 if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc) 1266 type = KRB5_KRBHST_ADMIN; 1267 else 1268 type = KRB5_KRBHST_KDC; 1269 } 1270 1271 ctx->send_data = send_data; 1272 1273 if ((int)send_data->length > context->large_msg_size) 1274 ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG; 1275 1276 /* loop until we get back a appropriate response */ 1277 1278 action = KRB5_SENDTO_INITIAL; 1279 1280 while (action != KRB5_SENDTO_DONE && action != KRB5_SENDTO_FAILED) { 1281 krb5_krbhst_info *hi; 1282 1283 switch (action) { 1284 case KRB5_SENDTO_INITIAL: 1285 ret = realm_via_plugin(context, realm, context->kdc_timeout, 1286 send_data, receive); 1287 if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) { 1288 action = KRB5_SENDTO_DONE; 1289 break; 1290 } 1291 action = KRB5_SENDTO_KRBHST; 1292 /* FALLTHOUGH */ 1293 case KRB5_SENDTO_KRBHST: 1294 if (ctx->krbhst == NULL) { 1295 ret = krb5_krbhst_init_flags(context, realm, type, 1296 ctx->flags, &handle); 1297 if (ret) 1298 goto out; 1299 1300 if (ctx->hostname) { 1301 ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname); 1302 if (ret) 1303 goto out; 1304 } 1305 } else { 1306 handle = heim_retain(ctx->krbhst); 1307 } 1308 action = KRB5_SENDTO_TIMEOUT; 1309 /* FALLTHOUGH */ 1310 case KRB5_SENDTO_TIMEOUT: 1311 1312 /* 1313 * If we completed, just got to next step 1314 */ 1315 1316 if (ctx->stateflags & KRBHST_COMPLETED) { 1317 action = KRB5_SENDTO_CONTINUE; 1318 break; 1319 } 1320 1321 /* 1322 * Pull out next host, if there is no more, close the 1323 * handle and mark as completed. 1324 * 1325 * Collect time spent in krbhst (dns, plugin, etc) 1326 */ 1327 1328 1329 gettimeofday(&nrstart, NULL); 1330 1331 ret = krb5_krbhst_next(context, handle, &hi); 1332 1333 gettimeofday(&nrstop, NULL); 1334 timevalsub(&nrstop, &nrstart); 1335 timevaladd(&ctx->stats.krbhst, &nrstop); 1336 1337 action = KRB5_SENDTO_CONTINUE; 1338 if (ret == 0) { 1339 _krb5_debugx(context, 5, "submissing new requests to new host"); 1340 if (submit_request(context, ctx, hi) != 0) 1341 action = KRB5_SENDTO_TIMEOUT; 1342 } else { 1343 _krb5_debugx(context, 5, "out of hosts, waiting for replies"); 1344 ctx->stateflags |= KRBHST_COMPLETED; 1345 } 1346 1347 break; 1348 case KRB5_SENDTO_CONTINUE: 1349 1350 ret = wait_response(context, &action, ctx); 1351 if (ret) 1352 goto out; 1353 1354 break; 1355 case KRB5_SENDTO_RESET: 1356 /* start over */ 1357 _krb5_debugx(context, 5, 1358 "krb5_sendto trying over again (reset): %d", 1359 numreset); 1360 reset_context(context, ctx); 1361 if (handle) { 1362 krb5_krbhst_free(context, handle); 1363 handle = NULL; 1364 } 1365 numreset++; 1366 if (numreset >= 3) 1367 action = KRB5_SENDTO_FAILED; 1368 else 1369 action = KRB5_SENDTO_KRBHST; 1370 1371 ret = 0; 1372 break; 1373 case KRB5_SENDTO_FILTER: 1374 /* default to next state, the filter function might modify this */ 1375 action = KRB5_SENDTO_DONE; 1376 1377 if (ctx->func) { 1378 ret = (*ctx->func)(context, ctx, ctx->data, 1379 &ctx->response, &action); 1380 if (ret) 1381 goto out; 1382 } 1383 break; 1384 case KRB5_SENDTO_FAILED: 1385 ret = KRB5_KDC_UNREACH; 1386 break; 1387 case KRB5_SENDTO_DONE: 1388 ret = 0; 1389 break; 1390 default: 1391 heim_abort("invalid krb5_sendto_context action: %d", (int)action); 1392 } 1393 } 1394 1395 out: 1396 gettimeofday(&stop_time, NULL); 1397 timevalsub(&stop_time, &ctx->stats.start_time); 1398 1399 if (ret == 0 && ctx->response.length) { 1400 *receive = ctx->response; 1401 krb5_data_zero(&ctx->response); 1402 } else { 1403 krb5_data_free(&ctx->response); 1404 krb5_clear_error_message (context); 1405 ret = KRB5_KDC_UNREACH; 1406 krb5_set_error_message(context, ret, 1407 N_("unable to reach any KDC in realm %s, tried %lu %s", ""), 1408 realm, ctx->stats.num_hosts, 1409 ctx->stats.num_hosts == 1 ? "KDC" : "KDCs"); 1410 } 1411 1412 _krb5_debugx(context, 1, 1413 "krb5_sendto_context %s done: %d hosts %lu packets %lu wc: %ld.%06d nr: %ld.%06d kh: %ld.%06d tid: %08x", 1414 ctx->realm, ret, 1415 ctx->stats.num_hosts, ctx->stats.sent_packets, 1416 stop_time.tv_sec, stop_time.tv_usec, 1417 ctx->stats.name_resolution.tv_sec, ctx->stats.name_resolution.tv_usec, 1418 ctx->stats.krbhst.tv_sec, ctx->stats.krbhst.tv_usec, ctx->stid); 1419 1420 1421 if (freectx) 1422 krb5_sendto_ctx_free(context, ctx); 1423 else 1424 reset_context(context, ctx); 1425 1426 if (handle) 1427 krb5_krbhst_free(context, handle); 1428 1429 return ret; 1430} 1431