/* * Copyright (c) 2001 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * 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 RCSID("$Id$"); 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 krb5_error_code srv_find_realm(krb5_context context, krb5_krbhst_info ***res, int *count, const char *realm, const char *dns_type, const char *proto, const char *service) { char domain[1024]; struct dns_reply *r; struct resource_record *rr; int num_srv; int proto_num; short int port; proto_num = string_to_proto(proto); if(proto_num < 0) { krb5_set_error_string(context, "unknown protocol `%s'", proto); return EINVAL; } if(proto_num == KRB5_KRBHST_HTTP) port = krb5_getportbyname (context, "http", "tcp", 80); else port = krb5_getportbyname (context, service, proto, 88); snprintf(domain, sizeof(domain), "_%s._%s.%s.", service, proto, realm); r = dns_lookup(domain, dns_type); if(r == NULL) { *res = NULL; *count = 0; return KRB5_KDC_UNREACH; } for(num_srv = 0, rr = r->head; rr; rr = rr->next) if(rr->type == T_SRV) num_srv++; *res = malloc(num_srv * sizeof(**res)); if(*res == NULL) { krb5_set_error_string(context, "malloc: out of memory"); return ENOMEM; } dns_srv_order(r); for(num_srv = 0, rr = r->head; rr; rr = rr->next) if(rr->type == T_SRV) { krb5_krbhst_info *hi; hi = calloc(1, sizeof(*hi) + strlen(rr->u.srv->target)); if(hi == NULL) { while(--num_srv >= 0) free((*res)[num_srv]); free(*res); return ENOMEM; } (*res)[num_srv++] = hi; hi->proto = proto_num; if(ntohs(port) != rr->u.srv->port) hi->port = rr->u.srv->port; strcpy(hi->hostname, rr->u.srv->target); } *count = num_srv; dns_free_data(r); return 0; } struct krbhst_data { char *realm; unsigned int flags; #define KD_CONFIG 1 #define KD_SRV_UDP 2 #define KD_SRV_TCP 4 #define KD_SRV_HTTP 8 #define KD_FALLBACK 16 krb5_error_code (*get_next)(krb5_context, struct krbhst_data *, krb5_krbhst_info**); unsigned int fallback_count; struct krb5_krbhst_info *hosts, **index, **end; }; static struct krb5_krbhst_info* parse_hostspec(const char *spec) { const char *p = spec; struct krb5_krbhst_info *hi; hi = calloc(1, sizeof(*hi) + strlen(spec)); if(hi == NULL) return NULL; hi->proto = KRB5_KRBHST_UDP; 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; }else if(strncmp(p, "tcp/", 4) == 0){ hi->proto = KRB5_KRBHST_TCP; p += 4; } else if(strncmp(p, "udp/", 4) == 0) { p += 4; } if(strsep_copy(&p, ":", hi->hostname, strlen(spec) + 1) < 0) { free(hi); return NULL; } /* get rid of trailing /, and convert to lower case */ hi->hostname[strcspn(hi->hostname, "/")] = '\0'; strlwr(hi->hostname); if(p != NULL) { char *end; hi->port = strtol(p, &end, 0); if(end == p) { free(hi); return NULL; } } return hi; } static void append_host_hostinfo(struct 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) { free(host); return; } *kd->end = host; kd->end = &host->next; } static krb5_error_code append_host_string(struct krbhst_data *kd, const char *host) { struct krb5_krbhst_info *hi; hi = parse_hostspec(host); if(hi == NULL) return ENOMEM; append_host_hostinfo(kd, hi); return 0; } krb5_error_code krb5_krbhst_format_string(krb5_context context, krb5_krbhst_info *host, char *hostname, size_t hostlen) { const char *proto = ""; char portstr[7] = ""; if(host->proto == KRB5_KRBHST_TCP) proto = "tcp/"; else if(host->proto == KRB5_KRBHST_HTTP) proto = "http://"; if(host->port != 0) snprintf(portstr, sizeof(portstr), ":%d", host->port); snprintf(hostname, hostlen, "%s%s%s", proto, host->hostname, portstr); return 0; } static krb5_boolean get_next(struct krbhst_data *kd, krb5_krbhst_info **host) { struct krb5_krbhst_info *hi = *kd->index; if(hi != NULL) { *host = hi; kd->index = &(*kd->index)->next; return TRUE; } return FALSE; } static void srv_get_hosts(krb5_context context, struct krbhst_data *kd, const char *proto, const char *service) { krb5_krbhst_info **res; int count, i; srv_find_realm(context, &res, &count, kd->realm, "SRV", proto, service); for(i = 0; i < count; i++) append_host_hostinfo(kd, res[i]); free(res); } static void config_get_hosts(krb5_context context, struct krbhst_data *kd, const char *conf_string) { int i; char **hostlist; hostlist = krb5_config_get_strings(context, NULL, "realms", kd->realm, conf_string, NULL); if(hostlist == NULL) return; for(i = 0; hostlist && hostlist[i] != NULL; i++) append_host_string(kd, hostlist[i]); free(hostlist); } static void fallback_get_hosts(krb5_context context, struct krbhst_data *kd, const char *serv_string) { char *host; struct dns_reply *r; if(kd->fallback_count == 0) asprintf(&host, "%s.%s.", serv_string, kd->realm); else asprintf(&host, "%s-%d.%s.", serv_string, kd->fallback_count, kd->realm); r = dns_lookup(host, "A"); if(r == NULL) r = dns_lookup(host, "CNAME"); if(r == NULL) { /* no more hosts, so we're done here */ free(host); kd->flags |= KD_FALLBACK; } else { host[strlen(host) - 1] = '\0'; append_host_string(kd, host); kd->fallback_count++; } } static krb5_error_code kdc_get_next(krb5_context context, struct krbhst_data *kd, krb5_krbhst_info **host) { if((kd->flags & KD_CONFIG) == 0) { config_get_hosts(context, kd, "kdc"); kd->flags |= KD_CONFIG; if(get_next(kd, host)) return 0; } if(context->srv_lookup) { if((kd->flags & KD_SRV_UDP) == 0) { srv_get_hosts(context, kd, "udp", "kerberos"); kd->flags |= KD_SRV_UDP; if(get_next(kd, host)) return 0; } if((kd->flags & KD_SRV_TCP) == 0) { srv_get_hosts(context, kd, "tcp", "kerberos"); kd->flags |= KD_SRV_TCP; if(get_next(kd, host)) return 0; } if((kd->flags & KD_SRV_HTTP) == 0) { srv_get_hosts(context, kd, "http", "kerberos"); kd->flags |= KD_SRV_HTTP; if(get_next(kd, host)) return 0; } } while((kd->flags & KD_FALLBACK) == 0) { fallback_get_hosts(context, kd, "kerberos"); if(get_next(kd, host)) return 0; } return KRB5_KDC_UNREACH; /* XXX */ } static krb5_error_code admin_get_next(krb5_context context, struct krbhst_data *kd, krb5_krbhst_info **host) { if((kd->flags & KD_CONFIG) == 0) { config_get_hosts(context, kd, "admin_server"); kd->flags |= KD_CONFIG; if(get_next(kd, host)) return 0; } if(context->srv_lookup) { if((kd->flags & KD_SRV_TCP) == 0) { srv_get_hosts(context, kd, "tcp", "kerberos-adm"); kd->flags |= KD_SRV_TCP; if(get_next(kd, host)) return 0; } } /* try any kdc? */ return KRB5_KDC_UNREACH; } static krb5_error_code kpasswd_get_next(krb5_context context, struct krbhst_data *kd, krb5_krbhst_info **host) { if((kd->flags & KD_CONFIG) == 0) { config_get_hosts(context, kd, "kpasswd_server"); if(get_next(kd, host)) return 0; } if(context->srv_lookup) { if((kd->flags & KD_SRV_UDP) == 0) { srv_get_hosts(context, kd, "udp", "kpasswd"); kd->flags |= KD_SRV_UDP; if(get_next(kd, host)) return 0; } } /* try admin server? */ return KRB5_KDC_UNREACH; /* XXX */ } static krb5_error_code krb524_get_next(krb5_context context, struct krbhst_data *kd, krb5_krbhst_info **host) { if((kd->flags & KD_CONFIG) == 0) { config_get_hosts(context, kd, "krb524_server"); if(get_next(kd, host)) return 0; kd->flags |= KD_CONFIG; } /* try kdc? */ return KRB5_KDC_UNREACH; /* XXX */ } static struct krbhst_data* common_init(krb5_context context, const char *realm) { struct krbhst_data *kd; if((kd = calloc(1, sizeof(*kd))) == NULL) return NULL; if((kd->realm = strdup(realm)) == NULL) return NULL; kd->end = kd->index = &kd->hosts; return kd; } krb5_error_code krb5_krbhst_init(krb5_context context, const char *realm, unsigned int type, void **handle) { struct krbhst_data *kd; krb5_error_code (*get_next)(krb5_context, struct krbhst_data *, krb5_krbhst_info **); switch(type) { case KRB5_KRBHST_KDC: get_next = kdc_get_next; break; case KRB5_KRBHST_ADMIN: get_next = admin_get_next; break; case KRB5_KRBHST_CHANGEPW: get_next = kpasswd_get_next; break; case KRB5_KRBHST_KRB524: get_next = krb524_get_next; break; default: krb5_set_error_string(context, "unknown krbhst type (%u)", type); return ENOTTY; } if((kd = common_init(context, realm)) == NULL) return ENOMEM; kd->get_next = get_next; *handle = kd; return 0; } krb5_error_code krb5_krbhst_next(krb5_context context, void *handle, krb5_krbhst_info **host) { struct krbhst_data *kd = handle; if(get_next(kd, host)) return 0; return (*kd->get_next)(context, kd, host); } krb5_error_code krb5_krbhst_next_as_string(krb5_context context, void *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); } void krb5_krbhst_reset(krb5_context context, void *handle) { struct krbhst_data *kd = handle; kd->index = &kd->hosts; } void krb5_krbhst_free(krb5_context context, void *handle) { struct krbhst_data *kd = handle; free(kd->realm); free(handle); } /* 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; void *handle; char host[MAXHOSTNAMELEN]; krb5_krbhst_info *hostinfo; ret = krb5_krbhst_init(context, realm, type, &handle); while(krb5_krbhst_next(context, handle, &hostinfo) == 0) nhost++; if(nhost == 0) return KRB5_KDC_UNREACH; *hostlist = calloc(nhost + 1, sizeof(**hostlist)); if(*hostlist == NULL) { krb5_krbhst_free(context, handle); return ENOMEM; } 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 ENOMEM; } } (*hostlist)[nhost++] = NULL; krb5_krbhst_free(context, handle); return 0; } krb5_error_code krb5_get_krb_admin_hst (krb5_context context, const krb5_realm *realm, char ***hostlist) { return gethostlist(context, *realm, KRB5_KRBHST_ADMIN, hostlist); } krb5_error_code krb5_get_krb_changepw_hst (krb5_context context, const krb5_realm *realm, char ***hostlist) { return gethostlist(context, *realm, KRB5_KRBHST_CHANGEPW, hostlist); } krb5_error_code krb5_get_krb524hst (krb5_context context, const krb5_realm *realm, char ***hostlist) { return gethostlist(context, *realm, KRB5_KRBHST_KRB524, hostlist); } krb5_error_code krb5_get_krbhst (krb5_context context, const krb5_realm *realm, char ***hostlist) { return gethostlist(context, *realm, KRB5_KRBHST_KDC, hostlist); } krb5_error_code krb5_free_krbhst (krb5_context context, char **hostlist) { char **p; for (p = hostlist; *p; ++p) free (*p); free (hostlist); return 0; } #define TEST #ifdef TEST int main(int argc, char **argv) { int i; krb5_context context; krb5_init_context (&context); for(i = 1; i < argc; i++) { void *handle; char host[MAXHOSTNAMELEN]; krb5_krbhst_init(context, argv[i], KRB5_KRBHST_KDC, &handle); while(krb5_krbhst_next_as_string(context, handle, host, sizeof(host)) == 0) printf("%s\n", host); krb5_krbhst_reset(context, handle); printf("----\n"); while(krb5_krbhst_next_as_string(context, handle, host, sizeof(host)) == 0) printf("%s\n", host); krb5_krbhst_free(context, handle); } return 0; } #endif