libcli/dns: Add dns_lookup
[samba.git] / libcli / dns / dns_lookup.c
1 /*
2  *  Unix SMB/CIFS implementation.
3  *  Internal DNS query structures
4  *  Copyright (C) Volker Lendecke 2018
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include "replace.h"
21 #include "libcli/dns/dns_lookup.h"
22 #include "libcli/dns/resolvconf.h"
23 #include "libcli/dns/libdns.h"
24 #include "lib/util/tevent_unix.h"
25 #include "lib/util/samba_util.h"
26 #include "lib/util/debug.h"
27
28 struct dns_lookup_state {
29         struct tevent_context *ev;
30         const char *name;
31         enum dns_qclass qclass;
32         enum dns_qtype qtype;
33
34         char **nameservers;
35         size_t num_nameservers;
36         size_t num_sent;
37
38         struct tevent_req **dns_subreqs;
39         struct tevent_req *wait_subreq;
40
41         struct dns_name_packet *reply;
42 };
43
44 static int dns_lookup_send_next(struct tevent_req *req);
45
46 static void dns_lookup_done(struct tevent_req *subreq);
47 static void dns_lookup_waited(struct tevent_req *subreq);
48
49 struct tevent_req *dns_lookup_send(TALLOC_CTX *mem_ctx,
50                                    struct tevent_context *ev,
51                                    FILE *resolv_conf_fp,
52                                    const char *name,
53                                    enum dns_qclass qclass,
54                                    enum dns_qtype qtype)
55 {
56         struct tevent_req *req;
57         struct dns_lookup_state *state;
58         FILE *fp = resolv_conf_fp;
59         int ret;
60
61         req = tevent_req_create(mem_ctx, &state, struct dns_lookup_state);
62         if (req == NULL) {
63                 return NULL;
64         }
65         state->ev = ev;
66         state->name = name;
67         state->qclass = qclass;
68         state->qtype = qtype;
69
70         if (resolv_conf_fp == NULL) {
71                 fp = fopen("/etc/resolv.conf", "r");
72                 if (fp == NULL) {
73                         tevent_req_error(req, errno);
74                         return tevent_req_post(req, ev);
75                 }
76         }
77
78         ret = parse_resolvconf_fp(
79                 fp,
80                 state,
81                 &state->nameservers,
82                 &state->num_nameservers);
83
84         if (resolv_conf_fp == NULL) {
85                 fclose(fp);
86         }
87
88         if (ret != 0) {
89                 tevent_req_error(req, ret);
90                 return tevent_req_post(req, ev);
91         }
92
93         if (state->num_nameservers == 0) {
94                 /*
95                  * glibc's getaddrinfo returns EAI_AGAIN when no
96                  * nameservers are configured. EAGAIN seems closest.
97                  */
98                 tevent_req_error(req, EAGAIN);
99                 return tevent_req_post(req, ev);
100         }
101
102         state->dns_subreqs = talloc_zero_array(
103                 state,
104                 struct tevent_req *,
105                 state->num_nameservers);
106
107         if (tevent_req_nomem(state->dns_subreqs, req)) {
108                 return tevent_req_post(req, ev);
109         }
110
111         ret = dns_lookup_send_next(req);
112         if (tevent_req_error(req, ret)) {
113                 return tevent_req_post(req, ev);
114         }
115
116         return req;
117 }
118
119 static int dns_lookup_send_next(struct tevent_req *req)
120 {
121         struct dns_lookup_state *state = tevent_req_data(
122                 req, struct dns_lookup_state);
123
124         DBG_DEBUG("Sending DNS request #%zu to %s\n",
125                   state->num_sent,
126                   state->nameservers[state->num_sent]);
127
128         state->dns_subreqs[state->num_sent] = dns_cli_request_send(
129                 state->dns_subreqs,
130                 state->ev,
131                 state->nameservers[state->num_sent],
132                 state->name,
133                 state->qclass,
134                 state->qtype);
135
136         if (state->dns_subreqs[state->num_sent] == NULL) {
137                 return ENOMEM;
138         }
139         tevent_req_set_callback(state->dns_subreqs[state->num_sent],
140                                 dns_lookup_done,
141                                 req);
142         state->num_sent += 1;
143
144         if (state->num_sent == state->num_nameservers) {
145                 /*
146                  * No more nameservers left
147                  */
148                 DBG_DEBUG("cancelling wait_subreq\n");
149                 TALLOC_FREE(state->wait_subreq);
150                 return 0;
151         }
152
153         if (state->wait_subreq != NULL) {
154                 /*
155                  * This can happen if we fire the next request upon
156                  * dns_cli_request returning a network-level error
157                  */
158                 return 0;
159         }
160
161         state->wait_subreq = tevent_wakeup_send(
162                 state,
163                 state->ev,
164                 tevent_timeval_current_ofs(1, 0));
165         if (state->wait_subreq == NULL) {
166                 return ENOMEM;
167         }
168         tevent_req_set_callback(state->wait_subreq, dns_lookup_waited, req);
169
170         return 0;
171 }
172
173 static void dns_lookup_done(struct tevent_req *subreq)
174 {
175         struct tevent_req *req = tevent_req_callback_data(
176                 subreq, struct tevent_req);
177         struct dns_lookup_state *state = tevent_req_data(
178                 req, struct dns_lookup_state);
179         int dns_cli_request_ret;
180         size_t i;
181
182         dns_cli_request_ret = dns_cli_request_recv(
183                 subreq,
184                 state,
185                 &state->reply);
186
187         for (i = 0; i < state->num_nameservers; i++) {
188                 if (state->dns_subreqs[i] == subreq) {
189                         break;
190                 }
191         }
192
193         TALLOC_FREE(subreq);
194
195         if (i == state->num_nameservers) {
196                 /* should never happen */
197                 DBG_WARNING("Failed to find subreq");
198                 tevent_req_error(req, EINVAL);
199                 return;
200         }
201         state->dns_subreqs[i] = NULL;
202
203         if (dns_cli_request_ret == 0) {
204                 /*
205                  * Success, cancel everything else
206                  */
207                 TALLOC_FREE(state->dns_subreqs);
208                 TALLOC_FREE(state->wait_subreq);
209                 tevent_req_done(req);
210                 return;
211         }
212
213         DBG_DEBUG("dns_cli_request[%zu] returned %s\n", i,
214                   strerror(dns_cli_request_ret));
215
216         if (state->num_sent < state->num_nameservers) {
217                 /*
218                  * We have a nameserver left to try
219                  */
220                 int ret;
221
222                 ret = dns_lookup_send_next(req);
223                 if (tevent_req_error(req, ret)) {
224                         return;
225                 }
226         }
227
228         DBG_DEBUG("looking for outstanding requests\n");
229
230         for (i = 0; i<state->num_nameservers; i++) {
231                 if (state->dns_subreqs[i] != NULL) {
232                         break;
233                 }
234         }
235
236         DBG_DEBUG("i=%zu, num_nameservers=%zu\n",
237                   i, state->num_nameservers);
238
239         if (i == state->num_nameservers) {
240                 /*
241                  * Report the lower-level error if we have nothing
242                  * outstanding anymore
243                  */
244                 tevent_req_error(req, dns_cli_request_ret);
245                 return;
246         }
247
248         /*
249          * Do nothing: We have other nameservers that might come back
250          * with something good.
251          */
252 }
253
254 static void dns_lookup_waited(struct tevent_req *subreq)
255 {
256         struct tevent_req *req = tevent_req_callback_data(
257                 subreq, struct tevent_req);
258         struct dns_lookup_state *state = tevent_req_data(
259                 req, struct dns_lookup_state);
260         int ret;
261         bool ok;
262
263         DBG_DEBUG("waited\n");
264
265         ok = tevent_wakeup_recv(subreq);
266         TALLOC_FREE(subreq);
267         if (!ok) {
268                 tevent_req_oom(req);
269                 return;
270         }
271         state->wait_subreq = NULL;
272
273         ret = dns_lookup_send_next(req);
274         if (tevent_req_error(req, ret)) {
275                 return;
276         }
277
278         /*
279          * dns_lookup_send_next() has already triggered the next wakeup
280          */
281 }
282
283 int dns_lookup_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
284                     struct dns_name_packet **reply)
285 {
286         struct dns_lookup_state *state = tevent_req_data(
287                 req, struct dns_lookup_state);
288         int err;
289
290         if (tevent_req_is_unix_error(req, &err)) {
291                 return err;
292         }
293
294         *reply = talloc_move(mem_ctx, &state->reply);
295
296         tevent_req_received(req);
297         return 0;
298 }
299
300 int dns_lookup(FILE *resolv_conf_fp,
301                const char *name,
302                enum dns_qclass qclass,
303                enum dns_qtype qtype,
304                TALLOC_CTX *mem_ctx,
305                struct dns_name_packet **reply)
306 {
307         struct tevent_context *ev;
308         struct tevent_req *req;
309         int ret = ENOMEM;
310
311         ev = samba_tevent_context_init(mem_ctx);
312         if (ev == NULL) {
313                 goto fail;
314         }
315         req = dns_lookup_send(ev, ev, resolv_conf_fp, name, qclass, qtype);
316         if (req == NULL) {
317                 goto fail;
318         }
319         if (!tevent_req_poll_unix(req, ev, &ret)) {
320                 goto fail;
321         }
322         ret = dns_lookup_recv(req, mem_ctx, reply);
323 fail:
324         TALLOC_FREE(ev);
325         return ret;
326 }