summaryrefslogtreecommitdiff
path: root/src/openvpn/multi.c
diff options
context:
space:
mode:
authorBernhard Schmidt <berni@debian.org>2020-09-01 16:52:17 +0200
committerBernhard Schmidt <berni@debian.org>2020-09-01 16:52:17 +0200
commit9fc3b98112217f2d92a67977dbde0987cc7a1803 (patch)
tree29fcc8654ee65d9dd89ade797bea2f3d9dfd9cfd /src/openvpn/multi.c
parenta8758c0e03eed188dcb9da0e4fd781a67c25bf1e (diff)
parent69b02b1f7fd609d84ace13ab04697158de2418a9 (diff)
Merge branch 'debian/experimental-2.5'
Diffstat (limited to 'src/openvpn/multi.c')
-rw-r--r--src/openvpn/multi.c1473
1 files changed, 1070 insertions, 403 deletions
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index c8c9a40..1373818 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -34,21 +34,23 @@
#include "syshead.h"
-#if P2MP_SERVER
-
+#include "forward.h"
#include "multi.h"
#include "push.h"
-#include "misc.h"
+#include "run_command.h"
#include "otime.h"
+#include "pf.h"
#include "gremlin.h"
#include "mstats.h"
#include "ssl_verify.h"
+#include "ssl_ncp.h"
+#include "vlan.h"
#include <inttypes.h>
#include "memdbg.h"
-#include "forward-inline.h"
-#include "pf-inline.h"
+
+#include "crypto_backend.h"
/*#define MULTI_DEBUG_EVENT_LOOP*/
@@ -136,7 +138,7 @@ learn_address_script(const struct multi_context *m,
msg(M_WARN, "WARNING: learn-address plugin call failed");
ret = false;
}
- argv_reset(&argv);
+ argv_free(&argv);
}
if (m->top.options.learn_address_script)
@@ -153,7 +155,7 @@ learn_address_script(const struct multi_context *m,
{
ret = false;
}
- argv_reset(&argv);
+ argv_free(&argv);
}
gc_free(&gc);
@@ -386,7 +388,8 @@ multi_init(struct multi_context *m, struct context *t, bool tcp_mode, int thread
* differently based on whether a tun or tap style
* tunnel.
*/
- if (t->options.ifconfig_pool_defined)
+ if (t->options.ifconfig_pool_defined
+ || t->options.ifconfig_ipv6_pool_defined)
{
int pool_type = IFCONFIG_POOL_INDIV;
@@ -395,7 +398,8 @@ multi_init(struct multi_context *m, struct context *t, bool tcp_mode, int thread
pool_type = IFCONFIG_POOL_30NET;
}
- m->ifconfig_pool = ifconfig_pool_init(pool_type,
+ m->ifconfig_pool = ifconfig_pool_init(t->options.ifconfig_pool_defined,
+ pool_type,
t->options.ifconfig_pool_start,
t->options.ifconfig_pool_end,
t->options.duplicate_cn,
@@ -564,44 +568,36 @@ multi_client_disconnect_setenv(struct multi_instance *mi)
setenv_stats(&mi->context);
/* setenv connection duration */
- {
- const unsigned int duration = (unsigned int) now - mi->created;
- setenv_unsigned(mi->context.c2.es, "time_duration", duration);
- }
+ setenv_long_long(mi->context.c2.es, "time_duration", now - mi->created);
}
static void
multi_client_disconnect_script(struct multi_instance *mi)
{
- if ((mi->context.c2.context_auth == CAS_SUCCEEDED && mi->connection_established_flag)
- || mi->context.c2.context_auth == CAS_PARTIAL)
- {
- multi_client_disconnect_setenv(mi);
+ multi_client_disconnect_setenv(mi);
- if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT))
+ if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT))
+ {
+ if (plugin_call(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT, NULL, NULL, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
{
- if (plugin_call(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT, NULL, NULL, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
- {
- msg(M_WARN, "WARNING: client-disconnect plugin call failed");
- }
+ msg(M_WARN, "WARNING: client-disconnect plugin call failed");
}
+ }
- if (mi->context.options.client_disconnect_script)
- {
- struct argv argv = argv_new();
- setenv_str(mi->context.c2.es, "script_type", "client-disconnect");
- argv_parse_cmd(&argv, mi->context.options.client_disconnect_script);
- openvpn_run_script(&argv, mi->context.c2.es, 0, "--client-disconnect");
- argv_reset(&argv);
- }
+ if (mi->context.options.client_disconnect_script)
+ {
+ struct argv argv = argv_new();
+ setenv_str(mi->context.c2.es, "script_type", "client-disconnect");
+ argv_parse_cmd(&argv, mi->context.options.client_disconnect_script);
+ openvpn_run_script(&argv, mi->context.c2.es, 0, "--client-disconnect");
+ argv_free(&argv);
+ }
#ifdef MANAGEMENT_DEF_AUTH
- if (management)
- {
- management_notify_client_close(management, &mi->context.c2.mda_context, mi->context.c2.es);
- }
-#endif
-
+ if (management)
+ {
+ management_notify_client_close(management, &mi->context.c2.mda_context, mi->context.c2.es);
}
+#endif
}
void
@@ -682,14 +678,13 @@ multi_close_instance(struct multi_context *m,
#ifdef MANAGEMENT_DEF_AUTH
set_cc_config(mi, NULL);
#endif
-
- multi_client_disconnect_script(mi);
-
- if (mi->did_open_context)
+ if (mi->context.c2.context_auth == CAS_SUCCEEDED)
{
- close_context(&mi->context, SIGTERM, CC_GC_FREE);
+ multi_client_disconnect_script(mi);
}
+ close_context(&mi->context, SIGTERM, CC_GC_FREE);
+
multi_tcp_instance_specific_free(mi);
ungenerate_prefix(mi);
@@ -787,7 +782,6 @@ multi_create_instance(struct multi_context *m, const struct mroute_addr *real)
generate_prefix(mi);
}
- mi->did_open_context = true;
inherit_context_child(&mi->context, &m->top);
if (IS_SIG(&mi->context))
{
@@ -827,10 +821,8 @@ multi_create_instance(struct multi_context *m, const struct mroute_addr *real)
mi->did_cid_hash = true;
#endif
- mi->context.c2.push_reply_deferred = true;
-
-#ifdef ENABLE_ASYNC_PUSH
mi->context.c2.push_request_received = false;
+#ifdef ENABLE_ASYNC_PUSH
mi->inotify_watch = -1;
#endif
@@ -941,8 +933,8 @@ multi_print_status(struct multi_context *m, struct status_output *so, const int
*/
status_printf(so, "TITLE%c%s", sep, title_string);
status_printf(so, "TIME%c%s%c%u", sep, time_string(now, 0, false, &gc_top), sep, (unsigned int)now);
- status_printf(so, "HEADER%cCLIENT_LIST%cCommon Name%cReal Address%cVirtual Address%cVirtual IPv6 Address%cBytes Received%cBytes Sent%cConnected Since%cConnected Since (time_t)%cUsername%cClient ID%cPeer ID",
- sep, sep, sep, sep, sep, sep, sep, sep, sep, sep, sep, sep);
+ status_printf(so, "HEADER%cCLIENT_LIST%cCommon Name%cReal Address%cVirtual Address%cVirtual IPv6 Address%cBytes Received%cBytes Sent%cConnected Since%cConnected Since (time_t)%cUsername%cClient ID%cPeer ID%cData Channel Cipher",
+ sep, sep, sep, sep, sep, sep, sep, sep, sep, sep, sep, sep, sep);
hash_iterator_init(m->hash, &hi);
while ((he = hash_iterator_next(&hi)))
{
@@ -957,7 +949,7 @@ multi_print_status(struct multi_context *m, struct status_output *so, const int
#else
""
#endif
- "%c%" PRIu32,
+ "%c%" PRIu32 "%c%s",
sep, tls_common_name(mi->context.c2.tls_multi, false),
sep, mroute_addr_print(&mi->real, &gc),
sep, print_in_addr_t(mi->reporting_addr, IA_EMPTY_IF_UNDEF, &gc),
@@ -972,7 +964,8 @@ multi_print_status(struct multi_context *m, struct status_output *so, const int
#else
sep,
#endif
- sep, mi->context.c2.tls_multi ? mi->context.c2.tls_multi->peer_id : UINT32_MAX);
+ sep, mi->context.c2.tls_multi ? mi->context.c2.tls_multi->peer_id : UINT32_MAX,
+ sep, translate_cipher_name_to_openvpn(mi->context.options.ciphername));
}
gc_free(&gc);
}
@@ -1495,41 +1488,47 @@ multi_select_virtual_addr(struct multi_context *m, struct multi_instance *mi)
const int tunnel_topology = TUNNEL_TOPOLOGY(mi->context.c1.tuntap);
msg( M_INFO, "MULTI_sva: pool returned IPv4=%s, IPv6=%s",
- print_in_addr_t( remote, 0, &gc ),
+ (mi->context.options.ifconfig_pool_defined
+ ? print_in_addr_t(remote, 0, &gc)
+ : "(Not enabled)"),
(mi->context.options.ifconfig_ipv6_pool_defined
? print_in6_addr( remote_ipv6, 0, &gc )
: "(Not enabled)") );
- /* set push_ifconfig_remote_netmask from pool ifconfig address(es) */
- mi->context.c2.push_ifconfig_local = remote;
- if (tunnel_type == DEV_TYPE_TAP || (tunnel_type == DEV_TYPE_TUN && tunnel_topology == TOP_SUBNET))
+ if (mi->context.options.ifconfig_pool_defined)
{
- mi->context.c2.push_ifconfig_remote_netmask = mi->context.options.ifconfig_pool_netmask;
- if (!mi->context.c2.push_ifconfig_remote_netmask)
+ /* set push_ifconfig_remote_netmask from pool ifconfig address(es) */
+ mi->context.c2.push_ifconfig_local = remote;
+ if (tunnel_type == DEV_TYPE_TAP || (tunnel_type == DEV_TYPE_TUN && tunnel_topology == TOP_SUBNET))
{
- mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->remote_netmask;
+ mi->context.c2.push_ifconfig_remote_netmask = mi->context.options.ifconfig_pool_netmask;
+ if (!mi->context.c2.push_ifconfig_remote_netmask)
+ {
+ mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->remote_netmask;
+ }
}
- }
- else if (tunnel_type == DEV_TYPE_TUN)
- {
- if (tunnel_topology == TOP_P2P)
+ else if (tunnel_type == DEV_TYPE_TUN)
{
- mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->local;
+ if (tunnel_topology == TOP_P2P)
+ {
+ mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->local;
+ }
+ else if (tunnel_topology == TOP_NET30)
+ {
+ mi->context.c2.push_ifconfig_remote_netmask = local;
+ }
}
- else if (tunnel_topology == TOP_NET30)
+
+ if (mi->context.c2.push_ifconfig_remote_netmask)
{
- mi->context.c2.push_ifconfig_remote_netmask = local;
+ mi->context.c2.push_ifconfig_defined = true;
+ }
+ else
+ {
+ msg(D_MULTI_ERRORS,
+ "MULTI: no --ifconfig-pool netmask parameter is available to push to %s",
+ multi_instance_string(mi, false, &gc));
}
- }
-
- if (mi->context.c2.push_ifconfig_remote_netmask)
- {
- mi->context.c2.push_ifconfig_defined = true;
- }
- else
- {
- msg(D_MULTI_ERRORS, "MULTI: no --ifconfig-pool netmask parameter is available to push to %s",
- multi_instance_string(mi, false, &gc));
}
if (mi->context.options.ifconfig_ipv6_pool_defined)
@@ -1636,16 +1635,15 @@ static void
multi_client_connect_post(struct multi_context *m,
struct multi_instance *mi,
const char *dc_file,
- unsigned int option_permissions_mask,
unsigned int *option_types_found)
{
/* Did script generate a dynamic config file? */
- if (test_file(dc_file))
+ if (platform_test_file(dc_file))
{
options_server_import(&mi->context.options,
dc_file,
D_IMPORT_ERRORS|M_OPTERR,
- option_permissions_mask,
+ CLIENT_CONNECT_OPT_MASK,
option_types_found,
mi->context.c2.es);
@@ -1669,7 +1667,6 @@ static void
multi_client_connect_post_plugin(struct multi_context *m,
struct multi_instance *mi,
const struct plugin_return *pr,
- unsigned int option_permissions_mask,
unsigned int *option_types_found)
{
struct plugin_return config;
@@ -1687,7 +1684,7 @@ multi_client_connect_post_plugin(struct multi_context *m,
options_string_import(&mi->context.options,
config.list[i]->value,
D_IMPORT_ERRORS|M_OPTERR,
- option_permissions_mask,
+ CLIENT_CONNECT_OPT_MASK,
option_types_found,
mi->context.c2.es);
}
@@ -1706,29 +1703,30 @@ multi_client_connect_post_plugin(struct multi_context *m,
#endif /* ifdef ENABLE_PLUGIN */
-#ifdef MANAGEMENT_DEF_AUTH
/*
* Called to load management-derived client-connect config
*/
-static void
+enum client_connect_return
multi_client_connect_mda(struct multi_context *m,
struct multi_instance *mi,
- const struct buffer_list *config,
- unsigned int option_permissions_mask,
+ bool deferred,
unsigned int *option_types_found)
{
- if (config)
+ /* We never return CC_RET_DEFERRED */
+ ASSERT(!deferred);
+ enum client_connect_return ret = CC_RET_SKIPPED;
+#ifdef MANAGEMENT_DEF_AUTH
+ if (mi->cc_config)
{
struct buffer_entry *be;
-
- for (be = config->head; be != NULL; be = be->next)
+ for (be = mi->cc_config->head; be != NULL; be = be->next)
{
const char *opt = BSTR(&be->buf);
options_string_import(&mi->context.options,
opt,
D_IMPORT_ERRORS|M_OPTERR,
- option_permissions_mask,
+ CLIENT_CONNECT_OPT_MASK,
option_types_found,
mi->context.c2.es);
}
@@ -1741,10 +1739,12 @@ multi_client_connect_mda(struct multi_context *m,
*/
multi_select_virtual_addr(m, mi);
multi_set_virtual_addr_env(mi);
- }
-}
+ ret = CC_RET_SUCCEEDED;
+ }
#endif /* ifdef MANAGEMENT_DEF_AUTH */
+ return ret;
+}
static void
multi_client_connect_setenv(struct multi_context *m,
@@ -1765,350 +1765,951 @@ multi_client_connect_setenv(struct multi_context *m,
{
const char *created_ascii = time_string(mi->created, 0, false, &gc);
setenv_str(mi->context.c2.es, "time_ascii", created_ascii);
- setenv_unsigned(mi->context.c2.es, "time_unix", (unsigned int)mi->created);
+ setenv_long_long(mi->context.c2.es, "time_unix", mi->created);
}
gc_free(&gc);
}
-/*
- * Called as soon as the SSL/TLS connection authenticates.
+/**
+ * Extracts the IV_PROTO variable and returns its value or 0
+ * if it cannot be extracted.
*
- * Instance-specific directives to be processed:
- *
- * iroute start-ip end-ip
- * ifconfig-push local remote-netmask
- * push
*/
-static void
-multi_connection_established(struct multi_context *m, struct multi_instance *mi)
+static unsigned int
+extract_iv_proto(const char *peer_info)
{
- if (tls_authentication_status(mi->context.c2.tls_multi, 0) == TLS_AUTHENTICATION_SUCCEEDED)
+
+ const char *optstr = peer_info ? strstr(peer_info, "IV_PROTO=") : NULL;
+ if (optstr)
{
- struct gc_arena gc = gc_new();
- unsigned int option_types_found = 0;
+ int proto = 0;
+ int r = sscanf(optstr, "IV_PROTO=%d", &proto);
+ if (r == 1 && proto > 0)
+ {
+ return proto;
+ }
+ }
+ return 0;
+}
- const unsigned int option_permissions_mask =
- OPT_P_INSTANCE
- | OPT_P_INHERIT
- | OPT_P_PUSH
- | OPT_P_TIMER
- | OPT_P_CONFIG
- | OPT_P_ECHO
- | OPT_P_COMP
- | OPT_P_SOCKFLAGS;
+/**
+ * Calculates the options that depend on the client capabilities
+ * based on local options and available peer info
+ * - choosen cipher
+ * - peer id
+ */
+static bool
+multi_client_set_protocol_options(struct context *c)
+{
+ struct tls_multi *tls_multi = c->c2.tls_multi;
+ const char *const peer_info = tls_multi->peer_info;
+ struct options *o = &c->options;
- int cc_succeeded = true; /* client connect script status */
- int cc_succeeded_count = 0;
- ASSERT(mi->context.c1.tuntap);
+ unsigned int proto = extract_iv_proto(peer_info);
+ if (proto & IV_PROTO_DATA_V2)
+ {
+ tls_multi->use_peer_id = true;
+ }
+ if (proto & IV_PROTO_REQUEST_PUSH)
+ {
+ c->c2.push_request_received = true;
+ }
- /* lock down the common name and cert hashes so they can't change during future TLS renegotiations */
- tls_lock_common_name(mi->context.c2.tls_multi);
- tls_lock_cert_hash_set(mi->context.c2.tls_multi);
+ /* Select cipher if client supports Negotiable Crypto Parameters */
+ if (!o->ncp_enabled)
+ {
+ return true;
+ }
- /* generate a msg() prefix for this client instance */
- generate_prefix(mi);
+ /* if we have already created our key, we cannot *change* our own
+ * cipher -> so log the fact and push the "what we have now" cipher
+ * (so the client is always told what we expect it to use)
+ */
+ const struct tls_session *session = &tls_multi->session[TM_ACTIVE];
+ if (session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized)
+ {
+ msg(M_INFO, "PUSH: client wants to negotiate cipher (NCP), but "
+ "server has already generated data channel keys, "
+ "re-sending previously negotiated cipher '%s'",
+ o->ciphername );
+ return true;
+ }
- /* delete instances of previous clients with same common-name */
- if (!mi->context.options.duplicate_cn)
+ /*
+ * Push the first cipher from --data-ciphers to the client that
+ * the client announces to be supporting.
+ */
+ char *push_cipher = ncp_get_best_cipher(o->ncp_ciphers, peer_info,
+ tls_multi->remote_ciphername,
+ &o->gc);
+
+ if (push_cipher)
+ {
+ o->ciphername = push_cipher;
+ return true;
+ }
+
+ /* NCP cipher negotiation failed. Try to figure out why exactly it
+ * failed and give good error messages and potentially do a fallback
+ * for non NCP clients */
+ struct gc_arena gc = gc_new();
+ bool ret = false;
+
+ const char *peer_ciphers = tls_peer_ncp_list(peer_info, &gc);
+ /* If we are in a situation where we know the client ciphers, there is no
+ * reason to fall back to a cipher that will not be accepted by the other
+ * side, in this situation we fail the auth*/
+ if (strlen(peer_ciphers) > 0)
+ {
+ msg(M_INFO, "PUSH: No common cipher between server and client. "
+ "Server data-ciphers: '%s', client supported ciphers '%s'",
+ o->ncp_ciphers, peer_ciphers);
+ }
+ else if (tls_multi->remote_ciphername)
+ {
+ msg(M_INFO, "PUSH: No common cipher between server and client. "
+ "Server data-ciphers: '%s', client supports cipher '%s'",
+ o->ncp_ciphers, tls_multi->remote_ciphername);
+ }
+ else
+ {
+ msg(M_INFO, "PUSH: No NCP or OCC cipher data received from peer.");
+
+ if (o->enable_ncp_fallback && !tls_multi->remote_ciphername)
{
- multi_delete_dup(m, mi);
+ msg(M_INFO, "Using data channel cipher '%s' since "
+ "--data-ciphers-fallback is set.", o->ciphername);
+ ret = true;
}
+ else
+ {
+ msg(M_INFO, "Use --data-ciphers-fallback with the cipher the "
+ "client is using if you want to allow the client to connect");
+ }
+ }
+ if (!ret)
+ {
+ auth_set_client_reason(tls_multi, "Data channel cipher negotiation "
+ "failed (no shared cipher)");
+ }
- /* reset pool handle to null */
- mi->vaddr_handle = -1;
+ gc_free(&gc);
+ return ret;
+}
- /*
- * Try to source a dynamic config file from the
- * --client-config-dir directory.
- */
- if (mi->context.options.client_config_dir)
- {
- const char *ccd_file;
+/**
+ * Delete the temporary file for the return value of client connect
+ * It also removes it from client_connect_defer_state and environment
+ */
+static void
+ccs_delete_deferred_ret_file(struct multi_instance *mi)
+{
+ struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+ if (!ccs->deferred_ret_file)
+ {
+ return;
+ }
+
+ setenv_del(mi->context.c2.es, "client_connect_deferred_file");
+ if (!platform_unlink(ccs->deferred_ret_file))
+ {
+ msg(D_MULTI_ERRORS, "MULTI: problem deleting temporary file: %s",
+ ccs->deferred_ret_file);
+ }
+ free(ccs->deferred_ret_file);
+ ccs->deferred_ret_file = NULL;
+}
- ccd_file = gen_path(mi->context.options.client_config_dir,
- tls_common_name(mi->context.c2.tls_multi, false),
- &gc);
+/**
+ * Create a temporary file for the return value of client connect
+ * and puts it into the client_connect_defer_state and environment
+ * as "client_connect_deferred_file"
+ *
+ * @return boolean value if creation was successful
+ */
+static bool
+ccs_gen_deferred_ret_file(struct multi_instance *mi)
+{
+ struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+ struct gc_arena gc = gc_new();
+ const char *fn;
- /* try common-name file */
- if (test_file(ccd_file))
+ /* Delete file if it already exists */
+ ccs_delete_deferred_ret_file(mi);
+
+ fn = platform_create_temp_file(mi->context.options.tmp_dir, "ccr", &gc);
+ if (!fn)
+ {
+ gc_free(&gc);
+ return false;
+ }
+ ccs->deferred_ret_file = string_alloc(fn, NULL);
+
+ setenv_str(mi->context.c2.es, "client_connect_deferred_file",
+ ccs->deferred_ret_file);
+
+ gc_free(&gc);
+ return true;
+}
+
+/**
+ * Tests whether the deferred return value file exists and returns the
+ * contained return value.
+ *
+ * @return CC_RET_SKIPPED if the file does not exist or is empty.
+ * CC_RET_DEFERRED, CC_RET_SUCCEEDED or CC_RET_FAILED depending on
+ * the value stored in the file.
+ */
+static enum client_connect_return
+ccs_test_deferred_ret_file(struct multi_instance *mi)
+{
+ struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+ FILE *fp = fopen(ccs->deferred_ret_file, "r");
+ if (!fp)
+ {
+ return CC_RET_SKIPPED;
+ }
+
+ enum client_connect_return ret = CC_RET_SKIPPED;
+ const int c = fgetc(fp);
+ switch (c)
+ {
+ case '0':
+ ret = CC_RET_FAILED;
+ break;
+
+ case '1':
+ ret = CC_RET_SUCCEEDED;
+ break;
+
+ case '2':
+ ret = CC_RET_DEFERRED;
+ break;
+
+ case EOF:
+ if (feof(fp))
{
- options_server_import(&mi->context.options,
- ccd_file,
- D_IMPORT_ERRORS|M_OPTERR,
- option_permissions_mask,
- &option_types_found,
- mi->context.c2.es);
+ ret = CC_RET_SKIPPED;
+ break;
}
- else /* try default file */
- {
- ccd_file = gen_path(mi->context.options.client_config_dir,
- CCD_DEFAULT,
- &gc);
- if (test_file(ccd_file))
- {
- options_server_import(&mi->context.options,
- ccd_file,
- D_IMPORT_ERRORS|M_OPTERR,
- option_permissions_mask,
- &option_types_found,
- mi->context.c2.es);
- }
- }
+ /* Not EOF but other error -> fall through to error state */
+ default:
+ /* We received an unknown/unexpected value. Assume failure. */
+ msg(M_WARN, "WARNING: Unknown/unexpected value in deferred"
+ "client-connect resultfile");
+ ret = CC_RET_FAILED;
+ }
+ fclose(fp);
+
+ return ret;
+}
+
+/**
+ * Deletes the temporary file for the config directives of the client connect
+ * script and removes it into the client_connect_defer_state and environment
+ *
+ */
+static void
+ccs_delete_config_file(struct multi_instance *mi)
+{
+ struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+ if (ccs->config_file)
+ {
+ setenv_del(mi->context.c2.es, "client_connect_config_file");
+ if (!platform_unlink(ccs->config_file))
+ {
+ msg(D_MULTI_ERRORS, "MULTI: problem deleting temporary file: %s",
+ ccs->config_file);
}
+ free(ccs->config_file);
+ ccs->config_file = NULL;
+ }
+}
- /*
- * Select a virtual address from either --ifconfig-push in --client-config-dir file
- * or --ifconfig-pool.
- */
- multi_select_virtual_addr(m, mi);
+/**
+ * Create a temporary file for the config directives of the client connect
+ * script and puts it into the client_connect_defer_state and environment
+ * as "client_connect_config_file"
+ *
+ * @return boolean value if creation was successful
+ */
+static bool
+ccs_gen_config_file(struct multi_instance *mi)
+{
+ struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+ struct gc_arena gc = gc_new();
+ const char *fn;
- /* do --client-connect setenvs */
- multi_client_connect_setenv(m, mi);
+ if (ccs->config_file)
+ {
+ ccs_delete_config_file(mi);
+ }
+ fn = platform_create_temp_file(mi->context.options.tmp_dir, "cc", &gc);
+ if (!fn)
+ {
+ gc_free(&gc);
+ return false;
+ }
+ ccs->config_file = string_alloc(fn, NULL);
+
+ setenv_str(mi->context.c2.es, "client_connect_config_file",
+ ccs->config_file);
+
+ gc_free(&gc);
+ return true;
+}
+
+static enum client_connect_return
+multi_client_connect_call_plugin_v1(struct multi_context *m,
+ struct multi_instance *mi,
+ bool deferred,
+ unsigned int *option_types_found)
+{
+ enum client_connect_return ret = CC_RET_SKIPPED;
#ifdef ENABLE_PLUGIN
- /*
- * Call client-connect plug-in.
- */
+ ASSERT(m);
+ ASSERT(mi);
+ ASSERT(option_types_found);
+ struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
- /* deprecated callback, use a file for passing back return info */
- if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT))
- {
- struct argv argv = argv_new();
- const char *dc_file = create_temp_file(mi->context.options.tmp_dir, "cc", &gc);
+ /* deprecated callback, use a file for passing back return info */
+ if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT))
+ {
+ struct argv argv = argv_new();
+ int call;
- if (!dc_file)
+ if (!deferred)
+ {
+ call = OPENVPN_PLUGIN_CLIENT_CONNECT;
+ if (!ccs_gen_config_file(mi)
+ || !ccs_gen_deferred_ret_file(mi))
{
- cc_succeeded = false;
- goto script_depr_failed;
+ ret = CC_RET_FAILED;
+ goto cleanup;
}
+ }
+ else
+ {
+ call = OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER;
+ /* the initial call should have created these files */
+ ASSERT(ccs->config_file);
+ ASSERT(ccs->deferred_ret_file);
+ }
- argv_printf(&argv, "%s", dc_file);
- if (plugin_call(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT, &argv, NULL, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
- {
- msg(M_WARN, "WARNING: client-connect plugin call failed");
- cc_succeeded = false;
- }
- else
- {
- multi_client_connect_post(m, mi, dc_file, option_permissions_mask, &option_types_found);
- ++cc_succeeded_count;
- }
+ argv_printf(&argv, "%s", ccs->config_file);
+ int plug_ret = plugin_call(mi->context.plugins, call,
+ &argv, NULL, mi->context.c2.es);
+ if (plug_ret == OPENVPN_PLUGIN_FUNC_SUCCESS)
+ {
+ ret = CC_RET_SUCCEEDED;
+ }
+ else if (plug_ret == OPENVPN_PLUGIN_FUNC_DEFERRED)
+ {
+ ret = CC_RET_DEFERRED;
+ /**
+ * Contrary to the plugin v2 API, we do not demand a working
+ * deferred plugin as all return can be handled by the files
+ * and plugin_call return success if a plugin is not defined
+ */
+ }
+ else
+ {
+ msg(M_WARN, "WARNING: client-connect plugin call failed");
+ ret = CC_RET_FAILED;
+ }
- if (!platform_unlink(dc_file))
- {
- msg(D_MULTI_ERRORS, "MULTI: problem deleting temporary file: %s",
- dc_file);
- }
-script_depr_failed:
- argv_reset(&argv);
+ /**
+ * plugin api v1 client connect async feature has both plugin and
+ * file return status, so in cases where the file has a code that
+ * demands override, we override our return code
+ */
+ int file_ret = ccs_test_deferred_ret_file(mi);
+
+ if (file_ret == CC_RET_FAILED)
+ {
+ ret = CC_RET_FAILED;
+ }
+ else if (ret == CC_RET_SUCCEEDED && file_ret == CC_RET_DEFERRED)
+ {
+ ret = CC_RET_DEFERRED;
+ }
+
+ /* if we still think we have succeeded, do postprocessing */
+ if (ret == CC_RET_SUCCEEDED)
+ {
+ multi_client_connect_post(m, mi, ccs->config_file,
+ option_types_found);
}
+cleanup:
+ argv_free(&argv);
- /* V2 callback, use a plugin_return struct for passing back return info */
- if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT_V2))
+ if (ret != CC_RET_DEFERRED)
{
- struct plugin_return pr;
+ ccs_delete_config_file(mi);
+ ccs_delete_deferred_ret_file(mi);
+ }
+ }
+#endif /* ifdef ENABLE_PLUGIN */
+ return ret;
+}
- plugin_return_init(&pr);
+static enum client_connect_return
+multi_client_connect_call_plugin_v2(struct multi_context *m,
+ struct multi_instance *mi,
+ bool deferred,
+ unsigned int *option_types_found)
+{
+ enum client_connect_return ret = CC_RET_SKIPPED;
+#ifdef ENABLE_PLUGIN
+ ASSERT(m);
+ ASSERT(mi);
+ ASSERT(option_types_found);
- if (plugin_call(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT_V2, NULL, &pr, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
- {
- msg(M_WARN, "WARNING: client-connect-v2 plugin call failed");
- cc_succeeded = false;
- }
- else
+ int call = deferred ? OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2 :
+ OPENVPN_PLUGIN_CLIENT_CONNECT_V2;
+ /* V2 callback, use a plugin_return struct for passing back return info */
+ if (plugin_defined(mi->context.plugins, call))
+ {
+ struct plugin_return pr;
+
+ plugin_return_init(&pr);
+
+ int plug_ret = plugin_call(mi->context.plugins, call,
+ NULL, &pr, mi->context.c2.es);
+ if (plug_ret == OPENVPN_PLUGIN_FUNC_SUCCESS)
+ {
+ multi_client_connect_post_plugin(m, mi, &pr, option_types_found);
+ ret = CC_RET_SUCCEEDED;
+ }
+ else if (plug_ret == OPENVPN_PLUGIN_FUNC_DEFERRED)
+ {
+ ret = CC_RET_DEFERRED;
+ if (!(plugin_defined(mi->context.plugins,
+ OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2)))
{
- multi_client_connect_post_plugin(m, mi, &pr, option_permissions_mask, &option_types_found);
- ++cc_succeeded_count;
+ msg(M_WARN, "A plugin that defers from the "
+ "OPENVPN_PLUGIN_CLIENT_CONNECT_V2 call must also "
+ "declare support for "
+ "OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2");
+ ret = CC_RET_FAILED;
}
-
- plugin_return_free(&pr);
}
+ else
+ {
+ msg(M_WARN, "WARNING: client-connect-v2 plugin call failed");
+ ret = CC_RET_FAILED;
+ }
+
+
+ plugin_return_free(&pr);
+ }
#endif /* ifdef ENABLE_PLUGIN */
+ return ret;
+}
+
+static enum client_connect_return
+multi_client_connect_script_deferred(struct multi_context *m,
+ struct multi_instance *mi,
+ unsigned int *option_types_found)
+{
+ ASSERT(mi);
+ ASSERT(option_types_found);
+ struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+ enum client_connect_return ret = CC_RET_SKIPPED;
+ ret = ccs_test_deferred_ret_file(mi);
+
+ if (ret == CC_RET_SKIPPED)
+ {
/*
- * Run --client-connect script.
+ * Skipped and deferred are equivalent in this context.
+ * skipped means that the called program has not yet
+ * written a return status implicitly needing more time
+ * while deferred is the explicit notification that it
+ * needs more time
*/
- if (mi->context.options.client_connect_script && cc_succeeded)
- {
- struct argv argv = argv_new();
- const char *dc_file = NULL;
+ ret = CC_RET_DEFERRED;
+ }
- setenv_str(mi->context.c2.es, "script_type", "client-connect");
+ if (ret == CC_RET_SUCCEEDED)
+ {
+ ccs_delete_deferred_ret_file(mi);
+ multi_client_connect_post(m, mi, ccs->config_file,
+ option_types_found);
+ ccs_delete_config_file(mi);
+ }
+ if (ret == CC_RET_FAILED)
+ {
+ msg(M_INFO, "MULTI: deferred --client-connect script returned CC_RET_FAILED");
+ ccs_delete_deferred_ret_file(mi);
+ ccs_delete_config_file(mi);
+ }
+ return ret;
+}
- dc_file = create_temp_file(mi->context.options.tmp_dir, "cc", &gc);
- if (!dc_file)
- {
- cc_succeeded = false;
- goto script_failed;
- }
+/**
+ * Runs the --client-connect script if one is defined.
+ */
+static enum client_connect_return
+multi_client_connect_call_script(struct multi_context *m,
+ struct multi_instance *mi,
+ bool deferred,
+ unsigned int *option_types_found)
+{
+ if (deferred)
+ {
+ return multi_client_connect_script_deferred(m, mi, option_types_found);
+ }
+ ASSERT(m);
+ ASSERT(mi);
+
+ enum client_connect_return ret = CC_RET_SKIPPED;
+ struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+
+ if (mi->context.options.client_connect_script)
+ {
+ struct argv argv = argv_new();
+ struct gc_arena gc = gc_new();
+
+ setenv_str(mi->context.c2.es, "script_type", "client-connect");
+
+ if (!ccs_gen_config_file(mi)
+ || !ccs_gen_deferred_ret_file(mi))
+ {
+ ret = CC_RET_FAILED;
+ goto cleanup;
+ }
- argv_parse_cmd(&argv, mi->context.options.client_connect_script);
- argv_printf_cat(&argv, "%s", dc_file);
+ argv_parse_cmd(&argv, mi->context.options.client_connect_script);
+ argv_printf_cat(&argv, "%s", ccs->config_file);
- if (openvpn_run_script(&argv, mi->context.c2.es, 0, "--client-connect"))
+ if (openvpn_run_script(&argv, mi->context.c2.es, 0, "--client-connect"))
+ {
+ if (ccs_test_deferred_ret_file(mi) == CC_RET_DEFERRED)
{
- multi_client_connect_post(m, mi, dc_file, option_permissions_mask, &option_types_found);
- ++cc_succeeded_count;
+ ret = CC_RET_DEFERRED;
}
else
{
- cc_succeeded = false;
+ multi_client_connect_post(m, mi, ccs->config_file,
+ option_types_found);
+ ret = CC_RET_SUCCEEDED;
}
+ }
+ else
+ {
+ ret = CC_RET_FAILED;
+ }
+cleanup:
+ if (ret != CC_RET_DEFERRED)
+ {
+ ccs_delete_config_file(mi);
+ ccs_delete_deferred_ret_file(mi);
+ }
+ argv_free(&argv);
+ gc_free(&gc);
+ }
+ return ret;
+}
- if (!platform_unlink(dc_file))
- {
- msg(D_MULTI_ERRORS, "MULTI: problem deleting temporary file: %s",
- dc_file);
- }
+/**
+ * Generates the data channel keys
+ */
+static bool
+multi_client_generate_tls_keys(struct context *c)
+{
+ struct frame *frame_fragment = NULL;
+#ifdef ENABLE_FRAGMENT
+ if (c->options.ce.fragment)
+ {
+ frame_fragment = &c->c2.frame_fragment;
+ }
+#endif
+ struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
+ if (!tls_session_update_crypto_params(session, &c->options,
+ &c->c2.frame, frame_fragment))
+ {
+ msg(D_TLS_ERRORS, "TLS Error: initializing data channel failed");
+ register_signal(c, SIGUSR1, "process-push-msg-failed");
+ return false;
+ }
-script_failed:
- argv_reset(&argv);
+ return true;
+}
+
+static void
+multi_client_connect_late_setup(struct multi_context *m,
+ struct multi_instance *mi,
+ const unsigned int option_types_found)
+{
+ ASSERT(m);
+ ASSERT(mi);
+
+ struct gc_arena gc = gc_new();
+ /*
+ * Process sourced options.
+ */
+ do_deferred_options(&mi->context, option_types_found);
+
+ /*
+ * make sure we got ifconfig settings from somewhere
+ */
+ if (!mi->context.c2.push_ifconfig_defined)
+ {
+ msg(D_MULTI_ERRORS, "MULTI: no dynamic or static remote"
+ "--ifconfig address is available for %s",
+ multi_instance_string(mi, false, &gc));
+ }
+
+ /*
+ * make sure that ifconfig settings comply with constraints
+ */
+ if (!ifconfig_push_constraint_satisfied(&mi->context))
+ {
+ const char *ifconfig_constraint_network =
+ print_in_addr_t(mi->context.options.push_ifconfig_constraint_network, 0, &gc);
+ const char *ifconfig_constraint_netmask =
+ print_in_addr_t(mi->context.options.push_ifconfig_constraint_netmask, 0, &gc);
+
+ /* JYFIXME -- this should cause the connection to fail */
+ msg(D_MULTI_ERRORS, "MULTI ERROR: primary virtual IP for %s (%s)"
+ "violates tunnel network/netmask constraint (%s/%s)",
+ multi_instance_string(mi, false, &gc),
+ print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc),
+ ifconfig_constraint_network, ifconfig_constraint_netmask);
+ }
+
+ /*
+ * For routed tunnels, set up internal route to endpoint
+ * plus add all iroute routes.
+ */
+ if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)
+ {
+ if (mi->context.c2.push_ifconfig_defined)
+ {
+ multi_learn_in_addr_t(m, mi,
+ mi->context.c2.push_ifconfig_local,
+ -1, true);
+ msg(D_MULTI_LOW, "MULTI: primary virtual IP for %s: %s",
+ multi_instance_string(mi, false, &gc),
+ print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc));
}
- /*
- * Check for client-connect script left by management interface client
- */
-#ifdef MANAGEMENT_DEF_AUTH
- if (cc_succeeded && mi->cc_config)
+ if (mi->context.c2.push_ifconfig_ipv6_defined)
{
- multi_client_connect_mda(m, mi, mi->cc_config, option_permissions_mask, &option_types_found);
- ++cc_succeeded_count;
+ multi_learn_in6_addr(m, mi,
+ mi->context.c2.push_ifconfig_ipv6_local,
+ -1, true);
+ /* TODO: find out where addresses are "unlearned"!! */
+ const char *ifconfig_local_ipv6 =
+ print_in6_addr(mi->context.c2.push_ifconfig_ipv6_local, 0, &gc);
+ msg(D_MULTI_LOW, "MULTI: primary virtual IPv6 for %s: %s",
+ multi_instance_string(mi, false, &gc),
+ ifconfig_local_ipv6);
}
-#endif
+
+ /* add routes locally, pointing to new client, if
+ * --iroute options have been specified */
+ multi_add_iroutes(m, mi);
/*
- * Check for "disable" directive in client-config-dir file
- * or config file generated by --client-connect script.
+ * iroutes represent subnets which are "owned" by a particular
+ * client. Therefore, do not actually push a route to a client
+ * if it matches one of the client's iroutes.
*/
- if (mi->context.options.disable)
+ remove_iroutes_from_push_route_list(&mi->context.options);
+ }
+ else if (mi->context.options.iroutes)
+ {
+ msg(D_MULTI_ERRORS, "MULTI: --iroute options rejected for %s -- iroute"
+ "only works with tun-style tunnels",
+ multi_instance_string(mi, false, &gc));
+ }
+
+ /* set our client's VPN endpoint for status reporting purposes */
+ mi->reporting_addr = mi->context.c2.push_ifconfig_local;
+ mi->reporting_addr_ipv6 = mi->context.c2.push_ifconfig_ipv6_local;
+
+ /* set context-level authentication flag */
+ mi->context.c2.context_auth = CAS_SUCCEEDED;
+
+ /* authentication complete, calculate dynamic client specific options */
+ if (!multi_client_set_protocol_options(&mi->context))
+ {
+ mi->context.c2.context_auth = CAS_FAILED;
+ }
+ /* Generate data channel keys only if setting protocol options
+ * has not failed */
+ else if (!multi_client_generate_tls_keys(&mi->context))
+ {
+ mi->context.c2.context_auth = CAS_FAILED;
+ }
+
+ /* send push reply if ready */
+ if (mi->context.c2.push_request_received)
+ {
+ process_incoming_push_request(&mi->context);
+ }
+
+ gc_free(&gc);
+}
+
+static void
+multi_client_connect_early_setup(struct multi_context *m,
+ struct multi_instance *mi)
+{
+ ASSERT(mi->context.c1.tuntap);
+ /*
+ * lock down the common name and cert hashes so they can't change
+ * during future TLS renegotiations
+ */
+ tls_lock_common_name(mi->context.c2.tls_multi);
+ tls_lock_cert_hash_set(mi->context.c2.tls_multi);
+
+ /* generate a msg() prefix for this client instance */
+ generate_prefix(mi);
+
+ /* delete instances of previous clients with same common-name */
+ if (!mi->context.options.duplicate_cn)
+ {
+ multi_delete_dup(m, mi);
+ }
+
+ /* reset pool handle to null */
+ mi->vaddr_handle = -1;
+
+ /* do --client-connect setenvs */
+ multi_select_virtual_addr(m, mi);
+
+ multi_client_connect_setenv(m, mi);
+}
+
+/**
+ * Try to source a dynamic config file from the
+ * --client-config-dir directory.
+ */
+static enum client_connect_return
+multi_client_connect_source_ccd(struct multi_context *m,
+ struct multi_instance *mi,
+ bool deferred,
+ unsigned int *option_types_found)
+{
+ /* Since we never return a CC_RET_DEFERRED, this indicates a serious
+ * problem */
+ ASSERT(!deferred);
+ enum client_connect_return ret = CC_RET_SKIPPED;
+ if (mi->context.options.client_config_dir)
+ {
+ struct gc_arena gc = gc_new();
+ const char *ccd_file = NULL;
+
+ const char *ccd_client =
+ platform_gen_path(mi->context.options.client_config_dir,
+ tls_common_name(mi->context.c2.tls_multi, false),
+ &gc);
+
+ const char *ccd_default =
+ platform_gen_path(mi->context.options.client_config_dir,
+ CCD_DEFAULT, &gc);
+
+
+ /* try common-name file */
+ if (platform_test_file(ccd_client))
{
- msg(D_MULTI_ERRORS, "MULTI: client has been rejected due to 'disable' directive");
- cc_succeeded = false;
- cc_succeeded_count = 0;
+ ccd_file = ccd_client;
+ }
+ /* try default file */
+ else if (platform_test_file(ccd_default))
+ {
+ ccd_file = ccd_default;
}
- if (cc_succeeded)
+ if (ccd_file)
{
+ options_server_import(&mi->context.options,
+ ccd_file,
+ D_IMPORT_ERRORS|M_OPTERR,
+ CLIENT_CONNECT_OPT_MASK,
+ option_types_found,
+ mi->context.c2.es);
/*
- * Process sourced options.
+ * Select a virtual address from either --ifconfig-push in
+ * --client-config-dir file or --ifconfig-pool.
*/
- do_deferred_options(&mi->context, option_types_found);
+ multi_select_virtual_addr(m, mi);
- /*
- * make sure we got ifconfig settings from somewhere
- */
- if (!mi->context.c2.push_ifconfig_defined)
- {
- msg(D_MULTI_ERRORS, "MULTI: no dynamic or static remote --ifconfig address is available for %s",
- multi_instance_string(mi, false, &gc));
- }
+ multi_client_connect_setenv(m, mi);
- /*
- * make sure that ifconfig settings comply with constraints
- */
- if (!ifconfig_push_constraint_satisfied(&mi->context))
- {
- /* JYFIXME -- this should cause the connection to fail */
- msg(D_MULTI_ERRORS, "MULTI ERROR: primary virtual IP for %s (%s) violates tunnel network/netmask constraint (%s/%s)",
- multi_instance_string(mi, false, &gc),
- print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc),
- print_in_addr_t(mi->context.options.push_ifconfig_constraint_network, 0, &gc),
- print_in_addr_t(mi->context.options.push_ifconfig_constraint_netmask, 0, &gc));
- }
+ ret = CC_RET_SUCCEEDED;
+ }
+ gc_free(&gc);
+ }
+ return ret;
+}
- /*
- * For routed tunnels, set up internal route to endpoint
- * plus add all iroute routes.
- */
- if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)
- {
- if (mi->context.c2.push_ifconfig_defined)
- {
- multi_learn_in_addr_t(m, mi, mi->context.c2.push_ifconfig_local, -1, true);
- msg(D_MULTI_LOW, "MULTI: primary virtual IP for %s: %s",
- multi_instance_string(mi, false, &gc),
- print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc));
- }
+typedef enum client_connect_return (*multi_client_connect_handler)
+ (struct multi_context *m, struct multi_instance *mi,
+ bool from_deferred, unsigned int *option_types_found);
- if (mi->context.c2.push_ifconfig_ipv6_defined)
- {
- multi_learn_in6_addr(m, mi, mi->context.c2.push_ifconfig_ipv6_local, -1, true);
- /* TODO: find out where addresses are "unlearned"!! */
- msg(D_MULTI_LOW, "MULTI: primary virtual IPv6 for %s: %s",
- multi_instance_string(mi, false, &gc),
- print_in6_addr(mi->context.c2.push_ifconfig_ipv6_local, 0, &gc));
- }
+static const multi_client_connect_handler client_connect_handlers[] = {
+ multi_client_connect_source_ccd,
+ multi_client_connect_call_plugin_v1,
+ multi_client_connect_call_plugin_v2,
+ multi_client_connect_call_script,
+ multi_client_connect_mda,
+ NULL,
+};
- /* add routes locally, pointing to new client, if
- * --iroute options have been specified */
- multi_add_iroutes(m, mi);
+/*
+ * Called as soon as the SSL/TLS connection is authenticated.
+ *
+ * Will collect the client specific configuration from the different
+ * sources like ccd files, connect plugins and management interface.
+ *
+ * This method starts with cas_context CAS_PENDING and will move the
+ * state machine to either CAS_SUCCEEDED on success or
+ * CAS_FAILED/CAS_PARTIAL on failure.
+ *
+ * Instance-specific directives to be processed (CLIENT_CONNECT_OPT_MASK)
+ * include:
+ *
+ * iroute start-ip end-ip
+ * ifconfig-push local remote-netmask
+ * push
+ *
+ *
+ */
+static void
+multi_connection_established(struct multi_context *m, struct multi_instance *mi)
+{
+ if (tls_authentication_status(mi->context.c2.tls_multi, 0)
+ != TLS_AUTHENTICATION_SUCCEEDED)
+ {
+ return;
+ }
+
+ /* We are only called for the CAS_PENDING_x states, so we
+ * can ignore other states here */
+ bool from_deferred = (mi->context.c2.context_auth != CAS_PENDING);
+ int *cur_handler_index = &mi->client_connect_defer_state.cur_handler_index;
+ unsigned int *option_types_found =
+ &mi->client_connect_defer_state.option_types_found;
+
+ /* We are called for the first time */
+ if (!from_deferred)
+ {
+ *cur_handler_index = 0;
+ *option_types_found = 0;
+ /* Initially we have no handler that has returned a result */
+ mi->context.c2.context_auth = CAS_PENDING_DEFERRED;
+
+ multi_client_connect_early_setup(m, mi);
+ }
+
+ bool cc_succeeded = true;
+
+ while (cc_succeeded
+ && client_connect_handlers[*cur_handler_index] != NULL)
+ {
+ enum client_connect_return ret;
+ ret = client_connect_handlers[*cur_handler_index](m, mi, from_deferred,
+ option_types_found);
+
+ from_deferred = false;
+
+ switch (ret)
+ {
+ case CC_RET_SUCCEEDED:
/*
- * iroutes represent subnets which are "owned" by a particular
- * client. Therefore, do not actually push a route to a client
- * if it matches one of the client's iroutes.
+ * Remember that we already had at least one handler
+ * returning a result should we go to into deferred state
*/
- remove_iroutes_from_push_route_list(&mi->context.options);
- }
- else if (mi->context.options.iroutes)
- {
- msg(D_MULTI_ERRORS, "MULTI: --iroute options rejected for %s -- iroute only works with tun-style tunnels",
- multi_instance_string(mi, false, &gc));
- }
+ mi->context.c2.context_auth = CAS_PENDING_DEFERRED_PARTIAL;
+ break;
- /* set our client's VPN endpoint for status reporting purposes */
- mi->reporting_addr = mi->context.c2.push_ifconfig_local;
- mi->reporting_addr_ipv6 = mi->context.c2.push_ifconfig_ipv6_local;
+ case CC_RET_SKIPPED:
+ /*
+ * Move on with the next handler without modifying any
+ * other state
+ */
+ break;
- /* set context-level authentication flag */
- mi->context.c2.context_auth = CAS_SUCCEEDED;
+ case CC_RET_DEFERRED:
+ /*
+ * we already set client_connect_status to DEFERRED_RESULT or
+ * DEFERRED_NO_RESULT. We just return
+ * from the function as having client_connect_status
+ */
+ return;
-#ifdef ENABLE_ASYNC_PUSH
- /* authentication complete, send push reply */
- if (mi->context.c2.push_request_received)
- {
- process_incoming_push_request(&mi->context);
- }
-#endif
+ case CC_RET_FAILED:
+ /*
+ * One handler failed. We abort the chain and set the final
+ * result to failed
+ */
+ cc_succeeded = false;
+ break;
+
+ default:
+ ASSERT(0);
}
- else
+
+ /*
+ * Check for "disable" directive in client-config-dir file
+ * or config file generated by --client-connect script.
+ */
+ if (mi->context.options.disable)
{
- /* set context-level authentication flag */
- mi->context.c2.context_auth = cc_succeeded_count ? CAS_PARTIAL : CAS_FAILED;
+ msg(D_MULTI_ERRORS, "MULTI: client has been rejected due to "
+ "'disable' directive");
+ cc_succeeded = false;
}
- /* set flag so we don't get called again */
- mi->connection_established_flag = true;
-
- /* increment number of current authenticated clients */
- ++m->n_clients;
- update_mstat_n_clients(m->n_clients);
- --mi->n_clients_delta;
+ (*cur_handler_index)++;
+ }
-#ifdef MANAGEMENT_DEF_AUTH
- if (management)
+ if (cc_succeeded)
+ {
+ multi_client_connect_late_setup(m, mi, *option_types_found);
+ }
+ else
+ {
+ /* run the disconnect script if we had a connect script that
+ * did not fail */
+ if (mi->context.c2.context_auth == CAS_PENDING_DEFERRED_PARTIAL)
{
- management_connection_established(management, &mi->context.c2.mda_context, mi->context.c2.es);
+ multi_client_disconnect_script(mi);
}
-#endif
- gc_free(&gc);
+ mi->context.c2.context_auth = CAS_FAILED;
}
- /*
- * Reply now to client's PUSH_REQUEST query
- */
- mi->context.c2.push_reply_deferred = false;
+ /* increment number of current authenticated clients */
+ ++m->n_clients;
+ update_mstat_n_clients(m->n_clients);
+ --mi->n_clients_delta;
+
+#ifdef MANAGEMENT_DEF_AUTH
+ if (management)
+ {
+ management_connection_established(management,
+ &mi->context.c2.mda_context, mi->context.c2.es);
+ }
+#endif
}
#ifdef ENABLE_ASYNC_PUSH
/*
- * Called when inotify event is fired, which happens when acf file is closed or deleted.
- * Continues authentication and sends push_reply.
+ * Called when inotify event is fired, which happens when acf
+ * or connect-status file is closed or deleted.
+ * Continues authentication and sends push_reply
+ * (or be deferred again by client-connect)
*/
void
multi_process_file_closed(struct multi_context *m, const unsigned int mpp_flags)
@@ -2134,28 +2735,6 @@ multi_process_file_closed(struct multi_context *m, const unsigned int mpp_flags)
{
/* continue authentication, perform NCP negotiation and send push_reply */
multi_process_post(m, mi, mpp_flags);
-
- /* With NCP and deferred authentication, we perform cipher negotiation and
- * data channel keys generation on incoming push request, assuming that auth
- * succeeded. When auth succeeds in between push requests and async push is used,
- * we send push reply immediately. Above multi_process_post() call performs
- * NCP negotiation and here we do keys generation. */
-
- struct context *c = &mi->context;
- struct frame *frame_fragment = NULL;
-#ifdef ENABLE_FRAGMENT
- if (c->options.ce.fragment)
- {
- frame_fragment = &c->c2.frame_fragment;
- }
-#endif
- struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
- if (!tls_session_update_crypto_params(session, &c->options,
- &c->c2.frame, frame_fragment))
- {
- msg(D_TLS_ERRORS, "TLS Error: initializing data channel failed");
- register_signal(c, SIGUSR1, "init-data-channel-failed");
- }
}
else
{
@@ -2227,7 +2806,8 @@ static void
multi_bcast(struct multi_context *m,
const struct buffer *buf,
const struct multi_instance *sender_instance,
- const struct mroute_addr *sender_addr)
+ const struct mroute_addr *sender_addr,
+ uint16_t vid)
{
struct hash_iterator hi;
struct hash_element *he;
@@ -2251,7 +2831,11 @@ multi_bcast(struct multi_context *m,
#ifdef ENABLE_PF
if (sender_instance)
{
- if (!pf_c2c_test(&sender_instance->context, &mi->context, "bcast_c2c"))
+ if (!pf_c2c_test(&sender_instance->context.c2.pf,
+ sender_instance->context.c2.tls_multi,
+ &mi->context.c2.pf,
+ mi->context.c2.tls_multi,
+ "bcast_c2c"))
{
msg(D_PF_DROPPED_BCAST, "PF: client[%s] -> client[%s] packet dropped by BCAST packet filter",
mi_prefix(sender_instance),
@@ -2261,7 +2845,8 @@ multi_bcast(struct multi_context *m,
}
if (sender_addr)
{
- if (!pf_addr_test(&mi->context, sender_addr, "bcast_src_addr"))
+ if (!pf_addr_test(&mi->context.c2.pf, &mi->context,
+ sender_addr, "bcast_src_addr"))
{
struct gc_arena gc = gc_new();
msg(D_PF_DROPPED_BCAST, "PF: addr[%s] -> client[%s] packet dropped by BCAST packet filter",
@@ -2272,6 +2857,10 @@ multi_bcast(struct multi_context *m,
}
}
#endif /* ifdef ENABLE_PF */
+ if (vid != 0 && vid != mi->context.options.vlan_pvid)
+ {
+ continue;
+ }
multi_add_mbuf(m, mi, mb);
}
}
@@ -2329,6 +2918,32 @@ multi_schedule_context_wakeup(struct multi_context *m, struct multi_instance *mi
compute_wakeup_sigma(&mi->context.c2.timeval));
}
+#if defined(ENABLE_ASYNC_PUSH) && defined(ENABLE_DEF_AUTH)
+static void
+add_inotify_file_watch(struct multi_context *m, struct multi_instance *mi,
+ int inotify_fd, const char *file)
+{
+ /* watch acf file */
+ long watch_descriptor = inotify_add_watch(inotify_fd, file,
+ IN_CLOSE_WRITE | IN_ONESHOT);
+ if (watch_descriptor >= 0)
+ {
+ if (mi->inotify_watch != -1)
+ {
+ hash_remove(m->inotify_watchers,
+ (void *) (unsigned long)mi->inotify_watch);
+ }
+ hash_add(m->inotify_watchers, (const uintptr_t *)watch_descriptor,
+ mi, true);
+ mi->inotify_watch = watch_descriptor;
+ }
+ else
+ {
+ msg(M_NONFATAL | M_ERRNO, "MULTI: inotify_add_watch error");
+ }
+}
+#endif /* if defined(ENABLE_ASYNC_PUSH) && defined(ENABLE_DEF_AUTH) */
+
/*
* Figure instance-specific timers, convert
* earliest to absolute time in mi->wakeup,
@@ -2344,12 +2959,12 @@ multi_process_post(struct multi_context *m, struct multi_instance *mi, const uns
if (!IS_SIG(&mi->context) && ((flags & MPP_PRE_SELECT) || ((flags & MPP_CONDITIONAL_PRE_SELECT) && !ANY_OUT(&mi->context))))
{
#if defined(ENABLE_ASYNC_PUSH) && defined(ENABLE_DEF_AUTH)
- bool was_authenticated = false;
+ bool was_unauthenticated = true;
struct key_state *ks = NULL;
if (mi->context.c2.tls_multi)
{
ks = &mi->context.c2.tls_multi->session[TM_ACTIVE].key[KS_PRIMARY];
- was_authenticated = ks->authenticated;
+ was_unauthenticated = (ks->authenticated == KS_AUTH_FALSE);
}
#endif
@@ -2358,23 +2973,16 @@ multi_process_post(struct multi_context *m, struct multi_instance *mi, const uns
pre_select(&mi->context);
#if defined(ENABLE_ASYNC_PUSH) && defined(ENABLE_DEF_AUTH)
- if (ks && ks->auth_control_file && ks->auth_deferred && !was_authenticated)
+ /*
+ * if we see the state transition from unauthenticated to deferred
+ * and an auth_control_file, we assume it got just added and add
+ * inotify watch to that file
+ */
+ if (ks && ks->auth_control_file && was_unauthenticated
+ && (ks->authenticated == KS_AUTH_DEFERRED))
{
- /* watch acf file */
- long watch_descriptor = inotify_add_watch(m->top.c2.inotify_fd, ks->auth_control_file, IN_CLOSE_WRITE | IN_ONESHOT);
- if (watch_descriptor >= 0)
- {
- if (mi->inotify_watch != -1)
- {
- hash_remove(m->inotify_watchers, (void *) (unsigned long)mi->inotify_watch);
- }
- hash_add(m->inotify_watchers, (const uintptr_t *)watch_descriptor, mi, true);
- mi->inotify_watch = watch_descriptor;
- }
- else
- {
- msg(M_NONFATAL | M_ERRNO, "MULTI: inotify_add_watch error");
- }
+ add_inotify_file_watch(m, mi, m->top.c2.inotify_fd,
+ ks->auth_control_file);
}
#endif
@@ -2382,11 +2990,20 @@ multi_process_post(struct multi_context *m, struct multi_instance *mi, const uns
{
/* connection is "established" when SSL/TLS key negotiation succeeds
* and (if specified) auth user/pass succeeds */
- if (!mi->connection_established_flag && CONNECTION_ESTABLISHED(&mi->context))
+ if (is_cas_pending(mi->context.c2.context_auth)
+ && CONNECTION_ESTABLISHED(&mi->context))
{
multi_connection_established(m, mi);
}
-
+#if defined(ENABLE_ASYNC_PUSH) && defined(ENABLE_DEF_AUTH)
+ if (is_cas_pending(mi->context.c2.context_auth)
+ && mi->client_connect_defer_state.deferred_ret_file)
+ {
+ add_inotify_file_watch(m, mi, m->top.c2.inotify_fd,
+ mi->client_connect_defer_state.
+ deferred_ret_file);
+ }
+#endif
/* tell scheduler to wake us up at some point in the future */
multi_schedule_context_wakeup(m, mi);
}
@@ -2406,14 +3023,14 @@ multi_process_post(struct multi_context *m, struct multi_instance *mi, const uns
multi_set_pending(m, ANY_OUT(&mi->context) ? mi : NULL);
#ifdef MULTI_DEBUG_EVENT_LOOP
- printf("POST %s[%d] to=%d lo=%d/%d w=%d/%d\n",
+ printf("POST %s[%d] to=%d lo=%d/%d w=%" PRIi64 "/%ld\n",
id(mi),
(int) (mi == m->pending),
mi ? mi->context.c2.to_tun.len : -1,
mi ? mi->context.c2.to_link.len : -1,
(mi && mi->context.c2.fragment) ? mi->context.c2.fragment->outgoing.len : -1,
- (int)mi->context.c2.timeval.tv_sec,
- (int)mi->context.c2.timeval.tv_usec);
+ (int64_t)mi->context.c2.timeval.tv_sec,
+ (long)mi->context.c2.timeval.tv_usec);
#endif
}
@@ -2579,6 +3196,7 @@ multi_process_incoming_link(struct multi_context *m, struct multi_instance *inst
&dest,
NULL,
NULL,
+ 0,
&c->c2.to_tun,
DEV_TYPE_TUN);
@@ -2610,7 +3228,7 @@ multi_process_incoming_link(struct multi_context *m, struct multi_instance *inst
if (mroute_flags & MROUTE_EXTRACT_MCAST)
{
/* for now, treat multicast as broadcast */
- multi_bcast(m, &c->c2.to_tun, m->pending, NULL);
+ multi_bcast(m, &c->c2.to_tun, m->pending, NULL, 0);
}
else /* possible client to client routing */
{
@@ -2621,7 +3239,10 @@ multi_process_incoming_link(struct multi_context *m, struct multi_instance *inst
if (mi)
{
#ifdef ENABLE_PF
- if (!pf_c2c_test(c, &mi->context, "tun_c2c"))
+ if (!pf_c2c_test(&c->c2.pf, c->c2.tls_multi,
+ &mi->context.c2.pf,
+ mi->context.c2.tls_multi,
+ "tun_c2c"))
{
msg(D_PF_DROPPED, "PF: client -> client[%s] packet dropped by TUN packet filter",
mi_prefix(mi));
@@ -2637,7 +3258,8 @@ multi_process_incoming_link(struct multi_context *m, struct multi_instance *inst
}
}
#ifdef ENABLE_PF
- if (c->c2.to_tun.len && !pf_addr_test(c, &dest, "tun_dest_addr"))
+ if (c->c2.to_tun.len && !pf_addr_test(&c->c2.pf, c, &dest,
+ "tun_dest_addr"))
{
msg(D_PF_DROPPED, "PF: client -> addr[%s] packet dropped by TUN packet filter",
mroute_addr_print_ex(&dest, MAPF_SHOW_ARP, &gc));
@@ -2647,10 +3269,25 @@ multi_process_incoming_link(struct multi_context *m, struct multi_instance *inst
}
else if (TUNNEL_TYPE(m->top.c1.tuntap) == DEV_TYPE_TAP)
{
+ uint16_t vid = 0;
#ifdef ENABLE_PF
struct mroute_addr edest;
mroute_addr_reset(&edest);
#endif
+
+ if (m->top.options.vlan_tagging)
+ {
+ if (vlan_is_tagged(&c->c2.to_tun))
+ {
+ /* Drop VLAN-tagged frame. */
+ msg(D_VLAN_DEBUG, "dropping incoming VLAN-tagged frame");
+ c->c2.to_tun.len = 0;
+ }
+ else
+ {
+ vid = c->options.vlan_pvid;
+ }
+ }
/* extract packet source and dest addresses */
mroute_flags = mroute_extract_addr_from_packet(&src,
&dest,
@@ -2660,6 +3297,7 @@ multi_process_incoming_link(struct multi_context *m, struct multi_instance *inst
#else
NULL,
#endif
+ vid,
&c->c2.to_tun,
DEV_TYPE_TAP);
@@ -2672,7 +3310,8 @@ multi_process_incoming_link(struct multi_context *m, struct multi_instance *inst
{
if (mroute_flags & (MROUTE_EXTRACT_BCAST|MROUTE_EXTRACT_MCAST))
{
- multi_bcast(m, &c->c2.to_tun, m->pending, NULL);
+ multi_bcast(m, &c->c2.to_tun, m->pending, NULL,
+ vid);
}
else /* try client-to-client routing */
{
@@ -2682,7 +3321,10 @@ multi_process_incoming_link(struct multi_context *m, struct multi_instance *inst
if (mi)
{
#ifdef ENABLE_PF
- if (!pf_c2c_test(c, &mi->context, "tap_c2c"))
+ if (!pf_c2c_test(&c->c2.pf, c->c2.tls_multi,
+ &mi->context.c2.pf,
+ mi->context.c2.tls_multi,
+ "tap_c2c"))
{
msg(D_PF_DROPPED, "PF: client -> client[%s] packet dropped by TAP packet filter",
mi_prefix(mi));
@@ -2698,7 +3340,9 @@ multi_process_incoming_link(struct multi_context *m, struct multi_instance *inst
}
}
#ifdef ENABLE_PF
- if (c->c2.to_tun.len && !pf_addr_test(c, &edest, "tap_dest_addr"))
+ if (c->c2.to_tun.len && !pf_addr_test(&c->c2.pf, c,
+ &edest,
+ "tap_dest_addr"))
{
msg(D_PF_DROPPED, "PF: client -> addr[%s] packet dropped by TAP packet filter",
mroute_addr_print_ex(&edest, MAPF_SHOW_ARP, &gc));
@@ -2745,6 +3389,7 @@ multi_process_incoming_tun(struct multi_context *m, const unsigned int mpp_flags
unsigned int mroute_flags;
struct mroute_addr src, dest;
const int dev_type = TUNNEL_TYPE(m->top.c1.tuntap);
+ int16_t vid = 0;
#ifdef ENABLE_PF
struct mroute_addr esrc, *e1, *e2;
@@ -2769,6 +3414,15 @@ multi_process_incoming_tun(struct multi_context *m, const unsigned int mpp_flags
return true;
}
+ if (dev_type == DEV_TYPE_TAP && m->top.options.vlan_tagging)
+ {
+ vid = vlan_decapsulate(&m->top, &m->top.c2.buf);
+ if (vid < 0)
+ {
+ return false;
+ }
+ }
+
/*
* Route an incoming tun/tap packet to
* the appropriate multi_instance object.
@@ -2782,6 +3436,7 @@ multi_process_incoming_tun(struct multi_context *m, const unsigned int mpp_flags
NULL,
#endif
NULL,
+ vid,
&m->top.c2.buf,
dev_type);
@@ -2794,9 +3449,9 @@ multi_process_incoming_tun(struct multi_context *m, const unsigned int mpp_flags
{
/* for now, treat multicast as broadcast */
#ifdef ENABLE_PF
- multi_bcast(m, &m->top.c2.buf, NULL, e2);
+ multi_bcast(m, &m->top.c2.buf, NULL, e2, vid);
#else
- multi_bcast(m, &m->top.c2.buf, NULL, NULL);
+ multi_bcast(m, &m->top.c2.buf, NULL, NULL, vid);
#endif
}
else
@@ -2811,7 +3466,7 @@ multi_process_incoming_tun(struct multi_context *m, const unsigned int mpp_flags
set_prefix(m->pending);
#ifdef ENABLE_PF
- if (!pf_addr_test(c, e2, "tun_tap_src_addr"))
+ if (!pf_addr_test(&c->c2.pf, c, e2, "tun_tap_src_addr"))
{
msg(D_PF_DROPPED, "PF: addr[%s] -> client packet dropped by packet filter",
mroute_addr_print_ex(&src, MAPF_SHOW_ARP, &gc));
@@ -2859,7 +3514,7 @@ multi_get_queue(struct mbuf_set *ms)
if (mbuf_extract_item(ms, &item)) /* cleartext IP packet */
{
- unsigned int pip_flags = PIPV4_PASSTOS;
+ unsigned int pip_flags = PIPV4_PASSTOS | PIPV6_IMCP_NOHOST_SERVER;
set_prefix(item.instance);
item.instance->context.c2.buf = item.buffer->buf;
@@ -2978,7 +3633,7 @@ gremlin_flood_clients(struct multi_context *m)
for (i = 0; i < parm.n_packets; ++i)
{
- multi_bcast(m, &buf, NULL, NULL);
+ multi_bcast(m, &buf, NULL, NULL, 0);
}
gc_free(&gc);
@@ -3260,6 +3915,24 @@ management_kill_by_cid(void *arg, const unsigned long cid, const char *kill_msg)
}
static bool
+management_client_pending_auth(void *arg,
+ const unsigned long cid,
+ const char *extra)
+{
+ struct multi_context *m = (struct multi_context *) arg;
+ struct multi_instance *mi = lookup_by_cid(m, cid);
+ if (mi)
+ {
+ /* sends INFO_PRE and AUTH_PENDING messages to client */
+ bool ret = send_auth_pending_messages(&mi->context, extra);
+ multi_schedule_context_wakeup(m, mi);
+ return ret;
+ }
+ return false;
+}
+
+
+static bool
management_client_auth(void *arg,
const unsigned long cid,
const unsigned int mda_key_id,
@@ -3280,7 +3953,7 @@ management_client_auth(void *arg,
{
if (auth)
{
- if (!mi->connection_established_flag)
+ if (is_cas_pending(mi->context.c2.context_auth))
{
set_cc_config(mi, cc_config);
cc_config_owned = false;
@@ -3292,7 +3965,7 @@ management_client_auth(void *arg,
{
msg(D_MULTI_LOW, "MULTI: connection rejected: %s, CLI:%s", reason, np(client_reason));
}
- if (mi->connection_established_flag)
+ if (!is_cas_pending(mi->context.c2.context_auth))
{
send_auth_failed(&mi->context, client_reason); /* mid-session reauth failed */
multi_schedule_context_wakeup(m, mi);
@@ -3366,6 +4039,7 @@ init_management_callback_multi(struct multi_context *m)
#ifdef MANAGEMENT_DEF_AUTH
cb.kill_by_cid = management_kill_by_cid;
cb.client_auth = management_client_auth;
+ cb.client_pending_auth = management_client_pending_auth;
cb.get_peer_info = management_get_peer_info;
#endif
#ifdef MANAGEMENT_PF
@@ -3393,10 +4067,3 @@ tunnel_server(struct context *top)
tunnel_server_tcp(top);
}
}
-
-#else /* if P2MP_SERVER */
-static void
-dummy(void)
-{
-}
-#endif /* P2MP_SERVER */