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
91krb5_error_code
92krb5_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
152errout:
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
165krb5_error_code krb5int_translate_gai_error (int);
166
167static krb5_error_code
168krb5int_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.  */
189krb5_error_code
190krb5int_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
199krb5_error_code KRB5_CALLCONV
200krb5_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
291krb5_error_code
292krb5int_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
340krb5_error_code KRB5_CALLCONV
341krb5_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 */
419krb5_error_code KRB5_CALLCONV
420krb5int_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 */
485krb5_error_code
486krb5int_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 */
538krb5_error_code
539krb5int_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