1 /* 2 * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. 3 * Copyright 2014 Nexenta Systems, Inc. All rights reserved. 4 */ 5 6 /* 7 * lib/krb5/os/locate_kdc.c 8 * 9 * Copyright 1990,2000,2001,2002,2003,2004,2006 Massachusetts Institute of Technology. 10 * All Rights Reserved. 11 * 12 * Export of this software from the United States of America may 13 * require a specific license from the United States Government. 14 * It is the responsibility of any person or organization contemplating 15 * export to obtain such a license before exporting. 16 * 17 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 18 * distribute this software and its documentation for any purpose and 19 * without fee is hereby granted, provided that the above copyright 20 * notice appear in all copies and that both that copyright notice and 21 * this permission notice appear in supporting documentation, and that 22 * the name of M.I.T. not be used in advertising or publicity pertaining 23 * to distribution of the software without specific, written prior 24 * permission. Furthermore if you modify this software you must label 25 * your software as modified software and not distribute it in such a 26 * fashion that it might be confused with the original M.I.T. software. 27 * M.I.T. makes no representations about the suitability of 28 * this software for any purpose. It is provided "as is" without express 29 * or implied warranty. 30 * 31 * 32 * get socket addresses for KDC. 33 */ 34 35 /* 36 * Solaris Kerberos 37 * Re-factored the following routines to get a clear separation of locating 38 * KDC entries (krb5.conf/DNS-SRVrecs) versus mapping them to net addresses 39 * to allow us to output better error msgs: 40 * krb5int_locate_server 41 * prof_locate_server 42 * dns_locate_server 43 * krb5_locate_srv_conf_1 (removed) 44 * krb5_locate_srv_dns_1 (removed) 45 * prof_hostnames2netaddrs (new) 46 * hostlist2str (new) 47 * dns_hostnames2netaddrs (new) 48 * dnslist2str (new) 49 * Also, for the profile get_master==1 case, the algorithm has been 50 * simplified to just do a profile_get_values on "admin_server" and 51 * not try to match those against "kdc" entries (does not seem necessary 52 * and the DNS-SRVrecs code does not do that). 53 */ 54 55 #include "fake-addrinfo.h" 56 #include "k5-int.h" 57 #include "os-proto.h" 58 #include <stdio.h> 59 #ifdef KRB5_DNS_LOOKUP 60 #ifdef WSHELPER 61 #include <wshelper.h> 62 #else /* WSHELPER */ 63 #include <netinet/in.h> 64 #include <arpa/inet.h> 65 #include <arpa/nameser.h> 66 #include <resolv.h> 67 #include <netdb.h> 68 #endif /* WSHELPER */ 69 #ifndef T_SRV 70 #define T_SRV 33 71 #endif /* T_SRV */ 72 #include <syslog.h> 73 #include <locale.h> 74 75 #if USE_DLOPEN 76 #include <dlfcn.h> 77 #endif 78 79 /* for old Unixes and friends ... */ 80 #ifndef MAXHOSTNAMELEN 81 #define MAXHOSTNAMELEN 64 82 #endif 83 84 #define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1) 85 86 /* Solaris Kerberos: default to dns lookup for the KDC but not the realm */ 87 #define DEFAULT_LOOKUP_KDC 1 88 #define DEFAULT_LOOKUP_REALM 0 89 90 static int 91 maybe_use_dns (krb5_context context, const char *name, int defalt) 92 { 93 krb5_error_code code; 94 char * value = NULL; 95 int use_dns = 0; 96 97 code = profile_get_string(context->profile, "libdefaults", 98 name, 0, 0, &value); 99 if (value == 0 && code == 0) 100 code = profile_get_string(context->profile, "libdefaults", 101 "dns_fallback", 0, 0, &value); 102 if (code) 103 return defalt; 104 105 if (value == 0) 106 return defalt; 107 108 use_dns = _krb5_conf_boolean(value); 109 profile_release_string(value); 110 return use_dns; 111 } 112 113 int 114 _krb5_use_dns_kdc(krb5_context context) 115 { 116 return maybe_use_dns (context, "dns_lookup_kdc", DEFAULT_LOOKUP_KDC); 117 } 118 119 int 120 _krb5_use_dns_realm(krb5_context context) 121 { 122 return maybe_use_dns (context, "dns_lookup_realm", DEFAULT_LOOKUP_REALM); 123 } 124 125 #endif /* KRB5_DNS_LOOKUP */ 126 127 int 128 krb5int_grow_addrlist (struct addrlist *lp, int nmore) 129 { 130 int i; 131 int newspace = lp->space + nmore; 132 size_t newsize = newspace * sizeof (*lp->addrs); 133 void *newaddrs; 134 135 newaddrs = realloc (lp->addrs, newsize); 136 if (newaddrs == NULL) 137 return errno; 138 lp->addrs = newaddrs; 139 for (i = lp->space; i < newspace; i++) { 140 lp->addrs[i].ai = NULL; 141 lp->addrs[i].freefn = NULL; 142 lp->addrs[i].data = NULL; 143 } 144 lp->space = newspace; 145 return 0; 146 } 147 #define grow_list krb5int_grow_addrlist 148 149 /* Free up everything pointed to by the addrlist structure, but don't 150 free the structure itself. */ 151 void 152 krb5int_free_addrlist (struct addrlist *lp) 153 { 154 int i; 155 for (i = 0; i < lp->naddrs; i++) 156 if (lp->addrs[i].freefn) 157 (lp->addrs[i].freefn)(lp->addrs[i].data); 158 free (lp->addrs); 159 lp->addrs = NULL; 160 lp->naddrs = lp->space = 0; 161 } 162 #define free_list krb5int_free_addrlist 163 164 static int translate_ai_error (int err) 165 { 166 switch (err) { 167 case 0: 168 return 0; 169 case EAI_BADFLAGS: 170 case EAI_FAMILY: 171 case EAI_SOCKTYPE: 172 case EAI_SERVICE: 173 /* All of these indicate bad inputs to getaddrinfo. */ 174 return EINVAL; 175 case EAI_AGAIN: 176 /* Translate to standard errno code. */ 177 return EAGAIN; 178 case EAI_MEMORY: 179 /* Translate to standard errno code. */ 180 return ENOMEM; 181 #ifdef EAI_ADDRFAMILY 182 case EAI_ADDRFAMILY: 183 #endif 184 #if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME 185 case EAI_NODATA: 186 #endif 187 case EAI_NONAME: 188 /* Name not known or no address data, but no error. Do 189 nothing more. */ 190 return 0; 191 #ifdef EAI_OVERFLOW 192 case EAI_OVERFLOW: 193 /* An argument buffer overflowed. */ 194 return EINVAL; /* XXX */ 195 #endif 196 #ifdef EAI_SYSTEM 197 case EAI_SYSTEM: 198 /* System error, obviously. */ 199 return errno; 200 #endif 201 default: 202 /* An error code we haven't handled? */ 203 return EINVAL; 204 } 205 } 206 207 /* Solaris Kerberos: want dbg messages to syslog */ 208 #include <stdarg.h> 209 static inline void Tprintf(const char *fmt, ...) 210 { 211 #ifdef TEST 212 va_list ap; 213 char err_str[2048]; 214 215 va_start(ap, fmt); 216 vsnprintf(err_str, sizeof (err_str), fmt, args); 217 syslog(LOG_DEBUG, err_str); 218 va_end(ap); 219 #endif 220 } 221 222 #if 0 223 extern void krb5int_debug_fprint(const char *, ...); 224 #define dprint krb5int_debug_fprint 225 #define print_addrlist krb5int_print_addrlist 226 extern void print_addrlist (const struct addrlist *a); 227 #else 228 static inline void dprint(const char *fmt, ...) { } 229 static inline void print_addrlist(const struct addrlist *a) { } 230 #endif 231 232 static int add_addrinfo_to_list (struct addrlist *lp, struct addrinfo *a, 233 void (*freefn)(void *), void *data) 234 { 235 int err; 236 237 dprint("\tadding %p=%A to %p (naddrs=%d space=%d)\n", a, a, lp, 238 lp->naddrs, lp->space); 239 240 if (lp->naddrs == lp->space) { 241 err = grow_list (lp, 1); 242 if (err) { 243 Tprintf ("grow_list failed %d\n", err); 244 return err; 245 } 246 } 247 Tprintf("setting element %d\n", lp->naddrs); 248 lp->addrs[lp->naddrs].ai = a; 249 lp->addrs[lp->naddrs].freefn = freefn; 250 lp->addrs[lp->naddrs].data = data; 251 lp->naddrs++; 252 Tprintf ("\tcount is now %d: ", lp->naddrs); 253 print_addrlist(lp); 254 Tprintf("\n"); 255 return 0; 256 } 257 258 #define add_host_to_list krb5int_add_host_to_list 259 260 static void call_freeaddrinfo(void *data) 261 { 262 /* Strict interpretation of the C standard says we can't assume 263 that the ABI for f(void*) and f(struct foo *) will be 264 compatible. Use this stub just to be paranoid. */ 265 freeaddrinfo(data); 266 } 267 268 int 269 krb5int_add_host_to_list (struct addrlist *lp, const char *hostname, 270 int port, int secport, 271 int socktype, int family) 272 { 273 struct addrinfo *addrs, *a, *anext, hint; 274 int err; 275 char portbuf[10], secportbuf[10]; 276 void (*freefn)(void *); 277 278 Tprintf ("adding hostname %s, ports %d,%d, family %d, socktype %d\n", 279 hostname, ntohs (port), ntohs (secport), 280 family, socktype); 281 282 memset(&hint, 0, sizeof(hint)); 283 hint.ai_family = family; 284 hint.ai_socktype = socktype; 285 #ifdef AI_NUMERICSERV 286 hint.ai_flags = AI_NUMERICSERV; 287 #endif 288 sprintf(portbuf, "%d", ntohs(port)); 289 sprintf(secportbuf, "%d", ntohs(secport)); 290 err = getaddrinfo (hostname, portbuf, &hint, &addrs); 291 if (err) { 292 Tprintf ("\tgetaddrinfo(\"%s\", \"%s\", ...)\n\treturns %d: %s\n", 293 hostname, portbuf, err, gai_strerror (err)); 294 return translate_ai_error (err); 295 } 296 freefn = call_freeaddrinfo; 297 anext = 0; 298 for (a = addrs; a != 0 && err == 0; a = anext, freefn = 0) { 299 anext = a->ai_next; 300 err = add_addrinfo_to_list (lp, a, freefn, a); 301 } 302 if (err || secport == 0) 303 goto egress; 304 if (socktype == 0) 305 socktype = SOCK_DGRAM; 306 else if (socktype != SOCK_DGRAM) 307 goto egress; 308 hint.ai_family = AF_INET; 309 err = getaddrinfo (hostname, secportbuf, &hint, &addrs); 310 if (err) { 311 err = translate_ai_error (err); 312 goto egress; 313 } 314 freefn = call_freeaddrinfo; 315 for (a = addrs; a != 0 && err == 0; a = anext, freefn = 0) { 316 anext = a->ai_next; 317 err = add_addrinfo_to_list (lp, a, freefn, a); 318 } 319 egress: 320 /* Solaris Kerberos */ 321 if (anext) 322 freeaddrinfo (anext); 323 return err; 324 } 325 326 #include <locate_plugin.h> 327 328 #if TARGET_OS_MAC 329 static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR, LIBDIR "/krb5/plugins/libkrb5", NULL }; /* should be a list */ 330 #else 331 static const char *objdirs[] = { LIBDIR "/krb5/plugins/libkrb5", NULL }; 332 #endif 333 334 struct module_callback_data { 335 int out_of_mem; 336 struct addrlist *lp; 337 }; 338 339 static int 340 module_callback (void *cbdata, int socktype, struct sockaddr *sa) 341 { 342 struct module_callback_data *d = cbdata; 343 struct { 344 struct addrinfo ai; 345 union { 346 struct sockaddr_in sin; 347 struct sockaddr_in6 sin6; 348 } u; 349 } *x; 350 351 if (socktype != SOCK_STREAM && socktype != SOCK_DGRAM) 352 return 0; 353 if (sa->sa_family != AF_INET && sa->sa_family != AF_INET6) 354 return 0; 355 x = malloc (sizeof (*x)); 356 if (x == 0) { 357 d->out_of_mem = 1; 358 return 1; 359 } 360 memset(x, 0, sizeof (*x)); 361 x->ai.ai_addr = (struct sockaddr *) &x->u; 362 x->ai.ai_socktype = socktype; 363 x->ai.ai_family = sa->sa_family; 364 if (sa->sa_family == AF_INET) { 365 x->u.sin = *(struct sockaddr_in *)sa; 366 x->ai.ai_addrlen = sizeof(struct sockaddr_in); 367 } 368 if (sa->sa_family == AF_INET6) { 369 x->u.sin6 = *(struct sockaddr_in6 *)sa; 370 x->ai.ai_addrlen = sizeof(struct sockaddr_in6); 371 } 372 if (add_addrinfo_to_list (d->lp, &x->ai, free, x) != 0) { 373 /* Assumes only error is ENOMEM. */ 374 d->out_of_mem = 1; 375 return 1; 376 } 377 return 0; 378 } 379 380 static krb5_error_code 381 module_locate_server (krb5_context ctx, const krb5_data *realm, 382 struct addrlist *addrlist, 383 enum locate_service_type svc, int socktype, int family) 384 { 385 struct krb5plugin_service_locate_result *res = NULL; 386 krb5_error_code code; 387 struct krb5plugin_service_locate_ftable *vtbl = NULL; 388 void **ptrs; 389 int i; 390 struct module_callback_data cbdata = { 0, }; 391 392 Tprintf("in module_locate_server\n"); 393 cbdata.lp = addrlist; 394 if (!PLUGIN_DIR_OPEN (&ctx->libkrb5_plugins)) { 395 396 code = krb5int_open_plugin_dirs (objdirs, NULL, &ctx->libkrb5_plugins, 397 &ctx->err); 398 if (code) 399 return KRB5_PLUGIN_NO_HANDLE; 400 } 401 402 code = krb5int_get_plugin_dir_data (&ctx->libkrb5_plugins, 403 "service_locator", &ptrs, &ctx->err); 404 if (code) { 405 Tprintf("error looking up plugin symbols: %s\n", 406 krb5_get_error_message(ctx, code)); 407 return KRB5_PLUGIN_NO_HANDLE; 408 } 409 410 for (i = 0; ptrs[i]; i++) { 411 void *blob; 412 413 vtbl = ptrs[i]; 414 Tprintf("element %d is %p\n", i, ptrs[i]); 415 416 /* For now, don't keep the plugin data alive. For long-lived 417 contexts, it may be desirable to change that later. */ 418 code = vtbl->init(ctx, &blob); 419 if (code) 420 continue; 421 422 code = vtbl->lookup(blob, svc, realm->data, socktype, family, 423 module_callback, &cbdata); 424 vtbl->fini(blob); 425 if (code == KRB5_PLUGIN_NO_HANDLE) { 426 /* Module passes, keep going. */ 427 /* XXX */ 428 Tprintf("plugin doesn't handle this realm (KRB5_PLUGIN_NO_HANDLE)\n"); 429 continue; 430 } 431 if (code != 0) { 432 /* Module encountered an actual error. */ 433 Tprintf("plugin lookup routine returned error %d: %s\n", 434 code, error_message(code)); 435 krb5int_free_plugin_dir_data (ptrs); 436 return code; 437 } 438 break; 439 } 440 if (ptrs[i] == NULL) { 441 Tprintf("ran off end of plugin list\n"); 442 krb5int_free_plugin_dir_data (ptrs); 443 return KRB5_PLUGIN_NO_HANDLE; 444 } 445 Tprintf("stopped with plugin #%d, res=%p\n", i, res); 446 447 /* Got something back, yippee. */ 448 Tprintf("now have %d addrs in list %p\n", addrlist->naddrs, addrlist); 449 print_addrlist(addrlist); 450 krb5int_free_plugin_dir_data (ptrs); 451 return 0; 452 } 453 454 /* XXX - move to locate_plugin.h? */ 455 typedef krb5_error_code (*krb5_lookup_func)( 456 void *, 457 enum locate_service_type svc, const char *realm, 458 int socktype, int family, 459 int (*cbfunc)(void *,int,struct sockaddr *), 460 void *cbdata); 461 462 /* 463 * Solaris Kerberos (illumos) 464 * 465 * Allow main programs to provide an override function for _locate_server, 466 * named _krb5_override_service_locator(). If that function is found in 467 * the main program, it's called like a service locator plugin function. 468 * If it returns KRB5_PLUGIN_NO_HANDLE, continue with other _locate_server 469 * functions. If it returns anything else (zero or some other error), 470 * that return is "final" (no other _locate_server functions are called). 471 * This mechanism is used by programs like "idmapd" that want to completely 472 * control service location. 473 */ 474 static krb5_error_code 475 override_locate_server (krb5_context ctx, const krb5_data *realm, 476 struct addrlist *addrlist, 477 enum locate_service_type svc, int socktype, int family) 478 { 479 struct module_callback_data cbdata = { 0, }; 480 krb5_error_code code; 481 void *dlh; 482 krb5_lookup_func lookup_func; 483 484 Tprintf("in override_locate_server\n"); 485 cbdata.lp = addrlist; 486 487 if ((dlh = dlopen(0, RTLD_FIRST | RTLD_LAZY)) == NULL) { 488 Tprintf("dlopen failed\n"); 489 return KRB5_PLUGIN_NO_HANDLE; 490 } 491 lookup_func = (krb5_lookup_func) dlsym( 492 dlh, "_krb5_override_service_locator"); 493 dlclose(dlh); 494 if (lookup_func == NULL) { 495 Tprintf("dlsym failed\n"); 496 return KRB5_PLUGIN_NO_HANDLE; 497 } 498 499 code = lookup_func(ctx, svc, realm->data, socktype, family, 500 module_callback, &cbdata); 501 if (code == KRB5_PLUGIN_NO_HANDLE) { 502 Tprintf("override lookup routine returned KRB5_PLUGIN_NO_HANDLE\n"); 503 return code; 504 } 505 if (code != 0) { 506 /* Module encountered an actual error. */ 507 Tprintf("override lookup routine returned error %d: %s\n", 508 code, error_message(code)); 509 return code; 510 } 511 512 /* Got something back, yippee. */ 513 Tprintf("now have %d addrs in list %p\n", addrlist->naddrs, addrlist); 514 print_addrlist(addrlist); 515 516 return 0; 517 } 518 /* Solaris Kerberos (illumos) */ 519 520 static krb5_error_code 521 prof_locate_server (krb5_context context, const krb5_data *realm, 522 char ***hostlist, 523 enum locate_service_type svc) 524 { 525 const char *realm_srv_names[4]; 526 char **hl, *host, *profname; 527 krb5_error_code code; 528 int i, j, count; 529 530 *hostlist = NULL; /* default - indicate no KDCs found */ 531 532 switch (svc) { 533 case locate_service_kdc: 534 profname = "kdc"; 535 break; 536 case locate_service_master_kdc: 537 profname = "master_kdc"; 538 break; 539 case locate_service_kadmin: 540 profname = "admin_server"; 541 break; 542 case locate_service_krb524: 543 profname = "krb524_server"; 544 break; 545 case locate_service_kpasswd: 546 profname = "kpasswd_server"; 547 break; 548 default: 549 return EINVAL; 550 } 551 552 if ((host = malloc(realm->length + 1)) == NULL) 553 return ENOMEM; 554 555 (void) strncpy(host, realm->data, realm->length); 556 host[realm->length] = '\0'; 557 hl = 0; 558 559 realm_srv_names[0] = "realms"; 560 realm_srv_names[1] = host; 561 realm_srv_names[2] = profname; 562 realm_srv_names[3] = 0; 563 564 code = profile_get_values(context->profile, realm_srv_names, &hl); 565 if (code) { 566 Tprintf ("config file lookup failed: %s\n", 567 error_message(code)); 568 if (code == PROF_NO_SECTION || code == PROF_NO_RELATION) 569 code = KRB5_REALM_UNKNOWN; 570 krb5_xfree(host); 571 return code; 572 } 573 krb5_xfree(host); 574 575 *hostlist = hl; 576 577 return 0; 578 } 579 580 static krb5_error_code 581 dns_locate_server (krb5_context context, const krb5_data *realm, 582 struct srv_dns_entry **dns_list_head, 583 enum locate_service_type svc, int socktype, int family) 584 { 585 const char *dnsname; 586 int use_dns = _krb5_use_dns_kdc(context); 587 krb5_error_code code; 588 struct srv_dns_entry *head = NULL; 589 590 *dns_list_head = NULL; /* default: indicate we have found no KDCs */ 591 592 if (!use_dns) 593 return KRB5_PLUGIN_NO_HANDLE; 594 595 switch (svc) { 596 case locate_service_kdc: 597 dnsname = "_kerberos"; 598 break; 599 case locate_service_master_kdc: 600 dnsname = "_kerberos-master"; 601 break; 602 case locate_service_kadmin: 603 dnsname = "_kerberos-adm"; 604 break; 605 case locate_service_krb524: 606 dnsname = "_krb524"; 607 break; 608 case locate_service_kpasswd: 609 dnsname = "_kpasswd"; 610 break; 611 default: 612 return KRB5_PLUGIN_NO_HANDLE; 613 } 614 615 code = 0; 616 if (socktype == SOCK_DGRAM || socktype == 0) { 617 code = krb5int_make_srv_query_realm(realm, dnsname, "_udp", &head); 618 if (code) 619 Tprintf("dns udp lookup returned error %d\n", code); 620 } 621 if ((socktype == SOCK_STREAM || socktype == 0) && code == 0) { 622 code = krb5int_make_srv_query_realm(realm, dnsname, "_tcp", &head); 623 if (code) 624 Tprintf("dns tcp lookup returned error %d\n", code); 625 } 626 627 if (head == NULL) 628 return 0; 629 630 /* Check for the "." case indicating no support. */ 631 if (head->next == 0 && head->host[0] == 0) { 632 free(head->host); 633 free(head); 634 return KRB5_ERR_NO_SERVICE; 635 } 636 637 /* 638 * Okay! Now we've got a linked list of entries sorted by 639 * priority. Return it so later we can map hostnames to net addresses. 640 */ 641 *dns_list_head = head; 642 643 return 0; 644 } 645 646 /* 647 * Given the list of hostnames of KDCs found in DNS SRV recs, lets go 648 * thru NSS (name svc switch) to get the net addrs. 649 */ 650 static krb5_error_code 651 dns_hostnames2netaddrs( 652 struct srv_dns_entry *head, 653 enum locate_service_type svc, 654 int socktype, 655 int family, 656 struct addrlist *addrlist) 657 { 658 struct srv_dns_entry *entry = NULL, *next; 659 krb5_error_code code; 660 661 Tprintf ("walking answer list:\n"); 662 for (entry = head; entry != NULL; entry = entry->next) { 663 code = 0; 664 if (socktype) 665 code = add_host_to_list (addrlist, entry->host, 666 htons (entry->port), 0, 667 socktype, family); 668 else { 669 (void) add_host_to_list (addrlist, entry->host, 670 htons (entry->port), 0, 671 SOCK_DGRAM, family); 672 673 code = add_host_to_list (addrlist, entry->host, 674 htons (entry->port), 0, 675 SOCK_STREAM, family); 676 } 677 if (code) { 678 Tprintf(" fail add_host code=%d %s\n", code, entry->host); 679 } 680 } 681 Tprintf ("[end]\n"); 682 683 return code; 684 } 685 686 /* 687 * Given the DNS SRV recs list, return a string of all the hosts like so: 688 * "fqdn0[,fqdn1][,fqdnN]" 689 */ 690 static char * 691 dnslist2str(struct srv_dns_entry *dns_list_head) 692 { 693 struct srv_dns_entry *head = dns_list_head; 694 struct srv_dns_entry *entry = NULL, *next; 695 unsigned int size = 0, c = 0, buf_size; 696 char *s = NULL; 697 698 for (entry = head; entry; entry = entry->next, c++) { 699 size += strlen(entry->host); 700 } 701 if (!c) 702 return NULL; 703 704 /* hostnames + commas + NULL */ 705 buf_size = size + (c - 1) + 1; 706 s = malloc(buf_size); 707 if (!s) 708 return NULL; 709 710 (void) strlcpy(s, head->host, buf_size); 711 for (entry = head->next; entry; entry = entry->next) { 712 (void) strlcat(s, ",", buf_size); 713 (void) strlcat(s, entry->host, buf_size); 714 } 715 716 return s; 717 } 718 719 /* 720 * Given the profile hostlist, return a string of all the hosts like so: 721 * "fqdn0[,fqdn1][,fqdnN]" 722 */ 723 static char * 724 hostlist2str(char **hostlist) 725 { 726 unsigned int c = 0, size = 0, buf_size; 727 char **hl = hostlist, *s = NULL; 728 729 while (hl && *hl) { 730 size += strlen(*hl); 731 hl++; 732 c++; 733 } 734 if (!c) 735 return NULL; 736 737 /* hostnames + commas + NULL */ 738 buf_size = size + (c - 1) + 1; 739 s = malloc(buf_size); 740 if (!s) 741 return NULL; 742 743 hl = hostlist; 744 (void) strlcpy(s, *hl, buf_size); 745 hl++; 746 while (hl && *hl) { 747 (void) strlcat(s, ",", buf_size); 748 (void) strlcat(s, *hl, buf_size); 749 hl++; 750 } 751 752 return s; 753 } 754 755 /* 756 * Take the profile KDC list and return a list of net addrs. 757 */ 758 static krb5_error_code 759 prof_hostnames2netaddrs( 760 char **hostlist, 761 enum locate_service_type svc, 762 int socktype, 763 int family, 764 struct addrlist *addrlist) /* output */ 765 { 766 int udpport = 0 , sec_udpport = 0; 767 int code, i; 768 struct servent *serv; 769 770 int count = 0; 771 while (hostlist && hostlist[count]) 772 count++; 773 if (count == 0) { 774 return 0; 775 } 776 777 switch (svc) { 778 case locate_service_kdc: 779 case locate_service_master_kdc: 780 /* We used to use /etc/services for these, but enough systems 781 have old, crufty, wrong settings that this is probably 782 better. */ 783 udpport = htons(KRB5_DEFAULT_PORT); 784 sec_udpport = htons(KRB5_DEFAULT_SEC_PORT); 785 break; 786 case locate_service_kadmin: 787 udpport = htons(DEFAULT_KADM5_PORT); 788 break; 789 case locate_service_krb524: 790 serv = getservbyname(KRB524_SERVICE, "udp"); 791 udpport = serv ? serv->s_port : htons (KRB524_PORT); 792 break; 793 case locate_service_kpasswd: 794 udpport = htons(DEFAULT_KPASSWD_PORT); 795 break; 796 default: 797 return EINVAL; 798 } 799 800 for (i=0; hostlist[i]; i++) { 801 int p1, p2; 802 char *cp, *port, *host; 803 804 host = hostlist[i]; 805 /* 806 * Strip off excess whitespace 807 */ 808 cp = strchr(host, ' '); 809 if (cp) 810 *cp = 0; 811 cp = strchr(host, '\t'); 812 if (cp) 813 *cp = 0; 814 port = strchr(host, ':'); 815 if (port) { 816 *port = 0; 817 port++; 818 } 819 820 if (port) { 821 unsigned long l; 822 #ifdef HAVE_STROUL 823 char *endptr; 824 l = strtoul (port, &endptr, 10); 825 if (endptr == NULL || *endptr != 0) 826 return EINVAL; 827 #else 828 l = atoi (port); 829 #endif 830 /* L is unsigned, don't need to check <0. */ 831 if (l == 0 || l > 65535) 832 return EINVAL; 833 p1 = htons (l); 834 p2 = 0; 835 } else { 836 p1 = udpport; 837 p2 = sec_udpport; 838 } 839 840 841 if (socktype != 0) { 842 code = add_host_to_list (addrlist, hostlist[i], p1, p2, 843 socktype, family); 844 } else { 845 code = add_host_to_list (addrlist, hostlist[i], p1, p2, 846 SOCK_DGRAM, family); 847 if (code == 0) 848 code = add_host_to_list (addrlist, hostlist[i], p1, p2, 849 SOCK_STREAM, family); 850 } 851 } 852 853 return code; 854 } 855 856 /* 857 * Wrapper function for the various backends 858 */ 859 860 krb5_error_code 861 krb5int_locate_server (krb5_context context, const krb5_data *realm, 862 struct addrlist *addrlist, 863 enum locate_service_type svc, 864 int socktype, int family) 865 { 866 krb5_error_code code; 867 struct addrlist al = ADDRLIST_INIT; 868 char **hostlist = NULL; 869 struct srv_dns_entry *dns_list_head = NULL; 870 871 *addrlist = al; 872 873 /* 874 * Solaris Kerberos (illumos) 875 * Allow main programs to override _locate_server() 876 */ 877 code = override_locate_server(context, realm, &al, svc, socktype, family); 878 if (code != KRB5_PLUGIN_NO_HANDLE) { 879 if (code == 0) 880 *addrlist = al; 881 else if (al.space) 882 free_list (&al); 883 return (code); 884 } 885 /* Solaris Kerberos (illumos) */ 886 887 code = module_locate_server(context, realm, &al, svc, socktype, family); 888 Tprintf("module_locate_server returns %d\n", code); 889 if (code == KRB5_PLUGIN_NO_HANDLE) { 890 /* 891 * We always try the local file before DNS. Note that there 892 * is no way to indicate "service not available" via the 893 * config file. 894 */ 895 code = prof_locate_server(context, realm, &hostlist, svc); 896 897 /* 898 * Solaris Kerberos: 899 * If kpasswd_server has not been configured and dns_lookup_kdc - 900 * dns_fallback are not configured then admin_server should 901 * be inferred, per krb5.conf(4). 902 */ 903 if (code && svc == locate_service_kpasswd && 904 !maybe_use_dns(context, "dns_lookup_kdc", 0)) { 905 code = prof_locate_server(context, realm, &hostlist, 906 locate_service_kadmin); 907 } 908 909 #ifdef KRB5_DNS_LOOKUP 910 /* 911 * Solaris Kerberos: 912 * There is no point in trying to locate the KDC in DNS if "realm" 913 * is empty. 914 */ 915 /* Try DNS for all profile errors? */ 916 if (code && !krb5_is_referral_realm(realm)) { 917 krb5_error_code code2; 918 code2 = dns_locate_server(context, realm, &dns_list_head, 919 svc, socktype, family); 920 921 if (code2 != KRB5_PLUGIN_NO_HANDLE) 922 code = code2; 923 } 924 #endif /* KRB5_DNS_LOOKUP */ 925 926 /* We could put more heuristics here, like looking up a hostname 927 of "kerberos."+REALM, etc. */ 928 } 929 930 if (code != 0) { 931 if (al.space) 932 free_list (&al); 933 if (hostlist) 934 profile_free_list(hostlist); 935 if (dns_list_head) 936 krb5int_free_srv_dns_data(dns_list_head); 937 938 return code; 939 } 940 941 /* 942 * At this point we have no errors, let's check to see if we have 943 * any KDC entries from krb5.conf or DNS. 944 */ 945 if (!hostlist && !dns_list_head) { 946 switch(svc) { 947 case locate_service_master_kdc: 948 krb5_set_error_message(context, 949 KRB5_REALM_CANT_RESOLVE, 950 dgettext(TEXT_DOMAIN, 951 "Cannot find a master KDC entry in krb5.conf(4) or DNS Service Location records for realm '%.*s'"), 952 realm->length, realm->data); 953 break; 954 case locate_service_kadmin: 955 krb5_set_error_message(context, 956 KRB5_REALM_CANT_RESOLVE, 957 dgettext(TEXT_DOMAIN, 958 "Cannot find a kadmin KDC entry in krb5.conf(4) or DNS Service Location records for realm '%.*s'"), 959 realm->length, realm->data); 960 break; 961 case locate_service_kpasswd: 962 krb5_set_error_message(context, 963 KRB5_REALM_CANT_RESOLVE, 964 dgettext(TEXT_DOMAIN, 965 "Cannot find a kpasswd KDC entry in krb5.conf(4) or DNS Service Location records for realm '%.*s'"), 966 realm->length, realm->data); 967 break; 968 default: /* locate_service_kdc: */ 969 krb5_set_error_message(context, 970 KRB5_REALM_CANT_RESOLVE, 971 dgettext(TEXT_DOMAIN, 972 "Cannot find any KDC entries in krb5.conf(4) or DNS Service Location records for realm '%.*s'"), 973 realm->length, realm->data); 974 975 } 976 return KRB5_REALM_CANT_RESOLVE; 977 } 978 979 /* We have KDC entries, let see if we can get their net addrs. */ 980 if (hostlist) 981 code = prof_hostnames2netaddrs(hostlist, svc, 982 socktype, family, &al); 983 else if (dns_list_head) 984 code = dns_hostnames2netaddrs(dns_list_head, svc, 985 socktype, family, &al); 986 if (code) { 987 if (hostlist) 988 profile_free_list(hostlist); 989 if (dns_list_head) 990 krb5int_free_srv_dns_data(dns_list_head); 991 return code; 992 } 993 994 /* 995 * Solaris Kerberos: 996 * If an entry for _kerberos-master. does not exist (checked for 997 * above) but _kpasswd. does then treat that as an entry for the 998 * master KDC (but use port 88 not the kpasswd port). MS AD creates 999 * kpasswd entries by default in DNS. 1000 */ 1001 if (!dns_list_head && svc == locate_service_master_kdc && 1002 al.naddrs == 0) { 1003 1004 /* Look for _kpasswd._tcp|udp */ 1005 code = dns_locate_server(context, realm, &dns_list_head, 1006 locate_service_kpasswd, socktype, family); 1007 1008 if (code == 0 && dns_list_head) { 1009 int i; 1010 struct addrinfo *a; 1011 1012 code = dns_hostnames2netaddrs(dns_list_head, svc, 1013 socktype, family, &al); 1014 1015 /* Set the port to 88 instead of the kpasswd port */ 1016 if (code == 0 && al.naddrs > 0) { 1017 for (i = 0; i < al.naddrs; i++) { 1018 if (al.addrs[i].ai->ai_family == AF_INET) 1019 for (a = al.addrs[i].ai; a != NULL; a = a->ai_next) 1020 ((struct sockaddr_in *)a->ai_addr)->sin_port = 1021 htons(KRB5_DEFAULT_PORT); 1022 1023 if (al.addrs[i].ai->ai_family == AF_INET6) 1024 for (a = al.addrs[i].ai; a != NULL; a = a->ai_next) 1025 ((struct sockaddr_in6 *)a->ai_addr)->sin6_port = 1026 htons(KRB5_DEFAULT_PORT); 1027 } 1028 } 1029 } 1030 } 1031 1032 /* No errors so far, lets see if we have KDC net addrs */ 1033 if (al.naddrs == 0) { 1034 char *hostlist_str = NULL, *dnslist_str = NULL; 1035 if (al.space) 1036 free_list (&al); 1037 1038 if (hostlist) { 1039 hostlist_str = hostlist2str(hostlist); 1040 krb5_set_error_message(context, KRB5_REALM_CANT_RESOLVE, 1041 dgettext(TEXT_DOMAIN, 1042 "Cannot resolve network address for KDCs '%s' specified in krb5.conf(4) for realm %.*s"), 1043 hostlist_str ? hostlist_str : "unknown", 1044 realm->length, realm->data); 1045 if (hostlist_str) 1046 free(hostlist_str); 1047 } else if (dns_list_head) { 1048 dnslist_str = dnslist2str(dns_list_head); 1049 krb5_set_error_message(context, KRB5_REALM_CANT_RESOLVE, 1050 dgettext(TEXT_DOMAIN, 1051 "Cannot resolve network address for KDCs '%s' discovered via DNS Service Location records for realm '%.*s'"), 1052 dnslist_str ? dnslist_str : "unknown", 1053 realm->length, realm->data); 1054 if (dnslist_str) 1055 free(dnslist_str); 1056 } 1057 1058 if (hostlist) 1059 profile_free_list(hostlist); 1060 if (dns_list_head) 1061 krb5int_free_srv_dns_data(dns_list_head); 1062 1063 return KRB5_REALM_CANT_RESOLVE; 1064 } 1065 1066 if (hostlist) 1067 profile_free_list(hostlist); 1068 if (dns_list_head) 1069 krb5int_free_srv_dns_data(dns_list_head); 1070 1071 *addrlist = al; 1072 return 0; 1073 } 1074 1075 krb5_error_code 1076 krb5_locate_kdc(krb5_context context, const krb5_data *realm, 1077 struct addrlist *addrlist, 1078 int get_masters, int socktype, int family) 1079 { 1080 return krb5int_locate_server(context, realm, addrlist, 1081 (get_masters 1082 ? locate_service_master_kdc 1083 : locate_service_kdc), 1084 socktype, family); 1085 } 1086 1087 /* 1088 * Solaris Kerberos: for backward compat. Avoid using this 1089 * function! 1090 */ 1091 krb5_error_code 1092 krb5_get_servername(krb5_context context, 1093 const krb5_data *realm, 1094 const char *name, const char *proto, 1095 char *srvhost, 1096 unsigned short *port) 1097 { 1098 krb5_error_code code = KRB5_REALM_UNKNOWN; 1099 1100 #ifdef KRB5_DNS_LOOKUP 1101 { 1102 int use_dns = _krb5_use_dns_kdc(context); 1103 1104 if (use_dns) { 1105 struct srv_dns_entry *head = NULL; 1106 1107 code = krb5int_make_srv_query_realm(realm, name, proto, &head); 1108 if (code) 1109 return (code); 1110 1111 if (head == NULL) 1112 return KRB5_REALM_CANT_RESOLVE; 1113 1114 *port = head->port; 1115 (void) strlcpy(srvhost, head->host, MAX_DNS_NAMELEN); 1116 1117 #ifdef DEBUG 1118 fprintf (stderr, "krb5_get_servername svrhost %s, port %d\n", 1119 srvhost, *port); 1120 #endif 1121 krb5int_free_srv_dns_data(head); 1122 } 1123 } 1124 #endif /* KRB5_DNS_LOOKUP */ 1125 1126 return (code); 1127 } 1128