libxt_policy.c revision 73866357e4a7a0fdc1b293bf8863fee2bd56da9e
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	}
265
266	policy_info = info;
267	return 1;
268}
269
270static int policy4_parse(int c, char **argv, int invert, unsigned int *flags,
271                         const void *entry, struct xt_entry_match **match)
272{
273	return policy_parse(c, argv, invert, flags, (void *)(*match)->data,
274	       NFPROTO_IPV4);
275}
276
277static int policy6_parse(int c, char **argv, int invert, unsigned int *flags,
278                        const void *entry, struct xt_entry_match **match)
279{
280	return policy_parse(c, argv, invert, flags, (void *)(*match)->data,
281	       NFPROTO_IPV6);
282}
283
284static void policy_check(unsigned int flags)
285{
286	struct xt_policy_info *info = policy_info;
287	struct xt_policy_elem *e;
288	int i;
289
290	if (info == NULL)
291		xtables_error(PARAMETER_PROBLEM,
292		           "policy match: no parameters given");
293
294	if (!(info->flags & (XT_POLICY_MATCH_IN | XT_POLICY_MATCH_OUT)))
295		xtables_error(PARAMETER_PROBLEM,
296		           "policy match: neither --dir in nor --dir out specified");
297
298	if (info->flags & XT_POLICY_MATCH_NONE) {
299		if (info->flags & XT_POLICY_MATCH_STRICT)
300			xtables_error(PARAMETER_PROBLEM,
301			           "policy match: policy none but --strict given");
302
303		if (info->len != 0)
304			xtables_error(PARAMETER_PROBLEM,
305			           "policy match: policy none but policy given");
306	} else
307		info->len++;	/* increase len by 1, no --next after last element */
308
309	if (!(info->flags & XT_POLICY_MATCH_STRICT) && info->len > 1)
310		xtables_error(PARAMETER_PROBLEM,
311		           "policy match: multiple elements but no --strict");
312
313	for (i = 0; i < info->len; i++) {
314		e = &info->pol[i];
315
316		if (info->flags & XT_POLICY_MATCH_STRICT &&
317		    !(e->match.reqid || e->match.spi || e->match.saddr ||
318		      e->match.daddr || e->match.proto || e->match.mode))
319			xtables_error(PARAMETER_PROBLEM,
320			           "policy match: empty policy element");
321
322		if ((e->match.saddr || e->match.daddr)
323		    && ((e->mode == XT_POLICY_MODE_TUNNEL && e->invert.mode) ||
324		        (e->mode == XT_POLICY_MODE_TRANSPORT && !e->invert.mode)))
325			xtables_error(PARAMETER_PROBLEM,
326			           "policy match: --tunnel-src/--tunnel-dst "
327			           "is only valid in tunnel mode");
328	}
329}
330
331static void print_mode(const char *prefix, uint8_t mode, int numeric)
332{
333	printf(" %smode ", prefix);
334
335	switch (mode) {
336	case XT_POLICY_MODE_TRANSPORT:
337		printf("transport");
338		break;
339	case XT_POLICY_MODE_TUNNEL:
340		printf("tunnel");
341		break;
342	default:
343		printf("???");
344		break;
345	}
346}
347
348static void print_proto(const char *prefix, uint8_t proto, int numeric)
349{
350	struct protoent *p = NULL;
351
352	printf(" %sproto ", prefix);
353	if (!numeric)
354		p = getprotobynumber(proto);
355	if (p != NULL)
356		printf("%s", p->p_name);
357	else
358		printf("%u", proto);
359}
360
361#define PRINT_INVERT(x)		\
362do {				\
363	if (x)			\
364		printf(" !");	\
365} while(0)
366
367static void print_entry(const char *prefix, const struct xt_policy_elem *e,
368                        bool numeric, uint8_t family)
369{
370	if (e->match.reqid) {
371		PRINT_INVERT(e->invert.reqid);
372		printf(" %sreqid %u", prefix, e->reqid);
373	}
374	if (e->match.spi) {
375		PRINT_INVERT(e->invert.spi);
376		printf(" %sspi 0x%x", prefix, e->spi);
377	}
378	if (e->match.proto) {
379		PRINT_INVERT(e->invert.proto);
380		print_proto(prefix, e->proto, numeric);
381	}
382	if (e->match.mode) {
383		PRINT_INVERT(e->invert.mode);
384		print_mode(prefix, e->mode, numeric);
385	}
386	if (e->match.daddr) {
387		PRINT_INVERT(e->invert.daddr);
388		if (family == NFPROTO_IPV6)
389			printf(" %stunnel-dst %s%s", prefix,
390			       xtables_ip6addr_to_numeric(&e->daddr.a6),
391			       xtables_ip6mask_to_numeric(&e->dmask.a6));
392		else
393			printf(" %stunnel-dst %s%s", prefix,
394			       xtables_ipaddr_to_numeric(&e->daddr.a4),
395			       xtables_ipmask_to_numeric(&e->dmask.a4));
396	}
397	if (e->match.saddr) {
398		PRINT_INVERT(e->invert.saddr);
399		if (family == NFPROTO_IPV6)
400			printf(" %stunnel-src %s%s", prefix,
401			       xtables_ip6addr_to_numeric(&e->saddr.a6),
402			       xtables_ip6mask_to_numeric(&e->smask.a6));
403		else
404			printf(" %stunnel-src %s%s", prefix,
405			       xtables_ipaddr_to_numeric(&e->saddr.a4),
406			       xtables_ipmask_to_numeric(&e->smask.a4));
407	}
408}
409
410static void print_flags(char *prefix, const struct xt_policy_info *info)
411{
412	if (info->flags & XT_POLICY_MATCH_IN)
413		printf(" %sdir in", prefix);
414	else
415		printf(" %sdir out", prefix);
416
417	if (info->flags & XT_POLICY_MATCH_NONE)
418		printf(" %spol none", prefix);
419	else
420		printf(" %spol ipsec", prefix);
421
422	if (info->flags & XT_POLICY_MATCH_STRICT)
423		printf(" %sstrict", prefix);
424}
425
426static void policy4_print(const void *ip, const struct xt_entry_match *match,
427                          int numeric)
428{
429	const struct xt_policy_info *info = (void *)match->data;
430	unsigned int i;
431
432	printf(" policy match");
433	print_flags("", info);
434	for (i = 0; i < info->len; i++) {
435		if (info->len > 1)
436			printf(" [%u]", i);
437		print_entry("", &info->pol[i], numeric, NFPROTO_IPV4);
438	}
439}
440
441static void policy6_print(const void *ip, const struct xt_entry_match *match,
442                          int numeric)
443{
444	const struct xt_policy_info *info = (void *)match->data;
445	unsigned int i;
446
447	printf(" policy match");
448	print_flags("", info);
449	for (i = 0; i < info->len; i++) {
450		if (info->len > 1)
451			printf(" [%u]", i);
452		print_entry("", &info->pol[i], numeric, NFPROTO_IPV6);
453	}
454}
455
456static void policy4_save(const void *ip, const struct xt_entry_match *match)
457{
458	const struct xt_policy_info *info = (void *)match->data;
459	unsigned int i;
460
461	print_flags("--", info);
462	for (i = 0; i < info->len; i++) {
463		print_entry("--", &info->pol[i], false, NFPROTO_IPV4);
464		if (i + 1 < info->len)
465			printf(" --next");
466	}
467}
468
469static void policy6_save(const void *ip, const struct xt_entry_match *match)
470{
471	const struct xt_policy_info *info = (void *)match->data;
472	unsigned int i;
473
474	print_flags("--", info);
475	for (i = 0; i < info->len; i++) {
476		print_entry("--", &info->pol[i], false, NFPROTO_IPV6);
477		if (i + 1 < info->len)
478			printf(" --next");
479	}
480}
481
482static struct xtables_match policy_mt_reg[] = {
483	{
484		.name          = "policy",
485		.version       = XTABLES_VERSION,
486		.family        = NFPROTO_IPV4,
487		.size          = XT_ALIGN(sizeof(struct xt_policy_info)),
488		.userspacesize = XT_ALIGN(sizeof(struct xt_policy_info)),
489		.help          = policy_help,
490		.parse         = policy4_parse,
491		.final_check   = policy_check,
492		.print         = policy4_print,
493		.save          = policy4_save,
494		.extra_opts    = policy_opts,
495	},
496	{
497		.name          = "policy",
498		.version       = XTABLES_VERSION,
499		.family        = NFPROTO_IPV6,
500		.size          = XT_ALIGN(sizeof(struct xt_policy_info)),
501		.userspacesize = XT_ALIGN(sizeof(struct xt_policy_info)),
502		.help          = policy_help,
503		.parse         = policy6_parse,
504		.final_check   = policy_check,
505		.print         = policy6_print,
506		.save          = policy6_save,
507		.extra_opts    = policy_opts,
508	},
509};
510
511void _init(void)
512{
513	xtables_register_matches(policy_mt_reg, ARRAY_SIZE(policy_mt_reg));
514}
515