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