dnsmasq received SIGSEGV!
Jan screwed up!
Simon says: `fortune`
New patch in attachment

reproducer:
set up a line like: server=/somedomain/$some_ip
add an regex address match
query for somedomain

Depending on order of config statements &&/|| if $some_ip could be reached
dnsmasq dies in forward.c because that part is not "regex ready" (NULL deref,
because regex do not have a domain string).
Jepp, i have totally overseen this interaction (like prop. many other).

Solution:
Let's also match server lines with a regex!

I also changed the order in which an "if" is evaluated, after a little run in my
brains logic evaluator this should be a NOP (besides doing a cheap flag test
before an expensive string compare/regex compare), but i could be wrong...
Regex matches on "server" do not try to find the longest match, this could give
you some funky problems if you use very elaborate matching rules.
Maybe i will take that server match out again, it's enough if dnsmasq does not
crash in that place...

Nearby, do never ever set a regex on "local", dragons be there...
Which reminds me to state:
Patch is only lightly tested in my small environment, there are prop. a million
bugs left (as before), this is just a quick fix for the problem at hand.
Take this incident is a friendly little remainder that this change is a deep
poke into dnsmasq's internals.

I better do not touch any source code ever again...

New patch against 2.52 attached. Apply with -p1.
Also with conditional compilation, so don't forget to set HAVE_REGEX to you
COPTS like:
make COPTS=-DHAVE_REGEX
or uncomment it in config.h

Greetings
        Jan

-- 
John encountered the following Zen-like line in his generated XML:
        <>There is no phenotype</>
He was enlightened.
=== modified file 'Makefile'
--- upstream/Makefile	2010-01-23 19:48:37 +0000
+++ regex/Makefile	2010-01-23 19:49:07 +0000
@@ -32,8 +32,9 @@
 PO = po
 MAN = man
 
-DNSMASQ_CFLAGS=`echo $(COPTS) | ../bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --cflags dbus-1` 
-DNSMASQ_LIBS=  `echo $(COPTS) | ../bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --libs dbus-1` 
+DNSMASQ_CFLAGS=`echo $(COPTS) | ../bld/pkg-wrapper HAVE_DBUS  $(PKG_CONFIG) --cflags dbus-1` 
+DNSMASQ_LIBS=  `echo $(COPTS) | ../bld/pkg-wrapper HAVE_DBUS  $(PKG_CONFIG) --libs dbus-1` 
+DNSMASQ_LIBS +=`echo $(COPTS) | ../bld/pkg-wrapper HAVE_REGEX $(PKG_CONFIG) --libs libpcre`
 SUNOS_LIBS= `if uname | grep SunOS 2>&1 >/dev/null; then echo -lsocket -lnsl -lposix4; fi`
 
 OBJS = cache.o rfc1035.o util.o option.o forward.o network.o \

=== modified file 'src/config.h'
--- upstream/src/config.h	2010-01-23 19:48:37 +0000
+++ regex/src/config.h	2010-01-23 19:49:07 +0000
@@ -147,6 +147,10 @@
    define some methods to allow (re)configuration of the upstream DNS 
    servers via DBus.
 
+HAVE_REGEX
+   Define this if you want to link against lib pcre to get regex
+   support in "address=" matches
+
 NOTES:
    For Linux you should define 
       HAVE_LINUX_NETWORK
@@ -171,6 +175,7 @@
 #define HAVE_SCRIPT
 /* #define HAVE_BROKEN_RTC */
 /* #define HAVE_DBUS */
+/* #define HAVE_REGEX */
 
 /* Allow TFTP to be disabled with COPTS=-DNO_TFTP */
 #ifdef NO_TFTP

=== modified file 'src/dnsmasq.c'
--- upstream/src/dnsmasq.c	2010-01-23 19:48:37 +0000
+++ regex/src/dnsmasq.c	2010-01-23 19:49:08 +0000
@@ -37,6 +37,10 @@
 "no-"
 #endif
 "DBus "
+#ifndef HAVE_REGEX
+"no-"
+#endif
+"regex "
 #ifndef LOCALEDIR
 "no-"
 #endif

=== modified file 'src/dnsmasq.h'
--- upstream/src/dnsmasq.h	2010-01-23 19:48:37 +0000
+++ regex/src/dnsmasq.h	2010-01-23 19:49:08 +0000
@@ -120,6 +120,10 @@
 #include <priv.h>
 #endif
 
+#ifdef HAVE_REGEX
+#  include <pcre.h>
+#endif
+
 /* daemon is function in the C library.... */
 #define daemon dnsmasq_daemon
 
@@ -319,6 +323,7 @@
 #define SERV_MARK            256  /* for mark-and-delete */
 #define SERV_TYPE    (SERV_HAS_DOMAIN | SERV_FOR_NODOTS)
 #define SERV_COUNTED         512  /* workspace for log code */
+#define SERV_IS_REGEX       1024  /* server entry is a regex */
 
 struct serverfd {
   int fd;
@@ -337,6 +342,10 @@
   char interface[IF_NAMESIZE+1];
   struct serverfd *sfd; 
   char *domain; /* set if this server only handles a domain. */ 
+#ifdef HAVE_REGEX
+  pcre *regex;
+  pcre_extra *pextra;
+#endif
   int flags, tcpfd;
   unsigned int queries, failed_queries;
   struct server *next; 

=== modified file 'src/forward.c'
--- upstream/src/forward.c	2010-01-23 19:48:37 +0000
+++ regex/src/forward.c	2010-02-04 14:39:20 +0000
@@ -149,12 +149,38 @@
       }
     else if (serv->flags & SERV_HAS_DOMAIN)
       {
-	unsigned int domainlen = strlen(serv->domain);
-	char *matchstart = qdomain + namelen - domainlen;
-	if (namelen >= domainlen &&
-	    hostname_isequal(matchstart, serv->domain) &&
-	    domainlen >= matchlen &&
-	    (domainlen == 0 || namelen == domainlen || *(serv->domain) == '.' || *(matchstart-1) == '.' ))
+	unsigned int domainlen = matchlen;
+	int serverhit = 0;
+
+#ifdef HAVE_REGEX
+	if (serv->flags & SERV_IS_REGEX)
+	  {
+	    int captcount = 0;
+	    if (pcre_fullinfo(serv->regex, serv->pextra, PCRE_INFO_CAPTURECOUNT, &captcount) == 0)
+	      {
+		/* C99 dyn-array, or alloca must be used */
+		int ovect[(captcount + 1) * 3];
+		if (pcre_exec(serv->regex, serv->pextra, qdomain, namelen, 0, 0, ovect, (captcount + 1) * 3) > 0)
+		  {
+		    domainlen = (unsigned int) (ovect[1] - ovect[0]);
+		    if (domainlen >= matchlen)
+		      serverhit = 1;
+		  }
+	      }
+	  }
+	else
+#endif
+	  {
+	    domainlen = strlen(serv->domain);
+	    char *matchstart = qdomain + namelen - domainlen;
+	    if (namelen >= domainlen &&
+	        hostname_isequal(matchstart, serv->domain) &&
+	        domainlen >= matchlen &&
+	        (domainlen == 0 || namelen == domainlen || *(serv->domain) == '.' || *(matchstart-1) == '.' ))
+	       serverhit = 1;
+	  }
+
+	if (serverhit)
 	  {
 	    unsigned short sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6;
 	    *type = SERV_HAS_DOMAIN;
@@ -201,6 +227,27 @@
   return  flags;
 }
 
+static int match_domain_for_forward(char *domain, struct server *serv)
+{
+  int ret_val = 0;
+  if(serv->flags & SERV_IS_REGEX)
+    {
+#ifdef HAVE_REGEX
+      int captcount = 0;
+      if (pcre_fullinfo(serv->regex, serv->pextra, PCRE_INFO_CAPTURECOUNT, &captcount) == 0)
+	{
+	  /* C99 dyn-array, or alloca must be used */
+	  int ovect[(captcount + 1) * 3];
+	  ret_val = pcre_exec(serv->regex, serv->pextra, domain,
+	                      strlen(domain), 0, 0, ovect, (captcount + 1) * 3) > 0;
+	}
+#endif
+    }
+  else
+    ret_val = hostname_isequal(domain, serv->domain);
+  return ret_val;
+}
+
 static int forward_query(int udpfd, union mysockaddr *udpaddr,
 			 struct all_addr *dst_addr, unsigned int dst_iface,
 			 HEADER *header, size_t plen, time_t now, struct frec *forward)
@@ -286,8 +333,8 @@
 	     must be NULL also. */
 	  
 	  if (type == (start->flags & SERV_TYPE) &&
-	      (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) &&
-	      !(start->flags & SERV_LITERAL_ADDRESS))
+	      !(start->flags & SERV_LITERAL_ADDRESS) &&
+	      (type != SERV_HAS_DOMAIN || match_domain_for_forward(domain, start)))
 	    {
 	      int fd;
 

=== modified file 'src/network.c'
--- upstream/src/network.c	2010-01-23 19:48:37 +0000
+++ regex/src/network.c	2010-01-23 19:49:08 +0000
@@ -741,7 +741,7 @@
 	  char *s1, *s2;
 	  if (!(new->flags & SERV_HAS_DOMAIN))
 	    s1 = _("unqualified"), s2 = _("names");
-	  else if (strlen(new->domain) == 0)
+	  else if (new->domain && strlen(new->domain) == 0)
 	    s1 = _("default"), s2 = "";
 	  else
 	    s1 = _("domain"), s2 = new->domain;

=== modified file 'src/option.c'
--- upstream/src/option.c	2010-01-23 19:48:37 +0000
+++ regex/src/option.c	2010-01-23 19:49:08 +0000
@@ -1405,10 +1405,16 @@
 	    arg++;
 	    while ((end = split_chr(arg, '/')))
 	      {
-		char *domain = NULL;
+		char *domain = NULL, *regex = NULL;
+		char *real_end = arg + strlen(arg);
 		/* # matches everything and becomes a zero length domain string */
 		if (strcmp(arg, "#") == 0)
 		  domain = "";
+		else if (*arg == ':' && *(real_end - 1) == ':')
+		  {
+		     *(real_end - 1) = '\0';
+		     regex = arg + 1;
+		  }
 		else if (strlen (arg) != 0 && !(domain = canonicalise_opt(arg)))
 		  option = '?';
 		serv = opt_malloc(sizeof(struct server));
@@ -1416,7 +1422,28 @@
 		serv->next = newlist;
 		newlist = serv;
 		serv->domain = domain;
-		serv->flags = domain ? SERV_HAS_DOMAIN : SERV_FOR_NODOTS;
+		serv->flags = domain || regex ? SERV_HAS_DOMAIN : SERV_FOR_NODOTS;
+		if (regex)
+		  {
+#ifdef HAVE_REGEX
+		    const char *error;
+		    int erroff;
+		    serv->regex = pcre_compile(regex, 0, &error, &erroff, NULL);
+
+		    if (!serv->regex)
+		      {
+			 option = '?';
+			 problem = (char *) error;
+			 break;
+		      }
+		    serv->flags |= SERV_IS_REGEX;
+		    serv->pextra = pcre_study(serv->regex, 0, &error);
+#else
+		    option = '?';
+		    problem = "Using a regex while server was configured without regex support!";
+		    break;
+#endif
+		  }
 		arg = end;
 	      }
 	    if (!newlist)

Reply via email to