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