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-2018 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 (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
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
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
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
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 
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  */
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
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");
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];
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  {
430  }
431  if (status == -1)
432  {
433  warn("DOWN-ROOT: Error receiving script execution confirmation from background process");
434  }
435  }
436  }
438 }
439 
440 OPENVPN_EXPORT void
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
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 */
480  close(context->foreground_fd);
481  context->foreground_fd = -1;
482  }
483 }
484 
485 /*
486  * Background process -- runs with privilege.
487  */
488 static void
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  {
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  */
static int run_script(char *const *argv, char *const *envp)
Definition: down-root.c:247
#define RESPONSE_SCRIPT_FAILED
Definition: down-root.c:58
Contains all state information for one tunnel.
Definition: openvpn.h:500
static void free_context(struct down_root_context *context)
Definition: down-root.c:230
static void daemonize(const char *envp[])
Definition: down-root.c:165
#define OPENVPN_PLUGIN_DOWN
#define SIGUSR1
Definition: config-msvc.h:116
#define SIGTERM
Definition: config-msvc.h:118
#define SIGHUP
Definition: config-msvc.h:114
#define OPENVPN_EXPORT
#define OPENVPN_PLUGIN_FUNC_SUCCESS
char ** command
Definition: down-root.c:78
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:441
static void set_signals(void)
Definition: down-root.c:217
#define COMMAND_RUN_SCRIPT
Definition: down-root.c:51
#define OPENVPN_PLUGIN_FUNC_ERROR
static int send_control(int fd, int code)
Definition: down-root.c:145
OPENVPN_EXPORT void openvpn_plugin_abort_v1(openvpn_plugin_handle_t handle)
Definition: down-root.c:472
#define RESPONSE_INIT_SUCCEEDED
Definition: down-root.c:55
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:343
static SERVICE_STATUS status
Definition: automatic.c:43
void * openvpn_plugin_handle_t
int daemon(int nochdir, int noclose)
Definition: compat-daemon.c:57
static const char * get_env(const char *name, const char *envp[])
Definition: down-root.c:87
static int recv_control(int fd)
Definition: down-root.c:130
pid_t background_pid
Definition: down-root.c:72
static int string_array_len(const char *array[])
Definition: down-root.c:112
#define SIGINT
Definition: config-msvc.h:115
#define SIGUSR2
Definition: config-msvc.h:117
static void close_fds_except(int keep)
Definition: down-root.c:199
OPENVPN_EXPORT openvpn_plugin_handle_t openvpn_plugin_open_v1(unsigned int *type_mask, const char *argv[], const char *envp[])
Definition: down-root.c:277
#define RESPONSE_SCRIPT_SUCCEEDED
Definition: down-root.c:57
#define free
Definition: cmocka.c:1850
#define COMMAND_EXIT
Definition: down-root.c:52
static void down_root_server(const int fd, char *const *argv, char *const *envp, const int verb)
Definition: down-root.c:489
Definition: argv.h:35
#define OPENVPN_PLUGIN_UP
#define ssize_t
Definition: config-msvc.h:105
#define OPENVPN_PLUGIN_MASK(x)
#define DEBUG(verb)
Definition: down-root.c:48