| lh | 9ed821d | 2023-04-07 01:36:19 -0700 | [diff] [blame] | 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_ipv4/ipt_policy.h> | 
|  | 16 |  | 
|  | 17 | /* | 
|  | 18 | * HACK: global pointer to current matchinfo for making | 
|  | 19 | * final checks and adjustments in final_check. | 
|  | 20 | */ | 
|  | 21 | static struct ipt_policy_info *policy_info; | 
|  | 22 |  | 
|  | 23 | static 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 |  | 
|  | 41 | static 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 |  | 
|  | 94 | static int parse_direction(char *s) | 
|  | 95 | { | 
|  | 96 | if (strcmp(s, "in") == 0) | 
|  | 97 | return IPT_POLICY_MATCH_IN; | 
|  | 98 | if (strcmp(s, "out") == 0) | 
|  | 99 | return IPT_POLICY_MATCH_OUT; | 
|  | 100 | xtables_error(PARAMETER_PROBLEM, "policy_match: invalid dir \"%s\"", s); | 
|  | 101 | } | 
|  | 102 |  | 
|  | 103 | static int parse_policy(char *s) | 
|  | 104 | { | 
|  | 105 | if (strcmp(s, "none") == 0) | 
|  | 106 | return IPT_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 |  | 
|  | 112 | static int parse_mode(char *s) | 
|  | 113 | { | 
|  | 114 | if (strcmp(s, "transport") == 0) | 
|  | 115 | return IPT_POLICY_MODE_TRANSPORT; | 
|  | 116 | if (strcmp(s, "tunnel") == 0) | 
|  | 117 | return IPT_POLICY_MODE_TUNNEL; | 
|  | 118 | xtables_error(PARAMETER_PROBLEM, "policy match: invalid mode \"%s\"", s); | 
|  | 119 | } | 
|  | 120 |  | 
|  | 121 | static int policy_parse(int c, char **argv, int invert, unsigned int *flags, | 
|  | 122 | const void *entry, struct xt_entry_match **match) | 
|  | 123 | { | 
|  | 124 | struct ipt_policy_info *info = (void *)(*match)->data; | 
|  | 125 | struct ipt_policy_elem *e = &info->pol[info->len]; | 
|  | 126 | struct in_addr *addr = NULL, mask; | 
|  | 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 & (IPT_POLICY_MATCH_IN|IPT_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(argv[optind-1]); | 
|  | 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(argv[optind-1]); | 
|  | 149 | break; | 
|  | 150 | case '3': | 
|  | 151 | if (info->flags & IPT_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 |= IPT_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 | xtables_ipparse_any(argv[optind-1], &addr, &mask, &naddr); | 
|  | 189 | if (naddr > 1) | 
|  | 190 | xtables_error(PARAMETER_PROBLEM, | 
|  | 191 | "policy match: name resolves to multiple IPs"); | 
|  | 192 |  | 
|  | 193 | e->match.saddr = 1; | 
|  | 194 | e->invert.saddr = invert; | 
|  | 195 | e->saddr.a4 = addr[0]; | 
|  | 196 | e->smask.a4 = mask; | 
|  | 197 | break; | 
|  | 198 | case '7': | 
|  | 199 | if (e->match.daddr) | 
|  | 200 | xtables_error(PARAMETER_PROBLEM, | 
|  | 201 | "policy match: double --tunnel-dst option"); | 
|  | 202 |  | 
|  | 203 | xtables_ipparse_any(argv[optind-1], &addr, &mask, &naddr); | 
|  | 204 | if (naddr > 1) | 
|  | 205 | xtables_error(PARAMETER_PROBLEM, | 
|  | 206 | "policy match: name resolves to multiple IPs"); | 
|  | 207 |  | 
|  | 208 | e->match.daddr = 1; | 
|  | 209 | e->invert.daddr = invert; | 
|  | 210 | e->daddr.a4 = addr[0]; | 
|  | 211 | e->dmask.a4 = mask; | 
|  | 212 | break; | 
|  | 213 | case '8': | 
|  | 214 | if (e->match.proto) | 
|  | 215 | xtables_error(PARAMETER_PROBLEM, | 
|  | 216 | "policy match: double --proto option"); | 
|  | 217 |  | 
|  | 218 | e->proto = xtables_parse_protocol(argv[optind-1]); | 
|  | 219 | if (e->proto != IPPROTO_AH && e->proto != IPPROTO_ESP && | 
|  | 220 | e->proto != IPPROTO_COMP) | 
|  | 221 | xtables_error(PARAMETER_PROBLEM, | 
|  | 222 | "policy match: protocol must ah/esp/ipcomp"); | 
|  | 223 | e->match.proto = 1; | 
|  | 224 | e->invert.proto = invert; | 
|  | 225 | break; | 
|  | 226 | case '9': | 
|  | 227 | if (e->match.mode) | 
|  | 228 | xtables_error(PARAMETER_PROBLEM, | 
|  | 229 | "policy match: double --mode option"); | 
|  | 230 |  | 
|  | 231 | mode = parse_mode(argv[optind-1]); | 
|  | 232 | e->match.mode = 1; | 
|  | 233 | e->invert.mode = invert; | 
|  | 234 | e->mode = mode; | 
|  | 235 | break; | 
|  | 236 | case 'a': | 
|  | 237 | if (invert) | 
|  | 238 | xtables_error(PARAMETER_PROBLEM, | 
|  | 239 | "policy match: can't invert --next option"); | 
|  | 240 |  | 
|  | 241 | if (++info->len == IPT_POLICY_MAX_ELEM) | 
|  | 242 | xtables_error(PARAMETER_PROBLEM, | 
|  | 243 | "policy match: maximum policy depth reached"); | 
|  | 244 | break; | 
|  | 245 | default: | 
|  | 246 | return 0; | 
|  | 247 | } | 
|  | 248 |  | 
|  | 249 | policy_info = info; | 
|  | 250 | return 1; | 
|  | 251 | } | 
|  | 252 |  | 
|  | 253 | static void policy_check(unsigned int flags) | 
|  | 254 | { | 
|  | 255 | struct ipt_policy_info *info = policy_info; | 
|  | 256 | struct ipt_policy_elem *e; | 
|  | 257 | int i; | 
|  | 258 |  | 
|  | 259 | if (info == NULL) | 
|  | 260 | xtables_error(PARAMETER_PROBLEM, | 
|  | 261 | "policy match: no parameters given"); | 
|  | 262 |  | 
|  | 263 | if (!(info->flags & (IPT_POLICY_MATCH_IN|IPT_POLICY_MATCH_OUT))) | 
|  | 264 | xtables_error(PARAMETER_PROBLEM, | 
|  | 265 | "policy match: neither --in nor --out specified"); | 
|  | 266 |  | 
|  | 267 | if (info->flags & IPT_POLICY_MATCH_NONE) { | 
|  | 268 | if (info->flags & IPT_POLICY_MATCH_STRICT) | 
|  | 269 | xtables_error(PARAMETER_PROBLEM, | 
|  | 270 | "policy match: policy none but --strict given"); | 
|  | 271 |  | 
|  | 272 | if (info->len != 0) | 
|  | 273 | xtables_error(PARAMETER_PROBLEM, | 
|  | 274 | "policy match: policy none but policy given"); | 
|  | 275 | } else | 
|  | 276 | info->len++;	/* increase len by 1, no --next after last element */ | 
|  | 277 |  | 
|  | 278 | if (!(info->flags & IPT_POLICY_MATCH_STRICT) && info->len > 1) | 
|  | 279 | xtables_error(PARAMETER_PROBLEM, | 
|  | 280 | "policy match: multiple elements but no --strict"); | 
|  | 281 |  | 
|  | 282 | for (i = 0; i < info->len; i++) { | 
|  | 283 | e = &info->pol[i]; | 
|  | 284 |  | 
|  | 285 | if (info->flags & IPT_POLICY_MATCH_STRICT && | 
|  | 286 | !(e->match.reqid || e->match.spi || e->match.saddr || | 
|  | 287 | e->match.daddr || e->match.proto || e->match.mode)) | 
|  | 288 | xtables_error(PARAMETER_PROBLEM, | 
|  | 289 | "policy match: empty policy element"); | 
|  | 290 |  | 
|  | 291 | if ((e->match.saddr || e->match.daddr) | 
|  | 292 | && ((e->mode == IPT_POLICY_MODE_TUNNEL && e->invert.mode) || | 
|  | 293 | (e->mode == IPT_POLICY_MODE_TRANSPORT && !e->invert.mode))) | 
|  | 294 | xtables_error(PARAMETER_PROBLEM, | 
|  | 295 | "policy match: --tunnel-src/--tunnel-dst " | 
|  | 296 | "is only valid in tunnel mode"); | 
|  | 297 | } | 
|  | 298 | } | 
|  | 299 |  | 
|  | 300 | static void print_mode(char *prefix, u_int8_t mode, int numeric) | 
|  | 301 | { | 
|  | 302 | printf("%smode ", prefix); | 
|  | 303 |  | 
|  | 304 | switch (mode) { | 
|  | 305 | case IPT_POLICY_MODE_TRANSPORT: | 
|  | 306 | printf("transport "); | 
|  | 307 | break; | 
|  | 308 | case IPT_POLICY_MODE_TUNNEL: | 
|  | 309 | printf("tunnel "); | 
|  | 310 | break; | 
|  | 311 | default: | 
|  | 312 | printf("??? "); | 
|  | 313 | break; | 
|  | 314 | } | 
|  | 315 | } | 
|  | 316 |  | 
|  | 317 | static void print_proto(char *prefix, u_int8_t proto, int numeric) | 
|  | 318 | { | 
|  | 319 | struct protoent *p = NULL; | 
|  | 320 |  | 
|  | 321 | printf("%sproto ", prefix); | 
|  | 322 | if (!numeric) | 
|  | 323 | p = getprotobynumber(proto); | 
|  | 324 | if (p != NULL) | 
|  | 325 | printf("%s ", p->p_name); | 
|  | 326 | else | 
|  | 327 | printf("%u ", proto); | 
|  | 328 | } | 
|  | 329 |  | 
|  | 330 | #define PRINT_INVERT(x)		\ | 
|  | 331 | do {				\ | 
|  | 332 | if (x)			\ | 
|  | 333 | printf("! ");	\ | 
|  | 334 | } while(0) | 
|  | 335 |  | 
|  | 336 | static void print_entry(char *prefix, const struct ipt_policy_elem *e, | 
|  | 337 | int numeric) | 
|  | 338 | { | 
|  | 339 | if (e->match.reqid) { | 
|  | 340 | PRINT_INVERT(e->invert.reqid); | 
|  | 341 | printf("%sreqid %u ", prefix, e->reqid); | 
|  | 342 | } | 
|  | 343 | if (e->match.spi) { | 
|  | 344 | PRINT_INVERT(e->invert.spi); | 
|  | 345 | printf("%sspi 0x%x ", prefix, e->spi); | 
|  | 346 | } | 
|  | 347 | if (e->match.proto) { | 
|  | 348 | PRINT_INVERT(e->invert.proto); | 
|  | 349 | print_proto(prefix, e->proto, numeric); | 
|  | 350 | } | 
|  | 351 | if (e->match.mode) { | 
|  | 352 | PRINT_INVERT(e->invert.mode); | 
|  | 353 | print_mode(prefix, e->mode, numeric); | 
|  | 354 | } | 
|  | 355 | if (e->match.daddr) { | 
|  | 356 | PRINT_INVERT(e->invert.daddr); | 
|  | 357 | printf("%stunnel-dst %s%s ", prefix, | 
|  | 358 | xtables_ipaddr_to_numeric((const void *)&e->daddr), | 
|  | 359 | xtables_ipmask_to_numeric((const void *)&e->dmask)); | 
|  | 360 | } | 
|  | 361 | if (e->match.saddr) { | 
|  | 362 | PRINT_INVERT(e->invert.saddr); | 
|  | 363 | printf("%stunnel-src %s%s ", prefix, | 
|  | 364 | xtables_ipaddr_to_numeric((const void *)&e->saddr), | 
|  | 365 | xtables_ipmask_to_numeric((const void *)&e->smask)); | 
|  | 366 | } | 
|  | 367 | } | 
|  | 368 |  | 
|  | 369 | static void print_flags(char *prefix, const struct ipt_policy_info *info) | 
|  | 370 | { | 
|  | 371 | if (info->flags & IPT_POLICY_MATCH_IN) | 
|  | 372 | printf("%sdir in ", prefix); | 
|  | 373 | else | 
|  | 374 | printf("%sdir out ", prefix); | 
|  | 375 |  | 
|  | 376 | if (info->flags & IPT_POLICY_MATCH_NONE) | 
|  | 377 | printf("%spol none ", prefix); | 
|  | 378 | else | 
|  | 379 | printf("%spol ipsec ", prefix); | 
|  | 380 |  | 
|  | 381 | if (info->flags & IPT_POLICY_MATCH_STRICT) | 
|  | 382 | printf("%sstrict ", prefix); | 
|  | 383 | } | 
|  | 384 |  | 
|  | 385 | static void policy_print(const void *ip, const struct xt_entry_match *match, | 
|  | 386 | int numeric) | 
|  | 387 | { | 
|  | 388 | const struct ipt_policy_info *info = (void *)match->data; | 
|  | 389 | unsigned int i; | 
|  | 390 |  | 
|  | 391 | printf("policy match "); | 
|  | 392 | print_flags("", info); | 
|  | 393 | for (i = 0; i < info->len; i++) { | 
|  | 394 | if (info->len > 1) | 
|  | 395 | printf("[%u] ", i); | 
|  | 396 | print_entry("", &info->pol[i], numeric); | 
|  | 397 | } | 
|  | 398 | } | 
|  | 399 |  | 
|  | 400 | static void policy_save(const void *ip, const struct xt_entry_match *match) | 
|  | 401 | { | 
|  | 402 | const struct ipt_policy_info *info = (void *)match->data; | 
|  | 403 | unsigned int i; | 
|  | 404 |  | 
|  | 405 | print_flags("--", info); | 
|  | 406 | for (i = 0; i < info->len; i++) { | 
|  | 407 | print_entry("--", &info->pol[i], 0); | 
|  | 408 | if (i + 1 < info->len) | 
|  | 409 | printf("--next "); | 
|  | 410 | } | 
|  | 411 | } | 
|  | 412 |  | 
|  | 413 | static struct xtables_match policy_mt_reg = { | 
|  | 414 | .name		= "policy", | 
|  | 415 | .version	= XTABLES_VERSION, | 
|  | 416 | .family		= NFPROTO_IPV4, | 
|  | 417 | .size		= XT_ALIGN(sizeof(struct ipt_policy_info)), | 
|  | 418 | .userspacesize	= XT_ALIGN(sizeof(struct ipt_policy_info)), | 
|  | 419 | .help		= policy_help, | 
|  | 420 | .parse		= policy_parse, | 
|  | 421 | .final_check	= policy_check, | 
|  | 422 | .print		= policy_print, | 
|  | 423 | .save		= policy_save, | 
|  | 424 | .extra_opts	= policy_opts, | 
|  | 425 | }; | 
|  | 426 |  | 
|  | 427 | void _init(void) | 
|  | 428 | { | 
|  | 429 | xtables_register_match(&policy_mt_reg); | 
|  | 430 | } |