/* * 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 * * 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_CONFIG_VERSION_H #include "config-version.h" #endif #include "syshead.h" #ifdef ENABLE_PLUGIN #ifdef HAVE_DLFCN_H #include #endif #include "buffer.h" #include "error.h" #include "misc.h" #include "plugin.h" #include "ssl_backend.h" #include "win32.h" #include "memdbg.h" #define PLUGIN_SYMBOL_REQUIRED (1<<0) /* used only for program aborts */ static struct plugin_common *static_plugin_common = NULL; /* GLOBAL */ static void plugin_show_string_array(int msglevel, const char *name, const char *array[]) { int i; for (i = 0; array[i]; ++i) { if (env_safe_to_print(array[i])) { msg(msglevel, "%s[%d] = '%s'", name, i, array[i]); } } } static void plugin_show_args_env(int msglevel, const char *argv[], const char *envp[]) { if (check_debug_level(msglevel)) { plugin_show_string_array(msglevel, "ARGV", argv); plugin_show_string_array(msglevel, "ENVP", envp); } } static const char * plugin_type_name(const int type) { switch (type) { case OPENVPN_PLUGIN_UP: return "PLUGIN_UP"; case OPENVPN_PLUGIN_DOWN: return "PLUGIN_DOWN"; case OPENVPN_PLUGIN_ROUTE_UP: return "PLUGIN_ROUTE_UP"; case OPENVPN_PLUGIN_IPCHANGE: return "PLUGIN_IPCHANGE"; case OPENVPN_PLUGIN_TLS_VERIFY: return "PLUGIN_TLS_VERIFY"; case OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY: return "PLUGIN_AUTH_USER_PASS_VERIFY"; case OPENVPN_PLUGIN_CLIENT_CONNECT: return "PLUGIN_CLIENT_CONNECT"; case OPENVPN_PLUGIN_CLIENT_CONNECT_V2: return "PLUGIN_CLIENT_CONNECT"; case OPENVPN_PLUGIN_CLIENT_DISCONNECT: return "PLUGIN_CLIENT_DISCONNECT"; case OPENVPN_PLUGIN_LEARN_ADDRESS: return "PLUGIN_LEARN_ADDRESS"; case OPENVPN_PLUGIN_TLS_FINAL: return "PLUGIN_TLS_FINAL"; case OPENVPN_PLUGIN_ENABLE_PF: return "PLUGIN_ENABLE_PF"; case OPENVPN_PLUGIN_ROUTE_PREDOWN: return "PLUGIN_ROUTE_PREDOWN"; default: return "PLUGIN_???"; } } static const char * plugin_mask_string(const unsigned int type_mask, struct gc_arena *gc) { struct buffer out = alloc_buf_gc(256, gc); bool first = true; int i; for (i = 0; i < OPENVPN_PLUGIN_N; ++i) { if (OPENVPN_PLUGIN_MASK(i) & type_mask) { if (!first) { buf_printf(&out, "|"); } buf_printf(&out, "%s", plugin_type_name(i)); first = false; } } return BSTR(&out); } static inline unsigned int plugin_supported_types(void) { return ((1<n < MAX_PLUGINS) { struct plugin_option *o = &list->plugins[list->n++]; o->argv = make_extended_arg_array(p, gc); if (o->argv[0]) { o->so_pathname = o->argv[0]; } return true; } else { return false; } } #ifndef ENABLE_SMALL void plugin_option_list_print(const struct plugin_option_list *list, int msglevel) { int i; struct gc_arena gc = gc_new(); for (i = 0; i < list->n; ++i) { const struct plugin_option *o = &list->plugins[i]; msg(msglevel, " plugin[%d] %s '%s'", i, o->so_pathname, print_argv(o->argv, &gc, PA_BRACKET)); } gc_free(&gc); } #endif #ifndef _WIN32 static void libdl_resolve_symbol(void *handle, void **dest, const char *symbol, const char *plugin_name, const unsigned int flags) { *dest = dlsym(handle, symbol); if ((flags & PLUGIN_SYMBOL_REQUIRED) && !*dest) { msg(M_FATAL, "PLUGIN: could not find required symbol '%s' in plugin shared object %s: %s", symbol, plugin_name, dlerror()); } } #else /* ifndef _WIN32 */ static void dll_resolve_symbol(HMODULE module, void **dest, const char *symbol, const char *plugin_name, const unsigned int flags) { *dest = GetProcAddress(module, symbol); if ((flags & PLUGIN_SYMBOL_REQUIRED) && !*dest) { msg(M_FATAL, "PLUGIN: could not find required symbol '%s' in plugin DLL %s", symbol, plugin_name); } } #endif /* ifndef _WIN32 */ static void plugin_init_item(struct plugin *p, const struct plugin_option *o) { struct gc_arena gc = gc_new(); bool rel = false; p->so_pathname = o->so_pathname; p->plugin_type_mask = plugin_supported_types(); #ifndef _WIN32 p->handle = NULL; /* If the plug-in filename is not an absolute path, * or beginning with '.', it should use the PLUGIN_LIBDIR * as the base directory for loading the plug-in. * * This means the following scenarios are loaded from these places: * --plugin fancyplug.so -> $PLUGIN_LIBDIR/fancyplug.so * --plugin my/fancyplug.so -> $PLUGIN_LIBDIR/my/fancyplug.so * --plugin ./fancyplug.so -> $CWD/fancyplug.so * --plugin /usr/lib/my/fancyplug.so -> /usr/lib/my/fancyplug.so * * Please note that $CWD means the directory OpenVPN is either started from * or the directory OpenVPN have changed into using --cd before --plugin * was parsed. * */ if (!platform_absolute_pathname(p->so_pathname) && p->so_pathname[0] != '.') { char full[PATH_MAX]; openvpn_snprintf(full, sizeof(full), "%s/%s", PLUGIN_LIBDIR, p->so_pathname); p->handle = dlopen(full, RTLD_NOW); } else { rel = !platform_absolute_pathname(p->so_pathname); p->handle = dlopen(p->so_pathname, RTLD_NOW); } if (!p->handle) { msg(M_ERR, "PLUGIN_INIT: could not load plugin shared object %s: %s", p->so_pathname, dlerror()); } #define PLUGIN_SYM(var, name, flags) libdl_resolve_symbol(p->handle, (void *)&p->var, name, p->so_pathname, flags) #else /* ifndef _WIN32 */ rel = !platform_absolute_pathname(p->so_pathname); p->module = LoadLibraryW(wide_string(p->so_pathname, &gc)); if (!p->module) { msg(M_ERR, "PLUGIN_INIT: could not load plugin DLL: %s", p->so_pathname); } #define PLUGIN_SYM(var, name, flags) dll_resolve_symbol(p->module, (void *)&p->var, name, p->so_pathname, flags) #endif /* ifndef _WIN32 */ PLUGIN_SYM(open1, "openvpn_plugin_open_v1", 0); PLUGIN_SYM(open2, "openvpn_plugin_open_v2", 0); PLUGIN_SYM(open3, "openvpn_plugin_open_v3", 0); PLUGIN_SYM(func1, "openvpn_plugin_func_v1", 0); PLUGIN_SYM(func2, "openvpn_plugin_func_v2", 0); PLUGIN_SYM(func3, "openvpn_plugin_func_v3", 0); PLUGIN_SYM(close, "openvpn_plugin_close_v1", PLUGIN_SYMBOL_REQUIRED); PLUGIN_SYM(abort, "openvpn_plugin_abort_v1", 0); PLUGIN_SYM(client_constructor, "openvpn_plugin_client_constructor_v1", 0); PLUGIN_SYM(client_destructor, "openvpn_plugin_client_destructor_v1", 0); PLUGIN_SYM(min_version_required, "openvpn_plugin_min_version_required_v1", 0); PLUGIN_SYM(initialization_point, "openvpn_plugin_select_initialization_point_v1", 0); if (!p->open1 && !p->open2 && !p->open3) { msg(M_FATAL, "PLUGIN: symbol openvpn_plugin_open_vX is undefined in plugin: %s", p->so_pathname); } if (!p->func1 && !p->func2 && !p->func3) { msg(M_FATAL, "PLUGIN: symbol openvpn_plugin_func_vX is undefined in plugin: %s", p->so_pathname); } /* * Verify that we are sufficiently up-to-date to handle the plugin */ if (p->min_version_required) { const int plugin_needs_version = (*p->min_version_required)(); if (plugin_needs_version > OPENVPN_PLUGIN_VERSION) { msg(M_FATAL, "PLUGIN_INIT: plugin needs interface version %d, but this version of OpenVPN only supports version %d: %s", plugin_needs_version, OPENVPN_PLUGIN_VERSION, p->so_pathname); } } if (p->initialization_point) { p->requested_initialization_point = (*p->initialization_point)(); } else { p->requested_initialization_point = OPENVPN_PLUGIN_INIT_PRE_DAEMON; } if (rel) { msg(M_WARN, "WARNING: plugin '%s' specified by a relative pathname -- using an absolute pathname would be more secure", p->so_pathname); } p->initialized = true; gc_free(&gc); } static void plugin_vlog(openvpn_plugin_log_flags_t flags, const char *name, const char *format, va_list arglist) { unsigned int msg_flags = 0; if (!format) { return; } if (!name || name[0] == '\0') { msg(D_PLUGIN_DEBUG, "PLUGIN: suppressed log message from plugin with unknown name"); return; } if (flags & PLOG_ERR) { msg_flags = M_INFO | M_NONFATAL; } else if (flags & PLOG_WARN) { msg_flags = M_INFO | M_WARN; } else if (flags & PLOG_NOTE) { msg_flags = M_INFO; } else if (flags & PLOG_DEBUG) { msg_flags = D_PLUGIN_DEBUG; } if (flags & PLOG_ERRNO) { msg_flags |= M_ERRNO; } if (flags & PLOG_NOMUTE) { msg_flags |= M_NOMUTE; } if (msg_test(msg_flags)) { struct gc_arena gc; char *msg_fmt; /* Never add instance prefix; not thread safe */ msg_flags |= M_NOIPREFIX; gc_init(&gc); msg_fmt = gc_malloc(ERR_BUF_SIZE, false, &gc); openvpn_snprintf(msg_fmt, ERR_BUF_SIZE, "PLUGIN %s: %s", name, format); x_msg_va(msg_flags, msg_fmt, arglist); gc_free(&gc); } } static void plugin_log(openvpn_plugin_log_flags_t flags, const char *name, const char *format, ...) { va_list arglist; va_start(arglist, format); plugin_vlog(flags, name, format, arglist); va_end(arglist); } static struct openvpn_plugin_callbacks callbacks = { plugin_log, plugin_vlog, secure_memzero /* plugin_secure_memzero */ }; /* Provide a wrapper macro for a version patch level string to plug-ins. * This is located here purely to not make the code too messy with #ifndef * inside a struct declaration */ #ifndef CONFIGURE_GIT_REVISION #define _OPENVPN_PATCH_LEVEL OPENVPN_VERSION_PATCH #else #define _OPENVPN_PATCH_LEVEL "git:" CONFIGURE_GIT_REVISION CONFIGURE_GIT_FLAGS #endif static void plugin_open_item(struct plugin *p, const struct plugin_option *o, struct openvpn_plugin_string_list **retlist, const char **envp, const int init_point) { ASSERT(p->initialized); /* clear return list */ if (retlist) { *retlist = NULL; } if (!p->plugin_handle && init_point == p->requested_initialization_point) { struct gc_arena gc = gc_new(); dmsg(D_PLUGIN_DEBUG, "PLUGIN_INIT: PRE"); plugin_show_args_env(D_PLUGIN_DEBUG, o->argv, envp); /* * Call the plugin initialization */ if (p->open3) { struct openvpn_plugin_args_open_in args = { p->plugin_type_mask, (const char **const) o->argv, (const char **const) envp, &callbacks, SSLAPI, PACKAGE_VERSION, OPENVPN_VERSION_MAJOR, OPENVPN_VERSION_MINOR, _OPENVPN_PATCH_LEVEL}; struct openvpn_plugin_args_open_return retargs; CLEAR(retargs); retargs.return_list = retlist; if ((*p->open3)(OPENVPN_PLUGINv3_STRUCTVER, &args, &retargs) == OPENVPN_PLUGIN_FUNC_SUCCESS) { p->plugin_type_mask = retargs.type_mask; p->plugin_handle = retargs.handle; } else { p->plugin_handle = NULL; } } else if (p->open2) { p->plugin_handle = (*p->open2)(&p->plugin_type_mask, o->argv, envp, retlist); } else if (p->open1) { p->plugin_handle = (*p->open1)(&p->plugin_type_mask, o->argv, envp); } else { ASSERT(0); } msg(D_PLUGIN, "PLUGIN_INIT: POST %s '%s' intercepted=%s %s", p->so_pathname, print_argv(o->argv, &gc, PA_BRACKET), plugin_mask_string(p->plugin_type_mask, &gc), (retlist && *retlist) ? "[RETLIST]" : ""); if ((p->plugin_type_mask | plugin_supported_types()) != plugin_supported_types()) { msg(M_FATAL, "PLUGIN_INIT: plugin %s expressed interest in unsupported plugin types: [want=0x%08x, have=0x%08x]", p->so_pathname, p->plugin_type_mask, plugin_supported_types()); } if (p->plugin_handle == NULL) { msg(M_FATAL, "PLUGIN_INIT: plugin initialization function failed: %s", p->so_pathname); } gc_free(&gc); } } static int plugin_call_item(const struct plugin *p, void *per_client_context, const int type, const struct argv *av, struct openvpn_plugin_string_list **retlist, const char **envp, int certdepth, openvpn_x509_cert_t *current_cert ) { int status = OPENVPN_PLUGIN_FUNC_SUCCESS; /* clear return list */ if (retlist) { *retlist = NULL; } if (p->plugin_handle && (p->plugin_type_mask & OPENVPN_PLUGIN_MASK(type))) { struct gc_arena gc = gc_new(); struct argv a = argv_insert_head(av, p->so_pathname); dmsg(D_PLUGIN_DEBUG, "PLUGIN_CALL: PRE type=%s", plugin_type_name(type)); plugin_show_args_env(D_PLUGIN_DEBUG, (const char **)a.argv, envp); /* * Call the plugin work function */ if (p->func3) { struct openvpn_plugin_args_func_in args = { type, (const char **const) a.argv, (const char **const) envp, p->plugin_handle, per_client_context, (current_cert ? certdepth : -1), current_cert }; struct openvpn_plugin_args_func_return retargs; CLEAR(retargs); retargs.return_list = retlist; status = (*p->func3)(OPENVPN_PLUGINv3_STRUCTVER, &args, &retargs); } else if (p->func2) { status = (*p->func2)(p->plugin_handle, type, (const char **)a.argv, envp, per_client_context, retlist); } else if (p->func1) { status = (*p->func1)(p->plugin_handle, type, (const char **)a.argv, envp); } else { ASSERT(0); } msg(D_PLUGIN, "PLUGIN_CALL: POST %s/%s status=%d", p->so_pathname, plugin_type_name(type), status); if (status == OPENVPN_PLUGIN_FUNC_ERROR) { msg(M_WARN, "PLUGIN_CALL: plugin function %s failed with status %d: %s", plugin_type_name(type), status, p->so_pathname); } argv_reset(&a); gc_free(&gc); } return status; } static void plugin_close_item(struct plugin *p) { if (p->initialized) { msg(D_PLUGIN, "PLUGIN_CLOSE: %s", p->so_pathname); /* * Call the plugin close function */ if (p->plugin_handle) { (*p->close)(p->plugin_handle); } #ifndef _WIN32 if (dlclose(p->handle)) { msg(M_WARN, "PLUGIN_CLOSE: dlclose() failed on plugin: %s", p->so_pathname); } #elif defined(_WIN32) if (!FreeLibrary(p->module)) { msg(M_WARN, "PLUGIN_CLOSE: FreeLibrary() failed on plugin: %s", p->so_pathname); } #endif p->initialized = false; } } static void plugin_abort_item(const struct plugin *p) { /* * Call the plugin abort function */ if (p->abort) { (*p->abort)(p->plugin_handle); } } static void plugin_per_client_init(const struct plugin_common *pc, struct plugin_per_client *cli, const int init_point) { const int n = pc->n; int i; for (i = 0; i < n; ++i) { const struct plugin *p = &pc->plugins[i]; if (p->plugin_handle && (init_point < 0 || init_point == p->requested_initialization_point) && p->client_constructor) { cli->per_client_context[i] = (*p->client_constructor)(p->plugin_handle); } } } static void plugin_per_client_destroy(const struct plugin_common *pc, struct plugin_per_client *cli) { const int n = pc->n; int i; for (i = 0; i < n; ++i) { const struct plugin *p = &pc->plugins[i]; void *cc = cli->per_client_context[i]; if (p->client_destructor && cc) { (*p->client_destructor)(p->plugin_handle, cc); } } CLEAR(*cli); } struct plugin_list * plugin_list_inherit(const struct plugin_list *src) { struct plugin_list *pl; ALLOC_OBJ_CLEAR(pl, struct plugin_list); pl->common = src->common; ASSERT(pl->common); plugin_per_client_init(pl->common, &pl->per_client, -1); return pl; } static struct plugin_common * plugin_common_init(const struct plugin_option_list *list) { int i; struct plugin_common *pc; ALLOC_OBJ_CLEAR(pc, struct plugin_common); for (i = 0; i < list->n; ++i) { plugin_init_item(&pc->plugins[i], &list->plugins[i]); pc->n = i + 1; } static_plugin_common = pc; return pc; } static void plugin_common_open(struct plugin_common *pc, const struct plugin_option_list *list, struct plugin_return *pr, const struct env_set *es, const int init_point) { struct gc_arena gc = gc_new(); int i; const char **envp; envp = make_env_array(es, false, &gc); if (pr) { plugin_return_init(pr); } for (i = 0; i < pc->n; ++i) { plugin_open_item(&pc->plugins[i], &list->plugins[i], pr ? &pr->list[i] : NULL, envp, init_point); } if (pr) { pr->n = i; } gc_free(&gc); } static void plugin_common_close(struct plugin_common *pc) { static_plugin_common = NULL; if (pc) { int i; for (i = 0; i < pc->n; ++i) { plugin_close_item(&pc->plugins[i]); } free(pc); } } struct plugin_list * plugin_list_init(const struct plugin_option_list *list) { struct plugin_list *pl; ALLOC_OBJ_CLEAR(pl, struct plugin_list); pl->common = plugin_common_init(list); pl->common_owned = true; return pl; } void plugin_list_open(struct plugin_list *pl, const struct plugin_option_list *list, struct plugin_return *pr, const struct env_set *es, const int init_point) { plugin_common_open(pl->common, list, pr, es, init_point); plugin_per_client_init(pl->common, &pl->per_client, init_point); } int plugin_call_ssl(const struct plugin_list *pl, const int type, const struct argv *av, struct plugin_return *pr, struct env_set *es, int certdepth, openvpn_x509_cert_t *current_cert ) { if (pr) { plugin_return_init(pr); } if (plugin_defined(pl, type)) { struct gc_arena gc = gc_new(); int i; const char **envp; const int n = plugin_n(pl); bool success = false; bool error = false; bool deferred = false; setenv_del(es, "script_type"); envp = make_env_array(es, false, &gc); for (i = 0; i < n; ++i) { const int status = plugin_call_item(&pl->common->plugins[i], pl->per_client.per_client_context[i], type, av, pr ? &pr->list[i] : NULL, envp, certdepth, current_cert ); switch (status) { case OPENVPN_PLUGIN_FUNC_SUCCESS: success = true; break; case OPENVPN_PLUGIN_FUNC_DEFERRED: deferred = true; break; default: error = true; break; } } if (pr) { pr->n = i; } gc_free(&gc); if (type == OPENVPN_PLUGIN_ENABLE_PF && success) { return OPENVPN_PLUGIN_FUNC_SUCCESS; } else if (error) { return OPENVPN_PLUGIN_FUNC_ERROR; } else if (deferred) { return OPENVPN_PLUGIN_FUNC_DEFERRED; } } return OPENVPN_PLUGIN_FUNC_SUCCESS; } void plugin_list_close(struct plugin_list *pl) { if (pl) { if (pl->common) { plugin_per_client_destroy(pl->common, &pl->per_client); if (pl->common_owned) { plugin_common_close(pl->common); } } free(pl); } } void plugin_abort(void) { struct plugin_common *pc = static_plugin_common; static_plugin_common = NULL; if (pc) { int i; for (i = 0; i < pc->n; ++i) { plugin_abort_item(&pc->plugins[i]); } } } bool plugin_defined(const struct plugin_list *pl, const int type) { bool ret = false; if (pl) { const struct plugin_common *pc = pl->common; if (pc) { int i; const unsigned int mask = OPENVPN_PLUGIN_MASK(type); for (i = 0; i < pc->n; ++i) { if (pc->plugins[i].plugin_type_mask & mask) { ret = true; break; } } } } return ret; } /* * Plugin return functions */ static void openvpn_plugin_string_list_item_free(struct openvpn_plugin_string_list *l) { if (l) { free(l->name); string_clear(l->value); free(l->value); free(l); } } static void openvpn_plugin_string_list_free(struct openvpn_plugin_string_list *l) { struct openvpn_plugin_string_list *next; while (l) { next = l->next; openvpn_plugin_string_list_item_free(l); l = next; } } static struct openvpn_plugin_string_list * openvpn_plugin_string_list_find(struct openvpn_plugin_string_list *l, const char *name) { while (l) { if (!strcmp(l->name, name)) { return l; } l = l->next; } return NULL; } void plugin_return_get_column(const struct plugin_return *src, struct plugin_return *dest, const char *colname) { int i; dest->n = 0; for (i = 0; i < src->n; ++i) { dest->list[i] = openvpn_plugin_string_list_find(src->list[i], colname); } dest->n = i; } void plugin_return_free(struct plugin_return *pr) { int i; for (i = 0; i < pr->n; ++i) { openvpn_plugin_string_list_free(pr->list[i]); } pr->n = 0; } #ifdef ENABLE_DEBUG void plugin_return_print(const int msglevel, const char *prefix, const struct plugin_return *pr) { int i; msg(msglevel, "PLUGIN_RETURN_PRINT %s", prefix); for (i = 0; i < pr->n; ++i) { struct openvpn_plugin_string_list *l = pr->list[i]; int count = 0; msg(msglevel, "PLUGIN #%d (%s)", i, prefix); while (l) { msg(msglevel, "[%d] '%s' -> '%s'\n", ++count, l->name, l->value); l = l->next; } } } #endif /* ifdef ENABLE_DEBUG */ #else /* ifdef ENABLE_PLUGIN */ static void dummy(void) { } #endif /* ENABLE_PLUGIN */