1/*	$OpenBSD: asr.c,v 1.68 2022/01/20 14:18:10 naddy Exp $	*/
2/*
3 * Copyright (c) 2010-2012 Eric Faurot <eric@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/types.h>
19#include <sys/socket.h>
20#include <sys/stat.h>
21#include <netinet/in.h>
22#include <arpa/inet.h>
23#include <arpa/nameser.h>
24#include <netdb.h>
25
26#include <asr.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <resolv.h>
30#include <poll.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <unistd.h>
35#include <limits.h>
36
37#include "asr_private.h"
38
39#include "thread_private.h"
40
41#define DEFAULT_CONF		"lookup file\n"
42#define DEFAULT_LOOKUP		"lookup bind file"
43
44#define RELOAD_DELAY		15 /* seconds */
45
46static void asr_check_reload(struct asr *);
47static struct asr_ctx *asr_ctx_create(void);
48static void asr_ctx_ref(struct asr_ctx *);
49static void asr_ctx_free(struct asr_ctx *);
50static int asr_ctx_add_searchdomain(struct asr_ctx *, const char *);
51static int asr_ctx_from_file(struct asr_ctx *, const char *);
52static int asr_ctx_from_string(struct asr_ctx *, const char *);
53static int asr_ctx_parse(struct asr_ctx *, const char *);
54static int asr_parse_nameserver(struct sockaddr *, const char *);
55static int asr_ndots(const char *);
56static void pass0(char **, int, struct asr_ctx *);
57static int strsplit(char *, char **, int);
58static void asr_ctx_envopts(struct asr_ctx *);
59static void *__THREAD_NAME(_asr);
60
61static struct asr *_asr = NULL;
62
63/* Allocate and configure an async "resolver". */
64static void *
65_asr_resolver(void)
66{
67	static int	 init = 0;
68	struct asr	*asr;
69
70	if (init == 0) {
71#ifdef DEBUG
72		if (getenv("ASR_DEBUG"))
73			_asr_debug = stderr;
74#endif
75		init = 1;
76	}
77
78	if ((asr = calloc(1, sizeof(*asr))) == NULL)
79		goto fail;
80
81	asr_check_reload(asr);
82	if (asr->a_ctx == NULL) {
83		if ((asr->a_ctx = asr_ctx_create()) == NULL)
84			goto fail;
85		if (asr_ctx_from_string(asr->a_ctx, DEFAULT_CONF) == -1)
86			goto fail;
87		asr_ctx_envopts(asr->a_ctx);
88	}
89
90#ifdef DEBUG
91	_asr_dump_config(_asr_debug, asr);
92#endif
93	return (asr);
94
95    fail:
96	if (asr) {
97		if (asr->a_ctx)
98			asr_ctx_free(asr->a_ctx);
99		free(asr);
100	}
101
102	return (NULL);
103}
104
105/*
106 * Free the "asr" async resolver (or the thread-local resolver if NULL).
107 * Drop the reference to the current context.
108 */
109void
110_asr_resolver_done(void *arg)
111{
112	struct asr_ctx *ac = arg;
113	struct asr *asr;
114	struct asr **priv;
115
116	if (ac) {
117		_asr_ctx_unref(ac);
118		return;
119	} else {
120		priv = _THREAD_PRIVATE_DT(_asr, _asr, NULL, &_asr);
121		if (*priv == NULL)
122			return;
123		asr = *priv;
124		*priv = NULL;
125	}
126
127	_asr_ctx_unref(asr->a_ctx);
128	free(asr);
129}
130
131static void
132_asr_resolver_done_tp(void *arg)
133{
134	struct asr **priv = arg;
135	struct asr *asr;
136
137	if (*priv == NULL)
138		return;
139	asr = *priv;
140
141	_asr_ctx_unref(asr->a_ctx);
142	free(asr);
143	free(priv);
144}
145
146void *
147asr_resolver_from_string(const char *str)
148{
149	struct asr_ctx *ac;
150
151	if ((ac = asr_ctx_create()) == NULL)
152		return NULL;
153
154	if (asr_ctx_from_string(ac, str) == -1) {
155		asr_ctx_free(ac);
156		return NULL;
157	}
158
159	return ac;
160}
161DEF_WEAK(asr_resolver_from_string);
162
163void
164asr_resolver_free(void *arg)
165{
166	_asr_ctx_unref(arg);
167}
168DEF_WEAK(asr_resolver_free);
169
170/*
171 * Cancel an async query.
172 */
173void
174asr_abort(struct asr_query *as)
175{
176	_asr_async_free(as);
177}
178
179/*
180 * Resume the "as" async query resolution.  Return one of ASYNC_COND,
181 * or ASYNC_DONE and put query-specific return values in the user-allocated
182 * memory at "ar".
183 */
184int
185asr_run(struct asr_query *as, struct asr_result *ar)
186{
187	int	r, saved_errno = errno;
188
189	memset(ar, 0, sizeof(*ar));
190
191	DPRINT("asr: asr_run(%p, %p) %s ctx=[%p]\n", as, ar,
192	    _asr_querystr(as->as_type), as->as_ctx);
193	r = as->as_run(as, ar);
194
195	DPRINT("asr: asr_run(%p, %p) -> %s", as, ar, _asr_transitionstr(r));
196#ifdef DEBUG
197	if (r == ASYNC_COND)
198#endif
199		DPRINT(" fd=%i timeout=%i", ar->ar_fd, ar->ar_timeout);
200	DPRINT("\n");
201	if (r == ASYNC_DONE)
202		_asr_async_free(as);
203
204	errno = saved_errno;
205
206	return (r);
207}
208DEF_WEAK(asr_run);
209
210static int
211poll_intrsafe(struct pollfd *fds, nfds_t nfds, int timeout)
212{
213	struct timespec pollstart, pollend, elapsed;
214	int r;
215
216	if (WRAP(clock_gettime)(CLOCK_MONOTONIC, &pollstart))
217		return -1;
218
219	while ((r = poll(fds, 1, timeout)) == -1 && errno == EINTR) {
220		if (WRAP(clock_gettime)(CLOCK_MONOTONIC, &pollend))
221			return -1;
222		timespecsub(&pollend, &pollstart, &elapsed);
223		timeout -= elapsed.tv_sec * 1000 + elapsed.tv_nsec / 1000000;
224		if (timeout < 1)
225			return 0;
226	}
227
228	return r;
229}
230
231/*
232 * Same as asr_run, but run in a loop that handles the fd conditions result.
233 */
234int
235asr_run_sync(struct asr_query *as, struct asr_result *ar)
236{
237	struct pollfd	 fds[1];
238	int		 r, saved_errno = errno;
239
240	while ((r = asr_run(as, ar)) == ASYNC_COND) {
241		fds[0].fd = ar->ar_fd;
242		fds[0].events = (ar->ar_cond == ASR_WANT_READ) ? POLLIN:POLLOUT;
243
244		if (poll_intrsafe(fds, 1, ar->ar_timeout) == -1) {
245			memset(ar, 0, sizeof(*ar));
246			ar->ar_errno = errno;
247			ar->ar_h_errno = NETDB_INTERNAL;
248			ar->ar_gai_errno = EAI_SYSTEM;
249			ar->ar_rrset_errno = NETDB_INTERNAL;
250			_asr_async_free(as);
251			errno = saved_errno;
252			return ASYNC_DONE;
253		}
254
255		/*
256		 * Otherwise, just ignore the error and let asr_run()
257		 * catch the failure.
258		 */
259	}
260
261	errno = saved_errno;
262
263	return (r);
264}
265DEF_WEAK(asr_run_sync);
266
267/*
268 * Create a new async request of the given "type" on the async context "ac".
269 * Take a reference on it so it does not get deleted while the async query
270 * is running.
271 */
272struct asr_query *
273_asr_async_new(struct asr_ctx *ac, int type)
274{
275	struct asr_query	*as;
276
277	DPRINT("asr: asr_async_new(ctx=%p) type=%i refcount=%i\n", ac, type,
278	    ac ? ac->ac_refcount : 0);
279	if (ac == NULL || (as = calloc(1, sizeof(*as))) == NULL)
280		return (NULL);
281
282	ac->ac_refcount += 1;
283	as->as_ctx = ac;
284	as->as_fd = -1;
285	as->as_type = type;
286	as->as_state = ASR_STATE_INIT;
287
288	return (as);
289}
290
291/*
292 * Free an async query and unref the associated context.
293 */
294void
295_asr_async_free(struct asr_query *as)
296{
297	DPRINT("asr: asr_async_free(%p)\n", as);
298
299	if (as->as_subq)
300		_asr_async_free(as->as_subq);
301
302	switch (as->as_type) {
303	case ASR_SEND:
304		if (as->as_fd != -1)
305			close(as->as_fd);
306		if (as->as.dns.obuf && !(as->as_flags & ASYNC_EXTOBUF))
307			free(as->as.dns.obuf);
308		if (as->as.dns.ibuf)
309			free(as->as.dns.ibuf);
310		if (as->as.dns.dname)
311			free(as->as.dns.dname);
312		break;
313
314	case ASR_SEARCH:
315		if (as->as.search.name)
316			free(as->as.search.name);
317		break;
318
319	case ASR_GETRRSETBYNAME:
320		if (as->as.rrset.name)
321			free(as->as.rrset.name);
322		break;
323
324	case ASR_GETHOSTBYNAME:
325	case ASR_GETHOSTBYADDR:
326		if (as->as.hostnamadr.name)
327			free(as->as.hostnamadr.name);
328		break;
329
330	case ASR_GETADDRINFO:
331		if (as->as.ai.aifirst)
332			freeaddrinfo(as->as.ai.aifirst);
333		if (as->as.ai.hostname)
334			free(as->as.ai.hostname);
335		if (as->as.ai.servname)
336			free(as->as.ai.servname);
337		if (as->as.ai.fqdn)
338			free(as->as.ai.fqdn);
339		break;
340
341	case ASR_GETNAMEINFO:
342		break;
343	}
344
345	_asr_ctx_unref(as->as_ctx);
346	free(as);
347}
348
349/*
350 * Get a context from the given resolver. This takes a new reference to
351 * the returned context, which *must* be explicitly dropped when done
352 * using this context.
353 */
354struct asr_ctx *
355_asr_use_resolver(void *arg)
356{
357	struct asr_ctx *ac = arg;
358	struct asr *asr;
359	struct asr **priv;
360
361	if (ac) {
362		asr_ctx_ref(ac);
363		return ac;
364	}
365	else {
366		DPRINT("using thread-local resolver\n");
367		priv = _THREAD_PRIVATE_DT(_asr, _asr, _asr_resolver_done_tp,
368		    &_asr);
369		if (*priv == NULL) {
370			DPRINT("setting up thread-local resolver\n");
371			*priv = _asr_resolver();
372		}
373		asr = *priv;
374	}
375	if (asr != NULL) {
376		asr_check_reload(asr);
377		asr_ctx_ref(asr->a_ctx);
378		return (asr->a_ctx);
379	}
380	return (NULL);
381}
382
383static void
384asr_ctx_ref(struct asr_ctx *ac)
385{
386	DPRINT("asr: asr_ctx_ref(ctx=%p) refcount=%i\n", ac, ac->ac_refcount);
387	ac->ac_refcount += 1;
388}
389
390/*
391 * Drop a reference to an async context, freeing it if the reference
392 * count drops to 0.
393 */
394void
395_asr_ctx_unref(struct asr_ctx *ac)
396{
397	DPRINT("asr: asr_ctx_unref(ctx=%p) refcount=%i\n", ac,
398	    ac ? ac->ac_refcount : 0);
399	if (ac == NULL)
400		return;
401	if (--ac->ac_refcount)
402		return;
403
404	asr_ctx_free(ac);
405}
406
407static void
408asr_ctx_free(struct asr_ctx *ac)
409{
410	int i;
411
412	if (ac->ac_domain)
413		free(ac->ac_domain);
414	for (i = 0; i < ASR_MAXNS; i++)
415		free(ac->ac_ns[i]);
416	for (i = 0; i < ASR_MAXDOM; i++)
417		free(ac->ac_dom[i]);
418
419	free(ac);
420}
421
422/*
423 * Reload the configuration file if it has changed on disk.
424 */
425static void
426asr_check_reload(struct asr *asr)
427{
428	struct asr_ctx	*ac;
429	struct stat	 st;
430	struct timespec	 ts;
431	pid_t		 pid;
432
433	pid = getpid();
434	if (pid != asr->a_pid) {
435		asr->a_pid = pid;
436		asr->a_rtime = 0;
437	}
438
439	if (WRAP(clock_gettime)(CLOCK_MONOTONIC, &ts) == -1)
440		return;
441
442	if ((ts.tv_sec - asr->a_rtime) < RELOAD_DELAY && asr->a_rtime != 0)
443		return;
444	asr->a_rtime = ts.tv_sec;
445
446	DPRINT("asr: checking for update of \"%s\"\n", _PATH_RESCONF);
447	if (stat(_PATH_RESCONF, &st) == -1 ||
448	    asr->a_mtime == st.st_mtime ||
449	    (ac = asr_ctx_create()) == NULL)
450		return;
451	asr->a_mtime = st.st_mtime;
452
453	DPRINT("asr: reloading config file\n");
454	if (asr_ctx_from_file(ac, _PATH_RESCONF) == -1) {
455		asr_ctx_free(ac);
456		return;
457	}
458
459	asr_ctx_envopts(ac);
460	if (asr->a_ctx)
461		_asr_ctx_unref(asr->a_ctx);
462	asr->a_ctx = ac;
463}
464
465/*
466 * Construct a fully-qualified domain name for the given name and domain.
467 * If "name" ends with a '.' it is considered as a FQDN by itself.
468 * Otherwise, the domain, which must be a FQDN, is appended to "name" (it
469 * may have a leading dot which would be ignored). If the domain is null,
470 * then "." is used. Return the length of the constructed FQDN or (0) on
471 * error.
472 */
473size_t
474_asr_make_fqdn(const char *name, const char *domain, char *buf, size_t buflen)
475{
476	size_t	len;
477
478	if (domain == NULL)
479		domain = ".";
480	else if ((len = strlen(domain)) == 0)
481		return (0);
482	else if (domain[len -1] != '.')
483		return (0);
484
485	len = strlen(name);
486	if (len == 0) {
487		if (strlcpy(buf, domain, buflen) >= buflen)
488			return (0);
489	} else if (name[len - 1] !=  '.') {
490		if (domain[0] == '.')
491			domain += 1;
492		if (strlcpy(buf, name, buflen) >= buflen ||
493		    strlcat(buf, ".", buflen) >= buflen ||
494		    strlcat(buf, domain, buflen) >= buflen)
495			return (0);
496	} else {
497		if (strlcpy(buf, name, buflen) >= buflen)
498			return (0);
499	}
500
501	return (strlen(buf));
502}
503
504/*
505 * Count the dots in a string.
506 */
507static int
508asr_ndots(const char *s)
509{
510	int n;
511
512	for (n = 0; *s; s++)
513		if (*s == '.')
514			n += 1;
515
516	return (n);
517}
518
519/*
520 * Allocate a new empty context.
521 */
522static struct asr_ctx *
523asr_ctx_create(void)
524{
525	struct asr_ctx	*ac;
526
527	if ((ac = calloc(1, sizeof(*ac))) == NULL)
528		return (NULL);
529
530	ac->ac_options = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
531	ac->ac_refcount = 1;
532	ac->ac_ndots = 1;
533	ac->ac_family[0] = AF_INET;
534	ac->ac_family[1] = AF_INET6;
535	ac->ac_family[2] = -1;
536
537	ac->ac_nscount = 0;
538	ac->ac_nstimeout = 5;
539	ac->ac_nsretries = 4;
540
541	return (ac);
542}
543
544struct asr_ctx *
545_asr_no_resolver(void)
546{
547	return asr_ctx_create();
548}
549
550/*
551 * Add a search domain to the async context.
552 */
553static int
554asr_ctx_add_searchdomain(struct asr_ctx *ac, const char *domain)
555{
556	char buf[MAXDNAME];
557
558	if (ac->ac_domcount == ASR_MAXDOM)
559		return (-1);
560
561	if (_asr_make_fqdn(domain, NULL, buf, sizeof(buf)) == 0)
562		return (-1);
563
564	if ((ac->ac_dom[ac->ac_domcount] = strdup(buf)) == NULL)
565		return (0);
566
567	ac->ac_domcount += 1;
568
569	return (1);
570}
571
572static int
573strsplit(char *line, char **tokens, int ntokens)
574{
575	int	ntok;
576	char	*cp, **tp;
577
578	for (cp = line, tp = tokens, ntok = 0;
579	    ntok < ntokens && (*tp = strsep(&cp, " \t")) != NULL; )
580		if (**tp != '\0') {
581			tp++;
582			ntok++;
583		}
584
585	return (ntok);
586}
587
588/*
589 * Pass on a split config line.
590 */
591static void
592pass0(char **tok, int n, struct asr_ctx *ac)
593{
594	int		 i, j, d;
595	const char	*e;
596	struct sockaddr_storage	ss;
597
598	if (!strcmp(tok[0], "nameserver")) {
599		if (ac->ac_nscount == ASR_MAXNS)
600			return;
601		if (n != 2)
602			return;
603		if (asr_parse_nameserver((struct sockaddr *)&ss, tok[1]))
604			return;
605		if ((ac->ac_ns[ac->ac_nscount] = calloc(1, ss.ss_len)) == NULL)
606			return;
607		memmove(ac->ac_ns[ac->ac_nscount], &ss, ss.ss_len);
608		ac->ac_nscount += 1;
609
610	} else if (!strcmp(tok[0], "domain")) {
611		if (n != 2)
612			return;
613		if (ac->ac_domain)
614			return;
615		ac->ac_domain = strdup(tok[1]);
616
617	} else if (!strcmp(tok[0], "lookup")) {
618		/* ensure that each lookup is only given once */
619		for (i = 1; i < n; i++)
620			for (j = i + 1; j < n; j++)
621				if (!strcmp(tok[i], tok[j]))
622					return;
623		ac->ac_dbcount = 0;
624		for (i = 1; i < n && ac->ac_dbcount < ASR_MAXDB; i++) {
625			if (!strcmp(tok[i], "yp")) {
626				/* silently deprecated */
627			} else if (!strcmp(tok[i], "bind"))
628				ac->ac_db[ac->ac_dbcount++] = ASR_DB_DNS;
629			else if (!strcmp(tok[i], "file"))
630				ac->ac_db[ac->ac_dbcount++] = ASR_DB_FILE;
631		}
632	} else if (!strcmp(tok[0], "search")) {
633		/* resolv.conf says the last line wins */
634		for (i = 0; i < ASR_MAXDOM; i++) {
635			free(ac->ac_dom[i]);
636			ac->ac_dom[i] = NULL;
637		}
638		ac->ac_domcount = 0;
639		for (i = 1; i < n; i++)
640			asr_ctx_add_searchdomain(ac, tok[i]);
641
642	} else if (!strcmp(tok[0], "family")) {
643		if (n == 1 || n > 3)
644			return;
645		for (i = 1; i < n; i++)
646			if (strcmp(tok[i], "inet4") && strcmp(tok[i], "inet6"))
647				return;
648		for (i = 1; i < n; i++)
649			ac->ac_family[i - 1] = strcmp(tok[i], "inet4") ? \
650			    AF_INET6 : AF_INET;
651		ac->ac_family[i - 1] = -1;
652
653	} else if (!strcmp(tok[0], "options")) {
654		for (i = 1; i < n; i++) {
655			if (!strcmp(tok[i], "tcp"))
656				ac->ac_options |= RES_USEVC;
657			else if (!strcmp(tok[i], "edns0"))
658				ac->ac_options |= RES_USE_EDNS0;
659			else if ((!strncmp(tok[i], "ndots:", 6))) {
660				e = NULL;
661				d = strtonum(tok[i] + 6, 1, 16, &e);
662				if (e == NULL)
663					ac->ac_ndots = d;
664			} else if (!strcmp(tok[i], "trust-ad"))
665				ac->ac_options |= RES_TRUSTAD;
666		}
667	}
668}
669
670/*
671 * Setup an async context with the config specified in the string "str".
672 */
673static int
674asr_ctx_from_string(struct asr_ctx *ac, const char *str)
675{
676	struct sockaddr_in6	*sin6;
677	struct sockaddr_in	*sin;
678	int			 i, trustad;
679	char			 buf[512], *ch;
680
681	asr_ctx_parse(ac, str);
682
683	if (ac->ac_dbcount == 0) {
684		/* No lookup directive */
685		asr_ctx_parse(ac, DEFAULT_LOOKUP);
686	}
687
688	if (ac->ac_nscount == 0)
689		asr_ctx_parse(ac, "nameserver 127.0.0.1");
690
691	if (ac->ac_domain == NULL)
692		if (gethostname(buf, sizeof buf) == 0) {
693			ch = strchr(buf, '.');
694			if (ch)
695				ac->ac_domain = strdup(ch + 1);
696			else /* Assume root. see resolv.conf(5) */
697				ac->ac_domain = strdup("");
698		}
699
700	/* If no search domain was specified, use the local subdomains */
701	if (ac->ac_domcount == 0)
702		for (ch = ac->ac_domain; ch; ) {
703			asr_ctx_add_searchdomain(ac, ch);
704			ch = strchr(ch, '.');
705			if (ch && asr_ndots(++ch) == 0)
706				break;
707		}
708
709	trustad = 1;
710	for (i = 0; i < ac->ac_nscount && trustad; i++) {
711		switch (ac->ac_ns[i]->sa_family) {
712		case AF_INET:
713			sin = (struct sockaddr_in *)ac->ac_ns[i];
714			if (sin->sin_addr.s_addr != htonl(INADDR_LOOPBACK))
715				trustad = 0;
716			break;
717		case AF_INET6:
718			sin6 = (struct sockaddr_in6 *)ac->ac_ns[i];
719			if (!IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr))
720				trustad = 0;
721			break;
722		default:
723			trustad = 0;
724			break;
725		}
726	}
727	if (trustad)
728		ac->ac_options |= RES_TRUSTAD;
729
730	return (0);
731}
732
733/*
734 * Setup the "ac" async context from the file at location "path".
735 */
736static int
737asr_ctx_from_file(struct asr_ctx *ac, const char *path)
738{
739	FILE	*cf;
740	char	 buf[4096];
741	ssize_t	 r;
742
743	cf = fopen(path, "re");
744	if (cf == NULL)
745		return (-1);
746
747	r = fread(buf, 1, sizeof buf - 1, cf);
748	if (feof(cf) == 0) {
749		DPRINT("asr: config file too long: \"%s\"\n", path);
750		r = -1;
751	}
752	fclose(cf);
753	if (r == -1)
754		return (-1);
755	buf[r] = '\0';
756
757	return asr_ctx_from_string(ac, buf);
758}
759
760/*
761 * Parse lines in the configuration string. For each one, split it into
762 * tokens and pass them to "pass0" for processing.
763 */
764static int
765asr_ctx_parse(struct asr_ctx *ac, const char *str)
766{
767	size_t		 len;
768	const char	*line;
769	char		 buf[1024];
770	char		*tok[10];
771	int		 ntok;
772
773	line = str;
774	while (*line) {
775		len = strcspn(line, "\n\0");
776		if (len < sizeof buf) {
777			memmove(buf, line, len);
778			buf[len] = '\0';
779		} else
780			buf[0] = '\0';
781		line += len;
782		if (*line == '\n')
783			line++;
784		buf[strcspn(buf, ";#")] = '\0';
785		if ((ntok = strsplit(buf, tok, 10)) == 0)
786			continue;
787
788		pass0(tok, ntok, ac);
789	}
790
791	return (0);
792}
793
794/*
795 * Check for environment variables altering the configuration as described
796 * in resolv.conf(5).  Although not documented there, this feature is disabled
797 * for setuid/setgid programs.
798 */
799static void
800asr_ctx_envopts(struct asr_ctx *ac)
801{
802	char	buf[4096], *e;
803	size_t	s;
804
805	if (issetugid()) {
806		ac->ac_options |= RES_NOALIASES;
807		return;
808	}
809
810	if ((e = getenv("RES_OPTIONS")) != NULL) {
811		strlcpy(buf, "options ", sizeof buf);
812		strlcat(buf, e, sizeof buf);
813		s = strlcat(buf, "\n", sizeof buf);
814		if (s < sizeof buf)
815			asr_ctx_parse(ac, buf);
816	}
817
818	if ((e = getenv("LOCALDOMAIN")) != NULL) {
819		strlcpy(buf, "search ", sizeof buf);
820		strlcat(buf, e, sizeof buf);
821		s = strlcat(buf, "\n", sizeof buf);
822		if (s < sizeof buf)
823			asr_ctx_parse(ac, buf);
824	}
825}
826
827/*
828 * Parse a resolv.conf(5) nameserver string into a sockaddr.
829 */
830static int
831asr_parse_nameserver(struct sockaddr *sa, const char *s)
832{
833	in_port_t	 portno = 53;
834
835	if (_asr_sockaddr_from_str(sa, PF_UNSPEC, s) == -1)
836		return (-1);
837
838	if (sa->sa_family == PF_INET)
839		((struct sockaddr_in *)sa)->sin_port = htons(portno);
840	else if (sa->sa_family == PF_INET6)
841		((struct sockaddr_in6 *)sa)->sin6_port = htons(portno);
842
843	return (0);
844}
845
846/*
847 * Turn a (uncompressed) DNS domain name into a regular nul-terminated string
848 * where labels are separated by dots. The result is put into the "buf" buffer,
849 * truncated if it exceeds "max" chars. The function returns "buf".
850 */
851char *
852_asr_strdname(const char *_dname, char *buf, size_t max)
853{
854	const unsigned char *dname = _dname;
855	char	*res;
856	size_t	 left, count;
857
858	if (_dname[0] == 0) {
859		strlcpy(buf, ".", max);
860		return buf;
861	}
862
863	res = buf;
864	left = max - 1;
865	while (dname[0] && left) {
866		count = (dname[0] < (left - 1)) ? dname[0] : (left - 1);
867		memmove(buf, dname + 1, count);
868		dname += dname[0] + 1;
869		left -= count;
870		buf += count;
871		if (left) {
872			left -= 1;
873			*buf++ = '.';
874		}
875	}
876	buf[0] = 0;
877
878	return (res);
879}
880
881/*
882 * Read and split the next line from the given namedb file.
883 * Return -1 on error, or put the result in the "tokens" array of
884 * size "ntoken" and returns the number of token on the line.
885 */
886int
887_asr_parse_namedb_line(FILE *file, char **tokens, int ntoken, char *lbuf, size_t sz)
888{
889	size_t	  len;
890	char	 *buf;
891	int	  ntok;
892
893    again:
894	if ((buf = fgetln(file, &len)) == NULL)
895		return (-1);
896
897	if (len >= sz)
898		goto again;
899
900	if (buf[len - 1] == '\n')
901		len--;
902	else {
903		memcpy(lbuf, buf, len);
904		buf = lbuf;
905	}
906
907	buf[len] = '\0';
908	buf[strcspn(buf, "#")] = '\0';
909	if ((ntok = strsplit(buf, tokens, ntoken)) == 0)
910		goto again;
911
912	return (ntok);
913}
914
915/*
916 * Update the async context so that it uses the next configured DB.
917 * Return 0 on success, or -1 if no more DBs is available.
918 */
919int
920_asr_iter_db(struct asr_query *as)
921{
922	if (as->as_db_idx >= as->as_ctx->ac_dbcount) {
923		DPRINT("asr_iter_db: done\n");
924		return (-1);
925	}
926
927	as->as_db_idx += 1;
928	DPRINT("asr_iter_db: %i\n", as->as_db_idx);
929
930	return (0);
931}
932