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