libxt_string.c revision e88a7c2c7175742b58b6aa03f2b5aba2d80330a1
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 <stdbool.h>
25#include <stdio.h>
26#include <netdb.h>
27#include <string.h>
28#include <stdlib.h>
29#include <getopt.h>
30#include <ctype.h>
31#include <xtables.h>
32#include <stddef.h>
33#include <linux/netfilter/xt_string.h>
34
35static void string_help(void)
36{
37	printf(
38"string match options:\n"
39"--from                       Offset to start searching from\n"
40"--to                         Offset to stop searching\n"
41"--algo                       Algorithm\n"
42"--icase                      Ignore case (default: 0)\n"
43"[!] --string string          Match a string in a packet\n"
44"[!] --hex-string string      Match a hex string in a packet\n");
45}
46
47static const struct option string_opts[] = {
48	{.name = "from",       .has_arg = true,  .val = '1'},
49	{.name = "to",         .has_arg = true,  .val = '2'},
50	{.name = "algo",       .has_arg = true,  .val = '3'},
51	{.name = "string",     .has_arg = true,  .val = '4'},
52	{.name = "hex-string", .has_arg = true,  .val = '5'},
53	{.name = "icase",      .has_arg = false, .val = '6'},
54	XT_GETOPT_TABLEEND,
55};
56
57static void string_init(struct xt_entry_match *m)
58{
59	struct xt_string_info *i = (struct xt_string_info *) m->data;
60
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, argv);
206		parse_string(optarg, 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, argv);
222		parse_hex_string(optarg, 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	return 1;
242}
243
244static void string_check(unsigned int flags)
245{
246	if (!(flags & STRING))
247		xtables_error(PARAMETER_PROBLEM,
248			   "STRING match: You must specify `--string' or "
249			   "`--hex-string'");
250	if (!(flags & ALGO))
251		xtables_error(PARAMETER_PROBLEM,
252			   "STRING match: You must specify `--algo'");
253}
254
255/* Test to see if the string contains non-printable chars or quotes */
256static unsigned short int
257is_hex_string(const char *str, const unsigned short int len)
258{
259	unsigned int i;
260	for (i=0; i < len; i++)
261		if (! isprint(str[i]))
262			return 1;  /* string contains at least one non-printable char */
263	/* use hex output if the last char is a "\" */
264	if ((unsigned char) str[len-1] == 0x5c)
265		return 1;
266	return 0;
267}
268
269/* Print string with "|" chars included as one would pass to --hex-string */
270static void
271print_hex_string(const char *str, const unsigned short int len)
272{
273	unsigned int i;
274	/* start hex block */
275	printf("\"|");
276	for (i=0; i < len; i++) {
277		/* see if we need to prepend a zero */
278		if ((unsigned char) str[i] <= 0x0F)
279			printf("0%x", (unsigned char) str[i]);
280		else
281			printf("%x", (unsigned char) str[i]);
282	}
283	/* close hex block */
284	printf("|\" ");
285}
286
287static void
288print_string(const char *str, const unsigned short int len)
289{
290	unsigned int i;
291	printf(" \"");
292	for (i=0; i < len; i++) {
293		if ((unsigned char) str[i] == 0x22)  /* escape any embedded quotes */
294			printf("%c", 0x5c);
295		printf("%c", (unsigned char) str[i]);
296	}
297	printf("\"");  /* closing quote */
298}
299
300static void
301string_print(const void *ip, const struct xt_entry_match *match, int numeric)
302{
303	const struct xt_string_info *info =
304	    (const struct xt_string_info*) match->data;
305	const int revision = match->u.user.revision;
306	int invert = (revision == 0 ? info->u.v0.invert :
307				    info->u.v1.flags & XT_STRING_FLAG_INVERT);
308
309	if (is_hex_string(info->pattern, info->patlen)) {
310		printf(" STRING match %s", invert ? "!" : "");
311		print_hex_string(info->pattern, info->patlen);
312	} else {
313		printf(" STRING match %s", invert ? "!" : "");
314		print_string(info->pattern, info->patlen);
315	}
316	printf(" ALGO name %s", info->algo);
317	if (info->from_offset != 0)
318		printf(" FROM %u", info->from_offset);
319	if (info->to_offset != 0)
320		printf(" TO %u", info->to_offset);
321	if (revision > 0 && info->u.v1.flags & XT_STRING_FLAG_IGNORECASE)
322		printf(" ICASE");
323}
324
325static void string_save(const void *ip, const struct xt_entry_match *match)
326{
327	const struct xt_string_info *info =
328	    (const struct xt_string_info*) match->data;
329	const int revision = match->u.user.revision;
330	int invert = (revision == 0 ? info->u.v0.invert :
331				    info->u.v1.flags & XT_STRING_FLAG_INVERT);
332
333	if (is_hex_string(info->pattern, info->patlen)) {
334		printf("%s --hex-string", (invert) ? " !" : "");
335		print_hex_string(info->pattern, info->patlen);
336	} else {
337		printf("%s --string", (invert) ? " !": "");
338		print_string(info->pattern, info->patlen);
339	}
340	printf(" --algo %s", info->algo);
341	if (info->from_offset != 0)
342		printf(" --from %u", info->from_offset);
343	if (info->to_offset != 0)
344		printf(" --to %u", info->to_offset);
345	if (revision > 0 && info->u.v1.flags & XT_STRING_FLAG_IGNORECASE)
346		printf(" --icase");
347}
348
349
350static struct xtables_match string_mt_reg[] = {
351	{
352		.name          = "string",
353		.revision      = 0,
354		.family        = NFPROTO_UNSPEC,
355		.version       = XTABLES_VERSION,
356		.size          = XT_ALIGN(sizeof(struct xt_string_info)),
357		.userspacesize = offsetof(struct xt_string_info, config),
358		.help          = string_help,
359		.init          = string_init,
360		.parse         = string_parse,
361		.final_check   = string_check,
362		.print         = string_print,
363		.save          = string_save,
364		.extra_opts    = string_opts,
365	},
366	{
367		.name          = "string",
368		.revision      = 1,
369		.family        = NFPROTO_UNSPEC,
370		.version       = XTABLES_VERSION,
371		.size          = XT_ALIGN(sizeof(struct xt_string_info)),
372		.userspacesize = offsetof(struct xt_string_info, config),
373		.help          = string_help,
374		.init          = string_init,
375		.parse         = string_parse,
376		.final_check   = string_check,
377		.print         = string_print,
378		.save          = string_save,
379		.extra_opts    = string_opts,
380	},
381};
382
383void _init(void)
384{
385	xtables_register_matches(string_mt_reg, ARRAY_SIZE(string_mt_reg));
386}
387