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