firewall3: check the return value of fw3_parse_options()
[project/firewall3.git] / utils.c
diff --git a/utils.c b/utils.c
index aef1cc6..875a141 100644 (file)
--- a/utils.c
+++ b/utils.c
@@ -1,7 +1,7 @@
 /*
  * firewall3 - 3rd OpenWrt UCI firewall implementation
  *
- *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *   Copyright (C) 2013 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
@@ -16,6 +16,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#define _GNU_SOURCE
 #include "utils.h"
 #include "options.h"
 
@@ -132,6 +133,32 @@ info(const char* format, ...)
        fprintf(stderr, "\n");
 }
 
+void *
+fw3_alloc(size_t size)
+{
+       void *mem;
+
+       mem = calloc(1, size);
+
+       if (!mem)
+               error("Out of memory while allocating %d bytes", size);
+
+       return mem;
+}
+
+char *
+fw3_strdup(const char *s)
+{
+       char *ns;
+
+       ns = strdup(s);
+
+       if (!ns)
+               error("Out of memory while duplicating string '%s'", s);
+
+       return ns;
+}
+
 const char *
 fw3_find_command(const char *cmd)
 {
@@ -198,7 +225,7 @@ __fw3_command_pipe(bool silent, const char *command, ...)
                return false;
 
        argn = 2;
-       args = malloc(argn * sizeof(arg));
+       args = calloc(argn, sizeof(arg));
 
        if (!args)
                return false;
@@ -357,6 +384,21 @@ write_defaults_uci(struct uci_context *ctx, struct fw3_defaults *d,
 
        uci_add_section(ctx, dest, "defaults", &ptr.s);
 
+       ptr.o      = NULL;
+       ptr.option = "input";
+       ptr.value  = fw3_flag_names[d->policy_input];
+       uci_set(ctx, &ptr);
+
+       ptr.o      = NULL;
+       ptr.option = "output";
+       ptr.value  = fw3_flag_names[d->policy_output];
+       uci_set(ctx, &ptr);
+
+       ptr.o      = NULL;
+       ptr.option = "forward";
+       ptr.value  = fw3_flag_names[d->policy_forward];
+       uci_set(ctx, &ptr);
+
        sprintf(buf, "0x%x", d->flags[0]);
        ptr.o      = NULL;
        ptr.option = "__flags_v4";
@@ -372,13 +414,14 @@ write_defaults_uci(struct uci_context *ctx, struct fw3_defaults *d,
 
 static void
 write_zone_uci(struct uci_context *ctx, struct fw3_zone *z,
-               struct uci_package *dest)
+               struct uci_package *dest, struct ifaddrs *ifaddr)
 {
        struct fw3_device *dev;
        struct fw3_address *sub;
+       struct ifaddrs *ifa;
        enum fw3_family fam = FW3_FAMILY_ANY;
 
-       char *p, buf[34];
+       char *p, buf[INET6_ADDRSTRLEN];
 
        struct uci_ptr ptr = { .p = dest };
 
@@ -420,11 +463,6 @@ write_zone_uci(struct uci_context *ctx, struct fw3_zone *z,
        uci_set(ctx, &ptr);
 
        ptr.o      = NULL;
-       ptr.option = "conntrack";
-       ptr.value  = z->conntrack ? "1" : "0";
-       uci_set(ctx, &ptr);
-
-       ptr.o      = NULL;
        ptr.option = "mtu_fix";
        ptr.value  = z->mtu_fix ? "1" : "0";
        uci_set(ctx, &ptr);
@@ -447,15 +485,21 @@ write_zone_uci(struct uci_context *ctx, struct fw3_zone *z,
 
        fw3_foreach(dev, &z->devices)
        {
+               char *ep;
+
                if (!dev)
                        continue;
 
                p = buf;
+               ep = buf + sizeof(buf);
 
                if (dev->invert)
-                       p += sprintf(p, "!");
+                       p += snprintf(p, ep - p, "!");
 
-               p += sprintf(p, "%s", dev->name);
+               if (*dev->network)
+                       p += snprintf(p, ep - p, "%s@%s", dev->name, dev->network);
+               else
+                       p += snprintf(p, ep - p, "%s", dev->name);
 
                ptr.value = buf;
                uci_add_list(ctx, &ptr);
@@ -469,10 +513,39 @@ write_zone_uci(struct uci_context *ctx, struct fw3_zone *z,
                if (!sub)
                        continue;
 
-               ptr.value = fw3_address_to_string(sub, true);
+               ptr.value = fw3_address_to_string(sub, true, false);
                uci_add_list(ctx, &ptr);
        }
 
+       ptr.o      = NULL;
+       ptr.option = "__addrs";
+
+       fw3_foreach(dev, &z->devices)
+       {
+               if (!dev)
+                       continue;
+
+               for (ifa = ifaddr; ifa; ifa = ifa->ifa_next)
+               {
+                       if (!ifa->ifa_addr || strcmp(dev->name, ifa->ifa_name))
+                               continue;
+
+                       if (ifa->ifa_addr->sa_family == AF_INET)
+                               inet_ntop(AF_INET,
+                                         &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr,
+                                         buf, sizeof(buf));
+                       else if (ifa->ifa_addr->sa_family == AF_INET6)
+                               inet_ntop(AF_INET6,
+                                         &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr,
+                                         buf, sizeof(buf));
+                       else
+                               continue;
+
+                       ptr.value = buf;
+                       uci_add_list(ctx, &ptr);
+               }
+       }
+
        sprintf(buf, "0x%x", z->flags[0]);
        ptr.o      = NULL;
        ptr.option = "__flags_v4";
@@ -490,11 +563,13 @@ static void
 write_ipset_uci(struct uci_context *ctx, struct fw3_ipset *s,
                 struct uci_package *dest)
 {
-       char buf[sizeof("0xffffffff\0")];
+       struct fw3_ipset_datatype *type;
+
+       char buf[sizeof("65535-65535\0")];
 
        struct uci_ptr ptr = { .p = dest };
 
-       if (!s->enabled || (s->external && *s->external))
+       if (!s->enabled || s->external)
                return;
 
        uci_add_section(ctx, dest, "ipset", &ptr.s);
@@ -504,17 +579,36 @@ write_ipset_uci(struct uci_context *ctx, struct fw3_ipset *s,
        ptr.value  = s->name;
        uci_set(ctx, &ptr);
 
-       sprintf(buf, "0x%x", s->flags[0]);
        ptr.o      = NULL;
-       ptr.option = "__flags_v4";
-       ptr.value  = buf;
+       ptr.option = "storage";
+       ptr.value  = fw3_ipset_method_names[s->method];
        uci_set(ctx, &ptr);
 
-       sprintf(buf, "0x%x", s->flags[1]);
-       ptr.o      = NULL;
-       ptr.option = "__flags_v6";
-       ptr.value  = buf;
-       uci_set(ctx, &ptr);
+       list_for_each_entry(type, &s->datatypes, list)
+       {
+               sprintf(buf, "%s_%s", type->dir, fw3_ipset_type_names[type->type]);
+               ptr.o      = NULL;
+               ptr.option = "match";
+               ptr.value  = buf;
+               uci_add_list(ctx, &ptr);
+       }
+
+       if (s->iprange.set)
+       {
+               ptr.o      = NULL;
+               ptr.option = "iprange";
+               ptr.value  = fw3_address_to_string(&s->iprange, false, false);
+               uci_set(ctx, &ptr);
+       }
+
+       if (s->portrange.set)
+       {
+               sprintf(buf, "%u-%u", s->portrange.port_min, s->portrange.port_max);
+               ptr.o      = NULL;
+               ptr.option = "portrange";
+               ptr.value  = buf;
+               uci_set(ctx, &ptr);
+       }
 }
 
 void
@@ -524,6 +618,7 @@ fw3_write_statefile(void *state)
        struct fw3_state *s = state;
        struct fw3_zone *z;
        struct fw3_ipset *i;
+       struct ifaddrs *ifaddr;
 
        struct uci_package *p;
 
@@ -542,6 +637,12 @@ fw3_write_statefile(void *state)
                        return;
                }
 
+               if (getifaddrs(&ifaddr))
+               {
+                       warn("Cannot get interface addresses: %s", strerror(errno));
+                       ifaddr = NULL;
+               }
+
                if ((p = uci_lookup_package(s->uci, "fw3_state")) != NULL)
                        uci_unload(s->uci, p);
 
@@ -552,7 +653,7 @@ fw3_write_statefile(void *state)
                        write_defaults_uci(s->uci, &s->defaults, p);
 
                        list_for_each_entry(z, &s->zones, list)
-                               write_zone_uci(s->uci, z, p);
+                               write_zone_uci(s->uci, z, p, ifaddr);
 
                        list_for_each_entry(i, &s->ipsets, list)
                                write_ipset_uci(s->uci, i, p);
@@ -561,7 +662,11 @@ fw3_write_statefile(void *state)
                        uci_unload(s->uci, p);
                }
 
+               fsync(fileno(sf));
                fclose(sf);
+
+               if (ifaddr)
+                       freeifaddrs(ifaddr);
        }
 }
 
@@ -588,51 +693,30 @@ fw3_free_object(void *obj, const void *opts)
        free(obj);
 }
 
-
-bool
-fw3_pr_rulespec(int table, int family, uint32_t *flags, uint32_t mask,
-                const struct fw3_rule_spec *r, const char *fmt, ...)
+void
+fw3_free_list(struct list_head *head)
 {
-       char buf[256];
-       bool rv = false;
-
-       va_list ap;
-       uint32_t f = flags ? flags[family == FW3_FAMILY_V6] : 0;
+       struct list_head *entry, *tmp;
 
-       if (mask)
-               f &= mask;
+       if (!head)
+               return;
 
-       for (; r->format; r++)
+       list_for_each_safe(entry, tmp, head)
        {
-               if (!fw3_is_family(r, family))
-                       continue;
-
-               if (r->table != table)
-                       continue;
-
-               if ((r->flag != 0) && !hasbit(f, r->flag))
-                       continue;
-
-               va_start(ap, fmt);
-               vsnprintf(buf, sizeof(buf), r->format, ap);
-               va_end(ap);
-
-               fw3_pr(fmt, buf);
-
-               rv = true;
+               list_del(entry);
+               free(entry);
        }
 
-       return rv;
+       free(head);
 }
 
-
 bool
 fw3_hotplug(bool add, void *zone, void *device)
 {
        struct fw3_zone *z = zone;
        struct fw3_device *d = device;
 
-       if (!d->network)
+       if (!*d->network)
                return false;
 
        switch (fork())
@@ -651,12 +735,12 @@ fw3_hotplug(bool add, void *zone, void *device)
        close(0);
        close(1);
        close(2);
-       chdir("/");
+       if (chdir("/")) {};
 
        clearenv();
        setenv("ACTION",    add ? "add" : "remove", 1);
        setenv("ZONE",      z->name,                1);
-       setenv("INTERFACE", d->network->name,       1);
+       setenv("INTERFACE", d->network,             1);
        setenv("DEVICE",    d->name,                1);
 
        execl(FW3_HOTPLUG, FW3_HOTPLUG, "firewall", NULL);
@@ -664,3 +748,148 @@ fw3_hotplug(bool add, void *zone, void *device)
        /* unreached */
        return false;
 }
+
+int
+fw3_netmask2bitlen(int family, void *mask)
+{
+       int bits;
+       struct in_addr *v4;
+       struct in6_addr *v6;
+
+       if (family == FW3_FAMILY_V6)
+               for (bits = 0, v6 = mask;
+                    bits < 128 && (v6->s6_addr[bits / 8] << (bits % 8)) & 128;
+                    bits++);
+       else
+               for (bits = 0, v4 = mask;
+                    bits < 32 && (ntohl(v4->s_addr) << bits) & 0x80000000;
+                    bits++);
+
+       return bits;
+}
+
+bool
+fw3_bitlen2netmask(int family, int bits, void *mask)
+{
+       int i;
+       uint8_t rem, b;
+       struct in_addr *v4;
+       struct in6_addr *v6;
+
+       if (family == FW3_FAMILY_V6)
+       {
+               if (bits < -128 || bits > 128)
+                       return false;
+
+               v6 = mask;
+               rem = abs(bits);
+
+               for (i = 0; i < sizeof(v6->s6_addr); i++)
+               {
+                       b = (rem > 8) ? 8 : rem;
+                       v6->s6_addr[i] = (uint8_t)(0xFF << (8 - b));
+                       rem -= b;
+               }
+
+               if (bits < 0)
+                       for (i = 0; i < sizeof(v6->s6_addr); i++)
+                               v6->s6_addr[i] = ~v6->s6_addr[i];
+       }
+       else
+       {
+               if (bits < -32 || bits > 32)
+                       return false;
+
+               v4 = mask;
+               v4->s_addr = bits ? htonl(~((1 << (32 - abs(bits))) - 1)) : 0;
+
+               if (bits < 0)
+                       v4->s_addr = ~v4->s_addr;
+       }
+
+       return true;
+}
+
+void
+fw3_flush_conntrack(void *state)
+{
+       bool found;
+       struct fw3_state *s = state;
+       struct fw3_address *addr;
+       struct fw3_device *dev;
+       struct fw3_zone *zone;
+       struct ifaddrs *ifaddr, *ifa;
+       struct sockaddr_in *sin;
+       struct sockaddr_in6 *sin6;
+       char buf[INET6_ADDRSTRLEN];
+       FILE *ct;
+
+       if (!state)
+       {
+               if ((ct = fopen("/proc/net/nf_conntrack", "w")) != NULL)
+               {
+                       info(" * Flushing conntrack table ...");
+
+                       fwrite("f\n", 1, 2, ct);
+                       fclose(ct);
+               }
+
+               return;
+       }
+
+       if (getifaddrs(&ifaddr))
+       {
+               warn("Cannot get interface addresses: %s", strerror(errno));
+               return;
+       }
+
+       if ((ct = fopen("/proc/net/nf_conntrack", "w")) != NULL)
+       {
+               list_for_each_entry(zone, &s->zones, list)
+               list_for_each_entry(addr, &zone->old_addrs, list)
+               {
+                       found = false;
+
+                       list_for_each_entry(dev, &zone->devices, list)
+                       {
+                               for (ifa = ifaddr; ifa && !found; ifa = ifa->ifa_next)
+                               {
+                                       if (!ifa->ifa_addr || strcmp(dev->name, ifa->ifa_name))
+                                               continue;
+
+                                       sin = (struct sockaddr_in *)ifa->ifa_addr;
+                                       sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
+
+                                       if (addr->family == FW3_FAMILY_V4 &&
+                                               sin->sin_family == AF_INET)
+                                       {
+                                               found = !memcmp(&addr->address.v4, &sin->sin_addr,
+                                                                               sizeof(sin->sin_addr));
+                                       }
+                                       else if (addr->family == FW3_FAMILY_V6 &&
+                                                        sin6->sin6_family == AF_INET6)
+                                       {
+                                               found = !memcmp(&addr->address.v6, &sin6->sin6_addr,
+                                                                               sizeof(sin6->sin6_addr));
+                                       }
+                               }
+
+                               if (found)
+                                       break;
+                       }
+
+                       if (!found)
+                       {
+                               inet_ntop(addr->family == FW3_FAMILY_V4 ? AF_INET : AF_INET6,
+                                                 &addr->address.v4, buf, sizeof(buf));
+
+                               info(" * Flushing conntrack: %s", buf);
+                               fprintf(ct, "%s\n", buf);
+                       }
+               }
+
+               fclose(ct);
+       }
+
+       freeifaddrs(ifaddr);
+}