libxt_string.c revision f2a77520693f0a6dd1df1f87be4b81913961c1f5
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
24#include <stdio.h>
25#include <netdb.h>
26#include <string.h>
27#include <stdlib.h>
28#include <getopt.h>
29#include <ctype.h>
30#include <xtables.h>
31#include <stddef.h>
32#include <linux/netfilter/xt_string.h>
33
34static void string_help(void)
35{
36	printf(
37"string match options:\n"
38"--from                       Offset to start searching from\n"
39"--to                         Offset to stop searching\n"
40"--algo                       Algorithm\n"
41"--icase                      Ignore case (default: 0)\n"
42"[!] --string string          Match a string in a packet\n"
43"[!] --hex-string string      Match a hex string in a packet\n");
44}
45
46static const struct option string_opts[] = {
47	{ "from", 1, NULL, '1' },
48	{ "to", 1, NULL, '2' },
49	{ "algo", 1, NULL, '3' },
50	{ "string", 1, NULL, '4' },
51	{ "hex-string", 1, NULL, '5' },
52	{ "icase", 0, NULL, '6' },
53	{ .name = NULL }
54};
55
56static void string_init(struct xt_entry_match *m)
57{
58	struct xt_string_info *i = (struct xt_string_info *) m->data;
59
60	if (i->to_offset == 0)
61		i->to_offset = UINT16_MAX;
62}
63
64static void
65parse_string(const char *s, struct xt_string_info *info)
66{
67	/* xt_string does not need \0 at the end of the pattern */
68	if (strlen(s) <= XT_STRING_MAX_PATTERN_SIZE) {
69		strncpy(info->pattern, s, XT_STRING_MAX_PATTERN_SIZE);
70		info->patlen = strnlen(s, XT_STRING_MAX_PATTERN_SIZE);
71		return;
72	}
73	xtables_error(PARAMETER_PROBLEM, "STRING too long \"%s\"", s);
74}
75
76static void
77parse_algo(const char *s, struct xt_string_info *info)
78{
79	/* xt_string needs \0 for algo name */
80	if (strlen(s) < XT_STRING_MAX_ALGO_NAME_SIZE) {
81		strncpy(info->algo, s, XT_STRING_MAX_ALGO_NAME_SIZE);
82		return;
83	}
84	xtables_error(PARAMETER_PROBLEM, "ALGO too long \"%s\"", s);
85}
86
87static void
88parse_hex_string(const char *s, struct xt_string_info *info)
89{
90	int i=0, slen, sindex=0, schar;
91	short hex_f = 0, literal_f = 0;
92	char hextmp[3];
93
94	slen = strlen(s);
95
96	if (slen == 0) {
97		xtables_error(PARAMETER_PROBLEM,
98			"STRING must contain at least one char");
99	}
100
101	while (i < slen) {
102		if (s[i] == '\\' && !hex_f) {
103			literal_f = 1;
104		} else if (s[i] == '\\') {
105			xtables_error(PARAMETER_PROBLEM,
106				"Cannot include literals in hex data");
107		} else if (s[i] == '|') {
108			if (hex_f)
109				hex_f = 0;
110			else {
111				hex_f = 1;
112				/* get past any initial whitespace just after the '|' */
113				while (s[i+1] == ' ')
114					i++;
115			}
116			if (i+1 >= slen)
117				break;
118			else
119				i++;  /* advance to the next character */
120		}
121
122		if (literal_f) {
123			if (i+1 >= slen) {
124				xtables_error(PARAMETER_PROBLEM,
125					"Bad literal placement at end of string");
126			}
127			info->pattern[sindex] = s[i+1];
128			i += 2;  /* skip over literal char */
129			literal_f = 0;
130		} else if (hex_f) {
131			if (i+1 >= slen) {
132				xtables_error(PARAMETER_PROBLEM,
133					"Odd number of hex digits");
134			}
135			if (i+2 >= slen) {
136				/* must end with a "|" */
137				xtables_error(PARAMETER_PROBLEM, "Invalid hex block");
138			}
139			if (! isxdigit(s[i])) /* check for valid hex char */
140				xtables_error(PARAMETER_PROBLEM, "Invalid hex char '%c'", s[i]);
141			if (! isxdigit(s[i+1])) /* check for valid hex char */
142				xtables_error(PARAMETER_PROBLEM, "Invalid hex char '%c'", s[i+1]);
143			hextmp[0] = s[i];
144			hextmp[1] = s[i+1];
145			hextmp[2] = '\0';
146			if (! sscanf(hextmp, "%x", &schar))
147				xtables_error(PARAMETER_PROBLEM,
148					"Invalid hex char `%c'", s[i]);
149			info->pattern[sindex] = (char) schar;
150			if (s[i+2] == ' ')
151				i += 3;  /* spaces included in the hex block */
152			else
153				i += 2;
154		} else {  /* the char is not part of hex data, so just copy */
155			info->pattern[sindex] = s[i];
156			i++;
157		}
158		if (sindex > XT_STRING_MAX_PATTERN_SIZE)
159			xtables_error(PARAMETER_PROBLEM, "STRING too long \"%s\"", s);
160		sindex++;
161	}
162	info->patlen = sindex;
163}
164
165#define STRING 0x1
166#define ALGO   0x2
167#define FROM   0x4
168#define TO     0x8
169#define ICASE  0x10
170
171static int
172string_parse(int c, char **argv, int invert, unsigned int *flags,
173             const void *entry, struct xt_entry_match **match)
174{
175	struct xt_string_info *stringinfo =
176	    (struct xt_string_info *)(*match)->data;
177	const int revision = (*match)->u.user.revision;
178
179	switch (c) {
180	case '1':
181		if (*flags & FROM)
182			xtables_error(PARAMETER_PROBLEM,
183				   "Can't specify multiple --from");
184		stringinfo->from_offset = atoi(optarg);
185		*flags |= FROM;
186		break;
187	case '2':
188		if (*flags & TO)
189			xtables_error(PARAMETER_PROBLEM,
190				   "Can't specify multiple --to");
191		stringinfo->to_offset = atoi(optarg);
192		*flags |= TO;
193		break;
194	case '3':
195		if (*flags & ALGO)
196			xtables_error(PARAMETER_PROBLEM,
197				   "Can't specify multiple --algo");
198		parse_algo(optarg, stringinfo);
199		*flags |= ALGO;
200		break;
201	case '4':
202		if (*flags & STRING)
203			xtables_error(PARAMETER_PROBLEM,
204				   "Can't specify multiple --string");
205		xtables_check_inverse(optarg, &invert, &optind, 0);
206		parse_string(argv[optind-1], stringinfo);
207		if (invert) {
208			if (revision == 0)
209				stringinfo->u.v0.invert = 1;
210			else
211				stringinfo->u.v1.flags |= XT_STRING_FLAG_INVERT;
212		}
213		*flags |= STRING;
214		break;
215
216	case '5':
217		if (*flags & STRING)
218			xtables_error(PARAMETER_PROBLEM,
219				   "Can't specify multiple --hex-string");
220
221		xtables_check_inverse(optarg, &invert, &optind, 0);
222		parse_hex_string(argv[optind-1], stringinfo);  /* sets length */
223		if (invert) {
224			if (revision == 0)
225				stringinfo->u.v0.invert = 1;
226			else
227				stringinfo->u.v1.flags |= XT_STRING_FLAG_INVERT;
228		}
229		*flags |= STRING;
230		break;
231
232	case '6':
233		if (revision == 0)
234			xtables_error(VERSION_PROBLEM,
235				   "Kernel doesn't support --icase");
236
237		stringinfo->u.v1.flags |= XT_STRING_FLAG_IGNORECASE;
238		*flags |= ICASE;
239		break;
240
241	default:
242		return 0;
243	}
244	return 1;
245}
246
247static void string_check(unsigned int flags)
248{
249	if (!(flags & STRING))
250		xtables_error(PARAMETER_PROBLEM,
251			   "STRING match: You must specify `--string' or "
252			   "`--hex-string'");
253	if (!(flags & ALGO))
254		xtables_error(PARAMETER_PROBLEM,
255			   "STRING match: You must specify `--algo'");
256}
257
258/* Test to see if the string contains non-printable chars or quotes */
259static unsigned short int
260is_hex_string(const char *str, const unsigned short int len)
261{
262	unsigned int i;
263	for (i=0; i < len; i++)
264		if (! isprint(str[i]))
265			return 1;  /* string contains at least one non-printable char */
266	/* use hex output if the last char is a "\" */
267	if ((unsigned char) str[len-1] == 0x5c)
268		return 1;
269	return 0;
270}
271
272/* Print string with "|" chars included as one would pass to --hex-string */
273static void
274print_hex_string(const char *str, const unsigned short int len)
275{
276	unsigned int i;
277	/* start hex block */
278	printf("\"|");
279	for (i=0; i < len; i++) {
280		/* see if we need to prepend a zero */
281		if ((unsigned char) str[i] <= 0x0F)
282			printf("0%x", (unsigned char) str[i]);
283		else
284			printf("%x", (unsigned char) str[i]);
285	}
286	/* close hex block */
287	printf("|\" ");
288}
289
290static void
291print_string(const char *str, const unsigned short int len)
292{
293	unsigned int i;
294	printf("\"");
295	for (i=0; i < len; i++) {
296		if ((unsigned char) str[i] == 0x22)  /* escape any embedded quotes */
297			printf("%c", 0x5c);
298		printf("%c", (unsigned char) str[i]);
299	}
300	printf("\" ");  /* closing space and quote */
301}
302
303static void
304string_print(const void *ip, const struct xt_entry_match *match, int numeric)
305{
306	const struct xt_string_info *info =
307	    (const struct xt_string_info*) match->data;
308	const int revision = match->u.user.revision;
309	int invert = (revision == 0 ? info->u.v0.invert :
310				    info->u.v1.flags & XT_STRING_FLAG_INVERT);
311
312	if (is_hex_string(info->pattern, info->patlen)) {
313		printf("STRING match %s", invert ? "!" : "");
314		print_hex_string(info->pattern, info->patlen);
315	} else {
316		printf("STRING match %s", invert ? "!" : "");
317		print_string(info->pattern, info->patlen);
318	}
319	printf("ALGO name %s ", info->algo);
320	if (info->from_offset != 0)
321		printf("FROM %u ", info->from_offset);
322	if (info->to_offset != 0)
323		printf("TO %u ", info->to_offset);
324	if (revision > 0 && info->u.v1.flags & XT_STRING_FLAG_IGNORECASE)
325		printf("ICASE ");
326}
327
328static void string_save(const void *ip, const struct xt_entry_match *match)
329{
330	const struct xt_string_info *info =
331	    (const struct xt_string_info*) match->data;
332	const int revision = match->u.user.revision;
333	int invert = (revision == 0 ? info->u.v0.invert :
334				    info->u.v1.flags & XT_STRING_FLAG_INVERT);
335
336	if (is_hex_string(info->pattern, info->patlen)) {
337		printf("%s--hex-string ", (invert) ? "! ": "");
338		print_hex_string(info->pattern, info->patlen);
339	} else {
340		printf("%s--string ", (invert) ? "! ": "");
341		print_string(info->pattern, info->patlen);
342	}
343	printf("--algo %s ", info->algo);
344	if (info->from_offset != 0)
345		printf("--from %u ", info->from_offset);
346	if (info->to_offset != 0)
347		printf("--to %u ", info->to_offset);
348	if (revision > 0 && info->u.v1.flags & XT_STRING_FLAG_IGNORECASE)
349		printf("--icase ");
350}
351
352
353static struct xtables_match string_mt_reg[] = {
354	{
355		.name          = "string",
356		.revision      = 0,
357		.family        = NFPROTO_UNSPEC,
358		.version       = XTABLES_VERSION,
359		.size          = XT_ALIGN(sizeof(struct xt_string_info)),
360		.userspacesize = offsetof(struct xt_string_info, config),
361		.help          = string_help,
362		.init          = string_init,
363		.parse         = string_parse,
364		.final_check   = string_check,
365		.print         = string_print,
366		.save          = string_save,
367		.extra_opts    = string_opts,
368	},
369	{
370		.name          = "string",
371		.revision      = 1,
372		.family        = NFPROTO_UNSPEC,
373		.version       = XTABLES_VERSION,
374		.size          = XT_ALIGN(sizeof(struct xt_string_info)),
375		.userspacesize = offsetof(struct xt_string_info, config),
376		.help          = string_help,
377		.init          = string_init,
378		.parse         = string_parse,
379		.final_check   = string_check,
380		.print         = string_print,
381		.save          = string_save,
382		.extra_opts    = string_opts,
383	},
384};
385
386void _init(void)
387{
388	xtables_register_matches(string_mt_reg, ARRAY_SIZE(string_mt_reg));
389}
390