// DNS support -- return information about LDAP servers, // by looking up a SRV record. // // SRV records, as discussed in RFC 2782, point to locations of // services. In the words of that RFC, // // If a SRV-cognizant LDAP client wants to discover a LDAP server that // supports TCP protocol and provides LDAP service for the domain // example.com., it does a lookup of // // _ldap._tcp.example.com // // The get_sorted_srv_records() function below will take a domain example.com, // prepend the "_ldap._tcp", // do the lookup to obtain the SRV record, // and return a string containing the hosts and ports, // sorted as described in the RFC. // // It's unexpectedly hard to find clear documentation about DNS lookups, // but the following does seem to be portable, // though without much in the way of intelligible manpages on any platform. // // See: // Scrappy documentation: https://docstore.mik.ua/orelly/networking_2ndEd/dns/ch15_02.htm // Wikipedia: https://en.wikipedia.org/wiki/List_of_DNS_record_types // RFC 1035, Domain names -- implementation and specification // // This module exposes a function // // char* get_sorted_srv_records(const char* domain); // // which returns a space-separated list of ldap:// URIs obtained from // the SRV record corresponding to the given domain, in a random order // appropriate to the weights obtained. // // This can be compiled as a standalone (test?) program; see STANDALONE below. // CentOS features.h requires _BSD_SOURCE in order to get types.h to define u_char, // which resolv.h refers to. #define _BSD_SOURCE 1 // Debian prefers _DEFAULT_SOURCE, and issues a deprecation warning // if _BSD_SOURCE is defined and _DEFAULT_SOURCE isn't. #define _DEFAULT_SOURCE 1 // ... and _XOPEN_SOURCE to get getopt // (on eg CentOS, this also happens with _POSIX_C_SOURCE >= 2, but // that _prevents_ macOS types.h from defining u_char). #define _XOPEN_SOURCE 1 #include #include #include #include #include #include "config.h" // See the docs for autoconf AC_HEADER_RESOLV #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_NETINET_IN_H # include /* inet_ functions / structs */ #endif #ifdef HAVE_ARPA_NAMESER_H # include /* DNS HEADER struct */ #endif #ifdef HAVE_NETDB_H # include #endif #include // There is a test main program at the bottom of this file. // To compile: // cc -Wall -std=c99 -o dns-support -DSTANDALONE=1 -lresolv dns-support.c // #ifndef STANDALONE #define STANDALONE 0 #endif #if STANDALONE #include #include // dummy out functions provided by the other modules in this kit static int _chatter = 2; int get_verbosity(void) { return _chatter; } void change_verbosity(int inc) { _chatter += inc; } void log_err_message(void* dummy, const char* fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } #else // we call get_verbosity and log_err_message #include "ldaputil.h" #include "ldap-config.h" #endif typedef unsigned char byte; // Return a uniform random deviate in [0,1) // // random(3) returns a number in [0, 2^31-1]. // We convert this to a double by dividing it by 2^31 exactly. // Thus the return value is always strictly less than one. static double random_f(void) { static byte initialised_p = 0; static double denom; if (! initialised_p) { srandom(time(NULL)); denom = ldexp(1, 31); initialised_p = 1; // Explore a few properties of 'denom' // int exp; // double mantissa = frexp(denom, &exp); // printf("%g -> %f.2^%d; ", denom, mantissa, exp); // double two31 = (double)2147483647; // 2^31-1 as an integer // mantissa = frexp(two31, &exp); // printf("%g -> %f.2^%d; ", two31, mantissa, exp); // printf("ratio = %g, less than one? %s\n", // two31/denom, two31/denom < 1.0 ? "yes" : "no"); } double univariate = (double)random(); return univariate / denom; } // Uncompress a domain-name. // The returned pointer points to storage which is reused on each invocation. static const char* uncompress(ns_msg msg, const byte* p, int* skiplen_p) { static char ans[NS_MAXDNAME]; int skiplen = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg), p, ans, sizeof(ans)); if (skiplen_p != NULL) *skiplen_p = skiplen; return (skiplen >= 0 ? ans : NULL); } typedef struct srv_result_s { char host[NS_MAXDNAME+1]; unsigned int port; unsigned int priority; float order; } srv_result; static int srv_result_compar(const void* a_v, const void* b_v) { srv_result* a = (srv_result*)a_v; srv_result* b = (srv_result*)b_v; if (a->priority == b->priority) { return a->order < b->order ? -1 : +1; } else { return a->priority - b->priority; } } /* * For a given domain, retrieve a sequence of SRV records, * sort them, and display the result. * * RFC 2782 * * The SRV record is defined (textually) to be (priority, weight, * port, domain-name), where the first three are * two-byte integers. The domain-name is not to be compressed, * according to the RFC, 'unless and until permitted by future * standards action', but uncompress doesn't seem to mind, and it * seems future-proof. * * See the RFC for other observations on how best to use SRV * records, as both a server and as a client. The RFC says, * of the host selection algorithm: * * Priority * The priority of this target host. A client MUST attempt to * contact the target host with the lowest-numbered priority it can * reach; target hosts with the same priority SHOULD be tried in an * order defined by the weight field. The range is 0-65535. This * is a 16 bit unsigned integer in network byte order. * * Weight * A server selection mechanism. The weight field specifies a * relative weight for entries with the same priority. Larger * weights SHOULD be given a proportionately higher probability of * being selected. The range of this number is 0-65535. This is a * 16 bit unsigned integer in network byte order. Domain * administrators SHOULD use Weight 0 when there isn't any server * selection to do, to make the RR easier to read for humans (less * noisy). In the presence of records containing weights greater * than 0, records with weight 0 should have a very small chance of * being selected. * * In the absence of a protocol whose specification calls for the * use of other weighting information, a client arranges the SRV * RRs of the same Priority in the order in which target hosts, * specified by the SRV RRs, will be contacted. * * The algorithm there is rather cumbersome. * * Instead, for each host, i, record the priority and an ordering * parameter which is drawn from an exponential distribution with * parameter weight_i (which we can obtain with * -log(1-uniform())/weight_i, where uniform() is a uniform random * deviate in [0,1)). The minimum of an ensemble of such random * variables is item i, with probability weight_i/(sum_j(weight_j)). * See eg . * * With this done, sort the records. The comparator function orders * host with lower priority first, and when two hosts have equal * priority, it orders first the one with the lower ordering parameter. */ char* get_sorted_srv_records(const char* domain) { byte buf[NS_PACKETSZ]; size_t retlen = 0; // length of return string int chatter = get_verbosity(); char ldapdomain[NS_MAXDNAME+1]; snprintf(ldapdomain, NS_MAXDNAME, "_ldap._tcp.%s", domain); ldapdomain[NS_MAXDNAME] = '\0'; // just in case errno = 0; int res_len = res_query(ldapdomain, ns_c_in, ns_t_srv, buf, sizeof(buf)); if (res_len < 0) { if (errno != 0) { log_err_message(NULL, "res_query: error %s\n", strerror(errno)); } return NULL; } ns_msg msg; ns_rr rr; ns_initparse(buf, res_len, &msg); int nmsg = ns_msg_count(msg, ns_s_an); srv_result* srv = (srv_result*)malloc(nmsg * sizeof(srv_result)); if (srv == NULL) { log_err_message(NULL, "Unable to allocate %zu\n", nmsg * sizeof(srv_result)); return NULL; } if (chatter > 2) fprintf(stderr, "SRV for %s:\n", domain); for (int recnum=0; recnum 2) { fprintf(stderr, "%30s:%d\t%d %d -> %f\n", srv[recnum].host, srv[recnum].port, srv[recnum].priority, weight, srv[recnum].order); } } else { log_err_message(NULL, "failed to uncompress domainname! %s\n", strerror(errno)); return NULL; } } qsort(srv, nmsg, sizeof(srv_result), srv_result_compar); char* rbuf = (char*)malloc(retlen); if (rbuf == NULL) { log_err_message(NULL, "Can't allocate %zd!\n", retlen); return NULL; } if (chatter > 2) { fprintf(stderr, "Order:\n"); for (int recnum=0; recnum0; argc--, argv++) { if (**argv == '-') { while (*++*argv) { switch (**argv) { case 'v': change_verbosity(+1); break; default: Usage(); } } } else { if (domain) Usage(); domain = *argv; } } if (domain == NULL) Usage(); char* ldap_search_list = get_sorted_srv_records(domain); printf("%s\n", ldap_search_list); exit(0); } #endif