/* Code to restore the iptables state, from file by iptables-save.
|
* (C) 2000-2002 by Harald Welte <laforge@gnumonks.org>
|
* based on previous code from Rusty Russell <rusty@linuxcare.com.au>
|
*
|
* This code is distributed under the terms of GNU GPL v2
|
*/
|
|
#include <getopt.h>
|
#include <errno.h>
|
#include <stdbool.h>
|
#include <string.h>
|
#include <stdio.h>
|
#include <stdlib.h>
|
#include "iptables.h"
|
#include "xtables.h"
|
#include "libiptc/libiptc.h"
|
#include "xtables-multi.h"
|
#include "nft.h"
|
#include <libnftnl/chain.h>
|
|
#ifdef DEBUG
|
#define DEBUGP(x, args...) fprintf(stderr, x, ## args)
|
#else
|
#define DEBUGP(x, args...)
|
#endif
|
|
static int counters = 0, verbose = 0, noflush = 0;
|
|
/* Keeping track of external matches and targets. */
|
static const struct option options[] = {
|
{.name = "counters", .has_arg = false, .val = 'c'},
|
{.name = "verbose", .has_arg = false, .val = 'v'},
|
{.name = "test", .has_arg = false, .val = 't'},
|
{.name = "help", .has_arg = false, .val = 'h'},
|
{.name = "noflush", .has_arg = false, .val = 'n'},
|
{.name = "modprobe", .has_arg = true, .val = 'M'},
|
{.name = "table", .has_arg = true, .val = 'T'},
|
{.name = "ipv4", .has_arg = false, .val = '4'},
|
{.name = "ipv6", .has_arg = false, .val = '6'},
|
{NULL},
|
};
|
|
static void print_usage(const char *name, const char *version) __attribute__((noreturn));
|
|
#define prog_name xtables_globals.program_name
|
|
static void print_usage(const char *name, const char *version)
|
{
|
fprintf(stderr, "Usage: %s [-c] [-v] [-t] [-h] [-n] [-T table] [-M command] [-4] [-6]\n"
|
" [ --counters ]\n"
|
" [ --verbose ]\n"
|
" [ --test ]\n"
|
" [ --help ]\n"
|
" [ --noflush ]\n"
|
" [ --table=<TABLE> ]\n"
|
" [ --modprobe=<command> ]\n"
|
" [ --ipv4 ]\n"
|
" [ --ipv6 ]\n", name);
|
|
exit(1);
|
}
|
|
static int parse_counters(char *string, struct xt_counters *ctr)
|
{
|
unsigned long long pcnt, bcnt;
|
int ret;
|
|
ret = sscanf(string, "[%llu:%llu]", &pcnt, &bcnt);
|
ctr->pcnt = pcnt;
|
ctr->bcnt = bcnt;
|
return ret == 2;
|
}
|
|
/* global new argv and argc */
|
static char *newargv[255];
|
static int newargc;
|
|
/* function adding one argument to newargv, updating newargc
|
* returns true if argument added, false otherwise */
|
static int add_argv(char *what) {
|
DEBUGP("add_argv: %s\n", what);
|
if (what && newargc + 1 < ARRAY_SIZE(newargv)) {
|
newargv[newargc] = strdup(what);
|
newargv[++newargc] = NULL;
|
return 1;
|
} else {
|
xtables_error(PARAMETER_PROBLEM,
|
"Parser cannot handle more arguments\n");
|
return 0;
|
}
|
}
|
|
static void free_argv(void) {
|
int i;
|
|
for (i = 0; i < newargc; i++)
|
free(newargv[i]);
|
}
|
|
static void add_param_to_argv(char *parsestart)
|
{
|
int quote_open = 0, escaped = 0, param_len = 0;
|
char param_buffer[1024], *curchar;
|
|
/* After fighting with strtok enough, here's now
|
* a 'real' parser. According to Rusty I'm now no
|
* longer a real hacker, but I can live with that */
|
|
for (curchar = parsestart; *curchar; curchar++) {
|
if (quote_open) {
|
if (escaped) {
|
param_buffer[param_len++] = *curchar;
|
escaped = 0;
|
continue;
|
} else if (*curchar == '\\') {
|
escaped = 1;
|
continue;
|
} else if (*curchar == '"') {
|
quote_open = 0;
|
*curchar = ' ';
|
} else {
|
param_buffer[param_len++] = *curchar;
|
continue;
|
}
|
} else {
|
if (*curchar == '"') {
|
quote_open = 1;
|
continue;
|
}
|
}
|
|
if (*curchar == ' '
|
|| *curchar == '\t'
|
|| * curchar == '\n') {
|
if (!param_len) {
|
/* two spaces? */
|
continue;
|
}
|
|
param_buffer[param_len] = '\0';
|
|
/* check if table name specified */
|
if (!strncmp(param_buffer, "-t", 2)
|
|| !strncmp(param_buffer, "--table", 8)) {
|
xtables_error(PARAMETER_PROBLEM,
|
"The -t option (seen in line %u) cannot be "
|
"used in xtables-restore.\n", line);
|
exit(1);
|
}
|
|
add_argv(param_buffer);
|
param_len = 0;
|
} else {
|
/* regular character, copy to buffer */
|
param_buffer[param_len++] = *curchar;
|
|
if (param_len >= sizeof(param_buffer))
|
xtables_error(PARAMETER_PROBLEM,
|
"Parameter too long!");
|
}
|
}
|
}
|
|
static struct nftnl_chain_list *get_chain_list(struct nft_handle *h)
|
{
|
struct nftnl_chain_list *chain_list;
|
|
chain_list = nft_chain_dump(h);
|
if (chain_list == NULL)
|
xtables_error(OTHER_PROBLEM, "cannot retrieve chain list\n");
|
|
return chain_list;
|
}
|
|
static void chain_delete(struct nftnl_chain_list *clist, const char *curtable,
|
const char *chain)
|
{
|
struct nftnl_chain *chain_obj;
|
|
chain_obj = nft_chain_list_find(clist, curtable, chain);
|
/* This chain has been found, delete from list. Later
|
* on, unvisited chains will be purged out.
|
*/
|
if (chain_obj != NULL)
|
nftnl_chain_list_del(chain_obj);
|
}
|
|
struct nft_xt_restore_cb restore_cb = {
|
.chain_list = get_chain_list,
|
.commit = nft_commit,
|
.abort = nft_abort,
|
.chains_purge = nft_table_purge_chains,
|
.rule_flush = nft_rule_flush,
|
.chain_del = chain_delete,
|
.do_command = do_commandx,
|
.chain_set = nft_chain_set,
|
.chain_user_add = nft_chain_user_add,
|
};
|
|
static const struct xtc_ops xtc_ops = {
|
.strerror = nft_strerror,
|
};
|
|
void xtables_restore_parse(struct nft_handle *h,
|
struct nft_xt_restore_parse *p,
|
struct nft_xt_restore_cb *cb,
|
int argc, char *argv[])
|
{
|
char buffer[10240];
|
int in_table = 0;
|
char curtable[XT_TABLE_MAXNAMELEN + 1];
|
const struct xtc_ops *ops = &xtc_ops;
|
struct nftnl_chain_list *chain_list = NULL;
|
|
line = 0;
|
|
if (cb->chain_list)
|
chain_list = cb->chain_list(h);
|
|
/* Grab standard input. */
|
while (fgets(buffer, sizeof(buffer), p->in)) {
|
int ret = 0;
|
|
line++;
|
if (buffer[0] == '\n')
|
continue;
|
else if (buffer[0] == '#') {
|
if (verbose)
|
fputs(buffer, stdout);
|
continue;
|
} else if ((strcmp(buffer, "COMMIT\n") == 0) && (in_table)) {
|
if (!p->testing) {
|
/* Commit per table, although we support
|
* global commit at once, stick by now to
|
* the existing behaviour.
|
*/
|
DEBUGP("Calling commit\n");
|
if (cb->commit)
|
ret = cb->commit(h);
|
} else {
|
DEBUGP("Not calling commit, testing\n");
|
if (cb->abort)
|
ret = cb->abort(h);
|
}
|
in_table = 0;
|
|
/* Purge out unused chains in this table */
|
if (!p->testing && cb->chains_purge)
|
cb->chains_purge(h, curtable, chain_list);
|
|
} else if ((buffer[0] == '*') && (!in_table)) {
|
/* New table */
|
char *table;
|
|
table = strtok(buffer+1, " \t\n");
|
DEBUGP("line %u, table '%s'\n", line, table);
|
if (!table) {
|
xtables_error(PARAMETER_PROBLEM,
|
"%s: line %u table name invalid\n",
|
xt_params->program_name, line);
|
exit(1);
|
}
|
strncpy(curtable, table, XT_TABLE_MAXNAMELEN);
|
curtable[XT_TABLE_MAXNAMELEN] = '\0';
|
|
if (p->tablename && (strcmp(p->tablename, table) != 0))
|
continue;
|
|
if (noflush == 0) {
|
DEBUGP("Cleaning all chains of table '%s'\n",
|
table);
|
if (cb->rule_flush)
|
cb->rule_flush(h, NULL, table);
|
}
|
|
ret = 1;
|
in_table = 1;
|
|
if (cb->table_new)
|
cb->table_new(h, table);
|
|
} else if ((buffer[0] == ':') && (in_table)) {
|
/* New chain. */
|
char *policy, *chain = NULL;
|
struct xt_counters count = {};
|
|
chain = strtok(buffer+1, " \t\n");
|
DEBUGP("line %u, chain '%s'\n", line, chain);
|
if (!chain) {
|
xtables_error(PARAMETER_PROBLEM,
|
"%s: line %u chain name invalid\n",
|
xt_params->program_name, line);
|
exit(1);
|
}
|
|
if (cb->chain_del)
|
cb->chain_del(chain_list, curtable, chain);
|
|
if (strlen(chain) >= XT_EXTENSION_MAXNAMELEN)
|
xtables_error(PARAMETER_PROBLEM,
|
"Invalid chain name `%s' "
|
"(%u chars max)",
|
chain, XT_EXTENSION_MAXNAMELEN - 1);
|
|
policy = strtok(NULL, " \t\n");
|
DEBUGP("line %u, policy '%s'\n", line, policy);
|
if (!policy) {
|
xtables_error(PARAMETER_PROBLEM,
|
"%s: line %u policy invalid\n",
|
xt_params->program_name, line);
|
exit(1);
|
}
|
|
if (strcmp(policy, "-") != 0) {
|
if (counters) {
|
char *ctrs;
|
ctrs = strtok(NULL, " \t\n");
|
|
if (!ctrs || !parse_counters(ctrs, &count))
|
xtables_error(PARAMETER_PROBLEM,
|
"invalid policy counters "
|
"for chain '%s'\n", chain);
|
|
}
|
if (cb->chain_set &&
|
cb->chain_set(h, curtable, chain, policy, &count) < 0) {
|
xtables_error(OTHER_PROBLEM,
|
"Can't set policy `%s'"
|
" on `%s' line %u: %s\n",
|
policy, chain, line,
|
ops->strerror(errno));
|
}
|
DEBUGP("Setting policy of chain %s to %s\n",
|
chain, policy);
|
ret = 1;
|
|
} else {
|
if (cb->chain_user_add &&
|
cb->chain_user_add(h, chain, curtable) < 0) {
|
if (errno == EEXIST)
|
continue;
|
|
xtables_error(PARAMETER_PROBLEM,
|
"cannot create chain "
|
"'%s' (%s)\n", chain,
|
strerror(errno));
|
}
|
continue;
|
}
|
|
} else if (in_table) {
|
int a;
|
char *ptr = buffer;
|
char *pcnt = NULL;
|
char *bcnt = NULL;
|
char *parsestart;
|
|
/* reset the newargv */
|
newargc = 0;
|
|
if (buffer[0] == '[') {
|
/* we have counters in our input */
|
ptr = strchr(buffer, ']');
|
if (!ptr)
|
xtables_error(PARAMETER_PROBLEM,
|
"Bad line %u: need ]\n",
|
line);
|
|
pcnt = strtok(buffer+1, ":");
|
if (!pcnt)
|
xtables_error(PARAMETER_PROBLEM,
|
"Bad line %u: need :\n",
|
line);
|
|
bcnt = strtok(NULL, "]");
|
if (!bcnt)
|
xtables_error(PARAMETER_PROBLEM,
|
"Bad line %u: need ]\n",
|
line);
|
|
/* start command parsing after counter */
|
parsestart = ptr + 1;
|
} else {
|
/* start command parsing at start of line */
|
parsestart = buffer;
|
}
|
|
add_argv(argv[0]);
|
add_argv("-t");
|
add_argv(curtable);
|
|
if (counters && pcnt && bcnt) {
|
add_argv("--set-counters");
|
add_argv((char *) pcnt);
|
add_argv((char *) bcnt);
|
}
|
|
add_param_to_argv(parsestart);
|
|
DEBUGP("calling do_command4(%u, argv, &%s, handle):\n",
|
newargc, curtable);
|
|
for (a = 0; a < newargc; a++)
|
DEBUGP("argv[%u]: %s\n", a, newargv[a]);
|
|
ret = cb->do_command(h, newargc, newargv,
|
&newargv[2], true);
|
if (ret < 0) {
|
if (cb->abort)
|
ret = cb->abort(h);
|
else
|
ret = 0;
|
|
if (ret < 0) {
|
fprintf(stderr, "failed to abort "
|
"commit operation\n");
|
}
|
exit(1);
|
}
|
|
free_argv();
|
fflush(stdout);
|
}
|
if (p->tablename && (strcmp(p->tablename, curtable) != 0))
|
continue;
|
if (!ret) {
|
fprintf(stderr, "%s: line %u failed\n",
|
xt_params->program_name, line);
|
exit(1);
|
}
|
}
|
if (in_table) {
|
fprintf(stderr, "%s: COMMIT expected at line %u\n",
|
xt_params->program_name, line + 1);
|
exit(1);
|
}
|
}
|
|
static int
|
xtables_restore_main(int family, const char *progname, int argc, char *argv[])
|
{
|
struct nft_handle h = {
|
.family = family,
|
.restore = true,
|
};
|
int c;
|
struct nft_xt_restore_parse p = {};
|
|
line = 0;
|
|
xtables_globals.program_name = progname;
|
c = xtables_init_all(&xtables_globals, family);
|
if (c < 0) {
|
fprintf(stderr, "%s/%s Failed to initialize xtables\n",
|
xtables_globals.program_name,
|
xtables_globals.program_version);
|
exit(1);
|
}
|
#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
|
init_extensions();
|
init_extensions4();
|
#endif
|
|
if (nft_init(&h, xtables_ipv4) < 0) {
|
fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
|
xtables_globals.program_name,
|
xtables_globals.program_version,
|
strerror(errno));
|
exit(EXIT_FAILURE);
|
}
|
|
while ((c = getopt_long(argc, argv, "bcvthnM:T:46", options, NULL)) != -1) {
|
switch (c) {
|
case 'b':
|
fprintf(stderr, "-b/--binary option is not implemented\n");
|
break;
|
case 'c':
|
counters = 1;
|
break;
|
case 'v':
|
verbose = 1;
|
break;
|
case 't':
|
p.testing = 1;
|
break;
|
case 'h':
|
print_usage("xtables-restore",
|
IPTABLES_VERSION);
|
break;
|
case 'n':
|
noflush = 1;
|
break;
|
case 'M':
|
xtables_modprobe_program = optarg;
|
break;
|
case 'T':
|
p.tablename = optarg;
|
break;
|
case '4':
|
h.family = AF_INET;
|
break;
|
case '6':
|
h.family = AF_INET6;
|
xtables_set_nfproto(AF_INET6);
|
break;
|
}
|
}
|
|
if (optind == argc - 1) {
|
p.in = fopen(argv[optind], "re");
|
if (!p.in) {
|
fprintf(stderr, "Can't open %s: %s\n", argv[optind],
|
strerror(errno));
|
exit(1);
|
}
|
} else if (optind < argc) {
|
fprintf(stderr, "Unknown arguments found on commandline\n");
|
exit(1);
|
} else {
|
p.in = stdin;
|
}
|
|
xtables_restore_parse(&h, &p, &restore_cb, argc, argv);
|
|
fclose(p.in);
|
return 0;
|
}
|
|
int xtables_ip4_restore_main(int argc, char *argv[])
|
{
|
return xtables_restore_main(NFPROTO_IPV4, "iptables-restore",
|
argc, argv);
|
}
|
|
int xtables_ip6_restore_main(int argc, char *argv[])
|
{
|
return xtables_restore_main(NFPROTO_IPV6, "ip6tables-restore",
|
argc, argv);
|
}
|