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