src/plugins/down-root/down-root.c
6fbf66fa
 /*
  *  OpenVPN -- An application to securely tunnel IP networks
  *             over a single TCP/UDP port, with support for SSL/TLS-based
  *             session authentication and key exchange,
  *             packet encryption, packet authentication, and
  *             packet compression.
  *
49979459
  *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
f87b1bec
  *  Copyright (C) 2013      David Sommerseth <davids@redhat.com>
6fbf66fa
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2
  *  as published by the Free Software Foundation.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
caa54ac3
  *  You should have received a copy of the GNU General Public License along
  *  with this program; if not, write to the Free Software Foundation, Inc.,
  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
6fbf66fa
  */
 
 /*
  * OpenVPN plugin module to do privileged down-script execution.
  */
 
ce8271f5
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 
6fbf66fa
 #include <stdio.h>
 #include <string.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/wait.h>
 #include <fcntl.h>
 #include <signal.h>
 #include <syslog.h>
b0f2c521
 #include <errno.h>
7dd51f6f
 #include <err.h>
6fbf66fa
 
ce8271f5
 #include <openvpn-plugin.h>
6fbf66fa
 
 #define DEBUG(verb) ((verb) >= 7)
 
 /* Command codes for foreground -> background communication */
f87b1bec
 #define COMMAND_RUN_SCRIPT 1
 #define COMMAND_EXIT       2
6fbf66fa
 
 /* Response codes for background -> foreground communication */
 #define RESPONSE_INIT_SUCCEEDED   10
 #define RESPONSE_INIT_FAILED      11
 #define RESPONSE_SCRIPT_SUCCEEDED 12
 #define RESPONSE_SCRIPT_FAILED    13
 
 /* Background process function */
81d882d5
 static void down_root_server(const int fd, char *const *argv, char *const *envp, const int verb);
6fbf66fa
 
 /*
  * Plugin state, used by foreground
  */
 struct down_root_context
 {
e2e9a69c
     /* Foreground's socket to background process */
     int foreground_fd;
6fbf66fa
 
e2e9a69c
     /* Process ID of background process */
     pid_t background_pid;
6fbf66fa
 
e2e9a69c
     /* Verbosity level of OpenVPN */
     int verb;
6fbf66fa
 
e2e9a69c
     /* down command */
     char **command;
6fbf66fa
 };
 
 /*
  * Given an environmental variable name, search
  * the envp array for its value, returning it
  * if found or NULL otherwise.
  */
 static const char *
81d882d5
 get_env(const char *name, const char *envp[])
6fbf66fa
 {
e2e9a69c
     if (envp)
6fbf66fa
     {
e2e9a69c
         int i;
81d882d5
         const int namelen = strlen(name);
e2e9a69c
         for (i = 0; envp[i]; ++i)
         {
81d882d5
             if (!strncmp(envp[i], name, namelen))
e2e9a69c
             {
                 const char *cp = envp[i] + namelen;
                 if (*cp == '=')
81d882d5
                 {
e2e9a69c
                     return cp + 1;
81d882d5
                 }
e2e9a69c
             }
         }
6fbf66fa
     }
e2e9a69c
     return NULL;
6fbf66fa
 }
 
 /*
  * Return the length of a string array
  */
 static int
81d882d5
 string_array_len(const char *array[])
6fbf66fa
 {
e2e9a69c
     int i = 0;
     if (array)
6fbf66fa
     {
e2e9a69c
         while (array[i])
4cd4899e
         {
e2e9a69c
             ++i;
4cd4899e
         }
6fbf66fa
     }
e2e9a69c
     return i;
6fbf66fa
 }
 
 /*
  * Socket read/write functions.
  */
 
 static int
81d882d5
 recv_control(int fd)
6fbf66fa
 {
e2e9a69c
     unsigned char c;
81d882d5
     const ssize_t size = read(fd, &c, sizeof(c));
     if (size == sizeof(c))
     {
e2e9a69c
         return c;
81d882d5
     }
e2e9a69c
     else
81d882d5
     {
e2e9a69c
         return -1;
81d882d5
     }
6fbf66fa
 }
 
 static int
81d882d5
 send_control(int fd, int code)
6fbf66fa
 {
e2e9a69c
     unsigned char c = (unsigned char) code;
81d882d5
     const ssize_t size = write(fd, &c, sizeof(c));
     if (size == sizeof(c))
     {
e2e9a69c
         return (int) size;
81d882d5
     }
e2e9a69c
     else
81d882d5
     {
e2e9a69c
         return -1;
81d882d5
     }
6fbf66fa
 }
 
 /*
  * Daemonize if "daemon" env var is true.
  * Preserve stderr across daemonization if
  * "daemon_log_redirect" env var is true.
  */
 static void
81d882d5
 daemonize(const char *envp[])
6fbf66fa
 {
81d882d5
     const char *daemon_string = get_env("daemon", envp);
e2e9a69c
     if (daemon_string && daemon_string[0] == '1')
6fbf66fa
     {
81d882d5
         const char *log_redirect = get_env("daemon_log_redirect", envp);
e2e9a69c
         int fd = -1;
         if (log_redirect && log_redirect[0] == '1')
         {
81d882d5
             fd = dup(2);
         }
         if (daemon(0, 0) < 0)
         {
             warn("DOWN-ROOT: daemonization failed");
e2e9a69c
         }
         else if (fd >= 3)
         {
81d882d5
             dup2(fd, 2);
             close(fd);
e2e9a69c
         }
6fbf66fa
     }
 }
 
 /*
  * Close most of parent's fds.
  * Keep stdin/stdout/stderr, plus one
  * other fd which is presumed to be
  * our pipe back to parent.
  * Admittedly, a bit of a kludge,
  * but posix doesn't give us a kind
  * of FD_CLOEXEC which will stop
  * fds from crossing a fork().
  */
 static void
81d882d5
 close_fds_except(int keep)
6fbf66fa
 {
e2e9a69c
     int i;
81d882d5
     closelog();
e2e9a69c
     for (i = 3; i <= 100; ++i)
6fbf66fa
     {
e2e9a69c
         if (i != keep)
81d882d5
         {
             close(i);
         }
6fbf66fa
     }
 }
 
 /*
  * Usually we ignore signals, because our parent will
  * deal with them.
  */
 static void
81d882d5
 set_signals(void)
6fbf66fa
 {
81d882d5
     signal(SIGTERM, SIG_DFL);
6fbf66fa
 
81d882d5
     signal(SIGINT, SIG_IGN);
     signal(SIGHUP, SIG_IGN);
     signal(SIGUSR1, SIG_IGN);
     signal(SIGUSR2, SIG_IGN);
     signal(SIGPIPE, SIG_IGN);
6fbf66fa
 }
 
 
 static void
81d882d5
 free_context(struct down_root_context *context)
6fbf66fa
 {
e2e9a69c
     if (context)
6fbf66fa
     {
e2e9a69c
         if (context->command)
         {
81d882d5
             free(context->command);
e2e9a69c
         }
81d882d5
         free(context);
6fbf66fa
     }
 }
 
f87b1bec
 /* Run the script using execve().  As execve() replaces the
  * current process with the new one, do a fork first before
  * calling execve()
  */
 static int
81d882d5
 run_script(char *const *argv, char *const *envp)
e2e9a69c
 {
     pid_t pid;
     int ret = 0;
 
     pid = fork();
     if (pid == (pid_t)0)   /* child side */
     {
         execve(argv[0], argv, envp);
         /* If execve() fails to run, exit child with exit code 127 */
         err(127, "DOWN-ROOT: Failed execute: %s", argv[0]);
     }
81d882d5
     else if (pid < (pid_t)0)
e2e9a69c
     {
81d882d5
         warn("DOWN-ROOT: Failed to fork child to run %s", argv[0]);
e2e9a69c
         return -1;
     }
     else     /* parent side */
     {
81d882d5
         if (waitpid(pid, &ret, 0) != pid)
e2e9a69c
         {
             /* waitpid does not return error information via errno */
             fprintf(stderr, "DOWN-ROOT: waitpid() failed, don't know exit code of child (%s)\n", argv[0]);
             return -1;
         }
f87b1bec
     }
e2e9a69c
     return ret;
f87b1bec
 }
 
6fbf66fa
 OPENVPN_EXPORT openvpn_plugin_handle_t
81d882d5
 openvpn_plugin_open_v1(unsigned int *type_mask, const char *argv[], const char *envp[])
6fbf66fa
 {
e2e9a69c
     struct down_root_context *context;
     int i = 0;
 
     /*
      * Allocate our context
      */
81d882d5
     context = (struct down_root_context *) calloc(1, sizeof(struct down_root_context));
e2e9a69c
     if (!context)
b0f2c521
     {
81d882d5
         warn("DOWN-ROOT: Could not allocate memory for plug-in context");
e2e9a69c
         goto error;
b0f2c521
     }
e2e9a69c
     context->foreground_fd = -1;
 
     /*
      * Intercept the --up and --down callbacks
      */
81d882d5
     *type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_UP) | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_DOWN);
e2e9a69c
 
     /*
      * Make sure we have two string arguments: the first is the .so name,
      * the second is the script command.
      */
81d882d5
     if (string_array_len(argv) < 2)
6fbf66fa
     {
81d882d5
         fprintf(stderr, "DOWN-ROOT: need down script command\n");
e2e9a69c
         goto error;
6fbf66fa
     }
 
e2e9a69c
     /*
      * Save the arguments in our context
      */
     context->command = calloc(string_array_len(argv), sizeof(char *));
     if (!context->command)
b0f2c521
     {
81d882d5
         warn("DOWN-ROOT: Could not allocate memory for command array");
e2e9a69c
         goto error;
b0f2c521
     }
 
e2e9a69c
     /* Ignore argv[0], as it contains just the plug-in file name */
     for (i = 1; i < string_array_len(argv); i++)
f87b1bec
     {
e2e9a69c
         context->command[i-1] = (char *) argv[i];
f87b1bec
     }
6fbf66fa
 
e2e9a69c
     /*
      * Get verbosity level from environment
      */
     {
81d882d5
         const char *verb_string = get_env("verb", envp);
e2e9a69c
         if (verb_string)
81d882d5
         {
             context->verb = atoi(verb_string);
         }
e2e9a69c
     }
6fbf66fa
 
e2e9a69c
     return (openvpn_plugin_handle_t) context;
6fbf66fa
 
e2e9a69c
 error:
81d882d5
     free_context(context);
e2e9a69c
     return NULL;
6fbf66fa
 }
 
 OPENVPN_EXPORT int
81d882d5
 openvpn_plugin_func_v1(openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
6fbf66fa
 {
e2e9a69c
     struct down_root_context *context = (struct down_root_context *) handle;
6fbf66fa
 
e2e9a69c
     if (type == OPENVPN_PLUGIN_UP && context->foreground_fd == -1) /* fork off a process to hold onto root */
6fbf66fa
     {
e2e9a69c
         pid_t pid;
         int fd[2];
 
         /*
          * Make a socket for foreground and background processes
          * to communicate.
          */
81d882d5
         if (socketpair(PF_UNIX, SOCK_DGRAM, 0, fd) == -1)
e2e9a69c
         {
81d882d5
             warn("DOWN-ROOT: socketpair call failed");
e2e9a69c
             return OPENVPN_PLUGIN_FUNC_ERROR;
         }
 
         /*
          * Fork off the privileged process.  It will remain privileged
          * even after the foreground process drops its privileges.
          */
81d882d5
         pid = fork();
e2e9a69c
 
         if (pid)
         {
             int status;
 
             /*
              * Foreground Process
              */
 
             context->background_pid = pid;
 
             /* close our copy of child's socket */
81d882d5
             close(fd[1]);
e2e9a69c
 
             /* don't let future subprocesses inherit child socket */
81d882d5
             if (fcntl(fd[0], F_SETFD, FD_CLOEXEC) < 0)
e2e9a69c
             {
81d882d5
                 warn("DOWN-ROOT: Set FD_CLOEXEC flag on socket file descriptor failed");
e2e9a69c
             }
 
             /* wait for background child process to initialize */
81d882d5
             status = recv_control(fd[0]);
e2e9a69c
             if (status == RESPONSE_INIT_SUCCEEDED)
             {
                 context->foreground_fd = fd[0];
                 return OPENVPN_PLUGIN_FUNC_SUCCESS;
             }
         }
         else
         {
             /*
              * Background Process
              */
 
             /* close all parent fds except our socket back to parent */
81d882d5
             close_fds_except(fd[1]);
e2e9a69c
 
             /* Ignore most signals (the parent will receive them) */
81d882d5
             set_signals();
e2e9a69c
 
             /* Daemonize if --daemon option is set. */
81d882d5
             daemonize(envp);
e2e9a69c
 
             /* execute the event loop */
81d882d5
             down_root_server(fd[1], context->command, (char *const *) envp, context->verb);
e2e9a69c
 
81d882d5
             close(fd[1]);
             exit(0);
e2e9a69c
             return 0; /* NOTREACHED */
         }
6fbf66fa
     }
e2e9a69c
     else if (type == OPENVPN_PLUGIN_DOWN && context->foreground_fd >= 0)
6fbf66fa
     {
81d882d5
         if (send_control(context->foreground_fd, COMMAND_RUN_SCRIPT) == -1)
e2e9a69c
         {
81d882d5
             warn("DOWN-ROOT: Error sending script execution signal to background process");
e2e9a69c
         }
         else
         {
81d882d5
             const int status = recv_control(context->foreground_fd);
e2e9a69c
             if (status == RESPONSE_SCRIPT_SUCCEEDED)
81d882d5
             {
e2e9a69c
                 return OPENVPN_PLUGIN_FUNC_SUCCESS;
81d882d5
             }
e2e9a69c
             if (status == -1)
             {
81d882d5
                 warn("DOWN-ROOT: Error receiving script execution confirmation from background process");
e2e9a69c
             }
         }
6fbf66fa
     }
e2e9a69c
     return OPENVPN_PLUGIN_FUNC_ERROR;
6fbf66fa
 }
 
 OPENVPN_EXPORT void
81d882d5
 openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
6fbf66fa
 {
e2e9a69c
     struct down_root_context *context = (struct down_root_context *) handle;
6fbf66fa
 
81d882d5
     if (DEBUG(context->verb))
     {
         fprintf(stderr, "DOWN-ROOT: close\n");
     }
6fbf66fa
 
e2e9a69c
     if (context->foreground_fd >= 0)
6fbf66fa
     {
e2e9a69c
         /* tell background process to exit */
81d882d5
         if (send_control(context->foreground_fd, COMMAND_EXIT) == -1)
e2e9a69c
         {
81d882d5
             warn("DOWN-ROOT: Error signalling background process to exit");
e2e9a69c
         }
 
         /* wait for background process to exit */
         if (context->background_pid > 0)
81d882d5
         {
             waitpid(context->background_pid, NULL, 0);
         }
e2e9a69c
 
81d882d5
         close(context->foreground_fd);
e2e9a69c
         context->foreground_fd = -1;
6fbf66fa
     }
 
81d882d5
     free_context(context);
6fbf66fa
 }
 
 OPENVPN_EXPORT void
81d882d5
 openvpn_plugin_abort_v1(openvpn_plugin_handle_t handle)
6fbf66fa
 {
e2e9a69c
     struct down_root_context *context = (struct down_root_context *) handle;
6fbf66fa
 
e2e9a69c
     if (context && context->foreground_fd >= 0)
6fbf66fa
     {
e2e9a69c
         /* tell background process to exit */
81d882d5
         send_control(context->foreground_fd, COMMAND_EXIT);
         close(context->foreground_fd);
e2e9a69c
         context->foreground_fd = -1;
6fbf66fa
     }
 }
 
 /*
  * Background process -- runs with privilege.
  */
 static void
81d882d5
 down_root_server(const int fd, char *const *argv, char *const *envp, const int verb)
6fbf66fa
 {
e2e9a69c
     /*
      * Do initialization
      */
81d882d5
     if (DEBUG(verb))
     {
         fprintf(stderr, "DOWN-ROOT: BACKGROUND: INIT command='%s'\n", argv[0]);
     }
e2e9a69c
 
     /*
      * Tell foreground that we initialized successfully
      */
81d882d5
     if (send_control(fd, RESPONSE_INIT_SUCCEEDED) == -1)
6fbf66fa
     {
81d882d5
         warn("DOWN-ROOT: BACKGROUND: write error on response socket [1]");
e2e9a69c
         goto done;
6fbf66fa
     }
 
e2e9a69c
     /*
      * Event loop
      */
     while (1)
6fbf66fa
     {
e2e9a69c
         int command_code;
         int exit_code = -1;
 
         /* get a command from foreground process */
81d882d5
         command_code = recv_control(fd);
e2e9a69c
 
81d882d5
         if (DEBUG(verb))
         {
             fprintf(stderr, "DOWN-ROOT: BACKGROUND: received command code: %d\n", command_code);
         }
e2e9a69c
 
         switch (command_code)
         {
81d882d5
             case COMMAND_RUN_SCRIPT:
                 if ( (exit_code = run_script(argv, envp)) == 0) /* Succeeded */
e2e9a69c
                 {
81d882d5
                     if (send_control(fd, RESPONSE_SCRIPT_SUCCEEDED) == -1)
                     {
                         warn("DOWN-ROOT: BACKGROUND: write error on response socket [2]");
                         goto done;
                     }
e2e9a69c
                 }
81d882d5
                 else /* Failed */
e2e9a69c
                 {
81d882d5
                     fprintf(stderr, "DOWN-ROOT: BACKGROUND: %s exited with exit code %i\n", argv[0], exit_code);
                     if (send_control(fd, RESPONSE_SCRIPT_FAILED) == -1)
                     {
                         warn("DOWN-ROOT: BACKGROUND: write error on response socket [3]");
                         goto done;
                     }
e2e9a69c
                 }
81d882d5
                 break;
e2e9a69c
 
81d882d5
             case COMMAND_EXIT:
                 goto done;
e2e9a69c
 
81d882d5
             case -1:
                 warn("DOWN-ROOT: BACKGROUND: read error on command channel");
                 goto done;
e2e9a69c
 
81d882d5
             default:
                 fprintf(stderr, "DOWN-ROOT: BACKGROUND: unknown command code: code=%d, exiting\n",
                         command_code);
                 goto done;
e2e9a69c
         }
6fbf66fa
     }
 
e2e9a69c
 done:
81d882d5
     if (DEBUG(verb))
     {
         fprintf(stderr, "DOWN-ROOT: BACKGROUND: EXIT\n");
     }
6fbf66fa
 
e2e9a69c
     return;
6fbf66fa
 }
e2e9a69c
 
 
 /*
81d882d5
  * Local variables:
  * c-file-style: "bsd"
  * c-basic-offset: 4
  * indent-tabs-mode: nil
  * End:
  */