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