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 
60 static char *MODULE = "multi-auth";
61 
62 /*
63  * Our context, where we keep our state.
64  */
65 
66 struct plugin_context {
68  char *authid;
71 };
72 
73 /* local wrapping of the log function, to add more details */
75 static void
76 plog(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  int n_calls;
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  */
122 static const char *
123 get_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 */
145 static const char *
146 np(const char *str)
147 {
148  if (str)
149  {
150  return str;
151  }
152  else
153  {
154  return "[NULL]";
155  }
156 }
157 
158 static int
159 atoi_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 */
172 OPENVPN_EXPORT int
174 {
176 }
177 
178 /* use v3 functions so we can use openvpn's logging and base64 etc. */
179 OPENVPN_EXPORT int
180 openvpn_plugin_open_v3(const int v3structver,
181  struct openvpn_plugin_args_open_in const *args,
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);
188  }
189 
190  /* Save global pointers to functions exported from openvpn */
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  */
244 
245  plog(context, PLOG_NOTE, "initialization succeeded");
247 
248 error:
249  plog(context, PLOG_NOTE, "initialization failed");
250  if (context)
251  {
252  free(context);
253  }
255 }
256 
257 static bool
259  const char *username, const char *password)
260 {
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  {
274  "User/Password auth result: FAIL");
275  return false;
276  }
277  else
278  {
280  "User/Password auth result: PASS");
281  return true;
282  }
283  }
284  return false;
285 }
286 
287 
288 static 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");
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  {
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  {
325  }
326  if (p1 > 0) /* parent process */
327  {
328  waitpid(p1, NULL, 0);
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  {
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 
381 OPENVPN_EXPORT int
382 openvpn_plugin_func_v3(const int v3structver,
383  struct openvpn_plugin_args_func_in const *args,
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);
390  }
391  const char **argv = args->argv;
392  const char **envp = args->envp;
393  struct plugin_context *context = (struct plugin_context *) args->handle;
395  switch (args->type)
396  {
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_?");
404  }
405 }
406 
407 OPENVPN_EXPORT void *
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 
415 OPENVPN_EXPORT void
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 
423 OPENVPN_EXPORT void
425 {
426  struct plugin_context *context = (struct plugin_context *) handle;
427  plog(context, PLOG_NOTE, "FUNC: openvpn_plugin_close_v1");
428  free(context);
429 }
plugin_context::test_valid_user
char * test_valid_user
Definition: multi-auth.c:69
PLOG_NOTE
@ PLOG_NOTE
Definition: openvpn-plugin.h:235
plugin_per_client_context::generated_pf_file
bool generated_pf_file
Definition: multi-auth.c:113
plugin_per_client_context::n_calls
int n_calls
Definition: multi-auth.c:112
openvpn_plugin_args_func_in::handle
openvpn_plugin_handle_t handle
Definition: openvpn-plugin.h:434
openvpn_plugin_client_destructor_v1
OPENVPN_EXPORT void openvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle, void *per_client_context)
Definition: multi-auth.c:416
argv
Definition: argv.h:35
openvpn_plugin_args_open_in
Arguments used to transport variables to the plug-in.
Definition: openvpn-plugin.h:359
context
Contains all state information for one tunnel.
Definition: openvpn.h:473
plugin_context::test_valid_pass
char * test_valid_pass
Definition: multi-auth.c:70
plugin_context
Definition: sample-client-connect.c:62
OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY
#define OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY
Definition: openvpn-plugin.h:122
openvpn_plugin_args_open_return::type_mask
int type_mask
Definition: openvpn-plugin.h:396
OPENVPN_PLUGIN_STRUCTVER_MIN
#define OPENVPN_PLUGIN_STRUCTVER_MIN
Definition: multi-auth.c:108
plog
static void plog(const struct plugin_context *ctx, int flags, char *fmt,...)
Definition: multi-auth.c:76
OPENVPN_PLUGIN_MASK
#define OPENVPN_PLUGIN_MASK(x)
Definition: openvpn-plugin.h:137
openvpn_plugin_client_constructor_v1
OPENVPN_EXPORT void * openvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)
Definition: multi-auth.c:408
np
static const char * np(const char *str)
Definition: multi-auth.c:146
openvpn_plugin_args_func_in::type
const int type
Definition: openvpn-plugin.h:431
OPENVPN_PLUGIN_FUNC_ERROR
#define OPENVPN_PLUGIN_FUNC_ERROR
Definition: openvpn-plugin.h:149
PLOG_ERRNO
@ PLOG_ERRNO
Definition: openvpn-plugin.h:238
openvpn_plugin_open_v3
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_PLUGIN_VERSION_MIN
#define OPENVPN_PLUGIN_VERSION_MIN
Definition: multi-auth.c:107
write
@ write
Definition: interactive.c:224
do_auth_user_pass
static bool do_auth_user_pass(struct plugin_context *context, const char *username, const char *password)
Definition: multi-auth.c:258
plugin_per_client_context
Definition: sample-client-connect.c:72
OPENVPN_EXPORT
#define OPENVPN_EXPORT
Definition: openvpn-plugin.h:156
atoi_null0
static int atoi_null0(const char *str)
Definition: multi-auth.c:159
OPENVPN_PLUGIN_FUNC_DEFERRED
#define OPENVPN_PLUGIN_FUNC_DEFERRED
Definition: openvpn-plugin.h:150
plugin_vlog_t
void(*) typedef void(* plugin_vlog_t)(openvpn_plugin_log_flags_t flags, const char *plugin_name, const char *format, va_list arglist) _ovpn_chk_fmt(3
Definition: openvpn-plugin.h:258
MODULE
static char * MODULE
Definition: multi-auth.c:60
get_env
static const char * get_env(const char *name, const char *envp[])
Definition: multi-auth.c:123
openvpn_plugin_args_func_in::per_client_context
void * per_client_context
Definition: openvpn-plugin.h:435
openvpn_plugin_args_func_in::envp
const char **const envp
Definition: openvpn-plugin.h:433
auth_user_pass_verify
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_plugin_callbacks::plugin_vlog
plugin_vlog_t plugin_vlog
Definition: openvpn-plugin.h:320
PLOG_ERR
@ PLOG_ERR
Definition: openvpn-plugin.h:233
plugin_context::test_deferred_auth
int test_deferred_auth
Definition: multi-auth.c:67
openvpn_plugin_args_func_return
Arguments used to transport variables to and from the plug-in.
Definition: openvpn-plugin.h:452
openvpn_plugin_args_open_in::argv
const char **const argv
Definition: openvpn-plugin.h:362
openvpn_plugin_func_v3
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
plugin_context::password
const char * password
Definition: log.c:42
openvpn_plugin_args_open_return
Arguments used to transport variables from the plug-in back to the OpenVPN process.
Definition: openvpn-plugin.h:394
openvpn_plugin_args_func_in
Arguments used to transport variables to and from the plug-in.
Definition: openvpn-plugin.h:429
openvpn_plugin_args_open_return::handle
openvpn_plugin_handle_t handle
Definition: openvpn-plugin.h:397
OPENVPN_PLUGIN_FUNC_SUCCESS
#define OPENVPN_PLUGIN_FUNC_SUCCESS
Definition: openvpn-plugin.h:148
config.h
plugin_context::authid
char * authid
Definition: multi-auth.c:68
openvpn_plugin_args_func_in::argv
const char **const argv
Definition: openvpn-plugin.h:432
plugin_context::username
const char * username
Definition: log.c:41
openvpn-plugin.h
_plugin_vlog_func
static plugin_vlog_t _plugin_vlog_func
Definition: multi-auth.c:74
openvpn_plugin_handle_t
void * openvpn_plugin_handle_t
Definition: openvpn-plugin.h:143
openvpn_plugin_close_v1
OPENVPN_EXPORT void openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
Definition: multi-auth.c:424
openvpn_plugin_args_open_in::callbacks
struct openvpn_plugin_callbacks * callbacks
Definition: openvpn-plugin.h:364
openvpn_plugin_min_version_required_v1
OPENVPN_EXPORT int openvpn_plugin_min_version_required_v1()
Definition: multi-auth.c:173