#include "k5-int.h"
#include "fake-addrinfo.h"
#include "os-proto.h"
#ifdef KRB5_DNS_LOOKUP
#define DEFAULT_LOOKUP_KDC 1
#if KRB5_DNS_LOOKUP_REALM
#define DEFAULT_LOOKUP_REALM 1
#else
#define DEFAULT_LOOKUP_REALM 0
#endif
#define DEFAULT_URI_LOOKUP TRUE
struct kdclist_entry {
krb5_data realm;
struct server_entry server;
};
struct kdclist {
size_t count;
struct kdclist_entry *list;
};
static int
maybe_use_dns (krb5_context context, const char *name, int defalt)
{
krb5_error_code code;
char * value = NULL;
int use_dns = 0;
code = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
name, 0, 0, &value);
if (value == 0 && code == 0) {
code = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
KRB5_CONF_DNS_FALLBACK, 0, 0, &value);
}
if (code)
return defalt;
if (value == 0)
return defalt;
use_dns = _krb5_conf_boolean(value);
profile_release_string(value);
return use_dns;
}
static krb5_boolean
use_dns_uri(krb5_context ctx)
{
krb5_error_code ret;
int use;
ret = profile_get_boolean(ctx->profile, KRB5_CONF_LIBDEFAULTS,
KRB5_CONF_DNS_URI_LOOKUP, NULL,
DEFAULT_URI_LOOKUP, &use);
return ret ? DEFAULT_URI_LOOKUP : use;
}
int
_krb5_use_dns_kdc(krb5_context context)
{
return maybe_use_dns(context, KRB5_CONF_DNS_LOOKUP_KDC,
DEFAULT_LOOKUP_KDC);
}
int
_krb5_use_dns_realm(krb5_context context)
{
return maybe_use_dns(context, KRB5_CONF_DNS_LOOKUP_REALM,
DEFAULT_LOOKUP_REALM);
}
static krb5_error_code
get_sitename(krb5_context context, const krb5_data *realm, char **out)
{
krb5_error_code ret;
char *realmstr;
*out = NULL;
realmstr = k5memdup0(realm->data, realm->length, &ret);
if (realmstr == NULL)
return ret;
ret = profile_get_string(context->profile, KRB5_CONF_REALMS,
realmstr, KRB5_CONF_SITENAME, NULL, out);
free(realmstr);
return ret;
}
#endif
void
k5_free_serverlist (struct serverlist *list)
{
size_t i;
for (i = 0; i < list->nservers; i++) {
free(list->servers[i].hostname);
free(list->servers[i].uri_path);
}
free(list->servers);
list->servers = NULL;
list->nservers = 0;
}
#include <stdarg.h>
static inline void
Tprintf(const char *fmt, ...)
{
#ifdef TEST
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
#endif
}
static struct server_entry *
new_server_entry(struct serverlist *list)
{
struct server_entry *newservers, *entry;
size_t newspace = (list->nservers + 1) * sizeof(struct server_entry);
newservers = realloc(list->servers, newspace);
if (newservers == NULL)
return NULL;
list->servers = newservers;
entry = &newservers[list->nservers];
memset(entry, 0, sizeof(*entry));
entry->primary = -1;
return entry;
}
static int
add_addr_to_list(struct serverlist *list, k5_transport transport, int family,
size_t addrlen, struct sockaddr *addr)
{
struct server_entry *entry;
entry = new_server_entry(list);
if (entry == NULL)
return ENOMEM;
entry->transport = transport;
entry->family = family;
entry->hostname = NULL;
entry->uri_path = NULL;
entry->addrlen = addrlen;
memcpy(&entry->addr, addr, addrlen);
list->nservers++;
return 0;
}
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)
{
struct server_entry *entry;
entry = new_server_entry(list);
if (entry == NULL)
return ENOMEM;
entry->transport = transport;
entry->family = family;
entry->hostname = strdup(hostname);
if (entry->hostname == NULL)
goto oom;
if (uri_path != NULL) {
entry->uri_path = strdup(uri_path);
if (entry->uri_path == NULL)
goto oom;
}
entry->port = port;
entry->primary = primary;
list->nservers++;
return 0;
oom:
free(entry->hostname);
entry->hostname = NULL;
return ENOMEM;
}
static void
parse_uri_if_https(const char *host_or_uri, k5_transport *transport,
const char **host, const char **uri_path)
{
char *cp;
if (strncmp(host_or_uri, "https://", 8) == 0) {
*transport = HTTPS;
*host = host_or_uri + 8;
cp = strchr(*host, '/');
if (cp != NULL) {
*cp = '\0';
*uri_path = cp + 1;
}
}
}
static krb5_boolean
server_list_contains(struct serverlist *list, struct server_entry *server)
{
struct server_entry *ent;
for (ent = list->servers; ent < list->servers + list->nservers; ent++) {
if (server->port != ent->port)
continue;
if (server->hostname != NULL && ent->hostname != NULL &&
strcmp(server->hostname, ent->hostname) == 0)
return TRUE;
if (server->hostname == NULL && ent->hostname == NULL &&
server->addrlen == ent->addrlen &&
memcmp(&server->addr, &ent->addr, server->addrlen) == 0)
return TRUE;
}
return FALSE;
}
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)
{
const char *realm_srv_names[4];
char **hostlist = NULL, *realmstr = NULL, *host = NULL;
const char *hostspec;
krb5_error_code code;
size_t i;
int default_port;
Tprintf("looking in krb5.conf for realm %s entry %s; ports %d,%d\n",
realm->data, name, udpport);
realmstr = k5memdup0(realm->data, realm->length, &code);
if (realmstr == NULL)
goto cleanup;
realm_srv_names[0] = KRB5_CONF_REALMS;
realm_srv_names[1] = realmstr;
realm_srv_names[2] = name;
realm_srv_names[3] = 0;
code = profile_get_values(context->profile, realm_srv_names, &hostlist);
if (code == PROF_NO_RELATION && strcmp(name, KRB5_CONF_PRIMARY_KDC) == 0) {
realm_srv_names[2] = KRB5_CONF_MASTER_KDC;
code = profile_get_values(context->profile, realm_srv_names,
&hostlist);
}
if (code) {
Tprintf("config file lookup failed: %s\n", error_message(code));
if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
code = 0;
goto cleanup;
}
for (i = 0; hostlist[i]; i++) {
int port_num;
k5_transport this_transport = transport;
const char *uri_path = NULL;
hostspec = hostlist[i];
Tprintf("entry %d is '%s'\n", i, hostspec);
#ifndef _WIN32
if (hostspec[0] == '/') {
struct sockaddr_un sun = { 0 };
sun.sun_family = AF_UNIX;
if (strlcpy(sun.sun_path, hostspec, sizeof(sun.sun_path)) >=
sizeof(sun.sun_path)) {
code = ENAMETOOLONG;
goto cleanup;
}
code = add_addr_to_list(serverlist, UNIXSOCK, AF_UNIX, sizeof(sun),
(struct sockaddr *)&sun);
if (code)
goto cleanup;
continue;
}
#endif
parse_uri_if_https(hostspec, &this_transport, &hostspec, &uri_path);
default_port = (this_transport == HTTPS) ? 443 : udpport;
code = k5_parse_host_string(hostspec, default_port, &host, &port_num);
if (code == 0 && host == NULL)
code = EINVAL;
if (code)
goto cleanup;
code = add_host_to_list(serverlist, host, port_num, this_transport,
AF_UNSPEC, uri_path, -1);
if (code)
goto cleanup;
free(host);
host = NULL;
}
cleanup:
free(realmstr);
free(host);
profile_free_list(hostlist);
return code;
}
#ifdef TEST
static krb5_error_code
krb5_locate_srv_conf(krb5_context context, const krb5_data *realm,
const char *name, struct serverlist *al, int udpport)
{
krb5_error_code ret;
ret = locate_srv_conf_1(context, realm, name, al, TCP_OR_UDP, udpport);
if (ret)
return ret;
if (al->nservers == 0)
return KRB5_REALM_CANT_RESOLVE;
return 0;
}
#endif
#ifdef KRB5_DNS_LOOKUP
static krb5_error_code
locate_srv_dns_1(krb5_context context, const krb5_data *realm,
const char *service, const char *protocol,
struct serverlist *serverlist)
{
struct srv_dns_entry *head = NULL, *entry = NULL;
krb5_error_code code = 0;
k5_transport transport;
char *sitename;
code = get_sitename(context, realm, &sitename);
if (code)
return code;
code = krb5int_make_srv_query_realm(context, realm, service, protocol,
sitename, &head);
free(sitename);
if (code)
return 0;
if (head == NULL)
return 0;
if (head->next == NULL && head->host[0] == '\0') {
code = KRB5_ERR_NO_SERVICE;
goto cleanup;
}
for (entry = head; entry != NULL; entry = entry->next) {
transport = (strcmp(protocol, "_tcp") == 0) ? TCP : UDP;
code = add_host_to_list(serverlist, entry->host, entry->port,
transport, AF_UNSPEC, NULL, -1);
if (code)
goto cleanup;
}
cleanup:
krb5int_free_srv_dns_data(head);
return code;
}
#endif
#include <krb5/locate_plugin.h>
#if TARGET_OS_MAC
static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR,
LIBDIR "/krb5/plugins/libkrb5",
NULL };
#else
static const char *objdirs[] = { LIBDIR "/krb5/plugins/libkrb5", NULL };
#endif
struct module_callback_data {
int out_of_mem;
struct serverlist *list;
};
static int
module_callback(void *cbdata, int socktype, struct sockaddr *sa)
{
struct module_callback_data *d = cbdata;
size_t addrlen;
k5_transport transport;
if (socktype != SOCK_STREAM && socktype != SOCK_DGRAM)
return 0;
if (sa->sa_family == AF_INET)
addrlen = sizeof(struct sockaddr_in);
else if (sa->sa_family == AF_INET6)
addrlen = sizeof(struct sockaddr_in6);
else
return 0;
transport = (socktype == SOCK_STREAM) ? TCP : UDP;
if (add_addr_to_list(d->list, transport, sa->sa_family, addrlen,
sa) != 0) {
d->out_of_mem = 1;
return 1;
}
return 0;
}
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)
{
struct krb5plugin_service_locate_result *res = NULL;
krb5_error_code code;
struct krb5plugin_service_locate_ftable *vtbl = NULL;
void **ptrs;
char *realmz;
size_t i;
int socktype;
struct module_callback_data cbdata = { 0, };
const char *msg;
Tprintf("in module_locate_server\n");
cbdata.list = serverlist;
if (!PLUGIN_DIR_OPEN(&ctx->libkrb5_plugins)) {
code = krb5int_open_plugin_dirs(objdirs, NULL, &ctx->libkrb5_plugins,
&ctx->err);
if (code)
return KRB5_PLUGIN_NO_HANDLE;
}
code = krb5int_get_plugin_dir_data(&ctx->libkrb5_plugins,
"service_locator", &ptrs, &ctx->err);
if (code) {
Tprintf("error looking up plugin symbols: %s\n",
(msg = krb5_get_error_message(ctx, code)));
krb5_free_error_message(ctx, msg);
return KRB5_PLUGIN_NO_HANDLE;
}
if (realm->length >= UINT_MAX) {
krb5int_free_plugin_dir_data(ptrs);
return ENOMEM;
}
realmz = k5memdup0(realm->data, realm->length, &code);
if (realmz == NULL) {
krb5int_free_plugin_dir_data(ptrs);
return code;
}
for (i = 0; ptrs[i]; i++) {
void *blob;
vtbl = ptrs[i];
Tprintf("element %d is %p\n", i, ptrs[i]);
code = vtbl->init(ctx, &blob);
if (code)
continue;
socktype = (transport == TCP) ? SOCK_STREAM : SOCK_DGRAM;
code = vtbl->lookup(blob, svc, realmz, socktype, AF_UNSPEC,
module_callback, &cbdata);
if (code == 0 && transport == TCP_OR_UDP) {
code = vtbl->lookup(blob, svc, realmz, SOCK_STREAM, AF_UNSPEC,
module_callback, &cbdata);
if (code == KRB5_PLUGIN_NO_HANDLE)
code = 0;
}
vtbl->fini(blob);
if (code == KRB5_PLUGIN_NO_HANDLE) {
Tprintf("plugin doesn't handle this realm (KRB5_PLUGIN_NO_HANDLE)"
"\n");
continue;
}
if (code != 0) {
Tprintf("plugin lookup routine returned error %d: %s\n",
code, error_message(code));
free(realmz);
krb5int_free_plugin_dir_data(ptrs);
return code;
}
break;
}
if (ptrs[i] == NULL) {
Tprintf("ran off end of plugin list\n");
free(realmz);
krb5int_free_plugin_dir_data(ptrs);
return KRB5_PLUGIN_NO_HANDLE;
}
Tprintf("stopped with plugin #%d, res=%p\n", i, res);
Tprintf("now have %lu addrs in list %p\n",
(unsigned long)serverlist->nservers, serverlist);
free(realmz);
krb5int_free_plugin_dir_data(ptrs);
return 0;
}
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)
{
const char *profname;
int dflport = 0;
struct servent *serv;
switch (svc) {
case locate_service_kdc:
profname = KRB5_CONF_KDC;
kdc_ports:
dflport = KRB5_DEFAULT_PORT;
break;
case locate_service_primary_kdc:
profname = KRB5_CONF_PRIMARY_KDC;
goto kdc_ports;
case locate_service_kadmin:
profname = KRB5_CONF_ADMIN_SERVER;
dflport = DEFAULT_KADM5_PORT;
break;
case locate_service_krb524:
profname = KRB5_CONF_KRB524_SERVER;
serv = getservbyname("krb524", "udp");
dflport = serv ? serv->s_port : 4444;
break;
case locate_service_kpasswd:
profname = KRB5_CONF_KPASSWD_SERVER;
dflport = DEFAULT_KPASSWD_PORT;
break;
default:
return EBUSY;
}
return locate_srv_conf_1(context, realm, profname, serverlist, transport,
dflport);
}
#ifdef KRB5_DNS_LOOKUP
static void
parse_uri_fields(const char *uri, k5_transport *transport_out,
const char **host_out, int *primary_out)
{
k5_transport transport;
int primary = FALSE;
*transport_out = 0;
*host_out = NULL;
*primary_out = -1;
if (strncasecmp(uri, "krb5srv", 7) != 0)
return;
uri += 7;
if (*uri != ':')
return;
uri++;
if (*uri == '\0')
return;
for (; *uri != ':' && *uri != '\0'; uri++) {
if (*uri == 'm' || *uri == 'M')
primary = TRUE;
}
if (*uri != ':')
return;
uri++;
if (strncasecmp(uri, "udp", 3) == 0) {
transport = UDP;
uri += 3;
} else if (strncasecmp(uri, "tcp", 3) == 0) {
transport = TCP;
uri += 3;
} else if (strncasecmp(uri, "kkdcp", 5) == 0) {
transport = HTTPS;
uri += 5;
} else {
return;
}
if (*uri != ':')
return;
*host_out = uri + 1;
*transport_out = transport;
*primary_out = primary;
}
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)
{
krb5_error_code ret;
k5_transport transport, host_trans;
struct srv_dns_entry *answers, *entry;
char *host, *sitename;
const char *host_field, *path;
int port, def_port, primary;
ret = get_sitename(context, realm, &sitename);
if (ret)
return ret;
ret = k5_make_uri_query(context, realm, req_service, sitename, &answers);
free(sitename);
if (ret || answers == NULL)
return ret;
for (entry = answers; entry != NULL; entry = entry->next) {
def_port = default_port;
path = NULL;
parse_uri_fields(entry->host, &transport, &host_field, &primary);
if (host_field == NULL)
continue;
if (req_transport != TCP_OR_UDP && req_transport != transport)
continue;
if (transport == HTTPS) {
host_trans = 0;
def_port = 443;
parse_uri_if_https(host_field, &host_trans, &host_field, &path);
if (host_trans != HTTPS)
continue;
}
ret = k5_parse_host_string(host_field, def_port, &host, &port);
if (ret == ENOMEM)
break;
if (ret || host == NULL) {
ret = 0;
continue;
}
ret = add_host_to_list(serverlist, host, port, transport, AF_UNSPEC,
path, primary);
free(host);
if (ret)
break;
}
krb5int_free_srv_dns_data(answers);
return ret;
}
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)
{
krb5_error_code ret;
char *svcname;
int def_port;
krb5_boolean find_primary = FALSE;
if (!_krb5_use_dns_kdc(context) || !use_dns_uri(context))
return 0;
switch (svc) {
case locate_service_primary_kdc:
find_primary = TRUE;
case locate_service_kdc:
svcname = "_kerberos";
def_port = 88;
break;
case locate_service_kadmin:
svcname = "_kerberos-adm";
def_port = 749;
break;
case locate_service_kpasswd:
svcname = "_kpasswd";
def_port = 464;
break;
default:
return 0;
}
ret = locate_uri(context, realm, svcname, serverlist, transport, def_port,
find_primary);
if (serverlist->nservers == 0)
TRACE_DNS_URI_NOTFOUND(context);
return ret;
}
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)
{
const char *dnsname;
int use_dns = _krb5_use_dns_kdc(context);
krb5_error_code code;
if (!use_dns)
return 0;
switch (svc) {
case locate_service_kdc:
dnsname = "_kerberos";
break;
case locate_service_primary_kdc:
dnsname = "_kerberos-master";
break;
case locate_service_kadmin:
dnsname = "_kerberos-adm";
break;
case locate_service_krb524:
dnsname = "_krb524";
break;
case locate_service_kpasswd:
dnsname = "_kpasswd";
break;
default:
return 0;
}
code = 0;
if (transport == UDP || transport == TCP_OR_UDP)
code = locate_srv_dns_1(context, realm, dnsname, "_udp", serverlist);
if ((transport == TCP || transport == TCP_OR_UDP) && code == 0)
code = locate_srv_dns_1(context, realm, dnsname, "_tcp", serverlist);
if (serverlist->nservers == 0)
TRACE_DNS_SRV_NOTFOUND(context);
return code;
}
#endif
static krb5_error_code
locate_server(krb5_context context, const krb5_data *realm,
struct serverlist *serverlist, enum locate_service_type svc,
k5_transport transport)
{
krb5_error_code ret;
struct serverlist list = SERVERLIST_INIT;
*serverlist = list;
ret = module_locate_server(context, realm, &list, svc, transport);
if (ret != KRB5_PLUGIN_NO_HANDLE)
goto done;
ret = prof_locate_server(context, realm, &list, svc, transport);
if (ret)
goto done;
#ifdef KRB5_DNS_LOOKUP
if (list.nservers == 0) {
ret = dns_locate_server_uri(context, realm, &list, svc, transport);
if (ret)
goto done;
}
if (list.nservers == 0)
ret = dns_locate_server_srv(context, realm, &list, svc, transport);
#endif
done:
if (ret) {
k5_free_serverlist(&list);
return ret;
}
*serverlist = list;
return 0;
}
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)
{
krb5_error_code ret;
k5_transport transport = no_udp ? TCP : TCP_OR_UDP;
memset(serverlist, 0, sizeof(*serverlist));
if (realm == NULL || realm->data == NULL || realm->data[0] == 0) {
k5_setmsg(context, KRB5_REALM_CANT_RESOLVE,
"Cannot find KDC for invalid realm name \"\"");
return KRB5_REALM_CANT_RESOLVE;
}
ret = locate_server(context, realm, serverlist, svc, transport);
if (ret)
return ret;
if (serverlist->nservers == 0) {
k5_free_serverlist(serverlist);
k5_setmsg(context, KRB5_REALM_UNKNOWN,
_("Cannot find KDC for realm \"%.*s\""),
realm->length, realm->data);
return KRB5_REALM_UNKNOWN;
}
return 0;
}
krb5_error_code
k5_locate_kdc(krb5_context context, const krb5_data *realm,
struct serverlist *serverlist, krb5_boolean get_primaries,
krb5_boolean no_udp)
{
enum locate_service_type stype;
stype = get_primaries ? locate_service_primary_kdc : locate_service_kdc;
return k5_locate_server(context, realm, serverlist, stype, no_udp);
}
krb5_error_code
k5_kdclist_create(struct kdclist **kdcs_out)
{
struct kdclist *kdcs;
*kdcs_out = NULL;
kdcs = malloc(sizeof(*kdcs));
if (kdcs == NULL)
return ENOMEM;
kdcs->count = 0;
kdcs->list = NULL;
*kdcs_out = kdcs;
return 0;
}
krb5_error_code
k5_kdclist_add(struct kdclist *kdcs, const krb5_data *realm,
struct server_entry *server)
{
krb5_error_code ret;
struct kdclist_entry *newptr, *ent;
newptr = realloc(kdcs->list, (kdcs->count + 1) * sizeof(*kdcs->list));
if (newptr == NULL)
return ENOMEM;
kdcs->list = newptr;
ent = &kdcs->list[kdcs->count];
ret = krb5int_copy_data_contents(NULL, realm, &ent->realm);
if (ret)
return ret;
ent->server = *server;
memset(server, 0, sizeof(*server));
kdcs->count++;
return 0;
}
static krb5_boolean
mark_entry(struct kdclist_entry *ent, struct serverlist *primaries)
{
if (primaries->nservers == 0) {
ent->server.primary = 1;
return FALSE;
}
ent->server.primary = server_list_contains(primaries, &ent->server);
return !ent->server.primary;
}
static krb5_boolean
mark_matching_servers(struct kdclist *kdcs, size_t start,
struct serverlist *primaries)
{
size_t i;
struct kdclist_entry *ent = &kdcs->list[start];
if (mark_entry(ent, primaries))
return TRUE;
for (i = start + 1; i < kdcs->count; i++) {
if (kdcs->list[i].server.primary == 1)
continue;
if (kdcs->list[i].server.transport != ent->server.transport)
continue;
if (!data_eq(kdcs->list[i].realm, ent->realm))
continue;
if (mark_entry(&kdcs->list[i], primaries))
return TRUE;
}
return FALSE;
}
krb5_boolean
k5_kdclist_any_replicas(krb5_context context, struct kdclist *kdcs)
{
size_t i;
struct kdclist_entry *ent;
struct serverlist primaries;
krb5_boolean found;
for (i = 0; i < kdcs->count; i++) {
if (kdcs->list[i].server.primary == 0)
return TRUE;
}
for (i = 0; i < kdcs->count; i++) {
ent = &kdcs->list[i];
if (ent->server.primary == 1)
continue;
if (locate_server(context, &ent->realm, &primaries,
locate_service_primary_kdc,
ent->server.transport) != 0)
return FALSE;
found = mark_matching_servers(kdcs, i, &primaries);
k5_free_serverlist(&primaries);
if (found)
return TRUE;
}
return FALSE;
}
void
k5_kdclist_free(struct kdclist *kdcs)
{
size_t i;
if (kdcs == NULL)
return;
for (i = 0; i < kdcs->count; i++) {
free(kdcs->list[i].realm.data);
free(kdcs->list[i].server.hostname);
free(kdcs->list[i].server.uri_path);
}
free(kdcs->list);
free(kdcs);
}