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