From 72d9820b339aaeca54327f252e7443d37c4846d4 Mon Sep 17 00:00:00 2001 From: Mark Huang Date: Fri, 29 Oct 2004 15:06:42 +0000 Subject: [PATCH] iptables-1.2.9-2.3.1.src.rpm --- .#iptables.c.1.54 | 2298 ++++++++++++++++++++++++++ .#iptables.c.1.55 | 2303 +++++++++++++++++++++++++++ Makefile.nolibnsl | 208 +++ extensions/.#libipt_ECN.c.1.7 | 180 +++ extensions/.#libipt_recent.c.1.6 | 229 +++ extensions/.addrtype-test | 5 + extensions/.pool-test | 2 + extensions/libipt_POOL.c | 155 ++ extensions/libipt_icmp.c.print_type | 310 ++++ extensions/libipt_pool.c | 144 ++ extensions/libipt_pool.c.orig | 144 ++ include/libippool/ip_pool_support.h | 26 + ip6tables.8 | 821 ++++++++++ ip6tables.c.selinux | 2300 ++++++++++++++++++++++++++ ippool/Makefile | 18 + ippool/ippool.c | 589 +++++++ ippool/libippool.c | 72 + iptables-restore.c.counters | 384 +++++ iptables.8 | 1072 +++++++++++++ iptables.c.selinux | 2298 ++++++++++++++++++++++++++ libipq/libipq.c.netlink | 373 +++++ libiptc/libip6tc.c.orig | 449 ++++++ libiptc2/.#libiptc.c.1.40 | 1972 +++++++++++++++++++++++ libiptc2/foo.diff | 391 +++++ libiptc2/libip4tc.c | 507 ++++++ libiptc2/libip6tc.c | 449 ++++++ libiptc2/libiptc.c | 1830 +++++++++++++++++++++ libiptc2/libiptc.cvs.c | 1920 ++++++++++++++++++++++ libiptc2/libiptc2.c | 1758 ++++++++++++++++++++ libiptc2/linux_list.h | 161 ++ libiptc2/linux_listhelp.h | 99 ++ 31 files changed, 23467 insertions(+) create mode 100644 .#iptables.c.1.54 create mode 100644 .#iptables.c.1.55 create mode 100644 Makefile.nolibnsl create mode 100644 extensions/.#libipt_ECN.c.1.7 create mode 100644 extensions/.#libipt_recent.c.1.6 create mode 100755 extensions/.addrtype-test create mode 100755 extensions/.pool-test create mode 100644 extensions/libipt_POOL.c create mode 100644 extensions/libipt_icmp.c.print_type create mode 100644 extensions/libipt_pool.c create mode 100644 extensions/libipt_pool.c.orig create mode 100644 include/libippool/ip_pool_support.h create mode 100644 ip6tables.8 create mode 100644 ip6tables.c.selinux create mode 100644 ippool/Makefile create mode 100644 ippool/ippool.c create mode 100644 ippool/libippool.c create mode 100644 iptables-restore.c.counters create mode 100644 iptables.8 create mode 100644 iptables.c.selinux create mode 100644 libipq/libipq.c.netlink create mode 100644 libiptc/libip6tc.c.orig create mode 100644 libiptc2/.#libiptc.c.1.40 create mode 100644 libiptc2/foo.diff create mode 100644 libiptc2/libip4tc.c create mode 100644 libiptc2/libip6tc.c create mode 100644 libiptc2/libiptc.c create mode 100644 libiptc2/libiptc.cvs.c create mode 100644 libiptc2/libiptc2.c create mode 100644 libiptc2/linux_list.h create mode 100644 libiptc2/linux_listhelp.h diff --git a/.#iptables.c.1.54 b/.#iptables.c.1.54 new file mode 100644 index 0000000..a838e56 --- /dev/null +++ b/.#iptables.c.1.54 @@ -0,0 +1,2298 @@ +/* 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 + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef IPT_LIB_DIR +#define IPT_LIB_DIR "/usr/lib/iptables" +#endif + +#ifndef PROC_SYS_MODPROBE +#define PROC_SYS_MODPROBE "/proc/sys/kernel/modprobe" +#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_CHECK 0x0800U +#define CMD_RENAME_CHAIN 0x1000U +#define NUMBER_OF_CMD 13 +static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z', + 'N', 'X', 'P', 'E' }; + +#define OPTION_OFFSET 256 + +#define OPT_NONE 0x00000U +#define OPT_NUMERIC 0x00001U +#define OPT_SOURCE 0x00002U +#define OPT_DESTINATION 0x00004U +#define OPT_PROTOCOL 0x00008U +#define OPT_JUMP 0x00010U +#define OPT_VERBOSE 0x00020U +#define OPT_EXPANDED 0x00040U +#define OPT_VIANAMEIN 0x00080U +#define OPT_VIANAMEOUT 0x00100U +#define OPT_FRAGMENT 0x00200U +#define OPT_LINENUMBERS 0x00400U +#define OPT_COUNTERS 0x00800U +#define NUMBER_OF_OPT 12 +static const char optflags[NUMBER_OF_OPT] += { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', 'f', '3', 'c'}; + +static struct option original_opts[] = { + { "append", 1, 0, 'A' }, + { "delete", 1, 0, 'D' }, + { "insert", 1, 0, 'I' }, + { "replace", 1, 0, 'R' }, + { "list", 2, 0, 'L' }, + { "flush", 2, 0, 'F' }, + { "zero", 2, 0, 'Z' }, + { "new-chain", 1, 0, 'N' }, + { "delete-chain", 2, 0, 'X' }, + { "rename-chain", 1, 0, 'E' }, + { "policy", 1, 0, 'P' }, + { "source", 1, 0, 's' }, + { "destination", 1, 0, 'd' }, + { "src", 1, 0, 's' }, /* synonym */ + { "dst", 1, 0, 'd' }, /* synonym */ + { "protocol", 1, 0, 'p' }, + { "in-interface", 1, 0, 'i' }, + { "jump", 1, 0, 'j' }, + { "table", 1, 0, 't' }, + { "match", 1, 0, 'm' }, + { "numeric", 0, 0, 'n' }, + { "out-interface", 1, 0, 'o' }, + { "verbose", 0, 0, 'v' }, + { "exact", 0, 0, 'x' }, + { "fragments", 0, 0, 'f' }, + { "version", 0, 0, 'V' }, + { "help", 2, 0, 'h' }, + { "line-numbers", 0, 0, '0' }, + { "modprobe", 1, 0, 'M' }, + { "set-counters", 1, 0, 'c' }, + { 0 } +}; + +/* we need this for iptables-restore. iptables-restore.c sets line to the + * current line of the input file, in order to give a more precise error + * message. iptables itself doesn't need this, so it is initialized to the + * magic number of -1 */ +int line = -1; + +#ifndef __OPTIMIZE__ +struct ipt_entry_target * +ipt_get_target(struct ipt_entry *e) +{ + return (void *)e + e->target_offset; +} +#endif + +static struct option *opts = original_opts; +static unsigned int global_option_offset = 0; + +/* 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 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 -f --line */ +/*INSERT*/ {'x',' ',' ',' ',' ',' ','x',' ',' ',' ','x'}, +/*DELETE*/ {'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'}, +/*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','x'}, +/*CHECK*/ {'x','+','+','+','x',' ','x',' ',' ',' ','x'}, +/*RENAME*/ {'x','x','x','x','x',' ','x','x','x','x','x'} +}; + +static 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 +}; + +const char *program_version; +const char *program_name; + +/* Keeping track of external matches and targets: linked lists. */ +struct iptables_match *iptables_matches = NULL; +struct iptables_target *iptables_targets = NULL; + +/* Extra debugging from libiptc */ +extern void dump_entries(const iptc_handle_t handle); + +/* A few hardcoded protocols for 'all' and in case the user has no + /etc/protocols */ +struct pprot { + char *name; + u_int8_t num; +}; + +/* 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 + +static const struct pprot chain_protos[] = { + { "tcp", IPPROTO_TCP }, + { "udp", IPPROTO_UDP }, + { "icmp", IPPROTO_ICMP }, + { "esp", IPPROTO_ESP }, + { "ah", IPPROTO_AH }, + { "all", 0 }, +}; + +static char * +proto_to_name(u_int8_t proto, int nolookup) +{ + unsigned int i; + + if (proto && !nolookup) { + struct protoent *pent = getprotobynumber(proto); + if (pent) + return pent->p_name; + } + + for (i = 0; i < sizeof(chain_protos)/sizeof(struct pprot); i++) + if (chain_protos[i].num == proto) + return chain_protos[i].name; + + return NULL; +} + +struct in_addr * +dotted_to_addr(const char *dotted) +{ + static struct in_addr addr; + unsigned char *addrp; + char *p, *q; + unsigned int onebyte; + int i; + char buf[20]; + + /* copy dotted string, because we need to modify it */ + strncpy(buf, dotted, sizeof(buf) - 1); + addrp = (unsigned char *) &(addr.s_addr); + + p = buf; + for (i = 0; i < 3; i++) { + if ((q = strchr(p, '.')) == NULL) + return (struct in_addr *) NULL; + + *q = '\0'; + if (string_to_number(p, 0, 255, &onebyte) == -1) + return (struct in_addr *) NULL; + + addrp[i] = (unsigned char) onebyte; + p = q + 1; + } + + /* we've checked 3 bytes, now we check the last one */ + if (string_to_number(p, 0, 255, &onebyte) == -1) + return (struct in_addr *) NULL; + + addrp[3] = (unsigned char) onebyte; + + return &addr; +} + +static struct in_addr * +network_to_addr(const char *name) +{ + struct netent *net; + static struct in_addr addr; + + if ((net = getnetbyname(name)) != NULL) { + if (net->n_addrtype != AF_INET) + return (struct in_addr *) NULL; + addr.s_addr = htonl((unsigned long) net->n_net); + return &addr; + } + + return (struct in_addr *) NULL; +} + +static void +inaddrcpy(struct in_addr *dst, struct in_addr *src) +{ + /* memcpy(dst, src, sizeof(struct in_addr)); */ + dst->s_addr = src->s_addr; +} + +void +exit_error(enum exittype status, char *msg, ...) +{ + va_list args; + + va_start(args, msg); + fprintf(stderr, "%s v%s: ", program_name, program_version); + 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"); + exit(status); +} + +void +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", + program_name, program_name ); + exit(status); +} + +void +exit_printhelp(void) +{ + struct iptables_match *m = NULL; + struct iptables_target *t = NULL; + + printf("%s v%s\n\n" +"Usage: %s -[AD] chain rule-specification [options]\n" +" %s -[RI] chain rulenum rule-specification [options]\n" +" %s -D chain rulenum [options]\n" +" %s -[LFZ] [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", + program_name, program_version, program_name, program_name, + program_name, program_name, program_name, program_name, + program_name, program_name); + + printf( +"Commands:\n" +"Either long or short options are allowed.\n" +" --append -A chain Append to chain\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] List the rules in a chain or all chains\n" +" --flush -F [chain] Delete all rules in chain or all chains\n" +" --zero -Z [chain] 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" +" --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" +" --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 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 matches & targets */ + for (t=iptables_targets;t;t=t->next) { + printf("\n"); + t->help(); + } + for (m=iptables_matches;m;m=m->next) { + printf("\n"); + m->help(); + } + exit(0); +} + +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(int *cmd, const int newcmd, const int othercmds, int invert) +{ + if (invert) + exit_error(PARAMETER_PROBLEM, "unexpected ! flag"); + if (*cmd & (~othercmds)) + exit_error(PARAMETER_PROBLEM, "Can't use -%c with -%c\n", + cmd2char(newcmd), cmd2char(*cmd & (~othercmds))); + *cmd |= newcmd; +} + +int +check_inverse(const char option[], int *invert, int *optind, int argc) +{ + if (option && strcmp(option, "!") == 0) { + if (*invert) + exit_error(PARAMETER_PROBLEM, + "Multiple `!' flags not allowed"); + *invert = TRUE; + if (optind) { + *optind = *optind+1; + if (argc && *optind > argc) + exit_error(PARAMETER_PROBLEM, + "no argument following `!'"); + } + + return TRUE; + } + return FALSE; +} + +static void * +fw_calloc(size_t count, size_t size) +{ + void *p; + + if ((p = calloc(count, size)) == NULL) { + perror("iptables: calloc failed"); + exit(1); + } + return p; +} + +static void * +fw_malloc(size_t size) +{ + void *p; + + if ((p = malloc(size)) == NULL) { + perror("iptables: malloc failed"); + exit(1); + } + return p; +} + +static struct in_addr * +host_to_addr(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 (struct in_addr *) NULL; + + while (host->h_addr_list[*naddr] != (char *) NULL) + (*naddr)++; + addr = fw_calloc(*naddr, sizeof(struct in_addr)); + for (i = 0; i < *naddr; i++) + inaddrcpy(&(addr[i]), + (struct in_addr *) host->h_addr_list[i]); + return addr; + } + + return (struct in_addr *) NULL; +} + +static char * +addr_to_host(const struct in_addr *addr) +{ + struct hostent *host; + + if ((host = gethostbyaddr((char *) addr, + sizeof(struct in_addr), AF_INET)) != NULL) + return (char *) host->h_name; + + return (char *) NULL; +} + +/* + * 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. +*/ + +static struct in_addr * +parse_hostnetwork(const char *name, unsigned int *naddrs) +{ + struct in_addr *addrp, *addrptmp; + + if ((addrptmp = dotted_to_addr(name)) != NULL || + (addrptmp = network_to_addr(name)) != NULL) { + addrp = fw_malloc(sizeof(struct in_addr)); + inaddrcpy(addrp, addrptmp); + *naddrs = 1; + return addrp; + } + if ((addrp = host_to_addr(name, naddrs)) != NULL) + return addrp; + + exit_error(PARAMETER_PROBLEM, "host/network `%s' not found", name); +} + +static struct in_addr * +parse_mask(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 = dotted_to_addr(mask)) != NULL) + /* dotted_to_addr already returns a network byte order addr */ + return addrp; + if (string_to_number(mask, 0, 32, &bits) == -1) + exit_error(PARAMETER_PROBLEM, + "invalid mask `%s' specified", mask); + if (bits != 0) { + maskaddr.s_addr = htonl(0xFFFFFFFF << (32 - bits)); + return &maskaddr; + } + + maskaddr.s_addr = 0L; + return &maskaddr; +} + +void +parse_hostnetworkmask(const char *name, struct in_addr **addrpp, + struct in_addr *maskp, unsigned int *naddrs) +{ + struct in_addr *addrp; + char buf[256]; + char *p; + int i, j, k, n; + + strncpy(buf, name, sizeof(buf) - 1); + if ((p = strrchr(buf, '/')) != NULL) { + *p = '\0'; + addrp = parse_mask(p + 1); + } else + addrp = parse_mask(NULL); + inaddrcpy(maskp, addrp); + + /* if a null mask is given, the name is ignored, like in "any/0" */ + if (maskp->s_addr == 0L) + strcpy(buf, "0.0.0.0"); + + addrp = *addrpp = parse_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) { + (*naddrs)--; + j--; + break; + } + } + } +} + +struct iptables_match * +find_match(const char *name, enum ipt_tryload tryload) +{ + struct iptables_match *ptr; + + for (ptr = iptables_matches; ptr; ptr = ptr->next) { + if (strcmp(name, ptr->name) == 0) + break; + } + +#ifndef NO_SHARED_LIBS + if (!ptr && tryload != DONT_LOAD) { + char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so") + + strlen(name)]; + sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name); + if (dlopen(path, RTLD_NOW)) { + /* Found library. If it didn't register itself, + maybe they specified target as match. */ + ptr = find_match(name, DONT_LOAD); + + if (!ptr) + exit_error(PARAMETER_PROBLEM, + "Couldn't load match `%s'\n", + name); + } else if (tryload == LOAD_MUST_SUCCEED) + exit_error(PARAMETER_PROBLEM, + "Couldn't load match `%s':%s\n", + name, dlerror()); + } +#else + if (ptr && !ptr->loaded) { + if (tryload != DONT_LOAD) + ptr->loaded = 1; + else + ptr = NULL; + } + if(!ptr && (tryload == LOAD_MUST_SUCCEED)) { + exit_error(PARAMETER_PROBLEM, + "Couldn't find match `%s'\n", name); + } +#endif + + if (ptr) + ptr->used = 1; + + return ptr; +} + +/* Christophe Burki wants `-p 6' to imply `-m tcp'. */ +static struct iptables_match * +find_proto(const char *pname, enum ipt_tryload tryload, int nolookup) +{ + unsigned int proto; + + if (string_to_number(pname, 0, 255, &proto) != -1) { + char *protoname = proto_to_name(proto, nolookup); + + if (protoname) + return find_match(protoname, tryload); + } else + return find_match(pname, tryload); + + return NULL; +} + +u_int16_t +parse_protocol(const char *s) +{ + unsigned int proto; + + if (string_to_number(s, 0, 255, &proto) == -1) { + struct protoent *pent; + + if ((pent = getprotobyname(s))) + proto = pent->p_proto; + else { + unsigned int i; + for (i = 0; + i < sizeof(chain_protos)/sizeof(struct pprot); + i++) { + if (strcmp(s, chain_protos[i].name) == 0) { + proto = chain_protos[i].num; + break; + } + } + if (i == sizeof(chain_protos)/sizeof(struct pprot)) + exit_error(PARAMETER_PROBLEM, + "unknown protocol `%s' specified", + s); + } + } + + return (u_int16_t)proto; +} + +static void +parse_interface(const char *arg, char *vianame, unsigned char *mask) +{ + int vialen = strlen(arg); + unsigned int i; + + memset(mask, 0, IFNAMSIZ); + memset(vianame, 0, IFNAMSIZ); + + if (vialen + 1 > IFNAMSIZ) + exit_error(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 (!isalnum(vianame[i]) + && vianame[i] != '_' + && vianame[i] != '.') { + printf("Warning: wierd character in interface" + " `%s' (No aliases, :, ! or *).\n", + vianame); + break; + } + } + } +} + +/* Can't be zero. */ +static int +parse_rulenumber(const char *rule) +{ + unsigned int rulenum; + + if (string_to_number(rule, 1, INT_MAX, &rulenum) == -1) + exit_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) + exit_error(PARAMETER_PROBLEM, + "Invalid target name (too short)"); + + if (strlen(targetname)+1 > sizeof(ipt_chainlabel)) + exit_error(PARAMETER_PROBLEM, + "Invalid target name `%s' (%i chars max)", + targetname, sizeof(ipt_chainlabel)-1); + + for (ptr = targetname; *ptr; ptr++) + if (isspace(*ptr)) + exit_error(PARAMETER_PROBLEM, + "Invalid target name `%s'", targetname); + return targetname; +} + +static char * +addr_to_network(const struct in_addr *addr) +{ + struct netent *net; + + if ((net = getnetbyaddr((long) ntohl(addr->s_addr), AF_INET)) != NULL) + return (char *) net->n_name; + + return (char *) NULL; +} + +char * +addr_to_dotted(const struct in_addr *addrp) +{ + static char buf[20]; + const unsigned char *bytep; + + bytep = (const unsigned char *) &(addrp->s_addr); + sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]); + return buf; +} + +char * +addr_to_anyname(const struct in_addr *addr) +{ + char *name; + + if ((name = addr_to_host(addr)) != NULL || + (name = addr_to_network(addr)) != NULL) + return name; + + return addr_to_dotted(addr); +} + +char * +mask_to_dotted(const struct in_addr *mask) +{ + int i; + static char buf[20]; + u_int32_t maskaddr, bits; + + 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", addr_to_dotted(mask)); + + return buf; +} + +int +string_to_number(const char *s, unsigned int min, unsigned int max, + unsigned int *ret) +{ + long number; + char *end; + + /* Handle hex, octal, etc. */ + errno = 0; + number = strtol(s, &end, 0); + if (*end == '\0' && end != s) { + /* we parsed a number, let's see if we want this */ + if (errno != ERANGE && min <= number && number <= max) { + *ret = number; + return 0; + } + } + return -1; +} + +static void +set_option(unsigned int *options, unsigned int option, u_int8_t *invflg, + int invert) +{ + if (*options & option) + exit_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]) + exit_error(PARAMETER_PROBLEM, + "cannot have ! before -%c", + opt2char(option)); + *invflg |= inverse_for_options[i]; + } +} + +struct iptables_target * +find_target(const char *name, enum ipt_tryload tryload) +{ + struct iptables_target *ptr; + + /* Standard target? */ + if (strcmp(name, "") == 0 + || strcmp(name, IPTC_LABEL_ACCEPT) == 0 + || strcmp(name, IPTC_LABEL_DROP) == 0 + || strcmp(name, IPTC_LABEL_QUEUE) == 0 + || strcmp(name, IPTC_LABEL_RETURN) == 0) + name = "standard"; + + for (ptr = iptables_targets; ptr; ptr = ptr->next) { + if (strcmp(name, ptr->name) == 0) + break; + } + +#ifndef NO_SHARED_LIBS + if (!ptr && tryload != DONT_LOAD) { + char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so") + + strlen(name)]; + sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name); + if (dlopen(path, RTLD_NOW)) { + /* Found library. If it didn't register itself, + maybe they specified match as a target. */ + ptr = find_target(name, DONT_LOAD); + if (!ptr) + exit_error(PARAMETER_PROBLEM, + "Couldn't load target `%s'\n", + name); + } else if (tryload == LOAD_MUST_SUCCEED) + exit_error(PARAMETER_PROBLEM, + "Couldn't load target `%s':%s\n", + name, dlerror()); + } +#else + if (ptr && !ptr->loaded) { + if (tryload != DONT_LOAD) + ptr->loaded = 1; + else + ptr = NULL; + } + if(!ptr && (tryload == LOAD_MUST_SUCCEED)) { + exit_error(PARAMETER_PROBLEM, + "Couldn't find target `%s'\n", name); + } +#endif + + if (ptr) + ptr->used = 1; + + return ptr; +} + +static struct option * +merge_options(struct option *oldopts, const struct option *newopts, + unsigned int *option_offset) +{ + unsigned int num_old, num_new, i; + struct option *merge; + + for (num_old = 0; oldopts[num_old].name; num_old++); + for (num_new = 0; newopts[num_new].name; num_new++); + + global_option_offset += OPTION_OFFSET; + *option_offset = global_option_offset; + + merge = malloc(sizeof(struct option) * (num_new + num_old + 1)); + memcpy(merge, oldopts, num_old * sizeof(struct option)); + for (i = 0; i < num_new; i++) { + merge[num_old + i] = newopts[i]; + merge[num_old + i].val += *option_offset; + } + memset(merge + num_old + num_new, 0, sizeof(struct option)); + + return merge; +} + +void +register_match(struct iptables_match *me) +{ + struct iptables_match **i; + + if (strcmp(me->version, program_version) != 0) { + fprintf(stderr, "%s: match `%s' v%s (I'm v%s).\n", + program_name, me->name, me->version, program_version); + exit(1); + } + + if (find_match(me->name, DONT_LOAD)) { + fprintf(stderr, "%s: match `%s' already registered.\n", + program_name, me->name); + exit(1); + } + + if (me->size != IPT_ALIGN(me->size)) { + fprintf(stderr, "%s: match `%s' has invalid size %u.\n", + program_name, me->name, me->size); + exit(1); + } + + /* Append to list. */ + for (i = &iptables_matches; *i; i = &(*i)->next); + me->next = NULL; + *i = me; + + me->m = NULL; + me->mflags = 0; +} + +void +register_target(struct iptables_target *me) +{ + if (strcmp(me->version, program_version) != 0) { + fprintf(stderr, "%s: target `%s' v%s (I'm v%s).\n", + program_name, me->name, me->version, program_version); + exit(1); + } + + if (find_target(me->name, DONT_LOAD)) { + fprintf(stderr, "%s: target `%s' already registered.\n", + program_name, me->name); + exit(1); + } + + if (me->size != IPT_ALIGN(me->size)) { + fprintf(stderr, "%s: target `%s' has invalid size %u.\n", + program_name, me->name, me->size); + exit(1); + } + + /* Prepend to list. */ + me->next = iptables_targets; + iptables_targets = me; + me->t = NULL; + me->tflags = 0; +} + +static void +print_num(u_int64_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 "), number); + } + else printf(FMT("%4lluG ","%lluG "), number); + } + else printf(FMT("%4lluM ","%lluM "), number); + } else + printf(FMT("%4lluK ","%lluK "), number); + } else + printf(FMT("%5llu ","%llu "), number); + } else + printf(FMT("%8llu ","%llu "), number); +} + + +static void +print_header(unsigned int format, const char *chain, iptc_handle_t *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) +{ + struct iptables_match *match = find_match(m->u.user.name, TRY_LOAD); + + 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 hysterical raisins */ +static void +print_firewall(const struct ipt_entry *fw, + const char *targname, + unsigned int num, + unsigned int format, + const iptc_handle_t handle) +{ + struct iptables_target *target = NULL; + const struct ipt_entry_target *t; + u_int8_t flags; + char buf[BUFSIZ]; + + if (!iptc_is_chain(targname, handle)) + target = find_target(targname, TRY_LOAD); + else + target = find_target(IPT_STANDARD_TARGET, LOAD_MUST_SUCCEED); + + t = ipt_get_target((struct ipt_entry *)fw); + flags = fw->ip.flags; + + if (format & FMT_LINENUMBERS) + printf(FMT("%-4u ", "%u "), num+1); + + 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); + { + 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) + sprintf(buf, "%s", addr_to_dotted(&(fw->ip.src))); + else + sprintf(buf, "%s", addr_to_anyname(&(fw->ip.src))); + strcat(buf, mask_to_dotted(&(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) + sprintf(buf, "%s", addr_to_dotted(&(fw->ip.dst))); + else + sprintf(buf, "%s", addr_to_anyname(&(fw->ip.dst))); + strcat(buf, mask_to_dotted(&(fw->ip.dmsk))); + printf(FMT("%-19s","-> %s"), buf); + } + + if (format & FMT_NOTABLE) + fputs(" ", stdout); + + 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] ", + t->u.target_size - sizeof(*t)); + + if (!(format & FMT_NONEWLINE)) + fputc('\n', stdout); +} + +static void +print_firewall_line(const struct ipt_entry *fw, + const iptc_handle_t 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[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + int verbose, + iptc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[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 *daddr, + int verbose, + iptc_handle_t *handle) +{ + fw->ip.src.s_addr = saddr->s_addr; + fw->ip.dst.s_addr = daddr->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[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + int verbose, + iptc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[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(struct ipt_entry *fw) +{ + /* Establish mask for comparison */ + unsigned int size; + struct iptables_match *m; + unsigned char *mask, *mptr; + + size = sizeof(struct ipt_entry); + for (m = iptables_matches; m; m = m->next) { + if (!m->used) + continue; + + size += IPT_ALIGN(sizeof(struct ipt_entry_match)) + m->size; + } + + mask = fw_calloc(1, size + + IPT_ALIGN(sizeof(struct ipt_entry_target)) + + iptables_targets->size); + + memset(mask, 0xFF, sizeof(struct ipt_entry)); + mptr = mask + sizeof(struct ipt_entry); + + for (m = iptables_matches; m; m = m->next) { + if (!m->used) + continue; + + memset(mptr, 0xFF, + IPT_ALIGN(sizeof(struct ipt_entry_match)) + + m->userspacesize); + mptr += IPT_ALIGN(sizeof(struct ipt_entry_match)) + m->size; + } + + memset(mptr, 0xFF, + IPT_ALIGN(sizeof(struct ipt_entry_target)) + + iptables_targets->userspacesize); + + return mask; +} + +static int +delete_entry(const ipt_chainlabel chain, + struct ipt_entry *fw, + unsigned int nsaddrs, + const struct in_addr saddrs[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + int verbose, + iptc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + unsigned char *mask; + + mask = make_delete_mask(fw); + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[j].s_addr; + if (verbose) + print_firewall_line(fw, *handle); + ret &= iptc_delete_entry(chain, fw, mask, handle); + } + } + return ret; +} + +int +for_each_chain(int (*fn)(const ipt_chainlabel, int, iptc_handle_t *), + int verbose, int builtinstoo, iptc_handle_t *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 = fw_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)) + continue; + ret &= fn(chains + i*sizeof(ipt_chainlabel), verbose, handle); + } + + free(chains); + return ret; +} + +int +flush_entries(const ipt_chainlabel chain, int verbose, + iptc_handle_t *handle) +{ + if (!chain) + return for_each_chain(flush_entries, 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, + iptc_handle_t *handle) +{ + if (!chain) + return for_each_chain(zero_entries, verbose, 1, handle); + + if (verbose) + fprintf(stdout, "Zeroing chain `%s'\n", chain); + return iptc_zero_entries(chain, handle); +} + +int +delete_chain(const ipt_chainlabel chain, int verbose, + iptc_handle_t *handle) +{ + if (!chain) + return for_each_chain(delete_chain, 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 verbose, int numeric, + int expanded, int linenumbers, iptc_handle_t *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"); + + print_header(format, this, handle); + i = iptc_first_rule(this, handle); + + num = 0; + while (i) { + print_firewall(i, + iptc_get_target(i, handle), + num++, + format, + *handle); + i = iptc_next_rule(i, handle); + } + found = 1; + } + + errno = ENOENT; + return found; +} + +static char *get_modprobe(void) +{ + int procfile; + char *ret; + + procfile = open(PROC_SYS_MODPROBE, O_RDONLY); + if (procfile < 0) + return NULL; + + ret = malloc(1024); + if (ret) { + switch (read(procfile, ret, 1024)) { + case -1: goto fail; + case 1024: 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 iptables_insmod(const char *modname, const char *modprobe) +{ + char *buf = NULL; + char *argv[3]; + + /* If they don't explicitly set it, read out of kernel */ + if (!modprobe) { + buf = get_modprobe(); + if (!buf) + return -1; + modprobe = buf; + } + + switch (fork()) { + case 0: + argv[0] = (char *)modprobe; + argv[1] = (char *)modname; + argv[2] = NULL; + execv(argv[0], argv); + + /* not usually reached */ + exit(0); + case -1: + return -1; + + default: /* parent */ + wait(NULL); + } + + free(buf); + return 0; +} + +static struct ipt_entry * +generate_entry(const struct ipt_entry *fw, + struct iptables_match *matches, + struct ipt_entry_target *target) +{ + unsigned int size; + struct iptables_match *m; + struct ipt_entry *e; + + size = sizeof(struct ipt_entry); + for (m = matches; m; m = m->next) { + if (!m->used) + continue; + + size += m->m->u.match_size; + } + + e = fw_malloc(size + target->u.target_size); + *e = *fw; + e->target_offset = size; + e->next_offset = size + target->u.target_size; + + size = 0; + for (m = matches; m; m = m->next) { + if (!m->used) + continue; + + memcpy(e->elems + size, m->m, m->m->u.match_size); + size += m->m->u.match_size; + } + memcpy(e->elems + size, target, target->u.target_size); + + return e; +} + +int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle) +{ + struct ipt_entry fw, *e = NULL; + int invert = 0; + unsigned int nsaddrs = 0, ndaddrs = 0; + struct in_addr *saddrs = NULL, *daddrs = NULL; + + int c, verbose = 0; + const char *chain = NULL; + const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL; + const char *policy = NULL, *newname = NULL; + unsigned int rulenum = 0, options = 0, command = 0; + const char *pcnt = NULL, *bcnt = NULL; + int ret = 1; + struct iptables_match *m; + struct iptables_target *target = NULL; + struct iptables_target *t; + const char *jumpto = ""; + char *protocol = NULL; + const char *modprobe = NULL; + int proto_used = 0; + + memset(&fw, 0, sizeof(fw)); + + opts = original_opts; + global_option_offset = 0; + + /* re-set optind to 0 in case do_command gets called + * a second time */ + optind = 0; + + /* clear mflags in case do_command gets called a second time + * (we clear the global list of all matches for security)*/ + for (m = iptables_matches; m; m = m->next) { + m->mflags = 0; + m->used = 0; + } + + for (t = iptables_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; + + while ((c = getopt_long(argc, argv, + "-A:D:R:I:L::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvnt:m:xc:", + opts, NULL)) != -1) { + switch (c) { + /* + * Command selection + */ + case 'A': + add_command(&command, CMD_APPEND, CMD_NONE, + invert); + chain = optarg; + break; + + case 'D': + add_command(&command, CMD_DELETE, CMD_NONE, + 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, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else + exit_error(PARAMETER_PROBLEM, + "-%c requires a rule number", + cmd2char(CMD_REPLACE)); + break; + + case 'I': + add_command(&command, CMD_INSERT, CMD_NONE, + 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, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'F': + add_command(&command, CMD_FLUSH, CMD_NONE, + 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, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'N': + if (optarg && *optarg == '-') + exit_error(PARAMETER_PROBLEM, + "chain name not allowed to start " + "with `-'\n"); + if (find_target(optarg, TRY_LOAD)) + exit_error(PARAMETER_PROBLEM, + "chain name may not clash " + "with target name\n"); + add_command(&command, CMD_NEW_CHAIN, CMD_NONE, + invert); + chain = optarg; + break; + + case 'X': + add_command(&command, CMD_DELETE_CHAIN, CMD_NONE, + 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, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + newname = argv[optind++]; + else + exit_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, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + policy = argv[optind++]; + else + exit_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 (!iptables_matches && protocol) + find_match(protocol, TRY_LOAD); + + exit_printhelp(); + + /* + * Option selection + */ + case 'p': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_PROTOCOL, &fw.ip.invflags, + invert); + + /* Canonicalize into lower case */ + for (protocol = argv[optind-1]; *protocol; protocol++) + *protocol = tolower(*protocol); + + protocol = argv[optind-1]; + fw.ip.proto = parse_protocol(protocol); + + if (fw.ip.proto == 0 + && (fw.ip.invflags & IPT_INV_PROTO)) + exit_error(PARAMETER_PROBLEM, + "rule would never match protocol"); + fw.nfcache |= NFC_IP_PROTO; + break; + + case 's': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_SOURCE, &fw.ip.invflags, + invert); + shostnetworkmask = argv[optind-1]; + fw.nfcache |= NFC_IP_SRC; + break; + + case 'd': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_DESTINATION, &fw.ip.invflags, + invert); + dhostnetworkmask = argv[optind-1]; + fw.nfcache |= NFC_IP_DST; + break; + + case 'j': + set_option(&options, OPT_JUMP, &fw.ip.invflags, + invert); + jumpto = parse_target(optarg); + /* TRY_LOAD (may be chain name) */ + target = find_target(jumpto, TRY_LOAD); + + if (target) { + size_t size; + + size = IPT_ALIGN(sizeof(struct ipt_entry_target)) + + target->size; + + target->t = fw_calloc(1, size); + target->t->u.target_size = size; + strcpy(target->t->u.user.name, jumpto); + target->init(target->t, &fw.nfcache); + opts = merge_options(opts, target->extra_opts, &target->option_offset); + } + break; + + + case 'i': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_VIANAMEIN, &fw.ip.invflags, + invert); + parse_interface(argv[optind-1], + fw.ip.iniface, + fw.ip.iniface_mask); + fw.nfcache |= NFC_IP_IF_IN; + break; + + case 'o': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_VIANAMEOUT, &fw.ip.invflags, + invert); + parse_interface(argv[optind-1], + fw.ip.outiface, + fw.ip.outiface_mask); + fw.nfcache |= NFC_IP_IF_OUT; + break; + + case 'f': + set_option(&options, OPT_FRAGMENT, &fw.ip.invflags, + invert); + fw.ip.flags |= IPT_F_FRAG; + fw.nfcache |= NFC_IP_FRAG; + break; + + case 'v': + if (!verbose) + set_option(&options, OPT_VERBOSE, + &fw.ip.invflags, invert); + verbose++; + break; + + case 'm': { + size_t size; + + if (invert) + exit_error(PARAMETER_PROBLEM, + "unexpected ! flag before --match"); + + m = find_match(optarg, LOAD_MUST_SUCCEED); + size = IPT_ALIGN(sizeof(struct ipt_entry_match)) + + m->size; + m->m = fw_calloc(1, size); + m->m->u.match_size = size; + strcpy(m->m->u.user.name, m->name); + m->init(m->m, &fw.nfcache); + opts = merge_options(opts, m->extra_opts, &m->option_offset); + } + break; + + case 'n': + set_option(&options, OPT_NUMERIC, &fw.ip.invflags, + invert); + break; + + case 't': + if (invert) + exit_error(PARAMETER_PROBLEM, + "unexpected ! flag before --table"); + *table = argv[optind-1]; + break; + + case 'x': + set_option(&options, OPT_EXPANDED, &fw.ip.invflags, + invert); + break; + + case 'V': + if (invert) + printf("Not %s ;-)\n", program_version); + else + printf("%s v%s\n", + program_name, program_version); + exit(0); + + case '0': + set_option(&options, OPT_LINENUMBERS, &fw.ip.invflags, + invert); + break; + + case 'M': + modprobe = optarg; + break; + + case 'c': + + set_option(&options, OPT_COUNTERS, &fw.ip.invflags, + invert); + pcnt = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + bcnt = argv[optind++]; + else + exit_error(PARAMETER_PROBLEM, + "-%c requires packet and byte counter", + opt2char(OPT_COUNTERS)); + + if (sscanf(pcnt, "%llu", &fw.counters.pcnt) != 1) + exit_error(PARAMETER_PROBLEM, + "-%c packet counter not numeric", + opt2char(OPT_COUNTERS)); + + if (sscanf(bcnt, "%llu", &fw.counters.bcnt) != 1) + exit_error(PARAMETER_PROBLEM, + "-%c byte counter not numeric", + opt2char(OPT_COUNTERS)); + + break; + + + case 1: /* non option */ + if (optarg[0] == '!' && optarg[1] == '\0') { + if (invert) + exit_error(PARAMETER_PROBLEM, + "multiple consecutive ! not" + " allowed"); + invert = TRUE; + optarg[0] = '\0'; + continue; + } + printf("Bad argument `%s'\n", optarg); + exit_tryhelp(2); + + default: + /* FIXME: This scheme doesn't allow two of the same + matches --RR */ + if (!target + || !(target->parse(c - target->option_offset, + argv, invert, + &target->tflags, + &fw, &target->t))) { + for (m = iptables_matches; m; m = m->next) { + if (!m->used) + continue; + + if (m->parse(c - m->option_offset, + argv, invert, + &m->mflags, + &fw, + &fw.nfcache, + &m->m)) + break; + } + + /* If you listen carefully, you can + actually hear this code suck. */ + + /* some explanations (after four different bugs + * in 3 different releases): If we encountere 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 iptables-restore!] + * - the protocol extension can be successively + * loaded + */ + if (m == NULL + && protocol + && (!find_proto(protocol, DONT_LOAD, + options&OPT_NUMERIC) + || (find_proto(protocol, DONT_LOAD, + options&OPT_NUMERIC) + && (proto_used == 0)) + ) + && (m = find_proto(protocol, TRY_LOAD, + options&OPT_NUMERIC))) { + /* Try loading protocol */ + size_t size; + + proto_used = 1; + + size = IPT_ALIGN(sizeof(struct ipt_entry_match)) + + m->size; + + m->m = fw_calloc(1, size); + m->m->u.match_size = size; + strcpy(m->m->u.user.name, m->name); + m->init(m->m, &fw.nfcache); + + opts = merge_options(opts, + m->extra_opts, &m->option_offset); + + optind--; + continue; + } + if (!m) + exit_error(PARAMETER_PROBLEM, + "Unknown arg `%s'", + argv[optind-1]); + } + } + invert = FALSE; + } + + for (m = iptables_matches; m; m = m->next) { + if (!m->used) + continue; + + m->final_check(m->mflags); + } + + if (target) + target->final_check(target->tflags); + + /* Fix me: must put inverse options checking here --MN */ + + if (optind < argc) + exit_error(PARAMETER_PROBLEM, + "unknown arguments found on commandline"); + if (!command) + exit_error(PARAMETER_PROBLEM, "no command specified"); + if (invert) + exit_error(PARAMETER_PROBLEM, + "nothing appropriate following !"); + + if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)) { + if (!(options & OPT_DESTINATION)) + dhostnetworkmask = "0.0.0.0/0"; + if (!(options & OPT_SOURCE)) + shostnetworkmask = "0.0.0.0/0"; + } + + if (shostnetworkmask) + parse_hostnetworkmask(shostnetworkmask, &saddrs, + &(fw.ip.smsk), &nsaddrs); + + if (dhostnetworkmask) + parse_hostnetworkmask(dhostnetworkmask, &daddrs, + &(fw.ip.dmsk), &ndaddrs); + + if ((nsaddrs > 1 || ndaddrs > 1) && + (fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP))) + exit_error(PARAMETER_PROBLEM, "! not allowed with multiple" + " source or destination IP addresses"); + + if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1)) + exit_error(PARAMETER_PROBLEM, "Replacement rule does not " + "specify a unique address"); + + generic_opt_check(command, options); + + if (chain && strlen(chain) > IPT_FUNCTION_MAXNAMELEN) + exit_error(PARAMETER_PROBLEM, + "chain name `%s' too long (must be under %i chars)", + chain, IPT_FUNCTION_MAXNAMELEN); + + /* only allocate handle if we weren't called with a handle */ + if (!*handle) + *handle = iptc_init(*table); + + if (!*handle) { + /* try to insmod the module if iptc_init failed */ + iptables_insmod("ip_tables", modprobe); + *handle = iptc_init(*table); + } + + if (!*handle) + exit_error(VERSION_PROBLEM, + "can't initialize iptables table `%s': %s", + *table, iptc_strerror(errno)); + + if (command == CMD_APPEND + || command == CMD_DELETE + || command == CMD_INSERT + || command == CMD_REPLACE) { + if (strcmp(chain, "PREROUTING") == 0 + || strcmp(chain, "INPUT") == 0) { + /* -o not valid with incoming packets. */ + if (options & OPT_VIANAMEOUT) + exit_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 (options & OPT_VIANAMEIN) + exit_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEIN), + chain); + } + + if (target && iptc_is_chain(jumpto, *handle)) { + printf("Warning: using chain %s, not extension\n", + jumpto); + + target = NULL; + } + + /* If they didn't specify a target, or it's a chain + name, use standard. */ + if (!target + && (strlen(jumpto) == 0 + || iptc_is_chain(jumpto, *handle))) { + size_t size; + + target = find_target(IPT_STANDARD_TARGET, + LOAD_MUST_SUCCEED); + + size = sizeof(struct ipt_entry_target) + + target->size; + target->t = fw_calloc(1, size); + target->t->u.target_size = size; + strcpy(target->t->u.user.name, jumpto); + target->init(target->t, &fw.nfcache); + } + + if (!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. */ + find_target(jumpto, LOAD_MUST_SUCCEED); + } else { + e = generate_entry(&fw, iptables_matches, target->t); + } + } + + switch (command) { + case CMD_APPEND: + ret = append_entry(chain, e, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_DELETE: + ret = delete_entry(chain, e, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_DELETE_NUM: + ret = iptc_delete_num_entry(chain, rulenum - 1, handle); + break; + case CMD_REPLACE: + ret = replace_entry(chain, e, rulenum - 1, + saddrs, daddrs, options&OPT_VERBOSE, + handle); + break; + case CMD_INSERT: + ret = insert_entry(chain, e, rulenum - 1, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_LIST: + ret = list_entries(chain, + options&OPT_VERBOSE, + options&OPT_NUMERIC, + options&OPT_EXPANDED, + options&OPT_LINENUMBERS, + handle); + break; + case CMD_FLUSH: + ret = flush_entries(chain, options&OPT_VERBOSE, handle); + break; + case CMD_ZERO: + ret = zero_entries(chain, options&OPT_VERBOSE, handle); + break; + case CMD_LIST|CMD_ZERO: + ret = list_entries(chain, + options&OPT_VERBOSE, + options&OPT_NUMERIC, + options&OPT_EXPANDED, + options&OPT_LINENUMBERS, + handle); + if (ret) + ret = zero_entries(chain, + options&OPT_VERBOSE, handle); + break; + case CMD_NEW_CHAIN: + ret = iptc_create_chain(chain, handle); + break; + case CMD_DELETE_CHAIN: + ret = delete_chain(chain, 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, NULL, handle); + break; + default: + /* We should never reach this... */ + exit_tryhelp(2); + } + + if (verbose > 1) + dump_entries(*handle); + + return ret; +} diff --git a/.#iptables.c.1.55 b/.#iptables.c.1.55 new file mode 100644 index 0000000..b8087f1 --- /dev/null +++ b/.#iptables.c.1.55 @@ -0,0 +1,2303 @@ +/* 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 + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef IPT_LIB_DIR +#define IPT_LIB_DIR "/usr/lib/iptables" +#endif + +#ifndef PROC_SYS_MODPROBE +#define PROC_SYS_MODPROBE "/proc/sys/kernel/modprobe" +#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_CHECK 0x0800U +#define CMD_RENAME_CHAIN 0x1000U +#define NUMBER_OF_CMD 13 +static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z', + 'N', 'X', 'P', 'E' }; + +#define OPTION_OFFSET 256 + +#define OPT_NONE 0x00000U +#define OPT_NUMERIC 0x00001U +#define OPT_SOURCE 0x00002U +#define OPT_DESTINATION 0x00004U +#define OPT_PROTOCOL 0x00008U +#define OPT_JUMP 0x00010U +#define OPT_VERBOSE 0x00020U +#define OPT_EXPANDED 0x00040U +#define OPT_VIANAMEIN 0x00080U +#define OPT_VIANAMEOUT 0x00100U +#define OPT_FRAGMENT 0x00200U +#define OPT_LINENUMBERS 0x00400U +#define OPT_COUNTERS 0x00800U +#define NUMBER_OF_OPT 12 +static const char optflags[NUMBER_OF_OPT] += { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', 'f', '3', 'c'}; + +static struct option original_opts[] = { + { "append", 1, 0, 'A' }, + { "delete", 1, 0, 'D' }, + { "insert", 1, 0, 'I' }, + { "replace", 1, 0, 'R' }, + { "list", 2, 0, 'L' }, + { "flush", 2, 0, 'F' }, + { "zero", 2, 0, 'Z' }, + { "new-chain", 1, 0, 'N' }, + { "delete-chain", 2, 0, 'X' }, + { "rename-chain", 1, 0, 'E' }, + { "policy", 1, 0, 'P' }, + { "source", 1, 0, 's' }, + { "destination", 1, 0, 'd' }, + { "src", 1, 0, 's' }, /* synonym */ + { "dst", 1, 0, 'd' }, /* synonym */ + { "protocol", 1, 0, 'p' }, + { "in-interface", 1, 0, 'i' }, + { "jump", 1, 0, 'j' }, + { "table", 1, 0, 't' }, + { "match", 1, 0, 'm' }, + { "numeric", 0, 0, 'n' }, + { "out-interface", 1, 0, 'o' }, + { "verbose", 0, 0, 'v' }, + { "exact", 0, 0, 'x' }, + { "fragments", 0, 0, 'f' }, + { "version", 0, 0, 'V' }, + { "help", 2, 0, 'h' }, + { "line-numbers", 0, 0, '0' }, + { "modprobe", 1, 0, 'M' }, + { "set-counters", 1, 0, 'c' }, + { 0 } +}; + +/* we need this for iptables-restore. iptables-restore.c sets line to the + * current line of the input file, in order to give a more precise error + * message. iptables itself doesn't need this, so it is initialized to the + * magic number of -1 */ +int line = -1; + +#ifndef __OPTIMIZE__ +struct ipt_entry_target * +ipt_get_target(struct ipt_entry *e) +{ + return (void *)e + e->target_offset; +} +#endif + +static struct option *opts = original_opts; +static unsigned int global_option_offset = 0; + +/* 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 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 -f --line */ +/*INSERT*/ {'x',' ',' ',' ',' ',' ','x',' ',' ',' ','x'}, +/*DELETE*/ {'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'}, +/*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','x'}, +/*CHECK*/ {'x','+','+','+','x',' ','x',' ',' ',' ','x'}, +/*RENAME*/ {'x','x','x','x','x',' ','x','x','x','x','x'} +}; + +static 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 +}; + +const char *program_version; +const char *program_name; + +/* Keeping track of external matches and targets: linked lists. */ +struct iptables_match *iptables_matches = NULL; +struct iptables_target *iptables_targets = NULL; + +/* Extra debugging from libiptc */ +extern void dump_entries(const iptc_handle_t handle); + +/* A few hardcoded protocols for 'all' and in case the user has no + /etc/protocols */ +struct pprot { + char *name; + u_int8_t num; +}; + +/* 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 + +static const struct pprot chain_protos[] = { + { "tcp", IPPROTO_TCP }, + { "udp", IPPROTO_UDP }, + { "icmp", IPPROTO_ICMP }, + { "esp", IPPROTO_ESP }, + { "ah", IPPROTO_AH }, + { "all", 0 }, +}; + +static char * +proto_to_name(u_int8_t proto, int nolookup) +{ + unsigned int i; + + if (proto && !nolookup) { + struct protoent *pent = getprotobynumber(proto); + if (pent) + return pent->p_name; + } + + for (i = 0; i < sizeof(chain_protos)/sizeof(struct pprot); i++) + if (chain_protos[i].num == proto) + return chain_protos[i].name; + + return NULL; +} + +struct in_addr * +dotted_to_addr(const char *dotted) +{ + static struct in_addr addr; + unsigned char *addrp; + char *p, *q; + unsigned int onebyte; + int i; + char buf[20]; + + /* copy dotted string, because we need to modify it */ + strncpy(buf, dotted, sizeof(buf) - 1); + addrp = (unsigned char *) &(addr.s_addr); + + p = buf; + for (i = 0; i < 3; i++) { + if ((q = strchr(p, '.')) == NULL) + return (struct in_addr *) NULL; + + *q = '\0'; + if (string_to_number(p, 0, 255, &onebyte) == -1) + return (struct in_addr *) NULL; + + addrp[i] = (unsigned char) onebyte; + p = q + 1; + } + + /* we've checked 3 bytes, now we check the last one */ + if (string_to_number(p, 0, 255, &onebyte) == -1) + return (struct in_addr *) NULL; + + addrp[3] = (unsigned char) onebyte; + + return &addr; +} + +static struct in_addr * +network_to_addr(const char *name) +{ + struct netent *net; + static struct in_addr addr; + + if ((net = getnetbyname(name)) != NULL) { + if (net->n_addrtype != AF_INET) + return (struct in_addr *) NULL; + addr.s_addr = htonl((unsigned long) net->n_net); + return &addr; + } + + return (struct in_addr *) NULL; +} + +static void +inaddrcpy(struct in_addr *dst, struct in_addr *src) +{ + /* memcpy(dst, src, sizeof(struct in_addr)); */ + dst->s_addr = src->s_addr; +} + +void +exit_error(enum exittype status, char *msg, ...) +{ + va_list args; + + va_start(args, msg); + fprintf(stderr, "%s v%s: ", program_name, program_version); + 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"); + exit(status); +} + +void +exit_tryhelp(int status) +{ +<<<<<<< iptables.c + if (line != -1) + fprintf(stderr, "Error occurred at line: %d\n", line); +======= + if (line != -1) + fprintf(stderr, "Error occured at line: %d\n", line); +>>>>>>> 1.55 + fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n", + program_name, program_name ); + exit(status); +} + +void +exit_printhelp(void) +{ + struct iptables_match *m = NULL; + struct iptables_target *t = NULL; + + printf("%s v%s\n\n" +"Usage: %s -[AD] chain rule-specification [options]\n" +" %s -[RI] chain rulenum rule-specification [options]\n" +" %s -D chain rulenum [options]\n" +" %s -[LFZ] [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", + program_name, program_version, program_name, program_name, + program_name, program_name, program_name, program_name, + program_name, program_name); + + printf( +"Commands:\n" +"Either long or short options are allowed.\n" +" --append -A chain Append to chain\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] List the rules in a chain or all chains\n" +" --flush -F [chain] Delete all rules in chain or all chains\n" +" --zero -Z [chain] 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" +" --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" +" --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 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 matches & targets */ + for (t=iptables_targets;t;t=t->next) { + printf("\n"); + t->help(); + } + for (m=iptables_matches;m;m=m->next) { + printf("\n"); + m->help(); + } + exit(0); +} + +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(int *cmd, const int newcmd, const int othercmds, int invert) +{ + if (invert) + exit_error(PARAMETER_PROBLEM, "unexpected ! flag"); + if (*cmd & (~othercmds)) + exit_error(PARAMETER_PROBLEM, "Can't use -%c with -%c\n", + cmd2char(newcmd), cmd2char(*cmd & (~othercmds))); + *cmd |= newcmd; +} + +int +check_inverse(const char option[], int *invert, int *optind, int argc) +{ + if (option && strcmp(option, "!") == 0) { + if (*invert) + exit_error(PARAMETER_PROBLEM, + "Multiple `!' flags not allowed"); + *invert = TRUE; + if (optind) { + *optind = *optind+1; + if (argc && *optind > argc) + exit_error(PARAMETER_PROBLEM, + "no argument following `!'"); + } + + return TRUE; + } + return FALSE; +} + +static void * +fw_calloc(size_t count, size_t size) +{ + void *p; + + if ((p = calloc(count, size)) == NULL) { + perror("iptables: calloc failed"); + exit(1); + } + return p; +} + +static void * +fw_malloc(size_t size) +{ + void *p; + + if ((p = malloc(size)) == NULL) { + perror("iptables: malloc failed"); + exit(1); + } + return p; +} + +static struct in_addr * +host_to_addr(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 (struct in_addr *) NULL; + + while (host->h_addr_list[*naddr] != (char *) NULL) + (*naddr)++; + addr = fw_calloc(*naddr, sizeof(struct in_addr)); + for (i = 0; i < *naddr; i++) + inaddrcpy(&(addr[i]), + (struct in_addr *) host->h_addr_list[i]); + return addr; + } + + return (struct in_addr *) NULL; +} + +static char * +addr_to_host(const struct in_addr *addr) +{ + struct hostent *host; + + if ((host = gethostbyaddr((char *) addr, + sizeof(struct in_addr), AF_INET)) != NULL) + return (char *) host->h_name; + + return (char *) NULL; +} + +/* + * 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. +*/ + +static struct in_addr * +parse_hostnetwork(const char *name, unsigned int *naddrs) +{ + struct in_addr *addrp, *addrptmp; + + if ((addrptmp = dotted_to_addr(name)) != NULL || + (addrptmp = network_to_addr(name)) != NULL) { + addrp = fw_malloc(sizeof(struct in_addr)); + inaddrcpy(addrp, addrptmp); + *naddrs = 1; + return addrp; + } + if ((addrp = host_to_addr(name, naddrs)) != NULL) + return addrp; + + exit_error(PARAMETER_PROBLEM, "host/network `%s' not found", name); +} + +static struct in_addr * +parse_mask(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 = dotted_to_addr(mask)) != NULL) + /* dotted_to_addr already returns a network byte order addr */ + return addrp; + if (string_to_number(mask, 0, 32, &bits) == -1) + exit_error(PARAMETER_PROBLEM, + "invalid mask `%s' specified", mask); + if (bits != 0) { + maskaddr.s_addr = htonl(0xFFFFFFFF << (32 - bits)); + return &maskaddr; + } + + maskaddr.s_addr = 0L; + return &maskaddr; +} + +void +parse_hostnetworkmask(const char *name, struct in_addr **addrpp, + struct in_addr *maskp, unsigned int *naddrs) +{ + struct in_addr *addrp; + char buf[256]; + char *p; + int i, j, k, n; + + strncpy(buf, name, sizeof(buf) - 1); + if ((p = strrchr(buf, '/')) != NULL) { + *p = '\0'; + addrp = parse_mask(p + 1); + } else + addrp = parse_mask(NULL); + inaddrcpy(maskp, addrp); + + /* if a null mask is given, the name is ignored, like in "any/0" */ + if (maskp->s_addr == 0L) + strcpy(buf, "0.0.0.0"); + + addrp = *addrpp = parse_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) { + (*naddrs)--; + j--; + break; + } + } + } +} + +struct iptables_match * +find_match(const char *name, enum ipt_tryload tryload) +{ + struct iptables_match *ptr; + + for (ptr = iptables_matches; ptr; ptr = ptr->next) { + if (strcmp(name, ptr->name) == 0) + break; + } + +#ifndef NO_SHARED_LIBS + if (!ptr && tryload != DONT_LOAD) { + char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so") + + strlen(name)]; + sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name); + if (dlopen(path, RTLD_NOW)) { + /* Found library. If it didn't register itself, + maybe they specified target as match. */ + ptr = find_match(name, DONT_LOAD); + + if (!ptr) + exit_error(PARAMETER_PROBLEM, + "Couldn't load match `%s'\n", + name); + } else if (tryload == LOAD_MUST_SUCCEED) + exit_error(PARAMETER_PROBLEM, + "Couldn't load match `%s':%s\n", + name, dlerror()); + } +#else + if (ptr && !ptr->loaded) { + if (tryload != DONT_LOAD) + ptr->loaded = 1; + else + ptr = NULL; + } + if(!ptr && (tryload == LOAD_MUST_SUCCEED)) { + exit_error(PARAMETER_PROBLEM, + "Couldn't find match `%s'\n", name); + } +#endif + + if (ptr) + ptr->used = 1; + + return ptr; +} + +/* Christophe Burki wants `-p 6' to imply `-m tcp'. */ +static struct iptables_match * +find_proto(const char *pname, enum ipt_tryload tryload, int nolookup) +{ + unsigned int proto; + + if (string_to_number(pname, 0, 255, &proto) != -1) { + char *protoname = proto_to_name(proto, nolookup); + + if (protoname) + return find_match(protoname, tryload); + } else + return find_match(pname, tryload); + + return NULL; +} + +u_int16_t +parse_protocol(const char *s) +{ + unsigned int proto; + + if (string_to_number(s, 0, 255, &proto) == -1) { + struct protoent *pent; + + if ((pent = getprotobyname(s))) + proto = pent->p_proto; + else { + unsigned int i; + for (i = 0; + i < sizeof(chain_protos)/sizeof(struct pprot); + i++) { + if (strcmp(s, chain_protos[i].name) == 0) { + proto = chain_protos[i].num; + break; + } + } + if (i == sizeof(chain_protos)/sizeof(struct pprot)) + exit_error(PARAMETER_PROBLEM, + "unknown protocol `%s' specified", + s); + } + } + + return (u_int16_t)proto; +} + +static void +parse_interface(const char *arg, char *vianame, unsigned char *mask) +{ + int vialen = strlen(arg); + unsigned int i; + + memset(mask, 0, IFNAMSIZ); + memset(vianame, 0, IFNAMSIZ); + + if (vialen + 1 > IFNAMSIZ) + exit_error(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 (!isalnum(vianame[i]) + && vianame[i] != '_' + && vianame[i] != '.') { + printf("Warning: wierd character in interface" + " `%s' (No aliases, :, ! or *).\n", + vianame); + break; + } + } + } +} + +/* Can't be zero. */ +static int +parse_rulenumber(const char *rule) +{ + unsigned int rulenum; + + if (string_to_number(rule, 1, INT_MAX, &rulenum) == -1) + exit_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) + exit_error(PARAMETER_PROBLEM, + "Invalid target name (too short)"); + + if (strlen(targetname)+1 > sizeof(ipt_chainlabel)) + exit_error(PARAMETER_PROBLEM, + "Invalid target name `%s' (%i chars max)", + targetname, sizeof(ipt_chainlabel)-1); + + for (ptr = targetname; *ptr; ptr++) + if (isspace(*ptr)) + exit_error(PARAMETER_PROBLEM, + "Invalid target name `%s'", targetname); + return targetname; +} + +static char * +addr_to_network(const struct in_addr *addr) +{ + struct netent *net; + + if ((net = getnetbyaddr((long) ntohl(addr->s_addr), AF_INET)) != NULL) + return (char *) net->n_name; + + return (char *) NULL; +} + +char * +addr_to_dotted(const struct in_addr *addrp) +{ + static char buf[20]; + const unsigned char *bytep; + + bytep = (const unsigned char *) &(addrp->s_addr); + sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]); + return buf; +} + +char * +addr_to_anyname(const struct in_addr *addr) +{ + char *name; + + if ((name = addr_to_host(addr)) != NULL || + (name = addr_to_network(addr)) != NULL) + return name; + + return addr_to_dotted(addr); +} + +char * +mask_to_dotted(const struct in_addr *mask) +{ + int i; + static char buf[20]; + u_int32_t maskaddr, bits; + + 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", addr_to_dotted(mask)); + + return buf; +} + +int +string_to_number(const char *s, unsigned int min, unsigned int max, + unsigned int *ret) +{ + long number; + char *end; + + /* Handle hex, octal, etc. */ + errno = 0; + number = strtol(s, &end, 0); + if (*end == '\0' && end != s) { + /* we parsed a number, let's see if we want this */ + if (errno != ERANGE && min <= number && number <= max) { + *ret = number; + return 0; + } + } + return -1; +} + +static void +set_option(unsigned int *options, unsigned int option, u_int8_t *invflg, + int invert) +{ + if (*options & option) + exit_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]) + exit_error(PARAMETER_PROBLEM, + "cannot have ! before -%c", + opt2char(option)); + *invflg |= inverse_for_options[i]; + } +} + +struct iptables_target * +find_target(const char *name, enum ipt_tryload tryload) +{ + struct iptables_target *ptr; + + /* Standard target? */ + if (strcmp(name, "") == 0 + || strcmp(name, IPTC_LABEL_ACCEPT) == 0 + || strcmp(name, IPTC_LABEL_DROP) == 0 + || strcmp(name, IPTC_LABEL_QUEUE) == 0 + || strcmp(name, IPTC_LABEL_RETURN) == 0) + name = "standard"; + + for (ptr = iptables_targets; ptr; ptr = ptr->next) { + if (strcmp(name, ptr->name) == 0) + break; + } + +#ifndef NO_SHARED_LIBS + if (!ptr && tryload != DONT_LOAD) { + char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so") + + strlen(name)]; + sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name); + if (dlopen(path, RTLD_NOW)) { + /* Found library. If it didn't register itself, + maybe they specified match as a target. */ + ptr = find_target(name, DONT_LOAD); + if (!ptr) + exit_error(PARAMETER_PROBLEM, + "Couldn't load target `%s'\n", + name); + } else if (tryload == LOAD_MUST_SUCCEED) + exit_error(PARAMETER_PROBLEM, + "Couldn't load target `%s':%s\n", + name, dlerror()); + } +#else + if (ptr && !ptr->loaded) { + if (tryload != DONT_LOAD) + ptr->loaded = 1; + else + ptr = NULL; + } + if(!ptr && (tryload == LOAD_MUST_SUCCEED)) { + exit_error(PARAMETER_PROBLEM, + "Couldn't find target `%s'\n", name); + } +#endif + + if (ptr) + ptr->used = 1; + + return ptr; +} + +static struct option * +merge_options(struct option *oldopts, const struct option *newopts, + unsigned int *option_offset) +{ + unsigned int num_old, num_new, i; + struct option *merge; + + for (num_old = 0; oldopts[num_old].name; num_old++); + for (num_new = 0; newopts[num_new].name; num_new++); + + global_option_offset += OPTION_OFFSET; + *option_offset = global_option_offset; + + merge = malloc(sizeof(struct option) * (num_new + num_old + 1)); + memcpy(merge, oldopts, num_old * sizeof(struct option)); + for (i = 0; i < num_new; i++) { + merge[num_old + i] = newopts[i]; + merge[num_old + i].val += *option_offset; + } + memset(merge + num_old + num_new, 0, sizeof(struct option)); + + return merge; +} + +void +register_match(struct iptables_match *me) +{ + struct iptables_match **i; + + if (strcmp(me->version, program_version) != 0) { + fprintf(stderr, "%s: match `%s' v%s (I'm v%s).\n", + program_name, me->name, me->version, program_version); + exit(1); + } + + if (find_match(me->name, DONT_LOAD)) { + fprintf(stderr, "%s: match `%s' already registered.\n", + program_name, me->name); + exit(1); + } + + if (me->size != IPT_ALIGN(me->size)) { + fprintf(stderr, "%s: match `%s' has invalid size %u.\n", + program_name, me->name, me->size); + exit(1); + } + + /* Append to list. */ + for (i = &iptables_matches; *i; i = &(*i)->next); + me->next = NULL; + *i = me; + + me->m = NULL; + me->mflags = 0; +} + +void +register_target(struct iptables_target *me) +{ + if (strcmp(me->version, program_version) != 0) { + fprintf(stderr, "%s: target `%s' v%s (I'm v%s).\n", + program_name, me->name, me->version, program_version); + exit(1); + } + + if (find_target(me->name, DONT_LOAD)) { + fprintf(stderr, "%s: target `%s' already registered.\n", + program_name, me->name); + exit(1); + } + + if (me->size != IPT_ALIGN(me->size)) { + fprintf(stderr, "%s: target `%s' has invalid size %u.\n", + program_name, me->name, me->size); + exit(1); + } + + /* Prepend to list. */ + me->next = iptables_targets; + iptables_targets = me; + me->t = NULL; + me->tflags = 0; +} + +static void +print_num(u_int64_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 "), number); + } + else printf(FMT("%4lluG ","%lluG "), number); + } + else printf(FMT("%4lluM ","%lluM "), number); + } else + printf(FMT("%4lluK ","%lluK "), number); + } else + printf(FMT("%5llu ","%llu "), number); + } else + printf(FMT("%8llu ","%llu "), number); +} + + +static void +print_header(unsigned int format, const char *chain, iptc_handle_t *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) +{ + struct iptables_match *match = find_match(m->u.user.name, TRY_LOAD); + + 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 hysterical raisins */ +static void +print_firewall(const struct ipt_entry *fw, + const char *targname, + unsigned int num, + unsigned int format, + const iptc_handle_t handle) +{ + struct iptables_target *target = NULL; + const struct ipt_entry_target *t; + u_int8_t flags; + char buf[BUFSIZ]; + + if (!iptc_is_chain(targname, handle)) + target = find_target(targname, TRY_LOAD); + else + target = find_target(IPT_STANDARD_TARGET, LOAD_MUST_SUCCEED); + + t = ipt_get_target((struct ipt_entry *)fw); + flags = fw->ip.flags; + + if (format & FMT_LINENUMBERS) + printf(FMT("%-4u ", "%u "), num+1); + + 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); + { + 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) + sprintf(buf, "%s", addr_to_dotted(&(fw->ip.src))); + else + sprintf(buf, "%s", addr_to_anyname(&(fw->ip.src))); + strcat(buf, mask_to_dotted(&(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) + sprintf(buf, "%s", addr_to_dotted(&(fw->ip.dst))); + else + sprintf(buf, "%s", addr_to_anyname(&(fw->ip.dst))); + strcat(buf, mask_to_dotted(&(fw->ip.dmsk))); + printf(FMT("%-19s","-> %s"), buf); + } + + if (format & FMT_NOTABLE) + fputs(" ", stdout); + + 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] ", + t->u.target_size - sizeof(*t)); + + if (!(format & FMT_NONEWLINE)) + fputc('\n', stdout); +} + +static void +print_firewall_line(const struct ipt_entry *fw, + const iptc_handle_t 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[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + int verbose, + iptc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[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 *daddr, + int verbose, + iptc_handle_t *handle) +{ + fw->ip.src.s_addr = saddr->s_addr; + fw->ip.dst.s_addr = daddr->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[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + int verbose, + iptc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[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(struct ipt_entry *fw) +{ + /* Establish mask for comparison */ + unsigned int size; + struct iptables_match *m; + unsigned char *mask, *mptr; + + size = sizeof(struct ipt_entry); + for (m = iptables_matches; m; m = m->next) { + if (!m->used) + continue; + + size += IPT_ALIGN(sizeof(struct ipt_entry_match)) + m->size; + } + + mask = fw_calloc(1, size + + IPT_ALIGN(sizeof(struct ipt_entry_target)) + + iptables_targets->size); + + memset(mask, 0xFF, sizeof(struct ipt_entry)); + mptr = mask + sizeof(struct ipt_entry); + + for (m = iptables_matches; m; m = m->next) { + if (!m->used) + continue; + + memset(mptr, 0xFF, + IPT_ALIGN(sizeof(struct ipt_entry_match)) + + m->userspacesize); + mptr += IPT_ALIGN(sizeof(struct ipt_entry_match)) + m->size; + } + + memset(mptr, 0xFF, + IPT_ALIGN(sizeof(struct ipt_entry_target)) + + iptables_targets->userspacesize); + + return mask; +} + +static int +delete_entry(const ipt_chainlabel chain, + struct ipt_entry *fw, + unsigned int nsaddrs, + const struct in_addr saddrs[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + int verbose, + iptc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + unsigned char *mask; + + mask = make_delete_mask(fw); + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[j].s_addr; + if (verbose) + print_firewall_line(fw, *handle); + ret &= iptc_delete_entry(chain, fw, mask, handle); + } + } + return ret; +} + +int +for_each_chain(int (*fn)(const ipt_chainlabel, int, iptc_handle_t *), + int verbose, int builtinstoo, iptc_handle_t *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 = fw_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)) + continue; + ret &= fn(chains + i*sizeof(ipt_chainlabel), verbose, handle); + } + + free(chains); + return ret; +} + +int +flush_entries(const ipt_chainlabel chain, int verbose, + iptc_handle_t *handle) +{ + if (!chain) + return for_each_chain(flush_entries, 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, + iptc_handle_t *handle) +{ + if (!chain) + return for_each_chain(zero_entries, verbose, 1, handle); + + if (verbose) + fprintf(stdout, "Zeroing chain `%s'\n", chain); + return iptc_zero_entries(chain, handle); +} + +int +delete_chain(const ipt_chainlabel chain, int verbose, + iptc_handle_t *handle) +{ + if (!chain) + return for_each_chain(delete_chain, 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 verbose, int numeric, + int expanded, int linenumbers, iptc_handle_t *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"); + + print_header(format, this, handle); + i = iptc_first_rule(this, handle); + + num = 0; + while (i) { + print_firewall(i, + iptc_get_target(i, handle), + num++, + format, + *handle); + i = iptc_next_rule(i, handle); + } + found = 1; + } + + errno = ENOENT; + return found; +} + +static char *get_modprobe(void) +{ + int procfile; + char *ret; + + procfile = open(PROC_SYS_MODPROBE, O_RDONLY); + if (procfile < 0) + return NULL; + + ret = malloc(1024); + if (ret) { + switch (read(procfile, ret, 1024)) { + case -1: goto fail; + case 1024: 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 iptables_insmod(const char *modname, const char *modprobe) +{ + char *buf = NULL; + char *argv[3]; + + /* If they don't explicitly set it, read out of kernel */ + if (!modprobe) { + buf = get_modprobe(); + if (!buf) + return -1; + modprobe = buf; + } + + switch (fork()) { + case 0: + argv[0] = (char *)modprobe; + argv[1] = (char *)modname; + argv[2] = NULL; + execv(argv[0], argv); + + /* not usually reached */ + exit(0); + case -1: + return -1; + + default: /* parent */ + wait(NULL); + } + + free(buf); + return 0; +} + +static struct ipt_entry * +generate_entry(const struct ipt_entry *fw, + struct iptables_match *matches, + struct ipt_entry_target *target) +{ + unsigned int size; + struct iptables_match *m; + struct ipt_entry *e; + + size = sizeof(struct ipt_entry); + for (m = matches; m; m = m->next) { + if (!m->used) + continue; + + size += m->m->u.match_size; + } + + e = fw_malloc(size + target->u.target_size); + *e = *fw; + e->target_offset = size; + e->next_offset = size + target->u.target_size; + + size = 0; + for (m = matches; m; m = m->next) { + if (!m->used) + continue; + + memcpy(e->elems + size, m->m, m->m->u.match_size); + size += m->m->u.match_size; + } + memcpy(e->elems + size, target, target->u.target_size); + + return e; +} + +int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle) +{ + struct ipt_entry fw, *e = NULL; + int invert = 0; + unsigned int nsaddrs = 0, ndaddrs = 0; + struct in_addr *saddrs = NULL, *daddrs = NULL; + + int c, verbose = 0; + const char *chain = NULL; + const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL; + const char *policy = NULL, *newname = NULL; + unsigned int rulenum = 0, options = 0, command = 0; + const char *pcnt = NULL, *bcnt = NULL; + int ret = 1; + struct iptables_match *m; + struct iptables_target *target = NULL; + struct iptables_target *t; + const char *jumpto = ""; + char *protocol = NULL; + const char *modprobe = NULL; + int proto_used = 0; + + memset(&fw, 0, sizeof(fw)); + + opts = original_opts; + global_option_offset = 0; + + /* re-set optind to 0 in case do_command gets called + * a second time */ + optind = 0; + + /* clear mflags in case do_command gets called a second time + * (we clear the global list of all matches for security)*/ + for (m = iptables_matches; m; m = m->next) { + m->mflags = 0; + m->used = 0; + } + + for (t = iptables_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; + + while ((c = getopt_long(argc, argv, + "-A:D:R:I:L::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvnt:m:xc:", + opts, NULL)) != -1) { + switch (c) { + /* + * Command selection + */ + case 'A': + add_command(&command, CMD_APPEND, CMD_NONE, + invert); + chain = optarg; + break; + + case 'D': + add_command(&command, CMD_DELETE, CMD_NONE, + 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, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else + exit_error(PARAMETER_PROBLEM, + "-%c requires a rule number", + cmd2char(CMD_REPLACE)); + break; + + case 'I': + add_command(&command, CMD_INSERT, CMD_NONE, + 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, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'F': + add_command(&command, CMD_FLUSH, CMD_NONE, + 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, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'N': + if (optarg && *optarg == '-') + exit_error(PARAMETER_PROBLEM, + "chain name not allowed to start " + "with `-'\n"); + if (find_target(optarg, TRY_LOAD)) + exit_error(PARAMETER_PROBLEM, + "chain name may not clash " + "with target name\n"); + add_command(&command, CMD_NEW_CHAIN, CMD_NONE, + invert); + chain = optarg; + break; + + case 'X': + add_command(&command, CMD_DELETE_CHAIN, CMD_NONE, + 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, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + newname = argv[optind++]; + else + exit_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, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + policy = argv[optind++]; + else + exit_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 (!iptables_matches && protocol) + find_match(protocol, TRY_LOAD); + + exit_printhelp(); + + /* + * Option selection + */ + case 'p': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_PROTOCOL, &fw.ip.invflags, + invert); + + /* Canonicalize into lower case */ + for (protocol = argv[optind-1]; *protocol; protocol++) + *protocol = tolower(*protocol); + + protocol = argv[optind-1]; + fw.ip.proto = parse_protocol(protocol); + + if (fw.ip.proto == 0 + && (fw.ip.invflags & IPT_INV_PROTO)) + exit_error(PARAMETER_PROBLEM, + "rule would never match protocol"); + fw.nfcache |= NFC_IP_PROTO; + break; + + case 's': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_SOURCE, &fw.ip.invflags, + invert); + shostnetworkmask = argv[optind-1]; + fw.nfcache |= NFC_IP_SRC; + break; + + case 'd': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_DESTINATION, &fw.ip.invflags, + invert); + dhostnetworkmask = argv[optind-1]; + fw.nfcache |= NFC_IP_DST; + break; + + case 'j': + set_option(&options, OPT_JUMP, &fw.ip.invflags, + invert); + jumpto = parse_target(optarg); + /* TRY_LOAD (may be chain name) */ + target = find_target(jumpto, TRY_LOAD); + + if (target) { + size_t size; + + size = IPT_ALIGN(sizeof(struct ipt_entry_target)) + + target->size; + + target->t = fw_calloc(1, size); + target->t->u.target_size = size; + strcpy(target->t->u.user.name, jumpto); + target->init(target->t, &fw.nfcache); + opts = merge_options(opts, target->extra_opts, &target->option_offset); + } + break; + + + case 'i': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_VIANAMEIN, &fw.ip.invflags, + invert); + parse_interface(argv[optind-1], + fw.ip.iniface, + fw.ip.iniface_mask); + fw.nfcache |= NFC_IP_IF_IN; + break; + + case 'o': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_VIANAMEOUT, &fw.ip.invflags, + invert); + parse_interface(argv[optind-1], + fw.ip.outiface, + fw.ip.outiface_mask); + fw.nfcache |= NFC_IP_IF_OUT; + break; + + case 'f': + set_option(&options, OPT_FRAGMENT, &fw.ip.invflags, + invert); + fw.ip.flags |= IPT_F_FRAG; + fw.nfcache |= NFC_IP_FRAG; + break; + + case 'v': + if (!verbose) + set_option(&options, OPT_VERBOSE, + &fw.ip.invflags, invert); + verbose++; + break; + + case 'm': { + size_t size; + + if (invert) + exit_error(PARAMETER_PROBLEM, + "unexpected ! flag before --match"); + + m = find_match(optarg, LOAD_MUST_SUCCEED); + size = IPT_ALIGN(sizeof(struct ipt_entry_match)) + + m->size; + m->m = fw_calloc(1, size); + m->m->u.match_size = size; + strcpy(m->m->u.user.name, m->name); + m->init(m->m, &fw.nfcache); + opts = merge_options(opts, m->extra_opts, &m->option_offset); + } + break; + + case 'n': + set_option(&options, OPT_NUMERIC, &fw.ip.invflags, + invert); + break; + + case 't': + if (invert) + exit_error(PARAMETER_PROBLEM, + "unexpected ! flag before --table"); + *table = argv[optind-1]; + break; + + case 'x': + set_option(&options, OPT_EXPANDED, &fw.ip.invflags, + invert); + break; + + case 'V': + if (invert) + printf("Not %s ;-)\n", program_version); + else + printf("%s v%s\n", + program_name, program_version); + exit(0); + + case '0': + set_option(&options, OPT_LINENUMBERS, &fw.ip.invflags, + invert); + break; + + case 'M': + modprobe = optarg; + break; + + case 'c': + + set_option(&options, OPT_COUNTERS, &fw.ip.invflags, + invert); + pcnt = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + bcnt = argv[optind++]; + else + exit_error(PARAMETER_PROBLEM, + "-%c requires packet and byte counter", + opt2char(OPT_COUNTERS)); + + if (sscanf(pcnt, "%llu", &fw.counters.pcnt) != 1) + exit_error(PARAMETER_PROBLEM, + "-%c packet counter not numeric", + opt2char(OPT_COUNTERS)); + + if (sscanf(bcnt, "%llu", &fw.counters.bcnt) != 1) + exit_error(PARAMETER_PROBLEM, + "-%c byte counter not numeric", + opt2char(OPT_COUNTERS)); + + break; + + + case 1: /* non option */ + if (optarg[0] == '!' && optarg[1] == '\0') { + if (invert) + exit_error(PARAMETER_PROBLEM, + "multiple consecutive ! not" + " allowed"); + invert = TRUE; + optarg[0] = '\0'; + continue; + } + printf("Bad argument `%s'\n", optarg); + exit_tryhelp(2); + + default: + /* FIXME: This scheme doesn't allow two of the same + matches --RR */ + if (!target + || !(target->parse(c - target->option_offset, + argv, invert, + &target->tflags, + &fw, &target->t))) { + for (m = iptables_matches; m; m = m->next) { + if (!m->used) + continue; + + if (m->parse(c - m->option_offset, + argv, invert, + &m->mflags, + &fw, + &fw.nfcache, + &m->m)) + break; + } + + /* If you listen carefully, you can + actually hear this code suck. */ + + /* some explanations (after four different bugs + * in 3 different releases): If we encountere 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 iptables-restore!] + * - the protocol extension can be successively + * loaded + */ + if (m == NULL + && protocol + && (!find_proto(protocol, DONT_LOAD, + options&OPT_NUMERIC) + || (find_proto(protocol, DONT_LOAD, + options&OPT_NUMERIC) + && (proto_used == 0)) + ) + && (m = find_proto(protocol, TRY_LOAD, + options&OPT_NUMERIC))) { + /* Try loading protocol */ + size_t size; + + proto_used = 1; + + size = IPT_ALIGN(sizeof(struct ipt_entry_match)) + + m->size; + + m->m = fw_calloc(1, size); + m->m->u.match_size = size; + strcpy(m->m->u.user.name, m->name); + m->init(m->m, &fw.nfcache); + + opts = merge_options(opts, + m->extra_opts, &m->option_offset); + + optind--; + continue; + } + if (!m) + exit_error(PARAMETER_PROBLEM, + "Unknown arg `%s'", + argv[optind-1]); + } + } + invert = FALSE; + } + + for (m = iptables_matches; m; m = m->next) { + if (!m->used) + continue; + + m->final_check(m->mflags); + } + + if (target) + target->final_check(target->tflags); + + /* Fix me: must put inverse options checking here --MN */ + + if (optind < argc) + exit_error(PARAMETER_PROBLEM, + "unknown arguments found on commandline"); + if (!command) + exit_error(PARAMETER_PROBLEM, "no command specified"); + if (invert) + exit_error(PARAMETER_PROBLEM, + "nothing appropriate following !"); + + if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)) { + if (!(options & OPT_DESTINATION)) + dhostnetworkmask = "0.0.0.0/0"; + if (!(options & OPT_SOURCE)) + shostnetworkmask = "0.0.0.0/0"; + } + + if (shostnetworkmask) + parse_hostnetworkmask(shostnetworkmask, &saddrs, + &(fw.ip.smsk), &nsaddrs); + + if (dhostnetworkmask) + parse_hostnetworkmask(dhostnetworkmask, &daddrs, + &(fw.ip.dmsk), &ndaddrs); + + if ((nsaddrs > 1 || ndaddrs > 1) && + (fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP))) + exit_error(PARAMETER_PROBLEM, "! not allowed with multiple" + " source or destination IP addresses"); + + if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1)) + exit_error(PARAMETER_PROBLEM, "Replacement rule does not " + "specify a unique address"); + + generic_opt_check(command, options); + + if (chain && strlen(chain) > IPT_FUNCTION_MAXNAMELEN) + exit_error(PARAMETER_PROBLEM, + "chain name `%s' too long (must be under %i chars)", + chain, IPT_FUNCTION_MAXNAMELEN); + + /* only allocate handle if we weren't called with a handle */ + if (!*handle) + *handle = iptc_init(*table); + + if (!*handle) { + /* try to insmod the module if iptc_init failed */ + iptables_insmod("ip_tables", modprobe); + *handle = iptc_init(*table); + } + + if (!*handle) + exit_error(VERSION_PROBLEM, + "can't initialize iptables table `%s': %s", + *table, iptc_strerror(errno)); + + if (command == CMD_APPEND + || command == CMD_DELETE + || command == CMD_INSERT + || command == CMD_REPLACE) { + if (strcmp(chain, "PREROUTING") == 0 + || strcmp(chain, "INPUT") == 0) { + /* -o not valid with incoming packets. */ + if (options & OPT_VIANAMEOUT) + exit_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 (options & OPT_VIANAMEIN) + exit_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEIN), + chain); + } + + if (target && iptc_is_chain(jumpto, *handle)) { + printf("Warning: using chain %s, not extension\n", + jumpto); + + target = NULL; + } + + /* If they didn't specify a target, or it's a chain + name, use standard. */ + if (!target + && (strlen(jumpto) == 0 + || iptc_is_chain(jumpto, *handle))) { + size_t size; + + target = find_target(IPT_STANDARD_TARGET, + LOAD_MUST_SUCCEED); + + size = sizeof(struct ipt_entry_target) + + target->size; + target->t = fw_calloc(1, size); + target->t->u.target_size = size; + strcpy(target->t->u.user.name, jumpto); + target->init(target->t, &fw.nfcache); + } + + if (!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. */ + find_target(jumpto, LOAD_MUST_SUCCEED); + } else { + e = generate_entry(&fw, iptables_matches, target->t); + } + } + + switch (command) { + case CMD_APPEND: + ret = append_entry(chain, e, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_DELETE: + ret = delete_entry(chain, e, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_DELETE_NUM: + ret = iptc_delete_num_entry(chain, rulenum - 1, handle); + break; + case CMD_REPLACE: + ret = replace_entry(chain, e, rulenum - 1, + saddrs, daddrs, options&OPT_VERBOSE, + handle); + break; + case CMD_INSERT: + ret = insert_entry(chain, e, rulenum - 1, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_LIST: + ret = list_entries(chain, + options&OPT_VERBOSE, + options&OPT_NUMERIC, + options&OPT_EXPANDED, + options&OPT_LINENUMBERS, + handle); + break; + case CMD_FLUSH: + ret = flush_entries(chain, options&OPT_VERBOSE, handle); + break; + case CMD_ZERO: + ret = zero_entries(chain, options&OPT_VERBOSE, handle); + break; + case CMD_LIST|CMD_ZERO: + ret = list_entries(chain, + options&OPT_VERBOSE, + options&OPT_NUMERIC, + options&OPT_EXPANDED, + options&OPT_LINENUMBERS, + handle); + if (ret) + ret = zero_entries(chain, + options&OPT_VERBOSE, handle); + break; + case CMD_NEW_CHAIN: + ret = iptc_create_chain(chain, handle); + break; + case CMD_DELETE_CHAIN: + ret = delete_chain(chain, 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, NULL, handle); + break; + default: + /* We should never reach this... */ + exit_tryhelp(2); + } + + if (verbose > 1) + dump_entries(*handle); + + return ret; +} diff --git a/Makefile.nolibnsl b/Makefile.nolibnsl new file mode 100644 index 0000000..622a680 --- /dev/null +++ b/Makefile.nolibnsl @@ -0,0 +1,208 @@ +# uncomment this to get a fully statically linked version +# NO_SHARED_LIBS = 1 + +# uncomment this to disable IPv6 support +# DO_IPV6 = 0 + +###################################################################### +# YOU SHOULD NOT NEED TO TOUCH ANYTHING BELOW THIS LINE +###################################################################### + +# Standard part of Makefile for topdir. +TOPLEVEL_INCLUDED=YES + +ifndef KERNEL_DIR +KERNEL_DIR=/usr/src/linux +endif +IPTABLES_VERSION:=1.2.9 +OLD_IPTABLES_VERSION:=1.2.8 + +PREFIX:=/usr +LIBDIR:=$(PREFIX)/lib +BINDIR:=$(PREFIX)/sbin +MANDIR:=$(PREFIX)/man +INCDIR:=$(PREFIX)/include + +# directory for new iptables releases +RELEASE_DIR:=/tmp + +# Need libc6 for this. FIXME: Should covert to autoconf. +ifeq ($(shell [ -f /usr/include/netinet/ip6.h ] && echo YES), YES) +DO_IPV6:=1 +endif + +COPT_FLAGS:=-O2 +CFLAGS:=$(COPT_FLAGS) -Wall -Wunused -I$(KERNEL_DIR)/include -Iinclude/ -DIPTABLES_VERSION=\"$(IPTABLES_VERSION)\" #-g -DDEBUG #-pg # -DIPTC_DEBUG + +ifdef NO_SHARED_LIBS +CFLAGS += -DNO_SHARED_LIBS=1 +endif + +ifndef NO_SHARED_LIBS +DEPFILES = $(SHARED_LIBS:%.so=%.d) +SH_CFLAGS:=$(CFLAGS) -fPIC +STATIC_LIBS = +STATIC6_LIBS = +LDFLAGS = -rdynamic +LDLIBS = -ldl -lnsl +else +DEPFILES = $(EXT_OBJS:%.o=%.d) +STATIC_LIBS = extensions/libext.a +STATIC6_LIBS = extensions/libext6.a +LDFLAGS = -static +LDLIBS = +endif + +EXTRAS+=iptables iptables.o +EXTRA_INSTALLS+=$(DESTDIR)$(BINDIR)/iptables $(DESTDIR)$(MANDIR)/man8/iptables.8 + +# No longer experimental. +EXTRAS+=iptables-save iptables-restore +EXTRA_INSTALLS+=$(DESTDIR)$(BINDIR)/iptables-save $(DESTDIR)$(BINDIR)/iptables-restore $(DESTDIR)$(MANDIR)/man8/iptables-restore.8 $(DESTDIR)$(MANDIR)/man8/iptables-save.8 + +ifeq ($(DO_IPV6), 1) +EXTRAS+=ip6tables ip6tables.o +EXTRA_INSTALLS+=$(DESTDIR)$(BINDIR)/ip6tables $(DESTDIR)$(MANDIR)/man8/ip6tables.8 +EXTRAS_EXP+=ip6tables-save ip6tables-restore +EXTRA_INSTALLS_EXP+=$(DESTDIR)$(BINDIR)/ip6tables-save $(DESTDIR)$(BINDIR)/ip6tables-restore # $(DESTDIR)$(MANDIR)/man8/iptables-restore.8 $(DESTDIR)$(MANDIR)/man8/iptables-save.8 $(DESTDIR)$(MANDIR)/man8/ip6tables-save.8 $(DESTDIR)$(MANDIR)/man8/ip6tables-restore.8 +endif + +# Sparc64 hack +ifeq ($(shell uname -m),sparc64) +# The kernel is 64-bit, even though userspace is 32. +CFLAGS+=-DIPT_MIN_ALIGN=8 -DKERNEL_64_USERSPACE_32 +endif + +# HPPA hack +ifeq ($(shell uname -m),parisc64) +# The kernel is 64-bit, even though userspace is 32. +CFLAGS+=-DIPT_MIN_ALIGN=8 -DKERNEL_64_USERSPACE_32 +endif + +ifndef IPT_LIBDIR +IPT_LIBDIR:=$(LIBDIR)/iptables +endif + +.PHONY: default +default: print-extensions all + +.PHONY: print-extensions +print-extensions: + @[ -n "$(OPTIONALS)" ] && echo Extensions found: $(OPTIONALS) + +iptables.o: iptables.c + $(CC) $(CFLAGS) -DIPT_LIB_DIR=\"$(IPT_LIBDIR)\" -c -o $@ $< + +iptables: iptables-standalone.c iptables.o $(STATIC_LIBS) libiptc/libiptc.a + $(CC) $(CFLAGS) -DIPT_LIB_DIR=\"$(IPT_LIBDIR)\" $(LDFLAGS) -o $@ $^ $(LDLIBS) + +$(DESTDIR)$(BINDIR)/iptables: iptables + @[ -d $(DESTDIR)$(BINDIR) ] || mkdir -p $(DESTDIR)$(BINDIR) + cp $< $@ + +iptables-save: iptables-save.c iptables.o $(STATIC_LIBS) libiptc/libiptc.a + $(CC) $(CFLAGS) -DIPT_LIB_DIR=\"$(IPT_LIBDIR)\" $(LDFLAGS) -o $@ $^ $(LDLIBS) + +$(DESTDIR)$(BINDIR)/iptables-save: iptables-save + @[ -d $(DESTDIR)$(BINDIR) ] || mkdir -p $(DESTDIR)$(BINDIR) + cp $< $@ + +iptables-restore: iptables-restore.c iptables.o $(STATIC_LIBS) libiptc/libiptc.a + $(CC) $(CFLAGS) -DIPT_LIB_DIR=\"$(IPT_LIBDIR)\" $(LDFLAGS) -o $@ $^ $(LDLIBS) + +$(DESTDIR)$(BINDIR)/iptables-restore: iptables-restore + @[ -d $(DESTDIR)$(BINDIR) ] || mkdir -p $(DESTDIR)$(BINDIR) + cp $< $@ + +ip6tables.o: ip6tables.c + $(CC) $(CFLAGS) -DIP6T_LIB_DIR=\"$(IPT_LIBDIR)\" -c -o $@ $< + +ip6tables: ip6tables-standalone.c ip6tables.o $(STATIC6_LIBS) libiptc/libiptc.a + $(CC) $(CFLAGS) -DIP6T_LIB_DIR=\"$(IPT_LIBDIR)\" $(LDFLAGS) -o $@ $^ $(LDLIBS) + +$(DESTDIR)$(BINDIR)/ip6tables: ip6tables + @[ -d $(DESTDIR)$(BINDIR) ] || mkdir -p $(DESTDIR)$(BINDIR) + cp $< $@ + +ip6tables-save: ip6tables-save.c ip6tables.o $(STATIC6_LIBS) libiptc/libiptc.a + $(CC) $(CFLAGS) -DIP6T_LIB_DIR=\"$(IPT_LIBDIR)\" $(LDFLAGS) -o $@ $^ $(LDLIBS) + +$(DESTDIR)$(BINDIR)/ip6tables-save: ip6tables-save + @[ -d $(DESTDIR)$(BINDIR) ] || mkdir -p $(DESTDIR)$(BINDIR) + cp $< $@ + +ip6tables-restore: ip6tables-restore.c ip6tables.o $(STATIC6_LIBS) libiptc/libiptc.a + $(CC) $(CFLAGS) -DIP6T_LIB_DIR=\"$(IPT_LIBDIR)\" $(LDFLAGS) -o $@ $^ $(LDLIBS) + +$(DESTDIR)$(BINDIR)/ip6tables-restore: ip6tables-restore + @[ -d $(DESTDIR)$(BINDIR) ] || mkdir -p $(DESTDIR)$(BINDIR) + cp $< $@ + +$(DESTDIR)$(MANDIR)/man8/%.8: %.8 + @[ -d $(DESTDIR)$(MANDIR)/man8 ] || mkdir -p $(DESTDIR)$(MANDIR)/man8 + cp $< $@ + +EXTRA_DEPENDS+=iptables-standalone.d iptables.d + +iptables-standalone.d iptables.d: %.d: %.c + @-$(CC) -M -MG $(CFLAGS) $< | sed -e 's@^.*\.o:@$*.d $*.o:@' > $@ + + +# Development Targets +.PHONY: install-devel-man3 +install-devel-man3: $(DEVEL_MAN3) + @[ -d $(DESTDIR)$(MANDIR)/man3 ] || mkdir -p $(DESTDIR)$(MANDIR)/man3 + @cp -v $(DEVEL_MAN3) $(DESTDIR)$(MANDIR)/man3 + +.PHONY: install-devel-headers +install-devel-headers: $(DEVEL_HEADERS) + @[ -d $(DESTDIR)$(INCDIR) ] || mkdir -p $(DESTDIR)$(INCDIR) + @cp -v $(DEVEL_HEADERS) $(DESTDIR)$(INCDIR) + +.PHONY: install-devel-libs +install-devel-libs: $(DEVEL_LIBS) + @[ -d $(DESTDIR)$(LIBDIR) ] || mkdir -p $(DESTDIR)$(LIBDIR) + @cp -v $(DEVEL_LIBS) $(DESTDIR)$(LIBDIR) + +.PHONY: install-devel +install-devel: all install-devel-man3 install-devel-headers install-devel-libs + +.PHONY: distclean +distclean: clean + @rm -f TAGS `find . -name '*~' -o -name '.*~'` `find . -name '*.rej'` `find . -name '*.d'` .makefirst + +# Rusty's distro magic. +.PHONY: distrib +distrib: check distclean delrelease $(RELEASE_DIR)/iptables-$(IPTABLES_VERSION).tar.bz2 diff md5sums # nowhitespace + +# Makefile must not define: +# -g -pg -DIPTC_DEBUG +.PHONY: check +check: + @if echo $(CFLAGS) | egrep -e '-g|-pg|IPTC_DEBUG' >/dev/null; then echo Remove debugging flags; exit 1; else exit 0; fi + +.PHONY: nowhitespace +nowhitespace: + @if grep -n '[ ]$$' `find . -name 'Makefile' -o -name '*.[ch]'`; then exit 1; else exit 0; fi + +.PHONY: delrelease +delrelease: + rm -f $(RELEASE_DIR)/iptables-$(IPTABLES_VERSION).tar.bz2 + +$(RELEASE_DIR)/iptables-$(IPTABLES_VERSION).tar.bz2: + cd .. && ln -sf userspace iptables-$(IPTABLES_VERSION) && tar cvf - --exclude CVS iptables-$(IPTABLES_VERSION)/. | bzip2 -9 > $@ && rm iptables-$(IPTABLES_VERSION) + +.PHONY: diff +diff: $(RELEASE_DIR)/iptables-$(IPTABLES_VERSION).tar.bz2 + @mkdir /tmp/diffdir + @cd /tmp/diffdir && tar -x --bzip2 -f $(RELEASE_DIR)/iptables-$(IPTABLES_VERSION).tar.bz2 + @set -e; cd /tmp/diffdir; tar -x --bzip2 -f $(RELEASE_DIR)/iptables-$(OLD_IPTABLES_VERSION).tar.bz2; echo Creating patch-iptables-$(OLD_IPTABLES_VERSION)-$(IPTABLES_VERSION).bz2; diff -urN iptables-$(OLD_IPTABLES_VERSION) iptables-$(IPTABLES_VERSION) | bzip2 -9 > $(RELEASE_DIR)/patch-iptables-$(OLD_IPTABLES_VERSION)-$(IPTABLES_VERSION).bz2 + @rm -rf /tmp/diffdir + +.PHONY: md5sums +md5sums: + cd $(RELEASE_DIR)/ && md5sum patch-iptables-*-$(IPTABLES_VERSION).bz2 iptables-$(IPTABLES_VERSION).tar.bz2 + +# $(wildcard) fails wierdly with make v.3.78.1. +include $(shell echo */Makefile) +include Rules.make diff --git a/extensions/.#libipt_ECN.c.1.7 b/extensions/.#libipt_ECN.c.1.7 new file mode 100644 index 0000000..7bb0044 --- /dev/null +++ b/extensions/.#libipt_ECN.c.1.7 @@ -0,0 +1,180 @@ +/* Shared library add-on to iptables for ECN, $Version$ + * + * (C) 2002 by Harald Welte + * + * This program is distributed under the terms of GNU GPL v2, 1991 + * + * libipt_ECN.c borrowed heavily from libipt_DSCP.c + * + * $Id: libipt_ECN.c,v 1.7 2002/05/29 15:11:36 laforge Exp $ + */ +#include +#include +#include +#include + +#include +#include +#include + +static void init(struct ipt_entry_target *t, unsigned int *nfcache) +{ +} + +static void help(void) +{ + printf( +"ECN target v%s options\n" +" --ecn-tcp-remove Remove all ECN bits from TCP header\n" +"ECN target v%s EXPERIMENTAL options (use with extreme care!)\n" +" --ecn-ip-ect Set the IPv4 ECT codepoint (0 to 3)\n" +" --ecn-tcp-cwr Set the IPv4 CWR bit (0 or 1)\n" +" --ecn-tcp-ece Set the IPv4 CWR bit (0 or 1)\n", + IPTABLES_VERSION, IPTABLES_VERSION +); +} + +static struct option opts[] = { + { "ecn-tcp-remove", 0, 0, 'F' }, + { "ecn-tcp-cwr", 1, 0, 'G' }, + { "ecn-tcp-ece", 1, 0, 'H' }, + { "ecn-ip-ect", 1, 0, '9' }, + { 0 } +}; + +static int +parse(int c, char **argv, int invert, unsigned int *flags, + const struct ipt_entry *entry, + struct ipt_entry_target **target) +{ + unsigned int result; + struct ipt_ECN_info *einfo + = (struct ipt_ECN_info *)(*target)->data; + + switch (c) { + case 'F': + if (*flags) + exit_error(PARAMETER_PROBLEM, + "ECN target: Only use --ecn-tcp-remove ONCE!"); + einfo->operation = IPT_ECN_OP_SET_ECE | IPT_ECN_OP_SET_CWR; + einfo->proto.tcp.ece = 0; + einfo->proto.tcp.cwr = 0; + *flags = 1; + break; + case 'G': + if (*flags & IPT_ECN_OP_SET_CWR) + exit_error(PARAMETER_PROBLEM, + "ECN target: Only use --ecn-tcp-cwr ONCE!"); + if (string_to_number(optarg, 0, 1, &result)) + exit_error(PARAMETER_PROBLEM, + "ECN target: Value out of range"); + einfo->operation |= IPT_ECN_OP_SET_CWR; + einfo->proto.tcp.cwr = result; + *flags |= IPT_ECN_OP_SET_CWR; + break; + case 'H': + if (*flags & IPT_ECN_OP_SET_ECE) + exit_error(PARAMETER_PROBLEM, + "ECN target: Only use --ecn-tcp-ece ONCE!"); + if (string_to_number(optarg, 0, 1, &result)) + exit_error(PARAMETER_PROBLEM, + "ECN target: Value out of range"); + einfo->operation |= IPT_ECN_OP_SET_ECE; + einfo->proto.tcp.ece = result; + *flags |= IPT_ECN_OP_SET_ECE; + break; + case '9': + if (*flags & IPT_ECN_OP_SET_IP) + exit_error(PARAMETER_PROBLEM, + "ECN target: Only use --ecn-ip-ect ONCE!"); + if (string_to_number(optarg, 0, 3, &result)) + exit_error(PARAMETER_PROBLEM, + "ECN target: Value out of range"); + einfo->operation |= IPT_ECN_OP_SET_IP; + einfo->ip_ect = (result << IPT_ECN_SHIFT); + default: + return 0; + } + + return 1; +} + +static void +final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, + "ECN target: Parameter --ecn-remove is required"); +} + +/* Prints out the targinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_target *target, + int numeric) +{ + const struct ipt_ECN_info *einfo = + (const struct ipt_ECN_info *)target->data; + + printf("ECN "); + + if (einfo->operation == (IPT_ECN_OP_SET_ECE|IPT_ECN_OP_SET_CWR) + && einfo->proto.tcp.ece == 0 + && einfo->proto.tcp.cwr == 0) + printf("TCP remove "); + else { + if (einfo->operation & IPT_ECN_OP_SET_ECE) + printf("ECE=%u ", einfo->proto.tcp.ece); + + if (einfo->operation & IPT_ECN_OP_SET_CWR) + printf("CWR=%u ", einfo->proto.tcp.cwr); + + if (einfo->operation & IPT_ECN_OP_SET_IP) + printf("ECT codepoint=%u ", einfo->ip_ect); + } +} + +/* Saves the union ipt_targinfo in parsable form to stdout. */ +static void +save(const struct ipt_ip *ip, const struct ipt_entry_target *target) +{ + const struct ipt_ECN_info *einfo = + (const struct ipt_ECN_info *)target->data; + + if (einfo->operation == (IPT_ECN_OP_SET_ECE|IPT_ECN_OP_SET_CWR) + && einfo->proto.tcp.ece == 0 + && einfo->proto.tcp.cwr == 0) + printf("--ecn-tcp-remove "); + else { + + if (einfo->operation & IPT_ECN_OP_SET_ECE) + printf("--ecn-tcp-ece %d ", einfo->proto.tcp.ece); + + if (einfo->operation & IPT_ECN_OP_SET_CWR) + printf("--ecn-tcp-cwr %d ", einfo->proto.tcp.cwr); + + if (einfo->operation & IPT_ECN_OP_SET_IP) + printf("--ecn-ip-ect %d ", einfo->ip_ect); + } +} + +static +struct iptables_target ecn += { NULL, + "ECN", + IPTABLES_VERSION, + IPT_ALIGN(sizeof(struct ipt_ECN_info)), + IPT_ALIGN(sizeof(struct ipt_ECN_info)), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_target(&ecn); +} diff --git a/extensions/.#libipt_recent.c.1.6 b/extensions/.#libipt_recent.c.1.6 new file mode 100644 index 0000000..cb86597 --- /dev/null +++ b/extensions/.#libipt_recent.c.1.6 @@ -0,0 +1,229 @@ +/* Shared library add-on to iptables to add recent matching support. */ +#include +#include +#include +#include +#include + +#include +#include + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"recent v%s options:\n" +"[!] --set Add source address to list, always matches.\n" +"[!] --rcheck Match if source address in list.\n" +"[!] --update Match if source address in list, also update last-seen time.\n" +"[!] --remove Match if source address in list, also removes that address from list.\n" +" --seconds seconds For check and update commands above.\n" +" Specifies that the match will only occur if source address last seen within\n" +" the last 'seconds' seconds.\n" +" --hitcount hits For check and update commands above.\n" +" Specifies that the match will only occur if source address seen hits times.\n" +" May be used in conjunction with the seconds option.\n" +" --rttl For check and update commands above.\n" +" Specifies that the match will only occur if the source address and the TTL\n" +" match between this packet and the one which was set.\n" +" Useful if you have problems with people spoofing their source address in order\n" +" to DoS you via this module.\n" +" --name name Name of the recent list to be used. DEFAULT used if none given.\n" +" --rsource Save the source address of each packet in the recent list table (default).\n" +" --rdest Save the destination address of each packet in the recent list table.\n" +, +IPTABLES_VERSION); + +} + +static struct option opts[] = { + { "set", 0, 0, 201 }, + { "rcheck", 0, 0, 202 }, + { "update", 0, 0, 203 }, + { "seconds", 1, 0, 204 }, + { "hitcount", 1, 0, 205 }, + { "remove",0, 0, 206 }, + { "rttl",0, 0, 207 }, + { "name", 1, 0, 208 }, + { "rsource", 0, 0, 209 }, + { "rdest", 0, 0, 210 }, + {0} +}; + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *match, unsigned int *nfcache) +{ + struct ipt_recent_info *info = (struct ipt_recent_info *)(match)->data; + + *nfcache |= NFC_UNKNOWN; + + strncpy(info->name, "DEFAULT", 200); + info->side = IPT_RECENT_SOURCE; +} + +/* Function which parses command options; returns true if it + ate an option */ +static int +parse(int c, char **argv, int invert, unsigned int *flags, + const struct ipt_entry *entry, + unsigned int *nfcache, + struct ipt_entry_match **match) +{ + struct ipt_recent_info *info = (struct ipt_recent_info *)(*match)->data; + switch (c) { + case 201: + if (*flags) + exit_error(PARAMETER_PROBLEM, + "recent: only one of `--set', `--check' " + "`--update' or `--remove' may be set"); + check_inverse(optarg, &invert, &optind, 0); + info->check_set |= IPT_RECENT_SET; + if (invert) + info->invert = 1; + *flags = 1; + break; + + case 202: + if (*flags) + exit_error(PARAMETER_PROBLEM, + "recent: only one of `--set', `--check' " + "`--update' or `--remove' may be set"); + check_inverse(optarg, &invert, &optind, 0); + info->check_set |= IPT_RECENT_CHECK; + if (invert) + info->invert = 1; + *flags = 1; + break; + + case 203: + if (*flags) + exit_error(PARAMETER_PROBLEM, + "recent: only one of `--set', `--check' " + "`--update' or `--remove' may be set"); + check_inverse(optarg, &invert, &optind, 0); + info->check_set |= IPT_RECENT_UPDATE; + if (invert) + info->invert = 1; + *flags = 1; + break; + + case 206: + if (*flags) + exit_error(PARAMETER_PROBLEM, + "recent: only one of `--set', `--check' " + "`--update' or `--remove' may be set"); + check_inverse(optarg, &invert, &optind, 0); + info->check_set |= IPT_RECENT_REMOVE; + if (invert) + info->invert = 1; + *flags = 1; + break; + + case 204: + info->seconds = atoi(optarg); + break; + + case 205: + info->hit_count = atoi(optarg); + break; + + case 207: + info->check_set |= IPT_RECENT_TTL; + break; + + case 208: + strncpy(info->name, optarg, 200); + break; + + case 209: + info->side = IPT_RECENT_SOURCE; + break; + + case 210: + info->side = IPT_RECENT_DEST; + break; + + default: + return 0; + } + + return 1; +} + +/* Final check; must have specified a specific option. */ +static void +final_check(unsigned int flags) +{ + + if (!flags) + exit_error(PARAMETER_PROBLEM, + "recent: you must specify one of `--set', `--check' " + "`--update' or `--remove'"); +} + +/* Prints out the matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, + int numeric) +{ + struct ipt_recent_info *info = (struct ipt_recent_info *)match->data; + + if (info->invert) fputc('!', stdout); + + printf("recent: "); + if(info->check_set & IPT_RECENT_SET) printf("SET "); + if(info->check_set & IPT_RECENT_CHECK) printf("CHECK "); + if(info->check_set & IPT_RECENT_UPDATE) printf("UPDATE "); + if(info->check_set & IPT_RECENT_REMOVE) printf("REMOVE "); + if(info->seconds) printf("seconds: %d ", info->seconds); + if(info->hit_count) printf("hit_count: %d ", info->hit_count); + if(info->check_set & IPT_RECENT_TTL) printf("TTL-Match "); + if(info->name) printf("name: %s ", info->name); + if(info->side == IPT_RECENT_SOURCE) printf("side: source "); + if(info->side == IPT_RECENT_DEST) printf("side: dest"); +} + +/* Saves the union ipt_matchinfo in parsable form to stdout. */ +static void +save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + struct ipt_recent_info *info = (struct ipt_recent_info *)match; + + if (info->invert) fputc('!', stdout); + + printf("recent: "); + if(info->check_set & IPT_RECENT_SET) printf("SET "); + if(info->check_set & IPT_RECENT_CHECK) printf("CHECK "); + if(info->check_set & IPT_RECENT_UPDATE) printf("UPDATE "); + if(info->check_set & IPT_RECENT_REMOVE) printf("REMOVE "); + if(info->seconds) printf("seconds: %d ",info->seconds); + if(info->hit_count) printf("hit_count: %d ",info->hit_count); + if(info->check_set & IPT_RECENT_TTL) printf("TTL-Match "); + if(info->name) printf("name: %s ",info->name); + if(info->side == IPT_RECENT_SOURCE) printf("side: source "); + if(info->side == IPT_RECENT_DEST) printf("side: dest"); +} + +static +struct iptables_match recent += { NULL, + "recent", + IPTABLES_VERSION, + IPT_ALIGN(sizeof(struct ipt_recent_info)), + IPT_ALIGN(sizeof(struct ipt_recent_info)), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_match(&recent); +} diff --git a/extensions/.addrtype-test b/extensions/.addrtype-test new file mode 100755 index 0000000..cda582b --- /dev/null +++ b/extensions/.addrtype-test @@ -0,0 +1,5 @@ +#!/bin/bash + +if test -f $KERNEL_DIR/include/linux/netfilter_ipv4/ipt_addrtype.h; then + echo "addrtype" +fi diff --git a/extensions/.pool-test b/extensions/.pool-test new file mode 100755 index 0000000..54d04bf --- /dev/null +++ b/extensions/.pool-test @@ -0,0 +1,2 @@ +#! /bin/sh +[ -f $KERNEL_DIR/include/linux/netfilter_ipv4/ip_pool.h ] && echo pool POOL diff --git a/extensions/libipt_POOL.c b/extensions/libipt_POOL.c new file mode 100644 index 0000000..931dcfd --- /dev/null +++ b/extensions/libipt_POOL.c @@ -0,0 +1,155 @@ +/* Shared library add-on to iptables to add IP pool mangling target. */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +/* FIXME --RR */ +#define ip_pool_init ip_POOL_init +#define ip_pool_get_index ip_POOL_get_index +#define ip_pool_get_name ip_POOL_get_name +#include "../ippool/libippool.c" + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"POOL v%s options:\n" +" --add-srcip \n" +" --del-srcip \n" +" --add-dstip \n" +" --del-dstip \n" +" add/del src/dst IP from pool.\n\n", +IPTABLES_VERSION); +} + +static struct option opts[] = { + { "add-srcip", 1, 0, '1' }, + { "del-srcip", 1, 0, '2' }, + { "add-dstip", 1, 0, '3' }, + { "del-dstip", 1, 0, '4' }, + { 0 } +}; + +/* Initialize the target. */ +static void +init(struct ipt_entry_target *target, unsigned int *nfcache) +{ + struct ipt_pool_info *ipi = (struct ipt_pool_info *) target->data; + + ipi->src = ipi->dst = IP_POOL_NONE; + ipi->flags = 0; + + /* Can't cache this */ + *nfcache |= NFC_UNKNOWN; +} + +/* Function which parses command options; returns true if it + ate an option */ +static int +parse(int c, char **argv, int invert, unsigned int *flags, + const struct ipt_entry *entry, + struct ipt_entry_target **target) +{ + struct ipt_pool_info *ipi = (struct ipt_pool_info *) (*target)->data; + switch (c) { + case '1': /* --add-srcip */ + ipi->src = ip_pool_get_index(optarg); + ipi->flags &= ~IPT_POOL_DEL_SRC; + break; + case '2': /* --del-srcip */ + ipi->src = ip_pool_get_index(optarg); + ipi->flags |= IPT_POOL_DEL_SRC; + break; + case '3': /* --add-dstip */ + ipi->dst = ip_pool_get_index(optarg); + ipi->flags &= ~IPT_POOL_DEL_DST; + break; + case '4': /* --del-dstip */ + ipi->dst = ip_pool_get_index(optarg); + ipi->flags |= IPT_POOL_DEL_DST; + break; + default: + return 0; + } + return 1; +} + +/* Final check; don't care. */ +static void final_check(unsigned int flags) +{ +} + +/* Prints out the targinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_target *target, + int numeric) +{ + char buf[256]; + struct ipt_pool_info *ipi = (struct ipt_pool_info *) target->data; + + printf("POOL"); + if (ipi->src != IP_POOL_NONE) { + printf(" --%s-srcip %s", + (ipi->flags & IPT_POOL_DEL_SRC) ? "del" : "add", + ip_pool_get_name(buf, sizeof(buf), ipi->src, numeric)); + } + if (ipi->dst != IP_POOL_NONE) { + printf(" --%s-dstip %s", + (ipi->flags & IPT_POOL_DEL_DST) ? "del" : "add", + ip_pool_get_name(buf, sizeof(buf), ipi->dst, numeric)); + } +} + +/* Saves the union ipt_targinfo in parsable form to stdout. */ +static void +save(const struct ipt_ip *ip, const struct ipt_entry_target *target) +{ + char buf[256]; + struct ipt_pool_info *ipi = (struct ipt_pool_info *) target->data; + + printf("-j POOL"); + if (ipi->src != IP_POOL_NONE) { + printf(" --%s-srcip %s", + (ipi->flags & IPT_POOL_DEL_SRC) ? "del" : "add", + ip_pool_get_name(buf, sizeof(buf), ipi->src, 0)); + } + if (ipi->dst != IP_POOL_NONE) { + printf(" --%s-dstip %s", + (ipi->flags & IPT_POOL_DEL_DST) ? "del" : "add", + ip_pool_get_name(buf, sizeof(buf), ipi->dst, 0)); + } +} + +static +struct iptables_target ipt_pool_target += { NULL, + "POOL", + IPTABLES_VERSION, + IPT_ALIGN(sizeof(struct ipt_pool_info)), + IPT_ALIGN(sizeof(struct ipt_pool_info)), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_target(&ipt_pool_target); +} diff --git a/extensions/libipt_icmp.c.print_type b/extensions/libipt_icmp.c.print_type new file mode 100644 index 0000000..6c7b1c3 --- /dev/null +++ b/extensions/libipt_icmp.c.print_type @@ -0,0 +1,310 @@ +/* Shared library add-on to iptables to add ICMP support. */ +#include +#include +#include +#include +#include +#include +#include + +/* special hack for icmp-type 'any': + * Up to kernel <=2.4.20 the problem was: + * '-p icmp ' matches all icmp packets + * '-p icmp -m icmp' matches _only_ ICMP type 0 :( + * This is now fixed by initializing the field * to icmp type 0xFF + * See: https://bugzilla.netfilter.org/cgi-bin/bugzilla/show_bug.cgi?id=37 + */ + +struct icmp_names { + const char *name; + u_int8_t type; + u_int8_t code_min, code_max; +}; + +static const struct icmp_names icmp_codes[] = { + { "any", 0xFF, 0, 0xFF }, + { "echo-reply", 0, 0, 0xFF }, + /* Alias */ { "pong", 0, 0, 0xFF }, + + { "destination-unreachable", 3, 0, 0xFF }, + { "network-unreachable", 3, 0, 0 }, + { "host-unreachable", 3, 1, 1 }, + { "protocol-unreachable", 3, 2, 2 }, + { "port-unreachable", 3, 3, 3 }, + { "fragmentation-needed", 3, 4, 4 }, + { "source-route-failed", 3, 5, 5 }, + { "network-unknown", 3, 6, 6 }, + { "host-unknown", 3, 7, 7 }, + { "network-prohibited", 3, 9, 9 }, + { "host-prohibited", 3, 10, 10 }, + { "TOS-network-unreachable", 3, 11, 11 }, + { "TOS-host-unreachable", 3, 12, 12 }, + { "communication-prohibited", 3, 13, 13 }, + { "host-precedence-violation", 3, 14, 14 }, + { "precedence-cutoff", 3, 15, 15 }, + + { "source-quench", 4, 0, 0xFF }, + + { "redirect", 5, 0, 0xFF }, + { "network-redirect", 5, 0, 0 }, + { "host-redirect", 5, 1, 1 }, + { "TOS-network-redirect", 5, 2, 2 }, + { "TOS-host-redirect", 5, 3, 3 }, + + { "echo-request", 8, 0, 0xFF }, + /* Alias */ { "ping", 8, 0, 0xFF }, + + { "router-advertisement", 9, 0, 0xFF }, + + { "router-solicitation", 10, 0, 0xFF }, + + { "time-exceeded", 11, 0, 0xFF }, + /* Alias */ { "ttl-exceeded", 11, 0, 0xFF }, + { "ttl-zero-during-transit", 11, 0, 0 }, + { "ttl-zero-during-reassembly", 11, 1, 1 }, + + { "parameter-problem", 12, 0, 0xFF }, + { "ip-header-bad", 12, 0, 0 }, + { "required-option-missing", 12, 1, 1 }, + + { "timestamp-request", 13, 0, 0xFF }, + + { "timestamp-reply", 14, 0, 0xFF }, + + { "address-mask-request", 17, 0, 0xFF }, + + { "address-mask-reply", 18, 0, 0xFF } +}; + +static void +print_icmptypes() +{ + unsigned int i; + printf("Valid ICMP Types:"); + + for (i = 0; i < sizeof(icmp_codes)/sizeof(struct icmp_names); i++) { + if (i && icmp_codes[i].type == icmp_codes[i-1].type) { + if (icmp_codes[i].code_min == icmp_codes[i-1].code_min + && (icmp_codes[i].code_max + == icmp_codes[i-1].code_max)) + printf(" (%s)", icmp_codes[i].name); + else + printf("\n %s", icmp_codes[i].name); + } + else + printf("\n%s", icmp_codes[i].name); + } + printf("\n"); +} + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"ICMP v%s options:\n" +" --icmp-type [!] typename match icmp type\n" +" (or numeric type or type/code)\n" +"\n", IPTABLES_VERSION); + print_icmptypes(); +} + +static struct option opts[] = { + { "icmp-type", 1, 0, '1' }, + {0} +}; + +static unsigned int +parse_icmp(const char *icmptype, u_int8_t *type, u_int8_t code[]) +{ + unsigned int limit = sizeof(icmp_codes)/sizeof(struct icmp_names); + unsigned int match = limit; + unsigned int i; + + for (i = 0; i < limit; i++) { + if (strncasecmp(icmp_codes[i].name, icmptype, strlen(icmptype)) + == 0) { + if (match != limit) + exit_error(PARAMETER_PROBLEM, + "Ambiguous ICMP type `%s':" + " `%s' or `%s'?", + icmptype, + icmp_codes[match].name, + icmp_codes[i].name); + match = i; + } + } + + if (match != limit) { + *type = icmp_codes[match].type; + code[0] = icmp_codes[match].code_min; + code[1] = icmp_codes[match].code_max; + } else { + char *slash; + char buffer[strlen(icmptype) + 1]; + unsigned int number; + + strcpy(buffer, icmptype); + slash = strchr(buffer, '/'); + + if (slash) + *slash = '\0'; + + if (string_to_number(buffer, 0, 255, &number) == -1) + exit_error(PARAMETER_PROBLEM, + "Invalid ICMP type `%s'\n", buffer); + *type = number; + if (slash) { + if (string_to_number(slash+1, 0, 255, &number) == -1) + exit_error(PARAMETER_PROBLEM, + "Invalid ICMP code `%s'\n", + slash+1); + code[0] = code[1] = number; + } else { + code[0] = 0; + code[1] = 0xFF; + } + } + + if (code[0] == 0 && code[1] == 0xFF) + return NFC_IP_SRC_PT; + else return NFC_IP_SRC_PT | NFC_IP_DST_PT; +} + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *m, unsigned int *nfcache) +{ + struct ipt_icmp *icmpinfo = (struct ipt_icmp *)m->data; + + icmpinfo->type = 0xFF; + icmpinfo->code[1] = 0xFF; +} + +/* Function which parses command options; returns true if it + ate an option */ +static int +parse(int c, char **argv, int invert, unsigned int *flags, + const struct ipt_entry *entry, + unsigned int *nfcache, + struct ipt_entry_match **match) +{ + struct ipt_icmp *icmpinfo = (struct ipt_icmp *)(*match)->data; + + switch (c) { + case '1': + check_inverse(optarg, &invert, &optind, 0); + *nfcache |= parse_icmp(argv[optind-1], + &icmpinfo->type, + icmpinfo->code); + if (invert) + icmpinfo->invflags |= IPT_ICMP_INV; + break; + + default: + return 0; + } + + return 1; +} + +static void print_icmptype(u_int8_t type, + u_int8_t code_min, u_int8_t code_max, + int invert, + int numeric) +{ + if (!numeric) { + unsigned int i; + + for (i = 0; + i < sizeof(icmp_codes)/sizeof(struct icmp_names); + i++) { + if (icmp_codes[i].type == type + && icmp_codes[i].code_min == code_min + && icmp_codes[i].code_max == code_max) + break; + } + + if (i != sizeof(icmp_codes)/sizeof(struct icmp_names)) { + printf("%s%s ", + invert ? "!" : "", + icmp_codes[i].name); + return; + } + } + + if (invert) + printf("!"); + + printf("type %u", type); + if (code_min == 0 && code_max == 0xFF) + printf(" "); + else if (code_min == code_max) + printf(" code %u ", code_min); + else + printf(" codes %u-%u ", code_min, code_max); +} + +/* Prints out the union ipt_matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, + int numeric) +{ + const struct ipt_icmp *icmp = (struct ipt_icmp *)match->data; + + printf("icmp "); + print_icmptype(icmp->type, icmp->code[0], icmp->code[1], + icmp->invflags & IPT_ICMP_INV, + numeric); + + if (icmp->invflags & ~IPT_ICMP_INV) + printf("Unknown invflags: 0x%X ", + icmp->invflags & ~IPT_ICMP_INV); +} + +/* Saves the match in parsable form to stdout. */ +static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + const struct ipt_icmp *icmp = (struct ipt_icmp *)match->data; + + if (icmp->invflags & IPT_ICMP_INV) + printf("! "); + + /* special hack for 'any' case */ + if (icmp->type == 0xFF) { + print_icmptype(icmp->type, icmp->code[0], icmp->code[1], + icmp->invflags & IPT_ICMP_INV, 0); + } else { + printf("--icmp-type %u", icmp->type); + if (icmp->code[0] != 0 || icmp->code[1] != 0xFF) + printf("/%u", icmp->code[0]); + printf(" "); + } +} + +/* Final check; we don't care. */ +static void final_check(unsigned int flags) +{ +} + +static +struct iptables_match icmp += { NULL, + "icmp", + IPTABLES_VERSION, + IPT_ALIGN(sizeof(struct ipt_icmp)), + IPT_ALIGN(sizeof(struct ipt_icmp)), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_match(&icmp); +} diff --git a/extensions/libipt_pool.c b/extensions/libipt_pool.c new file mode 100644 index 0000000..082d76c --- /dev/null +++ b/extensions/libipt_pool.c @@ -0,0 +1,144 @@ +/* Shared library add-on to iptables to add IP address pool matching. */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* FIXME --RR */ +#include "../ippool/libippool.c" + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"pool v%s options:\n" +" [!] --srcpool NAME|INDEX\n" +" [!] --dstpool NAME|INDEX\n" +" Pool index (or name from %s) to match\n" +"\n", IPTABLES_VERSION, IPPOOL_CONF); +} + +static struct option opts[] = { + { "srcpool", 1, 0, '1' }, + { "dstpool", 1, 0, '2' }, + {0} +}; + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *match, unsigned int *nfcache) +{ + struct ipt_pool_info *info = + (struct ipt_pool_info *)match->data; + + info->src = IP_POOL_NONE; + info->dst = IP_POOL_NONE; + info->flags = 0; + /* Can't cache this - XXX */ + *nfcache |= NFC_UNKNOWN; +} + +/* Function which parses command options; returns true if it ate an option */ +static int +parse(int c, char **argv, int invert, unsigned int *flags, + const struct ipt_entry *entry, + unsigned int *nfcache, + struct ipt_entry_match **match) +{ + struct ipt_pool_info *info = + (struct ipt_pool_info *)(*match)->data; + + switch (c) { + case '1': + check_inverse(optarg, &invert, &optind, 0); + info->src = ip_pool_get_index(argv[optind-1]); + if (invert) info->flags |= IPT_POOL_INV_SRC; + *flags = 1; + break; + case '2': + check_inverse(optarg, &invert, &optind, 0); + info->dst = ip_pool_get_index(argv[optind-1]); + if (invert) info->flags |= IPT_POOL_INV_DST; + *flags = 1; + break; + + default: + return 0; + } + + return 1; +} + +/* Final check; must have specified --srcpool or --dstpool. */ +static void final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, "You must specify either `--srcpool or --dstpool'"); +} + +/* Prints out the matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, + int numeric) +{ + char buf[256]; + struct ipt_pool_info *info = + (struct ipt_pool_info *)match->data; + + if (info->src != IP_POOL_NONE) + printf("%ssrcpool %s ", + (info->flags & IPT_POOL_INV_SRC) ? "!" : "", + ip_pool_get_name(buf, sizeof(buf), info->src, 0)); + if (info->dst != IP_POOL_NONE) + printf("%sdstpool %s ", + (info->flags & IPT_POOL_INV_DST) ? "!" : "", + ip_pool_get_name(buf, sizeof(buf), info->dst, 0)); +} + +/* Saves the matchinfo in parsable form to stdout. */ +static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + char buf[256]; + struct ipt_pool_info *info = + (struct ipt_pool_info *)match->data; + + if (info->src != IP_POOL_NONE) + printf("%s--srcpool %s ", + (info->flags & IPT_POOL_INV_SRC) ? "! " : "", + ip_pool_get_name(buf, sizeof(buf), info->src, 0)); + if (info->dst != IP_POOL_NONE) + printf("%s--dstpool %s ", + (info->flags & IPT_POOL_INV_DST) ? "! " : "", + ip_pool_get_name(buf, sizeof(buf), info->dst, 0)); +} + +static +struct iptables_match pool += { NULL, + "pool", + IPTABLES_VERSION, + IPT_ALIGN(sizeof(struct ipt_pool_info)), + IPT_ALIGN(sizeof(struct ipt_pool_info)), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_match(&pool); +} diff --git a/extensions/libipt_pool.c.orig b/extensions/libipt_pool.c.orig new file mode 100644 index 0000000..666599d --- /dev/null +++ b/extensions/libipt_pool.c.orig @@ -0,0 +1,144 @@ +/* Shared library add-on to iptables to add IP address pool matching. */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* FIXME --RR */ +#include "../ippool/libippool.c" + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"pool v%s options:\n" +" [!] --srcpool NAME|INDEX\n" +" [!] --dstpool NAME|INDEX\n" +" Pool index (or name from %s) to match\n" +"\n", IPTABLES_VERSION, IPPOOL_CONF); +} + +static struct option opts[] = { + { "srcpool", 1, 0, '1' }, + { "dstpool", 1, 0, '2' }, + {0} +}; + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *match, unsigned int *nfcache) +{ + struct ipt_pool_info *info = + (struct ipt_pool_info *)match->data; + + info->src = IP_POOL_NONE; + info->dst = IP_POOL_NONE; + info->flags = 0; + /* Can't cache this - XXX */ + *nfcache |= NFC_UNKNOWN; +} + +/* Function which parses command options; returns true if it ate an option */ +static int +parse(int c, char **argv, int invert, unsigned int *flags, + const struct ipt_entry *entry, + unsigned int *nfcache, + struct ipt_entry_match **match) +{ + struct ipt_pool_info *info = + (struct ipt_pool_info *)(*match)->data; + + switch (c) { + case '1': + check_inverse(optarg, &invert, &optind, 0); + info->src = ip_pool_get_index(argv[optind-1]); + if (invert) info->flags |= IPT_POOL_INV_SRC; + *flags = 1; + break; + case '2': + check_inverse(optarg, &invert, &optind, 0); + info->dst = ip_pool_get_index(argv[optind-1]); + if (invert) info->flags |= IPT_POOL_INV_DST; + *flags = 1; + break; + + default: + return 0; + } + + return 1; +} + +/* Final check; must have specified --srcpool or --dstpool. */ +static void final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, "You must specify either `--srcpool or --dstpool'"); +} + +/* Prints out the matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, + int numeric) +{ + char buf[256]; + struct ipt_pool_info *info = + (struct ipt_pool_info *)match->data; + + if (info->src != IP_POOL_NONE) + printf("%ssrcpool %s ", + (info->flags & IPT_POOL_INV_SRC) ? "!" : "", + ip_pool_get_name(buf, sizeof(buf), info->src, 0)); + if (info->dst != IP_POOL_NONE) + printf("%sdstpool %s ", + (info->flags & IPT_POOL_INV_DST) ? "!" : "", + ip_pool_get_name(buf, sizeof(buf), info->dst, 0)); +} + +/* Saves the matchinfo in parsable form to stdout. */ +static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + char buf[256]; + struct ipt_pool_info *info = + (struct ipt_pool_info *)match->data; + + if (info->src != IP_POOL_NONE) + printf("%s--srcpool %s", + (info->flags & IPT_POOL_INV_SRC) ? "! " : "", + ip_pool_get_name(buf, sizeof(buf), info->src, 0)); + if (info->dst != IP_POOL_NONE) + printf("%s--dstpool %s", + (info->flags & IPT_POOL_INV_DST) ? "! " : "", + ip_pool_get_name(buf, sizeof(buf), info->dst, 0)); +} + +static +struct iptables_match pool += { NULL, + "pool", + IPTABLES_VERSION, + IPT_ALIGN(sizeof(struct ipt_pool_info)), + IPT_ALIGN(sizeof(struct ipt_pool_info)), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_match(&pool); +} diff --git a/include/libippool/ip_pool_support.h b/include/libippool/ip_pool_support.h new file mode 100644 index 0000000..4a87f02 --- /dev/null +++ b/include/libippool/ip_pool_support.h @@ -0,0 +1,26 @@ +/* support function prototypes for IP pool management (config file, mostly) */ +#ifndef _IP_POOL_SUPPORT_H +#define _IP_POOL_SUPPORT_H + +#include +#include +#include + +#ifndef IPPOOL_CONF +#define IPPOOL_CONF "/etc/ippool.conf" +#endif + +/* called just to draw in this support .o */ +void ip_pool_init(void); + +/* given a pool name (or number), return pool index, possibly reading .conf */ +ip_pool_t ip_pool_get_index(char *name); + +/* given a pool index, and a buffer to store a name, search for the index + * in the .conf file, and give the textual name, if present; if not, the + * numeric index is returned. If numeric_flag == 1, the numeric index is + * always returned + */ +char *ip_pool_get_name(char *buf, int size, ip_pool_t index, int numeric_flag); + +#endif /*_IP_POOL_SUPPORT_H*/ diff --git a/ip6tables.8 b/ip6tables.8 new file mode 100644 index 0000000..53a310c --- /dev/null +++ b/ip6tables.8 @@ -0,0 +1,821 @@ +.TH IP6TABLES 8 "Mar 09, 2002" "" "" +.\" +.\" 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 \- IPv6 packet filter administration +.SH SYNOPSIS +.BR "ip6tables [-t table] -[AD] " "chain rule-specification [options]" +.br +.BR "ip6tables [-t table] -I " "chain [rulenum] rule-specification [options]" +.br +.BR "ip6tables [-t table] -R " "chain rulenum rule-specification [options]" +.br +.BR "ip6tables [-t table] -D " "chain rulenum [options]" +.br +.BR "ip6tables [-t table] -[LFZ] " "[chain] [options]" +.br +.BR "ip6tables [-t table] -N " "chain" +.br +.BR "ip6tables [-t table] -X " "[chain]" +.br +.BR "ip6tables [-t table] -P " "chain target [options]" +.br +.BR "ip6tables [-t table] -E " "old-chain-name new-chain-name" +.SH DESCRIPTION +.B Ip6tables +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. + +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 +.IR ACCEPT , +.IR DROP , +.IR QUEUE , +or +.IR RETURN . +.PP +.I ACCEPT +means to let the packet through. +.I DROP +means to drop the packet on the floor. +.I QUEUE +means to pass the packet to userspace (if supported by the kernel). +.I RETURN +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 +.I RETURN +is matched, the target specified by the chain policy determines the +fate of the packet. +.SH TABLES +There are currently two independent tables (which tables are present +at any time depends on the kernel configuration options and which +modules are present), as nat table has not been implemented yet. +.TP +.BI "-t, --table " "table" +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 +.BR "filter" : +This is the default table (if no -t option is passed). It contains +the built-in chains +.B INPUT +(for packets coming into the box itself), +.B FORWARD +(for packets being routed through the box), and +.B OUTPUT +(for locally-generated packets). +.TP +.BR "mangle" : +This table is used for specialized packet alteration. Until kernel +2.4.17 it had two built-in chains: +.B PREROUTING +(for altering incoming packets before routing) and +.B OUTPUT +(for altering locally-generated packets before routing). +Since kernel 2.4.18, three other built-in chains are also supported: +.B INPUT +(for packets coming into the box itself), +.B FORWARD +(for altering packets being routed through the box), and +.B POSTROUTING +(for altering packets as they are about to go out). +.RE +.SH OPTIONS +The options that are recognized by +.B ip6tables +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 +.B ip6tables +can differentiate it from all other options. +.TP +.BI "-A, --append " "chain rule-specification" +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 +.BI "-D, --delete " "chain rule-specification" +.ns +.TP +.BI "-D, --delete " "chain rulenum" +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 +.B "-I, --insert" +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 +.BI "-R, --replace " "chain rulenum rule-specification" +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 +.BR "-L, --list " "[\fIchain\fP]" +List all rules in the selected chain. If no chain is selected, all +chains are listed. As every other iptables command, it applies to the +specified table (filter is the default), so mangle rules get listed by +.nf + ip6tables -t mangle -n -L +.fi +Please note that it is often used with the +.B -n +option, in order to avoid long reverse DNS lookups. +It is legal to specify the +.B -Z +(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 +.BR "-F, --flush " "[\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 +.BR "-Z, --zero " "[\fIchain\fP]" +Zero the packet and byte counters in all chains. It is legal to +specify the +.B "-L, --list" +(list) option as well, to see the counters immediately before they are +cleared. (See above.) +.TP +.BI "-N, --new-chain " "chain" +Create a new user-defined chain by the given name. There must be no +target of that name already. +.TP +.BR "-X, --delete-chain " "[\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. If no argument is given, it +will attempt to delete every non-builtin chain in the table. +.TP +.BI "-P, --policy " "chain target" +Set the policy for the chain to the given target. See the section +.B TARGETS +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 +.BI "-E, --rename-chain " "old-chain new-chain" +Rename the user specified chain to the user supplied name. This is +cosmetic, and has no effect on the structure of the table. +.TP +.B -h +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 +.BR "-p, --protocol " "[!] \fIprotocol\fP" +The protocol of the rule or of the packet to check. +The specified protocol can be one of +.IR tcp , +.IR udp , +.IR ipv6-icmp|icmpv6 , +or +.IR all , +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 +.IR all . +Protocol +.I all +will match with all protocols and is taken as default when this +option is omitted. +.TP +.BR "-s, --source " "[!] \fIaddress\fP[/\fImask\fP]" +Source specification. +.I Address +can be either a hostname (please note that specifying +any name to be resolved with a remote query such as DNS is a really bad idea), +a network IPv6 address (with /mask), or a plain IPv6 address. +(the network name isn't supported now). +The +.I mask +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 +.I 64 +is equivalent to +.IR ffff:ffff:ffff:ffff:0000:0000:0000:0000 . +A "!" argument before the address specification inverts the sense of +the address. The flag +.B --src +is an alias for this option. +.TP +.BR "-d, --destination " "[!] \fIaddress\fP[/\fImask\fP]" +Destination specification. +See the description of the +.B -s +(source) flag for a detailed description of the syntax. The flag +.B --dst +is an alias for this option. +.TP +.BI "-j, --jump " "target" +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 +.B EXTENSIONS +below). If this +option is omitted in a rule, then matching the rule will have no +effect on the packet's fate, but the counters on the rule will be +incremented. +.TP +.BR "-i, --in-interface " "[!] \fIname\fP" +Name of an interface via which a packet is going to be received (only for +packets entering the +.BR INPUT , +.B FORWARD +and +.B PREROUTING +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 +.BR "-o, --out-interface " "[!] \fIname\fP" +Name of an interface via which a packet is going to be sent (for packets +entering the +.BR FORWARD +and +.B OUTPUT +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 +.\" Currently not supported (header-based) +.\" +.\" .B "[!] " "-f, --fragment" +.\" 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 +.B "-c, --set-counters " "PKTS BYTES" +This enables the administrator to initialize the packet and byte +counters of a rule (during +.B INSERT, +.B APPEND, +.B REPLACE +operations). +.SS "OTHER OPTIONS" +The following additional options can be specified: +.TP +.B "-v, --verbose" +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 +.B -x +flag to change this). +For appending, insertion, deletion and replacement, this causes +detailed information on the rule or rules to be printed. +.TP +.B "-n, --numeric" +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 +.B "-x, --exact" +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 +.B -L +command. +.TP +.B "--line-numbers" +When listing rules, add line numbers to the beginning of each rule, +corresponding to that rule's position in the chain. +.TP +.B "--modprobe=command" +When adding or inserting rules into a chain, use +.B command +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 +.B -p +or +.B --protocol +is specified, or with the +.B -m +or +.B --match +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 +.B -h +or +.B --help +options after the module has been specified to receive help specific +to that module. + +The following are included in the base package, and most of these can +be preceded by a +.B ! +to invert the sense of the match. +.SS tcp +These extensions are loaded if `--protocol tcp' is specified. It +provides the following options: +.TP +.BR "--source-port " "[!] \fIport\fP[:\fIport\fP]" +Source port or port range specification. This can either be a service +name or a port number. An inclusive range can also be specified, +using the format +.IR port : port . +If the first port is omitted, "0" is assumed; if the last is omitted, +"65535" is assumed. +If the second port greater then the first they will be swapped. +The flag +.B --sport +is a convenient alias for this option. +.TP +.BR "--destination-port " "[!] \fIport\fP[:\fIport\fP]" +Destination port or port range specification. The flag +.B --dport +is a convenient alias for this option. +.TP +.BR "--tcp-flags " "[!] \fImask\fP \fIcomp\fP" +Match when the TCP flags are as specified. The first argument is the +flags which we should examine, written as a comma-separated list, and +the second argument is a comma-separated list of flags which must be +set. Flags are: +.BR "SYN ACK FIN RST URG PSH ALL NONE" . +Hence the command +.nf + ip6tables -A FORWARD -p tcp --tcp-flags SYN,ACK,FIN,RST SYN +.fi +will only match packets with the SYN flag set, and the ACK, FIN and +RST flags unset. +.TP +.B "[!] --syn" +Only match TCP packets with the SYN bit set and the ACK and RST bits +cleared. Such packets are used to request TCP connection initiation; +for example, blocking such packets coming in an interface will prevent +incoming TCP connections, but outgoing TCP connections will be +unaffected. +It is equivalent to \fB--tcp-flags SYN,RST,ACK SYN\fP. +If the "!" flag precedes the "--syn", the sense of the +option is inverted. +.TP +.BR "--tcp-option " "[!] \fInumber\fP" +Match if TCP option set. +.SS udp +These extensions are loaded if `--protocol udp' is specified. It +provides the following options: +.TP +.BR "--source-port " "[!] \fIport\fP[:\fIport\fP]" +Source port or port range specification. +See the description of the +.B --source-port +option of the TCP extension for details. +.TP +.BR "--destination-port " "[!] \fIport\fP[:\fIport\fP]" +Destination port or port range specification. +See the description of the +.B --destination-port +option of the TCP extension for details. +.SS ipv6-icmp +This extension is loaded if `--protocol ipv6-icmp' or `--protocol icmpv6' is +specified. It provides the following option: +.TP +.BR "--icmpv6-type " "[!] \fItypename\fP" +This allows specification of the ICMP type, which can be a numeric +IPv6-ICMP type, or one of the IPv6-ICMP type names shown by the command +.nf + ip6tables -p ipv6-icmp -h +.fi +.SS mac +.TP +.BR "--mac-source " "[!] \fIaddress\fP" +Match source MAC address. It must be of the form XX:XX:XX:XX:XX:XX. +Note that this only makes sense for packets coming from an Ethernet device +and entering the +.BR PREROUTING , +.B FORWARD +or +.B INPUT +chains. +.SS limit +This module matches at a limited rate using a token bucket filter. +A rule using this extension will match until this limit is reached +(unless the `!' flag is used). It can be used in combination with the +.B LOG +target to give limited logging, for example. +.TP +.BI "--limit " "rate" +Maximum average matching rate: specified as a number, with an optional +`/second', `/minute', `/hour', or `/day' suffix; the default is +3/hour. +.TP +.BI "--limit-burst " "number" +Maximum initial number of packets to match: this number gets +recharged by one every time the limit specified above is not reached, +up to this number; the default is 5. +.SS multiport +This module matches a set of source or destination ports. Up to 15 +ports can be specified. It can only be used in conjunction with +.B "-p tcp" +or +.BR "-p udp" . +.TP +.BR "--source-ports " "\fIport\fP[,\fIport\fP[,\fIport\fP...]]" +Match if the source port is one of the given ports. The flag +.B --sports +is a convenient alias for this option. +.TP +.BR "--destination-ports " "\fIport\fP[,\fIport\fP[,\fIport\fP...]]" +Match if the destination port is one of the given ports. The flag +.B --dports +is a convenient alias for this option. +.TP +.BR "--ports " "\fIport\fP[,\fIport\fP[,\fIport\fP...]]" +Match if the both the source and destination ports are equal to each +other and to one of the given ports. +.SS mark +This module matches the netfilter mark field associated with a packet +(which can be set using the +.B MARK +target below). +.TP +.BR "--mark " "\fIvalue\fP[/\fImask\fP]" +Matches packets with the given unsigned mark value (if a mask is +specified, this is logically ANDed with the mask before the +comparison). +.SS owner +This module attempts to match various characteristics of the packet +creator, for locally-generated packets. It is only valid in the +.B OUTPUT +chain, and even this some packets (such as ICMP ping responses) may +have no owner, and hence never match. This is regarded as experimental. +.TP +.BI "--uid-owner " "userid" +Matches if the packet was created by a process with the given +effective user id. +.TP +.BI "--gid-owner " "groupid" +Matches if the packet was created by a process with the given +effective group id. +.TP +.BI "--pid-owner " "processid" +Matches if the packet was created by a process with the given +process id. +.TP +.BI "--sid-owner " "sessionid" +Matches if the packet was created by a process in the given session +group. +.\" .SS state +.\" This module, when combined with connection tracking, allows access to +.\" the connection tracking state for this packet. +.\" .TP +.\" .BI "--state " "state" +.\" Where state is a comma separated list of the connection states to +.\" match. Possible states are +.\" .B INVALID +.\" meaning that the packet is associated with no known connection, +.\" .B ESTABLISHED +.\" meaning that the packet is associated with a connection which has seen +.\" packets in both directions, +.\" .B NEW +.\" meaning that the packet has started a new connection, or otherwise +.\" associated with a connection which has not seen packets in both +.\" directions, and +.\" .B RELATED +.\" meaning that the packet is starting a new connection, but is +.\" associated with an existing connection, such as an FTP data transfer, +.\" or an ICMP error. +.\" .SS unclean +.\" This module takes no options, but attempts to match packets which seem +.\" malformed or unusual. This is regarded as experimental. +.\" .SS tos +.\" This module matches the 8 bits of Type of Service field in the IP +.\" header (ie. including the precedence bits). +.\" .TP +.\" .BI "--tos " "tos" +.\" The argument is either a standard name, (use +.\" .br +.\" iptables -m tos -h +.\" .br +.\" to see the list), or a numeric value to match. +.SH TARGET EXTENSIONS +ip6tables can use extended target modules: the following are included +in the standard distribution. +.SS LOG +Turn on kernel logging of matching packets. When this option is set +for a rule, the Linux kernel will print some information on all +matching packets (like most IPv6 IPv6-header fields) via the kernel log +(where it can be read with +.I dmesg +or +.IR syslogd (8)). +This is a "non-terminating target", i.e. rule traversal continues at +the next rule. So if you want to LOG the packets you refuse, use two +separate rules with the same matching criteria, first using target LOG +then DROP (or REJECT). +.TP +.BI "--log-level " "level" +Level of logging (numeric or see \fIsyslog.conf\fP(5)). +.TP +.BI "--log-prefix " "prefix" +Prefix log messages with the specified prefix; up to 29 letters long, +and useful for distinguishing messages in the logs. +.TP +.B --log-tcp-sequence +Log TCP sequence numbers. This is a security risk if the log is +readable by users. +.TP +.B --log-tcp-options +Log options from the TCP packet header. +.TP +.B --log-ip-options +Log options from the IPv6 packet header. +.SS MARK +This is used to set the netfilter mark value associated with the +packet. It is only valid in the +.B mangle +table. +.TP +.BI "--set-mark " "mark" +.SS REJECT +This is used to send back an error packet in response to the matched +packet: otherwise it is equivalent to +.B DROP +so it is a terminating TARGET, ending rule traversal. +This target is only valid in the +.BR INPUT , +.B FORWARD +and +.B OUTPUT +chains, and user-defined chains which are only called from those +chains. The following option controls the nature of the error packet +returned: +.TP +.BI "--reject-with " "type" +The type given can be +.nf +.B " icmp6-no-route" +.B " no-route" +.B " icmp6-adm-prohibited" +.B " adm-prohibited" +.B " icmp6-addr-unreachable" +.B " addr-unreach" +.B " icmp6-port-unreachable" +.B " port-unreach" +.fi +which return the appropriate IPv6-ICMP error message (\fBport-unreach\fP is +the default). Finally, the option +.B tcp-reset +can be used on rules which only match the TCP protocol: this causes a +TCP RST packet to be sent back. This is mainly useful for blocking +.I ident +(113/tcp) probes which frequently occur when sending mail to broken mail +hosts (which won't accept your mail otherwise). +.\" .SS TOS +.\" This is used to set the 8-bit Type of Service field in the IP header. +.\" It is only valid in the +.\" .B mangle +.\" table. +.\" .TP +.\" .BI "--set-tos " "tos" +.\" You can use a numeric TOS values, or use +.\" .br +.\" iptables -j TOS -h +.\" .br +.\" to see the list of valid TOS names. +.\" .SS MIRROR +.\" This is an experimental demonstration target which inverts the source +.\" and destination fields in the IP header and retransmits the packet. +.\" It is only valid in the +.\" .BR INPUT , +.\" .B FORWARD +.\" and +.\" .B PREROUTING +.\" chains, and user-defined chains which are only called from those +.\" chains. Note that the outgoing packets are +.\" .B NOT +.\" seen by any packet filtering chains, connection tracking or NAT, to +.\" avoid loops and other problems. +.\" .SS SNAT +.\" This target is only valid in the +.\" .B nat +.\" table, in the +.\" .B POSTROUTING +.\" chain. It specifies that the source address of the packet should be +.\" modified (and all future packets in this connection will also be +.\" mangled), and rules should cease being examined. It takes one option: +.\" .TP +.\" .BR "--to-source " "\fIipaddr\fP[-\fIipaddr\fP][:\fIport\fP-\fIport\fP]" +.\" which can specify a single new source IP address, an inclusive range +.\" of IP addresses, and optionally, a port range (which is only valid if +.\" the rule also specifies +.\" .B "-p tcp" +.\" or +.\" .BR "-p udp" ). +.\" If no port range is specified, then source ports below 512 will be +.\" mapped to other ports below 512: those between 512 and 1023 inclusive +.\" will be mapped to ports below 1024, and other ports will be mapped to +.\" 1024 or above. Where possible, no port alteration will occur. +.\" .SS DNAT +.\" This target is only valid in the +.\" .B nat +.\" table, in the +.\" .B PREROUTING +.\" and +.\" .B OUTPUT +.\" chains, and user-defined chains which are only called from those +.\" chains. It specifies that the destination address of the packet +.\" should be modified (and all future packets in this connection will +.\" also be mangled), and rules should cease being examined. It takes one +.\" option: +.\" .TP +.\" .BR "--to-destination " "\fIipaddr\fP[-\fIipaddr\fP][:\fIport\fP-\fIport\fP]" +.\" which can specify a single new destination IP address, an inclusive +.\" range of IP addresses, and optionally, a port range (which is only +.\" valid if the rule also specifies +.\" .B "-p tcp" +.\" or +.\" .BR "-p udp" ). +.\" If no port range is specified, then the destination port will never be +.\" modified. +.\" .SS MASQUERADE +.\" This target is only valid in the +.\" .B nat +.\" table, in the +.\" .B POSTROUTING +.\" chain. It should only be used with dynamically assigned IP (dialup) +.\" connections: if you have a static IP address, you should use the SNAT +.\" target. Masquerading is equivalent to specifying a mapping to the IP +.\" address of the interface the packet is going out, but also has the +.\" effect that connections are +.\" .I forgotten +.\" when the interface goes down. This is the correct behavior when the +.\" next dialup is unlikely to have the same interface address (and hence +.\" any established connections are lost anyway). It takes one option: +.\" .TP +.\" .BR "--to-ports " "\fIport\fP[-\fIport\fP]" +.\" This specifies a range of source ports to use, overriding the default +.\" .B SNAT +.\" source port-selection heuristics (see above). This is only valid +.\" if the rule also specifies +.\" .B "-p tcp" +.\" or +.\" .BR "-p udp" . +.\" .SS REDIRECT +.\" This target is only valid in the +.\" .B nat +.\" table, in the +.\" .B PREROUTING +.\" and +.\" .B OUTPUT +.\" chains, and user-defined chains which are only called from those +.\" chains. It alters the destination IP address to send the packet to +.\" the machine itself (locally-generated packets are mapped to the +.\" 127.0.0.1 address). It takes one option: +.\" .TP +.\" .BR "--to-ports " "\fIport\fP[-\fIport\fP]" +.\" This specifies a destination port or range of ports to use: without +.\" this, the destination port is never altered. This is only valid +.\" if the rule also specifies +.\" .B "-p tcp" +.\" or +.\" .BR "-p udp" . +.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 +.B ip6tables +is very similar to ipchains by Rusty Russell. The main difference is +that the chains +.B INPUT +and +.B OUTPUT +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 +.B -i +refers to the input interface; +.B -o +refers to the output interface, and both are available for packets +entering the +.B FORWARD +chain. +.\" .PP The various forms of NAT have been separated out; +.\" .B iptables +.\" 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: +.\" .br +.\" -j MASQ +.\" .br +.\" -M -S +.\" .br +.\" -M -L +.\" .br +There are several other changes in ip6tables. +.SH SEE ALSO +.BR ip6tables-save (8), +.BR ip6tables-restore(8), +.BR iptables (8), +.BR iptables-save (8), +.BR iptables-restore (8). +.P +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 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 target, TTL match+target and libipulog. +.PP +The Netfilter Core Team is: Marc Boucher, Martin Josefsson, Jozsef Kadlecsik, +James Morris, 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 .. diff --git a/ip6tables.c.selinux b/ip6tables.c.selinux new file mode 100644 index 0000000..fc4e284 --- /dev/null +++ b/ip6tables.c.selinux @@ -0,0 +1,2300 @@ +/* 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 +#include + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef IP6T_LIB_DIR +#define IP6T_LIB_DIR "/usr/lib/iptables" +#endif + +#ifndef PROC_SYS_MODPROBE +#define PROC_SYS_MODPROBE "/proc/sys/kernel/modprobe" +#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_CHECK 0x0800U +#define CMD_RENAME_CHAIN 0x1000U +#define NUMBER_OF_CMD 13 +static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z', + 'N', 'X', 'P', 'E' }; + +#define OPTION_OFFSET 256 + +#define OPT_NONE 0x00000U +#define OPT_NUMERIC 0x00001U +#define OPT_SOURCE 0x00002U +#define OPT_DESTINATION 0x00004U +#define OPT_PROTOCOL 0x00008U +#define OPT_JUMP 0x00010U +#define OPT_VERBOSE 0x00020U +#define OPT_EXPANDED 0x00040U +#define OPT_VIANAMEIN 0x00080U +#define OPT_VIANAMEOUT 0x00100U +#define OPT_LINENUMBERS 0x00200U +#define OPT_COUNTERS 0x00400U +#define NUMBER_OF_OPT 11 +static const char optflags[NUMBER_OF_OPT] += { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', '3', 'c'}; + +static struct option original_opts[] = { + { "append", 1, 0, 'A' }, + { "delete", 1, 0, 'D' }, + { "insert", 1, 0, 'I' }, + { "replace", 1, 0, 'R' }, + { "list", 2, 0, 'L' }, + { "flush", 2, 0, 'F' }, + { "zero", 2, 0, 'Z' }, + { "new-chain", 1, 0, 'N' }, + { "delete-chain", 2, 0, 'X' }, + { "rename-chain", 1, 0, 'E' }, + { "policy", 1, 0, 'P' }, + { "source", 1, 0, 's' }, + { "destination", 1, 0, 'd' }, + { "src", 1, 0, 's' }, /* synonym */ + { "dst", 1, 0, 'd' }, /* synonym */ + { "protocol", 1, 0, 'p' }, + { "in-interface", 1, 0, 'i' }, + { "jump", 1, 0, 'j' }, + { "table", 1, 0, 't' }, + { "match", 1, 0, 'm' }, + { "numeric", 0, 0, 'n' }, + { "out-interface", 1, 0, 'o' }, + { "verbose", 0, 0, 'v' }, + { "exact", 0, 0, 'x' }, + { "version", 0, 0, 'V' }, + { "help", 2, 0, 'h' }, + { "line-numbers", 0, 0, '0' }, + { "modprobe", 1, 0, 'M' }, + { "set-counters", 1, 0, 'c' }, + { 0 } +}; + +/* we need this for ip6tables-restore. ip6tables-restore.c sets line to the + * current line of the input file, in order to give a more precise error + * message. ip6tables itself doesn't need this, so it is initialized to the + * magic number of -1 */ +int line = -1; + +#ifndef __OPTIMIZE__ +struct ip6t_entry_target * +ip6t_get_target(struct ip6t_entry *e) +{ + return (void *)e + e->target_offset; +} +#endif + +static struct option *opts = original_opts; +static unsigned int global_option_offset = 0; + +/* 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 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 */ +/*INSERT*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x'}, +/*DELETE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x'}, +/*DELETE_NUM*/{'x','x','x','x','x',' ','x','x','x','x'}, +/*REPLACE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x'}, +/*APPEND*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x'}, +/*LIST*/ {' ','x','x','x','x',' ',' ','x','x',' '}, +/*FLUSH*/ {'x','x','x','x','x',' ','x','x','x','x'}, +/*ZERO*/ {'x','x','x','x','x',' ','x','x','x','x'}, +/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x'}, +/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x'}, +/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x'}, +/*CHECK*/ {'x','+','+','+','x',' ','x',' ',' ','x'}, +/*RENAME*/ {'x','x','x','x','x',' ','x','x','x','x'} +}; + +static 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 +}; + +const char *program_version; +const char *program_name; + +/* Keeping track of external matches and targets: linked lists. */ +struct ip6tables_match *ip6tables_matches = NULL; +struct ip6tables_target *ip6tables_targets = NULL; + +/* Extra debugging from libiptc */ +extern void dump_entries6(const ip6tc_handle_t handle); + +/* A few hardcoded protocols for 'all' and in case the user has no + /etc/protocols */ +struct pprot { + char *name; + u_int8_t num; +}; + +/* 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 + +static const struct pprot chain_protos[] = { + { "tcp", IPPROTO_TCP }, + { "udp", IPPROTO_UDP }, + { "icmpv6", IPPROTO_ICMPV6 }, + { "esp", IPPROTO_ESP }, + { "ah", IPPROTO_AH }, + { "all", 0 }, +}; + +static char * +proto_to_name(u_int8_t proto, int nolookup) +{ + unsigned int i; + + if (proto && !nolookup) { + struct protoent *pent = getprotobynumber(proto); + if (pent) + return pent->p_name; + } + + for (i = 0; i < sizeof(chain_protos)/sizeof(struct pprot); i++) + if (chain_protos[i].num == proto) + return chain_protos[i].name; + + return NULL; +} + +static void +in6addrcpy(struct in6_addr *dst, struct in6_addr *src) +{ + memcpy(dst, src, sizeof(struct in6_addr)); + /* dst->s6_addr = src->s6_addr; */ +} + +void +exit_error(enum exittype status, char *msg, ...) +{ + va_list args; + + va_start(args, msg); + fprintf(stderr, "%s v%s: ", program_name, program_version); + 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"); + exit(status); +} + +void +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", + program_name, program_name ); + exit(status); +} + +void +exit_printhelp(void) +{ + struct ip6tables_match *m = NULL; + struct ip6tables_target *t = NULL; + + printf("%s v%s\n\n" +"Usage: %s -[AD] chain rule-specification [options]\n" +" %s -[RI] chain rulenum rule-specification [options]\n" +" %s -D chain rulenum [options]\n" +" %s -[LFZ] [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", + program_name, program_version, program_name, program_name, + program_name, program_name, program_name, program_name, + program_name, program_name); + + printf( +"Commands:\n" +"Either long or short options are allowed.\n" +" --append -A chain Append to chain\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] List the rules in a chain or all chains\n" +" --flush -F [chain] Delete all rules in chain or all chains\n" +" --zero -Z [chain] 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" +" --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" +" --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 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 + matches & targets */ + for (t=ip6tables_targets;t;t=t->next) { + printf("\n"); + t->help(); + } + for (m=ip6tables_matches;m;m=m->next) { + printf("\n"); + m->help(); + } + exit(0); +} + +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(int *cmd, const int newcmd, const int othercmds, int invert) +{ + if (invert) + exit_error(PARAMETER_PROBLEM, "unexpected ! flag"); + if (*cmd & (~othercmds)) + exit_error(PARAMETER_PROBLEM, "Can't use -%c with -%c\n", + cmd2char(newcmd), cmd2char(*cmd & (~othercmds))); + *cmd |= newcmd; +} + +int +check_inverse(const char option[], int *invert, int *optind, int argc) +{ + if (option && strcmp(option, "!") == 0) { + if (*invert) + exit_error(PARAMETER_PROBLEM, + "Multiple `!' flags not allowed"); + *invert = TRUE; + if (optind) { + *optind = *optind+1; + if (argc && *optind > argc) + exit_error(PARAMETER_PROBLEM, + "no argument following `!'"); + } + + return TRUE; + } + return FALSE; +} + +static void * +fw_calloc(size_t count, size_t size) +{ + void *p; + + if ((p = calloc(count, size)) == NULL) { + perror("ip6tables: calloc failed"); + exit(1); + } + return p; +} + +static void * +fw_malloc(size_t size) +{ + void *p; + + if ((p = malloc(size)) == NULL) { + perror("ip6tables: malloc failed"); + exit(1); + } + return p; +} + +static char * +addr_to_numeric(const struct in6_addr *addrp) +{ + /* 0000:0000:0000:0000:0000:000.000.000.000 + * 0000:0000:0000:0000:0000:0000:0000:0000 */ + static char buf[50+1]; + return (char *)inet_ntop(AF_INET6, addrp, buf, sizeof(buf)); +} + +static struct in6_addr * +numeric_to_addr(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 (struct in6_addr *)NULL; +} + + +static struct in6_addr * +host_to_addr(const char *name, unsigned int *naddr) +{ + struct addrinfo hints; + struct addrinfo *res; + static struct in6_addr *addr; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags=AI_CANONNAME; + hints.ai_family=AF_INET6; + hints.ai_socktype=SOCK_RAW; + hints.ai_protocol=41; + hints.ai_next=NULL; + + *naddr = 0; + if ( (err=getaddrinfo(name, NULL, &hints, &res)) != 0 ){ +#ifdef DEBUG + fprintf(stderr,"Name2IP: %s\n",gai_strerror(err)); +#endif + return (struct in6_addr *) NULL; + } else { + if (res->ai_family != AF_INET6 || + res->ai_addrlen != sizeof(struct sockaddr_in6)) + return (struct in6_addr *) NULL; + +#ifdef DEBUG + fprintf(stderr, "resolved: len=%d %s ", res->ai_addrlen, + addr_to_numeric(&(((struct sockaddr_in6 *)res->ai_addr)->sin6_addr))); +#endif + /* Get the first element of the address-chain */ + addr = fw_calloc(1, sizeof(struct in6_addr)); + in6addrcpy(addr, (struct in6_addr *) + &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr); + freeaddrinfo(res); + *naddr = 1; + return addr; + } + + return (struct in6_addr *) NULL; +} + +static char * +addr_to_host(const struct in6_addr *addr) +{ + struct sockaddr_in6 saddr; + int err; + static char hostname[NI_MAXHOST]; + + memset(&saddr, 0, sizeof(struct sockaddr_in6)); + in6addrcpy(&(saddr.sin6_addr),(struct in6_addr *)addr); + saddr.sin6_family = AF_INET6; + + if ( (err=getnameinfo((struct sockaddr *)&saddr, + sizeof(struct sockaddr_in6), + hostname, sizeof(hostname)-1, + NULL, 0, 0)) != 0 ){ +#ifdef DEBUG + fprintf(stderr,"IP2Name: %s\n",gai_strerror(err)); +#endif + return (char *) NULL; + } else { +#ifdef DEBUG + fprintf (stderr, "\naddr2host: %s\n", hostname); +#endif + + return hostname; + } + + return (char *) NULL; +} + +static char * +mask_to_numeric(const struct in6_addr *addrp) +{ + static char buf[50+2]; + int l = ipv6_prefix_length(addrp); + if (l == -1) { + strcpy(buf, "/"); + strcat(buf, addr_to_numeric(addrp)); + return buf; + } + sprintf(buf, "/%d", l); + return buf; +} + +static struct in6_addr * +network_to_addr(const char *name) +{ + /* abort();*/ + /* TODO: not implemented yet, but the exception breaks the + * name resolvation */ + return (struct in6_addr *)NULL; +} + +static char * +addr_to_anyname(const struct in6_addr *addr) +{ + char *name; + + if ((name = addr_to_host(addr)) != NULL) + return name; + + return addr_to_numeric(addr); +} + +/* + * 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. +*/ + +static struct in6_addr * +parse_hostnetwork(const char *name, unsigned int *naddrs) +{ + struct in6_addr *addrp, *addrptmp; + + if ((addrptmp = numeric_to_addr(name)) != NULL || + (addrptmp = network_to_addr(name)) != NULL) { + addrp = fw_malloc(sizeof(struct in6_addr)); + in6addrcpy(addrp, addrptmp); + *naddrs = 1; + return addrp; + } + if ((addrp = host_to_addr(name, naddrs)) != NULL) + return addrp; + + exit_error(PARAMETER_PROBLEM, "host/network `%s' not found", name); +} + +static struct in6_addr * +parse_mask(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 = numeric_to_addr(mask)) != NULL) + return addrp; + if (string_to_number(mask, 0, 128, &bits) == -1) + exit_error(PARAMETER_PROBLEM, + "invalid mask `%s' specified", mask); + if (bits != 0) { + char *p = (char *)&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 +parse_hostnetworkmask(const char *name, struct in6_addr **addrpp, + struct in6_addr *maskp, unsigned int *naddrs) +{ + struct in6_addr *addrp; + char buf[256]; + char *p; + int i, j, n; + + strncpy(buf, name, sizeof(buf) - 1); + if ((p = strrchr(buf, '/')) != NULL) { + *p = '\0'; + addrp = parse_mask(p + 1); + } else + addrp = parse_mask(NULL); + in6addrcpy(maskp, addrp); + + /* if a null mask is given, the name is ignored, like in "any/0" */ + if (!memcmp(maskp, &in6addr_any, sizeof(in6addr_any))) + strcpy(buf, "::"); + + addrp = *addrpp = parse_hostnetwork(buf, naddrs); + n = *naddrs; + for (i = 0, j = 0; i < n; i++) { + int k; + for (k = 0; k < 4; k++) + addrp[j].in6_u.u6_addr32[k] &= maskp->in6_u.u6_addr32[k]; + j++; + for (k = 0; k < j - 1; k++) { + if (IN6_ARE_ADDR_EQUAL(&addrp[k], &addrp[j - 1])) { + (*naddrs)--; + j--; + break; + } + } + } +} + +struct ip6tables_match * +find_match(const char *name, enum ip6t_tryload tryload) +{ + struct ip6tables_match *ptr; + int icmphack = 0; + + /* 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) ) icmphack = 1; + + if (!icmphack) { + for (ptr = ip6tables_matches; ptr; ptr = ptr->next) { + if (strcmp(name, ptr->name) == 0) + break; + } + } else { + for (ptr = ip6tables_matches; ptr; ptr = ptr->next) { + if (strcmp("icmp6", ptr->name) == 0) + break; + } + } + +#ifndef NO_SHARED_LIBS + if (!ptr && tryload != DONT_LOAD) { + char path[sizeof(IP6T_LIB_DIR) + sizeof("/libip6t_.so") + + strlen(name)]; + if (!icmphack) + sprintf(path, IP6T_LIB_DIR "/libip6t_%s.so", name); + else + sprintf(path, IP6T_LIB_DIR "/libip6t_%s.so", "icmpv6"); + if (dlopen(path, RTLD_NOW)) { + /* Found library. If it didn't register itself, + maybe they specified target as match. */ + ptr = find_match(name, DONT_LOAD); + + if (!ptr) + exit_error(PARAMETER_PROBLEM, + "Couldn't load match `%s'\n", + name); + } else if (tryload == LOAD_MUST_SUCCEED) + exit_error(PARAMETER_PROBLEM, + "Couldn't load match `%s':%s\n", + name, dlerror()); + } +#else + if (ptr && !ptr->loaded) { + if (tryload != DONT_LOAD) + ptr->loaded = 1; + else + ptr = NULL; + } + if(!ptr && (tryload == LOAD_MUST_SUCCEED)) { + exit_error(PARAMETER_PROBLEM, + "Couldn't find match `%s'\n", name); + } +#endif + + if (ptr) + ptr->used = 1; + + return ptr; +} + +/* Christophe Burki wants `-p 6' to imply `-m tcp'. */ +static struct ip6tables_match * +find_proto(const char *pname, enum ip6t_tryload tryload, int nolookup) +{ + unsigned int proto; + + if (string_to_number(pname, 0, 255, &proto) != -1) { + char *protoname = proto_to_name(proto, nolookup); + + if (protoname) + return find_match(protoname, tryload); + } else + return find_match(pname, tryload); + + return NULL; +} + +u_int16_t +parse_protocol(const char *s) +{ + unsigned int proto; + + if (string_to_number(s, 0, 255, &proto) == -1) { + struct protoent *pent; + + if ((pent = getprotobyname(s))) + proto = pent->p_proto; + else { + unsigned int i; + for (i = 0; + i < sizeof(chain_protos)/sizeof(struct pprot); + i++) { + if (strcmp(s, chain_protos[i].name) == 0) { + proto = chain_protos[i].num; + break; + } + } + if (i == sizeof(chain_protos)/sizeof(struct pprot)) + exit_error(PARAMETER_PROBLEM, + "unknown protocol `%s' specified", + s); + } + } + + return (u_int16_t)proto; +} + +static void +parse_interface(const char *arg, char *vianame, unsigned char *mask) +{ + int vialen = strlen(arg); + unsigned int i; + + memset(mask, 0, IFNAMSIZ); + memset(vianame, 0, IFNAMSIZ); + + if (vialen + 1 > IFNAMSIZ) + exit_error(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 (!isalnum(vianame[i]) + && vianame[i] != '_' + && vianame[i] != '.') { + printf("Warning: wierd character in interface" + " `%s' (No aliases, :, ! or *).\n", + vianame); + break; + } + } + } +} + +/* Can't be zero. */ +static int +parse_rulenumber(const char *rule) +{ + unsigned int rulenum; + + if (string_to_number(rule, 1, INT_MAX, &rulenum) == -1) + exit_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) + exit_error(PARAMETER_PROBLEM, + "Invalid target name (too short)"); + + if (strlen(targetname)+1 > sizeof(ip6t_chainlabel)) + exit_error(PARAMETER_PROBLEM, + "Invalid target name `%s' (%i chars max)", + targetname, sizeof(ip6t_chainlabel)-1); + + for (ptr = targetname; *ptr; ptr++) + if (isspace(*ptr)) + exit_error(PARAMETER_PROBLEM, + "Invalid target name `%s'", targetname); + return targetname; +} + +int +string_to_number(const char *s, unsigned int min, unsigned int max, + unsigned int *ret) +{ + long number; + char *end; + + /* Handle hex, octal, etc. */ + errno = 0; + number = strtol(s, &end, 0); + if (*end == '\0' && end != s) { + /* we parsed a number, let's see if we want this */ + if (errno != ERANGE && min <= number && number <= max) { + *ret = number; + return 0; + } + } + return -1; +} + +static void +set_option(unsigned int *options, unsigned int option, u_int8_t *invflg, + int invert) +{ + if (*options & option) + exit_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]) + exit_error(PARAMETER_PROBLEM, + "cannot have ! before -%c", + opt2char(option)); + *invflg |= inverse_for_options[i]; + } +} + +struct ip6tables_target * +find_target(const char *name, enum ip6t_tryload tryload) +{ + struct ip6tables_target *ptr; + + /* Standard target? */ + if (strcmp(name, "") == 0 + || strcmp(name, IP6TC_LABEL_ACCEPT) == 0 + || strcmp(name, IP6TC_LABEL_DROP) == 0 + || strcmp(name, IP6TC_LABEL_QUEUE) == 0 + || strcmp(name, IP6TC_LABEL_RETURN) == 0) + name = "standard"; + + for (ptr = ip6tables_targets; ptr; ptr = ptr->next) { + if (strcmp(name, ptr->name) == 0) + break; + } + +#ifndef NO_SHARED_LIBS + if (!ptr && tryload != DONT_LOAD) { + char path[sizeof(IP6T_LIB_DIR) + sizeof("/libip6t_.so") + + strlen(name)]; + sprintf(path, IP6T_LIB_DIR "/libip6t_%s.so", name); + if (dlopen(path, RTLD_NOW)) { + /* Found library. If it didn't register itself, + maybe they specified match as a target. */ + ptr = find_target(name, DONT_LOAD); + if (!ptr) + exit_error(PARAMETER_PROBLEM, + "Couldn't load target `%s'\n", + name); + } else if (tryload == LOAD_MUST_SUCCEED) + exit_error(PARAMETER_PROBLEM, + "Couldn't load target `%s':%s\n", + name, dlerror()); + } +#else + if (ptr && !ptr->loaded) { + if (tryload != DONT_LOAD) + ptr->loaded = 1; + else + ptr = NULL; + } + if(!ptr && (tryload == LOAD_MUST_SUCCEED)) { + exit_error(PARAMETER_PROBLEM, + "Couldn't find target `%s'\n", name); + } +#endif + + if (ptr) + ptr->used = 1; + + return ptr; +} + +static struct option * +merge_options(struct option *oldopts, const struct option *newopts, + unsigned int *option_offset) +{ + unsigned int num_old, num_new, i; + struct option *merge; + + for (num_old = 0; oldopts[num_old].name; num_old++); + for (num_new = 0; newopts[num_new].name; num_new++); + + global_option_offset += OPTION_OFFSET; + *option_offset = global_option_offset; + + merge = malloc(sizeof(struct option) * (num_new + num_old + 1)); + memcpy(merge, oldopts, num_old * sizeof(struct option)); + for (i = 0; i < num_new; i++) { + merge[num_old + i] = newopts[i]; + merge[num_old + i].val += *option_offset; + } + memset(merge + num_old + num_new, 0, sizeof(struct option)); + + return merge; +} + +void +register_match6(struct ip6tables_match *me) +{ + struct ip6tables_match **i; + + if (strcmp(me->version, program_version) != 0) { + fprintf(stderr, "%s: match `%s' v%s (I'm v%s).\n", + program_name, me->name, me->version, program_version); + exit(1); + } + + if (find_match(me->name, DONT_LOAD)) { + fprintf(stderr, "%s: match `%s' already registered.\n", + program_name, me->name); + exit(1); + } + + if (me->size != IP6T_ALIGN(me->size)) { + fprintf(stderr, "%s: match `%s' has invalid size %u.\n", + program_name, me->name, me->size); + exit(1); + } + + /* Append to list. */ + for (i = &ip6tables_matches; *i; i = &(*i)->next); + me->next = NULL; + *i = me; + + me->m = NULL; + me->mflags = 0; +} + +void +register_target6(struct ip6tables_target *me) +{ + if (strcmp(me->version, program_version) != 0) { + fprintf(stderr, "%s: target `%s' v%s (I'm v%s).\n", + program_name, me->name, me->version, program_version); + exit(1); + } + + if (find_target(me->name, DONT_LOAD)) { + fprintf(stderr, "%s: target `%s' already registered.\n", + program_name, me->name); + exit(1); + } + + if (me->size != IP6T_ALIGN(me->size)) { + fprintf(stderr, "%s: target `%s' has invalid size %u.\n", + program_name, me->name, me->size); + exit(1); + } + + /* Prepend to list. */ + me->next = ip6tables_targets; + ip6tables_targets = me; + me->t = NULL; + me->tflags = 0; +} + +static void +print_num(u_int64_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 "), number); + } + else printf(FMT("%4lluG ","%lluG "), number); + } + else printf(FMT("%4lluM ","%lluM "), number); + } else + printf(FMT("%4lluK ","%lluK "), number); + } else + printf(FMT("%5llu ","%llu "), number); + } else + printf(FMT("%8llu ","%llu "), number); +} + + +static void +print_header(unsigned int format, const char *chain, ip6tc_handle_t *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) +{ + struct ip6tables_match *match = find_match(m->u.user.name, TRY_LOAD); + + 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 hysterical raisins */ +static void +print_firewall(const struct ip6t_entry *fw, + const char *targname, + unsigned int num, + unsigned int format, + const ip6tc_handle_t handle) +{ + struct ip6tables_target *target = NULL; + const struct ip6t_entry_target *t; + u_int8_t flags; + char buf[BUFSIZ]; + + if (!ip6tc_is_chain(targname, handle)) + target = find_target(targname, TRY_LOAD); + else + target = find_target(IP6T_STANDARD_TARGET, LOAD_MUST_SUCCEED); + + t = ip6t_get_target((struct ip6t_entry *)fw); + flags = fw->ipv6.flags; + + if (format & FMT_LINENUMBERS) + printf(FMT("%-4u ", "%u "), num+1); + + 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); + { + 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) + sprintf(buf, "%s", addr_to_numeric(&(fw->ipv6.src))); + else + sprintf(buf, "%s", addr_to_anyname(&(fw->ipv6.src))); + strcat(buf, mask_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) + sprintf(buf, "%s", addr_to_numeric(&(fw->ipv6.dst))); + else + sprintf(buf, "%s", addr_to_anyname(&(fw->ipv6.dst))); + strcat(buf, mask_to_numeric(&(fw->ipv6.dmsk))); + printf(FMT("%-19s","-> %s"), buf); + } + + if (format & FMT_NOTABLE) + fputs(" ", stdout); + + 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] ", + t->u.target_size - sizeof(*t)); + + if (!(format & FMT_NONEWLINE)) + fputc('\n', stdout); +} + +static void +print_firewall_line(const struct ip6t_entry *fw, + const ip6tc_handle_t 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[], + unsigned int ndaddrs, + const struct in6_addr daddrs[], + int verbose, + ip6tc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ipv6.src = saddrs[i]; + for (j = 0; j < ndaddrs; j++) { + fw->ipv6.dst = daddrs[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 *daddr, + int verbose, + ip6tc_handle_t *handle) +{ + fw->ipv6.src = *saddr; + fw->ipv6.dst = *daddr; + + 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[], + unsigned int ndaddrs, + const struct in6_addr daddrs[], + int verbose, + ip6tc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ipv6.src = saddrs[i]; + for (j = 0; j < ndaddrs; j++) { + fw->ipv6.dst = daddrs[j]; + if (verbose) + print_firewall_line(fw, *handle); + ret &= ip6tc_insert_entry(chain, fw, rulenum, handle); + } + } + + return ret; +} + +static unsigned char * +make_delete_mask(struct ip6t_entry *fw) +{ + /* Establish mask for comparison */ + unsigned int size; + struct ip6tables_match *m; + unsigned char *mask, *mptr; + + size = sizeof(struct ip6t_entry); + for (m = ip6tables_matches; m; m = m->next) { + if (!m->used) + continue; + + size += IP6T_ALIGN(sizeof(struct ip6t_entry_match)) + m->size; + } + + mask = fw_calloc(1, size + + IP6T_ALIGN(sizeof(struct ip6t_entry_target)) + + ip6tables_targets->size); + + memset(mask, 0xFF, sizeof(struct ip6t_entry)); + mptr = mask + sizeof(struct ip6t_entry); + + for (m = ip6tables_matches; m; m = m->next) { + if (!m->used) + continue; + + memset(mptr, 0xFF, + IP6T_ALIGN(sizeof(struct ip6t_entry_match)) + + m->userspacesize); + mptr += IP6T_ALIGN(sizeof(struct ip6t_entry_match)) + m->size; + } + + memset(mptr, 0xFF, + IP6T_ALIGN(sizeof(struct ip6t_entry_target)) + + ip6tables_targets->userspacesize); + + return mask; +} + +static int +delete_entry(const ip6t_chainlabel chain, + struct ip6t_entry *fw, + unsigned int nsaddrs, + const struct in6_addr saddrs[], + unsigned int ndaddrs, + const struct in6_addr daddrs[], + int verbose, + ip6tc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + unsigned char *mask; + + mask = make_delete_mask(fw); + for (i = 0; i < nsaddrs; i++) { + fw->ipv6.src = saddrs[i]; + for (j = 0; j < ndaddrs; j++) { + fw->ipv6.dst = daddrs[j]; + if (verbose) + print_firewall_line(fw, *handle); + ret &= ip6tc_delete_entry(chain, fw, mask, handle); + } + } + return ret; +} + +int +for_each_chain(int (*fn)(const ip6t_chainlabel, int, ip6tc_handle_t *), + int verbose, int builtinstoo, ip6tc_handle_t *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 = fw_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)) + continue; + ret &= fn(chains + i*sizeof(ip6t_chainlabel), verbose, handle); + } + + free(chains); + return ret; +} + +int +flush_entries(const ip6t_chainlabel chain, int verbose, + ip6tc_handle_t *handle) +{ + if (!chain) + return for_each_chain(flush_entries, 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, + ip6tc_handle_t *handle) +{ + if (!chain) + return for_each_chain(zero_entries, verbose, 1, handle); + + if (verbose) + fprintf(stdout, "Zeroing chain `%s'\n", chain); + return ip6tc_zero_entries(chain, handle); +} + +int +delete_chain(const ip6t_chainlabel chain, int verbose, + ip6tc_handle_t *handle) +{ + if (!chain) + return for_each_chain(delete_chain, 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 verbose, int numeric, + int expanded, int linenumbers, ip6tc_handle_t *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"); + + print_header(format, this, handle); + i = ip6tc_first_rule(this, handle); + + num = 0; + while (i) { + print_firewall(i, + ip6tc_get_target(i, handle), + num++, + format, + *handle); + i = ip6tc_next_rule(i, handle); + } + found = 1; + } + + errno = ENOENT; + return found; +} + +static char *get_modprobe(void) +{ + int procfile; + char *ret; + + procfile = open(PROC_SYS_MODPROBE, O_RDONLY); + if (procfile < 0) + return NULL; + + ret = malloc(1024); + if (ret) { + switch (read(procfile, ret, 1024)) { + case -1: goto fail; + case 1024: 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 ip6tables_insmod(const char *modname, const char *modprobe) +{ + char *buf = NULL; + char *argv[3]; + + /* If they don't explicitly set it, read out of kernel */ + if (!modprobe) { + buf = get_modprobe(); + if (!buf) + return -1; + modprobe = buf; + } + + switch (fork()) { + case 0: + argv[0] = (char *)modprobe; + argv[1] = (char *)modname; + argv[2] = NULL; + execv(argv[0], argv); + + /* not usually reached */ + exit(0); + case -1: + return -1; + + default: /* parent */ + wait(NULL); + } + + free(buf); + return 0; +} + +static struct ip6t_entry * +generate_entry(const struct ip6t_entry *fw, + struct ip6tables_match *matches, + struct ip6t_entry_target *target) +{ + unsigned int size; + struct ip6tables_match *m; + struct ip6t_entry *e; + + size = sizeof(struct ip6t_entry); + for (m = matches; m; m = m->next) { + if (!m->used) + continue; + + size += m->m->u.match_size; + } + + e = fw_malloc(size + target->u.target_size); + *e = *fw; + e->target_offset = size; + e->next_offset = size + target->u.target_size; + + size = 0; + for (m = matches; m; m = m->next) { + if (!m->used) + continue; + + memcpy(e->elems + size, m->m, m->m->u.match_size); + size += m->m->u.match_size; + } + memcpy(e->elems + size, target, target->u.target_size); + + return e; +} + +int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle) +{ + struct ip6t_entry fw, *e = NULL; + int invert = 0; + unsigned int nsaddrs = 0, ndaddrs = 0; + struct in6_addr *saddrs = NULL, *daddrs = NULL; + + int c, verbose = 0; + const char *chain = NULL; + const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL; + const char *policy = NULL, *newname = NULL; + unsigned int rulenum = 0, options = 0, command = 0; + const char *pcnt = NULL, *bcnt = NULL; + int ret = 1; + struct ip6tables_match *m; + struct ip6tables_target *target = NULL; + struct ip6tables_target *t; + const char *jumpto = ""; + char *protocol = NULL; + const char *modprobe = NULL; + int proto_used = 0; + char icmp6p[] = "icmpv6"; + + memset(&fw, 0, sizeof(fw)); + + opts = original_opts; + global_option_offset = 0; + + /* re-set optind to 0 in case do_command gets called + * a second time */ + optind = 0; + + /* clear mflags in case do_command gets called a second time + * (we clear the global list of all matches for security)*/ + for (m = ip6tables_matches; m; m = m->next) { + m->mflags = 0; + m->used = 0; + } + + for (t = ip6tables_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; + + while ((c = getopt_long(argc, argv, + "-A:D:R:I:L::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:bvnt:m:xc:", + opts, NULL)) != -1) { + switch (c) { + /* + * Command selection + */ + case 'A': + add_command(&command, CMD_APPEND, CMD_NONE, + invert); + chain = optarg; + break; + + case 'D': + add_command(&command, CMD_DELETE, CMD_NONE, + 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, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else + exit_error(PARAMETER_PROBLEM, + "-%c requires a rule number", + cmd2char(CMD_REPLACE)); + break; + + case 'I': + add_command(&command, CMD_INSERT, CMD_NONE, + 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, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'F': + add_command(&command, CMD_FLUSH, CMD_NONE, + 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, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'N': + if (optarg && *optarg == '-') + exit_error(PARAMETER_PROBLEM, + "chain name not allowed to start " + "with `-'\n"); + if (find_target(optarg, TRY_LOAD)) + exit_error(PARAMETER_PROBLEM, + "chain name may not clash " + "with target name\n"); + add_command(&command, CMD_NEW_CHAIN, CMD_NONE, + invert); + chain = optarg; + break; + + case 'X': + add_command(&command, CMD_DELETE_CHAIN, CMD_NONE, + 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, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + newname = argv[optind++]; + else + exit_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, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + policy = argv[optind++]; + else + exit_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 (!ip6tables_matches && protocol) + find_match(protocol, TRY_LOAD); + + exit_printhelp(); + + /* + * Option selection + */ + case 'p': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_PROTOCOL, &fw.ipv6.invflags, + invert); + + /* Canonicalize into lower case */ + for (protocol = argv[optind-1]; *protocol; protocol++) + *protocol = tolower(*protocol); + + protocol = argv[optind-1]; + if ( strcmp(protocol,"ipv6-icmp") == 0) + protocol = icmp6p; + fw.ipv6.proto = parse_protocol(protocol); + fw.ipv6.flags |= IP6T_F_PROTO; + + if (fw.ipv6.proto == 0 + && (fw.ipv6.invflags & IP6T_INV_PROTO)) + exit_error(PARAMETER_PROBLEM, + "rule would never match protocol"); + fw.nfcache |= NFC_IP6_PROTO; + break; + + case 's': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_SOURCE, &fw.ipv6.invflags, + invert); + shostnetworkmask = argv[optind-1]; + fw.nfcache |= NFC_IP6_SRC; + break; + + case 'd': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_DESTINATION, &fw.ipv6.invflags, + invert); + dhostnetworkmask = argv[optind-1]; + fw.nfcache |= NFC_IP6_DST; + break; + + case 'j': + set_option(&options, OPT_JUMP, &fw.ipv6.invflags, + invert); + jumpto = parse_target(optarg); + /* TRY_LOAD (may be chain name) */ + target = find_target(jumpto, TRY_LOAD); + + if (target) { + size_t size; + + size = IP6T_ALIGN(sizeof(struct ip6t_entry_target)) + + target->size; + + target->t = fw_calloc(1, size); + target->t->u.target_size = size; + strcpy(target->t->u.user.name, jumpto); + target->init(target->t, &fw.nfcache); + opts = merge_options(opts, target->extra_opts, &target->option_offset); + } + break; + + + case 'i': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_VIANAMEIN, &fw.ipv6.invflags, + invert); + parse_interface(argv[optind-1], + fw.ipv6.iniface, + fw.ipv6.iniface_mask); + fw.nfcache |= NFC_IP6_IF_IN; + break; + + case 'o': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_VIANAMEOUT, &fw.ipv6.invflags, + invert); + parse_interface(argv[optind-1], + fw.ipv6.outiface, + fw.ipv6.outiface_mask); + fw.nfcache |= NFC_IP6_IF_OUT; + break; + + case 'v': + if (!verbose) + set_option(&options, OPT_VERBOSE, + &fw.ipv6.invflags, invert); + verbose++; + break; + + case 'm': { + size_t size; + + if (invert) + exit_error(PARAMETER_PROBLEM, + "unexpected ! flag before --match"); + + m = find_match(optarg, LOAD_MUST_SUCCEED); + size = IP6T_ALIGN(sizeof(struct ip6t_entry_match)) + + m->size; + m->m = fw_calloc(1, size); + m->m->u.match_size = size; + strcpy(m->m->u.user.name, m->name); + m->init(m->m, &fw.nfcache); + opts = merge_options(opts, m->extra_opts, &m->option_offset); + } + break; + + case 'n': + set_option(&options, OPT_NUMERIC, &fw.ipv6.invflags, + invert); + break; + + case 't': + if (invert) + exit_error(PARAMETER_PROBLEM, + "unexpected ! flag before --table"); + *table = argv[optind-1]; + break; + + case 'x': + set_option(&options, OPT_EXPANDED, &fw.ipv6.invflags, + invert); + break; + + case 'V': + if (invert) + printf("Not %s ;-)\n", program_version); + else + printf("%s v%s\n", + program_name, program_version); + exit(0); + + case '0': + set_option(&options, OPT_LINENUMBERS, &fw.ipv6.invflags, + invert); + break; + + case 'M': + modprobe = optarg; + break; + + case 'c': + + set_option(&options, OPT_COUNTERS, &fw.ipv6.invflags, + invert); + pcnt = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + bcnt = argv[optind++]; + else + exit_error(PARAMETER_PROBLEM, + "-%c requires packet and byte counter", + opt2char(OPT_COUNTERS)); + + if (sscanf(pcnt, "%llu", &fw.counters.pcnt) != 1) + exit_error(PARAMETER_PROBLEM, + "-%c packet counter not numeric", + opt2char(OPT_COUNTERS)); + + if (sscanf(bcnt, "%llu", &fw.counters.bcnt) != 1) + exit_error(PARAMETER_PROBLEM, + "-%c byte counter not numeric", + opt2char(OPT_COUNTERS)); + + break; + + + case 1: /* non option */ + if (optarg[0] == '!' && optarg[1] == '\0') { + if (invert) + exit_error(PARAMETER_PROBLEM, + "multiple consecutive ! not" + " allowed"); + invert = TRUE; + optarg[0] = '\0'; + continue; + } + printf("Bad argument `%s'\n", optarg); + exit_tryhelp(2); + + default: + /* FIXME: This scheme doesn't allow two of the same + matches --RR */ + if (!target + || !(target->parse(c - target->option_offset, + argv, invert, + &target->tflags, + &fw, &target->t))) { + for (m = ip6tables_matches; m; m = m->next) { + if (!m->used) + continue; + + if (m->parse(c - m->option_offset, + argv, invert, + &m->mflags, + &fw, + &fw.nfcache, + &m->m)) + break; + } + + /* If you listen carefully, you can + actually hear this code suck. */ + + /* some explanations (after four different bugs + * in 3 different releases): If we encountere 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 iptables-restore!] + * - the protocol extension can be successively + * loaded + */ + if (m == NULL + && protocol + && (!find_proto(protocol, DONT_LOAD, + options&OPT_NUMERIC) + || (find_proto(protocol, DONT_LOAD, + options&OPT_NUMERIC) + && (proto_used == 0)) + ) + && (m = find_proto(protocol, TRY_LOAD, + options&OPT_NUMERIC))) { + /* Try loading protocol */ + size_t size; + + proto_used = 1; + + size = IP6T_ALIGN(sizeof(struct ip6t_entry_match)) + + m->size; + + m->m = fw_calloc(1, size); + m->m->u.match_size = size; + strcpy(m->m->u.user.name, m->name); + m->init(m->m, &fw.nfcache); + + opts = merge_options(opts, + m->extra_opts, &m->option_offset); + + optind--; + continue; + } + + if (!m) + exit_error(PARAMETER_PROBLEM, + "Unknown arg `%s'", + argv[optind-1]); + } + } + invert = FALSE; + } + + for (m = ip6tables_matches; m; m = m->next) { + if (!m->used) + continue; + + m->final_check(m->mflags); + } + + if (target) + target->final_check(target->tflags); + + /* Fix me: must put inverse options checking here --MN */ + + if (optind < argc) + exit_error(PARAMETER_PROBLEM, + "unknown arguments found on commandline"); + if (!command) + exit_error(PARAMETER_PROBLEM, "no command specified"); + if (invert) + exit_error(PARAMETER_PROBLEM, + "nothing appropriate following !"); + + if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)) { + if (!(options & OPT_DESTINATION)) + dhostnetworkmask = "::0/0"; + if (!(options & OPT_SOURCE)) + shostnetworkmask = "::0/0"; + } + + if (shostnetworkmask) + parse_hostnetworkmask(shostnetworkmask, &saddrs, + &(fw.ipv6.smsk), &nsaddrs); + + if (dhostnetworkmask) + parse_hostnetworkmask(dhostnetworkmask, &daddrs, + &(fw.ipv6.dmsk), &ndaddrs); + + if ((nsaddrs > 1 || ndaddrs > 1) && + (fw.ipv6.invflags & (IP6T_INV_SRCIP | IP6T_INV_DSTIP))) + exit_error(PARAMETER_PROBLEM, "! not allowed with multiple" + " source or destination IP addresses"); + + if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1)) + exit_error(PARAMETER_PROBLEM, "Replacement rule does not " + "specify a unique address"); + + generic_opt_check(command, options); + + if (chain && strlen(chain) > IP6T_FUNCTION_MAXNAMELEN) + exit_error(PARAMETER_PROBLEM, + "chain name `%s' too long (must be under %i chars)", + chain, IP6T_FUNCTION_MAXNAMELEN); + + /* only allocate handle if we weren't called with a handle */ + if (!*handle) + *handle = ip6tc_init(*table); + + if (!*handle) { + /* try to insmod the module if iptc_init failed */ + ip6tables_insmod("ip6_tables", modprobe); + *handle = ip6tc_init(*table); + } + + if (!*handle) + exit_error(VERSION_PROBLEM, + "can't initialize ip6tables table `%s': %s", + *table, ip6tc_strerror(errno)); + + if (command == CMD_APPEND + || command == CMD_DELETE + || command == CMD_INSERT + || command == CMD_REPLACE) { + if (strcmp(chain, "PREROUTING") == 0 + || strcmp(chain, "INPUT") == 0) { + /* -o not valid with incoming packets. */ + if (options & OPT_VIANAMEOUT) + exit_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 (options & OPT_VIANAMEIN) + exit_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEIN), + chain); + } + + if (target && ip6tc_is_chain(jumpto, *handle)) { + printf("Warning: using chain %s, not extension\n", + jumpto); + + target = NULL; + } + + /* If they didn't specify a target, or it's a chain + name, use standard. */ + if (!target + && (strlen(jumpto) == 0 + || ip6tc_is_chain(jumpto, *handle))) { + size_t size; + + target = find_target(IP6T_STANDARD_TARGET, + LOAD_MUST_SUCCEED); + + size = sizeof(struct ip6t_entry_target) + + target->size; + target->t = fw_calloc(1, size); + target->t->u.target_size = size; + strcpy(target->t->u.user.name, jumpto); + target->init(target->t, &fw.nfcache); + } + + if (!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. */ + find_target(jumpto, LOAD_MUST_SUCCEED); + } else { + e = generate_entry(&fw, ip6tables_matches, target->t); + } + } + + switch (command) { + case CMD_APPEND: + ret = append_entry(chain, e, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_DELETE: + ret = delete_entry(chain, e, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_DELETE_NUM: + ret = ip6tc_delete_num_entry(chain, rulenum - 1, handle); + break; + case CMD_REPLACE: + ret = replace_entry(chain, e, rulenum - 1, + saddrs, daddrs, options&OPT_VERBOSE, + handle); + break; + case CMD_INSERT: + ret = insert_entry(chain, e, rulenum - 1, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_LIST: + ret = list_entries(chain, + options&OPT_VERBOSE, + options&OPT_NUMERIC, + options&OPT_EXPANDED, + options&OPT_LINENUMBERS, + handle); + break; + case CMD_FLUSH: + ret = flush_entries(chain, options&OPT_VERBOSE, handle); + break; + case CMD_ZERO: + ret = zero_entries(chain, options&OPT_VERBOSE, handle); + break; + case CMD_LIST|CMD_ZERO: + ret = list_entries(chain, + options&OPT_VERBOSE, + options&OPT_NUMERIC, + options&OPT_EXPANDED, + options&OPT_LINENUMBERS, + handle); + if (ret) + ret = zero_entries(chain, + options&OPT_VERBOSE, handle); + break; + case CMD_NEW_CHAIN: + ret = ip6tc_create_chain(chain, handle); + break; + case CMD_DELETE_CHAIN: + ret = delete_chain(chain, 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, NULL, handle); + break; + default: + /* We should never reach this... */ + exit_tryhelp(2); + } + + if (verbose > 1) + dump_entries6(*handle); + + return ret; +} diff --git a/ippool/Makefile b/ippool/Makefile new file mode 100644 index 0000000..100c2e4 --- /dev/null +++ b/ippool/Makefile @@ -0,0 +1,18 @@ +EXTRAS+=$(shell [ -f $(KERNEL_DIR)/include/linux/netfilter_ipv4/ip_pool.h ] && echo ippool/libippool.a ippool/ippool) + +ifndef TOPLEVEL_INCLUDED +local: + cd .. && $(MAKE) $(KERN_TARGETS) $(SHARED_LIBS) $(EXTRAS) + +else +EXTRA_DEPENDS+=$(shell [ -f $(KERNEL_DIR)/include/linux/netfilter_ipv4/ip_pool.h ] && echo ippool/libippool.d) + +ippool/libippool.a: ippool/libippool.a(ippool/libippool.o) + +ippool/libippool.d: %.d: %.c + @-$(CC) -M -MG $(CFLAGS) $< | sed -e 's@^.*\.o:@$*.d $*.a($*.o):@' > $@ + +ippool/ippool.o: ippool/ippool.c +ippool/ippool: ippool/ippool.o ippool/libippool.a + $(CC) $(CFLAGS) -o $@ $^ +endif diff --git a/ippool/ippool.c b/ippool/ippool.c new file mode 100644 index 0000000..3a680d3 --- /dev/null +++ b/ippool/ippool.c @@ -0,0 +1,589 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* translate errnos, as returned by our *sockopt() functions */ +static char *errno2msg(int op, int err) +{ + switch(err) { + case ERANGE: + return "Address out of pool range"; + } + return strerror(err); +} + +static void usage(char *msg) +{ + if (msg) fprintf(stderr, "ERROR: %s\n", msg); + fprintf(stderr, +"Usage: ippool [-LADCNF] [POOL] [args]\n" +"The POOL argument is either a number, or a name from %s\n" +, IPPOOL_CONF); + fprintf(stderr, +"ippool [-n] [-l|-L POOL] [-u] [-v]\n" + "\tLists all (-l), or a single (-L) pool.\n" + "\tWith -u, summarized usage counts are listed.\n" + "\tWith -v, each pool membership is shown, one per line.\n" +"ippool [-n] [-f|-F [POOL]]\n" + "\tflushes POOL (or all pools.)\n" +"ippool [-n] [-x|-X [POOL]]\n" + "\tremoves POOL (or all pools.)\n" +"ippool [-n] -B\n" + "\tcreates all fully specified pools found in the config file.\n" +"ippool [-n] -N POOL [-t type] [FIRST LAST]\n" + "\tcreates POOL of IP addresses FIRST to LAST (inclusive). If a\n" + "\tpool name from the config file %s is used, type and\n" + "\taddress information can be defined there. The -t argument\n" + "\tgives the type (default bitmap).\n" +"ippool [-n] -A POOL ADDR\n" + "\tadds ADDR to POOL\n" +"ippool [-n] -D POOL ADDR\n" + "\tremoves ADDR from POOL\n" +"ippool [-n] -C POOL ADDR\n" + "\ttests ADDR against membership in POOL\n" +, IPPOOL_CONF); + exit(1); +} + +/* config file parsing */ + +#define IP_POOL_T_NONE 0 +#define IP_POOL_T_BITMAP 1 + +static int conf_type = IP_POOL_T_NONE; +static unsigned long conf_addr = 0; +static unsigned long conf_addr2 = 0; + +#define SCAN_EOF (IP_POOL_NONE-1) + +static ip_pool_t get_index_line( + FILE *fp, + char **namep, + char **typep, + char **argp +) { + char *p; + ip_pool_t index; + static char buf[256]; + + if (namep) *namep = 0; + if (typep) *typep = 0; + if (argp) *argp = 0; + + if (!fgets(buf, sizeof(buf), fp)) return SCAN_EOF; + + p = strtok(buf, " \t\n"); + if (!p || *p == '#') return IP_POOL_NONE; + index = atoi(p); + + p = strtok(0, " \t\n"); + if (!p || *p == '#') return index; + if (namep) *namep = p; + + p = strtok(0, " \t\n"); + if (!p || *p == '#') return index; + if (typep) *typep = p; + + p = strtok(0, "#\n"); + if (argp) *argp = p; + + return index; +} + +static ip_pool_t get_index(char *name) +{ + FILE *fp; + char *poolname, *type, *arg, *p; + ip_pool_t res; + + if (isdigit(*name)) + return atoi(name); + fp = fopen(IPPOOL_CONF, "r"); + if (!fp) { + fprintf(stderr, "cannot open %s - no pool names", IPPOOL_CONF); + exit(1); + } + while (SCAN_EOF != (res=get_index_line(fp, &poolname, &type, &arg))) { + if (poolname && 0 == strcmp(poolname, name)) { + if (!type || (0 == strcmp(type, "bitmap"))) { + conf_type = IP_POOL_T_BITMAP; + p = strtok(arg, " \t"); + if (p) { + conf_addr = inet_addr(p); + p = strtok(0, " \t"); + if (p) + conf_addr2 = inet_addr(p); + else + conf_addr = 0; + } + } + break; + } + } + fclose(fp); + if (res == SCAN_EOF) { + fprintf(stderr, "pool '%s' not found in %s\n", + name, IPPOOL_CONF); + exit(1); + } + return res; +} + +static char *get_name(ip_pool_t index) +{ + FILE *fp; + static char sbuf[256]; + int ok = 0; + + fp = fopen(IPPOOL_CONF, "r"); + if (fp) { + while (fgets(sbuf, sizeof(sbuf), fp)) { + char *p = strtok(sbuf, " \t\n"); + + if (!p || *p == '#') continue; + if (index != atoi(p)) continue; + p = strtok(0, " \t\n"); + if (!p || *p == '#') continue; + memmove(sbuf, p, strlen(p)+1); + ok = 1; + break; + } + fclose(fp); + } + if (!ok) sprintf(sbuf, "%d", index); + return sbuf; +} + +/* user/kernel interaction */ + +static int fd = -1; +static int flag_n = 0; /* do not do anything; just brag about it */ +static int flag_v = 0; /* be verbose (list members) */ +static int flag_q = 0; /* be quiet */ +static int flag_u = 0; /* show usage counts in listings */ +static char *flag_t = "bitmap"; /* pool type */ + +static ip_pool_t high_nr(void) +{ + struct ip_pool_request req; + int reqlen = sizeof(req); + + req.op = IP_POOL_HIGH_NR; + if (0 > getsockopt(fd, SOL_IP, SO_IP_POOL, &req, &reqlen)) { + fprintf(stderr, + "IP_POOL_HIGH_NR failed: %s\n", + errno2msg(IP_POOL_HIGH_NR, errno)); + exit(1); + } + return req.index; +} + +static void do_list(ip_pool_t pool) +{ + struct ip_pool_request req; + int reqlen = sizeof(req); + u_int32_t first_ip; + u_int32_t last_ip; + + req.op = IP_POOL_LOOKUP; + req.index = pool; + req.addr = req.addr2 = 0; + reqlen = sizeof(req); + if (0 == getsockopt(fd, SOL_IP, SO_IP_POOL, &req, &reqlen)) { + struct in_addr ina; + ina.s_addr = req.addr; + printf("%s %s", get_name(req.index), inet_ntoa(ina)); + ina.s_addr = req.addr2; + printf(" %s", inet_ntoa(ina)); + first_ip = ntohl(req.addr); + last_ip = ntohl(req.addr2); + if (flag_u) { + req.op = IP_POOL_USAGE; + req.index = pool; + reqlen = sizeof(req); + if (0 == getsockopt(fd, SOL_IP, SO_IP_POOL, + &req, &reqlen)) { + printf(" %d %d", req.addr, req.addr2); + } else { + printf(" - -"); + } + } + printf("\n"); + if (flag_v) { + while (first_ip <= last_ip) { + req.op = IP_POOL_TEST_ADDR; + req.index = pool; + ina.s_addr = req.addr = htonl(first_ip); + reqlen = sizeof(req); + if (0 == getsockopt(fd, SOL_IP, SO_IP_POOL, + &req, &reqlen)) { + if (req.addr) printf("\t%s\n", + inet_ntoa(ina)); + } + first_ip++; + } + } + } +} + +static void do_list_all(void) +{ + ip_pool_t i, highest = high_nr(); + + for (i=0; i<=highest; i++) + do_list(i); +} + +static void do_flush(ip_pool_t pool) +{ + struct ip_pool_request req; + int reqlen = sizeof(req); + + req.op = IP_POOL_FLUSH; + req.index = pool; + if (flag_n) { + printf("ippool -F %d\n", req.index); + return; + } + if (0 > getsockopt(fd, SOL_IP, SO_IP_POOL, &req, &reqlen)) { + int err = errno; + fprintf(stderr, + "IP_POOL_FLUSH %s failed: %s\n", + get_name(pool), errno2msg(IP_POOL_FLUSH, err)); + exit(1); + } +} + +static void do_flush_all(void) +{ + ip_pool_t i, highest = high_nr(); + + for (i=0; i<=highest; i++) + do_flush(i); +} + +static void do_destroy(ip_pool_t pool) +{ + struct ip_pool_request req; + int reqlen = sizeof(req); + + req.op = IP_POOL_DESTROY; + req.index = pool; + if (flag_n) { + printf("ippool -X %d\n", req.index); + return; + } + if (0 > getsockopt(fd, SOL_IP, SO_IP_POOL, &req, &reqlen)) { + int err = errno; + fprintf(stderr, + "IP_POOL_DESTROY %s failed: %s\n", + get_name(pool), errno2msg(IP_POOL_DESTROY, err)); + exit(1); + } +} + +static void do_destroy_all(void) +{ + ip_pool_t i, highest = high_nr(); + + for (i=0; i<=highest; i++) + do_destroy(i); +} + +static int do_adddel(ip_pool_t pool, char *straddr, int op) +{ + struct ip_pool_request req; + int res; + int reqlen = sizeof(req); + + req.op = op; + req.index = pool; + req.addr = inet_addr(straddr); + req.addr2 = 0; + if (flag_n) { + printf("ippool -%c %s %s\n", + (op == IP_POOL_ADD_ADDR) ? 'A' : 'D', + get_name(req.index), straddr); + return 0; + } + res = getsockopt(fd, SOL_IP, SO_IP_POOL, &req, &reqlen); + if (0 > res) { + int err = errno; + fprintf(stderr, + "IP_POOL_ADD/DEL %s in %s failed: %s\n", + straddr, get_name(pool), errno2msg(op, err)); + exit(1); + } + if (!flag_q) + printf("%s %s %d %d\n", get_name(pool), straddr, req.addr, + op == IP_POOL_ADD_ADDR ? 1 : 0); + return req.addr; +} + +static int do_check(ip_pool_t pool, char *straddr) +{ + struct ip_pool_request req; + int reqlen = sizeof(req); + + req.op = IP_POOL_TEST_ADDR; + req.index = pool; + req.addr = inet_addr(straddr); + if (0 > getsockopt(fd, SOL_IP, SO_IP_POOL, &req, &reqlen)) { + int err = errno; + fprintf(stderr, + "IP_POOL_TEST_ADDR %s in %s failed: %s\n", + straddr, get_name(pool), + errno2msg(IP_POOL_TEST_ADDR, err)); + exit(1); + } + if (!flag_q) + printf("%s %s %d\n", get_name(req.index), straddr, req.addr); + return !req.addr; +} + +static void do_new(ip_pool_t pool, int argc, char **argv) +{ + struct ip_pool_request req; + int reqlen = sizeof(req); + + req.op = IP_POOL_INIT; + req.index = pool; + if (argc >= 2) { + conf_type = IP_POOL_T_BITMAP; + conf_addr = inet_addr(argv[0]); + conf_addr2 = inet_addr(argv[1]); + } + if (conf_type != IP_POOL_T_BITMAP || conf_addr == 0 || conf_addr2 == 0) + usage("bad pool specification"); + req.addr = conf_addr; + req.addr2 = conf_addr2; + if (flag_n) { + printf("ippool -N %s [-T %s] ...\n", get_name(pool), + conf_type == IP_POOL_T_BITMAP + ? "bitmap" + : "???"); + return; + } + if (0 > getsockopt(fd, SOL_IP, SO_IP_POOL, &req, &reqlen)) { + struct in_addr ina; + int err = errno; + ina.s_addr = conf_addr; + fprintf(stderr, + "IP_POOL_INIT %s [%s-", + get_name(pool), inet_ntoa(ina)); + ina.s_addr = conf_addr2; + fprintf(stderr, + "%s] failed: %s\n", + inet_ntoa(ina), errno2msg(IP_POOL_INIT, err)); + exit(1); + } +} + +static void do_new_all(void) +{ + FILE *fp; + char *poolname, *type, *arg, *p; + int res; + + fp = fopen(IPPOOL_CONF, "r"); + if (!fp) { + fprintf(stderr, "cannot open %s - no pool names", IPPOOL_CONF); + exit(1); + } + while (SCAN_EOF != (res=get_index_line(fp, &poolname, &type, &arg))) { + if (poolname && type && (0 == strcmp(type, "bitmap"))) { + conf_type = IP_POOL_T_BITMAP; + p = strtok(arg, " \t"); + if (p) { + conf_addr = inet_addr(p); + p = strtok(0, " \t"); + if (p) + conf_addr2 = inet_addr(p); + else + conf_addr = 0; + } + if (conf_addr != 0) { + if (flag_v) + printf("ippool -N %s (%s) [%s]\n", + poolname, type, arg); + do_new(res, 0, 0); + } + } + } + fclose(fp); +} + +int main(int argc, char **argv) +{ + int opt; + int op; +#define OP_NONE 0 +#define OP_LIST 1 +#define OP_LIST_ALL 2 +#define OP_FLUSH 3 +#define OP_FLUSH_ALL 4 +#define OP_DESTROY 5 +#define OP_DESTROY_ALL 6 +#define OP_ADD 7 +#define OP_DEL 8 +#define OP_CHECK 9 +#define OP_NEW 10 +#define OP_NEW_ALL 11 +#define OP_HIGHEST 12 + char *op_pool; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + fprintf(stderr, "cannot get DGRAM socket: %s\n", + strerror(errno)); + exit(1); + } + op_pool = 0; + op = OP_NONE; + /* GRRR. I thought getopt() would allow an "L*" specifier, for an -L + * taking an optional argument. Does not work. Bad. + * Adding -l for -L without argument, also -f/-F and -x/-X. + */ + while (EOF != (opt=getopt( argc, argv, "HhnvuqA:D:C:N:t:L:F:X:lfxB"))) + switch(opt) { + case 'l': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_LIST_ALL; + break; + case 'L': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_LIST; + op_pool = optarg; + break; + case 'f': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_FLUSH_ALL; + break; + case 'F': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_FLUSH; + op_pool = optarg; + break; + case 'x': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_DESTROY_ALL; + break; + case 'X': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_DESTROY; + op_pool = optarg; + break; + case 'A': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_ADD; + op_pool = optarg; + break; + case 'D': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_DEL; + op_pool = optarg; + break; + case 'C': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_CHECK; + op_pool = optarg; + break; + case 'B': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_NEW_ALL; + break; + case 'N': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_NEW; + op_pool = optarg; + break; + case 'H': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_HIGHEST; + break; + case 't': + flag_t = optarg; + break; + case 'n': + flag_n = 1; + break; + case 'v': + flag_v = 1; + break; + case 'u': + flag_u = 1; + break; + case 'q': + flag_q = 1; + break; + case 'h': + usage(0); + default: + usage("bad option"); + } + if (op == OP_NONE) + usage("no operation specified"); + if (op == OP_LIST_ALL) { + do_list_all(); + return 0; + } + if (op == OP_LIST) { + do_list(get_index(op_pool)); + return 0; + } + if (op == OP_FLUSH_ALL) { + do_flush_all(); + return 0; + } + if (op == OP_FLUSH) { + do_flush(get_index(op_pool)); + return 0; + } + if (op == OP_DESTROY_ALL) { + do_destroy_all(); + return 0; + } + if (op == OP_DESTROY) { + do_destroy(get_index(op_pool)); + return 0; + } + if (op == OP_CHECK) { + if (optind >= argc) + usage("missing address to check"); + return do_check(get_index(op_pool), argv[optind]); + } + if (op == OP_NEW_ALL) { + do_new_all(); + return 0; + } + if (op == OP_NEW) { + do_new(get_index(op_pool), argc-optind, argv+optind); + return 0; + } + if (op == OP_ADD) { + if (optind >= argc) + usage("missing address to add"); + return do_adddel(get_index(op_pool), + argv[optind], IP_POOL_ADD_ADDR); + } + if (op == OP_DEL) { + if (optind >= argc) + usage("missing address to delete"); + return do_adddel(get_index(op_pool), + argv[optind], IP_POOL_DEL_ADDR); + } + if (op == OP_HIGHEST) { + printf("%d\n", high_nr()); + return 0; + } + usage("no operation specified"); + return 0; +} diff --git a/ippool/libippool.c b/ippool/libippool.c new file mode 100644 index 0000000..7f3ac76 --- /dev/null +++ b/ippool/libippool.c @@ -0,0 +1,72 @@ +/* support functions for ip_pool modules */ + +#include +#include +#include +#include + +#include + +void ip_pool_init(void) +{ +} + +ip_pool_t ip_pool_get_index(char *name) +{ + FILE *fp; + char buf[256]; + + if (isdigit(*name)) + return atoi(name); + fp = fopen(IPPOOL_CONF, "r"); + if (!fp) exit_error(PARAMETER_PROBLEM, + "cannot open %s - no pool names", IPPOOL_CONF); + while (fgets(buf, sizeof(buf), fp)) { + char *p = strtok(buf, " \t\n"); + ip_pool_t index; + + if (!p || *p == '#') continue; + index = atoi(p); + p = strtok(0, " \t\n"); + if (p && 0 == strcmp(p, name)) { + fclose(fp); + return index; + } + } + exit_error(PARAMETER_PROBLEM, + "pool '%s' not found in %s\n", name, IPPOOL_CONF); +} + +char *ip_pool_get_name(char *buf, int size, ip_pool_t index, int numeric) +{ + FILE *fp; + int ok = 0; + + /* make sure we have enough room, at least for a %d */ + if (size < 16) + exit_error(PARAMETER_PROBLEM, + "ip_pool_support:get_name buf too small (%d vs. 16)\n", + size); + if (numeric) + goto do_numeric; + fp = fopen(IPPOOL_CONF, "r"); + if (fp) { + while (fgets(buf, size, fp)) { + char *p = strtok(buf, " \t\n"); + + if (!p || *p == '#') continue; + if (index != atoi(p)) continue; + p = strtok(0, " \t\n"); + if (!p || *p == '#') continue; + memmove(buf, p, strlen(p)+1); + ok = 1; + break; + } + fclose(fp); + } + if (!ok) { +do_numeric: + sprintf(buf, "%d", index); + } + return buf; +} diff --git a/iptables-restore.c.counters b/iptables-restore.c.counters new file mode 100644 index 0000000..b40630c --- /dev/null +++ b/iptables-restore.c.counters @@ -0,0 +1,384 @@ +/* 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 + * + * $Id: iptables-restore.c,v 1.26 2003/05/02 15:30:11 laforge Exp $ + */ + +#include +#include +#include +#include +#include +#include "iptables.h" +#include "libiptc/libiptc.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 struct option options[] = { + { "binary", 0, 0, 'b' }, + { "counters", 0, 0, 'c' }, + { "verbose", 1, 0, 'v' }, + { "help", 0, 0, 'h' }, + { "noflush", 0, 0, 'n'}, + { "modprobe", 1, 0, 'M'}, + { 0 } +}; + +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] [-h]\n" + " [ --binary ]\n" + " [ --counters ]\n" + " [ --verbose ]\n" + " [ --help ]\n" + " [ --noflush ]\n" + " [ --modprobe=]\n", name); + + exit(1); +} + +iptc_handle_t create_handle(const char *tablename, const char* modprobe ) +{ + iptc_handle_t handle; + + handle = iptc_init(tablename); + + if (!handle) { + /* try to insmod the module if iptc_init failed */ + iptables_insmod("ip_tables", modprobe); + handle = iptc_init(tablename); + } + + if (!handle) { + exit_error(PARAMETER_PROBLEM, "%s: unable to initialize" + "table '%s'\n", program_name, tablename); + exit(1); + } + return handle; +} + +int parse_counters(char *string, struct ipt_counters *ctr) +{ + return (sscanf(string, "[%llu:%llu]", &ctr->pcnt, &ctr->bcnt) == 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) < sizeof(newargv)/sizeof(char *))) { + newargv[newargc] = strdup(what); + newargc++; + return 1; + } else + return 0; +} + +static void free_argv(void) { + int i; + + for (i = 0; i < newargc; i++) + free(newargv[i]); +} + +int main(int argc, char *argv[]) +{ + iptc_handle_t handle = NULL; + char buffer[10240]; + int c; + char curtable[IPT_TABLE_MAXNAMELEN + 1]; + FILE *in; + const char *modprobe = 0; + int in_table = 0; + + program_name = "iptables-restore"; + program_version = IPTABLES_VERSION; + line = 0; + +#ifdef NO_SHARED_LIBS + init_extensions(); +#endif + + while ((c = getopt_long(argc, argv, "bcvhnM:", options, NULL)) != -1) { + switch (c) { + case 'b': + binary = 1; + break; + case 'c': + counters = 1; + break; + case 'v': + verbose = 1; + break; + case 'h': + print_usage("iptables-restore", + IPTABLES_VERSION); + break; + case 'n': + noflush = 1; + break; + case 'M': + modprobe = optarg; + break; + } + } + + if (optind == argc - 1) { + in = fopen(argv[optind], "r"); + 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; + + /* 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)) { + DEBUGP("Calling commit\n"); + ret = iptc_commit(&handle); + 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) { + exit_error(PARAMETER_PROBLEM, + "%s: line %u table name invalid\n", + program_name, line); + exit(1); + } + strncpy(curtable, table, IPT_TABLE_MAXNAMELEN); + + if (handle) + iptc_free(&handle); + + handle = create_handle(table, modprobe); + if (noflush == 0) { + DEBUGP("Cleaning all chains of table '%s'\n", + table); + for_each_chain(flush_entries, verbose, 1, + &handle); + + DEBUGP("Deleting all user-defined chains " + "of table '%s'\n", table); + for_each_chain(delete_chain, 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) { + exit_error(PARAMETER_PROBLEM, + "%s: line %u chain name invalid\n", + program_name, line); + exit(1); + } + + if (!iptc_builtin(chain, handle)) { + DEBUGP("Creating new chain '%s'\n", chain); + if (!iptc_create_chain(chain, &handle)) + exit_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) { + exit_error(PARAMETER_PROBLEM, + "%s: line %u policy invalid\n", + program_name, line); + exit(1); + } + + if (strcmp(policy, "-") != 0) { + struct ipt_counters count; + + if (counters) { + char *ctrs; + ctrs = strtok(NULL, " \t\n"); + + parse_counters(ctrs, &count); + + } 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)) + exit_error(OTHER_PROBLEM, + "Can't set policy `%s'" + " on `%s' line %u: %s\n", + chain, policy, 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 *param_start, *curchar; + int quote_open; + + /* reset the newargv */ + newargc = 0; + + if (buffer[0] == '[') { + /* we have counters in our input */ + ptr = strchr(buffer, ']'); + if (!ptr) + exit_error(PARAMETER_PROBLEM, + "Bad line %u: need ]\n", + line); + + pcnt = strtok(buffer+1, ":"); + if (!pcnt) + exit_error(PARAMETER_PROBLEM, + "Bad line %u: need :\n", + line); + + bcnt = strtok(NULL, "]"); + if (!bcnt) + exit_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((char *) &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; + param_start = parsestart; + + for (curchar = parsestart; *curchar; curchar++) { + if (*curchar == '"') { + if (quote_open) { + quote_open = 0; + *curchar = ' '; + } else { + quote_open = 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)) { + exit_error(PARAMETER_PROBLEM, + "Line %u seems to have a " + "-t table option.\n", line); + exit(1); + } + + add_argv(param_buffer); + param_start += param_len + 1; + } else { + /* regular character, skip */ + } + } + + DEBUGP("calling do_command(%u, argv, &%s, handle):\n", + newargc, curtable); + + for (a = 0; a < newargc; a++) + DEBUGP("argv[%u]: %s\n", a, newargv[a]); + + ret = do_command(newargc, newargv, + &newargv[2], &handle); + + free_argv(); + } + if (!ret) { + fprintf(stderr, "%s: line %u failed\n", + program_name, line); + exit(1); + } + } + + return 0; +} diff --git a/iptables.8 b/iptables.8 new file mode 100644 index 0000000..b79f1ec --- /dev/null +++ b/iptables.8 @@ -0,0 +1,1072 @@ +.TH IPTABLES 8 "Mar 09, 2002" "" "" +.\" +.\" 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 \- administration tool for IPv4 packet filtering and NAT +.SH SYNOPSIS +.BR "iptables [-t table] -[AD] " "chain rule-specification [options]" +.br +.BR "iptables [-t table] -I " "chain [rulenum] rule-specification [options]" +.br +.BR "iptables [-t table] -R " "chain rulenum rule-specification [options]" +.br +.BR "iptables [-t table] -D " "chain rulenum [options]" +.br +.BR "iptables [-t table] -[LFZ] " "[chain] [options]" +.br +.BR "iptables [-t table] -N " "chain" +.br +.BR "iptables [-t table] -X " "[chain]" +.br +.BR "iptables [-t table] -P " "chain target [options]" +.br +.BR "iptables [-t table] -E " "old-chain-name new-chain-name" +.SH DESCRIPTION +.B Iptables +is used to set up, maintain, and inspect the tables of IP 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. + +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 +.IR ACCEPT , +.IR DROP , +.IR QUEUE , +or +.IR RETURN . +.PP +.I ACCEPT +means to let the packet through. +.I DROP +means to drop the packet on the floor. +.I QUEUE +means to pass the packet to userspace (if supported by the kernel). +.I RETURN +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 +.I RETURN +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 +.BI "-t, --table " "table" +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 +.BR "filter" : +This is the default table (if no -t option is passed). It contains +the built-in chains +.B INPUT +(for packets coming into the box itself), +.B FORWARD +(for packets being routed through the box), and +.B OUTPUT +(for locally-generated packets). +.TP +.BR "nat" : +This table is consulted when a packet that creates a new +connection is encountered. It consists of three built-ins: +.B PREROUTING +(for altering packets as soon as they come in), +.B OUTPUT +(for altering locally-generated packets before routing), and +.B POSTROUTING +(for altering packets as they are about to go out). +.TP +.BR "mangle" : +This table is used for specialized packet alteration. Until kernel +2.4.17 it had two built-in chains: +.B PREROUTING +(for altering incoming packets before routing) and +.B OUTPUT +(for altering locally-generated packets before routing). +Since kernel 2.4.18, three other built-in chains are also supported: +.B INPUT +(for packets coming into the box itself), +.B FORWARD +(for altering packets being routed through the box), and +.B POSTROUTING +(for altering packets as they are about to go out). +.RE +.SH OPTIONS +The options that are recognized by +.B iptables +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 +.B iptables +can differentiate it from all other options. +.TP +.BI "-A, --append " "chain rule-specification" +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 +.BI "-D, --delete " "chain rule-specification" +.ns +.TP +.BI "-D, --delete " "chain rulenum" +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 +.BR "-I, --insert " "\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 +.BI "-R, --replace " "chain rulenum rule-specification" +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 +.BR "-L, --list " "[\fIchain\fP]" +List all rules in the selected chain. If no chain is selected, all +chains are listed. As 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 +.B -n +option, in order to avoid long reverse DNS lookups. +It is legal to specify the +.B -Z +(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 +.BR "-F, --flush " "[\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 +.BR "-Z, --zero " "[\fIchain\fP]" +Zero the packet and byte counters in all chains. It is legal to +specify the +.B "-L, --list" +(list) option as well, to see the counters immediately before they are +cleared. (See above.) +.TP +.BI "-N, --new-chain " "chain" +Create a new user-defined chain by the given name. There must be no +target of that name already. +.TP +.BR "-X, --delete-chain " "[\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. If no argument is given, it +will attempt to delete every non-builtin chain in the table. +.TP +.BI "-P, --policy " "chain target" +Set the policy for the chain to the given target. See the section +.B TARGETS +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 +.BI "-E, --rename-chain " "old-chain new-chain" +Rename the user specified chain to the user supplied name. This is +cosmetic, and has no effect on the structure of the table. +.TP +.B -h +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 +.BR "-p, --protocol " "[!] \fIprotocol\fP" +The protocol of the rule or of the packet to check. +The specified protocol can be one of +.IR tcp , +.IR udp , +.IR icmp , +or +.IR all , +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 +.IR all . +Protocol +.I all +will match with all protocols and is taken as default when this +option is omitted. +.TP +.BR "-s, --source " "[!] \fIaddress\fP[/\fImask\fP]" +Source specification. +.I Address +can be either a network name, a hostname (please note that specifying +any name to be resolved with a remote query such as DNS is a really bad idea), +a network IP address (with /mask), or a plain IP address. +The +.I mask +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 +.I 24 +is equivalent to +.IR 255.255.255.0 . +A "!" argument before the address specification inverts the sense of +the address. The flag +.B --src +is an alias for this option. +.TP +.BR "-d, --destination " "[!] \fIaddress\fP[/\fImask\fP]" +Destination specification. +See the description of the +.B -s +(source) flag for a detailed description of the syntax. The flag +.B --dst +is an alias for this option. +.TP +.BI "-j, --jump " "target" +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 +.B EXTENSIONS +below). If this +option is omitted in a rule, then matching the rule will have no +effect on the packet's fate, but the counters on the rule will be +incremented. +.TP +.BR "-i, --in-interface " "[!] \fIname\fP" +Name of an interface via which a packet is going to be received (only for +packets entering the +.BR INPUT , +.B FORWARD +and +.B PREROUTING +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 +.BR "-o, --out-interface " "[!] \fIname\fP" +Name of an interface via which a packet is going to be sent (for packets +entering the +.BR FORWARD , +.B OUTPUT +and +.B POSTROUTING +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 +.B "[!] " "-f, --fragment" +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 +.BI "-c, --set-counters " "PKTS BYTES" +This enables the administrator to initialize the packet and byte +counters of a rule (during +.B INSERT, +.B APPEND, +.B REPLACE +operations). +.SS "OTHER OPTIONS" +The following additional options can be specified: +.TP +.B "-v, --verbose" +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 +.B -x +flag to change this). +For appending, insertion, deletion and replacement, this causes +detailed information on the rule or rules to be printed. +.TP +.B "-n, --numeric" +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 +.B "-x, --exact" +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 +.B -L +command. +.TP +.B "--line-numbers" +When listing rules, add line numbers to the beginning of each rule, +corresponding to that rule's position in the chain. +.TP +.B "--modprobe=command" +When adding or inserting rules into a chain, use +.B command +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 +.B -p +or +.B --protocol +is specified, or with the +.B -m +or +.B --match +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 +.B -h +or +.B --help +options after the module has been specified to receive help specific +to that module. + +The following are included in the base package, and most of these can +be preceded by a +.B ! +to invert the sense of the match. +.SS ah +This module matches the SPIs in AH header of IPSec packets. +.TP +.BR "--ahspi " "[!] \fIspi\fP[:\fIspi\fP]" +.SS conntrack +This module, when combined with connection tracking, allows access to +more connection tracking information than the "state" match. +(this module is present only if iptables was compiled under a kernel +supporting this feature) +.TP +.BI "--ctstate " "state" +Where state is a comma separated list of the connection states to +match. Possible states are +.B INVALID +meaning that the packet is associated with no known connection, +.B ESTABLISHED +meaning that the packet is associated with a connection which has seen +packets in both directions, +.B NEW +meaning that the packet has started a new connection, or otherwise +associated with a connection which has not seen packets in both +directions, and +.B RELATED +meaning that the packet is starting a new connection, but is +associated with an existing connection, such as an FTP data transfer, +or an ICMP error. +.B SNAT +A virtual state, matching if the original source address differs from +the reply destination. +.B DNAT +A virtual state, matching if the original destination differs from the +reply source. +.TP +.BI "--ctproto " "proto" +Protocol to match (by number or name) +.TP +.BI "--ctorigsrc " "[!] \fIaddress\fP[/\fImask\fP]" +Match against original source address +.TP +.BI "--ctorigdst " "[!] \fIaddress\fP[/\fImask\fP]" +Match against original destination address +.TP +.BI "--ctreplsrc " "[!] \fIaddress\fP[/\fImask\fP]" +Match against reply source address +.TP +.BI "--ctrepldst " "[!] \fIaddress\fB[/\fImask\fP]" +Match against reply destination address +.TP +.BI "--ctstatus " "[\fINONE|EXPECTED|SEEN_REPLY|ASSURED\fP][,...]" +Match against internal conntrack states +.TP +.BI "--ctexpire " "\fItime\fP[\fI:time\fP]" +Match remaining lifetime in seconds against given value +or range of values (inclusive) +.SS dscp +This module matches the 6 bit DSCP field within the TOS field in the +IP header. DSCP has superseded TOS within the IETF. +.TP +.BI "--dscp " "value" +Match against a numeric (decimal or hex) value [0-32]. +.TP +.BI "--dscp-class " "\fIDiffServ Class\fP" +Match the DiffServ class. This value may be any of the +BE, EF, AFxx or CSx classes. It will then be converted +into it's according numeric value. +.SS esp +This module matches the SPIs in ESP header of IPSec packets. +.TP +.BR "--espspi " "[!] \fIspi\fP[:\fIspi\fP]" +.SS helper +This module matches packets related to a specific conntrack-helper. +.TP +.BI "--helper " "string" +Matches packets related to the specified conntrack-helper. +.RS +.PP +string can be "ftp" for packets related to a ftp-session on default port. +For other ports append -portnr to the value, ie. "ftp-2121". +.PP +Same rules apply for other conntrack-helpers. +.RE +.SS icmp +This extension is loaded if `--protocol icmp' is specified. It +provides the following option: +.TP +.BR "--icmp-type " "[!] \fItypename\fP" +This allows specification of the ICMP type, which can be a numeric +ICMP type, or one of the ICMP type names shown by the command +.nf + iptables -p icmp -h +.fi +.SS length +This module matches the length of a packet against a specific value +or range of values. +.TP +.BR "--length " "\fIlength\fP[:\fIlength\fP]" +.SS limit +This module matches at a limited rate using a token bucket filter. +A rule using this extension will match until this limit is reached +(unless the `!' flag is used). It can be used in combination with the +.B LOG +target to give limited logging, for example. +.TP +.BI "--limit " "rate" +Maximum average matching rate: specified as a number, with an optional +`/second', `/minute', `/hour', or `/day' suffix; the default is +3/hour. +.TP +.BI "--limit-burst " "number" +Maximum initial number of packets to match: this number gets +recharged by one every time the limit specified above is not reached, +up to this number; the default is 5. +.SS mac +.TP +.BR "--mac-source " "[!] \fIaddress\fP" +Match source MAC address. It must be of the form XX:XX:XX:XX:XX:XX. +Note that this only makes sense for packets coming from an Ethernet device +and entering the +.BR PREROUTING , +.B FORWARD +or +.B INPUT +chains. +.SS mark +This module matches the netfilter mark field associated with a packet +(which can be set using the +.B MARK +target below). +.TP +.BR "--mark " "\fIvalue\fP[/\fImask\fP]" +Matches packets with the given unsigned mark value (if a mask is +specified, this is logically ANDed with the mask before the +comparison). +.SS multiport +This module matches a set of source or destination ports. Up to 15 +ports can be specified. It can only be used in conjunction with +.B "-p tcp" +or +.BR "-p udp" . +.TP +.BR "--source-ports " "\fIport\fP[,\fIport\fP[,\fIport\fP...]]" +Match if the source port is one of the given ports. The flag +.B --sports +is a convenient alias for this option. +.TP +.BR "--destination-ports " "\fIport\fP[,\fIport\fP[,\fIport\fP...]]" +Match if the destination port is one of the given ports. The flag +.B --dports +is a convenient alias for this option. +.TP +.BR "--ports " "\fIport\fP[,\fIport\fP[,\fIport\fP...]]" +Match if the both the source and destination ports are equal to each +other and to one of the given ports. +.SS owner +This module attempts to match various characteristics of the packet +creator, for locally-generated packets. It is only valid in the +.B OUTPUT +chain, and even this some packets (such as ICMP ping responses) may +have no owner, and hence never match. +.TP +.BI "--uid-owner " "userid" +Matches if the packet was created by a process with the given +effective user id. +.TP +.BI "--gid-owner " "groupid" +Matches if the packet was created by a process with the given +effective group id. +.TP +.BI "--pid-owner " "processid" +Matches if the packet was created by a process with the given +process id. +.TP +.BI "--sid-owner " "sessionid" +Matches if the packet was created by a process in the given session +group. +.TP +.BI "--cmd-owner " "name" +Matches if the packet was created by a process with the given command name. +(this option is present only if iptables was compiled under a kernel +supporting this feature) +.SS physdev +This module matches on the bridge port input and output devices enslaved +to a bridge device. This module is a part of the infrastructure that enables +a transparent bridging IP firewall and is only useful for kernel versions +above version 2.5.44. +.TP +.B --physdev-in name +Name of a bridge port via which a packet is received (only for +packets entering the +.BR INPUT , +.B FORWARD +and +.B PREROUTING +chains). If the interface name ends in a "+", then any +interface which begins with this name will match. If the packet didn't arrive +through a bridge device, this packet won't match this option, unless '!' is used. +.TP +.B --physdev-out name +Name of a bridge port via which a packet is going to be sent (for packets +entering the +.BR FORWARD , +.B OUTPUT +and +.B POSTROUTING +chains). If the interface name ends in a "+", then any +interface which begins with this name will match. Note that in the +.BR nat " and " mangle +.B OUTPUT +chains one cannot match on the bridge output port, however one can in the +.B "filter OUTPUT" +chain. If the packet won't leave by a bridge device or it is yet unknown what +the output device will be, then the packet won't match this option, unless +'!' is used. +.TP +.B --physdev-is-in +Matches if the packet has entered through a bridge interface. +.TP +.B --physdev-is-out +Matches if the packet will leave through a bridge interface. +.TP +.B --physdev-is-bridged +Matches if the packet is being bridged and therefore is not being routed. +This is only useful in the FORWARD and POSTROUTING chains. +.SS pkttype +This module matches the link-layer packet type. +.TP +.BI "--pkt-type " "[\fIunicast\fP|\fIbroadcast\fP|\fImulticast\fP]" +.SS state +This module, when combined with connection tracking, allows access to +the connection tracking state for this packet. +.TP +.BI "--state " "state" +Where state is a comma separated list of the connection states to +match. Possible states are +.B INVALID +meaning that the packet could not be identified for some reason which +includes running out of memory and ICMP errors which don't correspond to any +known connection, +.B ESTABLISHED +meaning that the packet is associated with a connection which has seen +packets in both directions, +.B NEW +meaning that the packet has started a new connection, or otherwise +associated with a connection which has not seen packets in both +directions, and +.B RELATED +meaning that the packet is starting a new connection, but is +associated with an existing connection, such as an FTP data transfer, +or an ICMP error. +.SS tcp +These extensions are loaded if `--protocol tcp' is specified. It +provides the following options: +.TP +.BR "--source-port " "[!] \fIport\fP[:\fIport\fP]" +Source port or port range specification. This can either be a service +name or a port number. An inclusive range can also be specified, +using the format +.IR port : port . +If the first port is omitted, "0" is assumed; if the last is omitted, +"65535" is assumed. +If the second port greater then the first they will be swapped. +The flag +.B --sport +is a convenient alias for this option. +.TP +.BR "--destination-port " "[!] \fIport\fP[:\fIport\fP]" +Destination port or port range specification. The flag +.B --dport +is a convenient alias for this option. +.TP +.BR "--tcp-flags " "[!] \fImask\fP \fIcomp\fP" +Match when the TCP flags are as specified. The first argument is the +flags which we should examine, written as a comma-separated list, and +the second argument is a comma-separated list of flags which must be +set. Flags are: +.BR "SYN ACK FIN RST URG PSH ALL NONE" . +Hence the command +.nf + iptables -A FORWARD -p tcp --tcp-flags SYN,ACK,FIN,RST SYN +.fi +will only match packets with the SYN flag set, and the ACK, FIN and +RST flags unset. +.TP +.B "[!] --syn" +Only match TCP packets with the SYN bit set and the ACK and RST bits +cleared. Such packets are used to request TCP connection initiation; +for example, blocking such packets coming in an interface will prevent +incoming TCP connections, but outgoing TCP connections will be +unaffected. +It is equivalent to \fB--tcp-flags SYN,RST,ACK SYN\fP. +If the "!" flag precedes the "--syn", the sense of the +option is inverted. +.TP +.BR "--tcp-option " "[!] \fInumber\fP" +Match if TCP option set. +.TP +.BR "--mss " "\fIvalue\fP[:\fIvalue\fP]" +Match TCP SYN or SYN/ACK packets with the specified MSS value (or range), +which control the maximum packet size for that connection. +.SS tos +This module matches the 8 bits of Type of Service field in the IP +header (ie. including the precedence bits). +.TP +.BI "--tos " "tos" +The argument is either a standard name, (use +.br + iptables -m tos -h +.br +to see the list), or a numeric value to match. +.SS ttl +This module matches the time to live field in the IP header. +.TP +.BI "--ttl " "ttl" +Matches the given TTL value. +.SS udp +These extensions are loaded if `--protocol udp' is specified. It +provides the following options: +.TP +.BR "--source-port " "[!] \fIport\fP[:\fIport\fP]" +Source port or port range specification. +See the description of the +.B --source-port +option of the TCP extension for details. +.TP +.BR "--destination-port " "[!] \fIport\fP[:\fIport\fP]" +Destination port or port range specification. +See the description of the +.B --destination-port +option of the TCP extension for details. +.SS unclean +This module takes no options, but attempts to match packets which seem +malformed or unusual. This is regarded as experimental. +.SH TARGET EXTENSIONS +iptables can use extended target modules: the following are included +in the standard distribution. +.SS DNAT +This target is only valid in the +.B nat +table, in the +.B PREROUTING +and +.B OUTPUT +chains, and user-defined chains which are only called from those +chains. It specifies that the destination address of the packet +should be modified (and all future packets in this connection will +also be mangled), and rules should cease being examined. It takes one +type of option: +.TP +.BR "--to-destination " "\fIipaddr\fP[-\fIipaddr\fP][:\fIport\fP-\fIport\fP]" +which can specify a single new destination IP address, an inclusive +range of IP addresses, and optionally, a port range (which is only +valid if the rule also specifies +.B "-p tcp" +or +.BR "-p udp" ). +If no port range is specified, then the destination port will never be +modified. +.RS +.PP +You can add several --to-destination options. If you specify more +than one destination address, either via an address range or multiple +--to-destination options, a simple round-robin (one after another in +cycle) load balancing takes place between these adresses. +.SS DSCP +This target allows to alter the value of the DSCP bits within the TOS +header of the IPv4 packet. As this manipulates a packet, it can only +be used in the mangle table. +.TP +.BI "--set-dscp " "value" +Set the DSCP field to a numerical value (can be decimal or hex) +.TP +.BI "--set-dscp-class " "class" +Set the DSCP field to a DiffServ class. +.SS ECN +This target allows to selectively work around known ECN blackholes. +It can only be used in the mangle table. +.TP +.BI "--ecn-tcp-remove" +Remove all ECN bits from the TCP header. Of course, it can only be used +in conjunction with +.BR "-p tcp" . +.SS LOG +Turn on kernel logging of matching packets. When this option is set +for a rule, the Linux kernel will print some information on all +matching packets (like most IP header fields) via the kernel log +(where it can be read with +.I dmesg +or +.IR syslogd (8)). +This is a "non-terminating target", i.e. rule traversal continues at +the next rule. So if you want to LOG the packets you refuse, use two +separate rules with the same matching criteria, first using target LOG +then DROP (or REJECT). +.TP +.BI "--log-level " "level" +Level of logging (numeric or see \fIsyslog.conf\fP(5)). +.TP +.BI "--log-prefix " "prefix" +Prefix log messages with the specified prefix; up to 29 letters long, +and useful for distinguishing messages in the logs. +.TP +.B --log-tcp-sequence +Log TCP sequence numbers. This is a security risk if the log is +readable by users. +.TP +.B --log-tcp-options +Log options from the TCP packet header. +.TP +.B --log-ip-options +Log options from the IP packet header. +.SS MARK +This is used to set the netfilter mark value associated with the +packet. It is only valid in the +.B mangle +table. It can for example be used in conjunction with iproute2. +.TP +.BI "--set-mark " "mark" +.SS MASQUERADE +This target is only valid in the +.B nat +table, in the +.B POSTROUTING +chain. It should only be used with dynamically assigned IP (dialup) +connections: if you have a static IP address, you should use the SNAT +target. Masquerading is equivalent to specifying a mapping to the IP +address of the interface the packet is going out, but also has the +effect that connections are +.I forgotten +when the interface goes down. This is the correct behavior when the +next dialup is unlikely to have the same interface address (and hence +any established connections are lost anyway). It takes one option: +.TP +.BR "--to-ports " "\fIport\fP[-\fIport\fP]" +This specifies a range of source ports to use, overriding the default +.B SNAT +source port-selection heuristics (see above). This is only valid +if the rule also specifies +.B "-p tcp" +or +.BR "-p udp" . +.SS MIRROR +This is an experimental demonstration target which inverts the source +and destination fields in the IP header and retransmits the packet. +It is only valid in the +.BR INPUT , +.B FORWARD +and +.B PREROUTING +chains, and user-defined chains which are only called from those +chains. Note that the outgoing packets are +.B NOT +seen by any packet filtering chains, connection tracking or NAT, to +avoid loops and other problems. +.SS REDIRECT +This target is only valid in the +.B nat +table, in the +.B PREROUTING +and +.B OUTPUT +chains, and user-defined chains which are only called from those +chains. It alters the destination IP address to send the packet to +the machine itself (locally-generated packets are mapped to the +127.0.0.1 address). It takes one option: +.TP +.BR "--to-ports " "\fIport\fP[-\fIport\fP]" +This specifies a destination port or range of ports to use: without +this, the destination port is never altered. This is only valid +if the rule also specifies +.B "-p tcp" +or +.BR "-p udp" . +.SS REJECT +This is used to send back an error packet in response to the matched +packet: otherwise it is equivalent to +.B DROP +so it is a terminating TARGET, ending rule traversal. +This target is only valid in the +.BR INPUT , +.B FORWARD +and +.B OUTPUT +chains, and user-defined chains which are only called from those +chains. The following option controls the nature of the error packet +returned: +.TP +.BI "--reject-with " "type" +The type given can be +.nf +.B " icmp-net-unreachable" +.B " icmp-host-unreachable" +.B " icmp-port-unreachable" +.B " icmp-proto-unreachable" +.B " icmp-net-prohibited" +.B " icmp-host-prohibited or" +.B " icmp-admin-prohibited (*)" +.fi +which return the appropriate ICMP error message (\fBport-unreachable\fP is +the default). The option +.B tcp-reset +can be used on rules which only match the TCP protocol: this causes a +TCP RST packet to be sent back. This is mainly useful for blocking +.I ident +(113/tcp) probes which frequently occur when sending mail to broken mail +hosts (which won't accept your mail otherwise). +.TP +(*) Using icmp-admin-prohibited with kernels that do not support it will result in a plain DROP instead of REJECT +.SS SNAT +This target is only valid in the +.B nat +table, in the +.B POSTROUTING +chain. It specifies that the source address of the packet should be +modified (and all future packets in this connection will also be +mangled), and rules should cease being examined. It takes one type +of option: +.TP +.BR "--to-source " "\fIipaddr\fP[-\fIipaddr\fP][:\fIport\fP-\fIport\fP]" +which can specify a single new source IP address, an inclusive range +of IP addresses, and optionally, a port range (which is only valid if +the rule also specifies +.B "-p tcp" +or +.BR "-p udp" ). +If no port range is specified, then source ports below 512 will be +mapped to other ports below 512: those between 512 and 1023 inclusive +will be mapped to ports below 1024, and other ports will be mapped to +1024 or above. Where possible, no port alteration will occur. +.RS +.PP +You can add several --to-source options. If you specify more +than one source address, either via an address range or multiple +--to-source options, a simple round-robin (one after another in +cycle) takes place between these adresses. +.SS TCPMSS +This target allows to alter the MSS value of TCP SYN packets, to control +the maximum size for that connection (usually limiting it to your +outgoing interface's MTU minus 40). Of course, it can only be used +in conjunction with +.BR "-p tcp" . +.br +This target is used to overcome criminally braindead ISPs or servers +which block ICMP Fragmentation Needed packets. The symptoms of this +problem are that everything works fine from your Linux +firewall/router, but machines behind it can never exchange large +packets: +.PD 0 +.RS 0.1i +.TP 0.3i +1) +Web browsers connect, then hang with no data received. +.TP +2) +Small mail works fine, but large emails hang. +.TP +3) +ssh works fine, but scp hangs after initial handshaking. +.RE +.PD +Workaround: activate this option and add a rule to your firewall +configuration like: +.nf + iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN \\ + -j TCPMSS --clamp-mss-to-pmtu +.fi +.TP +.BI "--set-mss " "value" +Explicitly set MSS option to specified value. +.TP +.B "--clamp-mss-to-pmtu" +Automatically clamp MSS value to (path_MTU - 40). +.TP +These options are mutually exclusive. +.SS TOS +This is used to set the 8-bit Type of Service field in the IP header. +It is only valid in the +.B mangle +table. +.TP +.BI "--set-tos " "tos" +You can use a numeric TOS values, or use +.nf + iptables -j TOS -h +.fi +to see the list of valid TOS names. +.SS ULOG +This target provides userspace logging of matching packets. When this +target is set for a rule, the Linux kernel will multicast this packet +through a +.IR netlink +socket. One or more userspace processes may then subscribe to various +multicast groups and receive the packets. +Like LOG, this is a "non-terminating target", i.e. rule traversal +continues at the next rule. +.TP +.BI "--ulog-nlgroup " "nlgroup" +This specifies the netlink group (1-32) to which the packet is sent. +Default value is 1. +.TP +.BI "--ulog-prefix " "prefix" +Prefix log messages with the specified prefix; up to 32 characters +long, and useful for distinguishing messages in the logs. +.TP +.BI "--ulog-cprange " "size" +Number of bytes to be copied to userspace. A value of 0 always copies +the entire packet, regardless of its size. Default is 0. +.TP +.BI "--ulog-qthreshold " "size" +Number of packet to queue inside kernel. Setting this value to, e.g. 10 +accumulates ten packets inside the kernel and transmits them as one +netlink multipart message to userspace. Default is 1 (for backwards +compatibility). +.br +.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 +.B iptables +is very similar to ipchains by Rusty Russell. The main difference is +that the chains +.B INPUT +and +.B OUTPUT +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 +.B -i +refers to the input interface; +.B -o +refers to the output interface, and both are available for packets +entering the +.B FORWARD +chain. +.PP The various forms of NAT have been separated out; +.B iptables +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 +.BR iptables-save (8), +.BR iptables-restore (8), +.BR ip6tables (8), +.BR ip6tables-save (8), +.BR ip6tables-restore (8). +.P +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 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 target, TTL, DSCP, ECN matches and targets. +.PP +The Netfilter Core Team is: Marc Boucher, Martin Josefsson, Jozsef Kadlecsik, +James Morris, Harald Welte and Rusty Russell. +.PP +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 .. diff --git a/iptables.c.selinux b/iptables.c.selinux new file mode 100644 index 0000000..187fd86 --- /dev/null +++ b/iptables.c.selinux @@ -0,0 +1,2298 @@ +/* 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 + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef IPT_LIB_DIR +#define IPT_LIB_DIR "/usr/lib/iptables" +#endif + +#ifndef PROC_SYS_MODPROBE +#define PROC_SYS_MODPROBE "/proc/sys/kernel/modprobe" +#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_CHECK 0x0800U +#define CMD_RENAME_CHAIN 0x1000U +#define NUMBER_OF_CMD 13 +static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z', + 'N', 'X', 'P', 'E' }; + +#define OPTION_OFFSET 256 + +#define OPT_NONE 0x00000U +#define OPT_NUMERIC 0x00001U +#define OPT_SOURCE 0x00002U +#define OPT_DESTINATION 0x00004U +#define OPT_PROTOCOL 0x00008U +#define OPT_JUMP 0x00010U +#define OPT_VERBOSE 0x00020U +#define OPT_EXPANDED 0x00040U +#define OPT_VIANAMEIN 0x00080U +#define OPT_VIANAMEOUT 0x00100U +#define OPT_FRAGMENT 0x00200U +#define OPT_LINENUMBERS 0x00400U +#define OPT_COUNTERS 0x00800U +#define NUMBER_OF_OPT 12 +static const char optflags[NUMBER_OF_OPT] += { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', 'f', '3', 'c'}; + +static struct option original_opts[] = { + { "append", 1, 0, 'A' }, + { "delete", 1, 0, 'D' }, + { "insert", 1, 0, 'I' }, + { "replace", 1, 0, 'R' }, + { "list", 2, 0, 'L' }, + { "flush", 2, 0, 'F' }, + { "zero", 2, 0, 'Z' }, + { "new-chain", 1, 0, 'N' }, + { "delete-chain", 2, 0, 'X' }, + { "rename-chain", 1, 0, 'E' }, + { "policy", 1, 0, 'P' }, + { "source", 1, 0, 's' }, + { "destination", 1, 0, 'd' }, + { "src", 1, 0, 's' }, /* synonym */ + { "dst", 1, 0, 'd' }, /* synonym */ + { "protocol", 1, 0, 'p' }, + { "in-interface", 1, 0, 'i' }, + { "jump", 1, 0, 'j' }, + { "table", 1, 0, 't' }, + { "match", 1, 0, 'm' }, + { "numeric", 0, 0, 'n' }, + { "out-interface", 1, 0, 'o' }, + { "verbose", 0, 0, 'v' }, + { "exact", 0, 0, 'x' }, + { "fragments", 0, 0, 'f' }, + { "version", 0, 0, 'V' }, + { "help", 2, 0, 'h' }, + { "line-numbers", 0, 0, '0' }, + { "modprobe", 1, 0, 'M' }, + { "set-counters", 1, 0, 'c' }, + { 0 } +}; + +/* we need this for iptables-restore. iptables-restore.c sets line to the + * current line of the input file, in order to give a more precise error + * message. iptables itself doesn't need this, so it is initialized to the + * magic number of -1 */ +int line = -1; + +#ifndef __OPTIMIZE__ +struct ipt_entry_target * +ipt_get_target(struct ipt_entry *e) +{ + return (void *)e + e->target_offset; +} +#endif + +static struct option *opts = original_opts; +static unsigned int global_option_offset = 0; + +/* 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 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 -f --line */ +/*INSERT*/ {'x',' ',' ',' ',' ',' ','x',' ',' ',' ','x'}, +/*DELETE*/ {'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'}, +/*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','x'}, +/*CHECK*/ {'x','+','+','+','x',' ','x',' ',' ',' ','x'}, +/*RENAME*/ {'x','x','x','x','x',' ','x','x','x','x','x'} +}; + +static 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 +}; + +const char *program_version; +const char *program_name; + +/* Keeping track of external matches and targets: linked lists. */ +struct iptables_match *iptables_matches = NULL; +struct iptables_target *iptables_targets = NULL; + +/* Extra debugging from libiptc */ +extern void dump_entries(const iptc_handle_t handle); + +/* A few hardcoded protocols for 'all' and in case the user has no + /etc/protocols */ +struct pprot { + char *name; + u_int8_t num; +}; + +/* 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 + +static const struct pprot chain_protos[] = { + { "tcp", IPPROTO_TCP }, + { "udp", IPPROTO_UDP }, + { "icmp", IPPROTO_ICMP }, + { "esp", IPPROTO_ESP }, + { "ah", IPPROTO_AH }, + { "all", 0 }, +}; + +static char * +proto_to_name(u_int8_t proto, int nolookup) +{ + unsigned int i; + + if (proto && !nolookup) { + struct protoent *pent = getprotobynumber(proto); + if (pent) + return pent->p_name; + } + + for (i = 0; i < sizeof(chain_protos)/sizeof(struct pprot); i++) + if (chain_protos[i].num == proto) + return chain_protos[i].name; + + return NULL; +} + +struct in_addr * +dotted_to_addr(const char *dotted) +{ + static struct in_addr addr; + unsigned char *addrp; + char *p, *q; + unsigned int onebyte; + int i; + char buf[20]; + + /* copy dotted string, because we need to modify it */ + strncpy(buf, dotted, sizeof(buf) - 1); + addrp = (unsigned char *) &(addr.s_addr); + + p = buf; + for (i = 0; i < 3; i++) { + if ((q = strchr(p, '.')) == NULL) + return (struct in_addr *) NULL; + + *q = '\0'; + if (string_to_number(p, 0, 255, &onebyte) == -1) + return (struct in_addr *) NULL; + + addrp[i] = (unsigned char) onebyte; + p = q + 1; + } + + /* we've checked 3 bytes, now we check the last one */ + if (string_to_number(p, 0, 255, &onebyte) == -1) + return (struct in_addr *) NULL; + + addrp[3] = (unsigned char) onebyte; + + return &addr; +} + +static struct in_addr * +network_to_addr(const char *name) +{ + struct netent *net; + static struct in_addr addr; + + if ((net = getnetbyname(name)) != NULL) { + if (net->n_addrtype != AF_INET) + return (struct in_addr *) NULL; + addr.s_addr = htonl((unsigned long) net->n_net); + return &addr; + } + + return (struct in_addr *) NULL; +} + +static void +inaddrcpy(struct in_addr *dst, struct in_addr *src) +{ + /* memcpy(dst, src, sizeof(struct in_addr)); */ + dst->s_addr = src->s_addr; +} + +void +exit_error(enum exittype status, char *msg, ...) +{ + va_list args; + + va_start(args, msg); + fprintf(stderr, "%s v%s: ", program_name, program_version); + 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"); + exit(status); +} + +void +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", + program_name, program_name ); + exit(status); +} + +void +exit_printhelp(void) +{ + struct iptables_match *m = NULL; + struct iptables_target *t = NULL; + + printf("%s v%s\n\n" +"Usage: %s -[AD] chain rule-specification [options]\n" +" %s -[RI] chain rulenum rule-specification [options]\n" +" %s -D chain rulenum [options]\n" +" %s -[LFZ] [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", + program_name, program_version, program_name, program_name, + program_name, program_name, program_name, program_name, + program_name, program_name); + + printf( +"Commands:\n" +"Either long or short options are allowed.\n" +" --append -A chain Append to chain\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] List the rules in a chain or all chains\n" +" --flush -F [chain] Delete all rules in chain or all chains\n" +" --zero -Z [chain] 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" +" --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" +" --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 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 matches & targets */ + for (t=iptables_targets;t;t=t->next) { + printf("\n"); + t->help(); + } + for (m=iptables_matches;m;m=m->next) { + printf("\n"); + m->help(); + } + exit(0); +} + +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(int *cmd, const int newcmd, const int othercmds, int invert) +{ + if (invert) + exit_error(PARAMETER_PROBLEM, "unexpected ! flag"); + if (*cmd & (~othercmds)) + exit_error(PARAMETER_PROBLEM, "Can't use -%c with -%c\n", + cmd2char(newcmd), cmd2char(*cmd & (~othercmds))); + *cmd |= newcmd; +} + +int +check_inverse(const char option[], int *invert, int *optind, int argc) +{ + if (option && strcmp(option, "!") == 0) { + if (*invert) + exit_error(PARAMETER_PROBLEM, + "Multiple `!' flags not allowed"); + *invert = TRUE; + if (optind) { + *optind = *optind+1; + if (argc && *optind > argc) + exit_error(PARAMETER_PROBLEM, + "no argument following `!'"); + } + + return TRUE; + } + return FALSE; +} + +static void * +fw_calloc(size_t count, size_t size) +{ + void *p; + + if ((p = calloc(count, size)) == NULL) { + perror("iptables: calloc failed"); + exit(1); + } + return p; +} + +static void * +fw_malloc(size_t size) +{ + void *p; + + if ((p = malloc(size)) == NULL) { + perror("iptables: malloc failed"); + exit(1); + } + return p; +} + +static struct in_addr * +host_to_addr(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 (struct in_addr *) NULL; + + while (host->h_addr_list[*naddr] != (char *) NULL) + (*naddr)++; + addr = fw_calloc(*naddr, sizeof(struct in_addr)); + for (i = 0; i < *naddr; i++) + inaddrcpy(&(addr[i]), + (struct in_addr *) host->h_addr_list[i]); + return addr; + } + + return (struct in_addr *) NULL; +} + +static char * +addr_to_host(const struct in_addr *addr) +{ + struct hostent *host; + + if ((host = gethostbyaddr((char *) addr, + sizeof(struct in_addr), AF_INET)) != NULL) + return (char *) host->h_name; + + return (char *) NULL; +} + +/* + * 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. +*/ + +static struct in_addr * +parse_hostnetwork(const char *name, unsigned int *naddrs) +{ + struct in_addr *addrp, *addrptmp; + + if ((addrptmp = dotted_to_addr(name)) != NULL || + (addrptmp = network_to_addr(name)) != NULL) { + addrp = fw_malloc(sizeof(struct in_addr)); + inaddrcpy(addrp, addrptmp); + *naddrs = 1; + return addrp; + } + if ((addrp = host_to_addr(name, naddrs)) != NULL) + return addrp; + + exit_error(PARAMETER_PROBLEM, "host/network `%s' not found", name); +} + +static struct in_addr * +parse_mask(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 = dotted_to_addr(mask)) != NULL) + /* dotted_to_addr already returns a network byte order addr */ + return addrp; + if (string_to_number(mask, 0, 32, &bits) == -1) + exit_error(PARAMETER_PROBLEM, + "invalid mask `%s' specified", mask); + if (bits != 0) { + maskaddr.s_addr = htonl(0xFFFFFFFF << (32 - bits)); + return &maskaddr; + } + + maskaddr.s_addr = 0L; + return &maskaddr; +} + +void +parse_hostnetworkmask(const char *name, struct in_addr **addrpp, + struct in_addr *maskp, unsigned int *naddrs) +{ + struct in_addr *addrp; + char buf[256]; + char *p; + int i, j, k, n; + + strncpy(buf, name, sizeof(buf) - 1); + if ((p = strrchr(buf, '/')) != NULL) { + *p = '\0'; + addrp = parse_mask(p + 1); + } else + addrp = parse_mask(NULL); + inaddrcpy(maskp, addrp); + + /* if a null mask is given, the name is ignored, like in "any/0" */ + if (maskp->s_addr == 0L) + strcpy(buf, "0.0.0.0"); + + addrp = *addrpp = parse_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) { + (*naddrs)--; + j--; + break; + } + } + } +} + +struct iptables_match * +find_match(const char *name, enum ipt_tryload tryload) +{ + struct iptables_match *ptr; + + for (ptr = iptables_matches; ptr; ptr = ptr->next) { + if (strcmp(name, ptr->name) == 0) + break; + } + +#ifndef NO_SHARED_LIBS + if (!ptr && tryload != DONT_LOAD) { + char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so") + + strlen(name)]; + sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name); + if (dlopen(path, RTLD_NOW)) { + /* Found library. If it didn't register itself, + maybe they specified target as match. */ + ptr = find_match(name, DONT_LOAD); + + if (!ptr) + exit_error(PARAMETER_PROBLEM, + "Couldn't load match `%s'\n", + name); + } else if (tryload == LOAD_MUST_SUCCEED) + exit_error(PARAMETER_PROBLEM, + "Couldn't load match `%s':%s\n", + name, dlerror()); + } +#else + if (ptr && !ptr->loaded) { + if (tryload != DONT_LOAD) + ptr->loaded = 1; + else + ptr = NULL; + } + if(!ptr && (tryload == LOAD_MUST_SUCCEED)) { + exit_error(PARAMETER_PROBLEM, + "Couldn't find match `%s'\n", name); + } +#endif + + if (ptr) + ptr->used = 1; + + return ptr; +} + +/* Christophe Burki wants `-p 6' to imply `-m tcp'. */ +static struct iptables_match * +find_proto(const char *pname, enum ipt_tryload tryload, int nolookup) +{ + unsigned int proto; + + if (string_to_number(pname, 0, 255, &proto) != -1) { + char *protoname = proto_to_name(proto, nolookup); + + if (protoname) + return find_match(protoname, tryload); + } else + return find_match(pname, tryload); + + return NULL; +} + +u_int16_t +parse_protocol(const char *s) +{ + unsigned int proto; + + if (string_to_number(s, 0, 255, &proto) == -1) { + struct protoent *pent; + + if ((pent = getprotobyname(s))) + proto = pent->p_proto; + else { + unsigned int i; + for (i = 0; + i < sizeof(chain_protos)/sizeof(struct pprot); + i++) { + if (strcmp(s, chain_protos[i].name) == 0) { + proto = chain_protos[i].num; + break; + } + } + if (i == sizeof(chain_protos)/sizeof(struct pprot)) + exit_error(PARAMETER_PROBLEM, + "unknown protocol `%s' specified", + s); + } + } + + return (u_int16_t)proto; +} + +static void +parse_interface(const char *arg, char *vianame, unsigned char *mask) +{ + int vialen = strlen(arg); + unsigned int i; + + memset(mask, 0, IFNAMSIZ); + memset(vianame, 0, IFNAMSIZ); + + if (vialen + 1 > IFNAMSIZ) + exit_error(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 (!isalnum(vianame[i]) + && vianame[i] != '_' + && vianame[i] != '.') { + printf("Warning: wierd character in interface" + " `%s' (No aliases, :, ! or *).\n", + vianame); + break; + } + } + } +} + +/* Can't be zero. */ +static int +parse_rulenumber(const char *rule) +{ + unsigned int rulenum; + + if (string_to_number(rule, 1, INT_MAX, &rulenum) == -1) + exit_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) + exit_error(PARAMETER_PROBLEM, + "Invalid target name (too short)"); + + if (strlen(targetname)+1 > sizeof(ipt_chainlabel)) + exit_error(PARAMETER_PROBLEM, + "Invalid target name `%s' (%i chars max)", + targetname, sizeof(ipt_chainlabel)-1); + + for (ptr = targetname; *ptr; ptr++) + if (isspace(*ptr)) + exit_error(PARAMETER_PROBLEM, + "Invalid target name `%s'", targetname); + return targetname; +} + +static char * +addr_to_network(const struct in_addr *addr) +{ + struct netent *net; + + if ((net = getnetbyaddr((long) ntohl(addr->s_addr), AF_INET)) != NULL) + return (char *) net->n_name; + + return (char *) NULL; +} + +char * +addr_to_dotted(const struct in_addr *addrp) +{ + static char buf[20]; + const unsigned char *bytep; + + bytep = (const unsigned char *) &(addrp->s_addr); + sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]); + return buf; +} + +char * +addr_to_anyname(const struct in_addr *addr) +{ + char *name; + + if ((name = addr_to_host(addr)) != NULL || + (name = addr_to_network(addr)) != NULL) + return name; + + return addr_to_dotted(addr); +} + +char * +mask_to_dotted(const struct in_addr *mask) +{ + int i; + static char buf[20]; + u_int32_t maskaddr, bits; + + 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", addr_to_dotted(mask)); + + return buf; +} + +int +string_to_number(const char *s, unsigned int min, unsigned int max, + unsigned int *ret) +{ + long number; + char *end; + + /* Handle hex, octal, etc. */ + errno = 0; + number = strtol(s, &end, 0); + if (*end == '\0' && end != s) { + /* we parsed a number, let's see if we want this */ + if (errno != ERANGE && min <= number && number <= max) { + *ret = number; + return 0; + } + } + return -1; +} + +static void +set_option(unsigned int *options, unsigned int option, u_int8_t *invflg, + int invert) +{ + if (*options & option) + exit_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]) + exit_error(PARAMETER_PROBLEM, + "cannot have ! before -%c", + opt2char(option)); + *invflg |= inverse_for_options[i]; + } +} + +struct iptables_target * +find_target(const char *name, enum ipt_tryload tryload) +{ + struct iptables_target *ptr; + + /* Standard target? */ + if (strcmp(name, "") == 0 + || strcmp(name, IPTC_LABEL_ACCEPT) == 0 + || strcmp(name, IPTC_LABEL_DROP) == 0 + || strcmp(name, IPTC_LABEL_QUEUE) == 0 + || strcmp(name, IPTC_LABEL_RETURN) == 0) + name = "standard"; + + for (ptr = iptables_targets; ptr; ptr = ptr->next) { + if (strcmp(name, ptr->name) == 0) + break; + } + +#ifndef NO_SHARED_LIBS + if (!ptr && tryload != DONT_LOAD) { + char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so") + + strlen(name)]; + sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name); + if (dlopen(path, RTLD_NOW)) { + /* Found library. If it didn't register itself, + maybe they specified match as a target. */ + ptr = find_target(name, DONT_LOAD); + if (!ptr) + exit_error(PARAMETER_PROBLEM, + "Couldn't load target `%s'\n", + name); + } else if (tryload == LOAD_MUST_SUCCEED) + exit_error(PARAMETER_PROBLEM, + "Couldn't load target `%s':%s\n", + name, dlerror()); + } +#else + if (ptr && !ptr->loaded) { + if (tryload != DONT_LOAD) + ptr->loaded = 1; + else + ptr = NULL; + } + if(!ptr && (tryload == LOAD_MUST_SUCCEED)) { + exit_error(PARAMETER_PROBLEM, + "Couldn't find target `%s'\n", name); + } +#endif + + if (ptr) + ptr->used = 1; + + return ptr; +} + +static struct option * +merge_options(struct option *oldopts, const struct option *newopts, + unsigned int *option_offset) +{ + unsigned int num_old, num_new, i; + struct option *merge; + + for (num_old = 0; oldopts[num_old].name; num_old++); + for (num_new = 0; newopts[num_new].name; num_new++); + + global_option_offset += OPTION_OFFSET; + *option_offset = global_option_offset; + + merge = malloc(sizeof(struct option) * (num_new + num_old + 1)); + memcpy(merge, oldopts, num_old * sizeof(struct option)); + for (i = 0; i < num_new; i++) { + merge[num_old + i] = newopts[i]; + merge[num_old + i].val += *option_offset; + } + memset(merge + num_old + num_new, 0, sizeof(struct option)); + + return merge; +} + +void +register_match(struct iptables_match *me) +{ + struct iptables_match **i; + + if (strcmp(me->version, program_version) != 0) { + fprintf(stderr, "%s: match `%s' v%s (I'm v%s).\n", + program_name, me->name, me->version, program_version); + exit(1); + } + + if (find_match(me->name, DONT_LOAD)) { + fprintf(stderr, "%s: match `%s' already registered.\n", + program_name, me->name); + exit(1); + } + + if (me->size != IPT_ALIGN(me->size)) { + fprintf(stderr, "%s: match `%s' has invalid size %u.\n", + program_name, me->name, me->size); + exit(1); + } + + /* Append to list. */ + for (i = &iptables_matches; *i; i = &(*i)->next); + me->next = NULL; + *i = me; + + me->m = NULL; + me->mflags = 0; +} + +void +register_target(struct iptables_target *me) +{ + if (strcmp(me->version, program_version) != 0) { + fprintf(stderr, "%s: target `%s' v%s (I'm v%s).\n", + program_name, me->name, me->version, program_version); + exit(1); + } + + if (find_target(me->name, DONT_LOAD)) { + fprintf(stderr, "%s: target `%s' already registered.\n", + program_name, me->name); + exit(1); + } + + if (me->size != IPT_ALIGN(me->size)) { + fprintf(stderr, "%s: target `%s' has invalid size %u.\n", + program_name, me->name, me->size); + exit(1); + } + + /* Prepend to list. */ + me->next = iptables_targets; + iptables_targets = me; + me->t = NULL; + me->tflags = 0; +} + +static void +print_num(u_int64_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 "), number); + } + else printf(FMT("%4lluG ","%lluG "), number); + } + else printf(FMT("%4lluM ","%lluM "), number); + } else + printf(FMT("%4lluK ","%lluK "), number); + } else + printf(FMT("%5llu ","%llu "), number); + } else + printf(FMT("%8llu ","%llu "), number); +} + + +static void +print_header(unsigned int format, const char *chain, iptc_handle_t *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) +{ + struct iptables_match *match = find_match(m->u.user.name, TRY_LOAD); + + 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 hysterical raisins */ +static void +print_firewall(const struct ipt_entry *fw, + const char *targname, + unsigned int num, + unsigned int format, + const iptc_handle_t handle) +{ + struct iptables_target *target = NULL; + const struct ipt_entry_target *t; + u_int8_t flags; + char buf[BUFSIZ]; + + if (!iptc_is_chain(targname, handle)) + target = find_target(targname, TRY_LOAD); + else + target = find_target(IPT_STANDARD_TARGET, LOAD_MUST_SUCCEED); + + t = ipt_get_target((struct ipt_entry *)fw); + flags = fw->ip.flags; + + if (format & FMT_LINENUMBERS) + printf(FMT("%-4u ", "%u "), num+1); + + 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); + { + 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) + sprintf(buf, "%s", addr_to_dotted(&(fw->ip.src))); + else + sprintf(buf, "%s", addr_to_anyname(&(fw->ip.src))); + strcat(buf, mask_to_dotted(&(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) + sprintf(buf, "%s", addr_to_dotted(&(fw->ip.dst))); + else + sprintf(buf, "%s", addr_to_anyname(&(fw->ip.dst))); + strcat(buf, mask_to_dotted(&(fw->ip.dmsk))); + printf(FMT("%-19s ","-> %s"), buf); + } + + if (format & FMT_NOTABLE) + fputs(" ", stdout); + + 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] ", + t->u.target_size - sizeof(*t)); + + if (!(format & FMT_NONEWLINE)) + fputc('\n', stdout); +} + +static void +print_firewall_line(const struct ipt_entry *fw, + const iptc_handle_t 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[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + int verbose, + iptc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[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 *daddr, + int verbose, + iptc_handle_t *handle) +{ + fw->ip.src.s_addr = saddr->s_addr; + fw->ip.dst.s_addr = daddr->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[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + int verbose, + iptc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[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(struct ipt_entry *fw) +{ + /* Establish mask for comparison */ + unsigned int size; + struct iptables_match *m; + unsigned char *mask, *mptr; + + size = sizeof(struct ipt_entry); + for (m = iptables_matches; m; m = m->next) { + if (!m->used) + continue; + + size += IPT_ALIGN(sizeof(struct ipt_entry_match)) + m->size; + } + + mask = fw_calloc(1, size + + IPT_ALIGN(sizeof(struct ipt_entry_target)) + + iptables_targets->size); + + memset(mask, 0xFF, sizeof(struct ipt_entry)); + mptr = mask + sizeof(struct ipt_entry); + + for (m = iptables_matches; m; m = m->next) { + if (!m->used) + continue; + + memset(mptr, 0xFF, + IPT_ALIGN(sizeof(struct ipt_entry_match)) + + m->userspacesize); + mptr += IPT_ALIGN(sizeof(struct ipt_entry_match)) + m->size; + } + + memset(mptr, 0xFF, + IPT_ALIGN(sizeof(struct ipt_entry_target)) + + iptables_targets->userspacesize); + + return mask; +} + +static int +delete_entry(const ipt_chainlabel chain, + struct ipt_entry *fw, + unsigned int nsaddrs, + const struct in_addr saddrs[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + int verbose, + iptc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + unsigned char *mask; + + mask = make_delete_mask(fw); + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[j].s_addr; + if (verbose) + print_firewall_line(fw, *handle); + ret &= iptc_delete_entry(chain, fw, mask, handle); + } + } + return ret; +} + +int +for_each_chain(int (*fn)(const ipt_chainlabel, int, iptc_handle_t *), + int verbose, int builtinstoo, iptc_handle_t *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 = fw_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)) + continue; + ret &= fn(chains + i*sizeof(ipt_chainlabel), verbose, handle); + } + + free(chains); + return ret; +} + +int +flush_entries(const ipt_chainlabel chain, int verbose, + iptc_handle_t *handle) +{ + if (!chain) + return for_each_chain(flush_entries, 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, + iptc_handle_t *handle) +{ + if (!chain) + return for_each_chain(zero_entries, verbose, 1, handle); + + if (verbose) + fprintf(stdout, "Zeroing chain `%s'\n", chain); + return iptc_zero_entries(chain, handle); +} + +int +delete_chain(const ipt_chainlabel chain, int verbose, + iptc_handle_t *handle) +{ + if (!chain) + return for_each_chain(delete_chain, 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 verbose, int numeric, + int expanded, int linenumbers, iptc_handle_t *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"); + + print_header(format, this, handle); + i = iptc_first_rule(this, handle); + + num = 0; + while (i) { + print_firewall(i, + iptc_get_target(i, handle), + num++, + format, + *handle); + i = iptc_next_rule(i, handle); + } + found = 1; + } + + errno = ENOENT; + return found; +} + +static char *get_modprobe(void) +{ + int procfile; + char *ret; + + procfile = open(PROC_SYS_MODPROBE, O_RDONLY); + if (procfile < 0) + return NULL; + + ret = malloc(1024); + if (ret) { + switch (read(procfile, ret, 1024)) { + case -1: goto fail; + case 1024: 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 iptables_insmod(const char *modname, const char *modprobe) +{ + char *buf = NULL; + char *argv[3]; + + /* If they don't explicitly set it, read out of kernel */ + if (!modprobe) { + buf = get_modprobe(); + if (!buf) + return -1; + modprobe = buf; + } + + switch (fork()) { + case 0: + argv[0] = (char *)modprobe; + argv[1] = (char *)modname; + argv[2] = NULL; + execv(argv[0], argv); + + /* not usually reached */ + exit(0); + case -1: + return -1; + + default: /* parent */ + wait(NULL); + } + + free(buf); + return 0; +} + +static struct ipt_entry * +generate_entry(const struct ipt_entry *fw, + struct iptables_match *matches, + struct ipt_entry_target *target) +{ + unsigned int size; + struct iptables_match *m; + struct ipt_entry *e; + + size = sizeof(struct ipt_entry); + for (m = matches; m; m = m->next) { + if (!m->used) + continue; + + size += m->m->u.match_size; + } + + e = fw_malloc(size + target->u.target_size); + *e = *fw; + e->target_offset = size; + e->next_offset = size + target->u.target_size; + + size = 0; + for (m = matches; m; m = m->next) { + if (!m->used) + continue; + + memcpy(e->elems + size, m->m, m->m->u.match_size); + size += m->m->u.match_size; + } + memcpy(e->elems + size, target, target->u.target_size); + + return e; +} + +int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle) +{ + struct ipt_entry fw, *e = NULL; + int invert = 0; + unsigned int nsaddrs = 0, ndaddrs = 0; + struct in_addr *saddrs = NULL, *daddrs = NULL; + + int c, verbose = 0; + const char *chain = NULL; + const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL; + const char *policy = NULL, *newname = NULL; + unsigned int rulenum = 0, options = 0, command = 0; + const char *pcnt = NULL, *bcnt = NULL; + int ret = 1; + struct iptables_match *m; + struct iptables_target *target = NULL; + struct iptables_target *t; + const char *jumpto = ""; + char *protocol = NULL; + const char *modprobe = NULL; + int proto_used = 0; + + memset(&fw, 0, sizeof(fw)); + + opts = original_opts; + global_option_offset = 0; + + /* re-set optind to 0 in case do_command gets called + * a second time */ + optind = 0; + + /* clear mflags in case do_command gets called a second time + * (we clear the global list of all matches for security)*/ + for (m = iptables_matches; m; m = m->next) { + m->mflags = 0; + m->used = 0; + } + + for (t = iptables_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; + + while ((c = getopt_long(argc, argv, + "-A:D:R:I:L::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvnt:m:xc:", + opts, NULL)) != -1) { + switch (c) { + /* + * Command selection + */ + case 'A': + add_command(&command, CMD_APPEND, CMD_NONE, + invert); + chain = optarg; + break; + + case 'D': + add_command(&command, CMD_DELETE, CMD_NONE, + 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, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else + exit_error(PARAMETER_PROBLEM, + "-%c requires a rule number", + cmd2char(CMD_REPLACE)); + break; + + case 'I': + add_command(&command, CMD_INSERT, CMD_NONE, + 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, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'F': + add_command(&command, CMD_FLUSH, CMD_NONE, + 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, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'N': + if (optarg && *optarg == '-') + exit_error(PARAMETER_PROBLEM, + "chain name not allowed to start " + "with `-'\n"); + if (find_target(optarg, TRY_LOAD)) + exit_error(PARAMETER_PROBLEM, + "chain name may not clash " + "with target name\n"); + add_command(&command, CMD_NEW_CHAIN, CMD_NONE, + invert); + chain = optarg; + break; + + case 'X': + add_command(&command, CMD_DELETE_CHAIN, CMD_NONE, + 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, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + newname = argv[optind++]; + else + exit_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, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + policy = argv[optind++]; + else + exit_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 (!iptables_matches && protocol) + find_match(protocol, TRY_LOAD); + + exit_printhelp(); + + /* + * Option selection + */ + case 'p': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_PROTOCOL, &fw.ip.invflags, + invert); + + /* Canonicalize into lower case */ + for (protocol = argv[optind-1]; *protocol; protocol++) + *protocol = tolower(*protocol); + + protocol = argv[optind-1]; + fw.ip.proto = parse_protocol(protocol); + + if (fw.ip.proto == 0 + && (fw.ip.invflags & IPT_INV_PROTO)) + exit_error(PARAMETER_PROBLEM, + "rule would never match protocol"); + fw.nfcache |= NFC_IP_PROTO; + break; + + case 's': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_SOURCE, &fw.ip.invflags, + invert); + shostnetworkmask = argv[optind-1]; + fw.nfcache |= NFC_IP_SRC; + break; + + case 'd': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_DESTINATION, &fw.ip.invflags, + invert); + dhostnetworkmask = argv[optind-1]; + fw.nfcache |= NFC_IP_DST; + break; + + case 'j': + set_option(&options, OPT_JUMP, &fw.ip.invflags, + invert); + jumpto = parse_target(optarg); + /* TRY_LOAD (may be chain name) */ + target = find_target(jumpto, TRY_LOAD); + + if (target) { + size_t size; + + size = IPT_ALIGN(sizeof(struct ipt_entry_target)) + + target->size; + + target->t = fw_calloc(1, size); + target->t->u.target_size = size; + strcpy(target->t->u.user.name, jumpto); + target->init(target->t, &fw.nfcache); + opts = merge_options(opts, target->extra_opts, &target->option_offset); + } + break; + + + case 'i': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_VIANAMEIN, &fw.ip.invflags, + invert); + parse_interface(argv[optind-1], + fw.ip.iniface, + fw.ip.iniface_mask); + fw.nfcache |= NFC_IP_IF_IN; + break; + + case 'o': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_VIANAMEOUT, &fw.ip.invflags, + invert); + parse_interface(argv[optind-1], + fw.ip.outiface, + fw.ip.outiface_mask); + fw.nfcache |= NFC_IP_IF_OUT; + break; + + case 'f': + set_option(&options, OPT_FRAGMENT, &fw.ip.invflags, + invert); + fw.ip.flags |= IPT_F_FRAG; + fw.nfcache |= NFC_IP_FRAG; + break; + + case 'v': + if (!verbose) + set_option(&options, OPT_VERBOSE, + &fw.ip.invflags, invert); + verbose++; + break; + + case 'm': { + size_t size; + + if (invert) + exit_error(PARAMETER_PROBLEM, + "unexpected ! flag before --match"); + + m = find_match(optarg, LOAD_MUST_SUCCEED); + size = IPT_ALIGN(sizeof(struct ipt_entry_match)) + + m->size; + m->m = fw_calloc(1, size); + m->m->u.match_size = size; + strcpy(m->m->u.user.name, m->name); + m->init(m->m, &fw.nfcache); + opts = merge_options(opts, m->extra_opts, &m->option_offset); + } + break; + + case 'n': + set_option(&options, OPT_NUMERIC, &fw.ip.invflags, + invert); + break; + + case 't': + if (invert) + exit_error(PARAMETER_PROBLEM, + "unexpected ! flag before --table"); + *table = argv[optind-1]; + break; + + case 'x': + set_option(&options, OPT_EXPANDED, &fw.ip.invflags, + invert); + break; + + case 'V': + if (invert) + printf("Not %s ;-)\n", program_version); + else + printf("%s v%s\n", + program_name, program_version); + exit(0); + + case '0': + set_option(&options, OPT_LINENUMBERS, &fw.ip.invflags, + invert); + break; + + case 'M': + modprobe = optarg; + break; + + case 'c': + + set_option(&options, OPT_COUNTERS, &fw.ip.invflags, + invert); + pcnt = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + bcnt = argv[optind++]; + else + exit_error(PARAMETER_PROBLEM, + "-%c requires packet and byte counter", + opt2char(OPT_COUNTERS)); + + if (sscanf(pcnt, "%llu", &fw.counters.pcnt) != 1) + exit_error(PARAMETER_PROBLEM, + "-%c packet counter not numeric", + opt2char(OPT_COUNTERS)); + + if (sscanf(bcnt, "%llu", &fw.counters.bcnt) != 1) + exit_error(PARAMETER_PROBLEM, + "-%c byte counter not numeric", + opt2char(OPT_COUNTERS)); + + break; + + + case 1: /* non option */ + if (optarg[0] == '!' && optarg[1] == '\0') { + if (invert) + exit_error(PARAMETER_PROBLEM, + "multiple consecutive ! not" + " allowed"); + invert = TRUE; + optarg[0] = '\0'; + continue; + } + printf("Bad argument `%s'\n", optarg); + exit_tryhelp(2); + + default: + /* FIXME: This scheme doesn't allow two of the same + matches --RR */ + if (!target + || !(target->parse(c - target->option_offset, + argv, invert, + &target->tflags, + &fw, &target->t))) { + for (m = iptables_matches; m; m = m->next) { + if (!m->used) + continue; + + if (m->parse(c - m->option_offset, + argv, invert, + &m->mflags, + &fw, + &fw.nfcache, + &m->m)) + break; + } + + /* If you listen carefully, you can + actually hear this code suck. */ + + /* some explanations (after four different bugs + * in 3 different releases): If we encountere 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 iptables-restore!] + * - the protocol extension can be successively + * loaded + */ + if (m == NULL + && protocol + && (!find_proto(protocol, DONT_LOAD, + options&OPT_NUMERIC) + || (find_proto(protocol, DONT_LOAD, + options&OPT_NUMERIC) + && (proto_used == 0)) + ) + && (m = find_proto(protocol, TRY_LOAD, + options&OPT_NUMERIC))) { + /* Try loading protocol */ + size_t size; + + proto_used = 1; + + size = IPT_ALIGN(sizeof(struct ipt_entry_match)) + + m->size; + + m->m = fw_calloc(1, size); + m->m->u.match_size = size; + strcpy(m->m->u.user.name, m->name); + m->init(m->m, &fw.nfcache); + + opts = merge_options(opts, + m->extra_opts, &m->option_offset); + + optind--; + continue; + } + if (!m) + exit_error(PARAMETER_PROBLEM, + "Unknown arg `%s'", + argv[optind-1]); + } + } + invert = FALSE; + } + + for (m = iptables_matches; m; m = m->next) { + if (!m->used) + continue; + + m->final_check(m->mflags); + } + + if (target) + target->final_check(target->tflags); + + /* Fix me: must put inverse options checking here --MN */ + + if (optind < argc) + exit_error(PARAMETER_PROBLEM, + "unknown arguments found on commandline"); + if (!command) + exit_error(PARAMETER_PROBLEM, "no command specified"); + if (invert) + exit_error(PARAMETER_PROBLEM, + "nothing appropriate following !"); + + if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)) { + if (!(options & OPT_DESTINATION)) + dhostnetworkmask = "0.0.0.0/0"; + if (!(options & OPT_SOURCE)) + shostnetworkmask = "0.0.0.0/0"; + } + + if (shostnetworkmask) + parse_hostnetworkmask(shostnetworkmask, &saddrs, + &(fw.ip.smsk), &nsaddrs); + + if (dhostnetworkmask) + parse_hostnetworkmask(dhostnetworkmask, &daddrs, + &(fw.ip.dmsk), &ndaddrs); + + if ((nsaddrs > 1 || ndaddrs > 1) && + (fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP))) + exit_error(PARAMETER_PROBLEM, "! not allowed with multiple" + " source or destination IP addresses"); + + if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1)) + exit_error(PARAMETER_PROBLEM, "Replacement rule does not " + "specify a unique address"); + + generic_opt_check(command, options); + + if (chain && strlen(chain) > IPT_FUNCTION_MAXNAMELEN) + exit_error(PARAMETER_PROBLEM, + "chain name `%s' too long (must be under %i chars)", + chain, IPT_FUNCTION_MAXNAMELEN); + + /* only allocate handle if we weren't called with a handle */ + if (!*handle) + *handle = iptc_init(*table); + + if (!*handle) { + /* try to insmod the module if iptc_init failed */ + iptables_insmod("ip_tables", modprobe); + *handle = iptc_init(*table); + } + + if (!*handle) + exit_error(VERSION_PROBLEM, + "can't initialize iptables table `%s': %s", + *table, iptc_strerror(errno)); + + if (command == CMD_APPEND + || command == CMD_DELETE + || command == CMD_INSERT + || command == CMD_REPLACE) { + if (strcmp(chain, "PREROUTING") == 0 + || strcmp(chain, "INPUT") == 0) { + /* -o not valid with incoming packets. */ + if (options & OPT_VIANAMEOUT) + exit_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 (options & OPT_VIANAMEIN) + exit_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEIN), + chain); + } + + if (target && iptc_is_chain(jumpto, *handle)) { + printf("Warning: using chain %s, not extension\n", + jumpto); + + target = NULL; + } + + /* If they didn't specify a target, or it's a chain + name, use standard. */ + if (!target + && (strlen(jumpto) == 0 + || iptc_is_chain(jumpto, *handle))) { + size_t size; + + target = find_target(IPT_STANDARD_TARGET, + LOAD_MUST_SUCCEED); + + size = sizeof(struct ipt_entry_target) + + target->size; + target->t = fw_calloc(1, size); + target->t->u.target_size = size; + strcpy(target->t->u.user.name, jumpto); + target->init(target->t, &fw.nfcache); + } + + if (!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. */ + find_target(jumpto, LOAD_MUST_SUCCEED); + } else { + e = generate_entry(&fw, iptables_matches, target->t); + } + } + + switch (command) { + case CMD_APPEND: + ret = append_entry(chain, e, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_DELETE: + ret = delete_entry(chain, e, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_DELETE_NUM: + ret = iptc_delete_num_entry(chain, rulenum - 1, handle); + break; + case CMD_REPLACE: + ret = replace_entry(chain, e, rulenum - 1, + saddrs, daddrs, options&OPT_VERBOSE, + handle); + break; + case CMD_INSERT: + ret = insert_entry(chain, e, rulenum - 1, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_LIST: + ret = list_entries(chain, + options&OPT_VERBOSE, + options&OPT_NUMERIC, + options&OPT_EXPANDED, + options&OPT_LINENUMBERS, + handle); + break; + case CMD_FLUSH: + ret = flush_entries(chain, options&OPT_VERBOSE, handle); + break; + case CMD_ZERO: + ret = zero_entries(chain, options&OPT_VERBOSE, handle); + break; + case CMD_LIST|CMD_ZERO: + ret = list_entries(chain, + options&OPT_VERBOSE, + options&OPT_NUMERIC, + options&OPT_EXPANDED, + options&OPT_LINENUMBERS, + handle); + if (ret) + ret = zero_entries(chain, + options&OPT_VERBOSE, handle); + break; + case CMD_NEW_CHAIN: + ret = iptc_create_chain(chain, handle); + break; + case CMD_DELETE_CHAIN: + ret = delete_chain(chain, 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, NULL, handle); + break; + default: + /* We should never reach this... */ + exit_tryhelp(2); + } + + if (verbose > 1) + dump_entries(*handle); + + return ret; +} diff --git a/libipq/libipq.c.netlink b/libipq/libipq.c.netlink new file mode 100644 index 0000000..709c8a2 --- /dev/null +++ b/libipq/libipq.c.netlink @@ -0,0 +1,373 @@ +/* + * libipq.c + * + * IPQ userspace library. + * + * Please note that this library is still developmental, and there may + * be some API changes. + * + * Author: James Morris + * + * 07-11-2001 Modified by Fernando Anton to add support for IPv6. + * + * Copyright (c) 2000-2001 Netfilter Core Team + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +/**************************************************************************** + * + * Private interface + * + ****************************************************************************/ + +enum { + IPQ_ERR_NONE = 0, + IPQ_ERR_IMPL, + IPQ_ERR_HANDLE, + IPQ_ERR_SOCKET, + IPQ_ERR_BIND, + IPQ_ERR_BUFFER, + IPQ_ERR_RECV, + IPQ_ERR_NLEOF, + IPQ_ERR_ADDRLEN, + IPQ_ERR_STRUNC, + IPQ_ERR_RTRUNC, + IPQ_ERR_NLRECV, + IPQ_ERR_SEND, + IPQ_ERR_SUPP, + IPQ_ERR_RECVBUF, + IPQ_ERR_TIMEOUT, + IPQ_ERR_PROTOCOL +}; +#define IPQ_MAXERR IPQ_ERR_PROTOCOL + +struct ipq_errmap_t { + int errcode; + char *message; +} ipq_errmap[] = { + { IPQ_ERR_NONE, "Unknown error" }, + { IPQ_ERR_IMPL, "Implementation error" }, + { IPQ_ERR_HANDLE, "Unable to create netlink handle" }, + { IPQ_ERR_SOCKET, "Unable to create netlink socket" }, + { IPQ_ERR_BIND, "Unable to bind netlink socket" }, + { IPQ_ERR_BUFFER, "Unable to allocate buffer" }, + { IPQ_ERR_RECV, "Failed to receive netlink message" }, + { IPQ_ERR_NLEOF, "Received EOF on netlink socket" }, + { IPQ_ERR_ADDRLEN, "Invalid peer address length" }, + { IPQ_ERR_STRUNC, "Sent message truncated" }, + { IPQ_ERR_RTRUNC, "Received message truncated" }, + { IPQ_ERR_NLRECV, "Received error from netlink" }, + { IPQ_ERR_SEND, "Failed to send netlink message" }, + { IPQ_ERR_SUPP, "Operation not supported" }, + { IPQ_ERR_RECVBUF, "Receive buffer size invalid" }, + { IPQ_ERR_TIMEOUT, "Timeout"}, + { IPQ_ERR_PROTOCOL, "Invalid protocol specified" } +}; + +static int ipq_errno = IPQ_ERR_NONE; + +static ssize_t ipq_netlink_sendto(const struct ipq_handle *h, + const void *msg, size_t len); + +static ssize_t ipq_netlink_recvfrom(const struct ipq_handle *h, + unsigned char *buf, size_t len, + int timeout); + +static ssize_t ipq_netlink_sendmsg(const struct ipq_handle *h, + const struct msghdr *msg, + unsigned int flags); + +static char *ipq_strerror(int errcode); + +static ssize_t ipq_netlink_sendto(const struct ipq_handle *h, + const void *msg, size_t len) +{ + int status = sendto(h->fd, msg, len, 0, + (struct sockaddr *)&h->peer, sizeof(h->peer)); + if (status < 0) + ipq_errno = IPQ_ERR_SEND; + return status; +} + +static ssize_t ipq_netlink_sendmsg(const struct ipq_handle *h, + const struct msghdr *msg, + unsigned int flags) +{ + int status = sendmsg(h->fd, msg, flags); + if (status < 0) + ipq_errno = IPQ_ERR_SEND; + return status; +} + +static ssize_t ipq_netlink_recvfrom(const struct ipq_handle *h, + unsigned char *buf, size_t len, + int timeout) +{ + int addrlen, status; + struct nlmsghdr *nlh; + + if (len < sizeof(struct nlmsgerr)) { + ipq_errno = IPQ_ERR_RECVBUF; + return -1; + } + addrlen = sizeof(h->peer); + + if (timeout != 0) { + int ret; + struct timeval tv; + fd_set read_fds; + + if (timeout < 0) { + /* non-block non-timeout */ + tv.tv_sec = 0; + tv.tv_usec = 0; + } else { + tv.tv_sec = timeout / 1000000; + tv.tv_usec = timeout % 1000000; + } + + FD_ZERO(&read_fds); + FD_SET(h->fd, &read_fds); + ret = select(h->fd+1, &read_fds, NULL, NULL, &tv); + if (ret < 0) { + if (errno == EINTR) { + return 0; + } else { + ipq_errno = IPQ_ERR_RECV; + return -1; + } + } + if (!FD_ISSET(h->fd, &read_fds)) { + ipq_errno = IPQ_ERR_TIMEOUT; + return 0; + } + } + status = recvfrom(h->fd, buf, len, 0, + (struct sockaddr *)&h->peer, &addrlen); + if (status < 0) { + ipq_errno = IPQ_ERR_RECV; + return status; + } + if (addrlen != sizeof(h->peer)) { + ipq_errno = IPQ_ERR_RECV; + return -1; + } + if (status == 0) { + ipq_errno = IPQ_ERR_NLEOF; + return -1; + } + nlh = (struct nlmsghdr *)buf; + if (nlh->nlmsg_flags & MSG_TRUNC || nlh->nlmsg_len > status) { + ipq_errno = IPQ_ERR_RTRUNC; + return -1; + } + return status; +} + +static char *ipq_strerror(int errcode) +{ + if (errcode < 0 || errcode > IPQ_MAXERR) + errcode = IPQ_ERR_IMPL; + return ipq_errmap[errcode].message; +} + +/**************************************************************************** + * + * Public interface + * + ****************************************************************************/ + +/* + * Create and initialise an ipq handle. + */ +struct ipq_handle *ipq_create_handle(u_int32_t flags, u_int32_t protocol) +{ + int status; + struct ipq_handle *h; + + h = (struct ipq_handle *)malloc(sizeof(struct ipq_handle)); + if (h == NULL) { + ipq_errno = IPQ_ERR_HANDLE; + return NULL; + } + + memset(h, 0, sizeof(struct ipq_handle)); + + if (protocol == PF_INET) + h->fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_FIREWALL); + else if (protocol == PF_INET6) + h->fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_IP6_FW); + else { + ipq_errno = IPQ_ERR_PROTOCOL; + free(h); + return NULL; + } + + if (h->fd == -1) { + ipq_errno = IPQ_ERR_SOCKET; + close(h->fd); + free(h); + return NULL; + } + memset(&h->local, 0, sizeof(struct sockaddr_nl)); + h->local.nl_family = AF_NETLINK; + h->local.nl_pid = getpid(); + h->local.nl_groups = 0; + status = bind(h->fd, (struct sockaddr *)&h->local, sizeof(h->local)); + if (status == -1) { + ipq_errno = IPQ_ERR_BIND; + close(h->fd); + free(h); + return NULL; + } + memset(&h->peer, 0, sizeof(struct sockaddr_nl)); + h->peer.nl_family = AF_NETLINK; + h->peer.nl_pid = 0; + h->peer.nl_groups = 0; + return h; +} + +/* + * No error condition is checked here at this stage, but it may happen + * if/when reliable messaging is implemented. + */ +int ipq_destroy_handle(struct ipq_handle *h) +{ + if (h) { + close(h->fd); + free(h); + } + return 0; +} + +int ipq_set_mode(const struct ipq_handle *h, + u_int8_t mode, size_t range) +{ + struct { + struct nlmsghdr nlh; + ipq_peer_msg_t pm; + } req; + + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req)); + req.nlh.nlmsg_flags = NLM_F_REQUEST; + req.nlh.nlmsg_type = IPQM_MODE; + req.nlh.nlmsg_pid = h->local.nl_pid; + req.pm.msg.mode.value = mode; + req.pm.msg.mode.range = range; + return ipq_netlink_sendto(h, (void *)&req, req.nlh.nlmsg_len); +} + +/* + * timeout is in microseconds (1 second is 1000000 (1 million) microseconds) + * + */ +ssize_t ipq_read(const struct ipq_handle *h, + unsigned char *buf, size_t len, int timeout) +{ + return ipq_netlink_recvfrom(h, buf, len, timeout); +} + +int ipq_message_type(const unsigned char *buf) +{ + return ((struct nlmsghdr*)buf)->nlmsg_type; +} + +int ipq_get_msgerr(const unsigned char *buf) +{ + struct nlmsghdr *h = (struct nlmsghdr *)buf; + struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h); + return -err->error; +} + +ipq_packet_msg_t *ipq_get_packet(const unsigned char *buf) +{ + return NLMSG_DATA((struct nlmsghdr *)(buf)); +} + +int ipq_set_verdict(const struct ipq_handle *h, + ipq_id_t id, + unsigned int verdict, + size_t data_len, + unsigned char *buf) +{ + unsigned char nvecs; + size_t tlen; + struct nlmsghdr nlh; + ipq_peer_msg_t pm; + struct iovec iov[3]; + struct msghdr msg; + + memset(&nlh, 0, sizeof(nlh)); + nlh.nlmsg_flags = NLM_F_REQUEST; + nlh.nlmsg_type = IPQM_VERDICT; + nlh.nlmsg_pid = h->local.nl_pid; + memset(&pm, 0, sizeof(pm)); + pm.msg.verdict.value = verdict; + pm.msg.verdict.id = id; + pm.msg.verdict.data_len = data_len; + iov[0].iov_base = &nlh; + iov[0].iov_len = sizeof(nlh); + iov[1].iov_base = ± + iov[1].iov_len = sizeof(pm); + tlen = sizeof(nlh) + sizeof(pm); + nvecs = 2; + if (data_len && buf) { + iov[2].iov_base = buf; + iov[2].iov_len = data_len; + tlen += data_len; + nvecs++; + } + msg.msg_name = (void *)&h->peer; + msg.msg_namelen = sizeof(h->peer); + msg.msg_iov = iov; + msg.msg_iovlen = nvecs; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + nlh.nlmsg_len = tlen; + return ipq_netlink_sendmsg(h, &msg, 0); +} + +/* Not implemented yet */ +int ipq_ctl(const struct ipq_handle *h, int request, ...) +{ + return 1; +} + +char *ipq_errstr(void) +{ + return ipq_strerror(ipq_errno); +} + +void ipq_perror(const char *s) +{ + if (s) + fputs(s, stderr); + else + fputs("ERROR", stderr); + if (ipq_errno) + fprintf(stderr, ": %s", ipq_errstr()); + if (errno) + fprintf(stderr, ": %s", strerror(errno)); + fputc('\n', stderr); +} diff --git a/libiptc/libip6tc.c.orig b/libiptc/libip6tc.c.orig new file mode 100644 index 0000000..9a78a5a --- /dev/null +++ b/libiptc/libip6tc.c.orig @@ -0,0 +1,449 @@ +/* Library which manipulates firewall rules. Version 0.1. */ + +/* Architecture of firewall rules is as follows: + * + * Chains go INPUT, FORWARD, OUTPUT then user chains. + * Each user chain starts with an ERROR node. + * Every chain ends with an unconditional jump: a RETURN for user chains, + * and a POLICY for built-ins. + */ + +/* (C)1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See + COPYING for details). */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG_CONNTRACK +#define inline +#endif + +#if !defined(__GLIBC__) || (__GLIBC__ < 2) +typedef unsigned int socklen_t; +#endif + +#include "libiptc/libip6tc.h" + +#define HOOK_PRE_ROUTING NF_IP6_PRE_ROUTING +#define HOOK_LOCAL_IN NF_IP6_LOCAL_IN +#define HOOK_FORWARD NF_IP6_FORWARD +#define HOOK_LOCAL_OUT NF_IP6_LOCAL_OUT +#define HOOK_POST_ROUTING NF_IP6_POST_ROUTING + +#define STRUCT_ENTRY_TARGET struct ip6t_entry_target +#define STRUCT_ENTRY struct ip6t_entry +#define STRUCT_ENTRY_MATCH struct ip6t_entry_match +#define STRUCT_GETINFO struct ip6t_getinfo +#define STRUCT_GET_ENTRIES struct ip6t_get_entries +#define STRUCT_COUNTERS struct ip6t_counters +#define STRUCT_COUNTERS_INFO struct ip6t_counters_info +#define STRUCT_STANDARD_TARGET struct ip6t_standard_target +#define STRUCT_REPLACE struct ip6t_replace + +#define STRUCT_TC_HANDLE struct ip6tc_handle +#define TC_HANDLE_T ip6tc_handle_t + +#define ENTRY_ITERATE IP6T_ENTRY_ITERATE +#define TABLE_MAXNAMELEN IP6T_TABLE_MAXNAMELEN +#define FUNCTION_MAXNAMELEN IP6T_FUNCTION_MAXNAMELEN + +#define GET_TARGET ip6t_get_target + +#define ERROR_TARGET IP6T_ERROR_TARGET +#define NUMHOOKS NF_IP6_NUMHOOKS + +#define IPT_CHAINLABEL ip6t_chainlabel + +#define TC_DUMP_ENTRIES dump_entries6 +#define TC_IS_CHAIN ip6tc_is_chain +#define TC_FIRST_CHAIN ip6tc_first_chain +#define TC_NEXT_CHAIN ip6tc_next_chain +#define TC_FIRST_RULE ip6tc_first_rule +#define TC_NEXT_RULE ip6tc_next_rule +#define TC_GET_TARGET ip6tc_get_target +#define TC_BUILTIN ip6tc_builtin +#define TC_GET_POLICY ip6tc_get_policy +#define TC_INSERT_ENTRY ip6tc_insert_entry +#define TC_REPLACE_ENTRY ip6tc_replace_entry +#define TC_APPEND_ENTRY ip6tc_append_entry +#define TC_DELETE_ENTRY ip6tc_delete_entry +#define TC_DELETE_NUM_ENTRY ip6tc_delete_num_entry +#define TC_CHECK_PACKET ip6tc_check_packet +#define TC_FLUSH_ENTRIES ip6tc_flush_entries +#define TC_ZERO_ENTRIES ip6tc_zero_entries +#define TC_ZERO_COUNTER ip6tc_zero_counter +#define TC_READ_COUNTER ip6tc_read_counter +#define TC_SET_COUNTER ip6tc_set_counter +#define TC_CREATE_CHAIN ip6tc_create_chain +#define TC_GET_REFERENCES ip6tc_get_references +#define TC_DELETE_CHAIN ip6tc_delete_chain +#define TC_RENAME_CHAIN ip6tc_rename_chain +#define TC_SET_POLICY ip6tc_set_policy +#define TC_GET_RAW_SOCKET ip6tc_get_raw_socket +#define TC_INIT ip6tc_init +#define TC_FREE ip6tc_free +#define TC_COMMIT ip6tc_commit +#define TC_STRERROR ip6tc_strerror + +#define TC_AF AF_INET6 +#define TC_IPPROTO IPPROTO_IPV6 + +#define SO_SET_REPLACE IP6T_SO_SET_REPLACE +#define SO_SET_ADD_COUNTERS IP6T_SO_SET_ADD_COUNTERS +#define SO_GET_INFO IP6T_SO_GET_INFO +#define SO_GET_ENTRIES IP6T_SO_GET_ENTRIES +#define SO_GET_VERSION IP6T_SO_GET_VERSION + +#define STANDARD_TARGET IP6T_STANDARD_TARGET +#define LABEL_RETURN IP6TC_LABEL_RETURN +#define LABEL_ACCEPT IP6TC_LABEL_ACCEPT +#define LABEL_DROP IP6TC_LABEL_DROP +#define LABEL_QUEUE IP6TC_LABEL_QUEUE + +#define ALIGN IP6T_ALIGN +#define RETURN IP6T_RETURN + +#include "libiptc.c" + +#define BIT6(a, l) \ + ((ntohl(a->in6_u.u6_addr32[(l) / 32]) >> (31 - ((l) & 31))) & 1) + +int +ipv6_prefix_length(const struct in6_addr *a) +{ + int l, i; + for (l = 0; l < 128; l++) { + if (BIT6(a, l) == 0) + break; + } + for (i = l + 1; i < 128; i++) { + if (BIT6(a, i) == 1) + return -1; + } + return l; +} + +static int +dump_entry(struct ip6t_entry *e, const ip6tc_handle_t handle) +{ + size_t i; + char buf[40]; + int len; + struct ip6t_entry_target *t; + + printf("Entry %u (%lu):\n", entry2index(handle, e), + entry2offset(handle, e)); + puts("SRC IP: "); + inet_ntop(AF_INET6, &e->ipv6.src, buf, sizeof buf); + puts(buf); + putchar('/'); + len = ipv6_prefix_length(&e->ipv6.smsk); + if (len != -1) + printf("%d", len); + else { + inet_ntop(AF_INET6, &e->ipv6.smsk, buf, sizeof buf); + puts(buf); + } + putchar('\n'); + + puts("DST IP: "); + inet_ntop(AF_INET6, &e->ipv6.dst, buf, sizeof buf); + puts(buf); + putchar('/'); + len = ipv6_prefix_length(&e->ipv6.dmsk); + if (len != -1) + printf("%d", len); + else { + inet_ntop(AF_INET6, &e->ipv6.dmsk, buf, sizeof buf); + puts(buf); + } + putchar('\n'); + + printf("Interface: `%s'/", e->ipv6.iniface); + for (i = 0; i < IFNAMSIZ; i++) + printf("%c", e->ipv6.iniface_mask[i] ? 'X' : '.'); + printf("to `%s'/", e->ipv6.outiface); + for (i = 0; i < IFNAMSIZ; i++) + printf("%c", e->ipv6.outiface_mask[i] ? 'X' : '.'); + printf("\nProtocol: %u\n", e->ipv6.proto); + if (e->ipv6.flags & IP6T_F_TOS) + printf("TOS: %u\n", e->ipv6.tos); + printf("Flags: %02X\n", e->ipv6.flags); + printf("Invflags: %02X\n", e->ipv6.invflags); + printf("Counters: %llu packets, %llu bytes\n", + e->counters.pcnt, e->counters.bcnt); + printf("Cache: %08X ", e->nfcache); + if (e->nfcache & NFC_ALTERED) printf("ALTERED "); + if (e->nfcache & NFC_UNKNOWN) printf("UNKNOWN "); + if (e->nfcache & NFC_IP6_SRC) printf("IP6_SRC "); + if (e->nfcache & NFC_IP6_DST) printf("IP6_DST "); + if (e->nfcache & NFC_IP6_IF_IN) printf("IP6_IF_IN "); + if (e->nfcache & NFC_IP6_IF_OUT) printf("IP6_IF_OUT "); + if (e->nfcache & NFC_IP6_TOS) printf("IP6_TOS "); + if (e->nfcache & NFC_IP6_PROTO) printf("IP6_PROTO "); + if (e->nfcache & NFC_IP6_OPTIONS) printf("IP6_OPTIONS "); + if (e->nfcache & NFC_IP6_TCPFLAGS) printf("IP6_TCPFLAGS "); + if (e->nfcache & NFC_IP6_SRC_PT) printf("IP6_SRC_PT "); + if (e->nfcache & NFC_IP6_DST_PT) printf("IP6_DST_PT "); + if (e->nfcache & NFC_IP6_PROTO_UNKNOWN) printf("IP6_PROTO_UNKNOWN "); + printf("\n"); + + IP6T_MATCH_ITERATE(e, print_match); + + t = ip6t_get_target(e); + printf("Target name: `%s' [%u]\n", t->u.user.name, t->u.target_size); + if (strcmp(t->u.user.name, IP6T_STANDARD_TARGET) == 0) { + int pos = *(int *)t->data; + if (pos < 0) + printf("verdict=%s\n", + pos == -NF_ACCEPT-1 ? "NF_ACCEPT" + : pos == -NF_DROP-1 ? "NF_DROP" + : pos == IP6T_RETURN ? "RETURN" + : "UNKNOWN"); + else + printf("verdict=%u\n", pos); + } else if (strcmp(t->u.user.name, IP6T_ERROR_TARGET) == 0) + printf("error=`%s'\n", t->data); + + printf("\n"); + return 0; +} + +static int +is_same(const STRUCT_ENTRY *a, const STRUCT_ENTRY *b, + unsigned char *matchmask) +{ + unsigned int i; + STRUCT_ENTRY_TARGET *ta, *tb; + unsigned char *mptr; + + /* Always compare head structures: ignore mask here. */ + if (memcmp(&a->ipv6.src, &b->ipv6.src, sizeof(struct in6_addr)) + || memcmp(&a->ipv6.dst, &b->ipv6.dst, sizeof(struct in6_addr)) + || memcmp(&a->ipv6.smsk, &b->ipv6.smsk, sizeof(struct in6_addr)) + || memcmp(&a->ipv6.dmsk, &b->ipv6.dmsk, sizeof(struct in6_addr)) + || a->ipv6.proto != b->ipv6.proto + || a->ipv6.tos != b->ipv6.tos + || a->ipv6.flags != b->ipv6.flags + || a->ipv6.invflags != b->ipv6.invflags) + return 0; + + for (i = 0; i < IFNAMSIZ; i++) { + if (a->ipv6.iniface_mask[i] != b->ipv6.iniface_mask[i]) + return 0; + if ((a->ipv6.iniface[i] & a->ipv6.iniface_mask[i]) + != (b->ipv6.iniface[i] & b->ipv6.iniface_mask[i])) + return 0; + if (a->ipv6.outiface_mask[i] != b->ipv6.outiface_mask[i]) + return 0; + if ((a->ipv6.outiface[i] & a->ipv6.outiface_mask[i]) + != (b->ipv6.outiface[i] & b->ipv6.outiface_mask[i])) + return 0; + } + + if (a->nfcache != b->nfcache + || a->target_offset != b->target_offset + || a->next_offset != b->next_offset) + return 0; + + mptr = matchmask + sizeof(STRUCT_ENTRY); + if (IP6T_MATCH_ITERATE(a, match_different, a->elems, b->elems, &mptr)) + return 0; + + ta = GET_TARGET((STRUCT_ENTRY *)a); + tb = GET_TARGET((STRUCT_ENTRY *)b); + if (ta->u.target_size != tb->u.target_size) + return 0; + if (strcmp(ta->u.user.name, tb->u.user.name) != 0) + return 0; + mptr += sizeof(*ta); + + if (target_different(ta->data, tb->data, + ta->u.target_size - sizeof(*ta), mptr)) + return 0; + + return 1; +} + +/* All zeroes == unconditional rule. */ +static inline int +unconditional(const struct ip6t_ip6 *ipv6) +{ + unsigned int i; + + for (i = 0; i < sizeof(*ipv6); i++) + if (((char *)ipv6)[i]) + break; + + return (i == sizeof(*ipv6)); +} + +#ifdef IPTC_DEBUG +/* Do every conceivable sanity check on the handle */ +static void +do_check(TC_HANDLE_T h, unsigned int line) +{ + unsigned int i, n; + unsigned int user_offset; /* Offset of first user chain */ + int was_return; + + assert(h->changed == 0 || h->changed == 1); + if (strcmp(h->info.name, "filter") == 0) { + assert(h->info.valid_hooks + == (1 << NF_IP6_LOCAL_IN + | 1 << NF_IP6_FORWARD + | 1 << NF_IP6_LOCAL_OUT)); + + /* Hooks should be first three */ + assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == 0); + + n = get_chain_end(h, 0); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_FORWARD] == n); + + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n); + + user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT]; + } else if (strcmp(h->info.name, "nat") == 0) { + assert((h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_OUT + | 1 << NF_IP6_POST_ROUTING)) || + (h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_IN + | 1 << NF_IP6_LOCAL_OUT + | 1 << NF_IP6_POST_ROUTING))); + + assert(h->info.hook_entry[NF_IP6_PRE_ROUTING] == 0); + + n = get_chain_end(h, 0); + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_POST_ROUTING] == n); + n = get_chain_end(h, n); + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n); + user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT]; + + if (h->info.valid_hooks & (1 << NF_IP6_LOCAL_IN)) { + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == n); + user_offset = h->info.hook_entry[NF_IP6_LOCAL_IN]; + } + + } else if (strcmp(h->info.name, "mangle") == 0) { + /* This code is getting ugly because linux < 2.4.18-pre6 had + * two mangle hooks, linux >= 2.4.18-pre6 has five mangle hooks + * */ + assert((h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_OUT)) || + (h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_IN + | 1 << NF_IP6_FORWARD + | 1 << NF_IP6_LOCAL_OUT + | 1 << NF_IP6_POST_ROUTING))); + + /* Hooks should be first five */ + assert(h->info.hook_entry[NF_IP6_PRE_ROUTING] == 0); + + n = get_chain_end(h, 0); + + if (h->info.valid_hooks & (1 << NF_IP6_LOCAL_IN)) { + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == n); + n = get_chain_end(h, n); + } + + if (h->info.valid_hooks & (1 << NF_IP6_FORWARD)) { + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_FORWARD] == n); + n = get_chain_end(h, n); + } + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n); + user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT]; + + if (h->info.valid_hooks & (1 << NF_IP6_POST_ROUTING)) { + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_POST_ROUTING] == n); + user_offset = h->info.hook_entry[NF_IP6_POST_ROUTING]; + } + } else { + fprintf(stderr, "Unknown table `%s'\n", h->info.name); + abort(); + } + + /* User chain == end of last builtin + policy entry */ + user_offset = get_chain_end(h, user_offset); + user_offset += get_entry(h, user_offset)->next_offset; + + /* Overflows should be end of entry chains, and unconditional + policy nodes. */ + for (i = 0; i < NUMHOOKS; i++) { + STRUCT_ENTRY *e; + STRUCT_STANDARD_TARGET *t; + + if (!(h->info.valid_hooks & (1 << i))) + continue; + assert(h->info.underflow[i] + == get_chain_end(h, h->info.hook_entry[i])); + + e = get_entry(h, get_chain_end(h, h->info.hook_entry[i])); + assert(unconditional(&e->ipv6)); + assert(e->target_offset == sizeof(*e)); + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + printf("target_size=%u, align=%u\n", + t->target.u.target_size, ALIGN(sizeof(*t))); + assert(t->target.u.target_size == ALIGN(sizeof(*t))); + assert(e->next_offset == sizeof(*e) + ALIGN(sizeof(*t))); + + assert(strcmp(t->target.u.user.name, STANDARD_TARGET)==0); + assert(t->verdict == -NF_DROP-1 || t->verdict == -NF_ACCEPT-1); + + /* Hooks and underflows must be valid entries */ + entry2index(h, get_entry(h, h->info.hook_entry[i])); + entry2index(h, get_entry(h, h->info.underflow[i])); + } + + assert(h->info.size + >= h->info.num_entries * (sizeof(STRUCT_ENTRY) + +sizeof(STRUCT_STANDARD_TARGET))); + + assert(h->entries.size + >= (h->new_number + * (sizeof(STRUCT_ENTRY) + + sizeof(STRUCT_STANDARD_TARGET)))); + assert(strcmp(h->info.name, h->entries.name) == 0); + + i = 0; n = 0; + was_return = 0; + +#if 0 + /* Check all the entries. */ + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + check_entry, &i, &n, user_offset, &was_return, h); + + assert(i == h->new_number); + assert(n == h->entries.size); + + /* Final entry must be error node */ + assert(strcmp(GET_TARGET(index2entry(h, h->new_number-1)) + ->u.user.name, + ERROR_TARGET) == 0); +#endif +} +#endif /*IPTC_DEBUG*/ diff --git a/libiptc2/.#libiptc.c.1.40 b/libiptc2/.#libiptc.c.1.40 new file mode 100644 index 0000000..ce4af9b --- /dev/null +++ b/libiptc2/.#libiptc.c.1.40 @@ -0,0 +1,1972 @@ +/* Library which manipulates firewall rules. Version $Revision: 1.40 $ */ + +/* Architecture of firewall rules is as follows: + * + * Chains go INPUT, FORWARD, OUTPUT then user chains. + * Each user chain starts with an ERROR node. + * Every chain ends with an unconditional jump: a RETURN for user chains, + * and a POLICY for built-ins. + */ + +/* (C) 1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See + * COPYING for details). + * (C) 2000-2003 by the Netfilter Core Team + * + * 2003-Jun-20: Harald Welte : + * - Reimplementation of chain cache to use offsets instead of entries + * 2003-Jun-23: Harald Welte : + * - speed optimization, sponsored by Astaro AG (http://www.astaro.com/) + * don't rebuild the chain cache after every operation, instead fix it + * up after a ruleset change. + * 2003-Jun-30: Harald Welte : + * - total reimplementation + */ +#include "linux_listhelp.h" + +#ifndef IPT_LIB_DIR +#define IPT_LIB_DIR "/usr/lib/iptables" +#endif + +static int sockfd = -1; +static void *iptc_fn = NULL; + +static const char *hooknames[] += { [HOOK_PRE_ROUTING] "PREROUTING", + [HOOK_LOCAL_IN] "INPUT", + [HOOK_FORWARD] "FORWARD", + [HOOK_LOCAL_OUT] "OUTPUT", + [HOOK_POST_ROUTING] "POSTROUTING", +#ifdef HOOK_DROPPING + [HOOK_DROPPING] "DROPPING" +#endif +}; + +struct counter_map +{ + enum { + COUNTER_MAP_NOMAP, + COUNTER_MAP_NORMAL_MAP, + COUNTER_MAP_ZEROED, + COUNTER_MAP_SET + } maptype; + unsigned int mappos; +}; + +/* Convenience structures */ +struct ipt_error_target +{ + STRUCT_ENTRY_TARGET t; + char error[TABLE_MAXNAMELEN]; +}; + +struct rule_head +{ + struct list_head list; /* list of rules in chain */ + + struct chain_head *chain; /* we're part of this chain */ + struct rule_head *target; /* target of this rule, in case + it is a jump rule */ + + + unsigned int size; /* size of rule */ + STRUCT_ENTRY *entry_blob; /* pointer to entry in blob */ + STRUCT_ENTRY entry[0]; +}; + +struct chain_head +{ + struct list_head list; + + char name[TABLE_MAXNAMELEN]; + unsigned int hooknum; + struct list_head rules; +}; + +STRUCT_TC_HANDLE +{ + /* Have changes been made? */ + int changed; + + /* linked list of chains in this table */ + struct list_head chains; + + /* current position of first_chain() / next_chain() */ + struct chain_head *chain_iterator_cur; + + /* current position of first_rule() / next_rule() */ + struct rule_head *rule_iterator_cur; + + struct counter_map *counter_map; + + /* the structure we receive from getsockopt() */ + STRUCT_GETINFO info; + + /* Array of hook names */ + const char **hooknames; +#if 0 + /* Size in here reflects original state. */ + + + /* Cached position of chain heads (NULL = no cache). */ + unsigned int cache_num_chains; + unsigned int cache_num_builtins; + struct chain_cache *cache_chain_heads; + + /* Chain iterator: current chain cache entry. */ + struct chain_cache *cache_chain_iteration; + + /* Rule iterator: terminal rule */ + STRUCT_ENTRY *cache_rule_end; + + /* Number in here reflects current state. */ + unsigned int new_number; +#endif + STRUCT_GET_ENTRIES entries; +}; + +static void +set_changed(TC_HANDLE_T h) +{ + h->changed = 1; +} + +#ifdef IPTC_DEBUG +static void do_check(TC_HANDLE_T h, unsigned int line); +#define CHECK(h) do { if (!getenv("IPTC_NO_CHECK")) do_check((h), __LINE__); } while(0) +#else +#define CHECK(h) +#endif + +static struct rule_head *ruleh_alloc(unsigned int size) +{ + struct rule_head *ruleh = malloc(sizeof(*ruleh)+size); + if (!ruleh) + return NULL; + + memset(ruleh, 0, sizeof(*ruleh)+size); + ruleh->size = size; + + return ruleh; +} + +static void ruleh_free(struct rule_head *ruleh) +{ + list_del(&ruleh->list); + free(ruleh); +} + +static struct rule_head *ruleh_get_n(struct chain_head *chain, int num) +{ + return NULL; +} + +static struct chain_head *chainh_alloc(TC_HANDLE_T h, const char *name) +{ + struct chain_head *chainh = malloc(sizeof(*chainh)); + if (!chainh) + return NULL; + + memset(chainh, 0, sizeof(*chainh)); + strncpy(chainh->name, name, sizeof(&chainh->name)); + list_append(&chainh->list, &h->chains); + + return chainh; +} + +static void +chainh_free(struct chain_head *chainh) +{ + /* FIXME */ + struct list_head *cur_item, *item2; + + list_for_each_safe(cur_item, item2, &chainh->rules) { + struct rule_head *ruleh = list_entry(cur_item, + struct rule_head, + list); + ruleh_free(ruleh); + } + + list_del(&chainh->list); +} + +#if 0 +static inline int +get_number(const STRUCT_ENTRY *i, + const STRUCT_ENTRY *seek, + unsigned int *pos) +{ + if (i == seek) + return 1; + (*pos)++; + return 0; +} +#endif + +static struct chain_head * +chainh_find(TC_HANDLE_T h, const IPT_CHAINLABEL name) +{ + struct list_head *cur; + + list_for_each(cur, &h->chains) { + struct chain_head *ch = list_entry(cur, struct chain_head, + list); + if (!strcmp(name, ch->name)) + return ch; + } + return NULL; +} + +static inline unsigned long +entry2offset(const TC_HANDLE_T h, const STRUCT_ENTRY *e) +{ + return (void *)e - (void *)h->entries.entrytable; +} + +#if 0 +static unsigned int +entry2index(const TC_HANDLE_T h, const STRUCT_ENTRY *seek) +{ + unsigned int pos = 0; + + if (ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + get_number, seek, &pos) == 0) { + fprintf(stderr, "ERROR: offset %i not an entry!\n", + (char *)seek - (char *)h->entries.entrytable); + abort(); + } + return pos; +} + +static inline int +get_entry_n(STRUCT_ENTRY *i, + unsigned int number, + unsigned int *pos, + STRUCT_ENTRY **pe) +{ + if (*pos == number) { + *pe = i; + return 1; + } + (*pos)++; + return 0; +} + +static STRUCT_ENTRY * +index2entry(TC_HANDLE_T h, unsigned int index) +{ + unsigned int pos = 0; + STRUCT_ENTRY *ret = NULL; + + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + get_entry_n, index, &pos, &ret); + + return ret; +} + +static inline STRUCT_ENTRY * +get_entry(TC_HANDLE_T h, unsigned int offset) +{ + return (STRUCT_ENTRY *)((char *)h->entries.entrytable + offset); +} + + +static inline unsigned long +index2offset(TC_HANDLE_T h, unsigned int index) +{ + return entry2offset(h, index2entry(h, index)); +} + +static inline STRUCT_ENTRY * +offset2entry(TC_HANDLE_T h, unsigned int offset) +{ + return (STRUCT_ENTRY *) ((void *)h->entries.entrytable+offset); +} + +static inline unsigned int +offset2index(const TC_HANDLE_T h, unsigned int offset) +{ + return entry2index(h, offset2entry(h, offset)); +} + + +static const char * +get_errorlabel(TC_HANDLE_T h, unsigned int offset) +{ + STRUCT_ENTRY *e; + + e = get_entry(h, offset); + if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) != 0) { + fprintf(stderr, "ERROR: offset %u not an error node!\n", + offset); + abort(); + } + + return (const char *)GET_TARGET(e)->data; +} +#endif + +/* Allocate handle of given size */ +static TC_HANDLE_T +alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules) +{ + size_t len; + TC_HANDLE_T h; + + len = sizeof(STRUCT_TC_HANDLE) + + size + + num_rules * sizeof(struct counter_map); + + if ((h = malloc(len)) == NULL) { + errno = ENOMEM; + return NULL; + } + + h->changed = 0; + + h->counter_map = (void *)h + + sizeof(STRUCT_TC_HANDLE) + + size; + strcpy(h->info.name, tablename); + strcpy(h->entries.name, tablename); + INIT_LIST_HEAD(&h->chains); + + return h; +} + +struct rule_head * +append_entrycopy(STRUCT_ENTRY *e, struct chain_head *chain) +{ + struct rule_head *ruleh = ruleh_alloc(e->next_offset); + if (!ruleh) + return NULL; + + memcpy(&ruleh->entry, e, e->next_offset); + ruleh->chain = chain; + ruleh->entry_blob = e; + list_append(&ruleh->list, &chain->rules); + + return ruleh; +} + + +/* have to return 0 on success, bcf ENTRY_ITERATE */ +static inline int +parse_entry(STRUCT_ENTRY *e, TC_HANDLE_T h, struct chain_head **curchain) +{ + int i; + union tgt_u { + STRUCT_ENTRY_TARGET ent; + STRUCT_STANDARD_TARGET std; + struct ipt_error_target err; + } *tgt; + + tgt = (union tgt_u *) GET_TARGET(e); + + if (e->target_offset == sizeof(STRUCT_ENTRY) + && (strcmp(tgt->ent.u.user.name, IPT_STANDARD_TARGET) == 0)) { + /* jump to somewhere else */ + append_entrycopy(e, *curchain); + /* FIXME: */ + } else if (e->target_offset == sizeof(STRUCT_ENTRY) + && e->next_offset == sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(struct ipt_error_target)) + && !strcmp(tgt->ent.u.user.name, ERROR_TARGET)) { + /* chain head */ + *curchain = chainh_find(h, tgt->err.error); + if (*curchain) { + /* FIXME: error handling */ + return 1; + } + + *curchain = chainh_alloc(h, tgt->err.error); + /* FIXME: error handling */ + append_entrycopy(e, *curchain); + } else if (e->target_offset == sizeof(STRUCT_ENTRY) + && e->next_offset == sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(STRUCT_STANDARD_TARGET)) + && tgt->std.verdict == RETURN) { + /* chain end */ + append_entrycopy(e, *curchain); + *curchain = NULL; + } else { + /* normal rule */ + append_entrycopy(e, *curchain); + } + + /* iterate over hook_entries, needed to connect builtin + * chains with hook numbers */ + for (i = 0; i < NUMHOOKS; i++) { + if (!(h->info.valid_hooks & (1 << i))) + continue; + if (h->info.hook_entry[i] == entry2offset(h, e)) { + /* found hook entry point */ + if (*curchain) + (*curchain)->hooknum = i; + } + if (h->info.underflow[i] == entry2offset(h, e)) { + /* found underflow point */ + } + } + + return 0; +} + +static int parse_ruleset(TC_HANDLE_T h) +{ + struct chain_head *curchain; + + /* iterate over ruleset; create linked list of rule_head/chain_head */ + if (ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + parse_entry, h, &curchain)) { + /* some error happened while iterating */ + return 0; + } + + return 1; +} + +TC_HANDLE_T +TC_INIT(const char *tablename) +{ + TC_HANDLE_T h; + STRUCT_GETINFO info; + unsigned int i; + int tmp; + socklen_t s; + + iptc_fn = TC_INIT; + + if (sockfd != -1) { + close(sockfd); + sockfd = -1; + } + + if (strlen(tablename) >= TABLE_MAXNAMELEN) { + errno = EINVAL; + return NULL; + } + + sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW); + if (sockfd < 0) + return NULL; + + s = sizeof(info); + + strcpy(info.name, tablename); + if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0) + return NULL; + + if ((h = alloc_handle(info.name, info.size, info.num_entries)) + == NULL) { + close(sockfd); + sockfd = -1; + return NULL; + } + +/* Too hard --RR */ +#if 0 + sprintf(pathname, "%s/%s", IPT_LIB_DIR, info.name); + dynlib = dlopen(pathname, RTLD_NOW); + if (!dynlib) { + errno = ENOENT; + return NULL; + } + h->hooknames = dlsym(dynlib, "hooknames"); + if (!h->hooknames) { + errno = ENOENT; + return NULL; + } +#else + h->hooknames = hooknames; +#endif + + /* Initialize current state */ + h->info = info; + //h->new_number = h->info.num_entries; + for (i = 0; i < h->info.num_entries; i++) + h->counter_map[i] + = ((struct counter_map){COUNTER_MAP_NORMAL_MAP, i}); + + h->entries.size = h->info.size; + + tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size; + + if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries, + &tmp) < 0) { + close(sockfd); + sockfd = -1; + free(h); + return NULL; + } + + CHECK(h); + parse_ruleset(h); + + return h; +} + +void +TC_FREE(TC_HANDLE_T *h) +{ + struct list_head *cur_item, *item2; + + close(sockfd); + sockfd = -1; + + /* free all chains */ + list_for_each_safe(cur_item, item2, (*h)->chains.next) { + struct chain_head *chead = list_entry(cur_item, + struct chain_head, + list); + chainh_free(chead); + } + + /* FIXME: free all other ressources we might be using */ + + free(*h); + *h = NULL; +} + +static inline int +print_match(const STRUCT_ENTRY_MATCH *m) +{ + printf("Match name: `%s'\n", m->u.user.name); + return 0; +} + +static int dump_entry(STRUCT_ENTRY *e, const TC_HANDLE_T handle); + +#if 0 +void +TC_DUMP_ENTRIES(const TC_HANDLE_T handle) +{ + CHECK(handle); + + printf("libiptc v%s. %u entries, %u bytes.\n", + IPTABLES_VERSION, + handle->new_number, handle->entries.size); + printf("Table `%s'\n", handle->info.name); + printf("Hooks: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.hook_entry[HOOK_PRE_ROUTING], + handle->info.hook_entry[HOOK_LOCAL_IN], + handle->info.hook_entry[HOOK_FORWARD], + handle->info.hook_entry[HOOK_LOCAL_OUT], + handle->info.hook_entry[HOOK_POST_ROUTING]); + printf("Underflows: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.underflow[HOOK_PRE_ROUTING], + handle->info.underflow[HOOK_LOCAL_IN], + handle->info.underflow[HOOK_FORWARD], + handle->info.underflow[HOOK_LOCAL_OUT], + handle->info.underflow[HOOK_POST_ROUTING]); + + ENTRY_ITERATE(handle->entries.entrytable, handle->entries.size, + dump_entry, handle); +} + +/* Returns 0 if not hook entry, else hooknumber + 1 */ +static inline unsigned int +is_hook_entry(STRUCT_ENTRY *e, TC_HANDLE_T h) +{ + unsigned int i; + + for (i = 0; i < NUMHOOKS; i++) { + if ((h->info.valid_hooks & (1 << i)) + && get_entry(h, h->info.hook_entry[i]) == e) + return i+1; + } + return 0; +} + + +static int alphasort(const void *a, const void *b) +{ + return strcmp(((struct chain_cache *)a)->name, + ((struct chain_cache *)b)->name); +} +#endif + +/* Returns chain head if found, otherwise NULL. */ +static struct chain_head * +find_label(const char *name, TC_HANDLE_T handle) +{ + struct list_head *pos; + + if (list_empty(&handle->chains)) + return NULL; + + list_for_each(pos, &handle->chains) { + struct chain_head *c = list_entry(pos, struct chain_head, list); + if (!strcmp(c->name, name)) + return c; + } + + return NULL; +} + +/* Does this chain exist? */ +int TC_IS_CHAIN(const char *chain, const TC_HANDLE_T handle) +{ + return find_label(chain, handle) != NULL; +} + +#if 0 +/* Returns the position of the final (ie. unconditional) element. */ +static unsigned int +get_chain_end(const TC_HANDLE_T handle, unsigned int start) +{ + unsigned int last_off, off; + STRUCT_ENTRY *e; + + last_off = start; + e = get_entry(handle, start); + + /* Terminate when we meet a error label or a hook entry. */ + for (off = start + e->next_offset; + off < handle->entries.size; + last_off = off, off += e->next_offset) { + STRUCT_ENTRY_TARGET *t; + unsigned int i; + + e = get_entry(handle, off); + + /* We hit an entry point. */ + for (i = 0; i < NUMHOOKS; i++) { + if ((handle->info.valid_hooks & (1 << i)) + && off == handle->info.hook_entry[i]) + return last_off; + } + + /* We hit a user chain label */ + t = GET_TARGET(e); + if (strcmp(t->u.user.name, ERROR_TARGET) == 0) + return last_off; + } + /* SHOULD NEVER HAPPEN */ + fprintf(stderr, "ERROR: Off end (%u) of chain from %u!\n", + handle->entries.size, off); + abort(); +} +#endif + +/* Iterator functions to run through the chains. */ +const char * +TC_FIRST_CHAIN(TC_HANDLE_T *handle) +{ + struct chain_head *firsthead = list_entry((*handle)->chains.next, + struct chain_head, list); + (*handle)->chain_iterator_cur = firsthead; + + return firsthead->name; +} + +/* Iterator functions to run through the chains. Returns NULL at end. */ +const char * +TC_NEXT_CHAIN(TC_HANDLE_T *handle) +{ + struct chain_head *next = list_entry(&(*handle)->chain_iterator_cur->list.next, struct chain_head, list); + (*handle)->chain_iterator_cur = next; + + if (&next->list == &(*handle)->chains) + return NULL; + + return next->name; +} + +/* Get first rule in the given chain: NULL for empty chain. */ +const STRUCT_ENTRY * +TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct rule_head *r; + + c = find_label(chain, *handle); + if (!c) { + errno = ENOENT; + return NULL; + } + + /* Empty chain: single return/policy rule */ + if (list_empty(&c->rules)) + return NULL; + + r = list_entry(c->rules.next, struct rule_head, list); + (*handle)->rule_iterator_cur = r; + + return r->entry; +} + +/* Returns NULL when rules run out. */ +const STRUCT_ENTRY * +TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle) +{ + struct rule_head *r = list_entry((*handle)->rule_iterator_cur->list.next, struct rule_head, list); + + if (&r->list == &r->chain->rules) + return NULL; + + /* NOTE: prev is without any influence ! */ + return r->entry; +} + +#if 0 +/* How many rules in this chain? */ +unsigned int +TC_NUM_RULES(const char *chain, TC_HANDLE_T *handle) +{ + unsigned int off = 0; + STRUCT_ENTRY *start, *end; + + CHECK(*handle); + if (!find_label(&off, chain, *handle)) { + errno = ENOENT; + return (unsigned int)-1; + } + + start = get_entry(*handle, off); + end = get_entry(*handle, get_chain_end(*handle, off)); + + return entry2index(*handle, end) - entry2index(*handle, start); +} + +/* Get n'th rule in this chain. */ +const STRUCT_ENTRY *TC_GET_RULE(const char *chain, + unsigned int n, + TC_HANDLE_T *handle) +{ + unsigned int pos = 0, chainindex; + + CHECK(*handle); + if (!find_label(&pos, chain, *handle)) { + errno = ENOENT; + return NULL; + } + + chainindex = entry2index(*handle, get_entry(*handle, pos)); + + return index2entry(*handle, chainindex + n); +} +#endif + +static const char * +target_name(TC_HANDLE_T handle, const STRUCT_ENTRY *ce) +{ + int spos; + unsigned int labelidx; + STRUCT_ENTRY *jumpto; + + /* To avoid const warnings */ + STRUCT_ENTRY *e = (STRUCT_ENTRY *)ce; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) != 0) + return GET_TARGET(e)->u.user.name; + + /* Standard target: evaluate */ + spos = *(int *)GET_TARGET(e)->data; + if (spos < 0) { + if (spos == RETURN) + return LABEL_RETURN; + else if (spos == -NF_ACCEPT-1) + return LABEL_ACCEPT; + else if (spos == -NF_DROP-1) + return LABEL_DROP; + else if (spos == -NF_QUEUE-1) + return LABEL_QUEUE; + + fprintf(stderr, "ERROR: off %lu/%u not a valid target (%d)\n", + entry2offset(handle, e), handle->entries.size, + spos); + abort(); + } + + jumpto = get_entry(handle, spos); + + /* Fall through rule */ + if (jumpto == (void *)e + e->next_offset) + return ""; + + /* Must point to head of a chain: ie. after error rule */ + /* FIXME: this needs to deal with internal jump targets */ + labelidx = entry2index(handle, jumpto) - 1; + return get_errorlabel(handle, index2offset(handle, labelidx)); +} + +/* Returns a pointer to the target name of this position. */ +const char *TC_GET_TARGET(const STRUCT_ENTRY *e, + TC_HANDLE_T *handle) +{ + return target_name(*handle, e); +} + +/* Is this a built-in chain? Actually returns hook + 1. */ +int +TC_BUILTIN(const char *chain, const TC_HANDLE_T handle) +{ + unsigned int i; + + for (i = 0; i < NUMHOOKS; i++) { + if ((handle->info.valid_hooks & (1 << i)) + && handle->hooknames[i] + && strcmp(handle->hooknames[i], chain) == 0) + return i+1; + } + return 0; +} + +/* Get the policy of a given built-in chain */ +const char * +TC_GET_POLICY(const char *chain, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + unsigned int start; + STRUCT_ENTRY *e; + int hook; + + hook = TC_BUILTIN(chain, *handle); + if (hook != 0) + start = (*handle)->info.hook_entry[hook-1]; + else + return NULL; + + e = get_entry(*handle, get_chain_end(*handle, start)); + *counters = e->counters; + + return target_name(*handle, e); +} + +static int +correct_verdict(STRUCT_ENTRY *e, + char *base, + unsigned int offset, int delta_offset) +{ + STRUCT_STANDARD_TARGET *t = (void *)GET_TARGET(e); + unsigned int curr = (char *)e - base; + + /* Trap: insert of fall-through rule. Don't change fall-through + verdict to jump-over-next-rule. */ + if (strcmp(t->target.u.user.name, STANDARD_TARGET) == 0 + && t->verdict > (int)offset + && !(curr == offset && + t->verdict == curr + e->next_offset)) { + t->verdict += delta_offset; + } + + return 0; +} + +/* Adjusts standard verdict jump positions after an insertion/deletion. */ +static int +set_verdict(unsigned int offset, int delta_offset, TC_HANDLE_T *handle) +{ + ENTRY_ITERATE((*handle)->entries.entrytable, + (*handle)->entries.size, + correct_verdict, (char *)(*handle)->entries.entrytable, + offset, delta_offset); + + set_changed(*handle); + return 1; +} + +/* If prepend is set, then we are prepending to a chain: if the + * insertion position is an entry point, keep the entry point. */ +static int +insert_rules(unsigned int num_rules, unsigned int rules_size, + const STRUCT_ENTRY *insert, + unsigned int offset, unsigned int num_rules_offset, + int prepend, + TC_HANDLE_T *handle) +{ + TC_HANDLE_T newh; + STRUCT_GETINFO newinfo; + unsigned int i; + + if (offset >= (*handle)->entries.size) { + errno = EINVAL; + return 0; + } + + newinfo = (*handle)->info; + + /* Fix up entry points. */ + for (i = 0; i < NUMHOOKS; i++) { + /* Entry points to START of chain, so keep same if + inserting on at that point. */ + if ((*handle)->info.hook_entry[i] > offset) + newinfo.hook_entry[i] += rules_size; + + /* Underflow always points to END of chain (policy), + so if something is inserted at same point, it + should be advanced. */ + if ((*handle)->info.underflow[i] >= offset) + newinfo.underflow[i] += rules_size; + } + + newh = alloc_handle((*handle)->info.name, + (*handle)->entries.size + rules_size, + (*handle)->new_number + num_rules); + if (!newh) + return 0; + newh->info = newinfo; + + /* Copy pre... */ + memcpy(newh->entries.entrytable, (*handle)->entries.entrytable,offset); + /* ... Insert new ... */ + memcpy((char *)newh->entries.entrytable + offset, insert, rules_size); + /* ... copy post */ + memcpy((char *)newh->entries.entrytable + offset + rules_size, + (char *)(*handle)->entries.entrytable + offset, + (*handle)->entries.size - offset); + + /* Move counter map. */ + /* Copy pre... */ + memcpy(newh->counter_map, (*handle)->counter_map, + sizeof(struct counter_map) * num_rules_offset); + /* ... copy post */ + memcpy(newh->counter_map + num_rules_offset + num_rules, + (*handle)->counter_map + num_rules_offset, + sizeof(struct counter_map) * ((*handle)->new_number + - num_rules_offset)); + /* Set intermediates to no counter copy */ + for (i = 0; i < num_rules; i++) + newh->counter_map[num_rules_offset+i] + = ((struct counter_map){ COUNTER_MAP_SET, 0 }); + + newh->new_number = (*handle)->new_number + num_rules; + newh->entries.size = (*handle)->entries.size + rules_size; + newh->hooknames = (*handle)->hooknames; + + if ((*handle)->cache_chain_heads) + free((*handle)->cache_chain_heads); + free(*handle); + *handle = newh; + + return set_verdict(offset, rules_size, handle); +} + +static int +delete_rules(unsigned int num_rules, unsigned int rules_size, + unsigned int offset, unsigned int num_rules_offset, + TC_HANDLE_T *handle) +{ + unsigned int i; + + if (offset + rules_size > (*handle)->entries.size) { + errno = EINVAL; + return 0; + } + + /* Fix up entry points. */ + for (i = 0; i < NUMHOOKS; i++) { + /* In practice, we never delete up to a hook entry, + since the built-in chains are always first, + so these two are never equal */ + if ((*handle)->info.hook_entry[i] >= offset + rules_size) + (*handle)->info.hook_entry[i] -= rules_size; + else if ((*handle)->info.hook_entry[i] > offset) { + fprintf(stderr, "ERROR: Deleting entry %u %u %u\n", + i, (*handle)->info.hook_entry[i], offset); + abort(); + } + + /* Underflow points to policy (terminal) rule in + built-in, so sequality is valid here (when deleting + the last rule). */ + if ((*handle)->info.underflow[i] >= offset + rules_size) + (*handle)->info.underflow[i] -= rules_size; + else if ((*handle)->info.underflow[i] > offset) { + fprintf(stderr, "ERROR: Deleting uflow %u %u %u\n", + i, (*handle)->info.underflow[i], offset); + abort(); + } + } + + /* Move the rules down. */ + memmove((char *)(*handle)->entries.entrytable + offset, + (char *)(*handle)->entries.entrytable + offset + rules_size, + (*handle)->entries.size - (offset + rules_size)); + + /* Move the counter map down. */ + memmove(&(*handle)->counter_map[num_rules_offset], + &(*handle)->counter_map[num_rules_offset + num_rules], + sizeof(struct counter_map) + * ((*handle)->new_number - (num_rules + num_rules_offset))); + + /* Fix numbers */ + (*handle)->new_number -= num_rules; + (*handle)->entries.size -= rules_size; + + return set_verdict(offset, -(int)rules_size, handle); +} + +static int +standard_map(STRUCT_ENTRY *e, int verdict) +{ + STRUCT_STANDARD_TARGET *t; + + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (t->target.u.target_size + != ALIGN(sizeof(STRUCT_STANDARD_TARGET))) { + errno = EINVAL; + return 0; + } + /* memset for memcmp convenience on delete/replace */ + memset(t->target.u.user.name, 0, FUNCTION_MAXNAMELEN); + strcpy(t->target.u.user.name, STANDARD_TARGET); + t->verdict = verdict; + + return 1; +} + +static int +map_target(const TC_HANDLE_T handle, + STRUCT_ENTRY *e, + unsigned int offset, + STRUCT_ENTRY_TARGET *old) +{ + STRUCT_ENTRY_TARGET *t = GET_TARGET(e); + + /* Save old target (except data, which we don't change, except for + standard case, where we don't care). */ + *old = *t; + + /* Maybe it's empty (=> fall through) */ + if (strcmp(t->u.user.name, "") == 0) + return standard_map(e, offset + e->next_offset); + /* Maybe it's a standard target name... */ + else if (strcmp(t->u.user.name, LABEL_ACCEPT) == 0) + return standard_map(e, -NF_ACCEPT - 1); + else if (strcmp(t->u.user.name, LABEL_DROP) == 0) + return standard_map(e, -NF_DROP - 1); + else if (strcmp(t->u.user.name, LABEL_QUEUE) == 0) + return standard_map(e, -NF_QUEUE - 1); + else if (strcmp(t->u.user.name, LABEL_RETURN) == 0) + return standard_map(e, RETURN); + else if (TC_BUILTIN(t->u.user.name, handle)) { + /* Can't jump to builtins. */ + errno = EINVAL; + return 0; + } else { + /* Maybe it's an existing chain name. */ + struct chain_cache *c; + + c = find_label(t->u.user.name, handle); + if (c) + return standard_map(e, c->start_off); + } + + /* Must be a module? If not, kernel will reject... */ + /* memset to all 0 for your memcmp convenience. */ + memset(t->u.user.name + strlen(t->u.user.name), + 0, + FUNCTION_MAXNAMELEN - strlen(t->u.user.name)); + return 1; +} + +static void +unmap_target(STRUCT_ENTRY *e, STRUCT_ENTRY_TARGET *old) +{ + STRUCT_ENTRY_TARGET *t = GET_TARGET(e); + + /* Save old target (except data, which we don't change, except for + standard case, where we don't care). */ + *t = *old; +} + +/* Insert the entry `fw' in chain `chain' into position `rulenum'. */ +int +TC_INSERT_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int chainindex, offset; + STRUCT_ENTRY_TARGET old; + struct chain_cache *c; + STRUCT_ENTRY *tmp; + int ret; + + iptc_fn = TC_INSERT_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + + tmp = index2entry(*handle, chainindex + rulenum); + if (!tmp || tmp > offset2entry(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + offset = index2offset(*handle, chainindex + rulenum); + + /* Mapping target actually alters entry, but that's + transparent to the caller. */ + if (!map_target(*handle, (STRUCT_ENTRY *)e, offset, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, offset, + chainindex + rulenum, rulenum == 0, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +/* Atomically replace rule `rulenum' in `chain' with `fw'. */ +int +TC_REPLACE_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int chainindex, offset; + STRUCT_ENTRY_TARGET old; + struct chain_cache *c; + STRUCT_ENTRY *tmp; + int ret; + + iptc_fn = TC_REPLACE_ENTRY; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + + tmp = index2entry(*handle, chainindex + rulenum); + if (!tmp || tmp >= offset2entry(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + + offset = index2offset(*handle, chainindex + rulenum); + /* Replace = delete and insert. */ + if (!delete_rules(1, get_entry(*handle, offset)->next_offset, + offset, chainindex + rulenum, handle)) + return 0; + + if (!map_target(*handle, (STRUCT_ENTRY *)e, offset, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, offset, + chainindex + rulenum, 1, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +/* Append entry `fw' to chain `chain'. Equivalent to insert with + rulenum = length of chain. */ +int +TC_APPEND_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + TC_HANDLE_T *handle) +{ + struct chain_cache *c; + STRUCT_ENTRY_TARGET old; + int ret; + + iptc_fn = TC_APPEND_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + if (!map_target(*handle, (STRUCT_ENTRY *)e, + c->end_off, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, c->end_off, + offset2index(*handle, c->end_off), 0, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +static inline int +match_different(const STRUCT_ENTRY_MATCH *a, + const unsigned char *a_elems, + const unsigned char *b_elems, + unsigned char **maskptr) +{ + const STRUCT_ENTRY_MATCH *b; + unsigned int i; + + /* Offset of b is the same as a. */ + b = (void *)b_elems + ((unsigned char *)a - a_elems); + + if (a->u.match_size != b->u.match_size) + return 1; + + if (strcmp(a->u.user.name, b->u.user.name) != 0) + return 1; + + *maskptr += ALIGN(sizeof(*a)); + + for (i = 0; i < a->u.match_size - ALIGN(sizeof(*a)); i++) + if (((a->data[i] ^ b->data[i]) & (*maskptr)[i]) != 0) + return 1; + *maskptr += i; + return 0; +} + +static inline int +target_different(const unsigned char *a_targdata, + const unsigned char *b_targdata, + unsigned int tdatasize, + const unsigned char *mask) +{ + unsigned int i; + for (i = 0; i < tdatasize; i++) + if (((a_targdata[i] ^ b_targdata[i]) & mask[i]) != 0) + return 1; + + return 0; +} + +static int +is_same(const STRUCT_ENTRY *a, + const STRUCT_ENTRY *b, + unsigned char *matchmask); + +/* Delete the first rule in `chain' which matches `fw'. */ +int +TC_DELETE_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *origfw, + unsigned char *matchmask, + TC_HANDLE_T *handle) +{ + unsigned int offset; + struct chain_cache *c; + STRUCT_ENTRY *e, *fw; + + iptc_fn = TC_DELETE_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + fw = malloc(origfw->next_offset); + if (fw == NULL) { + errno = ENOMEM; + return 0; + } + + for (offset = c->start_off; offset < c->end_off; + offset += e->next_offset) { + STRUCT_ENTRY_TARGET discard; + + memcpy(fw, origfw, origfw->next_offset); + + /* FIXME: handle this in is_same --RR */ + if (!map_target(*handle, fw, offset, &discard)) { + free(fw); + return 0; + } + e = get_entry(*handle, offset); + +#if 0 + printf("Deleting:\n"); + dump_entry(newe); +#endif + if (is_same(e, fw, matchmask)) { + int ret; + ret = delete_rules(1, e->next_offset, + offset, entry2index(*handle, e), + handle); + free(fw); + return ret; + } + } + + free(fw); + errno = ENOENT; + return 0; +} + +/* Delete the rule in position `rulenum' in `chain'. */ +int +TC_DELETE_NUM_ENTRY(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int index; + int ret; + STRUCT_ENTRY *e; + struct chain_cache *c; + + iptc_fn = TC_DELETE_NUM_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + index = offset2index(*handle, c->start_off) + rulenum; + + if (index >= offset2index(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, index); + if (e == NULL) { + errno = EINVAL; + return 0; + } + + ret = delete_rules(1, e->next_offset, entry2offset(*handle, e), + index, handle); + return ret; +} + +/* Check the packet `fw' on chain `chain'. Returns the verdict, or + NULL and sets errno. */ +const char * +TC_CHECK_PACKET(const IPT_CHAINLABEL chain, + STRUCT_ENTRY *entry, + TC_HANDLE_T *handle) +{ + errno = ENOSYS; + return NULL; +} + +/* Flushes the entries in the given chain (ie. empties chain). */ +int +TC_FLUSH_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int startindex, endindex; + STRUCT_ENTRY *startentry, *endentry; + struct chain_cache *c; + int ret; + + iptc_fn = TC_FLUSH_ENTRIES; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + startindex = offset2index(*handle, c->start_off); + endindex = offset2index(*handle, c->end_off); + startentry = offset2entry(*handle, c->start_off); + endentry = offset2entry(*handle, c->end_off); + + ret = delete_rules(endindex - startindex, + (char *)endentry - (char *)startentry, + c->start_off, startindex, + handle); + return ret; +} + +/* Zeroes the counters in a chain. */ +int +TC_ZERO_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int i, end; + struct chain_cache *c; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + i = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + for (; i <= end; i++) { + if ((*handle)->counter_map[i].maptype ==COUNTER_MAP_NORMAL_MAP) + (*handle)->counter_map[i].maptype = COUNTER_MAP_ZEROED; + } + set_changed(*handle); + + return 1; +} + +STRUCT_COUNTERS * +TC_READ_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_READ_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return NULL; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return NULL; + } + + e = index2entry(*handle, chainindex + rulenum); + + return &e->counters; +} + +int +TC_ZERO_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_ZERO_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, chainindex + rulenum); + + if ((*handle)->counter_map[chainindex + rulenum].maptype + == COUNTER_MAP_NORMAL_MAP) { + (*handle)->counter_map[chainindex + rulenum].maptype + = COUNTER_MAP_ZEROED; + } + + set_changed(*handle); + + return 1; +} + +int +TC_SET_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_SET_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, chainindex + rulenum); + + (*handle)->counter_map[chainindex + rulenum].maptype + = COUNTER_MAP_SET; + + memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS)); + + set_changed(*handle); + + return 1; +} + +/* Creates a new chain. */ +/* To create a chain, create two rules: error node and unconditional + * return. */ +int +TC_CREATE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + int ret; + struct chainstart { + STRUCT_ENTRY head; + struct ipt_error_target name; + } *newc1; + struct chainend { + STRUCT_ENTRY ret; + STRUCT_STANDARD_TARGET target; + } *newc2; + struct chain_head *chead; + + iptc_fn = TC_CREATE_CHAIN; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(chain, *handle) + || strcmp(chain, LABEL_DROP) == 0 + || strcmp(chain, LABEL_ACCEPT) == 0 + || strcmp(chain, LABEL_QUEUE) == 0 + || strcmp(chain, LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (strlen(chain)+1 > sizeof(IPT_CHAINLABEL)) { + errno = EINVAL; + return 0; + } + + chead = chainh_alloc(*handle, chain); + if (!chead) { + errno = ENOMEM; + return 0; + } + + newc1 = ruleh_alloc(sizeof(*newc1)); + if (!newc1) { + chainh_free(chead); + return 0; + } + + newc2 = ruleh_alloc(sizeof(*newc2)); + if (!newc2) { + chainh_free(chead); + ruleh_free(newc1); + return 0; + } + + newc1->head.target_offset = sizeof(STRUCT_ENTRY); + newc1->head.next_offset + = sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(struct ipt_error_target)); + strcpy(newc1->name.t.u.user.name, ERROR_TARGET); + newc1->name.t.u.target_size = ALIGN(sizeof(struct ipt_error_target)); + strcpy(newc1->name.error, chain); + + newc2->ret.target_offset = sizeof(STRUCT_ENTRY); + newc2->ret.next_offset + = sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + strcpy(newc2->target.target.u.user.name, STANDARD_TARGET); + newc->target.target.u.target_size + = ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + newc->target.verdict = RETURN; + + list_prepend(newc1, &chead->rules); + list_append(newc2, &chead->rules); + + return 1; +} + +static int +count_ref(STRUCT_ENTRY *e, unsigned int offset, unsigned int *ref) +{ + STRUCT_STANDARD_TARGET *t; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) == 0) { + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (t->verdict == offset) + (*ref)++; + } + + return 0; +} + +/* Get the number of references to this chain. */ +int +TC_GET_REFERENCES(unsigned int *ref, const IPT_CHAINLABEL chain, + TC_HANDLE_T *handle) +{ + struct chain_cache *c; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + *ref = 0; + ENTRY_ITERATE((*handle)->entries.entrytable, + (*handle)->entries.size, + count_ref, c->start_off, ref); + return 1; +} + +/* Deletes a chain. */ +int +TC_DELETE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int labelidx, labeloff; + unsigned int references; + struct chain_cache *c; + int ret; + STRUCT_ENTRY *start; + + if (!TC_GET_REFERENCES(&references, chain, handle)) + return 0; + + iptc_fn = TC_DELETE_CHAIN; + + if (TC_BUILTIN(chain, *handle)) { + errno = EINVAL; + return 0; + } + + if (references > 0) { + errno = EMLINK; + return 0; + } + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + if (c->start_off != c->end_off) { + errno = ENOTEMPTY; + return 0; + } + + /* Need label index: preceeds chain start */ + labelidx = offset2index(*handle, c->start_off) - 1; + labeloff = index2offset(*handle, labelidx); + + start = offset2entry(*handle, c->start_off); + + ret = delete_rules(2, + get_entry(*handle, labeloff)->next_offset + + start->next_offset, + labeloff, labelidx, handle); + return ret; +} + +/* Renames a chain. */ +int TC_RENAME_CHAIN(const IPT_CHAINLABEL oldname, + const IPT_CHAINLABEL newname, + TC_HANDLE_T *handle) +{ + unsigned int labeloff, labelidx; + struct chain_cache *c; + struct ipt_error_target *t; + + iptc_fn = TC_RENAME_CHAIN; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(newname, *handle) + || strcmp(newname, LABEL_DROP) == 0 + || strcmp(newname, LABEL_ACCEPT) == 0 + || strcmp(newname, LABEL_QUEUE) == 0 + || strcmp(newname, LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (!(c = find_label(oldname, *handle)) + || TC_BUILTIN(oldname, *handle)) { + errno = ENOENT; + return 0; + } + + if (strlen(newname)+1 > sizeof(IPT_CHAINLABEL)) { + errno = EINVAL; + return 0; + } + + /* Need label index: preceeds chain start */ + labelidx = offset2index(*handle, c->start_off) - 1; + labeloff = index2offset(*handle, labelidx); + + t = (struct ipt_error_target *) + GET_TARGET(get_entry(*handle, labeloff)); + + memset(t->error, 0, sizeof(t->error)); + strcpy(t->error, newname); + set_changed(*handle); + + return 1; +} + +/* Sets the policy on a built-in chain. */ +int +TC_SET_POLICY(const IPT_CHAINLABEL chain, + const IPT_CHAINLABEL policy, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + unsigned int hook; + unsigned int policyoff, ctrindex; + STRUCT_ENTRY *e; + STRUCT_STANDARD_TARGET *t; + + iptc_fn = TC_SET_POLICY; + /* Figure out which chain. */ + hook = TC_BUILTIN(chain, *handle); + if (hook == 0) { + errno = ENOENT; + return 0; + } else + hook--; + + policyoff = get_chain_end(*handle, (*handle)->info.hook_entry[hook]); + if (policyoff != (*handle)->info.underflow[hook]) { + printf("ERROR: Policy for `%s' offset %u != underflow %u\n", + chain, policyoff, (*handle)->info.underflow[hook]); + return 0; + } + + e = get_entry(*handle, policyoff); + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (strcmp(policy, LABEL_ACCEPT) == 0) + t->verdict = -NF_ACCEPT - 1; + else if (strcmp(policy, LABEL_DROP) == 0) + t->verdict = -NF_DROP - 1; + else { + errno = EINVAL; + return 0; + } + + ctrindex = entry2index(*handle, e); + + if (counters) { + /* set byte and packet counters */ + memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS)); + + (*handle)->counter_map[ctrindex].maptype + = COUNTER_MAP_SET; + + } else { + (*handle)->counter_map[ctrindex] + = ((struct counter_map){ COUNTER_MAP_NOMAP, 0 }); + } + + set_changed(*handle); + + return 1; +} + +/* Without this, on gcc 2.7.2.3, we get: + libiptc.c: In function `TC_COMMIT': + libiptc.c:833: fixed or forbidden register was spilled. + This may be due to a compiler bug or to impossible asm + statements or clauses. +*/ +static void +subtract_counters(STRUCT_COUNTERS *answer, + const STRUCT_COUNTERS *a, + const STRUCT_COUNTERS *b) +{ + answer->pcnt = a->pcnt - b->pcnt; + answer->bcnt = a->bcnt - b->bcnt; +} + +int +TC_COMMIT(TC_HANDLE_T *handle) +{ + /* Replace, then map back the counters. */ + STRUCT_REPLACE *repl; + STRUCT_COUNTERS_INFO *newcounters; + unsigned int i; + size_t counterlen; + + CHECK(*handle); + + counterlen = sizeof(STRUCT_COUNTERS_INFO) + + sizeof(STRUCT_COUNTERS) * (*handle)->new_number; + +#if 0 + TC_DUMP_ENTRIES(*handle); +#endif + + /* Don't commit if nothing changed. */ + if (!(*handle)->changed) + goto finished; + + repl = malloc(sizeof(*repl) + (*handle)->entries.size); + if (!repl) { + errno = ENOMEM; + return 0; + } + + /* These are the old counters we will get from kernel */ + repl->counters = malloc(sizeof(STRUCT_COUNTERS) + * (*handle)->info.num_entries); + if (!repl->counters) { + free(repl); + errno = ENOMEM; + return 0; + } + + /* These are the counters we're going to put back, later. */ + newcounters = malloc(counterlen); + if (!newcounters) { + free(repl->counters); + free(repl); + errno = ENOMEM; + return 0; + } + + strcpy(repl->name, (*handle)->info.name); + repl->num_entries = (*handle)->new_number; + repl->size = (*handle)->entries.size; + memcpy(repl->hook_entry, (*handle)->info.hook_entry, + sizeof(repl->hook_entry)); + memcpy(repl->underflow, (*handle)->info.underflow, + sizeof(repl->underflow)); + repl->num_counters = (*handle)->info.num_entries; + repl->valid_hooks = (*handle)->info.valid_hooks; + memcpy(repl->entries, (*handle)->entries.entrytable, + (*handle)->entries.size); + + if (setsockopt(sockfd, TC_IPPROTO, SO_SET_REPLACE, repl, + sizeof(*repl) + (*handle)->entries.size) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + /* Put counters back. */ + strcpy(newcounters->name, (*handle)->info.name); + newcounters->num_counters = (*handle)->new_number; + for (i = 0; i < (*handle)->new_number; i++) { + unsigned int mappos = (*handle)->counter_map[i].mappos; + switch ((*handle)->counter_map[i].maptype) { + case COUNTER_MAP_NOMAP: + newcounters->counters[i] + = ((STRUCT_COUNTERS){ 0, 0 }); + break; + + case COUNTER_MAP_NORMAL_MAP: + /* Original read: X. + * Atomic read on replacement: X + Y. + * Currently in kernel: Z. + * Want in kernel: X + Y + Z. + * => Add in X + Y + * => Add in replacement read. + */ + newcounters->counters[i] = repl->counters[mappos]; + break; + + case COUNTER_MAP_ZEROED: + /* Original read: X. + * Atomic read on replacement: X + Y. + * Currently in kernel: Z. + * Want in kernel: Y + Z. + * => Add in Y. + * => Add in (replacement read - original read). + */ + subtract_counters(&newcounters->counters[i], + &repl->counters[mappos], + &index2entry(*handle, i)->counters); + break; + + case COUNTER_MAP_SET: + /* Want to set counter (iptables-restore) */ + + memcpy(&newcounters->counters[i], + &index2entry(*handle, i)->counters, + sizeof(STRUCT_COUNTERS)); + + break; + } + } + +#ifdef KERNEL_64_USERSPACE_32 + { + /* Kernel will think that pointer should be 64-bits, and get + padding. So we accomodate here (assumption: alignment of + `counters' is on 64-bit boundary). */ + u_int64_t *kernptr = (u_int64_t *)&newcounters->counters; + if ((unsigned long)&newcounters->counters % 8 != 0) { + fprintf(stderr, + "counters alignment incorrect! Mail rusty!\n"); + abort(); + } + *kernptr = newcounters->counters; + } +#endif /* KERNEL_64_USERSPACE_32 */ + + if (setsockopt(sockfd, TC_IPPROTO, SO_SET_ADD_COUNTERS, + newcounters, counterlen) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + free(repl->counters); + free(repl); + free(newcounters); + + finished: + TC_FREE(handle); + return 1; +} + +/* Get raw socket. */ +int +TC_GET_RAW_SOCKET() +{ + return sockfd; +} + +/* Translates errno numbers into more human-readable form than strerror. */ +const char * +TC_STRERROR(int err) +{ + unsigned int i; + struct table_struct { + void *fn; + int err; + const char *message; + } table [] = + { { TC_INIT, EPERM, "Permission denied (you must be root)" }, + { TC_INIT, EINVAL, "Module is wrong version" }, + { TC_INIT, ENOENT, + "Table does not exist (do you need to insmod?)" }, + { TC_DELETE_CHAIN, ENOTEMPTY, "Chain is not empty" }, + { TC_DELETE_CHAIN, EINVAL, "Can't delete built-in chain" }, + { TC_DELETE_CHAIN, EMLINK, + "Can't delete chain with references left" }, + { TC_CREATE_CHAIN, EEXIST, "Chain already exists" }, + { TC_INSERT_ENTRY, E2BIG, "Index of insertion too big" }, + { TC_REPLACE_ENTRY, E2BIG, "Index of replacement too big" }, + { TC_DELETE_NUM_ENTRY, E2BIG, "Index of deletion too big" }, + { TC_READ_COUNTER, E2BIG, "Index of counter too big" }, + { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" }, + { TC_INSERT_ENTRY, ELOOP, "Loop found in table" }, + { TC_INSERT_ENTRY, EINVAL, "Target problem" }, + /* EINVAL for CHECK probably means bad interface. */ + { TC_CHECK_PACKET, EINVAL, + "Bad arguments (does that interface exist?)" }, + { TC_CHECK_PACKET, ENOSYS, + "Checking will most likely never get implemented" }, + /* ENOENT for DELETE probably means no matching rule */ + { TC_DELETE_ENTRY, ENOENT, + "Bad rule (does a matching rule exist in that chain?)" }, + { TC_SET_POLICY, ENOENT, + "Bad built-in chain name" }, + { TC_SET_POLICY, EINVAL, + "Bad policy name" }, + + { NULL, 0, "Incompatible with this kernel" }, + { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" }, + { NULL, ENOSYS, "Will be implemented real soon. I promise ;)" }, + { NULL, ENOMEM, "Memory allocation problem" }, + { NULL, ENOENT, "No chain/target/match by that name" }, + }; + + for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) { + if ((!table[i].fn || table[i].fn == iptc_fn) + && table[i].err == err) + return table[i].message; + } + + return strerror(err); +} diff --git a/libiptc2/foo.diff b/libiptc2/foo.diff new file mode 100644 index 0000000..af69eb5 --- /dev/null +++ b/libiptc2/foo.diff @@ -0,0 +1,391 @@ +--- libiptc.c 2003-06-30 18:26:59.000000000 +0200 ++++ libiptc.c 2003-06-30 18:27:24.000000000 +0200 +@@ -64,19 +61,35 @@ + char error[TABLE_MAXNAMELEN]; + }; + +-struct chain_cache ++struct rule_head + { ++ struct list_head list; ++ ++ struct chain_head *chain; ++ ++ unsigned int size; ++ STRUCT_ENTRY entry[0]; ++} ++ ++struct chain_head ++{ ++ struct list_head list; ++ + char name[TABLE_MAXNAMELEN]; +- /* This is the first rule in chain. */ +- unsigned int start_off; +- /* Last rule in chain */ +- unsigned int end_off; ++ unsigned int hooknum; ++ struct list_head rules; + }; + + STRUCT_TC_HANDLE + { + /* Have changes been made? */ + int changed; ++ ++ struct list_head chains; ++ ++ struct chain_head *chain_iterator_cur; ++ ++#if 0 + /* Size in here reflects original state. */ + STRUCT_GETINFO info; + +@@ -98,6 +111,7 @@ + /* Number in here reflects current state. */ + unsigned int new_number; + STRUCT_GET_ENTRIES entries; ++#endif + }; + + static void +@@ -375,173 +389,25 @@ + } + return 0; + } +- +-static inline int +-add_chain(STRUCT_ENTRY *e, TC_HANDLE_T h, STRUCT_ENTRY **prev) +-{ +- unsigned int builtin; +- +- /* Last entry. End it. */ +- if (entry2offset(h, e) + e->next_offset == h->entries.size) { +- /* This is the ERROR node at end of the table */ +- h->cache_chain_heads[h->cache_num_chains-1].end_off = +- entry2offset(h, *prev); +- return 0; +- } +- +- /* We know this is the start of a new chain if it's an ERROR +- target, or a hook entry point */ +- if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) == 0) { +- /* prev was last entry in previous chain */ +- h->cache_chain_heads[h->cache_num_chains-1].end_off +- = entry2offset(h, *prev); +- +- strcpy(h->cache_chain_heads[h->cache_num_chains].name, +- (const char *)GET_TARGET(e)->data); +- h->cache_chain_heads[h->cache_num_chains].start_off +- = entry2offset(h, (void *)e + e->next_offset); +- h->cache_num_chains++; +- } else if ((builtin = is_hook_entry(e, h)) != 0) { +- if (h->cache_num_chains > 0) +- /* prev was last entry in previous chain */ +- h->cache_chain_heads[h->cache_num_chains-1].end_off +- = entry2offset(h, *prev); +- +- strcpy(h->cache_chain_heads[h->cache_num_chains].name, +- h->hooknames[builtin-1]); +- h->cache_chain_heads[h->cache_num_chains].start_off +- = entry2offset(h, (void *)e); +- h->cache_num_chains++; +- } +- +- *prev = e; +- return 0; +-} +- + static int alphasort(const void *a, const void *b) + { + return strcmp(((struct chain_cache *)a)->name, + ((struct chain_cache *)b)->name); + } + +-static int populate_cache(TC_HANDLE_T h) +-{ +- unsigned int i; +- STRUCT_ENTRY *prev; +- +- /* # chains < # rules / 2 + num builtins - 1 */ +- h->cache_chain_heads = malloc((h->new_number / 2 + 4) +- * sizeof(struct chain_cache)); +- if (!h->cache_chain_heads) { +- errno = ENOMEM; +- return 0; +- } +- +- h->cache_num_chains = 0; +- h->cache_num_builtins = 0; +- +- /* Count builtins */ +- for (i = 0; i < NUMHOOKS; i++) { +- if (h->info.valid_hooks & (1 << i)) +- h->cache_num_builtins++; +- } +- +- prev = NULL; +- ENTRY_ITERATE(h->entries.entrytable, h->entries.size, +- add_chain, h, &prev); +- +- qsort(h->cache_chain_heads + h->cache_num_builtins, +- h->cache_num_chains - h->cache_num_builtins, +- sizeof(struct chain_cache), alphasort); +- +- return 1; +-} +- +-static int +-correct_cache(TC_HANDLE_T h, unsigned int offset, int delta) +-{ +- int i; /* needs to be signed because deleting first +- chain can make it drop to -1 */ +- +- if (!delta) +- return 1; +- +- for (i = 0; i < h->cache_num_chains; i++) { +- struct chain_cache *cc = &h->cache_chain_heads[i]; +- +- if (delta < 0) { +- /* take care about deleted chains */ +- if (cc->start_off >= offset+delta +- && cc->end_off <= offset) { +- /* this chain is within the deleted range, +- * let's remove it from the cache */ +- void *start; +- unsigned int size; +- +- h->cache_num_chains--; +- if (i+1 >= h->cache_num_chains) +- continue; +- start = &h->cache_chain_heads[i+1]; +- size = (h->cache_num_chains-i) +- * sizeof(struct chain_cache); +- memmove(cc, start, size); +- +- /* iterate over same index again, since +- * it is now a different chain */ +- i--; +- continue; +- } +- } +- +- if (cc->start_off > offset) +- cc->start_off += delta; +- +- if (cc->end_off >= offset) +- cc->end_off += delta; +- } +- /* HW_FIXME: sorting might be needed, but just in case a new chain was +- * added */ +- +- return 1; +-} +- +-static int +-add_chain_cache(TC_HANDLE_T h, const char *name, unsigned int start_off, +- unsigned int end_off) +-{ +- struct chain_cache *ccs = realloc(h->cache_chain_heads, +- (h->new_number / 2 + 4 + 1) +- * sizeof(struct chain_cache)); +- struct chain_cache *newcc; +- +- if (!ccs) +- return 0; +- +- h->cache_chain_heads = ccs; +- newcc = &h->cache_chain_heads[h->cache_num_chains]; +- h->cache_num_chains++; +- +- strncpy(newcc->name, name, TABLE_MAXNAMELEN-1); +- newcc->start_off = start_off; +- newcc->end_off = end_off; +- +- return 1; +-} +- +-/* Returns cache ptr if found, otherwise NULL. */ +-static struct chain_cache * ++/* Returns chain head if found, otherwise NULL. */ ++static struct chain_head * + find_label(const char *name, TC_HANDLE_T handle) + { +- unsigned int i; ++ struct list_head *pos; + +- if (handle->cache_chain_heads == NULL +- && !populate_cache(handle)) ++ if (!handle->chains) + return NULL; + +- /* FIXME: Linear search through builtins, then binary --RR */ +- for (i = 0; i < handle->cache_num_chains; i++) { +- if (strcmp(handle->cache_chain_heads[i].name, name) == 0) +- return &handle->cache_chain_heads[i]; ++ list_for_each(pos, &handle->chains) { ++ struct chain_head *c = list_entry(pos, struct chain_head, list); ++ if (!strcmp(c->name, name)) ++ return c; + } + + return NULL; +@@ -594,34 +460,30 @@ + const char * + TC_FIRST_CHAIN(TC_HANDLE_T *handle) + { +- if ((*handle)->cache_chain_heads == NULL +- && !populate_cache(*handle)) +- return NULL; ++ (*handle)->chain_iterator_cur = (*handle)->chains; + +- (*handle)->cache_chain_iteration +- = &(*handle)->cache_chain_heads[0]; +- +- return (*handle)->cache_chain_iteration->name; ++ return (*handle)->chains.name; + } + + /* Iterator functions to run through the chains. Returns NULL at end. */ + const char * + TC_NEXT_CHAIN(TC_HANDLE_T *handle) + { +- (*handle)->cache_chain_iteration++; ++ struct chain_head *next = list_entry(&(*handle)->chain_iterator_cur->list.next, struct chain_head, list); ++ (*handle)->chain_iterator_cur = next; + +- if ((*handle)->cache_chain_iteration - (*handle)->cache_chain_heads +- == (*handle)->cache_num_chains) ++ if (next == (*handle)->chains) + return NULL; + +- return (*handle)->cache_chain_iteration->name; ++ return next->name; + } + + /* Get first rule in the given chain: NULL for empty chain. */ + const STRUCT_ENTRY * + TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle) + { +- struct chain_cache *c; ++ struct chain_head *c; ++ struct rule_head *r; + + c = find_label(chain, *handle); + if (!c) { +@@ -630,22 +492,26 @@ + } + + /* Empty chain: single return/policy rule */ +- if (c->start_off == c->end_off) ++ if (list_empty(c->rules)) + return NULL; + +- (*handle)->cache_rule_end = offset2entry(*handle, c->end_off); +- return offset2entry(*handle, c->start_off); ++ r = list_entry(&c->rules.next, struct rule_head, list); ++ (*handle)->rule_iterator_cur = r; ++ ++ return r->entry; + } + + /* Returns NULL when rules run out. */ + const STRUCT_ENTRY * + TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle) + { +- if ((void *)prev + prev->next_offset +- == (void *)(*handle)->cache_rule_end) ++ struct rule_head *r = list_entry((*handle)->rule_iterator_cur->list.next, struct rule_head, list); ++ ++ if (r == r->chain) + return NULL; + +- return (void *)prev + prev->next_offset; ++ /* NOTE: prev is without any influence ! */ ++ return r->entry; + } + + #if 0 +@@ -773,7 +639,7 @@ + return target_name(*handle, e); + } + +-static inline int ++static int + correct_verdict(STRUCT_ENTRY *e, + char *base, + unsigned int offset, int delta_offset) +@@ -874,16 +740,8 @@ + newh->entries.size = (*handle)->entries.size + rules_size; + newh->hooknames = (*handle)->hooknames; + +- newh->cache_chain_heads = (*handle)->cache_chain_heads; +- newh->cache_num_builtins = (*handle)->cache_num_builtins; +- newh->cache_num_chains = (*handle)->cache_num_chains; +- newh->cache_rule_end = (*handle)->cache_rule_end; +- newh->cache_chain_iteration = (*handle)->cache_chain_iteration; +- if (!correct_cache(newh, offset, rules_size)) { +- free(newh); +- return 0; +- } +- ++ if ((*handle)->cache_chain_heads) ++ free((*handle)->cache_chain_heads); + free(*handle); + *handle = newh; + +@@ -942,10 +800,6 @@ + (*handle)->new_number -= num_rules; + (*handle)->entries.size -= rules_size; + +- /* Fix the chain cache */ +- if (!correct_cache(*handle, offset, -(int)rules_size)) +- return 0; +- + return set_verdict(offset, -(int)rules_size, handle); + } + +@@ -1449,7 +1303,6 @@ + STRUCT_ENTRY ret; + STRUCT_STANDARD_TARGET target; + } newc; +- unsigned int destination; + + iptc_fn = TC_CREATE_CHAIN; + +@@ -1487,21 +1340,11 @@ + = ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + newc.target.verdict = RETURN; + +- destination = index2offset(*handle, (*handle)->new_number -1); +- + /* Add just before terminal entry */ + ret = insert_rules(2, sizeof(newc), &newc.head, +- destination, ++ index2offset(*handle, (*handle)->new_number - 1), + (*handle)->new_number - 1, + 0, handle); +- +- set_changed(*handle); +- +- /* add chain cache info for this chain */ +- add_chain_cache(*handle, chain, +- destination+newc.head.next_offset, +- destination+newc.head.next_offset); +- + return ret; + } + +@@ -1629,11 +1472,6 @@ + + memset(t->error, 0, sizeof(t->error)); + strcpy(t->error, newname); +- +- /* update chain cache */ +- memset(c->name, 0, sizeof(c->name)); +- strcpy(c->name, newname); +- + set_changed(*handle); + + return 1; diff --git a/libiptc2/libip4tc.c b/libiptc2/libip4tc.c new file mode 100644 index 0000000..e012c08 --- /dev/null +++ b/libiptc2/libip4tc.c @@ -0,0 +1,507 @@ +/* Library which manipulates firewall rules. Version 0.1. */ + +/* Architecture of firewall rules is as follows: + * + * Chains go INPUT, FORWARD, OUTPUT then user chains. + * Each user chain starts with an ERROR node. + * Every chain ends with an unconditional jump: a RETURN for user chains, + * and a POLICY for built-ins. + */ + +/* (C)1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See + COPYING for details). */ + +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG_CONNTRACK +#define inline +#endif + +#if !defined(__GLIBC__) || (__GLIBC__ < 2) +typedef unsigned int socklen_t; +#endif + +#include "libiptc/libiptc.h" + +#define IP_VERSION 4 +#define IP_OFFSET 0x1FFF + +#define HOOK_PRE_ROUTING NF_IP_PRE_ROUTING +#define HOOK_LOCAL_IN NF_IP_LOCAL_IN +#define HOOK_FORWARD NF_IP_FORWARD +#define HOOK_LOCAL_OUT NF_IP_LOCAL_OUT +#define HOOK_POST_ROUTING NF_IP_POST_ROUTING +#ifdef NF_IP_DROPPING +#define HOOK_DROPPING NF_IP_DROPPING +#endif + +#define STRUCT_ENTRY_TARGET struct ipt_entry_target +#define STRUCT_ENTRY struct ipt_entry +#define STRUCT_ENTRY_MATCH struct ipt_entry_match +#define STRUCT_GETINFO struct ipt_getinfo +#define STRUCT_GET_ENTRIES struct ipt_get_entries +#define STRUCT_COUNTERS struct ipt_counters +#define STRUCT_COUNTERS_INFO struct ipt_counters_info +#define STRUCT_STANDARD_TARGET struct ipt_standard_target +#define STRUCT_REPLACE struct ipt_replace + +#define STRUCT_TC_HANDLE struct iptc_handle +#define TC_HANDLE_T iptc_handle_t + +#define ENTRY_ITERATE IPT_ENTRY_ITERATE +#define TABLE_MAXNAMELEN IPT_TABLE_MAXNAMELEN +#define FUNCTION_MAXNAMELEN IPT_FUNCTION_MAXNAMELEN + +#define GET_TARGET ipt_get_target + +#define ERROR_TARGET IPT_ERROR_TARGET +#define NUMHOOKS NF_IP_NUMHOOKS + +#define IPT_CHAINLABEL ipt_chainlabel + +#define TC_DUMP_ENTRIES dump_entries +#define TC_IS_CHAIN iptc_is_chain +#define TC_FIRST_CHAIN iptc_first_chain +#define TC_NEXT_CHAIN iptc_next_chain +#define TC_FIRST_RULE iptc_first_rule +#define TC_NEXT_RULE iptc_next_rule +#define TC_GET_TARGET iptc_get_target +#define TC_BUILTIN iptc_builtin +#define TC_GET_POLICY iptc_get_policy +#define TC_INSERT_ENTRY iptc_insert_entry +#define TC_REPLACE_ENTRY iptc_replace_entry +#define TC_APPEND_ENTRY iptc_append_entry +#define TC_DELETE_ENTRY iptc_delete_entry +#define TC_DELETE_NUM_ENTRY iptc_delete_num_entry +#define TC_CHECK_PACKET iptc_check_packet +#define TC_FLUSH_ENTRIES iptc_flush_entries +#define TC_ZERO_ENTRIES iptc_zero_entries +#define TC_READ_COUNTER iptc_read_counter +#define TC_ZERO_COUNTER iptc_zero_counter +#define TC_SET_COUNTER iptc_set_counter +#define TC_CREATE_CHAIN iptc_create_chain +#define TC_GET_REFERENCES iptc_get_references +#define TC_DELETE_CHAIN iptc_delete_chain +#define TC_RENAME_CHAIN iptc_rename_chain +#define TC_SET_POLICY iptc_set_policy +#define TC_GET_RAW_SOCKET iptc_get_raw_socket +#define TC_INIT iptc_init +#define TC_FREE iptc_free +#define TC_COMMIT iptc_commit +#define TC_STRERROR iptc_strerror + +#define TC_AF AF_INET +#define TC_IPPROTO IPPROTO_IP + +#define SO_SET_REPLACE IPT_SO_SET_REPLACE +#define SO_SET_ADD_COUNTERS IPT_SO_SET_ADD_COUNTERS +#define SO_GET_INFO IPT_SO_GET_INFO +#define SO_GET_ENTRIES IPT_SO_GET_ENTRIES +#define SO_GET_VERSION IPT_SO_GET_VERSION + +#define STANDARD_TARGET IPT_STANDARD_TARGET +#define LABEL_RETURN IPTC_LABEL_RETURN +#define LABEL_ACCEPT IPTC_LABEL_ACCEPT +#define LABEL_DROP IPTC_LABEL_DROP +#define LABEL_QUEUE IPTC_LABEL_QUEUE + +#define ALIGN IPT_ALIGN +#define RETURN IPT_RETURN + +#include "libiptc.c" + +#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)) + +int +dump_entry(STRUCT_ENTRY *e, const TC_HANDLE_T handle) +{ + size_t i; + STRUCT_ENTRY_TARGET *t; + + printf("Entry %u (%lu):\n", entry2index(handle, e), + entry2offset(handle, e)); + printf("SRC IP: %u.%u.%u.%u/%u.%u.%u.%u\n", + IP_PARTS(e->ip.src.s_addr),IP_PARTS(e->ip.smsk.s_addr)); + printf("DST IP: %u.%u.%u.%u/%u.%u.%u.%u\n", + IP_PARTS(e->ip.dst.s_addr),IP_PARTS(e->ip.dmsk.s_addr)); + printf("Interface: `%s'/", e->ip.iniface); + for (i = 0; i < IFNAMSIZ; i++) + printf("%c", e->ip.iniface_mask[i] ? 'X' : '.'); + printf("to `%s'/", e->ip.outiface); + for (i = 0; i < IFNAMSIZ; i++) + printf("%c", e->ip.outiface_mask[i] ? 'X' : '.'); + printf("\nProtocol: %u\n", e->ip.proto); + printf("Flags: %02X\n", e->ip.flags); + printf("Invflags: %02X\n", e->ip.invflags); + printf("Counters: %llu packets, %llu bytes\n", + e->counters.pcnt, e->counters.bcnt); + printf("Cache: %08X ", e->nfcache); + if (e->nfcache & NFC_ALTERED) printf("ALTERED "); + if (e->nfcache & NFC_UNKNOWN) printf("UNKNOWN "); + if (e->nfcache & NFC_IP_SRC) printf("IP_SRC "); + if (e->nfcache & NFC_IP_DST) printf("IP_DST "); + if (e->nfcache & NFC_IP_IF_IN) printf("IP_IF_IN "); + if (e->nfcache & NFC_IP_IF_OUT) printf("IP_IF_OUT "); + if (e->nfcache & NFC_IP_TOS) printf("IP_TOS "); + if (e->nfcache & NFC_IP_PROTO) printf("IP_PROTO "); + if (e->nfcache & NFC_IP_OPTIONS) printf("IP_OPTIONS "); + if (e->nfcache & NFC_IP_TCPFLAGS) printf("IP_TCPFLAGS "); + if (e->nfcache & NFC_IP_SRC_PT) printf("IP_SRC_PT "); + if (e->nfcache & NFC_IP_DST_PT) printf("IP_DST_PT "); + if (e->nfcache & NFC_IP_PROTO_UNKNOWN) printf("IP_PROTO_UNKNOWN "); + printf("\n"); + + IPT_MATCH_ITERATE(e, print_match); + + t = GET_TARGET(e); + printf("Target name: `%s' [%u]\n", t->u.user.name, t->u.target_size); + if (strcmp(t->u.user.name, STANDARD_TARGET) == 0) { + int pos = *(int *)t->data; + if (pos < 0) + printf("verdict=%s\n", + pos == -NF_ACCEPT-1 ? "NF_ACCEPT" + : pos == -NF_DROP-1 ? "NF_DROP" + : pos == -NF_QUEUE-1 ? "NF_QUEUE" + : pos == RETURN ? "RETURN" + : "UNKNOWN"); + else + printf("verdict=%u\n", pos); + } else if (strcmp(t->u.user.name, IPT_ERROR_TARGET) == 0) + printf("error=`%s'\n", t->data); + + printf("\n"); + return 0; +} + +static int +is_same(const STRUCT_ENTRY *a, const STRUCT_ENTRY *b, unsigned char *matchmask) +{ + unsigned int i; + STRUCT_ENTRY_TARGET *ta, *tb; + unsigned char *mptr; + + /* Always compare head structures: ignore mask here. */ + if (a->ip.src.s_addr != b->ip.src.s_addr + || a->ip.dst.s_addr != b->ip.dst.s_addr + || a->ip.smsk.s_addr != b->ip.smsk.s_addr + || a->ip.dmsk.s_addr != b->ip.dmsk.s_addr + || a->ip.proto != b->ip.proto + || a->ip.flags != b->ip.flags + || a->ip.invflags != b->ip.invflags) + return 0; + + for (i = 0; i < IFNAMSIZ; i++) { + if (a->ip.iniface_mask[i] != b->ip.iniface_mask[i]) + return 0; + if ((a->ip.iniface[i] & a->ip.iniface_mask[i]) + != (b->ip.iniface[i] & b->ip.iniface_mask[i])) + return 0; + if (a->ip.outiface_mask[i] != b->ip.outiface_mask[i]) + return 0; + if ((a->ip.outiface[i] & a->ip.outiface_mask[i]) + != (b->ip.outiface[i] & b->ip.outiface_mask[i])) + return 0; + } + + if (a->nfcache != b->nfcache + || a->target_offset != b->target_offset + || a->next_offset != b->next_offset) + return 0; + + mptr = matchmask + sizeof(STRUCT_ENTRY); + if (IPT_MATCH_ITERATE(a, match_different, a->elems, b->elems, &mptr)) + return 0; + + ta = GET_TARGET((STRUCT_ENTRY *)a); + tb = GET_TARGET((STRUCT_ENTRY *)b); + if (ta->u.target_size != tb->u.target_size) + return 0; + if (strcmp(ta->u.user.name, tb->u.user.name) != 0) + return 0; + + mptr += sizeof(*ta); + if (target_different(ta->data, tb->data, + ta->u.target_size - sizeof(*ta), mptr)) + return 0; + + return 1; +} + +/***************************** DEBUGGING ********************************/ +static inline int +unconditional(const struct ipt_ip *ip) +{ + unsigned int i; + + for (i = 0; i < sizeof(*ip)/sizeof(u_int32_t); i++) + if (((u_int32_t *)ip)[i]) + return 0; + + return 1; +} + +static inline int +check_match(const STRUCT_ENTRY_MATCH *m, unsigned int *off) +{ + assert(m->u.match_size >= sizeof(STRUCT_ENTRY_MATCH)); + assert(ALIGN(m->u.match_size) == m->u.match_size); + + (*off) += m->u.match_size; + return 0; +} + +static inline int +check_entry(const STRUCT_ENTRY *e, unsigned int *i, unsigned int *off, + unsigned int user_offset, int *was_return, + TC_HANDLE_T h) +{ + unsigned int toff; + STRUCT_STANDARD_TARGET *t; + + assert(e->target_offset >= sizeof(STRUCT_ENTRY)); + assert(e->next_offset >= e->target_offset + + sizeof(STRUCT_ENTRY_TARGET)); + toff = sizeof(STRUCT_ENTRY); + IPT_MATCH_ITERATE(e, check_match, &toff); + + assert(toff == e->target_offset); + + t = (STRUCT_STANDARD_TARGET *) + GET_TARGET((STRUCT_ENTRY *)e); + /* next_offset will have to be multiple of entry alignment. */ + assert(e->next_offset == ALIGN(e->next_offset)); + assert(e->target_offset == ALIGN(e->target_offset)); + assert(t->target.u.target_size == ALIGN(t->target.u.target_size)); + assert(!TC_IS_CHAIN(t->target.u.user.name, h)); + + if (strcmp(t->target.u.user.name, STANDARD_TARGET) == 0) { + assert(t->target.u.target_size + == ALIGN(sizeof(STRUCT_STANDARD_TARGET))); + + assert(t->verdict == -NF_DROP-1 + || t->verdict == -NF_ACCEPT-1 + || t->verdict == RETURN + || t->verdict < (int)h->entries.size); + + if (t->verdict >= 0) { + STRUCT_ENTRY *te = get_entry(h, t->verdict); + int idx; + + idx = entry2index(h, te); + assert(strcmp(GET_TARGET(te)->u.user.name, + IPT_ERROR_TARGET) + != 0); + assert(te != e); + + /* Prior node must be error node, or this node. */ + assert(t->verdict == entry2offset(h, e)+e->next_offset + || strcmp(GET_TARGET(index2entry(h, idx-1)) + ->u.user.name, IPT_ERROR_TARGET) + == 0); + } + + if (t->verdict == RETURN + && unconditional(&e->ip) + && e->target_offset == sizeof(*e)) + *was_return = 1; + else + *was_return = 0; + } else if (strcmp(t->target.u.user.name, IPT_ERROR_TARGET) == 0) { + assert(t->target.u.target_size + == ALIGN(sizeof(struct ipt_error_target))); + + /* If this is in user area, previous must have been return */ + if (*off > user_offset) + assert(*was_return); + + *was_return = 0; + } + else *was_return = 0; + + if (*off == user_offset) + assert(strcmp(t->target.u.user.name, IPT_ERROR_TARGET) == 0); + + (*off) += e->next_offset; + (*i)++; + return 0; +} + +#ifdef IPTC_DEBUG +/* Do every conceivable sanity check on the handle */ +static void +do_check(TC_HANDLE_T h, unsigned int line) +{ + unsigned int i, n; + unsigned int user_offset; /* Offset of first user chain */ + int was_return; + + assert(h->changed == 0 || h->changed == 1); + if (strcmp(h->info.name, "filter") == 0) { + assert(h->info.valid_hooks + == (1 << NF_IP_LOCAL_IN + | 1 << NF_IP_FORWARD + | 1 << NF_IP_LOCAL_OUT)); + + /* Hooks should be first three */ + assert(h->info.hook_entry[NF_IP_LOCAL_IN] == 0); + + n = get_chain_end(h, 0); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_FORWARD] == n); + + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n); + + user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT]; + } else if (strcmp(h->info.name, "nat") == 0) { + assert((h->info.valid_hooks + == (1 << NF_IP_PRE_ROUTING + | 1 << NF_IP_POST_ROUTING + | 1 << NF_IP_LOCAL_OUT)) || + (h->info.valid_hooks + == (1 << NF_IP_PRE_ROUTING + | 1 << NF_IP_LOCAL_IN + | 1 << NF_IP_POST_ROUTING + | 1 << NF_IP_LOCAL_OUT))); + + assert(h->info.hook_entry[NF_IP_PRE_ROUTING] == 0); + + n = get_chain_end(h, 0); + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_POST_ROUTING] == n); + n = get_chain_end(h, n); + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n); + user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT]; + + if (h->info.valid_hooks & (1 << NF_IP_LOCAL_IN)) { + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_LOCAL_IN] == n); + user_offset = h->info.hook_entry[NF_IP_LOCAL_IN]; + } + + } else if (strcmp(h->info.name, "mangle") == 0) { + /* This code is getting ugly because linux < 2.4.18-pre6 had + * two mangle hooks, linux >= 2.4.18-pre6 has five mangle hooks + * */ + assert((h->info.valid_hooks + == (1 << NF_IP_PRE_ROUTING + | 1 << NF_IP_LOCAL_OUT)) || + (h->info.valid_hooks + == (1 << NF_IP_PRE_ROUTING + | 1 << NF_IP_LOCAL_IN + | 1 << NF_IP_FORWARD + | 1 << NF_IP_LOCAL_OUT + | 1 << NF_IP_POST_ROUTING))); + + /* Hooks should be first five */ + assert(h->info.hook_entry[NF_IP_PRE_ROUTING] == 0); + + n = get_chain_end(h, 0); + + if (h->info.valid_hooks & (1 << NF_IP_LOCAL_IN)) { + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_LOCAL_IN] == n); + n = get_chain_end(h, n); + } + + if (h->info.valid_hooks & (1 << NF_IP_FORWARD)) { + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_FORWARD] == n); + n = get_chain_end(h, n); + } + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n); + user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT]; + + if (h->info.valid_hooks & (1 << NF_IP_POST_ROUTING)) { + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_POST_ROUTING] == n); + user_offset = h->info.hook_entry[NF_IP_POST_ROUTING]; + } + +#ifdef NF_IP_DROPPING + } else if (strcmp(h->info.name, "drop") == 0) { + assert(h->info.valid_hooks == (1 << NF_IP_DROPPING)); + + /* Hook should be first */ + assert(h->info.hook_entry[NF_IP_DROPPING] == 0); + user_offset = 0; +#endif + } else { + fprintf(stderr, "Unknown table `%s'\n", h->info.name); + abort(); + } + + /* User chain == end of last builtin + policy entry */ + user_offset = get_chain_end(h, user_offset); + user_offset += get_entry(h, user_offset)->next_offset; + + /* Overflows should be end of entry chains, and unconditional + policy nodes. */ + for (i = 0; i < NUMHOOKS; i++) { + STRUCT_ENTRY *e; + STRUCT_STANDARD_TARGET *t; + + if (!(h->info.valid_hooks & (1 << i))) + continue; + assert(h->info.underflow[i] + == get_chain_end(h, h->info.hook_entry[i])); + + e = get_entry(h, get_chain_end(h, h->info.hook_entry[i])); + assert(unconditional(&e->ip)); + assert(e->target_offset == sizeof(*e)); + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + assert(t->target.u.target_size == ALIGN(sizeof(*t))); + assert(e->next_offset == sizeof(*e) + ALIGN(sizeof(*t))); + + assert(strcmp(t->target.u.user.name, STANDARD_TARGET)==0); + assert(t->verdict == -NF_DROP-1 || t->verdict == -NF_ACCEPT-1); + + /* Hooks and underflows must be valid entries */ + entry2index(h, get_entry(h, h->info.hook_entry[i])); + entry2index(h, get_entry(h, h->info.underflow[i])); + } + + assert(h->info.size + >= h->info.num_entries * (sizeof(STRUCT_ENTRY) + +sizeof(STRUCT_STANDARD_TARGET))); + + assert(h->entries.size + >= (h->new_number + * (sizeof(STRUCT_ENTRY) + + sizeof(STRUCT_STANDARD_TARGET)))); + assert(strcmp(h->info.name, h->entries.name) == 0); + + i = 0; n = 0; + was_return = 0; + /* Check all the entries. */ + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + check_entry, &i, &n, user_offset, &was_return, h); + + assert(i == h->new_number); + assert(n == h->entries.size); + + /* Final entry must be error node */ + assert(strcmp(GET_TARGET(index2entry(h, h->new_number-1)) + ->u.user.name, + ERROR_TARGET) == 0); +} +#endif /*IPTC_DEBUG*/ diff --git a/libiptc2/libip6tc.c b/libiptc2/libip6tc.c new file mode 100644 index 0000000..9a78a5a --- /dev/null +++ b/libiptc2/libip6tc.c @@ -0,0 +1,449 @@ +/* Library which manipulates firewall rules. Version 0.1. */ + +/* Architecture of firewall rules is as follows: + * + * Chains go INPUT, FORWARD, OUTPUT then user chains. + * Each user chain starts with an ERROR node. + * Every chain ends with an unconditional jump: a RETURN for user chains, + * and a POLICY for built-ins. + */ + +/* (C)1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See + COPYING for details). */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG_CONNTRACK +#define inline +#endif + +#if !defined(__GLIBC__) || (__GLIBC__ < 2) +typedef unsigned int socklen_t; +#endif + +#include "libiptc/libip6tc.h" + +#define HOOK_PRE_ROUTING NF_IP6_PRE_ROUTING +#define HOOK_LOCAL_IN NF_IP6_LOCAL_IN +#define HOOK_FORWARD NF_IP6_FORWARD +#define HOOK_LOCAL_OUT NF_IP6_LOCAL_OUT +#define HOOK_POST_ROUTING NF_IP6_POST_ROUTING + +#define STRUCT_ENTRY_TARGET struct ip6t_entry_target +#define STRUCT_ENTRY struct ip6t_entry +#define STRUCT_ENTRY_MATCH struct ip6t_entry_match +#define STRUCT_GETINFO struct ip6t_getinfo +#define STRUCT_GET_ENTRIES struct ip6t_get_entries +#define STRUCT_COUNTERS struct ip6t_counters +#define STRUCT_COUNTERS_INFO struct ip6t_counters_info +#define STRUCT_STANDARD_TARGET struct ip6t_standard_target +#define STRUCT_REPLACE struct ip6t_replace + +#define STRUCT_TC_HANDLE struct ip6tc_handle +#define TC_HANDLE_T ip6tc_handle_t + +#define ENTRY_ITERATE IP6T_ENTRY_ITERATE +#define TABLE_MAXNAMELEN IP6T_TABLE_MAXNAMELEN +#define FUNCTION_MAXNAMELEN IP6T_FUNCTION_MAXNAMELEN + +#define GET_TARGET ip6t_get_target + +#define ERROR_TARGET IP6T_ERROR_TARGET +#define NUMHOOKS NF_IP6_NUMHOOKS + +#define IPT_CHAINLABEL ip6t_chainlabel + +#define TC_DUMP_ENTRIES dump_entries6 +#define TC_IS_CHAIN ip6tc_is_chain +#define TC_FIRST_CHAIN ip6tc_first_chain +#define TC_NEXT_CHAIN ip6tc_next_chain +#define TC_FIRST_RULE ip6tc_first_rule +#define TC_NEXT_RULE ip6tc_next_rule +#define TC_GET_TARGET ip6tc_get_target +#define TC_BUILTIN ip6tc_builtin +#define TC_GET_POLICY ip6tc_get_policy +#define TC_INSERT_ENTRY ip6tc_insert_entry +#define TC_REPLACE_ENTRY ip6tc_replace_entry +#define TC_APPEND_ENTRY ip6tc_append_entry +#define TC_DELETE_ENTRY ip6tc_delete_entry +#define TC_DELETE_NUM_ENTRY ip6tc_delete_num_entry +#define TC_CHECK_PACKET ip6tc_check_packet +#define TC_FLUSH_ENTRIES ip6tc_flush_entries +#define TC_ZERO_ENTRIES ip6tc_zero_entries +#define TC_ZERO_COUNTER ip6tc_zero_counter +#define TC_READ_COUNTER ip6tc_read_counter +#define TC_SET_COUNTER ip6tc_set_counter +#define TC_CREATE_CHAIN ip6tc_create_chain +#define TC_GET_REFERENCES ip6tc_get_references +#define TC_DELETE_CHAIN ip6tc_delete_chain +#define TC_RENAME_CHAIN ip6tc_rename_chain +#define TC_SET_POLICY ip6tc_set_policy +#define TC_GET_RAW_SOCKET ip6tc_get_raw_socket +#define TC_INIT ip6tc_init +#define TC_FREE ip6tc_free +#define TC_COMMIT ip6tc_commit +#define TC_STRERROR ip6tc_strerror + +#define TC_AF AF_INET6 +#define TC_IPPROTO IPPROTO_IPV6 + +#define SO_SET_REPLACE IP6T_SO_SET_REPLACE +#define SO_SET_ADD_COUNTERS IP6T_SO_SET_ADD_COUNTERS +#define SO_GET_INFO IP6T_SO_GET_INFO +#define SO_GET_ENTRIES IP6T_SO_GET_ENTRIES +#define SO_GET_VERSION IP6T_SO_GET_VERSION + +#define STANDARD_TARGET IP6T_STANDARD_TARGET +#define LABEL_RETURN IP6TC_LABEL_RETURN +#define LABEL_ACCEPT IP6TC_LABEL_ACCEPT +#define LABEL_DROP IP6TC_LABEL_DROP +#define LABEL_QUEUE IP6TC_LABEL_QUEUE + +#define ALIGN IP6T_ALIGN +#define RETURN IP6T_RETURN + +#include "libiptc.c" + +#define BIT6(a, l) \ + ((ntohl(a->in6_u.u6_addr32[(l) / 32]) >> (31 - ((l) & 31))) & 1) + +int +ipv6_prefix_length(const struct in6_addr *a) +{ + int l, i; + for (l = 0; l < 128; l++) { + if (BIT6(a, l) == 0) + break; + } + for (i = l + 1; i < 128; i++) { + if (BIT6(a, i) == 1) + return -1; + } + return l; +} + +static int +dump_entry(struct ip6t_entry *e, const ip6tc_handle_t handle) +{ + size_t i; + char buf[40]; + int len; + struct ip6t_entry_target *t; + + printf("Entry %u (%lu):\n", entry2index(handle, e), + entry2offset(handle, e)); + puts("SRC IP: "); + inet_ntop(AF_INET6, &e->ipv6.src, buf, sizeof buf); + puts(buf); + putchar('/'); + len = ipv6_prefix_length(&e->ipv6.smsk); + if (len != -1) + printf("%d", len); + else { + inet_ntop(AF_INET6, &e->ipv6.smsk, buf, sizeof buf); + puts(buf); + } + putchar('\n'); + + puts("DST IP: "); + inet_ntop(AF_INET6, &e->ipv6.dst, buf, sizeof buf); + puts(buf); + putchar('/'); + len = ipv6_prefix_length(&e->ipv6.dmsk); + if (len != -1) + printf("%d", len); + else { + inet_ntop(AF_INET6, &e->ipv6.dmsk, buf, sizeof buf); + puts(buf); + } + putchar('\n'); + + printf("Interface: `%s'/", e->ipv6.iniface); + for (i = 0; i < IFNAMSIZ; i++) + printf("%c", e->ipv6.iniface_mask[i] ? 'X' : '.'); + printf("to `%s'/", e->ipv6.outiface); + for (i = 0; i < IFNAMSIZ; i++) + printf("%c", e->ipv6.outiface_mask[i] ? 'X' : '.'); + printf("\nProtocol: %u\n", e->ipv6.proto); + if (e->ipv6.flags & IP6T_F_TOS) + printf("TOS: %u\n", e->ipv6.tos); + printf("Flags: %02X\n", e->ipv6.flags); + printf("Invflags: %02X\n", e->ipv6.invflags); + printf("Counters: %llu packets, %llu bytes\n", + e->counters.pcnt, e->counters.bcnt); + printf("Cache: %08X ", e->nfcache); + if (e->nfcache & NFC_ALTERED) printf("ALTERED "); + if (e->nfcache & NFC_UNKNOWN) printf("UNKNOWN "); + if (e->nfcache & NFC_IP6_SRC) printf("IP6_SRC "); + if (e->nfcache & NFC_IP6_DST) printf("IP6_DST "); + if (e->nfcache & NFC_IP6_IF_IN) printf("IP6_IF_IN "); + if (e->nfcache & NFC_IP6_IF_OUT) printf("IP6_IF_OUT "); + if (e->nfcache & NFC_IP6_TOS) printf("IP6_TOS "); + if (e->nfcache & NFC_IP6_PROTO) printf("IP6_PROTO "); + if (e->nfcache & NFC_IP6_OPTIONS) printf("IP6_OPTIONS "); + if (e->nfcache & NFC_IP6_TCPFLAGS) printf("IP6_TCPFLAGS "); + if (e->nfcache & NFC_IP6_SRC_PT) printf("IP6_SRC_PT "); + if (e->nfcache & NFC_IP6_DST_PT) printf("IP6_DST_PT "); + if (e->nfcache & NFC_IP6_PROTO_UNKNOWN) printf("IP6_PROTO_UNKNOWN "); + printf("\n"); + + IP6T_MATCH_ITERATE(e, print_match); + + t = ip6t_get_target(e); + printf("Target name: `%s' [%u]\n", t->u.user.name, t->u.target_size); + if (strcmp(t->u.user.name, IP6T_STANDARD_TARGET) == 0) { + int pos = *(int *)t->data; + if (pos < 0) + printf("verdict=%s\n", + pos == -NF_ACCEPT-1 ? "NF_ACCEPT" + : pos == -NF_DROP-1 ? "NF_DROP" + : pos == IP6T_RETURN ? "RETURN" + : "UNKNOWN"); + else + printf("verdict=%u\n", pos); + } else if (strcmp(t->u.user.name, IP6T_ERROR_TARGET) == 0) + printf("error=`%s'\n", t->data); + + printf("\n"); + return 0; +} + +static int +is_same(const STRUCT_ENTRY *a, const STRUCT_ENTRY *b, + unsigned char *matchmask) +{ + unsigned int i; + STRUCT_ENTRY_TARGET *ta, *tb; + unsigned char *mptr; + + /* Always compare head structures: ignore mask here. */ + if (memcmp(&a->ipv6.src, &b->ipv6.src, sizeof(struct in6_addr)) + || memcmp(&a->ipv6.dst, &b->ipv6.dst, sizeof(struct in6_addr)) + || memcmp(&a->ipv6.smsk, &b->ipv6.smsk, sizeof(struct in6_addr)) + || memcmp(&a->ipv6.dmsk, &b->ipv6.dmsk, sizeof(struct in6_addr)) + || a->ipv6.proto != b->ipv6.proto + || a->ipv6.tos != b->ipv6.tos + || a->ipv6.flags != b->ipv6.flags + || a->ipv6.invflags != b->ipv6.invflags) + return 0; + + for (i = 0; i < IFNAMSIZ; i++) { + if (a->ipv6.iniface_mask[i] != b->ipv6.iniface_mask[i]) + return 0; + if ((a->ipv6.iniface[i] & a->ipv6.iniface_mask[i]) + != (b->ipv6.iniface[i] & b->ipv6.iniface_mask[i])) + return 0; + if (a->ipv6.outiface_mask[i] != b->ipv6.outiface_mask[i]) + return 0; + if ((a->ipv6.outiface[i] & a->ipv6.outiface_mask[i]) + != (b->ipv6.outiface[i] & b->ipv6.outiface_mask[i])) + return 0; + } + + if (a->nfcache != b->nfcache + || a->target_offset != b->target_offset + || a->next_offset != b->next_offset) + return 0; + + mptr = matchmask + sizeof(STRUCT_ENTRY); + if (IP6T_MATCH_ITERATE(a, match_different, a->elems, b->elems, &mptr)) + return 0; + + ta = GET_TARGET((STRUCT_ENTRY *)a); + tb = GET_TARGET((STRUCT_ENTRY *)b); + if (ta->u.target_size != tb->u.target_size) + return 0; + if (strcmp(ta->u.user.name, tb->u.user.name) != 0) + return 0; + mptr += sizeof(*ta); + + if (target_different(ta->data, tb->data, + ta->u.target_size - sizeof(*ta), mptr)) + return 0; + + return 1; +} + +/* All zeroes == unconditional rule. */ +static inline int +unconditional(const struct ip6t_ip6 *ipv6) +{ + unsigned int i; + + for (i = 0; i < sizeof(*ipv6); i++) + if (((char *)ipv6)[i]) + break; + + return (i == sizeof(*ipv6)); +} + +#ifdef IPTC_DEBUG +/* Do every conceivable sanity check on the handle */ +static void +do_check(TC_HANDLE_T h, unsigned int line) +{ + unsigned int i, n; + unsigned int user_offset; /* Offset of first user chain */ + int was_return; + + assert(h->changed == 0 || h->changed == 1); + if (strcmp(h->info.name, "filter") == 0) { + assert(h->info.valid_hooks + == (1 << NF_IP6_LOCAL_IN + | 1 << NF_IP6_FORWARD + | 1 << NF_IP6_LOCAL_OUT)); + + /* Hooks should be first three */ + assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == 0); + + n = get_chain_end(h, 0); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_FORWARD] == n); + + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n); + + user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT]; + } else if (strcmp(h->info.name, "nat") == 0) { + assert((h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_OUT + | 1 << NF_IP6_POST_ROUTING)) || + (h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_IN + | 1 << NF_IP6_LOCAL_OUT + | 1 << NF_IP6_POST_ROUTING))); + + assert(h->info.hook_entry[NF_IP6_PRE_ROUTING] == 0); + + n = get_chain_end(h, 0); + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_POST_ROUTING] == n); + n = get_chain_end(h, n); + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n); + user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT]; + + if (h->info.valid_hooks & (1 << NF_IP6_LOCAL_IN)) { + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == n); + user_offset = h->info.hook_entry[NF_IP6_LOCAL_IN]; + } + + } else if (strcmp(h->info.name, "mangle") == 0) { + /* This code is getting ugly because linux < 2.4.18-pre6 had + * two mangle hooks, linux >= 2.4.18-pre6 has five mangle hooks + * */ + assert((h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_OUT)) || + (h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_IN + | 1 << NF_IP6_FORWARD + | 1 << NF_IP6_LOCAL_OUT + | 1 << NF_IP6_POST_ROUTING))); + + /* Hooks should be first five */ + assert(h->info.hook_entry[NF_IP6_PRE_ROUTING] == 0); + + n = get_chain_end(h, 0); + + if (h->info.valid_hooks & (1 << NF_IP6_LOCAL_IN)) { + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == n); + n = get_chain_end(h, n); + } + + if (h->info.valid_hooks & (1 << NF_IP6_FORWARD)) { + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_FORWARD] == n); + n = get_chain_end(h, n); + } + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n); + user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT]; + + if (h->info.valid_hooks & (1 << NF_IP6_POST_ROUTING)) { + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_POST_ROUTING] == n); + user_offset = h->info.hook_entry[NF_IP6_POST_ROUTING]; + } + } else { + fprintf(stderr, "Unknown table `%s'\n", h->info.name); + abort(); + } + + /* User chain == end of last builtin + policy entry */ + user_offset = get_chain_end(h, user_offset); + user_offset += get_entry(h, user_offset)->next_offset; + + /* Overflows should be end of entry chains, and unconditional + policy nodes. */ + for (i = 0; i < NUMHOOKS; i++) { + STRUCT_ENTRY *e; + STRUCT_STANDARD_TARGET *t; + + if (!(h->info.valid_hooks & (1 << i))) + continue; + assert(h->info.underflow[i] + == get_chain_end(h, h->info.hook_entry[i])); + + e = get_entry(h, get_chain_end(h, h->info.hook_entry[i])); + assert(unconditional(&e->ipv6)); + assert(e->target_offset == sizeof(*e)); + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + printf("target_size=%u, align=%u\n", + t->target.u.target_size, ALIGN(sizeof(*t))); + assert(t->target.u.target_size == ALIGN(sizeof(*t))); + assert(e->next_offset == sizeof(*e) + ALIGN(sizeof(*t))); + + assert(strcmp(t->target.u.user.name, STANDARD_TARGET)==0); + assert(t->verdict == -NF_DROP-1 || t->verdict == -NF_ACCEPT-1); + + /* Hooks and underflows must be valid entries */ + entry2index(h, get_entry(h, h->info.hook_entry[i])); + entry2index(h, get_entry(h, h->info.underflow[i])); + } + + assert(h->info.size + >= h->info.num_entries * (sizeof(STRUCT_ENTRY) + +sizeof(STRUCT_STANDARD_TARGET))); + + assert(h->entries.size + >= (h->new_number + * (sizeof(STRUCT_ENTRY) + + sizeof(STRUCT_STANDARD_TARGET)))); + assert(strcmp(h->info.name, h->entries.name) == 0); + + i = 0; n = 0; + was_return = 0; + +#if 0 + /* Check all the entries. */ + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + check_entry, &i, &n, user_offset, &was_return, h); + + assert(i == h->new_number); + assert(n == h->entries.size); + + /* Final entry must be error node */ + assert(strcmp(GET_TARGET(index2entry(h, h->new_number-1)) + ->u.user.name, + ERROR_TARGET) == 0); +#endif +} +#endif /*IPTC_DEBUG*/ diff --git a/libiptc2/libiptc.c b/libiptc2/libiptc.c new file mode 100644 index 0000000..3c3c76a --- /dev/null +++ b/libiptc2/libiptc.c @@ -0,0 +1,1830 @@ +/* Library which manipulates firewall rules. Version $Revision: 1.41 $ */ + +/* Architecture of firewall rules is as follows: + * + * Chains go INPUT, FORWARD, OUTPUT then user chains. + * Each user chain starts with an ERROR node. + * Every chain ends with an unconditional jump: a RETURN for user chains, + * and a POLICY for built-ins. + */ + +/* (C) 1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See + * COPYING for details). + * (C) 2000-2003 by the Netfilter Core Team + * + * 2003-Jun-20: Harald Welte : + * - Reimplementation of chain cache to use offsets instead of entries + * 2003-Jun-23: Harald Welte : + * - speed optimization, sponsored by Astaro AG (http://www.astaro.com/) + * don't rebuild the chain cache after every operation, instead fix it + * up after a ruleset change. + * 2003-Jun-30: Harald Welte : + * - reimplementation from scratch. *sigh*. I hope nobody has to touch + * this code ever again. + */ +#include "linux_listhelp.h" + +#ifndef IPT_LIB_DIR +#define IPT_LIB_DIR "/usr/lib/iptables" +#endif + +static int sockfd = -1; +static void *iptc_fn = NULL; + +static const char *hooknames[] += { [HOOK_PRE_ROUTING] "PREROUTING", + [HOOK_LOCAL_IN] "INPUT", + [HOOK_FORWARD] "FORWARD", + [HOOK_LOCAL_OUT] "OUTPUT", + [HOOK_POST_ROUTING] "POSTROUTING", +#ifdef HOOK_DROPPING + [HOOK_DROPPING] "DROPPING" +#endif +}; + +struct counter_map +{ + enum { + COUNTER_MAP_NOMAP, + COUNTER_MAP_NORMAL_MAP, + COUNTER_MAP_ZEROED, + COUNTER_MAP_SET + } maptype; + unsigned int mappos; +}; + +/* Convenience structures */ +struct ipt_error_target +{ + STRUCT_ENTRY_TARGET t; + char error[TABLE_MAXNAMELEN]; +}; + +struct rule_head +{ + struct list_head list; /* list of rules in chain */ + + struct chain_head *chain; /* we're part of this chain */ + + struct chain_head *jumpto; /* target of this rule, in case + it is a jump rule */ + + struct counter_map counter_map; + + unsigned int size; /* size of rule */ + STRUCT_ENTRY *entry_blob; /* pointer to entry in blob */ + STRUCT_ENTRY entry[0]; +}; + +struct chain_head +{ + struct list_head list; + + char name[TABLE_MAXNAMELEN]; + unsigned int hooknum; + struct list_head rules; + struct rule_head *firstrule; /* first (ERROR) rule */ + struct rule_head *lastrule; /* last (RETURN) rule */ +}; + +STRUCT_TC_HANDLE +{ + /* Have changes been made? */ + int changed; + + /* linked list of chains in this table */ + struct list_head chains; + + /* current position of first_chain() / next_chain() */ + struct chain_head *chain_iterator_cur; + + /* current position of first_rule() / next_rule() */ + struct rule_head *rule_iterator_cur; + + /* the structure we receive from getsockopt() */ + STRUCT_GETINFO info; + + /* Array of hook names */ + const char **hooknames; +#if 0 + /* Size in here reflects original state. */ + + + /* Cached position of chain heads (NULL = no cache). */ + unsigned int cache_num_chains; + unsigned int cache_num_builtins; + struct chain_cache *cache_chain_heads; + + /* Chain iterator: current chain cache entry. */ + struct chain_cache *cache_chain_iteration; + + /* Rule iterator: terminal rule */ + STRUCT_ENTRY *cache_rule_end; + + /* Number in here reflects current state. */ + unsigned int new_number; +#endif + STRUCT_GET_ENTRIES entries; +}; + +static void +set_changed(TC_HANDLE_T h) +{ + h->changed = 1; +} + +#ifdef IPTC_DEBUG +static void do_check(TC_HANDLE_T h, unsigned int line); +#define CHECK(h) do { if (!getenv("IPTC_NO_CHECK")) do_check((h), __LINE__); } while(0) +#else +#define CHECK(h) +#endif + +static struct rule_head *ruleh_alloc(unsigned int size) +{ + struct rule_head *ruleh = malloc(sizeof(*ruleh)+size); + if (!ruleh) + return NULL; + + memset(ruleh, 0, sizeof(*ruleh)+size); + ruleh->size = size; + + return ruleh; +} + +static void ruleh_free(struct rule_head *ruleh) +{ + list_del(&ruleh->list); + free(ruleh); +} + +static struct chain_head *chainh_alloc(TC_HANDLE_T h, const char *name) +{ + struct chain_head *chainh = malloc(sizeof(*chainh)); + if (!chainh) + return NULL; + + memset(chainh, 0, sizeof(*chainh)); + strncpy(chainh->name, name, sizeof(&chainh->name)); + list_append(&chainh->list, &h->chains); + + return chainh; +} + +static void +chainh_clean(struct chain_head *chainh) +{ + /* FIXME */ + struct list_head *cur_item, *item2; + + list_for_each_safe(cur_item, item2, &chainh->rules) { + struct rule_head *ruleh = list_entry(cur_item, + struct rule_head, + list); + ruleh_free(ruleh); + } +} + +static void +chainh_free(struct chain_head *chainh) +{ + chainh_clean(chainh); + list_del(&chainh->list); +} + +static struct chain_head * +chainh_find(TC_HANDLE_T h, const IPT_CHAINLABEL name) +{ + struct list_head *cur; + + list_for_each(cur, &h->chains) { + struct chain_head *ch = list_entry(cur, struct chain_head, + list); + if (!strcmp(name, ch->name)) + return ch; + } + return NULL; +} + +/* Returns chain head if found, otherwise NULL. */ +static struct chain_head * +find_label(const char *name, TC_HANDLE_T handle) +{ + return chainh_find(handle, name); +} + + +/* + * functions that directly operate on the blob + */ + +static inline unsigned long +entry2offset(const TC_HANDLE_T h, const STRUCT_ENTRY *e) +{ + return (void *)e - (void *)h->entries.entrytable; +} + +static inline STRUCT_ENTRY * +get_entry(TC_HANDLE_T h, unsigned int offset) +{ + return (STRUCT_ENTRY *)((char *)h->entries.entrytable + offset); +} + +/* needed by entry2index */ +static inline int +get_number(const STRUCT_ENTRY *i, + const STRUCT_ENTRY *seek, + unsigned int *pos) +{ + if (i == seek) + return 1; + (*pos)++; + return 0; +} + +static unsigned int +entry2index(const TC_HANDLE_T h, const STRUCT_ENTRY *seek) +{ + unsigned int pos = 0; + + if (ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + get_number, seek, &pos) == 0) { + fprintf(stderr, "ERROR: offset %i not an entry!\n", + (char *)seek - (char *)h->entries.entrytable); + abort(); + } + return pos; +} + +static inline int +get_entry_n(STRUCT_ENTRY *i, + unsigned int number, + unsigned int *pos, + STRUCT_ENTRY **pe) +{ + if (*pos == number) { + *pe = i; + return 1; + } + (*pos)++; + return 0; +} + +static STRUCT_ENTRY * +index2entry(TC_HANDLE_T h, unsigned int index) +{ + unsigned int pos = 0; + STRUCT_ENTRY *ret = NULL; + + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + get_entry_n, index, &pos, &ret); + + return ret; +} + +static inline unsigned long +index2offset(TC_HANDLE_T h, unsigned int index) +{ + return entry2offset(h, index2entry(h, index)); +} + +static char * +get_errorlabel(TC_HANDLE_T h, unsigned int offset) +{ + STRUCT_ENTRY *e; + + e = get_entry(h, offset); + if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) != 0) { + fprintf(stderr, "ERROR: offset %u not an error node!\n", + offset); + abort(); + } + + return (char *)GET_TARGET(e)->data; +} + +#if 0 +static inline STRUCT_ENTRY * +offset2entry(TC_HANDLE_T h, unsigned int offset) +{ + return (STRUCT_ENTRY *) ((void *)h->entries.entrytable+offset); +} + +static inline unsigned int +offset2index(const TC_HANDLE_T h, unsigned int offset) +{ + return entry2index(h, offset2entry(h, offset)); +} + + +#endif + +/* Allocate handle of given size */ +static TC_HANDLE_T +alloc_tc_handle(const char *tablename, unsigned int size, + unsigned int num_rules) +{ + size_t len; + TC_HANDLE_T h; + + len = sizeof(STRUCT_TC_HANDLE) + + size + + num_rules * sizeof(struct counter_map); + + if ((h = malloc(len)) == NULL) { + errno = ENOMEM; + return NULL; + } + + h->changed = 0; + + strcpy(h->info.name, tablename); + strcpy(h->entries.name, tablename); + INIT_LIST_HEAD(&h->chains); + + return h; +} + +/* get the name of the chain that we jump to */ +static char * +parse_jumptarget(const STRUCT_ENTRY *e, TC_HANDLE_T h) +{ + STRUCT_ENTRY *jumpto; + int spos, labelidx; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) != 0) { + /* called for non-standard target */ + return "__FIXME"; + } + /* Standard target: evaluate */ + spos = *(int *)GET_TARGET(e)->data; + if (spos < 0) { + return "__FIXME"; + } + + jumpto = get_entry(h, spos); + + /* Fall through rule */ + if (jumpto == (void *)e + e->next_offset) + return ""; + + /* Must point to head of a chain: ie. after error rule */ + /* FIXME: this needs to deal with internal jump targets */ + labelidx = entry2index(h, jumpto) - 1; + return get_errorlabel(h, index2offset(h, labelidx)); +} + +/* parser functions */ + +struct rule_head * +append_entrycopy(const STRUCT_ENTRY *e, struct rule_head *prev) +{ + struct rule_head *ruleh = ruleh_alloc(e->next_offset); + if (!ruleh) + return NULL; + + memcpy(&ruleh->entry, e, e->next_offset); + ruleh->chain = prev->chain; + ruleh->entry_blob = e; + list_append(&ruleh->list, &prev->list); + + return ruleh; +} + +/* have to return 0 on success, bcf ENTRY_ITERATE */ +static inline int +parse_entry(const STRUCT_ENTRY *e, TC_HANDLE_T h, struct chain_head **curchain) +{ + int i; + union tgt_u { + STRUCT_ENTRY_TARGET ent; + STRUCT_STANDARD_TARGET std; + struct ipt_error_target err; + } *tgt; + + struct rule_head *lastrule = list_entry((*curchain)->rules.prev, + struct rule_head, list); + struct rule_head *newrule; + + tgt = (union tgt_u *) GET_TARGET(e); + + if (e->target_offset == sizeof(STRUCT_ENTRY) + && (strcmp(tgt->ent.u.user.name, IPT_STANDARD_TARGET) == 0)) { + /* jump to somewhere else */ + char *targname; + struct chain_head *chainh; + + newrule = append_entrycopy(e, lastrule); + + targname = parse_jumptarget(e, h); + if (!(chainh = find_label(targname, h))) { + chainh = chainh_alloc(h, targname); + } + if (!chainh) { + errno = ENOMEM; + return 1; + } + newrule->jumpto = chainh; + + } else if (e->target_offset == sizeof(STRUCT_ENTRY) + && e->next_offset == sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(struct ipt_error_target)) + && !strcmp(tgt->ent.u.user.name, ERROR_TARGET)) { + /* chain head */ + *curchain = chainh_find(h, tgt->err.error); + if (!(*curchain)) { + *curchain = chainh_alloc(h, tgt->err.error); + /* FIXME: error handling */ + } + newrule = append_entrycopy(e, lastrule); + (*curchain)->firstrule = newrule; + + } else if (e->target_offset == sizeof(STRUCT_ENTRY) + && e->next_offset == sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(STRUCT_STANDARD_TARGET)) + && tgt->std.verdict == RETURN) { + /* chain end */ + newrule = append_entrycopy(e, lastrule); + (*curchain)->lastrule = newrule; + *curchain = NULL; + } else { + /* normal rule */ + newrule = append_entrycopy(e, lastrule); + } + + /* create counter map entry */ + newrule->counter_map.maptype = COUNTER_MAP_NORMAL_MAP; + newrule->counter_map.mappos = entry2index(h, e); + + /* iterate over hook_entries, needed to connect builtin + * chains with hook numbers */ + for (i = 0; i < NUMHOOKS; i++) { + if (!(h->info.valid_hooks & (1 << i))) + continue; + if (h->info.hook_entry[i] == entry2offset(h, e)) { + /* found hook entry point */ + if (*curchain) + (*curchain)->hooknum = i; + } + if (h->info.underflow[i] == entry2offset(h, e)) { + /* found underflow point */ + } + } + + return 0; +} + +static int parse_ruleset(TC_HANDLE_T h) +{ + struct chain_head *curchain; + + /* iterate over ruleset; create linked list of rule_head/chain_head */ + if (ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + parse_entry, h, &curchain)) { + /* some error happened while iterating */ + return 0; + } + + return 1; +} + +TC_HANDLE_T +TC_INIT(const char *tablename) +{ + TC_HANDLE_T h; + STRUCT_GETINFO info; + int tmp; + socklen_t s; + + iptc_fn = TC_INIT; + + if (sockfd != -1) { + close(sockfd); + sockfd = -1; + } + + if (strlen(tablename) >= TABLE_MAXNAMELEN) { + errno = EINVAL; + return NULL; + } + + sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW); + if (sockfd < 0) + return NULL; + + s = sizeof(info); + + strcpy(info.name, tablename); + if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0) + return NULL; + + if ((h = alloc_tc_handle(info.name, info.size, info.num_entries)) + == NULL) { + close(sockfd); + sockfd = -1; + return NULL; + } + +/* Too hard --RR */ +#if 0 + sprintf(pathname, "%s/%s", IPT_LIB_DIR, info.name); + dynlib = dlopen(pathname, RTLD_NOW); + if (!dynlib) { + errno = ENOENT; + return NULL; + } + h->hooknames = dlsym(dynlib, "hooknames"); + if (!h->hooknames) { + errno = ENOENT; + return NULL; + } +#else + h->hooknames = hooknames; +#endif + + /* Initialize current state */ + h->info = info; + //h->new_number = h->info.num_entries; + // + h->entries.size = h->info.size; + + tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size; + + if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries, + &tmp) < 0) { + close(sockfd); + sockfd = -1; + free(h); + return NULL; + } + + CHECK(h); + parse_ruleset(h); + + return h; +} + +void +TC_FREE(TC_HANDLE_T *h) +{ + struct list_head *cur_item, *item2; + + close(sockfd); + sockfd = -1; + + /* free all chains */ + list_for_each_safe(cur_item, item2, &(*h)->chains) { + struct chain_head *chead = list_entry(cur_item, + struct chain_head, + list); + chainh_free(chead); + } + + /* FIXME: free all other ressources we might be using */ + + free(*h); + *h = NULL; +} + +static inline int +print_match(const STRUCT_ENTRY_MATCH *m) +{ + printf("Match name: `%s'\n", m->u.user.name); + return 0; +} + +static int dump_entry(STRUCT_ENTRY *e, const TC_HANDLE_T handle); + +#if 0 +void +TC_DUMP_ENTRIES(const TC_HANDLE_T handle) +{ + CHECK(handle); + + printf("libiptc v%s. %u entries, %u bytes.\n", + IPTABLES_VERSION, + handle->new_number, handle->entries.size); + printf("Table `%s'\n", handle->info.name); + printf("Hooks: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.hook_entry[HOOK_PRE_ROUTING], + handle->info.hook_entry[HOOK_LOCAL_IN], + handle->info.hook_entry[HOOK_FORWARD], + handle->info.hook_entry[HOOK_LOCAL_OUT], + handle->info.hook_entry[HOOK_POST_ROUTING]); + printf("Underflows: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.underflow[HOOK_PRE_ROUTING], + handle->info.underflow[HOOK_LOCAL_IN], + handle->info.underflow[HOOK_FORWARD], + handle->info.underflow[HOOK_LOCAL_OUT], + handle->info.underflow[HOOK_POST_ROUTING]); + + ENTRY_ITERATE(handle->entries.entrytable, handle->entries.size, + dump_entry, handle); +} + +/* Returns 0 if not hook entry, else hooknumber + 1 */ +static inline unsigned int +is_hook_entry(STRUCT_ENTRY *e, TC_HANDLE_T h) +{ + unsigned int i; + + for (i = 0; i < NUMHOOKS; i++) { + if ((h->info.valid_hooks & (1 << i)) + && get_entry(h, h->info.hook_entry[i]) == e) + return i+1; + } + return 0; +} + + +static int alphasort(const void *a, const void *b) +{ + return strcmp(((struct chain_cache *)a)->name, + ((struct chain_cache *)b)->name); +} +#endif + +/* Does this chain exist? */ +int TC_IS_CHAIN(const char *chain, const TC_HANDLE_T handle) +{ + return find_label(chain, handle) != NULL; +} + +#if 0 +/* Returns the position of the final (ie. unconditional) element. */ +static unsigned int +get_chain_end(const TC_HANDLE_T handle, unsigned int start) +{ + unsigned int last_off, off; + STRUCT_ENTRY *e; + + last_off = start; + e = get_entry(handle, start); + + /* Terminate when we meet a error label or a hook entry. */ + for (off = start + e->next_offset; + off < handle->entries.size; + last_off = off, off += e->next_offset) { + STRUCT_ENTRY_TARGET *t; + unsigned int i; + + e = get_entry(handle, off); + + /* We hit an entry point. */ + for (i = 0; i < NUMHOOKS; i++) { + if ((handle->info.valid_hooks & (1 << i)) + && off == handle->info.hook_entry[i]) + return last_off; + } + + /* We hit a user chain label */ + t = GET_TARGET(e); + if (strcmp(t->u.user.name, ERROR_TARGET) == 0) + return last_off; + } + /* SHOULD NEVER HAPPEN */ + fprintf(stderr, "ERROR: Off end (%u) of chain from %u!\n", + handle->entries.size, off); + abort(); +} +#endif + +/* Iterator functions to run through the chains. */ +const char * +TC_FIRST_CHAIN(TC_HANDLE_T *handle) +{ + struct chain_head *firsthead = list_entry((*handle)->chains.next, + struct chain_head, list); + (*handle)->chain_iterator_cur = firsthead; + + return firsthead->name; +} + +/* Iterator functions to run through the chains. Returns NULL at end. */ +const char * +TC_NEXT_CHAIN(TC_HANDLE_T *handle) +{ + struct chain_head *next = list_entry(&(*handle)->chain_iterator_cur->list.next, struct chain_head, list); + (*handle)->chain_iterator_cur = next; + + if (&next->list == &(*handle)->chains) + return NULL; + + return next->name; +} + +/* Get first rule in the given chain: NULL for empty chain. */ +const STRUCT_ENTRY * +TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct rule_head *r; + + c = find_label(chain, *handle); + if (!c) { + errno = ENOENT; + return NULL; + } + + /* Empty chain: single return/policy rule */ + if (list_empty(&c->rules)) + return NULL; + + r = list_entry(c->rules.next, struct rule_head, list); + (*handle)->rule_iterator_cur = r; + + return r->entry; +} + +/* Returns NULL when rules run out. */ +const STRUCT_ENTRY * +TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle) +{ + struct rule_head *r = list_entry((*handle)->rule_iterator_cur->list.next, struct rule_head, list); + + if (&r->list == &r->chain->rules) + return NULL; + + /* NOTE: prev is without any influence ! */ + return r->entry; +} + +#if 0 +/* How many rules in this chain? */ +unsigned int +TC_NUM_RULES(const char *chain, TC_HANDLE_T *handle) +{ + unsigned int off = 0; + STRUCT_ENTRY *start, *end; + + CHECK(*handle); + if (!find_label(&off, chain, *handle)) { + errno = ENOENT; + return (unsigned int)-1; + } + + start = get_entry(*handle, off); + end = get_entry(*handle, get_chain_end(*handle, off)); + + return entry2index(*handle, end) - entry2index(*handle, start); +} + +/* Get n'th rule in this chain. */ +const STRUCT_ENTRY *TC_GET_RULE(const char *chain, + unsigned int n, + TC_HANDLE_T *handle) +{ + unsigned int pos = 0, chainindex; + + CHECK(*handle); + if (!find_label(&pos, chain, *handle)) { + errno = ENOENT; + return NULL; + } + + chainindex = entry2index(*handle, get_entry(*handle, pos)); + + return index2entry(*handle, chainindex + n); +} +#endif + +static const char * +target_name(TC_HANDLE_T handle, const STRUCT_ENTRY *ce) +{ + int spos; + + /* To avoid const warnings */ + STRUCT_ENTRY *e = (STRUCT_ENTRY *)ce; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) != 0) + return GET_TARGET(e)->u.user.name; + + /* Standard target: evaluate */ + spos = *(int *)GET_TARGET(e)->data; + if (spos < 0) { + if (spos == RETURN) + return LABEL_RETURN; + else if (spos == -NF_ACCEPT-1) + return LABEL_ACCEPT; + else if (spos == -NF_DROP-1) + return LABEL_DROP; + else if (spos == -NF_QUEUE-1) + return LABEL_QUEUE; + + fprintf(stderr, "ERROR: entry %p not a valid target (%d)\n", + e, spos); + abort(); + } + +#if 0 +// jumpto = get_entry(handle, spos); + + /* Fall through rule */ + if (jumpto == (void *)e + e->next_offset) + return ""; + + /* Must point to head of a chain: ie. after error rule */ + /* FIXME: this needs to deal with internal jump targets */ + labelidx = entry2index(handle, jumpto) - 1; + return get_errorlabel(handle, index2offset(handle, labelidx)); +#endif + return ""; +} + +/* Returns a pointer to the target name of this position. */ +const char *TC_GET_TARGET(const STRUCT_ENTRY *e, + TC_HANDLE_T *handle) +{ + return target_name(*handle, e); +} + +/* Is this a built-in chain? Actually returns hook + 1. */ +int +TC_BUILTIN(const char *chain, const TC_HANDLE_T handle) +{ + unsigned int i; + + for (i = 0; i < NUMHOOKS; i++) { + if ((handle->info.valid_hooks & (1 << i)) + && handle->hooknames[i] + && strcmp(handle->hooknames[i], chain) == 0) + return i+1; + } + return 0; +} + +/* Get the policy of a given built-in chain */ +const char * +TC_GET_POLICY(const char *chain, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_head *chainh; + struct rule_head *ruleh; + int hook; + + hook = TC_BUILTIN(chain, *handle); + if (hook == 0) + return NULL; + + chainh = find_label(chain, *handle); + if (!chainh) { + errno = ENOENT; + return NULL; + } + + ruleh = chainh->lastrule; + + e = ruleh->entry; + *counters = e->counters; + + return target_name(*handle, e); +} + +#if 0 +static int +correct_verdict(STRUCT_ENTRY *e, + char *base, + unsigned int offset, int delta_offset) +{ + STRUCT_STANDARD_TARGET *t = (void *)GET_TARGET(e); + unsigned int curr = (char *)e - base; + + /* Trap: insert of fall-through rule. Don't change fall-through + verdict to jump-over-next-rule. */ + if (strcmp(t->target.u.user.name, STANDARD_TARGET) == 0 + && t->verdict > (int)offset + && !(curr == offset && + t->verdict == curr + e->next_offset)) { + t->verdict += delta_offset; + } + + return 0; +} + +/* Adjusts standard verdict jump positions after an insertion/deletion. */ +static int +set_verdict(unsigned int offset, int delta_offset, TC_HANDLE_T *handle) +{ + ENTRY_ITERATE((*handle)->entries.entrytable, + (*handle)->entries.size, + correct_verdict, (char *)(*handle)->entries.entrytable, + offset, delta_offset); + + set_changed(*handle); + return 1; +} +#endif + + + +static int +standard_map(STRUCT_ENTRY *e, int verdict) +{ + STRUCT_STANDARD_TARGET *t; + + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (t->target.u.target_size + != ALIGN(sizeof(STRUCT_STANDARD_TARGET))) { + errno = EINVAL; + return 0; + } + /* memset for memcmp convenience on delete/replace */ + memset(t->target.u.user.name, 0, FUNCTION_MAXNAMELEN); + strcpy(t->target.u.user.name, STANDARD_TARGET); + t->verdict = verdict; + + return 1; +} + +static int +map_target(const TC_HANDLE_T handle, + STRUCT_ENTRY *e, + unsigned int offset, + STRUCT_ENTRY_TARGET *old) +{ + STRUCT_ENTRY_TARGET *t = (STRUCT_ENTRY_TARGET *)GET_TARGET(e); + + /* Save old target (except data, which we don't change, except for + standard case, where we don't care). */ + *old = *t; + + /* Maybe it's empty (=> fall through) */ + if (strcmp(t->u.user.name, "") == 0) + return standard_map(e, offset + e->next_offset); + /* Maybe it's a standard target name... */ + else if (strcmp(t->u.user.name, LABEL_ACCEPT) == 0) + return standard_map(e, -NF_ACCEPT - 1); + else if (strcmp(t->u.user.name, LABEL_DROP) == 0) + return standard_map(e, -NF_DROP - 1); + else if (strcmp(t->u.user.name, LABEL_QUEUE) == 0) + return standard_map(e, -NF_QUEUE - 1); + else if (strcmp(t->u.user.name, LABEL_RETURN) == 0) + return standard_map(e, RETURN); + else if (TC_BUILTIN(t->u.user.name, handle)) { + /* Can't jump to builtins. */ + errno = EINVAL; + return 0; + } else { + /* Maybe it's an existing chain name. */ + struct chain_head *c; + +#if 0 + /* FIXME */ + c = find_label(t->u.user.name, handle); + if (c) + return standard_map(e, c->start_off); +#endif + } + + /* Must be a module? If not, kernel will reject... */ + /* memset to all 0 for your memcmp convenience. */ + memset(t->u.user.name + strlen(t->u.user.name), + 0, + FUNCTION_MAXNAMELEN - strlen(t->u.user.name)); + return 1; +} + +static void +unmap_target(STRUCT_ENTRY *e, STRUCT_ENTRY_TARGET *old) +{ + STRUCT_ENTRY_TARGET *t = GET_TARGET(e); + + /* Save old target (except data, which we don't change, except for + standard case, where we don't care). */ + *t = *old; +} + +static struct rule_head * +ruleh_get_n(struct chain_head *chead, int rulenum) +{ + int i = 0; + struct list_head *list; + + + list_for_each(list, &chead->rules) { + struct rule_head *rhead = list_entry(list, struct rule_head, + list); + i++; + if (i == rulenum) + return rhead; + } + return NULL; +} + +/* Insert the entry `e' in chain `chain' into position `rulenum'. */ +int +TC_INSERT_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct rule_head *prev; + + iptc_fn = TC_INSERT_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + prev = ruleh_get_n(c, rulenum-1); + if (!prev) { + errno = E2BIG; + return 0; + } + + if (append_entrycopy(e, prev)) + return 1; + + return 0; +} + +/* Atomically replace rule `rulenum' in `chain' with `fw'. */ +int +TC_REPLACE_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct rule_head *repl; + + iptc_fn = TC_REPLACE_ENTRY; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + repl = ruleh_get_n(c, rulenum); + if (!repl) { + errno = E2BIG; + return 0; + } + + if (!append_entrycopy(e, repl)) { + errno = ENOMEM; + return 0; + } + + ruleh_free(repl); + return 1; +} + +/* Append entry `e' to chain `chain'. Equivalent to insert with + rulenum = length of chain. */ +int +TC_APPEND_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct rule_head *rhead; + + iptc_fn = TC_APPEND_ENTRY; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + rhead = list_entry(c->rules.prev, struct rule_head, list); + if(append_entrycopy(e, rhead)) + return 1; + + return 0; +} + +static inline int +match_different(const STRUCT_ENTRY_MATCH *a, + const unsigned char *a_elems, + const unsigned char *b_elems, + unsigned char **maskptr) +{ + const STRUCT_ENTRY_MATCH *b; + unsigned int i; + + /* Offset of b is the same as a. */ + b = (void *)b_elems + ((unsigned char *)a - a_elems); + + if (a->u.match_size != b->u.match_size) + return 1; + + if (strcmp(a->u.user.name, b->u.user.name) != 0) + return 1; + + *maskptr += ALIGN(sizeof(*a)); + + for (i = 0; i < a->u.match_size - ALIGN(sizeof(*a)); i++) + if (((a->data[i] ^ b->data[i]) & (*maskptr)[i]) != 0) + return 1; + *maskptr += i; + return 0; +} + +static inline int +target_different(const unsigned char *a_targdata, + const unsigned char *b_targdata, + unsigned int tdatasize, + const unsigned char *mask) +{ + unsigned int i; + for (i = 0; i < tdatasize; i++) + if (((a_targdata[i] ^ b_targdata[i]) & mask[i]) != 0) + return 1; + + return 0; +} + +static int +is_same(const STRUCT_ENTRY *a, + const STRUCT_ENTRY *b, + unsigned char *matchmask); + +/* Delete the first rule in `chain' which matches `origfw'. */ +int +TC_DELETE_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *origfw, + unsigned char *matchmask, + TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct list_head *cur, *cur2; + + iptc_fn = TC_DELETE_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + list_for_each_safe(cur, cur2, &c->rules) { + struct rule_head *rhead = list_entry(cur, struct rule_head, + list); + if (is_same(rhead->entry, origfw, matchmask)) { + ruleh_free(rhead); + return 1; + } + } + + errno = ENOENT; + return 0; +} + +/* Delete the rule in position `rulenum' in `chain'. */ +int +TC_DELETE_NUM_ENTRY(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + struct chain_head *chainh; + struct rule_head *rhead; + + iptc_fn = TC_DELETE_NUM_ENTRY; + + if (!(chainh = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + rhead = ruleh_get_n(chainh, rulenum); + if (!rhead) { + errno = E2BIG; + return 0; + } + + ruleh_free(rhead); + + return 1; +} + +/* Check the packet `fw' on chain `chain'. Returns the verdict, or + NULL and sets errno. */ +const char * +TC_CHECK_PACKET(const IPT_CHAINLABEL chain, + STRUCT_ENTRY *entry, + TC_HANDLE_T *handle) +{ + errno = ENOSYS; + return NULL; +} + +/* Flushes the entries in the given chain (ie. empties chain). */ +int +TC_FLUSH_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + struct list_head *cur, *cur2; + struct chain_head *chainh; + + if (!(chainh = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + list_for_each_safe(cur, cur2, &chainh->rules) { + struct rule_head *ruleh = list_entry(cur, struct rule_head, + list); + /* don't free the entry and policy/return entries */ + if (ruleh != chainh->firstrule && ruleh != chainh->lastrule) + ruleh_free(ruleh); + } + return 1; +} + +/* Zeroes the counters in a chain. */ +int +TC_ZERO_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct list_head *cur; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + list_for_each(cur, c->rules.next) { + struct rule_head *r = list_entry(cur, struct rule_head, list); + if (r->counter_map.maptype == COUNTER_MAP_NORMAL_MAP) + r->counter_map.maptype = COUNTER_MAP_ZEROED; + } + set_changed(*handle); + + return 1; +} + +STRUCT_COUNTERS * +TC_READ_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_head *c; + struct rule_head *r; + + iptc_fn = TC_READ_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle) ) + || !(r = ruleh_get_n(c, rulenum))) { + errno = ENOENT; + return NULL; + } + + return &r->entry->counters; +} + +int +TC_ZERO_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_head *c; + struct rule_head *r; + + iptc_fn = TC_ZERO_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle)) + || !(r = ruleh_get_n(c, rulenum))) { + errno = ENOENT; + return 0; + } + + if (r->counter_map.maptype == COUNTER_MAP_NORMAL_MAP) + r->counter_map.maptype = COUNTER_MAP_ZEROED; + + set_changed(*handle); + + return 1; +} + +int +TC_SET_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_head *c; + struct rule_head *r; + + iptc_fn = TC_SET_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle)) + || !(r = ruleh_get_n(c, rulenum))) { + errno = ENOENT; + return 0; + } + + r->counter_map.maptype = COUNTER_MAP_SET; + memcpy(&r->entry->counters, counters, sizeof(STRUCT_COUNTERS)); + + set_changed(*handle); + + return 1; +} + +/* Creates a new chain. */ +/* To create a chain, create two rules: error node and unconditional + * return. */ +int +TC_CREATE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + int ret; + struct chainstart { + STRUCT_ENTRY head; + struct ipt_error_target name; + } *newc1; + struct chainend { + STRUCT_ENTRY ret; + STRUCT_STANDARD_TARGET target; + } *newc2; + struct rule_head *newr1, *newr2; + struct chain_head *chead; + + iptc_fn = TC_CREATE_CHAIN; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(chain, *handle) + || strcmp(chain, LABEL_DROP) == 0 + || strcmp(chain, LABEL_ACCEPT) == 0 + || strcmp(chain, LABEL_QUEUE) == 0 + || strcmp(chain, LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (strlen(chain)+1 > sizeof(IPT_CHAINLABEL)) { + errno = EINVAL; + return 0; + } + + chead = chainh_alloc(*handle, chain); + if (!chead) { + errno = ENOMEM; + return 0; + } + + newr1 = ruleh_alloc(sizeof(*newc1)); + if (!newr1) { + chainh_free(chead); + return 0; + } + newc1 = (struct chainstart *) newr1->entry; + + newr2 = ruleh_alloc(sizeof(*newc2)); + if (!newr2) { + chainh_free(chead); + ruleh_free(newr1); + return 0; + } + newc2 = (struct chainend *) newr2->entry; + + newc1->head.target_offset = sizeof(STRUCT_ENTRY); + newc1->head.next_offset + = sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(struct ipt_error_target)); + strcpy(newc1->name.t.u.user.name, ERROR_TARGET); + newc1->name.t.u.target_size = ALIGN(sizeof(struct ipt_error_target)); + strcpy(newc1->name.error, chain); + + newc2->ret.target_offset = sizeof(STRUCT_ENTRY); + newc2->ret.next_offset + = sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + strcpy(newc2->target.target.u.user.name, STANDARD_TARGET); + newc2->target.target.u.target_size + = ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + newc2->target.verdict = RETURN; + + list_prepend(&newr1->list, &chead->rules); + chead->firstrule = newr1; + list_append(&newr2->list, &chead->rules); + chead->lastrule = newr2; + + return 1; +} + +#if 0 +static int +count_ref(STRUCT_ENTRY *e, unsigned int offset, unsigned int *ref) +{ + STRUCT_STANDARD_TARGET *t; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) == 0) { + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (t->verdict == offset) + (*ref)++; + } + + return 0; +} + +/* Get the number of references to this chain. */ +int +TC_GET_REFERENCES(unsigned int *ref, const IPT_CHAINLABEL chain, + TC_HANDLE_T *handle) +{ + struct chain_cache *c; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + *ref = 0; + ENTRY_ITERATE((*handle)->entries.entrytable, + (*handle)->entries.size, + count_ref, c->start_off, ref); + return 1; +} +#endif + +static unsigned int +count_rules(struct chain_head *chainh) +{ + unsigned int numrules = 0; + struct list_head *cur; + + list_for_each(cur, &chainh->rules) { + numrules++; + } + + if (numrules <=2) + return 0; + else + return numrules-2; +} + +/* Deletes a chain. */ +int +TC_DELETE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int references; + struct chain_head *chainh; + +#if 0 + if (!TC_GET_REFERENCES(&references, chain, handle)) + return 0; + + iptc_fn = TC_DELETE_CHAIN; + + if (TC_BUILTIN(chain, *handle)) { + errno = EINVAL; + return 0; + } + + if (references > 0) { + errno = EMLINK; + return 0; + } +#endif + + if (!(chainh = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + if (!(count_rules(chainh) == 0)) { + errno = ENOTEMPTY; + return 0; + } + + chainh_free(chainh); + return 1; +} + +/* Renames a chain. */ +int TC_RENAME_CHAIN(const IPT_CHAINLABEL oldname, + const IPT_CHAINLABEL newname, + TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct rule_head *ruleh; + struct ipt_error_target *t; + + iptc_fn = TC_RENAME_CHAIN; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(newname, *handle) + || strcmp(newname, LABEL_DROP) == 0 + || strcmp(newname, LABEL_ACCEPT) == 0 + || strcmp(newname, LABEL_QUEUE) == 0 + || strcmp(newname, LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (!(c = find_label(oldname, *handle)) + || TC_BUILTIN(oldname, *handle)) { + errno = ENOENT; + return 0; + } + + if (strlen(newname)+1 > sizeof(IPT_CHAINLABEL)) { + errno = EINVAL; + return 0; + } + + ruleh = list_entry(&c->rules.next, struct rule_head, list); + + t = (struct ipt_error_target *) + GET_TARGET(ruleh->entry); + + memset(t->error, 0, sizeof(t->error)); + strcpy(t->error, newname); + + return 1; +} + +/* Sets the policy on a built-in chain. */ +int +TC_SET_POLICY(const IPT_CHAINLABEL chain, + const IPT_CHAINLABEL policy, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + int ctrindex; + unsigned int hook; + struct chain_head *chainh; + struct rule_head *policyrh; + STRUCT_ENTRY *e; + STRUCT_STANDARD_TARGET *t; + + iptc_fn = TC_SET_POLICY; + /* Figure out which chain. */ + hook = TC_BUILTIN(chain, *handle); + if (hook == 0) { + errno = ENOENT; + return 0; + } else + hook--; + + if (!(chainh = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + policyrh = chainh->lastrule; + if (policyrh) { + printf("ERROR: Policy for `%s' non-existant", chain); + return 0; + } + + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(policyrh->entry); + + if (strcmp(policy, LABEL_ACCEPT) == 0) + t->verdict = -NF_ACCEPT - 1; + else if (strcmp(policy, LABEL_DROP) == 0) + t->verdict = -NF_DROP - 1; + else { + errno = EINVAL; + return 0; + } + + ctrindex = entry2index(*handle, e); + + if (counters) { + /* set byte and packet counters */ + memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS)); + + policyrh->counter_map.maptype = COUNTER_MAP_SET; + + } else { + policyrh->counter_map.maptype = COUNTER_MAP_NOMAP; + policyrh->counter_map.mappos = 0; + } + + set_changed(*handle); + + return 1; +} + +/* Without this, on gcc 2.7.2.3, we get: + libiptc.c: In function `TC_COMMIT': + libiptc.c:833: fixed or forbidden register was spilled. + This may be due to a compiler bug or to impossible asm + statements or clauses. +*/ +static void +subtract_counters(STRUCT_COUNTERS *answer, + const STRUCT_COUNTERS *a, + const STRUCT_COUNTERS *b) +{ + answer->pcnt = a->pcnt - b->pcnt; + answer->bcnt = a->bcnt - b->bcnt; +} + +int +TC_COMMIT(TC_HANDLE_T *handle) +{ + /* Replace, then map back the counters. */ + STRUCT_REPLACE *repl; + STRUCT_COUNTERS_INFO *newcounters; + unsigned int i; + size_t counterlen; + + CHECK(*handle); + + counterlen = sizeof(STRUCT_COUNTERS_INFO) + + sizeof(STRUCT_COUNTERS) * (*handle)->new_number; + +#if 0 + TC_DUMP_ENTRIES(*handle); +#endif + + /* Don't commit if nothing changed. */ + if (!(*handle)->changed) + goto finished; + + repl = malloc(sizeof(*repl) + (*handle)->entries.size); + if (!repl) { + errno = ENOMEM; + return 0; + } + + /* These are the old counters we will get from kernel */ + repl->counters = malloc(sizeof(STRUCT_COUNTERS) + * (*handle)->info.num_entries); + if (!repl->counters) { + free(repl); + errno = ENOMEM; + return 0; + } + + /* These are the counters we're going to put back, later. */ + newcounters = malloc(counterlen); + if (!newcounters) { + free(repl->counters); + free(repl); + errno = ENOMEM; + return 0; + } + + strcpy(repl->name, (*handle)->info.name); + repl->num_entries = (*handle)->new_number; + repl->size = (*handle)->entries.size; + memcpy(repl->hook_entry, (*handle)->info.hook_entry, + sizeof(repl->hook_entry)); + memcpy(repl->underflow, (*handle)->info.underflow, + sizeof(repl->underflow)); + repl->num_counters = (*handle)->info.num_entries; + repl->valid_hooks = (*handle)->info.valid_hooks; + memcpy(repl->entries, (*handle)->entries.entrytable, + (*handle)->entries.size); + + if (setsockopt(sockfd, TC_IPPROTO, SO_SET_REPLACE, repl, + sizeof(*repl) + (*handle)->entries.size) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + /* Put counters back. */ + strcpy(newcounters->name, (*handle)->info.name); + newcounters->num_counters = (*handle)->new_number; + for (i = 0; i < (*handle)->new_number; i++) { + unsigned int mappos = (*handle)->counter_map[i].mappos; + switch ((*handle)->counter_map[i].maptype) { + case COUNTER_MAP_NOMAP: + newcounters->counters[i] + = ((STRUCT_COUNTERS){ 0, 0 }); + break; + + case COUNTER_MAP_NORMAL_MAP: + /* Original read: X. + * Atomic read on replacement: X + Y. + * Currently in kernel: Z. + * Want in kernel: X + Y + Z. + * => Add in X + Y + * => Add in replacement read. + */ + newcounters->counters[i] = repl->counters[mappos]; + break; + + case COUNTER_MAP_ZEROED: + /* Original read: X. + * Atomic read on replacement: X + Y. + * Currently in kernel: Z. + * Want in kernel: Y + Z. + * => Add in Y. + * => Add in (replacement read - original read). + */ + subtract_counters(&newcounters->counters[i], + &repl->counters[mappos], + &index2entry(*handle, i)->counters); + break; + + case COUNTER_MAP_SET: + /* Want to set counter (iptables-restore) */ + + memcpy(&newcounters->counters[i], + &index2entry(*handle, i)->counters, + sizeof(STRUCT_COUNTERS)); + + break; + } + } + +#ifdef KERNEL_64_USERSPACE_32 + { + /* Kernel will think that pointer should be 64-bits, and get + padding. So we accomodate here (assumption: alignment of + `counters' is on 64-bit boundary). */ + u_int64_t *kernptr = (u_int64_t *)&newcounters->counters; + if ((unsigned long)&newcounters->counters % 8 != 0) { + fprintf(stderr, + "counters alignment incorrect! Mail rusty!\n"); + abort(); + } + *kernptr = newcounters->counters; + } +#endif /* KERNEL_64_USERSPACE_32 */ + + if (setsockopt(sockfd, TC_IPPROTO, SO_SET_ADD_COUNTERS, + newcounters, counterlen) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + free(repl->counters); + free(repl); + free(newcounters); + + finished: + TC_FREE(handle); + return 1; +} + +/* Get raw socket. */ +int +TC_GET_RAW_SOCKET() +{ + return sockfd; +} + +/* Translates errno numbers into more human-readable form than strerror. */ +const char * +TC_STRERROR(int err) +{ + unsigned int i; + struct table_struct { + void *fn; + int err; + const char *message; + } table [] = + { { TC_INIT, EPERM, "Permission denied (you must be root)" }, + { TC_INIT, EINVAL, "Module is wrong version" }, + { TC_INIT, ENOENT, + "Table does not exist (do you need to insmod?)" }, + { TC_DELETE_CHAIN, ENOTEMPTY, "Chain is not empty" }, + { TC_DELETE_CHAIN, EINVAL, "Can't delete built-in chain" }, + { TC_DELETE_CHAIN, EMLINK, + "Can't delete chain with references left" }, + { TC_CREATE_CHAIN, EEXIST, "Chain already exists" }, + { TC_INSERT_ENTRY, E2BIG, "Index of insertion too big" }, + { TC_REPLACE_ENTRY, E2BIG, "Index of replacement too big" }, + { TC_DELETE_NUM_ENTRY, E2BIG, "Index of deletion too big" }, + { TC_READ_COUNTER, E2BIG, "Index of counter too big" }, + { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" }, + { TC_INSERT_ENTRY, ELOOP, "Loop found in table" }, + { TC_INSERT_ENTRY, EINVAL, "Target problem" }, + /* EINVAL for CHECK probably means bad interface. */ + { TC_CHECK_PACKET, EINVAL, + "Bad arguments (does that interface exist?)" }, + { TC_CHECK_PACKET, ENOSYS, + "Checking will most likely never get implemented" }, + /* ENOENT for DELETE probably means no matching rule */ + { TC_DELETE_ENTRY, ENOENT, + "Bad rule (does a matching rule exist in that chain?)" }, + { TC_SET_POLICY, ENOENT, + "Bad built-in chain name" }, + { TC_SET_POLICY, EINVAL, + "Bad policy name" }, + + { NULL, 0, "Incompatible with this kernel" }, + { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" }, + { NULL, ENOSYS, "Will be implemented real soon. I promise ;)" }, + { NULL, ENOMEM, "Memory allocation problem" }, + { NULL, ENOENT, "No chain/target/match by that name" }, + }; + + for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) { + if ((!table[i].fn || table[i].fn == iptc_fn) + && table[i].err == err) + return table[i].message; + } + + return strerror(err); +} diff --git a/libiptc2/libiptc.cvs.c b/libiptc2/libiptc.cvs.c new file mode 100644 index 0000000..3f03593 --- /dev/null +++ b/libiptc2/libiptc.cvs.c @@ -0,0 +1,1920 @@ +/* Library which manipulates firewall rules. Version $Revision: 1.40 $ */ + +/* Architecture of firewall rules is as follows: + * + * Chains go INPUT, FORWARD, OUTPUT then user chains. + * Each user chain starts with an ERROR node. + * Every chain ends with an unconditional jump: a RETURN for user chains, + * and a POLICY for built-ins. + */ + +/* (C) 1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See + * COPYING for details). + * (C) 2000-2003 by the Netfilter Core Team + * + * 2003-Jun-20: Harald Welte : + * - Reimplementation of chain cache to use offsets instead of entries + * 2003-Jun-23: Harald Welte : + * - performance optimization, sponsored by Astaro AG (http://www.astaro.com/) + * don't rebuild the chain cache after every operation, instead fix it + * up after a ruleset change. + */ + +#ifndef IPT_LIB_DIR +#define IPT_LIB_DIR "/usr/lib/iptables" +#endif + +#ifndef __OPTIMIZE__ +STRUCT_ENTRY_TARGET * +GET_TARGET(STRUCT_ENTRY *e) +{ + return (void *)e + e->target_offset; +} +#endif + +static int sockfd = -1; +static void *iptc_fn = NULL; + +static const char *hooknames[] += { [HOOK_PRE_ROUTING] "PREROUTING", + [HOOK_LOCAL_IN] "INPUT", + [HOOK_FORWARD] "FORWARD", + [HOOK_LOCAL_OUT] "OUTPUT", + [HOOK_POST_ROUTING] "POSTROUTING", +#ifdef HOOK_DROPPING + [HOOK_DROPPING] "DROPPING" +#endif +}; + +struct counter_map +{ + enum { + COUNTER_MAP_NOMAP, + COUNTER_MAP_NORMAL_MAP, + COUNTER_MAP_ZEROED, + COUNTER_MAP_SET + } maptype; + unsigned int mappos; +}; + +/* Convenience structures */ +struct ipt_error_target +{ + STRUCT_ENTRY_TARGET t; + char error[TABLE_MAXNAMELEN]; +}; + +struct chain_cache +{ + char name[TABLE_MAXNAMELEN]; + /* This is the first rule in chain. */ + unsigned int start_off; + /* Last rule in chain */ + unsigned int end_off; +}; + +STRUCT_TC_HANDLE +{ + /* Have changes been made? */ + int changed; + /* Size in here reflects original state. */ + STRUCT_GETINFO info; + + struct counter_map *counter_map; + /* Array of hook names */ + const char **hooknames; + + /* Cached position of chain heads (NULL = no cache). */ + unsigned int cache_num_chains; + unsigned int cache_num_builtins; + struct chain_cache *cache_chain_heads; + + /* Chain iterator: current chain cache entry. */ + struct chain_cache *cache_chain_iteration; + + /* Rule iterator: terminal rule */ + STRUCT_ENTRY *cache_rule_end; + + /* Number in here reflects current state. */ + unsigned int new_number; + STRUCT_GET_ENTRIES entries; +}; + +static void +set_changed(TC_HANDLE_T h) +{ + h->changed = 1; +} + +#ifdef IPTC_DEBUG +static void do_check(TC_HANDLE_T h, unsigned int line); +#define CHECK(h) do { if (!getenv("IPTC_NO_CHECK")) do_check((h), __LINE__); } while(0) +#else +#define CHECK(h) +#endif + +static inline int +get_number(const STRUCT_ENTRY *i, + const STRUCT_ENTRY *seek, + unsigned int *pos) +{ + if (i == seek) + return 1; + (*pos)++; + return 0; +} + +static unsigned int +entry2index(const TC_HANDLE_T h, const STRUCT_ENTRY *seek) +{ + unsigned int pos = 0; + + if (ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + get_number, seek, &pos) == 0) { + fprintf(stderr, "ERROR: offset %i not an entry!\n", + (char *)seek - (char *)h->entries.entrytable); + abort(); + } + return pos; +} + +static inline int +get_entry_n(STRUCT_ENTRY *i, + unsigned int number, + unsigned int *pos, + STRUCT_ENTRY **pe) +{ + if (*pos == number) { + *pe = i; + return 1; + } + (*pos)++; + return 0; +} + +static STRUCT_ENTRY * +index2entry(TC_HANDLE_T h, unsigned int index) +{ + unsigned int pos = 0; + STRUCT_ENTRY *ret = NULL; + + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + get_entry_n, index, &pos, &ret); + + return ret; +} + +static inline STRUCT_ENTRY * +get_entry(TC_HANDLE_T h, unsigned int offset) +{ + return (STRUCT_ENTRY *)((char *)h->entries.entrytable + offset); +} + +static inline unsigned long +entry2offset(const TC_HANDLE_T h, const STRUCT_ENTRY *e) +{ + return (void *)e - (void *)h->entries.entrytable; +} + +static inline unsigned long +index2offset(TC_HANDLE_T h, unsigned int index) +{ + return entry2offset(h, index2entry(h, index)); +} + +static inline STRUCT_ENTRY * +offset2entry(TC_HANDLE_T h, unsigned int offset) +{ + return (STRUCT_ENTRY *) ((void *)h->entries.entrytable+offset); +} + +static inline unsigned int +offset2index(const TC_HANDLE_T h, unsigned int offset) +{ + return entry2index(h, offset2entry(h, offset)); +} + + +static const char * +get_errorlabel(TC_HANDLE_T h, unsigned int offset) +{ + STRUCT_ENTRY *e; + + e = get_entry(h, offset); + if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) != 0) { + fprintf(stderr, "ERROR: offset %u not an error node!\n", + offset); + abort(); + } + + return (const char *)GET_TARGET(e)->data; +} + +/* Allocate handle of given size */ +static TC_HANDLE_T +alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules) +{ + size_t len; + TC_HANDLE_T h; + + len = sizeof(STRUCT_TC_HANDLE) + + size + + num_rules * sizeof(struct counter_map); + + if ((h = malloc(len)) == NULL) { + errno = ENOMEM; + return NULL; + } + + h->changed = 0; + h->cache_num_chains = 0; + h->cache_chain_heads = NULL; + h->counter_map = (void *)h + + sizeof(STRUCT_TC_HANDLE) + + size; + strcpy(h->info.name, tablename); + strcpy(h->entries.name, tablename); + + return h; +} + +TC_HANDLE_T +TC_INIT(const char *tablename) +{ + TC_HANDLE_T h; + STRUCT_GETINFO info; + unsigned int i; + int tmp; + socklen_t s; + + iptc_fn = TC_INIT; + + if (sockfd != -1) { + close(sockfd); + sockfd = -1; + } + + if (strlen(tablename) >= TABLE_MAXNAMELEN) { + errno = EINVAL; + return NULL; + } + + sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW); + if (sockfd < 0) + return NULL; + + s = sizeof(info); + + strcpy(info.name, tablename); + if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0) + return NULL; + + if ((h = alloc_handle(info.name, info.size, info.num_entries)) + == NULL) { + close(sockfd); + sockfd = -1; + return NULL; + } + +/* Too hard --RR */ +#if 0 + sprintf(pathname, "%s/%s", IPT_LIB_DIR, info.name); + dynlib = dlopen(pathname, RTLD_NOW); + if (!dynlib) { + errno = ENOENT; + return NULL; + } + h->hooknames = dlsym(dynlib, "hooknames"); + if (!h->hooknames) { + errno = ENOENT; + return NULL; + } +#else + h->hooknames = hooknames; +#endif + + /* Initialize current state */ + h->info = info; + h->new_number = h->info.num_entries; + for (i = 0; i < h->info.num_entries; i++) + h->counter_map[i] + = ((struct counter_map){COUNTER_MAP_NORMAL_MAP, i}); + + h->entries.size = h->info.size; + + tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size; + + if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries, + &tmp) < 0) { + close(sockfd); + sockfd = -1; + free(h); + return NULL; + } + + CHECK(h); + return h; +} + +void +TC_FREE(TC_HANDLE_T *h) +{ + close(sockfd); + sockfd = -1; + if ((*h)->cache_chain_heads) + free((*h)->cache_chain_heads); + free(*h); + *h = NULL; +} + +static inline int +print_match(const STRUCT_ENTRY_MATCH *m) +{ + printf("Match name: `%s'\n", m->u.user.name); + return 0; +} + +static int dump_entry(STRUCT_ENTRY *e, const TC_HANDLE_T handle); + +void +TC_DUMP_ENTRIES(const TC_HANDLE_T handle) +{ + CHECK(handle); + + printf("libiptc v%s. %u entries, %u bytes.\n", + IPTABLES_VERSION, + handle->new_number, handle->entries.size); + printf("Table `%s'\n", handle->info.name); + printf("Hooks: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.hook_entry[HOOK_PRE_ROUTING], + handle->info.hook_entry[HOOK_LOCAL_IN], + handle->info.hook_entry[HOOK_FORWARD], + handle->info.hook_entry[HOOK_LOCAL_OUT], + handle->info.hook_entry[HOOK_POST_ROUTING]); + printf("Underflows: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.underflow[HOOK_PRE_ROUTING], + handle->info.underflow[HOOK_LOCAL_IN], + handle->info.underflow[HOOK_FORWARD], + handle->info.underflow[HOOK_LOCAL_OUT], + handle->info.underflow[HOOK_POST_ROUTING]); + + ENTRY_ITERATE(handle->entries.entrytable, handle->entries.size, + dump_entry, handle); +} + +/* Returns 0 if not hook entry, else hooknumber + 1 */ +static inline unsigned int +is_hook_entry(STRUCT_ENTRY *e, TC_HANDLE_T h) +{ + unsigned int i; + + for (i = 0; i < NUMHOOKS; i++) { + if ((h->info.valid_hooks & (1 << i)) + && get_entry(h, h->info.hook_entry[i]) == e) + return i+1; + } + return 0; +} + +static inline int +add_chain(STRUCT_ENTRY *e, TC_HANDLE_T h, STRUCT_ENTRY **prev) +{ + unsigned int builtin; + + /* Last entry. End it. */ + if (entry2offset(h, e) + e->next_offset == h->entries.size) { + /* This is the ERROR node at end of the table */ + h->cache_chain_heads[h->cache_num_chains-1].end_off = + entry2offset(h, *prev); + return 0; + } + + /* We know this is the start of a new chain if it's an ERROR + target, or a hook entry point */ + if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) == 0) { + /* prev was last entry in previous chain */ + h->cache_chain_heads[h->cache_num_chains-1].end_off + = entry2offset(h, *prev); + + strcpy(h->cache_chain_heads[h->cache_num_chains].name, + (const char *)GET_TARGET(e)->data); + h->cache_chain_heads[h->cache_num_chains].start_off + = entry2offset(h, (void *)e + e->next_offset); + h->cache_num_chains++; + } else if ((builtin = is_hook_entry(e, h)) != 0) { + if (h->cache_num_chains > 0) + /* prev was last entry in previous chain */ + h->cache_chain_heads[h->cache_num_chains-1].end_off + = entry2offset(h, *prev); + + strcpy(h->cache_chain_heads[h->cache_num_chains].name, + h->hooknames[builtin-1]); + h->cache_chain_heads[h->cache_num_chains].start_off + = entry2offset(h, (void *)e); + h->cache_num_chains++; + } + + *prev = e; + return 0; +} + +static int alphasort(const void *a, const void *b) +{ + return strcmp(((struct chain_cache *)a)->name, + ((struct chain_cache *)b)->name); +} + +static int populate_cache(TC_HANDLE_T h) +{ + unsigned int i; + STRUCT_ENTRY *prev; + + /* # chains < # rules / 2 + num builtins - 1 */ + h->cache_chain_heads = malloc((h->new_number / 2 + 4) + * sizeof(struct chain_cache)); + if (!h->cache_chain_heads) { + errno = ENOMEM; + return 0; + } + + h->cache_num_chains = 0; + h->cache_num_builtins = 0; + + /* Count builtins */ + for (i = 0; i < NUMHOOKS; i++) { + if (h->info.valid_hooks & (1 << i)) + h->cache_num_builtins++; + } + + prev = NULL; + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + add_chain, h, &prev); + + qsort(h->cache_chain_heads + h->cache_num_builtins, + h->cache_num_chains - h->cache_num_builtins, + sizeof(struct chain_cache), alphasort); + + return 1; +} + +static int +correct_cache(TC_HANDLE_T h, unsigned int offset, int delta) +{ + int i; /* needs to be signed because deleting first + chain can make it drop to -1 */ + + if (!delta) + return 1; + + for (i = 0; i < h->cache_num_chains; i++) { + struct chain_cache *cc = &h->cache_chain_heads[i]; + + if (delta < 0) { + /* take care about deleted chains */ + if (cc->start_off >= offset+delta + && cc->end_off <= offset) { + /* this chain is within the deleted range, + * let's remove it from the cache */ + void *start; + unsigned int size; + + h->cache_num_chains--; + if (i+1 >= h->cache_num_chains) + continue; + start = &h->cache_chain_heads[i+1]; + size = (h->cache_num_chains-i) + * sizeof(struct chain_cache); + memmove(cc, start, size); + + /* iterate over same index again, since + * it is now a different chain */ + i--; + continue; + } + } + + if (cc->start_off > offset) + cc->start_off += delta; + + if (cc->end_off >= offset) + cc->end_off += delta; + } + /* HW_FIXME: sorting might be needed, but just in case a new chain was + * added */ + + return 1; +} + +static int +add_chain_cache(TC_HANDLE_T h, const char *name, unsigned int start_off, + unsigned int end_off) +{ + struct chain_cache *ccs = realloc(h->cache_chain_heads, + (h->new_number / 2 + 4 + 1) + * sizeof(struct chain_cache)); + struct chain_cache *newcc; + + if (!ccs) + return 0; + + h->cache_chain_heads = ccs; + newcc = &h->cache_chain_heads[h->cache_num_chains]; + h->cache_num_chains++; + + strncpy(newcc->name, name, TABLE_MAXNAMELEN-1); + newcc->start_off = start_off; + newcc->end_off = end_off; + + return 1; +} + +/* Returns cache ptr if found, otherwise NULL. */ +static struct chain_cache * +find_label(const char *name, TC_HANDLE_T handle) +{ + unsigned int i; + + if (handle->cache_chain_heads == NULL + && !populate_cache(handle)) + return NULL; + + /* FIXME: Linear search through builtins, then binary --RR */ + for (i = 0; i < handle->cache_num_chains; i++) { + if (strcmp(handle->cache_chain_heads[i].name, name) == 0) + return &handle->cache_chain_heads[i]; + } + + return NULL; +} + +/* Does this chain exist? */ +int TC_IS_CHAIN(const char *chain, const TC_HANDLE_T handle) +{ + return find_label(chain, handle) != NULL; +} + +/* Returns the position of the final (ie. unconditional) element. */ +static unsigned int +get_chain_end(const TC_HANDLE_T handle, unsigned int start) +{ + unsigned int last_off, off; + STRUCT_ENTRY *e; + + last_off = start; + e = get_entry(handle, start); + + /* Terminate when we meet a error label or a hook entry. */ + for (off = start + e->next_offset; + off < handle->entries.size; + last_off = off, off += e->next_offset) { + STRUCT_ENTRY_TARGET *t; + unsigned int i; + + e = get_entry(handle, off); + + /* We hit an entry point. */ + for (i = 0; i < NUMHOOKS; i++) { + if ((handle->info.valid_hooks & (1 << i)) + && off == handle->info.hook_entry[i]) + return last_off; + } + + /* We hit a user chain label */ + t = GET_TARGET(e); + if (strcmp(t->u.user.name, ERROR_TARGET) == 0) + return last_off; + } + /* SHOULD NEVER HAPPEN */ + fprintf(stderr, "ERROR: Off end (%u) of chain from %u!\n", + handle->entries.size, off); + abort(); +} + +/* Iterator functions to run through the chains. */ +const char * +TC_FIRST_CHAIN(TC_HANDLE_T *handle) +{ + if ((*handle)->cache_chain_heads == NULL + && !populate_cache(*handle)) + return NULL; + + (*handle)->cache_chain_iteration + = &(*handle)->cache_chain_heads[0]; + + return (*handle)->cache_chain_iteration->name; +} + +/* Iterator functions to run through the chains. Returns NULL at end. */ +const char * +TC_NEXT_CHAIN(TC_HANDLE_T *handle) +{ + (*handle)->cache_chain_iteration++; + + if ((*handle)->cache_chain_iteration - (*handle)->cache_chain_heads + == (*handle)->cache_num_chains) + return NULL; + + return (*handle)->cache_chain_iteration->name; +} + +/* Get first rule in the given chain: NULL for empty chain. */ +const STRUCT_ENTRY * +TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle) +{ + struct chain_cache *c; + + c = find_label(chain, *handle); + if (!c) { + errno = ENOENT; + return NULL; + } + + /* Empty chain: single return/policy rule */ + if (c->start_off == c->end_off) + return NULL; + + (*handle)->cache_rule_end = offset2entry(*handle, c->end_off); + return offset2entry(*handle, c->start_off); +} + +/* Returns NULL when rules run out. */ +const STRUCT_ENTRY * +TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle) +{ + if ((void *)prev + prev->next_offset + == (void *)(*handle)->cache_rule_end) + return NULL; + + return (void *)prev + prev->next_offset; +} + +#if 0 +/* How many rules in this chain? */ +unsigned int +TC_NUM_RULES(const char *chain, TC_HANDLE_T *handle) +{ + unsigned int off = 0; + STRUCT_ENTRY *start, *end; + + CHECK(*handle); + if (!find_label(&off, chain, *handle)) { + errno = ENOENT; + return (unsigned int)-1; + } + + start = get_entry(*handle, off); + end = get_entry(*handle, get_chain_end(*handle, off)); + + return entry2index(*handle, end) - entry2index(*handle, start); +} + +/* Get n'th rule in this chain. */ +const STRUCT_ENTRY *TC_GET_RULE(const char *chain, + unsigned int n, + TC_HANDLE_T *handle) +{ + unsigned int pos = 0, chainindex; + + CHECK(*handle); + if (!find_label(&pos, chain, *handle)) { + errno = ENOENT; + return NULL; + } + + chainindex = entry2index(*handle, get_entry(*handle, pos)); + + return index2entry(*handle, chainindex + n); +} +#endif + +static const char * +target_name(TC_HANDLE_T handle, const STRUCT_ENTRY *ce) +{ + int spos; + unsigned int labelidx; + STRUCT_ENTRY *jumpto; + + /* To avoid const warnings */ + STRUCT_ENTRY *e = (STRUCT_ENTRY *)ce; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) != 0) + return GET_TARGET(e)->u.user.name; + + /* Standard target: evaluate */ + spos = *(int *)GET_TARGET(e)->data; + if (spos < 0) { + if (spos == RETURN) + return LABEL_RETURN; + else if (spos == -NF_ACCEPT-1) + return LABEL_ACCEPT; + else if (spos == -NF_DROP-1) + return LABEL_DROP; + else if (spos == -NF_QUEUE-1) + return LABEL_QUEUE; + + fprintf(stderr, "ERROR: off %lu/%u not a valid target (%i)\n", + entry2offset(handle, e), handle->entries.size, + spos); + abort(); + } + + jumpto = get_entry(handle, spos); + + /* Fall through rule */ + if (jumpto == (void *)e + e->next_offset) + return ""; + + /* Must point to head of a chain: ie. after error rule */ + labelidx = entry2index(handle, jumpto) - 1; + return get_errorlabel(handle, index2offset(handle, labelidx)); +} + +/* Returns a pointer to the target name of this position. */ +const char *TC_GET_TARGET(const STRUCT_ENTRY *e, + TC_HANDLE_T *handle) +{ + return target_name(*handle, e); +} + +/* Is this a built-in chain? Actually returns hook + 1. */ +int +TC_BUILTIN(const char *chain, const TC_HANDLE_T handle) +{ + unsigned int i; + + for (i = 0; i < NUMHOOKS; i++) { + if ((handle->info.valid_hooks & (1 << i)) + && handle->hooknames[i] + && strcmp(handle->hooknames[i], chain) == 0) + return i+1; + } + return 0; +} + +/* Get the policy of a given built-in chain */ +const char * +TC_GET_POLICY(const char *chain, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + unsigned int start; + STRUCT_ENTRY *e; + int hook; + + hook = TC_BUILTIN(chain, *handle); + if (hook != 0) + start = (*handle)->info.hook_entry[hook-1]; + else + return NULL; + + e = get_entry(*handle, get_chain_end(*handle, start)); + *counters = e->counters; + + return target_name(*handle, e); +} + +static inline int +correct_verdict(STRUCT_ENTRY *e, + char *base, + unsigned int offset, int delta_offset) +{ + STRUCT_STANDARD_TARGET *t = (void *)GET_TARGET(e); + unsigned int curr = (char *)e - base; + + /* Trap: insert of fall-through rule. Don't change fall-through + verdict to jump-over-next-rule. */ + if (strcmp(t->target.u.user.name, STANDARD_TARGET) == 0 + && t->verdict > (int)offset + && !(curr == offset && + t->verdict == curr + e->next_offset)) { + t->verdict += delta_offset; + } + + return 0; +} + +/* Adjusts standard verdict jump positions after an insertion/deletion. */ +static int +set_verdict(unsigned int offset, int delta_offset, TC_HANDLE_T *handle) +{ + ENTRY_ITERATE((*handle)->entries.entrytable, + (*handle)->entries.size, + correct_verdict, (char *)(*handle)->entries.entrytable, + offset, delta_offset); + + set_changed(*handle); + return 1; +} + +/* If prepend is set, then we are prepending to a chain: if the + * insertion position is an entry point, keep the entry point. */ +static int +insert_rules(unsigned int num_rules, unsigned int rules_size, + const STRUCT_ENTRY *insert, + unsigned int offset, unsigned int num_rules_offset, + int prepend, + TC_HANDLE_T *handle) +{ + TC_HANDLE_T newh; + STRUCT_GETINFO newinfo; + unsigned int i; + + if (offset >= (*handle)->entries.size) { + errno = EINVAL; + return 0; + } + + newinfo = (*handle)->info; + + /* Fix up entry points. */ + for (i = 0; i < NUMHOOKS; i++) { + /* Entry points to START of chain, so keep same if + inserting on at that point. */ + if ((*handle)->info.hook_entry[i] > offset) + newinfo.hook_entry[i] += rules_size; + + /* Underflow always points to END of chain (policy), + so if something is inserted at same point, it + should be advanced. */ + if ((*handle)->info.underflow[i] >= offset) + newinfo.underflow[i] += rules_size; + } + + newh = alloc_handle((*handle)->info.name, + (*handle)->entries.size + rules_size, + (*handle)->new_number + num_rules); + if (!newh) + return 0; + newh->info = newinfo; + + /* Copy pre... */ + memcpy(newh->entries.entrytable, (*handle)->entries.entrytable,offset); + /* ... Insert new ... */ + memcpy((char *)newh->entries.entrytable + offset, insert, rules_size); + /* ... copy post */ + memcpy((char *)newh->entries.entrytable + offset + rules_size, + (char *)(*handle)->entries.entrytable + offset, + (*handle)->entries.size - offset); + + /* Move counter map. */ + /* Copy pre... */ + memcpy(newh->counter_map, (*handle)->counter_map, + sizeof(struct counter_map) * num_rules_offset); + /* ... copy post */ + memcpy(newh->counter_map + num_rules_offset + num_rules, + (*handle)->counter_map + num_rules_offset, + sizeof(struct counter_map) * ((*handle)->new_number + - num_rules_offset)); + /* Set intermediates to no counter copy */ + for (i = 0; i < num_rules; i++) + newh->counter_map[num_rules_offset+i] + = ((struct counter_map){ COUNTER_MAP_SET, 0 }); + + newh->new_number = (*handle)->new_number + num_rules; + newh->entries.size = (*handle)->entries.size + rules_size; + newh->hooknames = (*handle)->hooknames; + + newh->cache_chain_heads = (*handle)->cache_chain_heads; + newh->cache_num_builtins = (*handle)->cache_num_builtins; + newh->cache_num_chains = (*handle)->cache_num_chains; + newh->cache_rule_end = (*handle)->cache_rule_end; + newh->cache_chain_iteration = (*handle)->cache_chain_iteration; + if (!correct_cache(newh, offset, rules_size)) { + free(newh); + return 0; + } + + free(*handle); + *handle = newh; + + return set_verdict(offset, rules_size, handle); +} + +static int +delete_rules(unsigned int num_rules, unsigned int rules_size, + unsigned int offset, unsigned int num_rules_offset, + TC_HANDLE_T *handle) +{ + unsigned int i; + + if (offset + rules_size > (*handle)->entries.size) { + errno = EINVAL; + return 0; + } + + /* Fix up entry points. */ + for (i = 0; i < NUMHOOKS; i++) { + /* In practice, we never delete up to a hook entry, + since the built-in chains are always first, + so these two are never equal */ + if ((*handle)->info.hook_entry[i] >= offset + rules_size) + (*handle)->info.hook_entry[i] -= rules_size; + else if ((*handle)->info.hook_entry[i] > offset) { + fprintf(stderr, "ERROR: Deleting entry %u %u %u\n", + i, (*handle)->info.hook_entry[i], offset); + abort(); + } + + /* Underflow points to policy (terminal) rule in + built-in, so sequality is valid here (when deleting + the last rule). */ + if ((*handle)->info.underflow[i] >= offset + rules_size) + (*handle)->info.underflow[i] -= rules_size; + else if ((*handle)->info.underflow[i] > offset) { + fprintf(stderr, "ERROR: Deleting uflow %u %u %u\n", + i, (*handle)->info.underflow[i], offset); + abort(); + } + } + + /* Move the rules down. */ + memmove((char *)(*handle)->entries.entrytable + offset, + (char *)(*handle)->entries.entrytable + offset + rules_size, + (*handle)->entries.size - (offset + rules_size)); + + /* Move the counter map down. */ + memmove(&(*handle)->counter_map[num_rules_offset], + &(*handle)->counter_map[num_rules_offset + num_rules], + sizeof(struct counter_map) + * ((*handle)->new_number - (num_rules + num_rules_offset))); + + /* Fix numbers */ + (*handle)->new_number -= num_rules; + (*handle)->entries.size -= rules_size; + + /* Fix the chain cache */ + if (!correct_cache(*handle, offset, -(int)rules_size)) + return 0; + + return set_verdict(offset, -(int)rules_size, handle); +} + +static int +standard_map(STRUCT_ENTRY *e, int verdict) +{ + STRUCT_STANDARD_TARGET *t; + + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (t->target.u.target_size + != ALIGN(sizeof(STRUCT_STANDARD_TARGET))) { + errno = EINVAL; + return 0; + } + /* memset for memcmp convenience on delete/replace */ + memset(t->target.u.user.name, 0, FUNCTION_MAXNAMELEN); + strcpy(t->target.u.user.name, STANDARD_TARGET); + t->verdict = verdict; + + return 1; +} + +static int +map_target(const TC_HANDLE_T handle, + STRUCT_ENTRY *e, + unsigned int offset, + STRUCT_ENTRY_TARGET *old) +{ + STRUCT_ENTRY_TARGET *t = GET_TARGET(e); + + /* Save old target (except data, which we don't change, except for + standard case, where we don't care). */ + *old = *t; + + /* Maybe it's empty (=> fall through) */ + if (strcmp(t->u.user.name, "") == 0) + return standard_map(e, offset + e->next_offset); + /* Maybe it's a standard target name... */ + else if (strcmp(t->u.user.name, LABEL_ACCEPT) == 0) + return standard_map(e, -NF_ACCEPT - 1); + else if (strcmp(t->u.user.name, LABEL_DROP) == 0) + return standard_map(e, -NF_DROP - 1); + else if (strcmp(t->u.user.name, LABEL_QUEUE) == 0) + return standard_map(e, -NF_QUEUE - 1); + else if (strcmp(t->u.user.name, LABEL_RETURN) == 0) + return standard_map(e, RETURN); + else if (TC_BUILTIN(t->u.user.name, handle)) { + /* Can't jump to builtins. */ + errno = EINVAL; + return 0; + } else { + /* Maybe it's an existing chain name. */ + struct chain_cache *c; + + c = find_label(t->u.user.name, handle); + if (c) + return standard_map(e, c->start_off); + } + + /* Must be a module? If not, kernel will reject... */ + /* memset to all 0 for your memcmp convenience. */ + memset(t->u.user.name + strlen(t->u.user.name), + 0, + FUNCTION_MAXNAMELEN - strlen(t->u.user.name)); + return 1; +} + +static void +unmap_target(STRUCT_ENTRY *e, STRUCT_ENTRY_TARGET *old) +{ + STRUCT_ENTRY_TARGET *t = GET_TARGET(e); + + /* Save old target (except data, which we don't change, except for + standard case, where we don't care). */ + *t = *old; +} + +/* Insert the entry `fw' in chain `chain' into position `rulenum'. */ +int +TC_INSERT_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int chainindex, offset; + STRUCT_ENTRY_TARGET old; + struct chain_cache *c; + STRUCT_ENTRY *tmp; + int ret; + + iptc_fn = TC_INSERT_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + + tmp = index2entry(*handle, chainindex + rulenum); + if (!tmp || tmp > offset2entry(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + offset = index2offset(*handle, chainindex + rulenum); + + /* Mapping target actually alters entry, but that's + transparent to the caller. */ + if (!map_target(*handle, (STRUCT_ENTRY *)e, offset, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, offset, + chainindex + rulenum, rulenum == 0, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +/* Atomically replace rule `rulenum' in `chain' with `fw'. */ +int +TC_REPLACE_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int chainindex, offset; + STRUCT_ENTRY_TARGET old; + struct chain_cache *c; + STRUCT_ENTRY *tmp; + int ret; + + iptc_fn = TC_REPLACE_ENTRY; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + + tmp = index2entry(*handle, chainindex + rulenum); + if (!tmp || tmp >= offset2entry(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + + offset = index2offset(*handle, chainindex + rulenum); + /* Replace = delete and insert. */ + if (!delete_rules(1, get_entry(*handle, offset)->next_offset, + offset, chainindex + rulenum, handle)) + return 0; + + if (!map_target(*handle, (STRUCT_ENTRY *)e, offset, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, offset, + chainindex + rulenum, 1, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +/* Append entry `fw' to chain `chain'. Equivalent to insert with + rulenum = length of chain. */ +int +TC_APPEND_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + TC_HANDLE_T *handle) +{ + struct chain_cache *c; + STRUCT_ENTRY_TARGET old; + int ret; + + iptc_fn = TC_APPEND_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + if (!map_target(*handle, (STRUCT_ENTRY *)e, + c->end_off, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, c->end_off, + offset2index(*handle, c->end_off), 0, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +static inline int +match_different(const STRUCT_ENTRY_MATCH *a, + const unsigned char *a_elems, + const unsigned char *b_elems, + unsigned char **maskptr) +{ + const STRUCT_ENTRY_MATCH *b; + unsigned int i; + + /* Offset of b is the same as a. */ + b = (void *)b_elems + ((unsigned char *)a - a_elems); + + if (a->u.match_size != b->u.match_size) + return 1; + + if (strcmp(a->u.user.name, b->u.user.name) != 0) + return 1; + + *maskptr += ALIGN(sizeof(*a)); + + for (i = 0; i < a->u.match_size - ALIGN(sizeof(*a)); i++) + if (((a->data[i] ^ b->data[i]) & (*maskptr)[i]) != 0) + return 1; + *maskptr += i; + return 0; +} + +static inline int +target_different(const unsigned char *a_targdata, + const unsigned char *b_targdata, + unsigned int tdatasize, + const unsigned char *mask) +{ + unsigned int i; + for (i = 0; i < tdatasize; i++) + if (((a_targdata[i] ^ b_targdata[i]) & mask[i]) != 0) + return 1; + + return 0; +} + +static int +is_same(const STRUCT_ENTRY *a, + const STRUCT_ENTRY *b, + unsigned char *matchmask); + +/* Delete the first rule in `chain' which matches `fw'. */ +int +TC_DELETE_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *origfw, + unsigned char *matchmask, + TC_HANDLE_T *handle) +{ + unsigned int offset; + struct chain_cache *c; + STRUCT_ENTRY *e, *fw; + + iptc_fn = TC_DELETE_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + fw = malloc(origfw->next_offset); + if (fw == NULL) { + errno = ENOMEM; + return 0; + } + + for (offset = c->start_off; offset < c->end_off; + offset += e->next_offset) { + STRUCT_ENTRY_TARGET discard; + + memcpy(fw, origfw, origfw->next_offset); + + /* FIXME: handle this in is_same --RR */ + if (!map_target(*handle, fw, offset, &discard)) { + free(fw); + return 0; + } + e = get_entry(*handle, offset); + +#if 0 + printf("Deleting:\n"); + dump_entry(newe); +#endif + if (is_same(e, fw, matchmask)) { + int ret; + ret = delete_rules(1, e->next_offset, + offset, entry2index(*handle, e), + handle); + free(fw); + return ret; + } + } + + free(fw); + errno = ENOENT; + return 0; +} + +/* Delete the rule in position `rulenum' in `chain'. */ +int +TC_DELETE_NUM_ENTRY(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int index; + int ret; + STRUCT_ENTRY *e; + struct chain_cache *c; + + iptc_fn = TC_DELETE_NUM_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + index = offset2index(*handle, c->start_off) + rulenum; + + if (index >= offset2index(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, index); + if (e == NULL) { + errno = EINVAL; + return 0; + } + + ret = delete_rules(1, e->next_offset, entry2offset(*handle, e), + index, handle); + return ret; +} + +/* Check the packet `fw' on chain `chain'. Returns the verdict, or + NULL and sets errno. */ +const char * +TC_CHECK_PACKET(const IPT_CHAINLABEL chain, + STRUCT_ENTRY *entry, + TC_HANDLE_T *handle) +{ + errno = ENOSYS; + return NULL; +} + +/* Flushes the entries in the given chain (ie. empties chain). */ +int +TC_FLUSH_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int startindex, endindex; + STRUCT_ENTRY *startentry, *endentry; + struct chain_cache *c; + int ret; + + iptc_fn = TC_FLUSH_ENTRIES; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + startindex = offset2index(*handle, c->start_off); + endindex = offset2index(*handle, c->end_off); + startentry = offset2entry(*handle, c->start_off); + endentry = offset2entry(*handle, c->end_off); + + ret = delete_rules(endindex - startindex, + (char *)endentry - (char *)startentry, + c->start_off, startindex, + handle); + return ret; +} + +/* Zeroes the counters in a chain. */ +int +TC_ZERO_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int i, end; + struct chain_cache *c; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + i = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + for (; i <= end; i++) { + if ((*handle)->counter_map[i].maptype ==COUNTER_MAP_NORMAL_MAP) + (*handle)->counter_map[i].maptype = COUNTER_MAP_ZEROED; + } + set_changed(*handle); + + return 1; +} + +STRUCT_COUNTERS * +TC_READ_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_READ_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return NULL; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return NULL; + } + + e = index2entry(*handle, chainindex + rulenum); + + return &e->counters; +} + +int +TC_ZERO_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_ZERO_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, chainindex + rulenum); + + if ((*handle)->counter_map[chainindex + rulenum].maptype + == COUNTER_MAP_NORMAL_MAP) { + (*handle)->counter_map[chainindex + rulenum].maptype + = COUNTER_MAP_ZEROED; + } + + set_changed(*handle); + + return 1; +} + +int +TC_SET_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_SET_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, chainindex + rulenum); + + (*handle)->counter_map[chainindex + rulenum].maptype + = COUNTER_MAP_SET; + + memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS)); + + set_changed(*handle); + + return 1; +} + +/* Creates a new chain. */ +/* To create a chain, create two rules: error node and unconditional + * return. */ +int +TC_CREATE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + int ret; + struct { + STRUCT_ENTRY head; + struct ipt_error_target name; + STRUCT_ENTRY ret; + STRUCT_STANDARD_TARGET target; + } newc; + unsigned int destination; + + iptc_fn = TC_CREATE_CHAIN; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(chain, *handle) + || strcmp(chain, LABEL_DROP) == 0 + || strcmp(chain, LABEL_ACCEPT) == 0 + || strcmp(chain, LABEL_QUEUE) == 0 + || strcmp(chain, LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (strlen(chain)+1 > sizeof(IPT_CHAINLABEL)) { + errno = EINVAL; + return 0; + } + + memset(&newc, 0, sizeof(newc)); + newc.head.target_offset = sizeof(STRUCT_ENTRY); + newc.head.next_offset + = sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(struct ipt_error_target)); + strcpy(newc.name.t.u.user.name, ERROR_TARGET); + newc.name.t.u.target_size = ALIGN(sizeof(struct ipt_error_target)); + strcpy(newc.name.error, chain); + + newc.ret.target_offset = sizeof(STRUCT_ENTRY); + newc.ret.next_offset + = sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + strcpy(newc.target.target.u.user.name, STANDARD_TARGET); + newc.target.target.u.target_size + = ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + newc.target.verdict = RETURN; + + destination = index2offset(*handle, (*handle)->new_number -1); + + /* Add just before terminal entry */ + ret = insert_rules(2, sizeof(newc), &newc.head, + destination, + (*handle)->new_number - 1, + 0, handle); + + set_changed(*handle); + + /* add chain cache info for this chain */ + add_chain_cache(*handle, chain, + destination+newc.head.next_offset, + destination+newc.head.next_offset); + + return ret; +} + +static int +count_ref(STRUCT_ENTRY *e, unsigned int offset, unsigned int *ref) +{ + STRUCT_STANDARD_TARGET *t; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) == 0) { + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (t->verdict == offset) + (*ref)++; + } + + return 0; +} + +/* Get the number of references to this chain. */ +int +TC_GET_REFERENCES(unsigned int *ref, const IPT_CHAINLABEL chain, + TC_HANDLE_T *handle) +{ + struct chain_cache *c; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + *ref = 0; + ENTRY_ITERATE((*handle)->entries.entrytable, + (*handle)->entries.size, + count_ref, c->start_off, ref); + return 1; +} + +/* Deletes a chain. */ +int +TC_DELETE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int labelidx, labeloff; + unsigned int references; + struct chain_cache *c; + int ret; + STRUCT_ENTRY *start; + + if (!TC_GET_REFERENCES(&references, chain, handle)) + return 0; + + iptc_fn = TC_DELETE_CHAIN; + + if (TC_BUILTIN(chain, *handle)) { + errno = EINVAL; + return 0; + } + + if (references > 0) { + errno = EMLINK; + return 0; + } + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + if (c->start_off != c->end_off) { + errno = ENOTEMPTY; + return 0; + } + + /* Need label index: preceeds chain start */ + labelidx = offset2index(*handle, c->start_off) - 1; + labeloff = index2offset(*handle, labelidx); + + start = offset2entry(*handle, c->start_off); + + ret = delete_rules(2, + get_entry(*handle, labeloff)->next_offset + + start->next_offset, + labeloff, labelidx, handle); + return ret; +} + +/* Renames a chain. */ +int TC_RENAME_CHAIN(const IPT_CHAINLABEL oldname, + const IPT_CHAINLABEL newname, + TC_HANDLE_T *handle) +{ + unsigned int labeloff, labelidx; + struct chain_cache *c; + struct ipt_error_target *t; + + iptc_fn = TC_RENAME_CHAIN; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(newname, *handle) + || strcmp(newname, LABEL_DROP) == 0 + || strcmp(newname, LABEL_ACCEPT) == 0 + || strcmp(newname, LABEL_QUEUE) == 0 + || strcmp(newname, LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (!(c = find_label(oldname, *handle)) + || TC_BUILTIN(oldname, *handle)) { + errno = ENOENT; + return 0; + } + + if (strlen(newname)+1 > sizeof(IPT_CHAINLABEL)) { + errno = EINVAL; + return 0; + } + + /* Need label index: preceeds chain start */ + labelidx = offset2index(*handle, c->start_off) - 1; + labeloff = index2offset(*handle, labelidx); + + t = (struct ipt_error_target *) + GET_TARGET(get_entry(*handle, labeloff)); + + memset(t->error, 0, sizeof(t->error)); + strcpy(t->error, newname); + + /* update chain cache */ + memset(c->name, 0, sizeof(c->name)); + strcpy(c->name, newname); + + set_changed(*handle); + + return 1; +} + +/* Sets the policy on a built-in chain. */ +int +TC_SET_POLICY(const IPT_CHAINLABEL chain, + const IPT_CHAINLABEL policy, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + unsigned int hook; + unsigned int policyoff, ctrindex; + STRUCT_ENTRY *e; + STRUCT_STANDARD_TARGET *t; + + iptc_fn = TC_SET_POLICY; + /* Figure out which chain. */ + hook = TC_BUILTIN(chain, *handle); + if (hook == 0) { + errno = ENOENT; + return 0; + } else + hook--; + + policyoff = get_chain_end(*handle, (*handle)->info.hook_entry[hook]); + if (policyoff != (*handle)->info.underflow[hook]) { + printf("ERROR: Policy for `%s' offset %u != underflow %u\n", + chain, policyoff, (*handle)->info.underflow[hook]); + return 0; + } + + e = get_entry(*handle, policyoff); + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (strcmp(policy, LABEL_ACCEPT) == 0) + t->verdict = -NF_ACCEPT - 1; + else if (strcmp(policy, LABEL_DROP) == 0) + t->verdict = -NF_DROP - 1; + else { + errno = EINVAL; + return 0; + } + + ctrindex = entry2index(*handle, e); + + if (counters) { + /* set byte and packet counters */ + memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS)); + + (*handle)->counter_map[ctrindex].maptype + = COUNTER_MAP_SET; + + } else { + (*handle)->counter_map[ctrindex] + = ((struct counter_map){ COUNTER_MAP_NOMAP, 0 }); + } + + set_changed(*handle); + + return 1; +} + +/* Without this, on gcc 2.7.2.3, we get: + libiptc.c: In function `TC_COMMIT': + libiptc.c:833: fixed or forbidden register was spilled. + This may be due to a compiler bug or to impossible asm + statements or clauses. +*/ +static void +subtract_counters(STRUCT_COUNTERS *answer, + const STRUCT_COUNTERS *a, + const STRUCT_COUNTERS *b) +{ + answer->pcnt = a->pcnt - b->pcnt; + answer->bcnt = a->bcnt - b->bcnt; +} + +int +TC_COMMIT(TC_HANDLE_T *handle) +{ + /* Replace, then map back the counters. */ + STRUCT_REPLACE *repl; + STRUCT_COUNTERS_INFO *newcounters; + unsigned int i; + size_t counterlen; + + CHECK(*handle); + + counterlen = sizeof(STRUCT_COUNTERS_INFO) + + sizeof(STRUCT_COUNTERS) * (*handle)->new_number; + +#if 0 + TC_DUMP_ENTRIES(*handle); +#endif + + /* Don't commit if nothing changed. */ + if (!(*handle)->changed) + goto finished; + + repl = malloc(sizeof(*repl) + (*handle)->entries.size); + if (!repl) { + errno = ENOMEM; + return 0; + } + + /* These are the old counters we will get from kernel */ + repl->counters = malloc(sizeof(STRUCT_COUNTERS) + * (*handle)->info.num_entries); + if (!repl->counters) { + free(repl); + errno = ENOMEM; + return 0; + } + + /* These are the counters we're going to put back, later. */ + newcounters = malloc(counterlen); + if (!newcounters) { + free(repl->counters); + free(repl); + errno = ENOMEM; + return 0; + } + + strcpy(repl->name, (*handle)->info.name); + repl->num_entries = (*handle)->new_number; + repl->size = (*handle)->entries.size; + memcpy(repl->hook_entry, (*handle)->info.hook_entry, + sizeof(repl->hook_entry)); + memcpy(repl->underflow, (*handle)->info.underflow, + sizeof(repl->underflow)); + repl->num_counters = (*handle)->info.num_entries; + repl->valid_hooks = (*handle)->info.valid_hooks; + memcpy(repl->entries, (*handle)->entries.entrytable, + (*handle)->entries.size); + + if (setsockopt(sockfd, TC_IPPROTO, SO_SET_REPLACE, repl, + sizeof(*repl) + (*handle)->entries.size) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + /* Put counters back. */ + strcpy(newcounters->name, (*handle)->info.name); + newcounters->num_counters = (*handle)->new_number; + for (i = 0; i < (*handle)->new_number; i++) { + unsigned int mappos = (*handle)->counter_map[i].mappos; + switch ((*handle)->counter_map[i].maptype) { + case COUNTER_MAP_NOMAP: + newcounters->counters[i] + = ((STRUCT_COUNTERS){ 0, 0 }); + break; + + case COUNTER_MAP_NORMAL_MAP: + /* Original read: X. + * Atomic read on replacement: X + Y. + * Currently in kernel: Z. + * Want in kernel: X + Y + Z. + * => Add in X + Y + * => Add in replacement read. + */ + newcounters->counters[i] = repl->counters[mappos]; + break; + + case COUNTER_MAP_ZEROED: + /* Original read: X. + * Atomic read on replacement: X + Y. + * Currently in kernel: Z. + * Want in kernel: Y + Z. + * => Add in Y. + * => Add in (replacement read - original read). + */ + subtract_counters(&newcounters->counters[i], + &repl->counters[mappos], + &index2entry(*handle, i)->counters); + break; + + case COUNTER_MAP_SET: + /* Want to set counter (iptables-restore) */ + + memcpy(&newcounters->counters[i], + &index2entry(*handle, i)->counters, + sizeof(STRUCT_COUNTERS)); + + break; + } + } + +#ifdef KERNEL_64_USERSPACE_32 + { + /* Kernel will think that pointer should be 64-bits, and get + padding. So we accomodate here (assumption: alignment of + `counters' is on 64-bit boundary). */ + u_int64_t *kernptr = (u_int64_t *)&newcounters->counters; + if ((unsigned long)&newcounters->counters % 8 != 0) { + fprintf(stderr, + "counters alignment incorrect! Mail rusty!\n"); + abort(); + } + *kernptr = newcounters->counters; + } +#endif /* KERNEL_64_USERSPACE_32 */ + + if (setsockopt(sockfd, TC_IPPROTO, SO_SET_ADD_COUNTERS, + newcounters, counterlen) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + free(repl->counters); + free(repl); + free(newcounters); + + finished: + TC_FREE(handle); + return 1; +} + +/* Get raw socket. */ +int +TC_GET_RAW_SOCKET() +{ + return sockfd; +} + +/* Translates errno numbers into more human-readable form than strerror. */ +const char * +TC_STRERROR(int err) +{ + unsigned int i; + struct table_struct { + void *fn; + int err; + const char *message; + } table [] = + { { TC_INIT, EPERM, "Permission denied (you must be root)" }, + { TC_INIT, EINVAL, "Module is wrong version" }, + { TC_INIT, ENOENT, + "Table does not exist (do you need to insmod?)" }, + { TC_DELETE_CHAIN, ENOTEMPTY, "Chain is not empty" }, + { TC_DELETE_CHAIN, EINVAL, "Can't delete built-in chain" }, + { TC_DELETE_CHAIN, EMLINK, + "Can't delete chain with references left" }, + { TC_CREATE_CHAIN, EEXIST, "Chain already exists" }, + { TC_INSERT_ENTRY, E2BIG, "Index of insertion too big" }, + { TC_REPLACE_ENTRY, E2BIG, "Index of replacement too big" }, + { TC_DELETE_NUM_ENTRY, E2BIG, "Index of deletion too big" }, + { TC_READ_COUNTER, E2BIG, "Index of counter too big" }, + { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" }, + { TC_INSERT_ENTRY, ELOOP, "Loop found in table" }, + { TC_INSERT_ENTRY, EINVAL, "Target problem" }, + /* EINVAL for CHECK probably means bad interface. */ + { TC_CHECK_PACKET, EINVAL, + "Bad arguments (does that interface exist?)" }, + { TC_CHECK_PACKET, ENOSYS, + "Checking will most likely never get implemented" }, + /* ENOENT for DELETE probably means no matching rule */ + { TC_DELETE_ENTRY, ENOENT, + "Bad rule (does a matching rule exist in that chain?)" }, + { TC_SET_POLICY, ENOENT, + "Bad built-in chain name" }, + { TC_SET_POLICY, EINVAL, + "Bad policy name" }, + + { NULL, 0, "Incompatible with this kernel" }, + { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" }, + { NULL, ENOSYS, "Will be implemented real soon. I promise ;)" }, + { NULL, ENOMEM, "Memory allocation problem" }, + { NULL, ENOENT, "No chain/target/match by that name" }, + }; + + for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) { + if ((!table[i].fn || table[i].fn == iptc_fn) + && table[i].err == err) + return table[i].message; + } + + return strerror(err); +} diff --git a/libiptc2/libiptc2.c b/libiptc2/libiptc2.c new file mode 100644 index 0000000..a7d7915 --- /dev/null +++ b/libiptc2/libiptc2.c @@ -0,0 +1,1758 @@ +/* Library which manipulates firewall rules. Version $Revision: 1.39 $ */ + +/* Architecture of firewall rules is as follows: + * + * Chains go INPUT, FORWARD, OUTPUT then user chains. + * Each user chain starts with an ERROR node. + * Every chain ends with an unconditional jump: a RETURN for user chains, + * and a POLICY for built-ins. + */ + +/* (C) 1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See + * COPYING for details). + * (C) 2000-2003 by the Netfilter Core Team + * + * 2003-Jun-20: Harald Welte target_offset; +} +#endif + +static int sockfd = -1; +static void *iptc_fn = NULL; + +static const char *hooknames[] += { [HOOK_PRE_ROUTING] "PREROUTING", + [HOOK_LOCAL_IN] "INPUT", + [HOOK_FORWARD] "FORWARD", + [HOOK_LOCAL_OUT] "OUTPUT", + [HOOK_POST_ROUTING] "POSTROUTING", +#ifdef HOOK_DROPPING + [HOOK_DROPPING] "DROPPING" +#endif +}; + +struct counter_map +{ + enum { + COUNTER_MAP_NOMAP, + COUNTER_MAP_NORMAL_MAP, + COUNTER_MAP_ZEROED, + COUNTER_MAP_SET + } maptype; + unsigned int mappos; +}; + +/* Convenience structures */ +struct ipt_error_target +{ + STRUCT_ENTRY_TARGET t; + char error[TABLE_MAXNAMELEN]; +}; + +struct rule_head +{ + struct list_head list; + + struct chain_head *chain; + + unsigned int size; + STRUCT_ENTRY entry[0]; +} + +struct chain_head +{ + struct list_head list; + + char name[TABLE_MAXNAMELEN]; + unsigned int hooknum; + struct list_head rules; +}; + +STRUCT_TC_HANDLE +{ + /* Have changes been made? */ + int changed; + + struct list_head chains; + + struct chain_head *chain_iterator_cur; + +#if 0 + /* Size in here reflects original state. */ + STRUCT_GETINFO info; + + struct counter_map *counter_map; + /* Array of hook names */ + const char **hooknames; + + /* Cached position of chain heads (NULL = no cache). */ + unsigned int cache_num_chains; + unsigned int cache_num_builtins; + struct chain_cache *cache_chain_heads; + + /* Chain iterator: current chain cache entry. */ + struct chain_cache *cache_chain_iteration; + + /* Rule iterator: terminal rule */ + STRUCT_ENTRY *cache_rule_end; + + /* Number in here reflects current state. */ + unsigned int new_number; + STRUCT_GET_ENTRIES entries; +#endif +}; + +static void +set_changed(TC_HANDLE_T h) +{ + h->changed = 1; +} + +#ifdef IPTC_DEBUG +static void do_check(TC_HANDLE_T h, unsigned int line); +#define CHECK(h) do { if (!getenv("IPTC_NO_CHECK")) do_check((h), __LINE__); } while(0) +#else +#define CHECK(h) +#endif + +static inline int +get_number(const STRUCT_ENTRY *i, + const STRUCT_ENTRY *seek, + unsigned int *pos) +{ + if (i == seek) + return 1; + (*pos)++; + return 0; +} + +static unsigned int +entry2index(const TC_HANDLE_T h, const STRUCT_ENTRY *seek) +{ + unsigned int pos = 0; + + if (ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + get_number, seek, &pos) == 0) { + fprintf(stderr, "ERROR: offset %i not an entry!\n", + (char *)seek - (char *)h->entries.entrytable); + abort(); + } + return pos; +} + +static inline int +get_entry_n(STRUCT_ENTRY *i, + unsigned int number, + unsigned int *pos, + STRUCT_ENTRY **pe) +{ + if (*pos == number) { + *pe = i; + return 1; + } + (*pos)++; + return 0; +} + +static STRUCT_ENTRY * +index2entry(TC_HANDLE_T h, unsigned int index) +{ + unsigned int pos = 0; + STRUCT_ENTRY *ret = NULL; + + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + get_entry_n, index, &pos, &ret); + + return ret; +} + +static inline STRUCT_ENTRY * +get_entry(TC_HANDLE_T h, unsigned int offset) +{ + return (STRUCT_ENTRY *)((char *)h->entries.entrytable + offset); +} + +static inline unsigned long +entry2offset(const TC_HANDLE_T h, const STRUCT_ENTRY *e) +{ + return (void *)e - (void *)h->entries.entrytable; +} + +static inline unsigned long +index2offset(TC_HANDLE_T h, unsigned int index) +{ + return entry2offset(h, index2entry(h, index)); +} + +static inline STRUCT_ENTRY * +offset2entry(TC_HANDLE_T h, unsigned int offset) +{ + return (STRUCT_ENTRY *) ((void *)h->entries.entrytable+offset); +} + +static inline unsigned int +offset2index(const TC_HANDLE_T h, unsigned int offset) +{ + return entry2index(h, offset2entry(h, offset)); +} + + +static const char * +get_errorlabel(TC_HANDLE_T h, unsigned int offset) +{ + STRUCT_ENTRY *e; + + e = get_entry(h, offset); + if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) != 0) { + fprintf(stderr, "ERROR: offset %u not an error node!\n", + offset); + abort(); + } + + return (const char *)GET_TARGET(e)->data; +} + +/* Allocate handle of given size */ +static TC_HANDLE_T +alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules) +{ + size_t len; + TC_HANDLE_T h; + + len = sizeof(STRUCT_TC_HANDLE) + + size + + num_rules * sizeof(struct counter_map); + + if ((h = malloc(len)) == NULL) { + errno = ENOMEM; + return NULL; + } + + h->changed = 0; + h->cache_num_chains = 0; + h->cache_chain_heads = NULL; + h->counter_map = (void *)h + + sizeof(STRUCT_TC_HANDLE) + + size; + strcpy(h->info.name, tablename); + strcpy(h->entries.name, tablename); + + return h; +} + +TC_HANDLE_T +TC_INIT(const char *tablename) +{ + TC_HANDLE_T h; + STRUCT_GETINFO info; + unsigned int i; + int tmp; + socklen_t s; + + iptc_fn = TC_INIT; + + if (sockfd != -1) { + close(sockfd); + sockfd = -1; + } + + if (strlen(tablename) >= TABLE_MAXNAMELEN) { + errno = EINVAL; + return NULL; + } + + sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW); + if (sockfd < 0) + return NULL; + + s = sizeof(info); + + strcpy(info.name, tablename); + if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0) + return NULL; + + if ((h = alloc_handle(info.name, info.size, info.num_entries)) + == NULL) { + close(sockfd); + sockfd = -1; + return NULL; + } + +/* Too hard --RR */ +#if 0 + sprintf(pathname, "%s/%s", IPT_LIB_DIR, info.name); + dynlib = dlopen(pathname, RTLD_NOW); + if (!dynlib) { + errno = ENOENT; + return NULL; + } + h->hooknames = dlsym(dynlib, "hooknames"); + if (!h->hooknames) { + errno = ENOENT; + return NULL; + } +#else + h->hooknames = hooknames; +#endif + + /* Initialize current state */ + h->info = info; + h->new_number = h->info.num_entries; + for (i = 0; i < h->info.num_entries; i++) + h->counter_map[i] + = ((struct counter_map){COUNTER_MAP_NORMAL_MAP, i}); + + h->entries.size = h->info.size; + + tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size; + + if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries, + &tmp) < 0) { + close(sockfd); + sockfd = -1; + free(h); + return NULL; + } + + CHECK(h); + return h; +} + +void +TC_FREE(TC_HANDLE_T *h) +{ + close(sockfd); + sockfd = -1; + if ((*h)->cache_chain_heads) + free((*h)->cache_chain_heads); + free(*h); + *h = NULL; +} + +static inline int +print_match(const STRUCT_ENTRY_MATCH *m) +{ + printf("Match name: `%s'\n", m->u.user.name); + return 0; +} + +static int dump_entry(STRUCT_ENTRY *e, const TC_HANDLE_T handle); + +void +TC_DUMP_ENTRIES(const TC_HANDLE_T handle) +{ + CHECK(handle); + + printf("libiptc v%s. %u entries, %u bytes.\n", + IPTABLES_VERSION, + handle->new_number, handle->entries.size); + printf("Table `%s'\n", handle->info.name); + printf("Hooks: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.hook_entry[HOOK_PRE_ROUTING], + handle->info.hook_entry[HOOK_LOCAL_IN], + handle->info.hook_entry[HOOK_FORWARD], + handle->info.hook_entry[HOOK_LOCAL_OUT], + handle->info.hook_entry[HOOK_POST_ROUTING]); + printf("Underflows: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.underflow[HOOK_PRE_ROUTING], + handle->info.underflow[HOOK_LOCAL_IN], + handle->info.underflow[HOOK_FORWARD], + handle->info.underflow[HOOK_LOCAL_OUT], + handle->info.underflow[HOOK_POST_ROUTING]); + + ENTRY_ITERATE(handle->entries.entrytable, handle->entries.size, + dump_entry, handle); +} + +/* Returns 0 if not hook entry, else hooknumber + 1 */ +static inline unsigned int +is_hook_entry(STRUCT_ENTRY *e, TC_HANDLE_T h) +{ + unsigned int i; + + for (i = 0; i < NUMHOOKS; i++) { + if ((h->info.valid_hooks & (1 << i)) + && get_entry(h, h->info.hook_entry[i]) == e) + return i+1; + } + return 0; +} +static int alphasort(const void *a, const void *b) +{ + return strcmp(((struct chain_cache *)a)->name, + ((struct chain_cache *)b)->name); +} + +/* Returns chain head if found, otherwise NULL. */ +static struct chain_head * +find_label(const char *name, TC_HANDLE_T handle) +{ + struct list_head *pos; + + if (!handle->chains) + return NULL; + + list_for_each(pos, &handle->chains) { + struct chain_head *c = list_entry(pos, struct chain_head, list); + if (!strcmp(c->name, name)) + return c; + } + + return NULL; +} + +/* Does this chain exist? */ +int TC_IS_CHAIN(const char *chain, const TC_HANDLE_T handle) +{ + return find_label(chain, handle) != NULL; +} + +/* Returns the position of the final (ie. unconditional) element. */ +static unsigned int +get_chain_end(const TC_HANDLE_T handle, unsigned int start) +{ + unsigned int last_off, off; + STRUCT_ENTRY *e; + + last_off = start; + e = get_entry(handle, start); + + /* Terminate when we meet a error label or a hook entry. */ + for (off = start + e->next_offset; + off < handle->entries.size; + last_off = off, off += e->next_offset) { + STRUCT_ENTRY_TARGET *t; + unsigned int i; + + e = get_entry(handle, off); + + /* We hit an entry point. */ + for (i = 0; i < NUMHOOKS; i++) { + if ((handle->info.valid_hooks & (1 << i)) + && off == handle->info.hook_entry[i]) + return last_off; + } + + /* We hit a user chain label */ + t = GET_TARGET(e); + if (strcmp(t->u.user.name, ERROR_TARGET) == 0) + return last_off; + } + /* SHOULD NEVER HAPPEN */ + fprintf(stderr, "ERROR: Off end (%u) of chain from %u!\n", + handle->entries.size, off); + abort(); +} + +/* Iterator functions to run through the chains. */ +const char * +TC_FIRST_CHAIN(TC_HANDLE_T *handle) +{ + (*handle)->chain_iterator_cur = (*handle)->chains; + + return (*handle)->chains.name; +} + +/* Iterator functions to run through the chains. Returns NULL at end. */ +const char * +TC_NEXT_CHAIN(TC_HANDLE_T *handle) +{ + struct chain_head *next = list_entry(&(*handle)->chain_iterator_cur->list.next, struct chain_head, list); + (*handle)->chain_iterator_cur = next; + + if (next == (*handle)->chains) + return NULL; + + return next->name; +} + +/* Get first rule in the given chain: NULL for empty chain. */ +const STRUCT_ENTRY * +TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct rule_head *r; + + c = find_label(chain, *handle); + if (!c) { + errno = ENOENT; + return NULL; + } + + /* Empty chain: single return/policy rule */ + if (list_empty(c->rules)) + return NULL; + + r = list_entry(&c->rules.next, struct rule_head, list); + (*handle)->rule_iterator_cur = r; + + return r->entry; +} + +/* Returns NULL when rules run out. */ +const STRUCT_ENTRY * +TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle) +{ + struct rule_head *r = list_entry((*handle)->rule_iterator_cur->list.next, struct rule_head, list); + + if (r == r->chain) + return NULL; + + /* NOTE: prev is without any influence ! */ + return r->entry; +} + +#if 0 +/* How many rules in this chain? */ +unsigned int +TC_NUM_RULES(const char *chain, TC_HANDLE_T *handle) +{ + unsigned int off = 0; + STRUCT_ENTRY *start, *end; + + CHECK(*handle); + if (!find_label(&off, chain, *handle)) { + errno = ENOENT; + return (unsigned int)-1; + } + + start = get_entry(*handle, off); + end = get_entry(*handle, get_chain_end(*handle, off)); + + return entry2index(*handle, end) - entry2index(*handle, start); +} + +/* Get n'th rule in this chain. */ +const STRUCT_ENTRY *TC_GET_RULE(const char *chain, + unsigned int n, + TC_HANDLE_T *handle) +{ + unsigned int pos = 0, chainindex; + + CHECK(*handle); + if (!find_label(&pos, chain, *handle)) { + errno = ENOENT; + return NULL; + } + + chainindex = entry2index(*handle, get_entry(*handle, pos)); + + return index2entry(*handle, chainindex + n); +} +#endif + +static const char * +target_name(TC_HANDLE_T handle, const STRUCT_ENTRY *ce) +{ + int spos; + unsigned int labelidx; + STRUCT_ENTRY *jumpto; + + /* To avoid const warnings */ + STRUCT_ENTRY *e = (STRUCT_ENTRY *)ce; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) != 0) + return GET_TARGET(e)->u.user.name; + + /* Standard target: evaluate */ + spos = *(int *)GET_TARGET(e)->data; + if (spos < 0) { + if (spos == RETURN) + return LABEL_RETURN; + else if (spos == -NF_ACCEPT-1) + return LABEL_ACCEPT; + else if (spos == -NF_DROP-1) + return LABEL_DROP; + else if (spos == -NF_QUEUE-1) + return LABEL_QUEUE; + + fprintf(stderr, "ERROR: off %lu/%u not a valid target (%i)\n", + entry2offset(handle, e), handle->entries.size, + spos); + abort(); + } + + jumpto = get_entry(handle, spos); + + /* Fall through rule */ + if (jumpto == (void *)e + e->next_offset) + return ""; + + /* Must point to head of a chain: ie. after error rule */ + labelidx = entry2index(handle, jumpto) - 1; + return get_errorlabel(handle, index2offset(handle, labelidx)); +} + +/* Returns a pointer to the target name of this position. */ +const char *TC_GET_TARGET(const STRUCT_ENTRY *e, + TC_HANDLE_T *handle) +{ + return target_name(*handle, e); +} + +/* Is this a built-in chain? Actually returns hook + 1. */ +int +TC_BUILTIN(const char *chain, const TC_HANDLE_T handle) +{ + unsigned int i; + + for (i = 0; i < NUMHOOKS; i++) { + if ((handle->info.valid_hooks & (1 << i)) + && handle->hooknames[i] + && strcmp(handle->hooknames[i], chain) == 0) + return i+1; + } + return 0; +} + +/* Get the policy of a given built-in chain */ +const char * +TC_GET_POLICY(const char *chain, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + unsigned int start; + STRUCT_ENTRY *e; + int hook; + + hook = TC_BUILTIN(chain, *handle); + if (hook != 0) + start = (*handle)->info.hook_entry[hook-1]; + else + return NULL; + + e = get_entry(*handle, get_chain_end(*handle, start)); + *counters = e->counters; + + return target_name(*handle, e); +} + +static int +correct_verdict(STRUCT_ENTRY *e, + char *base, + unsigned int offset, int delta_offset) +{ + STRUCT_STANDARD_TARGET *t = (void *)GET_TARGET(e); + unsigned int curr = (char *)e - base; + + /* Trap: insert of fall-through rule. Don't change fall-through + verdict to jump-over-next-rule. */ + if (strcmp(t->target.u.user.name, STANDARD_TARGET) == 0 + && t->verdict > (int)offset + && !(curr == offset && + t->verdict == curr + e->next_offset)) { + t->verdict += delta_offset; + } + + return 0; +} + +/* Adjusts standard verdict jump positions after an insertion/deletion. */ +static int +set_verdict(unsigned int offset, int delta_offset, TC_HANDLE_T *handle) +{ + ENTRY_ITERATE((*handle)->entries.entrytable, + (*handle)->entries.size, + correct_verdict, (char *)(*handle)->entries.entrytable, + offset, delta_offset); + + set_changed(*handle); + return 1; +} + +/* If prepend is set, then we are prepending to a chain: if the + * insertion position is an entry point, keep the entry point. */ +static int +insert_rules(unsigned int num_rules, unsigned int rules_size, + const STRUCT_ENTRY *insert, + unsigned int offset, unsigned int num_rules_offset, + int prepend, + TC_HANDLE_T *handle) +{ + TC_HANDLE_T newh; + STRUCT_GETINFO newinfo; + unsigned int i; + + if (offset >= (*handle)->entries.size) { + errno = EINVAL; + return 0; + } + + newinfo = (*handle)->info; + + /* Fix up entry points. */ + for (i = 0; i < NUMHOOKS; i++) { + /* Entry points to START of chain, so keep same if + inserting on at that point. */ + if ((*handle)->info.hook_entry[i] > offset) + newinfo.hook_entry[i] += rules_size; + + /* Underflow always points to END of chain (policy), + so if something is inserted at same point, it + should be advanced. */ + if ((*handle)->info.underflow[i] >= offset) + newinfo.underflow[i] += rules_size; + } + + newh = alloc_handle((*handle)->info.name, + (*handle)->entries.size + rules_size, + (*handle)->new_number + num_rules); + if (!newh) + return 0; + newh->info = newinfo; + + /* Copy pre... */ + memcpy(newh->entries.entrytable, (*handle)->entries.entrytable,offset); + /* ... Insert new ... */ + memcpy((char *)newh->entries.entrytable + offset, insert, rules_size); + /* ... copy post */ + memcpy((char *)newh->entries.entrytable + offset + rules_size, + (char *)(*handle)->entries.entrytable + offset, + (*handle)->entries.size - offset); + + /* Move counter map. */ + /* Copy pre... */ + memcpy(newh->counter_map, (*handle)->counter_map, + sizeof(struct counter_map) * num_rules_offset); + /* ... copy post */ + memcpy(newh->counter_map + num_rules_offset + num_rules, + (*handle)->counter_map + num_rules_offset, + sizeof(struct counter_map) * ((*handle)->new_number + - num_rules_offset)); + /* Set intermediates to no counter copy */ + for (i = 0; i < num_rules; i++) + newh->counter_map[num_rules_offset+i] + = ((struct counter_map){ COUNTER_MAP_SET, 0 }); + + newh->new_number = (*handle)->new_number + num_rules; + newh->entries.size = (*handle)->entries.size + rules_size; + newh->hooknames = (*handle)->hooknames; + + if ((*handle)->cache_chain_heads) + free((*handle)->cache_chain_heads); + free(*handle); + *handle = newh; + + return set_verdict(offset, rules_size, handle); +} + +static int +delete_rules(unsigned int num_rules, unsigned int rules_size, + unsigned int offset, unsigned int num_rules_offset, + TC_HANDLE_T *handle) +{ + unsigned int i; + + if (offset + rules_size > (*handle)->entries.size) { + errno = EINVAL; + return 0; + } + + /* Fix up entry points. */ + for (i = 0; i < NUMHOOKS; i++) { + /* In practice, we never delete up to a hook entry, + since the built-in chains are always first, + so these two are never equal */ + if ((*handle)->info.hook_entry[i] >= offset + rules_size) + (*handle)->info.hook_entry[i] -= rules_size; + else if ((*handle)->info.hook_entry[i] > offset) { + fprintf(stderr, "ERROR: Deleting entry %u %u %u\n", + i, (*handle)->info.hook_entry[i], offset); + abort(); + } + + /* Underflow points to policy (terminal) rule in + built-in, so sequality is valid here (when deleting + the last rule). */ + if ((*handle)->info.underflow[i] >= offset + rules_size) + (*handle)->info.underflow[i] -= rules_size; + else if ((*handle)->info.underflow[i] > offset) { + fprintf(stderr, "ERROR: Deleting uflow %u %u %u\n", + i, (*handle)->info.underflow[i], offset); + abort(); + } + } + + /* Move the rules down. */ + memmove((char *)(*handle)->entries.entrytable + offset, + (char *)(*handle)->entries.entrytable + offset + rules_size, + (*handle)->entries.size - (offset + rules_size)); + + /* Move the counter map down. */ + memmove(&(*handle)->counter_map[num_rules_offset], + &(*handle)->counter_map[num_rules_offset + num_rules], + sizeof(struct counter_map) + * ((*handle)->new_number - (num_rules + num_rules_offset))); + + /* Fix numbers */ + (*handle)->new_number -= num_rules; + (*handle)->entries.size -= rules_size; + + return set_verdict(offset, -(int)rules_size, handle); +} + +static int +standard_map(STRUCT_ENTRY *e, int verdict) +{ + STRUCT_STANDARD_TARGET *t; + + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (t->target.u.target_size + != ALIGN(sizeof(STRUCT_STANDARD_TARGET))) { + errno = EINVAL; + return 0; + } + /* memset for memcmp convenience on delete/replace */ + memset(t->target.u.user.name, 0, FUNCTION_MAXNAMELEN); + strcpy(t->target.u.user.name, STANDARD_TARGET); + t->verdict = verdict; + + return 1; +} + +static int +map_target(const TC_HANDLE_T handle, + STRUCT_ENTRY *e, + unsigned int offset, + STRUCT_ENTRY_TARGET *old) +{ + STRUCT_ENTRY_TARGET *t = GET_TARGET(e); + + /* Save old target (except data, which we don't change, except for + standard case, where we don't care). */ + *old = *t; + + /* Maybe it's empty (=> fall through) */ + if (strcmp(t->u.user.name, "") == 0) + return standard_map(e, offset + e->next_offset); + /* Maybe it's a standard target name... */ + else if (strcmp(t->u.user.name, LABEL_ACCEPT) == 0) + return standard_map(e, -NF_ACCEPT - 1); + else if (strcmp(t->u.user.name, LABEL_DROP) == 0) + return standard_map(e, -NF_DROP - 1); + else if (strcmp(t->u.user.name, LABEL_QUEUE) == 0) + return standard_map(e, -NF_QUEUE - 1); + else if (strcmp(t->u.user.name, LABEL_RETURN) == 0) + return standard_map(e, RETURN); + else if (TC_BUILTIN(t->u.user.name, handle)) { + /* Can't jump to builtins. */ + errno = EINVAL; + return 0; + } else { + /* Maybe it's an existing chain name. */ + struct chain_cache *c; + + c = find_label(t->u.user.name, handle); + if (c) + return standard_map(e, c->start_off); + } + + /* Must be a module? If not, kernel will reject... */ + /* memset to all 0 for your memcmp convenience. */ + memset(t->u.user.name + strlen(t->u.user.name), + 0, + FUNCTION_MAXNAMELEN - strlen(t->u.user.name)); + return 1; +} + +static void +unmap_target(STRUCT_ENTRY *e, STRUCT_ENTRY_TARGET *old) +{ + STRUCT_ENTRY_TARGET *t = GET_TARGET(e); + + /* Save old target (except data, which we don't change, except for + standard case, where we don't care). */ + *t = *old; +} + +/* Insert the entry `fw' in chain `chain' into position `rulenum'. */ +int +TC_INSERT_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int chainindex, offset; + STRUCT_ENTRY_TARGET old; + struct chain_cache *c; + STRUCT_ENTRY *tmp; + int ret; + + iptc_fn = TC_INSERT_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + + tmp = index2entry(*handle, chainindex + rulenum); + if (!tmp || tmp > offset2entry(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + offset = index2offset(*handle, chainindex + rulenum); + + /* Mapping target actually alters entry, but that's + transparent to the caller. */ + if (!map_target(*handle, (STRUCT_ENTRY *)e, offset, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, offset, + chainindex + rulenum, rulenum == 0, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +/* Atomically replace rule `rulenum' in `chain' with `fw'. */ +int +TC_REPLACE_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int chainindex, offset; + STRUCT_ENTRY_TARGET old; + struct chain_cache *c; + STRUCT_ENTRY *tmp; + int ret; + + iptc_fn = TC_REPLACE_ENTRY; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + + tmp = index2entry(*handle, chainindex + rulenum); + if (!tmp || tmp >= offset2entry(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + + offset = index2offset(*handle, chainindex + rulenum); + /* Replace = delete and insert. */ + if (!delete_rules(1, get_entry(*handle, offset)->next_offset, + offset, chainindex + rulenum, handle)) + return 0; + + if (!map_target(*handle, (STRUCT_ENTRY *)e, offset, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, offset, + chainindex + rulenum, 1, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +/* Append entry `fw' to chain `chain'. Equivalent to insert with + rulenum = length of chain. */ +int +TC_APPEND_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + TC_HANDLE_T *handle) +{ + struct chain_cache *c; + STRUCT_ENTRY_TARGET old; + int ret; + + iptc_fn = TC_APPEND_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + if (!map_target(*handle, (STRUCT_ENTRY *)e, + c->end_off, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, c->end_off, + offset2index(*handle, c->end_off), 0, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +static inline int +match_different(const STRUCT_ENTRY_MATCH *a, + const unsigned char *a_elems, + const unsigned char *b_elems, + unsigned char **maskptr) +{ + const STRUCT_ENTRY_MATCH *b; + unsigned int i; + + /* Offset of b is the same as a. */ + b = (void *)b_elems + ((unsigned char *)a - a_elems); + + if (a->u.match_size != b->u.match_size) + return 1; + + if (strcmp(a->u.user.name, b->u.user.name) != 0) + return 1; + + *maskptr += ALIGN(sizeof(*a)); + + for (i = 0; i < a->u.match_size - ALIGN(sizeof(*a)); i++) + if (((a->data[i] ^ b->data[i]) & (*maskptr)[i]) != 0) + return 1; + *maskptr += i; + return 0; +} + +static inline int +target_different(const unsigned char *a_targdata, + const unsigned char *b_targdata, + unsigned int tdatasize, + const unsigned char *mask) +{ + unsigned int i; + for (i = 0; i < tdatasize; i++) + if (((a_targdata[i] ^ b_targdata[i]) & mask[i]) != 0) + return 1; + + return 0; +} + +static int +is_same(const STRUCT_ENTRY *a, + const STRUCT_ENTRY *b, + unsigned char *matchmask); + +/* Delete the first rule in `chain' which matches `fw'. */ +int +TC_DELETE_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *origfw, + unsigned char *matchmask, + TC_HANDLE_T *handle) +{ + unsigned int offset; + struct chain_cache *c; + STRUCT_ENTRY *e, *fw; + + iptc_fn = TC_DELETE_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + fw = malloc(origfw->next_offset); + if (fw == NULL) { + errno = ENOMEM; + return 0; + } + + for (offset = c->start_off; offset < c->end_off; + offset += e->next_offset) { + STRUCT_ENTRY_TARGET discard; + + memcpy(fw, origfw, origfw->next_offset); + + /* FIXME: handle this in is_same --RR */ + if (!map_target(*handle, fw, offset, &discard)) { + free(fw); + return 0; + } + e = get_entry(*handle, offset); + +#if 0 + printf("Deleting:\n"); + dump_entry(newe); +#endif + if (is_same(e, fw, matchmask)) { + int ret; + ret = delete_rules(1, e->next_offset, + offset, entry2index(*handle, e), + handle); + free(fw); + return ret; + } + } + + free(fw); + errno = ENOENT; + return 0; +} + +/* Delete the rule in position `rulenum' in `chain'. */ +int +TC_DELETE_NUM_ENTRY(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int index; + int ret; + STRUCT_ENTRY *e; + struct chain_cache *c; + + iptc_fn = TC_DELETE_NUM_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + index = offset2index(*handle, c->start_off) + rulenum; + + if (index >= offset2index(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, index); + if (e == NULL) { + errno = EINVAL; + return 0; + } + + ret = delete_rules(1, e->next_offset, entry2offset(*handle, e), + index, handle); + return ret; +} + +/* Check the packet `fw' on chain `chain'. Returns the verdict, or + NULL and sets errno. */ +const char * +TC_CHECK_PACKET(const IPT_CHAINLABEL chain, + STRUCT_ENTRY *entry, + TC_HANDLE_T *handle) +{ + errno = ENOSYS; + return NULL; +} + +/* Flushes the entries in the given chain (ie. empties chain). */ +int +TC_FLUSH_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int startindex, endindex; + STRUCT_ENTRY *startentry, *endentry; + struct chain_cache *c; + int ret; + + iptc_fn = TC_FLUSH_ENTRIES; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + startindex = offset2index(*handle, c->start_off); + endindex = offset2index(*handle, c->end_off); + startentry = offset2entry(*handle, c->start_off); + endentry = offset2entry(*handle, c->end_off); + + ret = delete_rules(endindex - startindex, + (char *)endentry - (char *)startentry, + c->start_off, startindex, + handle); + return ret; +} + +/* Zeroes the counters in a chain. */ +int +TC_ZERO_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int i, end; + struct chain_cache *c; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + i = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + for (; i <= end; i++) { + if ((*handle)->counter_map[i].maptype ==COUNTER_MAP_NORMAL_MAP) + (*handle)->counter_map[i].maptype = COUNTER_MAP_ZEROED; + } + set_changed(*handle); + + return 1; +} + +STRUCT_COUNTERS * +TC_READ_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_READ_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return NULL; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return NULL; + } + + e = index2entry(*handle, chainindex + rulenum); + + return &e->counters; +} + +int +TC_ZERO_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_ZERO_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, chainindex + rulenum); + + if ((*handle)->counter_map[chainindex + rulenum].maptype + == COUNTER_MAP_NORMAL_MAP) { + (*handle)->counter_map[chainindex + rulenum].maptype + = COUNTER_MAP_ZEROED; + } + + set_changed(*handle); + + return 1; +} + +int +TC_SET_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_SET_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, chainindex + rulenum); + + (*handle)->counter_map[chainindex + rulenum].maptype + = COUNTER_MAP_SET; + + memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS)); + + set_changed(*handle); + + return 1; +} + +/* Creates a new chain. */ +/* To create a chain, create two rules: error node and unconditional + * return. */ +int +TC_CREATE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + int ret; + struct { + STRUCT_ENTRY head; + struct ipt_error_target name; + STRUCT_ENTRY ret; + STRUCT_STANDARD_TARGET target; + } newc; + + iptc_fn = TC_CREATE_CHAIN; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(chain, *handle) + || strcmp(chain, LABEL_DROP) == 0 + || strcmp(chain, LABEL_ACCEPT) == 0 + || strcmp(chain, LABEL_QUEUE) == 0 + || strcmp(chain, LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (strlen(chain)+1 > sizeof(IPT_CHAINLABEL)) { + errno = EINVAL; + return 0; + } + + memset(&newc, 0, sizeof(newc)); + newc.head.target_offset = sizeof(STRUCT_ENTRY); + newc.head.next_offset + = sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(struct ipt_error_target)); + strcpy(newc.name.t.u.user.name, ERROR_TARGET); + newc.name.t.u.target_size = ALIGN(sizeof(struct ipt_error_target)); + strcpy(newc.name.error, chain); + + newc.ret.target_offset = sizeof(STRUCT_ENTRY); + newc.ret.next_offset + = sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + strcpy(newc.target.target.u.user.name, STANDARD_TARGET); + newc.target.target.u.target_size + = ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + newc.target.verdict = RETURN; + + /* Add just before terminal entry */ + ret = insert_rules(2, sizeof(newc), &newc.head, + index2offset(*handle, (*handle)->new_number - 1), + (*handle)->new_number - 1, + 0, handle); + return ret; +} + +static int +count_ref(STRUCT_ENTRY *e, unsigned int offset, unsigned int *ref) +{ + STRUCT_STANDARD_TARGET *t; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) == 0) { + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (t->verdict == offset) + (*ref)++; + } + + return 0; +} + +/* Get the number of references to this chain. */ +int +TC_GET_REFERENCES(unsigned int *ref, const IPT_CHAINLABEL chain, + TC_HANDLE_T *handle) +{ + struct chain_cache *c; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + *ref = 0; + ENTRY_ITERATE((*handle)->entries.entrytable, + (*handle)->entries.size, + count_ref, c->start_off, ref); + return 1; +} + +/* Deletes a chain. */ +int +TC_DELETE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int labelidx, labeloff; + unsigned int references; + struct chain_cache *c; + int ret; + STRUCT_ENTRY *start; + + if (!TC_GET_REFERENCES(&references, chain, handle)) + return 0; + + iptc_fn = TC_DELETE_CHAIN; + + if (TC_BUILTIN(chain, *handle)) { + errno = EINVAL; + return 0; + } + + if (references > 0) { + errno = EMLINK; + return 0; + } + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + if (c->start_off != c->end_off) { + errno = ENOTEMPTY; + return 0; + } + + /* Need label index: preceeds chain start */ + labelidx = offset2index(*handle, c->start_off) - 1; + labeloff = index2offset(*handle, labelidx); + + start = offset2entry(*handle, c->start_off); + + ret = delete_rules(2, + get_entry(*handle, labeloff)->next_offset + + start->next_offset, + labeloff, labelidx, handle); + return ret; +} + +/* Renames a chain. */ +int TC_RENAME_CHAIN(const IPT_CHAINLABEL oldname, + const IPT_CHAINLABEL newname, + TC_HANDLE_T *handle) +{ + unsigned int labeloff, labelidx; + struct chain_cache *c; + struct ipt_error_target *t; + + iptc_fn = TC_RENAME_CHAIN; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(newname, *handle) + || strcmp(newname, LABEL_DROP) == 0 + || strcmp(newname, LABEL_ACCEPT) == 0 + || strcmp(newname, LABEL_QUEUE) == 0 + || strcmp(newname, LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (!(c = find_label(oldname, *handle)) + || TC_BUILTIN(oldname, *handle)) { + errno = ENOENT; + return 0; + } + + if (strlen(newname)+1 > sizeof(IPT_CHAINLABEL)) { + errno = EINVAL; + return 0; + } + + /* Need label index: preceeds chain start */ + labelidx = offset2index(*handle, c->start_off) - 1; + labeloff = index2offset(*handle, labelidx); + + t = (struct ipt_error_target *) + GET_TARGET(get_entry(*handle, labeloff)); + + memset(t->error, 0, sizeof(t->error)); + strcpy(t->error, newname); + set_changed(*handle); + + return 1; +} + +/* Sets the policy on a built-in chain. */ +int +TC_SET_POLICY(const IPT_CHAINLABEL chain, + const IPT_CHAINLABEL policy, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + unsigned int hook; + unsigned int policyoff, ctrindex; + STRUCT_ENTRY *e; + STRUCT_STANDARD_TARGET *t; + + iptc_fn = TC_SET_POLICY; + /* Figure out which chain. */ + hook = TC_BUILTIN(chain, *handle); + if (hook == 0) { + errno = ENOENT; + return 0; + } else + hook--; + + policyoff = get_chain_end(*handle, (*handle)->info.hook_entry[hook]); + if (policyoff != (*handle)->info.underflow[hook]) { + printf("ERROR: Policy for `%s' offset %u != underflow %u\n", + chain, policyoff, (*handle)->info.underflow[hook]); + return 0; + } + + e = get_entry(*handle, policyoff); + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (strcmp(policy, LABEL_ACCEPT) == 0) + t->verdict = -NF_ACCEPT - 1; + else if (strcmp(policy, LABEL_DROP) == 0) + t->verdict = -NF_DROP - 1; + else { + errno = EINVAL; + return 0; + } + + ctrindex = entry2index(*handle, e); + + if (counters) { + /* set byte and packet counters */ + memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS)); + + (*handle)->counter_map[ctrindex].maptype + = COUNTER_MAP_SET; + + } else { + (*handle)->counter_map[ctrindex] + = ((struct counter_map){ COUNTER_MAP_NOMAP, 0 }); + } + + set_changed(*handle); + + return 1; +} + +/* Without this, on gcc 2.7.2.3, we get: + libiptc.c: In function `TC_COMMIT': + libiptc.c:833: fixed or forbidden register was spilled. + This may be due to a compiler bug or to impossible asm + statements or clauses. +*/ +static void +subtract_counters(STRUCT_COUNTERS *answer, + const STRUCT_COUNTERS *a, + const STRUCT_COUNTERS *b) +{ + answer->pcnt = a->pcnt - b->pcnt; + answer->bcnt = a->bcnt - b->bcnt; +} + +int +TC_COMMIT(TC_HANDLE_T *handle) +{ + /* Replace, then map back the counters. */ + STRUCT_REPLACE *repl; + STRUCT_COUNTERS_INFO *newcounters; + unsigned int i; + size_t counterlen; + + CHECK(*handle); + + counterlen = sizeof(STRUCT_COUNTERS_INFO) + + sizeof(STRUCT_COUNTERS) * (*handle)->new_number; + +#if 0 + TC_DUMP_ENTRIES(*handle); +#endif + + /* Don't commit if nothing changed. */ + if (!(*handle)->changed) + goto finished; + + repl = malloc(sizeof(*repl) + (*handle)->entries.size); + if (!repl) { + errno = ENOMEM; + return 0; + } + + /* These are the old counters we will get from kernel */ + repl->counters = malloc(sizeof(STRUCT_COUNTERS) + * (*handle)->info.num_entries); + if (!repl->counters) { + free(repl); + errno = ENOMEM; + return 0; + } + + /* These are the counters we're going to put back, later. */ + newcounters = malloc(counterlen); + if (!newcounters) { + free(repl->counters); + free(repl); + errno = ENOMEM; + return 0; + } + + strcpy(repl->name, (*handle)->info.name); + repl->num_entries = (*handle)->new_number; + repl->size = (*handle)->entries.size; + memcpy(repl->hook_entry, (*handle)->info.hook_entry, + sizeof(repl->hook_entry)); + memcpy(repl->underflow, (*handle)->info.underflow, + sizeof(repl->underflow)); + repl->num_counters = (*handle)->info.num_entries; + repl->valid_hooks = (*handle)->info.valid_hooks; + memcpy(repl->entries, (*handle)->entries.entrytable, + (*handle)->entries.size); + + if (setsockopt(sockfd, TC_IPPROTO, SO_SET_REPLACE, repl, + sizeof(*repl) + (*handle)->entries.size) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + /* Put counters back. */ + strcpy(newcounters->name, (*handle)->info.name); + newcounters->num_counters = (*handle)->new_number; + for (i = 0; i < (*handle)->new_number; i++) { + unsigned int mappos = (*handle)->counter_map[i].mappos; + switch ((*handle)->counter_map[i].maptype) { + case COUNTER_MAP_NOMAP: + newcounters->counters[i] + = ((STRUCT_COUNTERS){ 0, 0 }); + break; + + case COUNTER_MAP_NORMAL_MAP: + /* Original read: X. + * Atomic read on replacement: X + Y. + * Currently in kernel: Z. + * Want in kernel: X + Y + Z. + * => Add in X + Y + * => Add in replacement read. + */ + newcounters->counters[i] = repl->counters[mappos]; + break; + + case COUNTER_MAP_ZEROED: + /* Original read: X. + * Atomic read on replacement: X + Y. + * Currently in kernel: Z. + * Want in kernel: Y + Z. + * => Add in Y. + * => Add in (replacement read - original read). + */ + subtract_counters(&newcounters->counters[i], + &repl->counters[mappos], + &index2entry(*handle, i)->counters); + break; + + case COUNTER_MAP_SET: + /* Want to set counter (iptables-restore) */ + + memcpy(&newcounters->counters[i], + &index2entry(*handle, i)->counters, + sizeof(STRUCT_COUNTERS)); + + break; + } + } + +#ifdef KERNEL_64_USERSPACE_32 + { + /* Kernel will think that pointer should be 64-bits, and get + padding. So we accomodate here (assumption: alignment of + `counters' is on 64-bit boundary). */ + u_int64_t *kernptr = (u_int64_t *)&newcounters->counters; + if ((unsigned long)&newcounters->counters % 8 != 0) { + fprintf(stderr, + "counters alignment incorrect! Mail rusty!\n"); + abort(); + } + *kernptr = newcounters->counters; + } +#endif /* KERNEL_64_USERSPACE_32 */ + + if (setsockopt(sockfd, TC_IPPROTO, SO_SET_ADD_COUNTERS, + newcounters, counterlen) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + free(repl->counters); + free(repl); + free(newcounters); + + finished: + TC_FREE(handle); + return 1; +} + +/* Get raw socket. */ +int +TC_GET_RAW_SOCKET() +{ + return sockfd; +} + +/* Translates errno numbers into more human-readable form than strerror. */ +const char * +TC_STRERROR(int err) +{ + unsigned int i; + struct table_struct { + void *fn; + int err; + const char *message; + } table [] = + { { TC_INIT, EPERM, "Permission denied (you must be root)" }, + { TC_INIT, EINVAL, "Module is wrong version" }, + { TC_INIT, ENOENT, + "Table does not exist (do you need to insmod?)" }, + { TC_DELETE_CHAIN, ENOTEMPTY, "Chain is not empty" }, + { TC_DELETE_CHAIN, EINVAL, "Can't delete built-in chain" }, + { TC_DELETE_CHAIN, EMLINK, + "Can't delete chain with references left" }, + { TC_CREATE_CHAIN, EEXIST, "Chain already exists" }, + { TC_INSERT_ENTRY, E2BIG, "Index of insertion too big" }, + { TC_REPLACE_ENTRY, E2BIG, "Index of replacement too big" }, + { TC_DELETE_NUM_ENTRY, E2BIG, "Index of deletion too big" }, + { TC_READ_COUNTER, E2BIG, "Index of counter too big" }, + { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" }, + { TC_INSERT_ENTRY, ELOOP, "Loop found in table" }, + { TC_INSERT_ENTRY, EINVAL, "Target problem" }, + /* EINVAL for CHECK probably means bad interface. */ + { TC_CHECK_PACKET, EINVAL, + "Bad arguments (does that interface exist?)" }, + { TC_CHECK_PACKET, ENOSYS, + "Checking will most likely never get implemented" }, + /* ENOENT for DELETE probably means no matching rule */ + { TC_DELETE_ENTRY, ENOENT, + "Bad rule (does a matching rule exist in that chain?)" }, + { TC_SET_POLICY, ENOENT, + "Bad built-in chain name" }, + { TC_SET_POLICY, EINVAL, + "Bad policy name" }, + + { NULL, 0, "Incompatible with this kernel" }, + { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" }, + { NULL, ENOSYS, "Will be implemented real soon. I promise ;)" }, + { NULL, ENOMEM, "Memory allocation problem" }, + { NULL, ENOENT, "No chain/target/match by that name" }, + }; + + for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) { + if ((!table[i].fn || table[i].fn == iptc_fn) + && table[i].err == err) + return table[i].message; + } + + return strerror(err); +} diff --git a/libiptc2/linux_list.h b/libiptc2/linux_list.h new file mode 100644 index 0000000..41de482 --- /dev/null +++ b/libiptc2/linux_list.h @@ -0,0 +1,161 @@ +#ifndef _LINUXLIST_H +#define _LINUXLIST_H + + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_add(struct list_head * new, + struct list_head * prev, + struct list_head * next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static __inline__ void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static __inline__ void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_del(struct list_head * prev, + struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is in an undefined state. + */ +static __inline__ void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static __inline__ void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static __inline__ int list_empty(struct list_head *head) +{ + return head->next == head; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static __inline__ void list_splice(struct list_head *list, struct list_head *head) +{ + struct list_head *first = list->next; + + if (first != list) { + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +#endif diff --git a/libiptc2/linux_listhelp.h b/libiptc2/linux_listhelp.h new file mode 100644 index 0000000..466d921 --- /dev/null +++ b/libiptc2/linux_listhelp.h @@ -0,0 +1,99 @@ +#ifndef _LINUX_LISTHELP_H +#define _LINUX_LISTHELP_H +#include +#include +#include "linux_list.h" + +/* Header to do more comprehensive job than linux/list.h; assume list + is first entry in structure. */ + +/* Return pointer to first true entry, if any, or NULL. A macro + required to allow inlining of cmpfn. */ +#define LIST_FIND(head, cmpfn, type, args...) \ +({ \ + const struct list_head *__i = (head); \ + \ + do { \ + __i = __i->next; \ + if (__i == (head)) { \ + __i = NULL; \ + break; \ + } \ + } while (!cmpfn((const type)__i , ## args)); \ + (type)__i; \ +}) + +#define LIST_FIND_W(head, cmpfn, type, args...) \ +({ \ + const struct list_head *__i = (head); \ + \ + do { \ + __i = __i->next; \ + if (__i == (head)) { \ + __i = NULL; \ + break; \ + } \ + } while (!cmpfn((type)__i , ## args)); \ + (type)__i; \ +}) + +static inline int +__list_cmp_same(const void *p1, const void *p2) { return p1 == p2; } + +/* Is this entry in the list? */ +static inline int +list_inlist(struct list_head *head, const void *entry) +{ + return LIST_FIND(head, __list_cmp_same, void *, entry) != NULL; +} + +/* Delete from list. */ +#define LIST_DELETE(head, oldentry) list_del((struct list_head *)oldentry) + +/* Append. */ +static inline void +list_append(struct list_head *head, void *new) +{ + list_add((new), (head)->prev); +} + +/* Prepend. */ +static inline void +list_prepend(struct list_head *head, void *new) +{ + list_add(new, head); +} + +/* Insert according to ordering function; insert before first true. */ +#define LIST_INSERT(head, new, cmpfn) \ +do { \ + struct list_head *__i; \ + for (__i = (head)->next; \ + !cmpfn((new), (typeof (new))__i) && __i != (head); \ + __i = __i->next); \ + list_add((struct list_head *)(new), __i->prev); \ +} while(0) + +/* If the field after the list_head is a nul-terminated string, you + can use these functions. */ +static inline int __list_cmp_name(const void *i, const char *name) +{ + return strcmp(name, i+sizeof(struct list_head)) == 0; +} + +/* Returns false if same name already in list, otherwise does insert. */ +static inline int +list_named_insert(struct list_head *head, void *new) +{ + if (LIST_FIND(head, __list_cmp_name, void *, + new + sizeof(struct list_head))) + return 0; + list_prepend(head, new); + return 1; +} + +/* Find this named element in the list. */ +#define list_named_find(head, name) \ +LIST_FIND(head, __list_cmp_name, void *, name) + +#endif /*_LISTHELP_H*/ -- 2.43.0