This patch is part of a proposal to add a string filter to
ebtables, which would be similar to the string filter in
iptables.

Like iptables, the ebtables filter uses the xt_string module,
however some modifications have been made for this to work
correctly.

Currently ebtables assumes that the revision number of all match
modules is 0. The xt_string module doesn't register a match with
revision 0 so the solution is to modify ebtables to allow
extensions to specify a revision number, similar to iptables.
This gets passed down to the kernel, which is then able to find
the match module correctly.

Signed-off-by: Bernie Harris <[email protected]>
---
 ebtables.8              |  20 +++
 extensions/Makefile     |   2 +-
 extensions/ebt_string.c | 319 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/ebtables.h      |  16 ++-
 include/ebtables_u.h    |   1 +
 libebtc.c               |   6 +-
 6 files changed, 359 insertions(+), 5 deletions(-)
 create mode 100644 extensions/ebt_string.c

diff --git a/ebtables.8 b/ebtables.8
index 81d1cf6..e3290fe 100644
--- a/ebtables.8
+++ b/ebtables.8
@@ -810,6 +810,26 @@ The hello time timer (0-65535) range.
 .TP
 .BR "--stp-forward-delay " "[!] [\fIdelay\fP][:\fIdelay\fP]"
 The forward delay timer (0-65535) range.
+.SS string
+This module matches on a given string using some pattern matching strategy.
+.TP
+.BR "--string-algo " "\fIalgorithm\fP"
+The pattern matching strategy. (bm = Boyer-Moore, kmp = Knuth-Pratt-Morris)
+.TP
+.BR "--string-from " "\fIoffset\fP"
+The lowest offset from which a match can start. (default: 0)
+.TP
+.BR "--string-to " "\fIoffset\fP"
+The highest offset from which a match can start. (default: size of frame)
+.TP
+.BR "--string " "[!] \fIpattern\fP"
+Matches the given pattern.
+.TP
+.BR "--string-hex " "[!] \fIpattern\fP"
+Matches the given pattern in hex notation, e.g. '|0D 0A|', '|0D0A|', 
'www|09|netfilter|03|org|00|'
+.TP
+.BR "--string-icase"
+Ignore case when searching.
 .SS vlan
 Specify 802.1Q Tag Control Information fields.
 The protocol must be specified as
diff --git a/extensions/Makefile b/extensions/Makefile
index b3548e8..60a70a2 100644
--- a/extensions/Makefile
+++ b/extensions/Makefile
@@ -1,7 +1,7 @@
 #! /usr/bin/make
 
 EXT_FUNC+=802_3 nat arp arpreply ip ip6 standard log redirect vlan mark_m mark 
\
-          pkttype stp among limit ulog nflog
+          pkttype stp among limit ulog nflog string
 EXT_TABLES+=filter nat broute
 EXT_OBJS+=$(foreach T,$(EXT_FUNC), extensions/ebt_$(T).o)
 EXT_OBJS+=$(foreach T,$(EXT_TABLES), extensions/ebtable_$(T).o)
diff --git a/extensions/ebt_string.c b/extensions/ebt_string.c
new file mode 100644
index 0000000..793f5df
--- /dev/null
+++ b/extensions/ebt_string.c
@@ -0,0 +1,319 @@
+/* ebt_string
+ *
+ * Author:
+ * Bernie Harris <[email protected]>
+ *
+ * February, 2018
+ *
+ * Based on:
+ *  libxt_string.c, Copyright (C) 2000 Emmanuel Roger  <[email protected]>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <ctype.h>
+#include "../include/ebtables_u.h"
+#include <linux/if_packet.h>
+#include <linux/netfilter/xt_string.h>
+
+#define STRING_FROM  '1'
+#define STRING_TO    '2'
+#define STRING_ALGO  '3'
+#define STRING_ICASE '4'
+#define STRING       '5'
+#define STRING_HEX   '6'
+#define OPT_STRING_FROM  (1 << 0)
+#define OPT_STRING_TO    (1 << 1)
+#define OPT_STRING_ALGO  (1 << 2)
+#define OPT_STRING_ICASE (1 << 3)
+#define OPT_STRING       (1 << 4)
+#define OPT_STRING_HEX   (1 << 5)
+
+static const struct option opts[] =
+{
+       { "string-from"             , required_argument, 0, STRING_FROM },
+       { "string-to"               , required_argument, 0, STRING_TO },
+       { "string-algo"             , required_argument, 0, STRING_ALGO },
+       { "string-icase"            , no_argument,       0, STRING_ICASE },
+       { "string"                  , required_argument, 0, STRING },
+       { "string-hex"              , required_argument, 0, STRING_HEX },
+       { 0 }
+};
+
+static void print_help()
+{
+       printf(
+"string options:\n"
+"--string-from offset    : Offset to start searching from (default: 0)\n"
+"--string-to   offset    : Offset to stop searching (default: packet size)\n"
+"--string-algo algorithm : Algorithm (bm = Boyer-Moore, kmp = 
Knuth-Pratt-Morris)\n"
+"--string-icase          : Ignore case when searching\n"
+"--string     [!] string : Match a string in a packet\n"
+"--string-hex [!] string : Match a hex string in a packet, e.g. |0D 0A|, 
|0D0A|, netfilter|03|org\n");
+}
+
+static void init(struct ebt_entry_match *match)
+{
+       struct xt_string_info *info = (struct xt_string_info *)match->data;
+
+       info->to_offset = UINT16_MAX;
+}
+
+static void parse_string(const char *s, struct xt_string_info *info)
+{
+       /* xt_string does not need \0 at the end of the pattern */
+       if (strlen(s) <= XT_STRING_MAX_PATTERN_SIZE) {
+               strncpy(info->pattern, s, XT_STRING_MAX_PATTERN_SIZE);
+               info->patlen = strnlen(s, XT_STRING_MAX_PATTERN_SIZE);
+               return;
+       }
+       ebt_print_error2("STRING too long \"%s\"", s);
+}
+
+static void parse_hex_string(const char *s, struct xt_string_info *info)
+{
+       int i=0, slen, sindex=0, schar;
+       short hex_f = 0, literal_f = 0;
+       char hextmp[3];
+
+       slen = strlen(s);
+
+       if (slen == 0) {
+               ebt_print_error2("STRING must contain at least one char");
+       }
+
+       while (i < slen) {
+               if (s[i] == '\\' && !hex_f) {
+                       literal_f = 1;
+               } else if (s[i] == '\\') {
+                       ebt_print_error2("Cannot include literals in hex data");
+               } else if (s[i] == '|') {
+                       if (hex_f)
+                               hex_f = 0;
+                       else {
+                               hex_f = 1;
+                               /* get past any initial whitespace just after 
the '|' */
+                               while (s[i+1] == ' ')
+                                       i++;
+                       }
+                       if (i+1 >= slen)
+                               break;
+                       else
+                               i++;  /* advance to the next character */
+               }
+
+               if (literal_f) {
+                       if (i+1 >= slen) {
+                               ebt_print_error2("Bad literal placement at end 
of string");
+                       }
+                       info->pattern[sindex] = s[i+1];
+                       i += 2;  /* skip over literal char */
+                       literal_f = 0;
+               } else if (hex_f) {
+                       if (i+1 >= slen) {
+                               ebt_print_error2("Odd number of hex digits");
+                       }
+                       if (i+2 >= slen) {
+                               /* must end with a "|" */
+                               ebt_print_error2("Invalid hex block");
+                       }
+                       if (! isxdigit(s[i])) /* check for valid hex char */
+                               ebt_print_error2("Invalid hex char '%c'", s[i]);
+                       if (! isxdigit(s[i+1])) /* check for valid hex char */
+                               ebt_print_error2("Invalid hex char '%c'", 
s[i+1]);
+                       hextmp[0] = s[i];
+                       hextmp[1] = s[i+1];
+                       hextmp[2] = '\0';
+                       if (! sscanf(hextmp, "%x", &schar))
+                               ebt_print_error2("Invalid hex char `%c'", s[i]);
+                       info->pattern[sindex] = (char) schar;
+                       if (s[i+2] == ' ')
+                               i += 3;  /* spaces included in the hex block */
+                       else
+                               i += 2;
+               } else {  /* the char is not part of hex data, so just copy */
+                       info->pattern[sindex] = s[i];
+                       i++;
+               }
+               if (sindex > XT_STRING_MAX_PATTERN_SIZE)
+                       ebt_print_error2("STRING too long \"%s\"", s);
+               sindex++;
+       }
+       info->patlen = sindex;
+}
+
+static int parse(int c, char **argv, int argc, const struct ebt_u_entry *entry,
+                unsigned int *flags, struct ebt_entry_match **match)
+{
+       struct xt_string_info *info = (struct xt_string_info *)(*match)->data;
+       int i;
+       int input_string_length = 0;
+       char buf[3] = { 0 };
+
+       switch (c) {
+       case STRING_FROM:
+               ebt_check_option2(flags, OPT_STRING_FROM);
+               if (ebt_check_inverse2(optarg))
+                       ebt_print_error2("Unexpected `!' after --string-from");
+               info->from_offset = (__u16)strtoul(optarg, NULL, 10);
+               break;
+       case STRING_TO:
+               ebt_check_option2(flags, OPT_STRING_TO);
+               if (ebt_check_inverse2(optarg))
+                       ebt_print_error2("Unexpected `!' after --string-to");
+               info->to_offset = (__u16)strtoul(optarg, NULL, 10);
+               break;
+       case STRING_ALGO:
+               ebt_check_option2(flags, OPT_STRING_ALGO);
+               if (ebt_check_inverse2(optarg))
+                       ebt_print_error2("Unexpected `!' after --string-algo");
+               strncpy(info->algo, optarg, XT_STRING_MAX_ALGO_NAME_SIZE);
+               break;
+       case STRING_ICASE:
+               ebt_check_option2(flags, OPT_STRING_ICASE);
+               if (ebt_check_inverse2(optarg))
+                       ebt_print_error2("Unexpected `!' after --string-icase");
+               info->u.v1.flags |= XT_STRING_FLAG_IGNORECASE;
+               break;
+       case STRING:
+               ebt_check_option2(flags, OPT_STRING);
+               parse_string(optarg, info);
+               if (ebt_check_inverse2(optarg)) {
+                       info->u.v1.flags |= XT_STRING_FLAG_INVERT;
+               }
+               break;
+       case STRING_HEX:
+               ebt_check_option2(flags, OPT_STRING_HEX);
+               parse_hex_string(optarg, info);
+               if (ebt_check_inverse2(optarg)) {
+                       info->u.v1.flags |= XT_STRING_FLAG_INVERT;
+               }
+               break;
+       default:
+               return 0;
+       }
+       return 1;
+}
+
+static void final_check(const struct ebt_u_entry *entry,
+                       const struct ebt_entry_match *match, const char *name,
+                       unsigned int hookmask, unsigned int time)
+{
+       struct xt_string_info *info = (struct xt_string_info *)match->data;
+
+       if (info->to_offset < info->from_offset) {
+               ebt_print_error2("'to' offset should not be less than 'from' "
+                                "offset");
+       }
+}
+
+/* Test to see if the string contains non-printable chars or quotes */
+static unsigned short int is_hex_string(const char *str,
+                                       const unsigned short int len)
+{
+       unsigned int i;
+       for (i=0; i < len; i++) {
+               if (! isprint(str[i])) {
+                       /* string contains at least one non-printable char */
+                       return 1;
+               }
+       }
+       /* use hex output if the last char is a "\" */
+       if (str[len-1] == '\\')
+               return 1;
+       return 0;
+}
+
+/* Print string with "|" chars included as one would pass to --string-hex */
+static void print_hex_string(const char *str, const unsigned short int len)
+{
+       unsigned int i;
+       /* start hex block */
+       printf("\"|");
+       for (i=0; i < len; i++)
+               printf("%02x", (unsigned char)str[i]);
+       /* close hex block */
+       printf("|\" ");
+}
+
+static void print_string(const char *str, const unsigned short int len)
+{
+       unsigned int i;
+       printf("\"");
+       for (i=0; i < len; i++) {
+               if (str[i] == '\"' || str[i] == '\\')
+                       putchar('\\');
+               printf("%c", (unsigned char) str[i]);
+       }
+       printf("\" ");  /* closing quote */
+}
+
+static void print(const struct ebt_u_entry *entry,
+                 const struct ebt_entry_match *match)
+{
+       const struct xt_string_info *info =
+               (const struct xt_string_info *) match->data;
+       int invert = info->u.v1.flags & XT_STRING_FLAG_INVERT;
+
+       if (is_hex_string(info->pattern, info->patlen)) {
+               printf("--string-hex %s", invert ? "! " : "");
+               print_hex_string(info->pattern, info->patlen);
+       } else {
+               printf("--string %s", invert ? "! " : "");
+               print_string(info->pattern, info->patlen);
+       }
+       printf("--string-algo %s ", info->algo);
+       if (info->from_offset != 0)
+               printf("--string-from %u ", info->from_offset);
+       if (info->to_offset != 0)
+               printf("--string-to %u ", info->to_offset);
+       if (info->u.v1.flags & XT_STRING_FLAG_IGNORECASE)
+               printf("--string-icase ");
+}
+
+static int compare(const struct ebt_entry_match *m1,
+                  const struct ebt_entry_match *m2)
+{
+       const struct xt_string_info *info1 =
+               (const struct xt_string_info *) m1->data;
+       const struct xt_string_info *info2 =
+               (const struct xt_string_info *) m2->data;
+
+       if (info1->from_offset != info2->from_offset)
+               return 0;
+       if (info1->to_offset != info2->to_offset)
+               return 0;
+       if (info1->u.v1.flags != info2->u.v1.flags)
+               return 0;
+       if (info1->patlen != info2->patlen)
+               return 0;
+       if (strncmp (info1->algo, info2->algo, XT_STRING_MAX_ALGO_NAME_SIZE) != 
0)
+               return 0;
+       if (strncmp (info1->pattern, info2->pattern, info1->patlen) != 0)
+               return 0;
+
+       return 1;
+}
+
+static struct ebt_u_match string_match =
+{
+       .name           = "string",
+       .revision       = 1,
+       .size           = sizeof(struct xt_string_info),
+       .help           = print_help,
+       .init           = init,
+       .parse          = parse,
+       .final_check    = final_check,
+       .print          = print,
+       .compare        = compare,
+       .extra_ops      = opts,
+};
+
+void _init(void)
+{
+       ebt_register_match(&string_match);
+}
diff --git a/include/ebtables.h b/include/ebtables.h
index 8f520c6..9bbedbb 100644
--- a/include/ebtables.h
+++ b/include/ebtables.h
@@ -20,6 +20,7 @@
 #define EBT_TABLE_MAXNAMELEN 32
 #define EBT_CHAIN_MAXNAMELEN EBT_TABLE_MAXNAMELEN
 #define EBT_FUNCTION_MAXNAMELEN EBT_TABLE_MAXNAMELEN
+#define EBT_EXTENSION_MAXNAMELEN 31
 
 /* verdicts >0 are "branches" */
 #define EBT_ACCEPT   -1
@@ -113,7 +114,10 @@ struct ebt_entries {
 struct ebt_entry_match
 {
        union {
-               char name[EBT_FUNCTION_MAXNAMELEN];
+               struct {
+                       char name[EBT_EXTENSION_MAXNAMELEN];
+                       uint8_t revision;
+               };
                struct ebt_match *match;
        } u;
        /* size of data */
@@ -127,7 +131,10 @@ struct ebt_entry_match
 struct ebt_entry_watcher
 {
        union {
-               char name[EBT_FUNCTION_MAXNAMELEN];
+               struct {
+                       char name[EBT_EXTENSION_MAXNAMELEN];
+                       uint8_t revision;
+               };
                struct ebt_watcher *watcher;
        } u;
        /* size of data */
@@ -141,7 +148,10 @@ struct ebt_entry_watcher
 struct ebt_entry_target
 {
        union {
-               char name[EBT_FUNCTION_MAXNAMELEN];
+               struct {
+                       char name[EBT_EXTENSION_MAXNAMELEN];
+                       uint8_t revision;
+               };
                struct ebt_target *target;
        } u;
        /* size of data */
diff --git a/include/ebtables_u.h b/include/ebtables_u.h
index 35a5bcc..c6ca6f1 100644
--- a/include/ebtables_u.h
+++ b/include/ebtables_u.h
@@ -144,6 +144,7 @@ struct ebt_u_entry
 struct ebt_u_match
 {
        char name[EBT_FUNCTION_MAXNAMELEN];
+       uint8_t revision;
        /* size of the real match data */
        unsigned int size;
        void (*help)(void);
diff --git a/libebtc.c b/libebtc.c
index d474248..92fd764 100644
--- a/libebtc.c
+++ b/libebtc.c
@@ -272,6 +272,7 @@ void ebt_reinit_extensions()
                        if (!m->m)
                                ebt_print_memory();
                        strcpy(m->m->u.name, m->name);
+                       m->m->u.revision = m->revision;
                        m->m->match_size = EBT_ALIGN(m->size);
                        m->used = 0;
                }
@@ -550,8 +551,10 @@ int ebt_check_rule_exists(struct ebt_u_replace *replace,
                while (m_l) {
                        m = (struct ebt_u_match *)(m_l->m);
                        m_l2 = u_e->m_list;
-                       while (m_l2 && strcmp(m_l2->m->u.name, m->m->u.name))
+                       while (m_l2 && (strcmp(m_l2->m->u.name, m->m->u.name) ||
+                              m_l2->m->u.revision != m->m->u.revision)) {
                                m_l2 = m_l2->next;
+                       }
                        if (!m_l2 || !m->compare(m->m, m_l2->m))
                                goto letscontinue;
                        j++;
@@ -1209,6 +1212,7 @@ void ebt_register_match(struct ebt_u_match *m)
        if (!m->m)
                ebt_print_memory();
        strcpy(m->m->u.name, m->name);
+       m->m->u.revision = m->revision;
        m->m->match_size = EBT_ALIGN(m->size);
        m->init(m->m);
 
-- 
2.16.2

--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to