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 */
4 * Copyright 1990,2000,2001,2002,2003,2004,2006,2008 Massachusetts Institute of
5 * Technology. All Rights Reserved.
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.
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.
28 #include "fake-addrinfo.h"
30 #ifdef KRB5_DNS_LOOKUP
34 #include <netinet/in.h>
35 #include <arpa/inet.h>
36 #include <arpa/nameser.h>
42 #define DEFAULT_LOOKUP_KDC 1
43 #if KRB5_DNS_LOOKUP_REALM
44 #define DEFAULT_LOOKUP_REALM 1
46 #define DEFAULT_LOOKUP_REALM 0
48 #define DEFAULT_URI_LOOKUP TRUE
51 maybe_use_dns (krb5_context context, const char *name, int defalt)
57 code = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
59 if (value == 0 && code == 0) {
60 code = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
61 KRB5_CONF_DNS_FALLBACK, 0, 0, &value);
69 use_dns = _krb5_conf_boolean(value);
70 profile_release_string(value);
75 use_dns_uri(krb5_context ctx)
80 ret = profile_get_boolean(ctx->profile, KRB5_CONF_LIBDEFAULTS,
81 KRB5_CONF_DNS_URI_LOOKUP, NULL,
82 DEFAULT_URI_LOOKUP, &use);
83 return ret ? DEFAULT_URI_LOOKUP : use;
87 _krb5_use_dns_kdc(krb5_context context)
89 return maybe_use_dns(context, KRB5_CONF_DNS_LOOKUP_KDC,
94 _krb5_use_dns_realm(krb5_context context)
96 return maybe_use_dns(context, KRB5_CONF_DNS_LOOKUP_REALM,
97 DEFAULT_LOOKUP_REALM);
100 #endif /* KRB5_DNS_LOOKUP */
102 /* Free up everything pointed to by the serverlist structure, but don't
103 * free the structure itself. */
105 k5_free_serverlist (struct serverlist *list)
109 for (i = 0; i < list->nservers; i++) {
110 free(list->servers[i].hostname);
111 free(list->servers[i].uri_path);
114 list->servers = NULL;
120 Tprintf(const char *fmt, ...)
125 vfprintf(stderr, fmt, ap);
130 /* Make room for a new server entry in list and return a pointer to the new
131 * entry. (Do not increment list->nservers.) */
132 static struct server_entry *
133 new_server_entry(struct serverlist *list)
135 struct server_entry *newservers, *entry;
136 size_t newspace = (list->nservers + 1) * sizeof(struct server_entry);
138 newservers = realloc(list->servers, newspace);
139 if (newservers == NULL)
141 list->servers = newservers;
142 entry = &newservers[list->nservers];
143 memset(entry, 0, sizeof(*entry));
148 /* Add an address entry to list. */
150 add_addr_to_list(struct serverlist *list, k5_transport transport, int family,
151 size_t addrlen, struct sockaddr *addr)
153 struct server_entry *entry;
155 entry = new_server_entry(list);
158 entry->transport = transport;
159 entry->family = family;
160 entry->hostname = NULL;
161 entry->uri_path = NULL;
162 entry->addrlen = addrlen;
163 memcpy(&entry->addr, addr, addrlen);
168 /* Add a hostname entry to list. */
170 add_host_to_list(struct serverlist *list, const char *hostname, int port,
171 k5_transport transport, int family, const char *uri_path,
174 struct server_entry *entry;
176 entry = new_server_entry(list);
179 entry->transport = transport;
180 entry->family = family;
181 entry->hostname = strdup(hostname);
182 if (entry->hostname == NULL)
184 if (uri_path != NULL) {
185 entry->uri_path = strdup(uri_path);
186 if (entry->uri_path == NULL)
190 entry->master = master;
194 free(entry->hostname);
195 entry->hostname = NULL;
200 parse_uri_if_https(const char *host_or_uri, k5_transport *transport,
201 const char **host, const char **uri_path)
205 if (strncmp(host_or_uri, "https://", 8) == 0) {
207 *host = host_or_uri + 8;
209 cp = strchr(*host, '/');
217 /* Return true if server is identical to an entry in list. */
219 server_list_contains(struct serverlist *list, struct server_entry *server)
221 struct server_entry *ent;
223 for (ent = list->servers; ent < list->servers + list->nservers; ent++) {
224 if (server->hostname != NULL && ent->hostname != NULL &&
225 strcmp(server->hostname, ent->hostname) == 0)
227 if (server->hostname == NULL && ent->hostname == NULL &&
228 server->addrlen == ent->addrlen &&
229 memcmp(&server->addr, &ent->addr, server->addrlen) == 0)
235 static krb5_error_code
236 locate_srv_conf_1(krb5_context context, const krb5_data *realm,
237 const char * name, struct serverlist *serverlist,
238 k5_transport transport, int udpport)
240 const char *realm_srv_names[4];
241 char **hostlist = NULL, *realmstr = NULL, *host = NULL;
242 const char *hostspec;
243 krb5_error_code code;
246 Tprintf("looking in krb5.conf for realm %s entry %s; ports %d,%d\n",
247 realm->data, name, udpport);
249 realmstr = k5memdup0(realm->data, realm->length, &code);
250 if (realmstr == NULL)
253 realm_srv_names[0] = KRB5_CONF_REALMS;
254 realm_srv_names[1] = realmstr;
255 realm_srv_names[2] = name;
256 realm_srv_names[3] = 0;
257 code = profile_get_values(context->profile, realm_srv_names, &hostlist);
259 Tprintf("config file lookup failed: %s\n", error_message(code));
260 if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
265 for (i = 0; hostlist[i]; i++) {
267 k5_transport this_transport = transport;
268 const char *uri_path = NULL;
270 hostspec = hostlist[i];
271 Tprintf("entry %d is '%s'\n", i, hostspec);
273 parse_uri_if_https(hostspec, &this_transport, &hostspec, &uri_path);
275 default_port = (this_transport == HTTPS) ? 443 : udpport;
276 code = k5_parse_host_string(hostspec, default_port, &host, &port_num);
277 if (code == 0 && host == NULL)
282 code = add_host_to_list(serverlist, host, port_num, this_transport,
283 AF_UNSPEC, uri_path, -1);
294 profile_free_list(hostlist);
299 static krb5_error_code
300 krb5_locate_srv_conf(krb5_context context, const krb5_data *realm,
301 const char *name, struct serverlist *al, int udpport)
305 ret = locate_srv_conf_1(context, realm, name, al, TCP_OR_UDP, udpport);
308 if (al->nservers == 0) /* Couldn't resolve any KDC names */
309 return KRB5_REALM_CANT_RESOLVE;
314 #ifdef KRB5_DNS_LOOKUP
315 static krb5_error_code
316 locate_srv_dns_1(const krb5_data *realm, const char *service,
317 const char *protocol, struct serverlist *serverlist)
319 struct srv_dns_entry *head = NULL, *entry = NULL;
320 krb5_error_code code = 0;
321 k5_transport transport;
323 code = krb5int_make_srv_query_realm(realm, service, protocol, &head);
330 /* Check for the "." case indicating no support. */
331 if (head->next == NULL && head->host[0] == '\0') {
332 code = KRB5_ERR_NO_SERVICE;
336 for (entry = head; entry != NULL; entry = entry->next) {
337 transport = (strcmp(protocol, "_tcp") == 0) ? TCP : UDP;
338 code = add_host_to_list(serverlist, entry->host, entry->port,
339 transport, AF_UNSPEC, NULL, -1);
345 krb5int_free_srv_dns_data(head);
350 #include <krb5/locate_plugin.h>
353 static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR,
354 LIBDIR "/krb5/plugins/libkrb5",
355 NULL }; /* should be a list */
357 static const char *objdirs[] = { LIBDIR "/krb5/plugins/libkrb5", NULL };
360 struct module_callback_data {
362 struct serverlist *list;
366 module_callback(void *cbdata, int socktype, struct sockaddr *sa)
368 struct module_callback_data *d = cbdata;
370 k5_transport transport;
372 if (socktype != SOCK_STREAM && socktype != SOCK_DGRAM)
374 if (sa->sa_family == AF_INET)
375 addrlen = sizeof(struct sockaddr_in);
376 else if (sa->sa_family == AF_INET6)
377 addrlen = sizeof(struct sockaddr_in6);
380 transport = (socktype == SOCK_STREAM) ? TCP : UDP;
381 if (add_addr_to_list(d->list, transport, sa->sa_family, addrlen,
383 /* Assumes only error is ENOMEM. */
390 static krb5_error_code
391 module_locate_server(krb5_context ctx, const krb5_data *realm,
392 struct serverlist *serverlist,
393 enum locate_service_type svc, k5_transport transport)
395 struct krb5plugin_service_locate_result *res = NULL;
396 krb5_error_code code;
397 struct krb5plugin_service_locate_ftable *vtbl = NULL;
399 char *realmz; /* NUL-terminated realm */
401 struct module_callback_data cbdata = { 0, };
404 Tprintf("in module_locate_server\n");
405 cbdata.list = serverlist;
406 if (!PLUGIN_DIR_OPEN(&ctx->libkrb5_plugins)) {
408 code = krb5int_open_plugin_dirs(objdirs, NULL, &ctx->libkrb5_plugins,
411 return KRB5_PLUGIN_NO_HANDLE;
414 code = krb5int_get_plugin_dir_data(&ctx->libkrb5_plugins,
415 "service_locator", &ptrs, &ctx->err);
417 Tprintf("error looking up plugin symbols: %s\n",
418 (msg = krb5_get_error_message(ctx, code)));
419 krb5_free_error_message(ctx, msg);
420 return KRB5_PLUGIN_NO_HANDLE;
423 if (realm->length >= UINT_MAX) {
424 krb5int_free_plugin_dir_data(ptrs);
427 realmz = k5memdup0(realm->data, realm->length, &code);
428 if (realmz == NULL) {
429 krb5int_free_plugin_dir_data(ptrs);
432 for (i = 0; ptrs[i]; i++) {
436 Tprintf("element %d is %p\n", i, ptrs[i]);
438 /* For now, don't keep the plugin data alive. For long-lived
439 * contexts, it may be desirable to change that later. */
440 code = vtbl->init(ctx, &blob);
444 socktype = (transport == TCP) ? SOCK_STREAM : SOCK_DGRAM;
445 code = vtbl->lookup(blob, svc, realmz, socktype, AF_UNSPEC,
446 module_callback, &cbdata);
447 /* Also ask for TCP addresses if we got UDP addresses and want both. */
448 if (code == 0 && transport == TCP_OR_UDP) {
449 code = vtbl->lookup(blob, svc, realmz, SOCK_STREAM, AF_UNSPEC,
450 module_callback, &cbdata);
451 if (code == KRB5_PLUGIN_NO_HANDLE)
455 if (code == KRB5_PLUGIN_NO_HANDLE) {
456 /* Module passes, keep going. */
458 Tprintf("plugin doesn't handle this realm (KRB5_PLUGIN_NO_HANDLE)"
463 /* Module encountered an actual error. */
464 Tprintf("plugin lookup routine returned error %d: %s\n",
465 code, error_message(code));
467 krb5int_free_plugin_dir_data(ptrs);
472 if (ptrs[i] == NULL) {
473 Tprintf("ran off end of plugin list\n");
475 krb5int_free_plugin_dir_data(ptrs);
476 return KRB5_PLUGIN_NO_HANDLE;
478 Tprintf("stopped with plugin #%d, res=%p\n", i, res);
480 /* Got something back, yippee. */
481 Tprintf("now have %lu addrs in list %p\n",
482 (unsigned long)serverlist->nservers, serverlist);
484 krb5int_free_plugin_dir_data(ptrs);
488 static krb5_error_code
489 prof_locate_server(krb5_context context, const krb5_data *realm,
490 struct serverlist *serverlist, enum locate_service_type svc,
491 k5_transport transport)
493 const char *profname;
495 struct servent *serv;
498 case locate_service_kdc:
499 profname = KRB5_CONF_KDC;
500 /* We used to use /etc/services for these, but enough systems have old,
501 * crufty, wrong settings that this is probably better. */
503 dflport = KRB5_DEFAULT_PORT;
505 case locate_service_master_kdc:
506 profname = KRB5_CONF_MASTER_KDC;
508 case locate_service_kadmin:
509 profname = KRB5_CONF_ADMIN_SERVER;
510 dflport = DEFAULT_KADM5_PORT;
512 case locate_service_krb524:
513 profname = KRB5_CONF_KRB524_SERVER;
514 serv = getservbyname("krb524", "udp");
515 dflport = serv ? serv->s_port : 4444;
517 case locate_service_kpasswd:
518 profname = KRB5_CONF_KPASSWD_SERVER;
519 dflport = DEFAULT_KPASSWD_PORT;
522 return EBUSY; /* XXX */
525 return locate_srv_conf_1(context, realm, profname, serverlist, transport,
529 #ifdef KRB5_DNS_LOOKUP
532 * Parse the initial part of the URI, first confirming the scheme name. Get
533 * the transport, flags (indicating master status), and host. The host is
534 * either an address or hostname with an optional port, or an HTTPS URL.
535 * The format is krb5srv:flags:udp|tcp|kkdcp:host
537 * Return a NULL *host_out if there are any problems parsing the URI.
540 parse_uri_fields(const char *uri, k5_transport *transport_out,
541 const char **host_out, int *master_out)
544 k5_transport transport;
551 /* Confirm the scheme name. */
552 if (strncasecmp(uri, "krb5srv", 7) != 0)
563 /* Check the flags field for supported flags. */
564 for (; *uri != ':' && *uri != '\0'; uri++) {
565 if (*uri == 'm' || *uri == 'M')
571 /* Look for the transport type. */
573 if (strncasecmp(uri, "udp", 3) == 0) {
576 } else if (strncasecmp(uri, "tcp", 3) == 0) {
579 } else if (strncasecmp(uri, "kkdcp", 5) == 0) {
580 /* Currently the only MS-KKDCP transport type is HTTPS. */
590 /* The rest of the URI is the host (with optional port) or URI. */
592 *transport_out = transport;
593 *master_out = master;
597 * Collect a list of servers from DNS URI records, for the requested service
598 * and transport type. Problematic entries are skipped.
600 static krb5_error_code
601 locate_uri(const krb5_data *realm, const char *req_service,
602 struct serverlist *serverlist, k5_transport req_transport,
603 int default_port, krb5_boolean master_only)
606 k5_transport transport, host_trans;
607 struct srv_dns_entry *answers, *entry;
609 const char *host_field, *path;
610 int port, def_port, master;
612 ret = k5_make_uri_query(realm, req_service, &answers);
613 if (ret || answers == NULL)
616 for (entry = answers; entry != NULL; entry = entry->next) {
617 def_port = default_port;
620 parse_uri_fields(entry->host, &transport, &host_field, &master);
621 if (host_field == NULL)
624 /* TCP_OR_UDP allows entries of any transport type; otherwise
625 * we're asking for a match. */
626 if (req_transport != TCP_OR_UDP && req_transport != transport)
629 /* Process a MS-KKDCP target. */
630 if (transport == HTTPS) {
633 parse_uri_if_https(host_field, &host_trans, &host_field, &path);
634 if (host_trans != HTTPS)
638 ret = k5_parse_host_string(host_field, def_port, &host, &port);
642 if (ret || host == NULL) {
647 ret = add_host_to_list(serverlist, host, port, transport, AF_UNSPEC,
654 krb5int_free_srv_dns_data(answers);
658 static krb5_error_code
659 dns_locate_server_uri(krb5_context context, const krb5_data *realm,
660 struct serverlist *serverlist,
661 enum locate_service_type svc, k5_transport transport)
666 krb5_boolean find_master = FALSE;
668 if (!_krb5_use_dns_kdc(context) || !use_dns_uri(context))
672 case locate_service_master_kdc:
675 case locate_service_kdc:
676 svcname = "_kerberos";
679 case locate_service_kadmin:
680 svcname = "_kerberos-adm";
683 case locate_service_kpasswd:
684 svcname = "_kpasswd";
691 ret = locate_uri(realm, svcname, serverlist, transport, def_port,
694 Tprintf("dns URI lookup returned error %d\n", ret);
699 static krb5_error_code
700 dns_locate_server_srv(krb5_context context, const krb5_data *realm,
701 struct serverlist *serverlist,
702 enum locate_service_type svc, k5_transport transport)
705 int use_dns = _krb5_use_dns_kdc(context);
706 krb5_error_code code;
712 case locate_service_kdc:
713 dnsname = "_kerberos";
715 case locate_service_master_kdc:
716 dnsname = "_kerberos-master";
718 case locate_service_kadmin:
719 dnsname = "_kerberos-adm";
721 case locate_service_krb524:
724 case locate_service_kpasswd:
725 dnsname = "_kpasswd";
732 if (transport == UDP || transport == TCP_OR_UDP) {
733 code = locate_srv_dns_1(realm, dnsname, "_udp", serverlist);
735 Tprintf("dns udp lookup returned error %d\n", code);
737 if ((transport == TCP || transport == TCP_OR_UDP) && code == 0) {
738 code = locate_srv_dns_1(realm, dnsname, "_tcp", serverlist);
740 Tprintf("dns tcp lookup returned error %d\n", code);
744 #endif /* KRB5_DNS_LOOKUP */
747 * Try all of the server location methods in sequence. transport must be
748 * TCP_OR_UDP, TCP, or UDP. It is applied to hostname entries in the profile
749 * and affects whether we query modules or DNS for UDP or TCP or both, but does
750 * not restrict a method from returning entries of other transports.
752 static krb5_error_code
753 locate_server(krb5_context context, const krb5_data *realm,
754 struct serverlist *serverlist, enum locate_service_type svc,
755 k5_transport transport)
758 struct serverlist list = SERVERLIST_INIT;
762 /* Try modules. If a module returns 0 but leaves the list empty, return an
764 ret = module_locate_server(context, realm, &list, svc, transport);
765 if (ret != KRB5_PLUGIN_NO_HANDLE)
768 /* Try the profile. Fall back to DNS if it returns an empty list. */
769 ret = prof_locate_server(context, realm, &list, svc, transport);
773 #ifdef KRB5_DNS_LOOKUP
774 if (list.nservers == 0) {
775 ret = dns_locate_server_uri(context, realm, &list, svc, transport);
780 if (list.nservers == 0)
781 ret = dns_locate_server_srv(context, realm, &list, svc, transport);
786 k5_free_serverlist(&list);
794 * Wrapper function for the various backends
798 k5_locate_server(krb5_context context, const krb5_data *realm,
799 struct serverlist *serverlist, enum locate_service_type svc,
803 k5_transport transport = no_udp ? TCP : TCP_OR_UDP;
805 memset(serverlist, 0, sizeof(*serverlist));
806 if (realm == NULL || realm->data == NULL || realm->data[0] == 0) {
807 k5_setmsg(context, KRB5_REALM_CANT_RESOLVE,
808 "Cannot find KDC for invalid realm name \"\"");
809 return KRB5_REALM_CANT_RESOLVE;
812 ret = locate_server(context, realm, serverlist, svc, transport);
816 if (serverlist->nservers == 0) {
817 k5_free_serverlist(serverlist);
818 k5_setmsg(context, KRB5_REALM_UNKNOWN,
819 _("Cannot find KDC for realm \"%.*s\""),
820 realm->length, realm->data);
821 return KRB5_REALM_UNKNOWN;
827 k5_locate_kdc(krb5_context context, const krb5_data *realm,
828 struct serverlist *serverlist, krb5_boolean get_masters,
831 enum locate_service_type stype;
833 stype = get_masters ? locate_service_master_kdc : locate_service_kdc;
834 return k5_locate_server(context, realm, serverlist, stype, no_udp);
838 k5_kdc_is_master(krb5_context context, const krb5_data *realm,
839 struct server_entry *server)
841 struct serverlist list;
844 if (server->master != -1)
845 return server->master;
847 if (locate_server(context, realm, &list, locate_service_master_kdc,
848 server->transport) != 0)
850 found = server_list_contains(&list, server);
851 k5_free_serverlist(&list);