From 55fdd96e331e920ee62bd816a572ac24f6dcd1ae Mon Sep 17 00:00:00 2001 From: Jozsef Kadlecsik Date: Thu, 18 Oct 2018 17:27:49 +0200 Subject: Library reworked to support embedding ipset completely The ipset library is rewritten/extended to support embedding ipset, so that sets can fully be managed without calling the ipset binary. The ipset binary relies completely on the new library. The libipset.3 manpage was written about the library functions and usage. Signed-off-by: Jozsef Kadlecsik --- Make_global.am | 2 +- include/libipset/Makefile.am | 2 +- include/libipset/ipset.h | 90 +++ include/libipset/parse.h | 2 - include/libipset/session.h | 36 +- include/libipset/ui.h | 56 -- lib/Makefile.am | 4 +- lib/ipset.c | 1472 ++++++++++++++++++++++++++++++++++++++++++ lib/ipset_hash_ipport.c | 2 +- lib/ipset_hash_ipportip.c | 2 +- lib/ipset_hash_ipportnet.c | 2 +- lib/ipset_hash_netiface.c | 2 +- lib/ipset_hash_netport.c | 2 +- lib/ipset_hash_netportnet.c | 2 +- lib/libipset.3 | 242 +++++++ lib/libipset.map | 21 + lib/mnl.c | 2 +- lib/parse.c | 34 +- lib/print.c | 2 +- lib/session.c | 316 +++++++-- lib/ui.c | 42 -- src/Makefile.am | 2 +- src/ipset.c | 889 +------------------------ 23 files changed, 2160 insertions(+), 1066 deletions(-) create mode 100644 include/libipset/ipset.h delete mode 100644 include/libipset/ui.h create mode 100644 lib/ipset.c create mode 100644 lib/libipset.3 delete mode 100644 lib/ui.c diff --git a/Make_global.am b/Make_global.am index f695c4d..e666986 100644 --- a/Make_global.am +++ b/Make_global.am @@ -69,7 +69,7 @@ # interface. # curr:rev:age -LIBVERSION = 12:0:1 +LIBVERSION = 13:0:0 AM_CPPFLAGS = $(kinclude_CFLAGS) $(all_includes) -I$(top_srcdir)/include diff --git a/include/libipset/Makefile.am b/include/libipset/Makefile.am index 79a1357..c7f7b2b 100644 --- a/include/libipset/Makefile.am +++ b/include/libipset/Makefile.am @@ -16,7 +16,7 @@ pkginclude_HEADERS = \ session.h \ transport.h \ types.h \ - ui.h \ + ipset.h \ utils.h EXTRA_DIST = debug.h icmp.h icmpv6.h diff --git a/include/libipset/ipset.h b/include/libipset/ipset.h new file mode 100644 index 0000000..7eba74e --- /dev/null +++ b/include/libipset/ipset.h @@ -0,0 +1,90 @@ +/* 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. + */ +#ifndef LIBIPSET_IPSET_H +#define LIBIPSET_IPSET_H + +#include /* bool */ +#include /* enum ipset_cmd */ +#include /* ipset_session_* */ +#include /* ipset_load_types */ + +#define IPSET_CMD_ALIASES 3 + +/* Commands in userspace */ +struct ipset_commands { + enum ipset_cmd cmd; + int has_arg; + const char *name[IPSET_CMD_ALIASES]; + const char *help; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +extern const struct ipset_commands ipset_commands[]; + +struct ipset_session; +struct ipset_data; +struct ipset; + + +/* Environment options */ +struct ipset_envopts { + int flag; + int has_arg; + const char *name[2]; + const char *help; + int (*parse)(struct ipset *ipset, int flag, const char *str); + int (*print)(char *buf, unsigned int len, + const struct ipset_data *data, int flag, uint8_t env); +}; + +extern const struct ipset_envopts ipset_envopts[]; + +extern bool ipset_match_cmd(const char *arg, const char * const name[]); +extern bool ipset_match_option(const char *arg, const char * const name[]); +extern bool ipset_match_envopt(const char *arg, const char * const name[]); +extern void ipset_port_usage(void); +extern int ipset_parse_filename(struct ipset *ipset, int opt, const char *str); +extern int ipset_parse_output(struct ipset *ipset, + int opt, const char *str); +extern int ipset_envopt_parse(struct ipset *ipset, + int env, const char *str); + +enum ipset_exittype { + IPSET_NO_PROBLEM = 0, + IPSET_OTHER_PROBLEM, + IPSET_PARAMETER_PROBLEM, + IPSET_VERSION_PROBLEM, + IPSET_SESSION_PROBLEM, +}; + +typedef int (*ipset_custom_errorfn)(struct ipset *ipset, void *p, + int status, const char *msg, ...) + __attribute__ ((format (printf, 4, 5))); +typedef int (*ipset_standard_errorfn)(struct ipset *ipset, void *p); + +extern struct ipset_session * ipset_session(struct ipset *ipset); +extern bool ipset_is_interactive(struct ipset *ipset); +extern int ipset_custom_printf(struct ipset *ipset, + ipset_custom_errorfn custom_error, + ipset_standard_errorfn standard_error, + ipset_print_outfn outfn, + void *p); + +extern int ipset_parse_argv(struct ipset *ipset, int argc, char *argv[]); +extern int ipset_parse_line(struct ipset *ipset, char *line); +extern int ipset_parse_stream(struct ipset *ipset, FILE *f); +extern struct ipset * ipset_init(void); +extern int ipset_fini(struct ipset *ipset); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBIPSET_IPSET_H */ diff --git a/include/libipset/parse.h b/include/libipset/parse.h index 9223eeb..4bd5df5 100644 --- a/include/libipset/parse.h +++ b/include/libipset/parse.h @@ -104,8 +104,6 @@ extern int ipset_parse_skbmark(struct ipset_session *session, enum ipset_opt opt, const char *str); extern int ipset_parse_skbprio(struct ipset_session *session, enum ipset_opt opt, const char *str); -extern int ipset_parse_output(struct ipset_session *session, - int opt, const char *str); extern int ipset_parse_ignored(struct ipset_session *session, enum ipset_opt opt, const char *str); extern int ipset_parse_elem(struct ipset_session *session, diff --git a/include/libipset/session.h b/include/libipset/session.h index bac4d19..825d2c8 100644 --- a/include/libipset/session.h +++ b/include/libipset/session.h @@ -19,7 +19,6 @@ struct ipset_session; struct ipset_data; -struct ipset_handle; #ifdef __cplusplus extern "C" { @@ -33,6 +32,7 @@ extern const struct ipset_type * ipset_saved_type(const struct ipset_session *session); extern void ipset_session_lineno(struct ipset_session *session, uint32_t lineno); +extern void * ipset_session_printf_private(struct ipset_session *session); enum ipset_err_type { IPSET_ERROR, @@ -80,10 +80,12 @@ enum ipset_envopt { IPSET_ENV_LIST_HEADER = (1 << IPSET_ENV_BIT_LIST_HEADER), }; -extern int ipset_envopt_parse(struct ipset_session *session, - int env, const char *str); extern bool ipset_envopt_test(struct ipset_session *session, enum ipset_envopt env); +extern void ipset_envopt_set(struct ipset_session *session, + enum ipset_envopt env); +extern void ipset_envopt_unset(struct ipset_session *session, + enum ipset_envopt env); enum ipset_output_mode { IPSET_LIST_NONE, @@ -99,12 +101,30 @@ extern int ipset_commit(struct ipset_session *session); extern int ipset_cmd(struct ipset_session *session, enum ipset_cmd cmd, uint32_t lineno); -typedef int (*ipset_outfn)(const char *fmt, ...) - __attribute__ ((format (printf, 1, 2))); +typedef int (*ipset_print_outfn)(struct ipset_session *session, + void *p, const char *fmt, ...) + __attribute__ ((format (printf, 3, 4))); -extern int ipset_session_outfn(struct ipset_session *session, - ipset_outfn outfn); -extern struct ipset_session *ipset_session_init(ipset_outfn outfn); +extern int ipset_session_print_outfn(struct ipset_session *session, + ipset_print_outfn outfn, + void *p); + +enum ipset_io_type { + IPSET_IO_INPUT, + IPSET_IO_OUTPUT, +}; + +extern int ipset_session_io_full(struct ipset_session *session, + const char *filename, enum ipset_io_type what); +extern int ipset_session_io_normal(struct ipset_session *session, + const char *filename, enum ipset_io_type what); +extern FILE * ipset_session_io_stream(struct ipset_session *session, + enum ipset_io_type what); +extern int ipset_session_io_close(struct ipset_session *session, + enum ipset_io_type what); + +extern struct ipset_session *ipset_session_init(ipset_print_outfn outfn, + void *p); extern int ipset_session_fini(struct ipset_session *session); extern void ipset_debug_msg(const char *dir, void *buffer, int len); diff --git a/include/libipset/ui.h b/include/libipset/ui.h deleted file mode 100644 index 4846dc1..0000000 --- a/include/libipset/ui.h +++ /dev/null @@ -1,56 +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. - */ -#ifndef LIBIPSET_UI_H -#define LIBIPSET_UI_H - -#include /* bool */ -#include /* enum ipset_cmd */ - -#define IPSET_CMD_ALIASES 3 - -/* Commands in userspace */ -struct ipset_commands { - enum ipset_cmd cmd; - int has_arg; - const char *name[IPSET_CMD_ALIASES]; - const char *help; -}; - -#ifdef __cplusplus -extern "C" { -#endif - -extern const struct ipset_commands ipset_commands[]; - -struct ipset_session; -struct ipset_data; - -/* Environment options */ -struct ipset_envopts { - int flag; - int has_arg; - const char *name[2]; - const char *help; - int (*parse)(struct ipset_session *s, int flag, const char *str); - int (*print)(char *buf, unsigned int len, - const struct ipset_data *data, int flag, uint8_t env); -}; - -extern const struct ipset_envopts ipset_envopts[]; - -extern bool ipset_match_cmd(const char *arg, const char * const name[]); -extern bool ipset_match_option(const char *arg, const char * const name[]); -extern bool ipset_match_envopt(const char *arg, const char * const name[]); -extern void ipset_shift_argv(int *argc, char *argv[], int from); -extern void ipset_port_usage(void); -extern int ipset_parse_file(struct ipset_session *s, int opt, const char *str); - -#ifdef __cplusplus -} -#endif - -#endif /* LIBIPSET_UI_H */ 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 */ +#include /* isspace */ +#include /* errno */ +#include /* va_* */ +#include /* bool */ +#include /* printf */ +#include /* exit */ +#include /* str* */ + +#include + +#include /* D() */ +#include /* IPSET_CMD_* */ +#include /* id_to_icmp */ +#include /* id_to_icmpv6 */ +#include /* enum ipset_data */ +#include /* IPSET_*_ARG */ +#include /* ipset_envopt_parse */ +#include /* ipset_parse_family */ +#include /* ipset_print_family */ +#include /* STREQ */ +#include /* 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 /* IPSET_OPT_* */ #include /* parser functions */ #include /* printing functions */ -#include /* ipset_port_usage */ +#include /* ipset_port_usage */ #include /* 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 /* IPSET_OPT_* */ #include /* parser functions */ #include /* printing functions */ -#include /* ipset_port_usage */ +#include /* ipset_port_usage */ #include /* 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 /* IPSET_OPT_* */ #include /* parser functions */ #include /* printing functions */ -#include /* ipset_port_usage */ +#include /* ipset_port_usage */ #include /* 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 /* IPSET_OPT_* */ #include /* parser functions */ #include /* printing functions */ -#include /* ipset_port_usage */ +#include /* ipset_port_usage */ #include /* 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 /* IPSET_OPT_* */ #include /* parser functions */ #include /* printing functions */ -#include /* ipset_port_usage */ +#include /* ipset_port_usage */ #include /* 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 /* IPSET_OPT_* */ #include /* parser functions */ #include /* printing functions */ -#include /* ipset_port_usage */ +#include /* ipset_port_usage */ #include /* 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 +.\" +.\" 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 +.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; diff --git a/lib/mnl.c b/lib/mnl.c index 4e075cf..4ce90b4 100644 --- a/lib/mnl.c +++ b/lib/mnl.c @@ -13,7 +13,7 @@ #include /* enum ipset_cmd */ #include /* D() */ #include /* ipset_session_handle */ -#include /* IPSET_ENV_EXIST */ +#include /* IPSET_ENV_EXIST */ #include /* UNUSED */ #include /* 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) @@ -1875,34 +1875,6 @@ ipset_parse_skbprio(struct ipset_session *session, return ipset_data_set(data, IPSET_OPT_SKBPRIO, &major); } -/** - * 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 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 /* ipset set types */ #include /* IPSET_FLAG_ */ #include /* UNUSED */ -#include /* IPSET_ENV_* */ +#include /* IPSET_ENV_* */ #include /* 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, longjmp */ #include /* snprintf */ #include /* va_* */ +#include /* bool */ #include /* free */ #include /* str* */ #include /* getpagesize */ @@ -25,7 +26,7 @@ #include /* transport */ #include /* default backend */ #include /* STREQ */ -#include /* IPSET_ENV_* */ +#include /* IPSET_ENV_* */ #include /* 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 @@ -166,6 +154,34 @@ ipset_envopt_test(struct ipset_session *session, enum ipset_envopt opt) return session->envopts & 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 @@ -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(); @@ -2087,6 +2134,197 @@ free_session: return NULL; } +/** + * 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 /* printf */ -#include /* id_to_icmp */ -#include /* id_to_icmpv6 */ -#include /* 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); -} diff --git a/src/Makefile.am b/src/Makefile.am index 810efb7..438fcec 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,7 @@ include $(top_srcdir)/Make_global.am sbin_PROGRAMS = ipset -ipset_SOURCES = ipset.c ui.c +ipset_SOURCES = ipset.c ipset_LDADD = ../lib/libipset.la if ENABLE_SETTYPE_MODULES diff --git a/src/ipset.c b/src/ipset.c index 14a351a..fcb2247 100644 --- a/src/ipset.c +++ b/src/ipset.c @@ -7,898 +7,31 @@ * published by the Free Software Foundation. */ #include /* assert */ -#include /* isspace */ -#include /* errno */ -#include /* va_* */ -#include /* bool */ -#include /* fprintf, fgets */ +#include /* fprintf */ #include /* exit */ -#include /* str* */ #include - -#include /* D() */ -#include /* enum ipset_data */ -#include /* ipset_parse_* */ -#include /* ipset_session_* */ -#include /* struct ipset_type */ -#include /* core options, commands */ -#include /* STREQ */ - -static char program_name[] = PACKAGE; -static char program_version[] = PACKAGE_VERSION; - -static struct ipset_session *session; -static uint32_t restore_line; -static bool interactive; -static char cmdline[1024]; -static char *newargv[255]; -static int newargc; -static FILE *fd = NULL; -static const char *filename = NULL; - -enum exittype { - NO_PROBLEM = 0, - OTHER_PROBLEM, - PARAMETER_PROBLEM, - VERSION_PROBLEM, - SESSION_PROBLEM, -}; - -static int __attribute__((format(printf, 2, 3))) -exit_error(int status, const char *msg, ...) -{ - bool quiet = !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 != SESSION_PROBLEM) - fprintf(stderr, "\n"); - - if (status == PARAMETER_PROBLEM) - fprintf(stderr, - "Try `%s help' for more information.\n", - program_name); - } - /* Ignore errors in interactive mode */ - if (status && interactive) { - if (session) - ipset_session_report_reset(session); - return -1; - } - - if (session) - ipset_session_fini(session); - - D("status: %u", status); - if (fd) - fclose(fd); - exit(status > VERSION_PROBLEM ? OTHER_PROBLEM : status); - /* Unreached */ - return -1; -} - -static int -handle_error(void) -{ - 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 exit_error(SESSION_PROBLEM, "%s", - ipset_session_error(session)); - - if (!interactive) { - ipset_session_fini(session); - if (fd) - fclose(fd); - exit(OTHER_PROBLEM); - } - - ipset_session_report_reset(session); - return -1; -} - -static void -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++; - } -} - -int -ipset_parse_file(struct ipset_session *s UNUSED, - int opt UNUSED, const char *str) -{ - if (filename != NULL) - return exit_error(PARAMETER_PROBLEM, - "-file option can be specified once"); - filename = str; - - return 0; -} - -static -int __attribute__ ((format (printf, 1, 2))) -ipset_print_file(const char *fmt, ...) -{ - int len; - va_list args; - - assert(fd != NULL); - va_start(args, fmt); - len = vfprintf(fd, fmt, args); - va_end(args); - - return len; -} - -/* Build faked argv from parsed line */ -static void -build_argv(char *buffer) -{ - char *tmp, *arg; - int i; - bool quoted = false; - - /* Reset */ - for (i = 1; i < newargc; i++) { - if (newargv[i]) - free(newargv[i]); - newargv[i] = NULL; - } - newargc = 1; - - arg = calloc(strlen(buffer) + 1, sizeof(*buffer)); - for (tmp = buffer, i = 0; *tmp; tmp++) { - if ((newargc + 1) == (int)(sizeof(newargv)/sizeof(char *))) { - exit_error(PARAMETER_PROBLEM, - "Line is too long to parse."); - goto out; - } - 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) { - exit_error(PARAMETER_PROBLEM, "Missing close quote!"); - goto out; - } - if (!*arg) - continue; - newargv[newargc] = calloc(strlen(arg) + 1, sizeof(*arg)); - ipset_strlcpy(newargv[newargc++], arg, strlen(arg) + 1); - memset(arg, 0, strlen(arg) + 1); - i = 0; - } -out: - free(arg); -} - -/* Main parser function, workhorse */ -int parse_commandline(int argc, char *argv[]); - -/* - * Performs a restore from stdin - */ -static int -restore(char *argv0) -{ - int ret = 0; - char *c; - FILE *rfd = stdin; - - /* Initialize newargv/newargc */ - newargc = 0; - newargv[newargc] = calloc(strlen(argv0) + 1, sizeof(*argv0)); - ipset_strlcpy(newargv[newargc++], argv0, strlen(argv0) + 1); - if (filename) { - fd = fopen(filename, "r"); - if (!fd) { - return exit_error(OTHER_PROBLEM, - "Cannot open %s for reading: %s", - filename, strerror(errno)); - } - rfd = fd; - } - - while (fgets(cmdline, sizeof(cmdline), rfd)) { - restore_line++; - c = 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(session); - if (ret < 0) - handle_error(); - continue; - } - /* Build faked argv, argc */ - build_argv(c); - - /* Execute line */ - ret = parse_commandline(newargc, newargv); - if (ret < 0) - handle_error(); - } - /* implicit "COMMIT" at EOF */ - ret = ipset_commit(session); - if (ret < 0) - handle_error(); - - free(newargv[0]); - return ret; -} - -static bool do_parse(const struct ipset_arg *arg, bool family) -{ - return !((family == true) ^ (arg->opt == IPSET_OPT_FAMILY)); -} - -static int -call_parser(int *argc, char *argv[], const struct ipset_type *type, - enum ipset_adt cmd, bool family) -{ - 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 exit_error(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 exit_error(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( - session, arg, argv[i]); - if (ret < 0) - return ret; - } - i++; - break; - } - /* Fall through */ - default: - if (do_parse(arg, family)) { - ret = ipset_call_parser( - 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 exit_error(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 exit_error(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(const struct ipset_type *type, enum ipset_cmd command) -{ - enum ipset_adt cmd = cmd2cmd(command); - 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) { - exit_error(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)) { - exit_error(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(void) -{ - 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(const struct ipset_type *type, enum ipset_cmd command) -{ - 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: - exit_error(OTHER_PROBLEM, - "IP/CIDR range is not allowed in command %s " - "with set type %s and family %s", - cmd2name(command), type->name, - session_family()); - return; - case IPSET_OPT_IP_TO: - exit_error(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()); - return; - case IPSET_OPT_PORT_TO: - exit_error(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()); - return; - default: - break; - } - /* Other options */ - if (type->cmd[cmd].args[0] == IPSET_ARG_NONE) { - exit_error(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; - exit_error(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()); - return; - } - exit_error(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", -}; - -/* Workhorse */ -int -parse_commandline(int argc, char *argv[]) -{ - int ret = 0; - enum ipset_cmd cmd = IPSET_CMD_NONE; - int i; - char *arg0 = NULL, *arg1 = NULL, *c; - const struct ipset_envopts *opt; - const struct ipset_commands *command; - const struct ipset_type *type; - - /* Set session lineno to report parser errors correctly */ - ipset_session_lineno(session, 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 exit_error(PARAMETER_PROBLEM, - "Missing mandatory argument " - "to option %s", - opt->name[0]); - /* Fall through */ - case IPSET_OPTIONAL_ARG: - if (i + 1 <= argc) { - ret = opt->parse(session, opt->flag, - argv[i]); - if (ret < 0) - return handle_error(); - ipset_shift_argv(&argc, argv, i); - } - break; - case IPSET_NO_ARG: - ret = opt->parse(session, opt->flag, - opt->name[0]); - if (ret < 0) - return handle_error(); - 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 (restore_line != 0 && - (command->cmd == IPSET_CMD_RESTORE || - command->cmd == IPSET_CMD_VERSION || - command->cmd == IPSET_CMD_HELP)) - return exit_error(PARAMETER_PROBLEM, - "Command `%s' is invalid " - "in restore mode.", - command->name[0]); - if (interactive && command->cmd == IPSET_CMD_RESTORE) { - printf("Restore command ignored " - "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 exit_error(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 exit_error(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 (interactive) { - printf("No command specified\n"); - if (session) - ipset_envopt_parse(session, 0, "reset"); - return 0; - } - if (argc > 1 && STREQ(argv[1], "-")) { - interactive = true; - printf("%s> ", program_name); - /* Initialize newargv/newargc */ - newargv[newargc++] = program_name; - while (fgets(cmdline, sizeof(cmdline), stdin)) { - c = cmdline; - while (isspace(c[0])) - c++; - if (c[0] == '\0' || c[0] == '#') { - printf("%s> ", program_name); - continue; - } - /* Build fake argv, argc */ - build_argv(c); - /* Execute line: ignore soft errors */ - if (parse_commandline(newargc, newargv) < 0) - handle_error(); - printf("%s> ", program_name); - } - return exit_error(NO_PROBLEM, NULL); - } - if (argc > 1) - return exit_error(PARAMETER_PROBLEM, - "No command specified: unknown argument %s", - argv[1]); - return exit_error(PARAMETER_PROBLEM, "No command specified."); - case IPSET_CMD_VERSION: - printf("%s v%s, protocol version: %u\n", - program_name, program_version, IPSET_PROTOCOL); - if (interactive) - return 0; - return exit_error(NO_PROBLEM, NULL); - case IPSET_CMD_HELP: - help(); - - if (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 exit_error(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 (interactive) - return 0; - return exit_error(NO_PROBLEM, NULL); - case IPSET_CMD_QUIT: - return exit_error(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 handle_error(); - - ret = ipset_parse_typename(session, IPSET_OPT_TYPENAME, arg1); - if (ret < 0) - return handle_error(); - - type = ipset_type_get(session, cmd); - if (type == NULL) - return handle_error(); - - /* Parse create options: first check INET family */ - ret = call_parser(&argc, argv, type, IPSET_CREATE, true); - if (ret < 0) - return handle_error(); - else if (ret) - return ret; - - /* Parse create options: then check all options */ - ret = call_parser(&argc, argv, type, IPSET_CREATE, false); - if (ret < 0) - return handle_error(); - else if (ret) - return ret; - - /* Check mandatory, then allowed options */ - check_mandatory(type, cmd); - check_allowed(type, cmd); - - break; - case IPSET_CMD_LIST: - case IPSET_CMD_SAVE: - if (filename != NULL) { - fd = fopen(filename, "w"); - if (!fd) - return exit_error(OTHER_PROBLEM, - "Cannot open %s for writing: " - "%s", filename, - strerror(errno)); - ipset_session_outfn(session, ipset_print_file); - } - case IPSET_CMD_DESTROY: - case IPSET_CMD_FLUSH: - /* Args: [setname] */ - if (arg0) { - ret = ipset_parse_setname(session, - IPSET_SETNAME, arg0); - if (ret < 0) - return handle_error(); - } - 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 handle_error(); - ret = ipset_parse_setname(session, IPSET_OPT_SETNAME2, arg1); - if (ret < 0) - return handle_error(); - break; - - case IPSET_CMD_RESTORE: - /* Restore mode */ - if (argc > 1) - return exit_error(PARAMETER_PROBLEM, - "Unknown argument %s", argv[1]); - return restore(argv[0]); - 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 handle_error(); - - type = ipset_type_get(session, cmd); - if (type == NULL) - return handle_error(); - - ret = ipset_parse_elem(session, type->last_elem_optional, arg1); - if (ret < 0) - return handle_error(); - - /* Parse additional ADT options */ - ret = call_parser(&argc, argv, type, cmd2cmd(cmd), false); - if (ret < 0) - return handle_error(); - else if (ret) - return ret; - - /* Check mandatory, then allowed options */ - check_mandatory(type, cmd); - check_allowed(type, cmd); - - break; - default: - break; - } - - if (argc > 1) - return exit_error(PARAMETER_PROBLEM, - "Unknown argument %s", argv[1]); - ret = ipset_cmd(session, cmd, 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) - handle_error(); - - return ret; -} +#include /* ipset library */ int main(int argc, char *argv[]) { + struct ipset *ipset; int ret; /* Load set types */ ipset_load_types(); - /* Initialize session */ - session = ipset_session_init(printf); - if (session == NULL) - return exit_error(OTHER_PROBLEM, - "Cannot initialize ipset session, aborting."); + /* Initialize ipset library */ + ipset = ipset_init(); + if (ipset == NULL) { + fprintf(stderr, "Cannot initialize ipset, aborting."); + exit(1); + } - ret = parse_commandline(argc, argv); + ret = ipset_parse_argv(ipset, argc, argv); - ipset_session_fini(session); - if (fd) - fclose(fd); + ipset_fini(ipset); return ret; } -- cgit v1.2.3