• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
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 
print_usage(const char * name,const char * version)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 
parse_counters(char * string,struct xt_counters * ctr)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 */
add_argv(char * what)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 
free_argv(void)93 static void free_argv(void) {
94 	int i;
95 
96 	for (i = 0; i < newargc; i++)
97 		free(newargv[i]);
98 }
99 
add_param_to_argv(char * parsestart)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 
get_chain_list(struct nft_handle * h)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 
chain_delete(struct nftnl_chain_list * clist,const char * curtable,const char * chain)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 
xtables_restore_parse(struct nft_handle * h,struct nft_xt_restore_parse * p,struct nft_xt_restore_cb * cb,int argc,char * argv[])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
xtables_restore_main(int family,const char * progname,int argc,char * argv[])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 
xtables_ip4_restore_main(int argc,char * argv[])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 
xtables_ip6_restore_main(int argc,char * argv[])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