2 * lib/krb5/os/locate_kdc.c
4 * Copyright 1990,2000,2001,2002 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.
31 #include "fake-addrinfo.h"
35 #ifdef KRB5_DNS_LOOKUP
39 #include <arpa/inet.h>
40 #include <arpa/nameser.h>
48 /* for old Unixes and friends ... */
49 #ifndef MAXHOSTNAMELEN
50 #define MAXHOSTNAMELEN 64
53 #define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1)
55 #if KRB5_DNS_LOOKUP_KDC
56 #define DEFAULT_LOOKUP_KDC 1
58 #define DEFAULT_LOOKUP_KDC 0
60 #if KRB5_DNS_LOOKUP_REALM
61 #define DEFAULT_LOOKUP_REALM 1
63 #define DEFAULT_LOOKUP_REALM 0
67 maybe_use_dns (krb5_context context, const char *name, int defalt)
73 code = profile_get_string(context->profile, "libdefaults",
75 if (value == 0 && code == 0)
76 code = profile_get_string(context->profile, "libdefaults",
77 "dns_fallback", 0, 0, &value);
84 use_dns = _krb5_conf_boolean(value);
85 profile_release_string(value);
90 _krb5_use_dns_kdc(krb5_context context)
92 return maybe_use_dns (context, "dns_lookup_kdc", DEFAULT_LOOKUP_KDC);
96 _krb5_use_dns_realm(krb5_context context)
98 return maybe_use_dns (context, "dns_lookup_realm", DEFAULT_LOOKUP_REALM);
101 #endif /* KRB5_DNS_LOOKUP */
103 static int get_port (const char *service, int stream, int defalt)
105 #if 0 /* Only used for "kerberos" and "kerberos-sec", and we want the
106 right port numbers even on the OSes that botch the entries in
107 /etc/services. So don't bother with the lookup, except maybe
108 to produce a warning. */
109 struct addrinfo hints = { 0 };
113 hints.ai_family = PF_INET;
114 hints.ai_socktype = stream ? SOCK_STREAM : SOCK_DGRAM;
115 err = getaddrinfo (NULL, service, &hints, &ai);
116 if (err == 0 && ai != 0) {
117 if (ai->ai_addr->sa_family == AF_INET) {
118 int port = ((struct sockaddr_in *)ai->ai_addr)->sin_port;
125 /* Any error - don't complain, just use default. */
126 return htons (defalt);
130 krb5int_grow_addrlist (struct addrlist *lp, int nmore)
133 int newspace = lp->space + nmore;
134 size_t newsize = newspace * sizeof (struct addrlist);
135 struct addrinfo **newaddrs;
137 /* NULL check a concession to SunOS4 compatibility for now; not
138 required for pure ANSI support. */
140 newaddrs = realloc (lp->addrs, newsize);
142 newaddrs = malloc (newsize);
144 if (newaddrs == NULL)
146 for (i = lp->space; i < newspace; i++)
148 lp->addrs = newaddrs;
149 lp->space = newspace;
152 #define grow_list krb5int_grow_addrlist
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++)
161 freeaddrinfo (lp->addrs[i]);
164 lp->naddrs = lp->space = 0;
166 #define free_list krb5int_free_addrlist
168 static int translate_ai_error (int err)
177 /* All of these indicate bad inputs to getaddrinfo. */
180 /* Translate to standard errno code. */
183 /* Translate to standard errno code. */
185 #ifdef EAI_ADDRFAMILY
188 #if EAI_NODATA != EAI_NONAME
192 /* Name not known or no address data, but no error. Do
197 /* System error, obviously. */
201 /* An error code we haven't handled? */
206 static int add_addrinfo_to_list (struct addrlist *lp, struct addrinfo *a)
211 switch (a->ai_socktype) {
213 fprintf(stderr, "\tdgram\n");
216 fprintf(stderr, "\tstream\n");
219 fprintf(stderr, "\traw\n");
224 fprintf(stderr, "\tsocket type %d\n", a->ai_socktype);
229 if (lp->naddrs == lp->space) {
230 err = grow_list (lp, 1);
233 fprintf (stderr, "grow_list failed %d\n", err);
238 lp->addrs[lp->naddrs++] = a;
241 fprintf (stderr, "count is now %d\n", lp->naddrs);
246 #define add_host_to_list krb5int_add_host_to_list
249 krb5int_add_host_to_list (struct addrlist *lp, const char *hostname,
250 int port, int secport,
251 int socktype, int family)
253 struct addrinfo *addrs, *a, *anext, hint;
255 char portbuf[10], secportbuf[10];
258 fprintf (stderr, "adding hostname %s, ports %d,%d\n", hostname,
259 ntohs (port), ntohs (secport));
262 memset(&hint, 0, sizeof(hint));
263 hint.ai_family = family;
264 hint.ai_socktype = socktype;
265 sprintf(portbuf, "%d", ntohs(port));
266 sprintf(secportbuf, "%d", ntohs(secport));
267 err = getaddrinfo (hostname, portbuf, &hint, &addrs);
269 return translate_ai_error (err);
271 for (a = addrs; a != 0 && err == 0; a = anext) {
273 err = add_addrinfo_to_list (lp, a);
275 if (err || secport == 0)
278 socktype = SOCK_DGRAM;
279 else if (socktype != SOCK_DGRAM)
281 hint.ai_family = AF_INET;
282 err = getaddrinfo (hostname, secportbuf, &hint, &addrs);
284 err = translate_ai_error (err);
287 for (a = addrs; a != 0 && err == 0; a = anext) {
289 err = add_addrinfo_to_list (lp, a);
293 freeaddrinfo (anext);
298 * returns count of number of addresses found
299 * if master is non-NULL, it is filled in with the index of
303 static krb5_error_code
304 krb5_locate_srv_conf_1(krb5_context context, const krb5_data *realm,
305 const char * name, struct addrlist *addrlist,
306 int get_masters, int socktype,
307 int udpport, int sec_udpport, int family)
309 const char *realm_srv_names[4];
310 char **masterlist, **hostlist, *host, *port, *cp;
311 krb5_error_code code;
312 int i, j, count, ismaster;
316 "looking in krb5.conf for realm %s entry %s; ports %d,%d\n",
317 realm->data, name, ntohs (udpport), ntohs (sec_udpport));
320 if ((host = malloc(realm->length + 1)) == NULL)
323 strncpy(host, realm->data, realm->length);
324 host[realm->length] = '\0';
329 realm_srv_names[0] = "realms";
330 realm_srv_names[1] = host;
331 realm_srv_names[2] = name;
332 realm_srv_names[3] = 0;
334 code = profile_get_values(context->profile, realm_srv_names, &hostlist);
338 fprintf (stderr, "config file lookup failed: %s\n",
339 error_message(code));
341 if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
342 code = KRB5_REALM_UNKNOWN;
348 while (hostlist && hostlist[count])
351 fprintf (stderr, "found %d entries under 'kdc'\n", count);
355 profile_free_list(hostlist);
357 addrlist->naddrs = 0;
362 realm_srv_names[0] = "realms";
363 realm_srv_names[1] = host;
364 realm_srv_names[2] = "admin_server";
365 realm_srv_names[3] = 0;
367 code = profile_get_values(context->profile, realm_srv_names,
373 for (i=0; masterlist[i]; i++) {
374 host = masterlist[i];
377 * Strip off excess whitespace
379 cp = strchr(host, ' ');
382 cp = strchr(host, '\t');
385 cp = strchr(host, ':');
394 /* at this point, if master is non-NULL, then either the master kdc
395 is required, and there is one, or the master kdc is not required,
396 and there may or may not be one. */
398 #ifdef HAVE_NETINET_IN_H
403 for (i=0; hostlist[i]; i++) {
408 fprintf (stderr, "entry %d is '%s'\n", i, host);
411 * Strip off excess whitespace
413 cp = strchr(host, ' ');
416 cp = strchr(host, '\t');
419 port = strchr(host, ':');
427 for (j=0; masterlist[j]; j++) {
428 if (strcasecmp(hostlist[i], masterlist[j]) == 0) {
434 if (get_masters && !ismaster)
441 l = strtoul (port, &endptr, 10);
442 if (endptr == NULL || *endptr != 0)
447 /* L is unsigned, don't need to check <0. */
458 code = add_host_to_list (addrlist, hostlist[i], p1, p2,
461 code = add_host_to_list (addrlist, hostlist[i], p1, p2,
464 code = add_host_to_list (addrlist, hostlist[i], p1, p2,
465 SOCK_STREAM, family);
469 fprintf (stderr, "error %d returned from add_host_to_list\n", code);
472 profile_free_list (hostlist);
474 profile_free_list (masterlist);
480 profile_free_list(hostlist);
482 profile_free_list(masterlist);
488 static krb5_error_code
489 krb5_locate_srv_conf(krb5_context context, const krb5_data *realm,
490 const char *name, struct addrlist *al, int get_masters,
491 int udpport, int sec_udpport)
495 ret = krb5_locate_srv_conf_1 (context, realm, name, al,
496 get_masters, 0, udpport, sec_udpport, 0);
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 struct srv_dns_entry {
512 struct srv_dns_entry *next;
519 /* Do DNS SRV query, return results in *answers.
521 Make best effort to return all the data we can. On memory or
522 decoding errors, just return what we've got. Always return 0,
524 static krb5_error_code
525 make_srv_query_realm(const krb5_data *realm,
527 const char *protocol,
528 struct srv_dns_entry **answers)
531 unsigned char bytes[2048];
534 unsigned char *p=NULL;
535 char host[MAX_DNS_NAMELEN], *h;
537 int priority, weight, size, len, numanswers, numqueries, rdlen;
539 const int hdrsize = sizeof(HEADER);
541 struct srv_dns_entry *head = NULL;
542 struct srv_dns_entry *srv = NULL, *entry = NULL;
545 * First off, build a query of the form:
547 * service.protocol.realm
549 * which will most likely be something like:
551 * _kerberos._udp.REALM
555 if (memchr(realm->data, 0, realm->length))
557 if ( strlen(service) + strlen(protocol) + realm->length + 6
560 sprintf(host, "%s.%s.%.*s", service, protocol, (int) realm->length,
563 /* Realm names don't (normally) end with ".", but if the query
564 doesn't end with "." and doesn't get an answer as is, the
565 resolv code will try appending the local domain. Since the
566 realm names are absolutes, let's stop that.
568 But only if a name has been specified. If we are performing
569 a search on the prefix alone then the intention is to allow
570 the local domain or domain search lists to be expanded. */
572 h = host + strlen (host);
573 if ((h[-1] != '.') && ((h - host + 1) < sizeof(host)))
577 fprintf (stderr, "sending DNS SRV query for %s\n", host);
580 size = res_search(host, C_IN, T_SRV, answer.bytes, sizeof(answer.bytes));
582 if ((size < hdrsize) || (size > sizeof(answer.bytes)))
586 * We got an answer! First off, parse the header and figure out how
587 * many answers we got back.
592 numqueries = ntohs(answer.hdr.qdcount);
593 numanswers = ntohs(answer.hdr.ancount);
598 * We need to skip over all of the questions, so we have to iterate
599 * over every query record. dn_expand() is able to tell us the size
600 * of compress DNS names, so we use it.
603 #define INCR_CHECK(x,y) x += y; if (x > size + answer.bytes) goto out
604 #define CHECK(x,y) if (x + y > size + answer.bytes) goto out
605 #define NTOHSP(x,y) x[0] << 8 | x[1]; x += y
607 while (numqueries--) {
608 len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
611 INCR_CHECK(p, len + 4);
615 * We're now pointing at the answer records. Only process them if
616 * they're actually T_SRV records (they might be CNAME records,
619 * But in a DNS reply, if you get a CNAME you always get the associated
620 * "real" RR for that CNAME. RFC 1034, 3.6.2:
622 * CNAME RRs cause special action in DNS software. When a name server
623 * fails to find a desired RR in the resource set associated with the
624 * domain name, it checks to see if the resource set consists of a CNAME
625 * record with a matching class. If so, the name server includes the CNAME
626 * record in the response and restarts the query at the domain name
627 * specified in the data field of the CNAME record. The one exception to
628 * this rule is that queries which match the CNAME type are not restarted.
630 * In other words, CNAMEs do not need to be expanded by the client.
633 while (numanswers--) {
635 /* First is the name; use dn_expand to get the compressed size */
636 len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
641 /* Next is the query type */
645 /* Next is the query class; also skip over 4 byte TTL */
647 rrclass = NTOHSP(p,6);
649 /* Record data length */
655 * If this is an SRV record, process it. Record format is:
663 if (rrclass == C_IN && type == T_SRV) {
665 priority = NTOHSP(p,2);
667 weight = NTOHSP(p,2);
670 len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
676 * We got everything! Insert it into our list, but make sure
677 * it's in the right order. Right now we don't do anything
678 * with the weight field
681 srv = (struct srv_dns_entry *) malloc(sizeof(struct srv_dns_entry));
685 srv->priority = priority;
686 srv->weight = weight;
688 srv->host = strdup(host);
689 if (srv->host == NULL) {
694 if (head == NULL || head->priority > srv->priority) {
699 * This is confusing. Only insert an entry into this
701 * The next person has a higher priority (lower priorities
704 * There is no next entry (we're at the end)
706 for (entry = head; entry != NULL; entry = entry->next)
708 entry->next->priority > srv->priority) ||
709 entry->next == NULL) {
710 srv->next = entry->next;
715 INCR_CHECK(p, rdlen);
723 static krb5_error_code
724 krb5_locate_srv_dns_1 (const krb5_data *realm,
726 const char *protocol,
727 struct addrlist *addrlist,
730 struct srv_dns_entry *head = NULL;
731 struct srv_dns_entry *entry = NULL, *next;
732 krb5_error_code code = 0;
734 code = make_srv_query_realm(realm, service, protocol, &head);
739 * Okay! Now we've got a linked list of entries sorted by
740 * priority. Start looking up A records and returning
747 /* Check for the "." case indicating no support. */
748 if (head->next == 0 && head->host[0] == 0) {
751 return KRB5_ERR_NO_SERVICE;
755 fprintf (stderr, "walking answer list:\n");
757 for (entry = head; entry != NULL; entry = next) {
759 fprintf (stderr, "\tport=%d host=%s\n", entry->port, entry->host);
762 code = add_host_to_list (addrlist, entry->host, htons (entry->port), 0,
763 (strcmp("_tcp", protocol)
765 : SOCK_STREAM), family);
776 fprintf (stderr, "[end]\n");
779 for (entry = head; entry != NULL; ) {
780 struct srv_dns_entry *srv;
790 #endif /* KRB5_DNS_LOOKUP */
793 * Wrapper function for the two backends
797 krb5int_locate_server (krb5_context context, const krb5_data *realm,
798 struct addrlist *addrlist,
800 const char *profname, const char *dnsname,
802 /* network order port numbers! */
803 int dflport1, int dflport2,
806 krb5_error_code code;
807 struct addrlist al = ADDRLIST_INIT;
812 * We always try the local file first
815 code = krb5_locate_srv_conf_1(context, realm, profname, &al, get_masters,
816 socktype, dflport1, dflport2, family);
818 #ifdef KRB5_DNS_LOOKUP
819 if (code && dnsname != 0) {
820 int use_dns = _krb5_use_dns_kdc(context);
823 if (socktype == SOCK_DGRAM || socktype == 0) {
824 code = krb5_locate_srv_dns_1(realm, dnsname, "_udp",
828 fprintf(stderr, "dns udp lookup returned error %d\n",
832 if ((socktype == SOCK_STREAM || socktype == 0) && code == 0) {
833 code = krb5_locate_srv_dns_1(realm, dnsname, "_tcp",
837 fprintf(stderr, "dns tcp lookup returned error %d\n",
843 #endif /* KRB5_DNS_LOOKUP */
846 fprintf (stderr, "krb5int_locate_server found %d addresses\n",
849 fprintf (stderr, "krb5int_locate_server returning error code %d\n",
857 if (al.naddrs == 0) { /* No good servers */
860 return KRB5_REALM_CANT_RESOLVE;
867 krb5_locate_kdc(krb5_context context, const krb5_data *realm,
868 struct addrlist *addrlist,
869 int get_masters, int socktype, int family)
871 int udpport, sec_udpport;
873 udpport = get_port (KDC_PORTNAME, 0, KRB5_DEFAULT_PORT);
874 if (socktype == SOCK_STREAM)
877 sec_udpport = get_port (KDC_SECONDARY_PORTNAME, 0,
878 (udpport == htons (KRB5_DEFAULT_PORT)
879 ? KRB5_DEFAULT_SEC_PORT
880 : KRB5_DEFAULT_PORT));
881 if (sec_udpport == udpport)
885 return krb5int_locate_server(context, realm, addrlist, get_masters, "kdc",
889 socktype, udpport, sec_udpport, family);