155682Smarkm/*
2178825Sdfr * Copyright (c) 2001 - 2003 Kungliga Tekniska H�gskolan
355682Smarkm * (Royal Institute of Technology, Stockholm, Sweden).
455682Smarkm * All rights reserved.
555682Smarkm *
655682Smarkm * Redistribution and use in source and binary forms, with or without
755682Smarkm * modification, are permitted provided that the following conditions
855682Smarkm * are met:
955682Smarkm *
1055682Smarkm * 1. Redistributions of source code must retain the above copyright
1155682Smarkm *    notice, this list of conditions and the following disclaimer.
1255682Smarkm *
1355682Smarkm * 2. Redistributions in binary form must reproduce the above copyright
1455682Smarkm *    notice, this list of conditions and the following disclaimer in the
1555682Smarkm *    documentation and/or other materials provided with the distribution.
1655682Smarkm *
1755682Smarkm * 3. Neither the name of the Institute nor the names of its contributors
1855682Smarkm *    may be used to endorse or promote products derived from this software
1955682Smarkm *    without specific prior written permission.
2055682Smarkm *
2155682Smarkm * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
2255682Smarkm * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2355682Smarkm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2455682Smarkm * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
2555682Smarkm * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2655682Smarkm * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2755682Smarkm * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2855682Smarkm * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2955682Smarkm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3055682Smarkm * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3155682Smarkm * SUCH DAMAGE.
3255682Smarkm */
3355682Smarkm
3455682Smarkm#include "krb5_locl.h"
3555682Smarkm#include <resolve.h>
36178825Sdfr#include "locate_plugin.h"
3755682Smarkm
38178825SdfrRCSID("$Id: krbhst.c 21457 2007-07-10 12:53:25Z lha $");
3955682Smarkm
4090926Snectarstatic int
4190926Snectarstring_to_proto(const char *string)
4290926Snectar{
4390926Snectar    if(strcasecmp(string, "udp") == 0)
4490926Snectar	return KRB5_KRBHST_UDP;
4590926Snectar    else if(strcasecmp(string, "tcp") == 0)
4690926Snectar	return KRB5_KRBHST_TCP;
4790926Snectar    else if(strcasecmp(string, "http") == 0)
4890926Snectar	return KRB5_KRBHST_HTTP;
4990926Snectar    return -1;
5090926Snectar}
5190926Snectar
5255682Smarkm/*
5390926Snectar * set `res' and `count' to the result of looking up SRV RR in DNS for
5490926Snectar * `proto', `proto', `realm' using `dns_type'.
5590926Snectar * if `port' != 0, force that port number
5655682Smarkm */
5755682Smarkm
5890926Snectarstatic krb5_error_code
5990926Snectarsrv_find_realm(krb5_context context, krb5_krbhst_info ***res, int *count,
6090926Snectar	       const char *realm, const char *dns_type,
6190926Snectar	       const char *proto, const char *service, int port)
6255682Smarkm{
6390926Snectar    char domain[1024];
6490926Snectar    struct dns_reply *r;
6590926Snectar    struct resource_record *rr;
6690926Snectar    int num_srv;
6790926Snectar    int proto_num;
6890926Snectar    int def_port;
6955682Smarkm
70178825Sdfr    *res = NULL;
71178825Sdfr    *count = 0;
72178825Sdfr
7390926Snectar    proto_num = string_to_proto(proto);
7490926Snectar    if(proto_num < 0) {
7590926Snectar	krb5_set_error_string(context, "unknown protocol `%s'", proto);
7690926Snectar	return EINVAL;
7790926Snectar    }
7890926Snectar
7990926Snectar    if(proto_num == KRB5_KRBHST_HTTP)
8090926Snectar	def_port = ntohs(krb5_getportbyname (context, "http", "tcp", 80));
8190926Snectar    else if(port == 0)
8290926Snectar	def_port = ntohs(krb5_getportbyname (context, service, proto, 88));
8390926Snectar    else
8490926Snectar	def_port = port;
8590926Snectar
8690926Snectar    snprintf(domain, sizeof(domain), "_%s._%s.%s.", service, proto, realm);
8790926Snectar
8890926Snectar    r = dns_lookup(domain, dns_type);
89178825Sdfr    if(r == NULL)
9090926Snectar	return KRB5_KDC_UNREACH;
9190926Snectar
9290926Snectar    for(num_srv = 0, rr = r->head; rr; rr = rr->next)
9390926Snectar	if(rr->type == T_SRV)
9490926Snectar	    num_srv++;
9590926Snectar
9690926Snectar    *res = malloc(num_srv * sizeof(**res));
9790926Snectar    if(*res == NULL) {
9890926Snectar	dns_free_data(r);
9990926Snectar	krb5_set_error_string(context, "malloc: out of memory");
10055682Smarkm	return ENOMEM;
10178527Sassar    }
10290926Snectar
10390926Snectar    dns_srv_order(r);
10490926Snectar
10590926Snectar    for(num_srv = 0, rr = r->head; rr; rr = rr->next)
10690926Snectar	if(rr->type == T_SRV) {
10790926Snectar	    krb5_krbhst_info *hi;
108120945Snectar	    size_t len = strlen(rr->u.srv->target);
109120945Snectar
110120945Snectar	    hi = calloc(1, sizeof(*hi) + len);
11190926Snectar	    if(hi == NULL) {
11290926Snectar		dns_free_data(r);
11390926Snectar		while(--num_srv >= 0)
11490926Snectar		    free((*res)[num_srv]);
11590926Snectar		free(*res);
116178825Sdfr		*res = NULL;
11790926Snectar		return ENOMEM;
11890926Snectar	    }
11990926Snectar	    (*res)[num_srv++] = hi;
12090926Snectar
12190926Snectar	    hi->proto = proto_num;
12290926Snectar
12390926Snectar	    hi->def_port = def_port;
12490926Snectar	    if (port != 0)
12590926Snectar		hi->port = port;
12690926Snectar	    else
12790926Snectar		hi->port = rr->u.srv->port;
12890926Snectar
129120945Snectar	    strlcpy(hi->hostname, rr->u.srv->target, len + 1);
13078527Sassar	}
13190926Snectar
13290926Snectar    *count = num_srv;
13390926Snectar
13490926Snectar    dns_free_data(r);
13555682Smarkm    return 0;
13655682Smarkm}
13755682Smarkm
13890926Snectar
13990926Snectarstruct krb5_krbhst_data {
14090926Snectar    char *realm;
14190926Snectar    unsigned int flags;
14290926Snectar    int def_port;
14390926Snectar    int port;			/* hardwired port number if != 0 */
144178825Sdfr#define KD_CONFIG		 1
145178825Sdfr#define KD_SRV_UDP		 2
146178825Sdfr#define KD_SRV_TCP		 4
147178825Sdfr#define KD_SRV_HTTP		 8
148178825Sdfr#define KD_FALLBACK		16
149178825Sdfr#define KD_CONFIG_EXISTS	32
150178825Sdfr#define KD_LARGE_MSG		64
151178825Sdfr#define KD_PLUGIN	       128
15290926Snectar    krb5_error_code (*get_next)(krb5_context, struct krb5_krbhst_data *,
15390926Snectar				krb5_krbhst_info**);
15490926Snectar
15590926Snectar    unsigned int fallback_count;
15690926Snectar
15790926Snectar    struct krb5_krbhst_info *hosts, **index, **end;
15890926Snectar};
15990926Snectar
16090926Snectarstatic krb5_boolean
16190926Snectarkrbhst_empty(const struct krb5_krbhst_data *kd)
16290926Snectar{
16390926Snectar    return kd->index == &kd->hosts;
16490926Snectar}
16590926Snectar
16672445Sassar/*
167178825Sdfr * Return the default protocol for the `kd' (either TCP or UDP)
168178825Sdfr */
169178825Sdfr
170178825Sdfrstatic int
171178825Sdfrkrbhst_get_default_proto(struct krb5_krbhst_data *kd)
172178825Sdfr{
173178825Sdfr    if (kd->flags & KD_LARGE_MSG)
174178825Sdfr	return KRB5_KRBHST_TCP;
175178825Sdfr    return KRB5_KRBHST_UDP;
176178825Sdfr}
177178825Sdfr
178178825Sdfr
179178825Sdfr/*
18090926Snectar * parse `spec' into a krb5_krbhst_info, defaulting the port to `def_port'
18190926Snectar * and forcing it to `port' if port != 0
18272445Sassar */
18372445Sassar
18490926Snectarstatic struct krb5_krbhst_info*
185178825Sdfrparse_hostspec(krb5_context context, struct krb5_krbhst_data *kd,
186178825Sdfr	       const char *spec, int def_port, int port)
18790926Snectar{
18890926Snectar    const char *p = spec;
18990926Snectar    struct krb5_krbhst_info *hi;
19090926Snectar
19190926Snectar    hi = calloc(1, sizeof(*hi) + strlen(spec));
19290926Snectar    if(hi == NULL)
19390926Snectar	return NULL;
19490926Snectar
195178825Sdfr    hi->proto = krbhst_get_default_proto(kd);
19690926Snectar
19790926Snectar    if(strncmp(p, "http://", 7) == 0){
19890926Snectar	hi->proto = KRB5_KRBHST_HTTP;
19990926Snectar	p += 7;
20090926Snectar    } else if(strncmp(p, "http/", 5) == 0) {
20190926Snectar	hi->proto = KRB5_KRBHST_HTTP;
20290926Snectar	p += 5;
20390926Snectar	def_port = ntohs(krb5_getportbyname (context, "http", "tcp", 80));
20490926Snectar    }else if(strncmp(p, "tcp/", 4) == 0){
20590926Snectar	hi->proto = KRB5_KRBHST_TCP;
20690926Snectar	p += 4;
20790926Snectar    } else if(strncmp(p, "udp/", 4) == 0) {
20890926Snectar	p += 4;
20990926Snectar    }
21090926Snectar
21190926Snectar    if(strsep_copy(&p, ":", hi->hostname, strlen(spec) + 1) < 0) {
21290926Snectar	free(hi);
21390926Snectar	return NULL;
21490926Snectar    }
21590926Snectar    /* get rid of trailing /, and convert to lower case */
21690926Snectar    hi->hostname[strcspn(hi->hostname, "/")] = '\0';
21790926Snectar    strlwr(hi->hostname);
21890926Snectar
21990926Snectar    hi->port = hi->def_port = def_port;
22090926Snectar    if(p != NULL) {
22190926Snectar	char *end;
22290926Snectar	hi->port = strtol(p, &end, 0);
22390926Snectar	if(end == p) {
22490926Snectar	    free(hi);
22590926Snectar	    return NULL;
22690926Snectar	}
22790926Snectar    }
22890926Snectar    if (port)
22990926Snectar	hi->port = port;
23090926Snectar    return hi;
23190926Snectar}
23290926Snectar
233178825Sdfrvoid
234178825Sdfr_krb5_free_krbhst_info(krb5_krbhst_info *hi)
23590926Snectar{
23690926Snectar    if (hi->ai != NULL)
23790926Snectar	freeaddrinfo(hi->ai);
23890926Snectar    free(hi);
23990926Snectar}
24090926Snectar
241178825Sdfrkrb5_error_code
242178825Sdfr_krb5_krbhost_info_move(krb5_context context,
243178825Sdfr			krb5_krbhst_info *from,
244178825Sdfr			krb5_krbhst_info **to)
245178825Sdfr{
246178825Sdfr    size_t hostnamelen = strlen(from->hostname);
247178825Sdfr    /* trailing NUL is included in structure */
248178825Sdfr    *to = calloc(1, sizeof(**to) + hostnamelen);
249178825Sdfr    if(*to == NULL) {
250178825Sdfr	krb5_set_error_string(context, "malloc - out of memory");
251178825Sdfr	return ENOMEM;
252178825Sdfr    }
253178825Sdfr
254178825Sdfr    (*to)->proto = from->proto;
255178825Sdfr    (*to)->port = from->port;
256178825Sdfr    (*to)->def_port = from->def_port;
257178825Sdfr    (*to)->ai = from->ai;
258178825Sdfr    from->ai = NULL;
259178825Sdfr    (*to)->next = NULL;
260178825Sdfr    memcpy((*to)->hostname, from->hostname, hostnamelen + 1);
261178825Sdfr    return 0;
262178825Sdfr}
263178825Sdfr
264178825Sdfr
26590926Snectarstatic void
26690926Snectarappend_host_hostinfo(struct krb5_krbhst_data *kd, struct krb5_krbhst_info *host)
26790926Snectar{
26890926Snectar    struct krb5_krbhst_info *h;
26990926Snectar
27090926Snectar    for(h = kd->hosts; h; h = h->next)
27190926Snectar	if(h->proto == host->proto &&
27290926Snectar	   h->port == host->port &&
27390926Snectar	   strcmp(h->hostname, host->hostname) == 0) {
274178825Sdfr	    _krb5_free_krbhst_info(host);
27590926Snectar	    return;
27690926Snectar	}
27790926Snectar    *kd->end = host;
27890926Snectar    kd->end = &host->next;
27990926Snectar}
28090926Snectar
28155682Smarkmstatic krb5_error_code
28290926Snectarappend_host_string(krb5_context context, struct krb5_krbhst_data *kd,
28390926Snectar		   const char *host, int def_port, int port)
28455682Smarkm{
28590926Snectar    struct krb5_krbhst_info *hi;
28655682Smarkm
287178825Sdfr    hi = parse_hostspec(context, kd, host, def_port, port);
28890926Snectar    if(hi == NULL)
28990926Snectar	return ENOMEM;
29055682Smarkm
29190926Snectar    append_host_hostinfo(kd, hi);
29290926Snectar    return 0;
29390926Snectar}
29490926Snectar
29590926Snectar/*
29690926Snectar * return a readable representation of `host' in `hostname, hostlen'
29790926Snectar */
29890926Snectar
299178825Sdfrkrb5_error_code KRB5_LIB_FUNCTION
30090926Snectarkrb5_krbhst_format_string(krb5_context context, const krb5_krbhst_info *host,
30190926Snectar			  char *hostname, size_t hostlen)
30290926Snectar{
30390926Snectar    const char *proto = "";
30490926Snectar    char portstr[7] = "";
30590926Snectar    if(host->proto == KRB5_KRBHST_TCP)
30690926Snectar	proto = "tcp/";
30790926Snectar    else if(host->proto == KRB5_KRBHST_HTTP)
30890926Snectar	proto = "http://";
30990926Snectar    if(host->port != host->def_port)
31090926Snectar	snprintf(portstr, sizeof(portstr), ":%d", host->port);
31190926Snectar    snprintf(hostname, hostlen, "%s%s%s", proto, host->hostname, portstr);
31290926Snectar    return 0;
31390926Snectar}
31490926Snectar
31590926Snectar/*
31690926Snectar * create a getaddrinfo `hints' based on `proto'
31790926Snectar */
31890926Snectar
31990926Snectarstatic void
32090926Snectarmake_hints(struct addrinfo *hints, int proto)
32190926Snectar{
32290926Snectar    memset(hints, 0, sizeof(*hints));
32390926Snectar    hints->ai_family = AF_UNSPEC;
32490926Snectar    switch(proto) {
32590926Snectar    case KRB5_KRBHST_UDP :
32690926Snectar	hints->ai_socktype = SOCK_DGRAM;
32790926Snectar	break;
32890926Snectar    case KRB5_KRBHST_HTTP :
32990926Snectar    case KRB5_KRBHST_TCP :
33090926Snectar	hints->ai_socktype = SOCK_STREAM;
33190926Snectar	break;
33255682Smarkm    }
33390926Snectar}
33455682Smarkm
33590926Snectar/*
33690926Snectar * return an `struct addrinfo *' in `ai' corresponding to the information
33790926Snectar * in `host'.  free:ing is handled by krb5_krbhst_free.
33890926Snectar */
33955682Smarkm
340178825Sdfrkrb5_error_code KRB5_LIB_FUNCTION
34190926Snectarkrb5_krbhst_get_addrinfo(krb5_context context, krb5_krbhst_info *host,
34290926Snectar			 struct addrinfo **ai)
34390926Snectar{
34490926Snectar    struct addrinfo hints;
34590926Snectar    char portstr[NI_MAXSERV];
34690926Snectar    int ret;
34790926Snectar
34890926Snectar    if (host->ai == NULL) {
34990926Snectar	make_hints(&hints, host->proto);
35090926Snectar	snprintf (portstr, sizeof(portstr), "%d", host->port);
35190926Snectar	ret = getaddrinfo(host->hostname, portstr, &hints, &host->ai);
35290926Snectar	if (ret)
35390926Snectar	    return krb5_eai_to_heim_errno(ret, errno);
35455682Smarkm    }
35590926Snectar    *ai = host->ai;
35655682Smarkm    return 0;
35755682Smarkm}
35855682Smarkm
35990926Snectarstatic krb5_boolean
36090926Snectarget_next(struct krb5_krbhst_data *kd, krb5_krbhst_info **host)
36190926Snectar{
36290926Snectar    struct krb5_krbhst_info *hi = *kd->index;
36390926Snectar    if(hi != NULL) {
36490926Snectar	*host = hi;
36590926Snectar	kd->index = &(*kd->index)->next;
36690926Snectar	return TRUE;
36790926Snectar    }
36890926Snectar    return FALSE;
36990926Snectar}
37090926Snectar
37190926Snectarstatic void
37290926Snectarsrv_get_hosts(krb5_context context, struct krb5_krbhst_data *kd,
373178825Sdfr	      const char *proto, const char *service)
37490926Snectar{
37590926Snectar    krb5_krbhst_info **res;
37690926Snectar    int count, i;
37790926Snectar
378178825Sdfr    if (srv_find_realm(context, &res, &count, kd->realm, "SRV", proto, service,
379178825Sdfr		       kd->port))
380178825Sdfr	return;
38190926Snectar    for(i = 0; i < count; i++)
38290926Snectar	append_host_hostinfo(kd, res[i]);
38390926Snectar    free(res);
38490926Snectar}
38590926Snectar
38655682Smarkm/*
38790926Snectar * read the configuration for `conf_string', defaulting to kd->def_port and
38890926Snectar * forcing it to `kd->port' if kd->port != 0
38955682Smarkm */
39055682Smarkm
39190926Snectarstatic void
39290926Snectarconfig_get_hosts(krb5_context context, struct krb5_krbhst_data *kd,
39390926Snectar		 const char *conf_string)
39490926Snectar{
39590926Snectar    int i;
39690926Snectar
39790926Snectar    char **hostlist;
39890926Snectar    hostlist = krb5_config_get_strings(context, NULL,
39990926Snectar				       "realms", kd->realm, conf_string, NULL);
40090926Snectar
40190926Snectar    if(hostlist == NULL)
40290926Snectar	return;
40390926Snectar    kd->flags |= KD_CONFIG_EXISTS;
40490926Snectar    for(i = 0; hostlist && hostlist[i] != NULL; i++)
40590926Snectar	append_host_string(context, kd, hostlist[i], kd->def_port, kd->port);
40690926Snectar
40790926Snectar    krb5_config_free_strings(hostlist);
40890926Snectar}
40990926Snectar
41090926Snectar/*
41190926Snectar * as a fallback, look for `serv_string.kd->realm' (typically
41290926Snectar * kerberos.REALM, kerberos-1.REALM, ...
41390926Snectar * `port' is the default port for the service, and `proto' the
41490926Snectar * protocol
41590926Snectar */
41690926Snectar
41755682Smarkmstatic krb5_error_code
41890926Snectarfallback_get_hosts(krb5_context context, struct krb5_krbhst_data *kd,
41990926Snectar		   const char *serv_string, int port, int proto)
42055682Smarkm{
42190926Snectar    char *host;
42290926Snectar    int ret;
42390926Snectar    struct addrinfo *ai;
42490926Snectar    struct addrinfo hints;
42590926Snectar    char portstr[NI_MAXSERV];
42690926Snectar
427178825Sdfr    /*
428178825Sdfr     * Don't try forever in case the DNS server keep returning us
429178825Sdfr     * entries (like wildcard entries or the .nu TLD)
430178825Sdfr     */
431178825Sdfr    if(kd->fallback_count >= 5) {
432178825Sdfr	kd->flags |= KD_FALLBACK;
433178825Sdfr	return 0;
434178825Sdfr    }
435178825Sdfr
43690926Snectar    if(kd->fallback_count == 0)
43790926Snectar	asprintf(&host, "%s.%s.", serv_string, kd->realm);
43890926Snectar    else
43990926Snectar	asprintf(&host, "%s-%d.%s.",
44090926Snectar		 serv_string, kd->fallback_count, kd->realm);
44190926Snectar
44290926Snectar    if (host == NULL)
44390926Snectar	return ENOMEM;
44490926Snectar
44590926Snectar    make_hints(&hints, proto);
44690926Snectar    snprintf(portstr, sizeof(portstr), "%d", port);
44790926Snectar    ret = getaddrinfo(host, portstr, &hints, &ai);
44890926Snectar    if (ret) {
44990926Snectar	/* no more hosts, so we're done here */
45090926Snectar	free(host);
45190926Snectar	kd->flags |= KD_FALLBACK;
45290926Snectar    } else {
45390926Snectar	struct krb5_krbhst_info *hi;
45490926Snectar	size_t hostlen = strlen(host);
45590926Snectar
45690926Snectar	hi = calloc(1, sizeof(*hi) + hostlen);
45790926Snectar	if(hi == NULL) {
45890926Snectar	    free(host);
45990926Snectar	    return ENOMEM;
46090926Snectar	}
46190926Snectar
46290926Snectar	hi->proto = proto;
46390926Snectar	hi->port  = hi->def_port = port;
46490926Snectar	hi->ai    = ai;
465178825Sdfr	memmove(hi->hostname, host, hostlen);
466178825Sdfr	hi->hostname[hostlen] = '\0';
46790926Snectar	free(host);
46890926Snectar	append_host_hostinfo(kd, hi);
46990926Snectar	kd->fallback_count++;
47090926Snectar    }
47190926Snectar    return 0;
47290926Snectar}
47390926Snectar
474178825Sdfr/*
475178825Sdfr * Fetch hosts from plugin
476178825Sdfr */
477178825Sdfr
478178825Sdfrstatic krb5_error_code
479178825Sdfradd_locate(void *ctx, int type, struct sockaddr *addr)
480178825Sdfr{
481178825Sdfr    struct krb5_krbhst_info *hi;
482178825Sdfr    struct krb5_krbhst_data *kd = ctx;
483178825Sdfr    char host[NI_MAXHOST], port[NI_MAXSERV];
484178825Sdfr    struct addrinfo hints, *ai;
485178825Sdfr    socklen_t socklen;
486178825Sdfr    size_t hostlen;
487178825Sdfr    int ret;
488178825Sdfr
489178825Sdfr    socklen = socket_sockaddr_size(addr);
490178825Sdfr
491178825Sdfr    ret = getnameinfo(addr, socklen, host, sizeof(host), port, sizeof(port),
492178825Sdfr		      NI_NUMERICHOST|NI_NUMERICSERV);
493178825Sdfr    if (ret != 0)
494178825Sdfr	return 0;
495178825Sdfr
496178825Sdfr    make_hints(&hints, krbhst_get_default_proto(kd));
497178825Sdfr    ret = getaddrinfo(host, port, &hints, &ai);
498178825Sdfr    if (ret)
499178825Sdfr	return 0;
500178825Sdfr
501178825Sdfr    hostlen = strlen(host);
502178825Sdfr
503178825Sdfr    hi = calloc(1, sizeof(*hi) + hostlen);
504178825Sdfr    if(hi == NULL)
505178825Sdfr	return ENOMEM;
506178825Sdfr
507178825Sdfr    hi->proto = krbhst_get_default_proto(kd);
508178825Sdfr    hi->port  = hi->def_port = socket_get_port(addr);
509178825Sdfr    hi->ai    = ai;
510178825Sdfr    memmove(hi->hostname, host, hostlen);
511178825Sdfr    hi->hostname[hostlen] = '\0';
512178825Sdfr    append_host_hostinfo(kd, hi);
513178825Sdfr
514178825Sdfr    return 0;
515178825Sdfr}
516178825Sdfr
517178825Sdfrstatic void
518178825Sdfrplugin_get_hosts(krb5_context context,
519178825Sdfr		 struct krb5_krbhst_data *kd,
520178825Sdfr		 enum locate_service_type type)
521178825Sdfr{
522178825Sdfr    struct krb5_plugin *list = NULL, *e;
523178825Sdfr    krb5_error_code ret;
524178825Sdfr
525178825Sdfr    ret = _krb5_plugin_find(context, PLUGIN_TYPE_DATA, "resolve", &list);
526178825Sdfr    if(ret != 0 || list == NULL)
527178825Sdfr	return;
528178825Sdfr
529178825Sdfr    kd->flags |= KD_CONFIG_EXISTS;
530178825Sdfr
531178825Sdfr    for (e = list; e != NULL; e = _krb5_plugin_get_next(e)) {
532178825Sdfr	krb5plugin_service_locate_ftable *service;
533178825Sdfr	void *ctx;
534178825Sdfr
535178825Sdfr	service = _krb5_plugin_get_symbol(e);
536178825Sdfr	if (service->minor_version != 0)
537178825Sdfr	    continue;
538178825Sdfr
539178825Sdfr	(*service->init)(context, &ctx);
540178825Sdfr	ret = (*service->lookup)(ctx, type, kd->realm, 0, 0, add_locate, kd);
541178825Sdfr	(*service->fini)(ctx);
542178825Sdfr	if (ret) {
543178825Sdfr	    krb5_set_error_string(context, "Plugin failed to lookup");
544178825Sdfr	    break;
545178825Sdfr	}
546178825Sdfr    }
547178825Sdfr    _krb5_plugin_free(list);
548178825Sdfr}
549178825Sdfr
550178825Sdfr/*
551178825Sdfr *
552178825Sdfr */
553178825Sdfr
55490926Snectarstatic krb5_error_code
55590926Snectarkdc_get_next(krb5_context context,
55690926Snectar	     struct krb5_krbhst_data *kd,
55790926Snectar	     krb5_krbhst_info **host)
55890926Snectar{
55955682Smarkm    krb5_error_code ret;
56055682Smarkm
561178825Sdfr    if ((kd->flags & KD_PLUGIN) == 0) {
562178825Sdfr	plugin_get_hosts(context, kd, locate_service_kdc);
563178825Sdfr	kd->flags |= KD_PLUGIN;
564178825Sdfr	if(get_next(kd, host))
565178825Sdfr	    return 0;
566178825Sdfr    }
567178825Sdfr
56890926Snectar    if((kd->flags & KD_CONFIG) == 0) {
56990926Snectar	config_get_hosts(context, kd, "kdc");
57090926Snectar	kd->flags |= KD_CONFIG;
57190926Snectar	if(get_next(kd, host))
57290926Snectar	    return 0;
57390926Snectar    }
57455682Smarkm
57590926Snectar    if (kd->flags & KD_CONFIG_EXISTS)
57690926Snectar	return KRB5_KDC_UNREACH; /* XXX */
57790926Snectar
57890926Snectar    if(context->srv_lookup) {
579178825Sdfr	if((kd->flags & KD_SRV_UDP) == 0 && (kd->flags & KD_LARGE_MSG) == 0) {
58090926Snectar	    srv_get_hosts(context, kd, "udp", "kerberos");
58190926Snectar	    kd->flags |= KD_SRV_UDP;
58290926Snectar	    if(get_next(kd, host))
58390926Snectar		return 0;
58455682Smarkm	}
58590926Snectar
58690926Snectar	if((kd->flags & KD_SRV_TCP) == 0) {
58790926Snectar	    srv_get_hosts(context, kd, "tcp", "kerberos");
58890926Snectar	    kd->flags |= KD_SRV_TCP;
58990926Snectar	    if(get_next(kd, host))
59090926Snectar		return 0;
59190926Snectar	}
59290926Snectar	if((kd->flags & KD_SRV_HTTP) == 0) {
59390926Snectar	    srv_get_hosts(context, kd, "http", "kerberos");
59490926Snectar	    kd->flags |= KD_SRV_HTTP;
59590926Snectar	    if(get_next(kd, host))
59690926Snectar		return 0;
59790926Snectar	}
59855682Smarkm    }
59955682Smarkm
60090926Snectar    while((kd->flags & KD_FALLBACK) == 0) {
60190926Snectar	ret = fallback_get_hosts(context, kd, "kerberos",
602178825Sdfr				 kd->def_port,
603178825Sdfr				 krbhst_get_default_proto(kd));
60490926Snectar	if(ret)
60555682Smarkm	    return ret;
60690926Snectar	if(get_next(kd, host))
60790926Snectar	    return 0;
60890926Snectar    }
60990926Snectar
61090926Snectar    return KRB5_KDC_UNREACH; /* XXX */
61190926Snectar}
61290926Snectar
61390926Snectarstatic krb5_error_code
61490926Snectaradmin_get_next(krb5_context context,
61590926Snectar	       struct krb5_krbhst_data *kd,
61690926Snectar	       krb5_krbhst_info **host)
61790926Snectar{
61890926Snectar    krb5_error_code ret;
61990926Snectar
620178825Sdfr    if ((kd->flags & KD_PLUGIN) == 0) {
621178825Sdfr	plugin_get_hosts(context, kd, locate_service_kadmin);
622178825Sdfr	kd->flags |= KD_PLUGIN;
623178825Sdfr	if(get_next(kd, host))
624178825Sdfr	    return 0;
625178825Sdfr    }
626178825Sdfr
62790926Snectar    if((kd->flags & KD_CONFIG) == 0) {
62890926Snectar	config_get_hosts(context, kd, "admin_server");
62990926Snectar	kd->flags |= KD_CONFIG;
63090926Snectar	if(get_next(kd, host))
63190926Snectar	    return 0;
63290926Snectar    }
63390926Snectar
63490926Snectar    if (kd->flags & KD_CONFIG_EXISTS)
63590926Snectar	return KRB5_KDC_UNREACH; /* XXX */
63690926Snectar
63790926Snectar    if(context->srv_lookup) {
63890926Snectar	if((kd->flags & KD_SRV_TCP) == 0) {
63990926Snectar	    srv_get_hosts(context, kd, "tcp", "kerberos-adm");
64090926Snectar	    kd->flags |= KD_SRV_TCP;
64190926Snectar	    if(get_next(kd, host))
64290926Snectar		return 0;
64355682Smarkm	}
64455682Smarkm    }
64590926Snectar
64690926Snectar    if (krbhst_empty(kd)
64790926Snectar	&& (kd->flags & KD_FALLBACK) == 0) {
64890926Snectar	ret = fallback_get_hosts(context, kd, "kerberos",
649178825Sdfr				 kd->def_port,
650178825Sdfr				 krbhst_get_default_proto(kd));
65190926Snectar	if(ret)
65290926Snectar	    return ret;
65390926Snectar	kd->flags |= KD_FALLBACK;
65490926Snectar	if(get_next(kd, host))
65590926Snectar	    return 0;
65690926Snectar    }
65790926Snectar
65890926Snectar    return KRB5_KDC_UNREACH;	/* XXX */
65990926Snectar}
66090926Snectar
66190926Snectarstatic krb5_error_code
66290926Snectarkpasswd_get_next(krb5_context context,
66390926Snectar		 struct krb5_krbhst_data *kd,
66490926Snectar		 krb5_krbhst_info **host)
66590926Snectar{
666102644Snectar    krb5_error_code ret;
667102644Snectar
668178825Sdfr    if ((kd->flags & KD_PLUGIN) == 0) {
669178825Sdfr	plugin_get_hosts(context, kd, locate_service_kpasswd);
670178825Sdfr	kd->flags |= KD_PLUGIN;
671178825Sdfr	if(get_next(kd, host))
672178825Sdfr	    return 0;
673178825Sdfr    }
674178825Sdfr
67590926Snectar    if((kd->flags & KD_CONFIG) == 0) {
67690926Snectar	config_get_hosts(context, kd, "kpasswd_server");
677178825Sdfr	kd->flags |= KD_CONFIG;
67890926Snectar	if(get_next(kd, host))
67990926Snectar	    return 0;
68090926Snectar    }
68190926Snectar
68290926Snectar    if (kd->flags & KD_CONFIG_EXISTS)
68390926Snectar	return KRB5_KDC_UNREACH; /* XXX */
68490926Snectar
68590926Snectar    if(context->srv_lookup) {
68690926Snectar	if((kd->flags & KD_SRV_UDP) == 0) {
68790926Snectar	    srv_get_hosts(context, kd, "udp", "kpasswd");
68890926Snectar	    kd->flags |= KD_SRV_UDP;
68990926Snectar	    if(get_next(kd, host))
69090926Snectar		return 0;
69190926Snectar	}
692178825Sdfr	if((kd->flags & KD_SRV_TCP) == 0) {
693178825Sdfr	    srv_get_hosts(context, kd, "tcp", "kpasswd");
694178825Sdfr	    kd->flags |= KD_SRV_TCP;
695178825Sdfr	    if(get_next(kd, host))
696178825Sdfr		return 0;
697178825Sdfr	}
69890926Snectar    }
69990926Snectar
70090926Snectar    /* no matches -> try admin */
70190926Snectar
70290926Snectar    if (krbhst_empty(kd)) {
70390926Snectar	kd->flags = 0;
70490926Snectar	kd->port  = kd->def_port;
70590926Snectar	kd->get_next = admin_get_next;
706102644Snectar	ret = (*kd->get_next)(context, kd, host);
707102644Snectar	if (ret == 0)
708178825Sdfr	    (*host)->proto = krbhst_get_default_proto(kd);
709102644Snectar	return ret;
71090926Snectar    }
71190926Snectar
71290926Snectar    return KRB5_KDC_UNREACH; /* XXX */
71390926Snectar}
71490926Snectar
71590926Snectarstatic krb5_error_code
71690926Snectarkrb524_get_next(krb5_context context,
71790926Snectar		struct krb5_krbhst_data *kd,
71890926Snectar		krb5_krbhst_info **host)
71990926Snectar{
720178825Sdfr    if ((kd->flags & KD_PLUGIN) == 0) {
721178825Sdfr	plugin_get_hosts(context, kd, locate_service_krb524);
722178825Sdfr	kd->flags |= KD_PLUGIN;
723178825Sdfr	if(get_next(kd, host))
724178825Sdfr	    return 0;
725178825Sdfr    }
726178825Sdfr
72790926Snectar    if((kd->flags & KD_CONFIG) == 0) {
72890926Snectar	config_get_hosts(context, kd, "krb524_server");
72990926Snectar	if(get_next(kd, host))
73090926Snectar	    return 0;
73190926Snectar	kd->flags |= KD_CONFIG;
73290926Snectar    }
73390926Snectar
73490926Snectar    if (kd->flags & KD_CONFIG_EXISTS)
73590926Snectar	return KRB5_KDC_UNREACH; /* XXX */
73690926Snectar
73790926Snectar    if(context->srv_lookup) {
73890926Snectar	if((kd->flags & KD_SRV_UDP) == 0) {
73990926Snectar	    srv_get_hosts(context, kd, "udp", "krb524");
74090926Snectar	    kd->flags |= KD_SRV_UDP;
74190926Snectar	    if(get_next(kd, host))
74290926Snectar		return 0;
74390926Snectar	}
74490926Snectar
74590926Snectar	if((kd->flags & KD_SRV_TCP) == 0) {
74690926Snectar	    srv_get_hosts(context, kd, "tcp", "krb524");
74790926Snectar	    kd->flags |= KD_SRV_TCP;
74890926Snectar	    if(get_next(kd, host))
74990926Snectar		return 0;
75090926Snectar	}
75190926Snectar    }
75290926Snectar
75390926Snectar    /* no matches -> try kdc */
75490926Snectar
75590926Snectar    if (krbhst_empty(kd)) {
75690926Snectar	kd->flags = 0;
75790926Snectar	kd->port  = kd->def_port;
75890926Snectar	kd->get_next = kdc_get_next;
75990926Snectar	return (*kd->get_next)(context, kd, host);
76090926Snectar    }
76190926Snectar
76290926Snectar    return KRB5_KDC_UNREACH; /* XXX */
76390926Snectar}
76490926Snectar
76590926Snectarstatic struct krb5_krbhst_data*
76690926Snectarcommon_init(krb5_context context,
767178825Sdfr	    const char *realm,
768178825Sdfr	    int flags)
76990926Snectar{
77090926Snectar    struct krb5_krbhst_data *kd;
77190926Snectar
77290926Snectar    if((kd = calloc(1, sizeof(*kd))) == NULL)
77390926Snectar	return NULL;
77490926Snectar
77590926Snectar    if((kd->realm = strdup(realm)) == NULL) {
77690926Snectar	free(kd);
77790926Snectar	return NULL;
77890926Snectar    }
77990926Snectar
780178825Sdfr    /* For 'realms' without a . do not even think of going to DNS */
781178825Sdfr    if (!strchr(realm, '.'))
782178825Sdfr	kd->flags |= KD_CONFIG_EXISTS;
783178825Sdfr
784178825Sdfr    if (flags & KRB5_KRBHST_FLAGS_LARGE_MSG)
785178825Sdfr	kd->flags |= KD_LARGE_MSG;
78690926Snectar    kd->end = kd->index = &kd->hosts;
78790926Snectar    return kd;
78890926Snectar}
78990926Snectar
79090926Snectar/*
79190926Snectar * initialize `handle' to look for hosts of type `type' in realm `realm'
79290926Snectar */
79390926Snectar
794178825Sdfrkrb5_error_code KRB5_LIB_FUNCTION
79590926Snectarkrb5_krbhst_init(krb5_context context,
79690926Snectar		 const char *realm,
79790926Snectar		 unsigned int type,
79890926Snectar		 krb5_krbhst_handle *handle)
79990926Snectar{
800178825Sdfr    return krb5_krbhst_init_flags(context, realm, type, 0, handle);
801178825Sdfr}
802178825Sdfr
803178825Sdfrkrb5_error_code KRB5_LIB_FUNCTION
804178825Sdfrkrb5_krbhst_init_flags(krb5_context context,
805178825Sdfr		       const char *realm,
806178825Sdfr		       unsigned int type,
807178825Sdfr		       int flags,
808178825Sdfr		       krb5_krbhst_handle *handle)
809178825Sdfr{
81090926Snectar    struct krb5_krbhst_data *kd;
811178825Sdfr    krb5_error_code (*next)(krb5_context, struct krb5_krbhst_data *,
812178825Sdfr			    krb5_krbhst_info **);
81390926Snectar    int def_port;
81490926Snectar
81590926Snectar    switch(type) {
81690926Snectar    case KRB5_KRBHST_KDC:
817178825Sdfr	next = kdc_get_next;
81890926Snectar	def_port = ntohs(krb5_getportbyname (context, "kerberos", "udp", 88));
81990926Snectar	break;
82090926Snectar    case KRB5_KRBHST_ADMIN:
821178825Sdfr	next = admin_get_next;
82290926Snectar	def_port = ntohs(krb5_getportbyname (context, "kerberos-adm",
82390926Snectar					     "tcp", 749));
82490926Snectar	break;
82590926Snectar    case KRB5_KRBHST_CHANGEPW:
826178825Sdfr	next = kpasswd_get_next;
82790926Snectar	def_port = ntohs(krb5_getportbyname (context, "kpasswd", "udp",
82890926Snectar					     KPASSWD_PORT));
82990926Snectar	break;
83090926Snectar    case KRB5_KRBHST_KRB524:
831178825Sdfr	next = krb524_get_next;
83290926Snectar	def_port = ntohs(krb5_getportbyname (context, "krb524", "udp", 4444));
83390926Snectar	break;
83490926Snectar    default:
83590926Snectar	krb5_set_error_string(context, "unknown krbhst type (%u)", type);
83690926Snectar	return ENOTTY;
83790926Snectar    }
838178825Sdfr    if((kd = common_init(context, realm, flags)) == NULL)
83990926Snectar	return ENOMEM;
840178825Sdfr    kd->get_next = next;
84190926Snectar    kd->def_port = def_port;
84290926Snectar    *handle = kd;
84355682Smarkm    return 0;
84455682Smarkm}
84555682Smarkm
84672445Sassar/*
84790926Snectar * return the next host information from `handle' in `host'
84872445Sassar */
84972445Sassar
850178825Sdfrkrb5_error_code KRB5_LIB_FUNCTION
85190926Snectarkrb5_krbhst_next(krb5_context context,
85290926Snectar		 krb5_krbhst_handle handle,
85390926Snectar		 krb5_krbhst_info **host)
85490926Snectar{
85590926Snectar    if(get_next(handle, host))
85690926Snectar	return 0;
85790926Snectar
85890926Snectar    return (*handle->get_next)(context, handle, host);
85990926Snectar}
86090926Snectar
86190926Snectar/*
86290926Snectar * return the next host information from `handle' as a host name
86390926Snectar * in `hostname' (or length `hostlen)
86490926Snectar */
86590926Snectar
866178825Sdfrkrb5_error_code KRB5_LIB_FUNCTION
86790926Snectarkrb5_krbhst_next_as_string(krb5_context context,
86890926Snectar			   krb5_krbhst_handle handle,
86990926Snectar			   char *hostname,
87090926Snectar			   size_t hostlen)
87190926Snectar{
87290926Snectar    krb5_error_code ret;
87390926Snectar    krb5_krbhst_info *host;
87490926Snectar    ret = krb5_krbhst_next(context, handle, &host);
87590926Snectar    if(ret)
87690926Snectar	return ret;
87790926Snectar    return krb5_krbhst_format_string(context, host, hostname, hostlen);
87890926Snectar}
87990926Snectar
88090926Snectar
881178825Sdfrvoid KRB5_LIB_FUNCTION
88290926Snectarkrb5_krbhst_reset(krb5_context context, krb5_krbhst_handle handle)
88390926Snectar{
88490926Snectar    handle->index = &handle->hosts;
88590926Snectar}
88690926Snectar
887178825Sdfrvoid KRB5_LIB_FUNCTION
88890926Snectarkrb5_krbhst_free(krb5_context context, krb5_krbhst_handle handle)
88990926Snectar{
89090926Snectar    krb5_krbhst_info *h, *next;
89190926Snectar
89290926Snectar    if (handle == NULL)
89390926Snectar	return;
89490926Snectar
89590926Snectar    for (h = handle->hosts; h != NULL; h = next) {
89690926Snectar	next = h->next;
897178825Sdfr	_krb5_free_krbhst_info(h);
89890926Snectar    }
89990926Snectar
90090926Snectar    free(handle->realm);
90190926Snectar    free(handle);
90290926Snectar}
90390926Snectar
90490926Snectar/* backwards compatibility ahead */
90590926Snectar
90690926Snectarstatic krb5_error_code
90790926Snectargethostlist(krb5_context context, const char *realm,
90890926Snectar	    unsigned int type, char ***hostlist)
90990926Snectar{
91090926Snectar    krb5_error_code ret;
91190926Snectar    int nhost = 0;
91290926Snectar    krb5_krbhst_handle handle;
91390926Snectar    char host[MAXHOSTNAMELEN];
91490926Snectar    krb5_krbhst_info *hostinfo;
91590926Snectar
91690926Snectar    ret = krb5_krbhst_init(context, realm, type, &handle);
91790926Snectar    if (ret)
91890926Snectar	return ret;
91990926Snectar
92090926Snectar    while(krb5_krbhst_next(context, handle, &hostinfo) == 0)
92190926Snectar	nhost++;
922178825Sdfr    if(nhost == 0) {
923178825Sdfr	krb5_set_error_string(context, "No KDC found for realm %s", realm);
92490926Snectar	return KRB5_KDC_UNREACH;
925178825Sdfr    }
92690926Snectar    *hostlist = calloc(nhost + 1, sizeof(**hostlist));
92790926Snectar    if(*hostlist == NULL) {
92890926Snectar	krb5_krbhst_free(context, handle);
92990926Snectar	return ENOMEM;
93090926Snectar    }
93190926Snectar
93290926Snectar    krb5_krbhst_reset(context, handle);
93390926Snectar    nhost = 0;
93490926Snectar    while(krb5_krbhst_next_as_string(context, handle,
93590926Snectar				     host, sizeof(host)) == 0) {
93690926Snectar	if(((*hostlist)[nhost++] = strdup(host)) == NULL) {
93790926Snectar	    krb5_free_krbhst(context, *hostlist);
93890926Snectar	    krb5_krbhst_free(context, handle);
93990926Snectar	    return ENOMEM;
94090926Snectar	}
94190926Snectar    }
94290926Snectar    (*hostlist)[nhost++] = NULL;
94390926Snectar    krb5_krbhst_free(context, handle);
94490926Snectar    return 0;
94590926Snectar}
94690926Snectar
94790926Snectar/*
94890926Snectar * return an malloced list of kadmin-hosts for `realm' in `hostlist'
94990926Snectar */
95090926Snectar
951178825Sdfrkrb5_error_code KRB5_LIB_FUNCTION
95255682Smarkmkrb5_get_krb_admin_hst (krb5_context context,
95355682Smarkm			const krb5_realm *realm,
95455682Smarkm			char ***hostlist)
95555682Smarkm{
95690926Snectar    return gethostlist(context, *realm, KRB5_KRBHST_ADMIN, hostlist);
95755682Smarkm}
95855682Smarkm
95972445Sassar/*
96090926Snectar * return an malloced list of changepw-hosts for `realm' in `hostlist'
96172445Sassar */
96272445Sassar
963178825Sdfrkrb5_error_code KRB5_LIB_FUNCTION
96455682Smarkmkrb5_get_krb_changepw_hst (krb5_context context,
96555682Smarkm			   const krb5_realm *realm,
96655682Smarkm			   char ***hostlist)
96755682Smarkm{
96890926Snectar    return gethostlist(context, *realm, KRB5_KRBHST_CHANGEPW, hostlist);
96990926Snectar}
97072445Sassar
97190926Snectar/*
97290926Snectar * return an malloced list of 524-hosts for `realm' in `hostlist'
97390926Snectar */
97490926Snectar
975178825Sdfrkrb5_error_code KRB5_LIB_FUNCTION
97690926Snectarkrb5_get_krb524hst (krb5_context context,
97790926Snectar		    const krb5_realm *realm,
97890926Snectar		    char ***hostlist)
97990926Snectar{
98090926Snectar    return gethostlist(context, *realm, KRB5_KRBHST_KRB524, hostlist);
98155682Smarkm}
98255682Smarkm
98390926Snectar
98472445Sassar/*
98590926Snectar * return an malloced list of KDC's for `realm' in `hostlist'
98672445Sassar */
98772445Sassar
988178825Sdfrkrb5_error_code KRB5_LIB_FUNCTION
98955682Smarkmkrb5_get_krbhst (krb5_context context,
99055682Smarkm		 const krb5_realm *realm,
99155682Smarkm		 char ***hostlist)
99255682Smarkm{
99390926Snectar    return gethostlist(context, *realm, KRB5_KRBHST_KDC, hostlist);
99455682Smarkm}
99555682Smarkm
99672445Sassar/*
99790926Snectar * free all the memory allocated in `hostlist'
99872445Sassar */
99972445Sassar
1000178825Sdfrkrb5_error_code KRB5_LIB_FUNCTION
100155682Smarkmkrb5_free_krbhst (krb5_context context,
100255682Smarkm		  char **hostlist)
100355682Smarkm{
100455682Smarkm    char **p;
100555682Smarkm
100655682Smarkm    for (p = hostlist; *p; ++p)
100755682Smarkm	free (*p);
100855682Smarkm    free (hostlist);
100955682Smarkm    return 0;
101055682Smarkm}
1011