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 static int
maybe_use_dns(krb5_context context,const char * name,int defalt)42 maybe_use_dns (krb5_context context, const char *name, int defalt)
43 {
44     krb5_error_code code;
45     char * value = NULL;
46     int use_dns = 0;
47 
48     code = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
49                               name, 0, 0, &value);
50     if (value == 0 && code == 0) {
51         code = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
52                                   KRB5_CONF_DNS_FALLBACK, 0, 0, &value);
53     }
54     if (code)
55         return defalt;
56 
57     if (value == 0)
58         return defalt;
59 
60     use_dns = _krb5_conf_boolean(value);
61     profile_release_string(value);
62     return use_dns;
63 }
64 
65 static krb5_boolean
use_dns_uri(krb5_context ctx)66 use_dns_uri(krb5_context ctx)
67 {
68     krb5_error_code ret;
69     int use;
70 
71     ret = profile_get_boolean(ctx->profile, KRB5_CONF_LIBDEFAULTS,
72                               KRB5_CONF_DNS_URI_LOOKUP, NULL,
73                               DEFAULT_URI_LOOKUP, &use);
74     return ret ? DEFAULT_URI_LOOKUP : use;
75 }
76 
77 int
_krb5_use_dns_kdc(krb5_context context)78 _krb5_use_dns_kdc(krb5_context context)
79 {
80     return maybe_use_dns(context, KRB5_CONF_DNS_LOOKUP_KDC,
81                          DEFAULT_LOOKUP_KDC);
82 }
83 
84 int
_krb5_use_dns_realm(krb5_context context)85 _krb5_use_dns_realm(krb5_context context)
86 {
87     return maybe_use_dns(context, KRB5_CONF_DNS_LOOKUP_REALM,
88                          DEFAULT_LOOKUP_REALM);
89 }
90 
91 #endif /* KRB5_DNS_LOOKUP */
92 
93 /* Free up everything pointed to by the serverlist structure, but don't
94  * free the structure itself. */
95 void
k5_free_serverlist(struct serverlist * list)96 k5_free_serverlist (struct serverlist *list)
97 {
98     size_t i;
99 
100     for (i = 0; i < list->nservers; i++) {
101         free(list->servers[i].hostname);
102         free(list->servers[i].uri_path);
103     }
104     free(list->servers);
105     list->servers = NULL;
106     list->nservers = 0;
107 }
108 
109 #include <stdarg.h>
110 static inline void
Tprintf(const char * fmt,...)111 Tprintf(const char *fmt, ...)
112 {
113 #ifdef TEST
114     va_list ap;
115     va_start(ap, fmt);
116     vfprintf(stderr, fmt, ap);
117     va_end(ap);
118 #endif
119 }
120 
121 /* Make room for a new server entry in list and return a pointer to the new
122  * entry.  (Do not increment list->nservers.) */
123 static struct server_entry *
new_server_entry(struct serverlist * list)124 new_server_entry(struct serverlist *list)
125 {
126     struct server_entry *newservers, *entry;
127     size_t newspace = (list->nservers + 1) * sizeof(struct server_entry);
128 
129     newservers = realloc(list->servers, newspace);
130     if (newservers == NULL)
131         return NULL;
132     list->servers = newservers;
133     entry = &newservers[list->nservers];
134     memset(entry, 0, sizeof(*entry));
135     entry->primary = -1;
136     return entry;
137 }
138 
139 /* Add an address entry to list. */
140 static int
add_addr_to_list(struct serverlist * list,k5_transport transport,int family,size_t addrlen,struct sockaddr * addr)141 add_addr_to_list(struct serverlist *list, k5_transport transport, int family,
142                  size_t addrlen, struct sockaddr *addr)
143 {
144     struct server_entry *entry;
145 
146     entry = new_server_entry(list);
147     if (entry == NULL)
148         return ENOMEM;
149     entry->transport = transport;
150     entry->family = family;
151     entry->hostname = NULL;
152     entry->uri_path = NULL;
153     entry->addrlen = addrlen;
154     memcpy(&entry->addr, addr, addrlen);
155     list->nservers++;
156     return 0;
157 }
158 
159 /* Add a hostname entry to list. */
160 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)161 add_host_to_list(struct serverlist *list, const char *hostname, int port,
162                  k5_transport transport, int family, const char *uri_path,
163                  int primary)
164 {
165     struct server_entry *entry;
166 
167     entry = new_server_entry(list);
168     if (entry == NULL)
169         return ENOMEM;
170     entry->transport = transport;
171     entry->family = family;
172     entry->hostname = strdup(hostname);
173     if (entry->hostname == NULL)
174         goto oom;
175     if (uri_path != NULL) {
176         entry->uri_path = strdup(uri_path);
177         if (entry->uri_path == NULL)
178             goto oom;
179     }
180     entry->port = port;
181     entry->primary = primary;
182     list->nservers++;
183     return 0;
184 oom:
185     free(entry->hostname);
186     entry->hostname = NULL;
187     return ENOMEM;
188 }
189 
190 static void
parse_uri_if_https(const char * host_or_uri,k5_transport * transport,const char ** host,const char ** uri_path)191 parse_uri_if_https(const char *host_or_uri, k5_transport *transport,
192                    const char **host, const char **uri_path)
193 {
194     char *cp;
195 
196     if (strncmp(host_or_uri, "https://", 8) == 0) {
197         *transport = HTTPS;
198         *host = host_or_uri + 8;
199 
200         cp = strchr(*host, '/');
201         if (cp != NULL) {
202             *cp = '\0';
203             *uri_path = cp + 1;
204         }
205     }
206 }
207 
208 /* Return true if server is identical to an entry in list. */
209 static krb5_boolean
server_list_contains(struct serverlist * list,struct server_entry * server)210 server_list_contains(struct serverlist *list, struct server_entry *server)
211 {
212     struct server_entry *ent;
213 
214     for (ent = list->servers; ent < list->servers + list->nservers; ent++) {
215         if (server->hostname != NULL && ent->hostname != NULL &&
216             strcmp(server->hostname, ent->hostname) == 0)
217             return TRUE;
218         if (server->hostname == NULL && ent->hostname == NULL &&
219             server->addrlen == ent->addrlen &&
220             memcmp(&server->addr, &ent->addr, server->addrlen) == 0)
221             return TRUE;
222     }
223     return FALSE;
224 }
225 
226 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)227 locate_srv_conf_1(krb5_context context, const krb5_data *realm,
228                   const char * name, struct serverlist *serverlist,
229                   k5_transport transport, int udpport)
230 {
231     const char *realm_srv_names[4];
232     char **hostlist = NULL, *realmstr = NULL, *host = NULL;
233     const char *hostspec;
234     krb5_error_code code;
235     int i, default_port;
236 
237     Tprintf("looking in krb5.conf for realm %s entry %s; ports %d,%d\n",
238             realm->data, name, udpport);
239 
240     realmstr = k5memdup0(realm->data, realm->length, &code);
241     if (realmstr == NULL)
242         goto cleanup;
243 
244     realm_srv_names[0] = KRB5_CONF_REALMS;
245     realm_srv_names[1] = realmstr;
246     realm_srv_names[2] = name;
247     realm_srv_names[3] = 0;
248     code = profile_get_values(context->profile, realm_srv_names, &hostlist);
249     if (code == PROF_NO_RELATION && strcmp(name, KRB5_CONF_PRIMARY_KDC) == 0) {
250         realm_srv_names[2] = KRB5_CONF_MASTER_KDC;
251         code = profile_get_values(context->profile, realm_srv_names,
252                                   &hostlist);
253     }
254     if (code) {
255         Tprintf("config file lookup failed: %s\n", error_message(code));
256         if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
257             code = 0;
258         goto cleanup;
259     }
260 
261     for (i = 0; hostlist[i]; i++) {
262         int port_num;
263         k5_transport this_transport = transport;
264         const char *uri_path = NULL;
265 
266         hostspec = hostlist[i];
267         Tprintf("entry %d is '%s'\n", i, hostspec);
268 
269         parse_uri_if_https(hostspec, &this_transport, &hostspec, &uri_path);
270 
271         default_port = (this_transport == HTTPS) ? 443 : udpport;
272         code = k5_parse_host_string(hostspec, default_port, &host, &port_num);
273         if (code == 0 && host == NULL)
274             code = EINVAL;
275         if (code)
276             goto cleanup;
277 
278         code = add_host_to_list(serverlist, host, port_num, this_transport,
279                                 AF_UNSPEC, uri_path, -1);
280         if (code)
281             goto cleanup;
282 
283         free(host);
284         host = NULL;
285     }
286 
287 cleanup:
288     free(realmstr);
289     free(host);
290     profile_free_list(hostlist);
291     return code;
292 }
293 
294 #ifdef TEST
295 static krb5_error_code
krb5_locate_srv_conf(krb5_context context,const krb5_data * realm,const char * name,struct serverlist * al,int udpport)296 krb5_locate_srv_conf(krb5_context context, const krb5_data *realm,
297                      const char *name, struct serverlist *al, int udpport)
298 {
299     krb5_error_code ret;
300 
301     ret = locate_srv_conf_1(context, realm, name, al, TCP_OR_UDP, udpport);
302     if (ret)
303         return ret;
304     if (al->nservers == 0)        /* Couldn't resolve any KDC names */
305         return KRB5_REALM_CANT_RESOLVE;
306     return 0;
307 }
308 #endif
309 
310 #ifdef KRB5_DNS_LOOKUP
311 static krb5_error_code
locate_srv_dns_1(krb5_context context,const krb5_data * realm,const char * service,const char * protocol,struct serverlist * serverlist)312 locate_srv_dns_1(krb5_context context, const krb5_data *realm,
313                  const char *service, const char *protocol,
314                  struct serverlist *serverlist)
315 {
316     struct srv_dns_entry *head = NULL, *entry = NULL;
317     krb5_error_code code = 0;
318     k5_transport transport;
319 
320     code = krb5int_make_srv_query_realm(context, realm, service, protocol,
321                                         &head);
322     if (code)
323         return 0;
324 
325     if (head == NULL)
326         return 0;
327 
328     /* Check for the "." case indicating no support.  */
329     if (head->next == NULL && head->host[0] == '\0') {
330         code = KRB5_ERR_NO_SERVICE;
331         goto cleanup;
332     }
333 
334     for (entry = head; entry != NULL; entry = entry->next) {
335         transport = (strcmp(protocol, "_tcp") == 0) ? TCP : UDP;
336         code = add_host_to_list(serverlist, entry->host, entry->port,
337                                 transport, AF_UNSPEC, NULL, -1);
338         if (code)
339             goto cleanup;
340     }
341 
342 cleanup:
343     krb5int_free_srv_dns_data(head);
344     return code;
345 }
346 #endif
347 
348 #include <krb5/locate_plugin.h>
349 
350 #if TARGET_OS_MAC
351 static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR,
352                                  LIBDIR "/krb5/plugins/libkrb5",
353                                  NULL }; /* should be a list */
354 #else
355 static const char *objdirs[] = { LIBDIR "/krb5/plugins/libkrb5", NULL };
356 #endif
357 
358 struct module_callback_data {
359     int out_of_mem;
360     struct serverlist *list;
361 };
362 
363 static int
module_callback(void * cbdata,int socktype,struct sockaddr * sa)364 module_callback(void *cbdata, int socktype, struct sockaddr *sa)
365 {
366     struct module_callback_data *d = cbdata;
367     size_t addrlen;
368     k5_transport transport;
369 
370     if (socktype != SOCK_STREAM && socktype != SOCK_DGRAM)
371         return 0;
372     if (sa->sa_family == AF_INET)
373         addrlen = sizeof(struct sockaddr_in);
374     else if (sa->sa_family == AF_INET6)
375         addrlen = sizeof(struct sockaddr_in6);
376     else
377         return 0;
378     transport = (socktype == SOCK_STREAM) ? TCP : UDP;
379     if (add_addr_to_list(d->list, transport, sa->sa_family, addrlen,
380                          sa) != 0) {
381         /* Assumes only error is ENOMEM.  */
382         d->out_of_mem = 1;
383         return 1;
384     }
385     return 0;
386 }
387 
388 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)389 module_locate_server(krb5_context ctx, const krb5_data *realm,
390                      struct serverlist *serverlist,
391                      enum locate_service_type svc, k5_transport transport)
392 {
393     struct krb5plugin_service_locate_result *res = NULL;
394     krb5_error_code code;
395     struct krb5plugin_service_locate_ftable *vtbl = NULL;
396     void **ptrs;
397     char *realmz;               /* NUL-terminated realm */
398     int socktype, i;
399     struct module_callback_data cbdata = { 0, };
400     const char *msg;
401 
402     Tprintf("in module_locate_server\n");
403     cbdata.list = serverlist;
404     if (!PLUGIN_DIR_OPEN(&ctx->libkrb5_plugins)) {
405 
406         code = krb5int_open_plugin_dirs(objdirs, NULL, &ctx->libkrb5_plugins,
407                                         &ctx->err);
408         if (code)
409             return KRB5_PLUGIN_NO_HANDLE;
410     }
411 
412     code = krb5int_get_plugin_dir_data(&ctx->libkrb5_plugins,
413                                        "service_locator", &ptrs, &ctx->err);
414     if (code) {
415         Tprintf("error looking up plugin symbols: %s\n",
416                 (msg = krb5_get_error_message(ctx, code)));
417         krb5_free_error_message(ctx, msg);
418         return KRB5_PLUGIN_NO_HANDLE;
419     }
420 
421     if (realm->length >= UINT_MAX) {
422         krb5int_free_plugin_dir_data(ptrs);
423         return ENOMEM;
424     }
425     realmz = k5memdup0(realm->data, realm->length, &code);
426     if (realmz == NULL) {
427         krb5int_free_plugin_dir_data(ptrs);
428         return code;
429     }
430     for (i = 0; ptrs[i]; i++) {
431         void *blob;
432 
433         vtbl = ptrs[i];
434         Tprintf("element %d is %p\n", i, ptrs[i]);
435 
436         /* For now, don't keep the plugin data alive.  For long-lived
437          * contexts, it may be desirable to change that later. */
438         code = vtbl->init(ctx, &blob);
439         if (code)
440             continue;
441 
442         socktype = (transport == TCP) ? SOCK_STREAM : SOCK_DGRAM;
443         code = vtbl->lookup(blob, svc, realmz, socktype, AF_UNSPEC,
444                             module_callback, &cbdata);
445         /* Also ask for TCP addresses if we got UDP addresses and want both. */
446         if (code == 0 && transport == TCP_OR_UDP) {
447             code = vtbl->lookup(blob, svc, realmz, SOCK_STREAM, AF_UNSPEC,
448                                 module_callback, &cbdata);
449             if (code == KRB5_PLUGIN_NO_HANDLE)
450                 code = 0;
451         }
452         vtbl->fini(blob);
453         if (code == KRB5_PLUGIN_NO_HANDLE) {
454             /* Module passes, keep going.  */
455             /* XXX */
456             Tprintf("plugin doesn't handle this realm (KRB5_PLUGIN_NO_HANDLE)"
457                     "\n");
458             continue;
459         }
460         if (code != 0) {
461             /* Module encountered an actual error.  */
462             Tprintf("plugin lookup routine returned error %d: %s\n",
463                     code, error_message(code));
464             free(realmz);
465             krb5int_free_plugin_dir_data(ptrs);
466             return code;
467         }
468         break;
469     }
470     if (ptrs[i] == NULL) {
471         Tprintf("ran off end of plugin list\n");
472         free(realmz);
473         krb5int_free_plugin_dir_data(ptrs);
474         return KRB5_PLUGIN_NO_HANDLE;
475     }
476     Tprintf("stopped with plugin #%d, res=%p\n", i, res);
477 
478     /* Got something back, yippee.  */
479     Tprintf("now have %lu addrs in list %p\n",
480             (unsigned long)serverlist->nservers, serverlist);
481     free(realmz);
482     krb5int_free_plugin_dir_data(ptrs);
483     return 0;
484 }
485 
486 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)487 prof_locate_server(krb5_context context, const krb5_data *realm,
488                    struct serverlist *serverlist, enum locate_service_type svc,
489                    k5_transport transport)
490 {
491     const char *profname;
492     int dflport = 0;
493     struct servent *serv;
494 
495     switch (svc) {
496     case locate_service_kdc:
497         profname = KRB5_CONF_KDC;
498         /* We used to use /etc/services for these, but enough systems have old,
499          * crufty, wrong settings that this is probably better. */
500     kdc_ports:
501         dflport = KRB5_DEFAULT_PORT;
502         break;
503     case locate_service_primary_kdc:
504         profname = KRB5_CONF_PRIMARY_KDC;
505         goto kdc_ports;
506     case locate_service_kadmin:
507         profname = KRB5_CONF_ADMIN_SERVER;
508         dflport = DEFAULT_KADM5_PORT;
509         break;
510     case locate_service_krb524:
511         profname = KRB5_CONF_KRB524_SERVER;
512         serv = getservbyname("krb524", "udp");
513         dflport = serv ? serv->s_port : 4444;
514         break;
515     case locate_service_kpasswd:
516         profname = KRB5_CONF_KPASSWD_SERVER;
517         dflport = DEFAULT_KPASSWD_PORT;
518         break;
519     default:
520         return EBUSY;           /* XXX */
521     }
522 
523     return locate_srv_conf_1(context, realm, profname, serverlist, transport,
524                              dflport);
525 }
526 
527 #ifdef KRB5_DNS_LOOKUP
528 
529 /*
530  * Parse the initial part of the URI, first confirming the scheme name.  Get
531  * the transport, flags (indicating primary status), and host.  The host is
532  * either an address or hostname with an optional port, or an HTTPS URL.
533  * The format is krb5srv:flags:udp|tcp|kkdcp:host
534  *
535  * Return a NULL *host_out if there are any problems parsing the URI.
536  */
537 static void
parse_uri_fields(const char * uri,k5_transport * transport_out,const char ** host_out,int * primary_out)538 parse_uri_fields(const char *uri, k5_transport *transport_out,
539                  const char **host_out, int *primary_out)
540 
541 {
542     k5_transport transport;
543     int primary = FALSE;
544 
545     *transport_out = 0;
546     *host_out = NULL;
547     *primary_out = -1;
548 
549     /* Confirm the scheme name. */
550     if (strncasecmp(uri, "krb5srv", 7) != 0)
551         return;
552 
553     uri += 7;
554     if (*uri != ':')
555         return;
556 
557     uri++;
558     if (*uri == '\0')
559         return;
560 
561     /* Check the flags field for supported flags. */
562     for (; *uri != ':' && *uri != '\0'; uri++) {
563         if (*uri == 'm' || *uri == 'M')
564             primary = TRUE;
565     }
566     if (*uri != ':')
567         return;
568 
569     /* Look for the transport type. */
570     uri++;
571     if (strncasecmp(uri, "udp", 3) == 0) {
572         transport = UDP;
573         uri += 3;
574     } else if (strncasecmp(uri, "tcp", 3) == 0) {
575         transport = TCP;
576         uri += 3;
577     } else if (strncasecmp(uri, "kkdcp", 5) == 0) {
578         /* Currently the only MS-KKDCP transport type is HTTPS. */
579         transport = HTTPS;
580         uri += 5;
581     } else {
582         return;
583     }
584 
585     if (*uri != ':')
586         return;
587 
588     /* The rest of the URI is the host (with optional port) or URI. */
589     *host_out = uri + 1;
590     *transport_out = transport;
591     *primary_out = primary;
592 }
593 
594 /*
595  * Collect a list of servers from DNS URI records, for the requested service
596  * and transport type.  Problematic entries are skipped.
597  */
598 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)599 locate_uri(krb5_context context, const krb5_data *realm,
600            const char *req_service, struct serverlist *serverlist,
601            k5_transport req_transport, int default_port,
602            krb5_boolean primary_only)
603 {
604     krb5_error_code ret;
605     k5_transport transport, host_trans;
606     struct srv_dns_entry *answers, *entry;
607     char *host;
608     const char *host_field, *path;
609     int port, def_port, primary;
610 
611     ret = k5_make_uri_query(context, realm, req_service, &answers);
612     if (ret || answers == NULL)
613         return ret;
614 
615     for (entry = answers; entry != NULL; entry = entry->next) {
616         def_port = default_port;
617         path = NULL;
618 
619         parse_uri_fields(entry->host, &transport, &host_field, &primary);
620         if (host_field == NULL)
621             continue;
622 
623         /* TCP_OR_UDP allows entries of any transport type; otherwise
624          * we're asking for a match. */
625         if (req_transport != TCP_OR_UDP && req_transport != transport)
626             continue;
627 
628         /* Process a MS-KKDCP target. */
629         if (transport == HTTPS) {
630             host_trans = 0;
631             def_port = 443;
632             parse_uri_if_https(host_field, &host_trans, &host_field, &path);
633             if (host_trans != HTTPS)
634                 continue;
635         }
636 
637         ret = k5_parse_host_string(host_field, def_port, &host, &port);
638         if (ret == ENOMEM)
639             break;
640 
641         if (ret || host == NULL) {
642             ret = 0;
643             continue;
644         }
645 
646         ret = add_host_to_list(serverlist, host, port, transport, AF_UNSPEC,
647                                path, primary);
648         free(host);
649         if (ret)
650             break;
651     }
652 
653     krb5int_free_srv_dns_data(answers);
654     return ret;
655 }
656 
657 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)658 dns_locate_server_uri(krb5_context context, const krb5_data *realm,
659                       struct serverlist *serverlist,
660                       enum locate_service_type svc, k5_transport transport)
661 {
662     krb5_error_code ret;
663     char *svcname;
664     int def_port;
665     krb5_boolean find_primary = FALSE;
666 
667     if (!_krb5_use_dns_kdc(context) || !use_dns_uri(context))
668         return 0;
669 
670     switch (svc) {
671     case locate_service_primary_kdc:
672         find_primary = TRUE;
673         /* Fall through */
674     case locate_service_kdc:
675         svcname = "_kerberos";
676         def_port = 88;
677         break;
678     case locate_service_kadmin:
679         svcname = "_kerberos-adm";
680         def_port = 749;
681         break;
682     case locate_service_kpasswd:
683         svcname = "_kpasswd";
684         def_port = 464;
685         break;
686     default:
687         return 0;
688     }
689 
690     ret = locate_uri(context, realm, svcname, serverlist, transport, def_port,
691                      find_primary);
692 
693     if (serverlist->nservers == 0)
694         TRACE_DNS_URI_NOTFOUND(context);
695 
696     return ret;
697 }
698 
699 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)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)
703 {
704     const char *dnsname;
705     int use_dns = _krb5_use_dns_kdc(context);
706     krb5_error_code code;
707 
708     if (!use_dns)
709         return 0;
710 
711     switch (svc) {
712     case locate_service_kdc:
713         dnsname = "_kerberos";
714         break;
715     case locate_service_primary_kdc:
716         dnsname = "_kerberos-master";
717         break;
718     case locate_service_kadmin:
719         dnsname = "_kerberos-adm";
720         break;
721     case locate_service_krb524:
722         dnsname = "_krb524";
723         break;
724     case locate_service_kpasswd:
725         dnsname = "_kpasswd";
726         break;
727     default:
728         return 0;
729     }
730 
731     code = 0;
732     if (transport == UDP || transport == TCP_OR_UDP)
733         code = locate_srv_dns_1(context, realm, dnsname, "_udp", serverlist);
734 
735     if ((transport == TCP || transport == TCP_OR_UDP) && code == 0)
736         code = locate_srv_dns_1(context, realm, dnsname, "_tcp", serverlist);
737 
738     if (serverlist->nservers == 0)
739         TRACE_DNS_SRV_NOTFOUND(context);
740 
741     return code;
742 }
743 #endif /* KRB5_DNS_LOOKUP */
744 
745 /*
746  * Try all of the server location methods in sequence.  transport must be
747  * TCP_OR_UDP, TCP, or UDP.  It is applied to hostname entries in the profile
748  * and affects whether we query modules or DNS for UDP or TCP or both, but does
749  * not restrict a method from returning entries of other transports.
750  */
751 static krb5_error_code
locate_server(krb5_context context,const krb5_data * realm,struct serverlist * serverlist,enum locate_service_type svc,k5_transport transport)752 locate_server(krb5_context context, const krb5_data *realm,
753               struct serverlist *serverlist, enum locate_service_type svc,
754               k5_transport transport)
755 {
756     krb5_error_code ret;
757     struct serverlist list = SERVERLIST_INIT;
758 
759     *serverlist = list;
760 
761     /* Try modules.  If a module returns 0 but leaves the list empty, return an
762      * empty list. */
763     ret = module_locate_server(context, realm, &list, svc, transport);
764     if (ret != KRB5_PLUGIN_NO_HANDLE)
765         goto done;
766 
767     /* Try the profile.  Fall back to DNS if it returns an empty list. */
768     ret = prof_locate_server(context, realm, &list, svc, transport);
769     if (ret)
770         goto done;
771 
772 #ifdef KRB5_DNS_LOOKUP
773     if (list.nservers == 0) {
774         ret = dns_locate_server_uri(context, realm, &list, svc, transport);
775         if (ret)
776             goto done;
777     }
778 
779     if (list.nservers == 0)
780         ret = dns_locate_server_srv(context, realm, &list, svc, transport);
781 #endif
782 
783 done:
784     if (ret) {
785         k5_free_serverlist(&list);
786         return ret;
787     }
788     *serverlist = list;
789     return 0;
790 }
791 
792 /*
793  * Wrapper function for the various backends
794  */
795 
796 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)797 k5_locate_server(krb5_context context, const krb5_data *realm,
798                  struct serverlist *serverlist, enum locate_service_type svc,
799                  krb5_boolean no_udp)
800 {
801     krb5_error_code ret;
802     k5_transport transport = no_udp ? TCP : TCP_OR_UDP;
803 
804     memset(serverlist, 0, sizeof(*serverlist));
805     if (realm == NULL || realm->data == NULL || realm->data[0] == 0) {
806         k5_setmsg(context, KRB5_REALM_CANT_RESOLVE,
807                   "Cannot find KDC for invalid realm name \"\"");
808         return KRB5_REALM_CANT_RESOLVE;
809     }
810 
811     ret = locate_server(context, realm, serverlist, svc, transport);
812     if (ret)
813         return ret;
814 
815     if (serverlist->nservers == 0) {
816         k5_free_serverlist(serverlist);
817         k5_setmsg(context, KRB5_REALM_UNKNOWN,
818                   _("Cannot find KDC for realm \"%.*s\""),
819                   realm->length, realm->data);
820         return KRB5_REALM_UNKNOWN;
821     }
822     return 0;
823 }
824 
825 krb5_error_code
k5_locate_kdc(krb5_context context,const krb5_data * realm,struct serverlist * serverlist,krb5_boolean get_primaries,krb5_boolean no_udp)826 k5_locate_kdc(krb5_context context, const krb5_data *realm,
827               struct serverlist *serverlist, krb5_boolean get_primaries,
828               krb5_boolean no_udp)
829 {
830     enum locate_service_type stype;
831 
832     stype = get_primaries ? locate_service_primary_kdc : locate_service_kdc;
833     return k5_locate_server(context, realm, serverlist, stype, no_udp);
834 }
835 
836 krb5_boolean
k5_kdc_is_primary(krb5_context context,const krb5_data * realm,struct server_entry * server)837 k5_kdc_is_primary(krb5_context context, const krb5_data *realm,
838                   struct server_entry *server)
839 {
840     struct serverlist list;
841     krb5_boolean found;
842 
843     if (server->primary != -1)
844         return server->primary;
845 
846     if (locate_server(context, realm, &list, locate_service_primary_kdc,
847                       server->transport) != 0)
848         return FALSE;
849     found = server_list_contains(&list, server);
850     k5_free_serverlist(&list);
851     return found;
852 }
853