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