1/*
2   Shared library add-on to iptables to add layer 7 matching support.
3
4   By Matthew Strait <quadong@users.sf.net>, Oct 2003.
5
6   http://l7-filter.sf.net
7
8   This program is free software; you can redistribute it and/or
9   modify it under the terms of the GNU General Public License
10   as published by the Free Software Foundation; either version
11   2 of the License, or (at your option) any later version.
12   http://www.gnu.org/licenses/gpl.txt
13
14   Based on libipt_string.c (C) 2000 Emmanuel Roger <winfield@freegates.be>
15*/
16
17#define _GNU_SOURCE
18#include <stdio.h>
19#include <netdb.h>
20#include <string.h>
21#include <stdlib.h>
22#include <getopt.h>
23#include <ctype.h>
24#include <dirent.h>
25
26#include "iptables.h"
27#include <xtables.h>
28#include <linux/netfilter/x_tables.h>
29#include <linux/netfilter/xt_layer7.h>
30
31#define MAX_FN_LEN 256
32
33static char l7dir[MAX_FN_LEN] = "\0";
34
35/* Function which prints out usage message. */
36static void layer7_help(void)
37{
38	printf(
39	"LAYER7 match v%s options:\n"
40	"--l7dir <directory>  : Look for patterns here instead of /etc_ro/l7-protocols/\n"
41	"                       (--l7dir must be specified before --l7proto if used!)\n"
42	"--l7proto [!] <name> : Match the protocol defined in /etc_ro/l7-protocols/name.pat\n",
43	XTABLES_VERSION);
44}
45
46static struct option layer7_opts[] = {
47	{.name = "l7proto", .has_arg = true, .val = '1' },
48	{.name = "l7dir", .has_arg = true, .val = '2' },
49	XT_GETOPT_TABLEEND,
50};
51
52/* reads filename, puts protocol info into layer7_protocol_info, number of protocols to numprotos */
53static int parse_protocol_file(char * filename, const char * protoname, struct xt_layer7_info *info)
54{
55	FILE * f;
56	char * linebuf = NULL;
57	size_t len = 0;
58
59	enum { protocol, pattern, done } datatype = protocol;
60
61	f = fopen(filename, "r");
62
63	if(!f)
64		return 0;
65
66	while(getline(&linebuf, &len, f) != -1)
67	{
68		if(strlen(linebuf) < 2 || linebuf[0] == '#')
69			continue;
70
71		/* strip the pesky newline... */
72		if(linebuf[strlen(linebuf) - 1] == '\n')
73			linebuf[strlen(linebuf) - 1] = '\0';
74
75		if(datatype == protocol)
76		{
77			/* Ignore everything on the linebuf beginning with the
78			first space or tab . For instance, this allows the
79			protocol linebuf in http.pat to be "http " (or
80			"http I am so cool") instead of just "http". */
81			if(strchr(linebuf, ' ')){
82				char * space = strchr(linebuf, ' ');
83				space[0] = '\0';
84			}
85			if(strchr(linebuf, '\t')){
86				char * space = strchr(linebuf, '\t');
87				space[0] = '\0';
88			}
89
90			/* sanity check.  First non-comment non-blank
91			linebuf must be the same as the file name. */
92			if(strcmp(linebuf, protoname))
93				xtables_error(OTHER_PROBLEM,
94					"Protocol name (%s) doesn't match file name (%s).  Bailing out\n",
95					linebuf, filename);
96
97			if(strlen(linebuf) >= MAX_PROTOCOL_LEN)
98				 xtables_error(PARAMETER_PROBLEM,
99					"Protocol name in %s too long!", filename);
100			strncpy(info->protocol, linebuf, MAX_PROTOCOL_LEN);
101
102			datatype = pattern;
103		}
104		else if(datatype == pattern)
105		{
106			if(strlen(linebuf) >= MAX_PATTERN_LEN)
107				 xtables_error(PARAMETER_PROBLEM, "Pattern in %s too long!", filename);
108			strncpy(info->pattern, linebuf, MAX_PATTERN_LEN);
109
110			datatype = done;
111			break;
112		}
113		else
114			xtables_error(OTHER_PROBLEM, "Internal error");
115	}
116
117	if(datatype != done)
118		xtables_error(OTHER_PROBLEM, "Failed to get all needed data from %s", filename);
119
120	if(linebuf) free(linebuf);
121	fclose(f);
122
123	return 1;
124
125/*
126	fprintf(stderr, "protocol: %s\npattern: %s\n\n",
127			info->protocol,
128			info->pattern);
129*/
130}
131
132static int hex2dec(char c)
133{
134        switch (c)
135        {
136                case '0' ... '9':
137                        return c - '0';
138                case 'a' ... 'f':
139                        return c - 'a' + 10;
140                case 'A' ... 'F':
141                        return c - 'A' + 10;
142                default:
143                        xtables_error(OTHER_PROBLEM, "hex2dec: bad value!\n");
144                        return 0;
145        }
146}
147
148/* takes a string with \xHH escapes and returns one with the characters
149they stand for */
150static char * pre_process(char * s)
151{
152	char * result = malloc(strlen(s) + 1);
153	int sindex = 0, rindex_1 = 0;
154        while( sindex < strlen(s) )
155        {
156            if( sindex + 3 < strlen(s) &&
157                s[sindex] == '\\' && s[sindex+1] == 'x' &&
158                isxdigit(s[sindex + 2]) && isxdigit(s[sindex + 3]) )
159                {
160                        /* carefully remember to call tolower here... */
161                        result[rindex_1] = tolower( hex2dec(s[sindex + 2])*16 +
162                                                  hex2dec(s[sindex + 3] ) );
163
164			switch ( result[rindex_1] )
165			{
166			case 0x24:
167			case 0x28:
168			case 0x29:
169			case 0x2a:
170			case 0x2b:
171			case 0x2e:
172			case 0x3f:
173			case 0x5b:
174			case 0x5c:
175			case 0x5d:
176			case 0x5e:
177			case 0x7c:
178				fprintf(stderr,
179					"Warning: layer7 regexp contains a control character, %c, in hex (\\x%c%c).\n"
180					"I recommend that you write this as %c or \\%c, depending on what you meant.\n",
181					result[rindex_1], s[sindex + 2], s[sindex + 3], result[rindex_1], result[rindex_1]);
182				break;
183			case 0x00:
184				fprintf(stderr,
185					"Warning: null (\\x00) in layer7 regexp.  A null terminates the regexp string!\n");
186				break;
187			default:
188				break;
189			}
190
191
192                        sindex += 3; /* 4 total */
193                }
194                else
195                        result[rindex_1] = tolower(s[sindex]);
196
197		sindex++;
198		rindex_1++;
199        }
200	result[rindex_1] = '\0';
201
202	return result;
203}
204
205#define MAX_SUBDIRS 128
206static char ** readl7dir(char * dirname)
207{
208        DIR             * scratchdir;
209        struct dirent   ** namelist;
210	char ** subdirs = malloc(MAX_SUBDIRS * sizeof(char *));
211
212        int n, d = 1;
213	subdirs[0] = "";
214
215        n = scandir(dirname, &namelist, 0, alphasort);
216
217	if (n < 0)
218	{
219            perror("scandir");
220	    xtables_error(OTHER_PROBLEM, "Couldn't open %s\n", dirname);
221	}
222        else
223	{
224		while(n--)
225		{
226			char fulldirname[MAX_FN_LEN];
227
228			snprintf(fulldirname, MAX_FN_LEN, "%s/%s", dirname, namelist[n]->d_name);
229
230                	if((scratchdir = opendir(fulldirname)) != NULL)
231			{
232				closedir(scratchdir);
233
234				if(!strcmp(namelist[n]->d_name, ".") ||
235				   !strcmp(namelist[n]->d_name, ".."))
236					/* do nothing */ ;
237				else
238				{
239					subdirs[d] = malloc(strlen(namelist[n]->d_name) + 1);
240					strcpy(subdirs[d], namelist[n]->d_name);
241					d++;
242					if(d >= MAX_SUBDIRS - 1)
243					{
244						fprintf(stderr,
245						  "Too many subdirectories, skipping the rest!\n");
246						break;
247					}
248				}
249			}
250                	free(namelist[n]);
251            	}
252            	free(namelist);
253        }
254
255	subdirs[d] = NULL;
256
257	return subdirs;
258}
259
260static void
261parse_layer7_protocol(const char *s, struct xt_layer7_info *info)
262{
263	char filename[MAX_FN_LEN];
264	char * dir = NULL;
265	char ** subdirs;
266	int n = 0, done = 0;
267
268	if(strlen(l7dir) > 0)
269		dir = l7dir;
270	else
271		dir = "/etc_ro/l7-protocols";
272
273	subdirs = readl7dir(dir);
274
275	while(subdirs[n] != NULL)
276	{
277		int c = snprintf(filename, MAX_FN_LEN, "%s/%s/%s.pat", dir, subdirs[n], s);
278
279		//fprintf(stderr, "Trying to find pattern in %s ... ", filename);
280
281		if(c > MAX_FN_LEN)
282		{
283			xtables_error(OTHER_PROBLEM,
284				"Filename beginning with %s is too long!\n", filename);
285		}
286
287		/* read in the pattern from the file */
288		if(parse_protocol_file(filename, s, info))
289		{
290			//fprintf(stderr, "found\n");
291			done = 1;
292			break;
293		}
294
295		//fprintf(stderr, "not found\n");
296
297		n++;
298	}
299
300	if(!done)
301		xtables_error(OTHER_PROBLEM,
302			"Couldn't find a pattern definition file for %s.\n", s);
303
304	/* process \xHH escapes and tolower everything. (our regex lib has no
305	case insensitivity option.) */
306	strncpy(info->pattern, pre_process(info->pattern), MAX_PATTERN_LEN);
307}
308
309/* Function which parses command options; returns true if it ate an option */
310static int layer7_parse(int c, char **argv, int invert, unsigned int *flags,
311      const void *entry, struct xt_entry_match **match)
312{
313	struct xt_layer7_info *layer7info =
314		(struct xt_layer7_info *)(*match)->data;
315
316	switch (c) {
317	case '1':
318		parse_layer7_protocol(argv[optind-1], layer7info);
319		if (invert)
320			layer7info->invert = 1;
321		*flags = 1;
322		break;
323
324	case '2':
325		/* not going to use this, but maybe we need to strip a ! anyway (?) */
326		if(strlen(argv[optind-1]) >= MAX_FN_LEN)
327			xtables_error(PARAMETER_PROBLEM, "directory name too long\n");
328
329		strncpy(l7dir, argv[optind-1], MAX_FN_LEN);
330
331		*flags = 1;
332		break;
333
334	default:
335		return 0;
336	}
337
338	return 1;
339}
340
341/* Final check; must have specified --l7proto */
342static void layer7_final_check(unsigned int flags)
343{
344	if (!flags)
345		xtables_error(PARAMETER_PROBLEM,
346			   "LAYER7 match: You must specify `--l7proto'");
347}
348
349static void print_protocol(char s[], int invert, int numeric)
350{
351	fputs("l7proto ", stdout);
352	if (invert) fputc('!', stdout);
353	printf("%s ", s);
354}
355
356/* Prints out the matchinfo. */
357static void layer7_print(const void *ip,
358      const struct xt_entry_match *match,
359      int numeric)
360{
361	printf("LAYER7 ");
362
363	print_protocol(((struct xt_layer7_info *)match->data)->protocol,
364		  ((struct xt_layer7_info *)match->data)->invert, numeric);
365}
366/* Saves the union ipt_matchinfo in parsable form to stdout. */
367static void layer7_save(const void *ip, const struct xt_entry_match *match)
368{
369        const struct xt_layer7_info *info =
370            (const struct xt_layer7_info*) match->data;
371
372        printf("--l7proto %s%s ", (info->invert)   ? "! ": "", info->protocol);
373}
374
375static struct xtables_match layer7_match = {
376    .family        = NFPROTO_IPV4,
377    .name          = "layer7",
378    .version       = XTABLES_VERSION,
379    .size          = XT_ALIGN(sizeof(struct xt_layer7_info)),
380    .userspacesize = XT_ALIGN(sizeof(struct xt_layer7_info)),
381    .help          = layer7_help,
382    .parse         = layer7_parse,
383    .final_check   = layer7_final_check,
384    .print         = layer7_print,
385    .save          = layer7_save,
386    .extra_opts    = layer7_opts
387};
388
389void _init(void)
390{
391	xtables_register_match(&layer7_match);
392}
393