aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.inc5
-rw-r--r--lib/doh.c915
-rw-r--r--lib/doh.h44
-rw-r--r--lib/hostasyn.c25
-rw-r--r--lib/hostip.c77
-rw-r--r--lib/hostip.h13
-rw-r--r--lib/multi.c30
-rw-r--r--lib/setopt.c5
-rw-r--r--lib/urldata.h38
9 files changed, 1092 insertions, 60 deletions
diff --git a/lib/Makefile.inc b/lib/Makefile.inc
index 76ca6d041..ac0affeae 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -54,7 +54,8 @@ LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \
http_ntlm.c curl_ntlm_wb.c curl_ntlm_core.c curl_sasl.c rand.c \
curl_multibyte.c hostcheck.c conncache.c pipeline.c dotdot.c \
x509asn1.c http2.c smb.c curl_endian.c curl_des.c system_win32.c \
- mime.c sha256.c setopt.c curl_path.c curl_ctype.c curl_range.c psl.c
+ mime.c sha256.c setopt.c curl_path.c curl_ctype.c curl_range.c psl.c \
+ doh.c
LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h if2ip.h \
@@ -74,7 +75,7 @@ LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
curl_setup_once.h multihandle.h setup-vms.h pipeline.h dotdot.h \
x509asn1.h http2.h sigpipe.h smb.h curl_endian.h curl_des.h \
curl_printf.h system_win32.h rand.h mime.h curl_sha256.h setopt.h \
- curl_path.h curl_ctype.h curl_range.h psl.h
+ curl_path.h curl_ctype.h curl_range.h psl.h doh.h
LIB_RCFILES = libcurl.rc
diff --git a/lib/doh.c b/lib/doh.c
new file mode 100644
index 000000000..5d1cd3b78
--- /dev/null
+++ b/lib/doh.c
@@ -0,0 +1,915 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+#include "urldata.h"
+#include "curl_addrinfo.h"
+#include "sendf.h"
+#include "multiif.h"
+#include "url.h"
+#include "share.h"
+#include "curl_base64.h"
+#include "connect.h"
+#include "doh.h"
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+#define DNS_CLASS_IN 0x01
+#define DOH_MAX_RESPONSE_SIZE 3000 /* bytes */
+
+typedef enum {
+ DNS_TYPE_A = 1,
+ DNS_TYPE_NS = 2,
+ DNS_TYPE_CNAME = 5,
+ DNS_TYPE_AAAA = 28
+} DNStype;
+
+#define MAX_ADDR 24
+
+typedef enum {
+ DOH_OK,
+ DOH_DNS_BAD_LABEL, /* 1 */
+ DOH_DNS_OUT_OF_RANGE, /* 2 */
+ DOH_DNS_LABEL_LOOP, /* 3 */
+ DOH_TOO_SMALL_BUFFER, /* 4 */
+ DOH_OUT_OF_MEM, /* 5 */
+ DOH_DNS_RDATA_LEN, /* 6 */
+ DOH_DNS_MALFORMAT, /* 7 */
+ DOH_DNS_BAD_RCODE, /* 8 - no such name */
+ DOH_DNS_UNEXPECTED_TYPE, /* 9 */
+ DOH_DNS_UNEXPECTED_CLASS, /* 10 */
+ DOH_NO_CONTENT, /* 11 */
+ DOH_DNS_BAD_ID /* 12 */
+} DOHcode;
+
+static const char * const errors[]={
+ "",
+ "Bad label",
+ "Out of range",
+ "Label loop",
+ "Too small",
+ "Out of memory",
+ "RDATA length",
+ "Malformat",
+ "Bad RCODE",
+ "Unexpected TYPE",
+ "Unexpected CLASS",
+ "No content",
+ "Bad ID"
+};
+
+static const char *doh_strerror(DOHcode code)
+{
+ if((code >= DOH_OK) && (code <= DOH_DNS_BAD_ID))
+ return errors[code];
+ return "bad error code";
+}
+
+static DOHcode doh_encode(const char *host,
+ DNStype dnstype,
+ unsigned char *dnsp, /* buffer */
+ size_t len, /* buffer size */
+ size_t *olen) /* output length */
+{
+ size_t hostlen = strlen(host);
+ unsigned char *orig = dnsp;
+ const char *hostp = host;
+
+ if(len < (12 + hostlen + 4))
+ return DOH_TOO_SMALL_BUFFER;
+
+ *dnsp++ = 0; /* 16 bit id */
+ *dnsp++ = 0;
+ *dnsp++ = 0x01; /* |QR| Opcode |AA|TC|RD| Set the RD bit */
+ *dnsp++ = '\0'; /* |RA| Z | RCODE | */
+ *dnsp++ = '\0';
+ *dnsp++ = 1; /* QDCOUNT (number of entries in the question section) */
+ *dnsp++ = '\0';
+ *dnsp++ = '\0'; /* ANCOUNT */
+ *dnsp++ = '\0';
+ *dnsp++ = '\0'; /* NSCOUNT */
+ *dnsp++ = '\0';
+ *dnsp++ = '\0'; /* ARCOUNT */
+
+ /* store a QNAME */
+ do {
+ char *dot = strchr(hostp, '.');
+ size_t labellen;
+ bool found = false;
+ if(dot) {
+ found = true;
+ labellen = dot - hostp;
+ }
+ else
+ labellen = strlen(hostp);
+ if(labellen > 63)
+ /* too long label, error out */
+ return DOH_DNS_BAD_LABEL;
+ *dnsp++ = (unsigned char)labellen;
+ memcpy(dnsp, hostp, labellen);
+ dnsp += labellen;
+ hostp += labellen + 1;
+ if(!found) {
+ *dnsp++ = 0; /* terminating zero */
+ break;
+ }
+ } while(1);
+
+ *dnsp++ = '\0'; /* upper 8 bit TYPE */
+ *dnsp++ = (unsigned char)dnstype;
+ *dnsp++ = '\0'; /* upper 8 bit CLASS */
+ *dnsp++ = DNS_CLASS_IN; /* IN - "the Internet" */
+
+ *olen = dnsp - orig;
+ return DOH_OK;
+}
+
+static size_t
+doh_write_cb(void *contents, size_t size, size_t nmemb, void *userp)
+{
+ size_t realsize = size * nmemb;
+ struct dohresponse *mem = (struct dohresponse *)userp;
+
+ if((mem->size + realsize) > DOH_MAX_RESPONSE_SIZE)
+ /* suspiciously much for us */
+ return 0;
+
+ mem->memory = realloc(mem->memory, mem->size + realsize);
+ if(mem->memory == NULL)
+ /* out of memory! */
+ return 0;
+
+ memcpy(&(mem->memory[mem->size]), contents, realsize);
+ mem->size += realsize;
+
+ return realsize;
+}
+
+/* called from multi.c when this DOH transfer is complete */
+static int Curl_doh_done(struct Curl_easy *doh, CURLcode result)
+{
+ struct Curl_easy *data = doh->set.dohfor;
+ /* so one of the DOH request done for the 'data' transfer is now complete! */
+ data->req.doh.pending--;
+ infof(data, "a DOH request is completed, %d to go\n", data->req.doh.pending);
+ if(result)
+ infof(data, "DOH request %s\n", curl_easy_strerror(result));
+
+ if(!data->req.doh.pending) {
+ /* DOH completed */
+ curl_slist_free_all(data->req.doh.headers);
+ data->req.doh.headers = NULL;
+ Curl_expire(data, 0, EXPIRE_RUN_NOW);
+ }
+ return 0;
+}
+
+#define ERROR_CHECK_SETOPT(x,y) result = curl_easy_setopt(doh, x, y); \
+ if(result) goto error
+
+static CURLcode dohprobe(struct Curl_easy *data,
+ struct dnsprobe *p, DNStype dnstype,
+ const char *host,
+ const char *url, CURLM *multi,
+ struct curl_slist *headers)
+{
+ struct Curl_easy *doh = NULL;
+ char *nurl = NULL;
+ CURLcode result = CURLE_OK;
+ timediff_t timeout_ms;
+ DOHcode d = doh_encode(host, dnstype, p->dohbuffer, sizeof(p->dohbuffer),
+ &p->dohlen);
+ if(d) {
+ failf(data, "Failed to encode DOH packet [%d]\n", d);
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ p->dnstype = dnstype;
+ p->serverdoh.memory = NULL;
+ /* the memory will be grown as needed by realloc in the doh_write_cb
+ function */
+ p->serverdoh.size = 0;
+
+ /* Note: this is code for sending the DoH request with GET but there's still
+ no logic that actually enables this. We should either add that ability or
+ yank out the GET code. Discuss! */
+ if(data->set.doh_get) {
+ char *b64;
+ size_t b64len;
+ result = Curl_base64url_encode(data, (char *)p->dohbuffer, p->dohlen,
+ &b64, &b64len);
+ if(result)
+ goto error;
+ nurl = aprintf("%s?dns=%s", url, b64);
+ free(b64);
+ if(!nurl) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto error;
+ }
+ url = nurl;
+ }
+
+ timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+ /* Curl_open() is the internal version of curl_easy_init() */
+ result = Curl_open(&doh);
+ if(!result) {
+ ERROR_CHECK_SETOPT(CURLOPT_URL, url);
+ ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_write_cb);
+ ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, (void *)&p->serverdoh);
+ if(!data->set.doh_get) {
+ ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, &p->dohbuffer[0]);
+ ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, p->dohlen);
+ }
+ ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, headers);
+ ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
+#ifndef CURLDEBUG
+ /* enforce HTTPS if not debug */
+ ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
+#endif
+ ERROR_CHECK_SETOPT(CURLOPT_TIMEOUT_MS, (long)timeout_ms);
+ ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L);
+ doh->set.fmultidone = Curl_doh_done;
+ doh->set.dohfor = data; /* identify for which transfer this is done */
+ p->easy = doh;
+
+ /* add this transfer to the multi handle */
+ if(curl_multi_add_handle(multi, doh))
+ goto error;
+ }
+ else
+ goto error;
+ free(nurl);
+ return CURLE_OK;
+
+ error:
+ free(nurl);
+ Curl_close(doh);
+ return result;
+}
+
+/*
+ * Curl_doh() resolves a name using DOH. It resolves a name and returns a
+ * 'Curl_addrinfo *' with the address information.
+ */
+
+Curl_addrinfo *Curl_doh(struct connectdata *conn,
+ const char *hostname,
+ int port,
+ int *waitp)
+{
+ struct Curl_easy *data = conn->data;
+ CURLcode result = CURLE_OK;
+ *waitp = TRUE; /* this never returns synchronously */
+ (void)conn;
+ (void)hostname;
+ (void)port;
+
+ /* start clean, consider allocating this struct on demand */
+ memset(&data->req.doh, 0, sizeof(struct dohdata));
+
+ data->req.doh.host = hostname;
+ data->req.doh.port = port;
+ data->req.doh.headers =
+ curl_slist_append(NULL,
+ "Content-Type: application/dns-message");
+ if(!data->req.doh.headers)
+ goto error;
+
+ if(conn->ip_version != CURL_IPRESOLVE_V6) {
+ /* create IPv4 DOH request */
+ result = dohprobe(data, &data->req.doh.probe[0], DNS_TYPE_A,
+ hostname, data->set.str[STRING_DOH],
+ data->multi, data->req.doh.headers);
+ if(result)
+ goto error;
+ data->req.doh.pending++;
+ }
+
+ if(conn->ip_version != CURL_IPRESOLVE_V4) {
+ /* create IPv6 DOH request */
+ result = dohprobe(data, &data->req.doh.probe[1], DNS_TYPE_AAAA,
+ hostname, data->set.str[STRING_DOH],
+ data->multi, data->req.doh.headers);
+ if(result)
+ goto error;
+ data->req.doh.pending++;
+ }
+ return NULL;
+
+ error:
+ curl_slist_free_all(data->req.doh.headers);
+ data->req.doh.headers = NULL;
+ curl_easy_cleanup(data->req.doh.probe[0].easy);
+ data->req.doh.probe[0].easy = NULL;
+ curl_easy_cleanup(data->req.doh.probe[1].easy);
+ data->req.doh.probe[1].easy = NULL;
+ return NULL;
+}
+
+static DOHcode skipqname(unsigned char *doh, size_t dohlen,
+ unsigned int *indexp)
+{
+ unsigned char length;
+ do {
+ if(dohlen < (*indexp + 1))
+ return DOH_DNS_OUT_OF_RANGE;
+ length = doh[*indexp];
+ if((length & 0xc0) == 0xc0) {
+ /* name pointer, advance over it and be done */
+ if(dohlen < (*indexp + 2))
+ return DOH_DNS_OUT_OF_RANGE;
+ *indexp += 2;
+ break;
+ }
+ if(length & 0xc0)
+ return DOH_DNS_BAD_LABEL;
+ if(dohlen < (*indexp + 1 + length))
+ return DOH_DNS_OUT_OF_RANGE;
+ *indexp += 1 + length;
+ } while(length);
+ return DOH_OK;
+}
+
+static unsigned short get16bit(unsigned char *doh, int index)
+{
+ return (unsigned short)((doh[index] << 8) | doh[index + 1]);
+}
+
+static unsigned int get32bit(unsigned char *doh, int index)
+{
+ return (doh[index] << 24) | (doh[index + 1] << 16) |
+ (doh[index + 2] << 8) | doh[index + 3];
+}
+
+struct addr6 {
+ unsigned char byte[16];
+};
+
+struct cnamestore {
+ size_t len; /* length of cname */
+ char *alloc; /* allocated pointer */
+ size_t allocsize; /* allocated size */
+};
+
+struct dohaddr {
+ int type;
+ union {
+ unsigned int v4;
+ struct addr6 v6;
+ } ip;
+};
+
+struct dohentry {
+ unsigned int ttl;
+ int numaddr;
+ struct dohaddr addr[MAX_ADDR];
+ int numcname;
+ struct cnamestore cname[MAX_ADDR];
+};
+
+static DOHcode store_a(unsigned char *doh, int index, struct dohentry *d)
+{
+ /* silently ignore addresses over the limit */
+ if(d->numaddr < MAX_ADDR) {
+ struct dohaddr *a = &d->addr[d->numaddr];
+ a->type = DNS_TYPE_A;
+ a->ip.v4 = ntohl(get32bit(doh, index));
+ d->numaddr++;
+ }
+ return DOH_OK;
+}
+
+static DOHcode store_aaaa(unsigned char *doh, int index, struct dohentry *d)
+{
+ /* silently ignore addresses over the limit */
+ if(d->numaddr < MAX_ADDR) {
+ struct dohaddr *a = &d->addr[d->numaddr];
+ struct addr6 *inet6p = &a->ip.v6;
+ a->type = DNS_TYPE_AAAA;
+ memcpy(inet6p, &doh[index], 16);
+ d->numaddr++;
+ }
+ return DOH_OK;
+}
+
+static DOHcode cnameappend(struct cnamestore *c,
+ unsigned char *src,
+ size_t len)
+{
+ if(!c->alloc) {
+ c->allocsize = len + 1;
+ c->alloc = malloc(c->allocsize);
+ if(!c->alloc)
+ return DOH_OUT_OF_MEM;
+ }
+ else if(c->allocsize < (c->allocsize + len + 1)) {
+ char *ptr;
+ c->allocsize += len + 1;
+ ptr = realloc(c->alloc, c->allocsize);
+ if(!ptr) {
+ free(c->alloc);
+ return DOH_OUT_OF_MEM;
+ }
+ c->alloc = ptr;
+ }
+ memcpy(&c->alloc[c->len], src, len);
+ c->len += len;
+ c->alloc[c->len] = 0; /* keep it zero terminated */
+ return DOH_OK;
+}
+
+static DOHcode store_cname(unsigned char *doh,
+ size_t dohlen,
+ unsigned int index,
+ struct dohentry *d)
+{
+ struct cnamestore *c = &d->cname[d->numcname++];
+ unsigned int loop = 128; /* a valid DNS name can never loop this much */
+ unsigned char length;
+ do {
+ if(index >= dohlen)
+ return DOH_DNS_OUT_OF_RANGE;
+ length = doh[index];
+ if((length & 0xc0) == 0xc0) {
+ int newpos;
+ /* name pointer, get the new offset (14 bits) */
+ if((index + 1) >= dohlen)
+ return DOH_DNS_OUT_OF_RANGE;
+
+ /* move to the the new index */
+ newpos = (length & 0x3f) << 8 | doh[index + 1];
+ index = newpos;
+ continue;
+ }
+ else if(length & 0xc0)
+ return DOH_DNS_BAD_LABEL; /* bad input */
+ else
+ index++;
+
+ if(length) {
+ DOHcode rc;
+ if(c->len) {
+ rc = cnameappend(c, (unsigned char *)".", 1);
+ if(rc)
+ return rc;
+ }
+ if((index + length) > dohlen)
+ return DOH_DNS_BAD_LABEL;
+
+ rc = cnameappend(c, &doh[index], length);
+ if(rc)
+ return rc;
+ index += length;
+ }
+ } while(length && --loop);
+
+ if(!loop)
+ return DOH_DNS_LABEL_LOOP;
+ return DOH_OK;
+}
+
+static DOHcode rdata(unsigned char *doh,
+ size_t dohlen,
+ unsigned short rdlength,
+ unsigned short type,
+ int index,
+ struct dohentry *d)
+{
+ /* RDATA
+ - A (TYPE 1): 4 bytes
+ - AAAA (TYPE 28): 16 bytes
+ - NS (TYPE 2): N bytes */
+ DOHcode rc;
+
+ switch(type) {
+ case DNS_TYPE_A:
+ if(rdlength != 4)
+ return DOH_DNS_RDATA_LEN;
+ rc = store_a(doh, index, d);
+ if(rc)
+ return rc;
+ break;
+ case DNS_TYPE_AAAA:
+ if(rdlength != 16)
+ return DOH_DNS_RDATA_LEN;
+ rc = store_aaaa(doh, index, d);
+ if(rc)
+ return rc;
+ break;
+ case DNS_TYPE_CNAME:
+ rc = store_cname(doh, dohlen, index, d);
+ if(rc)
+ return rc;
+ break;
+ default:
+ /* unsupported type, just skip it */
+ break;
+ }
+ return DOH_OK;
+}
+
+static DOHcode doh_decode(unsigned char *doh,
+ size_t dohlen,
+ DNStype dnstype,
+ struct dohentry *d)
+{
+ unsigned char rcode;
+ unsigned short qdcount;
+ unsigned short ancount;
+ unsigned short type = 0;
+ unsigned short class;
+ unsigned short rdlength;
+ unsigned short nscount;
+ unsigned short arcount;
+ unsigned int index = 12;
+ DOHcode rc;
+
+ d->ttl = INT_MAX;
+
+ if(dohlen < 12)
+ return DOH_TOO_SMALL_BUFFER; /* too small */
+ if(doh[0] || doh[1])
+ return DOH_DNS_BAD_ID; /* bad ID */
+ rcode = doh[3] & 0x0f;
+ if(rcode)
+ return DOH_DNS_BAD_RCODE; /* bad rcode */
+
+ qdcount = get16bit(doh, 4);
+ while(qdcount) {
+ rc = skipqname(doh, dohlen, &index);
+ if(rc)
+ return rc; /* bad qname */
+ if(dohlen < (index + 4))
+ return DOH_DNS_OUT_OF_RANGE;
+ index += 4; /* skip question's type and class */
+ qdcount--;
+ }
+
+ ancount = get16bit(doh, 6);
+ while(ancount) {
+ unsigned int ttl;
+
+ rc = skipqname(doh, dohlen, &index);
+ if(rc)
+ return rc; /* bad qname */
+
+ if(dohlen < (index + 2))
+ return DOH_DNS_OUT_OF_RANGE;
+
+ type = get16bit(doh, index);
+ if((type != DNS_TYPE_CNAME) && (type != dnstype))
+ /* Not the same type as was asked for nor CNAME */
+ return DOH_DNS_UNEXPECTED_TYPE;
+ index += 2;
+
+ if(dohlen < (index + 2))
+ return DOH_DNS_OUT_OF_RANGE;
+ class = get16bit(doh, index);
+ if(DNS_CLASS_IN != class)
+ return DOH_DNS_UNEXPECTED_CLASS; /* unsupported */
+ index += 2;
+
+ if(dohlen < (index + 4))
+ return DOH_DNS_OUT_OF_RANGE;
+
+ ttl = get32bit(doh, index);
+ if(ttl < d->ttl)
+ d->ttl = ttl;
+ index += 4;
+
+ if(dohlen < (index + 2))
+ return DOH_DNS_OUT_OF_RANGE;
+
+ rdlength = get16bit(doh, index);
+ index += 2;
+ if(dohlen < (index + rdlength))
+ return DOH_DNS_OUT_OF_RANGE;
+
+ rc = rdata(doh, dohlen, rdlength, type, index, d);
+ if(rc)
+ return rc; /* bad rdata */
+ index += rdlength;
+ ancount--;
+ }
+
+ nscount = get16bit(doh, 8);
+ while(nscount) {
+ rc = skipqname(doh, dohlen, &index);
+ if(rc)
+ return rc; /* bad qname */
+
+ if(dohlen < (index + 8))
+ return DOH_DNS_OUT_OF_RANGE;
+
+ index += 2 + 2 + 4; /* type, class and ttl */
+
+ if(dohlen < (index + 2))
+ return DOH_DNS_OUT_OF_RANGE;
+
+ rdlength = get16bit(doh, index);
+ index += 2;
+ if(dohlen < (index + rdlength))
+ return DOH_DNS_OUT_OF_RANGE;
+ index += rdlength;
+ nscount--;
+ }
+
+ arcount = get16bit(doh, 10);
+ while(arcount) {
+ rc = skipqname(doh, dohlen, &index);
+ if(rc)
+ return rc; /* bad qname */
+
+ if(dohlen < (index + 8))
+ return DOH_DNS_OUT_OF_RANGE;
+
+ index += 2 + 2 + 4; /* type, class and ttl */
+
+ if(dohlen < (index + 2))
+ return DOH_DNS_OUT_OF_RANGE;
+
+ rdlength = get16bit(doh, index);
+ index += 2;
+ if(dohlen < (index + rdlength))
+ return DOH_DNS_OUT_OF_RANGE;
+ index += rdlength;
+ arcount--;
+ }
+
+ if(index != dohlen)
+ return DOH_DNS_MALFORMAT; /* something is wrong */
+
+ if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr)
+ /* nothing stored! */
+ return DOH_NO_CONTENT;
+
+ return DOH_OK; /* ok */
+}
+
+static void showdoh(struct Curl_easy *data,
+ struct dohentry *d)
+{
+ int i;
+ infof(data, "TTL: %u seconds\n", d->ttl);
+ for(i = 0; i < d->numaddr; i++) {
+ struct dohaddr *a = &d->addr[i];
+ if(a->type == DNS_TYPE_A) {
+ infof(data, "DOH A: %d.%d.%d.%d\n",
+ a->ip.v4 & 0xff, (a->ip.v4>>8) & 0xff,
+ (a->ip.v4>>16) & 0xff, a->ip.v4 >>24);
+ }
+ else if(a->type == DNS_TYPE_AAAA) {
+ int j;
+ char buffer[128];
+ char *ptr;
+ size_t len;
+ snprintf(buffer, 128, "DOH AAAA: ");
+ ptr = &buffer[10];
+ len = 118;
+ for(j = 0; j < 16; j += 2) {
+ size_t l;
+ snprintf(ptr, len, "%s%02x%02x", j?":":"", d->addr[i].ip.v6.byte[j],
+ d->addr[i].ip.v6.byte[j + 1]);
+ l = strlen(ptr);
+ len -= l;
+ ptr += l;
+ }
+ infof(data, "%s\n", buffer);
+ }
+ }
+ for(i = 0; i < d->numcname; i++) {
+ infof(data, "CNAME: %s\n", d->cname[i].alloc);
+ }
+}
+
+/*
+ * doh2ai()
+ *
+ * This function returns a pointer to the first element of a newly allocated
+ * Curl_addrinfo struct linked list filled with the data from a set of DOH
+ * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for
+ * a IPv6 stack, but usable also for IPv4, all hosts and environments.
+ *
+ * The memory allocated by this function *MUST* be free'd later on calling
+ * Curl_freeaddrinfo(). For each successful call to this function there
+ * must be an associated call later to Curl_freeaddrinfo().
+ */
+
+static Curl_addrinfo *
+doh2ai(const struct dohentry *de, const char *hostname, int port)
+{
+ Curl_addrinfo *ai;
+ Curl_addrinfo *prevai = NULL;
+ Curl_addrinfo *firstai = NULL;
+ struct sockaddr_in *addr;
+#ifdef ENABLE_IPV6
+ struct sockaddr_in6 *addr6;
+#endif
+ CURLcode result = CURLE_OK;
+ int i;
+
+ if(!de)
+ /* no input == no output! */
+ return NULL;
+
+ for(i = 0; i < de->numaddr; i++) {
+ size_t ss_size;
+ CURL_SA_FAMILY_T addrtype;
+ if(de->addr[i].type == DNS_TYPE_AAAA) {
+#ifndef ENABLE_IPV6
+ /* we can't handle IPv6 addresses */
+ continue;
+#else
+ ss_size = sizeof(struct sockaddr_in6);
+ addrtype = AF_INET6;
+#endif
+ }
+ else {
+ ss_size = sizeof(struct sockaddr_in);
+ addrtype = AF_INET;
+ }
+
+ ai = calloc(1, sizeof(Curl_addrinfo));
+ if(!ai) {
+ result = CURLE_OUT_OF_MEMORY;
+ break;
+ }
+ ai->ai_canonname = strdup(hostname);
+ if(!ai->ai_canonname) {
+ result = CURLE_OUT_OF_MEMORY;
+ free(ai);
+ break;
+ }
+ ai->ai_addr = calloc(1, ss_size);
+ if(!ai->ai_addr) {
+ result = CURLE_OUT_OF_MEMORY;
+ free(ai->ai_canonname);
+ free(ai);
+ break;
+ }
+
+ if(!firstai)
+ /* store the pointer we want to return from this function */
+ firstai = ai;
+
+ if(prevai)
+ /* make the previous entry point to this */
+ prevai->ai_next = ai;
+
+ ai->ai_family = addrtype;
+
+ /* we return all names as STREAM, so when using this address for TFTP
+ the type must be ignored and conn->socktype be used instead! */
+ ai->ai_socktype = SOCK_STREAM;
+
+ ai->ai_addrlen = (curl_socklen_t)ss_size;
+
+ /* leave the rest of the struct filled with zero */
+
+ switch(ai->ai_family) {
+ case AF_INET:
+ addr = (void *)ai->ai_addr; /* storage area for this info */
+
+ memcpy(&addr->sin_addr, &de->addr[i].ip.v4, sizeof(struct in_addr));
+ addr->sin_family = (CURL_SA_FAMILY_T)addrtype;
+ addr->sin_port = htons((unsigned short)port);
+ break;
+
+#ifdef ENABLE_IPV6
+ case AF_INET6:
+ addr6 = (void *)ai->ai_addr; /* storage area for this info */
+
+ memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr));
+ addr6->sin6_family = (CURL_SA_FAMILY_T)addrtype;
+ addr6->sin6_port = htons((unsigned short)port);
+ break;
+#endif
+ }
+
+ prevai = ai;
+ }
+
+ if(result) {
+ Curl_freeaddrinfo(firstai);
+ firstai = NULL;
+ }
+
+ return firstai;
+}
+
+static const char *type2name(DNStype dnstype)
+{
+ return (dnstype == DNS_TYPE_A)?"A":"AAAA";
+}
+
+static void de_cleanup(struct dohentry *d)
+{
+ int i = 0;
+ for(i = 0; i < d->numcname; i++) {
+ free(d->cname[i].alloc);
+ }
+}
+
+CURLcode Curl_doh_is_resolved(struct connectdata *conn,
+ struct Curl_dns_entry **dnsp)
+{
+ struct Curl_easy *data = conn->data;
+ *dnsp = NULL; /* defaults to no response */
+
+ if(!data->req.doh.probe[0].easy && !data->req.doh.probe[1].easy) {
+ failf(data, "Could not DOH-resolve: %s", conn->async.hostname);
+ return conn->bits.proxy?CURLE_COULDNT_RESOLVE_PROXY:
+ CURLE_COULDNT_RESOLVE_HOST;
+ }
+ else if(!data->req.doh.pending) {
+ DOHcode rc;
+ DOHcode rc2;
+ struct dohentry de;
+ struct Curl_dns_entry *dns;
+ struct Curl_addrinfo *ai;
+ /* remove DOH handles from multi handle and close them */
+ curl_multi_remove_handle(data->multi, data->req.doh.probe[0].easy);
+ Curl_close(data->req.doh.probe[0].easy);
+ curl_multi_remove_handle(data->multi, data->req.doh.probe[1].easy);
+ Curl_close(data->req.doh.probe[1].easy);
+
+ /* parse the responses, create the struct and return it! */
+ memset(&de, 0, sizeof(de));
+ rc = doh_decode(data->req.doh.probe[0].serverdoh.memory,
+ data->req.doh.probe[0].serverdoh.size,
+ data->req.doh.probe[0].dnstype,
+ &de);
+ free(data->req.doh.probe[0].serverdoh.memory);
+ if(rc) {
+ infof(data, "DOH: %s type %s for %s\n", doh_strerror(rc),
+ type2name(data->req.doh.probe[0].dnstype),
+ data->req.doh.host);
+ }
+ rc2 = doh_decode(data->req.doh.probe[1].serverdoh.memory,
+ data->req.doh.probe[1].serverdoh.size,
+ data->req.doh.probe[1].dnstype,
+ &de);
+ free(data->req.doh.probe[1].serverdoh.memory);
+ if(rc2) {
+ infof(data, "DOG: %s type %s for %s\n", doh_strerror(rc2),
+ type2name(data->req.doh.probe[1].dnstype),
+ data->req.doh.host);
+ }
+ if(!rc || !rc2) {
+ infof(data, "DOH Host name: %s\n", data->req.doh.host);
+ showdoh(data, &de);
+
+ ai = doh2ai(&de, data->req.doh.host, data->req.doh.port);
+ if(!ai) {
+ de_cleanup(&de);
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ if(data->share)
+ Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
+
+ /* we got a response, store it in the cache */
+ dns = Curl_cache_addr(data, ai, data->req.doh.host, data->req.doh.port);
+
+ if(data->share)
+ Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
+
+ de_cleanup(&de);
+ if(!dns)
+ /* returned failure, bail out nicely */
+ Curl_freeaddrinfo(ai);
+ else {
+ conn->async.dns = dns;
+ *dnsp = dns;
+ return CURLE_OK;
+ }
+ }
+ de_cleanup(&de);
+
+ return CURLE_COULDNT_RESOLVE_HOST;
+ }
+
+ return CURLE_OK;
+}
diff --git a/lib/doh.h b/lib/doh.h
new file mode 100644
index 000000000..e9c017c72
--- /dev/null
+++ b/lib/doh.h
@@ -0,0 +1,44 @@
+#ifndef HEADER_CURL_DOH_H
+#define HEADER_CURL_DOH_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "urldata.h"
+#include "curl_addrinfo.h"
+
+/*
+ * Curl_doh() resolve a name using DoH (DNS-over-HTTPS). It resolves a name
+ * and returns a 'Curl_addrinfo *' with the address information.
+ */
+
+Curl_addrinfo *Curl_doh(struct connectdata *conn,
+ const char *hostname,
+ int port,
+ int *waitp);
+
+CURLcode Curl_doh_is_resolved(struct connectdata *conn,
+ struct Curl_dns_entry **dns);
+
+int Curl_doh_getsock(struct connectdata *conn, curl_socket_t *socks,
+ int numsocks);
+
+#endif
diff --git a/lib/hostasyn.c b/lib/hostasyn.c
index e7b399ed2..6ff60ba61 100644
--- a/lib/hostasyn.c
+++ b/lib/hostasyn.c
@@ -111,31 +111,6 @@ CURLcode Curl_addrinfo_callback(struct connectdata *conn,
return result;
}
-/* Call this function after Curl_connect() has returned async=TRUE and
- then a successful name resolve has been received.
-
- Note: this function disconnects and frees the conn data in case of
- resolve failure */
-CURLcode Curl_async_resolved(struct connectdata *conn,
- bool *protocol_done)
-{
- CURLcode result;
-
- if(conn->async.dns) {
- conn->dns_entry = conn->async.dns;
- conn->async.dns = NULL;
- }
-
- result = Curl_setup_conn(conn, protocol_done);
-
- if(result)
- /* We're not allowed to return failure with memory left allocated
- in the connectdata struct, free those here */
- Curl_disconnect(conn->data, conn, TRUE); /* close the connection */
-
- return result;
-}
-
/*
* Curl_getaddrinfo() is the generic low-level name resolve API within this
* source file. There are several versions of this function - for different
diff --git a/lib/hostip.c b/lib/hostip.c
index bc20f7153..8d46c1d82 100644
--- a/lib/hostip.c
+++ b/lib/hostip.c
@@ -60,6 +60,7 @@
#include "url.h"
#include "inet_ntop.h"
#include "multiif.h"
+#include "doh.h"
#include "warnless.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -565,23 +566,27 @@ int Curl_resolv(struct connectdata *conn,
return CURLRESOLV_ERROR;
}
- /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a
- non-zero value indicating that we need to wait for the response to the
- resolve call */
- addr = Curl_getaddrinfo(conn,
+ if(data->set.doh) {
+ addr = Curl_doh(conn, hostname, port, &respwait);
+ }
+ else {
+ /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a
+ non-zero value indicating that we need to wait for the response to the
+ resolve call */
+ addr = Curl_getaddrinfo(conn,
#ifdef DEBUGBUILD
- (data->set.str[STRING_DEVICE]
- && !strcmp(data->set.str[STRING_DEVICE],
- "LocalHost"))?"localhost":
+ (data->set.str[STRING_DEVICE]
+ && !strcmp(data->set.str[STRING_DEVICE],
+ "LocalHost"))?"localhost":
#endif
- hostname, port, &respwait);
-
+ hostname, port, &respwait);
+ }
if(!addr) {
if(respwait) {
/* the response to our resolve call will come asynchronously at
a later time, good or bad */
/* First, check that we haven't received the info by now */
- result = Curl_resolver_is_resolved(conn, &dns);
+ result = Curl_resolv_check(conn, &dns);
if(result) /* error detected */
return CURLRESOLV_ERROR;
if(dns)
@@ -1053,3 +1058,55 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data)
return CURLE_OK;
}
+
+CURLcode Curl_resolv_check(struct connectdata *conn,
+ struct Curl_dns_entry **dns)
+{
+ if(conn->data->set.doh)
+ return Curl_doh_is_resolved(conn, dns);
+ return Curl_resolver_is_resolved(conn, dns);
+}
+
+int Curl_resolv_getsock(struct connectdata *conn,
+ curl_socket_t *socks,
+ int numsocks)
+{
+#ifdef CURLRES_ASYNCH
+ if(conn->data->set.doh)
+ /* nothing to wait for during DOH resolve, those handles have their own
+ sockets */
+ return GETSOCK_BLANK;
+ return Curl_resolver_getsock(conn, socks, numsocks);
+#else
+ (void)conn;
+ (void)socks;
+ (void)numsocks;
+ return GETSOCK_BLANK;
+#endif
+}
+
+/* Call this function after Curl_connect() has returned async=TRUE and
+ then a successful name resolve has been received.
+
+ Note: this function disconnects and frees the conn data in case of
+ resolve failure */
+CURLcode Curl_once_resolved(struct connectdata *conn,
+ bool *protocol_done)
+{
+ CURLcode result;
+
+ if(conn->async.dns) {
+ conn->dns_entry = conn->async.dns;
+ conn->async.dns = NULL;
+ }
+
+ result = Curl_setup_conn(conn, protocol_done);
+
+ if(result)
+ /* We're not allowed to return failure with memory left allocated
+ in the connectdata struct, free those here */
+ Curl_disconnect(conn->data, conn, TRUE); /* close the connection */
+
+ return result;
+}
+
diff --git a/lib/hostip.h b/lib/hostip.h
index 1de4bee8d..29fd1ef7c 100644
--- a/lib/hostip.h
+++ b/lib/hostip.h
@@ -145,12 +145,7 @@ int curl_dogetnameinfo(GETNAMEINFO_QUAL_ARG1 GETNAMEINFO_TYPE_ARG1 sa,
/* IPv4 threadsafe resolve function used for synch and asynch builds */
Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port);
-CURLcode Curl_async_resolved(struct connectdata *conn,
- bool *protocol_connect);
-
-#ifndef CURLRES_ASYNCH
-#define Curl_async_resolved(x,y) CURLE_OK
-#endif
+CURLcode Curl_once_resolved(struct connectdata *conn, bool *protocol_connect);
/*
* Curl_addrinfo_callback() is used when we build with any asynch specialty.
@@ -258,4 +253,10 @@ void Curl_hostcache_destroy(struct Curl_easy *data);
*/
CURLcode Curl_loadhostpairs(struct Curl_easy *data);
+CURLcode Curl_resolv_check(struct connectdata *conn,
+ struct Curl_dns_entry **dns);
+int Curl_resolv_getsock(struct connectdata *conn,
+ curl_socket_t *socks,
+ int numsocks);
+
#endif /* HEADER_CURL_HOSTIP_H */
diff --git a/lib/multi.c b/lib/multi.c
index 9d5afc093..066adfed3 100644
--- a/lib/multi.c
+++ b/lib/multi.c
@@ -906,7 +906,7 @@ static int multi_getsock(struct Curl_easy *data,
return 0;
case CURLM_STATE_WAITRESOLVE:
- return Curl_resolver_getsock(data->easy_conn, socks, numsocks);
+ return Curl_resolv_getsock(data->easy_conn, socks, numsocks);
case CURLM_STATE_PROTOCONNECT:
case CURLM_STATE_SENDPROTOCONNECT:
@@ -1236,7 +1236,7 @@ static CURLcode multi_reconnect_request(struct connectdata **connp)
return result;
/* Resolved, continue with the connection */
- result = Curl_async_resolved(conn, &protocol_done);
+ result = Curl_once_resolved(conn, &protocol_done);
if(result)
return result;
}
@@ -1512,7 +1512,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
}
if(!dns)
- result = Curl_resolver_is_resolved(data->easy_conn, &dns);
+ result = Curl_resolv_check(data->easy_conn, &dns);
/* Update sockets here, because the socket(s) may have been
closed and the application thus needs to be told, even if it
@@ -1525,10 +1525,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
if(dns) {
/* Perform the next step in the connection phase, and then move on
to the WAITCONNECT state */
- result = Curl_async_resolved(data->easy_conn, &protocol_connect);
+ result = Curl_once_resolved(data->easy_conn, &protocol_connect);
if(result)
- /* if Curl_async_resolved() returns failure, the connection struct
+ /* if Curl_once_resolved() returns failure, the connection struct
is already freed and gone */
data->easy_conn = NULL; /* no more connection */
else {
@@ -2132,15 +2132,21 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
}
if(CURLM_STATE_COMPLETED == data->mstate) {
- /* now fill in the Curl_message with this info */
- msg = &data->msg;
+ if(data->set.fmultidone) {
+ /* signal via callback instead */
+ data->set.fmultidone(data, result);
+ }
+ else {
+ /* now fill in the Curl_message with this info */
+ msg = &data->msg;
- msg->extmsg.msg = CURLMSG_DONE;
- msg->extmsg.easy_handle = data;
- msg->extmsg.data.result = result;
+ msg->extmsg.msg = CURLMSG_DONE;
+ msg->extmsg.easy_handle = data;
+ msg->extmsg.data.result = result;
- rc = multi_addmsg(multi, msg);
- DEBUGASSERT(!data->easy_conn);
+ rc = multi_addmsg(multi, msg);
+ DEBUGASSERT(!data->easy_conn);
+ }
multistate(data, CURLM_STATE_MSGSENT);
}
} while((rc == CURLM_CALL_MULTI_PERFORM) || multi_ischanged(multi, FALSE));
diff --git a/lib/setopt.c b/lib/setopt.c
index 66636a2da..475a33798 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -2603,6 +2603,11 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option,
data->set.disallow_username_in_url =
(0 != va_arg(param, long)) ? TRUE : FALSE;
break;
+ case CURLOPT_DOH_URL:
+ result = Curl_setstropt(&data->set.str[STRING_DOH],
+ va_arg(param, char *));
+ data->set.doh = data->set.str[STRING_DOH]?TRUE:FALSE;
+ break;
default:
/* unknown tag and its companion, just ignore: */
result = CURLE_UNKNOWN_OPTION;
diff --git a/lib/urldata.h b/lib/urldata.h
index add77e09f..769ab4fa4 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -476,7 +476,6 @@ struct hostname {
#define KEEP_SENDBITS (KEEP_SEND | KEEP_SEND_HOLD | KEEP_SEND_PAUSE)
-#ifdef CURLRES_ASYNCH
struct Curl_async {
char *hostname;
int port;
@@ -485,7 +484,6 @@ struct Curl_async {
int status; /* if done is TRUE, this is the status from the callback */
void *os_specific; /* 'struct thread_data' for Windows */
};
-#endif
#define FIRSTSOCKET 0
#define SECONDARYSOCKET 1
@@ -511,6 +509,28 @@ enum upgrade101 {
UPGR101_WORKING /* talking upgraded protocol */
};
+struct dohresponse {
+ unsigned char *memory;
+ size_t size;
+};
+
+/* one of these for each DoH request */
+struct dnsprobe {
+ CURL *easy;
+ int dnstype;
+ unsigned char dohbuffer[512];
+ size_t dohlen;
+ struct dohresponse serverdoh;
+};
+
+struct dohdata {
+ struct curl_slist *headers;
+ struct dnsprobe probe[2];
+ unsigned int pending; /* still outstanding requests */
+ const char *host;
+ int port;
+};
+
/*
* Request specific data in the easy handle (Curl_easy). Previously,
* these members were on the connectdata struct but since a conn struct may
@@ -606,6 +626,7 @@ struct SingleRequest {
void *protop; /* Allocated protocol-specific data. Each protocol
handler makes sure this points to data it needs. */
+ struct dohdata doh; /* DoH specific data for this request */
};
/*
@@ -969,11 +990,8 @@ struct connectdata {
#endif
char syserr_buf [256]; /* buffer for Curl_strerror() */
-
-#ifdef CURLRES_ASYNCH
/* data used for the asynch name resolve callback */
struct Curl_async async;
-#endif
/* These three are used for chunked-encoding trailer support */
char *trailer; /* allocated buffer to store trailer in */
@@ -1442,6 +1460,7 @@ enum dupstring {
STRING_UNIX_SOCKET_PATH, /* path to Unix socket, if used */
#endif
STRING_TARGET, /* CURLOPT_REQUEST_TARGET */
+ STRING_DOH, /* CURLOPT_DOH_URL */
/* -- end of zero-terminated strings -- */
STRING_LASTZEROTERMINATED,
@@ -1453,6 +1472,11 @@ enum dupstring {
STRING_LAST /* not used, just an end-of-list marker */
};
+/* callback that gets called when this easy handle is completed within a multi
+ handle. Only used for internally created transfers, like for example
+ DoH. */
+typedef int (*multidone_func)(struct Curl_easy *easy, CURLcode result);
+
struct UserDefined {
FILE *err; /* the stderr user data goes here */
void *debugdata; /* the data that will be passed to fdebug */
@@ -1688,6 +1712,10 @@ struct UserDefined {
before resolver start */
void *resolver_start_client; /* pointer to pass to resolver start callback */
bool disallow_username_in_url; /* disallow username in url */
+ bool doh; /* DNS-over-HTTPS enabled */
+ bool doh_get; /* use GET for DoH requests, instead of POST */
+ multidone_func fmultidone;
+ struct Curl_easy *dohfor; /* this is a DoH request for that transfer */
};
struct Names {