xref: /openbsd-src/lib/libc/asr/res_search_async.c (revision abe78e0284f7775d363a13655878a3554e377e0c)
1*abe78e02Sjca /*	$OpenBSD: res_search_async.c,v 1.21 2017/02/27 10:44:46 jca Exp $	*/
2b44da627Seric /*
3b44da627Seric  * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
4b44da627Seric  *
5b44da627Seric  * Permission to use, copy, modify, and distribute this software for any
6b44da627Seric  * purpose with or without fee is hereby granted, provided that the above
7b44da627Seric  * copyright notice and this permission notice appear in all copies.
8b44da627Seric  *
9b44da627Seric  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10b44da627Seric  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11b44da627Seric  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12b44da627Seric  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13b44da627Seric  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14b44da627Seric  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15b44da627Seric  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16b44da627Seric  */
17b44da627Seric 
18b44da627Seric #include <sys/types.h>
19d216d6b1Seric #include <sys/socket.h>
20b44da627Seric #include <sys/uio.h>
21b44da627Seric #include <arpa/nameser.h>
22d216d6b1Seric #include <netdb.h>
23b44da627Seric 
24d216d6b1Seric #include <asr.h>
25b44da627Seric #include <errno.h>
2686648719Seric #include <resolv.h>
27b44da627Seric #include <stdlib.h>
28b44da627Seric #include <string.h>
29b44da627Seric #include <unistd.h>
30b44da627Seric 
31b44da627Seric #include "asr_private.h"
32b44da627Seric 
335be03f8fSeric static int res_search_async_run(struct asr_query *, struct asr_result *);
345bd9e5c2Seric static size_t domcat(const char *, const char *, char *, size_t);
35b44da627Seric 
36b44da627Seric /*
37b44da627Seric  * Unlike res_query_async(), this function returns a valid packet only if
38b44da627Seric  * h_errno is NETDB_SUCCESS.
39b44da627Seric  */
405be03f8fSeric struct asr_query *
res_search_async(const char * name,int class,int type,void * asr)415be03f8fSeric res_search_async(const char *name, int class, int type, void *asr)
42b44da627Seric {
43b44da627Seric 	struct asr_ctx	 *ac;
445be03f8fSeric 	struct asr_query *as;
4546ab4803Seric 
4646ab4803Seric 	DPRINT("asr: res_search_async(\"%s\", %i, %i)\n", name, class, type);
4746ab4803Seric 
48253ef892Sderaadt 	ac = _asr_use_resolver(asr);
49253ef892Sderaadt 	as = _res_search_async_ctx(name, class, type, ac);
50253ef892Sderaadt 	_asr_ctx_unref(ac);
51b44da627Seric 
52b44da627Seric 	return (as);
53b44da627Seric }
545826fd8cSguenther DEF_WEAK(res_search_async);
55b44da627Seric 
565be03f8fSeric struct asr_query *
_res_search_async_ctx(const char * name,int class,int type,struct asr_ctx * ac)57253ef892Sderaadt _res_search_async_ctx(const char *name, int class, int type, struct asr_ctx *ac)
58b44da627Seric {
595be03f8fSeric 	struct asr_query	*as;
60b44da627Seric 
6180f48568Seric 	DPRINT("asr: res_search_async_ctx(\"%s\", %i, %i)\n", name, class,
6280f48568Seric 	    type);
63b44da627Seric 
64253ef892Sderaadt 	if ((as = _asr_async_new(ac, ASR_SEARCH)) == NULL)
65b44da627Seric 		goto err; /* errno set */
66b44da627Seric 	as->as_run  = res_search_async_run;
67b44da627Seric 	if ((as->as.search.name = strdup(name)) == NULL)
68b44da627Seric 		goto err; /* errno set */
69b44da627Seric 
70b44da627Seric 	as->as.search.class = class;
71b44da627Seric 	as->as.search.type = type;
72b44da627Seric 
73b44da627Seric 	return (as);
74b44da627Seric     err:
75b44da627Seric 	if (as)
76253ef892Sderaadt 		_asr_async_free(as);
77b44da627Seric 	return (NULL);
78b44da627Seric }
79b44da627Seric 
80b44da627Seric #define HERRNO_UNSET	-2
81b44da627Seric 
82b44da627Seric static int
res_search_async_run(struct asr_query * as,struct asr_result * ar)835be03f8fSeric res_search_async_run(struct asr_query *as, struct asr_result *ar)
84b44da627Seric {
85b44da627Seric 	int	r;
86b44da627Seric 	char	fqdn[MAXDNAME];
87b44da627Seric 
88b44da627Seric     next:
89b44da627Seric 	switch (as->as_state) {
90b44da627Seric 
91b44da627Seric 	case ASR_STATE_INIT:
92b44da627Seric 
93ab50be5eSeric 		if (as->as.search.name[0] == '\0') {
94ab50be5eSeric 			ar->ar_h_errno = NO_DATA;
95ab50be5eSeric 			async_set_state(as, ASR_STATE_HALT);
96ab50be5eSeric 			break;
97ab50be5eSeric 		}
98ab50be5eSeric 
99b44da627Seric 		as->as.search.saved_h_errno = HERRNO_UNSET;
100b44da627Seric 		async_set_state(as, ASR_STATE_NEXT_DOMAIN);
101b44da627Seric 		break;
102b44da627Seric 
103b44da627Seric 	case ASR_STATE_NEXT_DOMAIN:
10480f48568Seric 		/*
10580f48568Seric 		 * Reset flags to be able to identify the case in
10680f48568Seric 		 * STATE_SUBQUERY.
10780f48568Seric 		 */
108b44da627Seric 		as->as_dom_flags = 0;
109b44da627Seric 
110253ef892Sderaadt 		r = _asr_iter_domain(as, as->as.search.name, fqdn, sizeof(fqdn));
111b44da627Seric 		if (r == -1) {
112b44da627Seric 			async_set_state(as, ASR_STATE_NOT_FOUND);
113b44da627Seric 			break;
114b44da627Seric 		}
1156dde8a29Seric 		if (r == 0) {
116b44da627Seric 			ar->ar_errno = EINVAL;
117b44da627Seric 			ar->ar_h_errno = NO_RECOVERY;
118b44da627Seric 			ar->ar_datalen = -1;
119b44da627Seric 			ar->ar_data = NULL;
120b44da627Seric 			async_set_state(as, ASR_STATE_HALT);
121b44da627Seric 			break;
122b44da627Seric 		}
123f6f51dadSeric 		as->as_subq = _res_query_async_ctx(fqdn,
124c5221d45Seric 		    as->as.search.class, as->as.search.type, as->as_ctx);
125f6f51dadSeric 		if (as->as_subq == NULL) {
126b44da627Seric 			ar->ar_errno = errno;
127b44da627Seric 			if (errno == EINVAL)
128b44da627Seric 				ar->ar_h_errno = NO_RECOVERY;
129b44da627Seric 			else
130b44da627Seric 				ar->ar_h_errno = NETDB_INTERNAL;
131b44da627Seric 			ar->ar_datalen = -1;
132b44da627Seric 			ar->ar_data = NULL;
133b44da627Seric 			async_set_state(as, ASR_STATE_HALT);
134b44da627Seric 			break;
135b44da627Seric 		}
136b44da627Seric 		async_set_state(as, ASR_STATE_SUBQUERY);
137b44da627Seric 		break;
138b44da627Seric 
139b44da627Seric 	case ASR_STATE_SUBQUERY:
140b44da627Seric 
141f6f51dadSeric 		if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND)
142b44da627Seric 			return (ASYNC_COND);
143f6f51dadSeric 		as->as_subq = NULL;
144b44da627Seric 
145b44da627Seric 		if (ar->ar_h_errno == NETDB_SUCCESS) {
146b44da627Seric 			async_set_state(as, ASR_STATE_HALT);
147b44da627Seric 			break;
148b44da627Seric 		}
149b44da627Seric 
150b44da627Seric 		/*
151b44da627Seric 		 * The original res_search() does this in the domain search
152b44da627Seric 		 * loop, but only for ECONNREFUSED. I think we can do better
153b44da627Seric 		 * because technically if we get an errno, it means
154b44da627Seric 		 * we couldn't reach any nameserver, so there is no point
155b44da627Seric 		 * in trying further.
156b44da627Seric 		 */
157b44da627Seric 		if (ar->ar_errno) {
158b44da627Seric 			async_set_state(as, ASR_STATE_HALT);
159b44da627Seric 			break;
160b44da627Seric 		}
161b44da627Seric 
162b44da627Seric 		free(ar->ar_data);
163b44da627Seric 
164b44da627Seric 		/*
1651e1dfc0cSeric 		 * The original resolver does something like this.
166b44da627Seric 		 */
167b97dbfc0Seric 		if (as->as_dom_flags & ASYNC_DOM_NDOTS)
168b44da627Seric 			as->as.search.saved_h_errno = ar->ar_h_errno;
169b44da627Seric 
170b44da627Seric 		if (as->as_dom_flags & ASYNC_DOM_DOMAIN) {
171b44da627Seric 			if (ar->ar_h_errno == NO_DATA)
172*abe78e02Sjca 				as->as_flags |= ASYNC_NODATA;
173b44da627Seric 			else if (ar->ar_h_errno == TRY_AGAIN)
174*abe78e02Sjca 				as->as_flags |= ASYNC_AGAIN;
175b44da627Seric 		}
176b44da627Seric 
177b44da627Seric 		async_set_state(as, ASR_STATE_NEXT_DOMAIN);
178b44da627Seric 		break;
179b44da627Seric 
180b44da627Seric 	case ASR_STATE_NOT_FOUND:
181b44da627Seric 
182b44da627Seric 		if (as->as.search.saved_h_errno != HERRNO_UNSET)
183b44da627Seric 			ar->ar_h_errno = as->as.search.saved_h_errno;
184*abe78e02Sjca 		else if (as->as_flags & ASYNC_NODATA)
185b44da627Seric 			ar->ar_h_errno = NO_DATA;
186*abe78e02Sjca 		else if (as->as_flags & ASYNC_AGAIN)
187b44da627Seric 			ar->ar_h_errno = TRY_AGAIN;
188b44da627Seric 		/*
189b44da627Seric 		 * Else, we got the ar_h_errno value set by res_query_async()
190b44da627Seric 		 * for the last domain.
191b44da627Seric 		 */
192b44da627Seric 		ar->ar_datalen = -1;
193b44da627Seric 		ar->ar_data = NULL;
194b44da627Seric 		async_set_state(as, ASR_STATE_HALT);
195b44da627Seric 		break;
196b44da627Seric 
197b44da627Seric 	case ASR_STATE_HALT:
198b44da627Seric 
199b44da627Seric 		return (ASYNC_DONE);
200b44da627Seric 
201b44da627Seric 	default:
202b44da627Seric 		ar->ar_errno = EOPNOTSUPP;
203b44da627Seric 		ar->ar_h_errno = NETDB_INTERNAL;
204b44da627Seric 		async_set_state(as, ASR_STATE_HALT);
205b44da627Seric 		break;
206b44da627Seric 	}
207b44da627Seric 	goto next;
208b44da627Seric }
209eaa928d5Seric 
210eaa928d5Seric /*
211eaa928d5Seric  * Concatenate a name and a domain name. The result has no trailing dot.
212eaa928d5Seric  * Return the resulting string length, or 0 in case of error.
213eaa928d5Seric  */
2145bd9e5c2Seric static size_t
domcat(const char * name,const char * domain,char * buf,size_t buflen)2155bd9e5c2Seric domcat(const char *name, const char *domain, char *buf, size_t buflen)
216eaa928d5Seric {
217eaa928d5Seric 	size_t	r;
218eaa928d5Seric 
219253ef892Sderaadt 	r = _asr_make_fqdn(name, domain, buf, buflen);
220eaa928d5Seric 	if (r == 0)
221eaa928d5Seric 		return (0);
222eaa928d5Seric 	buf[r - 1] = '\0';
223eaa928d5Seric 
224eaa928d5Seric 	return (r - 1);
225eaa928d5Seric }
226eaa928d5Seric 
227eaa928d5Seric enum {
228eaa928d5Seric 	DOM_INIT,
229eaa928d5Seric 	DOM_DOMAIN,
230eaa928d5Seric 	DOM_DONE
231eaa928d5Seric };
232eaa928d5Seric 
233eaa928d5Seric /*
234eaa928d5Seric  * Implement the search domain strategy.
235eaa928d5Seric  *
236eaa928d5Seric  * This function works as a generator that constructs complete domains in
237eaa928d5Seric  * buffer "buf" of size "len" for the given host name "name", according to the
238eaa928d5Seric  * search rules defined by the resolving context.  It is supposed to be called
239eaa928d5Seric  * multiple times (with the same name) to generate the next possible domain
240eaa928d5Seric  * name, if any.
241eaa928d5Seric  *
242eaa928d5Seric  * It returns -1 if all possibilities have been exhausted, 0 if there was an
243eaa928d5Seric  * error generating the next name, or the resulting name length.
244eaa928d5Seric  */
245eaa928d5Seric int
_asr_iter_domain(struct asr_query * as,const char * name,char * buf,size_t len)246253ef892Sderaadt _asr_iter_domain(struct asr_query *as, const char *name, char * buf, size_t len)
247eaa928d5Seric {
248eaa928d5Seric 	const char	*c;
249eaa928d5Seric 	int		 dots;
250eaa928d5Seric 
251eaa928d5Seric 	switch (as->as_dom_step) {
252eaa928d5Seric 
253eaa928d5Seric 	case DOM_INIT:
254eaa928d5Seric 		/* First call */
255eaa928d5Seric 
256eaa928d5Seric 		/*
257eaa928d5Seric 		 * If "name" is an FQDN, that's the only result and we
258eaa928d5Seric 		 * don't try anything else.
259eaa928d5Seric 		 */
260eaa928d5Seric 		if (strlen(name) && name[strlen(name) - 1] ==  '.') {
2615bd9e5c2Seric 			DPRINT("asr: iter_domain(\"%s\") fqdn\n", name);
262eaa928d5Seric 			as->as_dom_flags |= ASYNC_DOM_FQDN;
263eaa928d5Seric 			as->as_dom_step = DOM_DONE;
2645bd9e5c2Seric 			return (domcat(name, NULL, buf, len));
265eaa928d5Seric 		}
266eaa928d5Seric 
267eaa928d5Seric 		/*
268eaa928d5Seric 		 * Otherwise, we iterate through the specified search domains.
269eaa928d5Seric 		 */
270eaa928d5Seric 		as->as_dom_step = DOM_DOMAIN;
271eaa928d5Seric 		as->as_dom_idx = 0;
272eaa928d5Seric 
273eaa928d5Seric 		/*
274eaa928d5Seric 		 * If "name" as enough dots, use it as-is first, as indicated
275eaa928d5Seric 		 * in resolv.conf(5).
276eaa928d5Seric 		 */
277eaa928d5Seric 		dots = 0;
278eaa928d5Seric 		for (c = name; *c; c++)
279eaa928d5Seric 			dots += (*c == '.');
280eaa928d5Seric 		if (dots >= as->as_ctx->ac_ndots) {
2815bd9e5c2Seric 			DPRINT("asr: iter_domain(\"%s\") ndots\n", name);
282eaa928d5Seric 			as->as_dom_flags |= ASYNC_DOM_NDOTS;
283eaa928d5Seric 			if (strlcpy(buf, name, len) >= len)
284eaa928d5Seric 				return (0);
285eaa928d5Seric 			return (strlen(buf));
286eaa928d5Seric 		}
287eaa928d5Seric 		/* Otherwise, starts using the search domains */
288eaa928d5Seric 		/* FALLTHROUGH */
289eaa928d5Seric 
290eaa928d5Seric 	case DOM_DOMAIN:
29186648719Seric 		if (as->as_dom_idx < as->as_ctx->ac_domcount &&
29286648719Seric 		    (as->as_ctx->ac_options & RES_DNSRCH || (
29386648719Seric 			as->as_ctx->ac_options & RES_DEFNAMES &&
29486648719Seric 			as->as_dom_idx == 0 &&
29586648719Seric 			strchr(name, '.') == NULL))) {
2965bd9e5c2Seric 			DPRINT("asr: iter_domain(\"%s\") domain \"%s\"\n",
297eaa928d5Seric 			    name, as->as_ctx->ac_dom[as->as_dom_idx]);
298eaa928d5Seric 			as->as_dom_flags |= ASYNC_DOM_DOMAIN;
2995bd9e5c2Seric 			return (domcat(name,
300eaa928d5Seric 			    as->as_ctx->ac_dom[as->as_dom_idx++], buf, len));
301eaa928d5Seric 		}
302eaa928d5Seric 
303eaa928d5Seric 		/* No more domain to try. */
304eaa928d5Seric 
305eaa928d5Seric 		as->as_dom_step = DOM_DONE;
306eaa928d5Seric 
307eaa928d5Seric 		/*
308eaa928d5Seric 		 * If the name was not tried as an absolute name before,
309eaa928d5Seric 		 * do it now.
310eaa928d5Seric 		 */
311eaa928d5Seric 		if (!(as->as_dom_flags & ASYNC_DOM_NDOTS)) {
3125bd9e5c2Seric 			DPRINT("asr: iter_domain(\"%s\") as is\n", name);
313eaa928d5Seric 			as->as_dom_flags |= ASYNC_DOM_ASIS;
314eaa928d5Seric 			if (strlcpy(buf, name, len) >= len)
315eaa928d5Seric 				return (0);
316eaa928d5Seric 			return (strlen(buf));
317eaa928d5Seric 		}
318eaa928d5Seric 		/* Otherwise, we are done. */
319eaa928d5Seric 
320eaa928d5Seric 	case DOM_DONE:
321eaa928d5Seric 	default:
3225bd9e5c2Seric 		DPRINT("asr: iter_domain(\"%s\") done\n", name);
323eaa928d5Seric 		return (-1);
324eaa928d5Seric 	}
325eaa928d5Seric }
326