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(char * contents,size_t size,size_t nmemb,void * userp)183 doh_write_cb(char *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((CURL *)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, (CURLSH *)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] = "[DoH] AAAA: ";
851 size_t len = strlen(buffer);
852 char *ptr = &buffer[len];
853 len = sizeof(buffer) - len;
854 for(j = 0; j < 16; j += 2) {
855 size_t l;
856 msnprintf(ptr, len, "%s%02x%02x", j ? ":" : "", d->addr[i].ip.v6[j],
857 d->addr[i].ip.v6[j + 1]);
858 l = strlen(ptr);
859 len -= l;
860 ptr += l;
861 }
862 infof(data, "%s", buffer);
863 }
864 }
865 #ifdef USE_HTTPSRR
866 for(i = 0; i < d->numhttps_rrs; i++) {
867 # ifdef DEBUGBUILD
868 doh_print_buf(data, "DoH HTTPS",
869 d->https_rrs[i].val, d->https_rrs[i].len);
870 # else
871 infof(data, "DoH HTTPS RR: length %d", d->https_rrs[i].len);
872 # endif
873 }
874 #endif
875 for(i = 0; i < d->numcname; i++) {
876 infof(data, "CNAME: %s", Curl_dyn_ptr(&d->cname[i]));
877 }
878 }
879 #else
880 #define doh_show(x,y)
881 #endif
882
883 /*
884 * doh2ai()
885 *
886 * This function returns a pointer to the first element of a newly allocated
887 * Curl_addrinfo struct linked list filled with the data from a set of DoH
888 * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for
889 * a IPv6 stack, but usable also for IPv4, all hosts and environments.
890 *
891 * The memory allocated by this function *MUST* be free'd later on calling
892 * Curl_freeaddrinfo(). For each successful call to this function there
893 * must be an associated call later to Curl_freeaddrinfo().
894 */
895
doh2ai(const struct dohentry * de,const char * hostname,int port,struct Curl_addrinfo ** aip)896 static CURLcode doh2ai(const struct dohentry *de, const char *hostname,
897 int port, struct Curl_addrinfo **aip)
898 {
899 struct Curl_addrinfo *ai;
900 struct Curl_addrinfo *prevai = NULL;
901 struct Curl_addrinfo *firstai = NULL;
902 struct sockaddr_in *addr;
903 #ifdef USE_IPV6
904 struct sockaddr_in6 *addr6;
905 #endif
906 CURLcode result = CURLE_OK;
907 int i;
908 size_t hostlen = strlen(hostname) + 1; /* include null-terminator */
909
910 DEBUGASSERT(de);
911
912 if(!de->numaddr)
913 return CURLE_COULDNT_RESOLVE_HOST;
914
915 for(i = 0; i < de->numaddr; i++) {
916 size_t ss_size;
917 CURL_SA_FAMILY_T addrtype;
918 if(de->addr[i].type == DNS_TYPE_AAAA) {
919 #ifndef USE_IPV6
920 /* we cannot handle IPv6 addresses */
921 continue;
922 #else
923 ss_size = sizeof(struct sockaddr_in6);
924 addrtype = AF_INET6;
925 #endif
926 }
927 else {
928 ss_size = sizeof(struct sockaddr_in);
929 addrtype = AF_INET;
930 }
931
932 ai = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen);
933 if(!ai) {
934 result = CURLE_OUT_OF_MEMORY;
935 break;
936 }
937 ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo));
938 ai->ai_canonname = (void *)((char *)ai->ai_addr + ss_size);
939 memcpy(ai->ai_canonname, hostname, hostlen);
940
941 if(!firstai)
942 /* store the pointer we want to return from this function */
943 firstai = ai;
944
945 if(prevai)
946 /* make the previous entry point to this */
947 prevai->ai_next = ai;
948
949 ai->ai_family = addrtype;
950
951 /* we return all names as STREAM, so when using this address for TFTP
952 the type must be ignored and conn->socktype be used instead! */
953 ai->ai_socktype = SOCK_STREAM;
954
955 ai->ai_addrlen = (curl_socklen_t)ss_size;
956
957 /* leave the rest of the struct filled with zero */
958
959 switch(ai->ai_family) {
960 case AF_INET:
961 addr = (void *)ai->ai_addr; /* storage area for this info */
962 DEBUGASSERT(sizeof(struct in_addr) == sizeof(de->addr[i].ip.v4));
963 memcpy(&addr->sin_addr, &de->addr[i].ip.v4, sizeof(struct in_addr));
964 #ifdef __MINGW32__
965 addr->sin_family = (short)addrtype;
966 #else
967 addr->sin_family = addrtype;
968 #endif
969 addr->sin_port = htons((unsigned short)port);
970 break;
971
972 #ifdef USE_IPV6
973 case AF_INET6:
974 addr6 = (void *)ai->ai_addr; /* storage area for this info */
975 DEBUGASSERT(sizeof(struct in6_addr) == sizeof(de->addr[i].ip.v6));
976 memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr));
977 #ifdef __MINGW32__
978 addr6->sin6_family = (short)addrtype;
979 #else
980 addr6->sin6_family = addrtype;
981 #endif
982 addr6->sin6_port = htons((unsigned short)port);
983 break;
984 #endif
985 }
986
987 prevai = ai;
988 }
989
990 if(result) {
991 Curl_freeaddrinfo(firstai);
992 firstai = NULL;
993 }
994 *aip = firstai;
995
996 return result;
997 }
998
999 #ifndef CURL_DISABLE_VERBOSE_STRINGS
doh_type2name(DNStype dnstype)1000 static const char *doh_type2name(DNStype dnstype)
1001 {
1002 switch(dnstype) {
1003 case DNS_TYPE_A:
1004 return "A";
1005 case DNS_TYPE_AAAA:
1006 return "AAAA";
1007 #ifdef USE_HTTPSRR
1008 case DNS_TYPE_HTTPS:
1009 return "HTTPS";
1010 #endif
1011 default:
1012 return "unknown";
1013 }
1014 }
1015 #endif
1016
de_cleanup(struct dohentry * d)1017 UNITTEST void de_cleanup(struct dohentry *d)
1018 {
1019 int i = 0;
1020 for(i = 0; i < d->numcname; i++) {
1021 Curl_dyn_free(&d->cname[i]);
1022 }
1023 #ifdef USE_HTTPSRR
1024 for(i = 0; i < d->numhttps_rrs; i++)
1025 Curl_safefree(d->https_rrs[i].val);
1026 #endif
1027 }
1028
1029 #ifdef USE_HTTPSRR
1030
1031 /*
1032 * @brief decode the DNS name in a binary RRData
1033 * @param buf points to the buffer (in/out)
1034 * @param remaining points to the remaining buffer length (in/out)
1035 * @param dnsname returns the string form name on success
1036 * @return is 1 for success, error otherwise
1037 *
1038 * The encoding here is defined in
1039 * https://tools.ietf.org/html/rfc1035#section-3.1
1040 *
1041 * The input buffer pointer will be modified so it points to
1042 * just after the end of the DNS name encoding on output. (And
1043 * that is why it is an "unsigned char **" :-)
1044 */
doh_decode_rdata_name(unsigned char ** buf,size_t * remaining,char ** dnsname)1045 static CURLcode doh_decode_rdata_name(unsigned char **buf, size_t *remaining,
1046 char **dnsname)
1047 {
1048 unsigned char *cp = NULL;
1049 int rem = 0;
1050 unsigned char clen = 0; /* chunk len */
1051 struct dynbuf thename;
1052
1053 DEBUGASSERT(buf && remaining && dnsname);
1054 if(!buf || !remaining || !dnsname)
1055 return CURLE_OUT_OF_MEMORY;
1056 rem = (int)*remaining;
1057 if(rem <= 0) {
1058 Curl_dyn_free(&thename);
1059 return CURLE_OUT_OF_MEMORY;
1060 }
1061 Curl_dyn_init(&thename, CURL_MAXLEN_host_name);
1062 cp = *buf;
1063 clen = *cp++;
1064 if(clen == 0) {
1065 /* special case - return "." as name */
1066 if(Curl_dyn_addn(&thename, ".", 1))
1067 return CURLE_OUT_OF_MEMORY;
1068 }
1069 while(clen) {
1070 if(clen >= rem) {
1071 Curl_dyn_free(&thename);
1072 return CURLE_OUT_OF_MEMORY;
1073 }
1074 if(Curl_dyn_addn(&thename, cp, clen) ||
1075 Curl_dyn_addn(&thename, ".", 1))
1076 return CURLE_TOO_LARGE;
1077
1078 cp += clen;
1079 rem -= (clen + 1);
1080 if(rem <= 0) {
1081 Curl_dyn_free(&thename);
1082 return CURLE_OUT_OF_MEMORY;
1083 }
1084 clen = *cp++;
1085 }
1086 *buf = cp;
1087 *remaining = rem - 1;
1088 *dnsname = Curl_dyn_ptr(&thename);
1089 return CURLE_OK;
1090 }
1091
doh_decode_rdata_alpn(unsigned char * rrval,size_t len,char ** alpns)1092 static CURLcode doh_decode_rdata_alpn(unsigned char *rrval, size_t len,
1093 char **alpns)
1094 {
1095 /*
1096 * spec here is as per draft-ietf-dnsop-svcb-https, section-7.1.1
1097 * encoding is catenated list of strings each preceded by a one
1098 * octet length
1099 * output is comma-sep list of the strings
1100 * implementations may or may not handle quoting of comma within
1101 * string values, so we might see a comma within the wire format
1102 * version of a string, in which case we will precede that by a
1103 * backslash - same goes for a backslash character, and of course
1104 * we need to use two backslashes in strings when we mean one;-)
1105 */
1106 int remaining = (int) len;
1107 char *oval;
1108 size_t i;
1109 unsigned char *cp = rrval;
1110 struct dynbuf dval;
1111
1112 if(!alpns)
1113 return CURLE_OUT_OF_MEMORY;
1114 Curl_dyn_init(&dval, DYN_DOH_RESPONSE);
1115 remaining = (int)len;
1116 cp = rrval;
1117 while(remaining > 0) {
1118 size_t tlen = (size_t) *cp++;
1119
1120 /* if not 1st time, add comma */
1121 if(remaining != (int)len && Curl_dyn_addn(&dval, ",", 1))
1122 goto err;
1123 remaining--;
1124 if(tlen > (size_t)remaining)
1125 goto err;
1126 /* add escape char if needed, clunky but easier to read */
1127 for(i = 0; i != tlen; i++) {
1128 if('\\' == *cp || ',' == *cp) {
1129 if(Curl_dyn_addn(&dval, "\\", 1))
1130 goto err;
1131 }
1132 if(Curl_dyn_addn(&dval, cp++, 1))
1133 goto err;
1134 }
1135 remaining -= (int)tlen;
1136 }
1137 /* this string is always null terminated */
1138 oval = Curl_dyn_ptr(&dval);
1139 if(!oval)
1140 goto err;
1141 *alpns = oval;
1142 return CURLE_OK;
1143 err:
1144 Curl_dyn_free(&dval);
1145 return CURLE_BAD_CONTENT_ENCODING;
1146 }
1147
1148 #ifdef DEBUGBUILD
doh_test_alpn_escapes(void)1149 static CURLcode doh_test_alpn_escapes(void)
1150 {
1151 /* we will use an example from draft-ietf-dnsop-svcb, figure 10 */
1152 static unsigned char example[] = {
1153 0x08, /* length 8 */
1154 0x66, 0x5c, 0x6f, 0x6f, 0x2c, 0x62, 0x61, 0x72, /* value "f\\oo,bar" */
1155 0x02, /* length 2 */
1156 0x68, 0x32 /* value "h2" */
1157 };
1158 size_t example_len = sizeof(example);
1159 char *aval = NULL;
1160 static const char *expected = "f\\\\oo\\,bar,h2";
1161
1162 if(doh_decode_rdata_alpn(example, example_len, &aval) != CURLE_OK)
1163 return CURLE_BAD_CONTENT_ENCODING;
1164 if(strlen(aval) != strlen(expected))
1165 return CURLE_BAD_CONTENT_ENCODING;
1166 if(memcmp(aval, expected, strlen(aval)))
1167 return CURLE_BAD_CONTENT_ENCODING;
1168 return CURLE_OK;
1169 }
1170 #endif
1171
doh_resp_decode_httpsrr(unsigned char * rrval,size_t len,struct Curl_https_rrinfo ** hrr)1172 static CURLcode doh_resp_decode_httpsrr(unsigned char *rrval, size_t len,
1173 struct Curl_https_rrinfo **hrr)
1174 {
1175 size_t remaining = len;
1176 unsigned char *cp = rrval;
1177 uint16_t pcode = 0, plen = 0;
1178 struct Curl_https_rrinfo *lhrr = NULL;
1179 char *dnsname = NULL;
1180
1181 #ifdef DEBUGBUILD
1182 /* a few tests of escaping, should not be here but ok for now */
1183 if(doh_test_alpn_escapes() != CURLE_OK)
1184 return CURLE_OUT_OF_MEMORY;
1185 #endif
1186 lhrr = calloc(1, sizeof(struct Curl_https_rrinfo));
1187 if(!lhrr)
1188 return CURLE_OUT_OF_MEMORY;
1189 lhrr->val = Curl_memdup(rrval, len);
1190 if(!lhrr->val)
1191 goto err;
1192 lhrr->len = len;
1193 if(remaining <= 2)
1194 goto err;
1195 lhrr->priority = (uint16_t)((cp[0] << 8) + cp[1]);
1196 cp += 2;
1197 remaining -= (uint16_t)2;
1198 if(doh_decode_rdata_name(&cp, &remaining, &dnsname) != CURLE_OK)
1199 goto err;
1200 lhrr->target = dnsname;
1201 while(remaining >= 4) {
1202 pcode = (uint16_t)((*cp << 8) + (*(cp + 1)));
1203 cp += 2;
1204 plen = (uint16_t)((*cp << 8) + (*(cp + 1)));
1205 cp += 2;
1206 remaining -= 4;
1207 if(pcode == HTTPS_RR_CODE_ALPN) {
1208 if(doh_decode_rdata_alpn(cp, plen, &lhrr->alpns) != CURLE_OK)
1209 goto err;
1210 }
1211 if(pcode == HTTPS_RR_CODE_NO_DEF_ALPN)
1212 lhrr->no_def_alpn = TRUE;
1213 else if(pcode == HTTPS_RR_CODE_IPV4) {
1214 if(!plen)
1215 goto err;
1216 lhrr->ipv4hints = Curl_memdup(cp, plen);
1217 if(!lhrr->ipv4hints)
1218 goto err;
1219 lhrr->ipv4hints_len = (size_t)plen;
1220 }
1221 else if(pcode == HTTPS_RR_CODE_ECH) {
1222 if(!plen)
1223 goto err;
1224 lhrr->echconfiglist = Curl_memdup(cp, plen);
1225 if(!lhrr->echconfiglist)
1226 goto err;
1227 lhrr->echconfiglist_len = (size_t)plen;
1228 }
1229 else if(pcode == HTTPS_RR_CODE_IPV6) {
1230 if(!plen)
1231 goto err;
1232 lhrr->ipv6hints = Curl_memdup(cp, plen);
1233 if(!lhrr->ipv6hints)
1234 goto err;
1235 lhrr->ipv6hints_len = (size_t)plen;
1236 }
1237 if(plen > 0 && plen <= remaining) {
1238 cp += plen;
1239 remaining -= plen;
1240 }
1241 }
1242 DEBUGASSERT(!remaining);
1243 *hrr = lhrr;
1244 return CURLE_OK;
1245 err:
1246 if(lhrr) {
1247 Curl_safefree(lhrr->target);
1248 Curl_safefree(lhrr->echconfiglist);
1249 Curl_safefree(lhrr->val);
1250 Curl_safefree(lhrr->alpns);
1251 Curl_safefree(lhrr);
1252 }
1253 return CURLE_OUT_OF_MEMORY;
1254 }
1255
1256 # ifdef DEBUGBUILD
doh_print_httpsrr(struct Curl_easy * data,struct Curl_https_rrinfo * hrr)1257 static void doh_print_httpsrr(struct Curl_easy *data,
1258 struct Curl_https_rrinfo *hrr)
1259 {
1260 DEBUGASSERT(hrr);
1261 infof(data, "HTTPS RR: priority %d, target: %s",
1262 hrr->priority, hrr->target);
1263 if(hrr->alpns)
1264 infof(data, "HTTPS RR: alpns %s", hrr->alpns);
1265 else
1266 infof(data, "HTTPS RR: no alpns");
1267 if(hrr->no_def_alpn)
1268 infof(data, "HTTPS RR: no_def_alpn set");
1269 else
1270 infof(data, "HTTPS RR: no_def_alpn not set");
1271 if(hrr->ipv4hints) {
1272 doh_print_buf(data, "HTTPS RR: ipv4hints",
1273 hrr->ipv4hints, hrr->ipv4hints_len);
1274 }
1275 else
1276 infof(data, "HTTPS RR: no ipv4hints");
1277 if(hrr->echconfiglist) {
1278 doh_print_buf(data, "HTTPS RR: ECHConfigList",
1279 hrr->echconfiglist, hrr->echconfiglist_len);
1280 }
1281 else
1282 infof(data, "HTTPS RR: no ECHConfigList");
1283 if(hrr->ipv6hints) {
1284 doh_print_buf(data, "HTTPS RR: ipv6hint",
1285 hrr->ipv6hints, hrr->ipv6hints_len);
1286 }
1287 else
1288 infof(data, "HTTPS RR: no ipv6hints");
1289 return;
1290 }
1291 # endif
1292 #endif
1293
Curl_doh_is_resolved(struct Curl_easy * data,struct Curl_dns_entry ** dnsp)1294 CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
1295 struct Curl_dns_entry **dnsp)
1296 {
1297 CURLcode result;
1298 struct doh_probes *dohp = data->req.doh;
1299 *dnsp = NULL; /* defaults to no response */
1300 if(!dohp)
1301 return CURLE_OUT_OF_MEMORY;
1302
1303 if(dohp->probe[DOH_SLOT_IPV4].easy_mid < 0 &&
1304 dohp->probe[DOH_SLOT_IPV6].easy_mid < 0) {
1305 failf(data, "Could not DoH-resolve: %s", data->state.async.hostname);
1306 return CONN_IS_PROXIED(data->conn) ? CURLE_COULDNT_RESOLVE_PROXY :
1307 CURLE_COULDNT_RESOLVE_HOST;
1308 }
1309 else if(!dohp->pending) {
1310 DOHcode rc[DOH_SLOT_COUNT];
1311 struct dohentry de;
1312 int slot;
1313
1314 memset(rc, 0, sizeof(rc));
1315 /* remove DoH handles from multi handle and close them */
1316 Curl_doh_close(data);
1317 /* parse the responses, create the struct and return it! */
1318 de_init(&de);
1319 for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
1320 struct doh_probe *p = &dohp->probe[slot];
1321 if(!p->dnstype)
1322 continue;
1323 rc[slot] = doh_resp_decode(Curl_dyn_uptr(&p->resp_body),
1324 Curl_dyn_len(&p->resp_body),
1325 p->dnstype, &de);
1326 Curl_dyn_free(&p->resp_body);
1327 #ifndef CURL_DISABLE_VERBOSE_STRINGS
1328 if(rc[slot]) {
1329 infof(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]),
1330 doh_type2name(p->dnstype), dohp->host);
1331 }
1332 #endif
1333 } /* next slot */
1334
1335 result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */
1336 if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) {
1337 /* we have an address, of one kind or other */
1338 struct Curl_dns_entry *dns;
1339 struct Curl_addrinfo *ai;
1340
1341
1342 if(Curl_trc_ft_is_verbose(data, &Curl_doh_trc)) {
1343 infof(data, "[DoH] hostname: %s", dohp->host);
1344 doh_show(data, &de);
1345 }
1346
1347 result = doh2ai(&de, dohp->host, dohp->port, &ai);
1348 if(result) {
1349 de_cleanup(&de);
1350 return result;
1351 }
1352
1353 if(data->share)
1354 Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
1355
1356 /* we got a response, store it in the cache */
1357 dns = Curl_cache_addr(data, ai, dohp->host, 0, dohp->port, FALSE);
1358
1359 if(data->share)
1360 Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
1361
1362 if(!dns) {
1363 /* returned failure, bail out nicely */
1364 Curl_freeaddrinfo(ai);
1365 }
1366 else {
1367 data->state.async.dns = dns;
1368 *dnsp = dns;
1369 result = CURLE_OK; /* address resolution OK */
1370 }
1371 } /* address processing done */
1372
1373 /* Now process any build-specific attributes retrieved from DNS */
1374 #ifdef USE_HTTPSRR
1375 if(de.numhttps_rrs > 0 && result == CURLE_OK && *dnsp) {
1376 struct Curl_https_rrinfo *hrr = NULL;
1377 result = doh_resp_decode_httpsrr(de.https_rrs->val, de.https_rrs->len,
1378 &hrr);
1379 if(result) {
1380 infof(data, "Failed to decode HTTPS RR");
1381 return result;
1382 }
1383 infof(data, "Some HTTPS RR to process");
1384 # ifdef DEBUGBUILD
1385 doh_print_httpsrr(data, hrr);
1386 # endif
1387 (*dnsp)->hinfo = hrr;
1388 }
1389 #endif
1390
1391 /* All done */
1392 de_cleanup(&de);
1393 Curl_doh_cleanup(data);
1394 return result;
1395
1396 } /* !dohp->pending */
1397
1398 /* else wait for pending DoH transactions to complete */
1399 return CURLE_OK;
1400 }
1401
Curl_doh_close(struct Curl_easy * data)1402 void Curl_doh_close(struct Curl_easy *data)
1403 {
1404 struct doh_probes *doh = data->req.doh;
1405 if(doh && data->multi) {
1406 struct Curl_easy *probe_data;
1407 curl_off_t mid;
1408 size_t slot;
1409 for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
1410 mid = doh->probe[slot].easy_mid;
1411 if(mid < 0)
1412 continue;
1413 doh->probe[slot].easy_mid = -1;
1414 /* should have been called before data is removed from multi handle */
1415 DEBUGASSERT(data->multi);
1416 probe_data = data->multi ? Curl_multi_get_handle(data->multi, mid) :
1417 NULL;
1418 if(!probe_data) {
1419 DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%"
1420 FMT_OFF_T " not found!",
1421 doh->probe[slot].easy_mid));
1422 continue;
1423 }
1424 /* data->multi might already be reset at this time */
1425 curl_multi_remove_handle(data->multi, probe_data);
1426 Curl_close(&probe_data);
1427 }
1428 }
1429 }
1430
Curl_doh_cleanup(struct Curl_easy * data)1431 void Curl_doh_cleanup(struct Curl_easy *data)
1432 {
1433 struct doh_probes *doh = data->req.doh;
1434 if(doh) {
1435 Curl_doh_close(data);
1436 curl_slist_free_all(doh->req_hds);
1437 data->req.doh->req_hds = NULL;
1438 Curl_safefree(data->req.doh);
1439 }
1440 }
1441
1442 #endif /* CURL_DISABLE_DOH */
1443