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