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 {
87 int test_deferred_auth;
88 int test_packet_filter;
89 };
90
91 struct plugin_per_client_context {
92 int n_calls;
93 bool generated_pf_file;
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 *
get_env(const char * name,const char * envp[])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 *
np(const char * str)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
atoi_null0(const char * str)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
openvpn_plugin_min_version_required_v1()155 openvpn_plugin_min_version_required_v1()
156 {
157 return OPENVPN_PLUGIN_VERSION_MIN;
158 }
159
160 /* use v3 functions so we can use openvpn's logging and base64 etc. */
161 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)162 openvpn_plugin_open_v3(const int v3structver,
163 struct openvpn_plugin_args_open_in const *args,
164 struct openvpn_plugin_args_open_return *ret)
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);
172 return OPENVPN_PLUGIN_FUNC_ERROR;
173 }
174
175 /* Save global pointers to functions exported from openvpn */
176 plugin_log = args->callbacks->plugin_log;
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 =
199 OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_UP)
200 |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_DOWN)
201 |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_ROUTE_UP)
202 |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_IPCHANGE)
203 |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_VERIFY)
204 |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY)
205 |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT_V2)
206 |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_DISCONNECT)
207 |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_LEARN_ADDRESS)
208 |OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_FINAL);
209
210 /* ENABLE_PF should only be called if we're actually willing to do PF */
211 if (context->test_packet_filter)
212 {
213 ret->type_mask |= OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_ENABLE_PF);
214 }
215
216 ret->handle = (openvpn_plugin_handle_t *) context;
217 plugin_log(PLOG_NOTE, MODULE, "initialization succeeded");
218 return OPENVPN_PLUGIN_FUNC_SUCCESS;
219
220 error:
221 if (context)
222 {
223 free(context);
224 }
225 plugin_log(PLOG_NOTE, MODULE, "initialization failed");
226 return OPENVPN_PLUGIN_FUNC_ERROR;
227 }
228
229 static int
auth_user_pass_verify(struct plugin_context * context,struct plugin_per_client_context * pcc,const char * argv[],const char * envp[])230 auth_user_pass_verify(struct plugin_context *context,
231 struct plugin_per_client_context *pcc,
232 const char *argv[], const char *envp[])
233 {
234 if (!context->test_deferred_auth)
235 {
236 return OPENVPN_PLUGIN_FUNC_SUCCESS;
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 {
254 return OPENVPN_PLUGIN_FUNC_ERROR;
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 {
267 return OPENVPN_PLUGIN_FUNC_ERROR;
268 }
269 if (p1 > 0) /* parent process */
270 {
271 waitpid(p1, NULL, 0);
272 return OPENVPN_PLUGIN_FUNC_DEFERRED;
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
tls_final(struct plugin_context * context,struct plugin_per_client_context * pcc,const char * argv[],const char * envp[])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 {
322 return OPENVPN_PLUGIN_FUNC_SUCCESS;
323 }
324
325 if (pcc->generated_pf_file) /* we already have created a file */
326 {
327 return OPENVPN_PLUGIN_FUNC_ERROR;
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 {
334 return OPENVPN_PLUGIN_FUNC_ERROR;
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 {
355 return OPENVPN_PLUGIN_FUNC_ERROR;
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
openvpn_plugin_func_v3(const int v3structver,struct openvpn_plugin_args_func_in const * args,struct openvpn_plugin_args_func_return * ret)446 openvpn_plugin_func_v3(const int v3structver,
447 struct openvpn_plugin_args_func_in const *args,
448 struct openvpn_plugin_args_func_return *ret)
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);
453 return OPENVPN_PLUGIN_FUNC_ERROR;
454 }
455 const char **argv = args->argv;
456 const char **envp = args->envp;
457 struct plugin_context *context = (struct plugin_context *) args->handle;
458 struct plugin_per_client_context *pcc = (struct plugin_per_client_context *) args->per_client_context;
459 switch (args->type)
460 {
461 case OPENVPN_PLUGIN_UP:
462 plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_UP");
463 return OPENVPN_PLUGIN_FUNC_SUCCESS;
464
465 case OPENVPN_PLUGIN_DOWN:
466 plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_DOWN");
467 return OPENVPN_PLUGIN_FUNC_SUCCESS;
468
469 case OPENVPN_PLUGIN_ROUTE_UP:
470 plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_ROUTE_UP");
471 return OPENVPN_PLUGIN_FUNC_SUCCESS;
472
473 case OPENVPN_PLUGIN_IPCHANGE:
474 plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_IPCHANGE");
475 return OPENVPN_PLUGIN_FUNC_SUCCESS;
476
477 case OPENVPN_PLUGIN_TLS_VERIFY:
478 plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_TLS_VERIFY");
479 return OPENVPN_PLUGIN_FUNC_SUCCESS;
480
481 case OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY:
482 plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY");
483 return auth_user_pass_verify(context, pcc, argv, envp);
484
485 case OPENVPN_PLUGIN_CLIENT_CONNECT_V2:
486 plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_CLIENT_CONNECT_V2");
487 return OPENVPN_PLUGIN_FUNC_SUCCESS;
488
489 case OPENVPN_PLUGIN_CLIENT_DISCONNECT:
490 plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_CLIENT_DISCONNECT");
491 return OPENVPN_PLUGIN_FUNC_SUCCESS;
492
493 case OPENVPN_PLUGIN_LEARN_ADDRESS:
494 plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_LEARN_ADDRESS");
495 return OPENVPN_PLUGIN_FUNC_SUCCESS;
496
497 case OPENVPN_PLUGIN_TLS_FINAL:
498 plugin_log(PLOG_NOTE, MODULE, "OPENVPN_PLUGIN_TLS_FINAL");
499 return tls_final(context, pcc, argv, envp);
500
501 case OPENVPN_PLUGIN_ENABLE_PF:
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_?");
517 return OPENVPN_PLUGIN_FUNC_ERROR;
518 }
519 }
520
521 OPENVPN_EXPORT void *
openvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)522 openvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)
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
openvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle,void * per_client_context)529 openvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle, void *per_client_context)
530 {
531 plugin_log(PLOG_NOTE, MODULE, "FUNC: openvpn_plugin_client_destructor_v1");
532 free(per_client_context);
533 }
534
535 OPENVPN_EXPORT void
openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)536 openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
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 }
542