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 #define FAI_PREFIX krb5int
48 #define FAI_IMPLEMENTATION
49 #include "fake-addrinfo.h"
51 /* for old Unixes and friends ... */
52 #ifndef MAXHOSTNAMELEN
53 #define MAXHOSTNAMELEN 64
56 #define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1)
58 #if KRB5_DNS_LOOKUP_KDC
59 #define DEFAULT_LOOKUP_KDC 1
61 #define DEFAULT_LOOKUP_KDC 0
63 #if KRB5_DNS_LOOKUP_REALM
64 #define DEFAULT_LOOKUP_REALM 1
66 #define DEFAULT_LOOKUP_REALM 0
70 maybe_use_dns (context, name, defalt)
79 code = profile_get_string(context->profile, "libdefaults",
81 if (value == 0 && code == 0)
82 code = profile_get_string(context->profile, "libdefaults",
83 "dns_fallback", 0, 0, &value);
90 use_dns = _krb5_conf_boolean(value);
91 profile_release_string(value);
96 _krb5_use_dns_kdc(context)
99 return maybe_use_dns (context, "dns_lookup_kdc", DEFAULT_LOOKUP_KDC);
103 _krb5_use_dns_realm(context)
104 krb5_context context;
106 return maybe_use_dns (context, "dns_lookup_realm", DEFAULT_LOOKUP_REALM);
109 #endif /* KRB5_DNS_LOOKUP */
111 static int get_port (const char *service, int stream, int defalt)
113 struct addrinfo hints = { 0 };
117 hints.ai_family = PF_INET;
118 hints.ai_socktype = stream ? SOCK_STREAM : SOCK_DGRAM;
119 err = getaddrinfo (NULL, service, &hints, &ai);
120 if (err == 0 && ai != 0) {
121 if (ai->ai_addr->sa_family == AF_INET) {
122 int port = ((struct sockaddr_in *)ai->ai_addr)->sin_port;
128 /* Any error - don't complain, just use default. */
129 return htons (defalt);
133 grow_list (struct addrlist *lp, int nmore)
136 int newspace = lp->space + nmore;
137 size_t newsize = newspace * sizeof (struct addrlist);
138 struct sockaddr **newaddrs;
140 /* NULL check a concession to SunOS4 compatibility for now; not
141 required for pure ANSI support. */
143 newaddrs = realloc (lp->addrs, newsize);
145 newaddrs = malloc (newsize);
147 if (newaddrs == NULL)
149 for (i = lp->space; i < newspace; i++)
151 lp->addrs = newaddrs;
152 lp->space = newspace;
156 /* Free up everything pointed to by the addrlist structure, but don't
157 free the structure itself. */
159 krb5int_free_addrlist (struct addrlist *lp)
162 for (i = 0; i < lp->naddrs; i++)
166 lp->naddrs = lp->space = 0;
168 #define free_list krb5int_free_addrlist
171 add_sockaddr_to_list (struct addrlist *lp, const struct sockaddr *addr,
174 struct sockaddr *copy;
178 char name[NI_MAXHOST];
180 fprintf (stderr, "\tadding sockaddr family %2d, len %d", addr->sa_family,
183 err = getnameinfo (addr, len, name, sizeof (name), NULL, 0,
184 NI_NUMERICHOST | NI_NUMERICSERV);
186 fprintf (stderr, "\t%s", name);
187 fprintf (stderr, "\n");
190 if (lp->naddrs == lp->space) {
191 err = grow_list (lp, 1);
194 fprintf (stderr, "grow_list failed %d\n", err);
206 memcpy (copy, addr, len);
207 lp->addrs[lp->naddrs++] = copy;
209 fprintf (stderr, "count is now %d\n", lp->naddrs);
214 static int translate_ai_error (int err)
224 /* All of these indicate bad inputs to getaddrinfo. */
227 /* Translate to standard errno code. */
230 /* Translate to standard errno code. */
234 /* Name not known or no address data, but no error. Do
238 /* System error, obviously. */
241 /* An error code we haven't handled? */
246 static int add_addrinfo_to_list (struct addrlist *lp, struct addrinfo *a)
248 return add_sockaddr_to_list (lp, a->ai_addr, a->ai_addrlen);
251 static void set_port_num (struct sockaddr *addr, int num)
253 switch (addr->sa_family) {
255 ((struct sockaddr_in *)addr)->sin_port = num;
257 #ifdef KRB5_USE_INET6
259 ((struct sockaddr_in6 *)addr)->sin6_port = num;
266 add_host_to_list (struct addrlist *lp, const char *hostname,
267 int port, int secport)
269 struct addrinfo *addrs, *a;
270 /* Must set err to 0 for the case we return err without ever
271 setting it -- !HAVE_GETADDRINFO and !hp */
275 fprintf (stderr, "adding hostname %s, ports %d,%d\n", hostname,
276 ntohs (port), ntohs (secport));
279 err = getaddrinfo (hostname, NULL, NULL, &addrs);
281 return translate_ai_error (err);
282 for (a = addrs; a; a = a->ai_next) {
283 set_port_num (a->ai_addr, port);
284 err = add_addrinfo_to_list (lp, a);
291 set_port_num (a->ai_addr, secport);
292 err = add_addrinfo_to_list (lp, a);
296 freeaddrinfo (addrs);
301 * returns count of number of addresses found
302 * if master is non-NULL, it is filled in with the index of
306 static krb5_error_code
307 krb5_locate_srv_conf_1(krb5_context context, const krb5_data *realm,
308 const char * name, struct addrlist *addrlist,
309 int get_masters, int udpport, int sec_udpport)
311 const char *realm_srv_names[4];
312 char **masterlist, **hostlist, *host, *port, *cp;
313 krb5_error_code code;
314 int i, j, count, ismaster;
318 "looking in krb5.conf for realm %s entry %s; ports %d,%d\n",
319 realm->data, name, ntohs (udpport), ntohs (sec_udpport));
322 if ((host = malloc(realm->length + 1)) == NULL)
325 strncpy(host, realm->data, realm->length);
326 host[realm->length] = '\0';
331 realm_srv_names[0] = "realms";
332 realm_srv_names[1] = host;
333 realm_srv_names[2] = name;
334 realm_srv_names[3] = 0;
336 code = profile_get_values(context->profile, realm_srv_names, &hostlist);
339 if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
340 code = KRB5_REALM_UNKNOWN;
346 while (hostlist && hostlist[count])
350 profile_free_list(hostlist);
352 addrlist->naddrs = 0;
357 realm_srv_names[0] = "realms";
358 realm_srv_names[1] = host;
359 realm_srv_names[2] = "admin_server";
360 realm_srv_names[3] = 0;
362 code = profile_get_values(context->profile, realm_srv_names,
368 for (i=0; masterlist[i]; i++) {
369 host = masterlist[i];
372 * Strip off excess whitespace
374 cp = strchr(host, ' ');
377 cp = strchr(host, '\t');
380 cp = strchr(host, ':');
389 /* at this point, if master is non-NULL, then either the master kdc
390 is required, and there is one, or the master kdc is not required,
391 and there may or may not be one. */
393 #ifdef HAVE_NETINET_IN_H
398 for (i=0; hostlist[i]; i++) {
403 * Strip off excess whitespace
405 cp = strchr(host, ' ');
408 cp = strchr(host, '\t');
411 port = strchr(host, ':');
419 for (j=0; masterlist[j]; j++) {
420 if (strcasecmp(hostlist[i], masterlist[j]) == 0) {
426 if (get_masters && !ismaster)
433 l = strtoul (port, &endptr, 10);
434 if (endptr == NULL || *endptr != 0)
439 /* L is unsigned, don't need to check <0. */
449 code = add_host_to_list (addrlist, hostlist[i], p1, p2);
452 fprintf (stderr, "error %d returned from add_host_to_list\n", code);
455 profile_free_list (hostlist);
457 profile_free_list (masterlist);
463 profile_free_list(hostlist);
465 profile_free_list(masterlist);
471 static krb5_error_code
472 krb5_locate_srv_conf(context, realm, name, al, get_masters,
473 udpport, sec_udpport)
474 krb5_context context;
475 const krb5_data *realm;
479 int udpport, sec_udpport;
483 ret = krb5_locate_srv_conf_1 (context, realm, name, al,
484 get_masters, udpport, sec_udpport);
487 if (al->naddrs == 0) /* Couldn't resolve any KDC names */
488 return KRB5_REALM_CANT_RESOLVE;
493 #ifdef KRB5_DNS_LOOKUP
496 * Lookup a KDC via DNS SRV records
499 static krb5_error_code
500 krb5_locate_srv_dns_1 (const krb5_data *realm,
502 const char *protocol,
503 struct addrlist *addrlist)
506 unsigned char bytes[2048];
509 unsigned char *p=NULL;
510 char host[MAX_DNS_NAMELEN], *h;
512 int priority, weight, size, len, numanswers, numqueries, rdlen;
514 const int hdrsize = sizeof(HEADER);
515 struct srv_dns_entry {
516 struct srv_dns_entry *next;
523 struct srv_dns_entry *head = NULL;
524 struct srv_dns_entry *srv = NULL, *entry = NULL;
525 krb5_error_code code = 0;
528 * First off, build a query of the form:
530 * service.protocol.realm
532 * which will most likely be something like:
534 * _kerberos._udp.REALM
538 if ( strlen(service) + strlen(protocol) + realm->length + 6
541 sprintf(host, "%s.%s.%.*s", service, protocol, (int) realm->length,
544 /* Realm names don't (normally) end with ".", but if the query
545 doesn't end with "." and doesn't get an answer as is, the
546 resolv code will try appending the local domain. Since the
547 realm names are absolutes, let's stop that.
549 But only if a name has been specified. If we are performing
550 a search on the prefix alone then the intention is to allow
551 the local domain or domain search lists to be expanded. */
553 h = host + strlen (host);
554 if ((h > host) && (h[-1] != '.') && ((h - host + 1) < sizeof(host)))
558 fprintf (stderr, "sending DNS SRV query for %s\n", host);
561 size = res_search(host, C_IN, T_SRV, answer.bytes, sizeof(answer.bytes));
567 * We got an answer! First off, parse the header and figure out how
568 * many answers we got back.
573 numqueries = ntohs(answer.hdr.qdcount);
574 numanswers = ntohs(answer.hdr.ancount);
579 * We need to skip over all of the questions, so we have to iterate
580 * over every query record. dn_expand() is able to tell us the size
581 * of compress DNS names, so we use it.
584 #define INCR_CHECK(x,y) x += y; if (x > size + answer.bytes) goto out
585 #define CHECK(x,y) if (x + y > size + answer.bytes) goto out
586 #define NTOHSP(x,y) x[0] << 8 | x[1]; x += y
588 while (numqueries--) {
589 len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
592 INCR_CHECK(p, len + 4);
596 * We're now pointing at the answer records. Only process them if
597 * they're actually T_SRV records (they might be CNAME records,
600 * But in a DNS reply, if you get a CNAME you always get the associated
601 * "real" RR for that CNAME. RFC 1034, 3.6.2:
603 * CNAME RRs cause special action in DNS software. When a name server
604 * fails to find a desired RR in the resource set associated with the
605 * domain name, it checks to see if the resource set consists of a CNAME
606 * record with a matching class. If so, the name server includes the CNAME
607 * record in the response and restarts the query at the domain name
608 * specified in the data field of the CNAME record. The one exception to
609 * this rule is that queries which match the CNAME type are not restarted.
611 * In other words, CNAMEs do not need to be expanded by the client.
614 while (numanswers--) {
616 /* First is the name; use dn_expand to get the compressed size */
617 len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
622 /* Next is the query type */
626 /* Next is the query class; also skip over 4 byte TTL */
630 /* Record data length */
636 * If this is an SRV record, process it. Record format is:
644 if (class == C_IN && type == T_SRV) {
646 priority = NTOHSP(p,2);
648 weight = NTOHSP(p,2);
651 len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
657 * We got everything! Insert it into our list, but make sure
658 * it's in the right order. Right now we don't do anything
659 * with the weight field
662 srv = (struct srv_dns_entry *) malloc(sizeof(struct srv_dns_entry));
666 srv->priority = priority;
667 srv->weight = weight;
669 srv->host = strdup(host);
671 if (head == NULL || head->priority > srv->priority) {
676 * This is confusing. Only insert an entry into this
678 * The next person has a higher priority (lower priorities
681 * There is no next entry (we're at the end)
683 for (entry = head; entry != NULL; entry = entry->next)
685 entry->next->priority > srv->priority) ||
686 entry->next == NULL) {
687 srv->next = entry->next;
692 INCR_CHECK(p, rdlen);
696 * Okay! Now we've got a linked list of entries sorted by
697 * priority. Start looking up A records and returning
705 fprintf (stderr, "walking answer list:\n");
707 for (entry = head; entry != NULL; entry = entry->next) {
709 fprintf (stderr, "\tport=%d host=%s\n", entry->port, entry->host);
711 code = add_host_to_list (addrlist, entry->host, htons (entry->port), 0);
716 fprintf (stderr, "[end]\n");
719 for (entry = head; entry != NULL; ) {
736 static krb5_error_code
737 krb5_locate_srv_dns(const krb5_data *realm,
738 const char *service, const char *protocol,
741 return krb5_locate_srv_dns_1 (realm, service, protocol, al);
744 #endif /* KRB5_DNS_LOOKUP */
747 * Wrapper function for the two backends
751 krb5int_locate_server (krb5_context context, const krb5_data *realm,
752 struct addrlist *addrlist,
754 const char *profname, const char *dnsname,
756 /* network order port numbers! */
757 int dflport1, int dflport2)
759 krb5_error_code code;
760 struct addrlist al = ADDRLIST_INIT;
765 * We always try the local file first
768 code = krb5_locate_srv_conf_1(context, realm, profname, &al, get_masters,
771 #ifdef KRB5_DNS_LOOKUP
772 if (code && dnsname != 0) {
773 int use_dns = _krb5_use_dns_kdc(context);
775 code = krb5_locate_srv_dns_1(realm, dnsname,
776 is_stream ? "_tcp" : "_udp", &al);
778 #endif /* KRB5_DNS_LOOKUP */
781 fprintf (stderr, "krb5int_locate_server found %d addresses\n",
784 fprintf (stderr, "krb5int_locate_server returning error code %d\n",
792 if (al.naddrs == 0) { /* No good servers */
795 return KRB5_REALM_CANT_RESOLVE;
802 krb5_locate_kdc(krb5_context context, const krb5_data *realm,
803 struct addrlist *addrlist,
806 int udpport, sec_udpport;
808 udpport = get_port (KDC_PORTNAME, 0, KRB5_DEFAULT_PORT);
809 sec_udpport = get_port (KDC_SECONDARY_PORTNAME, 0,
810 (udpport == htons (KRB5_DEFAULT_PORT)
811 ? KRB5_DEFAULT_SEC_PORT
812 : KRB5_DEFAULT_PORT));
813 if (sec_udpport == udpport)
816 return krb5int_locate_server (context, realm, addrlist, get_masters, "kdc",
820 0, udpport, sec_udpport);