new file mode 100644
@@ -0,0 +1,33 @@
+OpenVPN plugin examples.
+
+Examples provided:
+
+sample-client-connect.c
+
+ - hook to all plugin hooks that openvpn offers
+ - log which hook got called
+ - on CLIENT_CONNECT or CLIENT_CONNECT_V2 set some config variables
+ (push "route 192.0.2.x" and other easy recognizeable things)
+
+ - if the environment variable UV_WANT_CC_FAIL is set, fail
+ - if the environment variable UV_WANT_CC_DISABLE is set, reject ("disable")
+ - if the environment variable UV_WANT_CC_ASYNC is set, go to
+ asynchronous/deferred mode on CLIENT_CONNECT, and sleep for
+ ${UV_WANT_CC_ASYNC} seconds
+
+ - if the environment variable UV_WANT_CC2_FAIL is set, fail CC2
+ - if the environment variable UV_WANT_CC2_DISABLE is set, reject ("disable")
+ - if the environment variable UV_WANT_CC2_ASYNC is set, go to
+ asynchronous/deferred mode on CLIENT_CONNECT_V2, and sleep for
+ ${UV_WANT_CC2_ASYNC} seconds
+
+ (this can be client-controlled with --setenv UV_WANT_CC_ASYNC nnn
+ etc. --> for easy testing server code paths)
+
+To build:
+
+ ./build sample-client-connect (Linux/BSD/etc.)
+
+To use in OpenVPN, add to config file:
+
+ plugin sample-client-connect.so (Linux/BSD/etc.)
new file mode 100755
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+#
+# Build an OpenVPN plugin module on *nix. The argument should
+# be the base name of the C source file (without the .c).
+#
+
+# This directory is where we will look for openvpn-plugin.h
+CPPFLAGS="${CPPFLAGS:--I../../../include}"
+
+CC="${CC:-gcc}"
+CFLAGS="${CFLAGS:--O2 -Wall -Wno-unused-variable -g}"
+
+$CC $CPPFLAGS $CFLAGS -fPIC -c $1.c && \
+$CC $CFLAGS -fPIC -shared $LDFLAGS -Wl,-soname,$1.so -o $1.so $1.o -lc
new file mode 100644
@@ -0,0 +1,375 @@
+/*
+ * 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.
+ *
+ * Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
+ *
+ * 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; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * This file implements a simple OpenVPN plugin module which
+ * will log the calls made, and send back some config statements
+ * when called on the CLIENT_CONNECT and CLIENT_CONNECT_V2 hooks.
+ *
+ * it can be asked to fail or go to async/deferred mode by setting
+ * environment variables (UV_WANT_CC_FAIL, UV_WANT_CC_ASYNC,
+ * UV_WANT_CC2_ASYNC) - mostly used as a testing vehicle for the
+ * server side code to handle these cases
+ *
+ * See the README file for build instructions.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "openvpn-plugin.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "openvpn-plugin.h"
+
+/* Pointers to functions exported from openvpn */
+static plugin_log_t plugin_log = NULL;
+static plugin_secure_memzero_t plugin_secure_memzero = NULL;
+static plugin_base64_decode_t plugin_base64_decode = NULL;
+
+/* module name for plugin_log() */
+static char *MODULE = "sample-cc";
+
+/*
+ * Our context, where we keep our state.
+ */
+
+struct plugin_context {
+ int verb; /* logging verbosity */
+};
+
+/* TODO: see if this should go (together with constructor/destructor)
+ * or if we can make it into a useful demo
+ */
+struct plugin_per_client_context {
+ int n_calls;
+ bool generated_pf_file;
+};
+
+/*
+ * Given an environmental variable name, search
+ * the envp array for its value, returning it
+ * if found or NULL otherwise.
+ */
+static const char *
+get_env(const char *name, const char *envp[])
+{
+ if (envp)
+ {
+ int i;
+ const int namelen = strlen(name);
+ for (i = 0; envp[i]; ++i)
+ {
+ if (!strncmp(envp[i], name, namelen))
+ {
+ const char *cp = envp[i] + namelen;
+ if (*cp == '=')
+ {
+ return cp + 1;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+
+static int
+atoi_null0(const char *str)
+{
+ if (str)
+ {
+ return atoi(str);
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+/* use v3 functions so we can use openvpn's logging and base64 etc. */
+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)
+{
+ const char **argv = args->argv;
+ const char **envp = args->envp;
+
+ /* Check API compatibility -- struct version 5 or higher needed */
+ if (v3structver < 5)
+ {
+ fprintf(stderr, "sample-client-connect: this plugin is incompatible with the running version of OpenVPN\n");
+ return OPENVPN_PLUGIN_FUNC_ERROR;
+ }
+
+ /*
+ * Allocate our context
+ */
+ struct plugin_context *context = calloc(1, sizeof(struct plugin_context));
+ if (!context)
+ {
+ goto error;
+ }
+
+ /*
+ * Intercept just about everything...
+ */
+ ret->type_mask =
+ OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_UP)
+ |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_DOWN)
+ |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_ROUTE_UP)
+ |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_IPCHANGE)
+ |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_VERIFY)
+ |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT)
+ |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT_V2)
+ |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_DISCONNECT)
+ |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_LEARN_ADDRESS)
+ |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_FINAL);
+
+ /* Save global pointers to functions exported from openvpn */
+ plugin_log = args->callbacks->plugin_log;
+ plugin_secure_memzero = args->callbacks->plugin_secure_memzero;
+ plugin_base64_decode = args->callbacks->plugin_base64_decode;
+
+ /*
+ * Get verbosity level from environment
+ */
+ context->verb = atoi_null0(get_env("verb", envp));
+
+ ret->handle = (openvpn_plugin_handle_t *) context;
+ plugin_log(PLOG_NOTE, MODULE, "initialization succeeded");
+ return OPENVPN_PLUGIN_FUNC_SUCCESS;
+
+error:
+ if (context)
+ {
+ free(context);
+ }
+ return OPENVPN_PLUGIN_FUNC_ERROR;
+}
+
+
+/* there are two possible interfaces for an openvpn plugin how
+ * to be called on "client connect", which primarily differ in the
+ * way config options are handed back to the client instance
+ * (see openvpn/multi.c, multi_client_connect_call_plugin_{v1,v2}())
+ *
+ * OPENVPN_PLUGIN_CLIENT_CONNECT
+ * openvpn creates a temp file and passes the name to the plugin
+ * (via argv[1] variable, argv[0] is the name of the plugin)
+ * the plugin can write config statements to that file, and openvpn
+ * reads it in like a "ccd/$cn" per-client config file
+ *
+ * OPENVPN_PLUGIN_CLIENT_CONNECT_V2
+ * the caller passes in a pointer to an "openvpn_plugin_string_list"
+ * (openvpn-plugin.h), which is a linked list of (name,value) pairs
+ *
+ * we fill in one node with name="config" and value="our config"
+ *
+ * both "l" and "l->name" and "l->value" are malloc()ed by the plugin
+ * and free()ed by the caller (openvpn_plugin_string_list_free())
+ */
+
+int
+openvpn_plugin_client_connect(struct plugin_context *context,
+ const char **argv,
+ const char **envp)
+{
+ /* log environment variables handed to us by OpenVPN, but
+ * only if "setenv verb" is 3 or higher (arbitrary number)
+ */
+ if (context->verb>=3)
+ {
+ for (int i = 0; argv[i]; i++)
+ {
+ plugin_log(PLOG_NOTE, MODULE, "per-client argv: %s", argv[i]);
+ }
+ for (int i = 0; envp[i]; i++)
+ {
+ plugin_log(PLOG_NOTE, MODULE, "per-client env: %s", envp[i]);
+ }
+ }
+
+ /* by setting "UV_WANT_CC_FAIL" we can be triggered to fail */
+ const char *p = get_env("UV_WANT_CC_FAIL", envp);
+ if (p)
+ {
+ plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC_FAIL=%s -> fail", p);
+ return OPENVPN_PLUGIN_FUNC_ERROR;
+ }
+
+ /* does the caller want options? give them some */
+ if (argv[1])
+ {
+ FILE *fp = fopen(argv[1],"w");
+ if (!fp)
+ {
+ plugin_log(PLOG_ERR, MODULE, "fopen('%s') failed", argv[1]);
+ return OPENVPN_PLUGIN_FUNC_ERROR;
+ }
+ fprintf(fp, "push \"route 192.0.2.1 255.255.255.255\"\n");
+ fprintf(fp, "push \"echo sample-cc plugin 1 called\"\n");
+ if (get_env("UV_WANT_CC_DISABLE", envp))
+ {
+ plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC_DISABLE, reject");
+ fprintf(fp, "disable\n");
+ }
+ fclose(fp);
+ }
+
+ return OPENVPN_PLUGIN_FUNC_SUCCESS;
+}
+
+int
+openvpn_plugin_client_connect_v2(struct plugin_context *context,
+ const char **envp,
+ struct openvpn_plugin_string_list **return_list)
+{
+ /* by setting "UV_WANT_CC2_FAIL" we can be triggered to fail here */
+ const char *p = get_env("UV_WANT_CC2_FAIL", envp);
+ if (p)
+ {
+ plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC2_FAIL=%s -> fail", p);
+ return OPENVPN_PLUGIN_FUNC_ERROR;
+ }
+
+ struct openvpn_plugin_string_list *rl =
+ calloc(1, sizeof(struct openvpn_plugin_string_list));
+ if (!rl)
+ {
+ plugin_log(PLOG_ERR, MODULE, "malloc(return_list) failed");
+ return OPENVPN_PLUGIN_FUNC_ERROR;
+ }
+ rl->name = strdup("config");
+ if (get_env("UV_WANT_CC2_DISABLE", envp))
+ {
+ plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC2_DISABLE, reject");
+ rl->value = strdup("disable\n");
+ }
+ else
+ {
+ rl->value = strdup("push \"route 192.0.2.2 255.255.255.255\"\npush \"setenv MOOH\"\n");
+ }
+
+ if (!rl->name || !rl->value)
+ {
+ plugin_log(PLOG_ERR, MODULE, "malloc(return_list->xx) failed");
+ return OPENVPN_PLUGIN_FUNC_ERROR;
+ }
+
+ *return_list = rl;
+
+ return OPENVPN_PLUGIN_FUNC_SUCCESS;
+}
+
+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)
+{
+ struct plugin_context *context = (struct plugin_context *) handle;
+ struct plugin_per_client_context *pcc = (struct plugin_per_client_context *) per_client_context;
+
+ /* for most functions, we just "don't do anything" but log the
+ * event received (so one can follow it in the log and understand
+ * the sequence of events). CONNECT and CONNECT_V2 are handled
+ */
+ switch (type)
+ {
+ case OPENVPN_PLUGIN_UP:
+ plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_UP");
+ break;
+
+ case OPENVPN_PLUGIN_DOWN:
+ plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_DOWN");
+ break;
+
+ case OPENVPN_PLUGIN_ROUTE_UP:
+ plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_ROUTE_UP");
+ break;
+
+ case OPENVPN_PLUGIN_IPCHANGE:
+ plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_IPCHANGE");
+ break;
+
+ case OPENVPN_PLUGIN_TLS_VERIFY:
+ plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_TLS_VERIFY");
+ break;
+
+ case OPENVPN_PLUGIN_CLIENT_CONNECT:
+ plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_CLIENT_CONNECT");
+ return openvpn_plugin_client_connect(context, argv, envp);
+
+ case OPENVPN_PLUGIN_CLIENT_CONNECT_V2:
+ plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_CLIENT_CONNECT_V2");
+ return openvpn_plugin_client_connect_v2(context, envp,
+ return_list);
+
+ case OPENVPN_PLUGIN_CLIENT_DISCONNECT:
+ plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_CLIENT_DISCONNECT");
+ break;
+
+ case OPENVPN_PLUGIN_LEARN_ADDRESS:
+ plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_LEARN_ADDRESS");
+ break;
+
+ case OPENVPN_PLUGIN_TLS_FINAL:
+ plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_TLS_FINAL");
+ break;
+
+ default:
+ plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_? type=%d\n", type);
+ }
+ return OPENVPN_PLUGIN_FUNC_SUCCESS;
+}
+
+OPENVPN_EXPORT void *
+openvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)
+{
+ printf("FUNC: openvpn_plugin_client_constructor_v1\n");
+ return calloc(1, sizeof(struct plugin_per_client_context));
+}
+
+OPENVPN_EXPORT void
+openvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle, void *per_client_context)
+{
+ printf("FUNC: openvpn_plugin_client_destructor_v1\n");
+ free(per_client_context);
+}
+
+OPENVPN_EXPORT void
+openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
+{
+ struct plugin_context *context = (struct plugin_context *) handle;
+ printf("FUNC: openvpn_plugin_close_v1\n");
+ free(context);
+}