xref: /openbsd-src/lib/libc/asr/asr.c (revision aea60bee5e9bad0aab62f480f19c2fb34c068de4)
1 /*	$OpenBSD: asr.c,v 1.35 2015/01/16 16:48:51 deraadt 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 <err.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <resolv.h>
31 #include <poll.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <limits.h>
37 
38 #include "asr_private.h"
39 
40 #ifndef ASR_OPT_THREADSAFE
41 #define ASR_OPT_THREADSAFE 1
42 #endif
43 #ifndef ASR_OPT_HOSTALIASES
44 #define ASR_OPT_HOSTALIASES 1
45 #endif
46 #ifndef ASR_OPT_ENVOPTS
47 #define ASR_OPT_ENVOPTS 1
48 #endif
49 #ifndef ASR_OPT_RELOADCONF
50 #define ASR_OPT_RELOADCONF 1
51 #endif
52 #ifndef ASR_OPT_ALTCONF
53 #define ASR_OPT_ALTCONF 1
54 #endif
55 
56 #if ASR_OPT_THREADSAFE
57 #include "thread_private.h"
58 #endif
59 
60 #define DEFAULT_CONFFILE	"/etc/resolv.conf"
61 #define DEFAULT_HOSTFILE	"/etc/hosts"
62 #define DEFAULT_CONF		"lookup file\n"
63 #define DEFAULT_LOOKUP		"lookup bind file"
64 
65 #define RELOAD_DELAY		15 /* seconds */
66 
67 static void asr_check_reload(struct asr *);
68 static struct asr_ctx *asr_ctx_create(void);
69 static void asr_ctx_ref(struct asr_ctx *);
70 static void asr_ctx_free(struct asr_ctx *);
71 static int asr_ctx_add_searchdomain(struct asr_ctx *, const char *);
72 static int asr_ctx_from_file(struct asr_ctx *, const char *);
73 static int asr_ctx_from_string(struct asr_ctx *, const char *);
74 static int asr_ctx_parse(struct asr_ctx *, const char *);
75 static int asr_parse_nameserver(struct sockaddr *, const char *);
76 static int asr_ndots(const char *);
77 static void pass0(char **, int, struct asr_ctx *);
78 static int strsplit(char *, char **, int);
79 #if ASR_OPT_ENVOPTS
80 static void asr_ctx_envopts(struct asr_ctx *);
81 #endif
82 #if ASR_OPT_THREADSAFE
83 static void *__THREAD_NAME(_asr);
84 #else
85 #	define _THREAD_PRIVATE(a, b, c)  (c)
86 #endif
87 
88 static struct asr *_asr = NULL;
89 
90 /* Allocate and configure an async "resolver". */
91 void *
92 asr_resolver(const char *conf)
93 {
94 	static int	 init = 0;
95 	struct asr	*asr;
96 
97 	if (init == 0) {
98 #ifdef DEBUG
99 		if (getenv("ASR_DEBUG"))
100 			asr_debug = stderr;
101 #endif
102 		init = 1;
103 	}
104 
105 	if ((asr = calloc(1, sizeof(*asr))) == NULL)
106 		goto fail;
107 
108 #if ASR_OPT_ALTCONF
109 	/* If not setuid/setgid, allow to use an alternate config. */
110 	if (conf == NULL && !issetugid())
111 		conf = getenv("ASR_CONFIG");
112 #endif
113 
114 	if (conf == NULL)
115 		conf = DEFAULT_CONFFILE;
116 
117 	if (conf[0] == '!') {
118 		/* Use the rest of the string as config file */
119 		if ((asr->a_ctx = asr_ctx_create()) == NULL)
120 			goto fail;
121 		if (asr_ctx_from_string(asr->a_ctx, conf + 1) == -1)
122 			goto fail;
123 	} else {
124 		/* Use the given config file */
125 		asr->a_path = strdup(conf);
126 		if (asr->a_path == NULL)
127 			goto fail;
128 		asr_check_reload(asr);
129 		if (asr->a_ctx == NULL) {
130 			if ((asr->a_ctx = asr_ctx_create()) == NULL)
131 				goto fail;
132 			if (asr_ctx_from_string(asr->a_ctx, DEFAULT_CONF) == -1)
133 				goto fail;
134 #if ASR_OPT_ENVOPTS
135 			asr_ctx_envopts(asr->a_ctx);
136 #endif
137 		}
138 	}
139 
140 #ifdef DEBUG
141 	asr_dump_config(asr_debug, asr);
142 #endif
143 	return (asr);
144 
145     fail:
146 	if (asr) {
147 		if (asr->a_ctx)
148 			asr_ctx_free(asr->a_ctx);
149 		free(asr->a_path);
150 		free(asr);
151 	}
152 
153 	return (NULL);
154 }
155 
156 /*
157  * Free the "asr" async resolver (or the thread-local resolver if NULL).
158  * Drop the reference to the current context.
159  */
160 void
161 asr_resolver_done(void *arg)
162 {
163 	struct asr *asr = arg;
164 	struct asr **priv;
165 
166 	if (asr == NULL) {
167 		priv = _THREAD_PRIVATE(_asr, _asr, &_asr);
168 		if (*priv == NULL)
169 			return;
170 		asr = *priv;
171 		*priv = NULL;
172 	}
173 
174 	asr_ctx_unref(asr->a_ctx);
175 	free(asr->a_path);
176 	free(asr);
177 }
178 
179 /*
180  * Cancel an async query.
181  */
182 void
183 asr_abort(struct asr_query *as)
184 {
185 	asr_async_free(as);
186 }
187 
188 /*
189  * Resume the "as" async query resolution.  Return one of ASYNC_COND,
190  * or ASYNC_DONE and put query-specific return values in the user-allocated
191  * memory at "ar".
192  */
193 int
194 asr_run(struct asr_query *as, struct asr_result *ar)
195 {
196 	int	r, saved_errno = errno;
197 
198 	DPRINT("asr: asr_run(%p, %p) %s ctx=[%p]\n", as, ar,
199 	    asr_querystr(as->as_type), as->as_ctx);
200 	r = as->as_run(as, ar);
201 
202 	DPRINT("asr: asr_run(%p, %p) -> %s", as, ar, asr_transitionstr(r));
203 #ifdef DEBUG
204 	if (r == ASYNC_COND)
205 #endif
206 		DPRINT(" fd=%i timeout=%i", ar->ar_fd, ar->ar_timeout);
207 	DPRINT("\n");
208 	if (r == ASYNC_DONE)
209 		asr_async_free(as);
210 
211 	errno = saved_errno;
212 
213 	return (r);
214 }
215 
216 /*
217  * Same as above, 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 	again:
229 		r = poll(fds, 1, ar->ar_timeout);
230 		if (r == -1 && errno == EINTR)
231 			goto again;
232 		/*
233 		 * Otherwise, just ignore the error and let asr_run()
234 		 * catch the failure.
235 		 */
236 	}
237 
238 	errno = saved_errno;
239 
240 	return (r);
241 }
242 
243 /*
244  * Create a new async request of the given "type" on the async context "ac".
245  * Take a reference on it so it does not gets deleted while the async query
246  * is running.
247  */
248 struct asr_query *
249 asr_async_new(struct asr_ctx *ac, int type)
250 {
251 	struct asr_query	*as;
252 
253 	DPRINT("asr: asr_async_new(ctx=%p) type=%i refcount=%i\n", ac, type,
254 	    ac ? ac->ac_refcount : 0);
255 	if (ac == NULL || (as = calloc(1, sizeof(*as))) == NULL)
256 		return (NULL);
257 
258 	ac->ac_refcount += 1;
259 	as->as_ctx = ac;
260 	as->as_fd = -1;
261 	as->as_type = type;
262 	as->as_state = ASR_STATE_INIT;
263 
264 	return (as);
265 }
266 
267 /*
268  * Free an async query and unref the associated context.
269  */
270 void
271 asr_async_free(struct asr_query *as)
272 {
273 	DPRINT("asr: asr_async_free(%p)\n", as);
274 	switch (as->as_type) {
275 	case ASR_SEND:
276 		if (as->as_fd != -1)
277 			close(as->as_fd);
278 		if (as->as.dns.obuf && !(as->as.dns.flags & ASYNC_EXTOBUF))
279 			free(as->as.dns.obuf);
280 		if (as->as.dns.ibuf)
281 			free(as->as.dns.ibuf);
282 		if (as->as.dns.dname)
283 			free(as->as.dns.dname);
284 		break;
285 
286 	case ASR_SEARCH:
287 		if (as->as.search.subq)
288 			asr_async_free(as->as.search.subq);
289 		if (as->as.search.name)
290 			free(as->as.search.name);
291 		break;
292 
293 	case ASR_GETRRSETBYNAME:
294 		if (as->as.rrset.subq)
295 			asr_async_free(as->as.rrset.subq);
296 		if (as->as.rrset.name)
297 			free(as->as.rrset.name);
298 		break;
299 
300 	case ASR_GETHOSTBYNAME:
301 	case ASR_GETHOSTBYADDR:
302 		if (as->as.hostnamadr.subq)
303 			asr_async_free(as->as.hostnamadr.subq);
304 		if (as->as.hostnamadr.name)
305 			free(as->as.hostnamadr.name);
306 		break;
307 
308 	case ASR_GETNETBYNAME:
309 	case ASR_GETNETBYADDR:
310 		if (as->as.netnamadr.subq)
311 			asr_async_free(as->as.netnamadr.subq);
312 		if (as->as.netnamadr.name)
313 			free(as->as.netnamadr.name);
314 		break;
315 
316 	case ASR_GETADDRINFO:
317 		if (as->as.ai.subq)
318 			asr_async_free(as->as.ai.subq);
319 		if (as->as.ai.aifirst)
320 			freeaddrinfo(as->as.ai.aifirst);
321 		if (as->as.ai.hostname)
322 			free(as->as.ai.hostname);
323 		if (as->as.ai.servname)
324 			free(as->as.ai.servname);
325 		if (as->as.ai.fqdn)
326 			free(as->as.ai.fqdn);
327 		break;
328 
329 	case ASR_GETNAMEINFO:
330 		if (as->as.ni.subq)
331 			asr_async_free(as->as.ni.subq);
332 		break;
333 	}
334 
335 	asr_ctx_unref(as->as_ctx);
336 	free(as);
337 }
338 
339 /*
340  * Get a context from the given resolver. This takes a new reference to
341  * the returned context, which *must* be explicitely dropped when done
342  * using this context.
343  */
344 struct asr_ctx *
345 asr_use_resolver(void *arg)
346 {
347 	struct asr *asr = arg;
348 	struct asr **priv;
349 
350 	if (asr == NULL) {
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(NULL);
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 #if ASR_OPT_RELOADCONF
414 	struct stat	 st;
415 	struct timespec	 ts;
416 #endif
417 
418 	if (asr->a_path == NULL)
419 		return;
420 
421 #if ASR_OPT_RELOADCONF
422 	if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
423 		return;
424 
425 	if ((ts.tv_sec - asr->a_rtime) < RELOAD_DELAY && asr->a_rtime != 0)
426 		return;
427 	asr->a_rtime = ts.tv_sec;
428 
429 	DPRINT("asr: checking for update of \"%s\"\n", asr->a_path);
430 	if (stat(asr->a_path, &st) == -1 ||
431 	    asr->a_mtime == st.st_mtime ||
432 	    (ac = asr_ctx_create()) == NULL)
433 		return;
434 	asr->a_mtime = st.st_mtime;
435 #else
436 	if ((ac = asr_ctx_create()) == NULL)
437 		return;
438 #endif
439 
440 	DPRINT("asr: reloading config file\n");
441 	if (asr_ctx_from_file(ac, asr->a_path) == -1) {
442 		asr_ctx_free(ac);
443 		return;
444 	}
445 
446 #if ASR_OPT_ENVOPTS
447 	asr_ctx_envopts(ac);
448 #endif
449 	if (asr->a_ctx)
450 		asr_ctx_unref(asr->a_ctx);
451 	asr->a_ctx = ac;
452 }
453 
454 /*
455  * Construct a fully-qualified domain name for the given name and domain.
456  * If "name" ends with a '.' it is considered as a FQDN by itself.
457  * Otherwise, the domain, which must be a FQDN, is appended to "name" (it
458  * may have a leading dot which would be ignored). If the domain is null,
459  * then "." is used. Return the length of the constructed FQDN or (0) on
460  * error.
461  */
462 size_t
463 asr_make_fqdn(const char *name, const char *domain, char *buf, size_t buflen)
464 {
465 	size_t	len;
466 
467 	if (domain == NULL)
468 		domain = ".";
469 	else if ((len = strlen(domain)) == 0)
470 		return (0);
471 	else if (domain[len -1] != '.')
472 		return (0);
473 
474 	len = strlen(name);
475 	if (len == 0) {
476 		if (strlcpy(buf, domain, buflen) >= buflen)
477 			return (0);
478 	} else if (name[len - 1] !=  '.') {
479 		if (domain[0] == '.')
480 			domain += 1;
481 		if (strlcpy(buf, name, buflen) >= buflen ||
482 		    strlcat(buf, ".", buflen) >= buflen ||
483 		    strlcat(buf, domain, buflen) >= buflen)
484 			return (0);
485 	} else {
486 		if (strlcpy(buf, name, buflen) >= buflen)
487 			return (0);
488 	}
489 
490 	return (strlen(buf));
491 }
492 
493 /*
494  * Count the dots in a string.
495  */
496 static int
497 asr_ndots(const char *s)
498 {
499 	int n;
500 
501 	for (n = 0; *s; s++)
502 		if (*s == '.')
503 			n += 1;
504 
505 	return (n);
506 }
507 
508 /*
509  * Allocate a new empty context.
510  */
511 static struct asr_ctx *
512 asr_ctx_create(void)
513 {
514 	struct asr_ctx	*ac;
515 
516 	if ((ac = calloc(1, sizeof(*ac))) == NULL)
517 		return (NULL);
518 
519 	ac->ac_options = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
520 	ac->ac_refcount = 1;
521 	ac->ac_ndots = 1;
522 	ac->ac_family[0] = AF_INET;
523 	ac->ac_family[1] = AF_INET6;
524 	ac->ac_family[2] = -1;
525 
526 	ac->ac_hostfile = DEFAULT_HOSTFILE;
527 
528 	ac->ac_nscount = 0;
529 	ac->ac_nstimeout = 5;
530 	ac->ac_nsretries = 4;
531 
532 	return (ac);
533 }
534 
535 /*
536  * Add a search domain to the async context.
537  */
538 static int
539 asr_ctx_add_searchdomain(struct asr_ctx *ac, const char *domain)
540 {
541 	char buf[MAXDNAME];
542 
543 	if (ac->ac_domcount == ASR_MAXDOM)
544 		return (-1);
545 
546 	if (asr_make_fqdn(domain, NULL, buf, sizeof(buf)) == 0)
547 		return (-1);
548 
549 	if ((ac->ac_dom[ac->ac_domcount] = strdup(buf)) == NULL)
550 		return (0);
551 
552 	ac->ac_domcount += 1;
553 
554 	return (1);
555 }
556 
557 static int
558 strsplit(char *line, char **tokens, int ntokens)
559 {
560 	int	ntok;
561 	char	*cp, **tp;
562 
563 	for (cp = line, tp = tokens, ntok = 0;
564 	    ntok < ntokens && (*tp = strsep(&cp, " \t")) != NULL; )
565 		if (**tp != '\0') {
566 			tp++;
567 			ntok++;
568 		}
569 
570 	return (ntok);
571 }
572 
573 /*
574  * Pass on a split config line.
575  */
576 static void
577 pass0(char **tok, int n, struct asr_ctx *ac)
578 {
579 	int		 i, j, d;
580 	const char	*e;
581 	struct sockaddr_storage	ss;
582 
583 	if (!strcmp(tok[0], "nameserver")) {
584 		if (ac->ac_nscount == ASR_MAXNS)
585 			return;
586 		if (n != 2)
587 			return;
588 		if (asr_parse_nameserver((struct sockaddr *)&ss, tok[1]))
589 			return;
590 		if ((ac->ac_ns[ac->ac_nscount] = calloc(1, ss.ss_len)) == NULL)
591 			return;
592 		memmove(ac->ac_ns[ac->ac_nscount], &ss, ss.ss_len);
593 		ac->ac_nscount += 1;
594 
595 	} else if (!strcmp(tok[0], "domain")) {
596 		if (n != 2)
597 			return;
598 		if (ac->ac_domain)
599 			return;
600 		ac->ac_domain = strdup(tok[1]);
601 
602 	} else if (!strcmp(tok[0], "lookup")) {
603 		/* ensure that each lookup is only given once */
604 		for (i = 1; i < n; i++)
605 			for (j = i + 1; j < n; j++)
606 				if (!strcmp(tok[i], tok[j]))
607 					return;
608 		ac->ac_dbcount = 0;
609 		for (i = 1; i < n && ac->ac_dbcount < ASR_MAXDB; i++) {
610 			if (!strcmp(tok[i], "yp"))
611 				ac->ac_db[ac->ac_dbcount++] = ASR_DB_YP;
612 			else if (!strcmp(tok[i], "bind"))
613 				ac->ac_db[ac->ac_dbcount++] = ASR_DB_DNS;
614 			else if (!strcmp(tok[i], "file"))
615 				ac->ac_db[ac->ac_dbcount++] = ASR_DB_FILE;
616 		}
617 	} else if (!strcmp(tok[0], "search")) {
618 		/* resolv.conf says the last line wins */
619 		for (i = 0; i < ASR_MAXDOM; i++)
620 			free(ac->ac_dom[i]);
621 		ac->ac_domcount = 0;
622 		for (i = 1; i < n; i++)
623 			asr_ctx_add_searchdomain(ac, tok[i]);
624 
625 	} else if (!strcmp(tok[0], "family")) {
626 		if (n == 1 || n > 3)
627 			return;
628 		for (i = 1; i < n; i++)
629 			if (strcmp(tok[i], "inet4") && strcmp(tok[i], "inet6"))
630 				return;
631 		for (i = 1; i < n; i++)
632 			ac->ac_family[i - 1] = strcmp(tok[i], "inet4") ? \
633 			    AF_INET6 : AF_INET;
634 		ac->ac_family[i - 1] = -1;
635 
636 	} else if (!strcmp(tok[0], "options")) {
637 		for (i = 1; i < n; i++) {
638 			if (!strcmp(tok[i], "tcp"))
639 				ac->ac_options |= RES_USEVC;
640 			else if ((!strncmp(tok[i], "ndots:", 6))) {
641 				e = NULL;
642 				d = strtonum(tok[i] + 6, 1, 16, &e);
643 				if (e == NULL)
644 					ac->ac_ndots = d;
645 			}
646 		}
647 	}
648 }
649 
650 /*
651  * Setup an async context with the config specified in the string "str".
652  */
653 static int
654 asr_ctx_from_string(struct asr_ctx *ac, const char *str)
655 {
656 	char		 buf[512], *ch;
657 
658 	asr_ctx_parse(ac, str);
659 
660 	if (ac->ac_dbcount == 0) {
661 		/* No lookup directive */
662 		asr_ctx_parse(ac, DEFAULT_LOOKUP);
663 	}
664 
665 	if (ac->ac_nscount == 0)
666 		asr_ctx_parse(ac, "nameserver 127.0.0.1");
667 
668 	if (ac->ac_domain == NULL)
669 		if (gethostname(buf, sizeof buf) == 0) {
670 			ch = strchr(buf, '.');
671 			if (ch)
672 				ac->ac_domain = strdup(ch + 1);
673 			else /* Assume root. see resolv.conf(5) */
674 				ac->ac_domain = strdup("");
675 		}
676 
677 	/* If no search domain was specified, use the local subdomains */
678 	if (ac->ac_domcount == 0)
679 		for (ch = ac->ac_domain; ch; ) {
680 			asr_ctx_add_searchdomain(ac, ch);
681 			ch = strchr(ch, '.');
682 			if (ch && asr_ndots(++ch) == 0)
683 				break;
684 		}
685 
686 	return (0);
687 }
688 
689 /*
690  * Setup the "ac" async context from the file at location "path".
691  */
692 static int
693 asr_ctx_from_file(struct asr_ctx *ac, const char *path)
694 {
695 	FILE	*cf;
696 	char	 buf[4096];
697 	ssize_t	 r;
698 
699 	cf = fopen(path, "re");
700 	if (cf == NULL)
701 		return (-1);
702 
703 	r = fread(buf, 1, sizeof buf - 1, cf);
704 	if (feof(cf) == 0) {
705 		DPRINT("asr: config file too long: \"%s\"\n", path);
706 		r = -1;
707 	}
708 	fclose(cf);
709 	if (r == -1)
710 		return (-1);
711 	buf[r] = '\0';
712 
713 	return asr_ctx_from_string(ac, buf);
714 }
715 
716 /*
717  * Parse lines in the configuration string. For each one, split it into
718  * tokens and pass them to "pass0" for processing.
719  */
720 static int
721 asr_ctx_parse(struct asr_ctx *ac, const char *str)
722 {
723 	size_t		 len;
724 	const char	*line;
725 	char		 buf[1024];
726 	char		*tok[10];
727 	int		 ntok;
728 
729 	line = str;
730 	while (*line) {
731 		len = strcspn(line, "\n\0");
732 		if (len < sizeof buf) {
733 			memmove(buf, line, len);
734 			buf[len] = '\0';
735 		} else
736 			buf[0] = '\0';
737 		line += len;
738 		if (*line == '\n')
739 			line++;
740 		buf[strcspn(buf, ";#")] = '\0';
741 		if ((ntok = strsplit(buf, tok, 10)) == 0)
742 			continue;
743 
744 		pass0(tok, ntok, ac);
745 	}
746 
747 	return (0);
748 }
749 
750 #if ASR_OPT_ENVOPTS
751 /*
752  * Check for environment variables altering the configuration as described
753  * in resolv.conf(5).  Altough not documented there, this feature is disabled
754  * for setuid/setgid programs.
755  */
756 static void
757 asr_ctx_envopts(struct asr_ctx *ac)
758 {
759 	char	buf[4096], *e;
760 	size_t	s;
761 
762 	if (issetugid()) {
763 		ac->ac_options |= RES_NOALIASES;
764 		return;
765 	}
766 
767 	if ((e = getenv("RES_OPTIONS")) != NULL) {
768 		strlcpy(buf, "options ", sizeof buf);
769 		strlcat(buf, e, sizeof buf);
770 		s = strlcat(buf, "\n", sizeof buf);
771 		s = strlcat(buf, "\n", sizeof buf);
772 		if (s < sizeof buf)
773 			asr_ctx_parse(ac, buf);
774 	}
775 
776 	if ((e = getenv("LOCALDOMAIN")) != NULL) {
777 		strlcpy(buf, "search ", sizeof buf);
778 		strlcat(buf, e, sizeof buf);
779 		s = strlcat(buf, "\n", sizeof buf);
780 		if (s < sizeof buf)
781 			asr_ctx_parse(ac, buf);
782 	}
783 }
784 #endif
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 	const char	*estr;
793 	char		 buf[256];
794 	char		*port = NULL;
795 	in_port_t	 portno = 53;
796 
797 	if (*s == '[') {
798 		strlcpy(buf, s + 1, sizeof buf);
799 		s = buf;
800 		port = strchr(buf, ']');
801 		if (port == NULL)
802 			return (-1);
803 		*port++ = '\0';
804 		if (*port != ':')
805 			return (-1);
806 		port++;
807 	}
808 
809 	if (port) {
810 		portno = strtonum(port, 1, USHRT_MAX, &estr);
811 		if (estr)
812 			return (-1);
813 	}
814 
815 	if (asr_sockaddr_from_str(sa, PF_UNSPEC, s) == -1)
816 		return (-1);
817 
818 	if (sa->sa_family == PF_INET)
819 		((struct sockaddr_in *)sa)->sin_port = htons(portno);
820 	else if (sa->sa_family == PF_INET6)
821 		((struct sockaddr_in6 *)sa)->sin6_port = htons(portno);
822 
823 	return (0);
824 }
825 
826 /*
827  * Turn a (uncompressed) DNS domain name into a regular nul-terminated string
828  * where labels are separated by dots. The result is put into the "buf" buffer,
829  * truncated if it exceeds "max" chars. The function returns "buf".
830  */
831 char *
832 asr_strdname(const char *_dname, char *buf, size_t max)
833 {
834 	const unsigned char *dname = _dname;
835 	char	*res;
836 	size_t	 left, n, count;
837 
838 	if (_dname[0] == 0) {
839 		strlcpy(buf, ".", max);
840 		return buf;
841 	}
842 
843 	res = buf;
844 	left = max - 1;
845 	for (n = 0; dname[0] && left; n += dname[0]) {
846 		count = (dname[0] < (left - 1)) ? dname[0] : (left - 1);
847 		memmove(buf, dname + 1, count);
848 		dname += dname[0] + 1;
849 		left -= count;
850 		buf += count;
851 		if (left) {
852 			left -= 1;
853 			*buf++ = '.';
854 		}
855 	}
856 	buf[0] = 0;
857 
858 	return (res);
859 }
860 
861 /*
862  * Read and split the next line from the given namedb file.
863  * Return -1 on error, or put the result in the "tokens" array of
864  * size "ntoken" and returns the number of token on the line.
865  */
866 int
867 asr_parse_namedb_line(FILE *file, char **tokens, int ntoken)
868 {
869 	size_t	  len;
870 	char	 *buf;
871 	int	  ntok;
872 
873     again:
874 	if ((buf = fgetln(file, &len)) == NULL)
875 		return (-1);
876 
877 	if (buf[len - 1] == '\n')
878 		len--;
879 
880 	buf[len] = '\0';
881 	buf[strcspn(buf, "#")] = '\0';
882 	if ((ntok = strsplit(buf, tokens, ntoken)) == 0)
883 		goto again;
884 
885 	return (ntok);
886 }
887 
888 /*
889  * Update the async context so that it uses the next configured DB.
890  * Return 0 on success, or -1 if no more DBs is available.
891  */
892 int
893 asr_iter_db(struct asr_query *as)
894 {
895 	if (as->as_db_idx >= as->as_ctx->ac_dbcount) {
896 		DPRINT("asr_iter_db: done\n");
897 		return (-1);
898 	}
899 
900 	as->as_db_idx += 1;
901 	DPRINT("asr_iter_db: %i\n", as->as_db_idx);
902 
903 	return (0);
904 }
905 
906 /*
907  * Check if the hostname "name" is a user-defined alias as per hostname(7).
908  * If so, copies the result in the buffer "abuf" of size "abufsz" and
909  * return "abuf". Otherwise return NULL.
910  */
911 char *
912 asr_hostalias(struct asr_ctx *ac, const char *name, char *abuf, size_t abufsz)
913 {
914 #if ASR_OPT_HOSTALIASES
915 	FILE	 *fp;
916 	size_t	  len;
917 	char	 *file, *buf, *tokens[2];
918 	int	  ntok;
919 
920 	if (ac->ac_options & RES_NOALIASES ||
921 	    asr_ndots(name) != 0 ||
922 	    issetugid() ||
923 	    (file = getenv("HOSTALIASES")) == NULL ||
924 	    (fp = fopen(file, "re")) == NULL)
925 		return (NULL);
926 
927 	DPRINT("asr: looking up aliases in \"%s\"\n", file);
928 
929 	while ((buf = fgetln(fp, &len)) != NULL) {
930 		if (buf[len - 1] == '\n')
931 			len--;
932 		buf[len] = '\0';
933 		if ((ntok = strsplit(buf, tokens, 2)) != 2)
934 			continue;
935 		if (!strcasecmp(tokens[0], name)) {
936 			if (strlcpy(abuf, tokens[1], abufsz) > abufsz)
937 				continue;
938 			DPRINT("asr: found alias \"%s\"\n", abuf);
939 			fclose(fp);
940 			return (abuf);
941 		}
942 	}
943 
944 	fclose(fp);
945 #endif
946 	return (NULL);
947 }
948