1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/os/locate_kdc.c - Get addresses for realm KDCs and other servers */
3 /*
4 * Copyright 1990,2000,2001,2002,2003,2004,2006,2008 Massachusetts Institute of
5 * Technology. All Rights Reserved.
6 *
7 * Export of this software from the United States of America may
8 * require a specific license from the United States Government.
9 * It is the responsibility of any person or organization contemplating
10 * export to obtain such a license before exporting.
11 *
12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13 * distribute this software and its documentation for any purpose and
14 * without fee is hereby granted, provided that the above copyright
15 * notice appear in all copies and that both that copyright notice and
16 * this permission notice appear in supporting documentation, and that
17 * the name of M.I.T. not be used in advertising or publicity pertaining
18 * to distribution of the software without specific, written prior
19 * permission. Furthermore if you modify this software you must label
20 * your software as modified software and not distribute it in such a
21 * fashion that it might be confused with the original M.I.T. software.
22 * M.I.T. makes no representations about the suitability of
23 * this software for any purpose. It is provided "as is" without express
24 * or implied warranty.
25 */
26
27 #include "k5-int.h"
28 #include "fake-addrinfo.h"
29 #include "os-proto.h"
30
31 #ifdef KRB5_DNS_LOOKUP
32
33 #define DEFAULT_LOOKUP_KDC 1
34 #if KRB5_DNS_LOOKUP_REALM
35 #define DEFAULT_LOOKUP_REALM 1
36 #else
37 #define DEFAULT_LOOKUP_REALM 0
38 #endif
39 #define DEFAULT_URI_LOOKUP TRUE
40
41 struct kdclist_entry {
42 krb5_data realm;
43 struct server_entry server;
44 };
45
46 struct kdclist {
47 size_t count;
48 struct kdclist_entry *list;
49 };
50
51 static int
maybe_use_dns(krb5_context context,const char * name,int defalt)52 maybe_use_dns (krb5_context context, const char *name, int defalt)
53 {
54 krb5_error_code code;
55 char * value = NULL;
56 int use_dns = 0;
57
58 code = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
59 name, 0, 0, &value);
60 if (value == 0 && code == 0) {
61 code = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
62 KRB5_CONF_DNS_FALLBACK, 0, 0, &value);
63 }
64 if (code)
65 return defalt;
66
67 if (value == 0)
68 return defalt;
69
70 use_dns = _krb5_conf_boolean(value);
71 profile_release_string(value);
72 return use_dns;
73 }
74
75 static krb5_boolean
use_dns_uri(krb5_context ctx)76 use_dns_uri(krb5_context ctx)
77 {
78 krb5_error_code ret;
79 int use;
80
81 ret = profile_get_boolean(ctx->profile, KRB5_CONF_LIBDEFAULTS,
82 KRB5_CONF_DNS_URI_LOOKUP, NULL,
83 DEFAULT_URI_LOOKUP, &use);
84 return ret ? DEFAULT_URI_LOOKUP : use;
85 }
86
87 int
_krb5_use_dns_kdc(krb5_context context)88 _krb5_use_dns_kdc(krb5_context context)
89 {
90 return maybe_use_dns(context, KRB5_CONF_DNS_LOOKUP_KDC,
91 DEFAULT_LOOKUP_KDC);
92 }
93
94 int
_krb5_use_dns_realm(krb5_context context)95 _krb5_use_dns_realm(krb5_context context)
96 {
97 return maybe_use_dns(context, KRB5_CONF_DNS_LOOKUP_REALM,
98 DEFAULT_LOOKUP_REALM);
99 }
100
101 static krb5_error_code
get_sitename(krb5_context context,const krb5_data * realm,char ** out)102 get_sitename(krb5_context context, const krb5_data *realm, char **out)
103 {
104 krb5_error_code ret;
105 char *realmstr;
106
107 *out = NULL;
108 realmstr = k5memdup0(realm->data, realm->length, &ret);
109 if (realmstr == NULL)
110 return ret;
111 ret = profile_get_string(context->profile, KRB5_CONF_REALMS,
112 realmstr, KRB5_CONF_SITENAME, NULL, out);
113 free(realmstr);
114 return ret;
115 }
116
117 #endif /* KRB5_DNS_LOOKUP */
118
119 /* Free up everything pointed to by the serverlist structure, but don't
120 * free the structure itself. */
121 void
k5_free_serverlist(struct serverlist * list)122 k5_free_serverlist (struct serverlist *list)
123 {
124 size_t i;
125
126 for (i = 0; i < list->nservers; i++) {
127 free(list->servers[i].hostname);
128 free(list->servers[i].uri_path);
129 }
130 free(list->servers);
131 list->servers = NULL;
132 list->nservers = 0;
133 }
134
135 #include <stdarg.h>
136 static inline void
Tprintf(const char * fmt,...)137 Tprintf(const char *fmt, ...)
138 {
139 #ifdef TEST
140 va_list ap;
141 va_start(ap, fmt);
142 vfprintf(stderr, fmt, ap);
143 va_end(ap);
144 #endif
145 }
146
147 /* Make room for a new server entry in list and return a pointer to the new
148 * entry. (Do not increment list->nservers.) */
149 static struct server_entry *
new_server_entry(struct serverlist * list)150 new_server_entry(struct serverlist *list)
151 {
152 struct server_entry *newservers, *entry;
153 size_t newspace = (list->nservers + 1) * sizeof(struct server_entry);
154
155 newservers = realloc(list->servers, newspace);
156 if (newservers == NULL)
157 return NULL;
158 list->servers = newservers;
159 entry = &newservers[list->nservers];
160 memset(entry, 0, sizeof(*entry));
161 entry->primary = -1;
162 return entry;
163 }
164
165 /* Add an address entry to list. */
166 static int
add_addr_to_list(struct serverlist * list,k5_transport transport,int family,size_t addrlen,struct sockaddr * addr)167 add_addr_to_list(struct serverlist *list, k5_transport transport, int family,
168 size_t addrlen, struct sockaddr *addr)
169 {
170 struct server_entry *entry;
171
172 entry = new_server_entry(list);
173 if (entry == NULL)
174 return ENOMEM;
175 entry->transport = transport;
176 entry->family = family;
177 entry->hostname = NULL;
178 entry->uri_path = NULL;
179 entry->addrlen = addrlen;
180 memcpy(&entry->addr, addr, addrlen);
181 list->nservers++;
182 return 0;
183 }
184
185 /* Add a hostname entry to list. */
186 static int
add_host_to_list(struct serverlist * list,const char * hostname,int port,k5_transport transport,int family,const char * uri_path,int primary)187 add_host_to_list(struct serverlist *list, const char *hostname, int port,
188 k5_transport transport, int family, const char *uri_path,
189 int primary)
190 {
191 struct server_entry *entry;
192
193 entry = new_server_entry(list);
194 if (entry == NULL)
195 return ENOMEM;
196 entry->transport = transport;
197 entry->family = family;
198 entry->hostname = strdup(hostname);
199 if (entry->hostname == NULL)
200 goto oom;
201 if (uri_path != NULL) {
202 entry->uri_path = strdup(uri_path);
203 if (entry->uri_path == NULL)
204 goto oom;
205 }
206 entry->port = port;
207 entry->primary = primary;
208 list->nservers++;
209 return 0;
210 oom:
211 free(entry->hostname);
212 entry->hostname = NULL;
213 return ENOMEM;
214 }
215
216 static void
parse_uri_if_https(const char * host_or_uri,k5_transport * transport,const char ** host,const char ** uri_path)217 parse_uri_if_https(const char *host_or_uri, k5_transport *transport,
218 const char **host, const char **uri_path)
219 {
220 char *cp;
221
222 if (strncmp(host_or_uri, "https://", 8) == 0) {
223 *transport = HTTPS;
224 *host = host_or_uri + 8;
225
226 cp = strchr(*host, '/');
227 if (cp != NULL) {
228 *cp = '\0';
229 *uri_path = cp + 1;
230 }
231 }
232 }
233
234 /* Return true if server is identical to an entry in list. */
235 static krb5_boolean
server_list_contains(struct serverlist * list,struct server_entry * server)236 server_list_contains(struct serverlist *list, struct server_entry *server)
237 {
238 struct server_entry *ent;
239
240 for (ent = list->servers; ent < list->servers + list->nservers; ent++) {
241 if (server->port != ent->port)
242 continue;
243 if (server->hostname != NULL && ent->hostname != NULL &&
244 strcmp(server->hostname, ent->hostname) == 0)
245 return TRUE;
246 if (server->hostname == NULL && ent->hostname == NULL &&
247 server->addrlen == ent->addrlen &&
248 memcmp(&server->addr, &ent->addr, server->addrlen) == 0)
249 return TRUE;
250 }
251 return FALSE;
252 }
253
254 static krb5_error_code
locate_srv_conf_1(krb5_context context,const krb5_data * realm,const char * name,struct serverlist * serverlist,k5_transport transport,int udpport)255 locate_srv_conf_1(krb5_context context, const krb5_data *realm,
256 const char * name, struct serverlist *serverlist,
257 k5_transport transport, int udpport)
258 {
259 const char *realm_srv_names[4];
260 char **hostlist = NULL, *realmstr = NULL, *host = NULL;
261 const char *hostspec;
262 krb5_error_code code;
263 size_t i;
264 int default_port;
265
266 Tprintf("looking in krb5.conf for realm %s entry %s; ports %d,%d\n",
267 realm->data, name, udpport);
268
269 realmstr = k5memdup0(realm->data, realm->length, &code);
270 if (realmstr == NULL)
271 goto cleanup;
272
273 realm_srv_names[0] = KRB5_CONF_REALMS;
274 realm_srv_names[1] = realmstr;
275 realm_srv_names[2] = name;
276 realm_srv_names[3] = 0;
277 code = profile_get_values(context->profile, realm_srv_names, &hostlist);
278 if (code == PROF_NO_RELATION && strcmp(name, KRB5_CONF_PRIMARY_KDC) == 0) {
279 realm_srv_names[2] = KRB5_CONF_MASTER_KDC;
280 code = profile_get_values(context->profile, realm_srv_names,
281 &hostlist);
282 }
283 if (code) {
284 Tprintf("config file lookup failed: %s\n", error_message(code));
285 if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
286 code = 0;
287 goto cleanup;
288 }
289
290 for (i = 0; hostlist[i]; i++) {
291 int port_num;
292 k5_transport this_transport = transport;
293 const char *uri_path = NULL;
294
295 hostspec = hostlist[i];
296 Tprintf("entry %d is '%s'\n", i, hostspec);
297
298 #ifndef _WIN32
299 if (hostspec[0] == '/') {
300 struct sockaddr_un sun = { 0 };
301
302 sun.sun_family = AF_UNIX;
303 if (strlcpy(sun.sun_path, hostspec, sizeof(sun.sun_path)) >=
304 sizeof(sun.sun_path)) {
305 code = ENAMETOOLONG;
306 goto cleanup;
307 }
308 code = add_addr_to_list(serverlist, UNIXSOCK, AF_UNIX, sizeof(sun),
309 (struct sockaddr *)&sun);
310 if (code)
311 goto cleanup;
312 continue;
313 }
314 #endif
315 parse_uri_if_https(hostspec, &this_transport, &hostspec, &uri_path);
316
317 default_port = (this_transport == HTTPS) ? 443 : udpport;
318 code = k5_parse_host_string(hostspec, default_port, &host, &port_num);
319 if (code == 0 && host == NULL)
320 code = EINVAL;
321 if (code)
322 goto cleanup;
323
324 code = add_host_to_list(serverlist, host, port_num, this_transport,
325 AF_UNSPEC, uri_path, -1);
326 if (code)
327 goto cleanup;
328
329 free(host);
330 host = NULL;
331 }
332
333 cleanup:
334 free(realmstr);
335 free(host);
336 profile_free_list(hostlist);
337 return code;
338 }
339
340 #ifdef TEST
341 static krb5_error_code
krb5_locate_srv_conf(krb5_context context,const krb5_data * realm,const char * name,struct serverlist * al,int udpport)342 krb5_locate_srv_conf(krb5_context context, const krb5_data *realm,
343 const char *name, struct serverlist *al, int udpport)
344 {
345 krb5_error_code ret;
346
347 ret = locate_srv_conf_1(context, realm, name, al, TCP_OR_UDP, udpport);
348 if (ret)
349 return ret;
350 if (al->nservers == 0) /* Couldn't resolve any KDC names */
351 return KRB5_REALM_CANT_RESOLVE;
352 return 0;
353 }
354 #endif
355
356 #ifdef KRB5_DNS_LOOKUP
357 static krb5_error_code
locate_srv_dns_1(krb5_context context,const krb5_data * realm,const char * service,const char * protocol,struct serverlist * serverlist)358 locate_srv_dns_1(krb5_context context, const krb5_data *realm,
359 const char *service, const char *protocol,
360 struct serverlist *serverlist)
361 {
362 struct srv_dns_entry *head = NULL, *entry = NULL;
363 krb5_error_code code = 0;
364 k5_transport transport;
365 char *sitename;
366
367 code = get_sitename(context, realm, &sitename);
368 if (code)
369 return code;
370 code = krb5int_make_srv_query_realm(context, realm, service, protocol,
371 sitename, &head);
372 free(sitename);
373 if (code)
374 return 0;
375
376 if (head == NULL)
377 return 0;
378
379 /* Check for the "." case indicating no support. */
380 if (head->next == NULL && head->host[0] == '\0') {
381 code = KRB5_ERR_NO_SERVICE;
382 goto cleanup;
383 }
384
385 for (entry = head; entry != NULL; entry = entry->next) {
386 transport = (strcmp(protocol, "_tcp") == 0) ? TCP : UDP;
387 code = add_host_to_list(serverlist, entry->host, entry->port,
388 transport, AF_UNSPEC, NULL, -1);
389 if (code)
390 goto cleanup;
391 }
392
393 cleanup:
394 krb5int_free_srv_dns_data(head);
395 return code;
396 }
397 #endif
398
399 #include <krb5/locate_plugin.h>
400
401 #if TARGET_OS_MAC
402 static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR,
403 LIBDIR "/krb5/plugins/libkrb5",
404 NULL }; /* should be a list */
405 #else
406 static const char *objdirs[] = { LIBDIR "/krb5/plugins/libkrb5", NULL };
407 #endif
408
409 struct module_callback_data {
410 int out_of_mem;
411 struct serverlist *list;
412 };
413
414 static int
module_callback(void * cbdata,int socktype,struct sockaddr * sa)415 module_callback(void *cbdata, int socktype, struct sockaddr *sa)
416 {
417 struct module_callback_data *d = cbdata;
418 size_t addrlen;
419 k5_transport transport;
420
421 if (socktype != SOCK_STREAM && socktype != SOCK_DGRAM)
422 return 0;
423 if (sa->sa_family == AF_INET)
424 addrlen = sizeof(struct sockaddr_in);
425 else if (sa->sa_family == AF_INET6)
426 addrlen = sizeof(struct sockaddr_in6);
427 else
428 return 0;
429 transport = (socktype == SOCK_STREAM) ? TCP : UDP;
430 if (add_addr_to_list(d->list, transport, sa->sa_family, addrlen,
431 sa) != 0) {
432 /* Assumes only error is ENOMEM. */
433 d->out_of_mem = 1;
434 return 1;
435 }
436 return 0;
437 }
438
439 static krb5_error_code
module_locate_server(krb5_context ctx,const krb5_data * realm,struct serverlist * serverlist,enum locate_service_type svc,k5_transport transport)440 module_locate_server(krb5_context ctx, const krb5_data *realm,
441 struct serverlist *serverlist,
442 enum locate_service_type svc, k5_transport transport)
443 {
444 struct krb5plugin_service_locate_result *res = NULL;
445 krb5_error_code code;
446 struct krb5plugin_service_locate_ftable *vtbl = NULL;
447 void **ptrs;
448 char *realmz; /* NUL-terminated realm */
449 size_t i;
450 int socktype;
451 struct module_callback_data cbdata = { 0, };
452 const char *msg;
453
454 Tprintf("in module_locate_server\n");
455 cbdata.list = serverlist;
456 if (!PLUGIN_DIR_OPEN(&ctx->libkrb5_plugins)) {
457
458 code = krb5int_open_plugin_dirs(objdirs, NULL, &ctx->libkrb5_plugins,
459 &ctx->err);
460 if (code)
461 return KRB5_PLUGIN_NO_HANDLE;
462 }
463
464 code = krb5int_get_plugin_dir_data(&ctx->libkrb5_plugins,
465 "service_locator", &ptrs, &ctx->err);
466 if (code) {
467 Tprintf("error looking up plugin symbols: %s\n",
468 (msg = krb5_get_error_message(ctx, code)));
469 krb5_free_error_message(ctx, msg);
470 return KRB5_PLUGIN_NO_HANDLE;
471 }
472
473 if (realm->length >= UINT_MAX) {
474 krb5int_free_plugin_dir_data(ptrs);
475 return ENOMEM;
476 }
477 realmz = k5memdup0(realm->data, realm->length, &code);
478 if (realmz == NULL) {
479 krb5int_free_plugin_dir_data(ptrs);
480 return code;
481 }
482 for (i = 0; ptrs[i]; i++) {
483 void *blob;
484
485 vtbl = ptrs[i];
486 Tprintf("element %d is %p\n", i, ptrs[i]);
487
488 /* For now, don't keep the plugin data alive. For long-lived
489 * contexts, it may be desirable to change that later. */
490 code = vtbl->init(ctx, &blob);
491 if (code)
492 continue;
493
494 socktype = (transport == TCP) ? SOCK_STREAM : SOCK_DGRAM;
495 code = vtbl->lookup(blob, svc, realmz, socktype, AF_UNSPEC,
496 module_callback, &cbdata);
497 /* Also ask for TCP addresses if we got UDP addresses and want both. */
498 if (code == 0 && transport == TCP_OR_UDP) {
499 code = vtbl->lookup(blob, svc, realmz, SOCK_STREAM, AF_UNSPEC,
500 module_callback, &cbdata);
501 if (code == KRB5_PLUGIN_NO_HANDLE)
502 code = 0;
503 }
504 vtbl->fini(blob);
505 if (code == KRB5_PLUGIN_NO_HANDLE) {
506 /* Module passes, keep going. */
507 /* XXX */
508 Tprintf("plugin doesn't handle this realm (KRB5_PLUGIN_NO_HANDLE)"
509 "\n");
510 continue;
511 }
512 if (code != 0) {
513 /* Module encountered an actual error. */
514 Tprintf("plugin lookup routine returned error %d: %s\n",
515 code, error_message(code));
516 free(realmz);
517 krb5int_free_plugin_dir_data(ptrs);
518 return code;
519 }
520 break;
521 }
522 if (ptrs[i] == NULL) {
523 Tprintf("ran off end of plugin list\n");
524 free(realmz);
525 krb5int_free_plugin_dir_data(ptrs);
526 return KRB5_PLUGIN_NO_HANDLE;
527 }
528 Tprintf("stopped with plugin #%d, res=%p\n", i, res);
529
530 /* Got something back, yippee. */
531 Tprintf("now have %lu addrs in list %p\n",
532 (unsigned long)serverlist->nservers, serverlist);
533 free(realmz);
534 krb5int_free_plugin_dir_data(ptrs);
535 return 0;
536 }
537
538 static krb5_error_code
prof_locate_server(krb5_context context,const krb5_data * realm,struct serverlist * serverlist,enum locate_service_type svc,k5_transport transport)539 prof_locate_server(krb5_context context, const krb5_data *realm,
540 struct serverlist *serverlist, enum locate_service_type svc,
541 k5_transport transport)
542 {
543 const char *profname;
544 int dflport = 0;
545 struct servent *serv;
546
547 switch (svc) {
548 case locate_service_kdc:
549 profname = KRB5_CONF_KDC;
550 /* We used to use /etc/services for these, but enough systems have old,
551 * crufty, wrong settings that this is probably better. */
552 kdc_ports:
553 dflport = KRB5_DEFAULT_PORT;
554 break;
555 case locate_service_primary_kdc:
556 profname = KRB5_CONF_PRIMARY_KDC;
557 goto kdc_ports;
558 case locate_service_kadmin:
559 profname = KRB5_CONF_ADMIN_SERVER;
560 dflport = DEFAULT_KADM5_PORT;
561 break;
562 case locate_service_krb524:
563 profname = KRB5_CONF_KRB524_SERVER;
564 serv = getservbyname("krb524", "udp");
565 dflport = serv ? serv->s_port : 4444;
566 break;
567 case locate_service_kpasswd:
568 profname = KRB5_CONF_KPASSWD_SERVER;
569 dflport = DEFAULT_KPASSWD_PORT;
570 break;
571 default:
572 return EBUSY; /* XXX */
573 }
574
575 return locate_srv_conf_1(context, realm, profname, serverlist, transport,
576 dflport);
577 }
578
579 #ifdef KRB5_DNS_LOOKUP
580
581 /*
582 * Parse the initial part of the URI, first confirming the scheme name. Get
583 * the transport, flags (indicating primary status), and host. The host is
584 * either an address or hostname with an optional port, or an HTTPS URL.
585 * The format is krb5srv:flags:udp|tcp|kkdcp:host
586 *
587 * Return a NULL *host_out if there are any problems parsing the URI.
588 */
589 static void
parse_uri_fields(const char * uri,k5_transport * transport_out,const char ** host_out,int * primary_out)590 parse_uri_fields(const char *uri, k5_transport *transport_out,
591 const char **host_out, int *primary_out)
592
593 {
594 k5_transport transport;
595 int primary = FALSE;
596
597 *transport_out = 0;
598 *host_out = NULL;
599 *primary_out = -1;
600
601 /* Confirm the scheme name. */
602 if (strncasecmp(uri, "krb5srv", 7) != 0)
603 return;
604
605 uri += 7;
606 if (*uri != ':')
607 return;
608
609 uri++;
610 if (*uri == '\0')
611 return;
612
613 /* Check the flags field for supported flags. */
614 for (; *uri != ':' && *uri != '\0'; uri++) {
615 if (*uri == 'm' || *uri == 'M')
616 primary = TRUE;
617 }
618 if (*uri != ':')
619 return;
620
621 /* Look for the transport type. */
622 uri++;
623 if (strncasecmp(uri, "udp", 3) == 0) {
624 transport = UDP;
625 uri += 3;
626 } else if (strncasecmp(uri, "tcp", 3) == 0) {
627 transport = TCP;
628 uri += 3;
629 } else if (strncasecmp(uri, "kkdcp", 5) == 0) {
630 /* Currently the only MS-KKDCP transport type is HTTPS. */
631 transport = HTTPS;
632 uri += 5;
633 } else {
634 return;
635 }
636
637 if (*uri != ':')
638 return;
639
640 /* The rest of the URI is the host (with optional port) or URI. */
641 *host_out = uri + 1;
642 *transport_out = transport;
643 *primary_out = primary;
644 }
645
646 /*
647 * Collect a list of servers from DNS URI records, for the requested service
648 * and transport type. Problematic entries are skipped.
649 */
650 static krb5_error_code
locate_uri(krb5_context context,const krb5_data * realm,const char * req_service,struct serverlist * serverlist,k5_transport req_transport,int default_port,krb5_boolean primary_only)651 locate_uri(krb5_context context, const krb5_data *realm,
652 const char *req_service, struct serverlist *serverlist,
653 k5_transport req_transport, int default_port,
654 krb5_boolean primary_only)
655 {
656 krb5_error_code ret;
657 k5_transport transport, host_trans;
658 struct srv_dns_entry *answers, *entry;
659 char *host, *sitename;
660 const char *host_field, *path;
661 int port, def_port, primary;
662
663 ret = get_sitename(context, realm, &sitename);
664 if (ret)
665 return ret;
666 ret = k5_make_uri_query(context, realm, req_service, sitename, &answers);
667 free(sitename);
668 if (ret || answers == NULL)
669 return ret;
670
671 for (entry = answers; entry != NULL; entry = entry->next) {
672 def_port = default_port;
673 path = NULL;
674
675 parse_uri_fields(entry->host, &transport, &host_field, &primary);
676 if (host_field == NULL)
677 continue;
678
679 /* TCP_OR_UDP allows entries of any transport type; otherwise
680 * we're asking for a match. */
681 if (req_transport != TCP_OR_UDP && req_transport != transport)
682 continue;
683
684 /* Process a MS-KKDCP target. */
685 if (transport == HTTPS) {
686 host_trans = 0;
687 def_port = 443;
688 parse_uri_if_https(host_field, &host_trans, &host_field, &path);
689 if (host_trans != HTTPS)
690 continue;
691 }
692
693 ret = k5_parse_host_string(host_field, def_port, &host, &port);
694 if (ret == ENOMEM)
695 break;
696
697 if (ret || host == NULL) {
698 ret = 0;
699 continue;
700 }
701
702 ret = add_host_to_list(serverlist, host, port, transport, AF_UNSPEC,
703 path, primary);
704 free(host);
705 if (ret)
706 break;
707 }
708
709 krb5int_free_srv_dns_data(answers);
710 return ret;
711 }
712
713 static krb5_error_code
dns_locate_server_uri(krb5_context context,const krb5_data * realm,struct serverlist * serverlist,enum locate_service_type svc,k5_transport transport)714 dns_locate_server_uri(krb5_context context, const krb5_data *realm,
715 struct serverlist *serverlist,
716 enum locate_service_type svc, k5_transport transport)
717 {
718 krb5_error_code ret;
719 char *svcname;
720 int def_port;
721 krb5_boolean find_primary = FALSE;
722
723 if (!_krb5_use_dns_kdc(context) || !use_dns_uri(context))
724 return 0;
725
726 switch (svc) {
727 case locate_service_primary_kdc:
728 find_primary = TRUE;
729 /* Fall through */
730 case locate_service_kdc:
731 svcname = "_kerberos";
732 def_port = 88;
733 break;
734 case locate_service_kadmin:
735 svcname = "_kerberos-adm";
736 def_port = 749;
737 break;
738 case locate_service_kpasswd:
739 svcname = "_kpasswd";
740 def_port = 464;
741 break;
742 default:
743 return 0;
744 }
745
746 ret = locate_uri(context, realm, svcname, serverlist, transport, def_port,
747 find_primary);
748
749 if (serverlist->nservers == 0)
750 TRACE_DNS_URI_NOTFOUND(context);
751
752 return ret;
753 }
754
755 static krb5_error_code
dns_locate_server_srv(krb5_context context,const krb5_data * realm,struct serverlist * serverlist,enum locate_service_type svc,k5_transport transport)756 dns_locate_server_srv(krb5_context context, const krb5_data *realm,
757 struct serverlist *serverlist,
758 enum locate_service_type svc, k5_transport transport)
759 {
760 const char *dnsname;
761 int use_dns = _krb5_use_dns_kdc(context);
762 krb5_error_code code;
763
764 if (!use_dns)
765 return 0;
766
767 switch (svc) {
768 case locate_service_kdc:
769 dnsname = "_kerberos";
770 break;
771 case locate_service_primary_kdc:
772 dnsname = "_kerberos-master";
773 break;
774 case locate_service_kadmin:
775 dnsname = "_kerberos-adm";
776 break;
777 case locate_service_krb524:
778 dnsname = "_krb524";
779 break;
780 case locate_service_kpasswd:
781 dnsname = "_kpasswd";
782 break;
783 default:
784 return 0;
785 }
786
787 code = 0;
788 if (transport == UDP || transport == TCP_OR_UDP)
789 code = locate_srv_dns_1(context, realm, dnsname, "_udp", serverlist);
790
791 if ((transport == TCP || transport == TCP_OR_UDP) && code == 0)
792 code = locate_srv_dns_1(context, realm, dnsname, "_tcp", serverlist);
793
794 if (serverlist->nservers == 0)
795 TRACE_DNS_SRV_NOTFOUND(context);
796
797 return code;
798 }
799 #endif /* KRB5_DNS_LOOKUP */
800
801 /*
802 * Try all of the server location methods in sequence. transport must be
803 * TCP_OR_UDP, TCP, or UDP. It is applied to hostname entries in the profile
804 * and affects whether we query modules or DNS for UDP or TCP or both, but does
805 * not restrict a method from returning entries of other transports.
806 */
807 static krb5_error_code
locate_server(krb5_context context,const krb5_data * realm,struct serverlist * serverlist,enum locate_service_type svc,k5_transport transport)808 locate_server(krb5_context context, const krb5_data *realm,
809 struct serverlist *serverlist, enum locate_service_type svc,
810 k5_transport transport)
811 {
812 krb5_error_code ret;
813 struct serverlist list = SERVERLIST_INIT;
814
815 *serverlist = list;
816
817 /* Try modules. If a module returns 0 but leaves the list empty, return an
818 * empty list. */
819 ret = module_locate_server(context, realm, &list, svc, transport);
820 if (ret != KRB5_PLUGIN_NO_HANDLE)
821 goto done;
822
823 /* Try the profile. Fall back to DNS if it returns an empty list. */
824 ret = prof_locate_server(context, realm, &list, svc, transport);
825 if (ret)
826 goto done;
827
828 #ifdef KRB5_DNS_LOOKUP
829 if (list.nservers == 0) {
830 ret = dns_locate_server_uri(context, realm, &list, svc, transport);
831 if (ret)
832 goto done;
833 }
834
835 if (list.nservers == 0)
836 ret = dns_locate_server_srv(context, realm, &list, svc, transport);
837 #endif
838
839 done:
840 if (ret) {
841 k5_free_serverlist(&list);
842 return ret;
843 }
844 *serverlist = list;
845 return 0;
846 }
847
848 /*
849 * Wrapper function for the various backends
850 */
851
852 krb5_error_code
k5_locate_server(krb5_context context,const krb5_data * realm,struct serverlist * serverlist,enum locate_service_type svc,krb5_boolean no_udp)853 k5_locate_server(krb5_context context, const krb5_data *realm,
854 struct serverlist *serverlist, enum locate_service_type svc,
855 krb5_boolean no_udp)
856 {
857 krb5_error_code ret;
858 k5_transport transport = no_udp ? TCP : TCP_OR_UDP;
859
860 memset(serverlist, 0, sizeof(*serverlist));
861 if (realm == NULL || realm->data == NULL || realm->data[0] == 0) {
862 k5_setmsg(context, KRB5_REALM_CANT_RESOLVE,
863 "Cannot find KDC for invalid realm name \"\"");
864 return KRB5_REALM_CANT_RESOLVE;
865 }
866
867 ret = locate_server(context, realm, serverlist, svc, transport);
868 if (ret)
869 return ret;
870
871 if (serverlist->nservers == 0) {
872 k5_free_serverlist(serverlist);
873 k5_setmsg(context, KRB5_REALM_UNKNOWN,
874 _("Cannot find KDC for realm \"%.*s\""),
875 realm->length, realm->data);
876 return KRB5_REALM_UNKNOWN;
877 }
878 return 0;
879 }
880
881 krb5_error_code
k5_locate_kdc(krb5_context context,const krb5_data * realm,struct serverlist * serverlist,krb5_boolean get_primaries,krb5_boolean no_udp)882 k5_locate_kdc(krb5_context context, const krb5_data *realm,
883 struct serverlist *serverlist, krb5_boolean get_primaries,
884 krb5_boolean no_udp)
885 {
886 enum locate_service_type stype;
887
888 stype = get_primaries ? locate_service_primary_kdc : locate_service_kdc;
889 return k5_locate_server(context, realm, serverlist, stype, no_udp);
890 }
891
892 krb5_error_code
k5_kdclist_create(struct kdclist ** kdcs_out)893 k5_kdclist_create(struct kdclist **kdcs_out)
894 {
895 struct kdclist *kdcs;
896
897 *kdcs_out = NULL;
898 kdcs = malloc(sizeof(*kdcs));
899 if (kdcs == NULL)
900 return ENOMEM;
901 kdcs->count = 0;
902 kdcs->list = NULL;
903 *kdcs_out = kdcs;
904 return 0;
905 }
906
907 krb5_error_code
k5_kdclist_add(struct kdclist * kdcs,const krb5_data * realm,struct server_entry * server)908 k5_kdclist_add(struct kdclist *kdcs, const krb5_data *realm,
909 struct server_entry *server)
910 {
911 krb5_error_code ret;
912 struct kdclist_entry *newptr, *ent;
913
914 newptr = realloc(kdcs->list, (kdcs->count + 1) * sizeof(*kdcs->list));
915 if (newptr == NULL)
916 return ENOMEM;
917 kdcs->list = newptr;
918 ent = &kdcs->list[kdcs->count];
919 ret = krb5int_copy_data_contents(NULL, realm, &ent->realm);
920 if (ret)
921 return ret;
922 /* Steal memory ownership from *server. */
923 ent->server = *server;
924 memset(server, 0, sizeof(*server));
925 kdcs->count++;
926 return 0;
927 }
928
929 /*
930 * If primaries is empty, mark ent as primary (the realm has no primary KDCs
931 * and therefore no KDCs are replicas). Otherwise mark ent according to
932 * whether it is present in primaries. Return true if ent is determined to be
933 * a replica.
934 */
935 static krb5_boolean
mark_entry(struct kdclist_entry * ent,struct serverlist * primaries)936 mark_entry(struct kdclist_entry *ent, struct serverlist *primaries)
937 {
938 if (primaries->nservers == 0) {
939 ent->server.primary = 1;
940 return FALSE;
941 }
942 ent->server.primary = server_list_contains(primaries, &ent->server);
943 return !ent->server.primary;
944 }
945
946 /* Mark kdcs->list[start] and all entries with the same realm and transport
947 * according to primaries. Stop and return true if a replica is found. */
948 static krb5_boolean
mark_matching_servers(struct kdclist * kdcs,size_t start,struct serverlist * primaries)949 mark_matching_servers(struct kdclist *kdcs, size_t start,
950 struct serverlist *primaries)
951 {
952 size_t i;
953 struct kdclist_entry *ent = &kdcs->list[start];
954
955 if (mark_entry(ent, primaries))
956 return TRUE;
957 for (i = start + 1; i < kdcs->count; i++) {
958 if (kdcs->list[i].server.primary == 1)
959 continue;
960 if (kdcs->list[i].server.transport != ent->server.transport)
961 continue;
962 if (!data_eq(kdcs->list[i].realm, ent->realm))
963 continue;
964 if (mark_entry(&kdcs->list[i], primaries))
965 return TRUE;
966 }
967 return FALSE;
968 }
969
970 /* Return true if any entry in kdcs is a replica. May modify the primary
971 * fields of entries in kdcs. */
972 krb5_boolean
k5_kdclist_any_replicas(krb5_context context,struct kdclist * kdcs)973 k5_kdclist_any_replicas(krb5_context context, struct kdclist *kdcs)
974 {
975 size_t i;
976 struct kdclist_entry *ent;
977 struct serverlist primaries;
978 krb5_boolean found;
979
980 /* Check if we already know that any of the KDCs is a replica. */
981 for (i = 0; i < kdcs->count; i++) {
982 if (kdcs->list[i].server.primary == 0)
983 return TRUE;
984 }
985
986 for (i = 0; i < kdcs->count; i++) {
987 ent = &kdcs->list[i];
988
989 /* Skip this entry if we already know that it's not a replica. */
990 if (ent->server.primary == 1)
991 continue;
992
993 /* Look up the primary KDCs for this entry's realm and transport. Give
994 * up and return false on error. */
995 if (locate_server(context, &ent->realm, &primaries,
996 locate_service_primary_kdc,
997 ent->server.transport) != 0)
998 return FALSE;
999
1000 /* Using the list of primaries, determine whether this entry and any
1001 * entries with the same realm and transport are replicas. */
1002 found = mark_matching_servers(kdcs, i, &primaries);
1003
1004 k5_free_serverlist(&primaries);
1005 if (found)
1006 return TRUE;
1007 }
1008
1009 return FALSE;
1010 }
1011
1012 void
k5_kdclist_free(struct kdclist * kdcs)1013 k5_kdclist_free(struct kdclist *kdcs)
1014 {
1015 size_t i;
1016
1017 if (kdcs == NULL)
1018 return;
1019 for (i = 0; i < kdcs->count; i++) {
1020 free(kdcs->list[i].realm.data);
1021 free(kdcs->list[i].server.hostname);
1022 free(kdcs->list[i].server.uri_path);
1023 }
1024 free(kdcs->list);
1025 free(kdcs);
1026 }
1027