1/* Shared library add-on to iptables to add string matching support.
2 *
3 * Copyright (C) 2000 Emmanuel Roger  <winfield@freegates.be>
4 *
5 * 2005-08-05 Pablo Neira Ayuso <pablo@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@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@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
31enum {
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
43static 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
56static 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
72static 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
79static void
80parse_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
91static void
92parse_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
169static 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
204static 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 */
213static unsigned short int
214is_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 */
227static void
228print_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
239static void
240print_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
252static void
253string_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
277static 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
302static 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
335void _init(void)
336{
337	xtables_register_matches(string_mt_reg, ARRAY_SIZE(string_mt_reg));
338}
339