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