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.
  *
d7fa38f2
  *  Copyright (C) 2002-2009 OpenVPN Technologies, 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.
  *
  *  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 */
3c7f2f55
 static struct plugin_common *static_plugin_common = NULL; /* GLOBAL */
6fbf66fa
 
 static void
 plugin_show_string_array (int msglevel, const char *name, const char *array[])
 {
   int i;
   for (i = 0; array[i]; ++i)
093e7eba
     {
       if (env_safe_to_print (array[i]))
 	msg (msglevel, "%s[%d] = '%s'", name, i, array[i]);
     }
6fbf66fa
 }
 
 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";
3c7f2f55
     case OPENVPN_PLUGIN_CLIENT_CONNECT_V2:
       return "PLUGIN_CLIENT_CONNECT";
6fbf66fa
     case OPENVPN_PLUGIN_CLIENT_DISCONNECT:
       return "PLUGIN_CLIENT_DISCONNECT";
     case OPENVPN_PLUGIN_LEARN_ADDRESS:
       return "PLUGIN_LEARN_ADDRESS";
d92819fa
     case OPENVPN_PLUGIN_TLS_FINAL:
       return "PLUGIN_TLS_FINAL";
47ae8457
     case OPENVPN_PLUGIN_ENABLE_PF:
       return "OPENVPN_PLUGIN_ENABLE_PF";
6fbf66fa
     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<<OPENVPN_PLUGIN_N)-1);
 }
 
 struct plugin_option_list *
 plugin_option_list_new (struct gc_arena *gc)
 {
   struct plugin_option_list *ret;
   ALLOC_OBJ_CLEAR_GC (ret, struct plugin_option_list, gc);
   return ret;
 }
 
 bool
eadf16a6
 plugin_option_list_add (struct plugin_option_list *list, char **p, struct gc_arena *gc)
6fbf66fa
 {
   if (list->n < MAX_PLUGINS)
     {
       struct plugin_option *o = &list->plugins[list->n++];
eadf16a6
       o->argv = make_extended_arg_array (p, gc);
       if (o->argv[0])
 	o->so_pathname = o->argv[0];
6fbf66fa
       return true;
     }
   else
     return false;
 }
 
 #ifdef ENABLE_DEBUG
 void
 plugin_option_list_print (const struct plugin_option_list *list, int msglevel)
 {
   int i;
eadf16a6
   struct gc_arena gc = gc_new ();
 
6fbf66fa
   for (i = 0; i < list->n; ++i)
     {
       const struct plugin_option *o = &list->plugins[i];
eadf16a6
       msg (msglevel, "  plugin[%d] %s '%s'", i, o->so_pathname, print_argv (o->argv, &gc, PA_BRACKET));
6fbf66fa
     }
eadf16a6
 
   gc_free (&gc);
6fbf66fa
 }
 #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
e1791bb1
 plugin_init_item (struct plugin *p, const struct plugin_option *o)
6fbf66fa
 {
   struct gc_arena gc = gc_new ();
d1dcc3e7
   bool rel = false;
 
6fbf66fa
   p->so_pathname = o->so_pathname;
   p->plugin_type_mask = plugin_supported_types ();
 
 #if defined(USE_LIBDL)
3c7f2f55
 
e342be3f
   p->handle = NULL;
 #if defined(PLUGIN_LIBDIR)
d1dcc3e7
   if (!absolute_pathname (p->so_pathname))
e342be3f
     {
       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)
 	{
d1dcc3e7
 	  rel = true;
e342be3f
 	  p->handle = dlopen (p->so_pathname, RTLD_NOW);
 	}
 #endif
     }
   else
 #endif
     {
d1dcc3e7
       rel = !absolute_pathname (p->so_pathname);
e342be3f
       p->handle = dlopen (p->so_pathname, RTLD_NOW);
     }
6fbf66fa
   if (!p->handle)
     msg (M_ERR, "PLUGIN_INIT: could not load plugin shared object %s: %s", p->so_pathname, dlerror());
3c7f2f55
 
 # define PLUGIN_SYM(var, name, flags) libdl_resolve_symbol (p->handle, (void*)&p->var, name, p->so_pathname, flags)
 
6fbf66fa
 #elif defined(USE_LOAD_LIBRARY)
3c7f2f55
 
d1dcc3e7
   rel = !absolute_pathname (p->so_pathname);
6fbf66fa
   p->module = LoadLibrary (p->so_pathname);
   if (!p->module)
     msg (M_ERR, "PLUGIN_INIT: could not load plugin DLL: %s", p->so_pathname);
3c7f2f55
 
 # define PLUGIN_SYM(var, name, flags) dll_resolve_symbol (p->module, (void*)&p->var, name, p->so_pathname, flags)
 
6fbf66fa
 #endif
 
3c7f2f55
   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);
e1791bb1
   PLUGIN_SYM (min_version_required, "openvpn_plugin_min_version_required_v1", 0);
   PLUGIN_SYM (initialization_point, "openvpn_plugin_select_initialization_point_v1", 0);
3c7f2f55
 
   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);
 
6fbf66fa
   /*
3c7f2f55
    * 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);
     }
 
e1791bb1
   if (p->initialization_point)
     p->requested_initialization_point = (*p->initialization_point)();
   else
     p->requested_initialization_point = OPENVPN_PLUGIN_INIT_PRE_DAEMON;
 
d1dcc3e7
   if (rel)
     msg (M_WARN, "WARNING: plugin '%s' specified by a relative pathname -- using an absolute pathname would be more secure", p->so_pathname);
 
e1791bb1
   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 */
3c7f2f55
   if (retlist)
     *retlist = NULL;
 
e1791bb1
   if (!p->plugin_handle && init_point == p->requested_initialization_point)
     {
       struct gc_arena gc = gc_new ();
6fbf66fa
 
e1791bb1
       dmsg (D_PLUGIN_DEBUG, "PLUGIN_INIT: PRE");
eadf16a6
       plugin_show_args_env (D_PLUGIN_DEBUG, o->argv, envp);
6fbf66fa
 
e1791bb1
       /*
        * Call the plugin initialization
        */
       if (p->open2)
eadf16a6
 	p->plugin_handle = (*p->open2)(&p->plugin_type_mask, o->argv, envp, retlist);
e1791bb1
       else if (p->open1)
eadf16a6
 	p->plugin_handle = (*p->open1)(&p->plugin_type_mask, o->argv, envp);
e1791bb1
       else
 	ASSERT (0);
6fbf66fa
 
e1791bb1
       msg (D_PLUGIN, "PLUGIN_INIT: POST %s '%s' intercepted=%s %s",
 	   p->so_pathname,
eadf16a6
 	   print_argv (o->argv, &gc, PA_BRACKET),
e1791bb1
 	   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);
6fbf66fa
 
e1791bb1
       gc_free (&gc);
     }
6fbf66fa
 }
 
 static int
3c7f2f55
 plugin_call_item (const struct plugin *p,
 		  void *per_client_context,
 		  const int type,
5a2e9a25
 		  const struct argv *av,
3c7f2f55
 		  struct openvpn_plugin_string_list **retlist,
 		  const char **envp)
6fbf66fa
 {
   int status = OPENVPN_PLUGIN_FUNC_SUCCESS;
 
e1791bb1
   /* clear return list */
   if (retlist)
     *retlist = NULL;
 
   if (p->plugin_handle && (p->plugin_type_mask & OPENVPN_PLUGIN_MASK (type)))
6fbf66fa
     {
       struct gc_arena gc = gc_new ();
5a2e9a25
       struct argv a = argv_insert_head (av, p->so_pathname);
6fbf66fa
 
       dmsg (D_PLUGIN_DEBUG, "PLUGIN_CALL: PRE type=%s", plugin_type_name (type));
5a2e9a25
       plugin_show_args_env (D_PLUGIN_DEBUG, (const char **)a.argv, envp);
6fbf66fa
 
       /*
        * Call the plugin work function
        */
3c7f2f55
       if (p->func2)
5a2e9a25
 	status = (*p->func2)(p->plugin_handle, type, (const char **)a.argv, envp, per_client_context, retlist);
3c7f2f55
       else if (p->func1)
5a2e9a25
 	status = (*p->func1)(p->plugin_handle, type, (const char **)a.argv, envp);
3c7f2f55
       else
 	ASSERT (0);
6fbf66fa
 
       msg (D_PLUGIN, "PLUGIN_CALL: POST %s/%s status=%d",
 	   p->so_pathname,
 	   plugin_type_name (type),
 	   status);
 
344ee918
       if (status == OPENVPN_PLUGIN_FUNC_ERROR)
6fbf66fa
 	msg (M_WARN, "PLUGIN_CALL: plugin function %s failed with status %d: %s",
 	     plugin_type_name (type),
 	     status,
 	     p->so_pathname);
 
5a2e9a25
       argv_reset (&a);
6fbf66fa
       gc_free (&gc);
     }
   return status;
 }
 
 static void
e1791bb1
 plugin_close_item (struct plugin *p)
6fbf66fa
 {
e1791bb1
   if (p->initialized)
     {
       msg (D_PLUGIN, "PLUGIN_CLOSE: %s", p->so_pathname);
6fbf66fa
 
e1791bb1
       /*
        * Call the plugin close function
        */
       if (p->plugin_handle)
 	(*p->close)(p->plugin_handle);
6fbf66fa
 
 #if defined(USE_LIBDL)
e1791bb1
       if (dlclose (p->handle))
 	msg (M_WARN, "PLUGIN_CLOSE: dlclose() failed on plugin: %s", p->so_pathname);
6fbf66fa
 #elif defined(USE_LOAD_LIBRARY)
e1791bb1
       if (!FreeLibrary (p->module))
 	msg (M_WARN, "PLUGIN_CLOSE: FreeLibrary() failed on plugin: %s", p->so_pathname);
6fbf66fa
 #endif
e1791bb1
 
       p->initialized = false;
     }
6fbf66fa
 }
 
 static void
 plugin_abort_item (const struct plugin *p)
 {
   /*
    * Call the plugin abort function
    */
   if (p->abort)
     (*p->abort)(p->plugin_handle);
 }
 
3c7f2f55
 static void
e1791bb1
 plugin_per_client_init (const struct plugin_common *pc,
 			struct plugin_per_client *cli,
 			const int init_point)
3c7f2f55
 {
   const int n = pc->n;
   int i;
 
   for (i = 0; i < n; ++i)
     {
       const struct plugin *p = &pc->plugins[i];
e1791bb1
       if (p->plugin_handle
 	  && (init_point < 0 || init_point == p->requested_initialization_point)
 	  && p->client_constructor)
3c7f2f55
 	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)
 {
e1791bb1
   const int n = pc->n;
   int i;
3c7f2f55
 
e1791bb1
   for (i = 0; i < n; ++i)
     {
       const struct plugin *p = &pc->plugins[i];
       void *cc = cli->per_client_context[i];
3c7f2f55
 
e1791bb1
       if (p->client_destructor && cc)
 	(*p->client_destructor)(p->plugin_handle, cc);
3c7f2f55
     }
e1791bb1
   CLEAR (*cli);
3c7f2f55
 }
 
6fbf66fa
 struct plugin_list *
3c7f2f55
 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);
e1791bb1
   plugin_per_client_init (pl->common, &pl->per_client, -1);
3c7f2f55
   return pl;
 }
 
 static struct plugin_common *
e1791bb1
 plugin_common_init (const struct plugin_option_list *list)
6fbf66fa
 {
   int i;
3c7f2f55
   struct plugin_common *pc;
6fbf66fa
 
3c7f2f55
   ALLOC_OBJ_CLEAR (pc, struct plugin_common);
6fbf66fa
 
e1791bb1
   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;
 
5a2e9a25
   envp = make_env_array (es, false, &gc);
6fbf66fa
 
3c7f2f55
   if (pr)
     plugin_return_init (pr);
 
e1791bb1
   for (i = 0; i < pc->n; ++i)
6fbf66fa
     {
e1791bb1
       plugin_open_item (&pc->plugins[i],
3c7f2f55
 			&list->plugins[i],
 			pr ? &pr->list[i] : NULL,
e1791bb1
 			envp,
 			init_point);
6fbf66fa
     }
 
3c7f2f55
   if (pr)
     pr->n = i;
 
6fbf66fa
   gc_free (&gc);
3c7f2f55
 }
 
 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 *
e1791bb1
 plugin_list_init (const struct plugin_option_list *list)
3c7f2f55
 {
   struct plugin_list *pl;
   ALLOC_OBJ_CLEAR (pl, struct plugin_list);
e1791bb1
   pl->common = plugin_common_init (list);
3c7f2f55
   pl->common_owned = true;
6fbf66fa
   return pl;
 }
 
e1791bb1
 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);
 }
 
6fbf66fa
 int
3c7f2f55
 plugin_call (const struct plugin_list *pl,
 	     const int type,
5a2e9a25
 	     const struct argv *av,
3c7f2f55
 	     struct plugin_return *pr,
 	     struct env_set *es)
6fbf66fa
 {
3c7f2f55
   if (pr)
     plugin_return_init (pr);
6fbf66fa
 
   if (plugin_defined (pl, type))
     {
       struct gc_arena gc = gc_new ();
       int i;
       const char **envp;
3c7f2f55
       const int n = plugin_n (pl);
47ae8457
       bool success = false;
344ee918
       bool error = false;
       bool deferred = false;
6fbf66fa
       
       mutex_lock_static (L_PLUGIN);
 
       setenv_del (es, "script_type");
5a2e9a25
       envp = make_env_array (es, false, &gc);
6fbf66fa
 
3c7f2f55
       for (i = 0; i < n; ++i)
6fbf66fa
 	{
344ee918
 	  const int status = plugin_call_item (&pl->common->plugins[i],
 					       pl->per_client.per_client_context[i],
 					       type,
5a2e9a25
 					       av,
344ee918
 					       pr ? &pr->list[i] : NULL,
 					       envp);
47ae8457
 	  switch (status)
 	    {
 	    case OPENVPN_PLUGIN_FUNC_SUCCESS:
 	      success = true;
 	      break;
 	    case OPENVPN_PLUGIN_FUNC_DEFERRED:
 	      deferred = true;
 	      break;
 	    default:
 	      error = true;
 	      break;
 	    }
6fbf66fa
 	}
 
3c7f2f55
       if (pr)
 	pr->n = i;
 
6fbf66fa
       mutex_unlock_static (L_PLUGIN);
 
       gc_free (&gc);
 
47ae8457
       if (type == OPENVPN_PLUGIN_ENABLE_PF && success)
 	return OPENVPN_PLUGIN_FUNC_SUCCESS;
       else if (error)
344ee918
 	return OPENVPN_PLUGIN_FUNC_ERROR;
       else if (deferred)
 	return OPENVPN_PLUGIN_FUNC_DEFERRED;
3c7f2f55
     }
344ee918
 
   return OPENVPN_PLUGIN_FUNC_SUCCESS;
6fbf66fa
 }
 
 void
 plugin_list_close (struct plugin_list *pl)
 {
   if (pl)
     {
3c7f2f55
       if (pl->common)
 	{
 	  plugin_per_client_destroy (pl->common, &pl->per_client);
 
 	  if (pl->common_owned)
 	    plugin_common_close (pl->common);
 	}
 
6fbf66fa
       free (pl);
     }
 }
 
 void
 plugin_abort (void)
 {
3c7f2f55
   struct plugin_common *pc = static_plugin_common;
   static_plugin_common = NULL;
   if (pc)
6fbf66fa
     {
       int i;
 
3c7f2f55
       for (i = 0; i < pc->n; ++i)
 	plugin_abort_item (&pc->plugins[i]);
6fbf66fa
     }
 }
 
 bool
 plugin_defined (const struct plugin_list *pl, const int type)
 {
   bool ret = false;
3c7f2f55
 
6fbf66fa
   if (pl)
     {
3c7f2f55
       const struct plugin_common *pc = pl->common;
 
       if (pc)
6fbf66fa
 	{
3c7f2f55
 	  int i;
 	  const unsigned int mask = OPENVPN_PLUGIN_MASK (type);
 	  for (i = 0; i < pc->n; ++i)
6fbf66fa
 	    {
3c7f2f55
 	      if (pc->plugins[i].plugin_type_mask & mask)
 		{
 		  ret = true;
 		  break;
 		}
6fbf66fa
 	    }
 	}
     }
   return ret;
 }
 
3c7f2f55
 /*
  * Plugin return functions
  */
 
 static void
 openvpn_plugin_string_list_item_free (struct openvpn_plugin_string_list *l)
 {
   if (l)
     {
       free (l->name);
d40f2b20
       string_clear (l->value);
3c7f2f55
       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
 
6fbf66fa
 #else
 static void dummy(void) {}
 #endif /* ENABLE_PLUGIN */