Convert DEBUG_REFERRALS to TRACE_* framework
[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 "fake-addrinfo.h"
28 #include "k5-int.h"
29 #include "os-proto.h"
30 #include <stdio.h>
31 #ifdef KRB5_DNS_LOOKUP
32 #ifdef WSHELPER
33 #include <wshelper.h>
34 #else /* WSHELPER */
35 #include <netinet/in.h>
36 #include <arpa/inet.h>
37 #include <arpa/nameser.h>
38 #include <resolv.h>
39 #include <netdb.h>
40 #endif /* WSHELPER */
41 #ifndef T_SRV
42 #define T_SRV 33
43 #endif /* T_SRV */
44
45 /* for old Unixes and friends ... */
46 #ifndef MAXHOSTNAMELEN
47 #define MAXHOSTNAMELEN 64
48 #endif
49
50 #if KRB5_DNS_LOOKUP_KDC
51 #define DEFAULT_LOOKUP_KDC 1
52 #else
53 #define DEFAULT_LOOKUP_KDC 0
54 #endif
55 #if KRB5_DNS_LOOKUP_REALM
56 #define DEFAULT_LOOKUP_REALM 1
57 #else
58 #define DEFAULT_LOOKUP_REALM 0
59 #endif
60
61 static int
62 maybe_use_dns (krb5_context context, const char *name, int defalt)
63 {
64     krb5_error_code code;
65     char * value = NULL;
66     int use_dns = 0;
67
68     code = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
69                               name, 0, 0, &value);
70     if (value == 0 && code == 0)
71         code = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
72                                   KRB5_CONF_DNS_FALLBACK, 0, 0, &value);
73     if (code)
74         return defalt;
75
76     if (value == 0)
77         return defalt;
78
79     use_dns = _krb5_conf_boolean(value);
80     profile_release_string(value);
81     return use_dns;
82 }
83
84 int
85 _krb5_use_dns_kdc(krb5_context context)
86 {
87     return maybe_use_dns (context, KRB5_CONF_DNS_LOOKUP_KDC, DEFAULT_LOOKUP_KDC);
88 }
89
90 int
91 _krb5_use_dns_realm(krb5_context context)
92 {
93     return maybe_use_dns (context, KRB5_CONF_DNS_LOOKUP_REALM, DEFAULT_LOOKUP_REALM);
94 }
95
96 #endif /* KRB5_DNS_LOOKUP */
97
98 /* Free up everything pointed to by the serverlist structure, but don't
99    free the structure itself.  */
100 void
101 k5_free_serverlist (struct serverlist *list)
102 {
103     size_t i;
104
105     for (i = 0; i < list->nservers; i++)
106         free(list->servers[i].hostname);
107     free(list->servers);
108     list->servers = NULL;
109     list->nservers = 0;
110 }
111
112 #include <stdarg.h>
113 static inline void
114 Tprintf(const char *fmt, ...)
115 {
116 #ifdef TEST
117     va_list ap;
118     va_start(ap, fmt);
119     vfprintf(stderr, fmt, ap);
120     va_end(ap);
121 #endif
122 }
123
124 #if 0
125 extern void krb5int_debug_fprint(const char *, ...);
126 #define dprint krb5int_debug_fprint
127 #else
128 static inline void dprint(const char *fmt, ...) { }
129 #endif
130
131 /* Make room for a new server entry in list and return a pointer to the new
132  * entry.  (Do not increment list->nservers.) */
133 static struct server_entry *
134 new_server_entry(struct serverlist *list)
135 {
136     struct server_entry *newservers, *entry;
137     size_t newspace = (list->nservers + 1) * sizeof(struct server_entry);
138
139     newservers = realloc(list->servers, newspace);
140     if (newservers == NULL)
141         return NULL;
142     list->servers = newservers;
143     entry = &newservers[list->nservers];
144     memset(entry, 0, sizeof(*entry));
145     return entry;
146 }
147
148 /* Add an address entry to list. */
149 static int
150 add_addr_to_list(struct serverlist *list, int socktype, 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->socktype = socktype;
159     entry->family = family;
160     entry->hostname = NULL;
161     entry->addrlen = addrlen;
162     memcpy(&entry->addr, addr, addrlen);
163     list->nservers++;
164     return 0;
165 }
166
167 /* Add a hostname entry to list. */
168 static int
169 add_host_to_list(struct serverlist *list, const char *hostname, int port,
170                  int socktype, int family)
171 {
172     struct server_entry *entry;
173
174     entry = new_server_entry(list);
175     if (entry == NULL)
176         return ENOMEM;
177     entry->socktype = socktype;
178     entry->family = family;
179     entry->hostname = strdup(hostname);
180     if (entry->hostname == NULL)
181         return ENOMEM;
182     entry->port = port;
183     list->nservers++;
184     return 0;
185 }
186
187 static krb5_error_code
188 locate_srv_conf_1(krb5_context context, const krb5_data *realm,
189                   const char * name, struct serverlist *serverlist,
190                   int socktype, int udpport, int sec_udpport)
191 {
192     const char  *realm_srv_names[4];
193     char **hostlist, *host, *port, *cp;
194     krb5_error_code code;
195     int i, count;
196
197     Tprintf ("looking in krb5.conf for realm %s entry %s; ports %d,%d\n",
198              realm->data, name, ntohs (udpport), ntohs (sec_udpport));
199
200     if ((host = malloc(realm->length + 1)) == NULL)
201         return ENOMEM;
202
203     strncpy(host, realm->data, realm->length);
204     host[realm->length] = '\0';
205     hostlist = 0;
206
207     realm_srv_names[0] = KRB5_CONF_REALMS;
208     realm_srv_names[1] = host;
209     realm_srv_names[2] = name;
210     realm_srv_names[3] = 0;
211
212     code = profile_get_values(context->profile, realm_srv_names, &hostlist);
213     free(host);
214
215     if (code) {
216         Tprintf ("config file lookup failed: %s\n",
217                  error_message(code));
218         if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
219             code = KRB5_REALM_UNKNOWN;
220         return code;
221     }
222
223     count = 0;
224     while (hostlist && hostlist[count])
225         count++;
226     Tprintf ("found %d entries under 'kdc'\n", count);
227
228     if (count == 0) {
229         profile_free_list(hostlist);
230         serverlist->nservers = 0;
231         return 0;
232     }
233
234     for (i=0; hostlist[i]; i++) {
235         int p1, p2;
236
237         host = hostlist[i];
238         Tprintf ("entry %d is '%s'\n", i, host);
239         /* Find port number, and strip off any excess characters. */
240         if (*host == '[' && (cp = strchr(host, ']')))
241             cp = cp + 1;
242         else
243             cp = host + strcspn(host, " \t:");
244         port = (*cp == ':') ? cp + 1 : NULL;
245         *cp = '\0';
246
247         if (port) {
248             unsigned long l;
249 #ifdef HAVE_STROUL
250             char *endptr;
251             l = strtoul (port, &endptr, 10);
252             if (endptr == NULL || *endptr != 0)
253                 return EINVAL;
254 #else
255             l = atoi (port);
256 #endif
257             /* L is unsigned, don't need to check <0.  */
258             if (l > 65535)
259                 return EINVAL;
260             p1 = htons (l);
261             p2 = 0;
262         } else {
263             p1 = udpport;
264             p2 = sec_udpport;
265         }
266
267         /* If the hostname was in brackets, strip those off now. */
268         if (*host == '[' && (cp = strchr(host, ']'))) {
269             host++;
270             *cp = '\0';
271         }
272
273         code = add_host_to_list(serverlist, host, p1, socktype, AF_UNSPEC);
274         /* Second port is for IPv4 UDP only, and should possibly go away as
275          * it was originally a krb4 compatibility measure. */
276         if (code == 0 && p2 != 0 &&
277             (socktype == 0 || socktype == SOCK_DGRAM))
278             code = add_host_to_list(serverlist, host, p2, SOCK_DGRAM, AF_INET);
279         if (code)
280             goto cleanup;
281     }
282
283 cleanup:
284     profile_free_list(hostlist);
285     return code;
286 }
287
288 #ifdef TEST
289 static krb5_error_code
290 krb5_locate_srv_conf(krb5_context context, const krb5_data *realm,
291                      const char *name, struct serverlist *al, int udpport,
292                      int sec_udpport)
293 {
294     krb5_error_code ret;
295
296     ret = locate_srv_conf_1(context, realm, name, al, 0, udpport, sec_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(const krb5_data *realm, const char *service,
308                  const char *protocol, struct serverlist *serverlist)
309 {
310     struct srv_dns_entry *head = NULL, *entry = NULL;
311     krb5_error_code code = 0;
312     int socktype;
313
314     code = krb5int_make_srv_query_realm(realm, service, protocol, &head);
315     if (code)
316         return 0;
317
318     if (head == NULL)
319         return 0;
320
321     /* Check for the "." case indicating no support.  */
322     if (head->next == NULL && head->host[0] == '\0') {
323         code = KRB5_ERR_NO_SERVICE;
324         goto cleanup;
325     }
326
327     for (entry = head; entry != NULL; entry = entry->next) {
328         socktype = (strcmp(protocol, "_tcp") == 0) ? SOCK_STREAM : SOCK_DGRAM;
329         code = add_host_to_list(serverlist, entry->host, htons(entry->port),
330                                 socktype, AF_UNSPEC);
331         if (code)
332             goto cleanup;
333     }
334
335 cleanup:
336     krb5int_free_srv_dns_data(head);
337     return code;
338 }
339 #endif
340
341 #include <krb5/locate_plugin.h>
342
343 #if TARGET_OS_MAC
344 static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR, LIBDIR "/krb5/plugins/libkrb5", NULL }; /* should be a list */
345 #else
346 static const char *objdirs[] = { LIBDIR "/krb5/plugins/libkrb5", NULL };
347 #endif
348
349 struct module_callback_data {
350     int out_of_mem;
351     struct serverlist *list;
352 };
353
354 static int
355 module_callback(void *cbdata, int socktype, struct sockaddr *sa)
356 {
357     struct module_callback_data *d = cbdata;
358     size_t addrlen;
359
360     if (socktype != SOCK_STREAM && socktype != SOCK_DGRAM)
361         return 0;
362     if (sa->sa_family == AF_INET)
363         addrlen = sizeof(struct sockaddr_in);
364     else if (sa->sa_family == AF_INET6)
365         addrlen = sizeof(struct sockaddr_in6);
366     else
367         return 0;
368     if (add_addr_to_list(d->list, socktype, sa->sa_family, addrlen,
369                          sa) != 0) {
370         /* Assumes only error is ENOMEM.  */
371         d->out_of_mem = 1;
372         return 1;
373     }
374     return 0;
375 }
376
377 static krb5_error_code
378 module_locate_server(krb5_context ctx, const krb5_data *realm,
379                      struct serverlist *serverlist,
380                      enum locate_service_type svc, int socktype)
381 {
382     struct krb5plugin_service_locate_result *res = NULL;
383     krb5_error_code code;
384     struct krb5plugin_service_locate_ftable *vtbl = NULL;
385     void **ptrs;
386     char *realmz;               /* NUL-terminated realm */
387     int i;
388     struct module_callback_data cbdata = { 0, };
389     const char *msg;
390
391     Tprintf("in module_locate_server\n");
392     cbdata.list = serverlist;
393     if (!PLUGIN_DIR_OPEN (&ctx->libkrb5_plugins)) {
394
395         code = krb5int_open_plugin_dirs (objdirs, NULL, &ctx->libkrb5_plugins,
396                                          &ctx->err);
397         if (code)
398             return KRB5_PLUGIN_NO_HANDLE;
399     }
400
401     code = krb5int_get_plugin_dir_data (&ctx->libkrb5_plugins,
402                                         "service_locator", &ptrs, &ctx->err);
403     if (code) {
404         Tprintf("error looking up plugin symbols: %s\n",
405                 (msg = krb5_get_error_message(ctx, code)));
406         krb5_free_error_message(ctx, msg);
407         return KRB5_PLUGIN_NO_HANDLE;
408     }
409
410     if (realm->length >= UINT_MAX) {
411         krb5int_free_plugin_dir_data(ptrs);
412         return ENOMEM;
413     }
414     realmz = malloc(realm->length + 1);
415     if (realmz == NULL) {
416         krb5int_free_plugin_dir_data(ptrs);
417         return ENOMEM;
418     }
419     memcpy(realmz, realm->data, realm->length);
420     realmz[realm->length] = '\0';
421     for (i = 0; ptrs[i]; i++) {
422         void *blob;
423
424         vtbl = ptrs[i];
425         Tprintf("element %d is %p\n", i, ptrs[i]);
426
427         /* For now, don't keep the plugin data alive.  For long-lived
428            contexts, it may be desirable to change that later.  */
429         code = vtbl->init(ctx, &blob);
430         if (code)
431             continue;
432
433         code = vtbl->lookup(blob, svc, realmz,
434                             (socktype != 0) ? socktype : SOCK_DGRAM, AF_UNSPEC,
435                             module_callback, &cbdata);
436         /* Also ask for TCP addresses if we got UDP addresses and want both. */
437         if (code == 0 && socktype == 0) {
438             code = vtbl->lookup(blob, svc, realmz, SOCK_STREAM, AF_UNSPEC,
439                                 module_callback, &cbdata);
440             if (code == KRB5_PLUGIN_NO_HANDLE)
441                 code = 0;
442         }
443         vtbl->fini(blob);
444         if (code == KRB5_PLUGIN_NO_HANDLE) {
445             /* Module passes, keep going.  */
446             /* XXX */
447             Tprintf("plugin doesn't handle this realm (KRB5_PLUGIN_NO_HANDLE)\n");
448             continue;
449         }
450         if (code != 0) {
451             /* Module encountered an actual error.  */
452             Tprintf("plugin lookup routine returned error %d: %s\n",
453                     code, error_message(code));
454             free(realmz);
455             krb5int_free_plugin_dir_data (ptrs);
456             return code;
457         }
458         break;
459     }
460     if (ptrs[i] == NULL) {
461         Tprintf("ran off end of plugin list\n");
462         free(realmz);
463         krb5int_free_plugin_dir_data (ptrs);
464         return KRB5_PLUGIN_NO_HANDLE;
465     }
466     Tprintf("stopped with plugin #%d, res=%p\n", i, res);
467
468     /* Got something back, yippee.  */
469     Tprintf("now have %lu addrs in list %p\n",
470             (unsigned long) serverlist->nservers, serverlist);
471     free(realmz);
472     krb5int_free_plugin_dir_data (ptrs);
473     return 0;
474 }
475
476 static krb5_error_code
477 prof_locate_server(krb5_context context, const krb5_data *realm,
478                    struct serverlist *serverlist, enum locate_service_type svc,
479                    int socktype)
480 {
481     const char *profname;
482     int dflport1, dflport2 = 0;
483     struct servent *serv;
484
485     switch (svc) {
486     case locate_service_kdc:
487         profname = KRB5_CONF_KDC;
488         /* We used to use /etc/services for these, but enough systems
489            have old, crufty, wrong settings that this is probably
490            better.  */
491     kdc_ports:
492         dflport1 = htons(KRB5_DEFAULT_PORT);
493         dflport2 = htons(KRB5_DEFAULT_SEC_PORT);
494         break;
495     case locate_service_master_kdc:
496         profname = KRB5_CONF_MASTER_KDC;
497         goto kdc_ports;
498     case locate_service_kadmin:
499         profname = KRB5_CONF_ADMIN_SERVER;
500         dflport1 = htons(DEFAULT_KADM5_PORT);
501         break;
502     case locate_service_krb524:
503         profname = KRB5_CONF_KRB524_SERVER;
504         serv = getservbyname(KRB524_SERVICE, "udp");
505         dflport1 = serv ? serv->s_port : htons (KRB524_PORT);
506         break;
507     case locate_service_kpasswd:
508         profname = KRB5_CONF_KPASSWD_SERVER;
509         dflport1 = htons(DEFAULT_KPASSWD_PORT);
510         break;
511     default:
512         return EBUSY;           /* XXX */
513     }
514
515     return locate_srv_conf_1(context, realm, profname, serverlist, socktype,
516                              dflport1, dflport2);
517 }
518
519 #ifdef KRB5_DNS_LOOKUP
520 static krb5_error_code
521 dns_locate_server(krb5_context context, const krb5_data *realm,
522                   struct serverlist *serverlist, enum locate_service_type svc,
523                   int socktype)
524 {
525     const char *dnsname;
526     int use_dns = _krb5_use_dns_kdc(context);
527     krb5_error_code code;
528
529     if (!use_dns)
530         return KRB5_PLUGIN_NO_HANDLE;
531
532     switch (svc) {
533     case locate_service_kdc:
534         dnsname = "_kerberos";
535         break;
536     case locate_service_master_kdc:
537         dnsname = "_kerberos-master";
538         break;
539     case locate_service_kadmin:
540         dnsname = "_kerberos-adm";
541         break;
542     case locate_service_krb524:
543         dnsname = "_krb524";
544         break;
545     case locate_service_kpasswd:
546         dnsname = "_kpasswd";
547         break;
548     default:
549         return KRB5_PLUGIN_NO_HANDLE;
550     }
551
552     code = 0;
553     if (socktype == SOCK_DGRAM || socktype == 0) {
554         code = locate_srv_dns_1(realm, dnsname, "_udp", serverlist);
555         if (code)
556             Tprintf("dns udp lookup returned error %d\n", code);
557     }
558     if ((socktype == SOCK_STREAM || socktype == 0) && code == 0) {
559         code = locate_srv_dns_1(realm, dnsname, "_tcp", serverlist);
560         if (code)
561             Tprintf("dns tcp lookup returned error %d\n", code);
562     }
563     return code;
564 }
565 #endif /* KRB5_DNS_LOOKUP */
566
567 /*
568  * Wrapper function for the various backends
569  */
570
571 krb5_error_code
572 k5_locate_server(krb5_context context, const krb5_data *realm,
573                  struct serverlist *serverlist, enum locate_service_type svc,
574                  int socktype)
575 {
576     krb5_error_code code;
577     struct serverlist al = SERVERLIST_INIT;
578
579     *serverlist = al;
580
581     if (realm == NULL || realm->data == NULL || realm->data[0] == 0) {
582         krb5_set_error_message(context, KRB5_REALM_CANT_RESOLVE,
583                                "Cannot find KDC for invalid realm name \"\"");
584         return KRB5_REALM_CANT_RESOLVE;
585     }
586
587     code = module_locate_server(context, realm, &al, svc, socktype);
588     Tprintf("module_locate_server returns %d\n", code);
589     if (code == KRB5_PLUGIN_NO_HANDLE) {
590         /*
591          * We always try the local file before DNS.  Note that there
592          * is no way to indicate "service not available" via the
593          * config file.
594          */
595
596         code = prof_locate_server(context, realm, &al, svc, socktype);
597
598 #ifdef KRB5_DNS_LOOKUP
599         if (code) {             /* Try DNS for all profile errors?  */
600             krb5_error_code code2;
601             code2 = dns_locate_server(context, realm, &al, svc, socktype);
602             if (code2 != KRB5_PLUGIN_NO_HANDLE)
603                 code = code2;
604         }
605 #endif /* KRB5_DNS_LOOKUP */
606
607         /* We could put more heuristics here, like looking up a hostname
608            of "kerberos."+REALM, etc.  */
609     }
610     if (code == 0)
611         Tprintf ("krb5int_locate_server found %d addresses\n",
612                  al.nservers);
613     else
614         Tprintf ("krb5int_locate_server returning error code %d/%s\n",
615                  code, error_message(code));
616     if (code != 0) {
617         k5_free_serverlist(&al);
618         return code;
619     }
620     if (al.nservers == 0) {       /* No good servers */
621         k5_free_serverlist(&al);
622         krb5_set_error_message(context, KRB5_REALM_CANT_RESOLVE,
623                                _("Cannot resolve servers for KDC in realm "
624                                  "\"%.*s\""), realm->length, realm->data);
625         return KRB5_REALM_CANT_RESOLVE;
626     }
627     *serverlist = al;
628     return 0;
629 }
630
631 krb5_error_code
632 k5_locate_kdc(krb5_context context, const krb5_data *realm,
633               struct serverlist *serverlist, int get_masters, int socktype)
634 {
635     enum locate_service_type stype;
636
637     stype = get_masters ? locate_service_master_kdc : locate_service_kdc;
638     return k5_locate_server(context, realm, serverlist, stype, socktype);
639 }