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