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