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