pull up r24055 from trunk
[krb5.git] / src / lib / krb5 / os / locate_kdc.c
index bae1523a46a16b2d1d5ed6c423b4f6fb88c1ea15..d0134c16f4a08b747ea59d3323acba00d1da6ec9 100644 (file)
@@ -1,14 +1,15 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 /*
  * lib/krb5/os/locate_kdc.c
  *
- * Copyright 1990 by the Massachusetts Institute of Technology.
+ * Copyright 1990,2000,2001,2002,2003,2004,2006,2008 Massachusetts Institute of Technology.
  * All Rights Reserved.
  *
  * 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
  * 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.
- * 
+ *
  *
  * get socket addresses for KDC.
  */
 
-#define NEED_SOCKETS
+#include "fake-addrinfo.h"
 #include "k5-int.h"
 #include "os-proto.h"
 #include <stdio.h>
@@ -35,6 +36,7 @@
 #ifdef WSHELPER
 #include <wshelper.h>
 #else /* WSHELPER */
+#include <netinet/in.h>
 #include <arpa/inet.h>
 #include <arpa/nameser.h>
 #include <resolv.h>
@@ -50,9 +52,6 @@
 #endif
 
 #define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1)
-#ifndef KPASSWD_PORTNAME
-#define KPASSWD_PORTNAME "kpasswd"
-#endif
 
 #if KRB5_DNS_LOOKUP_KDC
 #define DEFAULT_LOOKUP_KDC 1
 #endif
 
 static int
-maybe_use_dns (context, name, defalt)
-     krb5_context context;
-     const char *name;
-     int defalt;
+maybe_use_dns (krb5_context context, const char *name, int defalt)
 {
     krb5_error_code code;
     char * value = NULL;
     int use_dns = 0;
 
-    code = profile_get_string(context->profile, "libdefaults",
+    code = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
                               name, 0, 0, &value);
     if (value == 0 && code == 0)
-       code = profile_get_string(context->profile, "libdefaults",
-                                 "dns_fallback", 0, 0, &value);
+        code = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
+                                  KRB5_CONF_DNS_FALLBACK, 0, 0, &value);
     if (code)
         return defalt;
 
     if (value == 0)
-       return defalt;
+        return defalt;
 
     use_dns = _krb5_conf_boolean(value);
     profile_release_string(value);
@@ -92,51 +88,243 @@ maybe_use_dns (context, name, defalt)
 }
 
 int
-_krb5_use_dns_kdc(context)
-    krb5_context context;
+_krb5_use_dns_kdc(krb5_context context)
 {
-    return maybe_use_dns (context, "dns_lookup_kdc", DEFAULT_LOOKUP_KDC);
+    return maybe_use_dns (context, KRB5_CONF_DNS_LOOKUP_KDC, DEFAULT_LOOKUP_KDC);
 }
 
 int
-_krb5_use_dns_realm(context)
-    krb5_context context;
+_krb5_use_dns_realm(krb5_context context)
 {
-    return maybe_use_dns (context, "dns_lookup_realm", DEFAULT_LOOKUP_REALM);
+    return maybe_use_dns (context, KRB5_CONF_DNS_LOOKUP_REALM, DEFAULT_LOOKUP_REALM);
 }
 
 #endif /* KRB5_DNS_LOOKUP */
 
+int
+krb5int_grow_addrlist (struct addrlist *lp, int nmore)
+{
+    size_t i;
+    size_t newspace = lp->space + nmore;
+    size_t newsize = newspace * sizeof (*lp->addrs);
+    void *newaddrs;
+
+    newaddrs = realloc (lp->addrs, newsize);
+    if (newaddrs == NULL)
+        return ENOMEM;
+    lp->addrs = newaddrs;
+    for (i = lp->space; i < newspace; i++) {
+        lp->addrs[i].ai = NULL;
+        lp->addrs[i].freefn = NULL;
+        lp->addrs[i].data = NULL;
+    }
+    lp->space = newspace;
+    return 0;
+}
+#define grow_list krb5int_grow_addrlist
+
+/* Free up everything pointed to by the addrlist structure, but don't
+   free the structure itself.  */
+void
+krb5int_free_addrlist (struct addrlist *lp)
+{
+    size_t i;
+    for (i = 0; i < lp->naddrs; i++)
+        if (lp->addrs[i].freefn)
+            (lp->addrs[i].freefn)(lp->addrs[i].data);
+    free (lp->addrs);
+    lp->addrs = NULL;
+    lp->naddrs = lp->space = 0;
+}
+#define free_list krb5int_free_addrlist
+
+static int
+translate_ai_error (int err)
+{
+    switch (err) {
+    case 0:
+        return 0;
+    case EAI_BADFLAGS:
+    case EAI_FAMILY:
+    case EAI_SOCKTYPE:
+    case EAI_SERVICE:
+        /* All of these indicate bad inputs to getaddrinfo.  */
+        return EINVAL;
+    case EAI_AGAIN:
+        /* Translate to standard errno code.  */
+        return EAGAIN;
+    case EAI_MEMORY:
+        /* Translate to standard errno code.  */
+        return ENOMEM;
+#ifdef EAI_ADDRFAMILY
+    case EAI_ADDRFAMILY:
+#endif
+#if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME
+    case EAI_NODATA:
+#endif
+    case EAI_NONAME:
+        /* Name not known or no address data, but no error.  Do
+           nothing more.  */
+        return 0;
+#ifdef EAI_OVERFLOW
+    case EAI_OVERFLOW:
+        /* An argument buffer overflowed.  */
+        return EINVAL;          /* XXX */
+#endif
+#ifdef EAI_SYSTEM
+    case EAI_SYSTEM:
+        /* System error, obviously.  */
+        return errno;
+#endif
+    default:
+        /* An error code we haven't handled?  */
+        return EINVAL;
+    }
+}
+
+#include <stdarg.h>
+static inline void
+Tprintf(const char *fmt, ...)
+{
+#ifdef TEST
+    va_list ap;
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+#endif
+}
+
+#if 0
+extern void krb5int_debug_fprint(const char *, ...);
+#define dprint krb5int_debug_fprint
+#define print_addrlist krb5int_print_addrlist
+extern void print_addrlist (const struct addrlist *a);
+#else
+static inline void dprint(const char *fmt, ...) { }
+static inline void print_addrlist(const struct addrlist *a) { }
+#endif
+
+static int
+add_addrinfo_to_list(struct addrlist *lp, struct addrinfo *a,
+                     void (*freefn)(void *), void *data)
+{
+    int err;
+
+    dprint("\tadding %p=%A to %p (naddrs=%d space=%d)\n", a, a, lp,
+           lp->naddrs, lp->space);
+
+    if (lp->naddrs == lp->space) {
+        err = grow_list (lp, 1);
+        if (err) {
+            Tprintf ("grow_list failed %d\n", err);
+            return err;
+        }
+    }
+    Tprintf("setting element %d\n", lp->naddrs);
+    lp->addrs[lp->naddrs].ai = a;
+    lp->addrs[lp->naddrs].freefn = freefn;
+    lp->addrs[lp->naddrs].data = data;
+    lp->naddrs++;
+    Tprintf ("\tcount is now %lu: ", (unsigned long) lp->naddrs);
+    print_addrlist(lp);
+    Tprintf("\n");
+    return 0;
+}
+
+#define add_host_to_list krb5int_add_host_to_list
+
+static void
+call_freeaddrinfo(void *data)
+{
+    /* Strict interpretation of the C standard says we can't assume
+       that the ABI for f(void*) and f(struct foo *) will be
+       compatible.  Use this stub just to be paranoid.  */
+    freeaddrinfo(data);
+}
+
+int
+krb5int_add_host_to_list (struct addrlist *lp, const char *hostname,
+                          int port, int secport,
+                          int socktype, int family)
+{
+    struct addrinfo *addrs, *a, *anext, hint;
+    int err, result;
+    char portbuf[10], secportbuf[10];
+    void (*freefn)(void *);
+
+    Tprintf ("adding hostname %s, ports %d,%d, family %d, socktype %d\n",
+             hostname, ntohs (port), ntohs (secport),
+             family, socktype);
+
+    memset(&hint, 0, sizeof(hint));
+    hint.ai_family = family;
+    hint.ai_socktype = socktype;
+#ifdef AI_NUMERICSERV
+    hint.ai_flags = AI_NUMERICSERV;
+#endif
+    result = snprintf(portbuf, sizeof(portbuf), "%d", ntohs(port));
+    if (SNPRINTF_OVERFLOW(result, sizeof(portbuf)))
+        /* XXX */
+        return EINVAL;
+    result = snprintf(secportbuf, sizeof(secportbuf), "%d", ntohs(secport));
+    if (SNPRINTF_OVERFLOW(result, sizeof(secportbuf)))
+        return EINVAL;
+    err = getaddrinfo (hostname, portbuf, &hint, &addrs);
+    if (err) {
+        Tprintf ("\tgetaddrinfo(\"%s\", \"%s\", ...)\n\treturns %d: %s\n",
+                 hostname, portbuf, err, gai_strerror (err));
+        return translate_ai_error (err);
+    }
+    freefn = call_freeaddrinfo;
+    anext = 0;
+    for (a = addrs; a != 0 && err == 0; a = anext, freefn = 0) {
+        anext = a->ai_next;
+        err = add_addrinfo_to_list (lp, a, freefn, a);
+    }
+    if (err || secport == 0)
+        goto egress;
+    if (socktype == 0)
+        socktype = SOCK_DGRAM;
+    else if (socktype != SOCK_DGRAM)
+        goto egress;
+    hint.ai_family = AF_INET;
+    err = getaddrinfo (hostname, secportbuf, &hint, &addrs);
+    if (err) {
+        err = translate_ai_error (err);
+        goto egress;
+    }
+    freefn = call_freeaddrinfo;
+    for (a = addrs; a != 0 && err == 0; a = anext, freefn = 0) {
+        anext = a->ai_next;
+        err = add_addrinfo_to_list (lp, a, freefn, a);
+    }
+egress:
+    /* XXX Memory leaks possible here if add_addrinfo_to_list fails.  */
+    return err;
+}
+
 /*
  * returns count of number of addresses found
  * if master is non-NULL, it is filled in with the index of
  * the master kdc
  */
 
-krb5_error_code
-krb5_locate_srv_conf(context, realm, name, addr_pp, naddrs, get_masters)
-    krb5_context context;
-    const krb5_data *realm;
-    const char * name;
-    struct sockaddr **addr_pp;
-    int *naddrs;
-    int get_masters;
+static krb5_error_code
+krb5_locate_srv_conf_1(krb5_context context, const krb5_data *realm,
+                       const char * name, struct addrlist *addrlist,
+                       int get_masters, int socktype,
+                       int udpport, int sec_udpport, int family)
 {
-    const char *realm_srv_names[4];
+    const char  *realm_srv_names[4];
     char **masterlist, **hostlist, *host, *port, *cp;
     krb5_error_code code;
-    int i, j, out, count, ismaster;
-    struct sockaddr *addr_p;
-    struct sockaddr_in *sin_p;
-    struct hostent *hp;
-    struct servent *sp;
-#ifdef HAVE_NETINET_IN_H
-    u_short udpport;
-    u_short sec_udpport;
-#endif
+    int i, j, count, ismaster;
 
-    if ((host = malloc(realm->length + 1)) == NULL) 
-       return ENOMEM;
+    Tprintf ("looking in krb5.conf for realm %s entry %s; ports %d,%d\n",
+             realm->data, name, ntohs (udpport), ntohs (sec_udpport));
+
+    if ((host = malloc(realm->length + 1)) == NULL)
+        return ENOMEM;
 
     strncpy(host, realm->data, realm->length);
     host[realm->length] = '\0';
@@ -144,7 +332,7 @@ krb5_locate_srv_conf(context, realm, name, addr_pp, naddrs, get_masters)
 
     masterlist = NULL;
 
-    realm_srv_names[0] = "realms";
+    realm_srv_names[0] = KRB5_CONF_REALMS;
     realm_srv_names[1] = host;
     realm_srv_names[2] = name;
     realm_srv_names[3] = 0;
@@ -152,75 +340,49 @@ krb5_locate_srv_conf(context, realm, name, addr_pp, naddrs, get_masters)
     code = profile_get_values(context->profile, realm_srv_names, &hostlist);
 
     if (code) {
+        Tprintf ("config file lookup failed: %s\n",
+                 error_message(code));
         if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
             code = KRB5_REALM_UNKNOWN;
-       krb5_xfree(host);
-       return code;
-     }
-
-#ifdef HAVE_NETINET_IN_H
-    if ( !strcmp(name,"kpasswd_server") ) {
-        if ((sp = getservbyname(KPASSWD_PORTNAME, "udp")))
-            udpport = sp->s_port;
-        else
-            udpport = htons(DEFAULT_KPASSWD_PORT);
-        sec_udpport = 0;
-    } else {
-    if ((sp = getservbyname(KDC_PORTNAME, "udp")))
-       udpport = sp->s_port;
-        else 
-            udpport = htons(KRB5_DEFAULT_PORT);
-    if ((sp = getservbyname(KDC_SECONDARY_PORTNAME, "udp")))
-       sec_udpport = sp->s_port;
-        else
-            sec_udpport = htons(KRB5_DEFAULT_SEC_PORT);
+        free(host);
+        return code;
     }
-#endif
-    if (sec_udpport == udpport)
-       sec_udpport = 0;
 
     count = 0;
     while (hostlist && hostlist[count])
-           count++;
-    
+        count++;
+    Tprintf ("found %d entries under 'kdc'\n", count);
+
     if (count == 0) {
         profile_free_list(hostlist);
-       krb5_xfree(host);
-       *naddrs = 0;
-       return 0;
+        free(host);
+        addrlist->naddrs = 0;
+        return 0;
     }
-    
+
     if (get_masters) {
-       realm_srv_names[0] = "realms";
-       realm_srv_names[1] = host;
-       realm_srv_names[2] = "admin_server";
-       realm_srv_names[3] = 0;
-
-       code = profile_get_values(context->profile, realm_srv_names,
-                                 &masterlist);
-
-       krb5_xfree(host);
-
-       if (code == 0) {
-           for (i=0; masterlist[i]; i++) {
-               host = masterlist[i];
-
-               /*
-                * Strip off excess whitespace
-                */
-               cp = strchr(host, ' ');
-               if (cp)
-                   *cp = 0;
-               cp = strchr(host, '\t');
-               if (cp)
-                   *cp = 0;
-               cp = strchr(host, ':');
-               if (cp)
-                   *cp = 0;
-           }
-       }
+        realm_srv_names[0] = KRB5_CONF_REALMS;
+        realm_srv_names[1] = host;
+        realm_srv_names[2] = KRB5_CONF_ADMIN_SERVER;
+        realm_srv_names[3] = 0;
+
+        code = profile_get_values(context->profile, realm_srv_names,
+                                  &masterlist);
+
+        free(host);
+
+        if (code == 0) {
+            for (i=0; masterlist[i]; i++) {
+                host = masterlist[i];
+                /* Strip off excess characters. */
+                if (*host == '[' && (cp = strchr(host, ']')))
+                    *(cp + 1) = '\0';
+                else
+                    *(host + strcspn(host, " \t:")) = '\0';
+            }
+        }
     } else {
-       krb5_xfree(host);
+        free(host);
     }
 
     /* at this point, if master is non-NULL, then either the master kdc
@@ -229,86 +391,78 @@ krb5_locate_srv_conf(context, realm, name, addr_pp, naddrs, get_masters)
 
 #ifdef HAVE_NETINET_IN_H
     if (sec_udpport)
-           count = count * 2;
+        count = count * 2;
 #endif
 
-    addr_p = (struct sockaddr *)malloc (sizeof (struct sockaddr) * count);
-    if (addr_p == NULL) {
-        if (hostlist)
-            profile_free_list(hostlist);
-        if (masterlist)
-            profile_free_list(masterlist);
-       return ENOMEM;
-    }
-
-    for (i=0, out=0; hostlist[i]; i++) {
-       host = hostlist[i];
-       /*
-        * Strip off excess whitespace
-        */
-       cp = strchr(host, ' ');
-       if (cp)
-           *cp = 0;
-       cp = strchr(host, '\t');
-       if (cp)
-           *cp = 0;
-       port = strchr(host, ':');
-       if (port) {
-           *port = 0;
-           port++;
-       }
-
-       if ((hp = gethostbyname(hostlist[i])) == 0) {
-           continue;
-       }
-
-       ismaster = 0;
-       if (masterlist) {
-           for (j=0; masterlist[j]; j++) {
-               if (strcasecmp(hostlist[i], masterlist[j]) == 0) {
-                   ismaster = 1;
-               }
-           }
-       }
-
-        if ( !get_masters || ismaster ) {
-            switch (hp->h_addrtype) {
+    for (i=0; hostlist[i]; i++) {
+        int p1, p2;
 
-#ifdef HAVE_NETINET_IN_H
-            case AF_INET:
-                for (j=0; hp->h_addr_list[j]; j++) {
-                    sin_p = (struct sockaddr_in *) &addr_p[out++];
-                    memset ((char *)sin_p, 0, sizeof(struct sockaddr));
-                    sin_p->sin_family = hp->h_addrtype;
-                    sin_p->sin_port = port ? htons(atoi(port)) : udpport;
-                    memcpy((char *)&sin_p->sin_addr,
-                            (char *)hp->h_addr_list[j],
-                            sizeof(struct in_addr));
-                    if (out+1 >= count) {
-                        count += 5;
-                        addr_p = (struct sockaddr *)
-                            realloc ((char *)addr_p,
-                                      sizeof(struct sockaddr) * count);
-                        if (addr_p == NULL) {
-                            if ( hostlist )
-                                profile_free_list(hostlist);
-                            if ( masterlist )
-                                profile_free_list(masterlist);
-                            return ENOMEM;
-                        }
-                    }
-                    if (sec_udpport && !port) {
-                        addr_p[out] = addr_p[out-1];
-                        sin_p = (struct sockaddr_in *) &addr_p[out++];
-                        sin_p->sin_port = sec_udpport;
-                    }
+        host = hostlist[i];
+        Tprintf ("entry %d is '%s'\n", i, host);
+        /* Find port number, and strip off any excess characters. */
+        if (*host == '[' && (cp = strchr(host, ']')))
+            cp = cp + 1;
+        else
+            cp = host + strcspn(host, " \t:");
+        port = (*cp == ':') ? cp + 1 : NULL;
+        *cp = '\0';
+
+        ismaster = 0;
+        if (masterlist) {
+            for (j=0; masterlist[j]; j++) {
+                if (strcasecmp(hostlist[i], masterlist[j]) == 0) {
+                    ismaster = 1;
                 }
-                break;
-#endif
-            default:
-                break;
             }
         }
+
+        if (get_masters && !ismaster)
+            continue;
+
+        if (port) {
+            unsigned long l;
+#ifdef HAVE_STROUL
+            char *endptr;
+            l = strtoul (port, &endptr, 10);
+            if (endptr == NULL || *endptr != 0)
+                return EINVAL;
+#else
+            l = atoi (port);
+#endif
+            /* L is unsigned, don't need to check <0.  */
+            if (l > 65535)
+                return EINVAL;
+            p1 = htons (l);
+            p2 = 0;
+        } else {
+            p1 = udpport;
+            p2 = sec_udpport;
+        }
+
+        /* If the hostname was in brackets, strip those off now. */
+        if (*host == '[' && (cp = strchr(host, ']'))) {
+            host++;
+            *cp = '\0';
+        }
+
+        if (socktype != 0)
+            code = add_host_to_list(addrlist, host, p1, p2, socktype, family);
+        else {
+            code = add_host_to_list(addrlist, host, p1, p2, SOCK_DGRAM,
+                                    family);
+            if (code == 0)
+                code = add_host_to_list(addrlist, host, p1, p2, SOCK_STREAM,
+                                        family);
+        }
+        if (code) {
+            Tprintf ("error %d (%s) returned from add_host_to_list\n", code,
+                     error_message (code));
+            if (hostlist)
+                profile_free_list (hostlist);
+            if (masterlist)
+                profile_free_list (masterlist);
+            return code;
+        }
     }
 
     if (hostlist)
@@ -316,347 +470,404 @@ krb5_locate_srv_conf(context, realm, name, addr_pp, naddrs, get_masters)
     if (masterlist)
         profile_free_list(masterlist);
 
-    if (out == 0) {     /* Couldn't resolve any KDC names */
-        free (addr_p);
-        return KRB5_REALM_CANT_RESOLVE;
-    }
-
-    *addr_pp = addr_p;
-    *naddrs = out;
     return 0;
 }
 
-#ifdef KRB5_DNS_LOOKUP
+#ifdef TEST
+static krb5_error_code
+krb5_locate_srv_conf(krb5_context context, const krb5_data *realm,
+                     const char *name, struct addrlist *al, int get_masters,
+                     int udpport, int sec_udpport)
+{
+    krb5_error_code ret;
 
-/*
- * Lookup a KDC via DNS SRV records
- */
+    ret = krb5_locate_srv_conf_1 (context, realm, name, al,
+                                  get_masters, 0, udpport, sec_udpport, 0);
+    if (ret)
+        return ret;
+    if (al->naddrs == 0)        /* Couldn't resolve any KDC names */
+        return KRB5_REALM_CANT_RESOLVE;
+    return 0;
+}
+#endif
 
-krb5_error_code
-krb5_locate_srv_dns(realm, service, protocol, addr_pp, naddrs)
-    const krb5_data *realm;
-    const char *service;
-    const char *protocol;
-    struct sockaddr **addr_pp;
-    int *naddrs;
+#ifdef KRB5_DNS_LOOKUP
+static krb5_error_code
+krb5_locate_srv_dns_1 (const krb5_data *realm,
+                       const char *service,
+                       const char *protocol,
+                       struct addrlist *addrlist,
+                       int family)
 {
-    int out, j, count;
-    union {
-        unsigned char bytes[2048];
-        HEADER hdr;
-    } answer;
-    unsigned char *p=NULL;
-    char host[MAX_DNS_NAMELEN], *h;
-    struct sockaddr *addr = NULL;
-    struct sockaddr_in *sin = NULL;
-    struct hostent *hp = NULL;
-    int type, class;
-    int priority, weight, size, len, numanswers, numqueries, rdlen;
-    unsigned short port;
-    const int hdrsize = sizeof(HEADER);
-    struct srv_dns_entry {
-       struct srv_dns_entry *next;
-       int priority;
-       int weight;
-       unsigned short port;
-       char *host;
-    };
-
     struct srv_dns_entry *head = NULL;
-    struct srv_dns_entry *srv = NULL, *entry = NULL;
-
-    out = 0;
-    addr = (struct sockaddr *) malloc(sizeof(struct sockaddr));
-    if (addr == NULL)
-       return ENOMEM;
+    struct srv_dns_entry *entry = NULL, *next;
+    krb5_error_code code = 0;
 
-    count = 1;
+    code = krb5int_make_srv_query_realm(realm, service, protocol, &head);
+    if (code)
+        return 0;
 
     /*
-     * First off, build a query of the form:
-     *
-     * service.protocol.realm
-     *
-     * which will most likely be something like:
-     *
-     * _kerberos._udp.REALM
-     *
+     * Okay!  Now we've got a linked list of entries sorted by
+     * priority.  Start looking up A records and returning
+     * addresses.
      */
 
-    if ( strlen(service) + strlen(protocol) + realm->length + 6 
-         > MAX_DNS_NAMELEN )
-        goto out;
-    sprintf(host, "%s.%s.%.*s", service, protocol, realm->length,
-           realm->data);
-
-    /* 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, ".");
-
-    size = res_search(host, C_IN, T_SRV, answer.bytes, sizeof(answer.bytes));
-
-    if (size < hdrsize)
-       goto out;
+    if (head == NULL)
+        return 0;
 
-    /*
-     * We got an answer!  First off, parse the header and figure out how
-     * many answers we got back.
-     */
+    /* Check for the "." case indicating no support.  */
+    if (head->next == 0 && head->host[0] == 0) {
+        free(head->host);
+        free(head);
+        return KRB5_ERR_NO_SERVICE;
+    }
 
-    p = answer.bytes;
+    Tprintf ("walking answer list:\n");
+    for (entry = head; entry != NULL; entry = next) {
+        Tprintf ("\tport=%d host=%s\n", entry->port, entry->host);
+        next = entry->next;
+        code = add_host_to_list (addrlist, entry->host, htons (entry->port), 0,
+                                 (strcmp("_tcp", protocol)
+                                  ? SOCK_DGRAM
+                                  : SOCK_STREAM), family);
+        if (code) {
+            break;
+        }
+        if (entry == head) {
+            free(entry->host);
+            free(entry);
+            head = next;
+            entry = 0;
+        }
+    }
+    Tprintf ("[end]\n");
 
-    numqueries = ntohs(answer.hdr.qdcount);
-    numanswers = ntohs(answer.hdr.ancount);
+    krb5int_free_srv_dns_data(head);
+    return code;
+}
+#endif
 
-    p += sizeof(HEADER);
+#include <krb5/locate_plugin.h>
 
-    /*
-     * We need to skip over all of the questions, so we have to iterate
-     * over every query record.  dn_expand() is able to tell us the size
-     * of compress DNS names, so we use it.
-     */
+#if TARGET_OS_MAC
+static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR, LIBDIR "/krb5/plugins/libkrb5", NULL }; /* should be a list */
+#else
+static const char *objdirs[] = { LIBDIR "/krb5/plugins/libkrb5", NULL };
+#endif
 
-#define INCR_CHECK(x,y) x += y; if (x > size + answer.bytes) goto out
-#define CHECK(x,y) if (x + y > size + answer.bytes) goto out
-#define NTOHSP(x,y) x[0] << 8 | x[1]; x += y
+struct module_callback_data {
+    int out_of_mem;
+    struct addrlist *lp;
+};
 
-    while (numqueries--) {
-       len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
-       if (len < 0)
-           goto out;
-       INCR_CHECK(p, len + 4);
+static int
+module_callback (void *cbdata, int socktype, struct sockaddr *sa)
+{
+    struct module_callback_data *d = cbdata;
+    struct {
+        struct addrinfo ai;
+        union {
+            struct sockaddr_in sin;
+#ifdef KRB5_USE_INET6
+            struct sockaddr_in6 sin6;
+#endif
+        } u;
+    } *x;
+
+    if (socktype != SOCK_STREAM && socktype != SOCK_DGRAM)
+        return 0;
+    if (sa->sa_family != AF_INET
+#ifdef KRB5_USE_INET6
+        && sa->sa_family != AF_INET6
+#endif
+    )
+        return 0;
+    x = calloc (1, sizeof (*x));
+    if (x == 0) {
+        d->out_of_mem = 1;
+        return 1;
     }
+    x->ai.ai_addr = (struct sockaddr *) &x->u;
+    x->ai.ai_socktype = socktype;
+    x->ai.ai_family = sa->sa_family;
+    if (sa->sa_family == AF_INET) {
+        x->u.sin = *(struct sockaddr_in *)sa;
+        x->ai.ai_addrlen = sizeof(struct sockaddr_in);
+    }
+#ifdef KRB5_USE_INET6
+    if (sa->sa_family == AF_INET6) {
+        x->u.sin6 = *(struct sockaddr_in6 *)sa;
+        x->ai.ai_addrlen = sizeof(struct sockaddr_in6);
+    }
+#endif
+    if (add_addrinfo_to_list (d->lp, &x->ai, free, x) != 0) {
+        /* Assumes only error is ENOMEM.  */
+        d->out_of_mem = 1;
+        return 1;
+    }
+    return 0;
+}
 
-    /*
-     * We're now pointing at the answer records.  Only process them if
-     * they're actually T_SRV records (they might be CNAME records,
-     * for instance).
-     *
-     * But in a DNS reply, if you get a CNAME you always get the associated
-     * "real" RR for that CNAME.  RFC 1034, 3.6.2:
-     *
-     * CNAME RRs cause special action in DNS software.  When a name server
-     * fails to find a desired RR in the resource set associated with the
-     * domain name, it checks to see if the resource set consists of a CNAME
-     * record with a matching class.  If so, the name server includes the CNAME
-     * record in the response and restarts the query at the domain name
-     * specified in the data field of the CNAME record.  The one exception to
-     * this rule is that queries which match the CNAME type are not restarted.
-     *
-     * In other words, CNAMEs do not need to be expanded by the client.
-     */
-
-    while (numanswers--) {
-
-       /* First is 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)
-           goto out;
-       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);
-       class = NTOHSP(p,6);
-
-       /* Record data length */
-
-        CHECK(p,2);
-       rdlen = NTOHSP(p,2);
-
-       /*
-        * If this is an SRV record, process it.  Record format is:
-        *
-        * Priority
-        * Weight
-        * Port
-        * Server name
-        */
-
-       if (class == C_IN && type == T_SRV) {
-            CHECK(p,2);
-           priority = NTOHSP(p,2);
-           CHECK(p, 2);
-           weight = NTOHSP(p,2);
-           CHECK(p, 2);
-           port = NTOHSP(p,2);
-           len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
-           if (len < 0)
-               goto out;
-           INCR_CHECK(p, len);
-
-           /*
-            * We got everything!  Insert it into our list, but make sure
-            * it's in the right order.  Right now we don't do anything
-            * with the weight field
-            */
-
-           srv = (struct srv_dns_entry *) malloc(sizeof(struct srv_dns_entry));
-           if (srv == NULL)
-               goto out;
-       
-           srv->priority = priority;
-           srv->weight = weight;
-           srv->port = port;
-           srv->host = strdup(host);
-
-           if (head == NULL || head->priority > srv->priority) {
-               srv->next = head;
-               head = srv;
-           } else
-               /*
-                * This is confusing.  Only insert an entry into this
-                * spot if:
-                * The next person has a higher priority (lower priorities
-                * are preferred).
-                * Or
-                * There is no next entry (we're at the end)
-                */
-               for (entry = head; entry != NULL; entry = entry->next)
-                   if ((entry->next &&
-                        entry->next->priority > srv->priority) ||
-                       entry->next == NULL) {
-                       srv->next = entry->next;
-                       entry->next = srv;
-                       break;
-                   }
-       } else
-           INCR_CHECK(p, rdlen);
+static krb5_error_code
+module_locate_server (krb5_context ctx, const krb5_data *realm,
+                      struct addrlist *addrlist,
+                      enum locate_service_type svc, int socktype, int family)
+{
+    struct krb5plugin_service_locate_result *res = NULL;
+    krb5_error_code code;
+    struct krb5plugin_service_locate_ftable *vtbl = NULL;
+    void **ptrs;
+    char *realmz;               /* NUL-terminated realm */
+    int i;
+    struct module_callback_data cbdata = { 0, };
+    const char *msg;
+
+    Tprintf("in module_locate_server\n");
+    cbdata.lp = addrlist;
+    if (!PLUGIN_DIR_OPEN (&ctx->libkrb5_plugins)) {
+
+        code = krb5int_open_plugin_dirs (objdirs, NULL, &ctx->libkrb5_plugins,
+                                         &ctx->err);
+        if (code)
+            return KRB5_PLUGIN_NO_HANDLE;
     }
-       
-    /*
-     * Okay!  Now we've got a linked list of entries sorted by
-     * priority.  Start looking up A records and returning
-     * addresses.
-     */
 
-    if (head == NULL)
-       goto out;
+    code = krb5int_get_plugin_dir_data (&ctx->libkrb5_plugins,
+                                        "service_locator", &ptrs, &ctx->err);
+    if (code) {
+        Tprintf("error looking up plugin symbols: %s\n",
+                (msg = krb5_get_error_message(ctx, code)));
+        krb5_free_error_message(ctx, msg);
+        return KRB5_PLUGIN_NO_HANDLE;
+    }
 
-    for (entry = head; entry != NULL; entry = entry->next) {
-       hp = gethostbyname(entry->host);
-       if (hp != 0) {
-           switch (hp->h_addrtype) {
-#ifdef HAVE_NETINET_IN_H
-            case AF_INET:
-               for (j=0; hp->h_addr_list[j]; j++) {
-                   sin = (struct sockaddr_in *) &addr[out++];
-                   memset ((char *) sin, 0, sizeof (struct sockaddr));
-                   sin->sin_family = hp->h_addrtype;
-                   sin->sin_port = htons(entry->port);
-                   memcpy((char *) &sin->sin_addr,
-                          (char *) hp->h_addr_list[j],
-                          sizeof(struct in_addr));
-                   if (out + 1 >= count) {
-                       count += 5;
-                       addr = (struct sockaddr *)
-                               realloc((char *) addr,
-                                       sizeof(struct sockaddr) * count);
-                       if (!addr)
-                           goto out;
-                   }
-               }
-               break;
-#endif /* HAVE_NETINET_IN_H */
-           default:
-               break;
-           }
-       }
+    if (realm->length >= UINT_MAX) {
+        krb5int_free_plugin_dir_data(ptrs);
+        return ENOMEM;
+    }
+    realmz = malloc(realm->length + 1);
+    if (realmz == NULL) {
+        krb5int_free_plugin_dir_data(ptrs);
+        return ENOMEM;
+    }
+    memcpy(realmz, realm->data, realm->length);
+    realmz[realm->length] = '\0';
+    for (i = 0; ptrs[i]; i++) {
+        void *blob;
+
+        vtbl = ptrs[i];
+        Tprintf("element %d is %p\n", i, ptrs[i]);
+
+        /* For now, don't keep the plugin data alive.  For long-lived
+           contexts, it may be desirable to change that later.  */
+        code = vtbl->init(ctx, &blob);
+        if (code)
+            continue;
+
+        code = vtbl->lookup(blob, svc, realmz, socktype, family,
+                            module_callback, &cbdata);
+        vtbl->fini(blob);
+        if (code == KRB5_PLUGIN_NO_HANDLE) {
+            /* Module passes, keep going.  */
+            /* XXX */
+            Tprintf("plugin doesn't handle this realm (KRB5_PLUGIN_NO_HANDLE)\n");
+            continue;
+        }
+        if (code != 0) {
+            /* Module encountered an actual error.  */
+            Tprintf("plugin lookup routine returned error %d: %s\n",
+                    code, error_message(code));
+            free(realmz);
+            krb5int_free_plugin_dir_data (ptrs);
+            return code;
+        }
+        break;
+    }
+    if (ptrs[i] == NULL) {
+        Tprintf("ran off end of plugin list\n");
+        free(realmz);
+        krb5int_free_plugin_dir_data (ptrs);
+        return KRB5_PLUGIN_NO_HANDLE;
     }
+    Tprintf("stopped with plugin #%d, res=%p\n", i, res);
+
+    /* Got something back, yippee.  */
+    Tprintf("now have %lu addrs in list %p\n",
+            (unsigned long) addrlist->naddrs, addrlist);
+    print_addrlist(addrlist);
+    free(realmz);
+    krb5int_free_plugin_dir_data (ptrs);
+    return 0;
+}
 
-    for (entry = head; entry != NULL; ) {
-       free(entry->host);
-        entry->host = NULL;
-       srv = entry;
-       entry = entry->next;
-       free(srv);
-        srv = NULL;
+static krb5_error_code
+prof_locate_server (krb5_context context, const krb5_data *realm,
+                    struct addrlist *addrlist,
+                    enum locate_service_type svc, int socktype, int family)
+{
+    const char *profname;
+    int dflport1, dflport2 = 0;
+    struct servent *serv;
+
+    switch (svc) {
+    case locate_service_kdc:
+        profname = KRB5_CONF_KDC;
+        /* We used to use /etc/services for these, but enough systems
+           have old, crufty, wrong settings that this is probably
+           better.  */
+    kdc_ports:
+        dflport1 = htons(KRB5_DEFAULT_PORT);
+        dflport2 = htons(KRB5_DEFAULT_SEC_PORT);
+        break;
+    case locate_service_master_kdc:
+        profname = KRB5_CONF_MASTER_KDC;
+        goto kdc_ports;
+    case locate_service_kadmin:
+        profname = KRB5_CONF_ADMIN_SERVER;
+        dflport1 = htons(DEFAULT_KADM5_PORT);
+        break;
+    case locate_service_krb524:
+        profname = KRB5_CONF_KRB524_SERVER;
+        serv = getservbyname(KRB524_SERVICE, "udp");
+        dflport1 = serv ? serv->s_port : htons (KRB524_PORT);
+        break;
+    case locate_service_kpasswd:
+        profname = KRB5_CONF_KPASSWD_SERVER;
+        dflport1 = htons(DEFAULT_KPASSWD_PORT);
+        break;
+    default:
+        return EBUSY;           /* XXX */
     }
 
-  out:
-    if (srv)
-        free(srv);
+    return krb5_locate_srv_conf_1 (context, realm, profname, addrlist,
+                                   0, socktype,
+                                   dflport1, dflport2, family);
+}
 
-    if (out == 0) {    /* No good servers */
-        if (addr)
-            free(addr);
-       return KRB5_REALM_CANT_RESOLVE;
+static krb5_error_code
+dns_locate_server (krb5_context context, const krb5_data *realm,
+                   struct addrlist *addrlist,
+                   enum locate_service_type svc, int socktype, int family)
+{
+    const char *dnsname;
+    int use_dns = _krb5_use_dns_kdc(context);
+    krb5_error_code code;
+
+    if (!use_dns)
+        return KRB5_PLUGIN_NO_HANDLE;
+
+    switch (svc) {
+    case locate_service_kdc:
+        dnsname = "_kerberos";
+        break;
+    case locate_service_master_kdc:
+        dnsname = "_kerberos-master";
+        break;
+    case locate_service_kadmin:
+        dnsname = "_kerberos-adm";
+        break;
+    case locate_service_krb524:
+        dnsname = "_krb524";
+        break;
+    case locate_service_kpasswd:
+        dnsname = "_kpasswd";
+        break;
+    default:
+        return KRB5_PLUGIN_NO_HANDLE;
     }
 
-    *addr_pp = addr;
-    *naddrs = out;
-    return 0;
+    code = 0;
+    if (socktype == SOCK_DGRAM || socktype == 0) {
+        code = krb5_locate_srv_dns_1(realm, dnsname, "_udp", addrlist, family);
+        if (code)
+            Tprintf("dns udp lookup returned error %d\n", code);
+    }
+    if ((socktype == SOCK_STREAM || socktype == 0) && code == 0) {
+        code = krb5_locate_srv_dns_1(realm, dnsname, "_tcp", addrlist, family);
+        if (code)
+            Tprintf("dns tcp lookup returned error %d\n", code);
+    }
+    return code;
 }
-#endif /* KRB5_DNS_LOOKUP */
 
 /*
- * Wrapper function for the two backends
+ * Wrapper function for the various backends
  */
 
 krb5_error_code
-krb5_locate_kdc(context, realm, addr_pp, naddrs, get_masters)
-    krb5_context context;
-    const krb5_data *realm;
-    struct sockaddr **addr_pp;
-    int *naddrs;
-    int get_masters;
+krb5int_locate_server (krb5_context context, const krb5_data *realm,
+                       struct addrlist *addrlist,
+                       enum locate_service_type svc,
+                       int socktype, int family)
 {
     krb5_error_code code;
+    struct addrlist al = ADDRLIST_INIT;
 
-    /*
-     * We always try the local file first
-     */
+    *addrlist = al;
 
-    code = krb5_locate_srv_conf(context, realm, "kdc", addr_pp, naddrs,
-                                 get_masters);
+    if (realm == NULL || realm->data == NULL || realm->data[0] == 0) {
+        krb5_set_error_message(context, KRB5_REALM_CANT_RESOLVE,
+                               "Cannot find KDC for invalid realm name \"\"");
+        return KRB5_REALM_CANT_RESOLVE;
+    }
+
+    code = module_locate_server(context, realm, &al, svc, socktype, family);
+    Tprintf("module_locate_server returns %d\n", code);
+    if (code == KRB5_PLUGIN_NO_HANDLE) {
+        /*
+         * We always try the local file before DNS.  Note that there
+         * is no way to indicate "service not available" via the
+         * config file.
+         */
+
+        code = prof_locate_server(context, realm, &al, svc, socktype, family);
 
 #ifdef KRB5_DNS_LOOKUP
-    if (code) {
-        int use_dns = _krb5_use_dns_kdc(context);
-        if ( use_dns ) {
-            code = krb5_locate_srv_dns(realm, 
-                                        get_masters ? "_kerberos-master" : "_kerberos",
-                                        "_udp", addr_pp, naddrs);
+        if (code) {             /* Try DNS for all profile errors?  */
+            krb5_error_code code2;
+            code2 = dns_locate_server(context, realm, &al, svc, socktype,
+                                      family);
+            if (code2 != KRB5_PLUGIN_NO_HANDLE)
+                code = code2;
         }
-    }
 #endif /* KRB5_DNS_LOOKUP */
-    return (code);
-}
 
-#if 0 /* Why is this useful?  It's not used now, and it's certainly
-        not useful if you don't have the DNS code enabled.  -KR  */
+        /* We could put more heuristics here, like looking up a hostname
+           of "kerberos."+REALM, etc.  */
+    }
+    if (code == 0)
+        Tprintf ("krb5int_locate_server found %d addresses\n",
+                 al.naddrs);
+    else
+        Tprintf ("krb5int_locate_server returning error code %d/%s\n",
+                 code, error_message(code));
+    if (code != 0) {
+        if (al.space)
+            free_list (&al);
+        return code;
+    }
+    if (al.naddrs == 0) {       /* No good servers */
+        if (al.space)
+            free_list (&al);
+        krb5_set_error_message(context, KRB5_REALM_CANT_RESOLVE,
+                               "Cannot resolve network address for KDC in realm \"%.*s\"",
+                               realm->length, realm->data);
 
-/*
- * It turns out that it is really useful to be able to use these functions
- * for other things (like admin servers), so create an abstract function
- * for this
- */
+        return KRB5_REALM_CANT_RESOLVE;
+    }
+    *addrlist = al;
+    return 0;
+}
 
 krb5_error_code
-krb5_locate_server(realm, name, proto, addr_pp, naddrs)
-    const krb5_data *realm;
-    const char *name, *proto;
-    struct sockaddr **addr_pp;
-    int *naddrs;
+krb5_locate_kdc(krb5_context context, const krb5_data *realm,
+                struct addrlist *addrlist,
+                int get_masters, int socktype, int family)
 {
-    krb5_error_code code = KRB5_REALM_UNKNOWN;
-#ifdef KRB5_DNS_LOOKUP
-    code = krb5_locate_srv_dns(realm, name, proto,
-                                (struct sockaddr **) addr_pp, naddrs);
-#endif /* KRB5_DNS_LOOKUP */
-    return (code);
+    return krb5int_locate_server(context, realm, addrlist,
+                                 (get_masters
+                                  ? locate_service_master_kdc
+                                  : locate_service_kdc),
+                                 socktype, family);
 }
-#endif