From 59e470381f84f2fdf0640c7bc67827f3f0c64784 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
Date: Fri, 2 Nov 2018 22:39:39 +0000
Subject: [PATCH 11/30] Free config file values on parsing errors.

This time I have a little bit more controversal patches. But I think
still useful. They fixes memory leaks that might occur in some cases.
Most dnsmasq errors is fatal, so it does not matter. But some are not.
Some parts are reloaded on SIGHUP signal, so it might leak more than once.

Some example when it changes the failures. Use dhcp-options file with
this content:

tag:error,vendor:redhat
option:ntp-server,1.2.3.4.5
option6:ntp-server,[:::]

Is not fatal and dnsmasq will start. On each reload command, it would
leak some memory. I validated it using valgrind --leak-check=full
dnsmasq -d. This patch fixes it. It introduces something that might be
considered constructor and destructor of selected structures.

Signed-off-by: Kevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>
---
 src/option.c | 533 ++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 352 insertions(+), 181 deletions(-)

--- a/src/option.c
+++ b/src/option.c
@@ -577,14 +577,15 @@ static void *opt_malloc(size_t size)
   return ret;
 }
 
-static char *opt_string_alloc(char *cp)
+static char *opt_string_alloc(const char *cp)
 {
   char *ret = NULL;
+  size_t len;
   
-  if (cp && strlen(cp) != 0)
+  if (cp && (len = strlen(cp)) != 0)
     {
-      ret = opt_malloc(strlen(cp)+1);
-      strcpy(ret, cp); 
+      ret = opt_malloc(len+1);
+      memcpy(ret, cp, len+1); 
       
       /* restore hidden metachars */
       unhide_metas(ret);
@@ -759,6 +760,8 @@ static void do_usage(void)
 }
 
 #define ret_err(x) do { strcpy(errstr, (x)); return 0; } while (0)
+#define ret_err_free(x,m) do { strcpy(errstr, (x)); free((m)); return 0; } while (0)
+#define goto_err(x) do { strcpy(errstr, (x)); goto on_error; } while (0)
 
 static char *parse_mysockaddr(char *arg, union mysockaddr *addr) 
 {
@@ -904,6 +907,8 @@ static struct server *add_rev4(struct in
       p += sprintf(p, "%d.", (a >> 24) & 0xff);
       break;
     default:
+      free(serv->domain);
+      free(serv);
       return NULL;
     }
 
@@ -958,6 +963,97 @@ static char *set_prefix(char *arg)
    return arg;
 }
 
+static struct dhcp_netid *
+dhcp_netid_create(const char *net, struct dhcp_netid *next)
+{
+  struct dhcp_netid *tt;
+  tt = opt_malloc(sizeof (struct dhcp_netid));
+  tt->net = opt_string_alloc(net);
+  tt->next = next;
+  return tt;
+}
+
+static void dhcp_netid_free(struct dhcp_netid *nid)
+{
+  while (nid)
+    {
+      struct dhcp_netid *tmp = nid;
+      nid = nid->next;
+      free(tmp->net);
+      free(tmp);
+    }
+}
+
+/* Parse one or more tag:s before parameters.
+ * Moves arg to the end of tags. */
+static struct dhcp_netid * dhcp_tags(char **arg)
+{
+  struct dhcp_netid *id = NULL;
+
+  while (is_tag_prefix(*arg))
+    {
+      char *comma = split(*arg);
+      id = dhcp_netid_create((*arg)+4, id);
+      *arg = comma;
+    };
+  if (!*arg)
+    {
+      dhcp_netid_free(id);
+      id = NULL;
+    }
+  return id;
+}
+
+static void dhcp_netid_list_free(struct dhcp_netid_list *netid)
+{
+  while (netid)
+    {
+      struct dhcp_netid_list *tmplist = netid;
+      netid = netid->next;
+      dhcp_netid_free(tmplist->list);
+      free(tmplist);
+    }
+}
+
+static void dhcp_config_free(struct dhcp_config *config)
+{
+  if (config)
+    {
+      struct hwaddr_config *hwaddr = config->hwaddr;
+      while (hwaddr)
+        {
+	  struct hwaddr_config *tmp = hwaddr;
+          hwaddr = hwaddr->next;
+	  free(tmp);
+        }
+      dhcp_netid_list_free(config->netid);
+      if (config->flags & CONFIG_CLID)
+        free(config->clid);
+      free(config);
+    }
+}
+
+static void dhcp_context_free(struct dhcp_context *ctx)
+{
+  if (ctx)
+    {
+      dhcp_netid_free(ctx->filter);
+      free(ctx->netid.net);
+      free(ctx->template_interface);
+      free(ctx);
+    }
+}
+
+static void dhcp_opt_free(struct dhcp_opt *opt)
+{
+  if (opt->flags & DHOPT_VENDOR)
+    free(opt->u.vendor_class);
+  dhcp_netid_free(opt->netid);
+  free(opt->val);
+  free(opt);
+}
+
+
 /* This is too insanely large to keep in-line in the switch */
 static int parse_dhcp_opt(char *errstr, char *arg, int flags)
 {
@@ -965,7 +1061,6 @@ static int parse_dhcp_opt(char *errstr,
   char lenchar = 0, *cp;
   int addrs, digs, is_addr, is_addr6, is_hex, is_dec, is_string, dots;
   char *comma = NULL;
-  struct dhcp_netid *np = NULL;
   u16 opt_len = 0;
   int is6 = 0;
   int option_ok = 0;
@@ -1052,14 +1147,9 @@ static int parse_dhcp_opt(char *errstr,
 	}
       else
 	{
-	  new->netid = opt_malloc(sizeof (struct dhcp_netid));
 	  /* allow optional "net:" or "tag:" for consistency */
-	  if (is_tag_prefix(arg))
-	    new->netid->net = opt_string_alloc(arg+4);
-	  else
-	    new->netid->net = opt_string_alloc(set_prefix(arg));
-	  new->netid->next = np;
-	  np = new->netid;
+	  const char *name = (is_tag_prefix(arg)) ? arg+4 : set_prefix(arg);
+	  new->netid = dhcp_netid_create(name, new->netid);
 	}
       
       arg = comma; 
@@ -1069,7 +1159,7 @@ static int parse_dhcp_opt(char *errstr,
   if (is6)
     {
       if (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE))
-	ret_err(_("unsupported encapsulation for IPv6 option"));
+	goto_err(_("unsupported encapsulation for IPv6 option"));
       
       if (opt_len == 0 &&
 	  !(new->flags & DHOPT_RFC3925))
@@ -1083,7 +1173,7 @@ static int parse_dhcp_opt(char *errstr,
   
   /* option may be missing with rfc3925 match */
   if (!option_ok)
-    ret_err(_("bad dhcp-option"));
+    goto_err(_("bad dhcp-option"));
   
   if (comma)
     {
@@ -1151,10 +1241,10 @@ static int parse_dhcp_opt(char *errstr,
 	  is_string = is_dec = is_hex = 0;
 	  
 	  if (!is6 && (!is_addr || dots == 0))
-	    ret_err(_("bad IP address"));
+	    goto_err(_("bad IP address"));
 
 	   if (is6 && !is_addr6)
-	     ret_err(_("bad IPv6 address"));
+	     goto_err(_("bad IPv6 address"));
 	}
       /* or names */
       else if (opt_len & (OT_NAME | OT_RFC1035_NAME | OT_CSTRING))
@@ -1247,7 +1337,7 @@ static int parse_dhcp_opt(char *errstr,
 	      comma = split(cp);
 	      slash = split_chr(cp, '/');
 	      if (!inet_pton(AF_INET, cp, &in))
-		ret_err(_("bad IPv4 address"));
+		goto_err(_("bad IPv4 address"));
 	      if (!slash)
 		{
 		  memcpy(op, &in, INADDRSZ);
@@ -1292,8 +1382,8 @@ static int parse_dhcp_opt(char *errstr,
 		  op += IN6ADDRSZ;
 		  continue;
 		}
-	  
-	      ret_err(_("bad IPv6 address"));
+
+	      goto_err(_("bad IPv6 address"));
 	    } 
 	  new->len = op - new->val;
 	}
@@ -1320,7 +1410,7 @@ static int parse_dhcp_opt(char *errstr,
 		  if (strcmp (arg, ".") != 0)
 		    {
 		      if (!(dom = canonicalise_opt(arg)))
-			ret_err(_("bad domain in dhcp-option"));
+			goto_err(_("bad domain in dhcp-option"));
 			
 		      domlen = strlen(dom) + 2;
 		    }
@@ -1414,7 +1504,7 @@ static int parse_dhcp_opt(char *errstr,
 		{
 		  char *dom = canonicalise_opt(arg);
 		  if (!dom)
-		    ret_err(_("bad domain in dhcp-option"));
+		    goto_err(_("bad domain in dhcp-option"));
 		    		  
 		  newp = opt_malloc(len + strlen(dom) + 2);
 		  
@@ -1452,14 +1542,14 @@ static int parse_dhcp_opt(char *errstr,
       ((new->len > 255) || 
       (new->len > 253 && (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE))) ||
        (new->len > 250 && (new->flags & DHOPT_RFC3925))))
-    ret_err(_("dhcp-option too long"));
+    goto_err(_("dhcp-option too long"));
   
   if (flags == DHOPT_MATCH)
     {
       if ((new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR)) ||
 	  !new->netid ||
 	  new->netid->next)
-	ret_err(_("illegal dhcp-match"));
+	goto_err(_("illegal dhcp-match"));
        
       if (is6)
 	{
@@ -1484,6 +1574,9 @@ static int parse_dhcp_opt(char *errstr,
     }
     
   return 1;
+on_error:
+  dhcp_opt_free(new);
+  return 0;
 }
 
 #endif
@@ -1498,6 +1591,16 @@ void reset_option_bool(unsigned int opt)
   option_var(opt) &= ~(option_val(opt));
 }
 
+static void server_list_free(struct server *list)
+{
+  while (list)
+    {
+      struct server *tmp = list;
+      list = list->next;
+      free(tmp);
+    }
+}
+
 static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line, int servers_only)
 {      
   int i;
@@ -1679,13 +1782,13 @@ static int one_opt(int option, char *arg
 	      /* has subnet+len */
 	      err = parse_mysockaddr(arg, &new->addr);
 	      if (err)
-		ret_err(err);
+		ret_err_free(err, new);
 	      if (!atoi_check(end, &new->mask))
-		ret_err(gen_err);
+		ret_err_free(gen_err, new);
 	      new->addr_used = 1;
 	    } 
 	  else if (!atoi_check(arg, &new->mask))
-	    ret_err(gen_err);
+	    ret_err_free(gen_err, new);
 	    
           daemon->add_subnet4 = new;
 
@@ -1697,15 +1800,15 @@ static int one_opt(int option, char *arg
 		  /* has subnet+len */
                   err = parse_mysockaddr(comma, &new->addr);
                   if (err)
-                    ret_err(err);
+                    ret_err_free(err, new);
                   if (!atoi_check(end, &new->mask))
-                    ret_err(gen_err);
+                    ret_err_free(gen_err, new);
                   new->addr_used = 1;
                 }
               else
                 {
                   if (!atoi_check(comma, &new->mask))
-                    ret_err(gen_err);
+                    ret_err_free(gen_err, new);
                 }
           
 	      daemon->add_subnet6 = new;
@@ -1912,7 +2015,10 @@ static int one_opt(int option, char *arg
 		  else if (strcmp(fam, "6") == 0)
 		    new->addr.sa.sa_family = AF_INET6;
 		  else
-		    ret_err(gen_err);
+		  {
+		    free(new->name);
+		    ret_err_free(gen_err, new);
+		  }
 		} 
 	    }
 	  new->next = daemon->authinterface;
@@ -2077,7 +2183,7 @@ static int one_opt(int option, char *arg
 
 		      arg = split(netpart);
 		      if (!atoi_check(netpart, &msize))
-			ret_err(gen_err);
+			ret_err_free(gen_err, new);
 		      else if (inet_pton(AF_INET, comma, &new->start))
 			{
 			  int mask = (1 << (32 - msize)) - 1;
@@ -2090,18 +2196,18 @@ static int one_opt(int option, char *arg
 				{
 				  if (!(new->prefix = canonicalise_opt(arg)) ||
 				      strlen(new->prefix) > MAXLABEL - INET_ADDRSTRLEN)
-				    ret_err(_("bad prefix"));
+				    ret_err_free(_("bad prefix"), new);
 				}
 			      else if (strcmp(arg, "local") != 0 ||
 				       (msize != 8 && msize != 16 && msize != 24))
-				ret_err(gen_err);
+				ret_err_free(gen_err, new);
 			      else
 				{
 				   /* generate the equivalent of
 				      local=/xxx.yyy.zzz.in-addr.arpa/ */
 				  struct server *serv = add_rev4(new->start, msize);
 				  if (!serv)
-				    ret_err(_("bad prefix"));
+				    ret_err_free(_("bad prefix"), new);
 
 				  serv->flags |= SERV_NO_ADDR;
 
@@ -2130,17 +2236,17 @@ static int one_opt(int option, char *arg
 			  setaddr6part(&new->end6, addrpart | mask);
 			  
 			  if (msize < 64)
-			    ret_err(gen_err);
+			    ret_err_free(gen_err, new);
 			  else if (arg)
 			    {
 			      if (option != 's')
 				{
 				  if (!(new->prefix = canonicalise_opt(arg)) ||
 				      strlen(new->prefix) > MAXLABEL - INET6_ADDRSTRLEN)
-				    ret_err(_("bad prefix"));
+				    ret_err_free(_("bad prefix"), new);
 				}	
 			      else if (strcmp(arg, "local") != 0 || ((msize & 4) != 0))
-				ret_err(gen_err);
+				ret_err_free(gen_err, new);
 			      else 
 				{
 				  /* generate the equivalent of
@@ -2159,7 +2265,7 @@ static int one_opt(int option, char *arg
 			    }
 			}
 		      else
-			ret_err(gen_err);
+			ret_err_free(gen_err, new);
 		    }
 		  else
 		    {
@@ -2173,7 +2279,7 @@ static int one_opt(int option, char *arg
 			  if (!arg)
 			    new->end.s_addr = new->start.s_addr;
 			  else if (!inet_pton(AF_INET, arg, &new->end))
-			    ret_err(gen_err);
+			    ret_err_free(gen_err, new);
 			}
 		      else if (inet_pton(AF_INET6, comma, &new->start6))
 			{
@@ -2181,16 +2287,16 @@ static int one_opt(int option, char *arg
 			  if (!arg)
 			    memcpy(&new->end6, &new->start6, IN6ADDRSZ);
 			  else if (!inet_pton(AF_INET6, arg, &new->end6))
-			    ret_err(gen_err);
+			    ret_err_free(gen_err, new);
 			}
 		      else 
-			ret_err(gen_err);
+			ret_err_free(gen_err, new);
 
 		      if (option != 's' && prefstr)
 			{
 			  if (!(new->prefix = canonicalise_opt(prefstr)) ||
 			      strlen(new->prefix) > MAXLABEL - INET_ADDRSTRLEN)
-			    ret_err(_("bad prefix"));
+			    ret_err_free(_("bad prefix"), new);
 			}
 		    }
 
@@ -2352,7 +2458,7 @@ static int one_opt(int option, char *arg
 #endif
 	  }
 	else
-	  ret_err(gen_err);
+	  ret_err_free(gen_err, new);
 
 	new->used = 0;
 	if (option == 'a')
@@ -2423,7 +2529,10 @@ static int one_opt(int option, char *arg
 	  {
 	    newlist->flags |= SERV_LITERAL_ADDRESS;
 	    if (!(newlist->flags & SERV_TYPE))
-	      ret_err(gen_err);
+	      {
+	        server_list_free(newlist);
+	        ret_err(gen_err);
+	      }
 	  }
 	else if (option == LOPT_NO_REBIND)
 	  newlist->flags |= SERV_NO_REBIND;
@@ -2440,7 +2549,10 @@ static int one_opt(int option, char *arg
 	  {
 	    char *err = parse_server(arg, &newlist->addr, &newlist->source_addr, newlist->interface, &newlist->flags);
 	    if (err)
-	      ret_err(err);
+	      {
+	        server_list_free(newlist);
+	        ret_err(err);
+	      }
 	  }
 	
 	serv = newlist;
@@ -2776,21 +2888,19 @@ static int one_opt(int option, char *arg
 	      {
 		if (is_tag_prefix(arg))
 		  {
-		    struct dhcp_netid *tt = opt_malloc(sizeof (struct dhcp_netid));
-		    tt->net = opt_string_alloc(arg+4);
-		    tt->next = new->filter;
 		    /* ignore empty tag */
-		    if (tt->net)
-		      new->filter = tt;
+		    if (arg[4])
+		      new->filter = dhcp_netid_create(arg+4, new->filter);
 		  }
 		else
 		  {
 		    if (new->netid.net)
-		      ret_err(_("only one tag allowed"));
-		    else if (strstr(arg, "set:") == arg)
-		      new->netid.net = opt_string_alloc(arg+4);
+		      {
+			dhcp_context_free(new);
+			ret_err(_("only one tag allowed"));
+		      }
 		    else
-		      new->netid.net = opt_string_alloc(arg);
+		      new->netid.net = opt_string_alloc(set_prefix(arg));
 		  }
 		arg = comma;
 	      }
@@ -2806,7 +2916,10 @@ static int one_opt(int option, char *arg
 	    break;
 	
 	if (k < 2)
-	  ret_err(_("bad dhcp-range"));
+	  {
+	    dhcp_context_free(new);
+	    ret_err(_("bad dhcp-range"));
+	  }
 	
 	if (inet_pton(AF_INET, a[0], &new->start))
 	  {
@@ -2818,7 +2931,10 @@ static int one_opt(int option, char *arg
 	    else if (strcmp(a[1], "proxy") == 0)
 	      new->flags |= CONTEXT_PROXY;
 	    else if (!inet_pton(AF_INET, a[1], &new->end))
-	      ret_err(_("bad dhcp-range"));
+	      {
+		dhcp_context_free(new);
+		ret_err(_("bad dhcp-range"));
+	      }
 	    
 	    if (ntohl(new->start.s_addr) > ntohl(new->end.s_addr))
 	      {
@@ -2833,7 +2949,10 @@ static int one_opt(int option, char *arg
 		new->flags |= CONTEXT_NETMASK;
 		leasepos = 3;
 		if (!is_same_net(new->start, new->end, new->netmask))
-		  ret_err(_("inconsistent DHCP range"));
+		  {
+		    dhcp_context_free(new);
+		    ret_err(_("inconsistent DHCP range"));
+		  }
 		
 	    
 		if (k >= 4 && strchr(a[3], '.') &&  
@@ -2847,6 +2966,8 @@ static int one_opt(int option, char *arg
 #ifdef HAVE_DHCP6
 	else if (inet_pton(AF_INET6, a[0], &new->start6))
 	  {
+	    const char *err = NULL;
+
 	    new->flags |= CONTEXT_V6; 
 	    new->prefix = 64; /* default */
 	    new->end6 = new->start6;
@@ -2892,19 +3013,24 @@ static int one_opt(int option, char *arg
 		  }
 	      }
 	    
-	    if (new->prefix != 64)
+	    if (new->prefix > 64)
 	      {
 		if (new->flags & CONTEXT_RA)
-		  ret_err(_("prefix length must be exactly 64 for RA subnets"));
+		  err=(_("prefix length must be exactly 64 for RA subnets"));
 		else if (new->flags & CONTEXT_TEMPLATE)
-		  ret_err(_("prefix length must be exactly 64 for subnet constructors"));
+		  err=(_("prefix length must be exactly 64 for subnet constructors"));
 	      }
-
-	    if (new->prefix < 64)
-	      ret_err(_("prefix length must be at least 64"));
+	    else if (new->prefix < 64)
+	      err=(_("prefix length must be at least 64"));
 	    
-	    if (!is_same_net6(&new->start6, &new->end6, new->prefix))
-	      ret_err(_("inconsistent DHCPv6 range"));
+	    if (!err && !is_same_net6(&new->start6, &new->end6, new->prefix))
+	      err=(_("inconsistent DHCPv6 range"));
+
+	    if (err)
+	      {
+		dhcp_context_free(new);
+		ret_err(err);
+	      }
 
 	    /* dhcp-range=:: enables DHCP stateless on any interface */
 	    if (IN6_IS_ADDR_UNSPECIFIED(&new->start6) && !(new->flags & CONTEXT_TEMPLATE))
@@ -2915,7 +3041,10 @@ static int one_opt(int option, char *arg
 		struct in6_addr zero;
 		memset(&zero, 0, sizeof(zero));
 		if (!is_same_net6(&zero, &new->start6, new->prefix))
-		  ret_err(_("prefix must be zero with \"constructor:\" argument"));
+		  {
+		    dhcp_context_free(new);
+		    ret_err(_("prefix must be zero with \"constructor:\" argument"));
+		  }
 	      }
 	    
 	    if (addr6part(&new->start6) > addr6part(&new->end6))
@@ -2927,12 +3056,18 @@ static int one_opt(int option, char *arg
 	  }
 #endif
 	else
-	  ret_err(_("bad dhcp-range"));
+	  {
+	    dhcp_context_free(new);
+	    ret_err(_("bad dhcp-range"));
+	  }
 	
 	if (leasepos < k)
 	  {
 	    if (leasepos != k-1)
-	      ret_err(_("bad dhcp-range"));
+	      {
+		dhcp_context_free(new);
+		ret_err(_("bad dhcp-range"));
+	      }
 	    
 	    if (strcmp(a[leasepos], "infinite") == 0)
 	      new->lease_time = 0xffffffff;
@@ -2971,7 +3106,7 @@ static int one_opt(int option, char *arg
 			break;
 
 		    if (*cp || (leasepos+1 < k))
-		      ret_err(_("bad dhcp-range"));
+		      ret_err_free(_("bad dhcp-range"), new);
 		    
 		    new->lease_time = atoi(a[leasepos]) * fac;
 		    /* Leases of a minute or less confuse
@@ -2998,6 +3133,7 @@ static int one_opt(int option, char *arg
 	new->flags = (option == LOPT_BANK) ? CONFIG_BANK : 0;
 	new->hwaddr = NULL;
 	new->netid = NULL;
+	new->clid = NULL;
 
 	if ((a[0] = arg))
 	  for (k = 1; k < 7; k++)
@@ -3028,7 +3164,10 @@ static int one_opt(int option, char *arg
 			}
 
 		      if (len == -1)
-			ret_err(_("bad hex constant"));
+			{
+			  dhcp_config_free(new);
+			  ret_err(_("bad hex constant"));
+			}
 		      else if ((new->clid = opt_malloc(len)))
 			{
 			  new->flags |= CONFIG_CLID;
@@ -3040,17 +3179,17 @@ static int one_opt(int option, char *arg
 	      /* dhcp-host has strange backwards-compat needs. */
 	      else if (strstr(arg, "net:") == arg || strstr(arg, "set:") == arg)
 		{
-		  struct dhcp_netid *newtag = opt_malloc(sizeof(struct dhcp_netid));
 		  struct dhcp_netid_list *newlist = opt_malloc(sizeof(struct dhcp_netid_list));
-		  newtag->net = opt_malloc(strlen(arg + 4) + 1);
 		  newlist->next = new->netid;
 		  new->netid = newlist;
-		  newlist->list = newtag;
-		  strcpy(newtag->net, arg+4);
-		  unhide_metas(newtag->net);
+		  newlist->list = dhcp_netid_create(arg+4, NULL);
 		}
 	      else if (strstr(arg, "tag:") == arg)
-		ret_err(_("cannot match tags in --dhcp-host"));
+		{
+		  
+		  dhcp_config_free(new);
+		  ret_err(_("cannot match tags in --dhcp-host"));
+		}
 #ifdef HAVE_DHCP6
 	      else if (arg[0] == '[' && arg[strlen(arg)-1] == ']')
 		{
@@ -3058,7 +3197,10 @@ static int one_opt(int option, char *arg
 		  arg++;
 		  
 		  if (!inet_pton(AF_INET6, arg, &new->addr6))
-		    ret_err(_("bad IPv6 address"));
+		    {
+		      dhcp_config_free(new);
+		      ret_err(_("bad IPv6 address"));
+		    }
 
 		  for (i= 0; i < 8; i++)
 		    if (new->addr6.s6_addr[i] != 0)
@@ -3076,10 +3218,13 @@ static int one_opt(int option, char *arg
 		  struct hwaddr_config *newhw = opt_malloc(sizeof(struct hwaddr_config));
 		  if ((newhw->hwaddr_len = parse_hex(a[j], newhw->hwaddr, DHCP_CHADDR_MAX, 
 						     &newhw->wildcard_mask, &newhw->hwaddr_type)) == -1)
-		    ret_err(_("bad hex constant"));
+		    {
+		      free(newhw);
+		      dhcp_config_free(new);
+		      ret_err(_("bad hex constant"));
+		    }
 		  else
 		    {
-		      
 		      newhw->next = new->hwaddr;
 		      new->hwaddr = newhw;
 		    }		    
@@ -3156,7 +3301,10 @@ static int one_opt(int option, char *arg
 		    {
 		      if (!(new->hostname = canonicalise_opt(a[j])) ||
 			  !legal_hostname(new->hostname))
-			ret_err(_("bad DHCP host name"));
+			{
+			  dhcp_config_free(new);
+			  ret_err(_("bad DHCP host name"));
+			}
 		     
 		      new->flags |= CONFIG_NAME;
 		      new->domain = strip_hostname(new->hostname);			
@@ -3209,10 +3357,7 @@ static int one_opt(int option, char *arg
 	      }
 	    else
 	      {
-		struct dhcp_netid *newtag = opt_malloc(sizeof(struct dhcp_netid));
-		newtag->net = opt_malloc(len - 3);
-		strcpy(newtag->net, arg+4);
-		unhide_metas(newtag->net);
+		struct dhcp_netid *newtag = dhcp_netid_create(arg+4, NULL);
 
 		if (strstr(arg, "set:") == arg)
 		  {
@@ -3229,7 +3374,7 @@ static int one_opt(int option, char *arg
 		else 
 		  {
 		    new->set = NULL;
-		    free(newtag);
+		    dhcp_netid_free(newtag);
 		    break;
 		  }
 	      }
@@ -3238,7 +3383,11 @@ static int one_opt(int option, char *arg
 	  }
 
 	if (!new->set)
-	  ret_err(_("bad tag-if"));
+	  {
+	    dhcp_netid_free(new->tag);
+	    dhcp_netid_list_free(new->set);
+	    ret_err_free(_("bad tag-if"), new);
+	  }
 	  
 	break;
       }
@@ -3281,19 +3430,12 @@ static int one_opt(int option, char *arg
       
     case 'M': /* --dhcp-boot */
       {
-	struct dhcp_netid *id = NULL;
-	while (is_tag_prefix(arg))
-	  {
-	    struct dhcp_netid *newid = opt_malloc(sizeof(struct dhcp_netid));
-	    newid->next = id;
-	    id = newid;
-	    comma = split(arg);
-	    newid->net = opt_string_alloc(arg+4);
-	    arg = comma;
-	  };
+	struct dhcp_netid *id = dhcp_tags(&arg);
 	
-	if (!arg)
-	  ret_err(gen_err);
+	if (!id)
+	  {
+	    ret_err(gen_err);
+	  }
 	else 
 	  {
 	    char *dhcp_file, *dhcp_sname = NULL, *tftp_sname = NULL;
@@ -3339,19 +3481,12 @@ static int one_opt(int option, char *arg
 
     case LOPT_REPLY_DELAY: /* --dhcp-reply-delay */
       {
-	struct dhcp_netid *id = NULL;
-	while (is_tag_prefix(arg))
-	  {
-	    struct dhcp_netid *newid = opt_malloc(sizeof(struct dhcp_netid));
-	    newid->next = id;
-	    id = newid;
-	    comma = split(arg);
-	    newid->net = opt_string_alloc(arg+4);
-	    arg = comma;
-	  };
+	struct dhcp_netid *id = dhcp_tags(&arg);
 	
-	if (!arg)
-	  ret_err(gen_err);
+	if (!id)
+	  {
+	    ret_err(gen_err);
+	  }
 	else
 	  {
 	    struct delay_config *new;
@@ -3376,19 +3511,13 @@ static int one_opt(int option, char *arg
 	 
 	 new->netid = NULL;
 	 new->opt = 10; /* PXE_MENU_PROMPT */
-
-	 while (is_tag_prefix(arg))
-	  {
-	     struct dhcp_netid *nn = opt_malloc(sizeof (struct dhcp_netid));
-	     comma = split(arg);
-	     nn->next = new->netid;
-	     new->netid = nn;
-	     nn->net = opt_string_alloc(arg+4);
-	     arg = comma;
-	   }
+	 new->netid = dhcp_tags(&arg);
 	 
-	 if (!arg)
-	   ret_err(gen_err);
+	 if (!new->netid)
+	   {
+	     dhcp_opt_free(new);
+	     ret_err(gen_err);
+	   }
 	 else
 	   {
 	     comma = split(arg);
@@ -3424,17 +3553,8 @@ static int one_opt(int option, char *arg
 	 new->netid = NULL;
 	 new->sname = NULL;
 	 new->server.s_addr = 0;
+	 new->netid = dhcp_tags(&arg);
 
-	 while (is_tag_prefix(arg))
-	   {
-	     struct dhcp_netid *nn = opt_malloc(sizeof (struct dhcp_netid));
-	     comma = split(arg);
-	     nn->next = new->netid;
-	     new->netid = nn;
-	     nn->net = opt_string_alloc(arg+4);
-	     arg = comma;
-	   }
-       
 	 if (arg && (comma = split(arg)))
 	   {
 	     for (i = 0; CSA[i]; i++)
@@ -3511,7 +3631,10 @@ static int one_opt(int option, char *arg
 	    unhide_metas(comma);
 	    new->hwaddr_len = parse_hex(comma, new->hwaddr, DHCP_CHADDR_MAX, &new->mask, &new->hwaddr_type);
 	    if (new->hwaddr_len == -1)
-	      ret_err(gen_err);
+	      {
+		free(new->netid.net);
+		ret_err_free(gen_err, new);
+	      }
 	    else
 	      {
 		new->next = daemon->dhcp_macs;
@@ -3528,7 +3651,7 @@ static int one_opt(int option, char *arg
 	
 	if (!(comma = split(arg)) ||
 	    !atoi_check16(comma, &new->class))
-	  ret_err(gen_err);
+	  ret_err_free(gen_err, new);
 	
 	new->tag.net = opt_string_alloc(set_prefix(arg));
 	new->next = daemon->prefix_classes;
@@ -3550,7 +3673,7 @@ static int one_opt(int option, char *arg
 	 struct dhcp_vendor *new = opt_malloc(sizeof(struct dhcp_vendor));
 	 
 	 if (!(comma = split(arg)))
-	   ret_err(gen_err);
+	   ret_err_free(gen_err, new);
 	
 	 new->netid.net = opt_string_alloc(set_prefix(arg));
 	 /* check for hex string - must digits may include : must not have nothing else, 
@@ -3560,7 +3683,10 @@ static int one_opt(int option, char *arg
 	 if ((comma = split(arg)))
 	   {
 	     if (option  != 'U' || strstr(arg, "enterprise:") != arg)
-	       ret_err(gen_err);
+	       {
+	         free(new->netid.net);
+	         ret_err_free(gen_err, new);
+	       }
 	     else
 	       new->enterprise = atoi(arg+11);
 	   }
@@ -3662,14 +3788,8 @@ static int one_opt(int option, char *arg
 	  }
 	
 	while (arg) {
-	  struct dhcp_netid *member = opt_malloc(sizeof(struct dhcp_netid));
 	  comma = split(arg);
-	  member->next = list;
-	  list = member;
-	  if (is_tag_prefix(arg))
-	    member->net = opt_string_alloc(arg+4);
-	  else
-	    member->net = opt_string_alloc(arg);
+	  list = dhcp_netid_create(is_tag_prefix(arg) ? arg+4 :arg, list);
 	  arg = comma;
 	}
 	
@@ -3683,7 +3803,7 @@ static int one_opt(int option, char *arg
 	struct addr_list *new = opt_malloc(sizeof(struct addr_list));
 	comma = split(arg);
 	if (!(inet_pton(AF_INET, arg, &new->addr) > 0))
-	  ret_err(_("bad dhcp-proxy address"));
+	  ret_err_free(_("bad dhcp-proxy address"), new);
 	new->next = daemon->override_relays;
 	daemon->override_relays = new;
 	arg = comma;
@@ -3709,7 +3829,10 @@ static int one_opt(int option, char *arg
 	  }
 #endif
 	else
-	  ret_err(_("Bad dhcp-relay"));
+	  {
+	    free(new->interface);
+	    ret_err_free(_("Bad dhcp-relay"), new);
+	  }
 	
 	break;
       }
@@ -3749,8 +3872,11 @@ static int one_opt(int option, char *arg
 	   arg = split(comma);
 	   if (!atoi_check(comma, &new->interval) || 
 	      (arg && !atoi_check(arg, &new->lifetime)))
+             {
 err:
-	    ret_err(_("bad RA-params"));
+	       free(new->name);
+	       ret_err_free(_("bad RA-params"), new);
+             }
 	  
 	  new->next = daemon->ra_interfaces;
 	  daemon->ra_interfaces = new;
@@ -3799,7 +3925,7 @@ err:
 	    (!(inet_pton(AF_INET, dash, &new->end) > 0) ||
 	     !is_same_net(new->in, new->end, new->mask) ||
 	     ntohl(new->in.s_addr) > ntohl(new->end.s_addr)))
-	  ret_err(_("invalid alias range"));
+	  ret_err_free(_("invalid alias range"), new);
 	
 	break;
       }
@@ -3832,7 +3958,7 @@ err:
 	    else if (strcmp(arg, "6") == 0)
 	      new->family = AF_INET6;
 	    else
-	      ret_err(gen_err);
+	      ret_err_free(gen_err, new);
 	  } 
 	new->intr = opt_string_alloc(comma);
 	break;
@@ -3864,11 +3990,19 @@ err:
 	    alias = canonicalise_opt(arg);
 
 	    if (!alias || !target)
-	      ret_err(_("bad CNAME"));
+	      {
+		free(target);
+		free(alias);
+		ret_err(_("bad CNAME"));
+	      }
 	    
 	    for (new = daemon->cnames; new; new = new->next)
 	      if (hostname_isequal(new->alias, alias))
-		ret_err(_("duplicate CNAME"));
+		{
+		  free(target);
+		  free(alias);
+		  ret_err(_("duplicate CNAME"));
+		}
 	    new = opt_malloc(sizeof(struct cname));
 	    new->next = daemon->cnames;
 	    daemon->cnames = new;
@@ -3891,7 +4025,11 @@ err:
 	
 	if (!(dom = canonicalise_opt(arg)) ||
 	    (comma && !(target = canonicalise_opt(comma))))
-	  ret_err(_("bad PTR record"));
+	  {
+	    free(dom);
+	    free(target);
+	    ret_err(_("bad PTR record"));
+	  }
 	else
 	  {
 	    new = opt_malloc(sizeof(struct ptr_record));
@@ -3909,7 +4047,7 @@ err:
 	int k = 0;
 	struct naptr *new;
 	int order, pref;
-	char *name, *replace = NULL;
+	char *name=NULL, *replace = NULL;
 
 	if ((a[0] = arg))
 	  for (k = 1; k < 7; k++)
@@ -3922,7 +4060,11 @@ err:
 	    !atoi_check16(a[1], &order) || 
 	    !atoi_check16(a[2], &pref) ||
 	    (k == 7 && !(replace = canonicalise_opt(a[6]))))
-	  ret_err(_("bad NAPTR record"));
+          {
+	    free(name);
+	    free(replace);
+	    ret_err(_("bad NAPTR record"));
+          }
 	else
 	  {
 	    new = opt_malloc(sizeof(struct naptr));
@@ -3944,22 +4086,26 @@ err:
        	struct txt_record *new;
 	size_t len = 0;
 	char *data;
-	int val;
+	int class;
 
 	comma = split(arg);
 	data = split(comma);
 		
 	new = opt_malloc(sizeof(struct txt_record));
-	new->next = daemon->rr;
-	daemon->rr = new;
+	new->name = NULL;
 	
-	if (!atoi_check(comma, &val) || 
+	if (!atoi_check(comma, &class) || 
 	    !(new->name = canonicalise_opt(arg)) ||
 	    (data && (len = parse_hex(data, (unsigned char *)data, -1, NULL, NULL)) == -1U))
-	  ret_err(_("bad RR record"));
-	   	
-	new->class = val;
+          {
+            free(new->name);
+	    ret_err_free(_("bad RR record"), new);
+          }
+
 	new->len = 0;
+	new->class = class;
+	new->next = daemon->rr;
+	daemon->rr = new;
 	
 	if (data)
 	  {
@@ -4011,14 +4157,14 @@ err:
 	comma = split(arg);
 		
 	new = opt_malloc(sizeof(struct txt_record));
-	new->next = daemon->txt;
-	daemon->txt = new;
 	new->class = C_IN;
 	new->stat = 0;
 
 	if (!(new->name = canonicalise_opt(arg)))
-	  ret_err(_("bad TXT record"));
+	  ret_err_free(_("bad TXT record"), new);
 	
+	new->next = daemon->txt;
+	daemon->txt = new;
 	len = comma ? strlen(comma) : 0;
 	len += (len/255) + 1; /* room for extra counts */
 	new->txt = p = opt_malloc(len);
@@ -4065,24 +4211,32 @@ err:
 	    arg = comma;
 	    comma = split(arg);
 	    if (!(target = canonicalise_opt(arg)))
-	      ret_err(_("bad SRV target"));
+	      ret_err_free(_("bad SRV target"), name);
 		
 	    if (comma)
 	      {
 		arg = comma;
 		comma = split(arg);
 		if (!atoi_check16(arg, &port))
-		  ret_err(_("invalid port number"));
+                  {
+                    free(name);
+		    ret_err_free(_("invalid port number"), target);
+                  }
 		
 		if (comma)
 		  {
 		    arg = comma;
 		    comma = split(arg);
 		    if (!atoi_check16(arg, &priority))
-		      ret_err(_("invalid priority"));
-			
+                      {
+                        free(name);
+		        ret_err_free(_("invalid priority"), target);
+		      }
 		    if (comma && !atoi_check16(comma, &weight))
-		      ret_err(_("invalid weight"));
+                      {
+                        free(name);
+		        ret_err_free(_("invalid weight"), target);
+                      }
 		  }
 	      }
 	  }
@@ -4101,13 +4255,15 @@ err:
       
     case LOPT_HOST_REC: /* --host-record */
       {
-	struct host_record *new = opt_malloc(sizeof(struct host_record));
-	memset(new, 0, sizeof(struct host_record));
-	new->ttl = -1;
+	struct host_record *new;
 
 	if (!arg || !(comma = split(arg)))
 	  ret_err(_("Bad host-record"));
 	
+	new = opt_malloc(sizeof(struct host_record));
+	memset(new, 0, sizeof(struct host_record));
+	new->ttl = -1;
+
 	while (arg)
 	  {
 	    struct all_addr addr;
@@ -4126,10 +4282,19 @@ err:
 	      {
 		int nomem;
 		char *canon = canonicalise(arg, &nomem);
-		struct name_list *nl = opt_malloc(sizeof(struct name_list));
+		struct name_list *nl;
 		if (!canon)
-		  ret_err(_("Bad name in host-record"));
+                  {
+		    struct name_list *tmp = new->names, *next;
+		    for (tmp = new->names; tmp; tmp = next)
+		      {
+			next = tmp->next;
+			free(tmp);
+		      }
+		    ret_err_free(_("Bad name in host-record"), new);
+                  }
 
+		nl = opt_malloc(sizeof(struct name_list));
 		nl->name = canon;
 		/* keep order, so that PTR record goes to first name */
 		nl->next = NULL;
@@ -4179,6 +4344,7 @@ err:
 	int len;
 	
 	new->class = C_IN;
+	new->name = NULL;
 
 	if ((comma = split(arg)) && (algo = split(comma)))
 	  {
@@ -4203,7 +4369,7 @@ err:
 	    !atoi_check8(algo, &new->algo) ||
 	    !atoi_check8(digest, &new->digest_type) ||
 	    !(new->name = canonicalise_opt(arg)))
-	  ret_err(_("bad trust anchor"));
+	  ret_err_free(_("bad trust anchor"), new);
 	    
 	/* Upper bound on length */
 	len = (2*strlen(keyhex))+1;
@@ -4217,7 +4383,10 @@ err:
 	  else
 	    cp++;
 	if ((new->digestlen = parse_hex(keyhex, (unsigned char *)new->digest, len, NULL, NULL)) == -1)
-	  ret_err(_("bad HEX in trust anchor"));
+	  {
+	    free(new->name);
+	    ret_err_free(_("bad HEX in trust anchor"), new);
+	  }
 	
 	new->next = daemon->ds;
 	daemon->ds = new;
@@ -4686,8 +4855,8 @@ void read_opts(int argc, char **argv, ch
   size_t argbuf_size = MAXDNAME;
   char *argbuf = opt_malloc(argbuf_size);
   char *buff = opt_malloc(MAXDNAME);
-  int option, conffile_opt = '7', testmode = 0;
-  char *arg, *conffile = CONFFILE;
+  int option, testmode = 0;
+  char *arg, *conffile = NULL;
       
   opterr = 0;
 
@@ -4796,7 +4965,8 @@ void read_opts(int argc, char **argv, ch
         }
       else if (option == 'C')
 	{
-	  conffile_opt = 0; /* file must exist */
+          if (conffile)
+            free(conffile);
 	  conffile = opt_string_alloc(arg);
 	}
       else
@@ -4814,10 +4984,11 @@ void read_opts(int argc, char **argv, ch
 
   if (conffile)
     {
-      one_file(conffile, conffile_opt);
-      if (conffile_opt == 0)
-	free(conffile);
+      one_file(conffile, 0);
+      free(conffile);
     }
+  else
+    one_file(CONFFILE, '7');
 
   /* port might not be known when the address is parsed - fill in here */
   if (daemon->servers)
