Imported Upstream version 1.17
[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
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
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
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
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
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
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
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 *
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->master = -1;
136     return entry;
137 }
138
139 /* Add an address entry to list. */
140 static int
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
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 master)
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->master = master;
182     list->nservers++;
183     return 0;
184 oom:
185     free(entry->hostname);
186     entry->hostname = NULL;
187     return ENOMEM;
188 }
189
190 static void
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
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
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) {
250         Tprintf("config file lookup failed: %s\n", error_message(code));
251         if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
252             code = 0;
253         goto cleanup;
254     }
255
256     for (i = 0; hostlist[i]; i++) {
257         int port_num;
258         k5_transport this_transport = transport;
259         const char *uri_path = NULL;
260
261         hostspec = hostlist[i];
262         Tprintf("entry %d is '%s'\n", i, hostspec);
263
264         parse_uri_if_https(hostspec, &this_transport, &hostspec, &uri_path);
265
266         default_port = (this_transport == HTTPS) ? 443 : udpport;
267         code = k5_parse_host_string(hostspec, default_port, &host, &port_num);
268         if (code == 0 && host == NULL)
269             code = EINVAL;
270         if (code)
271             goto cleanup;
272
273         code = add_host_to_list(serverlist, host, port_num, this_transport,
274                                 AF_UNSPEC, uri_path, -1);
275         if (code)
276             goto cleanup;
277
278         free(host);
279         host = NULL;
280     }
281
282 cleanup:
283     free(realmstr);
284     free(host);
285     profile_free_list(hostlist);
286     return code;
287 }
288
289 #ifdef TEST
290 static krb5_error_code
291 krb5_locate_srv_conf(krb5_context context, const krb5_data *realm,
292                      const char *name, struct serverlist *al, int udpport)
293 {
294     krb5_error_code ret;
295
296     ret = locate_srv_conf_1(context, realm, name, al, TCP_OR_UDP, udpport);
297     if (ret)
298         return ret;
299     if (al->nservers == 0)        /* Couldn't resolve any KDC names */
300         return KRB5_REALM_CANT_RESOLVE;
301     return 0;
302 }
303 #endif
304
305 #ifdef KRB5_DNS_LOOKUP
306 static krb5_error_code
307 locate_srv_dns_1(krb5_context context, const krb5_data *realm,
308                  const char *service, const char *protocol,
309                  struct serverlist *serverlist)
310 {
311     struct srv_dns_entry *head = NULL, *entry = NULL;
312     krb5_error_code code = 0;
313     k5_transport transport;
314
315     code = krb5int_make_srv_query_realm(context, realm, service, protocol,
316                                         &head);
317     if (code)
318         return 0;
319
320     if (head == NULL)
321         return 0;
322
323     /* Check for the "." case indicating no support.  */
324     if (head->next == NULL && head->host[0] == '\0') {
325         code = KRB5_ERR_NO_SERVICE;
326         goto cleanup;
327     }
328
329     for (entry = head; entry != NULL; entry = entry->next) {
330         transport = (strcmp(protocol, "_tcp") == 0) ? TCP : UDP;
331         code = add_host_to_list(serverlist, entry->host, entry->port,
332                                 transport, AF_UNSPEC, NULL, -1);
333         if (code)
334             goto cleanup;
335     }
336
337 cleanup:
338     krb5int_free_srv_dns_data(head);
339     return code;
340 }
341 #endif
342
343 #include <krb5/locate_plugin.h>
344
345 #if TARGET_OS_MAC
346 static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR,
347                                  LIBDIR "/krb5/plugins/libkrb5",
348                                  NULL }; /* should be a list */
349 #else
350 static const char *objdirs[] = { LIBDIR "/krb5/plugins/libkrb5", NULL };
351 #endif
352
353 struct module_callback_data {
354     int out_of_mem;
355     struct serverlist *list;
356 };
357
358 static int
359 module_callback(void *cbdata, int socktype, struct sockaddr *sa)
360 {
361     struct module_callback_data *d = cbdata;
362     size_t addrlen;
363     k5_transport transport;
364
365     if (socktype != SOCK_STREAM && socktype != SOCK_DGRAM)
366         return 0;
367     if (sa->sa_family == AF_INET)
368         addrlen = sizeof(struct sockaddr_in);
369     else if (sa->sa_family == AF_INET6)
370         addrlen = sizeof(struct sockaddr_in6);
371     else
372         return 0;
373     transport = (socktype == SOCK_STREAM) ? TCP : UDP;
374     if (add_addr_to_list(d->list, transport, sa->sa_family, addrlen,
375                          sa) != 0) {
376         /* Assumes only error is ENOMEM.  */
377         d->out_of_mem = 1;
378         return 1;
379     }
380     return 0;
381 }
382
383 static krb5_error_code
384 module_locate_server(krb5_context ctx, const krb5_data *realm,
385                      struct serverlist *serverlist,
386                      enum locate_service_type svc, k5_transport transport)
387 {
388     struct krb5plugin_service_locate_result *res = NULL;
389     krb5_error_code code;
390     struct krb5plugin_service_locate_ftable *vtbl = NULL;
391     void **ptrs;
392     char *realmz;               /* NUL-terminated realm */
393     int socktype, i;
394     struct module_callback_data cbdata = { 0, };
395     const char *msg;
396
397     Tprintf("in module_locate_server\n");
398     cbdata.list = serverlist;
399     if (!PLUGIN_DIR_OPEN(&ctx->libkrb5_plugins)) {
400
401         code = krb5int_open_plugin_dirs(objdirs, NULL, &ctx->libkrb5_plugins,
402                                         &ctx->err);
403         if (code)
404             return KRB5_PLUGIN_NO_HANDLE;
405     }
406
407     code = krb5int_get_plugin_dir_data(&ctx->libkrb5_plugins,
408                                        "service_locator", &ptrs, &ctx->err);
409     if (code) {
410         Tprintf("error looking up plugin symbols: %s\n",
411                 (msg = krb5_get_error_message(ctx, code)));
412         krb5_free_error_message(ctx, msg);
413         return KRB5_PLUGIN_NO_HANDLE;
414     }
415
416     if (realm->length >= UINT_MAX) {
417         krb5int_free_plugin_dir_data(ptrs);
418         return ENOMEM;
419     }
420     realmz = k5memdup0(realm->data, realm->length, &code);
421     if (realmz == NULL) {
422         krb5int_free_plugin_dir_data(ptrs);
423         return code;
424     }
425     for (i = 0; ptrs[i]; i++) {
426         void *blob;
427
428         vtbl = ptrs[i];
429         Tprintf("element %d is %p\n", i, ptrs[i]);
430
431         /* For now, don't keep the plugin data alive.  For long-lived
432          * contexts, it may be desirable to change that later. */
433         code = vtbl->init(ctx, &blob);
434         if (code)
435             continue;
436
437         socktype = (transport == TCP) ? SOCK_STREAM : SOCK_DGRAM;
438         code = vtbl->lookup(blob, svc, realmz, socktype, AF_UNSPEC,
439                             module_callback, &cbdata);
440         /* Also ask for TCP addresses if we got UDP addresses and want both. */
441         if (code == 0 && transport == TCP_OR_UDP) {
442             code = vtbl->lookup(blob, svc, realmz, SOCK_STREAM, AF_UNSPEC,
443                                 module_callback, &cbdata);
444             if (code == KRB5_PLUGIN_NO_HANDLE)
445                 code = 0;
446         }
447         vtbl->fini(blob);
448         if (code == KRB5_PLUGIN_NO_HANDLE) {
449             /* Module passes, keep going.  */
450             /* XXX */
451             Tprintf("plugin doesn't handle this realm (KRB5_PLUGIN_NO_HANDLE)"
452                     "\n");
453             continue;
454         }
455         if (code != 0) {
456             /* Module encountered an actual error.  */
457             Tprintf("plugin lookup routine returned error %d: %s\n",
458                     code, error_message(code));
459             free(realmz);
460             krb5int_free_plugin_dir_data(ptrs);
461             return code;
462         }
463         break;
464     }
465     if (ptrs[i] == NULL) {
466         Tprintf("ran off end of plugin list\n");
467         free(realmz);
468         krb5int_free_plugin_dir_data(ptrs);
469         return KRB5_PLUGIN_NO_HANDLE;
470     }
471     Tprintf("stopped with plugin #%d, res=%p\n", i, res);
472
473     /* Got something back, yippee.  */
474     Tprintf("now have %lu addrs in list %p\n",
475             (unsigned long)serverlist->nservers, serverlist);
476     free(realmz);
477     krb5int_free_plugin_dir_data(ptrs);
478     return 0;
479 }
480
481 static krb5_error_code
482 prof_locate_server(krb5_context context, const krb5_data *realm,
483                    struct serverlist *serverlist, enum locate_service_type svc,
484                    k5_transport transport)
485 {
486     const char *profname;
487     int dflport = 0;
488     struct servent *serv;
489
490     switch (svc) {
491     case locate_service_kdc:
492         profname = KRB5_CONF_KDC;
493         /* We used to use /etc/services for these, but enough systems have old,
494          * crufty, wrong settings that this is probably better. */
495     kdc_ports:
496         dflport = KRB5_DEFAULT_PORT;
497         break;
498     case locate_service_master_kdc:
499         profname = KRB5_CONF_MASTER_KDC;
500         goto kdc_ports;
501     case locate_service_kadmin:
502         profname = KRB5_CONF_ADMIN_SERVER;
503         dflport = DEFAULT_KADM5_PORT;
504         break;
505     case locate_service_krb524:
506         profname = KRB5_CONF_KRB524_SERVER;
507         serv = getservbyname("krb524", "udp");
508         dflport = serv ? serv->s_port : 4444;
509         break;
510     case locate_service_kpasswd:
511         profname = KRB5_CONF_KPASSWD_SERVER;
512         dflport = DEFAULT_KPASSWD_PORT;
513         break;
514     default:
515         return EBUSY;           /* XXX */
516     }
517
518     return locate_srv_conf_1(context, realm, profname, serverlist, transport,
519                              dflport);
520 }
521
522 #ifdef KRB5_DNS_LOOKUP
523
524 /*
525  * Parse the initial part of the URI, first confirming the scheme name.  Get
526  * the transport, flags (indicating master status), and host.  The host is
527  * either an address or hostname with an optional port, or an HTTPS URL.
528  * The format is krb5srv:flags:udp|tcp|kkdcp:host
529  *
530  * Return a NULL *host_out if there are any problems parsing the URI.
531  */
532 static void
533 parse_uri_fields(const char *uri, k5_transport *transport_out,
534                  const char **host_out, int *master_out)
535
536 {
537     k5_transport transport;
538     int master = FALSE;
539
540     *transport_out = 0;
541     *host_out = NULL;
542     *master_out = -1;
543
544     /* Confirm the scheme name. */
545     if (strncasecmp(uri, "krb5srv", 7) != 0)
546         return;
547
548     uri += 7;
549     if (*uri != ':')
550         return;
551
552     uri++;
553     if (*uri == '\0')
554         return;
555
556     /* Check the flags field for supported flags. */
557     for (; *uri != ':' && *uri != '\0'; uri++) {
558         if (*uri == 'm' || *uri == 'M')
559             master = TRUE;
560     }
561     if (*uri != ':')
562         return;
563
564     /* Look for the transport type. */
565     uri++;
566     if (strncasecmp(uri, "udp", 3) == 0) {
567         transport = UDP;
568         uri += 3;
569     } else if (strncasecmp(uri, "tcp", 3) == 0) {
570         transport = TCP;
571         uri += 3;
572     } else if (strncasecmp(uri, "kkdcp", 5) == 0) {
573         /* Currently the only MS-KKDCP transport type is HTTPS. */
574         transport = HTTPS;
575         uri += 5;
576     } else {
577         return;
578     }
579
580     if (*uri != ':')
581         return;
582
583     /* The rest of the URI is the host (with optional port) or URI. */
584     *host_out = uri + 1;
585     *transport_out = transport;
586     *master_out = master;
587 }
588
589 /*
590  * Collect a list of servers from DNS URI records, for the requested service
591  * and transport type.  Problematic entries are skipped.
592  */
593 static krb5_error_code
594 locate_uri(krb5_context context, const krb5_data *realm,
595            const char *req_service, struct serverlist *serverlist,
596            k5_transport req_transport, int default_port,
597            krb5_boolean master_only)
598 {
599     krb5_error_code ret;
600     k5_transport transport, host_trans;
601     struct srv_dns_entry *answers, *entry;
602     char *host;
603     const char *host_field, *path;
604     int port, def_port, master;
605
606     ret = k5_make_uri_query(context, realm, req_service, &answers);
607     if (ret || answers == NULL)
608         return ret;
609
610     for (entry = answers; entry != NULL; entry = entry->next) {
611         def_port = default_port;
612         path = NULL;
613
614         parse_uri_fields(entry->host, &transport, &host_field, &master);
615         if (host_field == NULL)
616             continue;
617
618         /* TCP_OR_UDP allows entries of any transport type; otherwise
619          * we're asking for a match. */
620         if (req_transport != TCP_OR_UDP && req_transport != transport)
621             continue;
622
623         /* Process a MS-KKDCP target. */
624         if (transport == HTTPS) {
625             host_trans = 0;
626             def_port = 443;
627             parse_uri_if_https(host_field, &host_trans, &host_field, &path);
628             if (host_trans != HTTPS)
629                 continue;
630         }
631
632         ret = k5_parse_host_string(host_field, def_port, &host, &port);
633         if (ret == ENOMEM)
634             break;
635
636         if (ret || host == NULL) {
637             ret = 0;
638             continue;
639         }
640
641         ret = add_host_to_list(serverlist, host, port, transport, AF_UNSPEC,
642                                path, master);
643         free(host);
644         if (ret)
645             break;
646     }
647
648     krb5int_free_srv_dns_data(answers);
649     return ret;
650 }
651
652 static krb5_error_code
653 dns_locate_server_uri(krb5_context context, const krb5_data *realm,
654                       struct serverlist *serverlist,
655                       enum locate_service_type svc, k5_transport transport)
656 {
657     krb5_error_code ret;
658     char *svcname;
659     int def_port;
660     krb5_boolean find_master = FALSE;
661
662     if (!_krb5_use_dns_kdc(context) || !use_dns_uri(context))
663         return 0;
664
665     switch (svc) {
666     case locate_service_master_kdc:
667         find_master = TRUE;
668         /* Fall through */
669     case locate_service_kdc:
670         svcname = "_kerberos";
671         def_port = 88;
672         break;
673     case locate_service_kadmin:
674         svcname = "_kerberos-adm";
675         def_port = 749;
676         break;
677     case locate_service_kpasswd:
678         svcname = "_kpasswd";
679         def_port = 464;
680         break;
681     default:
682         return 0;
683     }
684
685     ret = locate_uri(context, realm, svcname, serverlist, transport, def_port,
686                      find_master);
687
688     if (serverlist->nservers == 0)
689         TRACE_DNS_URI_NOTFOUND(context);
690
691     return ret;
692 }
693
694 static krb5_error_code
695 dns_locate_server_srv(krb5_context context, const krb5_data *realm,
696                       struct serverlist *serverlist,
697                       enum locate_service_type svc, k5_transport transport)
698 {
699     const char *dnsname;
700     int use_dns = _krb5_use_dns_kdc(context);
701     krb5_error_code code;
702
703     if (!use_dns)
704         return 0;
705
706     switch (svc) {
707     case locate_service_kdc:
708         dnsname = "_kerberos";
709         break;
710     case locate_service_master_kdc:
711         dnsname = "_kerberos-master";
712         break;
713     case locate_service_kadmin:
714         dnsname = "_kerberos-adm";
715         break;
716     case locate_service_krb524:
717         dnsname = "_krb524";
718         break;
719     case locate_service_kpasswd:
720         dnsname = "_kpasswd";
721         break;
722     default:
723         return 0;
724     }
725
726     code = 0;
727     if (transport == UDP || transport == TCP_OR_UDP)
728         code = locate_srv_dns_1(context, realm, dnsname, "_udp", serverlist);
729
730     if ((transport == TCP || transport == TCP_OR_UDP) && code == 0)
731         code = locate_srv_dns_1(context, realm, dnsname, "_tcp", serverlist);
732
733     if (serverlist->nservers == 0)
734         TRACE_DNS_SRV_NOTFOUND(context);
735
736     return code;
737 }
738 #endif /* KRB5_DNS_LOOKUP */
739
740 /*
741  * Try all of the server location methods in sequence.  transport must be
742  * TCP_OR_UDP, TCP, or UDP.  It is applied to hostname entries in the profile
743  * and affects whether we query modules or DNS for UDP or TCP or both, but does
744  * not restrict a method from returning entries of other transports.
745  */
746 static krb5_error_code
747 locate_server(krb5_context context, const krb5_data *realm,
748               struct serverlist *serverlist, enum locate_service_type svc,
749               k5_transport transport)
750 {
751     krb5_error_code ret;
752     struct serverlist list = SERVERLIST_INIT;
753
754     *serverlist = list;
755
756     /* Try modules.  If a module returns 0 but leaves the list empty, return an
757      * empty list. */
758     ret = module_locate_server(context, realm, &list, svc, transport);
759     if (ret != KRB5_PLUGIN_NO_HANDLE)
760         goto done;
761
762     /* Try the profile.  Fall back to DNS if it returns an empty list. */
763     ret = prof_locate_server(context, realm, &list, svc, transport);
764     if (ret)
765         goto done;
766
767 #ifdef KRB5_DNS_LOOKUP
768     if (list.nservers == 0) {
769         ret = dns_locate_server_uri(context, realm, &list, svc, transport);
770         if (ret)
771             goto done;
772     }
773
774     if (list.nservers == 0)
775         ret = dns_locate_server_srv(context, realm, &list, svc, transport);
776 #endif
777
778 done:
779     if (ret) {
780         k5_free_serverlist(&list);
781         return ret;
782     }
783     *serverlist = list;
784     return 0;
785 }
786
787 /*
788  * Wrapper function for the various backends
789  */
790
791 krb5_error_code
792 k5_locate_server(krb5_context context, const krb5_data *realm,
793                  struct serverlist *serverlist, enum locate_service_type svc,
794                  krb5_boolean no_udp)
795 {
796     krb5_error_code ret;
797     k5_transport transport = no_udp ? TCP : TCP_OR_UDP;
798
799     memset(serverlist, 0, sizeof(*serverlist));
800     if (realm == NULL || realm->data == NULL || realm->data[0] == 0) {
801         k5_setmsg(context, KRB5_REALM_CANT_RESOLVE,
802                   "Cannot find KDC for invalid realm name \"\"");
803         return KRB5_REALM_CANT_RESOLVE;
804     }
805
806     ret = locate_server(context, realm, serverlist, svc, transport);
807     if (ret)
808         return ret;
809
810     if (serverlist->nservers == 0) {
811         k5_free_serverlist(serverlist);
812         k5_setmsg(context, KRB5_REALM_UNKNOWN,
813                   _("Cannot find KDC for realm \"%.*s\""),
814                   realm->length, realm->data);
815         return KRB5_REALM_UNKNOWN;
816     }
817     return 0;
818 }
819
820 krb5_error_code
821 k5_locate_kdc(krb5_context context, const krb5_data *realm,
822               struct serverlist *serverlist, krb5_boolean get_masters,
823               krb5_boolean no_udp)
824 {
825     enum locate_service_type stype;
826
827     stype = get_masters ? locate_service_master_kdc : locate_service_kdc;
828     return k5_locate_server(context, realm, serverlist, stype, no_udp);
829 }
830
831 krb5_boolean
832 k5_kdc_is_master(krb5_context context, const krb5_data *realm,
833                  struct server_entry *server)
834 {
835     struct serverlist list;
836     krb5_boolean found;
837
838     if (server->master != -1)
839         return server->master;
840
841     if (locate_server(context, realm, &list, locate_service_master_kdc,
842                       server->transport) != 0)
843         return FALSE;
844     found = server_list_contains(&list, server);
845     k5_free_serverlist(&list);
846     return found;
847 }