xref: /freebsd/crypto/krb5/src/lib/krb5/os/locate_kdc.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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