From 033e25a3ad215ee3f5a07f0a3315f74c4abfaced Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Tue, 7 Jun 2011 14:02:37 +0200 Subject: src: move all iptables pieces into a separate directory (Unclutter top-level dir) Signed-off-by: Jan Engelhardt --- iptables/.gitignore | 14 + iptables/Makefile.am | 67 ++ iptables/ip6tables-multi.h | 8 + iptables/ip6tables-restore.8 | 50 + iptables/ip6tables-restore.c | 466 +++++++++ iptables/ip6tables-save.8 | 53 ++ iptables/ip6tables-save.c | 185 ++++ iptables/ip6tables-standalone.c | 84 ++ iptables/ip6tables.8.in | 440 +++++++++ iptables/ip6tables.c | 1967 ++++++++++++++++++++++++++++++++++++++ iptables/iptables-apply | 174 ++++ iptables/iptables-apply.8 | 44 + iptables/iptables-multi.h | 8 + iptables/iptables-restore.8 | 47 + iptables/iptables-restore.c | 471 +++++++++ iptables/iptables-save.8 | 51 + iptables/iptables-save.c | 185 ++++ iptables/iptables-standalone.c | 87 ++ iptables/iptables-xml.1 | 87 ++ iptables/iptables-xml.c | 874 +++++++++++++++++ iptables/iptables.8.in | 449 +++++++++ iptables/iptables.c | 2003 +++++++++++++++++++++++++++++++++++++++ iptables/iptables.xslt | 138 +++ iptables/xshared.c | 207 ++++ iptables/xshared.h | 87 ++ iptables/xtables-multi.c | 41 + iptables/xtables-multi.h | 6 + iptables/xtables.c | 1832 +++++++++++++++++++++++++++++++++++ iptables/xtables.pc.in | 13 + iptables/xtoptions.c | 1155 ++++++++++++++++++++++ 30 files changed, 11293 insertions(+) create mode 100644 iptables/.gitignore create mode 100644 iptables/Makefile.am create mode 100644 iptables/ip6tables-multi.h create mode 100644 iptables/ip6tables-restore.8 create mode 100644 iptables/ip6tables-restore.c create mode 100644 iptables/ip6tables-save.8 create mode 100644 iptables/ip6tables-save.c create mode 100644 iptables/ip6tables-standalone.c create mode 100644 iptables/ip6tables.8.in create mode 100644 iptables/ip6tables.c create mode 100755 iptables/iptables-apply create mode 100644 iptables/iptables-apply.8 create mode 100644 iptables/iptables-multi.h create mode 100644 iptables/iptables-restore.8 create mode 100644 iptables/iptables-restore.c create mode 100644 iptables/iptables-save.8 create mode 100644 iptables/iptables-save.c create mode 100644 iptables/iptables-standalone.c create mode 100644 iptables/iptables-xml.1 create mode 100644 iptables/iptables-xml.c create mode 100644 iptables/iptables.8.in create mode 100644 iptables/iptables.c create mode 100644 iptables/iptables.xslt create mode 100644 iptables/xshared.c create mode 100644 iptables/xshared.h create mode 100644 iptables/xtables-multi.c create mode 100644 iptables/xtables-multi.h create mode 100644 iptables/xtables.c create mode 100644 iptables/xtables.pc.in create mode 100644 iptables/xtoptions.c (limited to 'iptables') diff --git a/iptables/.gitignore b/iptables/.gitignore new file mode 100644 index 00000000..5a089376 --- /dev/null +++ b/iptables/.gitignore @@ -0,0 +1,14 @@ +/ip6tables +/ip6tables.8 +/ip6tables-save +/ip6tables-restore +/ip6tables-static +/iptables +/iptables.8 +/iptables-save +/iptables-restore +/iptables-static +/iptables-xml +/xtables-multi + +/xtables.pc diff --git a/iptables/Makefile.am b/iptables/Makefile.am new file mode 100644 index 00000000..13cca9c6 --- /dev/null +++ b/iptables/Makefile.am @@ -0,0 +1,67 @@ +# -*- Makefile -*- + +AM_CFLAGS = ${regular_CFLAGS} +AM_CPPFLAGS = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include ${kinclude_CPPFLAGS} + +lib_LTLIBRARIES = libxtables.la +libxtables_la_SOURCES = xtables.c xtoptions.c +libxtables_la_LDFLAGS = -version-info ${libxtables_vcurrent}:0:${libxtables_vage} +if ENABLE_SHARED +libxtables_la_CFLAGS = ${AM_CFLAGS} +libxtables_la_LIBADD = -ldl +else +libxtables_la_CFLAGS = ${AM_CFLAGS} -DNO_SHARED_LIBS=1 +libxtables_la_LIBADD = +endif + +xtables_multi_SOURCES = xtables-multi.c iptables-xml.c +xtables_multi_CFLAGS = ${AM_CFLAGS} -DIPTABLES_MULTI +xtables_multi_LDFLAGS = -rdynamic +xtables_multi_LDADD = ../extensions/libext.a +if ENABLE_STATIC +xtables_multi_CFLAGS += -DALL_INCLUSIVE +endif +if ENABLE_IPV4 +xtables_multi_SOURCES += iptables-save.c iptables-restore.c \ + iptables-standalone.c iptables.c +xtables_multi_CFLAGS += -DENABLE_IPV4 +xtables_multi_LDADD += ../libiptc/libip4tc.la ../extensions/libext4.a +endif +if ENABLE_IPV6 +xtables_multi_SOURCES += ip6tables-save.c ip6tables-restore.c \ + ip6tables-standalone.c ip6tables.c +xtables_multi_CFLAGS += -DENABLE_IPV6 +xtables_multi_LDADD += ../libiptc/libip6tc.la ../extensions/libext6.a +endif +xtables_multi_SOURCES += xshared.c +xtables_multi_LDADD += libxtables.la -lm + +sbin_PROGRAMS = xtables-multi +man_MANS = iptables.8 iptables-restore.8 iptables-save.8 \ + iptables-xml.1 ip6tables.8 ip6tables-restore.8 \ + ip6tables-save.8 +CLEANFILES = iptables.8 ip6tables.8 + +vx_bin_links = iptables-xml +if ENABLE_IPV4 +v4_sbin_links = iptables iptables-restore iptables-save +endif +if ENABLE_IPV6 +v6_sbin_links = ip6tables ip6tables-restore ip6tables-save +endif + +iptables.8: ${srcdir}/iptables.8.in ../extensions/matches4.man ../extensions/targets4.man + ${AM_VERBOSE_GEN} sed -e 's/@PACKAGE_AND_VERSION@/${PACKAGE} ${PACKAGE_VERSION}/g' -e '/@MATCH@/ r extensions/matches4.man' -e '/@TARGET@/ r extensions/targets4.man' $< >$@; + +ip6tables.8: ${srcdir}/ip6tables.8.in ../extensions/matches6.man ../extensions/targets6.man + ${AM_VERBOSE_GEN} sed -e 's/@PACKAGE_AND_VERSION@/${PACKAGE} ${PACKAGE_VERSION}/g' -e '/@MATCH@/ r extensions/matches6.man' -e '/@TARGET@/ r extensions/targets6.man' $< >$@; + +pkgconfig_DATA = xtables.pc + +# Using if..fi avoids an ugly "error (ignored)" message :) +install-exec-hook: + -if test -z "${DESTDIR}"; then /sbin/ldconfig; fi; + ${INSTALL} -dm0755 "${DESTDIR}${bindir}"; + for i in ${vx_bin_links}; do ${LN_S} -f "${sbindir}/xtables-multi" "${DESTDIR}${bindir}/$$i"; done; + for i in ${v4_sbin_links}; do ${LN_S} -f xtables-multi "${DESTDIR}${sbindir}/$$i"; done; + for i in ${v6_sbin_links}; do ${LN_S} -f xtables-multi "${DESTDIR}${sbindir}/$$i"; done; diff --git a/iptables/ip6tables-multi.h b/iptables/ip6tables-multi.h new file mode 100644 index 00000000..551029ad --- /dev/null +++ b/iptables/ip6tables-multi.h @@ -0,0 +1,8 @@ +#ifndef _IP6TABLES_MULTI_H +#define _IP6TABLES_MULTI_H 1 + +extern int ip6tables_main(int, char **); +extern int ip6tables_save_main(int, char **); +extern int ip6tables_restore_main(int, char **); + +#endif /* _IP6TABLES_MULTI_H */ diff --git a/iptables/ip6tables-restore.8 b/iptables/ip6tables-restore.8 new file mode 100644 index 00000000..02648070 --- /dev/null +++ b/iptables/ip6tables-restore.8 @@ -0,0 +1,50 @@ +.TH IP6TABLES-RESTORE 8 "Jan 30, 2002" "" "" +.\" +.\" Man page written by Harald Welte +.\" It is based on the iptables man page. +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" +.SH NAME +ip6tables-restore \(em Restore IPv6 Tables +.SH SYNOPSIS +\fBip6tables\-restore\fP [\fB\-c\fP] [\fB\-n\fP] +.SH DESCRIPTION +.PP +.B ip6tables-restore +is used to restore IPv6 Tables from data specified on STDIN. Use +I/O redirection provided by your shell to read from a file +.TP +\fB\-c\fR, \fB\-\-counters\fR +restore the values of all packet and byte counters +.TP +\fB\-n\fR, \fB\-\-noflush\fR +.TP +don't flush the previous contents of the table. If not specified, +.B ip6tables-restore +flushes (deletes) all previous contents of the respective IPv6 Table. +.SH BUGS +None known as of iptables-1.2.1 release +.SH AUTHORS +Harald Welte +.br +Andras Kis-Szabo +.SH SEE ALSO +\fBip6tables\-save\fP(8), \fBip6tables\fP(8) +.PP +The iptables-HOWTO, which details more iptables usage, the NAT-HOWTO, +which details NAT, and the netfilter-hacking-HOWTO which details the +internals. diff --git a/iptables/ip6tables-restore.c b/iptables/ip6tables-restore.c new file mode 100644 index 00000000..420bc523 --- /dev/null +++ b/iptables/ip6tables-restore.c @@ -0,0 +1,466 @@ +/* Code to restore the iptables state, from file by ip6tables-save. + * Author: Andras Kis-Szabo + * + * based on iptables-restore + * Authors: + * Harald Welte + * Rusty Russell + * This code is distributed under the terms of GNU GPL v2 + */ + +#include +#include +#include +#include +#include +#include +#include "ip6tables.h" +#include "xtables.h" +#include "libiptc/libip6tc.h" +#include "ip6tables-multi.h" + +#ifdef DEBUG +#define DEBUGP(x, args...) fprintf(stderr, x, ## args) +#else +#define DEBUGP(x, args...) +#endif + +static int binary = 0, counters = 0, verbose = 0, noflush = 0; + +/* Keeping track of external matches and targets. */ +static const struct option options[] = { + {.name = "binary", .has_arg = false, .val = 'b'}, + {.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'}, + {NULL}, +}; + +static void print_usage(const char *name, const char *version) __attribute__((noreturn)); + +static void print_usage(const char *name, const char *version) +{ + fprintf(stderr, "Usage: %s [-b] [-c] [-v] [-t] [-h]\n" + " [ --binary ]\n" + " [ --counters ]\n" + " [ --verbose ]\n" + " [ --test ]\n" + " [ --help ]\n" + " [ --noflush ]\n" + " [ --modprobe=]\n", name); + + exit(1); +} + +static struct ip6tc_handle *create_handle(const char *tablename) +{ + struct ip6tc_handle *handle; + + handle = ip6tc_init(tablename); + + if (!handle) { + /* try to insmod the module if iptc_init failed */ + xtables_load_ko(xtables_modprobe_program, false); + handle = ip6tc_init(tablename); + } + + if (!handle) { + xtables_error(PARAMETER_PROBLEM, "%s: unable to initialize " + "table '%s'\n", ip6tables_globals.program_name, + tablename); + exit(1); + } + return handle; +} + +static int parse_counters(char *string, struct ip6t_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); + newargc++; + 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]); +} + +#ifdef IPTABLES_MULTI +int ip6tables_restore_main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + struct ip6tc_handle *handle = NULL; + char buffer[10240]; + int c; + char curtable[IP6T_TABLE_MAXNAMELEN + 1]; + FILE *in; + int in_table = 0, testing = 0; + + line = 0; + + ip6tables_globals.program_name = "ip6tables-restore"; + c = xtables_init_all(&ip6tables_globals, NFPROTO_IPV6); + if (c < 0) { + fprintf(stderr, "%s/%s Failed to initialize xtables\n", + ip6tables_globals.program_name, + ip6tables_globals.program_version); + exit(1); + } +#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) + init_extensions(); + init_extensions6(); +#endif + + while ((c = getopt_long(argc, argv, "bcvthnM:", options, NULL)) != -1) { + switch (c) { + case 'b': + binary = 1; + break; + case 'c': + counters = 1; + break; + case 'v': + verbose = 1; + break; + case 't': + testing = 1; + break; + case 'h': + print_usage("ip6tables-restore", + IPTABLES_VERSION); + break; + case 'n': + noflush = 1; + break; + case 'M': + xtables_modprobe_program = optarg; + break; + } + } + + if (optind == argc - 1) { + in = fopen(argv[optind], "re"); + if (!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 in = stdin; + + /* Grab standard input. */ + while (fgets(buffer, sizeof(buffer), 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 (!testing) { + DEBUGP("Calling commit\n"); + ret = ip6tc_commit(handle); + ip6tc_free(handle); + handle = NULL; + } else { + DEBUGP("Not calling commit, testing\n"); + ret = 1; + } + in_table = 0; + } 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", + ip6tables_globals.program_name, + line); + exit(1); + } + strncpy(curtable, table, IP6T_TABLE_MAXNAMELEN); + curtable[IP6T_TABLE_MAXNAMELEN] = '\0'; + + if (handle) + ip6tc_free(handle); + + handle = create_handle(table); + if (noflush == 0) { + DEBUGP("Cleaning all chains of table '%s'\n", + table); + for_each_chain6(flush_entries6, verbose, 1, + handle); + + DEBUGP("Deleting all user-defined chains " + "of table '%s'\n", table); + for_each_chain6(delete_chain6, verbose, 0, + handle); + } + + ret = 1; + in_table = 1; + + } else if ((buffer[0] == ':') && (in_table)) { + /* New chain. */ + char *policy, *chain; + + 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", + ip6tables_globals.program_name, + line); + exit(1); + } + + if (strlen(chain) >= XT_EXTENSION_MAXNAMELEN) + xtables_error(PARAMETER_PROBLEM, + "Invalid chain name `%s' " + "(%u chars max)", + chain, XT_EXTENSION_MAXNAMELEN - 1); + + if (ip6tc_builtin(chain, handle) <= 0) { + if (noflush && ip6tc_is_chain(chain, handle)) { + DEBUGP("Flushing existing user defined chain '%s'\n", chain); + if (!ip6tc_flush_entries(chain, handle)) + xtables_error(PARAMETER_PROBLEM, + "error flushing chain " + "'%s':%s\n", chain, + strerror(errno)); + } else { + DEBUGP("Creating new chain '%s'\n", chain); + if (!ip6tc_create_chain(chain, handle)) + xtables_error(PARAMETER_PROBLEM, + "error creating chain " + "'%s':%s\n", chain, + strerror(errno)); + } + } + + 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", + ip6tables_globals.program_name, + line); + exit(1); + } + + if (strcmp(policy, "-") != 0) { + struct ip6t_counters count; + + 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); + + } else { + memset(&count, 0, + sizeof(struct ip6t_counters)); + } + + DEBUGP("Setting policy of chain %s to %s\n", + chain, policy); + + if (!ip6tc_set_policy(chain, policy, &count, + handle)) + xtables_error(OTHER_PROBLEM, + "Can't set policy `%s'" + " on `%s' line %u: %s\n", + policy, chain, line, + ip6tc_strerror(errno)); + } + + ret = 1; + + } else if (in_table) { + int a; + char *ptr = buffer; + char *pcnt = NULL; + char *bcnt = NULL; + char *parsestart; + + /* the parser */ + char *curchar; + int quote_open, escaped; + size_t param_len; + + /* 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); + } + + /* 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 */ + + quote_open = 0; + escaped = 0; + param_len = 0; + + for (curchar = parsestart; *curchar; curchar++) { + char param_buffer[1024]; + + 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, + "Line %u seems to have a " + "-t table option.\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!"); + } + } + + DEBUGP("calling do_command6(%u, argv, &%s, handle):\n", + newargc, curtable); + + for (a = 0; a < newargc; a++) + DEBUGP("argv[%u]: %s\n", a, newargv[a]); + + ret = do_command6(newargc, newargv, + &newargv[2], &handle); + + free_argv(); + fflush(stdout); + } + if (!ret) { + fprintf(stderr, "%s: line %u failed\n", + ip6tables_globals.program_name, + line); + exit(1); + } + } + if (in_table) { + fprintf(stderr, "%s: COMMIT expected at line %u\n", + ip6tables_globals.program_name, + line + 1); + exit(1); + } + + if (in != NULL) + fclose(in); + return 0; +} diff --git a/iptables/ip6tables-save.8 b/iptables/ip6tables-save.8 new file mode 100644 index 00000000..457be821 --- /dev/null +++ b/iptables/ip6tables-save.8 @@ -0,0 +1,53 @@ +.TH IP6TABLES-SAVE 8 "Jan 30, 2002" "" "" +.\" +.\" Man page written by Harald Welte +.\" It is based on the iptables man page. +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" +.SH NAME +ip6tables-save \(em dump iptables rules to stdout +.SH SYNOPSIS +\fBip6tables\-save\fP [\fB\-M\fP \fImodprobe\fP] [\fB\-c\fP] +[\fB\-t\fP \fItable\fP +.SH DESCRIPTION +.PP +.B ip6tables-save +is used to dump the contents of an IPv6 Table in easily parseable format +to STDOUT. Use I/O-redirection provided by your shell to write to a file. +.TP +\fB\-M\fP \fImodprobe_program\fP +Specify the path to the modprobe program. By default, iptables-save will +inspect /proc/sys/kernel/modprobe to determine the executable's path. +.TP +\fB\-c\fR, \fB\-\-counters\fR +include the current values of all packet and byte counters in the output +.TP +\fB\-t\fR, \fB\-\-table\fR \fItablename\fP +restrict output to only one table. If not specified, output includes all +available tables. +.SH BUGS +None known as of iptables-1.2.1 release +.SH AUTHORS +Harald Welte +.br +Andras Kis-Szabo +.SH SEE ALSO +\fBip6tables\-restore\fP(8), \fBip6tables\fP(8) +.PP +The iptables-HOWTO, which details more iptables usage, the NAT-HOWTO, +which details NAT, and the netfilter-hacking-HOWTO which details the +internals. diff --git a/iptables/ip6tables-save.c b/iptables/ip6tables-save.c new file mode 100644 index 00000000..39a33256 --- /dev/null +++ b/iptables/ip6tables-save.c @@ -0,0 +1,185 @@ +/* Code to save the ip6tables state, in human readable-form. */ +/* Author: Andras Kis-Szabo + * Original code: iptables-save + * Authors: Paul 'Rusty' Russel and + * Harald Welte + * This code is distributed under the terms of GNU GPL v2 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libiptc/libip6tc.h" +#include "ip6tables.h" +#include "ip6tables-multi.h" + +#ifndef NO_SHARED_LIBS +#include +#endif + +static int show_binary = 0, show_counters = 0; + +static const struct option options[] = { + {.name = "binary", .has_arg = false, .val = 'b'}, + {.name = "counters", .has_arg = false, .val = 'c'}, + {.name = "dump", .has_arg = false, .val = 'd'}, + {.name = "table", .has_arg = true, .val = 't'}, + {.name = "modprobe", .has_arg = true, .val = 'M'}, + {NULL}, +}; + + +/* Debugging prototype. */ +static int for_each_table(int (*func)(const char *tablename)) +{ + int ret = 1; + FILE *procfile = NULL; + char tablename[IP6T_TABLE_MAXNAMELEN+1]; + + procfile = fopen("/proc/net/ip6_tables_names", "re"); + if (!procfile) + return ret; + + while (fgets(tablename, sizeof(tablename), procfile)) { + if (tablename[strlen(tablename) - 1] != '\n') + xtables_error(OTHER_PROBLEM, + "Badly formed tablename `%s'\n", + tablename); + tablename[strlen(tablename) - 1] = '\0'; + ret &= func(tablename); + } + + fclose(procfile); + return ret; +} + + +static int do_output(const char *tablename) +{ + struct ip6tc_handle *h; + const char *chain = NULL; + + if (!tablename) + return for_each_table(&do_output); + + h = ip6tc_init(tablename); + if (h == NULL) { + xtables_load_ko(xtables_modprobe_program, false); + h = ip6tc_init(tablename); + } + if (!h) + xtables_error(OTHER_PROBLEM, "Cannot initialize: %s\n", + ip6tc_strerror(errno)); + + if (!show_binary) { + time_t now = time(NULL); + + printf("# Generated by ip6tables-save v%s on %s", + IPTABLES_VERSION, ctime(&now)); + printf("*%s\n", tablename); + + /* Dump out chain names first, + * thereby preventing dependency conflicts */ + for (chain = ip6tc_first_chain(h); + chain; + chain = ip6tc_next_chain(h)) { + + printf(":%s ", chain); + if (ip6tc_builtin(chain, h)) { + struct ip6t_counters count; + printf("%s ", + ip6tc_get_policy(chain, &count, h)); + printf("[%llu:%llu]\n", (unsigned long long)count.pcnt, (unsigned long long)count.bcnt); + } else { + printf("- [0:0]\n"); + } + } + + + for (chain = ip6tc_first_chain(h); + chain; + chain = ip6tc_next_chain(h)) { + const struct ip6t_entry *e; + + /* Dump out rules */ + e = ip6tc_first_rule(chain, h); + while(e) { + print_rule6(e, h, chain, show_counters); + e = ip6tc_next_rule(e, h); + } + } + + now = time(NULL); + printf("COMMIT\n"); + printf("# Completed on %s", ctime(&now)); + } else { + /* Binary, huh? OK. */ + xtables_error(OTHER_PROBLEM, "Binary NYI\n"); + } + + ip6tc_free(h); + + return 1; +} + +/* Format: + * :Chain name POLICY packets bytes + * rule + */ +#ifdef IPTABLES_MULTI +int ip6tables_save_main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + const char *tablename = NULL; + int c; + + ip6tables_globals.program_name = "ip6tables-save"; + c = xtables_init_all(&ip6tables_globals, NFPROTO_IPV6); + if (c < 0) { + fprintf(stderr, "%s/%s Failed to initialize xtables\n", + ip6tables_globals.program_name, + ip6tables_globals.program_version); + exit(1); + } +#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) + init_extensions(); + init_extensions6(); +#endif + + while ((c = getopt_long(argc, argv, "bcdt:", options, NULL)) != -1) { + switch (c) { + case 'b': + show_binary = 1; + break; + + case 'c': + show_counters = 1; + break; + + case 't': + /* Select specific table. */ + tablename = optarg; + break; + case 'M': + xtables_modprobe_program = optarg; + break; + case 'd': + do_output(tablename); + exit(0); + } + } + + if (optind < argc) { + fprintf(stderr, "Unknown arguments found on commandline\n"); + exit(1); + } + + return !do_output(tablename); +} diff --git a/iptables/ip6tables-standalone.c b/iptables/ip6tables-standalone.c new file mode 100644 index 00000000..9d8d5a0f --- /dev/null +++ b/iptables/ip6tables-standalone.c @@ -0,0 +1,84 @@ +/* + * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au + * + * (C) 2000-2002 by the netfilter coreteam : + * Paul 'Rusty' Russell + * Marc Boucher + * James Morris + * Harald Welte + * Jozsef Kadlecsik + * + * Based on the ipchains code by Paul Russell and Michael Neuling + * + * iptables -- IP firewall administration for kernels with + * firewall table (aimed for the 2.3 kernels) + * + * See the accompanying manual page iptables(8) for information + * about proper usage of this program. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include "ip6tables-multi.h" + +#ifdef IPTABLES_MULTI +int +ip6tables_main(int argc, char *argv[]) +#else +int +main(int argc, char *argv[]) +#endif +{ + int ret; + char *table = "filter"; + struct ip6tc_handle *handle = NULL; + + ip6tables_globals.program_name = "ip6tables"; + ret = xtables_init_all(&ip6tables_globals, NFPROTO_IPV6); + if (ret < 0) { + fprintf(stderr, "%s/%s Failed to initialize xtables\n", + ip6tables_globals.program_name, + ip6tables_globals.program_version); + exit(1); + } + +#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) + init_extensions(); + init_extensions6(); +#endif + + ret = do_command6(argc, argv, &table, &handle); + if (ret) { + ret = ip6tc_commit(handle); + ip6tc_free(handle); + } + + if (!ret) { + if (errno == EINVAL) { + fprintf(stderr, "ip6tables: %s. " + "Run `dmesg' for more information.\n", + ip6tc_strerror(errno)); + } else { + fprintf(stderr, "ip6tables: %s.\n", + ip6tc_strerror(errno)); + } + } + + exit(!ret); +} diff --git a/iptables/ip6tables.8.in b/iptables/ip6tables.8.in new file mode 100644 index 00000000..48ba18e1 --- /dev/null +++ b/iptables/ip6tables.8.in @@ -0,0 +1,440 @@ +.TH IP6TABLES 8 "" "iptables 1.4.4" "iptables 1.4.4" +.\" +.\" Man page written by Andras Kis-Szabo +.\" It is based on iptables man page. +.\" +.\" iptables page by Herve Eychenne +.\" It is based on ipchains man page. +.\" +.\" ipchains page by Paul ``Rusty'' Russell March 1997 +.\" Based on the original ipfwadm man page by Jos Vos +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" +.SH NAME +ip6tables \(em IPv6 packet filter administration +.SH SYNOPSIS +\fBip6tables\fP [\fB\-t\fP \fItable\fP] {\fB\-A\fP|\fB\-C\fP|\fB\-D\fP} +\fIchain rule-specification\fP [\fIoptions...\fP] +.PP +\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-I\fP \fIchain\fP [\fIrulenum\fP] +\fIrule-specification\fP [\fIoptions...\fP] +.PP +\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-R\fP \fIchain rulenum +rule-specification\fP [\fIoptions...\fP] +.PP +\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-D\fP \fIchain rulenum\fP +[\fIoptions...\fP] +.PP +\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-S\fP [\fIchain\fP [\fIrulenum\fP]] +.PP +\fBip6tables\fP [\fB\-t\fP \fItable\fP] {\fB\-F\fP|\fB\-L\fP|\fB\-Z\fP} +[\fIchain\fP [\fIrulenum\fP]] [\fIoptions...\fP] +.PP +\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-N\fP \fIchain\fP +.PP +\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-X\fP [\fIchain\fP] +.PP +\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-P\fP \fIchain target\fP +[\fIoptions...\fP] +.PP +\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-E\fP \fIold-chain-name new-chain-name\fP +.SH DESCRIPTION +\fBIp6tables\fP is used to set up, maintain, and inspect the +tables of IPv6 packet +filter rules in the Linux kernel. Several different tables +may be defined. Each table contains a number of built-in +chains and may also contain user-defined chains. +.PP +Each chain is a list of rules which can match a set of packets. Each +rule specifies what to do with a packet that matches. This is called +a `target', which may be a jump to a user-defined chain in the same +table. +.SH TARGETS +A firewall rule specifies criteria for a packet and a target. If the +packet does not match, the next rule in the chain is the examined; if +it does match, then the next rule is specified by the value of the +target, which can be the name of a user-defined chain or one of the +special values \fBACCEPT\fP, \fBDROP\fP, \fBQUEUE\fP or \fBRETURN\fP. +.PP +\fBACCEPT\fP means to let the packet through. +\fBDROP\fP means to drop the packet on the floor. +\fBQUEUE\fP means to pass the packet to userspace. +(How the packet can be received +by a userspace process differs by the particular queue handler. 2.4.x +and 2.6.x kernels up to 2.6.13 include the \fBip_queue\fP +queue handler. Kernels 2.6.14 and later additionally include the +\fBnfnetlink_queue\fP queue handler. Packets with a target of QUEUE will be +sent to queue number '0' in this case. Please also see the \fBNFQUEUE\fP +target as described later in this man page.) +\fBRETURN\fP means stop traversing this chain and resume at the next +rule in the +previous (calling) chain. If the end of a built-in chain is reached +or a rule in a built-in chain with target \fBRETURN\fP +is matched, the target specified by the chain policy determines the +fate of the packet. +.SH TABLES +There are currently three independent tables (which tables are present +at any time depends on the kernel configuration options and which +modules are present). +.TP +\fB\-t\fP, \fB\-\-table\fP \fItable\fP +This option specifies the packet matching table which the command +should operate on. If the kernel is configured with automatic module +loading, an attempt will be made to load the appropriate module for +that table if it is not already there. + +The tables are as follows: +.RS +.TP .4i +\fBfilter\fP: +This is the default table (if no \-t option is passed). It contains +the built-in chains \fBINPUT\fP (for packets destined to local sockets), +\fBFORWARD\fP (for packets being routed through the box), and +\fBOUTPUT\fP (for locally-generated packets). +.TP +\fBmangle\fP: +This table is used for specialized packet alteration. Until kernel +2.4.17 it had two built-in chains: \fBPREROUTING\fP +(for altering incoming packets before routing) and \fBOUTPUT\fP +(for altering locally-generated packets before routing). +Since kernel 2.4.18, three other built-in chains are also supported: +\fBINPUT\fP (for packets coming into the box itself), \fBFORWARD\fP +(for altering packets being routed through the box), and \fBPOSTROUTING\fP +(for altering packets as they are about to go out). +.TP +\fBraw\fP: +This table is used mainly for configuring exemptions from connection +tracking in combination with the NOTRACK target. It registers at the netfilter +hooks with higher priority and is thus called before ip_conntrack, or any other +IP tables. It provides the following built-in chains: \fBPREROUTING\fP +(for packets arriving via any network interface) \fBOUTPUT\fP +(for packets generated by local processes) +.TP +\fBsecurity\fP: +This table is used for Mandatory Access Control (MAC) networking rules, such +as those enabled by the \fBSECMARK\fP and \fBCONNSECMARK\fP targets. +Mandatory Access Control is implemented by Linux Security Modules such as +SELinux. The security table is called after the filter table, allowing any +Discretionary Access Control (DAC) rules in the filter table to take effect +before MAC rules. This table provides the following built-in chains: +\fBINPUT\fP (for packets coming into the box itself), +\fBOUTPUT\fP (for altering locally-generated packets before routing), and +\fBFORWARD\fP (for altering packets being routed through the box). +.RE +.SH OPTIONS +The options that are recognized by +\fBip6tables\fP can be divided into several different groups. +.SS COMMANDS +These options specify the specific action to perform. Only one of them +can be specified on the command line unless otherwise specified +below. For all the long versions of the command and option names, you +need to use only enough letters to ensure that +\fBip6tables\fP can differentiate it from all other options. +.TP +\fB\-A\fP, \fB\-\-append\fP \fIchain rule-specification\fP +Append one or more rules to the end of the selected chain. +When the source and/or destination names resolve to more than one +address, a rule will be added for each possible address combination. +.TP +\fB\-C\fP, \fB\-\-check\fP \fIchain rule-specification\fP +Check whether a rule matching the specification does exist in the +selected chain. This command uses the same logic as \fB\-D\fP to +find a matching entry, but does not alter the existing iptables +configuration and uses its exit code to indicate success or failure. +.TP +\fB\-D\fP, \fB\-\-delete\fP \fIchain rule-specification\fP +.ns +.TP +\fB\-D\fP, \fB\-\-delete\fP \fIchain rulenum\fP +Delete one or more rules from the selected chain. There are two +versions of this command: the rule can be specified as a number in the +chain (starting at 1 for the first rule) or a rule to match. +.TP +\fB\-I\fP, \fB\-\-insert\fP \fIchain\fP [\fIrulenum\fP] \fIrule-specification\fP +Insert one or more rules in the selected chain as the given rule +number. So, if the rule number is 1, the rule or rules are inserted +at the head of the chain. This is also the default if no rule number +is specified. +.TP +\fB\-R\fP, \fB\-\-replace\fP \fIchain rulenum rule-specification\fP +Replace a rule in the selected chain. If the source and/or +destination names resolve to multiple addresses, the command will +fail. Rules are numbered starting at 1. +.TP +\fB\-L\fP, \fB\-\-list\fP [\fIchain\fP] +List all rules in the selected chain. If no chain is selected, all +chains are listed. Like every other ip6tables command, it applies to the +specified table (filter is the default). +.IP "" +Please note that it is often used with the \fB\-n\fP +option, in order to avoid long reverse DNS lookups. +It is legal to specify the \fB\-Z\fP +(zero) option as well, in which case the chain(s) will be atomically +listed and zeroed. The exact output is affected by the other +arguments given. The exact rules are suppressed until you use +.nf + ip6tables \-L \-v +.fi +.TP +\fB\-S\fP, \fB\-\-list\-rules\fP [\fIchain\fP] +Print all rules in the selected chain. If no chain is selected, all +chains are printed like ip6tables-save. Like every other ip6tables command, +it applies to the specified table (filter is the default). +.TP +\fB\-F\fP, \fB\-\-flush\fP [\fIchain\fP] +Flush the selected chain (all the chains in the table if none is given). +This is equivalent to deleting all the rules one by one. +.TP +\fB\-Z\fP, \fB\-\-zero\fP [\fIchain\fP [\fIrulenum\fP]] +Zero the packet and byte counters in all chains, or only the given chain, +or only the given rule in a chain. It is legal to +specify the +\fB\-L\fP, \fB\-\-list\fP +(list) option as well, to see the counters immediately before they are +cleared. (See above.) +.TP +\fB\-N\fP, \fB\-\-new\-chain\fP \fIchain\fP +Create a new user-defined chain by the given name. There must be no +target of that name already. +.TP +\fB\-X\fP, \fB\-\-delete\-chain\fP [\fIchain\fP] +Delete the optional user-defined chain specified. There must be no references +to the chain. If there are, you must delete or replace the referring rules +before the chain can be deleted. The chain must be empty, i.e. not contain +any rules. If no argument is given, it will attempt to delete every +non-builtin chain in the table. +.TP +\fB\-P\fP, \fB\-\-policy\fP \fIchain target\fP +Set the policy for the chain to the given target. See the section \fBTARGETS\fP +for the legal targets. Only built-in (non-user-defined) chains can have +policies, and neither built-in nor user-defined chains can be policy +targets. +.TP +\fB\-E\fP, \fB\-\-rename\-chain\fP \fIold\-chain new\-chain\fP +Rename the user specified chain to the user supplied name. This is +cosmetic, and has no effect on the structure of the table. +.TP +\fB\-A\fP, \fB\-\-append\fP \fIchain rule-specification\fP +Append one or more rules to the end of the selected chain. +When the source and/or destination names resolve to more than one +address, a rule will be added for each possible address combination. +.TP +\fB\-h\fP +Help. +Give a (currently very brief) description of the command syntax. +.SS PARAMETERS +The following parameters make up a rule specification (as used in the +add, delete, insert, replace and append commands). +.TP +[\fB!\fP] \fB\-p\fP, \fB\-\-protocol\fP \fIprotocol\fP +The protocol of the rule or of the packet to check. +The specified protocol can be one of \fBtcp\fP, \fBudp\fP, \fBudplite\fP, +\fBicmpv6\fP, \fBesp\fP, \fBmh\fP or the special keyword "\fBall\fP", +or it can be a numeric value, representing one of these protocols or a +different one. A protocol name from /etc/protocols is also allowed. +But IPv6 extension headers except \fBesp\fP are not allowed. +\fBesp\fP and \fBipv6\-nonext\fP +can be used with Kernel version 2.6.11 or later. +A "!" argument before the protocol inverts the +test. The number zero is equivalent to \fBall\fP. "\fBall\fP" +will match with all protocols and is taken as default when this +option is omitted. +.TP +[\fB!\fP] \fB\-s\fP, \fB\-\-source\fP \fIaddress\fP[\fB/\fP\fImask\fP] +Source specification. +\fIAddress\fP can be either be a hostname, +a network IP address (with \fB/\fP\fImask\fP), or a plain IP address. +Names will be resolved once only, before the rule is submitted to the kernel. +Please note that specifying any name to be resolved with a remote query such as +DNS is a really bad idea. +(Resolving network names is not supported at this time.) +The \fImask\fP is a plain number, +specifying the number of 1's at the left side of the network mask. +A "!" argument before the address specification inverts the sense of +the address. The flag \fB\-\-src\fP +is an alias for this option. +Multiple addresses can be specified, but this will \fBexpand to multiple +rules\fP (when adding with \-A), or will cause multiple rules to be +deleted (with \-D). +.TP +[\fB!\fP] \fB\-d\fP, \fB\-\-destination\fP \fIaddress\fP[\fB/\fP\fImask\fP] +Destination specification. +See the description of the \fB\-s\fP +(source) flag for a detailed description of the syntax. The flag +\fB\-\-dst\fP is an alias for this option. +.TP +\fB\-j\fP, \fB\-\-jump\fP \fItarget\fP +This specifies the target of the rule; i.e., what to do if the packet +matches it. The target can be a user-defined chain (other than the +one this rule is in), one of the special builtin targets which decide +the fate of the packet immediately, or an extension (see \fBEXTENSIONS\fP +below). If this +option is omitted in a rule (and \fB\-g\fP +is not used), then matching the rule will have no +effect on the packet's fate, but the counters on the rule will be +incremented. +.TP +\fB\-g\fP, \fB\-\-goto\fP \fIchain\fP +This specifies that the processing should continue in a user +specified chain. Unlike the \-\-jump option return will not continue +processing in this chain but instead in the chain that called us via +\-\-jump. +.TP +[\fB!\fP] \fB\-i\fP, \fB\-\-in\-interface\fP \fIname\fP +Name of an interface via which a packet was received (only for +packets entering the \fBINPUT\fP, \fBFORWARD\fP and \fBPREROUTING\fP +chains). When the "!" argument is used before the interface name, the +sense is inverted. If the interface name ends in a "+", then any +interface which begins with this name will match. If this option is +omitted, any interface name will match. +.TP +[\fB!\fP] \fB\-o\fP, \fB\-\-out\-interface\fP \fIname\fP +Name of an interface via which a packet is going to be sent (for packets +entering the \fBFORWARD\fP, \fBOUTPUT\fP and \fBPOSTROUTING\fP +chains). When the "!" argument is used before the interface name, the +sense is inverted. If the interface name ends in a "+", then any +interface which begins with this name will match. If this option is +omitted, any interface name will match. +.\" Currently not supported (header-based) +.\" .TP +.\" [\fB!\fP] \fB\-f\fP, \fB\-\-fragment\fP +.\" This means that the rule only refers to second and further fragments +.\" of fragmented packets. Since there is no way to tell the source or +.\" destination ports of such a packet (or ICMP type), such a packet will +.\" not match any rules which specify them. When the "!" argument +.\" precedes the "\-f" flag, the rule will only match head fragments, or +.\" unfragmented packets. +.TP +\fB\-c\fP, \fB\-\-set\-counters\fP \fIpackets bytes\fP +This enables the administrator to initialize the packet and byte +counters of a rule (during \fBINSERT\fP, \fBAPPEND\fP, \fBREPLACE\fP +operations). +.SS "OTHER OPTIONS" +The following additional options can be specified: +.TP +\fB\-v\fP, \fB\-\-verbose\fP +Verbose output. This option makes the list command show the interface +name, the rule options (if any), and the TOS masks. The packet and +byte counters are also listed, with the suffix 'K', 'M' or 'G' for +1000, 1,000,000 and 1,000,000,000 multipliers respectively (but see +the \fB\-x\fP flag to change this). +For appending, insertion, deletion and replacement, this causes +detailed information on the rule or rules to be printed. +.TP +\fB\-n\fP, \fB\-\-numeric\fP +Numeric output. +IP addresses and port numbers will be printed in numeric format. +By default, the program will try to display them as host names, +network names, or services (whenever applicable). +.TP +\fB\-x\fP, \fB\-\-exact\fP +Expand numbers. +Display the exact value of the packet and byte counters, +instead of only the rounded number in K's (multiples of 1000) +M's (multiples of 1000K) or G's (multiples of 1000M). This option is +only relevant for the \fB\-L\fP command. +.TP +\fB\-\-line\-numbers\fP +When listing rules, add line numbers to the beginning of each rule, +corresponding to that rule's position in the chain. +.TP +\fB\-\-modprobe=\fP\fIcommand\fP +When adding or inserting rules into a chain, use \fIcommand\fP +to load any necessary modules (targets, match extensions, etc). +.SH MATCH EXTENSIONS +ip6tables can use extended packet matching modules. These are loaded +in two ways: implicitly, when \fB\-p\fP or \fB\-\-protocol\fP +is specified, or with the \fB\-m\fP or \fB\-\-match\fP +options, followed by the matching module name; after these, various +extra command line options become available, depending on the specific +module. You can specify multiple extended match modules in one line, +and you can use the \fB\-h\fP or \fB\-\-help\fP +options after the module has been specified to receive help specific +to that module. +.PP +The following are included in the base package, and most of these can +be preceded by a "\fB!\fP" to invert the sense of the match. +.\" @MATCH@ +.SH TARGET EXTENSIONS +ip6tables can use extended target modules: the following are included +in the standard distribution. +.\" @TARGET@ +.SH DIAGNOSTICS +Various error messages are printed to standard error. The exit code +is 0 for correct functioning. Errors which appear to be caused by +invalid or abused command line parameters cause an exit code of 2, and +other errors cause an exit code of 1. +.SH BUGS +Bugs? What's this? ;-) +Well... the counters are not reliable on sparc64. +.SH COMPATIBILITY WITH IPCHAINS +This \fBip6tables\fP +is very similar to ipchains by Rusty Russell. The main difference is +that the chains \fBINPUT\fP and \fBOUTPUT\fP +are only traversed for packets coming into the local host and +originating from the local host respectively. Hence every packet only +passes through one of the three chains (except loopback traffic, which +involves both INPUT and OUTPUT chains); previously a forwarded packet +would pass through all three. +.PP +The other main difference is that \fB\-i\fP refers to the input interface; +\fB\-o\fP refers to the output interface, and both are available for packets +entering the \fBFORWARD\fP chain. +There are several other changes in ip6tables. +.SH SEE ALSO +\fBip6tables\-save\fP(8), +\fBip6tables\-restore\fP(8), +\fBiptables\fP(8), +\fBiptables\-save\fP(8), +\fBiptables\-restore\fP(8), +\fBlibipq\fP(3). +.PP +The packet-filtering-HOWTO details iptables usage for +packet filtering, +the netfilter-extensions-HOWTO details the extensions that are +not in the standard distribution, +and the netfilter-hacking-HOWTO details the netfilter internals. +.br +See +.BR "http://www.netfilter.org/" . +.SH AUTHORS +Rusty Russell wrote iptables, in early consultation with Michael +Neuling. +.PP +Marc Boucher made Rusty abandon ipnatctl by lobbying for a generic packet +selection framework in iptables, then wrote the mangle table, the owner match, +the mark stuff, and ran around doing cool stuff everywhere. +.PP +James Morris wrote the TOS target, and tos match. +.PP +Jozsef Kadlecsik wrote the REJECT target. +.PP +Harald Welte wrote the ULOG and NFQUEUE target, the new libiptc, as well as TTL match+target and libipulog. +.PP +The Netfilter Core Team is: Marc Boucher, Martin Josefsson, Yasuyuki Kozakai, +Jozsef Kadlecsik, Patrick McHardy, James Morris, Pablo Neira Ayuso, +Harald Welte and Rusty Russell. +.PP +ip6tables man page created by Andras Kis-Szabo, based on +iptables man page written by Herve Eychenne . +.\" .. and did I mention that we are incredibly cool people? +.\" .. sexy, too .. +.\" .. witty, charming, powerful .. +.\" .. and most of all, modest .. +.SH VERSION +.PP +This manual page applies to ip6tables @PACKAGE_VERSION@. diff --git a/iptables/ip6tables.c b/iptables/ip6tables.c new file mode 100644 index 00000000..4df73b8d --- /dev/null +++ b/iptables/ip6tables.c @@ -0,0 +1,1967 @@ +/* Code to take an ip6tables-style command line and do it. */ + +/* + * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au + * + * (C) 2000-2002 by the netfilter coreteam : + * Paul 'Rusty' Russell + * Marc Boucher + * James Morris + * Harald Welte + * Jozsef Kadlecsik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ip6tables-multi.h" +#include "xshared.h" + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#define FMT_NUMERIC 0x0001 +#define FMT_NOCOUNTS 0x0002 +#define FMT_KILOMEGAGIGA 0x0004 +#define FMT_OPTIONS 0x0008 +#define FMT_NOTABLE 0x0010 +#define FMT_NOTARGET 0x0020 +#define FMT_VIA 0x0040 +#define FMT_NONEWLINE 0x0080 +#define FMT_LINENUMBERS 0x0100 + +#define FMT_PRINT_RULE (FMT_NOCOUNTS | FMT_OPTIONS | FMT_VIA \ + | FMT_NUMERIC | FMT_NOTABLE) +#define FMT(tab,notab) ((format) & FMT_NOTABLE ? (notab) : (tab)) + + +#define CMD_NONE 0x0000U +#define CMD_INSERT 0x0001U +#define CMD_DELETE 0x0002U +#define CMD_DELETE_NUM 0x0004U +#define CMD_REPLACE 0x0008U +#define CMD_APPEND 0x0010U +#define CMD_LIST 0x0020U +#define CMD_FLUSH 0x0040U +#define CMD_ZERO 0x0080U +#define CMD_NEW_CHAIN 0x0100U +#define CMD_DELETE_CHAIN 0x0200U +#define CMD_SET_POLICY 0x0400U +#define CMD_RENAME_CHAIN 0x0800U +#define CMD_LIST_RULES 0x1000U +#define CMD_ZERO_NUM 0x2000U +#define CMD_CHECK 0x4000U +#define NUMBER_OF_CMD 16 +static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z', + 'Z', 'N', 'X', 'P', 'E', 'S', 'C' }; + +#define NUMBER_OF_OPT ARRAY_SIZE(optflags) +static const char optflags[] += { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', '0', 'c'}; + +static struct option original_opts[] = { + {.name = "append", .has_arg = 1, .val = 'A'}, + {.name = "delete", .has_arg = 1, .val = 'D'}, + {.name = "check" , .has_arg = 1, .val = 'C'}, + {.name = "insert", .has_arg = 1, .val = 'I'}, + {.name = "replace", .has_arg = 1, .val = 'R'}, + {.name = "list", .has_arg = 2, .val = 'L'}, + {.name = "list-rules", .has_arg = 2, .val = 'S'}, + {.name = "flush", .has_arg = 2, .val = 'F'}, + {.name = "zero", .has_arg = 2, .val = 'Z'}, + {.name = "new-chain", .has_arg = 1, .val = 'N'}, + {.name = "delete-chain", .has_arg = 2, .val = 'X'}, + {.name = "rename-chain", .has_arg = 1, .val = 'E'}, + {.name = "policy", .has_arg = 1, .val = 'P'}, + {.name = "source", .has_arg = 1, .val = 's'}, + {.name = "destination", .has_arg = 1, .val = 'd'}, + {.name = "src", .has_arg = 1, .val = 's'}, /* synonym */ + {.name = "dst", .has_arg = 1, .val = 'd'}, /* synonym */ + {.name = "protocol", .has_arg = 1, .val = 'p'}, + {.name = "in-interface", .has_arg = 1, .val = 'i'}, + {.name = "jump", .has_arg = 1, .val = 'j'}, + {.name = "table", .has_arg = 1, .val = 't'}, + {.name = "match", .has_arg = 1, .val = 'm'}, + {.name = "numeric", .has_arg = 0, .val = 'n'}, + {.name = "out-interface", .has_arg = 1, .val = 'o'}, + {.name = "verbose", .has_arg = 0, .val = 'v'}, + {.name = "exact", .has_arg = 0, .val = 'x'}, + {.name = "version", .has_arg = 0, .val = 'V'}, + {.name = "help", .has_arg = 2, .val = 'h'}, + {.name = "line-numbers", .has_arg = 0, .val = '0'}, + {.name = "modprobe", .has_arg = 1, .val = 'M'}, + {.name = "set-counters", .has_arg = 1, .val = 'c'}, + {.name = "goto", .has_arg = 1, .val = 'g'}, + {.name = "ipv4", .has_arg = 0, .val = '4'}, + {.name = "ipv6", .has_arg = 0, .val = '6'}, + {NULL}, +}; + +void ip6tables_exit_error(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3))); +struct xtables_globals ip6tables_globals = { + .option_offset = 0, + .program_version = IPTABLES_VERSION, + .orig_opts = original_opts, + .exit_err = ip6tables_exit_error, +}; + +/* Table of legal combinations of commands and options. If any of the + * given commands make an option legal, that option is legal (applies to + * CMD_LIST and CMD_ZERO only). + * Key: + * + compulsory + * x illegal + * optional + */ + +static const char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] = +/* Well, it's better than "Re: Linux vs FreeBSD" */ +{ + /* -n -s -d -p -j -v -x -i -o --line -c */ +/*INSERT*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' '}, +/*DELETE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x'}, +/*DELETE_NUM*/{'x','x','x','x','x',' ','x','x','x','x','x'}, +/*REPLACE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' '}, +/*APPEND*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' '}, +/*LIST*/ {' ','x','x','x','x',' ',' ','x','x',' ','x'}, +/*FLUSH*/ {'x','x','x','x','x',' ','x','x','x','x','x'}, +/*ZERO*/ {'x','x','x','x','x',' ','x','x','x','x','x'}, +/*ZERO_NUM*/ {'x','x','x','x','x',' ','x','x','x','x','x'}, +/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x'}, +/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x'}, +/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x',' '}, +/*RENAME*/ {'x','x','x','x','x',' ','x','x','x','x','x'}, +/*LIST_RULES*/{'x','x','x','x','x',' ','x','x','x','x','x'}, +/*CHECK*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x'}, +}; + +static const unsigned int inverse_for_options[NUMBER_OF_OPT] = +{ +/* -n */ 0, +/* -s */ IP6T_INV_SRCIP, +/* -d */ IP6T_INV_DSTIP, +/* -p */ IP6T_INV_PROTO, +/* -j */ 0, +/* -v */ 0, +/* -x */ 0, +/* -i */ IP6T_INV_VIA_IN, +/* -o */ IP6T_INV_VIA_OUT, +/*--line*/ 0, +/* -c */ 0, +}; + +#define opts ip6tables_globals.opts +#define prog_name ip6tables_globals.program_name +#define prog_vers ip6tables_globals.program_version +/* A few hardcoded protocols for 'all' and in case the user has no + /etc/protocols */ +struct pprot { + const char *name; + uint8_t num; +}; + +static void __attribute__((noreturn)) +exit_tryhelp(int status) +{ + if (line != -1) + fprintf(stderr, "Error occurred at line: %d\n", line); + fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n", + prog_name, prog_name); + xtables_free_opts(1); + exit(status); +} + +static void +exit_printhelp(const struct xtables_rule_match *matches) +{ + printf("%s v%s\n\n" +"Usage: %s -[ACD] chain rule-specification [options]\n" +" %s -I chain [rulenum] rule-specification [options]\n" +" %s -R chain rulenum rule-specification [options]\n" +" %s -D chain rulenum [options]\n" +" %s -[LS] [chain [rulenum]] [options]\n" +" %s -[FZ] [chain] [options]\n" +" %s -[NX] chain\n" +" %s -E old-chain-name new-chain-name\n" +" %s -P chain target [options]\n" +" %s -h (print this help information)\n\n", + prog_name, prog_vers, prog_name, prog_name, + prog_name, prog_name, prog_name, prog_name, + prog_name, prog_name, prog_name, prog_name); + + printf( +"Commands:\n" +"Either long or short options are allowed.\n" +" --append -A chain Append to chain\n" +" --check -C chain Check for the existence of a rule\n" +" --delete -D chain Delete matching rule from chain\n" +" --delete -D chain rulenum\n" +" Delete rule rulenum (1 = first) from chain\n" +" --insert -I chain [rulenum]\n" +" Insert in chain as rulenum (default 1=first)\n" +" --replace -R chain rulenum\n" +" Replace rule rulenum (1 = first) in chain\n" +" --list -L [chain [rulenum]]\n" +" List the rules in a chain or all chains\n" +" --list-rules -S [chain [rulenum]]\n" +" Print the rules in a chain or all chains\n" +" --flush -F [chain] Delete all rules in chain or all chains\n" +" --zero -Z [chain [rulenum]]\n" +" Zero counters in chain or all chains\n" +" --new -N chain Create a new user-defined chain\n" +" --delete-chain\n" +" -X [chain] Delete a user-defined chain\n" +" --policy -P chain target\n" +" Change policy on chain to target\n" +" --rename-chain\n" +" -E old-chain new-chain\n" +" Change chain name, (moving any references)\n" + +"Options:\n" +" --ipv4 -4 Error (line is ignored by ip6tables-restore)\n" +" --ipv6 -6 Nothing (line is ignored by iptables-restore)\n" +"[!] --proto -p proto protocol: by number or name, eg. `tcp'\n" +"[!] --source -s address[/mask][,...]\n" +" source specification\n" +"[!] --destination -d address[/mask][,...]\n" +" destination specification\n" +"[!] --in-interface -i input name[+]\n" +" network interface name ([+] for wildcard)\n" +" --jump -j target\n" +" target for rule (may load target extension)\n" +#ifdef IP6T_F_GOTO +" --goto -g chain\n" +" jump to chain with no return\n" +#endif +" --match -m match\n" +" extended match (may load extension)\n" +" --numeric -n numeric output of addresses and ports\n" +"[!] --out-interface -o output name[+]\n" +" network interface name ([+] for wildcard)\n" +" --table -t table table to manipulate (default: `filter')\n" +" --verbose -v verbose mode\n" +" --line-numbers print line numbers when listing\n" +" --exact -x expand numbers (display exact values)\n" +/*"[!] --fragment -f match second or further fragments only\n"*/ +" --modprobe= try to insert modules using this command\n" +" --set-counters PKTS BYTES set the counter during insert/append\n" +"[!] --version -V print package version.\n"); + + print_extension_helps(xtables_targets, matches); + exit(0); +} + +void +ip6tables_exit_error(enum xtables_exittype status, const char *msg, ...) +{ + va_list args; + + va_start(args, msg); + fprintf(stderr, "%s v%s: ", prog_name, prog_vers); + vfprintf(stderr, msg, args); + va_end(args); + fprintf(stderr, "\n"); + if (status == PARAMETER_PROBLEM) + exit_tryhelp(status); + if (status == VERSION_PROBLEM) + fprintf(stderr, + "Perhaps ip6tables or your kernel needs to be upgraded.\n"); + /* On error paths, make sure that we don't leak memory */ + xtables_free_opts(1); + exit(status); +} + +static void +generic_opt_check(int command, int options) +{ + int i, j, legal = 0; + + /* Check that commands are valid with options. Complicated by the + * fact that if an option is legal with *any* command given, it is + * legal overall (ie. -z and -l). + */ + for (i = 0; i < NUMBER_OF_OPT; i++) { + legal = 0; /* -1 => illegal, 1 => legal, 0 => undecided. */ + + for (j = 0; j < NUMBER_OF_CMD; j++) { + if (!(command & (1< 1; option >>= 1, ptr++); + + return *ptr; +} + +static char +cmd2char(int option) +{ + const char *ptr; + for (ptr = cmdflags; option > 1; option >>= 1, ptr++); + + return *ptr; +} + +static void +add_command(unsigned int *cmd, const int newcmd, const int othercmds, + int invert) +{ + if (invert) + xtables_error(PARAMETER_PROBLEM, "unexpected '!' flag"); + if (*cmd & (~othercmds)) + xtables_error(PARAMETER_PROBLEM, "Cannot use -%c with -%c\n", + cmd2char(newcmd), cmd2char(*cmd & (~othercmds))); + *cmd |= newcmd; +} + +/* + * All functions starting with "parse" should succeed, otherwise + * the program fails. + * Most routines return pointers to static data that may change + * between calls to the same or other routines with a few exceptions: + * "host_to_addr", "parse_hostnetwork", and "parse_hostnetworkmask" + * return global static data. +*/ + +/* These are invalid numbers as upper layer protocol */ +static int is_exthdr(uint16_t proto) +{ + return (proto == IPPROTO_ROUTING || + proto == IPPROTO_FRAGMENT || + proto == IPPROTO_AH || + proto == IPPROTO_DSTOPTS); +} + +/* Can't be zero. */ +static int +parse_rulenumber(const char *rule) +{ + unsigned int rulenum; + + if (!xtables_strtoui(rule, NULL, &rulenum, 1, INT_MAX)) + xtables_error(PARAMETER_PROBLEM, + "Invalid rule number `%s'", rule); + + return rulenum; +} + +static const char * +parse_target(const char *targetname) +{ + const char *ptr; + + if (strlen(targetname) < 1) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name (too short)"); + + if (strlen(targetname) >= XT_EXTENSION_MAXNAMELEN) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name `%s' (%u chars max)", + targetname, XT_EXTENSION_MAXNAMELEN - 1); + + for (ptr = targetname; *ptr; ptr++) + if (isspace(*ptr)) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name `%s'", targetname); + return targetname; +} + +static void +set_option(unsigned int *options, unsigned int option, uint8_t *invflg, + int invert) +{ + if (*options & option) + xtables_error(PARAMETER_PROBLEM, "multiple -%c flags not allowed", + opt2char(option)); + *options |= option; + + if (invert) { + unsigned int i; + for (i = 0; 1 << i != option; i++); + + if (!inverse_for_options[i]) + xtables_error(PARAMETER_PROBLEM, + "cannot have ! before -%c", + opt2char(option)); + *invflg |= inverse_for_options[i]; + } +} + +static void +print_num(uint64_t number, unsigned int format) +{ + if (format & FMT_KILOMEGAGIGA) { + if (number > 99999) { + number = (number + 500) / 1000; + if (number > 9999) { + number = (number + 500) / 1000; + if (number > 9999) { + number = (number + 500) / 1000; + if (number > 9999) { + number = (number + 500) / 1000; + printf(FMT("%4lluT ","%lluT "), (unsigned long long)number); + } + else printf(FMT("%4lluG ","%lluG "), (unsigned long long)number); + } + else printf(FMT("%4lluM ","%lluM "), (unsigned long long)number); + } else + printf(FMT("%4lluK ","%lluK "), (unsigned long long)number); + } else + printf(FMT("%5llu ","%llu "), (unsigned long long)number); + } else + printf(FMT("%8llu ","%llu "), (unsigned long long)number); +} + + +static void +print_header(unsigned int format, const char *chain, struct ip6tc_handle *handle) +{ + struct ip6t_counters counters; + const char *pol = ip6tc_get_policy(chain, &counters, handle); + printf("Chain %s", chain); + if (pol) { + printf(" (policy %s", pol); + if (!(format & FMT_NOCOUNTS)) { + fputc(' ', stdout); + print_num(counters.pcnt, (format|FMT_NOTABLE)); + fputs("packets, ", stdout); + print_num(counters.bcnt, (format|FMT_NOTABLE)); + fputs("bytes", stdout); + } + printf(")\n"); + } else { + unsigned int refs; + if (!ip6tc_get_references(&refs, chain, handle)) + printf(" (ERROR obtaining refs)\n"); + else + printf(" (%u references)\n", refs); + } + + if (format & FMT_LINENUMBERS) + printf(FMT("%-4s ", "%s "), "num"); + if (!(format & FMT_NOCOUNTS)) { + if (format & FMT_KILOMEGAGIGA) { + printf(FMT("%5s ","%s "), "pkts"); + printf(FMT("%5s ","%s "), "bytes"); + } else { + printf(FMT("%8s ","%s "), "pkts"); + printf(FMT("%10s ","%s "), "bytes"); + } + } + if (!(format & FMT_NOTARGET)) + printf(FMT("%-9s ","%s "), "target"); + fputs(" prot ", stdout); + if (format & FMT_OPTIONS) + fputs("opt", stdout); + if (format & FMT_VIA) { + printf(FMT(" %-6s ","%s "), "in"); + printf(FMT("%-6s ","%s "), "out"); + } + printf(FMT(" %-19s ","%s "), "source"); + printf(FMT(" %-19s "," %s "), "destination"); + printf("\n"); +} + + +static int +print_match(const struct ip6t_entry_match *m, + const struct ip6t_ip6 *ip, + int numeric) +{ + const struct xtables_match *match = + xtables_find_match(m->u.user.name, XTF_TRY_LOAD, NULL); + + if (match) { + if (match->print) + match->print(ip, m, numeric); + else + printf("%s ", match->name); + } else { + if (m->u.user.name[0]) + printf("UNKNOWN match `%s' ", m->u.user.name); + } + /* Don't stop iterating. */ + return 0; +} + +/* e is called `fw' here for historical reasons */ +static void +print_firewall(const struct ip6t_entry *fw, + const char *targname, + unsigned int num, + unsigned int format, + struct ip6tc_handle *const handle) +{ + const struct xtables_target *target = NULL; + const struct ip6t_entry_target *t; + char buf[BUFSIZ]; + + if (!ip6tc_is_chain(targname, handle)) + target = xtables_find_target(targname, XTF_TRY_LOAD); + else + target = xtables_find_target(IP6T_STANDARD_TARGET, + XTF_LOAD_MUST_SUCCEED); + + t = ip6t_get_target((struct ip6t_entry *)fw); + + if (format & FMT_LINENUMBERS) + printf(FMT("%-4u ", "%u "), num); + + if (!(format & FMT_NOCOUNTS)) { + print_num(fw->counters.pcnt, format); + print_num(fw->counters.bcnt, format); + } + + if (!(format & FMT_NOTARGET)) + printf(FMT("%-9s ", "%s "), targname); + + fputc(fw->ipv6.invflags & IP6T_INV_PROTO ? '!' : ' ', stdout); + { + const char *pname = proto_to_name(fw->ipv6.proto, format&FMT_NUMERIC); + if (pname) + printf(FMT("%-5s", "%s "), pname); + else + printf(FMT("%-5hu", "%hu "), fw->ipv6.proto); + } + + if (format & FMT_OPTIONS) { + if (format & FMT_NOTABLE) + fputs("opt ", stdout); + fputc(' ', stdout); /* Invert flag of FRAG */ + fputc(' ', stdout); /* -f */ + fputc(' ', stdout); + } + + if (format & FMT_VIA) { + char iface[IFNAMSIZ+2]; + + if (fw->ipv6.invflags & IP6T_INV_VIA_IN) { + iface[0] = '!'; + iface[1] = '\0'; + } + else iface[0] = '\0'; + + if (fw->ipv6.iniface[0] != '\0') { + strcat(iface, fw->ipv6.iniface); + } + else if (format & FMT_NUMERIC) strcat(iface, "*"); + else strcat(iface, "any"); + printf(FMT(" %-6s ","in %s "), iface); + + if (fw->ipv6.invflags & IP6T_INV_VIA_OUT) { + iface[0] = '!'; + iface[1] = '\0'; + } + else iface[0] = '\0'; + + if (fw->ipv6.outiface[0] != '\0') { + strcat(iface, fw->ipv6.outiface); + } + else if (format & FMT_NUMERIC) strcat(iface, "*"); + else strcat(iface, "any"); + printf(FMT("%-6s ","out %s "), iface); + } + + fputc(fw->ipv6.invflags & IP6T_INV_SRCIP ? '!' : ' ', stdout); + if (!memcmp(&fw->ipv6.smsk, &in6addr_any, sizeof in6addr_any) + && !(format & FMT_NUMERIC)) + printf(FMT("%-19s ","%s "), "anywhere"); + else { + if (format & FMT_NUMERIC) + strcpy(buf, xtables_ip6addr_to_numeric(&fw->ipv6.src)); + else + strcpy(buf, xtables_ip6addr_to_anyname(&fw->ipv6.src)); + strcat(buf, xtables_ip6mask_to_numeric(&fw->ipv6.smsk)); + printf(FMT("%-19s ","%s "), buf); + } + + fputc(fw->ipv6.invflags & IP6T_INV_DSTIP ? '!' : ' ', stdout); + if (!memcmp(&fw->ipv6.dmsk, &in6addr_any, sizeof in6addr_any) + && !(format & FMT_NUMERIC)) + printf(FMT("%-19s ","-> %s"), "anywhere"); + else { + if (format & FMT_NUMERIC) + strcpy(buf, xtables_ip6addr_to_numeric(&fw->ipv6.dst)); + else + strcpy(buf, xtables_ip6addr_to_anyname(&fw->ipv6.dst)); + strcat(buf, xtables_ip6mask_to_numeric(&fw->ipv6.dmsk)); + printf(FMT("%-19s ","-> %s"), buf); + } + + if (format & FMT_NOTABLE) + fputs(" ", stdout); + +#ifdef IP6T_F_GOTO + if(fw->ipv6.flags & IP6T_F_GOTO) + printf("[goto] "); +#endif + + IP6T_MATCH_ITERATE(fw, print_match, &fw->ipv6, format & FMT_NUMERIC); + + if (target) { + if (target->print) + /* Print the target information. */ + target->print(&fw->ipv6, t, format & FMT_NUMERIC); + } else if (t->u.target_size != sizeof(*t)) + printf("[%u bytes of unknown target data] ", + (unsigned int)(t->u.target_size - sizeof(*t))); + + if (!(format & FMT_NONEWLINE)) + fputc('\n', stdout); +} + +static void +print_firewall_line(const struct ip6t_entry *fw, + struct ip6tc_handle *const h) +{ + struct ip6t_entry_target *t; + + t = ip6t_get_target((struct ip6t_entry *)fw); + print_firewall(fw, t->u.user.name, 0, FMT_PRINT_RULE, h); +} + +static int +append_entry(const ip6t_chainlabel chain, + struct ip6t_entry *fw, + unsigned int nsaddrs, + const struct in6_addr saddrs[], + const struct in6_addr smasks[], + unsigned int ndaddrs, + const struct in6_addr daddrs[], + const struct in6_addr dmasks[], + int verbose, + struct ip6tc_handle *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ipv6.src = saddrs[i]; + fw->ipv6.smsk = smasks[i]; + for (j = 0; j < ndaddrs; j++) { + fw->ipv6.dst = daddrs[j]; + fw->ipv6.dmsk = dmasks[j]; + if (verbose) + print_firewall_line(fw, handle); + ret &= ip6tc_append_entry(chain, fw, handle); + } + } + + return ret; +} + +static int +replace_entry(const ip6t_chainlabel chain, + struct ip6t_entry *fw, + unsigned int rulenum, + const struct in6_addr *saddr, const struct in6_addr *smask, + const struct in6_addr *daddr, const struct in6_addr *dmask, + int verbose, + struct ip6tc_handle *handle) +{ + fw->ipv6.src = *saddr; + fw->ipv6.dst = *daddr; + fw->ipv6.smsk = *smask; + fw->ipv6.dmsk = *dmask; + + if (verbose) + print_firewall_line(fw, handle); + return ip6tc_replace_entry(chain, fw, rulenum, handle); +} + +static int +insert_entry(const ip6t_chainlabel chain, + struct ip6t_entry *fw, + unsigned int rulenum, + unsigned int nsaddrs, + const struct in6_addr saddrs[], + const struct in6_addr smasks[], + unsigned int ndaddrs, + const struct in6_addr daddrs[], + const struct in6_addr dmasks[], + int verbose, + struct ip6tc_handle *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ipv6.src = saddrs[i]; + fw->ipv6.smsk = smasks[i]; + for (j = 0; j < ndaddrs; j++) { + fw->ipv6.dst = daddrs[j]; + fw->ipv6.dmsk = dmasks[j]; + if (verbose) + print_firewall_line(fw, handle); + ret &= ip6tc_insert_entry(chain, fw, rulenum, handle); + } + } + + return ret; +} + +static unsigned char * +make_delete_mask(const struct xtables_rule_match *matches, + const struct xtables_target *target) +{ + /* Establish mask for comparison */ + unsigned int size; + const struct xtables_rule_match *matchp; + unsigned char *mask, *mptr; + + size = sizeof(struct ip6t_entry); + for (matchp = matches; matchp; matchp = matchp->next) + size += XT_ALIGN(sizeof(struct ip6t_entry_match)) + matchp->match->size; + + mask = xtables_calloc(1, size + + XT_ALIGN(sizeof(struct ip6t_entry_target)) + + target->size); + + memset(mask, 0xFF, sizeof(struct ip6t_entry)); + mptr = mask + sizeof(struct ip6t_entry); + + for (matchp = matches; matchp; matchp = matchp->next) { + memset(mptr, 0xFF, + XT_ALIGN(sizeof(struct ip6t_entry_match)) + + matchp->match->userspacesize); + mptr += XT_ALIGN(sizeof(struct ip6t_entry_match)) + matchp->match->size; + } + + memset(mptr, 0xFF, + XT_ALIGN(sizeof(struct ip6t_entry_target)) + + target->userspacesize); + + return mask; +} + +static int +delete_entry(const ip6t_chainlabel chain, + struct ip6t_entry *fw, + unsigned int nsaddrs, + const struct in6_addr saddrs[], + const struct in6_addr smasks[], + unsigned int ndaddrs, + const struct in6_addr daddrs[], + const struct in6_addr dmasks[], + int verbose, + struct ip6tc_handle *handle, + struct xtables_rule_match *matches, + const struct xtables_target *target) +{ + unsigned int i, j; + int ret = 1; + unsigned char *mask; + + mask = make_delete_mask(matches, target); + for (i = 0; i < nsaddrs; i++) { + fw->ipv6.src = saddrs[i]; + fw->ipv6.smsk = smasks[i]; + for (j = 0; j < ndaddrs; j++) { + fw->ipv6.dst = daddrs[j]; + fw->ipv6.dmsk = dmasks[j]; + if (verbose) + print_firewall_line(fw, handle); + ret &= ip6tc_delete_entry(chain, fw, mask, handle); + } + } + free(mask); + + return ret; +} + +static int +check_entry(const ip6t_chainlabel chain, struct ip6t_entry *fw, + unsigned int nsaddrs, const struct in6_addr *saddrs, + const struct in6_addr *smasks, unsigned int ndaddrs, + const struct in6_addr *daddrs, const struct in6_addr *dmasks, + bool verbose, struct ip6tc_handle *handle, + struct xtables_rule_match *matches, + const struct xtables_target *target) +{ + unsigned int i, j; + int ret = 1; + unsigned char *mask; + + mask = make_delete_mask(matches, target); + for (i = 0; i < nsaddrs; i++) { + fw->ipv6.src = saddrs[i]; + fw->ipv6.smsk = smasks[i]; + for (j = 0; j < ndaddrs; j++) { + fw->ipv6.dst = daddrs[j]; + fw->ipv6.dmsk = dmasks[j]; + if (verbose) + print_firewall_line(fw, handle); + ret &= ip6tc_check_entry(chain, fw, mask, handle); + } + } + + free(mask); + return ret; +} + +int +for_each_chain6(int (*fn)(const ip6t_chainlabel, int, struct ip6tc_handle *), + int verbose, int builtinstoo, struct ip6tc_handle *handle) +{ + int ret = 1; + const char *chain; + char *chains; + unsigned int i, chaincount = 0; + + chain = ip6tc_first_chain(handle); + while (chain) { + chaincount++; + chain = ip6tc_next_chain(handle); + } + + chains = xtables_malloc(sizeof(ip6t_chainlabel) * chaincount); + i = 0; + chain = ip6tc_first_chain(handle); + while (chain) { + strcpy(chains + i*sizeof(ip6t_chainlabel), chain); + i++; + chain = ip6tc_next_chain(handle); + } + + for (i = 0; i < chaincount; i++) { + if (!builtinstoo + && ip6tc_builtin(chains + i*sizeof(ip6t_chainlabel), + handle) == 1) + continue; + ret &= fn(chains + i*sizeof(ip6t_chainlabel), verbose, handle); + } + + free(chains); + return ret; +} + +int +flush_entries6(const ip6t_chainlabel chain, int verbose, + struct ip6tc_handle *handle) +{ + if (!chain) + return for_each_chain6(flush_entries6, verbose, 1, handle); + + if (verbose) + fprintf(stdout, "Flushing chain `%s'\n", chain); + return ip6tc_flush_entries(chain, handle); +} + +static int +zero_entries(const ip6t_chainlabel chain, int verbose, + struct ip6tc_handle *handle) +{ + if (!chain) + return for_each_chain6(zero_entries, verbose, 1, handle); + + if (verbose) + fprintf(stdout, "Zeroing chain `%s'\n", chain); + return ip6tc_zero_entries(chain, handle); +} + +int +delete_chain6(const ip6t_chainlabel chain, int verbose, + struct ip6tc_handle *handle) +{ + if (!chain) + return for_each_chain6(delete_chain6, verbose, 0, handle); + + if (verbose) + fprintf(stdout, "Deleting chain `%s'\n", chain); + return ip6tc_delete_chain(chain, handle); +} + +static int +list_entries(const ip6t_chainlabel chain, int rulenum, int verbose, int numeric, + int expanded, int linenumbers, struct ip6tc_handle *handle) +{ + int found = 0; + unsigned int format; + const char *this; + + format = FMT_OPTIONS; + if (!verbose) + format |= FMT_NOCOUNTS; + else + format |= FMT_VIA; + + if (numeric) + format |= FMT_NUMERIC; + + if (!expanded) + format |= FMT_KILOMEGAGIGA; + + if (linenumbers) + format |= FMT_LINENUMBERS; + + for (this = ip6tc_first_chain(handle); + this; + this = ip6tc_next_chain(handle)) { + const struct ip6t_entry *i; + unsigned int num; + + if (chain && strcmp(chain, this) != 0) + continue; + + if (found) printf("\n"); + + if (!rulenum) + print_header(format, this, handle); + i = ip6tc_first_rule(this, handle); + + num = 0; + while (i) { + num++; + if (!rulenum || num == rulenum) + print_firewall(i, + ip6tc_get_target(i, handle), + num, + format, + handle); + i = ip6tc_next_rule(i, handle); + } + found = 1; + } + + errno = ENOENT; + return found; +} + +/* This assumes that mask is contiguous, and byte-bounded. */ +static void +print_iface(char letter, const char *iface, const unsigned char *mask, + int invert) +{ + unsigned int i; + + if (mask[0] == 0) + return; + + printf("%s -%c ", invert ? " !" : "", letter); + + for (i = 0; i < IFNAMSIZ; i++) { + if (mask[i] != 0) { + if (iface[i] != '\0') + printf("%c", iface[i]); + } else { + /* we can access iface[i-1] here, because + * a few lines above we make sure that mask[0] != 0 */ + if (iface[i-1] != '\0') + printf("+"); + break; + } + } +} + +/* The ip6tables looks up the /etc/protocols. */ +static void print_proto(uint16_t proto, int invert) +{ + if (proto) { + unsigned int i; + const char *invertstr = invert ? " !" : ""; + + const struct protoent *pent = getprotobynumber(proto); + if (pent) { + printf("%s -p %s", + invertstr, pent->p_name); + return; + } + + for (i = 0; xtables_chain_protos[i].name != NULL; ++i) + if (xtables_chain_protos[i].num == proto) { + printf("%s -p %s", + invertstr, xtables_chain_protos[i].name); + return; + } + + printf("%s -p %u", invertstr, proto); + } +} + +static int print_match_save(const struct ip6t_entry_match *e, + const struct ip6t_ip6 *ip) +{ + const struct xtables_match *match = + xtables_find_match(e->u.user.name, XTF_TRY_LOAD, NULL); + + if (match) { + printf(" -m %s", e->u.user.name); + + /* some matches don't provide a save function */ + if (match->save) + match->save(ip, e); + } else { + if (e->u.match_size) { + fprintf(stderr, + "Can't find library for match `%s'\n", + e->u.user.name); + exit(1); + } + } + return 0; +} + +/* print a given ip including mask if neccessary */ +static void print_ip(const char *prefix, const struct in6_addr *ip, + const struct in6_addr *mask, int invert) +{ + char buf[51]; + int l = ipv6_prefix_length(mask); + + if (l == 0 && !invert) + return; + + printf("%s %s %s", + invert ? " !" : "", + prefix, + inet_ntop(AF_INET6, ip, buf, sizeof buf)); + + if (l == -1) + printf("/%s", inet_ntop(AF_INET6, mask, buf, sizeof buf)); + else + printf("/%d", l); +} + +/* We want this to be readable, so only print out neccessary fields. + * Because that's the kind of world I want to live in. */ +void print_rule6(const struct ip6t_entry *e, + struct ip6tc_handle *h, const char *chain, int counters) +{ + const struct ip6t_entry_target *t; + const char *target_name; + + /* print counters for iptables-save */ + if (counters > 0) + printf("[%llu:%llu] ", (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt); + + /* print chain name */ + printf("-A %s", chain); + + /* Print IP part. */ + print_ip("-s", &(e->ipv6.src), &(e->ipv6.smsk), + e->ipv6.invflags & IP6T_INV_SRCIP); + + print_ip("-d", &(e->ipv6.dst), &(e->ipv6.dmsk), + e->ipv6.invflags & IP6T_INV_DSTIP); + + print_iface('i', e->ipv6.iniface, e->ipv6.iniface_mask, + e->ipv6.invflags & IP6T_INV_VIA_IN); + + print_iface('o', e->ipv6.outiface, e->ipv6.outiface_mask, + e->ipv6.invflags & IP6T_INV_VIA_OUT); + + print_proto(e->ipv6.proto, e->ipv6.invflags & IP6T_INV_PROTO); + +#if 0 + /* not definied in ipv6 + * FIXME: linux/netfilter_ipv6/ip6_tables: IP6T_INV_FRAG why definied? */ + if (e->ipv6.flags & IPT_F_FRAG) + printf("%s -f", + e->ipv6.invflags & IP6T_INV_FRAG ? " !" : ""); +#endif + + if (e->ipv6.flags & IP6T_F_TOS) + printf("%s -? %d", + e->ipv6.invflags & IP6T_INV_TOS ? " !" : "", + e->ipv6.tos); + + /* Print matchinfo part */ + if (e->target_offset) { + IP6T_MATCH_ITERATE(e, print_match_save, &e->ipv6); + } + + /* print counters for iptables -R */ + if (counters < 0) + printf(" -c %llu %llu", (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt); + + /* Print target name */ + target_name = ip6tc_get_target(e, h); + if (target_name && (*target_name != '\0')) +#ifdef IP6T_F_GOTO + printf(" -%c %s", e->ipv6.flags & IP6T_F_GOTO ? 'g' : 'j', target_name); +#else + printf(" -j %s", target_name); +#endif + + /* Print targinfo part */ + t = ip6t_get_target((struct ip6t_entry *)e); + if (t->u.user.name[0]) { + struct xtables_target *target = + xtables_find_target(t->u.user.name, XTF_TRY_LOAD); + + if (!target) { + fprintf(stderr, "Can't find library for target `%s'\n", + t->u.user.name); + exit(1); + } + + if (target->save) + target->save(&e->ipv6, t); + else { + /* If the target size is greater than ip6t_entry_target + * there is something to be saved, we just don't know + * how to print it */ + if (t->u.target_size != + sizeof(struct ip6t_entry_target)) { + fprintf(stderr, "Target `%s' is missing " + "save function\n", + t->u.user.name); + exit(1); + } + } + } + printf("\n"); +} + +static int +list_rules(const ip6t_chainlabel chain, int rulenum, int counters, + struct ip6tc_handle *handle) +{ + const char *this = NULL; + int found = 0; + + if (counters) + counters = -1; /* iptables -c format */ + + /* Dump out chain names first, + * thereby preventing dependency conflicts */ + if (!rulenum) for (this = ip6tc_first_chain(handle); + this; + this = ip6tc_next_chain(handle)) { + if (chain && strcmp(this, chain) != 0) + continue; + + if (ip6tc_builtin(this, handle)) { + struct ip6t_counters count; + printf("-P %s %s", this, ip6tc_get_policy(this, &count, handle)); + if (counters) + printf(" -c %llu %llu", (unsigned long long)count.pcnt, (unsigned long long)count.bcnt); + printf("\n"); + } else { + printf("-N %s\n", this); + } + } + + for (this = ip6tc_first_chain(handle); + this; + this = ip6tc_next_chain(handle)) { + const struct ip6t_entry *e; + int num = 0; + + if (chain && strcmp(this, chain) != 0) + continue; + + /* Dump out rules */ + e = ip6tc_first_rule(this, handle); + while(e) { + num++; + if (!rulenum || num == rulenum) + print_rule6(e, handle, this, counters); + e = ip6tc_next_rule(e, handle); + } + found = 1; + } + + errno = ENOENT; + return found; +} + +static struct ip6t_entry * +generate_entry(const struct ip6t_entry *fw, + struct xtables_rule_match *matches, + struct ip6t_entry_target *target) +{ + unsigned int size; + struct xtables_rule_match *matchp; + struct ip6t_entry *e; + + size = sizeof(struct ip6t_entry); + for (matchp = matches; matchp; matchp = matchp->next) + size += matchp->match->m->u.match_size; + + e = xtables_malloc(size + target->u.target_size); + *e = *fw; + e->target_offset = size; + e->next_offset = size + target->u.target_size; + + size = 0; + for (matchp = matches; matchp; matchp = matchp->next) { + memcpy(e->elems + size, matchp->match->m, matchp->match->m->u.match_size); + size += matchp->match->m->u.match_size; + } + memcpy(e->elems + size, target, target->u.target_size); + + return e; +} + +static void clear_rule_matches(struct xtables_rule_match **matches) +{ + struct xtables_rule_match *matchp, *tmp; + + for (matchp = *matches; matchp;) { + tmp = matchp->next; + if (matchp->match->m) { + free(matchp->match->m); + matchp->match->m = NULL; + } + if (matchp->match == matchp->match->next) { + free(matchp->match); + matchp->match = NULL; + } + free(matchp); + matchp = tmp; + } + + *matches = NULL; +} + +static void command_jump(struct iptables_command_state *cs) +{ + size_t size; + + set_option(&cs->options, OPT_JUMP, &cs->fw6.ipv6.invflags, cs->invert); + cs->jumpto = parse_target(optarg); + /* TRY_LOAD (may be chain name) */ + cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD); + + if (cs->target == NULL) + return; + + size = XT_ALIGN(sizeof(struct ip6t_entry_target)) + cs->target->size; + + cs->target->t = xtables_calloc(1, size); + cs->target->t->u.target_size = size; + strcpy(cs->target->t->u.user.name, cs->jumpto); + cs->target->t->u.user.revision = cs->target->revision; + if (cs->target->init != NULL) + cs->target->init(cs->target->t); + if (cs->target->x6_options != NULL) + opts = xtables_options_xfrm(ip6tables_globals.orig_opts, opts, + cs->target->x6_options, + &cs->target->option_offset); + else + opts = xtables_merge_options(ip6tables_globals.orig_opts, opts, + cs->target->extra_opts, + &cs->target->option_offset); + if (opts == NULL) + xtables_error(OTHER_PROBLEM, "can't alloc memory!"); +} + +static void command_match(struct iptables_command_state *cs) +{ + struct xtables_match *m; + size_t size; + + if (cs->invert) + xtables_error(PARAMETER_PROBLEM, + "unexpected ! flag before --match"); + + m = xtables_find_match(optarg, XTF_LOAD_MUST_SUCCEED, &cs->matches); + size = XT_ALIGN(sizeof(struct ip6t_entry_match)) + m->size; + m->m = xtables_calloc(1, size); + m->m->u.match_size = size; + strcpy(m->m->u.user.name, m->name); + m->m->u.user.revision = m->revision; + if (m->init != NULL) + m->init(m->m); + if (m == m->next) + return; + /* Merge options for non-cloned matches */ + if (m->x6_options != NULL) + opts = xtables_options_xfrm(ip6tables_globals.orig_opts, opts, + m->x6_options, &m->option_offset); + else if (m->extra_opts != NULL) + opts = xtables_merge_options(ip6tables_globals.orig_opts, opts, + m->extra_opts, &m->option_offset); +} + +int do_command6(int argc, char *argv[], char **table, struct ip6tc_handle **handle) +{ + struct iptables_command_state cs; + struct ip6t_entry *e = NULL; + unsigned int nsaddrs = 0, ndaddrs = 0; + struct in6_addr *saddrs = NULL, *daddrs = NULL; + struct in6_addr *smasks = NULL, *dmasks = NULL; + + int verbose = 0; + const char *chain = NULL; + const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL; + const char *policy = NULL, *newname = NULL; + unsigned int rulenum = 0, command = 0; + const char *pcnt = NULL, *bcnt = NULL; + int ret = 1; + struct xtables_match *m; + struct xtables_rule_match *matchp; + struct xtables_target *t; + unsigned long long cnt; + + memset(&cs, 0, sizeof(cs)); + cs.jumpto = ""; + cs.argv = argv; + + /* re-set optind to 0 in case do_command6 gets called + * a second time */ + optind = 0; + + /* clear mflags in case do_command6 gets called a second time + * (we clear the global list of all matches for security)*/ + for (m = xtables_matches; m; m = m->next) + m->mflags = 0; + + for (t = xtables_targets; t; t = t->next) { + t->tflags = 0; + t->used = 0; + } + + /* Suppress error messages: we may add new options if we + demand-load a protocol. */ + opterr = 0; + + opts = xt_params->orig_opts; + while ((cs.c = getopt_long(argc, argv, + "-:A:C:D:R:I:L::S::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:bvnt:m:xc:g:46", + opts, NULL)) != -1) { + switch (cs.c) { + /* + * Command selection + */ + case 'A': + add_command(&command, CMD_APPEND, CMD_NONE, + cs.invert); + chain = optarg; + break; + + case 'C': + add_command(&command, CMD_CHECK, CMD_NONE, + cs.invert); + chain = optarg; + break; + + case 'D': + add_command(&command, CMD_DELETE, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') { + rulenum = parse_rulenumber(argv[optind++]); + command = CMD_DELETE_NUM; + } + break; + + case 'R': + add_command(&command, CMD_REPLACE, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires a rule number", + cmd2char(CMD_REPLACE)); + break; + + case 'I': + add_command(&command, CMD_INSERT, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else rulenum = 1; + break; + + case 'L': + add_command(&command, CMD_LIST, + CMD_ZERO | CMD_ZERO_NUM, cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + break; + + case 'S': + add_command(&command, CMD_LIST_RULES, + CMD_ZERO | CMD_ZERO_NUM, cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + break; + + case 'F': + add_command(&command, CMD_FLUSH, CMD_NONE, + cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'Z': + add_command(&command, CMD_ZERO, CMD_LIST|CMD_LIST_RULES, + cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') { + rulenum = parse_rulenumber(argv[optind++]); + command = CMD_ZERO_NUM; + } + break; + + case 'N': + if (optarg && (*optarg == '-' || *optarg == '!')) + xtables_error(PARAMETER_PROBLEM, + "chain name not allowed to start " + "with `%c'\n", *optarg); + if (xtables_find_target(optarg, XTF_TRY_LOAD)) + xtables_error(PARAMETER_PROBLEM, + "chain name may not clash " + "with target name\n"); + add_command(&command, CMD_NEW_CHAIN, CMD_NONE, + cs.invert); + chain = optarg; + break; + + case 'X': + add_command(&command, CMD_DELETE_CHAIN, CMD_NONE, + cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'E': + add_command(&command, CMD_RENAME_CHAIN, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + newname = argv[optind++]; + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires old-chain-name and " + "new-chain-name", + cmd2char(CMD_RENAME_CHAIN)); + break; + + case 'P': + add_command(&command, CMD_SET_POLICY, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + policy = argv[optind++]; + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires a chain and a policy", + cmd2char(CMD_SET_POLICY)); + break; + + case 'h': + if (!optarg) + optarg = argv[optind]; + + /* ip6tables -p icmp -h */ + if (!cs.matches && cs.protocol) + xtables_find_match(cs.protocol, XTF_TRY_LOAD, + &cs.matches); + + exit_printhelp(cs.matches); + + /* + * Option selection + */ + case 'p': + xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv); + set_option(&cs.options, OPT_PROTOCOL, &cs.fw6.ipv6.invflags, + cs.invert); + + /* Canonicalize into lower case */ + for (cs.protocol = optarg; *cs.protocol; cs.protocol++) + *cs.protocol = tolower(*cs.protocol); + + cs.protocol = optarg; + cs.fw6.ipv6.proto = xtables_parse_protocol(cs.protocol); + cs.fw6.ipv6.flags |= IP6T_F_PROTO; + + if (cs.fw6.ipv6.proto == 0 + && (cs.fw6.ipv6.invflags & IP6T_INV_PROTO)) + xtables_error(PARAMETER_PROBLEM, + "rule would never match protocol"); + + if (is_exthdr(cs.fw6.ipv6.proto) + && (cs.fw6.ipv6.invflags & IP6T_INV_PROTO) == 0) + fprintf(stderr, + "Warning: never matched protocol: %s. " + "use extension match instead.\n", + cs.protocol); + break; + + case 's': + xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv); + set_option(&cs.options, OPT_SOURCE, &cs.fw6.ipv6.invflags, + cs.invert); + shostnetworkmask = optarg; + break; + + case 'd': + xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv); + set_option(&cs.options, OPT_DESTINATION, &cs.fw6.ipv6.invflags, + cs.invert); + dhostnetworkmask = optarg; + break; + +#ifdef IP6T_F_GOTO + case 'g': + set_option(&cs.options, OPT_JUMP, &cs.fw6.ipv6.invflags, + cs.invert); + cs.fw6.ipv6.flags |= IP6T_F_GOTO; + cs.jumpto = parse_target(optarg); + break; +#endif + + case 'j': + command_jump(&cs); + break; + + + case 'i': + if (*optarg == '\0') + xtables_error(PARAMETER_PROBLEM, + "Empty interface is likely to be " + "undesired"); + xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv); + set_option(&cs.options, OPT_VIANAMEIN, &cs.fw6.ipv6.invflags, + cs.invert); + xtables_parse_interface(optarg, + cs.fw6.ipv6.iniface, + cs.fw6.ipv6.iniface_mask); + break; + + case 'o': + if (*optarg == '\0') + xtables_error(PARAMETER_PROBLEM, + "Empty interface is likely to be " + "undesired"); + xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv); + set_option(&cs.options, OPT_VIANAMEOUT, &cs.fw6.ipv6.invflags, + cs.invert); + xtables_parse_interface(optarg, + cs.fw6.ipv6.outiface, + cs.fw6.ipv6.outiface_mask); + break; + + case 'v': + if (!verbose) + set_option(&cs.options, OPT_VERBOSE, + &cs.fw6.ipv6.invflags, cs.invert); + verbose++; + break; + + case 'm': + command_match(&cs); + break; + + case 'n': + set_option(&cs.options, OPT_NUMERIC, &cs.fw6.ipv6.invflags, + cs.invert); + break; + + case 't': + if (cs.invert) + xtables_error(PARAMETER_PROBLEM, + "unexpected ! flag before --table"); + *table = optarg; + break; + + case 'x': + set_option(&cs.options, OPT_EXPANDED, &cs.fw6.ipv6.invflags, + cs.invert); + break; + + case 'V': + if (cs.invert) + printf("Not %s ;-)\n", prog_vers); + else + printf("%s v%s\n", + prog_name, prog_vers); + exit(0); + + case '0': + set_option(&cs.options, OPT_LINENUMBERS, &cs.fw6.ipv6.invflags, + cs.invert); + break; + + case 'M': + xtables_modprobe_program = optarg; + break; + + case 'c': + + set_option(&cs.options, OPT_COUNTERS, &cs.fw6.ipv6.invflags, + cs.invert); + pcnt = optarg; + bcnt = strchr(pcnt + 1, ','); + if (bcnt) + bcnt++; + if (!bcnt && optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + bcnt = argv[optind++]; + if (!bcnt) + xtables_error(PARAMETER_PROBLEM, + "-%c requires packet and byte counter", + opt2char(OPT_COUNTERS)); + + if (sscanf(pcnt, "%llu", &cnt) != 1) + xtables_error(PARAMETER_PROBLEM, + "-%c packet counter not numeric", + opt2char(OPT_COUNTERS)); + cs.fw6.counters.pcnt = cnt; + + if (sscanf(bcnt, "%llu", &cnt) != 1) + xtables_error(PARAMETER_PROBLEM, + "-%c byte counter not numeric", + opt2char(OPT_COUNTERS)); + cs.fw6.counters.bcnt = cnt; + break; + + case '4': + /* This is not the IPv4 iptables */ + if (line != -1) + return 1; /* success: line ignored */ + fprintf(stderr, "This is the IPv6 version of ip6tables.\n"); + exit_tryhelp(2); + + case '6': + /* This is indeed the IPv6 ip6tables */ + break; + + case 1: /* non option */ + if (optarg[0] == '!' && optarg[1] == '\0') { + if (cs.invert) + xtables_error(PARAMETER_PROBLEM, + "multiple consecutive ! not" + " allowed"); + cs.invert = TRUE; + optarg[0] = '\0'; + continue; + } + fprintf(stderr, "Bad argument `%s'\n", optarg); + exit_tryhelp(2); + + default: + command_default(&cs, &ip6tables_globals); + break; + } + cs.invert = FALSE; + } + + for (matchp = cs.matches; matchp; matchp = matchp->next) + xtables_option_mfcall(matchp->match); + if (cs.target != NULL) + xtables_option_tfcall(cs.target); + + /* Fix me: must put inverse options checking here --MN */ + + if (optind < argc) + xtables_error(PARAMETER_PROBLEM, + "unknown arguments found on commandline"); + if (!command) + xtables_error(PARAMETER_PROBLEM, "no command specified"); + if (cs.invert) + xtables_error(PARAMETER_PROBLEM, + "nothing appropriate following !"); + + if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND | CMD_CHECK)) { + if (!(cs.options & OPT_DESTINATION)) + dhostnetworkmask = "::0/0"; + if (!(cs.options & OPT_SOURCE)) + shostnetworkmask = "::0/0"; + } + + if (shostnetworkmask) + xtables_ip6parse_multiple(shostnetworkmask, &saddrs, + &smasks, &nsaddrs); + + if (dhostnetworkmask) + xtables_ip6parse_multiple(dhostnetworkmask, &daddrs, + &dmasks, &ndaddrs); + + if ((nsaddrs > 1 || ndaddrs > 1) && + (cs.fw6.ipv6.invflags & (IP6T_INV_SRCIP | IP6T_INV_DSTIP))) + xtables_error(PARAMETER_PROBLEM, "! not allowed with multiple" + " source or destination IP addresses"); + + if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1)) + xtables_error(PARAMETER_PROBLEM, "Replacement rule does not " + "specify a unique address"); + + generic_opt_check(command, cs.options); + + if (chain != NULL && strlen(chain) >= XT_EXTENSION_MAXNAMELEN) + xtables_error(PARAMETER_PROBLEM, + "chain name `%s' too long (must be under %u chars)", + chain, XT_EXTENSION_MAXNAMELEN); + + /* only allocate handle if we weren't called with a handle */ + if (!*handle) + *handle = ip6tc_init(*table); + + /* try to insmod the module if iptc_init failed */ + if (!*handle && xtables_load_ko(xtables_modprobe_program, false) != -1) + *handle = ip6tc_init(*table); + + if (!*handle) + xtables_error(VERSION_PROBLEM, + "can't initialize ip6tables table `%s': %s", + *table, ip6tc_strerror(errno)); + + if (command == CMD_APPEND + || command == CMD_DELETE + || command == CMD_CHECK + || command == CMD_INSERT + || command == CMD_REPLACE) { + if (strcmp(chain, "PREROUTING") == 0 + || strcmp(chain, "INPUT") == 0) { + /* -o not valid with incoming packets. */ + if (cs.options & OPT_VIANAMEOUT) + xtables_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEOUT), + chain); + } + + if (strcmp(chain, "POSTROUTING") == 0 + || strcmp(chain, "OUTPUT") == 0) { + /* -i not valid with outgoing packets */ + if (cs.options & OPT_VIANAMEIN) + xtables_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEIN), + chain); + } + + if (cs.target && ip6tc_is_chain(cs.jumpto, *handle)) { + fprintf(stderr, + "Warning: using chain %s, not extension\n", + cs.jumpto); + + if (cs.target->t) + free(cs.target->t); + + cs.target = NULL; + } + + /* If they didn't specify a target, or it's a chain + name, use standard. */ + if (!cs.target + && (strlen(cs.jumpto) == 0 + || ip6tc_is_chain(cs.jumpto, *handle))) { + size_t size; + + cs.target = xtables_find_target(IP6T_STANDARD_TARGET, + XTF_LOAD_MUST_SUCCEED); + + size = sizeof(struct ip6t_entry_target) + + cs.target->size; + cs.target->t = xtables_calloc(1, size); + cs.target->t->u.target_size = size; + strcpy(cs.target->t->u.user.name, cs.jumpto); + if (cs.target->init != NULL) + cs.target->init(cs.target->t); + } + + if (!cs.target) { + /* it is no chain, and we can't load a plugin. + * We cannot know if the plugin is corrupt, non + * existant OR if the user just misspelled a + * chain. */ +#ifdef IP6T_F_GOTO + if (cs.fw6.ipv6.flags & IP6T_F_GOTO) + xtables_error(PARAMETER_PROBLEM, + "goto '%s' is not a chain\n", + cs.jumpto); +#endif + xtables_find_target(cs.jumpto, XTF_LOAD_MUST_SUCCEED); + } else { + e = generate_entry(&cs.fw6, cs.matches, cs.target->t); + free(cs.target->t); + } + } + + switch (command) { + case CMD_APPEND: + ret = append_entry(chain, e, + nsaddrs, saddrs, smasks, + ndaddrs, daddrs, dmasks, + cs.options&OPT_VERBOSE, + *handle); + break; + case CMD_DELETE: + ret = delete_entry(chain, e, + nsaddrs, saddrs, smasks, + ndaddrs, daddrs, dmasks, + cs.options&OPT_VERBOSE, + *handle, cs.matches, cs.target); + break; + case CMD_DELETE_NUM: + ret = ip6tc_delete_num_entry(chain, rulenum - 1, *handle); + break; + case CMD_CHECK: + ret = check_entry(chain, e, + nsaddrs, saddrs, smasks, + ndaddrs, daddrs, dmasks, + cs.options&OPT_VERBOSE, + *handle, cs.matches, cs.target); + break; + case CMD_REPLACE: + ret = replace_entry(chain, e, rulenum - 1, + saddrs, smasks, daddrs, dmasks, + cs.options&OPT_VERBOSE, *handle); + break; + case CMD_INSERT: + ret = insert_entry(chain, e, rulenum - 1, + nsaddrs, saddrs, smasks, + ndaddrs, daddrs, dmasks, + cs.options&OPT_VERBOSE, + *handle); + break; + case CMD_FLUSH: + ret = flush_entries6(chain, cs.options&OPT_VERBOSE, *handle); + break; + case CMD_ZERO: + ret = zero_entries(chain, cs.options&OPT_VERBOSE, *handle); + break; + case CMD_ZERO_NUM: + ret = ip6tc_zero_counter(chain, rulenum, *handle); + break; + case CMD_LIST: + case CMD_LIST|CMD_ZERO: + case CMD_LIST|CMD_ZERO_NUM: + ret = list_entries(chain, + rulenum, + cs.options&OPT_VERBOSE, + cs.options&OPT_NUMERIC, + cs.options&OPT_EXPANDED, + cs.options&OPT_LINENUMBERS, + *handle); + if (ret && (command & CMD_ZERO)) + ret = zero_entries(chain, + cs.options&OPT_VERBOSE, *handle); + if (ret && (command & CMD_ZERO_NUM)) + ret = ip6tc_zero_counter(chain, rulenum, *handle); + break; + case CMD_LIST_RULES: + case CMD_LIST_RULES|CMD_ZERO: + case CMD_LIST_RULES|CMD_ZERO_NUM: + ret = list_rules(chain, + rulenum, + cs.options&OPT_VERBOSE, + *handle); + if (ret && (command & CMD_ZERO)) + ret = zero_entries(chain, + cs.options&OPT_VERBOSE, *handle); + if (ret && (command & CMD_ZERO_NUM)) + ret = ip6tc_zero_counter(chain, rulenum, *handle); + break; + case CMD_NEW_CHAIN: + ret = ip6tc_create_chain(chain, *handle); + break; + case CMD_DELETE_CHAIN: + ret = delete_chain6(chain, cs.options&OPT_VERBOSE, *handle); + break; + case CMD_RENAME_CHAIN: + ret = ip6tc_rename_chain(chain, newname, *handle); + break; + case CMD_SET_POLICY: + ret = ip6tc_set_policy(chain, policy, cs.options&OPT_COUNTERS ? &cs.fw6.counters : NULL, *handle); + break; + default: + /* We should never reach this... */ + exit_tryhelp(2); + } + + if (verbose > 1) + dump_entries6(*handle); + + clear_rule_matches(&cs.matches); + + if (e != NULL) { + free(e); + e = NULL; + } + + free(saddrs); + free(smasks); + free(daddrs); + free(dmasks); + xtables_free_opts(1); + + return ret; +} diff --git a/iptables/iptables-apply b/iptables/iptables-apply new file mode 100755 index 00000000..5fec76b0 --- /dev/null +++ b/iptables/iptables-apply @@ -0,0 +1,174 @@ +#!/bin/bash +# +# iptables-apply -- a safer way to update iptables remotely +# +# Copyright © Martin F. Krafft +# Released under the terms of the Artistic Licence 2.0 +# +set -eu + +PROGNAME="${0##*/}"; +VERSION=1.0 + +TIMEOUT=10 +DEFAULT_FILE=/etc/network/iptables + +function blurb() +{ + cat <<-_eof + $PROGNAME $VERSION -- a safer way to update iptables remotely + _eof +} + +function copyright() +{ + cat <<-_eof + $PROGNAME is C Martin F. Krafft . + + The program has been published under the terms of the Artistic Licence 2.0 + _eof +} + +function about() +{ + blurb + echo + copyright +} + +function usage() +{ + cat <<-_eof + Usage: $PROGNAME [options] ruleset + + The script will try to apply a new ruleset (as output by iptables-save/read + by iptables-restore) to iptables, then prompt the user whether the changes + are okay. If the new ruleset cut the existing connection, the user will not + be able to answer affirmatively. In this case, the script rolls back to the + previous ruleset. + + The following options may be specified, using standard conventions: + + -t | --timeout Specify the timeout in seconds (default: $TIMEOUT) + -V | --version Display version information + -h | --help Display this help text + _eof +} + +SHORTOPTS="t:Vh"; +LONGOPTS="timeout:,version,help"; + +OPTS=$(getopt -s bash -o "$SHORTOPTS" -l "$LONGOPTS" -n "$PROGNAME" -- "$@") || exit $? +for opt in $OPTS; do + case "$opt" in + (-*) unset OPT_STATE;; + (*) + case "${OPT_STATE:-}" in + (SET_TIMEOUT) + eval TIMEOUT=$opt + case "$TIMEOUT" in + ([0-9]*) :;; + (*) + echo "E: non-numeric timeout value." >&2 + exit 1 + ;; + esac + ;; + esac + ;; + esac + + case "$opt" in + (-h|--help) usage >&2; exit 0;; + (-V|--version) about >&2; exit 0;; + (-t|--timeout) OPT_STATE=SET_TIMEOUT;; + (--) break;; + esac + shift +done + +FILE="${1:-$DEFAULT_FILE}"; + +if [[ -z "$FILE" ]]; then + echo "E: missing file argument." >&2 + exit 1 +fi + +if [[ ! -r "$FILE" ]]; then + echo "E: cannot read $FILE" >&2 + exit 2 +fi + +case "${0##*/}" in + (*6*) + SAVE=ip6tables-save + RESTORE=ip6tables-restore + ;; + (*) + SAVE=iptables-save + RESTORE=iptables-restore + ;; +esac + +COMMANDS=(tempfile "$SAVE" "$RESTORE") + +for cmd in "${COMMANDS[@]}"; do + if ! command -v $cmd >/dev/null; then + echo "E: command not found: $cmd" >&2 + exit 127 + fi +done + +umask 0700 + +TMPFILE=$(tempfile -p iptap) +trap "rm -f $TMPFILE" EXIT 1 2 3 4 5 6 7 8 10 11 12 13 14 15 + +if ! "$SAVE" >"$TMPFILE"; then + if ! grep -q ipt /proc/modules 2>/dev/null; then + echo "E: iptables support lacking from the kernel." >&2 + exit 3 + else + echo "E: unknown error saving current iptables ruleset." >&2 + exit 4 + fi +fi + +[ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban stop + +echo -n "Applying new ruleset... " +if ! "$RESTORE" <"$FILE"; then + echo "failed." + echo "E: unknown error applying new iptables ruleset." >&2 + exit 5 +else + echo done. +fi + +echo -n "Can you establish NEW connections to the machine? (y/N) " + +read -n1 -t "${TIMEOUT:-15}" ret 2>&1 || : +case "${ret:-}" in + (y*|Y*) + echo + echo ... then my job is done. See you next time. + ;; + (*) + if [[ -z "${ret:-}" ]]; then + echo "apparently not..." + else + echo + fi + echo "Timeout. Something happened (or did not). Better play it safe..." + echo -n "Reverting to old ruleset... " + "$RESTORE" <"$TMPFILE"; + echo done. + exit 255 + ;; +esac + +[ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban start + +exit 0 + +# vim:noet:sw=8 diff --git a/iptables/iptables-apply.8 b/iptables/iptables-apply.8 new file mode 100644 index 00000000..8208fd0f --- /dev/null +++ b/iptables/iptables-apply.8 @@ -0,0 +1,44 @@ +.\" Title: iptables-apply +.\" Author: Martin F. Krafft +.\" Date: Jun 04, 2006 +.\" +.TH iptables\-apply 8 2006-06-04 +.\" disable hyphenation +.nh +.SH NAME +iptables-apply \- a safer way to update iptables remotely +.SH SYNOPSIS +\fBiptables\-apply\fP [\-\fBhV\fP] [\fB-t\fP \fItimeout\fP] \fIruleset\-file\fP +.SH "DESCRIPTION" +.PP +iptables\-apply will try to apply a new ruleset (as output by +iptables\-save/read by iptables\-restore) to iptables, then prompt the +user whether the changes are okay. If the new ruleset cut the existing +connection, the user will not be able to answer affirmatively. In this +case, the script rolls back to the previous ruleset after the timeout +expired. The timeout can be set with \fB\-t\fP. +.PP +When called as ip6tables\-apply, the script will use +ip6tables\-save/\-restore instead. +.SH OPTIONS +.TP +\fB\-t\fP \fIseconds\fR, \fB\-\-timeout\fP \fIseconds\fR +Sets the timeout after which the script will roll back to the previous +ruleset. +.TP +\fB\-h\fP, \fB\-\-help\fP +Display usage information. +.TP +\fB\-V\fP, \fB\-\-version\fP +Display version information. +.SH "SEE ALSO" +.PP +\fBiptables-restore\fP(8), \fBiptables-save\fP(8), \fBiptables\fR(8). +.SH LEGALESE +.PP +iptables\-apply is copyright by Martin F. Krafft. +.PP +This manual page was written by Martin F. Krafft +.PP +Permission is granted to copy, distribute and/or modify this document +under the terms of the Artistic License 2.0. diff --git a/iptables/iptables-multi.h b/iptables/iptables-multi.h new file mode 100644 index 00000000..a2bb8784 --- /dev/null +++ b/iptables/iptables-multi.h @@ -0,0 +1,8 @@ +#ifndef _IPTABLES_MULTI_H +#define _IPTABLES_MULTI_H 1 + +extern int iptables_main(int, char **); +extern int iptables_save_main(int, char **); +extern int iptables_restore_main(int, char **); + +#endif /* _IPTABLES_MULTI_H */ diff --git a/iptables/iptables-restore.8 b/iptables/iptables-restore.8 new file mode 100644 index 00000000..a52bcebe --- /dev/null +++ b/iptables/iptables-restore.8 @@ -0,0 +1,47 @@ +.TH IPTABLES-RESTORE 8 "Jan 04, 2001" "" "" +.\" +.\" Man page written by Harald Welte +.\" It is based on the iptables man page. +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" +.SH NAME +iptables-restore \(em Restore IP Tables +.SH SYNOPSIS +\fBiptables\-restore\fP [\fB\-c\fP] [\fB\-n\fP] +.SH DESCRIPTION +.PP +.B iptables-restore +is used to restore IP Tables from data specified on STDIN. Use +I/O redirection provided by your shell to read from a file +.TP +\fB\-c\fR, \fB\-\-counters\fR +restore the values of all packet and byte counters +.TP +\fB\-n\fR, \fB\-\-noflush\fR +don't flush the previous contents of the table. If not specified, +.B iptables-restore +flushes (deletes) all previous contents of the respective IP Table. +.SH BUGS +None known as of iptables-1.2.1 release +.SH AUTHOR +Harald Welte +.SH SEE ALSO +\fBiptables\-save\fP(8), \fBiptables\fP(8) +.PP +The iptables-HOWTO, which details more iptables usage, the NAT-HOWTO, +which details NAT, and the netfilter-hacking-HOWTO which details the +internals. diff --git a/iptables/iptables-restore.c b/iptables/iptables-restore.c new file mode 100644 index 00000000..26245997 --- /dev/null +++ b/iptables/iptables-restore.c @@ -0,0 +1,471 @@ +/* Code to restore the iptables state, from file by iptables-save. + * (C) 2000-2002 by Harald Welte + * based on previous code from Rusty Russell + * + * This code is distributed under the terms of GNU GPL v2 + */ + +#include +#include +#include +#include +#include +#include +#include "iptables.h" +#include "xtables.h" +#include "libiptc/libiptc.h" +#include "iptables-multi.h" + +#ifdef DEBUG +#define DEBUGP(x, args...) fprintf(stderr, x, ## args) +#else +#define DEBUGP(x, args...) +#endif + +static int binary = 0, counters = 0, verbose = 0, noflush = 0; + +/* Keeping track of external matches and targets. */ +static const struct option options[] = { + {.name = "binary", .has_arg = false, .val = 'b'}, + {.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'}, + {NULL}, +}; + +static void print_usage(const char *name, const char *version) __attribute__((noreturn)); + +#define prog_name iptables_globals.program_name + +static void print_usage(const char *name, const char *version) +{ + fprintf(stderr, "Usage: %s [-b] [-c] [-v] [-t] [-h]\n" + " [ --binary ]\n" + " [ --counters ]\n" + " [ --verbose ]\n" + " [ --test ]\n" + " [ --help ]\n" + " [ --noflush ]\n" + " [ --table= ]\n" + " [ --modprobe=]\n", name); + + exit(1); +} + +static struct iptc_handle *create_handle(const char *tablename) +{ + struct iptc_handle *handle; + + handle = iptc_init(tablename); + + if (!handle) { + /* try to insmod the module if iptc_init failed */ + xtables_load_ko(xtables_modprobe_program, false); + handle = iptc_init(tablename); + } + + if (!handle) { + xtables_error(PARAMETER_PROBLEM, "%s: unable to initialize " + "table '%s'\n", prog_name, tablename); + exit(1); + } + return handle; +} + +static int parse_counters(char *string, struct ipt_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); + newargc++; + 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]); +} + +#ifdef IPTABLES_MULTI +int +iptables_restore_main(int argc, char *argv[]) +#else +int +main(int argc, char *argv[]) +#endif +{ + struct iptc_handle *handle = NULL; + char buffer[10240]; + int c; + char curtable[IPT_TABLE_MAXNAMELEN + 1]; + FILE *in; + int in_table = 0, testing = 0; + const char *tablename = NULL; + + line = 0; + + iptables_globals.program_name = "iptables-restore"; + c = xtables_init_all(&iptables_globals, NFPROTO_IPV4); + if (c < 0) { + fprintf(stderr, "%s/%s Failed to initialize xtables\n", + iptables_globals.program_name, + iptables_globals.program_version); + exit(1); + } +#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) + init_extensions(); + init_extensions4(); +#endif + + while ((c = getopt_long(argc, argv, "bcvthnM:T:", options, NULL)) != -1) { + switch (c) { + case 'b': + binary = 1; + break; + case 'c': + counters = 1; + break; + case 'v': + verbose = 1; + break; + case 't': + testing = 1; + break; + case 'h': + print_usage("iptables-restore", + IPTABLES_VERSION); + break; + case 'n': + noflush = 1; + break; + case 'M': + xtables_modprobe_program = optarg; + break; + case 'T': + tablename = optarg; + break; + } + } + + if (optind == argc - 1) { + in = fopen(argv[optind], "re"); + if (!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 in = stdin; + + /* Grab standard input. */ + while (fgets(buffer, sizeof(buffer), 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 (!testing) { + DEBUGP("Calling commit\n"); + ret = iptc_commit(handle); + iptc_free(handle); + handle = NULL; + } else { + DEBUGP("Not calling commit, testing\n"); + ret = 1; + } + in_table = 0; + } 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", + prog_name, line); + exit(1); + } + strncpy(curtable, table, IPT_TABLE_MAXNAMELEN); + curtable[IPT_TABLE_MAXNAMELEN] = '\0'; + + if (tablename && (strcmp(tablename, table) != 0)) + continue; + if (handle) + iptc_free(handle); + + handle = create_handle(table); + if (noflush == 0) { + DEBUGP("Cleaning all chains of table '%s'\n", + table); + for_each_chain4(flush_entries4, verbose, 1, + handle); + + DEBUGP("Deleting all user-defined chains " + "of table '%s'\n", table); + for_each_chain4(delete_chain4, verbose, 0, + handle); + } + + ret = 1; + in_table = 1; + + } else if ((buffer[0] == ':') && (in_table)) { + /* New chain. */ + char *policy, *chain; + + 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", + prog_name, line); + exit(1); + } + + if (strlen(chain) >= XT_EXTENSION_MAXNAMELEN) + xtables_error(PARAMETER_PROBLEM, + "Invalid chain name `%s' " + "(%u chars max)", + chain, XT_EXTENSION_MAXNAMELEN - 1); + + if (iptc_builtin(chain, handle) <= 0) { + if (noflush && iptc_is_chain(chain, handle)) { + DEBUGP("Flushing existing user defined chain '%s'\n", chain); + if (!iptc_flush_entries(chain, handle)) + xtables_error(PARAMETER_PROBLEM, + "error flushing chain " + "'%s':%s\n", chain, + strerror(errno)); + } else { + DEBUGP("Creating new chain '%s'\n", chain); + if (!iptc_create_chain(chain, handle)) + xtables_error(PARAMETER_PROBLEM, + "error creating chain " + "'%s':%s\n", chain, + strerror(errno)); + } + } + + 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", + prog_name, line); + exit(1); + } + + if (strcmp(policy, "-") != 0) { + struct ipt_counters count; + + 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); + + } else { + memset(&count, 0, + sizeof(struct ipt_counters)); + } + + DEBUGP("Setting policy of chain %s to %s\n", + chain, policy); + + if (!iptc_set_policy(chain, policy, &count, + handle)) + xtables_error(OTHER_PROBLEM, + "Can't set policy `%s'" + " on `%s' line %u: %s\n", + policy, chain, line, + iptc_strerror(errno)); + } + + ret = 1; + + } else if (in_table) { + int a; + char *ptr = buffer; + char *pcnt = NULL; + char *bcnt = NULL; + char *parsestart; + + /* the parser */ + char *curchar; + int quote_open, escaped; + size_t param_len; + + /* 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); + } + + /* 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 */ + + quote_open = 0; + escaped = 0; + param_len = 0; + + for (curchar = parsestart; *curchar; curchar++) { + char param_buffer[1024]; + + 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, + "Line %u seems to have a " + "-t table option.\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!"); + } + } + + 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 = do_command4(newargc, newargv, + &newargv[2], &handle); + + free_argv(); + fflush(stdout); + } + if (tablename && (strcmp(tablename, curtable) != 0)) + continue; + if (!ret) { + fprintf(stderr, "%s: line %u failed\n", + prog_name, line); + exit(1); + } + } + if (in_table) { + fprintf(stderr, "%s: COMMIT expected at line %u\n", + prog_name, line + 1); + exit(1); + } + + if (in != NULL) + fclose(in); + return 0; +} diff --git a/iptables/iptables-save.8 b/iptables/iptables-save.8 new file mode 100644 index 00000000..c2e0a949 --- /dev/null +++ b/iptables/iptables-save.8 @@ -0,0 +1,51 @@ +.TH IPTABLES-SAVE 8 "Jan 04, 2001" "" "" +.\" +.\" Man page written by Harald Welte +.\" It is based on the iptables man page. +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" +.SH NAME +iptables-save \(em dump iptables rules to stdout +.SH SYNOPSIS +\fBiptables\-save\fP [\fB\-M\fP \fImodprobe\fP] [\fB\-c\fP] +[\fB\-t\fP \fItable\fP] +.SH DESCRIPTION +.PP +.B iptables-save +is used to dump the contents of an IP Table in easily parseable format +to STDOUT. Use I/O-redirection provided by your shell to write to a file. +.TP +\fB\-M\fP \fImodprobe_program\fP +Specify the path to the modprobe program. By default, iptables-save will +inspect /proc/sys/kernel/modprobe to determine the executable's path. +.TP +\fB\-c\fR, \fB\-\-counters\fR +include the current values of all packet and byte counters in the output +.TP +\fB\-t\fR, \fB\-\-table\fR \fItablename\fP +restrict output to only one table. If not specified, output includes all +available tables. +.SH BUGS +None known as of iptables-1.2.1 release +.SH AUTHOR +Harald Welte +.SH SEE ALSO +\fBiptables\-restore\fP(8), \fBiptables\fP(8) +.PP +The iptables-HOWTO, which details more iptables usage, the NAT-HOWTO, +which details NAT, and the netfilter-hacking-HOWTO which details the +internals. diff --git a/iptables/iptables-save.c b/iptables/iptables-save.c new file mode 100644 index 00000000..7542bdc0 --- /dev/null +++ b/iptables/iptables-save.c @@ -0,0 +1,185 @@ +/* Code to save the iptables state, in human readable-form. */ +/* (C) 1999 by Paul 'Rusty' Russell and + * (C) 2000-2002 by Harald Welte + * + * This code is distributed under the terms of GNU GPL v2 + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "libiptc/libiptc.h" +#include "iptables.h" +#include "iptables-multi.h" + +#ifndef NO_SHARED_LIBS +#include +#endif + +static int show_binary = 0, show_counters = 0; + +static const struct option options[] = { + {.name = "binary", .has_arg = false, .val = 'b'}, + {.name = "counters", .has_arg = false, .val = 'c'}, + {.name = "dump", .has_arg = false, .val = 'd'}, + {.name = "table", .has_arg = true, .val = 't'}, + {.name = "modprobe", .has_arg = true, .val = 'M'}, + {NULL}, +}; + +/* Debugging prototype. */ +static int for_each_table(int (*func)(const char *tablename)) +{ + int ret = 1; + FILE *procfile = NULL; + char tablename[IPT_TABLE_MAXNAMELEN+1]; + + procfile = fopen("/proc/net/ip_tables_names", "re"); + if (!procfile) + return ret; + + while (fgets(tablename, sizeof(tablename), procfile)) { + if (tablename[strlen(tablename) - 1] != '\n') + xtables_error(OTHER_PROBLEM, + "Badly formed tablename `%s'\n", + tablename); + tablename[strlen(tablename) - 1] = '\0'; + ret &= func(tablename); + } + + fclose(procfile); + return ret; +} + + +static int do_output(const char *tablename) +{ + struct iptc_handle *h; + const char *chain = NULL; + + if (!tablename) + return for_each_table(&do_output); + + h = iptc_init(tablename); + if (h == NULL) { + xtables_load_ko(xtables_modprobe_program, false); + h = iptc_init(tablename); + } + if (!h) + xtables_error(OTHER_PROBLEM, "Cannot initialize: %s\n", + iptc_strerror(errno)); + + if (!show_binary) { + time_t now = time(NULL); + + printf("# Generated by iptables-save v%s on %s", + IPTABLES_VERSION, ctime(&now)); + printf("*%s\n", tablename); + + /* Dump out chain names first, + * thereby preventing dependency conflicts */ + for (chain = iptc_first_chain(h); + chain; + chain = iptc_next_chain(h)) { + + printf(":%s ", chain); + if (iptc_builtin(chain, h)) { + struct ipt_counters count; + printf("%s ", + iptc_get_policy(chain, &count, h)); + printf("[%llu:%llu]\n", (unsigned long long)count.pcnt, (unsigned long long)count.bcnt); + } else { + printf("- [0:0]\n"); + } + } + + + for (chain = iptc_first_chain(h); + chain; + chain = iptc_next_chain(h)) { + const struct ipt_entry *e; + + /* Dump out rules */ + e = iptc_first_rule(chain, h); + while(e) { + print_rule4(e, h, chain, show_counters); + e = iptc_next_rule(e, h); + } + } + + now = time(NULL); + printf("COMMIT\n"); + printf("# Completed on %s", ctime(&now)); + } else { + /* Binary, huh? OK. */ + xtables_error(OTHER_PROBLEM, "Binary NYI\n"); + } + + iptc_free(h); + + return 1; +} + +/* Format: + * :Chain name POLICY packets bytes + * rule + */ +#ifdef IPTABLES_MULTI +int +iptables_save_main(int argc, char *argv[]) +#else +int +main(int argc, char *argv[]) +#endif +{ + const char *tablename = NULL; + int c; + + iptables_globals.program_name = "iptables-save"; + c = xtables_init_all(&iptables_globals, NFPROTO_IPV4); + if (c < 0) { + fprintf(stderr, "%s/%s Failed to initialize xtables\n", + iptables_globals.program_name, + iptables_globals.program_version); + exit(1); + } +#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) + init_extensions(); + init_extensions4(); +#endif + + while ((c = getopt_long(argc, argv, "bcdt:", options, NULL)) != -1) { + switch (c) { + case 'b': + show_binary = 1; + break; + + case 'c': + show_counters = 1; + break; + + case 't': + /* Select specific table. */ + tablename = optarg; + break; + case 'M': + xtables_modprobe_program = optarg; + break; + case 'd': + do_output(tablename); + exit(0); + } + } + + if (optind < argc) { + fprintf(stderr, "Unknown arguments found on commandline\n"); + exit(1); + } + + return !do_output(tablename); +} diff --git a/iptables/iptables-standalone.c b/iptables/iptables-standalone.c new file mode 100644 index 00000000..87f1d31a --- /dev/null +++ b/iptables/iptables-standalone.c @@ -0,0 +1,87 @@ +/* + * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au + * + * Based on the ipchains code by Paul Russell and Michael Neuling + * + * (C) 2000-2002 by the netfilter coreteam : + * Paul 'Rusty' Russell + * Marc Boucher + * James Morris + * Harald Welte + * Jozsef Kadlecsik + * + * iptables -- IP firewall administration for kernels with + * firewall table (aimed for the 2.3 kernels) + * + * See the accompanying manual page iptables(8) for information + * about proper usage of this program. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include "iptables-multi.h" + +#ifdef IPTABLES_MULTI +int +iptables_main(int argc, char *argv[]) +#else +int +main(int argc, char *argv[]) +#endif +{ + int ret; + char *table = "filter"; + struct iptc_handle *handle = NULL; + + iptables_globals.program_name = "iptables"; + ret = xtables_init_all(&iptables_globals, NFPROTO_IPV4); + if (ret < 0) { + fprintf(stderr, "%s/%s Failed to initialize xtables\n", + iptables_globals.program_name, + iptables_globals.program_version); + exit(1); + } +#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) + init_extensions(); + init_extensions4(); +#endif + + ret = do_command4(argc, argv, &table, &handle); + if (ret) { + ret = iptc_commit(handle); + iptc_free(handle); + } + + if (!ret) { + if (errno == EINVAL) { + fprintf(stderr, "iptables: %s. " + "Run `dmesg' for more information.\n", + iptc_strerror(errno)); + } else { + fprintf(stderr, "iptables: %s.\n", + iptc_strerror(errno)); + } + if (errno == EAGAIN) { + exit(RESOURCE_PROBLEM); + } + } + + exit(!ret); +} diff --git a/iptables/iptables-xml.1 b/iptables/iptables-xml.1 new file mode 100644 index 00000000..048c2cb8 --- /dev/null +++ b/iptables/iptables-xml.1 @@ -0,0 +1,87 @@ +.TH IPTABLES-XML 8 "Jul 16, 2007" "" "" +.\" +.\" Man page written by Sam Liddicott +.\" It is based on the iptables-save man page. +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" +.SH NAME +iptables-xml \(em Convert iptables-save format to XML +.SH SYNOPSIS +\fBiptables\-xml\fP [\fB\-c\fP] [\fB\-v\fP] +.SH DESCRIPTION +.PP +.B iptables-xml +is used to convert the output of iptables-save into an easily manipulatable +XML format to STDOUT. Use I/O-redirection provided by your shell to write to +a file. +.TP +\fB\-c\fR, \fB\-\-combine\fR +combine consecutive rules with the same matches but different targets. iptables +does not currently support more than one target per match, so this simulates +that by collecting the targets from consecutive iptables rules into one action +tag, but only when the rule matches are identical. Terminating actions like +RETURN, DROP, ACCEPT and QUEUE are not combined with subsequent targets. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +Output xml comments containing the iptables line from which the XML is derived + +.PP +iptables-xml does a mechanistic conversion to a very expressive xml +format; the only semantic considerations are for \-g and \-j targets in +order to discriminate between and as it +helps xml processing scripts if they can tell the difference between a +target like SNAT and another chain. + +Some sample output is: + + +
+ + + + +

tcp

+
+ + 8443 + +
+ + + + + + +
+
+
+ + +.PP +Conversion from XML to iptables-save format may be done using the +iptables.xslt script and xsltproc, or a custom program using +libxsltproc or similar; in this fashion: + +xsltproc iptables.xslt my-iptables.xml | iptables-restore + +.SH BUGS +None known as of iptables-1.3.7 release +.SH AUTHOR +Sam Liddicott +.SH SEE ALSO +\fBiptables\-save\fP(8), \fBiptables\-restore\fP(8), \fBiptables\fP(8) diff --git a/iptables/iptables-xml.c b/iptables/iptables-xml.c new file mode 100644 index 00000000..5aa638c0 --- /dev/null +++ b/iptables/iptables-xml.c @@ -0,0 +1,874 @@ +/* Code to convert iptables-save format to xml format, + * (C) 2006 Ufo Mechanic + * based on iptables-restore (C) 2000-2002 by Harald Welte + * based on previous code from Rusty Russell + * + * This code is distributed under the terms of GNU GPL v2 + */ + +#include +#include +#include +#include +#include +#include +#include "iptables.h" +#include "libiptc/libiptc.h" +#include "xtables-multi.h" +#include + +#ifdef DEBUG +#define DEBUGP(x, args...) fprintf(stderr, x, ## args) +#else +#define DEBUGP(x, args...) +#endif + +#ifndef IPTABLES_MULTI +int line = 0; +#endif + +struct xtables_globals iptables_xml_globals = { + .option_offset = 0, + .program_version = IPTABLES_VERSION, + .program_name = "iptables-xml", +}; +#define prog_name iptables_xml_globals.program_name +#define prog_vers iptables_xml_globals.program_version + +static void print_usage(const char *name, const char *version) + __attribute__ ((noreturn)); + +static int verbose = 0; +/* Whether to combine actions of sequential rules with identical conditions */ +static int combine = 0; +/* Keeping track of external matches and targets. */ +static struct option options[] = { + {"verbose", 0, NULL, 'v'}, + {"combine", 0, NULL, 'c'}, + {"help", 0, NULL, 'h'}, + { .name = NULL } +}; + +static void +print_usage(const char *name, const char *version) +{ + fprintf(stderr, "Usage: %s [-c] [-v] [-h]\n" + " [--combine ]\n" + " [ --verbose ]\n" " [ --help ]\n", name); + + exit(1); +} + +static int +parse_counters(char *string, struct ipt_counters *ctr) +{ + __u64 *pcnt, *bcnt; + + if (string != NULL) { + pcnt = &ctr->pcnt; + bcnt = &ctr->bcnt; + return (sscanf + (string, "[%llu:%llu]", + (unsigned long long *)pcnt, + (unsigned long long *)bcnt) == 2); + } else + return (0 == 2); +} + +/* global new argv and argc */ +static char *newargv[255]; +static unsigned int newargc = 0; + +static char *oldargv[255]; +static unsigned int oldargc = 0; + +/* arg meta data, were they quoted, frinstance */ +static int newargvattr[255]; + +#define IPT_CHAIN_MAXNAMELEN IPT_TABLE_MAXNAMELEN +static char closeActionTag[IPT_TABLE_MAXNAMELEN + 1]; +static char closeRuleTag[IPT_TABLE_MAXNAMELEN + 1]; +static char curTable[IPT_TABLE_MAXNAMELEN + 1]; +static char curChain[IPT_CHAIN_MAXNAMELEN + 1]; + +struct chain { + char *chain; + char *policy; + struct ipt_counters count; + int created; +}; + +#define maxChains 10240 /* max chains per table */ +static struct chain chains[maxChains]; +static int nextChain = 0; + +/* funCtion adding one argument to newargv, updating newargc + * returns true if argument added, false otherwise */ +static int +add_argv(char *what, int quoted) +{ + DEBUGP("add_argv: %d %s\n", newargc, what); + if (what && newargc + 1 < ARRAY_SIZE(newargv)) { + newargv[newargc] = strdup(what); + newargvattr[newargc] = quoted; + newargc++; + return 1; + } else + return 0; +} + +static void +free_argv(void) +{ + unsigned int i; + + for (i = 0; i < newargc; i++) { + free(newargv[i]); + newargv[i] = NULL; + } + newargc = 0; + + for (i = 0; i < oldargc; i++) { + free(oldargv[i]); + oldargv[i] = NULL; + } + oldargc = 0; +} + +/* save parsed rule for comparison with next rule + to perform action agregation on duplicate conditions */ +static void +save_argv(void) +{ + unsigned int i; + + for (i = 0; i < oldargc; i++) + free(oldargv[i]); + oldargc = newargc; + newargc = 0; + for (i = 0; i < oldargc; i++) { + oldargv[i] = newargv[i]; + newargv[i] = NULL; + } +} + +/* like puts but with xml encoding */ +static void +xmlEncode(char *text) +{ + while (text && *text) { + if ((unsigned char) (*text) >= 127) + printf("&#%d;", (unsigned char) (*text)); + else if (*text == '&') + printf("&"); + else if (*text == '<') + printf("<"); + else if (*text == '>') + printf(">"); + else if (*text == '"') + printf("""); + else + putchar(*text); + text++; + } +} + +/* Output text as a comment, avoiding a double hyphen */ +static void +xmlCommentEscape(char *comment) +{ + int h_count = 0; + + while (comment && *comment) { + if (*comment == '-') { + h_count++; + if (h_count >= 2) { + h_count = 0; + putchar(' '); + } + putchar('*'); + } + /* strip trailing newline */ + if (*comment == '\n' && *(comment + 1) == 0); + else + putchar(*comment); + comment++; + } +} + +static void +xmlComment(char *comment) +{ + printf("\n"); +} + +static void +xmlAttrS(char *name, char *value) +{ + printf("%s=\"", name); + xmlEncode(value); + printf("\" "); +} + +static void +xmlAttrI(char *name, long long int num) +{ + printf("%s=\"%lld\" ", name, num); +} + +static void +closeChain(void) +{ + if (curChain[0] == 0) + return; + + if (closeActionTag[0]) + printf("%s\n", closeActionTag); + closeActionTag[0] = 0; + if (closeRuleTag[0]) + printf("%s\n", closeRuleTag); + closeRuleTag[0] = 0; + if (curChain[0]) + printf(" \n"); + curChain[0] = 0; + //lastRule[0]=0; +} + +static void +openChain(char *chain, char *policy, struct ipt_counters *ctr, char close) +{ + closeChain(); + + strncpy(curChain, chain, IPT_CHAIN_MAXNAMELEN); + curChain[IPT_CHAIN_MAXNAMELEN] = '\0'; + + printf(" pcnt); + xmlAttrI("byte-count", (unsigned long long) ctr->bcnt); + if (close) { + printf("%c", close); + curChain[0] = 0; + } + printf(">\n"); +} + +static int +existsChain(char *chain) +{ + /* open a saved chain */ + int c = 0; + + if (0 == strcmp(curChain, chain)) + return 1; + for (c = 0; c < nextChain; c++) + if (chains[c].chain && strcmp(chains[c].chain, chain) == 0) + return 1; + return 0; +} + +static void +needChain(char *chain) +{ + /* open a saved chain */ + int c = 0; + + if (0 == strcmp(curChain, chain)) + return; + + for (c = 0; c < nextChain; c++) + if (chains[c].chain && strcmp(chains[c].chain, chain) == 0) { + openChain(chains[c].chain, chains[c].policy, + &(chains[c].count), '\0'); + /* And, mark it as done so we don't create + an empty chain at table-end time */ + chains[c].created = 1; + } +} + +static void +saveChain(char *chain, char *policy, struct ipt_counters *ctr) +{ + if (nextChain >= maxChains) { + xtables_error(PARAMETER_PROBLEM, + "%s: line %u chain name invalid\n", + prog_name, line); + exit(1); + }; + chains[nextChain].chain = strdup(chain); + chains[nextChain].policy = strdup(policy); + chains[nextChain].count = *ctr; + chains[nextChain].created = 0; + nextChain++; +} + +static void +finishChains(void) +{ + int c; + + for (c = 0; c < nextChain; c++) + if (!chains[c].created) { + openChain(chains[c].chain, chains[c].policy, + &(chains[c].count), '/'); + free(chains[c].chain); + free(chains[c].policy); + } + nextChain = 0; +} + +static void +closeTable(void) +{ + closeChain(); + finishChains(); + if (curTable[0]) + printf(" \n"); + curTable[0] = 0; +} + +static void +openTable(char *table) +{ + closeTable(); + + strncpy(curTable, table, IPT_TABLE_MAXNAMELEN); + curTable[IPT_TABLE_MAXNAMELEN] = '\0'; + + printf(" \n"); +} + +// is char* -j --jump -g or --goto +static int +isTarget(char *arg) +{ + return ((arg) + && (strcmp((arg), "-j") == 0 || strcmp((arg), "--jump") == 0 + || strcmp((arg), "-g") == 0 + || strcmp((arg), "--goto") == 0)); +} + +// is it a terminating target like -j ACCEPT, etc +// (or I guess -j SNAT in nat table, but we don't check for that yet +static int +isTerminatingTarget(char *arg) +{ + return ((arg) + && (strcmp((arg), "ACCEPT") == 0 + || strcmp((arg), "DROP") == 0 + || strcmp((arg), "QUEUE") == 0 + || strcmp((arg), "RETURN") == 0)); +} + +// part=-1 means do conditions, part=1 means do rules, part=0 means do both +static void +do_rule_part(char *leveltag1, char *leveltag2, int part, int argc, + char *argv[], int argvattr[]) +{ + int arg = 1; // ignore leading -A + char invert_next = 0; + char *spacer = ""; // space when needed to assemble arguments + char *level1 = NULL; + char *level2 = NULL; + char *leveli1 = " "; + char *leveli2 = " "; + +#define CLOSE_LEVEL(LEVEL) \ + do { \ + if (level ## LEVEL) printf("\n", \ + (leveltag ## LEVEL)?(leveltag ## LEVEL):(level ## LEVEL)); \ + level ## LEVEL=NULL;\ + } while(0) + +#define OPEN_LEVEL(LEVEL,TAG) \ + do {\ + level ## LEVEL=TAG;\ + if (leveltag ## LEVEL) {\ + printf("%s<%s ", (leveli ## LEVEL), \ + (leveltag ## LEVEL));\ + xmlAttrS("type", (TAG)); \ + } else printf("%s<%s ", (leveli ## LEVEL), (level ## LEVEL)); \ + } while(0) + + if (part == 1) { /* skip */ + /* use argvattr to tell which arguments were quoted + to avoid comparing quoted arguments, like comments, to -j, */ + while (arg < argc && (argvattr[arg] || !isTarget(argv[arg]))) + arg++; + } + + /* Before we start, if the first arg is -[^-] and not -m or -j or -g + then start a dummy tag for old style built-in matches. + We would do this in any case, but no need if it would be empty */ + if (arg < argc && argv[arg][0] == '-' && !isTarget(argv[arg]) + && strcmp(argv[arg], "-m") != 0) { + OPEN_LEVEL(1, "match"); + printf(">\n"); + } + while (arg < argc) { + // If ! is followed by -* then apply to that else output as data + // Stop, if we need to + if (part == -1 && !argvattr[arg] && (isTarget(argv[arg]))) { + break; + } else if (!argvattr[arg] && strcmp(argv[arg], "!") == 0) { + if ((arg + 1) < argc && argv[arg + 1][0] == '-') + invert_next = '!'; + else + printf("%s%s", spacer, argv[arg]); + spacer = " "; + } else if (!argvattr[arg] && isTarget(argv[arg]) + && existsChain(argv[arg + 1]) + && (2 + arg >= argc)) { + if (!((1 + arg) < argc)) + // no args to -j, -m or -g, ignore & finish loop + break; + CLOSE_LEVEL(2); + if (level1) + printf("%s", leveli1); + CLOSE_LEVEL(1); + spacer = ""; + invert_next = 0; + if (strcmp(argv[arg], "-g") == 0 + || strcmp(argv[arg], "--goto") == 0) { + /* goto user chain */ + OPEN_LEVEL(1, "goto"); + printf(">\n"); + arg++; + OPEN_LEVEL(2, argv[arg]); + printf("/>\n"); + level2 = NULL; + } else { + /* call user chain */ + OPEN_LEVEL(1, "call"); + printf(">\n"); + arg++; + OPEN_LEVEL(2, argv[arg]); + printf("/>\n"); + level2 = NULL; + } + } else if (!argvattr[arg] + && (isTarget(argv[arg]) + || strcmp(argv[arg], "-m") == 0 + || strcmp(argv[arg], "--module") == 0)) { + if (!((1 + arg) < argc)) + // no args to -j, -m or -g, ignore & finish loop + break; + CLOSE_LEVEL(2); + if (level1) + printf("%s", leveli1); + CLOSE_LEVEL(1); + spacer = ""; + invert_next = 0; + arg++; + OPEN_LEVEL(1, (argv[arg])); + // Optimize case, can we close this tag already? + if ((arg + 1) >= argc || (!argvattr[arg + 1] + && (isTarget(argv[arg + 1]) + || strcmp(argv[arg + 1], + "-m") == 0 + || strcmp(argv[arg + 1], + "--module") == + 0))) { + printf(" />\n"); + level1 = NULL; + } else { + printf(">\n"); + } + } else if (!argvattr[arg] && argv[arg][0] == '-') { + char *tag; + CLOSE_LEVEL(2); + // Skip past any - + tag = argv[arg]; + while (*tag == '-' && *tag) + tag++; + + spacer = ""; + OPEN_LEVEL(2, tag); + if (invert_next) + printf(" invert=\"1\""); + invert_next = 0; + + // Optimize case, can we close this tag already? + if (!((arg + 1) < argc) + || (argv[arg + 1][0] == '-' /* NOT QUOTED */ )) { + printf(" />\n"); + level2 = NULL; + } else { + printf(">"); + } + } else { // regular data + char *spaces = strchr(argv[arg], ' '); + printf("%s", spacer); + if (spaces || argvattr[arg]) + printf("""); + // if argv[arg] contains a space, enclose in quotes + xmlEncode(argv[arg]); + if (spaces || argvattr[arg]) + printf("""); + spacer = " "; + } + arg++; + } + CLOSE_LEVEL(2); + if (level1) + printf("%s", leveli1); + CLOSE_LEVEL(1); +} + +static int +compareRules(void) +{ + /* compare arguments up to -j or -g for match. + NOTE: We don't want to combine actions if there were no criteria + in each rule, or rules didn't have an action + NOTE: Depends on arguments being in some kind of "normal" order which + is the case when processing the ACTUAL output of actual iptables-save + rather than a file merely in a compatable format */ + + unsigned int old = 0; + unsigned int new = 0; + + int compare = 0; + + while (new < newargc && old < oldargc) { + if (isTarget(oldargv[old]) && isTarget(newargv[new])) { + /* if oldarg was a terminating action then it makes no sense + * to combine further actions into the same xml */ + if (((strcmp((oldargv[old]), "-j") == 0 + || strcmp((oldargv[old]), "--jump") == 0) + && old+1 < oldargc + && isTerminatingTarget(oldargv[old+1]) ) + || strcmp((oldargv[old]), "-g") == 0 + || strcmp((oldargv[old]), "--goto") == 0 ) { + /* Previous rule had terminating action */ + compare = 0; + } else { + compare = 1; + } + break; + } + // break when old!=new + if (strcmp(oldargv[old], newargv[new]) != 0) { + compare = 0; + break; + } + + old++; + new++; + } + // We won't match unless both rules had a target. + // This means we don't combine target-less rules, which is good + + return compare == 1; +} + +/* has a nice parsed rule starting with -A */ +static void +do_rule(char *pcnt, char *bcnt, int argc, char *argv[], int argvattr[]) +{ + /* are these conditions the same as the previous rule? + * If so, skip arg straight to -j or -g */ + if (combine && argc > 2 && !isTarget(argv[2]) && compareRules()) { + xmlComment("Combine action from next rule"); + } else { + + if (closeActionTag[0]) { + printf("%s\n", closeActionTag); + closeActionTag[0] = 0; + } + if (closeRuleTag[0]) { + printf("%s\n", closeRuleTag); + closeRuleTag[0] = 0; + } + + printf(" \n"); + + strncpy(closeRuleTag, " \n", IPT_TABLE_MAXNAMELEN); + closeRuleTag[IPT_TABLE_MAXNAMELEN] = '\0'; + + /* no point in writing out condition if there isn't one */ + if (argc >= 3 && !isTarget(argv[2])) { + printf(" \n"); + do_rule_part(NULL, NULL, -1, argc, argv, argvattr); + printf(" \n"); + } + } + /* Write out the action */ + //do_rule_part("action","arg",1,argc,argv,argvattr); + if (!closeActionTag[0]) { + printf(" \n"); + strncpy(closeActionTag, " \n", + IPT_TABLE_MAXNAMELEN); + closeActionTag[IPT_TABLE_MAXNAMELEN] = '\0'; + } + do_rule_part(NULL, NULL, 1, argc, argv, argvattr); +} + +#ifdef IPTABLES_MULTI +int +iptables_xml_main(int argc, char *argv[]) +#else +int +main(int argc, char *argv[]) +#endif +{ + char buffer[10240]; + int c; + FILE *in; + + line = 0; + + xtables_set_params(&iptables_xml_globals); + while ((c = getopt_long(argc, argv, "cvh", options, NULL)) != -1) { + switch (c) { + case 'c': + combine = 1; + break; + case 'v': + printf("xptables-xml\n"); + verbose = 1; + break; + case 'h': + print_usage("iptables-xml", IPTABLES_VERSION); + break; + } + } + + if (optind == argc - 1) { + in = fopen(argv[optind], "re"); + if (!in) { + fprintf(stderr, "Can't open %s: %s", argv[optind], + strerror(errno)); + exit(1); + } + } else if (optind < argc) { + fprintf(stderr, "Unknown arguments found on commandline"); + exit(1); + } else + in = stdin; + + printf("\n"); + + /* Grab standard input. */ + while (fgets(buffer, sizeof(buffer), in)) { + int ret = 0; + + line++; + + if (buffer[0] == '\n') + continue; + else if (buffer[0] == '#') { + xmlComment(buffer); + continue; + } + + if (verbose) { + printf("\n"); + } + + if ((strcmp(buffer, "COMMIT\n") == 0) && (curTable[0])) { + DEBUGP("Calling commit\n"); + closeTable(); + ret = 1; + } else if ((buffer[0] == '*')) { + /* 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", + prog_name, line); + exit(1); + } + openTable(table); + + ret = 1; + } else if ((buffer[0] == ':') && (curTable[0])) { + /* New chain. */ + char *policy, *chain; + struct ipt_counters count; + char *ctrs; + + 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", + prog_name, line); + exit(1); + } + + DEBUGP("Creating new chain '%s'\n", chain); + + 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", + prog_name, line); + exit(1); + } + + ctrs = strtok(NULL, " \t\n"); + parse_counters(ctrs, &count); + saveChain(chain, policy, &count); + + ret = 1; + } else if (curTable[0]) { + unsigned int a; + char *ptr = buffer; + char *pcnt = NULL; + char *bcnt = NULL; + char *parsestart; + char *chain = NULL; + + /* the parser */ + char *param_start, *curchar; + int quote_open, quoted; + + /* 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; + } + + + /* This is a 'real' parser crafted in artist mode + * not hacker mode. If the author can live with that + * then so can everyone else */ + + quote_open = 0; + /* We need to know which args were quoted so we + can preserve quote */ + quoted = 0; + param_start = parsestart; + + for (curchar = parsestart; *curchar; curchar++) { + if (*curchar == '"') { + /* quote_open cannot be true if there + * was no previous character. Thus, + * curchar-1 has to be within bounds */ + if (quote_open && + *(curchar - 1) != '\\') { + quote_open = 0; + *curchar = ' '; + } else { + quote_open = 1; + quoted = 1; + param_start++; + } + } + if (*curchar == ' ' + || *curchar == '\t' || *curchar == '\n') { + char param_buffer[1024]; + int param_len = curchar - param_start; + + if (quote_open) + continue; + + if (!param_len) { + /* two spaces? */ + param_start++; + continue; + } + + /* end of one parameter */ + strncpy(param_buffer, param_start, + param_len); + *(param_buffer + param_len) = '\0'; + + /* check if table name specified */ + if (!strncmp(param_buffer, "-t", 3) + || !strncmp(param_buffer, + "--table", 8)) { + xtables_error(PARAMETER_PROBLEM, + "Line %u seems to have a " + "-t table option.\n", + line); + exit(1); + } + + add_argv(param_buffer, quoted); + if (newargc >= 2 + && 0 == + strcmp(newargv[newargc - 2], "-A")) + chain = newargv[newargc - 1]; + quoted = 0; + param_start += param_len + 1; + } else { + /* regular character, skip */ + } + } + + 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]); + + needChain(chain);// Should we explicitly look for -A + do_rule(pcnt, bcnt, newargc, newargv, newargvattr); + + save_argv(); + ret = 1; + } + if (!ret) { + fprintf(stderr, "%s: line %u failed\n", + prog_name, line); + exit(1); + } + } + if (curTable[0]) { + fprintf(stderr, "%s: COMMIT expected at line %u\n", + prog_name, line + 1); + exit(1); + } + + if (in != NULL) + fclose(in); + printf("\n"); + free_argv(); + + return 0; +} diff --git a/iptables/iptables.8.in b/iptables/iptables.8.in new file mode 100644 index 00000000..d09bf7aa --- /dev/null +++ b/iptables/iptables.8.in @@ -0,0 +1,449 @@ +.TH IPTABLES 8 "" "@PACKAGE_AND_VERSION@" "@PACKAGE_AND_VERSION@" +.\" +.\" Man page written by Herve Eychenne (May 1999) +.\" It is based on ipchains page. +.\" TODO: add a word for protocol helpers (FTP, IRC, SNMP-ALG) +.\" +.\" ipchains page by Paul ``Rusty'' Russell March 1997 +.\" Based on the original ipfwadm man page by Jos Vos +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" +.SH NAME +iptables \(em administration tool for IPv4 packet filtering and NAT +.SH SYNOPSIS +\fBiptables\fP [\fB\-t\fP \fItable\fP] {\fB\-A\fP|\fB\-C\fP|\fB\-D\fP} +\fIchain\fP \fIrule-specification\fP +.PP +\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-I\fP \fIchain\fP [\fIrulenum\fP] \fIrule-specification\fP +.PP +\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-R\fP \fIchain rulenum rule-specification\fP +.PP +\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-D\fP \fIchain rulenum\fP +.PP +\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-S\fP [\fIchain\fP [\fIrulenum\fP]] +.PP +\fBiptables\fP [\fB\-t\fP \fItable\fP] {\fB\-F\fP|\fB\-L\fP|\fB\-Z\fP} [\fIchain\fP [\fIrulenum\fP]] [\fIoptions...\fP] +.PP +\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-N\fP \fIchain\fP +.PP +\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-X\fP [\fIchain\fP] +.PP +\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-P\fP \fIchain target\fP +.PP +\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-E\fP \fIold-chain-name new-chain-name\fP +.PP +rule-specification = [\fImatches...\fP] [\fItarget\fP] +.PP +match = \fB\-m\fP \fImatchname\fP [\fIper-match-options\fP] +.PP +target = \fB\-j\fP \fItargetname\fP [\fIper\-target\-options\fP] +.SH DESCRIPTION +\fBIptables\fP is used to set up, maintain, and inspect the +tables of IPv4 packet +filter rules in the Linux kernel. Several different tables +may be defined. Each table contains a number of built-in +chains and may also contain user-defined chains. +.PP +Each chain is a list of rules which can match a set of packets. Each +rule specifies what to do with a packet that matches. This is called +a `target', which may be a jump to a user-defined chain in the same +table. +.SH TARGETS +A firewall rule specifies criteria for a packet and a target. If the +packet does not match, the next rule in the chain is the examined; if +it does match, then the next rule is specified by the value of the +target, which can be the name of a user-defined chain or one of the +special values \fBACCEPT\fP, \fBDROP\fP, \fBQUEUE\fP or \fBRETURN\fP. +.PP +\fBACCEPT\fP means to let the packet through. +\fBDROP\fP means to drop the packet on the floor. +\fBQUEUE\fP means to pass the packet to userspace. +(How the packet can be received +by a userspace process differs by the particular queue handler. 2.4.x +and 2.6.x kernels up to 2.6.13 include the \fBip_queue\fP +queue handler. Kernels 2.6.14 and later additionally include the +\fBnfnetlink_queue\fP queue handler. Packets with a target of QUEUE will be +sent to queue number '0' in this case. Please also see the \fBNFQUEUE\fP +target as described later in this man page.) +\fBRETURN\fP means stop traversing this chain and resume at the next +rule in the +previous (calling) chain. If the end of a built-in chain is reached +or a rule in a built-in chain with target \fBRETURN\fP +is matched, the target specified by the chain policy determines the +fate of the packet. +.SH TABLES +There are currently three independent tables (which tables are present +at any time depends on the kernel configuration options and which +modules are present). +.TP +\fB\-t\fP, \fB\-\-table\fP \fItable\fP +This option specifies the packet matching table which the command +should operate on. If the kernel is configured with automatic module +loading, an attempt will be made to load the appropriate module for +that table if it is not already there. + +The tables are as follows: +.RS +.TP .4i +\fBfilter\fP: +This is the default table (if no \-t option is passed). It contains +the built-in chains \fBINPUT\fP (for packets destined to local sockets), +\fBFORWARD\fP (for packets being routed through the box), and +\fBOUTPUT\fP (for locally-generated packets). +.TP +\fBnat\fP: +This table is consulted when a packet that creates a new +connection is encountered. It consists of three built-ins: \fBPREROUTING\fP +(for altering packets as soon as they come in), \fBOUTPUT\fP +(for altering locally-generated packets before routing), and \fBPOSTROUTING\fP +(for altering packets as they are about to go out). +.TP +\fBmangle\fP: +This table is used for specialized packet alteration. Until kernel +2.4.17 it had two built-in chains: \fBPREROUTING\fP +(for altering incoming packets before routing) and \fBOUTPUT\fP +(for altering locally-generated packets before routing). +Since kernel 2.4.18, three other built-in chains are also supported: +\fBINPUT\fP (for packets coming into the box itself), \fBFORWARD\fP +(for altering packets being routed through the box), and \fBPOSTROUTING\fP +(for altering packets as they are about to go out). +.TP +\fBraw\fP: +This table is used mainly for configuring exemptions from connection +tracking in combination with the NOTRACK target. It registers at the netfilter +hooks with higher priority and is thus called before ip_conntrack, or any other +IP tables. It provides the following built-in chains: \fBPREROUTING\fP +(for packets arriving via any network interface) \fBOUTPUT\fP +(for packets generated by local processes) +.TP +\fBsecurity\fP: +This table is used for Mandatory Access Control (MAC) networking rules, such +as those enabled by the \fBSECMARK\fP and \fBCONNSECMARK\fP targets. +Mandatory Access Control is implemented by Linux Security Modules such as +SELinux. The security table is called after the filter table, allowing any +Discretionary Access Control (DAC) rules in the filter table to take effect +before MAC rules. This table provides the following built-in chains: +\fBINPUT\fP (for packets coming into the box itself), +\fBOUTPUT\fP (for altering locally-generated packets before routing), and +\fBFORWARD\fP (for altering packets being routed through the box). +.RE +.SH OPTIONS +The options that are recognized by +\fBiptables\fP can be divided into several different groups. +.SS COMMANDS +These options specify the desired action to perform. Only one of them +can be specified on the command line unless otherwise stated +below. For long versions of the command and option names, you +need to use only enough letters to ensure that +\fBiptables\fP can differentiate it from all other options. +.TP +\fB\-A\fP, \fB\-\-append\fP \fIchain rule-specification\fP +Append one or more rules to the end of the selected chain. +When the source and/or destination names resolve to more than one +address, a rule will be added for each possible address combination. +.TP +\fB\-C\fP, \fB\-\-check\fP \fIchain rule-specification\fP +Check whether a rule matching the specification does exist in the +selected chain. This command uses the same logic as \fB\-D\fP to +find a matching entry, but does not alter the existing iptables +configuration and uses its exit code to indicate success or failure. +.TP +\fB\-D\fP, \fB\-\-delete\fP \fIchain rule-specification\fP +.ns +.TP +\fB\-D\fP, \fB\-\-delete\fP \fIchain rulenum\fP +Delete one or more rules from the selected chain. There are two +versions of this command: the rule can be specified as a number in the +chain (starting at 1 for the first rule) or a rule to match. +.TP +\fB\-I\fP, \fB\-\-insert\fP \fIchain\fP [\fIrulenum\fP] \fIrule-specification\fP +Insert one or more rules in the selected chain as the given rule +number. So, if the rule number is 1, the rule or rules are inserted +at the head of the chain. This is also the default if no rule number +is specified. +.TP +\fB\-R\fP, \fB\-\-replace\fP \fIchain rulenum rule-specification\fP +Replace a rule in the selected chain. If the source and/or +destination names resolve to multiple addresses, the command will +fail. Rules are numbered starting at 1. +.TP +\fB\-L\fP, \fB\-\-list\fP [\fIchain\fP] +List all rules in the selected chain. If no chain is selected, all +chains are listed. Like every other iptables command, it applies to the +specified table (filter is the default), so NAT rules get listed by +.nf + iptables \-t nat \-n \-L +.fi +Please note that it is often used with the \fB\-n\fP +option, in order to avoid long reverse DNS lookups. +It is legal to specify the \fB\-Z\fP +(zero) option as well, in which case the chain(s) will be atomically +listed and zeroed. The exact output is affected by the other +arguments given. The exact rules are suppressed until you use +.nf + iptables \-L \-v +.fi +.TP +\fB\-S\fP, \fB\-\-list\-rules\fP [\fIchain\fP] +Print all rules in the selected chain. If no chain is selected, all +chains are printed like iptables-save. Like every other iptables command, +it applies to the specified table (filter is the default). +.TP +\fB\-F\fP, \fB\-\-flush\fP [\fIchain\fP] +Flush the selected chain (all the chains in the table if none is given). +This is equivalent to deleting all the rules one by one. +.TP +\fB\-Z\fP, \fB\-\-zero\fP [\fIchain\fP [\fIrulenum\fP]] +Zero the packet and byte counters in all chains, or only the given chain, +or only the given rule in a chain. It is legal to +specify the +\fB\-L\fP, \fB\-\-list\fP +(list) option as well, to see the counters immediately before they are +cleared. (See above.) +.TP +\fB\-N\fP, \fB\-\-new\-chain\fP \fIchain\fP +Create a new user-defined chain by the given name. There must be no +target of that name already. +.TP +\fB\-X\fP, \fB\-\-delete\-chain\fP [\fIchain\fP] +Delete the optional user-defined chain specified. There must be no references +to the chain. If there are, you must delete or replace the referring rules +before the chain can be deleted. The chain must be empty, i.e. not contain +any rules. If no argument is given, it will attempt to delete every +non-builtin chain in the table. +.TP +\fB\-P\fP, \fB\-\-policy\fP \fIchain target\fP +Set the policy for the chain to the given target. See the section \fBTARGETS\fP +for the legal targets. Only built-in (non-user-defined) chains can have +policies, and neither built-in nor user-defined chains can be policy +targets. +.TP +\fB\-E\fP, \fB\-\-rename\-chain\fP \fIold\-chain new\-chain\fP +Rename the user specified chain to the user supplied name. This is +cosmetic, and has no effect on the structure of the table. +.TP +\fB\-h\fP +Help. +Give a (currently very brief) description of the command syntax. +.SS PARAMETERS +The following parameters make up a rule specification (as used in the +add, delete, insert, replace and append commands). +.TP +[\fB!\fP] \fB\-p\fP, \fB\-\-protocol\fP \fIprotocol\fP +The protocol of the rule or of the packet to check. +The specified protocol can be one of \fBtcp\fP, \fBudp\fP, \fBudplite\fP, +\fBicmp\fP, \fBesp\fP, \fBah\fP, \fBsctp\fP or the special keyword "\fBall\fP", +or it can be a numeric value, representing one of these protocols or a +different one. A protocol name from /etc/protocols is also allowed. +A "!" argument before the protocol inverts the +test. The number zero is equivalent to \fBall\fP. "\fBall\fP" +will match with all protocols and is taken as default when this +option is omitted. +.TP +[\fB!\fP] \fB\-s\fP, \fB\-\-source\fP \fIaddress\fP[\fB/\fP\fImask\fP][\fB,\fP\fI...\fP] +Source specification. \fIAddress\fP +can be either a network name, a hostname, a network IP address (with +\fB/\fP\fImask\fP), or a plain IP address. Hostnames will +be resolved once only, before the rule is submitted to the kernel. +Please note that specifying any name to be resolved with a remote query such as +DNS is a really bad idea. +The \fImask\fP +can be either a network mask or a plain number, +specifying the number of 1's at the left side of the network mask. +Thus, a mask of \fI24\fP is equivalent to \fI255.255.255.0\fP. +A "!" argument before the address specification inverts the sense of +the address. The flag \fB\-\-src\fP is an alias for this option. +Multiple addresses can be specified, but this will \fBexpand to multiple +rules\fP (when adding with \-A), or will cause multiple rules to be +deleted (with \-D). +.TP +[\fB!\fP] \fB\-d\fP, \fB\-\-destination\fP \fIaddress\fP[\fB/\fP\fImask\fP][\fB,\fP\fI...\fP] +Destination specification. +See the description of the \fB\-s\fP +(source) flag for a detailed description of the syntax. The flag +\fB\-\-dst\fP is an alias for this option. +.TP +\fB\-j\fP, \fB\-\-jump\fP \fItarget\fP +This specifies the target of the rule; i.e., what to do if the packet +matches it. The target can be a user-defined chain (other than the +one this rule is in), one of the special builtin targets which decide +the fate of the packet immediately, or an extension (see \fBEXTENSIONS\fP +below). If this +option is omitted in a rule (and \fB\-g\fP +is not used), then matching the rule will have no +effect on the packet's fate, but the counters on the rule will be +incremented. +.TP +\fB\-g\fP, \fB\-\-goto\fP \fIchain\fP +This specifies that the processing should continue in a user +specified chain. Unlike the \-\-jump option return will not continue +processing in this chain but instead in the chain that called us via +\-\-jump. +.TP +[\fB!\fP] \fB\-i\fP, \fB\-\-in\-interface\fP \fIname\fP +Name of an interface via which a packet was received (only for +packets entering the \fBINPUT\fP, \fBFORWARD\fP and \fBPREROUTING\fP +chains). When the "!" argument is used before the interface name, the +sense is inverted. If the interface name ends in a "+", then any +interface which begins with this name will match. If this option is +omitted, any interface name will match. +.TP +[\fB!\fP] \fB\-o\fP, \fB\-\-out\-interface\fP \fIname\fP +Name of an interface via which a packet is going to be sent (for packets +entering the \fBFORWARD\fP, \fBOUTPUT\fP and \fBPOSTROUTING\fP +chains). When the "!" argument is used before the interface name, the +sense is inverted. If the interface name ends in a "+", then any +interface which begins with this name will match. If this option is +omitted, any interface name will match. +.TP +[\fB!\fP] \fB\-f\fP, \fB\-\-fragment\fP +This means that the rule only refers to second and further fragments +of fragmented packets. Since there is no way to tell the source or +destination ports of such a packet (or ICMP type), such a packet will +not match any rules which specify them. When the "!" argument +precedes the "\-f" flag, the rule will only match head fragments, or +unfragmented packets. +.TP +\fB\-c\fP, \fB\-\-set\-counters\fP \fIpackets bytes\fP +This enables the administrator to initialize the packet and byte +counters of a rule (during \fBINSERT\fP, \fBAPPEND\fP, \fBREPLACE\fP +operations). +.SS "OTHER OPTIONS" +The following additional options can be specified: +.TP +\fB\-v\fP, \fB\-\-verbose\fP +Verbose output. This option makes the list command show the interface +name, the rule options (if any), and the TOS masks. The packet and +byte counters are also listed, with the suffix 'K', 'M' or 'G' for +1000, 1,000,000 and 1,000,000,000 multipliers respectively (but see +the \fB\-x\fP flag to change this). +For appending, insertion, deletion and replacement, this causes +detailed information on the rule or rules to be printed. +.TP +\fB\-n\fP, \fB\-\-numeric\fP +Numeric output. +IP addresses and port numbers will be printed in numeric format. +By default, the program will try to display them as host names, +network names, or services (whenever applicable). +.TP +\fB\-x\fP, \fB\-\-exact\fP +Expand numbers. +Display the exact value of the packet and byte counters, +instead of only the rounded number in K's (multiples of 1000) +M's (multiples of 1000K) or G's (multiples of 1000M). This option is +only relevant for the \fB\-L\fP command. +.TP +\fB\-\-line\-numbers\fP +When listing rules, add line numbers to the beginning of each rule, +corresponding to that rule's position in the chain. +.TP +\fB\-\-modprobe=\fP\fIcommand\fP +When adding or inserting rules into a chain, use \fIcommand\fP +to load any necessary modules (targets, match extensions, etc). +.SH MATCH EXTENSIONS +iptables can use extended packet matching modules. These are loaded +in two ways: implicitly, when \fB\-p\fP or \fB\-\-protocol\fP +is specified, or with the \fB\-m\fP or \fB\-\-match\fP +options, followed by the matching module name; after these, various +extra command line options become available, depending on the specific +module. You can specify multiple extended match modules in one line, +and you can use the \fB\-h\fP or \fB\-\-help\fP +options after the module has been specified to receive help specific +to that module. +.PP +The following are included in the base package, and most of these can +be preceded by a "\fB!\fP" to invert the sense of the match. +.\" @MATCH@ +.SH TARGET EXTENSIONS +iptables can use extended target modules: the following are included +in the standard distribution. +.\" @TARGET@ +.SH DIAGNOSTICS +Various error messages are printed to standard error. The exit code +is 0 for correct functioning. Errors which appear to be caused by +invalid or abused command line parameters cause an exit code of 2, and +other errors cause an exit code of 1. +.SH BUGS +Bugs? What's this? ;-) +Well, you might want to have a look at http://bugzilla.netfilter.org/ +.SH COMPATIBILITY WITH IPCHAINS +This \fBiptables\fP +is very similar to ipchains by Rusty Russell. The main difference is +that the chains \fBINPUT\fP and \fBOUTPUT\fP +are only traversed for packets coming into the local host and +originating from the local host respectively. Hence every packet only +passes through one of the three chains (except loopback traffic, which +involves both INPUT and OUTPUT chains); previously a forwarded packet +would pass through all three. +.PP +The other main difference is that \fB\-i\fP refers to the input interface; +\fB\-o\fP refers to the output interface, and both are available for packets +entering the \fBFORWARD\fP chain. +.PP +The various forms of NAT have been separated out; \fBiptables\fP +is a pure packet filter when using the default `filter' table, with +optional extension modules. This should simplify much of the previous +confusion over the combination of IP masquerading and packet filtering +seen previously. So the following options are handled differently: +.nf + \-j MASQ + \-M \-S + \-M \-L +.fi +There are several other changes in iptables. +.SH SEE ALSO +\fBiptables\-save\fP(8), +\fBiptables\-restore\fP(8), +\fBip6tables\fP(8), +\fBip6tables\-save\fP(8), +\fBip6tables\-restore\fP(8), +\fBlibipq\fP(3). +.PP +The packet-filtering-HOWTO details iptables usage for +packet filtering, the NAT-HOWTO details NAT, +the netfilter-extensions-HOWTO details the extensions that are +not in the standard distribution, +and the netfilter-hacking-HOWTO details the netfilter internals. +.br +See +.BR "http://www.netfilter.org/" . +.SH AUTHORS +Rusty Russell originally wrote iptables, in early consultation with Michael +Neuling. +.PP +Marc Boucher made Rusty abandon ipnatctl by lobbying for a generic packet +selection framework in iptables, then wrote the mangle table, the owner match, +the mark stuff, and ran around doing cool stuff everywhere. +.PP +James Morris wrote the TOS target, and tos match. +.PP +Jozsef Kadlecsik wrote the REJECT target. +.PP +Harald Welte wrote the ULOG and NFQUEUE target, the new libiptc, as well as the TTL, DSCP, ECN matches and targets. +.PP +The Netfilter Core Team is: Marc Boucher, Martin Josefsson, Yasuyuki Kozakai, +Jozsef Kadlecsik, Patrick McHardy, James Morris, Pablo Neira Ayuso, +Harald Welte and Rusty Russell. +.PP +Man page originally written by Herve Eychenne . +.\" .. and did I mention that we are incredibly cool people? +.\" .. sexy, too .. +.\" .. witty, charming, powerful .. +.\" .. and most of all, modest .. +.SH VERSION +.PP +This manual page applies to iptables @PACKAGE_VERSION@. diff --git a/iptables/iptables.c b/iptables/iptables.c new file mode 100644 index 00000000..d9c6436c --- /dev/null +++ b/iptables/iptables.c @@ -0,0 +1,2003 @@ +/* Code to take an iptables-style command line and do it. */ + +/* + * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au + * + * (C) 2000-2002 by the netfilter coreteam : + * Paul 'Rusty' Russell + * Marc Boucher + * James Morris + * Harald Welte + * Jozsef Kadlecsik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xshared.h" + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#define FMT_NUMERIC 0x0001 +#define FMT_NOCOUNTS 0x0002 +#define FMT_KILOMEGAGIGA 0x0004 +#define FMT_OPTIONS 0x0008 +#define FMT_NOTABLE 0x0010 +#define FMT_NOTARGET 0x0020 +#define FMT_VIA 0x0040 +#define FMT_NONEWLINE 0x0080 +#define FMT_LINENUMBERS 0x0100 + +#define FMT_PRINT_RULE (FMT_NOCOUNTS | FMT_OPTIONS | FMT_VIA \ + | FMT_NUMERIC | FMT_NOTABLE) +#define FMT(tab,notab) ((format) & FMT_NOTABLE ? (notab) : (tab)) + + +#define CMD_NONE 0x0000U +#define CMD_INSERT 0x0001U +#define CMD_DELETE 0x0002U +#define CMD_DELETE_NUM 0x0004U +#define CMD_REPLACE 0x0008U +#define CMD_APPEND 0x0010U +#define CMD_LIST 0x0020U +#define CMD_FLUSH 0x0040U +#define CMD_ZERO 0x0080U +#define CMD_NEW_CHAIN 0x0100U +#define CMD_DELETE_CHAIN 0x0200U +#define CMD_SET_POLICY 0x0400U +#define CMD_RENAME_CHAIN 0x0800U +#define CMD_LIST_RULES 0x1000U +#define CMD_ZERO_NUM 0x2000U +#define CMD_CHECK 0x4000U +#define NUMBER_OF_CMD 16 +static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z', + 'Z', 'N', 'X', 'P', 'E', 'S', 'C' }; + +#define OPT_FRAGMENT 0x00800U +#define NUMBER_OF_OPT ARRAY_SIZE(optflags) +static const char optflags[] += { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', '0', 'c', 'f'}; + +static struct option original_opts[] = { + {.name = "append", .has_arg = 1, .val = 'A'}, + {.name = "delete", .has_arg = 1, .val = 'D'}, + {.name = "check", .has_arg = 1, .val = 'C'}, + {.name = "insert", .has_arg = 1, .val = 'I'}, + {.name = "replace", .has_arg = 1, .val = 'R'}, + {.name = "list", .has_arg = 2, .val = 'L'}, + {.name = "list-rules", .has_arg = 2, .val = 'S'}, + {.name = "flush", .has_arg = 2, .val = 'F'}, + {.name = "zero", .has_arg = 2, .val = 'Z'}, + {.name = "new-chain", .has_arg = 1, .val = 'N'}, + {.name = "delete-chain", .has_arg = 2, .val = 'X'}, + {.name = "rename-chain", .has_arg = 1, .val = 'E'}, + {.name = "policy", .has_arg = 1, .val = 'P'}, + {.name = "source", .has_arg = 1, .val = 's'}, + {.name = "destination", .has_arg = 1, .val = 'd'}, + {.name = "src", .has_arg = 1, .val = 's'}, /* synonym */ + {.name = "dst", .has_arg = 1, .val = 'd'}, /* synonym */ + {.name = "protocol", .has_arg = 1, .val = 'p'}, + {.name = "in-interface", .has_arg = 1, .val = 'i'}, + {.name = "jump", .has_arg = 1, .val = 'j'}, + {.name = "table", .has_arg = 1, .val = 't'}, + {.name = "match", .has_arg = 1, .val = 'm'}, + {.name = "numeric", .has_arg = 0, .val = 'n'}, + {.name = "out-interface", .has_arg = 1, .val = 'o'}, + {.name = "verbose", .has_arg = 0, .val = 'v'}, + {.name = "exact", .has_arg = 0, .val = 'x'}, + {.name = "fragments", .has_arg = 0, .val = 'f'}, + {.name = "version", .has_arg = 0, .val = 'V'}, + {.name = "help", .has_arg = 2, .val = 'h'}, + {.name = "line-numbers", .has_arg = 0, .val = '0'}, + {.name = "modprobe", .has_arg = 1, .val = 'M'}, + {.name = "set-counters", .has_arg = 1, .val = 'c'}, + {.name = "goto", .has_arg = 1, .val = 'g'}, + {.name = "ipv4", .has_arg = 0, .val = '4'}, + {.name = "ipv6", .has_arg = 0, .val = '6'}, + {NULL}, +}; + +void iptables_exit_error(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3))); + +struct xtables_globals iptables_globals = { + .option_offset = 0, + .program_version = IPTABLES_VERSION, + .orig_opts = original_opts, + .exit_err = iptables_exit_error, +}; + +/* Table of legal combinations of commands and options. If any of the + * given commands make an option legal, that option is legal (applies to + * CMD_LIST and CMD_ZERO only). + * Key: + * + compulsory + * x illegal + * optional + */ + +static const char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] = +/* Well, it's better than "Re: Linux vs FreeBSD" */ +{ + /* -n -s -d -p -j -v -x -i -o --line -c -f */ +/*INSERT*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '}, +/*DELETE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x',' '}, +/*DELETE_NUM*/{'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*REPLACE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '}, +/*APPEND*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '}, +/*LIST*/ {' ','x','x','x','x',' ',' ','x','x',' ','x','x'}, +/*FLUSH*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*ZERO*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*ZERO_NUM*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x',' ','x'}, +/*RENAME*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*LIST_RULES*/{'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*CHECK*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x',' '}, +}; + +static const int inverse_for_options[NUMBER_OF_OPT] = +{ +/* -n */ 0, +/* -s */ IPT_INV_SRCIP, +/* -d */ IPT_INV_DSTIP, +/* -p */ IPT_INV_PROTO, +/* -j */ 0, +/* -v */ 0, +/* -x */ 0, +/* -i */ IPT_INV_VIA_IN, +/* -o */ IPT_INV_VIA_OUT, +/* -f */ IPT_INV_FRAG, +/*--line*/ 0, +/* -c */ 0, +}; + +#define opts iptables_globals.opts +#define prog_name iptables_globals.program_name +#define prog_vers iptables_globals.program_version + +int kernel_version; + +/* Primitive headers... */ +/* defined in netinet/in.h */ +#if 0 +#ifndef IPPROTO_ESP +#define IPPROTO_ESP 50 +#endif +#ifndef IPPROTO_AH +#define IPPROTO_AH 51 +#endif +#endif + +enum { + IPT_DOTTED_ADDR = 0, + IPT_DOTTED_MASK +}; + +static void __attribute__((noreturn)) +exit_tryhelp(int status) +{ + if (line != -1) + fprintf(stderr, "Error occurred at line: %d\n", line); + fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n", + prog_name, prog_name); + xtables_free_opts(1); + exit(status); +} + +static void +exit_printhelp(const struct xtables_rule_match *matches) +{ + printf("%s v%s\n\n" +"Usage: %s -[ACD] chain rule-specification [options]\n" +" %s -I chain [rulenum] rule-specification [options]\n" +" %s -R chain rulenum rule-specification [options]\n" +" %s -D chain rulenum [options]\n" +" %s -[LS] [chain [rulenum]] [options]\n" +" %s -[FZ] [chain] [options]\n" +" %s -[NX] chain\n" +" %s -E old-chain-name new-chain-name\n" +" %s -P chain target [options]\n" +" %s -h (print this help information)\n\n", + prog_name, prog_vers, prog_name, prog_name, + prog_name, prog_name, prog_name, prog_name, + prog_name, prog_name, prog_name, prog_name); + + printf( +"Commands:\n" +"Either long or short options are allowed.\n" +" --append -A chain Append to chain\n" +" --check -C chain Check for the existence of a rule\n" +" --delete -D chain Delete matching rule from chain\n" +" --delete -D chain rulenum\n" +" Delete rule rulenum (1 = first) from chain\n" +" --insert -I chain [rulenum]\n" +" Insert in chain as rulenum (default 1=first)\n" +" --replace -R chain rulenum\n" +" Replace rule rulenum (1 = first) in chain\n" +" --list -L [chain [rulenum]]\n" +" List the rules in a chain or all chains\n" +" --list-rules -S [chain [rulenum]]\n" +" Print the rules in a chain or all chains\n" +" --flush -F [chain] Delete all rules in chain or all chains\n" +" --zero -Z [chain [rulenum]]\n" +" Zero counters in chain or all chains\n" +" --new -N chain Create a new user-defined chain\n" +" --delete-chain\n" +" -X [chain] Delete a user-defined chain\n" +" --policy -P chain target\n" +" Change policy on chain to target\n" +" --rename-chain\n" +" -E old-chain new-chain\n" +" Change chain name, (moving any references)\n" + +"Options:\n" +" --ipv4 -4 Nothing (line is ignored by ip6tables-restore)\n" +" --ipv6 -6 Error (line is ignored by iptables-restore)\n" +"[!] --proto -p proto protocol: by number or name, eg. `tcp'\n" +"[!] --source -s address[/mask][...]\n" +" source specification\n" +"[!] --destination -d address[/mask][...]\n" +" destination specification\n" +"[!] --in-interface -i input name[+]\n" +" network interface name ([+] for wildcard)\n" +" --jump -j target\n" +" target for rule (may load target extension)\n" +#ifdef IPT_F_GOTO +" --goto -g chain\n" +" jump to chain with no return\n" +#endif +" --match -m match\n" +" extended match (may load extension)\n" +" --numeric -n numeric output of addresses and ports\n" +"[!] --out-interface -o output name[+]\n" +" network interface name ([+] for wildcard)\n" +" --table -t table table to manipulate (default: `filter')\n" +" --verbose -v verbose mode\n" +" --line-numbers print line numbers when listing\n" +" --exact -x expand numbers (display exact values)\n" +"[!] --fragment -f match second or further fragments only\n" +" --modprobe= try to insert modules using this command\n" +" --set-counters PKTS BYTES set the counter during insert/append\n" +"[!] --version -V print package version.\n"); + + print_extension_helps(xtables_targets, matches); + exit(0); +} + +void +iptables_exit_error(enum xtables_exittype status, const char *msg, ...) +{ + va_list args; + + va_start(args, msg); + fprintf(stderr, "%s v%s: ", prog_name, prog_vers); + vfprintf(stderr, msg, args); + va_end(args); + fprintf(stderr, "\n"); + if (status == PARAMETER_PROBLEM) + exit_tryhelp(status); + if (status == VERSION_PROBLEM) + fprintf(stderr, + "Perhaps iptables or your kernel needs to be upgraded.\n"); + /* On error paths, make sure that we don't leak memory */ + xtables_free_opts(1); + exit(status); +} + +static void +generic_opt_check(int command, int options) +{ + int i, j, legal = 0; + + /* Check that commands are valid with options. Complicated by the + * fact that if an option is legal with *any* command given, it is + * legal overall (ie. -z and -l). + */ + for (i = 0; i < NUMBER_OF_OPT; i++) { + legal = 0; /* -1 => illegal, 1 => legal, 0 => undecided. */ + + for (j = 0; j < NUMBER_OF_CMD; j++) { + if (!(command & (1< 1; option >>= 1, ptr++); + + return *ptr; +} + +static char +cmd2char(int option) +{ + const char *ptr; + for (ptr = cmdflags; option > 1; option >>= 1, ptr++); + + return *ptr; +} + +static void +add_command(unsigned int *cmd, const int newcmd, const int othercmds, + int invert) +{ + if (invert) + xtables_error(PARAMETER_PROBLEM, "unexpected ! flag"); + if (*cmd & (~othercmds)) + xtables_error(PARAMETER_PROBLEM, "Cannot use -%c with -%c\n", + cmd2char(newcmd), cmd2char(*cmd & (~othercmds))); + *cmd |= newcmd; +} + +/* + * All functions starting with "parse" should succeed, otherwise + * the program fails. + * Most routines return pointers to static data that may change + * between calls to the same or other routines with a few exceptions: + * "host_to_addr", "parse_hostnetwork", and "parse_hostnetworkmask" + * return global static data. +*/ + +/* Christophe Burki wants `-p 6' to imply `-m tcp'. */ +/* Can't be zero. */ +static int +parse_rulenumber(const char *rule) +{ + unsigned int rulenum; + + if (!xtables_strtoui(rule, NULL, &rulenum, 1, INT_MAX)) + xtables_error(PARAMETER_PROBLEM, + "Invalid rule number `%s'", rule); + + return rulenum; +} + +static const char * +parse_target(const char *targetname) +{ + const char *ptr; + + if (strlen(targetname) < 1) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name (too short)"); + + if (strlen(targetname) >= XT_EXTENSION_MAXNAMELEN) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name `%s' (%u chars max)", + targetname, XT_EXTENSION_MAXNAMELEN - 1); + + for (ptr = targetname; *ptr; ptr++) + if (isspace(*ptr)) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name `%s'", targetname); + return targetname; +} + +static void +set_option(unsigned int *options, unsigned int option, uint8_t *invflg, + int invert) +{ + if (*options & option) + xtables_error(PARAMETER_PROBLEM, "multiple -%c flags not allowed", + opt2char(option)); + *options |= option; + + if (invert) { + unsigned int i; + for (i = 0; 1 << i != option; i++); + + if (!inverse_for_options[i]) + xtables_error(PARAMETER_PROBLEM, + "cannot have ! before -%c", + opt2char(option)); + *invflg |= inverse_for_options[i]; + } +} + +static void +print_num(uint64_t number, unsigned int format) +{ + if (format & FMT_KILOMEGAGIGA) { + if (number > 99999) { + number = (number + 500) / 1000; + if (number > 9999) { + number = (number + 500) / 1000; + if (number > 9999) { + number = (number + 500) / 1000; + if (number > 9999) { + number = (number + 500) / 1000; + printf(FMT("%4lluT ","%lluT "), (unsigned long long)number); + } + else printf(FMT("%4lluG ","%lluG "), (unsigned long long)number); + } + else printf(FMT("%4lluM ","%lluM "), (unsigned long long)number); + } else + printf(FMT("%4lluK ","%lluK "), (unsigned long long)number); + } else + printf(FMT("%5llu ","%llu "), (unsigned long long)number); + } else + printf(FMT("%8llu ","%llu "), (unsigned long long)number); +} + + +static void +print_header(unsigned int format, const char *chain, struct iptc_handle *handle) +{ + struct ipt_counters counters; + const char *pol = iptc_get_policy(chain, &counters, handle); + printf("Chain %s", chain); + if (pol) { + printf(" (policy %s", pol); + if (!(format & FMT_NOCOUNTS)) { + fputc(' ', stdout); + print_num(counters.pcnt, (format|FMT_NOTABLE)); + fputs("packets, ", stdout); + print_num(counters.bcnt, (format|FMT_NOTABLE)); + fputs("bytes", stdout); + } + printf(")\n"); + } else { + unsigned int refs; + if (!iptc_get_references(&refs, chain, handle)) + printf(" (ERROR obtaining refs)\n"); + else + printf(" (%u references)\n", refs); + } + + if (format & FMT_LINENUMBERS) + printf(FMT("%-4s ", "%s "), "num"); + if (!(format & FMT_NOCOUNTS)) { + if (format & FMT_KILOMEGAGIGA) { + printf(FMT("%5s ","%s "), "pkts"); + printf(FMT("%5s ","%s "), "bytes"); + } else { + printf(FMT("%8s ","%s "), "pkts"); + printf(FMT("%10s ","%s "), "bytes"); + } + } + if (!(format & FMT_NOTARGET)) + printf(FMT("%-9s ","%s "), "target"); + fputs(" prot ", stdout); + if (format & FMT_OPTIONS) + fputs("opt", stdout); + if (format & FMT_VIA) { + printf(FMT(" %-6s ","%s "), "in"); + printf(FMT("%-6s ","%s "), "out"); + } + printf(FMT(" %-19s ","%s "), "source"); + printf(FMT(" %-19s "," %s "), "destination"); + printf("\n"); +} + + +static int +print_match(const struct ipt_entry_match *m, + const struct ipt_ip *ip, + int numeric) +{ + const struct xtables_match *match = + xtables_find_match(m->u.user.name, XTF_TRY_LOAD, NULL); + + if (match) { + if (match->print) + match->print(ip, m, numeric); + else + printf("%s ", match->name); + } else { + if (m->u.user.name[0]) + printf("UNKNOWN match `%s' ", m->u.user.name); + } + /* Don't stop iterating. */ + return 0; +} + +/* e is called `fw' here for historical reasons */ +static void +print_firewall(const struct ipt_entry *fw, + const char *targname, + unsigned int num, + unsigned int format, + struct iptc_handle *const handle) +{ + const struct xtables_target *target = NULL; + const struct ipt_entry_target *t; + uint8_t flags; + char buf[BUFSIZ]; + + if (!iptc_is_chain(targname, handle)) + target = xtables_find_target(targname, XTF_TRY_LOAD); + else + target = xtables_find_target(IPT_STANDARD_TARGET, + XTF_LOAD_MUST_SUCCEED); + + t = ipt_get_target((struct ipt_entry *)fw); + flags = fw->ip.flags; + + if (format & FMT_LINENUMBERS) + printf(FMT("%-4u ", "%u "), num); + + if (!(format & FMT_NOCOUNTS)) { + print_num(fw->counters.pcnt, format); + print_num(fw->counters.bcnt, format); + } + + if (!(format & FMT_NOTARGET)) + printf(FMT("%-9s ", "%s "), targname); + + fputc(fw->ip.invflags & IPT_INV_PROTO ? '!' : ' ', stdout); + { + const char *pname = proto_to_name(fw->ip.proto, format&FMT_NUMERIC); + if (pname) + printf(FMT("%-5s", "%s "), pname); + else + printf(FMT("%-5hu", "%hu "), fw->ip.proto); + } + + if (format & FMT_OPTIONS) { + if (format & FMT_NOTABLE) + fputs("opt ", stdout); + fputc(fw->ip.invflags & IPT_INV_FRAG ? '!' : '-', stdout); + fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout); + fputc(' ', stdout); + } + + if (format & FMT_VIA) { + char iface[IFNAMSIZ+2]; + + if (fw->ip.invflags & IPT_INV_VIA_IN) { + iface[0] = '!'; + iface[1] = '\0'; + } + else iface[0] = '\0'; + + if (fw->ip.iniface[0] != '\0') { + strcat(iface, fw->ip.iniface); + } + else if (format & FMT_NUMERIC) strcat(iface, "*"); + else strcat(iface, "any"); + printf(FMT(" %-6s ","in %s "), iface); + + if (fw->ip.invflags & IPT_INV_VIA_OUT) { + iface[0] = '!'; + iface[1] = '\0'; + } + else iface[0] = '\0'; + + if (fw->ip.outiface[0] != '\0') { + strcat(iface, fw->ip.outiface); + } + else if (format & FMT_NUMERIC) strcat(iface, "*"); + else strcat(iface, "any"); + printf(FMT("%-6s ","out %s "), iface); + } + + fputc(fw->ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout); + if (fw->ip.smsk.s_addr == 0L && !(format & FMT_NUMERIC)) + printf(FMT("%-19s ","%s "), "anywhere"); + else { + if (format & FMT_NUMERIC) + strcpy(buf, xtables_ipaddr_to_numeric(&fw->ip.src)); + else + strcpy(buf, xtables_ipaddr_to_anyname(&fw->ip.src)); + strcat(buf, xtables_ipmask_to_numeric(&fw->ip.smsk)); + printf(FMT("%-19s ","%s "), buf); + } + + fputc(fw->ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout); + if (fw->ip.dmsk.s_addr == 0L && !(format & FMT_NUMERIC)) + printf(FMT("%-19s ","-> %s"), "anywhere"); + else { + if (format & FMT_NUMERIC) + strcpy(buf, xtables_ipaddr_to_numeric(&fw->ip.dst)); + else + strcpy(buf, xtables_ipaddr_to_anyname(&fw->ip.dst)); + strcat(buf, xtables_ipmask_to_numeric(&fw->ip.dmsk)); + printf(FMT("%-19s ","-> %s"), buf); + } + + if (format & FMT_NOTABLE) + fputs(" ", stdout); + +#ifdef IPT_F_GOTO + if(fw->ip.flags & IPT_F_GOTO) + printf("[goto] "); +#endif + + IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC); + + if (target) { + if (target->print) + /* Print the target information. */ + target->print(&fw->ip, t, format & FMT_NUMERIC); + } else if (t->u.target_size != sizeof(*t)) + printf("[%u bytes of unknown target data] ", + (unsigned int)(t->u.target_size - sizeof(*t))); + + if (!(format & FMT_NONEWLINE)) + fputc('\n', stdout); +} + +static void +print_firewall_line(const struct ipt_entry *fw, + struct iptc_handle *const h) +{ + struct ipt_entry_target *t; + + t = ipt_get_target((struct ipt_entry *)fw); + print_firewall(fw, t->u.user.name, 0, FMT_PRINT_RULE, h); +} + +static int +append_entry(const ipt_chainlabel chain, + struct ipt_entry *fw, + unsigned int nsaddrs, + const struct in_addr saddrs[], + const struct in_addr smasks[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + const struct in_addr dmasks[], + int verbose, + struct iptc_handle *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + fw->ip.smsk.s_addr = smasks[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[j].s_addr; + fw->ip.dmsk.s_addr = dmasks[j].s_addr; + if (verbose) + print_firewall_line(fw, handle); + ret &= iptc_append_entry(chain, fw, handle); + } + } + + return ret; +} + +static int +replace_entry(const ipt_chainlabel chain, + struct ipt_entry *fw, + unsigned int rulenum, + const struct in_addr *saddr, const struct in_addr *smask, + const struct in_addr *daddr, const struct in_addr *dmask, + int verbose, + struct iptc_handle *handle) +{ + fw->ip.src.s_addr = saddr->s_addr; + fw->ip.dst.s_addr = daddr->s_addr; + fw->ip.smsk.s_addr = smask->s_addr; + fw->ip.dmsk.s_addr = dmask->s_addr; + + if (verbose) + print_firewall_line(fw, handle); + return iptc_replace_entry(chain, fw, rulenum, handle); +} + +static int +insert_entry(const ipt_chainlabel chain, + struct ipt_entry *fw, + unsigned int rulenum, + unsigned int nsaddrs, + const struct in_addr saddrs[], + const struct in_addr smasks[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + const struct in_addr dmasks[], + int verbose, + struct iptc_handle *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + fw->ip.smsk.s_addr = smasks[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[j].s_addr; + fw->ip.dmsk.s_addr = dmasks[j].s_addr; + if (verbose) + print_firewall_line(fw, handle); + ret &= iptc_insert_entry(chain, fw, rulenum, handle); + } + } + + return ret; +} + +static unsigned char * +make_delete_mask(const struct xtables_rule_match *matches, + const struct xtables_target *target) +{ + /* Establish mask for comparison */ + unsigned int size; + const struct xtables_rule_match *matchp; + unsigned char *mask, *mptr; + + size = sizeof(struct ipt_entry); + for (matchp = matches; matchp; matchp = matchp->next) + size += XT_ALIGN(sizeof(struct ipt_entry_match)) + matchp->match->size; + + mask = xtables_calloc(1, size + + XT_ALIGN(sizeof(struct ipt_entry_target)) + + target->size); + + memset(mask, 0xFF, sizeof(struct ipt_entry)); + mptr = mask + sizeof(struct ipt_entry); + + for (matchp = matches; matchp; matchp = matchp->next) { + memset(mptr, 0xFF, + XT_ALIGN(sizeof(struct ipt_entry_match)) + + matchp->match->userspacesize); + mptr += XT_ALIGN(sizeof(struct ipt_entry_match)) + matchp->match->size; + } + + memset(mptr, 0xFF, + XT_ALIGN(sizeof(struct ipt_entry_target)) + + target->userspacesize); + + return mask; +} + +static int +delete_entry(const ipt_chainlabel chain, + struct ipt_entry *fw, + unsigned int nsaddrs, + const struct in_addr saddrs[], + const struct in_addr smasks[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + const struct in_addr dmasks[], + int verbose, + struct iptc_handle *handle, + struct xtables_rule_match *matches, + const struct xtables_target *target) +{ + unsigned int i, j; + int ret = 1; + unsigned char *mask; + + mask = make_delete_mask(matches, target); + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + fw->ip.smsk.s_addr = smasks[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[j].s_addr; + fw->ip.dmsk.s_addr = dmasks[j].s_addr; + if (verbose) + print_firewall_line(fw, handle); + ret &= iptc_delete_entry(chain, fw, mask, handle); + } + } + free(mask); + + return ret; +} + +static int +check_entry(const ipt_chainlabel chain, struct ipt_entry *fw, + unsigned int nsaddrs, const struct in_addr *saddrs, + const struct in_addr *smasks, unsigned int ndaddrs, + const struct in_addr *daddrs, const struct in_addr *dmasks, + bool verbose, struct iptc_handle *handle, + struct xtables_rule_match *matches, + const struct xtables_target *target) +{ + unsigned int i, j; + int ret = 1; + unsigned char *mask; + + mask = make_delete_mask(matches, target); + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + fw->ip.smsk.s_addr = smasks[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[j].s_addr; + fw->ip.dmsk.s_addr = dmasks[j].s_addr; + if (verbose) + print_firewall_line(fw, handle); + ret &= iptc_check_entry(chain, fw, mask, handle); + } + } + + free(mask); + return ret; +} + +int +for_each_chain4(int (*fn)(const ipt_chainlabel, int, struct iptc_handle *), + int verbose, int builtinstoo, struct iptc_handle *handle) +{ + int ret = 1; + const char *chain; + char *chains; + unsigned int i, chaincount = 0; + + chain = iptc_first_chain(handle); + while (chain) { + chaincount++; + chain = iptc_next_chain(handle); + } + + chains = xtables_malloc(sizeof(ipt_chainlabel) * chaincount); + i = 0; + chain = iptc_first_chain(handle); + while (chain) { + strcpy(chains + i*sizeof(ipt_chainlabel), chain); + i++; + chain = iptc_next_chain(handle); + } + + for (i = 0; i < chaincount; i++) { + if (!builtinstoo + && iptc_builtin(chains + i*sizeof(ipt_chainlabel), + handle) == 1) + continue; + ret &= fn(chains + i*sizeof(ipt_chainlabel), verbose, handle); + } + + free(chains); + return ret; +} + +int +flush_entries4(const ipt_chainlabel chain, int verbose, + struct iptc_handle *handle) +{ + if (!chain) + return for_each_chain4(flush_entries4, verbose, 1, handle); + + if (verbose) + fprintf(stdout, "Flushing chain `%s'\n", chain); + return iptc_flush_entries(chain, handle); +} + +static int +zero_entries(const ipt_chainlabel chain, int verbose, + struct iptc_handle *handle) +{ + if (!chain) + return for_each_chain4(zero_entries, verbose, 1, handle); + + if (verbose) + fprintf(stdout, "Zeroing chain `%s'\n", chain); + return iptc_zero_entries(chain, handle); +} + +int +delete_chain4(const ipt_chainlabel chain, int verbose, + struct iptc_handle *handle) +{ + if (!chain) + return for_each_chain4(delete_chain4, verbose, 0, handle); + + if (verbose) + fprintf(stdout, "Deleting chain `%s'\n", chain); + return iptc_delete_chain(chain, handle); +} + +static int +list_entries(const ipt_chainlabel chain, int rulenum, int verbose, int numeric, + int expanded, int linenumbers, struct iptc_handle *handle) +{ + int found = 0; + unsigned int format; + const char *this; + + format = FMT_OPTIONS; + if (!verbose) + format |= FMT_NOCOUNTS; + else + format |= FMT_VIA; + + if (numeric) + format |= FMT_NUMERIC; + + if (!expanded) + format |= FMT_KILOMEGAGIGA; + + if (linenumbers) + format |= FMT_LINENUMBERS; + + for (this = iptc_first_chain(handle); + this; + this = iptc_next_chain(handle)) { + const struct ipt_entry *i; + unsigned int num; + + if (chain && strcmp(chain, this) != 0) + continue; + + if (found) printf("\n"); + + if (!rulenum) + print_header(format, this, handle); + i = iptc_first_rule(this, handle); + + num = 0; + while (i) { + num++; + if (!rulenum || num == rulenum) + print_firewall(i, + iptc_get_target(i, handle), + num, + format, + handle); + i = iptc_next_rule(i, handle); + } + found = 1; + } + + errno = ENOENT; + return found; +} + +static void print_proto(uint16_t proto, int invert) +{ + if (proto) { + unsigned int i; + const char *invertstr = invert ? " !" : ""; + + const struct protoent *pent = getprotobynumber(proto); + if (pent) { + printf("%s -p %s", invertstr, pent->p_name); + return; + } + + for (i = 0; xtables_chain_protos[i].name != NULL; ++i) + if (xtables_chain_protos[i].num == proto) { + printf("%s -p %s", + invertstr, xtables_chain_protos[i].name); + return; + } + + printf("%s -p %u", invertstr, proto); + } +} + +#define IP_PARTS_NATIVE(n) \ +(unsigned int)((n)>>24)&0xFF, \ +(unsigned int)((n)>>16)&0xFF, \ +(unsigned int)((n)>>8)&0xFF, \ +(unsigned int)((n)&0xFF) + +#define IP_PARTS(n) IP_PARTS_NATIVE(ntohl(n)) + +/* This assumes that mask is contiguous, and byte-bounded. */ +static void +print_iface(char letter, const char *iface, const unsigned char *mask, + int invert) +{ + unsigned int i; + + if (mask[0] == 0) + return; + + printf("%s -%c ", invert ? " !" : "", letter); + + for (i = 0; i < IFNAMSIZ; i++) { + if (mask[i] != 0) { + if (iface[i] != '\0') + printf("%c", iface[i]); + } else { + /* we can access iface[i-1] here, because + * a few lines above we make sure that mask[0] != 0 */ + if (iface[i-1] != '\0') + printf("+"); + break; + } + } +} + +static int print_match_save(const struct ipt_entry_match *e, + const struct ipt_ip *ip) +{ + const struct xtables_match *match = + xtables_find_match(e->u.user.name, XTF_TRY_LOAD, NULL); + + if (match) { + printf(" -m %s", e->u.user.name); + + /* some matches don't provide a save function */ + if (match->save) + match->save(ip, e); + } else { + if (e->u.match_size) { + fprintf(stderr, + "Can't find library for match `%s'\n", + e->u.user.name); + exit(1); + } + } + return 0; +} + +/* print a given ip including mask if neccessary */ +static void print_ip(const char *prefix, uint32_t ip, + uint32_t mask, int invert) +{ + uint32_t bits, hmask = ntohl(mask); + int i; + + if (!mask && !ip && !invert) + return; + + printf("%s %s %u.%u.%u.%u", + invert ? " !" : "", + prefix, + IP_PARTS(ip)); + + if (mask == 0xFFFFFFFFU) { + printf("/32"); + return; + } + + i = 32; + bits = 0xFFFFFFFEU; + while (--i >= 0 && hmask != bits) + bits <<= 1; + if (i >= 0) + printf("/%u", i); + else + printf("/%u.%u.%u.%u", IP_PARTS(mask)); +} + +/* We want this to be readable, so only print out neccessary fields. + * Because that's the kind of world I want to live in. */ +void print_rule4(const struct ipt_entry *e, + struct iptc_handle *h, const char *chain, int counters) +{ + const struct ipt_entry_target *t; + const char *target_name; + + /* print counters for iptables-save */ + if (counters > 0) + printf("[%llu:%llu] ", (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt); + + /* print chain name */ + printf("-A %s", chain); + + /* Print IP part. */ + print_ip("-s", e->ip.src.s_addr,e->ip.smsk.s_addr, + e->ip.invflags & IPT_INV_SRCIP); + + print_ip("-d", e->ip.dst.s_addr, e->ip.dmsk.s_addr, + e->ip.invflags & IPT_INV_DSTIP); + + print_iface('i', e->ip.iniface, e->ip.iniface_mask, + e->ip.invflags & IPT_INV_VIA_IN); + + print_iface('o', e->ip.outiface, e->ip.outiface_mask, + e->ip.invflags & IPT_INV_VIA_OUT); + + print_proto(e->ip.proto, e->ip.invflags & IPT_INV_PROTO); + + if (e->ip.flags & IPT_F_FRAG) + printf("%s -f", + e->ip.invflags & IPT_INV_FRAG ? " !" : ""); + + /* Print matchinfo part */ + if (e->target_offset) { + IPT_MATCH_ITERATE(e, print_match_save, &e->ip); + } + + /* print counters for iptables -R */ + if (counters < 0) + printf(" -c %llu %llu", (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt); + + /* Print target name */ + target_name = iptc_get_target(e, h); + if (target_name && (*target_name != '\0')) +#ifdef IPT_F_GOTO + printf(" -%c %s", e->ip.flags & IPT_F_GOTO ? 'g' : 'j', target_name); +#else + printf(" -j %s", target_name); +#endif + + /* Print targinfo part */ + t = ipt_get_target((struct ipt_entry *)e); + if (t->u.user.name[0]) { + const struct xtables_target *target = + xtables_find_target(t->u.user.name, XTF_TRY_LOAD); + + if (!target) { + fprintf(stderr, "Can't find library for target `%s'\n", + t->u.user.name); + exit(1); + } + + if (target->save) + target->save(&e->ip, t); + else { + /* If the target size is greater than ipt_entry_target + * there is something to be saved, we just don't know + * how to print it */ + if (t->u.target_size != + sizeof(struct ipt_entry_target)) { + fprintf(stderr, "Target `%s' is missing " + "save function\n", + t->u.user.name); + exit(1); + } + } + } + printf("\n"); +} + +static int +list_rules(const ipt_chainlabel chain, int rulenum, int counters, + struct iptc_handle *handle) +{ + const char *this = NULL; + int found = 0; + + if (counters) + counters = -1; /* iptables -c format */ + + /* Dump out chain names first, + * thereby preventing dependency conflicts */ + if (!rulenum) for (this = iptc_first_chain(handle); + this; + this = iptc_next_chain(handle)) { + if (chain && strcmp(this, chain) != 0) + continue; + + if (iptc_builtin(this, handle)) { + struct ipt_counters count; + printf("-P %s %s", this, iptc_get_policy(this, &count, handle)); + if (counters) + printf(" -c %llu %llu", (unsigned long long)count.pcnt, (unsigned long long)count.bcnt); + printf("\n"); + } else { + printf("-N %s\n", this); + } + } + + for (this = iptc_first_chain(handle); + this; + this = iptc_next_chain(handle)) { + const struct ipt_entry *e; + int num = 0; + + if (chain && strcmp(this, chain) != 0) + continue; + + /* Dump out rules */ + e = iptc_first_rule(this, handle); + while(e) { + num++; + if (!rulenum || num == rulenum) + print_rule4(e, handle, this, counters); + e = iptc_next_rule(e, handle); + } + found = 1; + } + + errno = ENOENT; + return found; +} + +static struct ipt_entry * +generate_entry(const struct ipt_entry *fw, + struct xtables_rule_match *matches, + struct ipt_entry_target *target) +{ + unsigned int size; + struct xtables_rule_match *matchp; + struct ipt_entry *e; + + size = sizeof(struct ipt_entry); + for (matchp = matches; matchp; matchp = matchp->next) + size += matchp->match->m->u.match_size; + + e = xtables_malloc(size + target->u.target_size); + *e = *fw; + e->target_offset = size; + e->next_offset = size + target->u.target_size; + + size = 0; + for (matchp = matches; matchp; matchp = matchp->next) { + memcpy(e->elems + size, matchp->match->m, matchp->match->m->u.match_size); + size += matchp->match->m->u.match_size; + } + memcpy(e->elems + size, target, target->u.target_size); + + return e; +} + +static void clear_rule_matches(struct xtables_rule_match **matches) +{ + struct xtables_rule_match *matchp, *tmp; + + for (matchp = *matches; matchp;) { + tmp = matchp->next; + if (matchp->match->m) { + free(matchp->match->m); + matchp->match->m = NULL; + } + if (matchp->match == matchp->match->next) { + free(matchp->match); + matchp->match = NULL; + } + free(matchp); + matchp = tmp; + } + + *matches = NULL; +} + +void +get_kernel_version(void) { + static struct utsname uts; + int x = 0, y = 0, z = 0; + + if (uname(&uts) == -1) { + fprintf(stderr, "Unable to retrieve kernel version.\n"); + xtables_free_opts(1); + exit(1); + } + + sscanf(uts.release, "%d.%d.%d", &x, &y, &z); + kernel_version = LINUX_VERSION(x, y, z); +} + +static void command_jump(struct iptables_command_state *cs) +{ + size_t size; + + set_option(&cs->options, OPT_JUMP, &cs->fw.ip.invflags, cs->invert); + cs->jumpto = parse_target(optarg); + /* TRY_LOAD (may be chain name) */ + cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD); + + if (cs->target == NULL) + return; + + size = XT_ALIGN(sizeof(struct ipt_entry_target)) + + cs->target->size; + + cs->target->t = xtables_calloc(1, size); + cs->target->t->u.target_size = size; + strcpy(cs->target->t->u.user.name, cs->jumpto); + cs->target->t->u.user.revision = cs->target->revision; + if (cs->target->init != NULL) + cs->target->init(cs->target->t); + if (cs->target->x6_options != NULL) + opts = xtables_options_xfrm(iptables_globals.orig_opts, opts, + cs->target->x6_options, + &cs->target->option_offset); + else + opts = xtables_merge_options(iptables_globals.orig_opts, opts, + cs->target->extra_opts, + &cs->target->option_offset); + if (opts == NULL) + xtables_error(OTHER_PROBLEM, "can't alloc memory!"); +} + +static void command_match(struct iptables_command_state *cs) +{ + struct xtables_match *m; + size_t size; + + if (cs->invert) + xtables_error(PARAMETER_PROBLEM, + "unexpected ! flag before --match"); + + m = xtables_find_match(optarg, XTF_LOAD_MUST_SUCCEED, &cs->matches); + size = XT_ALIGN(sizeof(struct ipt_entry_match)) + m->size; + m->m = xtables_calloc(1, size); + m->m->u.match_size = size; + strcpy(m->m->u.user.name, m->name); + m->m->u.user.revision = m->revision; + if (m->init != NULL) + m->init(m->m); + if (m == m->next) + return; + /* Merge options for non-cloned matches */ + if (m->x6_options != NULL) + opts = xtables_options_xfrm(iptables_globals.orig_opts, opts, + m->x6_options, &m->option_offset); + else if (m->extra_opts != NULL) + opts = xtables_merge_options(iptables_globals.orig_opts, opts, + m->extra_opts, &m->option_offset); + if (opts == NULL) + xtables_error(OTHER_PROBLEM, "can't alloc memory!"); +} + +int do_command4(int argc, char *argv[], char **table, struct iptc_handle **handle) +{ + struct iptables_command_state cs; + struct ipt_entry *e = NULL; + unsigned int nsaddrs = 0, ndaddrs = 0; + struct in_addr *saddrs = NULL, *smasks = NULL; + struct in_addr *daddrs = NULL, *dmasks = NULL; + + int verbose = 0; + const char *chain = NULL; + const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL; + const char *policy = NULL, *newname = NULL; + unsigned int rulenum = 0, command = 0; + const char *pcnt = NULL, *bcnt = NULL; + int ret = 1; + struct xtables_match *m; + struct xtables_rule_match *matchp; + struct xtables_target *t; + unsigned long long cnt; + + memset(&cs, 0, sizeof(cs)); + cs.jumpto = ""; + cs.argv = argv; + + /* re-set optind to 0 in case do_command4 gets called + * a second time */ + optind = 0; + + /* clear mflags in case do_command4 gets called a second time + * (we clear the global list of all matches for security)*/ + for (m = xtables_matches; m; m = m->next) + m->mflags = 0; + + for (t = xtables_targets; t; t = t->next) { + t->tflags = 0; + t->used = 0; + } + + /* Suppress error messages: we may add new options if we + demand-load a protocol. */ + opterr = 0; + + opts = xt_params->orig_opts; + while ((cs.c = getopt_long(argc, argv, + "-:A:C:D:R:I:L::S::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvnt:m:xc:g:46", + opts, NULL)) != -1) { + switch (cs.c) { + /* + * Command selection + */ + case 'A': + add_command(&command, CMD_APPEND, CMD_NONE, + cs.invert); + chain = optarg; + break; + + case 'C': + add_command(&command, CMD_CHECK, CMD_NONE, + cs.invert); + chain = optarg; + break; + + case 'D': + add_command(&command, CMD_DELETE, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') { + rulenum = parse_rulenumber(argv[optind++]); + command = CMD_DELETE_NUM; + } + break; + + case 'R': + add_command(&command, CMD_REPLACE, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires a rule number", + cmd2char(CMD_REPLACE)); + break; + + case 'I': + add_command(&command, CMD_INSERT, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else rulenum = 1; + break; + + case 'L': + add_command(&command, CMD_LIST, + CMD_ZERO | CMD_ZERO_NUM, cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + break; + + case 'S': + add_command(&command, CMD_LIST_RULES, + CMD_ZERO|CMD_ZERO_NUM, cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + break; + + case 'F': + add_command(&command, CMD_FLUSH, CMD_NONE, + cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'Z': + add_command(&command, CMD_ZERO, CMD_LIST|CMD_LIST_RULES, + cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') { + rulenum = parse_rulenumber(argv[optind++]); + command = CMD_ZERO_NUM; + } + break; + + case 'N': + if (optarg && (*optarg == '-' || *optarg == '!')) + xtables_error(PARAMETER_PROBLEM, + "chain name not allowed to start " + "with `%c'\n", *optarg); + if (xtables_find_target(optarg, XTF_TRY_LOAD)) + xtables_error(PARAMETER_PROBLEM, + "chain name may not clash " + "with target name\n"); + add_command(&command, CMD_NEW_CHAIN, CMD_NONE, + cs.invert); + chain = optarg; + break; + + case 'X': + add_command(&command, CMD_DELETE_CHAIN, CMD_NONE, + cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'E': + add_command(&command, CMD_RENAME_CHAIN, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + newname = argv[optind++]; + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires old-chain-name and " + "new-chain-name", + cmd2char(CMD_RENAME_CHAIN)); + break; + + case 'P': + add_command(&command, CMD_SET_POLICY, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + policy = argv[optind++]; + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires a chain and a policy", + cmd2char(CMD_SET_POLICY)); + break; + + case 'h': + if (!optarg) + optarg = argv[optind]; + + /* iptables -p icmp -h */ + if (!cs.matches && cs.protocol) + xtables_find_match(cs.protocol, + XTF_TRY_LOAD, &cs.matches); + + exit_printhelp(cs.matches); + + /* + * Option selection + */ + case 'p': + xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv); + set_option(&cs.options, OPT_PROTOCOL, &cs.fw.ip.invflags, + cs.invert); + + /* Canonicalize into lower case */ + for (cs.protocol = optarg; *cs.protocol; cs.protocol++) + *cs.protocol = tolower(*cs.protocol); + + cs.protocol = optarg; + cs.fw.ip.proto = xtables_parse_protocol(cs.protocol); + + if (cs.fw.ip.proto == 0 + && (cs.fw.ip.invflags & IPT_INV_PROTO)) + xtables_error(PARAMETER_PROBLEM, + "rule would never match protocol"); + break; + + case 's': + xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv); + set_option(&cs.options, OPT_SOURCE, &cs.fw.ip.invflags, + cs.invert); + shostnetworkmask = optarg; + break; + + case 'd': + xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv); + set_option(&cs.options, OPT_DESTINATION, &cs.fw.ip.invflags, + cs.invert); + dhostnetworkmask = optarg; + break; + +#ifdef IPT_F_GOTO + case 'g': + set_option(&cs.options, OPT_JUMP, &cs.fw.ip.invflags, + cs.invert); + cs.fw.ip.flags |= IPT_F_GOTO; + cs.jumpto = parse_target(optarg); + break; +#endif + + case 'j': + command_jump(&cs); + break; + + + case 'i': + if (*optarg == '\0') + xtables_error(PARAMETER_PROBLEM, + "Empty interface is likely to be " + "undesired"); + xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv); + set_option(&cs.options, OPT_VIANAMEIN, &cs.fw.ip.invflags, + cs.invert); + xtables_parse_interface(optarg, + cs.fw.ip.iniface, + cs.fw.ip.iniface_mask); + break; + + case 'o': + if (*optarg == '\0') + xtables_error(PARAMETER_PROBLEM, + "Empty interface is likely to be " + "undesired"); + xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv); + set_option(&cs.options, OPT_VIANAMEOUT, &cs.fw.ip.invflags, + cs.invert); + xtables_parse_interface(optarg, + cs.fw.ip.outiface, + cs.fw.ip.outiface_mask); + break; + + case 'f': + set_option(&cs.options, OPT_FRAGMENT, &cs.fw.ip.invflags, + cs.invert); + cs.fw.ip.flags |= IPT_F_FRAG; + break; + + case 'v': + if (!verbose) + set_option(&cs.options, OPT_VERBOSE, + &cs.fw.ip.invflags, cs.invert); + verbose++; + break; + + case 'm': + command_match(&cs); + break; + + case 'n': + set_option(&cs.options, OPT_NUMERIC, &cs.fw.ip.invflags, + cs.invert); + break; + + case 't': + if (cs.invert) + xtables_error(PARAMETER_PROBLEM, + "unexpected ! flag before --table"); + *table = optarg; + break; + + case 'x': + set_option(&cs.options, OPT_EXPANDED, &cs.fw.ip.invflags, + cs.invert); + break; + + case 'V': + if (cs.invert) + printf("Not %s ;-)\n", prog_vers); + else + printf("%s v%s\n", + prog_name, prog_vers); + exit(0); + + case '0': + set_option(&cs.options, OPT_LINENUMBERS, &cs.fw.ip.invflags, + cs.invert); + break; + + case 'M': + xtables_modprobe_program = optarg; + break; + + case 'c': + + set_option(&cs.options, OPT_COUNTERS, &cs.fw.ip.invflags, + cs.invert); + pcnt = optarg; + bcnt = strchr(pcnt + 1, ','); + if (bcnt) + bcnt++; + if (!bcnt && optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + bcnt = argv[optind++]; + if (!bcnt) + xtables_error(PARAMETER_PROBLEM, + "-%c requires packet and byte counter", + opt2char(OPT_COUNTERS)); + + if (sscanf(pcnt, "%llu", &cnt) != 1) + xtables_error(PARAMETER_PROBLEM, + "-%c packet counter not numeric", + opt2char(OPT_COUNTERS)); + cs.fw.counters.pcnt = cnt; + + if (sscanf(bcnt, "%llu", &cnt) != 1) + xtables_error(PARAMETER_PROBLEM, + "-%c byte counter not numeric", + opt2char(OPT_COUNTERS)); + cs.fw.counters.bcnt = cnt; + break; + + case '4': + /* This is indeed the IPv4 iptables */ + break; + + case '6': + /* This is not the IPv6 ip6tables */ + if (line != -1) + return 1; /* success: line ignored */ + fprintf(stderr, "This is the IPv4 version of iptables.\n"); + exit_tryhelp(2); + + case 1: /* non option */ + if (optarg[0] == '!' && optarg[1] == '\0') { + if (cs.invert) + xtables_error(PARAMETER_PROBLEM, + "multiple consecutive ! not" + " allowed"); + cs.invert = TRUE; + optarg[0] = '\0'; + continue; + } + fprintf(stderr, "Bad argument `%s'\n", optarg); + exit_tryhelp(2); + + default: + command_default(&cs, &iptables_globals); + break; + } + cs.invert = FALSE; + } + + if (strcmp(*table, "nat") == 0 && + ((policy != NULL && strcmp(policy, "DROP") == 0) || + (cs.jumpto != NULL && strcmp(cs.jumpto, "DROP") == 0))) + xtables_error(PARAMETER_PROBLEM, + "\nThe \"nat\" table is not intended for filtering, " + "the use of DROP is therefore inhibited.\n\n"); + + for (matchp = cs.matches; matchp; matchp = matchp->next) + xtables_option_mfcall(matchp->match); + if (cs.target != NULL) + xtables_option_tfcall(cs.target); + + /* Fix me: must put inverse options checking here --MN */ + + if (optind < argc) + xtables_error(PARAMETER_PROBLEM, + "unknown arguments found on commandline"); + if (!command) + xtables_error(PARAMETER_PROBLEM, "no command specified"); + if (cs.invert) + xtables_error(PARAMETER_PROBLEM, + "nothing appropriate following !"); + + if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND | CMD_CHECK)) { + if (!(cs.options & OPT_DESTINATION)) + dhostnetworkmask = "0.0.0.0/0"; + if (!(cs.options & OPT_SOURCE)) + shostnetworkmask = "0.0.0.0/0"; + } + + if (shostnetworkmask) + xtables_ipparse_multiple(shostnetworkmask, &saddrs, + &smasks, &nsaddrs); + + if (dhostnetworkmask) + xtables_ipparse_multiple(dhostnetworkmask, &daddrs, + &dmasks, &ndaddrs); + + if ((nsaddrs > 1 || ndaddrs > 1) && + (cs.fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP))) + xtables_error(PARAMETER_PROBLEM, "! not allowed with multiple" + " source or destination IP addresses"); + + if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1)) + xtables_error(PARAMETER_PROBLEM, "Replacement rule does not " + "specify a unique address"); + + generic_opt_check(command, cs.options); + + if (chain != NULL && strlen(chain) >= XT_EXTENSION_MAXNAMELEN) + xtables_error(PARAMETER_PROBLEM, + "chain name `%s' too long (must be under %u chars)", + chain, XT_EXTENSION_MAXNAMELEN); + + /* only allocate handle if we weren't called with a handle */ + if (!*handle) + *handle = iptc_init(*table); + + /* try to insmod the module if iptc_init failed */ + if (!*handle && xtables_load_ko(xtables_modprobe_program, false) != -1) + *handle = iptc_init(*table); + + if (!*handle) + xtables_error(VERSION_PROBLEM, + "can't initialize iptables table `%s': %s", + *table, iptc_strerror(errno)); + + if (command == CMD_APPEND + || command == CMD_DELETE + || command == CMD_CHECK + || command == CMD_INSERT + || command == CMD_REPLACE) { + if (strcmp(chain, "PREROUTING") == 0 + || strcmp(chain, "INPUT") == 0) { + /* -o not valid with incoming packets. */ + if (cs.options & OPT_VIANAMEOUT) + xtables_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEOUT), + chain); + } + + if (strcmp(chain, "POSTROUTING") == 0 + || strcmp(chain, "OUTPUT") == 0) { + /* -i not valid with outgoing packets */ + if (cs.options & OPT_VIANAMEIN) + xtables_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEIN), + chain); + } + + if (cs.target && iptc_is_chain(cs.jumpto, *handle)) { + fprintf(stderr, + "Warning: using chain %s, not extension\n", + cs.jumpto); + + if (cs.target->t) + free(cs.target->t); + + cs.target = NULL; + } + + /* If they didn't specify a target, or it's a chain + name, use standard. */ + if (!cs.target + && (strlen(cs.jumpto) == 0 + || iptc_is_chain(cs.jumpto, *handle))) { + size_t size; + + cs.target = xtables_find_target(IPT_STANDARD_TARGET, + XTF_LOAD_MUST_SUCCEED); + + size = sizeof(struct ipt_entry_target) + + cs.target->size; + cs.target->t = xtables_calloc(1, size); + cs.target->t->u.target_size = size; + strcpy(cs.target->t->u.user.name, cs.jumpto); + if (!iptc_is_chain(cs.jumpto, *handle)) + cs.target->t->u.user.revision = cs.target->revision; + if (cs.target->init != NULL) + cs.target->init(cs.target->t); + } + + if (!cs.target) { + /* it is no chain, and we can't load a plugin. + * We cannot know if the plugin is corrupt, non + * existant OR if the user just misspelled a + * chain. */ +#ifdef IPT_F_GOTO + if (cs.fw.ip.flags & IPT_F_GOTO) + xtables_error(PARAMETER_PROBLEM, + "goto '%s' is not a chain\n", + cs.jumpto); +#endif + xtables_find_target(cs.jumpto, XTF_LOAD_MUST_SUCCEED); + } else { + e = generate_entry(&cs.fw, cs.matches, cs.target->t); + free(cs.target->t); + } + } + + switch (command) { + case CMD_APPEND: + ret = append_entry(chain, e, + nsaddrs, saddrs, smasks, + ndaddrs, daddrs, dmasks, + cs.options&OPT_VERBOSE, + *handle); + break; + case CMD_DELETE: + ret = delete_entry(chain, e, + nsaddrs, saddrs, smasks, + ndaddrs, daddrs, dmasks, + cs.options&OPT_VERBOSE, + *handle, cs.matches, cs.target); + break; + case CMD_DELETE_NUM: + ret = iptc_delete_num_entry(chain, rulenum - 1, *handle); + break; + case CMD_CHECK: + ret = check_entry(chain, e, + nsaddrs, saddrs, smasks, + ndaddrs, daddrs, dmasks, + cs.options&OPT_VERBOSE, + *handle, cs.matches, cs.target); + break; + case CMD_REPLACE: + ret = replace_entry(chain, e, rulenum - 1, + saddrs, smasks, daddrs, dmasks, + cs.options&OPT_VERBOSE, *handle); + break; + case CMD_INSERT: + ret = insert_entry(chain, e, rulenum - 1, + nsaddrs, saddrs, smasks, + ndaddrs, daddrs, dmasks, + cs.options&OPT_VERBOSE, + *handle); + break; + case CMD_FLUSH: + ret = flush_entries4(chain, cs.options&OPT_VERBOSE, *handle); + break; + case CMD_ZERO: + ret = zero_entries(chain, cs.options&OPT_VERBOSE, *handle); + break; + case CMD_ZERO_NUM: + ret = iptc_zero_counter(chain, rulenum, *handle); + break; + case CMD_LIST: + case CMD_LIST|CMD_ZERO: + case CMD_LIST|CMD_ZERO_NUM: + ret = list_entries(chain, + rulenum, + cs.options&OPT_VERBOSE, + cs.options&OPT_NUMERIC, + cs.options&OPT_EXPANDED, + cs.options&OPT_LINENUMBERS, + *handle); + if (ret && (command & CMD_ZERO)) + ret = zero_entries(chain, + cs.options&OPT_VERBOSE, *handle); + if (ret && (command & CMD_ZERO_NUM)) + ret = iptc_zero_counter(chain, rulenum, *handle); + break; + case CMD_LIST_RULES: + case CMD_LIST_RULES|CMD_ZERO: + case CMD_LIST_RULES|CMD_ZERO_NUM: + ret = list_rules(chain, + rulenum, + cs.options&OPT_VERBOSE, + *handle); + if (ret && (command & CMD_ZERO)) + ret = zero_entries(chain, + cs.options&OPT_VERBOSE, *handle); + if (ret && (command & CMD_ZERO_NUM)) + ret = iptc_zero_counter(chain, rulenum, *handle); + break; + case CMD_NEW_CHAIN: + ret = iptc_create_chain(chain, *handle); + break; + case CMD_DELETE_CHAIN: + ret = delete_chain4(chain, cs.options&OPT_VERBOSE, *handle); + break; + case CMD_RENAME_CHAIN: + ret = iptc_rename_chain(chain, newname, *handle); + break; + case CMD_SET_POLICY: + ret = iptc_set_policy(chain, policy, cs.options&OPT_COUNTERS ? &cs.fw.counters : NULL, *handle); + break; + default: + /* We should never reach this... */ + exit_tryhelp(2); + } + + if (verbose > 1) + dump_entries(*handle); + + clear_rule_matches(&cs.matches); + + if (e != NULL) { + free(e); + e = NULL; + } + + free(saddrs); + free(smasks); + free(daddrs); + free(dmasks); + xtables_free_opts(1); + + return ret; +} diff --git a/iptables/iptables.xslt b/iptables/iptables.xslt new file mode 100644 index 00000000..d6a432cf --- /dev/null +++ b/iptables/iptables.xslt @@ -0,0 +1,138 @@ + + + + + + + + + + + -m + + + + + + + + + + + -g + + + + + -j + + + + + + -j + + + + + + + ! + - + + + - + + + + + + + + + + + + + + + + + + + -A + + + + + + + + + + + + + + + + + + + + + + + + + + + # Generated by iptables.xslt + * + + + : + + + + - + + + + + + + + + COMMIT # Completed + + + + + [ + + 0 + : + + 0 + ] + + + + + + + + + + + + diff --git a/iptables/xshared.c b/iptables/xshared.c new file mode 100644 index 00000000..4651888a --- /dev/null +++ b/iptables/xshared.c @@ -0,0 +1,207 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xshared.h" + +/* + * Print out any special helps. A user might like to be able to add a --help + * to the commandline, and see expected results. So we call help for all + * specified matches and targets. + */ +void print_extension_helps(const struct xtables_target *t, + const struct xtables_rule_match *m) +{ + for (; t != NULL; t = t->next) { + if (t->used) { + printf("\n"); + if (t->help == NULL) + printf("%s does not take any options\n", + t->name); + else + t->help(); + } + } + for (; m != NULL; m = m->next) { + printf("\n"); + if (m->match->help == NULL) + printf("%s does not take any options\n", + m->match->name); + else + m->match->help(); + } +} + +const char * +proto_to_name(uint8_t proto, int nolookup) +{ + unsigned int i; + + if (proto && !nolookup) { + struct protoent *pent = getprotobynumber(proto); + if (pent) + return pent->p_name; + } + + for (i = 0; xtables_chain_protos[i].name != NULL; ++i) + if (xtables_chain_protos[i].num == proto) + return xtables_chain_protos[i].name; + + return NULL; +} + +static struct xtables_match * +find_proto(const char *pname, enum xtables_tryload tryload, + int nolookup, struct xtables_rule_match **matches) +{ + unsigned int proto; + + if (xtables_strtoui(pname, NULL, &proto, 0, UINT8_MAX)) { + const char *protoname = proto_to_name(proto, nolookup); + + if (protoname) + return xtables_find_match(protoname, tryload, matches); + } else + return xtables_find_match(pname, tryload, matches); + + return NULL; +} + +/* + * Some explanations (after four different bugs in 3 different releases): If + * we encounter a parameter, that has not been parsed yet, it's not an option + * of an explicitly loaded match or a target. However, we support implicit + * loading of the protocol match extension. '-p tcp' means 'l4 proto 6' and at + * the same time 'load tcp protocol match on demand if we specify --dport'. + * + * To make this work, we need to make sure: + * - the parameter has not been parsed by a match (m above) + * - a protocol has been specified + * - the protocol extension has not been loaded yet, or is loaded and unused + * [think of ip6tables-restore!] + * - the protocol extension can be successively loaded + */ +static bool should_load_proto(struct iptables_command_state *cs) +{ + if (cs->protocol == NULL) + return false; + if (find_proto(cs->protocol, XTF_DONT_LOAD, + cs->options & OPT_NUMERIC, NULL) == NULL) + return true; + return !cs->proto_used; +} + +struct xtables_match *load_proto(struct iptables_command_state *cs) +{ + if (!should_load_proto(cs)) + return NULL; + return find_proto(cs->protocol, XTF_TRY_LOAD, + cs->options & OPT_NUMERIC, &cs->matches); +} + +void command_default(struct iptables_command_state *cs, + struct xtables_globals *gl) +{ + struct xtables_rule_match *matchp; + struct xtables_match *m; + + if (cs->target != NULL && + (cs->target->parse != NULL || cs->target->x6_parse != NULL) && + cs->c >= cs->target->option_offset && + cs->c < cs->target->option_offset + XT_OPTION_OFFSET_SCALE) { + xtables_option_tpcall(cs->c, cs->argv, cs->invert, + cs->target, &cs->fw); + return; + } + + for (matchp = cs->matches; matchp; matchp = matchp->next) { + m = matchp->match; + + if (matchp->completed || + (m->x6_parse == NULL && m->parse == NULL)) + continue; + if (cs->c < matchp->match->option_offset || + cs->c >= matchp->match->option_offset + XT_OPTION_OFFSET_SCALE) + continue; + xtables_option_mpcall(cs->c, cs->argv, cs->invert, m, &cs->fw); + return; + } + + /* Try loading protocol */ + m = load_proto(cs); + if (m != NULL) { + size_t size; + + cs->proto_used = 1; + + size = XT_ALIGN(sizeof(struct ip6t_entry_match)) + m->size; + + m->m = xtables_calloc(1, size); + m->m->u.match_size = size; + strcpy(m->m->u.user.name, m->name); + m->m->u.user.revision = m->revision; + if (m->init != NULL) + m->init(m->m); + + if (m->x6_options != NULL) + gl->opts = xtables_options_xfrm(gl->orig_opts, + gl->opts, + m->x6_options, + &m->option_offset); + else + gl->opts = xtables_merge_options(gl->orig_opts, + gl->opts, + m->extra_opts, + &m->option_offset); + if (gl->opts == NULL) + xtables_error(OTHER_PROBLEM, "can't alloc memory!"); + optind--; + return; + } + + if (cs->c == ':') + xtables_error(PARAMETER_PROBLEM, "option \"%s\" " + "requires an argument", cs->argv[optind-1]); + if (cs->c == '?') + xtables_error(PARAMETER_PROBLEM, "unknown option " + "\"%s\"", cs->argv[optind-1]); + xtables_error(PARAMETER_PROBLEM, "Unknown arg \"%s\"", optarg); +} + +static mainfunc_t subcmd_get(const char *cmd, const struct subcommand *cb) +{ + for (; cb->name != NULL; ++cb) + if (strcmp(cb->name, cmd) == 0) + return cb->main; + return NULL; +} + +int subcmd_main(int argc, char **argv, const struct subcommand *cb) +{ + const char *cmd = basename(*argv); + mainfunc_t f = subcmd_get(cmd, cb); + + if (f == NULL && argc > 1) { + /* + * Unable to find a main method for our command name? + * Let's try again with the first argument! + */ + ++argv; + --argc; + f = subcmd_get(*argv, cb); + } + + /* now we should have a valid function pointer */ + if (f != NULL) + return f(argc, argv); + + fprintf(stderr, "ERROR: No valid subcommand given.\nValid subcommands:\n"); + for (; cb->name != NULL; ++cb) + fprintf(stderr, " * %s\n", cb->name); + exit(EXIT_FAILURE); +} diff --git a/iptables/xshared.h b/iptables/xshared.h new file mode 100644 index 00000000..d868c06f --- /dev/null +++ b/iptables/xshared.h @@ -0,0 +1,87 @@ +#ifndef IPTABLES_XSHARED_H +#define IPTABLES_XSHARED_H 1 + +#include +#include +#include +#include +#include +#include + +enum { + OPT_NONE = 0, + OPT_NUMERIC = 1 << 0, + OPT_SOURCE = 1 << 1, + OPT_DESTINATION = 1 << 2, + OPT_PROTOCOL = 1 << 3, + OPT_JUMP = 1 << 4, + OPT_VERBOSE = 1 << 5, + OPT_EXPANDED = 1 << 6, + OPT_VIANAMEIN = 1 << 7, + OPT_VIANAMEOUT = 1 << 8, + OPT_LINENUMBERS = 1 << 9, + OPT_COUNTERS = 1 << 10, +}; + +struct xtables_globals; +struct xtables_rule_match; +struct xtables_target; + +/** + * xtables_afinfo - protocol family dependent information + * @kmod: kernel module basename (e.g. "ip_tables") + * @proc_exists: file which exists in procfs when module already loaded + * @libprefix: prefix of .so library name (e.g. "libipt_") + * @family: nfproto family + * @ipproto: used by setsockopt (e.g. IPPROTO_IP) + * @so_rev_match: optname to check revision support of match + * @so_rev_target: optname to check revision support of target + */ +struct xtables_afinfo { + const char *kmod; + const char *proc_exists; + const char *libprefix; + uint8_t family; + uint8_t ipproto; + int so_rev_match; + int so_rev_target; +}; + +struct iptables_command_state { + union { + struct ipt_entry fw; + struct ip6t_entry fw6; + }; + int invert; + int c; + unsigned int options; + struct xtables_rule_match *matches; + struct xtables_target *target; + char *protocol; + int proto_used; + const char *jumpto; + char **argv; +}; + +typedef int (*mainfunc_t)(int, char **); + +struct subcommand { + const char *name; + mainfunc_t main; +}; + +enum { + XT_OPTION_OFFSET_SCALE = 256, +}; + +extern void print_extension_helps(const struct xtables_target *, + const struct xtables_rule_match *); +extern const char *proto_to_name(uint8_t, int); +extern void command_default(struct iptables_command_state *, + struct xtables_globals *); +extern struct xtables_match *load_proto(struct iptables_command_state *); +extern int subcmd_main(int, char **, const struct subcommand *); + +extern const struct xtables_afinfo *afinfo; + +#endif /* IPTABLES_XSHARED_H */ diff --git a/iptables/xtables-multi.c b/iptables/xtables-multi.c new file mode 100644 index 00000000..8014d5fb --- /dev/null +++ b/iptables/xtables-multi.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include "xshared.h" + +#include "xtables-multi.h" + +#ifdef ENABLE_IPV4 +#include "iptables-multi.h" +#endif + +#ifdef ENABLE_IPV6 +#include "ip6tables-multi.h" +#endif + +static const struct subcommand multi_subcommands[] = { +#ifdef ENABLE_IPV4 + {"iptables", iptables_main}, + {"main4", iptables_main}, + {"iptables-save", iptables_save_main}, + {"save4", iptables_save_main}, + {"iptables-restore", iptables_restore_main}, + {"restore4", iptables_restore_main}, +#endif + {"iptables-xml", iptables_xml_main}, + {"xml", iptables_xml_main}, +#ifdef ENABLE_IPV6 + {"ip6tables", ip6tables_main}, + {"main6", ip6tables_main}, + {"ip6tables-save", ip6tables_save_main}, + {"save6", ip6tables_save_main}, + {"ip6tables-restore", ip6tables_restore_main}, + {"restore6", ip6tables_restore_main}, +#endif + {NULL}, +}; + +int main(int argc, char **argv) +{ + return subcmd_main(argc, argv, multi_subcommands); +} diff --git a/iptables/xtables-multi.h b/iptables/xtables-multi.h new file mode 100644 index 00000000..615724b1 --- /dev/null +++ b/iptables/xtables-multi.h @@ -0,0 +1,6 @@ +#ifndef _XTABLES_MULTI_H +#define _XTABLES_MULTI_H 1 + +extern int iptables_xml_main(int, char **); + +#endif /* _XTABLES_MULTI_H */ diff --git a/iptables/xtables.c b/iptables/xtables.c new file mode 100644 index 00000000..acfcf8bd --- /dev/null +++ b/iptables/xtables.c @@ -0,0 +1,1832 @@ +/* + * (C) 2000-2006 by the netfilter coreteam : + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for PROC_SUPER_MAGIC */ + +#include +#include /* INT_MAX in ip_tables.h/ip6_tables.h */ +#include +#include +#include + +#ifndef NO_SHARED_LIBS +#include +#endif +#ifndef IPT_SO_GET_REVISION_MATCH /* Old kernel source. */ +# define IPT_SO_GET_REVISION_MATCH (IPT_BASE_CTL + 2) +# define IPT_SO_GET_REVISION_TARGET (IPT_BASE_CTL + 3) +#endif +#ifndef IP6T_SO_GET_REVISION_MATCH /* Old kernel source. */ +# define IP6T_SO_GET_REVISION_MATCH 68 +# define IP6T_SO_GET_REVISION_TARGET 69 +#endif +#include +#include "iptables/internal.h" +#include "xshared.h" + +#define NPROTO 255 + +#ifndef PROC_SYS_MODPROBE +#define PROC_SYS_MODPROBE "/proc/sys/kernel/modprobe" +#endif + +/* we need this for ip6?tables-restore. ip6?tables-restore.c sets line to the + * current line of the input file, in order to give a more precise error + * message. ip6?tables itself doesn't need this, so it is initialized to the + * magic number of -1 */ +int line = -1; + +void basic_exit_err(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3))); + +struct xtables_globals *xt_params = NULL; + +void basic_exit_err(enum xtables_exittype status, const char *msg, ...) +{ + va_list args; + + va_start(args, msg); + fprintf(stderr, "%s v%s: ", xt_params->program_name, xt_params->program_version); + vfprintf(stderr, msg, args); + va_end(args); + fprintf(stderr, "\n"); + exit(status); +} + +void xtables_free_opts(int unused) +{ + if (xt_params->opts != xt_params->orig_opts) { + free(xt_params->opts); + xt_params->opts = NULL; + } +} + +struct option *xtables_merge_options(struct option *orig_opts, + struct option *oldopts, + const struct option *newopts, + unsigned int *option_offset) +{ + unsigned int num_oold = 0, num_old = 0, num_new = 0, i; + struct option *merge, *mp; + + if (newopts == NULL) + return oldopts; + + for (num_oold = 0; orig_opts[num_oold].name; num_oold++) ; + if (oldopts != NULL) + for (num_old = 0; oldopts[num_old].name; num_old++) ; + for (num_new = 0; newopts[num_new].name; num_new++) ; + + /* + * Since @oldopts also has @orig_opts already (and does so at the + * start), skip these entries. + */ + oldopts += num_oold; + num_old -= num_oold; + + merge = malloc(sizeof(*mp) * (num_oold + num_old + num_new + 1)); + if (merge == NULL) + return NULL; + + /* Let the base options -[ADI...] have precedence over everything */ + memcpy(merge, orig_opts, sizeof(*mp) * num_oold); + mp = merge + num_oold; + + /* Second, the new options */ + xt_params->option_offset += XT_OPTION_OFFSET_SCALE; + *option_offset = xt_params->option_offset; + memcpy(mp, newopts, sizeof(*mp) * num_new); + + for (i = 0; i < num_new; ++i, ++mp) + mp->val += *option_offset; + + /* Third, the old options */ + memcpy(mp, oldopts, sizeof(*mp) * num_old); + mp += num_old; + xtables_free_opts(0); + + /* Clear trailing entry */ + memset(mp, 0, sizeof(*mp)); + return merge; +} + +static const struct xtables_afinfo afinfo_ipv4 = { + .kmod = "ip_tables", + .proc_exists = "/proc/net/ip_tables_names", + .libprefix = "libipt_", + .family = NFPROTO_IPV4, + .ipproto = IPPROTO_IP, + .so_rev_match = IPT_SO_GET_REVISION_MATCH, + .so_rev_target = IPT_SO_GET_REVISION_TARGET, +}; + +static const struct xtables_afinfo afinfo_ipv6 = { + .kmod = "ip6_tables", + .proc_exists = "/proc/net/ip6_tables_names", + .libprefix = "libip6t_", + .family = NFPROTO_IPV6, + .ipproto = IPPROTO_IPV6, + .so_rev_match = IP6T_SO_GET_REVISION_MATCH, + .so_rev_target = IP6T_SO_GET_REVISION_TARGET, +}; + +const struct xtables_afinfo *afinfo; + +/* Search path for Xtables .so files */ +static const char *xtables_libdir; + +/* the path to command to load kernel module */ +const char *xtables_modprobe_program; + +/* Keep track of matches/targets pending full registration: linked lists. */ +struct xtables_match *xtables_pending_matches; +struct xtables_target *xtables_pending_targets; + +/* Keep track of fully registered external matches/targets: linked lists. */ +struct xtables_match *xtables_matches; +struct xtables_target *xtables_targets; + +/* Fully register a match/target which was previously partially registered. */ +static void xtables_fully_register_pending_match(struct xtables_match *me); +static void xtables_fully_register_pending_target(struct xtables_target *me); + +void xtables_init(void) +{ + xtables_libdir = getenv("XTABLES_LIBDIR"); + if (xtables_libdir != NULL) + return; + xtables_libdir = getenv("IPTABLES_LIB_DIR"); + if (xtables_libdir != NULL) { + fprintf(stderr, "IPTABLES_LIB_DIR is deprecated, " + "use XTABLES_LIBDIR.\n"); + return; + } + /* + * Well yes, IP6TABLES_LIB_DIR is of lower priority over + * IPTABLES_LIB_DIR since this moved to libxtables; I think that is ok + * for these env vars are deprecated anyhow, and in light of the + * (shared) libxt_*.so files, makes less sense to have + * IPTABLES_LIB_DIR != IP6TABLES_LIB_DIR. + */ + xtables_libdir = getenv("IP6TABLES_LIB_DIR"); + if (xtables_libdir != NULL) { + fprintf(stderr, "IP6TABLES_LIB_DIR is deprecated, " + "use XTABLES_LIBDIR.\n"); + return; + } + xtables_libdir = XTABLES_LIBDIR; +} + +void xtables_set_nfproto(uint8_t nfproto) +{ + switch (nfproto) { + case NFPROTO_IPV4: + afinfo = &afinfo_ipv4; + break; + case NFPROTO_IPV6: + afinfo = &afinfo_ipv6; + break; + default: + fprintf(stderr, "libxtables: unhandled NFPROTO in %s\n", + __func__); + } +} + +/** + * xtables_set_params - set the global parameters used by xtables + * @xtp: input xtables_globals structure + * + * The app is expected to pass a valid xtables_globals data-filled + * with proper values + * @xtp cannot be NULL + * + * Returns -1 on failure to set and 0 on success + */ +int xtables_set_params(struct xtables_globals *xtp) +{ + if (!xtp) { + fprintf(stderr, "%s: Illegal global params\n",__func__); + return -1; + } + + xt_params = xtp; + + if (!xt_params->exit_err) + xt_params->exit_err = basic_exit_err; + + return 0; +} + +int xtables_init_all(struct xtables_globals *xtp, uint8_t nfproto) +{ + xtables_init(); + xtables_set_nfproto(nfproto); + return xtables_set_params(xtp); +} + +/** + * xtables_*alloc - wrappers that exit on failure + */ +void *xtables_calloc(size_t count, size_t size) +{ + void *p; + + if ((p = calloc(count, size)) == NULL) { + perror("ip[6]tables: calloc failed"); + exit(1); + } + + return p; +} + +void *xtables_malloc(size_t size) +{ + void *p; + + if ((p = malloc(size)) == NULL) { + perror("ip[6]tables: malloc failed"); + exit(1); + } + + return p; +} + +void *xtables_realloc(void *ptr, size_t size) +{ + void *p; + + if ((p = realloc(ptr, size)) == NULL) { + perror("ip[6]tables: realloc failed"); + exit(1); + } + + return p; +} + +static char *get_modprobe(void) +{ + int procfile; + char *ret; + +#define PROCFILE_BUFSIZ 1024 + procfile = open(PROC_SYS_MODPROBE, O_RDONLY); + if (procfile < 0) + return NULL; + if (fcntl(procfile, F_SETFD, FD_CLOEXEC) == -1) { + fprintf(stderr, "Could not set close on exec: %s\n", + strerror(errno)); + exit(1); + } + + ret = malloc(PROCFILE_BUFSIZ); + if (ret) { + memset(ret, 0, PROCFILE_BUFSIZ); + switch (read(procfile, ret, PROCFILE_BUFSIZ)) { + case -1: goto fail; + case PROCFILE_BUFSIZ: goto fail; /* Partial read. Wierd */ + } + if (ret[strlen(ret)-1]=='\n') + ret[strlen(ret)-1]=0; + close(procfile); + return ret; + } + fail: + free(ret); + close(procfile); + return NULL; +} + +int xtables_insmod(const char *modname, const char *modprobe, bool quiet) +{ + char *buf = NULL; + char *argv[4]; + int status; + + /* If they don't explicitly set it, read out of kernel */ + if (!modprobe) { + buf = get_modprobe(); + if (!buf) + return -1; + modprobe = buf; + } + + /* + * Need to flush the buffer, or the child may output it again + * when switching the program thru execv. + */ + fflush(stdout); + + switch (vfork()) { + case 0: + argv[0] = (char *)modprobe; + argv[1] = (char *)modname; + if (quiet) { + argv[2] = "-q"; + argv[3] = NULL; + } else { + argv[2] = NULL; + argv[3] = NULL; + } + execv(argv[0], argv); + + /* not usually reached */ + exit(1); + case -1: + return -1; + + default: /* parent */ + wait(&status); + } + + free(buf); + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) + return 0; + return -1; +} + +/* return true if a given file exists within procfs */ +static bool proc_file_exists(const char *filename) +{ + struct stat s; + struct statfs f; + + if (lstat(filename, &s)) + return false; + if (!S_ISREG(s.st_mode)) + return false; + if (statfs(filename, &f)) + return false; + if (f.f_type != PROC_SUPER_MAGIC) + return false; + return true; +} + +int xtables_load_ko(const char *modprobe, bool quiet) +{ + static bool loaded = false; + int ret; + + if (loaded) + return 0; + + if (proc_file_exists(afinfo->proc_exists)) { + loaded = true; + return 0; + }; + + ret = xtables_insmod(afinfo->kmod, modprobe, quiet); + if (ret == 0) + loaded = true; + + return ret; +} + +/** + * xtables_strtou{i,l} - string to number conversion + * @s: input string + * @end: like strtoul's "end" pointer + * @value: pointer for result + * @min: minimum accepted value + * @max: maximum accepted value + * + * If @end is NULL, we assume the caller wants a "strict strtoul", and hence + * "15a" is rejected. + * In either case, the value obtained is compared for min-max compliance. + * Base is always 0, i.e. autodetect depending on @s. + * + * Returns true/false whether number was accepted. On failure, *value has + * undefined contents. + */ +bool xtables_strtoul(const char *s, char **end, uintmax_t *value, + uintmax_t min, uintmax_t max) +{ + uintmax_t v; + const char *p; + char *my_end; + + errno = 0; + /* Since strtoul allows leading minus, we have to check for ourself. */ + for (p = s; isspace(*p); ++p) + ; + if (*p == '-') + return false; + v = strtoumax(s, &my_end, 0); + if (my_end == s) + return false; + if (end != NULL) + *end = my_end; + + if (errno != ERANGE && min <= v && (max == 0 || v <= max)) { + if (value != NULL) + *value = v; + if (end == NULL) + return *my_end == '\0'; + return true; + } + + return false; +} + +bool xtables_strtoui(const char *s, char **end, unsigned int *value, + unsigned int min, unsigned int max) +{ + uintmax_t v; + bool ret; + + ret = xtables_strtoul(s, end, &v, min, max); + if (value != NULL) + *value = v; + return ret; +} + +int xtables_service_to_port(const char *name, const char *proto) +{ + struct servent *service; + + if ((service = getservbyname(name, proto)) != NULL) + return ntohs((unsigned short) service->s_port); + + return -1; +} + +uint16_t xtables_parse_port(const char *port, const char *proto) +{ + unsigned int portnum; + + if (xtables_strtoui(port, NULL, &portnum, 0, UINT16_MAX) || + (portnum = xtables_service_to_port(port, proto)) != (unsigned)-1) + return portnum; + + xt_params->exit_err(PARAMETER_PROBLEM, + "invalid port/service `%s' specified", port); +} + +void xtables_parse_interface(const char *arg, char *vianame, + unsigned char *mask) +{ + unsigned int vialen = strlen(arg); + unsigned int i; + + memset(mask, 0, IFNAMSIZ); + memset(vianame, 0, IFNAMSIZ); + + if (vialen + 1 > IFNAMSIZ) + xt_params->exit_err(PARAMETER_PROBLEM, + "interface name `%s' must be shorter than IFNAMSIZ" + " (%i)", arg, IFNAMSIZ-1); + + strcpy(vianame, arg); + if (vialen == 0) + memset(mask, 0, IFNAMSIZ); + else if (vianame[vialen - 1] == '+') { + memset(mask, 0xFF, vialen - 1); + memset(mask + vialen - 1, 0, IFNAMSIZ - vialen + 1); + /* Don't remove `+' here! -HW */ + } else { + /* Include nul-terminator in match */ + memset(mask, 0xFF, vialen + 1); + memset(mask + vialen + 1, 0, IFNAMSIZ - vialen - 1); + for (i = 0; vianame[i]; i++) { + if (vianame[i] == '/' || + vianame[i] == ' ') { + fprintf(stderr, + "Warning: weird character in interface" + " `%s' ('/' and ' ' are not allowed by the kernel).\n", + vianame); + break; + } + } + } +} + +#ifndef NO_SHARED_LIBS +static void *load_extension(const char *search_path, const char *af_prefix, + const char *name, bool is_target) +{ + const char *all_prefixes[] = {"libxt_", af_prefix, NULL}; + const char **prefix; + const char *dir = search_path, *next; + void *ptr = NULL; + struct stat sb; + char path[256]; + + do { + next = strchr(dir, ':'); + if (next == NULL) + next = dir + strlen(dir); + + for (prefix = all_prefixes; *prefix != NULL; ++prefix) { + snprintf(path, sizeof(path), "%.*s/%s%s.so", + (unsigned int)(next - dir), dir, + *prefix, name); + + if (stat(path, &sb) != 0) { + if (errno == ENOENT) + continue; + fprintf(stderr, "%s: %s\n", path, + strerror(errno)); + return NULL; + } + if (dlopen(path, RTLD_NOW) == NULL) { + fprintf(stderr, "%s: %s\n", path, dlerror()); + break; + } + + if (is_target) + ptr = xtables_find_target(name, XTF_DONT_LOAD); + else + ptr = xtables_find_match(name, + XTF_DONT_LOAD, NULL); + + if (ptr != NULL) + return ptr; + + fprintf(stderr, "%s: no \"%s\" extension found for " + "this protocol\n", path, name); + errno = ENOENT; + return NULL; + } + dir = next + 1; + } while (*next != '\0'); + + return NULL; +} +#endif + +struct xtables_match * +xtables_find_match(const char *name, enum xtables_tryload tryload, + struct xtables_rule_match **matches) +{ + struct xtables_match **dptr; + struct xtables_match *ptr; + const char *icmp6 = "icmp6"; + + if (strlen(name) >= XT_EXTENSION_MAXNAMELEN) + xtables_error(PARAMETER_PROBLEM, + "Invalid match name \"%s\" (%u chars max)", + name, XT_EXTENSION_MAXNAMELEN - 1); + + /* This is ugly as hell. Nonetheless, there is no way of changing + * this without hurting backwards compatibility */ + if ( (strcmp(name,"icmpv6") == 0) || + (strcmp(name,"ipv6-icmp") == 0) || + (strcmp(name,"icmp6") == 0) ) + name = icmp6; + + /* Trigger delayed initialization */ + for (dptr = &xtables_pending_matches; *dptr; ) { + if (strcmp(name, (*dptr)->name) == 0) { + ptr = *dptr; + *dptr = (*dptr)->next; + ptr->next = NULL; + xtables_fully_register_pending_match(ptr); + } else { + dptr = &((*dptr)->next); + } + } + + for (ptr = xtables_matches; ptr; ptr = ptr->next) { + if (strcmp(name, ptr->name) == 0) { + struct xtables_match *clone; + + /* First match of this type: */ + if (ptr->m == NULL) + break; + + /* Second and subsequent clones */ + clone = xtables_malloc(sizeof(struct xtables_match)); + memcpy(clone, ptr, sizeof(struct xtables_match)); + clone->mflags = 0; + /* This is a clone: */ + clone->next = clone; + + ptr = clone; + break; + } + } + +#ifndef NO_SHARED_LIBS + if (!ptr && tryload != XTF_DONT_LOAD && tryload != XTF_DURING_LOAD) { + ptr = load_extension(xtables_libdir, afinfo->libprefix, + name, false); + + if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED) + xt_params->exit_err(PARAMETER_PROBLEM, + "Couldn't load match `%s':%s\n", + name, strerror(errno)); + } +#else + if (ptr && !ptr->loaded) { + if (tryload != XTF_DONT_LOAD) + ptr->loaded = 1; + else + ptr = NULL; + } + if(!ptr && (tryload == XTF_LOAD_MUST_SUCCEED)) { + xt_params->exit_err(PARAMETER_PROBLEM, + "Couldn't find match `%s'\n", name); + } +#endif + + if (ptr && matches) { + struct xtables_rule_match **i; + struct xtables_rule_match *newentry; + + newentry = xtables_malloc(sizeof(struct xtables_rule_match)); + + for (i = matches; *i; i = &(*i)->next) { + if (strcmp(name, (*i)->match->name) == 0) + (*i)->completed = true; + } + newentry->match = ptr; + newentry->completed = false; + newentry->next = NULL; + *i = newentry; + } + + return ptr; +} + +struct xtables_target * +xtables_find_target(const char *name, enum xtables_tryload tryload) +{ + struct xtables_target **dptr; + struct xtables_target *ptr; + + /* Standard target? */ + if (strcmp(name, "") == 0 + || strcmp(name, XTC_LABEL_ACCEPT) == 0 + || strcmp(name, XTC_LABEL_DROP) == 0 + || strcmp(name, XTC_LABEL_QUEUE) == 0 + || strcmp(name, XTC_LABEL_RETURN) == 0) + name = "standard"; + + /* Trigger delayed initialization */ + for (dptr = &xtables_pending_targets; *dptr; ) { + if (strcmp(name, (*dptr)->name) == 0) { + ptr = *dptr; + *dptr = (*dptr)->next; + ptr->next = NULL; + xtables_fully_register_pending_target(ptr); + } else { + dptr = &((*dptr)->next); + } + } + + for (ptr = xtables_targets; ptr; ptr = ptr->next) { + if (strcmp(name, ptr->name) == 0) + break; + } + +#ifndef NO_SHARED_LIBS + if (!ptr && tryload != XTF_DONT_LOAD && tryload != XTF_DURING_LOAD) { + ptr = load_extension(xtables_libdir, afinfo->libprefix, + name, true); + + if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED) + xt_params->exit_err(PARAMETER_PROBLEM, + "Couldn't load target `%s':%s\n", + name, strerror(errno)); + } +#else + if (ptr && !ptr->loaded) { + if (tryload != XTF_DONT_LOAD) + ptr->loaded = 1; + else + ptr = NULL; + } + if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED) { + xt_params->exit_err(PARAMETER_PROBLEM, + "Couldn't find target `%s'\n", name); + } +#endif + + if (ptr) + ptr->used = 1; + + return ptr; +} + +static int compatible_revision(const char *name, uint8_t revision, int opt) +{ + struct xt_get_revision rev; + socklen_t s = sizeof(rev); + int max_rev, sockfd; + + sockfd = socket(afinfo->family, SOCK_RAW, IPPROTO_RAW); + if (sockfd < 0) { + if (errno == EPERM) { + /* revision 0 is always supported. */ + if (revision != 0) + fprintf(stderr, "%s: Could not determine whether " + "revision %u is supported, " + "assuming it is.\n", + name, revision); + return 1; + } + fprintf(stderr, "Could not open socket to kernel: %s\n", + strerror(errno)); + exit(1); + } + + if (fcntl(sockfd, F_SETFD, FD_CLOEXEC) == -1) { + fprintf(stderr, "Could not set close on exec: %s\n", + strerror(errno)); + exit(1); + } + + xtables_load_ko(xtables_modprobe_program, true); + + strcpy(rev.name, name); + rev.revision = revision; + + max_rev = getsockopt(sockfd, afinfo->ipproto, opt, &rev, &s); + if (max_rev < 0) { + /* Definitely don't support this? */ + if (errno == ENOENT || errno == EPROTONOSUPPORT) { + close(sockfd); + return 0; + } else if (errno == ENOPROTOOPT) { + close(sockfd); + /* Assume only revision 0 support (old kernel) */ + return (revision == 0); + } else { + fprintf(stderr, "getsockopt failed strangely: %s\n", + strerror(errno)); + exit(1); + } + } + close(sockfd); + return 1; +} + + +static int compatible_match_revision(const char *name, uint8_t revision) +{ + return compatible_revision(name, revision, afinfo->so_rev_match); +} + +static int compatible_target_revision(const char *name, uint8_t revision) +{ + return compatible_revision(name, revision, afinfo->so_rev_target); +} + +static void xtables_check_options(const char *name, const struct option *opt) +{ + for (; opt->name != NULL; ++opt) + if (opt->val < 0 || opt->val >= XT_OPTION_OFFSET_SCALE) { + fprintf(stderr, "%s: Extension %s uses invalid " + "option value %d\n",xt_params->program_name, + name, opt->val); + exit(1); + } +} + +void xtables_register_match(struct xtables_match *me) +{ + if (me->version == NULL) { + fprintf(stderr, "%s: match %s<%u> is missing a version\n", + xt_params->program_name, me->name, me->revision); + exit(1); + } + if (strcmp(me->version, XTABLES_VERSION) != 0) { + fprintf(stderr, "%s: match \"%s\" has version \"%s\", " + "but \"%s\" is required.\n", + xt_params->program_name, me->name, + me->version, XTABLES_VERSION); + exit(1); + } + + if (strlen(me->name) >= XT_EXTENSION_MAXNAMELEN) { + fprintf(stderr, "%s: match `%s' has invalid name\n", + xt_params->program_name, me->name); + exit(1); + } + + if (me->family >= NPROTO) { + fprintf(stderr, + "%s: BUG: match %s has invalid protocol family\n", + xt_params->program_name, me->name); + exit(1); + } + + if (me->x6_options != NULL) + xtables_option_metavalidate(me->name, me->x6_options); + if (me->extra_opts != NULL) + xtables_check_options(me->name, me->extra_opts); + + /* ignore not interested match */ + if (me->family != afinfo->family && me->family != AF_UNSPEC) + return; + + /* place on linked list of matches pending full registration */ + me->next = xtables_pending_matches; + xtables_pending_matches = me; +} + +static void xtables_fully_register_pending_match(struct xtables_match *me) +{ + struct xtables_match **i, *old; + + old = xtables_find_match(me->name, XTF_DURING_LOAD, NULL); + if (old) { + if (old->revision == me->revision && + old->family == me->family) { + fprintf(stderr, + "%s: match `%s' already registered.\n", + xt_params->program_name, me->name); + exit(1); + } + + /* Now we have two (or more) options, check compatibility. */ + if (compatible_match_revision(old->name, old->revision) + && old->revision > me->revision) + return; + + /* See if new match can be used. */ + if (!compatible_match_revision(me->name, me->revision)) + return; + + /* Prefer !AF_UNSPEC over AF_UNSPEC for same revision. */ + if (old->revision == me->revision && me->family == AF_UNSPEC) + return; + + /* Delete old one. */ + for (i = &xtables_matches; *i!=old; i = &(*i)->next); + *i = old->next; + } + + if (me->size != XT_ALIGN(me->size)) { + fprintf(stderr, "%s: match `%s' has invalid size %u.\n", + xt_params->program_name, me->name, + (unsigned int)me->size); + exit(1); + } + + /* Append to list. */ + for (i = &xtables_matches; *i; i = &(*i)->next); + me->next = NULL; + *i = me; + + me->m = NULL; + me->mflags = 0; +} + +void xtables_register_matches(struct xtables_match *match, unsigned int n) +{ + do { + xtables_register_match(&match[--n]); + } while (n > 0); +} + +void xtables_register_target(struct xtables_target *me) +{ + if (me->version == NULL) { + fprintf(stderr, "%s: target %s<%u> is missing a version\n", + xt_params->program_name, me->name, me->revision); + exit(1); + } + if (strcmp(me->version, XTABLES_VERSION) != 0) { + fprintf(stderr, "%s: target \"%s\" has version \"%s\", " + "but \"%s\" is required.\n", + xt_params->program_name, me->name, + me->version, XTABLES_VERSION); + exit(1); + } + + if (strlen(me->name) >= XT_EXTENSION_MAXNAMELEN) { + fprintf(stderr, "%s: target `%s' has invalid name\n", + xt_params->program_name, me->name); + exit(1); + } + + if (me->family >= NPROTO) { + fprintf(stderr, + "%s: BUG: target %s has invalid protocol family\n", + xt_params->program_name, me->name); + exit(1); + } + + if (me->x6_options != NULL) + xtables_option_metavalidate(me->name, me->x6_options); + if (me->extra_opts != NULL) + xtables_check_options(me->name, me->extra_opts); + + /* ignore not interested target */ + if (me->family != afinfo->family && me->family != AF_UNSPEC) + return; + + /* place on linked list of targets pending full registration */ + me->next = xtables_pending_targets; + xtables_pending_targets = me; +} + +static void xtables_fully_register_pending_target(struct xtables_target *me) +{ + struct xtables_target *old; + + old = xtables_find_target(me->name, XTF_DURING_LOAD); + if (old) { + struct xtables_target **i; + + if (old->revision == me->revision && + old->family == me->family) { + fprintf(stderr, + "%s: target `%s' already registered.\n", + xt_params->program_name, me->name); + exit(1); + } + + /* Now we have two (or more) options, check compatibility. */ + if (compatible_target_revision(old->name, old->revision) + && old->revision > me->revision) + return; + + /* See if new target can be used. */ + if (!compatible_target_revision(me->name, me->revision)) + return; + + /* Prefer !AF_UNSPEC over AF_UNSPEC for same revision. */ + if (old->revision == me->revision && me->family == AF_UNSPEC) + return; + + /* Delete old one. */ + for (i = &xtables_targets; *i!=old; i = &(*i)->next); + *i = old->next; + } + + if (me->size != XT_ALIGN(me->size)) { + fprintf(stderr, "%s: target `%s' has invalid size %u.\n", + xt_params->program_name, me->name, + (unsigned int)me->size); + exit(1); + } + + /* Prepend to list. */ + me->next = xtables_targets; + xtables_targets = me; + me->t = NULL; + me->tflags = 0; +} + +void xtables_register_targets(struct xtables_target *target, unsigned int n) +{ + do { + xtables_register_target(&target[--n]); + } while (n > 0); +} + +/** + * xtables_param_act - act on condition + * @status: a constant from enum xtables_exittype + * + * %XTF_ONLY_ONCE: print error message that option may only be used once. + * @p1: module name (e.g. "mark") + * @p2(...): option in conflict (e.g. "--mark") + * @p3(...): condition to match on (see extensions/ for examples) + * + * %XTF_NO_INVERT: option does not support inversion + * @p1: module name + * @p2: option in conflict + * @p3: condition to match on + * + * %XTF_BAD_VALUE: bad value for option + * @p1: module name + * @p2: option with which the problem occured (e.g. "--mark") + * @p3: string the user passed in (e.g. "99999999999999") + * + * %XTF_ONE_ACTION: two mutually exclusive actions have been specified + * @p1: module name + * + * Displays an error message and exits the program. + */ +void xtables_param_act(unsigned int status, const char *p1, ...) +{ + const char *p2, *p3; + va_list args; + bool b; + + va_start(args, p1); + + switch (status) { + case XTF_ONLY_ONCE: + p2 = va_arg(args, const char *); + b = va_arg(args, unsigned int); + if (!b) + return; + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: \"%s\" option may only be specified once", + p1, p2); + break; + case XTF_NO_INVERT: + p2 = va_arg(args, const char *); + b = va_arg(args, unsigned int); + if (!b) + return; + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: \"%s\" option cannot be inverted", p1, p2); + break; + case XTF_BAD_VALUE: + p2 = va_arg(args, const char *); + p3 = va_arg(args, const char *); + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: Bad value for \"%s\" option: \"%s\"", + p1, p2, p3); + break; + case XTF_ONE_ACTION: + b = va_arg(args, unsigned int); + if (!b) + return; + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: At most one action is possible", p1); + break; + default: + xt_params->exit_err(status, p1, args); + break; + } + + va_end(args); +} + +const char *xtables_ipaddr_to_numeric(const struct in_addr *addrp) +{ + static char buf[20]; + const unsigned char *bytep = (const void *)&addrp->s_addr; + + sprintf(buf, "%u.%u.%u.%u", bytep[0], bytep[1], bytep[2], bytep[3]); + return buf; +} + +static const char *ipaddr_to_host(const struct in_addr *addr) +{ + struct hostent *host; + + host = gethostbyaddr(addr, sizeof(struct in_addr), AF_INET); + if (host == NULL) + return NULL; + + return host->h_name; +} + +static const char *ipaddr_to_network(const struct in_addr *addr) +{ + struct netent *net; + + if ((net = getnetbyaddr(ntohl(addr->s_addr), AF_INET)) != NULL) + return net->n_name; + + return NULL; +} + +const char *xtables_ipaddr_to_anyname(const struct in_addr *addr) +{ + const char *name; + + if ((name = ipaddr_to_host(addr)) != NULL || + (name = ipaddr_to_network(addr)) != NULL) + return name; + + return xtables_ipaddr_to_numeric(addr); +} + +const char *xtables_ipmask_to_numeric(const struct in_addr *mask) +{ + static char buf[20]; + uint32_t maskaddr, bits; + int i; + + maskaddr = ntohl(mask->s_addr); + + if (maskaddr == 0xFFFFFFFFL) + /* we don't want to see "/32" */ + return ""; + + i = 32; + bits = 0xFFFFFFFEL; + while (--i >= 0 && maskaddr != bits) + bits <<= 1; + if (i >= 0) + sprintf(buf, "/%d", i); + else + /* mask was not a decent combination of 1's and 0's */ + sprintf(buf, "/%s", xtables_ipaddr_to_numeric(mask)); + + return buf; +} + +static struct in_addr *__numeric_to_ipaddr(const char *dotted, bool is_mask) +{ + static struct in_addr addr; + unsigned char *addrp; + unsigned int onebyte; + char buf[20], *p, *q; + int i; + + /* copy dotted string, because we need to modify it */ + strncpy(buf, dotted, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + addrp = (void *)&addr.s_addr; + + p = buf; + for (i = 0; i < 3; ++i) { + if ((q = strchr(p, '.')) == NULL) { + if (is_mask) + return NULL; + + /* autocomplete, this is a network address */ + if (!xtables_strtoui(p, NULL, &onebyte, 0, UINT8_MAX)) + return NULL; + + addrp[i] = onebyte; + while (i < 3) + addrp[++i] = 0; + + return &addr; + } + + *q = '\0'; + if (!xtables_strtoui(p, NULL, &onebyte, 0, UINT8_MAX)) + return NULL; + + addrp[i] = onebyte; + p = q + 1; + } + + /* we have checked 3 bytes, now we check the last one */ + if (!xtables_strtoui(p, NULL, &onebyte, 0, UINT8_MAX)) + return NULL; + + addrp[3] = onebyte; + return &addr; +} + +struct in_addr *xtables_numeric_to_ipaddr(const char *dotted) +{ + return __numeric_to_ipaddr(dotted, false); +} + +struct in_addr *xtables_numeric_to_ipmask(const char *dotted) +{ + return __numeric_to_ipaddr(dotted, true); +} + +static struct in_addr *network_to_ipaddr(const char *name) +{ + static struct in_addr addr; + struct netent *net; + + if ((net = getnetbyname(name)) != NULL) { + if (net->n_addrtype != AF_INET) + return NULL; + addr.s_addr = htonl(net->n_net); + return &addr; + } + + return NULL; +} + +static struct in_addr *host_to_ipaddr(const char *name, unsigned int *naddr) +{ + struct hostent *host; + struct in_addr *addr; + unsigned int i; + + *naddr = 0; + if ((host = gethostbyname(name)) != NULL) { + if (host->h_addrtype != AF_INET || + host->h_length != sizeof(struct in_addr)) + return NULL; + + while (host->h_addr_list[*naddr] != NULL) + ++*naddr; + addr = xtables_calloc(*naddr, sizeof(struct in_addr)); + for (i = 0; i < *naddr; i++) + memcpy(&addr[i], host->h_addr_list[i], + sizeof(struct in_addr)); + return addr; + } + + return NULL; +} + +static struct in_addr * +ipparse_hostnetwork(const char *name, unsigned int *naddrs) +{ + struct in_addr *addrptmp, *addrp; + + if ((addrptmp = xtables_numeric_to_ipaddr(name)) != NULL || + (addrptmp = network_to_ipaddr(name)) != NULL) { + addrp = xtables_malloc(sizeof(struct in_addr)); + memcpy(addrp, addrptmp, sizeof(*addrp)); + *naddrs = 1; + return addrp; + } + if ((addrptmp = host_to_ipaddr(name, naddrs)) != NULL) + return addrptmp; + + xt_params->exit_err(PARAMETER_PROBLEM, "host/network `%s' not found", name); +} + +static struct in_addr *parse_ipmask(const char *mask) +{ + static struct in_addr maskaddr; + struct in_addr *addrp; + unsigned int bits; + + if (mask == NULL) { + /* no mask at all defaults to 32 bits */ + maskaddr.s_addr = 0xFFFFFFFF; + return &maskaddr; + } + if ((addrp = xtables_numeric_to_ipmask(mask)) != NULL) + /* dotted_to_addr already returns a network byte order addr */ + return addrp; + if (!xtables_strtoui(mask, NULL, &bits, 0, 32)) + xt_params->exit_err(PARAMETER_PROBLEM, + "invalid mask `%s' specified", mask); + if (bits != 0) { + maskaddr.s_addr = htonl(0xFFFFFFFF << (32 - bits)); + return &maskaddr; + } + + maskaddr.s_addr = 0U; + return &maskaddr; +} + +void xtables_ipparse_multiple(const char *name, struct in_addr **addrpp, + struct in_addr **maskpp, unsigned int *naddrs) +{ + struct in_addr *addrp; + char buf[256], *p; + unsigned int len, i, j, n, count = 1; + const char *loop = name; + + while ((loop = strchr(loop, ',')) != NULL) { + ++count; + ++loop; /* skip ',' */ + } + + *addrpp = xtables_malloc(sizeof(struct in_addr) * count); + *maskpp = xtables_malloc(sizeof(struct in_addr) * count); + + loop = name; + + for (i = 0; i < count; ++i) { + if (loop == NULL) + break; + if (*loop == ',') + ++loop; + if (*loop == '\0') + break; + p = strchr(loop, ','); + if (p != NULL) + len = p - loop; + else + len = strlen(loop); + if (len == 0 || sizeof(buf) - 1 < len) + break; + + strncpy(buf, loop, len); + buf[len] = '\0'; + loop += len; + if ((p = strrchr(buf, '/')) != NULL) { + *p = '\0'; + addrp = parse_ipmask(p + 1); + } else { + addrp = parse_ipmask(NULL); + } + memcpy(*maskpp + i, addrp, sizeof(*addrp)); + + /* if a null mask is given, the name is ignored, like in "any/0" */ + if ((*maskpp + i)->s_addr == 0) + /* + * A bit pointless to process multiple addresses + * in this case... + */ + strcpy(buf, "0.0.0.0"); + + addrp = ipparse_hostnetwork(buf, &n); + if (n > 1) { + count += n - 1; + *addrpp = xtables_realloc(*addrpp, + sizeof(struct in_addr) * count); + *maskpp = xtables_realloc(*maskpp, + sizeof(struct in_addr) * count); + for (j = 0; j < n; ++j) + /* for each new addr */ + memcpy(*addrpp + i + j, addrp + j, + sizeof(*addrp)); + for (j = 1; j < n; ++j) + /* for each new mask */ + memcpy(*maskpp + i + j, *maskpp + i, + sizeof(*addrp)); + i += n - 1; + } else { + memcpy(*addrpp + i, addrp, sizeof(*addrp)); + } + /* free what ipparse_hostnetwork had allocated: */ + free(addrp); + } + *naddrs = count; + for (i = 0; i < count; ++i) + (*addrpp+i)->s_addr &= (*maskpp+i)->s_addr; +} + + +/** + * xtables_ipparse_any - transform arbitrary name to in_addr + * + * Possible inputs (pseudo regex): + * m{^($hostname|$networkname|$ipaddr)(/$mask)?} + * "1.2.3.4/5", "1.2.3.4", "hostname", "networkname" + */ +void xtables_ipparse_any(const char *name, struct in_addr **addrpp, + struct in_addr *maskp, unsigned int *naddrs) +{ + unsigned int i, j, k, n; + struct in_addr *addrp; + char buf[256], *p; + + strncpy(buf, name, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + if ((p = strrchr(buf, '/')) != NULL) { + *p = '\0'; + addrp = parse_ipmask(p + 1); + } else { + addrp = parse_ipmask(NULL); + } + memcpy(maskp, addrp, sizeof(*maskp)); + + /* if a null mask is given, the name is ignored, like in "any/0" */ + if (maskp->s_addr == 0U) + strcpy(buf, "0.0.0.0"); + + addrp = *addrpp = ipparse_hostnetwork(buf, naddrs); + n = *naddrs; + for (i = 0, j = 0; i < n; ++i) { + addrp[j++].s_addr &= maskp->s_addr; + for (k = 0; k < j - 1; ++k) + if (addrp[k].s_addr == addrp[j-1].s_addr) { + /* + * Nuke the dup by copying an address from the + * tail here, and check the current position + * again (--j). + */ + memcpy(&addrp[--j], &addrp[--*naddrs], + sizeof(struct in_addr)); + break; + } + } +} + +const char *xtables_ip6addr_to_numeric(const struct in6_addr *addrp) +{ + /* 0000:0000:0000:0000:0000:0000:000.000.000.000 + * 0000:0000:0000:0000:0000:0000:0000:0000 */ + static char buf[50+1]; + return inet_ntop(AF_INET6, addrp, buf, sizeof(buf)); +} + +static const char *ip6addr_to_host(const struct in6_addr *addr) +{ + static char hostname[NI_MAXHOST]; + struct sockaddr_in6 saddr; + int err; + + memset(&saddr, 0, sizeof(struct sockaddr_in6)); + memcpy(&saddr.sin6_addr, addr, sizeof(*addr)); + saddr.sin6_family = AF_INET6; + + err = getnameinfo((const void *)&saddr, sizeof(struct sockaddr_in6), + hostname, sizeof(hostname) - 1, NULL, 0, 0); + if (err != 0) { +#ifdef DEBUG + fprintf(stderr,"IP2Name: %s\n",gai_strerror(err)); +#endif + return NULL; + } + +#ifdef DEBUG + fprintf (stderr, "\naddr2host: %s\n", hostname); +#endif + return hostname; +} + +const char *xtables_ip6addr_to_anyname(const struct in6_addr *addr) +{ + const char *name; + + if ((name = ip6addr_to_host(addr)) != NULL) + return name; + + return xtables_ip6addr_to_numeric(addr); +} + +static int ip6addr_prefix_length(const struct in6_addr *k) +{ + unsigned int bits = 0; + uint32_t a, b, c, d; + + a = ntohl(k->s6_addr32[0]); + b = ntohl(k->s6_addr32[1]); + c = ntohl(k->s6_addr32[2]); + d = ntohl(k->s6_addr32[3]); + while (a & 0x80000000U) { + ++bits; + a <<= 1; + a |= (b >> 31) & 1; + b <<= 1; + b |= (c >> 31) & 1; + c <<= 1; + c |= (d >> 31) & 1; + d <<= 1; + } + if (a != 0 || b != 0 || c != 0 || d != 0) + return -1; + return bits; +} + +const char *xtables_ip6mask_to_numeric(const struct in6_addr *addrp) +{ + static char buf[50+2]; + int l = ip6addr_prefix_length(addrp); + + if (l == -1) { + strcpy(buf, "/"); + strcat(buf, xtables_ip6addr_to_numeric(addrp)); + return buf; + } + sprintf(buf, "/%d", l); + return buf; +} + +struct in6_addr *xtables_numeric_to_ip6addr(const char *num) +{ + static struct in6_addr ap; + int err; + + if ((err = inet_pton(AF_INET6, num, &ap)) == 1) + return ≈ +#ifdef DEBUG + fprintf(stderr, "\nnumeric2addr: %d\n", err); +#endif + return NULL; +} + +static struct in6_addr * +host_to_ip6addr(const char *name, unsigned int *naddr) +{ + struct in6_addr *addr; + struct addrinfo hints; + struct addrinfo *res, *p; + int err; + unsigned int i; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_RAW; + + *naddr = 0; + if ((err = getaddrinfo(name, NULL, &hints, &res)) != 0) { +#ifdef DEBUG + fprintf(stderr,"Name2IP: %s\n",gai_strerror(err)); +#endif + return NULL; + } else { + /* Find length of address chain */ + for (p = res; p != NULL; p = p->ai_next) + ++*naddr; +#ifdef DEBUG + fprintf(stderr, "resolved: len=%d %s ", res->ai_addrlen, + xtables_ip6addr_to_numeric(&((struct sockaddr_in6 *)res->ai_addr)->sin6_addr)); +#endif + /* Copy each element of the address chain */ + addr = xtables_calloc(*naddr, sizeof(struct in6_addr)); + for (i = 0, p = res; p != NULL; p = p->ai_next) + memcpy(&addr[i++], + &((const struct sockaddr_in6 *)p->ai_addr)->sin6_addr, + sizeof(struct in6_addr)); + freeaddrinfo(res); + return addr; + } + + return NULL; +} + +static struct in6_addr *network_to_ip6addr(const char *name) +{ + /* abort();*/ + /* TODO: not implemented yet, but the exception breaks the + * name resolvation */ + return NULL; +} + +static struct in6_addr * +ip6parse_hostnetwork(const char *name, unsigned int *naddrs) +{ + struct in6_addr *addrp, *addrptmp; + + if ((addrptmp = xtables_numeric_to_ip6addr(name)) != NULL || + (addrptmp = network_to_ip6addr(name)) != NULL) { + addrp = xtables_malloc(sizeof(struct in6_addr)); + memcpy(addrp, addrptmp, sizeof(*addrp)); + *naddrs = 1; + return addrp; + } + if ((addrp = host_to_ip6addr(name, naddrs)) != NULL) + return addrp; + + xt_params->exit_err(PARAMETER_PROBLEM, "host/network `%s' not found", name); +} + +static struct in6_addr *parse_ip6mask(char *mask) +{ + static struct in6_addr maskaddr; + struct in6_addr *addrp; + unsigned int bits; + + if (mask == NULL) { + /* no mask at all defaults to 128 bits */ + memset(&maskaddr, 0xff, sizeof maskaddr); + return &maskaddr; + } + if ((addrp = xtables_numeric_to_ip6addr(mask)) != NULL) + return addrp; + if (!xtables_strtoui(mask, NULL, &bits, 0, 128)) + xt_params->exit_err(PARAMETER_PROBLEM, + "invalid mask `%s' specified", mask); + if (bits != 0) { + char *p = (void *)&maskaddr; + memset(p, 0xff, bits / 8); + memset(p + (bits / 8) + 1, 0, (128 - bits) / 8); + p[bits/8] = 0xff << (8 - (bits & 7)); + return &maskaddr; + } + + memset(&maskaddr, 0, sizeof(maskaddr)); + return &maskaddr; +} + +void +xtables_ip6parse_multiple(const char *name, struct in6_addr **addrpp, + struct in6_addr **maskpp, unsigned int *naddrs) +{ + static const struct in6_addr zero_addr; + struct in6_addr *addrp; + char buf[256], *p; + unsigned int len, i, j, n, count = 1; + const char *loop = name; + + while ((loop = strchr(loop, ',')) != NULL) { + ++count; + ++loop; /* skip ',' */ + } + + *addrpp = xtables_malloc(sizeof(struct in6_addr) * count); + *maskpp = xtables_malloc(sizeof(struct in6_addr) * count); + + loop = name; + + for (i = 0; i < count /*NB: count can grow*/; ++i) { + if (loop == NULL) + break; + if (*loop == ',') + ++loop; + if (*loop == '\0') + break; + p = strchr(loop, ','); + if (p != NULL) + len = p - loop; + else + len = strlen(loop); + if (len == 0 || sizeof(buf) - 1 < len) + break; + + strncpy(buf, loop, len); + buf[len] = '\0'; + loop += len; + if ((p = strrchr(buf, '/')) != NULL) { + *p = '\0'; + addrp = parse_ip6mask(p + 1); + } else { + addrp = parse_ip6mask(NULL); + } + memcpy(*maskpp + i, addrp, sizeof(*addrp)); + + /* if a null mask is given, the name is ignored, like in "any/0" */ + if (memcmp(*maskpp + i, &zero_addr, sizeof(zero_addr)) == 0) + strcpy(buf, "::"); + + addrp = ip6parse_hostnetwork(buf, &n); + if (n > 1) { + count += n - 1; + *addrpp = xtables_realloc(*addrpp, + sizeof(struct in6_addr) * count); + *maskpp = xtables_realloc(*maskpp, + sizeof(struct in6_addr) * count); + for (j = 0; j < n; ++j) + /* for each new addr */ + memcpy(*addrpp + i + j, addrp + j, + sizeof(*addrp)); + for (j = 1; j < n; ++j) + /* for each new mask */ + memcpy(*maskpp + i + j, *maskpp + i, + sizeof(*addrp)); + i += n - 1; + } else { + memcpy(*addrpp + i, addrp, sizeof(*addrp)); + } + /* free what ip6parse_hostnetwork had allocated: */ + free(addrp); + } + *naddrs = count; + for (i = 0; i < count; ++i) + for (j = 0; j < 4; ++j) + (*addrpp+i)->s6_addr32[j] &= (*maskpp+i)->s6_addr32[j]; +} + +void xtables_ip6parse_any(const char *name, struct in6_addr **addrpp, + struct in6_addr *maskp, unsigned int *naddrs) +{ + static const struct in6_addr zero_addr; + struct in6_addr *addrp; + unsigned int i, j, k, n; + char buf[256], *p; + + strncpy(buf, name, sizeof(buf) - 1); + buf[sizeof(buf)-1] = '\0'; + if ((p = strrchr(buf, '/')) != NULL) { + *p = '\0'; + addrp = parse_ip6mask(p + 1); + } else { + addrp = parse_ip6mask(NULL); + } + memcpy(maskp, addrp, sizeof(*maskp)); + + /* if a null mask is given, the name is ignored, like in "any/0" */ + if (memcmp(maskp, &zero_addr, sizeof(zero_addr)) == 0) + strcpy(buf, "::"); + + addrp = *addrpp = ip6parse_hostnetwork(buf, naddrs); + n = *naddrs; + for (i = 0, j = 0; i < n; ++i) { + for (k = 0; k < 4; ++k) + addrp[j].s6_addr32[k] &= maskp->s6_addr32[k]; + ++j; + for (k = 0; k < j - 1; ++k) + if (IN6_ARE_ADDR_EQUAL(&addrp[k], &addrp[j - 1])) { + /* + * Nuke the dup by copying an address from the + * tail here, and check the current position + * again (--j). + */ + memcpy(&addrp[--j], &addrp[--*naddrs], + sizeof(struct in_addr)); + break; + } + } +} + +void xtables_save_string(const char *value) +{ + static const char no_quote_chars[] = "_-0123456789" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + static const char escape_chars[] = "\"\\'"; + size_t length; + const char *p; + + length = strspn(value, no_quote_chars); + if (length > 0 && value[length] == 0) { + /* no quoting required */ + putchar(' '); + fputs(value, stdout); + } else { + /* there is at least one dangerous character in the + value, which we have to quote. Write double quotes + around the value and escape special characters with + a backslash */ + printf(" \""); + + for (p = strpbrk(value, escape_chars); p != NULL; + p = strpbrk(value, escape_chars)) { + if (p > value) + fwrite(value, 1, p - value, stdout); + putchar('\\'); + putchar(*p); + value = p + 1; + } + + /* print the rest and finish the double quoted + string */ + fputs(value, stdout); + putchar('\"'); + } +} + +/** + * Check for option-intrapositional negation. + * Do not use in new code. + */ +int xtables_check_inverse(const char option[], int *invert, + int *my_optind, int argc, char **argv) +{ + if (option == NULL || strcmp(option, "!") != 0) + return false; + + fprintf(stderr, "Using intrapositioned negation " + "(`--option ! this`) is deprecated in favor of " + "extrapositioned (`! --option this`).\n"); + + if (*invert) + xt_params->exit_err(PARAMETER_PROBLEM, + "Multiple `!' flags not allowed"); + *invert = true; + if (my_optind != NULL) { + optarg = argv[*my_optind]; + ++*my_optind; + if (argc && *my_optind > argc) + xt_params->exit_err(PARAMETER_PROBLEM, + "no argument following `!'"); + } + + return true; +} + +const struct xtables_pprot xtables_chain_protos[] = { + {"tcp", IPPROTO_TCP}, + {"sctp", IPPROTO_SCTP}, + {"udp", IPPROTO_UDP}, + {"udplite", IPPROTO_UDPLITE}, + {"icmp", IPPROTO_ICMP}, + {"icmpv6", IPPROTO_ICMPV6}, + {"ipv6-icmp", IPPROTO_ICMPV6}, + {"esp", IPPROTO_ESP}, + {"ah", IPPROTO_AH}, + {"ipv6-mh", IPPROTO_MH}, + {"mh", IPPROTO_MH}, + {"all", 0}, + {NULL}, +}; + +uint16_t +xtables_parse_protocol(const char *s) +{ + const struct protoent *pent; + unsigned int proto, i; + + if (xtables_strtoui(s, NULL, &proto, 0, UINT8_MAX)) + return proto; + + /* first deal with the special case of 'all' to prevent + * people from being able to redefine 'all' in nsswitch + * and/or provoke expensive [not working] ldap/nis/... + * lookups */ + if (strcmp(s, "all") == 0) + return 0; + + pent = getprotobyname(s); + if (pent != NULL) + return pent->p_proto; + + for (i = 0; i < ARRAY_SIZE(xtables_chain_protos); ++i) { + if (xtables_chain_protos[i].name == NULL) + continue; + if (strcmp(s, xtables_chain_protos[i].name) == 0) + return xtables_chain_protos[i].num; + } + xt_params->exit_err(PARAMETER_PROBLEM, + "unknown protocol \"%s\" specified", s); + return -1; +} diff --git a/iptables/xtables.pc.in b/iptables/xtables.pc.in new file mode 100644 index 00000000..43f35d54 --- /dev/null +++ b/iptables/xtables.pc.in @@ -0,0 +1,13 @@ + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +xtlibdir=@xtlibdir@ +includedir=@includedir@ + +Name: xtables +Description: Shared Xtables code for extensions and iproute2 +Version: @PACKAGE_VERSION@ +Cflags: -I${includedir} +Libs: -L${libdir} -lxtables +Libs.private: -ldl diff --git a/iptables/xtoptions.c b/iptables/xtoptions.c new file mode 100644 index 00000000..ac0601f2 --- /dev/null +++ b/iptables/xtoptions.c @@ -0,0 +1,1155 @@ +/* + * Argument parser + * Copyright © Jan Engelhardt, 2011 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xtables.h" +#include "xshared.h" +#ifndef IPTOS_NORMALSVC +# define IPTOS_NORMALSVC 0 +#endif + +#define XTOPT_MKPTR(cb) \ + ((void *)((char *)(cb)->data + (cb)->entry->ptroff)) + +/** + * Simple key-value pairs for syslog levels + */ +struct syslog_level { + char name[8]; + uint8_t level; +}; + +struct tos_value_mask { + uint8_t value, mask; +}; + +static const size_t xtopt_psize[] = { + /* + * All types not listed here, and thus essentially being initialized to + * zero have zero on purpose. + */ + [XTTYPE_UINT8] = sizeof(uint8_t), + [XTTYPE_UINT16] = sizeof(uint16_t), + [XTTYPE_UINT32] = sizeof(uint32_t), + [XTTYPE_UINT64] = sizeof(uint64_t), + [XTTYPE_UINT8RC] = sizeof(uint8_t[2]), + [XTTYPE_UINT16RC] = sizeof(uint16_t[2]), + [XTTYPE_UINT32RC] = sizeof(uint32_t[2]), + [XTTYPE_UINT64RC] = sizeof(uint64_t[2]), + [XTTYPE_DOUBLE] = sizeof(double), + [XTTYPE_STRING] = -1, + [XTTYPE_SYSLOGLEVEL] = sizeof(uint8_t), + [XTTYPE_HOST] = sizeof(union nf_inet_addr), + [XTTYPE_HOSTMASK] = sizeof(union nf_inet_addr), + [XTTYPE_PROTOCOL] = sizeof(uint8_t), + [XTTYPE_PORT] = sizeof(uint16_t), + [XTTYPE_PORTRC] = sizeof(uint16_t[2]), + [XTTYPE_PLENMASK] = sizeof(union nf_inet_addr), + [XTTYPE_ETHERMAC] = sizeof(uint8_t[6]), +}; + +/** + * Creates getopt options from the x6-style option map, and assigns each a + * getopt id. + */ +struct option * +xtables_options_xfrm(struct option *orig_opts, struct option *oldopts, + const struct xt_option_entry *entry, unsigned int *offset) +{ + unsigned int num_orig, num_old = 0, num_new, i; + struct option *merge, *mp; + + if (entry == NULL) + return oldopts; + for (num_orig = 0; orig_opts[num_orig].name != NULL; ++num_orig) + ; + if (oldopts != NULL) + for (num_old = 0; oldopts[num_old].name != NULL; ++num_old) + ; + for (num_new = 0; entry[num_new].name != NULL; ++num_new) + ; + + /* + * Since @oldopts also has @orig_opts already (and does so at the + * start), skip these entries. + */ + oldopts += num_orig; + num_old -= num_orig; + + merge = malloc(sizeof(*mp) * (num_orig + num_old + num_new + 1)); + if (merge == NULL) + return NULL; + + /* Let the base options -[ADI...] have precedence over everything */ + memcpy(merge, orig_opts, sizeof(*mp) * num_orig); + mp = merge + num_orig; + + /* Second, the new options */ + xt_params->option_offset += XT_OPTION_OFFSET_SCALE; + *offset = xt_params->option_offset; + + for (i = 0; i < num_new; ++i, ++mp, ++entry) { + mp->name = entry->name; + mp->has_arg = entry->type != XTTYPE_NONE; + mp->flag = NULL; + mp->val = entry->id + *offset; + } + + /* Third, the old options */ + memcpy(mp, oldopts, sizeof(*mp) * num_old); + mp += num_old; + xtables_free_opts(0); + + /* Clear trailing entry */ + memset(mp, 0, sizeof(*mp)); + return merge; +} + +/** + * Give the upper limit for a certain type. + */ +static uintmax_t xtopt_max_by_type(enum xt_option_type type) +{ + switch (type) { + case XTTYPE_UINT8: + case XTTYPE_UINT8RC: + return UINT8_MAX; + case XTTYPE_UINT16: + case XTTYPE_UINT16RC: + return UINT16_MAX; + case XTTYPE_UINT32: + case XTTYPE_UINT32RC: + return UINT32_MAX; + case XTTYPE_UINT64: + case XTTYPE_UINT64RC: + return UINT64_MAX; + default: + return 0; + } +} + +/** + * Return the size of a single entity based upon a type - predominantly an + * XTTYPE_UINT*RC type. + */ +static size_t xtopt_esize_by_type(enum xt_option_type type) +{ + switch (type) { + case XTTYPE_UINT8RC: + return xtopt_psize[XTTYPE_UINT8]; + case XTTYPE_UINT16RC: + return xtopt_psize[XTTYPE_UINT16]; + case XTTYPE_UINT32RC: + return xtopt_psize[XTTYPE_UINT32]; + case XTTYPE_UINT64RC: + return xtopt_psize[XTTYPE_UINT64]; + default: + return xtopt_psize[type]; + } +} + +/** + * Require a simple integer. + */ +static void xtopt_parse_int(struct xt_option_call *cb) +{ + const struct xt_option_entry *entry = cb->entry; + uintmax_t lmin = 0, lmax = xtopt_max_by_type(entry->type); + uintmax_t value; + + if (cb->entry->min != 0) + lmin = cb->entry->min; + if (cb->entry->max != 0) + lmax = cb->entry->max; + + if (!xtables_strtoul(cb->arg, NULL, &value, lmin, lmax)) + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: bad value for option \"--%s\", " + "or out of range (%ju-%ju).\n", + cb->ext_name, entry->name, lmin, lmax); + + if (entry->type == XTTYPE_UINT8) { + cb->val.u8 = value; + if (entry->flags & XTOPT_PUT) + *(uint8_t *)XTOPT_MKPTR(cb) = cb->val.u8; + } else if (entry->type == XTTYPE_UINT16) { + cb->val.u16 = value; + if (entry->flags & XTOPT_PUT) + *(uint16_t *)XTOPT_MKPTR(cb) = cb->val.u16; + } else if (entry->type == XTTYPE_UINT32) { + cb->val.u32 = value; + if (entry->flags & XTOPT_PUT) + *(uint32_t *)XTOPT_MKPTR(cb) = cb->val.u32; + } else if (entry->type == XTTYPE_UINT64) { + cb->val.u64 = value; + if (entry->flags & XTOPT_PUT) + *(uint64_t *)XTOPT_MKPTR(cb) = cb->val.u64; + } +} + +/** + * Require a simple floating point number. + */ +static void xtopt_parse_float(struct xt_option_call *cb) +{ + const struct xt_option_entry *entry = cb->entry; + double value; + char *end; + + value = strtod(cb->arg, &end); + if (end == cb->arg || *end != '\0' || + (entry->min != entry->max && + (value < entry->min || value > entry->max))) + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: bad value for option \"--%s\", " + "or out of range (%u-%u).\n", + cb->ext_name, entry->name, entry->min, entry->max); + + cb->val.dbl = value; + if (entry->flags & XTOPT_PUT) + *(double *)XTOPT_MKPTR(cb) = cb->val.dbl; +} + +/** + * Copy the parsed value to the appropriate entry in cb->val. + */ +static void xtopt_mint_value_to_cb(struct xt_option_call *cb, uintmax_t value) +{ + const struct xt_option_entry *entry = cb->entry; + + if (cb->nvals >= ARRAY_SIZE(cb->val.u32_range)) + return; + if (entry->type == XTTYPE_UINT8RC) + cb->val.u8_range[cb->nvals] = value; + else if (entry->type == XTTYPE_UINT16RC) + cb->val.u16_range[cb->nvals] = value; + else if (entry->type == XTTYPE_UINT32RC) + cb->val.u32_range[cb->nvals] = value; + else if (entry->type == XTTYPE_UINT64RC) + cb->val.u64_range[cb->nvals] = value; +} + +/** + * Copy the parsed value to the data area, using appropriate type access. + */ +static void xtopt_mint_value_to_ptr(struct xt_option_call *cb, void **datap, + uintmax_t value) +{ + const struct xt_option_entry *entry = cb->entry; + void *data = *datap; + + if (!(entry->flags & XTOPT_PUT)) + return; + if (entry->type == XTTYPE_UINT8RC) + *(uint8_t *)data = value; + else if (entry->type == XTTYPE_UINT16RC) + *(uint16_t *)data = value; + else if (entry->type == XTTYPE_UINT32RC) + *(uint32_t *)data = value; + else if (entry->type == XTTYPE_UINT64RC) + *(uint64_t *)data = value; + data += xtopt_esize_by_type(entry->type); + *datap = data; +} + +/** + * Multiple integer parse routine. + * + * This function is capable of parsing any number of fields. Only the first + * two values from the string will be put into @cb however (and as such, + * @cb->val.uXX_range is just that large) to cater for the few extensions that + * do not have a range[2] field, but {min, max}, and which cannot use + * XTOPT_POINTER. + */ +static void xtopt_parse_mint(struct xt_option_call *cb) +{ + const struct xt_option_entry *entry = cb->entry; + const char *arg = cb->arg; + size_t esize = xtopt_esize_by_type(entry->type); + const uintmax_t lmax = xtopt_max_by_type(entry->type); + void *put = XTOPT_MKPTR(cb); + unsigned int maxiter; + uintmax_t value; + char *end = ""; + char sep = ':'; + + maxiter = entry->size / esize; + if (maxiter == 0) + maxiter = ARRAY_SIZE(cb->val.u32_range); + if (entry->size % esize != 0) + xt_params->exit_err(OTHER_PROBLEM, "%s: memory block does " + "not have proper size\n", __func__); + + cb->nvals = 0; + for (arg = cb->arg, end = (char *)arg; ; arg = end + 1) { + if (cb->nvals == maxiter) + xt_params->exit_err(PARAMETER_PROBLEM, "%s: Too many " + "components for option \"--%s\" (max: %u)\n", + cb->ext_name, entry->name, maxiter); + if (*arg == '\0' || *arg == sep) { + /* Default range components when field not spec'd. */ + end = (char *)arg; + value = (cb->nvals == 1) ? lmax : 0; + } else { + if (!xtables_strtoul(arg, &end, &value, 0, lmax)) + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: bad value for option \"--%s\" near " + "\"%s\", or out of range (0-%ju).\n", + cb->ext_name, entry->name, arg, lmax); + if (*end != '\0' && *end != sep) + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: Argument to \"--%s\" has " + "unexpected characters near \"%s\".\n", + cb->ext_name, entry->name, end); + } + xtopt_mint_value_to_cb(cb, value); + ++cb->nvals; + xtopt_mint_value_to_ptr(cb, &put, value); + if (*end == '\0') + break; + } +} + +static void xtopt_parse_string(struct xt_option_call *cb) +{ + const struct xt_option_entry *entry = cb->entry; + size_t z = strlen(cb->arg); + char *p; + + if (entry->min != 0 && z < entry->min) + xt_params->exit_err(PARAMETER_PROBLEM, + "Argument must have a minimum length of " + "%u characters\n", entry->min); + if (entry->max != 0 && z > entry->max) + xt_params->exit_err(PARAMETER_PROBLEM, + "Argument must have a maximum length of " + "%u characters\n", entry->max); + if (!(entry->flags & XTOPT_PUT)) + return; + if (z >= entry->size) + z = entry->size - 1; + p = XTOPT_MKPTR(cb); + strncpy(p, cb->arg, z); + p[z] = '\0'; +} + +static const struct tos_symbol_info { + unsigned char value; + const char *name; +} tos_symbol_names[] = { + {IPTOS_LOWDELAY, "Minimize-Delay"}, + {IPTOS_THROUGHPUT, "Maximize-Throughput"}, + {IPTOS_RELIABILITY, "Maximize-Reliability"}, + {IPTOS_MINCOST, "Minimize-Cost"}, + {IPTOS_NORMALSVC, "Normal-Service"}, + {}, +}; + +/* + * tos_parse_numeric - parse a string like "15/255" + * + * @str: input string + * @tvm: (value/mask) tuple + * @max: maximum allowed value (must be pow(2,some_int)-1) + */ +static bool tos_parse_numeric(const char *str, struct xt_option_call *cb, + unsigned int max) +{ + unsigned int value; + char *end; + + xtables_strtoui(str, &end, &value, 0, max); + cb->val.tos_value = value; + cb->val.tos_mask = max; + + if (*end == '/') { + const char *p = end + 1; + + if (!xtables_strtoui(p, &end, &value, 0, max)) + xtables_error(PARAMETER_PROBLEM, "Illegal value: \"%s\"", + str); + cb->val.tos_mask = value; + } + + if (*end != '\0') + xtables_error(PARAMETER_PROBLEM, "Illegal value: \"%s\"", str); + return true; +} + +/** + * @str: input string + * @tvm: (value/mask) tuple + * @def_mask: mask to force when a symbolic name is used + */ +static void xtopt_parse_tosmask(struct xt_option_call *cb) +{ + const struct tos_symbol_info *symbol; + char *tmp; + + if (xtables_strtoui(cb->arg, &tmp, NULL, 0, UINT8_MAX)) { + tos_parse_numeric(cb->arg, cb, UINT8_MAX); + return; + } + /* + * This is our way we deal with different defaults + * for different revisions. + */ + cb->val.tos_mask = cb->entry->max; + for (symbol = tos_symbol_names; symbol->name != NULL; ++symbol) + if (strcasecmp(cb->arg, symbol->name) == 0) { + cb->val.tos_value = symbol->value; + return; + } + + xtables_error(PARAMETER_PROBLEM, "Symbolic name \"%s\" is unknown", + cb->arg); +} + +/** + * Validate the input for being conformant to "mark[/mask]". + */ +static void xtopt_parse_markmask(struct xt_option_call *cb) +{ + unsigned int mark = 0, mask = ~0U; + char *end; + + if (!xtables_strtoui(cb->arg, &end, &mark, 0, UINT32_MAX)) + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: bad mark value for option \"--%s\", " + "or out of range.\n", + cb->ext_name, cb->entry->name); + if (*end == '/' && + !xtables_strtoui(end + 1, &end, &mask, 0, UINT32_MAX)) + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: bad mask value for option \"--%s\", " + "or out of range.\n", + cb->ext_name, cb->entry->name); + if (*end != '\0') + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: trailing garbage after value " + "for option \"--%s\".\n", + cb->ext_name, cb->entry->name); + cb->val.mark = mark; + cb->val.mask = mask; +} + +static int xtopt_sysloglvl_compare(const void *a, const void *b) +{ + const char *name = a; + const struct syslog_level *entry = b; + + return strcmp(name, entry->name); +} + +static void xtopt_parse_sysloglevel(struct xt_option_call *cb) +{ + static const struct syslog_level log_names[] = { /* must be sorted */ + {"alert", LOG_ALERT}, + {"crit", LOG_CRIT}, + {"debug", LOG_DEBUG}, + {"emerg", LOG_EMERG}, + {"error", LOG_ERR}, /* deprecated */ + {"info", LOG_INFO}, + {"notice", LOG_NOTICE}, + {"panic", LOG_EMERG}, /* deprecated */ + {"warning", LOG_WARNING}, + }; + const struct syslog_level *e; + unsigned int num = 0; + + if (!xtables_strtoui(cb->arg, NULL, &num, 0, 7)) { + e = bsearch(cb->arg, log_names, ARRAY_SIZE(log_names), + sizeof(*log_names), xtopt_sysloglvl_compare); + if (e == NULL) + xt_params->exit_err(PARAMETER_PROBLEM, + "log level \"%s\" unknown\n", cb->arg); + num = e->level; + } + cb->val.syslog_level = num; + if (cb->entry->flags & XTOPT_PUT) + *(uint8_t *)XTOPT_MKPTR(cb) = num; +} + +static void *xtables_sa_host(const void *sa, unsigned int afproto) +{ + if (afproto == AF_INET6) + return &((struct sockaddr_in6 *)sa)->sin6_addr; + else if (afproto == AF_INET) + return &((struct sockaddr_in *)sa)->sin_addr; + return (void *)sa; +} + +static socklen_t xtables_sa_hostlen(unsigned int afproto) +{ + if (afproto == AF_INET6) + return sizeof(struct in6_addr); + else if (afproto == AF_INET) + return sizeof(struct in_addr); + return 0; +} + +/** + * Accepts: a hostname (DNS), or a single inetaddr - without any mask. The + * result is stored in @cb->val.haddr. Additionally, @cb->val.hmask and + * @cb->val.hlen are set for completeness to the appropriate values. + */ +static void xtopt_parse_host(struct xt_option_call *cb) +{ + struct addrinfo hints = {.ai_family = afinfo->family}; + unsigned int adcount = 0; + struct addrinfo *res, *p; + int ret; + + ret = getaddrinfo(cb->arg, NULL, &hints, &res); + if (ret < 0) + xt_params->exit_err(PARAMETER_PROBLEM, + "getaddrinfo: %s\n", gai_strerror(ret)); + + memset(&cb->val.hmask, 0xFF, sizeof(cb->val.hmask)); + cb->val.hlen = (afinfo->family == NFPROTO_IPV4) ? 32 : 128; + + for (p = res; p != NULL; p = p->ai_next) { + if (adcount == 0) { + memset(&cb->val.haddr, 0, sizeof(cb->val.haddr)); + memcpy(&cb->val.haddr, + xtables_sa_host(p->ai_addr, p->ai_family), + xtables_sa_hostlen(p->ai_family)); + ++adcount; + continue; + } + if (memcmp(&cb->val.haddr, + xtables_sa_host(p->ai_addr, p->ai_family), + xtables_sa_hostlen(p->ai_family)) != 0) + xt_params->exit_err(PARAMETER_PROBLEM, + "%s resolves to more than one address\n", + cb->arg); + } + + freeaddrinfo(res); + if (cb->entry->flags & XTOPT_PUT) + /* Validation in xtables_option_metavalidate */ + memcpy(XTOPT_MKPTR(cb), &cb->val.haddr, + sizeof(cb->val.haddr)); +} + +/** + * @name: port name, or number as a string (e.g. "http" or "80") + * + * Resolve a port name to a number. Returns the port number in integral + * form on success, or <0 on error. (errno will not be set.) + */ +static int xtables_getportbyname(const char *name) +{ + struct addrinfo *res = NULL, *p; + int ret; + + ret = getaddrinfo(NULL, name, NULL, &res); + if (ret < 0) + return -1; + ret = -1; + for (p = res; p != NULL; p = p->ai_next) { + if (p->ai_family == AF_INET6) { + ret = ((struct sockaddr_in6 *)p->ai_addr)->sin6_port; + break; + } else if (p->ai_family == AF_INET) { + ret = ((struct sockaddr_in *)p->ai_addr)->sin_port; + break; + } + } + freeaddrinfo(res); + if (ret < 0) + return ret; + return ntohs(ret); +} + +/** + * Validate and parse a protocol specification (number or name) by use of + * /etc/protocols and put the result into @cb->val.protocol. + */ +static void xtopt_parse_protocol(struct xt_option_call *cb) +{ + cb->val.protocol = xtables_parse_protocol(cb->arg); + if (cb->entry->flags & XTOPT_PUT) + *(uint8_t *)XTOPT_MKPTR(cb) = cb->val.protocol; +} + +/** + * Validate and parse a port specification and put the result into + * @cb->val.port. + */ +static void xtopt_parse_port(struct xt_option_call *cb) +{ + const struct xt_option_entry *entry = cb->entry; + int ret; + + ret = xtables_getportbyname(cb->arg); + if (ret < 0) + xt_params->exit_err(PARAMETER_PROBLEM, + "Port \"%s\" does not resolve to anything.\n", + cb->arg); + if (entry->flags & XTOPT_NBO) + ret = htons(ret); + cb->val.port = ret; + if (entry->flags & XTOPT_PUT) + *(uint16_t *)XTOPT_MKPTR(cb) = cb->val.port; +} + +static void xtopt_parse_mport(struct xt_option_call *cb) +{ + static const size_t esize = sizeof(uint16_t); + const struct xt_option_entry *entry = cb->entry; + char *lo_arg, *wp_arg, *arg; + unsigned int maxiter; + int value; + + wp_arg = lo_arg = strdup(cb->arg); + if (lo_arg == NULL) + xt_params->exit_err(RESOURCE_PROBLEM, "strdup"); + + maxiter = entry->size / esize; + if (maxiter == 0) + maxiter = 2; /* ARRAY_SIZE(cb->val.port_range) */ + if (entry->size % esize != 0) + xt_params->exit_err(OTHER_PROBLEM, "%s: memory block does " + "not have proper size\n", __func__); + + cb->val.port_range[0] = 0; + cb->val.port_range[1] = UINT16_MAX; + cb->nvals = 0; + + while ((arg = strsep(&wp_arg, ":")) != NULL) { + if (cb->nvals == maxiter) + xt_params->exit_err(PARAMETER_PROBLEM, "%s: Too many " + "components for option \"--%s\" (max: %u)\n", + cb->ext_name, entry->name, maxiter); + if (*arg == '\0') { + ++cb->nvals; + continue; + } + + value = xtables_getportbyname(arg); + if (value < 0) + xt_params->exit_err(PARAMETER_PROBLEM, + "Port \"%s\" does not resolve to " + "anything.\n", arg); + if (entry->flags & XTOPT_NBO) + value = htons(value); + if (cb->nvals < ARRAY_SIZE(cb->val.port_range)) + cb->val.port_range[cb->nvals] = value; + ++cb->nvals; + } + + if (cb->nvals == 1) { + cb->val.port_range[1] = cb->val.port_range[0]; + ++cb->nvals; + } + if (entry->flags & XTOPT_PUT) + memcpy(XTOPT_MKPTR(cb), cb->val.port_range, sizeof(uint16_t) * + (cb->nvals <= maxiter ? cb->nvals : maxiter)); + free(lo_arg); +} + +/** + * Parse an integer and ensure it is within the address family's prefix length + * limits. The result is stored in @cb->val.hlen. + */ +static void xtopt_parse_plen(struct xt_option_call *cb) +{ + const struct xt_option_entry *entry = cb->entry; + unsigned int prefix_len = 128; /* happiness is a warm gcc */ + + cb->val.hlen = (afinfo->family == NFPROTO_IPV4) ? 32 : 128; + if (!xtables_strtoui(cb->arg, NULL, &prefix_len, 0, cb->val.hlen)) + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: bad value for option \"--%s\", " + "or out of range (%u-%u).\n", + cb->ext_name, entry->name, 0, cb->val.hlen); + + cb->val.hlen = prefix_len; +} + +/** + * Reuse xtopt_parse_plen for testing the integer. Afterwards convert this to + * a bitmask, and make it available through @cb->val.hmask (hlen remains + * valid). If %XTOPT_PUT is used, hmask will be copied to the target area. + */ +static void xtopt_parse_plenmask(struct xt_option_call *cb) +{ + const struct xt_option_entry *entry = cb->entry; + uint32_t *mask = cb->val.hmask.all; + + xtopt_parse_plen(cb); + + memset(mask, 0xFF, sizeof(union nf_inet_addr)); + /* This shifting is AF-independent. */ + if (cb->val.hlen == 0) { + mask[0] = mask[1] = mask[2] = mask[3] = 0; + } else if (cb->val.hlen <= 32) { + mask[0] <<= 32 - cb->val.hlen; + mask[1] = mask[2] = mask[3] = 0; + } else if (cb->val.hlen <= 64) { + mask[1] <<= 32 - (cb->val.hlen - 32); + mask[2] = mask[3] = 0; + } else if (cb->val.hlen <= 96) { + mask[2] <<= 32 - (cb->val.hlen - 64); + mask[3] = 0; + } else if (cb->val.hlen <= 128) { + mask[3] <<= 32 - (cb->val.hlen - 96); + } + mask[0] = htonl(mask[0]); + mask[1] = htonl(mask[1]); + mask[2] = htonl(mask[2]); + mask[3] = htonl(mask[3]); + if (entry->flags & XTOPT_PUT) + memcpy(XTOPT_MKPTR(cb), mask, sizeof(union nf_inet_addr)); +} + +static void xtopt_parse_hostmask(struct xt_option_call *cb) +{ + const char *orig_arg = cb->arg; + char *work, *p; + + if (strchr(cb->arg, '/') == NULL) { + xtopt_parse_host(cb); + return; + } + work = strdup(orig_arg); + if (work == NULL) + xt_params->exit_err(PARAMETER_PROBLEM, "strdup"); + p = strchr(work, '/'); /* by def this can't be NULL now */ + *p++ = '\0'; + /* + * Because xtopt_parse_host and xtopt_parse_plenmask would store + * different things in the same target area, XTTYPE_HOSTMASK must + * disallow XTOPT_PUT, which it does by forcing its absence, + * cf. not being listed in xtopt_psize. + */ + cb->arg = work; + xtopt_parse_host(cb); + cb->arg = p; + xtopt_parse_plenmask(cb); + cb->arg = orig_arg; +} + +static void xtopt_parse_ethermac(struct xt_option_call *cb) +{ + const char *arg = cb->arg; + unsigned int i; + char *end; + + for (i = 0; i < ARRAY_SIZE(cb->val.ethermac) - 1; ++i) { + cb->val.ethermac[i] = strtoul(arg, &end, 16); + if (cb->val.ethermac[i] > UINT8_MAX || *end != ':') + goto out; + arg = end + 1; + } + i = ARRAY_SIZE(cb->val.ethermac) - 1; + cb->val.ethermac[i] = strtoul(arg, &end, 16); + if (cb->val.ethermac[i] > UINT8_MAX || *end != '\0') + goto out; + if (cb->entry->flags & XTOPT_PUT) + memcpy(XTOPT_MKPTR(cb), cb->val.ethermac, + sizeof(cb->val.ethermac)); + return; + out: + xt_params->exit_err(PARAMETER_PROBLEM, "ether"); +} + +static void (*const xtopt_subparse[])(struct xt_option_call *) = { + [XTTYPE_UINT8] = xtopt_parse_int, + [XTTYPE_UINT16] = xtopt_parse_int, + [XTTYPE_UINT32] = xtopt_parse_int, + [XTTYPE_UINT64] = xtopt_parse_int, + [XTTYPE_UINT8RC] = xtopt_parse_mint, + [XTTYPE_UINT16RC] = xtopt_parse_mint, + [XTTYPE_UINT32RC] = xtopt_parse_mint, + [XTTYPE_UINT64RC] = xtopt_parse_mint, + [XTTYPE_DOUBLE] = xtopt_parse_float, + [XTTYPE_STRING] = xtopt_parse_string, + [XTTYPE_TOSMASK] = xtopt_parse_tosmask, + [XTTYPE_MARKMASK32] = xtopt_parse_markmask, + [XTTYPE_SYSLOGLEVEL] = xtopt_parse_sysloglevel, + [XTTYPE_HOST] = xtopt_parse_host, + [XTTYPE_HOSTMASK] = xtopt_parse_hostmask, + [XTTYPE_PROTOCOL] = xtopt_parse_protocol, + [XTTYPE_PORT] = xtopt_parse_port, + [XTTYPE_PORTRC] = xtopt_parse_mport, + [XTTYPE_PLEN] = xtopt_parse_plen, + [XTTYPE_PLENMASK] = xtopt_parse_plenmask, + [XTTYPE_ETHERMAC] = xtopt_parse_ethermac, +}; + +/** + * The master option parsing routine. May be used for the ".x6_parse" + * function pointer in extensions if fully automatic parsing is desired. + * It may be also called manually from a custom x6_parse function. + */ +void xtables_option_parse(struct xt_option_call *cb) +{ + const struct xt_option_entry *entry = cb->entry; + unsigned int eflag = 1 << cb->entry->id; + + /* + * With {.id = P_FOO, .excl = P_FOO} we can have simple double-use + * prevention. Though it turned out that this is too much typing (most + * of the options are one-time use only), so now we also have + * %XTOPT_MULTI. + */ + if ((!(entry->flags & XTOPT_MULTI) || (entry->excl & eflag)) && + cb->xflags & eflag) + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: option \"--%s\" can only be used once.\n", + cb->ext_name, cb->entry->name); + if (cb->invert && !(entry->flags & XTOPT_INVERT)) + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: option \"--%s\" cannot be inverted.\n", + cb->ext_name, entry->name); + if (entry->type != XTTYPE_NONE && optarg == NULL) + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: option \"--%s\" requires an argument.\n", + cb->ext_name, entry->name); + if (entry->type <= ARRAY_SIZE(xtopt_subparse) && + xtopt_subparse[entry->type] != NULL) + xtopt_subparse[entry->type](cb); + /* Exclusion with other flags tested later in finalize. */ + cb->xflags |= 1 << entry->id; +} + +/** + * Verifies that an extension's option map descriptor is valid, and ought to + * be called right after the extension has been loaded, and before option + * merging/xfrm. + */ +void xtables_option_metavalidate(const char *name, + const struct xt_option_entry *entry) +{ + for (; entry->name != NULL; ++entry) { + if (entry->id >= CHAR_BIT * sizeof(unsigned int) || + entry->id >= XT_OPTION_OFFSET_SCALE) + xt_params->exit_err(OTHER_PROBLEM, + "Extension %s uses invalid ID %u\n", + name, entry->id); + if (!(entry->flags & XTOPT_PUT)) + continue; + if (entry->type >= ARRAY_SIZE(xtopt_psize) || + xtopt_psize[entry->type] == 0) + xt_params->exit_err(OTHER_PROBLEM, + "%s: entry type of option \"--%s\" cannot be " + "combined with XTOPT_PUT\n", + name, entry->name); + if (xtopt_psize[entry->type] != -1 && + xtopt_psize[entry->type] != entry->size) + xt_params->exit_err(OTHER_PROBLEM, + "%s: option \"--%s\" points to a memory block " + "of wrong size (expected %zu, got %zu)\n", + name, entry->name, + xtopt_psize[entry->type], entry->size); + } +} + +/** + * Find an option entry by its id. + */ +static const struct xt_option_entry * +xtables_option_lookup(const struct xt_option_entry *entry, unsigned int id) +{ + for (; entry->name != NULL; ++entry) + if (entry->id == id) + return entry; + return NULL; +} + +/** + * @c: getopt id (i.e. with offset) + * @fw: struct ipt_entry or ip6t_entry + * + * Dispatch arguments to the appropriate parse function, based upon the + * extension's choice of API. + */ +void xtables_option_tpcall(unsigned int c, char **argv, bool invert, + struct xtables_target *t, void *fw) +{ + struct xt_option_call cb; + + if (t->x6_parse == NULL) { + if (t->parse != NULL) + t->parse(c - t->option_offset, argv, invert, + &t->tflags, fw, &t->t); + return; + } + + c -= t->option_offset; + cb.entry = xtables_option_lookup(t->x6_options, c); + if (cb.entry == NULL) + xtables_error(OTHER_PROBLEM, + "Extension does not know id %u\n", c); + cb.arg = optarg; + cb.invert = invert; + cb.ext_name = t->name; + cb.data = t->t->data; + cb.xflags = t->tflags; + cb.target = &t->t; + cb.xt_entry = fw; + t->x6_parse(&cb); + t->tflags = cb.xflags; +} + +/** + * @c: getopt id (i.e. with offset) + * @fw: struct ipt_entry or ip6t_entry + * + * Dispatch arguments to the appropriate parse function, based upon the + * extension's choice of API. + */ +void xtables_option_mpcall(unsigned int c, char **argv, bool invert, + struct xtables_match *m, void *fw) +{ + struct xt_option_call cb; + + if (m->x6_parse == NULL) { + if (m->parse != NULL) + m->parse(c - m->option_offset, argv, invert, + &m->mflags, fw, &m->m); + return; + } + + c -= m->option_offset; + cb.entry = xtables_option_lookup(m->x6_options, c); + if (cb.entry == NULL) + xtables_error(OTHER_PROBLEM, + "Extension does not know id %u\n", c); + cb.arg = optarg; + cb.invert = invert; + cb.ext_name = m->name; + cb.data = m->m->data; + cb.xflags = m->mflags; + cb.match = &m->m; + cb.xt_entry = fw; + m->x6_parse(&cb); + m->mflags = cb.xflags; +} + +/** + * @name: name of extension + * @entry: current option (from all ext's entries) being validated + * @xflags: flags the extension has collected + * @i: conflicting option (id) to test for + */ +static void +xtables_option_fcheck2(const char *name, const struct xt_option_entry *entry, + const struct xt_option_entry *other, + unsigned int xflags) +{ + unsigned int ef = 1 << entry->id, of = 1 << other->id; + + if (entry->also & of && !(xflags & of)) + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: option \"--%s\" also requires \"--%s\".\n", + name, entry->name, other->name); + + if (!(entry->excl & of)) + /* Use of entry does not collide with other option, good. */ + return; + if ((xflags & (ef | of)) != (ef | of)) + /* Conflicting options were not used. */ + return; + + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: option \"--%s\" cannot be used together with \"--%s\".\n", + name, entry->name, other->name); +} + +/** + * @name: name of extension + * @xflags: accumulated flags + * @entry: extension's option table + * + * Check that all option constraints have been met. This effectively replaces + * ->final_check of the older API. + */ +void xtables_options_fcheck(const char *name, unsigned int xflags, + const struct xt_option_entry *table) +{ + const struct xt_option_entry *entry, *other; + unsigned int i; + + for (entry = table; entry->name != NULL; ++entry) { + if (entry->flags & XTOPT_MAND && + !(xflags & (1 << entry->id))) + xt_params->exit_err(PARAMETER_PROBLEM, + "%s: option \"--%s\" must be specified\n", + name, entry->name); + if (!(xflags & (1 << entry->id))) + /* Not required, not specified, thus skip. */ + continue; + + for (i = 0; i < CHAR_BIT * sizeof(entry->id); ++i) { + if (entry->id == i) + /* + * Avoid conflict with self. Multi-use check + * was done earlier in xtables_option_parse. + */ + continue; + other = xtables_option_lookup(table, i); + if (other == NULL) + continue; + xtables_option_fcheck2(name, entry, other, xflags); + } + } +} + +/** + * Dispatch arguments to the appropriate final_check function, based upon the + * extension's choice of API. + */ +void xtables_option_tfcall(struct xtables_target *t) +{ + if (t->x6_fcheck != NULL) { + struct xt_fcheck_call cb; + + cb.ext_name = t->name; + cb.data = t->t->data; + cb.xflags = t->tflags; + t->x6_fcheck(&cb); + } else if (t->final_check != NULL) { + t->final_check(t->tflags); + } + if (t->x6_options != NULL) + xtables_options_fcheck(t->name, t->tflags, t->x6_options); +} + +/** + * Dispatch arguments to the appropriate final_check function, based upon the + * extension's choice of API. + */ +void xtables_option_mfcall(struct xtables_match *m) +{ + if (m->x6_fcheck != NULL) { + struct xt_fcheck_call cb; + + cb.ext_name = m->name; + cb.data = m->m->data; + cb.xflags = m->mflags; + m->x6_fcheck(&cb); + } else if (m->final_check != NULL) { + m->final_check(m->mflags); + } + if (m->x6_options != NULL) + xtables_options_fcheck(m->name, m->mflags, m->x6_options); +} + +struct xtables_lmap *xtables_lmap_init(const char *file) +{ + struct xtables_lmap *lmap_head = NULL, *lmap_prev = NULL, *lmap_this; + char buf[512]; + FILE *fp; + char *cur, *nxt; + int id; + + fp = fopen(file, "re"); + if (fp == NULL) + return NULL; + + while (fgets(buf, sizeof(buf), fp) != NULL) { + cur = buf; + while (isspace(*cur)) + ++cur; + if (*cur == '#' || *cur == '\n' || *cur == '\0') + continue; + + /* iproute2 allows hex and dec format */ + errno = 0; + id = strtoul(cur, &nxt, strncmp(cur, "0x", 2) == 0 ? 16 : 10); + if (nxt == cur || errno != 0) + continue; + + /* same boundaries as in iproute2 */ + if (id < 0 || id > 255) + continue; + cur = nxt; + + if (!isspace(*cur)) + continue; + while (isspace(*cur)) + ++cur; + if (*cur == '#' || *cur == '\n' || *cur == '\0') + continue; + nxt = cur; + while (*nxt != '\0' && !isspace(*nxt)) + ++nxt; + if (nxt == cur) + continue; + *nxt = '\0'; + + /* found valid data */ + lmap_this = malloc(sizeof(*lmap_this)); + if (lmap_this == NULL) { + perror("malloc"); + goto out; + } + lmap_this->id = id; + lmap_this->name = strdup(cur); + if (lmap_this->name == NULL) { + free(lmap_this); + goto out; + } + lmap_this->next = NULL; + + if (lmap_prev != NULL) + lmap_prev->next = lmap_this; + else + lmap_head = lmap_this; + lmap_prev = lmap_this; + } + + fclose(fp); + return lmap_head; + out: + xtables_lmap_free(lmap_head); + return NULL; +} + +void xtables_lmap_free(struct xtables_lmap *head) +{ + struct xtables_lmap *next; + + for (; head != NULL; head = next) { + next = head->next; + free(head->name); + free(head); + } +} + +int xtables_lmap_name2id(const struct xtables_lmap *head, const char *name) +{ + for (; head != NULL; head = head->next) + if (strcmp(head->name, name) == 0) + return head->id; + return -1; +} + +const char *xtables_lmap_id2name(const struct xtables_lmap *head, int id) +{ + for (; head != NULL; head = head->next) + if (head->id == id) + return head->name; + return NULL; +} -- cgit v1.2.3