1 /*
2  *  OpenVPN -- An application to securely tunnel IP networks
3  *             over a single TCP/UDP port, with support for SSL/TLS-based
4  *             session authentication and key exchange,
5  *             packet encryption, packet authentication, and
6  *             packet compression.
7  *
8  *  Copyright (C) 2002-2022 OpenVPN Inc <sales@openvpn.net>
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License version 2
12  *  as published by the Free Software Foundation.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License along
20  *  with this program; if not, write to the Free Software Foundation, Inc.,
21  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23 
24 /*
25  * This file implements a simple OpenVPN plugin module which
26  * will log the calls made, and send back some config statements
27  * when called on the CLIENT_CONNECT and CLIENT_CONNECT_V2 hooks.
28  *
29  * it can be asked to fail or go to async/deferred mode by setting
30  * environment variables (UV_WANT_CC_FAIL, UV_WANT_CC_ASYNC,
31  * UV_WANT_CC2_ASYNC) - mostly used as a testing vehicle for the
32  * server side code to handle these cases
33  *
34  * See the README file for build instructions and env control variables.
35  */
36 
37 /* strdup() might need special defines to be visible in <string.h> */
38 #include "config.h"
39 
40 #include <stdio.h>
41 #include <string.h>
42 #include <stdlib.h>
43 #include <stdbool.h>
44 #include <unistd.h>
45 #include <fcntl.h>
46 #include <sys/wait.h>
47 
48 #include "openvpn-plugin.h"
49 
50 /* Pointers to functions exported from openvpn */
51 static plugin_log_t plugin_log = NULL;
52 static plugin_secure_memzero_t plugin_secure_memzero = NULL;
53 static plugin_base64_decode_t plugin_base64_decode = NULL;
54 
55 /* module name for plugin_log() */
56 static char *MODULE = "sample-cc";
57 
58 /*
59  * Our context, where we keep our state.
60  */
61 
62 struct plugin_context {
63     int verb;                           /* logging verbosity */
64 };
65 
66 /* this is used for the CLIENT_CONNECT_V2 async/deferred handler
67  *
68  * the "CLIENT_CONNECT_V2" handler puts per-client information into
69  * this, and the "CLIENT_CONNECT_DEFER_V2" handler looks at it to see
70  * if it's time yet to succeed/fail
71  */
72 struct plugin_per_client_context {
73     time_t sleep_until;                 /* wakeup time (time() + sleep) */
74     bool want_fail;
75     bool want_disable;
76     const char *client_config;
77 };
78 
79 /*
80  * Given an environmental variable name, search
81  * the envp array for its value, returning it
82  * if found or NULL otherwise.
83  */
84 static const char *
get_env(const char * name,const char * envp[])85 get_env(const char *name, const char *envp[])
86 {
87     if (envp)
88     {
89         int i;
90         const int namelen = strlen(name);
91         for (i = 0; envp[i]; ++i)
92         {
93             if (!strncmp(envp[i], name, namelen))
94             {
95                 const char *cp = envp[i] + namelen;
96                 if (*cp == '=')
97                 {
98                     return cp + 1;
99                 }
100             }
101         }
102     }
103     return NULL;
104 }
105 
106 
107 static int
atoi_null0(const char * str)108 atoi_null0(const char *str)
109 {
110     if (str)
111     {
112         return atoi(str);
113     }
114     else
115     {
116         return 0;
117     }
118 }
119 
120 /* use v3 functions so we can use openvpn's logging and base64 etc. */
121 OPENVPN_EXPORT int
openvpn_plugin_open_v3(const int v3structver,struct openvpn_plugin_args_open_in const * args,struct openvpn_plugin_args_open_return * ret)122 openvpn_plugin_open_v3(const int v3structver,
123                        struct openvpn_plugin_args_open_in const *args,
124                        struct openvpn_plugin_args_open_return *ret)
125 {
126     /* const char **argv = args->argv; */ /* command line arguments (unused) */
127     const char **envp = args->envp;       /* environment variables */
128 
129     /* Check API compatibility -- struct version 5 or higher needed */
130     if (v3structver < 5)
131     {
132         fprintf(stderr, "sample-client-connect: this plugin is incompatible with the running version of OpenVPN\n");
133         return OPENVPN_PLUGIN_FUNC_ERROR;
134     }
135 
136     /*
137      * Allocate our context
138      */
139     struct plugin_context *context = calloc(1, sizeof(struct plugin_context));
140     if (!context)
141     {
142         goto error;
143     }
144 
145     /*
146      * Intercept just about everything...
147      */
148     ret->type_mask =
149         OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_UP)
150         |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_DOWN)
151         |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_ROUTE_UP)
152         |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_IPCHANGE)
153         |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_VERIFY)
154         |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT)
155         |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT_V2)
156         |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2)
157         |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_DISCONNECT)
158         |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_LEARN_ADDRESS)
159         |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_FINAL);
160 
161     /* Save global pointers to functions exported from openvpn */
162     plugin_log = args->callbacks->plugin_log;
163     plugin_secure_memzero = args->callbacks->plugin_secure_memzero;
164     plugin_base64_decode = args->callbacks->plugin_base64_decode;
165 
166     /*
167      * Get verbosity level from environment
168      */
169     context->verb = atoi_null0(get_env("verb", envp));
170 
171     ret->handle = (openvpn_plugin_handle_t *) context;
172     plugin_log(PLOG_NOTE, MODULE, "initialization succeeded");
173     return OPENVPN_PLUGIN_FUNC_SUCCESS;
174 
175 error:
176     if (context)
177     {
178         free(context);
179     }
180     return OPENVPN_PLUGIN_FUNC_ERROR;
181 }
182 
183 
184 /* there are two possible interfaces for an openvpn plugin how
185  * to be called on "client connect", which primarily differ in the
186  * way config options are handed back to the client instance
187  * (see openvpn/multi.c, multi_client_connect_call_plugin_{v1,v2}())
188  *
189  * OPENVPN_PLUGIN_CLIENT_CONNECT
190  *   openvpn creates a temp file and passes the name to the plugin
191  *    (via argv[1] variable, argv[0] is the name of the plugin)
192  *   the plugin can write config statements to that file, and openvpn
193  *    reads it in like a "ccd/$cn" per-client config file
194  *
195  * OPENVPN_PLUGIN_CLIENT_CONNECT_V2
196  *   the caller passes in a pointer to an "openvpn_plugin_string_list"
197  *   (openvpn-plugin.h), which is a linked list of (name,value) pairs
198  *
199  *   we fill in one node with name="config" and value="our config"
200  *
201  *   both "l" and "l->name" and "l->value" are malloc()ed by the plugin
202  *   and free()ed by the caller (openvpn_plugin_string_list_free())
203  */
204 
205 /* helper function to write actual "here are your options" file,
206  * called from sync and sync handler
207  */
208 int
write_cc_options_file(const char * name,const char ** envp)209 write_cc_options_file(const char *name, const char **envp)
210 {
211     if (!name)
212     {
213         return OPENVPN_PLUGIN_FUNC_SUCCESS;
214     }
215 
216     FILE *fp = fopen(name,"w");
217     if (!fp)
218     {
219         plugin_log(PLOG_ERR, MODULE, "fopen('%s') failed", name);
220         return OPENVPN_PLUGIN_FUNC_ERROR;
221     }
222 
223     /* config to-be-sent can come from "setenv plugin_cc_config" in openvpn */
224     const char *p = get_env("plugin_cc_config", envp);
225     if (p)
226     {
227         fprintf(fp, "%s\n", p);
228     }
229 
230     /* some generic config snippets so we know it worked */
231     fprintf(fp, "push \"echo sample-cc plugin 1 called\"\n");
232 
233     /* if the caller wants, reject client by means of "disable" option */
234     if (get_env("UV_WANT_CC_DISABLE", envp))
235     {
236         plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC_DISABLE, reject");
237         fprintf(fp, "disable\n");
238     }
239     fclose(fp);
240 
241     return OPENVPN_PLUGIN_FUNC_SUCCESS;
242 }
243 
244 int
cc_handle_deferred_v1(int seconds,const char * name,const char ** envp)245 cc_handle_deferred_v1(int seconds, const char *name, const char **envp)
246 {
247     const char *ccd_file = get_env("client_connect_deferred_file", envp);
248     if (!ccd_file)
249     {
250         plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC_ASYNC=%d, but "
251                    "'client_connect_deferred_file' not set -> fail", seconds);
252         return OPENVPN_PLUGIN_FUNC_ERROR;
253     }
254 
255     /* the CLIENT_CONNECT (v1) API is a bit tricky to work with, because
256      * completition can be signalled both by the "deferred_file" and by
257      * the new ...CLIENT_CONNECT_DEFER API - which is optional.
258      *
259      * For OpenVPN to be able to differenciate, we must create the file
260      * right away if we want to use that for signalling.
261      */
262     int fd = open(ccd_file, O_WRONLY);
263     if (fd < 0)
264     {
265         plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "open('%s') failed", ccd_file);
266         return OPENVPN_PLUGIN_FUNC_ERROR;
267     }
268 
269     if (write(fd, "2", 1) != 1)
270     {
271         plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "write to '%s' failed", ccd_file );
272         close(fd);
273         return OPENVPN_PLUGIN_FUNC_ERROR;
274     }
275     close(fd);
276 
277     /* we do not want to complicate our lives with having to wait()
278      * for child processes (so they are not zombiefied) *and* we MUST NOT
279      * fiddle with signal handlers (= shared with openvpn main), so
280      * we use double-fork() trick.
281      */
282 
283     /* fork, sleep, succeed/fail according to env vars */
284     pid_t p1 = fork();
285     if (p1 < 0)                 /* Fork failed */
286     {
287         return OPENVPN_PLUGIN_FUNC_ERROR;
288     }
289     if (p1 > 0)                 /* parent process */
290     {
291         waitpid(p1, NULL, 0);
292         return OPENVPN_PLUGIN_FUNC_DEFERRED;
293     }
294 
295     /* first gen child process, fork() again and exit() right away */
296     pid_t p2 = fork();
297     if (p2 < 0)
298     {
299         plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: fork(2) failed");
300         exit(1);
301     }
302     if (p2 > 0)                 /* new parent: exit right away */
303     {
304         exit(0);
305     }
306 
307     /* (grand-)child process
308      *  - never call "return" now (would mess up openvpn)
309      *  - return status is communicated by file
310      *  - then exit()
311      */
312 
313     /* do mighty complicated work that will really take time here... */
314     plugin_log(PLOG_NOTE, MODULE, "in async/deferred handler, sleep(%d)", seconds);
315     sleep(seconds);
316 
317     /* write config options to openvpn */
318     int ret = write_cc_options_file(name, envp);
319 
320     /* by setting "UV_WANT_CC_FAIL" we can be triggered to fail */
321     const char *p = get_env("UV_WANT_CC_FAIL", envp);
322     if (p)
323     {
324         plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC_FAIL=%s -> fail", p);
325         ret = OPENVPN_PLUGIN_FUNC_ERROR;
326     }
327 
328     /* now signal success/failure state to openvpn */
329     fd = open(ccd_file, O_WRONLY);
330     if (fd < 0)
331     {
332         plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "open('%s') failed", ccd_file);
333         exit(1);
334     }
335 
336     plugin_log(PLOG_NOTE, MODULE, "cc_handle_deferred_v1: done, signalling %s",
337                (ret == OPENVPN_PLUGIN_FUNC_SUCCESS) ? "success" : "fail" );
338 
339     if (write(fd, (ret == OPENVPN_PLUGIN_FUNC_SUCCESS) ? "1" : "0", 1) != 1)
340     {
341         plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "write to '%s' failed", ccd_file );
342     }
343     close(fd);
344 
345     exit(0);
346 }
347 
348 int
openvpn_plugin_client_connect(struct plugin_context * context,const char ** argv,const char ** envp)349 openvpn_plugin_client_connect(struct plugin_context *context,
350                               const char **argv,
351                               const char **envp)
352 {
353     /* log environment variables handed to us by OpenVPN, but
354      * only if "setenv verb" is 3 or higher (arbitrary number)
355      */
356     if (context->verb>=3)
357     {
358         for (int i = 0; argv[i]; i++)
359         {
360             plugin_log(PLOG_NOTE, MODULE, "per-client argv: %s", argv[i]);
361         }
362         for (int i = 0; envp[i]; i++)
363         {
364             plugin_log(PLOG_NOTE, MODULE, "per-client env: %s", envp[i]);
365         }
366     }
367 
368     /* by setting "UV_WANT_CC_ASYNC" we go to async/deferred mode */
369     const char *p = get_env("UV_WANT_CC_ASYNC", envp);
370     if (p)
371     {
372         /* the return value will usually be OPENVPN_PLUGIN_FUNC_DEFERRED
373          * ("I will do my job in the background, check the status file!")
374          * but depending on env setup it might be "..._ERRROR"
375          */
376         return cc_handle_deferred_v1(atoi(p), argv[1], envp);
377     }
378 
379     /* -- this is synchronous mode (openvpn waits for us) -- */
380 
381     /* by setting "UV_WANT_CC_FAIL" we can be triggered to fail */
382     p = get_env("UV_WANT_CC_FAIL", envp);
383     if (p)
384     {
385         plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC_FAIL=%s -> fail", p);
386         return OPENVPN_PLUGIN_FUNC_ERROR;
387     }
388 
389     /* does the caller want options?  give them some */
390     int ret = write_cc_options_file(argv[1], envp);
391 
392     return ret;
393 }
394 
395 int
openvpn_plugin_client_connect_v2(struct plugin_context * context,struct plugin_per_client_context * pcc,const char ** envp,struct openvpn_plugin_string_list ** return_list)396 openvpn_plugin_client_connect_v2(struct plugin_context *context,
397                                  struct plugin_per_client_context *pcc,
398                                  const char **envp,
399                                  struct openvpn_plugin_string_list **return_list)
400 {
401     /* by setting "UV_WANT_CC2_ASYNC" we go to async/deferred mode */
402     const char *want_async = get_env("UV_WANT_CC2_ASYNC", envp);
403     const char *want_fail = get_env("UV_WANT_CC2_FAIL", envp);
404     const char *want_disable = get_env("UV_WANT_CC2_DISABLE", envp);
405 
406     /* config to push towards client - can be controlled by OpenVPN
407      * config ("setenv plugin_cc2_config ...") - mostly useful in a
408      * regression test environment to push stuff like routes which are
409      * then verified by t_client ping tests
410      */
411     const char *client_config = get_env("plugin_cc2_config", envp);
412     if (!client_config)
413     {
414         /* pick something meaningless which can be verified in client log */
415         client_config = "push \"setenv CC2 MOOH\"\n";
416     }
417 
418     if (want_async)
419     {
420         /* we do no really useful work here, so we just tell the
421          * "CLIENT_CONNECT_DEFER_V2" handler that it should sleep
422          * and then "do things" via the per-client-context
423          */
424         pcc->sleep_until = time(NULL) + atoi(want_async);
425         pcc->want_fail = (want_fail != NULL);
426         pcc->want_disable = (want_disable != NULL);
427         pcc->client_config = client_config;
428         plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC2_ASYNC=%s -> set up deferred handler", want_async);
429         return OPENVPN_PLUGIN_FUNC_DEFERRED;
430     }
431 
432     /* by setting "UV_WANT_CC2_FAIL" we can be triggered to fail here */
433     if (want_fail)
434     {
435         plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC2_FAIL=%s -> fail", want_fail);
436         return OPENVPN_PLUGIN_FUNC_ERROR;
437     }
438 
439     struct openvpn_plugin_string_list *rl =
440         calloc(1, sizeof(struct openvpn_plugin_string_list));
441     if (!rl)
442     {
443         plugin_log(PLOG_ERR, MODULE, "malloc(return_list) failed");
444         return OPENVPN_PLUGIN_FUNC_ERROR;
445     }
446     rl->name = strdup("config");
447     if (want_disable)
448     {
449         plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC2_DISABLE, reject");
450         rl->value = strdup("disable\n");
451     }
452     else
453     {
454         rl->value = strdup(client_config);
455     }
456 
457     if (!rl->name || !rl->value)
458     {
459         plugin_log(PLOG_ERR, MODULE, "malloc(return_list->xx) failed");
460         return OPENVPN_PLUGIN_FUNC_ERROR;
461     }
462 
463     *return_list = rl;
464 
465     return OPENVPN_PLUGIN_FUNC_SUCCESS;
466 }
467 
468 int
openvpn_plugin_client_connect_defer_v2(struct plugin_context * context,struct plugin_per_client_context * pcc,struct openvpn_plugin_string_list ** return_list)469 openvpn_plugin_client_connect_defer_v2(struct plugin_context *context,
470                                        struct plugin_per_client_context *pcc,
471                                        struct openvpn_plugin_string_list
472                                        **return_list)
473 {
474     time_t time_left = pcc->sleep_until - time(NULL);
475     plugin_log(PLOG_NOTE, MODULE, "defer_v2: seconds left=%d",
476                (int) time_left);
477 
478     /* not yet due? */
479     if (time_left > 0)
480     {
481         return OPENVPN_PLUGIN_FUNC_DEFERRED;
482     }
483 
484     /* client wants fail? */
485     if (pcc->want_fail)
486     {
487         plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC2_FAIL -> fail" );
488         return OPENVPN_PLUGIN_FUNC_ERROR;
489     }
490 
491     /* fill in RL according to with-disable / without-disable */
492 
493     /* TODO: unify this with non-deferred case */
494     struct openvpn_plugin_string_list *rl =
495         calloc(1, sizeof(struct openvpn_plugin_string_list));
496     if (!rl)
497     {
498         plugin_log(PLOG_ERR, MODULE, "malloc(return_list) failed");
499         return OPENVPN_PLUGIN_FUNC_ERROR;
500     }
501     rl->name = strdup("config");
502     if (pcc->want_disable)
503     {
504         plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC2_DISABLE, reject");
505         rl->value = strdup("disable\n");
506     }
507     else
508     {
509         rl->value = strdup(pcc->client_config);
510     }
511 
512     if (!rl->name || !rl->value)
513     {
514         plugin_log(PLOG_ERR, MODULE, "malloc(return_list->xx) failed");
515         return OPENVPN_PLUGIN_FUNC_ERROR;
516     }
517 
518     *return_list = rl;
519 
520     return OPENVPN_PLUGIN_FUNC_SUCCESS;
521 }
522 
523 OPENVPN_EXPORT int
openvpn_plugin_func_v2(openvpn_plugin_handle_t handle,const int type,const char * argv[],const char * envp[],void * per_client_context,struct openvpn_plugin_string_list ** return_list)524 openvpn_plugin_func_v2(openvpn_plugin_handle_t handle,
525                        const int type,
526                        const char *argv[],
527                        const char *envp[],
528                        void *per_client_context,
529                        struct openvpn_plugin_string_list **return_list)
530 {
531     struct plugin_context *context = (struct plugin_context *) handle;
532     struct plugin_per_client_context *pcc = (struct plugin_per_client_context *) per_client_context;
533 
534     /* for most functions, we just "don't do anything" but log the
535      * event received (so one can follow it in the log and understand
536      * the sequence of events).  CONNECT and CONNECT_V2 are handled
537      */
538     switch (type)
539     {
540         case OPENVPN_PLUGIN_UP:
541             plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_UP");
542             break;
543 
544         case OPENVPN_PLUGIN_DOWN:
545             plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_DOWN");
546             break;
547 
548         case OPENVPN_PLUGIN_ROUTE_UP:
549             plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_ROUTE_UP");
550             break;
551 
552         case OPENVPN_PLUGIN_IPCHANGE:
553             plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_IPCHANGE");
554             break;
555 
556         case OPENVPN_PLUGIN_TLS_VERIFY:
557             plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_TLS_VERIFY");
558             break;
559 
560         case OPENVPN_PLUGIN_CLIENT_CONNECT:
561             plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_CLIENT_CONNECT");
562             return openvpn_plugin_client_connect(context, argv, envp);
563 
564         case OPENVPN_PLUGIN_CLIENT_CONNECT_V2:
565             plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_CLIENT_CONNECT_V2");
566             return openvpn_plugin_client_connect_v2(context, pcc, envp,
567                                                     return_list);
568 
569         case OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2:
570             plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2");
571             return openvpn_plugin_client_connect_defer_v2(context, pcc,
572                                                           return_list);
573 
574         case OPENVPN_PLUGIN_CLIENT_DISCONNECT:
575             plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_CLIENT_DISCONNECT");
576             break;
577 
578         case OPENVPN_PLUGIN_LEARN_ADDRESS:
579             plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_LEARN_ADDRESS");
580             break;
581 
582         case OPENVPN_PLUGIN_TLS_FINAL:
583             plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_TLS_FINAL");
584             break;
585 
586         default:
587             plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_? type=%d\n", type);
588     }
589     return OPENVPN_PLUGIN_FUNC_SUCCESS;
590 }
591 
592 OPENVPN_EXPORT void *
openvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)593 openvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)
594 {
595     printf("FUNC: openvpn_plugin_client_constructor_v1\n");
596     return calloc(1, sizeof(struct plugin_per_client_context));
597 }
598 
599 OPENVPN_EXPORT void
openvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle,void * per_client_context)600 openvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle, void *per_client_context)
601 {
602     printf("FUNC: openvpn_plugin_client_destructor_v1\n");
603     free(per_client_context);
604 }
605 
606 OPENVPN_EXPORT void
openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)607 openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
608 {
609     struct plugin_context *context = (struct plugin_context *) handle;
610     printf("FUNC: openvpn_plugin_close_v1\n");
611     free(context);
612 }
613