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-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
9 *
10 *  This program is free software; you can redistribute it and/or modify
11 *  it under the terms of the GNU General Public License version 2
12 *  as published by the Free Software Foundation.
13 *
14 *  This program is distributed in the hope that it will be useful,
15 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 *  GNU General Public License for more details.
18 *
19 *  You should have received a copy of the GNU General Public License
20 *  along with this program (see the file COPYING included with this
21 *  distribution); if not, write to the Free Software Foundation, Inc.,
22 *  59 Temple Place, Suite 330, Boston, MA  02111-1307  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
44#include <openvpn-plugin.h>
45
46#define DEBUG(verb) ((verb) >= 7)
47
48/* Command codes for foreground -> background communication */
49#define COMMAND_RUN_SCRIPT 0
50#define COMMAND_EXIT       1
51
52/* Response codes for background -> foreground communication */
53#define RESPONSE_INIT_SUCCEEDED   10
54#define RESPONSE_INIT_FAILED      11
55#define RESPONSE_SCRIPT_SUCCEEDED 12
56#define RESPONSE_SCRIPT_FAILED    13
57
58/* Background process function */
59static void down_root_server (const int fd, char *command, const char *argv[], const char *envp[], const int verb);
60
61/*
62 * Plugin state, used by foreground
63 */
64struct down_root_context
65{
66  /* Foreground's socket to background process */
67  int foreground_fd;
68
69  /* Process ID of background process */
70  pid_t background_pid;
71
72  /* Verbosity level of OpenVPN */
73  int verb;
74
75  /* down command */
76  char *command;
77};
78
79/*
80 * Given an environmental variable name, search
81 * the envp array for its value, returning it
82 * if found or NULL otherwise.
83 */
84static const char *
85get_env (const char *name, const char *envp[])
86{
87  if (envp)
88    {
89      int i;
90      const int namelen = strlen (name);
91      for (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		return cp + 1;
98	    }
99	}
100    }
101  return NULL;
102}
103
104/*
105 * Return the length of a string array
106 */
107static int
108string_array_len (const char *array[])
109{
110  int i = 0;
111  if (array)
112    {
113      while (array[i])
114	++i;
115    }
116  return i;
117}
118
119/*
120 * Socket read/write functions.
121 */
122
123static int
124recv_control (int fd)
125{
126  unsigned char c;
127  const ssize_t size = read (fd, &c, sizeof (c));
128  if (size == sizeof (c))
129    return c;
130  else
131    return -1;
132}
133
134static int
135send_control (int fd, int code)
136{
137  unsigned char c = (unsigned char) code;
138  const ssize_t size = write (fd, &c, sizeof (c));
139  if (size == sizeof (c))
140    return (int) size;
141  else
142    return -1;
143}
144
145/*
146 * Daemonize if "daemon" env var is true.
147 * Preserve stderr across daemonization if
148 * "daemon_log_redirect" env var is true.
149 */
150static void
151daemonize (const char *envp[])
152{
153  const char *daemon_string = get_env ("daemon", envp);
154  if (daemon_string && daemon_string[0] == '1')
155    {
156      const char *log_redirect = get_env ("daemon_log_redirect", envp);
157      int fd = -1;
158      if (log_redirect && log_redirect[0] == '1')
159	fd = dup (2);
160      if (daemon (0, 0) < 0)
161	{
162	  fprintf (stderr, "DOWN-ROOT: daemonization failed\n");
163	}
164      else if (fd >= 3)
165	{
166	  dup2 (fd, 2);
167	  close (fd);
168	}
169    }
170}
171
172/*
173 * Close most of parent's fds.
174 * Keep stdin/stdout/stderr, plus one
175 * other fd which is presumed to be
176 * our pipe back to parent.
177 * Admittedly, a bit of a kludge,
178 * but posix doesn't give us a kind
179 * of FD_CLOEXEC which will stop
180 * fds from crossing a fork().
181 */
182static void
183close_fds_except (int keep)
184{
185  int i;
186  closelog ();
187  for (i = 3; i <= 100; ++i)
188    {
189      if (i != keep)
190	close (i);
191    }
192}
193
194/*
195 * Usually we ignore signals, because our parent will
196 * deal with them.
197 */
198static void
199set_signals (void)
200{
201  signal (SIGTERM, SIG_DFL);
202
203  signal (SIGINT, SIG_IGN);
204  signal (SIGHUP, SIG_IGN);
205  signal (SIGUSR1, SIG_IGN);
206  signal (SIGUSR2, SIG_IGN);
207  signal (SIGPIPE, SIG_IGN);
208}
209
210/*
211 * convert system() return into a success/failure value
212 */
213int
214system_ok (int stat)
215{
216#ifdef WIN32
217  return stat == 0;
218#else
219  return stat != -1 && WIFEXITED (stat) && WEXITSTATUS (stat) == 0;
220#endif
221}
222
223static char *
224build_command_line (const char *argv[])
225{
226  int size = 0;
227  int n = 0;
228  int i;
229  char *string;
230
231  /* precompute size */
232  if (argv)
233    {
234      for (i = 0; argv[i]; ++i)
235	{
236	  size += (strlen (argv[i]) + 1); /* string length plus trailing space */
237	  ++n;
238	}
239    }
240  ++size;                                 /* for null terminator */
241
242  /* allocate memory */
243  string = (char *) malloc (size);
244  if (!string)
245    {
246      fprintf (stderr, "DOWN-ROOT: out of memory\n");
247      exit (1);
248    }
249  string[0] = '\0';
250
251  /* build string */
252  for (i = 0; i < n; ++i)
253    {
254      strcat (string, argv[i]);
255      if (i + 1 < n)
256	strcat (string, " ");
257    }
258  return string;
259}
260
261static void
262free_context (struct down_root_context *context)
263{
264  if (context)
265    {
266      if (context->command)
267	free (context->command);
268      free (context);
269    }
270}
271
272OPENVPN_EXPORT openvpn_plugin_handle_t
273openvpn_plugin_open_v1 (unsigned int *type_mask, const char *argv[], const char *envp[])
274{
275  struct down_root_context *context;
276
277  /*
278   * Allocate our context
279   */
280  context = (struct down_root_context *) calloc (1, sizeof (struct down_root_context));
281  if (!context)
282    goto error;
283  context->foreground_fd = -1;
284
285  /*
286   * Intercept the --up and --down callbacks
287   */
288  *type_mask = OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_UP) | OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_DOWN);
289
290  /*
291   * Make sure we have two string arguments: the first is the .so name,
292   * the second is the script command.
293   */
294  if (string_array_len (argv) < 2)
295    {
296      fprintf (stderr, "DOWN-ROOT: need down script command\n");
297      goto error;
298    }
299
300  /*
301   * Save our argument in context
302   */
303  context->command = build_command_line (&argv[1]);
304
305  /*
306   * Get verbosity level from environment
307   */
308  {
309    const char *verb_string = get_env ("verb", envp);
310    if (verb_string)
311      context->verb = atoi (verb_string);
312  }
313
314  return (openvpn_plugin_handle_t) context;
315
316 error:
317  free_context (context);
318  return NULL;
319}
320
321OPENVPN_EXPORT int
322openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
323{
324  struct down_root_context *context = (struct down_root_context *) handle;
325
326  if (type == OPENVPN_PLUGIN_UP && context->foreground_fd == -1) /* fork off a process to hold onto root */
327    {
328      pid_t pid;
329      int fd[2];
330
331      /*
332       * Make a socket for foreground and background processes
333       * to communicate.
334       */
335      if (socketpair (PF_UNIX, SOCK_DGRAM, 0, fd) == -1)
336	{
337	  fprintf (stderr, "DOWN-ROOT: socketpair call failed\n");
338	  return OPENVPN_PLUGIN_FUNC_ERROR;
339	}
340
341      /*
342       * Fork off the privileged process.  It will remain privileged
343       * even after the foreground process drops its privileges.
344       */
345      pid = fork ();
346
347      if (pid)
348	{
349	  int status;
350
351	  /*
352	   * Foreground Process
353	   */
354
355	  context->background_pid = pid;
356
357	  /* close our copy of child's socket */
358	  close (fd[1]);
359
360	  /* don't let future subprocesses inherit child socket */
361	  if (fcntl (fd[0], F_SETFD, FD_CLOEXEC) < 0)
362	    fprintf (stderr, "DOWN-ROOT: Set FD_CLOEXEC flag on socket file descriptor failed\n");
363
364	  /* wait for background child process to initialize */
365	  status = recv_control (fd[0]);
366	  if (status == RESPONSE_INIT_SUCCEEDED)
367	    {
368	      context->foreground_fd = fd[0];
369	      return OPENVPN_PLUGIN_FUNC_SUCCESS;
370	    }
371	}
372      else
373	{
374	  /*
375	   * Background Process
376	   */
377
378	  /* close all parent fds except our socket back to parent */
379	  close_fds_except (fd[1]);
380
381	  /* Ignore most signals (the parent will receive them) */
382	  set_signals ();
383
384	  /* Daemonize if --daemon option is set. */
385	  daemonize (envp);
386
387	  /* execute the event loop */
388	  down_root_server (fd[1], context->command, argv, envp, context->verb);
389
390	  close (fd[1]);
391	  exit (0);
392	  return 0; /* NOTREACHED */
393	}
394    }
395  else if (type == OPENVPN_PLUGIN_DOWN && context->foreground_fd >= 0)
396    {
397      if (send_control (context->foreground_fd, COMMAND_RUN_SCRIPT) == -1)
398	{
399	  fprintf (stderr, "DOWN-ROOT: Error sending script execution signal to background process\n");
400	}
401      else
402	{
403	  const int status = recv_control (context->foreground_fd);
404	  if (status == RESPONSE_SCRIPT_SUCCEEDED)
405	    return OPENVPN_PLUGIN_FUNC_SUCCESS;
406	  if (status == -1)
407	    fprintf (stderr, "DOWN-ROOT: Error receiving script execution confirmation from background process\n");
408	}
409    }
410  return OPENVPN_PLUGIN_FUNC_ERROR;
411}
412
413OPENVPN_EXPORT void
414openvpn_plugin_close_v1 (openvpn_plugin_handle_t handle)
415{
416  struct down_root_context *context = (struct down_root_context *) handle;
417
418  if (DEBUG (context->verb))
419    fprintf (stderr, "DOWN-ROOT: close\n");
420
421  if (context->foreground_fd >= 0)
422    {
423      /* tell background process to exit */
424      if (send_control (context->foreground_fd, COMMAND_EXIT) == -1)
425	fprintf (stderr, "DOWN-ROOT: Error signaling background process to exit\n");
426
427      /* wait for background process to exit */
428      if (context->background_pid > 0)
429	waitpid (context->background_pid, NULL, 0);
430
431      close (context->foreground_fd);
432      context->foreground_fd = -1;
433    }
434
435  free_context (context);
436}
437
438OPENVPN_EXPORT void
439openvpn_plugin_abort_v1 (openvpn_plugin_handle_t handle)
440{
441  struct down_root_context *context = (struct down_root_context *) handle;
442
443  if (context && context->foreground_fd >= 0)
444    {
445      /* tell background process to exit */
446      send_control (context->foreground_fd, COMMAND_EXIT);
447      close (context->foreground_fd);
448      context->foreground_fd = -1;
449    }
450}
451
452/*
453 * Background process -- runs with privilege.
454 */
455static void
456down_root_server (const int fd, char *command, const char *argv[], const char *envp[], const int verb)
457{
458  const char *p[3];
459  char *command_line = NULL;
460  char *argv_cat = NULL;
461  int i;
462
463  /*
464   * Do initialization
465   */
466  if (DEBUG (verb))
467    fprintf (stderr, "DOWN-ROOT: BACKGROUND: INIT command='%s'\n", command);
468
469  /*
470   * Tell foreground that we initialized successfully
471   */
472  if (send_control (fd, RESPONSE_INIT_SUCCEEDED) == -1)
473    {
474      fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [1]\n");
475      goto done;
476    }
477
478  /*
479   * Build command line
480   */
481  if (string_array_len (argv) >= 2)
482    argv_cat = build_command_line (&argv[1]);
483  else
484    argv_cat = build_command_line (NULL);
485  p[0] = command;
486  p[1] = argv_cat;
487  p[2] = NULL;
488  command_line = build_command_line (p);
489
490  /*
491   * Save envp in environment
492   */
493  for (i = 0; envp[i]; ++i)
494    {
495      putenv ((char *)envp[i]);
496    }
497
498  /*
499   * Event loop
500   */
501  while (1)
502    {
503      int command_code;
504      int status;
505
506      /* get a command from foreground process */
507      command_code = recv_control (fd);
508
509      if (DEBUG (verb))
510	fprintf (stderr, "DOWN-ROOT: BACKGROUND: received command code: %d\n", command_code);
511
512      switch (command_code)
513	{
514	case COMMAND_RUN_SCRIPT:
515	  status = system (command_line);
516	  if (system_ok (status)) /* Succeeded */
517	    {
518	      if (send_control (fd, RESPONSE_SCRIPT_SUCCEEDED) == -1)
519		{
520		  fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [2]\n");
521		  goto done;
522		}
523	    }
524	  else /* Failed */
525	    {
526	      if (send_control (fd, RESPONSE_SCRIPT_FAILED) == -1)
527		{
528		  fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [3]\n");
529		  goto done;
530		}
531	    }
532	  break;
533
534	case COMMAND_EXIT:
535	  goto done;
536
537	case -1:
538	  fprintf (stderr, "DOWN-ROOT: BACKGROUND: read error on command channel\n");
539	  goto done;
540
541	default:
542	  fprintf (stderr, "DOWN-ROOT: BACKGROUND: unknown command code: code=%d, exiting\n",
543		   command_code);
544	  goto done;
545	}
546    }
547
548 done:
549  if (argv_cat)
550    free (argv_cat);
551  if (command_line)
552    free (command_line);
553  if (DEBUG (verb))
554    fprintf (stderr, "DOWN-ROOT: BACKGROUND: EXIT\n");
555
556  return;
557}
558