src/openvpn/plugin.c
6fbf66fa
 /*
  *  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.
  *
49979459
  *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
6fbf66fa
  *
  *  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.
  *
caa54ac3
  *  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.
6fbf66fa
  */
 
c110b289
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #elif defined(_MSC_VER)
 #include "config-msvc.h"
 #endif
6a40276c
 #ifdef HAVE_CONFIG_VERSION_H
 #include "config-version.h"
 #endif
c110b289
 
6fbf66fa
 #include "syshead.h"
 
 #ifdef ENABLE_PLUGIN
 
bdae4110
 #ifdef HAVE_DLFCN_H
 #include <dlfcn.h>
 #endif
 
6fbf66fa
 #include "buffer.h"
 #include "error.h"
 #include "misc.h"
 #include "plugin.h"
587df08a
 #include "ssl_backend.h"
71bbbd76
 #include "win32.h"
6fbf66fa
 #include "memdbg.h"
 
 #define PLUGIN_SYMBOL_REQUIRED (1<<0)
 
 /* used only for program aborts */
3c7f2f55
 static struct plugin_common *static_plugin_common = NULL; /* GLOBAL */
6fbf66fa
 
 static void
81d882d5
 plugin_show_string_array(int msglevel, const char *name, const char *array[])
6fbf66fa
 {
81d882d5
     int i;
     for (i = 0; array[i]; ++i)
093e7eba
     {
81d882d5
         if (env_safe_to_print(array[i]))
         {
             msg(msglevel, "%s[%d] = '%s'", name, i, array[i]);
         }
093e7eba
     }
6fbf66fa
 }
 
 static void
81d882d5
 plugin_show_args_env(int msglevel, const char *argv[], const char *envp[])
6fbf66fa
 {
81d882d5
     if (check_debug_level(msglevel))
6fbf66fa
     {
81d882d5
         plugin_show_string_array(msglevel, "ARGV", argv);
         plugin_show_string_array(msglevel, "ENVP", envp);
6fbf66fa
     }
 }
 
 static const char *
81d882d5
 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_???";
6fbf66fa
     }
 }
 
 static const char *
81d882d5
 plugin_mask_string(const unsigned int type_mask, struct gc_arena *gc)
6fbf66fa
 {
81d882d5
     struct buffer out = alloc_buf_gc(256, gc);
     bool first = true;
     int i;
6fbf66fa
 
81d882d5
     for (i = 0; i < OPENVPN_PLUGIN_N; ++i)
6fbf66fa
     {
81d882d5
         if (OPENVPN_PLUGIN_MASK(i) & type_mask)
         {
             if (!first)
             {
                 buf_printf(&out, "|");
             }
             buf_printf(&out, "%s", plugin_type_name(i));
             first = false;
         }
6fbf66fa
     }
81d882d5
     return BSTR(&out);
6fbf66fa
 }
 
 static inline unsigned int
81d882d5
 plugin_supported_types(void)
6fbf66fa
 {
81d882d5
     return ((1<<OPENVPN_PLUGIN_N)-1);
6fbf66fa
 }
 
 struct plugin_option_list *
81d882d5
 plugin_option_list_new(struct gc_arena *gc)
6fbf66fa
 {
81d882d5
     struct plugin_option_list *ret;
     ALLOC_OBJ_CLEAR_GC(ret, struct plugin_option_list, gc);
     return ret;
6fbf66fa
 }
 
 bool
81d882d5
 plugin_option_list_add(struct plugin_option_list *list, char **p, struct gc_arena *gc)
6fbf66fa
 {
81d882d5
     if (list->n < MAX_PLUGINS)
6fbf66fa
     {
81d882d5
         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;
6fbf66fa
     }
 }
 
6c61d0dd
 #ifndef ENABLE_SMALL
6fbf66fa
 void
81d882d5
 plugin_option_list_print(const struct plugin_option_list *list, int msglevel)
6fbf66fa
 {
81d882d5
     int i;
     struct gc_arena gc = gc_new();
eadf16a6
 
81d882d5
     for (i = 0; i < list->n; ++i)
6fbf66fa
     {
81d882d5
         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));
6fbf66fa
     }
eadf16a6
 
81d882d5
     gc_free(&gc);
6fbf66fa
 }
 #endif
 
445b192a
 #ifndef _WIN32
6fbf66fa
 
 static void
81d882d5
 libdl_resolve_symbol(void *handle, void **dest, const char *symbol, const char *plugin_name, const unsigned int flags)
6fbf66fa
 {
81d882d5
     *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());
     }
6fbf66fa
 }
 
81d882d5
 #else  /* ifndef _WIN32 */
6fbf66fa
 
 static void
81d882d5
 dll_resolve_symbol(HMODULE module, void **dest, const char *symbol, const char *plugin_name, const unsigned int flags)
6fbf66fa
 {
81d882d5
     *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);
     }
6fbf66fa
 }
 
81d882d5
 #endif /* ifndef _WIN32 */
6fbf66fa
 
 static void
81d882d5
 plugin_init_item(struct plugin *p, const struct plugin_option *o)
6fbf66fa
 {
81d882d5
     struct gc_arena gc = gc_new();
     bool rel = false;
d1dcc3e7
 
81d882d5
     p->so_pathname = o->so_pathname;
     p->plugin_type_mask = plugin_supported_types();
6fbf66fa
 
445b192a
 #ifndef _WIN32
3c7f2f55
 
81d882d5
     p->handle = NULL;
4590c383
 
f9609f1d
     /* 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.
      *
      */
b7bea782
     if (!platform_absolute_pathname(p->so_pathname)
f9609f1d
         && p->so_pathname[0] != '.')
e342be3f
     {
81d882d5
         char full[PATH_MAX];
e342be3f
 
81d882d5
         openvpn_snprintf(full, sizeof(full), "%s/%s", PLUGIN_LIBDIR, p->so_pathname);
         p->handle = dlopen(full, RTLD_NOW);
e342be3f
     }
81d882d5
     else
e342be3f
     {
b7bea782
         rel = !platform_absolute_pathname(p->so_pathname);
81d882d5
         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());
e342be3f
     }
3c7f2f55
 
81d882d5
 #define PLUGIN_SYM(var, name, flags) libdl_resolve_symbol(p->handle, (void *)&p->var, name, p->so_pathname, flags)
3c7f2f55
 
81d882d5
 #else  /* ifndef _WIN32 */
3c7f2f55
 
b7bea782
     rel = !platform_absolute_pathname(p->so_pathname);
81d882d5
     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);
     }
3c7f2f55
 
81d882d5
 #define PLUGIN_SYM(var, name, flags) dll_resolve_symbol(p->module, (void *)&p->var, name, p->so_pathname, flags)
3c7f2f55
 
81d882d5
 #endif /* ifndef _WIN32 */
6fbf66fa
 
81d882d5
     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);
3c7f2f55
 
81d882d5
     if (!p->open1 && !p->open2 && !p->open3)
     {
         msg(M_FATAL, "PLUGIN: symbol openvpn_plugin_open_vX is undefined in plugin: %s", p->so_pathname);
     }
3c7f2f55
 
81d882d5
     if (!p->func1 && !p->func2 && !p->func3)
     {
         msg(M_FATAL, "PLUGIN: symbol openvpn_plugin_func_vX is undefined in plugin: %s", p->so_pathname);
     }
3c7f2f55
 
81d882d5
     /*
      * Verify that we are sufficiently up-to-date to handle the plugin
      */
     if (p->min_version_required)
3c7f2f55
     {
81d882d5
         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);
         }
3c7f2f55
     }
 
81d882d5
     if (p->initialization_point)
     {
         p->requested_initialization_point = (*p->initialization_point)();
     }
     else
     {
         p->requested_initialization_point = OPENVPN_PLUGIN_INIT_PRE_DAEMON;
     }
e1791bb1
 
81d882d5
     if (rel)
     {
         msg(M_WARN, "WARNING: plugin '%s' specified by a relative pathname -- using an absolute pathname would be more secure", p->so_pathname);
     }
d1dcc3e7
 
81d882d5
     p->initialized = true;
e1791bb1
 
81d882d5
     gc_free(&gc);
e1791bb1
 }
 
 static void
81d882d5
 plugin_vlog(openvpn_plugin_log_flags_t flags, const char *name, const char *format, va_list arglist)
be532e0d
 {
81d882d5
     unsigned int msg_flags = 0;
be532e0d
 
81d882d5
     if (!format)
     {
         return;
     }
be532e0d
 
81d882d5
     if (!name || name[0] == '\0')
be532e0d
     {
81d882d5
         msg(D_PLUGIN_DEBUG, "PLUGIN: suppressed log message from plugin with unknown name");
         return;
be532e0d
     }
 
81d882d5
     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;
     }
be532e0d
 
81d882d5
     if (flags & PLOG_ERRNO)
     {
         msg_flags |= M_ERRNO;
     }
     if (flags & PLOG_NOMUTE)
     {
         msg_flags |= M_NOMUTE;
     }
be532e0d
 
81d882d5
     if (msg_test(msg_flags))
be532e0d
     {
81d882d5
         struct gc_arena gc;
         char *msg_fmt;
be532e0d
 
81d882d5
         /* Never add instance prefix; not thread safe */
         msg_flags |= M_NOIPREFIX;
be532e0d
 
81d882d5
         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);
be532e0d
 
81d882d5
         gc_free(&gc);
be532e0d
     }
 }
 
 static void
81d882d5
 plugin_log(openvpn_plugin_log_flags_t flags, const char *name, const char *format, ...)
be532e0d
 {
81d882d5
     va_list arglist;
     va_start(arglist, format);
     plugin_vlog(flags, name, format, arglist);
     va_end(arglist);
be532e0d
 }
 
 static struct openvpn_plugin_callbacks callbacks = {
81d882d5
     plugin_log,
f018dfcc
     plugin_vlog,
     secure_memzero   /* plugin_secure_memzero */
be532e0d
 };
 
6a40276c
 
 /* 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
81d882d5
 #define _OPENVPN_PATCH_LEVEL OPENVPN_VERSION_PATCH
6a40276c
 #else
81d882d5
 #define _OPENVPN_PATCH_LEVEL "git:" CONFIGURE_GIT_REVISION CONFIGURE_GIT_FLAGS
6a40276c
 #endif
 
be532e0d
 static void
81d882d5
 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);
76b41d7f
         }
6fbf66fa
 
81d882d5
         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());
         }
e1791bb1
 
81d882d5
         if (p->plugin_handle == NULL)
         {
             msg(M_FATAL, "PLUGIN_INIT: plugin initialization function failed: %s",
                 p->so_pathname);
         }
6fbf66fa
 
81d882d5
         gc_free(&gc);
e1791bb1
     }
6fbf66fa
 }
 
 static int
81d882d5
 plugin_call_item(const struct plugin *p,
                  void *per_client_context,
                  const int type,
                  const struct argv *av,
                  struct openvpn_plugin_string_list **retlist,
c7ca9133
                  const char **envp,
                  int certdepth,
81d882d5
                  openvpn_x509_cert_t *current_cert
                  )
6fbf66fa
 {
81d882d5
     int status = OPENVPN_PLUGIN_FUNC_SUCCESS;
e1791bb1
 
81d882d5
     /* clear return list */
     if (retlist)
6fbf66fa
     {
81d882d5
         *retlist = NULL;
     }
6fbf66fa
 
81d882d5
     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
             };
76b41d7f
 
81d882d5
             struct openvpn_plugin_args_func_return retargs;
6fbf66fa
 
81d882d5
             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);
         }
6fbf66fa
 
81d882d5
         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);
         }
6fbf66fa
 
81d882d5
         argv_reset(&a);
         gc_free(&gc);
6fbf66fa
     }
81d882d5
     return status;
6fbf66fa
 }
 
 static void
81d882d5
 plugin_close_item(struct plugin *p)
6fbf66fa
 {
81d882d5
     if (p->initialized)
e1791bb1
     {
81d882d5
         msg(D_PLUGIN, "PLUGIN_CLOSE: %s", p->so_pathname);
 
         /*
          * Call the plugin close function
          */
         if (p->plugin_handle)
         {
             (*p->close)(p->plugin_handle);
         }
6fbf66fa
 
445b192a
 #ifndef _WIN32
81d882d5
         if (dlclose(p->handle))
         {
             msg(M_WARN, "PLUGIN_CLOSE: dlclose() failed on plugin: %s", p->so_pathname);
         }
445b192a
 #elif defined(_WIN32)
81d882d5
         if (!FreeLibrary(p->module))
         {
             msg(M_WARN, "PLUGIN_CLOSE: FreeLibrary() failed on plugin: %s", p->so_pathname);
         }
6fbf66fa
 #endif
e1791bb1
 
81d882d5
         p->initialized = false;
e1791bb1
     }
6fbf66fa
 }
 
 static void
81d882d5
 plugin_abort_item(const struct plugin *p)
6fbf66fa
 {
81d882d5
     /*
      * Call the plugin abort function
      */
     if (p->abort)
     {
         (*p->abort)(p->plugin_handle);
     }
6fbf66fa
 }
 
3c7f2f55
 static void
81d882d5
 plugin_per_client_init(const struct plugin_common *pc,
                        struct plugin_per_client *cli,
                        const int init_point)
3c7f2f55
 {
81d882d5
     const int n = pc->n;
     int i;
3c7f2f55
 
81d882d5
     for (i = 0; i < n; ++i)
3c7f2f55
     {
81d882d5
         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);
         }
3c7f2f55
     }
 }
 
 static void
81d882d5
 plugin_per_client_destroy(const struct plugin_common *pc, struct plugin_per_client *cli)
3c7f2f55
 {
81d882d5
     const int n = pc->n;
     int i;
3c7f2f55
 
81d882d5
     for (i = 0; i < n; ++i)
e1791bb1
     {
81d882d5
         const struct plugin *p = &pc->plugins[i];
         void *cc = cli->per_client_context[i];
3c7f2f55
 
81d882d5
         if (p->client_destructor && cc)
         {
             (*p->client_destructor)(p->plugin_handle, cc);
         }
3c7f2f55
     }
81d882d5
     CLEAR(*cli);
3c7f2f55
 }
 
6fbf66fa
 struct plugin_list *
81d882d5
 plugin_list_inherit(const struct plugin_list *src)
3c7f2f55
 {
81d882d5
     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;
3c7f2f55
 }
 
 static struct plugin_common *
81d882d5
 plugin_common_init(const struct plugin_option_list *list)
6fbf66fa
 {
81d882d5
     int i;
     struct plugin_common *pc;
6fbf66fa
 
81d882d5
     ALLOC_OBJ_CLEAR(pc, struct plugin_common);
6fbf66fa
 
81d882d5
     for (i = 0; i < list->n; ++i)
e1791bb1
     {
81d882d5
         plugin_init_item(&pc->plugins[i],
                          &list->plugins[i]);
         pc->n = i + 1;
e1791bb1
     }
 
81d882d5
     static_plugin_common = pc;
     return pc;
e1791bb1
 }
 
 static void
81d882d5
 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)
e1791bb1
 {
81d882d5
     struct gc_arena gc = gc_new();
     int i;
     const char **envp;
e1791bb1
 
81d882d5
     envp = make_env_array(es, false, &gc);
6fbf66fa
 
81d882d5
     if (pr)
     {
         plugin_return_init(pr);
     }
3c7f2f55
 
81d882d5
     for (i = 0; i < pc->n; ++i)
6fbf66fa
     {
81d882d5
         plugin_open_item(&pc->plugins[i],
                          &list->plugins[i],
                          pr ? &pr->list[i] : NULL,
                          envp,
                          init_point);
6fbf66fa
     }
 
81d882d5
     if (pr)
     {
         pr->n = i;
     }
3c7f2f55
 
81d882d5
     gc_free(&gc);
3c7f2f55
 }
 
 static void
81d882d5
 plugin_common_close(struct plugin_common *pc)
3c7f2f55
 {
81d882d5
     static_plugin_common = NULL;
     if (pc)
3c7f2f55
     {
81d882d5
         int i;
3c7f2f55
 
81d882d5
         for (i = 0; i < pc->n; ++i)
4cd4899e
         {
81d882d5
             plugin_close_item(&pc->plugins[i]);
4cd4899e
         }
81d882d5
         free(pc);
3c7f2f55
     }
 }
 
 struct plugin_list *
81d882d5
 plugin_list_init(const struct plugin_option_list *list)
3c7f2f55
 {
81d882d5
     struct plugin_list *pl;
     ALLOC_OBJ_CLEAR(pl, struct plugin_list);
     pl->common = plugin_common_init(list);
     pl->common_owned = true;
     return pl;
6fbf66fa
 }
 
e1791bb1
 void
81d882d5
 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)
e1791bb1
 {
81d882d5
     plugin_common_open(pl->common, list, pr, es, init_point);
     plugin_per_client_init(pl->common, &pl->per_client, init_point);
e1791bb1
 }
 
6fbf66fa
 int
81d882d5
 plugin_call_ssl(const struct plugin_list *pl,
                 const int type,
                 const struct argv *av,
                 struct plugin_return *pr,
c7ca9133
                 struct env_set *es,
                 int certdepth,
81d882d5
                 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,
c7ca9133
                                                 envp,
                                                 certdepth,
81d882d5
                                                 current_cert
                                                 );
             switch (status)
             {
                 case OPENVPN_PLUGIN_FUNC_SUCCESS:
                     success = true;
                     break;
 
                 case OPENVPN_PLUGIN_FUNC_DEFERRED:
                     deferred = true;
                     break;
 
                 default:
                     error = true;
                     break;
             }
         }
6fbf66fa
 
81d882d5
         if (pr)
         {
             pr->n = i;
         }
3c7f2f55
 
81d882d5
         gc_free(&gc);
6fbf66fa
 
81d882d5
         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;
         }
3c7f2f55
     }
344ee918
 
81d882d5
     return OPENVPN_PLUGIN_FUNC_SUCCESS;
6fbf66fa
 }
 
 void
81d882d5
 plugin_list_close(struct plugin_list *pl)
6fbf66fa
 {
81d882d5
     if (pl)
6fbf66fa
     {
81d882d5
         if (pl->common)
         {
             plugin_per_client_destroy(pl->common, &pl->per_client);
 
             if (pl->common_owned)
             {
                 plugin_common_close(pl->common);
             }
         }
3c7f2f55
 
81d882d5
         free(pl);
6fbf66fa
     }
 }
 
 void
81d882d5
 plugin_abort(void)
6fbf66fa
 {
81d882d5
     struct plugin_common *pc = static_plugin_common;
     static_plugin_common = NULL;
     if (pc)
6fbf66fa
     {
81d882d5
         int i;
6fbf66fa
 
81d882d5
         for (i = 0; i < pc->n; ++i)
4cd4899e
         {
81d882d5
             plugin_abort_item(&pc->plugins[i]);
4cd4899e
         }
6fbf66fa
     }
 }
 
 bool
81d882d5
 plugin_defined(const struct plugin_list *pl, const int type)
6fbf66fa
 {
81d882d5
     bool ret = false;
3c7f2f55
 
81d882d5
     if (pl)
6fbf66fa
     {
81d882d5
         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;
                 }
             }
         }
6fbf66fa
     }
81d882d5
     return ret;
6fbf66fa
 }
 
3c7f2f55
 /*
  * Plugin return functions
  */
 
 static void
81d882d5
 openvpn_plugin_string_list_item_free(struct openvpn_plugin_string_list *l)
3c7f2f55
 {
81d882d5
     if (l)
3c7f2f55
     {
81d882d5
         free(l->name);
         string_clear(l->value);
         free(l->value);
         free(l);
3c7f2f55
     }
 }
 
 static void
81d882d5
 openvpn_plugin_string_list_free(struct openvpn_plugin_string_list *l)
3c7f2f55
 {
81d882d5
     struct openvpn_plugin_string_list *next;
     while (l)
3c7f2f55
     {
81d882d5
         next = l->next;
         openvpn_plugin_string_list_item_free(l);
         l = next;
3c7f2f55
     }
 }
 
 static struct openvpn_plugin_string_list *
81d882d5
 openvpn_plugin_string_list_find(struct openvpn_plugin_string_list *l, const char *name)
3c7f2f55
 {
81d882d5
     while (l)
3c7f2f55
     {
81d882d5
         if (!strcmp(l->name, name))
         {
             return l;
         }
         l = l->next;
3c7f2f55
     }
81d882d5
     return NULL;
3c7f2f55
 }
 
 void
81d882d5
 plugin_return_get_column(const struct plugin_return *src,
                          struct plugin_return *dest,
                          const char *colname)
3c7f2f55
 {
81d882d5
     int i;
3c7f2f55
 
81d882d5
     dest->n = 0;
     for (i = 0; i < src->n; ++i)
4cd4899e
     {
81d882d5
         dest->list[i] = openvpn_plugin_string_list_find(src->list[i], colname);
4cd4899e
     }
81d882d5
     dest->n = i;
3c7f2f55
 }
 
 void
81d882d5
 plugin_return_free(struct plugin_return *pr)
3c7f2f55
 {
81d882d5
     int i;
     for (i = 0; i < pr->n; ++i)
4cd4899e
     {
81d882d5
         openvpn_plugin_string_list_free(pr->list[i]);
4cd4899e
     }
81d882d5
     pr->n = 0;
3c7f2f55
 }
 
 #ifdef ENABLE_DEBUG
 void
81d882d5
 plugin_return_print(const int msglevel, const char *prefix, const struct plugin_return *pr)
3c7f2f55
 {
81d882d5
     int i;
     msg(msglevel, "PLUGIN_RETURN_PRINT %s", prefix);
     for (i = 0; i < pr->n; ++i)
3c7f2f55
     {
81d882d5
         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;
         }
3c7f2f55
     }
 }
81d882d5
 #endif /* ifdef ENABLE_DEBUG */
3c7f2f55
 
81d882d5
 #else  /* ifdef ENABLE_PLUGIN */
 static void
4cd4899e
 dummy(void)
 {
81d882d5
 }
6fbf66fa
 #endif /* ENABLE_PLUGIN */