1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24
25#include "curl_setup.h"
26
27#ifndef CURL_DISABLE_DOH
28
29#include "urldata.h"
30#include "curl_addrinfo.h"
31#include "doh.h"
32
33#include "sendf.h"
34#include "multiif.h"
35#include "url.h"
36#include "share.h"
37#include "curl_base64.h"
38#include "connect.h"
39#include "strdup.h"
40#include "dynbuf.h"
41/* The last 3 #include files should be in this order */
42#include "curl_printf.h"
43#include "curl_memory.h"
44#include "memdebug.h"
45
46#define DNS_CLASS_IN 0x01
47
48#ifndef CURL_DISABLE_VERBOSE_STRINGS
49static const char * const errors[]={
50 "",
51 "Bad label",
52 "Out of range",
53 "Label loop",
54 "Too small",
55 "Out of memory",
56 "RDATA length",
57 "Malformat",
58 "Bad RCODE",
59 "Unexpected TYPE",
60 "Unexpected CLASS",
61 "No content",
62 "Bad ID",
63 "Name too long"
64};
65
66static const char *doh_strerror(DOHcode code)
67{
68 if((code >= DOH_OK) && (code <= DOH_DNS_NAME_TOO_LONG))
69 return errors[code];
70 return "bad error code";
71}
72#endif
73
74/* @unittest 1655
75 */
76UNITTEST DOHcode doh_encode(const char *host,
77 DNStype dnstype,
78 unsigned char *dnsp, /* buffer */
79 size_t len, /* buffer size */
80 size_t *olen) /* output length */
81{
82 const size_t hostlen = strlen(s: host);
83 unsigned char *orig = dnsp;
84 const char *hostp = host;
85
86 /* The expected output length is 16 bytes more than the length of
87 * the QNAME-encoding of the host name.
88 *
89 * A valid DNS name may not contain a zero-length label, except at
90 * the end. For this reason, a name beginning with a dot, or
91 * containing a sequence of two or more consecutive dots, is invalid
92 * and cannot be encoded as a QNAME.
93 *
94 * If the host name ends with a trailing dot, the corresponding
95 * QNAME-encoding is one byte longer than the host name. If (as is
96 * also valid) the hostname is shortened by the omission of the
97 * trailing dot, then its QNAME-encoding will be two bytes longer
98 * than the host name.
99 *
100 * Each [ label, dot ] pair is encoded as [ length, label ],
101 * preserving overall length. A final [ label ] without a dot is
102 * also encoded as [ length, label ], increasing overall length
103 * by one. The encoding is completed by appending a zero byte,
104 * representing the zero-length root label, again increasing
105 * the overall length by one.
106 */
107
108 size_t expected_len;
109 DEBUGASSERT(hostlen);
110 expected_len = 12 + 1 + hostlen + 4;
111 if(host[hostlen-1]!='.')
112 expected_len++;
113
114 if(expected_len > (256 + 16)) /* RFCs 1034, 1035 */
115 return DOH_DNS_NAME_TOO_LONG;
116
117 if(len < expected_len)
118 return DOH_TOO_SMALL_BUFFER;
119
120 *dnsp++ = 0; /* 16 bit id */
121 *dnsp++ = 0;
122 *dnsp++ = 0x01; /* |QR| Opcode |AA|TC|RD| Set the RD bit */
123 *dnsp++ = '\0'; /* |RA| Z | RCODE | */
124 *dnsp++ = '\0';
125 *dnsp++ = 1; /* QDCOUNT (number of entries in the question section) */
126 *dnsp++ = '\0';
127 *dnsp++ = '\0'; /* ANCOUNT */
128 *dnsp++ = '\0';
129 *dnsp++ = '\0'; /* NSCOUNT */
130 *dnsp++ = '\0';
131 *dnsp++ = '\0'; /* ARCOUNT */
132
133 /* encode each label and store it in the QNAME */
134 while(*hostp) {
135 size_t labellen;
136 char *dot = strchr(s: hostp, c: '.');
137 if(dot)
138 labellen = dot - hostp;
139 else
140 labellen = strlen(s: hostp);
141 if((labellen > 63) || (!labellen)) {
142 /* label is too long or too short, error out */
143 *olen = 0;
144 return DOH_DNS_BAD_LABEL;
145 }
146 /* label is non-empty, process it */
147 *dnsp++ = (unsigned char)labellen;
148 memcpy(dest: dnsp, src: hostp, n: labellen);
149 dnsp += labellen;
150 hostp += labellen;
151 /* advance past dot, but only if there is one */
152 if(dot)
153 hostp++;
154 } /* next label */
155
156 *dnsp++ = 0; /* append zero-length label for root */
157
158 /* There are assigned TYPE codes beyond 255: use range [1..65535] */
159 *dnsp++ = (unsigned char)(255 & (dnstype>>8)); /* upper 8 bit TYPE */
160 *dnsp++ = (unsigned char)(255 & dnstype); /* lower 8 bit TYPE */
161
162 *dnsp++ = '\0'; /* upper 8 bit CLASS */
163 *dnsp++ = DNS_CLASS_IN; /* IN - "the Internet" */
164
165 *olen = dnsp - orig;
166
167 /* verify that our estimation of length is valid, since
168 * this has led to buffer overflows in this function */
169 DEBUGASSERT(*olen == expected_len);
170 return DOH_OK;
171}
172
173static size_t
174doh_write_cb(const void *contents, size_t size, size_t nmemb, void *userp)
175{
176 size_t realsize = size * nmemb;
177 struct dynbuf *mem = (struct dynbuf *)userp;
178
179 if(Curl_dyn_addn(s: mem, mem: contents, len: realsize))
180 return 0;
181
182 return realsize;
183}
184
185/* called from multi.c when this DoH transfer is complete */
186static int doh_done(struct Curl_easy *doh, CURLcode result)
187{
188 struct Curl_easy *data = doh->set.dohfor;
189 struct dohdata *dohp = data->req.doh;
190 /* so one of the DoH request done for the 'data' transfer is now complete! */
191 dohp->pending--;
192 infof(data, "a DoH request is completed, %u to go", dohp->pending);
193 if(result)
194 infof(data, "DoH request %s", curl_easy_strerror(result));
195
196 if(!dohp->pending) {
197 /* DoH completed */
198 curl_slist_free_all(list: dohp->headers);
199 dohp->headers = NULL;
200 Curl_expire(data, milli: 0, EXPIRE_RUN_NOW);
201 }
202 return 0;
203}
204
205#define ERROR_CHECK_SETOPT(x,y) \
206do { \
207 result = curl_easy_setopt(doh, x, y); \
208 if(result && \
209 result != CURLE_NOT_BUILT_IN && \
210 result != CURLE_UNKNOWN_OPTION) \
211 goto error; \
212} while(0)
213
214static CURLcode dohprobe(struct Curl_easy *data,
215 struct dnsprobe *p, DNStype dnstype,
216 const char *host,
217 const char *url, CURLM *multi,
218 struct curl_slist *headers)
219{
220 struct Curl_easy *doh = NULL;
221 char *nurl = NULL;
222 CURLcode result = CURLE_OK;
223 timediff_t timeout_ms;
224 DOHcode d = doh_encode(host, dnstype, dnsp: p->dohbuffer, len: sizeof(p->dohbuffer),
225 olen: &p->dohlen);
226 if(d) {
227 failf(data, fmt: "Failed to encode DoH packet [%d]", d);
228 return CURLE_OUT_OF_MEMORY;
229 }
230
231 p->dnstype = dnstype;
232 Curl_dyn_init(s: &p->serverdoh, DYN_DOH_RESPONSE);
233
234 timeout_ms = Curl_timeleft(data, NULL, TRUE);
235 if(timeout_ms <= 0) {
236 result = CURLE_OPERATION_TIMEDOUT;
237 goto error;
238 }
239 /* Curl_open() is the internal version of curl_easy_init() */
240 result = Curl_open(curl: &doh);
241 if(!result) {
242 /* pass in the struct pointer via a local variable to please coverity and
243 the gcc typecheck helpers */
244 struct dynbuf *resp = &p->serverdoh;
245 doh->internal = true;
246 ERROR_CHECK_SETOPT(CURLOPT_URL, url);
247 ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https");
248 ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_write_cb);
249 ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, resp);
250 ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, p->dohbuffer);
251 ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)p->dohlen);
252 ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, headers);
253#ifdef USE_HTTP2
254 ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
255#endif
256#ifndef CURLDEBUG
257 /* enforce HTTPS if not debug */
258 ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
259#else
260 /* in debug mode, also allow http */
261 ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS);
262#endif
263 ERROR_CHECK_SETOPT(CURLOPT_TIMEOUT_MS, (long)timeout_ms);
264 ERROR_CHECK_SETOPT(CURLOPT_SHARE, data->share);
265 if(data->set.err && data->set.err != stderr)
266 ERROR_CHECK_SETOPT(CURLOPT_STDERR, data->set.err);
267 if(data->set.verbose)
268 ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L);
269 if(data->set.no_signal)
270 ERROR_CHECK_SETOPT(CURLOPT_NOSIGNAL, 1L);
271
272 ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYHOST,
273 data->set.doh_verifyhost ? 2L : 0L);
274 ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYPEER,
275 data->set.doh_verifypeer ? 1L : 0L);
276 ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYSTATUS,
277 data->set.doh_verifystatus ? 1L : 0L);
278
279 /* Inherit *some* SSL options from the user's transfer. This is a
280 best-guess as to which options are needed for compatibility. #3661
281
282 Note DoH does not inherit the user's proxy server so proxy SSL settings
283 have no effect and are not inherited. If that changes then two new
284 options should be added to check doh proxy insecure separately,
285 CURLOPT_DOH_PROXY_SSL_VERIFYHOST and CURLOPT_DOH_PROXY_SSL_VERIFYPEER.
286 */
287 if(data->set.ssl.falsestart)
288 ERROR_CHECK_SETOPT(CURLOPT_SSL_FALSESTART, 1L);
289 if(data->set.str[STRING_SSL_CAFILE]) {
290 ERROR_CHECK_SETOPT(CURLOPT_CAINFO,
291 data->set.str[STRING_SSL_CAFILE]);
292 }
293 if(data->set.blobs[BLOB_CAINFO]) {
294 ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB,
295 data->set.blobs[BLOB_CAINFO]);
296 }
297 if(data->set.str[STRING_SSL_CAPATH]) {
298 ERROR_CHECK_SETOPT(CURLOPT_CAPATH,
299 data->set.str[STRING_SSL_CAPATH]);
300 }
301 if(data->set.str[STRING_SSL_CRLFILE]) {
302 ERROR_CHECK_SETOPT(CURLOPT_CRLFILE,
303 data->set.str[STRING_SSL_CRLFILE]);
304 }
305 if(data->set.ssl.certinfo)
306 ERROR_CHECK_SETOPT(CURLOPT_CERTINFO, 1L);
307 if(data->set.ssl.fsslctx)
308 ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl.fsslctx);
309 if(data->set.ssl.fsslctxp)
310 ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl.fsslctxp);
311 if(data->set.fdebug)
312 ERROR_CHECK_SETOPT(CURLOPT_DEBUGFUNCTION, data->set.fdebug);
313 if(data->set.debugdata)
314 ERROR_CHECK_SETOPT(CURLOPT_DEBUGDATA, data->set.debugdata);
315 if(data->set.str[STRING_SSL_EC_CURVES]) {
316 ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES,
317 data->set.str[STRING_SSL_EC_CURVES]);
318 }
319
320 {
321 long mask =
322 (data->set.ssl.enable_beast ?
323 CURLSSLOPT_ALLOW_BEAST : 0) |
324 (data->set.ssl.no_revoke ?
325 CURLSSLOPT_NO_REVOKE : 0) |
326 (data->set.ssl.no_partialchain ?
327 CURLSSLOPT_NO_PARTIALCHAIN : 0) |
328 (data->set.ssl.revoke_best_effort ?
329 CURLSSLOPT_REVOKE_BEST_EFFORT : 0) |
330 (data->set.ssl.native_ca_store ?
331 CURLSSLOPT_NATIVE_CA : 0) |
332 (data->set.ssl.auto_client_cert ?
333 CURLSSLOPT_AUTO_CLIENT_CERT : 0);
334
335 (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, mask);
336 }
337
338 doh->set.fmultidone = doh_done;
339 doh->set.dohfor = data; /* identify for which transfer this is done */
340 p->easy = doh;
341
342 /* DoH private_data must be null because the user must have a way to
343 distinguish their transfer's handle from DoH handles in user
344 callbacks (ie SSL CTX callback). */
345 DEBUGASSERT(!doh->set.private_data);
346
347 if(curl_multi_add_handle(multi_handle: multi, curl_handle: doh))
348 goto error;
349 }
350 else
351 goto error;
352 free(nurl);
353 return CURLE_OK;
354
355error:
356 free(nurl);
357 Curl_close(datap: &doh);
358 return result;
359}
360
361/*
362 * Curl_doh() resolves a name using DoH. It resolves a name and returns a
363 * 'Curl_addrinfo *' with the address information.
364 */
365
366struct Curl_addrinfo *Curl_doh(struct Curl_easy *data,
367 const char *hostname,
368 int port,
369 int *waitp)
370{
371 CURLcode result = CURLE_OK;
372 int slot;
373 struct dohdata *dohp;
374 struct connectdata *conn = data->conn;
375 *waitp = TRUE; /* this never returns synchronously */
376 (void)hostname;
377 (void)port;
378
379 DEBUGASSERT(!data->req.doh);
380 DEBUGASSERT(conn);
381
382 /* start clean, consider allocating this struct on demand */
383 dohp = data->req.doh = calloc(sizeof(struct dohdata), 1);
384 if(!dohp)
385 return NULL;
386
387 conn->bits.doh = TRUE;
388 dohp->host = hostname;
389 dohp->port = port;
390 dohp->headers =
391 curl_slist_append(NULL,
392 data: "Content-Type: application/dns-message");
393 if(!dohp->headers)
394 goto error;
395
396 /* create IPv4 DoH request */
397 result = dohprobe(data, p: &dohp->probe[DOH_PROBE_SLOT_IPADDR_V4],
398 dnstype: DNS_TYPE_A, host: hostname, url: data->set.str[STRING_DOH],
399 multi: data->multi, headers: dohp->headers);
400 if(result)
401 goto error;
402 dohp->pending++;
403
404#ifdef ENABLE_IPV6
405 if((conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
406 /* create IPv6 DoH request */
407 result = dohprobe(data, p: &dohp->probe[DOH_PROBE_SLOT_IPADDR_V6],
408 dnstype: DNS_TYPE_AAAA, host: hostname, url: data->set.str[STRING_DOH],
409 multi: data->multi, headers: dohp->headers);
410 if(result)
411 goto error;
412 dohp->pending++;
413 }
414#endif
415 return NULL;
416
417error:
418 curl_slist_free_all(list: dohp->headers);
419 data->req.doh->headers = NULL;
420 for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) {
421 Curl_close(datap: &dohp->probe[slot].easy);
422 }
423 Curl_safefree(data->req.doh);
424 return NULL;
425}
426
427static DOHcode skipqname(const unsigned char *doh, size_t dohlen,
428 unsigned int *indexp)
429{
430 unsigned char length;
431 do {
432 if(dohlen < (*indexp + 1))
433 return DOH_DNS_OUT_OF_RANGE;
434 length = doh[*indexp];
435 if((length & 0xc0) == 0xc0) {
436 /* name pointer, advance over it and be done */
437 if(dohlen < (*indexp + 2))
438 return DOH_DNS_OUT_OF_RANGE;
439 *indexp += 2;
440 break;
441 }
442 if(length & 0xc0)
443 return DOH_DNS_BAD_LABEL;
444 if(dohlen < (*indexp + 1 + length))
445 return DOH_DNS_OUT_OF_RANGE;
446 *indexp += 1 + length;
447 } while(length);
448 return DOH_OK;
449}
450
451static unsigned short get16bit(const unsigned char *doh, int index)
452{
453 return (unsigned short)((doh[index] << 8) | doh[index + 1]);
454}
455
456static unsigned int get32bit(const unsigned char *doh, int index)
457{
458 /* make clang and gcc optimize this to bswap by incrementing
459 the pointer first. */
460 doh += index;
461
462 /* avoid undefined behavior by casting to unsigned before shifting
463 24 bits, possibly into the sign bit. codegen is same, but
464 ub sanitizer won't be upset */
465 return ( (unsigned)doh[0] << 24) | (doh[1] << 16) |(doh[2] << 8) | doh[3];
466}
467
468static DOHcode store_a(const unsigned char *doh, int index, struct dohentry *d)
469{
470 /* silently ignore addresses over the limit */
471 if(d->numaddr < DOH_MAX_ADDR) {
472 struct dohaddr *a = &d->addr[d->numaddr];
473 a->type = DNS_TYPE_A;
474 memcpy(dest: &a->ip.v4, src: &doh[index], n: 4);
475 d->numaddr++;
476 }
477 return DOH_OK;
478}
479
480static DOHcode store_aaaa(const unsigned char *doh,
481 int index,
482 struct dohentry *d)
483{
484 /* silently ignore addresses over the limit */
485 if(d->numaddr < DOH_MAX_ADDR) {
486 struct dohaddr *a = &d->addr[d->numaddr];
487 a->type = DNS_TYPE_AAAA;
488 memcpy(dest: &a->ip.v6, src: &doh[index], n: 16);
489 d->numaddr++;
490 }
491 return DOH_OK;
492}
493
494static DOHcode store_cname(const unsigned char *doh,
495 size_t dohlen,
496 unsigned int index,
497 struct dohentry *d)
498{
499 struct dynbuf *c;
500 unsigned int loop = 128; /* a valid DNS name can never loop this much */
501 unsigned char length;
502
503 if(d->numcname == DOH_MAX_CNAME)
504 return DOH_OK; /* skip! */
505
506 c = &d->cname[d->numcname++];
507 do {
508 if(index >= dohlen)
509 return DOH_DNS_OUT_OF_RANGE;
510 length = doh[index];
511 if((length & 0xc0) == 0xc0) {
512 int newpos;
513 /* name pointer, get the new offset (14 bits) */
514 if((index + 1) >= dohlen)
515 return DOH_DNS_OUT_OF_RANGE;
516
517 /* move to the new index */
518 newpos = (length & 0x3f) << 8 | doh[index + 1];
519 index = newpos;
520 continue;
521 }
522 else if(length & 0xc0)
523 return DOH_DNS_BAD_LABEL; /* bad input */
524 else
525 index++;
526
527 if(length) {
528 if(Curl_dyn_len(s: c)) {
529 if(Curl_dyn_addn(s: c, STRCONST(".")))
530 return DOH_OUT_OF_MEM;
531 }
532 if((index + length) > dohlen)
533 return DOH_DNS_BAD_LABEL;
534
535 if(Curl_dyn_addn(s: c, mem: &doh[index], len: length))
536 return DOH_OUT_OF_MEM;
537 index += length;
538 }
539 } while(length && --loop);
540
541 if(!loop)
542 return DOH_DNS_LABEL_LOOP;
543 return DOH_OK;
544}
545
546static DOHcode rdata(const unsigned char *doh,
547 size_t dohlen,
548 unsigned short rdlength,
549 unsigned short type,
550 int index,
551 struct dohentry *d)
552{
553 /* RDATA
554 - A (TYPE 1): 4 bytes
555 - AAAA (TYPE 28): 16 bytes
556 - NS (TYPE 2): N bytes */
557 DOHcode rc;
558
559 switch(type) {
560 case DNS_TYPE_A:
561 if(rdlength != 4)
562 return DOH_DNS_RDATA_LEN;
563 rc = store_a(doh, index, d);
564 if(rc)
565 return rc;
566 break;
567 case DNS_TYPE_AAAA:
568 if(rdlength != 16)
569 return DOH_DNS_RDATA_LEN;
570 rc = store_aaaa(doh, index, d);
571 if(rc)
572 return rc;
573 break;
574 case DNS_TYPE_CNAME:
575 rc = store_cname(doh, dohlen, index, d);
576 if(rc)
577 return rc;
578 break;
579 case DNS_TYPE_DNAME:
580 /* explicit for clarity; just skip; rely on synthesized CNAME */
581 break;
582 default:
583 /* unsupported type, just skip it */
584 break;
585 }
586 return DOH_OK;
587}
588
589UNITTEST void de_init(struct dohentry *de)
590{
591 int i;
592 memset(s: de, c: 0, n: sizeof(*de));
593 de->ttl = INT_MAX;
594 for(i = 0; i < DOH_MAX_CNAME; i++)
595 Curl_dyn_init(s: &de->cname[i], DYN_DOH_CNAME);
596}
597
598
599UNITTEST DOHcode doh_decode(const unsigned char *doh,
600 size_t dohlen,
601 DNStype dnstype,
602 struct dohentry *d)
603{
604 unsigned char rcode;
605 unsigned short qdcount;
606 unsigned short ancount;
607 unsigned short type = 0;
608 unsigned short rdlength;
609 unsigned short nscount;
610 unsigned short arcount;
611 unsigned int index = 12;
612 DOHcode rc;
613
614 if(dohlen < 12)
615 return DOH_TOO_SMALL_BUFFER; /* too small */
616 if(!doh || doh[0] || doh[1])
617 return DOH_DNS_BAD_ID; /* bad ID */
618 rcode = doh[3] & 0x0f;
619 if(rcode)
620 return DOH_DNS_BAD_RCODE; /* bad rcode */
621
622 qdcount = get16bit(doh, index: 4);
623 while(qdcount) {
624 rc = skipqname(doh, dohlen, indexp: &index);
625 if(rc)
626 return rc; /* bad qname */
627 if(dohlen < (index + 4))
628 return DOH_DNS_OUT_OF_RANGE;
629 index += 4; /* skip question's type and class */
630 qdcount--;
631 }
632
633 ancount = get16bit(doh, index: 6);
634 while(ancount) {
635 unsigned short class;
636 unsigned int ttl;
637
638 rc = skipqname(doh, dohlen, indexp: &index);
639 if(rc)
640 return rc; /* bad qname */
641
642 if(dohlen < (index + 2))
643 return DOH_DNS_OUT_OF_RANGE;
644
645 type = get16bit(doh, index);
646 if((type != DNS_TYPE_CNAME) /* may be synthesized from DNAME */
647 && (type != DNS_TYPE_DNAME) /* if present, accept and ignore */
648 && (type != dnstype))
649 /* Not the same type as was asked for nor CNAME nor DNAME */
650 return DOH_DNS_UNEXPECTED_TYPE;
651 index += 2;
652
653 if(dohlen < (index + 2))
654 return DOH_DNS_OUT_OF_RANGE;
655 class = get16bit(doh, index);
656 if(DNS_CLASS_IN != class)
657 return DOH_DNS_UNEXPECTED_CLASS; /* unsupported */
658 index += 2;
659
660 if(dohlen < (index + 4))
661 return DOH_DNS_OUT_OF_RANGE;
662
663 ttl = get32bit(doh, index);
664 if(ttl < d->ttl)
665 d->ttl = ttl;
666 index += 4;
667
668 if(dohlen < (index + 2))
669 return DOH_DNS_OUT_OF_RANGE;
670
671 rdlength = get16bit(doh, index);
672 index += 2;
673 if(dohlen < (index + rdlength))
674 return DOH_DNS_OUT_OF_RANGE;
675
676 rc = rdata(doh, dohlen, rdlength, type, index, d);
677 if(rc)
678 return rc; /* bad rdata */
679 index += rdlength;
680 ancount--;
681 }
682
683 nscount = get16bit(doh, index: 8);
684 while(nscount) {
685 rc = skipqname(doh, dohlen, indexp: &index);
686 if(rc)
687 return rc; /* bad qname */
688
689 if(dohlen < (index + 8))
690 return DOH_DNS_OUT_OF_RANGE;
691
692 index += 2 + 2 + 4; /* type, class and ttl */
693
694 if(dohlen < (index + 2))
695 return DOH_DNS_OUT_OF_RANGE;
696
697 rdlength = get16bit(doh, index);
698 index += 2;
699 if(dohlen < (index + rdlength))
700 return DOH_DNS_OUT_OF_RANGE;
701 index += rdlength;
702 nscount--;
703 }
704
705 arcount = get16bit(doh, index: 10);
706 while(arcount) {
707 rc = skipqname(doh, dohlen, indexp: &index);
708 if(rc)
709 return rc; /* bad qname */
710
711 if(dohlen < (index + 8))
712 return DOH_DNS_OUT_OF_RANGE;
713
714 index += 2 + 2 + 4; /* type, class and ttl */
715
716 if(dohlen < (index + 2))
717 return DOH_DNS_OUT_OF_RANGE;
718
719 rdlength = get16bit(doh, index);
720 index += 2;
721 if(dohlen < (index + rdlength))
722 return DOH_DNS_OUT_OF_RANGE;
723 index += rdlength;
724 arcount--;
725 }
726
727 if(index != dohlen)
728 return DOH_DNS_MALFORMAT; /* something is wrong */
729
730 if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr)
731 /* nothing stored! */
732 return DOH_NO_CONTENT;
733
734 return DOH_OK; /* ok */
735}
736
737#ifndef CURL_DISABLE_VERBOSE_STRINGS
738static void showdoh(struct Curl_easy *data,
739 const struct dohentry *d)
740{
741 int i;
742 infof(data, "TTL: %u seconds", d->ttl);
743 for(i = 0; i < d->numaddr; i++) {
744 const struct dohaddr *a = &d->addr[i];
745 if(a->type == DNS_TYPE_A) {
746 infof(data, "DoH A: %u.%u.%u.%u",
747 a->ip.v4[0], a->ip.v4[1],
748 a->ip.v4[2], a->ip.v4[3]);
749 }
750 else if(a->type == DNS_TYPE_AAAA) {
751 int j;
752 char buffer[128];
753 char *ptr;
754 size_t len;
755 msnprintf(buffer, maxlength: 128, format: "DoH AAAA: ");
756 ptr = &buffer[10];
757 len = 118;
758 for(j = 0; j < 16; j += 2) {
759 size_t l;
760 msnprintf(buffer: ptr, maxlength: len, format: "%s%02x%02x", j?":":"", d->addr[i].ip.v6[j],
761 d->addr[i].ip.v6[j + 1]);
762 l = strlen(s: ptr);
763 len -= l;
764 ptr += l;
765 }
766 infof(data, "%s", buffer);
767 }
768 }
769 for(i = 0; i < d->numcname; i++) {
770 infof(data, "CNAME: %s", Curl_dyn_ptr(&d->cname[i]));
771 }
772}
773#else
774#define showdoh(x,y)
775#endif
776
777/*
778 * doh2ai()
779 *
780 * This function returns a pointer to the first element of a newly allocated
781 * Curl_addrinfo struct linked list filled with the data from a set of DoH
782 * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for
783 * a IPv6 stack, but usable also for IPv4, all hosts and environments.
784 *
785 * The memory allocated by this function *MUST* be free'd later on calling
786 * Curl_freeaddrinfo(). For each successful call to this function there
787 * must be an associated call later to Curl_freeaddrinfo().
788 */
789
790static struct Curl_addrinfo *
791doh2ai(const struct dohentry *de, const char *hostname, int port)
792{
793 struct Curl_addrinfo *ai;
794 struct Curl_addrinfo *prevai = NULL;
795 struct Curl_addrinfo *firstai = NULL;
796 struct sockaddr_in *addr;
797#ifdef ENABLE_IPV6
798 struct sockaddr_in6 *addr6;
799#endif
800 CURLcode result = CURLE_OK;
801 int i;
802 size_t hostlen = strlen(s: hostname) + 1; /* include null-terminator */
803
804 if(!de)
805 /* no input == no output! */
806 return NULL;
807
808 for(i = 0; i < de->numaddr; i++) {
809 size_t ss_size;
810 CURL_SA_FAMILY_T addrtype;
811 if(de->addr[i].type == DNS_TYPE_AAAA) {
812#ifndef ENABLE_IPV6
813 /* we can't handle IPv6 addresses */
814 continue;
815#else
816 ss_size = sizeof(struct sockaddr_in6);
817 addrtype = AF_INET6;
818#endif
819 }
820 else {
821 ss_size = sizeof(struct sockaddr_in);
822 addrtype = AF_INET;
823 }
824
825 ai = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen);
826 if(!ai) {
827 result = CURLE_OUT_OF_MEMORY;
828 break;
829 }
830 ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo));
831 ai->ai_canonname = (void *)((char *)ai->ai_addr + ss_size);
832 memcpy(dest: ai->ai_canonname, src: hostname, n: hostlen);
833
834 if(!firstai)
835 /* store the pointer we want to return from this function */
836 firstai = ai;
837
838 if(prevai)
839 /* make the previous entry point to this */
840 prevai->ai_next = ai;
841
842 ai->ai_family = addrtype;
843
844 /* we return all names as STREAM, so when using this address for TFTP
845 the type must be ignored and conn->socktype be used instead! */
846 ai->ai_socktype = SOCK_STREAM;
847
848 ai->ai_addrlen = (curl_socklen_t)ss_size;
849
850 /* leave the rest of the struct filled with zero */
851
852 switch(ai->ai_family) {
853 case AF_INET:
854 addr = (void *)ai->ai_addr; /* storage area for this info */
855 DEBUGASSERT(sizeof(struct in_addr) == sizeof(de->addr[i].ip.v4));
856 memcpy(dest: &addr->sin_addr, src: &de->addr[i].ip.v4, n: sizeof(struct in_addr));
857 addr->sin_family = addrtype;
858 addr->sin_port = htons((unsigned short)port);
859 break;
860
861#ifdef ENABLE_IPV6
862 case AF_INET6:
863 addr6 = (void *)ai->ai_addr; /* storage area for this info */
864 DEBUGASSERT(sizeof(struct in6_addr) == sizeof(de->addr[i].ip.v6));
865 memcpy(dest: &addr6->sin6_addr, src: &de->addr[i].ip.v6, n: sizeof(struct in6_addr));
866 addr6->sin6_family = addrtype;
867 addr6->sin6_port = htons((unsigned short)port);
868 break;
869#endif
870 }
871
872 prevai = ai;
873 }
874
875 if(result) {
876 Curl_freeaddrinfo(cahead: firstai);
877 firstai = NULL;
878 }
879
880 return firstai;
881}
882
883#ifndef CURL_DISABLE_VERBOSE_STRINGS
884static const char *type2name(DNStype dnstype)
885{
886 return (dnstype == DNS_TYPE_A)?"A":"AAAA";
887}
888#endif
889
890UNITTEST void de_cleanup(struct dohentry *d)
891{
892 int i = 0;
893 for(i = 0; i < d->numcname; i++) {
894 Curl_dyn_free(s: &d->cname[i]);
895 }
896}
897
898CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
899 struct Curl_dns_entry **dnsp)
900{
901 CURLcode result;
902 struct dohdata *dohp = data->req.doh;
903 *dnsp = NULL; /* defaults to no response */
904 if(!dohp)
905 return CURLE_OUT_OF_MEMORY;
906
907 if(!dohp->probe[DOH_PROBE_SLOT_IPADDR_V4].easy &&
908 !dohp->probe[DOH_PROBE_SLOT_IPADDR_V6].easy) {
909 failf(data, fmt: "Could not DoH-resolve: %s", data->state.async.hostname);
910 return CONN_IS_PROXIED(data->conn)?CURLE_COULDNT_RESOLVE_PROXY:
911 CURLE_COULDNT_RESOLVE_HOST;
912 }
913 else if(!dohp->pending) {
914 DOHcode rc[DOH_PROBE_SLOTS] = {
915 DOH_OK, DOH_OK
916 };
917 struct dohentry de;
918 int slot;
919 /* remove DoH handles from multi handle and close them */
920 for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) {
921 curl_multi_remove_handle(multi_handle: data->multi, curl_handle: dohp->probe[slot].easy);
922 Curl_close(datap: &dohp->probe[slot].easy);
923 }
924 /* parse the responses, create the struct and return it! */
925 de_init(de: &de);
926 for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) {
927 struct dnsprobe *p = &dohp->probe[slot];
928 if(!p->dnstype)
929 continue;
930 rc[slot] = doh_decode(doh: Curl_dyn_uptr(s: &p->serverdoh),
931 dohlen: Curl_dyn_len(s: &p->serverdoh),
932 dnstype: p->dnstype,
933 d: &de);
934 Curl_dyn_free(s: &p->serverdoh);
935 if(rc[slot]) {
936 infof(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]),
937 type2name(p->dnstype), dohp->host);
938 }
939 } /* next slot */
940
941 result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */
942 if(!rc[DOH_PROBE_SLOT_IPADDR_V4] || !rc[DOH_PROBE_SLOT_IPADDR_V6]) {
943 /* we have an address, of one kind or other */
944 struct Curl_dns_entry *dns;
945 struct Curl_addrinfo *ai;
946
947 infof(data, "DoH Host name: %s", dohp->host);
948 showdoh(data, d: &de);
949
950 ai = doh2ai(de: &de, hostname: dohp->host, port: dohp->port);
951 if(!ai) {
952 de_cleanup(d: &de);
953 return CURLE_OUT_OF_MEMORY;
954 }
955
956 if(data->share)
957 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
958
959 /* we got a response, store it in the cache */
960 dns = Curl_cache_addr(data, addr: ai, hostname: dohp->host, hostlen: 0, port: dohp->port);
961
962 if(data->share)
963 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
964
965 if(!dns) {
966 /* returned failure, bail out nicely */
967 Curl_freeaddrinfo(cahead: ai);
968 }
969 else {
970 data->state.async.dns = dns;
971 *dnsp = dns;
972 result = CURLE_OK; /* address resolution OK */
973 }
974 } /* address processing done */
975
976 /* Now process any build-specific attributes retrieved from DNS */
977
978 /* All done */
979 de_cleanup(d: &de);
980 Curl_safefree(data->req.doh);
981 return result;
982
983 } /* !dohp->pending */
984
985 /* else wait for pending DoH transactions to complete */
986 return CURLE_OK;
987}
988
989#endif /* CURL_DISABLE_DOH */
990