* dnssrv.c:
[krb5.git] / src / lib / krb5 / os / locate_kdc.c
1 /*
2  * lib/krb5/os/locate_kdc.c
3  *
4  * Copyright 1990,2000,2001,2002 by the Massachusetts Institute of Technology.
5  * 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  * get socket addresses for KDC.
28  */
29
30 #define NEED_SOCKETS
31 #include "fake-addrinfo.h"
32 #include "k5-int.h"
33 #include "os-proto.h"
34 #include <stdio.h>
35 #ifdef KRB5_DNS_LOOKUP
36 #ifdef WSHELPER
37 #include <wshelper.h>
38 #else /* WSHELPER */
39 #include <netinet/in.h>
40 #include <arpa/inet.h>
41 #include <arpa/nameser.h>
42 #include <resolv.h>
43 #include <netdb.h>
44 #endif /* WSHELPER */
45 #ifndef T_SRV
46 #define T_SRV 33
47 #endif /* T_SRV */
48
49 /* for old Unixes and friends ... */
50 #ifndef MAXHOSTNAMELEN
51 #define MAXHOSTNAMELEN 64
52 #endif
53
54 #define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1)
55
56 #if KRB5_DNS_LOOKUP_KDC
57 #define DEFAULT_LOOKUP_KDC 1
58 #else
59 #define DEFAULT_LOOKUP_KDC 0
60 #endif
61 #if KRB5_DNS_LOOKUP_REALM
62 #define DEFAULT_LOOKUP_REALM 1
63 #else
64 #define DEFAULT_LOOKUP_REALM 0
65 #endif
66
67 static int
68 maybe_use_dns (krb5_context context, const char *name, int defalt)
69 {
70     krb5_error_code code;
71     char * value = NULL;
72     int use_dns = 0;
73
74     code = profile_get_string(context->profile, "libdefaults",
75                               name, 0, 0, &value);
76     if (value == 0 && code == 0)
77         code = profile_get_string(context->profile, "libdefaults",
78                                   "dns_fallback", 0, 0, &value);
79     if (code)
80         return defalt;
81
82     if (value == 0)
83         return defalt;
84
85     use_dns = _krb5_conf_boolean(value);
86     profile_release_string(value);
87     return use_dns;
88 }
89
90 int
91 _krb5_use_dns_kdc(krb5_context context)
92 {
93     return maybe_use_dns (context, "dns_lookup_kdc", DEFAULT_LOOKUP_KDC);
94 }
95
96 int
97 _krb5_use_dns_realm(krb5_context context)
98 {
99     return maybe_use_dns (context, "dns_lookup_realm", DEFAULT_LOOKUP_REALM);
100 }
101
102 #endif /* KRB5_DNS_LOOKUP */
103
104 static int get_port (const char *service, int stream, int defalt)
105 {
106 #if 0 /* Only used for "kerberos" and "kerberos-sec", and we want the
107          right port numbers even on the OSes that botch the entries in
108          /etc/services.  So don't bother with the lookup, except maybe
109          to produce a warning.  */
110     struct addrinfo hints = { 0 };
111     struct addrinfo *ai;
112     int err;
113
114     hints.ai_family = PF_INET;
115     hints.ai_socktype = stream ? SOCK_STREAM : SOCK_DGRAM;
116     err = getaddrinfo (NULL, service, &hints, &ai);
117     if (err == 0 && ai != 0) {
118         if (ai->ai_addr->sa_family == AF_INET) {
119             int port = ((struct sockaddr_in *)ai->ai_addr)->sin_port;
120             freeaddrinfo (ai);
121             return port;
122         }
123         freeaddrinfo (ai);
124     }
125 #endif
126     /* Any error - don't complain, just use default.  */
127     return htons (defalt);
128 }
129
130 int
131 krb5int_grow_addrlist (struct addrlist *lp, int nmore)
132 {
133     int i;
134     int newspace = lp->space + nmore;
135     size_t newsize = newspace * sizeof (struct addrlist);
136     struct addrinfo **newaddrs;
137
138     /* NULL check a concession to SunOS4 compatibility for now; not
139        required for pure ANSI support.  */
140     if (lp->addrs)
141         newaddrs = realloc (lp->addrs, newsize);
142     else
143         newaddrs = malloc (newsize);
144
145     if (newaddrs == NULL)
146         return errno;
147     for (i = lp->space; i < newspace; i++)
148         newaddrs[i] = NULL;
149     lp->addrs = newaddrs;
150     lp->space = newspace;
151     return 0;
152 }
153 #define grow_list krb5int_grow_addrlist
154
155 /* Free up everything pointed to by the addrlist structure, but don't
156    free the structure itself.  */
157 void
158 krb5int_free_addrlist (struct addrlist *lp)
159 {
160     int i;
161     for (i = 0; i < lp->naddrs; i++)
162         freeaddrinfo (lp->addrs[i]);
163     free (lp->addrs);
164     lp->addrs = NULL;
165     lp->naddrs = lp->space = 0;
166 }
167 #define free_list krb5int_free_addrlist
168
169 static int translate_ai_error (int err)
170 {
171     switch (err) {
172     case 0:
173         return 0;
174     case EAI_BADFLAGS:
175     case EAI_FAMILY:
176     case EAI_SOCKTYPE:
177     case EAI_SERVICE:
178         /* All of these indicate bad inputs to getaddrinfo.  */
179         return EINVAL;
180     case EAI_AGAIN:
181         /* Translate to standard errno code.  */
182         return EAGAIN;
183     case EAI_MEMORY:
184         /* Translate to standard errno code.  */
185         return ENOMEM;
186 #ifdef EAI_ADDRFAMILY
187     case EAI_ADDRFAMILY:
188 #endif
189 #if EAI_NODATA != EAI_NONAME
190     case EAI_NODATA:
191 #endif
192     case EAI_NONAME:
193         /* Name not known or no address data, but no error.  Do
194            nothing more.  */
195         return 0;
196 #ifdef EAI_SYSTEM
197     case EAI_SYSTEM:
198         /* System error, obviously.  */
199         return errno;
200 #endif
201     default:
202         /* An error code we haven't handled?  */
203         return EINVAL;
204     }
205 }
206
207 static int add_addrinfo_to_list (struct addrlist *lp, struct addrinfo *a)
208 {
209     int err;
210
211 #ifdef TEST
212     switch (a->ai_socktype) {
213     case SOCK_DGRAM:
214         fprintf(stderr, "\tdgram\n");
215         break;
216     case SOCK_STREAM:
217         fprintf(stderr, "\tstream\n");
218         break;
219     case SOCK_RAW:
220         fprintf(stderr, "\traw\n");
221         break;
222     case 0:
223         break;
224     default:
225         fprintf(stderr, "\tsocket type %d\n", a->ai_socktype);
226         break;
227     }
228 #endif
229
230     if (lp->naddrs == lp->space) {
231         err = grow_list (lp, 1);
232         if (err) {
233 #ifdef TEST
234             fprintf (stderr, "grow_list failed %d\n", err);
235 #endif
236             return err;
237         }
238     }
239     lp->addrs[lp->naddrs++] = a;
240     a->ai_next = 0;
241 #ifdef TEST
242     fprintf (stderr, "count is now %d\n", lp->naddrs);
243 #endif
244     return 0;
245 }
246
247 #define add_host_to_list krb5int_add_host_to_list
248
249 int
250 krb5int_add_host_to_list (struct addrlist *lp, const char *hostname,
251                           int port, int secport,
252                           int socktype, int family)
253 {
254     struct addrinfo *addrs, *a, *anext, hint;
255     int err;
256     char portbuf[10], secportbuf[10];
257
258 #ifdef TEST
259     fprintf (stderr, "adding hostname %s, ports %d,%d\n", hostname,
260              ntohs (port), ntohs (secport));
261 #endif
262
263     memset(&hint, 0, sizeof(hint));
264     hint.ai_family = family;
265     hint.ai_socktype = socktype;
266     sprintf(portbuf, "%d", ntohs(port));
267     sprintf(secportbuf, "%d", ntohs(secport));
268     err = getaddrinfo (hostname, portbuf, &hint, &addrs);
269     if (err)
270         return translate_ai_error (err);
271     anext = 0;
272     for (a = addrs; a != 0 && err == 0; a = anext) {
273         anext = a->ai_next;
274         err = add_addrinfo_to_list (lp, a);
275     }
276     if (err || secport == 0)
277         goto egress;
278     if (socktype == 0)
279         socktype = SOCK_DGRAM;
280     else if (socktype != SOCK_DGRAM)
281         goto egress;
282     hint.ai_family = AF_INET;
283     err = getaddrinfo (hostname, secportbuf, &hint, &addrs);
284     if (err) {
285         err = translate_ai_error (err);
286         goto egress;
287     }
288     for (a = addrs; a != 0 && err == 0; a = anext) {
289         anext = a->ai_next;
290         err = add_addrinfo_to_list (lp, a);
291     }
292 egress:
293     if (anext)
294         freeaddrinfo (anext);
295     return err;
296 }
297
298 /*
299  * returns count of number of addresses found
300  * if master is non-NULL, it is filled in with the index of
301  * the master kdc
302  */
303
304 static krb5_error_code
305 krb5_locate_srv_conf_1(krb5_context context, const krb5_data *realm,
306                        const char * name, struct addrlist *addrlist,
307                        int get_masters, int socktype,
308                        int udpport, int sec_udpport, int family)
309 {
310     const char  *realm_srv_names[4];
311     char **masterlist, **hostlist, *host, *port, *cp;
312     krb5_error_code code;
313     int i, j, count, ismaster;
314
315 #ifdef TEST
316     fprintf (stderr,
317              "looking in krb5.conf for realm %s entry %s; ports %d,%d\n",
318              realm->data, name, ntohs (udpport), ntohs (sec_udpport));
319 #endif
320
321     if ((host = malloc(realm->length + 1)) == NULL) 
322         return ENOMEM;
323
324     strncpy(host, realm->data, realm->length);
325     host[realm->length] = '\0';
326     hostlist = 0;
327
328     masterlist = NULL;
329
330     realm_srv_names[0] = "realms";
331     realm_srv_names[1] = host;
332     realm_srv_names[2] = name;
333     realm_srv_names[3] = 0;
334
335     code = profile_get_values(context->profile, realm_srv_names, &hostlist);
336
337     if (code) {
338 #ifdef TEST
339         fprintf (stderr, "config file lookup failed: %s\n",
340                  error_message(code));
341 #endif
342         if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
343             code = KRB5_REALM_UNKNOWN;
344         krb5_xfree(host);
345         return code;
346      }
347
348     count = 0;
349     while (hostlist && hostlist[count])
350             count++;
351 #ifdef TEST
352     fprintf (stderr, "found %d entries under 'kdc'\n", count);
353 #endif
354     
355     if (count == 0) {
356         profile_free_list(hostlist);
357         krb5_xfree(host);
358         addrlist->naddrs = 0;
359         return 0;
360     }
361     
362     if (get_masters) {
363         realm_srv_names[0] = "realms";
364         realm_srv_names[1] = host;
365         realm_srv_names[2] = "admin_server";
366         realm_srv_names[3] = 0;
367
368         code = profile_get_values(context->profile, realm_srv_names,
369                                   &masterlist);
370
371         krb5_xfree(host);
372
373         if (code == 0) {
374             for (i=0; masterlist[i]; i++) {
375                 host = masterlist[i];
376
377                 /*
378                  * Strip off excess whitespace
379                  */
380                 cp = strchr(host, ' ');
381                 if (cp)
382                     *cp = 0;
383                 cp = strchr(host, '\t');
384                 if (cp)
385                     *cp = 0;
386                 cp = strchr(host, ':');
387                 if (cp)
388                     *cp = 0;
389             }
390         }
391     } else {
392         krb5_xfree(host);
393     }
394
395     /* at this point, if master is non-NULL, then either the master kdc
396        is required, and there is one, or the master kdc is not required,
397        and there may or may not be one. */
398
399 #ifdef HAVE_NETINET_IN_H
400     if (sec_udpport)
401             count = count * 2;
402 #endif
403
404     for (i=0; hostlist[i]; i++) {
405         int p1, p2;
406
407         host = hostlist[i];
408 #ifdef TEST
409         fprintf (stderr, "entry %d is '%s'\n", i, host);
410 #endif
411         /*
412          * Strip off excess whitespace
413          */
414         cp = strchr(host, ' ');
415         if (cp)
416             *cp = 0;
417         cp = strchr(host, '\t');
418         if (cp)
419             *cp = 0;
420         port = strchr(host, ':');
421         if (port) {
422             *port = 0;
423             port++;
424         }
425
426         ismaster = 0;
427         if (masterlist) {
428             for (j=0; masterlist[j]; j++) {
429                 if (strcasecmp(hostlist[i], masterlist[j]) == 0) {
430                     ismaster = 1;
431                 }
432             }
433         }
434
435         if (get_masters && !ismaster)
436             continue;
437
438         if (port) {
439             unsigned long l;
440 #ifdef HAVE_STROUL
441             char *endptr;
442             l = strtoul (port, &endptr, 10);
443             if (endptr == NULL || *endptr != 0)
444                 return EINVAL;
445 #else
446             l = atoi (port);
447 #endif
448             /* L is unsigned, don't need to check <0.  */
449             if (l > 65535)
450                 return EINVAL;
451             p1 = htons (l);
452             p2 = 0;
453         } else {
454             p1 = udpport;
455             p2 = sec_udpport;
456         }
457
458         if (socktype != 0)
459             code = add_host_to_list (addrlist, hostlist[i], p1, p2,
460                                      socktype, family);
461         else {
462             code = add_host_to_list (addrlist, hostlist[i], p1, p2,
463                                      SOCK_DGRAM, family);
464             if (code == 0)
465                 code = add_host_to_list (addrlist, hostlist[i], p1, p2,
466                                          SOCK_STREAM, family);
467         }
468         if (code) {
469 #ifdef TEST
470             fprintf (stderr, "error %d returned from add_host_to_list\n", code);
471 #endif
472             if (hostlist)
473                 profile_free_list (hostlist);
474             if (masterlist)
475                 profile_free_list (masterlist);
476             return code;
477         }
478     }
479
480     if (hostlist)
481         profile_free_list(hostlist);
482     if (masterlist)
483         profile_free_list(masterlist);
484
485     return 0;
486 }
487
488 #ifdef TEST
489 static krb5_error_code
490 krb5_locate_srv_conf(krb5_context context, const krb5_data *realm,
491                      const char *name, struct addrlist *al, int get_masters,
492                      int udpport, int sec_udpport)
493 {
494     krb5_error_code ret;
495
496     ret = krb5_locate_srv_conf_1 (context, realm, name, al,
497                                   get_masters, 0, udpport, sec_udpport, 0);
498     if (ret)
499         return ret;
500     if (al->naddrs == 0)        /* Couldn't resolve any KDC names */
501         return KRB5_REALM_CANT_RESOLVE;
502     return 0;
503 }
504 #endif
505
506 static krb5_error_code
507 krb5_locate_srv_dns_1 (const krb5_data *realm,
508                        const char *service,
509                        const char *protocol,
510                        struct addrlist *addrlist,
511                        int family)
512 {
513     struct srv_dns_entry *head = NULL;
514     struct srv_dns_entry *entry = NULL, *next;
515     krb5_error_code code = 0;
516
517     code = krb5int_make_srv_query_realm(realm, service, protocol, &head);
518     if (code)
519         return 0;
520
521     /*
522      * Okay!  Now we've got a linked list of entries sorted by
523      * priority.  Start looking up A records and returning
524      * addresses.
525      */
526
527     if (head == NULL)
528         return 0;
529
530     /* Check for the "." case indicating no support.  */
531     if (head->next == 0 && head->host[0] == 0) {
532         free(head->host);
533         free(head);
534         return KRB5_ERR_NO_SERVICE;
535     }
536
537 #ifdef TEST
538     fprintf (stderr, "walking answer list:\n");
539 #endif
540     for (entry = head; entry != NULL; entry = next) {
541 #ifdef TEST
542         fprintf (stderr, "\tport=%d host=%s\n", entry->port, entry->host);
543 #endif
544         next = entry->next;
545         code = add_host_to_list (addrlist, entry->host, htons (entry->port), 0,
546                                  (strcmp("_tcp", protocol)
547                                   ? SOCK_DGRAM
548                                   : SOCK_STREAM), family);
549         if (code)
550             break;
551         if (entry == head) {
552             free(entry->host);
553             free(entry);
554             head = next;
555             entry = 0;
556         }
557     }
558 #ifdef TEST
559     fprintf (stderr, "[end]\n");
560 #endif
561
562     krb5int_free_srv_dns_data(head);
563     return code;
564 }
565
566 /*
567  * Wrapper function for the two backends
568  */
569
570 krb5_error_code
571 krb5int_locate_server (krb5_context context, const krb5_data *realm,
572                        struct addrlist *addrlist,
573                        int get_masters,
574                        const char *profname, const char *dnsname,
575                        int socktype,
576                        /* network order port numbers! */
577                        int dflport1, int dflport2,
578                        int family)
579 {
580     krb5_error_code code;
581     struct addrlist al = ADDRLIST_INIT;
582
583     *addrlist = al;
584
585     /*
586      * We always try the local file first
587      */
588
589     code = krb5_locate_srv_conf_1(context, realm, profname, &al, get_masters,
590                                   socktype, dflport1, dflport2, family);
591
592 #ifdef KRB5_DNS_LOOKUP
593     if (code && dnsname != 0) {
594         int use_dns = _krb5_use_dns_kdc(context);
595         if (use_dns) {
596             code = 0;
597             if (socktype == SOCK_DGRAM || socktype == 0) {
598                 code = krb5_locate_srv_dns_1(realm, dnsname, "_udp",
599                                              &al, family);
600 #ifdef TEST
601                 if (code)
602                     fprintf(stderr, "dns udp lookup returned error %d\n",
603                             code);
604 #endif
605             }
606             if ((socktype == SOCK_STREAM || socktype == 0) && code == 0) {
607                 code = krb5_locate_srv_dns_1(realm, dnsname, "_tcp",
608                                              &al, family);
609 #ifdef TEST
610                 if (code)
611                     fprintf(stderr, "dns tcp lookup returned error %d\n",
612                             code);
613 #endif
614             }
615         }
616     }
617 #endif /* KRB5_DNS_LOOKUP */
618 #ifdef TEST
619     if (code == 0)
620         fprintf (stderr, "krb5int_locate_server found %d addresses\n",
621                  al.naddrs);
622     else
623         fprintf (stderr, "krb5int_locate_server returning error code %d\n",
624                  code);
625 #endif
626     if (code != 0) {
627         if (al.space)
628             free_list (&al);
629         return code;
630     }
631     if (al.naddrs == 0) {       /* No good servers */
632         if (al.space)
633             free_list (&al);
634         return KRB5_REALM_CANT_RESOLVE;
635     }
636     *addrlist = al;
637     return 0;
638 }
639
640 krb5_error_code
641 krb5_locate_kdc(krb5_context context, const krb5_data *realm,
642                 struct addrlist *addrlist,
643                 int get_masters, int socktype, int family)
644 {
645     int udpport, sec_udpport;
646
647     udpport = get_port (KDC_PORTNAME, 0, KRB5_DEFAULT_PORT);
648     if (socktype == SOCK_STREAM)
649         sec_udpport = 0;
650     else {
651         sec_udpport = get_port (KDC_SECONDARY_PORTNAME, 0,
652                                 (udpport == htons (KRB5_DEFAULT_PORT)
653                                  ? KRB5_DEFAULT_SEC_PORT
654                                  : KRB5_DEFAULT_PORT));
655         if (sec_udpport == udpport)
656             sec_udpport = 0;
657     }
658
659     return krb5int_locate_server(context, realm, addrlist, 0,
660                                  get_masters ? "master_kdc" : "kdc",
661                                  (get_masters
662                                   ? "_kerberos-master"
663                                   : "_kerberos"),
664                                  socktype, udpport, sec_udpport, family);
665 }