xref: /openbsd-src/lib/libc/asr/asr.c (revision 5be03f8ff4bfdb72cf93ab0aee9dc90ee972fe34)
1 /*	$OpenBSD: asr.c,v 1.32 2014/03/25 19:48:11 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 void *
90 asr_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 asr_resolver_done(void *arg)
160 {
161 	struct asr *asr = arg;
162 	struct asr **priv;
163 
164 	if (asr == NULL) {
165 		priv = _THREAD_PRIVATE(_asr, _asr, &_asr);
166 		if (*priv == NULL)
167 			return;
168 		asr = *priv;
169 		*priv = NULL;
170 	}
171 
172 	asr_ctx_unref(asr->a_ctx);
173 	free(asr->a_path);
174 	free(asr);
175 }
176 
177 /*
178  * Cancel an async query.
179  */
180 void
181 asr_abort(struct asr_query *as)
182 {
183 	asr_async_free(as);
184 }
185 
186 /*
187  * Resume the "as" async query resolution.  Return one of ASYNC_COND,
188  * or ASYNC_DONE and put query-specific return values in the user-allocated
189  * memory at "ar".
190  */
191 int
192 asr_run(struct asr_query *as, struct asr_result *ar)
193 {
194 	int	r, saved_errno = errno;
195 
196 	DPRINT("asr: asr_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: asr_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 		asr_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 asr_run_sync(struct asr_query *as, struct asr_result *ar)
219 {
220 	struct pollfd	 fds[1];
221 	int		 r, saved_errno = errno;
222 
223 	while ((r = asr_run(as, ar)) == ASYNC_COND) {
224 		fds[0].fd = ar->ar_fd;
225 		fds[0].events = (ar->ar_cond == ASR_WANT_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 asr_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 asr_query *
247 asr_async_new(struct asr_ctx *ac, int type)
248 {
249 	struct asr_query	*as;
250 
251 	DPRINT("asr: asr_async_new(ctx=%p) type=%i refcount=%i\n", ac, type,
252 	    ac ? ac->ac_refcount : 0);
253 	if (ac == NULL || (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 asr_async_free(struct asr_query *as)
270 {
271 	DPRINT("asr: 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)
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 			asr_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 			asr_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 			asr_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 			asr_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 			asr_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 			asr_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(void *arg)
344 {
345 	struct asr *asr = arg;
346 	struct asr **priv;
347 
348 	if (asr == NULL) {
349 		DPRINT("using thread-local resolver\n");
350 		priv = _THREAD_PRIVATE(_asr, _asr, &_asr);
351 		if (*priv == NULL) {
352 			DPRINT("setting up thread-local resolver\n");
353 			*priv = asr_resolver(NULL);
354 		}
355 		asr = *priv;
356 	}
357 	if (asr != NULL) {
358 		asr_check_reload(asr);
359 		asr_ctx_ref(asr->a_ctx);
360 		return (asr->a_ctx);
361 	}
362 	return (NULL);
363 }
364 
365 static void
366 asr_ctx_ref(struct asr_ctx *ac)
367 {
368 	DPRINT("asr: asr_ctx_ref(ctx=%p) refcount=%i\n", ac, ac->ac_refcount);
369 	ac->ac_refcount += 1;
370 }
371 
372 /*
373  * Drop a reference to an async context, freeing it if the reference
374  * count drops to 0.
375  */
376 void
377 asr_ctx_unref(struct asr_ctx *ac)
378 {
379 	DPRINT("asr: asr_ctx_unref(ctx=%p) refcount=%i\n", ac,
380 	    ac ? ac->ac_refcount : 0);
381 	if (ac == NULL)
382 		return;
383 	if (--ac->ac_refcount)
384 		return;
385 
386 	asr_ctx_free(ac);
387 }
388 
389 static void
390 asr_ctx_free(struct asr_ctx *ac)
391 {
392 	int i;
393 
394 	if (ac->ac_domain)
395 		free(ac->ac_domain);
396 	for (i = 0; i < ASR_MAXNS; i++)
397 		free(ac->ac_ns[i]);
398 	for (i = 0; i < ASR_MAXDOM; i++)
399 		free(ac->ac_dom[i]);
400 
401 	free(ac);
402 }
403 
404 /*
405  * Reload the configuration file if it has changed on disk.
406  */
407 static void
408 asr_check_reload(struct asr *asr)
409 {
410 	struct asr_ctx	*ac;
411 #if ASR_OPT_RELOADCONF
412 	struct stat	 st;
413 	struct timespec	 ts;
414 #endif
415 
416 	if (asr->a_path == NULL)
417 		return;
418 
419 #if ASR_OPT_RELOADCONF
420 	if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
421 		return;
422 
423 	if ((ts.tv_sec - asr->a_rtime) < RELOAD_DELAY && asr->a_rtime != 0)
424 		return;
425 	asr->a_rtime = ts.tv_sec;
426 
427 	DPRINT("asr: checking for update of \"%s\"\n", asr->a_path);
428 	if (stat(asr->a_path, &st) == -1 ||
429 	    asr->a_mtime == st.st_mtime ||
430 	    (ac = asr_ctx_create()) == NULL)
431 		return;
432 	asr->a_mtime = st.st_mtime;
433 #else
434 	if ((ac = asr_ctx_create()) == NULL)
435 		return;
436 #endif
437 
438 	DPRINT("asr: reloading config file\n");
439 	if (asr_ctx_from_file(ac, asr->a_path) == -1) {
440 		asr_ctx_free(ac);
441 		return;
442 	}
443 
444 #if ASR_OPT_ENVOPTS
445 	asr_ctx_envopts(ac);
446 #endif
447 	if (asr->a_ctx)
448 		asr_ctx_unref(asr->a_ctx);
449 	asr->a_ctx = ac;
450 }
451 
452 /*
453  * Construct a fully-qualified domain name for the given name and domain.
454  * If "name" ends with a '.' it is considered as a FQDN by itself.
455  * Otherwise, the domain, which must be a FQDN, is appended to "name" (it
456  * may have a leading dot which would be ignored). If the domain is null,
457  * then "." is used. Return the length of the constructed FQDN or (0) on
458  * error.
459  */
460 size_t
461 asr_make_fqdn(const char *name, const char *domain, char *buf, size_t buflen)
462 {
463 	size_t	len;
464 
465 	if (domain == NULL)
466 		domain = ".";
467 	else if ((len = strlen(domain)) == 0)
468 		return (0);
469 	else if (domain[len -1] != '.')
470 		return (0);
471 
472 	len = strlen(name);
473 	if (len == 0) {
474 		if (strlcpy(buf, domain, buflen) >= buflen)
475 			return (0);
476 	} else if (name[len - 1] !=  '.') {
477 		if (domain[0] == '.')
478 			domain += 1;
479 		if (strlcpy(buf, name, buflen) >= buflen ||
480 		    strlcat(buf, ".", buflen) >= buflen ||
481 		    strlcat(buf, domain, buflen) >= buflen)
482 			return (0);
483 	} else {
484 		if (strlcpy(buf, name, buflen) >= buflen)
485 			return (0);
486 	}
487 
488 	return (strlen(buf));
489 }
490 
491 /*
492  * Count the dots in a string.
493  */
494 static int
495 asr_ndots(const char *s)
496 {
497 	int n;
498 
499 	for (n = 0; *s; s++)
500 		if (*s == '.')
501 			n += 1;
502 
503 	return (n);
504 }
505 
506 /*
507  * Allocate a new empty context.
508  */
509 static struct asr_ctx *
510 asr_ctx_create(void)
511 {
512 	struct asr_ctx	*ac;
513 
514 	if ((ac = calloc(1, sizeof(*ac))) == NULL)
515 		return (NULL);
516 
517 	ac->ac_options = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
518 	ac->ac_refcount = 1;
519 	ac->ac_ndots = 1;
520 	ac->ac_family[0] = AF_INET;
521 	ac->ac_family[1] = AF_INET6;
522 	ac->ac_family[2] = -1;
523 
524 	ac->ac_hostfile = DEFAULT_HOSTFILE;
525 
526 	ac->ac_nscount = 0;
527 	ac->ac_nstimeout = 5;
528 	ac->ac_nsretries = 4;
529 
530 	return (ac);
531 }
532 
533 /*
534  * Add a search domain to the async context.
535  */
536 static int
537 asr_ctx_add_searchdomain(struct asr_ctx *ac, const char *domain)
538 {
539 	char buf[MAXDNAME];
540 
541 	if (ac->ac_domcount == ASR_MAXDOM)
542 		return (-1);
543 
544 	if (asr_make_fqdn(domain, NULL, buf, sizeof(buf)) == 0)
545 		return (-1);
546 
547 	if ((ac->ac_dom[ac->ac_domcount] = strdup(buf)) == NULL)
548 		return (0);
549 
550 	ac->ac_domcount += 1;
551 
552 	return (1);
553 }
554 
555 static int
556 strsplit(char *line, char **tokens, int ntokens)
557 {
558 	int	ntok;
559 	char	*cp, **tp;
560 
561 	for (cp = line, tp = tokens, ntok = 0;
562 	    ntok < ntokens && (*tp = strsep(&cp, " \t")) != NULL; )
563 		if (**tp != '\0') {
564 			tp++;
565 			ntok++;
566 		}
567 
568 	return (ntok);
569 }
570 
571 /*
572  * Pass on a split config line.
573  */
574 static void
575 pass0(char **tok, int n, struct asr_ctx *ac)
576 {
577 	int		 i, j, d;
578 	const char	*e;
579 	struct sockaddr_storage	ss;
580 
581 	if (!strcmp(tok[0], "nameserver")) {
582 		if (ac->ac_nscount == ASR_MAXNS)
583 			return;
584 		if (n != 2)
585 			return;
586 		if (asr_parse_nameserver((struct sockaddr *)&ss, tok[1]))
587 			return;
588 		if ((ac->ac_ns[ac->ac_nscount] = calloc(1, ss.ss_len)) == NULL)
589 			return;
590 		memmove(ac->ac_ns[ac->ac_nscount], &ss, ss.ss_len);
591 		ac->ac_nscount += 1;
592 
593 	} else if (!strcmp(tok[0], "domain")) {
594 		if (n != 2)
595 			return;
596 		if (ac->ac_domain)
597 			return;
598 		ac->ac_domain = strdup(tok[1]);
599 
600 	} else if (!strcmp(tok[0], "lookup")) {
601 		/* ensure that each lookup is only given once */
602 		for (i = 1; i < n; i++)
603 			for (j = i + 1; j < n; j++)
604 				if (!strcmp(tok[i], tok[j]))
605 					return;
606 		ac->ac_dbcount = 0;
607 		for (i = 1; i < n && ac->ac_dbcount < ASR_MAXDB; i++) {
608 			if (!strcmp(tok[i], "yp"))
609 				ac->ac_db[ac->ac_dbcount++] = ASR_DB_YP;
610 			else if (!strcmp(tok[i], "bind"))
611 				ac->ac_db[ac->ac_dbcount++] = ASR_DB_DNS;
612 			else if (!strcmp(tok[i], "file"))
613 				ac->ac_db[ac->ac_dbcount++] = ASR_DB_FILE;
614 		}
615 	} else if (!strcmp(tok[0], "search")) {
616 		/* resolv.conf says the last line wins */
617 		for (i = 0; i < ASR_MAXDOM; i++)
618 			free(ac->ac_dom[i]);
619 		ac->ac_domcount = 0;
620 		for (i = 1; i < n; i++)
621 			asr_ctx_add_searchdomain(ac, tok[i]);
622 
623 	} else if (!strcmp(tok[0], "family")) {
624 		if (n == 1 || n > 3)
625 			return;
626 		for (i = 1; i < n; i++)
627 			if (strcmp(tok[i], "inet4") && strcmp(tok[i], "inet6"))
628 				return;
629 		for (i = 1; i < n; i++)
630 			ac->ac_family[i - 1] = strcmp(tok[i], "inet4") ? \
631 			    AF_INET6 : AF_INET;
632 		ac->ac_family[i - 1] = -1;
633 
634 	} else if (!strcmp(tok[0], "options")) {
635 		for (i = 1; i < n; i++) {
636 			if (!strcmp(tok[i], "tcp"))
637 				ac->ac_options |= RES_USEVC;
638 			else if ((!strncmp(tok[i], "ndots:", 6))) {
639 				e = NULL;
640 				d = strtonum(tok[i] + 6, 1, 16, &e);
641 				if (e == NULL)
642 					ac->ac_ndots = d;
643 			}
644 		}
645 	}
646 }
647 
648 /*
649  * Setup an async context with the config specified in the string "str".
650  */
651 static int
652 asr_ctx_from_string(struct asr_ctx *ac, const char *str)
653 {
654 	char		 buf[512], *ch;
655 
656 	asr_ctx_parse(ac, str);
657 
658 	if (ac->ac_dbcount == 0) {
659 		/* No lookup directive */
660 		asr_ctx_parse(ac, DEFAULT_LOOKUP);
661 	}
662 
663 	if (ac->ac_nscount == 0)
664 		asr_ctx_parse(ac, "nameserver 127.0.0.1");
665 
666 	if (ac->ac_domain == NULL)
667 		if (gethostname(buf, sizeof buf) == 0) {
668 			ch = strchr(buf, '.');
669 			if (ch)
670 				ac->ac_domain = strdup(ch + 1);
671 			else /* Assume root. see resolv.conf(5) */
672 				ac->ac_domain = strdup("");
673 		}
674 
675 	/* If no search domain was specified, use the local subdomains */
676 	if (ac->ac_domcount == 0)
677 		for (ch = ac->ac_domain; ch; ) {
678 			asr_ctx_add_searchdomain(ac, ch);
679 			ch = strchr(ch, '.');
680 			if (ch && asr_ndots(++ch) == 0)
681 				break;
682 		}
683 
684 	return (0);
685 }
686 
687 /*
688  * Setup the "ac" async context from the file at location "path".
689  */
690 static int
691 asr_ctx_from_file(struct asr_ctx *ac, const char *path)
692 {
693 	FILE	*cf;
694 	char	 buf[4096];
695 	ssize_t	 r;
696 
697 	cf = fopen(path, "r");
698 	if (cf == NULL)
699 		return (-1);
700 
701 	r = fread(buf, 1, sizeof buf - 1, cf);
702 	if (feof(cf) == 0) {
703 		DPRINT("asr: config file too long: \"%s\"\n", path);
704 		r = -1;
705 	}
706 	fclose(cf);
707 	if (r == -1)
708 		return (-1);
709 	buf[r] = '\0';
710 
711 	return asr_ctx_from_string(ac, buf);
712 }
713 
714 /*
715  * Parse lines in the configuration string. For each one, split it into
716  * tokens and pass them to "pass0" for processing.
717  */
718 static int
719 asr_ctx_parse(struct asr_ctx *ac, const char *str)
720 {
721 	size_t		 len;
722 	const char	*line;
723 	char		 buf[1024];
724 	char		*tok[10];
725 	int		 ntok;
726 
727 	line = str;
728 	while (*line) {
729 		len = strcspn(line, "\n\0");
730 		if (len < sizeof buf) {
731 			memmove(buf, line, len);
732 			buf[len] = '\0';
733 		} else
734 			buf[0] = '\0';
735 		line += len;
736 		if (*line == '\n')
737 			line++;
738 		buf[strcspn(buf, ";#")] = '\0';
739 		if ((ntok = strsplit(buf, tok, 10)) == 0)
740 			continue;
741 
742 		pass0(tok, ntok, ac);
743 	}
744 
745 	return (0);
746 }
747 
748 #if ASR_OPT_ENVOPTS
749 /*
750  * Check for environment variables altering the configuration as described
751  * in resolv.conf(5).  Altough not documented there, this feature is disabled
752  * for setuid/setgid programs.
753  */
754 static void
755 asr_ctx_envopts(struct asr_ctx *ac)
756 {
757 	char	buf[4096], *e;
758 	size_t	s;
759 
760 	if (issetugid()) {
761 		ac->ac_options |= RES_NOALIASES;
762 		return;
763 	}
764 
765 	if ((e = getenv("RES_OPTIONS")) != NULL) {
766 		strlcpy(buf, "options ", sizeof buf);
767 		strlcat(buf, e, sizeof buf);
768 		s = strlcat(buf, "\n", sizeof buf);
769 		s = strlcat(buf, "\n", sizeof buf);
770 		if (s < sizeof buf)
771 			asr_ctx_parse(ac, buf);
772 	}
773 
774 	if ((e = getenv("LOCALDOMAIN")) != NULL) {
775 		strlcpy(buf, "search ", sizeof buf);
776 		strlcat(buf, e, sizeof buf);
777 		s = strlcat(buf, "\n", sizeof buf);
778 		if (s < sizeof buf)
779 			asr_ctx_parse(ac, buf);
780 	}
781 }
782 #endif
783 
784 /*
785  * Parse a resolv.conf(5) nameserver string into a sockaddr.
786  */
787 static int
788 asr_parse_nameserver(struct sockaddr *sa, const char *s)
789 {
790 	const char	*estr;
791 	char		 buf[256];
792 	char		*port = NULL;
793 	in_port_t	 portno = 53;
794 
795 	if (*s == '[') {
796 		strlcpy(buf, s + 1, sizeof buf);
797 		s = buf;
798 		port = strchr(buf, ']');
799 		if (port == NULL)
800 			return (-1);
801 		*port++ = '\0';
802 		if (*port != ':')
803 			return (-1);
804 		port++;
805 	}
806 
807 	if (port) {
808 		portno = strtonum(port, 1, USHRT_MAX, &estr);
809 		if (estr)
810 			return (-1);
811 	}
812 
813 	if (asr_sockaddr_from_str(sa, PF_UNSPEC, s) == -1)
814 		return (-1);
815 
816 	if (sa->sa_family == PF_INET)
817 		((struct sockaddr_in *)sa)->sin_port = htons(portno);
818 	else if (sa->sa_family == PF_INET6)
819 		((struct sockaddr_in6 *)sa)->sin6_port = htons(portno);
820 
821 	return (0);
822 }
823 
824 /*
825  * Turn a (uncompressed) DNS domain name into a regular nul-terminated string
826  * where labels are separated by dots. The result is put into the "buf" buffer,
827  * truncated if it exceeds "max" chars. The function returns "buf".
828  */
829 char *
830 asr_strdname(const char *_dname, char *buf, size_t max)
831 {
832 	const unsigned char *dname = _dname;
833 	char	*res;
834 	size_t	 left, n, count;
835 
836 	if (_dname[0] == 0) {
837 		strlcpy(buf, ".", max);
838 		return buf;
839 	}
840 
841 	res = buf;
842 	left = max - 1;
843 	for (n = 0; dname[0] && left; n += dname[0]) {
844 		count = (dname[0] < (left - 1)) ? dname[0] : (left - 1);
845 		memmove(buf, dname + 1, count);
846 		dname += dname[0] + 1;
847 		left -= count;
848 		buf += count;
849 		if (left) {
850 			left -= 1;
851 			*buf++ = '.';
852 		}
853 	}
854 	buf[0] = 0;
855 
856 	return (res);
857 }
858 
859 /*
860  * Read and split the next line from the given namedb file.
861  * Return -1 on error, or put the result in the "tokens" array of
862  * size "ntoken" and returns the number of token on the line.
863  */
864 int
865 asr_parse_namedb_line(FILE *file, char **tokens, int ntoken)
866 {
867 	size_t	  len;
868 	char	 *buf;
869 	int	  ntok;
870 
871     again:
872 	if ((buf = fgetln(file, &len)) == NULL)
873 		return (-1);
874 
875 	if (buf[len - 1] == '\n')
876 		len--;
877 
878 	buf[len] = '\0';
879 	buf[strcspn(buf, "#")] = '\0';
880 	if ((ntok = strsplit(buf, tokens, ntoken)) == 0)
881 		goto again;
882 
883 	return (ntok);
884 }
885 
886 /*
887  * Update the async context so that it uses the next configured DB.
888  * Return 0 on success, or -1 if no more DBs is available.
889  */
890 int
891 asr_iter_db(struct asr_query *as)
892 {
893 	if (as->as_db_idx >= as->as_ctx->ac_dbcount) {
894 		DPRINT("asr_iter_db: done\n");
895 		return (-1);
896 	}
897 
898 	as->as_db_idx += 1;
899 	DPRINT("asr_iter_db: %i\n", as->as_db_idx);
900 
901 	return (0);
902 }
903 
904 /*
905  * Check if the hostname "name" is a user-defined alias as per hostname(7).
906  * If so, copies the result in the buffer "abuf" of size "abufsz" and
907  * return "abuf". Otherwise return NULL.
908  */
909 char *
910 asr_hostalias(struct asr_ctx *ac, const char *name, char *abuf, size_t abufsz)
911 {
912 #if ASR_OPT_HOSTALIASES
913 	FILE	 *fp;
914 	size_t	  len;
915 	char	 *file, *buf, *tokens[2];
916 	int	  ntok;
917 
918 	if (ac->ac_options & RES_NOALIASES ||
919 	    asr_ndots(name) != 0 ||
920 	    issetugid() ||
921 	    (file = getenv("HOSTALIASES")) == NULL ||
922 	    (fp = fopen(file, "r")) == NULL)
923 		return (NULL);
924 
925 	DPRINT("asr: looking up aliases in \"%s\"\n", file);
926 
927 	while ((buf = fgetln(fp, &len)) != NULL) {
928 		if (buf[len - 1] == '\n')
929 			len--;
930 		buf[len] = '\0';
931 		if ((ntok = strsplit(buf, tokens, 2)) != 2)
932 			continue;
933 		if (!strcasecmp(tokens[0], name)) {
934 			if (strlcpy(abuf, tokens[1], abufsz) > abufsz)
935 				continue;
936 			DPRINT("asr: found alias \"%s\"\n", abuf);
937 			fclose(fp);
938 			return (abuf);
939 		}
940 	}
941 
942 	fclose(fp);
943 #endif
944 	return (NULL);
945 }
946