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