Home | History | Annotate | Line # | Download | only in krb5
      1 /*	$NetBSD: get_host_realm.c,v 1.2 2017/01/28 21:31:49 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1997 - 2005 Kungliga Tekniska Hgskolan
      5  * (Royal Institute of Technology, Stockholm, Sweden).
      6  * All rights reserved.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  *
     12  * 1. Redistributions of source code must retain the above copyright
     13  *    notice, this list of conditions and the following disclaimer.
     14  *
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * 3. Neither the name of the Institute nor the names of its contributors
     20  *    may be used to endorse or promote products derived from this software
     21  *    without specific prior written permission.
     22  *
     23  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
     24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
     27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     33  * SUCH DAMAGE.
     34  */
     35 
     36 #include "krb5_locl.h"
     37 #include <krb5/resolve.h>
     38 
     39 /* To automagically find the correct realm of a host (without
     40  * [domain_realm] in krb5.conf) add a text record for your domain with
     41  * the name of your realm, like this:
     42  *
     43  * _kerberos	IN	TXT	"FOO.SE"
     44  *
     45  * The search is recursive, so you can add entries for specific
     46  * hosts. To find the realm of host a.b.c, it first tries
     47  * _kerberos.a.b.c, then _kerberos.b.c and so on.
     48  *
     49  * This method is described in draft-ietf-cat-krb-dns-locate-03.txt.
     50  *
     51  */
     52 
     53 static int
     54 copy_txt_to_realms(krb5_context context,
     55 		   const char *domain,
     56 		   struct rk_resource_record *head,
     57 		   krb5_realm **realms)
     58 {
     59     struct rk_resource_record *rr;
     60     unsigned int n, i;
     61 
     62     for(n = 0, rr = head; rr; rr = rr->next)
     63 	if (rr->type == rk_ns_t_txt)
     64 	    ++n;
     65 
     66     if (n == 0)
     67 	return -1;
     68 
     69     *realms = malloc ((n + 1) * sizeof(krb5_realm));
     70     if (*realms == NULL)
     71 	return krb5_enomem(context);;
     72 
     73     for (i = 0; i < n + 1; ++i)
     74 	(*realms)[i] = NULL;
     75 
     76     for (i = 0, rr = head; rr; rr = rr->next) {
     77 	if (rr->type == rk_ns_t_txt) {
     78 	    char *tmp = NULL;
     79 	    int invalid_tld = 1;
     80 
     81 	    /* Check for a gTLD controlled interruption */
     82 	    if (strcmp("Your DNS configuration needs immediate "
     83 			"attention see https://icann.org/namecollision",
     84 			rr->u.txt) != 0) {
     85 		invalid_tld = 0;
     86 		tmp = strdup(rr->u.txt);
     87 	    }
     88 	    if (tmp == NULL) {
     89 		for (i = 0; i < n; ++i)
     90 		    free ((*realms)[i]);
     91 		free (*realms);
     92 		if (invalid_tld) {
     93 		    krb5_warnx(context,
     94 			       "Realm lookup failed: "
     95 			       "Domain '%s' needs immediate attention "
     96 			       "see https://icann.org/namecollision",
     97 				domain);
     98 		    return KRB5_KDC_UNREACH;
     99 		}
    100 		return krb5_enomem(context);;
    101 	    }
    102 	    (*realms)[i] = tmp;
    103 	    ++i;
    104 	}
    105     }
    106     return 0;
    107 }
    108 
    109 static int
    110 dns_find_realm(krb5_context context,
    111 	       const char *domain,
    112 	       krb5_realm **realms)
    113 {
    114     static const char *default_labels[] = { "_kerberos", NULL };
    115     char dom[MAXHOSTNAMELEN];
    116     struct rk_dns_reply *r;
    117     const char **labels;
    118     char **config_labels;
    119     int i, ret = 0;
    120 
    121     config_labels = krb5_config_get_strings(context, NULL, "libdefaults",
    122 					    "dns_lookup_realm_labels", NULL);
    123     if(config_labels != NULL)
    124 	labels = (const char **)config_labels;
    125     else
    126 	labels = default_labels;
    127     if(*domain == '.')
    128 	domain++;
    129     for (i = 0; labels[i] != NULL; i++) {
    130 	ret = snprintf(dom, sizeof(dom), "%s.%s.", labels[i], domain);
    131 	if(ret < 0 || (size_t)ret >= sizeof(dom)) {
    132 	    ret = krb5_enomem(context);
    133 	    goto out;
    134 	}
    135     	r = rk_dns_lookup(dom, "TXT");
    136     	if(r != NULL) {
    137 	    ret = copy_txt_to_realms(context, domain, r->head, realms);
    138 	    rk_dns_free_data(r);
    139 	    if(ret == 0)
    140 		goto out;
    141 	}
    142     }
    143     krb5_set_error_message(context, KRB5_KDC_UNREACH,
    144 			    "Realm lookup failed: "
    145 			    "No DNS TXT record for %s",
    146 			    domain);
    147     ret = KRB5_KDC_UNREACH;
    148 out:
    149     if (config_labels)
    150 	krb5_config_free_strings(config_labels);
    151     return ret;
    152 }
    153 
    154 /*
    155  * Try to figure out what realms host in `domain' belong to from the
    156  * configuration file.
    157  */
    158 
    159 static int
    160 config_find_realm(krb5_context context,
    161 		  const char *domain,
    162 		  krb5_realm **realms)
    163 {
    164     char **tmp = krb5_config_get_strings (context, NULL,
    165 					  "domain_realm",
    166 					  domain,
    167 					  NULL);
    168 
    169     if (tmp == NULL)
    170 	return -1;
    171     *realms = tmp;
    172     return 0;
    173 }
    174 
    175 /*
    176  * This function assumes that `host' is a FQDN (and doesn't handle the
    177  * special case of host == NULL either).
    178  * Try to find mapping in the config file or DNS and it that fails,
    179  * fall back to guessing
    180  */
    181 
    182 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    183 _krb5_get_host_realm_int(krb5_context context,
    184                          const char *host,
    185                          krb5_boolean use_dns,
    186                          krb5_realm **realms)
    187 {
    188     const char *p, *q;
    189     const char *port;
    190     krb5_boolean dns_locate_enable;
    191     krb5_error_code ret = 0;
    192 
    193     /* Strip off any trailing ":port" suffix. */
    194     port = strchr(host, ':');
    195     if (port != NULL) {
    196         host = strndup(host, port - host);
    197         if (host == NULL)
    198             return krb5_enomem(context);
    199     }
    200 
    201     dns_locate_enable = krb5_config_get_bool_default(context, NULL, TRUE,
    202         "libdefaults", "dns_lookup_realm", NULL);
    203     for (p = host; p != NULL; p = strchr (p + 1, '.')) {
    204         if (config_find_realm(context, p, realms) == 0) {
    205             if (strcasecmp(*realms[0], "dns_locate") != 0)
    206                 break;
    207 	    krb5_free_host_realm(context, *realms);
    208 	    *realms = NULL;
    209             if (!use_dns)
    210                 continue;
    211             for (q = host; q != NULL; q = strchr(q + 1, '.'))
    212                 if (dns_find_realm(context, q, realms) == 0)
    213                     break;
    214             if (q)
    215                 break;
    216         } else if (use_dns && dns_locate_enable) {
    217             if (dns_find_realm(context, p, realms) == 0)
    218                 break;
    219         }
    220     }
    221 
    222     /*
    223      * If 'p' is NULL, we did not find an explicit realm mapping in either the
    224      * configuration file or DNS.  Try the hostname suffix as a last resort.
    225      *
    226      * XXX: If we implement a KDC-specific variant of this function just for
    227      * referrals, we could check whether we have a cross-realm TGT for the
    228      * realm in question, and if not try the parent (loop again).
    229      */
    230     if (p == NULL) {
    231         p = strchr(host, '.');
    232         if (p != NULL) {
    233             p++;
    234             *realms = malloc(2 * sizeof(krb5_realm));
    235             if (*realms != NULL &&
    236                 ((*realms)[0] = strdup(p)) != NULL) {
    237                 strupr((*realms)[0]);
    238                 (*realms)[1] = NULL;
    239             } else {
    240                 free(*realms);
    241                 ret = krb5_enomem(context);
    242             }
    243         } else {
    244             krb5_set_error_message(context, KRB5_ERR_HOST_REALM_UNKNOWN,
    245                                    N_("unable to find realm of host %s", ""),
    246                                    host);
    247             ret = KRB5_ERR_HOST_REALM_UNKNOWN;
    248         }
    249     }
    250 
    251     /* If 'port' is not NULL, we have a copy of 'host' to free. */
    252     if (port)
    253         free((void *)host);
    254     return ret;
    255 }
    256 
    257 /*
    258  * Return the realm(s) of `host' as a NULL-terminated list in
    259  * `realms'. Free `realms' with krb5_free_host_realm().
    260  */
    261 
    262 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    263 krb5_get_host_realm(krb5_context context,
    264 		    const char *targethost,
    265 		    krb5_realm **realms)
    266 {
    267     const char *host = targethost;
    268     char hostname[MAXHOSTNAMELEN];
    269     krb5_error_code ret;
    270     int use_dns;
    271 
    272     if (host == NULL) {
    273 	if (gethostname (hostname, sizeof(hostname))) {
    274 	    *realms = NULL;
    275 	    return errno;
    276 	}
    277 	host = hostname;
    278     }
    279 
    280     /*
    281      * If our local hostname is without components, don't even try to dns.
    282      */
    283 
    284     use_dns = (strchr(host, '.') != NULL);
    285 
    286     ret = _krb5_get_host_realm_int (context, host, use_dns, realms);
    287     if (ret && targethost != NULL) {
    288 	/*
    289 	 * If there was no realm mapping for the host (and we wasn't
    290 	 * looking for ourself), guess at the local realm, maybe our
    291 	 * KDC knows better then we do and we get a referral back.
    292 	 */
    293 	ret = krb5_get_default_realms(context, realms);
    294 	if (ret) {
    295 	    krb5_set_error_message(context, KRB5_ERR_HOST_REALM_UNKNOWN,
    296 				   N_("Unable to find realm of host %s", ""),
    297 				   host);
    298 	    return KRB5_ERR_HOST_REALM_UNKNOWN;
    299 	}
    300     }
    301     return ret;
    302 }
    303