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