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 "xshared.h"
16#include "xtables.h"
17#include "libiptc/libiptc.h"
18#include "iptables-multi.h"
19
20#ifdef DEBUG
21#define DEBUGP(x, args...) fprintf(stderr, x, ## args)
22#else
23#define DEBUGP(x, args...)
24#endif
25
26static int counters = 0, verbose = 0, noflush = 0, wait = 0;
27
28static struct timeval wait_interval = {
29	.tv_sec	= 1,
30};
31
32/* Keeping track of external matches and targets.  */
33static const struct option options[] = {
34	{.name = "counters",      .has_arg = 0, .val = 'c'},
35	{.name = "verbose",       .has_arg = 0, .val = 'v'},
36	{.name = "test",          .has_arg = 0, .val = 't'},
37	{.name = "help",          .has_arg = 0, .val = 'h'},
38	{.name = "noflush",       .has_arg = 0, .val = 'n'},
39	{.name = "modprobe",      .has_arg = 1, .val = 'M'},
40	{.name = "table",         .has_arg = 1, .val = 'T'},
41	{.name = "wait",          .has_arg = 2, .val = 'w'},
42	{.name = "wait-interval", .has_arg = 2, .val = 'W'},
43	{NULL},
44};
45
46static void print_usage(const char *name, const char *version) __attribute__((noreturn));
47
48#define prog_name iptables_globals.program_name
49
50static void print_usage(const char *name, const char *version)
51{
52	fprintf(stderr, "Usage: %s [-c] [-v] [-t] [-h] [-n] [-w secs] [-W usecs] [-T table] [-M command]\n"
53			"	   [ --counters ]\n"
54			"	   [ --verbose ]\n"
55			"	   [ --test ]\n"
56			"	   [ --help ]\n"
57			"	   [ --noflush ]\n"
58			"	   [ --wait=<seconds>\n"
59			"	   [ --wait-interval=<usecs>\n"
60			"	   [ --table=<TABLE> ]\n"
61			"	   [ --modprobe=<command> ]\n", name);
62
63	exit(1);
64}
65
66static struct xtc_handle *create_handle(const char *tablename)
67{
68	struct xtc_handle *handle;
69
70	handle = iptc_init(tablename);
71
72	if (!handle) {
73		/* try to insmod the module if iptc_init failed */
74		xtables_load_ko(xtables_modprobe_program, false);
75		handle = iptc_init(tablename);
76	}
77
78	if (!handle) {
79		xtables_error(PARAMETER_PROBLEM, "%s: unable to initialize "
80			"table '%s'\n", prog_name, tablename);
81		exit(1);
82	}
83	return handle;
84}
85
86static int parse_counters(char *string, struct xt_counters *ctr)
87{
88	unsigned long long pcnt, bcnt;
89	int ret;
90
91	ret = sscanf(string, "[%llu:%llu]", &pcnt, &bcnt);
92	ctr->pcnt = pcnt;
93	ctr->bcnt = bcnt;
94	return ret == 2;
95}
96
97/* global new argv and argc */
98static char *newargv[255];
99static int newargc;
100
101/* function adding one argument to newargv, updating newargc
102 * returns true if argument added, false otherwise */
103static int add_argv(char *what) {
104	DEBUGP("add_argv: %s\n", what);
105	if (what && newargc + 1 < ARRAY_SIZE(newargv)) {
106		newargv[newargc] = strdup(what);
107		newargv[++newargc] = NULL;
108		return 1;
109	} else {
110		xtables_error(PARAMETER_PROBLEM,
111			"Parser cannot handle more arguments\n");
112		return 0;
113	}
114}
115
116static void free_argv(void) {
117	int i;
118
119	for (i = 0; i < newargc; i++)
120		free(newargv[i]);
121}
122
123static void add_param_to_argv(char *parsestart)
124{
125	int quote_open = 0, escaped = 0, param_len = 0;
126	char param_buffer[1024], *curchar;
127
128	/* After fighting with strtok enough, here's now
129	 * a 'real' parser. According to Rusty I'm now no
130	 * longer a real hacker, but I can live with that */
131
132	for (curchar = parsestart; *curchar; curchar++) {
133		if (quote_open) {
134			if (escaped) {
135				param_buffer[param_len++] = *curchar;
136				escaped = 0;
137				continue;
138			} else if (*curchar == '\\') {
139				escaped = 1;
140				continue;
141			} else if (*curchar == '"') {
142				quote_open = 0;
143				*curchar = ' ';
144			} else {
145				param_buffer[param_len++] = *curchar;
146				continue;
147			}
148		} else {
149			if (*curchar == '"') {
150				quote_open = 1;
151				continue;
152			}
153		}
154
155		if (*curchar == ' '
156		    || *curchar == '\t'
157		    || * curchar == '\n') {
158			if (!param_len) {
159				/* two spaces? */
160				continue;
161			}
162
163			param_buffer[param_len] = '\0';
164
165			/* check if table name specified */
166			if (!strncmp(param_buffer, "-t", 2)
167			    || !strncmp(param_buffer, "--table", 8)) {
168				xtables_error(PARAMETER_PROBLEM,
169				"The -t option (seen in line %u) cannot be "
170				"used in iptables-restore.\n", line);
171				exit(1);
172			}
173
174			add_argv(param_buffer);
175			param_len = 0;
176		} else {
177			/* regular character, copy to buffer */
178			param_buffer[param_len++] = *curchar;
179
180			if (param_len >= sizeof(param_buffer))
181				xtables_error(PARAMETER_PROBLEM,
182				   "Parameter too long!");
183		}
184	}
185}
186
187int
188iptables_restore_main(int argc, char *argv[])
189{
190	struct xtc_handle *handle = NULL;
191	char buffer[10240];
192	int c, lock;
193	char curtable[XT_TABLE_MAXNAMELEN + 1];
194	FILE *in;
195	int in_table = 0, testing = 0;
196	const char *tablename = NULL;
197	const struct xtc_ops *ops = &iptc_ops;
198
199	line = 0;
200	lock = XT_LOCK_NOT_ACQUIRED;
201
202	iptables_globals.program_name = "iptables-restore";
203	c = xtables_init_all(&iptables_globals, NFPROTO_IPV4);
204	if (c < 0) {
205		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
206				iptables_globals.program_name,
207				iptables_globals.program_version);
208		exit(1);
209	}
210#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
211	init_extensions();
212	init_extensions4();
213#endif
214
215	while ((c = getopt_long(argc, argv, "bcvthnwWM:T:", options, NULL)) != -1) {
216		switch (c) {
217			case 'b':
218				fprintf(stderr, "-b/--binary option is not implemented\n");
219				break;
220			case 'c':
221				counters = 1;
222				break;
223			case 'v':
224				verbose = 1;
225				break;
226			case 't':
227				testing = 1;
228				break;
229			case 'h':
230				print_usage("iptables-restore",
231					    IPTABLES_VERSION);
232				break;
233			case 'n':
234				noflush = 1;
235				break;
236			case 'w':
237				wait = parse_wait_time(argc, argv);
238				break;
239			case 'W':
240				parse_wait_interval(argc, argv, &wait_interval);
241				break;
242			case 'M':
243				xtables_modprobe_program = optarg;
244				break;
245			case 'T':
246				tablename = optarg;
247				break;
248		}
249	}
250
251	if (optind == argc - 1) {
252		in = fopen(argv[optind], "re");
253		if (!in) {
254			fprintf(stderr, "Can't open %s: %s\n", argv[optind],
255				strerror(errno));
256			exit(1);
257		}
258	}
259	else if (optind < argc) {
260		fprintf(stderr, "Unknown arguments found on commandline\n");
261		exit(1);
262	}
263	else in = stdin;
264
265	/* Grab standard input. */
266	while (fgets(buffer, sizeof(buffer), in)) {
267		int ret = 0;
268
269		line++;
270		if (buffer[0] == '\n')
271			continue;
272		else if (buffer[0] == '#') {
273			if (verbose) {
274				fputs(buffer, stdout);
275				fflush(stdout);
276			}
277			continue;
278		} else if ((strcmp(buffer, "COMMIT\n") == 0) && (in_table)) {
279			if (!testing) {
280				DEBUGP("Calling commit\n");
281				ret = ops->commit(handle);
282				ops->free(handle);
283				handle = NULL;
284			} else {
285				DEBUGP("Not calling commit, testing\n");
286				ret = 1;
287			}
288
289			/* Done with the current table, release the lock. */
290			if (lock >= 0) {
291				xtables_unlock(lock);
292				lock = XT_LOCK_NOT_ACQUIRED;
293			}
294
295			in_table = 0;
296		} else if ((buffer[0] == '*') && (!in_table)) {
297			/* Acquire a lock before we create a new table handle */
298			lock = xtables_lock_or_exit(wait, &wait_interval);
299
300			/* New table */
301			char *table;
302
303			table = strtok(buffer+1, " \t\n");
304			DEBUGP("line %u, table '%s'\n", line, table);
305			if (!table) {
306				xtables_error(PARAMETER_PROBLEM,
307					"%s: line %u table name invalid\n",
308					xt_params->program_name, line);
309				exit(1);
310			}
311			strncpy(curtable, table, XT_TABLE_MAXNAMELEN);
312			curtable[XT_TABLE_MAXNAMELEN] = '\0';
313
314			if (tablename && (strcmp(tablename, table) != 0))
315				continue;
316			if (handle)
317				ops->free(handle);
318
319			handle = create_handle(table);
320			if (noflush == 0) {
321				DEBUGP("Cleaning all chains of table '%s'\n",
322					table);
323				for_each_chain4(flush_entries4, verbose, 1,
324						handle);
325
326				DEBUGP("Deleting all user-defined chains "
327				       "of table '%s'\n", table);
328				for_each_chain4(delete_chain4, verbose, 0,
329						handle);
330			}
331
332			ret = 1;
333			in_table = 1;
334
335		} else if ((buffer[0] == ':') && (in_table)) {
336			/* New chain. */
337			char *policy, *chain;
338
339			chain = strtok(buffer+1, " \t\n");
340			DEBUGP("line %u, chain '%s'\n", line, chain);
341			if (!chain) {
342				xtables_error(PARAMETER_PROBLEM,
343					   "%s: line %u chain name invalid\n",
344					   xt_params->program_name, line);
345				exit(1);
346			}
347
348			if (strlen(chain) >= XT_EXTENSION_MAXNAMELEN)
349				xtables_error(PARAMETER_PROBLEM,
350					   "Invalid chain name `%s' "
351					   "(%u chars max)",
352					   chain, XT_EXTENSION_MAXNAMELEN - 1);
353
354			if (ops->builtin(chain, handle) <= 0) {
355				if (noflush && ops->is_chain(chain, handle)) {
356					DEBUGP("Flushing existing user defined chain '%s'\n", chain);
357					if (!ops->flush_entries(chain, handle))
358						xtables_error(PARAMETER_PROBLEM,
359							   "error flushing chain "
360							   "'%s':%s\n", chain,
361							   strerror(errno));
362				} else {
363					DEBUGP("Creating new chain '%s'\n", chain);
364					if (!ops->create_chain(chain, handle))
365						xtables_error(PARAMETER_PROBLEM,
366							   "error creating chain "
367							   "'%s':%s\n", chain,
368							   strerror(errno));
369				}
370			}
371
372			policy = strtok(NULL, " \t\n");
373			DEBUGP("line %u, policy '%s'\n", line, policy);
374			if (!policy) {
375				xtables_error(PARAMETER_PROBLEM,
376					   "%s: line %u policy invalid\n",
377					   xt_params->program_name, line);
378				exit(1);
379			}
380
381			if (strcmp(policy, "-") != 0) {
382				struct xt_counters count;
383
384				if (counters) {
385					char *ctrs;
386					ctrs = strtok(NULL, " \t\n");
387
388					if (!ctrs || !parse_counters(ctrs, &count))
389						xtables_error(PARAMETER_PROBLEM,
390							   "invalid policy counters "
391							   "for chain '%s'\n", chain);
392
393				} else {
394					memset(&count, 0, sizeof(count));
395				}
396
397				DEBUGP("Setting policy of chain %s to %s\n",
398					chain, policy);
399
400				if (!ops->set_policy(chain, policy, &count,
401						     handle))
402					xtables_error(OTHER_PROBLEM,
403						"Can't set policy `%s'"
404						" on `%s' line %u: %s\n",
405						policy, chain, line,
406						ops->strerror(errno));
407			}
408
409			ret = 1;
410
411		} else if (in_table) {
412			int a;
413			char *ptr = buffer;
414			char *pcnt = NULL;
415			char *bcnt = NULL;
416			char *parsestart;
417
418			/* reset the newargv */
419			newargc = 0;
420
421			if (buffer[0] == '[') {
422				/* we have counters in our input */
423				ptr = strchr(buffer, ']');
424				if (!ptr)
425					xtables_error(PARAMETER_PROBLEM,
426						   "Bad line %u: need ]\n",
427						   line);
428
429				pcnt = strtok(buffer+1, ":");
430				if (!pcnt)
431					xtables_error(PARAMETER_PROBLEM,
432						   "Bad line %u: need :\n",
433						   line);
434
435				bcnt = strtok(NULL, "]");
436				if (!bcnt)
437					xtables_error(PARAMETER_PROBLEM,
438						   "Bad line %u: need ]\n",
439						   line);
440
441				/* start command parsing after counter */
442				parsestart = ptr + 1;
443			} else {
444				/* start command parsing at start of line */
445				parsestart = buffer;
446			}
447
448			add_argv(argv[0]);
449			add_argv("-t");
450			add_argv(curtable);
451
452			if (counters && pcnt && bcnt) {
453				add_argv("--set-counters");
454				add_argv((char *) pcnt);
455				add_argv((char *) bcnt);
456			}
457
458			add_param_to_argv(parsestart);
459
460			DEBUGP("calling do_command4(%u, argv, &%s, handle):\n",
461				newargc, curtable);
462
463			for (a = 0; a < newargc; a++)
464				DEBUGP("argv[%u]: %s\n", a, newargv[a]);
465
466			ret = do_command4(newargc, newargv,
467					 &newargv[2], &handle, true);
468
469			free_argv();
470			fflush(stdout);
471		}
472		if (tablename && (strcmp(tablename, curtable) != 0))
473			continue;
474		if (!ret) {
475			fprintf(stderr, "%s: line %u failed\n",
476					xt_params->program_name, line);
477			exit(1);
478		}
479	}
480	if (in_table) {
481		fprintf(stderr, "%s: COMMIT expected at line %u\n",
482				xt_params->program_name, line + 1);
483		exit(1);
484	}
485
486	fclose(in);
487	return 0;
488}
489