libxt_policy.c revision 7ac405297ec38449b30e3b05fd6bf2082fd3d803
1/* Shared library add-on to iptables to add policy support. */
2#include <stdbool.h>
3#include <stdio.h>
4#include <netdb.h>
5#include <string.h>
6#include <stdlib.h>
7#include <syslog.h>
8#include <getopt.h>
9#include <netdb.h>
10#include <errno.h>
11#include <sys/socket.h>
12#include <netinet/in.h>
13#include <arpa/inet.h>
14#include <xtables.h>
15
16#include <linux/netfilter/xt_policy.h>
17
18/*
19 * HACK: global pointer to current matchinfo for making
20 * final checks and adjustments in final_check.
21 */
22static struct xt_policy_info *policy_info;
23
24static void policy_help(void)
25{
26	printf(
27"policy match 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}
41
42static const struct option policy_opts[] =
43{
44	{
45		.name		= "dir",
46		.has_arg	= true,
47		.val		= '1',
48	},
49	{
50		.name		= "pol",
51		.has_arg	= true,
52		.val		= '2',
53	},
54	{
55		.name		= "strict",
56		.has_arg	= false,
57		.val		= '3'
58	},
59	{
60		.name		= "reqid",
61		.has_arg	= true,
62		.val		= '4',
63	},
64	{
65		.name		= "spi",
66		.has_arg	= true,
67		.val		= '5'
68	},
69	{
70		.name		= "tunnel-src",
71		.has_arg	= true,
72		.val		= '6'
73	},
74	{
75		.name		= "tunnel-dst",
76		.has_arg	= true,
77		.val		= '7'
78	},
79	{
80		.name		= "proto",
81		.has_arg	= true,
82		.val		= '8'
83	},
84	{
85		.name		= "mode",
86		.has_arg	= true,
87		.val		= '9'
88	},
89	{
90		.name		= "next",
91		.has_arg	= false,
92		.val		= 'a'
93	},
94	XT_GETOPT_TABLEEND,
95};
96
97static int parse_direction(char *s)
98{
99	if (strcmp(s, "in") == 0)
100		return XT_POLICY_MATCH_IN;
101	if (strcmp(s, "out") == 0)
102		return XT_POLICY_MATCH_OUT;
103	xtables_error(PARAMETER_PROBLEM, "policy_match: invalid dir \"%s\"", s);
104}
105
106static int parse_policy(char *s)
107{
108	if (strcmp(s, "none") == 0)
109		return XT_POLICY_MATCH_NONE;
110	if (strcmp(s, "ipsec") == 0)
111		return 0;
112	xtables_error(PARAMETER_PROBLEM, "policy match: invalid policy \"%s\"", s);
113}
114
115static int parse_mode(char *s)
116{
117	if (strcmp(s, "transport") == 0)
118		return XT_POLICY_MODE_TRANSPORT;
119	if (strcmp(s, "tunnel") == 0)
120		return XT_POLICY_MODE_TUNNEL;
121	xtables_error(PARAMETER_PROBLEM, "policy match: invalid mode \"%s\"", s);
122}
123
124static int policy_parse(int c, char **argv, int invert, unsigned int *flags,
125                        struct xt_policy_info *info, uint8_t family)
126{
127	struct xt_policy_elem *e = &info->pol[info->len];
128	struct in_addr *addr = NULL, mask;
129	struct in6_addr *addr6 = NULL, mask6;
130	unsigned int naddr = 0, num;
131	int mode;
132
133	xtables_check_inverse(optarg, &invert, &optind, 0, argv);
134
135	switch (c) {
136	case '1':
137		if (info->flags & (XT_POLICY_MATCH_IN | XT_POLICY_MATCH_OUT))
138			xtables_error(PARAMETER_PROBLEM,
139			           "policy match: double --dir option");
140		if (invert)
141			xtables_error(PARAMETER_PROBLEM,
142			           "policy match: can't invert --dir option");
143
144		info->flags |= parse_direction(optarg);
145		break;
146	case '2':
147		if (invert)
148			xtables_error(PARAMETER_PROBLEM,
149			           "policy match: can't invert --policy option");
150
151		info->flags |= parse_policy(optarg);
152		break;
153	case '3':
154		if (info->flags & XT_POLICY_MATCH_STRICT)
155			xtables_error(PARAMETER_PROBLEM,
156			           "policy match: double --strict option");
157
158		if (invert)
159			xtables_error(PARAMETER_PROBLEM,
160			           "policy match: can't invert --strict option");
161
162		info->flags |= XT_POLICY_MATCH_STRICT;
163		break;
164	case '4':
165		if (e->match.reqid)
166			xtables_error(PARAMETER_PROBLEM,
167			           "policy match: double --reqid option");
168
169		e->match.reqid = 1;
170		e->invert.reqid = invert;
171		if (!xtables_strtoui(optarg, NULL, &num, 0, UINT32_MAX))
172			xtables_param_act(XTF_BAD_VALUE, "policy", "--spi", optarg);
173		e->reqid = num;
174		break;
175	case '5':
176		if (e->match.spi)
177			xtables_error(PARAMETER_PROBLEM,
178			           "policy match: double --spi option");
179
180		e->match.spi = 1;
181		e->invert.spi = invert;
182		if (!xtables_strtoui(optarg, NULL, &num, 0, UINT32_MAX))
183			xtables_param_act(XTF_BAD_VALUE, "policy", "--spi", optarg);
184		e->spi = num;
185		break;
186	case '6':
187		if (e->match.saddr)
188			xtables_error(PARAMETER_PROBLEM,
189			           "policy match: double --tunnel-src option");
190
191		if (family == NFPROTO_IPV6)
192			xtables_ip6parse_any(optarg, &addr6, &mask6, &naddr);
193		else
194			xtables_ipparse_any(optarg, &addr, &mask, &naddr);
195		if (naddr > 1)
196			xtables_error(PARAMETER_PROBLEM,
197			           "policy match: name resolves to multiple IPs");
198
199		e->match.saddr = 1;
200		e->invert.saddr = invert;
201		if (family == NFPROTO_IPV6) {
202			memcpy(&e->saddr.a6, addr6, sizeof(*addr6));
203			memcpy(&e->smask.a6, &mask6, sizeof(mask6));
204		} else {
205			e->saddr.a4 = addr[0];
206			e->smask.a4 = mask;
207		}
208                break;
209	case '7':
210		if (e->match.daddr)
211			xtables_error(PARAMETER_PROBLEM,
212			           "policy match: double --tunnel-dst option");
213
214		if (family == NFPROTO_IPV6)
215			xtables_ip6parse_any(optarg, &addr6, &mask6, &naddr);
216		else
217			xtables_ipparse_any(optarg, &addr, &mask, &naddr);
218		if (naddr > 1)
219			xtables_error(PARAMETER_PROBLEM,
220			           "policy match: name resolves to multiple IPs");
221
222		e->match.daddr = 1;
223		e->invert.daddr = invert;
224		if (family == NFPROTO_IPV6) {
225			memcpy(&e->daddr.a6, addr6, sizeof(*addr6));
226			memcpy(&e->dmask.a6, &mask6, sizeof(mask6));
227		} else {
228			e->daddr.a4 = addr[0];
229			e->dmask.a4 = mask;
230		}
231		break;
232	case '8':
233		if (e->match.proto)
234			xtables_error(PARAMETER_PROBLEM,
235			           "policy match: double --proto option");
236
237		e->proto = xtables_parse_protocol(optarg);
238		if (e->proto != IPPROTO_AH && e->proto != IPPROTO_ESP &&
239		    e->proto != IPPROTO_COMP)
240			xtables_error(PARAMETER_PROBLEM,
241			           "policy match: protocol must ah/esp/ipcomp");
242		e->match.proto = 1;
243		e->invert.proto = invert;
244		break;
245	case '9':
246		if (e->match.mode)
247			xtables_error(PARAMETER_PROBLEM,
248			           "policy match: double --mode option");
249
250		mode = parse_mode(optarg);
251		e->match.mode = 1;
252		e->invert.mode = invert;
253		e->mode = mode;
254		break;
255	case 'a':
256		if (invert)
257			xtables_error(PARAMETER_PROBLEM,
258			           "policy match: can't invert --next option");
259
260		if (++info->len == XT_POLICY_MAX_ELEM)
261			xtables_error(PARAMETER_PROBLEM,
262			           "policy match: maximum policy depth reached");
263		break;
264	default:
265		return 0;
266	}
267
268	policy_info = info;
269	return 1;
270}
271
272static int policy4_parse(int c, char **argv, int invert, unsigned int *flags,
273                         const void *entry, struct xt_entry_match **match)
274{
275	return policy_parse(c, argv, invert, flags, (void *)(*match)->data,
276	       NFPROTO_IPV4);
277}
278
279static int policy6_parse(int c, char **argv, int invert, unsigned int *flags,
280                        const void *entry, struct xt_entry_match **match)
281{
282	return policy_parse(c, argv, invert, flags, (void *)(*match)->data,
283	       NFPROTO_IPV6);
284}
285
286static void policy_check(unsigned int flags)
287{
288	struct xt_policy_info *info = policy_info;
289	struct xt_policy_elem *e;
290	int i;
291
292	if (info == NULL)
293		xtables_error(PARAMETER_PROBLEM,
294		           "policy match: no parameters given");
295
296	if (!(info->flags & (XT_POLICY_MATCH_IN | XT_POLICY_MATCH_OUT)))
297		xtables_error(PARAMETER_PROBLEM,
298		           "policy match: neither --dir in nor --dir out specified");
299
300	if (info->flags & XT_POLICY_MATCH_NONE) {
301		if (info->flags & XT_POLICY_MATCH_STRICT)
302			xtables_error(PARAMETER_PROBLEM,
303			           "policy match: policy none but --strict given");
304
305		if (info->len != 0)
306			xtables_error(PARAMETER_PROBLEM,
307			           "policy match: policy none but policy given");
308	} else
309		info->len++;	/* increase len by 1, no --next after last element */
310
311	if (!(info->flags & XT_POLICY_MATCH_STRICT) && info->len > 1)
312		xtables_error(PARAMETER_PROBLEM,
313		           "policy match: multiple elements but no --strict");
314
315	for (i = 0; i < info->len; i++) {
316		e = &info->pol[i];
317
318		if (info->flags & XT_POLICY_MATCH_STRICT &&
319		    !(e->match.reqid || e->match.spi || e->match.saddr ||
320		      e->match.daddr || e->match.proto || e->match.mode))
321			xtables_error(PARAMETER_PROBLEM,
322			           "policy match: empty policy element");
323
324		if ((e->match.saddr || e->match.daddr)
325		    && ((e->mode == XT_POLICY_MODE_TUNNEL && e->invert.mode) ||
326		        (e->mode == XT_POLICY_MODE_TRANSPORT && !e->invert.mode)))
327			xtables_error(PARAMETER_PROBLEM,
328			           "policy match: --tunnel-src/--tunnel-dst "
329			           "is only valid in tunnel mode");
330	}
331}
332
333static void print_mode(const char *prefix, uint8_t mode, int numeric)
334{
335	printf("%smode ", prefix);
336
337	switch (mode) {
338	case XT_POLICY_MODE_TRANSPORT:
339		printf("transport ");
340		break;
341	case XT_POLICY_MODE_TUNNEL:
342		printf("tunnel ");
343		break;
344	default:
345		printf("??? ");
346		break;
347	}
348}
349
350static void print_proto(const char *prefix, uint8_t proto, int numeric)
351{
352	struct protoent *p = NULL;
353
354	printf("%sproto ", prefix);
355	if (!numeric)
356		p = getprotobynumber(proto);
357	if (p != NULL)
358		printf("%s ", p->p_name);
359	else
360		printf("%u ", proto);
361}
362
363#define PRINT_INVERT(x)		\
364do {				\
365	if (x)			\
366		printf("! ");	\
367} while(0)
368
369static void print_entry(const char *prefix, const struct xt_policy_elem *e,
370                        bool numeric, uint8_t family)
371{
372	if (e->match.reqid) {
373		PRINT_INVERT(e->invert.reqid);
374		printf("%sreqid %u ", prefix, e->reqid);
375	}
376	if (e->match.spi) {
377		PRINT_INVERT(e->invert.spi);
378		printf("%sspi 0x%x ", prefix, e->spi);
379	}
380	if (e->match.proto) {
381		PRINT_INVERT(e->invert.proto);
382		print_proto(prefix, e->proto, numeric);
383	}
384	if (e->match.mode) {
385		PRINT_INVERT(e->invert.mode);
386		print_mode(prefix, e->mode, numeric);
387	}
388	if (e->match.daddr) {
389		PRINT_INVERT(e->invert.daddr);
390		if (family == NFPROTO_IPV6)
391			printf("%stunnel-dst %s%s ", prefix,
392			       xtables_ip6addr_to_numeric(&e->daddr.a6),
393			       xtables_ip6mask_to_numeric(&e->dmask.a6));
394		else
395			printf("%stunnel-dst %s%s ", prefix,
396			       xtables_ipaddr_to_numeric(&e->daddr.a4),
397			       xtables_ipmask_to_numeric(&e->dmask.a4));
398	}
399	if (e->match.saddr) {
400		PRINT_INVERT(e->invert.saddr);
401		if (family == NFPROTO_IPV6)
402			printf("%stunnel-src %s%s ", prefix,
403			       xtables_ip6addr_to_numeric(&e->saddr.a6),
404			       xtables_ip6mask_to_numeric(&e->smask.a6));
405		else
406			printf("%stunnel-src %s%s ", prefix,
407			       xtables_ipaddr_to_numeric(&e->saddr.a4),
408			       xtables_ipmask_to_numeric(&e->smask.a4));
409	}
410}
411
412static void print_flags(char *prefix, const struct xt_policy_info *info)
413{
414	if (info->flags & XT_POLICY_MATCH_IN)
415		printf("%sdir in ", prefix);
416	else
417		printf("%sdir out ", prefix);
418
419	if (info->flags & XT_POLICY_MATCH_NONE)
420		printf("%spol none ", prefix);
421	else
422		printf("%spol ipsec ", prefix);
423
424	if (info->flags & XT_POLICY_MATCH_STRICT)
425		printf("%sstrict ", prefix);
426}
427
428static void policy4_print(const void *ip, const struct xt_entry_match *match,
429                          int numeric)
430{
431	const struct xt_policy_info *info = (void *)match->data;
432	unsigned int i;
433
434	printf("policy match ");
435	print_flags("", info);
436	for (i = 0; i < info->len; i++) {
437		if (info->len > 1)
438			printf("[%u] ", i);
439		print_entry("", &info->pol[i], numeric, NFPROTO_IPV4);
440	}
441}
442
443static void policy6_print(const void *ip, const struct xt_entry_match *match,
444                          int numeric)
445{
446	const struct xt_policy_info *info = (void *)match->data;
447	unsigned int i;
448
449	printf("policy match ");
450	print_flags("", info);
451	for (i = 0; i < info->len; i++) {
452		if (info->len > 1)
453			printf("[%u] ", i);
454		print_entry("", &info->pol[i], numeric, NFPROTO_IPV6);
455	}
456}
457
458static void policy4_save(const void *ip, const struct xt_entry_match *match)
459{
460	const struct xt_policy_info *info = (void *)match->data;
461	unsigned int i;
462
463	print_flags("--", info);
464	for (i = 0; i < info->len; i++) {
465		print_entry("--", &info->pol[i], false, NFPROTO_IPV4);
466		if (i + 1 < info->len)
467			printf("--next ");
468	}
469}
470
471static void policy6_save(const void *ip, const struct xt_entry_match *match)
472{
473	const struct xt_policy_info *info = (void *)match->data;
474	unsigned int i;
475
476	print_flags("--", info);
477	for (i = 0; i < info->len; i++) {
478		print_entry("--", &info->pol[i], false, NFPROTO_IPV6);
479		if (i + 1 < info->len)
480			printf("--next ");
481	}
482}
483
484static struct xtables_match policy_mt_reg[] = {
485	{
486		.name          = "policy",
487		.version       = XTABLES_VERSION,
488		.family        = NFPROTO_IPV4,
489		.size          = XT_ALIGN(sizeof(struct xt_policy_info)),
490		.userspacesize = XT_ALIGN(sizeof(struct xt_policy_info)),
491		.help          = policy_help,
492		.parse         = policy4_parse,
493		.final_check   = policy_check,
494		.print         = policy4_print,
495		.save          = policy4_save,
496		.extra_opts    = policy_opts,
497	},
498	{
499		.name          = "policy",
500		.version       = XTABLES_VERSION,
501		.family        = NFPROTO_IPV6,
502		.size          = XT_ALIGN(sizeof(struct xt_policy_info)),
503		.userspacesize = XT_ALIGN(sizeof(struct xt_policy_info)),
504		.help          = policy_help,
505		.parse         = policy6_parse,
506		.final_check   = policy_check,
507		.print         = policy6_print,
508		.save          = policy6_save,
509		.extra_opts    = policy_opts,
510	},
511};
512
513void _init(void)
514{
515	xtables_register_matches(policy_mt_reg, ARRAY_SIZE(policy_mt_reg));
516}
517