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