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