Imported Upstream version 1.15.1
[platform/upstream/krb5.git] / src / lib / krb5 / os / locate_kdc.c
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 #ifdef KRB5_DNS_LOOKUP
31 #ifdef WSHELPER
32 #include <wshelper.h>
33 #else /* WSHELPER */
34 #include <netinet/in.h>
35 #include <arpa/inet.h>
36 #include <arpa/nameser.h>
37 #include <resolv.h>
38 #include <netdb.h>
39 #endif /* WSHELPER */
40 #include "dnsglue.h"
41
42 #define DEFAULT_LOOKUP_KDC 1
43 #if KRB5_DNS_LOOKUP_REALM
44 #define DEFAULT_LOOKUP_REALM 1
45 #else
46 #define DEFAULT_LOOKUP_REALM 0
47 #endif
48 #define DEFAULT_URI_LOOKUP TRUE
49
50 static int
51 maybe_use_dns (krb5_context context, const char *name, int defalt)
52 {
53     krb5_error_code code;
54     char * value = NULL;
55     int use_dns = 0;
56
57     code = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
58                               name, 0, 0, &value);
59     if (value == 0 && code == 0) {
60         code = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
61                                   KRB5_CONF_DNS_FALLBACK, 0, 0, &value);
62     }
63     if (code)
64         return defalt;
65
66     if (value == 0)
67         return defalt;
68
69     use_dns = _krb5_conf_boolean(value);
70     profile_release_string(value);
71     return use_dns;
72 }
73
74 static krb5_boolean
75 use_dns_uri(krb5_context ctx)
76 {
77     krb5_error_code ret;
78     int use;
79
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;
84 }
85
86 int
87 _krb5_use_dns_kdc(krb5_context context)
88 {
89     return maybe_use_dns(context, KRB5_CONF_DNS_LOOKUP_KDC,
90                          DEFAULT_LOOKUP_KDC);
91 }
92
93 int
94 _krb5_use_dns_realm(krb5_context context)
95 {
96     return maybe_use_dns(context, KRB5_CONF_DNS_LOOKUP_REALM,
97                          DEFAULT_LOOKUP_REALM);
98 }
99
100 #endif /* KRB5_DNS_LOOKUP */
101
102 /* Free up everything pointed to by the serverlist structure, but don't
103  * free the structure itself. */
104 void
105 k5_free_serverlist (struct serverlist *list)
106 {
107     size_t i;
108
109     for (i = 0; i < list->nservers; i++) {
110         free(list->servers[i].hostname);
111         free(list->servers[i].uri_path);
112     }
113     free(list->servers);
114     list->servers = NULL;
115     list->nservers = 0;
116 }
117
118 #include <stdarg.h>
119 static inline void
120 Tprintf(const char *fmt, ...)
121 {
122 #ifdef TEST
123     va_list ap;
124     va_start(ap, fmt);
125     vfprintf(stderr, fmt, ap);
126     va_end(ap);
127 #endif
128 }
129
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)
134 {
135     struct server_entry *newservers, *entry;
136     size_t newspace = (list->nservers + 1) * sizeof(struct server_entry);
137
138     newservers = realloc(list->servers, newspace);
139     if (newservers == NULL)
140         return NULL;
141     list->servers = newservers;
142     entry = &newservers[list->nservers];
143     memset(entry, 0, sizeof(*entry));
144     entry->master = -1;
145     return entry;
146 }
147
148 /* Add an address entry to list. */
149 static int
150 add_addr_to_list(struct serverlist *list, k5_transport transport, int family,
151                  size_t addrlen, struct sockaddr *addr)
152 {
153     struct server_entry *entry;
154
155     entry = new_server_entry(list);
156     if (entry == NULL)
157         return ENOMEM;
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);
164     list->nservers++;
165     return 0;
166 }
167
168 /* Add a hostname entry to list. */
169 static int
170 add_host_to_list(struct serverlist *list, const char *hostname, int port,
171                  k5_transport transport, int family, const char *uri_path,
172                  int master)
173 {
174     struct server_entry *entry;
175
176     entry = new_server_entry(list);
177     if (entry == NULL)
178         return ENOMEM;
179     entry->transport = transport;
180     entry->family = family;
181     entry->hostname = strdup(hostname);
182     if (entry->hostname == NULL)
183         goto oom;
184     if (uri_path != NULL) {
185         entry->uri_path = strdup(uri_path);
186         if (entry->uri_path == NULL)
187             goto oom;
188     }
189     entry->port = port;
190     entry->master = master;
191     list->nservers++;
192     return 0;
193 oom:
194     free(entry->hostname);
195     entry->hostname = NULL;
196     return ENOMEM;
197 }
198
199 static void
200 parse_uri_if_https(const char *host_or_uri, k5_transport *transport,
201                    const char **host, const char **uri_path)
202 {
203     char *cp;
204
205     if (strncmp(host_or_uri, "https://", 8) == 0) {
206         *transport = HTTPS;
207         *host = host_or_uri + 8;
208
209         cp = strchr(*host, '/');
210         if (cp != NULL) {
211             *cp = '\0';
212             *uri_path = cp + 1;
213         }
214     }
215 }
216
217 /* Return true if server is identical to an entry in list. */
218 static krb5_boolean
219 server_list_contains(struct serverlist *list, struct server_entry *server)
220 {
221     struct server_entry *ent;
222
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)
226             return TRUE;
227         if (server->hostname == NULL && ent->hostname == NULL &&
228             server->addrlen == ent->addrlen &&
229             memcmp(&server->addr, &ent->addr, server->addrlen) == 0)
230             return TRUE;
231     }
232     return FALSE;
233 }
234
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)
239 {
240     const char *realm_srv_names[4];
241     char **hostlist = NULL, *realmstr = NULL, *host = NULL;
242     const char *hostspec;
243     krb5_error_code code;
244     int i, default_port;
245
246     Tprintf("looking in krb5.conf for realm %s entry %s; ports %d,%d\n",
247             realm->data, name, udpport);
248
249     realmstr = k5memdup0(realm->data, realm->length, &code);
250     if (realmstr == NULL)
251         goto cleanup;
252
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);
258     if (code) {
259         Tprintf("config file lookup failed: %s\n", error_message(code));
260         if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
261             code = 0;
262         goto cleanup;
263     }
264
265     for (i = 0; hostlist[i]; i++) {
266         int port_num;
267         k5_transport this_transport = transport;
268         const char *uri_path = NULL;
269
270         hostspec = hostlist[i];
271         Tprintf("entry %d is '%s'\n", i, hostspec);
272
273         parse_uri_if_https(hostspec, &this_transport, &hostspec, &uri_path);
274
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)
278             code = EINVAL;
279         if (code)
280             goto cleanup;
281
282         code = add_host_to_list(serverlist, host, port_num, this_transport,
283                                 AF_UNSPEC, uri_path, -1);
284         if (code)
285             goto cleanup;
286
287         free(host);
288         host = NULL;
289     }
290
291 cleanup:
292     free(realmstr);
293     free(host);
294     profile_free_list(hostlist);
295     return code;
296 }
297
298 #ifdef TEST
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)
302 {
303     krb5_error_code ret;
304
305     ret = locate_srv_conf_1(context, realm, name, al, TCP_OR_UDP, udpport);
306     if (ret)
307         return ret;
308     if (al->nservers == 0)        /* Couldn't resolve any KDC names */
309         return KRB5_REALM_CANT_RESOLVE;
310     return 0;
311 }
312 #endif
313
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)
318 {
319     struct srv_dns_entry *head = NULL, *entry = NULL;
320     krb5_error_code code = 0;
321     k5_transport transport;
322
323     code = krb5int_make_srv_query_realm(realm, service, protocol, &head);
324     if (code)
325         return 0;
326
327     if (head == NULL)
328         return 0;
329
330     /* Check for the "." case indicating no support.  */
331     if (head->next == NULL && head->host[0] == '\0') {
332         code = KRB5_ERR_NO_SERVICE;
333         goto cleanup;
334     }
335
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);
340         if (code)
341             goto cleanup;
342     }
343
344 cleanup:
345     krb5int_free_srv_dns_data(head);
346     return code;
347 }
348 #endif
349
350 #include <krb5/locate_plugin.h>
351
352 #if TARGET_OS_MAC
353 static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR,
354                                  LIBDIR "/krb5/plugins/libkrb5",
355                                  NULL }; /* should be a list */
356 #else
357 static const char *objdirs[] = { LIBDIR "/krb5/plugins/libkrb5", NULL };
358 #endif
359
360 struct module_callback_data {
361     int out_of_mem;
362     struct serverlist *list;
363 };
364
365 static int
366 module_callback(void *cbdata, int socktype, struct sockaddr *sa)
367 {
368     struct module_callback_data *d = cbdata;
369     size_t addrlen;
370     k5_transport transport;
371
372     if (socktype != SOCK_STREAM && socktype != SOCK_DGRAM)
373         return 0;
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);
378     else
379         return 0;
380     transport = (socktype == SOCK_STREAM) ? TCP : UDP;
381     if (add_addr_to_list(d->list, transport, sa->sa_family, addrlen,
382                          sa) != 0) {
383         /* Assumes only error is ENOMEM.  */
384         d->out_of_mem = 1;
385         return 1;
386     }
387     return 0;
388 }
389
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)
394 {
395     struct krb5plugin_service_locate_result *res = NULL;
396     krb5_error_code code;
397     struct krb5plugin_service_locate_ftable *vtbl = NULL;
398     void **ptrs;
399     char *realmz;               /* NUL-terminated realm */
400     int socktype, i;
401     struct module_callback_data cbdata = { 0, };
402     const char *msg;
403
404     Tprintf("in module_locate_server\n");
405     cbdata.list = serverlist;
406     if (!PLUGIN_DIR_OPEN(&ctx->libkrb5_plugins)) {
407
408         code = krb5int_open_plugin_dirs(objdirs, NULL, &ctx->libkrb5_plugins,
409                                         &ctx->err);
410         if (code)
411             return KRB5_PLUGIN_NO_HANDLE;
412     }
413
414     code = krb5int_get_plugin_dir_data(&ctx->libkrb5_plugins,
415                                        "service_locator", &ptrs, &ctx->err);
416     if (code) {
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;
421     }
422
423     if (realm->length >= UINT_MAX) {
424         krb5int_free_plugin_dir_data(ptrs);
425         return ENOMEM;
426     }
427     realmz = k5memdup0(realm->data, realm->length, &code);
428     if (realmz == NULL) {
429         krb5int_free_plugin_dir_data(ptrs);
430         return code;
431     }
432     for (i = 0; ptrs[i]; i++) {
433         void *blob;
434
435         vtbl = ptrs[i];
436         Tprintf("element %d is %p\n", i, ptrs[i]);
437
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);
441         if (code)
442             continue;
443
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)
452                 code = 0;
453         }
454         vtbl->fini(blob);
455         if (code == KRB5_PLUGIN_NO_HANDLE) {
456             /* Module passes, keep going.  */
457             /* XXX */
458             Tprintf("plugin doesn't handle this realm (KRB5_PLUGIN_NO_HANDLE)"
459                     "\n");
460             continue;
461         }
462         if (code != 0) {
463             /* Module encountered an actual error.  */
464             Tprintf("plugin lookup routine returned error %d: %s\n",
465                     code, error_message(code));
466             free(realmz);
467             krb5int_free_plugin_dir_data(ptrs);
468             return code;
469         }
470         break;
471     }
472     if (ptrs[i] == NULL) {
473         Tprintf("ran off end of plugin list\n");
474         free(realmz);
475         krb5int_free_plugin_dir_data(ptrs);
476         return KRB5_PLUGIN_NO_HANDLE;
477     }
478     Tprintf("stopped with plugin #%d, res=%p\n", i, res);
479
480     /* Got something back, yippee.  */
481     Tprintf("now have %lu addrs in list %p\n",
482             (unsigned long)serverlist->nservers, serverlist);
483     free(realmz);
484     krb5int_free_plugin_dir_data(ptrs);
485     return 0;
486 }
487
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)
492 {
493     const char *profname;
494     int dflport = 0;
495     struct servent *serv;
496
497     switch (svc) {
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. */
502     kdc_ports:
503         dflport = KRB5_DEFAULT_PORT;
504         break;
505     case locate_service_master_kdc:
506         profname = KRB5_CONF_MASTER_KDC;
507         goto kdc_ports;
508     case locate_service_kadmin:
509         profname = KRB5_CONF_ADMIN_SERVER;
510         dflport = DEFAULT_KADM5_PORT;
511         break;
512     case locate_service_krb524:
513         profname = KRB5_CONF_KRB524_SERVER;
514         serv = getservbyname("krb524", "udp");
515         dflport = serv ? serv->s_port : 4444;
516         break;
517     case locate_service_kpasswd:
518         profname = KRB5_CONF_KPASSWD_SERVER;
519         dflport = DEFAULT_KPASSWD_PORT;
520         break;
521     default:
522         return EBUSY;           /* XXX */
523     }
524
525     return locate_srv_conf_1(context, realm, profname, serverlist, transport,
526                              dflport);
527 }
528
529 #ifdef KRB5_DNS_LOOKUP
530
531 /*
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
536  *
537  * Return a NULL *host_out if there are any problems parsing the URI.
538  */
539 static void
540 parse_uri_fields(const char *uri, k5_transport *transport_out,
541                  const char **host_out, int *master_out)
542
543 {
544     k5_transport transport;
545     int master = FALSE;
546
547     *transport_out = 0;
548     *host_out = NULL;
549     *master_out = -1;
550
551     /* Confirm the scheme name. */
552     if (strncasecmp(uri, "krb5srv", 7) != 0)
553         return;
554
555     uri += 7;
556     if (*uri != ':')
557         return;
558
559     uri++;
560     if (*uri == '\0')
561         return;
562
563     /* Check the flags field for supported flags. */
564     for (; *uri != ':' && *uri != '\0'; uri++) {
565         if (*uri == 'm' || *uri == 'M')
566             master = TRUE;
567     }
568     if (*uri != ':')
569         return;
570
571     /* Look for the transport type. */
572     uri++;
573     if (strncasecmp(uri, "udp", 3) == 0) {
574         transport = UDP;
575         uri += 3;
576     } else if (strncasecmp(uri, "tcp", 3) == 0) {
577         transport = TCP;
578         uri += 3;
579     } else if (strncasecmp(uri, "kkdcp", 5) == 0) {
580         /* Currently the only MS-KKDCP transport type is HTTPS. */
581         transport = HTTPS;
582         uri += 5;
583     } else {
584         return;
585     }
586
587     if (*uri != ':')
588         return;
589
590     /* The rest of the URI is the host (with optional port) or URI. */
591     *host_out = uri + 1;
592     *transport_out = transport;
593     *master_out = master;
594 }
595
596 /*
597  * Collect a list of servers from DNS URI records, for the requested service
598  * and transport type.  Problematic entries are skipped.
599  */
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)
604 {
605     krb5_error_code ret;
606     k5_transport transport, host_trans;
607     struct srv_dns_entry *answers, *entry;
608     char *host;
609     const char *host_field, *path;
610     int port, def_port, master;
611
612     ret = k5_make_uri_query(realm, req_service, &answers);
613     if (ret || answers == NULL)
614         return ret;
615
616     for (entry = answers; entry != NULL; entry = entry->next) {
617         def_port = default_port;
618         path = NULL;
619
620         parse_uri_fields(entry->host, &transport, &host_field, &master);
621         if (host_field == NULL)
622             continue;
623
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)
627             continue;
628
629         /* Process a MS-KKDCP target. */
630         if (transport == HTTPS) {
631             host_trans = 0;
632             def_port = 443;
633             parse_uri_if_https(host_field, &host_trans, &host_field, &path);
634             if (host_trans != HTTPS)
635                 continue;
636         }
637
638         ret = k5_parse_host_string(host_field, def_port, &host, &port);
639         if (ret == ENOMEM)
640             break;
641
642         if (ret || host == NULL) {
643             ret = 0;
644             continue;
645         }
646
647         ret = add_host_to_list(serverlist, host, port, transport, AF_UNSPEC,
648                                path, master);
649         free(host);
650         if (ret)
651             break;
652     }
653
654     krb5int_free_srv_dns_data(answers);
655     return ret;
656 }
657
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)
662 {
663     krb5_error_code ret;
664     char *svcname;
665     int def_port;
666     krb5_boolean find_master = FALSE;
667
668     if (!_krb5_use_dns_kdc(context) || !use_dns_uri(context))
669         return 0;
670
671     switch (svc) {
672     case locate_service_master_kdc:
673         find_master = TRUE;
674         /* Fall through */
675     case locate_service_kdc:
676         svcname = "_kerberos";
677         def_port = 88;
678         break;
679     case locate_service_kadmin:
680         svcname = "_kerberos-adm";
681         def_port = 749;
682         break;
683     case locate_service_kpasswd:
684         svcname = "_kpasswd";
685         def_port = 464;
686         break;
687     default:
688         return 0;
689     }
690
691     ret = locate_uri(realm, svcname, serverlist, transport, def_port,
692                      find_master);
693     if (ret)
694         Tprintf("dns URI lookup returned error %d\n", ret);
695
696     return ret;
697 }
698
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)
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_master_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(realm, dnsname, "_udp", serverlist);
734         if (code)
735             Tprintf("dns udp lookup returned error %d\n", code);
736     }
737     if ((transport == TCP || transport == TCP_OR_UDP) && code == 0) {
738         code = locate_srv_dns_1(realm, dnsname, "_tcp", serverlist);
739         if (code)
740             Tprintf("dns tcp lookup returned error %d\n", code);
741     }
742     return code;
743 }
744 #endif /* KRB5_DNS_LOOKUP */
745
746 /*
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.
751  */
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)
756 {
757     krb5_error_code ret;
758     struct serverlist list = SERVERLIST_INIT;
759
760     *serverlist = list;
761
762     /* Try modules.  If a module returns 0 but leaves the list empty, return an
763      * empty list. */
764     ret = module_locate_server(context, realm, &list, svc, transport);
765     if (ret != KRB5_PLUGIN_NO_HANDLE)
766         goto done;
767
768     /* Try the profile.  Fall back to DNS if it returns an empty list. */
769     ret = prof_locate_server(context, realm, &list, svc, transport);
770     if (ret)
771         goto done;
772
773 #ifdef KRB5_DNS_LOOKUP
774     if (list.nservers == 0) {
775         ret = dns_locate_server_uri(context, realm, &list, svc, transport);
776         if (ret)
777             goto done;
778     }
779
780     if (list.nservers == 0)
781         ret = dns_locate_server_srv(context, realm, &list, svc, transport);
782 #endif
783
784 done:
785     if (ret) {
786         k5_free_serverlist(&list);
787         return ret;
788     }
789     *serverlist = list;
790     return 0;
791 }
792
793 /*
794  * Wrapper function for the various backends
795  */
796
797 krb5_error_code
798 k5_locate_server(krb5_context context, const krb5_data *realm,
799                  struct serverlist *serverlist, enum locate_service_type svc,
800                  krb5_boolean no_udp)
801 {
802     krb5_error_code ret;
803     k5_transport transport = no_udp ? TCP : TCP_OR_UDP;
804
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;
810     }
811
812     ret = locate_server(context, realm, serverlist, svc, transport);
813     if (ret)
814         return ret;
815
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;
822     }
823     return 0;
824 }
825
826 krb5_error_code
827 k5_locate_kdc(krb5_context context, const krb5_data *realm,
828               struct serverlist *serverlist, krb5_boolean get_masters,
829               krb5_boolean no_udp)
830 {
831     enum locate_service_type stype;
832
833     stype = get_masters ? locate_service_master_kdc : locate_service_kdc;
834     return k5_locate_server(context, realm, serverlist, stype, no_udp);
835 }
836
837 krb5_boolean
838 k5_kdc_is_master(krb5_context context, const krb5_data *realm,
839                  struct server_entry *server)
840 {
841     struct serverlist list;
842     krb5_boolean found;
843
844     if (server->master != -1)
845         return server->master;
846
847     if (locate_server(context, realm, &list, locate_service_master_kdc,
848                       server->transport) != 0)
849         return FALSE;
850     found = server_list_contains(&list, server);
851     k5_free_serverlist(&list);
852     return found;
853 }