From patchwork Thu Sep 17 06:19:09 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gert Doering X-Patchwork-Id: 1462 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director10.mail.ord1d.rsapps.net ([172.30.191.6]) by backend30.mail.ord1d.rsapps.net with LMTP id GDwkFLSMY19JcQAAIUCqbw (envelope-from ) for ; Thu, 17 Sep 2020 12:20:04 -0400 Received: from proxy9.mail.ord1d.rsapps.net ([172.30.191.6]) by director10.mail.ord1d.rsapps.net with LMTP id +HnjE7SMY1+WewAApN4f7A (envelope-from ) for ; Thu, 17 Sep 2020 12:20:04 -0400 Received: from smtp6.gate.ord1c ([172.30.191.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy9.mail.ord1d.rsapps.net with LMTPS id sLRzE7SMY1/kZwAA7h+8OQ (envelope-from ) for ; Thu, 17 Sep 2020 12:20:04 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp6.gate.ord1c.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=greenie.muc.de X-Suspicious-Flag: YES X-Classification-ID: a3cc8e74-f901-11ea-94a7-bc305bf03f9c-1-1 Received: from [216.105.38.7] ([216.105.38.7:59342] helo=lists.sourceforge.net) by smtp6.gate.ord1c.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 27/B6-24119-3BC836F5; Thu, 17 Sep 2020 12:20:03 -0400 Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1kIwd1-0001Lo-Ly; Thu, 17 Sep 2020 16:19:23 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kIwcz-0001Le-U3 for openvpn-devel@lists.sourceforge.net; Thu, 17 Sep 2020 16:19:21 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=0qsHwRYsoa5NZ2hpBqYiUOgeLjBRvPpR38U6KpI/FKY=; b=L8ONsXJm+L6cWv/ErZVWmTNx90 k/bEgxOgjDci90FakHijKis7pcgmk06t52WqXAF4jNhZQWxThJmrA4PXxz8ULEUTBdXm4jZLSwTgw ua41Txa2kVkODQtFo9sdNrOH8Ev2+AhUlU9GQg0CY2h8TU6aTxKFoZWuMPumT0MZoP+A=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=0qsHwRYsoa5NZ2hpBqYiUOgeLjBRvPpR38U6KpI/FKY=; b=bZ3LMR973wtW5q40IVCSCFDwRE 7KlGR1UWkwIS8aa5pAhGH8nWN4Mcu82K8QMA2cqh/48kciuMwfvM9sbyKBBLbl8QdcVPb1OVZTFJA OIdn+2lk9NhmHY21tKco6g7GcVx1QCj+XE1smYT0VWB+SlxrzuRXNWYERbjejGhRAMtg=; Received: from vmail1.greenie.net ([195.30.8.66]) by sfi-mx-3.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.92.2) id 1kIwct-00D9oC-NE for openvpn-devel@lists.sourceforge.net; Thu, 17 Sep 2020 16:19:21 +0000 Received: from gentoo.ov.greenie.net (gentoo.ov.greenie.net [IPv6:2001:608:0:814:0:0:f000:11]) by vmail1.greenie.net (8.16.1/8.12.11) with SMTP id 08HGJ9TX086033 for ; Thu, 17 Sep 2020 18:19:09 +0200 (CEST) Received: (nullmailer pid 11619 invoked by uid 1000); Thu, 17 Sep 2020 16:19:09 -0000 From: Gert Doering To: openvpn-devel@lists.sourceforge.net Date: Thu, 17 Sep 2020 18:19:09 +0200 Message-Id: <20200917161909.11573-1-gert@greenie.muc.de> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200911214002.24419-1-gert@greenie.muc.de> References: <20200911214002.24419-1-gert@greenie.muc.de> MIME-Version: 1.0 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.6.2 (vmail1.greenie.net [IPv6:2001:608:1:995a:20c:29ff:feb8:10eb]); Thu, 17 Sep 2020 18:19:09 +0200 (CEST) X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 SPF_NONE SPF: sender does not publish an SPF Record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record X-Headers-End: 1kIwct-00D9oC-NE Subject: [Openvpn-devel] [PATCH v4] Add demo plugin that excercises "CLIENT_CONNECT" and "CLIENT_CONNECT_V2" paths X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox This is a new "samples" plugin which does not do many useful things, besides - show how a plugin is programmed - how the various messages get dispatched - how to pass back information from a client-connect/v2 plugin - how to do async-cc plugins [not yet implemented] the operation of the plugin is controlled by UV_WANT_* environment variables controlled by the client ("--setenv UV_WANT_CC_FAIL 1 --push-peer-info"), to "fail CLIENT_CONNECT" or "use async-cc for CLIENT_CONNECT_V2" or "send 'disable' back from ...") - which is useful for automated testing of server success/defer/fail code paths for the CLIENT_CONNECT_* functions. See samples/sample-plugins/client-connect/README for details how to do this. v2: - implement async / deferred operation both for CLIENT_CONNECT and CLIENT_CONNECT_V2 plugin calls - implement returning openvpn-controlled (setenv) config snippets (so the client side can verify in automated testing that the plugin operated correctly, without hard-coding something in the plugin code) v3: - remove -Wno-unused-variable from Makefile - remove unused "char ** argv" (commented out, but kept as reference) v4: - upgrade to use the build infra brought by commit 0b5141d8f946 - remove local Makefile - include "config.h" to get what is needed to get rid of the strdup() warning Acked-By: David Sommerseth --- sample/sample-plugins/Makefile.plugins | 3 +- sample/sample-plugins/README | 6 + sample/sample-plugins/client-connect/README | 38 ++ .../client-connect/sample-client-connect.c | 612 ++++++++++++++++++ 4 files changed, 658 insertions(+), 1 deletion(-) create mode 100644 sample/sample-plugins/client-connect/README create mode 100644 sample/sample-plugins/client-connect/sample-client-connect.c diff --git a/sample/sample-plugins/Makefile.plugins b/sample/sample-plugins/Makefile.plugins index f550fc19..37559a86 100644 --- a/sample/sample-plugins/Makefile.plugins +++ b/sample/sample-plugins/Makefile.plugins @@ -11,7 +11,8 @@ PLUGINS = \ keying-material-exporter-demo/keyingmaterialexporter \ log/log log/log_v3 \ simple/base64 \ - simple/simple + simple/simple \ + client-connect/sample-client-connect # All the plugins to build - rewritten with .so extension all : $(foreach var, $(PLUGINS), $(var).so) diff --git a/sample/sample-plugins/README b/sample/sample-plugins/README index 3ed60348..cf1b355e 100644 --- a/sample/sample-plugins/README +++ b/sample/sample-plugins/README @@ -13,6 +13,12 @@ log/log_v3.c -- A variant of log/log.c, which makes use of the OpenVPN plug-in v3 API. This will also log even more information related to certificates in use. +* client-connect (and logging) +client-connect/sample-client-connect -- demonstrate how to use the + CLIENT_CONNECT and CLIENT_CONNECT_V2 hooks to achieve + "per client configuration / logging / ..." actions, + both in synchronous and async/deferred mode + * cryptography related simple/base64.c -- Example using the OpenVPN exported base64 encode/decode functions diff --git a/sample/sample-plugins/client-connect/README b/sample/sample-plugins/client-connect/README new file mode 100644 index 00000000..cb3e0f3c --- /dev/null +++ b/sample/sample-plugins/client-connect/README @@ -0,0 +1,38 @@ +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 + (controlled by "setenv plugin_cc_config ..." and "plugin_cc2_config" + in openvpn's config) + + - 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 for unixy platforms (not very sophisticated right now, needs gmake): + + .../sample-plugins$ gmake client-connect/sample-client-connect.so + +(This plugin has not been tested on Windows, and might not even work due +to its use of fork() and wait(). Let us know if it does or needs patches) + + +To use in OpenVPN, add to config file: + + plugin sample-client-connect.so (Linux/BSD/etc.) diff --git a/sample/sample-plugins/client-connect/sample-client-connect.c b/sample/sample-plugins/client-connect/sample-client-connect.c new file mode 100644 index 00000000..6168076f --- /dev/null +++ b/sample/sample-plugins/client-connect/sample-client-connect.c @@ -0,0 +1,612 @@ +/* + * 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 + * + * 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 and env control variables. + */ + +/* strdup() might need special defines to be visible in */ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#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 */ +}; + +/* this is used for the CLIENT_CONNECT_V2 async/deferred handler + * + * the "CLIENT_CONNECT_V2" handler puts per-client information into + * this, and the "CLIENT_CONNECT_DEFER_V2" handler looks at it to see + * if it's time yet to succeed/fail + */ +struct plugin_per_client_context { + time_t sleep_until; /* wakeup time (time() + sleep) */ + bool want_fail; + bool want_disable; + const char *client_config; +}; + +/* + * 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; */ /* command line arguments (unused) */ + const char **envp = args->envp; /* environment variables */ + + /* 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_CONNECT_DEFER_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()) + */ + +/* helper function to write actual "here are your options" file, + * called from sync and sync handler + */ +int +write_cc_options_file(const char *name, const char **envp) +{ + if (!name) + { + return OPENVPN_PLUGIN_FUNC_SUCCESS; + } + + FILE *fp = fopen(name,"w"); + if (!fp) + { + plugin_log(PLOG_ERR, MODULE, "fopen('%s') failed", name); + return OPENVPN_PLUGIN_FUNC_ERROR; + } + + /* config to-be-sent can come from "setenv plugin_cc_config" in openvpn */ + const char *p = get_env("plugin_cc_config", envp); + if (p) + { + fprintf(fp, "%s\n", p); + } + + /* some generic config snippets so we know it worked */ + fprintf(fp, "push \"echo sample-cc plugin 1 called\"\n"); + + /* if the caller wants, reject client by means of "disable" option */ + 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 +cc_handle_deferred_v1(int seconds, const char *name, const char **envp) +{ + const char *ccd_file = get_env("client_connect_deferred_file", envp); + if (!ccd_file) + { + plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC_ASYNC=%d, but " + "'client_connect_deferred_file' not set -> fail", seconds); + return OPENVPN_PLUGIN_FUNC_ERROR; + } + + /* the CLIENT_CONNECT (v1) API is a bit tricky to work with, because + * completition can be signalled both by the "deferred_file" and by + * the new ...CLIENT_CONNECT_DEFER API - which is optional. + * + * For OpenVPN to be able to differenciate, we must create the file + * right away if we want to use that for signalling. + */ + int fd = open(ccd_file, O_WRONLY); + if (fd < 0) + { + plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "open('%s') failed", ccd_file); + return OPENVPN_PLUGIN_FUNC_ERROR; + } + + if (write(fd, "2", 1) != 1) + { + plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "write to '%s' failed", ccd_file ); + close(fd); + return OPENVPN_PLUGIN_FUNC_ERROR; + } + close(fd); + + /* we do not want to complicate our lives with having to wait() + * for child processes (so they are not zombiefied) *and* we MUST NOT + * fiddle with signal handlers (= shared with openvpn main), so + * we use double-fork() trick. + */ + + /* fork, sleep, succeed/fail according to env vars */ + pid_t p1 = fork(); + if (p1 < 0) /* Fork failed */ + { + return OPENVPN_PLUGIN_FUNC_ERROR; + } + if (p1 > 0) /* parent process */ + { + waitpid(p1, NULL, 0); + return OPENVPN_PLUGIN_FUNC_DEFERRED; + } + + /* first gen child process, fork() again and exit() right away */ + pid_t p2 = fork(); + if (p2 < 0) + { + plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: fork(2) failed"); + exit(1); + } + if (p2 > 0) /* new parent: exit right away */ + { + exit(0); + } + + /* (grand-)child process + * - never call "return" now (would mess up openvpn) + * - return status is communicated by file + * - then exit() + */ + + /* do mighty complicated work that will really take time here... */ + plugin_log(PLOG_NOTE, MODULE, "in async/deferred handler, sleep(%d)", seconds); + sleep(seconds); + + /* write config options to openvpn */ + int ret = write_cc_options_file(name, envp); + + /* 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); + ret = OPENVPN_PLUGIN_FUNC_ERROR; + } + + /* now signal success/failure state to openvpn */ + fd = open(ccd_file, O_WRONLY); + if (fd < 0) + { + plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "open('%s') failed", ccd_file); + exit(1); + } + + plugin_log(PLOG_NOTE, MODULE, "cc_handle_deferred_v1: done, signalling %s", + (ret == OPENVPN_PLUGIN_FUNC_SUCCESS) ? "success" : "fail" ); + + if (write(fd, (ret == OPENVPN_PLUGIN_FUNC_SUCCESS) ? "1" : "0", 1) != 1) + { + plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "write to '%s' failed", ccd_file ); + } + close(fd); + + exit(0); +} + +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_ASYNC" we go to async/deferred mode */ + const char *p = get_env("UV_WANT_CC_ASYNC", envp); + if (p) + { + /* the return value will usually be OPENVPN_PLUGIN_FUNC_DEFERRED + * ("I will do my job in the background, check the status file!") + * but depending on env setup it might be "..._ERRROR" + */ + return cc_handle_deferred_v1(atoi(p), argv[1], envp); + } + + /* -- this is synchronous mode (openvpn waits for us) -- */ + + /* by setting "UV_WANT_CC_FAIL" we can be triggered to fail */ + 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 */ + int ret = write_cc_options_file(argv[1], envp); + + 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) +{ + /* by setting "UV_WANT_CC2_ASYNC" we go to async/deferred mode */ + const char *want_async = get_env("UV_WANT_CC2_ASYNC", envp); + const char *want_fail = get_env("UV_WANT_CC2_FAIL", envp); + const char *want_disable = get_env("UV_WANT_CC2_DISABLE", envp); + + /* config to push towards client - can be controlled by OpenVPN + * config ("setenv plugin_cc2_config ...") - mostly useful in a + * regression test environment to push stuff like routes which are + * then verified by t_client ping tests + */ + const char *client_config = get_env("plugin_cc2_config", envp); + if (!client_config) + { + /* pick something meaningless which can be verified in client log */ + client_config = "push \"setenv CC2 MOOH\"\n"; + } + + if (want_async) + { + /* we do no really useful work here, so we just tell the + * "CLIENT_CONNECT_DEFER_V2" handler that it should sleep + * and then "do things" via the per-client-context + */ + pcc->sleep_until = time(NULL) + atoi(want_async); + pcc->want_fail = (want_fail != NULL); + pcc->want_disable = (want_disable != NULL); + pcc->client_config = client_config; + plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC2_ASYNC=%s -> set up deferred handler", want_async); + return OPENVPN_PLUGIN_FUNC_DEFERRED; + } + + /* by setting "UV_WANT_CC2_FAIL" we can be triggered to fail here */ + if (want_fail) + { + plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC2_FAIL=%s -> fail", want_fail); + 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 (want_disable) + { + plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC2_DISABLE, reject"); + rl->value = strdup("disable\n"); + } + else + { + rl->value = strdup(client_config); + } + + 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; +} + +int +openvpn_plugin_client_connect_defer_v2(struct plugin_context *context, + struct plugin_per_client_context *pcc, + struct openvpn_plugin_string_list + **return_list) +{ + time_t time_left = pcc->sleep_until - time(NULL); + plugin_log(PLOG_NOTE, MODULE, "defer_v2: seconds left=%d", + (int) time_left); + + /* not yet due? */ + if (time_left > 0) + { + return OPENVPN_PLUGIN_FUNC_DEFERRED; + } + + /* client wants fail? */ + if (pcc->want_fail) + { + plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC2_FAIL -> fail" ); + return OPENVPN_PLUGIN_FUNC_ERROR; + } + + /* fill in RL according to with-disable / without-disable */ + + /* TODO: unify this with non-deferred case */ + 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 (pcc->want_disable) + { + plugin_log(PLOG_NOTE, MODULE, "env has UV_WANT_CC2_DISABLE, reject"); + rl->value = strdup("disable\n"); + } + else + { + rl->value = strdup(pcc->client_config); + } + + 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, pcc, envp, + return_list); + + case OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2: + plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2"); + return openvpn_plugin_client_connect_defer_v2(context, pcc, + 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); +}