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