summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile21
-rw-r--r--duo_openvpn.c118
-rwxr-xr-xduo_openvpn.py79
-rw-r--r--openvpn-plugin.h461
4 files changed, 651 insertions, 28 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..c63932f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,21 @@
+
+all: duo_openvpn.so
+
+duo_openvpn.o: duo_openvpn.c
+ gcc -fPIC -c duo_openvpn.c
+
+duo_openvpn.so: duo_openvpn.o
+ gcc -fPIC -shared -Wl,-soname,duo_openvpn.so -o duo_openvpn.so duo_openvpn.o -lc
+
+install: duo_openvpn.so
+ mkdir -p /opt/duo
+ cp duo_openvpn.so /opt/duo
+ cp duo_openvpn.py /opt/duo
+ chmod 755 /opt/duo/duo_openvpn.so
+ chmod 755 /opt/duo/duo_openvpn.py
+
+uninstall:
+ rm -rf /opt/duo
+
+clean:
+ rm -f *.so *.o
diff --git a/duo_openvpn.c b/duo_openvpn.c
new file mode 100644
index 0000000..0486765
--- /dev/null
+++ b/duo_openvpn.c
@@ -0,0 +1,118 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <syslog.h>
+
+#include "openvpn-plugin.h"
+
+#define PYTHON "python"
+#define DUO_PATH "/opt/duo/duo_openvpn.py"
+
+struct context {
+ char *ikey;
+ char *skey;
+ char *host;
+};
+
+static const char *
+get_env(const char *name, const char *envp[])
+{
+ int i, namelen;
+ const char *cp;
+
+ if (envp) {
+ namelen = strlen(name);
+ for (i = 0; envp[i]; ++i) {
+ if (!strncmp(envp[i], name, namelen)) {
+ cp = envp[i] + namelen;
+ if (*cp == '=') {
+ return cp + 1;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+static int
+auth_user_pass_verify(struct context *ctx, const char *args[], const char *envp[])
+{
+ int pid;
+ const char *control, *username, *password, *ipaddr;
+ char *argv[] = { PYTHON, DUO_PATH, NULL };
+
+ control = get_env("auth_control_file", envp);
+ username = get_env("common_name", envp);
+ password = get_env("password", envp);
+ ipaddr = get_env("untrusted_ip", envp);
+
+ if (!control || !username || !password || !ipaddr) {
+ return OPENVPN_PLUGIN_FUNC_ERROR;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ return OPENVPN_PLUGIN_FUNC_ERROR;
+ }
+
+ if (pid == 0) {
+ if (ctx->ikey && ctx->skey && ctx->host) {
+ setenv("ikey", ctx->ikey, 1);
+ setenv("skey", ctx->skey, 1);
+ setenv("host", ctx->host, 1);
+ }
+
+ setenv("control", control, 1);
+ setenv("username", username, 1);
+ setenv("password", password, 1);
+ setenv("ipaddr", ipaddr, 1);
+
+ execvp(argv[0], argv);
+ exit(1);
+ }
+
+ return OPENVPN_PLUGIN_FUNC_DEFERRED;
+}
+
+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 context *ctx = (struct context *) handle;
+
+ if (type == OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY) {
+ return auth_user_pass_verify(ctx, argv, envp);
+ } else {
+ return OPENVPN_PLUGIN_FUNC_ERROR;
+ }
+}
+
+OPENVPN_EXPORT openvpn_plugin_handle_t
+openvpn_plugin_open_v2(unsigned int *type_mask, const char *argv[], const char *envp[], struct openvpn_plugin_string_list **return_list)
+{
+ struct context *ctx;
+
+ ctx = (struct context *) calloc(1, sizeof(struct context));
+
+ if (argv[1] && argv[2] && argv[3]) {
+ ctx->ikey = strdup(argv[1]);
+ ctx->skey = strdup(argv[2]);
+ ctx->host = strdup(argv[3]);
+ }
+
+ *type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY);
+
+ return (openvpn_plugin_handle_t) ctx;
+}
+
+OPENVPN_EXPORT void
+openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
+{
+ struct context *ctx = (struct context *) handle;
+
+ free(ctx->ikey);
+ free(ctx->skey);
+ free(ctx->host);
+ free(ctx);
+}
diff --git a/duo_openvpn.py b/duo_openvpn.py
index 3a7424d..d7d238e 100755
--- a/duo_openvpn.py
+++ b/duo_openvpn.py
@@ -7,10 +7,6 @@
import os, sys, urllib, hashlib, httplib, hmac, base64, json, syslog
-IKEY = ''
-SKEY = ''
-HOST = ''
-
API_RESULT_AUTH = 'auth'
API_RESULT_ALLOW = 'allow'
API_RESULT_DENY = 'deny'
@@ -69,20 +65,38 @@ def log(msg):
msg = 'Duo OpenVPN: %s' % msg
syslog.syslog(msg)
-def preauth(username):
+def success(control):
+ log('writing success code to %s' % control)
+
+ f = open(control, 'w')
+ f.write('1')
+ f.close()
+
+ sys.exit(0)
+
+def failure(control):
+ log('writing failure code to %s' % control)
+
+ f = open(control, 'w')
+ f.write('0')
+ f.close()
+
+ sys.exit(1)
+
+def preauth(ikey, skey, host, control, username):
log('pre-authentication for %s' % username)
args = {
'user': username,
}
- response = api(IKEY, SKEY, HOST, 'POST', '/rest/v1/preauth', **args)
+ response = api(ikey, skey, host, 'POST', '/rest/v1/preauth', **args)
result = response.get('result')
if not result:
log('invalid API response: %s' % response)
- sys.exit(1)
+ failure(control)
if result == API_RESULT_AUTH:
return
@@ -91,22 +105,22 @@ def preauth(username):
if not status:
log('invalid API response: %s' % response)
- sys.exit(1)
+ failure(control)
if result == API_RESULT_ENROLL:
log('user %s is not enrolled: %s' % (username, status))
- sys.exit(1)
+ failure(control)
elif result == API_RESULT_DENY:
log('preauth failure for %s: %s' % (username, status))
- sys.exit(1)
+ failure(control)
elif result == API_RESULT_ALLOW:
log('preauth success for %s: %s' % (username, status))
- sys.exit(0)
+ success(control)
else:
log('unknown preauth result: %s' % result)
- sys.exit(1)
+ failure(control)
-def auth(username, password, ipaddr):
+def auth(ikey, skey, host, control, username, password, ipaddr):
log('authentication for %s' % username)
args = {
@@ -116,47 +130,56 @@ def auth(username, password, ipaddr):
'ipaddr': ipaddr
}
- response = api(IKEY, SKEY, HOST, 'POST', '/rest/v1/auth', **args)
+ response = api(ikey, skey, host, 'POST', '/rest/v1/auth', **args)
result = response.get('result')
status = response.get('status')
if not result or not status:
log('invalid API response: %s' % response)
- sys.exit(1)
+ failure(control)
if result == API_RESULT_ALLOW:
log('auth success for %s: %s' % (username, status))
- sys.exit(0)
+ success(control)
elif result == API_RESULT_DENY:
log('auth failure for %s: %s' % (username, status))
- sys.exit(1)
+ failure(control)
else:
log('unknown auth result: %s' % result)
- sys.exit(1)
+ failure(control)
def main():
- username = os.environ.get('common_name')
+ ikey = os.environ.get('ikey')
+ skey = os.environ.get('skey')
+ host = os.environ.get('host')
+
+ if not ikey or not skey or not host:
+ log('required ikey/skey/host configuration parameters not found')
+ failure(control)
+
+ control = os.environ.get('control')
+ username = os.environ.get('username')
password = os.environ.get('password')
- ipaddr = os.environ.get('untrusted_ip', '0.0.0.0')
+ ipaddr = os.environ.get('ipaddr', '0.0.0.0')
- if not username or not password:
- log('environment variables not found')
- sys.exit(1)
+ if not control or not username or not password:
+ log('required environment variables not found')
+ failure(control)
try:
- preauth(username)
+ preauth(ikey, skey, host, control, username)
except Exception, e:
log(str(e))
- sys.exit(1)
+ failure(control)
try:
- auth(username, password, ipaddr)
+ auth(ikey, skey, host, control, username, password, ipaddr)
except Exception, e:
log(str(e))
- sys.exit(1)
+ failure(control)
- sys.exit(1)
+ failure(control)
if __name__ == '__main__':
main()
diff --git a/openvpn-plugin.h b/openvpn-plugin.h
new file mode 100644
index 0000000..173a0c1
--- /dev/null
+++ b/openvpn-plugin.h
@@ -0,0 +1,461 @@
+/*
+ * 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-2010 OpenVPN Technologies, 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 (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
+ */
+
+#define OPENVPN_PLUGIN_VERSION 2
+
+/*
+ * Plug-in types. These types correspond to the set of script callbacks
+ * supported by OpenVPN.
+ *
+ * This is the general call sequence to expect when running in server mode:
+ *
+ * Initial Server Startup:
+ *
+ * FUNC: openvpn_plugin_open_v1
+ * FUNC: openvpn_plugin_client_constructor_v1 (this is the top-level "generic"
+ * client template)
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_UP
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_ROUTE_UP
+ *
+ * New Client Connection:
+ *
+ * FUNC: openvpn_plugin_client_constructor_v1
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_ENABLE_PF
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_TLS_VERIFY (called once for every cert
+ * in the server chain)
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_TLS_FINAL
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_IPCHANGE
+ *
+ * [If OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY returned OPENVPN_PLUGIN_FUNC_DEFERRED,
+ * we don't proceed until authentication is verified via auth_control_file]
+ *
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_CLIENT_CONNECT_V2
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_LEARN_ADDRESS
+ *
+ * [Client session ensues]
+ *
+ * For each "TLS soft reset", according to reneg-sec option (or similar):
+ *
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_ENABLE_PF
+ *
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_TLS_VERIFY (called once for every cert
+ * in the server chain)
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_TLS_FINAL
+ *
+ * [If OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY returned OPENVPN_PLUGIN_FUNC_DEFERRED,
+ * we expect that authentication is verified via auth_control_file within
+ * the number of seconds defined by the "hand-window" option. Data channel traffic
+ * will continue to flow uninterrupted during this period.]
+ *
+ * [Client session continues]
+ *
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_CLIENT_DISCONNECT
+ * FUNC: openvpn_plugin_client_destructor_v1
+ *
+ * [ some time may pass ]
+ *
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_LEARN_ADDRESS (this coincides with a
+ * lazy free of initial
+ * learned addr object)
+ * Server Shutdown:
+ *
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_DOWN
+ * FUNC: openvpn_plugin_client_destructor_v1 (top-level "generic" client)
+ * FUNC: openvpn_plugin_close_v1
+ */
+#define OPENVPN_PLUGIN_UP 0
+#define OPENVPN_PLUGIN_DOWN 1
+#define OPENVPN_PLUGIN_ROUTE_UP 2
+#define OPENVPN_PLUGIN_IPCHANGE 3
+#define OPENVPN_PLUGIN_TLS_VERIFY 4
+#define OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY 5
+#define OPENVPN_PLUGIN_CLIENT_CONNECT 6
+#define OPENVPN_PLUGIN_CLIENT_DISCONNECT 7
+#define OPENVPN_PLUGIN_LEARN_ADDRESS 8
+#define OPENVPN_PLUGIN_CLIENT_CONNECT_V2 9
+#define OPENVPN_PLUGIN_TLS_FINAL 10
+#define OPENVPN_PLUGIN_ENABLE_PF 11
+#define OPENVPN_PLUGIN_N 12
+
+/*
+ * Build a mask out of a set of plug-in types.
+ */
+#define OPENVPN_PLUGIN_MASK(x) (1<<(x))
+
+/*
+ * A pointer to a plugin-defined object which contains
+ * the object state.
+ */
+typedef void *openvpn_plugin_handle_t;
+
+/*
+ * Return value for openvpn_plugin_func_v1 function
+ */
+#define OPENVPN_PLUGIN_FUNC_SUCCESS 0
+#define OPENVPN_PLUGIN_FUNC_ERROR 1
+#define OPENVPN_PLUGIN_FUNC_DEFERRED 2
+
+/*
+ * For Windows (needs to be modified for MSVC)
+ */
+#if defined(__MINGW32_VERSION) && !defined(OPENVPN_PLUGIN_H)
+# define OPENVPN_EXPORT __declspec(dllexport)
+#else
+# define OPENVPN_EXPORT
+#endif
+
+/*
+ * If OPENVPN_PLUGIN_H is defined, we know that we are being
+ * included in an OpenVPN compile, rather than a plugin compile.
+ */
+#ifdef OPENVPN_PLUGIN_H
+
+/*
+ * We are compiling OpenVPN.
+ */
+#define OPENVPN_PLUGIN_DEF typedef
+#define OPENVPN_PLUGIN_FUNC(name) (*name)
+
+#else
+
+/*
+ * We are compiling plugin.
+ */
+#define OPENVPN_PLUGIN_DEF OPENVPN_EXPORT
+#define OPENVPN_PLUGIN_FUNC(name) name
+
+#endif
+
+/*
+ * Used by openvpn_plugin_func to return structured
+ * data. The plugin should allocate all structure
+ * instances, name strings, and value strings with
+ * malloc, since OpenVPN will assume that it
+ * can free the list by calling free() over the same.
+ */
+struct openvpn_plugin_string_list
+{
+ struct openvpn_plugin_string_list *next;
+ char *name;
+ char *value;
+};
+
+/*
+ * Multiple plugin modules can be cascaded, and modules can be
+ * used in tandem with scripts. The order of operation is that
+ * the module func() functions are called in the order that
+ * the modules were specified in the config file. If a script
+ * was specified as well, it will be called last. If the
+ * return code of the module/script controls an authentication
+ * function (such as tls-verify or auth-user-pass-verify), then
+ * every module and script must return success (0) in order for
+ * the connection to be authenticated.
+ *
+ * Notes:
+ *
+ * Plugins which use a privilege-separation model (by forking in
+ * their initialization function before the main OpenVPN process
+ * downgrades root privileges and/or executes a chroot) must
+ * daemonize after a fork if the "daemon" environmental variable is
+ * set. In addition, if the "daemon_log_redirect" variable is set,
+ * the plugin should preserve stdout/stderr across the daemon()
+ * syscall. See the daemonize() function in plugin/auth-pam/auth-pam.c
+ * for an example.
+ */
+
+/*
+ * Prototypes for functions which OpenVPN plug-ins must define.
+ */
+
+/*
+ * FUNCTION: openvpn_plugin_open_v2
+ *
+ * REQUIRED: YES
+ *
+ * Called on initial plug-in load. OpenVPN will preserve plug-in state
+ * across SIGUSR1 restarts but not across SIGHUP restarts. A SIGHUP reset
+ * will cause the plugin to be closed and reopened.
+ *
+ * ARGUMENTS
+ *
+ * *type_mask : Set by OpenVPN to the logical OR of all script
+ * types which this version of OpenVPN supports. The plug-in
+ * should set this value to the logical OR of all script types
+ * which the plug-in wants to intercept. For example, if the
+ * script wants to intercept the client-connect and
+ * client-disconnect script types:
+ *
+ * *type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT)
+ * | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_DISCONNECT)
+ *
+ * argv : a NULL-terminated array of options provided to the OpenVPN
+ * "plug-in" directive. argv[0] is the dynamic library pathname.
+ *
+ * envp : a NULL-terminated array of OpenVPN-set environmental
+ * variables in "name=value" format. Note that for security reasons,
+ * these variables are not actually written to the "official"
+ * environmental variable store of the process.
+ *
+ * return_list : used to return data back to OpenVPN.
+ *
+ * RETURN VALUE
+ *
+ * An openvpn_plugin_handle_t value on success, NULL on failure
+ */
+OPENVPN_PLUGIN_DEF openvpn_plugin_handle_t OPENVPN_PLUGIN_FUNC(openvpn_plugin_open_v2)
+ (unsigned int *type_mask,
+ const char *argv[],
+ const char *envp[],
+ struct openvpn_plugin_string_list **return_list);
+
+/*
+ * FUNCTION: openvpn_plugin_func_v2
+ *
+ * Called to perform the work of a given script type.
+ *
+ * REQUIRED: YES
+ *
+ * ARGUMENTS
+ *
+ * handle : the openvpn_plugin_handle_t value which was returned by
+ * openvpn_plugin_open.
+ *
+ * type : one of the PLUGIN_x types
+ *
+ * argv : a NULL-terminated array of "command line" options which
+ * would normally be passed to the script. argv[0] is the dynamic
+ * library pathname.
+ *
+ * envp : a NULL-terminated array of OpenVPN-set environmental
+ * variables in "name=value" format. Note that for security reasons,
+ * these variables are not actually written to the "official"
+ * environmental variable store of the process.
+ *
+ * per_client_context : the per-client context pointer which was returned by
+ * openvpn_plugin_client_constructor_v1, if defined.
+ *
+ * return_list : used to return data back to OpenVPN.
+ *
+ * RETURN VALUE
+ *
+ * OPENVPN_PLUGIN_FUNC_SUCCESS on success, OPENVPN_PLUGIN_FUNC_ERROR on failure
+ *
+ * In addition, OPENVPN_PLUGIN_FUNC_DEFERRED may be returned by
+ * OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY. This enables asynchronous
+ * authentication where the plugin (or one of its agents) may indicate
+ * authentication success/failure some number of seconds after the return
+ * of the OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY handler by writing a single
+ * char to the file named by auth_control_file in the environmental variable
+ * list (envp).
+ *
+ * first char of auth_control_file:
+ * '0' -- indicates auth failure
+ * '1' -- indicates auth success
+ *
+ * OpenVPN will delete the auth_control_file after it goes out of scope.
+ *
+ * If an OPENVPN_PLUGIN_ENABLE_PF handler is defined and returns success
+ * for a particular client instance, packet filtering will be enabled for that
+ * instance. OpenVPN will then attempt to read the packet filter configuration
+ * from the temporary file named by the environmental variable pf_file. This
+ * file may be generated asynchronously and may be dynamically updated during the
+ * client session, however the client will be blocked from sending or receiving
+ * VPN tunnel packets until the packet filter file has been generated. OpenVPN
+ * will periodically test the packet filter file over the life of the client
+ * instance and reload when modified. OpenVPN will delete the packet filter file
+ * when the client instance goes out of scope.
+ *
+ * Packet filter file grammar:
+ *
+ * [CLIENTS DROP|ACCEPT]
+ * {+|-}common_name1
+ * {+|-}common_name2
+ * . . .
+ * [SUBNETS DROP|ACCEPT]
+ * {+|-}subnet1
+ * {+|-}subnet2
+ * . . .
+ * [END]
+ *
+ * Subnet: IP-ADDRESS | IP-ADDRESS/NUM_NETWORK_BITS
+ *
+ * CLIENTS refers to the set of clients (by their common-name) which
+ * this instance is allowed ('+') to connect to, or is excluded ('-')
+ * from connecting to. Note that in the case of client-to-client
+ * connections, such communication must be allowed by the packet filter
+ * configuration files of both clients.
+ *
+ * SUBNETS refers to IP addresses or IP address subnets which this
+ * instance may connect to ('+') or is excluded ('-') from connecting
+ * to.
+ *
+ * DROP or ACCEPT defines default policy when there is no explicit match
+ * for a common-name or subnet. The [END] tag must exist. A special
+ * purpose tag called [KILL] will immediately kill the client instance.
+ * A given client or subnet rule applies to both incoming and outgoing
+ * packets.
+ *
+ * See plugin/defer/simple.c for an example on using asynchronous
+ * authentication and client-specific packet filtering.
+ */
+OPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(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);
+
+/*
+ * FUNCTION: openvpn_plugin_close_v1
+ *
+ * REQUIRED: YES
+ *
+ * ARGUMENTS
+ *
+ * handle : the openvpn_plugin_handle_t value which was returned by
+ * openvpn_plugin_open.
+ *
+ * Called immediately prior to plug-in unload.
+ */
+OPENVPN_PLUGIN_DEF void OPENVPN_PLUGIN_FUNC(openvpn_plugin_close_v1)
+ (openvpn_plugin_handle_t handle);
+
+/*
+ * FUNCTION: openvpn_plugin_abort_v1
+ *
+ * REQUIRED: NO
+ *
+ * ARGUMENTS
+ *
+ * handle : the openvpn_plugin_handle_t value which was returned by
+ * openvpn_plugin_open.
+ *
+ * Called when OpenVPN is in the process of aborting due to a fatal error.
+ * Will only be called on an open context returned by a prior successful
+ * openvpn_plugin_open callback.
+ */
+OPENVPN_PLUGIN_DEF void OPENVPN_PLUGIN_FUNC(openvpn_plugin_abort_v1)
+ (openvpn_plugin_handle_t handle);
+
+/*
+ * FUNCTION: openvpn_plugin_client_constructor_v1
+ *
+ * Called to allocate a per-client memory region, which
+ * is then passed to the openvpn_plugin_func_v2 function.
+ * This function is called every time the OpenVPN server
+ * constructs a client instance object, which normally
+ * occurs when a session-initiating packet is received
+ * by a new client, even before the client has authenticated.
+ *
+ * This function should allocate the private memory needed
+ * by the plugin to track individual OpenVPN clients, and
+ * return a void * to this memory region.
+ *
+ * REQUIRED: NO
+ *
+ * ARGUMENTS
+ *
+ * handle : the openvpn_plugin_handle_t value which was returned by
+ * openvpn_plugin_open.
+ *
+ * RETURN VALUE
+ *
+ * void * pointer to plugin's private per-client memory region, or NULL
+ * if no memory region is required.
+ */
+OPENVPN_PLUGIN_DEF void * OPENVPN_PLUGIN_FUNC(openvpn_plugin_client_constructor_v1)
+ (openvpn_plugin_handle_t handle);
+
+/*
+ * FUNCTION: openvpn_plugin_client_destructor_v1
+ *
+ * This function is called on client instance object destruction.
+ *
+ * REQUIRED: NO
+ *
+ * ARGUMENTS
+ *
+ * handle : the openvpn_plugin_handle_t value which was returned by
+ * openvpn_plugin_open.
+ *
+ * per_client_context : the per-client context pointer which was returned by
+ * openvpn_plugin_client_constructor_v1, if defined.
+ */
+OPENVPN_PLUGIN_DEF void OPENVPN_PLUGIN_FUNC(openvpn_plugin_client_destructor_v1)
+ (openvpn_plugin_handle_t handle, void *per_client_context);
+
+/*
+ * FUNCTION: openvpn_plugin_select_initialization_point_v1
+ *
+ * Several different points exist in OpenVPN's initialization sequence where
+ * the openvpn_plugin_open function can be called. While the default is
+ * OPENVPN_PLUGIN_INIT_PRE_DAEMON, this function can be used to select a
+ * different initialization point. For example, if your plugin needs to
+ * return configuration parameters to OpenVPN, use
+ * OPENVPN_PLUGIN_INIT_PRE_CONFIG_PARSE.
+ *
+ * REQUIRED: NO
+ *
+ * RETURN VALUE:
+ *
+ * An OPENVPN_PLUGIN_INIT_x value.
+ */
+#define OPENVPN_PLUGIN_INIT_PRE_CONFIG_PARSE 1
+#define OPENVPN_PLUGIN_INIT_PRE_DAEMON 2 /* default */
+#define OPENVPN_PLUGIN_INIT_POST_DAEMON 3
+#define OPENVPN_PLUGIN_INIT_POST_UID_CHANGE 4
+
+OPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_select_initialization_point_v1)
+ (void);
+
+/*
+ * FUNCTION: openvpn_plugin_min_version_required_v1
+ *
+ * This function is called by OpenVPN to query the minimum
+ plugin interface version number required by the plugin.
+ *
+ * REQUIRED: NO
+ *
+ * RETURN VALUE
+ *
+ * The minimum OpenVPN plugin interface version number necessary to support
+ * this plugin.
+ */
+OPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_min_version_required_v1)
+ (void);
+
+/*
+ * Deprecated functions which are still supported for backward compatibility.
+ */
+
+OPENVPN_PLUGIN_DEF openvpn_plugin_handle_t OPENVPN_PLUGIN_FUNC(openvpn_plugin_open_v1)
+ (unsigned int *type_mask,
+ const char *argv[],
+ const char *envp[]);
+
+OPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_func_v1)
+ (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[]);