summaryrefslogtreecommitdiffstats
path: root/lib/ipset.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ipset.c')
-rw-r--r--lib/ipset.c1472
1 files changed, 1472 insertions, 0 deletions
diff --git a/lib/ipset.c b/lib/ipset.c
new file mode 100644
index 0000000..a52f3d6
--- /dev/null
+++ b/lib/ipset.c
@@ -0,0 +1,1472 @@
+/* Copyright 2007-2010 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <assert.h> /* assert */
+#include <ctype.h> /* isspace */
+#include <errno.h> /* errno */
+#include <stdarg.h> /* va_* */
+#include <stdbool.h> /* bool */
+#include <stdio.h> /* printf */
+#include <stdlib.h> /* exit */
+#include <string.h> /* str* */
+
+#include <config.h>
+
+#include <libipset/debug.h> /* D() */
+#include <libipset/linux_ip_set.h> /* IPSET_CMD_* */
+#include <libipset/icmp.h> /* id_to_icmp */
+#include <libipset/icmpv6.h> /* id_to_icmpv6 */
+#include <libipset/data.h> /* enum ipset_data */
+#include <libipset/types.h> /* IPSET_*_ARG */
+#include <libipset/session.h> /* ipset_envopt_parse */
+#include <libipset/parse.h> /* ipset_parse_family */
+#include <libipset/print.h> /* ipset_print_family */
+#include <libipset/utils.h> /* STREQ */
+#include <libipset/ipset.h> /* prototypes */
+
+static char program_name[] = PACKAGE;
+static char program_version[] = PACKAGE_VERSION;
+
+#define MAX_CMDLINE_CHARS 1024
+#define MAX_ARGS 32
+
+/* The ipset structure */
+struct ipset {
+ ipset_custom_errorfn custom_error;
+ /* Custom error message function */
+ ipset_standard_errorfn standard_error;
+ /* Standard error message function */
+ struct ipset_session *session; /* Session */
+ uint32_t restore_line; /* Restore lineno */
+ bool interactive; /* "Interactive" CLI */
+ bool full_io; /* Use session ios */
+ bool no_vhi; /* No version/help/interactive */
+ char cmdline[MAX_CMDLINE_CHARS]; /* For restore mode */
+ char *newargv[MAX_ARGS];
+ int newargc;
+ const char *filename; /* Input/output filename */
+};
+
+/* Commands and environment options */
+
+const struct ipset_commands ipset_commands[] = {
+ /* Order is important */
+
+ { /* c[reate], --create, n[ew], -N */
+ .cmd = IPSET_CMD_CREATE,
+ .name = { "create", "new", "-N" },
+ .has_arg = IPSET_MANDATORY_ARG2,
+ .help = "SETNAME TYPENAME [type-specific-options]\n"
+ " Create a new set",
+ },
+ { /* a[dd], --add, -A */
+ .cmd = IPSET_CMD_ADD,
+ .name = { "add", "-A", NULL },
+ .has_arg = IPSET_MANDATORY_ARG2,
+ .help = "SETNAME ENTRY\n"
+ " Add entry to the named set",
+ },
+ { /* d[el], --del, -D */
+ .cmd = IPSET_CMD_DEL,
+ .name = { "del", "-D", NULL },
+ .has_arg = IPSET_MANDATORY_ARG2,
+ .help = "SETNAME ENTRY\n"
+ " Delete entry from the named set",
+ },
+ { /* t[est], --test, -T */
+ .cmd = IPSET_CMD_TEST,
+ .name = { "test", "-T", NULL },
+ .has_arg = IPSET_MANDATORY_ARG2,
+ .help = "SETNAME ENTRY\n"
+ " Test entry in the named set",
+ },
+ { /* des[troy], --destroy, x, -X */
+ .cmd = IPSET_CMD_DESTROY,
+ .name = { "destroy", "x", "-X" },
+ .has_arg = IPSET_OPTIONAL_ARG,
+ .help = "[SETNAME]\n"
+ " Destroy a named set or all sets",
+ },
+ { /* l[ist], --list, -L */
+ .cmd = IPSET_CMD_LIST,
+ .name = { "list", "-L", NULL },
+ .has_arg = IPSET_OPTIONAL_ARG,
+ .help = "[SETNAME]\n"
+ " List the entries of a named set or all sets",
+ },
+ { /* s[save], --save, -S */
+ .cmd = IPSET_CMD_SAVE,
+ .name = { "save", "-S", NULL },
+ .has_arg = IPSET_OPTIONAL_ARG,
+ .help = "[SETNAME]\n"
+ " Save the named set or all sets to stdout",
+ },
+ { /* r[estore], --restore, -R */
+ .cmd = IPSET_CMD_RESTORE,
+ .name = { "restore", "-R", NULL },
+ .has_arg = IPSET_NO_ARG,
+ .help = "\n"
+ " Restore a saved state",
+ },
+ { /* f[lush], --flush, -F */
+ .cmd = IPSET_CMD_FLUSH,
+ .name = { "flush", "-F", NULL },
+ .has_arg = IPSET_OPTIONAL_ARG,
+ .help = "[SETNAME]\n"
+ " Flush a named set or all sets",
+ },
+ { /* ren[ame], --rename, e, -E */
+ .cmd = IPSET_CMD_RENAME,
+ .name = { "rename", "e", "-E" },
+ .has_arg = IPSET_MANDATORY_ARG2,
+ .help = "FROM-SETNAME TO-SETNAME\n"
+ " Rename two sets",
+ },
+ { /* sw[ap], --swap, w, -W */
+ .cmd = IPSET_CMD_SWAP,
+ .name = { "swap", "w", "-W" },
+ .has_arg = IPSET_MANDATORY_ARG2,
+ .help = "FROM-SETNAME TO-SETNAME\n"
+ " Swap the contect of two existing sets",
+ },
+ { /* h[elp, --help, -H */
+ .cmd = IPSET_CMD_HELP,
+ .name = { "help", "-h", "-H" },
+ .has_arg = IPSET_OPTIONAL_ARG,
+ .help = "[TYPENAME]\n"
+ " Print help, and settype specific help",
+ },
+ { /* v[ersion], --version, -V */
+ .cmd = IPSET_CMD_VERSION,
+ .name = { "version", "-v", "-V" },
+ .has_arg = IPSET_NO_ARG,
+ .help = "\n"
+ " Print version information",
+ },
+ { /* q[uit] */
+ .cmd = IPSET_CMD_QUIT,
+ .name = { "quit", NULL },
+ .has_arg = IPSET_NO_ARG,
+ .help = "\n"
+ " Quit interactive mode",
+ },
+ { },
+};
+
+/**
+ * ipset_match_cmd - try to match as a prefix or letter-command
+ * @arg: possible command string
+ * @name: command and it's aliases
+ *
+ * Returns true if @arg is a known command.
+ */
+bool
+ipset_match_cmd(const char *arg, const char * const name[])
+{
+ size_t len, skip = 0;
+ int i;
+
+ assert(arg);
+ assert(name && name[0]);
+
+ /* Ignore two leading dashes */
+ if (arg[0] == '-' && arg[1] == '-')
+ skip = 2;
+
+ len = strlen(arg);
+ if (len <= skip || (len == 1 && arg[0] == '-'))
+ return false;
+
+ for (i = 0; i < IPSET_CMD_ALIASES && name[i] != NULL; i++) {
+ /* New command name options */
+ if (STRNEQ(arg + skip, name[i], len - skip))
+ return true;
+ }
+ return false;
+}
+
+/* Used up so far
+ *
+ * -A add
+ * -D del
+ * -E rename
+ * -f -file
+ * -F flush
+ * -h help
+ * -H help
+ * -L list
+ * -n -name
+ * -N create
+ * -o -output
+ * -r -resolve
+ * -R restore
+ * -s -sorted
+ * -S save
+ * -t -terse
+ * -T test
+ * -q -quiet
+ * -X destroy
+ * -v version
+ * -V version
+ * -W swap
+ * -! -exist
+ */
+
+const struct ipset_envopts ipset_envopts[] = {
+ { .name = { "-o", "-output" },
+ .has_arg = IPSET_MANDATORY_ARG, .flag = IPSET_OPT_MAX,
+ .parse = ipset_parse_output,
+ .help = "plain|save|xml\n"
+ " Specify output mode for listing sets.\n"
+ " Default value for \"list\" command is mode \"plain\"\n"
+ " and for \"save\" command is mode \"save\".",
+ },
+ { .name = { "-s", "-sorted" },
+ .parse = ipset_envopt_parse,
+ .has_arg = IPSET_NO_ARG, .flag = IPSET_ENV_SORTED,
+ .help = "\n"
+ " Print elements sorted (if supported by the set type).",
+ },
+ { .name = { "-q", "-quiet" },
+ .parse = ipset_envopt_parse,
+ .has_arg = IPSET_NO_ARG, .flag = IPSET_ENV_QUIET,
+ .help = "\n"
+ " Suppress any notice or warning message.",
+ },
+ { .name = { "-r", "-resolve" },
+ .parse = ipset_envopt_parse,
+ .has_arg = IPSET_NO_ARG, .flag = IPSET_ENV_RESOLVE,
+ .help = "\n"
+ " Try to resolve IP addresses in the output (slow!)",
+ },
+ { .name = { "-!", "-exist" },
+ .parse = ipset_envopt_parse,
+ .has_arg = IPSET_NO_ARG, .flag = IPSET_ENV_EXIST,
+ .help = "\n"
+ " Ignore errors when creating or adding sets or\n"
+ " elements that do exist or when deleting elements\n"
+ " that don't exist.",
+ },
+ { .name = { "-n", "-name" },
+ .parse = ipset_envopt_parse,
+ .has_arg = IPSET_NO_ARG, .flag = IPSET_ENV_LIST_SETNAME,
+ .help = "\n"
+ " When listing, just list setnames from the kernel.\n",
+ },
+ { .name = { "-t", "-terse" },
+ .parse = ipset_envopt_parse,
+ .has_arg = IPSET_NO_ARG, .flag = IPSET_ENV_LIST_HEADER,
+ .help = "\n"
+ " When listing, list setnames and set headers\n"
+ " from kernel only.",
+ },
+ { .name = { "-f", "-file" },
+ .parse = ipset_parse_filename,
+ .has_arg = IPSET_MANDATORY_ARG, .flag = IPSET_OPT_MAX,
+ .help = "\n"
+ " Read from the given file instead of standard\n"
+ " input (restore) or write to given file instead\n"
+ " of standard output (list/save).",
+ },
+ { },
+};
+
+/**
+ * ipset_match_option - strict option matching
+ * @arg: possible option string
+ * @name: known option and it's alias
+ *
+ * Two leading dashes are ignored.
+ *
+ * Returns true if @arg is a known option.
+ */
+bool
+ipset_match_option(const char *arg, const char * const name[])
+{
+ assert(arg);
+ assert(name && name[0]);
+
+ /* Skip two leading dashes */
+ if (arg[0] == '-' && arg[1] == '-')
+ arg++, arg++;
+
+ return STREQ(arg, name[0]) ||
+ (name[1] != NULL && STREQ(arg, name[1]));
+}
+
+/**
+ * ipset_match_envopt - strict envopt matching
+ * @arg: possible envopt string
+ * @name: known envopt and it's alias
+ *
+ * One leading dash is ignored.
+ *
+ * Returns true if @arg is a known envopt.
+ */
+bool
+ipset_match_envopt(const char *arg, const char * const name[])
+{
+ assert(arg);
+ assert(name && name[0]);
+
+ /* Skip one leading dash */
+ if (arg[0] == '-' && arg[1] == '-')
+ arg++;
+
+ return STREQ(arg, name[0]) ||
+ (name[1] != NULL && STREQ(arg, name[1]));
+}
+
+static void
+ipset_shift_argv(int *argc, char *argv[], int from)
+{
+ int i;
+
+ assert(*argc >= from + 1);
+
+ for (i = from + 1; i <= *argc; i++)
+ argv[i-1] = argv[i];
+ (*argc)--;
+ return;
+}
+
+/**
+ * ipset_port_usage - prints the usage for the port parameter
+ *
+ * Print the usage for the port parameter to stdout.
+ */
+void
+ipset_port_usage(void)
+{
+ int i;
+ const char *name;
+
+ printf(" [PROTO:]PORT is a valid pattern of the following:\n"
+ " PORTNAME TCP port name from /etc/services\n"
+ " PORTNUMBER TCP port number identifier\n"
+ " tcp|sctp|udp|udplite:PORTNAME|PORTNUMBER\n"
+ " icmp:CODENAME supported ICMP codename\n"
+ " icmp:TYPE/CODE ICMP type/code value\n"
+ " icmpv6:CODENAME supported ICMPv6 codename\n"
+ " icmpv6:TYPE/CODE ICMPv6 type/code value\n"
+ " PROTO:0 all other protocols\n\n");
+
+ printf(" Supported ICMP codenames:\n");
+ i = 0;
+ while ((name = id_to_icmp(i++)) != NULL)
+ printf(" %s\n", name);
+ printf(" Supported ICMPv6 codenames:\n");
+ i = 0;
+ while ((name = id_to_icmpv6(i++)) != NULL)
+ printf(" %s\n", name);
+}
+
+/**
+ * ipset_parse_filename - parse filename
+ * @ipset: ipset structure
+ * @opt: option kind of the data
+ * @str: string to parse
+ *
+ * Parse filename of "-file" option, which can be used once only.
+ *
+ * Returns 0 on success or a negative error code.
+ */
+int
+ipset_parse_filename(struct ipset *ipset,
+ int opt UNUSED, const char *str)
+{
+ void *p = ipset_session_printf_private(ipset->session);
+
+ if (ipset->filename)
+ return ipset->custom_error(ipset, p, IPSET_PARAMETER_PROBLEM,
+ "-file option cannot be used when full io is activated");
+ ipset->filename = str;
+
+ return 0;
+}
+
+/**
+ * ipset_parse_output - parse output format name
+ * @ipset: ipset structure
+ * @opt: option kind of the data
+ * @str: string to parse
+ *
+ * Parse output format names and set session mode.
+ * The value is stored in the session.
+ *
+ * Returns 0 on success or a negative error code.
+ */
+int
+ipset_parse_output(struct ipset *ipset,
+ int opt UNUSED, const char *str)
+{
+ struct ipset_session *session;
+
+ assert(ipset);
+ assert(str);
+
+ session = ipset_session(ipset);
+ if (STREQ(str, "plain"))
+ return ipset_session_output(session, IPSET_LIST_PLAIN);
+ else if (STREQ(str, "xml"))
+ return ipset_session_output(session, IPSET_LIST_XML);
+ else if (STREQ(str, "save"))
+ return ipset_session_output(session, IPSET_LIST_SAVE);
+
+ return ipset_err(session,
+ "Syntax error: unknown output mode '%s'", str);
+}
+
+/**
+ * ipset_envopt_parse - parse/set environment option
+ * @ipset: ipset structure
+ * @opt: environment option
+ * @arg: option argument (unused)
+ *
+ * Parse and set an environment option.
+ *
+ * Returns 0 on success or a negative error code.
+ */
+int
+ipset_envopt_parse(struct ipset *ipset, int opt,
+ const char *arg UNUSED)
+{
+ struct ipset_session *session;
+ assert(ipset);
+
+ session = ipset_session(ipset);
+ switch (opt) {
+ case IPSET_ENV_SORTED:
+ case IPSET_ENV_QUIET:
+ case IPSET_ENV_RESOLVE:
+ case IPSET_ENV_EXIST:
+ case IPSET_ENV_LIST_SETNAME:
+ case IPSET_ENV_LIST_HEADER:
+ ipset_envopt_set(session, opt);
+ return 0;
+ default:
+ break;
+ }
+ return -1;
+}
+
+static int __attribute__((format(printf, 4, 5)))
+default_custom_error(struct ipset *ipset, void *p UNUSED,
+ int status, const char *msg, ...)
+{
+ struct ipset_session *session = ipset_session(ipset);
+ bool is_interactive = ipset_is_interactive(ipset);
+ bool quiet = !is_interactive &&
+ session &&
+ ipset_envopt_test(session, IPSET_ENV_QUIET);
+
+ if (status && msg && !quiet) {
+ va_list args;
+
+ fprintf(stderr, "%s v%s: ", program_name, program_version);
+ va_start(args, msg);
+ vfprintf(stderr, msg, args);
+ va_end(args);
+ if (status != IPSET_SESSION_PROBLEM)
+ fprintf(stderr, "\n");
+
+ if (status == IPSET_PARAMETER_PROBLEM)
+ fprintf(stderr,
+ "Try `%s help' for more information.\n",
+ program_name);
+ }
+ /* Ignore errors in interactive mode */
+ if (status && is_interactive) {
+ if (session)
+ ipset_session_report_reset(session);
+ return -1;
+ }
+
+ D("status: %u", status);
+ ipset_fini(ipset);
+ exit(status > IPSET_VERSION_PROBLEM ? IPSET_OTHER_PROBLEM : status);
+ /* Unreached */
+ return -1;
+}
+
+static int
+default_standard_error(struct ipset *ipset, void *p)
+{
+ struct ipset_session *session = ipset_session(ipset);
+ bool is_interactive = ipset_is_interactive(ipset);
+
+ if (ipset_session_warning(session) &&
+ !ipset_envopt_test(session, IPSET_ENV_QUIET))
+ fprintf(stderr, "Warning: %s\n",
+ ipset_session_warning(session));
+ if (ipset_session_error(session))
+ return ipset->custom_error(ipset, p,
+ IPSET_SESSION_PROBLEM, "%s",
+ ipset_session_error(session));
+
+ if (!is_interactive) {
+ ipset_fini(ipset);
+ exit(IPSET_OTHER_PROBLEM);
+ }
+
+ ipset_session_report_reset(session);
+ return -1;
+}
+
+static void
+default_help(void)
+{
+ const struct ipset_commands *c;
+ const struct ipset_envopts *opt = ipset_envopts;
+
+ printf("%s v%s\n\n"
+ "Usage: %s [options] COMMAND\n\nCommands:\n",
+ program_name, program_version, program_name);
+
+ for (c = ipset_commands; c->cmd; c++)
+ printf("%s %s\n", c->name[0], c->help);
+ printf("\nOptions:\n");
+
+ while (opt->flag) {
+ if (opt->help)
+ printf("%s %s\n", opt->name[0], opt->help);
+ opt++;
+ }
+}
+
+static void
+reset_argv(struct ipset *ipset)
+{
+ int i;
+
+ /* Reset */
+ for (i = 1; i < ipset->newargc; i++) {
+ if (ipset->newargv[i])
+ free(ipset->newargv[i]);
+ ipset->newargv[i] = NULL;
+ }
+ ipset->newargc = 1;
+}
+
+/* Build fake argv from parsed line */
+static int
+build_argv(struct ipset *ipset, char *buffer)
+{
+ void *p = ipset_session_printf_private(ipset->session);
+ char *tmp, *arg;
+ int i;
+ bool quoted = false;
+
+ reset_argv(ipset);
+ arg = calloc(strlen(buffer) + 1, sizeof(*buffer));
+ if (!arg)
+ return ipset->custom_error(ipset, p, IPSET_OTHER_PROBLEM,
+ "Cannot allocate memory.");
+ for (tmp = buffer, i = 0; *tmp; tmp++) {
+ if ((ipset->newargc + 1) ==
+ (int)(sizeof(ipset->newargv)/sizeof(char *))) {
+ free(arg);
+ return ipset->custom_error(ipset,
+ p, IPSET_PARAMETER_PROBLEM,
+ "Line is too long to parse.");
+ }
+ switch (*tmp) {
+ case '"':
+ quoted = !quoted;
+ if (*(tmp+1))
+ continue;
+ break;
+ case ' ':
+ case '\r':
+ case '\n':
+ case '\t':
+ if (!quoted)
+ break;
+ arg[i++] = *tmp;
+ continue;
+ default:
+ arg[i++] = *tmp;
+ if (*(tmp+1))
+ continue;
+ break;
+ }
+ if (!*(tmp+1) && quoted) {
+ free(arg);
+ return ipset->custom_error(ipset,
+ p, IPSET_PARAMETER_PROBLEM,
+ "Missing close quote!");
+ }
+ if (!*arg)
+ continue;
+ ipset->newargv[ipset->newargc] =
+ calloc(strlen(arg) + 1, sizeof(*arg));
+ if (!ipset->newargv[ipset->newargc]) {
+ free(arg);
+ return ipset->custom_error(ipset,
+ p, IPSET_OTHER_PROBLEM,
+ "Cannot allocate memory.");
+ }
+ ipset_strlcpy(ipset->newargv[ipset->newargc++],
+ arg, strlen(arg) + 1);
+ memset(arg, 0, strlen(arg) + 1);
+ i = 0;
+ }
+
+ free(arg);
+ return 0;
+}
+
+static int
+restore(struct ipset *ipset)
+{
+ struct ipset_session *session = ipset_session(ipset);
+ int ret = 0;
+ FILE *f = stdin; /* Default from stdin */
+
+ if (ipset->filename) {
+ ret = ipset_session_io_normal(session, ipset->filename,
+ IPSET_IO_INPUT);
+ if (ret)
+ return ret;
+ f = ipset_session_io_stream(session, IPSET_IO_INPUT);
+ }
+ return ipset_parse_stream(ipset, f);
+}
+
+static bool do_parse(const struct ipset_arg *arg, bool family)
+{
+ return !((family == true) ^ (arg->opt == IPSET_OPT_FAMILY));
+}
+
+static int
+call_parser(struct ipset *ipset, int *argc, char *argv[],
+ const struct ipset_type *type, enum ipset_adt cmd, bool family)
+{
+ void *p = ipset_session_printf_private(ipset->session);
+ const struct ipset_arg *arg;
+ const char *optstr;
+ const struct ipset_type *t = type;
+ uint8_t revision = type->revision;
+ int ret = 0, i = 1, j;
+
+ /* Currently CREATE and ADT may have got additional arguments */
+ if (type->cmd[cmd].args[0] == IPSET_ARG_NONE && *argc > 1)
+ return ipset->custom_error(ipset,
+ p, IPSET_PARAMETER_PROBLEM,
+ "Unknown argument: `%s'", argv[i]);
+
+ while (*argc > i) {
+ ret = -1;
+ for (j = 0; type->cmd[cmd].args[j] != IPSET_ARG_NONE; j++) {
+ arg = ipset_keyword(type->cmd[cmd].args[j]);
+ D("argc: %u, %s vs %s", i, argv[i], arg->name[0]);
+ if (!(ipset_match_option(argv[i], arg->name)))
+ continue;
+
+ optstr = argv[i];
+ /* Matched option */
+ D("match %s, argc %u, i %u, %s",
+ arg->name[0], *argc, i + 1,
+ do_parse(arg, family) ? "parse" : "skip");
+ i++;
+ ret = 0;
+ switch (arg->has_arg) {
+ case IPSET_MANDATORY_ARG:
+ if (*argc - i < 1)
+ return ipset->custom_error(ipset, p,
+ IPSET_PARAMETER_PROBLEM,
+ "Missing mandatory argument "
+ "of option `%s'",
+ arg->name[0]);
+ /* Fall through */
+ case IPSET_OPTIONAL_ARG:
+ if (*argc - i >= 1) {
+ if (do_parse(arg, family)) {
+ ret = ipset_call_parser(
+ ipset->session,
+ arg, argv[i]);
+ if (ret < 0)
+ return ret;
+ }
+ i++;
+ break;
+ }
+ /* Fall through */
+ default:
+ if (do_parse(arg, family)) {
+ ret = ipset_call_parser(
+ ipset->session, arg, optstr);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ break;
+ }
+ if (ret < 0)
+ goto err_unknown;
+ }
+ if (!family)
+ *argc = 0;
+ return ret;
+
+err_unknown:
+ while ((type = ipset_type_higher_rev(t)) != t) {
+ for (j = 0; type->cmd[cmd].args[j] != IPSET_ARG_NONE; j++) {
+ arg = ipset_keyword(type->cmd[cmd].args[j]);
+ D("argc: %u, %s vs %s", i, argv[i], arg->name[0]);
+ if (ipset_match_option(argv[i], arg->name))
+ return ipset->custom_error(ipset, p,
+ IPSET_PARAMETER_PROBLEM,
+ "Argument `%s' is supported in the kernel module "
+ "of the set type %s starting from the revision %u "
+ "and you have installed revision %u only. "
+ "Your kernel is behind your ipset utility.",
+ argv[i], type->name,
+ type->revision, revision);
+ }
+ t = type;
+ }
+ return ipset->custom_error(ipset, p, IPSET_PARAMETER_PROBLEM,
+ "Unknown argument: `%s'", argv[i]);
+}
+
+static enum ipset_adt
+cmd2cmd(int cmd)
+{
+ switch (cmd) {
+ case IPSET_CMD_ADD:
+ return IPSET_ADD;
+ case IPSET_CMD_DEL:
+ return IPSET_DEL;
+ case IPSET_CMD_TEST:
+ return IPSET_TEST;
+ case IPSET_CMD_CREATE:
+ return IPSET_CREATE;
+ default:
+ return 0;
+ }
+}
+
+static void
+check_mandatory(struct ipset *ipset,
+ const struct ipset_type *type, enum ipset_cmd command)
+{
+ enum ipset_adt cmd = cmd2cmd(command);
+ struct ipset_session *session = ipset->session;
+ void *p = ipset_session_printf_private(session);
+ uint64_t flags = ipset_data_flags(ipset_session_data(session));
+ uint64_t mandatory = type->cmd[cmd].need;
+ const struct ipset_arg *arg;
+ int i;
+
+ /* Range can be expressed by ip/cidr */
+ if (flags & IPSET_FLAG(IPSET_OPT_CIDR))
+ flags |= IPSET_FLAG(IPSET_OPT_IP_TO);
+
+ mandatory &= ~flags;
+ if (!mandatory)
+ return;
+ if (type->cmd[cmd].args[0] == IPSET_ARG_NONE) {
+ ipset->custom_error(ipset, p, IPSET_OTHER_PROBLEM,
+ "There are missing mandatory flags "
+ "but can't check them. "
+ "It's a bug, please report the problem.");
+ return;
+ }
+
+ for (i = 0; type->cmd[cmd].args[i] != IPSET_ARG_NONE; i++) {
+ arg = ipset_keyword(type->cmd[cmd].args[i]);
+ if (mandatory & IPSET_FLAG(arg->opt)) {
+ ipset->custom_error(ipset, p, IPSET_PARAMETER_PROBLEM,
+ "Mandatory option `%s' is missing",
+ arg->name[0]);
+ return;
+ }
+ }
+}
+
+static const char *
+cmd2name(enum ipset_cmd cmd)
+{
+ const struct ipset_commands *c;
+
+ for (c = ipset_commands; c->cmd; c++)
+ if (cmd == c->cmd)
+ return c->name[0];
+ return "unknown command";
+}
+
+static const char *
+session_family(struct ipset_session *session)
+{
+ switch (ipset_data_family(ipset_session_data(session))) {
+ case NFPROTO_IPV4:
+ return "inet";
+ case NFPROTO_IPV6:
+ return "inet6";
+ default:
+ return "unspec";
+ }
+}
+
+static void
+check_allowed(struct ipset *ipset,
+ const struct ipset_type *type, enum ipset_cmd command)
+{
+ struct ipset_session *session = ipset->session;
+ void *p = ipset_session_printf_private(session);
+ uint64_t flags = ipset_data_flags(ipset_session_data(session));
+ enum ipset_adt cmd = cmd2cmd(command);
+ uint64_t allowed = type->cmd[cmd].full;
+ uint64_t cmdflags = command == IPSET_CMD_CREATE
+ ? IPSET_CREATE_FLAGS : IPSET_ADT_FLAGS;
+ const struct ipset_arg *arg;
+ enum ipset_opt i;
+ int j;
+
+ /* Range can be expressed by ip/cidr or from-to */
+ if (allowed & IPSET_FLAG(IPSET_OPT_IP_TO))
+ allowed |= IPSET_FLAG(IPSET_OPT_CIDR);
+
+ for (i = IPSET_OPT_IP; i < IPSET_OPT_FLAGS; i++) {
+ if (!(cmdflags & IPSET_FLAG(i)) ||
+ (allowed & IPSET_FLAG(i)) ||
+ !(flags & IPSET_FLAG(i)))
+ continue;
+ /* Not allowed element-expressions */
+ switch (i) {
+ case IPSET_OPT_CIDR:
+ ipset->custom_error(ipset, p, IPSET_OTHER_PROBLEM,
+ "IP/CIDR range is not allowed in command %s "
+ "with set type %s and family %s",
+ cmd2name(command), type->name,
+ session_family(ipset->session));
+ return;
+ case IPSET_OPT_IP_TO:
+ ipset->custom_error(ipset, p, IPSET_OTHER_PROBLEM,
+ "FROM-TO IP range is not allowed in command %s "
+ "with set type %s and family %s",
+ cmd2name(command), type->name,
+ session_family(ipset->session));
+ return;
+ case IPSET_OPT_PORT_TO:
+ ipset->custom_error(ipset, p, IPSET_OTHER_PROBLEM,
+ "FROM-TO port range is not allowed in command %s "
+ "with set type %s and family %s",
+ cmd2name(command), type->name,
+ session_family(ipset->session));
+ return;
+ default:
+ break;
+ }
+ /* Other options */
+ if (type->cmd[cmd].args[0] == IPSET_ARG_NONE) {
+ ipset->custom_error(ipset, p, IPSET_OTHER_PROBLEM,
+ "There are not allowed options (%u) "
+ "but option list is empty. "
+ "It's a bug, please report the problem.", i);
+ return;
+ }
+ for (j = 0; type->cmd[cmd].args[j] != IPSET_ARG_NONE; j++) {
+ arg = ipset_keyword(type->cmd[cmd].args[j]);
+ if (arg->opt != i)
+ continue;
+ ipset->custom_error(ipset, p, IPSET_OTHER_PROBLEM,
+ "%s parameter is not allowed in command %s "
+ "with set type %s and family %s",
+ arg->name[0],
+ cmd2name(command), type->name,
+ session_family(ipset->session));
+ return;
+ }
+ ipset->custom_error(ipset, p, IPSET_OTHER_PROBLEM,
+ "There are not allowed options (%u) "
+ "but can't resolve them. "
+ "It's a bug, please report the problem.", i);
+ return;
+ }
+}
+
+static const struct ipset_type *
+type_find(const char *name)
+{
+ const struct ipset_type *t = ipset_types();
+
+ while (t) {
+ if (ipset_match_typename(name, t))
+ return t;
+ t = t->next;
+ }
+ return NULL;
+}
+
+static enum ipset_adt cmd_help_order[] = {
+ IPSET_CREATE,
+ IPSET_ADD,
+ IPSET_DEL,
+ IPSET_TEST,
+ IPSET_CADT_MAX,
+};
+
+static const char *cmd_prefix[] = {
+ [IPSET_CREATE] = "create SETNAME",
+ [IPSET_ADD] = "add SETNAME",
+ [IPSET_DEL] = "del SETNAME",
+ [IPSET_TEST] = "test SETNAME",
+};
+
+/* Workhorses */
+
+/**
+ * ipset_parse_argv - parse and argv array and execute the command
+ * @ipset: ipset structure
+ * @argc: length of the array
+ * @argv: array of strings
+ *
+ * Parse an array of strings and execute the ipset command.
+ *
+ * Returns 0 on success or a negative error code.
+ */
+int
+ipset_parse_argv(struct ipset *ipset, int oargc, char *oargv[])
+{
+ int ret = 0;
+ enum ipset_cmd cmd = IPSET_CMD_NONE;
+ int i;
+ char *arg0 = NULL, *arg1 = NULL;
+ const struct ipset_envopts *opt;
+ const struct ipset_commands *command;
+ const struct ipset_type *type;
+ struct ipset_session *session = ipset->session;
+ void *p = ipset_session_printf_private(session);
+ int argc = oargc;
+ char *argv[MAX_ARGS] = {};
+
+ /* We need a local copy because of ipset_shift_argv */
+ memcpy(argv, oargv, sizeof(char *) * argc);
+
+ /* Set session lineno to report parser errors correctly */
+ ipset_session_lineno(session, ipset->restore_line);
+
+ /* Commandline parsing, somewhat similar to that of 'ip' */
+
+ /* First: parse core options */
+ for (opt = ipset_envopts; opt->flag; opt++) {
+ for (i = 1; i < argc; ) {
+ if (!ipset_match_envopt(argv[i], opt->name)) {
+ i++;
+ continue;
+ }
+ /* Shift off matched option */
+ ipset_shift_argv(&argc, argv, i);
+ switch (opt->has_arg) {
+ case IPSET_MANDATORY_ARG:
+ if (i + 1 > argc)
+ return ipset->custom_error(ipset, p,
+ IPSET_PARAMETER_PROBLEM,
+ "Missing mandatory argument "
+ "to option %s",
+ opt->name[0]);
+ /* Fall through */
+ case IPSET_OPTIONAL_ARG:
+ if (i + 1 <= argc) {
+ ret = opt->parse(ipset, opt->flag,
+ argv[i]);
+ if (ret < 0)
+ return ipset->standard_error(ipset, p);
+ ipset_shift_argv(&argc, argv, i);
+ }
+ break;
+ case IPSET_NO_ARG:
+ ret = opt->parse(ipset, opt->flag,
+ opt->name[0]);
+ if (ret < 0)
+ return ipset->standard_error(ipset, p);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /* Second: parse command */
+ for (command = ipset_commands;
+ argc > 1 && command->cmd && cmd == IPSET_CMD_NONE;
+ command++) {
+ if (!ipset_match_cmd(argv[1], command->name))
+ continue;
+
+ if (ipset->restore_line != 0 &&
+ (command->cmd == IPSET_CMD_RESTORE ||
+ command->cmd == IPSET_CMD_VERSION ||
+ command->cmd == IPSET_CMD_HELP))
+ return ipset->custom_error(ipset, p,
+ IPSET_PARAMETER_PROBLEM,
+ "Command `%s' is invalid "
+ "in restore mode.",
+ command->name[0]);
+ if (ipset->interactive && command->cmd == IPSET_CMD_RESTORE) {
+ printf("Restore command is not supported "
+ "in interactive mode\n");
+ return 0;
+ }
+
+ /* Shift off matched command arg */
+ ipset_shift_argv(&argc, argv, 1);
+ cmd = command->cmd;
+ switch (command->has_arg) {
+ case IPSET_MANDATORY_ARG:
+ case IPSET_MANDATORY_ARG2:
+ if (argc < 2)
+ return ipset->custom_error(ipset, p,
+ IPSET_PARAMETER_PROBLEM,
+ "Missing mandatory argument "
+ "to command %s",
+ command->name[0]);
+ /* Fall through */
+ case IPSET_OPTIONAL_ARG:
+ arg0 = argv[1];
+ if (argc >= 2)
+ /* Shift off first arg */
+ ipset_shift_argv(&argc, argv, 1);
+ break;
+ default:
+ break;
+ }
+ if (command->has_arg == IPSET_MANDATORY_ARG2) {
+ if (argc < 2)
+ return ipset->custom_error(ipset, p,
+ IPSET_PARAMETER_PROBLEM,
+ "Missing second mandatory "
+ "argument to command %s",
+ command->name[0]);
+ arg1 = argv[1];
+ /* Shift off second arg */
+ ipset_shift_argv(&argc, argv, 1);
+ }
+ break;
+ }
+
+ /* Third: catch interactive mode, handle help, version */
+ switch (cmd) {
+ case IPSET_CMD_NONE:
+ if (ipset->interactive) {
+ printf("No command specified\n");
+ if (session)
+ ipset_envopt_parse(ipset, 0, "reset");
+ return 0;
+ }
+ if (argc > 1 && STREQ(argv[1], "-")) {
+ if (ipset->no_vhi)
+ return 0;
+ ipset->interactive = true;
+ printf("%s> ", program_name);
+ while (fgets(ipset->cmdline,
+ sizeof(ipset->cmdline), stdin)) {
+ /* Execute line: ignore soft errors */
+ if (ipset_parse_line(ipset, ipset->cmdline) < 0)
+ ipset->standard_error(ipset, p);
+ printf("%s> ", program_name);
+ }
+ return ipset->custom_error(ipset, p,
+ IPSET_NO_PROBLEM, NULL);
+ }
+ if (argc > 1)
+ return ipset->custom_error(ipset,
+ p, IPSET_PARAMETER_PROBLEM,
+ "No command specified: unknown argument %s",
+ argv[1]);
+ return ipset->custom_error(ipset, p, IPSET_PARAMETER_PROBLEM,
+ "No command specified.");
+ case IPSET_CMD_VERSION:
+ if (ipset->no_vhi)
+ return 0;
+ printf("%s v%s, protocol version: %u\n",
+ program_name, program_version, IPSET_PROTOCOL);
+ if (ipset->interactive)
+ return 0;
+ return ipset->custom_error(ipset, p, IPSET_NO_PROBLEM, NULL);
+ case IPSET_CMD_HELP:
+ if (ipset->no_vhi)
+ return 0;
+ default_help();
+
+ if (ipset->interactive ||
+ !ipset_envopt_test(session, IPSET_ENV_QUIET)) {
+ if (arg0) {
+ const struct ipset_arg *arg;
+ int k;
+
+ /* Type-specific help, without kernel checking */
+ type = type_find(arg0);
+ if (!type)
+ return ipset->custom_error(ipset, p,
+ IPSET_PARAMETER_PROBLEM,
+ "Unknown settype: `%s'", arg0);
+ printf("\n%s type specific options:\n\n", type->name);
+ for (i = 0; cmd_help_order[i] != IPSET_CADT_MAX; i++) {
+ cmd = cmd_help_order[i];
+ printf("%s %s %s\n",
+ cmd_prefix[cmd], type->name, type->cmd[cmd].help);
+ for (k = 0; type->cmd[cmd].args[k] != IPSET_ARG_NONE; k++) {
+ arg = ipset_keyword(type->cmd[cmd].args[k]);
+ if (!arg->help || arg->help[0] == '\0')
+ continue;
+ printf(" %s\n", arg->help);
+ }
+ }
+ printf("\n%s\n", type->usage);
+ if (type->usagefn)
+ type->usagefn();
+ if (type->family == NFPROTO_UNSPEC)
+ printf("\nType %s is family neutral.\n",
+ type->name);
+ else if (type->family == NFPROTO_IPSET_IPV46)
+ printf("\nType %s supports inet "
+ "and inet6.\n",
+ type->name);
+ else
+ printf("\nType %s supports family "
+ "%s only.\n",
+ type->name,
+ type->family == NFPROTO_IPV4
+ ? "inet" : "inet6");
+ } else {
+ printf("\nSupported set types:\n");
+ type = ipset_types();
+ while (type) {
+ printf(" %s\t%s%u\t%s\n",
+ type->name,
+ strlen(type->name) < 12 ? "\t" : "",
+ type->revision,
+ type->description);
+ type = type->next;
+ }
+ }
+ }
+ if (ipset->interactive)
+ return 0;
+ return ipset->custom_error(ipset, p, IPSET_NO_PROBLEM, NULL);
+ case IPSET_CMD_QUIT:
+ return ipset->custom_error(ipset, p, IPSET_NO_PROBLEM, NULL);
+ default:
+ break;
+ }
+
+ /* Forth: parse command args and issue the command */
+ switch (cmd) {
+ case IPSET_CMD_CREATE:
+ /* Args: setname typename [type specific options] */
+ ret = ipset_parse_setname(session, IPSET_SETNAME, arg0);
+ if (ret < 0)
+ return ipset->standard_error(ipset, p);
+
+ ret = ipset_parse_typename(session, IPSET_OPT_TYPENAME, arg1);
+ if (ret < 0)
+ return ipset->standard_error(ipset, p);
+
+ type = ipset_type_get(session, cmd);
+ if (type == NULL)
+ return ipset->standard_error(ipset, p);
+
+ /* Parse create options: first check INET family */
+ ret = call_parser(ipset, &argc, argv, type, IPSET_CREATE, true);
+ if (ret < 0)
+ return ipset->standard_error(ipset, p);
+ else if (ret)
+ return ret;
+
+ /* Parse create options: then check all options */
+ ret = call_parser(ipset, &argc, argv, type, IPSET_CREATE, false);
+ if (ret < 0)
+ return ipset->standard_error(ipset, p);
+ else if (ret)
+ return ret;
+
+ /* Check mandatory, then allowed options */
+ check_mandatory(ipset, type, cmd);
+ check_allowed(ipset, type, cmd);
+
+ break;
+ case IPSET_CMD_LIST:
+ case IPSET_CMD_SAVE:
+ if (ipset->filename != NULL) {
+ ret = ipset_session_io_normal(session,
+ ipset->filename, IPSET_IO_OUTPUT);
+ if (!ret)
+ return ret;
+ }
+ case IPSET_CMD_DESTROY:
+ case IPSET_CMD_FLUSH:
+ /* Args: [setname] */
+ if (arg0) {
+ ret = ipset_parse_setname(session,
+ IPSET_SETNAME, arg0);
+ if (ret < 0)
+ return ipset->standard_error(ipset, p);
+ }
+ break;
+
+ case IPSET_CMD_RENAME:
+ case IPSET_CMD_SWAP:
+ /* Args: from-setname to-setname */
+ ret = ipset_parse_setname(session, IPSET_SETNAME, arg0);
+ if (ret < 0)
+ return ipset->standard_error(ipset, p);
+ ret = ipset_parse_setname(session, IPSET_OPT_SETNAME2, arg1);
+ if (ret < 0)
+ return ipset->standard_error(ipset, p);
+ break;
+
+ case IPSET_CMD_RESTORE:
+ /* Restore mode */
+ if (argc > 1)
+ return ipset->custom_error(ipset,
+ p, IPSET_PARAMETER_PROBLEM,
+ "Unknown argument %s", argv[1]);
+ return restore(ipset);
+ case IPSET_CMD_ADD:
+ case IPSET_CMD_DEL:
+ case IPSET_CMD_TEST:
+ D("ADT: setname %s", arg0);
+ /* Args: setname ip [options] */
+ ret = ipset_parse_setname(session, IPSET_SETNAME, arg0);
+ if (ret < 0)
+ return ipset->standard_error(ipset, p);
+
+ type = ipset_type_get(session, cmd);
+ if (type == NULL)
+ return ipset->standard_error(ipset, p);
+
+ ret = ipset_parse_elem(session, type->last_elem_optional, arg1);
+ if (ret < 0)
+ return ipset->standard_error(ipset, p);
+
+ /* Parse additional ADT options */
+ ret = call_parser(ipset, &argc, argv, type, cmd2cmd(cmd), false);
+ if (ret < 0)
+ return ipset->standard_error(ipset, p);
+ else if (ret)
+ return ret;
+
+ /* Check mandatory, then allowed options */
+ check_mandatory(ipset, type, cmd);
+ check_allowed(ipset, type, cmd);
+
+ break;
+ default:
+ break;
+ }
+
+ if (argc > 1)
+ return ipset->custom_error(ipset, p, IPSET_PARAMETER_PROBLEM,
+ "Unknown argument %s", argv[1]);
+ ret = ipset_cmd(session, cmd, ipset->restore_line);
+ D("ret %d", ret);
+ /* Special case for TEST and non-quiet mode */
+ if (cmd == IPSET_CMD_TEST && ipset_session_warning(session)) {
+ if (!ipset_envopt_test(session, IPSET_ENV_QUIET))
+ fprintf(stderr, "%s", ipset_session_warning(session));
+ ipset_session_report_reset(session);
+ }
+ if (ret < 0)
+ ipset->standard_error(ipset, p);
+
+ return ret;
+}
+
+/**
+ * ipset_parse_line - parse a string as a command line and execute it
+ * @ipset: ipset structure
+ * @line: string of line
+ *
+ * Parse a string as a command line and execute the ipset command.
+ *
+ * Returns 0 on success or a negative error code.
+ */
+int
+ipset_parse_line(struct ipset *ipset, char *line)
+{
+ char *c = line;
+ int ret;
+
+ reset_argv(ipset);
+
+ while (isspace(c[0]))
+ c++;
+ if (c[0] == '\0' || c[0] == '#') {
+ if (ipset->interactive)
+ printf("%s> ", program_name);
+ return 0;
+ }
+ /* Build fake argv, argc */
+ ret = build_argv(ipset, c);
+ if (ret < 0)
+ return ret;
+ /* Parse and execute line */
+ return ipset_parse_argv(ipset, ipset->newargc, ipset->newargv);
+}
+
+/**
+ * ipset_parse_stream - parse an stream and execute the commands
+ * @ipset: ipset structure
+ * @f: stream
+ *
+ * Parse an already opened file as stream and execute the commands.
+ *
+ * Returns 0 on success or a negative error code.
+ */
+int
+ipset_parse_stream(struct ipset *ipset, FILE *f)
+{
+ struct ipset_session *session = ipset_session(ipset);
+ void *p = ipset_session_printf_private(session);
+ int ret = 0;
+ char *c;
+
+ while (fgets(ipset->cmdline, sizeof(ipset->cmdline), f)) {
+ ipset->restore_line++;
+ c = ipset->cmdline;
+ while (isspace(c[0]))
+ c++;
+ if (c[0] == '\0' || c[0] == '#')
+ continue;
+ else if (STREQ(c, "COMMIT\n") || STREQ(c, "COMMIT\r\n")) {
+ ret = ipset_commit(ipset->session);
+ if (ret < 0)
+ ipset->standard_error(ipset, p);
+ continue;
+ }
+ /* Build faked argv, argc */
+ ret = build_argv(ipset, c);
+ if (ret < 0)
+ return ret;
+
+ /* Execute line */
+ ret = ipset_parse_argv(ipset, ipset->newargc, ipset->newargv);
+ if (ret < 0)
+ ipset->standard_error(ipset, p);
+ }
+ /* implicit "COMMIT" at EOF */
+ ret = ipset_commit(ipset->session);
+ if (ret < 0)
+ ipset->standard_error(ipset, p);
+
+ return ret;
+}
+
+/**
+ * ipset_session - returns the session pointer of an ipset structure
+ * @ipset: ipset structure
+ *
+ * Returns the session pointer of an ipset structure.
+ */
+struct ipset_session *
+ipset_session(struct ipset *ipset)
+{
+ return ipset->session;
+}
+
+/**
+ * ipset_is_interactive - is the interactive mode enabled?
+ * @ipset: ipset structure
+ *
+ * Returns true if the interactive mode is enabled.
+ */
+bool
+ipset_is_interactive(struct ipset *ipset)
+{
+ return ipset->interactive;
+}
+
+/**
+ * ipset_custom_printf - set custom print functions
+ * @ipset: ipset structure
+ * @custom_error: custom error function
+ * @standard_error: standard error function
+ * @print_outfn: output/printing function
+ * @p: pointer to private data area
+ *
+ * The function makes possible to set custom error and
+ * output functions for the library. The private data
+ * pointer can be used to pass arbitrary data to these functions.
+ * If a function argument is NULL, the default printing function is set.
+ *
+ * Returns 0 on success or a negative error code.
+ */
+int
+ipset_custom_printf(struct ipset *ipset,
+ ipset_custom_errorfn custom_error,
+ ipset_standard_errorfn standard_error,
+ ipset_print_outfn print_outfn,
+ void *p)
+{
+ ipset->no_vhi = !!(custom_error || standard_error || print_outfn);
+ ipset->custom_error =
+ custom_error ? custom_error : default_custom_error;
+ ipset->standard_error =
+ standard_error ? standard_error : default_standard_error;
+
+ return ipset_session_print_outfn(ipset->session, print_outfn, p);
+}
+
+/**
+ * ipset_init - initialize ipset library interface
+ *
+ * Initialize the ipset library interface.
+ *
+ * Returns the created ipset structure for success or NULL for failure.
+ */
+struct ipset *
+ipset_init(void)
+{
+ struct ipset *ipset;
+
+ ipset = calloc(1, sizeof(struct ipset));
+ if (ipset == NULL)
+ return NULL;
+ ipset->newargv[0] =
+ calloc(strlen(program_name) + 1, sizeof(*program_name));
+ if (!ipset->newargv[0]) {
+ free(ipset);
+ return NULL;
+ }
+ ipset_strlcpy(ipset->newargv[0], program_name,
+ strlen(program_name) + 1);
+ ipset->newargc = 1;
+ ipset->session = ipset_session_init(NULL, NULL);
+ if (ipset->session == NULL) {
+ free(ipset->newargv[0]);
+ free(ipset);
+ return NULL;
+ }
+ ipset_custom_printf(ipset, NULL, NULL, NULL, NULL);
+ return ipset;
+}
+
+/**
+ * ipset_fini - destroy an ipset library interface
+ * @ipset: ipset structure
+ *
+ * Destroys an ipset library interface
+ *
+ * Returns 0 on success or a negative error code.
+ */
+int
+ipset_fini(struct ipset *ipset)
+{
+ assert(ipset);
+
+ if (ipset->session)
+ ipset_session_fini(ipset->session);
+ reset_argv(ipset);
+ if (ipset->newargv[0])
+ free(ipset->newargv[0]);
+
+ free(ipset);
+ return 0;
+}