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
49 static 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
doh_strerror(DOHcode code)66 static 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 */
doh_encode(const char * host,DNStype dnstype,unsigned char * dnsp,size_t len,size_t * olen)76 UNITTEST 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(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(hostp, '.');
137 if(dot)
138 labellen = dot - hostp;
139 else
140 labellen = strlen(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(dnsp, hostp, 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
173 static size_t
doh_write_cb(const void * contents,size_t size,size_t nmemb,void * userp)174 doh_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(mem, contents, realsize))
180 return 0;
181
182 return realsize;
183 }
184
185 /* called from multi.c when this DoH transfer is complete */
doh_done(struct Curl_easy * doh,CURLcode result)186 static 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(dohp->headers);
199 dohp->headers = NULL;
200 Curl_expire(data, 0, EXPIRE_RUN_NOW);
201 }
202 return 0;
203 }
204
205 #define ERROR_CHECK_SETOPT(x,y) \
206 do { \
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
dohprobe(struct Curl_easy * data,struct dnsprobe * p,DNStype dnstype,const char * host,const char * url,CURLM * multi,struct curl_slist * headers)214 static 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 CURLcode result = CURLE_OK;
222 timediff_t timeout_ms;
223 DOHcode d = doh_encode(host, dnstype, p->dohbuffer, sizeof(p->dohbuffer),
224 &p->dohlen);
225 if(d) {
226 failf(data, "Failed to encode DoH packet [%d]", d);
227 return CURLE_OUT_OF_MEMORY;
228 }
229
230 p->dnstype = dnstype;
231 Curl_dyn_init(&p->serverdoh, DYN_DOH_RESPONSE);
232
233 timeout_ms = Curl_timeleft(data, NULL, TRUE);
234 if(timeout_ms <= 0) {
235 result = CURLE_OPERATION_TIMEDOUT;
236 goto error;
237 }
238 /* Curl_open() is the internal version of curl_easy_init() */
239 result = Curl_open(&doh);
240 if(!result) {
241 /* pass in the struct pointer via a local variable to please coverity and
242 the gcc typecheck helpers */
243 struct dynbuf *resp = &p->serverdoh;
244 doh->state.internal = true;
245 ERROR_CHECK_SETOPT(CURLOPT_URL, url);
246 ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https");
247 ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_write_cb);
248 ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, resp);
249 ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, p->dohbuffer);
250 ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)p->dohlen);
251 ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, headers);
252 #ifdef USE_HTTP2
253 ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
254 ERROR_CHECK_SETOPT(CURLOPT_PIPEWAIT, 1L);
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 handles must not inherit private_data. The handles may be passed to
343 the user via callbacks and the user will be able to identify them as
344 internal handles because private data is not set. The user can then set
345 private_data via CURLOPT_PRIVATE if they so choose. */
346 DEBUGASSERT(!doh->set.private_data);
347
348 if(curl_multi_add_handle(multi, doh))
349 goto error;
350 }
351 else
352 goto error;
353 return CURLE_OK;
354
355 error:
356 Curl_close(&doh);
357 return result;
358 }
359
360 /*
361 * Curl_doh() resolves a name using DoH. It resolves a name and returns a
362 * 'Curl_addrinfo *' with the address information.
363 */
364
Curl_doh(struct Curl_easy * data,const char * hostname,int port,int * waitp)365 struct Curl_addrinfo *Curl_doh(struct Curl_easy *data,
366 const char *hostname,
367 int port,
368 int *waitp)
369 {
370 CURLcode result = CURLE_OK;
371 int slot;
372 struct dohdata *dohp;
373 struct connectdata *conn = data->conn;
374 *waitp = FALSE;
375 (void)hostname;
376 (void)port;
377
378 DEBUGASSERT(!data->req.doh);
379 DEBUGASSERT(conn);
380
381 /* start clean, consider allocating this struct on demand */
382 dohp = data->req.doh = calloc(1, sizeof(struct dohdata));
383 if(!dohp)
384 return NULL;
385
386 conn->bits.doh = TRUE;
387 dohp->host = hostname;
388 dohp->port = port;
389 dohp->headers =
390 curl_slist_append(NULL,
391 "Content-Type: application/dns-message");
392 if(!dohp->headers)
393 goto error;
394
395 /* create IPv4 DoH request */
396 result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V4],
397 DNS_TYPE_A, hostname, data->set.str[STRING_DOH],
398 data->multi, dohp->headers);
399 if(result)
400 goto error;
401 dohp->pending++;
402
403 #ifdef ENABLE_IPV6
404 if((conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
405 /* create IPv6 DoH request */
406 result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V6],
407 DNS_TYPE_AAAA, hostname, data->set.str[STRING_DOH],
408 data->multi, dohp->headers);
409 if(result)
410 goto error;
411 dohp->pending++;
412 }
413 #endif
414 *waitp = TRUE; /* this never returns synchronously */
415 return NULL;
416
417 error:
418 curl_slist_free_all(dohp->headers);
419 data->req.doh->headers = NULL;
420 for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) {
421 (void)curl_multi_remove_handle(data->multi, dohp->probe[slot].easy);
422 Curl_close(&dohp->probe[slot].easy);
423 }
424 Curl_safefree(data->req.doh);
425 return NULL;
426 }
427
skipqname(const unsigned char * doh,size_t dohlen,unsigned int * indexp)428 static DOHcode skipqname(const unsigned char *doh, size_t dohlen,
429 unsigned int *indexp)
430 {
431 unsigned char length;
432 do {
433 if(dohlen < (*indexp + 1))
434 return DOH_DNS_OUT_OF_RANGE;
435 length = doh[*indexp];
436 if((length & 0xc0) == 0xc0) {
437 /* name pointer, advance over it and be done */
438 if(dohlen < (*indexp + 2))
439 return DOH_DNS_OUT_OF_RANGE;
440 *indexp += 2;
441 break;
442 }
443 if(length & 0xc0)
444 return DOH_DNS_BAD_LABEL;
445 if(dohlen < (*indexp + 1 + length))
446 return DOH_DNS_OUT_OF_RANGE;
447 *indexp += (unsigned int)(1 + length);
448 } while(length);
449 return DOH_OK;
450 }
451
get16bit(const unsigned char * doh,int index)452 static unsigned short get16bit(const unsigned char *doh, int index)
453 {
454 return (unsigned short)((doh[index] << 8) | doh[index + 1]);
455 }
456
get32bit(const unsigned char * doh,int index)457 static unsigned int get32bit(const unsigned char *doh, int index)
458 {
459 /* make clang and gcc optimize this to bswap by incrementing
460 the pointer first. */
461 doh += index;
462
463 /* avoid undefined behavior by casting to unsigned before shifting
464 24 bits, possibly into the sign bit. codegen is same, but
465 ub sanitizer won't be upset */
466 return ((unsigned)doh[0] << 24) | ((unsigned)doh[1] << 16) |
467 ((unsigned)doh[2] << 8) | doh[3];
468 }
469
store_a(const unsigned char * doh,int index,struct dohentry * d)470 static DOHcode store_a(const unsigned char *doh, int index, struct dohentry *d)
471 {
472 /* silently ignore addresses over the limit */
473 if(d->numaddr < DOH_MAX_ADDR) {
474 struct dohaddr *a = &d->addr[d->numaddr];
475 a->type = DNS_TYPE_A;
476 memcpy(&a->ip.v4, &doh[index], 4);
477 d->numaddr++;
478 }
479 return DOH_OK;
480 }
481
store_aaaa(const unsigned char * doh,int index,struct dohentry * d)482 static DOHcode store_aaaa(const unsigned char *doh,
483 int index,
484 struct dohentry *d)
485 {
486 /* silently ignore addresses over the limit */
487 if(d->numaddr < DOH_MAX_ADDR) {
488 struct dohaddr *a = &d->addr[d->numaddr];
489 a->type = DNS_TYPE_AAAA;
490 memcpy(&a->ip.v6, &doh[index], 16);
491 d->numaddr++;
492 }
493 return DOH_OK;
494 }
495
store_cname(const unsigned char * doh,size_t dohlen,unsigned int index,struct dohentry * d)496 static DOHcode store_cname(const unsigned char *doh,
497 size_t dohlen,
498 unsigned int index,
499 struct dohentry *d)
500 {
501 struct dynbuf *c;
502 unsigned int loop = 128; /* a valid DNS name can never loop this much */
503 unsigned char length;
504
505 if(d->numcname == DOH_MAX_CNAME)
506 return DOH_OK; /* skip! */
507
508 c = &d->cname[d->numcname++];
509 do {
510 if(index >= dohlen)
511 return DOH_DNS_OUT_OF_RANGE;
512 length = doh[index];
513 if((length & 0xc0) == 0xc0) {
514 int newpos;
515 /* name pointer, get the new offset (14 bits) */
516 if((index + 1) >= dohlen)
517 return DOH_DNS_OUT_OF_RANGE;
518
519 /* move to the new index */
520 newpos = (length & 0x3f) << 8 | doh[index + 1];
521 index = newpos;
522 continue;
523 }
524 else if(length & 0xc0)
525 return DOH_DNS_BAD_LABEL; /* bad input */
526 else
527 index++;
528
529 if(length) {
530 if(Curl_dyn_len(c)) {
531 if(Curl_dyn_addn(c, STRCONST(".")))
532 return DOH_OUT_OF_MEM;
533 }
534 if((index + length) > dohlen)
535 return DOH_DNS_BAD_LABEL;
536
537 if(Curl_dyn_addn(c, &doh[index], length))
538 return DOH_OUT_OF_MEM;
539 index += length;
540 }
541 } while(length && --loop);
542
543 if(!loop)
544 return DOH_DNS_LABEL_LOOP;
545 return DOH_OK;
546 }
547
rdata(const unsigned char * doh,size_t dohlen,unsigned short rdlength,unsigned short type,int index,struct dohentry * d)548 static DOHcode rdata(const unsigned char *doh,
549 size_t dohlen,
550 unsigned short rdlength,
551 unsigned short type,
552 int index,
553 struct dohentry *d)
554 {
555 /* RDATA
556 - A (TYPE 1): 4 bytes
557 - AAAA (TYPE 28): 16 bytes
558 - NS (TYPE 2): N bytes */
559 DOHcode rc;
560
561 switch(type) {
562 case DNS_TYPE_A:
563 if(rdlength != 4)
564 return DOH_DNS_RDATA_LEN;
565 rc = store_a(doh, index, d);
566 if(rc)
567 return rc;
568 break;
569 case DNS_TYPE_AAAA:
570 if(rdlength != 16)
571 return DOH_DNS_RDATA_LEN;
572 rc = store_aaaa(doh, index, d);
573 if(rc)
574 return rc;
575 break;
576 case DNS_TYPE_CNAME:
577 rc = store_cname(doh, dohlen, index, d);
578 if(rc)
579 return rc;
580 break;
581 case DNS_TYPE_DNAME:
582 /* explicit for clarity; just skip; rely on synthesized CNAME */
583 break;
584 default:
585 /* unsupported type, just skip it */
586 break;
587 }
588 return DOH_OK;
589 }
590
de_init(struct dohentry * de)591 UNITTEST void de_init(struct dohentry *de)
592 {
593 int i;
594 memset(de, 0, sizeof(*de));
595 de->ttl = INT_MAX;
596 for(i = 0; i < DOH_MAX_CNAME; i++)
597 Curl_dyn_init(&de->cname[i], DYN_DOH_CNAME);
598 }
599
600
doh_decode(const unsigned char * doh,size_t dohlen,DNStype dnstype,struct dohentry * d)601 UNITTEST DOHcode doh_decode(const unsigned char *doh,
602 size_t dohlen,
603 DNStype dnstype,
604 struct dohentry *d)
605 {
606 unsigned char rcode;
607 unsigned short qdcount;
608 unsigned short ancount;
609 unsigned short type = 0;
610 unsigned short rdlength;
611 unsigned short nscount;
612 unsigned short arcount;
613 unsigned int index = 12;
614 DOHcode rc;
615
616 if(dohlen < 12)
617 return DOH_TOO_SMALL_BUFFER; /* too small */
618 if(!doh || doh[0] || doh[1])
619 return DOH_DNS_BAD_ID; /* bad ID */
620 rcode = doh[3] & 0x0f;
621 if(rcode)
622 return DOH_DNS_BAD_RCODE; /* bad rcode */
623
624 qdcount = get16bit(doh, 4);
625 while(qdcount) {
626 rc = skipqname(doh, dohlen, &index);
627 if(rc)
628 return rc; /* bad qname */
629 if(dohlen < (index + 4))
630 return DOH_DNS_OUT_OF_RANGE;
631 index += 4; /* skip question's type and class */
632 qdcount--;
633 }
634
635 ancount = get16bit(doh, 6);
636 while(ancount) {
637 unsigned short class;
638 unsigned int ttl;
639
640 rc = skipqname(doh, dohlen, &index);
641 if(rc)
642 return rc; /* bad qname */
643
644 if(dohlen < (index + 2))
645 return DOH_DNS_OUT_OF_RANGE;
646
647 type = get16bit(doh, index);
648 if((type != DNS_TYPE_CNAME) /* may be synthesized from DNAME */
649 && (type != DNS_TYPE_DNAME) /* if present, accept and ignore */
650 && (type != dnstype))
651 /* Not the same type as was asked for nor CNAME nor DNAME */
652 return DOH_DNS_UNEXPECTED_TYPE;
653 index += 2;
654
655 if(dohlen < (index + 2))
656 return DOH_DNS_OUT_OF_RANGE;
657 class = get16bit(doh, index);
658 if(DNS_CLASS_IN != class)
659 return DOH_DNS_UNEXPECTED_CLASS; /* unsupported */
660 index += 2;
661
662 if(dohlen < (index + 4))
663 return DOH_DNS_OUT_OF_RANGE;
664
665 ttl = get32bit(doh, index);
666 if(ttl < d->ttl)
667 d->ttl = ttl;
668 index += 4;
669
670 if(dohlen < (index + 2))
671 return DOH_DNS_OUT_OF_RANGE;
672
673 rdlength = get16bit(doh, index);
674 index += 2;
675 if(dohlen < (index + rdlength))
676 return DOH_DNS_OUT_OF_RANGE;
677
678 rc = rdata(doh, dohlen, rdlength, type, index, d);
679 if(rc)
680 return rc; /* bad rdata */
681 index += rdlength;
682 ancount--;
683 }
684
685 nscount = get16bit(doh, 8);
686 while(nscount) {
687 rc = skipqname(doh, dohlen, &index);
688 if(rc)
689 return rc; /* bad qname */
690
691 if(dohlen < (index + 8))
692 return DOH_DNS_OUT_OF_RANGE;
693
694 index += 2 + 2 + 4; /* type, class and ttl */
695
696 if(dohlen < (index + 2))
697 return DOH_DNS_OUT_OF_RANGE;
698
699 rdlength = get16bit(doh, index);
700 index += 2;
701 if(dohlen < (index + rdlength))
702 return DOH_DNS_OUT_OF_RANGE;
703 index += rdlength;
704 nscount--;
705 }
706
707 arcount = get16bit(doh, 10);
708 while(arcount) {
709 rc = skipqname(doh, dohlen, &index);
710 if(rc)
711 return rc; /* bad qname */
712
713 if(dohlen < (index + 8))
714 return DOH_DNS_OUT_OF_RANGE;
715
716 index += 2 + 2 + 4; /* type, class and ttl */
717
718 if(dohlen < (index + 2))
719 return DOH_DNS_OUT_OF_RANGE;
720
721 rdlength = get16bit(doh, index);
722 index += 2;
723 if(dohlen < (index + rdlength))
724 return DOH_DNS_OUT_OF_RANGE;
725 index += rdlength;
726 arcount--;
727 }
728
729 if(index != dohlen)
730 return DOH_DNS_MALFORMAT; /* something is wrong */
731
732 if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr)
733 /* nothing stored! */
734 return DOH_NO_CONTENT;
735
736 return DOH_OK; /* ok */
737 }
738
739 #ifndef CURL_DISABLE_VERBOSE_STRINGS
showdoh(struct Curl_easy * data,const struct dohentry * d)740 static void showdoh(struct Curl_easy *data,
741 const struct dohentry *d)
742 {
743 int i;
744 infof(data, "TTL: %u seconds", d->ttl);
745 for(i = 0; i < d->numaddr; i++) {
746 const struct dohaddr *a = &d->addr[i];
747 if(a->type == DNS_TYPE_A) {
748 infof(data, "DoH A: %u.%u.%u.%u",
749 a->ip.v4[0], a->ip.v4[1],
750 a->ip.v4[2], a->ip.v4[3]);
751 }
752 else if(a->type == DNS_TYPE_AAAA) {
753 int j;
754 char buffer[128];
755 char *ptr;
756 size_t len;
757 msnprintf(buffer, 128, "DoH AAAA: ");
758 ptr = &buffer[10];
759 len = 118;
760 for(j = 0; j < 16; j += 2) {
761 size_t l;
762 msnprintf(ptr, len, "%s%02x%02x", j?":":"", d->addr[i].ip.v6[j],
763 d->addr[i].ip.v6[j + 1]);
764 l = strlen(ptr);
765 len -= l;
766 ptr += l;
767 }
768 infof(data, "%s", buffer);
769 }
770 }
771 for(i = 0; i < d->numcname; i++) {
772 infof(data, "CNAME: %s", Curl_dyn_ptr(&d->cname[i]));
773 }
774 }
775 #else
776 #define showdoh(x,y)
777 #endif
778
779 /*
780 * doh2ai()
781 *
782 * This function returns a pointer to the first element of a newly allocated
783 * Curl_addrinfo struct linked list filled with the data from a set of DoH
784 * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for
785 * a IPv6 stack, but usable also for IPv4, all hosts and environments.
786 *
787 * The memory allocated by this function *MUST* be free'd later on calling
788 * Curl_freeaddrinfo(). For each successful call to this function there
789 * must be an associated call later to Curl_freeaddrinfo().
790 */
791
doh2ai(const struct dohentry * de,const char * hostname,int port,struct Curl_addrinfo ** aip)792 static CURLcode doh2ai(const struct dohentry *de, const char *hostname,
793 int port, struct Curl_addrinfo **aip)
794 {
795 struct Curl_addrinfo *ai;
796 struct Curl_addrinfo *prevai = NULL;
797 struct Curl_addrinfo *firstai = NULL;
798 struct sockaddr_in *addr;
799 #ifdef ENABLE_IPV6
800 struct sockaddr_in6 *addr6;
801 #endif
802 CURLcode result = CURLE_OK;
803 int i;
804 size_t hostlen = strlen(hostname) + 1; /* include null-terminator */
805
806 DEBUGASSERT(de);
807
808 if(!de->numaddr)
809 return CURLE_COULDNT_RESOLVE_HOST;
810
811 for(i = 0; i < de->numaddr; i++) {
812 size_t ss_size;
813 CURL_SA_FAMILY_T addrtype;
814 if(de->addr[i].type == DNS_TYPE_AAAA) {
815 #ifndef ENABLE_IPV6
816 /* we can't handle IPv6 addresses */
817 continue;
818 #else
819 ss_size = sizeof(struct sockaddr_in6);
820 addrtype = AF_INET6;
821 #endif
822 }
823 else {
824 ss_size = sizeof(struct sockaddr_in);
825 addrtype = AF_INET;
826 }
827
828 ai = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen);
829 if(!ai) {
830 result = CURLE_OUT_OF_MEMORY;
831 break;
832 }
833 ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo));
834 ai->ai_canonname = (void *)((char *)ai->ai_addr + ss_size);
835 memcpy(ai->ai_canonname, hostname, hostlen);
836
837 if(!firstai)
838 /* store the pointer we want to return from this function */
839 firstai = ai;
840
841 if(prevai)
842 /* make the previous entry point to this */
843 prevai->ai_next = ai;
844
845 ai->ai_family = addrtype;
846
847 /* we return all names as STREAM, so when using this address for TFTP
848 the type must be ignored and conn->socktype be used instead! */
849 ai->ai_socktype = SOCK_STREAM;
850
851 ai->ai_addrlen = (curl_socklen_t)ss_size;
852
853 /* leave the rest of the struct filled with zero */
854
855 switch(ai->ai_family) {
856 case AF_INET:
857 addr = (void *)ai->ai_addr; /* storage area for this info */
858 DEBUGASSERT(sizeof(struct in_addr) == sizeof(de->addr[i].ip.v4));
859 memcpy(&addr->sin_addr, &de->addr[i].ip.v4, sizeof(struct in_addr));
860 addr->sin_family = addrtype;
861 addr->sin_port = htons((unsigned short)port);
862 break;
863
864 #ifdef ENABLE_IPV6
865 case AF_INET6:
866 addr6 = (void *)ai->ai_addr; /* storage area for this info */
867 DEBUGASSERT(sizeof(struct in6_addr) == sizeof(de->addr[i].ip.v6));
868 memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr));
869 addr6->sin6_family = addrtype;
870 addr6->sin6_port = htons((unsigned short)port);
871 break;
872 #endif
873 }
874
875 prevai = ai;
876 }
877
878 if(result) {
879 Curl_freeaddrinfo(firstai);
880 firstai = NULL;
881 }
882 *aip = firstai;
883
884 return result;
885 }
886
887 #ifndef CURL_DISABLE_VERBOSE_STRINGS
type2name(DNStype dnstype)888 static const char *type2name(DNStype dnstype)
889 {
890 return (dnstype == DNS_TYPE_A)?"A":"AAAA";
891 }
892 #endif
893
de_cleanup(struct dohentry * d)894 UNITTEST void de_cleanup(struct dohentry *d)
895 {
896 int i = 0;
897 for(i = 0; i < d->numcname; i++) {
898 Curl_dyn_free(&d->cname[i]);
899 }
900 }
901
Curl_doh_is_resolved(struct Curl_easy * data,struct Curl_dns_entry ** dnsp)902 CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
903 struct Curl_dns_entry **dnsp)
904 {
905 CURLcode result;
906 struct dohdata *dohp = data->req.doh;
907 *dnsp = NULL; /* defaults to no response */
908 if(!dohp)
909 return CURLE_OUT_OF_MEMORY;
910
911 if(!dohp->probe[DOH_PROBE_SLOT_IPADDR_V4].easy &&
912 !dohp->probe[DOH_PROBE_SLOT_IPADDR_V6].easy) {
913 failf(data, "Could not DoH-resolve: %s", data->state.async.hostname);
914 return CONN_IS_PROXIED(data->conn)?CURLE_COULDNT_RESOLVE_PROXY:
915 CURLE_COULDNT_RESOLVE_HOST;
916 }
917 else if(!dohp->pending) {
918 DOHcode rc[DOH_PROBE_SLOTS] = {
919 DOH_OK, DOH_OK
920 };
921 struct dohentry de;
922 int slot;
923 /* remove DoH handles from multi handle and close them */
924 for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) {
925 curl_multi_remove_handle(data->multi, dohp->probe[slot].easy);
926 Curl_close(&dohp->probe[slot].easy);
927 }
928 /* parse the responses, create the struct and return it! */
929 de_init(&de);
930 for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) {
931 struct dnsprobe *p = &dohp->probe[slot];
932 if(!p->dnstype)
933 continue;
934 rc[slot] = doh_decode(Curl_dyn_uptr(&p->serverdoh),
935 Curl_dyn_len(&p->serverdoh),
936 p->dnstype,
937 &de);
938 Curl_dyn_free(&p->serverdoh);
939 #ifndef CURL_DISABLE_VERBOSE_STRINGS
940 if(rc[slot]) {
941 infof(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]),
942 type2name(p->dnstype), dohp->host);
943 }
944 #endif
945 } /* next slot */
946
947 result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */
948 if(!rc[DOH_PROBE_SLOT_IPADDR_V4] || !rc[DOH_PROBE_SLOT_IPADDR_V6]) {
949 /* we have an address, of one kind or other */
950 struct Curl_dns_entry *dns;
951 struct Curl_addrinfo *ai;
952
953 infof(data, "DoH Host name: %s", dohp->host);
954 showdoh(data, &de);
955
956 result = doh2ai(&de, dohp->host, dohp->port, &ai);
957 if(result) {
958 de_cleanup(&de);
959 return result;
960 }
961
962 if(data->share)
963 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
964
965 /* we got a response, store it in the cache */
966 dns = Curl_cache_addr(data, ai, dohp->host, 0, dohp->port);
967
968 if(data->share)
969 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
970
971 if(!dns) {
972 /* returned failure, bail out nicely */
973 Curl_freeaddrinfo(ai);
974 }
975 else {
976 data->state.async.dns = dns;
977 *dnsp = dns;
978 result = CURLE_OK; /* address resolution OK */
979 }
980 } /* address processing done */
981
982 /* Now process any build-specific attributes retrieved from DNS */
983
984 /* All done */
985 de_cleanup(&de);
986 Curl_safefree(data->req.doh);
987 return result;
988
989 } /* !dohp->pending */
990
991 /* else wait for pending DoH transactions to complete */
992 return CURLE_OK;
993 }
994
995 #endif /* CURL_DISABLE_DOH */
996