* dnssrv.c:
[krb5.git] / src / lib / krb5 / os / hst_realm.c
index 0b17b4198ac3d6a49249a51d27711eaa87103bc6..be538a025c082dd188cbabfa876fd24676835f3e 100644 (file)
@@ -1,21 +1,32 @@
 /*
- * $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
@@ -54,107 +65,423 @@ static char rcsid_hst_realm_c[] =
  * 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;
+}