xref: /openbsd-src/lib/libc/asr/res_search_async.c (revision 46ab48037a854f55e538941cf0b61f61d515d06f)
1 /*	$OpenBSD: res_search_async.c,v 1.2 2012/09/09 09:42:06 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 
21 #include <arpa/nameser.h>
22 
23 #include <err.h>
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 /*
30  * TODO:
31  *
32  * - make it possible to reuse ibuf if it was NULL when first called,
33  *   to avoid reallocating buffers everytime.
34  */
35 
36 #include "asr.h"
37 #include "asr_private.h"
38 
39 static int res_search_async_run(struct async *, struct async_res *);
40 
41 /*
42  * Unlike res_query_async(), this function returns a valid packet only if
43  * h_errno is NETDB_SUCCESS.
44  */
45 struct async *
46 res_search_async(const char *name, int class, int type, unsigned char *ans,
47 	int anslen, struct asr *asr)
48 {
49 	struct asr_ctx	*ac;
50 	struct async	*as;
51 
52 	DPRINT("asr: res_search_async(\"%s\", %i, %i)\n", name, class, type);
53 
54 	ac = asr_use_resolver(asr);
55 	as = res_search_async_ctx(name, class, type, ans, anslen, ac);
56 	asr_ctx_unref(ac);
57 
58 	return (as);
59 }
60 
61 struct async *
62 res_search_async_ctx(const char *name, int class, int type, unsigned char *ans,
63 	int anslen, struct asr_ctx *ac)
64 {
65 	struct async	*as;
66 
67 	DPRINT("asr: res_search_async_ctx(\"%s\", %i, %i)\n", name, class, type);
68 
69 	if ((as = 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 	if (ans) {
76 		as->as.search.flags |= ASYNC_EXTIBUF;
77 		as->as.search.ibuf = ans;
78 		as->as.search.ibufsize = anslen;
79 	} else {
80 		as->as.search.ibuf = NULL;
81 		as->as.search.ibufsize = 0;
82 	}
83 	as->as.search.ibuflen = 0;
84 
85 	as->as.search.class = class;
86 	as->as.search.type = type;
87 
88 	return (as);
89     err:
90 	if (as)
91 		async_free(as);
92 	return (NULL);
93 }
94 
95 #define HERRNO_UNSET	-2
96 
97 static int
98 res_search_async_run(struct async *as, struct async_res *ar)
99 {
100 	int	r;
101 	char	fqdn[MAXDNAME];
102 
103     next:
104 	switch(as->as_state) {
105 
106 	case ASR_STATE_INIT:
107 
108 		as->as.search.saved_h_errno = HERRNO_UNSET;
109 		async_set_state(as, ASR_STATE_NEXT_DOMAIN);
110 		break;
111 
112 	case ASR_STATE_NEXT_DOMAIN:
113 
114 		/* Reset flags to be able to identify the case in STATE_SUBQUERY. */
115 		as->as_dom_flags = 0;
116 
117 		r = asr_iter_domain(as, as->as.search.name, fqdn, sizeof(fqdn));
118 		if (r == -1) {
119 			async_set_state(as, ASR_STATE_NOT_FOUND);
120 			break;
121 		}
122 		if (r > sizeof(fqdn)) {
123 			ar->ar_errno = EINVAL;
124 			ar->ar_h_errno = NO_RECOVERY;
125 			ar->ar_datalen = -1;
126 			ar->ar_data = NULL;
127 			async_set_state(as, ASR_STATE_HALT);
128 			break;
129 		}
130 		as->as.search.subq = res_query_async_ctx(fqdn,
131 		    as->as.search.class, as->as.search.type,
132 		    as->as.search.ibuf, as->as.search.ibufsize, as->as_ctx);
133 		if (as->as.search.subq == NULL) {
134 			ar->ar_errno = errno;
135 			if (errno == EINVAL)
136 				ar->ar_h_errno = NO_RECOVERY;
137 			else
138 				ar->ar_h_errno = NETDB_INTERNAL;
139 			ar->ar_datalen = -1;
140 			ar->ar_data = NULL;
141 			async_set_state(as, ASR_STATE_HALT);
142 			break;
143 		}
144 		async_set_state(as, ASR_STATE_SUBQUERY);
145 		break;
146 
147 	case ASR_STATE_SUBQUERY:
148 
149 		if ((r = async_run(as->as.search.subq, ar)) == ASYNC_COND)
150 			return (ASYNC_COND);
151 		as->as.search.subq = NULL;
152 
153 		if (ar->ar_h_errno == NETDB_SUCCESS) {
154 			async_set_state(as, ASR_STATE_HALT);
155 			break;
156 		}
157 
158 		/*
159 		 * The original res_search() does this in the domain search
160 		 * loop, but only for ECONNREFUSED. I think we can do better
161 		 * because technically if we get an errno, it means
162 		 * we couldn't reach any nameserver, so there is no point
163 		 * in trying further.
164 		 */
165 		if (ar->ar_errno) {
166 			async_set_state(as, ASR_STATE_HALT);
167 			break;
168 		}
169 
170 		/*
171 		 * If we don't use an external buffer, the packet was allocated
172 		 * by the subquery and it must be freed now.
173 		 */
174 		if ((as->as.search.flags & ASYNC_EXTIBUF) == 0)
175 			free(ar->ar_data);
176 
177 		/*
178 		 * The original resolver does something like this, to
179 		 */
180 		if (as->as_dom_flags & (ASYNC_DOM_NDOTS | ASYNC_DOM_ASIS))
181 			as->as.search.saved_h_errno = ar->ar_h_errno;
182 
183 		if (as->as_dom_flags & ASYNC_DOM_DOMAIN) {
184 			if (ar->ar_h_errno == NO_DATA)
185 				as->as.search.flags |= ASYNC_NODATA;
186 			else if (ar->ar_h_errno == TRY_AGAIN)
187 				as->as.search.flags |= ASYNC_AGAIN;
188 		}
189 
190 		async_set_state(as, ASR_STATE_NEXT_DOMAIN);
191 		break;
192 
193 	case ASR_STATE_NOT_FOUND:
194 
195 		if (as->as.search.saved_h_errno != HERRNO_UNSET)
196 			ar->ar_h_errno = as->as.search.saved_h_errno;
197 		else if (as->as.search.flags & ASYNC_NODATA)
198 			ar->ar_h_errno = NO_DATA;
199 		else if (as->as.search.flags & ASYNC_AGAIN)
200 			ar->ar_h_errno = TRY_AGAIN;
201 		/*
202 		 * Else, we got the ar_h_errno value set by res_query_async()
203 		 * for the last domain.
204 		 */
205 		ar->ar_datalen = -1;
206 		ar->ar_data = NULL;
207 		async_set_state(as, ASR_STATE_HALT);
208 		break;
209 
210 	case ASR_STATE_HALT:
211 
212 		return (ASYNC_DONE);
213 
214 	default:
215 		ar->ar_errno = EOPNOTSUPP;
216 		ar->ar_h_errno = NETDB_INTERNAL;
217 		async_set_state(as, ASR_STATE_HALT);
218                 break;
219 	}
220 	goto next;
221 }
222