diff --git a/CHANGES b/CHANGES index 44b2e55848..a57db6aaed 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +5408. [protocol] Print Extended DNS Errors if present in OPT record. + [GL #1835] + 5407. [func] The zone timers are now exported to the statistics channel. Thanks to Paul Frieden, Verizon Media. [GL #1232] diff --git a/bin/dig/dighost.c b/bin/dig/dighost.c index bfc0c2c65f..0384d7ccad 100644 --- a/bin/dig/dighost.c +++ b/bin/dig/dighost.c @@ -1452,6 +1452,7 @@ dig_ednsoptname_t optnames[] = { { 12, "PAD" }, /* shorthand */ { 13, "CHAIN" }, /* RFC 7901 */ { 14, "KEY-TAG" }, /* RFC 8145 */ + { 15, "EDE" }, /* ietf-dnsop-extended-error-16 */ { 16, "CLIENT-TAG" }, /* draft-bellis-dnsop-edns-tags */ { 17, "SERVER-TAG" }, /* draft-bellis-dnsop-edns-tags */ { 26946, "DEVICEID" }, /* Brian Hartvigsen */ diff --git a/bin/tests/system/digdelv/tests.sh b/bin/tests/system/digdelv/tests.sh index 2d2c63e285..bfb6ccb9c8 100644 --- a/bin/tests/system/digdelv/tests.sh +++ b/bin/tests/system/digdelv/tests.sh @@ -568,7 +568,8 @@ if [ -x "$DIG" ] ; then echo_i "checking ednsopt LLQ prints as expected ($n)" ret=0 dig_with_opts @10.53.0.3 +ednsopt=llq:0001000200001234567812345678fefefefe +qr a.example > dig.out.test$n 2>&1 || ret=1 - grep 'LLQ: Version: 1, Opcode: 2, Error: 0, Identifier: 1311768465173141112, Lifetime: 4278124286$' dig.out.test$n > /dev/null || ret=1 + pat='LLQ: Version: 1, Opcode: 2, Error: 0, Identifier: 1311768465173141112, Lifetime: 4278124286$' + tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status+ret)) @@ -662,6 +663,51 @@ if [ -x "$DIG" ] ; then if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status+ret)) + n=$((n+1)) + echo_i "check that Extended DNS Error 0 is printed correctly ($n)" + # First defined EDE code, additional text "foo". + dig_with_opts @10.53.0.3 +ednsopt=ede:0000666f6f a.example +qr > dig.out.test$n 2>&1 || ret=1 + pat='^; EDE: 0 (Other): (foo)$' + tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status+ret)) + + n=$((n+1)) + echo_i "check that Extended DNS Error 24 is printed correctly ($n)" + # Last defined EDE code, no additional text. + dig_with_opts @10.53.0.3 +ednsopt=ede:0018 a.example +qr > dig.out.test$n 2>&1 || ret=1 + pat='^; EDE: 24 (Invalid Data)$' + tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status+ret)) + + n=$((n+1)) + echo_i "check that Extended DNS Error 25 is printed correctly ($n)" + # First undefined EDE code, additional text "foo". + dig_with_opts @10.53.0.3 +ednsopt=ede:0019666f6f a.example +qr > dig.out.test$n 2>&1 || ret=1 + pat='^; EDE: 25: (foo)$' + tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status+ret)) + + n=$((n+1)) + echo_i "check that invalid Extended DNS Error (length 0) is printed ($n)" + # EDE payload is too short + dig_with_opts @10.53.0.3 +ednsopt=ede a.example +qr > dig.out.test$n 2>&1 || ret=1 + pat='^; EDE:$' + tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status+ret)) + + n=$((n+1)) + echo_i "check that invalid Extended DNS Error (length 1) is printed ($n)" + # EDE payload is too short + dig_with_opts @10.53.0.3 +ednsopt=ede:00 a.example +qr > dig.out.test$n 2>&1 || ret=1 + pat='^; EDE: 00 (".")$' + tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status+ret)) + n=$((n+1)) echo_i "check that dig handles malformed option '+ednsopt=:' gracefully ($n)" ret=0 @@ -686,7 +732,8 @@ if [ -x "$DIG" ] ; then echo_i "check that dig -q -m works ($n)" ret=0 dig_with_opts @10.53.0.3 -q -m > dig.out.test$n 2>&1 - grep '^;-m\..*IN.*A$' dig.out.test$n > /dev/null || ret=1 + pat='^;-m\..*IN.*A$' + tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1 grep "Dump of all outstanding memory allocations" dig.out.test$n > /dev/null && ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status+ret)) @@ -780,7 +827,8 @@ if [ -x "$DIG" ] ; then echo_i "check that dig +short +expandaaaa works ($n)" ret=0 dig_with_opts @10.53.0.3 +short +expandaaaa AAAA ns2.example > dig.out.test$n 2>&1 || ret=1 - grep '^fd92:7065:0b8e:ffff:0000:0000:0000:0002$' dig.out.test$n > /dev/null || ret=1 + pat='^fd92:7065:0b8e:ffff:0000:0000:0000:0002$' + tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status+ret)) diff --git a/doc/arm/notes-9.17.2.xml b/doc/arm/notes-9.17.2.xml index 193c13c2c1..2c70de6135 100644 --- a/doc/arm/notes-9.17.2.xml +++ b/doc/arm/notes-9.17.2.xml @@ -96,6 +96,13 @@ Contributed by Paul Frieden, Verizon Media. [GL #1232] + + + dig and other tools can now print the Extended + DNS Error (EDE) option when it appears in a request or response. + [GL #1834] + + diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h index e2eef88c06..ec3e065daa 100644 --- a/lib/dns/include/dns/message.h +++ b/lib/dns/include/dns/message.h @@ -107,6 +107,7 @@ #define DNS_OPT_TCP_KEEPALIVE 11 /*%< TCP keepalive opt code */ #define DNS_OPT_PAD 12 /*%< PAD opt code */ #define DNS_OPT_KEY_TAG 14 /*%< Key tag opt code */ +#define DNS_OPT_EDE 15 /*%< Extended DNS Error opt code */ #define DNS_OPT_CLIENT_TAG 16 /*%< Client tag opt code */ #define DNS_OPT_SERVER_TAG 17 /*%< Server tag opt code */ diff --git a/lib/dns/message.c b/lib/dns/message.c index 4e4d692a47..a3b0ae4ea6 100644 --- a/lib/dns/message.c +++ b/lib/dns/message.c @@ -23,6 +23,7 @@ #include #include #include /* Required for HP/UX (and others?) */ +#include #include #include @@ -127,6 +128,32 @@ static const char *opcodetext[] = { "QUERY", "IQUERY", "STATUS", "RESERVED12", "RESERVED13", "RESERVED14", "RESERVED15" }; +static const char *edetext[] = { "Other", + "Unsupported DNSKEY Algorithm", + "Unsupported DS Digest Type", + "Stale Answer", + "Forged Answer", + "DNSSEC Indeterminate", + "DNSSEC Bogus", + "Signature Expired", + "Signature Not Yet Valid", + "DNSKEY Missing", + "RRSIGs Missing", + "No Zone Key Bit Set", + "NSEC Missing", + "Cached Error", + "Not Ready", + "Blocked", + "Censored", + "Filtered", + "Prohibited", + "Stale NXDOMAIN Answer", + "Not Authoritative", + "Not Supported", + "No Reachable Authority", + "Network Error", + "Invalid Data" }; + /*% * "helper" type, which consists of a block of some type, and is linkable. * For it to work, sizeof(dns_msgblock_t) must be a multiple of the pointer @@ -3590,7 +3617,7 @@ dns_message_pseudosectiontoyaml(dns_message_t *msg, dns_pseudosection_t section, * Print EDNS info, if any. * * WARNING: The option contents may be malformed as - * dig +ednsopt=value: does not validity + * dig +ednsopt=value: does not perform validity * checking. */ dns_rdata_init(&rdata); @@ -4004,6 +4031,36 @@ dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section, ADD_STRING(target, "\n"); continue; } + } else if (optcode == DNS_OPT_EDE) { + ADD_STRING(target, "; EDE:"); + if (optlen >= 2U) { + uint16_t ede; + ede = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u", ede); + ADD_STRING(target, buf); +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + if (ede < ARRAY_SIZE(edetext)) { + ADD_STRING(target, " ("); + ADD_STRING(target, + edetext[ede]); + ADD_STRING(target, ")"); + } + optlen -= 2; + if (optlen != 0) { + ADD_STRING(target, ":"); + } + } else if (optlen == 1U) { + /* Malformed */ + optdata = isc_buffer_current(&optbuf); + snprintf(buf, sizeof(buf), + " %02x (\"%c\")\n", optdata[0], + isprint(optdata[0]) + ? optdata[0] + : '.'); + isc_buffer_forward(&optbuf, optlen); + ADD_STRING(target, buf); + continue; + } } else if (optcode == DNS_OPT_CLIENT_TAG) { uint16_t id; ADD_STRING(target, "; CLIENT-TAG:"); @@ -4034,23 +4091,31 @@ dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section, if (optlen != 0) { int i; + bool utf8ok = false; ADD_STRING(target, " "); optdata = isc_buffer_current(&optbuf); - for (i = 0; i < optlen; i++) { - const char *sep; - switch (optcode) { - case DNS_OPT_COOKIE: - sep = ""; - break; - default: - sep = " "; - break; + if (optcode == DNS_OPT_EDE) { + utf8ok = isc_utf8_valid(optdata, + optlen); + } + if (!utf8ok) { + for (i = 0; i < optlen; i++) { + const char *sep; + switch (optcode) { + case DNS_OPT_COOKIE: + sep = ""; + break; + default: + sep = " "; + break; + } + snprintf(buf, sizeof(buf), + "%02x%s", optdata[i], + sep); + ADD_STRING(target, buf); } - snprintf(buf, sizeof(buf), "%02x%s", - optdata[i], sep); - ADD_STRING(target, buf); } isc_buffer_forward(&optbuf, optlen); @@ -4087,9 +4152,13 @@ dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section, /* * For non-COOKIE options, add a printable - * version + * version. */ - ADD_STRING(target, "(\""); + if (optcode != DNS_OPT_EDE) { + ADD_STRING(target, "(\""); + } else { + ADD_STRING(target, "("); + } if (isc_buffer_availablelength(target) < optlen) { return (ISC_R_NOSPACE); @@ -4098,11 +4167,18 @@ dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section, if (isprint(optdata[i])) { isc_buffer_putmem( target, &optdata[i], 1); + } else if (utf8ok && optdata[i] > 127) { + isc_buffer_putmem( + target, &optdata[i], 1); } else { isc_buffer_putstr(target, "."); } } - ADD_STRING(target, "\")"); + if (optcode != DNS_OPT_EDE) { + ADD_STRING(target, "\")"); + } else { + ADD_STRING(target, ")"); + } } ADD_STRING(target, "\n"); } diff --git a/lib/dns/rdata/generic/opt_41.c b/lib/dns/rdata/generic/opt_41.c index 22c3f472e6..55a96853f1 100644 --- a/lib/dns/rdata/generic/opt_41.c +++ b/lib/dns/rdata/generic/opt_41.c @@ -18,6 +18,8 @@ (DNS_RDATATYPEATTR_SINGLETON | DNS_RDATATYPEATTR_META | \ DNS_RDATATYPEATTR_NOTQUESTION) +#include + static inline isc_result_t fromtext_opt(ARGS_FROMTEXT) { /* @@ -206,6 +208,24 @@ fromwire_opt(ARGS_FROMWIRE) { } isc_region_consume(&sregion, length); break; + case DNS_OPT_EDE: + if (length < 2) { + return (DNS_R_OPTERR); + } + /* UTF-8 Byte Order Mark is not permitted. RFC 5198 */ + if (isc_utf8_bom(sregion.base + 2, length - 2)) { + return (DNS_R_OPTERR); + } + /* + * The EXTRA-TEXT field is specified as UTF-8, and + * therefore must be validated for correctness + * according to RFC 3269 security considerations. + */ + if (!isc_utf8_valid(sregion.base + 2, length - 2)) { + return (DNS_R_OPTERR); + } + isc_region_consume(&sregion, length); + break; case DNS_OPT_CLIENT_TAG: /* FALLTHROUGH */ case DNS_OPT_SERVER_TAG: diff --git a/lib/isc/Makefile.am b/lib/isc/Makefile.am index 702d02012a..2d96823e93 100644 --- a/lib/isc/Makefile.am +++ b/lib/isc/Makefile.am @@ -88,6 +88,7 @@ libisc_la_HEADERS = \ include/isc/timer.h \ include/isc/tm.h \ include/isc/types.h \ + include/isc/utf8.h \ include/isc/util.h \ pthreads/include/isc/condition.h\ pthreads/include/isc/mutex.h \ @@ -205,6 +206,7 @@ libisc_la_SOURCES = \ taskpool.c \ timer.c \ tm.c \ + utf8.c \ pthreads/condition.c \ pthreads/mutex.c \ pthreads/thread.c \ diff --git a/lib/isc/include/isc/utf8.h b/lib/isc/include/isc/utf8.h new file mode 100644 index 0000000000..a642c3facd --- /dev/null +++ b/lib/isc/include/isc/utf8.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file isc/utf8.h */ + +#pragma once + +#include +#include + +ISC_LANG_BEGINDECLS + +bool +isc_utf8_bom(const unsigned char *buf, size_t len); +/*< + * Returns 'true' if the string of bytes in 'buf' starts + * with an UTF-8 Byte Order Mark. + * + * Requires: + *\li 'buf' != NULL + */ + +bool +isc_utf8_valid(const unsigned char *buf, size_t len); +/*< + * Returns 'true' if the string of bytes in 'buf' is a valid UTF-8 + * byte sequence otherwise 'false' is returned. + * + * Requires: + *\li 'buf' != NULL + */ + +ISC_LANG_ENDDECLS diff --git a/lib/isc/utf8.c b/lib/isc/utf8.c new file mode 100644 index 0000000000..5bfd94f8d1 --- /dev/null +++ b/lib/isc/utf8.c @@ -0,0 +1,86 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#include +#include + +/* + * UTF-8 is defined in "The Unicode Standard -- Version 4.0" + * Also see RFC 3629. + * + * Char. number range | UTF-8 octet sequence + * (hexadecimal) | (binary) + * --------------------+--------------------------------------------- + * 0000 0000-0000 007F | 0xxxxxxx + * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ +bool +isc_utf8_valid(const unsigned char *buf, size_t len) { + REQUIRE(buf != NULL); + + for (size_t i = 0; i < len; i++) { + if (buf[i] <= 0x7f) { + continue; + } + if ((i + 1) < len && (buf[i] & 0xe0) == 0xc0 && + (buf[i + 1] & 0xc0) == 0x80) { + unsigned int w; + w = (buf[i] & 0x1f) << 6; + w |= (buf[++i] & 0x3f); + if (w < 0x80) { + return (false); + } + continue; + } + if ((i + 2) < len && (buf[i] & 0xf0) == 0xe0 && + (buf[i + 1] & 0xc0) == 0x80 && (buf[i + 2] & 0xc0) == 0x80) + { + unsigned int w; + w = (buf[i] & 0x0f) << 12; + w |= (buf[++i] & 0x3f) << 6; + w |= (buf[++i] & 0x3f); + if (w < 0x0800) { + return (false); + } + continue; + } + if ((i + 3) < len && (buf[i] & 0xf8) == 0xf0 && + (buf[i + 1] & 0xc0) == 0x80 && + (buf[i + 2] & 0xc0) == 0x80 && (buf[i + 3] & 0xc0) == 0x80) + { + unsigned int w; + w = (buf[i] & 0x07) << 18; + w |= (buf[++i] & 0x3f) << 12; + w |= (buf[++i] & 0x3f) << 6; + w |= (buf[++i] & 0x3f); + if (w < 0x10000 || w > 0x10FFFF) { + return (false); + } + continue; + } + return (false); + } + return (true); +} + +bool +isc_utf8_bom(const unsigned char *buf, size_t len) { + REQUIRE(buf != NULL); + + if (len >= 3U && !memcmp(buf, "\xef\xbb\xbf", 3)) { + return (true); + } + return (false); +} diff --git a/lib/isc/win32/libisc.def.in b/lib/isc/win32/libisc.def.in index aecf800244..7b47b79c26 100644 --- a/lib/isc/win32/libisc.def.in +++ b/lib/isc/win32/libisc.def.in @@ -689,6 +689,8 @@ isc_timermgr_destroy isc_timermgr_poke isc_tm_timegm isc_tm_strptime +isc_utf8_bom +isc_utf8_valid isc_win32os_versioncheck openlog @IF PKCS11 diff --git a/lib/isc/win32/libisc.vcxproj.filters.in b/lib/isc/win32/libisc.vcxproj.filters.in index f4e1964f06..5dcf67ab50 100644 --- a/lib/isc/win32/libisc.vcxproj.filters.in +++ b/lib/isc/win32/libisc.vcxproj.filters.in @@ -260,6 +260,9 @@ Library Header Files + + Library Header Files + Library Header Files @@ -605,6 +608,9 @@ Library Source Files + + Library Source Files + @IF PKCS11 Library Source Files diff --git a/lib/isc/win32/libisc.vcxproj.in b/lib/isc/win32/libisc.vcxproj.in index b4574460b7..297df38905 100644 --- a/lib/isc/win32/libisc.vcxproj.in +++ b/lib/isc/win32/libisc.vcxproj.in @@ -370,6 +370,7 @@ copy InstallFiles ..\Build\Release\ + @IF PKCS11 @@ -471,6 +472,7 @@ copy InstallFiles ..\Build\Release\ + @IF PKCS11 diff --git a/util/copyrights b/util/copyrights index 4aecf882c5..5c71e8dee8 100644 --- a/util/copyrights +++ b/util/copyrights @@ -1866,6 +1866,7 @@ ./lib/isc/include/isc/timer.h C 1998,1999,2000,2001,2002,2004,2005,2006,2007,2008,2009,2012,2013,2014,2016,2018,2019,2020 ./lib/isc/include/isc/tm.h C 2014,2016,2018,2019,2020 ./lib/isc/include/isc/types.h C 1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2012,2013,2014,2016,2017,2018,2019,2020 +./lib/isc/include/isc/utf8.h C 2020 ./lib/isc/include/isc/util.h C 1998,1999,2000,2001,2004,2005,2006,2007,2010,2011,2012,2015,2016,2017,2018,2019,2020 ./lib/isc/include/pk11/constants.h C 2014,2016,2017,2018,2019,2020 ./lib/isc/include/pk11/internal.h C 2014,2016,2018,2019,2020 @@ -1990,6 +1991,7 @@ ./lib/isc/unix/stdtime.c C 1999,2000,2001,2004,2005,2007,2016,2018,2019,2020 ./lib/isc/unix/syslog.c C 2001,2004,2005,2007,2016,2018,2019,2020 ./lib/isc/unix/time.c C 1998,1999,2000,2001,2003,2004,2005,2006,2007,2008,2011,2012,2014,2015,2016,2017,2018,2019,2020 +./lib/isc/utf8.c C 2020 ./lib/isc/win32/DLLMain.c C 2001,2004,2007,2016,2018,2019,2020 ./lib/isc/win32/condition.c C 1998,1999,2000,2001,2004,2006,2007,2016,2018,2019,2020 ./lib/isc/win32/dir.c C 1999,2000,2001,2004,2007,2008,2009,2011,2012,2013,2016,2017,2018,2019,2020