Convert DEBUG_REFERRALS to TRACE_* framework
[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     TRACE_GET_HOST_REALM(context, host);
144
145     retval = krb5int_clean_hostname(context, host, local_host, sizeof local_host);
146     if (retval)
147         return retval;
148
149     /*
150       Search for the best match for the host or domain.
151       Example: Given a host a.b.c.d, try to match on:
152       1) A.B.C.D
153       2) .B.C.D
154       3) B.C.D
155       4) .C.D
156       5) C.D
157       6) .D
158       7) D
159     */
160
161     cp = local_host;
162     TRACE_GET_HOST_REALM_LOCALHOST(context, local_host);
163     realm = (char *)NULL;
164     temp_realm = 0;
165     while (cp) {
166         TRACE_GET_HOST_REALM_DOMAIN_REALM_MAP(context, cp);
167         retval = profile_get_string(context->profile, KRB5_CONF_DOMAIN_REALM, cp,
168                                     0, (char *)NULL, &temp_realm);
169         if (retval)
170             return retval;
171         if (temp_realm != (char *)NULL)
172             break;      /* Match found */
173
174         /* Setup for another test */
175         if (*cp == '.') {
176             cp++;
177         } else {
178             cp = strchr(cp, '.');
179         }
180     }
181     if (temp_realm) {
182         TRACE_GET_HOST_REALM_TEMP_REALM(context, temp_realm);
183         realm = strdup(temp_realm);
184         if (!realm) {
185             profile_release_string(temp_realm);
186             return ENOMEM;
187         }
188         profile_release_string(temp_realm);
189     }
190
191     if (realm == (char *)NULL) {
192         if (!(cp = strdup(KRB5_REFERRAL_REALM)))
193             return ENOMEM;
194         realm = cp;
195     }
196
197     if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) {
198         if (realm != (char *)NULL)
199             free(realm);
200         return ENOMEM;
201     }
202
203     retrealms[0] = realm;
204     retrealms[1] = 0;
205
206     TRACE_GET_HOST_REALM_RETURN(context, host, realm);
207     *realmsp = retrealms;
208     return 0;
209 }
210
211 #if defined(_WIN32) && !defined(__CYGWIN32__)
212 # ifndef EAFNOSUPPORT
213 #  define EAFNOSUPPORT WSAEAFNOSUPPORT
214 # endif
215 #endif
216
217 krb5_error_code
218 krb5int_translate_gai_error (int num)
219 {
220     switch (num) {
221 #ifdef EAI_ADDRFAMILY
222     case EAI_ADDRFAMILY:
223         return EAFNOSUPPORT;
224 #endif
225     case EAI_AGAIN:
226         return EAGAIN;
227     case EAI_BADFLAGS:
228         return EINVAL;
229     case EAI_FAIL:
230         return KRB5_EAI_FAIL;
231     case EAI_FAMILY:
232         return EAFNOSUPPORT;
233     case EAI_MEMORY:
234         return ENOMEM;
235 #if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME
236     case EAI_NODATA:
237         return KRB5_EAI_NODATA;
238 #endif
239     case EAI_NONAME:
240         return KRB5_EAI_NONAME;
241 #if defined(EAI_OVERFLOW)
242     case EAI_OVERFLOW:
243         return EINVAL;          /* XXX */
244 #endif
245     case EAI_SERVICE:
246         return KRB5_EAI_SERVICE;
247     case EAI_SOCKTYPE:
248         return EINVAL;
249 #ifdef EAI_SYSTEM
250     case EAI_SYSTEM:
251         return errno;
252 #endif
253     }
254     abort ();
255     return -1;
256 }
257
258
259 /*
260  * Ganked from krb5_get_host_realm; handles determining a fallback realm
261  * to try in the case where referrals have failed and it's time to go
262  * look at TXT records or make a DNS-based assumption.
263  */
264
265 krb5_error_code KRB5_CALLCONV
266 krb5_get_fallback_host_realm(krb5_context context,
267                              krb5_data *hdata, char ***realmsp)
268 {
269     char **retrealms;
270     char *realm, *cp;
271     krb5_error_code retval;
272     char local_host[MAXDNAME+1], host[MAXDNAME+1];
273
274     /* Convert what we hope is a hostname to a string. */
275     memcpy(host, hdata->data, hdata->length);
276     host[hdata->length]=0;
277
278     TRACE_GET_FALLBACK_HOST_REALM(context, host);
279
280     retval = krb5int_clean_hostname(context, host, local_host, sizeof local_host);
281     if (retval)
282         return retval;
283
284     /*
285      * Try looking up a _kerberos.<hostname> TXT record in DNS.  This
286      * heuristic is turned off by default since, in the absence of
287      * secure DNS, it can allow an attacker to control the realm used
288      * for a host.
289      */
290     realm = (char *)NULL;
291 #ifdef KRB5_DNS_LOOKUP
292     if (_krb5_use_dns_realm(context)) {
293         cp = local_host;
294         do {
295             retval = krb5_try_realm_txt_rr("_kerberos", cp, &realm);
296             cp = strchr(cp,'.');
297             if (cp)
298                 cp++;
299         } while (retval && cp && cp[0]);
300     }
301 #endif /* KRB5_DNS_LOOKUP */
302
303     /*
304      * Next try searching the domain components as realms.  This
305      * heuristic is also turned off by default.  If DNS lookups for
306      * KDCs are enabled (as they are by default), an attacker could
307      * control which domain component is used as the realm for a host.
308      */
309     if (realm == (char *)NULL) {
310         int limit;
311         errcode_t code;
312
313         code = profile_get_integer(context->profile, KRB5_CONF_LIBDEFAULTS,
314                                    KRB5_CONF_REALM_TRY_DOMAINS, 0, -1, &limit);
315         if (code == 0) {
316             retval = domain_heuristic(context, local_host, &realm, limit);
317             if (retval)
318                 return retval;
319         }
320     }
321
322     /*
323      * The next fallback--and the first one to apply with default
324      * configuration--is to use the upper-cased parent domain of the
325      * hostname, regardless of whether we can actually look it up as a
326      * realm.
327      */
328     if (realm == (char *)NULL) {
329         cp = strchr(local_host, '.');
330         if (cp) {
331             if (!(realm = strdup(cp + 1)))
332                 return ENOMEM;
333             for (cp = realm; *cp; cp++)
334                 if (islower((int) (*cp)))
335                     *cp = toupper((int) *cp);
336         }
337     }
338
339     /*
340      * The final fallback--used when the fully-qualified hostname has
341      * only one component--is to use the local default realm.
342      */
343     if (realm == (char *)NULL) {
344         retval = krb5_get_default_realm(context, &realm);
345         if (retval)
346             return retval;
347     }
348
349     if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) {
350         if (realm != (char *)NULL)
351             free(realm);
352         return ENOMEM;
353     }
354
355     retrealms[0] = realm;
356     retrealms[1] = 0;
357
358     TRACE_GET_FALLBACK_HOST_REALM_RETURN(context, host, realm);
359     *realmsp = retrealms;
360     return 0;
361 }
362
363 /*
364  * Common code for krb5_get_host_realm and krb5_get_fallback_host_realm
365  * to do basic sanity checks on supplied hostname.
366  */
367 krb5_error_code
368 krb5int_clean_hostname(krb5_context context,
369                        const char *host, char *local_host, size_t lhsize)
370 {
371     char *cp;
372     krb5_error_code retval;
373     int l;
374
375     local_host[0]=0;
376     TRACE_CLEAN_HOSTNAME(context, host, lhsize, local_host);
377     if (host) {
378         /* Filter out numeric addresses if the caller utterly failed to
379            convert them to names.  */
380         /* IPv4 - dotted quads only */
381         if (strspn(host, "01234567890.") == strlen(host)) {
382             /* All numbers and dots... if it's three dots, it's an
383                IP address, and we reject it.  But "12345" could be
384                a local hostname, couldn't it?  We'll just assume
385                that a name with three dots is not meant to be an
386                all-numeric hostname three all-numeric domains down
387                from the current domain.  */
388             int ndots = 0;
389             const char *p;
390             for (p = host; *p; p++)
391                 if (*p == '.')
392                     ndots++;
393             if (ndots == 3)
394                 return KRB5_ERR_NUMERIC_REALM;
395         }
396         if (strchr(host, ':'))
397             /* IPv6 numeric address form?  Bye bye.  */
398             return KRB5_ERR_NUMERIC_REALM;
399
400         /* Should probably error out if strlen(host) > MAXDNAME.  */
401         strncpy(local_host, host, lhsize);
402         local_host[lhsize - 1] = '\0';
403     } else {
404         retval = krb5int_get_fq_local_hostname (local_host, lhsize);
405         if (retval)
406             return retval;
407     }
408
409     /* fold to lowercase */
410     for (cp = local_host; *cp; cp++) {
411         if (isupper((unsigned char) (*cp)))
412             *cp = tolower((unsigned char) *cp);
413     }
414     l = strlen(local_host);
415     /* strip off trailing dot */
416     if (l && local_host[l-1] == '.')
417         local_host[l-1] = 0;
418
419     TRACE_CLEAN_HOSTNAME_RETURN(context, host, lhsize, local_host);
420     return 0;
421 }
422
423 /*
424  * Walk through the components of a domain.  At each stage determine
425  * if a KDC can be located for that domain.  Return a realm
426  * corresponding to the upper-cased domain name for which a KDC was
427  * found or NULL if no KDC was found.  Stop searching after limit
428  * labels have been removed from the domain (-1 means don't search at
429  * all, 0 means try only the full domain itself, 1 means also try the
430  * parent domain, etc.) or when we reach a parent with only one label.
431  */
432 static krb5_error_code
433 domain_heuristic(krb5_context context, const char *domain,
434                  char **realm, int limit)
435 {
436     krb5_error_code retval = 0, r;
437     struct serverlist slist;
438     krb5_data drealm;
439     char *cp = NULL, *fqdn, *dot;
440
441     *realm = NULL;
442     if (limit < 0)
443         return 0;
444
445     memset(&drealm, 0, sizeof (drealm));
446     fqdn = strdup(domain);
447     if (!fqdn) {
448         retval = ENOMEM;
449         goto cleanup;
450     }
451
452     /* Upper case the domain (for use as a realm) */
453     for (cp = fqdn; *cp; cp++) {
454         if (islower((int)(*cp)))
455             *cp = toupper((int)*cp);
456     }
457
458     /* Search up to limit parents, as long as we have multiple labels. */
459     cp = fqdn;
460     while (limit-- >= 0 && (dot = strchr(cp, '.')) != NULL) {
461
462         drealm.length = strlen(cp);
463         drealm.data = cp;
464
465         /* Find a kdc based on this part of the domain name. */
466         r = k5_locate_kdc(context, &drealm, &slist, FALSE, SOCK_DGRAM);
467         if (!r) { /* Found a KDC! */
468             k5_free_serverlist(&slist);
469             *realm = strdup(cp);
470             if (!*realm) {
471                 retval = ENOMEM;
472                 goto cleanup;
473             }
474             break;
475         }
476
477         cp = dot + 1;
478     }
479
480 cleanup:
481     free(fqdn);
482     return retval;
483 }
484
485 /*
486  * Frees the storage taken by a realm list returned by krb5_get_host_realm.
487  */
488
489 krb5_error_code KRB5_CALLCONV
490 krb5_free_host_realm(krb5_context context, char *const *realmlist)
491 {
492     /* same format, so why duplicate code? */
493     return krb5_free_krbhst(context, realmlist);
494 }