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 #include "config.h"
8 #include <getopt.h>
9 #include <errno.h>
10 #include <libgen.h>
11 #include <stdbool.h>
12 #include <string.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include "iptables.h"
16 #include "xtables.h"
17 #include "libiptc/libiptc.h"
18 #include "xtables-multi.h"
19 #include "nft.h"
20 #include "nft-bridge.h"
21 #include "nft-cache.h"
22 #include <libnftnl/chain.h>
23
24 static int counters, verbose;
25
26 /* Keeping track of external matches and targets. */
27 static const struct option options[] = {
28 {.name = "counters", .has_arg = false, .val = 'c'},
29 {.name = "verbose", .has_arg = false, .val = 'v'},
30 {.name = "version", .has_arg = 0, .val = 'V'},
31 {.name = "test", .has_arg = false, .val = 't'},
32 {.name = "help", .has_arg = false, .val = 'h'},
33 {.name = "noflush", .has_arg = false, .val = 'n'},
34 {.name = "modprobe", .has_arg = true, .val = 'M'},
35 {.name = "table", .has_arg = true, .val = 'T'},
36 {.name = "ipv4", .has_arg = false, .val = '4'},
37 {.name = "ipv6", .has_arg = false, .val = '6'},
38 {.name = "wait", .has_arg = 2, .val = 'w'},
39 {.name = "wait-interval", .has_arg = 2, .val = 'W'},
40 {NULL},
41 };
42
43 #define prog_name xtables_globals.program_name
44 #define prog_vers xtables_globals.program_version
45
print_usage(const char * name,const char * version)46 static void print_usage(const char *name, const char *version)
47 {
48 fprintf(stderr, "Usage: %s [-c] [-v] [-V] [-t] [-h] [-n] [-T table] [-M command] [-4] [-6] [file]\n"
49 " [ --counters ]\n"
50 " [ --verbose ]\n"
51 " [ --version]\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
61 static const struct nft_xt_restore_cb restore_cb = {
62 .commit = nft_commit,
63 .abort = nft_abort,
64 .table_new = nft_table_new,
65 .table_flush = nft_table_flush,
66 .do_command = do_commandx,
67 .chain_set = nft_chain_set,
68 .chain_restore = nft_chain_restore,
69 };
70
71 struct nft_xt_restore_state {
72 const struct builtin_table *curtable;
73 struct argv_store av_store;
74 bool in_table;
75 };
76
xtables_restore_parse_line(struct nft_handle * h,const struct nft_xt_restore_parse * p,struct nft_xt_restore_state * state,char * buffer)77 static void xtables_restore_parse_line(struct nft_handle *h,
78 const struct nft_xt_restore_parse *p,
79 struct nft_xt_restore_state *state,
80 char *buffer)
81 {
82 const struct nft_xt_restore_cb *cb = p->cb;
83 int ret = 0;
84
85 if (buffer[0] == '\n')
86 return;
87 else if (buffer[0] == '#') {
88 if (verbose) {
89 fputs(buffer, stdout);
90 fflush(stdout);
91 }
92 return;
93 } else if (state->in_table &&
94 (strncmp(buffer, "COMMIT", 6) == 0) &&
95 (buffer[6] == '\0' || buffer[6] == '\n')) {
96 if (!p->testing) {
97 /* Commit per table, although we support
98 * global commit at once, stick by now to
99 * the existing behaviour.
100 */
101 DEBUGP("Calling commit\n");
102 if (cb->commit)
103 ret = cb->commit(h);
104 } else {
105 DEBUGP("Not calling commit, testing\n");
106 if (cb->abort)
107 ret = cb->abort(h);
108 }
109 state->in_table = false;
110
111 } else if ((buffer[0] == '*') && (!state->in_table || !p->commit)) {
112 /* New table */
113 char *table;
114
115 table = strtok(buffer+1, " \t\n");
116 DEBUGP("line %u, table '%s'\n", line, table);
117 if (!table)
118 xtables_error(PARAMETER_PROBLEM,
119 "%s: line %u table name invalid\n",
120 xt_params->program_name, line);
121
122 state->curtable = nft_table_builtin_find(h, table);
123 if (!state->curtable)
124 xtables_error(PARAMETER_PROBLEM,
125 "%s: line %u table name '%s' invalid\n",
126 xt_params->program_name, line, table);
127
128 if (p->tablename && (strcmp(p->tablename, table) != 0))
129 return;
130
131 if (h->noflush == 0) {
132 DEBUGP("Cleaning all chains of table '%s'\n", table);
133 if (cb->table_flush)
134 cb->table_flush(h, table);
135 }
136
137 ret = 1;
138 state->in_table = true;
139
140 if (cb->table_new)
141 cb->table_new(h, table);
142
143 } else if ((buffer[0] == ':') && state->in_table) {
144 /* New chain. */
145 char *policy, *chain = NULL;
146 struct xt_counters count = {};
147
148 chain = strtok(buffer+1, " \t\n");
149 DEBUGP("line %u, chain '%s'\n", line, chain);
150 if (!chain)
151 xtables_error(PARAMETER_PROBLEM,
152 "%s: line %u chain name invalid\n",
153 xt_params->program_name, line);
154
155 if (strlen(chain) >= XT_EXTENSION_MAXNAMELEN)
156 xtables_error(PARAMETER_PROBLEM,
157 "Invalid chain name `%s' (%u chars max)",
158 chain, XT_EXTENSION_MAXNAMELEN - 1);
159
160 policy = strtok(NULL, " \t\n");
161 DEBUGP("line %u, policy '%s'\n", line, policy);
162 if (!policy)
163 xtables_error(PARAMETER_PROBLEM,
164 "%s: line %u policy invalid\n",
165 xt_params->program_name, line);
166
167 if (nft_chain_builtin_find(state->curtable, chain)) {
168 if (counters) {
169 char *ctrs;
170 ctrs = strtok(NULL, " \t\n");
171
172 if (!ctrs || !parse_counters(ctrs, &count))
173 xtables_error(PARAMETER_PROBLEM,
174 "invalid policy counters for chain '%s'\n",
175 chain);
176
177 }
178 if (cb->chain_set &&
179 cb->chain_set(h, state->curtable->name,
180 chain, policy, &count) < 0) {
181 xtables_error(OTHER_PROBLEM,
182 "Can't set policy `%s' on `%s' line %u: %s\n",
183 policy, chain, line,
184 strerror(errno));
185 }
186 DEBUGP("Setting policy of chain %s to %s\n",
187 chain, policy);
188 } else if (cb->chain_restore(h, chain, state->curtable->name) < 0 &&
189 errno != EEXIST) {
190 xtables_error(PARAMETER_PROBLEM,
191 "cannot create chain '%s' (%s)\n",
192 chain, strerror(errno));
193 } else if (h->family == NFPROTO_BRIDGE &&
194 !ebt_set_user_chain_policy(h, state->curtable->name,
195 chain, policy)) {
196 xtables_error(OTHER_PROBLEM,
197 "Can't set policy `%s' on `%s' line %u: %s\n",
198 policy, chain, line,
199 strerror(errno));
200 }
201 ret = 1;
202 } else if (state->in_table) {
203 char *pcnt = NULL;
204 char *bcnt = NULL;
205 char *parsestart = buffer;
206
207 add_argv(&state->av_store, xt_params->program_name, 0);
208 add_argv(&state->av_store, "-t", 0);
209 add_argv(&state->av_store, state->curtable->name, 0);
210
211 tokenize_rule_counters(&parsestart, &pcnt, &bcnt, line);
212 if (counters && pcnt && bcnt) {
213 add_argv(&state->av_store, "--set-counters", 0);
214 add_argv(&state->av_store, pcnt, 0);
215 add_argv(&state->av_store, bcnt, 0);
216 }
217
218 add_param_to_argv(&state->av_store, parsestart, line);
219
220 DEBUGP("calling do_command4(%u, argv, &%s, handle):\n",
221 state->av_store.argc, state->curtable->name);
222 debug_print_argv(&state->av_store);
223
224 ret = cb->do_command(h, state->av_store.argc,
225 state->av_store.argv,
226 &state->av_store.argv[2], true);
227 if (ret < 0) {
228 if (cb->abort)
229 ret = cb->abort(h);
230 else
231 ret = 0;
232
233 if (ret < 0) {
234 fprintf(stderr,
235 "failed to abort commit operation\n");
236 }
237 exit(1);
238 }
239
240 free_argv(&state->av_store);
241 fflush(stdout);
242 }
243 if (p->tablename && state->curtable &&
244 (strcmp(p->tablename, state->curtable->name) != 0))
245 return;
246 if (!ret) {
247 fprintf(stderr, "%s: line %u failed\n",
248 xt_params->program_name, line);
249 exit(1);
250 }
251 }
252
253 /* Return true if given iptables-restore line will require a full cache.
254 * Typically these are commands referring to an existing rule
255 * (either by number or content) or commands listing the ruleset. */
cmd_needs_full_cache(char * cmd)256 static bool cmd_needs_full_cache(char *cmd)
257 {
258 char c, chain[32];
259 int rulenum, mcount;
260
261 mcount = sscanf(cmd, "-%c %31s %d", &c, chain, &rulenum);
262
263 if (mcount == 3)
264 return true;
265 if (mcount < 1)
266 return false;
267
268 switch (c) {
269 case 'D':
270 case 'C':
271 case 'S':
272 case 'L':
273 case 'Z':
274 return true;
275 }
276
277 return false;
278 }
279
280 #define PREBUFSIZ 65536
281
xtables_restore_parse(struct nft_handle * h,const struct nft_xt_restore_parse * p)282 void xtables_restore_parse(struct nft_handle *h,
283 const struct nft_xt_restore_parse *p)
284 {
285 struct nft_xt_restore_state state = {};
286 char preload_buffer[PREBUFSIZ] = {}, buffer[10240], *ptr;
287
288 if (!h->noflush) {
289 nft_fake_cache(h);
290 } else {
291 ssize_t pblen = sizeof(preload_buffer);
292 bool do_cache = false;
293
294 ptr = preload_buffer;
295 while (fgets(buffer, sizeof(buffer), p->in)) {
296 size_t blen = strlen(buffer);
297
298 /* drop trailing newline; xtables_restore_parse_line()
299 * uses strtok() which replaces them by nul-characters,
300 * causing unpredictable string delimiting in
301 * preload_buffer */
302 if (buffer[blen - 1] == '\n')
303 buffer[blen - 1] = '\0';
304 else
305 blen++;
306
307 pblen -= blen;
308 if (pblen <= 0) {
309 /* buffer exhausted */
310 do_cache = true;
311 break;
312 }
313
314 if (cmd_needs_full_cache(buffer)) {
315 do_cache = true;
316 break;
317 }
318
319 /* copy string including terminating nul-char */
320 memcpy(ptr, buffer, blen);
321 ptr += blen;
322 buffer[0] = '\0';
323 }
324
325 if (do_cache)
326 nft_build_cache(h, NULL);
327 }
328
329 line = 0;
330 ptr = preload_buffer;
331 while (*ptr) {
332 h->error.lineno = ++line;
333 DEBUGP("%s: buffered line %d: '%s'\n", __func__, line, ptr);
334 xtables_restore_parse_line(h, p, &state, ptr);
335 ptr += strlen(ptr) + 1;
336 }
337 if (*buffer) {
338 h->error.lineno = ++line;
339 DEBUGP("%s: overrun line %d: '%s'\n", __func__, line, buffer);
340 xtables_restore_parse_line(h, p, &state, buffer);
341 }
342 while (fgets(buffer, sizeof(buffer), p->in)) {
343 h->error.lineno = ++line;
344 DEBUGP("%s: input line %d: '%s'\n", __func__, line, buffer);
345 xtables_restore_parse_line(h, p, &state, buffer);
346 }
347 if (state.in_table && p->commit) {
348 fprintf(stderr, "%s: COMMIT expected at line %u\n",
349 xt_params->program_name, line + 1);
350 exit(1);
351 } else if (state.in_table && p->cb->commit && !p->cb->commit(h)) {
352 xtables_error(OTHER_PROBLEM, "%s: final implicit COMMIT failed",
353 xt_params->program_name);
354 }
355 }
356
357 static int
xtables_restore_main(int family,const char * progname,int argc,char * argv[])358 xtables_restore_main(int family, const char *progname, int argc, char *argv[])
359 {
360 const struct builtin_table *tables;
361 struct nft_handle h = {
362 .family = family,
363 .restore = true,
364 };
365 int c;
366 struct nft_xt_restore_parse p = {
367 .commit = true,
368 .cb = &restore_cb,
369 };
370
371 line = 0;
372
373 xtables_globals.program_name = progname;
374 c = xtables_init_all(&xtables_globals, family);
375 if (c < 0) {
376 fprintf(stderr, "%s/%s Failed to initialize xtables\n",
377 xtables_globals.program_name,
378 xtables_globals.program_version);
379 exit(1);
380 }
381
382 while ((c = getopt_long(argc, argv, "bcvVthnM:T:46wW", options, NULL)) != -1) {
383 switch (c) {
384 case 'b':
385 fprintf(stderr, "-b/--binary option is not implemented\n");
386 break;
387 case 'c':
388 counters = 1;
389 break;
390 case 'v':
391 verbose = 1;
392 break;
393 case 'V':
394 printf("%s v%s (nf_tables)\n", prog_name, prog_vers);
395 exit(0);
396 case 't':
397 p.testing = 1;
398 break;
399 case 'h':
400 print_usage(prog_name, PACKAGE_VERSION);
401 exit(0);
402 case 'n':
403 h.noflush = 1;
404 break;
405 case 'M':
406 xtables_modprobe_program = optarg;
407 break;
408 case 'T':
409 p.tablename = optarg;
410 break;
411 case '4':
412 h.family = AF_INET;
413 break;
414 case '6':
415 h.family = AF_INET6;
416 xtables_set_nfproto(AF_INET6);
417 break;
418 case 'w': /* fallthrough. Ignored by xt-restore */
419 case 'W':
420 if (!optarg && xs_has_arg(argc, argv))
421 optind++;
422 break;
423 default:
424 fprintf(stderr,
425 "Try `%s -h' for more information.\n",
426 prog_name);
427 exit(1);
428 }
429 }
430
431 if (optind == argc - 1) {
432 p.in = fopen(argv[optind], "re");
433 if (!p.in) {
434 fprintf(stderr, "Can't open %s: %s\n", argv[optind],
435 strerror(errno));
436 exit(1);
437 }
438 } else if (optind < argc) {
439 fprintf(stderr, "Unknown arguments found on commandline\n");
440 exit(1);
441 } else {
442 p.in = stdin;
443 }
444
445 switch (family) {
446 case NFPROTO_IPV4:
447 case NFPROTO_IPV6: /* fallthough, same table */
448 tables = xtables_ipv4;
449 #if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
450 init_extensions();
451 init_extensions4();
452 #endif
453 break;
454 case NFPROTO_ARP:
455 tables = xtables_arp;
456 break;
457 case NFPROTO_BRIDGE:
458 tables = xtables_bridge;
459 break;
460 default:
461 fprintf(stderr, "Unknown family %d\n", family);
462 return 1;
463 }
464
465 if (nft_init(&h, tables) < 0) {
466 fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
467 xtables_globals.program_name,
468 xtables_globals.program_version,
469 strerror(errno));
470 exit(EXIT_FAILURE);
471 }
472
473 xtables_restore_parse(&h, &p);
474
475 nft_fini(&h);
476 fclose(p.in);
477 return 0;
478 }
479
xtables_ip4_restore_main(int argc,char * argv[])480 int xtables_ip4_restore_main(int argc, char *argv[])
481 {
482 return xtables_restore_main(NFPROTO_IPV4, basename(*argv),
483 argc, argv);
484 }
485
xtables_ip6_restore_main(int argc,char * argv[])486 int xtables_ip6_restore_main(int argc, char *argv[])
487 {
488 return xtables_restore_main(NFPROTO_IPV6, basename(*argv),
489 argc, argv);
490 }
491
ebt_table_flush(struct nft_handle * h,const char * table)492 static int ebt_table_flush(struct nft_handle *h, const char *table)
493 {
494 /* drop any pending policy rule add/removal jobs */
495 nft_abort_policy_rule(h, table);
496 return nft_table_flush(h, table);
497 }
498
499 static const struct nft_xt_restore_cb ebt_restore_cb = {
500 .commit = nft_bridge_commit,
501 .table_new = nft_table_new,
502 .table_flush = ebt_table_flush,
503 .do_command = do_commandeb,
504 .chain_set = nft_chain_set,
505 .chain_restore = nft_chain_restore,
506 };
507
508 static const struct option ebt_restore_options[] = {
509 {.name = "noflush", .has_arg = 0, .val = 'n'},
510 { 0 }
511 };
512
xtables_eb_restore_main(int argc,char * argv[])513 int xtables_eb_restore_main(int argc, char *argv[])
514 {
515 struct nft_xt_restore_parse p = {
516 .in = stdin,
517 .cb = &ebt_restore_cb,
518 };
519 bool noflush = false;
520 struct nft_handle h;
521 int c;
522
523 while ((c = getopt_long(argc, argv, "n",
524 ebt_restore_options, NULL)) != -1) {
525 switch(c) {
526 case 'n':
527 noflush = 1;
528 break;
529 default:
530 fprintf(stderr,
531 "Usage: ebtables-restore [ --noflush ]\n");
532 exit(1);
533 break;
534 }
535 }
536
537 nft_init_eb(&h, "ebtables-restore");
538 h.noflush = noflush;
539 xtables_restore_parse(&h, &p);
540 nft_fini(&h);
541
542 return 0;
543 }
544
545 static const struct nft_xt_restore_cb arp_restore_cb = {
546 .commit = nft_commit,
547 .table_new = nft_table_new,
548 .table_flush = nft_table_flush,
549 .do_command = do_commandarp,
550 .chain_set = nft_chain_set,
551 .chain_restore = nft_chain_restore,
552 };
553
xtables_arp_restore_main(int argc,char * argv[])554 int xtables_arp_restore_main(int argc, char *argv[])
555 {
556 struct nft_xt_restore_parse p = {
557 .in = stdin,
558 .cb = &arp_restore_cb,
559 };
560 struct nft_handle h;
561
562 nft_init_arp(&h, "arptables-restore");
563 xtables_restore_parse(&h, &p);
564 nft_fini(&h);
565
566 return 0;
567 }
568