blob: 97fbe19eca54fb603ef9d1552705f462e49baec3 [file] [log] [blame]
/* ebt_string
*
* Author:
* Bernie Harris <bernie.harris@alliedtelesis.co.nz>
*
* February, 2018
*
* Based on:
* libxt_string.c, Copyright (C) 2000 Emmanuel Roger <winfield@freegates.be>
*/
#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_error3("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_error3("STRING must contain at least one char");
}
while (i < slen) {
if (s[i] == '\\' && !hex_f) {
literal_f = 1;
} else if (s[i] == '\\') {
ebt_print_error3("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_error3("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_error3("Odd number of hex digits");
}
if (i+2 >= slen) {
/* must end with a "|" */
ebt_print_error3("Invalid hex block");
}
if (! isxdigit(s[i])) /* check for valid hex char */
ebt_print_error3("Invalid hex char '%c'", s[i]);
if (! isxdigit(s[i+1])) /* check for valid hex char */
ebt_print_error3("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_error3("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_error3("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;
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");
if (snprintf(info->algo, sizeof(info->algo), "%s", optarg) >=
sizeof(info->algo))
ebt_print_error2("\"%s\" is truncated", info->algo);
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_error3("'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,
};
static void _INIT(void)
{
ebt_register_match(&string_match);
}