options: improve handling of negations when parsing space separated values
[project/firewall3.git] / options.c
index 43025a8..ae6bf5d 100644 (file)
--- a/options.c
+++ b/options.c
@@ -1,7 +1,7 @@
 /*
  * firewall3 - 3rd OpenWrt UCI firewall implementation
  *
- *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *   Copyright (C) 2013-2014 Jo-Philipp Wich <jo@mein.io>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -78,6 +78,7 @@ const char *fw3_flag_names[__FW3_FLAG_MAX] = {
        "MARK",
        "DNAT",
        "SNAT",
+       "MASQUERADE",
 
        "ACCEPT",
        "REJECT",
@@ -142,9 +143,10 @@ fw3_parse_bool(void *ptr, const char *val, bool is_list)
 bool
 fw3_parse_int(void *ptr, const char *val, bool is_list)
 {
-       int n = strtol(val, NULL, 0);
+       char *e;
+       int n = strtol(val, &e, 0);
 
-       if (errno == ERANGE || errno == EINVAL)
+       if (e == val || *e)
                return false;
 
        *((int *)ptr) = n;
@@ -163,7 +165,7 @@ bool
 fw3_parse_target(void *ptr, const char *val, bool is_list)
 {
        return parse_enum(ptr, val, &fw3_flag_names[FW3_FLAG_ACCEPT],
-                         FW3_FLAG_ACCEPT, FW3_FLAG_SNAT);
+                         FW3_FLAG_ACCEPT, FW3_FLAG_MASQUERADE);
 }
 
 bool
@@ -242,8 +244,8 @@ fw3_parse_address(void *ptr, const char *val, bool is_list)
        struct fw3_address addr = { };
        struct in_addr v4;
        struct in6_addr v6;
-       char *p, *s, *e;
-       int i, m = -1;
+       char *p = NULL, *m = NULL, *s, *e;
+       int bits = -1;
 
        if (*val == '!')
        {
@@ -256,99 +258,114 @@ fw3_parse_address(void *ptr, const char *val, bool is_list)
        if (!s)
                return false;
 
-       if ((p = strchr(s, '/')) != NULL)
-       {
+       if ((m = strchr(s, '/')) != NULL)
+               *m++ = 0;
+       else if ((p = strchr(s, '-')) != NULL)
                *p++ = 0;
-               m = strtoul(p, &e, 10);
 
-               if ((e == p) || (*e != 0))
+       if (inet_pton(AF_INET6, s, &v6))
+       {
+               addr.family = FW3_FAMILY_V6;
+               addr.address.v6 = v6;
+
+               if (m)
                {
-                       if (strchr(s, ':') || !inet_pton(AF_INET, p, &v4))
+                       if (!inet_pton(AF_INET6, m, &v6))
                        {
-                               free(s);
-                               return false;
-                       }
+                               bits = strtol(m, &e, 10);
 
-                       for (i = 0, m = 32; !(v4.s_addr & 1) && (i < 32); i++)
-                       {
-                               m--;
-                               v4.s_addr >>= 1;
+                               if ((*e != 0) || !fw3_bitlen2netmask(addr.family, bits, &v6))
+                                       goto fail;
                        }
-               }
-       }
-       else if ((p = strchr(s, '-')) != NULL)
-       {
-               *p++ = 0;
 
-               if (inet_pton(AF_INET6, p, &v6))
-               {
-                       addr.family = FW3_FAMILY_V6;
-                       addr.address2.v6 = v6;
-                       addr.range = true;
+                       addr.mask.v6 = v6;
                }
-               else if (inet_pton(AF_INET, p, &v4))
+               else if (p)
                {
-                       addr.family = FW3_FAMILY_V4;
-                       addr.address2.v4 = v4;
+                       if (!inet_pton(AF_INET6, p, &addr.mask.v6))
+                               goto fail;
+
                        addr.range = true;
                }
                else
                {
-                       free(s);
-                       return false;
+                       memset(addr.mask.v6.s6_addr, 0xFF, 16);
                }
        }
-
-       if (inet_pton(AF_INET6, s, &v6))
-       {
-               addr.family = FW3_FAMILY_V6;
-               addr.address.v6 = v6;
-               addr.mask = (m >= 0) ? m : 128;
-       }
        else if (inet_pton(AF_INET, s, &v4))
        {
                addr.family = FW3_FAMILY_V4;
                addr.address.v4 = v4;
-               addr.mask = (m >= 0) ? m : 32;
+
+               if (m)
+               {
+                       if (!inet_pton(AF_INET, m, &v4))
+                       {
+                               bits = strtol(m, &e, 10);
+
+                               if ((*e != 0) || !fw3_bitlen2netmask(addr.family, bits, &v4))
+                                       goto fail;
+                       }
+
+                       addr.mask.v4 = v4;
+               }
+               else if (p)
+               {
+                       if (!inet_pton(AF_INET, p, &addr.mask.v4))
+                               goto fail;
+
+                       addr.range = true;
+               }
+               else
+               {
+                       addr.mask.v4.s_addr = 0xFFFFFFFF;
+               }
        }
        else
        {
-               free(s);
-               return false;
+               goto fail;
        }
 
        free(s);
        addr.set = true;
        put_value(ptr, &addr, sizeof(addr), is_list);
        return true;
+
+fail:
+       free(s);
+       return false;
 }
 
 bool
 fw3_parse_network(void *ptr, const char *val, bool is_list)
 {
        struct fw3_device dev = { };
-       struct fw3_address *addr;
-       struct list_head *addr_list;
+       struct fw3_address *addr, *tmp;
+       LIST_HEAD(addr_list);
 
        if (!fw3_parse_address(ptr, val, is_list))
        {
                if (!fw3_parse_device(&dev, val, false))
                        return false;
 
-               addr_list = fw3_ubus_address(dev.name);
-
-               if (addr_list)
+               fw3_ubus_address(&addr_list, dev.name);
+               list_for_each_entry(addr, &addr_list, list)
                {
-                       list_for_each_entry(addr, addr_list, list)
-                       {
-                               addr->invert = dev.invert;
-                               addr->resolved = true;
+                       addr->invert = dev.invert;
+                       addr->resolved = true;
+               }
 
-                               if (!put_value(ptr, addr, sizeof(*addr), is_list))
-                                       break;
-                       }
+               if (is_list)
+               {
+                       list_splice_tail(&addr_list, ptr);
+               }
+               else if (!list_empty(&addr_list))
+               {
+                       memcpy(ptr, list_first_entry(&addr_list, typeof(*addr), list),
+                              sizeof(*addr));
 
-                       fw3_free_list(addr_list);
+                       list_for_each_entry_safe(addr, tmp, &addr_list, list)
+                               free(addr);
                }
        }
 
@@ -425,7 +442,7 @@ fw3_parse_port(void *ptr, const char *val, bool is_list)
 bool
 fw3_parse_family(void *ptr, const char *val, bool is_list)
 {
-       if (!strcmp(val, "any"))
+       if (!strcmp(val, "any") || !strcmp(val, "*"))
                *((enum fw3_family *)ptr) = FW3_FAMILY_ANY;
        else if (!strcmp(val, "inet") || strrchr(val, '4'))
                *((enum fw3_family *)ptr) = FW3_FAMILY_V4;
@@ -518,6 +535,7 @@ fw3_parse_protocol(void *ptr, const char *val, bool is_list)
 {
        struct fw3_protocol proto = { };
        struct protoent *ent;
+       char *e;
 
        if (*val == '!')
        {
@@ -525,7 +543,7 @@ fw3_parse_protocol(void *ptr, const char *val, bool is_list)
                while (isspace(*++val));
        }
 
-       if (!strcmp(val, "all"))
+       if (!strcmp(val, "all") || !strcmp(val, "any") || !strcmp(val, "*"))
        {
                proto.any = true;
                put_value(ptr, &proto, sizeof(proto), is_list);
@@ -556,9 +574,9 @@ fw3_parse_protocol(void *ptr, const char *val, bool is_list)
                return true;
        }
 
-       proto.protocol = strtoul(val, NULL, 10);
+       proto.protocol = strtoul(val, &e, 10);
 
-       if (errno == ERANGE || errno == EINVAL)
+       if ((e == val) || (*e != 0))
                return false;
 
        put_value(ptr, &proto, sizeof(proto), is_list);
@@ -577,20 +595,22 @@ fw3_parse_ipset_datatype(void *ptr, const char *val, bool is_list)
 {
        struct fw3_ipset_datatype type = { };
 
+       type.dir = "src";
+
        if (!strncmp(val, "dest_", 5))
        {
                val += 5;
-               type.dest = true;
+               type.dir = "dst";
        }
        else if (!strncmp(val, "dst_", 4))
        {
                val += 4;
-               type.dest = true;
+               type.dir = "dst";
        }
        else if (!strncmp(val, "src_", 4))
        {
                val += 4;
-               type.dest = false;
+               type.dir = "src";
        }
 
        if (parse_enum(&type.type, val, &fw3_ipset_type_names[FW3_IPSET_TYPE_IP],
@@ -698,7 +718,7 @@ fw3_parse_weekdays(void *ptr, const char *val, bool is_list)
 
        if (*val == '!')
        {
-               setbit(*(uint8_t *)ptr, 0);
+               fw3_setbit(*(uint8_t *)ptr, 0);
                while (isspace(*++val));
        }
 
@@ -718,7 +738,7 @@ fw3_parse_weekdays(void *ptr, const char *val, bool is_list)
                        }
                }
 
-               setbit(*(uint8_t *)ptr, w);
+               fw3_setbit(*(uint8_t *)ptr, w);
        }
 
        free(s);
@@ -733,7 +753,7 @@ fw3_parse_monthdays(void *ptr, const char *val, bool is_list)
 
        if (*val == '!')
        {
-               setbit(*(uint32_t *)ptr, 0);
+               fw3_setbit(*(uint32_t *)ptr, 0);
                while (isspace(*++val));
        }
 
@@ -750,7 +770,7 @@ fw3_parse_monthdays(void *ptr, const char *val, bool is_list)
                        return false;
                }
 
-               setbit(*(uint32_t *)ptr, d);
+               fw3_setbit(*(uint32_t *)ptr, d);
        }
 
        free(s);
@@ -809,17 +829,74 @@ fw3_parse_mark(void *ptr, const char *val, bool is_list)
        return true;
 }
 
+bool
+fw3_parse_setmatch(void *ptr, const char *val, bool is_list)
+{
+       struct fw3_setmatch *m = ptr;
+       char *p, *s;
+       int i;
+
+       if (*val == '!')
+       {
+               m->invert = true;
+               while (isspace(*++val));
+       }
+
+       if (!(s = strdup(val)))
+               return false;
+
+       if (!(p = strtok(s, " \t")))
+       {
+               free(s);
+               return false;
+       }
+
+       strncpy(m->name, p, sizeof(m->name));
+
+       for (i = 0, p = strtok(NULL, " \t,");
+            i < 3 && p != NULL;
+            i++, p = strtok(NULL, " \t,"))
+       {
+               if (!strncmp(p, "dest", 4) || !strncmp(p, "dst", 3))
+                       m->dir[i] = "dst";
+               else if (!strncmp(p, "src", 3))
+                       m->dir[i] = "src";
+       }
+
+       free(s);
+
+       m->set = true;
+       return true;
+}
+
+bool
+fw3_parse_direction(void *ptr, const char *val, bool is_list)
+{
+       bool *is_out = ptr;
+       bool valid = true;
 
-void
+       if (!strcmp(val, "in") || !strcmp(val, "ingress"))
+               *is_out = false;
+       else if (!strcmp(val, "out") || !strcmp(val, "egress"))
+               *is_out = true;
+       else
+               valid = false;
+
+       return valid;
+}
+
+
+bool
 fw3_parse_options(void *s, const struct fw3_option *opts,
                   struct uci_section *section)
 {
        char *p, *v;
-       bool known;
+       bool known, inv;
        struct uci_element *e, *l;
        struct uci_option *o;
        const struct fw3_option *opt;
        struct list_head *dest;
+       bool valid = true;
 
        uci_foreach_element(&section->options, e)
        {
@@ -839,6 +916,7 @@ fw3_parse_options(void *s, const struct fw3_option *opts,
                                if (!opt->elem_size)
                                {
                                        warn_elem(e, "must not be a list");
+                                       valid = false;
                                }
                                else
                                {
@@ -852,6 +930,7 @@ fw3_parse_options(void *s, const struct fw3_option *opts,
                                                if (!opt->parse(dest, l->name, true))
                                                {
                                                        warn_elem(e, "has invalid value '%s'", l->name);
+                                                       valid = false;
                                                        continue;
                                                }
                                        }
@@ -867,20 +946,53 @@ fw3_parse_options(void *s, const struct fw3_option *opts,
                                if (!opt->elem_size)
                                {
                                        if (!opt->parse((char *)s + opt->offset, o->v.string, false))
+                                       {
                                                warn_elem(e, "has invalid value '%s'", o->v.string);
+                                               valid = false;
+                                       }
                                }
                                else
                                {
+                                       inv = false;
                                        dest = (struct list_head *)((char *)s + opt->offset);
 
                                        for (p = strtok(v, " \t"); p != NULL; p = strtok(NULL, " \t"))
                                        {
+                                               /* If we encounter a sole "!" token, assume that it
+                                                * is meant to be part of the next token, so silently
+                                                * skip it and remember the state... */
+                                               if (!strcmp(p, "!"))
+                                               {
+                                                       inv = true;
+                                                       continue;
+                                               }
+
+                                               /* The previous token was a sole "!", rewind pointer
+                                                * back by one byte to precede the value with an
+                                                * exclamation mark which effectively turns
+                                                * ("!", "foo") into ("!foo") */
+                                               if (inv)
+                                               {
+                                                       *--p = '!';
+                                                       inv = false;
+                                               }
+
                                                if (!opt->parse(dest, p, true))
                                                {
                                                        warn_elem(e, "has invalid value '%s'", p);
+                                                       valid = false;
                                                        continue;
                                                }
                                        }
+
+                                       /* The last token was a sole "!" without any subsequent
+                                        * text, so pass it to the option parser as-is. */
+                                       if (inv && !opt->parse(dest, "!", true))
+                                       {
+                                               warn_elem(e, "has invalid value '%s'", p);
+                                               valid = false;
+                                               continue;
+                                       }
                                }
                        }
 
@@ -891,11 +1003,119 @@ fw3_parse_options(void *s, const struct fw3_option *opts,
                if (!known)
                        warn_elem(e, "is unknown");
        }
+
+       return valid;
+}
+
+
+bool
+fw3_parse_blob_options(void *s, const struct fw3_option *opts,
+                       struct blob_attr *a, const char *name)
+{
+       char *p, *v, buf[16];
+       bool known;
+       unsigned rem, erem;
+       struct blob_attr *o, *e;
+       const struct fw3_option *opt;
+       struct list_head *dest;
+       bool valid = true;
+
+       blobmsg_for_each_attr(o, a, rem)
+       {
+               known = false;
+
+               for (opt = opts; opt->name; opt++)
+               {
+                       if (!opt->parse)
+                               continue;
+
+                       if (strcmp(opt->name, blobmsg_name(o)))
+                               continue;
+
+                       if (blobmsg_type(o) == BLOBMSG_TYPE_ARRAY)
+                       {
+                               if (!opt->elem_size)
+                               {
+                                       fprintf(stderr, "%s: '%s' must not be a list\n",
+                                               name, opt->name);
+
+                                       valid = false;
+                               }
+                               else
+                               {
+                                       dest = (struct list_head *)((char *)s + opt->offset);
+
+                                       blobmsg_for_each_attr(e, o, erem)
+                                       {
+                                               if (blobmsg_type(e) == BLOBMSG_TYPE_INT32) {
+                                                       snprintf(buf, sizeof(buf), "%d", blobmsg_get_u32(e));
+                                                       v = buf;
+                                               } else {
+                                                       v = blobmsg_get_string(e);
+                                               }
+
+                                               if (!opt->parse(dest, v, true))
+                                               {
+                                                       fprintf(stderr, "%s: '%s' has invalid value '%s'\n",
+                                                               name, opt->name, v);
+                                                       valid = false;
+                                                       continue;
+                                               }
+                                       }
+                               }
+                       }
+                       else
+                       {
+                               if (blobmsg_type(o) == BLOBMSG_TYPE_INT32) {
+                                       snprintf(buf, sizeof(buf), "%d", blobmsg_get_u32(o));
+                                       v = buf;
+                               } else {
+                                       v = blobmsg_get_string(o);
+                               }
+
+                               if (!v)
+                                       continue;
+
+                               if (!opt->elem_size)
+                               {
+                                       if (!opt->parse((char *)s + opt->offset, v, false))
+                                       {
+                                               fprintf(stderr, "%s: '%s' has invalid value '%s'\n",
+                                                       name, opt->name, v);
+                                               valid = false;
+                                       }
+                               }
+                               else
+                               {
+                                       dest = (struct list_head *)((char *)s + opt->offset);
+
+                                       for (p = strtok(v, " \t"); p != NULL; p = strtok(NULL, " \t"))
+                                       {
+                                               if (!opt->parse(dest, p, true))
+                                               {
+                                                       fprintf(stderr, "%s: '%s' has invalid value '%s'\n",
+                                                               name, opt->name, p);
+                                                       valid = false;
+                                                       continue;
+                                               }
+                                       }
+                               }
+                       }
+
+                       known = true;
+                       break;
+               }
+
+               if (!known && strcmp(blobmsg_name(o), "type"))
+                       fprintf(stderr, "%s: '%s' is unknown\n", name, blobmsg_name(o));
+       }
+
+       return valid;
 }
 
 
 const char *
-fw3_address_to_string(struct fw3_address *address, bool allow_invert)
+fw3_address_to_string(struct fw3_address *address, bool allow_invert, bool as_cidr)
 {
        char *p, ip[INET6_ADDRSTRLEN];
        static char buf[INET6_ADDRSTRLEN * 2 + 2];
@@ -913,13 +1133,21 @@ fw3_address_to_string(struct fw3_address *address, bool allow_invert)
        if (address->range)
        {
                inet_ntop(address->family == FW3_FAMILY_V4 ? AF_INET : AF_INET6,
-                         &address->address2.v4, ip, sizeof(ip));
+                         &address->mask.v4, ip, sizeof(ip));
 
                p += sprintf(p, "-%s", ip);
        }
+       else if (!as_cidr)
+       {
+               inet_ntop(address->family == FW3_FAMILY_V4 ? AF_INET : AF_INET6,
+                         &address->mask.v4, ip, sizeof(ip));
+
+               p += sprintf(p, "/%s", ip);
+       }
        else
        {
-               p += sprintf(p, "/%u", address->mask);
+               p += sprintf(p, "/%u", fw3_netmask2bitlen(address->family,
+                                                         &address->mask.v6));
        }
 
        return buf;