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