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