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, and it tried with a individual timeout of kdc_timeout / 3. 50 * - TCP and HTTP requests are tried 1 time. 51 * 52 * Total wait time shorter then (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 (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 < 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 default: 879 heim_abort("undefined transport protocol: %d", (int)host->hi->proto); 880 } 881 882 host->tries = host->fun->ntries; 883 884 heim_array_append_value(ctx->hosts, host); 885 886 return host; 887} 888 889 890/* 891 * 892 */ 893 894static krb5_error_code 895submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi) 896{ 897 unsigned long submitted_host = 0; 898 krb5_boolean freeai = FALSE; 899 struct timeval nrstart, nrstop; 900 krb5_error_code ret; 901 struct addrinfo *ai = NULL, *a; 902 struct host *host; 903 904 ret = kdc_via_plugin(context, hi, context->kdc_timeout, 905 ctx->send_data, &ctx->response); 906 if (ret == 0) { 907 return 0; 908 } else if (ret != KRB5_PLUGIN_NO_HANDLE) { 909 _krb5_debugx(context, 5, "send via plugin failed %s: %d", 910 hi->hostname, ret); 911 return ret; 912 } 913 914 /* 915 * If we have a proxy, let use the address of the proxy instead of 916 * the KDC and let the proxy deal with the resolving of the KDC. 917 */ 918 919 gettimeofday(&nrstart, NULL); 920 921 if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) { 922 char *proxy2 = strdup(context->http_proxy); 923 char *el, *proxy = proxy2; 924 struct addrinfo hints; 925 char portstr[NI_MAXSERV]; 926 927 if (proxy == NULL) 928 return ENOMEM; 929 if (strncmp(proxy, "http://", 7) == 0) 930 proxy += 7; 931 932 /* check for url terminating slash */ 933 el = strchr(proxy, '/'); 934 if (el != NULL) 935 *el = '\0'; 936 937 /* check for port in hostname, used below as port */ 938 el = strchr(proxy, ':'); 939 if(el != NULL) 940 *el++ = '\0'; 941 942 memset(&hints, 0, sizeof(hints)); 943 hints.ai_family = PF_UNSPEC; 944 hints.ai_socktype = SOCK_STREAM; 945 946 snprintf(portstr, sizeof(portstr), "%d", 947 ntohs(init_port(el, htons(80)))); 948 949 ret = getaddrinfo(proxy, portstr, &hints, &ai); 950 free(proxy2); 951 if (ret) 952 return krb5_eai_to_heim_errno(ret, errno); 953 954 freeai = TRUE; 955 } else if (hi->proto == KRB5_KRBHST_KKDCP) { 956 ai = NULL; 957 } else { 958 ret = krb5_krbhst_get_addrinfo(context, hi, &ai); 959 if (ret) 960 return ret; 961 } 962 963 /* add up times */ 964 gettimeofday(&nrstop, NULL); 965 timevalsub(&nrstop, &nrstart); 966 timevaladd(&ctx->stats.name_resolution, &nrstop); 967 968 ctx->stats.num_hosts++; 969 970 for (a = ai; a != NULL; a = a->ai_next) { 971 rk_socket_t fd; 972 973 fd = socket(a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol); 974 if (rk_IS_BAD_SOCKET(fd)) 975 continue; 976 rk_cloexec(fd); 977 socket_set_nopipe(fd, 1); 978 socket_set_nonblocking(fd, 1); 979 980#ifndef NO_LIMIT_FD_SETSIZE 981 if (fd >= FD_SETSIZE) { 982 _krb5_debugx(context, 0, "fd too large for select"); 983 rk_closesocket(fd); 984 continue; 985 } 986#endif 987 host = host_create(context, ctx, hi, a, fd); 988 if (host == NULL) { 989 rk_closesocket(fd); 990 continue; 991 } 992 993 /* 994 * Connect directly next host, wait a host_timeout for each next address 995 */ 996 if (submitted_host == 0) 997 host_connect(context, ctx, host); 998 else { 999 debug_host(context, 5, host, 1000 "Queuing host in future (in %ds), " 1001 "its the %lu address on the same name", 1002 (int)(context->host_timeout * submitted_host), 1003 submitted_host + 1); 1004 host->timeout = time(NULL) + (submitted_host * context->host_timeout); 1005 } 1006 1007 submitted_host++; 1008 heim_release(host); 1009 } 1010 1011 if (hi->proto == KRB5_KRBHST_KKDCP) { 1012 int fds[2]; 1013 1014 heim_assert(ai == NULL, "kkdcp host with ai ?"); 1015 1016 if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) < 0) 1017 return KRB5_KDC_UNREACH; 1018 1019 socket_set_nopipe(fds[0], 1); 1020 socket_set_nopipe(fds[1], 1); 1021 socket_set_nonblocking(fds[0], 1); 1022 socket_set_nonblocking(fds[1], 1); 1023 1024 host = host_create(context, ctx, hi, NULL, fds[0]); 1025 if (host == NULL) { 1026 close(fds[0]); 1027 close(fds[1]); 1028 return ENOMEM; 1029 } 1030 host->fd2 = fds[1]; 1031 1032 host_next_timeout(context, host); 1033 host_connected(context, ctx, host); 1034 1035 submitted_host++; 1036 heim_release(host); 1037 } 1038 1039 1040 if (freeai) 1041 freeaddrinfo(ai); 1042 1043 if (!submitted_host) 1044 return KRB5_KDC_UNREACH; 1045 1046 return 0; 1047} 1048 1049static void 1050set_fd_status(struct host *h, fd_set *rfds, fd_set *wfds, int *max_fd) 1051{ 1052#ifndef NO_LIMIT_FD_SETSIZE 1053 heim_assert(h->fd < FD_SETSIZE, "fd too large"); 1054#endif 1055 switch (h->state) { 1056 case WAITING_REPLY: 1057 FD_SET(h->fd, rfds); 1058 break; 1059 case CONNECTING: 1060 case CONNECTED: 1061 FD_SET(h->fd, rfds); 1062 FD_SET(h->fd, wfds); 1063 break; 1064 case DEAD: 1065 case CONNECT: 1066 break; 1067 default: 1068 heim_abort("set_fd_status: invalid host state: %d", (int)h->state); 1069 } 1070 if (h->fd > *max_fd) 1071 *max_fd = h->fd + 1; 1072} 1073 1074 1075 1076static krb5_error_code 1077wait_response(krb5_context context, int *action, krb5_sendto_ctx ctx) 1078{ 1079 __block struct host *next_pending = NULL; 1080 __block fd_set rfds, wfds; 1081 __block int max_fd = 0; 1082 __block int ret; 1083 struct timeval tv; 1084 time_t timenow; 1085 1086 FD_ZERO(&rfds); 1087 FD_ZERO(&wfds); 1088 1089 /* oh, we have a reply, it must be a plugin that got it for us */ 1090 if (ctx->response.length) { 1091 *action = KRB5_SENDTO_FILTER; 1092 return 0; 1093 } 1094 1095 timenow = time(NULL); 1096 1097 heim_array_iterate(ctx->hosts, ^(heim_object_t obj, int *stop) { 1098 struct host *h = (struct host *)obj; 1099 1100 /* skip dead hosts */ 1101 if (h->state == DEAD) 1102 return; 1103 1104 /* 1105 * process submitted by pending hosts here 1106 */ 1107 if (h->state == CONNECT) { 1108 if (h->timeout < timenow) { 1109 host_connect(context, ctx, h); 1110 } else if (next_pending == NULL || next_pending->timeout > h->timeout) { 1111 next_pending = h; 1112 return; 1113 } else { 1114 return; 1115 } 1116 } 1117 1118 /* if host timed out, dec tries and (retry or kill host) */ 1119 if (h->timeout < timenow) { 1120 heim_assert(h->tries != 0, "tries should not reach 0"); 1121 h->tries--; 1122 if (h->tries == 0) { 1123 host_dead(context, h, "host timed out"); 1124 return; 1125 } else { 1126 debug_host(context, 5, h, "retrying sending to"); 1127 host_next_timeout(context, h); 1128 host_connected(context, ctx, h); 1129 } 1130 } 1131 1132 set_fd_status(h, &rfds, &wfds, &max_fd); 1133 }); 1134 1135 /* 1136 * We have no host to wait for, but there is one pending, lets 1137 * kick that one off. 1138 */ 1139 if (max_fd == 0 && next_pending) { 1140 time_t forward = next_pending->timeout - timenow; 1141 1142 host_connect(context, ctx, next_pending); 1143 set_fd_status(next_pending, &rfds, &wfds, &max_fd); 1144 1145 /* 1146 * Move all waiting host forward in time too, only if the next 1147 * host didn't happen the same about the same time as the last 1148 * expiration 1149 */ 1150 if (forward > 0) { 1151 heim_array_iterate(ctx->hosts, ^(heim_object_t obj, int *stop) { 1152 struct host *h = (struct host *)obj; 1153 1154 if (h->state != CONNECT) 1155 return; 1156 h->timeout -= forward; 1157 if (h->timeout < timenow) 1158 h->timeout = timenow; 1159 }); 1160 } 1161 } 1162 1163 heim_array_filter(ctx->hosts, ^(heim_object_t obj) { 1164 struct host *h = (struct host *)obj; 1165 return (int)((h->state == DEAD) ? true : false); 1166 }); 1167 1168 if (heim_array_get_length(ctx->hosts) == 0) { 1169 if (ctx->stateflags & KRBHST_COMPLETED) { 1170 _krb5_debugx(context, 5, "no more hosts to send/recv packets to/from " 1171 "trying to pulling more hosts"); 1172 *action = KRB5_SENDTO_FAILED; 1173 } else { 1174 _krb5_debugx(context, 5, "no more hosts to send/recv packets to/from " 1175 "and no more hosts -> failure"); 1176 *action = KRB5_SENDTO_TIMEOUT; 1177 } 1178 return 0; 1179 } 1180 1181 tv.tv_sec = 1; 1182 tv.tv_usec = 0; 1183 1184 ret = select(max_fd + 1, &rfds, &wfds, NULL, &tv); 1185 if (ret < 0) { 1186 if (errno != EAGAIN || errno != EINTR) 1187 return errno; 1188 ret = 0; 1189 } 1190 if (ret == 0) { 1191 *action = KRB5_SENDTO_TIMEOUT; 1192 return 0; 1193 } 1194 1195 ret = 0; 1196 heim_array_iterate(ctx->hosts, ^(heim_object_t obj, int *stop) { 1197 struct host *h = (struct host *)obj; 1198 int readable, writeable; 1199 heim_assert(h->state != DEAD, "dead host resurected"); 1200 1201#ifndef NO_LIMIT_FD_SETSIZE 1202 heim_assert(h->fd < FD_SETSIZE, "fd too large"); 1203#endif 1204 readable = FD_ISSET(h->fd, &rfds); 1205 writeable = FD_ISSET(h->fd, &wfds); 1206 1207 if (readable || writeable) 1208 ret |= eval_host_state(context, ctx, h, readable, writeable); 1209 1210 /* if there is already a reply, just fall though the array */ 1211 if (ret) 1212 *stop = 1; 1213 }); 1214 if (ret) 1215 *action = KRB5_SENDTO_FILTER; 1216 else 1217 *action = KRB5_SENDTO_CONTINUE; 1218 1219 return 0; 1220} 1221 1222static void 1223reset_context(krb5_context context, krb5_sendto_ctx ctx) 1224{ 1225 krb5_data_free(&ctx->response); 1226 heim_release(ctx->hosts); 1227 ctx->hosts = heim_array_create(); 1228 ctx->stateflags = 0; 1229} 1230 1231 1232/* 1233 * 1234 */ 1235 1236KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1237krb5_sendto_context(krb5_context context, 1238 krb5_sendto_ctx ctx, 1239 const krb5_data *send_data, 1240 krb5_const_realm realm, 1241 krb5_data *receive) 1242{ 1243 krb5_error_code ret; 1244 krb5_krbhst_handle handle = NULL; 1245 struct timeval nrstart, nrstop, stop_time; 1246 int type, freectx = 0; 1247 int action; 1248 int numreset = 0; 1249 1250 krb5_data_zero(receive); 1251 1252 HEIM_WARN_BLOCKING("krb5_sendto_context", warn_once); 1253 1254 if (ctx == NULL) { 1255 ret = krb5_sendto_ctx_alloc(context, &ctx); 1256 if (ret) 1257 goto out; 1258 freectx = 1; 1259 } 1260 1261 memset(&ctx->stats, 0, sizeof(ctx->stats)); 1262 gettimeofday(&ctx->stats.start_time, NULL); 1263 1264 ctx->realm = realm; 1265 ctx->stid = (context->num_kdc_requests++) << 16; 1266 1267 type = ctx->type; 1268 if (type == 0) { 1269 if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc) 1270 type = KRB5_KRBHST_ADMIN; 1271 else 1272 type = KRB5_KRBHST_KDC; 1273 } 1274 1275 ctx->send_data = send_data; 1276 1277 if ((int)send_data->length > context->large_msg_size) 1278 ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG; 1279 1280 /* loop until we get back a appropriate response */ 1281 1282 action = KRB5_SENDTO_INITIAL; 1283 1284 while (action != KRB5_SENDTO_DONE && action != KRB5_SENDTO_FAILED) { 1285 krb5_krbhst_info *hi; 1286 1287 switch (action) { 1288 case KRB5_SENDTO_INITIAL: 1289 ret = realm_via_plugin(context, realm, context->kdc_timeout, 1290 send_data, receive); 1291 if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) { 1292 action = KRB5_SENDTO_DONE; 1293 break; 1294 } 1295 action = KRB5_SENDTO_KRBHST; 1296 /* FALLTHOUGH */ 1297 case KRB5_SENDTO_KRBHST: 1298 if (ctx->krbhst == NULL) { 1299 ret = krb5_krbhst_init_flags(context, realm, type, 1300 ctx->flags, &handle); 1301 if (ret) 1302 goto out; 1303 1304 if (ctx->hostname) { 1305 ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname); 1306 if (ret) 1307 goto out; 1308 } 1309 } else { 1310 handle = heim_retain(ctx->krbhst); 1311 } 1312 action = KRB5_SENDTO_TIMEOUT; 1313 /* FALLTHOUGH */ 1314 case KRB5_SENDTO_TIMEOUT: 1315 1316 /* 1317 * If we completed, just got to next step 1318 */ 1319 1320 if (ctx->stateflags & KRBHST_COMPLETED) { 1321 action = KRB5_SENDTO_CONTINUE; 1322 break; 1323 } 1324 1325 /* 1326 * Pull out next host, if there is no more, close the 1327 * handle and mark as completed. 1328 * 1329 * Collect time spent in krbhst (dns, plugin, etc) 1330 */ 1331 1332 1333 gettimeofday(&nrstart, NULL); 1334 1335 ret = krb5_krbhst_next(context, handle, &hi); 1336 1337 gettimeofday(&nrstop, NULL); 1338 timevalsub(&nrstop, &nrstart); 1339 timevaladd(&ctx->stats.krbhst, &nrstop); 1340 1341 action = KRB5_SENDTO_CONTINUE; 1342 if (ret == 0) { 1343 _krb5_debugx(context, 5, "submissing new requests to new host"); 1344 if (submit_request(context, ctx, hi) != 0) 1345 action = KRB5_SENDTO_TIMEOUT; 1346 } else { 1347 _krb5_debugx(context, 5, "out of hosts, waiting for replies"); 1348 ctx->stateflags |= KRBHST_COMPLETED; 1349 } 1350 1351 break; 1352 case KRB5_SENDTO_CONTINUE: 1353 1354 ret = wait_response(context, &action, ctx); 1355 if (ret) 1356 goto out; 1357 1358 break; 1359 case KRB5_SENDTO_RESET: 1360 /* start over */ 1361 _krb5_debugx(context, 5, 1362 "krb5_sendto trying over again (reset): %d", 1363 numreset); 1364 reset_context(context, ctx); 1365 if (handle) { 1366 krb5_krbhst_free(context, handle); 1367 handle = NULL; 1368 } 1369 numreset++; 1370 if (numreset >= 3) 1371 action = KRB5_SENDTO_FAILED; 1372 else 1373 action = KRB5_SENDTO_KRBHST; 1374 1375 break; 1376 case KRB5_SENDTO_FILTER: 1377 /* default to next state, the filter function might modify this */ 1378 action = KRB5_SENDTO_DONE; 1379 1380 if (ctx->func) { 1381 ret = (*ctx->func)(context, ctx, ctx->data, 1382 &ctx->response, &action); 1383 if (ret) 1384 goto out; 1385 } 1386 break; 1387 case KRB5_SENDTO_FAILED: 1388 ret = KRB5_KDC_UNREACH; 1389 break; 1390 case KRB5_SENDTO_DONE: 1391 ret = 0; 1392 break; 1393 default: 1394 heim_abort("invalid krb5_sendto_context action: %d", (int)action); 1395 } 1396 } 1397 1398 out: 1399 gettimeofday(&stop_time, NULL); 1400 timevalsub(&stop_time, &ctx->stats.start_time); 1401 1402 if (ret == 0 && ctx->response.length) { 1403 *receive = ctx->response; 1404 krb5_data_zero(&ctx->response); 1405 } else { 1406 krb5_data_free(&ctx->response); 1407 krb5_clear_error_message (context); 1408 ret = KRB5_KDC_UNREACH; 1409 krb5_set_error_message(context, ret, 1410 N_("unable to reach any KDC in realm %s, tried %lu %s", ""), 1411 realm, ctx->stats.num_hosts, 1412 ctx->stats.num_hosts == 1 ? "KDC" : "KDCs"); 1413 } 1414 1415 _krb5_debugx(context, 1, 1416 "krb5_sendto_context %s done: %d hosts %lu packets %lu wc: %ld.%06d nr: %ld.%06d kh: %ld.%06d tid: %08x", 1417 ctx->realm, ret, 1418 ctx->stats.num_hosts, ctx->stats.sent_packets, 1419 stop_time.tv_sec, stop_time.tv_usec, 1420 ctx->stats.name_resolution.tv_sec, ctx->stats.name_resolution.tv_usec, 1421 ctx->stats.krbhst.tv_sec, ctx->stats.krbhst.tv_usec, ctx->stid); 1422 1423 1424 if (freectx) 1425 krb5_sendto_ctx_free(context, ctx); 1426 else 1427 reset_context(context, ctx); 1428 1429 if (handle) 1430 krb5_krbhst_free(context, handle); 1431 1432 return ret; 1433} 1434