2000-08-31 Jeffrey Altman <jaltman@columbia.edu>
[krb5.git] / src / lib / krb5 / os / locate_kdc.c
1 /*
2  * lib/krb5/os/locate_kdc.c
3  *
4  * Copyright 1990 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 "k5-int.h"
32 #include "os-proto.h"
33 #include <stdio.h>
34 #ifdef KRB5_DNS_LOOKUP
35 #ifdef WSHELPER
36 #include <wshelper.h>
37 #else /* WSHELPER */
38 #include <arpa/inet.h>
39 #include <arpa/nameser.h>
40 #include <resolv.h>
41 #include <netdb.h>
42 #endif /* WSHELPER */
43 #ifndef T_SRV
44 #define T_SRV 33
45 #endif /* T_SRV */
46
47 /* for old Unixes and friends ... */
48 #ifndef MAXHOSTNAMELEN
49 #define MAXHOSTNAMELEN 64
50 #endif
51
52 #define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1)
53 #ifndef KPASSWD_PORTNAME
54 #define KPASSWD_PORTNAME "kpasswd"
55 #endif
56
57 #if KRB5_DNS_LOOKUP_KDC
58 #define DEFAULT_LOOKUP_KDC 1
59 #else
60 #define DEFAULT_LOOKUP_KDC 0
61 #endif
62 #if KRB5_DNS_LOOKUP_REALM
63 #define DEFAULT_LOOKUP_REALM 1
64 #else
65 #define DEFAULT_LOOKUP_REALM 0
66 #endif
67
68 static int
69 maybe_use_dns (context, name, defalt)
70      krb5_context context;
71      const char *name;
72      int defalt;
73 {
74     krb5_error_code code;
75     char * value = NULL;
76     int use_dns = 0;
77
78     code = profile_get_string(context->profile, "libdefaults",
79                               name, 0, 0, &value);
80     if (value == 0 && code == 0)
81         code = profile_get_string(context->profile, "libdefaults",
82                                   "dns_fallback", 0, 0, &value);
83     if (code)
84         return defalt;
85
86     if (value == 0)
87         return defalt;
88
89     use_dns = _krb5_conf_boolean(value);
90     profile_release_string(value);
91     return use_dns;
92 }
93
94 int
95 _krb5_use_dns_kdc(context)
96     krb5_context context;
97 {
98     return maybe_use_dns (context, "dns_lookup_kdc", DEFAULT_LOOKUP_KDC);
99 }
100
101 int
102 _krb5_use_dns_realm(context)
103     krb5_context context;
104 {
105     return maybe_use_dns (context, "dns_lookup_realm", DEFAULT_LOOKUP_REALM);
106 }
107
108 #endif /* KRB5_DNS_LOOKUP */
109
110 /*
111  * returns count of number of addresses found
112  * if master is non-NULL, it is filled in with the index of
113  * the master kdc
114  */
115
116 krb5_error_code
117 krb5_locate_srv_conf(context, realm, name, addr_pp, naddrs, get_masters)
118     krb5_context context;
119     const krb5_data *realm;
120     const char * name;
121     struct sockaddr **addr_pp;
122     int *naddrs;
123     int get_masters;
124 {
125     const char  *realm_srv_names[4];
126     char **masterlist, **hostlist, *host, *port, *cp;
127     krb5_error_code code;
128     int i, j, out, count, ismaster;
129     struct sockaddr *addr_p;
130     struct sockaddr_in *sin_p;
131     struct hostent *hp;
132     struct servent *sp;
133 #ifdef HAVE_NETINET_IN_H
134     u_short udpport;
135     u_short sec_udpport;
136 #endif
137
138     if ((host = malloc(realm->length + 1)) == NULL) 
139         return ENOMEM;
140
141     strncpy(host, realm->data, realm->length);
142     host[realm->length] = '\0';
143     hostlist = 0;
144
145     masterlist = NULL;
146
147     realm_srv_names[0] = "realms";
148     realm_srv_names[1] = host;
149     realm_srv_names[2] = name;
150     realm_srv_names[3] = 0;
151
152     code = profile_get_values(context->profile, realm_srv_names, &hostlist);
153
154     if (code) {
155         if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
156             code = KRB5_REALM_UNKNOWN;
157         krb5_xfree(host);
158         return code;
159      }
160
161 #ifdef HAVE_NETINET_IN_H
162     if ( !strcmp(name,"kpasswd_server") ) {
163         if ((sp = getservbyname(KPASSWD_PORTNAME, "udp")))
164             udpport = sp->s_port;
165         else
166             udpport = htons(DEFAULT_KPASSWD_PORT);
167         sec_udpport = 0;
168     } else {
169     if ((sp = getservbyname(KDC_PORTNAME, "udp")))
170         udpport = sp->s_port;
171         else 
172             udpport = htons(KRB5_DEFAULT_PORT);
173     if ((sp = getservbyname(KDC_SECONDARY_PORTNAME, "udp")))
174         sec_udpport = sp->s_port;
175         else
176             sec_udpport = htons(KRB5_DEFAULT_SEC_PORT);
177     }
178 #endif
179     if (sec_udpport == udpport)
180         sec_udpport = 0;
181
182     count = 0;
183     while (hostlist && hostlist[count])
184             count++;
185     
186     if (count == 0) {
187         profile_free_list(hostlist);
188         krb5_xfree(host);
189         *naddrs = 0;
190         return 0;
191     }
192     
193     if (get_masters) {
194         realm_srv_names[0] = "realms";
195         realm_srv_names[1] = host;
196         realm_srv_names[2] = "admin_server";
197         realm_srv_names[3] = 0;
198
199         code = profile_get_values(context->profile, realm_srv_names,
200                                   &masterlist);
201
202         krb5_xfree(host);
203
204         if (code == 0) {
205             for (i=0; masterlist[i]; i++) {
206                 host = masterlist[i];
207
208                 /*
209                  * Strip off excess whitespace
210                  */
211                 cp = strchr(host, ' ');
212                 if (cp)
213                     *cp = 0;
214                 cp = strchr(host, '\t');
215                 if (cp)
216                     *cp = 0;
217                 cp = strchr(host, ':');
218                 if (cp)
219                     *cp = 0;
220             }
221         }
222     } else {
223         krb5_xfree(host);
224     }
225
226     /* at this point, if master is non-NULL, then either the master kdc
227        is required, and there is one, or the master kdc is not required,
228        and there may or may not be one. */
229
230 #ifdef HAVE_NETINET_IN_H
231     if (sec_udpport)
232             count = count * 2;
233 #endif
234
235     addr_p = (struct sockaddr *)malloc (sizeof (struct sockaddr) * count);
236     if (addr_p == NULL) {
237         if (hostlist)
238             profile_free_list(hostlist);
239         if (masterlist)
240             profile_free_list(masterlist);
241         return ENOMEM;
242     }
243
244     for (i=0, out=0; hostlist[i]; i++) {
245         host = hostlist[i];
246         /*
247          * Strip off excess whitespace
248          */
249         cp = strchr(host, ' ');
250         if (cp)
251             *cp = 0;
252         cp = strchr(host, '\t');
253         if (cp)
254             *cp = 0;
255         port = strchr(host, ':');
256         if (port) {
257             *port = 0;
258             port++;
259         }
260
261         if ((hp = gethostbyname(hostlist[i])) == 0) {
262             continue;
263         }
264
265         ismaster = 0;
266         if (masterlist) {
267             for (j=0; masterlist[j]; j++) {
268                 if (strcasecmp(hostlist[i], masterlist[j]) == 0) {
269                     ismaster = 1;
270                 }
271             }
272         }
273
274         if ( !get_masters || ismaster ) {
275             switch (hp->h_addrtype) {
276
277 #ifdef HAVE_NETINET_IN_H
278             case AF_INET:
279                 for (j=0; hp->h_addr_list[j]; j++) {
280                     sin_p = (struct sockaddr_in *) &addr_p[out++];
281                     memset ((char *)sin_p, 0, sizeof(struct sockaddr));
282                     sin_p->sin_family = hp->h_addrtype;
283                     sin_p->sin_port = port ? htons(atoi(port)) : udpport;
284                     memcpy((char *)&sin_p->sin_addr,
285                             (char *)hp->h_addr_list[j],
286                             sizeof(struct in_addr));
287                     if (out+1 >= count) {
288                         count += 5;
289                         addr_p = (struct sockaddr *)
290                             realloc ((char *)addr_p,
291                                       sizeof(struct sockaddr) * count);
292                         if (addr_p == NULL) {
293                             if ( hostlist )
294                                 profile_free_list(hostlist);
295                             if ( masterlist )
296                                 profile_free_list(masterlist);
297                             return ENOMEM;
298                         }
299                     }
300                     if (sec_udpport && !port) {
301                         addr_p[out] = addr_p[out-1];
302                         sin_p = (struct sockaddr_in *) &addr_p[out++];
303                         sin_p->sin_port = sec_udpport;
304                     }
305                 }
306                 break;
307 #endif
308             default:
309                 break;
310             }
311         }
312     }
313
314     if (hostlist)
315         profile_free_list(hostlist);
316     if (masterlist)
317         profile_free_list(masterlist);
318
319     if (out == 0) {     /* Couldn't resolve any KDC names */
320         free (addr_p);
321         return KRB5_REALM_CANT_RESOLVE;
322     }
323
324     *addr_pp = addr_p;
325     *naddrs = out;
326     return 0;
327 }
328
329 #ifdef KRB5_DNS_LOOKUP
330
331 /*
332  * Lookup a KDC via DNS SRV records
333  */
334
335 krb5_error_code
336 krb5_locate_srv_dns(realm, service, protocol, addr_pp, naddrs)
337     const krb5_data *realm;
338     const char *service;
339     const char *protocol;
340     struct sockaddr **addr_pp;
341     int *naddrs;
342 {
343     int out, j, count;
344     union {
345         unsigned char bytes[2048];
346         HEADER hdr;
347     } answer;
348     unsigned char *p=NULL;
349     char host[MAX_DNS_NAMELEN], *h;
350     struct sockaddr *addr = NULL;
351     struct sockaddr_in *sin = NULL;
352     struct hostent *hp = NULL;
353     int type, class;
354     int priority, weight, size, len, numanswers, numqueries, rdlen;
355     unsigned short port;
356     const int hdrsize = sizeof(HEADER);
357     struct srv_dns_entry {
358         struct srv_dns_entry *next;
359         int priority;
360         int weight;
361         unsigned short port;
362         char *host;
363     };
364
365     struct srv_dns_entry *head = NULL;
366     struct srv_dns_entry *srv = NULL, *entry = NULL;
367
368     out = 0;
369     addr = (struct sockaddr *) malloc(sizeof(struct sockaddr));
370     if (addr == NULL)
371         return ENOMEM;
372
373     count = 1;
374
375     /*
376      * First off, build a query of the form:
377      *
378      * service.protocol.realm
379      *
380      * which will most likely be something like:
381      *
382      * _kerberos._udp.REALM
383      *
384      */
385
386     if ( strlen(service) + strlen(protocol) + realm->length + 6 
387          > MAX_DNS_NAMELEN )
388         goto out;
389     sprintf(host, "%s.%s.%.*s", service, protocol, realm->length,
390             realm->data);
391
392     /* Realm names don't (normally) end with ".", but if the query
393     doesn't end with "." and doesn't get an answer as is, the
394     resolv code will try appending the local domain.  Since the
395     realm names are absolutes, let's stop that.  
396
397     But only if a name has been specified.  If we are performing
398     a search on the prefix alone then the intention is to allow
399     the local domain or domain search lists to be expanded.
400     */
401
402     h = host + strlen (host);
403     if ((h > host) && (h[-1] != '.') && ((h - host + 1) < sizeof(host)))
404         strcpy (h, ".");
405
406     size = res_search(host, C_IN, T_SRV, answer.bytes, sizeof(answer.bytes));
407
408     if (size < hdrsize)
409         goto out;
410
411     /*
412      * We got an answer!  First off, parse the header and figure out how
413      * many answers we got back.
414      */
415
416     p = answer.bytes;
417
418     numqueries = ntohs(answer.hdr.qdcount);
419     numanswers = ntohs(answer.hdr.ancount);
420
421     p += sizeof(HEADER);
422
423     /*
424      * We need to skip over all of the questions, so we have to iterate
425      * over every query record.  dn_expand() is able to tell us the size
426      * of compress DNS names, so we use it.
427      */
428
429 #define INCR_CHECK(x,y) x += y; if (x > size + answer.bytes) goto out
430 #define CHECK(x,y) if (x + y > size + answer.bytes) goto out
431 #define NTOHSP(x,y) x[0] << 8 | x[1]; x += y
432
433     while (numqueries--) {
434         len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
435         if (len < 0)
436             goto out;
437         INCR_CHECK(p, len + 4);
438     }
439
440     /*
441      * We're now pointing at the answer records.  Only process them if
442      * they're actually T_SRV records (they might be CNAME records,
443      * for instance).
444      *
445      * But in a DNS reply, if you get a CNAME you always get the associated
446      * "real" RR for that CNAME.  RFC 1034, 3.6.2:
447      *
448      * CNAME RRs cause special action in DNS software.  When a name server
449      * fails to find a desired RR in the resource set associated with the
450      * domain name, it checks to see if the resource set consists of a CNAME
451      * record with a matching class.  If so, the name server includes the CNAME
452      * record in the response and restarts the query at the domain name
453      * specified in the data field of the CNAME record.  The one exception to
454      * this rule is that queries which match the CNAME type are not restarted.
455      *
456      * In other words, CNAMEs do not need to be expanded by the client.
457      */
458
459     while (numanswers--) {
460
461         /* First is the name; use dn_expand to get the compressed size */
462         len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
463         if (len < 0)
464             goto out;
465         INCR_CHECK(p, len);
466
467         /* Next is the query type */
468         CHECK(p, 2);
469         type = NTOHSP(p,2);
470
471         /* Next is the query class; also skip over 4 byte TTL */
472         CHECK(p, 6);
473         class = NTOHSP(p,6);
474
475         /* Record data length */
476
477         CHECK(p,2);
478         rdlen = NTOHSP(p,2);
479
480         /*
481          * If this is an SRV record, process it.  Record format is:
482          *
483          * Priority
484          * Weight
485          * Port
486          * Server name
487          */
488
489         if (class == C_IN && type == T_SRV) {
490             CHECK(p,2);
491             priority = NTOHSP(p,2);
492             CHECK(p, 2);
493             weight = NTOHSP(p,2);
494             CHECK(p, 2);
495             port = NTOHSP(p,2);
496             len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
497             if (len < 0)
498                 goto out;
499             INCR_CHECK(p, len);
500
501             /*
502              * We got everything!  Insert it into our list, but make sure
503              * it's in the right order.  Right now we don't do anything
504              * with the weight field
505              */
506
507             srv = (struct srv_dns_entry *) malloc(sizeof(struct srv_dns_entry));
508             if (srv == NULL)
509                 goto out;
510         
511             srv->priority = priority;
512             srv->weight = weight;
513             srv->port = port;
514             srv->host = strdup(host);
515
516             if (head == NULL || head->priority > srv->priority) {
517                 srv->next = head;
518                 head = srv;
519             } else
520                 /*
521                  * This is confusing.  Only insert an entry into this
522                  * spot if:
523                  * The next person has a higher priority (lower priorities
524                  * are preferred).
525                  * Or
526                  * There is no next entry (we're at the end)
527                  */
528                 for (entry = head; entry != NULL; entry = entry->next)
529                     if ((entry->next &&
530                          entry->next->priority > srv->priority) ||
531                         entry->next == NULL) {
532                         srv->next = entry->next;
533                         entry->next = srv;
534                         break;
535                     }
536         } else
537             INCR_CHECK(p, rdlen);
538     }
539         
540     /*
541      * Okay!  Now we've got a linked list of entries sorted by
542      * priority.  Start looking up A records and returning
543      * addresses.
544      */
545
546     if (head == NULL)
547         goto out;
548
549     for (entry = head; entry != NULL; entry = entry->next) {
550         hp = gethostbyname(entry->host);
551         if (hp != 0) {
552             switch (hp->h_addrtype) {
553 #ifdef HAVE_NETINET_IN_H
554             case AF_INET:
555                 for (j=0; hp->h_addr_list[j]; j++) {
556                     sin = (struct sockaddr_in *) &addr[out++];
557                     memset ((char *) sin, 0, sizeof (struct sockaddr));
558                     sin->sin_family = hp->h_addrtype;
559                     sin->sin_port = htons(entry->port);
560                     memcpy((char *) &sin->sin_addr,
561                            (char *) hp->h_addr_list[j],
562                            sizeof(struct in_addr));
563                     if (out + 1 >= count) {
564                         count += 5;
565                         addr = (struct sockaddr *)
566                                 realloc((char *) addr,
567                                         sizeof(struct sockaddr) * count);
568                         if (!addr)
569                             goto out;
570                     }
571                 }
572                 break;
573 #endif /* HAVE_NETINET_IN_H */
574             default:
575                 break;
576             }
577         }
578     }
579
580     for (entry = head; entry != NULL; ) {
581         free(entry->host);
582         entry->host = NULL;
583         srv = entry;
584         entry = entry->next;
585         free(srv);
586         srv = NULL;
587     }
588
589   out:
590     if (srv)
591         free(srv);
592
593     if (out == 0) {     /* No good servers */
594         if (addr)
595             free(addr);
596         return KRB5_REALM_CANT_RESOLVE;
597     }
598
599     *addr_pp = addr;
600     *naddrs = out;
601     return 0;
602 }
603 #endif /* KRB5_DNS_LOOKUP */
604
605 /*
606  * Wrapper function for the two backends
607  */
608
609 krb5_error_code
610 krb5_locate_kdc(context, realm, addr_pp, naddrs, get_masters)
611     krb5_context context;
612     const krb5_data *realm;
613     struct sockaddr **addr_pp;
614     int *naddrs;
615     int get_masters;
616 {
617     krb5_error_code code;
618
619     /*
620      * We always try the local file first
621      */
622
623     code = krb5_locate_srv_conf(context, realm, "kdc", addr_pp, naddrs,
624                                  get_masters);
625
626 #ifdef KRB5_DNS_LOOKUP
627     if (code) {
628         int use_dns = _krb5_use_dns_kdc(context);
629         if ( use_dns ) {
630             code = krb5_locate_srv_dns(realm, 
631                                         get_masters ? "_kerberos-master" : "_kerberos",
632                                         "_udp", addr_pp, naddrs);
633         }
634     }
635 #endif /* KRB5_DNS_LOOKUP */
636     return (code);
637 }
638
639 #if 0 /* Why is this useful?  It's not used now, and it's certainly
640          not useful if you don't have the DNS code enabled.  -KR  */
641
642 /*
643  * It turns out that it is really useful to be able to use these functions
644  * for other things (like admin servers), so create an abstract function
645  * for this
646  */
647
648 krb5_error_code
649 krb5_locate_server(realm, name, proto, addr_pp, naddrs)
650     const krb5_data *realm;
651     const char *name, *proto;
652     struct sockaddr **addr_pp;
653     int *naddrs;
654 {
655     krb5_error_code code = KRB5_REALM_UNKNOWN;
656 #ifdef KRB5_DNS_LOOKUP
657     code = krb5_locate_srv_dns(realm, name, proto,
658                                 (struct sockaddr **) addr_pp, naddrs);
659 #endif /* KRB5_DNS_LOOKUP */
660     return (code);
661 }
662 #endif