From 2e3328229470730f9c81ea439afe3a9cb1504276 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20R=C3=BChsen?= <tim.ruehsen@gmx.de>
Date: Tue, 9 Apr 2019 16:58:18 +0200
Subject: [PATCH] Perform A-Label roundtrip for lookup functions by default

This adds another check to avoid unexpected results.
It was a longstanding FIXME.

Thanks to  Jonathan Birch of Microsoft Corporation,
Florian Weimer (GNU glibc) and Nikos Mavrogiannopoulos (GnuTLS)
for investigation, discussion and testing.
---
 lib/error.c   |   2 ++
 lib/idn2.h.in |  12 ++++---
 lib/lookup.c  | 105 +++++++++++++++++++++++++++++++++++++++++-----------------
 src/blurbs.h  |   4 +--
 src/idn2.c    |  32 ++++++++++--------
 src/idn2.ggo  |   1 +
 6 files changed, 105 insertions(+), 51 deletions(-)

diff --git a/lib/error.c b/lib/error.c
index 0304044..8483f30 100644
--- a/lib/error.c
+++ b/lib/error.c
@@ -77,6 +77,7 @@ idn2_strerror (int rc)
     case IDN2_DOT_IN_LABEL: return _("domain label has forbidden dot (TR46)");
     case IDN2_INVALID_TRANSITIONAL: return _("domain label has character forbidden in transitional mode (TR46)");
     case IDN2_INVALID_NONTRANSITIONAL: return _("domain label has character forbidden in non-transitional mode (TR46)");
+    case IDN2_ALABEL_ROUNDTRIP_FAILED: return _("Alabel roundtrip failed");
     default: return _("Unknown error");
     }
 }
@@ -129,6 +130,7 @@ idn2_strerror_name (int rc)
     case IDN2_DOT_IN_LABEL: return ERR2STR (IDN2_DOT_IN_LABEL);
     case IDN2_INVALID_TRANSITIONAL: return ERR2STR (IDN2_INVALID_TRANSITIONAL);
     case IDN2_INVALID_NONTRANSITIONAL: return ERR2STR (IDN2_INVALID_NONTRANSITIONAL);
+    case IDN2_ALABEL_ROUNDTRIP_FAILED: return ERR2STR (IDN2_ALABEL_ROUNDTRIP_FAILED);
     default: return "IDN2_UNKNOWN";
     }
 }
diff --git a/lib/idn2.h.in b/lib/idn2.h.in
index bae7e2e..b979bda 100644
--- a/lib/idn2.h.in
+++ b/lib/idn2.h.in
@@ -150,10 +150,11 @@ extern "C"
 /**
  * idn2_flags:
  * @IDN2_NFC_INPUT: Normalize input string using normalization form C.
- * @IDN2_ALABEL_ROUNDTRIP: Perform optional IDNA2008 lookup roundtrip check (not implemented yet).
- * @IDN2_NO_TR46: Disable Unicode TR46 processing (default).
+ * @IDN2_ALABEL_ROUNDTRIP: Perform optional IDNA2008 lookup roundtrip check (default).
+ * @IDN2_NO_ALABEL_ROUNDTRIP: Disable ALabel lookup roundtrip check.
+ * @IDN2_NO_TR46: Disable Unicode TR46 processing.
  * @IDN2_TRANSITIONAL: Perform Unicode TR46 transitional processing.
- * @IDN2_NONTRANSITIONAL: Perform Unicode TR46 non-transitional processing.
+ * @IDN2_NONTRANSITIONAL: Perform Unicode TR46 non-transitional processing (default).
  * @IDN2_ALLOW_UNASSIGNED: Libidn compatibility flag, unused.
  * @IDN2_USE_STD3_ASCII_RULES: Use STD3 ASCII rules.
  * This is a #TR46 only flag, and will be ignored when set without either
@@ -170,7 +171,8 @@ extern "C"
     IDN2_NONTRANSITIONAL = 8,
     IDN2_ALLOW_UNASSIGNED = 16,
     IDN2_USE_STD3_ASCII_RULES = 32,
-    IDN2_NO_TR46 = 64
+    IDN2_NO_TR46 = 64,
+    IDN2_NO_ALABEL_ROUNDTRIP = 128
   } idn2_flags;
 
 /* IDNA2008 with UTF-8 encoded inputs. */
@@ -221,6 +223,7 @@ extern "C"
  * @IDN2_DOT_IN_LABEL: Label has forbidden dot (TR46).
  * @IDN2_INVALID_TRANSITIONAL: Label has character forbidden in transitional mode (TR46).
  * @IDN2_INVALID_NONTRANSITIONAL: Label has character forbidden in non-transitional mode (TR46).
+ * @IDN2_ALABEL_ROUNDTRIP_FAILED: ALabel -> Ulabel -> ALabel result differs from input.
  *
  * Return codes for IDN2 functions.  All return codes are negative
  * except for the successful code IDN2_OK which are guaranteed to be
@@ -259,6 +262,7 @@ extern "C"
     IDN2_DOT_IN_LABEL = -311,
     IDN2_INVALID_TRANSITIONAL = -312,
     IDN2_INVALID_NONTRANSITIONAL = -313,
+    IDN2_ALABEL_ROUNDTRIP_FAILED = -314,
   } idn2_rc;
 
 /* Auxilliary functions. */
diff --git a/lib/lookup.c b/lib/lookup.c
index 1d922a5..5e814e0 100644
--- a/lib/lookup.c
+++ b/lib/lookup.c
@@ -51,6 +51,9 @@ static int set_default_flags(int *flags)
   if (((*flags) & (IDN2_TRANSITIONAL|IDN2_NONTRANSITIONAL)) && ((*flags) & IDN2_NO_TR46))
     return IDN2_INVALID_FLAGS;
 
+  if (((*flags) & IDN2_ALABEL_ROUNDTRIP) && ((*flags) & IDN2_NO_ALABEL_ROUNDTRIP))
+    return IDN2_INVALID_FLAGS;
+
   if (!((*flags) & (IDN2_NO_TR46|IDN2_TRANSITIONAL)))
     *flags |= IDN2_NONTRANSITIONAL;
 
@@ -63,23 +66,39 @@ label (const uint8_t * src, size_t srclen, uint8_t * dst, size_t * dstlen,
 {
   size_t plen;
   uint32_t *p;
-  int rc;
-  size_t tmpl;
-
-  if (_idn2_ascii_p (src, srclen))
-    {
-      if (flags & IDN2_ALABEL_ROUNDTRIP)
-	/* FIXME implement this MAY:
-
-	   If the input to this procedure appears to be an A-label
-	   (i.e., it starts in "xn--", interpreted
-	   case-insensitively), the lookup application MAY attempt to
-	   convert it to a U-label, first ensuring that the A-label is
-	   entirely in lowercase (converting it to lowercase if
-	   necessary), and apply the tests of Section 5.4 and the
-	   conversion of Section 5.5 to that form. */
-	return IDN2_INVALID_FLAGS;
+  const uint8_t *src_org = NULL;
+  uint8_t *src_allocated = NULL;
+  int rc, check_roundtrip = 0;
+  size_t tmpl, srclen_org = 0;
+  uint32_t label_u32[IDN2_LABEL_MAX_LENGTH];
+  size_t label32_len = IDN2_LABEL_MAX_LENGTH;
+
+  if (_idn2_ascii_p (src, srclen)) {
+    if (!(flags & IDN2_NO_ALABEL_ROUNDTRIP) && srclen >= 4 && memcmp (src, "xn--", 4) == 0) {
+      /*
+	 If the input to this procedure appears to be an A-label
+	 (i.e., it starts in "xn--", interpreted
+	 case-insensitively), the lookup application MAY attempt to
+	 convert it to a U-label, first ensuring that the A-label is
+	 entirely in lowercase (converting it to lowercase if
+	 necessary), and apply the tests of Section 5.4 and the
+	 conversion of Section 5.5 to that form. */
+      rc = _idn2_punycode_decode (srclen - 4, (char *) src + 4, &label32_len, label_u32);
+      if (rc)
+	return rc;
 
+      check_roundtrip = 1;
+      src_org = src;
+      srclen_org = srclen;
+
+      srclen = IDN2_LABEL_MAX_LENGTH;
+      src = src_allocated = u32_to_u8 (label_u32, label32_len, NULL, &srclen);
+      if (!src) {
+	if (errno == ENOMEM)
+	  return IDN2_MALLOC;
+	return IDN2_ENCODING_ERROR;
+      }
+    } else {
       if (srclen > IDN2_LABEL_MAX_LENGTH)
 	return IDN2_TOO_BIG_LABEL;
       if (srclen > *dstlen)
@@ -89,10 +108,11 @@ label (const uint8_t * src, size_t srclen, uint8_t * dst, size_t * dstlen,
       *dstlen = srclen;
       return IDN2_OK;
     }
+  }
 
   rc = _idn2_u8_to_u32_nfc (src, srclen, &p, &plen, flags & IDN2_NFC_INPUT);
   if (rc != IDN2_OK)
-    return rc;
+    goto out;
 
   if (!(flags & IDN2_TRANSITIONAL))
     {
@@ -110,8 +130,8 @@ label (const uint8_t * src, size_t srclen, uint8_t * dst, size_t * dstlen,
 
       if (rc != IDN2_OK)
 	{
-	  free(p);
-	  return rc;
+	  free (p);
+	  goto out;
 	}
     }
 
@@ -124,11 +144,25 @@ label (const uint8_t * src, size_t srclen, uint8_t * dst, size_t * dstlen,
   rc = _idn2_punycode_encode (plen, p, &tmpl, (char *) dst + 4);
   free (p);
   if (rc != IDN2_OK)
-    return rc;
+    goto out;
+
 
   *dstlen = 4 + tmpl;
 
-  return IDN2_OK;
+  if (check_roundtrip)
+    {
+      if (srclen_org != *dstlen || memcmp (src_org, dst, srclen_org))
+      {
+        rc = IDN2_ALABEL_ROUNDTRIP_FAILED;
+	goto out;
+      }
+    }
+
+  rc = IDN2_OK;
+
+out:
+  free (src_allocated);
+  return rc;
 }
 
 #define TR46_TRANSITIONAL_CHECK \
@@ -371,13 +405,17 @@ _tr46 (const uint8_t * domain_u8, uint8_t ** out, int flags)
  * Pass %IDN2_NFC_INPUT in @flags to convert input to NFC form before
  * further processing.  %IDN2_TRANSITIONAL and %IDN2_NONTRANSITIONAL
  * do already imply %IDN2_NFC_INPUT.
+ *
  * Pass %IDN2_ALABEL_ROUNDTRIP in @flags to
  * convert any input A-labels to U-labels and perform additional
- * testing (not implemented yet).
+ * testing. This is default since version 2.2.
+ * To switch this behavior off, pass IDN2_NO_ALABEL_ROUNDTRIP
+ *
  * Pass %IDN2_TRANSITIONAL to enable Unicode TR46
  * transitional processing, and %IDN2_NONTRANSITIONAL to enable
- * Unicode TR46 non-transitional processing.  Multiple flags may be
- * specified by binary or:ing them together.
+ * Unicode TR46 non-transitional processing.
+ *
+ * Multiple flags may be specified by binary or:ing them together.
  *
  * After version 2.0.3: %IDN2_USE_STD3_ASCII_RULES disabled by default.
  * Previously we were eliminating non-STD3 characters from domain strings
@@ -495,14 +533,19 @@ idn2_lookup_u8 (const uint8_t * src, uint8_t ** lookupname, int flags)
  * to be encoded in the locale's default coding system, and will be
  * transcoded to UTF-8 and NFC normalized by this function.
  *
- * Pass %IDN2_ALABEL_ROUNDTRIP in @flags to convert any input A-labels
- * to U-labels and perform additional testing.  Pass
- * %IDN2_TRANSITIONAL to enable Unicode TR46 transitional processing,
+ * Pass %IDN2_ALABEL_ROUNDTRIP in @flags to
+ * convert any input A-labels to U-labels and perform additional
+ * testing. This is default since version 2.2.
+ * To switch this behavior off, pass IDN2_NO_ALABEL_ROUNDTRIP
+ *
+ * Pass %IDN2_TRANSITIONAL to enable Unicode TR46 transitional processing,
  * and %IDN2_NONTRANSITIONAL to enable Unicode TR46 non-transitional
- * processing.  Multiple flags may be specified by binary or:ing them
- * together, for example %IDN2_ALABEL_ROUNDTRIP |
- * %IDN2_NONTRANSITIONAL.  The %IDN2_NFC_INPUT in @flags is always
- * enabled in this function.
+ * processing.
+ *
+ * Multiple flags may be specified by binary or:ing them together, for
+ * example %IDN2_ALABEL_ROUNDTRIP | %IDN2_NONTRANSITIONAL.
+ *
+ * The %IDN2_NFC_INPUT in @flags is always enabled in this function.
  *
  * After version 0.11: @lookupname may be NULL to test lookup of @src
  * without allocating memory.
diff --git a/src/blurbs.h b/src/blurbs.h
index 2d71ed0..4fdea0f 100644
--- a/src/blurbs.h
+++ b/src/blurbs.h
@@ -1,5 +1,5 @@
 /* blurbs.h - warranty and conditions blurbs
-   Copyright (C) 2011-2017 Simon Josefsson
+   Copyright (C) 2011-2019 Simon Josefsson, Tim Ruehsen
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -16,7 +16,7 @@
 */
 
 #define GREETING							\
-  "Copyright (C) 2011-2017  Simon Josefsson\n"				\
+  "Copyright (C) 2011-2019  Simon Josefsson, Tim Ruehsen\n"				\
   "This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n" \
   "This is free software, and you are welcome to redistribute it\n"	\
   "under certain conditions; type `show c' for details.\n\n"
diff --git a/src/idn2.c b/src/idn2.c
index 9fdaf12..161ab56 100644
--- a/src/idn2.c
+++ b/src/idn2.c
@@ -1,5 +1,5 @@
 /* idn2.c - command line interface to libidn2
-   Copyright (C) 2011-2017 Simon Josefsson
+   Copyright (C) 2011-2019 Simon Josefsson, Tim Ruehsen
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -50,7 +50,7 @@ const char version_etc_copyright[] =
   /* Do *not* mark this string for translation.  %s is a copyright
      symbol suitable for this locale, and %d is the copyright
      year.  */
-  "Copyright %s %d Simon Josefsson.";
+  "Copyright 2011-%s %d Simon Josefsson, Tim Ruehsen.";
 
 static void
 usage (int status)
@@ -78,23 +78,24 @@ to signal the end of parameters, as in `idn2 --quiet -- -foo'.\n\
 Mandatory arguments to long options are mandatory for short options too.\n\
 "), stdout);
       fputs (_("\
-  -h, --help               Print help and exit\n\
-  -V, --version            Print version and exit\n\
+  -h, --help                Print help and exit\n\
+  -V, --version             Print version and exit\n\
 "), stdout);
       fputs (_("\
-  -d, --decode             Decode (punycode) domain name\n\
-  -l, --lookup             Lookup domain name (default)\n\
-  -r, --register           Register label\n\
+  -d, --decode              Decode (punycode) domain name\n\
+  -l, --lookup              Lookup domain name (default)\n\
+  -r, --register            Register label\n\
 "), stdout);
       fputs (_("\
-  -T, --tr46t              Enable TR46 transitional processing\n\
-  -N, --tr46nt             Enable TR46 non-transitional processing\n\
-      --no-tr46            Disable TR46 processing\n\
+  -T, --tr46t               Enable TR46 transitional processing\n\
+  -N, --tr46nt              Enable TR46 non-transitional processing\n\
+      --no-tr46             Disable TR46 processing\n\
 "), stdout);
       fputs (_("\
-      --usestd3asciirules  Enable STD3 ASCII rules\n\
-      --debug              Print debugging information\n\
-      --quiet              Silent operation\n\
+      --usestd3asciirules   Enable STD3 ASCII rules\n\
+      --no-alabelroundtrip  Disable ALabel rountrip for lookups\n\
+      --debug               Print debugging information\n\
+      --quiet               Silent operation\n\
 "), stdout);
       emit_bug_reporting_address ();
     }
@@ -201,7 +202,7 @@ main (int argc, char *argv[])
   if (args_info.version_given)
     {
       version_etc (stdout, "idn2", PACKAGE_NAME, VERSION,
-		   "Simon Josefsson", (char *) NULL);
+		   "Simon Josefsson, Tim Ruehsen", (char *) NULL);
       return EXIT_SUCCESS;
     }
 
@@ -230,6 +231,9 @@ main (int argc, char *argv[])
   if (flags && args_info.usestd3asciirules_given)
     flags |= IDN2_USE_STD3_ASCII_RULES;
 
+  if (flags && args_info.no_alabelroundtrip_given)
+    flags |= IDN2_NO_ALABEL_ROUNDTRIP;
+
   for (cmdn = 0; cmdn < args_info.inputs_num; cmdn++)
     process_input (args_info.inputs[cmdn], flags | IDN2_NFC_INPUT);
 
diff --git a/src/idn2.ggo b/src/idn2.ggo
index 3732cb5..04a5360 100644
--- a/src/idn2.ggo
+++ b/src/idn2.ggo
@@ -20,5 +20,6 @@ option "tr46t" T "Enable TR46 transitional processing" flag off
 option "tr46nt" N "Enable TR46 non-transitional processing" flag off
 option "no-tr46" - "Disable TR46 processing" flag off
 option "usestd3asciirules" - "Enable STD3 ASCII rules" flag off
+option "no-alabelroundtrip" - "Disable ALabel roundtrip for lookups" flag off
 option "debug" - "Print debugging information" flag off
 option "quiet" - "Silent operation" flag off
-- 
1.8.3.1

