diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.am | 4 | ||||
-rw-r--r-- | lib/ipset.c | 1472 | ||||
-rw-r--r-- | lib/ipset_hash_ipport.c | 2 | ||||
-rw-r--r-- | lib/ipset_hash_ipportip.c | 2 | ||||
-rw-r--r-- | lib/ipset_hash_ipportnet.c | 2 | ||||
-rw-r--r-- | lib/ipset_hash_netiface.c | 2 | ||||
-rw-r--r-- | lib/ipset_hash_netport.c | 2 | ||||
-rw-r--r-- | lib/ipset_hash_netportnet.c | 2 | ||||
-rw-r--r-- | lib/libipset.3 | 242 | ||||
-rw-r--r-- | lib/libipset.map | 21 | ||||
-rw-r--r-- | lib/mnl.c | 2 | ||||
-rw-r--r-- | lib/parse.c | 34 | ||||
-rw-r--r-- | lib/print.c | 2 | ||||
-rw-r--r-- | lib/session.c | 316 | ||||
-rw-r--r-- | lib/ui.c | 42 |
15 files changed, 2028 insertions, 119 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index d85d5bb..281f693 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -37,7 +37,7 @@ libipset_la_SOURCES = \ print.c \ session.c \ types.c \ - ui.c \ + ipset.c \ types_init.c EXTRA_libipset_la_SOURCES = \ @@ -48,6 +48,8 @@ EXTRA_DIST = $(IPSET_SETTYPE_LIST) libipset.map pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libipset.pc +dist_man_MANS = libipset.3 + sparse-check: $(libipset_la_SOURCES:.c=.d) %.d: %.c 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; +} diff --git a/lib/ipset_hash_ipport.c b/lib/ipset_hash_ipport.c index 870a02a..c505412 100644 --- a/lib/ipset_hash_ipport.c +++ b/lib/ipset_hash_ipport.c @@ -7,7 +7,7 @@ #include <libipset/data.h> /* IPSET_OPT_* */ #include <libipset/parse.h> /* parser functions */ #include <libipset/print.h> /* printing functions */ -#include <libipset/ui.h> /* ipset_port_usage */ +#include <libipset/ipset.h> /* ipset_port_usage */ #include <libipset/types.h> /* prototypes */ /* SCTP and UDPLITE support */ diff --git a/lib/ipset_hash_ipportip.c b/lib/ipset_hash_ipportip.c index c7fc153..b8e14a8 100644 --- a/lib/ipset_hash_ipportip.c +++ b/lib/ipset_hash_ipportip.c @@ -7,7 +7,7 @@ #include <libipset/data.h> /* IPSET_OPT_* */ #include <libipset/parse.h> /* parser functions */ #include <libipset/print.h> /* printing functions */ -#include <libipset/ui.h> /* ipset_port_usage */ +#include <libipset/ipset.h> /* ipset_port_usage */ #include <libipset/types.h> /* prototypes */ /* SCTP and UDPLITE support */ diff --git a/lib/ipset_hash_ipportnet.c b/lib/ipset_hash_ipportnet.c index e0e9eb1..bcc3c7b 100644 --- a/lib/ipset_hash_ipportnet.c +++ b/lib/ipset_hash_ipportnet.c @@ -7,7 +7,7 @@ #include <libipset/data.h> /* IPSET_OPT_* */ #include <libipset/parse.h> /* parser functions */ #include <libipset/print.h> /* printing functions */ -#include <libipset/ui.h> /* ipset_port_usage */ +#include <libipset/ipset.h> /* ipset_port_usage */ #include <libipset/types.h> /* prototypes */ /* SCTP and UDPLITE support */ diff --git a/lib/ipset_hash_netiface.c b/lib/ipset_hash_netiface.c index 9a4e7fa..1d829a3 100644 --- a/lib/ipset_hash_netiface.c +++ b/lib/ipset_hash_netiface.c @@ -7,7 +7,7 @@ #include <libipset/data.h> /* IPSET_OPT_* */ #include <libipset/parse.h> /* parser functions */ #include <libipset/print.h> /* printing functions */ -#include <libipset/ui.h> /* ipset_port_usage */ +#include <libipset/ipset.h> /* ipset_port_usage */ #include <libipset/types.h> /* prototypes */ /* Initial revision */ diff --git a/lib/ipset_hash_netport.c b/lib/ipset_hash_netport.c index e6d9aa9..f6409e2 100644 --- a/lib/ipset_hash_netport.c +++ b/lib/ipset_hash_netport.c @@ -7,7 +7,7 @@ #include <libipset/data.h> /* IPSET_OPT_* */ #include <libipset/parse.h> /* parser functions */ #include <libipset/print.h> /* printing functions */ -#include <libipset/ui.h> /* ipset_port_usage */ +#include <libipset/ipset.h> /* ipset_port_usage */ #include <libipset/types.h> /* prototypes */ /* SCTP and UDPLITE support */ diff --git a/lib/ipset_hash_netportnet.c b/lib/ipset_hash_netportnet.c index 3e19718..4d04dd8 100644 --- a/lib/ipset_hash_netportnet.c +++ b/lib/ipset_hash_netportnet.c @@ -7,7 +7,7 @@ #include <libipset/data.h> /* IPSET_OPT_* */ #include <libipset/parse.h> /* parser functions */ #include <libipset/print.h> /* printing functions */ -#include <libipset/ui.h> /* ipset_port_usage */ +#include <libipset/ipset.h> /* ipset_port_usage */ #include <libipset/types.h> /* prototypes */ /* initial revision */ diff --git a/lib/libipset.3 b/lib/libipset.3 new file mode 100644 index 0000000..840db06 --- /dev/null +++ b/lib/libipset.3 @@ -0,0 +1,242 @@ +.\" Man page written by 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 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. +.TH libipset 3 "Oct 16, 2018" "Jozsef Kadlecsik" "" +.SH NAME +libipset \- A library for using ipset +.SH SYNOPSIS +.nf +#include <libipset/ipset.h> +.sp +void ipset_load_types(void) +.sp +struct ipset * ipset_init(void) +int ipset_parse_argv(struct ipset *ipset, int argc, char *argv[]) +.sp +int ipset_parse_line(struct ipset *ipset, char *line) +.sp +int ipset_parse_stream(struct ipset *ipset, FILE *f) +.sp +int ipset_fini(struct ipset *ipset) +.sp +int ipset_custom_printf(struct ipset *ipset, + ipset_custom_errorfn custom_error, + ipset_standard_errorfn standard_error, + ipset_print_outfn outfn, + void *p) +.sp +struct ipset_session * ipset_session(struct ipset *ipset) +.sp +int ipset_session_full_io(struct ipset_session *session, + const char *filename, + enum ipset_io_type what) +.sp +int ipset_session_normal_io(struct ipset_session *session, + const char *filename, + enum ipset_io_type what) +.sp +FILE * ipset_session_io_stream(struct ipset_session *session, + enum ipset_io_type what) +.sp +int ipset_session_io_close(struct ipset_session *session, + enum ipset_io_type what) +.SH DESCRIPTION +libipset provides a library interface to +.BR ipset(8). +The integer return valued functions return 0 on success and a negative +value on failure. +.TP +ipset_load_types +Loads in the supported ipset types in the library and make them +available for the ipset interface. + +.TP +ipset_init +Initializes the ipset interface: allocates and initializes the required +internal structures, opens up the netlink channel. The function returns +the library interface structure of type +.B +struct ipset * +or +.B NULL +on failure. + +.TP +ipset_parse_argv +Parses the +.B argc +lenght of array of strings +.B argv +with the already initialized +.B +ipset +library structure. +If the command is successfully parsed, it is then submitted to the kernel +to execute. In the case of error, the textual error message is printed +and a negative number is returned. + +.TP +ipset_parse_line +Parses the string +.B line +with the already initialized +.B +ipset +library structure. The line is supposed to contain +a single ipset command in restore format. If the command is successfully +parsed, it is then submitted to the kernel to execute. In the case of +error, the textual error message is printed and a negative number is +returned. + +.TP +ipset_parse_stream +Parses the stream +.B f +with the already initialized +.B +ipset +library structure. The stream may contain multiple newline +separated ipset commands in restore format. The commands are parsed +and then submitted to the kernel in batches for efficiecy. In the case of +error, the textual error message is printed and a negative number is +returned. + +.TP +ipset_fini +Closes the netlink channel, closes opened streams and releases allocated +structures holding by the +.B ipset +library structure. + +.PP +The following functions makes possible to customize the interface. +.TP +ipset_custom_printf +Sets or resets the print functions for the +.B +ipset +library structure, where +.B +custom_error +is the custom error print function for the internal library errors, +.B +standard_error +is the print function for the netlink/kernel related errors and +.B +outfn +is the output function to print the result of list/save commands. +The +.B +p +pointer makes possible to pass arbitrary structure to the custom +print functions. If +.B +NULL +is passed instead of a function pointer, the default print function +is set for the given task. If any of the print functions is non-default, +then the +.I +version, +.I +help, +.I +interactive +ipset commands are ignored. + +.TP +ipset_session +The function returns the session structure +of the +.B +ipset +library structure, in order to manipulate the IO parameters. + +.TP +ipset_session_full_io +You can controll the full IO, i.e. input (restore) and output (save) +separatedly by the function. The +.B +session +parameter is the session structure of the library interface, +.B +filename +is the filename you want to use for input or output +and +.B +what +tells the function you want to set input or output file. +If there's an already opened file for the given IO mode, it is closed. +The function returns an error if normal mode is in use. If +.B +NULL +is passed instead of a filename, already opened file is closed +and the normal stream is set for the given IO mode (stdin for input, +stdout for output). Input/output files can be set separatedly. + +.TP +ipset_session_normal_io +You can controll the normal IO, which corresponds to the interface +provided by +.B +ipset(8) +itself. +.B +session +parameter is the session structure of the library interface, +.B +filename +is the filename you want to use for input or output +and +.B +what +tells the function you want to set input or output file. +If there's an already opened file for input/output, it is closed. +The function returns an error if full mode is in use. If +.B +NULL +is passed instead of a filename, already opened file is closed +and the normal stream is set for the given IO mode (stdin for input, +stdout for output). Input/output files cannot be set separatedly. + +.TP +ipset_session_io_stream +The function returns the stream set for the +.B +session +where +.B +what +tells the funtion you want to get the input or the output stream. + +.TP +ipset_session_io_close +The function closes the stream for the +.B +session +where +.B +what +tells the funtion you want to close the input or the output +stream. After closing, the standard streams are set: stdin for input, +stdout for output. + +.SH AUTHORS +ipset/libipset was designed and written by Jozsef Kadlecsik. + +.SH SEE ALSO +.BR ipset(8), +.br +/usr/include/libipset/ipset.h +/usr/include/libipset/session.h diff --git a/lib/libipset.map b/lib/libipset.map index 475fae5..a2383f3 100644 --- a/lib/libipset.map +++ b/lib/libipset.map @@ -173,3 +173,24 @@ LIBIPSET_4.7 { global: ipset_session_warning_as_error; } LIBIPSET_4.6; + +LIBIPSET_4.8 { +global: + ipset_parse_filename; + ipset_session; + ipset_is_interactive; + ipset_custom_printf; + ipset_parse_argv; + ipset_parse_line; + ipset_parse_stream; + ipset_init; + ipset_fini; + ipset_session_printf_private; + ipset_envopt_set; + ipset_envopt_unset; + ipset_session_print_outfn; + ipset_session_io_full; + ipset_session_io_normal; + ipset_session_io_stream; + ipset_session_io_close; +} LIBIPSET_4.7; @@ -13,7 +13,7 @@ #include <libipset/linux_ip_set.h> /* enum ipset_cmd */ #include <libipset/debug.h> /* D() */ #include <libipset/session.h> /* ipset_session_handle */ -#include <libipset/ui.h> /* IPSET_ENV_EXIST */ +#include <libipset/ipset.h> /* IPSET_ENV_EXIST */ #include <libipset/utils.h> /* UNUSED */ #include <libipset/mnl.h> /* prototypes */ diff --git a/lib/parse.c b/lib/parse.c index 4963d51..a88b9e2 100644 --- a/lib/parse.c +++ b/lib/parse.c @@ -1396,11 +1396,11 @@ ipset_parse_iptimeout(struct ipset_session *session, #define check_setname(str, saved) \ do { \ if (strlen(str) > IPSET_MAXNAMELEN - 1) { \ - int err; \ - err = syntax_err("setname '%s' is longer than %u characters",\ + int __err; \ + __err = syntax_err("setname '%s' is longer than %u characters",\ str, IPSET_MAXNAMELEN - 1); \ free(saved); \ - return err; \ + return __err; \ } \ } while (0) @@ -1876,34 +1876,6 @@ ipset_parse_skbprio(struct ipset_session *session, } /** - * ipset_parse_output - parse output format name - * @session: session 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_session *session, - int opt UNUSED, const char *str) -{ - assert(session); - assert(str); - - 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 syntax_err("unknown output mode '%s'", str); -} - -/** * ipset_parse_ignored - "parse" ignored option * @session: session structure * @opt: option kind of the data diff --git a/lib/print.c b/lib/print.c index 7dd229e..02ffe41 100644 --- a/lib/print.c +++ b/lib/print.c @@ -23,7 +23,7 @@ #include <libipset/types.h> /* ipset set types */ #include <libipset/session.h> /* IPSET_FLAG_ */ #include <libipset/utils.h> /* UNUSED */ -#include <libipset/ui.h> /* IPSET_ENV_* */ +#include <libipset/ipset.h> /* IPSET_ENV_* */ #include <libipset/print.h> /* prototypes */ /* Print data (to output buffer). All function must follow snprintf. */ diff --git a/lib/session.c b/lib/session.c index 16b5549..e782573 100644 --- a/lib/session.c +++ b/lib/session.c @@ -10,6 +10,7 @@ #include <setjmp.h> /* setjmp, longjmp */ #include <stdio.h> /* snprintf */ #include <stdarg.h> /* va_* */ +#include <stdbool.h> /* bool */ #include <stdlib.h> /* free */ #include <string.h> /* str* */ #include <unistd.h> /* getpagesize */ @@ -25,7 +26,7 @@ #include <libipset/transport.h> /* transport */ #include <libipset/mnl.h> /* default backend */ #include <libipset/utils.h> /* STREQ */ -#include <libipset/ui.h> /* IPSET_ENV_* */ +#include <libipset/ipset.h> /* IPSET_ENV_* */ #include <libipset/session.h> /* prototypes */ #define IPSET_NEST_MAX 4 @@ -47,7 +48,11 @@ struct ipset_session { /* Output buffer */ char outbuf[IPSET_OUTBUFLEN]; /* Output buffer */ enum ipset_output_mode mode; /* Output mode */ - ipset_outfn outfn; /* Output function */ + ipset_print_outfn print_outfn; /* Output function to file */ + void *p; /* Private data for print_outfn */ + /* Session IO */ + bool normal_io, full_io; /* Default/normal/full IO */ + FILE *istream, *ostream; /* Session input/output stream */ /* Error/warning reporting */ char report[IPSET_ERRORBUFLEN]; /* Error/report buffer */ char *errmsg; @@ -115,41 +120,24 @@ ipset_session_lineno(struct ipset_session *session, uint32_t lineno) session->lineno = lineno; } -/* - * Environment options - */ - /** - * ipset_envopt_parse - parse/set environment option + * ipset_session_printf_private - returns the session private pointer * @session: session structure - * @opt: environment option - * @arg: option argument (unused) - * - * Parse and set an environment option. * - * Returns 0 on success or a negative error code. + * Returns the private pointer in the session structure, + * for private/custom print fuctions. */ -int -ipset_envopt_parse(struct ipset_session *session, int opt, - const char *arg UNUSED) +void * +ipset_session_printf_private(struct ipset_session *session) { assert(session); - - 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: - session->envopts |= opt; - return 0; - default: - break; - } - return -1; + return session->p; } +/* + * Environment options + */ + /** * ipset_envopt_test - test environment option * @session: session structure @@ -167,6 +155,34 @@ ipset_envopt_test(struct ipset_session *session, enum ipset_envopt opt) } /** + * ipset_envopt_set - set environment option + * @session: session structure + * @opt: environment option + * + * Set an environment option of the session. + */ +void +ipset_envopt_set(struct ipset_session *session, enum ipset_envopt opt) +{ + assert(session); + session->envopts |= opt; +} + +/** + * ipset_envopt_unset - unset environment option + * @session: session structure + * @opt: environment option + * + * Unset an environment option of the session. + */ +void +ipset_envopt_unset(struct ipset_session *session, enum ipset_envopt opt) +{ + assert(session); + session->envopts &= ~opt; +} + +/** * ipset_session_output - set the session output mode * @session: session structure * @mode: output mode @@ -722,7 +738,8 @@ static const char cmd2name[][9] = { static inline int call_outfn(struct ipset_session *session) { - int ret = session->outfn("%s", session->outbuf); + int ret = session->print_outfn(session, session->p, + "%s", session->outbuf); session->outbuf[0] = '\0'; @@ -2034,29 +2051,57 @@ cleanup: return ret; } +static +int __attribute__ ((format (printf, 3, 4))) +default_print_outfn(struct ipset_session *session, void *p UNUSED, + const char *fmt, ...) +{ + int len; + va_list args; + + va_start(args, fmt); + len = vfprintf(session->ostream, fmt, args); + va_end(args); + + return len; +} + /** - * ipset_session_outfn - set session output printing function + * ipset_session_print_outfn - set session output printing function + * @session: session structure + * @outfn: output printing function + * @p: pointer to private area * - * Set the session printing function. + * Set the session output printing function. If the @outfn is NULL, + * then the default output function is configured. You can set + * the @p pointer to a private area: the output printing function + * is called with @p in one of its arguments. * + * Returns 0 on success or a negative error code. */ int -ipset_session_outfn(struct ipset_session *session, ipset_outfn outfn) +ipset_session_print_outfn(struct ipset_session *session, + ipset_print_outfn outfn, + void *p) { - session->outfn = outfn ? outfn : printf; + session->print_outfn = outfn ? outfn : default_print_outfn; + session->p = p; return 0; } /** * ipset_session_init - initialize an ipset session + * @outfn: output printing function + * @p: pointer to private area * * Initialize an ipset session by allocating a session structure - * and filling out with the initialization data. + * and filling out with the initialization data. The function + * calls ipset_session_print_outfn() to set @print_outfn, @p. * * Returns the created session sctructure on success or NULL. */ struct ipset_session * -ipset_session_init(ipset_outfn outfn) +ipset_session_init(ipset_print_outfn print_outfn, void *p) { struct ipset_session *session; size_t bufsize = getpagesize(); @@ -2067,12 +2112,14 @@ ipset_session_init(ipset_outfn outfn) return NULL; session->bufsize = bufsize; session->buffer = session + 1; + session->istream = stdin; + session->ostream = stdout; /* The single transport method yet */ session->transport = &ipset_mnl_transport; /* Output function */ - session->outfn = outfn; + ipset_session_print_outfn(session, print_outfn, p); /* Initialize data structures */ session->data = ipset_data_init(); @@ -2088,6 +2135,197 @@ free_session: } /** + * ipset_session_io_full - set full IO for the session + * @session: session structure + * @filename: filename + * @what: operate on input/output + * + * The normal "-file" CLI interface does not provide an interface + * to set both the input (restore) and output (list/save) for + * a session. This function makes it possible to configure those. + * + * When a filename for input is passed, then the file will be opened + * for reading. + * When a filename for output is passed, then the file will be opened + * for writing. + * Previously opened files are closed. + * If NULL is passed as filename, stdin/stdout is set. + * Input/output files can be set separatedly. + * The function returns error if the file cannot be opened or + * normal IO mode is already set. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_session_io_full(struct ipset_session *session, const char *filename, + enum ipset_io_type what) +{ + FILE *f; + + assert(session); + + if (session->normal_io) + return ipset_err(session, + "Normal IO is in use, full IO cannot be selected"); + + switch (what) { + case IPSET_IO_INPUT: + if (session->istream != stdin) + fclose(session->istream); + if (!filename) { + session->istream = stdin; + } else { + f = fopen(filename, "r"); + if (!f) + return ipset_err(session, + "Cannot open %s for reading: %s", + filename, strerror(errno)); + session->istream = f; + } + break; + case IPSET_IO_OUTPUT: + if (session->ostream != stdout) + fclose(session->ostream); + if (!filename) { + session->ostream = stdout; + } else { + f = fopen(filename, "w"); + if (!f) + return ipset_err(session, + "Cannot open %s for writing: %s", + filename, strerror(errno)); + session->ostream = f; + } + break; + default: + return ipset_err(session, + "Library error, invalid ipset_io_type"); + } + session->full_io = !(session->istream == stdin && + session->ostream == stdout); + return 0; +} + +/** + * ipset_session_io_normal - set normal IO for the session + * @session: session structure + * @filename: filename + * @what: operate on input/output + * + * The normal "-file" CLI interface to set either the input (restore) + * or output (list/save) for a session. This function does not make + * possible to set both independently. + * + * When a filename for input is passed, then the file will be opened + * for reading. + * When a filename for output is passed, then the file will be opened + * for writing. + * Previously opened files are closed. + * If NULL is passed as filename, stdin/stdout is set. + * Input/output files cannot be set separatedly. + * The function returns error if the file cannot be opened or + * full IO mode is already set. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_session_io_normal(struct ipset_session *session, const char *filename, + enum ipset_io_type what) +{ + FILE *f; + + assert(session); + assert(filename); + + if (session->full_io) + return ipset_err(session, + "Full IO is in use, normal IO cannot be selected"); + if (session->istream != stdin) { + fclose(session->istream); + session->istream = stdin; + } + if (session->ostream != stdout) { + fclose(session->ostream); + session->ostream = stdout; + } + switch (what) { + case IPSET_IO_INPUT: + f = fopen(filename, "r"); + if (!f) + return ipset_err(session, + "Cannot open %s for reading: %s", + filename, strerror(errno)); + session->istream = f; + break; + case IPSET_IO_OUTPUT: + f = fopen(filename, "w"); + if (!f) + return ipset_err(session, + "Cannot open %s for writing: %s", + filename, strerror(errno)); + session->ostream = f; + break; + default: + return ipset_err(session, + "Library error, invalid ipset_io_type"); + } + session->normal_io = !(session->istream == stdin && + session->ostream == stdout); + return 0; +} + +/** + * ipset_session_io_stream - returns the input or output stream + * @what: operate on input/output + * + * Returns the input or output stream of the session. + */ +FILE * +ipset_session_io_stream(struct ipset_session *session, + enum ipset_io_type what) +{ + switch (what) { + case IPSET_IO_INPUT: + return session->istream; + case IPSET_IO_OUTPUT: + return session->ostream; + default: + return NULL; + } +} + +/** + * ipset_session_io_close - closes the input or output stream + * @what: operate on input/output + * + * Closes the input or output stream of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_session_io_close(struct ipset_session *session, + enum ipset_io_type what) +{ + switch (what) { + case IPSET_IO_INPUT: + if (session->istream != stdin) { + fclose(session->istream); + session->istream = stdin; + } + break; + case IPSET_IO_OUTPUT: + if (session->ostream != stdout) { + fclose(session->ostream); + session->ostream = stdout; + } + break; + default: + break; + } + return 0; +} + +/** * ipset_session_fini - destroy an ipset session * @session: session structure * @@ -2104,6 +2342,10 @@ ipset_session_fini(struct ipset_session *session) session->transport->fini(session->handle); if (session->data) ipset_data_fini(session->data); + if (session->istream != stdin) + fclose(session->istream); + if (session->ostream != stdout) + fclose(session->ostream); ipset_cache_fini(); free(session); diff --git a/lib/ui.c b/lib/ui.c deleted file mode 100644 index 7cb4bc2..0000000 --- a/lib/ui.c +++ /dev/null @@ -1,42 +0,0 @@ -/* 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 <stdio.h> /* printf */ -#include <libipset/icmp.h> /* id_to_icmp */ -#include <libipset/icmpv6.h> /* id_to_icmpv6 */ -#include <libipset/ui.h> /* prototypes */ - -/** - * 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); -} |