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-2022 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 */
61 static 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 */
66 struct down_root_context
67 {
68 /* Foreground's socket to background process */
69 int foreground_fd;
70
71 /* Process ID of background process */
72 pid_t background_pid;
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 */
86 static const char *
get_env(const char * name,const char * envp[])87 get_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 */
111 static int
string_array_len(const char * array[])112 string_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
129 static int
recv_control(int fd)130 recv_control(int fd)
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
144 static int
send_control(int fd,int code)145 send_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 */
164 static void
daemonize(const char * envp[])165 daemonize(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 (daemon(0, 0) < 0)
177 {
178 warn("DOWN-ROOT: daemonization failed");
179 }
180 else if (fd >= 3)
181 {
182 dup2(fd, 2);
183 close(fd);
184 }
185 }
186 }
187
188 /*
189 * Close most of parent's fds.
190 * Keep stdin/stdout/stderr, plus one
191 * other fd which is presumed to be
192 * our pipe back to parent.
193 * Admittedly, a bit of a kludge,
194 * but posix doesn't give us a kind
195 * of FD_CLOEXEC which will stop
196 * fds from crossing a fork().
197 */
198 static void
close_fds_except(int keep)199 close_fds_except(int keep)
200 {
201 int i;
202 closelog();
203 for (i = 3; i <= 100; ++i)
204 {
205 if (i != keep)
206 {
207 close(i);
208 }
209 }
210 }
211
212 /*
213 * Usually we ignore signals, because our parent will
214 * deal with them.
215 */
216 static void
set_signals(void)217 set_signals(void)
218 {
219 signal(SIGTERM, SIG_DFL);
220
221 signal(SIGINT, SIG_IGN);
222 signal(SIGHUP, SIG_IGN);
223 signal(SIGUSR1, SIG_IGN);
224 signal(SIGUSR2, SIG_IGN);
225 signal(SIGPIPE, SIG_IGN);
226 }
227
228
229 static void
free_context(struct down_root_context * context)230 free_context(struct down_root_context *context)
231 {
232 if (context)
233 {
234 if (context->command)
235 {
236 free(context->command);
237 }
238 free(context);
239 }
240 }
241
242 /* Run the script using execve(). As execve() replaces the
243 * current process with the new one, do a fork first before
244 * calling execve()
245 */
246 static int
run_script(char * const * argv,char * const * envp)247 run_script(char *const *argv, char *const *envp)
248 {
249 pid_t pid;
250 int ret = 0;
251
252 pid = fork();
253 if (pid == (pid_t)0) /* child side */
254 {
255 execve(argv[0], argv, envp);
256 /* If execve() fails to run, exit child with exit code 127 */
257 err(127, "DOWN-ROOT: Failed execute: %s", argv[0]);
258 }
259 else if (pid < (pid_t)0)
260 {
261 warn("DOWN-ROOT: Failed to fork child to run %s", argv[0]);
262 return -1;
263 }
264 else /* parent side */
265 {
266 if (waitpid(pid, &ret, 0) != pid)
267 {
268 /* waitpid does not return error information via errno */
269 fprintf(stderr, "DOWN-ROOT: waitpid() failed, don't know exit code of child (%s)\n", argv[0]);
270 return -1;
271 }
272 }
273 return ret;
274 }
275
276 OPENVPN_EXPORT openvpn_plugin_handle_t
openvpn_plugin_open_v1(unsigned int * type_mask,const char * argv[],const char * envp[])277 openvpn_plugin_open_v1(unsigned int *type_mask, const char *argv[], const char *envp[])
278 {
279 struct down_root_context *context;
280 int i = 0;
281
282 /*
283 * Allocate our context
284 */
285 context = (struct down_root_context *) calloc(1, sizeof(struct down_root_context));
286 if (!context)
287 {
288 warn("DOWN-ROOT: Could not allocate memory for plug-in context");
289 goto error;
290 }
291 context->foreground_fd = -1;
292
293 /*
294 * Intercept the --up and --down callbacks
295 */
296 *type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_UP) | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_DOWN);
297
298 /*
299 * Make sure we have two string arguments: the first is the .so name,
300 * the second is the script command.
301 */
302 if (string_array_len(argv) < 2)
303 {
304 fprintf(stderr, "DOWN-ROOT: need down script command\n");
305 goto error;
306 }
307
308 /*
309 * Save the arguments in our context
310 */
311 context->command = calloc(string_array_len(argv), sizeof(char *));
312 if (!context->command)
313 {
314 warn("DOWN-ROOT: Could not allocate memory for command array");
315 goto error;
316 }
317
318 /* Ignore argv[0], as it contains just the plug-in file name */
319 for (i = 1; i < string_array_len(argv); i++)
320 {
321 context->command[i-1] = (char *) argv[i];
322 }
323
324 /*
325 * Get verbosity level from environment
326 */
327 {
328 const char *verb_string = get_env("verb", envp);
329 if (verb_string)
330 {
331 context->verb = atoi(verb_string);
332 }
333 }
334
335 return (openvpn_plugin_handle_t) context;
336
337 error:
338 free_context(context);
339 return NULL;
340 }
341
342 OPENVPN_EXPORT int
openvpn_plugin_func_v1(openvpn_plugin_handle_t handle,const int type,const char * argv[],const char * envp[])343 openvpn_plugin_func_v1(openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
344 {
345 struct down_root_context *context = (struct down_root_context *) handle;
346
347 if (type == OPENVPN_PLUGIN_UP && context->foreground_fd == -1) /* fork off a process to hold onto root */
348 {
349 pid_t pid;
350 int fd[2];
351
352 /*
353 * Make a socket for foreground and background processes
354 * to communicate.
355 */
356 if (socketpair(PF_UNIX, SOCK_DGRAM, 0, fd) == -1)
357 {
358 warn("DOWN-ROOT: socketpair call failed");
359 return OPENVPN_PLUGIN_FUNC_ERROR;
360 }
361
362 /*
363 * Fork off the privileged process. It will remain privileged
364 * even after the foreground process drops its privileges.
365 */
366 pid = fork();
367
368 if (pid)
369 {
370 int status;
371
372 /*
373 * Foreground Process
374 */
375
376 context->background_pid = pid;
377
378 /* close our copy of child's socket */
379 close(fd[1]);
380
381 /* don't let future subprocesses inherit child socket */
382 if (fcntl(fd[0], F_SETFD, FD_CLOEXEC) < 0)
383 {
384 warn("DOWN-ROOT: Set FD_CLOEXEC flag on socket file descriptor failed");
385 }
386
387 /* wait for background child process to initialize */
388 status = recv_control(fd[0]);
389 if (status == RESPONSE_INIT_SUCCEEDED)
390 {
391 context->foreground_fd = fd[0];
392 return OPENVPN_PLUGIN_FUNC_SUCCESS;
393 }
394 }
395 else
396 {
397 /*
398 * Background Process
399 */
400
401 /* close all parent fds except our socket back to parent */
402 close_fds_except(fd[1]);
403
404 /* Ignore most signals (the parent will receive them) */
405 set_signals();
406
407 /* Daemonize if --daemon option is set. */
408 daemonize(envp);
409
410 /* execute the event loop */
411 down_root_server(fd[1], context->command, (char *const *) envp, context->verb);
412
413 close(fd[1]);
414 exit(0);
415 return 0; /* NOTREACHED */
416 }
417 }
418 else if (type == OPENVPN_PLUGIN_DOWN && context->foreground_fd >= 0)
419 {
420 if (send_control(context->foreground_fd, COMMAND_RUN_SCRIPT) == -1)
421 {
422 warn("DOWN-ROOT: Error sending script execution signal to background process");
423 }
424 else
425 {
426 const int status = recv_control(context->foreground_fd);
427 if (status == RESPONSE_SCRIPT_SUCCEEDED)
428 {
429 return OPENVPN_PLUGIN_FUNC_SUCCESS;
430 }
431 if (status == -1)
432 {
433 warn("DOWN-ROOT: Error receiving script execution confirmation from background process");
434 }
435 }
436 }
437 return OPENVPN_PLUGIN_FUNC_ERROR;
438 }
439
440 OPENVPN_EXPORT void
openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)441 openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
442 {
443 struct down_root_context *context = (struct down_root_context *) handle;
444
445 if (DEBUG(context->verb))
446 {
447 fprintf(stderr, "DOWN-ROOT: close\n");
448 }
449
450 if (context->foreground_fd >= 0)
451 {
452 /* tell background process to exit */
453 if (send_control(context->foreground_fd, COMMAND_EXIT) == -1)
454 {
455 warn("DOWN-ROOT: Error signalling background process to exit");
456 }
457
458 /* wait for background process to exit */
459 if (context->background_pid > 0)
460 {
461 waitpid(context->background_pid, NULL, 0);
462 }
463
464 close(context->foreground_fd);
465 context->foreground_fd = -1;
466 }
467
468 free_context(context);
469 }
470
471 OPENVPN_EXPORT void
openvpn_plugin_abort_v1(openvpn_plugin_handle_t handle)472 openvpn_plugin_abort_v1(openvpn_plugin_handle_t handle)
473 {
474 struct down_root_context *context = (struct down_root_context *) handle;
475
476 if (context && context->foreground_fd >= 0)
477 {
478 /* tell background process to exit */
479 send_control(context->foreground_fd, COMMAND_EXIT);
480 close(context->foreground_fd);
481 context->foreground_fd = -1;
482 }
483 }
484
485 /*
486 * Background process -- runs with privilege.
487 */
488 static void
down_root_server(const int fd,char * const * argv,char * const * envp,const int verb)489 down_root_server(const int fd, char *const *argv, char *const *envp, const int verb)
490 {
491 /*
492 * Do initialization
493 */
494 if (DEBUG(verb))
495 {
496 fprintf(stderr, "DOWN-ROOT: BACKGROUND: INIT command='%s'\n", argv[0]);
497 }
498
499 /*
500 * Tell foreground that we initialized successfully
501 */
502 if (send_control(fd, RESPONSE_INIT_SUCCEEDED) == -1)
503 {
504 warn("DOWN-ROOT: BACKGROUND: write error on response socket [1]");
505 goto done;
506 }
507
508 /*
509 * Event loop
510 */
511 while (1)
512 {
513 int command_code;
514 int exit_code = -1;
515
516 /* get a command from foreground process */
517 command_code = recv_control(fd);
518
519 if (DEBUG(verb))
520 {
521 fprintf(stderr, "DOWN-ROOT: BACKGROUND: received command code: %d\n", command_code);
522 }
523
524 switch (command_code)
525 {
526 case COMMAND_RUN_SCRIPT:
527 if ( (exit_code = run_script(argv, envp)) == 0) /* Succeeded */
528 {
529 if (send_control(fd, RESPONSE_SCRIPT_SUCCEEDED) == -1)
530 {
531 warn("DOWN-ROOT: BACKGROUND: write error on response socket [2]");
532 goto done;
533 }
534 }
535 else /* Failed */
536 {
537 fprintf(stderr, "DOWN-ROOT: BACKGROUND: %s exited with exit code %i\n", argv[0], exit_code);
538 if (send_control(fd, RESPONSE_SCRIPT_FAILED) == -1)
539 {
540 warn("DOWN-ROOT: BACKGROUND: write error on response socket [3]");
541 goto done;
542 }
543 }
544 break;
545
546 case COMMAND_EXIT:
547 goto done;
548
549 case -1:
550 warn("DOWN-ROOT: BACKGROUND: read error on command channel");
551 goto done;
552
553 default:
554 fprintf(stderr, "DOWN-ROOT: BACKGROUND: unknown command code: code=%d, exiting\n",
555 command_code);
556 goto done;
557 }
558 }
559
560 done:
561 if (DEBUG(verb))
562 {
563 fprintf(stderr, "DOWN-ROOT: BACKGROUND: EXIT\n");
564 }
565
566 return;
567 }
568
569
570 /*
571 * Local variables:
572 * c-file-style: "bsd"
573 * c-basic-offset: 4
574 * indent-tabs-mode: nil
575 * End:
576 */
577