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