xref: /openbsd-src/lib/libc/asr/asr.c (revision 1ed934d0d08bf9c3f460e8740d169e7606f8702f)
1 /*	$OpenBSD: asr.c,v 1.26 2013/05/27 17:31:01 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_HOSTALIASES
78 static char *asr_hostalias(const char *, char *, size_t);
79 #endif
80 #if ASR_OPT_ENVOPTS
81 static void asr_ctx_envopts(struct asr_ctx *);
82 #endif
83 #if ASR_OPT_THREADSAFE
84 static void *__THREAD_NAME(_asr);
85 #else
86 #	define _THREAD_PRIVATE(a, b, c)  (c)
87 #endif
88 
89 static struct asr *_asr = NULL;
90 
91 /* Allocate and configure an async "resolver". */
92 struct asr *
93 async_resolver(const char *conf)
94 {
95 	static int	 init = 0;
96 	struct asr	*asr;
97 
98 	if (init == 0) {
99 #ifdef DEBUG
100 		if (getenv("ASR_DEBUG"))
101 			asr_debug = stderr;
102 #endif
103 		init = 1;
104 	}
105 
106 	if ((asr = calloc(1, sizeof(*asr))) == NULL)
107 		goto fail;
108 
109 #if ASR_OPT_ALTCONF
110 	/* If not setuid/setgid, allow to use an alternate config. */
111 	if (conf == NULL && !issetugid())
112 		conf = getenv("ASR_CONFIG");
113 #endif
114 
115 	if (conf == NULL)
116 		conf = DEFAULT_CONFFILE;
117 
118 	if (conf[0] == '!') {
119 		/* Use the rest of the string as config file */
120 		if ((asr->a_ctx = asr_ctx_create()) == NULL)
121 			goto fail;
122 		if (asr_ctx_from_string(asr->a_ctx, conf + 1) == -1)
123 			goto fail;
124 	} else {
125 		/* Use the given config file */
126 		asr->a_path = strdup(conf);
127 		if (asr->a_path == NULL)
128 			goto fail;
129 		asr_check_reload(asr);
130 		if (asr->a_ctx == NULL) {
131 			if ((asr->a_ctx = asr_ctx_create()) == NULL)
132 				goto fail;
133 			if (asr_ctx_from_string(asr->a_ctx, DEFAULT_CONF) == -1)
134 				goto fail;
135 #if ASR_OPT_ENVOPTS
136 			asr_ctx_envopts(asr->a_ctx);
137 #endif
138 		}
139 	}
140 
141 #ifdef DEBUG
142 	asr_dump_config(asr_debug, asr);
143 #endif
144 	return (asr);
145 
146     fail:
147 	if (asr) {
148 		if (asr->a_ctx)
149 			asr_ctx_free(asr->a_ctx);
150 		free(asr->a_path);
151 		free(asr);
152 	}
153 
154 	return (NULL);
155 }
156 
157 /*
158  * Free the "asr" async resolver (or the thread-local resolver if NULL).
159  * Drop the reference to the current context.
160  */
161 void
162 async_resolver_done(struct asr *asr)
163 {
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 async_abort(struct async *as)
184 {
185 	async_free(as);
186 }
187 
188 /*
189  * Resume the "as" async query resolution.  Return one of ASYNC_COND,
190  * ASYNC_YIELD or ASYNC_DONE and put query-specific return values in
191  * the user-allocated memory at "ar".
192  */
193 int
194 async_run(struct async *as, struct async_res *ar)
195 {
196 	int	r, saved_errno = errno;
197 
198 	DPRINT("asr: async_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: async_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 		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 async_run_sync(struct async *as, struct async_res *ar)
221 {
222 	struct pollfd	 fds[1];
223 	int		 r, saved_errno = errno;
224 
225 	while ((r = async_run(as, ar)) == ASYNC_COND) {
226 		fds[0].fd = ar->ar_fd;
227 		fds[0].events = (ar->ar_cond == ASYNC_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 async_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 async *
249 async_new(struct asr_ctx *ac, int type)
250 {
251 	struct async	*as;
252 
253 	DPRINT("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 async_free(struct async *as)
272 {
273 	DPRINT("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 			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 			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 			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 			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 			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 			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(struct asr *asr)
346 {
347 	struct asr **priv;
348 
349 	if (asr == NULL) {
350 		DPRINT("using thread-local resolver\n");
351 		priv = _THREAD_PRIVATE(_asr, _asr, &_asr);
352 		if (*priv == NULL) {
353 			DPRINT("setting up thread-local resolver\n");
354 			*priv = async_resolver(NULL);
355 		}
356 		asr = *priv;
357 	}
358 	if (asr != NULL) {
359 		asr_check_reload(asr);
360 		asr_ctx_ref(asr->a_ctx);
361 		return (asr->a_ctx);
362 	}
363 	return (NULL);
364 }
365 
366 static void
367 asr_ctx_ref(struct asr_ctx *ac)
368 {
369 	DPRINT("asr: asr_ctx_ref(ctx=%p) refcount=%i\n", ac, ac->ac_refcount);
370 	ac->ac_refcount += 1;
371 }
372 
373 /*
374  * Drop a reference to an async context, freeing it if the reference
375  * count drops to 0.
376  */
377 void
378 asr_ctx_unref(struct asr_ctx *ac)
379 {
380 	DPRINT("asr: asr_ctx_unref(ctx=%p) refcount=%i\n", ac,
381 	    ac ? ac->ac_refcount : 0);
382 	if (ac == NULL)
383 		return;
384 	if (--ac->ac_refcount)
385 		return;
386 
387 	asr_ctx_free(ac);
388 }
389 
390 static void
391 asr_ctx_free(struct asr_ctx *ac)
392 {
393 	int i;
394 
395 	if (ac->ac_domain)
396 		free(ac->ac_domain);
397 	for (i = 0; i < ASR_MAXNS; i++)
398 		free(ac->ac_ns[i]);
399 	for (i = 0; i < ASR_MAXDOM; i++)
400 		free(ac->ac_dom[i]);
401 
402 	free(ac);
403 }
404 
405 /*
406  * Reload the configuration file if it has changed on disk.
407  */
408 static void
409 asr_check_reload(struct asr *asr)
410 {
411 	struct asr_ctx	*ac;
412 #if ASR_OPT_RELOADCONF
413 	struct stat	 st;
414 	struct timespec	 ts;
415 #endif
416 
417 	if (asr->a_path == NULL)
418 		return;
419 
420 #if ASR_OPT_RELOADCONF
421 	if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
422 		return;
423 
424 	if ((ts.tv_sec - asr->a_rtime) < RELOAD_DELAY && asr->a_rtime != 0)
425 		return;
426 	asr->a_rtime = ts.tv_sec;
427 
428 	DPRINT("asr: checking for update of \"%s\"\n", asr->a_path);
429 	if (stat(asr->a_path, &st) == -1 ||
430 	    asr->a_mtime == st.st_mtime ||
431 	    (ac = asr_ctx_create()) == NULL)
432 		return;
433 	asr->a_mtime = st.st_mtime;
434 #else
435 	if ((ac = asr_ctx_create()) == NULL)
436 		return;
437 #endif
438 
439 	DPRINT("asr: reloading config file\n");
440 	if (asr_ctx_from_file(ac, asr->a_path) == -1) {
441 		asr_ctx_free(ac);
442 		return;
443 	}
444 
445 #if ASR_OPT_ENVOPTS
446 	asr_ctx_envopts(ac);
447 #endif
448 	if (asr->a_ctx)
449 		asr_ctx_unref(asr->a_ctx);
450 	asr->a_ctx = ac;
451 }
452 
453 /*
454  * Construct a fully-qualified domain name for the given name and domain.
455  * If "name" ends with a '.' it is considered as a FQDN by itself.
456  * Otherwise, the domain, which must be a FQDN, is appended to "name" (it
457  * may have a leading dot which would be ignored). If the domain is null,
458  * then "." is used. Return the length of the constructed FQDN or (0) on
459  * error.
460  */
461 size_t
462 asr_make_fqdn(const char *name, const char *domain, char *buf, size_t buflen)
463 {
464 	size_t	len;
465 
466 	if (domain == NULL)
467 		domain = ".";
468 	else if ((len = strlen(domain)) == 0)
469 		return (0);
470 	else if (domain[len -1] != '.')
471 		return (0);
472 
473 	len = strlen(name);
474 	if (len == 0) {
475 		if (strlcpy(buf, domain, buflen) >= buflen)
476 			return (0);
477 	} else if (name[len - 1] !=  '.') {
478 		if (domain[0] == '.')
479 			domain += 1;
480 		if (strlcpy(buf, name, buflen) >= buflen ||
481 		    strlcat(buf, ".", buflen) >= buflen ||
482 		    strlcat(buf, domain, buflen) >= buflen)
483 			return (0);
484 	} else {
485 		if (strlcpy(buf, name, buflen) >= buflen)
486 			return (0);
487 	}
488 
489 	return (strlen(buf));
490 }
491 
492 /*
493  * Concatenate a name and a domain name. The result has no trailing dot.
494  * Return the resulting string length, or 0 in case of error.
495  */
496 size_t
497 asr_domcat(const char *name, const char *domain, char *buf, size_t buflen)
498 {
499 	size_t	r;
500 
501 	r = asr_make_fqdn(name, domain, buf, buflen);
502 	if (r == 0)
503 		return (0);
504 	buf[r - 1] = '\0';
505 
506 	return (r - 1);
507 }
508 
509 /*
510  * Count the dots in a string.
511  */
512 static int
513 asr_ndots(const char *s)
514 {
515 	int n;
516 
517 	for (n = 0; *s; s++)
518 		if (*s == '.')
519 			n += 1;
520 
521 	return (n);
522 }
523 
524 /*
525  * Allocate a new empty context.
526  */
527 static struct asr_ctx *
528 asr_ctx_create(void)
529 {
530 	struct asr_ctx	*ac;
531 
532 	if ((ac = calloc(1, sizeof(*ac))) == NULL)
533 		return (NULL);
534 
535 	ac->ac_options = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
536 	ac->ac_refcount = 1;
537 	ac->ac_ndots = 1;
538 	ac->ac_family[0] = AF_INET;
539 	ac->ac_family[1] = AF_INET6;
540 	ac->ac_family[2] = -1;
541 
542 	ac->ac_hostfile = DEFAULT_HOSTFILE;
543 
544 	ac->ac_nscount = 0;
545 	ac->ac_nstimeout = 5;
546 	ac->ac_nsretries = 4;
547 
548 	return (ac);
549 }
550 
551 /*
552  * Add a search domain to the async context.
553  */
554 static int
555 asr_ctx_add_searchdomain(struct asr_ctx *ac, const char *domain)
556 {
557 	char buf[MAXDNAME];
558 
559 	if (ac->ac_domcount == ASR_MAXDOM)
560 		return (-1);
561 
562 	if (asr_make_fqdn(domain, NULL, buf, sizeof(buf)) == 0)
563 		return (-1);
564 
565 	if ((ac->ac_dom[ac->ac_domcount] = strdup(buf)) == NULL)
566 		return (0);
567 
568 	ac->ac_domcount += 1;
569 
570 	return (1);
571 }
572 
573 static int
574 strsplit(char *line, char **tokens, int ntokens)
575 {
576 	int	ntok;
577 	char	*cp, **tp;
578 
579 	for (cp = line, tp = tokens, ntok = 0;
580 	    ntok < ntokens && (*tp = strsep(&cp, " \t")) != NULL; )
581 		if (**tp != '\0') {
582 			tp++;
583 			ntok++;
584 		}
585 
586 	return (ntok);
587 }
588 
589 /*
590  * Pass on a split config line.
591  */
592 static void
593 pass0(char **tok, int n, struct asr_ctx *ac)
594 {
595 	int		 i, j, d;
596 	const char	*e;
597 	struct sockaddr_storage	ss;
598 
599 	if (!strcmp(tok[0], "nameserver")) {
600 		if (ac->ac_nscount == ASR_MAXNS)
601 			return;
602 		if (n != 2)
603 			return;
604 		if (asr_parse_nameserver((struct sockaddr *)&ss, tok[1]))
605 			return;
606 		if ((ac->ac_ns[ac->ac_nscount] = calloc(1, ss.ss_len)) == NULL)
607 			return;
608 		memmove(ac->ac_ns[ac->ac_nscount], &ss, ss.ss_len);
609 		ac->ac_nscount += 1;
610 
611 	} else if (!strcmp(tok[0], "domain")) {
612 		if (n != 2)
613 			return;
614 		if (ac->ac_domain)
615 			return;
616 		ac->ac_domain = strdup(tok[1]);
617 
618 	} else if (!strcmp(tok[0], "lookup")) {
619 		/* ensure that each lookup is only given once */
620 		for (i = 1; i < n; i++)
621 			for (j = i + 1; j < n; j++)
622 				if (!strcmp(tok[i], tok[j]))
623 					return;
624 		ac->ac_dbcount = 0;
625 		for (i = 1; i < n && ac->ac_dbcount < ASR_MAXDB; i++) {
626 			if (!strcmp(tok[i], "yp"))
627 				ac->ac_db[ac->ac_dbcount++] = ASR_DB_YP;
628 			else if (!strcmp(tok[i], "bind"))
629 				ac->ac_db[ac->ac_dbcount++] = ASR_DB_DNS;
630 			else if (!strcmp(tok[i], "file"))
631 				ac->ac_db[ac->ac_dbcount++] = ASR_DB_FILE;
632 		}
633 	} else if (!strcmp(tok[0], "search")) {
634 		/* resolv.conf says the last line wins */
635 		for (i = 0; i < ASR_MAXDOM; i++)
636 			free(ac->ac_dom[i]);
637 		ac->ac_domcount = 0;
638 		for (i = 1; i < n; i++)
639 			asr_ctx_add_searchdomain(ac, tok[i]);
640 
641 	} else if (!strcmp(tok[0], "family")) {
642 		if (n == 1 || n > 3)
643 			return;
644 		for (i = 1; i < n; i++)
645 			if (strcmp(tok[i], "inet4") && strcmp(tok[i], "inet6"))
646 				return;
647 		for (i = 1; i < n; i++)
648 			ac->ac_family[i - 1] = strcmp(tok[i], "inet4") ? \
649 			    AF_INET6 : AF_INET;
650 		ac->ac_family[i - 1] = -1;
651 
652 	} else if (!strcmp(tok[0], "options")) {
653 		for (i = 1; i < n; i++) {
654 			if (!strcmp(tok[i], "tcp"))
655 				ac->ac_options |= RES_USEVC;
656 			else if ((!strncmp(tok[i], "ndots:", 6))) {
657 				e = NULL;
658 				d = strtonum(tok[i] + 6, 1, 16, &e);
659 				if (e == NULL)
660 					ac->ac_ndots = d;
661 			}
662 		}
663 	}
664 }
665 
666 /*
667  * Setup an async context with the config specified in the string "str".
668  */
669 static int
670 asr_ctx_from_string(struct asr_ctx *ac, const char *str)
671 {
672 	char		 buf[512], *ch;
673 
674 	asr_ctx_parse(ac, str);
675 
676 	if (ac->ac_dbcount == 0) {
677 		/* No lookup directive */
678 		asr_ctx_parse(ac, DEFAULT_LOOKUP);
679 	}
680 
681 	if (ac->ac_nscount == 0)
682 		asr_ctx_parse(ac, "nameserver 127.0.0.1");
683 
684 	if (ac->ac_domain == NULL)
685 		if (gethostname(buf, sizeof buf) == 0) {
686 			ch = strchr(buf, '.');
687 			if (ch)
688 				ac->ac_domain = strdup(ch + 1);
689 			else /* Assume root. see resolv.conf(5) */
690 				ac->ac_domain = strdup("");
691 		}
692 
693 	/* If no search domain was specified, use the local subdomains */
694 	if (ac->ac_domcount == 0)
695 		for (ch = ac->ac_domain; ch; ) {
696 			asr_ctx_add_searchdomain(ac, ch);
697 			ch = strchr(ch, '.');
698 			if (ch && asr_ndots(++ch) == 0)
699 				break;
700 		}
701 
702 	return (0);
703 }
704 
705 /*
706  * Setup the "ac" async context from the file at location "path".
707  */
708 static int
709 asr_ctx_from_file(struct asr_ctx *ac, const char *path)
710 {
711 	FILE	*cf;
712 	char	 buf[4096];
713 	ssize_t	 r;
714 
715 	cf = fopen(path, "r");
716 	if (cf == NULL)
717 		return (-1);
718 
719 	r = fread(buf, 1, sizeof buf - 1, cf);
720 	if (feof(cf) == 0) {
721 		DPRINT("asr: config file too long: \"%s\"\n", path);
722 		r = -1;
723 	}
724 	fclose(cf);
725 	if (r == -1)
726 		return (-1);
727 	buf[r] = '\0';
728 
729 	return asr_ctx_from_string(ac, buf);
730 }
731 
732 /*
733  * Parse lines in the configuration string. For each one, split it into
734  * tokens and pass them to "pass0" for processing.
735  */
736 static int
737 asr_ctx_parse(struct asr_ctx *ac, const char *str)
738 {
739 	size_t		 len;
740 	const char	*line;
741 	char		 buf[1024];
742 	char		*tok[10];
743 	int		 ntok;
744 
745 	line = str;
746 	while (*line) {
747 		len = strcspn(line, "\n\0");
748 		if (len < sizeof buf) {
749 			memmove(buf, line, len);
750 			buf[len] = '\0';
751 		} else
752 			buf[0] = '\0';
753 		line += len;
754 		if (*line == '\n')
755 			line++;
756 		buf[strcspn(buf, ";#")] = '\0';
757 		if ((ntok = strsplit(buf, tok, 10)) == 0)
758 			continue;
759 
760 		pass0(tok, ntok, ac);
761 	}
762 
763 	return (0);
764 }
765 
766 #if ASR_OPT_ENVOPTS
767 /*
768  * Check for environment variables altering the configuration as described
769  * in resolv.conf(5).  Altough not documented there, this feature is disabled
770  * for setuid/setgid programs.
771  */
772 static void
773 asr_ctx_envopts(struct asr_ctx *ac)
774 {
775 	char	buf[4096], *e;
776 	size_t	s;
777 
778 	if (issetugid()) {
779 		ac->ac_options |= RES_NOALIASES;
780 		return;
781 	}
782 
783 	if ((e = getenv("RES_OPTIONS")) != NULL) {
784 		strlcpy(buf, "options ", sizeof buf);
785 		strlcat(buf, e, sizeof buf);
786 		s = strlcat(buf, "\n", sizeof buf);
787 		s = strlcat(buf, "\n", sizeof buf);
788 		if (s < sizeof buf)
789 			asr_ctx_parse(ac, buf);
790 	}
791 
792 	if ((e = getenv("LOCALDOMAIN")) != NULL) {
793 		strlcpy(buf, "search ", sizeof buf);
794 		strlcat(buf, e, sizeof buf);
795 		s = strlcat(buf, "\n", sizeof buf);
796 		if (s < sizeof buf)
797 			asr_ctx_parse(ac, buf);
798 	}
799 }
800 #endif
801 
802 /*
803  * Parse a resolv.conf(5) nameserver string into a sockaddr.
804  */
805 static int
806 asr_parse_nameserver(struct sockaddr *sa, const char *s)
807 {
808 	const char	*estr;
809 	char		 buf[256];
810 	char		*port = NULL;
811 	in_port_t	 portno = 53;
812 
813 	if (*s == '[') {
814 		strlcpy(buf, s + 1, sizeof buf);
815 		s = buf;
816 		port = strchr(buf, ']');
817 		if (port == NULL)
818 			return (-1);
819 		*port++ = '\0';
820 		if (*port != ':')
821 			return (-1);
822 		port++;
823 	}
824 
825 	if (port) {
826 		portno = strtonum(port, 1, USHRT_MAX, &estr);
827 		if (estr)
828 			return (-1);
829 	}
830 
831 	if (sockaddr_from_str(sa, PF_UNSPEC, s) == -1)
832 		return (-1);
833 
834 	if (sa->sa_family == PF_INET)
835 		((struct sockaddr_in *)sa)->sin_port = htons(portno);
836 	else if (sa->sa_family == PF_INET6)
837 		((struct sockaddr_in6 *)sa)->sin6_port = htons(portno);
838 
839 	return (0);
840 }
841 
842 /*
843  * Turn a (uncompressed) DNS domain name into a regular nul-terminated string
844  * where labels are separated by dots. The result is put into the "buf" buffer,
845  * truncated if it exceeds "max" chars. The function returns "buf".
846  */
847 char *
848 asr_strdname(const char *_dname, char *buf, size_t max)
849 {
850 	const unsigned char *dname = _dname;
851 	char	*res;
852 	size_t	 left, n, count;
853 
854 	if (_dname[0] == 0) {
855 		strlcpy(buf, ".", max);
856 		return buf;
857 	}
858 
859 	res = buf;
860 	left = max - 1;
861 	for (n = 0; dname[0] && left; n += dname[0]) {
862 		count = (dname[0] < (left - 1)) ? dname[0] : (left - 1);
863 		memmove(buf, dname + 1, count);
864 		dname += dname[0] + 1;
865 		left -= count;
866 		buf += count;
867 		if (left) {
868 			left -= 1;
869 			*buf++ = '.';
870 		}
871 	}
872 	buf[0] = 0;
873 
874 	return (res);
875 }
876 
877 /*
878  * Read and split the next line from the given namedb file.
879  * Return -1 on error, or put the result in the "tokens" array of
880  * size "ntoken" and returns the number of token on the line.
881  */
882 int
883 asr_parse_namedb_line(FILE *file, char **tokens, int ntoken)
884 {
885 	size_t	  len;
886 	char	 *buf;
887 	int	  ntok;
888 
889     again:
890 	if ((buf = fgetln(file, &len)) == NULL)
891 		return (-1);
892 
893 	if (buf[len - 1] == '\n')
894 		len--;
895 
896 	buf[len] = '\0';
897 	buf[strcspn(buf, "#")] = '\0';
898 	if ((ntok = strsplit(buf, tokens, ntoken)) == 0)
899 		goto again;
900 
901 	return (ntok);
902 }
903 
904 /*
905  * Update the async context so that it uses the next configured DB.
906  * Return 0 on success, or -1 if no more DBs is available.
907  */
908 int
909 asr_iter_db(struct async *as)
910 {
911 	if (as->as_db_idx >= as->as_ctx->ac_dbcount) {
912 		DPRINT("asr_iter_db: done\n");
913 		return (-1);
914 	}
915 
916 	as->as_db_idx += 1;
917 	as->as_ns_idx = 0;
918 	DPRINT("asr_iter_db: %i\n", as->as_db_idx);
919 
920 	return (0);
921 }
922 
923 /*
924  * Set the async context nameserver index to the next nameserver of the
925  * currently used DB (assuming it is DNS), cycling over the list until the
926  * maximum retry counter is reached.  Return 0 on success, or -1 if all
927  * nameservers were used.
928  */
929 int
930 asr_iter_ns(struct async *as)
931 {
932 	for (;;) {
933 		if (as->as_ns_cycles >= as->as_ctx->ac_nsretries)
934 			return (-1);
935 
936 		as->as_ns_idx += 1;
937 		if (as->as_ns_idx <= as->as_ctx->ac_nscount)
938 			break;
939 		as->as_ns_idx = 0;
940 		as->as_ns_cycles++;
941 		DPRINT("asr: asr_iter_ns(): cycle %i\n", as->as_ns_cycles);
942 	}
943 
944 	as->as_timeout = 1000 * (as->as_ctx->ac_nstimeout << as->as_ns_cycles);
945 	if (as->as_ns_cycles > 0)
946 		as->as_timeout /= as->as_ctx->ac_nscount;
947 	if (as->as_timeout < 1000)
948 		as->as_timeout = 1000;
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 -1 if all possibilities have been exhausted, 0 if there was an
969  * error generating the next name, or the resulting name length.
970  */
971 int
972 asr_iter_domain(struct async *as, const char *name, char * buf, size_t len)
973 {
974 #if ASR_OPT_HOSTALIASES
975 	char	*alias;
976 #endif
977 
978 	switch (as->as_dom_step) {
979 
980 	case DOM_INIT:
981 		/* First call */
982 
983 		/*
984 		 * If "name" is an FQDN, that's the only result and we
985 		 * don't try anything else.
986 		 */
987 		if (strlen(name) && name[strlen(name) - 1] ==  '.') {
988 			DPRINT("asr: asr_iter_domain(\"%s\") fqdn\n", name);
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 #if ASR_OPT_HOSTALIASES
995 		/*
996 		 * If "name" has no dots, it might be an alias. If so,
997 		 * That's also the only result.
998 		 */
999 		if ((as->as_ctx->ac_options & RES_NOALIASES) == 0 &&
1000 		    asr_ndots(name) == 0 &&
1001 		    (alias = asr_hostalias(name, buf, len)) != NULL) {
1002 			DPRINT("asr: asr_iter_domain(\"%s\") is alias \"%s\"\n",
1003 			    name, alias);
1004 			as->as_dom_flags |= ASYNC_DOM_HOSTALIAS;
1005 			as->as_dom_step = DOM_DONE;
1006 			return (asr_domcat(alias, NULL, buf, len));
1007 		}
1008 #endif
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 			DPRINT("asr: asr_iter_domain(\"%s\") ndots\n", name);
1022 			as->as_dom_flags |= ASYNC_DOM_NDOTS;
1023 			if (strlcpy(buf, name, len) >= len)
1024 				return (0);
1025 			return (strlen(buf));
1026 		}
1027 		/* Otherwise, starts using the search domains */
1028 		/* FALLTHROUGH */
1029 
1030 	case DOM_DOMAIN:
1031 		if (as->as_dom_idx < as->as_ctx->ac_domcount) {
1032 			DPRINT("asr: asr_iter_domain(\"%s\") domain \"%s\"\n",
1033 			    name, as->as_ctx->ac_dom[as->as_dom_idx]);
1034 			as->as_dom_flags |= ASYNC_DOM_DOMAIN;
1035 			return (asr_domcat(name,
1036 			    as->as_ctx->ac_dom[as->as_dom_idx++], buf, len));
1037 		}
1038 
1039 		/* No more domain to try. */
1040 
1041 		as->as_dom_step = DOM_DONE;
1042 
1043 		/*
1044 		 * If the name was not tried as an absolute name before,
1045 		 * do it now.
1046 		 */
1047 		if (!(as->as_dom_flags & ASYNC_DOM_NDOTS)) {
1048 			DPRINT("asr: asr_iter_domain(\"%s\") as is\n", name);
1049 			as->as_dom_flags |= ASYNC_DOM_ASIS;
1050 			if (strlcpy(buf, name, len) >= len)
1051 				return (0);
1052 			return (strlen(buf));
1053 		}
1054 		/* Otherwise, we are done. */
1055 
1056 	case DOM_DONE:
1057 	default:
1058 		DPRINT("asr: asr_iter_domain(\"%s\") done\n", name);
1059 		return (-1);
1060 	}
1061 }
1062 
1063 #if ASR_OPT_HOSTALIASES
1064 /*
1065  * Check if the hostname "name" is a user-defined alias as per hostname(7).
1066  * If so, copies the result in the buffer "abuf" of size "abufsz" and
1067  * return "abuf". Otherwise return NULL.
1068  */
1069 static char *
1070 asr_hostalias(const char *name, char *abuf, size_t abufsz)
1071 {
1072 	FILE	 *fp;
1073 	size_t	  len;
1074 	char	 *file, *buf, *tokens[2];
1075 	int	  ntok;
1076 
1077 	file = getenv("HOSTALIASES");
1078 	if (file == NULL || issetugid() != 0 || (fp = fopen(file, "r")) == NULL)
1079 		return (NULL);
1080 
1081 	DPRINT("asr: looking up aliases in \"%s\"\n", file);
1082 
1083 	while ((buf = fgetln(fp, &len)) != NULL) {
1084 		if (buf[len - 1] == '\n')
1085 			len--;
1086 		buf[len] = '\0';
1087 		if ((ntok = strsplit(buf, tokens, 2)) != 2)
1088 			continue;
1089 		if (!strcasecmp(tokens[0], name)) {
1090 			if (strlcpy(abuf, tokens[1], abufsz) > abufsz)
1091 				continue;
1092 			DPRINT("asr: found alias \"%s\"\n", abuf);
1093 			fclose(fp);
1094 			return (abuf);
1095 		}
1096 	}
1097 
1098 	fclose(fp);
1099 	return (NULL);
1100 }
1101 #endif
1102