iptables: fix regression with unintended free in need_protomatch
[project/firewall3.git] / ipsets.c
index 7a72fd35a378941304c8308f75f65b91ae6fc8d3..e7cde16e930a438c8850a79b51b0e0cac65171bf 100644 (file)
--- a/ipsets.c
+++ b/ipsets.c
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <ctype.h>
+
 #include "ipsets.h"
 
 
 const struct fw3_option fw3_ipset_opts[] = {
        FW3_OPT("enabled",       bool,           ipset,     enabled),
+       FW3_OPT("reload_set",    bool,           ipset,     reload_set),
+       FW3_OPT("counters",      bool,           ipset,     counters),
+       FW3_OPT("comment",       bool,           ipset,     comment),
 
        FW3_OPT("name",          string,         ipset,     name),
        FW3_OPT("family",        family,         ipset,     family),
@@ -38,6 +43,9 @@ const struct fw3_option fw3_ipset_opts[] = {
 
        FW3_OPT("external",      string,         ipset,     external),
 
+       FW3_LIST("entry",        setentry,       ipset,     entries),
+       FW3_OPT("loadfile",      string,         ipset,     loadfile),
+
        { }
 };
 
@@ -95,7 +103,7 @@ check_types(struct uci_element *e, struct fw3_ipset *ipset)
        {
                if (i >= 3)
                {
-                       warn_elem(e, "must not have more than 3 datatypes assigned");
+                       warn_section("ipset", ipset, e, "must not have more than 3 datatypes assigned");
                        return false;
                }
 
@@ -116,8 +124,8 @@ check_types(struct uci_element *e, struct fw3_ipset *ipset)
                        {
                                ipset->method = ipset_types[i].method;
 
-                               warn_elem(e, "defines no storage method, assuming '%s'",
-                                         fw3_ipset_method_names[ipset->method]);
+                               warn_section("ipset", ipset, e, "defines no storage method, assuming '%s'",
+                                       fw3_ipset_method_names[ipset->method]);
 
                                break;
                        }
@@ -136,56 +144,56 @@ check_types(struct uci_element *e, struct fw3_ipset *ipset)
                                if ((ipset_types[i].required & OPT_IPRANGE) &&
                                        !ipset->iprange.set)
                                {
-                                       warn_elem(e, "requires an ip range");
+                                       warn_section("ipset", ipset, e, "requires an ip range");
                                        return false;
                                }
 
                                if ((ipset_types[i].required & OPT_PORTRANGE) &&
                                    !ipset->portrange.set)
                                {
-                                       warn_elem(e, "requires a port range");
+                                       warn_section("ipset", ipset, e, "requires a port range");
                                        return false;
                                }
 
                                if (!(ipset_types[i].required & OPT_IPRANGE) &&
                                    ipset->iprange.set)
                                {
-                                       warn_elem(e, "iprange ignored");
+                                       warn_section("ipset", ipset, e, "iprange ignored");
                                        ipset->iprange.set = false;
                                }
 
                                if (!(ipset_types[i].required & OPT_PORTRANGE) &&
                                    ipset->portrange.set)
                                {
-                                       warn_elem(e, "portrange ignored");
+                                       warn_section("ipset", ipset, e, "portrange ignored");
                                        ipset->portrange.set = false;
                                }
 
                                if (!(ipset_types[i].optional & OPT_NETMASK) &&
                                    ipset->netmask > 0)
                                {
-                                       warn_elem(e, "netmask ignored");
+                                       warn_section("ipset", ipset, e, "netmask ignored");
                                        ipset->netmask = 0;
                                }
 
                                if (!(ipset_types[i].optional & OPT_HASHSIZE) &&
                                    ipset->hashsize > 0)
                                {
-                                       warn_elem(e, "hashsize ignored");
+                                       warn_section("ipset", ipset, e, "hashsize ignored");
                                        ipset->hashsize = 0;
                                }
 
                                if (!(ipset_types[i].optional & OPT_MAXELEM) &&
                                    ipset->maxelem > 0)
                                {
-                                       warn_elem(e, "maxelem ignored");
+                                       warn_section("ipset", ipset, e, "maxelem ignored");
                                        ipset->maxelem = 0;
                                }
 
                                if (!(ipset_types[i].optional & OPT_FAMILY) &&
                                    ipset->family != FW3_FAMILY_V4)
                                {
-                                       warn_elem(e, "family ignored");
+                                       warn_section("ipset", ipset, e, "family ignored");
                                        ipset->family = FW3_FAMILY_V4;
                                }
                        }
@@ -194,12 +202,55 @@ check_types(struct uci_element *e, struct fw3_ipset *ipset)
                }
        }
 
-       warn_elem(e, "has an invalid combination of storage method and matches");
+       warn_section("ipset", ipset, e, "has an invalid combination of storage method and matches");
        return false;
 }
 
-struct fw3_ipset *
-fw3_alloc_ipset(void)
+static bool
+check_ipset(struct fw3_state *state, struct fw3_ipset *ipset, struct uci_element *e)
+{
+       if (!ipset->enabled) {
+               return false;
+       }
+
+       if (ipset->external)
+       {
+               if (!*ipset->external)
+                       ipset->external = NULL;
+               else if (!ipset->name)
+                       ipset->name = ipset->external;
+       }
+
+       if (!ipset->name || !*ipset->name)
+       {
+               warn_section("ipset", ipset, e, "ipset must have a name assigned");
+       }
+       //else if (fw3_lookup_ipset(state, ipset->name) != NULL)
+       //{
+       //      warn_section("ipset", ipset, e, "has duplicated set name", ipset->name);
+       //}
+       else if (ipset->family == FW3_FAMILY_ANY)
+       {
+               warn_section("ipset", ipset, e, "must not have family 'any'");
+       }
+       else if (ipset->iprange.set && ipset->family != ipset->iprange.family)
+       {
+               warn_section("ipset", ipset, e, "has iprange of wrong address family");
+       }
+       else if (list_empty(&ipset->datatypes))
+       {
+               warn_section("ipset", ipset, e, "has no datatypes assigned");
+       }
+       else if (check_types(e, ipset))
+       {
+               return true;
+       }
+
+       return false;
+}
+
+static struct fw3_ipset *
+fw3_alloc_ipset(struct fw3_state *state)
 {
        struct fw3_ipset *ipset;
 
@@ -208,25 +259,61 @@ fw3_alloc_ipset(void)
                return NULL;
 
        INIT_LIST_HEAD(&ipset->datatypes);
+       INIT_LIST_HEAD(&ipset->entries);
+
+       ipset->comment    = false;
+       ipset->counters   = false;
+       ipset->enabled    = true;
+       ipset->family     = FW3_FAMILY_V4;
+       ipset->reload_set = false;
+       ipset->timeout    = -1; /* no timeout by default */
 
-       ipset->enabled = true;
-       ipset->family  = FW3_FAMILY_V4;
+       list_add_tail(&ipset->list, &state->ipsets);
 
        return ipset;
 }
 
 void
-fw3_load_ipsets(struct fw3_state *state, struct uci_package *p)
+fw3_load_ipsets(struct fw3_state *state, struct uci_package *p,
+               struct blob_attr *a)
 {
        struct uci_section *s;
        struct uci_element *e;
        struct fw3_ipset *ipset;
+       struct blob_attr *entry;
+       unsigned rem;
 
        INIT_LIST_HEAD(&state->ipsets);
 
        if (state->disable_ipsets)
                return;
 
+       blob_for_each_attr(entry, a, rem)
+       {
+               const char *type;
+               const char *name = "ubus ipset";
+
+               if (!fw3_attr_parse_name_type(entry, &name, &type))
+                       continue;
+
+               if (strcmp(type, "ipset"))
+                       continue;
+
+               ipset = fw3_alloc_ipset(state);
+               if (!ipset)
+                       continue;
+
+               if (!fw3_parse_blob_options(ipset, fw3_ipset_opts, entry, name))
+               {
+                       warn_section("ipset", ipset, NULL, "skipped due to invalid options");
+                       fw3_free_ipset(ipset);
+                       continue;
+               }
+
+               if (!check_ipset(state, ipset, NULL))
+                       fw3_free_ipset(ipset);
+       }
+
        uci_foreach_element(&p->sections, e)
        {
                s = uci_to_section(e);
@@ -234,7 +321,7 @@ fw3_load_ipsets(struct fw3_state *state, struct uci_package *p)
                if (strcmp(s->type, "ipset"))
                        continue;
 
-               ipset = fw3_alloc_ipset();
+               ipset = fw3_alloc_ipset(state);
 
                if (!ipset)
                        continue;
@@ -242,50 +329,47 @@ fw3_load_ipsets(struct fw3_state *state, struct uci_package *p)
                if (!fw3_parse_options(ipset, fw3_ipset_opts, s))
                        warn_elem(e, "has invalid options");
 
-               if (ipset->external)
-               {
-                       if (!*ipset->external)
-                               ipset->external = NULL;
-                       else if (!ipset->name)
-                               ipset->name = ipset->external;
-               }
+               if (!check_ipset(state, ipset, e))
+                       fw3_free_ipset(ipset);
+       }
+}
 
-               if (!ipset->name || !*ipset->name)
-               {
-                       warn_elem(e, "must have a name assigned");
-               }
-               //else if (fw3_lookup_ipset(state, ipset->name) != NULL)
-               //{
-               //      warn_elem(e, "has duplicated set name '%s'", ipset->name);
-               //}
-               else if (ipset->family == FW3_FAMILY_ANY)
-               {
-                       warn_elem(e, "must not have family 'any'");
-               }
-               else if (ipset->iprange.set && ipset->family != ipset->iprange.family)
-               {
-                       warn_elem(e, "has iprange of wrong address family");
-               }
-               else if (list_empty(&ipset->datatypes))
-               {
-                       warn_elem(e, "has no datatypes assigned");
-               }
-               else if (check_types(e, ipset))
-               {
-                       list_add_tail(&ipset->list, &state->ipsets);
-                       continue;
-               }
 
-               fw3_free_ipset(ipset);
+static void
+load_file(struct fw3_ipset *ipset)
+{
+       FILE *f;
+       char line[128];
+       char *p;
+
+       if (!ipset->loadfile)
+               return;
+
+       info("   * Loading file %s", ipset->loadfile);
+
+       f = fopen(ipset->loadfile, "r");
+
+       if (!f) {
+               info("     ! Skipping due to open error: %s", strerror(errno));
+               return;
+       }
+
+       while (fgets(line, sizeof(line), f)) {
+               p = line;
+               while (isspace(*p))
+                       p++;
+               if (*p && *p != '#')
+                       fw3_pr("add %s %s", ipset->name, line);
        }
-}
 
+       fclose(f);
+}
 
 static void
 create_ipset(struct fw3_ipset *ipset, struct fw3_state *state)
 {
        bool first = true;
-
+       struct fw3_setentry *entry;
        struct fw3_ipset_datatype *type;
 
        info(" * Creating ipset %s", ipset->name);
@@ -312,7 +396,7 @@ create_ipset(struct fw3_ipset *ipset, struct fw3_state *state)
                       ipset->portrange.port_min, ipset->portrange.port_max);
        }
 
-       if (ipset->timeout > 0)
+       if (ipset->timeout >= 0)
                fw3_pr(" timeout %u", ipset->timeout);
 
        if (ipset->maxelem > 0)
@@ -324,13 +408,25 @@ create_ipset(struct fw3_ipset *ipset, struct fw3_state *state)
        if (ipset->hashsize > 0)
                fw3_pr(" hashsize %u", ipset->hashsize);
 
+       if (ipset->counters)
+               fw3_pr(" counters");
+
+       if (ipset->comment)
+               fw3_pr(" comment");
+
        fw3_pr("\n");
+
+       list_for_each_entry(entry, &ipset->entries, list)
+               fw3_pr("add %s %s\n", ipset->name, entry->value);
+
+       load_file(ipset);
 }
 
 void
-fw3_create_ipsets(struct fw3_state *state)
+fw3_create_ipsets(struct fw3_state *state, enum fw3_family family,
+                 bool reload_set)
 {
-       int tries;
+       unsigned int delay, tries;
        bool exec = false;
        struct fw3_ipset *ipset;
 
@@ -340,9 +436,16 @@ fw3_create_ipsets(struct fw3_state *state)
        /* spawn ipsets */
        list_for_each_entry(ipset, &state->ipsets, list)
        {
+               if (ipset->family != family)
+                       continue;
+
                if (ipset->external)
                        continue;
 
+               if (fw3_check_ipset(ipset) &&
+                   (reload_set && !ipset->reload_set))
+                       continue;
+
                if (!exec)
                {
                        exec = fw3_command_pipe(false, "ipset", "-exist", "-");
@@ -360,27 +463,36 @@ fw3_create_ipsets(struct fw3_state *state)
                fw3_command_close();
        }
 
-       /* wait for ipsets to appear */
+       /* wait a little expontially for ipsets to appear */
        list_for_each_entry(ipset, &state->ipsets, list)
        {
                if (ipset->external)
                        continue;
 
+               delay = 5;
                for (tries = 0; !fw3_check_ipset(ipset) && tries < 10; tries++)
-                       usleep(50000);
+                       usleep(delay<<1);
        }
 }
 
 void
-fw3_destroy_ipsets(struct fw3_state *state)
+fw3_destroy_ipsets(struct fw3_state *state, enum fw3_family family,
+                  bool reload_set)
 {
-       int tries;
+       unsigned int delay, tries;
        bool exec = false;
        struct fw3_ipset *ipset;
 
+       if (state->disable_ipsets)
+               return;
+
        /* destroy ipsets */
        list_for_each_entry(ipset, &state->ipsets, list)
        {
+               if (ipset->family != family ||
+                   (reload_set && !ipset->reload_set))
+                       continue;
+
                if (!exec)
                {
                        exec = fw3_command_pipe(false, "ipset", "-exist", "-");
@@ -407,8 +519,9 @@ fw3_destroy_ipsets(struct fw3_state *state)
                if (ipset->external)
                        continue;
 
+               delay = 5;
                for (tries = 0; fw3_check_ipset(ipset) && tries < 10; tries++)
-                       usleep(50000);
+                       usleep(delay<<1);
        }
 }
 
@@ -467,3 +580,43 @@ out:
 
        return rv;
 }
+
+void
+fw3_ipsets_update_run_state(enum fw3_family family, struct fw3_state *run_state,
+                           struct fw3_state *cfg_state)
+{
+       struct fw3_ipset *ipset_run, *ipset_cfg;
+       bool in_cfg;
+
+       list_for_each_entry(ipset_run, &run_state->ipsets, list) {
+               if (ipset_run->family != family)
+                       continue;
+
+               in_cfg = false;
+
+               list_for_each_entry(ipset_cfg, &cfg_state->ipsets, list) {
+                       if (ipset_cfg->family != family)
+                               continue;
+
+                       if (strlen(ipset_run->name) ==
+                           strlen(ipset_cfg->name) &&
+                           !strcmp(ipset_run->name, ipset_cfg->name)) {
+                               in_cfg = true;
+                               break;
+                       }
+               }
+
+               /* If a set is found in run_state, but not in cfg_state then the
+                * set has been deleted/renamed. Set reload_set to true to force
+                * the old set to be destroyed in the "stop" fase of the reload.
+                * If the set is found, then copy the reload_set value from the
+                * configuration state. This ensures that the elements are
+                * always updated according to the configuration, and not the
+                * runtime state (which the user might have forgotten).
+                */
+               if (!in_cfg)
+                       ipset_run->reload_set = true;
+               else
+                       ipset_run->reload_set = ipset_cfg->reload_set;
+       }
+}