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