xref: /curl/lib/doh.c (revision fbf5d507)
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