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
38RCSID("$Id: krbhst.c 21457 2007-07-10 12:53:25Z lha $");
39
40static int
41string_to_proto(const char *string)
42{
43    if(strcasecmp(string, "udp") == 0)
44	return KRB5_KRBHST_UDP;
45    else if(strcasecmp(string, "tcp") == 0)
46	return KRB5_KRBHST_TCP;
47    else if(strcasecmp(string, "http") == 0)
48	return KRB5_KRBHST_HTTP;
49    return -1;
50}
51
52/*
53 * set `res' and `count' to the result of looking up SRV RR in DNS for
54 * `proto', `proto', `realm' using `dns_type'.
55 * if `port' != 0, force that port number
56 */
57
58static krb5_error_code
59srv_find_realm(krb5_context context, krb5_krbhst_info ***res, int *count,
60	       const char *realm, const char *dns_type,
61	       const char *proto, const char *service, int port)
62{
63    char domain[1024];
64    struct dns_reply *r;
65    struct resource_record *rr;
66    int num_srv;
67    int proto_num;
68    int def_port;
69
70    *res = NULL;
71    *count = 0;
72
73    proto_num = string_to_proto(proto);
74    if(proto_num < 0) {
75	krb5_set_error_string(context, "unknown protocol `%s'", 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 = dns_lookup(domain, dns_type);
89    if(r == NULL)
90	return KRB5_KDC_UNREACH;
91
92    for(num_srv = 0, rr = r->head; rr; rr = rr->next)
93	if(rr->type == T_SRV)
94	    num_srv++;
95
96    *res = malloc(num_srv * sizeof(**res));
97    if(*res == NULL) {
98	dns_free_data(r);
99	krb5_set_error_string(context, "malloc: out of memory");
100	return ENOMEM;
101    }
102
103    dns_srv_order(r);
104
105    for(num_srv = 0, rr = r->head; rr; rr = rr->next)
106	if(rr->type == T_SRV) {
107	    krb5_krbhst_info *hi;
108	    size_t len = strlen(rr->u.srv->target);
109
110	    hi = calloc(1, sizeof(*hi) + len);
111	    if(hi == NULL) {
112		dns_free_data(r);
113		while(--num_srv >= 0)
114		    free((*res)[num_srv]);
115		free(*res);
116		*res = NULL;
117		return ENOMEM;
118	    }
119	    (*res)[num_srv++] = hi;
120
121	    hi->proto = proto_num;
122
123	    hi->def_port = def_port;
124	    if (port != 0)
125		hi->port = port;
126	    else
127		hi->port = rr->u.srv->port;
128
129	    strlcpy(hi->hostname, rr->u.srv->target, len + 1);
130	}
131
132    *count = num_srv;
133
134    dns_free_data(r);
135    return 0;
136}
137
138
139struct krb5_krbhst_data {
140    char *realm;
141    unsigned int flags;
142    int def_port;
143    int port;			/* hardwired port number if != 0 */
144#define KD_CONFIG		 1
145#define KD_SRV_UDP		 2
146#define KD_SRV_TCP		 4
147#define KD_SRV_HTTP		 8
148#define KD_FALLBACK		16
149#define KD_CONFIG_EXISTS	32
150#define KD_LARGE_MSG		64
151#define KD_PLUGIN	       128
152    krb5_error_code (*get_next)(krb5_context, struct krb5_krbhst_data *,
153				krb5_krbhst_info**);
154
155    unsigned int fallback_count;
156
157    struct krb5_krbhst_info *hosts, **index, **end;
158};
159
160static krb5_boolean
161krbhst_empty(const struct krb5_krbhst_data *kd)
162{
163    return kd->index == &kd->hosts;
164}
165
166/*
167 * Return the default protocol for the `kd' (either TCP or UDP)
168 */
169
170static int
171krbhst_get_default_proto(struct krb5_krbhst_data *kd)
172{
173    if (kd->flags & KD_LARGE_MSG)
174	return KRB5_KRBHST_TCP;
175    return KRB5_KRBHST_UDP;
176}
177
178
179/*
180 * parse `spec' into a krb5_krbhst_info, defaulting the port to `def_port'
181 * and forcing it to `port' if port != 0
182 */
183
184static struct krb5_krbhst_info*
185parse_hostspec(krb5_context context, struct krb5_krbhst_data *kd,
186	       const char *spec, int def_port, int port)
187{
188    const char *p = spec;
189    struct krb5_krbhst_info *hi;
190
191    hi = calloc(1, sizeof(*hi) + strlen(spec));
192    if(hi == NULL)
193	return NULL;
194
195    hi->proto = krbhst_get_default_proto(kd);
196
197    if(strncmp(p, "http://", 7) == 0){
198	hi->proto = KRB5_KRBHST_HTTP;
199	p += 7;
200    } else if(strncmp(p, "http/", 5) == 0) {
201	hi->proto = KRB5_KRBHST_HTTP;
202	p += 5;
203	def_port = ntohs(krb5_getportbyname (context, "http", "tcp", 80));
204    }else if(strncmp(p, "tcp/", 4) == 0){
205	hi->proto = KRB5_KRBHST_TCP;
206	p += 4;
207    } else if(strncmp(p, "udp/", 4) == 0) {
208	p += 4;
209    }
210
211    if(strsep_copy(&p, ":", hi->hostname, strlen(spec) + 1) < 0) {
212	free(hi);
213	return NULL;
214    }
215    /* get rid of trailing /, and convert to lower case */
216    hi->hostname[strcspn(hi->hostname, "/")] = '\0';
217    strlwr(hi->hostname);
218
219    hi->port = hi->def_port = def_port;
220    if(p != NULL) {
221	char *end;
222	hi->port = strtol(p, &end, 0);
223	if(end == p) {
224	    free(hi);
225	    return NULL;
226	}
227    }
228    if (port)
229	hi->port = port;
230    return hi;
231}
232
233void
234_krb5_free_krbhst_info(krb5_krbhst_info *hi)
235{
236    if (hi->ai != NULL)
237	freeaddrinfo(hi->ai);
238    free(hi);
239}
240
241krb5_error_code
242_krb5_krbhost_info_move(krb5_context context,
243			krb5_krbhst_info *from,
244			krb5_krbhst_info **to)
245{
246    size_t hostnamelen = strlen(from->hostname);
247    /* trailing NUL is included in structure */
248    *to = calloc(1, sizeof(**to) + hostnamelen);
249    if(*to == NULL) {
250	krb5_set_error_string(context, "malloc - out of memory");
251	return ENOMEM;
252    }
253
254    (*to)->proto = from->proto;
255    (*to)->port = from->port;
256    (*to)->def_port = from->def_port;
257    (*to)->ai = from->ai;
258    from->ai = NULL;
259    (*to)->next = NULL;
260    memcpy((*to)->hostname, from->hostname, hostnamelen + 1);
261    return 0;
262}
263
264
265static void
266append_host_hostinfo(struct krb5_krbhst_data *kd, struct krb5_krbhst_info *host)
267{
268    struct krb5_krbhst_info *h;
269
270    for(h = kd->hosts; h; h = h->next)
271	if(h->proto == host->proto &&
272	   h->port == host->port &&
273	   strcmp(h->hostname, host->hostname) == 0) {
274	    _krb5_free_krbhst_info(host);
275	    return;
276	}
277    *kd->end = host;
278    kd->end = &host->next;
279}
280
281static krb5_error_code
282append_host_string(krb5_context context, struct krb5_krbhst_data *kd,
283		   const char *host, int def_port, int port)
284{
285    struct krb5_krbhst_info *hi;
286
287    hi = parse_hostspec(context, kd, host, def_port, port);
288    if(hi == NULL)
289	return ENOMEM;
290
291    append_host_hostinfo(kd, hi);
292    return 0;
293}
294
295/*
296 * return a readable representation of `host' in `hostname, hostlen'
297 */
298
299krb5_error_code KRB5_LIB_FUNCTION
300krb5_krbhst_format_string(krb5_context context, const krb5_krbhst_info *host,
301			  char *hostname, size_t hostlen)
302{
303    const char *proto = "";
304    char portstr[7] = "";
305    if(host->proto == KRB5_KRBHST_TCP)
306	proto = "tcp/";
307    else if(host->proto == KRB5_KRBHST_HTTP)
308	proto = "http://";
309    if(host->port != host->def_port)
310	snprintf(portstr, sizeof(portstr), ":%d", host->port);
311    snprintf(hostname, hostlen, "%s%s%s", proto, host->hostname, portstr);
312    return 0;
313}
314
315/*
316 * create a getaddrinfo `hints' based on `proto'
317 */
318
319static void
320make_hints(struct addrinfo *hints, int proto)
321{
322    memset(hints, 0, sizeof(*hints));
323    hints->ai_family = AF_UNSPEC;
324    switch(proto) {
325    case KRB5_KRBHST_UDP :
326	hints->ai_socktype = SOCK_DGRAM;
327	break;
328    case KRB5_KRBHST_HTTP :
329    case KRB5_KRBHST_TCP :
330	hints->ai_socktype = SOCK_STREAM;
331	break;
332    }
333}
334
335/*
336 * return an `struct addrinfo *' in `ai' corresponding to the information
337 * in `host'.  free:ing is handled by krb5_krbhst_free.
338 */
339
340krb5_error_code KRB5_LIB_FUNCTION
341krb5_krbhst_get_addrinfo(krb5_context context, krb5_krbhst_info *host,
342			 struct addrinfo **ai)
343{
344    struct addrinfo hints;
345    char portstr[NI_MAXSERV];
346    int ret;
347
348    if (host->ai == NULL) {
349	make_hints(&hints, host->proto);
350	snprintf (portstr, sizeof(portstr), "%d", host->port);
351	ret = getaddrinfo(host->hostname, portstr, &hints, &host->ai);
352	if (ret)
353	    return krb5_eai_to_heim_errno(ret, errno);
354    }
355    *ai = host->ai;
356    return 0;
357}
358
359static krb5_boolean
360get_next(struct krb5_krbhst_data *kd, krb5_krbhst_info **host)
361{
362    struct krb5_krbhst_info *hi = *kd->index;
363    if(hi != NULL) {
364	*host = hi;
365	kd->index = &(*kd->index)->next;
366	return TRUE;
367    }
368    return FALSE;
369}
370
371static void
372srv_get_hosts(krb5_context context, struct krb5_krbhst_data *kd,
373	      const char *proto, const char *service)
374{
375    krb5_krbhst_info **res;
376    int count, i;
377
378    if (srv_find_realm(context, &res, &count, kd->realm, "SRV", proto, service,
379		       kd->port))
380	return;
381    for(i = 0; i < count; i++)
382	append_host_hostinfo(kd, res[i]);
383    free(res);
384}
385
386/*
387 * read the configuration for `conf_string', defaulting to kd->def_port and
388 * forcing it to `kd->port' if kd->port != 0
389 */
390
391static void
392config_get_hosts(krb5_context context, struct krb5_krbhst_data *kd,
393		 const char *conf_string)
394{
395    int i;
396
397    char **hostlist;
398    hostlist = krb5_config_get_strings(context, NULL,
399				       "realms", kd->realm, conf_string, NULL);
400
401    if(hostlist == NULL)
402	return;
403    kd->flags |= KD_CONFIG_EXISTS;
404    for(i = 0; hostlist && hostlist[i] != NULL; i++)
405	append_host_string(context, kd, hostlist[i], kd->def_port, kd->port);
406
407    krb5_config_free_strings(hostlist);
408}
409
410/*
411 * as a fallback, look for `serv_string.kd->realm' (typically
412 * kerberos.REALM, kerberos-1.REALM, ...
413 * `port' is the default port for the service, and `proto' the
414 * protocol
415 */
416
417static krb5_error_code
418fallback_get_hosts(krb5_context context, struct krb5_krbhst_data *kd,
419		   const char *serv_string, int port, int proto)
420{
421    char *host;
422    int ret;
423    struct addrinfo *ai;
424    struct addrinfo hints;
425    char portstr[NI_MAXSERV];
426
427    /*
428     * Don't try forever in case the DNS server keep returning us
429     * entries (like wildcard entries or the .nu TLD)
430     */
431    if(kd->fallback_count >= 5) {
432	kd->flags |= KD_FALLBACK;
433	return 0;
434    }
435
436    if(kd->fallback_count == 0)
437	asprintf(&host, "%s.%s.", serv_string, kd->realm);
438    else
439	asprintf(&host, "%s-%d.%s.",
440		 serv_string, kd->fallback_count, kd->realm);
441
442    if (host == NULL)
443	return ENOMEM;
444
445    make_hints(&hints, proto);
446    snprintf(portstr, sizeof(portstr), "%d", port);
447    ret = getaddrinfo(host, portstr, &hints, &ai);
448    if (ret) {
449	/* no more hosts, so we're done here */
450	free(host);
451	kd->flags |= KD_FALLBACK;
452    } else {
453	struct krb5_krbhst_info *hi;
454	size_t hostlen = strlen(host);
455
456	hi = calloc(1, sizeof(*hi) + hostlen);
457	if(hi == NULL) {
458	    free(host);
459	    return ENOMEM;
460	}
461
462	hi->proto = proto;
463	hi->port  = hi->def_port = port;
464	hi->ai    = ai;
465	memmove(hi->hostname, host, hostlen);
466	hi->hostname[hostlen] = '\0';
467	free(host);
468	append_host_hostinfo(kd, hi);
469	kd->fallback_count++;
470    }
471    return 0;
472}
473
474/*
475 * Fetch hosts from plugin
476 */
477
478static krb5_error_code
479add_locate(void *ctx, int type, struct sockaddr *addr)
480{
481    struct krb5_krbhst_info *hi;
482    struct krb5_krbhst_data *kd = ctx;
483    char host[NI_MAXHOST], port[NI_MAXSERV];
484    struct addrinfo hints, *ai;
485    socklen_t socklen;
486    size_t hostlen;
487    int ret;
488
489    socklen = socket_sockaddr_size(addr);
490
491    ret = getnameinfo(addr, socklen, host, sizeof(host), port, sizeof(port),
492		      NI_NUMERICHOST|NI_NUMERICSERV);
493    if (ret != 0)
494	return 0;
495
496    make_hints(&hints, krbhst_get_default_proto(kd));
497    ret = getaddrinfo(host, port, &hints, &ai);
498    if (ret)
499	return 0;
500
501    hostlen = strlen(host);
502
503    hi = calloc(1, sizeof(*hi) + hostlen);
504    if(hi == NULL)
505	return ENOMEM;
506
507    hi->proto = krbhst_get_default_proto(kd);
508    hi->port  = hi->def_port = socket_get_port(addr);
509    hi->ai    = ai;
510    memmove(hi->hostname, host, hostlen);
511    hi->hostname[hostlen] = '\0';
512    append_host_hostinfo(kd, hi);
513
514    return 0;
515}
516
517static void
518plugin_get_hosts(krb5_context context,
519		 struct krb5_krbhst_data *kd,
520		 enum locate_service_type type)
521{
522    struct krb5_plugin *list = NULL, *e;
523    krb5_error_code ret;
524
525    ret = _krb5_plugin_find(context, PLUGIN_TYPE_DATA, "resolve", &list);
526    if(ret != 0 || list == NULL)
527	return;
528
529    kd->flags |= KD_CONFIG_EXISTS;
530
531    for (e = list; e != NULL; e = _krb5_plugin_get_next(e)) {
532	krb5plugin_service_locate_ftable *service;
533	void *ctx;
534
535	service = _krb5_plugin_get_symbol(e);
536	if (service->minor_version != 0)
537	    continue;
538
539	(*service->init)(context, &ctx);
540	ret = (*service->lookup)(ctx, type, kd->realm, 0, 0, add_locate, kd);
541	(*service->fini)(ctx);
542	if (ret) {
543	    krb5_set_error_string(context, "Plugin failed to lookup");
544	    break;
545	}
546    }
547    _krb5_plugin_free(list);
548}
549
550/*
551 *
552 */
553
554static krb5_error_code
555kdc_get_next(krb5_context context,
556	     struct krb5_krbhst_data *kd,
557	     krb5_krbhst_info **host)
558{
559    krb5_error_code ret;
560
561    if ((kd->flags & KD_PLUGIN) == 0) {
562	plugin_get_hosts(context, kd, locate_service_kdc);
563	kd->flags |= KD_PLUGIN;
564	if(get_next(kd, host))
565	    return 0;
566    }
567
568    if((kd->flags & KD_CONFIG) == 0) {
569	config_get_hosts(context, kd, "kdc");
570	kd->flags |= KD_CONFIG;
571	if(get_next(kd, host))
572	    return 0;
573    }
574
575    if (kd->flags & KD_CONFIG_EXISTS)
576	return KRB5_KDC_UNREACH; /* XXX */
577
578    if(context->srv_lookup) {
579	if((kd->flags & KD_SRV_UDP) == 0 && (kd->flags & KD_LARGE_MSG) == 0) {
580	    srv_get_hosts(context, kd, "udp", "kerberos");
581	    kd->flags |= KD_SRV_UDP;
582	    if(get_next(kd, host))
583		return 0;
584	}
585
586	if((kd->flags & KD_SRV_TCP) == 0) {
587	    srv_get_hosts(context, kd, "tcp", "kerberos");
588	    kd->flags |= KD_SRV_TCP;
589	    if(get_next(kd, host))
590		return 0;
591	}
592	if((kd->flags & KD_SRV_HTTP) == 0) {
593	    srv_get_hosts(context, kd, "http", "kerberos");
594	    kd->flags |= KD_SRV_HTTP;
595	    if(get_next(kd, host))
596		return 0;
597	}
598    }
599
600    while((kd->flags & KD_FALLBACK) == 0) {
601	ret = fallback_get_hosts(context, kd, "kerberos",
602				 kd->def_port,
603				 krbhst_get_default_proto(kd));
604	if(ret)
605	    return ret;
606	if(get_next(kd, host))
607	    return 0;
608    }
609
610    return KRB5_KDC_UNREACH; /* XXX */
611}
612
613static krb5_error_code
614admin_get_next(krb5_context context,
615	       struct krb5_krbhst_data *kd,
616	       krb5_krbhst_info **host)
617{
618    krb5_error_code ret;
619
620    if ((kd->flags & KD_PLUGIN) == 0) {
621	plugin_get_hosts(context, kd, locate_service_kadmin);
622	kd->flags |= KD_PLUGIN;
623	if(get_next(kd, host))
624	    return 0;
625    }
626
627    if((kd->flags & KD_CONFIG) == 0) {
628	config_get_hosts(context, kd, "admin_server");
629	kd->flags |= KD_CONFIG;
630	if(get_next(kd, host))
631	    return 0;
632    }
633
634    if (kd->flags & KD_CONFIG_EXISTS)
635	return KRB5_KDC_UNREACH; /* XXX */
636
637    if(context->srv_lookup) {
638	if((kd->flags & KD_SRV_TCP) == 0) {
639	    srv_get_hosts(context, kd, "tcp", "kerberos-adm");
640	    kd->flags |= KD_SRV_TCP;
641	    if(get_next(kd, host))
642		return 0;
643	}
644    }
645
646    if (krbhst_empty(kd)
647	&& (kd->flags & KD_FALLBACK) == 0) {
648	ret = fallback_get_hosts(context, kd, "kerberos",
649				 kd->def_port,
650				 krbhst_get_default_proto(kd));
651	if(ret)
652	    return ret;
653	kd->flags |= KD_FALLBACK;
654	if(get_next(kd, host))
655	    return 0;
656    }
657
658    return KRB5_KDC_UNREACH;	/* XXX */
659}
660
661static krb5_error_code
662kpasswd_get_next(krb5_context context,
663		 struct krb5_krbhst_data *kd,
664		 krb5_krbhst_info **host)
665{
666    krb5_error_code ret;
667
668    if ((kd->flags & KD_PLUGIN) == 0) {
669	plugin_get_hosts(context, kd, locate_service_kpasswd);
670	kd->flags |= KD_PLUGIN;
671	if(get_next(kd, host))
672	    return 0;
673    }
674
675    if((kd->flags & KD_CONFIG) == 0) {
676	config_get_hosts(context, kd, "kpasswd_server");
677	kd->flags |= KD_CONFIG;
678	if(get_next(kd, host))
679	    return 0;
680    }
681
682    if (kd->flags & KD_CONFIG_EXISTS)
683	return KRB5_KDC_UNREACH; /* XXX */
684
685    if(context->srv_lookup) {
686	if((kd->flags & KD_SRV_UDP) == 0) {
687	    srv_get_hosts(context, kd, "udp", "kpasswd");
688	    kd->flags |= KD_SRV_UDP;
689	    if(get_next(kd, host))
690		return 0;
691	}
692	if((kd->flags & KD_SRV_TCP) == 0) {
693	    srv_get_hosts(context, kd, "tcp", "kpasswd");
694	    kd->flags |= KD_SRV_TCP;
695	    if(get_next(kd, host))
696		return 0;
697	}
698    }
699
700    /* no matches -> try admin */
701
702    if (krbhst_empty(kd)) {
703	kd->flags = 0;
704	kd->port  = kd->def_port;
705	kd->get_next = admin_get_next;
706	ret = (*kd->get_next)(context, kd, host);
707	if (ret == 0)
708	    (*host)->proto = krbhst_get_default_proto(kd);
709	return ret;
710    }
711
712    return KRB5_KDC_UNREACH; /* XXX */
713}
714
715static krb5_error_code
716krb524_get_next(krb5_context context,
717		struct krb5_krbhst_data *kd,
718		krb5_krbhst_info **host)
719{
720    if ((kd->flags & KD_PLUGIN) == 0) {
721	plugin_get_hosts(context, kd, locate_service_krb524);
722	kd->flags |= KD_PLUGIN;
723	if(get_next(kd, host))
724	    return 0;
725    }
726
727    if((kd->flags & KD_CONFIG) == 0) {
728	config_get_hosts(context, kd, "krb524_server");
729	if(get_next(kd, host))
730	    return 0;
731	kd->flags |= KD_CONFIG;
732    }
733
734    if (kd->flags & KD_CONFIG_EXISTS)
735	return KRB5_KDC_UNREACH; /* XXX */
736
737    if(context->srv_lookup) {
738	if((kd->flags & KD_SRV_UDP) == 0) {
739	    srv_get_hosts(context, kd, "udp", "krb524");
740	    kd->flags |= KD_SRV_UDP;
741	    if(get_next(kd, host))
742		return 0;
743	}
744
745	if((kd->flags & KD_SRV_TCP) == 0) {
746	    srv_get_hosts(context, kd, "tcp", "krb524");
747	    kd->flags |= KD_SRV_TCP;
748	    if(get_next(kd, host))
749		return 0;
750	}
751    }
752
753    /* no matches -> try kdc */
754
755    if (krbhst_empty(kd)) {
756	kd->flags = 0;
757	kd->port  = kd->def_port;
758	kd->get_next = kdc_get_next;
759	return (*kd->get_next)(context, kd, host);
760    }
761
762    return KRB5_KDC_UNREACH; /* XXX */
763}
764
765static struct krb5_krbhst_data*
766common_init(krb5_context context,
767	    const char *realm,
768	    int flags)
769{
770    struct krb5_krbhst_data *kd;
771
772    if((kd = calloc(1, sizeof(*kd))) == NULL)
773	return NULL;
774
775    if((kd->realm = strdup(realm)) == NULL) {
776	free(kd);
777	return NULL;
778    }
779
780    /* For 'realms' without a . do not even think of going to DNS */
781    if (!strchr(realm, '.'))
782	kd->flags |= KD_CONFIG_EXISTS;
783
784    if (flags & KRB5_KRBHST_FLAGS_LARGE_MSG)
785	kd->flags |= KD_LARGE_MSG;
786    kd->end = kd->index = &kd->hosts;
787    return kd;
788}
789
790/*
791 * initialize `handle' to look for hosts of type `type' in realm `realm'
792 */
793
794krb5_error_code KRB5_LIB_FUNCTION
795krb5_krbhst_init(krb5_context context,
796		 const char *realm,
797		 unsigned int type,
798		 krb5_krbhst_handle *handle)
799{
800    return krb5_krbhst_init_flags(context, realm, type, 0, handle);
801}
802
803krb5_error_code KRB5_LIB_FUNCTION
804krb5_krbhst_init_flags(krb5_context context,
805		       const char *realm,
806		       unsigned int type,
807		       int flags,
808		       krb5_krbhst_handle *handle)
809{
810    struct krb5_krbhst_data *kd;
811    krb5_error_code (*next)(krb5_context, struct krb5_krbhst_data *,
812			    krb5_krbhst_info **);
813    int def_port;
814
815    switch(type) {
816    case KRB5_KRBHST_KDC:
817	next = kdc_get_next;
818	def_port = ntohs(krb5_getportbyname (context, "kerberos", "udp", 88));
819	break;
820    case KRB5_KRBHST_ADMIN:
821	next = admin_get_next;
822	def_port = ntohs(krb5_getportbyname (context, "kerberos-adm",
823					     "tcp", 749));
824	break;
825    case KRB5_KRBHST_CHANGEPW:
826	next = kpasswd_get_next;
827	def_port = ntohs(krb5_getportbyname (context, "kpasswd", "udp",
828					     KPASSWD_PORT));
829	break;
830    case KRB5_KRBHST_KRB524:
831	next = krb524_get_next;
832	def_port = ntohs(krb5_getportbyname (context, "krb524", "udp", 4444));
833	break;
834    default:
835	krb5_set_error_string(context, "unknown krbhst type (%u)", type);
836	return ENOTTY;
837    }
838    if((kd = common_init(context, realm, flags)) == NULL)
839	return ENOMEM;
840    kd->get_next = next;
841    kd->def_port = def_port;
842    *handle = kd;
843    return 0;
844}
845
846/*
847 * return the next host information from `handle' in `host'
848 */
849
850krb5_error_code KRB5_LIB_FUNCTION
851krb5_krbhst_next(krb5_context context,
852		 krb5_krbhst_handle handle,
853		 krb5_krbhst_info **host)
854{
855    if(get_next(handle, host))
856	return 0;
857
858    return (*handle->get_next)(context, handle, host);
859}
860
861/*
862 * return the next host information from `handle' as a host name
863 * in `hostname' (or length `hostlen)
864 */
865
866krb5_error_code KRB5_LIB_FUNCTION
867krb5_krbhst_next_as_string(krb5_context context,
868			   krb5_krbhst_handle handle,
869			   char *hostname,
870			   size_t hostlen)
871{
872    krb5_error_code ret;
873    krb5_krbhst_info *host;
874    ret = krb5_krbhst_next(context, handle, &host);
875    if(ret)
876	return ret;
877    return krb5_krbhst_format_string(context, host, hostname, hostlen);
878}
879
880
881void KRB5_LIB_FUNCTION
882krb5_krbhst_reset(krb5_context context, krb5_krbhst_handle handle)
883{
884    handle->index = &handle->hosts;
885}
886
887void KRB5_LIB_FUNCTION
888krb5_krbhst_free(krb5_context context, krb5_krbhst_handle handle)
889{
890    krb5_krbhst_info *h, *next;
891
892    if (handle == NULL)
893	return;
894
895    for (h = handle->hosts; h != NULL; h = next) {
896	next = h->next;
897	_krb5_free_krbhst_info(h);
898    }
899
900    free(handle->realm);
901    free(handle);
902}
903
904/* backwards compatibility ahead */
905
906static krb5_error_code
907gethostlist(krb5_context context, const char *realm,
908	    unsigned int type, char ***hostlist)
909{
910    krb5_error_code ret;
911    int nhost = 0;
912    krb5_krbhst_handle handle;
913    char host[MAXHOSTNAMELEN];
914    krb5_krbhst_info *hostinfo;
915
916    ret = krb5_krbhst_init(context, realm, type, &handle);
917    if (ret)
918	return ret;
919
920    while(krb5_krbhst_next(context, handle, &hostinfo) == 0)
921	nhost++;
922    if(nhost == 0) {
923	krb5_set_error_string(context, "No KDC found for realm %s", realm);
924	return KRB5_KDC_UNREACH;
925    }
926    *hostlist = calloc(nhost + 1, sizeof(**hostlist));
927    if(*hostlist == NULL) {
928	krb5_krbhst_free(context, handle);
929	return ENOMEM;
930    }
931
932    krb5_krbhst_reset(context, handle);
933    nhost = 0;
934    while(krb5_krbhst_next_as_string(context, handle,
935				     host, sizeof(host)) == 0) {
936	if(((*hostlist)[nhost++] = strdup(host)) == NULL) {
937	    krb5_free_krbhst(context, *hostlist);
938	    krb5_krbhst_free(context, handle);
939	    return ENOMEM;
940	}
941    }
942    (*hostlist)[nhost++] = NULL;
943    krb5_krbhst_free(context, handle);
944    return 0;
945}
946
947/*
948 * return an malloced list of kadmin-hosts for `realm' in `hostlist'
949 */
950
951krb5_error_code KRB5_LIB_FUNCTION
952krb5_get_krb_admin_hst (krb5_context context,
953			const krb5_realm *realm,
954			char ***hostlist)
955{
956    return gethostlist(context, *realm, KRB5_KRBHST_ADMIN, hostlist);
957}
958
959/*
960 * return an malloced list of changepw-hosts for `realm' in `hostlist'
961 */
962
963krb5_error_code KRB5_LIB_FUNCTION
964krb5_get_krb_changepw_hst (krb5_context context,
965			   const krb5_realm *realm,
966			   char ***hostlist)
967{
968    return gethostlist(context, *realm, KRB5_KRBHST_CHANGEPW, hostlist);
969}
970
971/*
972 * return an malloced list of 524-hosts for `realm' in `hostlist'
973 */
974
975krb5_error_code KRB5_LIB_FUNCTION
976krb5_get_krb524hst (krb5_context context,
977		    const krb5_realm *realm,
978		    char ***hostlist)
979{
980    return gethostlist(context, *realm, KRB5_KRBHST_KRB524, hostlist);
981}
982
983
984/*
985 * return an malloced list of KDC's for `realm' in `hostlist'
986 */
987
988krb5_error_code KRB5_LIB_FUNCTION
989krb5_get_krbhst (krb5_context context,
990		 const krb5_realm *realm,
991		 char ***hostlist)
992{
993    return gethostlist(context, *realm, KRB5_KRBHST_KDC, hostlist);
994}
995
996/*
997 * free all the memory allocated in `hostlist'
998 */
999
1000krb5_error_code KRB5_LIB_FUNCTION
1001krb5_free_krbhst (krb5_context context,
1002		  char **hostlist)
1003{
1004    char **p;
1005
1006    for (p = hostlist; *p; ++p)
1007	free (*p);
1008    free (hostlist);
1009    return 0;
1010}
1011