diff --git a/bin/dig/dighost.c b/bin/dig/dighost.c index 30d5231375..b31900934b 100644 --- a/bin/dig/dighost.c +++ b/bin/dig/dighost.c @@ -4282,16 +4282,61 @@ idn_locale_to_ace(const char *src, char *dst, size_t dstlen) { */ static void idn_ace_to_locale(const char *src, char **dst) { - char *local_src; + char *local_src, *utf8_src; int res; - res = idn2_to_unicode_8zlz(src, &local_src, - IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL); + /* + * We need to: + * + * 1) check whether 'src' is a valid IDNA2008 name, + * 2) if it is, output it in the current locale's character encoding. + * + * Unlike idn2_to_ascii_*(), idn2_to_unicode_*() functions are unable + * to perform IDNA2008 validity checks. Thus, we need to decode any + * Punycode in 'src', check if the resulting name is a valid IDNA2008 + * name, and only once we ensure it is, output that name in the current + * locale's character encoding. + * + * We could just use idn2_to_unicode_8zlz() + idn2_to_ascii_lz(), but + * then we would not be able to universally tell invalid names and + * character encoding errors apart (if the current locale uses ASCII + * for character encoding, the former function would fail even for a + * valid IDNA2008 name, as long as it contained any non-ASCII + * character). Thus, we need to take a longer route. + * + * First, convert 'src' to UTF-8, ignoring the current locale. + */ + res = idn2_to_unicode_8z8z(src, &utf8_src, 0); + if (res != IDN2_OK) { + fatal("Bad ACE string '%s' (%s), use +noidnout", + src, idn2_strerror(res)); + } + + /* + * Then, check whether decoded 'src' is a valid IDNA2008 name. + */ + res = idn2_to_ascii_8z(utf8_src, NULL, IDN2_NONTRANSITIONAL); if (res != IDN2_OK) { fatal("'%s' is not a legal IDNA2008 name (%s), use +noidnout", src, idn2_strerror(res)); } + /* + * Finally, try converting the decoded 'src' into the current locale's + * character encoding. + */ + res = idn2_to_unicode_8zlz(utf8_src, &local_src, 0); + if (res != IDN2_OK) { + fatal("Cannot represent '%s' in the current locale (%s), " + "use +noidnout or a different locale", + src, idn2_strerror(res)); + } + + /* + * Free the interim conversion result. + */ + idn2_free(utf8_src); + *dst = local_src; } #endif /* HAVE_LIBIDN2 */ diff --git a/bin/tests/system/idna/tests.sh b/bin/tests/system/idna/tests.sh index d7a968306a..644aa3eccb 100644 --- a/bin/tests/system/idna/tests.sh +++ b/bin/tests/system/idna/tests.sh @@ -136,46 +136,6 @@ idna_fail() { status=`expr $status + $ret` } -# Check if current version of libidn2 is >= a given version -# -# This requires that: -# a) "pkg-config" exists on the system -# b) The libidn2 installed has an associated ".pc" file -# c) The system sort command supports "-V" -# -# $1 - Minimum version required -# -# Returns: -# 0 - Version check is OK, libidn2 at required version or greater. -# 1 - Version check was made, but libidn2 not at required version. -# 2 - Could not carry out version check - -libidn_version_check() { - ret=2 - if [ -n "`command -v pkg-config`" ]; then - version=`pkg-config --modversion --silence-errors libidn2` - if [ -n "$version" ]; then - # Does the sort command have a "-V" flag on this system? - sort -V 2>&1 > /dev/null << . -. - if [ $? -eq 0 ]; then - # Sort -V exists. Sort the IDN version and the minimum version - # required. If the IDN version is greater than or equal to that - # version, it will appear last in the list. - last_version=`printf "%s\n" $version $1 | sort -V | tail -1` - if [ "$version" = "$last_version" ]; then - ret=0 - else - ret=1 - fi - fi - fi - fi - - return $ret -} - - # Function to check that case is preserved for an all-ASCII label. # # Without IDNA support, case-preservation is the expected behavior. @@ -310,16 +270,7 @@ idna_enabled_test() { text="Checking fake A-label" idna_fail "$text" "" "xn--ahahah" idna_test "$text" "+noidnin +noidnout" "xn--ahahah" "xn--ahahah." - - # Owing to issues with libdns, the next test will fail for versions of - # libidn earlier than 2.0.5. For this reason, get the version (if - # available) and compare with 2.0.5. - libidn_version_check 2.0.5 - if [ $? -ne 0 ]; then - echo_i "Skipping fake A-label +noidnin +idnout test (libidn2 version issues)" - else - idna_test "$text" "+noidnin +idnout" "xn--ahahah" "xn--ahahah." - fi + idna_fail "$text" "+noidnin +idnout" "xn--ahahah" idna_fail "$text" "+idnin +noidnout" "xn--ahahah" idna_fail "$text" "+idnin +idnout" "xn--ahahah" @@ -327,7 +278,7 @@ idna_enabled_test() { # BIND rejects such labels: with +idnin label="xn--xflod18hstflod18hstflod18hstflod18hstflod18hstflod18-1iejjjj" - text="Checking punycode label shorter than minimum valid length" + text="Checking punycode label longer than maximum valid length" idna_fail "$text" "" "$label" idna_fail "$text" "+noidnin +noidnout" "$label" idna_fail "$text" "+noidnin +idnout" "$label" @@ -337,10 +288,11 @@ idna_enabled_test() { - # Tests of a valid unicode string but an invalid U-label + # Tests of a valid unicode string but an invalid U-label (input) # - # Symbols are not valid IDNA2008 names. Check whether dig rejects them to - # ensure no IDNA2003 fallbacks are in place. + # Symbols are not valid IDNA2008 names. Check whether dig rejects them + # when they are supplied on the command line to ensure no IDNA2003 + # fallbacks are in place. # # +noidnin: "dig" should send unicode octets to the server and display the # returned qname in the same form. @@ -348,12 +300,34 @@ idna_enabled_test() { # # The +[no]idnout options should not have any effect on the test. - text="Checking invalid U-label" + text="Checking invalid input U-label" idna_fail "$text" "" "√.com" idna_test "$text" "+noidnin +noidnout" "√.com" "\226\136\154.com." idna_test "$text" "+noidnin +idnout" "√.com" "\226\136\154.com." idna_fail "$text" "+idnin +noidnout" "√.com" idna_fail "$text" "+idnin +idnout" "√.com" + + # Tests of a valid unicode string but an invalid U-label (output) + # + # Symbols are not valid IDNA2008 names. Check whether dig rejects them + # when they are received in DNS responses to ensure no IDNA2003 fallbacks + # are in place. + # + # Note that an invalid U-label is accepted even when +idnin is in effect + # because "xn--19g" is valid Punycode. + # + # +noidnout: "dig" should send the ACE string to the server and display the + # returned qname. + # +idnout: "dig" should generate an error. + # + # The +[no]idnin options should not have any effect on the test. + + text="Checking invalid output U-label" + idna_fail "$text" "" "xn--19g" + idna_test "$text" "+noidnin +noidnout" "xn--19g" "xn--19g." + idna_fail "$text" "+noidnin +idnout" "xn--19g" + idna_test "$text" "+idnin +noidnout" "xn--19g" "xn--19g." + idna_fail "$text" "+idnin +idnout" "xn--19g" }