1*abe78e02Sjca /* $OpenBSD: res_search_async.c,v 1.21 2017/02/27 10:44:46 jca Exp $ */
2b44da627Seric /*
3b44da627Seric * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
4b44da627Seric *
5b44da627Seric * Permission to use, copy, modify, and distribute this software for any
6b44da627Seric * purpose with or without fee is hereby granted, provided that the above
7b44da627Seric * copyright notice and this permission notice appear in all copies.
8b44da627Seric *
9b44da627Seric * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10b44da627Seric * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11b44da627Seric * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12b44da627Seric * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13b44da627Seric * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14b44da627Seric * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15b44da627Seric * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16b44da627Seric */
17b44da627Seric
18b44da627Seric #include <sys/types.h>
19d216d6b1Seric #include <sys/socket.h>
20b44da627Seric #include <sys/uio.h>
21b44da627Seric #include <arpa/nameser.h>
22d216d6b1Seric #include <netdb.h>
23b44da627Seric
24d216d6b1Seric #include <asr.h>
25b44da627Seric #include <errno.h>
2686648719Seric #include <resolv.h>
27b44da627Seric #include <stdlib.h>
28b44da627Seric #include <string.h>
29b44da627Seric #include <unistd.h>
30b44da627Seric
31b44da627Seric #include "asr_private.h"
32b44da627Seric
335be03f8fSeric static int res_search_async_run(struct asr_query *, struct asr_result *);
345bd9e5c2Seric static size_t domcat(const char *, const char *, char *, size_t);
35b44da627Seric
36b44da627Seric /*
37b44da627Seric * Unlike res_query_async(), this function returns a valid packet only if
38b44da627Seric * h_errno is NETDB_SUCCESS.
39b44da627Seric */
405be03f8fSeric struct asr_query *
res_search_async(const char * name,int class,int type,void * asr)415be03f8fSeric res_search_async(const char *name, int class, int type, void *asr)
42b44da627Seric {
43b44da627Seric struct asr_ctx *ac;
445be03f8fSeric struct asr_query *as;
4546ab4803Seric
4646ab4803Seric DPRINT("asr: res_search_async(\"%s\", %i, %i)\n", name, class, type);
4746ab4803Seric
48253ef892Sderaadt ac = _asr_use_resolver(asr);
49253ef892Sderaadt as = _res_search_async_ctx(name, class, type, ac);
50253ef892Sderaadt _asr_ctx_unref(ac);
51b44da627Seric
52b44da627Seric return (as);
53b44da627Seric }
545826fd8cSguenther DEF_WEAK(res_search_async);
55b44da627Seric
565be03f8fSeric struct asr_query *
_res_search_async_ctx(const char * name,int class,int type,struct asr_ctx * ac)57253ef892Sderaadt _res_search_async_ctx(const char *name, int class, int type, struct asr_ctx *ac)
58b44da627Seric {
595be03f8fSeric struct asr_query *as;
60b44da627Seric
6180f48568Seric DPRINT("asr: res_search_async_ctx(\"%s\", %i, %i)\n", name, class,
6280f48568Seric type);
63b44da627Seric
64253ef892Sderaadt if ((as = _asr_async_new(ac, ASR_SEARCH)) == NULL)
65b44da627Seric goto err; /* errno set */
66b44da627Seric as->as_run = res_search_async_run;
67b44da627Seric if ((as->as.search.name = strdup(name)) == NULL)
68b44da627Seric goto err; /* errno set */
69b44da627Seric
70b44da627Seric as->as.search.class = class;
71b44da627Seric as->as.search.type = type;
72b44da627Seric
73b44da627Seric return (as);
74b44da627Seric err:
75b44da627Seric if (as)
76253ef892Sderaadt _asr_async_free(as);
77b44da627Seric return (NULL);
78b44da627Seric }
79b44da627Seric
80b44da627Seric #define HERRNO_UNSET -2
81b44da627Seric
82b44da627Seric static int
res_search_async_run(struct asr_query * as,struct asr_result * ar)835be03f8fSeric res_search_async_run(struct asr_query *as, struct asr_result *ar)
84b44da627Seric {
85b44da627Seric int r;
86b44da627Seric char fqdn[MAXDNAME];
87b44da627Seric
88b44da627Seric next:
89b44da627Seric switch (as->as_state) {
90b44da627Seric
91b44da627Seric case ASR_STATE_INIT:
92b44da627Seric
93ab50be5eSeric if (as->as.search.name[0] == '\0') {
94ab50be5eSeric ar->ar_h_errno = NO_DATA;
95ab50be5eSeric async_set_state(as, ASR_STATE_HALT);
96ab50be5eSeric break;
97ab50be5eSeric }
98ab50be5eSeric
99b44da627Seric as->as.search.saved_h_errno = HERRNO_UNSET;
100b44da627Seric async_set_state(as, ASR_STATE_NEXT_DOMAIN);
101b44da627Seric break;
102b44da627Seric
103b44da627Seric case ASR_STATE_NEXT_DOMAIN:
10480f48568Seric /*
10580f48568Seric * Reset flags to be able to identify the case in
10680f48568Seric * STATE_SUBQUERY.
10780f48568Seric */
108b44da627Seric as->as_dom_flags = 0;
109b44da627Seric
110253ef892Sderaadt r = _asr_iter_domain(as, as->as.search.name, fqdn, sizeof(fqdn));
111b44da627Seric if (r == -1) {
112b44da627Seric async_set_state(as, ASR_STATE_NOT_FOUND);
113b44da627Seric break;
114b44da627Seric }
1156dde8a29Seric if (r == 0) {
116b44da627Seric ar->ar_errno = EINVAL;
117b44da627Seric ar->ar_h_errno = NO_RECOVERY;
118b44da627Seric ar->ar_datalen = -1;
119b44da627Seric ar->ar_data = NULL;
120b44da627Seric async_set_state(as, ASR_STATE_HALT);
121b44da627Seric break;
122b44da627Seric }
123f6f51dadSeric as->as_subq = _res_query_async_ctx(fqdn,
124c5221d45Seric as->as.search.class, as->as.search.type, as->as_ctx);
125f6f51dadSeric if (as->as_subq == NULL) {
126b44da627Seric ar->ar_errno = errno;
127b44da627Seric if (errno == EINVAL)
128b44da627Seric ar->ar_h_errno = NO_RECOVERY;
129b44da627Seric else
130b44da627Seric ar->ar_h_errno = NETDB_INTERNAL;
131b44da627Seric ar->ar_datalen = -1;
132b44da627Seric ar->ar_data = NULL;
133b44da627Seric async_set_state(as, ASR_STATE_HALT);
134b44da627Seric break;
135b44da627Seric }
136b44da627Seric async_set_state(as, ASR_STATE_SUBQUERY);
137b44da627Seric break;
138b44da627Seric
139b44da627Seric case ASR_STATE_SUBQUERY:
140b44da627Seric
141f6f51dadSeric if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND)
142b44da627Seric return (ASYNC_COND);
143f6f51dadSeric as->as_subq = NULL;
144b44da627Seric
145b44da627Seric if (ar->ar_h_errno == NETDB_SUCCESS) {
146b44da627Seric async_set_state(as, ASR_STATE_HALT);
147b44da627Seric break;
148b44da627Seric }
149b44da627Seric
150b44da627Seric /*
151b44da627Seric * The original res_search() does this in the domain search
152b44da627Seric * loop, but only for ECONNREFUSED. I think we can do better
153b44da627Seric * because technically if we get an errno, it means
154b44da627Seric * we couldn't reach any nameserver, so there is no point
155b44da627Seric * in trying further.
156b44da627Seric */
157b44da627Seric if (ar->ar_errno) {
158b44da627Seric async_set_state(as, ASR_STATE_HALT);
159b44da627Seric break;
160b44da627Seric }
161b44da627Seric
162b44da627Seric free(ar->ar_data);
163b44da627Seric
164b44da627Seric /*
1651e1dfc0cSeric * The original resolver does something like this.
166b44da627Seric */
167b97dbfc0Seric if (as->as_dom_flags & ASYNC_DOM_NDOTS)
168b44da627Seric as->as.search.saved_h_errno = ar->ar_h_errno;
169b44da627Seric
170b44da627Seric if (as->as_dom_flags & ASYNC_DOM_DOMAIN) {
171b44da627Seric if (ar->ar_h_errno == NO_DATA)
172*abe78e02Sjca as->as_flags |= ASYNC_NODATA;
173b44da627Seric else if (ar->ar_h_errno == TRY_AGAIN)
174*abe78e02Sjca as->as_flags |= ASYNC_AGAIN;
175b44da627Seric }
176b44da627Seric
177b44da627Seric async_set_state(as, ASR_STATE_NEXT_DOMAIN);
178b44da627Seric break;
179b44da627Seric
180b44da627Seric case ASR_STATE_NOT_FOUND:
181b44da627Seric
182b44da627Seric if (as->as.search.saved_h_errno != HERRNO_UNSET)
183b44da627Seric ar->ar_h_errno = as->as.search.saved_h_errno;
184*abe78e02Sjca else if (as->as_flags & ASYNC_NODATA)
185b44da627Seric ar->ar_h_errno = NO_DATA;
186*abe78e02Sjca else if (as->as_flags & ASYNC_AGAIN)
187b44da627Seric ar->ar_h_errno = TRY_AGAIN;
188b44da627Seric /*
189b44da627Seric * Else, we got the ar_h_errno value set by res_query_async()
190b44da627Seric * for the last domain.
191b44da627Seric */
192b44da627Seric ar->ar_datalen = -1;
193b44da627Seric ar->ar_data = NULL;
194b44da627Seric async_set_state(as, ASR_STATE_HALT);
195b44da627Seric break;
196b44da627Seric
197b44da627Seric case ASR_STATE_HALT:
198b44da627Seric
199b44da627Seric return (ASYNC_DONE);
200b44da627Seric
201b44da627Seric default:
202b44da627Seric ar->ar_errno = EOPNOTSUPP;
203b44da627Seric ar->ar_h_errno = NETDB_INTERNAL;
204b44da627Seric async_set_state(as, ASR_STATE_HALT);
205b44da627Seric break;
206b44da627Seric }
207b44da627Seric goto next;
208b44da627Seric }
209eaa928d5Seric
210eaa928d5Seric /*
211eaa928d5Seric * Concatenate a name and a domain name. The result has no trailing dot.
212eaa928d5Seric * Return the resulting string length, or 0 in case of error.
213eaa928d5Seric */
2145bd9e5c2Seric static size_t
domcat(const char * name,const char * domain,char * buf,size_t buflen)2155bd9e5c2Seric domcat(const char *name, const char *domain, char *buf, size_t buflen)
216eaa928d5Seric {
217eaa928d5Seric size_t r;
218eaa928d5Seric
219253ef892Sderaadt r = _asr_make_fqdn(name, domain, buf, buflen);
220eaa928d5Seric if (r == 0)
221eaa928d5Seric return (0);
222eaa928d5Seric buf[r - 1] = '\0';
223eaa928d5Seric
224eaa928d5Seric return (r - 1);
225eaa928d5Seric }
226eaa928d5Seric
227eaa928d5Seric enum {
228eaa928d5Seric DOM_INIT,
229eaa928d5Seric DOM_DOMAIN,
230eaa928d5Seric DOM_DONE
231eaa928d5Seric };
232eaa928d5Seric
233eaa928d5Seric /*
234eaa928d5Seric * Implement the search domain strategy.
235eaa928d5Seric *
236eaa928d5Seric * This function works as a generator that constructs complete domains in
237eaa928d5Seric * buffer "buf" of size "len" for the given host name "name", according to the
238eaa928d5Seric * search rules defined by the resolving context. It is supposed to be called
239eaa928d5Seric * multiple times (with the same name) to generate the next possible domain
240eaa928d5Seric * name, if any.
241eaa928d5Seric *
242eaa928d5Seric * It returns -1 if all possibilities have been exhausted, 0 if there was an
243eaa928d5Seric * error generating the next name, or the resulting name length.
244eaa928d5Seric */
245eaa928d5Seric int
_asr_iter_domain(struct asr_query * as,const char * name,char * buf,size_t len)246253ef892Sderaadt _asr_iter_domain(struct asr_query *as, const char *name, char * buf, size_t len)
247eaa928d5Seric {
248eaa928d5Seric const char *c;
249eaa928d5Seric int dots;
250eaa928d5Seric
251eaa928d5Seric switch (as->as_dom_step) {
252eaa928d5Seric
253eaa928d5Seric case DOM_INIT:
254eaa928d5Seric /* First call */
255eaa928d5Seric
256eaa928d5Seric /*
257eaa928d5Seric * If "name" is an FQDN, that's the only result and we
258eaa928d5Seric * don't try anything else.
259eaa928d5Seric */
260eaa928d5Seric if (strlen(name) && name[strlen(name) - 1] == '.') {
2615bd9e5c2Seric DPRINT("asr: iter_domain(\"%s\") fqdn\n", name);
262eaa928d5Seric as->as_dom_flags |= ASYNC_DOM_FQDN;
263eaa928d5Seric as->as_dom_step = DOM_DONE;
2645bd9e5c2Seric return (domcat(name, NULL, buf, len));
265eaa928d5Seric }
266eaa928d5Seric
267eaa928d5Seric /*
268eaa928d5Seric * Otherwise, we iterate through the specified search domains.
269eaa928d5Seric */
270eaa928d5Seric as->as_dom_step = DOM_DOMAIN;
271eaa928d5Seric as->as_dom_idx = 0;
272eaa928d5Seric
273eaa928d5Seric /*
274eaa928d5Seric * If "name" as enough dots, use it as-is first, as indicated
275eaa928d5Seric * in resolv.conf(5).
276eaa928d5Seric */
277eaa928d5Seric dots = 0;
278eaa928d5Seric for (c = name; *c; c++)
279eaa928d5Seric dots += (*c == '.');
280eaa928d5Seric if (dots >= as->as_ctx->ac_ndots) {
2815bd9e5c2Seric DPRINT("asr: iter_domain(\"%s\") ndots\n", name);
282eaa928d5Seric as->as_dom_flags |= ASYNC_DOM_NDOTS;
283eaa928d5Seric if (strlcpy(buf, name, len) >= len)
284eaa928d5Seric return (0);
285eaa928d5Seric return (strlen(buf));
286eaa928d5Seric }
287eaa928d5Seric /* Otherwise, starts using the search domains */
288eaa928d5Seric /* FALLTHROUGH */
289eaa928d5Seric
290eaa928d5Seric case DOM_DOMAIN:
29186648719Seric if (as->as_dom_idx < as->as_ctx->ac_domcount &&
29286648719Seric (as->as_ctx->ac_options & RES_DNSRCH || (
29386648719Seric as->as_ctx->ac_options & RES_DEFNAMES &&
29486648719Seric as->as_dom_idx == 0 &&
29586648719Seric strchr(name, '.') == NULL))) {
2965bd9e5c2Seric DPRINT("asr: iter_domain(\"%s\") domain \"%s\"\n",
297eaa928d5Seric name, as->as_ctx->ac_dom[as->as_dom_idx]);
298eaa928d5Seric as->as_dom_flags |= ASYNC_DOM_DOMAIN;
2995bd9e5c2Seric return (domcat(name,
300eaa928d5Seric as->as_ctx->ac_dom[as->as_dom_idx++], buf, len));
301eaa928d5Seric }
302eaa928d5Seric
303eaa928d5Seric /* No more domain to try. */
304eaa928d5Seric
305eaa928d5Seric as->as_dom_step = DOM_DONE;
306eaa928d5Seric
307eaa928d5Seric /*
308eaa928d5Seric * If the name was not tried as an absolute name before,
309eaa928d5Seric * do it now.
310eaa928d5Seric */
311eaa928d5Seric if (!(as->as_dom_flags & ASYNC_DOM_NDOTS)) {
3125bd9e5c2Seric DPRINT("asr: iter_domain(\"%s\") as is\n", name);
313eaa928d5Seric as->as_dom_flags |= ASYNC_DOM_ASIS;
314eaa928d5Seric if (strlcpy(buf, name, len) >= len)
315eaa928d5Seric return (0);
316eaa928d5Seric return (strlen(buf));
317eaa928d5Seric }
318eaa928d5Seric /* Otherwise, we are done. */
319eaa928d5Seric
320eaa928d5Seric case DOM_DONE:
321eaa928d5Seric default:
3225bd9e5c2Seric DPRINT("asr: iter_domain(\"%s\") done\n", name);
323eaa928d5Seric return (-1);
324eaa928d5Seric }
325eaa928d5Seric }
326