2 * lib/krb5/os/locate_kdc.c
4 * Copyright 1990,2000,2001 by the Massachusetts Institute of Technology.
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.
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.
27 * get socket addresses for KDC.
34 #ifdef KRB5_DNS_LOOKUP
38 #include <arpa/inet.h>
39 #include <arpa/nameser.h>
47 /* for old Unixes and friends ... */
48 #ifndef MAXHOSTNAMELEN
49 #define MAXHOSTNAMELEN 64
52 #define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1)
54 #if KRB5_DNS_LOOKUP_KDC
55 #define DEFAULT_LOOKUP_KDC 1
57 #define DEFAULT_LOOKUP_KDC 0
59 #if KRB5_DNS_LOOKUP_REALM
60 #define DEFAULT_LOOKUP_REALM 1
62 #define DEFAULT_LOOKUP_REALM 0
66 maybe_use_dns (context, name, defalt)
75 code = profile_get_string(context->profile, "libdefaults",
77 if (value == 0 && code == 0)
78 code = profile_get_string(context->profile, "libdefaults",
79 "dns_fallback", 0, 0, &value);
86 use_dns = _krb5_conf_boolean(value);
87 profile_release_string(value);
92 _krb5_use_dns_kdc(context)
95 return maybe_use_dns (context, "dns_lookup_kdc", DEFAULT_LOOKUP_KDC);
99 _krb5_use_dns_realm(context)
100 krb5_context context;
102 return maybe_use_dns (context, "dns_lookup_realm", DEFAULT_LOOKUP_REALM);
105 #endif /* KRB5_DNS_LOOKUP */
107 static int get_port (const char *service, int stream, int defalt)
109 #ifdef HAVE_GETADDRINFO
110 struct addrinfo hints = { 0 };
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;
125 /* Any error - don't complain, just use default. */
126 return htons (defalt);
129 sp = getservbyname (service, stream ? "tcp" : "udp"); /* NOT THREAD SAFE */
132 return htons (defalt);
137 struct sockaddr **addrs;
141 #define ADDRLIST_INIT { 0, 0, 0 }
144 grow_list (struct addrlist *lp, int nmore)
147 int newspace = lp->space + nmore;
148 size_t newsize = newspace * sizeof (struct addrlist);
149 struct sockaddr **newaddrs;
151 /* NULL check a concession to SunOS4 compatibility for now; not
152 required for pure ANSI support. */
154 newaddrs = realloc (lp->addrs, newsize);
156 newaddrs = malloc (newsize);
158 if (newaddrs == NULL)
160 for (i = lp->space; i < newspace; i++)
162 lp->addrs = newaddrs;
163 lp->space = newspace;
168 free_list (struct addrlist *lp)
171 for (i = 0; i < lp->naddrs; i++)
175 lp->naddrs = lp->space = 0;
179 add_sockaddr_to_list (struct addrlist *lp, const struct sockaddr *addr,
182 struct sockaddr *copy;
185 fprintf (stderr, "\tadding sockaddr family %2d, len %d", addr->sa_family,
187 #ifdef HAVE_GETNAMEINFO
189 char name[NI_MAXHOST];
192 err = getnameinfo (addr, len, name, sizeof (name), NULL, 0,
193 NI_NUMERICHOST | NI_NUMERICSERV);
195 fprintf (stderr, "\t%s", name);
198 if (addr->sa_family == AF_INET)
199 fprintf (stderr, "\t%s",
200 inet_ntoa (((const struct sockaddr_in *)addr)->sin_addr));
202 fprintf (stderr, "\n");
205 if (lp->naddrs == lp->space) {
206 int err = grow_list (lp, 1);
209 fprintf (stderr, "grow_list failed %d\n", err);
221 memcpy (copy, addr, len);
222 lp->addrs[lp->naddrs++] = copy;
224 fprintf (stderr, "count is now %d\n", lp->naddrs);
229 #ifdef HAVE_GETADDRINFO
230 static int translate_ai_error (int err)
240 /* All of these indicate bad inputs to getaddrinfo. */
243 /* Translate to standard errno code. */
246 /* Translate to standard errno code. */
250 /* Name not known or no address data, but no error. Do
254 /* System error, obviously. */
257 /* An error code we haven't handled? */
262 static int add_addrinfo_to_list (struct addrlist *lp, struct addrinfo *a)
264 return add_sockaddr_to_list (lp, a->ai_addr, a->ai_addrlen);
267 static void set_port_num (struct sockaddr *addr, int num)
269 switch (addr->sa_family) {
271 ((struct sockaddr_in *)addr)->sin_port = num;
274 ((struct sockaddr_in6 *)addr)->sin6_port = num;
281 add_host_to_list (struct addrlist *lp, const char *hostname,
282 int port, int secport)
284 #ifdef HAVE_GETADDRINFO
285 struct addrinfo *addrs, *a;
292 fprintf (stderr, "adding hostname %s, ports %d,%d\n", hostname,
293 ntohs (port), ntohs (secport));
296 #ifdef HAVE_GETADDRINFO
297 err = getaddrinfo (hostname, NULL, NULL, &addrs);
299 return translate_ai_error (err);
300 for (a = addrs; a; a = a->ai_next) {
301 set_port_num (a->ai_addr, port);
302 err = add_addrinfo_to_list (lp, a);
309 set_port_num (a->ai_addr, secport);
310 err = add_addrinfo_to_list (lp, a);
314 freeaddrinfo (addrs);
316 hp = gethostbyname (hostname);
319 for (i = 0; hp->h_addr_list[i] != 0; i++) {
320 struct sockaddr_in sin4;
322 memset (&sin4, 0, sizeof (sin4));
323 memcpy (&sin4.sin_addr, hp->h_addr_list[i],
324 sizeof (sin4.sin_addr));
325 sin4.sin_family = AF_INET;
326 sin4.sin_port = port;
327 err = add_sockaddr_to_list (lp, (struct sockaddr *) &sin4,
332 sin4.sin_port = secport;
333 err = add_sockaddr_to_list (lp, (struct sockaddr *) &sin4,
346 * returns count of number of addresses found
347 * if master is non-NULL, it is filled in with the index of
351 static krb5_error_code
352 krb5_locate_srv_conf_1(krb5_context context, const krb5_data *realm,
353 const char * name, struct addrlist *addrlist,
354 int get_masters, int udpport, int sec_udpport)
356 const char *realm_srv_names[4];
357 char **masterlist, **hostlist, *host, *port, *cp;
358 krb5_error_code code;
359 int i, j, count, ismaster;
363 "looking in krb5.conf for realm %s entry %s; ports %d,%d\n",
364 realm->data, name, ntohs (udpport), ntohs (sec_udpport));
367 if ((host = malloc(realm->length + 1)) == NULL)
370 strncpy(host, realm->data, realm->length);
371 host[realm->length] = '\0';
376 realm_srv_names[0] = "realms";
377 realm_srv_names[1] = host;
378 realm_srv_names[2] = name;
379 realm_srv_names[3] = 0;
381 code = profile_get_values(context->profile, realm_srv_names, &hostlist);
384 if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
385 code = KRB5_REALM_UNKNOWN;
391 while (hostlist && hostlist[count])
395 profile_free_list(hostlist);
397 addrlist->naddrs = 0;
402 realm_srv_names[0] = "realms";
403 realm_srv_names[1] = host;
404 realm_srv_names[2] = "admin_server";
405 realm_srv_names[3] = 0;
407 code = profile_get_values(context->profile, realm_srv_names,
413 for (i=0; masterlist[i]; i++) {
414 host = masterlist[i];
417 * Strip off excess whitespace
419 cp = strchr(host, ' ');
422 cp = strchr(host, '\t');
425 cp = strchr(host, ':');
434 /* at this point, if master is non-NULL, then either the master kdc
435 is required, and there is one, or the master kdc is not required,
436 and there may or may not be one. */
438 #ifdef HAVE_NETINET_IN_H
443 for (i=0; hostlist[i]; i++) {
448 * Strip off excess whitespace
450 cp = strchr(host, ' ');
453 cp = strchr(host, '\t');
456 port = strchr(host, ':');
464 for (j=0; masterlist[j]; j++) {
465 if (strcasecmp(hostlist[i], masterlist[j]) == 0) {
471 if (get_masters && !ismaster)
478 l = strtoul (port, &endptr, 10);
479 if (endptr == NULL || *endptr != 0)
484 /* L is unsigned, don't need to check <0. */
494 code = add_host_to_list (addrlist, hostlist[i], p1, p2);
497 fprintf (stderr, "error %d returned from add_host_to_list\n", code);
500 profile_free_list (hostlist);
502 profile_free_list (masterlist);
508 profile_free_list(hostlist);
510 profile_free_list(masterlist);
516 static krb5_error_code
517 krb5_locate_srv_conf(context, realm, name, addr_pp, naddrs, get_masters,
518 udpport, sec_udpport)
519 krb5_context context;
520 const krb5_data *realm;
522 struct sockaddr ***addr_pp;
525 int udpport, sec_udpport;
528 struct addrlist al = ADDRLIST_INIT;
530 ret = krb5_locate_srv_conf_1 (context, realm, name, &al,
531 get_masters, udpport, sec_udpport);
536 if (al.naddrs == 0) /* Couldn't resolve any KDC names */
537 return KRB5_REALM_CANT_RESOLVE;
544 #ifdef KRB5_DNS_LOOKUP
547 * Lookup a KDC via DNS SRV records
550 static krb5_error_code
551 krb5_locate_srv_dns_1 (const krb5_data *realm,
553 const char *protocol,
554 struct addrlist *addrlist)
557 unsigned char bytes[2048];
560 unsigned char *p=NULL;
561 char host[MAX_DNS_NAMELEN], *h;
563 int priority, weight, size, len, numanswers, numqueries, rdlen;
565 const int hdrsize = sizeof(HEADER);
566 struct srv_dns_entry {
567 struct srv_dns_entry *next;
574 struct srv_dns_entry *head = NULL;
575 struct srv_dns_entry *srv = NULL, *entry = NULL;
576 krb5_error_code code = 0;
579 * First off, build a query of the form:
581 * service.protocol.realm
583 * which will most likely be something like:
585 * _kerberos._udp.REALM
589 if ( strlen(service) + strlen(protocol) + realm->length + 6
592 sprintf(host, "%s.%s.%.*s", service, protocol, (int) realm->length,
595 /* Realm names don't (normally) end with ".", but if the query
596 doesn't end with "." and doesn't get an answer as is, the
597 resolv code will try appending the local domain. Since the
598 realm names are absolutes, let's stop that.
600 But only if a name has been specified. If we are performing
601 a search on the prefix alone then the intention is to allow
602 the local domain or domain search lists to be expanded. */
604 h = host + strlen (host);
605 if ((h > host) && (h[-1] != '.') && ((h - host + 1) < sizeof(host)))
609 fprintf (stderr, "sending DNS SRV query for %s\n", host);
612 size = res_search(host, C_IN, T_SRV, answer.bytes, sizeof(answer.bytes));
618 * We got an answer! First off, parse the header and figure out how
619 * many answers we got back.
624 numqueries = ntohs(answer.hdr.qdcount);
625 numanswers = ntohs(answer.hdr.ancount);
630 * We need to skip over all of the questions, so we have to iterate
631 * over every query record. dn_expand() is able to tell us the size
632 * of compress DNS names, so we use it.
635 #define INCR_CHECK(x,y) x += y; if (x > size + answer.bytes) goto out
636 #define CHECK(x,y) if (x + y > size + answer.bytes) goto out
637 #define NTOHSP(x,y) x[0] << 8 | x[1]; x += y
639 while (numqueries--) {
640 len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
643 INCR_CHECK(p, len + 4);
647 * We're now pointing at the answer records. Only process them if
648 * they're actually T_SRV records (they might be CNAME records,
651 * But in a DNS reply, if you get a CNAME you always get the associated
652 * "real" RR for that CNAME. RFC 1034, 3.6.2:
654 * CNAME RRs cause special action in DNS software. When a name server
655 * fails to find a desired RR in the resource set associated with the
656 * domain name, it checks to see if the resource set consists of a CNAME
657 * record with a matching class. If so, the name server includes the CNAME
658 * record in the response and restarts the query at the domain name
659 * specified in the data field of the CNAME record. The one exception to
660 * this rule is that queries which match the CNAME type are not restarted.
662 * In other words, CNAMEs do not need to be expanded by the client.
665 while (numanswers--) {
667 /* First is the name; use dn_expand to get the compressed size */
668 len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
673 /* Next is the query type */
677 /* Next is the query class; also skip over 4 byte TTL */
681 /* Record data length */
687 * If this is an SRV record, process it. Record format is:
695 if (class == C_IN && type == T_SRV) {
697 priority = NTOHSP(p,2);
699 weight = NTOHSP(p,2);
702 len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
708 * We got everything! Insert it into our list, but make sure
709 * it's in the right order. Right now we don't do anything
710 * with the weight field
713 srv = (struct srv_dns_entry *) malloc(sizeof(struct srv_dns_entry));
717 srv->priority = priority;
718 srv->weight = weight;
720 srv->host = strdup(host);
722 if (head == NULL || head->priority > srv->priority) {
727 * This is confusing. Only insert an entry into this
729 * The next person has a higher priority (lower priorities
732 * There is no next entry (we're at the end)
734 for (entry = head; entry != NULL; entry = entry->next)
736 entry->next->priority > srv->priority) ||
737 entry->next == NULL) {
738 srv->next = entry->next;
743 INCR_CHECK(p, rdlen);
747 * Okay! Now we've got a linked list of entries sorted by
748 * priority. Start looking up A records and returning
756 fprintf (stderr, "walking answer list:\n");
758 for (entry = head; entry != NULL; entry = entry->next) {
760 fprintf (stderr, "\tport=%d host=%s\n", entry->port, entry->host);
762 code = add_host_to_list (addrlist, entry->host, htons (entry->port), 0);
767 fprintf (stderr, "[end]\n");
770 for (entry = head; entry != NULL; ) {
787 static krb5_error_code
788 krb5_locate_srv_dns(const krb5_data *realm,
789 const char *service, const char *protocol,
790 struct sockaddr ***addr_pp, int *naddrs)
792 struct addrlist al = ADDRLIST_INIT;
793 krb5_error_code code;
795 code = krb5_locate_srv_dns_1 (realm, service, protocol, &al);
801 #endif /* KRB5_DNS_LOOKUP */
804 * Wrapper function for the two backends
808 krb5int_locate_server (krb5_context context, const krb5_data *realm,
809 struct sockaddr ***addr_pp, int *naddrs,
811 const char *profname, const char *dnsname,
813 /* network order port numbers! */
814 int dflport1, int dflport2)
816 krb5_error_code code;
817 struct addrlist al = ADDRLIST_INIT;
820 * We always try the local file first
823 code = krb5_locate_srv_conf_1(context, realm, profname, &al, get_masters,
826 #ifdef KRB5_DNS_LOOKUP
827 if (code && dnsname != 0) {
828 int use_dns = _krb5_use_dns_kdc(context);
830 code = krb5_locate_srv_dns_1(realm, dnsname,
831 is_stream ? "_tcp" : "_udp", &al);
833 #endif /* KRB5_DNS_LOOKUP */
836 fprintf (stderr, "krb5int_locate_server found %d addresses\n",
839 fprintf (stderr, "krb5int_locate_server returning error code %d\n",
847 if (al.naddrs == 0) { /* No good servers */
850 return KRB5_REALM_CANT_RESOLVE;
858 krb5_locate_kdc(context, realm, addr_pp, naddrs, get_masters)
859 krb5_context context;
860 const krb5_data *realm;
861 struct sockaddr ***addr_pp;
865 int udpport, sec_udpport;
867 udpport = get_port (KDC_PORTNAME, 0, KRB5_DEFAULT_PORT);
868 sec_udpport = get_port (KDC_SECONDARY_PORTNAME, 0,
869 (udpport == htons (KRB5_DEFAULT_PORT)
870 ? KRB5_DEFAULT_SEC_PORT
871 : KRB5_DEFAULT_PORT));
872 if (sec_udpport == udpport)
875 return krb5int_locate_server (context, realm, addr_pp, naddrs, get_masters,
880 0, udpport, sec_udpport);