1 /* 2 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 4 */ 5 6 #pragma ident "%Z%%M% %I% %E% SMI" 7 /* 8 * lib/krb5/os/locate_kdc.c 9 * 10 * Copyright 1990,2000,2001,2002 by the Massachusetts Institute of Technology. 11 * All Rights Reserved. 12 * 13 * Export of this software from the United States of America may 14 * require a specific license from the United States Government. 15 * It is the responsibility of any person or organization contemplating 16 * export to obtain such a license before exporting. 17 * 18 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 19 * distribute this software and its documentation for any purpose and 20 * without fee is hereby granted, provided that the above copyright 21 * notice appear in all copies and that both that copyright notice and 22 * this permission notice appear in supporting documentation, and that 23 * the name of M.I.T. not be used in advertising or publicity pertaining 24 * to distribution of the software without specific, written prior 25 * permission. Furthermore if you modify this software you must label 26 * your software as modified software and not distribute it in such a 27 * fashion that it might be confused with the original M.I.T. software. 28 * M.I.T. makes no representations about the suitability of 29 * this software for any purpose. It is provided "as is" without express 30 * or implied warranty. 31 * 32 * 33 * get socket addresses for KDC. 34 */ 35 36 #define NEED_SOCKETS 37 #include "fake-addrinfo.h" 38 #include "k5-int.h" 39 #include "os-proto.h" 40 #include <stdio.h> 41 #ifdef KRB5_DNS_LOOKUP 42 #ifdef WSHELPER 43 #include <wshelper.h> 44 #else /* WSHELPER */ 45 #include <netinet/in.h> 46 #include <arpa/inet.h> 47 #include <arpa/nameser.h> 48 #include <resolv.h> 49 #include <netdb.h> 50 #endif /* WSHELPER */ 51 #ifndef T_SRV 52 #define T_SRV 33 53 #endif /* T_SRV */ 54 55 /* for old Unixes and friends ... */ 56 #ifndef MAXHOSTNAMELEN 57 #define MAXHOSTNAMELEN 64 58 #endif 59 60 #define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1) 61 62 /* Solaris Kerberos: default to dns lookup for the KDC but not the realm */ 63 #define DEFAULT_LOOKUP_KDC 1 64 #define DEFAULT_LOOKUP_REALM 0 65 66 #ifdef DEBUG 67 /* Solaris Kerberos: want dbg messages to syslog */ 68 69 #define fprintf krbint_dbg_syslog 70 #include <stdarg.h> 71 #include <com_err.h> 72 static int 73 krbint_dbg_syslog(FILE *file, const char *fmt, ...) 74 { 75 va_list args; 76 char err_str[2048]; 77 78 va_start(args, fmt); 79 vsnprintf(err_str, sizeof (err_str), fmt, args); 80 syslog(LOG_DEBUG, err_str); 81 va_end(args); 82 83 return (0); 84 } 85 #endif 86 87 static int 88 maybe_use_dns (krb5_context context, const char *name, int defalt) 89 { 90 krb5_error_code code; 91 char * value = NULL; 92 int use_dns = 0; 93 94 code = profile_get_string(context->profile, "libdefaults", 95 name, 0, 0, &value); 96 if (value == 0 && code == 0) 97 code = profile_get_string(context->profile, "libdefaults", 98 "dns_fallback", 0, 0, &value); 99 if (code) 100 return defalt; 101 102 if (value == 0) 103 return defalt; 104 105 use_dns = _krb5_conf_boolean(value); 106 profile_release_string(value); 107 return use_dns; 108 } 109 110 int 111 _krb5_use_dns_kdc(krb5_context context) 112 { 113 return maybe_use_dns (context, "dns_lookup_kdc", DEFAULT_LOOKUP_KDC); 114 } 115 116 int 117 _krb5_use_dns_realm(krb5_context context) 118 { 119 return maybe_use_dns (context, "dns_lookup_realm", DEFAULT_LOOKUP_REALM); 120 } 121 122 #endif /* KRB5_DNS_LOOKUP */ 123 124 /*ARGSUSED*/ 125 static int get_port (const char *service, int stream, int defalt) 126 { 127 #if 0 /* Only used for "kerberos" and "kerberos-sec", and we want the 128 right port numbers even on the OSes that botch the entries in 129 /etc/services. So don't bother with the lookup, except maybe 130 to produce a warning. */ 131 struct addrinfo hints = { 0 }; 132 struct addrinfo *ai; 133 int err; 134 135 hints.ai_family = PF_INET; 136 hints.ai_socktype = stream ? SOCK_STREAM : SOCK_DGRAM; 137 err = getaddrinfo (NULL, service, &hints, &ai); 138 if (err == 0 && ai != 0) { 139 if (ai->ai_addr->sa_family == AF_INET) { 140 int port = ((struct sockaddr_in *)ai->ai_addr)->sin_port; 141 freeaddrinfo (ai); 142 return port; 143 } 144 freeaddrinfo (ai); 145 } 146 #endif 147 /* Any error - don't complain, just use default. */ 148 return htons (defalt); 149 } 150 151 int 152 krb5int_grow_addrlist (struct addrlist *lp, int nmore) 153 { 154 int i; 155 int newspace = lp->space + nmore; 156 size_t newsize = newspace * sizeof (struct addrlist); 157 struct addrinfo **newaddrs; 158 159 /* NULL check a concession to SunOS4 compatibility for now; not 160 required for pure ANSI support. */ 161 if (lp->addrs) 162 newaddrs = realloc (lp->addrs, newsize); 163 else 164 newaddrs = malloc (newsize); 165 166 if (newaddrs == NULL) 167 return errno; 168 for (i = lp->space; i < newspace; i++) 169 newaddrs[i] = NULL; 170 lp->addrs = newaddrs; 171 lp->space = newspace; 172 return 0; 173 } 174 #define grow_list krb5int_grow_addrlist 175 176 /* Free up everything pointed to by the addrlist structure, but don't 177 free the structure itself. */ 178 void 179 krb5int_free_addrlist (struct addrlist *lp) 180 { 181 int i; 182 for (i = 0; i < lp->naddrs; i++) 183 freeaddrinfo (lp->addrs[i]); 184 free (lp->addrs); 185 lp->addrs = NULL; 186 lp->naddrs = lp->space = 0; 187 } 188 #define free_list krb5int_free_addrlist 189 190 static int translate_ai_error (int err) 191 { 192 switch (err) { 193 case 0: 194 return 0; 195 case EAI_BADFLAGS: 196 case EAI_FAMILY: 197 case EAI_SOCKTYPE: 198 case EAI_SERVICE: 199 /* All of these indicate bad inputs to getaddrinfo. */ 200 return EINVAL; 201 case EAI_AGAIN: 202 /* Translate to standard errno code. */ 203 return EAGAIN; 204 case EAI_MEMORY: 205 /* Translate to standard errno code. */ 206 return ENOMEM; 207 #ifdef EAI_ADDRFAMILY 208 case EAI_ADDRFAMILY: 209 #endif 210 #if EAI_NODATA != EAI_NONAME 211 case EAI_NODATA: 212 #endif 213 case EAI_NONAME: 214 /* Name not known or no address data, but no error. Do 215 nothing more. */ 216 return 0; 217 #ifdef EAI_SYSTEM 218 case EAI_SYSTEM: 219 /* System error, obviously. */ 220 return errno; 221 #endif 222 default: 223 /* An error code we haven't handled? */ 224 return EINVAL; 225 } 226 } 227 228 static int add_addrinfo_to_list (struct addrlist *lp, struct addrinfo *a) 229 { 230 int err; 231 232 #ifdef DEBUG 233 switch (a->ai_socktype) { 234 case SOCK_DGRAM: 235 fprintf(stderr, "\tdgram\n"); 236 break; 237 case SOCK_STREAM: 238 fprintf(stderr, "\tstream\n"); 239 break; 240 case SOCK_RAW: 241 fprintf(stderr, "\traw\n"); 242 break; 243 case 0: 244 break; 245 default: 246 fprintf(stderr, "\tsocket type %d\n", a->ai_socktype); 247 break; 248 } 249 #endif 250 251 if (lp->naddrs == lp->space) { 252 err = grow_list (lp, 1); 253 if (err) { 254 #ifdef DEBUG 255 fprintf (stderr, "grow_list failed %d\n", err); 256 #endif 257 return err; 258 } 259 } 260 lp->addrs[lp->naddrs++] = a; 261 a->ai_next = 0; 262 #ifdef DEBUG 263 fprintf (stderr, "count is now %d\n", lp->naddrs); 264 #endif 265 return 0; 266 } 267 268 #define add_host_to_list krb5int_add_host_to_list 269 270 int 271 krb5int_add_host_to_list (struct addrlist *lp, const char *hostname, 272 int port, int secport, 273 int socktype, int family) 274 { 275 struct addrinfo *addrs, *a, *anext, hint; 276 int err; 277 char portbuf[10], secportbuf[10]; 278 279 #ifdef DEBUG 280 fprintf (stderr, "adding hostname %s, ports %d,%d\n", hostname, 281 ntohs (port), ntohs (secport)); 282 #endif 283 284 memset(&hint, 0, sizeof(hint)); 285 hint.ai_family = family; 286 hint.ai_socktype = socktype; 287 sprintf(portbuf, "%d", ntohs(port)); 288 sprintf(secportbuf, "%d", ntohs(secport)); 289 err = getaddrinfo (hostname, portbuf, &hint, &addrs); 290 if (err) 291 return translate_ai_error (err); 292 anext = 0; 293 for (a = addrs; a != 0 && err == 0; a = anext) { 294 anext = a->ai_next; 295 err = add_addrinfo_to_list (lp, a); 296 } 297 if (err || secport == 0) 298 goto egress; 299 if (socktype == 0) 300 socktype = SOCK_DGRAM; 301 else if (socktype != SOCK_DGRAM) 302 goto egress; 303 hint.ai_family = AF_INET; 304 err = getaddrinfo (hostname, secportbuf, &hint, &addrs); 305 if (err) { 306 err = translate_ai_error (err); 307 goto egress; 308 } 309 for (a = addrs; a != 0 && err == 0; a = anext) { 310 anext = a->ai_next; 311 err = add_addrinfo_to_list (lp, a); 312 } 313 egress: 314 if (anext) 315 freeaddrinfo (anext); 316 return err; 317 } 318 319 /* 320 * returns count of number of addresses found 321 * if master is non-NULL, it is filled in with the index of 322 * the master kdc 323 */ 324 325 static krb5_error_code 326 krb5_locate_srv_conf_1(krb5_context context, const krb5_data *realm, 327 const char * name, struct addrlist *addrlist, 328 int get_masters, int socktype, 329 int udpport, int sec_udpport, int family) 330 { 331 const char *realm_srv_names[4]; 332 char **masterlist, **hostlist, *host, *port, *cp; 333 krb5_error_code code; 334 int i, j, count, ismaster; 335 336 #ifdef DEBUG 337 fprintf (stderr, 338 "looking in krb5.conf for realm %s entry %s; ports %d,%d\n", 339 realm->data, name, ntohs (udpport), ntohs (sec_udpport)); 340 #endif 341 342 if ((host = malloc(realm->length + 1)) == NULL) 343 return ENOMEM; 344 345 strncpy(host, realm->data, realm->length); 346 host[realm->length] = '\0'; 347 hostlist = 0; 348 349 masterlist = NULL; 350 351 realm_srv_names[0] = "realms"; 352 realm_srv_names[1] = host; 353 realm_srv_names[2] = name; 354 realm_srv_names[3] = 0; 355 356 code = profile_get_values(context->profile, realm_srv_names, &hostlist); 357 358 if (code) { 359 #ifdef DEBUG 360 fprintf (stderr, "config file lookup failed: %s\n", 361 error_message(code)); 362 #endif 363 if (code == PROF_NO_SECTION || code == PROF_NO_RELATION) 364 code = KRB5_REALM_UNKNOWN; 365 krb5_xfree(host); 366 return code; 367 } 368 369 count = 0; 370 while (hostlist && hostlist[count]) 371 count++; 372 #ifdef DEBUG 373 fprintf (stderr, "found %d entries under 'kdc'\n", count); 374 #endif 375 376 if (count == 0) { 377 profile_free_list(hostlist); 378 krb5_xfree(host); 379 addrlist->naddrs = 0; 380 return 0; 381 } 382 383 if (get_masters) { 384 realm_srv_names[0] = "realms"; 385 realm_srv_names[1] = host; 386 realm_srv_names[2] = "admin_server"; 387 realm_srv_names[3] = 0; 388 389 code = profile_get_values(context->profile, realm_srv_names, 390 &masterlist); 391 392 krb5_xfree(host); 393 394 if (code == 0) { 395 for (i=0; masterlist[i]; i++) { 396 host = masterlist[i]; 397 398 /* 399 * Strip off excess whitespace 400 */ 401 cp = strchr(host, ' '); 402 if (cp) 403 *cp = 0; 404 cp = strchr(host, '\t'); 405 if (cp) 406 *cp = 0; 407 cp = strchr(host, ':'); 408 if (cp) 409 *cp = 0; 410 } 411 } 412 } else { 413 krb5_xfree(host); 414 } 415 416 /* at this point, if master is non-NULL, then either the master kdc 417 is required, and there is one, or the master kdc is not required, 418 and there may or may not be one. */ 419 420 #ifdef HAVE_NETINET_IN_H 421 if (sec_udpport) 422 count = count * 2; 423 #endif 424 425 for (i=0; hostlist[i]; i++) { 426 int p1, p2; 427 428 host = hostlist[i]; 429 #ifdef DEBUG 430 fprintf (stderr, "entry %d is '%s'\n", i, host); 431 #endif 432 /* 433 * Strip off excess whitespace 434 */ 435 cp = strchr(host, ' '); 436 if (cp) 437 *cp = 0; 438 cp = strchr(host, '\t'); 439 if (cp) 440 *cp = 0; 441 port = strchr(host, ':'); 442 if (port) { 443 *port = 0; 444 port++; 445 } 446 447 ismaster = 0; 448 if (masterlist) { 449 for (j=0; masterlist[j]; j++) { 450 if (strcasecmp(hostlist[i], masterlist[j]) == 0) { 451 ismaster = 1; 452 } 453 } 454 } 455 456 if (get_masters && !ismaster) 457 continue; 458 459 if (port) { 460 unsigned long l; 461 #ifdef HAVE_STROUL 462 char *endptr; 463 l = strtoul (port, &endptr, 10); 464 if (endptr == NULL || *endptr != 0) 465 return EINVAL; 466 #else 467 l = atoi (port); 468 #endif 469 /* L is unsigned, don't need to check <0. */ 470 if (l > 65535) 471 return EINVAL; 472 p1 = htons (l); 473 p2 = 0; 474 } else { 475 p1 = udpport; 476 p2 = sec_udpport; 477 } 478 479 if (socktype != 0) 480 code = add_host_to_list (addrlist, hostlist[i], p1, p2, 481 socktype, family); 482 else { 483 code = add_host_to_list (addrlist, hostlist[i], p1, p2, 484 SOCK_DGRAM, family); 485 if (code == 0) 486 code = add_host_to_list (addrlist, hostlist[i], p1, p2, 487 SOCK_STREAM, family); 488 } 489 if (code) { 490 #ifdef DEBUG 491 fprintf (stderr, "error %d returned from add_host_to_list\n", code); 492 #endif 493 if (hostlist) 494 profile_free_list (hostlist); 495 if (masterlist) 496 profile_free_list (masterlist); 497 return code; 498 } 499 } 500 501 if (hostlist) 502 profile_free_list(hostlist); 503 if (masterlist) 504 profile_free_list(masterlist); 505 506 return 0; 507 } 508 509 #ifdef KRB5_DNS_LOOKUP 510 511 #define make_srv_query_realm krb5int_make_srv_query_realm 512 513 static krb5_error_code 514 krb5_locate_srv_dns_1 (const krb5_data *realm, 515 const char *service, 516 const char *protocol, 517 struct addrlist *addrlist, 518 int family) 519 { 520 struct srv_dns_entry *head = NULL; 521 struct srv_dns_entry *entry = NULL, *next; 522 krb5_error_code code = 0; 523 524 code = make_srv_query_realm(realm, service, protocol, &head); 525 if (code) 526 return 0; 527 528 /* 529 * Okay! Now we've got a linked list of entries sorted by 530 * priority. Start looking up A records and returning 531 * addresses. 532 */ 533 534 if (head == NULL) 535 return 0; 536 537 /* Check for the "." case indicating no support. */ 538 if (head->next == 0 && head->host[0] == 0) { 539 free(head->host); 540 free(head); 541 return KRB5_ERR_NO_SERVICE; 542 } 543 544 #ifdef DEBUG 545 fprintf (stderr, "walking answer list:\n"); 546 #endif 547 for (entry = head; entry != NULL; entry = next) { 548 #ifdef DEBUG 549 fprintf (stderr, "\tport=%d host=%s\n", entry->port, entry->host); 550 #endif 551 next = entry->next; 552 code = add_host_to_list (addrlist, entry->host, htons (entry->port), 0, 553 (strcmp("_tcp", protocol) 554 ? SOCK_DGRAM 555 : SOCK_STREAM), family); 556 if (code) 557 break; 558 if (entry == head) { 559 free(entry->host); 560 free(entry); 561 head = next; 562 entry = 0; 563 } 564 } 565 #ifdef DEBUG 566 fprintf (stderr, "[end]\n"); 567 #endif 568 569 krb5int_free_srv_dns_data(head); 570 return code; 571 } 572 #endif /* KRB5_DNS_LOOKUP */ 573 574 /* 575 * Wrapper function for the two backends 576 */ 577 578 krb5_error_code 579 krb5int_locate_server (krb5_context context, const krb5_data *realm, 580 struct addrlist *addrlist, 581 int get_masters, 582 const char *profname, const char *dnsname, 583 int socktype, 584 /* network order port numbers! */ 585 int dflport1, int dflport2, 586 int family) 587 { 588 krb5_error_code code; 589 struct addrlist al = ADDRLIST_INIT; 590 591 *addrlist = al; 592 593 /* Solaris Kerberos: skip local file search if profname == NULL */ 594 if (profname != NULL) { 595 /* 596 * We always try the local file first 597 */ 598 code = krb5_locate_srv_conf_1(context, realm, profname, &al, 599 get_masters, socktype, dflport1, dflport2, family); 600 } 601 602 #ifdef KRB5_DNS_LOOKUP 603 if (code && dnsname != 0) { 604 int use_dns = _krb5_use_dns_kdc(context); 605 if (use_dns) { 606 code = 0; 607 if (socktype == SOCK_DGRAM || socktype == 0) { 608 code = krb5_locate_srv_dns_1(realm, dnsname, "_udp", 609 &al, family); 610 #ifdef DEBUG 611 if (code) 612 fprintf(stderr, "dns udp lookup returned error %d\n", 613 code); 614 #endif 615 } 616 if ((socktype == SOCK_STREAM || socktype == 0) && code == 0) { 617 code = krb5_locate_srv_dns_1(realm, dnsname, "_tcp", 618 &al, family); 619 #ifdef DEBUG 620 if (code) 621 fprintf(stderr, "dns tcp lookup returned error %d\n", 622 code); 623 #endif 624 } 625 } 626 } 627 #endif /* KRB5_DNS_LOOKUP */ 628 #ifdef DEBUG 629 if (code == 0) 630 fprintf (stderr, "krb5int_locate_server found %d addresses\n", 631 al.naddrs); 632 else 633 fprintf (stderr, "krb5int_locate_server returning error code %d\n", 634 code); 635 #endif 636 if (code != 0) { 637 if (al.space) 638 free_list (&al); 639 return code; 640 } 641 if (al.naddrs == 0) { /* No good servers */ 642 if (al.space) 643 free_list (&al); 644 return KRB5_REALM_CANT_RESOLVE; 645 } 646 *addrlist = al; 647 return 0; 648 } 649 650 krb5_error_code 651 krb5_locate_kdc(krb5_context context, const krb5_data *realm, 652 struct addrlist *addrlist, 653 int get_masters, int socktype, int family) 654 { 655 int udpport, sec_udpport; 656 657 udpport = get_port (KDC_PORTNAME, 0, KRB5_DEFAULT_PORT); 658 if (socktype == SOCK_STREAM) 659 sec_udpport = 0; 660 else { 661 sec_udpport = get_port (KDC_SECONDARY_PORTNAME, 0, 662 (udpport == htons (KRB5_DEFAULT_PORT) 663 ? KRB5_DEFAULT_SEC_PORT 664 : KRB5_DEFAULT_PORT)); 665 if (sec_udpport == udpport) 666 sec_udpport = 0; 667 } 668 669 return krb5int_locate_server(context, realm, addrlist, get_masters, "kdc", 670 (get_masters 671 ? "_kerberos-master" 672 : "_kerberos"), 673 socktype, udpport, sec_udpport, family); 674 } 675 676 /* 677 * Solaris Kerberos: for backward compat. Avoid using this 678 * function! 679 */ 680 krb5_error_code 681 krb5_get_servername(krb5_context context, 682 const krb5_data *realm, 683 const char *name, const char *proto, 684 char *srvhost, 685 unsigned short *port) 686 { 687 krb5_error_code code = KRB5_REALM_UNKNOWN; 688 689 #ifdef KRB5_DNS_LOOKUP 690 { 691 int use_dns = _krb5_use_dns_kdc(context); 692 693 if (use_dns) { 694 struct srv_dns_entry *head = NULL; 695 696 code = make_srv_query_realm(realm, name, proto, &head); 697 if (code) 698 return (code); 699 700 if (head == NULL) 701 return KRB5_REALM_CANT_RESOLVE; 702 703 *port = head->port; 704 (void) strlcpy(srvhost, head->host, MAX_DNS_NAMELEN); 705 706 #ifdef DEBUG 707 fprintf (stderr, "krb5_get_servername svrhost %s, port %d\n", 708 srvhost, *port); 709 #endif 710 krb5int_free_srv_dns_data(head); 711 } 712 } 713 #endif /* KRB5_DNS_LOOKUP */ 714 715 return (code); 716 } 717