iptables-1.3.2-20050720
[iptables.git] / iptables.c
index 5863ef4..92c6c49 100644 (file)
 #define FALSE 0
 #endif
 
-#ifndef IPT_LIB_DIR
-#define IPT_LIB_DIR "/usr/lib/iptables"
-#endif
-
 #ifndef PROC_SYS_MODPROBE
 #define PROC_SYS_MODPROBE "/proc/sys/kernel/modprobe"
 #endif
@@ -105,7 +101,7 @@ static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z',
 #define OPT_COUNTERS   0x00800U
 #define NUMBER_OF_OPT  12
 static const char optflags[NUMBER_OF_OPT]
-= { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', 'f', '3', 'c'};
+= { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', 'f', '0', 'c'};
 
 static struct option original_opts[] = {
        { "append", 1, 0, 'A' },
@@ -147,14 +143,6 @@ static struct option original_opts[] = {
  * magic number of -1 */
 int line = -1;
 
-#ifndef __OPTIMIZE__
-struct ipt_entry_target *
-ipt_get_target(struct ipt_entry *e)
-{
-       return (void *)e + e->target_offset;
-}
-#endif
-
 static struct option *opts = original_opts;
 static unsigned int global_option_offset = 0;
 
@@ -203,6 +191,7 @@ static int inverse_for_options[NUMBER_OF_OPT] =
 
 const char *program_version;
 const char *program_name;
+char *lib_dir;
 
 /* Keeping track of external matches and targets: linked lists.  */
 struct iptables_match *iptables_matches = NULL;
@@ -235,6 +224,7 @@ static const struct pprot chain_protos[] = {
        { "icmp", IPPROTO_ICMP },
        { "esp", IPPROTO_ESP },
        { "ah", IPPROTO_AH },
+       { "sctp", IPPROTO_SCTP },
        { "all", 0 },
 };
 
@@ -268,6 +258,7 @@ dotted_to_addr(const char *dotted)
 
        /* copy dotted string, because we need to modify it */
        strncpy(buf, dotted, sizeof(buf) - 1);
+       buf[sizeof(buf) - 1] = '\0';
        addrp = (unsigned char *) &(addr.s_addr);
 
        p = buf;
@@ -315,6 +306,16 @@ inaddrcpy(struct in_addr *dst, struct in_addr *src)
        dst->s_addr = src->s_addr;
 }
 
+static void free_opts(int reset_offset)
+{
+       if (opts != original_opts) {
+               free(opts);
+               opts = original_opts;
+               if (reset_offset)
+                       global_option_offset = 0;
+       }
+}
+
 void
 exit_error(enum exittype status, char *msg, ...)
 {
@@ -330,6 +331,8 @@ exit_error(enum exittype status, char *msg, ...)
        if (status == VERSION_PROBLEM)
                fprintf(stderr,
                        "Perhaps iptables or your kernel needs to be upgraded.\n");
+       /* On error paths, make sure that we don't leak memory */
+       free_opts(1);
        exit(status);
 }
 
@@ -340,13 +343,14 @@ exit_tryhelp(int status)
                fprintf(stderr, "Error occurred at line: %d\n", line);
        fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n",
                        program_name, program_name );
+       free_opts(1);
        exit(status);
 }
 
 void
-exit_printhelp(void)
+exit_printhelp(struct iptables_rule_match *matches)
 {
-       struct iptables_match *m = NULL;
+       struct iptables_rule_match *matchp = NULL;
        struct iptables_target *t = NULL;
 
        printf("%s v%s\n\n"
@@ -411,14 +415,16 @@ exit_printhelp(void)
 
        /* Print out any special helps. A user might like to be able
           to add a --help to the commandline, and see expected
-          results. So we call help for all matches & targets */
-       for (t=iptables_targets;t;t=t->next) {
-               printf("\n");
-               t->help();
+          results. So we call help for all specified matches & targets */
+       for (t = iptables_targets; t ;t = t->next) {
+               if (t->used) {
+                       printf("\n");
+                       t->help();
+               }
        }
-       for (m=iptables_matches;m;m=m->next) {
+       for (matchp = matches; matchp; matchp = matchp->next) {
                printf("\n");
-               m->help();
+               matchp->match->help();
        }
        exit(0);
 }
@@ -547,7 +553,7 @@ host_to_addr(const char *name, unsigned int *naddr)
 
                while (host->h_addr_list[*naddr] != (char *) NULL)
                        (*naddr)++;
-               addr = fw_calloc(*naddr, sizeof(struct in_addr));
+               addr = fw_calloc(*naddr, sizeof(struct in_addr) * *naddr);
                for (i = 0; i < *naddr; i++)
                        inaddrcpy(&(addr[i]),
                                  (struct in_addr *) host->h_addr_list[i]);
@@ -633,6 +639,7 @@ parse_hostnetworkmask(const char *name, struct in_addr **addrpp,
        int i, j, k, n;
 
        strncpy(buf, name, sizeof(buf) - 1);
+       buf[sizeof(buf) - 1] = '\0';
        if ((p = strrchr(buf, '/')) != NULL) {
                *p = '\0';
                addrp = parse_mask(p + 1);
@@ -659,7 +666,7 @@ parse_hostnetworkmask(const char *name, struct in_addr **addrpp,
 }
 
 struct iptables_match *
-find_match(const char *name, enum ipt_tryload tryload)
+find_match(const char *name, enum ipt_tryload tryload, struct iptables_rule_match **matches)
 {
        struct iptables_match *ptr;
 
@@ -670,13 +677,13 @@ find_match(const char *name, enum ipt_tryload tryload)
 
 #ifndef NO_SHARED_LIBS
        if (!ptr && tryload != DONT_LOAD) {
-               char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so")
+               char path[strlen(lib_dir) + sizeof("/libipt_.so")
                         + strlen(name)];
-               sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name);
+               sprintf(path, "%s/libipt_%s.so", lib_dir, name);
                if (dlopen(path, RTLD_NOW)) {
                        /* Found library.  If it didn't register itself,
                           maybe they specified target as match. */
-                       ptr = find_match(name, DONT_LOAD);
+                       ptr = find_match(name, DONT_LOAD, NULL);
 
                        if (!ptr)
                                exit_error(PARAMETER_PROBLEM,
@@ -700,15 +707,24 @@ find_match(const char *name, enum ipt_tryload tryload)
        }
 #endif
 
-       if (ptr)
-               ptr->used = 1;
+       if (ptr && matches) {
+               struct iptables_rule_match **i;
+               struct iptables_rule_match *newentry;
+
+               newentry = fw_malloc(sizeof(struct iptables_rule_match));
+
+               for (i = matches; *i; i = &(*i)->next);
+               newentry->match = ptr;
+               newentry->next = NULL;
+               *i = newentry;
+       }
 
        return ptr;
 }
 
 /* Christophe Burki wants `-p 6' to imply `-m tcp'.  */
 static struct iptables_match *
-find_proto(const char *pname, enum ipt_tryload tryload, int nolookup)
+find_proto(const char *pname, enum ipt_tryload tryload, int nolookup, struct iptables_rule_match **matches)
 {
        unsigned int proto;
 
@@ -716,9 +732,9 @@ find_proto(const char *pname, enum ipt_tryload tryload, int nolookup)
                char *protoname = proto_to_name(proto, nolookup);
 
                if (protoname)
-                       return find_match(protoname, tryload);
+                       return find_match(protoname, tryload, matches);
        } else
-               return find_match(pname, tryload);
+               return find_match(pname, tryload, matches);
 
        return NULL;
 }
@@ -753,8 +769,7 @@ parse_protocol(const char *s)
        return (u_int16_t)proto;
 }
 
-static void
-parse_interface(const char *arg, char *vianame, unsigned char *mask)
+void parse_interface(const char *arg, char *vianame, unsigned char *mask)
 {
        int vialen = strlen(arg);
        unsigned int i;
@@ -768,7 +783,7 @@ parse_interface(const char *arg, char *vianame, unsigned char *mask)
                           " (%i)", arg, IFNAMSIZ-1);
 
        strcpy(vianame, arg);
-       if (vialen == 0)
+       if ((vialen == 0) || (vialen == 1 && vianame[0] == '+'))
                memset(mask, 0, IFNAMSIZ);
        else if (vianame[vialen - 1] == '+') {
                memset(mask, 0xFF, vialen - 1);
@@ -815,8 +830,8 @@ parse_target(const char *targetname)
 
        if (strlen(targetname)+1 > sizeof(ipt_chainlabel))
                exit_error(PARAMETER_PROBLEM,
-                          "Invalid target name `%s' (%i chars max)",
-                          targetname, sizeof(ipt_chainlabel)-1);
+                          "Invalid target name `%s' (%u chars max)",
+                          targetname, (unsigned int)sizeof(ipt_chainlabel)-1);
 
        for (ptr = targetname; *ptr; ptr++)
                if (isspace(*ptr))
@@ -886,18 +901,18 @@ mask_to_dotted(const struct in_addr *mask)
 }
 
 int
-string_to_number(const char *s, unsigned int min, unsigned int max,
-                unsigned int *ret)
+string_to_number_ll(const char *s, unsigned long long min, unsigned long long max,
+                unsigned long long *ret)
 {
-       long number;
+       unsigned long long number;
        char *end;
 
        /* Handle hex, octal, etc. */
        errno = 0;
-       number = strtol(s, &end, 0);
+       number = strtoull(s, &end, 0);
        if (*end == '\0' && end != s) {
                /* we parsed a number, let's see if we want this */
-               if (errno != ERANGE && min <= number && number <= max) {
+               if (errno != ERANGE && min <= number && (!max || number <= max)) {
                        *ret = number;
                        return 0;
                }
@@ -905,6 +920,31 @@ string_to_number(const char *s, unsigned int min, unsigned int max,
        return -1;
 }
 
+int
+string_to_number_l(const char *s, unsigned long min, unsigned long max,
+                unsigned long *ret)
+{
+       int result;
+       unsigned long long number;
+
+       result = string_to_number_ll(s, min, max, &number);
+       *ret = (unsigned long)number;
+
+       return result;
+}
+
+int string_to_number(const char *s, unsigned int min, unsigned int max,
+               unsigned int *ret)
+{
+       int result;
+       unsigned long number;
+
+       result = string_to_number_l(s, min, max, &number);
+       *ret = (unsigned int)number;
+
+       return result;
+}
+
 static void
 set_option(unsigned int *options, unsigned int option, u_int8_t *invflg,
           int invert)
@@ -946,9 +986,9 @@ find_target(const char *name, enum ipt_tryload tryload)
 
 #ifndef NO_SHARED_LIBS
        if (!ptr && tryload != DONT_LOAD) {
-               char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so")
+               char path[strlen(lib_dir) + sizeof("/libipt_.so")
                         + strlen(name)];
-               sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name);
+               sprintf(path, "%s/libipt_%s.so", lib_dir, name);
                if (dlopen(path, RTLD_NOW)) {
                        /* Found library.  If it didn't register itself,
                           maybe they specified match as a target. */
@@ -988,6 +1028,9 @@ merge_options(struct option *oldopts, const struct option *newopts,
        unsigned int num_old, num_new, i;
        struct option *merge;
 
+       /* Release previous options merged if any */
+       free_opts(0);
+       
        for (num_old = 0; oldopts[num_old].name; num_old++);
        for (num_new = 0; newopts[num_new].name; num_new++);
 
@@ -1005,10 +1048,56 @@ merge_options(struct option *oldopts, const struct option *newopts,
        return merge;
 }
 
+static int compatible_revision(const char *name, u_int8_t revision, int opt)
+{
+       struct ipt_get_revision rev;
+       socklen_t s = sizeof(rev);
+       int max_rev, sockfd;
+
+       sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+       if (sockfd < 0) {
+               fprintf(stderr, "Could not open socket to kernel: %s\n",
+                       strerror(errno));
+               exit(1);
+       }
+
+       strcpy(rev.name, name);
+       rev.revision = revision;
+
+       max_rev = getsockopt(sockfd, IPPROTO_IP, opt, &rev, &s);
+       if (max_rev < 0) {
+               /* Definitely don't support this? */
+               if (errno == EPROTONOSUPPORT) {
+                       close(sockfd);
+                       return 0;
+               } else if (errno == ENOPROTOOPT) {
+                       close(sockfd);
+                       /* Assume only revision 0 support (old kernel) */
+                       return (revision == 0);
+               } else {
+                       fprintf(stderr, "getsockopt failed strangely: %s\n",
+                               strerror(errno));
+                       exit(1);
+               }
+       }
+       close(sockfd);
+       return 1;
+}
+
+static int compatible_match_revision(const char *name, u_int8_t revision)
+{
+       return compatible_revision(name, revision, IPT_SO_GET_REVISION_MATCH);
+}
+
+static int compatible_target_revision(const char *name, u_int8_t revision)
+{
+       return compatible_revision(name, revision, IPT_SO_GET_REVISION_TARGET);
+}
+
 void
 register_match(struct iptables_match *me)
 {
-       struct iptables_match **i;
+       struct iptables_match **i, *old;
 
        if (strcmp(me->version, program_version) != 0) {
                fprintf(stderr, "%s: match `%s' v%s (I'm v%s).\n",
@@ -1016,15 +1105,39 @@ register_match(struct iptables_match *me)
                exit(1);
        }
 
-       if (find_match(me->name, DONT_LOAD)) {
-               fprintf(stderr, "%s: match `%s' already registered.\n",
+       /* Revision field stole a char from name. */
+       if (strlen(me->name) >= IPT_FUNCTION_MAXNAMELEN-1) {
+               fprintf(stderr, "%s: target `%s' has invalid name\n",
                        program_name, me->name);
                exit(1);
        }
 
+       old = find_match(me->name, DONT_LOAD, NULL);
+       if (old) {
+               if (old->revision == me->revision) {
+                       fprintf(stderr,
+                               "%s: match `%s' already registered.\n",
+                               program_name, me->name);
+                       exit(1);
+               }
+
+               /* Now we have two (or more) options, check compatibility. */
+               if (compatible_match_revision(old->name, old->revision)
+                   && old->revision > me->revision)
+                       return;
+
+               /* Replace if compatible. */
+               if (!compatible_match_revision(me->name, me->revision))
+                       return;
+
+               /* Delete old one. */
+               for (i = &iptables_matches; *i!=old; i = &(*i)->next);
+               *i = old->next;
+       }
+
        if (me->size != IPT_ALIGN(me->size)) {
                fprintf(stderr, "%s: match `%s' has invalid size %u.\n",
-                       program_name, me->name, me->size);
+                       program_name, me->name, (unsigned int)me->size);
                exit(1);
        }
 
@@ -1040,21 +1153,49 @@ register_match(struct iptables_match *me)
 void
 register_target(struct iptables_target *me)
 {
+       struct iptables_target *old;
+
        if (strcmp(me->version, program_version) != 0) {
                fprintf(stderr, "%s: target `%s' v%s (I'm v%s).\n",
                        program_name, me->name, me->version, program_version);
                exit(1);
        }
 
-       if (find_target(me->name, DONT_LOAD)) {
-               fprintf(stderr, "%s: target `%s' already registered.\n",
+       /* Revision field stole a char from name. */
+       if (strlen(me->name) >= IPT_FUNCTION_MAXNAMELEN-1) {
+               fprintf(stderr, "%s: target `%s' has invalid name\n",
                        program_name, me->name);
                exit(1);
        }
 
+       old = find_target(me->name, DONT_LOAD);
+       if (old) {
+               struct iptables_target **i;
+
+               if (old->revision == me->revision) {
+                       fprintf(stderr,
+                               "%s: target `%s' already registered.\n",
+                               program_name, me->name);
+                       exit(1);
+               }
+
+               /* Now we have two (or more) options, check compatibility. */
+               if (compatible_target_revision(old->name, old->revision)
+                   && old->revision > me->revision)
+                       return;
+
+               /* Replace if compatible. */
+               if (!compatible_target_revision(me->name, me->revision))
+                       return;
+
+               /* Delete old one. */
+               for (i = &iptables_targets; *i!=old; i = &(*i)->next);
+               *i = old->next;
+       }
+
        if (me->size != IPT_ALIGN(me->size)) {
                fprintf(stderr, "%s: target `%s' has invalid size %u.\n",
-                       program_name, me->name, me->size);
+                       program_name, me->name, (unsigned int)me->size);
                exit(1);
        }
 
@@ -1077,17 +1218,17 @@ print_num(u_int64_t number, unsigned int format)
                                        number = (number + 500) / 1000;
                                        if (number > 9999) {
                                                number = (number + 500) / 1000;
-                                               printf(FMT("%4lluT ","%lluT "), number);
+                                               printf(FMT("%4lluT ","%lluT "), (unsigned long long)number);
                                        }
-                                       else printf(FMT("%4lluG ","%lluG "), number);
+                                       else printf(FMT("%4lluG ","%lluG "), (unsigned long long)number);
                                }
-                               else printf(FMT("%4lluM ","%lluM "), number);
+                               else printf(FMT("%4lluM ","%lluM "), (unsigned long long)number);
                        } else
-                               printf(FMT("%4lluK ","%lluK "), number);
+                               printf(FMT("%4lluK ","%lluK "), (unsigned long long)number);
                } else
-                       printf(FMT("%5llu ","%llu "), number);
+                       printf(FMT("%5llu ","%llu "), (unsigned long long)number);
        } else
-               printf(FMT("%8llu ","%llu "), number);
+               printf(FMT("%8llu ","%llu "), (unsigned long long)number);
 }
 
 
@@ -1146,7 +1287,7 @@ print_match(const struct ipt_entry_match *m,
            const struct ipt_ip *ip,
            int numeric)
 {
-       struct iptables_match *match = find_match(m->u.user.name, TRY_LOAD);
+       struct iptables_match *match = find_match(m->u.user.name, TRY_LOAD, NULL);
 
        if (match) {
                if (match->print)
@@ -1275,7 +1416,7 @@ print_firewall(const struct ipt_entry *fw,
                        target->print(&fw->ip, t, format & FMT_NUMERIC);
        } else if (t->u.target_size != sizeof(*t))
                printf("[%u bytes of unknown target data] ",
-                      t->u.target_size - sizeof(*t));
+                      (unsigned int)(t->u.target_size - sizeof(*t)));
 
        if (!(format & FMT_NONEWLINE))
                fputc('\n', stdout);
@@ -1362,20 +1503,16 @@ insert_entry(const ipt_chainlabel chain,
 }
 
 static unsigned char *
-make_delete_mask(struct ipt_entry *fw)
+make_delete_mask(struct ipt_entry *fw, struct iptables_rule_match *matches)
 {
        /* Establish mask for comparison */
        unsigned int size;
-       struct iptables_match *m;
+       struct iptables_rule_match *matchp;
        unsigned char *mask, *mptr;
 
        size = sizeof(struct ipt_entry);
-       for (m = iptables_matches; m; m = m->next) {
-               if (!m->used)
-                       continue;
-
-               size += IPT_ALIGN(sizeof(struct ipt_entry_match)) + m->size;
-       }
+       for (matchp = matches; matchp; matchp = matchp->next)
+               size += IPT_ALIGN(sizeof(struct ipt_entry_match)) + matchp->match->size;
 
        mask = fw_calloc(1, size
                         + IPT_ALIGN(sizeof(struct ipt_entry_target))
@@ -1384,14 +1521,11 @@ make_delete_mask(struct ipt_entry *fw)
        memset(mask, 0xFF, sizeof(struct ipt_entry));
        mptr = mask + sizeof(struct ipt_entry);
 
-       for (m = iptables_matches; m; m = m->next) {
-               if (!m->used)
-                       continue;
-
+       for (matchp = matches; matchp; matchp = matchp->next) {
                memset(mptr, 0xFF,
                       IPT_ALIGN(sizeof(struct ipt_entry_match))
-                      + m->userspacesize);
-               mptr += IPT_ALIGN(sizeof(struct ipt_entry_match)) + m->size;
+                      + matchp->match->userspacesize);
+               mptr += IPT_ALIGN(sizeof(struct ipt_entry_match)) + matchp->match->size;
        }
 
        memset(mptr, 0xFF,
@@ -1409,13 +1543,14 @@ delete_entry(const ipt_chainlabel chain,
             unsigned int ndaddrs,
             const struct in_addr daddrs[],
             int verbose,
-            iptc_handle_t *handle)
+            iptc_handle_t *handle,
+            struct iptables_rule_match *matches)
 {
        unsigned int i, j;
        int ret = 1;
        unsigned char *mask;
 
-       mask = make_delete_mask(fw);
+       mask = make_delete_mask(fw, matches);
        for (i = 0; i < nsaddrs; i++) {
                fw->ip.src.s_addr = saddrs[i].s_addr;
                for (j = 0; j < ndaddrs; j++) {
@@ -1425,6 +1560,8 @@ delete_entry(const ipt_chainlabel chain,
                        ret &= iptc_delete_entry(chain, fw, mask, handle);
                }
        }
+       free(mask);
+
        return ret;
 }
 
@@ -1455,7 +1592,7 @@ for_each_chain(int (*fn)(const ipt_chainlabel, int, iptc_handle_t *),
        for (i = 0; i < chaincount; i++) {
                if (!builtinstoo
                    && iptc_builtin(chains + i*sizeof(ipt_chainlabel),
-                                   *handle))
+                                   *handle) == 1)
                        continue;
                ret &= fn(chains + i*sizeof(ipt_chainlabel), verbose, handle);
        }
@@ -1558,15 +1695,17 @@ static char *get_modprobe(void)
        int procfile;
        char *ret;
 
+#define PROCFILE_BUFSIZ        1024
        procfile = open(PROC_SYS_MODPROBE, O_RDONLY);
        if (procfile < 0)
                return NULL;
 
-       ret = malloc(1024);
+       ret = (char *) malloc(PROCFILE_BUFSIZ);
        if (ret) {
-               switch (read(procfile, ret, 1024)) {
+               memset(ret, 0, PROCFILE_BUFSIZ);
+               switch (read(procfile, ret, PROCFILE_BUFSIZ)) {
                case -1: goto fail;
-               case 1024: goto fail; /* Partial read.  Wierd */
+               case PROCFILE_BUFSIZ: goto fail; /* Partial read.  Wierd */
                }
                if (ret[strlen(ret)-1]=='\n') 
                        ret[strlen(ret)-1]=0;
@@ -1583,7 +1722,7 @@ int iptables_insmod(const char *modname, const char *modprobe)
 {
        char *buf = NULL;
        char *argv[3];
-       int i=0;
+       int status;
 
        /* If they don't explicitly set it, read out of kernel */
        if (!modprobe) {
@@ -1595,44 +1734,38 @@ int iptables_insmod(const char *modname, const char *modprobe)
 
        switch (fork()) {
        case 0:
-               /* close open file descriptors */
-               for (i=0; i< 10; i++) {
-                 close(i);
-               }
                argv[0] = (char *)modprobe;
                argv[1] = (char *)modname;
                argv[2] = NULL;
                execv(argv[0], argv);
 
                /* not usually reached */
-               exit(0);
+               exit(1);
        case -1:
                return -1;
 
        default: /* parent */
-               wait(NULL);
+               wait(&status);
        }
 
        free(buf);
-       return 0;
+       if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
+               return 0;
+       return -1;
 }
 
 static struct ipt_entry *
 generate_entry(const struct ipt_entry *fw,
-              struct iptables_match *matches,
+              struct iptables_rule_match *matches,
               struct ipt_entry_target *target)
 {
        unsigned int size;
-       struct iptables_match *m;
+       struct iptables_rule_match *matchp;
        struct ipt_entry *e;
 
        size = sizeof(struct ipt_entry);
-       for (m = matches; m; m = m->next) {
-               if (!m->used)
-                       continue;
-
-               size += m->m->u.match_size;
-       }
+       for (matchp = matches; matchp; matchp = matchp->next)
+               size += matchp->match->m->u.match_size;
 
        e = fw_malloc(size + target->u.target_size);
        *e = *fw;
@@ -1640,18 +1773,38 @@ generate_entry(const struct ipt_entry *fw,
        e->next_offset = size + target->u.target_size;
 
        size = 0;
-       for (m = matches; m; m = m->next) {
-               if (!m->used)
-                       continue;
-
-               memcpy(e->elems + size, m->m, m->m->u.match_size);
-               size += m->m->u.match_size;
+       for (matchp = matches; matchp; matchp = matchp->next) {
+               memcpy(e->elems + size, matchp->match->m, matchp->match->m->u.match_size);
+               size += matchp->match->m->u.match_size;
        }
        memcpy(e->elems + size, target, target->u.target_size);
 
        return e;
 }
 
+void clear_rule_matches(struct iptables_rule_match **matches)
+{
+       struct iptables_rule_match *matchp, *tmp;
+
+       for (matchp = *matches; matchp;) {
+               tmp = matchp->next;
+               if (matchp->match->m)
+                       free(matchp->match->m);
+               free(matchp);
+               matchp = tmp;
+       }
+
+       *matches = NULL;
+}
+
+static void set_revision(char *name, u_int8_t revision)
+{
+       /* Old kernel sources don't have ".revision" field,
+          but we stole a byte from name. */
+       name[IPT_FUNCTION_MAXNAMELEN - 2] = '\0';
+       name[IPT_FUNCTION_MAXNAMELEN - 1] = revision;
+}
+
 int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
 {
        struct ipt_entry fw, *e = NULL;
@@ -1667,6 +1820,8 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
        const char *pcnt = NULL, *bcnt = NULL;
        int ret = 1;
        struct iptables_match *m;
+       struct iptables_rule_match *matches = NULL;
+       struct iptables_rule_match *matchp;
        struct iptables_target *target = NULL;
        struct iptables_target *t;
        const char *jumpto = "";
@@ -1676,19 +1831,14 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
 
        memset(&fw, 0, sizeof(fw));
 
-       opts = original_opts;
-       global_option_offset = 0;
-
        /* re-set optind to 0 in case do_command gets called
         * a second time */
        optind = 0;
 
        /* clear mflags in case do_command gets called a second time
         * (we clear the global list of all matches for security)*/
-       for (m = iptables_matches; m; m = m->next) {
+       for (m = iptables_matches; m; m = m->next)
                m->mflags = 0;
-               m->used = 0;
-       }
 
        for (t = iptables_targets; t; t = t->next) {
                t->tflags = 0;
@@ -1774,10 +1924,10 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                        break;
 
                case 'N':
-                       if (optarg && *optarg == '-')
+                       if (optarg && (*optarg == '-' || *optarg == '!'))
                                exit_error(PARAMETER_PROBLEM,
                                           "chain name not allowed to start "
-                                          "with `-'\n");
+                                          "with `%c'\n", *optarg);
                        if (find_target(optarg, TRY_LOAD))
                                exit_error(PARAMETER_PROBLEM,
                                           "chain name may not clash "
@@ -1828,10 +1978,10 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                                optarg = argv[optind];
 
                        /* iptables -p icmp -h */
-                       if (!iptables_matches && protocol)
-                               find_match(protocol, TRY_LOAD);
+                       if (!matches && protocol)
+                               find_match(protocol, TRY_LOAD, &matches);
 
-                       exit_printhelp();
+                       exit_printhelp(matches);
 
                        /*
                         * Option selection
@@ -1852,7 +2002,6 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                            && (fw.ip.invflags & IPT_INV_PROTO))
                                exit_error(PARAMETER_PROBLEM,
                                           "rule would never match protocol");
-                       fw.nfcache |= NFC_IP_PROTO;
                        break;
 
                case 's':
@@ -1860,7 +2009,6 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                        set_option(&options, OPT_SOURCE, &fw.ip.invflags,
                                   invert);
                        shostnetworkmask = argv[optind-1];
-                       fw.nfcache |= NFC_IP_SRC;
                        break;
 
                case 'd':
@@ -1868,7 +2016,6 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                        set_option(&options, OPT_DESTINATION, &fw.ip.invflags,
                                   invert);
                        dhostnetworkmask = argv[optind-1];
-                       fw.nfcache |= NFC_IP_DST;
                        break;
 
                case 'j':
@@ -1887,7 +2034,10 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                                target->t = fw_calloc(1, size);
                                target->t->u.target_size = size;
                                strcpy(target->t->u.user.name, jumpto);
-                               target->init(target->t, &fw.nfcache);
+                               set_revision(target->t->u.user.name,
+                                            target->revision);
+                               if (target->init != NULL)
+                                       target->init(target->t, &fw.nfcache);
                                opts = merge_options(opts, target->extra_opts, &target->option_offset);
                        }
                        break;
@@ -1900,7 +2050,6 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                        parse_interface(argv[optind-1],
                                        fw.ip.iniface,
                                        fw.ip.iniface_mask);
-                       fw.nfcache |= NFC_IP_IF_IN;
                        break;
 
                case 'o':
@@ -1910,14 +2059,12 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                        parse_interface(argv[optind-1],
                                        fw.ip.outiface,
                                        fw.ip.outiface_mask);
-                       fw.nfcache |= NFC_IP_IF_OUT;
                        break;
 
                case 'f':
                        set_option(&options, OPT_FRAGMENT, &fw.ip.invflags,
                                   invert);
                        fw.ip.flags |= IPT_F_FRAG;
-                       fw.nfcache |= NFC_IP_FRAG;
                        break;
 
                case 'v':
@@ -1934,13 +2081,15 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                                exit_error(PARAMETER_PROBLEM,
                                           "unexpected ! flag before --match");
 
-                       m = find_match(optarg, LOAD_MUST_SUCCEED);
+                       m = find_match(optarg, LOAD_MUST_SUCCEED, &matches);
                        size = IPT_ALIGN(sizeof(struct ipt_entry_match))
                                         + m->size;
                        m->m = fw_calloc(1, size);
                        m->m->u.match_size = size;
                        strcpy(m->m->u.user.name, m->name);
-                       m->init(m->m, &fw.nfcache);
+                       set_revision(m->m->u.user.name, m->revision);
+                       if (m->init != NULL)
+                               m->init(m->m, &fw.nfcache);
                        opts = merge_options(opts, m->extra_opts, &m->option_offset);
                }
                break;
@@ -1992,12 +2141,12 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                                        "-%c requires packet and byte counter",
                                        opt2char(OPT_COUNTERS));
 
-                       if (sscanf(pcnt, "%llu", &fw.counters.pcnt) != 1)
+                       if (sscanf(pcnt, "%llu", (unsigned long long *)&fw.counters.pcnt) != 1)
                                exit_error(PARAMETER_PROBLEM,
                                        "-%c packet counter not numeric",
                                        opt2char(OPT_COUNTERS));
 
-                       if (sscanf(bcnt, "%llu", &fw.counters.bcnt) != 1)
+                       if (sscanf(bcnt, "%llu", (unsigned long long *)&fw.counters.bcnt) != 1)
                                exit_error(PARAMETER_PROBLEM,
                                        "-%c byte counter not numeric",
                                        opt2char(OPT_COUNTERS));
@@ -2026,18 +2175,16 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                                               argv, invert,
                                               &target->tflags,
                                               &fw, &target->t))) {
-                               for (m = iptables_matches; m; m = m->next) {
-                                       if (!m->used)
-                                               continue;
-
-                                       if (m->parse(c - m->option_offset,
+                               for (matchp = matches; matchp; matchp = matchp->next) {
+                                       if (matchp->match->parse(c - matchp->match->option_offset,
                                                     argv, invert,
-                                                    &m->mflags,
+                                                    &matchp->match->mflags,
                                                     &fw,
                                                     &fw.nfcache,
-                                                    &m->m))
+                                                    &matchp->match->m))
                                                break;
                                }
+                               m = matchp ? matchp->match : NULL;
 
                                /* If you listen carefully, you can
                                   actually hear this code suck. */
@@ -2065,13 +2212,13 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                                if (m == NULL
                                    && protocol
                                    && (!find_proto(protocol, DONT_LOAD,
-                                                  options&OPT_NUMERIC) 
+                                                  options&OPT_NUMERIC, NULL
                                        || (find_proto(protocol, DONT_LOAD,
-                                                       options&OPT_NUMERIC)
+                                                       options&OPT_NUMERIC, NULL)
                                            && (proto_used == 0))
                                       )
                                    && (m = find_proto(protocol, TRY_LOAD,
-                                                      options&OPT_NUMERIC))) {
+                                                      options&OPT_NUMERIC, &matches))) {
                                        /* Try loading protocol */
                                        size_t size;
                                        
@@ -2083,7 +2230,10 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                                        m->m = fw_calloc(1, size);
                                        m->m->u.match_size = size;
                                        strcpy(m->m->u.user.name, m->name);
-                                       m->init(m->m, &fw.nfcache);
+                                       set_revision(m->m->u.user.name,
+                                                    m->revision);
+                                       if (m->init != NULL)
+                                               m->init(m->m, &fw.nfcache);
 
                                        opts = merge_options(opts,
                                            m->extra_opts, &m->option_offset);
@@ -2100,12 +2250,8 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                invert = FALSE;
        }
 
-       for (m = iptables_matches; m; m = m->next) {
-               if (!m->used)
-                       continue;
-
-               m->final_check(m->mflags);
-       }
+       for (matchp = matches; matchp; matchp = matchp->next)
+               matchp->match->final_check(matchp->match->mflags);
 
        if (target)
                target->final_check(target->tflags);
@@ -2156,11 +2302,9 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
        if (!*handle)
                *handle = iptc_init(*table);
 
-       if (!*handle) {
-               /* try to insmod the module if iptc_init failed */
-               iptables_insmod("ip_tables", modprobe);
+       /* try to insmod the module if iptc_init failed */
+       if (!*handle && iptables_insmod("ip_tables", modprobe) != -1)
                *handle = iptc_init(*table);
-       }
 
        if (!*handle)
                exit_error(VERSION_PROBLEM,
@@ -2195,6 +2339,9 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                        printf("Warning: using chain %s, not extension\n",
                               jumpto);
 
+                       if (target->t)
+                               free(target->t);
+
                        target = NULL;
                }
 
@@ -2213,7 +2360,9 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                        target->t = fw_calloc(1, size);
                        target->t->u.target_size = size;
                        strcpy(target->t->u.user.name, jumpto);
-                       target->init(target->t, &fw.nfcache);
+                       set_revision(target->t->u.user.name, target->revision);
+                       if (target->init != NULL)
+                               target->init(target->t, &fw.nfcache);
                }
 
                if (!target) {
@@ -2223,7 +2372,8 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                         * chain. */
                        find_target(jumpto, LOAD_MUST_SUCCEED);
                } else {
-                       e = generate_entry(&fw, iptables_matches, target->t);
+                       e = generate_entry(&fw, matches, target->t);
+                       free(target->t);
                }
        }
 
@@ -2238,7 +2388,7 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
                ret = delete_entry(chain, e,
                                   nsaddrs, saddrs, ndaddrs, daddrs,
                                   options&OPT_VERBOSE,
-                                  handle);
+                                  handle, matches);
                break;
        case CMD_DELETE_NUM:
                ret = iptc_delete_num_entry(chain, rulenum - 1, handle);
@@ -2299,5 +2449,16 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
        if (verbose > 1)
                dump_entries(*handle);
 
+       clear_rule_matches(&matches);
+
+       if (e != NULL) {
+               free(e);
+               e = NULL;
+       }
+
+       free(saddrs);
+       free(daddrs);
+       free_opts(1);
+
        return ret;
 }