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