OpenVPN
multi-auth.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 * can do either an instant authentication or a deferred auth.
26 * The purpose of this plug-in is to test multiple auth plugins
27 * in the same configuration file
28 *
29 * Plugin arguments:
30 *
31 * multi-auth.so LOG_ID DEFER_TIME USERNAME PASSWORD
32 *
33 * LOG_ID is just an ID string used to separate auth results in the log
34 * DEFER_TIME is the time to defer the auth. Set to 0 to return immediately
35 * USERNAME is the username for a valid authentication
36 * PASSWORD is the password for a valid authentication
37 *
38 * The DEFER_TIME time unit is in ms.
39 *
40 * Sample usage:
41 *
42 * plugin multi-auth.so MA_1 0 foo bar # Instant reply user:foo pass:bar
43 * plugin multi-auth.so MA_2 5000 fux bax # Defer 5 sec, user:fux pass: bax
44 *
45 */
46#include "config.h"
47#include <stdio.h>
48#include <string.h>
49#include <stdlib.h>
50#include <stdarg.h>
51#include <unistd.h>
52#include <stdbool.h>
53#include <fcntl.h>
54#include <sys/types.h>
55#include <sys/wait.h>
56
57#include "openvpn-plugin.h"
58
59static char *MODULE = "multi-auth";
60
61/*
62 * Our context, where we keep our state.
63 */
64
65struct plugin_context
66{
68 char *authid;
71};
72
73/* local wrapping of the log function, to add more details */
74static plugin_vlog_t _plugin_vlog_func = NULL;
75static void
76plog(const struct plugin_context *ctx, int flags, char *fmt, ...)
77{
78 char logid[129];
79
80 if (ctx && ctx->authid)
81 {
82 snprintf(logid, 128, "%s[%s]", MODULE, ctx->authid);
83 }
84 else
85 {
86 snprintf(logid, 128, "%s", MODULE);
87 }
88
89 va_list arglist;
90 va_start(arglist, fmt);
91 _plugin_vlog_func(flags, logid, fmt, arglist);
92 va_end(arglist);
93}
94
95
96/*
97 * Constants indicating minimum API and struct versions by the functions
98 * in this plugin. Consult openvpn-plugin.h, look for:
99 * OPENVPN_PLUGIN_VERSION and OPENVPN_PLUGINv3_STRUCTVER
100 *
101 * Strictly speaking, this sample code only requires plugin_log, a feature
102 * of structver version 1. However, '1' lines up with ancient versions
103 * of openvpn that are past end-of-support. As such, we are requiring
104 * structver '5' here to indicate a desire for modern openvpn, rather
105 * than a need for any particular feature found in structver beyond '1'.
106 */
107#define OPENVPN_PLUGIN_VERSION_MIN 3
108#define OPENVPN_PLUGIN_STRUCTVER_MIN 5
109
110
112{
115};
116
117
118/*
119 * Given an environmental variable name, search
120 * the envp array for its value, returning it
121 * if found or NULL otherwise.
122 */
123static const char *
124get_env(const char *name, const char *envp[])
125{
126 if (envp)
127 {
128 const size_t namelen = strlen(name);
129 for (int i = 0; envp[i]; ++i)
130 {
131 if (!strncmp(envp[i], name, namelen))
132 {
133 const char *cp = envp[i] + namelen;
134 if (*cp == '=')
135 {
136 return cp + 1;
137 }
138 }
139 }
140 }
141 return NULL;
142}
143
144/* used for safe printf of possible NULL strings */
145static const char *
146np(const char *str)
147{
148 if (str)
149 {
150 return str;
151 }
152 else
153 {
154 return "[NULL]";
155 }
156}
157
158static int
159atoi_null0(const char *str)
160{
161 if (str)
162 {
163 return atoi(str);
164 }
165 else
166 {
167 return 0;
168 }
169}
170
171/* Require a minimum OpenVPN Plugin API */
172OPENVPN_EXPORT int
177
178/* use v3 functions so we can use openvpn's logging and base64 etc. */
179OPENVPN_EXPORT int
180openvpn_plugin_open_v3(const int v3structver, struct openvpn_plugin_args_open_in const *args,
181 struct openvpn_plugin_args_open_return *ret)
182{
183 if (v3structver < OPENVPN_PLUGIN_STRUCTVER_MIN)
184 {
185 fprintf(stderr, "%s: this plugin is incompatible with the running version of OpenVPN\n",
186 MODULE);
187 return OPENVPN_PLUGIN_FUNC_ERROR;
188 }
189
190 /* Save global pointers to functions exported from openvpn */
191 _plugin_vlog_func = args->callbacks->plugin_vlog;
192
193 plog(NULL, PLOG_NOTE, "FUNC: openvpn_plugin_open_v3");
194
195 /*
196 * Allocate our context
197 */
198 struct plugin_context *context = NULL;
199 context = (struct plugin_context *)calloc(1, sizeof(struct plugin_context));
200 if (!context)
201 {
202 goto error;
203 }
204
205 /* simple module argument parsing */
206 if ((args->argv[4]) && !args->argv[5])
207 {
208 context->authid = strdup(args->argv[1]);
209 if (!context->authid)
210 {
211 plog(context, PLOG_ERR, "Out of memory");
212 goto error;
213 }
214 context->test_deferred_auth = atoi_null0(args->argv[2]);
215 context->test_valid_user = strdup(args->argv[3]);
216 if (!context->test_valid_user)
217 {
218 plog(context, PLOG_ERR, "Out of memory");
219 goto error;
220 }
221 context->test_valid_pass = strdup(args->argv[4]);
222 if (!context->test_valid_pass)
223 {
224 plog(context, PLOG_ERR, "Out of memory");
225 goto error;
226 }
227 }
228 else
229 {
230 plog(context, PLOG_ERR, "Too many arguments provided");
231 goto error;
232 }
233
234 if (context->test_deferred_auth > 0)
235 {
236 plog(context, PLOG_NOTE, "TEST_DEFERRED_AUTH %d", context->test_deferred_auth);
237 }
238
239 /*
240 * Which callbacks to intercept.
241 */
242 ret->type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY);
243 ret->handle = (openvpn_plugin_handle_t *)context;
244
245 plog(context, PLOG_NOTE, "initialization succeeded");
246 return OPENVPN_PLUGIN_FUNC_SUCCESS;
247
248error:
249 plog(context, PLOG_NOTE, "initialization failed");
250 if (context)
251 {
252 free(context);
253 }
254 return OPENVPN_PLUGIN_FUNC_ERROR;
255}
256
257static bool
258do_auth_user_pass(struct plugin_context *context, const char *username, const char *password)
259{
260 plog(context, PLOG_NOTE, "expect_user=%s, received_user=%s, expect_passw=%s, received_passw=%s",
261 np(context->test_valid_user), np(username), np(context->test_valid_pass), np(password));
262
263 if (context->test_valid_user && context->test_valid_pass)
264 {
265 if ((strcmp(context->test_valid_user, username) != 0)
266 || (strcmp(context->test_valid_pass, password) != 0))
267 {
268 plog(context, PLOG_ERR, "User/Password auth result: FAIL");
269 return false;
270 }
271 else
272 {
273 plog(context, PLOG_NOTE, "User/Password auth result: PASS");
274 return true;
275 }
276 }
277 return false;
278}
279
280
281static int
283 const char *argv[], const char *envp[])
284{
285 /* get username/password from envp string array */
286 const char *username = get_env("username", envp);
287 const char *password = get_env("password", envp);
288
289 if (!context->test_deferred_auth)
290 {
291 plog(context, PLOG_NOTE, "Direct authentication");
292 return do_auth_user_pass(context, username, password) ? OPENVPN_PLUGIN_FUNC_SUCCESS
293 : OPENVPN_PLUGIN_FUNC_ERROR;
294 }
295
296 /* get auth_control_file filename from envp string array*/
297 const char *auth_control_file = get_env("auth_control_file", envp);
298 plog(context, PLOG_NOTE, "auth_control_file=%s", auth_control_file);
299
300 /* Authenticate asynchronously in n seconds */
301 if (!auth_control_file)
302 {
303 return OPENVPN_PLUGIN_FUNC_ERROR;
304 }
305
306 /* we do not want to complicate our lives with having to wait()
307 * for child processes (so they are not zombiefied) *and* we MUST NOT
308 * fiddle with signal handlers (= shared with openvpn main), so
309 * we use double-fork() trick.
310 */
311
312 /* fork, sleep, succeed (no "real" auth done = always succeed) */
313 pid_t p1 = fork();
314 if (p1 < 0) /* Fork failed */
315 {
316 return OPENVPN_PLUGIN_FUNC_ERROR;
317 }
318 if (p1 > 0) /* parent process */
319 {
320 waitpid(p1, NULL, 0);
321 return OPENVPN_PLUGIN_FUNC_DEFERRED;
322 }
323
324 /* first gen child process, fork() again and exit() right away */
325 pid_t p2 = fork();
326 if (p2 < 0)
327 {
328 plog(context, PLOG_ERR | PLOG_ERRNO, "BACKGROUND: fork(2) failed");
329 exit(1);
330 }
331
332 if (p2 != 0) /* new parent: exit right away */
333 {
334 exit(0);
335 }
336
337 /* (grand-)child process
338 * - never call "return" now (would mess up openvpn)
339 * - return status is communicated by file
340 * - then exit()
341 */
342
343 /* do mighty complicated work that will really take time here... */
344 useconds_t wait_time = (useconds_t)context->test_deferred_auth * 1000;
345 plog(context, PLOG_NOTE, "in async/deferred handler, usleep(%u)", wait_time);
346 usleep(wait_time);
347
348 /* now signal success state to openvpn */
349 int fd = open(auth_control_file, O_WRONLY);
350 if (fd < 0)
351 {
352 plog(context, PLOG_ERR | PLOG_ERRNO, "open('%s') failed", auth_control_file);
353 exit(1);
354 }
355
356 char result[2] = "0\0";
358 {
359 result[0] = '1';
360 }
361
362 if (write(fd, result, 1) != 1)
363 {
364 plog(context, PLOG_ERR | PLOG_ERRNO, "write to '%s' failed", auth_control_file);
365 }
366 close(fd);
367
368 exit(0);
369}
370
371
372OPENVPN_EXPORT int
373openvpn_plugin_func_v3(const int v3structver, struct openvpn_plugin_args_func_in const *args,
374 struct openvpn_plugin_args_func_return *ret)
375{
376 if (v3structver < OPENVPN_PLUGIN_STRUCTVER_MIN)
377 {
378 fprintf(stderr, "%s: this plugin is incompatible with the running version of OpenVPN\n",
379 MODULE);
380 return OPENVPN_PLUGIN_FUNC_ERROR;
381 }
382 const char **argv = args->argv;
383 const char **envp = args->envp;
384 struct plugin_context *context = (struct plugin_context *)args->handle;
385 struct plugin_per_client_context *pcc =
386 (struct plugin_per_client_context *)args->per_client_context;
387 switch (args->type)
388 {
389 case OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY:
390 plog(context, PLOG_NOTE, "OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY");
391 return auth_user_pass_verify(context, pcc, argv, envp);
392
393 default:
394 plog(context, PLOG_NOTE, "OPENVPN_PLUGIN_?");
395 return OPENVPN_PLUGIN_FUNC_ERROR;
396 }
397}
398
399OPENVPN_EXPORT void *
400openvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)
401{
402 struct plugin_context *context = (struct plugin_context *)handle;
403 plog(context, PLOG_NOTE, "FUNC: openvpn_plugin_client_constructor_v1");
404 return calloc(1, sizeof(struct plugin_per_client_context));
405}
406
407OPENVPN_EXPORT void
408openvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle, void *per_client_context)
409{
410 struct plugin_context *context = (struct plugin_context *)handle;
411 plog(context, PLOG_NOTE, "FUNC: openvpn_plugin_client_destructor_v1");
412 free(per_client_context);
413}
414
415OPENVPN_EXPORT void
416openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
417{
418 struct plugin_context *context = (struct plugin_context *)handle;
419 plog(context, PLOG_NOTE, "FUNC: openvpn_plugin_close_v1");
420 free(context);
421}
@ write
OPENVPN_EXPORT void openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
Definition multi-auth.c:416
static int auth_user_pass_verify(struct plugin_context *context, struct plugin_per_client_context *pcc, const char *argv[], const char *envp[])
Definition multi-auth.c:282
OPENVPN_EXPORT void openvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle, void *per_client_context)
Definition multi-auth.c:408
static const char * np(const char *str)
Definition multi-auth.c:146
#define OPENVPN_PLUGIN_VERSION_MIN
Definition multi-auth.c:107
static char * MODULE
Definition multi-auth.c:59
#define OPENVPN_PLUGIN_STRUCTVER_MIN
Definition multi-auth.c:108
static const char * get_env(const char *name, const char *envp[])
Definition multi-auth.c:124
OPENVPN_EXPORT int openvpn_plugin_func_v3(const int v3structver, struct openvpn_plugin_args_func_in const *args, struct openvpn_plugin_args_func_return *ret)
Definition multi-auth.c:373
OPENVPN_EXPORT void * openvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)
Definition multi-auth.c:400
static bool do_auth_user_pass(struct plugin_context *context, const char *username, const char *password)
Definition multi-auth.c:258
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)
Definition multi-auth.c:180
OPENVPN_EXPORT int openvpn_plugin_min_version_required_v1(void)
Definition multi-auth.c:173
static int atoi_null0(const char *str)
Definition multi-auth.c:159
static plugin_vlog_t _plugin_vlog_func
Definition multi-auth.c:74
static void plog(const struct plugin_context *ctx, int flags, char *fmt,...)
Definition multi-auth.c:76
Definition argv.h:35
char ** argv
Definition argv.h:39
Contains all state information for one tunnel.
Definition openvpn.h:474
char * test_valid_user
Definition multi-auth.c:69
int test_deferred_auth
Definition multi-auth.c:67
const char * password
Definition log.c:42
const char * username
Definition log.c:41
char * test_valid_pass
Definition multi-auth.c:70