summaryrefslogtreecommitdiff
path: root/usr/src/lib/libadutils/common/srv_query.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/libadutils/common/srv_query.c')
-rw-r--r--usr/src/lib/libadutils/common/srv_query.c612
1 files changed, 612 insertions, 0 deletions
diff --git a/usr/src/lib/libadutils/common/srv_query.c b/usr/src/lib/libadutils/common/srv_query.c
new file mode 100644
index 0000000000..aa7a833421
--- /dev/null
+++ b/usr/src/lib/libadutils/common/srv_query.c
@@ -0,0 +1,612 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * DNS query helper functions for addisc.c
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+#include <ldap.h>
+#include <sasl/sasl.h>
+#include <sys/u8_textprep.h>
+#include <syslog.h>
+#include <uuid/uuid.h>
+#include <ads/dsgetdc.h>
+#include "adutils_impl.h"
+#include "addisc_impl.h"
+
+static void save_addr(ad_disc_cds_t *, sa_family_t, uchar_t *, size_t);
+static struct addrinfo *make_addrinfo(sa_family_t, uchar_t *, size_t);
+
+static void do_getaddrinfo(ad_disc_cds_t *);
+static ad_disc_cds_t *srv_parse(uchar_t *, int, int *, int *);
+static void add_preferred(ad_disc_cds_t *, ad_disc_ds_t *, int *, int);
+static void get_addresses(ad_disc_cds_t *, int);
+
+/*
+ * Simplified version of srv_query() for domain auto-discovery.
+ */
+int
+srv_getdom(res_state state, const char *svc_name, char **rrname)
+{
+ union {
+ HEADER hdr;
+ uchar_t buf[NS_MAXMSG];
+ } msg;
+ int len, qdcount, ancount;
+ uchar_t *ptr, *eom;
+ char namebuf[NS_MAXDNAME];
+
+ /* query necessary resource records */
+
+ *rrname = NULL;
+ if (DBG(DNS, 1)) {
+ logger(LOG_DEBUG, "Looking for SRV RRs '%s.*'", svc_name);
+ }
+ len = res_nsearch(state, svc_name, C_IN, T_SRV,
+ msg.buf, sizeof (msg.buf));
+ if (len < 0) {
+ if (DBG(DNS, 0)) {
+ logger(LOG_DEBUG,
+ "DNS search for '%s' failed (%s)",
+ svc_name, hstrerror(state->res_h_errno));
+ }
+ return (-1);
+ }
+
+ if (len > sizeof (msg.buf)) {
+ logger(LOG_WARNING,
+ "DNS query %ib message doesn't fit into %ib buffer",
+ len, sizeof (msg.buf));
+ len = sizeof (msg.buf);
+ }
+
+ /* parse the reply header */
+
+ ptr = msg.buf + sizeof (msg.hdr);
+ eom = msg.buf + len;
+ qdcount = ntohs(msg.hdr.qdcount);
+ ancount = ntohs(msg.hdr.ancount);
+
+ /* skip the question section */
+
+ len = ns_skiprr(ptr, eom, ns_s_qd, qdcount);
+ if (len < 0) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ return (-1);
+ }
+ ptr += len;
+
+ /* parse the answer section */
+ if (ancount < 1) {
+ logger(LOG_ERR, "DNS query - no answers");
+ return (-1);
+ }
+
+ len = dn_expand(msg.buf, eom, ptr, namebuf, sizeof (namebuf));
+ if (len < 0) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ return (-1);
+ }
+ *rrname = strdup(namebuf);
+ if (*rrname == NULL) {
+ logger(LOG_ERR, "Out of memory");
+ return (-1);
+ }
+
+ return (0);
+}
+
+
+/*
+ * Compare SRC RRs; used with qsort(). Sort order:
+ * "Earliest" (lowest number) priority first,
+ * then weight highest to lowest.
+ */
+static int
+srvcmp(ad_disc_ds_t *s1, ad_disc_ds_t *s2)
+{
+ if (s1->priority < s2->priority)
+ return (-1);
+ else if (s1->priority > s2->priority)
+ return (1);
+
+ if (s1->weight < s2->weight)
+ return (1);
+ else if (s1->weight > s2->weight)
+ return (-1);
+
+ return (0);
+}
+
+/*
+ * Query or search the SRV RRs for a given name.
+ *
+ * If dname == NULL then search (as in res_nsearch(3RESOLV), honoring any
+ * search list/option), else query (as in res_nquery(3RESOLV)).
+ *
+ * The output TTL will be the one of the SRV RR with the lowest TTL.
+ */
+ad_disc_cds_t *
+srv_query(res_state state, const char *svc_name, const char *dname,
+ ad_disc_ds_t *prefer)
+{
+ ad_disc_cds_t *cds_res = NULL;
+ uchar_t *msg = NULL;
+ int len, scnt, maxcnt;
+
+ msg = malloc(NS_MAXMSG);
+ if (msg == NULL) {
+ logger(LOG_ERR, "Out of memory");
+ return (NULL);
+ }
+
+ /* query necessary resource records */
+
+ /* Search, querydomain or query */
+ if (dname == NULL) {
+ dname = "*";
+ if (DBG(DNS, 1)) {
+ logger(LOG_DEBUG, "Looking for SRV RRs '%s.*'",
+ svc_name);
+ }
+ len = res_nsearch(state, svc_name, C_IN, T_SRV,
+ msg, NS_MAXMSG);
+ if (len < 0) {
+ if (DBG(DNS, 0)) {
+ logger(LOG_DEBUG,
+ "DNS search for '%s' failed (%s)",
+ svc_name, hstrerror(state->res_h_errno));
+ }
+ goto errout;
+ }
+ } else { /* dname != NULL */
+ if (DBG(DNS, 1)) {
+ logger(LOG_DEBUG, "Looking for SRV RRs '%s.%s' ",
+ svc_name, dname);
+ }
+
+ len = res_nquerydomain(state, svc_name, dname, C_IN, T_SRV,
+ msg, NS_MAXMSG);
+
+ if (len < 0) {
+ if (DBG(DNS, 0)) {
+ logger(LOG_DEBUG, "DNS: %s.%s: %s",
+ svc_name, dname,
+ hstrerror(state->res_h_errno));
+ }
+ goto errout;
+ }
+ }
+
+ if (len > NS_MAXMSG) {
+ logger(LOG_WARNING,
+ "DNS query %ib message doesn't fit into %ib buffer",
+ len, NS_MAXMSG);
+ len = NS_MAXMSG;
+ }
+
+
+ /* parse the reply header */
+
+ cds_res = srv_parse(msg, len, &scnt, &maxcnt);
+ if (cds_res == NULL)
+ goto errout;
+
+ if (prefer != NULL)
+ add_preferred(cds_res, prefer, &scnt, maxcnt);
+
+ get_addresses(cds_res, scnt);
+
+ /* sort list of candidates */
+ if (scnt > 1)
+ qsort(cds_res, scnt, sizeof (*cds_res),
+ (int (*)(const void *, const void *))srvcmp);
+
+ free(msg);
+ return (cds_res);
+
+errout:
+ free(msg);
+ return (NULL);
+}
+
+static ad_disc_cds_t *
+srv_parse(uchar_t *msg, int len, int *scnt, int *maxcnt)
+{
+ ad_disc_cds_t *cds;
+ ad_disc_cds_t *cds_res = NULL;
+ HEADER *hdr;
+ int i, qdcount, ancount, nscount, arcount;
+ uchar_t *ptr, *eom;
+ uchar_t *end;
+ uint16_t type;
+ /* LINTED E_FUNC_SET_NOT_USED */
+ uint16_t class;
+ uint32_t rttl;
+ uint16_t size;
+ char namebuf[NS_MAXDNAME];
+
+ eom = msg + len;
+ hdr = (void *)msg;
+ ptr = msg + sizeof (HEADER);
+
+ qdcount = ntohs(hdr->qdcount);
+ ancount = ntohs(hdr->ancount);
+ nscount = ntohs(hdr->nscount);
+ arcount = ntohs(hdr->arcount);
+
+ /* skip the question section */
+
+ len = ns_skiprr(ptr, eom, ns_s_qd, qdcount);
+ if (len < 0) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ return (NULL);
+ }
+ ptr += len;
+
+ /*
+ * Walk through the answer section, building the result array.
+ * The array size is +2 because we (possibly) add the preferred
+ * DC if it was not there, and an empty one (null termination).
+ */
+
+ *maxcnt = ancount + 2;
+ cds_res = calloc(*maxcnt, sizeof (*cds_res));
+ if (cds_res == NULL) {
+ logger(LOG_ERR, "Out of memory");
+ return (NULL);
+ }
+
+ cds = cds_res;
+ for (i = 0; i < ancount; i++) {
+
+ len = dn_expand(msg, eom, ptr, namebuf,
+ sizeof (namebuf));
+ if (len < 0) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ goto err;
+ }
+ ptr += len;
+ NS_GET16(type, ptr);
+ NS_GET16(class, ptr);
+ NS_GET32(rttl, ptr);
+ NS_GET16(size, ptr);
+ if ((end = ptr + size) > eom) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ goto err;
+ }
+
+ if (type != T_SRV) {
+ ptr = end;
+ continue;
+ }
+
+ NS_GET16(cds->cds_ds.priority, ptr);
+ NS_GET16(cds->cds_ds.weight, ptr);
+ NS_GET16(cds->cds_ds.port, ptr);
+ len = dn_expand(msg, eom, ptr, cds->cds_ds.host,
+ sizeof (cds->cds_ds.host));
+ if (len < 0) {
+ logger(LOG_ERR, "DNS query invalid SRV record");
+ goto err;
+ }
+
+ cds->cds_ds.ttl = rttl;
+
+ if (DBG(DNS, 2)) {
+ logger(LOG_DEBUG, " %s", namebuf);
+ logger(LOG_DEBUG,
+ " ttl=%d pri=%d weight=%d %s:%d",
+ rttl, cds->cds_ds.priority, cds->cds_ds.weight,
+ cds->cds_ds.host, cds->cds_ds.port);
+ }
+ cds++;
+
+ /* move ptr to the end of current record */
+ ptr = end;
+ }
+ *scnt = (cds - cds_res);
+
+ /* skip the nameservers section (if any) */
+
+ len = ns_skiprr(ptr, eom, ns_s_ns, nscount);
+ if (len < 0) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ goto err;
+ }
+ ptr += len;
+
+ /* walk through the additional records */
+ for (i = 0; i < arcount; i++) {
+ sa_family_t af;
+
+ len = dn_expand(msg, eom, ptr, namebuf,
+ sizeof (namebuf));
+ if (len < 0) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ goto err;
+ }
+ ptr += len;
+ NS_GET16(type, ptr);
+ NS_GET16(class, ptr);
+ NS_GET32(rttl, ptr);
+ NS_GET16(size, ptr);
+ if ((end = ptr + size) > eom) {
+ logger(LOG_ERR, "DNS query invalid message format");
+ goto err;
+ }
+ switch (type) {
+ case ns_t_a:
+ af = AF_INET;
+ break;
+ case ns_t_aaaa:
+ af = AF_INET6;
+ break;
+ default:
+ continue;
+ }
+
+ if (DBG(DNS, 2)) {
+ char abuf[INET6_ADDRSTRLEN];
+ const char *ap;
+
+ ap = inet_ntop(af, ptr, abuf, sizeof (abuf));
+ logger(LOG_DEBUG, " %s %s %s",
+ (af == AF_INET) ? "A " : "AAAA",
+ (ap) ? ap : "?", namebuf);
+ }
+
+ /* Find the server, add to its address list. */
+ for (cds = cds_res; cds->cds_ds.host[0] != '\0'; cds++)
+ if (0 == strcmp(namebuf, cds->cds_ds.host))
+ save_addr(cds, af, ptr, size);
+
+ /* move ptr to the end of current record */
+ ptr = end;
+ }
+
+ return (cds_res);
+
+err:
+ free(cds_res);
+ return (NULL);
+}
+
+/*
+ * Save this address on the server, if not already there.
+ */
+static void
+save_addr(ad_disc_cds_t *cds, sa_family_t af, uchar_t *addr, size_t alen)
+{
+ struct addrinfo *ai, *new_ai, *last_ai;
+
+ new_ai = make_addrinfo(af, addr, alen);
+ if (new_ai == NULL)
+ return;
+
+ last_ai = NULL;
+ for (ai = cds->cds_ai; ai != NULL; ai = ai->ai_next) {
+ last_ai = ai;
+
+ if (new_ai->ai_family == ai->ai_family &&
+ new_ai->ai_addrlen == ai->ai_addrlen &&
+ 0 == memcmp(new_ai->ai_addr, ai->ai_addr,
+ ai->ai_addrlen)) {
+ /* it's already there */
+ freeaddrinfo(new_ai);
+ return;
+ }
+ }
+
+ /* Not found. Append. */
+ if (last_ai != NULL) {
+ last_ai->ai_next = new_ai;
+ } else {
+ cds->cds_ai = new_ai;
+ }
+}
+
+static struct addrinfo *
+make_addrinfo(sa_family_t af, uchar_t *addr, size_t alen)
+{
+ struct addrinfo *ai;
+ struct sockaddr *sa;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ int slen;
+
+ ai = calloc(1, sizeof (*ai));
+ sa = calloc(1, sizeof (struct sockaddr_in6));
+
+ if (ai == NULL || sa == NULL) {
+ logger(LOG_ERR, "Out of memory");
+ goto errout;
+ }
+
+ switch (af) {
+ case AF_INET:
+ sin = (void *)sa;
+ if (alen < sizeof (in_addr_t)) {
+ logger(LOG_ERR, "bad IPv4 addr len");
+ goto errout;
+ }
+ alen = sizeof (in_addr_t);
+ sin->sin_family = af;
+ (void) memcpy(&sin->sin_addr, addr, alen);
+ slen = sizeof (*sin);
+ break;
+
+ case AF_INET6:
+ sin6 = (void *)sa;
+ if (alen < sizeof (in6_addr_t)) {
+ logger(LOG_ERR, "bad IPv6 addr len");
+ goto errout;
+ }
+ alen = sizeof (in6_addr_t);
+ sin6->sin6_family = af;
+ (void) memcpy(&sin6->sin6_addr, addr, alen);
+ slen = sizeof (*sin6);
+ break;
+
+ default:
+ goto errout;
+ }
+
+ ai->ai_family = af;
+ ai->ai_addrlen = slen;
+ ai->ai_addr = sa;
+ sa->sa_family = af;
+ return (ai);
+
+errout:
+ free(ai);
+ free(sa);
+ return (NULL);
+}
+
+/*
+ * Set a preferred candidate, which may already be in the list,
+ * in which case we just bump its priority, or else add it.
+ */
+static void
+add_preferred(ad_disc_cds_t *cds, ad_disc_ds_t *prefer, int *nds, int maxds)
+{
+ ad_disc_ds_t *ds;
+ int i;
+
+ assert(*nds < maxds);
+ for (i = 0; i < *nds; i++) {
+ ds = &cds[i].cds_ds;
+
+ if (strcasecmp(ds->host, prefer->host) == 0) {
+ /* Force this element to be sorted first. */
+ ds->priority = 0;
+ ds->weight = 200;
+ return;
+ }
+ }
+
+ /*
+ * The preferred DC was not found in this DNS response,
+ * so add it. Again arrange for it to be sorted first.
+ * Address info. is added later.
+ */
+ ds = &cds[i].cds_ds;
+ (void) memcpy(ds, prefer, sizeof (*ds));
+ ds->priority = 0;
+ ds->weight = 200;
+ *nds = i + 1;
+}
+
+/*
+ * Do another pass over the array to check for missing addresses and
+ * try resolving the names. Normally, the DNS response from AD will
+ * have supplied additional address records for all the SRV records.
+ */
+static void
+get_addresses(ad_disc_cds_t *cds, int cnt)
+{
+ int i;
+
+ for (i = 0; i < cnt; i++) {
+ if (cds[i].cds_ai == NULL) {
+ do_getaddrinfo(&cds[i]);
+ }
+ }
+}
+
+static void
+do_getaddrinfo(ad_disc_cds_t *cds)
+{
+ struct addrinfo hints;
+ struct addrinfo *ai;
+ ad_disc_ds_t *ds;
+ time_t t0, t1;
+ int err;
+
+ (void) memset(&hints, 0, sizeof (hints));
+ hints.ai_protocol = IPPROTO_TCP;
+ hints.ai_socktype = SOCK_STREAM;
+ ds = &cds->cds_ds;
+
+ /*
+ * This getaddrinfo call may take a LONG time, i.e. if our
+ * DNS servers are misconfigured or not responding.
+ * We need something like getaddrinfo_a(), with a timeout.
+ * For now, just log when this happens so we'll know
+ * if these calls are taking a long time.
+ */
+ if (DBG(DNS, 2))
+ logger(LOG_DEBUG, "getaddrinfo %s ...", ds->host);
+ t0 = time(NULL);
+ err = getaddrinfo(cds->cds_ds.host, NULL, &hints, &ai);
+ t1 = time(NULL);
+ if (DBG(DNS, 2))
+ logger(LOG_DEBUG, "getaddrinfo %s rc=%d", ds->host, err);
+ if (t1 > (t0 + 5)) {
+ logger(LOG_WARNING, "Lookup host (%s) took %u sec. "
+ "(Check DNS settings)", ds->host, (int)(t1 - t0));
+ }
+ if (err != 0) {
+ logger(LOG_ERR, "No address for host: %s (%s)",
+ ds->host, gai_strerror(err));
+ /* Make this sort at the end. */
+ ds->priority = 1 << 16;
+ return;
+ }
+
+ cds->cds_ai = ai;
+}
+
+void
+srv_free(ad_disc_cds_t *cds_vec)
+{
+ ad_disc_cds_t *cds;
+
+ for (cds = cds_vec; cds->cds_ds.host[0] != '\0'; cds++) {
+ if (cds->cds_ai != NULL) {
+ freeaddrinfo(cds->cds_ai);
+ }
+ }
+ free(cds_vec);
+}