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