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