update to iptables-1.3.8
[iptables.git] / ip6tables.c
index 6afe68f..f316c7a 100644 (file)
@@ -81,8 +81,7 @@
 #define CMD_NEW_CHAIN          0x0100U
 #define CMD_DELETE_CHAIN       0x0200U
 #define CMD_SET_POLICY         0x0400U
-#define CMD_CHECK              0x0800U
-#define CMD_RENAME_CHAIN       0x1000U
+#define CMD_RENAME_CHAIN       0x0800U
 #define NUMBER_OF_CMD  13
 static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z',
                                 'N', 'X', 'P', 'E' };
@@ -159,20 +158,19 @@ static unsigned int global_option_offset = 0;
 static char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] =
 /* Well, it's better than "Re: Linux vs FreeBSD" */
 {
-       /*     -n  -s  -d  -p  -j  -v  -x  -i  -o  --line */
-/*INSERT*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x'},
-/*DELETE*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x'},
-/*DELETE_NUM*/{'x','x','x','x','x',' ','x','x','x','x'},
-/*REPLACE*/   {'x',' ',' ',' ',' ',' ','x',' ',' ','x'},
-/*APPEND*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x'},
-/*LIST*/      {' ','x','x','x','x',' ',' ','x','x',' '},
-/*FLUSH*/     {'x','x','x','x','x',' ','x','x','x','x'},
-/*ZERO*/      {'x','x','x','x','x',' ','x','x','x','x'},
-/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x'},
-/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x'},
-/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x'},
-/*CHECK*/     {'x','+','+','+','x',' ','x',' ',' ','x'},
-/*RENAME*/    {'x','x','x','x','x',' ','x','x','x','x'}
+       /*     -n  -s  -d  -p  -j  -v  -x  -i  -o  --line -c */
+/*INSERT*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' '},
+/*DELETE*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x'},
+/*DELETE_NUM*/{'x','x','x','x','x',' ','x','x','x','x','x'},
+/*REPLACE*/   {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' '},
+/*APPEND*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' '},
+/*LIST*/      {' ','x','x','x','x',' ',' ','x','x',' ','x'},
+/*FLUSH*/     {'x','x','x','x','x',' ','x','x','x','x','x'},
+/*ZERO*/      {'x','x','x','x','x',' ','x','x','x','x','x'},
+/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x'},
+/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x'},
+/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x','x'},
+/*RENAME*/    {'x','x','x','x','x',' ','x','x','x','x','x'}
 };
 
 static int inverse_for_options[NUMBER_OF_OPT] =
@@ -186,13 +184,17 @@ static int inverse_for_options[NUMBER_OF_OPT] =
 /* -x */ 0,
 /* -i */ IP6T_INV_VIA_IN,
 /* -o */ IP6T_INV_VIA_OUT,
-/*--line*/ 0
+/*--line*/ 0,
+/* -c */ 0,
 };
 
 const char *program_version;
 const char *program_name;
 char *lib_dir;
 
+/* the path to command to load kernel module */
+const char *modprobe = NULL;
+
 /* Keeping track of external matches and targets: linked lists.  */
 struct ip6tables_match *ip6tables_matches = NULL;
 struct ip6tables_target *ip6tables_targets = NULL;
@@ -217,13 +219,20 @@ struct pprot {
 #define IPPROTO_AH 51
 #endif
 #endif
+#ifndef IPPROTO_MH
+#define IPPROTO_MH 135
+#endif
 
 static const struct pprot chain_protos[] = {
        { "tcp", IPPROTO_TCP },
        { "udp", IPPROTO_UDP },
+       { "udplite", IPPROTO_UDPLITE },
        { "icmpv6", IPPROTO_ICMPV6 },
+       { "ipv6-icmp", IPPROTO_ICMPV6 },
        { "esp", IPPROTO_ESP },
        { "ah", IPPROTO_AH },
+       { "ipv6-mh", IPPROTO_MH },
+       { "mh", IPPROTO_MH },
        { "all", 0 },
 };
 
@@ -245,6 +254,30 @@ proto_to_name(u_int8_t proto, int nolookup)
        return NULL;
 }
 
+int
+service_to_port(const char *name, const char *proto)
+{
+       struct servent *service;
+
+       if ((service = getservbyname(name, proto)) != NULL)
+               return ntohs((unsigned short) service->s_port);
+
+       return -1;
+}
+
+u_int16_t
+parse_port(const char *port, const char *proto)
+{
+       unsigned int portnum;
+
+       if ((string_to_number(port, 0, 65535, &portnum)) != -1 ||
+           (portnum = service_to_port(port, proto)) != -1)
+               return (u_int16_t)portnum;
+
+       exit_error(PARAMETER_PROBLEM,
+                  "invalid port/service `%s' specified", port);
+}
+
 static void
 in6addrcpy(struct in6_addr *dst, struct in6_addr *src)
 {
@@ -710,37 +743,46 @@ parse_hostnetworkmask(const char *name, struct in6_addr **addrpp,
 }
 
 struct ip6tables_match *
-find_match(const char *name, enum ip6t_tryload tryload, struct ip6tables_rule_match **matches)
+find_match(const char *match_name, enum ip6t_tryload tryload, struct ip6tables_rule_match **matches)
 {
        struct ip6tables_match *ptr;
-       int icmphack = 0;
+       const char *icmp6 = "icmp6";
+       const char *name;
   
        /* This is ugly as hell. Nonetheless, there is no way of changing
         * this without hurting backwards compatibility */
-       if ( (strcmp(name,"icmpv6") == 0) ||
-            (strcmp(name,"ipv6-icmp") == 0) ||
-            (strcmp(name,"icmp6") == 0) ) icmphack = 1;
+       if ( (strcmp(match_name,"icmpv6") == 0) ||
+            (strcmp(match_name,"ipv6-icmp") == 0) ||
+            (strcmp(match_name,"icmp6") == 0) )
+               name = icmp6;
+       else
+               name = match_name;
  
-       if (!icmphack) {
-               for (ptr = ip6tables_matches; ptr; ptr = ptr->next) {
-                       if (strcmp(name, ptr->name) == 0)
-                               break;
-               }
-       } else {
-               for (ptr = ip6tables_matches; ptr; ptr = ptr->next) {
-                       if (strcmp("icmp6", ptr->name) == 0)
-                               break;
-               }
-       }
+       for (ptr = ip6tables_matches; ptr; ptr = ptr->next) {
+               if (strcmp(name, ptr->name) == 0) {
+                       struct ip6tables_match *clone;
+                       
+                       /* First match of this type: */
+                       if (ptr->m == NULL)
+                               break;
+
+                       /* Second and subsequent clones */
+                       clone = fw_malloc(sizeof(struct ip6tables_match));
+                       memcpy(clone, ptr, sizeof(struct ip6tables_match));
+                       clone->mflags = 0;
+                       /* This is a clone: */
+                       clone->next = clone;
+
+                       ptr = clone;
+                       break;
+               }
+       }
 
 #ifndef NO_SHARED_LIBS
        if (!ptr && tryload != DONT_LOAD && tryload != DURING_LOAD) {
                char path[strlen(lib_dir) + sizeof("/libip6t_.so")
                         + strlen(name)];
-               if (!icmphack)
-                       sprintf(path, "%s/libip6t_%s.so", lib_dir, name);
-               else
-                       sprintf(path, "%s/libip6t_%s.so", lib_dir, "icmpv6");
+               sprintf(path, "%s/libip6t_%s.so", lib_dir, name);
                if (dlopen(path, RTLD_NOW)) {
                        /* Found library.  If it didn't register itself,
                           maybe they specified target as match. */
@@ -774,8 +816,12 @@ find_match(const char *name, enum ip6t_tryload tryload, struct ip6tables_rule_ma
 
                newentry = fw_malloc(sizeof(struct ip6tables_rule_match));
 
-               for (i = matches; *i; i = &(*i)->next);
+               for (i = matches; *i; i = &(*i)->next) {
+                       if (strcmp(name, (*i)->match->name) == 0)
+                               (*i)->completed = 1;
+               }
                newentry->match = ptr;
+               newentry->completed = 0;
                newentry->next = NULL;
                *i = newentry;
        }
@@ -808,6 +854,13 @@ parse_protocol(const char *s)
        if (string_to_number(s, 0, 255, &proto) == -1) {
                struct protoent *pent;
 
+               /* first deal with the special case of 'all' to prevent
+                * people from being able to redefine 'all' in nsswitch
+                * and/or provoke expensive [not working] ldap/nis/... 
+                * lookups */
+               if (!strcmp(s, "all"))
+                       return 0;
+
                if ((pent = getprotobyname(s)))
                        proto = pent->p_proto;
                else {
@@ -830,6 +883,15 @@ parse_protocol(const char *s)
        return (u_int16_t)proto;
 }
 
+/* These are invalid numbers as upper layer protocol */
+static int is_exthdr(u_int16_t proto)
+{
+       return (proto == IPPROTO_ROUTING ||
+               proto == IPPROTO_FRAGMENT ||
+               proto == IPPROTO_AH ||
+               proto == IPPROTO_DSTOPTS);
+}
+
 void parse_interface(const char *arg, char *vianame, unsigned char *mask)
 {
        int vialen = strlen(arg);
@@ -855,10 +917,10 @@ void parse_interface(const char *arg, char *vianame, unsigned char *mask)
                memset(mask, 0xFF, vialen + 1);
                memset(mask + vialen + 1, 0, IFNAMSIZ - vialen - 1);
                for (i = 0; vianame[i]; i++) {
-                       if (!isalnum(vianame[i]) 
-                           && vianame[i] != '_' 
-                           && vianame[i] != '.') {
-                               printf("Warning: wierd character in interface"
+                       if (vianame[i] == ':' ||
+                           vianame[i] == '!' ||
+                           vianame[i] == '*') {
+                               printf("Warning: weird character in interface"
                                       " `%s' (No aliases, :, ! or *).\n",
                                       vianame);
                                break;
@@ -1047,10 +1109,53 @@ 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 ip6t_get_revision rev;
+       socklen_t s = sizeof(rev);
+       int max_rev, sockfd;
+
+       sockfd = socket(AF_INET6, 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;
+
+       load_ip6tables_ko(modprobe, 1);
+
+       max_rev = getsockopt(sockfd, IPPROTO_IPV6, 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, IP6T_SO_GET_REVISION_MATCH);
+}
+
 void
 register_match6(struct ip6tables_match *me)
 {
-       struct ip6tables_match **i;
+       struct ip6tables_match **i, *old;
 
        if (strcmp(me->version, program_version) != 0) {
                fprintf(stderr, "%s: match `%s' v%s (I'm v%s).\n",
@@ -1058,12 +1163,36 @@ register_match6(struct ip6tables_match *me)
                exit(1);
        }
 
-       if (find_match(me->name, DURING_LOAD, NULL)) {
-               fprintf(stderr, "%s: match `%s' already registered.\n",
+       /* Revision field stole a char from name. */
+       if (strlen(me->name) >= IP6T_FUNCTION_MAXNAMELEN-1) {
+               fprintf(stderr, "%s: target `%s' has invalid name\n",
                        program_name, me->name);
                exit(1);
        }
 
+       old = find_match(me->name, DURING_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 = &ip6tables_matches; *i!=old; i = &(*i)->next);
+               *i = old->next;
+       }
+       
        if (me->size != IP6T_ALIGN(me->size)) {
                fprintf(stderr, "%s: match `%s' has invalid size %u.\n",
                        program_name, me->name, (unsigned int)me->size);
@@ -1621,10 +1750,10 @@ static char *get_modprobe(void)
        return NULL;
 }
 
-int ip6tables_insmod(const char *modname, const char *modprobe)
+int ip6tables_insmod(const char *modname, const char *modprobe, int quiet)
 {
        char *buf = NULL;
-       char *argv[3];
+       char *argv[4];
        int status;
 
        /* If they don't explicitly set it, read out of kernel */
@@ -1639,7 +1768,13 @@ int ip6tables_insmod(const char *modname, const char *modprobe)
        case 0:
                argv[0] = (char *)modprobe;
                argv[1] = (char *)modname;
-               argv[2] = NULL;
+               if (quiet) {
+                       argv[2] = "-q";
+                       argv[3] = NULL;
+               } else {
+                       argv[2] = NULL;
+                       argv[3] = NULL;
+               }
                execv(argv[0], argv);
 
                /* not usually reached */
@@ -1657,6 +1792,19 @@ int ip6tables_insmod(const char *modname, const char *modprobe)
        return -1;
 }
 
+int load_ip6tables_ko(const char *modprobe, int quiet)
+{
+       static int loaded = 0;
+       static int ret = -1;
+
+       if (!loaded) {
+               ret = ip6tables_insmod("ip6_tables", modprobe, quiet);
+               loaded = (ret == 0);
+       }
+
+       return ret;
+}
+
 static struct ip6t_entry *
 generate_entry(const struct ip6t_entry *fw,
               struct ip6tables_rule_match *matches,
@@ -1691,8 +1839,14 @@ void clear_rule_matches(struct ip6tables_rule_match **matches)
 
        for (matchp = *matches; matchp;) {
                tmp = matchp->next;
-               if (matchp->match->m)
+               if (matchp->match->m) {
                        free(matchp->match->m);
+                       matchp->match->m = NULL;
+               }
+               if (matchp->match == matchp->match->next) {
+                       free(matchp->match);
+                       matchp->match = NULL;
+               }
                free(matchp);
                matchp = tmp;
        }
@@ -1700,6 +1854,14 @@ void clear_rule_matches(struct ip6tables_rule_match **matches)
        *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[IP6T_FUNCTION_MAXNAMELEN - 2] = '\0';
+       name[IP6T_FUNCTION_MAXNAMELEN - 1] = revision;
+}
+
 int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle)
 {
        struct ip6t_entry fw, *e = NULL;
@@ -1721,9 +1883,7 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle)
        struct ip6tables_target *t;
        const char *jumpto = "";
        char *protocol = NULL;
-       const char *modprobe = NULL;
        int proto_used = 0;
-       char icmp6p[] = "icmpv6";
 
        memset(&fw, 0, sizeof(fw));
 
@@ -1851,7 +2011,7 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle)
                                newname = argv[optind++];
                        else
                                exit_error(PARAMETER_PROBLEM,
-                                          "-%c requires old-chain-name and "
+                                          "-%c requires old-chain-name and "
                                           "new-chain-name",
                                            cmd2char(CMD_RENAME_CHAIN));
                        break;
@@ -1892,8 +2052,6 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle)
                                *protocol = tolower(*protocol);
 
                        protocol = argv[optind-1];
-                       if ( strcmp(protocol,"ipv6-icmp") == 0)
-                               protocol = icmp6p;
                        fw.ipv6.proto = parse_protocol(protocol);
                        fw.ipv6.flags |= IP6T_F_PROTO;
 
@@ -1901,6 +2059,12 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle)
                            && (fw.ipv6.invflags & IP6T_INV_PROTO))
                                exit_error(PARAMETER_PROBLEM,
                                           "rule would never match protocol");
+                       
+                       if (is_exthdr(fw.ipv6.proto)
+                           && (fw.ipv6.invflags & IP6T_INV_PROTO) == 0)
+                               printf("Warning: never matched protocol: %s. "
+                                      "use extension match instead.\n",
+                                      protocol);
                        break;
 
                case 's':
@@ -1978,9 +2142,12 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle)
                        m->m = fw_calloc(1, size);
                        m->m->u.match_size = size;
                        strcpy(m->m->u.user.name, m->name);
+                       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);
+                       if (m != m->next)
+                               /* Merge options for non-cloned matches */
+                               opts = merge_options(opts, m->extra_opts, &m->option_offset);
                }
                break;
 
@@ -2058,14 +2225,14 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle)
                        exit_tryhelp(2);
 
                default:
-                       /* FIXME: This scheme doesn't allow two of the same
-                          matches --RR */
                        if (!target
                            || !(target->parse(c - target->option_offset,
                                               argv, invert,
                                               &target->tflags,
                                               &fw, &target->t))) {
                                for (matchp = matches; matchp; matchp = matchp->next) {
+                                       if (matchp->completed) 
+                                               continue;
                                        if (matchp->match->parse(c - matchp->match->option_offset,
                                                     argv, invert,
                                                     &matchp->match->mflags,
@@ -2080,7 +2247,7 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle)
                                   actually hear this code suck. */
 
                                /* some explanations (after four different bugs
-                                * in 3 different releases): If we encountere a
+                                * in 3 different releases): If we encounter a
                                 * parameter, that has not been parsed yet,
                                 * it's not an option of an explicitly loaded
                                 * match or a target.  However, we support
@@ -2120,6 +2287,8 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle)
                                        m->m = fw_calloc(1, size);
                                        m->m->u.match_size = size;
                                        strcpy(m->m->u.user.name, m->name);
+                                       set_revision(m->m->u.user.name,
+                                                    m->revision);
                                        if (m->init != NULL)
                                                m->init(m->m, &fw.nfcache);
 
@@ -2192,7 +2361,7 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle)
                *handle = ip6tc_init(*table);
 
        /* try to insmod the module if iptc_init failed */
-       if (!*handle && ip6tables_insmod("ip6_tables", modprobe) != -1)
+       if (!*handle && load_ip6tables_ko(modprobe, 0) != -1)
                *handle = ip6tc_init(*table);
 
        if (!*handle)