/* * 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-2008 OpenVPN Technologies, 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 (see the file COPYING included with this * distribution); if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "syshead.h" #ifdef ENABLE_PLUGIN #include "buffer.h" #include "error.h" #include "misc.h" #include "plugin.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 "OPENVPN_PLUGIN_ENABLE_PF"; 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; } #ifdef ENABLE_DEBUG 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 #if defined(USE_LIBDL) 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()); } #elif defined(USE_LOAD_LIBRARY) 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 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 (); #if defined(USE_LIBDL) p->handle = NULL; #if defined(PLUGIN_LIBDIR) if (!absolute_pathname (p->so_pathname)) { char full[PATH_MAX]; openvpn_snprintf (full, sizeof(full), "%s/%s", PLUGIN_LIBDIR, p->so_pathname); p->handle = dlopen (full, RTLD_NOW); #if defined(ENABLE_PLUGIN_SEARCH) if (!p->handle) { rel = true; p->handle = dlopen (p->so_pathname, RTLD_NOW); } #endif } else #endif { rel = !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) #elif defined(USE_LOAD_LIBRARY) rel = !absolute_pathname (p->so_pathname); p->module = LoadLibrary (p->so_pathname); 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 PLUGIN_SYM (open1, "openvpn_plugin_open_v1", 0); PLUGIN_SYM (open2, "openvpn_plugin_open_v2", 0); PLUGIN_SYM (func1, "openvpn_plugin_func_v1", 0); PLUGIN_SYM (func2, "openvpn_plugin_func_v2", 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) msg (M_FATAL, "PLUGIN: symbol openvpn_plugin_open_vX is undefined in plugin: %s", p->so_pathname); if (!p->func1 && !p->func2) 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_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->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 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->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); #if defined(USE_LIBDL) if (dlclose (p->handle)) msg (M_WARN, "PLUGIN_CLOSE: dlclose() failed on plugin: %s", p->so_pathname); #elif defined(USE_LOAD_LIBRARY) 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 (const struct plugin_list *pl, const int type, const struct argv *av, struct plugin_return *pr, struct env_set *es) { 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; mutex_lock_static (L_PLUGIN); 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); 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; mutex_unlock_static (L_PLUGIN); 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 #else static void dummy(void) {} #endif /* ENABLE_PLUGIN */