• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 2018 - 2020, 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.haxx.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  ***************************************************************************/
22 
23 #include "curl_setup.h"
24 
25 #ifndef CURL_DISABLE_DOH
26 
27 #include "urldata.h"
28 #include "curl_addrinfo.h"
29 #include "doh.h"
30 
31 #include "sendf.h"
32 #include "multiif.h"
33 #include "url.h"
34 #include "share.h"
35 #include "curl_base64.h"
36 #include "connect.h"
37 #include "strdup.h"
38 #include "dynbuf.h"
39 /* The last 3 #include files should be in this order */
40 #include "curl_printf.h"
41 #include "curl_memory.h"
42 #include "memdebug.h"
43 
44 #define DNS_CLASS_IN 0x01
45 
46 #ifndef CURL_DISABLE_VERBOSE_STRINGS
47 static const char * const errors[]={
48   "",
49   "Bad label",
50   "Out of range",
51   "Label loop",
52   "Too small",
53   "Out of memory",
54   "RDATA length",
55   "Malformat",
56   "Bad RCODE",
57   "Unexpected TYPE",
58   "Unexpected CLASS",
59   "No content",
60   "Bad ID",
61   "Name too long"
62 };
63 
doh_strerror(DOHcode code)64 static const char *doh_strerror(DOHcode code)
65 {
66   if((code >= DOH_OK) && (code <= DOH_DNS_NAME_TOO_LONG))
67     return errors[code];
68   return "bad error code";
69 }
70 #endif
71 
72 #ifdef DEBUGBUILD
73 #define UNITTEST
74 #else
75 #define UNITTEST static
76 #endif
77 
78 /* @unittest 1655
79  */
doh_encode(const char * host,DNStype dnstype,unsigned char * dnsp,size_t len,size_t * olen)80 UNITTEST DOHcode doh_encode(const char *host,
81                             DNStype dnstype,
82                             unsigned char *dnsp, /* buffer */
83                             size_t len,  /* buffer size */
84                             size_t *olen) /* output length */
85 {
86   const size_t hostlen = strlen(host);
87   unsigned char *orig = dnsp;
88   const char *hostp = host;
89 
90   /* The expected output length is 16 bytes more than the length of
91    * the QNAME-encoding of the host name.
92    *
93    * A valid DNS name may not contain a zero-length label, except at
94    * the end.  For this reason, a name beginning with a dot, or
95    * containing a sequence of two or more consecutive dots, is invalid
96    * and cannot be encoded as a QNAME.
97    *
98    * If the host name ends with a trailing dot, the corresponding
99    * QNAME-encoding is one byte longer than the host name. If (as is
100    * also valid) the hostname is shortened by the omission of the
101    * trailing dot, then its QNAME-encoding will be two bytes longer
102    * than the host name.
103    *
104    * Each [ label, dot ] pair is encoded as [ length, label ],
105    * preserving overall length.  A final [ label ] without a dot is
106    * also encoded as [ length, label ], increasing overall length
107    * by one. The encoding is completed by appending a zero byte,
108    * representing the zero-length root label, again increasing
109    * the overall length by one.
110    */
111 
112   size_t expected_len;
113   DEBUGASSERT(hostlen);
114   expected_len = 12 + 1 + hostlen + 4;
115   if(host[hostlen-1]!='.')
116     expected_len++;
117 
118   if(expected_len > (256 + 16)) /* RFCs 1034, 1035 */
119     return DOH_DNS_NAME_TOO_LONG;
120 
121   if(len < expected_len)
122     return DOH_TOO_SMALL_BUFFER;
123 
124   *dnsp++ = 0; /* 16 bit id */
125   *dnsp++ = 0;
126   *dnsp++ = 0x01; /* |QR|   Opcode  |AA|TC|RD| Set the RD bit */
127   *dnsp++ = '\0'; /* |RA|   Z    |   RCODE   |                */
128   *dnsp++ = '\0';
129   *dnsp++ = 1;    /* QDCOUNT (number of entries in the question section) */
130   *dnsp++ = '\0';
131   *dnsp++ = '\0'; /* ANCOUNT */
132   *dnsp++ = '\0';
133   *dnsp++ = '\0'; /* NSCOUNT */
134   *dnsp++ = '\0';
135   *dnsp++ = '\0'; /* ARCOUNT */
136 
137   /* encode each label and store it in the QNAME */
138   while(*hostp) {
139     size_t labellen;
140     char *dot = strchr(hostp, '.');
141     if(dot)
142       labellen = dot - hostp;
143     else
144       labellen = strlen(hostp);
145     if((labellen > 63) || (!labellen)) {
146       /* label is too long or too short, error out */
147       *olen = 0;
148       return DOH_DNS_BAD_LABEL;
149     }
150     /* label is non-empty, process it */
151     *dnsp++ = (unsigned char)labellen;
152     memcpy(dnsp, hostp, labellen);
153     dnsp += labellen;
154     hostp += labellen;
155     /* advance past dot, but only if there is one */
156     if(dot)
157       hostp++;
158   } /* next label */
159 
160   *dnsp++ = 0; /* append zero-length label for root */
161 
162   /* There are assigned TYPE codes beyond 255: use range [1..65535]  */
163   *dnsp++ = (unsigned char)(255 & (dnstype>>8)); /* upper 8 bit TYPE */
164   *dnsp++ = (unsigned char)(255 & dnstype);      /* lower 8 bit TYPE */
165 
166   *dnsp++ = '\0'; /* upper 8 bit CLASS */
167   *dnsp++ = DNS_CLASS_IN; /* IN - "the Internet" */
168 
169   *olen = dnsp - orig;
170 
171   /* verify that our estimation of length is valid, since
172    * this has led to buffer overflows in this function */
173   DEBUGASSERT(*olen == expected_len);
174   return DOH_OK;
175 }
176 
177 static size_t
doh_write_cb(const void * contents,size_t size,size_t nmemb,void * userp)178 doh_write_cb(const void *contents, size_t size, size_t nmemb, void *userp)
179 {
180   size_t realsize = size * nmemb;
181   struct dynbuf *mem = (struct dynbuf *)userp;
182 
183   if(Curl_dyn_addn(mem, contents, realsize))
184     return 0;
185 
186   return realsize;
187 }
188 
189 /* called from multi.c when this DOH transfer is complete */
Curl_doh_done(struct Curl_easy * doh,CURLcode result)190 static int Curl_doh_done(struct Curl_easy *doh, CURLcode result)
191 {
192   struct Curl_easy *data = doh->set.dohfor;
193   /* so one of the DOH request done for the 'data' transfer is now complete! */
194   data->req.doh.pending--;
195   infof(data, "a DOH request is completed, %u to go\n", data->req.doh.pending);
196   if(result)
197     infof(data, "DOH request %s\n", curl_easy_strerror(result));
198 
199   if(!data->req.doh.pending) {
200     /* DOH completed */
201     curl_slist_free_all(data->req.doh.headers);
202     data->req.doh.headers = NULL;
203     Curl_expire(data, 0, EXPIRE_RUN_NOW);
204   }
205   return 0;
206 }
207 
208 #define ERROR_CHECK_SETOPT(x,y) \
209 do {                                      \
210   result = curl_easy_setopt(doh, x, y);   \
211   if(result)                              \
212     goto error;                           \
213 } while(0)
214 
dohprobe(struct Curl_easy * data,struct dnsprobe * p,DNStype dnstype,const char * host,const char * url,CURLM * multi,struct curl_slist * headers)215 static CURLcode dohprobe(struct Curl_easy *data,
216                          struct dnsprobe *p, DNStype dnstype,
217                          const char *host,
218                          const char *url, CURLM *multi,
219                          struct curl_slist *headers)
220 {
221   struct Curl_easy *doh = NULL;
222   char *nurl = NULL;
223   CURLcode result = CURLE_OK;
224   timediff_t timeout_ms;
225   DOHcode d = doh_encode(host, dnstype, p->dohbuffer, sizeof(p->dohbuffer),
226                          &p->dohlen);
227   if(d) {
228     failf(data, "Failed to encode DOH packet [%d]\n", d);
229     return CURLE_OUT_OF_MEMORY;
230   }
231 
232   p->dnstype = dnstype;
233   Curl_dyn_init(&p->serverdoh, DYN_DOH_RESPONSE);
234 
235   /* Note: this is code for sending the DoH request with GET but there's still
236      no logic that actually enables this. We should either add that ability or
237      yank out the GET code. Discuss! */
238   if(data->set.doh_get) {
239     char *b64;
240     size_t b64len;
241     result = Curl_base64url_encode(data, (char *)p->dohbuffer, p->dohlen,
242                                    &b64, &b64len);
243     if(result)
244       goto error;
245     nurl = aprintf("%s?dns=%s", url, b64);
246     free(b64);
247     if(!nurl) {
248       result = CURLE_OUT_OF_MEMORY;
249       goto error;
250     }
251     url = nurl;
252   }
253 
254   timeout_ms = Curl_timeleft(data, NULL, TRUE);
255   if(timeout_ms <= 0) {
256     result = CURLE_OPERATION_TIMEDOUT;
257     goto error;
258   }
259   /* Curl_open() is the internal version of curl_easy_init() */
260   result = Curl_open(&doh);
261   if(!result) {
262     /* pass in the struct pointer via a local variable to please coverity and
263        the gcc typecheck helpers */
264     struct dynbuf *resp = &p->serverdoh;
265     ERROR_CHECK_SETOPT(CURLOPT_URL, url);
266     ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_write_cb);
267     ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, resp);
268     if(!data->set.doh_get) {
269       ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, p->dohbuffer);
270       ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)p->dohlen);
271     }
272     ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, headers);
273 #ifdef USE_NGHTTP2
274     ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
275 #endif
276 #ifndef CURLDEBUG
277     /* enforce HTTPS if not debug */
278     ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
279 #else
280     /* in debug mode, also allow http */
281     ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS);
282 #endif
283     ERROR_CHECK_SETOPT(CURLOPT_TIMEOUT_MS, (long)timeout_ms);
284     if(data->set.verbose)
285       ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L);
286     if(data->set.no_signal)
287       ERROR_CHECK_SETOPT(CURLOPT_NOSIGNAL, 1L);
288 
289     /* Inherit *some* SSL options from the user's transfer. This is a
290        best-guess as to which options are needed for compatibility. #3661 */
291     if(data->set.ssl.falsestart)
292       ERROR_CHECK_SETOPT(CURLOPT_SSL_FALSESTART, 1L);
293     if(data->set.ssl.primary.verifyhost)
294       ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYHOST, 2L);
295 #ifndef CURL_DISABLE_PROXY
296     if(data->set.proxy_ssl.primary.verifyhost)
297       ERROR_CHECK_SETOPT(CURLOPT_PROXY_SSL_VERIFYHOST, 2L);
298     if(data->set.proxy_ssl.primary.verifypeer)
299       ERROR_CHECK_SETOPT(CURLOPT_PROXY_SSL_VERIFYPEER, 1L);
300     if(data->set.str[STRING_SSL_CAFILE_PROXY]) {
301       ERROR_CHECK_SETOPT(CURLOPT_PROXY_CAINFO,
302         data->set.str[STRING_SSL_CAFILE_PROXY]);
303     }
304     if(data->set.str[STRING_SSL_CRLFILE_PROXY]) {
305       ERROR_CHECK_SETOPT(CURLOPT_PROXY_CRLFILE,
306         data->set.str[STRING_SSL_CRLFILE_PROXY]);
307     }
308     if(data->set.proxy_ssl.no_revoke)
309       ERROR_CHECK_SETOPT(CURLOPT_PROXY_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE);
310     else if(data->set.proxy_ssl.revoke_best_effort)
311       ERROR_CHECK_SETOPT(CURLOPT_PROXY_SSL_OPTIONS,
312                          CURLSSLOPT_REVOKE_BEST_EFFORT);
313     if(data->set.str[STRING_SSL_CAPATH_PROXY]) {
314       ERROR_CHECK_SETOPT(CURLOPT_PROXY_CAPATH,
315         data->set.str[STRING_SSL_CAPATH_PROXY]);
316     }
317 #endif
318     if(data->set.ssl.primary.verifypeer)
319       ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYPEER, 1L);
320     if(data->set.ssl.primary.verifystatus)
321       ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYSTATUS, 1L);
322     if(data->set.str[STRING_SSL_CAFILE_ORIG]) {
323       ERROR_CHECK_SETOPT(CURLOPT_CAINFO,
324         data->set.str[STRING_SSL_CAFILE_ORIG]);
325     }
326     if(data->set.str[STRING_SSL_CAPATH_ORIG]) {
327       ERROR_CHECK_SETOPT(CURLOPT_CAPATH,
328         data->set.str[STRING_SSL_CAPATH_ORIG]);
329     }
330     if(data->set.str[STRING_SSL_CRLFILE_ORIG]) {
331       ERROR_CHECK_SETOPT(CURLOPT_CRLFILE,
332         data->set.str[STRING_SSL_CRLFILE_ORIG]);
333     }
334     if(data->set.ssl.certinfo)
335       ERROR_CHECK_SETOPT(CURLOPT_CERTINFO, 1L);
336     if(data->set.str[STRING_SSL_RANDOM_FILE]) {
337       ERROR_CHECK_SETOPT(CURLOPT_RANDOM_FILE,
338         data->set.str[STRING_SSL_RANDOM_FILE]);
339     }
340     if(data->set.str[STRING_SSL_EGDSOCKET]) {
341       ERROR_CHECK_SETOPT(CURLOPT_EGDSOCKET,
342         data->set.str[STRING_SSL_EGDSOCKET]);
343     }
344     if(data->set.ssl.no_revoke)
345       ERROR_CHECK_SETOPT(CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE);
346     else if(data->set.ssl.revoke_best_effort)
347       ERROR_CHECK_SETOPT(CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
348     if(data->set.ssl.fsslctx)
349       ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl.fsslctx);
350     if(data->set.ssl.fsslctxp)
351       ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl.fsslctxp);
352     if(data->set.str[STRING_SSL_EC_CURVES]) {
353       ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES,
354         data->set.str[STRING_SSL_EC_CURVES]);
355     }
356 
357     doh->set.fmultidone = Curl_doh_done;
358     doh->set.dohfor = data; /* identify for which transfer this is done */
359     p->easy = doh;
360 
361     /* add this transfer to the multi handle */
362     if(curl_multi_add_handle(multi, doh))
363       goto error;
364   }
365   else
366     goto error;
367   free(nurl);
368   return CURLE_OK;
369 
370   error:
371   free(nurl);
372   Curl_close(&doh);
373   return result;
374 }
375 
376 /*
377  * Curl_doh() resolves a name using DOH. It resolves a name and returns a
378  * 'Curl_addrinfo *' with the address information.
379  */
380 
Curl_doh(struct connectdata * conn,const char * hostname,int port,int * waitp)381 struct Curl_addrinfo *Curl_doh(struct connectdata *conn,
382                                const char *hostname,
383                                int port,
384                                int *waitp)
385 {
386   struct Curl_easy *data = conn->data;
387   CURLcode result = CURLE_OK;
388   int slot;
389   *waitp = TRUE; /* this never returns synchronously */
390   (void)conn;
391   (void)hostname;
392   (void)port;
393 
394   /* start clean, consider allocating this struct on demand */
395   memset(&data->req.doh, 0, sizeof(struct dohdata));
396 
397   conn->bits.doh = TRUE;
398   data->req.doh.host = hostname;
399   data->req.doh.port = port;
400   data->req.doh.headers =
401     curl_slist_append(NULL,
402                       "Content-Type: application/dns-message");
403   if(!data->req.doh.headers)
404     goto error;
405 
406   if(conn->ip_version != CURL_IPRESOLVE_V6) {
407     /* create IPv4 DOH request */
408     result = dohprobe(data, &data->req.doh.probe[DOH_PROBE_SLOT_IPADDR_V4],
409                       DNS_TYPE_A, hostname, data->set.str[STRING_DOH],
410                       data->multi, data->req.doh.headers);
411     if(result)
412       goto error;
413     data->req.doh.pending++;
414   }
415 
416   if(conn->ip_version != CURL_IPRESOLVE_V4) {
417     /* create IPv6 DOH request */
418     result = dohprobe(data, &data->req.doh.probe[DOH_PROBE_SLOT_IPADDR_V6],
419                       DNS_TYPE_AAAA, hostname, data->set.str[STRING_DOH],
420                       data->multi, data->req.doh.headers);
421     if(result)
422       goto error;
423     data->req.doh.pending++;
424   }
425   return NULL;
426 
427   error:
428   curl_slist_free_all(data->req.doh.headers);
429   data->req.doh.headers = NULL;
430   for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) {
431     Curl_close(&data->req.doh.probe[slot].easy);
432   }
433   return NULL;
434 }
435 
skipqname(const unsigned char * doh,size_t dohlen,unsigned int * indexp)436 static DOHcode skipqname(const unsigned char *doh, size_t dohlen,
437                          unsigned int *indexp)
438 {
439   unsigned char length;
440   do {
441     if(dohlen < (*indexp + 1))
442       return DOH_DNS_OUT_OF_RANGE;
443     length = doh[*indexp];
444     if((length & 0xc0) == 0xc0) {
445       /* name pointer, advance over it and be done */
446       if(dohlen < (*indexp + 2))
447         return DOH_DNS_OUT_OF_RANGE;
448       *indexp += 2;
449       break;
450     }
451     if(length & 0xc0)
452       return DOH_DNS_BAD_LABEL;
453     if(dohlen < (*indexp + 1 + length))
454       return DOH_DNS_OUT_OF_RANGE;
455     *indexp += 1 + length;
456   } while(length);
457   return DOH_OK;
458 }
459 
get16bit(const unsigned char * doh,int index)460 static unsigned short get16bit(const unsigned char *doh, int index)
461 {
462   return (unsigned short)((doh[index] << 8) | doh[index + 1]);
463 }
464 
get32bit(const unsigned char * doh,int index)465 static unsigned int get32bit(const unsigned char *doh, int index)
466 {
467    /* make clang and gcc optimize this to bswap by incrementing
468       the pointer first. */
469    doh += index;
470 
471    /* avoid undefined behaviour by casting to unsigned before shifting
472       24 bits, possibly into the sign bit. codegen is same, but
473       ub sanitizer won't be upset */
474   return ( (unsigned)doh[0] << 24) | (doh[1] << 16) |(doh[2] << 8) | doh[3];
475 }
476 
store_a(const unsigned char * doh,int index,struct dohentry * d)477 static DOHcode store_a(const unsigned char *doh, int index, struct dohentry *d)
478 {
479   /* silently ignore addresses over the limit */
480   if(d->numaddr < DOH_MAX_ADDR) {
481     struct dohaddr *a = &d->addr[d->numaddr];
482     a->type = DNS_TYPE_A;
483     memcpy(&a->ip.v4, &doh[index], 4);
484     d->numaddr++;
485   }
486   return DOH_OK;
487 }
488 
store_aaaa(const unsigned char * doh,int index,struct dohentry * d)489 static DOHcode store_aaaa(const unsigned char *doh,
490                           int index,
491                           struct dohentry *d)
492 {
493   /* silently ignore addresses over the limit */
494   if(d->numaddr < DOH_MAX_ADDR) {
495     struct dohaddr *a = &d->addr[d->numaddr];
496     a->type = DNS_TYPE_AAAA;
497     memcpy(&a->ip.v6, &doh[index], 16);
498     d->numaddr++;
499   }
500   return DOH_OK;
501 }
502 
store_cname(const unsigned char * doh,size_t dohlen,unsigned int index,struct dohentry * d)503 static DOHcode store_cname(const unsigned char *doh,
504                            size_t dohlen,
505                            unsigned int index,
506                            struct dohentry *d)
507 {
508   struct dynbuf *c;
509   unsigned int loop = 128; /* a valid DNS name can never loop this much */
510   unsigned char length;
511 
512   if(d->numcname == DOH_MAX_CNAME)
513     return DOH_OK; /* skip! */
514 
515   c = &d->cname[d->numcname++];
516   do {
517     if(index >= dohlen)
518       return DOH_DNS_OUT_OF_RANGE;
519     length = doh[index];
520     if((length & 0xc0) == 0xc0) {
521       int newpos;
522       /* name pointer, get the new offset (14 bits) */
523       if((index + 1) >= dohlen)
524         return DOH_DNS_OUT_OF_RANGE;
525 
526       /* move to the new index */
527       newpos = (length & 0x3f) << 8 | doh[index + 1];
528       index = newpos;
529       continue;
530     }
531     else if(length & 0xc0)
532       return DOH_DNS_BAD_LABEL; /* bad input */
533     else
534       index++;
535 
536     if(length) {
537       if(Curl_dyn_len(c)) {
538         if(Curl_dyn_add(c, "."))
539           return DOH_OUT_OF_MEM;
540       }
541       if((index + length) > dohlen)
542         return DOH_DNS_BAD_LABEL;
543 
544       if(Curl_dyn_addn(c, &doh[index], length))
545         return DOH_OUT_OF_MEM;
546       index += length;
547     }
548   } while(length && --loop);
549 
550   if(!loop)
551     return DOH_DNS_LABEL_LOOP;
552   return DOH_OK;
553 }
554 
rdata(const unsigned char * doh,size_t dohlen,unsigned short rdlength,unsigned short type,int index,struct dohentry * d)555 static DOHcode rdata(const unsigned char *doh,
556                      size_t dohlen,
557                      unsigned short rdlength,
558                      unsigned short type,
559                      int index,
560                      struct dohentry *d)
561 {
562   /* RDATA
563      - A (TYPE 1):  4 bytes
564      - AAAA (TYPE 28): 16 bytes
565      - NS (TYPE 2): N bytes */
566   DOHcode rc;
567 
568   switch(type) {
569   case DNS_TYPE_A:
570     if(rdlength != 4)
571       return DOH_DNS_RDATA_LEN;
572     rc = store_a(doh, index, d);
573     if(rc)
574       return rc;
575     break;
576   case DNS_TYPE_AAAA:
577     if(rdlength != 16)
578       return DOH_DNS_RDATA_LEN;
579     rc = store_aaaa(doh, index, d);
580     if(rc)
581       return rc;
582     break;
583   case DNS_TYPE_CNAME:
584     rc = store_cname(doh, dohlen, index, d);
585     if(rc)
586       return rc;
587     break;
588   case DNS_TYPE_DNAME:
589     /* explicit for clarity; just skip; rely on synthesized CNAME  */
590     break;
591   default:
592     /* unsupported type, just skip it */
593     break;
594   }
595   return DOH_OK;
596 }
597 
de_init(struct dohentry * de)598 UNITTEST void de_init(struct dohentry *de)
599 {
600   int i;
601   memset(de, 0, sizeof(*de));
602   de->ttl = INT_MAX;
603   for(i = 0; i < DOH_MAX_CNAME; i++)
604     Curl_dyn_init(&de->cname[i], DYN_DOH_CNAME);
605 }
606 
607 
doh_decode(const unsigned char * doh,size_t dohlen,DNStype dnstype,struct dohentry * d)608 UNITTEST DOHcode doh_decode(const unsigned char *doh,
609                             size_t dohlen,
610                             DNStype dnstype,
611                             struct dohentry *d)
612 {
613   unsigned char rcode;
614   unsigned short qdcount;
615   unsigned short ancount;
616   unsigned short type = 0;
617   unsigned short rdlength;
618   unsigned short nscount;
619   unsigned short arcount;
620   unsigned int index = 12;
621   DOHcode rc;
622 
623   if(dohlen < 12)
624     return DOH_TOO_SMALL_BUFFER; /* too small */
625   if(!doh || doh[0] || doh[1])
626     return DOH_DNS_BAD_ID; /* bad ID */
627   rcode = doh[3] & 0x0f;
628   if(rcode)
629     return DOH_DNS_BAD_RCODE; /* bad rcode */
630 
631   qdcount = get16bit(doh, 4);
632   while(qdcount) {
633     rc = skipqname(doh, dohlen, &index);
634     if(rc)
635       return rc; /* bad qname */
636     if(dohlen < (index + 4))
637       return DOH_DNS_OUT_OF_RANGE;
638     index += 4; /* skip question's type and class */
639     qdcount--;
640   }
641 
642   ancount = get16bit(doh, 6);
643   while(ancount) {
644     unsigned short class;
645     unsigned int ttl;
646 
647     rc = skipqname(doh, dohlen, &index);
648     if(rc)
649       return rc; /* bad qname */
650 
651     if(dohlen < (index + 2))
652       return DOH_DNS_OUT_OF_RANGE;
653 
654     type = get16bit(doh, index);
655     if((type != DNS_TYPE_CNAME)    /* may be synthesized from DNAME */
656        && (type != DNS_TYPE_DNAME) /* if present, accept and ignore */
657        && (type != dnstype))
658       /* Not the same type as was asked for nor CNAME nor DNAME */
659       return DOH_DNS_UNEXPECTED_TYPE;
660     index += 2;
661 
662     if(dohlen < (index + 2))
663       return DOH_DNS_OUT_OF_RANGE;
664     class = get16bit(doh, index);
665     if(DNS_CLASS_IN != class)
666       return DOH_DNS_UNEXPECTED_CLASS; /* unsupported */
667     index += 2;
668 
669     if(dohlen < (index + 4))
670       return DOH_DNS_OUT_OF_RANGE;
671 
672     ttl = get32bit(doh, index);
673     if(ttl < d->ttl)
674       d->ttl = ttl;
675     index += 4;
676 
677     if(dohlen < (index + 2))
678       return DOH_DNS_OUT_OF_RANGE;
679 
680     rdlength = get16bit(doh, index);
681     index += 2;
682     if(dohlen < (index + rdlength))
683       return DOH_DNS_OUT_OF_RANGE;
684 
685     rc = rdata(doh, dohlen, rdlength, type, index, d);
686     if(rc)
687       return rc; /* bad rdata */
688     index += rdlength;
689     ancount--;
690   }
691 
692   nscount = get16bit(doh, 8);
693   while(nscount) {
694     rc = skipqname(doh, dohlen, &index);
695     if(rc)
696       return rc; /* bad qname */
697 
698     if(dohlen < (index + 8))
699       return DOH_DNS_OUT_OF_RANGE;
700 
701     index += 2 + 2 + 4; /* type, class and ttl */
702 
703     if(dohlen < (index + 2))
704       return DOH_DNS_OUT_OF_RANGE;
705 
706     rdlength = get16bit(doh, index);
707     index += 2;
708     if(dohlen < (index + rdlength))
709       return DOH_DNS_OUT_OF_RANGE;
710     index += rdlength;
711     nscount--;
712   }
713 
714   arcount = get16bit(doh, 10);
715   while(arcount) {
716     rc = skipqname(doh, dohlen, &index);
717     if(rc)
718       return rc; /* bad qname */
719 
720     if(dohlen < (index + 8))
721       return DOH_DNS_OUT_OF_RANGE;
722 
723     index += 2 + 2 + 4; /* type, class and ttl */
724 
725     if(dohlen < (index + 2))
726       return DOH_DNS_OUT_OF_RANGE;
727 
728     rdlength = get16bit(doh, index);
729     index += 2;
730     if(dohlen < (index + rdlength))
731       return DOH_DNS_OUT_OF_RANGE;
732     index += rdlength;
733     arcount--;
734   }
735 
736   if(index != dohlen)
737     return DOH_DNS_MALFORMAT; /* something is wrong */
738 
739   if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr)
740     /* nothing stored! */
741     return DOH_NO_CONTENT;
742 
743   return DOH_OK; /* ok */
744 }
745 
746 #ifndef CURL_DISABLE_VERBOSE_STRINGS
showdoh(struct Curl_easy * data,const struct dohentry * d)747 static void showdoh(struct Curl_easy *data,
748                     const struct dohentry *d)
749 {
750   int i;
751   infof(data, "TTL: %u seconds\n", d->ttl);
752   for(i = 0; i < d->numaddr; i++) {
753     const struct dohaddr *a = &d->addr[i];
754     if(a->type == DNS_TYPE_A) {
755       infof(data, "DOH A: %u.%u.%u.%u\n",
756             a->ip.v4[0], a->ip.v4[1],
757             a->ip.v4[2], a->ip.v4[3]);
758     }
759     else if(a->type == DNS_TYPE_AAAA) {
760       int j;
761       char buffer[128];
762       char *ptr;
763       size_t len;
764       msnprintf(buffer, 128, "DOH AAAA: ");
765       ptr = &buffer[10];
766       len = 118;
767       for(j = 0; j < 16; j += 2) {
768         size_t l;
769         msnprintf(ptr, len, "%s%02x%02x", j?":":"", d->addr[i].ip.v6[j],
770                   d->addr[i].ip.v6[j + 1]);
771         l = strlen(ptr);
772         len -= l;
773         ptr += l;
774       }
775       infof(data, "%s\n", buffer);
776     }
777   }
778   for(i = 0; i < d->numcname; i++) {
779     infof(data, "CNAME: %s\n", Curl_dyn_ptr(&d->cname[i]));
780   }
781 }
782 #else
783 #define showdoh(x,y)
784 #endif
785 
786 /*
787  * doh2ai()
788  *
789  * This function returns a pointer to the first element of a newly allocated
790  * Curl_addrinfo struct linked list filled with the data from a set of DOH
791  * lookups.  Curl_addrinfo is meant to work like the addrinfo struct does for
792  * a IPv6 stack, but usable also for IPv4, all hosts and environments.
793  *
794  * The memory allocated by this function *MUST* be free'd later on calling
795  * Curl_freeaddrinfo().  For each successful call to this function there
796  * must be an associated call later to Curl_freeaddrinfo().
797  */
798 
799 static struct Curl_addrinfo *
doh2ai(const struct dohentry * de,const char * hostname,int port)800 doh2ai(const struct dohentry *de, const char *hostname, int port)
801 {
802   struct Curl_addrinfo *ai;
803   struct Curl_addrinfo *prevai = NULL;
804   struct Curl_addrinfo *firstai = NULL;
805   struct sockaddr_in *addr;
806 #ifdef ENABLE_IPV6
807   struct sockaddr_in6 *addr6;
808 #endif
809   CURLcode result = CURLE_OK;
810   int i;
811   size_t hostlen = strlen(hostname) + 1; /* include zero terminator */
812 
813   if(!de)
814     /* no input == no output! */
815     return NULL;
816 
817   for(i = 0; i < de->numaddr; i++) {
818     size_t ss_size;
819     CURL_SA_FAMILY_T addrtype;
820     if(de->addr[i].type == DNS_TYPE_AAAA) {
821 #ifndef ENABLE_IPV6
822       /* we can't handle IPv6 addresses */
823       continue;
824 #else
825       ss_size = sizeof(struct sockaddr_in6);
826       addrtype = AF_INET6;
827 #endif
828     }
829     else {
830       ss_size = sizeof(struct sockaddr_in);
831       addrtype = AF_INET;
832     }
833 
834     ai = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen);
835     if(!ai) {
836       result = CURLE_OUT_OF_MEMORY;
837       break;
838     }
839     ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo));
840     ai->ai_canonname = (void *)((char *)ai->ai_addr + ss_size);
841     memcpy(ai->ai_canonname, hostname, hostlen);
842 
843     if(!firstai)
844       /* store the pointer we want to return from this function */
845       firstai = ai;
846 
847     if(prevai)
848       /* make the previous entry point to this */
849       prevai->ai_next = ai;
850 
851     ai->ai_family = addrtype;
852 
853     /* we return all names as STREAM, so when using this address for TFTP
854        the type must be ignored and conn->socktype be used instead! */
855     ai->ai_socktype = SOCK_STREAM;
856 
857     ai->ai_addrlen = (curl_socklen_t)ss_size;
858 
859     /* leave the rest of the struct filled with zero */
860 
861     switch(ai->ai_family) {
862     case AF_INET:
863       addr = (void *)ai->ai_addr; /* storage area for this info */
864       DEBUGASSERT(sizeof(struct in_addr) == sizeof(de->addr[i].ip.v4));
865       memcpy(&addr->sin_addr, &de->addr[i].ip.v4, sizeof(struct in_addr));
866       addr->sin_family = addrtype;
867       addr->sin_port = htons((unsigned short)port);
868       break;
869 
870 #ifdef ENABLE_IPV6
871     case AF_INET6:
872       addr6 = (void *)ai->ai_addr; /* storage area for this info */
873       DEBUGASSERT(sizeof(struct in6_addr) == sizeof(de->addr[i].ip.v6));
874       memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr));
875       addr6->sin6_family = addrtype;
876       addr6->sin6_port = htons((unsigned short)port);
877       break;
878 #endif
879     }
880 
881     prevai = ai;
882   }
883 
884   if(result) {
885     Curl_freeaddrinfo(firstai);
886     firstai = NULL;
887   }
888 
889   return firstai;
890 }
891 
892 #ifndef CURL_DISABLE_VERBOSE_STRINGS
type2name(DNStype dnstype)893 static const char *type2name(DNStype dnstype)
894 {
895   return (dnstype == DNS_TYPE_A)?"A":"AAAA";
896 }
897 #endif
898 
de_cleanup(struct dohentry * d)899 UNITTEST void de_cleanup(struct dohentry *d)
900 {
901   int i = 0;
902   for(i = 0; i < d->numcname; i++) {
903     Curl_dyn_free(&d->cname[i]);
904   }
905 }
906 
Curl_doh_is_resolved(struct connectdata * conn,struct Curl_dns_entry ** dnsp)907 CURLcode Curl_doh_is_resolved(struct connectdata *conn,
908                               struct Curl_dns_entry **dnsp)
909 {
910   CURLcode result;
911   struct Curl_easy *data = conn->data;
912   *dnsp = NULL; /* defaults to no response */
913 
914   if(!data->req.doh.probe[DOH_PROBE_SLOT_IPADDR_V4].easy &&
915      !data->req.doh.probe[DOH_PROBE_SLOT_IPADDR_V6].easy) {
916     failf(data, "Could not DOH-resolve: %s", conn->async.hostname);
917     return conn->bits.proxy?CURLE_COULDNT_RESOLVE_PROXY:
918       CURLE_COULDNT_RESOLVE_HOST;
919   }
920   else if(!data->req.doh.pending) {
921     DOHcode rc[DOH_PROBE_SLOTS] = {
922       DOH_OK, DOH_OK
923     };
924     struct dohentry de;
925     int slot;
926     /* remove DOH handles from multi handle and close them */
927     for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) {
928       curl_multi_remove_handle(data->multi, data->req.doh.probe[slot].easy);
929       Curl_close(&data->req.doh.probe[slot].easy);
930     }
931     /* parse the responses, create the struct and return it! */
932     de_init(&de);
933     for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) {
934       struct dnsprobe *p = &data->req.doh.probe[slot];
935       if(!p->dnstype)
936         continue;
937       rc[slot] = doh_decode(Curl_dyn_uptr(&p->serverdoh),
938                             Curl_dyn_len(&p->serverdoh),
939                             p->dnstype,
940                             &de);
941       Curl_dyn_free(&p->serverdoh);
942       if(rc[slot]) {
943         infof(data, "DOH: %s type %s for %s\n", doh_strerror(rc[slot]),
944               type2name(p->dnstype), data->req.doh.host);
945       }
946     } /* next slot */
947 
948     result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */
949     if(!rc[DOH_PROBE_SLOT_IPADDR_V4] || !rc[DOH_PROBE_SLOT_IPADDR_V6]) {
950       /* we have an address, of one kind or other */
951       struct Curl_dns_entry *dns;
952       struct Curl_addrinfo *ai;
953 
954       infof(data, "DOH Host name: %s\n", data->req.doh.host);
955       showdoh(data, &de);
956 
957       ai = doh2ai(&de, data->req.doh.host, data->req.doh.port);
958       if(!ai) {
959         de_cleanup(&de);
960         return CURLE_OUT_OF_MEMORY;
961       }
962 
963       if(data->share)
964         Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
965 
966       /* we got a response, store it in the cache */
967       dns = Curl_cache_addr(data, ai, data->req.doh.host, data->req.doh.port);
968 
969       if(data->share)
970         Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
971 
972       if(!dns) {
973         /* returned failure, bail out nicely */
974         Curl_freeaddrinfo(ai);
975       }
976       else {
977         conn->async.dns = dns;
978         *dnsp = dns;
979         result = CURLE_OK;      /* address resolution OK */
980       }
981     } /* address processing done */
982 
983     /* Now process any build-specific attributes retrieved from DNS */
984 
985     /* All done */
986     de_cleanup(&de);
987     return result;
988 
989   } /* !data->req.doh.pending */
990 
991   /* else wait for pending DOH transactions to complete */
992   return CURLE_OK;
993 }
994 
995 #endif /* CURL_DISABLE_DOH */
996