1/* 2 * Copyright (c) 1997 - 2002 Kungliga Tekniska Högskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * 3. Neither the name of the Institute nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34#include "krb5_locl.h" 35#include "send_to_kdc_plugin.h" 36 37struct send_to_kdc { 38 krb5_send_to_kdc_func func; 39 void *data; 40}; 41 42/* 43 * send the data in `req' on the socket `fd' (which is datagram iff udp) 44 * waiting `tmout' for a reply and returning the reply in `rep'. 45 * iff limit read up to this many bytes 46 * returns 0 and data in `rep' if succesful, otherwise -1 47 */ 48 49static int 50recv_loop (int fd, 51 time_t tmout, 52 int udp, 53 size_t limit, 54 krb5_data *rep) 55{ 56 fd_set fdset; 57 struct timeval timeout; 58 int ret; 59 int nbytes; 60 61 if (fd >= FD_SETSIZE) { 62 return -1; 63 } 64 65 krb5_data_zero(rep); 66 do { 67 FD_ZERO(&fdset); 68 FD_SET(fd, &fdset); 69 timeout.tv_sec = tmout; 70 timeout.tv_usec = 0; 71 ret = select (fd + 1, &fdset, NULL, NULL, &timeout); 72 if (ret < 0) { 73 if (errno == EINTR) 74 continue; 75 return -1; 76 } else if (ret == 0) { 77 return 0; 78 } else { 79 void *tmp; 80 81 if (ioctl (fd, FIONREAD, &nbytes) < 0) { 82 krb5_data_free (rep); 83 return -1; 84 } 85 if(nbytes <= 0) 86 return 0; 87 88 if (limit) 89 nbytes = min(nbytes, limit - rep->length); 90 91 tmp = realloc (rep->data, rep->length + nbytes); 92 if (tmp == NULL) { 93 krb5_data_free (rep); 94 return -1; 95 } 96 rep->data = tmp; 97 ret = recv (fd, (char*)tmp + rep->length, nbytes, 0); 98 if (ret < 0) { 99 krb5_data_free (rep); 100 return -1; 101 } 102 rep->length += ret; 103 } 104 } while(!udp && (limit == 0 || rep->length < limit)); 105 return 0; 106} 107 108/* 109 * Send kerberos requests and receive a reply on a udp or any other kind 110 * of a datagram socket. See `recv_loop'. 111 */ 112 113static int 114send_and_recv_udp(int fd, 115 time_t tmout, 116 const krb5_data *req, 117 krb5_data *rep) 118{ 119 if (send (fd, req->data, req->length, 0) < 0) 120 return -1; 121 122 return recv_loop(fd, tmout, 1, 0, rep); 123} 124 125/* 126 * `send_and_recv' for a TCP (or any other stream) socket. 127 * Since there are no record limits on a stream socket the protocol here 128 * is to prepend the request with 4 bytes of its length and the reply 129 * is similarly encoded. 130 */ 131 132static int 133send_and_recv_tcp(int fd, 134 time_t tmout, 135 const krb5_data *req, 136 krb5_data *rep) 137{ 138 unsigned char len[4]; 139 unsigned long rep_len; 140 krb5_data len_data; 141 142 _krb5_put_int(len, req->length, 4); 143 if(net_write(fd, len, sizeof(len)) < 0) 144 return -1; 145 if(net_write(fd, req->data, req->length) < 0) 146 return -1; 147 if (recv_loop (fd, tmout, 0, 4, &len_data) < 0) 148 return -1; 149 if (len_data.length != 4) { 150 krb5_data_free (&len_data); 151 return -1; 152 } 153 _krb5_get_int(len_data.data, &rep_len, 4); 154 krb5_data_free (&len_data); 155 if (recv_loop (fd, tmout, 0, rep_len, rep) < 0) 156 return -1; 157 if(rep->length != rep_len) { 158 krb5_data_free (rep); 159 return -1; 160 } 161 return 0; 162} 163 164int 165_krb5_send_and_recv_tcp(int fd, 166 time_t tmout, 167 const krb5_data *req, 168 krb5_data *rep) 169{ 170 return send_and_recv_tcp(fd, tmout, req, rep); 171} 172 173/* 174 * `send_and_recv' tailored for the HTTP protocol. 175 */ 176 177static int 178send_and_recv_http(int fd, 179 time_t tmout, 180 const char *prefix, 181 const krb5_data *req, 182 krb5_data *rep) 183{ 184 char *request; 185 char *str; 186 int ret; 187 int len = base64_encode(req->data, req->length, &str); 188 189 if(len < 0) 190 return -1; 191 asprintf(&request, "GET %s%s HTTP/1.0\r\n\r\n", prefix, str); 192 free(str); 193 if (request == NULL) 194 return -1; 195 ret = net_write (fd, request, strlen(request)); 196 free (request); 197 if (ret < 0) 198 return ret; 199 ret = recv_loop(fd, tmout, 0, 0, rep); 200 if(ret) 201 return ret; 202 { 203 unsigned long rep_len; 204 char *s, *p; 205 206 s = realloc(rep->data, rep->length + 1); 207 if (s == NULL) { 208 krb5_data_free (rep); 209 return -1; 210 } 211 s[rep->length] = 0; 212 p = strstr(s, "\r\n\r\n"); 213 if(p == NULL) { 214 krb5_data_zero(rep); 215 free(s); 216 return -1; 217 } 218 p += 4; 219 rep->data = s; 220 rep->length -= p - s; 221 if(rep->length < 4) { /* remove length */ 222 krb5_data_zero(rep); 223 free(s); 224 return -1; 225 } 226 rep->length -= 4; 227 _krb5_get_int(p, &rep_len, 4); 228 if (rep_len != rep->length) { 229 krb5_data_zero(rep); 230 free(s); 231 return -1; 232 } 233 memmove(rep->data, p + 4, rep->length); 234 } 235 return 0; 236} 237 238static int 239init_port(const char *s, int fallback) 240{ 241 if (s) { 242 int tmp; 243 244 sscanf (s, "%d", &tmp); 245 return htons(tmp); 246 } else 247 return fallback; 248} 249 250/* 251 * Return 0 if succesful, otherwise 1 252 */ 253 254static int 255send_via_proxy (krb5_context context, 256 const krb5_krbhst_info *hi, 257 const krb5_data *send_data, 258 krb5_data *receive) 259{ 260 char *proxy2 = strdup(context->http_proxy); 261 char *proxy = proxy2; 262 char *prefix; 263 char *colon; 264 struct addrinfo hints; 265 struct addrinfo *ai, *a; 266 int ret; 267 int s = -1; 268 char portstr[NI_MAXSERV]; 269 270 if (proxy == NULL) 271 return ENOMEM; 272 if (strncmp (proxy, "http://", 7) == 0) 273 proxy += 7; 274 275 colon = strchr(proxy, ':'); 276 if(colon != NULL) 277 *colon++ = '\0'; 278 memset (&hints, 0, sizeof(hints)); 279 hints.ai_family = PF_UNSPEC; 280 hints.ai_socktype = SOCK_STREAM; 281 snprintf (portstr, sizeof(portstr), "%d", 282 ntohs(init_port (colon, htons(80)))); 283 ret = getaddrinfo (proxy, portstr, &hints, &ai); 284 free (proxy2); 285 if (ret) 286 return krb5_eai_to_heim_errno(ret, errno); 287 288 for (a = ai; a != NULL; a = a->ai_next) { 289 s = socket (a->ai_family, a->ai_socktype, a->ai_protocol | SOCK_CLOEXEC); 290 if (s < 0) 291 continue; 292 rk_cloexec(s); 293 if (connect (s, a->ai_addr, a->ai_addrlen) < 0) { 294 close (s); 295 continue; 296 } 297 break; 298 } 299 if (a == NULL) { 300 freeaddrinfo (ai); 301 return 1; 302 } 303 freeaddrinfo (ai); 304 305 asprintf(&prefix, "http://%s/", hi->hostname); 306 if(prefix == NULL) { 307 close(s); 308 return 1; 309 } 310 ret = send_and_recv_http(s, context->kdc_timeout, 311 prefix, send_data, receive); 312 close (s); 313 free(prefix); 314 if(ret == 0 && receive->length != 0) 315 return 0; 316 return 1; 317} 318 319static krb5_error_code 320send_via_plugin(krb5_context context, 321 krb5_krbhst_info *hi, 322 time_t timeout, 323 const krb5_data *send_data, 324 krb5_data *receive) 325{ 326 struct krb5_plugin *list = NULL, *e; 327 krb5_error_code ret; 328 329 ret = _krb5_plugin_find(context, PLUGIN_TYPE_DATA, KRB5_PLUGIN_SEND_TO_KDC, &list); 330 if(ret != 0 || list == NULL) 331 return KRB5_PLUGIN_NO_HANDLE; 332 333 for (e = list; e != NULL; e = _krb5_plugin_get_next(e)) { 334 krb5plugin_send_to_kdc_ftable *service; 335 void *ctx; 336 337 service = _krb5_plugin_get_symbol(e); 338 if (service->minor_version != 0) 339 continue; 340 341 (*service->init)(context, &ctx); 342 ret = (*service->send_to_kdc)(context, ctx, hi, 343 timeout, send_data, receive); 344 (*service->fini)(ctx); 345 if (ret == 0) 346 break; 347 if (ret != KRB5_PLUGIN_NO_HANDLE) { 348 krb5_set_error_message(context, ret, 349 N_("Plugin send_to_kdc failed to " 350 "lookup with error: %d", ""), ret); 351 break; 352 } 353 } 354 _krb5_plugin_free(list); 355 return KRB5_PLUGIN_NO_HANDLE; 356} 357 358 359/* 360 * Send the data `send' to one host from `handle` and get back the reply 361 * in `receive'. 362 */ 363 364krb5_error_code KRB5_LIB_FUNCTION 365krb5_sendto (krb5_context context, 366 const krb5_data *send_data, 367 krb5_krbhst_handle handle, 368 krb5_data *receive) 369{ 370 krb5_error_code ret; 371 int fd; 372 int i; 373 374 krb5_data_zero(receive); 375 376 for (i = 0; i < context->max_retries; ++i) { 377 krb5_krbhst_info *hi; 378 379 while (krb5_krbhst_next(context, handle, &hi) == 0) { 380 struct addrinfo *ai, *a; 381 382 if (context->send_to_kdc) { 383 struct send_to_kdc *s = context->send_to_kdc; 384 385 ret = (*s->func)(context, s->data, hi, 386 context->kdc_timeout, send_data, receive); 387 if (ret == 0 && receive->length != 0) 388 goto out; 389 continue; 390 } 391 392 ret = send_via_plugin(context, hi, context->kdc_timeout, 393 send_data, receive); 394 if (ret == 0 && receive->length != 0) 395 goto out; 396 else if (ret != KRB5_PLUGIN_NO_HANDLE) 397 continue; 398 399 if(hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) { 400 if (send_via_proxy (context, hi, send_data, receive) == 0) { 401 ret = 0; 402 goto out; 403 } 404 continue; 405 } 406 407 ret = krb5_krbhst_get_addrinfo(context, hi, &ai); 408 if (ret) 409 continue; 410 411 for (a = ai; a != NULL; a = a->ai_next) { 412 fd = socket (a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol); 413 if (fd < 0) 414 continue; 415 rk_cloexec(fd); 416 if (connect (fd, a->ai_addr, a->ai_addrlen) < 0) { 417 close (fd); 418 continue; 419 } 420 switch (hi->proto) { 421 case KRB5_KRBHST_HTTP : 422 ret = send_and_recv_http(fd, context->kdc_timeout, 423 "", send_data, receive); 424 break; 425 case KRB5_KRBHST_TCP : 426 ret = send_and_recv_tcp (fd, context->kdc_timeout, 427 send_data, receive); 428 break; 429 case KRB5_KRBHST_UDP : 430 ret = send_and_recv_udp (fd, context->kdc_timeout, 431 send_data, receive); 432 break; 433 } 434 close (fd); 435 if(ret == 0 && receive->length != 0) 436 goto out; 437 } 438 } 439 krb5_krbhst_reset(context, handle); 440 } 441 krb5_clear_error_message (context); 442 ret = KRB5_KDC_UNREACH; 443out: 444 return ret; 445} 446 447krb5_error_code KRB5_LIB_FUNCTION 448krb5_sendto_kdc(krb5_context context, 449 const krb5_data *send_data, 450 const krb5_realm *realm, 451 krb5_data *receive) 452{ 453 return krb5_sendto_kdc_flags(context, send_data, realm, receive, 0); 454} 455 456krb5_error_code KRB5_LIB_FUNCTION 457krb5_sendto_kdc_flags(krb5_context context, 458 const krb5_data *send_data, 459 const krb5_realm *realm, 460 krb5_data *receive, 461 int flags) 462{ 463 krb5_error_code ret; 464 krb5_sendto_ctx ctx; 465 466 ret = krb5_sendto_ctx_alloc(context, &ctx); 467 if (ret) 468 return ret; 469 krb5_sendto_ctx_add_flags(ctx, flags); 470 krb5_sendto_ctx_set_func(ctx, _krb5_kdc_retry, NULL); 471 472 ret = krb5_sendto_context(context, ctx, send_data, *realm, receive); 473 krb5_sendto_ctx_free(context, ctx); 474 return ret; 475} 476 477krb5_error_code KRB5_LIB_FUNCTION 478krb5_set_send_to_kdc_func(krb5_context context, 479 krb5_send_to_kdc_func func, 480 void *data) 481{ 482 free(context->send_to_kdc); 483 if (func == NULL) { 484 context->send_to_kdc = NULL; 485 return 0; 486 } 487 488 context->send_to_kdc = malloc(sizeof(*context->send_to_kdc)); 489 if (context->send_to_kdc == NULL) { 490 krb5_set_error_message(context, ENOMEM, 491 N_("malloc: out of memory", "")); 492 return ENOMEM; 493 } 494 495 context->send_to_kdc->func = func; 496 context->send_to_kdc->data = data; 497 return 0; 498} 499 500krb5_error_code KRB5_LIB_FUNCTION 501_krb5_copy_send_to_kdc_func(krb5_context context, krb5_context to) 502{ 503 if (context->send_to_kdc) 504 return krb5_set_send_to_kdc_func(to, 505 context->send_to_kdc->func, 506 context->send_to_kdc->data); 507 else 508 return krb5_set_send_to_kdc_func(to, NULL, NULL); 509} 510 511 512 513struct krb5_sendto_ctx_data { 514 int flags; 515 int type; 516 krb5_sendto_ctx_func func; 517 void *data; 518}; 519 520krb5_error_code KRB5_LIB_FUNCTION 521krb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx) 522{ 523 *ctx = calloc(1, sizeof(**ctx)); 524 if (*ctx == NULL) { 525 krb5_set_error_message(context, ENOMEM, 526 N_("malloc: out of memory", "")); 527 return ENOMEM; 528 } 529 return 0; 530} 531 532void KRB5_LIB_FUNCTION 533krb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx, int flags) 534{ 535 ctx->flags |= flags; 536} 537 538int KRB5_LIB_FUNCTION 539krb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx) 540{ 541 return ctx->flags; 542} 543 544void KRB5_LIB_FUNCTION 545krb5_sendto_ctx_set_type(krb5_sendto_ctx ctx, int type) 546{ 547 ctx->type = type; 548} 549 550 551void KRB5_LIB_FUNCTION 552krb5_sendto_ctx_set_func(krb5_sendto_ctx ctx, 553 krb5_sendto_ctx_func func, 554 void *data) 555{ 556 ctx->func = func; 557 ctx->data = data; 558} 559 560void KRB5_LIB_FUNCTION 561krb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx) 562{ 563 memset(ctx, 0, sizeof(*ctx)); 564 free(ctx); 565} 566 567krb5_error_code KRB5_LIB_FUNCTION 568krb5_sendto_context(krb5_context context, 569 krb5_sendto_ctx ctx, 570 const krb5_data *send_data, 571 const krb5_realm realm, 572 krb5_data *receive) 573{ 574 krb5_error_code ret; 575 krb5_krbhst_handle handle = NULL; 576 int type, freectx = 0; 577 int action; 578 579 krb5_data_zero(receive); 580 581 if (ctx == NULL) { 582 freectx = 1; 583 ret = krb5_sendto_ctx_alloc(context, &ctx); 584 if (ret) 585 return ret; 586 } 587 588 type = ctx->type; 589 if (type == 0) { 590 if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc) 591 type = KRB5_KRBHST_ADMIN; 592 else 593 type = KRB5_KRBHST_KDC; 594 } 595 596 if (send_data->length > context->large_msg_size) 597 ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG; 598 599 /* loop until we get back a appropriate response */ 600 601 do { 602 action = KRB5_SENDTO_DONE; 603 604 krb5_data_free(receive); 605 606 if (handle == NULL) { 607 ret = krb5_krbhst_init_flags(context, realm, type, 608 ctx->flags, &handle); 609 if (ret) { 610 if (freectx) 611 krb5_sendto_ctx_free(context, ctx); 612 return ret; 613 } 614 } 615 616 ret = krb5_sendto(context, send_data, handle, receive); 617 if (ret) 618 break; 619 if (ctx->func) { 620 ret = (*ctx->func)(context, ctx, ctx->data, receive, &action); 621 if (ret) 622 break; 623 } 624 if (action != KRB5_SENDTO_CONTINUE) { 625 krb5_krbhst_free(context, handle); 626 handle = NULL; 627 } 628 } while (action != KRB5_SENDTO_DONE); 629 if (handle) 630 krb5_krbhst_free(context, handle); 631 if (ret == KRB5_KDC_UNREACH) 632 krb5_set_error_message(context, ret, 633 N_("unable to reach any KDC in realm %s", ""), 634 realm); 635 if (ret) 636 krb5_data_free(receive); 637 if (freectx) 638 krb5_sendto_ctx_free(context, ctx); 639 return ret; 640} 641 642krb5_error_code 643_krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data, 644 const krb5_data *reply, int *action) 645{ 646 krb5_error_code ret; 647 KRB_ERROR error; 648 649 if(krb5_rd_error(context, reply, &error)) 650 return 0; 651 652 ret = krb5_error_from_rd_error(context, &error, NULL); 653 krb5_free_error_contents(context, &error); 654 655 switch(ret) { 656 case KRB5KRB_ERR_RESPONSE_TOO_BIG: { 657 if (krb5_sendto_ctx_get_flags(ctx) & KRB5_KRBHST_FLAGS_LARGE_MSG) 658 break; 659 krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG); 660 *action = KRB5_SENDTO_RESTART; 661 break; 662 } 663 case KRB5KDC_ERR_SVC_UNAVAILABLE: 664 *action = KRB5_SENDTO_CONTINUE; 665 break; 666 } 667 return 0; 668} 669