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