8824 libadutils: variable 'class' set but not used
[unleashed.git] / usr / src / lib / libadutils / common / srv_query.c
blobfd49e2f9ce9a4de14a404621c4246025faf1d927
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
23 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
28 * DNS query helper functions for addisc.c
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"
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);
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);
66 * Simplified version of srv_query() for domain auto-discovery.
68 int
69 srv_getdom(res_state state, const char *svc_name, char **rrname)
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];
79 /* query necessary resource records */
81 *rrname = NULL;
82 if (DBG(DNS, 1)) {
83 logger(LOG_DEBUG, "Looking for SRV RRs '%s.*'", svc_name);
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));
93 return (-1);
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);
103 /* parse the reply header */
105 ptr = msg.buf + sizeof (msg.hdr);
106 eom = msg.buf + len;
107 qdcount = ntohs(msg.hdr.qdcount);
108 ancount = ntohs(msg.hdr.ancount);
110 /* skip the question section */
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);
117 ptr += len;
119 /* parse the answer section */
120 if (ancount < 1) {
121 logger(LOG_ERR, "DNS query - no answers");
122 return (-1);
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);
130 *rrname = strdup(namebuf);
131 if (*rrname == NULL) {
132 logger(LOG_ERR, "Out of memory");
133 return (-1);
136 return (0);
141 * Compare SRC RRs; used with qsort(). Sort order:
142 * "Earliest" (lowest number) priority first,
143 * then weight highest to lowest.
145 static int
146 srvcmp(ad_disc_ds_t *s1, ad_disc_ds_t *s2)
148 if (s1->priority < s2->priority)
149 return (-1);
150 else if (s1->priority > s2->priority)
151 return (1);
153 if (s1->weight < s2->weight)
154 return (1);
155 else if (s1->weight > s2->weight)
156 return (-1);
158 return (0);
162 * Query or search the SRV RRs for a given name.
164 * If dname == NULL then search (as in res_nsearch(3RESOLV), honoring any
165 * search list/option), else query (as in res_nquery(3RESOLV)).
167 * The output TTL will be the one of the SRV RR with the lowest TTL.
169 ad_disc_cds_t *
170 srv_query(res_state state, const char *svc_name, const char *dname,
171 ad_disc_ds_t *prefer)
173 ad_disc_cds_t *cds_res = NULL;
174 uchar_t *msg = NULL;
175 int len, scnt, maxcnt;
177 msg = malloc(NS_MAXMSG);
178 if (msg == NULL) {
179 logger(LOG_ERR, "Out of memory");
180 return (NULL);
183 /* query necessary resource records */
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);
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));
200 goto errout;
202 } else { /* dname != NULL */
203 if (DBG(DNS, 1)) {
204 logger(LOG_DEBUG, "Looking for SRV RRs '%s.%s' ",
205 svc_name, dname);
208 len = res_nquerydomain(state, svc_name, dname, C_IN, T_SRV,
209 msg, NS_MAXMSG);
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));
217 goto errout;
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;
229 /* parse the reply header */
231 cds_res = srv_parse(msg, len, &scnt, &maxcnt);
232 if (cds_res == NULL)
233 goto errout;
235 if (prefer != NULL)
236 add_preferred(cds_res, prefer, &scnt, maxcnt);
238 get_addresses(cds_res, scnt);
240 /* sort list of candidates */
241 if (scnt > 1)
242 qsort(cds_res, scnt, sizeof (*cds_res),
243 (int (*)(const void *, const void *))srvcmp);
245 free(msg);
246 return (cds_res);
248 errout:
249 free(msg);
250 return (NULL);
253 static ad_disc_cds_t *
254 srv_parse(uchar_t *msg, int len, int *scnt, int *maxcnt)
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];
269 eom = msg + len;
270 hdr = (void *)msg;
271 ptr = msg + sizeof (HEADER);
273 qdcount = ntohs(hdr->qdcount);
274 ancount = ntohs(hdr->ancount);
275 nscount = ntohs(hdr->nscount);
276 arcount = ntohs(hdr->arcount);
278 /* skip the question section */
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);
285 ptr += len;
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).
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);
300 cds = cds_res;
301 for (i = 0; i < ancount; i++) {
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;
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;
319 if (type != T_SRV) {
320 ptr = end;
321 continue;
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;
334 cds->cds_ds.ttl = rttl;
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);
343 cds++;
345 /* move ptr to the end of current record */
346 ptr = end;
348 *scnt = (cds - cds_res);
350 /* skip the nameservers section (if any) */
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;
357 ptr += len;
359 /* walk through the additional records */
360 for (i = 0; i < arcount; i++) {
361 sa_family_t af;
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;
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;
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;
389 if (DBG(DNS, 2)) {
390 char abuf[INET6_ADDRSTRLEN];
391 const char *ap;
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);
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);
404 /* move ptr to the end of current record */
405 ptr = end;
408 return (cds_res);
410 err:
411 free(cds_res);
412 return (NULL);
416 * Save this address on the server, if not already there.
418 static void
419 save_addr(ad_disc_cds_t *cds, sa_family_t af, uchar_t *addr, size_t alen)
421 struct addrinfo *ai, *new_ai, *last_ai;
423 new_ai = make_addrinfo(af, addr, alen);
424 if (new_ai == NULL)
425 return;
427 last_ai = NULL;
428 for (ai = cds->cds_ai; ai != NULL; ai = ai->ai_next) {
429 last_ai = ai;
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;
441 /* Not found. Append. */
442 if (last_ai != NULL) {
443 last_ai->ai_next = new_ai;
444 } else {
445 cds->cds_ai = new_ai;
449 static struct addrinfo *
450 make_addrinfo(sa_family_t af, uchar_t *addr, size_t alen)
452 struct addrinfo *ai;
453 struct sockaddr *sa;
454 struct sockaddr_in *sin;
455 struct sockaddr_in6 *sin6;
456 int slen;
458 ai = calloc(1, sizeof (*ai));
459 sa = calloc(1, sizeof (struct sockaddr_in6));
461 if (ai == NULL || sa == NULL) {
462 logger(LOG_ERR, "Out of memory");
463 goto errout;
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;
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;
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;
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;
491 default:
492 goto errout;
495 ai->ai_family = af;
496 ai->ai_addrlen = slen;
497 ai->ai_addr = sa;
498 sa->sa_family = af;
499 return (ai);
501 errout:
502 free(ai);
503 free(sa);
504 return (NULL);
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.
511 static void
512 add_preferred(ad_disc_cds_t *cds, ad_disc_ds_t *prefer, int *nds, int maxds)
514 ad_disc_ds_t *ds;
515 int i;
517 assert(*nds < maxds);
518 for (i = 0; i < *nds; i++) {
519 ds = &cds[i].cds_ds;
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;
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.
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;
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.
546 static void
547 get_addresses(ad_disc_cds_t *cds, int cnt)
549 int i;
551 for (i = 0; i < cnt; i++) {
552 if (cds[i].cds_ai == NULL) {
553 do_getaddrinfo(&cds[i]);
558 static void
559 do_getaddrinfo(ad_disc_cds_t *cds)
561 struct addrinfo hints;
562 struct addrinfo *ai;
563 ad_disc_ds_t *ds;
564 time_t t0, t1;
565 int err;
567 (void) memset(&hints, 0, sizeof (hints));
568 hints.ai_protocol = IPPROTO_TCP;
569 hints.ai_socktype = SOCK_STREAM;
570 ds = &cds->cds_ds;
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.
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));
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;
598 cds->cds_ai = ai;
601 void
602 srv_free(ad_disc_cds_t *cds_vec)
604 ad_disc_cds_t *cds;
606 for (cds = cds_vec; cds->cds_ds.host[0] != '\0'; cds++) {
607 if (cds->cds_ai != NULL) {
608 freeaddrinfo(cds->cds_ai);
611 free(cds_vec);