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