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-2025 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, see <https://www.gnu.org/licenses/>.
22 */
23
24/*
25 * OpenVPN plugin module to do privileged down-script execution.
26 */
27
28#ifdef HAVE_CONFIG_H
29#include <config.h>
30#endif
31
32#include <stdio.h>
33#include <string.h>
34#include <unistd.h>
35#include <stdlib.h>
36#include <sys/types.h>
37#include <sys/socket.h>
38#include <sys/wait.h>
39#include <fcntl.h>
40#include <signal.h>
41#include <syslog.h>
42#include <errno.h>
43#include <err.h>
44
45#include <openvpn-plugin.h>
46
47#define DEBUG(verb) ((verb) >= 7)
48
49/* Command codes for foreground -> background communication */
50#define COMMAND_RUN_SCRIPT 1
51#define COMMAND_EXIT 2
52
53/* Response codes for background -> foreground communication */
54#define RESPONSE_INIT_SUCCEEDED 10
55#define RESPONSE_INIT_FAILED 11
56#define RESPONSE_SCRIPT_SUCCEEDED 12
57#define RESPONSE_SCRIPT_FAILED 13
58
59/* Background process function */
60static void down_root_server(const int fd, char *const *argv, char *const *envp, const int verb);
61
62/*
63 * Plugin state, used by foreground
64 */
66{
67 /* Foreground's socket to background process */
69
70 /* Process ID of background process */
72
73 /* Verbosity level of OpenVPN */
74 int verb;
75
76 /* down command */
77 char **command;
78};
79
80/*
81 * Given an environmental variable name, search
82 * the envp array for its value, returning it
83 * if found or NULL otherwise.
84 */
85static const char *
86get_env(const char *name, const char *envp[])
87{
88 if (envp)
89 {
90 const size_t namelen = strlen(name);
91 for (int i = 0; envp[i]; ++i)
92 {
93 if (!strncmp(envp[i], name, namelen))
94 {
95 const char *cp = envp[i] + namelen;
96 if (*cp == '=')
97 {
98 return cp + 1;
99 }
100 }
101 }
102 }
103 return NULL;
104}
105
106/*
107 * Return the length of a string array
108 */
109static size_t
110string_array_len(const char *array[])
111{
112 size_t i = 0;
113 if (array)
114 {
115 while (array[i])
116 {
117 ++i;
118 }
119 }
120 return i;
121}
122
123/*
124 * Socket read/write functions.
125 */
126
127static int
129{
130 unsigned char c;
131 const ssize_t size = read(fd, &c, sizeof(c));
132 if (size == sizeof(c))
133 {
134 return c;
135 }
136 else
137 {
138 return -1;
139 }
140}
141
142static ssize_t
143send_control(int fd, int code)
144{
145 unsigned char c = (unsigned char)code;
146 const ssize_t size = write(fd, &c, sizeof(c));
147 if (size == sizeof(c))
148 {
149 return size;
150 }
151 else
152 {
153 return -1;
154 }
155}
156
157/*
158 * Daemonize if "daemon" env var is true.
159 * Preserve stderr across daemonization if
160 * "daemon_log_redirect" env var is true.
161 */
162static void
163daemonize(const char *envp[])
164{
165 const char *daemon_string = get_env("daemon", envp);
166 if (daemon_string && daemon_string[0] == '1')
167 {
168 const char *log_redirect = get_env("daemon_log_redirect", envp);
169 int fd = -1;
170 if (log_redirect && log_redirect[0] == '1')
171 {
172 fd = dup(2);
173 }
174#if defined(__APPLE__) && defined(__clang__)
175#pragma clang diagnostic push
176#pragma clang diagnostic ignored "-Wdeprecated-declarations"
177#endif
178 if (daemon(0, 0) < 0)
179 {
180 warn("DOWN-ROOT: daemonization failed");
181 }
182#if defined(__APPLE__) && defined(__clang__)
183#pragma clang diagnostic pop
184#endif
185 else if (fd >= 3)
186 {
187 dup2(fd, 2);
188 close(fd);
189 }
190 }
191}
192
193/*
194 * Close most of parent's fds.
195 * Keep stdin/stdout/stderr, plus one
196 * other fd which is presumed to be
197 * our pipe back to parent.
198 * Admittedly, a bit of a kludge,
199 * but posix doesn't give us a kind
200 * of FD_CLOEXEC which will stop
201 * fds from crossing a fork().
202 */
203static void
205{
206 int i;
207 closelog();
208 for (i = 3; i <= 100; ++i)
209 {
210 if (i != keep)
211 {
212 close(i);
213 }
214 }
215}
216
217/*
218 * Usually we ignore signals, because our parent will
219 * deal with them.
220 */
221static void
223{
224 signal(SIGTERM, SIG_DFL);
225
226 signal(SIGINT, SIG_IGN);
227 signal(SIGHUP, SIG_IGN);
228 signal(SIGUSR1, SIG_IGN);
229 signal(SIGUSR2, SIG_IGN);
230 signal(SIGPIPE, SIG_IGN);
231}
232
233
234static void
236{
237 if (context)
238 {
239 free(context->command);
240 free(context);
241 }
242}
243
244/* Run the script using execve(). As execve() replaces the
245 * current process with the new one, do a fork first before
246 * calling execve()
247 */
248static int
249run_script(char *const *argv, char *const *envp)
250{
251 pid_t pid;
252 int ret = 0;
253
254 pid = fork();
255 if (pid == (pid_t)0) /* child side */
256 {
257 execve(argv[0], argv, envp);
258 /* If execve() fails to run, exit child with exit code 127 */
259 err(127, "DOWN-ROOT: Failed execute: %s", argv[0]);
260 }
261 else if (pid < (pid_t)0)
262 {
263 warn("DOWN-ROOT: Failed to fork child to run %s", argv[0]);
264 return -1;
265 }
266 else /* parent side */
267 {
268 if (waitpid(pid, &ret, 0) != pid)
269 {
270 /* waitpid does not return error information via errno */
271 fprintf(stderr, "DOWN-ROOT: waitpid() failed, don't know exit code of child (%s)\n",
272 argv[0]);
273 return -1;
274 }
275 }
276 return ret;
277}
278
279OPENVPN_EXPORT openvpn_plugin_handle_t
280openvpn_plugin_open_v1(unsigned int *type_mask, const char *argv[], const char *envp[])
281{
283
284 /*
285 * Allocate our context
286 */
287 context = (struct down_root_context *)calloc(1, sizeof(struct down_root_context));
288 if (!context)
289 {
290 warn("DOWN-ROOT: Could not allocate memory for plug-in context");
291 goto error;
292 }
293 context->foreground_fd = -1;
294
295 /*
296 * Intercept the --up and --down callbacks
297 */
298 *type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_UP) | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_DOWN);
299
300 /*
301 * Make sure we have two string arguments: the first is the .so name,
302 * the second is the script command.
303 */
304 if (string_array_len(argv) < 2)
305 {
306 fprintf(stderr, "DOWN-ROOT: need down script command\n");
307 goto error;
308 }
309
310 /*
311 * Save the arguments in our context
312 */
313 context->command = calloc(string_array_len(argv), sizeof(char *));
314 if (!context->command)
315 {
316 warn("DOWN-ROOT: Could not allocate memory for command array");
317 goto error;
318 }
319
320 /* Ignore argv[0], as it contains just the plug-in file name */
321 for (int i = 1; i < string_array_len(argv); i++)
322 {
323 context->command[i - 1] = (char *)argv[i];
324 }
325
326 /*
327 * Get verbosity level from environment
328 */
329 {
330 const char *verb_string = get_env("verb", envp);
331 if (verb_string)
332 {
333 context->verb = atoi(verb_string);
334 }
335 }
336
337 return (openvpn_plugin_handle_t)context;
338
339error:
341 return NULL;
342}
343
344OPENVPN_EXPORT int
345openvpn_plugin_func_v1(openvpn_plugin_handle_t handle, const int type, const char *argv[],
346 const char *envp[])
347{
348 struct down_root_context *context = (struct down_root_context *)handle;
349
350 if (type == OPENVPN_PLUGIN_UP
351 && 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(
438 "DOWN-ROOT: Error receiving script execution confirmation from background process");
439 }
440 }
441 }
442 return OPENVPN_PLUGIN_FUNC_ERROR;
443}
444
445OPENVPN_EXPORT void
446openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
447{
448 struct down_root_context *context = (struct down_root_context *)handle;
449
450 if (DEBUG(context->verb))
451 {
452 fprintf(stderr, "DOWN-ROOT: close\n");
453 }
454
455 if (context->foreground_fd >= 0)
456 {
457 /* tell background process to exit */
458 if (send_control(context->foreground_fd, COMMAND_EXIT) == -1)
459 {
460 warn("DOWN-ROOT: Error signalling background process to exit");
461 }
462
463 /* wait for background process to exit */
464 if (context->background_pid > 0)
465 {
466 waitpid(context->background_pid, NULL, 0);
467 }
468
469 close(context->foreground_fd);
470 context->foreground_fd = -1;
471 }
472
474}
475
476OPENVPN_EXPORT void
477openvpn_plugin_abort_v1(openvpn_plugin_handle_t handle)
478{
479 struct down_root_context *context = (struct down_root_context *)handle;
480
481 if (context && context->foreground_fd >= 0)
482 {
483 /* tell background process to exit */
484 send_control(context->foreground_fd, COMMAND_EXIT);
485 close(context->foreground_fd);
486 context->foreground_fd = -1;
487 }
488}
489
490/*
491 * Background process -- runs with privilege.
492 */
493static void
494down_root_server(const int fd, char *const *argv, char *const *envp, const int verb)
495{
496 /*
497 * Do initialization
498 */
499 if (DEBUG(verb))
500 {
501 fprintf(stderr, "DOWN-ROOT: BACKGROUND: INIT command='%s'\n", argv[0]);
502 }
503
504 /*
505 * Tell foreground that we initialized successfully
506 */
508 {
509 warn("DOWN-ROOT: BACKGROUND: write error on response socket [1]");
510 goto done;
511 }
512
513 /*
514 * Event loop
515 */
516 while (1)
517 {
518 int command_code;
519 int exit_code = -1;
520
521 /* get a command from foreground process */
522 command_code = recv_control(fd);
523
524 if (DEBUG(verb))
525 {
526 fprintf(stderr, "DOWN-ROOT: BACKGROUND: received command code: %d\n", command_code);
527 }
528
529 switch (command_code)
530 {
532 if ((exit_code = run_script(argv, envp)) == 0) /* Succeeded */
533 {
535 {
536 warn("DOWN-ROOT: BACKGROUND: write error on response socket [2]");
537 goto done;
538 }
539 }
540 else /* Failed */
541 {
542 fprintf(stderr, "DOWN-ROOT: BACKGROUND: %s exited with exit code %i\n", argv[0],
543 exit_code);
545 {
546 warn("DOWN-ROOT: BACKGROUND: write error on response socket [3]");
547 goto done;
548 }
549 }
550 break;
551
552 case COMMAND_EXIT:
553 goto done;
554
555 case -1:
556 warn("DOWN-ROOT: BACKGROUND: read error on command channel");
557 goto done;
558
559 default:
560 fprintf(stderr, "DOWN-ROOT: BACKGROUND: unknown command code: code=%d, exiting\n",
561 command_code);
562 goto done;
563 }
564 }
565
566done:
567 if (DEBUG(verb))
568 {
569 fprintf(stderr, "DOWN-ROOT: BACKGROUND: EXIT\n");
570 }
571
572 return;
573}
574
575
576/*
577 * Local variables:
578 * c-file-style: "bsd"
579 * c-basic-offset: 4
580 * indent-tabs-mode: nil
581 * End:
582 */
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:345
static void set_signals(void)
Definition down-root.c:222
OPENVPN_EXPORT void openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
Definition down-root.c:446
#define COMMAND_RUN_SCRIPT
Definition down-root.c:50
static ssize_t send_control(int fd, int code)
Definition down-root.c:143
static void free_context(struct down_root_context *context)
Definition down-root.c:235
#define RESPONSE_SCRIPT_FAILED
Definition down-root.c:57
static int run_script(char *const *argv, char *const *envp)
Definition down-root.c:249
#define RESPONSE_INIT_SUCCEEDED
Definition down-root.c:54
static void daemonize(const char *envp[])
Definition down-root.c:163
static size_t string_array_len(const char *array[])
Definition down-root.c:110
#define RESPONSE_SCRIPT_SUCCEEDED
Definition down-root.c:56
static void down_root_server(const int fd, char *const *argv, char *const *envp, const int verb)
Definition down-root.c:494
OPENVPN_EXPORT openvpn_plugin_handle_t openvpn_plugin_open_v1(unsigned int *type_mask, const char *argv[], const char *envp[])
Definition down-root.c:280
static const char * get_env(const char *name, const char *envp[])
Definition down-root.c:86
OPENVPN_EXPORT void openvpn_plugin_abort_v1(openvpn_plugin_handle_t handle)
Definition down-root.c:477
static int recv_control(int fd)
Definition down-root.c:128
#define COMMAND_EXIT
Definition down-root.c:51
static void close_fds_except(int keep)
Definition down-root.c:204
#define DEBUG(verb)
Definition down-root.c:47
static SERVICE_STATUS status
Definition interactive.c:51
@ write
@ read
Definition argv.h:35
Contains all state information for one tunnel.
Definition openvpn.h:474