xref: /openbsd-src/lib/libc/asr/asr.c (revision ac9b4aacc1da35008afea06a5d23c2f2dea9b93e)
1 /*	$OpenBSD: asr.c,v 1.4 2012/08/18 16:48:17 eric 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 #include <sys/types.h>
18 #include <sys/stat.h>
19 
20 #include <netinet/in.h>
21 #include <arpa/inet.h>
22 #include <arpa/nameser.h>
23 
24 #include <err.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <netdb.h>
28 #include <resolv.h>
29 #include <poll.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 
35 #include "asr.h"
36 #include "asr_private.h"
37 #include "thread_private.h"
38 
39 #define DEFAULT_CONFFILE	"/etc/resolv.conf"
40 #define DEFAULT_HOSTFILE	"/etc/hosts"
41 #define DEFAULT_CONF		"lookup bind file\nnameserver 127.0.0.1\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(const char*, int(*)(char**, int, struct asr_ctx*),
54     struct asr_ctx *);
55 static int asr_parse_nameserver(struct sockaddr *, const char *);
56 static char *asr_hostalias(const char *, char *, size_t);
57 static int asr_ndots(const char *);
58 static void asr_ctx_envopts(struct asr_ctx *);
59 static int pass0(char **, int, struct asr_ctx *);
60 
61 static void *__THREAD_NAME(_asr);
62 static struct asr *_asr = NULL;
63 
64 /* Allocate and configure an async "resolver". */
65 struct asr *
66 async_resolver(const char *conf)
67 {
68 	static int	 init = 0;
69 	struct asr	*asr;
70 
71 #ifdef DEBUG
72 	if (init == 0) {
73 		if (getenv("ASR_DEBUG"))
74 			asr_debug = 1;
75 		init = 1;
76 	}
77 #endif
78 	if ((asr = calloc(1, sizeof(*asr))) == NULL)
79 		goto fail;
80 
81 	/* If not setuid/setgid, allow to use an alternate config. */
82 	if (conf == NULL && !issetugid())
83 		conf = getenv("ASR_CONFIG");
84 
85 	if (conf == NULL)
86 		conf = DEFAULT_CONFFILE;
87 
88 	if (conf[0] == '!') {
89 		/* Use the rest of the string as config file */
90 		if ((asr->a_ctx = asr_ctx_create()) == NULL)
91 			goto fail;
92 		if (asr_ctx_from_string(asr->a_ctx, conf + 1) == -1)
93 			goto fail;
94 	} else {
95 		/* Use the given config file */
96 		asr->a_path = strdup(conf);
97 		asr_check_reload(asr);
98 		if (asr->a_ctx == NULL) {
99 			if ((asr->a_ctx = asr_ctx_create()) == NULL)
100 				goto fail;
101 			if (asr_ctx_from_string(asr->a_ctx, DEFAULT_CONF) == -1)
102 				goto fail;
103 			asr_ctx_envopts(asr->a_ctx);
104 		}
105 	}
106 
107 #ifdef DEBUG
108 	asr_dump(asr);
109 #endif
110 	return (asr);
111 
112     fail:
113 	if (asr) {
114 		if (asr->a_ctx)
115 			asr_ctx_free(asr->a_ctx);
116 		free(asr);
117 	}
118 
119 	return (NULL);
120 }
121 
122 /*
123  * Free the "asr" async resolver (or the thread-local resolver if NULL).
124  * Drop the reference to the current context.
125  */
126 void
127 async_resolver_done(struct asr *asr)
128 {
129 	struct asr **priv;
130 
131 	if (asr == NULL) {
132 		priv = _THREAD_PRIVATE(_asr, asr, &_asr);
133 		if (*priv == NULL)
134 			return;
135 		asr = *priv;
136 		*priv = NULL;
137 	}
138 
139 	asr_ctx_unref(asr->a_ctx);
140 	if (asr->a_path)
141 		free(asr->a_path);
142 	free(asr);
143 }
144 
145 /*
146  * Cancel an async query.
147  */
148 void
149 async_abort(struct async *as)
150 {
151 	async_free(as);
152 }
153 
154 /*
155  * Resume the "as" async query resolution.  Return one of ASYNC_COND,
156  * ASYNC_YIELD or ASYNC_DONE and put query-specific return values in
157  * the user-allocated memory at "ar".
158  */
159 int
160 async_run(struct async *as, struct async_res *ar)
161 {
162 	int	r, saved_errno = errno;
163 
164 #ifdef DEBUG
165 	asr_printf("asr: async_run(%p, %p) %s ctx=[%p]\n",
166 		as, ar, asr_querystr(as->as_type), as->as_ctx);
167 #endif
168 	r = as->as_run(as, ar);
169 
170 #ifdef DEBUG
171 	if (asr_debug) {
172 		asr_printf("asr: async_run(%p, %p) -> %s", as, ar,
173 		    asr_transitionstr(r));
174 		if (r == ASYNC_COND)
175 			asr_printf(" fd=%i timeout=%i\n",
176 			    ar->ar_fd, ar->ar_timeout);
177 		else
178 			asr_printf("\n");
179 		fflush(stderr);
180 	}
181 #endif
182 	if (r == ASYNC_DONE)
183 		async_free(as);
184 
185 	errno = saved_errno;
186 
187 	return (r);
188 }
189 
190 /*
191  * Same as above, but run in a loop that handles the fd conditions result.
192  */
193 int
194 async_run_sync(struct async *as, struct async_res *ar)
195 {
196 	struct pollfd	 fds[1];
197 	int		 r, saved_errno = errno;
198 
199 	while((r = async_run(as, ar)) == ASYNC_COND) {
200 		fds[0].fd = ar->ar_fd;
201 		fds[0].events = (ar->ar_cond == ASYNC_READ) ? POLLIN : POLLOUT;
202 	again:
203 		r = poll(fds, 1, ar->ar_timeout);
204 		if (r == -1 && errno == EINTR)
205 			goto again;
206 		/*
207 		 * Otherwise, just ignore the error and let async_run()
208 		 * catch the failure.
209 		 */
210 	}
211 
212 	errno = saved_errno;
213 
214 	return (r);
215 }
216 
217 /*
218  * Create a new async request of the given "type" on the async context "ac".
219  * Take a reference on it so it does not gets deleted while the async query
220  * is running.
221  */
222 struct async *
223 async_new(struct asr_ctx *ac, int type)
224 {
225 	struct async	*as;
226 #ifdef DEBUG
227 	asr_printf("asr: async_new(ctx=%p) type=%i refcount=%i\n",
228 	    ac, type, ac->ac_refcount);
229 #endif
230 	if ((as = calloc(1, sizeof(*as))) == NULL)
231 		return (NULL);
232 
233 	ac->ac_refcount += 1;
234 	as->as_ctx = ac;
235 	as->as_fd = -1;
236 	as->as_type = type;
237 	as->as_state = ASR_STATE_INIT;
238 
239 	return (as);
240 }
241 
242 /*
243  * Free an async query and unref the associated context.
244  */
245 void
246 async_free(struct async *as)
247 {
248 #ifdef DEBUG
249 	asr_printf("asr: async_free(%p)\n", as);
250 #endif
251 	switch(as->as_type) {
252 	case ASR_SEND:
253 		if (as->as_fd != -1)
254 			close(as->as_fd);
255 		if (as->as.dns.obuf && !(as->as.dns.flags & ASYNC_EXTOBUF))
256 			free (as->as.dns.obuf);
257 		if (as->as.dns.ibuf && !(as->as.dns.flags & ASYNC_EXTIBUF))
258 			free (as->as.dns.ibuf);
259 		if (as->as.dns.dname)
260 			free(as->as.dns.dname);
261 		break;
262 
263 	case ASR_SEARCH:
264 		if (as->as.search.subq)
265 			async_free(as->as.search.subq);
266 		if (as->as.search.name)
267 			free(as->as.search.name);
268 		break;
269 
270 	case ASR_GETRRSETBYNAME:
271 		if (as->as.rrset.subq)
272 			async_free(as->as.rrset.subq);
273 		if (as->as.rrset.name)
274 			free(as->as.rrset.name);
275 		break;
276 
277 	case ASR_GETHOSTBYNAME:
278 	case ASR_GETHOSTBYADDR:
279 		if (as->as.hostnamadr.subq)
280 			async_free(as->as.hostnamadr.subq);
281 		if (as->as.hostnamadr.name)
282 			free(as->as.hostnamadr.name);
283 		if (as->as.hostnamadr.dname)
284 			free(as->as.hostnamadr.dname);
285 		break;
286 
287 	case ASR_GETNETBYNAME:
288 	case ASR_GETNETBYADDR:
289 		if (as->as.netnamadr.subq)
290 			async_free(as->as.netnamadr.subq);
291 		if (as->as.netnamadr.name)
292 			free(as->as.netnamadr.name);
293 		break;
294 
295 	case ASR_GETADDRINFO:
296 		if (as->as.ai.subq)
297 			async_free(as->as.ai.subq);
298 		if (as->as.ai.aifirst)
299 			freeaddrinfo(as->as.ai.aifirst);
300 		if (as->as.ai.hostname)
301 			free(as->as.ai.hostname);
302 		if (as->as.ai.servname)
303 			free(as->as.ai.servname);
304 		break;
305 
306 	case ASR_GETNAMEINFO:
307 		if (as->as.ni.subq)
308 			async_free(as->as.ni.subq);
309 		break;
310 
311 	case ASR_HOSTADDR:
312 		if (as->as.host.name)
313 			free(as->as.host.name);
314 		if (as->as.host.subq)
315 			async_free(as->as.host.subq);
316 		if (as->as.host.pkt)
317 			free(as->as.host.pkt);
318 		if (as->as.host.file)
319 			fclose(as->as.host.file);
320 		break;
321 	}
322 
323 	asr_ctx_unref(as->as_ctx);
324 	free(as);
325 }
326 
327 /*
328  * Get a context from the given resolver. This takes a new reference to
329  * the returned context, which *must* be explicitely dropped when done
330  * using this context.
331  */
332 struct asr_ctx *
333 asr_use_resolver(struct asr *asr)
334 {
335 	struct asr **priv;
336 
337 	if (asr == NULL) {
338 		/* Use the thread-local resolver. */
339 #ifdef DEBUG
340 		asr_printf("using thread-local resolver\n");
341 #endif
342 		priv = _THREAD_PRIVATE(_asr, asr, &_asr);
343 		if (*priv == NULL) {
344 #ifdef DEBUG
345 			asr_printf("setting up thread-local resolver\n");
346 #endif
347 			*priv = async_resolver(NULL);
348 		}
349 		asr = *priv;
350 	}
351 
352 	asr_check_reload(asr);
353 	asr_ctx_ref(asr->a_ctx);
354 	return (asr->a_ctx);
355 }
356 
357 static void
358 asr_ctx_ref(struct asr_ctx *ac)
359 {
360 #ifdef DEBUG
361 	asr_printf("asr: asr_ctx_ref(ctx=%p) refcount=%i\n",
362 	    ac, ac->ac_refcount);
363 #endif
364 	ac->ac_refcount += 1;
365 }
366 
367 /*
368  * Drop a reference to an async context, freeing it if the reference
369  * count drops to 0.
370  */
371 void
372 asr_ctx_unref(struct asr_ctx *ac)
373 {
374 #ifdef DEBUG
375 	asr_printf("asr: asr_ctx_unref(ctx=%p) refcount=%i\n",
376 	    ac, ac->ac_refcount);
377 #endif
378 	if (--ac->ac_refcount)
379 		return;
380 
381 	asr_ctx_free(ac);
382 }
383 
384 static void
385 asr_ctx_free(struct asr_ctx *ac)
386 {
387 	int i;
388 
389 	if (ac->ac_domain)
390 		free(ac->ac_domain);
391 	for(i = 0; i < ac->ac_nscount; i++)
392 		free(ac->ac_ns[i]);
393 	for(i = 0; i < ac->ac_domcount; i++)
394 		free(ac->ac_dom[i]);
395 
396 	free(ac);
397 }
398 
399 /*
400  * Reload the configuration file if it has changed on disk.
401  */
402 static void
403 asr_check_reload(struct asr *asr)
404 {
405         struct stat	 st;
406 	struct asr_ctx	*ac;
407 	struct timespec	 tp;
408 
409 	if (asr->a_path == NULL)
410 		return;
411 
412 	if (clock_gettime(CLOCK_MONOTONIC, &tp) == -1)
413 		return;
414 
415 	if ((tp.tv_sec - asr->a_rtime) < RELOAD_DELAY)
416 		return;
417 	asr->a_rtime = tp.tv_sec;
418 
419 #ifdef DEBUG
420 	asr_printf("asr: checking for update of \"%s\"\n", asr->a_path);
421 #endif
422 
423 	if (stat(asr->a_path, &st) == -1 ||
424 	    asr->a_mtime == st.st_mtime ||
425 	    (ac = asr_ctx_create()) == NULL)
426 		return;
427 	asr->a_mtime = st.st_mtime;
428 
429 #ifdef DEBUG
430 	asr_printf("asr: reloading config file\n");
431 #endif
432 
433 	if (asr_ctx_from_file(ac, asr->a_path) == -1) {
434 		asr_ctx_free(ac);
435 		return;
436 	}
437 
438 	asr_ctx_envopts(ac);
439 	if (asr->a_ctx)
440 		asr_ctx_unref(asr->a_ctx);
441 	asr->a_ctx = ac;
442 }
443 
444 /*
445  * Construct a fully-qualified domain name for the given name and domain.
446  * If "name" ends with a '.' it is considered as a FQDN by itself.
447  * Otherwise, the domain, which must be a FQDN, is appended to "name" (it
448  * may have a leading dot which would be ignored). If the domain is null,
449  * then "." is used. Return the length of the constructed FQDN or (0) on
450  * error.
451  */
452 size_t
453 asr_make_fqdn(const char *name, const char *domain, char *buf, size_t buflen)
454 {
455 	size_t	len;
456 
457 	if (domain == NULL)
458 		domain = ".";
459 	else if ((len = strlen(domain)) == 0)
460 		return (0);
461 	else if (domain[len -1] != '.')
462 		return (0);
463 
464 	len = strlen(name);
465 	if (len == 0) {
466 		strlcpy(buf, domain, buflen);
467 	} else if (name[len - 1] !=  '.') {
468 		if (domain[0] == '.')
469 			domain += 1;
470 		strlcpy(buf, name, buflen);
471 		strlcat(buf, ".", buflen);
472 		strlcat(buf, domain, buflen);
473 	} else {
474 		strlcpy(buf, name, buflen);
475 	}
476 
477 	return (strlen(buf));
478 }
479 
480 /*
481  * Concatenate a name and a domain name. The result has no trailing dot.
482  */
483 size_t
484 asr_domcat(const char *name, const char *domain, char *buf, size_t buflen)
485 {
486 	size_t	r;
487 
488 	r = asr_make_fqdn(name, domain, buf, buflen);
489 	if (r == 0)
490 		return (0);
491 	buf[r - 1] = '\0';
492 
493 	return (r - 1);
494 }
495 
496 /*
497  * Count the dots in a string.
498  */
499 static int
500 asr_ndots(const char *s)
501 {
502 	int n;
503 
504 	for(n = 0; *s; s++)
505 		if (*s == '.')
506 			n += 1;
507 
508 	return (n);
509 }
510 
511 /*
512  * Allocate a new empty context.
513  */
514 static struct asr_ctx *
515 asr_ctx_create(void)
516 {
517 	struct asr_ctx	*ac;
518 
519 	if ((ac = calloc(1, sizeof(*ac))) == NULL)
520 		return (NULL);
521 
522 	ac->ac_options = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
523 	ac->ac_refcount = 1;
524 	ac->ac_ndots = 1;
525 	ac->ac_family[0] = AF_INET;
526 	ac->ac_family[1] = AF_INET6;
527 	ac->ac_family[2] = -1;
528 
529 	ac->ac_hostfile = DEFAULT_HOSTFILE;
530 
531 	ac->ac_nscount = 0;
532 	ac->ac_nstimeout = 1000;
533 	ac->ac_nsretries = 3;
534 
535 	return (ac);
536 }
537 
538 /*
539  * Add a search domain to the async context.
540  */
541 static int
542 asr_ctx_add_searchdomain(struct asr_ctx *ac, const char *domain)
543 {
544 	char buf[MAXDNAME];
545 
546 	if (ac->ac_domcount == ASR_MAXDOM)
547 		return (-1);
548 
549 	if (asr_make_fqdn(domain, NULL, buf, sizeof(buf)) == 0)
550 		return (-1);
551 
552 	if ((ac->ac_dom[ac->ac_domcount] = strdup(buf)) == NULL)
553 		return (0);
554 
555 	ac->ac_domcount += 1;
556 
557 	return (1);
558 }
559 
560 /*
561  * Pass on a split config line.
562  */
563 static int
564 pass0(char **tok, int n, struct asr_ctx *ac)
565 {
566 	int		 i, j, d;
567 	const char	*e;
568 	struct sockaddr_storage	ss;
569 
570 	if (!strcmp(tok[0], "nameserver")) {
571 		if (ac->ac_nscount == ASR_MAXNS)
572 			return (0);
573 		if (n != 2)
574 			return (0);
575 		if (asr_parse_nameserver((struct sockaddr*)&ss, tok[1]))
576 			return (0);
577 		if ((ac->ac_ns[ac->ac_nscount] = calloc(1, ss.ss_len)) == NULL)
578 			return (0);
579 		memmove(ac->ac_ns[ac->ac_nscount], &ss, ss.ss_len);
580 		ac->ac_nscount += 1;
581 
582 	} else if (!strcmp(tok[0], "domain")) {
583 		if (n != 2)
584 			return (0);
585 		if (ac->ac_domain)
586 			return (0);
587 		ac->ac_domain = strdup(tok[1]);
588 
589 	} else if (!strcmp(tok[0], "lookup")) {
590 		/* ignore the line if we already set lookup */
591 		if (ac->ac_dbcount != 0)
592 			return (0);
593 		if (n - 1 > ASR_MAXDB)
594 			return (0);
595 		/* ensure that each lookup is only given once */
596 		for(i = 1; i < n; i++)
597 			for(j = i + 1; j < n; j++)
598 				if (!strcmp(tok[i], tok[j]))
599 					return (0);
600 		for(i = 1; i < n; i++, ac->ac_dbcount++) {
601 			if (!strcmp(tok[i], "yp")) {
602 				ac->ac_db[i-1] = ASR_DB_YP;
603 			} else if (!strcmp(tok[i], "bind")) {
604 				ac->ac_db[i-1] = ASR_DB_DNS;
605 			} else if (!strcmp(tok[i], "file")) {
606 				ac->ac_db[i-1] = ASR_DB_FILE;
607 			} else {
608 				/* ignore the line */
609 				ac->ac_dbcount = 0;
610 				return (0);
611 			}
612 		}
613 	} else if (!strcmp(tok[0], "search")) {
614 		/* resolv.conf says the last line wins */
615 		for(i = 0; i < ac->ac_domcount; i++)
616 			free(ac->ac_dom[i]);
617 		ac->ac_domcount = 0;
618 		for(i = 1; i < n; i++)
619 			asr_ctx_add_searchdomain(ac, tok[i]);
620 
621 	} else if (!strcmp(tok[0], "family")) {
622 		if (n == 1 || n > 3)
623 			return (0);
624 		for (i = 1; i < n; i++)
625 			if (strcmp(tok[i], "inet4") && strcmp(tok[i], "inet6"))
626 				return (0);
627 		for (i = 1; i < n; i++)
628 			ac->ac_family[i - 1] = strcmp(tok[i], "inet4") ? \
629 			    AF_INET6 : AF_INET;
630 		ac->ac_family[i - 1] = -1;
631 
632 	} else if (!strcmp(tok[0], "options")) {
633 		for(i = 1; i < n; i++) {
634 			if (!strcmp(tok[i], "tcp"))
635 				ac->ac_options |= RES_USEVC;
636 			else if ((!strncmp(tok[i], "ndots:", 6))) {
637 				e = NULL;
638 				d = strtonum(tok[i] + 6, 1, 16, &e);
639 				if (e == NULL)
640 					ac->ac_ndots = d;
641 			}
642 		}
643 	}
644 
645 	return (0);
646 }
647 
648 /*
649  * Setup an async context with the config specified in the string "str".
650  */
651 static int
652 asr_ctx_from_string(struct asr_ctx *ac, const char *str)
653 {
654 	char		 buf[512], *ch;
655 
656 	asr_ctx_parse(str, pass0, ac);
657 
658 	if (ac->ac_dbcount == 0) {
659 		/* No lookup directive */
660 		asr_ctx_parse(DEFAULT_LOOKUP, pass0, ac);
661 	}
662 
663 	if (ac->ac_nscount == 0)
664 		asr_ctx_parse("nameserver 127.0.0.1", pass0, ac);
665 
666 	if (ac->ac_domain == NULL)
667 		if (gethostname(buf, sizeof buf) == 0) {
668 			ch = strchr(buf, '.');
669 			if (ch)
670 				ac->ac_domain = strdup(ch + 1);
671 			else /* Assume root. see resolv.conf(5) */
672 				ac->ac_domain = strdup("");
673 		}
674 
675 	/* If no search domain was specified, use the local subdomains */
676 	if (ac->ac_domcount == 0)
677 		for(ch = ac->ac_domain; ch; ) {
678 			asr_ctx_add_searchdomain(ac, ch);
679 			ch = strchr(ch, '.');
680 			if (ch && asr_ndots(++ch) == 0)
681 				break;
682 		}
683 
684 	return (0);
685 }
686 
687 /*
688  * Setup the "ac" async context from the file at location "path".
689  */
690 static int
691 asr_ctx_from_file(struct asr_ctx *ac, const char *path)
692 {
693 	FILE	*cf;
694 	char	 buf[4096];
695 	ssize_t	 r;
696 
697 	cf = fopen(path, "r");
698 	if (cf == NULL)
699 		return (-1);
700 
701 	r = fread(buf, 1, sizeof buf - 1, cf);
702 	if (feof(cf) == 0) {
703 #ifdef DEBUG
704 		asr_printf("asr: config file too long: \"%s\"\n", path);
705 #endif
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 a configuration string.  Lines are read one by one, comments are
718  * stripped and the remaining line is split into tokens which are passed
719  * to the "cb" callback function.  Parsing stops if the callback returns
720  * non-zero.
721  */
722 static int
723 asr_ctx_parse(const char *str, int (*cb)(char**, int, struct asr_ctx*),
724     struct asr_ctx *ac)
725 {
726 	size_t		 len;
727 	const char	*line;
728 	char		 buf[1024];
729 	char		*tok[10], **tp, *cp;
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 		for(cp = buf, tp = tok, ntok = 0;
745 		    tp < &tok[10] && (*tp = strsep(&cp, " \t")) != NULL; )
746 			if (**tp != '\0') {
747 				tp++;
748 				ntok++;
749 			}
750 		*tp = NULL;
751 
752 		if (tok[0] == NULL)
753 			continue;
754 
755 		if (cb(tok, ntok, ac))
756 			break;
757 	}
758 
759 	return (0);
760 }
761 
762 /*
763  * Check for environment variables altering the configuration as described
764  * in resolv.conf(5).  Altough not documented there, this feature is disabled
765  * for setuid/setgid programs.
766  */
767 static void
768 asr_ctx_envopts(struct asr_ctx *ac)
769 {
770 	char	buf[4096], *e;
771 	size_t	s;
772 
773 	if (issetugid()) {
774 		ac->ac_options |= RES_NOALIASES;
775 		return;
776 	}
777 
778 	if ((e = getenv("RES_OPTIONS")) != NULL) {
779 		strlcpy(buf, "options ", sizeof buf);
780 		strlcat(buf, e, sizeof buf);
781 		s = strlcat(buf, "\n", sizeof buf);
782 		s = strlcat(buf, "\n", sizeof buf);
783 		if (s < sizeof buf)
784 			asr_ctx_parse(buf, pass0, ac);
785 	}
786 
787 	if ((e = getenv("LOCALDOMAIN")) != NULL) {
788 		strlcpy(buf, "search ", sizeof buf);
789 		strlcat(buf, e, sizeof buf);
790 		s = strlcat(buf, "\n", sizeof buf);
791 		if (s < sizeof buf)
792 			asr_ctx_parse(buf, pass0, ac);
793 	}
794 }
795 
796 /*
797  * Parse a resolv.conf(5) nameserver string into a sockaddr.
798  */
799 static int
800 asr_parse_nameserver(struct sockaddr *sa, const char *s)
801 {
802 	const char	*estr;
803 	char		 buf[256];
804 	char		*port = NULL;
805 	in_port_t	 portno = 53;
806 
807 	if (*s == '[') {
808 		strlcpy(buf, s + 1, sizeof buf);
809 		s = buf;
810 		port = strchr(buf, ']');
811 		if (port == NULL)
812 			return (-1);
813 		*port++ = '\0';
814 		if (*port != ':')
815 			return (-1);
816 		port++;
817 	}
818 
819 	if (port) {
820 		portno = strtonum(port, 1, USHRT_MAX, &estr);
821 		if (estr)
822 			return (-1);
823 	}
824 
825 	if (sockaddr_from_str(sa, PF_UNSPEC, s) == -1)
826 		return (-1);
827 
828 	if (sa->sa_family == PF_INET)
829 		((struct sockaddr_in *)sa)->sin_port = htons(portno);
830 	else if (sa->sa_family == PF_INET6)
831 		((struct sockaddr_in6 *)sa)->sin6_port = htons(portno);
832 
833 	return (0);
834 }
835 
836 /*
837  * Turn a (uncompressed) DNS domain name into a regular nul-terminated string
838  * where labels are separated by dots. The result is put into the "buf" buffer,
839  * truncated if it exceeds "max" chars. The function returns "buf".
840  */
841 char*
842 asr_strdname(const char *_dname, char *buf, size_t max)
843 {
844 	const unsigned char *dname = _dname;
845 	char	*res;
846 	size_t	 left, n, count;
847 
848 	if (_dname[0] == 0) {
849 		strlcpy(buf, ".", max);
850 		return buf;
851 	}
852 
853 	res = buf;
854 	left = max - 1;
855 	for (n = 0; dname[0] && left; n += dname[0]) {
856 		count = (dname[0] < (left - 1)) ? dname[0] : (left - 1);
857 		memmove(buf, dname + 1, count);
858 		dname += dname[0] + 1;
859 		left -= count;
860 		buf += count;
861 		if (left) {
862 			left -= 1;
863 			*buf++ = '.';
864 		}
865 	}
866 	buf[0] = 0;
867 
868 	return (res);
869 }
870 
871 /*
872  * Read and split the next line from the given namedb file.
873  * Return -1 on error, or put the result in the "tokens" array of
874  * size "ntoken" and returns the number of token on the line.
875  */
876 int
877 asr_parse_namedb_line(FILE *file, char **tokens, int ntoken)
878 {
879 	size_t	  len;
880 	char	 *buf, *cp, **tp;
881 	int	  ntok;
882 
883   again:
884 	if ((buf = fgetln(file, &len)) == NULL)
885 		return (-1);
886 
887 	if (buf[len - 1] == '\n')
888 		len--;
889 
890 	buf[len] = '\0';
891 	buf[strcspn(buf, "#")] = '\0';
892 	for(cp = buf, tp = tokens, ntok = 0;
893 	    ntok < ntoken && (*tp = strsep(&cp, " \t")) != NULL;)
894 		if (**tp != '\0') {
895 			tp++;
896 			ntok++;
897 		}
898 	*tp = NULL;
899 	if (tokens[0] == NULL)
900 		goto again;
901 
902 	return (ntok);
903 }
904 
905 /*
906  * Update the async context so that it uses the next configured DB.
907  * Return 0 on success, or -1 if no more DBs is available.
908  */
909 int
910 asr_iter_db(struct async *as)
911 {
912 	if (as->as_db_idx >= as->as_ctx->ac_dbcount) {
913 #ifdef DEBUG
914 		asr_printf("asr_iter_db: done\n");
915 #endif
916 		return (-1);
917 	}
918 
919 	as->as_db_idx += 1;
920 	as->as_ns_idx = 0;
921 #ifdef DEBUG
922 	asr_printf("asr_iter_db: %i\n", as->as_db_idx);
923 #endif
924 	return (0);
925 }
926 
927 /*
928  * Set the async context nameserver index to the next nameserver of the
929  * currently used DB (assuming it is DNS), cycling over the list until the
930  * maximum retry counter is reached.  Return 0 on success, or -1 if all
931  * nameservers were used.
932  */
933 int
934 asr_iter_ns(struct async *as)
935 {
936 	for (;;) {
937 		if (as->as_ns_cycles >= as->as_ctx->ac_nsretries)
938 			return (-1);
939 
940 		as->as_ns_idx += 1;
941 		if (as->as_ns_idx <= as->as_ctx->ac_nscount)
942 			break;
943 		as->as_ns_idx = 0;
944 		as->as_ns_cycles++;
945 #ifdef DEBUG
946 		asr_printf("asr: asr_iter_ns(): cycle %i\n", as->as_ns_cycles);
947 #endif
948 	}
949 
950 	return (0);
951 }
952 
953 enum {
954 	DOM_INIT,
955 	DOM_DOMAIN,
956 	DOM_DONE
957 };
958 
959 /*
960  * Implement the search domain strategy.
961  *
962  * This function works as a generator that constructs complete domains in
963  * buffer "buf" of size "len" for the given host name "name", according to the
964  * search rules defined by the resolving context.  It is supposed to be called
965  * multiple times (with the same name) to generate the next possible domain
966  * name, if any.
967  *
968  * It returns 0 if it could generate a new domain name, or -1 when all
969  * possibilites have been exhausted.
970  */
971 int
972 asr_iter_domain(struct async *as, const char *name, char * buf, size_t len)
973 {
974 	char	*alias;
975 
976 	switch(as->as_dom_step) {
977 
978 	case DOM_INIT:
979 		/* First call */
980 
981 		/*
982 		 * If "name" is an FQDN, that's the only result and we
983 		 * don't try anything else.
984 		 */
985 		if (strlen(name) && name[strlen(name) - 1] ==  '.') {
986 #ifdef DEBUG
987 			asr_printf("asr: asr_iter_domain(\"%s\") fqdn\n", name);
988 #endif
989 			as->as_dom_flags |= ASYNC_DOM_FQDN;
990 			as->as_dom_step = DOM_DONE;
991 			return (asr_domcat(name, NULL, buf, len));
992 		}
993 
994 		/*
995 		 * If "name" has no dots, it might be an alias. If so,
996 		 * That's also the only result.
997 		 */
998 		if ((as->as_ctx->ac_options & RES_NOALIASES) == 0 &&
999 		    asr_ndots(name) == 0 &&
1000 		    (alias = asr_hostalias(name, buf, len)) != NULL) {
1001 #ifdef DEBUG
1002 			asr_printf("asr: asr_iter_domain(\"%s\") is alias "
1003 			    "\"%s\"\n", name, alias);
1004 #endif
1005 			as->as_dom_flags |= ASYNC_DOM_HOSTALIAS;
1006 			as->as_dom_step = DOM_DONE;
1007 			return (asr_domcat(alias, NULL, buf, len));
1008 		}
1009 
1010 		/*
1011 		 * Otherwise, we iterate through the specified search domains.
1012 		 */
1013 		as->as_dom_step = DOM_DOMAIN;
1014 		as->as_dom_idx = 0;
1015 
1016 		/*
1017 		 * If "name" as enough dots, use it as-is first, as indicated
1018 		 * in resolv.conf(5).
1019 		 */
1020 		if ((asr_ndots(name)) >= as->as_ctx->ac_ndots) {
1021 #ifdef DEBUG
1022 			asr_printf("asr: asr_iter_domain(\"%s\") ndots\n",
1023 			    name);
1024 #endif
1025 			as->as_dom_flags |= ASYNC_DOM_NDOTS;
1026 			strlcpy(buf, name, len);
1027 			return (0);
1028 		}
1029 		/* Otherwise, starts using the search domains */
1030 		/* FALLTHROUGH */
1031 
1032 	case DOM_DOMAIN:
1033 		if (as->as_dom_idx < as->as_ctx->ac_domcount) {
1034 #ifdef DEBUG
1035 			asr_printf("asr: asr_iter_domain(\"%s\") "
1036 			    "domain \"%s\"\n", name,
1037 			    as->as_ctx->ac_dom[as->as_dom_idx]);
1038 #endif
1039 			as->as_dom_flags |= ASYNC_DOM_DOMAIN;
1040 			return (asr_domcat(name,
1041 			    as->as_ctx->ac_dom[as->as_dom_idx++], buf, len));
1042 		}
1043 
1044 		/* No more domain to try. */
1045 
1046 		as->as_dom_step = DOM_DONE;
1047 
1048 		/*
1049 		 * If the name was not tried as an absolute name before,
1050 		 * do it now.
1051 		 */
1052 		if (!(as->as_dom_flags & ASYNC_DOM_NDOTS)) {
1053 #ifdef DEBUG
1054 			asr_printf("asr: asr_iter_domain(\"%s\") as is\n",
1055 			    name);
1056 #endif
1057 			as->as_dom_flags |= ASYNC_DOM_ASIS;
1058 			strlcpy(buf, name, len);
1059 			return (0);
1060 		}
1061 		/* Otherwise, we are done. */
1062 
1063 	case DOM_DONE:
1064 	default:
1065 #ifdef DEBUG
1066 		asr_printf("asr: asr_iter_domain(\"%s\") done\n", name);
1067 #endif
1068 		return (-1);
1069 	}
1070 }
1071 
1072 /*
1073  * Check if the hostname "name" is a user-defined alias as per hostname(7).
1074  * If so, copies the result in the buffer "abuf" of size "abufsz" and
1075  * return "abuf". Otherwise return NULL.
1076  */
1077 static char *
1078 asr_hostalias(const char *name, char *abuf, size_t abufsz)
1079 {
1080 	FILE	 *fp;
1081 	size_t	  len;
1082 	char	 *file, *buf, *cp, **tp, *tokens[2];
1083 	int	  ntok;
1084 
1085 	file = getenv("HOSTALIASES");
1086 	if (file == NULL || issetugid() != 0 || (fp = fopen(file, "r")) == NULL)
1087 		return (NULL);
1088 
1089 #ifdef DEBUG
1090 	asr_printf("asr: looking up aliases in \"%s\"\n", file);
1091 #endif
1092 
1093 	while ((buf = fgetln(fp, &len)) != NULL) {
1094 		if (buf[len - 1] == '\n')
1095 			len--;
1096 		buf[len] = '\0';
1097 		for(cp = buf, tp = tokens, ntok = 0;
1098 		    ntok < 2 && (*tp = strsep(&cp, " \t")) != NULL; )
1099 			if (**tp != '\0') {
1100 				tp++;
1101 				ntok++;
1102 			}
1103 		if (ntok != 2)
1104 			continue;
1105 		if (!strcasecmp(tokens[0], name)) {
1106 			if (strlcpy(abuf, tokens[1], abufsz) > abufsz)
1107 				continue;
1108 #ifdef DEBUG
1109 			asr_printf("asr: found alias \"%s\"\n", abuf);
1110 #endif
1111 			fclose(fp);
1112 			return (abuf);
1113 		}
1114 	}
1115 
1116 	fclose(fp);
1117 	return (NULL);
1118 }
1119