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