xref: /illumos-gate/usr/src/lib/libadutils/common/srv_query.c (revision 04427e3bf236c18cc532680b957267ee70b1037d)
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 
57 static void save_addr(ad_disc_cds_t *, sa_family_t, uchar_t *, size_t);
58 static struct addrinfo *make_addrinfo(sa_family_t, uchar_t *, size_t);
59 
60 static void do_getaddrinfo(ad_disc_cds_t *);
61 static ad_disc_cds_t *srv_parse(uchar_t *, int, int *, int *);
62 static void add_preferred(ad_disc_cds_t *, ad_disc_ds_t *, int *, int);
63 static void get_addresses(ad_disc_cds_t *, int);
64 
65 /*
66  * Simplified version of srv_query() for domain auto-discovery.
67  */
68 int
69 srv_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  */
145 static int
146 srvcmp(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  */
169 ad_disc_cds_t *
170 srv_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 
248 errout:
249 	free(msg);
250 	return (NULL);
251 }
252 
253 static ad_disc_cds_t *
254 srv_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;
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 
410 err:
411 	free(cds_res);
412 	return (NULL);
413 }
414 
415 /*
416  * Save this address on the server, if not already there.
417  */
418 static void
419 save_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 
449 static struct addrinfo *
450 make_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 
501 errout:
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  */
511 static void
512 add_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  */
546 static void
547 get_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 
558 static void
559 do_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 
601 void
602 srv_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