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