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 #include "fake-addrinfo.h"
49 /* for old Unixes and friends ... */
50 #ifndef MAXHOSTNAMELEN
51 #define MAXHOSTNAMELEN 64
54 #define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1)
56 #if KRB5_DNS_LOOKUP_KDC
57 #define DEFAULT_LOOKUP_KDC 1
59 #define DEFAULT_LOOKUP_KDC 0
61 #if KRB5_DNS_LOOKUP_REALM
62 #define DEFAULT_LOOKUP_REALM 1
64 #define DEFAULT_LOOKUP_REALM 0
68 maybe_use_dns (context, name, defalt)
77 code = profile_get_string(context->profile, "libdefaults",
79 if (value == 0 && code == 0)
80 code = profile_get_string(context->profile, "libdefaults",
81 "dns_fallback", 0, 0, &value);
88 use_dns = _krb5_conf_boolean(value);
89 profile_release_string(value);
94 _krb5_use_dns_kdc(context)
97 return maybe_use_dns (context, "dns_lookup_kdc", DEFAULT_LOOKUP_KDC);
101 _krb5_use_dns_realm(context)
102 krb5_context context;
104 return maybe_use_dns (context, "dns_lookup_realm", DEFAULT_LOOKUP_REALM);
107 #endif /* KRB5_DNS_LOOKUP */
109 static int get_port (const char *service, int stream, int defalt)
111 struct addrinfo hints = { 0 };
115 hints.ai_family = PF_INET;
116 hints.ai_socktype = stream ? SOCK_STREAM : SOCK_DGRAM;
117 err = getaddrinfo (NULL, service, &hints, &ai);
118 if (err == 0 && ai != 0) {
119 if (ai->ai_addr->sa_family == AF_INET) {
120 int port = ((struct sockaddr_in *)ai->ai_addr)->sin_port;
126 /* Any error - don't complain, just use default. */
127 return htons (defalt);
131 grow_list (struct addrlist *lp, int nmore)
134 int newspace = lp->space + nmore;
135 size_t newsize = newspace * sizeof (struct addrlist);
136 struct sockaddr **newaddrs;
138 /* NULL check a concession to SunOS4 compatibility for now; not
139 required for pure ANSI support. */
141 newaddrs = realloc (lp->addrs, newsize);
143 newaddrs = malloc (newsize);
145 if (newaddrs == NULL)
147 for (i = lp->space; i < newspace; i++)
149 lp->addrs = newaddrs;
150 lp->space = newspace;
154 /* Free up everything pointed to by the addrlist structure, but don't
155 free the structure itself. */
157 krb5int_free_addrlist (struct addrlist *lp)
160 for (i = 0; i < lp->naddrs; i++)
164 lp->naddrs = lp->space = 0;
166 #define free_list krb5int_free_addrlist
169 add_sockaddr_to_list (struct addrlist *lp, const struct sockaddr *addr,
172 struct sockaddr *copy;
176 char name[NI_MAXHOST];
178 fprintf (stderr, "\tadding sockaddr family %2d, len %d", addr->sa_family,
181 err = getnameinfo (addr, len, name, sizeof (name), NULL, 0,
182 NI_NUMERICHOST | NI_NUMERICSERV);
184 fprintf (stderr, "\t%s", name);
185 fprintf (stderr, "\n");
188 if (lp->naddrs == lp->space) {
189 err = grow_list (lp, 1);
192 fprintf (stderr, "grow_list failed %d\n", err);
204 memcpy (copy, addr, len);
205 lp->addrs[lp->naddrs++] = copy;
207 fprintf (stderr, "count is now %d\n", lp->naddrs);
212 static int translate_ai_error (int err)
217 #ifdef EAI_ADDRFAMILY
224 /* All of these indicate bad inputs to getaddrinfo. */
227 /* Translate to standard errno code. */
230 /* Translate to standard errno code. */
232 #if EAI_NODATA != EAI_NONAME
236 /* Name not known or no address data, but no error. Do
241 /* System error, obviously. */
245 /* An error code we haven't handled? */
250 static int add_addrinfo_to_list (struct addrlist *lp, struct addrinfo *a)
252 return add_sockaddr_to_list (lp, a->ai_addr, a->ai_addrlen);
255 static void set_port_num (struct sockaddr *addr, int num)
257 switch (addr->sa_family) {
259 ((struct sockaddr_in *)addr)->sin_port = num;
261 #ifdef KRB5_USE_INET6
263 ((struct sockaddr_in6 *)addr)->sin6_port = num;
270 add_host_to_list (struct addrlist *lp, const char *hostname,
271 int port, int secport)
273 struct addrinfo *addrs, *a;
274 /* Must set err to 0 for the case we return err without ever
275 setting it -- !HAVE_GETADDRINFO and !hp */
279 fprintf (stderr, "adding hostname %s, ports %d,%d\n", hostname,
280 ntohs (port), ntohs (secport));
283 err = getaddrinfo (hostname, NULL, NULL, &addrs);
285 return translate_ai_error (err);
286 for (a = addrs; a; a = a->ai_next) {
287 /* AIX 4.3.3 libc is broken. */
288 if (a->ai_addr->sa_family == 0)
289 a->ai_addr->sa_family = a->ai_family;
291 if (a->ai_addr->sa_len == 0)
292 a->ai_addr->sa_len = a->ai_addrlen;
295 set_port_num (a->ai_addr, port);
296 err = add_addrinfo_to_list (lp, a);
303 set_port_num (a->ai_addr, secport);
304 err = add_addrinfo_to_list (lp, a);
308 freeaddrinfo (addrs);
313 * returns count of number of addresses found
314 * if master is non-NULL, it is filled in with the index of
318 static krb5_error_code
319 krb5_locate_srv_conf_1(krb5_context context, const krb5_data *realm,
320 const char * name, struct addrlist *addrlist,
321 int get_masters, int udpport, int sec_udpport)
323 const char *realm_srv_names[4];
324 char **masterlist, **hostlist, *host, *port, *cp;
325 krb5_error_code code;
326 int i, j, count, ismaster;
330 "looking in krb5.conf for realm %s entry %s; ports %d,%d\n",
331 realm->data, name, ntohs (udpport), ntohs (sec_udpport));
334 if ((host = malloc(realm->length + 1)) == NULL)
337 strncpy(host, realm->data, realm->length);
338 host[realm->length] = '\0';
343 realm_srv_names[0] = "realms";
344 realm_srv_names[1] = host;
345 realm_srv_names[2] = name;
346 realm_srv_names[3] = 0;
348 code = profile_get_values(context->profile, realm_srv_names, &hostlist);
351 if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
352 code = KRB5_REALM_UNKNOWN;
358 while (hostlist && hostlist[count])
362 profile_free_list(hostlist);
364 addrlist->naddrs = 0;
369 realm_srv_names[0] = "realms";
370 realm_srv_names[1] = host;
371 realm_srv_names[2] = "admin_server";
372 realm_srv_names[3] = 0;
374 code = profile_get_values(context->profile, realm_srv_names,
380 for (i=0; masterlist[i]; i++) {
381 host = masterlist[i];
384 * Strip off excess whitespace
386 cp = strchr(host, ' ');
389 cp = strchr(host, '\t');
392 cp = strchr(host, ':');
401 /* at this point, if master is non-NULL, then either the master kdc
402 is required, and there is one, or the master kdc is not required,
403 and there may or may not be one. */
405 #ifdef HAVE_NETINET_IN_H
410 for (i=0; hostlist[i]; i++) {
415 * Strip off excess whitespace
417 cp = strchr(host, ' ');
420 cp = strchr(host, '\t');
423 port = strchr(host, ':');
431 for (j=0; masterlist[j]; j++) {
432 if (strcasecmp(hostlist[i], masterlist[j]) == 0) {
438 if (get_masters && !ismaster)
445 l = strtoul (port, &endptr, 10);
446 if (endptr == NULL || *endptr != 0)
451 /* L is unsigned, don't need to check <0. */
461 code = add_host_to_list (addrlist, hostlist[i], p1, p2);
464 fprintf (stderr, "error %d returned from add_host_to_list\n", code);
467 profile_free_list (hostlist);
469 profile_free_list (masterlist);
475 profile_free_list(hostlist);
477 profile_free_list(masterlist);
483 static krb5_error_code
484 krb5_locate_srv_conf(context, realm, name, al, get_masters,
485 udpport, sec_udpport)
486 krb5_context context;
487 const krb5_data *realm;
491 int udpport, sec_udpport;
495 ret = krb5_locate_srv_conf_1 (context, realm, name, al,
496 get_masters, udpport, sec_udpport);
499 if (al->naddrs == 0) /* Couldn't resolve any KDC names */
500 return KRB5_REALM_CANT_RESOLVE;
505 #ifdef KRB5_DNS_LOOKUP
508 * Lookup a KDC via DNS SRV records
511 static krb5_error_code
512 krb5_locate_srv_dns_1 (const krb5_data *realm,
514 const char *protocol,
515 struct addrlist *addrlist)
518 unsigned char bytes[2048];
521 unsigned char *p=NULL;
522 char host[MAX_DNS_NAMELEN], *h;
524 int priority, weight, size, len, numanswers, numqueries, rdlen;
526 const int hdrsize = sizeof(HEADER);
527 struct srv_dns_entry {
528 struct srv_dns_entry *next;
535 struct srv_dns_entry *head = NULL;
536 struct srv_dns_entry *srv = NULL, *entry = NULL;
537 krb5_error_code code = 0;
540 * First off, build a query of the form:
542 * service.protocol.realm
544 * which will most likely be something like:
546 * _kerberos._udp.REALM
550 if ( strlen(service) + strlen(protocol) + realm->length + 6
553 sprintf(host, "%s.%s.%.*s", service, protocol, (int) realm->length,
556 /* Realm names don't (normally) end with ".", but if the query
557 doesn't end with "." and doesn't get an answer as is, the
558 resolv code will try appending the local domain. Since the
559 realm names are absolutes, let's stop that.
561 But only if a name has been specified. If we are performing
562 a search on the prefix alone then the intention is to allow
563 the local domain or domain search lists to be expanded. */
565 h = host + strlen (host);
566 if ((h > host) && (h[-1] != '.') && ((h - host + 1) < sizeof(host)))
570 fprintf (stderr, "sending DNS SRV query for %s\n", host);
573 size = res_search(host, C_IN, T_SRV, answer.bytes, sizeof(answer.bytes));
579 * We got an answer! First off, parse the header and figure out how
580 * many answers we got back.
585 numqueries = ntohs(answer.hdr.qdcount);
586 numanswers = ntohs(answer.hdr.ancount);
591 * We need to skip over all of the questions, so we have to iterate
592 * over every query record. dn_expand() is able to tell us the size
593 * of compress DNS names, so we use it.
596 #define INCR_CHECK(x,y) x += y; if (x > size + answer.bytes) goto out
597 #define CHECK(x,y) if (x + y > size + answer.bytes) goto out
598 #define NTOHSP(x,y) x[0] << 8 | x[1]; x += y
600 while (numqueries--) {
601 len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
604 INCR_CHECK(p, len + 4);
608 * We're now pointing at the answer records. Only process them if
609 * they're actually T_SRV records (they might be CNAME records,
612 * But in a DNS reply, if you get a CNAME you always get the associated
613 * "real" RR for that CNAME. RFC 1034, 3.6.2:
615 * CNAME RRs cause special action in DNS software. When a name server
616 * fails to find a desired RR in the resource set associated with the
617 * domain name, it checks to see if the resource set consists of a CNAME
618 * record with a matching class. If so, the name server includes the CNAME
619 * record in the response and restarts the query at the domain name
620 * specified in the data field of the CNAME record. The one exception to
621 * this rule is that queries which match the CNAME type are not restarted.
623 * In other words, CNAMEs do not need to be expanded by the client.
626 while (numanswers--) {
628 /* First is the name; use dn_expand to get the compressed size */
629 len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
634 /* Next is the query type */
638 /* Next is the query class; also skip over 4 byte TTL */
642 /* Record data length */
648 * If this is an SRV record, process it. Record format is:
656 if (class == C_IN && type == T_SRV) {
658 priority = NTOHSP(p,2);
660 weight = NTOHSP(p,2);
663 len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
669 * We got everything! Insert it into our list, but make sure
670 * it's in the right order. Right now we don't do anything
671 * with the weight field
674 srv = (struct srv_dns_entry *) malloc(sizeof(struct srv_dns_entry));
678 srv->priority = priority;
679 srv->weight = weight;
681 srv->host = strdup(host);
683 if (head == NULL || head->priority > srv->priority) {
688 * This is confusing. Only insert an entry into this
690 * The next person has a higher priority (lower priorities
693 * There is no next entry (we're at the end)
695 for (entry = head; entry != NULL; entry = entry->next)
697 entry->next->priority > srv->priority) ||
698 entry->next == NULL) {
699 srv->next = entry->next;
704 INCR_CHECK(p, rdlen);
708 * Okay! Now we've got a linked list of entries sorted by
709 * priority. Start looking up A records and returning
717 fprintf (stderr, "walking answer list:\n");
719 for (entry = head; entry != NULL; entry = entry->next) {
721 fprintf (stderr, "\tport=%d host=%s\n", entry->port, entry->host);
723 code = add_host_to_list (addrlist, entry->host, htons (entry->port), 0);
728 fprintf (stderr, "[end]\n");
731 for (entry = head; entry != NULL; ) {
748 static krb5_error_code
749 krb5_locate_srv_dns(const krb5_data *realm,
750 const char *service, const char *protocol,
753 return krb5_locate_srv_dns_1 (realm, service, protocol, al);
756 #endif /* KRB5_DNS_LOOKUP */
759 * Wrapper function for the two backends
763 krb5int_locate_server (krb5_context context, const krb5_data *realm,
764 struct addrlist *addrlist,
766 const char *profname, const char *dnsname,
768 /* network order port numbers! */
769 int dflport1, int dflport2)
771 krb5_error_code code;
772 struct addrlist al = ADDRLIST_INIT;
777 * We always try the local file first
780 code = krb5_locate_srv_conf_1(context, realm, profname, &al, get_masters,
783 #ifdef KRB5_DNS_LOOKUP
784 if (code && dnsname != 0) {
785 int use_dns = _krb5_use_dns_kdc(context);
787 code = krb5_locate_srv_dns_1(realm, dnsname,
788 is_stream ? "_tcp" : "_udp", &al);
790 #endif /* KRB5_DNS_LOOKUP */
793 fprintf (stderr, "krb5int_locate_server found %d addresses\n",
796 fprintf (stderr, "krb5int_locate_server returning error code %d\n",
804 if (al.naddrs == 0) { /* No good servers */
807 return KRB5_REALM_CANT_RESOLVE;
814 krb5_locate_kdc(krb5_context context, const krb5_data *realm,
815 struct addrlist *addrlist,
818 int udpport, sec_udpport;
820 udpport = get_port (KDC_PORTNAME, 0, KRB5_DEFAULT_PORT);
821 sec_udpport = get_port (KDC_SECONDARY_PORTNAME, 0,
822 (udpport == htons (KRB5_DEFAULT_PORT)
823 ? KRB5_DEFAULT_SEC_PORT
824 : KRB5_DEFAULT_PORT));
825 if (sec_udpport == udpport)
828 return krb5int_locate_server (context, realm, addrlist, get_masters, "kdc",
832 0, udpport, sec_udpport);