Home | History | Annotate | Download | only in iptables
      1 /* Code to restore the iptables state, from file by iptables-save.
      2  * (C) 2000-2002 by Harald Welte <laforge (at) gnumonks.org>
      3  * based on previous code from Rusty Russell <rusty (at) 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 
     27 static int counters = 0, verbose = 0, noflush = 0;
     28 
     29 /* Keeping track of external matches and targets.  */
     30 static 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 
     43 static void print_usage(const char *name, const char *version) __attribute__((noreturn));
     44 
     45 #define prog_name xtables_globals.program_name
     46 
     47 static 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 
     63 static 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 */
     75 static char *newargv[255];
     76 static int newargc;
     77 
     78 /* function adding one argument to newargv, updating newargc
     79  * returns true if argument added, false otherwise */
     80 static 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 
     93 static void free_argv(void) {
     94 	int i;
     95 
     96 	for (i = 0; i < newargc; i++)
     97 		free(newargv[i]);
     98 }
     99 
    100 static 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 
    164 static 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 
    175 static 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 
    188 struct 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 
    200 static const struct xtc_ops xtc_ops = {
    201 	.strerror	= nft_strerror,
    202 };
    203 
    204 void 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 
    439 static int
    440 xtables_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 
    529 int xtables_ip4_restore_main(int argc, char *argv[])
    530 {
    531 	return xtables_restore_main(NFPROTO_IPV4, "iptables-restore",
    532 				    argc, argv);
    533 }
    534 
    535 int xtables_ip6_restore_main(int argc, char *argv[])
    536 {
    537 	return xtables_restore_main(NFPROTO_IPV6, "ip6tables-restore",
    538 				    argc, argv);
    539 }
    540