OpenVPN
down-root.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 * Copyright (C) 2013 David Sommerseth <davids@redhat.com>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2
13 * as published by the Free Software Foundation.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 */
24
25/*
26 * OpenVPN plugin module to do privileged down-script execution.
27 */
28
29#ifdef HAVE_CONFIG_H
30#include <config.h>
31#endif
32
33#include <stdio.h>
34#include <string.h>
35#include <unistd.h>
36#include <stdlib.h>
37#include <sys/types.h>
38#include <sys/socket.h>
39#include <sys/wait.h>
40#include <fcntl.h>
41#include <signal.h>
42#include <syslog.h>
43#include <errno.h>
44#include <err.h>
45
46#include <openvpn-plugin.h>
47
48#define DEBUG(verb) ((verb) >= 7)
49
50/* Command codes for foreground -> background communication */
51#define COMMAND_RUN_SCRIPT 1
52#define COMMAND_EXIT 2
53
54/* Response codes for background -> foreground communication */
55#define RESPONSE_INIT_SUCCEEDED 10
56#define RESPONSE_INIT_FAILED 11
57#define RESPONSE_SCRIPT_SUCCEEDED 12
58#define RESPONSE_SCRIPT_FAILED 13
59
60/* Background process function */
61static void down_root_server(const int fd, char *const *argv, char *const *envp, const int verb);
62
63/*
64 * Plugin state, used by foreground
65 */
67{
68 /* Foreground's socket to background process */
70
71 /* Process ID of background process */
73
74 /* Verbosity level of OpenVPN */
75 int verb;
76
77 /* down command */
78 char **command;
79};
80
81/*
82 * Given an environmental variable name, search
83 * the envp array for its value, returning it
84 * if found or NULL otherwise.
85 */
86static const char *
87get_env(const char *name, const char *envp[])
88{
89 if (envp)
90 {
91 int i;
92 const int namelen = strlen(name);
93 for (i = 0; envp[i]; ++i)
94 {
95 if (!strncmp(envp[i], name, namelen))
96 {
97 const char *cp = envp[i] + namelen;
98 if (*cp == '=')
99 {
100 return cp + 1;
101 }
102 }
103 }
104 }
105 return NULL;
106}
107
108/*
109 * Return the length of a string array
110 */
111static int
112string_array_len(const char *array[])
113{
114 int i = 0;
115 if (array)
116 {
117 while (array[i])
118 {
119 ++i;
120 }
121 }
122 return i;
123}
124
125/*
126 * Socket read/write functions.
127 */
128
129static int
131{
132 unsigned char c;
133 const ssize_t size = read(fd, &c, sizeof(c));
134 if (size == sizeof(c))
135 {
136 return c;
137 }
138 else
139 {
140 return -1;
141 }
142}
143
144static int
145send_control(int fd, int code)
146{
147 unsigned char c = (unsigned char) code;
148 const ssize_t size = write(fd, &c, sizeof(c));
149 if (size == sizeof(c))
150 {
151 return (int) size;
152 }
153 else
154 {
155 return -1;
156 }
157}
158
159/*
160 * Daemonize if "daemon" env var is true.
161 * Preserve stderr across daemonization if
162 * "daemon_log_redirect" env var is true.
163 */
164static void
165daemonize(const char *envp[])
166{
167 const char *daemon_string = get_env("daemon", envp);
168 if (daemon_string && daemon_string[0] == '1')
169 {
170 const char *log_redirect = get_env("daemon_log_redirect", envp);
171 int fd = -1;
172 if (log_redirect && log_redirect[0] == '1')
173 {
174 fd = dup(2);
175 }
176#if defined(__APPLE__) && defined(__clang__)
177#pragma clang diagnostic push
178#pragma clang diagnostic ignored "-Wdeprecated-declarations"
179#endif
180 if (daemon(0, 0) < 0)
181 {
182 warn("DOWN-ROOT: daemonization failed");
183 }
184#if defined(__APPLE__) && defined(__clang__)
185#pragma clang diagnostic pop
186#endif
187 else if (fd >= 3)
188 {
189 dup2(fd, 2);
190 close(fd);
191 }
192 }
193}
194
195/*
196 * Close most of parent's fds.
197 * Keep stdin/stdout/stderr, plus one
198 * other fd which is presumed to be
199 * our pipe back to parent.
200 * Admittedly, a bit of a kludge,
201 * but posix doesn't give us a kind
202 * of FD_CLOEXEC which will stop
203 * fds from crossing a fork().
204 */
205static void
207{
208 int i;
209 closelog();
210 for (i = 3; i <= 100; ++i)
211 {
212 if (i != keep)
213 {
214 close(i);
215 }
216 }
217}
218
219/*
220 * Usually we ignore signals, because our parent will
221 * deal with them.
222 */
223static void
225{
226 signal(SIGTERM, SIG_DFL);
227
228 signal(SIGINT, SIG_IGN);
229 signal(SIGHUP, SIG_IGN);
230 signal(SIGUSR1, SIG_IGN);
231 signal(SIGUSR2, SIG_IGN);
232 signal(SIGPIPE, SIG_IGN);
233}
234
235
236static void
238{
239 if (context)
240 {
241 free(context->command);
242 free(context);
243 }
244}
245
246/* Run the script using execve(). As execve() replaces the
247 * current process with the new one, do a fork first before
248 * calling execve()
249 */
250static int
251run_script(char *const *argv, char *const *envp)
252{
253 pid_t pid;
254 int ret = 0;
255
256 pid = fork();
257 if (pid == (pid_t)0) /* child side */
258 {
259 execve(argv[0], argv, envp);
260 /* If execve() fails to run, exit child with exit code 127 */
261 err(127, "DOWN-ROOT: Failed execute: %s", argv[0]);
262 }
263 else if (pid < (pid_t)0)
264 {
265 warn("DOWN-ROOT: Failed to fork child to run %s", argv[0]);
266 return -1;
267 }
268 else /* parent side */
269 {
270 if (waitpid(pid, &ret, 0) != pid)
271 {
272 /* waitpid does not return error information via errno */
273 fprintf(stderr, "DOWN-ROOT: waitpid() failed, don't know exit code of child (%s)\n", argv[0]);
274 return -1;
275 }
276 }
277 return ret;
278}
279
280OPENVPN_EXPORT openvpn_plugin_handle_t
281openvpn_plugin_open_v1(unsigned int *type_mask, const char *argv[], const char *envp[])
282{
284 int i = 0;
285
286 /*
287 * Allocate our context
288 */
289 context = (struct down_root_context *) calloc(1, sizeof(struct down_root_context));
290 if (!context)
291 {
292 warn("DOWN-ROOT: Could not allocate memory for plug-in context");
293 goto error;
294 }
295 context->foreground_fd = -1;
296
297 /*
298 * Intercept the --up and --down callbacks
299 */
300 *type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_UP) | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_DOWN);
301
302 /*
303 * Make sure we have two string arguments: the first is the .so name,
304 * the second is the script command.
305 */
306 if (string_array_len(argv) < 2)
307 {
308 fprintf(stderr, "DOWN-ROOT: need down script command\n");
309 goto error;
310 }
311
312 /*
313 * Save the arguments in our context
314 */
315 context->command = calloc(string_array_len(argv), sizeof(char *));
316 if (!context->command)
317 {
318 warn("DOWN-ROOT: Could not allocate memory for command array");
319 goto error;
320 }
321
322 /* Ignore argv[0], as it contains just the plug-in file name */
323 for (i = 1; i < string_array_len(argv); i++)
324 {
325 context->command[i-1] = (char *) argv[i];
326 }
327
328 /*
329 * Get verbosity level from environment
330 */
331 {
332 const char *verb_string = get_env("verb", envp);
333 if (verb_string)
334 {
335 context->verb = atoi(verb_string);
336 }
337 }
338
339 return (openvpn_plugin_handle_t) context;
340
341error:
343 return NULL;
344}
345
346OPENVPN_EXPORT int
347openvpn_plugin_func_v1(openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
348{
349 struct down_root_context *context = (struct down_root_context *) handle;
350
351 if (type == OPENVPN_PLUGIN_UP && context->foreground_fd == -1) /* fork off a process to hold onto root */
352 {
353 pid_t pid;
354 int fd[2];
355
356 /*
357 * Make a socket for foreground and background processes
358 * to communicate.
359 */
360 if (socketpair(PF_UNIX, SOCK_DGRAM, 0, fd) == -1)
361 {
362 warn("DOWN-ROOT: socketpair call failed");
363 return OPENVPN_PLUGIN_FUNC_ERROR;
364 }
365
366 /*
367 * Fork off the privileged process. It will remain privileged
368 * even after the foreground process drops its privileges.
369 */
370 pid = fork();
371
372 if (pid)
373 {
374 int status;
375
376 /*
377 * Foreground Process
378 */
379
380 context->background_pid = pid;
381
382 /* close our copy of child's socket */
383 close(fd[1]);
384
385 /* don't let future subprocesses inherit child socket */
386 if (fcntl(fd[0], F_SETFD, FD_CLOEXEC) < 0)
387 {
388 warn("DOWN-ROOT: Set FD_CLOEXEC flag on socket file descriptor failed");
389 }
390
391 /* wait for background child process to initialize */
392 status = recv_control(fd[0]);
394 {
395 context->foreground_fd = fd[0];
396 return OPENVPN_PLUGIN_FUNC_SUCCESS;
397 }
398 }
399 else
400 {
401 /*
402 * Background Process
403 */
404
405 /* close all parent fds except our socket back to parent */
406 close_fds_except(fd[1]);
407
408 /* Ignore most signals (the parent will receive them) */
409 set_signals();
410
411 /* Daemonize if --daemon option is set. */
412 daemonize(envp);
413
414 /* execute the event loop */
415 down_root_server(fd[1], context->command, (char *const *) envp, context->verb);
416
417 close(fd[1]);
418 exit(0);
419 return 0; /* NOTREACHED */
420 }
421 }
422 else if (type == OPENVPN_PLUGIN_DOWN && context->foreground_fd >= 0)
423 {
424 if (send_control(context->foreground_fd, COMMAND_RUN_SCRIPT) == -1)
425 {
426 warn("DOWN-ROOT: Error sending script execution signal to background process");
427 }
428 else
429 {
430 const int status = recv_control(context->foreground_fd);
432 {
433 return OPENVPN_PLUGIN_FUNC_SUCCESS;
434 }
435 if (status == -1)
436 {
437 warn("DOWN-ROOT: Error receiving script execution confirmation from background process");
438 }
439 }
440 }
441 return OPENVPN_PLUGIN_FUNC_ERROR;
442}
443
444OPENVPN_EXPORT void
445openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
446{
447 struct down_root_context *context = (struct down_root_context *) handle;
448
449 if (DEBUG(context->verb))
450 {
451 fprintf(stderr, "DOWN-ROOT: close\n");
452 }
453
454 if (context->foreground_fd >= 0)
455 {
456 /* tell background process to exit */
457 if (send_control(context->foreground_fd, COMMAND_EXIT) == -1)
458 {
459 warn("DOWN-ROOT: Error signalling background process to exit");
460 }
461
462 /* wait for background process to exit */
463 if (context->background_pid > 0)
464 {
465 waitpid(context->background_pid, NULL, 0);
466 }
467
468 close(context->foreground_fd);
469 context->foreground_fd = -1;
470 }
471
473}
474
475OPENVPN_EXPORT void
476openvpn_plugin_abort_v1(openvpn_plugin_handle_t handle)
477{
478 struct down_root_context *context = (struct down_root_context *) handle;
479
480 if (context && context->foreground_fd >= 0)
481 {
482 /* tell background process to exit */
483 send_control(context->foreground_fd, COMMAND_EXIT);
484 close(context->foreground_fd);
485 context->foreground_fd = -1;
486 }
487}
488
489/*
490 * Background process -- runs with privilege.
491 */
492static void
493down_root_server(const int fd, char *const *argv, char *const *envp, const int verb)
494{
495 /*
496 * Do initialization
497 */
498 if (DEBUG(verb))
499 {
500 fprintf(stderr, "DOWN-ROOT: BACKGROUND: INIT command='%s'\n", argv[0]);
501 }
502
503 /*
504 * Tell foreground that we initialized successfully
505 */
507 {
508 warn("DOWN-ROOT: BACKGROUND: write error on response socket [1]");
509 goto done;
510 }
511
512 /*
513 * Event loop
514 */
515 while (1)
516 {
517 int command_code;
518 int exit_code = -1;
519
520 /* get a command from foreground process */
521 command_code = recv_control(fd);
522
523 if (DEBUG(verb))
524 {
525 fprintf(stderr, "DOWN-ROOT: BACKGROUND: received command code: %d\n", command_code);
526 }
527
528 switch (command_code)
529 {
531 if ( (exit_code = run_script(argv, envp)) == 0) /* Succeeded */
532 {
534 {
535 warn("DOWN-ROOT: BACKGROUND: write error on response socket [2]");
536 goto done;
537 }
538 }
539 else /* Failed */
540 {
541 fprintf(stderr, "DOWN-ROOT: BACKGROUND: %s exited with exit code %i\n", argv[0], exit_code);
543 {
544 warn("DOWN-ROOT: BACKGROUND: write error on response socket [3]");
545 goto done;
546 }
547 }
548 break;
549
550 case COMMAND_EXIT:
551 goto done;
552
553 case -1:
554 warn("DOWN-ROOT: BACKGROUND: read error on command channel");
555 goto done;
556
557 default:
558 fprintf(stderr, "DOWN-ROOT: BACKGROUND: unknown command code: code=%d, exiting\n",
559 command_code);
560 goto done;
561 }
562 }
563
564done:
565 if (DEBUG(verb))
566 {
567 fprintf(stderr, "DOWN-ROOT: BACKGROUND: EXIT\n");
568 }
569
570 return;
571}
572
573
574/*
575 * Local variables:
576 * c-file-style: "bsd"
577 * c-basic-offset: 4
578 * indent-tabs-mode: nil
579 * End:
580 */
int daemon(int nochdir, int noclose)
OPENVPN_EXPORT int openvpn_plugin_func_v1(openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
Definition down-root.c:347
static void set_signals(void)
Definition down-root.c:224
OPENVPN_EXPORT void openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
Definition down-root.c:445
static int string_array_len(const char *array[])
Definition down-root.c:112
#define COMMAND_RUN_SCRIPT
Definition down-root.c:51
static void free_context(struct down_root_context *context)
Definition down-root.c:237
#define RESPONSE_SCRIPT_FAILED
Definition down-root.c:58
static int run_script(char *const *argv, char *const *envp)
Definition down-root.c:251
#define RESPONSE_INIT_SUCCEEDED
Definition down-root.c:55
static void daemonize(const char *envp[])
Definition down-root.c:165
#define RESPONSE_SCRIPT_SUCCEEDED
Definition down-root.c:57
static void down_root_server(const int fd, char *const *argv, char *const *envp, const int verb)
Definition down-root.c:493
OPENVPN_EXPORT openvpn_plugin_handle_t openvpn_plugin_open_v1(unsigned int *type_mask, const char *argv[], const char *envp[])
Definition down-root.c:281
static const char * get_env(const char *name, const char *envp[])
Definition down-root.c:87
OPENVPN_EXPORT void openvpn_plugin_abort_v1(openvpn_plugin_handle_t handle)
Definition down-root.c:476
static int recv_control(int fd)
Definition down-root.c:130
#define COMMAND_EXIT
Definition down-root.c:52
static int send_control(int fd, int code)
Definition down-root.c:145
static void close_fds_except(int keep)
Definition down-root.c:206
#define DEBUG(verb)
Definition down-root.c:48
static SERVICE_STATUS status
Definition interactive.c:53
@ write
@ read
Definition argv.h:35
Contains all state information for one tunnel.
Definition openvpn.h:474