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 #include "escape.h"
46
47 #define DNS_CLASS_IN 0x01
48
49 /* doh_print_buf truncates if the hex string will be more than this */
50 #define LOCAL_PB_HEXMAX 400
51
52 #ifndef CURL_DISABLE_VERBOSE_STRINGS
53 static const char * const errors[]={
54 "",
55 "Bad label",
56 "Out of range",
57 "Label loop",
58 "Too small",
59 "Out of memory",
60 "RDATA length",
61 "Malformat",
62 "Bad RCODE",
63 "Unexpected TYPE",
64 "Unexpected CLASS",
65 "No content",
66 "Bad ID",
67 "Name too long"
68 };
69
doh_strerror(DOHcode code)70 static const char *doh_strerror(DOHcode code)
71 {
72 if((code >= DOH_OK) && (code <= DOH_DNS_NAME_TOO_LONG))
73 return errors[code];
74 return "bad error code";
75 }
76
77 struct curl_trc_feat Curl_doh_trc = {
78 "DoH",
79 CURL_LOG_LVL_NONE,
80 };
81 #endif /* !CURL_DISABLE_VERBOSE_STRINGS */
82
83 /* @unittest 1655
84 */
doh_req_encode(const char * host,DNStype dnstype,unsigned char * dnsp,size_t len,size_t * olen)85 UNITTEST DOHcode doh_req_encode(const char *host,
86 DNStype dnstype,
87 unsigned char *dnsp, /* buffer */
88 size_t len, /* buffer size */
89 size_t *olen) /* output length */
90 {
91 const size_t hostlen = strlen(host);
92 unsigned char *orig = dnsp;
93 const char *hostp = host;
94
95 /* The expected output length is 16 bytes more than the length of
96 * the QNAME-encoding of the hostname.
97 *
98 * A valid DNS name may not contain a zero-length label, except at
99 * the end. For this reason, a name beginning with a dot, or
100 * containing a sequence of two or more consecutive dots, is invalid
101 * and cannot be encoded as a QNAME.
102 *
103 * If the hostname ends with a trailing dot, the corresponding
104 * QNAME-encoding is one byte longer than the hostname. If (as is
105 * also valid) the hostname is shortened by the omission of the
106 * trailing dot, then its QNAME-encoding will be two bytes longer
107 * than the hostname.
108 *
109 * Each [ label, dot ] pair is encoded as [ length, label ],
110 * preserving overall length. A final [ label ] without a dot is
111 * also encoded as [ length, label ], increasing overall length
112 * by one. The encoding is completed by appending a zero byte,
113 * representing the zero-length root label, again increasing
114 * the overall length by one.
115 */
116
117 size_t expected_len;
118 DEBUGASSERT(hostlen);
119 expected_len = 12 + 1 + hostlen + 4;
120 if(host[hostlen-1]!='.')
121 expected_len++;
122
123 if(expected_len > (256 + 16)) /* RFCs 1034, 1035 */
124 return DOH_DNS_NAME_TOO_LONG;
125
126 if(len < expected_len)
127 return DOH_TOO_SMALL_BUFFER;
128
129 *dnsp++ = 0; /* 16 bit id */
130 *dnsp++ = 0;
131 *dnsp++ = 0x01; /* |QR| Opcode |AA|TC|RD| Set the RD bit */
132 *dnsp++ = '\0'; /* |RA| Z | RCODE | */
133 *dnsp++ = '\0';
134 *dnsp++ = 1; /* QDCOUNT (number of entries in the question section) */
135 *dnsp++ = '\0';
136 *dnsp++ = '\0'; /* ANCOUNT */
137 *dnsp++ = '\0';
138 *dnsp++ = '\0'; /* NSCOUNT */
139 *dnsp++ = '\0';
140 *dnsp++ = '\0'; /* ARCOUNT */
141
142 /* encode each label and store it in the QNAME */
143 while(*hostp) {
144 size_t labellen;
145 char *dot = strchr(hostp, '.');
146 if(dot)
147 labellen = dot - hostp;
148 else
149 labellen = strlen(hostp);
150 if((labellen > 63) || (!labellen)) {
151 /* label is too long or too short, error out */
152 *olen = 0;
153 return DOH_DNS_BAD_LABEL;
154 }
155 /* label is non-empty, process it */
156 *dnsp++ = (unsigned char)labellen;
157 memcpy(dnsp, hostp, labellen);
158 dnsp += labellen;
159 hostp += labellen;
160 /* advance past dot, but only if there is one */
161 if(dot)
162 hostp++;
163 } /* next label */
164
165 *dnsp++ = 0; /* append zero-length label for root */
166
167 /* There are assigned TYPE codes beyond 255: use range [1..65535] */
168 *dnsp++ = (unsigned char)(255 & (dnstype >> 8)); /* upper 8 bit TYPE */
169 *dnsp++ = (unsigned char)(255 & dnstype); /* lower 8 bit TYPE */
170
171 *dnsp++ = '\0'; /* upper 8 bit CLASS */
172 *dnsp++ = DNS_CLASS_IN; /* IN - "the Internet" */
173
174 *olen = dnsp - orig;
175
176 /* verify that our estimation of length is valid, since
177 * this has led to buffer overflows in this function */
178 DEBUGASSERT(*olen == expected_len);
179 return DOH_OK;
180 }
181
182 static size_t
doh_write_cb(const void * contents,size_t size,size_t nmemb,void * userp)183 doh_write_cb(const void *contents, size_t size, size_t nmemb, void *userp)
184 {
185 size_t realsize = size * nmemb;
186 struct dynbuf *mem = (struct dynbuf *)userp;
187
188 if(Curl_dyn_addn(mem, contents, realsize))
189 return 0;
190
191 return realsize;
192 }
193
194 #if defined(USE_HTTPSRR) && defined(DEBUGBUILD)
doh_print_buf(struct Curl_easy * data,const char * prefix,unsigned char * buf,size_t len)195 static void doh_print_buf(struct Curl_easy *data,
196 const char *prefix,
197 unsigned char *buf, size_t len)
198 {
199 unsigned char hexstr[LOCAL_PB_HEXMAX];
200 size_t hlen = LOCAL_PB_HEXMAX;
201 bool truncated = false;
202
203 if(len > (LOCAL_PB_HEXMAX / 2))
204 truncated = true;
205 Curl_hexencode(buf, len, hexstr, hlen);
206 if(!truncated)
207 infof(data, "%s: len=%d, val=%s", prefix, (int)len, hexstr);
208 else
209 infof(data, "%s: len=%d (truncated)val=%s", prefix, (int)len, hexstr);
210 return;
211 }
212 #endif
213
214 /* called from multi.c when this DoH transfer is complete */
doh_done(struct Curl_easy * doh,CURLcode result)215 static int doh_done(struct Curl_easy *doh, CURLcode result)
216 {
217 struct Curl_easy *data; /* the transfer that asked for the DoH probe */
218
219 data = Curl_multi_get_handle(doh->multi, doh->set.dohfor_mid);
220 if(!data) {
221 DEBUGF(infof(doh, "doh_done: xfer for mid=%" FMT_OFF_T
222 " not found", doh->set.dohfor_mid));
223 DEBUGASSERT(0);
224 }
225 else {
226 struct doh_probes *dohp = data->req.doh;
227 /* one of the DoH request done for the 'data' transfer is now complete! */
228 dohp->pending--;
229 infof(doh, "a DoH request is completed, %u to go", dohp->pending);
230 if(result)
231 infof(doh, "DoH request %s", curl_easy_strerror(result));
232
233 if(!dohp->pending) {
234 /* DoH completed, run the transfer picking up the results */
235 Curl_expire(data, 0, EXPIRE_RUN_NOW);
236 }
237 }
238 return 0;
239 }
240
241 #define ERROR_CHECK_SETOPT(x,y) \
242 do { \
243 result = curl_easy_setopt(doh, x, y); \
244 if(result && \
245 result != CURLE_NOT_BUILT_IN && \
246 result != CURLE_UNKNOWN_OPTION) \
247 goto error; \
248 } while(0)
249
doh_run_probe(struct Curl_easy * data,struct doh_probe * p,DNStype dnstype,const char * host,const char * url,CURLM * multi,struct curl_slist * headers)250 static CURLcode doh_run_probe(struct Curl_easy *data,
251 struct doh_probe *p, DNStype dnstype,
252 const char *host,
253 const char *url, CURLM *multi,
254 struct curl_slist *headers)
255 {
256 struct Curl_easy *doh = NULL;
257 CURLcode result = CURLE_OK;
258 timediff_t timeout_ms;
259 DOHcode d = doh_req_encode(host, dnstype, p->req_body, sizeof(p->req_body),
260 &p->req_body_len);
261 if(d) {
262 failf(data, "Failed to encode DoH packet [%d]", d);
263 return CURLE_OUT_OF_MEMORY;
264 }
265
266 p->dnstype = dnstype;
267 Curl_dyn_init(&p->resp_body, DYN_DOH_RESPONSE);
268
269 timeout_ms = Curl_timeleft(data, NULL, TRUE);
270 if(timeout_ms <= 0) {
271 result = CURLE_OPERATION_TIMEDOUT;
272 goto error;
273 }
274 /* Curl_open() is the internal version of curl_easy_init() */
275 result = Curl_open(&doh);
276 if(result)
277 goto error;
278
279 /* pass in the struct pointer via a local variable to please coverity and
280 the gcc typecheck helpers */
281 doh->state.internal = true;
282 #ifndef CURL_DISABLE_VERBOSE_STRINGS
283 doh->state.feat = &Curl_doh_trc;
284 #endif
285 ERROR_CHECK_SETOPT(CURLOPT_URL, url);
286 ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https");
287 ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_write_cb);
288 ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, &p->resp_body);
289 ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, p->req_body);
290 ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)p->req_body_len);
291 ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, headers);
292 #ifdef USE_HTTP2
293 ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
294 ERROR_CHECK_SETOPT(CURLOPT_PIPEWAIT, 1L);
295 #endif
296 #ifndef DEBUGBUILD
297 /* enforce HTTPS if not debug */
298 ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
299 #else
300 /* in debug mode, also allow http */
301 ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS);
302 #endif
303 ERROR_CHECK_SETOPT(CURLOPT_TIMEOUT_MS, (long)timeout_ms);
304 ERROR_CHECK_SETOPT(CURLOPT_SHARE, data->share);
305 if(data->set.err && data->set.err != stderr)
306 ERROR_CHECK_SETOPT(CURLOPT_STDERR, data->set.err);
307 if(Curl_trc_ft_is_verbose(data, &Curl_doh_trc))
308 ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L);
309 if(data->set.no_signal)
310 ERROR_CHECK_SETOPT(CURLOPT_NOSIGNAL, 1L);
311
312 ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYHOST,
313 data->set.doh_verifyhost ? 2L : 0L);
314 ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYPEER,
315 data->set.doh_verifypeer ? 1L : 0L);
316 ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYSTATUS,
317 data->set.doh_verifystatus ? 1L : 0L);
318
319 /* Inherit *some* SSL options from the user's transfer. This is a
320 best-guess as to which options are needed for compatibility. #3661
321
322 Note DoH does not inherit the user's proxy server so proxy SSL settings
323 have no effect and are not inherited. If that changes then two new
324 options should be added to check doh proxy insecure separately,
325 CURLOPT_DOH_PROXY_SSL_VERIFYHOST and CURLOPT_DOH_PROXY_SSL_VERIFYPEER.
326 */
327 if(data->set.ssl.falsestart)
328 ERROR_CHECK_SETOPT(CURLOPT_SSL_FALSESTART, 1L);
329 if(data->set.str[STRING_SSL_CAFILE]) {
330 ERROR_CHECK_SETOPT(CURLOPT_CAINFO,
331 data->set.str[STRING_SSL_CAFILE]);
332 }
333 if(data->set.blobs[BLOB_CAINFO]) {
334 ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB,
335 data->set.blobs[BLOB_CAINFO]);
336 }
337 if(data->set.str[STRING_SSL_CAPATH]) {
338 ERROR_CHECK_SETOPT(CURLOPT_CAPATH,
339 data->set.str[STRING_SSL_CAPATH]);
340 }
341 if(data->set.str[STRING_SSL_CRLFILE]) {
342 ERROR_CHECK_SETOPT(CURLOPT_CRLFILE,
343 data->set.str[STRING_SSL_CRLFILE]);
344 }
345 if(data->set.ssl.certinfo)
346 ERROR_CHECK_SETOPT(CURLOPT_CERTINFO, 1L);
347 if(data->set.ssl.fsslctx)
348 ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl.fsslctx);
349 if(data->set.ssl.fsslctxp)
350 ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl.fsslctxp);
351 if(data->set.fdebug)
352 ERROR_CHECK_SETOPT(CURLOPT_DEBUGFUNCTION, data->set.fdebug);
353 if(data->set.debugdata)
354 ERROR_CHECK_SETOPT(CURLOPT_DEBUGDATA, data->set.debugdata);
355 if(data->set.str[STRING_SSL_EC_CURVES]) {
356 ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES,
357 data->set.str[STRING_SSL_EC_CURVES]);
358 }
359
360 {
361 long mask =
362 (data->set.ssl.enable_beast ?
363 CURLSSLOPT_ALLOW_BEAST : 0) |
364 (data->set.ssl.no_revoke ?
365 CURLSSLOPT_NO_REVOKE : 0) |
366 (data->set.ssl.no_partialchain ?
367 CURLSSLOPT_NO_PARTIALCHAIN : 0) |
368 (data->set.ssl.revoke_best_effort ?
369 CURLSSLOPT_REVOKE_BEST_EFFORT : 0) |
370 (data->set.ssl.native_ca_store ?
371 CURLSSLOPT_NATIVE_CA : 0) |
372 (data->set.ssl.auto_client_cert ?
373 CURLSSLOPT_AUTO_CLIENT_CERT : 0);
374
375 (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, mask);
376 }
377
378 doh->set.fmultidone = doh_done;
379 doh->set.dohfor_mid = data->mid; /* for which transfer this is done */
380
381 /* DoH handles must not inherit private_data. The handles may be passed to
382 the user via callbacks and the user will be able to identify them as
383 internal handles because private data is not set. The user can then set
384 private_data via CURLOPT_PRIVATE if they so choose. */
385 DEBUGASSERT(!doh->set.private_data);
386
387 if(curl_multi_add_handle(multi, doh))
388 goto error;
389
390 p->easy_mid = doh->mid;
391 return CURLE_OK;
392
393 error:
394 Curl_close(&doh);
395 p->easy_mid = -1;
396 return result;
397 }
398
399 /*
400 * Curl_doh() resolves a name using DoH. It resolves a name and returns a
401 * 'Curl_addrinfo *' with the address information.
402 */
403
Curl_doh(struct Curl_easy * data,const char * hostname,int port,int * waitp)404 struct Curl_addrinfo *Curl_doh(struct Curl_easy *data,
405 const char *hostname,
406 int port,
407 int *waitp)
408 {
409 CURLcode result = CURLE_OK;
410 struct doh_probes *dohp;
411 struct connectdata *conn = data->conn;
412 size_t i;
413 #ifdef USE_HTTPSRR
414 /* for now, this is only used when ECH is enabled */
415 # ifdef USE_ECH
416 char *qname = NULL;
417 # endif
418 #endif
419 *waitp = FALSE;
420 (void)hostname;
421 (void)port;
422
423 DEBUGASSERT(!data->req.doh);
424 DEBUGASSERT(conn);
425
426 /* start clean, consider allocating this struct on demand */
427 dohp = data->req.doh = calloc(1, sizeof(struct doh_probes));
428 if(!dohp)
429 return NULL;
430
431 for(i = 0; i < DOH_SLOT_COUNT; ++i) {
432 dohp->probe[i].easy_mid = -1;
433 }
434
435 conn->bits.doh = TRUE;
436 dohp->host = hostname;
437 dohp->port = port;
438 dohp->req_hds =
439 curl_slist_append(NULL,
440 "Content-Type: application/dns-message");
441 if(!dohp->req_hds)
442 goto error;
443
444 /* create IPv4 DoH request */
445 result = doh_run_probe(data, &dohp->probe[DOH_SLOT_IPV4],
446 DNS_TYPE_A, hostname, data->set.str[STRING_DOH],
447 data->multi, dohp->req_hds);
448 if(result)
449 goto error;
450 dohp->pending++;
451
452 #ifdef USE_IPV6
453 if((conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
454 /* create IPv6 DoH request */
455 result = doh_run_probe(data, &dohp->probe[DOH_SLOT_IPV6],
456 DNS_TYPE_AAAA, hostname, data->set.str[STRING_DOH],
457 data->multi, dohp->req_hds);
458 if(result)
459 goto error;
460 dohp->pending++;
461 }
462 #endif
463
464 #ifdef USE_HTTPSRR
465 /*
466 * TODO: Figure out the conditions under which we want to make
467 * a request for an HTTPS RR when we are not doing ECH. For now,
468 * making this request breaks a bunch of DoH tests, e.g. test2100,
469 * where the additional request does not match the pre-cooked data
470 * files, so there is a bit of work attached to making the request
471 * in a non-ECH use-case. For the present, we will only make the
472 * request when ECH is enabled in the build and is being used for
473 * the curl operation.
474 */
475 # ifdef USE_ECH
476 if(data->set.tls_ech & CURLECH_ENABLE
477 || data->set.tls_ech & CURLECH_HARD) {
478 if(port == 443)
479 qname = strdup(hostname);
480 else
481 qname = aprintf("_%d._https.%s", port, hostname);
482 if(!qname)
483 goto error;
484 result = doh_run_probe(data, &dohp->probe[DOH_SLOT_HTTPS_RR],
485 DNS_TYPE_HTTPS, qname, data->set.str[STRING_DOH],
486 data->multi, dohp->req_hds);
487 Curl_safefree(qname);
488 if(result)
489 goto error;
490 dohp->pending++;
491 }
492 # endif
493 #endif
494 *waitp = TRUE; /* this never returns synchronously */
495 return NULL;
496
497 error:
498 Curl_doh_cleanup(data);
499 return NULL;
500 }
501
doh_skipqname(const unsigned char * doh,size_t dohlen,unsigned int * indexp)502 static DOHcode doh_skipqname(const unsigned char *doh, size_t dohlen,
503 unsigned int *indexp)
504 {
505 unsigned char length;
506 do {
507 if(dohlen < (*indexp + 1))
508 return DOH_DNS_OUT_OF_RANGE;
509 length = doh[*indexp];
510 if((length & 0xc0) == 0xc0) {
511 /* name pointer, advance over it and be done */
512 if(dohlen < (*indexp + 2))
513 return DOH_DNS_OUT_OF_RANGE;
514 *indexp += 2;
515 break;
516 }
517 if(length & 0xc0)
518 return DOH_DNS_BAD_LABEL;
519 if(dohlen < (*indexp + 1 + length))
520 return DOH_DNS_OUT_OF_RANGE;
521 *indexp += (unsigned int)(1 + length);
522 } while(length);
523 return DOH_OK;
524 }
525
doh_get16bit(const unsigned char * doh,unsigned int index)526 static unsigned short doh_get16bit(const unsigned char *doh,
527 unsigned int index)
528 {
529 return (unsigned short)((doh[index] << 8) | doh[index + 1]);
530 }
531
doh_get32bit(const unsigned char * doh,unsigned int index)532 static unsigned int doh_get32bit(const unsigned char *doh, unsigned int index)
533 {
534 /* make clang and gcc optimize this to bswap by incrementing
535 the pointer first. */
536 doh += index;
537
538 /* avoid undefined behavior by casting to unsigned before shifting
539 24 bits, possibly into the sign bit. codegen is same, but
540 ub sanitizer will not be upset */
541 return ((unsigned)doh[0] << 24) | ((unsigned)doh[1] << 16) |
542 ((unsigned)doh[2] << 8) | doh[3];
543 }
544
doh_store_a(const unsigned char * doh,int index,struct dohentry * d)545 static void doh_store_a(const unsigned char *doh, int index,
546 struct dohentry *d)
547 {
548 /* silently ignore addresses over the limit */
549 if(d->numaddr < DOH_MAX_ADDR) {
550 struct dohaddr *a = &d->addr[d->numaddr];
551 a->type = DNS_TYPE_A;
552 memcpy(&a->ip.v4, &doh[index], 4);
553 d->numaddr++;
554 }
555 }
556
doh_store_aaaa(const unsigned char * doh,int index,struct dohentry * d)557 static void doh_store_aaaa(const unsigned char *doh, int index,
558 struct dohentry *d)
559 {
560 /* silently ignore addresses over the limit */
561 if(d->numaddr < DOH_MAX_ADDR) {
562 struct dohaddr *a = &d->addr[d->numaddr];
563 a->type = DNS_TYPE_AAAA;
564 memcpy(&a->ip.v6, &doh[index], 16);
565 d->numaddr++;
566 }
567 }
568
569 #ifdef USE_HTTPSRR
doh_store_https(const unsigned char * doh,int index,struct dohentry * d,uint16_t len)570 static DOHcode doh_store_https(const unsigned char *doh, int index,
571 struct dohentry *d, uint16_t len)
572 {
573 /* silently ignore RRs over the limit */
574 if(d->numhttps_rrs < DOH_MAX_HTTPS) {
575 struct dohhttps_rr *h = &d->https_rrs[d->numhttps_rrs];
576 h->val = Curl_memdup(&doh[index], len);
577 if(!h->val)
578 return DOH_OUT_OF_MEM;
579 h->len = len;
580 d->numhttps_rrs++;
581 }
582 return DOH_OK;
583 }
584 #endif
585
doh_store_cname(const unsigned char * doh,size_t dohlen,unsigned int index,struct dohentry * d)586 static DOHcode doh_store_cname(const unsigned char *doh, size_t dohlen,
587 unsigned int index, struct dohentry *d)
588 {
589 struct dynbuf *c;
590 unsigned int loop = 128; /* a valid DNS name can never loop this much */
591 unsigned char length;
592
593 if(d->numcname == DOH_MAX_CNAME)
594 return DOH_OK; /* skip! */
595
596 c = &d->cname[d->numcname++];
597 do {
598 if(index >= dohlen)
599 return DOH_DNS_OUT_OF_RANGE;
600 length = doh[index];
601 if((length & 0xc0) == 0xc0) {
602 int newpos;
603 /* name pointer, get the new offset (14 bits) */
604 if((index + 1) >= dohlen)
605 return DOH_DNS_OUT_OF_RANGE;
606
607 /* move to the new index */
608 newpos = (length & 0x3f) << 8 | doh[index + 1];
609 index = (unsigned int)newpos;
610 continue;
611 }
612 else if(length & 0xc0)
613 return DOH_DNS_BAD_LABEL; /* bad input */
614 else
615 index++;
616
617 if(length) {
618 if(Curl_dyn_len(c)) {
619 if(Curl_dyn_addn(c, STRCONST(".")))
620 return DOH_OUT_OF_MEM;
621 }
622 if((index + length) > dohlen)
623 return DOH_DNS_BAD_LABEL;
624
625 if(Curl_dyn_addn(c, &doh[index], length))
626 return DOH_OUT_OF_MEM;
627 index += length;
628 }
629 } while(length && --loop);
630
631 if(!loop)
632 return DOH_DNS_LABEL_LOOP;
633 return DOH_OK;
634 }
635
doh_rdata(const unsigned char * doh,size_t dohlen,unsigned short rdlength,unsigned short type,int index,struct dohentry * d)636 static DOHcode doh_rdata(const unsigned char *doh,
637 size_t dohlen,
638 unsigned short rdlength,
639 unsigned short type,
640 int index,
641 struct dohentry *d)
642 {
643 /* RDATA
644 - A (TYPE 1): 4 bytes
645 - AAAA (TYPE 28): 16 bytes
646 - NS (TYPE 2): N bytes
647 - HTTPS (TYPE 65): N bytes */
648 DOHcode rc;
649
650 switch(type) {
651 case DNS_TYPE_A:
652 if(rdlength != 4)
653 return DOH_DNS_RDATA_LEN;
654 doh_store_a(doh, index, d);
655 break;
656 case DNS_TYPE_AAAA:
657 if(rdlength != 16)
658 return DOH_DNS_RDATA_LEN;
659 doh_store_aaaa(doh, index, d);
660 break;
661 #ifdef USE_HTTPSRR
662 case DNS_TYPE_HTTPS:
663 rc = doh_store_https(doh, index, d, rdlength);
664 if(rc)
665 return rc;
666 break;
667 #endif
668 case DNS_TYPE_CNAME:
669 rc = doh_store_cname(doh, dohlen, (unsigned int)index, d);
670 if(rc)
671 return rc;
672 break;
673 case DNS_TYPE_DNAME:
674 /* explicit for clarity; just skip; rely on synthesized CNAME */
675 break;
676 default:
677 /* unsupported type, just skip it */
678 break;
679 }
680 return DOH_OK;
681 }
682
de_init(struct dohentry * de)683 UNITTEST void de_init(struct dohentry *de)
684 {
685 int i;
686 memset(de, 0, sizeof(*de));
687 de->ttl = INT_MAX;
688 for(i = 0; i < DOH_MAX_CNAME; i++)
689 Curl_dyn_init(&de->cname[i], DYN_DOH_CNAME);
690 }
691
692
doh_resp_decode(const unsigned char * doh,size_t dohlen,DNStype dnstype,struct dohentry * d)693 UNITTEST DOHcode doh_resp_decode(const unsigned char *doh,
694 size_t dohlen,
695 DNStype dnstype,
696 struct dohentry *d)
697 {
698 unsigned char rcode;
699 unsigned short qdcount;
700 unsigned short ancount;
701 unsigned short type = 0;
702 unsigned short rdlength;
703 unsigned short nscount;
704 unsigned short arcount;
705 unsigned int index = 12;
706 DOHcode rc;
707
708 if(dohlen < 12)
709 return DOH_TOO_SMALL_BUFFER; /* too small */
710 if(!doh || doh[0] || doh[1])
711 return DOH_DNS_BAD_ID; /* bad ID */
712 rcode = doh[3] & 0x0f;
713 if(rcode)
714 return DOH_DNS_BAD_RCODE; /* bad rcode */
715
716 qdcount = doh_get16bit(doh, 4);
717 while(qdcount) {
718 rc = doh_skipqname(doh, dohlen, &index);
719 if(rc)
720 return rc; /* bad qname */
721 if(dohlen < (index + 4))
722 return DOH_DNS_OUT_OF_RANGE;
723 index += 4; /* skip question's type and class */
724 qdcount--;
725 }
726
727 ancount = doh_get16bit(doh, 6);
728 while(ancount) {
729 unsigned short class;
730 unsigned int ttl;
731
732 rc = doh_skipqname(doh, dohlen, &index);
733 if(rc)
734 return rc; /* bad qname */
735
736 if(dohlen < (index + 2))
737 return DOH_DNS_OUT_OF_RANGE;
738
739 type = doh_get16bit(doh, index);
740 if((type != DNS_TYPE_CNAME) /* may be synthesized from DNAME */
741 && (type != DNS_TYPE_DNAME) /* if present, accept and ignore */
742 && (type != dnstype))
743 /* Not the same type as was asked for nor CNAME nor DNAME */
744 return DOH_DNS_UNEXPECTED_TYPE;
745 index += 2;
746
747 if(dohlen < (index + 2))
748 return DOH_DNS_OUT_OF_RANGE;
749 class = doh_get16bit(doh, index);
750 if(DNS_CLASS_IN != class)
751 return DOH_DNS_UNEXPECTED_CLASS; /* unsupported */
752 index += 2;
753
754 if(dohlen < (index + 4))
755 return DOH_DNS_OUT_OF_RANGE;
756
757 ttl = doh_get32bit(doh, index);
758 if(ttl < d->ttl)
759 d->ttl = ttl;
760 index += 4;
761
762 if(dohlen < (index + 2))
763 return DOH_DNS_OUT_OF_RANGE;
764
765 rdlength = doh_get16bit(doh, index);
766 index += 2;
767 if(dohlen < (index + rdlength))
768 return DOH_DNS_OUT_OF_RANGE;
769
770 rc = doh_rdata(doh, dohlen, rdlength, type, (int)index, d);
771 if(rc)
772 return rc; /* bad doh_rdata */
773 index += rdlength;
774 ancount--;
775 }
776
777 nscount = doh_get16bit(doh, 8);
778 while(nscount) {
779 rc = doh_skipqname(doh, dohlen, &index);
780 if(rc)
781 return rc; /* bad qname */
782
783 if(dohlen < (index + 8))
784 return DOH_DNS_OUT_OF_RANGE;
785
786 index += 2 + 2 + 4; /* type, class and ttl */
787
788 if(dohlen < (index + 2))
789 return DOH_DNS_OUT_OF_RANGE;
790
791 rdlength = doh_get16bit(doh, index);
792 index += 2;
793 if(dohlen < (index + rdlength))
794 return DOH_DNS_OUT_OF_RANGE;
795 index += rdlength;
796 nscount--;
797 }
798
799 arcount = doh_get16bit(doh, 10);
800 while(arcount) {
801 rc = doh_skipqname(doh, dohlen, &index);
802 if(rc)
803 return rc; /* bad qname */
804
805 if(dohlen < (index + 8))
806 return DOH_DNS_OUT_OF_RANGE;
807
808 index += 2 + 2 + 4; /* type, class and ttl */
809
810 if(dohlen < (index + 2))
811 return DOH_DNS_OUT_OF_RANGE;
812
813 rdlength = doh_get16bit(doh, index);
814 index += 2;
815 if(dohlen < (index + rdlength))
816 return DOH_DNS_OUT_OF_RANGE;
817 index += rdlength;
818 arcount--;
819 }
820
821 if(index != dohlen)
822 return DOH_DNS_MALFORMAT; /* something is wrong */
823
824 #ifdef USE_HTTTPS
825 if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr && !d->numhttps_rrs)
826 #else
827 if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr)
828 #endif
829 /* nothing stored! */
830 return DOH_NO_CONTENT;
831
832 return DOH_OK; /* ok */
833 }
834
835 #ifndef CURL_DISABLE_VERBOSE_STRINGS
doh_show(struct Curl_easy * data,const struct dohentry * d)836 static void doh_show(struct Curl_easy *data,
837 const struct dohentry *d)
838 {
839 int i;
840 infof(data, "[DoH] TTL: %u seconds", d->ttl);
841 for(i = 0; i < d->numaddr; i++) {
842 const struct dohaddr *a = &d->addr[i];
843 if(a->type == DNS_TYPE_A) {
844 infof(data, "[DoH] A: %u.%u.%u.%u",
845 a->ip.v4[0], a->ip.v4[1],
846 a->ip.v4[2], a->ip.v4[3]);
847 }
848 else if(a->type == DNS_TYPE_AAAA) {
849 int j;
850 char buffer[128];
851 char *ptr;
852 size_t len;
853 len = msnprintf(buffer, 128, "[DoH] AAAA: ");
854 ptr = &buffer[len];
855 len = sizeof(buffer) - len;
856 for(j = 0; j < 16; j += 2) {
857 size_t l;
858 msnprintf(ptr, len, "%s%02x%02x", j ? ":" : "", d->addr[i].ip.v6[j],
859 d->addr[i].ip.v6[j + 1]);
860 l = strlen(ptr);
861 len -= l;
862 ptr += l;
863 }
864 infof(data, "%s", buffer);
865 }
866 }
867 #ifdef USE_HTTPSRR
868 for(i = 0; i < d->numhttps_rrs; i++) {
869 # ifdef DEBUGBUILD
870 doh_print_buf(data, "DoH HTTPS",
871 d->https_rrs[i].val, d->https_rrs[i].len);
872 # else
873 infof(data, "DoH HTTPS RR: length %d", d->https_rrs[i].len);
874 # endif
875 }
876 #endif
877 for(i = 0; i < d->numcname; i++) {
878 infof(data, "CNAME: %s", Curl_dyn_ptr(&d->cname[i]));
879 }
880 }
881 #else
882 #define doh_show(x,y)
883 #endif
884
885 /*
886 * doh2ai()
887 *
888 * This function returns a pointer to the first element of a newly allocated
889 * Curl_addrinfo struct linked list filled with the data from a set of DoH
890 * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for
891 * a IPv6 stack, but usable also for IPv4, all hosts and environments.
892 *
893 * The memory allocated by this function *MUST* be free'd later on calling
894 * Curl_freeaddrinfo(). For each successful call to this function there
895 * must be an associated call later to Curl_freeaddrinfo().
896 */
897
doh2ai(const struct dohentry * de,const char * hostname,int port,struct Curl_addrinfo ** aip)898 static CURLcode doh2ai(const struct dohentry *de, const char *hostname,
899 int port, struct Curl_addrinfo **aip)
900 {
901 struct Curl_addrinfo *ai;
902 struct Curl_addrinfo *prevai = NULL;
903 struct Curl_addrinfo *firstai = NULL;
904 struct sockaddr_in *addr;
905 #ifdef USE_IPV6
906 struct sockaddr_in6 *addr6;
907 #endif
908 CURLcode result = CURLE_OK;
909 int i;
910 size_t hostlen = strlen(hostname) + 1; /* include null-terminator */
911
912 DEBUGASSERT(de);
913
914 if(!de->numaddr)
915 return CURLE_COULDNT_RESOLVE_HOST;
916
917 for(i = 0; i < de->numaddr; i++) {
918 size_t ss_size;
919 CURL_SA_FAMILY_T addrtype;
920 if(de->addr[i].type == DNS_TYPE_AAAA) {
921 #ifndef USE_IPV6
922 /* we cannot handle IPv6 addresses */
923 continue;
924 #else
925 ss_size = sizeof(struct sockaddr_in6);
926 addrtype = AF_INET6;
927 #endif
928 }
929 else {
930 ss_size = sizeof(struct sockaddr_in);
931 addrtype = AF_INET;
932 }
933
934 ai = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen);
935 if(!ai) {
936 result = CURLE_OUT_OF_MEMORY;
937 break;
938 }
939 ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo));
940 ai->ai_canonname = (void *)((char *)ai->ai_addr + ss_size);
941 memcpy(ai->ai_canonname, hostname, hostlen);
942
943 if(!firstai)
944 /* store the pointer we want to return from this function */
945 firstai = ai;
946
947 if(prevai)
948 /* make the previous entry point to this */
949 prevai->ai_next = ai;
950
951 ai->ai_family = addrtype;
952
953 /* we return all names as STREAM, so when using this address for TFTP
954 the type must be ignored and conn->socktype be used instead! */
955 ai->ai_socktype = SOCK_STREAM;
956
957 ai->ai_addrlen = (curl_socklen_t)ss_size;
958
959 /* leave the rest of the struct filled with zero */
960
961 switch(ai->ai_family) {
962 case AF_INET:
963 addr = (void *)ai->ai_addr; /* storage area for this info */
964 DEBUGASSERT(sizeof(struct in_addr) == sizeof(de->addr[i].ip.v4));
965 memcpy(&addr->sin_addr, &de->addr[i].ip.v4, sizeof(struct in_addr));
966 #ifdef __MINGW32__
967 addr->sin_family = (short)addrtype;
968 #else
969 addr->sin_family = addrtype;
970 #endif
971 addr->sin_port = htons((unsigned short)port);
972 break;
973
974 #ifdef USE_IPV6
975 case AF_INET6:
976 addr6 = (void *)ai->ai_addr; /* storage area for this info */
977 DEBUGASSERT(sizeof(struct in6_addr) == sizeof(de->addr[i].ip.v6));
978 memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr));
979 #ifdef __MINGW32__
980 addr6->sin6_family = (short)addrtype;
981 #else
982 addr6->sin6_family = addrtype;
983 #endif
984 addr6->sin6_port = htons((unsigned short)port);
985 break;
986 #endif
987 }
988
989 prevai = ai;
990 }
991
992 if(result) {
993 Curl_freeaddrinfo(firstai);
994 firstai = NULL;
995 }
996 *aip = firstai;
997
998 return result;
999 }
1000
1001 #ifndef CURL_DISABLE_VERBOSE_STRINGS
doh_type2name(DNStype dnstype)1002 static const char *doh_type2name(DNStype dnstype)
1003 {
1004 switch(dnstype) {
1005 case DNS_TYPE_A:
1006 return "A";
1007 case DNS_TYPE_AAAA:
1008 return "AAAA";
1009 #ifdef USE_HTTPSRR
1010 case DNS_TYPE_HTTPS:
1011 return "HTTPS";
1012 #endif
1013 default:
1014 return "unknown";
1015 }
1016 }
1017 #endif
1018
de_cleanup(struct dohentry * d)1019 UNITTEST void de_cleanup(struct dohentry *d)
1020 {
1021 int i = 0;
1022 for(i = 0; i < d->numcname; i++) {
1023 Curl_dyn_free(&d->cname[i]);
1024 }
1025 #ifdef USE_HTTPSRR
1026 for(i = 0; i < d->numhttps_rrs; i++)
1027 Curl_safefree(d->https_rrs[i].val);
1028 #endif
1029 }
1030
1031 #ifdef USE_HTTPSRR
1032
1033 /*
1034 * @brief decode the DNS name in a binary RRData
1035 * @param buf points to the buffer (in/out)
1036 * @param remaining points to the remaining buffer length (in/out)
1037 * @param dnsname returns the string form name on success
1038 * @return is 1 for success, error otherwise
1039 *
1040 * The encoding here is defined in
1041 * https://tools.ietf.org/html/rfc1035#section-3.1
1042 *
1043 * The input buffer pointer will be modified so it points to
1044 * just after the end of the DNS name encoding on output. (And
1045 * that is why it is an "unsigned char **" :-)
1046 */
doh_decode_rdata_name(unsigned char ** buf,size_t * remaining,char ** dnsname)1047 static CURLcode doh_decode_rdata_name(unsigned char **buf, size_t *remaining,
1048 char **dnsname)
1049 {
1050 unsigned char *cp = NULL;
1051 int rem = 0;
1052 unsigned char clen = 0; /* chunk len */
1053 struct dynbuf thename;
1054
1055 DEBUGASSERT(buf && remaining && dnsname);
1056 if(!buf || !remaining || !dnsname)
1057 return CURLE_OUT_OF_MEMORY;
1058 rem = (int)*remaining;
1059 if(rem <= 0) {
1060 Curl_dyn_free(&thename);
1061 return CURLE_OUT_OF_MEMORY;
1062 }
1063 Curl_dyn_init(&thename, CURL_MAXLEN_host_name);
1064 cp = *buf;
1065 clen = *cp++;
1066 if(clen == 0) {
1067 /* special case - return "." as name */
1068 if(Curl_dyn_addn(&thename, ".", 1))
1069 return CURLE_OUT_OF_MEMORY;
1070 }
1071 while(clen) {
1072 if(clen >= rem) {
1073 Curl_dyn_free(&thename);
1074 return CURLE_OUT_OF_MEMORY;
1075 }
1076 if(Curl_dyn_addn(&thename, cp, clen) ||
1077 Curl_dyn_addn(&thename, ".", 1))
1078 return CURLE_TOO_LARGE;
1079
1080 cp += clen;
1081 rem -= (clen + 1);
1082 if(rem <= 0) {
1083 Curl_dyn_free(&thename);
1084 return CURLE_OUT_OF_MEMORY;
1085 }
1086 clen = *cp++;
1087 }
1088 *buf = cp;
1089 *remaining = rem - 1;
1090 *dnsname = Curl_dyn_ptr(&thename);
1091 return CURLE_OK;
1092 }
1093
doh_decode_rdata_alpn(unsigned char * rrval,size_t len,char ** alpns)1094 static CURLcode doh_decode_rdata_alpn(unsigned char *rrval, size_t len,
1095 char **alpns)
1096 {
1097 /*
1098 * spec here is as per draft-ietf-dnsop-svcb-https, section-7.1.1
1099 * encoding is catenated list of strings each preceded by a one
1100 * octet length
1101 * output is comma-sep list of the strings
1102 * implementations may or may not handle quoting of comma within
1103 * string values, so we might see a comma within the wire format
1104 * version of a string, in which case we will precede that by a
1105 * backslash - same goes for a backslash character, and of course
1106 * we need to use two backslashes in strings when we mean one;-)
1107 */
1108 int remaining = (int) len;
1109 char *oval;
1110 size_t i;
1111 unsigned char *cp = rrval;
1112 struct dynbuf dval;
1113
1114 if(!alpns)
1115 return CURLE_OUT_OF_MEMORY;
1116 Curl_dyn_init(&dval, DYN_DOH_RESPONSE);
1117 remaining = (int)len;
1118 cp = rrval;
1119 while(remaining > 0) {
1120 size_t tlen = (size_t) *cp++;
1121
1122 /* if not 1st time, add comma */
1123 if(remaining != (int)len && Curl_dyn_addn(&dval, ",", 1))
1124 goto err;
1125 remaining--;
1126 if(tlen > (size_t)remaining)
1127 goto err;
1128 /* add escape char if needed, clunky but easier to read */
1129 for(i = 0; i != tlen; i++) {
1130 if('\\' == *cp || ',' == *cp) {
1131 if(Curl_dyn_addn(&dval, "\\", 1))
1132 goto err;
1133 }
1134 if(Curl_dyn_addn(&dval, cp++, 1))
1135 goto err;
1136 }
1137 remaining -= (int)tlen;
1138 }
1139 /* this string is always null terminated */
1140 oval = Curl_dyn_ptr(&dval);
1141 if(!oval)
1142 goto err;
1143 *alpns = oval;
1144 return CURLE_OK;
1145 err:
1146 Curl_dyn_free(&dval);
1147 return CURLE_BAD_CONTENT_ENCODING;
1148 }
1149
1150 #ifdef DEBUGBUILD
doh_test_alpn_escapes(void)1151 static CURLcode doh_test_alpn_escapes(void)
1152 {
1153 /* we will use an example from draft-ietf-dnsop-svcb, figure 10 */
1154 static unsigned char example[] = {
1155 0x08, /* length 8 */
1156 0x66, 0x5c, 0x6f, 0x6f, 0x2c, 0x62, 0x61, 0x72, /* value "f\\oo,bar" */
1157 0x02, /* length 2 */
1158 0x68, 0x32 /* value "h2" */
1159 };
1160 size_t example_len = sizeof(example);
1161 char *aval = NULL;
1162 static const char *expected = "f\\\\oo\\,bar,h2";
1163
1164 if(doh_decode_rdata_alpn(example, example_len, &aval) != CURLE_OK)
1165 return CURLE_BAD_CONTENT_ENCODING;
1166 if(strlen(aval) != strlen(expected))
1167 return CURLE_BAD_CONTENT_ENCODING;
1168 if(memcmp(aval, expected, strlen(aval)))
1169 return CURLE_BAD_CONTENT_ENCODING;
1170 return CURLE_OK;
1171 }
1172 #endif
1173
doh_resp_decode_httpsrr(unsigned char * rrval,size_t len,struct Curl_https_rrinfo ** hrr)1174 static CURLcode doh_resp_decode_httpsrr(unsigned char *rrval, size_t len,
1175 struct Curl_https_rrinfo **hrr)
1176 {
1177 size_t remaining = len;
1178 unsigned char *cp = rrval;
1179 uint16_t pcode = 0, plen = 0;
1180 struct Curl_https_rrinfo *lhrr = NULL;
1181 char *dnsname = NULL;
1182
1183 #ifdef DEBUGBUILD
1184 /* a few tests of escaping, should not be here but ok for now */
1185 if(doh_test_alpn_escapes() != CURLE_OK)
1186 return CURLE_OUT_OF_MEMORY;
1187 #endif
1188 lhrr = calloc(1, sizeof(struct Curl_https_rrinfo));
1189 if(!lhrr)
1190 return CURLE_OUT_OF_MEMORY;
1191 lhrr->val = Curl_memdup(rrval, len);
1192 if(!lhrr->val)
1193 goto err;
1194 lhrr->len = len;
1195 if(remaining <= 2)
1196 goto err;
1197 lhrr->priority = (uint16_t)((cp[0] << 8) + cp[1]);
1198 cp += 2;
1199 remaining -= (uint16_t)2;
1200 if(doh_decode_rdata_name(&cp, &remaining, &dnsname) != CURLE_OK)
1201 goto err;
1202 lhrr->target = dnsname;
1203 while(remaining >= 4) {
1204 pcode = (uint16_t)((*cp << 8) + (*(cp + 1)));
1205 cp += 2;
1206 plen = (uint16_t)((*cp << 8) + (*(cp + 1)));
1207 cp += 2;
1208 remaining -= 4;
1209 if(pcode == HTTPS_RR_CODE_ALPN) {
1210 if(doh_decode_rdata_alpn(cp, plen, &lhrr->alpns) != CURLE_OK)
1211 goto err;
1212 }
1213 if(pcode == HTTPS_RR_CODE_NO_DEF_ALPN)
1214 lhrr->no_def_alpn = TRUE;
1215 else if(pcode == HTTPS_RR_CODE_IPV4) {
1216 if(!plen)
1217 goto err;
1218 lhrr->ipv4hints = Curl_memdup(cp, plen);
1219 if(!lhrr->ipv4hints)
1220 goto err;
1221 lhrr->ipv4hints_len = (size_t)plen;
1222 }
1223 else if(pcode == HTTPS_RR_CODE_ECH) {
1224 if(!plen)
1225 goto err;
1226 lhrr->echconfiglist = Curl_memdup(cp, plen);
1227 if(!lhrr->echconfiglist)
1228 goto err;
1229 lhrr->echconfiglist_len = (size_t)plen;
1230 }
1231 else if(pcode == HTTPS_RR_CODE_IPV6) {
1232 if(!plen)
1233 goto err;
1234 lhrr->ipv6hints = Curl_memdup(cp, plen);
1235 if(!lhrr->ipv6hints)
1236 goto err;
1237 lhrr->ipv6hints_len = (size_t)plen;
1238 }
1239 if(plen > 0 && plen <= remaining) {
1240 cp += plen;
1241 remaining -= plen;
1242 }
1243 }
1244 DEBUGASSERT(!remaining);
1245 *hrr = lhrr;
1246 return CURLE_OK;
1247 err:
1248 if(lhrr) {
1249 Curl_safefree(lhrr->target);
1250 Curl_safefree(lhrr->echconfiglist);
1251 Curl_safefree(lhrr->val);
1252 Curl_safefree(lhrr->alpns);
1253 Curl_safefree(lhrr);
1254 }
1255 return CURLE_OUT_OF_MEMORY;
1256 }
1257
1258 # ifdef DEBUGBUILD
doh_print_httpsrr(struct Curl_easy * data,struct Curl_https_rrinfo * hrr)1259 static void doh_print_httpsrr(struct Curl_easy *data,
1260 struct Curl_https_rrinfo *hrr)
1261 {
1262 DEBUGASSERT(hrr);
1263 infof(data, "HTTPS RR: priority %d, target: %s",
1264 hrr->priority, hrr->target);
1265 if(hrr->alpns)
1266 infof(data, "HTTPS RR: alpns %s", hrr->alpns);
1267 else
1268 infof(data, "HTTPS RR: no alpns");
1269 if(hrr->no_def_alpn)
1270 infof(data, "HTTPS RR: no_def_alpn set");
1271 else
1272 infof(data, "HTTPS RR: no_def_alpn not set");
1273 if(hrr->ipv4hints) {
1274 doh_print_buf(data, "HTTPS RR: ipv4hints",
1275 hrr->ipv4hints, hrr->ipv4hints_len);
1276 }
1277 else
1278 infof(data, "HTTPS RR: no ipv4hints");
1279 if(hrr->echconfiglist) {
1280 doh_print_buf(data, "HTTPS RR: ECHConfigList",
1281 hrr->echconfiglist, hrr->echconfiglist_len);
1282 }
1283 else
1284 infof(data, "HTTPS RR: no ECHConfigList");
1285 if(hrr->ipv6hints) {
1286 doh_print_buf(data, "HTTPS RR: ipv6hint",
1287 hrr->ipv6hints, hrr->ipv6hints_len);
1288 }
1289 else
1290 infof(data, "HTTPS RR: no ipv6hints");
1291 return;
1292 }
1293 # endif
1294 #endif
1295
Curl_doh_is_resolved(struct Curl_easy * data,struct Curl_dns_entry ** dnsp)1296 CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
1297 struct Curl_dns_entry **dnsp)
1298 {
1299 CURLcode result;
1300 struct doh_probes *dohp = data->req.doh;
1301 *dnsp = NULL; /* defaults to no response */
1302 if(!dohp)
1303 return CURLE_OUT_OF_MEMORY;
1304
1305 if(dohp->probe[DOH_SLOT_IPV4].easy_mid < 0 &&
1306 dohp->probe[DOH_SLOT_IPV6].easy_mid < 0) {
1307 failf(data, "Could not DoH-resolve: %s", data->state.async.hostname);
1308 return CONN_IS_PROXIED(data->conn) ? CURLE_COULDNT_RESOLVE_PROXY :
1309 CURLE_COULDNT_RESOLVE_HOST;
1310 }
1311 else if(!dohp->pending) {
1312 DOHcode rc[DOH_SLOT_COUNT];
1313 struct dohentry de;
1314 int slot;
1315
1316 memset(rc, 0, sizeof(rc));
1317 /* remove DoH handles from multi handle and close them */
1318 Curl_doh_close(data);
1319 /* parse the responses, create the struct and return it! */
1320 de_init(&de);
1321 for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
1322 struct doh_probe *p = &dohp->probe[slot];
1323 if(!p->dnstype)
1324 continue;
1325 rc[slot] = doh_resp_decode(Curl_dyn_uptr(&p->resp_body),
1326 Curl_dyn_len(&p->resp_body),
1327 p->dnstype, &de);
1328 Curl_dyn_free(&p->resp_body);
1329 #ifndef CURL_DISABLE_VERBOSE_STRINGS
1330 if(rc[slot]) {
1331 infof(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]),
1332 doh_type2name(p->dnstype), dohp->host);
1333 }
1334 #endif
1335 } /* next slot */
1336
1337 result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */
1338 if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) {
1339 /* we have an address, of one kind or other */
1340 struct Curl_dns_entry *dns;
1341 struct Curl_addrinfo *ai;
1342
1343
1344 if(Curl_trc_ft_is_verbose(data, &Curl_doh_trc)) {
1345 infof(data, "[DoH] hostname: %s", dohp->host);
1346 doh_show(data, &de);
1347 }
1348
1349 result = doh2ai(&de, dohp->host, dohp->port, &ai);
1350 if(result) {
1351 de_cleanup(&de);
1352 return result;
1353 }
1354
1355 if(data->share)
1356 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
1357
1358 /* we got a response, store it in the cache */
1359 dns = Curl_cache_addr(data, ai, dohp->host, 0, dohp->port, FALSE);
1360
1361 if(data->share)
1362 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
1363
1364 if(!dns) {
1365 /* returned failure, bail out nicely */
1366 Curl_freeaddrinfo(ai);
1367 }
1368 else {
1369 data->state.async.dns = dns;
1370 *dnsp = dns;
1371 result = CURLE_OK; /* address resolution OK */
1372 }
1373 } /* address processing done */
1374
1375 /* Now process any build-specific attributes retrieved from DNS */
1376 #ifdef USE_HTTPSRR
1377 if(de.numhttps_rrs > 0 && result == CURLE_OK && *dnsp) {
1378 struct Curl_https_rrinfo *hrr = NULL;
1379 result = doh_resp_decode_httpsrr(de.https_rrs->val, de.https_rrs->len,
1380 &hrr);
1381 if(result) {
1382 infof(data, "Failed to decode HTTPS RR");
1383 return result;
1384 }
1385 infof(data, "Some HTTPS RR to process");
1386 # ifdef DEBUGBUILD
1387 doh_print_httpsrr(data, hrr);
1388 # endif
1389 (*dnsp)->hinfo = hrr;
1390 }
1391 #endif
1392
1393 /* All done */
1394 de_cleanup(&de);
1395 Curl_doh_cleanup(data);
1396 return result;
1397
1398 } /* !dohp->pending */
1399
1400 /* else wait for pending DoH transactions to complete */
1401 return CURLE_OK;
1402 }
1403
Curl_doh_close(struct Curl_easy * data)1404 void Curl_doh_close(struct Curl_easy *data)
1405 {
1406 struct doh_probes *doh = data->req.doh;
1407 if(doh && data->multi) {
1408 struct Curl_easy *probe_data;
1409 curl_off_t mid;
1410 size_t slot;
1411 for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
1412 mid = doh->probe[slot].easy_mid;
1413 if(mid < 0)
1414 continue;
1415 doh->probe[slot].easy_mid = -1;
1416 /* should have been called before data is removed from multi handle */
1417 DEBUGASSERT(data->multi);
1418 probe_data = data->multi ? Curl_multi_get_handle(data->multi, mid) :
1419 NULL;
1420 if(!probe_data) {
1421 DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%"
1422 FMT_OFF_T " not found!",
1423 doh->probe[slot].easy_mid));
1424 continue;
1425 }
1426 /* data->multi might already be reset at this time */
1427 curl_multi_remove_handle(data->multi, probe_data);
1428 Curl_close(&probe_data);
1429 }
1430 }
1431 }
1432
Curl_doh_cleanup(struct Curl_easy * data)1433 void Curl_doh_cleanup(struct Curl_easy *data)
1434 {
1435 struct doh_probes *doh = data->req.doh;
1436 if(doh) {
1437 Curl_doh_close(data);
1438 curl_slist_free_all(doh->req_hds);
1439 data->req.doh->req_hds = NULL;
1440 Curl_safefree(data->req.doh);
1441 }
1442 }
1443
1444 #endif /* CURL_DISABLE_DOH */
1445