/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* lib/krb5/os/hst_realm.c */ /* * Copyright 1990,1991,2002,2008,2009 by the Massachusetts Institute of * Technology. All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * krb5_get_host_realm() * krb5_get_fallback_host_realm() * krb5int_clean_hostname() * krb5_free_host_realm() */ /* Figures out the Kerberos realm names for host, filling in a pointer to an argv[] style list of names, terminated with a null pointer. If host is NULL, the local host's realms are determined. If there are no known realms for the host, the filled-in pointer is set to NULL. The pointer array and strings pointed to are all in allocated storage, and should be freed by the caller when finished. returns system errors */ /* * Implementation notes: * * this implementation only provides one realm per host, using the same * mapping file used in kerberos v4. * Given a fully-qualified domain-style primary host name, * return the name of the Kerberos realm for the host. * If the hostname contains no discernable domain, or an error occurs, * return the local realm name, as supplied by krb5_get_default_realm(). * If the hostname contains a domain, but no translation is found, * the hostname's domain is converted to upper-case and returned. * * The format of each line of the translation file is: * domain_name kerberos_realm * -or- * host_name kerberos_realm * * domain_name should be of the form .XXX.YYY (e.g. .LCS.MIT.EDU) * host names should be in the usual form (e.g. FOO.BAR.BAZ) */ #include "k5-int.h" #include "os-proto.h" #include #include #ifdef HAVE_STRING_H #include #else #include #endif #include "fake-addrinfo.h" static krb5_error_code domain_heuristic(krb5_context context, const char *domain, char **realm, int limit); #ifdef KRB5_DNS_LOOKUP #include "dnsglue.h" #else #ifndef MAXDNAME #define MAXDNAME (16 * MAXHOSTNAMELEN) #endif /* MAXDNAME */ #endif /* KRB5_DNS_LOOKUP */ krb5_error_code krb5int_translate_gai_error (int); static krb5_error_code get_fq_hostname(char *buf, size_t bufsize, const char *name) { struct addrinfo *ai, hints; int err; memset (&hints, 0, sizeof (hints)); hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG; err = getaddrinfo (name, 0, &hints, &ai); if (err) return krb5int_translate_gai_error (err); if (ai->ai_canonname == 0) return KRB5_EAI_FAIL; strncpy (buf, ai->ai_canonname, bufsize); buf[bufsize-1] = 0; freeaddrinfo (ai); return 0; } /* Get the local host name, try to make it fully-qualified. Always return a null-terminated string. Might return an error if gethostname fails. */ krb5_error_code krb5int_get_fq_local_hostname(char *buf, size_t bufsiz) { buf[0] = 0; if (gethostname (buf, bufsiz) == -1) return SOCKET_ERRNO; buf[bufsiz - 1] = 0; return get_fq_hostname (buf, bufsiz, buf); } krb5_error_code KRB5_CALLCONV krb5_get_host_realm(krb5_context context, const char *host, char ***realmsp) { char **retrealms; char *realm, *cp, *temp_realm; krb5_error_code retval; char local_host[MAXDNAME+1]; #ifdef DEBUG_REFERRALS printf("get_host_realm(host:%s) called\n",host); #endif retval = krb5int_clean_hostname(context, host, local_host, sizeof local_host); if (retval) return retval; /* Search for the best match for the host or domain. Example: Given a host a.b.c.d, try to match on: 1) A.B.C.D 2) .B.C.D 3) B.C.D 4) .C.D 5) C.D 6) .D 7) D */ cp = local_host; #ifdef DEBUG_REFERRALS printf(" local_host: %s\n",local_host); #endif realm = (char *)NULL; temp_realm = 0; while (cp) { #ifdef DEBUG_REFERRALS printf(" trying to look up %s in the domain_realm map\n",cp); #endif retval = profile_get_string(context->profile, KRB5_CONF_DOMAIN_REALM, cp, 0, (char *)NULL, &temp_realm); if (retval) return retval; if (temp_realm != (char *)NULL) break; /* Match found */ /* Setup for another test */ if (*cp == '.') { cp++; } else { cp = strchr(cp, '.'); } } #ifdef DEBUG_REFERRALS printf(" done searching the domain_realm map\n"); #endif if (temp_realm) { #ifdef DEBUG_REFERRALS printf(" temp_realm is %s\n",temp_realm); #endif realm = strdup(temp_realm); if (!realm) { profile_release_string(temp_realm); return ENOMEM; } profile_release_string(temp_realm); } if (realm == (char *)NULL) { if (!(cp = strdup(KRB5_REFERRAL_REALM))) return ENOMEM; realm = cp; } if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) { if (realm != (char *)NULL) free(realm); return ENOMEM; } retrealms[0] = realm; retrealms[1] = 0; *realmsp = retrealms; return 0; } #if defined(_WIN32) && !defined(__CYGWIN32__) # ifndef EAFNOSUPPORT # define EAFNOSUPPORT WSAEAFNOSUPPORT # endif #endif krb5_error_code krb5int_translate_gai_error (int num) { switch (num) { #ifdef EAI_ADDRFAMILY case EAI_ADDRFAMILY: return EAFNOSUPPORT; #endif case EAI_AGAIN: return EAGAIN; case EAI_BADFLAGS: return EINVAL; case EAI_FAIL: return KRB5_EAI_FAIL; case EAI_FAMILY: return EAFNOSUPPORT; case EAI_MEMORY: return ENOMEM; #if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME case EAI_NODATA: return KRB5_EAI_NODATA; #endif case EAI_NONAME: return KRB5_EAI_NONAME; #if defined(EAI_OVERFLOW) case EAI_OVERFLOW: return EINVAL; /* XXX */ #endif case EAI_SERVICE: return KRB5_EAI_SERVICE; case EAI_SOCKTYPE: return EINVAL; #ifdef EAI_SYSTEM case EAI_SYSTEM: return errno; #endif } abort (); return -1; } /* * Ganked from krb5_get_host_realm; handles determining a fallback realm * to try in the case where referrals have failed and it's time to go * look at TXT records or make a DNS-based assumption. */ krb5_error_code KRB5_CALLCONV krb5_get_fallback_host_realm(krb5_context context, krb5_data *hdata, char ***realmsp) { char **retrealms; char *realm, *cp; krb5_error_code retval; char local_host[MAXDNAME+1], host[MAXDNAME+1]; /* Convert what we hope is a hostname to a string. */ memcpy(host, hdata->data, hdata->length); host[hdata->length]=0; #ifdef DEBUG_REFERRALS printf("get_fallback_host_realm(host >%s<) called\n",host); #endif retval = krb5int_clean_hostname(context, host, local_host, sizeof local_host); if (retval) return retval; /* * Try looking up a _kerberos. TXT record in DNS. This * heuristic is turned off by default since, in the absence of * secure DNS, it can allow an attacker to control the realm used * for a host. */ realm = (char *)NULL; #ifdef KRB5_DNS_LOOKUP if (_krb5_use_dns_realm(context)) { cp = local_host; do { retval = krb5_try_realm_txt_rr("_kerberos", cp, &realm); cp = strchr(cp,'.'); if (cp) cp++; } while (retval && cp && cp[0]); } #endif /* KRB5_DNS_LOOKUP */ /* * Next try searching the domain components as realms. This * heuristic is also turned off by default. If DNS lookups for * KDCs are enabled (as they are by default), an attacker could * control which domain component is used as the realm for a host. */ if (realm == (char *)NULL) { int limit; errcode_t code; code = profile_get_integer(context->profile, KRB5_CONF_LIBDEFAULTS, KRB5_CONF_REALM_TRY_DOMAINS, 0, -1, &limit); if (code == 0) { retval = domain_heuristic(context, local_host, &realm, limit); if (retval) return retval; } } /* * The next fallback--and the first one to apply with default * configuration--is to use the upper-cased parent domain of the * hostname, regardless of whether we can actually look it up as a * realm. */ if (realm == (char *)NULL) { cp = strchr(local_host, '.'); if (cp) { if (!(realm = strdup(cp + 1))) return ENOMEM; for (cp = realm; *cp; cp++) if (islower((int) (*cp))) *cp = toupper((int) *cp); } } /* * The final fallback--used when the fully-qualified hostname has * only one component--is to use the local default realm. */ if (realm == (char *)NULL) { retval = krb5_get_default_realm(context, &realm); if (retval) return retval; } if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) { if (realm != (char *)NULL) free(realm); return ENOMEM; } retrealms[0] = realm; retrealms[1] = 0; *realmsp = retrealms; return 0; } /* * Common code for krb5_get_host_realm and krb5_get_fallback_host_realm * to do basic sanity checks on supplied hostname. */ krb5_error_code krb5int_clean_hostname(krb5_context context, const char *host, char *local_host, size_t lhsize) { char *cp; krb5_error_code retval; int l; local_host[0]=0; #ifdef DEBUG_REFERRALS printf("krb5int_clean_hostname called: host<%s>, local_host<%s>, size %d\n",host,local_host,lhsize); #endif if (host) { /* Filter out numeric addresses if the caller utterly failed to convert them to names. */ /* IPv4 - dotted quads only */ if (strspn(host, "01234567890.") == strlen(host)) { /* All numbers and dots... if it's three dots, it's an IP address, and we reject it. But "12345" could be a local hostname, couldn't it? We'll just assume that a name with three dots is not meant to be an all-numeric hostname three all-numeric domains down from the current domain. */ int ndots = 0; const char *p; for (p = host; *p; p++) if (*p == '.') ndots++; if (ndots == 3) return KRB5_ERR_NUMERIC_REALM; } if (strchr(host, ':')) /* IPv6 numeric address form? Bye bye. */ return KRB5_ERR_NUMERIC_REALM; /* Should probably error out if strlen(host) > MAXDNAME. */ strncpy(local_host, host, lhsize); local_host[lhsize - 1] = '\0'; } else { retval = krb5int_get_fq_local_hostname (local_host, lhsize); if (retval) return retval; } /* fold to lowercase */ for (cp = local_host; *cp; cp++) { if (isupper((unsigned char) (*cp))) *cp = tolower((unsigned char) *cp); } l = strlen(local_host); /* strip off trailing dot */ if (l && local_host[l-1] == '.') local_host[l-1] = 0; #ifdef DEBUG_REFERRALS printf("krb5int_clean_hostname ending: host<%s>, local_host<%s>, size %d\n",host,local_host,lhsize); #endif return 0; } /* * Walk through the components of a domain. At each stage determine * if a KDC can be located for that domain. Return a realm * corresponding to the upper-cased domain name for which a KDC was * found or NULL if no KDC was found. Stop searching after limit * labels have been removed from the domain (-1 means don't search at * all, 0 means try only the full domain itself, 1 means also try the * parent domain, etc.) or when we reach a parent with only one label. */ static krb5_error_code domain_heuristic(krb5_context context, const char *domain, char **realm, int limit) { krb5_error_code retval = 0, r; struct serverlist slist; krb5_data drealm; char *cp = NULL, *fqdn, *dot; *realm = NULL; if (limit < 0) return 0; memset(&drealm, 0, sizeof (drealm)); fqdn = strdup(domain); if (!fqdn) { retval = ENOMEM; goto cleanup; } /* Upper case the domain (for use as a realm) */ for (cp = fqdn; *cp; cp++) { if (islower((int)(*cp))) *cp = toupper((int)*cp); } /* Search up to limit parents, as long as we have multiple labels. */ cp = fqdn; while (limit-- >= 0 && (dot = strchr(cp, '.')) != NULL) { drealm.length = strlen(cp); drealm.data = cp; /* Find a kdc based on this part of the domain name. */ r = k5_locate_kdc(context, &drealm, &slist, FALSE, SOCK_DGRAM); if (!r) { /* Found a KDC! */ k5_free_serverlist(&slist); *realm = strdup(cp); if (!*realm) { retval = ENOMEM; goto cleanup; } break; } cp = dot + 1; } cleanup: free(fqdn); return retval; } /* * Frees the storage taken by a realm list returned by krb5_get_host_realm. */ krb5_error_code KRB5_CALLCONV krb5_free_host_realm(krb5_context context, char *const *realmlist) { /* same format, so why duplicate code? */ return krb5_free_krbhst(context, realmlist); }