/*
- * $Source$
- * $Author$
+ * lib/krb5/os/hst_realm.c
*
- * Copyright 1990 by the Massachusetts Institute of Technology.
+ * Copyright 1990,1991,2002 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
*
- * For copying and distribution information, please see the file
- * <krb5/copyright.h>.
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
*
* krb5_get_host_realm()
*/
-#if !defined(lint) && !defined(SABER)
-static char rcsid_hst_realm_c[] =
-"$Id$";
-#endif /* !lint & !SABER */
-
-#include <krb5/copyright.h>
/*
Figures out the Kerberos realm names for host, filling in a
* host names should be in the usual form (e.g. FOO.BAR.BAZ)
*/
-
-#include <krb5/krb5.h>
-#include <krb5/ext-proto.h>
-#include <krb5/libos-proto.h>
-#include <krb5/sysincl.h>
+#define NEED_SOCKETS
+#include "k5-int.h"
+#include "os-proto.h"
#include <ctype.h>
-
#include <stdio.h>
+#ifdef HAVE_STRING_H
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+#ifdef KRB5_DNS_LOOKUP
+#ifdef WSHELPER
+#include <wshelper.h>
+#else /* WSHELPER */
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#ifndef T_TXT /* not defined on SunOS 4 */
+# define T_TXT 15
+#endif
+#include <resolv.h>
+#include <netdb.h>
+#endif /* WSHELPER */
+#endif /* KRB5_DNS_LOOKUP */
+
+#include "fake-addrinfo.h"
/* for old Unixes and friends ... */
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64
#endif
-#define DEF_REALMNAME_SIZE 256
+#define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1)
-extern char *krb5_trans_file;
+#ifdef KRB5_DNS_LOOKUP
+/*
+ * Try to look up a TXT record pointing to a Kerberos realm
+ */
krb5_error_code
-krb5_get_host_realm(host, realmsp)
-const char *host;
-char ***realmsp;
+krb5_try_realm_txt_rr(const char *prefix, const char *name, char **realm)
{
- char **retrealms;
- char *domain;
- FILE *trans_file;
- char trans_host[MAXHOSTNAMELEN+1];
- char trans_realm[DEF_REALMNAME_SIZE];
- krb5_error_code retval;
- int scanval;
- char scanstring[7+2*16]; /* 7 chars + 16 for each decimal
- conversion */
+ union {
+ unsigned char bytes[2048];
+ HEADER hdr;
+ } answer;
+ unsigned char *p;
+ char host[MAX_DNS_NAMELEN], *h;
+ int size;
+ int type, rrclass, numanswers, numqueries, rdlen, len;
- if (!(retrealms = (char **)calloc(2, sizeof(*retrealms))))
- return ENOMEM;
- domain = strchr(host, '.');
+ /*
+ * Form our query, and send it via DNS
+ */
+
+ if (name == NULL || name[0] == '\0') {
+ if (strlen (prefix) >= sizeof(host)-1)
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+ strcpy(host,prefix);
+ } else {
+ if ( strlen(prefix) + strlen(name) + 3 > MAX_DNS_NAMELEN )
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+ sprintf(host,"%s.%s", prefix, name);
+
+ /* Realm names don't (normally) end with ".", but if the query
+ doesn't end with "." and doesn't get an answer as is, the
+ resolv code will try appending the local domain. Since the
+ realm names are absolutes, let's stop that.
+
+ But only if a name has been specified. If we are performing
+ a search on the prefix alone then the intention is to allow
+ the local domain or domain search lists to be expanded.
+ */
+
+ h = host + strlen (host);
+ if ((h > host) && (h[-1] != '.') && ((h - host + 1) < sizeof(host)))
+ strcpy (h, ".");
+ }
+#ifdef HAVE_RES_NSEARCH
+ {
+ res_state statp;
+ /* Weird... the man pages I've been looking at (Solaris 9) say
+ we pass a res_state object (which is a pointer) into
+ various routines, but they don't say much of anything about
+ what it should point to initially or how it should be
+ allocated.
+
+ They also give no indication what the return value of
+ res_ninit is. */
+ typedef union {
+ struct sockaddr_storage ss;
+ INT64_TYPE i64;
+ double d;
+ } aligned_thing;
+ aligned_thing statp_buf[(sizeof(*statp) + sizeof(aligned_thing) - 1) / sizeof(aligned_thing)];
+ int n;
+
+ statp = (res_state) &statp_buf;
+ memset(&statp_buf, 0, sizeof(statp_buf));
+ n = res_ninit(statp);
+ /* ignore n? */
+ size = res_nsearch(statp, host, C_IN, T_TXT,
+ answer.bytes, sizeof(answer.bytes));
+ }
+#else
+ size = res_search(host, C_IN, T_TXT, answer.bytes, sizeof(answer.bytes));
+#endif
- /* prepare default */
- if (domain) {
- char *cp;
+ if ((size < sizeof(HEADER)) || (size > sizeof(answer.bytes)))
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
- if (!(retrealms[0] = malloc(strlen(&domain[1])+1))) {
- xfree(retrealms);
- return ENOMEM;
+ p = answer.bytes;
+
+ numqueries = ntohs(answer.hdr.qdcount);
+ numanswers = ntohs(answer.hdr.ancount);
+
+ p += sizeof(HEADER);
+
+ /*
+ * We need to skip over the questions before we can get to the answers,
+ * which means we have to iterate over every query record. We use
+ * dn_expand to tell us how long each compressed name is.
+ */
+
+#define INCR_CHECK(x, y) x += y; if (x > size + answer.bytes) \
+ return KRB5_ERR_HOST_REALM_UNKNOWN
+#define CHECK(x, y) if (x + y > size + answer.bytes) \
+ return KRB5_ERR_HOST_REALM_UNKNOWN
+#define NTOHSP(x, y) x[0] << 8 | x[1]; x += y
+
+ while (numqueries--) {
+ len = dn_expand(answer.bytes, answer.bytes + size, p, host,
+ sizeof(host));
+ if (len < 0)
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+ INCR_CHECK(p, len + 4); /* Name plus type plus class */
+ }
+
+ /*
+ * We're now pointing at the answer records. Process the first
+ * TXT record we find.
+ */
+
+ while (numanswers--) {
+
+ /* First the name; use dn_expand to get the compressed size */
+ len = dn_expand(answer.bytes, answer.bytes + size, p,
+ host, sizeof(host));
+ if (len < 0)
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+ INCR_CHECK(p, len);
+
+ /* Next is the query type */
+ CHECK(p, 2);
+ type = NTOHSP(p,2);
+
+ /* Next is the query class; also skip over 4 byte TTL */
+ CHECK(p,6);
+ rrclass = NTOHSP(p,6);
+
+ /* Record data length - make sure we aren't truncated */
+
+ CHECK(p,2);
+ rdlen = NTOHSP(p,2);
+
+ if (p + rdlen > answer.bytes + size)
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+
+ /*
+ * If this is a TXT record, return the string. Note that the
+ * string has a 1-byte length in the front
+ */
+ /* XXX What about flagging multiple TXT records as an error? */
+
+ if (rrclass == C_IN && type == T_TXT) {
+ len = *p++;
+ if (p + len > answer.bytes + size)
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+ *realm = malloc(len + 1);
+ if (*realm == NULL)
+ return ENOMEM;
+ strncpy(*realm, (char *) p, len);
+ (*realm)[len] = '\0';
+ /* Avoid a common error. */
+ if ( (*realm)[len-1] == '.' )
+ (*realm)[len-1] = '\0';
+ return 0;
}
- strcpy(retrealms[0], &domain[1]);
- /* Upper-case realm */
- for (cp = retrealms[0]; *cp; cp++)
- if (islower(*cp))
- *cp = toupper(*cp);
+ }
+
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+}
+#endif /* KRB5_DNS_LOOKUP */
+
+krb5_error_code krb5int_translate_gai_error (int);
+
+static krb5_error_code
+krb5int_get_fq_hostname (char *buf, size_t bufsize, const char *name)
+{
+ struct addrinfo *ai, hints;
+ int err;
+
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_flags = AI_CANONNAME;
+ err = getaddrinfo (name, 0, &hints, &ai);
+ if (err)
+ return krb5int_translate_gai_error (err);
+ if (ai->ai_canonname == 0)
+ return KRB5_EAI_FAIL;
+ strncpy (buf, ai->ai_canonname, bufsize);
+ buf[bufsize-1] = 0;
+ freeaddrinfo (ai);
+ return 0;
+}
+
+/* Get the local host name, try to make it fully-qualified.
+ Always return a null-terminated string.
+ Might return an error if gethostname fails. */
+krb5_error_code
+krb5int_get_fq_local_hostname (char *buf, size_t bufsiz)
+{
+ buf[0] = 0;
+ if (gethostname (buf, bufsiz) == -1)
+ return SOCKET_ERRNO;
+ buf[bufsiz - 1] = 0;
+ return krb5int_get_fq_hostname (buf, bufsiz, buf);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_get_host_realm(krb5_context context, const char *host, char ***realmsp)
+{
+ char **retrealms;
+ char *default_realm, *realm, *cp, *temp_realm;
+ krb5_error_code retval;
+ int l;
+ char local_host[MAX_DNS_NAMELEN+1];
+
+ if (host) {
+ /* Filter out numeric addresses if the caller utterly failed to
+ convert them to names. */
+ /* IPv4 - dotted quads only */
+ if (strspn(host, "01234567890.") == strlen(host)) {
+ /* All numbers and dots... if it's three dots, it's an
+ IP address, and we reject it. But "12345" could be
+ a local hostname, couldn't it? We'll just assume
+ that a name with three dots is not meant to be an
+ all-numeric hostname three all-numeric domains down
+ from the current domain. */
+ int ndots = 0;
+ const char *p;
+ for (p = host; *p; p++)
+ if (*p == '.')
+ ndots++;
+ if (ndots == 3)
+ return KRB5_ERR_NUMERIC_REALM;
+ }
+ if (strchr(host, ':'))
+ /* IPv6 numeric address form? Bye bye. */
+ return KRB5_ERR_NUMERIC_REALM;
+
+ /* Should probably error out if strlen(host) > MAX_DNS_NAMELEN. */
+ strncpy(local_host, host, sizeof(local_host));
+ local_host[sizeof(local_host) - 1] = '\0';
} else {
- if (retval = krb5_get_default_realm(&retrealms[0])) {
- xfree(retrealms);
+ retval = krb5int_get_fq_local_hostname (local_host,
+ sizeof (local_host));
+ if (retval)
return retval;
- }
}
- if ((trans_file = fopen(krb5_trans_file, "r")) == (FILE *) 0) {
- xfree(retrealms[0]);
- xfree(retrealms);
- return KRB5_TRANS_CANTOPEN;
+ for (cp = local_host; *cp; cp++) {
+ if (isupper((int) (*cp)))
+ *cp = tolower((int) *cp);
}
- (void) sprintf(scanstring, "%%%ds %%%ds",
- sizeof(trans_host)-1,sizeof(trans_realm)-1);
- while (1) {
- if ((scanval = fscanf(trans_file, scanstring,
- trans_host, trans_realm)) != 2) {
- if (scanval == EOF) {
- fclose(trans_file);
- goto out;
- }
- continue; /* ignore broken lines */
- }
- trans_host[sizeof(trans_host)-1] = '\0';
- trans_realm[sizeof(trans_realm)-1] = '\0';
- if (!strcasecmp(trans_host, host)) {
- /* exact match of hostname, so return the realm */
- if (!(retrealms[0] = realloc(retrealms[0],
- strlen(trans_realm)+1))) {
- xfree(retrealms);
- return ENOMEM;
- }
- (void) strcpy(retrealms[0], trans_realm);
- fclose(trans_file);
- goto out;
- }
- if ((trans_host[0] == '.') && domain) {
- /* this is a possible domain match */
- if (!strcasecmp(trans_host, domain)) {
- /* domain match, save for later */
- if (!(retrealms[0] = realloc(retrealms[0],
- strlen(trans_realm)+1))) {
- xfree(retrealms);
- return ENOMEM;
- }
- (void) strcpy(retrealms[0], trans_realm);
- continue;
+ l = strlen(local_host);
+ /* strip off trailing dot */
+ if (l && local_host[l-1] == '.')
+ local_host[l-1] = 0;
+
+ /*
+ Search for the best match for the host or domain.
+ Example: Given a host a.b.c.d, try to match on:
+ 1) A.B.C.D
+ 2) .B.C.D
+ 3) B.C.D
+ 4) .C.D
+ 5) C.D
+ 6) .D
+ 7) D
+ */
+
+ cp = local_host;
+ realm = default_realm = (char *)NULL;
+ temp_realm = 0;
+ while (cp) {
+ retval = profile_get_string(context->profile, "domain_realm", cp,
+ 0, (char *)NULL, &temp_realm);
+ if (retval)
+ return retval;
+ if (temp_realm != (char *)NULL)
+ break; /* Match found */
+
+ /* Setup for another test */
+ if (*cp == '.') {
+ cp++;
+ if (default_realm == (char *)NULL) {
+ /* If nothing else works, use the host's domain */
+ default_realm = cp;
}
+ } else {
+ cp = strchr(cp, '.');
}
}
- out:
+ if (temp_realm) {
+ realm = malloc(strlen(temp_realm) + 1);
+ if (!realm) {
+ profile_release_string(temp_realm);
+ return ENOMEM;
+ }
+ strcpy(realm, temp_realm);
+ profile_release_string(temp_realm);
+ }
+
+#ifdef KRB5_DNS_LOOKUP
+ if (realm == (char *)NULL) {
+ int use_dns = _krb5_use_dns_realm(context);
+ if ( use_dns ) {
+ /*
+ * Since this didn't appear in our config file, try looking
+ * it up via DNS. Look for a TXT records of the form:
+ *
+ * _kerberos.<hostname>
+ *
+ */
+ cp = local_host;
+ do {
+ retval = krb5_try_realm_txt_rr("_kerberos", cp, &realm);
+ cp = strchr(cp,'.');
+ if (cp)
+ cp++;
+ } while (retval && cp && cp[0]);
+ }
+ }
+#endif /* KRB5_DNS_LOOKUP */
+ if (realm == (char *)NULL) {
+ if (default_realm != (char *)NULL) {
+ /* We are defaulting to the realm of the host */
+ if (!(cp = (char *)malloc(strlen(default_realm)+1)))
+ return ENOMEM;
+ strcpy(cp, default_realm);
+ realm = cp;
+
+ /* Assume the realm name is upper case */
+ for (cp = realm; *cp; cp++)
+ if (islower((int) (*cp)))
+ *cp = toupper((int) *cp);
+ } else {
+ /* We are defaulting to the local realm */
+ retval = krb5_get_default_realm(context, &realm);
+ if (retval) {
+ return retval;
+ }
+ }
+ }
+ if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) {
+ if (realm != (char *)NULL)
+ free(realm);
+ return ENOMEM;
+ }
+
+ retrealms[0] = realm;
+ retrealms[1] = 0;
+
*realmsp = retrealms;
return 0;
}
+
+#if defined(_WIN32) && !defined(__CYGWIN32__)
+# ifndef EAFNOSUPPORT
+# define EAFNOSUPPORT WSAEAFNOSUPPORT
+# endif
+#endif
+
+krb5_error_code
+krb5int_translate_gai_error (int num)
+{
+ switch (num) {
+#ifdef EAI_ADDRFAMILY
+ case EAI_ADDRFAMILY:
+ return EAFNOSUPPORT;
+#endif
+ case EAI_AGAIN:
+ return EAGAIN;
+ case EAI_BADFLAGS:
+ return EINVAL;
+ case EAI_FAIL:
+ return KRB5_EAI_FAIL;
+ case EAI_FAMILY:
+ return EAFNOSUPPORT;
+ case EAI_MEMORY:
+ return ENOMEM;
+#if EAI_NODATA != EAI_NONAME
+ case EAI_NODATA:
+ return KRB5_EAI_NODATA;
+#endif
+ case EAI_NONAME:
+ return KRB5_EAI_NONAME;
+ case EAI_SERVICE:
+ return KRB5_EAI_SERVICE;
+ case EAI_SOCKTYPE:
+ return EINVAL;
+#ifdef EAI_SYSTEM
+ case EAI_SYSTEM:
+ return errno;
+#endif
+ }
+ abort ();
+ return -1;
+}