xref: /openbsd/lib/libc/asr/res_search_async.c (revision cca36db2)
1 /*	$OpenBSD: res_search_async.c,v 1.1 2012/04/14 09:24:18 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 #ifdef DEBUG
52 	asr_printf("asr: res_search_async(\"%s\", %i, %i)\n", name, class, type);
53 #endif
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 #ifdef DEBUG
68 	asr_printf("asr: res_search_async_ctx(\"%s\", %i, %i)\n",
69 	    name, class, type);
70 #endif
71 
72 	if ((as = async_new(ac, ASR_SEARCH)) == NULL)
73 		goto err; /* errno set */
74 	as->as_run  = res_search_async_run;
75 	if ((as->as.search.name = strdup(name)) == NULL)
76 		goto err; /* errno set */
77 
78 	if (ans) {
79 		as->as.search.flags |= ASYNC_EXTIBUF;
80 		as->as.search.ibuf = ans;
81 		as->as.search.ibufsize = anslen;
82 	} else {
83 		as->as.search.ibuf = NULL;
84 		as->as.search.ibufsize = 0;
85 	}
86 	as->as.search.ibuflen = 0;
87 
88 	as->as.search.class = class;
89 	as->as.search.type = type;
90 
91 	return (as);
92     err:
93 	if (as)
94 		async_free(as);
95 	return (NULL);
96 }
97 
98 #define HERRNO_UNSET	-2
99 
100 static int
101 res_search_async_run(struct async *as, struct async_res *ar)
102 {
103 	int	r;
104 	char	fqdn[MAXDNAME];
105 
106     next:
107 	switch(as->as_state) {
108 
109 	case ASR_STATE_INIT:
110 
111 		as->as.search.saved_h_errno = HERRNO_UNSET;
112 		async_set_state(as, ASR_STATE_NEXT_DOMAIN);
113 		break;
114 
115 	case ASR_STATE_NEXT_DOMAIN:
116 
117 		/* Reset flags to be able to identify the case in STATE_SUBQUERY. */
118 		as->as_dom_flags = 0;
119 
120 		r = asr_iter_domain(as, as->as.search.name, fqdn, sizeof(fqdn));
121 		if (r == -1) {
122 			async_set_state(as, ASR_STATE_NOT_FOUND);
123 			break;
124 		}
125 		if (r > sizeof(fqdn)) {
126 			ar->ar_errno = EINVAL;
127 			ar->ar_h_errno = NO_RECOVERY;
128 			ar->ar_datalen = -1;
129 			ar->ar_data = NULL;
130 			async_set_state(as, ASR_STATE_HALT);
131 			break;
132 		}
133 		as->as.search.subq = res_query_async_ctx(fqdn,
134 		    as->as.search.class, as->as.search.type,
135 		    as->as.search.ibuf, as->as.search.ibufsize, as->as_ctx);
136 		if (as->as.search.subq == NULL) {
137 			ar->ar_errno = errno;
138 			if (errno == EINVAL)
139 				ar->ar_h_errno = NO_RECOVERY;
140 			else
141 				ar->ar_h_errno = NETDB_INTERNAL;
142 			ar->ar_datalen = -1;
143 			ar->ar_data = NULL;
144 			async_set_state(as, ASR_STATE_HALT);
145 			break;
146 		}
147 		async_set_state(as, ASR_STATE_SUBQUERY);
148 		break;
149 
150 	case ASR_STATE_SUBQUERY:
151 
152 		if ((r = async_run(as->as.search.subq, ar)) == ASYNC_COND)
153 			return (ASYNC_COND);
154 		as->as.search.subq = NULL;
155 
156 		if (ar->ar_h_errno == NETDB_SUCCESS) {
157 			async_set_state(as, ASR_STATE_HALT);
158 			break;
159 		}
160 
161 		/*
162 		 * The original res_search() does this in the domain search
163 		 * loop, but only for ECONNREFUSED. I think we can do better
164 		 * because technically if we get an errno, it means
165 		 * we couldn't reach any nameserver, so there is no point
166 		 * in trying further.
167 		 */
168 		if (ar->ar_errno) {
169 			async_set_state(as, ASR_STATE_HALT);
170 			break;
171 		}
172 
173 		/*
174 		 * If we don't use an external buffer, the packet was allocated
175 		 * by the subquery and it must be freed now.
176 		 */
177 		if ((as->as.search.flags & ASYNC_EXTIBUF) == 0)
178 			free(ar->ar_data);
179 
180 		/*
181 		 * The original resolver does something like this, to
182 		 */
183 		if (as->as_dom_flags & (ASYNC_DOM_NDOTS | ASYNC_DOM_ASIS))
184 			as->as.search.saved_h_errno = ar->ar_h_errno;
185 
186 		if (as->as_dom_flags & ASYNC_DOM_DOMAIN) {
187 			if (ar->ar_h_errno == NO_DATA)
188 				as->as.search.flags |= ASYNC_NODATA;
189 			else if (ar->ar_h_errno == TRY_AGAIN)
190 				as->as.search.flags |= ASYNC_AGAIN;
191 		}
192 
193 		async_set_state(as, ASR_STATE_NEXT_DOMAIN);
194 		break;
195 
196 	case ASR_STATE_NOT_FOUND:
197 
198 		if (as->as.search.saved_h_errno != HERRNO_UNSET)
199 			ar->ar_h_errno = as->as.search.saved_h_errno;
200 		else if (as->as.search.flags & ASYNC_NODATA)
201 			ar->ar_h_errno = NO_DATA;
202 		else if (as->as.search.flags & ASYNC_AGAIN)
203 			ar->ar_h_errno = TRY_AGAIN;
204 		/*
205 		 * Else, we got the ar_h_errno value set by res_query_async()
206 		 * for the last domain.
207 		 */
208 		ar->ar_datalen = -1;
209 		ar->ar_data = NULL;
210 		async_set_state(as, ASR_STATE_HALT);
211 		break;
212 
213 	case ASR_STATE_HALT:
214 
215 		return (ASYNC_DONE);
216 
217 	default:
218 		ar->ar_errno = EOPNOTSUPP;
219 		ar->ar_h_errno = NETDB_INTERNAL;
220 		async_set_state(as, ASR_STATE_HALT);
221                 break;
222 	}
223 	goto next;
224 }
225