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 */
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  */
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  */
86 static const char *
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
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
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
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
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 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  */
205 static 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  */
223 static 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 
236 static 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  */
250 static int
251 run_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 
281 openvpn_plugin_open_v1(unsigned int *type_mask, const char *argv[], const char *envp[])
282 {
283  struct down_root_context *context;
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  */
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 
340 
341 error:
343  return NULL;
344 }
345 
346 OPENVPN_EXPORT int
347 openvpn_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");
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];
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  {
434  }
435  if (status == -1)
436  {
437  warn("DOWN-ROOT: Error receiving script execution confirmation from background process");
438  }
439  }
440  }
442 }
443 
444 OPENVPN_EXPORT void
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 
475 OPENVPN_EXPORT void
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  */
492 static void
493 down_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  */
506  if (send_control(fd, RESPONSE_INIT_SUCCEEDED) == -1)
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  {
530  case COMMAND_RUN_SCRIPT:
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);
542  if (send_control(fd, RESPONSE_SCRIPT_FAILED) == -1)
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 
564 done:
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  */
OPENVPN_PLUGIN_UP
#define OPENVPN_PLUGIN_UP
Definition: openvpn-plugin.h:117
down_root_server
static void down_root_server(const int fd, char *const *argv, char *const *envp, const int verb)
Definition: down-root.c:493
OPENVPN_PLUGIN_DOWN
#define OPENVPN_PLUGIN_DOWN
Definition: openvpn-plugin.h:118
argv
Definition: argv.h:35
context
Contains all state information for one tunnel.
Definition: openvpn.h:476
daemonize
static void daemonize(const char *envp[])
Definition: down-root.c:165
RESPONSE_SCRIPT_FAILED
#define RESPONSE_SCRIPT_FAILED
Definition: down-root.c:58
COMMAND_RUN_SCRIPT
#define COMMAND_RUN_SCRIPT
Definition: down-root.c:51
free_context
static void free_context(struct down_root_context *context)
Definition: down-root.c:237
OPENVPN_PLUGIN_MASK
#define OPENVPN_PLUGIN_MASK(x)
Definition: openvpn-plugin.h:137
openvpn_plugin_close_v1
OPENVPN_EXPORT void openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
This cleans up the last part of the plug-in, allows it to shut down cleanly and release the plug-in g...
Definition: down-root.c:445
read
@ read
Definition: interactive.c:218
OPENVPN_PLUGIN_FUNC_ERROR
#define OPENVPN_PLUGIN_FUNC_ERROR
Definition: openvpn-plugin.h:149
openvpn_plugin_func_v1
OPENVPN_EXPORT int openvpn_plugin_func_v1(openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
This function is called by OpenVPN each time the OpenVPN reaches a point where plug-in calls should h...
Definition: down-root.c:347
set_signals
static void set_signals(void)
Definition: down-root.c:224
write
@ write
Definition: interactive.c:219
OPENVPN_EXPORT
#define OPENVPN_EXPORT
Definition: openvpn-plugin.h:156
RESPONSE_INIT_SUCCEEDED
#define RESPONSE_INIT_SUCCEEDED
Definition: down-root.c:55
down_root_context::foreground_fd
int foreground_fd
Definition: down-root.c:69
send_control
static int send_control(int fd, int code)
Definition: down-root.c:145
openvpn_plugin_abort_v1
OPENVPN_EXPORT void openvpn_plugin_abort_v1(openvpn_plugin_handle_t handle)
Definition: down-root.c:476
down_root_context::background_pid
pid_t background_pid
Definition: down-root.c:72
down_root_context::command
char ** command
Definition: down-root.c:78
daemon
int daemon(int nochdir, int noclose)
Definition: compat-daemon.c:51
down_root_context::verb
int verb
Definition: down-root.c:75
recv_control
static int recv_control(int fd)
Definition: down-root.c:130
status
static SERVICE_STATUS status
Definition: interactive.c:53
down_root_context
Definition: down-root.c:66
OPENVPN_PLUGIN_FUNC_SUCCESS
#define OPENVPN_PLUGIN_FUNC_SUCCESS
Definition: openvpn-plugin.h:148
config.h
get_env
static const char * get_env(const char *name, const char *envp[])
Definition: down-root.c:87
string_array_len
static int string_array_len(const char *array[])
Definition: down-root.c:112
DEBUG
#define DEBUG(verb)
Definition: down-root.c:48
openvpn-plugin.h
close_fds_except
static void close_fds_except(int keep)
Definition: down-root.c:206
openvpn_plugin_open_v1
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
openvpn_plugin_handle_t
void * openvpn_plugin_handle_t
Definition: openvpn-plugin.h:143
RESPONSE_SCRIPT_SUCCEEDED
#define RESPONSE_SCRIPT_SUCCEEDED
Definition: down-root.c:57
run_script
static int run_script(char *const *argv, char *const *envp)
Definition: down-root.c:251
COMMAND_EXIT
#define COMMAND_EXIT
Definition: down-root.c:52