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