+/* -*- 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>
#ifdef WSHELPER
#include <wshelper.h>
#else /* WSHELPER */
+#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <resolv.h>
#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);
}
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';
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;
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
#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)
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