1 #pragma ident "%Z%%M% %I% %E% SMI" 2 3 /* 4 * lib/krb5/os/hst_realm.c 5 * 6 * Copyright 1990,1991,2002 by the Massachusetts Institute of Technology. 7 * All Rights Reserved. 8 * 9 * Export of this software from the United States of America may 10 * require a specific license from the United States Government. 11 * It is the responsibility of any person or organization contemplating 12 * export to obtain such a license before exporting. 13 * 14 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 15 * distribute this software and its documentation for any purpose and 16 * without fee is hereby granted, provided that the above copyright 17 * notice appear in all copies and that both that copyright notice and 18 * this permission notice appear in supporting documentation, and that 19 * the name of M.I.T. not be used in advertising or publicity pertaining 20 * to distribution of the software without specific, written prior 21 * permission. Furthermore if you modify this software you must label 22 * your software as modified software and not distribute it in such a 23 * fashion that it might be confused with the original M.I.T. software. 24 * M.I.T. makes no representations about the suitability of 25 * this software for any purpose. It is provided "as is" without express 26 * or implied warranty. 27 * 28 * 29 * krb5_get_host_realm() 30 */ 31 32 /* 33 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 34 * Use is subject to license terms. 35 */ 36 37 /* 38 Figures out the Kerberos realm names for host, filling in a 39 pointer to an argv[] style list of names, terminated with a null pointer. 40 41 If host is NULL, the local host's realms are determined. 42 43 If there are no known realms for the host, the filled-in pointer is set 44 to NULL. 45 46 The pointer array and strings pointed to are all in allocated storage, 47 and should be freed by the caller when finished. 48 49 returns system errors 50 */ 51 52 /* 53 * Implementation notes: 54 * 55 * this implementation only provides one realm per host, using the same 56 * mapping file used in kerberos v4. 57 58 * Given a fully-qualified domain-style primary host name, 59 * return the name of the Kerberos realm for the host. 60 * If the hostname contains no discernable domain, or an error occurs, 61 * return the local realm name, as supplied by krb5_get_default_realm(). 62 * If the hostname contains a domain, but no translation is found, 63 * the hostname's domain is converted to upper-case and returned. 64 * 65 * The format of each line of the translation file is: 66 * domain_name kerberos_realm 67 * -or- 68 * host_name kerberos_realm 69 * 70 * domain_name should be of the form .XXX.YYY (e.g. .LCS.MIT.EDU) 71 * host names should be in the usual form (e.g. FOO.BAR.BAZ) 72 */ 73 74 75 #include "k5-int.h" 76 #include "os-proto.h" 77 #include <ctype.h> 78 #include <stdio.h> 79 #ifdef HAVE_STRING_H 80 #include <string.h> 81 #else 82 #include <strings.h> 83 #endif 84 85 #include <fake-addrinfo.h> 86 87 #ifdef KRB5_DNS_LOOKUP 88 89 #include "dnsglue.h" 90 /* 91 * Try to look up a TXT record pointing to a Kerberos realm 92 */ 93 94 krb5_error_code 95 krb5_try_realm_txt_rr(const char *prefix, const char *name, char **realm) 96 { 97 krb5_error_code retval = KRB5_ERR_HOST_REALM_UNKNOWN; 98 const unsigned char *p, *base; 99 char host[MAXDNAME], *h; 100 int ret, rdlen, len; 101 struct krb5int_dns_state *ds = NULL; 102 103 /* 104 * Form our query, and send it via DNS 105 */ 106 107 if (name == NULL || name[0] == '\0') { 108 if (strlen (prefix) >= sizeof(host)-1) 109 return KRB5_ERR_HOST_REALM_UNKNOWN; 110 strcpy(host,prefix); 111 } else { 112 if ( strlen(prefix) + strlen(name) + 3 > MAXDNAME ) 113 return KRB5_ERR_HOST_REALM_UNKNOWN; 114 /*LINTED*/ 115 sprintf(host,"%s.%s", prefix, name); 116 117 /* Realm names don't (normally) end with ".", but if the query 118 doesn't end with "." and doesn't get an answer as is, the 119 resolv code will try appending the local domain. Since the 120 realm names are absolutes, let's stop that. 121 122 But only if a name has been specified. If we are performing 123 a search on the prefix alone then the intention is to allow 124 the local domain or domain search lists to be expanded. 125 */ 126 127 h = host + strlen (host); 128 if ((h > host) && (h[-1] != '.') && ((h - host + 1) < sizeof(host))) 129 strcpy (h, "."); 130 } 131 ret = krb5int_dns_init(&ds, host, C_IN, T_TXT); 132 if (ret < 0) 133 goto errout; 134 135 ret = krb5int_dns_nextans(ds, &base, &rdlen); 136 if (ret < 0 || base == NULL) 137 goto errout; 138 139 p = base; 140 if (!INCR_OK(base, rdlen, p, 1)) 141 goto errout; 142 len = *p++; 143 *realm = malloc((size_t)len + 1); 144 if (*realm == NULL) { 145 retval = ENOMEM; 146 goto errout; 147 } 148 strncpy(*realm, (const char *)p, (size_t)len); 149 (*realm)[len] = '\0'; 150 /* Avoid a common error. */ 151 if ( (*realm)[len-1] == '.' ) 152 (*realm)[len-1] = '\0'; 153 retval = 0; 154 155 errout: 156 if (ds != NULL) { 157 krb5int_dns_fini(ds); 158 ds = NULL; 159 } 160 return retval; 161 } 162 #else /* KRB5_DNS_LOOKUP */ 163 #ifndef MAXDNAME 164 #define MAXDNAME (16 * MAXHOSTNAMELEN) 165 #endif /* MAXDNAME */ 166 #endif /* KRB5_DNS_LOOKUP */ 167 168 krb5_error_code krb5int_translate_gai_error (int); 169 170 static krb5_error_code 171 krb5int_get_fq_hostname (char *buf, size_t bufsize, const char *name) 172 { 173 struct addrinfo *ai, hints; 174 int err; 175 176 memset (&hints, 0, sizeof (hints)); 177 hints.ai_flags = AI_CANONNAME; 178 err = getaddrinfo (name, 0, &hints, &ai); 179 if (err) 180 return krb5int_translate_gai_error (err); 181 if (ai->ai_canonname == 0) 182 return KRB5_EAI_FAIL; 183 strncpy (buf, ai->ai_canonname, bufsize); 184 buf[bufsize-1] = 0; 185 freeaddrinfo (ai); 186 return 0; 187 } 188 189 /* Get the local host name, try to make it fully-qualified. 190 Always return a null-terminated string. 191 Might return an error if gethostname fails. */ 192 krb5_error_code 193 krb5int_get_fq_local_hostname (char *buf, size_t bufsiz) 194 { 195 buf[0] = 0; 196 if (gethostname (buf, bufsiz) == -1) 197 return SOCKET_ERRNO; 198 buf[bufsiz - 1] = 0; 199 return krb5int_get_fq_hostname (buf, bufsiz, buf); 200 } 201 202 krb5_error_code KRB5_CALLCONV 203 krb5_get_host_realm(krb5_context context, const char *host, char ***realmsp) 204 { 205 char **retrealms; 206 char *realm, *cp, *temp_realm; 207 krb5_error_code retval; 208 char local_host[MAXDNAME+1]; 209 210 #ifdef DEBUG_REFERRALS 211 printf("get_host_realm(host:%s) called\n",host); 212 #endif 213 214 krb5int_clean_hostname(context, host, local_host, sizeof local_host); 215 216 /* 217 Search for the best match for the host or domain. 218 Example: Given a host a.b.c.d, try to match on: 219 1) A.B.C.D 220 2) .B.C.D 221 3) B.C.D 222 4) .C.D 223 5) C.D 224 6) .D 225 7) D 226 */ 227 228 cp = local_host; 229 #ifdef DEBUG_REFERRALS 230 printf(" local_host: %s\n",local_host); 231 #endif 232 realm = (char *)NULL; 233 temp_realm = 0; 234 while (cp) { 235 #ifdef DEBUG_REFERRALS 236 printf(" trying to look up %s in the domain_realm map\n",cp); 237 #endif 238 retval = profile_get_string(context->profile, "domain_realm", cp, 239 0, (char *)NULL, &temp_realm); 240 if (retval) 241 return retval; 242 if (temp_realm != (char *)NULL) 243 break; /* Match found */ 244 245 /* Setup for another test */ 246 if (*cp == '.') { 247 cp++; 248 } else { 249 cp = strchr(cp, '.'); 250 } 251 } 252 #ifdef DEBUG_REFERRALS 253 printf(" done searching the domain_realm map\n"); 254 #endif 255 if (temp_realm) { 256 #ifdef DEBUG_REFERRALS 257 printf(" temp_realm is %s\n",temp_realm); 258 #endif 259 realm = malloc(strlen(temp_realm) + 1); 260 if (!realm) { 261 profile_release_string(temp_realm); 262 return ENOMEM; 263 } 264 strcpy(realm, temp_realm); 265 profile_release_string(temp_realm); 266 } 267 268 if (realm == (char *)NULL) { 269 if (!(cp = (char *)malloc(strlen(KRB5_REFERRAL_REALM)+1))) 270 return ENOMEM; 271 strcpy(cp, KRB5_REFERRAL_REALM); 272 realm = cp; 273 } 274 275 if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) { 276 if (realm != (char *)NULL) 277 free(realm); 278 return ENOMEM; 279 } 280 281 retrealms[0] = realm; 282 retrealms[1] = 0; 283 284 *realmsp = retrealms; 285 return 0; 286 } 287 288 #if defined(_WIN32) && !defined(__CYGWIN32__) 289 # ifndef EAFNOSUPPORT 290 # define EAFNOSUPPORT WSAEAFNOSUPPORT 291 # endif 292 #endif 293 294 krb5_error_code 295 krb5int_translate_gai_error (int num) 296 { 297 switch (num) { 298 #ifdef EAI_ADDRFAMILY 299 case EAI_ADDRFAMILY: 300 return EAFNOSUPPORT; 301 #endif 302 case EAI_AGAIN: 303 return EAGAIN; 304 case EAI_BADFLAGS: 305 return EINVAL; 306 case EAI_FAIL: 307 return KRB5_EAI_FAIL; 308 case EAI_FAMILY: 309 return EAFNOSUPPORT; 310 case EAI_MEMORY: 311 return ENOMEM; 312 #if EAI_NODATA != EAI_NONAME 313 case EAI_NODATA: 314 return KRB5_EAI_NODATA; 315 #endif 316 case EAI_NONAME: 317 return KRB5_EAI_NONAME; 318 case EAI_SERVICE: 319 return KRB5_EAI_SERVICE; 320 case EAI_SOCKTYPE: 321 return EINVAL; 322 #ifdef EAI_SYSTEM 323 case EAI_SYSTEM: 324 return errno; 325 #endif 326 } 327 /* abort (); */ 328 return -1; 329 } 330 331 332 /* 333 * Ganked from krb5_get_host_realm; handles determining a fallback realm 334 * to try in the case where referrals have failed and it's time to go 335 * look at TXT records or make a DNS-based assumption. 336 */ 337 338 krb5_error_code KRB5_CALLCONV 339 krb5_get_fallback_host_realm(krb5_context context, krb5_data *hdata, char ***realmsp) 340 { 341 char **retrealms; 342 char *realm = (char *)NULL, *cp; 343 krb5_error_code retval; 344 char local_host[MAXDNAME+1], host[MAXDNAME+1]; 345 346 /* Convert what we hope is a hostname to a string. */ 347 memcpy(host, hdata->data, hdata->length); 348 host[hdata->length]=0; 349 350 #ifdef DEBUG_REFERRALS 351 printf("get_fallback_host_realm(host >%s<) called\n",host); 352 #endif 353 354 krb5int_clean_hostname(context, host, local_host, sizeof local_host); 355 356 #ifdef DEBUG_REFERRALS 357 printf(" local_host: %s\n",local_host); 358 #endif 359 360 #ifdef KRB5_DNS_LOOKUP 361 if (_krb5_use_dns_realm(context)) { 362 /* 363 * Since this didn't appear in our config file, try looking 364 * it up via DNS. Look for a TXT records of the form: 365 * 366 * _kerberos.<hostname> 367 * 368 */ 369 cp = local_host; 370 do { 371 retval = krb5_try_realm_txt_rr("_kerberos", cp, &realm); 372 cp = strchr(cp,'.'); 373 if (cp) 374 cp++; 375 } while (retval && cp && cp[0]); 376 } else 377 #endif /* KRB5_DNS_LOOKUP */ 378 { 379 /* 380 * Solaris Kerberos: 381 * Fallback to looking for a realm based on the DNS domain 382 * of the host. Note: "local_host" here actually refers to the 383 * host and NOT necessarily the local hostnane. 384 */ 385 (void) krb5int_fqdn_get_realm(context, local_host, 386 &realm); 387 #ifdef DEBUG_REFERRALS 388 printf(" done finding DNS-based default realm: >%s<\n",realm); 389 #endif 390 } 391 392 393 if (realm == (char *)NULL) { 394 /* We are defaulting to the local realm */ 395 retval = krb5_get_default_realm(context, &realm); 396 if (retval) { 397 return retval; 398 } 399 } 400 if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) { 401 if (realm != (char *)NULL) 402 free(realm); 403 return ENOMEM; 404 } 405 406 retrealms[0] = realm; 407 retrealms[1] = 0; 408 409 *realmsp = retrealms; 410 return 0; 411 } 412 413 /* 414 * Common code for krb5_get_host_realm and krb5_get_fallback_host_realm 415 * to do basic sanity checks on supplied hostname. 416 */ 417 krb5_error_code KRB5_CALLCONV 418 krb5int_clean_hostname(krb5_context context, const char *host, char *local_host, size_t lhsize) 419 { 420 char *cp; 421 krb5_error_code retval; 422 int l; 423 424 local_host[0]=0; 425 #ifdef DEBUG_REFERRALS 426 printf("krb5int_clean_hostname called: host<%s>, local_host<%s>, size %d\n",host,local_host,lhsize); 427 #endif 428 if (host) { 429 /* Filter out numeric addresses if the caller utterly failed to 430 convert them to names. */ 431 /* IPv4 - dotted quads only */ 432 if (strspn(host, "01234567890.") == strlen(host)) { 433 /* All numbers and dots... if it's three dots, it's an 434 IP address, and we reject it. But "12345" could be 435 a local hostname, couldn't it? We'll just assume 436 that a name with three dots is not meant to be an 437 all-numeric hostname three all-numeric domains down 438 from the current domain. */ 439 int ndots = 0; 440 const char *p; 441 for (p = host; *p; p++) 442 if (*p == '.') 443 ndots++; 444 if (ndots == 3) 445 return KRB5_ERR_NUMERIC_REALM; 446 } 447 if (strchr(host, ':')) 448 /* IPv6 numeric address form? Bye bye. */ 449 return KRB5_ERR_NUMERIC_REALM; 450 451 /* Should probably error out if strlen(host) > MAXDNAME. */ 452 strncpy(local_host, host, lhsize); 453 local_host[lhsize - 1] = '\0'; 454 } else { 455 retval = krb5int_get_fq_local_hostname (local_host, lhsize); 456 if (retval) 457 return retval; 458 } 459 460 /* fold to lowercase */ 461 for (cp = local_host; *cp; cp++) { 462 if (isupper((unsigned char) (*cp))) 463 *cp = tolower((unsigned char) *cp); 464 } 465 l = strlen(local_host); 466 /* strip off trailing dot */ 467 if (l && local_host[l-1] == '.') 468 local_host[l-1] = 0; 469 470 #ifdef DEBUG_REFERRALS 471 printf("krb5int_clean_hostname ending: host<%s>, local_host<%s>, size %d\n",host,local_host,lhsize); 472 #endif 473 return 0; 474 } 475 476 /* 477 * Solaris Kerberos: 478 * Walk through the components of a domain. At each 479 * stage determine if a KDC can be located for that domain. 480 * Return a realm corresponding to the upper-cased domain name 481 * for which a KDC was found or NULL if no KDC was found. 482 */ 483 krb5_error_code 484 krb5int_domain_get_realm(krb5_context context, const char *domain, char **realm) { 485 krb5_error_code retval; 486 struct addrlist addrlist; 487 krb5_data drealm; 488 char *cp = NULL; 489 char *fqdn = NULL; 490 491 *realm = NULL; 492 memset(&drealm, 0, sizeof (drealm)); 493 494 if (!(fqdn = malloc(strlen(domain) + 1))) { 495 return (ENOMEM); 496 } 497 strlcpy(fqdn, domain, strlen(domain) + 1); 498 499 /* Upper case the domain (for use as a realm) */ 500 for (cp = fqdn; *cp; cp++) 501 if (islower((int)(*cp))) 502 *cp = toupper((int)*cp); 503 504 cp = fqdn; 505 while (strchr(cp, '.') != NULL) { 506 507 drealm.length = strlen(cp); 508 drealm.data = cp; 509 510 /* Find a kdc based on this part of the domain name */ 511 retval = krb5_locate_kdc(context, &drealm, &addrlist, 0, SOCK_DGRAM, 0); 512 krb5int_free_addrlist(&addrlist); 513 514 if (!retval) { /* Found a KDC! */ 515 if (!(*realm = malloc(strlen(cp) + 1))) { 516 free(fqdn); 517 return (ENOMEM); 518 } 519 strlcpy(*realm, cp, strlen(cp) + 1); 520 break; 521 } 522 523 cp = strchr(cp, '.'); 524 cp++; 525 } 526 free(fqdn); 527 return (0); 528 } 529 530 /* 531 * Solaris Kerberos: 532 * Discards the first component of the fqdn and calls 533 * krb5int_domain_get_realm() with the remaining string (domain). 534 * 535 */ 536 krb5_error_code 537 krb5int_fqdn_get_realm(krb5_context context, const char *fqdn, char **realm) { 538 char *domain = strchr(fqdn, '.'); 539 540 if (domain) { 541 domain++; 542 return (krb5int_domain_get_realm(context, domain, realm)); 543 } else { 544 return (-1); 545 } 546 } 547 548