| /* |
| * OpenVPN -- An application to securely tunnel IP networks |
| * over a single TCP/UDP port, with support for SSL/TLS-based |
| * session authentication and key exchange, |
| * packet encryption, packet authentication, and |
| * packet compression. |
| * |
| * Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 |
| * as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #elif defined(_MSC_VER) |
| #include "config-msvc.h" |
| #endif |
| |
| #ifdef HAVE_SYS_INOTIFY_H |
| #include <sys/inotify.h> |
| #define INOTIFY_EVENT_BUFFER_SIZE 16384 |
| #endif |
| |
| #include "syshead.h" |
| |
| #include "forward.h" |
| #include "multi.h" |
| #include "push.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 "crypto_backend.h" |
| |
| /*#define MULTI_DEBUG_EVENT_LOOP*/ |
| |
| #ifdef MULTI_DEBUG_EVENT_LOOP |
| static const char * |
| id(struct multi_instance *mi) |
| { |
| if (mi) |
| { |
| return tls_common_name(mi->context.c2.tls_multi, false); |
| } |
| else |
| { |
| return "NULL"; |
| } |
| } |
| #endif |
| |
| #ifdef MANAGEMENT_DEF_AUTH |
| static void |
| set_cc_config(struct multi_instance *mi, struct buffer_list *cc_config) |
| { |
| if (mi->cc_config) |
| { |
| buffer_list_free(mi->cc_config); |
| } |
| mi->cc_config = cc_config; |
| } |
| #endif |
| |
| static inline void |
| update_mstat_n_clients(const int n_clients) |
| { |
| #ifdef ENABLE_MEMSTATS |
| if (mmap_stats) |
| { |
| mmap_stats->n_clients = n_clients; |
| } |
| #endif |
| } |
| |
| static bool |
| learn_address_script(const struct multi_context *m, |
| const struct multi_instance *mi, |
| const char *op, |
| const struct mroute_addr *addr) |
| { |
| struct gc_arena gc = gc_new(); |
| struct env_set *es; |
| bool ret = true; |
| struct plugin_list *plugins; |
| |
| /* get environmental variable source */ |
| if (mi && mi->context.c2.es) |
| { |
| es = mi->context.c2.es; |
| } |
| else |
| { |
| es = env_set_create(&gc); |
| } |
| |
| /* get plugin source */ |
| if (mi) |
| { |
| plugins = mi->context.plugins; |
| } |
| else |
| { |
| plugins = m->top.plugins; |
| } |
| |
| if (plugin_defined(plugins, OPENVPN_PLUGIN_LEARN_ADDRESS)) |
| { |
| struct argv argv = argv_new(); |
| argv_printf(&argv, "%s %s", |
| op, |
| mroute_addr_print(addr, &gc)); |
| if (mi) |
| { |
| argv_printf_cat(&argv, "%s", tls_common_name(mi->context.c2.tls_multi, false)); |
| } |
| if (plugin_call(plugins, OPENVPN_PLUGIN_LEARN_ADDRESS, &argv, NULL, es) != OPENVPN_PLUGIN_FUNC_SUCCESS) |
| { |
| msg(M_WARN, "WARNING: learn-address plugin call failed"); |
| ret = false; |
| } |
| argv_free(&argv); |
| } |
| |
| if (m->top.options.learn_address_script) |
| { |
| struct argv argv = argv_new(); |
| setenv_str(es, "script_type", "learn-address"); |
| argv_parse_cmd(&argv, m->top.options.learn_address_script); |
| argv_printf_cat(&argv, "%s %s", op, mroute_addr_print(addr, &gc)); |
| if (mi) |
| { |
| argv_printf_cat(&argv, "%s", tls_common_name(mi->context.c2.tls_multi, false)); |
| } |
| if (!openvpn_run_script(&argv, es, 0, "--learn-address")) |
| { |
| ret = false; |
| } |
| argv_free(&argv); |
| } |
| |
| gc_free(&gc); |
| return ret; |
| } |
| |
| void |
| multi_ifconfig_pool_persist(struct multi_context *m, bool force) |
| { |
| /* write pool data to file */ |
| if (m->ifconfig_pool |
| && m->top.c1.ifconfig_pool_persist |
| && (force || ifconfig_pool_write_trigger(m->top.c1.ifconfig_pool_persist))) |
| { |
| ifconfig_pool_write(m->top.c1.ifconfig_pool_persist, m->ifconfig_pool); |
| } |
| } |
| |
| static void |
| multi_reap_range(const struct multi_context *m, |
| int start_bucket, |
| int end_bucket) |
| { |
| struct gc_arena gc = gc_new(); |
| struct hash_iterator hi; |
| struct hash_element *he; |
| |
| if (start_bucket < 0) |
| { |
| start_bucket = 0; |
| end_bucket = hash_n_buckets(m->vhash); |
| } |
| |
| dmsg(D_MULTI_DEBUG, "MULTI: REAP range %d -> %d", start_bucket, end_bucket); |
| hash_iterator_init_range(m->vhash, &hi, start_bucket, end_bucket); |
| while ((he = hash_iterator_next(&hi)) != NULL) |
| { |
| struct multi_route *r = (struct multi_route *) he->value; |
| if (!multi_route_defined(m, r)) |
| { |
| dmsg(D_MULTI_DEBUG, "MULTI: REAP DEL %s", |
| mroute_addr_print(&r->addr, &gc)); |
| learn_address_script(m, NULL, "delete", &r->addr); |
| multi_route_del(r); |
| hash_iterator_delete_element(&hi); |
| } |
| } |
| hash_iterator_free(&hi); |
| gc_free(&gc); |
| } |
| |
| static void |
| multi_reap_all(const struct multi_context *m) |
| { |
| multi_reap_range(m, -1, 0); |
| } |
| |
| static struct multi_reap * |
| multi_reap_new(int buckets_per_pass) |
| { |
| struct multi_reap *mr; |
| ALLOC_OBJ(mr, struct multi_reap); |
| mr->bucket_base = 0; |
| mr->buckets_per_pass = buckets_per_pass; |
| mr->last_call = now; |
| return mr; |
| } |
| |
| void |
| multi_reap_process_dowork(const struct multi_context *m) |
| { |
| struct multi_reap *mr = m->reaper; |
| if (mr->bucket_base >= hash_n_buckets(m->vhash)) |
| { |
| mr->bucket_base = 0; |
| } |
| multi_reap_range(m, mr->bucket_base, mr->bucket_base + mr->buckets_per_pass); |
| mr->bucket_base += mr->buckets_per_pass; |
| mr->last_call = now; |
| } |
| |
| static void |
| multi_reap_free(struct multi_reap *mr) |
| { |
| free(mr); |
| } |
| |
| /* |
| * How many buckets in vhash to reap per pass. |
| */ |
| static int |
| reap_buckets_per_pass(int n_buckets) |
| { |
| return constrain_int(n_buckets / REAP_DIVISOR, REAP_MIN, REAP_MAX); |
| } |
| |
| #ifdef MANAGEMENT_DEF_AUTH |
| |
| static uint32_t |
| cid_hash_function(const void *key, uint32_t iv) |
| { |
| const unsigned long *k = (const unsigned long *)key; |
| return (uint32_t) *k; |
| } |
| |
| static bool |
| cid_compare_function(const void *key1, const void *key2) |
| { |
| const unsigned long *k1 = (const unsigned long *)key1; |
| const unsigned long *k2 = (const unsigned long *)key2; |
| return *k1 == *k2; |
| } |
| |
| #endif |
| |
| #ifdef ENABLE_ASYNC_PUSH |
| static uint32_t |
| /* |
| * inotify watcher descriptors are used as hash value |
| */ |
| int_hash_function(const void *key, uint32_t iv) |
| { |
| return (unsigned long)key; |
| } |
| |
| static bool |
| int_compare_function(const void *key1, const void *key2) |
| { |
| return (unsigned long)key1 == (unsigned long)key2; |
| } |
| #endif |
| |
| /* |
| * Main initialization function, init multi_context object. |
| */ |
| void |
| multi_init(struct multi_context *m, struct context *t, bool tcp_mode, int thread_mode) |
| { |
| int dev = DEV_TYPE_UNDEF; |
| |
| msg(D_MULTI_LOW, "MULTI: multi_init called, r=%d v=%d", |
| t->options.real_hash_size, |
| t->options.virtual_hash_size); |
| |
| /* |
| * Get tun/tap/null device type |
| */ |
| dev = dev_type_enum(t->options.dev, t->options.dev_type); |
| |
| /* |
| * Init our multi_context object. |
| */ |
| CLEAR(*m); |
| |
| m->thread_mode = thread_mode; |
| |
| /* |
| * Real address hash table (source port number is |
| * considered to be part of the address). Used |
| * to determine which client sent an incoming packet |
| * which is seen on the TCP/UDP socket. |
| */ |
| m->hash = hash_init(t->options.real_hash_size, |
| get_random(), |
| mroute_addr_hash_function, |
| mroute_addr_compare_function); |
| |
| /* |
| * Virtual address hash table. Used to determine |
| * which client to route a packet to. |
| */ |
| m->vhash = hash_init(t->options.virtual_hash_size, |
| get_random(), |
| mroute_addr_hash_function, |
| mroute_addr_compare_function); |
| |
| /* |
| * This hash table is a clone of m->hash but with a |
| * bucket size of one so that it can be used |
| * for fast iteration through the list. |
| */ |
| m->iter = hash_init(1, |
| get_random(), |
| mroute_addr_hash_function, |
| mroute_addr_compare_function); |
| |
| #ifdef MANAGEMENT_DEF_AUTH |
| m->cid_hash = hash_init(t->options.real_hash_size, |
| 0, |
| cid_hash_function, |
| cid_compare_function); |
| #endif |
| |
| #ifdef ENABLE_ASYNC_PUSH |
| /* |
| * Mapping between inotify watch descriptors and |
| * multi_instances. |
| */ |
| m->inotify_watchers = hash_init(t->options.real_hash_size, |
| get_random(), |
| int_hash_function, |
| int_compare_function); |
| #endif |
| |
| /* |
| * This is our scheduler, for time-based wakeup |
| * events. |
| */ |
| m->schedule = schedule_init(); |
| |
| /* |
| * Limit frequency of incoming connections to control |
| * DoS. |
| */ |
| m->new_connection_limiter = frequency_limit_init(t->options.cf_max, |
| t->options.cf_per); |
| |
| /* |
| * Allocate broadcast/multicast buffer list |
| */ |
| m->mbuf = mbuf_init(t->options.n_bcast_buf); |
| |
| /* |
| * Different status file format options are available |
| */ |
| m->status_file_version = t->options.status_file_version; |
| |
| /* |
| * Possibly allocate an ifconfig pool, do it |
| * differently based on whether a tun or tap style |
| * tunnel. |
| */ |
| if (t->options.ifconfig_pool_defined |
| || t->options.ifconfig_ipv6_pool_defined) |
| { |
| int pool_type = IFCONFIG_POOL_INDIV; |
| |
| if (dev == DEV_TYPE_TUN && t->options.topology == TOP_NET30) |
| { |
| pool_type = IFCONFIG_POOL_30NET; |
| } |
| |
| 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, |
| t->options.ifconfig_ipv6_pool_defined, |
| t->options.ifconfig_ipv6_pool_base, |
| t->options.ifconfig_ipv6_pool_netbits ); |
| |
| /* reload pool data from file */ |
| if (t->c1.ifconfig_pool_persist) |
| { |
| ifconfig_pool_read(t->c1.ifconfig_pool_persist, m->ifconfig_pool); |
| } |
| } |
| |
| /* |
| * Help us keep track of routing table. |
| */ |
| m->route_helper = mroute_helper_init(MULTI_CACHE_ROUTE_TTL); |
| |
| /* |
| * Initialize route and instance reaper. |
| */ |
| m->reaper = multi_reap_new(reap_buckets_per_pass(t->options.virtual_hash_size)); |
| |
| /* |
| * Get local ifconfig address |
| */ |
| CLEAR(m->local); |
| ASSERT(t->c1.tuntap); |
| mroute_extract_in_addr_t(&m->local, t->c1.tuntap->local); |
| |
| /* |
| * Per-client limits |
| */ |
| m->max_clients = t->options.max_clients; |
| |
| m->instances = calloc(m->max_clients, sizeof(struct multi_instance *)); |
| |
| /* |
| * Initialize multi-socket TCP I/O wait object |
| */ |
| if (tcp_mode) |
| { |
| m->mtcp = multi_tcp_init(t->options.max_clients, &m->max_clients); |
| } |
| m->tcp_queue_limit = t->options.tcp_queue_limit; |
| |
| /* |
| * Allow client <-> client communication, without going through |
| * tun/tap interface and network stack? |
| */ |
| m->enable_c2c = t->options.enable_c2c; |
| |
| /* initialize stale routes check timer */ |
| if (t->options.stale_routes_check_interval > 0) |
| { |
| msg(M_INFO, "Initializing stale route check timer to run every %i seconds and to removing routes with activity timeout older than %i seconds", |
| t->options.stale_routes_check_interval, t->options.stale_routes_ageing_time); |
| event_timeout_init(&m->stale_routes_check_et, t->options.stale_routes_check_interval, 0); |
| } |
| |
| m->deferred_shutdown_signal.signal_received = 0; |
| } |
| |
| const char * |
| multi_instance_string(const struct multi_instance *mi, bool null, struct gc_arena *gc) |
| { |
| if (mi) |
| { |
| struct buffer out = alloc_buf_gc(MULTI_PREFIX_MAX_LENGTH, gc); |
| const char *cn = tls_common_name(mi->context.c2.tls_multi, true); |
| |
| if (cn) |
| { |
| buf_printf(&out, "%s/", cn); |
| } |
| buf_printf(&out, "%s", mroute_addr_print(&mi->real, gc)); |
| return BSTR(&out); |
| } |
| else if (null) |
| { |
| return NULL; |
| } |
| else |
| { |
| return "UNDEF"; |
| } |
| } |
| |
| static void |
| generate_prefix(struct multi_instance *mi) |
| { |
| struct gc_arena gc = gc_new(); |
| const char *prefix = multi_instance_string(mi, true, &gc); |
| if (prefix) |
| { |
| strncpynt(mi->msg_prefix, prefix, sizeof(mi->msg_prefix)); |
| } |
| else |
| { |
| mi->msg_prefix[0] = '\0'; |
| } |
| set_prefix(mi); |
| gc_free(&gc); |
| } |
| |
| void |
| ungenerate_prefix(struct multi_instance *mi) |
| { |
| mi->msg_prefix[0] = '\0'; |
| set_prefix(mi); |
| } |
| |
| static const char * |
| mi_prefix(const struct multi_instance *mi) |
| { |
| if (mi && mi->msg_prefix[0]) |
| { |
| return mi->msg_prefix; |
| } |
| else |
| { |
| return "UNDEF_I"; |
| } |
| } |
| |
| /* |
| * Tell the route helper about deleted iroutes so |
| * that it can update its mask of currently used |
| * CIDR netlengths. |
| */ |
| static void |
| multi_del_iroutes(struct multi_context *m, |
| struct multi_instance *mi) |
| { |
| const struct iroute *ir; |
| const struct iroute_ipv6 *ir6; |
| if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN) |
| { |
| for (ir = mi->context.options.iroutes; ir != NULL; ir = ir->next) |
| { |
| mroute_helper_del_iroute46(m->route_helper, ir->netbits); |
| } |
| |
| for (ir6 = mi->context.options.iroutes_ipv6; ir6 != NULL; ir6 = ir6->next) |
| { |
| mroute_helper_del_iroute46(m->route_helper, ir6->netbits); |
| } |
| } |
| } |
| |
| static void |
| setenv_stats(struct context *c) |
| { |
| setenv_counter(c->c2.es, "bytes_received", c->c2.link_read_bytes); |
| setenv_counter(c->c2.es, "bytes_sent", c->c2.link_write_bytes); |
| } |
| |
| static void |
| multi_client_disconnect_setenv(struct multi_instance *mi) |
| { |
| /* setenv client real IP address */ |
| setenv_trusted(mi->context.c2.es, get_link_socket_info(&mi->context)); |
| |
| /* setenv stats */ |
| setenv_stats(&mi->context); |
| |
| /* setenv connection duration */ |
| setenv_long_long(mi->context.c2.es, "time_duration", now - mi->created); |
| } |
| |
| static void |
| multi_client_disconnect_script(struct multi_instance *mi) |
| { |
| multi_client_disconnect_setenv(mi); |
| |
| 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) |
| { |
| 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_free(&argv); |
| } |
| #ifdef MANAGEMENT_DEF_AUTH |
| if (management) |
| { |
| management_notify_client_close(management, &mi->context.c2.mda_context, mi->context.c2.es); |
| } |
| #endif |
| } |
| |
| void |
| multi_close_instance(struct multi_context *m, |
| struct multi_instance *mi, |
| bool shutdown) |
| { |
| perf_push(PERF_MULTI_CLOSE_INSTANCE); |
| |
| ASSERT(!mi->halt); |
| mi->halt = true; |
| |
| dmsg(D_MULTI_DEBUG, "MULTI: multi_close_instance called"); |
| |
| /* adjust current client connection count */ |
| m->n_clients += mi->n_clients_delta; |
| update_mstat_n_clients(m->n_clients); |
| mi->n_clients_delta = 0; |
| |
| /* prevent dangling pointers */ |
| if (m->pending == mi) |
| { |
| multi_set_pending(m, NULL); |
| } |
| if (m->earliest_wakeup == mi) |
| { |
| m->earliest_wakeup = NULL; |
| } |
| |
| if (!shutdown) |
| { |
| if (mi->did_real_hash) |
| { |
| ASSERT(hash_remove(m->hash, &mi->real)); |
| } |
| if (mi->did_iter) |
| { |
| ASSERT(hash_remove(m->iter, &mi->real)); |
| } |
| #ifdef MANAGEMENT_DEF_AUTH |
| if (mi->did_cid_hash) |
| { |
| ASSERT(hash_remove(m->cid_hash, &mi->context.c2.mda_context.cid)); |
| } |
| #endif |
| |
| #ifdef ENABLE_ASYNC_PUSH |
| if (mi->inotify_watch != -1) |
| { |
| hash_remove(m->inotify_watchers, (void *) (unsigned long)mi->inotify_watch); |
| mi->inotify_watch = -1; |
| } |
| #endif |
| |
| if (mi->context.c2.tls_multi->peer_id != MAX_PEER_ID) |
| { |
| m->instances[mi->context.c2.tls_multi->peer_id] = NULL; |
| } |
| |
| schedule_remove_entry(m->schedule, (struct schedule_entry *) mi); |
| |
| ifconfig_pool_release(m->ifconfig_pool, mi->vaddr_handle, false); |
| |
| if (mi->did_iroutes) |
| { |
| multi_del_iroutes(m, mi); |
| mi->did_iroutes = false; |
| } |
| |
| if (m->mtcp) |
| { |
| multi_tcp_dereference_instance(m->mtcp, mi); |
| } |
| |
| mbuf_dereference_instance(m->mbuf, mi); |
| } |
| |
| #ifdef MANAGEMENT_DEF_AUTH |
| set_cc_config(mi, NULL); |
| #endif |
| if (mi->context.c2.tls_multi->multi_state == CAS_SUCCEEDED) |
| { |
| multi_client_disconnect_script(mi); |
| } |
| |
| close_context(&mi->context, SIGTERM, CC_GC_FREE); |
| |
| multi_tcp_instance_specific_free(mi); |
| |
| ungenerate_prefix(mi); |
| |
| /* |
| * Don't actually delete the instance memory allocation yet, |
| * because virtual routes may still point to it. Let the |
| * vhash reaper deal with it. |
| */ |
| multi_instance_dec_refcount(mi); |
| |
| perf_pop(); |
| } |
| |
| /* |
| * Called on shutdown or restart. |
| */ |
| void |
| multi_uninit(struct multi_context *m) |
| { |
| if (m->thread_mode & MC_WORK_THREAD) |
| { |
| multi_top_free(m); |
| m->thread_mode = MC_UNDEF; |
| } |
| else if (m->thread_mode) |
| { |
| if (m->hash) |
| { |
| struct hash_iterator hi; |
| struct hash_element *he; |
| |
| hash_iterator_init(m->iter, &hi); |
| while ((he = hash_iterator_next(&hi))) |
| { |
| struct multi_instance *mi = (struct multi_instance *) he->value; |
| mi->did_iter = false; |
| multi_close_instance(m, mi, true); |
| } |
| hash_iterator_free(&hi); |
| |
| multi_reap_all(m); |
| |
| hash_free(m->hash); |
| hash_free(m->vhash); |
| hash_free(m->iter); |
| #ifdef MANAGEMENT_DEF_AUTH |
| hash_free(m->cid_hash); |
| #endif |
| m->hash = NULL; |
| |
| free(m->instances); |
| |
| #ifdef ENABLE_ASYNC_PUSH |
| hash_free(m->inotify_watchers); |
| m->inotify_watchers = NULL; |
| #endif |
| |
| schedule_free(m->schedule); |
| mbuf_free(m->mbuf); |
| ifconfig_pool_free(m->ifconfig_pool); |
| frequency_limit_free(m->new_connection_limiter); |
| multi_reap_free(m->reaper); |
| mroute_helper_free(m->route_helper); |
| multi_tcp_free(m->mtcp); |
| m->thread_mode = MC_UNDEF; |
| } |
| } |
| } |
| |
| /* |
| * Create a client instance object for a newly connected client. |
| */ |
| struct multi_instance * |
| multi_create_instance(struct multi_context *m, const struct mroute_addr *real) |
| { |
| struct gc_arena gc = gc_new(); |
| struct multi_instance *mi; |
| |
| perf_push(PERF_MULTI_CREATE_INSTANCE); |
| |
| msg(D_MULTI_MEDIUM, "MULTI: multi_create_instance called"); |
| |
| ALLOC_OBJ_CLEAR(mi, struct multi_instance); |
| |
| mi->gc = gc_new(); |
| multi_instance_inc_refcount(mi); |
| mi->vaddr_handle = -1; |
| mi->created = now; |
| mroute_addr_init(&mi->real); |
| |
| if (real) |
| { |
| mi->real = *real; |
| generate_prefix(mi); |
| } |
| |
| inherit_context_child(&mi->context, &m->top); |
| if (IS_SIG(&mi->context)) |
| { |
| goto err; |
| } |
| |
| mi->context.c2.tls_multi->multi_state = CAS_PENDING; |
| |
| if (hash_n_elements(m->hash) >= m->max_clients) |
| { |
| msg(D_MULTI_ERRORS, "MULTI: new incoming connection would exceed maximum number of clients (%d)", m->max_clients); |
| goto err; |
| } |
| |
| if (!real) /* TCP mode? */ |
| { |
| if (!multi_tcp_instance_specific_init(m, mi)) |
| { |
| goto err; |
| } |
| generate_prefix(mi); |
| } |
| |
| if (!hash_add(m->iter, &mi->real, mi, false)) |
| { |
| msg(D_MULTI_LOW, "MULTI: unable to add real address [%s] to iterator hash table", |
| mroute_addr_print(&mi->real, &gc)); |
| goto err; |
| } |
| mi->did_iter = true; |
| |
| #ifdef MANAGEMENT_DEF_AUTH |
| do |
| { |
| mi->context.c2.mda_context.cid = m->cid_counter++; |
| } while (!hash_add(m->cid_hash, &mi->context.c2.mda_context.cid, mi, false)); |
| mi->did_cid_hash = true; |
| #endif |
| |
| mi->context.c2.push_request_received = false; |
| #ifdef ENABLE_ASYNC_PUSH |
| mi->inotify_watch = -1; |
| #endif |
| |
| if (!multi_process_post(m, mi, MPP_PRE_SELECT)) |
| { |
| msg(D_MULTI_ERRORS, "MULTI: signal occurred during client instance initialization"); |
| goto err; |
| } |
| |
| perf_pop(); |
| gc_free(&gc); |
| return mi; |
| |
| err: |
| multi_close_instance(m, mi, false); |
| perf_pop(); |
| gc_free(&gc); |
| return NULL; |
| } |
| |
| /* |
| * Dump tables -- triggered by SIGUSR2. |
| * If status file is defined, write to file. |
| * If status file is NULL, write to syslog. |
| */ |
| void |
| multi_print_status(struct multi_context *m, struct status_output *so, const int version) |
| { |
| if (m->hash) |
| { |
| struct gc_arena gc_top = gc_new(); |
| struct hash_iterator hi; |
| const struct hash_element *he; |
| |
| status_reset(so); |
| |
| if (version == 1) /* WAS: m->status_file_version */ |
| { |
| /* |
| * Status file version 1 |
| */ |
| status_printf(so, "OpenVPN CLIENT LIST"); |
| status_printf(so, "Updated,%s", time_string(0, 0, false, &gc_top)); |
| status_printf(so, "Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since"); |
| hash_iterator_init(m->hash, &hi); |
| while ((he = hash_iterator_next(&hi))) |
| { |
| struct gc_arena gc = gc_new(); |
| const struct multi_instance *mi = (struct multi_instance *) he->value; |
| |
| if (!mi->halt) |
| { |
| status_printf(so, "%s,%s," counter_format "," counter_format ",%s", |
| tls_common_name(mi->context.c2.tls_multi, false), |
| mroute_addr_print(&mi->real, &gc), |
| mi->context.c2.link_read_bytes, |
| mi->context.c2.link_write_bytes, |
| time_string(mi->created, 0, false, &gc)); |
| } |
| gc_free(&gc); |
| } |
| hash_iterator_free(&hi); |
| |
| status_printf(so, "ROUTING TABLE"); |
| status_printf(so, "Virtual Address,Common Name,Real Address,Last Ref"); |
| hash_iterator_init(m->vhash, &hi); |
| while ((he = hash_iterator_next(&hi))) |
| { |
| struct gc_arena gc = gc_new(); |
| const struct multi_route *route = (struct multi_route *) he->value; |
| |
| if (multi_route_defined(m, route)) |
| { |
| const struct multi_instance *mi = route->instance; |
| const struct mroute_addr *ma = &route->addr; |
| char flags[2] = {0, 0}; |
| |
| if (route->flags & MULTI_ROUTE_CACHE) |
| { |
| flags[0] = 'C'; |
| } |
| status_printf(so, "%s%s,%s,%s,%s", |
| mroute_addr_print(ma, &gc), |
| flags, |
| tls_common_name(mi->context.c2.tls_multi, false), |
| mroute_addr_print(&mi->real, &gc), |
| time_string(route->last_reference, 0, false, &gc)); |
| } |
| gc_free(&gc); |
| } |
| hash_iterator_free(&hi); |
| |
| status_printf(so, "GLOBAL STATS"); |
| if (m->mbuf) |
| { |
| status_printf(so, "Max bcast/mcast queue length,%d", |
| mbuf_maximum_queued(m->mbuf)); |
| } |
| |
| status_printf(so, "END"); |
| } |
| else if (version == 2 || version == 3) |
| { |
| const char sep = (version == 3) ? '\t' : ','; |
| |
| /* |
| * Status file version 2 and 3 |
| */ |
| 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%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))) |
| { |
| struct gc_arena gc = gc_new(); |
| const struct multi_instance *mi = (struct multi_instance *) he->value; |
| |
| if (!mi->halt) |
| { |
| status_printf(so, "CLIENT_LIST%c%s%c%s%c%s%c%s%c" counter_format "%c" counter_format "%c%s%c%u%c%s%c" |
| #ifdef MANAGEMENT_DEF_AUTH |
| "%lu" |
| #else |
| "" |
| #endif |
| "%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), |
| sep, print_in6_addr(mi->reporting_addr_ipv6, IA_EMPTY_IF_UNDEF, &gc), |
| sep, mi->context.c2.link_read_bytes, |
| sep, mi->context.c2.link_write_bytes, |
| sep, time_string(mi->created, 0, false, &gc), |
| sep, (unsigned int)mi->created, |
| sep, tls_username(mi->context.c2.tls_multi, false), |
| #ifdef MANAGEMENT_DEF_AUTH |
| sep, mi->context.c2.mda_context.cid, |
| #else |
| sep, |
| #endif |
| 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); |
| } |
| hash_iterator_free(&hi); |
| |
| status_printf(so, "HEADER%cROUTING_TABLE%cVirtual Address%cCommon Name%cReal Address%cLast Ref%cLast Ref (time_t)", |
| sep, sep, sep, sep, sep, sep); |
| hash_iterator_init(m->vhash, &hi); |
| while ((he = hash_iterator_next(&hi))) |
| { |
| struct gc_arena gc = gc_new(); |
| const struct multi_route *route = (struct multi_route *) he->value; |
| |
| if (multi_route_defined(m, route)) |
| { |
| const struct multi_instance *mi = route->instance; |
| const struct mroute_addr *ma = &route->addr; |
| char flags[2] = {0, 0}; |
| |
| if (route->flags & MULTI_ROUTE_CACHE) |
| { |
| flags[0] = 'C'; |
| } |
| status_printf(so, "ROUTING_TABLE%c%s%s%c%s%c%s%c%s%c%u", |
| sep, mroute_addr_print(ma, &gc), flags, |
| sep, tls_common_name(mi->context.c2.tls_multi, false), |
| sep, mroute_addr_print(&mi->real, &gc), |
| sep, time_string(route->last_reference, 0, false, &gc), |
| sep, (unsigned int)route->last_reference); |
| } |
| gc_free(&gc); |
| } |
| hash_iterator_free(&hi); |
| |
| if (m->mbuf) |
| { |
| status_printf(so, "GLOBAL_STATS%cMax bcast/mcast queue length%c%d", |
| sep, sep, mbuf_maximum_queued(m->mbuf)); |
| } |
| |
| status_printf(so, "END"); |
| } |
| else |
| { |
| status_printf(so, "ERROR: bad status format version number"); |
| } |
| |
| #ifdef PACKET_TRUNCATION_CHECK |
| { |
| status_printf(so, "HEADER,ERRORS,Common Name,TUN Read Trunc,TUN Write Trunc,Pre-encrypt Trunc,Post-decrypt Trunc"); |
| hash_iterator_init(m->hash, &hi); |
| while ((he = hash_iterator_next(&hi))) |
| { |
| struct gc_arena gc = gc_new(); |
| const struct multi_instance *mi = (struct multi_instance *) he->value; |
| |
| if (!mi->halt) |
| { |
| status_printf(so, "ERRORS,%s," counter_format "," counter_format "," counter_format "," counter_format, |
| tls_common_name(mi->context.c2.tls_multi, false), |
| m->top.c2.n_trunc_tun_read, |
| mi->context.c2.n_trunc_tun_write, |
| mi->context.c2.n_trunc_pre_encrypt, |
| mi->context.c2.n_trunc_post_decrypt); |
| } |
| gc_free(&gc); |
| } |
| hash_iterator_free(&hi); |
| } |
| #endif /* ifdef PACKET_TRUNCATION_CHECK */ |
| |
| status_flush(so); |
| gc_free(&gc_top); |
| } |
| |
| #ifdef ENABLE_ASYNC_PUSH |
| if (m->inotify_watchers) |
| { |
| msg(D_MULTI_DEBUG, "inotify watchers count: %d\n", hash_n_elements(m->inotify_watchers)); |
| } |
| #endif |
| } |
| |
| /* |
| * Learn a virtual address or route. |
| * The learn will fail if the learn address |
| * script/plugin fails. In this case the |
| * return value may be != mi. |
| * Return the instance which owns this route, |
| * or NULL if none. |
| */ |
| static struct multi_instance * |
| multi_learn_addr(struct multi_context *m, |
| struct multi_instance *mi, |
| const struct mroute_addr *addr, |
| const unsigned int flags) |
| { |
| struct hash_element *he; |
| const uint32_t hv = hash_value(m->vhash, addr); |
| struct hash_bucket *bucket = hash_bucket(m->vhash, hv); |
| struct multi_route *oldroute = NULL; |
| struct multi_instance *owner = NULL; |
| struct gc_arena gc = gc_new(); |
| |
| /* if route currently exists, get the instance which owns it */ |
| he = hash_lookup_fast(m->vhash, bucket, addr, hv); |
| if (he) |
| { |
| oldroute = (struct multi_route *) he->value; |
| } |
| if (oldroute && multi_route_defined(m, oldroute)) |
| { |
| owner = oldroute->instance; |
| } |
| |
| /* do we need to add address to hash table? */ |
| if ((!owner || owner != mi) && mroute_learnable_address(addr, &gc) |
| && !mroute_addr_equal(addr, &m->local)) |
| { |
| struct multi_route *newroute; |
| bool learn_succeeded = false; |
| |
| ALLOC_OBJ(newroute, struct multi_route); |
| newroute->addr = *addr; |
| newroute->instance = mi; |
| newroute->flags = flags; |
| newroute->last_reference = now; |
| newroute->cache_generation = 0; |
| |
| /* The cache is invalidated when cache_generation is incremented */ |
| if (flags & MULTI_ROUTE_CACHE) |
| { |
| newroute->cache_generation = m->route_helper->cache_generation; |
| } |
| |
| if (oldroute) /* route already exists? */ |
| { |
| if (route_quota_test(mi) && learn_address_script(m, mi, "update", &newroute->addr)) |
| { |
| learn_succeeded = true; |
| owner = mi; |
| multi_instance_inc_refcount(mi); |
| route_quota_inc(mi); |
| |
| /* delete old route */ |
| multi_route_del(oldroute); |
| |
| /* modify hash table entry, replacing old route */ |
| he->key = &newroute->addr; |
| he->value = newroute; |
| } |
| } |
| else |
| { |
| if (route_quota_test(mi) && learn_address_script(m, mi, "add", &newroute->addr)) |
| { |
| learn_succeeded = true; |
| owner = mi; |
| multi_instance_inc_refcount(mi); |
| route_quota_inc(mi); |
| |
| /* add new route */ |
| hash_add_fast(m->vhash, bucket, &newroute->addr, hv, newroute); |
| } |
| } |
| |
| msg(D_MULTI_LOW, "MULTI: Learn%s: %s -> %s", |
| learn_succeeded ? "" : " FAILED", |
| mroute_addr_print(&newroute->addr, &gc), |
| multi_instance_string(mi, false, &gc)); |
| |
| if (!learn_succeeded) |
| { |
| free(newroute); |
| } |
| } |
| gc_free(&gc); |
| |
| return owner; |
| } |
| |
| /* |
| * Get client instance based on virtual address. |
| */ |
| static struct multi_instance * |
| multi_get_instance_by_virtual_addr(struct multi_context *m, |
| const struct mroute_addr *addr, |
| bool cidr_routing) |
| { |
| struct multi_route *route; |
| struct multi_instance *ret = NULL; |
| |
| /* check for local address */ |
| if (mroute_addr_equal(addr, &m->local)) |
| { |
| return NULL; |
| } |
| |
| route = (struct multi_route *) hash_lookup(m->vhash, addr); |
| |
| /* does host route (possible cached) exist? */ |
| if (route && multi_route_defined(m, route)) |
| { |
| struct multi_instance *mi = route->instance; |
| route->last_reference = now; |
| ret = mi; |
| } |
| else if (cidr_routing) /* do we need to regenerate a host route cache entry? */ |
| { |
| struct mroute_helper *rh = m->route_helper; |
| struct mroute_addr tryaddr; |
| int i; |
| |
| /* cycle through each CIDR length */ |
| for (i = 0; i < rh->n_net_len; ++i) |
| { |
| tryaddr = *addr; |
| tryaddr.type |= MR_WITH_NETBITS; |
| tryaddr.netbits = rh->net_len[i]; |
| mroute_addr_mask_host_bits(&tryaddr); |
| |
| /* look up a possible route with netbits netmask */ |
| route = (struct multi_route *) hash_lookup(m->vhash, &tryaddr); |
| |
| if (route && multi_route_defined(m, route)) |
| { |
| /* found an applicable route, cache host route */ |
| struct multi_instance *mi = route->instance; |
| multi_learn_addr(m, mi, addr, MULTI_ROUTE_CACHE|MULTI_ROUTE_AGEABLE); |
| ret = mi; |
| break; |
| } |
| } |
| } |
| |
| #ifdef ENABLE_DEBUG |
| if (check_debug_level(D_MULTI_DEBUG)) |
| { |
| struct gc_arena gc = gc_new(); |
| const char *addr_text = mroute_addr_print(addr, &gc); |
| if (ret) |
| { |
| dmsg(D_MULTI_DEBUG, "GET INST BY VIRT: %s -> %s via %s", |
| addr_text, |
| multi_instance_string(ret, false, &gc), |
| mroute_addr_print(&route->addr, &gc)); |
| } |
| else |
| { |
| dmsg(D_MULTI_DEBUG, "GET INST BY VIRT: %s [failed]", |
| addr_text); |
| } |
| gc_free(&gc); |
| } |
| #endif |
| |
| ASSERT(!(ret && ret->halt)); |
| return ret; |
| } |
| |
| /* |
| * Helper function to multi_learn_addr(). |
| */ |
| static struct multi_instance * |
| multi_learn_in_addr_t(struct multi_context *m, |
| struct multi_instance *mi, |
| in_addr_t a, |
| int netbits, /* -1 if host route, otherwise # of network bits in address */ |
| bool primary) |
| { |
| struct openvpn_sockaddr remote_si; |
| struct mroute_addr addr; |
| |
| CLEAR(remote_si); |
| remote_si.addr.in4.sin_family = AF_INET; |
| remote_si.addr.in4.sin_addr.s_addr = htonl(a); |
| ASSERT(mroute_extract_openvpn_sockaddr(&addr, &remote_si, false)); |
| |
| if (netbits >= 0) |
| { |
| addr.type |= MR_WITH_NETBITS; |
| addr.netbits = (uint8_t) netbits; |
| } |
| |
| { |
| struct multi_instance *owner = multi_learn_addr(m, mi, &addr, 0); |
| #ifdef MANAGEMENT_DEF_AUTH |
| if (management && owner) |
| { |
| management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary); |
| } |
| #endif |
| return owner; |
| } |
| } |
| |
| static struct multi_instance * |
| multi_learn_in6_addr(struct multi_context *m, |
| struct multi_instance *mi, |
| struct in6_addr a6, |
| int netbits, /* -1 if host route, otherwise # of network bits in address */ |
| bool primary) |
| { |
| struct mroute_addr addr; |
| |
| addr.len = 16; |
| addr.type = MR_ADDR_IPV6; |
| addr.netbits = 0; |
| addr.v6.addr = a6; |
| |
| if (netbits >= 0) |
| { |
| addr.type |= MR_WITH_NETBITS; |
| addr.netbits = (uint8_t) netbits; |
| mroute_addr_mask_host_bits( &addr ); |
| } |
| |
| { |
| struct multi_instance *owner = multi_learn_addr(m, mi, &addr, 0); |
| #ifdef MANAGEMENT_DEF_AUTH |
| if (management && owner) |
| { |
| management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary); |
| } |
| #endif |
| return owner; |
| } |
| } |
| |
| /* |
| * A new client has connected, add routes (server -> client) |
| * to internal routing table. |
| */ |
| static void |
| multi_add_iroutes(struct multi_context *m, |
| struct multi_instance *mi) |
| { |
| struct gc_arena gc = gc_new(); |
| const struct iroute *ir; |
| const struct iroute_ipv6 *ir6; |
| if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN) |
| { |
| mi->did_iroutes = true; |
| for (ir = mi->context.options.iroutes; ir != NULL; ir = ir->next) |
| { |
| if (ir->netbits >= 0) |
| { |
| msg(D_MULTI_LOW, "MULTI: internal route %s/%d -> %s", |
| print_in_addr_t(ir->network, 0, &gc), |
| ir->netbits, |
| multi_instance_string(mi, false, &gc)); |
| } |
| else |
| { |
| msg(D_MULTI_LOW, "MULTI: internal route %s -> %s", |
| print_in_addr_t(ir->network, 0, &gc), |
| multi_instance_string(mi, false, &gc)); |
| } |
| |
| mroute_helper_add_iroute46(m->route_helper, ir->netbits); |
| |
| multi_learn_in_addr_t(m, mi, ir->network, ir->netbits, false); |
| } |
| for (ir6 = mi->context.options.iroutes_ipv6; ir6 != NULL; ir6 = ir6->next) |
| { |
| msg(D_MULTI_LOW, "MULTI: internal route %s/%d -> %s", |
| print_in6_addr(ir6->network, 0, &gc), |
| ir6->netbits, |
| multi_instance_string(mi, false, &gc)); |
| |
| mroute_helper_add_iroute46(m->route_helper, ir6->netbits); |
| |
| multi_learn_in6_addr(m, mi, ir6->network, ir6->netbits, false); |
| } |
| } |
| gc_free(&gc); |
| } |
| |
| /* |
| * Given an instance (new_mi), delete all other instances which use the |
| * same common name. |
| */ |
| static void |
| multi_delete_dup(struct multi_context *m, struct multi_instance *new_mi) |
| { |
| if (new_mi) |
| { |
| const char *new_cn = tls_common_name(new_mi->context.c2.tls_multi, true); |
| if (new_cn) |
| { |
| struct hash_iterator hi; |
| struct hash_element *he; |
| int count = 0; |
| |
| hash_iterator_init(m->iter, &hi); |
| while ((he = hash_iterator_next(&hi))) |
| { |
| struct multi_instance *mi = (struct multi_instance *) he->value; |
| if (mi != new_mi && !mi->halt) |
| { |
| const char *cn = tls_common_name(mi->context.c2.tls_multi, true); |
| if (cn && !strcmp(cn, new_cn)) |
| { |
| mi->did_iter = false; |
| multi_close_instance(m, mi, false); |
| hash_iterator_delete_element(&hi); |
| ++count; |
| } |
| } |
| } |
| hash_iterator_free(&hi); |
| |
| if (count) |
| { |
| msg(D_MULTI_LOW, "MULTI: new connection by client '%s' will cause previous active sessions by this client to be dropped. Remember to use the --duplicate-cn option if you want multiple clients using the same certificate or username to concurrently connect.", new_cn); |
| } |
| } |
| } |
| } |
| |
| static void |
| check_stale_routes(struct multi_context *m) |
| { |
| |
| struct gc_arena gc = gc_new(); |
| struct hash_iterator hi; |
| struct hash_element *he; |
| |
| dmsg(D_MULTI_DEBUG, "MULTI: Checking stale routes"); |
| hash_iterator_init_range(m->vhash, &hi, 0, hash_n_buckets(m->vhash)); |
| while ((he = hash_iterator_next(&hi)) != NULL) |
| { |
| struct multi_route *r = (struct multi_route *) he->value; |
| if (multi_route_defined(m, r) && difftime(now, r->last_reference) >= m->top.options.stale_routes_ageing_time) |
| { |
| dmsg(D_MULTI_DEBUG, "MULTI: Deleting stale route for address '%s'", |
| mroute_addr_print(&r->addr, &gc)); |
| learn_address_script(m, NULL, "delete", &r->addr); |
| multi_route_del(r); |
| hash_iterator_delete_element(&hi); |
| } |
| } |
| hash_iterator_free(&hi); |
| gc_free(&gc); |
| } |
| |
| /* |
| * Ensure that endpoint to be pushed to client |
| * complies with --ifconfig-push-constraint directive. |
| */ |
| static bool |
| ifconfig_push_constraint_satisfied(const struct context *c) |
| { |
| const struct options *o = &c->options; |
| if (o->push_ifconfig_constraint_defined && c->c2.push_ifconfig_defined) |
| { |
| return (o->push_ifconfig_constraint_netmask & c->c2.push_ifconfig_local) == o->push_ifconfig_constraint_network; |
| } |
| else |
| { |
| return true; |
| } |
| } |
| |
| /* |
| * Select a virtual address for a new client instance. |
| * Use an --ifconfig-push directive, if given (static IP). |
| * Otherwise use an --ifconfig-pool address (dynamic IP). |
| */ |
| static void |
| multi_select_virtual_addr(struct multi_context *m, struct multi_instance *mi) |
| { |
| struct gc_arena gc = gc_new(); |
| |
| /* |
| * If ifconfig addresses were set by dynamic config file, |
| * release pool addresses, otherwise keep them. |
| */ |
| if (mi->context.options.push_ifconfig_defined) |
| { |
| /* ifconfig addresses were set statically, |
| * release dynamic allocation */ |
| if (mi->vaddr_handle >= 0) |
| { |
| ifconfig_pool_release(m->ifconfig_pool, mi->vaddr_handle, true); |
| mi->vaddr_handle = -1; |
| } |
| |
| mi->context.c2.push_ifconfig_defined = true; |
| mi->context.c2.push_ifconfig_local = mi->context.options.push_ifconfig_local; |
| mi->context.c2.push_ifconfig_remote_netmask = mi->context.options.push_ifconfig_remote_netmask; |
| mi->context.c2.push_ifconfig_local_alias = mi->context.options.push_ifconfig_local_alias; |
| |
| /* the current implementation does not allow "static IPv4, pool IPv6", |
| * (see below) so issue a warning if that happens - don't break the |
| * session, though, as we don't even know if this client WANTS IPv6 |
| */ |
| if (mi->context.options.ifconfig_ipv6_pool_defined |
| && !mi->context.options.push_ifconfig_ipv6_defined) |
| { |
| msg( M_INFO, "MULTI_sva: WARNING: if --ifconfig-push is used for IPv4, automatic IPv6 assignment from --ifconfig-ipv6-pool does not work. Use --ifconfig-ipv6-push for IPv6 then." ); |
| } |
| } |
| else if (m->ifconfig_pool && mi->vaddr_handle < 0) /* otherwise, choose a pool address */ |
| { |
| in_addr_t local = 0, remote = 0; |
| struct in6_addr remote_ipv6; |
| const char *cn = NULL; |
| |
| if (!mi->context.options.duplicate_cn) |
| { |
| cn = tls_common_name(mi->context.c2.tls_multi, true); |
| } |
| |
| CLEAR(remote_ipv6); |
| mi->vaddr_handle = ifconfig_pool_acquire(m->ifconfig_pool, &local, &remote, &remote_ipv6, cn); |
| if (mi->vaddr_handle >= 0) |
| { |
| const int tunnel_type = TUNNEL_TYPE(mi->context.c1.tuntap); |
| const int tunnel_topology = TUNNEL_TOPOLOGY(mi->context.c1.tuntap); |
| |
| msg( M_INFO, "MULTI_sva: pool returned IPv4=%s, IPv6=%s", |
| (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)") ); |
| |
| if (mi->context.options.ifconfig_pool_defined) |
| { |
| /* 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.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) |
| { |
| 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; |
| } |
| } |
| |
| 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) |
| { |
| mi->context.c2.push_ifconfig_ipv6_local = remote_ipv6; |
| mi->context.c2.push_ifconfig_ipv6_remote = |
| mi->context.c1.tuntap->local_ipv6; |
| mi->context.c2.push_ifconfig_ipv6_netbits = |
| mi->context.options.ifconfig_ipv6_netbits; |
| mi->context.c2.push_ifconfig_ipv6_defined = true; |
| } |
| } |
| else |
| { |
| msg(D_MULTI_ERRORS, "MULTI: no free --ifconfig-pool addresses are available"); |
| } |
| } |
| |
| /* IPv6 push_ifconfig is a bit problematic - since IPv6 shares the |
| * pool handling with IPv4, the combination "static IPv4, dynamic IPv6" |
| * will fail (because no pool will be allocated in this case). |
| * OTOH, this doesn't make too much sense in reality - and the other |
| * way round ("dynamic IPv4, static IPv6") or "both static" makes sense |
| * -> and so it's implemented right now |
| */ |
| if (mi->context.options.push_ifconfig_ipv6_defined) |
| { |
| mi->context.c2.push_ifconfig_ipv6_local = |
| mi->context.options.push_ifconfig_ipv6_local; |
| mi->context.c2.push_ifconfig_ipv6_remote = |
| mi->context.options.push_ifconfig_ipv6_remote; |
| mi->context.c2.push_ifconfig_ipv6_netbits = |
| mi->context.options.push_ifconfig_ipv6_netbits; |
| mi->context.c2.push_ifconfig_ipv6_defined = true; |
| |
| msg( M_INFO, "MULTI_sva: push_ifconfig_ipv6 %s/%d", |
| print_in6_addr( mi->context.c2.push_ifconfig_ipv6_local, 0, &gc ), |
| mi->context.c2.push_ifconfig_ipv6_netbits ); |
| } |
| |
| gc_free(&gc); |
| } |
| |
| /* |
| * Set virtual address environmental variables. |
| */ |
| static void |
| multi_set_virtual_addr_env(struct multi_instance *mi) |
| { |
| setenv_del(mi->context.c2.es, "ifconfig_pool_local_ip"); |
| setenv_del(mi->context.c2.es, "ifconfig_pool_remote_ip"); |
| setenv_del(mi->context.c2.es, "ifconfig_pool_netmask"); |
| |
| if (mi->context.c2.push_ifconfig_defined) |
| { |
| const int tunnel_type = TUNNEL_TYPE(mi->context.c1.tuntap); |
| const int tunnel_topology = TUNNEL_TOPOLOGY(mi->context.c1.tuntap); |
| |
| setenv_in_addr_t(mi->context.c2.es, |
| "ifconfig_pool_remote_ip", |
| mi->context.c2.push_ifconfig_local, |
| SA_SET_IF_NONZERO); |
| |
| if (tunnel_type == DEV_TYPE_TAP || (tunnel_type == DEV_TYPE_TUN && tunnel_topology == TOP_SUBNET)) |
| { |
| setenv_in_addr_t(mi->context.c2.es, |
| "ifconfig_pool_netmask", |
| mi->context.c2.push_ifconfig_remote_netmask, |
| SA_SET_IF_NONZERO); |
| } |
| else if (tunnel_type == DEV_TYPE_TUN) |
| { |
| setenv_in_addr_t(mi->context.c2.es, |
| "ifconfig_pool_local_ip", |
| mi->context.c2.push_ifconfig_remote_netmask, |
| SA_SET_IF_NONZERO); |
| } |
| } |
| |
| setenv_del(mi->context.c2.es, "ifconfig_pool_local_ip6"); |
| setenv_del(mi->context.c2.es, "ifconfig_pool_remote_ip6"); |
| setenv_del(mi->context.c2.es, "ifconfig_pool_ip6_netbits"); |
| |
| if (mi->context.c2.push_ifconfig_ipv6_defined) |
| { |
| setenv_in6_addr(mi->context.c2.es, |
| "ifconfig_pool_remote", |
| &mi->context.c2.push_ifconfig_ipv6_local, |
| SA_SET_IF_NONZERO); |
| setenv_in6_addr(mi->context.c2.es, |
| "ifconfig_pool_local", |
| &mi->context.c2.push_ifconfig_ipv6_remote, |
| SA_SET_IF_NONZERO); |
| setenv_int(mi->context.c2.es, |
| "ifconfig_pool_ip6_netbits", |
| mi->context.c2.push_ifconfig_ipv6_netbits); |
| } |
| } |
| |
| /* |
| * Called after client-connect script is called |
| */ |
| static void |
| multi_client_connect_post(struct multi_context *m, |
| struct multi_instance *mi, |
| const char *dc_file, |
| unsigned int *option_types_found) |
| { |
| /* Did script generate a dynamic config file? */ |
| if (platform_test_file(dc_file)) |
| { |
| options_server_import(&mi->context.options, |
| dc_file, |
| D_IMPORT_ERRORS|M_OPTERR, |
| CLIENT_CONNECT_OPT_MASK, |
| option_types_found, |
| mi->context.c2.es); |
| |
| /* |
| * If the --client-connect script generates a config file |
| * with an --ifconfig-push directive, it will override any |
| * --ifconfig-push directive from the --client-config-dir |
| * directory or any --ifconfig-pool dynamic address. |
| */ |
| multi_select_virtual_addr(m, mi); |
| multi_set_virtual_addr_env(mi); |
| } |
| } |
| |
| #ifdef ENABLE_PLUGIN |
| |
| /* |
| * Called after client-connect plug-in is called |
| */ |
| static void |
| multi_client_connect_post_plugin(struct multi_context *m, |
| struct multi_instance *mi, |
| const struct plugin_return *pr, |
| unsigned int *option_types_found) |
| { |
| struct plugin_return config; |
| |
| plugin_return_get_column(pr, &config, "config"); |
| |
| /* Did script generate a dynamic config file? */ |
| if (plugin_return_defined(&config)) |
| { |
| int i; |
| for (i = 0; i < config.n; ++i) |
| { |
| if (config.list[i] && config.list[i]->value) |
| { |
| options_string_import(&mi->context.options, |
| config.list[i]->value, |
| D_IMPORT_ERRORS|M_OPTERR, |
| CLIENT_CONNECT_OPT_MASK, |
| option_types_found, |
| mi->context.c2.es); |
| } |
| } |
| |
| /* |
| * If the --client-connect script generates a config file |
| * with an --ifconfig-push directive, it will override any |
| * --ifconfig-push directive from the --client-config-dir |
| * directory or any --ifconfig-pool dynamic address. |
| */ |
| multi_select_virtual_addr(m, mi); |
| multi_set_virtual_addr_env(mi); |
| } |
| } |
| |
| #endif /* ifdef ENABLE_PLUGIN */ |
| |
| |
| /* |
| * Called to load management-derived client-connect config |
| */ |
| enum client_connect_return |
| multi_client_connect_mda(struct multi_context *m, |
| struct multi_instance *mi, |
| bool deferred, |
| unsigned int *option_types_found) |
| { |
| /* 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 = 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, |
| CLIENT_CONNECT_OPT_MASK, |
| option_types_found, |
| mi->context.c2.es); |
| } |
| |
| /* |
| * If the --client-connect script generates a config file |
| * with an --ifconfig-push directive, it will override any |
| * --ifconfig-push directive from the --client-config-dir |
| * directory or any --ifconfig-pool dynamic address. |
| */ |
| 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, |
| struct multi_instance *mi) |
| { |
| struct gc_arena gc = gc_new(); |
| |
| /* setenv incoming cert common name for script */ |
| setenv_str(mi->context.c2.es, "common_name", tls_common_name(mi->context.c2.tls_multi, true)); |
| |
| /* setenv client real IP address */ |
| setenv_trusted(mi->context.c2.es, get_link_socket_info(&mi->context)); |
| |
| /* setenv client virtual IP address */ |
| multi_set_virtual_addr_env(mi); |
| |
| /* setenv connection time */ |
| { |
| const char *created_ascii = time_string(mi->created, 0, false, &gc); |
| setenv_str(mi->context.c2.es, "time_ascii", created_ascii); |
| setenv_long_long(mi->context.c2.es, "time_unix", mi->created); |
| } |
| |
| gc_free(&gc); |
| } |
| |
| /** |
| * Extracts the IV_PROTO variable and returns its value or 0 |
| * if it cannot be extracted. |
| * |
| */ |
| static unsigned int |
| extract_iv_proto(const char *peer_info) |
| { |
| |
| const char *optstr = peer_info ? strstr(peer_info, "IV_PROTO=") : NULL; |
| if (optstr) |
| { |
| int proto = 0; |
| int r = sscanf(optstr, "IV_PROTO=%d", &proto); |
| if (r == 1 && proto > 0) |
| { |
| return proto; |
| } |
| } |
| return 0; |
| } |
| |
| /** |
| * 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; |
| |
| |
| 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; |
| } |
| |
| /* Select cipher if client supports Negotiable Crypto Parameters */ |
| if (!o->ncp_enabled) |
| { |
| return true; |
| } |
| |
| /* 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; |
| } |
| |
| /* |
| * 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) |
| { |
| 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)"); |
| } |
| |
| gc_free(&gc); |
| return ret; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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; |
| |
| /* 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)) |
| { |
| ret = CC_RET_SKIPPED; |
| break; |
| } |
| |
| /* 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; |
| } |
| } |
| |
| /** |
| * 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; |
| |
| 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 |
| 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(); |
| int call; |
| |
| if (!deferred) |
| { |
| call = OPENVPN_PLUGIN_CLIENT_CONNECT; |
| if (!ccs_gen_config_file(mi) |
| || !ccs_gen_deferred_ret_file(mi)) |
| { |
| 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", 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; |
| } |
| |
| |
| /** |
| * 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); |
| |
| if (ret != CC_RET_DEFERRED) |
| { |
| ccs_delete_config_file(mi); |
| ccs_delete_deferred_ret_file(mi); |
| } |
| } |
| #endif /* ifdef ENABLE_PLUGIN */ |
| return ret; |
| } |
| |
| 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); |
| |
| 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))) |
| { |
| 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; |
| } |
| } |
| 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) |
| { |
| /* |
| * 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 |
| */ |
| ret = CC_RET_DEFERRED; |
| } |
| |
| 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; |
| } |
| |
| /** |
| * 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", ccs->config_file); |
| |
| if (openvpn_run_script(&argv, mi->context.c2.es, 0, "--client-connect")) |
| { |
| if (ccs_test_deferred_ret_file(mi) == CC_RET_DEFERRED) |
| { |
| ret = CC_RET_DEFERRED; |
| } |
| else |
| { |
| 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; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| 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)); |
| } |
| |
| 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"!! */ |
| 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); |
| } |
| |
| /* add routes locally, pointing to new client, if |
| * --iroute options have been specified */ |
| multi_add_iroutes(m, mi); |
| |
| /* |
| * 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. |
| */ |
| 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.tls_multi->multi_state = CAS_SUCCEEDED; |
| |
| /* authentication complete, calculate dynamic client specific options */ |
| if (!multi_client_set_protocol_options(&mi->context)) |
| { |
| mi->context.c2.tls_multi->multi_state = 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.tls_multi->multi_state = 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)) |
| { |
| ccd_file = ccd_client; |
| } |
| /* try default file */ |
| else if (platform_test_file(ccd_default)) |
| { |
| ccd_file = ccd_default; |
| } |
| |
| 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); |
| /* |
| * Select a virtual address from either --ifconfig-push in |
| * --client-config-dir file or --ifconfig-pool. |
| */ |
| multi_select_virtual_addr(m, mi); |
| |
| multi_client_connect_setenv(m, mi); |
| |
| ret = CC_RET_SUCCEEDED; |
| } |
| gc_free(&gc); |
| } |
| return ret; |
| } |
| |
| 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); |
| |
| 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, |
| }; |
| |
| /* |
| * 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.tls_multi->multi_state != 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.tls_multi->multi_state = 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: |
| /* |
| * Remember that we already had at least one handler |
| * returning a result should we go to into deferred state |
| */ |
| mi->context.c2.tls_multi->multi_state = CAS_PENDING_DEFERRED_PARTIAL; |
| break; |
| |
| case CC_RET_SKIPPED: |
| /* |
| * Move on with the next handler without modifying any |
| * other state |
| */ |
| break; |
| |
| 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; |
| |
| 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); |
| } |
| |
| /* |
| * Check for "disable" directive in client-config-dir file |
| * or config file generated by --client-connect script. |
| */ |
| if (mi->context.options.disable) |
| { |
| msg(D_MULTI_ERRORS, "MULTI: client has been rejected due to " |
| "'disable' directive"); |
| cc_succeeded = false; |
| } |
| |
| (*cur_handler_index)++; |
| } |
| |
| 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.tls_multi->multi_state == CAS_PENDING_DEFERRED_PARTIAL) |
| { |
| multi_client_disconnect_script(mi); |
| } |
| |
| mi->context.c2.tls_multi->multi_state = CAS_FAILED; |
| } |
| |
| /* 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 |
| * 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) |
| { |
| char buffer[INOTIFY_EVENT_BUFFER_SIZE]; |
| size_t buffer_i = 0; |
| int r = read(m->top.c2.inotify_fd, buffer, INOTIFY_EVENT_BUFFER_SIZE); |
| |
| while (buffer_i < r) |
| { |
| /* parse inotify events */ |
| struct inotify_event *pevent = (struct inotify_event *) &buffer[buffer_i]; |
| size_t event_size = sizeof(struct inotify_event) + pevent->len; |
| buffer_i += event_size; |
| |
| msg(D_MULTI_DEBUG, "MULTI: modified fd %d, mask %d", pevent->wd, pevent->mask); |
| |
| struct multi_instance *mi = hash_lookup(m->inotify_watchers, (void *) (unsigned long) pevent->wd); |
| |
| if (pevent->mask & IN_CLOSE_WRITE) |
| { |
| if (mi) |
| { |
| /* continue authentication, perform NCP negotiation and send push_reply */ |
| multi_process_post(m, mi, mpp_flags); |
| } |
| else |
| { |
| msg(D_MULTI_ERRORS, "MULTI: multi_instance not found!"); |
| } |
| } |
| else if (pevent->mask & IN_IGNORED) |
| { |
| /* this event is _always_ fired when watch is removed or file is deleted */ |
| if (mi) |
| { |
| hash_remove(m->inotify_watchers, (void *) (unsigned long) pevent->wd); |
| mi->inotify_watch = -1; |
| } |
| } |
| else |
| { |
| msg(D_MULTI_ERRORS, "MULTI: unknown mask %d", pevent->mask); |
| } |
| } |
| } |
| #endif /* ifdef ENABLE_ASYNC_PUSH */ |
| |
| /* |
| * Add a mbuf buffer to a particular |
| * instance. |
| */ |
| void |
| multi_add_mbuf(struct multi_context *m, |
| struct multi_instance *mi, |
| struct mbuf_buffer *mb) |
| { |
| if (multi_output_queue_ready(m, mi)) |
| { |
| struct mbuf_item item; |
| item.buffer = mb; |
| item.instance = mi; |
| mbuf_add_item(m->mbuf, &item); |
| } |
| else |
| { |
| msg(D_MULTI_DROPPED, "MULTI: packet dropped due to output saturation (multi_add_mbuf)"); |
| } |
| } |
| |
| /* |
| * Add a packet to a client instance output queue. |
| */ |
| static inline void |
| multi_unicast(struct multi_context *m, |
| const struct buffer *buf, |
| struct multi_instance *mi) |
| { |
| struct mbuf_buffer *mb; |
| |
| if (BLEN(buf) > 0) |
| { |
| mb = mbuf_alloc_buf(buf); |
| mb->flags = MF_UNICAST; |
| multi_add_mbuf(m, mi, mb); |
| mbuf_free_buf(mb); |
| } |
| } |
| |
| /* |
| * Broadcast a packet to all clients. |
| */ |
| static void |
| multi_bcast(struct multi_context *m, |
| const struct buffer *buf, |
| const struct multi_instance *sender_instance, |
| const struct mroute_addr *sender_addr, |
| uint16_t vid) |
| { |
| struct hash_iterator hi; |
| struct hash_element *he; |
| struct multi_instance *mi; |
| struct mbuf_buffer *mb; |
| |
| if (BLEN(buf) > 0) |
| { |
| perf_push(PERF_MULTI_BCAST); |
| #ifdef MULTI_DEBUG_EVENT_LOOP |
| printf("BCAST len=%d\n", BLEN(buf)); |
| #endif |
| mb = mbuf_alloc_buf(buf); |
| hash_iterator_init(m->iter, &hi); |
| |
| while ((he = hash_iterator_next(&hi))) |
| { |
| mi = (struct multi_instance *) he->value; |
| if (mi != sender_instance && !mi->halt) |
| { |
| #ifdef ENABLE_PF |
| if (sender_instance) |
| { |
| 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), |
| mi_prefix(mi)); |
| continue; |
| } |
| } |
| if (sender_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", |
| mroute_addr_print_ex(sender_addr, MAPF_SHOW_ARP, &gc), |
| mi_prefix(mi)); |
| gc_free(&gc); |
| continue; |
| } |
| } |
| #endif /* ifdef ENABLE_PF */ |
| if (vid != 0 && vid != mi->context.options.vlan_pvid) |
| { |
| continue; |
| } |
| multi_add_mbuf(m, mi, mb); |
| } |
| } |
| |
| hash_iterator_free(&hi); |
| mbuf_free_buf(mb); |
| perf_pop(); |
| } |
| } |
| |
| /* |
| * Given a time delta, indicating that we wish to be |
| * awoken by the scheduler at time now + delta, figure |
| * a sigma parameter (in microseconds) that represents |
| * a sort of fuzz factor around delta, so that we're |
| * really telling the scheduler to wake us up any time |
| * between now + delta - sigma and now + delta + sigma. |
| * |
| * The sigma parameter helps the scheduler to run more efficiently. |
| * Sigma should be no larger than TV_WITHIN_SIGMA_MAX_USEC |
| */ |
| static inline unsigned int |
| compute_wakeup_sigma(const struct timeval *delta) |
| { |
| if (delta->tv_sec < 1) |
| { |
| /* if < 1 sec, fuzz = # of microseconds / 8 */ |
| return delta->tv_usec >> 3; |
| } |
| else |
| { |
| /* if < 10 minutes, fuzz = 13.1% of timeout */ |
| if (delta->tv_sec < 600) |
| { |
| return delta->tv_sec << 17; |
| } |
| else |
| { |
| return 120000000; /* if >= 10 minutes, fuzz = 2 minutes */ |
| } |
| } |
| } |
| |
| static void |
| multi_schedule_context_wakeup(struct multi_context *m, struct multi_instance *mi) |
| { |
| /* calculate an absolute wakeup time */ |
| ASSERT(!openvpn_gettimeofday(&mi->wakeup, NULL)); |
| tv_add(&mi->wakeup, &mi->context.c2.timeval); |
| |
| /* tell scheduler to wake us up at some point in the future */ |
| schedule_add_entry(m->schedule, |
| (struct schedule_entry *) mi, |
| &mi->wakeup, |
| 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, |
| * call scheduler with our future wakeup time. |
| * |
| * Also close context on signal. |
| */ |
| bool |
| multi_process_post(struct multi_context *m, struct multi_instance *mi, const unsigned int flags) |
| { |
| bool ret = true; |
| |
| 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_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_unauthenticated = (ks->authenticated == KS_AUTH_FALSE); |
| } |
| #endif |
| |
| /* figure timeouts and fetch possible outgoing |
| * to_link packets (such as ping or TLS control) */ |
| pre_select(&mi->context); |
| |
| #if defined(ENABLE_ASYNC_PUSH) && defined(ENABLE_DEF_AUTH) |
| /* |
| * 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)) |
| { |
| add_inotify_file_watch(m, mi, m->top.c2.inotify_fd, |
| ks->auth_control_file); |
| } |
| #endif |
| |
| if (!IS_SIG(&mi->context)) |
| { |
| /* connection is "established" when SSL/TLS key negotiation succeeds |
| * and (if specified) auth user/pass succeeds */ |
| if (is_cas_pending(mi->context.c2.tls_multi->multi_state) |
| && 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.tls_multi->multi_state) |
| && 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); |
| } |
| } |
| |
| if (IS_SIG(&mi->context)) |
| { |
| if (flags & MPP_CLOSE_ON_SIGNAL) |
| { |
| multi_close_instance_on_signal(m, mi); |
| ret = false; |
| } |
| } |
| else |
| { |
| /* continue to pend on output? */ |
| 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=%" 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, |
| (int64_t)mi->context.c2.timeval.tv_sec, |
| (long)mi->context.c2.timeval.tv_usec); |
| #endif |
| } |
| |
| if ((flags & MPP_RECORD_TOUCH) && m->mpp_touched) |
| { |
| *m->mpp_touched = mi; |
| } |
| |
| return ret; |
| } |
| |
| void |
| multi_process_float(struct multi_context *m, struct multi_instance *mi) |
| { |
| struct mroute_addr real; |
| struct hash *hash = m->hash; |
| struct gc_arena gc = gc_new(); |
| |
| if (!mroute_extract_openvpn_sockaddr(&real, &m->top.c2.from.dest, true)) |
| { |
| goto done; |
| } |
| |
| const uint32_t hv = hash_value(hash, &real); |
| struct hash_bucket *bucket = hash_bucket(hash, hv); |
| |
| /* make sure that we don't float to an address taken by another client */ |
| struct hash_element *he = hash_lookup_fast(hash, bucket, &real, hv); |
| if (he) |
| { |
| struct multi_instance *ex_mi = (struct multi_instance *) he->value; |
| |
| struct tls_multi *m1 = mi->context.c2.tls_multi; |
| struct tls_multi *m2 = ex_mi->context.c2.tls_multi; |
| |
| /* do not float if target address is taken by client with another cert */ |
| if (!cert_hash_compare(m1->locked_cert_hash_set, m2->locked_cert_hash_set)) |
| { |
| msg(D_MULTI_LOW, "Disallow float to an address taken by another client %s", |
| multi_instance_string(ex_mi, false, &gc)); |
| |
| mi->context.c2.buf.len = 0; |
| |
| goto done; |
| } |
| |
| msg(D_MULTI_MEDIUM, "closing instance %s", multi_instance_string(ex_mi, false, &gc)); |
| multi_close_instance(m, ex_mi, false); |
| } |
| |
| msg(D_MULTI_MEDIUM, "peer %" PRIu32 " (%s) floated from %s to %s", |
| mi->context.c2.tls_multi->peer_id, |
| tls_common_name(mi->context.c2.tls_multi, false), |
| mroute_addr_print(&mi->real, &gc), |
| print_link_socket_actual(&m->top.c2.from, &gc)); |
| |
| /* remove old address from hash table before changing address */ |
| ASSERT(hash_remove(m->hash, &mi->real)); |
| ASSERT(hash_remove(m->iter, &mi->real)); |
| |
| /* change external network address of the remote peer */ |
| mi->real = real; |
| generate_prefix(mi); |
| |
| mi->context.c2.from = m->top.c2.from; |
| mi->context.c2.to_link_addr = &mi->context.c2.from; |
| |
| /* inherit parent link_socket and link_socket_info */ |
| mi->context.c2.link_socket = m->top.c2.link_socket; |
| mi->context.c2.link_socket_info->lsa->actual = m->top.c2.from; |
| |
| tls_update_remote_addr(mi->context.c2. |