xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/krb5/os/hst_realm.c (revision 4e5b757fbcf21077677360be274461dcd9064106)
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 /*
34  Figures out the Kerberos realm names for host, filling in a
35  pointer to an argv[] style list of names, terminated with a null pointer.
36 
37  If host is NULL, the local host's realms are determined.
38 
39  If there are no known realms for the host, the filled-in pointer is set
40  to NULL.
41 
42  The pointer array and strings pointed to are all in allocated storage,
43  and should be freed by the caller when finished.
44 
45  returns system errors
46 */
47 
48 /*
49  * Implementation notes:
50  *
51  * this implementation only provides one realm per host, using the same
52  * mapping file used in kerberos v4.
53 
54  * Given a fully-qualified domain-style primary host name,
55  * return the name of the Kerberos realm for the host.
56  * If the hostname contains no discernable domain, or an error occurs,
57  * return the local realm name, as supplied by krb5_get_default_realm().
58  * If the hostname contains a domain, but no translation is found,
59  * the hostname's domain is converted to upper-case and returned.
60  *
61  * The format of each line of the translation file is:
62  * domain_name kerberos_realm
63  * -or-
64  * host_name kerberos_realm
65  *
66  * domain_name should be of the form .XXX.YYY (e.g. .LCS.MIT.EDU)
67  * host names should be in the usual form (e.g. FOO.BAR.BAZ)
68  */
69 
70 #define NEED_SOCKETS
71 #include "k5-int.h"
72 #include "os-proto.h"
73 #include <ctype.h>
74 #include <stdio.h>
75 #ifdef HAVE_STRING_H
76 #include <string.h>
77 #else
78 #include <strings.h>
79 #endif
80 
81 #ifdef KRB5_DNS_LOOKUP
82 #ifdef WSHELPER
83 #include <wshelper.h>
84 #else /* WSHELPER */
85 #include <arpa/inet.h>
86 #include <arpa/nameser.h>
87 #ifndef T_TXT /* not defined on SunOS 4 */
88 #  define T_TXT 15
89 #endif
90 #include <resolv.h>
91 #include <netdb.h>
92 #endif /* WSHELPER */
93 #endif /* KRB5_DNS_LOOKUP */
94 
95 #include <fake-addrinfo.h>
96 
97 #ifdef KRB5_DNS_LOOKUP
98 
99 #include "dnsglue.h"
100 
101 /*
102  * Try to look up a TXT record pointing to a Kerberos realm
103  */
104 
105 krb5_error_code
106 krb5_try_realm_txt_rr(const char *prefix, const char *name, char **realm)
107 {
108     krb5_error_code retval = KRB5_ERR_HOST_REALM_UNKNOWN;
109     const unsigned char *p, *base;
110     char host[MAXDNAME], *h;
111     int ret, rdlen, len;
112     struct krb5int_dns_state *ds = NULL;
113 
114     /*
115      * Form our query, and send it via DNS
116      */
117 
118     if (name == NULL || name[0] == '\0') {
119 	if (strlen (prefix) >= sizeof(host)-1)
120 	    return KRB5_ERR_HOST_REALM_UNKNOWN;
121         strcpy(host,prefix);
122     } else {
123         if ( strlen(prefix) + strlen(name) + 3 > MAXDNAME )
124             return KRB5_ERR_HOST_REALM_UNKNOWN;
125 	/*LINTED*/
126         sprintf(host,"%s.%s", prefix, name);
127 
128         /* Realm names don't (normally) end with ".", but if the query
129            doesn't end with "." and doesn't get an answer as is, the
130            resolv code will try appending the local domain.  Since the
131            realm names are absolutes, let's stop that.
132 
133            But only if a name has been specified.  If we are performing
134            a search on the prefix alone then the intention is to allow
135            the local domain or domain search lists to be expanded.
136         */
137 
138         h = host + strlen (host);
139         if ((h > host) && (h[-1] != '.') && ((h - host + 1) < sizeof(host)))
140             strcpy (h, ".");
141     }
142     ret = krb5int_dns_init(&ds, host, C_IN, T_TXT);
143     if (ret < 0)
144       goto errout;
145 
146     ret = krb5int_dns_nextans(ds, &base, &rdlen);
147     if (ret < 0 || base == NULL)
148       goto errout;
149 
150     p = base;
151     if (!INCR_OK(base, rdlen, p, 1))
152       goto errout;
153 
154     len = *p++;
155     *realm = malloc((size_t)len + 1);
156     if (*realm == NULL) {
157       retval = ENOMEM;
158       goto errout;
159     }
160     strncpy(*realm, (const char *)p, (size_t)len);
161     (*realm)[len] = '\0';
162     /* Avoid a common error. */
163     if ( (*realm)[len-1] == '.' )
164       (*realm)[len-1] = '\0';
165     retval = 0;
166 
167 errout:
168     if (ds != NULL) {
169       krb5int_dns_fini(ds);
170       ds = NULL;
171     }
172     return retval;
173 }
174 #else /* KRB5_DNS_LOOKUP */
175 #ifndef MAXDNAME
176 #define MAXDNAME (16 * MAXHOSTNAMELEN)
177 #endif /* MAXDNAME */
178 #endif /* KRB5_DNS_LOOKUP */
179 
180 
181 krb5_error_code krb5int_translate_gai_error (int);
182 
183 static krb5_error_code
184 krb5int_get_fq_hostname (char *buf, size_t bufsize, const char *name)
185 {
186     struct addrinfo *ai, hints;
187     int err;
188 
189     memset (&hints, 0, sizeof (hints));
190     hints.ai_family = AF_UNSPEC;
191     hints.ai_flags = AI_CANONNAME;
192     err = getaddrinfo (name, 0, &hints, &ai);
193     if (err)
194 	return krb5int_translate_gai_error (err);
195     if (ai->ai_canonname == 0)
196 	return KRB5_EAI_FAIL;
197     strncpy (buf, ai->ai_canonname, bufsize);
198     buf[bufsize-1] = 0;
199     freeaddrinfo (ai);
200     return 0;
201 }
202 
203 /* Get the local host name, try to make it fully-qualified.
204    Always return a null-terminated string.
205    Might return an error if gethostname fails.  */
206 krb5_error_code
207 krb5int_get_fq_local_hostname (char *buf, size_t bufsiz)
208 {
209     buf[0] = 0;
210     if (gethostname (buf, bufsiz) == -1)
211 	return SOCKET_ERRNO;
212     buf[bufsiz - 1] = 0;
213     return krb5int_get_fq_hostname (buf, bufsiz, buf);
214 }
215 
216 krb5_error_code KRB5_CALLCONV
217 krb5_get_host_realm(krb5_context context, const char *host, char ***realmsp)
218 {
219     char **retrealms;
220     char *default_realm, *realm, *cp, *temp_realm;
221     krb5_error_code retval;
222     int l;
223     char local_host[MAXDNAME+1];
224 
225     if (host) {
226 	/* Filter out numeric addresses if the caller utterly failed to
227 	   convert them to names.  */
228 	/* IPv4 - dotted quads only */
229 	if (strspn(host, "01234567890.") == strlen(host)) {
230 	    /* All numbers and dots... if it's three dots, it's an
231 	       IP address, and we reject it.  But "12345" could be
232 	       a local hostname, couldn't it?  We'll just assume
233 	       that a name with three dots is not meant to be an
234 	       all-numeric hostname three all-numeric domains down
235 	       from the current domain.  */
236 	    int ndots = 0;
237 	    const char *p;
238 	    for (p = host; *p; p++)
239 		if (*p == '.')
240 		    ndots++;
241 	    if (ndots == 3)
242 		return KRB5_ERR_NUMERIC_REALM;
243 	}
244 	if (strchr(host, ':'))
245 	    /* IPv6 numeric address form?  Bye bye.  */
246 	    return KRB5_ERR_NUMERIC_REALM;
247 
248 	/* Should probably error out if strlen(host) > MAX_DNS_NAMELEN.  */
249 	strncpy(local_host, host, sizeof(local_host));
250 	local_host[sizeof(local_host) - 1] = '\0';
251     } else {
252 	retval = krb5int_get_fq_local_hostname (local_host,
253 						sizeof (local_host));
254 	if (retval)
255 	    return retval;
256     }
257 
258     for (cp = local_host; *cp; cp++) {
259 	if (isupper((int) (*cp)))
260 	    *cp = tolower((int) *cp);
261     }
262     l = strlen(local_host);
263     /* strip off trailing dot */
264     if (l && local_host[l-1] == '.')
265 	    local_host[l-1] = 0;
266 
267     /*
268        Search for the best match for the host or domain.
269        Example: Given a host a.b.c.d, try to match on:
270          1) A.B.C.D
271 	 2) .B.C.D
272 	 3) B.C.D
273 	 4) .C.D
274 	 5) C.D
275 	 6) .D
276 	 7) D
277      */
278 
279     cp = local_host;
280     realm = default_realm = (char *)NULL;
281     temp_realm = 0;
282     while (cp) {
283 	retval = profile_get_string(context->profile, "domain_realm", cp,
284 				    0, (char *)NULL, &temp_realm);
285 	if (retval)
286 	    return retval;
287 	if (temp_realm != (char *)NULL)
288 	    break;	/* Match found */
289 
290 	/* Setup for another test */
291 	if (*cp == '.') {
292 	    cp++;
293 	    if (default_realm == (char *)NULL) {
294 		/* If nothing else works, use the host's domain */
295 		default_realm = cp;
296 	    }
297 	} else {
298 	    cp = strchr(cp, '.');
299 	}
300     }
301     if (temp_realm) {
302         realm = malloc(strlen(temp_realm) + 1);
303         if (!realm) {
304             profile_release_string(temp_realm);
305             return ENOMEM;
306         }
307         strcpy(realm, temp_realm);
308         profile_release_string(temp_realm);
309     }
310 
311 #ifdef KRB5_DNS_LOOKUP
312     if (realm == (char *)NULL) {
313         int use_dns = _krb5_use_dns_realm(context);
314         if ( use_dns ) {
315             /*
316              * Since this didn't appear in our config file, try looking
317              * it up via DNS.  Look for a TXT records of the form:
318              *
319              * _kerberos.<hostname>
320              *
321              */
322             cp = local_host;
323             do {
324                 retval = krb5_try_realm_txt_rr("_kerberos", cp, &realm);
325                 cp = strchr(cp,'.');
326                 if (cp)
327                     cp++;
328             } while (retval && cp && cp[0]);
329         }
330     }
331 #endif /* KRB5_DNS_LOOKUP */
332     if (realm == (char *)NULL) {
333         if (default_realm != (char *)NULL) {
334             /* We are defaulting to the realm of the host */
335             if (!(cp = (char *)malloc(strlen(default_realm)+1)))
336                 return ENOMEM;
337             strcpy(cp, default_realm);
338             realm = cp;
339 
340             /* Assume the realm name is upper case */
341             for (cp = realm; *cp; cp++)
342                 if (islower((int) (*cp)))
343                     *cp = toupper((int) *cp);
344         } else {
345             /* We are defaulting to the local realm */
346             retval = krb5_get_default_realm(context, &realm);
347             if (retval) {
348                 return retval;
349             }
350         }
351     }
352     if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) {
353 	if (realm != (char *)NULL)
354 	    free(realm);
355 	return ENOMEM;
356     }
357 
358     retrealms[0] = realm;
359     retrealms[1] = 0;
360 
361     *realmsp = retrealms;
362     return 0;
363 }
364 
365 #if defined(_WIN32) && !defined(__CYGWIN32__)
366 # ifndef EAFNOSUPPORT
367 #  define EAFNOSUPPORT WSAEAFNOSUPPORT
368 # endif
369 #endif
370 
371 krb5_error_code
372 krb5int_translate_gai_error (int num)
373 {
374     switch (num) {
375 #ifdef EAI_ADDRFAMILY
376     case EAI_ADDRFAMILY:
377 	return EAFNOSUPPORT;
378 #endif
379     case EAI_AGAIN:
380 	return EAGAIN;
381     case EAI_BADFLAGS:
382 	return EINVAL;
383     case EAI_FAIL:
384 	return KRB5_EAI_FAIL;
385     case EAI_FAMILY:
386 	return EAFNOSUPPORT;
387     case EAI_MEMORY:
388 	return ENOMEM;
389 #if EAI_NODATA != EAI_NONAME
390     case EAI_NODATA:
391 	return KRB5_EAI_NODATA;
392 #endif
393     case EAI_NONAME:
394 	return KRB5_EAI_NONAME;
395     case EAI_SERVICE:
396 	return KRB5_EAI_SERVICE;
397     case EAI_SOCKTYPE:
398 	return EINVAL;
399 #ifdef EAI_SYSTEM
400     case EAI_SYSTEM:
401 	return errno;
402 #endif
403     }
404     /* abort (); */
405     return -1;
406 }
407