1/*
2 * Copyright (c) 2001 - 2003 Kungliga Tekniska H��gskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include "krb5_locl.h"
35#include <resolve.h>
36#include "locate_plugin.h"
37
38static int
39string_to_proto(const char *string)
40{
41    if(strcasecmp(string, "udp") == 0)
42	return KRB5_KRBHST_UDP;
43    else if(strcasecmp(string, "tcp") == 0)
44	return KRB5_KRBHST_TCP;
45    else if(strcasecmp(string, "http") == 0)
46	return KRB5_KRBHST_HTTP;
47    return -1;
48}
49
50/*
51 * set `res' and `count' to the result of looking up SRV RR in DNS for
52 * `proto', `proto', `realm' using `dns_type'.
53 * if `port' != 0, force that port number
54 */
55
56static krb5_error_code
57srv_find_realm(krb5_context context, krb5_krbhst_info ***res, int *count,
58	       const char *realm, const char *dns_type,
59	       const char *proto, const char *service, int port)
60{
61    char domain[1024];
62    struct rk_dns_reply *r;
63    struct rk_resource_record *rr;
64    int num_srv;
65    int proto_num;
66    int def_port;
67
68    *res = NULL;
69    *count = 0;
70
71    proto_num = string_to_proto(proto);
72    if(proto_num < 0) {
73	krb5_set_error_message(context, EINVAL,
74			       N_("unknown protocol `%s' to lookup", ""),
75			       proto);
76	return EINVAL;
77    }
78
79    if(proto_num == KRB5_KRBHST_HTTP)
80	def_port = ntohs(krb5_getportbyname (context, "http", "tcp", 80));
81    else if(port == 0)
82	def_port = ntohs(krb5_getportbyname (context, service, proto, 88));
83    else
84	def_port = port;
85
86    snprintf(domain, sizeof(domain), "_%s._%s.%s.", service, proto, realm);
87
88    r = rk_dns_lookup(domain, dns_type);
89    if(r == NULL) {
90	_krb5_debug(context, 0,
91		    "DNS lookup failed domain: %s", domain);
92	return KRB5_KDC_UNREACH;
93    }
94
95    for(num_srv = 0, rr = r->head; rr; rr = rr->next)
96	if(rr->type == rk_ns_t_srv)
97	    num_srv++;
98
99    if (num_srv == 0) {
100	_krb5_debug(context, 0,
101		    "DNS SRV RR lookup domain nodata: %s", domain);
102	return KRB5_KDC_UNREACH;
103    }
104
105    *res = malloc(num_srv * sizeof(**res));
106    if(*res == NULL) {
107	rk_dns_free_data(r);
108	krb5_set_error_message(context, ENOMEM,
109			       N_("malloc: out of memory", ""));
110	return ENOMEM;
111    }
112
113    rk_dns_srv_order(r);
114
115    for(num_srv = 0, rr = r->head; rr; rr = rr->next)
116	if(rr->type == rk_ns_t_srv) {
117	    krb5_krbhst_info *hi;
118	    size_t len = strlen(rr->u.srv->target);
119
120	    hi = calloc(1, sizeof(*hi) + len);
121	    if(hi == NULL) {
122		rk_dns_free_data(r);
123		while(--num_srv >= 0)
124		    free((*res)[num_srv]);
125		free(*res);
126		*res = NULL;
127		return ENOMEM;
128	    }
129	    (*res)[num_srv++] = hi;
130
131	    hi->proto = proto_num;
132
133	    hi->def_port = def_port;
134	    if (port != 0)
135		hi->port = port;
136	    else
137		hi->port = rr->u.srv->port;
138
139	    strlcpy(hi->hostname, rr->u.srv->target, len + 1);
140	}
141
142    *count = num_srv;
143
144    rk_dns_free_data(r);
145    return 0;
146}
147
148
149struct krb5_krbhst_data {
150    char *realm;
151    unsigned int flags;
152    int def_port;
153    int port;			/* hardwired port number if != 0 */
154#define KD_CONFIG		 1
155#define KD_SRV_UDP		 2
156#define KD_SRV_TCP		 4
157#define KD_SRV_HTTP		 8
158#define KD_FALLBACK		16
159#define KD_CONFIG_EXISTS	32
160#define KD_LARGE_MSG		64
161#define KD_PLUGIN	       128
162    krb5_error_code (*get_next)(krb5_context, struct krb5_krbhst_data *,
163				krb5_krbhst_info**);
164
165    unsigned int fallback_count;
166
167    struct krb5_krbhst_info *hosts, **index, **end;
168};
169
170static krb5_boolean
171krbhst_empty(const struct krb5_krbhst_data *kd)
172{
173    return kd->index == &kd->hosts;
174}
175
176/*
177 * Return the default protocol for the `kd' (either TCP or UDP)
178 */
179
180static int
181krbhst_get_default_proto(struct krb5_krbhst_data *kd)
182{
183    if (kd->flags & KD_LARGE_MSG)
184	return KRB5_KRBHST_TCP;
185    return KRB5_KRBHST_UDP;
186}
187
188/*
189 *
190 */
191
192const char *
193_krb5_krbhst_get_realm(krb5_krbhst_handle handle)
194{
195    return handle->realm;
196}
197
198/*
199 * parse `spec' into a krb5_krbhst_info, defaulting the port to `def_port'
200 * and forcing it to `port' if port != 0
201 */
202
203static struct krb5_krbhst_info*
204parse_hostspec(krb5_context context, struct krb5_krbhst_data *kd,
205	       const char *spec, int def_port, int port)
206{
207    const char *p = spec, *q;
208    struct krb5_krbhst_info *hi;
209
210    hi = calloc(1, sizeof(*hi) + strlen(spec));
211    if(hi == NULL)
212	return NULL;
213
214    hi->proto = krbhst_get_default_proto(kd);
215
216    if(strncmp(p, "http://", 7) == 0){
217	hi->proto = KRB5_KRBHST_HTTP;
218	p += 7;
219    } else if(strncmp(p, "http/", 5) == 0) {
220	hi->proto = KRB5_KRBHST_HTTP;
221	p += 5;
222	def_port = ntohs(krb5_getportbyname (context, "http", "tcp", 80));
223    }else if(strncmp(p, "tcp/", 4) == 0){
224	hi->proto = KRB5_KRBHST_TCP;
225	p += 4;
226    } else if(strncmp(p, "udp/", 4) == 0) {
227	p += 4;
228    }
229
230    if (p[0] == '[' && (q = strchr(p, ']')) != NULL) {
231	/* if address looks like [foo:bar] or [foo:bar]: its a ipv6
232	   adress, strip of [] */
233	memcpy(hi->hostname, &p[1], q - p - 1);
234	hi->hostname[q - p - 1] = '\0';
235	p = q + 1;
236	/* get trailing : */
237	if (p[0] == ':')
238	    p++;
239    } else if(strsep_copy(&p, ":", hi->hostname, strlen(spec) + 1) < 0) {
240	/* copy everything before : */
241	free(hi);
242	return NULL;
243    }
244    /* get rid of trailing /, and convert to lower case */
245    hi->hostname[strcspn(hi->hostname, "/")] = '\0';
246    strlwr(hi->hostname);
247
248    hi->port = hi->def_port = def_port;
249    if(p != NULL && p[0]) {
250	char *end;
251	hi->port = strtol(p, &end, 0);
252	if(end == p) {
253	    free(hi);
254	    return NULL;
255	}
256    }
257    if (port)
258	hi->port = port;
259    return hi;
260}
261
262void
263_krb5_free_krbhst_info(krb5_krbhst_info *hi)
264{
265    if (hi->ai != NULL)
266	freeaddrinfo(hi->ai);
267    free(hi);
268}
269
270krb5_error_code
271_krb5_krbhost_info_move(krb5_context context,
272			krb5_krbhst_info *from,
273			krb5_krbhst_info **to)
274{
275    size_t hostnamelen = strlen(from->hostname);
276    /* trailing NUL is included in structure */
277    *to = calloc(1, sizeof(**to) + hostnamelen);
278    if(*to == NULL) {
279	krb5_set_error_message(context, ENOMEM,
280			       N_("malloc: out of memory", ""));
281	return ENOMEM;
282    }
283
284    (*to)->proto = from->proto;
285    (*to)->port = from->port;
286    (*to)->def_port = from->def_port;
287    (*to)->ai = from->ai;
288    from->ai = NULL;
289    (*to)->next = NULL;
290    memcpy((*to)->hostname, from->hostname, hostnamelen + 1);
291    return 0;
292}
293
294
295static void
296append_host_hostinfo(struct krb5_krbhst_data *kd, struct krb5_krbhst_info *host)
297{
298    struct krb5_krbhst_info *h;
299
300    for(h = kd->hosts; h; h = h->next)
301	if(h->proto == host->proto &&
302	   h->port == host->port &&
303	   strcmp(h->hostname, host->hostname) == 0) {
304	    _krb5_free_krbhst_info(host);
305	    return;
306	}
307    *kd->end = host;
308    kd->end = &host->next;
309}
310
311static krb5_error_code
312append_host_string(krb5_context context, struct krb5_krbhst_data *kd,
313		   const char *host, int def_port, int port)
314{
315    struct krb5_krbhst_info *hi;
316
317    hi = parse_hostspec(context, kd, host, def_port, port);
318    if(hi == NULL)
319	return ENOMEM;
320
321    append_host_hostinfo(kd, hi);
322    return 0;
323}
324
325/*
326 * return a readable representation of `host' in `hostname, hostlen'
327 */
328
329KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
330krb5_krbhst_format_string(krb5_context context, const krb5_krbhst_info *host,
331			  char *hostname, size_t hostlen)
332{
333    const char *proto = "";
334    char portstr[7] = "";
335    if(host->proto == KRB5_KRBHST_TCP)
336	proto = "tcp/";
337    else if(host->proto == KRB5_KRBHST_HTTP)
338	proto = "http://";
339    if(host->port != host->def_port)
340	snprintf(portstr, sizeof(portstr), ":%d", host->port);
341    snprintf(hostname, hostlen, "%s%s%s", proto, host->hostname, portstr);
342    return 0;
343}
344
345/*
346 * create a getaddrinfo `hints' based on `proto'
347 */
348
349static void
350make_hints(struct addrinfo *hints, int proto)
351{
352    memset(hints, 0, sizeof(*hints));
353    hints->ai_family = AF_UNSPEC;
354    switch(proto) {
355    case KRB5_KRBHST_UDP :
356	hints->ai_socktype = SOCK_DGRAM;
357	break;
358    case KRB5_KRBHST_HTTP :
359    case KRB5_KRBHST_TCP :
360	hints->ai_socktype = SOCK_STREAM;
361	break;
362    }
363}
364
365/**
366 * Return an `struct addrinfo *' for a KDC host.
367 *
368 * Returns an the struct addrinfo in in that corresponds to the
369 * information in `host'.  free:ing is handled by krb5_krbhst_free, so
370 * the returned ai must not be released.
371 *
372 * @ingroup krb5
373 */
374
375KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
376krb5_krbhst_get_addrinfo(krb5_context context, krb5_krbhst_info *host,
377			 struct addrinfo **ai)
378{
379    int ret = 0;
380
381    if (host->ai == NULL) {
382	struct addrinfo hints;
383	char portstr[NI_MAXSERV];
384	char *hostname = host->hostname;
385
386	snprintf (portstr, sizeof(portstr), "%d", host->port);
387	make_hints(&hints, host->proto);
388
389	/**
390	 * First try this as an IP address, this allows us to add a
391	 * dot at the end to stop using the search domains.
392	 */
393
394	hints.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV;
395
396	ret = getaddrinfo(host->hostname, portstr, &hints, &host->ai);
397	if (ret == 0)
398	    goto out;
399
400	/**
401	 * If the hostname contains a dot, assumes it's a FQDN and
402	 * don't use search domains since that might be painfully slow
403	 * when machine is disconnected from that network.
404	 */
405
406	hints.ai_flags &= ~(AI_NUMERICHOST);
407
408	if (strchr(hostname, '.') && hostname[strlen(hostname) - 1] != '.') {
409	    ret = asprintf(&hostname, "%s.", host->hostname);
410	    if (ret < 0 || hostname == NULL)
411		return ENOMEM;
412	}
413
414	ret = getaddrinfo(hostname, portstr, &hints, &host->ai);
415	if (hostname != host->hostname)
416	    free(hostname);
417	if (ret) {
418	    ret = krb5_eai_to_heim_errno(ret, errno);
419	    goto out;
420	}
421    }
422 out:
423    *ai = host->ai;
424    return ret;
425}
426
427static krb5_boolean
428get_next(struct krb5_krbhst_data *kd, krb5_krbhst_info **host)
429{
430    struct krb5_krbhst_info *hi = *kd->index;
431    if(hi != NULL) {
432	*host = hi;
433	kd->index = &(*kd->index)->next;
434	return TRUE;
435    }
436    return FALSE;
437}
438
439static void
440srv_get_hosts(krb5_context context, struct krb5_krbhst_data *kd,
441	      const char *proto, const char *service)
442{
443    krb5_error_code ret;
444    krb5_krbhst_info **res;
445    int count, i;
446
447    ret = srv_find_realm(context, &res, &count, kd->realm, "SRV", proto, service,
448			 kd->port);
449    _krb5_debug(context, 2, "searching DNS for realm %s %s.%s -> %d",
450		kd->realm, proto, service, ret);
451    if (ret)
452	return;
453    for(i = 0; i < count; i++)
454	append_host_hostinfo(kd, res[i]);
455    free(res);
456}
457
458/*
459 * read the configuration for `conf_string', defaulting to kd->def_port and
460 * forcing it to `kd->port' if kd->port != 0
461 */
462
463static void
464config_get_hosts(krb5_context context, struct krb5_krbhst_data *kd,
465		 const char *conf_string)
466{
467    int i;
468    char **hostlist;
469    hostlist = krb5_config_get_strings(context, NULL,
470				       "realms", kd->realm, conf_string, NULL);
471
472    _krb5_debug(context, 2, "configuration file for realm %s%s found",
473		kd->realm, hostlist ? "" : " not");
474
475    if(hostlist == NULL)
476	return;
477    kd->flags |= KD_CONFIG_EXISTS;
478    for(i = 0; hostlist && hostlist[i] != NULL; i++)
479	append_host_string(context, kd, hostlist[i], kd->def_port, kd->port);
480
481    krb5_config_free_strings(hostlist);
482}
483
484/*
485 * as a fallback, look for `serv_string.kd->realm' (typically
486 * kerberos.REALM, kerberos-1.REALM, ...
487 * `port' is the default port for the service, and `proto' the
488 * protocol
489 */
490
491static krb5_error_code
492fallback_get_hosts(krb5_context context, struct krb5_krbhst_data *kd,
493		   const char *serv_string, int port, int proto)
494{
495    char *host = NULL;
496    int ret;
497    struct addrinfo *ai;
498    struct addrinfo hints;
499    char portstr[NI_MAXSERV];
500
501    _krb5_debug(context, 2, "fallback lookup %d for realm %s (service %s)",
502		kd->fallback_count, kd->realm, serv_string);
503
504    /*
505     * Don't try forever in case the DNS server keep returning us
506     * entries (like wildcard entries or the .nu TLD)
507     */
508    if(kd->fallback_count >= 5) {
509	kd->flags |= KD_FALLBACK;
510	return 0;
511    }
512
513    if(kd->fallback_count == 0)
514	ret = asprintf(&host, "%s.%s.", serv_string, kd->realm);
515    else
516	ret = asprintf(&host, "%s-%d.%s.",
517		       serv_string, kd->fallback_count, kd->realm);
518
519    if (ret < 0 || host == NULL)
520	return ENOMEM;
521
522    make_hints(&hints, proto);
523    snprintf(portstr, sizeof(portstr), "%d", port);
524    ret = getaddrinfo(host, portstr, &hints, &ai);
525    if (ret) {
526	/* no more hosts, so we're done here */
527	free(host);
528	kd->flags |= KD_FALLBACK;
529    } else {
530	struct krb5_krbhst_info *hi;
531	size_t hostlen = strlen(host);
532
533	hi = calloc(1, sizeof(*hi) + hostlen);
534	if(hi == NULL) {
535	    free(host);
536	    return ENOMEM;
537	}
538
539	hi->proto = proto;
540	hi->port  = hi->def_port = port;
541	hi->ai    = ai;
542	memmove(hi->hostname, host, hostlen);
543	hi->hostname[hostlen] = '\0';
544	free(host);
545	append_host_hostinfo(kd, hi);
546	kd->fallback_count++;
547    }
548    return 0;
549}
550
551/*
552 * Fetch hosts from plugin
553 */
554
555static krb5_error_code
556add_locate(void *ctx, int type, struct sockaddr *addr)
557{
558    struct krb5_krbhst_info *hi;
559    struct krb5_krbhst_data *kd = ctx;
560    char host[NI_MAXHOST], port[NI_MAXSERV];
561    struct addrinfo hints, *ai;
562    socklen_t socklen;
563    size_t hostlen;
564    int ret;
565
566    socklen = socket_sockaddr_size(addr);
567
568    ret = getnameinfo(addr, socklen, host, sizeof(host), port, sizeof(port),
569		      NI_NUMERICHOST|NI_NUMERICSERV);
570    if (ret != 0)
571	return 0;
572
573    make_hints(&hints, krbhst_get_default_proto(kd));
574    ret = getaddrinfo(host, port, &hints, &ai);
575    if (ret)
576	return 0;
577
578    hostlen = strlen(host);
579
580    hi = calloc(1, sizeof(*hi) + hostlen);
581    if(hi == NULL)
582	return ENOMEM;
583
584    hi->proto = krbhst_get_default_proto(kd);
585    hi->port  = hi->def_port = socket_get_port(addr);
586    hi->ai    = ai;
587    memmove(hi->hostname, host, hostlen);
588    hi->hostname[hostlen] = '\0';
589    append_host_hostinfo(kd, hi);
590
591    return 0;
592}
593
594static void
595plugin_get_hosts(krb5_context context,
596		 struct krb5_krbhst_data *kd,
597		 enum locate_service_type type)
598{
599    struct krb5_plugin *list = NULL, *e;
600    krb5_error_code ret;
601
602    ret = _krb5_plugin_find(context, PLUGIN_TYPE_DATA,
603			    KRB5_PLUGIN_LOCATE, &list);
604    if(ret != 0 || list == NULL)
605	return;
606
607    for (e = list; e != NULL; e = _krb5_plugin_get_next(e)) {
608	krb5plugin_service_locate_ftable *service;
609	void *ctx;
610
611	service = _krb5_plugin_get_symbol(e);
612	if (service->minor_version != 0)
613	    continue;
614
615	(*service->init)(context, &ctx);
616	ret = (*service->lookup)(ctx, type, kd->realm, 0, 0, add_locate, kd);
617	(*service->fini)(ctx);
618	if (ret && ret != KRB5_PLUGIN_NO_HANDLE) {
619	    krb5_set_error_message(context, ret,
620				   N_("Locate plugin failed to lookup realm %s: %d", ""),
621				   kd->realm, ret);
622	    break;
623	} else if (ret == 0) {
624	    _krb5_debug(context, 2, "plugin found result for realm %s", kd->realm);
625	    kd->flags |= KD_CONFIG_EXISTS;
626	}
627
628    }
629    _krb5_plugin_free(list);
630}
631
632/*
633 *
634 */
635
636static krb5_error_code
637kdc_get_next(krb5_context context,
638	     struct krb5_krbhst_data *kd,
639	     krb5_krbhst_info **host)
640{
641    krb5_error_code ret;
642
643    if ((kd->flags & KD_PLUGIN) == 0) {
644	plugin_get_hosts(context, kd, locate_service_kdc);
645	kd->flags |= KD_PLUGIN;
646	if(get_next(kd, host))
647	    return 0;
648    }
649
650    if((kd->flags & KD_CONFIG) == 0) {
651	config_get_hosts(context, kd, "kdc");
652	kd->flags |= KD_CONFIG;
653	if(get_next(kd, host))
654	    return 0;
655    }
656
657    if (kd->flags & KD_CONFIG_EXISTS) {
658	_krb5_debug(context, 1,
659		    "Configuration exists for realm %s, wont go to DNS",
660		    kd->realm);
661	return KRB5_KDC_UNREACH;
662    }
663
664    if(context->srv_lookup) {
665	if((kd->flags & KD_SRV_UDP) == 0 && (kd->flags & KD_LARGE_MSG) == 0) {
666	    srv_get_hosts(context, kd, "udp", "kerberos");
667	    kd->flags |= KD_SRV_UDP;
668	    if(get_next(kd, host))
669		return 0;
670	}
671
672	if((kd->flags & KD_SRV_TCP) == 0) {
673	    srv_get_hosts(context, kd, "tcp", "kerberos");
674	    kd->flags |= KD_SRV_TCP;
675	    if(get_next(kd, host))
676		return 0;
677	}
678	if((kd->flags & KD_SRV_HTTP) == 0) {
679	    srv_get_hosts(context, kd, "http", "kerberos");
680	    kd->flags |= KD_SRV_HTTP;
681	    if(get_next(kd, host))
682		return 0;
683	}
684    }
685
686    while((kd->flags & KD_FALLBACK) == 0) {
687	ret = fallback_get_hosts(context, kd, "kerberos",
688				 kd->def_port,
689				 krbhst_get_default_proto(kd));
690	if(ret)
691	    return ret;
692	if(get_next(kd, host))
693	    return 0;
694    }
695
696    _krb5_debug(context, 0, "No KDC entries found for %s", kd->realm);
697
698    return KRB5_KDC_UNREACH; /* XXX */
699}
700
701static krb5_error_code
702admin_get_next(krb5_context context,
703	       struct krb5_krbhst_data *kd,
704	       krb5_krbhst_info **host)
705{
706    krb5_error_code ret;
707
708    if ((kd->flags & KD_PLUGIN) == 0) {
709	plugin_get_hosts(context, kd, locate_service_kadmin);
710	kd->flags |= KD_PLUGIN;
711	if(get_next(kd, host))
712	    return 0;
713    }
714
715    if((kd->flags & KD_CONFIG) == 0) {
716	config_get_hosts(context, kd, "admin_server");
717	kd->flags |= KD_CONFIG;
718	if(get_next(kd, host))
719	    return 0;
720    }
721
722    if (kd->flags & KD_CONFIG_EXISTS) {
723	_krb5_debug(context, 1,
724		    "Configuration exists for realm %s, wont go to DNS",
725		    kd->realm);
726	return KRB5_KDC_UNREACH;
727    }
728
729    if(context->srv_lookup) {
730	if((kd->flags & KD_SRV_TCP) == 0) {
731	    srv_get_hosts(context, kd, "tcp", "kerberos-adm");
732	    kd->flags |= KD_SRV_TCP;
733	    if(get_next(kd, host))
734		return 0;
735	}
736    }
737
738    if (krbhst_empty(kd)
739	&& (kd->flags & KD_FALLBACK) == 0) {
740	ret = fallback_get_hosts(context, kd, "kerberos",
741				 kd->def_port,
742				 krbhst_get_default_proto(kd));
743	if(ret)
744	    return ret;
745	kd->flags |= KD_FALLBACK;
746	if(get_next(kd, host))
747	    return 0;
748    }
749
750    _krb5_debug(context, 0, "No admin entries found for realm %s", kd->realm);
751
752    return KRB5_KDC_UNREACH;	/* XXX */
753}
754
755static krb5_error_code
756kpasswd_get_next(krb5_context context,
757		 struct krb5_krbhst_data *kd,
758		 krb5_krbhst_info **host)
759{
760    krb5_error_code ret;
761
762    if ((kd->flags & KD_PLUGIN) == 0) {
763	plugin_get_hosts(context, kd, locate_service_kpasswd);
764	kd->flags |= KD_PLUGIN;
765	if(get_next(kd, host))
766	    return 0;
767    }
768
769    if((kd->flags & KD_CONFIG) == 0) {
770	config_get_hosts(context, kd, "kpasswd_server");
771	kd->flags |= KD_CONFIG;
772	if(get_next(kd, host))
773	    return 0;
774    }
775
776    if (kd->flags & KD_CONFIG_EXISTS) {
777	_krb5_debug(context, 1,
778		    "Configuration exists for realm %s, wont go to DNS",
779		    kd->realm);
780	return KRB5_KDC_UNREACH;
781    }
782
783    if(context->srv_lookup) {
784	if((kd->flags & KD_SRV_UDP) == 0) {
785	    srv_get_hosts(context, kd, "udp", "kpasswd");
786	    kd->flags |= KD_SRV_UDP;
787	    if(get_next(kd, host))
788		return 0;
789	}
790	if((kd->flags & KD_SRV_TCP) == 0) {
791	    srv_get_hosts(context, kd, "tcp", "kpasswd");
792	    kd->flags |= KD_SRV_TCP;
793	    if(get_next(kd, host))
794		return 0;
795	}
796    }
797
798    /* no matches -> try admin */
799
800    if (krbhst_empty(kd)) {
801	kd->flags = 0;
802	kd->port  = kd->def_port;
803	kd->get_next = admin_get_next;
804	ret = (*kd->get_next)(context, kd, host);
805	if (ret == 0)
806	    (*host)->proto = krbhst_get_default_proto(kd);
807	return ret;
808    }
809
810    _krb5_debug(context, 0, "No kpasswd entries found for realm %s", kd->realm);
811
812    return KRB5_KDC_UNREACH;
813}
814
815static krb5_error_code
816krb524_get_next(krb5_context context,
817		struct krb5_krbhst_data *kd,
818		krb5_krbhst_info **host)
819{
820    if ((kd->flags & KD_PLUGIN) == 0) {
821	plugin_get_hosts(context, kd, locate_service_krb524);
822	kd->flags |= KD_PLUGIN;
823	if(get_next(kd, host))
824	    return 0;
825    }
826
827    if((kd->flags & KD_CONFIG) == 0) {
828	config_get_hosts(context, kd, "krb524_server");
829	if(get_next(kd, host))
830	    return 0;
831	kd->flags |= KD_CONFIG;
832    }
833
834    if (kd->flags & KD_CONFIG_EXISTS) {
835	_krb5_debug(context, 1,
836		    "Configuration exists for realm %s, wont go to DNS",
837		    kd->realm);
838	return KRB5_KDC_UNREACH;
839    }
840
841    if(context->srv_lookup) {
842	if((kd->flags & KD_SRV_UDP) == 0) {
843	    srv_get_hosts(context, kd, "udp", "krb524");
844	    kd->flags |= KD_SRV_UDP;
845	    if(get_next(kd, host))
846		return 0;
847	}
848
849	if((kd->flags & KD_SRV_TCP) == 0) {
850	    srv_get_hosts(context, kd, "tcp", "krb524");
851	    kd->flags |= KD_SRV_TCP;
852	    if(get_next(kd, host))
853		return 0;
854	}
855    }
856
857    /* no matches -> try kdc */
858
859    if (krbhst_empty(kd)) {
860	kd->flags = 0;
861	kd->port  = kd->def_port;
862	kd->get_next = kdc_get_next;
863	return (*kd->get_next)(context, kd, host);
864    }
865
866    _krb5_debug(context, 0, "No kpasswd entries found for realm %s", kd->realm);
867
868    return KRB5_KDC_UNREACH;
869}
870
871static struct krb5_krbhst_data*
872common_init(krb5_context context,
873	    const char *service,
874	    const char *realm,
875	    int flags)
876{
877    struct krb5_krbhst_data *kd;
878
879    if((kd = calloc(1, sizeof(*kd))) == NULL)
880	return NULL;
881
882    if((kd->realm = strdup(realm)) == NULL) {
883	free(kd);
884	return NULL;
885    }
886
887    _krb5_debug(context, 2, "Trying to find service %s for realm %s flags %x",
888		service, realm, flags);
889
890    /* For 'realms' without a . do not even think of going to DNS */
891    if (!strchr(realm, '.'))
892	kd->flags |= KD_CONFIG_EXISTS;
893
894    if (flags & KRB5_KRBHST_FLAGS_LARGE_MSG)
895	kd->flags |= KD_LARGE_MSG;
896    kd->end = kd->index = &kd->hosts;
897    return kd;
898}
899
900/*
901 * initialize `handle' to look for hosts of type `type' in realm `realm'
902 */
903
904KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
905krb5_krbhst_init(krb5_context context,
906		 const char *realm,
907		 unsigned int type,
908		 krb5_krbhst_handle *handle)
909{
910    return krb5_krbhst_init_flags(context, realm, type, 0, handle);
911}
912
913KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
914krb5_krbhst_init_flags(krb5_context context,
915		       const char *realm,
916		       unsigned int type,
917		       int flags,
918		       krb5_krbhst_handle *handle)
919{
920    struct krb5_krbhst_data *kd;
921    krb5_error_code (*next)(krb5_context, struct krb5_krbhst_data *,
922			    krb5_krbhst_info **);
923    int def_port;
924    const char *service;
925
926    switch(type) {
927    case KRB5_KRBHST_KDC:
928	next = kdc_get_next;
929	def_port = ntohs(krb5_getportbyname (context, "kerberos", "udp", 88));
930	service = "kdc";
931	break;
932    case KRB5_KRBHST_ADMIN:
933	next = admin_get_next;
934	def_port = ntohs(krb5_getportbyname (context, "kerberos-adm",
935					     "tcp", 749));
936	service = "admin";
937	break;
938    case KRB5_KRBHST_CHANGEPW:
939	next = kpasswd_get_next;
940	def_port = ntohs(krb5_getportbyname (context, "kpasswd", "udp",
941					     KPASSWD_PORT));
942	service = "change_password";
943	break;
944    case KRB5_KRBHST_KRB524:
945	next = krb524_get_next;
946	def_port = ntohs(krb5_getportbyname (context, "krb524", "udp", 4444));
947	service = "524";
948	break;
949    default:
950	krb5_set_error_message(context, ENOTTY,
951			       N_("unknown krbhst type (%u)", ""), type);
952	return ENOTTY;
953    }
954    if((kd = common_init(context, service, realm, flags)) == NULL)
955	return ENOMEM;
956    kd->get_next = next;
957    kd->def_port = def_port;
958    *handle = kd;
959    return 0;
960}
961
962/*
963 * return the next host information from `handle' in `host'
964 */
965
966KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
967krb5_krbhst_next(krb5_context context,
968		 krb5_krbhst_handle handle,
969		 krb5_krbhst_info **host)
970{
971    if(get_next(handle, host))
972	return 0;
973
974    return (*handle->get_next)(context, handle, host);
975}
976
977/*
978 * return the next host information from `handle' as a host name
979 * in `hostname' (or length `hostlen)
980 */
981
982KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
983krb5_krbhst_next_as_string(krb5_context context,
984			   krb5_krbhst_handle handle,
985			   char *hostname,
986			   size_t hostlen)
987{
988    krb5_error_code ret;
989    krb5_krbhst_info *host;
990    ret = krb5_krbhst_next(context, handle, &host);
991    if(ret)
992	return ret;
993    return krb5_krbhst_format_string(context, host, hostname, hostlen);
994}
995
996
997KRB5_LIB_FUNCTION void KRB5_LIB_CALL
998krb5_krbhst_reset(krb5_context context, krb5_krbhst_handle handle)
999{
1000    handle->index = &handle->hosts;
1001}
1002
1003KRB5_LIB_FUNCTION void KRB5_LIB_CALL
1004krb5_krbhst_free(krb5_context context, krb5_krbhst_handle handle)
1005{
1006    krb5_krbhst_info *h, *next;
1007
1008    if (handle == NULL)
1009	return;
1010
1011    for (h = handle->hosts; h != NULL; h = next) {
1012	next = h->next;
1013	_krb5_free_krbhst_info(h);
1014    }
1015
1016    free(handle->realm);
1017    free(handle);
1018}
1019
1020/* backwards compatibility ahead */
1021
1022static krb5_error_code
1023gethostlist(krb5_context context, const char *realm,
1024	    unsigned int type, char ***hostlist)
1025{
1026    krb5_error_code ret;
1027    int nhost = 0;
1028    krb5_krbhst_handle handle;
1029    char host[MAXHOSTNAMELEN];
1030    krb5_krbhst_info *hostinfo;
1031
1032    ret = krb5_krbhst_init(context, realm, type, &handle);
1033    if (ret)
1034	return ret;
1035
1036    while(krb5_krbhst_next(context, handle, &hostinfo) == 0)
1037	nhost++;
1038    if(nhost == 0) {
1039	krb5_set_error_message(context, KRB5_KDC_UNREACH,
1040			       N_("No KDC found for realm %s", ""), realm);
1041	return KRB5_KDC_UNREACH;
1042    }
1043    *hostlist = calloc(nhost + 1, sizeof(**hostlist));
1044    if(*hostlist == NULL) {
1045	krb5_krbhst_free(context, handle);
1046	return ENOMEM;
1047    }
1048
1049    krb5_krbhst_reset(context, handle);
1050    nhost = 0;
1051    while(krb5_krbhst_next_as_string(context, handle,
1052				     host, sizeof(host)) == 0) {
1053	if(((*hostlist)[nhost++] = strdup(host)) == NULL) {
1054	    krb5_free_krbhst(context, *hostlist);
1055	    krb5_krbhst_free(context, handle);
1056	    return ENOMEM;
1057	}
1058    }
1059    (*hostlist)[nhost] = NULL;
1060    krb5_krbhst_free(context, handle);
1061    return 0;
1062}
1063
1064/*
1065 * return an malloced list of kadmin-hosts for `realm' in `hostlist'
1066 */
1067
1068KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1069krb5_get_krb_admin_hst (krb5_context context,
1070			const krb5_realm *realm,
1071			char ***hostlist)
1072{
1073    return gethostlist(context, *realm, KRB5_KRBHST_ADMIN, hostlist);
1074}
1075
1076/*
1077 * return an malloced list of changepw-hosts for `realm' in `hostlist'
1078 */
1079
1080KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1081krb5_get_krb_changepw_hst (krb5_context context,
1082			   const krb5_realm *realm,
1083			   char ***hostlist)
1084{
1085    return gethostlist(context, *realm, KRB5_KRBHST_CHANGEPW, hostlist);
1086}
1087
1088/*
1089 * return an malloced list of 524-hosts for `realm' in `hostlist'
1090 */
1091
1092KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1093krb5_get_krb524hst (krb5_context context,
1094		    const krb5_realm *realm,
1095		    char ***hostlist)
1096{
1097    return gethostlist(context, *realm, KRB5_KRBHST_KRB524, hostlist);
1098}
1099
1100
1101/*
1102 * return an malloced list of KDC's for `realm' in `hostlist'
1103 */
1104
1105KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1106krb5_get_krbhst (krb5_context context,
1107		 const krb5_realm *realm,
1108		 char ***hostlist)
1109{
1110    return gethostlist(context, *realm, KRB5_KRBHST_KDC, hostlist);
1111}
1112
1113/*
1114 * free all the memory allocated in `hostlist'
1115 */
1116
1117KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1118krb5_free_krbhst (krb5_context context,
1119		  char **hostlist)
1120{
1121    char **p;
1122
1123    for (p = hostlist; *p; ++p)
1124	free (*p);
1125    free (hostlist);
1126    return 0;
1127}
1128