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