diff options
author | Bernhard Schmidt <berni@debian.org> | 2020-09-01 16:52:17 +0200 |
---|---|---|
committer | Bernhard Schmidt <berni@debian.org> | 2020-09-01 16:52:17 +0200 |
commit | 9fc3b98112217f2d92a67977dbde0987cc7a1803 (patch) | |
tree | 29fcc8654ee65d9dd89ade797bea2f3d9dfd9cfd /src/openvpn/multi.c | |
parent | a8758c0e03eed188dcb9da0e4fd781a67c25bf1e (diff) | |
parent | 69b02b1f7fd609d84ace13ab04697158de2418a9 (diff) |
Merge branch 'debian/experimental-2.5'
Diffstat (limited to 'src/openvpn/multi.c')
-rw-r--r-- | src/openvpn/multi.c | 1473 |
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 */ |