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