/* Shared library add-on to iptables to add policy support. */ #include <stdio.h> #include <netdb.h> #include <string.h> #include <stdlib.h> #include <syslog.h> #include <getopt.h> #include <netdb.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <iptables.h> #include <linux/netfilter_ipv4/ip_tables.h> #include "../include/linux/netfilter_ipv4/ipt_policy.h" /* * HACK: global pointer to current matchinfo for making * final checks and adjustments in final_check. */ static struct ipt_policy_info *policy_info; static void help(void) { printf( "policy v%s options:\n" " --dir in|out match policy applied during decapsulation/\n" " policy to be applied during encapsulation\n" " --pol none|ipsec match policy\n" " --strict match entire policy instead of single element\n" " at any position\n" "[!] --reqid reqid match reqid\n" "[!] --spi spi match SPI\n" "[!] --proto proto match protocol (ah/esp/ipcomp)\n" "[!] --mode mode match mode (transport/tunnel)\n" "[!] --tunnel-src addr/mask match tunnel source\n" "[!] --tunnel-dst addr/mask match tunnel destination\n" " --next begin next element in policy\n", IPTABLES_VERSION); } static struct option opts[] = { { .name = "dir", .has_arg = 1, .val = '1', }, { .name = "pol", .has_arg = 1, .val = '2', }, { .name = "strict", .val = '3' }, { .name = "reqid", .has_arg = 1, .val = '4', }, { .name = "spi", .has_arg = 1, .val = '5' }, { .name = "tunnel-src", .has_arg = 1, .val = '6' }, { .name = "tunnel-dst", .has_arg = 1, .val = '7' }, { .name = "proto", .has_arg = 1, .val = '8' }, { .name = "mode", .has_arg = 1, .val = '9' }, { .name = "next", .val = 'a' }, { } }; static void init(struct ipt_entry_match *m, unsigned int *nfcache) { *nfcache |= NFC_UNKNOWN; } static int parse_direction(char *s) { if (strcmp(s, "in") == 0) return IPT_POLICY_MATCH_IN; if (strcmp(s, "out") == 0) return IPT_POLICY_MATCH_OUT; exit_error(PARAMETER_PROBLEM, "policy_match: invalid dir `%s'", s); } static int parse_policy(char *s) { if (strcmp(s, "none") == 0) return IPT_POLICY_MATCH_NONE; if (strcmp(s, "ipsec") == 0) return 0; exit_error(PARAMETER_PROBLEM, "policy match: invalid policy `%s'", s); } static int parse_mode(char *s) { if (strcmp(s, "transport") == 0) return IPT_POLICY_MODE_TRANSPORT; if (strcmp(s, "tunnel") == 0) return IPT_POLICY_MODE_TUNNEL; exit_error(PARAMETER_PROBLEM, "policy match: invalid mode `%s'", s); } static int parse(int c, char **argv, int invert, unsigned int *flags, const struct ipt_entry *entry, unsigned int *nfcache, struct ipt_entry_match **match) { struct ipt_policy_info *info = (void *)(*match)->data; struct ipt_policy_elem *e = &info->pol[info->len]; struct in_addr *addr = NULL, mask; unsigned int naddr = 0; int mode; check_inverse(optarg, &invert, &optind, 0); switch (c) { case '1': if (info->flags & (IPT_POLICY_MATCH_IN|IPT_POLICY_MATCH_OUT)) exit_error(PARAMETER_PROBLEM, "policy match: double --dir option"); if (invert) exit_error(PARAMETER_PROBLEM, "policy match: can't invert --dir option"); info->flags |= parse_direction(argv[optind-1]); break; case '2': if (invert) exit_error(PARAMETER_PROBLEM, "policy match: can't invert --policy option"); info->flags |= parse_policy(argv[optind-1]); break; case '3': if (info->flags & IPT_POLICY_MATCH_STRICT) exit_error(PARAMETER_PROBLEM, "policy match: double --strict option"); if (invert) exit_error(PARAMETER_PROBLEM, "policy match: can't invert --strict option"); info->flags |= IPT_POLICY_MATCH_STRICT; break; case '4': if (e->match.reqid) exit_error(PARAMETER_PROBLEM, "policy match: double --reqid option"); e->match.reqid = 1; e->invert.reqid = invert; e->reqid = strtol(argv[optind-1], NULL, 10); break; case '5': if (e->match.spi) exit_error(PARAMETER_PROBLEM, "policy match: double --spi option"); e->match.spi = 1; e->invert.spi = invert; e->spi = strtol(argv[optind-1], NULL, 0x10); break; case '6': if (e->match.saddr) exit_error(PARAMETER_PROBLEM, "policy match: double --tunnel-src option"); parse_hostnetworkmask(argv[optind-1], &addr, &mask, &naddr); if (naddr > 1) exit_error(PARAMETER_PROBLEM, "policy match: name resolves to multiple IPs"); e->match.saddr = 1; e->invert.saddr = invert; e->saddr.a4 = addr[0]; e->smask.a4 = mask; break; case '7': if (e->match.daddr) exit_error(PARAMETER_PROBLEM, "policy match: double --tunnel-dst option"); parse_hostnetworkmask(argv[optind-1], &addr, &mask, &naddr); if (naddr > 1) exit_error(PARAMETER_PROBLEM, "policy match: name resolves to multiple IPs"); e->match.daddr = 1; e->invert.daddr = invert; e->daddr.a4 = addr[0]; e->dmask.a4 = mask; break; case '8': if (e->match.proto) exit_error(PARAMETER_PROBLEM, "policy match: double --proto option"); e->proto = parse_protocol(argv[optind-1]); if (e->proto != IPPROTO_AH && e->proto != IPPROTO_ESP && e->proto != IPPROTO_COMP) exit_error(PARAMETER_PROBLEM, "policy match: protocol must ah/esp/ipcomp"); e->match.proto = 1; e->invert.proto = invert; break; case '9': if (e->match.mode) exit_error(PARAMETER_PROBLEM, "policy match: double --mode option"); mode = parse_mode(argv[optind-1]); e->match.mode = 1; e->invert.mode = invert; e->mode = mode; break; case 'a': if (invert) exit_error(PARAMETER_PROBLEM, "policy match: can't invert --next option"); if (++info->len == IPT_POLICY_MAX_ELEM) exit_error(PARAMETER_PROBLEM, "policy match: maximum policy depth reached"); break; default: return 0; } policy_info = info; return 1; } static void final_check(unsigned int flags) { struct ipt_policy_info *info = policy_info; struct ipt_policy_elem *e; int i; if (info == NULL) exit_error(PARAMETER_PROBLEM, "policy match: no parameters given"); if (!(info->flags & (IPT_POLICY_MATCH_IN|IPT_POLICY_MATCH_OUT))) exit_error(PARAMETER_PROBLEM, "policy match: neither --in nor --out specified"); if (info->flags & IPT_POLICY_MATCH_NONE) { if (info->flags & IPT_POLICY_MATCH_STRICT) exit_error(PARAMETER_PROBLEM, "policy match: policy none but --strict given"); if (info->len != 0) exit_error(PARAMETER_PROBLEM, "policy match: policy none but policy given"); } else info->len++; /* increase len by 1, no --next after last element */ if (!(info->flags & IPT_POLICY_MATCH_STRICT) && info->len > 1) exit_error(PARAMETER_PROBLEM, "policy match: multiple elements but no --strict"); for (i = 0; i < info->len; i++) { e = &info->pol[i]; if (info->flags & IPT_POLICY_MATCH_STRICT && !(e->match.reqid || e->match.spi || e->match.saddr || e->match.daddr || e->match.proto || e->match.mode)) exit_error(PARAMETER_PROBLEM, "policy match: empty policy element"); if ((e->match.saddr || e->match.daddr) && ((e->mode == IPT_POLICY_MODE_TUNNEL && e->invert.mode) || (e->mode == IPT_POLICY_MODE_TRANSPORT && !e->invert.mode))) exit_error(PARAMETER_PROBLEM, "policy match: --tunnel-src/--tunnel-dst " "is only valid in tunnel mode"); } } static void print_mode(char *prefix, u_int8_t mode, int numeric) { printf("%smode ", prefix); switch (mode) { case IPT_POLICY_MODE_TRANSPORT: printf("transport "); break; case IPT_POLICY_MODE_TUNNEL: printf("tunnel "); break; default: printf("??? "); break; } } static void print_proto(char *prefix, u_int8_t proto, int numeric) { struct protoent *p = NULL; printf("%sproto ", prefix); if (!numeric) p = getprotobynumber(proto); if (p != NULL) printf("%s ", p->p_name); else printf("%u ", proto); } #define PRINT_INVERT(x) \ do { \ if (x) \ printf("! "); \ } while(0) static void print_entry(char *prefix, const struct ipt_policy_elem *e, int numeric) { if (e->match.reqid) { PRINT_INVERT(e->invert.reqid); printf("%sreqid %u ", prefix, e->reqid); } if (e->match.spi) { PRINT_INVERT(e->invert.spi); printf("%sspi 0x%x ", prefix, e->spi); } if (e->match.proto) { PRINT_INVERT(e->invert.proto); print_proto(prefix, e->proto, numeric); } if (e->match.mode) { PRINT_INVERT(e->invert.mode); print_mode(prefix, e->mode, numeric); } if (e->match.daddr) { PRINT_INVERT(e->invert.daddr); printf("%stunnel-dst %s%s ", prefix, addr_to_dotted((struct in_addr *)&e->daddr), mask_to_dotted((struct in_addr *)&e->dmask)); } if (e->match.saddr) { PRINT_INVERT(e->invert.saddr); printf("%stunnel-src %s%s ", prefix, addr_to_dotted((struct in_addr *)&e->saddr), mask_to_dotted((struct in_addr *)&e->smask)); } } static void print_flags(char *prefix, const struct ipt_policy_info *info) { if (info->flags & IPT_POLICY_MATCH_IN) printf("%sdir in ", prefix); else printf("%sdir out ", prefix); if (info->flags & IPT_POLICY_MATCH_NONE) printf("%spol none ", prefix); else printf("%spol ipsec ", prefix); if (info->flags & IPT_POLICY_MATCH_STRICT) printf("%sstrict ", prefix); } static void print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric) { const struct ipt_policy_info *info = (void *)match->data; unsigned int i; printf("policy match "); print_flags("", info); for (i = 0; i < info->len; i++) { if (info->len > 1) printf("[%u] ", i); print_entry("", &info->pol[i], numeric); } } static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) { const struct ipt_policy_info *info = (void *)match->data; unsigned int i; print_flags("--", info); for (i = 0; i < info->len; i++) { print_entry("--", &info->pol[i], 0); if (i + 1 < info->len) printf("--next "); } } struct iptables_match policy = { .name = "policy", .version = IPTABLES_VERSION, .size = IPT_ALIGN(sizeof(struct ipt_policy_info)), .userspacesize = IPT_ALIGN(sizeof(struct ipt_policy_info)), .help = help, .init = init, .parse = parse, .final_check = final_check, .print = print, .save = save, .extra_opts = opts }; void ipt_policy_init(void) { register_match(&policy); }