Home | History | Annotate | Download | only in extensions
      1 /* Shared library add-on to iptables to add string matching support.
      2  *
      3  * Copyright (C) 2000 Emmanuel Roger  <winfield (at) freegates.be>
      4  *
      5  * 2005-08-05 Pablo Neira Ayuso <pablo (at) eurodev.net>
      6  * 	- reimplemented to use new string matching iptables match
      7  * 	- add functionality to match packets by using window offsets
      8  * 	- add functionality to select the string matching algorithm
      9  *
     10  * ChangeLog
     11  *     29.12.2003: Michael Rash <mbr (at) cipherdyne.org>
     12  *             Fixed iptables save/restore for ascii strings
     13  *             that contain space chars, and hex strings that
     14  *             contain embedded NULL chars.  Updated to print
     15  *             strings in hex mode if any non-printable char
     16  *             is contained within the string.
     17  *
     18  *     27.01.2001: Gianni Tedesco <gianni (at) ecsc.co.uk>
     19  *             Changed --tos to --string in save(). Also
     20  *             updated to work with slightly modified
     21  *             ipt_string_info.
     22  */
     23 #define _GNU_SOURCE 1 /* strnlen for older glibcs */
     24 #include <stdio.h>
     25 #include <string.h>
     26 #include <stdlib.h>
     27 #include <ctype.h>
     28 #include <xtables.h>
     29 #include <linux/netfilter/xt_string.h>
     30 
     31 enum {
     32 	O_FROM = 0,
     33 	O_TO,
     34 	O_ALGO,
     35 	O_ICASE,
     36 	O_STRING,
     37 	O_HEX_STRING,
     38 	F_STRING     = 1 << O_STRING,
     39 	F_HEX_STRING = 1 << O_HEX_STRING,
     40 	F_OP_ANY     = F_STRING | F_HEX_STRING,
     41 };
     42 
     43 static void string_help(void)
     44 {
     45 	printf(
     46 "string match options:\n"
     47 "--from                       Offset to start searching from\n"
     48 "--to                         Offset to stop searching\n"
     49 "--algo                       Algorithm\n"
     50 "--icase                      Ignore case (default: 0)\n"
     51 "[!] --string string          Match a string in a packet\n"
     52 "[!] --hex-string string      Match a hex string in a packet\n");
     53 }
     54 
     55 #define s struct xt_string_info
     56 static const struct xt_option_entry string_opts[] = {
     57 	{.name = "from", .id = O_FROM, .type = XTTYPE_UINT16,
     58 	 .flags = XTOPT_PUT, XTOPT_POINTER(s, from_offset)},
     59 	{.name = "to", .id = O_TO, .type = XTTYPE_UINT16,
     60 	 .flags = XTOPT_PUT, XTOPT_POINTER(s, to_offset)},
     61 	{.name = "algo", .id = O_ALGO, .type = XTTYPE_STRING,
     62 	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, algo)},
     63 	{.name = "string", .id = O_STRING, .type = XTTYPE_STRING,
     64 	 .flags = XTOPT_INVERT, .excl = F_HEX_STRING},
     65 	{.name = "hex-string", .id = O_HEX_STRING, .type = XTTYPE_STRING,
     66 	 .flags = XTOPT_INVERT, .excl = F_STRING},
     67 	{.name = "icase", .id = O_ICASE, .type = XTTYPE_NONE},
     68 	XTOPT_TABLEEND,
     69 };
     70 #undef s
     71 
     72 static void string_init(struct xt_entry_match *m)
     73 {
     74 	struct xt_string_info *i = (struct xt_string_info *) m->data;
     75 
     76 	i->to_offset = UINT16_MAX;
     77 }
     78 
     79 static void
     80 parse_string(const char *s, struct xt_string_info *info)
     81 {
     82 	/* xt_string does not need \0 at the end of the pattern */
     83 	if (strlen(s) <= XT_STRING_MAX_PATTERN_SIZE) {
     84 		strncpy(info->pattern, s, XT_STRING_MAX_PATTERN_SIZE);
     85 		info->patlen = strnlen(s, XT_STRING_MAX_PATTERN_SIZE);
     86 		return;
     87 	}
     88 	xtables_error(PARAMETER_PROBLEM, "STRING too long \"%s\"", s);
     89 }
     90 
     91 static void
     92 parse_hex_string(const char *s, struct xt_string_info *info)
     93 {
     94 	int i=0, slen, sindex=0, schar;
     95 	short hex_f = 0, literal_f = 0;
     96 	char hextmp[3];
     97 
     98 	slen = strlen(s);
     99 
    100 	if (slen == 0) {
    101 		xtables_error(PARAMETER_PROBLEM,
    102 			"STRING must contain at least one char");
    103 	}
    104 
    105 	while (i < slen) {
    106 		if (s[i] == '\\' && !hex_f) {
    107 			literal_f = 1;
    108 		} else if (s[i] == '\\') {
    109 			xtables_error(PARAMETER_PROBLEM,
    110 				"Cannot include literals in hex data");
    111 		} else if (s[i] == '|') {
    112 			if (hex_f)
    113 				hex_f = 0;
    114 			else {
    115 				hex_f = 1;
    116 				/* get past any initial whitespace just after the '|' */
    117 				while (s[i+1] == ' ')
    118 					i++;
    119 			}
    120 			if (i+1 >= slen)
    121 				break;
    122 			else
    123 				i++;  /* advance to the next character */
    124 		}
    125 
    126 		if (literal_f) {
    127 			if (i+1 >= slen) {
    128 				xtables_error(PARAMETER_PROBLEM,
    129 					"Bad literal placement at end of string");
    130 			}
    131 			info->pattern[sindex] = s[i+1];
    132 			i += 2;  /* skip over literal char */
    133 			literal_f = 0;
    134 		} else if (hex_f) {
    135 			if (i+1 >= slen) {
    136 				xtables_error(PARAMETER_PROBLEM,
    137 					"Odd number of hex digits");
    138 			}
    139 			if (i+2 >= slen) {
    140 				/* must end with a "|" */
    141 				xtables_error(PARAMETER_PROBLEM, "Invalid hex block");
    142 			}
    143 			if (! isxdigit(s[i])) /* check for valid hex char */
    144 				xtables_error(PARAMETER_PROBLEM, "Invalid hex char '%c'", s[i]);
    145 			if (! isxdigit(s[i+1])) /* check for valid hex char */
    146 				xtables_error(PARAMETER_PROBLEM, "Invalid hex char '%c'", s[i+1]);
    147 			hextmp[0] = s[i];
    148 			hextmp[1] = s[i+1];
    149 			hextmp[2] = '\0';
    150 			if (! sscanf(hextmp, "%x", &schar))
    151 				xtables_error(PARAMETER_PROBLEM,
    152 					"Invalid hex char `%c'", s[i]);
    153 			info->pattern[sindex] = (char) schar;
    154 			if (s[i+2] == ' ')
    155 				i += 3;  /* spaces included in the hex block */
    156 			else
    157 				i += 2;
    158 		} else {  /* the char is not part of hex data, so just copy */
    159 			info->pattern[sindex] = s[i];
    160 			i++;
    161 		}
    162 		if (sindex > XT_STRING_MAX_PATTERN_SIZE)
    163 			xtables_error(PARAMETER_PROBLEM, "STRING too long \"%s\"", s);
    164 		sindex++;
    165 	}
    166 	info->patlen = sindex;
    167 }
    168 
    169 static void string_parse(struct xt_option_call *cb)
    170 {
    171 	struct xt_string_info *stringinfo = cb->data;
    172 	const unsigned int revision = (*cb->match)->u.user.revision;
    173 
    174 	xtables_option_parse(cb);
    175 	switch (cb->entry->id) {
    176 	case O_STRING:
    177 		parse_string(cb->arg, stringinfo);
    178 		if (cb->invert) {
    179 			if (revision == 0)
    180 				stringinfo->u.v0.invert = 1;
    181 			else
    182 				stringinfo->u.v1.flags |= XT_STRING_FLAG_INVERT;
    183 		}
    184 		break;
    185 	case O_HEX_STRING:
    186 		parse_hex_string(cb->arg, stringinfo);  /* sets length */
    187 		if (cb->invert) {
    188 			if (revision == 0)
    189 				stringinfo->u.v0.invert = 1;
    190 			else
    191 				stringinfo->u.v1.flags |= XT_STRING_FLAG_INVERT;
    192 		}
    193 		break;
    194 	case O_ICASE:
    195 		if (revision == 0)
    196 			xtables_error(VERSION_PROBLEM,
    197 				   "Kernel doesn't support --icase");
    198 
    199 		stringinfo->u.v1.flags |= XT_STRING_FLAG_IGNORECASE;
    200 		break;
    201 	}
    202 }
    203 
    204 static void string_check(struct xt_fcheck_call *cb)
    205 {
    206 	if (!(cb->xflags & F_OP_ANY))
    207 		xtables_error(PARAMETER_PROBLEM,
    208 			   "STRING match: You must specify `--string' or "
    209 			   "`--hex-string'");
    210 }
    211 
    212 /* Test to see if the string contains non-printable chars or quotes */
    213 static unsigned short int
    214 is_hex_string(const char *str, const unsigned short int len)
    215 {
    216 	unsigned int i;
    217 	for (i=0; i < len; i++)
    218 		if (! isprint(str[i]))
    219 			return 1;  /* string contains at least one non-printable char */
    220 	/* use hex output if the last char is a "\" */
    221 	if (str[len-1] == '\\')
    222 		return 1;
    223 	return 0;
    224 }
    225 
    226 /* Print string with "|" chars included as one would pass to --hex-string */
    227 static void
    228 print_hex_string(const char *str, const unsigned short int len)
    229 {
    230 	unsigned int i;
    231 	/* start hex block */
    232 	printf(" \"|");
    233 	for (i=0; i < len; i++)
    234 		printf("%02x", (unsigned char)str[i]);
    235 	/* close hex block */
    236 	printf("|\"");
    237 }
    238 
    239 static void
    240 print_string(const char *str, const unsigned short int len)
    241 {
    242 	unsigned int i;
    243 	printf(" \"");
    244 	for (i=0; i < len; i++) {
    245 		if (str[i] == '\"' || str[i] == '\\')
    246 			putchar('\\');
    247 		printf("%c", (unsigned char) str[i]);
    248 	}
    249 	printf("\"");  /* closing quote */
    250 }
    251 
    252 static void
    253 string_print(const void *ip, const struct xt_entry_match *match, int numeric)
    254 {
    255 	const struct xt_string_info *info =
    256 	    (const struct xt_string_info*) match->data;
    257 	const int revision = match->u.user.revision;
    258 	int invert = (revision == 0 ? info->u.v0.invert :
    259 				    info->u.v1.flags & XT_STRING_FLAG_INVERT);
    260 
    261 	if (is_hex_string(info->pattern, info->patlen)) {
    262 		printf(" STRING match %s", invert ? "!" : "");
    263 		print_hex_string(info->pattern, info->patlen);
    264 	} else {
    265 		printf(" STRING match %s", invert ? "!" : "");
    266 		print_string(info->pattern, info->patlen);
    267 	}
    268 	printf(" ALGO name %s", info->algo);
    269 	if (info->from_offset != 0)
    270 		printf(" FROM %u", info->from_offset);
    271 	if (info->to_offset != 0)
    272 		printf(" TO %u", info->to_offset);
    273 	if (revision > 0 && info->u.v1.flags & XT_STRING_FLAG_IGNORECASE)
    274 		printf(" ICASE");
    275 }
    276 
    277 static void string_save(const void *ip, const struct xt_entry_match *match)
    278 {
    279 	const struct xt_string_info *info =
    280 	    (const struct xt_string_info*) match->data;
    281 	const int revision = match->u.user.revision;
    282 	int invert = (revision == 0 ? info->u.v0.invert :
    283 				    info->u.v1.flags & XT_STRING_FLAG_INVERT);
    284 
    285 	if (is_hex_string(info->pattern, info->patlen)) {
    286 		printf("%s --hex-string", (invert) ? " !" : "");
    287 		print_hex_string(info->pattern, info->patlen);
    288 	} else {
    289 		printf("%s --string", (invert) ? " !": "");
    290 		print_string(info->pattern, info->patlen);
    291 	}
    292 	printf(" --algo %s", info->algo);
    293 	if (info->from_offset != 0)
    294 		printf(" --from %u", info->from_offset);
    295 	if (info->to_offset != 0)
    296 		printf(" --to %u", info->to_offset);
    297 	if (revision > 0 && info->u.v1.flags & XT_STRING_FLAG_IGNORECASE)
    298 		printf(" --icase");
    299 }
    300 
    301 
    302 static struct xtables_match string_mt_reg[] = {
    303 	{
    304 		.name          = "string",
    305 		.revision      = 0,
    306 		.family        = NFPROTO_UNSPEC,
    307 		.version       = XTABLES_VERSION,
    308 		.size          = XT_ALIGN(sizeof(struct xt_string_info)),
    309 		.userspacesize = offsetof(struct xt_string_info, config),
    310 		.help          = string_help,
    311 		.init          = string_init,
    312 		.print         = string_print,
    313 		.save          = string_save,
    314 		.x6_parse      = string_parse,
    315 		.x6_fcheck     = string_check,
    316 		.x6_options    = string_opts,
    317 	},
    318 	{
    319 		.name          = "string",
    320 		.revision      = 1,
    321 		.family        = NFPROTO_UNSPEC,
    322 		.version       = XTABLES_VERSION,
    323 		.size          = XT_ALIGN(sizeof(struct xt_string_info)),
    324 		.userspacesize = offsetof(struct xt_string_info, config),
    325 		.help          = string_help,
    326 		.init          = string_init,
    327 		.print         = string_print,
    328 		.save          = string_save,
    329 		.x6_parse      = string_parse,
    330 		.x6_fcheck     = string_check,
    331 		.x6_options    = string_opts,
    332 	},
    333 };
    334 
    335 void _init(void)
    336 {
    337 	xtables_register_matches(string_mt_reg, ARRAY_SIZE(string_mt_reg));
    338 }
    339