1/* Shared library add-on to iptables to add policy support. */
2#include <stdio.h>
3#include <netdb.h>
4#include <string.h>
5#include <stdlib.h>
6#include <syslog.h>
7#include <getopt.h>
8#include <netdb.h>
9#include <errno.h>
10#include <sys/socket.h>
11#include <netinet/in.h>
12#include <arpa/inet.h>
13#include <iptables.h>
14
15#include <linux/netfilter_ipv4/ip_tables.h>
16#include "../include/linux/netfilter_ipv4/ipt_policy.h"
17
18/*
19 * HACK: global pointer to current matchinfo for making
20 * final checks and adjustments in final_check.
21 */
22static struct ipt_policy_info *policy_info;
23
24static void help(void)
25{
26	printf(
27"policy v%s options:\n"
28"  --dir in|out			match policy applied during decapsulation/\n"
29"				policy to be applied during encapsulation\n"
30"  --pol none|ipsec		match policy\n"
31"  --strict 			match entire policy instead of single element\n"
32"				at any position\n"
33"[!] --reqid reqid		match reqid\n"
34"[!] --spi spi			match SPI\n"
35"[!] --proto proto		match protocol (ah/esp/ipcomp)\n"
36"[!] --mode mode 		match mode (transport/tunnel)\n"
37"[!] --tunnel-src addr/mask	match tunnel source\n"
38"[!] --tunnel-dst addr/mask	match tunnel destination\n"
39"  --next 			begin next element in policy\n",
40	IPTABLES_VERSION);
41}
42
43static struct option opts[] =
44{
45	{
46		.name		= "dir",
47		.has_arg	= 1,
48		.val		= '1',
49	},
50	{
51		.name		= "pol",
52		.has_arg	= 1,
53		.val		= '2',
54	},
55	{
56		.name		= "strict",
57		.val		= '3'
58	},
59	{
60		.name		= "reqid",
61		.has_arg	= 1,
62		.val		= '4',
63	},
64	{
65		.name		= "spi",
66		.has_arg	= 1,
67		.val		= '5'
68	},
69	{
70		.name		= "tunnel-src",
71		.has_arg	= 1,
72		.val		= '6'
73	},
74	{
75		.name		= "tunnel-dst",
76		.has_arg	= 1,
77		.val		= '7'
78	},
79	{
80		.name		= "proto",
81		.has_arg	= 1,
82		.val		= '8'
83	},
84	{
85		.name		= "mode",
86		.has_arg	= 1,
87		.val		= '9'
88	},
89	{
90		.name		= "next",
91		.val		= 'a'
92	},
93	{ }
94};
95
96static void init(struct ipt_entry_match *m, unsigned int *nfcache)
97{
98	*nfcache |= NFC_UNKNOWN;
99}
100
101static int parse_direction(char *s)
102{
103	if (strcmp(s, "in") == 0)
104		return IPT_POLICY_MATCH_IN;
105	if (strcmp(s, "out") == 0)
106		return IPT_POLICY_MATCH_OUT;
107	exit_error(PARAMETER_PROBLEM, "policy_match: invalid dir `%s'", s);
108}
109
110static int parse_policy(char *s)
111{
112	if (strcmp(s, "none") == 0)
113		return IPT_POLICY_MATCH_NONE;
114	if (strcmp(s, "ipsec") == 0)
115		return 0;
116	exit_error(PARAMETER_PROBLEM, "policy match: invalid policy `%s'", s);
117}
118
119static int parse_mode(char *s)
120{
121	if (strcmp(s, "transport") == 0)
122		return IPT_POLICY_MODE_TRANSPORT;
123	if (strcmp(s, "tunnel") == 0)
124		return IPT_POLICY_MODE_TUNNEL;
125	exit_error(PARAMETER_PROBLEM, "policy match: invalid mode `%s'", s);
126}
127
128static int parse(int c, char **argv, int invert, unsigned int *flags,
129                 const struct ipt_entry *entry,
130                 unsigned int *nfcache,
131                 struct ipt_entry_match **match)
132{
133	struct ipt_policy_info *info = (void *)(*match)->data;
134	struct ipt_policy_elem *e = &info->pol[info->len];
135	struct in_addr *addr = NULL, mask;
136	unsigned int naddr = 0;
137	int mode;
138
139	check_inverse(optarg, &invert, &optind, 0);
140
141	switch (c) {
142	case '1':
143		if (info->flags & (IPT_POLICY_MATCH_IN|IPT_POLICY_MATCH_OUT))
144			exit_error(PARAMETER_PROBLEM,
145			           "policy match: double --dir option");
146		if (invert)
147			exit_error(PARAMETER_PROBLEM,
148			           "policy match: can't invert --dir option");
149
150		info->flags |= parse_direction(argv[optind-1]);
151		break;
152	case '2':
153		if (invert)
154			exit_error(PARAMETER_PROBLEM,
155			           "policy match: can't invert --policy option");
156
157		info->flags |= parse_policy(argv[optind-1]);
158		break;
159	case '3':
160		if (info->flags & IPT_POLICY_MATCH_STRICT)
161			exit_error(PARAMETER_PROBLEM,
162			           "policy match: double --strict option");
163
164		if (invert)
165			exit_error(PARAMETER_PROBLEM,
166			           "policy match: can't invert --strict option");
167
168		info->flags |= IPT_POLICY_MATCH_STRICT;
169		break;
170	case '4':
171		if (e->match.reqid)
172			exit_error(PARAMETER_PROBLEM,
173			           "policy match: double --reqid option");
174
175		e->match.reqid = 1;
176		e->invert.reqid = invert;
177		e->reqid = strtol(argv[optind-1], NULL, 10);
178		break;
179	case '5':
180		if (e->match.spi)
181			exit_error(PARAMETER_PROBLEM,
182			           "policy match: double --spi option");
183
184		e->match.spi = 1;
185		e->invert.spi = invert;
186		e->spi = strtol(argv[optind-1], NULL, 0x10);
187		break;
188	case '6':
189		if (e->match.saddr)
190			exit_error(PARAMETER_PROBLEM,
191			           "policy match: double --tunnel-src option");
192
193		parse_hostnetworkmask(argv[optind-1], &addr, &mask, &naddr);
194		if (naddr > 1)
195			exit_error(PARAMETER_PROBLEM,
196			           "policy match: name resolves to multiple IPs");
197
198		e->match.saddr = 1;
199		e->invert.saddr = invert;
200		e->saddr.a4 = addr[0];
201		e->smask.a4 = mask;
202                break;
203	case '7':
204		if (e->match.daddr)
205			exit_error(PARAMETER_PROBLEM,
206			           "policy match: double --tunnel-dst option");
207
208		parse_hostnetworkmask(argv[optind-1], &addr, &mask, &naddr);
209		if (naddr > 1)
210			exit_error(PARAMETER_PROBLEM,
211			           "policy match: name resolves to multiple IPs");
212
213		e->match.daddr = 1;
214		e->invert.daddr = invert;
215		e->daddr.a4 = addr[0];
216		e->dmask.a4 = mask;
217		break;
218	case '8':
219		if (e->match.proto)
220			exit_error(PARAMETER_PROBLEM,
221			           "policy match: double --proto option");
222
223		e->proto = parse_protocol(argv[optind-1]);
224		if (e->proto != IPPROTO_AH && e->proto != IPPROTO_ESP &&
225		    e->proto != IPPROTO_COMP)
226			exit_error(PARAMETER_PROBLEM,
227			           "policy match: protocol must ah/esp/ipcomp");
228		e->match.proto = 1;
229		e->invert.proto = invert;
230		break;
231	case '9':
232		if (e->match.mode)
233			exit_error(PARAMETER_PROBLEM,
234			           "policy match: double --mode option");
235
236		mode = parse_mode(argv[optind-1]);
237		e->match.mode = 1;
238		e->invert.mode = invert;
239		e->mode = mode;
240		break;
241	case 'a':
242		if (invert)
243			exit_error(PARAMETER_PROBLEM,
244			           "policy match: can't invert --next option");
245
246		if (++info->len == IPT_POLICY_MAX_ELEM)
247			exit_error(PARAMETER_PROBLEM,
248			           "policy match: maximum policy depth reached");
249		break;
250	default:
251		return 0;
252	}
253
254	policy_info = info;
255	return 1;
256}
257
258static void final_check(unsigned int flags)
259{
260	struct ipt_policy_info *info = policy_info;
261	struct ipt_policy_elem *e;
262	int i;
263
264	if (info == NULL)
265		exit_error(PARAMETER_PROBLEM,
266		           "policy match: no parameters given");
267
268	if (!(info->flags & (IPT_POLICY_MATCH_IN|IPT_POLICY_MATCH_OUT)))
269		exit_error(PARAMETER_PROBLEM,
270		           "policy match: neither --in nor --out specified");
271
272	if (info->flags & IPT_POLICY_MATCH_NONE) {
273		if (info->flags & IPT_POLICY_MATCH_STRICT)
274			exit_error(PARAMETER_PROBLEM,
275			           "policy match: policy none but --strict given");
276
277		if (info->len != 0)
278			exit_error(PARAMETER_PROBLEM,
279			           "policy match: policy none but policy given");
280	} else
281		info->len++;	/* increase len by 1, no --next after last element */
282
283	if (!(info->flags & IPT_POLICY_MATCH_STRICT) && info->len > 1)
284		exit_error(PARAMETER_PROBLEM,
285		           "policy match: multiple elements but no --strict");
286
287	for (i = 0; i < info->len; i++) {
288		e = &info->pol[i];
289
290		if (info->flags & IPT_POLICY_MATCH_STRICT &&
291		    !(e->match.reqid || e->match.spi || e->match.saddr ||
292		      e->match.daddr || e->match.proto || e->match.mode))
293			exit_error(PARAMETER_PROBLEM,
294			           "policy match: empty policy element");
295
296		if ((e->match.saddr || e->match.daddr)
297		    && ((e->mode == IPT_POLICY_MODE_TUNNEL && e->invert.mode) ||
298		        (e->mode == IPT_POLICY_MODE_TRANSPORT && !e->invert.mode)))
299			exit_error(PARAMETER_PROBLEM,
300			           "policy match: --tunnel-src/--tunnel-dst "
301			           "is only valid in tunnel mode");
302	}
303}
304
305static void print_mode(char *prefix, u_int8_t mode, int numeric)
306{
307	printf("%smode ", prefix);
308
309	switch (mode) {
310	case IPT_POLICY_MODE_TRANSPORT:
311		printf("transport ");
312		break;
313	case IPT_POLICY_MODE_TUNNEL:
314		printf("tunnel ");
315		break;
316	default:
317		printf("??? ");
318		break;
319	}
320}
321
322static void print_proto(char *prefix, u_int8_t proto, int numeric)
323{
324	struct protoent *p = NULL;
325
326	printf("%sproto ", prefix);
327	if (!numeric)
328		p = getprotobynumber(proto);
329	if (p != NULL)
330		printf("%s ", p->p_name);
331	else
332		printf("%u ", proto);
333}
334
335#define PRINT_INVERT(x)		\
336do {				\
337	if (x)			\
338		printf("! ");	\
339} while(0)
340
341static void print_entry(char *prefix, const struct ipt_policy_elem *e,
342                        int numeric)
343{
344	if (e->match.reqid) {
345		PRINT_INVERT(e->invert.reqid);
346		printf("%sreqid %u ", prefix, e->reqid);
347	}
348	if (e->match.spi) {
349		PRINT_INVERT(e->invert.spi);
350		printf("%sspi 0x%x ", prefix, e->spi);
351	}
352	if (e->match.proto) {
353		PRINT_INVERT(e->invert.proto);
354		print_proto(prefix, e->proto, numeric);
355	}
356	if (e->match.mode) {
357		PRINT_INVERT(e->invert.mode);
358		print_mode(prefix, e->mode, numeric);
359	}
360	if (e->match.daddr) {
361		PRINT_INVERT(e->invert.daddr);
362		printf("%stunnel-dst %s%s ", prefix,
363		       addr_to_dotted((struct in_addr *)&e->daddr),
364		       mask_to_dotted((struct in_addr *)&e->dmask));
365	}
366	if (e->match.saddr) {
367		PRINT_INVERT(e->invert.saddr);
368		printf("%stunnel-src %s%s ", prefix,
369		       addr_to_dotted((struct in_addr *)&e->saddr),
370		       mask_to_dotted((struct in_addr *)&e->smask));
371	}
372}
373
374static void print_flags(char *prefix, const struct ipt_policy_info *info)
375{
376	if (info->flags & IPT_POLICY_MATCH_IN)
377		printf("%sdir in ", prefix);
378	else
379		printf("%sdir out ", prefix);
380
381	if (info->flags & IPT_POLICY_MATCH_NONE)
382		printf("%spol none ", prefix);
383	else
384		printf("%spol ipsec ", prefix);
385
386	if (info->flags & IPT_POLICY_MATCH_STRICT)
387		printf("%sstrict ", prefix);
388}
389
390static void print(const struct ipt_ip *ip,
391                  const struct ipt_entry_match *match,
392		  int numeric)
393{
394	const struct ipt_policy_info *info = (void *)match->data;
395	unsigned int i;
396
397	printf("policy match ");
398	print_flags("", info);
399	for (i = 0; i < info->len; i++) {
400		if (info->len > 1)
401			printf("[%u] ", i);
402		print_entry("", &info->pol[i], numeric);
403	}
404}
405
406static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
407{
408	const struct ipt_policy_info *info = (void *)match->data;
409	unsigned int i;
410
411	print_flags("--", info);
412	for (i = 0; i < info->len; i++) {
413		print_entry("--", &info->pol[i], 0);
414		if (i + 1 < info->len)
415			printf("--next ");
416	}
417}
418
419struct iptables_match policy = {
420	.name		= "policy",
421	.version	= IPTABLES_VERSION,
422	.size		= IPT_ALIGN(sizeof(struct ipt_policy_info)),
423	.userspacesize	= IPT_ALIGN(sizeof(struct ipt_policy_info)),
424	.help		= help,
425	.init		= init,
426	.parse		= parse,
427	.final_check	= final_check,
428	.print		= print,
429	.save		= save,
430	.extra_opts	= opts
431};
432
433void _init(void)
434{
435	register_match(&policy);
436}
437