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