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-2024 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, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 */
23
24/*
25 * This file implements a simple OpenVPN plugin module which
26 * can do either an instant authentication or a deferred auth.
27 * The purpose of this plug-in is to test multiple auth plugins
28 * in the same configuration file
29 *
30 * Plugin arguments:
31 *
32 * multi-auth.so LOG_ID DEFER_TIME USERNAME PASSWORD
33 *
34 * LOG_ID is just an ID string used to separate auth results in the log
35 * DEFER_TIME is the time to defer the auth. Set to 0 to return immediately
36 * USERNAME is the username for a valid authentication
37 * PASSWORD is the password for a valid authentication
38 *
39 * The DEFER_TIME time unit is in ms.
40 *
41 * Sample usage:
42 *
43 * plugin multi-auth.so MA_1 0 foo bar # Instant reply user:foo pass:bar
44 * plugin multi-auth.so MA_2 5000 fux bax # Defer 5 sec, user:fux pass: bax
45 *
46 */
47#include "config.h"
48#include <stdio.h>
49#include <string.h>
50#include <stdlib.h>
51#include <stdarg.h>
52#include <unistd.h>
53#include <stdbool.h>
54#include <fcntl.h>
55#include <sys/types.h>
56#include <sys/wait.h>
57
58#include "openvpn-plugin.h"
59
60static char *MODULE = "multi-auth";
61
62/*
63 * Our context, where we keep our state.
64 */
65
66struct plugin_context {
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
114};
115
116
117/*
118 * Given an environmental variable name, search
119 * the envp array for its value, returning it
120 * if found or NULL otherwise.
121 */
122static const char *
123get_env(const char *name, const char *envp[])
124{
125 if (envp)
126 {
127 int i;
128 const int namelen = strlen(name);
129 for (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,
181 struct openvpn_plugin_args_open_in const *args,
182 struct openvpn_plugin_args_open_return *ret)
183{
184 if (v3structver < OPENVPN_PLUGIN_STRUCTVER_MIN)
185 {
186 fprintf(stderr, "%s: this plugin is incompatible with the running version of OpenVPN\n", 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
259 const char *username, const char *password)
260{
261 plog(context, PLOG_NOTE,
262 "expect_user=%s, received_user=%s, expect_passw=%s, received_passw=%s",
263 np(context->test_valid_user),
264 np(username),
265 np(context->test_valid_pass),
266 np(password));
267
268 if (context->test_valid_user && context->test_valid_pass)
269 {
270 if ((strcmp(context->test_valid_user, username) != 0)
271 || (strcmp(context->test_valid_pass, password) != 0))
272 {
273 plog(context, PLOG_ERR,
274 "User/Password auth result: FAIL");
275 return false;
276 }
277 else
278 {
279 plog(context, PLOG_NOTE,
280 "User/Password auth result: PASS");
281 return true;
282 }
283 }
284 return false;
285}
286
287
288static int
290 struct plugin_per_client_context *pcc,
291 const char *argv[], const char *envp[])
292{
293 /* get username/password from envp string array */
294 const char *username = get_env("username", envp);
295 const char *password = get_env("password", envp);
296
297 if (!context->test_deferred_auth)
298 {
299 plog(context, PLOG_NOTE, "Direct authentication");
301 OPENVPN_PLUGIN_FUNC_SUCCESS : OPENVPN_PLUGIN_FUNC_ERROR;
302 }
303
304 /* get auth_control_file filename from envp string array*/
305 const char *auth_control_file = get_env("auth_control_file", envp);
306 plog(context, PLOG_NOTE, "auth_control_file=%s", auth_control_file);
307
308 /* Authenticate asynchronously in n seconds */
309 if (!auth_control_file)
310 {
311 return OPENVPN_PLUGIN_FUNC_ERROR;
312 }
313
314 /* we do not want to complicate our lives with having to wait()
315 * for child processes (so they are not zombiefied) *and* we MUST NOT
316 * fiddle with signal handlers (= shared with openvpn main), so
317 * we use double-fork() trick.
318 */
319
320 /* fork, sleep, succeed (no "real" auth done = always succeed) */
321 pid_t p1 = fork();
322 if (p1 < 0) /* Fork failed */
323 {
324 return OPENVPN_PLUGIN_FUNC_ERROR;
325 }
326 if (p1 > 0) /* parent process */
327 {
328 waitpid(p1, NULL, 0);
329 return OPENVPN_PLUGIN_FUNC_DEFERRED;
330 }
331
332 /* first gen child process, fork() again and exit() right away */
333 pid_t p2 = fork();
334 if (p2 < 0)
335 {
336 plog(context, PLOG_ERR|PLOG_ERRNO, "BACKGROUND: fork(2) failed");
337 exit(1);
338 }
339
340 if (p2 != 0) /* new parent: exit right away */
341 {
342 exit(0);
343 }
344
345 /* (grand-)child process
346 * - never call "return" now (would mess up openvpn)
347 * - return status is communicated by file
348 * - then exit()
349 */
350
351 /* do mighty complicated work that will really take time here... */
352 plog(context, PLOG_NOTE, "in async/deferred handler, usleep(%d)",
353 context->test_deferred_auth*1000);
354 usleep(context->test_deferred_auth*1000);
355
356 /* now signal success state to openvpn */
357 int fd = open(auth_control_file, O_WRONLY);
358 if (fd < 0)
359 {
360 plog(context, PLOG_ERR|PLOG_ERRNO,
361 "open('%s') failed", auth_control_file);
362 exit(1);
363 }
364
365 char result[2] = "0\0";
367 {
368 result[0] = '1';
369 }
370
371 if (write(fd, result, 1) != 1)
372 {
373 plog(context, PLOG_ERR|PLOG_ERRNO, "write to '%s' failed", auth_control_file );
374 }
375 close(fd);
376
377 exit(0);
378}
379
380
381OPENVPN_EXPORT int
382openvpn_plugin_func_v3(const int v3structver,
383 struct openvpn_plugin_args_func_in const *args,
384 struct openvpn_plugin_args_func_return *ret)
385{
386 if (v3structver < OPENVPN_PLUGIN_STRUCTVER_MIN)
387 {
388 fprintf(stderr, "%s: this plugin is incompatible with the running version of OpenVPN\n", MODULE);
389 return OPENVPN_PLUGIN_FUNC_ERROR;
390 }
391 const char **argv = args->argv;
392 const char **envp = args->envp;
393 struct plugin_context *context = (struct plugin_context *) args->handle;
394 struct plugin_per_client_context *pcc = (struct plugin_per_client_context *) args->per_client_context;
395 switch (args->type)
396 {
397 case OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY:
398 plog(context, PLOG_NOTE, "OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY");
399 return auth_user_pass_verify(context, pcc, argv, envp);
400
401 default:
402 plog(context, PLOG_NOTE, "OPENVPN_PLUGIN_?");
403 return OPENVPN_PLUGIN_FUNC_ERROR;
404 }
405}
406
407OPENVPN_EXPORT void *
408openvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)
409{
410 struct plugin_context *context = (struct plugin_context *) handle;
411 plog(context, PLOG_NOTE, "FUNC: openvpn_plugin_client_constructor_v1");
412 return calloc(1, sizeof(struct plugin_per_client_context));
413}
414
415OPENVPN_EXPORT void
416openvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle, void *per_client_context)
417{
418 struct plugin_context *context = (struct plugin_context *) handle;
419 plog(context, PLOG_NOTE, "FUNC: openvpn_plugin_client_destructor_v1");
420 free(per_client_context);
421}
422
423OPENVPN_EXPORT void
424openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
425{
426 struct plugin_context *context = (struct plugin_context *) handle;
427 plog(context, PLOG_NOTE, "FUNC: openvpn_plugin_close_v1");
428 free(context);
429}
@ write
OPENVPN_EXPORT int openvpn_plugin_min_version_required_v1()
Definition multi-auth.c:173
OPENVPN_EXPORT void openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
Definition multi-auth.c:424
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:289
OPENVPN_EXPORT void openvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle, void *per_client_context)
Definition multi-auth.c:416
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:60
#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:123
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:382
OPENVPN_EXPORT void * openvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)
Definition multi-auth.c:408
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
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