xref: /openbsd/lib/libc/asr/res_search_async.c (revision abe78e02)
1 /*	$OpenBSD: res_search_async.c,v 1.21 2017/02/27 10:44:46 jca Exp $	*/
2 /*
3  * Copyright (c) 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/socket.h>
20 #include <sys/uio.h>
21 #include <arpa/nameser.h>
22 #include <netdb.h>
23 
24 #include <asr.h>
25 #include <errno.h>
26 #include <resolv.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 #include "asr_private.h"
32 
33 static int res_search_async_run(struct asr_query *, struct asr_result *);
34 static size_t domcat(const char *, const char *, char *, size_t);
35 
36 /*
37  * Unlike res_query_async(), this function returns a valid packet only if
38  * h_errno is NETDB_SUCCESS.
39  */
40 struct asr_query *
res_search_async(const char * name,int class,int type,void * asr)41 res_search_async(const char *name, int class, int type, void *asr)
42 {
43 	struct asr_ctx	 *ac;
44 	struct asr_query *as;
45 
46 	DPRINT("asr: res_search_async(\"%s\", %i, %i)\n", name, class, type);
47 
48 	ac = _asr_use_resolver(asr);
49 	as = _res_search_async_ctx(name, class, type, ac);
50 	_asr_ctx_unref(ac);
51 
52 	return (as);
53 }
54 DEF_WEAK(res_search_async);
55 
56 struct asr_query *
_res_search_async_ctx(const char * name,int class,int type,struct asr_ctx * ac)57 _res_search_async_ctx(const char *name, int class, int type, struct asr_ctx *ac)
58 {
59 	struct asr_query	*as;
60 
61 	DPRINT("asr: res_search_async_ctx(\"%s\", %i, %i)\n", name, class,
62 	    type);
63 
64 	if ((as = _asr_async_new(ac, ASR_SEARCH)) == NULL)
65 		goto err; /* errno set */
66 	as->as_run  = res_search_async_run;
67 	if ((as->as.search.name = strdup(name)) == NULL)
68 		goto err; /* errno set */
69 
70 	as->as.search.class = class;
71 	as->as.search.type = type;
72 
73 	return (as);
74     err:
75 	if (as)
76 		_asr_async_free(as);
77 	return (NULL);
78 }
79 
80 #define HERRNO_UNSET	-2
81 
82 static int
res_search_async_run(struct asr_query * as,struct asr_result * ar)83 res_search_async_run(struct asr_query *as, struct asr_result *ar)
84 {
85 	int	r;
86 	char	fqdn[MAXDNAME];
87 
88     next:
89 	switch (as->as_state) {
90 
91 	case ASR_STATE_INIT:
92 
93 		if (as->as.search.name[0] == '\0') {
94 			ar->ar_h_errno = NO_DATA;
95 			async_set_state(as, ASR_STATE_HALT);
96 			break;
97 		}
98 
99 		as->as.search.saved_h_errno = HERRNO_UNSET;
100 		async_set_state(as, ASR_STATE_NEXT_DOMAIN);
101 		break;
102 
103 	case ASR_STATE_NEXT_DOMAIN:
104 		/*
105 		 * Reset flags to be able to identify the case in
106 		 * STATE_SUBQUERY.
107 		 */
108 		as->as_dom_flags = 0;
109 
110 		r = _asr_iter_domain(as, as->as.search.name, fqdn, sizeof(fqdn));
111 		if (r == -1) {
112 			async_set_state(as, ASR_STATE_NOT_FOUND);
113 			break;
114 		}
115 		if (r == 0) {
116 			ar->ar_errno = EINVAL;
117 			ar->ar_h_errno = NO_RECOVERY;
118 			ar->ar_datalen = -1;
119 			ar->ar_data = NULL;
120 			async_set_state(as, ASR_STATE_HALT);
121 			break;
122 		}
123 		as->as_subq = _res_query_async_ctx(fqdn,
124 		    as->as.search.class, as->as.search.type, as->as_ctx);
125 		if (as->as_subq == NULL) {
126 			ar->ar_errno = errno;
127 			if (errno == EINVAL)
128 				ar->ar_h_errno = NO_RECOVERY;
129 			else
130 				ar->ar_h_errno = NETDB_INTERNAL;
131 			ar->ar_datalen = -1;
132 			ar->ar_data = NULL;
133 			async_set_state(as, ASR_STATE_HALT);
134 			break;
135 		}
136 		async_set_state(as, ASR_STATE_SUBQUERY);
137 		break;
138 
139 	case ASR_STATE_SUBQUERY:
140 
141 		if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND)
142 			return (ASYNC_COND);
143 		as->as_subq = NULL;
144 
145 		if (ar->ar_h_errno == NETDB_SUCCESS) {
146 			async_set_state(as, ASR_STATE_HALT);
147 			break;
148 		}
149 
150 		/*
151 		 * The original res_search() does this in the domain search
152 		 * loop, but only for ECONNREFUSED. I think we can do better
153 		 * because technically if we get an errno, it means
154 		 * we couldn't reach any nameserver, so there is no point
155 		 * in trying further.
156 		 */
157 		if (ar->ar_errno) {
158 			async_set_state(as, ASR_STATE_HALT);
159 			break;
160 		}
161 
162 		free(ar->ar_data);
163 
164 		/*
165 		 * The original resolver does something like this.
166 		 */
167 		if (as->as_dom_flags & ASYNC_DOM_NDOTS)
168 			as->as.search.saved_h_errno = ar->ar_h_errno;
169 
170 		if (as->as_dom_flags & ASYNC_DOM_DOMAIN) {
171 			if (ar->ar_h_errno == NO_DATA)
172 				as->as_flags |= ASYNC_NODATA;
173 			else if (ar->ar_h_errno == TRY_AGAIN)
174 				as->as_flags |= ASYNC_AGAIN;
175 		}
176 
177 		async_set_state(as, ASR_STATE_NEXT_DOMAIN);
178 		break;
179 
180 	case ASR_STATE_NOT_FOUND:
181 
182 		if (as->as.search.saved_h_errno != HERRNO_UNSET)
183 			ar->ar_h_errno = as->as.search.saved_h_errno;
184 		else if (as->as_flags & ASYNC_NODATA)
185 			ar->ar_h_errno = NO_DATA;
186 		else if (as->as_flags & ASYNC_AGAIN)
187 			ar->ar_h_errno = TRY_AGAIN;
188 		/*
189 		 * Else, we got the ar_h_errno value set by res_query_async()
190 		 * for the last domain.
191 		 */
192 		ar->ar_datalen = -1;
193 		ar->ar_data = NULL;
194 		async_set_state(as, ASR_STATE_HALT);
195 		break;
196 
197 	case ASR_STATE_HALT:
198 
199 		return (ASYNC_DONE);
200 
201 	default:
202 		ar->ar_errno = EOPNOTSUPP;
203 		ar->ar_h_errno = NETDB_INTERNAL;
204 		async_set_state(as, ASR_STATE_HALT);
205 		break;
206 	}
207 	goto next;
208 }
209 
210 /*
211  * Concatenate a name and a domain name. The result has no trailing dot.
212  * Return the resulting string length, or 0 in case of error.
213  */
214 static size_t
domcat(const char * name,const char * domain,char * buf,size_t buflen)215 domcat(const char *name, const char *domain, char *buf, size_t buflen)
216 {
217 	size_t	r;
218 
219 	r = _asr_make_fqdn(name, domain, buf, buflen);
220 	if (r == 0)
221 		return (0);
222 	buf[r - 1] = '\0';
223 
224 	return (r - 1);
225 }
226 
227 enum {
228 	DOM_INIT,
229 	DOM_DOMAIN,
230 	DOM_DONE
231 };
232 
233 /*
234  * Implement the search domain strategy.
235  *
236  * This function works as a generator that constructs complete domains in
237  * buffer "buf" of size "len" for the given host name "name", according to the
238  * search rules defined by the resolving context.  It is supposed to be called
239  * multiple times (with the same name) to generate the next possible domain
240  * name, if any.
241  *
242  * It returns -1 if all possibilities have been exhausted, 0 if there was an
243  * error generating the next name, or the resulting name length.
244  */
245 int
_asr_iter_domain(struct asr_query * as,const char * name,char * buf,size_t len)246 _asr_iter_domain(struct asr_query *as, const char *name, char * buf, size_t len)
247 {
248 	const char	*c;
249 	int		 dots;
250 
251 	switch (as->as_dom_step) {
252 
253 	case DOM_INIT:
254 		/* First call */
255 
256 		/*
257 		 * If "name" is an FQDN, that's the only result and we
258 		 * don't try anything else.
259 		 */
260 		if (strlen(name) && name[strlen(name) - 1] ==  '.') {
261 			DPRINT("asr: iter_domain(\"%s\") fqdn\n", name);
262 			as->as_dom_flags |= ASYNC_DOM_FQDN;
263 			as->as_dom_step = DOM_DONE;
264 			return (domcat(name, NULL, buf, len));
265 		}
266 
267 		/*
268 		 * Otherwise, we iterate through the specified search domains.
269 		 */
270 		as->as_dom_step = DOM_DOMAIN;
271 		as->as_dom_idx = 0;
272 
273 		/*
274 		 * If "name" as enough dots, use it as-is first, as indicated
275 		 * in resolv.conf(5).
276 		 */
277 		dots = 0;
278 		for (c = name; *c; c++)
279 			dots += (*c == '.');
280 		if (dots >= as->as_ctx->ac_ndots) {
281 			DPRINT("asr: iter_domain(\"%s\") ndots\n", name);
282 			as->as_dom_flags |= ASYNC_DOM_NDOTS;
283 			if (strlcpy(buf, name, len) >= len)
284 				return (0);
285 			return (strlen(buf));
286 		}
287 		/* Otherwise, starts using the search domains */
288 		/* FALLTHROUGH */
289 
290 	case DOM_DOMAIN:
291 		if (as->as_dom_idx < as->as_ctx->ac_domcount &&
292 		    (as->as_ctx->ac_options & RES_DNSRCH || (
293 			as->as_ctx->ac_options & RES_DEFNAMES &&
294 			as->as_dom_idx == 0 &&
295 			strchr(name, '.') == NULL))) {
296 			DPRINT("asr: iter_domain(\"%s\") domain \"%s\"\n",
297 			    name, as->as_ctx->ac_dom[as->as_dom_idx]);
298 			as->as_dom_flags |= ASYNC_DOM_DOMAIN;
299 			return (domcat(name,
300 			    as->as_ctx->ac_dom[as->as_dom_idx++], buf, len));
301 		}
302 
303 		/* No more domain to try. */
304 
305 		as->as_dom_step = DOM_DONE;
306 
307 		/*
308 		 * If the name was not tried as an absolute name before,
309 		 * do it now.
310 		 */
311 		if (!(as->as_dom_flags & ASYNC_DOM_NDOTS)) {
312 			DPRINT("asr: iter_domain(\"%s\") as is\n", name);
313 			as->as_dom_flags |= ASYNC_DOM_ASIS;
314 			if (strlcpy(buf, name, len) >= len)
315 				return (0);
316 			return (strlen(buf));
317 		}
318 		/* Otherwise, we are done. */
319 
320 	case DOM_DONE:
321 	default:
322 		DPRINT("asr: iter_domain(\"%s\") done\n", name);
323 		return (-1);
324 	}
325 }
326