1/* Code to restore the iptables state, from file by iptables-save. 2 * (C) 2000-2002 by Harald Welte <laforge@gnumonks.org> 3 * based on previous code from Rusty Russell <rusty@linuxcare.com.au> 4 * 5 * This code is distributed under the terms of GNU GPL v2 6 */ 7 8#include <getopt.h> 9#include <errno.h> 10#include <stdbool.h> 11#include <string.h> 12#include <stdio.h> 13#include <stdlib.h> 14#include "iptables.h" 15#include "xtables.h" 16#include "libiptc/libiptc.h" 17#include "xtables-multi.h" 18#include "nft.h" 19#include <libnftnl/chain.h> 20 21#ifdef DEBUG 22#define DEBUGP(x, args...) fprintf(stderr, x, ## args) 23#else 24#define DEBUGP(x, args...) 25#endif 26 27static int counters = 0, verbose = 0, noflush = 0; 28 29/* Keeping track of external matches and targets. */ 30static const struct option options[] = { 31 {.name = "counters", .has_arg = false, .val = 'c'}, 32 {.name = "verbose", .has_arg = false, .val = 'v'}, 33 {.name = "test", .has_arg = false, .val = 't'}, 34 {.name = "help", .has_arg = false, .val = 'h'}, 35 {.name = "noflush", .has_arg = false, .val = 'n'}, 36 {.name = "modprobe", .has_arg = true, .val = 'M'}, 37 {.name = "table", .has_arg = true, .val = 'T'}, 38 {.name = "ipv4", .has_arg = false, .val = '4'}, 39 {.name = "ipv6", .has_arg = false, .val = '6'}, 40 {NULL}, 41}; 42 43static void print_usage(const char *name, const char *version) __attribute__((noreturn)); 44 45#define prog_name xtables_globals.program_name 46 47static void print_usage(const char *name, const char *version) 48{ 49 fprintf(stderr, "Usage: %s [-c] [-v] [-t] [-h] [-n] [-T table] [-M command] [-4] [-6]\n" 50 " [ --counters ]\n" 51 " [ --verbose ]\n" 52 " [ --test ]\n" 53 " [ --help ]\n" 54 " [ --noflush ]\n" 55 " [ --table=<TABLE> ]\n" 56 " [ --modprobe=<command> ]\n" 57 " [ --ipv4 ]\n" 58 " [ --ipv6 ]\n", name); 59 60 exit(1); 61} 62 63static int parse_counters(char *string, struct xt_counters *ctr) 64{ 65 unsigned long long pcnt, bcnt; 66 int ret; 67 68 ret = sscanf(string, "[%llu:%llu]", &pcnt, &bcnt); 69 ctr->pcnt = pcnt; 70 ctr->bcnt = bcnt; 71 return ret == 2; 72} 73 74/* global new argv and argc */ 75static char *newargv[255]; 76static int newargc; 77 78/* function adding one argument to newargv, updating newargc 79 * returns true if argument added, false otherwise */ 80static int add_argv(char *what) { 81 DEBUGP("add_argv: %s\n", what); 82 if (what && newargc + 1 < ARRAY_SIZE(newargv)) { 83 newargv[newargc] = strdup(what); 84 newargv[++newargc] = NULL; 85 return 1; 86 } else { 87 xtables_error(PARAMETER_PROBLEM, 88 "Parser cannot handle more arguments\n"); 89 return 0; 90 } 91} 92 93static void free_argv(void) { 94 int i; 95 96 for (i = 0; i < newargc; i++) 97 free(newargv[i]); 98} 99 100static void add_param_to_argv(char *parsestart) 101{ 102 int quote_open = 0, escaped = 0, param_len = 0; 103 char param_buffer[1024], *curchar; 104 105 /* After fighting with strtok enough, here's now 106 * a 'real' parser. According to Rusty I'm now no 107 * longer a real hacker, but I can live with that */ 108 109 for (curchar = parsestart; *curchar; curchar++) { 110 if (quote_open) { 111 if (escaped) { 112 param_buffer[param_len++] = *curchar; 113 escaped = 0; 114 continue; 115 } else if (*curchar == '\\') { 116 escaped = 1; 117 continue; 118 } else if (*curchar == '"') { 119 quote_open = 0; 120 *curchar = ' '; 121 } else { 122 param_buffer[param_len++] = *curchar; 123 continue; 124 } 125 } else { 126 if (*curchar == '"') { 127 quote_open = 1; 128 continue; 129 } 130 } 131 132 if (*curchar == ' ' 133 || *curchar == '\t' 134 || * curchar == '\n') { 135 if (!param_len) { 136 /* two spaces? */ 137 continue; 138 } 139 140 param_buffer[param_len] = '\0'; 141 142 /* check if table name specified */ 143 if (!strncmp(param_buffer, "-t", 2) 144 || !strncmp(param_buffer, "--table", 8)) { 145 xtables_error(PARAMETER_PROBLEM, 146 "The -t option (seen in line %u) cannot be " 147 "used in xtables-restore.\n", line); 148 exit(1); 149 } 150 151 add_argv(param_buffer); 152 param_len = 0; 153 } else { 154 /* regular character, copy to buffer */ 155 param_buffer[param_len++] = *curchar; 156 157 if (param_len >= sizeof(param_buffer)) 158 xtables_error(PARAMETER_PROBLEM, 159 "Parameter too long!"); 160 } 161 } 162} 163 164static struct nftnl_chain_list *get_chain_list(struct nft_handle *h) 165{ 166 struct nftnl_chain_list *chain_list; 167 168 chain_list = nft_chain_dump(h); 169 if (chain_list == NULL) 170 xtables_error(OTHER_PROBLEM, "cannot retrieve chain list\n"); 171 172 return chain_list; 173} 174 175static void chain_delete(struct nftnl_chain_list *clist, const char *curtable, 176 const char *chain) 177{ 178 struct nftnl_chain *chain_obj; 179 180 chain_obj = nft_chain_list_find(clist, curtable, chain); 181 /* This chain has been found, delete from list. Later 182 * on, unvisited chains will be purged out. 183 */ 184 if (chain_obj != NULL) 185 nftnl_chain_list_del(chain_obj); 186} 187 188struct nft_xt_restore_cb restore_cb = { 189 .chain_list = get_chain_list, 190 .commit = nft_commit, 191 .abort = nft_abort, 192 .chains_purge = nft_table_purge_chains, 193 .rule_flush = nft_rule_flush, 194 .chain_del = chain_delete, 195 .do_command = do_commandx, 196 .chain_set = nft_chain_set, 197 .chain_user_add = nft_chain_user_add, 198}; 199 200static const struct xtc_ops xtc_ops = { 201 .strerror = nft_strerror, 202}; 203 204void xtables_restore_parse(struct nft_handle *h, 205 struct nft_xt_restore_parse *p, 206 struct nft_xt_restore_cb *cb, 207 int argc, char *argv[]) 208{ 209 char buffer[10240]; 210 int in_table = 0; 211 char curtable[XT_TABLE_MAXNAMELEN + 1]; 212 const struct xtc_ops *ops = &xtc_ops; 213 struct nftnl_chain_list *chain_list = NULL; 214 215 line = 0; 216 217 if (cb->chain_list) 218 chain_list = cb->chain_list(h); 219 220 /* Grab standard input. */ 221 while (fgets(buffer, sizeof(buffer), p->in)) { 222 int ret = 0; 223 224 line++; 225 if (buffer[0] == '\n') 226 continue; 227 else if (buffer[0] == '#') { 228 if (verbose) 229 fputs(buffer, stdout); 230 continue; 231 } else if ((strcmp(buffer, "COMMIT\n") == 0) && (in_table)) { 232 if (!p->testing) { 233 /* Commit per table, although we support 234 * global commit at once, stick by now to 235 * the existing behaviour. 236 */ 237 DEBUGP("Calling commit\n"); 238 if (cb->commit) 239 ret = cb->commit(h); 240 } else { 241 DEBUGP("Not calling commit, testing\n"); 242 if (cb->abort) 243 ret = cb->abort(h); 244 } 245 in_table = 0; 246 247 /* Purge out unused chains in this table */ 248 if (!p->testing && cb->chains_purge) 249 cb->chains_purge(h, curtable, chain_list); 250 251 } else if ((buffer[0] == '*') && (!in_table)) { 252 /* New table */ 253 char *table; 254 255 table = strtok(buffer+1, " \t\n"); 256 DEBUGP("line %u, table '%s'\n", line, table); 257 if (!table) { 258 xtables_error(PARAMETER_PROBLEM, 259 "%s: line %u table name invalid\n", 260 xt_params->program_name, line); 261 exit(1); 262 } 263 strncpy(curtable, table, XT_TABLE_MAXNAMELEN); 264 curtable[XT_TABLE_MAXNAMELEN] = '\0'; 265 266 if (p->tablename && (strcmp(p->tablename, table) != 0)) 267 continue; 268 269 if (noflush == 0) { 270 DEBUGP("Cleaning all chains of table '%s'\n", 271 table); 272 if (cb->rule_flush) 273 cb->rule_flush(h, NULL, table); 274 } 275 276 ret = 1; 277 in_table = 1; 278 279 if (cb->table_new) 280 cb->table_new(h, table); 281 282 } else if ((buffer[0] == ':') && (in_table)) { 283 /* New chain. */ 284 char *policy, *chain = NULL; 285 struct xt_counters count = {}; 286 287 chain = strtok(buffer+1, " \t\n"); 288 DEBUGP("line %u, chain '%s'\n", line, chain); 289 if (!chain) { 290 xtables_error(PARAMETER_PROBLEM, 291 "%s: line %u chain name invalid\n", 292 xt_params->program_name, line); 293 exit(1); 294 } 295 296 if (cb->chain_del) 297 cb->chain_del(chain_list, curtable, chain); 298 299 if (strlen(chain) >= XT_EXTENSION_MAXNAMELEN) 300 xtables_error(PARAMETER_PROBLEM, 301 "Invalid chain name `%s' " 302 "(%u chars max)", 303 chain, XT_EXTENSION_MAXNAMELEN - 1); 304 305 policy = strtok(NULL, " \t\n"); 306 DEBUGP("line %u, policy '%s'\n", line, policy); 307 if (!policy) { 308 xtables_error(PARAMETER_PROBLEM, 309 "%s: line %u policy invalid\n", 310 xt_params->program_name, line); 311 exit(1); 312 } 313 314 if (strcmp(policy, "-") != 0) { 315 if (counters) { 316 char *ctrs; 317 ctrs = strtok(NULL, " \t\n"); 318 319 if (!ctrs || !parse_counters(ctrs, &count)) 320 xtables_error(PARAMETER_PROBLEM, 321 "invalid policy counters " 322 "for chain '%s'\n", chain); 323 324 } 325 if (cb->chain_set && 326 cb->chain_set(h, curtable, chain, policy, &count) < 0) { 327 xtables_error(OTHER_PROBLEM, 328 "Can't set policy `%s'" 329 " on `%s' line %u: %s\n", 330 policy, chain, line, 331 ops->strerror(errno)); 332 } 333 DEBUGP("Setting policy of chain %s to %s\n", 334 chain, policy); 335 ret = 1; 336 337 } else { 338 if (cb->chain_user_add && 339 cb->chain_user_add(h, chain, curtable) < 0) { 340 if (errno == EEXIST) 341 continue; 342 343 xtables_error(PARAMETER_PROBLEM, 344 "cannot create chain " 345 "'%s' (%s)\n", chain, 346 strerror(errno)); 347 } 348 continue; 349 } 350 351 } else if (in_table) { 352 int a; 353 char *ptr = buffer; 354 char *pcnt = NULL; 355 char *bcnt = NULL; 356 char *parsestart; 357 358 /* reset the newargv */ 359 newargc = 0; 360 361 if (buffer[0] == '[') { 362 /* we have counters in our input */ 363 ptr = strchr(buffer, ']'); 364 if (!ptr) 365 xtables_error(PARAMETER_PROBLEM, 366 "Bad line %u: need ]\n", 367 line); 368 369 pcnt = strtok(buffer+1, ":"); 370 if (!pcnt) 371 xtables_error(PARAMETER_PROBLEM, 372 "Bad line %u: need :\n", 373 line); 374 375 bcnt = strtok(NULL, "]"); 376 if (!bcnt) 377 xtables_error(PARAMETER_PROBLEM, 378 "Bad line %u: need ]\n", 379 line); 380 381 /* start command parsing after counter */ 382 parsestart = ptr + 1; 383 } else { 384 /* start command parsing at start of line */ 385 parsestart = buffer; 386 } 387 388 add_argv(argv[0]); 389 add_argv("-t"); 390 add_argv(curtable); 391 392 if (counters && pcnt && bcnt) { 393 add_argv("--set-counters"); 394 add_argv((char *) pcnt); 395 add_argv((char *) bcnt); 396 } 397 398 add_param_to_argv(parsestart); 399 400 DEBUGP("calling do_command4(%u, argv, &%s, handle):\n", 401 newargc, curtable); 402 403 for (a = 0; a < newargc; a++) 404 DEBUGP("argv[%u]: %s\n", a, newargv[a]); 405 406 ret = cb->do_command(h, newargc, newargv, 407 &newargv[2], true); 408 if (ret < 0) { 409 if (cb->abort) 410 ret = cb->abort(h); 411 else 412 ret = 0; 413 414 if (ret < 0) { 415 fprintf(stderr, "failed to abort " 416 "commit operation\n"); 417 } 418 exit(1); 419 } 420 421 free_argv(); 422 fflush(stdout); 423 } 424 if (p->tablename && (strcmp(p->tablename, curtable) != 0)) 425 continue; 426 if (!ret) { 427 fprintf(stderr, "%s: line %u failed\n", 428 xt_params->program_name, line); 429 exit(1); 430 } 431 } 432 if (in_table) { 433 fprintf(stderr, "%s: COMMIT expected at line %u\n", 434 xt_params->program_name, line + 1); 435 exit(1); 436 } 437} 438 439static int 440xtables_restore_main(int family, const char *progname, int argc, char *argv[]) 441{ 442 struct nft_handle h = { 443 .family = family, 444 .restore = true, 445 }; 446 int c; 447 struct nft_xt_restore_parse p = {}; 448 449 line = 0; 450 451 xtables_globals.program_name = progname; 452 c = xtables_init_all(&xtables_globals, family); 453 if (c < 0) { 454 fprintf(stderr, "%s/%s Failed to initialize xtables\n", 455 xtables_globals.program_name, 456 xtables_globals.program_version); 457 exit(1); 458 } 459#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) 460 init_extensions(); 461 init_extensions4(); 462#endif 463 464 if (nft_init(&h, xtables_ipv4) < 0) { 465 fprintf(stderr, "%s/%s Failed to initialize nft: %s\n", 466 xtables_globals.program_name, 467 xtables_globals.program_version, 468 strerror(errno)); 469 exit(EXIT_FAILURE); 470 } 471 472 while ((c = getopt_long(argc, argv, "bcvthnM:T:46", options, NULL)) != -1) { 473 switch (c) { 474 case 'b': 475 fprintf(stderr, "-b/--binary option is not implemented\n"); 476 break; 477 case 'c': 478 counters = 1; 479 break; 480 case 'v': 481 verbose = 1; 482 break; 483 case 't': 484 p.testing = 1; 485 break; 486 case 'h': 487 print_usage("xtables-restore", 488 IPTABLES_VERSION); 489 break; 490 case 'n': 491 noflush = 1; 492 break; 493 case 'M': 494 xtables_modprobe_program = optarg; 495 break; 496 case 'T': 497 p.tablename = optarg; 498 break; 499 case '4': 500 h.family = AF_INET; 501 break; 502 case '6': 503 h.family = AF_INET6; 504 xtables_set_nfproto(AF_INET6); 505 break; 506 } 507 } 508 509 if (optind == argc - 1) { 510 p.in = fopen(argv[optind], "re"); 511 if (!p.in) { 512 fprintf(stderr, "Can't open %s: %s\n", argv[optind], 513 strerror(errno)); 514 exit(1); 515 } 516 } else if (optind < argc) { 517 fprintf(stderr, "Unknown arguments found on commandline\n"); 518 exit(1); 519 } else { 520 p.in = stdin; 521 } 522 523 xtables_restore_parse(&h, &p, &restore_cb, argc, argv); 524 525 fclose(p.in); 526 return 0; 527} 528 529int xtables_ip4_restore_main(int argc, char *argv[]) 530{ 531 return xtables_restore_main(NFPROTO_IPV4, "iptables-restore", 532 argc, argv); 533} 534 535int xtables_ip6_restore_main(int argc, char *argv[]) 536{ 537 return xtables_restore_main(NFPROTO_IPV6, "ip6tables-restore", 538 argc, argv); 539} 540