diff --git a/bin/dig/dig.c b/bin/dig/dig.c index 76c491f073..c4f75c4fdd 100644 --- a/bin/dig/dig.c +++ b/bin/dig/dig.c @@ -228,6 +228,10 @@ help(void) { "SERVFAIL)\n" " +[no]header-only (Send query without a " "question section)\n" + " +[no]https[=###] (DNS over HTTPS mode) " + "[/]\n" + " +[no]https-get (Use GET instead of " + "default POST method\n" " +[no]identify (ID responders in short " "answers)\n" #ifdef HAVE_LIBIDN2 @@ -348,12 +352,18 @@ received(unsigned int bytes, isc_sockaddr_t *from, dig_query_t *query) { } if (query->lookup->tls_mode) { proto = "TLS"; + } else if (query->lookup->https_mode) { + if (query->lookup->http_plain) { + proto = "HTTP"; + } else { + proto = "HTTPS"; + } } else if (query->lookup->tcp_mode) { proto = "TCP"; } else { proto = "UDP"; } - printf(";; SERVER: %s(%s) (%s)\n", fromtext, query->servname, + printf(";; SERVER: %s(%s) (%s)\n", fromtext, query->userarg, proto); time(&tnow); (void)localtime_r(&tnow, &tmnow); @@ -1066,6 +1076,17 @@ plus_option(char *option, bool is_batchfile, bool *need_clone, (_l >= sizeof(B) || strncasecmp(cmd, B, _l) != 0)) \ goto invalid_option; \ } while (0) +#define FULLCHECK6(A, B, C, D, E, F) \ + do { \ + size_t _l = strlen(cmd); \ + if ((_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) && \ + (_l >= sizeof(B) || strncasecmp(cmd, B, _l) != 0) && \ + (_l >= sizeof(C) || strncasecmp(cmd, C, _l) != 0) && \ + (_l >= sizeof(D) || strncasecmp(cmd, D, _l) != 0) && \ + (_l >= sizeof(E) || strncasecmp(cmd, E, _l) != 0) && \ + (_l >= sizeof(F) || strncasecmp(cmd, F, _l) != 0)) \ + goto invalid_option; \ + } while (0) switch (cmd[0]) { case 'a': @@ -1412,8 +1433,78 @@ plus_option(char *option, bool is_batchfile, bool *need_clone, lookup->servfail_stops = state; break; case 'h': - FULLCHECK("header-only"); - lookup->header_only = state; + switch (cmd[1]) { + case 'e': /* header-only */ + FULLCHECK("header-only"); + lookup->header_only = state; + break; + case 't': + FULLCHECK6("https", "https-get", "https-post", + "http-plain", "http-plain-get", + "http-plain-post"); + if (lookup->https_path != NULL) { + isc_mem_free(mctx, lookup->https_path); + lookup->https_path = NULL; + } + if (!state) { + lookup->https_mode = false; + break; + } + lookup->https_mode = true; + if (cmd[4] == '-') { + lookup->http_plain = true; + switch (cmd[10]) { + case '\0': + FULLCHECK("http-plain"); + break; + case '-': + switch (cmd[6]) { + case 'p': + FULLCHECK("https-plain-post"); + break; + case 'g': + FULLCHECK("https-plain-get"); + lookup->https_get = true; + break; + } + break; + default: + goto invalid_option; + } + } else { + switch (cmd[5]) { + case '\0': + FULLCHECK("https"); + break; + case '-': + switch (cmd[6]) { + case 'p': + FULLCHECK("https-post"); + break; + case 'g': + FULLCHECK("https-get"); + lookup->https_get = true; + break; + } + break; + default: + goto invalid_option; + } + } + if (!lookup->tcp_mode_set) { + lookup->tcp_mode = state; + } + if (value == NULL) { + lookup->https_path = isc_mem_strdup( + mctx, DEFAULT_HTTPS_PATH); + } else { + lookup->https_path = isc_mem_strdup(mctx, + value); + } + break; + default: + goto invalid_option; + } break; case 'i': switch (cmd[1]) { diff --git a/bin/dig/dig.rst b/bin/dig/dig.rst index 9df9ef2c1e..c28b51e4ad 100644 --- a/bin/dig/dig.rst +++ b/bin/dig/dig.rst @@ -349,11 +349,38 @@ abbreviation is unambiguous; for example, ``+cd`` is equivalent to default is to add a question section. The query type and query name are ignored when this is set. +``+[no]https[=value]`` + This option indicates whether to use DNS-over-HTTPS (DoH) when querying + name servers. When this option is in use, the port number defaults to 443. + The HTTP POST request mode is used when sending the query. + + If ``value`` is specified, it will be used as the HTTP endpoint in the + query URI; the default is ``/dns-query``. So, for example, ``dig + @example.com +https`` will use the URI ``https://example.com/dns-query``. + +``+[no]https-get[=value]`` + Similar to ``+https``, except that the HTTP GET request mode is used + when sending the query. + +``+[no]https-post[=value]`` + Same as ``+https``. + +``+[no]http-plain[=value]`` + Similar to ``+https``, except that HTTP queries will be sent over a + non-encrypted channel. When this option is in use, the port number + defaults to 80 and the HTTP request mode is POST. + +``+[no]http-plain-get[=value]`` + Similar to ``+http-plain``, except that the HTTP request mode is GET. + +``+[no]http-plain-post[=value]`` + Same as ``+http-plain``. + ``+[no]identify`` - This option shows [or does not show] the IP address and port number that supplied - the answer, when the ``+short`` option is enabled. If short form - answers are requested, the default is not to show the source address - and port number of the server that provided the answer. + This option shows [or does not show] the IP address and port number that + supplied the answer, when the ``+short`` option is enabled. If short + form answers are requested, the default is not to show the source + address and port number of the server that provided the answer. ``+[no]idnin`` This option processes [or does not process] IDN domain names on input. This requires @@ -519,8 +546,9 @@ abbreviation is unambiguous; for example, ``+cd`` is equivalent to 5 seconds. An attempt to set ``T`` to less than 1 is silently set to 1. ``+[no]tls`` - This option indicates whether to use DNS over TLS (DoT) when querying - name servers. + This option indicates whether to use DNS-over-TLS (DoT) when querying + name servers. When this option is in use, the port number defaults + to 853. ``+[no]topdown`` This feature is related to ``dig +sigchase``, which is obsolete and diff --git a/bin/dig/dighost.c b/bin/dig/dighost.c index ff72aea4a5..54bb63e5d1 100644 --- a/bin/dig/dighost.c +++ b/bin/dig/dighost.c @@ -603,102 +603,43 @@ clone_server_list(dig_serverlist_t src, dig_serverlist_t *dest) { dig_lookup_t * make_empty_lookup(void) { dig_lookup_t *looknew; +#ifdef HAVE_LIBIDN2 + bool idn_allowed = isatty(1) ? (getenv("IDN_DISABLE") == NULL) : false; +#endif /* HAVE_LIBIDN2 */ debug("make_empty_lookup()"); INSIST(!free_now); - looknew = isc_mem_allocate(mctx, sizeof(struct dig_lookup)); - looknew->pending = true; - looknew->textname[0] = 0; - looknew->cmdline[0] = 0; - looknew->rdtype = dns_rdatatype_a; - looknew->qrdtype = dns_rdatatype_a; - looknew->rdclass = dns_rdataclass_in; - looknew->rdtypeset = false; - looknew->rdclassset = false; - looknew->sendspace = NULL; - looknew->sendmsg = NULL; - looknew->name = NULL; - looknew->oname = NULL; - looknew->xfr_q = NULL; - looknew->current_query = NULL; - looknew->doing_xfr = false; - looknew->ixfr_serial = 0; - looknew->trace = false; - looknew->trace_root = false; - looknew->identify = false; - looknew->identify_previous_line = false; - looknew->ignore = false; - looknew->servfail_stops = true; - looknew->besteffort = true; - looknew->dns64prefix = false; - looknew->dnssec = false; - looknew->ednsflags = 0; - looknew->opcode = dns_opcode_query; - looknew->expire = false; - looknew->nsid = false; - looknew->tcp_keepalive = false; - looknew->padding = 0; - looknew->header_only = false; - looknew->sendcookie = false; - looknew->seenbadcookie = false; - looknew->badcookie = true; - looknew->multiline = false; - looknew->nottl = false; - looknew->noclass = false; - looknew->onesoa = false; - looknew->use_usec = false; - looknew->nocrypto = false; - looknew->ttlunits = false; - looknew->expandaaaa = false; - looknew->qr = false; + looknew = isc_mem_allocate(mctx, sizeof(*looknew)); + *looknew = (dig_lookup_t){ + .pending = true, + .rdtype = dns_rdatatype_a, + .qrdtype = dns_rdatatype_a, + .rdclass = dns_rdataclass_in, + .servfail_stops = true, + .besteffort = true, + .opcode = dns_opcode_query, + .badcookie = true, #ifdef HAVE_LIBIDN2 - looknew->idnin = isatty(1) ? (getenv("IDN_DISABLE") == NULL) : false; - looknew->idnout = looknew->idnin; -#else /* ifdef HAVE_LIBIDN2 */ - looknew->idnin = false; - looknew->idnout = false; + .idnin = idn_allowed, + .idnout = idn_allowed, #endif /* HAVE_LIBIDN2 */ - looknew->udpsize = -1; - looknew->edns = -1; - looknew->recurse = true; - looknew->aaonly = false; - looknew->adflag = false; - looknew->cdflag = false; - looknew->raflag = false; - looknew->tcflag = false; - looknew->print_unknown_format = false; - looknew->zflag = false; - looknew->setqid = false; - looknew->qid = 0; - looknew->ns_search_only = false; - looknew->origin = NULL; - looknew->tsigctx = NULL; - looknew->querysig = NULL; - looknew->retries = tries; - looknew->nsfound = 0; - looknew->tcp_mode = false; - looknew->tcp_mode_set = false; - looknew->tls_mode = false; - looknew->comments = true; - looknew->stats = true; - looknew->section_question = true; - looknew->section_answer = true; - looknew->section_authority = true; - looknew->section_additional = true; - looknew->new_search = false; - looknew->done_as_is = false; - looknew->need_search = false; - looknew->ecs_addr = NULL; - looknew->cookie = NULL; - looknew->ednsopts = NULL; - looknew->ednsoptscnt = 0; - looknew->ednsneg = true; - looknew->mapped = true; - looknew->dscp = -1; - looknew->rrcomments = 0; - looknew->eoferr = 0; + .udpsize = -1, + .edns = -1, + .recurse = true, + .retries = tries, + .comments = true, + .stats = true, + .section_question = true, + .section_answer = true, + .section_authority = true, + .section_additional = true, + .ednsneg = true, + .mapped = true, + .dscp = -1, + }; + dns_fixedname_init(&looknew->fdomain); ISC_LINK_INIT(looknew, link); ISC_LIST_INIT(looknew->q); @@ -787,6 +728,12 @@ clone_lookup(dig_lookup_t *lookold, bool servers) { looknew->nsid = lookold->nsid; looknew->tcp_keepalive = lookold->tcp_keepalive; looknew->header_only = lookold->header_only; + looknew->https_mode = lookold->https_mode; + if (lookold->https_path != NULL) { + looknew->https_path = isc_mem_strdup(mctx, lookold->https_path); + } + looknew->https_get = lookold->https_get; + looknew->http_plain = lookold->http_plain; looknew->sendcookie = lookold->sendcookie; looknew->seenbadcookie = lookold->seenbadcookie; looknew->badcookie = lookold->badcookie; @@ -1638,6 +1585,10 @@ _destroy_lookup(dig_lookup_t *lookup) { isc_mem_free(mctx, lookup->ednsopts); } + if (lookup->https_path) { + isc_mem_free(mctx, lookup->https_path); + } + isc_mem_free(mctx, lookup); } @@ -2760,7 +2711,20 @@ start_tcp(dig_query_t *query) { * For TLS connections, we want to override the default * port number. */ - port = port_set ? port : (query->lookup->tls_mode ? 853 : 53); + if (!port_set) { + if (query->lookup->tls_mode) { + port = 853; + } else if (query->lookup->https_mode && + !query->lookup->http_plain) { + port = 443; + } else if (query->lookup->https_mode) { + port = 80; + } else { + port = 53; + } + } + + debug("query->servname = %s\n", query->servname); result = get_address(query->servname, port, &query->sockaddr); if (result != ISC_R_SUCCESS) { @@ -2835,7 +2799,27 @@ start_tcp(dig_query_t *query) { (isc_nmiface_t *)&query->sockaddr, tcp_connected, query, local_timeout, 0, query->tlsctx); - check_result(result, "isc_nm_tcpdnsconnect"); + check_result(result, "isc_nm_tlsdnsconnect"); + } else if (query->lookup->https_mode) { + char uri[4096] = { 0 }; + snprintf(uri, sizeof(uri), "https://%s:%u%s", + query->userarg, (uint16_t)port, + query->lookup->https_path); + + if (!query->lookup->http_plain) { + result = + isc_tlsctx_createclient(&query->tlsctx); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_tlsctx_enable_http2client_alpn( + query->tlsctx); + } + + result = isc_nm_httpconnect( + netmgr, (isc_nmiface_t *)&localaddr, + (isc_nmiface_t *)&query->sockaddr, uri, + !query->lookup->https_get, tcp_connected, query, + query->tlsctx, local_timeout, 0); + check_result(result, "isc_nm_httpconnect"); } else { result = isc_nm_tcpdnsconnect( netmgr, (isc_nmiface_t *)&localaddr, @@ -3211,6 +3195,7 @@ launch_next_query(dig_query_t *query) { } } } + lookup_detach(&l); return; } @@ -3582,8 +3567,7 @@ recv_done(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region, isc_sockaddr_t peer; REQUIRE(DIG_VALID_QUERY(query)); - INSIST(query->readhandle != NULL); - INSIST(handle == query->readhandle); + REQUIRE(query->readhandle != NULL); INSIST(!free_now); debug("recv_done(%p, %s, %p, %p)", handle, isc_result_totext(eresult), diff --git a/bin/dig/dighost.h b/bin/dig/dighost.h index b719b4856e..0dfeb56da5 100644 --- a/bin/dig/dighost.h +++ b/bin/dig/dighost.h @@ -76,6 +76,9 @@ #define DEFAULT_EDNS_VERSION 0 #define DEFAULT_EDNS_BUFSIZE 1232 +#define DEFAULT_HTTPS_PATH "/dns-query" +#define DEFAULT_HTTPS_QUERY "?dns=" + /*% * Lookup_limit is just a limiter, keeping too many lookups from being * created. It's job is mainly to prevent the program from running away @@ -168,6 +171,12 @@ struct dig_lookup { int rrcomments; unsigned int eoferr; uint16_t qid; + struct { + bool http_plain; + bool https_mode; + bool https_get; + char *https_path; + }; }; /*% The dig_query structure */ diff --git a/doc/man/dig.1in b/doc/man/dig.1in index 9b78ada40f..e5820f5f42 100644 --- a/doc/man/dig.1in +++ b/doc/man/dig.1in @@ -361,11 +361,38 @@ This option sends a query with a DNS header without a question section. The default is to add a question section. The query type and query name are ignored when this is set. .TP +.B \fB+[no]https[=value]\fP +This option indicates whether to use DNS\-over\-HTTPS (DoH) when querying +name servers. When this option is in use, the port number defaults to 443. +The HTTP POST request mode is used when sending the query. +.sp +If \fBvalue\fP is specified, it will be used as the HTTP endpoint in the +query URI; the default is \fB/dns\-query\fP\&. So, for example, \fBdig +@example.com +https\fP will use the URI \fBhttps://example.com/dns\-query\fP\&. +.TP +.B \fB+[no]https\-get[=value]\fP +Similar to \fB+https\fP, except that the HTTP GET request mode is used +when sending the query. +.TP +.B \fB+[no]https\-post[=value]\fP +Same as \fB+https\fP\&. +.TP +.B \fB+[no]http\-plain[=value]\fP +Similar to \fB+https\fP, except that HTTP queries will be sent over a +non\-encrypted channel. When this option is in use, the port number +defaults to 80 and the HTTP request mode is POST. +.TP +.B \fB+[no]http\-plain\-get[=value]\fP +Similar to \fB+http\-plain\fP, except that the HTTP request mode is GET. +.TP +.B \fB+[no]http\-plain\-post[=value]\fP +Same as \fB+http\-plain\fP\&. +.TP .B \fB+[no]identify\fP -This option shows [or does not show] the IP address and port number that supplied -the answer, when the \fB+short\fP option is enabled. If short form -answers are requested, the default is not to show the source address -and port number of the server that provided the answer. +This option shows [or does not show] the IP address and port number that +supplied the answer, when the \fB+short\fP option is enabled. If short +form answers are requested, the default is not to show the source +address and port number of the server that provided the answer. .TP .B \fB+[no]idnin\fP This option processes [or does not process] IDN domain names on input. This requires @@ -531,8 +558,9 @@ This option sets the timeout for a query to \fBT\fP seconds. The default timeout 5 seconds. An attempt to set \fBT\fP to less than 1 is silently set to 1. .TP .B \fB+[no]tls\fP -This option indicates whether to use DNS over TLS (DoT) when querying -name servers. +This option indicates whether to use DNS\-over\-TLS (DoT) when querying +name servers. When this option is in use, the port number defaults +to 853. .TP .B \fB+[no]topdown\fP This feature is related to \fBdig +sigchase\fP, which is obsolete and