1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
25 */
26
27/*
28 * DNS query helper functions for addisc.c
29 */
30
31#include <stdio.h>
32#include <string.h>
33#include <strings.h>
34#include <unistd.h>
35#include <assert.h>
36#include <stdlib.h>
37#include <net/if.h>
38#include <sys/types.h>
39#include <sys/socket.h>
40#include <sys/sockio.h>
41#include <netinet/in.h>
42#include <arpa/inet.h>
43#include <arpa/nameser.h>
44#include <resolv.h>
45#include <netdb.h>
46#include <ctype.h>
47#include <errno.h>
48#include <ldap.h>
49#include <sasl/sasl.h>
50#include <sys/u8_textprep.h>
51#include <syslog.h>
52#include <uuid/uuid.h>
53#include <ads/dsgetdc.h>
54#include "adutils_impl.h"
55#include "addisc_impl.h"
56
57static void save_addr(ad_disc_cds_t *, sa_family_t, uchar_t *, size_t);
58static struct addrinfo *make_addrinfo(sa_family_t, uchar_t *, size_t);
59
60static void do_getaddrinfo(ad_disc_cds_t *);
61static ad_disc_cds_t *srv_parse(uchar_t *, int, int *, int *);
62static void add_preferred(ad_disc_cds_t *, ad_disc_ds_t *, int *, int);
63static void get_addresses(ad_disc_cds_t *, int);
64
65/*
66 * Simplified version of srv_query() for domain auto-discovery.
67 */
68int
69srv_getdom(res_state state, const char *svc_name, char **rrname)
70{
71	union {
72		HEADER hdr;
73		uchar_t buf[NS_MAXMSG];
74	} msg;
75	int len, qdcount, ancount;
76	uchar_t *ptr, *eom;
77	char namebuf[NS_MAXDNAME];
78
79	/* query necessary resource records */
80
81	*rrname = NULL;
82	if (DBG(DNS, 1))  {
83		logger(LOG_DEBUG, "Looking for SRV RRs '%s.*'", svc_name);
84	}
85	len = res_nsearch(state, svc_name, C_IN, T_SRV,
86	    msg.buf, sizeof (msg.buf));
87	if (len < 0) {
88		if (DBG(DNS, 0)) {
89			logger(LOG_DEBUG,
90			    "DNS search for '%s' failed (%s)",
91			    svc_name, hstrerror(state->res_h_errno));
92		}
93		return (-1);
94	}
95
96	if (len > sizeof (msg.buf)) {
97		logger(LOG_WARNING,
98		    "DNS query %ib message doesn't fit into %ib buffer",
99		    len, sizeof (msg.buf));
100		len = sizeof (msg.buf);
101	}
102
103	/* parse the reply header */
104
105	ptr = msg.buf + sizeof (msg.hdr);
106	eom = msg.buf + len;
107	qdcount = ntohs(msg.hdr.qdcount);
108	ancount = ntohs(msg.hdr.ancount);
109
110	/* skip the question section */
111
112	len = ns_skiprr(ptr, eom, ns_s_qd, qdcount);
113	if (len < 0) {
114		logger(LOG_ERR, "DNS query invalid message format");
115		return (-1);
116	}
117	ptr += len;
118
119	/* parse the answer section */
120	if (ancount < 1) {
121		logger(LOG_ERR, "DNS query - no answers");
122		return (-1);
123	}
124
125	len = dn_expand(msg.buf, eom, ptr, namebuf, sizeof (namebuf));
126	if (len < 0) {
127		logger(LOG_ERR, "DNS query invalid message format");
128		return (-1);
129	}
130	*rrname = strdup(namebuf);
131	if (*rrname == NULL) {
132		logger(LOG_ERR, "Out of memory");
133		return (-1);
134	}
135
136	return (0);
137}
138
139
140/*
141 * Compare SRC RRs; used with qsort().  Sort order:
142 * "Earliest" (lowest number) priority first,
143 * then weight highest to lowest.
144 */
145static int
146srvcmp(ad_disc_ds_t *s1, ad_disc_ds_t *s2)
147{
148	if (s1->priority < s2->priority)
149		return (-1);
150	else if (s1->priority > s2->priority)
151		return (1);
152
153	if (s1->weight < s2->weight)
154		return (1);
155	else if (s1->weight > s2->weight)
156		return (-1);
157
158	return (0);
159}
160
161/*
162 * Query or search the SRV RRs for a given name.
163 *
164 * If dname == NULL then search (as in res_nsearch(3RESOLV), honoring any
165 * search list/option), else query (as in res_nquery(3RESOLV)).
166 *
167 * The output TTL will be the one of the SRV RR with the lowest TTL.
168 */
169ad_disc_cds_t *
170srv_query(res_state state, const char *svc_name, const char *dname,
171    ad_disc_ds_t *prefer)
172{
173	ad_disc_cds_t *cds_res = NULL;
174	uchar_t *msg = NULL;
175	int len, scnt, maxcnt;
176
177	msg = malloc(NS_MAXMSG);
178	if (msg == NULL) {
179		logger(LOG_ERR, "Out of memory");
180		return (NULL);
181	}
182
183	/* query necessary resource records */
184
185	/* Search, querydomain or query */
186	if (dname == NULL) {
187		dname = "*";
188		if (DBG(DNS, 1))  {
189			logger(LOG_DEBUG, "Looking for SRV RRs '%s.*'",
190			    svc_name);
191		}
192		len = res_nsearch(state, svc_name, C_IN, T_SRV,
193		    msg, NS_MAXMSG);
194		if (len < 0) {
195			if (DBG(DNS, 0)) {
196				logger(LOG_DEBUG,
197				    "DNS search for '%s' failed (%s)",
198				    svc_name, hstrerror(state->res_h_errno));
199			}
200			goto errout;
201		}
202	} else { /* dname != NULL */
203		if (DBG(DNS, 1)) {
204			logger(LOG_DEBUG, "Looking for SRV RRs '%s.%s' ",
205			    svc_name, dname);
206		}
207
208		len = res_nquerydomain(state, svc_name, dname, C_IN, T_SRV,
209		    msg, NS_MAXMSG);
210
211		if (len < 0) {
212			if (DBG(DNS, 0)) {
213				logger(LOG_DEBUG, "DNS: %s.%s: %s",
214				    svc_name, dname,
215				    hstrerror(state->res_h_errno));
216			}
217			goto errout;
218		}
219	}
220
221	if (len > NS_MAXMSG) {
222		logger(LOG_WARNING,
223		    "DNS query %ib message doesn't fit into %ib buffer",
224		    len, NS_MAXMSG);
225		len = NS_MAXMSG;
226	}
227
228
229	/* parse the reply header */
230
231	cds_res = srv_parse(msg, len, &scnt, &maxcnt);
232	if (cds_res == NULL)
233		goto errout;
234
235	if (prefer != NULL)
236		add_preferred(cds_res, prefer, &scnt, maxcnt);
237
238	get_addresses(cds_res, scnt);
239
240	/* sort list of candidates */
241	if (scnt > 1)
242		qsort(cds_res, scnt, sizeof (*cds_res),
243		    (int (*)(const void *, const void *))srvcmp);
244
245	free(msg);
246	return (cds_res);
247
248errout:
249	free(msg);
250	return (NULL);
251}
252
253static ad_disc_cds_t *
254srv_parse(uchar_t *msg, int len, int *scnt, int *maxcnt)
255{
256	ad_disc_cds_t *cds;
257	ad_disc_cds_t *cds_res = NULL;
258	HEADER *hdr;
259	int i, qdcount, ancount, nscount, arcount;
260	uchar_t *ptr, *eom;
261	uchar_t *end;
262	uint16_t type;
263	/* LINTED  E_FUNC_SET_NOT_USED */
264	uint16_t class __unused;
265	uint32_t rttl;
266	uint16_t size;
267	char namebuf[NS_MAXDNAME];
268
269	eom = msg + len;
270	hdr = (void *)msg;
271	ptr = msg + sizeof (HEADER);
272
273	qdcount = ntohs(hdr->qdcount);
274	ancount = ntohs(hdr->ancount);
275	nscount = ntohs(hdr->nscount);
276	arcount = ntohs(hdr->arcount);
277
278	/* skip the question section */
279
280	len = ns_skiprr(ptr, eom, ns_s_qd, qdcount);
281	if (len < 0) {
282		logger(LOG_ERR, "DNS query invalid message format");
283		return (NULL);
284	}
285	ptr += len;
286
287	/*
288	 * Walk through the answer section, building the result array.
289	 * The array size is +2 because we (possibly) add the preferred
290	 * DC if it was not there, and an empty one (null termination).
291	 */
292
293	*maxcnt = ancount + 2;
294	cds_res = calloc(*maxcnt, sizeof (*cds_res));
295	if (cds_res == NULL) {
296		logger(LOG_ERR, "Out of memory");
297		return (NULL);
298	}
299
300	cds = cds_res;
301	for (i = 0; i < ancount; i++) {
302
303		len = dn_expand(msg, eom, ptr, namebuf,
304		    sizeof (namebuf));
305		if (len < 0) {
306			logger(LOG_ERR, "DNS query invalid message format");
307			goto err;
308		}
309		ptr += len;
310		NS_GET16(type, ptr);
311		NS_GET16(class, ptr);
312		NS_GET32(rttl, ptr);
313		NS_GET16(size, ptr);
314		if ((end = ptr + size) > eom) {
315			logger(LOG_ERR, "DNS query invalid message format");
316			goto err;
317		}
318
319		if (type != T_SRV) {
320			ptr = end;
321			continue;
322		}
323
324		NS_GET16(cds->cds_ds.priority, ptr);
325		NS_GET16(cds->cds_ds.weight, ptr);
326		NS_GET16(cds->cds_ds.port, ptr);
327		len = dn_expand(msg, eom, ptr, cds->cds_ds.host,
328		    sizeof (cds->cds_ds.host));
329		if (len < 0) {
330			logger(LOG_ERR, "DNS query invalid SRV record");
331			goto err;
332		}
333
334		cds->cds_ds.ttl = rttl;
335
336		if (DBG(DNS, 2)) {
337			logger(LOG_DEBUG, "    %s", namebuf);
338			logger(LOG_DEBUG,
339			    "        ttl=%d pri=%d weight=%d %s:%d",
340			    rttl, cds->cds_ds.priority, cds->cds_ds.weight,
341			    cds->cds_ds.host, cds->cds_ds.port);
342		}
343		cds++;
344
345		/* move ptr to the end of current record */
346		ptr = end;
347	}
348	*scnt = (cds - cds_res);
349
350	/* skip the nameservers section (if any) */
351
352	len = ns_skiprr(ptr, eom, ns_s_ns, nscount);
353	if (len < 0) {
354		logger(LOG_ERR, "DNS query invalid message format");
355		goto err;
356	}
357	ptr += len;
358
359	/* walk through the additional records */
360	for (i = 0; i < arcount; i++) {
361		sa_family_t af;
362
363		len = dn_expand(msg, eom, ptr, namebuf,
364		    sizeof (namebuf));
365		if (len < 0) {
366			logger(LOG_ERR, "DNS query invalid message format");
367			goto err;
368		}
369		ptr += len;
370		NS_GET16(type, ptr);
371		NS_GET16(class, ptr);
372		NS_GET32(rttl, ptr);
373		NS_GET16(size, ptr);
374		if ((end = ptr + size) > eom) {
375			logger(LOG_ERR, "DNS query invalid message format");
376			goto err;
377		}
378		switch (type) {
379		case ns_t_a:
380			af = AF_INET;
381			break;
382		case ns_t_aaaa:
383			af = AF_INET6;
384			break;
385		default:
386			continue;
387		}
388
389		if (DBG(DNS, 2)) {
390			char abuf[INET6_ADDRSTRLEN];
391			const char *ap;
392
393			ap = inet_ntop(af, ptr, abuf, sizeof (abuf));
394			logger(LOG_DEBUG, "    %s    %s    %s",
395			    (af == AF_INET) ? "A   " : "AAAA",
396			    (ap) ? ap : "?", namebuf);
397		}
398
399		/* Find the server, add to its address list. */
400		for (cds = cds_res; cds->cds_ds.host[0] != '\0'; cds++)
401			if (0 == strcmp(namebuf, cds->cds_ds.host))
402				save_addr(cds, af, ptr, size);
403
404		/* move ptr to the end of current record */
405		ptr = end;
406	}
407
408	return (cds_res);
409
410err:
411	free(cds_res);
412	return (NULL);
413}
414
415/*
416 * Save this address on the server, if not already there.
417 */
418static void
419save_addr(ad_disc_cds_t *cds, sa_family_t af, uchar_t *addr, size_t alen)
420{
421	struct addrinfo *ai, *new_ai, *last_ai;
422
423	new_ai = make_addrinfo(af, addr, alen);
424	if (new_ai == NULL)
425		return;
426
427	last_ai = NULL;
428	for (ai = cds->cds_ai; ai != NULL; ai = ai->ai_next) {
429		last_ai = ai;
430
431		if (new_ai->ai_family == ai->ai_family &&
432		    new_ai->ai_addrlen == ai->ai_addrlen &&
433		    0 == memcmp(new_ai->ai_addr, ai->ai_addr,
434		    ai->ai_addrlen)) {
435			/* it's already there */
436			freeaddrinfo(new_ai);
437			return;
438		}
439	}
440
441	/* Not found.  Append. */
442	if (last_ai != NULL) {
443		last_ai->ai_next = new_ai;
444	} else {
445		cds->cds_ai = new_ai;
446	}
447}
448
449static struct addrinfo *
450make_addrinfo(sa_family_t af, uchar_t *addr, size_t alen)
451{
452	struct addrinfo *ai;
453	struct sockaddr *sa;
454	struct sockaddr_in *sin;
455	struct sockaddr_in6 *sin6;
456	int slen;
457
458	ai = calloc(1, sizeof (*ai));
459	sa = calloc(1, sizeof (struct sockaddr_in6));
460
461	if (ai == NULL || sa == NULL) {
462		logger(LOG_ERR, "Out of memory");
463		goto errout;
464	}
465
466	switch (af) {
467	case AF_INET:
468		sin = (void *)sa;
469		if (alen < sizeof (in_addr_t)) {
470			logger(LOG_ERR, "bad IPv4 addr len");
471			goto errout;
472		}
473		alen = sizeof (in_addr_t);
474		sin->sin_family = af;
475		(void) memcpy(&sin->sin_addr, addr, alen);
476		slen = sizeof (*sin);
477		break;
478
479	case AF_INET6:
480		sin6 = (void *)sa;
481		if (alen < sizeof (in6_addr_t)) {
482			logger(LOG_ERR, "bad IPv6 addr len");
483			goto errout;
484		}
485		alen = sizeof (in6_addr_t);
486		sin6->sin6_family = af;
487		(void) memcpy(&sin6->sin6_addr, addr, alen);
488		slen = sizeof (*sin6);
489		break;
490
491	default:
492		goto errout;
493	}
494
495	ai->ai_family = af;
496	ai->ai_addrlen = slen;
497	ai->ai_addr = sa;
498	sa->sa_family = af;
499	return (ai);
500
501errout:
502	free(ai);
503	free(sa);
504	return (NULL);
505}
506
507/*
508 * Set a preferred candidate, which may already be in the list,
509 * in which case we just bump its priority, or else add it.
510 */
511static void
512add_preferred(ad_disc_cds_t *cds, ad_disc_ds_t *prefer, int *nds, int maxds)
513{
514	ad_disc_ds_t *ds;
515	int i;
516
517	assert(*nds < maxds);
518	for (i = 0; i < *nds; i++) {
519		ds = &cds[i].cds_ds;
520
521		if (strcasecmp(ds->host, prefer->host) == 0) {
522			/* Force this element to be sorted first. */
523			ds->priority = 0;
524			ds->weight = 200;
525			return;
526		}
527	}
528
529	/*
530	 * The preferred DC was not found in this DNS response,
531	 * so add it.  Again arrange for it to be sorted first.
532	 * Address info. is added later.
533	 */
534	ds = &cds[i].cds_ds;
535	(void) memcpy(ds, prefer, sizeof (*ds));
536	ds->priority = 0;
537	ds->weight = 200;
538	*nds = i + 1;
539}
540
541/*
542 * Do another pass over the array to check for missing addresses and
543 * try resolving the names.  Normally, the DNS response from AD will
544 * have supplied additional address records for all the SRV records.
545 */
546static void
547get_addresses(ad_disc_cds_t *cds, int cnt)
548{
549	int i;
550
551	for (i = 0; i < cnt; i++) {
552		if (cds[i].cds_ai == NULL) {
553			do_getaddrinfo(&cds[i]);
554		}
555	}
556}
557
558static void
559do_getaddrinfo(ad_disc_cds_t *cds)
560{
561	struct addrinfo hints;
562	struct addrinfo *ai;
563	ad_disc_ds_t *ds;
564	time_t t0, t1;
565	int err;
566
567	(void) memset(&hints, 0, sizeof (hints));
568	hints.ai_protocol = IPPROTO_TCP;
569	hints.ai_socktype = SOCK_STREAM;
570	ds = &cds->cds_ds;
571
572	/*
573	 * This getaddrinfo call may take a LONG time, i.e. if our
574	 * DNS servers are misconfigured or not responding.
575	 * We need something like getaddrinfo_a(), with a timeout.
576	 * For now, just log when this happens so we'll know
577	 * if these calls are taking a long time.
578	 */
579	if (DBG(DNS, 2))
580		logger(LOG_DEBUG, "getaddrinfo %s ...", ds->host);
581	t0 = time(NULL);
582	err = getaddrinfo(cds->cds_ds.host, NULL, &hints, &ai);
583	t1 = time(NULL);
584	if (DBG(DNS, 2))
585		logger(LOG_DEBUG, "getaddrinfo %s rc=%d", ds->host, err);
586	if (t1 > (t0 + 5)) {
587		logger(LOG_WARNING, "Lookup host (%s) took %u sec. "
588		    "(Check DNS settings)", ds->host, (int)(t1 - t0));
589	}
590	if (err != 0) {
591		logger(LOG_ERR, "No address for host: %s (%s)",
592		    ds->host, gai_strerror(err));
593		/* Make this sort at the end. */
594		ds->priority = 1 << 16;
595		return;
596	}
597
598	cds->cds_ai = ai;
599}
600
601void
602srv_free(ad_disc_cds_t *cds_vec)
603{
604	ad_disc_cds_t *cds;
605
606	for (cds = cds_vec; cds->cds_ds.host[0] != '\0'; cds++) {
607		if (cds->cds_ai != NULL) {
608			freeaddrinfo(cds->cds_ai);
609		}
610	}
611	free(cds_vec);
612}
613