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