Hi,

I am submitting patch enabling IDN usage for input parameters. We have
support for IDN, but because locale is not properly initialized, no
parameters using special characters are accepted.

Patch #1 adds domain_len to more places, quite similar to struct server
and struct serv_local. It computes length of saved domain only once and
uses it on various processing. Just small optimization on not critical
bits, since main lookup_domain already uses it. I think it should be
used on more places.

I think we could define someting like struct binstring { char *str, u16
len }; and use it more in code. Pairs of string and its length are
common and handled different way. Because just basic char * is used on
many places, strlen is called again and again, often not required.
Especially might be useful for DHCP binary strings processing.

Patch #2 enables --address=/münchen.de/háčkyčárky.cz/ on input even on
builds without defined LOCALEDIR. Which is missing on Fedora and it
seems IDN support is effectively inactive that way. Adds --auth-zone
support for IDN too.

Patch #3 disables IDN_TRANSITIONAL mode. I wanted to try some refused
IDN name, but transitional mode accepts almost any character. It accepts
also --address=/💻🚱.test/. I think there is no need to support such
obscure names, but we should accept normal names supported by browsers.
Since I think IDN 2003 support were not properly enabled before, I think
we should start just from IDN 2008 standard.

I think there is still something weird, because --address=/испытание./
is accepted but --address=/испытание/ is not. Improvement anyway.

-- 
Petr Menšík
Software Engineer
Red Hat, http://www.redhat.com/
email: pemen...@redhat.com
PGP: DFCF908DB7C87E8E529925BC4931CA5B6C9FC5CB
From f1a6dca099305128ab5304a89748a96932f32256 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemen...@redhat.com>
Date: Thu, 30 Sep 2021 22:19:45 +0200
Subject: [PATCH 3/4] Disable transitional IDN rules, accept only sane names
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Transitional encoding accepts every emoticon you can think about.
Because setlocale were not enabled before, IDN 2003 input was not
accepted by dnsmasq. It makes no sense therefore to maintain backward
compatibility. Accept only proper encoded unicode names and reject
random unicode characters.

Signed-off-by: Petr Menšík <pemen...@redhat.com>
---
 src/util.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/util.c b/src/util.c
index e641905..7bef630 100644
--- a/src/util.c
+++ b/src/util.c
@@ -233,8 +233,6 @@ char *canonicalise(char *in, int *nomem)
     {
 #  ifdef HAVE_LIBIDN2
       rc = idn2_to_ascii_lz(in, &ret, IDN2_NONTRANSITIONAL);
-      if (rc == IDN2_DISALLOWED)
-	rc = idn2_to_ascii_lz(in, &ret, IDN2_TRANSITIONAL);
 #  else
       rc = idna_to_ascii_lz(in, &ret, 0);
 #  endif
-- 
2.31.1

From c079c47803029f620c51d16b807d9c48a23c99dd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemen...@redhat.com>
Date: Thu, 30 Sep 2021 22:15:39 +0200
Subject: [PATCH 2/4] Enable locale support for IDN at startup
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

--address=/münchen.de/ is not accepted unless LOCALEDIR is defined on
build. It is not by default. If LIBIDN1 or 2 is defined, call setlocale
to initialize locale required to translate domains to ascii form.

Signed-off-by: Petr Menšík <pemen...@redhat.com>
---
 src/dnsmasq.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index c7fa024..cfbbbf1 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -17,6 +17,9 @@
 /* Declare static char *compiler_opts  in config.h */
 #define DNSMASQ_COMPILE_OPTS
 
+#if defined(HAVE_IDN) || defined(HAVE_LIBIDN2) || defined(LOCALEDIR)
+#include <locale.h>
+#endif
 #include "dnsmasq.h"
 
 struct daemon *daemon;
@@ -69,8 +72,10 @@ int main (int argc, char **argv)
   int tftp_prefix_missing = 0;
 #endif
 
-#ifdef LOCALEDIR
+#if defined(HAVE_IDN) || defined(HAVE_LIBIDN2) || defined(LOCALEDIR)
   setlocale(LC_ALL, "");
+#endif
+#ifdef LOCALEDIR
   bindtextdomain("dnsmasq", LOCALEDIR); 
   textdomain("dnsmasq");
 #endif
-- 
2.31.1

From 31b925efc54161ccee41defa132270d8bac801ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemen...@redhat.com>
Date: Thu, 30 Sep 2021 17:30:40 +0200
Subject: [PATCH 1/4] Add domain_len to domain parameters in several structures
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Similar to serv_local structure, there is multiple structures with
domain stored from configuration. Compute and store domain length in
structure and avoid re-computing it on each use.

Add also IDN support to auth-zone.

Signed-off-by: Petr Menšík <pemen...@redhat.com>
---
 src/auth.c    | 15 +++++++--------
 src/dnsmasq.h |  3 +++
 src/forward.c |  6 +++---
 src/loop.c    |  4 ++--
 src/network.c |  6 +++---
 src/option.c  | 15 +++++++++++++--
 6 files changed, 31 insertions(+), 18 deletions(-)

diff --git a/src/auth.c b/src/auth.c
index aa2403b..52b6ed1 100644
--- a/src/auth.c
+++ b/src/auth.c
@@ -72,22 +72,21 @@ static int filter_zone(struct auth_zone *zone, int flag, union all_addr *addr_u)
 int in_zone(struct auth_zone *zone, char *name, char **cut)
 {
   size_t namelen = strlen(name);
-  size_t domainlen = strlen(zone->domain);
 
   if (cut)
     *cut = NULL;
   
-  if (namelen >= domainlen && 
-      hostname_isequal(zone->domain, &name[namelen - domainlen]))
+  if (namelen >= zone->domain_len &&
+      hostname_isequal(zone->domain, &name[namelen - zone->domain_len]))
     {
-      
-      if (namelen == domainlen)
+
+      if (namelen == zone->domain_len)
 	return 1;
-      
-      if (name[namelen - domainlen - 1] == '.')
+
+      if (name[namelen - zone->domain_len - 1] == '.')
 	{
 	  if (cut)
-	    *cut = &name[namelen - domainlen - 1]; 
+	    *cut = &name[namelen - zone->domain_len - 1];
 	  return 1;
 	}
     }
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 3fdc1b0..5132ee1 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -409,6 +409,7 @@ struct addrlist {
 
 struct auth_zone {
   char *domain;
+  u16 domain_len;
   struct auth_name_list {
     char *name;
     int flags;
@@ -611,12 +612,14 @@ struct serv_local {
 
 struct rebind_domain {
   char *domain;
+  u16 domain_len;
   struct rebind_domain *next;
 };
 
 struct ipsets {
   char **sets;
   char *domain;
+  u16 domain_len;
   struct ipsets *next;
 };
 
diff --git a/src/forward.c b/src/forward.c
index ceecfcd..f868734 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -152,10 +152,10 @@ static void server_send_log(struct server *server, int fd,
 static int domain_no_rebind(char *domain)
 {
   struct rebind_domain *rbd;
-  size_t tlen, dlen = strlen(domain);
+  u16 dlen = strlen(domain);
   
   for (rbd = daemon->no_rebind; rbd; rbd = rbd->next)
-    if (dlen >= (tlen = strlen(rbd->domain)) && strcmp(rbd->domain, &domain[dlen - tlen]) == 0)
+    if (dlen >= rbd->domain_len && strcmp(rbd->domain, &domain[dlen - rbd->domain_len]) == 0)
       return 1;
 
   return 0;
@@ -575,7 +575,7 @@ static struct ipsets *domain_find_sets(struct ipsets *setlist, const char *domai
   unsigned int matchlen = 0;
   for (ipset_pos = setlist; ipset_pos; ipset_pos = ipset_pos->next) 
     {
-      unsigned int domainlen = strlen(ipset_pos->domain);
+      unsigned int domainlen = ipset_pos->domain_len;
       const char *matchstart = domain + namelen - domainlen;
       if (namelen >= domainlen && hostname_isequal(matchstart, ipset_pos->domain) &&
           (domainlen == 0 || namelen == domainlen || *(matchstart - 1) == '.' ) &&
diff --git a/src/loop.c b/src/loop.c
index 01f0c28..a66102d 100644
--- a/src/loop.c
+++ b/src/loop.c
@@ -30,7 +30,7 @@ void loop_send_probes()
    /* Loop through all upstream servers not for particular domains, and send a query to that server which is
       identifiable, via the uid. If we see that query back again, then the server is looping, and we should not use it. */
    for (serv = daemon->servers; serv; serv = serv->next)
-     if (strlen(serv->domain) == 0 &&
+     if (serv->domain_len == 0 &&
 	 !(serv->flags & (SERV_FOR_NODOTS)))
        {
 	 ssize_t len = loop_make_probe(serv->uid);
@@ -98,7 +98,7 @@ int detect_loop(char *query, int type)
   uid = strtol(query, NULL, 16);
 
   for (serv = daemon->servers; serv; serv = serv->next)
-    if (strlen(serv->domain) == 0 &&
+    if (serv->domain_len == 0 &&
 	!(serv->flags & SERV_LOOP) &&
 	uid == serv->uid)
       {
diff --git a/src/network.c b/src/network.c
index 3c1c176..9450bb7 100644
--- a/src/network.c
+++ b/src/network.c
@@ -1532,7 +1532,7 @@ void check_servers(int no_loop_check)
 	  
 	  /* Disable DNSSEC validation when using server=/domain/.... servers
 	     unless there's a configured trust anchor. */
-	  if (strlen(serv->domain) != 0)
+	  if (serv->domain_len != 0)
 	    {
 	      struct ds_config *ds;
 	      char *domain = serv->domain;
@@ -1592,7 +1592,7 @@ void check_servers(int no_loop_check)
       if (++count > SERVERS_LOGGED)
 	continue;
       
-      if (strlen(serv->domain) != 0 || (serv->flags & SERV_FOR_NODOTS))
+      if (serv->domain_len != 0 || (serv->flags & SERV_FOR_NODOTS))
 	{
 	  char *s1, *s2, *s3 = "", *s4 = "";
 
@@ -1602,7 +1602,7 @@ void check_servers(int no_loop_check)
 #endif
 	  if (serv->flags & SERV_FOR_NODOTS)
 	    s1 = _("unqualified"), s2 = _("names");
-	  else if (strlen(serv->domain) == 0)
+	  else if (serv->domain_len == 0)
 	    s1 = _("default"), s2 = "";
 	  else
 	    s1 = _("domain"), s2 = serv->domain, s4 = (serv->flags & SERV_WILDCARD) ? "*" : "";
diff --git a/src/option.c b/src/option.c
index 4e533be..74d87d0 100644
--- a/src/option.c
+++ b/src/option.c
@@ -671,6 +671,13 @@ static char *canonicalise_opt(char *s)
   return ret;
 }
 
+static char *canonicalise_optlen(char *s, u16 *len)
+{
+  char *ret = canonicalise_opt(s);
+  *len = ret ? strlen(ret) : 0;
+  return ret;
+}
+
 static int numeric_check(char *a)
 {
   char *p;
@@ -2235,7 +2242,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
 	comma = split(arg);
 		
 	new = opt_malloc(sizeof(struct auth_zone));
-	new->domain = opt_string_alloc(arg);
+	new->domain = canonicalise_optlen(arg, &new->domain_len);
+	if (!new->domain)
+	  ret_err_free(_("invalid auth-zone"), new);
 	new->subnet = NULL;
 	new->exclude = NULL;
 	new->interface_names = NULL;
@@ -2728,7 +2737,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
 	do {
 	  comma = split_chr(arg, '/');
 	  new = opt_malloc(sizeof(struct  rebind_domain));
-	  new->domain = canonicalise_opt(arg);
+	  new->domain = canonicalise_optlen(arg, &new->domain_len);
 	  new->next = daemon->no_rebind;
 	  daemon->no_rebind = new;
 	  arg = comma;
@@ -2897,6 +2906,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
 		 ipsets = ipsets->next;
 		 memset(ipsets, 0, sizeof(struct ipsets));
 		 ipsets->domain = domain;
+		 ipsets->domain_len = strlen(domain);
 		 arg = end;
 	       }
 	   } 
@@ -2906,6 +2916,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
 	     ipsets = ipsets->next;
 	     memset(ipsets, 0, sizeof(struct ipsets));
 	     ipsets->domain = "";
+	     ipsets->domain_len = 0;
 	   }
 	 
 	 if (!arg || !*arg)
-- 
2.31.1

_______________________________________________
Dnsmasq-discuss mailing list
Dnsmasq-discuss@lists.thekelleys.org.uk
https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss

Reply via email to