diff options
Diffstat (limited to 'third_party/heimdal/lib/krb5/krbhst.c')
-rw-r--r-- | third_party/heimdal/lib/krb5/krbhst.c | 1282 |
1 files changed, 1282 insertions, 0 deletions
diff --git a/third_party/heimdal/lib/krb5/krbhst.c b/third_party/heimdal/lib/krb5/krbhst.c new file mode 100644 index 0000000..59a8e77 --- /dev/null +++ b/third_party/heimdal/lib/krb5/krbhst.c @@ -0,0 +1,1282 @@ +/* + * Copyright (c) 2001 - 2003 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "krb5_locl.h" +#include <resolve.h> +#include "locate_plugin.h" + +static int +string_to_proto(const char *string) +{ + if(strcasecmp(string, "udp") == 0) + return KRB5_KRBHST_UDP; + else if(strcasecmp(string, "tcp") == 0) + return KRB5_KRBHST_TCP; + else if(strcasecmp(string, "http") == 0) + return KRB5_KRBHST_HTTP; + return -1; +} + +static int +is_invalid_tld_srv_target(const char *target) +{ + return (strncmp("your-dns-needs-immediate-attention.", + target, 35) == 0 + && strchr(&target[35], '.') == NULL); +} + +/* + * set `res' and `count' to the result of looking up SRV RR in DNS for + * `proto', `proto', `realm' using `dns_type'. + * if `port' != 0, force that port number + */ + +static krb5_error_code +srv_find_realm(krb5_context context, krb5_krbhst_info ***res, int *count, + const char *realm, const char *dns_type, const char *sitename, + const char *proto, const char *service, int port) +{ + char domain[1024]; + struct rk_dns_reply *r; + struct rk_resource_record *rr; + int num_srv; + int proto_num; + int def_port; + + *res = NULL; + *count = 0; + + proto_num = string_to_proto(proto); + if(proto_num < 0) { + krb5_set_error_message(context, EINVAL, + N_("unknown protocol `%s' to lookup", ""), + proto); + return EINVAL; + } + + if(proto_num == KRB5_KRBHST_HTTP) + def_port = ntohs(krb5_getportbyname (context, "http", "tcp", 80)); + else if(port == 0) + def_port = ntohs(krb5_getportbyname (context, service, proto, 88)); + else + def_port = port; + + if (sitename) + snprintf(domain, sizeof(domain), "_%s._%s.%s._sites.%s.", + service, proto, sitename, realm); + else + snprintf(domain, sizeof(domain), "_%s._%s.%s.", service, proto, realm); + + r = rk_dns_lookup(domain, dns_type); + if(r == NULL) { + _krb5_debug(context, 0, + "DNS lookup failed domain: %s", domain); + return KRB5_KDC_UNREACH; + } + + for(num_srv = 0, rr = r->head; rr; rr = rr->next) + if(rr->type == rk_ns_t_srv) { + if (num_srv >= INT_MAX) { + rk_dns_free_data(r); + return KRB5_KDC_UNREACH; + } + if (num_srv >= SIZE_MAX / sizeof(**res)) { + rk_dns_free_data(r); + return KRB5_KDC_UNREACH; + } + num_srv++; + } + + if (num_srv == 0) { + _krb5_debug(context, 0, + "DNS SRV RR lookup domain nodata: %s", domain); + rk_dns_free_data(r); + return KRB5_KDC_UNREACH; + } + + *res = malloc(num_srv * sizeof(**res)); + if(*res == NULL) { + rk_dns_free_data(r); + return krb5_enomem(context); + } + + rk_dns_srv_order(r); + + for(num_srv = 0, rr = r->head; rr; rr = rr->next) + if(rr->type == rk_ns_t_srv) { + krb5_krbhst_info *hi = NULL; + size_t len; + int invalid_tld = 1; + + /* Test for top-level domain controlled interruptions */ + if (!is_invalid_tld_srv_target(rr->u.srv->target)) { + invalid_tld = 0; + len = strlen(rr->u.srv->target); + hi = calloc(1, sizeof(*hi) + len); + } + if(hi == NULL) { + rk_dns_free_data(r); + while(--num_srv >= 0) + free((*res)[num_srv]); + free(*res); + *res = NULL; + if (invalid_tld) { + krb5_warnx(context, + "Domain lookup failed: " + "Realm %s needs immediate attention " + "see https://icann.org/namecollision", + realm); + return KRB5_KDC_UNREACH; + } + return krb5_enomem(context); + } + (*res)[num_srv++] = hi; + + hi->proto = proto_num; + + hi->def_port = def_port; + if (port != 0) + hi->port = port; + else + hi->port = rr->u.srv->port; + + strlcpy(hi->hostname, rr->u.srv->target, len + 1); + } + + *count = num_srv; + + rk_dns_free_data(r); + return 0; +} + + +struct krb5_krbhst_data { + const char *config_param; + const char *srv_label; + char *realm; + unsigned int flags; + int def_port; + int port; /* hardwired port number if != 0 */ +#define KD_CONFIG 0x0001 +#define KD_SRV_UDP 0x0002 +#define KD_SRV_TCP 0x0004 +#define KD_SITE_SRV_UDP 0x0008 +#define KD_SITE_SRV_TCP 0x0010 +#define KD_SRV_HTTP 0x0020 +#define KD_SRV_KKDCP 0x0040 +#define KD_FALLBACK 0x0080 +#define KD_CONFIG_EXISTS 0x0100 +#define KD_LARGE_MSG 0x0200 +#define KD_PLUGIN 0x0400 +#define KD_HOSTNAMES 0x0800 + krb5_error_code (*get_next)(krb5_context, struct krb5_krbhst_data *, + krb5_krbhst_info**); + + char *hostname; + char *sitename; + unsigned int fallback_count; + + struct krb5_krbhst_info *hosts, **index, **end; +}; + +static krb5_boolean +krbhst_empty(const struct krb5_krbhst_data *kd) +{ + return kd->index == &kd->hosts; +} + +/* + * Return the default protocol for the `kd' (either TCP or UDP) + */ + +static int +krbhst_get_default_proto(struct krb5_krbhst_data *kd) +{ + if (kd->flags & KD_LARGE_MSG) + return KRB5_KRBHST_TCP; + return KRB5_KRBHST_UDP; +} + +static int +krbhst_get_default_port(struct krb5_krbhst_data *kd) +{ + return kd->def_port; +} + +/* + * + */ + +KRB5_LIB_FUNCTION const char * KRB5_LIB_CALL +_krb5_krbhst_get_realm(krb5_krbhst_handle handle) +{ + return handle->realm; +} + +/* + * parse `spec' into a krb5_krbhst_info, defaulting the port to `def_port' + * and forcing it to `port' if port != 0 + */ + +static struct krb5_krbhst_info* +parse_hostspec(krb5_context context, struct krb5_krbhst_data *kd, + const char *spec, int def_port, int port) +{ + const char *p = spec, *q; + struct krb5_krbhst_info *hi; + + hi = calloc(1, sizeof(*hi) + strlen(spec)); + if(hi == NULL) + return NULL; + + hi->proto = krbhst_get_default_proto(kd); + + if(strncmp(p, "http://", 7) == 0){ + hi->proto = KRB5_KRBHST_HTTP; + p += 7; + } else if(strncmp(p, "http/", 5) == 0) { + hi->proto = KRB5_KRBHST_HTTP; + p += 5; + def_port = ntohs(krb5_getportbyname (context, "http", "tcp", 80)); + }else if(strncmp(p, "tcp/", 4) == 0){ + hi->proto = KRB5_KRBHST_TCP; + p += 4; + } else if(strncmp(p, "udp/", 4) == 0) { + hi->proto = KRB5_KRBHST_UDP; + p += 4; + } + + if (p[0] == '[' && (q = strchr(p, ']')) != NULL) { + /* if address looks like [foo:bar] or [foo:bar]: its a ipv6 + adress, strip of [] */ + memcpy(hi->hostname, &p[1], q - p - 1); + hi->hostname[q - p - 1] = '\0'; + p = q + 1; + /* get trailing : */ + if (p[0] == ':') + p++; + } else if(strsep_copy(&p, ":", hi->hostname, strlen(spec) + 1) < 0) { + /* copy everything before : */ + free(hi); + return NULL; + } + /* get rid of trailing /, and convert to lower case */ + hi->hostname[strcspn(hi->hostname, "/")] = '\0'; + strlwr(hi->hostname); + + hi->port = hi->def_port = def_port; + if(p != NULL && p[0]) { + char *end; + hi->port = strtol(p, &end, 0); + if(end == p) { + free(hi); + return NULL; + } + } + if (port) + hi->port = port; + return hi; +} + +KRB5_LIB_FUNCTION void KRB5_LIB_CALL +_krb5_free_krbhst_info(krb5_krbhst_info *hi) +{ + if (hi->ai != NULL) + freeaddrinfo(hi->ai); + free(hi); +} + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +_krb5_krbhost_info_move(krb5_context context, + krb5_krbhst_info *from, + krb5_krbhst_info **to) +{ + size_t hostnamelen = strlen(from->hostname); + /* trailing NUL is included in structure */ + *to = calloc(1, sizeof(**to) + hostnamelen); + if (*to == NULL) + return krb5_enomem(context); + + (*to)->proto = from->proto; + (*to)->port = from->port; + (*to)->def_port = from->def_port; + (*to)->ai = from->ai; + from->ai = NULL; + (*to)->next = NULL; + memcpy((*to)->hostname, from->hostname, hostnamelen + 1); + return 0; +} + + +static void +append_host_hostinfo(struct krb5_krbhst_data *kd, struct krb5_krbhst_info *host) +{ + struct krb5_krbhst_info *h; + + for(h = kd->hosts; h; h = h->next) + if(h->proto == host->proto && + h->port == host->port && + strcmp(h->hostname, host->hostname) == 0) { + _krb5_free_krbhst_info(host); + return; + } + /* + * We should always initialize kd->end in common_init(), but static + * analyzers may not see that we do, and the compiler might conclude + * there's UB here. + */ + if (kd->end) + *kd->end = host; + kd->end = &host->next; +} + +static krb5_error_code +append_host_string(krb5_context context, struct krb5_krbhst_data *kd, + const char *host, int def_port, int port) +{ + struct krb5_krbhst_info *hi; + + hi = parse_hostspec(context, kd, host, def_port, port); + if(hi == NULL) + return krb5_enomem(context); + + append_host_hostinfo(kd, hi); + return 0; +} + +/* + * return a readable representation of `host' in `hostname, hostlen' + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_krbhst_format_string(krb5_context context, const krb5_krbhst_info *host, + char *hostname, size_t hostlen) +{ + const char *proto = ""; + if(host->proto == KRB5_KRBHST_TCP) + proto = "tcp/"; + else if(host->proto == KRB5_KRBHST_HTTP) + proto = "http://"; + if (host->port != host->def_port) + snprintf(hostname, hostlen, "%s%s:%d", proto, host->hostname, (int)host->port); + else + snprintf(hostname, hostlen, "%s%s", proto, host->hostname); + return 0; +} + +/* + * create a getaddrinfo `hints' based on `proto' + */ + +static void +make_hints(struct addrinfo *hints, int proto) +{ + memset(hints, 0, sizeof(*hints)); + hints->ai_family = AF_UNSPEC; + switch(proto) { + case KRB5_KRBHST_UDP : + hints->ai_socktype = SOCK_DGRAM; + break; + case KRB5_KRBHST_HTTP : + case KRB5_KRBHST_TCP : + hints->ai_socktype = SOCK_STREAM; + break; + } +} + +/** + * Return an `struct addrinfo *' for a KDC host. + * + * Returns an the struct addrinfo in in that corresponds to the + * information in `host'. free:ing is handled by krb5_krbhst_free, so + * the returned ai must not be released. + * + * @ingroup krb5 + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_krbhst_get_addrinfo(krb5_context context, krb5_krbhst_info *host, + struct addrinfo **ai) +{ + int ret = 0; + + if (host->ai == NULL) { + struct addrinfo hints; + char portstr[NI_MAXSERV]; + + snprintf (portstr, sizeof(portstr), "%d", host->port); + make_hints(&hints, host->proto); + + ret = getaddrinfo(host->hostname, portstr, &hints, &host->ai); + if (ret) { + ret = krb5_eai_to_heim_errno(ret, errno); + goto out; + } + } + out: + *ai = host->ai; + return ret; +} + +static krb5_boolean +get_next(struct krb5_krbhst_data *kd, krb5_krbhst_info **host) +{ + struct krb5_krbhst_info *hi = kd ? *kd->index : NULL; + if(hi != NULL) { + *host = hi; + kd->index = &(*kd->index)->next; + return TRUE; + } + return FALSE; +} + +static void +srv_get_hosts(krb5_context context, struct krb5_krbhst_data *kd, + const char *sitename, const char *proto, const char *service) +{ + krb5_error_code ret; + krb5_krbhst_info **res; + int count, i; + + if (krb5_realm_is_lkdc(kd->realm)) + return; + + ret = srv_find_realm(context, &res, &count, kd->realm, "SRV", + sitename, proto, service, kd->port); + _krb5_debug(context, 2, "searching DNS for realm %s %s.%s -> %d", + kd->realm, proto, service, ret); + if (ret) + return; + for(i = 0; i < count; i++) + append_host_hostinfo(kd, res[i]); + free(res); +} + +/* + * read the configuration for `conf_string', defaulting to kd->def_port and + * forcing it to `kd->port' if kd->port != 0 + */ + +static void +config_get_hosts(krb5_context context, struct krb5_krbhst_data *kd, + const char *conf_string) +{ + int i; + char **hostlist; + hostlist = krb5_config_get_strings(context, NULL, + "realms", kd->realm, conf_string, NULL); + + _krb5_debug(context, 2, "configuration file for realm %s%s found", + kd->realm, hostlist ? "" : " not"); + + if(hostlist == NULL) + return; + kd->flags |= KD_CONFIG_EXISTS; + for(i = 0; hostlist && hostlist[i] != NULL; i++) + append_host_string(context, kd, hostlist[i], kd->def_port, kd->port); + + krb5_config_free_strings(hostlist); +} + +/* + * as a fallback, look for `serv_string.kd->realm' (typically + * kerberos.REALM, kerberos-1.REALM, ... + * `port' is the default port for the service, and `proto' the + * protocol + */ + +static krb5_error_code +fallback_get_hosts(krb5_context context, struct krb5_krbhst_data *kd, + const char *serv_string, int port, int proto) +{ + char *host = NULL; + int ret; + struct addrinfo *ai; + struct addrinfo hints; + char portstr[NI_MAXSERV]; + + ret = krb5_config_get_bool_default(context, NULL, KRB5_FALLBACK_DEFAULT, + "libdefaults", "use_fallback", NULL); + if (!ret) { + kd->flags |= KD_FALLBACK; + return 0; + } + + _krb5_debug(context, 2, "fallback lookup %d for realm %s (service %s)", + kd->fallback_count, kd->realm, serv_string); + + /* + * Don't try forever in case the DNS server keep returning us + * entries (like wildcard entries or the .nu TLD) + * + * Also don't try LKDC realms since fallback wont work on them at all. + */ + if(kd->fallback_count >= 5 || krb5_realm_is_lkdc(kd->realm)) { + kd->flags |= KD_FALLBACK; + return 0; + } + + if(kd->fallback_count == 0) + ret = asprintf(&host, "%s.%s.", serv_string, kd->realm); + else + ret = asprintf(&host, "%s-%d.%s.", + serv_string, kd->fallback_count, kd->realm); + + if (ret < 0 || host == NULL) + return krb5_enomem(context); + + make_hints(&hints, proto); + snprintf(portstr, sizeof(portstr), "%d", port); + ret = getaddrinfo(host, portstr, &hints, &ai); + if (ret) { + /* no more hosts, so we're done here */ + free(host); + kd->flags |= KD_FALLBACK; + } else { + struct krb5_krbhst_info *hi; + size_t hostlen; + + /* Check for ICANN gTLD Name Collision address (127.0.53.53) */ + if (ai->ai_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr; + if (sin->sin_addr.s_addr == htonl(0x7f003535)) { + krb5_warnx(context, + "Fallback lookup failed: " + "Realm %s needs immediate attention " + "see https://icann.org/namecollision", + kd->realm); + free(host); + freeaddrinfo(ai); + return KRB5_KDC_UNREACH; + } + } + + hostlen = strlen(host); + hi = calloc(1, sizeof(*hi) + hostlen); + if(hi == NULL) { + free(host); + freeaddrinfo(ai); + return krb5_enomem(context); + } + + hi->proto = proto; + hi->port = hi->def_port = port; + hi->ai = ai; + memmove(hi->hostname, host, hostlen); + hi->hostname[hostlen] = '\0'; + free(host); + append_host_hostinfo(kd, hi); + kd->fallback_count++; + } + return 0; +} + +/* + * Fetch hosts from plugin + */ + +static krb5_error_code +add_plugin_host(struct krb5_krbhst_data *kd, + const char *host, + const char *port, + int portnum, + int proto) +{ + struct krb5_krbhst_info *hi; + struct addrinfo hints, *ai; + size_t hostlen; + int ret; + + make_hints(&hints, proto); + ret = getaddrinfo(host, port, &hints, &ai); + if (ret) + return 0; + + hostlen = strlen(host); + + hi = calloc(1, sizeof(*hi) + hostlen); + if (hi == NULL) { + freeaddrinfo(ai); + return ENOMEM; + } + + hi->proto = proto; + hi->port = hi->def_port = portnum; + hi->ai = ai; + memmove(hi->hostname, host, hostlen); + hi->hostname[hostlen] = '\0'; + append_host_hostinfo(kd, hi); + + return 0; +} + +static krb5_error_code +add_locate(void *ctx, int type, struct sockaddr *addr) +{ + struct krb5_krbhst_data *kd = ctx; + char host[NI_MAXHOST], port[NI_MAXSERV]; + socklen_t socklen; + krb5_error_code ret; + int proto, portnum; + + socklen = socket_sockaddr_size(addr); + portnum = socket_get_port(addr); + + ret = getnameinfo(addr, socklen, host, sizeof(host), port, sizeof(port), + NI_NUMERICHOST|NI_NUMERICSERV); + if (ret != 0) + return 0; + + if (kd->port) + snprintf(port, sizeof(port), "%d", kd->port); + else if (atoi(port) == 0) + snprintf(port, sizeof(port), "%d", krbhst_get_default_port(kd)); + + proto = krbhst_get_default_proto(kd); + + ret = add_plugin_host(kd, host, port, portnum, proto); + if (ret) + return ret; + + /* + * This is really kind of broken and should be solved a different + * way, some sites block UDP, and we don't, in the general case, + * fall back to TCP, that should also be done. But since that + * should require us to invert the whole "find kdc" stack, let put + * this in for now. + */ + + if (proto == KRB5_KRBHST_UDP) { + ret = add_plugin_host(kd, host, port, portnum, KRB5_KRBHST_TCP); + if (ret) + return ret; + } + + return 0; +} + +struct plctx { + enum locate_service_type type; + struct krb5_krbhst_data *kd; + unsigned long flags; +}; + +static KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +plcallback(krb5_context context, + const void *plug, void *plugctx, void *userctx) +{ + const krb5plugin_service_locate_ftable *locate = plug; + struct plctx *plctx = userctx; + + if (locate->minor_version >= KRB5_PLUGIN_LOCATE_VERSION_2) + return locate->lookup(plugctx, plctx->flags, plctx->type, plctx->kd->realm, 0, 0, add_locate, plctx->kd); + + if (plctx->flags & KRB5_PLF_ALLOW_HOMEDIR) + return locate->old_lookup(plugctx, plctx->type, plctx->kd->realm, 0, 0, add_locate, plctx->kd); + + return KRB5_PLUGIN_NO_HANDLE; +} + +static const char *const locate_plugin_deps[] = { "krb5", NULL }; + +static const struct heim_plugin_data +locate_plugin_data = { + "krb5", + KRB5_PLUGIN_LOCATE, + KRB5_PLUGIN_LOCATE_VERSION_0, + locate_plugin_deps, + krb5_get_instance +}; + +static void +plugin_get_hosts(krb5_context context, + struct krb5_krbhst_data *kd, + enum locate_service_type type) +{ + struct plctx ctx = { type, kd, 0 }; + + if (_krb5_homedir_access(context)) + ctx.flags |= KRB5_PLF_ALLOW_HOMEDIR; + + _krb5_plugin_run_f(context, &locate_plugin_data, + 0, &ctx, plcallback); +} + +/* + * + */ + +static void +hostnames_get_hosts(krb5_context context, + struct krb5_krbhst_data *kd, + const char *type) +{ + kd->flags |= KD_HOSTNAMES; + if (kd->hostname) + append_host_string(context, kd, kd->hostname, kd->def_port, kd->port); +} + + +/* + * + */ + +static krb5_error_code +kdc_get_next(krb5_context context, + struct krb5_krbhst_data *kd, + krb5_krbhst_info **host) +{ + krb5_error_code ret; + + if ((kd->flags & KD_HOSTNAMES) == 0) { + hostnames_get_hosts(context, kd, "kdc"); + if(get_next(kd, host)) + return 0; + } + + if ((kd->flags & KD_PLUGIN) == 0) { + plugin_get_hosts(context, kd, locate_service_kdc); + kd->flags |= KD_PLUGIN; + if(get_next(kd, host)) + return 0; + } + + if((kd->flags & KD_CONFIG) == 0) { + config_get_hosts(context, kd, kd->config_param); + kd->flags |= KD_CONFIG; + if(get_next(kd, host)) + return 0; + } + + if (kd->flags & KD_CONFIG_EXISTS) { + _krb5_debug(context, 1, + "Configuration exists for realm %s, wont go to DNS", + kd->realm); + return KRB5_KDC_UNREACH; + } + + if(context->srv_lookup) { + if(kd->sitename && (kd->flags & KD_SITE_SRV_TCP) == 0) { + srv_get_hosts(context, kd, kd->sitename, "tcp", "kerberos"); + kd->flags |= KD_SITE_SRV_TCP; + if(get_next(kd, host)) + return 0; + } + + if((kd->flags & KD_SRV_UDP) == 0 && (kd->flags & KD_LARGE_MSG) == 0) { + srv_get_hosts(context, kd, NULL, "udp", kd->srv_label); + kd->flags |= KD_SRV_UDP; + if(get_next(kd, host)) + return 0; + } + + if((kd->flags & KD_SRV_TCP) == 0) { + srv_get_hosts(context, kd, NULL, "tcp", kd->srv_label); + kd->flags |= KD_SRV_TCP; + if(get_next(kd, host)) + return 0; + } + if((kd->flags & KD_SRV_HTTP) == 0) { + srv_get_hosts(context, kd, NULL, "http", kd->srv_label); + kd->flags |= KD_SRV_HTTP; + if(get_next(kd, host)) + return 0; + } + } + + while((kd->flags & KD_FALLBACK) == 0) { + ret = fallback_get_hosts(context, kd, "kerberos", + kd->def_port, + krbhst_get_default_proto(kd)); + if(ret) + return ret; + if(get_next(kd, host)) + return 0; + } + + _krb5_debug(context, 0, "No KDC entries found for %s", kd->realm); + + return KRB5_KDC_UNREACH; /* XXX */ +} + +static krb5_error_code +admin_get_next(krb5_context context, + struct krb5_krbhst_data *kd, + krb5_krbhst_info **host) +{ + krb5_error_code ret; + + if ((kd->flags & KD_PLUGIN) == 0) { + plugin_get_hosts(context, kd, locate_service_kadmin); + kd->flags |= KD_PLUGIN; + if(get_next(kd, host)) + return 0; + } + + if((kd->flags & KD_CONFIG) == 0) { + config_get_hosts(context, kd, kd->config_param); + kd->flags |= KD_CONFIG; + if(get_next(kd, host)) + return 0; + } + + if (kd->flags & KD_CONFIG_EXISTS) { + _krb5_debug(context, 1, + "Configuration exists for realm %s, wont go to DNS", + kd->realm); + return KRB5_KDC_UNREACH; + } + + if(context->srv_lookup) { + if((kd->flags & KD_SRV_TCP) == 0) { + srv_get_hosts(context, kd, NULL, "tcp", kd->srv_label); + kd->flags |= KD_SRV_TCP; + if(get_next(kd, host)) + return 0; + } + } + + if (krbhst_empty(kd) + && (kd->flags & KD_FALLBACK) == 0) { + ret = fallback_get_hosts(context, kd, "kerberos", + kd->def_port, + krbhst_get_default_proto(kd)); + if(ret) + return ret; + kd->flags |= KD_FALLBACK; + if(get_next(kd, host)) + return 0; + } + + _krb5_debug(context, 0, "No admin entries found for realm %s", kd->realm); + + return KRB5_KDC_UNREACH; /* XXX */ +} + +static krb5_error_code +kpasswd_get_next(krb5_context context, + struct krb5_krbhst_data *kd, + krb5_krbhst_info **host) +{ + krb5_error_code ret; + + if ((kd->flags & KD_PLUGIN) == 0) { + plugin_get_hosts(context, kd, locate_service_kpasswd); + kd->flags |= KD_PLUGIN; + if(get_next(kd, host)) + return 0; + } + + if((kd->flags & KD_CONFIG) == 0) { + config_get_hosts(context, kd, kd->config_param); + kd->flags |= KD_CONFIG; + if(get_next(kd, host)) + return 0; + } + + if (kd->flags & KD_CONFIG_EXISTS) { + _krb5_debug(context, 1, + "Configuration exists for realm %s, wont go to DNS", + kd->realm); + return KRB5_KDC_UNREACH; + } + + if(context->srv_lookup) { + if((kd->flags & KD_SRV_UDP) == 0) { + srv_get_hosts(context, kd, NULL, "udp", kd->srv_label); + kd->flags |= KD_SRV_UDP; + if(get_next(kd, host)) + return 0; + } + if((kd->flags & KD_SRV_TCP) == 0) { + srv_get_hosts(context, kd, NULL, "tcp", kd->srv_label); + kd->flags |= KD_SRV_TCP; + if(get_next(kd, host)) + return 0; + } + } + + /* no matches -> try admin */ + + if (krbhst_empty(kd)) { + kd->flags = 0; + kd->port = kd->def_port; + kd->get_next = admin_get_next; + ret = (*kd->get_next)(context, kd, host); + if (ret == 0) + (*host)->proto = krbhst_get_default_proto(kd); + return ret; + } + + _krb5_debug(context, 0, "No kpasswd entries found for realm %s", kd->realm); + + return KRB5_KDC_UNREACH; +} + +static void KRB5_CALLCONV +krbhost_dealloc(void *ptr) +{ + struct krb5_krbhst_data *handle = (struct krb5_krbhst_data *)ptr; + krb5_krbhst_info *h, *next; + + for (h = handle->hosts; h != NULL; h = next) { + next = h->next; + _krb5_free_krbhst_info(h); + } + if (handle->hostname) + free(handle->hostname); + if (handle->sitename) + free(handle->sitename); + + free(handle->realm); +} + +static struct krb5_krbhst_data* +common_init(krb5_context context, + const char *config_param, + const char *srv_label, + const char *service, + const char *realm, + int flags) +{ + struct krb5_krbhst_data *kd; + + if ((kd = heim_alloc(sizeof(*kd), "krbhst-context", krbhost_dealloc)) == NULL) + return NULL; + + if((kd->realm = strdup(realm)) == NULL) { + heim_release(kd); + return NULL; + } + + kd->config_param = config_param; + kd->srv_label = srv_label; + + _krb5_debug(context, 2, "Trying to find service %s for realm %s flags %x", + service, realm, flags); + + /* For 'realms' without a . do not even think of going to DNS */ + if (!strchr(realm, '.')) + kd->flags |= KD_CONFIG_EXISTS; + + if (flags & KRB5_KRBHST_FLAGS_LARGE_MSG) + kd->flags |= KD_LARGE_MSG; + kd->end = kd->index = &kd->hosts; + return kd; +} + +/* + * initialize `handle' to look for hosts of type `type' in realm `realm' + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_krbhst_init(krb5_context context, + const char *realm, + unsigned int type, + krb5_krbhst_handle *handle) +{ + return krb5_krbhst_init_flags(context, realm, type, 0, handle); +} + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_krbhst_init_flags(krb5_context context, + const char *realm, + unsigned int type, + int flags, + krb5_krbhst_handle *handle) +{ + struct krb5_krbhst_data *kd; + krb5_error_code (*next)(krb5_context, struct krb5_krbhst_data *, + krb5_krbhst_info **); + int def_port; + const char *config_param; + const char *srv_label; + const char *service; + + *handle = NULL; + + switch(type) { + case KRB5_KRBHST_KDC: + next = kdc_get_next; + def_port = ntohs(krb5_getportbyname(context, "kerberos", "udp", 88)); + config_param = "kdc"; + srv_label = "kerberos"; + service = "kdc"; + break; + case KRB5_KRBHST_ADMIN: + next = admin_get_next; + def_port = ntohs(krb5_getportbyname(context, "kerberos-adm", + "tcp", 749)); + config_param = "admin_server"; + srv_label = "kerberos-adm"; + service = "admin"; + break; + case KRB5_KRBHST_READONLY_ADMIN: + next = admin_get_next; + def_port = ntohs(krb5_getportbyname(context, "kerberos-adm", + "tcp", 749)); + config_param = "readonly_admin_server"; + srv_label = "kerberos-adm-readonly"; + service = "admin"; + break; + case KRB5_KRBHST_CHANGEPW: + next = kpasswd_get_next; + def_port = ntohs(krb5_getportbyname(context, "kpasswd", "udp", + KPASSWD_PORT)); + config_param = "kpasswd_server"; + srv_label = "kpasswd"; + service = "change_password"; + break; + case KRB5_KRBHST_TKTBRIDGEAP: + next = kdc_get_next; + def_port = ntohs(krb5_getportbyname(context, "kerberos", "tcp", 88)); + config_param = "tktbridgeap"; + srv_label = "kerberos-tkt-bridge"; + service = "kdc"; + break; + default: + krb5_set_error_message(context, ENOTTY, + N_("unknown krbhst type (%u)", ""), type); + return ENOTTY; + } + if((kd = common_init(context, config_param, srv_label, service, realm, + flags)) == NULL) + return ENOMEM; + kd->get_next = next; + kd->def_port = def_port; + *handle = kd; + return 0; +} + +/* + * return the next host information from `handle' in `host' + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_krbhst_next(krb5_context context, + krb5_krbhst_handle handle, + krb5_krbhst_info **host) +{ + if(get_next(handle, host)) + return 0; + + return (*handle->get_next)(context, handle, host); +} + +/* + * return the next host information from `handle' as a host name + * in `hostname' (or length `hostlen) + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_krbhst_next_as_string(krb5_context context, + krb5_krbhst_handle handle, + char *hostname, + size_t hostlen) +{ + krb5_error_code ret; + krb5_krbhst_info *host; + ret = krb5_krbhst_next(context, handle, &host); + if(ret) + return ret; + return krb5_krbhst_format_string(context, host, hostname, hostlen); +} + +/* + * + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_krbhst_set_hostname(krb5_context context, + krb5_krbhst_handle handle, + const char *hostname) +{ + if (handle->hostname) + free(handle->hostname); + handle->hostname = strdup(hostname); + if (handle->hostname == NULL) + return ENOMEM; + return 0; +} + +krb5_error_code KRB5_LIB_FUNCTION +krb5_krbhst_set_sitename(krb5_context context, + krb5_krbhst_handle handle, + const char *sitename) +{ + if (handle->sitename) + free(handle->sitename); + handle->sitename = strdup(sitename); + if (handle->sitename == NULL) + return krb5_enomem(context); + return 0; +} + +KRB5_LIB_FUNCTION void KRB5_LIB_CALL +krb5_krbhst_reset(krb5_context context, krb5_krbhst_handle handle) +{ + handle->index = &handle->hosts; +} + +KRB5_LIB_FUNCTION void KRB5_LIB_CALL +krb5_krbhst_free(krb5_context context, krb5_krbhst_handle handle) +{ + heim_release(handle); +} + +#ifndef HEIMDAL_SMALLER + +/* backwards compatibility ahead */ + +static krb5_error_code +gethostlist(krb5_context context, const char *realm, + unsigned int type, char ***hostlist) +{ + krb5_error_code ret; + int nhost = 0; + krb5_krbhst_handle handle; + char host[MAXHOSTNAMELEN]; + krb5_krbhst_info *hostinfo; + + ret = krb5_krbhst_init(context, realm, type, &handle); + if (ret) + return ret; + + while (krb5_krbhst_next(context, handle, &hostinfo) == 0) + nhost++; + if (nhost == 0) { + krb5_set_error_message(context, KRB5_KDC_UNREACH, + N_("No KDC found for realm %s", ""), realm); + krb5_krbhst_free(context, handle); + return KRB5_KDC_UNREACH; + } + *hostlist = calloc(nhost + 1, sizeof(**hostlist)); + if (*hostlist == NULL) { + krb5_krbhst_free(context, handle); + return krb5_enomem(context); + } + + krb5_krbhst_reset(context, handle); + nhost = 0; + while (krb5_krbhst_next_as_string(context, handle, + host, sizeof(host)) == 0) { + if (((*hostlist)[nhost++] = strdup(host)) == NULL) { + krb5_free_krbhst(context, *hostlist); + krb5_krbhst_free(context, handle); + return krb5_enomem(context); + } + } + (*hostlist)[nhost] = NULL; + krb5_krbhst_free(context, handle); + return 0; +} + +/* + * Return a malloced list of kadmin-hosts for `realm' in `hostlist' + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_get_krb_admin_hst(krb5_context context, + const krb5_realm *realm, + char ***hostlist) +{ + return gethostlist(context, *realm, KRB5_KRBHST_ADMIN, hostlist); +} + +/* + * Return a malloced list of writable kadmin-hosts for `realm' in `hostlist' + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_get_krb_readonly_admin_hst(krb5_context context, + const krb5_realm *realm, + char ***hostlist) +{ + return gethostlist(context, *realm, KRB5_KRBHST_READONLY_ADMIN, hostlist); +} + +/* + * return an malloced list of changepw-hosts for `realm' in `hostlist' + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_get_krb_changepw_hst (krb5_context context, + const krb5_realm *realm, + char ***hostlist) +{ + return gethostlist(context, *realm, KRB5_KRBHST_CHANGEPW, hostlist); +} + +/* + * return an malloced list of 524-hosts for `realm' in `hostlist' + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_get_krb524hst (krb5_context context, + const krb5_realm *realm, + char ***hostlist) +{ + return gethostlist(context, *realm, KRB5_KRBHST_KRB524, hostlist); +} + +/* + * return an malloced list of KDC's for `realm' in `hostlist' + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_get_krbhst (krb5_context context, + const krb5_realm *realm, + char ***hostlist) +{ + return gethostlist(context, *realm, KRB5_KRBHST_KDC, hostlist); +} + +/* + * free all the memory allocated in `hostlist' + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_free_krbhst (krb5_context context, + char **hostlist) +{ + char **p; + + for (p = hostlist; *p; ++p) + free (*p); + free (hostlist); + return 0; +} + +#endif /* HEIMDAL_SMALLER */ |