diff --git a/conf/format-options.py b/conf/format-options.py index fc6e6e1fd..7d67c9890 100755 --- a/conf/format-options.py +++ b/conf/format-options.py @@ -92,8 +92,9 @@ class ConfigOption: class Parser: """Parses one or more files of configuration options""" - def __init__(self): + def __init__(self, sort = True): self.options = [] + self.sort = sort def parse(self, file): """Parses the given file and adds all options to the internal store""" @@ -145,7 +146,8 @@ class Parser: found.adopt(option) else: parent.options.append(option) - parent.options.sort() + if self.sort: + parent.options.sort() def __get_option(self, parts, create = False): """Searches/Creates the option (section) based on a list of section names""" @@ -160,7 +162,8 @@ class Parser: break option = ConfigOption(fullname, section = True) options.append(option) - options.sort() + if self.sort: + options.sort() options = option.options return option @@ -241,15 +244,16 @@ class ConfFormatter: def __print_section(self, section, indent, commented): """Print a section with all options""" - comment = "# " if commented or section.commented else "" + commented = commented or section.commented + comment = "# " if commented else "" self.__print_description(section, indent) print '{0}{1}{2} {{'.format(self.__indent * indent, comment, section.name) print for o in sorted(section.options, key=attrgetter('section')): if o.section: - self.__print_section(o, indent + 1, section.commented) + self.__print_section(o, indent + 1, commented) else: - self.__print_option(o, indent + 1, section.commented) + self.__print_option(o, indent + 1, commented) print '{0}{1}}}'.format(self.__indent * indent, comment) print @@ -309,9 +313,12 @@ options.add_option("-f", "--format", dest="format", type="choice", choices=["con options.add_option("-r", "--root", dest="root", metavar="NAME", help="root section of which options are printed, " "if not found everything is printed") +options.add_option("-n", "--nosort", action="store_false", dest="sort", + default=True, help="do not sort sections alphabetically") + (opts, args) = options.parse_args() -parser = Parser() +parser = Parser(opts.sort) if len(args): for filename in args: try: diff --git a/configure.ac b/configure.ac index 6dbcccf37..966b9d002 100644 --- a/configure.ac +++ b/configure.ac @@ -56,6 +56,7 @@ ARG_WITH_SUBST([ipseclibdir], [${libdir%/}/ipsec], [set installation path ARG_WITH_SUBST([plugindir], [${ipseclibdir%/}/plugins], [set the installation path of plugins]) ARG_WITH_SUBST([imcvdir], [${ipseclibdir%/}/imcvs], [set the installation path of IMC and IMV dynamic librariers]) ARG_WITH_SUBST([nm-ca-dir], [/usr/share/ca-certificates], [directory the NM backend uses to look up trusted root certificates]) +ARG_WITH_SUBST([swanctldir], [${sysconfdir}/swanctl], [base directory for swanctl configuration files and credentials]) ARG_WITH_SUBST([linux-headers], [\${top_srcdir}/src/include], [set directory of linux header files to use]) ARG_WITH_SUBST([routing-table], [220], [set routing table to use for IPsec routes]) ARG_WITH_SUBST([routing-table-prio], [220], [set priority for IPsec routing table]) @@ -265,6 +266,7 @@ ARG_ENABL_SET([medcli], [enable mediation client configuration database ARG_ENABL_SET([medsrv], [enable mediation server web frontend and daemon plugin.]) ARG_ENABL_SET([nm], [enable NetworkManager backend.]) ARG_DISBL_SET([scripts], [disable additional utilities (found in directory scripts).]) +ARG_ENABL_SET([swanctl], [enable swanctl configuration and control tool.]) ARG_ENABL_SET([tkm], [enable Trusted Key Manager support.]) ARG_DISBL_SET([tools], [disable additional utilities (scepclient and pki).]) ARG_ENABL_SET([aikgen], [enable AIK generator.]) @@ -399,6 +401,10 @@ if test x$fips_prf = xtrue; then fi fi +if test x$swanctl = xtrue; then + vici=true +fi + if test x$smp = xtrue -o x$tnccs_11 = xtrue -o x$tnc_ifmap = xtrue; then xml=true fi @@ -1389,6 +1395,7 @@ AM_CONDITIONAL(COVERAGE, test x$coverage = xtrue) AM_CONDITIONAL(USE_TKM, test x$tkm = xtrue) AM_CONDITIONAL(USE_CMD, test x$cmd = xtrue) AM_CONDITIONAL(USE_AIKGEN, test x$aikgen = xtrue) +AM_CONDITIONAL(USE_SWANCTL, test x$swanctl = xtrue) # ======================== # set global definitions @@ -1605,6 +1612,7 @@ AC_CONFIG_FILES([ src/checksum/Makefile src/conftest/Makefile src/pt-tls-client/Makefile + src/swanctl/Makefile scripts/Makefile testing/Makefile ]) @@ -1631,6 +1639,9 @@ AC_CONFIG_FILES([ src/pki/man/pki---signcrl.1 src/pki/man/pki---acert.1 src/pki/man/pki---verify.1 + src/swanctl/swanctl.8 + src/swanctl/swanctl.conf.5.head + src/swanctl/swanctl.conf.5.tail ]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index e76eb4398..38e4b834d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -76,6 +76,10 @@ if USE_TOOLS SUBDIRS += scepclient pki endif +if USE_SWANCTL + SUBDIRS += swanctl +endif + if USE_CONFTEST SUBDIRS += conftest endif diff --git a/src/checksum/Makefile.am b/src/checksum/Makefile.am index 82bbadcf1..078c59790 100644 --- a/src/checksum/Makefile.am +++ b/src/checksum/Makefile.am @@ -104,6 +104,10 @@ if USE_TOOLS exes += $(DESTDIR)$(bindir)/pki endif +if USE_SWANCTL + exes += $(DESTDIR)$(sbindir)/swanctl +endif + if USE_ATTR_SQL exes += $(DESTDIR)$(ipsecdir)/pool endif diff --git a/src/libcharon/daemon.c b/src/libcharon/daemon.c index 0cecd1d3b..16babf014 100644 --- a/src/libcharon/daemon.c +++ b/src/libcharon/daemon.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "daemon.h" @@ -476,6 +477,53 @@ static void destroy(private_daemon_t *this) free(this); } +/** + * Run a set of configured scripts + */ +static void run_scripts(private_daemon_t *this, char *verb) +{ + enumerator_t *enumerator; + char *key, *value, *pos, buf[1024]; + FILE *cmd; + + enumerator = lib->settings->create_key_value_enumerator(lib->settings, + "%s.%s-scripts", lib->ns, verb); + while (enumerator->enumerate(enumerator, &key, &value)) + { + DBG1(DBG_DMN, "executing %s script '%s' (%s):", verb, key, value); + cmd = popen(value, "r"); + if (!cmd) + { + DBG1(DBG_DMN, "executing %s script '%s' (%s) failed: %s", + verb, key, value, strerror(errno)); + continue; + } + while (TRUE) + { + if (!fgets(buf, sizeof(buf), cmd)) + { + if (ferror(cmd)) + { + DBG1(DBG_DMN, "reading from %s script '%s' (%s) failed", + verb, key, value); + } + break; + } + else + { + pos = buf + strlen(buf); + if (pos > buf && pos[-1] == '\n') + { + pos[-1] = '\0'; + } + DBG1(DBG_DMN, "%s: %s", key, buf); + } + } + pclose(cmd); + } + enumerator->destroy(enumerator); +} + METHOD(daemon_t, start, void, private_daemon_t *this) { @@ -483,6 +531,8 @@ METHOD(daemon_t, start, void, lib->processor->set_threads(lib->processor, lib->settings->get_int(lib->settings, "%s.threads", DEFAULT_THREADS, lib->ns)); + + run_scripts(this, "start"); } @@ -598,6 +648,8 @@ void libcharon_deinit() return; } + run_scripts(this, "stop"); + destroy(this); charon = NULL; } diff --git a/src/swanctl/.gitignore b/src/swanctl/.gitignore new file mode 100644 index 000000000..11c04cb46 --- /dev/null +++ b/src/swanctl/.gitignore @@ -0,0 +1,7 @@ +swanctl +swanctl.8 +swanctl.conf +swanctl.conf.5 +swanctl.conf.5.main +swanctl.conf.5.head +swanctl.conf.5.tail \ No newline at end of file diff --git a/src/swanctl/Makefile.am b/src/swanctl/Makefile.am new file mode 100644 index 000000000..a262a2fa7 --- /dev/null +++ b/src/swanctl/Makefile.am @@ -0,0 +1,65 @@ +sbin_PROGRAMS = swanctl + +swanctl_SOURCES = \ + command.c command.h \ + commands/initiate.c \ + commands/terminate.c \ + commands/install.c \ + commands/list_sas.c \ + commands/list_pols.c \ + commands/list_conns.c \ + commands/list_certs.c \ + commands/list_pools.c \ + commands/load_conns.c \ + commands/load_creds.c \ + commands/load_pools.c \ + commands/log.c \ + commands/version.c \ + swanctl.c swanctl.h + +swanctl_LDADD = \ + $(top_builddir)/src/libcharon/plugins/vici/libvici.la \ + $(top_builddir)/src/libstrongswan/libstrongswan.la + +swanctl.o : $(top_builddir)/config.status + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libcharon/plugins/vici \ + -DSWANCTLDIR=\""${swanctldir}\"" \ + -DPLUGINS=\""${s_plugins}\"" + +man_MANS = \ + swanctl.8 \ + swanctl.conf.5 + +BUILT_SOURCES = swanctl.conf swanctl.conf.5.main +EXTRA_DIST = swanctl.opt swanctl.conf swanctl.conf.5.main +CLEANFILES = $(man_MANS) + +.opt.conf: + $(AM_V_GEN) \ + $(PYTHON) $(top_srcdir)/conf/format-options.py -n -f conf $< > $(srcdir)/$@ + +swanctl.conf.5.main: swanctl.opt + $(AM_V_GEN) \ + $(PYTHON) $(top_srcdir)/conf/format-options.py -n -f man $< > $(srcdir)/$@ + +swanctl.conf.5: swanctl.conf.5.head swanctl.conf.5.main swanctl.conf.5.tail + $(AM_V_GEN) \ + cat swanctl.conf.5.head $(srcdir)/swanctl.conf.5.main swanctl.conf.5.tail > $@ + +maintainer-clean-local: + cd $(srcdir) && rm -f swanctl.conf swanctl.conf.5.main + +install-data-local: swanctl.conf + test -e "$(DESTDIR)$(swanctldir)" || $(INSTALL) -d "$(DESTDIR)$(swanctldir)" + test -e "$(DESTDIR)$(swanctldir)/x509" || $(INSTALL) -d "$(DESTDIR)$(swanctldir)/x509" || true + test -e "$(DESTDIR)$(swanctldir)/x509ca" || $(INSTALL) -d "$(DESTDIR)$(swanctldir)/x509ca" || true + test -e "$(DESTDIR)$(swanctldir)/x509aa" || $(INSTALL) -d "$(DESTDIR)$(swanctldir)/x509aa" || true + test -e "$(DESTDIR)$(swanctldir)/x509crl" || $(INSTALL) -d "$(DESTDIR)$(swanctldir)/x509crl" || true + test -e "$(DESTDIR)$(swanctldir)/x509ac" || $(INSTALL) -d "$(DESTDIR)$(swanctldir)/x509ac" || true + test -e "$(DESTDIR)$(swanctldir)/rsa" || $(INSTALL) -d -m 750 "$(DESTDIR)$(swanctldir)/rsa" || true + test -e "$(DESTDIR)$(swanctldir)/ecdsa" || $(INSTALL) -d -m 750 "$(DESTDIR)$(swanctldir)/ecdsa" || true + test -e "$(DESTDIR)$(swanctldir)/pkcs8" || $(INSTALL) -d -m 750 "$(DESTDIR)$(swanctldir)/pkcs8" || true + test -e "$(DESTDIR)$(swanctldir)/swanctl.conf" || $(INSTALL) -m 640 $(srcdir)/swanctl.conf $(DESTDIR)$(swanctldir)/swanctl.conf || true diff --git a/src/swanctl/command.c b/src/swanctl/command.c new file mode 100644 index 000000000..e488273bf --- /dev/null +++ b/src/swanctl/command.c @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2009 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * 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. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "command.h" + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include +#include +#include + +/** + * Registered commands. + */ +static command_t cmds[MAX_COMMANDS]; + +/** + * active command. + */ +static int active = 0; + +/** + * number of registered commands + */ +static int registered = 0; + +/** + * help command index + */ +static int help_idx; + +/** + * Uri to connect to + */ +static char *uri = NULL; + +static int argc; + +static char **argv; + +static options_t *options; + +/** + * Global options used by all subcommands + */ +static struct option command_opts[MAX_COMMANDS > MAX_OPTIONS ? + MAX_COMMANDS : MAX_OPTIONS]; + +/** + * Global optstring used by all subcommands + */ +static char command_optstring[(MAX_COMMANDS > MAX_OPTIONS ? + MAX_COMMANDS : MAX_OPTIONS) * 3]; + +/** + * Build command_opts/command_optstr for the active command + */ +static void build_opts() +{ + int i, pos = 0; + + memset(command_opts, 0, sizeof(command_opts)); + memset(command_optstring, 0, sizeof(command_optstring)); + if (active == help_idx) + { + for (i = 0; i < MAX_COMMANDS && cmds[i].cmd; i++) + { + command_opts[i].name = cmds[i].cmd; + command_opts[i].val = cmds[i].op; + command_optstring[i] = cmds[i].op; + } + } + else + { + for (i = 0; cmds[active].options[i].name; i++) + { + command_opts[i].name = cmds[active].options[i].name; + command_opts[i].has_arg = cmds[active].options[i].arg; + command_opts[i].val = cmds[active].options[i].op; + command_optstring[pos++] = cmds[active].options[i].op; + switch (cmds[active].options[i].arg) + { + case optional_argument: + command_optstring[pos++] = ':'; + /* FALL */ + case required_argument: + command_optstring[pos++] = ':'; + /* FALL */ + case no_argument: + default: + break; + } + } + } +} + +/** + * getopt_long wrapper + */ +int command_getopt(char **arg) +{ + int op; + + while (TRUE) + { + op = getopt_long(argc, argv, command_optstring, command_opts, NULL); + switch (op) + { + case '+': + if (!options->from(options, optarg, &argc, &argv, optind)) + { + /* a error value */ + return 255; + } + continue; + case 'v': + dbg_default_set_level(atoi(optarg)); + continue; + case 'u': + uri = optarg; + continue; + default: + *arg = optarg; + return op; + } + } +} + +/** + * Register a command + */ +void command_register(command_t command) +{ + int i; + + if (registered == MAX_COMMANDS) + { + fprintf(stderr, "unable to register command, please increase " + "MAX_COMMANDS\n"); + return; + } + + cmds[registered] = command; + /* append default options, but not to --help */ + if (!active) + { + for (i = 0; i < countof(cmds[registered].options) - 1; i++) + { + if (!cmds[registered].options[i].name) + { + break; + } + } + if (i > countof(cmds[registered].options) - 3) + { + fprintf(stderr, "command '%s' registered too many options, please " + "increase MAX_OPTIONS\n", command.cmd); + } + else + { + cmds[registered].options[i++] = (command_option_t) { + "debug", 'v', 1, "set debug level, default: 1" + }; + cmds[registered].options[i++] = (command_option_t) { + "options", '+', 1, "read command line options from file" + }; + cmds[registered].options[i++] = (command_option_t) { + "uri", 'u', 1, "service URI to connect to" + }; + } + } + registered++; +} + +/** + * Print usage text, with an optional error + */ +int command_usage(char *error, ...) +{ + va_list args; + FILE *out = stdout; + int i; + + if (error) + { + out = stderr; + fprintf(out, "Error: "); + va_start(args, error); + vfprintf(out, error, args); + va_end(args); + fprintf(out, "\n"); + } + fprintf(out, "strongSwan %s swanctl\n", VERSION); + + if (active == help_idx) + { + fprintf(out, "loaded plugins: %s\n", + lib->plugins->loaded_plugins(lib->plugins)); + } + + fprintf(out, "usage:\n"); + if (active == help_idx) + { + for (i = 0; i < MAX_COMMANDS && cmds[i].cmd; i++) + { + fprintf(out, " swanctl --%-10s (-%c) %s\n", + cmds[i].cmd, cmds[i].op, cmds[i].description); + } + } + else + { + for (i = 0; cmds[active].line[i]; i++) + { + if (i == 0) + { + fprintf(out, " swanctl --%s %s\n", + cmds[active].cmd, cmds[active].line[i]); + } + else + { + fprintf(out, " %s\n", cmds[active].line[i]); + } + } + for (i = 0; cmds[active].options[i].name; i++) + { + fprintf(out, " --%-15s (-%c) %s\n", + cmds[active].options[i].name, cmds[active].options[i].op, + cmds[active].options[i].desc); + } + } + return error != NULL; +} + +/** + * Dispatch cleanup hook + */ +static void cleanup() +{ + options->destroy(options); +} + +/** + * Open vici connection, call a command + */ +static int call_command(command_t *cmd) +{ + vici_conn_t *conn; + int ret; + + conn = vici_connect(uri); + if (!conn) + { + command_usage("connecting to '%s' URI failed: %s", + uri ?: "default", strerror(errno)); + return errno; + } + ret = cmd->call(conn); + vici_disconnect(conn); + return ret; +} + +/** + * Dispatch commands. + */ +int command_dispatch(int c, char *v[]) +{ + int op, i; + + options = options_create(); + atexit(cleanup); + active = help_idx = registered; + argc = c; + argv = v; + command_register((command_t){NULL, 'h', "help", "show usage information"}); + + build_opts(); + op = getopt_long(c, v, command_optstring, command_opts, NULL); + for (i = 0; i < MAX_COMMANDS && cmds[i].cmd; i++) + { + if (cmds[i].op == op) + { + active = i; + build_opts(); + if (help_idx == i) + { + return command_usage(NULL); + } + return call_command(&cmds[i]); + } + } + return command_usage(c > 1 ? "invalid operation" : NULL); +} diff --git a/src/swanctl/command.h b/src/swanctl/command.h new file mode 100644 index 000000000..a394796d1 --- /dev/null +++ b/src/swanctl/command.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2009 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * 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. See . + * + * 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. + */ + +/** + * @defgroup command command + * @{ @ingroup swanctl + */ + +#ifndef COMMAND_H_ +#define COMMAND_H_ + +#include +#include + +/** + * Maximum number of commands (+1). + */ +#define MAX_COMMANDS 15 + +/** + * Maximum number of options in a command (+3) + */ +#define MAX_OPTIONS 32 + +/** + * Maximum number of usage summary lines (+1) + */ +#define MAX_LINES 10 + +typedef struct command_t command_t; +typedef struct command_option_t command_option_t; +typedef enum command_type_t command_type_t; + +/** + * Option specification + */ +struct command_option_t { + /** long option string of the option */ + char *name; + /** short option character of the option */ + char op; + /** expected argument to option, no/req/opt_argument */ + int arg; + /** description of the option */ + char *desc; +}; + +/** + * Command specification. + */ +struct command_t { + /** Function implementing the command */ + int (*call)(vici_conn_t *conn); + /** short option character */ + char op; + /** long option string */ + char *cmd; + /** description of the command */ + char *description; + /** usage summary of the command */ + char *line[MAX_LINES]; + /** list of options the command accepts */ + command_option_t options[MAX_OPTIONS]; +}; + +/** + * Get the next option, as with getopt. + */ +int command_getopt(char **arg); + +/** + * Register a command. + */ +void command_register(command_t command); + +/** + * Dispatch commands. + */ +int command_dispatch(int argc, char *argv[]); + +/** + * Show usage information of active command. + */ +int command_usage(char *error, ...); + +#endif /** COMMAND_H_ @}*/ diff --git a/src/swanctl/commands/initiate.c b/src/swanctl/commands/initiate.c new file mode 100644 index 000000000..a4a83737c --- /dev/null +++ b/src/swanctl/commands/initiate.c @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "command.h" + +#include + + +CALLBACK(log_cb, void, + bool *raw, char *name, vici_res_t *msg) +{ + if (*raw) + { + vici_dump(msg, "log", stdout); + } + else + { + printf("[%s] %s\n", + vici_find_str(msg, " ", "group"), + vici_find_str(msg, "", "msg")); + } +} + +static int initiate(vici_conn_t *conn) +{ + vici_req_t *req; + vici_res_t *res; + bool raw = FALSE; + char *arg, *child = NULL; + int ret = 0, timeout = 0, level = 1; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 'r': + raw = TRUE; + continue; + case 'c': + child = arg; + continue; + case 't': + timeout = atoi(arg); + continue; + case 'l': + level = atoi(arg); + continue; + case EOF: + break; + default: + return command_usage("invalid --initiate option"); + } + break; + } + + if (vici_register(conn, "control-log", log_cb, &raw) != 0) + { + fprintf(stderr, "registering for log failed: %s\n", strerror(errno)); + return errno; + } + req = vici_begin("initiate"); + if (child) + { + vici_add_key_valuef(req, "child", "%s", child); + } + if (timeout) + { + vici_add_key_valuef(req, "timeout", "%d", timeout * 1000); + } + vici_add_key_valuef(req, "loglevel", "%d", level); + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "initiate request failed: %s\n", strerror(errno)); + return errno; + } + if (raw) + { + vici_dump(res, "initiate reply", stdout); + } + else + { + if (streq(vici_find_str(res, "no", "success"), "yes")) + { + printf("initiate completed successfully\n"); + } + else + { + fprintf(stderr, "initiate failed: %s\n", + vici_find_str(res, "", "errmsg")); + ret = 1; + } + } + vici_free_res(res); + return ret; +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + initiate, 'i', "initiate", "initiate a connection", + {"--child [--timeout ] [--raw]"}, + { + {"help", 'h', 0, "show usage information"}, + {"child", 'c', 1, "initate a CHILD_SA configuration"}, + {"timeout", 't', 1, "timeout in seconds before detaching"}, + {"raw", 'r', 0, "dump raw response message"}, + {"loglevel", 'l', 1, "verbosity of redirected log"}, + } + }); +} diff --git a/src/swanctl/commands/install.c b/src/swanctl/commands/install.c new file mode 100644 index 000000000..a0cef58d8 --- /dev/null +++ b/src/swanctl/commands/install.c @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "command.h" + +#include + +static int manage_policy(vici_conn_t *conn, char *label) +{ + vici_req_t *req; + vici_res_t *res; + bool raw = FALSE; + char *arg, *child = NULL; + int ret; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 'r': + raw = TRUE; + continue; + case 'c': + child = arg; + continue; + case EOF: + break; + default: + return command_usage("invalid --%s option", label); + } + break; + } + req = vici_begin(label); + if (child) + { + vici_add_key_valuef(req, "child", "%s", child); + } + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "%s request failed: %s\n", label, strerror(errno)); + return errno; + } + if (raw) + { + puts(label); + vici_dump(res, " reply", stdout); + } + else + { + if (streq(vici_find_str(res, "no", "success"), "yes")) + { + printf("%s completed successfully\n", label); + } + else + { + fprintf(stderr, "%s failed: %s\n", + label, vici_find_str(res, "", "errmsg")); + ret = 1; + } + } + vici_free_res(res); + return ret; +} + +static int uninstall(vici_conn_t *conn) +{ + return manage_policy(conn, "uninstall"); +} + +static int install(vici_conn_t *conn) +{ + return manage_policy(conn, "install"); +} + +/** + * Register the uninstall command. + */ +static void __attribute__ ((constructor))reg_uninstall() +{ + command_register((command_t) { + uninstall, 'u', "uninstall", "uninstall a trap or shunt policy", + {"--child [--raw]"}, + { + {"help", 'h', 0, "show usage information"}, + {"child", 'c', 1, "CHILD_SA configuration to uninstall"}, + {"raw", 'r', 0, "dump raw response message"}, + } + }); +} + +/** + * Register install the command. + */ +static void __attribute__ ((constructor))reg_install() +{ + command_register((command_t) { + install, 'p', "install", "install a trap or shunt policy", + {"--child [--raw]"}, + { + {"help", 'h', 0, "show usage information"}, + {"child", 'c', 1, "CHILD_SA configuration to install"}, + {"raw", 'r', 0, "dump raw response message"}, + } + }); +} diff --git a/src/swanctl/commands/list_certs.c b/src/swanctl/commands/list_certs.c new file mode 100644 index 000000000..d9b773892 --- /dev/null +++ b/src/swanctl/commands/list_certs.c @@ -0,0 +1,668 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * 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. + */ + +#define _GNU_SOURCE +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "command.h" + +typedef enum { + FORMAT_RAW = (1<<0), + FORMAT_PEM = (1<<1), +} format_options_t; + +/** + * Print PEM encoding of a certificate + */ +static void print_pem(certificate_t *cert) +{ + chunk_t encoding; + + if (cert->get_encoding(cert, CERT_PEM, &encoding)) + { + printf("%.*s", (int)encoding.len, encoding.ptr); + free(encoding.ptr); + } + else + { + fprintf(stderr, "PEM encoding certificate failed\n"); + } +} + +/** + * Print public key information + */ +static void print_pubkey(public_key_t *key, bool has_privkey) +{ + chunk_t chunk; + + printf("pubkey: %N %d bits", key_type_names, key->get_type(key), + key->get_keysize(key)); + if (has_privkey) + { + printf(", has private key"); + } + printf("\n"); + if (key->get_fingerprint(key, KEYID_PUBKEY_INFO_SHA1, &chunk)) + { + printf("keyid: %#B\n", &chunk); + } + if (key->get_fingerprint(key, KEYID_PUBKEY_SHA1, &chunk)) + { + printf("subjkey: %#B\n", &chunk); + } +} + +/** + * Print X509 specific certificate information + */ +static void print_x509(x509_t *x509) +{ + enumerator_t *enumerator; + identification_t *id; + traffic_selector_t *block; + chunk_t chunk; + bool first; + char *uri; + int len, explicit, inhibit; + x509_flag_t flags; + x509_cdp_t *cdp; + x509_cert_policy_t *policy; + x509_policy_mapping_t *mapping; + + chunk = chunk_skip_zero(x509->get_serial(x509)); + printf("serial: %#B\n", &chunk); + + first = TRUE; + enumerator = x509->create_subjectAltName_enumerator(x509); + while (enumerator->enumerate(enumerator, &id)) + { + if (first) + { + printf("altNames: "); + first = FALSE; + } + else + { + printf(", "); + } + printf("%Y", id); + } + if (!first) + { + printf("\n"); + } + enumerator->destroy(enumerator); + + flags = x509->get_flags(x509); + printf("flags: "); + if (flags & X509_CA) + { + printf("CA "); + } + if (flags & X509_CRL_SIGN) + { + printf("CRLSign "); + } + if (flags & X509_AA) + { + printf("AA "); + } + if (flags & X509_OCSP_SIGNER) + { + printf("OCSP "); + } + if (flags & X509_AA) + { + printf("AA "); + } + if (flags & X509_SERVER_AUTH) + { + printf("serverAuth "); + } + if (flags & X509_CLIENT_AUTH) + { + printf("clientAuth "); + } + if (flags & X509_IKE_INTERMEDIATE) + { + printf("iKEIntermediate "); + } + if (flags & X509_SELF_SIGNED) + { + printf("self-signed "); + } + printf("\n"); + + first = TRUE; + enumerator = x509->create_crl_uri_enumerator(x509); + while (enumerator->enumerate(enumerator, &cdp)) + { + if (first) + { + printf("CRL URIs: %s", cdp->uri); + first = FALSE; + } + else + { + printf(" %s", cdp->uri); + } + if (cdp->issuer) + { + printf(" (CRL issuer: %Y)", cdp->issuer); + } + printf("\n"); + } + enumerator->destroy(enumerator); + + first = TRUE; + enumerator = x509->create_ocsp_uri_enumerator(x509); + while (enumerator->enumerate(enumerator, &uri)) + { + if (first) + { + printf("OCSP URIs: %s\n", uri); + first = FALSE; + } + else + { + printf(" %s\n", uri); + } + } + enumerator->destroy(enumerator); + + len = x509->get_constraint(x509, X509_PATH_LEN); + if (len != X509_NO_CONSTRAINT) + { + printf("pathlen: %d\n", len); + } + + first = TRUE; + enumerator = x509->create_name_constraint_enumerator(x509, TRUE); + while (enumerator->enumerate(enumerator, &id)) + { + if (first) + { + printf("Permitted NameConstraints:\n"); + first = FALSE; + } + printf(" %Y\n", id); + } + enumerator->destroy(enumerator); + first = TRUE; + enumerator = x509->create_name_constraint_enumerator(x509, FALSE); + while (enumerator->enumerate(enumerator, &id)) + { + if (first) + { + printf("Excluded NameConstraints:\n"); + first = FALSE; + } + printf(" %Y\n", id); + } + enumerator->destroy(enumerator); + + first = TRUE; + enumerator = x509->create_cert_policy_enumerator(x509); + while (enumerator->enumerate(enumerator, &policy)) + { + char *oid; + + if (first) + { + printf("CertificatePolicies:\n"); + first = FALSE; + } + oid = asn1_oid_to_string(policy->oid); + if (oid) + { + printf(" %s\n", oid); + free(oid); + } + else + { + printf(" %#B\n", &policy->oid); + } + if (policy->cps_uri) + { + printf(" CPS: %s\n", policy->cps_uri); + } + if (policy->unotice_text) + { + printf(" Notice: %s\n", policy->unotice_text); + + } + } + enumerator->destroy(enumerator); + + first = TRUE; + enumerator = x509->create_policy_mapping_enumerator(x509); + while (enumerator->enumerate(enumerator, &mapping)) + { + char *issuer_oid, *subject_oid; + + if (first) + { + printf("PolicyMappings:\n"); + first = FALSE; + } + issuer_oid = asn1_oid_to_string(mapping->issuer); + subject_oid = asn1_oid_to_string(mapping->subject); + printf(" %s => %s\n", issuer_oid, subject_oid); + free(issuer_oid); + free(subject_oid); + } + enumerator->destroy(enumerator); + + explicit = x509->get_constraint(x509, X509_REQUIRE_EXPLICIT_POLICY); + inhibit = x509->get_constraint(x509, X509_INHIBIT_POLICY_MAPPING); + len = x509->get_constraint(x509, X509_INHIBIT_ANY_POLICY); + + if (explicit != X509_NO_CONSTRAINT || inhibit != X509_NO_CONSTRAINT || + len != X509_NO_CONSTRAINT) + { + printf("PolicyConstraints:\n"); + if (explicit != X509_NO_CONSTRAINT) + { + printf(" requireExplicitPolicy: %d\n", explicit); + } + if (inhibit != X509_NO_CONSTRAINT) + { + printf(" inhibitPolicyMapping: %d\n", inhibit); + } + if (len != X509_NO_CONSTRAINT) + { + printf(" inhibitAnyPolicy: %d\n", len); + } + } + + chunk = x509->get_authKeyIdentifier(x509); + if (chunk.ptr) + { + printf("authkeyId: %#B\n", &chunk); + } + + chunk = x509->get_subjectKeyIdentifier(x509); + if (chunk.ptr) + { + printf("subjkeyId: %#B\n", &chunk); + } + if (x509->get_flags(x509) & X509_IP_ADDR_BLOCKS) + { + first = TRUE; + printf("addresses: "); + enumerator = x509->create_ipAddrBlock_enumerator(x509); + while (enumerator->enumerate(enumerator, &block)) + { + if (first) + { + first = FALSE; + } + else + { + printf(", "); + } + printf("%R", block); + } + enumerator->destroy(enumerator); + printf("\n"); + } +} + +/** + * Print CRL specific information + */ +static void print_crl(crl_t *crl) +{ + enumerator_t *enumerator; + time_t ts; + crl_reason_t reason; + chunk_t chunk; + int count = 0; + bool first; + char buf[64]; + struct tm tm; + x509_cdp_t *cdp; + + chunk = chunk_skip_zero(crl->get_serial(crl)); + printf("serial: %#B\n", &chunk); + + if (crl->is_delta_crl(crl, &chunk)) + { + chunk = chunk_skip_zero(chunk); + printf("delta CRL: for serial %#B\n", &chunk); + } + chunk = crl->get_authKeyIdentifier(crl); + printf("authKeyId: %#B\n", &chunk); + + first = TRUE; + enumerator = crl->create_delta_crl_uri_enumerator(crl); + while (enumerator->enumerate(enumerator, &cdp)) + { + if (first) + { + printf("freshest: %s", cdp->uri); + first = FALSE; + } + else + { + printf(" %s", cdp->uri); + } + if (cdp->issuer) + { + printf(" (CRL issuer: %Y)", cdp->issuer); + } + printf("\n"); + } + enumerator->destroy(enumerator); + + enumerator = crl->create_enumerator(crl); + while (enumerator->enumerate(enumerator, &chunk, &ts, &reason)) + { + count++; + } + enumerator->destroy(enumerator); + + printf("%d revoked certificate%s%s\n", count, + count == 1 ? "" : "s", count ? ":" : ""); + enumerator = crl->create_enumerator(crl); + while (enumerator->enumerate(enumerator, &chunk, &ts, &reason)) + { + chunk = chunk_skip_zero(chunk); + localtime_r(&ts, &tm); + strftime(buf, sizeof(buf), "%F %T", &tm); + printf(" %#B %N %s\n", &chunk, crl_reason_names, reason, buf); + count++; + } + enumerator->destroy(enumerator); +} + +/** + * Print AC specific information + */ +static void print_ac(ac_t *ac) +{ + ac_group_type_t type; + identification_t *id; + enumerator_t *groups; + chunk_t chunk; + bool first = TRUE; + + chunk = chunk_skip_zero(ac->get_serial(ac)); + printf("serial: %#B\n", &chunk); + + id = ac->get_holderIssuer(ac); + if (id) + { + printf("hissuer: \"%Y\"\n", id); + } + chunk = chunk_skip_zero(ac->get_holderSerial(ac)); + if (chunk.ptr) + { + printf("hserial: %#B\n", &chunk); + } + groups = ac->create_group_enumerator(ac); + while (groups->enumerate(groups, &type, &chunk)) + { + int oid; + char *str; + + if (first) + { + printf("groups: "); + first = FALSE; + } + else + { + printf(" "); + } + switch (type) + { + case AC_GROUP_TYPE_STRING: + printf("%.*s", (int)chunk.len, chunk.ptr); + break; + case AC_GROUP_TYPE_OID: + oid = asn1_known_oid(chunk); + if (oid == OID_UNKNOWN) + { + str = asn1_oid_to_string(chunk); + if (str) + { + printf("%s", str); + free(str); + } + else + { + printf("OID:%#B", &chunk); + } + } + else + { + printf("%s", oid_names[oid].name); + } + break; + case AC_GROUP_TYPE_OCTETS: + printf("%#B", &chunk); + break; + } + printf("\n"); + } + groups->destroy(groups); + + chunk = ac->get_authKeyIdentifier(ac); + if (chunk.ptr) + { + printf("authkey: %#B\n", &chunk); + } +} + +/** + * Print certificate information + */ +static void print_cert(certificate_t *cert, bool has_privkey) +{ + time_t now, notAfter, notBefore; + public_key_t *key; + + now = time(NULL); + + printf("cert: %N\n", certificate_type_names, cert->get_type(cert)); + if (cert->get_type(cert) != CERT_X509_CRL) + { + printf("subject: \"%Y\"\n", cert->get_subject(cert)); + } + printf("issuer: \"%Y\"\n", cert->get_issuer(cert)); + + cert->get_validity(cert, &now, ¬Before, ¬After); + printf("validity: not before %T, ", ¬Before, FALSE); + if (now < notBefore) + { + printf("not valid yet (valid in %V)\n", &now, ¬Before); + } + else + { + printf("ok\n"); + } + printf(" not after %T, ", ¬After, FALSE); + if (now > notAfter) + { + printf("expired (%V ago)\n", &now, ¬After); + } + else + { + printf("ok (expires in %V)\n", &now, ¬After); + } + + switch (cert->get_type(cert)) + { + case CERT_X509: + print_x509((x509_t*)cert); + break; + case CERT_X509_CRL: + print_crl((crl_t*)cert); + break; + case CERT_X509_AC: + print_ac((ac_t*)cert); + break; + default: + fprintf(stderr, "parsing certificate subtype %N not implemented\n", + certificate_type_names, cert->get_type(cert)); + break; + } + key = cert->get_public_key(cert); + if (key) + { + print_pubkey(key, has_privkey); + key->destroy(key); + } + printf("\n"); +} + +CALLBACK(list_cb, void, + format_options_t *format, char *name, vici_res_t *res) +{ + if (*format & FORMAT_RAW) + { + vici_dump(res, "list-cert event", stdout); + } + else + { + certificate_type_t type; + certificate_t *cert; + void *buf; + int len; + bool has_privkey; + + buf = vici_find(res, &len, "data"); + type = enum_from_name(certificate_type_names, + vici_find_str(res, "ANY", "type")); + has_privkey = streq(vici_find_str(res, "no", "has_privkey"), "yes"); + if (type != -1 && type != CERT_ANY && buf) + { + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, type, + BUILD_BLOB_ASN1_DER, chunk_create(buf, len), + BUILD_END); + if (cert) + { + if (*format & FORMAT_PEM) + { + print_pem(cert); + } + else + { + print_cert(cert, has_privkey); + } + cert->destroy(cert); + } + else + { + fprintf(stderr, "parsing certificate failed\n"); + } + } + else + { + fprintf(stderr, "received incomplete certificate data\n"); + } + } +} + +static int list_certs(vici_conn_t *conn) +{ + vici_req_t *req; + vici_res_t *res; + format_options_t format = 0; + char *arg, *subject = NULL, *type = NULL; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 's': + subject = arg; + continue; + case 't': + type = arg; + continue; + case 'p': + format |= FORMAT_PEM; + continue; + case 'r': + format |= FORMAT_RAW; + continue; + case EOF: + break; + default: + return command_usage("invalid --list-certs option"); + } + break; + } + if (vici_register(conn, "list-cert", list_cb, &format) != 0) + { + fprintf(stderr, "registering for certificates failed: %s\n", + strerror(errno)); + return errno; + } + req = vici_begin("list-certs"); + if (type) + { + vici_add_key_valuef(req, "type", "%s", type); + } + if (subject) + { + vici_add_key_valuef(req, "subject", "%s", subject); + } + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "list-certs request failed: %s\n", strerror(errno)); + return errno; + } + if (format & FORMAT_RAW) + { + vici_dump(res, "list-certs reply", stdout); + } + vici_free_res(res); + return 0; +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + list_certs, 'x', "list-certs", "list stored certificates", + {"[--subject ] [--type X509|X509_AC|X509_CRL] [--pem] [--raw]"}, + { + {"help", 'h', 0, "show usage information"}, + {"subject", 's', 1, "filter by certificate subject"}, + {"type", 't', 1, "filter by certificate type"}, + {"pem", 'p', 0, "print PEM encoding of certificate"}, + {"raw", 'r', 0, "dump raw response message"}, + } + }); +} diff --git a/src/swanctl/commands/list_conns.c b/src/swanctl/commands/list_conns.c new file mode 100644 index 000000000..4a7cd3583 --- /dev/null +++ b/src/swanctl/commands/list_conns.c @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * 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. + */ + +#define _GNU_SOURCE +#include +#include + +#include "command.h" + +#include + +/** + * Free hashtable with contained strings + */ +static void free_hashtable(hashtable_t *hashtable) +{ + enumerator_t *enumerator; + char *str; + + enumerator = hashtable->create_enumerator(hashtable); + while (enumerator->enumerate(enumerator, NULL, &str)) + { + free(str); + } + enumerator->destroy(enumerator); + + hashtable->destroy(hashtable); +} + +CALLBACK(values, int, + hashtable_t *sa, vici_res_t *res, char *name, void *value, int len) +{ + chunk_t chunk; + char *str; + + chunk = chunk_create(value, len); + if (chunk_printable(chunk, NULL, ' ')) + { + if (asprintf(&str, "%.*s", len, value) >= 0) + { + free(sa->put(sa, name, str)); + } + } + return 0; +} + + +CALLBACK(list, int, + hashtable_t *sa, vici_res_t *res, char *name, void *value, int len) +{ + chunk_t chunk; + char *str; + + chunk = chunk_create(value, len); + if (chunk_printable(chunk, NULL, ' ')) + { + str = sa->get(sa, name); + if (asprintf(&str, "%s%s%.*s", + str ?: "", str ? " " : "", len, value) >= 0) + { + free(sa->put(sa, name, str)); + } + } + return 0; +} + +CALLBACK(children_sn, int, + hashtable_t *ike, vici_res_t *res, char *name) +{ + hashtable_t *child; + int ret; + + child = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1); + ret = vici_parse_cb(res, NULL, values, list, child); + if (ret == 0) + { + printf(" %s: %s\n", name, child->get(child, "mode")); + printf(" local: %s\n", child->get(child, "local-ts")); + printf(" remote: %s\n", child->get(child, "remote-ts")); + } + free_hashtable(child); + return ret; +} + +CALLBACK(conn_sn, int, + hashtable_t *ike, vici_res_t *res, char *name) +{ + int ret = 0; + + if (streq(name, "children")) + { + return vici_parse_cb(res, children_sn, NULL, NULL, NULL); + } + if (streq(name, "local") || streq(name, "remote")) + { + hashtable_t *auth; + + auth = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1); + ret = vici_parse_cb(res, NULL, values, list, auth); + if (ret == 0) + { + printf(" %s %s authentication:\n", + name, auth->get(auth, "class") ?: "unspecified"); + if (auth->get(auth, "id")) + { + printf(" id: %s\n", auth->get(auth, "id")); + } + if (auth->get(auth, "groups")) + { + printf(" groups: %s\n", auth->get(auth, "groups")); + } + if (auth->get(auth, "certs")) + { + printf(" certs: %s\n", auth->get(auth, "certs")); + } + if (auth->get(auth, "cacerts")) + { + printf(" cacerts: %s\n", auth->get(auth, "cacerts")); + } + } + free_hashtable(auth); + } + return ret; +} + +CALLBACK(conn_list, int, + hashtable_t *sa, vici_res_t *res, char *name, void *value, int len) +{ + if (chunk_printable(chunk_create(value, len), NULL, ' ')) + { + if (streq(name, "local_addrs")) + { + printf(" local: %.*s\n", len, value); + } + if (streq(name, "remote_addrs")) + { + printf(" remote: %.*s\n", len, value); + } + } + return 0; +} + +CALLBACK(conns, int, + void *null, vici_res_t *res, char *name) +{ + printf("%s: %s\n", name, vici_find_str(res, "", "%s.version", name)); + + return vici_parse_cb(res, conn_sn, NULL, conn_list, NULL); +} + +CALLBACK(list_cb, void, + bool *raw, char *name, vici_res_t *res) +{ + if (*raw) + { + vici_dump(res, "list-conn event", stdout); + } + else + { + if (vici_parse_cb(res, conns, NULL, NULL, NULL) != 0) + { + fprintf(stderr, "parsing conn event failed: %s\n", strerror(errno)); + } + } +} + +static int list_conns(vici_conn_t *conn) +{ + vici_req_t *req; + vici_res_t *res; + bool raw = FALSE; + char *arg; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 'r': + raw = TRUE; + continue; + case EOF: + break; + default: + return command_usage("invalid --list-conns option"); + } + break; + } + if (vici_register(conn, "list-conn", list_cb, &raw) != 0) + { + fprintf(stderr, "registering for connections failed: %s\n", + strerror(errno)); + return errno; + } + req = vici_begin("list-conns"); + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "list-conns request failed: %s\n", strerror(errno)); + return errno; + } + if (raw) + { + vici_dump(res, "list-conns reply", stdout); + } + vici_free_res(res); + return 0; +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + list_conns, 'L', "list-conns", "list loaded configurations", + {"[--raw]"}, + { + {"help", 'h', 0, "show usage information"}, + {"raw", 'r', 0, "dump raw response message"}, + } + }); +} diff --git a/src/swanctl/commands/list_pols.c b/src/swanctl/commands/list_pols.c new file mode 100644 index 000000000..a65753836 --- /dev/null +++ b/src/swanctl/commands/list_pols.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * 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. + */ + +#define _GNU_SOURCE +#include +#include + +#include "command.h" + +#include + +/** + * Free hashtable with contained strings + */ +static void free_hashtable(hashtable_t *hashtable) +{ + enumerator_t *enumerator; + char *str; + + enumerator = hashtable->create_enumerator(hashtable); + while (enumerator->enumerate(enumerator, NULL, &str)) + { + free(str); + } + enumerator->destroy(enumerator); + + hashtable->destroy(hashtable); +} + +CALLBACK(policy_values, int, + hashtable_t *pol, vici_res_t *res, char *name, void *value, int len) +{ + chunk_t chunk; + char *str; + + chunk = chunk_create(value, len); + if (chunk_printable(chunk, NULL, ' ')) + { + if (asprintf(&str, "%.*s", len, value) >= 0) + { + free(pol->put(pol, name, str)); + } + } + return 0; +} + +CALLBACK(policy_list, int, + hashtable_t *pol, vici_res_t *res, char *name, void *value, int len) +{ + chunk_t chunk; + char *str; + + chunk = chunk_create(value, len); + if (chunk_printable(chunk, NULL, ' ')) + { + str = pol->get(pol, name); + if (asprintf(&str, "%s%s%.*s", + str ?: "", str ? " " : "", len, value) >= 0) + { + free(pol->put(pol, name, str)); + } + } + return 0; +} + +CALLBACK(policies, int, + void *null, vici_res_t *res, char *name) +{ + hashtable_t *pol; + int ret; + + pol = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1); + ret = vici_parse_cb(res, NULL, policy_values, policy_list, pol); + + printf("%s, %s\n", name, pol->get(pol, "mode")); + printf(" local: %s\n", pol->get(pol, "local-ts")); + printf(" remote: %s\n", pol->get(pol, "remote-ts")); + + free_hashtable(pol); + return ret; +} + +CALLBACK(list_cb, void, + bool *raw, char *name, vici_res_t *res) +{ + if (*raw) + { + vici_dump(res, "list-policy event", stdout); + } + else + { + if (vici_parse_cb(res, policies, NULL, NULL, NULL) != 0) + { + fprintf(stderr, "parsing policy event failed: %s\n", strerror(errno)); + } + } +} + +static int list_pols(vici_conn_t *conn) +{ + vici_req_t *req; + vici_res_t *res; + bool raw = FALSE, trap = FALSE, drop = FALSE, pass = FALSE; + char *arg, *child = NULL; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 'c': + child = arg; + continue; + case 't': + trap = TRUE; + continue; + case 'd': + drop = TRUE; + continue; + case 'p': + pass = TRUE; + continue; + case 'r': + raw = TRUE; + continue; + case EOF: + break; + default: + return command_usage("invalid --list-pols option"); + } + break; + } + if (!trap && !drop && !pass) + { + trap = drop = pass = TRUE; + } + if (vici_register(conn, "list-policy", list_cb, &raw) != 0) + { + fprintf(stderr, "registering for policies failed: %s\n", + strerror(errno)); + return errno; + } + req = vici_begin("list-policies"); + if (child) + { + vici_add_key_valuef(req, "child", "%s", child); + } + if (trap) + { + vici_add_key_valuef(req, "trap", "yes"); + } + if (drop) + { + vici_add_key_valuef(req, "drop", "yes"); + } + if (pass) + { + vici_add_key_valuef(req, "pass", "yes"); + } + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "list-policies request failed: %s\n", strerror(errno)); + return errno; + } + if (raw) + { + vici_dump(res, "list-policies reply", stdout); + } + vici_free_res(res); + return 0; +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + list_pols, 'P', "list-pols", "list currently installed policies", + {"[--child ] [--trap] [--drop] [--pass] [--raw]"}, + { + {"help", 'h', 0, "show usage information"}, + {"child", 'c', 1, "filter policies by CHILD_SA config name"}, + {"trap", 't', 0, "list trap policies"}, + {"drop", 'd', 0, "list drop policies"}, + {"pass", 'p', 0, "list bypass policies"}, + {"raw", 'r', 0, "dump raw response message"}, + } + }); +} diff --git a/src/swanctl/commands/list_pools.c b/src/swanctl/commands/list_pools.c new file mode 100644 index 000000000..beff65b96 --- /dev/null +++ b/src/swanctl/commands/list_pools.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * 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. + */ + +#define _GNU_SOURCE +#include +#include + +#include "command.h" + +CALLBACK(list_pool, int, + linked_list_t *list, vici_res_t *res, char *name) +{ + char pool[64], leases[32]; + + snprintf(pool, sizeof(pool), "%s:", name); + snprintf(leases, sizeof(leases), "%s / %s / %s", + vici_find_str(res, "", "%s.online", name), + vici_find_str(res, "", "%s.offline", name), + vici_find_str(res, "", "%s.size", name)); + + printf("%-20s %-30s %16s\n", + name, vici_find_str(res, "", "%s.base", name), leases); + + return 0; +} + +static int list_pools(vici_conn_t *conn) +{ + vici_req_t *req; + vici_res_t *res; + bool raw = FALSE; + char *arg; + int ret = 0; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 'r': + raw = TRUE; + continue; + case EOF: + break; + default: + return command_usage("invalid --list-pools option"); + } + break; + } + + req = vici_begin("get-pools"); + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "get-pools request failed: %s\n", strerror(errno)); + return errno; + } + if (raw) + { + vici_dump(res, "get-pools reply", stdout); + } + else + { + ret = vici_parse_cb(res, list_pool, NULL, NULL, NULL); + } + vici_free_res(res); + return ret; +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + list_pools, 'A', "list-pools", "list loaded pool configurations", + {"[--raw]"}, + { + {"help", 'h', 0, "show usage information"}, + {"raw", 'r', 0, "dump raw response message"}, + } + }); +} diff --git a/src/swanctl/commands/list_sas.c b/src/swanctl/commands/list_sas.c new file mode 100644 index 000000000..ae8c7cb22 --- /dev/null +++ b/src/swanctl/commands/list_sas.c @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * 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. + */ + +#define _GNU_SOURCE +#include +#include + +#include "command.h" + +#include + +/** + * Free hashtable with contained strings + */ +static void free_hashtable(hashtable_t *hashtable) +{ + enumerator_t *enumerator; + char *str; + + enumerator = hashtable->create_enumerator(hashtable); + while (enumerator->enumerate(enumerator, NULL, &str)) + { + free(str); + } + enumerator->destroy(enumerator); + + hashtable->destroy(hashtable); +} + +CALLBACK(sa_values, int, + hashtable_t *sa, vici_res_t *res, char *name, void *value, int len) +{ + chunk_t chunk; + char *str; + + chunk = chunk_create(value, len); + if (chunk_printable(chunk, NULL, ' ')) + { + if (asprintf(&str, "%.*s", len, value) >= 0) + { + free(sa->put(sa, name, str)); + } + } + return 0; +} + + +CALLBACK(sa_list, int, + hashtable_t *sa, vici_res_t *res, char *name, void *value, int len) +{ + chunk_t chunk; + char *str; + + chunk = chunk_create(value, len); + if (chunk_printable(chunk, NULL, ' ')) + { + str = sa->get(sa, name); + if (asprintf(&str, "%s%s%.*s", + str ?: "", str ? " " : "", len, value) >= 0) + { + free(sa->put(sa, name, str)); + } + } + return 0; +} + +CALLBACK(child_sas, int, + hashtable_t *ike, vici_res_t *res, char *name) +{ + hashtable_t *child; + int ret; + + child = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1); + ret = vici_parse_cb(res, NULL, sa_values, sa_list, child); + if (ret == 0) + { + printf(" %s: #%s, %s, %s%s, %s:", + name, child->get(child, "reqid"), + child->get(child, "state"), child->get(child, "mode"), + child->get(child, "encap") ? "-in-UDP" : "", + child->get(child, "protocol")); + + if (child->get(child, "encr-alg")) + { + printf("%s", child->get(child, "encr-alg")); + if (child->get(child, "encr-keysize")) + { + printf("-%s", child->get(child, "encr-keysize")); + } + } + if (child->get(child, "integ-alg")) + { + if (child->get(child, "encr-alg")) + { + printf("/"); + } + printf("%s", child->get(child, "integ-alg")); + if (child->get(child, "integ-keysize")) + { + printf("-%s", child->get(child, "integ-keysize")); + } + } + if (child->get(child, "prf-alg")) + { + printf("/%s", child->get(child, "prf-alg")); + } + if (child->get(child, "dh-group")) + { + printf("/%s", child->get(child, "dh-group")); + } + if (child->get(child, "esn")) + { + printf("/%s", child->get(child, "esn")); + } + printf("\n"); + + printf(" installed %s ago", child->get(child, "install-time")); + if (child->get(child, "rekey-time")) + { + printf(", rekeying in %ss", child->get(child, "rekey-time")); + } + if (child->get(child, "life-time")) + { + printf(", expires in %ss", child->get(child, "life-time")); + } + printf("\n"); + + printf(" in %s%s%s", child->get(child, "spi-in"), + child->get(child, "cpi-in") ? "/" : "", + child->get(child, "cpi-in") ?: ""); + printf(", %6s bytes, %5s packets", + child->get(child, "bytes-in"), child->get(child, "packets-in")); + if (child->get(child, "use-in")) + { + printf(", %5ss ago", child->get(child, "use-in")); + } + printf("\n"); + + printf(" out %s%s%s", child->get(child, "spi-out"), + child->get(child, "cpi-out") ? "/" : "", + child->get(child, "cpi-out") ?: ""); + printf(", %6s bytes, %5s packets", + child->get(child, "bytes-out"), child->get(child, "packets-out")); + if (child->get(child, "use-out")) + { + printf(", %5ss ago", child->get(child, "use-out")); + } + printf("\n"); + + printf(" local %s\n", child->get(child, "local-ts")); + printf(" remote %s\n", child->get(child, "remote-ts")); + } + free_hashtable(child); + return ret; +} + +CALLBACK(ike_sa, int, + hashtable_t *ike, vici_res_t *res, char *name) +{ + if (streq(name, "child-sas")) + { + printf("%s: #%s, %s, IKEv%s, %s:%s\n", + ike->get(ike, "name"), ike->get(ike, "uniqueid"), + ike->get(ike, "state"), ike->get(ike, "version"), + ike->get(ike, "initiator-spi"), ike->get(ike, "responder-spi")); + + printf(" local '%s' @ %s\n", + ike->get(ike, "local-id"), ike->get(ike, "local-host")); + printf(" remote '%s' @ %s", + ike->get(ike, "remote-id"), ike->get(ike, "remote-host")); + if (ike->get(ike, "remote-eap-id")) + { + printf(" EAP: '%s'", ike->get(ike, "remote-eap-id")); + } + if (ike->get(ike, "remote-xauth-id")) + { + printf(" XAuth: '%s'", ike->get(ike, "remote-xauth-id")); + } + printf("\n"); + + if (ike->get(ike, "encr-alg")) + { + printf(" %s", ike->get(ike, "encr-alg")); + if (ike->get(ike, "encr-keysize")) + { + printf("-%s", ike->get(ike, "encr-keysize")); + } + if (ike->get(ike, "integ-alg")) + { + printf("/%s", ike->get(ike, "integ-alg")); + } + if (ike->get(ike, "integ-keysize")) + { + printf("-%s", ike->get(ike, "integ-keysize")); + } + printf("/%s", ike->get(ike, "prf-alg")); + printf("/%s", ike->get(ike, "dh-group")); + printf("\n"); + } + + if (ike->get(ike, "established")) + { + printf(" established %s ago", ike->get(ike, "established")); + if (ike->get(ike, "rekey-time")) + { + printf(", rekeying in %ss", ike->get(ike, "rekey-time")); + } + if (ike->get(ike, "reauth-time")) + { + printf(", reauth in %ss", ike->get(ike, "reauth-time")); + } + if (ike->get(ike, "life-time")) + { + printf(", expires in %ss", ike->get(ike, "life-time")); + } + printf("\n"); + } + + if (ike->get(ike, "tasks-queued")) + { + printf(" queued: %s\n", ike->get(ike, "tasks-queued")); + } + if (ike->get(ike, "tasks-active")) + { + printf(" active: %s\n", ike->get(ike, "tasks-active")); + } + if (ike->get(ike, "tasks-passive")) + { + printf(" passive: %s\n", ike->get(ike, "tasks-passive")); + } + + return vici_parse_cb(res, child_sas, NULL, NULL, ike); + } + return 0; +} + +CALLBACK(ike_sas, int, + void *null, vici_res_t *res, char *name) +{ + hashtable_t *ike; + int ret; + + ike = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1); + ike->put(ike, "name", strdup(name)); + ret = vici_parse_cb(res, ike_sa, sa_values, sa_list, ike); + free_hashtable(ike); + return ret; +} + +CALLBACK(list_cb, void, + bool *raw, char *name, vici_res_t *res) +{ + if (*raw) + { + vici_dump(res, "list-sa event", stdout); + } + else + { + if (vici_parse_cb(res, ike_sas, NULL, NULL, NULL) != 0) + { + fprintf(stderr, "parsing SA event failed: %s\n", strerror(errno)); + } + } +} + +static int list_sas(vici_conn_t *conn) +{ + vici_req_t *req; + vici_res_t *res; + bool raw = FALSE, noblock = FALSE; + char *arg, *ike = NULL; + int ike_id = 0; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 'i': + ike = arg; + continue; + case 'I': + ike_id = atoi(arg); + continue; + case 'n': + noblock = TRUE; + continue; + case 'r': + raw = TRUE; + continue; + case EOF: + break; + default: + return command_usage("invalid --list-sas option"); + } + break; + } + if (vici_register(conn, "list-sa", list_cb, &raw) != 0) + { + fprintf(stderr, "registering for SAs failed: %s\n", strerror(errno)); + return errno; + } + req = vici_begin("list-sas"); + if (ike) + { + vici_add_key_valuef(req, "ike", "%s", ike); + } + if (ike_id) + { + vici_add_key_valuef(req, "ike-id", "%d", ike_id); + } + if (noblock) + { + vici_add_key_valuef(req, "noblock", "yes"); + } + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "list-sas request failed: %s\n", strerror(errno)); + return errno; + } + if (raw) + { + vici_dump(res, "list-sas reply", stdout); + } + vici_free_res(res); + return 0; +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + list_sas, 'l', "list-sas", "list currently active IKE_SAs", + {"[--raw]"}, + { + {"help", 'h', 0, "show usage information"}, + {"ike", 'i', 1, "filter IKE_SAs by name"}, + {"ike-id", 'I', 1, "filter IKE_SAs by unique identifier"}, + {"noblock", 'n', 0, "don't wait for IKE_SAs in use"}, + {"raw", 'r', 0, "dump raw response message"}, + } + }); +} diff --git a/src/swanctl/commands/load_conns.c b/src/swanctl/commands/load_conns.c new file mode 100644 index 000000000..d418cd3a4 --- /dev/null +++ b/src/swanctl/commands/load_conns.c @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * 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. + */ + +#define _GNU_SOURCE +#include +#include +#include + +#include "command.h" +#include "swanctl.h" + +/** + * Check if we should handle a key as a list of comma separated values + */ +static bool is_list_key(char *key) +{ + char *keys[] = { + "local_addrs", + "remote_addrs", + "proposals", + "esp_proposals", + "ah_proposals", + "local_ts", + "remote_ts", + "vips", + "pools", + "groups", + }; + int i; + + for (i = 0; i < countof(keys); i++) + { + if (strcaseeq(keys[i], key)) + { + return TRUE; + } + } + return FALSE; +} + +/** + * Check if we should handle a key as a list of comma separated files + */ +static bool is_file_list_key(char *key) +{ + char *keys[] = { + "certs", + "cacerts", + }; + int i; + + for (i = 0; i < countof(keys); i++) + { + if (strcaseeq(keys[i], key)) + { + return TRUE; + } + } + return FALSE; +} + +/** + * Add a vici list from a comma separated string value + */ +static void add_list_key(vici_req_t *req, char *key, char *value) +{ + enumerator_t *enumerator; + char *token; + + vici_begin_list(req, key); + enumerator = enumerator_create_token(value, ",", " "); + while (enumerator->enumerate(enumerator, &token)) + { + vici_add_list_itemf(req, "%s", token); + } + enumerator->destroy(enumerator); + vici_end_list(req); +} + +/** + * Add a vici list of blobs from a comma separated file list + */ +static void add_file_list_key(vici_req_t *req, char *key, char *value) +{ + enumerator_t *enumerator; + chunk_t *map; + char *token, buf[PATH_MAX]; + + vici_begin_list(req, key); + enumerator = enumerator_create_token(value, ",", " "); + while (enumerator->enumerate(enumerator, &token)) + { + if (*token != '/') + { + if (streq(key, "certs")) + { + snprintf(buf, sizeof(buf), "%s/%s", SWANCTL_X509DIR, token); + token = buf; + } + if (streq(key, "cacerts")) + { + snprintf(buf, sizeof(buf), "%s/%s", SWANCTL_X509CADIR, token); + token = buf; + } + } + + map = chunk_map(token, FALSE); + if (map) + { + vici_add_list_item(req, map->ptr, map->len); + chunk_unmap(map); + } + else + { + fprintf(stderr, "loading certificate '%s' failed: %s\n", + token, strerror(errno)); + } + } + enumerator->destroy(enumerator); + vici_end_list(req); +} + +/** + * Translate setting key/values from a section into vici key-values/lists + */ +static void add_key_values(vici_req_t *req, settings_t *cfg, char *section) +{ + enumerator_t *enumerator; + char *key, *value; + + enumerator = cfg->create_key_value_enumerator(cfg, section); + while (enumerator->enumerate(enumerator, &key, &value)) + { + if (is_list_key(key)) + { + add_list_key(req, key, value); + } + else if (is_file_list_key(key)) + { + add_file_list_key(req, key, value); + } + else + { + vici_add_key_valuef(req, key, "%s", value); + } + } + enumerator->destroy(enumerator); +} + +/** + * Translate a settings section to a vici section + */ +static void add_sections(vici_req_t *req, settings_t *cfg, char *section) +{ + enumerator_t *enumerator; + char *name, buf[256]; + + enumerator = cfg->create_section_enumerator(cfg, section); + while (enumerator->enumerate(enumerator, &name)) + { + vici_begin_section(req, name); + snprintf(buf, sizeof(buf), "%s.%s", section, name); + add_key_values(req, cfg, buf); + add_sections(req, cfg, buf); + vici_end_section(req); + } + enumerator->destroy(enumerator); +} + +/** + * Load an IKE_SA config with CHILD_SA configs from a section + */ +static bool load_conn(vici_conn_t *conn, settings_t *cfg, + char *section, bool raw) +{ + vici_req_t *req; + vici_res_t *res; + bool ret = TRUE; + char buf[128]; + + snprintf(buf, sizeof(buf), "%s.%s", "connections", section); + + req = vici_begin("load-conn"); + + vici_begin_section(req, section); + add_key_values(req, cfg, buf); + add_sections(req, cfg, buf); + vici_end_section(req); + + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "load-conn request failed: %s\n", strerror(errno)); + return FALSE; + } + if (raw) + { + vici_dump(res, "load-conn reply", stdout); + } + else if (!streq(vici_find_str(res, "no", "success"), "yes")) + { + fprintf(stderr, "loading connection '%s' failed: %s\n", + section, vici_find_str(res, "", "errmsg")); + ret = FALSE; + } + else + { + printf("loaded connection '%s'\n", section); + } + vici_free_res(res); + return ret; +} + +CALLBACK(list_conn, int, + linked_list_t *list, vici_res_t *res, char *name, void *value, int len) +{ + if (streq(name, "conns")) + { + char *str; + + if (asprintf(&str, "%.*s", len, value) != -1) + { + list->insert_last(list, str); + } + } + return 0; +} + +/** + * Create a list of currently loaded connections + */ +static linked_list_t* list_conns(vici_conn_t *conn, bool raw) +{ + linked_list_t *list; + vici_res_t *res; + + list = linked_list_create(); + + res = vici_submit(vici_begin("get-conns"), conn); + if (res) + { + if (raw) + { + vici_dump(res, "get-conns reply", stdout); + } + vici_parse_cb(res, NULL, NULL, list_conn, list); + vici_free_res(res); + } + return list; +} + +/** + * Remove and free a string from a list + */ +static void remove_from_list(linked_list_t *list, char *str) +{ + enumerator_t *enumerator; + char *current; + + enumerator = list->create_enumerator(list); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (streq(current, str)) + { + list->remove_at(list, enumerator); + free(current); + } + } + enumerator->destroy(enumerator); +} + +/** + * Unload a connection by name + */ +static bool unload_conn(vici_conn_t *conn, char *name, bool raw) +{ + vici_req_t *req; + vici_res_t *res; + bool ret = TRUE; + + req = vici_begin("unload-conn"); + vici_add_key_valuef(req, "name", "%s", name); + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "unload-conn request failed: %s\n", strerror(errno)); + return FALSE; + } + if (raw) + { + vici_dump(res, "unload-conn reply", stdout); + } + else if (!streq(vici_find_str(res, "no", "success"), "yes")) + { + fprintf(stderr, "unloading connection '%s' failed: %s\n", + name, vici_find_str(res, "", "errmsg")); + ret = FALSE; + } + vici_free_res(res); + return ret; +} + +static int load_conns(vici_conn_t *conn) +{ + bool raw = FALSE; + u_int found = 0, loaded = 0, unloaded = 0; + char *arg, *section; + enumerator_t *enumerator; + linked_list_t *conns; + settings_t *cfg; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 'r': + raw = TRUE; + continue; + case EOF: + break; + default: + return command_usage("invalid --load-conns option"); + } + break; + } + + cfg = settings_create(SWANCTL_CONF); + if (!cfg) + { + fprintf(stderr, "parsing '%s' failed\n", SWANCTL_CONF); + return EINVAL; + } + + conns = list_conns(conn, raw); + + enumerator = cfg->create_section_enumerator(cfg, "connections"); + while (enumerator->enumerate(enumerator, §ion)) + { + remove_from_list(conns, section); + found++; + if (load_conn(conn, cfg, section, raw)) + { + loaded++; + } + } + enumerator->destroy(enumerator); + + cfg->destroy(cfg); + + /* unload all connection in daemon, but not in file */ + while (conns->remove_first(conns, (void**)§ion) == SUCCESS) + { + if (unload_conn(conn, section, raw)) + { + unloaded++; + } + free(section); + } + conns->destroy(conns); + + if (raw) + { + return 0; + } + if (found == 0) + { + printf("no connections found, %u unloaded\n", unloaded); + return 0; + } + if (loaded == found) + { + printf("successfully loaded %u connections, %u unloaded\n", + loaded, unloaded); + return 0; + } + fprintf(stderr, "loaded %u of %u connections, %u failed to load, " + "%u unloaded\n", loaded, found, found - loaded, unloaded); + return EINVAL; +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + load_conns, 'c', "load-conns", "(re-)load connection configuration", + {"[--raw]"}, + { + {"help", 'h', 0, "show usage information"}, + {"raw", 'r', 0, "dump raw response message"}, + } + }); +} diff --git a/src/swanctl/commands/load_creds.c b/src/swanctl/commands/load_creds.c new file mode 100644 index 000000000..94d31f49f --- /dev/null +++ b/src/swanctl/commands/load_creds.c @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * 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. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "command.h" +#include "swanctl.h" + +#include + +/** + * Load a single certificate over vici + */ +static bool load_cert(vici_conn_t *conn, bool raw, char *dir, + char *type, chunk_t data) +{ + vici_req_t *req; + vici_res_t *res; + bool ret = TRUE; + + req = vici_begin("load-cert"); + + vici_add_key_valuef(req, "type", "%s", type); + vici_add_key_value(req, "data", data.ptr, data.len); + + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "load-cert request failed: %s\n", strerror(errno)); + return FALSE; + } + if (raw) + { + vici_dump(res, "load-cert reply", stdout); + } + else if (!streq(vici_find_str(res, "no", "success"), "yes")) + { + fprintf(stderr, "loading '%s' failed: %s\n", + dir, vici_find_str(res, "", "errmsg")); + ret = FALSE; + } + else + { + printf("loaded %s certificate '%s'\n", type, dir); + } + vici_free_res(res); + return ret; +} + +/** + * Load certficiates from a directory + */ +static void load_certs(vici_conn_t *conn, bool raw, char *type, char *dir) +{ + enumerator_t *enumerator; + struct stat st; + chunk_t *map; + char *path; + + enumerator = enumerator_create_directory(dir); + if (enumerator) + { + while (enumerator->enumerate(enumerator, NULL, &path, &st)) + { + if (S_ISREG(st.st_mode)) + { + map = chunk_map(path, FALSE); + if (map) + { + load_cert(conn, raw, path, type, *map); + chunk_unmap(map); + } + else + { + fprintf(stderr, "mapping '%s' failed: %s, skipped\n", + path, strerror(errno)); + } + } + } + enumerator->destroy(enumerator); + } +} + +/** + * Load a single private key over vici + */ +static bool load_key(vici_conn_t *conn, bool raw, char *dir, + char *type, chunk_t data) +{ + vici_req_t *req; + vici_res_t *res; + bool ret = TRUE; + + req = vici_begin("load-key"); + + vici_add_key_valuef(req, "type", "%s", type); + vici_add_key_value(req, "data", data.ptr, data.len); + + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "load-key request failed: %s\n", strerror(errno)); + return FALSE; + } + if (raw) + { + vici_dump(res, "load-key reply", stdout); + } + else if (!streq(vici_find_str(res, "no", "success"), "yes")) + { + fprintf(stderr, "loading '%s' failed: %s\n", + dir, vici_find_str(res, "", "errmsg")); + ret = FALSE; + } + else + { + printf("loaded %s key '%s'\n", type, dir); + } + vici_free_res(res); + return ret; +} + +/** + * Callback function to prompt for private key passwords + */ +CALLBACK(password_cb, shared_key_t*, + char *prompt, shared_key_type_t type, + identification_t *me, identification_t *other, + id_match_t *match_me, id_match_t *match_other) +{ + char *pwd; + + if (type != SHARED_PRIVATE_KEY_PASS) + { + return NULL; + } + pwd = getpass(prompt); + if (!pwd || strlen(pwd) == 0) + { + return NULL; + } + if (match_me) + { + *match_me = ID_MATCH_PERFECT; + } + if (match_other) + { + *match_other = ID_MATCH_PERFECT; + } + return shared_key_create(type, chunk_clone(chunk_from_str(pwd))); +} + +/** + * Try to parse a potentially encrypted private key + */ +static private_key_t* decrypt_key(char *name, char *type, chunk_t encoding) +{ + key_type_t kt = KEY_ANY; + private_key_t *private; + callback_cred_t *cb; + char buf[128]; + + if (streq(type, "rsa")) + { + kt = KEY_RSA; + } + else if (streq(type, "ecdsa")) + { + kt = KEY_ECDSA; + } + + snprintf(buf, sizeof(buf), "Password for '%s': ", name); + + cb = callback_cred_create_shared(password_cb, buf); + lib->credmgr->add_set(lib->credmgr, &cb->set); + + private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, kt, + BUILD_BLOB_PEM, encoding, BUILD_END); + + lib->credmgr->remove_set(lib->credmgr, &cb->set); + cb->destroy(cb); + + return private; +} + +/** + * Try to decrypt and load a private key + */ +static bool load_encrypted_key(vici_conn_t *conn, bool raw, + char *rel, char *path, char *type, chunk_t data) +{ + private_key_t *private; + bool loaded = FALSE; + chunk_t encoding; + + private = decrypt_key(rel, type, data); + if (private) + { + if (private->get_encoding(private, PRIVKEY_ASN1_DER, + &encoding)) + { + switch (private->get_type(private)) + { + case KEY_RSA: + loaded = load_key(conn, raw, path, "rsa", encoding); + break; + case KEY_ECDSA: + loaded = load_key(conn, raw, path, "ecdsa", encoding); + break; + default: + break; + } + chunk_clear(&encoding); + } + private->destroy(private); + } + return loaded; +} + +/** + * Load private keys from a directory + */ +static void load_keys(vici_conn_t *conn, bool raw, bool noprompt, + char *type, char *dir) +{ + enumerator_t *enumerator; + struct stat st; + chunk_t *map; + char *path, *rel; + + enumerator = enumerator_create_directory(dir); + if (enumerator) + { + while (enumerator->enumerate(enumerator, &rel, &path, &st)) + { + if (S_ISREG(st.st_mode)) + { + map = chunk_map(path, FALSE); + if (map) + { + if (noprompt || + !load_encrypted_key(conn, raw, rel, path, type, *map)) + { + load_key(conn, raw, path, type, *map); + } + chunk_unmap(map); + } + else + { + fprintf(stderr, "mapping '%s' failed: %s, skipped\n", + path, strerror(errno)); + } + } + } + enumerator->destroy(enumerator); + } +} + +/** + * Load a single secret over VICI + */ +static bool load_secret(vici_conn_t *conn, settings_t *cfg, + char *section, bool raw) +{ + enumerator_t *enumerator; + vici_req_t *req; + vici_res_t *res; + chunk_t data; + char *key, *value, buf[128], *type = NULL; + bool ret = TRUE; + int i; + char *types[] = { + "eap", + "xauth", + "ike", + }; + + for (i = 0; i < countof(types); i++) + { + if (strpfx(section, types[i])) + { + type = types[i]; + break; + } + } + if (!type) + { + fprintf(stderr, "ignoring unsupported secret '%s'\n", section); + return FALSE; + } + + value = cfg->get_str(cfg, "secrets.%s.secret", NULL, section); + if (!value) + { + fprintf(stderr, "missing secret in '%s', ignored\n", section); + return FALSE; + } + if (strcasepfx(value, "0x")) + { + data = chunk_from_hex(chunk_from_str(value + 2), NULL); + } + else if (strcasepfx(value, "0s")) + { + data = chunk_from_base64(chunk_from_str(value + 2), NULL); + } + else + { + data = chunk_clone(chunk_from_str(value)); + } + + req = vici_begin("load-shared"); + + vici_add_key_valuef(req, "type", "%s", type); + vici_add_key_value(req, "data", data.ptr, data.len); + chunk_clear(&data); + + vici_begin_list(req, "owners"); + snprintf(buf, sizeof(buf), "secrets.%s", section); + enumerator = cfg->create_key_value_enumerator(cfg, buf); + while (enumerator->enumerate(enumerator, &key, &value)) + { + if (strpfx(key, "id")) + { + vici_add_list_itemf(req, "%s", value); + } + } + enumerator->destroy(enumerator); + vici_end_list(req); + + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "load-shared request failed: %s\n", strerror(errno)); + return FALSE; + } + if (raw) + { + vici_dump(res, "load-shared reply", stdout); + } + else if (!streq(vici_find_str(res, "no", "success"), "yes")) + { + fprintf(stderr, "loading shared secret failed: %s\n", + vici_find_str(res, "", "errmsg")); + ret = FALSE; + } + else + { + printf("loaded %s secret '%s'\n", type, section); + } + vici_free_res(res); + return ret; +} + +/** + * Clear all currently loaded credentials + */ +static bool clear_creds(vici_conn_t *conn, bool raw) +{ + vici_res_t *res; + + res = vici_submit(vici_begin("clear-creds"), conn); + if (!res) + { + fprintf(stderr, "clear-creds request failed: %s\n", strerror(errno)); + return FALSE; + } + if (raw) + { + vici_dump(res, "clear-creds reply", stdout); + } + vici_free_res(res); + return TRUE; +} + +static int load_creds(vici_conn_t *conn) +{ + bool raw = FALSE, clear = FALSE, noprompt = FALSE; + enumerator_t *enumerator; + settings_t *cfg; + char *arg, *section; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 'c': + clear = TRUE; + continue; + case 'n': + noprompt = TRUE; + continue; + case 'r': + raw = TRUE; + continue; + case EOF: + break; + default: + return command_usage("invalid --load-creds option"); + } + break; + } + + if (clear) + { + if (!clear_creds(conn, raw)) + { + return ECONNREFUSED; + } + } + + load_certs(conn, raw, "x509", SWANCTL_X509DIR); + load_certs(conn, raw, "x509ca", SWANCTL_X509CADIR); + load_certs(conn, raw, "x509aa", SWANCTL_X509AADIR); + load_certs(conn, raw, "x509crl", SWANCTL_X509CRLDIR); + load_certs(conn, raw, "x509ac", SWANCTL_X509ACDIR); + + load_keys(conn, raw, noprompt, "rsa", SWANCTL_RSADIR); + load_keys(conn, raw, noprompt, "ecdsa", SWANCTL_ECDSADIR); + load_keys(conn, raw, noprompt, "any", SWANCTL_PKCS8DIR); + + cfg = settings_create(SWANCTL_CONF); + if (!cfg) + { + fprintf(stderr, "parsing '%s' failed\n", SWANCTL_CONF); + return EINVAL; + } + + enumerator = cfg->create_section_enumerator(cfg, "secrets"); + while (enumerator->enumerate(enumerator, §ion)) + { + load_secret(conn, cfg, section, raw); + } + enumerator->destroy(enumerator); + + cfg->destroy(cfg); + + return 0; +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + load_creds, 's', "load-creds", "(re-)load credentials", + {"[--raw]"}, + { + {"help", 'h', 0, "show usage information"}, + {"clear", 'c', 0, "clear previously loaded credentials"}, + {"noprompt", 'n', 0, "do not prompt for passwords"}, + {"raw", 'r', 0, "dump raw response message"}, + } + }); +} diff --git a/src/swanctl/commands/load_pools.c b/src/swanctl/commands/load_pools.c new file mode 100644 index 000000000..1224021a7 --- /dev/null +++ b/src/swanctl/commands/load_pools.c @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * 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. + */ + +#define _GNU_SOURCE +#include +#include +#include + +#include "command.h" +#include "swanctl.h" + +/** + * Add a vici list from a comma separated string value + */ +static void add_list_key(vici_req_t *req, char *key, char *value) +{ + enumerator_t *enumerator; + char *token; + + vici_begin_list(req, key); + enumerator = enumerator_create_token(value, ",", " "); + while (enumerator->enumerate(enumerator, &token)) + { + vici_add_list_itemf(req, "%s", token); + } + enumerator->destroy(enumerator); + vici_end_list(req); +} + +/** + * Translate setting key/values from a section into vici key-values/lists + */ +static void add_key_values(vici_req_t *req, settings_t *cfg, char *section) +{ + enumerator_t *enumerator; + char *key, *value; + + enumerator = cfg->create_key_value_enumerator(cfg, section); + while (enumerator->enumerate(enumerator, &key, &value)) + { + /* pool subnet is encoded as key/value, all other attributes as list */ + if (streq(key, "addrs")) + { + vici_add_key_valuef(req, key, "%s", value); + } + else + { + add_list_key(req, key, value); + } + } + enumerator->destroy(enumerator); +} + +/** + * Load a pool configuration + */ +static bool load_pool(vici_conn_t *conn, settings_t *cfg, + char *section, bool raw) +{ + vici_req_t *req; + vici_res_t *res; + bool ret = TRUE; + char buf[128]; + + snprintf(buf, sizeof(buf), "%s.%s", "pools", section); + + req = vici_begin("load-pool"); + + vici_begin_section(req, section); + add_key_values(req, cfg, buf); + vici_end_section(req); + + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "load-pool request failed: %s\n", strerror(errno)); + return FALSE; + } + if (raw) + { + vici_dump(res, "load-pool reply", stdout); + } + else if (!streq(vici_find_str(res, "no", "success"), "yes")) + { + fprintf(stderr, "loading pool '%s' failed: %s\n", + section, vici_find_str(res, "", "errmsg")); + ret = FALSE; + } + else + { + printf("loaded pool '%s'\n", section); + } + vici_free_res(res); + return ret; +} + +CALLBACK(list_pool, int, + linked_list_t *list, vici_res_t *res, char *name) +{ + list->insert_last(list, strdup(name)); + return 0; +} + +/** + * Create a list of currently loaded pools + */ +static linked_list_t* list_pools(vici_conn_t *conn, bool raw) +{ + linked_list_t *list; + vici_res_t *res; + + list = linked_list_create(); + + res = vici_submit(vici_begin("get-pools"), conn); + if (res) + { + if (raw) + { + vici_dump(res, "get-pools reply", stdout); + } + vici_parse_cb(res, list_pool, NULL, NULL, list); + vici_free_res(res); + } + return list; +} + +/** + * Remove and free a string from a list + */ +static void remove_from_list(linked_list_t *list, char *str) +{ + enumerator_t *enumerator; + char *current; + + enumerator = list->create_enumerator(list); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (streq(current, str)) + { + list->remove_at(list, enumerator); + free(current); + } + } + enumerator->destroy(enumerator); +} + +/** + * Unload a pool by name + */ +static bool unload_pool(vici_conn_t *conn, char *name, bool raw) +{ + vici_req_t *req; + vici_res_t *res; + bool ret = TRUE; + + req = vici_begin("unload-pool"); + vici_add_key_valuef(req, "name", "%s", name); + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "unload-pool request failed: %s\n", strerror(errno)); + return FALSE; + } + if (raw) + { + vici_dump(res, "unload-pool reply", stdout); + } + else if (!streq(vici_find_str(res, "no", "success"), "yes")) + { + fprintf(stderr, "unloading pool '%s' failed: %s\n", + name, vici_find_str(res, "", "errmsg")); + ret = FALSE; + } + vici_free_res(res); + return ret; +} + +static int load_pools(vici_conn_t *conn) +{ + bool raw = FALSE; + u_int found = 0, loaded = 0, unloaded = 0; + char *arg, *section; + enumerator_t *enumerator; + linked_list_t *pools; + settings_t *cfg; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 'r': + raw = TRUE; + continue; + case EOF: + break; + default: + return command_usage("invalid --load-pools option"); + } + break; + } + + cfg = settings_create(SWANCTL_CONF); + if (!cfg) + { + fprintf(stderr, "parsing '%s' failed\n", SWANCTL_CONF); + return EINVAL; + } + + pools = list_pools(conn, raw); + + enumerator = cfg->create_section_enumerator(cfg, "pools"); + while (enumerator->enumerate(enumerator, §ion)) + { + remove_from_list(pools, section); + found++; + if (load_pool(conn, cfg, section, raw)) + { + loaded++; + } + } + enumerator->destroy(enumerator); + + cfg->destroy(cfg); + + /* unload all pools in daemon, but not in file */ + while (pools->remove_first(pools, (void**)§ion) == SUCCESS) + { + if (unload_pool(conn, section, raw)) + { + unloaded++; + } + free(section); + } + pools->destroy(pools); + + if (raw) + { + return 0; + } + if (found == 0) + { + printf("no pools found, %u unloaded\n", unloaded); + return 0; + } + if (loaded == found) + { + printf("successfully loaded %u pools, %u unloaded\n", + loaded, unloaded); + return 0; + } + fprintf(stderr, "loaded %u of %u pools, %u failed to load, " + "%u unloaded\n", loaded, found, found - loaded, unloaded); + return EINVAL; +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + load_pools, 'a', "load-pools", "(re-)load pool configuration", + {"[--raw]"}, + { + {"help", 'h', 0, "show usage information"}, + {"raw", 'r', 0, "dump raw response message"}, + } + }); +} diff --git a/src/swanctl/commands/log.c b/src/swanctl/commands/log.c new file mode 100644 index 000000000..4810025d4 --- /dev/null +++ b/src/swanctl/commands/log.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "command.h" + +#include +#include + +CALLBACK(log_cb, void, + bool *raw, char *name, vici_res_t *msg) +{ + if (*raw) + { + vici_dump(msg, "log", stdout); + } + else + { + char *current, *next; + + current = vici_find_str(msg, NULL, "msg"); + while (current) + { + next = strchr(current, '\n'); + printf("%.2d[%s] ", vici_find_int(msg, 0, "thread"), + vici_find_str(msg, " ", "group")); + if (next == NULL) + { + printf("%s\n", current); + break; + } + printf("%.*s\n", (int)(next - current), current); + current = next + 1; + } + } +} + +static int logcmd(vici_conn_t *conn) +{ + bool raw = FALSE; + char *arg; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 'r': + raw = TRUE; + continue; + case EOF: + break; + default: + return command_usage("invalid --log option"); + } + break; + } + + if (vici_register(conn, "log", log_cb, &raw) != 0) + { + fprintf(stderr, "registering for log failed: %s\n", strerror(errno)); + return errno; + } + while (TRUE) + { + sleep(1); + } + return 0; +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + logcmd, 'T', "log", "trace logging output", + {"[--raw]"}, + { + {"help", 'h', 0, "show usage information"}, + {"raw", 'r', 0, "dump raw response message"}, + } + }); +} diff --git a/src/swanctl/commands/terminate.c b/src/swanctl/commands/terminate.c new file mode 100644 index 000000000..b0e2c6671 --- /dev/null +++ b/src/swanctl/commands/terminate.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "command.h" + +#include + + +CALLBACK(log_cb, void, + bool *raw, char *name, vici_res_t *msg) +{ + if (*raw) + { + vici_dump(msg, "log", stdout); + } + else + { + printf("[%s] %s\n", + vici_find_str(msg, " ", "group"), + vici_find_str(msg, "", "msg")); + } +} + +static int terminate(vici_conn_t *conn) +{ + vici_req_t *req; + vici_res_t *res; + bool raw = FALSE; + char *arg, *child = NULL, *ike = NULL; + int ret = 0, timeout = 0, level = 1, child_id = 0, ike_id = 0; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 'r': + raw = TRUE; + continue; + case 'c': + child = arg; + continue; + case 'i': + ike = arg; + continue; + case 'C': + child_id = atoi(arg); + continue; + case 'I': + ike_id = atoi(arg); + continue; + case 't': + timeout = atoi(arg); + continue; + case 'l': + level = atoi(arg); + continue; + case EOF: + break; + default: + return command_usage("invalid --terminate option"); + } + break; + } + + if (vici_register(conn, "control-log", log_cb, &raw) != 0) + { + fprintf(stderr, "registering for log failed: %s\n", strerror(errno)); + return errno; + } + req = vici_begin("terminate"); + if (child) + { + vici_add_key_valuef(req, "child", "%s", child); + } + if (ike) + { + vici_add_key_valuef(req, "ike", "%s", ike); + } + if (child_id) + { + vici_add_key_valuef(req, "child-id", "%d", child_id); + } + if (ike_id) + { + vici_add_key_valuef(req, "ike-id", "%d", ike_id); + } + if (timeout) + { + vici_add_key_valuef(req, "timeout", "%d", timeout * 1000); + } + vici_add_key_valuef(req, "loglevel", "%d", level); + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "terminate request failed: %s\n", strerror(errno)); + return errno; + } + if (raw) + { + vici_dump(res, "terminate reply", stdout); + } + else + { + if (streq(vici_find_str(res, "no", "success"), "yes")) + { + printf("terminate completed successfully\n"); + } + else + { + fprintf(stderr, "terminate failed: %s\n", + vici_find_str(res, "", "errmsg")); + ret = 1; + } + } + vici_free_res(res); + return ret; +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + terminate, 't', "terminate", "terminate a connection", + {"--child | --ike | --ike-id ", + "[--timeout ] [--raw]"}, + { + {"help", 'h', 0, "show usage information"}, + {"child", 'c', 1, "terminate by CHILD_SA name"}, + {"ike", 'i', 1, "terminate by IKE_SA name"}, + {"child-id", 'C', 1, "terminate by CHILD_SA reqid"}, + {"ike-id", 'I', 1, "terminate by IKE_SA unique identifier"}, + {"timeout", 't', 1, "timeout in seconds before detaching"}, + {"raw", 'r', 0, "dump raw response message"}, + {"loglevel", 'l', 1, "verbosity of redirected log"}, + } + }); +} diff --git a/src/swanctl/commands/version.c b/src/swanctl/commands/version.c new file mode 100644 index 000000000..c44004dee --- /dev/null +++ b/src/swanctl/commands/version.c @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "command.h" + +#include + +static int version(vici_conn_t *conn) +{ + vici_req_t *req; + vici_res_t *res; + char *arg; + bool raw = FALSE, daemon = FALSE;; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 'r': + raw = TRUE; + continue; + case 'd': + daemon = TRUE; + continue; + case EOF: + break; + default: + return command_usage("invalid --terminate option"); + } + break; + } + + if (!daemon) + { + printf("strongSwan swanctl %s\n", VERSION); + return 0; + } + + req = vici_begin("version"); + res = vici_submit(req, conn); + if (!res) + { + fprintf(stderr, "version request failed: %s\n", strerror(errno)); + return errno; + } + if (raw) + { + vici_dump(res, "version reply", stdout); + } + else + { + printf("strongSwan %s %s (%s, %s, %s)\n", + vici_find_str(res, "", "version"), + vici_find_str(res, "", "daemon"), + vici_find_str(res, "", "sysname"), + vici_find_str(res, "", "release"), + vici_find_str(res, "", "machine")); + } + vici_free_res(res); + return 0; +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + version, 'v', "version", "show version information", + {"[--raw]"}, + { + {"help", 'h', 0, "show usage information"}, + {"daemon", 'd', 0, "query daemon version"}, + {"raw", 'r', 0, "dump raw response message"}, + } + }); +} diff --git a/src/swanctl/swanctl.8.in b/src/swanctl/swanctl.8.in new file mode 100644 index 000000000..d7abae67a --- /dev/null +++ b/src/swanctl/swanctl.8.in @@ -0,0 +1,83 @@ +.TH SWANCTL 8 "2014-04-28" "@PACKAGE_VERSION@" "strongSwan" +.SH NAME +swanctl \- strongSwan configuration, control and monitoring command line interface. +.SH SYNOPSIS +.SY "swanctl" +.I command +.RI [ option\~ .\|.\|.] +.YS +. +.SY "swanctl" +.B \-h +| +.B \-\-help +.YS +. +.SH DESCRIPTION +swanctl is a cross-platform command line utility to configure, control and +monitor the strongSwan IKE daemon. It is a replacement for the aging +.BR starter , +.B ipsec +and +.B stroke +tools. + +swanctl uses a configuration file called +.BR swanctl.conf (5) +to parse configurations and credentials. Private keys, certificates and other +PKI related credentials are read from specific directories. + +To communicate with the IKE daemon, swanctl uses the VICI protocol, the +Versatile IKE Configuration Interface. This stable interface is usable by +other tools and is often preferable than scripting swanctl and parsing its +output. + +.SH COMMANDS +.TP +.B "\-i, \-\-initiate" +initiate a connection +.TP +.B "\-t, \-\-terminate" +\-\-terminate\fR +terminate a connection +.TP +.B "\-p, \-\-install" +install a trap or shunt policy +.TP +.B "\-u, \-\-uninstall" +uninstall a trap or shunt policy +.TP +.B "\-l, \-\-list\-sas" +list currently active IKE_SAs +.TP +.B "\-P, \-\-list\-pols" +list currently installed policies +.TP +.B "\-L, \-\-list\-conns" +list loaded configurations +.TP +.B "\-x, \-\-list\-certs" +list stored certificates +.TP +.B "\-A, \-\-list\-pools" +list loaded pool configurations +.TP +.B "\-c, \-\-load\-conns" +(re\-)load connection configuration +.TP +.B "\-s, \-\-load\-creds" +(re\-)load credentials +.TP +.B "\-a, \-\-load\-pools" +(re\-)load pool configuration +.TP +.B "\-T, \-\-log" +trace logging output +.TP +.B "\-v, \-\-version" +show daemon version information +.TP +.B "\-h, \-\-help" +show usage information +.SH SEE ALSO +.BR swanctl.conf (5) diff --git a/src/swanctl/swanctl.c b/src/swanctl/swanctl.c new file mode 100644 index 000000000..7aacf839d --- /dev/null +++ b/src/swanctl/swanctl.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "command.h" + +#include + +#include + +/** + * Cleanup library atexit() + */ +static void cleanup() +{ + lib->processor->cancel(lib->processor); + library_deinit(); +} + +/** + * Library initialization and operation parsing + */ +int main(int argc, char *argv[]) +{ + atexit(cleanup); + if (!library_init(NULL, "swanctl")) + { + exit(SS_RC_LIBSTRONGSWAN_INTEGRITY); + } + if (lib->integrity && + !lib->integrity->check_file(lib->integrity, "swanctl", argv[0])) + { + fprintf(stderr, "integrity check of swanctl failed\n"); + exit(SS_RC_DAEMON_INTEGRITY); + } + if (!lib->plugins->load(lib->plugins, + lib->settings->get_str(lib->settings, "swanctl.load", PLUGINS))) + { + exit(SS_RC_INITIALIZATION_FAILED); + } + dbg_default_set_level(0); + lib->processor->set_threads(lib->processor, 4); + dbg_default_set_level(0); + + return command_dispatch(argc, argv); +} diff --git a/src/swanctl/swanctl.conf.5.head.in b/src/swanctl/swanctl.conf.5.head.in new file mode 100644 index 000000000..84f734eb9 --- /dev/null +++ b/src/swanctl/swanctl.conf.5.head.in @@ -0,0 +1,24 @@ +.TH SWANCTL.CONF 5 "" "@PACKAGE_VERSION@" "strongSwan" +.SH NAME +swanctl.conf \- swanctl configuration file +.SH DESCRIPTION +swanctl.conf is the configuration file used by the +.BR swanctl (8) +tool to load configurations and credentials into the strongSwan IKE daemon. + +For a description of the basic file syntax refer to +.BR strongswan.conf (5). + +.SH TIME FORMATS +For all options that define a time, the time is specified in seconds. The +.RI "" "s" "," +.RI "" "m" "," +.RI "" "h" "" +and +.RI "" "d" "" +suffixes explicitly define the units for seconds, minutes, hours and days, +respectively. + +.SH SETTINGS +The following settings can be used to configure connections, credentials and +pools. diff --git a/src/swanctl/swanctl.conf.5.tail.in b/src/swanctl/swanctl.conf.5.tail.in new file mode 100644 index 000000000..4d24608da --- /dev/null +++ b/src/swanctl/swanctl.conf.5.tail.in @@ -0,0 +1,10 @@ +.SH FILES +. +.nf +.na +/etc/swanctl/swanctl.conf configuration file +.ad +.fi +. +.SH SEE ALSO +.BR swanctl (8) diff --git a/src/swanctl/swanctl.h b/src/swanctl/swanctl.h new file mode 100644 index 000000000..e1c7d91f5 --- /dev/null +++ b/src/swanctl/swanctl.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * 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. See . + * + * 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. + */ + +/** + * @defgroup swanctl swanctl + * @{ @ingroup swanctl + */ + +#ifndef SWANCTL_H_ +#define SWANCTL_H_ + +/** + * Configuration file for connections, etc. + */ +#define SWANCTL_CONF SWANCTLDIR "/swanctl.conf" + +/** + * Directory for X.509 end entity certs + */ +#define SWANCTL_X509DIR SWANCTLDIR "/x509" + +/** + * Directory for X.509 CA certs + */ +#define SWANCTL_X509CADIR SWANCTLDIR "/x509ca" + +/** + * Directory for X.509 Attribute Authority certs + */ +#define SWANCTL_X509AADIR SWANCTLDIR "/x509aa" + +/** + * Directory for X.509 CRLs + */ +#define SWANCTL_X509CRLDIR SWANCTLDIR "/x509crl" + +/** + * Directory for X.509 Attribute certificates + */ +#define SWANCTL_X509ACDIR SWANCTLDIR "/x509ac" + +/** + * Directory for RSA private keys + */ +#define SWANCTL_RSADIR SWANCTLDIR "/rsa" + +/** + * Directory for ECDSA private keys + */ +#define SWANCTL_ECDSADIR SWANCTLDIR "/ecdsa" + +/** + * Directory for PKCS#8 encoded private keys + */ +#define SWANCTL_PKCS8DIR SWANCTLDIR "/pkcs8" + +#endif /** SWANCTL_H_ @}*/ diff --git a/src/swanctl/swanctl.opt b/src/swanctl/swanctl.opt new file mode 100644 index 000000000..73907b6ce --- /dev/null +++ b/src/swanctl/swanctl.opt @@ -0,0 +1,739 @@ +connections { # } + Section defining IKE connection configurations. + + Section defining IKE connection configurations. + + The connections section defines IKE connection configurations, each in + its own subsections. In the keyword description below, the connection + is named __, but an arbitrary yet unique connection name can be + chosen for each connection subsection. + +connections. { # } + Section for an IKE connection named . + +connections..version = 0 + IKE major version to use for connection. + + IKE major version to use for connection. _1_ uses IKEv1 aka ISAKMP, _2_ + uses IKEv2. A connection using the default of _0_ accepts both IKEv1 + and IKEv2 as responder, and initiates the connection actively with IKEv2. + +connections..local_addrs = %any + Local address(es) to use for IKE communication, comma separated. + + Local address(es) to use for IKE communication, comma separated. Takes + single IPv4/IPv6 addresses, DNS names, CIDR subnets or IP address ranges. + + As initiator, the first non-range/non-subnet is used to initiate the + connection from. As responder, the local destination address must match at + least to one of the specified addresses, subnets or ranges. + +connections..remote_addrs = %any + Remote address(es) to use for IKE communication, comma separated. + + Remote address(es) to use for IKE communication, comma separated. Takes + single IPv4/IPv6 addresses, DNS names, CIDR subnets or IP address ranges. + + As initiator, the first non-range/non-subnet is used to initiate the + connection to. As responder, the initiator source address must match at + least to one of the specified addresses, subnets or ranges. + + To initiate a connection, at least one specific address or DNS name must + be specified. + +connections..local_port = 500 + Local UPD port for IKE communication. + + Local UPD port for IKE communication. By default the port of the socket + backend is used, which is usually _500_. If port _500_ is used, automatic + IKE port floating to port 4500 is used to work around NAT issues. + + Using a non-default local IKE port requires support from the socket backend + in use (socket-dynamic). + +connections..remote_port = 500 + Remote UDP port for IKE communication. + + Remote UPD port for IKE communication. If the default of port _500_ is used, + automatic IKE port floating to port 4500 is used to work around NAT issues. + +connections..proposals = default + Comma separated proposals to accept for IKE. + + A proposal is a set of algorithms. For non-AEAD algorithms, this includes + for IKE an encryption algorithm, an integrity algorithm, a pseudo random + function and a Diffie-Hellman group. For AEAD algorithms, instead of + encryption and integrity algorithms, a combined algorithm is used. + + In IKEv2, multiple algorithms of the same kind can be specified in a single + proposal, from which one gets selected. In IKEv1, only one algorithm per + kind is allowed per proposal, more algorithms get implicitly stripped. Use + multiple proposals to offer different algorithms combinations in IKEv1. + + Algorithm keywords get separated using dashes. Multiple proposals may be + separated by commas. The special value _default_ forms a default proposal + of supported algorithms considered safe, and is usually a good choice + for interoperability. + +connections..vips = + Virtual IPs to request in configuration payload / Mode Config. + + Comma separated list of virtual IPs to request in IKEv2 configuration + payloads or IKEv1 Mode Config. The wildcard addresses _0.0.0.0_ and _::_ + request an arbitrary address, specific addresses may be defined. The + responder may return a different address, though, or none at all. + +connections..aggressive = no + Use Aggressive Mode in IKEv1. + + Enables Aggressive Mode instead of Main Mode with Identity Protection. + Aggressive Mode is considered less secure, because the ID and HASH + payloads are exchanged unprotected. This allows a passive attacker to + snoop peer identities, and even worse, start dictionary attacks on the + Preshared Key. + +connections..pull = yes + Set the Mode Config mode to use. + + If the default of _yes_ is used, Mode Config works in pull mode, where + the initiator actively requests a virtual IP. With _no_, push mode is used, + where the responder pushes down a virtual IP to the initiating peer. + + Push mode is currently supported for IKEv1, but not in IKEv2. It is used + by a few implementations only, pull mode is recommended. + +connections..encap = no + Enforce UDP encapsulation by faking NAT-D payloads. + + To enforce UDP encapsulation of ESP packets, the IKE daemon can fake the + NAT detection payloads. This makes the peer believe that NAT takes + place on the path, forcing it to encapsulate ESP packets in UDP. + + Usually this is not required, but it can help to work around connectivity + issues with too restrictive intermediary firewalls. + +connections..mobike = yes + Enables MOBIKE on IKEv2 connections. + + Enables MOBIKE on IKEv2 connections. MOBIKE is enabled by default on IKEv2 + connections, and allows mobility of clients and multi-homing on servers by + migrating active IPsec tunnels. + + Usually keeping MOBIKE enabled is unproblematic, as it is not used if the + peer does not indicate support for it. However, due to the design of MOBIKE, + IKEv2 always floats to port 4500 starting from the second exchange. Some + implementations don't like this behavior, hence it can be disabled. + +connections..dpd_delay = 0s + Interval of liveness checks (DPD). + + Interval to check the liveness of a peer actively using IKEv2 INFORMATIONAL + exchanges or IKEv1 R_U_THERE messages. Active DPD checking is only enforced + if no IKE or ESP/AH packet has been received for the configured DPD delay. + +connections..dpd_timeout = 0s + Timeout for DPD checks (IKEV1 only). + + Charon by default uses the normal retransmission mechanism and timeouts to + check the liveness of a peer, as all messages are used for liveness + checking. For compatibility reasons, with IKEv1 a custom interval may be + specified; this option has no effect on connections using IKE2. + +connections..fragmentation = no + Use IKEv1 UDP packet fragmentation (_yes_, _no_ or _force_). + + The default of _no_ disables IKEv1 fragmentation mechanism, _yes_ enables + it if support has been indicated by the peer. _force_ enforces + fragmentation if required even before the peer had a chance to indicate + support for it. + + IKE fragmentation is currently not supported with IKEv2. + +connections..send_certreq = yes + Send certificate requests payloads (_yes_ or _no_). + + Send certificate request payloads to offer trusted root CA certificates + to the peer. Certificate requests help the peer to choose an appropriate + certificate/private key for authentication and are enabled by default. + + Disabling certificate requests can be useful if too many trusted root CA + certificates are installed, as each certificate request increases the size + of the initial IKE packets. + +connections..send_cert = ifasked + Send certificate payloads (_yes_, _no_ or _ifasked_). + + Send certificate payloads when using certificate authentication. With the + default of _ifasked_ the daemon sends certificate payloads only if + certificate requests have been received. _no_ disables sending of + certificate payloads, _yes_ always sends certificate payloads whenever + certificate authentication is used. + +connections..keyingtries = 1 + Number of retransmission sequences to perform during initial connect. + + Number of retransmission sequences to perform during initial connect. + Instead of giving up initiation after the first retransmission sequence with + the default value of _1_, additional sequences may be started according to + the configured value. A value of _0_ initiates a new sequence until the + connection establishes or fails with a permanent error. + +connections..unique = no + Connection uniqueness policy (_never_, _no_, _keep_ or _replace_). + + Connection uniqueness policy to enforce. To avoid multiple connections + from the same user, a uniqueness policy can be enforced. The value _never_ + does never enforce such a policy, even if a peer included INITIAL_CONTACT + notification messages, whereas _no_ replaces existing connections for the + same identity if a new one has the INITIAL_CONTACT notify. _keep_ rejects + new connection attempts if the same user already has an active connection, + _replace_ deletes any existing connection if a new one for the same user + gets established. + + To compare connections for uniqueness, the remote IKE identity is used. If + EAP or XAuth authentication is involved, the EAP-Identity or XAuth username + is used to enforce the uniqueness policy instead. + +connections..reauth_time = 0s + Time to schedule IKE reauthentication. + + Time to schedule IKE reauthentication. IKE reauthentication recreates the + IKE/ISAKMP SA from scratch and re-evaluates the credentials. In asymmetric + configurations (with EAP or configuration payloads) it might not be possible + to actively reauthenticate as responder. The IKEv2 reauthentication lifetime + negotiation can instruct the client to perform reauthentication. + + Reauthentication is disabled by default. Enabling it usually may lead + to small connection interruptions, as strongSwan uses a break-before-make + policy with IKEv2 to avoid any conflicts with associated tunnel resources. + +connections..rekey_time = 4h + Time to schedule IKE rekeying. + + IKE rekeying refreshes key material using a Diffie-Hellman exchange, but + does not re-check associated credentials. It is supported in IKEv2 only, + IKEv1 performs a reauthentication procedure instead. + + With the default value IKE rekeying is scheduled every 4 hours, minus the + configured **rand_time**. + +connections..over_time = 10% of rekey_time/reauth_time + Hard IKE_SA lifetime if rekey/reauth does not complete, as time. + + Hard IKE_SA lifetime if rekey/reauth does not complete, as time. + To avoid having an IKE/ISAKMP kept alive if IKE reauthentication or rekeying + fails perpetually, a maximum hard lifetime may be specified. If the + IKE_SA fails to rekey or reauthenticate within the specified time, the + IKE_SA gets closed. + + In contrast to CHILD_SA rekeying, **over_time** is relative in time to the + **rekey_time** _and_ **reauth_time** values, as it applies to both. + + The default is 10% of the longer of **rekey_time** and **reauth_time**. + +connections..rand_time = over_time + Range of random time to subtract from rekey/reauth times. + + Time range from which to choose a random value to subtract from + rekey/reauth times. To avoid having both peers initiating the rekey/reauth + procedure simultaneously, a random time gets subtracted from the + rekey/reauth times. + + The default is equal to the configured **over_time**. + +connections..pools = + Comma separated list of named IP pools. + + Comma separated list of named IP pools to allocate virtual IP addresses and + other configuration attributes from. Each name references a pool by name + from either the **pools** section or an external pool. + +connections..local {} + Section for a local authentication round. + + Section for a local authentication round. A local authentication round + defines the rules how authentication is performed for the local peer. + Multiple rounds may be defined to use IKEv2 RFC 4739 Multiple Authentication + or IKEv1 XAuth. + + Each round is defined in a section having _local_ as prefix, and an optional + unique suffix. To define a single authentication round, the suffix may be + omitted. + +connections..local.certs = + Comma separated list of certificate candidates to use for authentication. + + Comma separated list of certificate candidates to use for authentication. + The certificates may use a relative path from the **swanctl** _x509_ + directory, or an absolute path. + + The certificate used for authentication is selected based on the received + certificate request payloads. If no appropriate CA can be located, the + first certificate is used. + +connections..local.auth = pubkey + Authentication to perform locally (_pubkey_, _psk_, _xauth[-backend]_ or + _eap[-method]_). + + Authentication to perform locally. _pubkey_ uses public key authentication + using a private key associated to a usable certificate. _psk_ uses + pre-shared key authentication. The IKEv1 specific _xauth_ is used for + XAuth or Hybrid authentication, while the IKEv2 specific _eap_ keyword + defines EAP authentication. + + For _xauth_, a specific backend name may be appended, separated by a dash. + The appropriate _xauth_ backend is selected to perform the XAuth exchange. + For traditional XAuth, the _xauth_ method is usually defined in the second + authentication round following an initial _pubkey_ (or _psk_) round. Using + _xauth_ in the first round performs Hybrid Mode client authentication. + + For _eap_, a specific EAP method name may be appended, separated by a dash. + An EAP module implementing the appropriate method is selected to perform + the EAP conversation. + +connections..local.id = + IKE identity to use for authentication round. + + IKE identity to use for authentication round. When using certificate + authentication, the IKE identity must be contained in the certificate, + either as subject or as subjectAltName. + +connections..local.eap_id = id + Client EAP-Identity to use in EAP-Identity exchange and the EAP method. + +connections..local.aaa_id = remote-id + Server side EAP-Identity to expect in the EAP method. + + Server side EAP-Identity to expect in the EAP method. Some EAP methods, such + as EAP-TLS, use an identity for the server to perform mutual authentication. + This identity may differ from the IKE identity, especially when EAP + authentication is delegated from the IKE responder to an AAA backend. + + For EAP-(T)TLS, this defines the identity for wich the server must provide + a certificate in the TLS exchange. + +connections..local.xauth_id = id + Client XAuth username used in the XAuth exchange. + +connections..remote {} + Section for a remote authentication round. + + Section for a remote authentication round. A remote authentication round + defines the constraints how the peers must authenticate to use this + connection. Multiple rounds may be defined to use IKEv2 RFC 4739 Multiple + Authentication or IKEv1 XAuth. + + Each round is defined in a section having _remote_ as prefix, and an + optional unique suffix. To define a single authentication round, the suffix + may be omitted. + +connections..remote.id = %any + IKE identity to expect for authentication round. + + IKE identity to expect for authentication round. When using certificate + authentication, the IKE identity must be contained in the certificate, + either as subject or as subjectAltName. + +connections..remote.groups = + Authorization group memberships to require. + + Comma separated authorization group memberships to require. The peer must + prove membership to at least one of the specified groups. Group membership + can be certified by different means, for example by appropriate Attribute + Certificates or by an AAA backend involved in the authentication. + +connections..remote.certs = + Comma separated list of certificate to accept for authentication. + + Comma separated list of certificates to accept for authentication. + The certificates may use a relative path from the **swanctl** _x509_ + directory, or an absolute path. + +connections..remote.cacert = + Comma separated list of CA certificates to accept for authentication. + + Comma separated list of CA certificates to accept for authentication. + The certificates may use a relative path from the **swanctl** _x509ca_ + directory, or an absolute path. + +connections..remote.revocation = relaxed + Certificate revocation policy, (_strict_, _ifuri_ or _relaxed_). + + Certificate revocation policy for CRL or OCSP revocation. + + A _strict_ revocation policy fails if no revocation information is + available, i.e. the certificate is not known to be unrevoked. + + _ifuri_ fails only if a CRL/OCSP URI is available, but certificate + revocation checking fails, i.e. there should be revocation information + available, but it could not be obtained. + + The default revocation policy _relaxed_ fails only if a certificate + is revoked, i.e. it is explicitly known that it is bad. + +connections..remote.auth = pubkey + Authentication to expect from remote (_pubkey_, _psk_, _xauth[-backend]_ or + _eap[-method]_). + + Authentication to expect from remote. See the **local** sections **auth** + keyword description about the details of supported mechanisms. + +connections..children. {} + CHILD_SA configuration sub-section. + + CHILD_SA configuration sub-section. Each connection definition may have + one or more sections in its _children_ subsection. The section name + defines the name of the CHILD_SA configuration, which must be unique within + the connection. + +connections..children..ah_proposals = + AH proposals to offer for the CHILD_SA. + + AH proposals to offer for the CHILD_SA. A proposal is a set of algorithms. + For AH, this includes an integrity algorithm and an optional Diffie-Hellman + group. If a DH group is specified, CHILD_SA/Quick Mode rekeying and initial + negotiation uses a separate Diffie-Hellman exchange using the specified + group. + + In IKEv2, multiple algorithms of the same kind can be specified in a single + proposal, from which one gets selected. In IKEv1, only one algorithm per + kind is allowed per proposal, more algorithms get implicitly stripped. Use + multiple proposals to offer different algorithms combinations in IKEv1. + + Algorithm keywords get separated using dashes. Multiple proposals may be + separated by commas. The special value _default_ forms a default proposal + of supported algorithms considered safe, and is usually a good choice + for interoperability. By default no AH proposals are included, instead ESP + is proposed. + +connections..children..esp_proposals = default + ESP proposals to offer for the CHILD_SA. + + ESP proposals to offer for the CHILD_SA. A proposal is a set of algorithms. + For ESP non-AEAD proposals, this includes an integrity algorithm, an + encryption algorithm, an optional Diffie-Hellman group and an optional + Extended Sequence Number Mode indicator. For AEAD proposals, a combined + mode algorithm is used instead of the separate encryption/integrity + algorithms. + + If a DH group is specified, CHILD_SA/Quick Mode rekeying and initial (non + IKE_AUTH piggybacked) negotiation uses a separate Diffie-Hellman exchange + using the specified group. Extended Sequence Number support may be indicated + with the _esn_ and _noesn_ values, both may be included to indicate support + for both modes. If omitted, _noesn_ is assumed. + + In IKEv2, multiple algorithms of the same kind can be specified in a single + proposal, from which one gets selected. In IKEv1, only one algorithm per + kind is allowed per proposal, more algorithms get implicitly stripped. Use + multiple proposals to offer different algorithms combinations in IKEv1. + + Algorithm keywords get separated using dashes. Multiple proposals may be + separated by commas. The special value _default_ forms a default proposal + of supported algorithms considered safe, and is usually a good choice + for interoperability. If no algorithms are specified for AH nor ESP, + the _default_ set of algorithms for ESP is included. + +connections..children..local_ts = dynamic + Local traffic selectors to include in CHILD_SA. + + Comma separated list of local traffic selectors to include in CHILD_SA. + Each selector is a CIDR subnet definition, followed by an optional + proto/port selector. The special value _dynamic_ may be used instead of a + subnet definition, which gets replaced by the tunnel outer address or the + virtual IP, if negotiated. This is the default. + + A protocol/port selector is surrounded by opening and closing square + brackets. Between these brackets, a numeric or **getservent**(3) protocol + name may be specified. After the optional protocol restriction, an optional + port restriction may be specified, separated by a slash. The port + restriction may be numeric, a **getservent**(3) service name, or the special + value _opaque_ for RFC 4301 OPAQUE selectors. Port ranges may be specified + as well, none of the kernel backends currently support port ranges, though. + + Unless the Unity extension is used, IKEv1 supports the first specified + selector only. IKEv1 uses very similar traffic selector narrowing as it is + supported in the IKEv2 protocol. + +connections..children..remote_ts = dynamic + Remote selectors to include in CHILD_SA. + + Comma separated list of remote selectors to include in CHILD_SA. See + **local_ts** for a description of the selector syntax. + +connections..children..rekey_time = 1h + Time to schedule CHILD_SA rekeying. + + Time to schedule CHILD_SA rekeying. CHILD_SA rekeying refreshes key + material, optionally using a Diffie-Hellman exchange if a group is + specified in the proposal. + + To avoid rekey collisions initiated by both ends simultaneously, a value + in the range of **rand_time** gets subtracted to form the effective soft + lifetime. + + By default CHILD_SA rekeying is scheduled every hour, minus **rand_time**. + +connections..children..life_time = rekey_time + 10% + Maximum lifetime before CHILD_SA gets closed, as time. + + Maximum lifetime before CHILD_SA gets closed. Usually this hard lifetime + is never reached, because the CHILD_SA gets rekeyed before. + If that fails for whatever reason, this limit closes the CHILD_SA. + + The default is 10% more than the **rekey_time**. + +connections..children..rand_time = life_time - rekey_time + Range of random time to subtract from **rekey_time**. + + Time range from which to choose a random value to subtract from + **rekey_time**. The default is the difference between **life_time** and + **rekey_time**. + +connections..children..rekey_bytes = 0 + Number of bytes processed before initiating CHILD_SA rekeying. + + Number of bytes processed before initiating CHILD_SA rekeying. CHILD_SA + rekeying refreshes key material, optionally using a Diffie-Hellman exchange + if a group is specified in the proposal. + + To avoid rekey collisions initiated by both ends simultaneously, a value + in the range of **rand_bytes** gets subtracted to form the effective soft + volume limit. + + Volume based CHILD_SA rekeying is disabled by default. + +connections..children..life_bytes = rekey_bytes + 10% + Maximum bytes processed before CHILD_SA gets closed. + + Maximum bytes processed before CHILD_SA gets closed. Usually this hard + volume limit is never reached, because the CHILD_SA gets rekeyed before. + If that fails for whatever reason, this limit closes the CHILD_SA. + + The default is 10% more than **rekey_bytes**. + +connections..children..rand_bytes = life_bytes - rekey_bytes + Range of random bytes to subtract from **rekey_bytes**. + + Byte range from which to choose a random value to subtract from + **rekey_bytes**. The default is the difference between **life_bytes** and + **rekey_bytes**. + +connections..children..rekey_packets = 0 + Number of packets processed before initiating CHILD_SA rekeying. + + Number of packets processed before initiating CHILD_SA rekeying. CHILD_SA + rekeying refreshes key material, optionally using a Diffie-Hellman exchange + if a group is specified in the proposal. + + To avoid rekey collisions initiated by both ends simultaneously, a value + in the range of **rand_packets** gets subtracted to form the effective soft + packet count limit. + + Packet count based CHILD_SA rekeying is disabled by default. + +connections..children..life_packets = rekey_packets + 10% + Maximum number of packets processed before CHILD_SA gets closed. + + Maximum number of packets processed before CHILD_SA gets closed. Usually + this hard packets limit is never reached, because the CHILD_SA gets rekeyed + before. If that fails for whatever reason, this limit closes the CHILD_SA. + + The default is 10% more than **rekey_bytes**. + +connections..children..rand_packets = life_packets - rekey_packets + Range of random packets to subtract from **packets_bytes**. + + Packet range from which to choose a random value to subtract from + **rekey_packets**. The default is the difference between **life_packets** + and **rekey_packets**. + +connections..children..updown = + Updown script to invoke on CHILD_SA up and down events. + +connections..children..hostaccess = yes + Hostaccess variable to pass to **updown** script. + +connections..children..mode = tunnel + IPsec Mode to establish (_tunnel_, _transport_, _beet_, _pass_ or _drop_). + + IPsec Mode to establish CHILD_SA with. _tunnel_ negotiates the CHILD_SA + in IPsec Tunnel Mode, whereas _transport_ uses IPsec Transport Mode. _beet_ + is the Bound End to End Tunnel mixture mode, working with fixed inner + addresses without the need to include them in each packet. + + Both _transport_ and _beet_ modes are subject to mode negotiation; _tunnel_ + mode is negotiated if the preferred mode is not available. + + _pass_ and _drop_ are used to install shunt policies, which explicitly + bypass the defined traffic from IPsec processing, or drop it, respectively. + +connections..children..dpd_action = clear + Action to perform on DPD timeout (_clear_, _trap_ or _restart_). + + Action to perform for this CHILD_SA on DPD timeout. The default _clear_ + closes the CHILD_SA and does not take further action. _trap_ installs + a trap policy, which will catch matching traffic and tries to re-negotiate + the tunnel on-demand. _restart_ immediately tries to re-negotiate the + CHILD_SA under a fresh IKE_SA. + +connections..children..ipcomp = no + Enable IPComp compression before encryption. + + Enable IPComp compression before encryption. If enabled, IKE tries to + negotiate IPComp compression to compress ESP payload data prior to + encryption. + +connections..children..inactivity = 0s + Timeout before closing CHILD_SA after inactivity. + + Timeout before closing CHILD_SA after inactivity. If no traffic has + been processed in either direction for the configured timeout, the CHILD_SA + gets closed due to inactivity. The default value of _0_ disables inactivity + checks. + +connections..children..reqid = 0 + Fixed reqid to use for this CHILD_SA. + + Fixed reqid to use for this CHILD_SA. This might be helpful in some + scenarios, but works only if each CHILD_SA configuration is instantiated + not more than once. The default of _0_ uses dynamic reqids, allocated + incrementally. + +connections..children..mark_in = 0/0x00000000 + Netfilter mark and mask for input traffic. + + Netfilter mark and mask for input traffic. On Linux Netfilter may apply + marks to each packet coming from a tunnel having that option set. The + mark may then be used by Netfilter to match rules. + + An additional mask may be appended to the mark, separated by _/_. The + default mask if omitted is 0xffffffff. + +connections..children..mark_out = 0/0x00000000 + Netfilter mark and mask for output traffic. + + Netfilter mark and mask for output traffic. On Linux Netfilter may require + marks on each packet to match a policy having that option set. This allows + Netfilter rules to select specific tunnels for outgoing traffic. + + An additional mask may be appended to the mark, separated by _/_. The + default mask if omitted is 0xffffffff. + +connections..children..tfc_padding = 0 + Traffic Flow Confidentiality padding. + + Pads ESP packets with additional data to have a consistent ESP packet size + for improved Traffic Flow Confidentiality. The padding defines the minimum + size of all ESP packets sent. + + The default value of 0 disables TFC padding, the special value _mtu_ adds + TFC padding to create a packet size equal to the Path Maximum Transfer Unit. + +connections..children..start_action = none + Action to perform after loading the configuration (_none_, _trap_, _start_). + + Action to perform after loading the configuration. The default of _none_ + loads the connection only, which then can be manually initiated or used as + a responder configuration. + + The value _trap_ installs a trap policy, which triggers the tunnel as soon + as matching traffic has been detected. The value _start_ initiates + the connection actively. + + When unloading or replacing a CHILD_SA configuration having a + **start_action** different from _none_, the inverse action is performed. + Configurations with _start_ get closed, while such with _trap_ get + uninstalled. + +connections..children..close_action = none + Action to perform after a CHILD_SA gets closed (_none_, _trap_, _start_). + + Action to perform after a CHILD_SA gets closed by the peer. The default of + _none_ does not take any action, _trap_ installs a trap policy for the + CHILD_SA. _start_ tries to re-create the CHILD_SA. + + **close_action** does not provide any guarantee that the CHILD_SA is kept + alive. It acts on explicit close messages only, but not on negotiation + failures. Use trap policies to reliably re-create failed CHILD_SAs. + +secrets { # } + Section defining secrets for IKE and EAP/XAuth authentication. + + Section defining secrets for IKE and EAP/XAuth authentication. The + **secrets** section takes sub-sections having a specific prefix which + defines the secret type. + +secrets.eap { # } + EAP secret section for a specific secret. + + EAP secret section for a specific secret. Each EAP secret is defined in + a unique section having the _eap_ prefix. EAP secrets are used for XAuth + authentication as well. + +secrets.xauth { # } + XAuth secret section for a specific secret. + + XAuth secret section for a specific secret. **xauth** is just an alias + for **eap**, secrets under both section prefixes are used for both EAP and + XAuth authentication. + +secrets.eap.secret = + Value of the EAP/XAuth secret. + + Value of the EAP/XAuth secret. It may either be an ASCII string, a hex + encoded string if it has a _0x_ prefix, or a Base64 encoded string if it + has a _0s_ prefix in its value. + +secrets.eap.id = + Identity the EAP/XAuth secret belongs to. + + Identity the EAP/XAuth secret belongs to. Multiple unique identities may + be specified, each having an _id_ prefix, if a secret is shared between + multiple users. + +secrets.ike { # } + IKE preshared secret section for a specific secret. + + IKE preshared secret section for a specific secret. Each IKE PSK is defined + in a unique section having the _ike_ prefix. + +secrets.ike.secret = + Value of the IKE preshared secret. + + Value of the IKE preshared secret. It may either be an ASCII string, + a hex encoded string if it has a _0x_ prefix, or a Base64 encoded string if + it has a _0s_ prefix in its value. + +secrets.ike.id = + IKE identity the IKE preshared secret belongs to. + + IKE identity the IKE preshared secret belongs to. Multiple unique identities + may be specified, each having an _id_ prefix, if a secret is shared between + multiple peers. + +pools { # } + Section defining named pools. + + Section defining named pools. Named pools may be referenced by connections + with the **pools** option to assign virtual IPs and other configuration + attributes. + +pools. { # } + Section defining a single pool with a unique name. + +pools..addrs = + Subnet defining addresses allocated in pool. + + Subnet defining addresses allocated in pool. Accepts a single CIDR subnet + defining the pool to allocate addresses from. Pools must be unique and + non-overlapping. + +pools.. = + Comma separated list of additional attributes from type . + + Comma separated list of additional attributes of type ****. The + attribute type may be one of _dns_, _nbns_, _dhcp_, _netmask_, _server_, + _subnet_, _split_include_ and _split_exclude_ to define addresses or CIDR + subnets for the corresponding attribute types. Alternatively, **** can + be a numerical identifier, for which string attribute values are accepted + as well.