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