aboutsummaryrefslogtreecommitdiffstats
path: root/package/network/services/dnsmasq/patches/0011-Free-config-file-values-on-parsing-errors.patch
diff options
context:
space:
mode:
Diffstat (limited to 'package/network/services/dnsmasq/patches/0011-Free-config-file-values-on-parsing-errors.patch')
-rw-r--r--package/network/services/dnsmasq/patches/0011-Free-config-file-values-on-parsing-errors.patch1199
1 files changed, 1199 insertions, 0 deletions
diff --git a/package/network/services/dnsmasq/patches/0011-Free-config-file-values-on-parsing-errors.patch b/package/network/services/dnsmasq/patches/0011-Free-config-file-values-on-parsing-errors.patch
new file mode 100644
index 0000000000..3ba971fe72
--- /dev/null
+++ b/package/network/services/dnsmasq/patches/0011-Free-config-file-values-on-parsing-errors.patch
@@ -0,0 +1,1199 @@
+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/11] 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)