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