OpenVPN
simple.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-2018 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  * will test deferred authentication and packet filtering.
27  *
28  * Will run on Windows or *nix.
29  *
30  * Sample usage:
31  *
32  * setenv test_deferred_auth 20
33  * setenv test_packet_filter 10
34  * plugin plugin/defer/simple.so
35  *
36  * This will enable deferred authentication to occur 20
37  * seconds after the normal TLS authentication process,
38  * and will cause a packet filter file to be generated 10
39  * seconds after the initial TLS negotiation, using
40  * {common-name}.pf as the source.
41  *
42  * Sample packet filter configuration:
43  *
44  * [CLIENTS DROP]
45  * +otherclient
46  * [SUBNETS DROP]
47  * +10.0.0.0/8
48  * -10.10.0.8
49  * [END]
50  *
51  * See the README file for build instructions.
52  */
53 
54 #include <stdio.h>
55 #include <string.h>
56 #include <stdlib.h>
57 #include <unistd.h>
58 #include <stdbool.h>
59 #include <fcntl.h>
60 #include <sys/types.h>
61 #include <sys/wait.h>
62 
63 #include "openvpn-plugin.h"
64 
65 /* Pointers to functions exported from openvpn */
66 static plugin_log_t plugin_log = NULL;
67 
68 /*
69  * Constants indicating minimum API and struct versions by the functions
70  * in this plugin. Consult openvpn-plugin.h, look for:
71  * OPENVPN_PLUGIN_VERSION and OPENVPN_PLUGINv3_STRUCTVER
72  *
73  * Strictly speaking, this sample code only requires plugin_log, a feature
74  * of structver version 1. However, '1' lines up with ancient versions
75  * of openvpn that are past end-of-support. As such, we are requiring
76  * structver '5' here to indicate a desire for modern openvpn, rather
77  * than a need for any particular feature found in structver beyond '1'.
78  */
79 #define OPENVPN_PLUGIN_VERSION_MIN 3
80 #define OPENVPN_PLUGIN_STRUCTVER_MIN 5
81 
82 /*
83  * Our context, where we keep our state.
84  */
85 
86 struct plugin_context {
89 };
90 
92  int n_calls;
94 };
95 
96 /* module name for plugin_log() */
97 static char *MODULE = "defer/simple";
98 
99 /*
100  * Given an environmental variable name, search
101  * the envp array for its value, returning it
102  * if found or NULL otherwise.
103  */
104 static const char *
105 get_env(const char *name, const char *envp[])
106 {
107  if (envp)
108  {
109  int i;
110  const int namelen = strlen(name);
111  for (i = 0; envp[i]; ++i)
112  {
113  if (!strncmp(envp[i], name, namelen))
114  {
115  const char *cp = envp[i] + namelen;
116  if (*cp == '=')
117  {
118  return cp + 1;
119  }
120  }
121  }
122  }
123  return NULL;
124 }
125 
126 /* used for safe printf of possible NULL strings */
127 static const char *
128 np(const char *str)
129 {
130  if (str)
131  {
132  return str;
133  }
134  else
135  {
136  return "[NULL]";
137  }
138 }
139 
140 static int
141 atoi_null0(const char *str)
142 {
143  if (str)
144  {
145  return atoi(str);
146  }
147  else
148  {
149  return 0;
150  }
151 }
152 
153 /* Require a minimum OpenVPN Plugin API */
154 OPENVPN_EXPORT int
156 {
158 }
159 
160 /* use v3 functions so we can use openvpn's logging and base64 etc. */
161 OPENVPN_EXPORT int
162 openvpn_plugin_open_v3(const int v3structver,
163  struct openvpn_plugin_args_open_in const *args,
165 {
166  const char **envp = args->envp; /* environment variables */
167  struct plugin_context *context;
168 
169  if (v3structver < OPENVPN_PLUGIN_STRUCTVER_MIN)
170  {
171  fprintf(stderr, "%s: this plugin is incompatible with the running version of OpenVPN\n", MODULE);
173  }
174 
175  /* Save global pointers to functions exported from openvpn */
177 
178  plugin_log(PLOG_NOTE, MODULE, "FUNC: openvpn_plugin_open_v3");
179 
180  /*
181  * Allocate our context
182  */
183  context = (struct plugin_context *) calloc(1, sizeof(struct plugin_context));
184  if (!context)
185  {
186  goto error;
187  }
188 
189  context->test_deferred_auth = atoi_null0(get_env("test_deferred_auth", envp));
190  plugin_log(PLOG_NOTE, MODULE, "TEST_DEFERRED_AUTH %d", context->test_deferred_auth);
191 
192  context->test_packet_filter = atoi_null0(get_env("test_packet_filter", envp));
193  plugin_log(PLOG_NOTE, MODULE, "TEST_PACKET_FILTER %d", context->test_packet_filter);
194 
195  /*
196  * Which callbacks to intercept.
197  */
198  ret->type_mask =
209 
210  /* ENABLE_PF should only be called if we're actually willing to do PF */
211  if (context->test_packet_filter)
212  {
214  }
215 
216  ret->handle = (openvpn_plugin_handle_t *) context;
217  plugin_log(PLOG_NOTE, MODULE, "initialization succeeded");
219 
220 error:
221  if (context)
222  {
223  free(context);
224  }
225  plugin_log(PLOG_NOTE, MODULE, "initialization failed");
227 }
228 
229 static int
231  struct plugin_per_client_context *pcc,
232  const char *argv[], const char *envp[])
233 {
234  if (!context->test_deferred_auth)
235  {
237  }
238 
239  /* get username/password from envp string array */
240  const char *username = get_env("username", envp);
241  const char *password = get_env("password", envp);
242 
243  /* get auth_control_file filename from envp string array*/
244  const char *auth_control_file = get_env("auth_control_file", envp);
245 
246  plugin_log(PLOG_NOTE, MODULE, "DEFER u='%s' p='%s' acf='%s'",
247  np(username),
248  np(password),
249  np(auth_control_file));
250 
251  /* Authenticate asynchronously in n seconds */
252  if (!auth_control_file)
253  {
255  }
256 
257  /* we do not want to complicate our lives with having to wait()
258  * for child processes (so they are not zombiefied) *and* we MUST NOT
259  * fiddle with signal handlers (= shared with openvpn main), so
260  * we use double-fork() trick.
261  */
262 
263  /* fork, sleep, succeed (no "real" auth done = always succeed) */
264  pid_t p1 = fork();
265  if (p1 < 0) /* Fork failed */
266  {
268  }
269  if (p1 > 0) /* parent process */
270  {
271  waitpid(p1, NULL, 0);
273  }
274 
275  /* first gen child process, fork() again and exit() right away */
276  pid_t p2 = fork();
277  if (p2 < 0)
278  {
279  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: fork(2) failed");
280  exit(1);
281  }
282 
283  if (p2 != 0) /* new parent: exit right away */
284  {
285  exit(0);
286  }
287 
288  /* (grand-)child process
289  * - never call "return" now (would mess up openvpn)
290  * - return status is communicated by file
291  * - then exit()
292  */
293 
294  /* do mighty complicated work that will really take time here... */
295  plugin_log(PLOG_NOTE, MODULE, "in async/deferred handler, sleep(%d)", context->test_deferred_auth);
296  sleep(context->test_deferred_auth);
297 
298  /* now signal success state to openvpn */
299  int fd = open(auth_control_file, O_WRONLY);
300  if (fd < 0)
301  {
302  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "open('%s') failed", auth_control_file);
303  exit(1);
304  }
305 
306  plugin_log(PLOG_NOTE, MODULE, "auth_user_pass_verify: done" );
307 
308  if (write(fd, "1", 1) != 1)
309  {
310  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "write to '%s' failed", auth_control_file );
311  }
312  close(fd);
313 
314  exit(0);
315 }
316 
317 static int
318 tls_final(struct plugin_context *context, struct plugin_per_client_context *pcc, const char *argv[], const char *envp[])
319 {
320  if (!context->test_packet_filter) /* no PF testing, nothing to do */
321  {
323  }
324 
325  if (pcc->generated_pf_file) /* we already have created a file */
326  {
328  }
329 
330  const char *pff = get_env("pf_file", envp);
331  const char *cn = get_env("username", envp);
332  if (!pff || !cn) /* required vars missing */
333  {
335  }
336 
337  pcc->generated_pf_file = true;
338 
339  /* the PF API is, basically
340  * - OpenVPN sends a filename (pf_file) to the plugin
341  * - OpenVPN main loop will check every second if that file shows up
342  * - when it does, it will be read & used for the pf config
343  * the pre-created file needs to be removed in ...ENABLE_PF
344  * to make deferred PF setup work
345  *
346  * the regular PF hook does not know the client username or CN, so
347  * this is deferred to the TLS_FINAL hook which knows these things
348  */
349 
350  /* do the double fork dance (see above for more verbose comments)
351  */
352  pid_t p1 = fork();
353  if (p1 < 0) /* Fork failed */
354  {
356  }
357  if (p1 > 0) /* parent process */
358  {
359  waitpid(p1, NULL, 0);
360  return OPENVPN_PLUGIN_FUNC_SUCCESS; /* no _DEFERRED here! */
361  }
362 
363  /* first gen child process, fork() again and exit() right away */
364  pid_t p2 = fork();
365  if (p2 < 0)
366  {
367  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: fork(2) failed");
368  exit(1);
369  }
370 
371  if (p2 != 0) /* new parent: exit right away */
372  {
373  exit(0);
374  }
375 
376  /* (grand-)child process
377  * - never call "return" now (would mess up openvpn)
378  * - return status is communicated by file
379  * - then exit()
380  */
381 
382  /* at this point, the plugin can take its time, because OpenVPN will
383  * no longer block waiting for the call to finish
384  *
385  * in this example, we build a PF file by copying over a file
386  * named "<username>.pf" to the OpenVPN-provided pf file name
387  *
388  * a real example could do a LDAP lookup, a REST call, ...
389  */
390  plugin_log(PLOG_NOTE, MODULE, "in async/deferred tls_final handler, sleep(%d)", context->test_packet_filter);
391  sleep(context->test_packet_filter);
392 
393  char buf[256];
394  snprintf(buf, sizeof(buf), "%s.pf", cn );
395 
396  /* there is a small race condition here - OpenVPN could detect our
397  * file while we have only written half of it. So "perfect" code
398  * needs to create this with a temp file name, and then rename() it
399  * after it has been written. But I am lazy.
400  */
401 
402  int w_fd = open( pff, O_WRONLY|O_CREAT, 0600 );
403  if (w_fd < 0)
404  {
405  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "can't write to '%s'", pff);
406  exit(0);
407  }
408 
409  int r_fd = open( buf, O_RDONLY );
410  if (r_fd < 0)
411  {
412  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "can't read '%s', creating empty pf file", buf);
413  close(w_fd);
414  exit(0);
415  }
416 
417  char data[1024];
418 
419  int r;
420  do
421  {
422  r = read(r_fd, data, sizeof(data));
423  if (r < 0)
424  {
425  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "error reading '%s'", buf);
426  close(r_fd);
427  close(w_fd);
428  exit(0);
429  }
430  int w = write(w_fd, data, r);
431  if (w < 0 || w != r)
432  {
433  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "error writing %d bytes to '%s'", r, pff);
434  close(r_fd);
435  close(w_fd);
436  exit(0);
437  }
438  }
439  while(r > 0);
440 
441  plugin_log(PLOG_NOTE, MODULE, "copied PF config from '%s' to '%s', job done", buf, pff);
442  exit(0);
443 }
444 
445 OPENVPN_EXPORT int
446 openvpn_plugin_func_v3(const int v3structver,
447  struct openvpn_plugin_args_func_in const *args,
449 {
450  if (v3structver < OPENVPN_PLUGIN_STRUCTVER_MIN)
451  {
452  fprintf(stderr, "%s: this plugin is incompatible with the running version of OpenVPN\n", MODULE);
454  }
455  const char **argv = args->argv;
456  const char **envp = args->envp;
457  struct plugin_context *context = (struct plugin_context *) args->handle;
459  switch (args->type)
460  {
461  case OPENVPN_PLUGIN_UP:
462  plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_UP");
464 
465  case OPENVPN_PLUGIN_DOWN:
466  plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_DOWN");
468 
470  plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_ROUTE_UP");
472 
474  plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_IPCHANGE");
476 
478  plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_TLS_VERIFY");
480 
482  plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY");
483  return auth_user_pass_verify(context, pcc, argv, envp);
484 
486  plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_CLIENT_CONNECT_V2");
488 
490  plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_CLIENT_DISCONNECT");
492 
494  plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_LEARN_ADDRESS");
496 
498  plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_TLS_FINAL");
499  return tls_final(context, pcc, argv, envp);
500 
502  plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_ENABLE_PF");
503 
504  /* OpenVPN pre-creates the file, which gets in the way of
505  * deferred pf setup - so remove it here, and re-create
506  * it in the background handler (in tls_final()) when ready
507  */
508  const char *pff = get_env("pf_file", envp);
509  if (pff)
510  {
511  (void) unlink(pff);
512  }
513  return OPENVPN_PLUGIN_FUNC_SUCCESS; /* must succeed */
514 
515  default:
516  plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_?");
518  }
519 }
520 
521 OPENVPN_EXPORT void *
523 {
524  plugin_log(PLOG_NOTE, MODULE, "FUNC: openvpn_plugin_client_constructor_v1");
525  return calloc(1, sizeof(struct plugin_per_client_context));
526 }
527 
528 OPENVPN_EXPORT void
530 {
531  plugin_log(PLOG_NOTE, MODULE, "FUNC: openvpn_plugin_client_destructor_v1");
532  free(per_client_context);
533 }
534 
535 OPENVPN_EXPORT void
537 {
538  struct plugin_context *context = (struct plugin_context *) handle;
539  plugin_log(PLOG_NOTE, MODULE, "FUNC: openvpn_plugin_close_v1");
540  free(context);
541 }
Arguments used to transport variables to the plug-in.
OPENVPN_EXPORT void openvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle, void *per_client_context)
Definition: simple.c:529
static const char * get_env(const char *name, const char *envp[])
Definition: simple.c:105
static int auth_user_pass_verify(struct plugin_context *context, struct plugin_per_client_context *pcc, const char *argv[], const char *envp[])
Definition: simple.c:230
const char * password
Definition: log.c:42
Contains all state information for one tunnel.
Definition: openvpn.h:463
#define OPENVPN_PLUGIN_VERSION_MIN
Definition: simple.c:79
openvpn_plugin_handle_t * handle
#define OPENVPN_PLUGIN_DOWN
#define OPENVPN_PLUGIN_ROUTE_UP
int test_deferred_auth
Definition: simple.c:87
#define OPENVPN_EXPORT
#define OPENVPN_PLUGIN_ENABLE_PF
#define OPENVPN_PLUGIN_FUNC_SUCCESS
#define snprintf
Definition: config-msvc.h:96
OPENVPN_EXPORT int openvpn_plugin_min_version_required_v1()
Definition: simple.c:155
#define OPENVPN_PLUGIN_LEARN_ADDRESS
const char * username
Definition: log.c:41
#define OPENVPN_PLUGIN_IPCHANGE
static int tls_final(struct plugin_context *context, struct plugin_per_client_context *pcc, const char *argv[], const char *envp[])
Definition: simple.c:318
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: simple.c:162
#define OPENVPN_PLUGIN_FUNC_DEFERRED
#define OPENVPN_PLUGIN_TLS_VERIFY
#define OPENVPN_PLUGIN_FUNC_ERROR
static const char * np(const char *str)
Definition: simple.c:128
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: simple.c:446
#define sleep(x)
Definition: syshead.h:43
Arguments used to transport variables to and from the plug-in.
static plugin_log_t plugin_log
Definition: simple.c:66
#define OPENVPN_PLUGIN_TLS_FINAL
openvpn_plugin_handle_t handle
void(* plugin_log_t)(openvpn_plugin_log_flags_t flags, const char *plugin_name, const char *format,...) _ovpn_chk_fmt(3
#define OPENVPN_PLUGIN_CLIENT_DISCONNECT
void * openvpn_plugin_handle_t
OPENVPN_EXPORT void openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
Definition: simple.c:536
static int atoi_null0(const char *str)
Definition: simple.c:141
Arguments used to transport variables to and from the plug-in.
#define OPENVPN_PLUGIN_STRUCTVER_MIN
Definition: simple.c:80
#define OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY
static char * MODULE
Definition: simple.c:97
struct openvpn_plugin_callbacks * callbacks
OPENVPN_EXPORT void * openvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)
Definition: simple.c:522
int test_packet_filter
Definition: simple.c:88
#define free
Definition: cmocka.c:1850
Definition: argv.h:35
Arguments used to transport variables from the plug-in back to the OpenVPN process.
#define OPENVPN_PLUGIN_CLIENT_CONNECT_V2
#define OPENVPN_PLUGIN_UP
#define OPENVPN_PLUGIN_MASK(x)