xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/krb5/os/hst_realm.c (revision 257873cfc1dd3337766407f80397db60a56f2f5a)
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 2008 Sun Microsystems, Inc.  All rights reserved.
32  * Use is subject to license terms.
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 #ifdef KRB5_DNS_LOOKUP
86 
87 #include "dnsglue.h"
88 /*
89  * Try to look up a TXT record pointing to a Kerberos realm
90  */
91 
92 krb5_error_code
93 krb5_try_realm_txt_rr(const char *prefix, const char *name, char **realm)
94 {
95     krb5_error_code retval = KRB5_ERR_HOST_REALM_UNKNOWN;
96     const unsigned char *p, *base;
97     char host[MAXDNAME], *h;
98     int ret, rdlen, len;
99     struct krb5int_dns_state *ds = NULL;
100 
101     /*
102      * Form our query, and send it via DNS
103      */
104 
105     if (name == NULL || name[0] == '\0') {
106 	if (strlen (prefix) >= sizeof(host)-1)
107 	    return KRB5_ERR_HOST_REALM_UNKNOWN;
108         strcpy(host,prefix);
109     } else {
110         if ( strlen(prefix) + strlen(name) + 3 > MAXDNAME )
111             return KRB5_ERR_HOST_REALM_UNKNOWN;
112 	/*LINTED*/
113         sprintf(host,"%s.%s", prefix, name);
114 
115         /* Realm names don't (normally) end with ".", but if the query
116            doesn't end with "." and doesn't get an answer as is, the
117            resolv code will try appending the local domain.  Since the
118            realm names are absolutes, let's stop that.
119 
120            But only if a name has been specified.  If we are performing
121            a search on the prefix alone then the intention is to allow
122            the local domain or domain search lists to be expanded.
123         */
124 
125         h = host + strlen (host);
126         if ((h > host) && (h[-1] != '.') && ((h - host + 1) < sizeof(host)))
127             strcpy (h, ".");
128     }
129     ret = krb5int_dns_init(&ds, host, C_IN, T_TXT);
130     if (ret < 0)
131 	goto errout;
132 
133     ret = krb5int_dns_nextans(ds, &base, &rdlen);
134     if (ret < 0 || base == NULL)
135 	goto errout;
136 
137     p = base;
138     if (!INCR_OK(base, rdlen, p, 1))
139 	goto errout;
140     len = *p++;
141     *realm = malloc((size_t)len + 1);
142     if (*realm == NULL) {
143 	retval = ENOMEM;
144 	goto errout;
145     }
146     strncpy(*realm, (const char *)p, (size_t)len);
147     (*realm)[len] = '\0';
148     /* Avoid a common error. */
149     if ( (*realm)[len-1] == '.' )
150 	(*realm)[len-1] = '\0';
151     retval = 0;
152 
153 errout:
154     if (ds != NULL) {
155 	krb5int_dns_fini(ds);
156 	ds = NULL;
157     }
158     return retval;
159 }
160 #else /* KRB5_DNS_LOOKUP */
161 #ifndef MAXDNAME
162 #define MAXDNAME (16 * MAXHOSTNAMELEN)
163 #endif /* MAXDNAME */
164 #endif /* KRB5_DNS_LOOKUP */
165 
166 krb5_error_code krb5int_translate_gai_error (int);
167 
168 static krb5_error_code
169 krb5int_get_fq_hostname (char *buf, size_t bufsize, const char *name)
170 {
171     struct addrinfo *ai, hints;
172     int err;
173 
174     memset (&hints, 0, sizeof (hints));
175     hints.ai_flags = AI_CANONNAME;
176     err = getaddrinfo (name, 0, &hints, &ai);
177     if (err)
178 	return krb5int_translate_gai_error (err);
179     if (ai->ai_canonname == 0)
180 	return KRB5_EAI_FAIL;
181     strncpy (buf, ai->ai_canonname, bufsize);
182     buf[bufsize-1] = 0;
183     freeaddrinfo (ai);
184     return 0;
185 }
186 
187 /* Get the local host name, try to make it fully-qualified.
188    Always return a null-terminated string.
189    Might return an error if gethostname fails.  */
190 krb5_error_code
191 krb5int_get_fq_local_hostname (char *buf, size_t bufsiz)
192 {
193     buf[0] = 0;
194     if (gethostname (buf, bufsiz) == -1)
195 	return SOCKET_ERRNO;
196     buf[bufsiz - 1] = 0;
197     return krb5int_get_fq_hostname (buf, bufsiz, buf);
198 }
199 
200 krb5_error_code KRB5_CALLCONV
201 krb5_get_host_realm(krb5_context context, const char *host, char ***realmsp)
202 {
203     char **retrealms;
204     char *realm, *cp, *temp_realm;
205     krb5_error_code retval;
206     char local_host[MAXDNAME+1];
207 
208 #ifdef DEBUG_REFERRALS
209     printf("get_host_realm(host:%s) called\n",host);
210 #endif
211 
212     krb5int_clean_hostname(context, host, local_host, sizeof local_host);
213 
214     /*
215        Search for the best match for the host or domain.
216        Example: Given a host a.b.c.d, try to match on:
217          1) A.B.C.D
218 	 2) .B.C.D
219 	 3) B.C.D
220 	 4) .C.D
221 	 5) C.D
222 	 6) .D
223 	 7) D
224      */
225 
226     cp = local_host;
227 #ifdef DEBUG_REFERRALS
228     printf("  local_host: %s\n",local_host);
229 #endif
230     realm = (char *)NULL;
231     temp_realm = 0;
232     while (cp) {
233 #ifdef DEBUG_REFERRALS
234         printf("  trying to look up %s in the domain_realm map\n",cp);
235 #endif
236 	retval = profile_get_string(context->profile, "domain_realm", cp,
237 				    0, (char *)NULL, &temp_realm);
238 	if (retval)
239 	    return retval;
240 	if (temp_realm != (char *)NULL)
241 	    break;	/* Match found */
242 
243 	/* Setup for another test */
244 	if (*cp == '.') {
245 	    cp++;
246 	} else {
247 	    cp = strchr(cp, '.');
248 	}
249     }
250 #ifdef DEBUG_REFERRALS
251     printf("  done searching the domain_realm map\n");
252 #endif
253     if (temp_realm) {
254 #ifdef DEBUG_REFERRALS
255     printf("  temp_realm is %s\n",temp_realm);
256 #endif
257         realm = malloc(strlen(temp_realm) + 1);
258         if (!realm) {
259             profile_release_string(temp_realm);
260             return ENOMEM;
261         }
262         strcpy(realm, temp_realm);
263         profile_release_string(temp_realm);
264     }
265 
266     if (realm == (char *)NULL) {
267         if (!(cp = (char *)malloc(strlen(KRB5_REFERRAL_REALM)+1)))
268 	    return ENOMEM;
269 	strcpy(cp, KRB5_REFERRAL_REALM);
270 	realm = cp;
271     }
272 
273     if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) {
274 	if (realm != (char *)NULL)
275 	    free(realm);
276 	return ENOMEM;
277     }
278 
279     retrealms[0] = realm;
280     retrealms[1] = 0;
281 
282     *realmsp = retrealms;
283     return 0;
284 }
285 
286 #if defined(_WIN32) && !defined(__CYGWIN32__)
287 # ifndef EAFNOSUPPORT
288 #  define EAFNOSUPPORT WSAEAFNOSUPPORT
289 # endif
290 #endif
291 
292 krb5_error_code
293 krb5int_translate_gai_error (int num)
294 {
295     switch (num) {
296 #ifdef EAI_ADDRFAMILY
297     case EAI_ADDRFAMILY:
298 	return EAFNOSUPPORT;
299 #endif
300     case EAI_AGAIN:
301 	return EAGAIN;
302     case EAI_BADFLAGS:
303 	return EINVAL;
304     case EAI_FAIL:
305 	return KRB5_EAI_FAIL;
306     case EAI_FAMILY:
307 	return EAFNOSUPPORT;
308     case EAI_MEMORY:
309 	return ENOMEM;
310 #if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME
311     case EAI_NODATA:
312 	return KRB5_EAI_NODATA;
313 #endif
314     case EAI_NONAME:
315 	return KRB5_EAI_NONAME;
316 #if defined(EAI_OVERFLOW)
317     case EAI_OVERFLOW:
318 	return EINVAL;		/* XXX */
319 #endif
320     case EAI_SERVICE:
321 	return KRB5_EAI_SERVICE;
322     case EAI_SOCKTYPE:
323 	return EINVAL;
324 #ifdef EAI_SYSTEM
325     case EAI_SYSTEM:
326 	return errno;
327 #endif
328     }
329     /* Solaris Kerberos */
330     /* abort (); */
331     return -1;
332 }
333 
334 
335 /*
336  * Ganked from krb5_get_host_realm; handles determining a fallback realm
337  * to try in the case where referrals have failed and it's time to go
338  * look at TXT records or make a DNS-based assumption.
339  */
340 
341 krb5_error_code KRB5_CALLCONV
342 krb5_get_fallback_host_realm(krb5_context context, krb5_data *hdata, char ***realmsp)
343 {
344     char **retrealms;
345     char *realm = (char *)NULL, *cp;
346     krb5_error_code retval;
347     char local_host[MAXDNAME+1], host[MAXDNAME+1];
348 
349     /* Convert what we hope is a hostname to a string. */
350     memcpy(host, hdata->data, hdata->length);
351     host[hdata->length]=0;
352 
353 #ifdef DEBUG_REFERRALS
354     printf("get_fallback_host_realm(host >%s<) called\n",host);
355 #endif
356 
357     krb5int_clean_hostname(context, host, local_host, sizeof local_host);
358 
359 #ifdef DEBUG_REFERRALS
360     printf("  local_host: %s\n",local_host);
361 #endif
362 
363 #ifdef KRB5_DNS_LOOKUP
364     if (_krb5_use_dns_realm(context)) {
365         /*
366          * Since this didn't appear in our config file, try looking
367          * it up via DNS.  Look for a TXT records of the form:
368          *
369          * _kerberos.<hostname>
370          *
371          */
372         cp = local_host;
373         do {
374             retval = krb5_try_realm_txt_rr("_kerberos", cp, &realm);
375             cp = strchr(cp,'.');
376             if (cp)
377                 cp++;
378         } while (retval && cp && cp[0]);
379     } else
380 #endif /* KRB5_DNS_LOOKUP */
381     {
382         /*
383          * Solaris Kerberos:
384          * Fallback to looking for a realm based on the DNS domain
385          * of the host. Note: "local_host" here actually refers to the
386          * host and NOT necessarily the local hostnane.
387          */
388         (void) krb5int_fqdn_get_realm(context, local_host,
389                                     &realm);
390 #ifdef DEBUG_REFERRALS
391         printf("  done finding DNS-based default realm: >%s<\n",realm);
392 #endif
393     }
394 
395 
396     if (realm == (char *)NULL) {
397         /* We are defaulting to the local realm */
398         retval = krb5_get_default_realm(context, &realm);
399         if (retval) {
400              return retval;
401         }
402     }
403     if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) {
404 	if (realm != (char *)NULL)
405 	    free(realm);
406 	return ENOMEM;
407     }
408 
409     retrealms[0] = realm;
410     retrealms[1] = 0;
411 
412     *realmsp = retrealms;
413     return 0;
414 }
415 
416 /*
417  * Common code for krb5_get_host_realm and krb5_get_fallback_host_realm
418  * to do basic sanity checks on supplied hostname.
419  */
420 krb5_error_code KRB5_CALLCONV
421 krb5int_clean_hostname(krb5_context context, const char *host, char *local_host, size_t lhsize)
422 {
423     char *cp;
424     krb5_error_code retval;
425     int l;
426 
427     local_host[0]=0;
428 #ifdef DEBUG_REFERRALS
429     printf("krb5int_clean_hostname called: host<%s>, local_host<%s>, size %d\n",host,local_host,lhsize);
430 #endif
431     if (host) {
432 	/* Filter out numeric addresses if the caller utterly failed to
433 	   convert them to names.  */
434 	/* IPv4 - dotted quads only */
435 	if (strspn(host, "01234567890.") == strlen(host)) {
436 	    /* All numbers and dots... if it's three dots, it's an
437 	       IP address, and we reject it.  But "12345" could be
438 	       a local hostname, couldn't it?  We'll just assume
439 	       that a name with three dots is not meant to be an
440 	       all-numeric hostname three all-numeric domains down
441 	       from the current domain.  */
442 	    int ndots = 0;
443 	    const char *p;
444 	    for (p = host; *p; p++)
445 		if (*p == '.')
446 		    ndots++;
447 	    if (ndots == 3)
448 		return KRB5_ERR_NUMERIC_REALM;
449 	}
450 	if (strchr(host, ':'))
451 	    /* IPv6 numeric address form?  Bye bye.  */
452 	    return KRB5_ERR_NUMERIC_REALM;
453 
454 	/* Should probably error out if strlen(host) > MAXDNAME.  */
455 	strncpy(local_host, host, lhsize);
456 	local_host[lhsize - 1] = '\0';
457     } else {
458         retval = krb5int_get_fq_local_hostname (local_host, lhsize);
459 	if (retval)
460 	    return retval;
461     }
462 
463     /* fold to lowercase */
464     for (cp = local_host; *cp; cp++) {
465 	if (isupper((unsigned char) (*cp)))
466 	    *cp = tolower((unsigned char) *cp);
467     }
468     l = strlen(local_host);
469     /* strip off trailing dot */
470     if (l && local_host[l-1] == '.')
471 	    local_host[l-1] = 0;
472 
473 #ifdef DEBUG_REFERRALS
474     printf("krb5int_clean_hostname ending: host<%s>, local_host<%s>, size %d\n",host,local_host,lhsize);
475 #endif
476     return 0;
477 }
478 
479 /*
480  * Solaris Kerberos:
481  * Walk through the components of a domain. At each
482  * stage determine if a KDC can be located for that domain.
483  * Return a realm corresponding to the upper-cased domain name
484  * for which a KDC was found or NULL if no KDC was found.
485  */
486 krb5_error_code
487 krb5int_domain_get_realm(krb5_context context, const char *domain, char **realm) {
488     krb5_error_code retval;
489     struct addrlist addrlist;
490     krb5_data drealm;
491     char *cp = NULL;
492     char *fqdn = NULL;
493 
494     *realm = NULL;
495     memset(&drealm, 0, sizeof (drealm));
496 
497     if (!(fqdn = malloc(strlen(domain) + 1))) {
498         return (ENOMEM);
499     }
500     strlcpy(fqdn, domain, strlen(domain) + 1);
501 
502     /* Upper case the domain (for use as a realm) */
503     for (cp = fqdn; *cp; cp++)
504         if (islower((int)(*cp)))
505             *cp = toupper((int)*cp);
506 
507     cp = fqdn;
508     while (strchr(cp, '.') != NULL) {
509 
510         drealm.length = strlen(cp);
511         drealm.data = cp;
512 
513         /* Find a kdc based on this part of the domain name */
514         retval = krb5_locate_kdc(context, &drealm, &addrlist, 0, SOCK_DGRAM, 0);
515         krb5int_free_addrlist(&addrlist);
516 
517         if (!retval) { /* Found a KDC! */
518             if (!(*realm = malloc(strlen(cp) + 1))) {
519                 free(fqdn);
520                 return (ENOMEM);
521             }
522             strlcpy(*realm, cp, strlen(cp) + 1);
523             break;
524         }
525 
526         cp = strchr(cp, '.');
527         cp++;
528     }
529     free(fqdn);
530     return (0);
531 }
532 
533 /*
534  * Solaris Kerberos:
535  * Discards the first component of the fqdn and calls
536  * krb5int_domain_get_realm() with the remaining string (domain).
537  *
538  */
539 krb5_error_code
540 krb5int_fqdn_get_realm(krb5_context context, const char *fqdn, char **realm) {
541     char *domain = strchr(fqdn, '.');
542 
543     if (domain) {
544         domain++;
545         return (krb5int_domain_get_realm(context, domain, realm));
546     } else {
547         return (-1);
548     }
549 }
550 
551