mirror of
https://github.com/OpenVPN/openvpn.git
synced 2026-02-03 20:39:40 -05:00
git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@5599 e7ae566f-a301-0410-adde-c780ea21d3b5
553 lines
12 KiB
C
553 lines
12 KiB
C
/*
|
|
* 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.
|
|
*
|
|
* Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program (see the file COPYING included with this
|
|
* distribution); if not, write to the Free Software Foundation, Inc.,
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
/*
|
|
* OpenVPN plugin module to do privileged down-script execution.
|
|
*/
|
|
|
|
#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>
|
|
|
|
#include "openvpn-plugin.h"
|
|
|
|
#define DEBUG(verb) ((verb) >= 7)
|
|
|
|
/* Command codes for foreground -> background communication */
|
|
#define COMMAND_RUN_SCRIPT 0
|
|
#define COMMAND_EXIT 1
|
|
|
|
/* 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 */
|
|
static void down_root_server (const int fd, char *command, const char *argv[], const char *envp[], const int verb);
|
|
|
|
/*
|
|
* Plugin state, used by foreground
|
|
*/
|
|
struct down_root_context
|
|
{
|
|
/* Foreground's socket to background process */
|
|
int foreground_fd;
|
|
|
|
/* Process ID of background process */
|
|
pid_t background_pid;
|
|
|
|
/* Verbosity level of OpenVPN */
|
|
int verb;
|
|
|
|
/* down command */
|
|
char *command;
|
|
};
|
|
|
|
/*
|
|
* Given an environmental variable name, search
|
|
* the envp array for its value, returning it
|
|
* if found or NULL otherwise.
|
|
*/
|
|
static const char *
|
|
get_env (const char *name, const char *envp[])
|
|
{
|
|
if (envp)
|
|
{
|
|
int i;
|
|
const int namelen = strlen (name);
|
|
for (i = 0; envp[i]; ++i)
|
|
{
|
|
if (!strncmp (envp[i], name, namelen))
|
|
{
|
|
const char *cp = envp[i] + namelen;
|
|
if (*cp == '=')
|
|
return cp + 1;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Return the length of a string array
|
|
*/
|
|
static int
|
|
string_array_len (const char *array[])
|
|
{
|
|
int i = 0;
|
|
if (array)
|
|
{
|
|
while (array[i])
|
|
++i;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* Socket read/write functions.
|
|
*/
|
|
|
|
static int
|
|
recv_control (int fd)
|
|
{
|
|
unsigned char c;
|
|
const ssize_t size = read (fd, &c, sizeof (c));
|
|
if (size == sizeof (c))
|
|
return c;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
send_control (int fd, int code)
|
|
{
|
|
unsigned char c = (unsigned char) code;
|
|
const ssize_t size = write (fd, &c, sizeof (c));
|
|
if (size == sizeof (c))
|
|
return (int) size;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Daemonize if "daemon" env var is true.
|
|
* Preserve stderr across daemonization if
|
|
* "daemon_log_redirect" env var is true.
|
|
*/
|
|
static void
|
|
daemonize (const char *envp[])
|
|
{
|
|
const char *daemon_string = get_env ("daemon", envp);
|
|
if (daemon_string && daemon_string[0] == '1')
|
|
{
|
|
const char *log_redirect = get_env ("daemon_log_redirect", envp);
|
|
int fd = -1;
|
|
if (log_redirect && log_redirect[0] == '1')
|
|
fd = dup (2);
|
|
if (daemon (0, 0) < 0)
|
|
{
|
|
fprintf (stderr, "DOWN-ROOT: daemonization failed\n");
|
|
}
|
|
else if (fd >= 3)
|
|
{
|
|
dup2 (fd, 2);
|
|
close (fd);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
close_fds_except (int keep)
|
|
{
|
|
int i;
|
|
closelog ();
|
|
for (i = 3; i <= 100; ++i)
|
|
{
|
|
if (i != keep)
|
|
close (i);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Usually we ignore signals, because our parent will
|
|
* deal with them.
|
|
*/
|
|
static void
|
|
set_signals (void)
|
|
{
|
|
signal (SIGTERM, SIG_DFL);
|
|
|
|
signal (SIGINT, SIG_IGN);
|
|
signal (SIGHUP, SIG_IGN);
|
|
signal (SIGUSR1, SIG_IGN);
|
|
signal (SIGUSR2, SIG_IGN);
|
|
signal (SIGPIPE, SIG_IGN);
|
|
}
|
|
|
|
/*
|
|
* convert system() return into a success/failure value
|
|
*/
|
|
int
|
|
system_ok (int stat)
|
|
{
|
|
#ifdef WIN32
|
|
return stat == 0;
|
|
#else
|
|
return stat != -1 && WIFEXITED (stat) && WEXITSTATUS (stat) == 0;
|
|
#endif
|
|
}
|
|
|
|
static char *
|
|
build_command_line (const char *argv[])
|
|
{
|
|
int size = 0;
|
|
int n = 0;
|
|
int i;
|
|
char *string;
|
|
|
|
/* precompute size */
|
|
if (argv)
|
|
{
|
|
for (i = 0; argv[i]; ++i)
|
|
{
|
|
size += (strlen (argv[i]) + 1); /* string length plus trailing space */
|
|
++n;
|
|
}
|
|
}
|
|
++size; /* for null terminator */
|
|
|
|
/* allocate memory */
|
|
string = (char *) malloc (size);
|
|
if (!string)
|
|
{
|
|
fprintf (stderr, "DOWN-ROOT: out of memory\n");
|
|
exit (1);
|
|
}
|
|
string[0] = '\0';
|
|
|
|
/* build string */
|
|
for (i = 0; i < n; ++i)
|
|
{
|
|
strcat (string, argv[i]);
|
|
if (i + 1 < n)
|
|
strcat (string, " ");
|
|
}
|
|
return string;
|
|
}
|
|
|
|
static void
|
|
free_context (struct down_root_context *context)
|
|
{
|
|
if (context)
|
|
{
|
|
if (context->command)
|
|
free (context->command);
|
|
free (context);
|
|
}
|
|
}
|
|
|
|
OPENVPN_EXPORT openvpn_plugin_handle_t
|
|
openvpn_plugin_open_v1 (unsigned int *type_mask, const char *argv[], const char *envp[])
|
|
{
|
|
struct down_root_context *context;
|
|
|
|
/*
|
|
* Allocate our context
|
|
*/
|
|
context = (struct down_root_context *) calloc (1, sizeof (struct down_root_context));
|
|
if (!context)
|
|
goto error;
|
|
context->foreground_fd = -1;
|
|
|
|
/*
|
|
* Intercept the --up and --down callbacks
|
|
*/
|
|
*type_mask = OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_UP) | OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_DOWN);
|
|
|
|
/*
|
|
* Make sure we have two string arguments: the first is the .so name,
|
|
* the second is the script command.
|
|
*/
|
|
if (string_array_len (argv) < 2)
|
|
{
|
|
fprintf (stderr, "DOWN-ROOT: need down script command\n");
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* Save our argument in context
|
|
*/
|
|
context->command = build_command_line (&argv[1]);
|
|
|
|
/*
|
|
* Get verbosity level from environment
|
|
*/
|
|
{
|
|
const char *verb_string = get_env ("verb", envp);
|
|
if (verb_string)
|
|
context->verb = atoi (verb_string);
|
|
}
|
|
|
|
return (openvpn_plugin_handle_t) context;
|
|
|
|
error:
|
|
free_context (context);
|
|
return NULL;
|
|
}
|
|
|
|
OPENVPN_EXPORT int
|
|
openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
|
|
{
|
|
struct down_root_context *context = (struct down_root_context *) handle;
|
|
|
|
if (type == OPENVPN_PLUGIN_UP && context->foreground_fd == -1) /* fork off a process to hold onto root */
|
|
{
|
|
pid_t pid;
|
|
int fd[2];
|
|
|
|
/*
|
|
* Make a socket for foreground and background processes
|
|
* to communicate.
|
|
*/
|
|
if (socketpair (PF_UNIX, SOCK_DGRAM, 0, fd) == -1)
|
|
{
|
|
fprintf (stderr, "DOWN-ROOT: socketpair call failed\n");
|
|
return OPENVPN_PLUGIN_FUNC_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Fork off the privileged process. It will remain privileged
|
|
* even after the foreground process drops its privileges.
|
|
*/
|
|
pid = fork ();
|
|
|
|
if (pid)
|
|
{
|
|
int status;
|
|
|
|
/*
|
|
* Foreground Process
|
|
*/
|
|
|
|
context->background_pid = pid;
|
|
|
|
/* close our copy of child's socket */
|
|
close (fd[1]);
|
|
|
|
/* don't let future subprocesses inherit child socket */
|
|
if (fcntl (fd[0], F_SETFD, FD_CLOEXEC) < 0)
|
|
fprintf (stderr, "DOWN-ROOT: Set FD_CLOEXEC flag on socket file descriptor failed\n");
|
|
|
|
/* wait for background child process to initialize */
|
|
status = recv_control (fd[0]);
|
|
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 */
|
|
close_fds_except (fd[1]);
|
|
|
|
/* Ignore most signals (the parent will receive them) */
|
|
set_signals ();
|
|
|
|
/* Daemonize if --daemon option is set. */
|
|
daemonize (envp);
|
|
|
|
/* execute the event loop */
|
|
down_root_server (fd[1], context->command, argv, envp, context->verb);
|
|
|
|
close (fd[1]);
|
|
exit (0);
|
|
return 0; /* NOTREACHED */
|
|
}
|
|
}
|
|
else if (type == OPENVPN_PLUGIN_DOWN && context->foreground_fd >= 0)
|
|
{
|
|
if (send_control (context->foreground_fd, COMMAND_RUN_SCRIPT) == -1)
|
|
{
|
|
fprintf (stderr, "DOWN-ROOT: Error sending script execution signal to background process\n");
|
|
}
|
|
else
|
|
{
|
|
const int status = recv_control (context->foreground_fd);
|
|
if (status == RESPONSE_SCRIPT_SUCCEEDED)
|
|
return OPENVPN_PLUGIN_FUNC_SUCCESS;
|
|
if (status == -1)
|
|
fprintf (stderr, "DOWN-ROOT: Error receiving script execution confirmation from background process\n");
|
|
}
|
|
}
|
|
return OPENVPN_PLUGIN_FUNC_ERROR;
|
|
}
|
|
|
|
OPENVPN_EXPORT void
|
|
openvpn_plugin_close_v1 (openvpn_plugin_handle_t handle)
|
|
{
|
|
struct down_root_context *context = (struct down_root_context *) handle;
|
|
|
|
if (DEBUG (context->verb))
|
|
fprintf (stderr, "DOWN-ROOT: close\n");
|
|
|
|
if (context->foreground_fd >= 0)
|
|
{
|
|
/* tell background process to exit */
|
|
if (send_control (context->foreground_fd, COMMAND_EXIT) == -1)
|
|
fprintf (stderr, "DOWN-ROOT: Error signaling background process to exit\n");
|
|
|
|
/* wait for background process to exit */
|
|
if (context->background_pid > 0)
|
|
waitpid (context->background_pid, NULL, 0);
|
|
|
|
close (context->foreground_fd);
|
|
context->foreground_fd = -1;
|
|
}
|
|
|
|
free_context (context);
|
|
}
|
|
|
|
OPENVPN_EXPORT void
|
|
openvpn_plugin_abort_v1 (openvpn_plugin_handle_t handle)
|
|
{
|
|
struct down_root_context *context = (struct down_root_context *) handle;
|
|
|
|
if (context && context->foreground_fd >= 0)
|
|
{
|
|
/* tell background process to exit */
|
|
send_control (context->foreground_fd, COMMAND_EXIT);
|
|
close (context->foreground_fd);
|
|
context->foreground_fd = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Background process -- runs with privilege.
|
|
*/
|
|
static void
|
|
down_root_server (const int fd, char *command, const char *argv[], const char *envp[], const int verb)
|
|
{
|
|
const char *p[3];
|
|
char *command_line = NULL;
|
|
char *argv_cat = NULL;
|
|
int i;
|
|
|
|
/*
|
|
* Do initialization
|
|
*/
|
|
if (DEBUG (verb))
|
|
fprintf (stderr, "DOWN-ROOT: BACKGROUND: INIT command='%s'\n", command);
|
|
|
|
/*
|
|
* Tell foreground that we initialized successfully
|
|
*/
|
|
if (send_control (fd, RESPONSE_INIT_SUCCEEDED) == -1)
|
|
{
|
|
fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [1]\n");
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Build command line
|
|
*/
|
|
if (string_array_len (argv) >= 2)
|
|
argv_cat = build_command_line (&argv[1]);
|
|
else
|
|
argv_cat = build_command_line (NULL);
|
|
p[0] = command;
|
|
p[1] = argv_cat;
|
|
p[2] = NULL;
|
|
command_line = build_command_line (p);
|
|
|
|
/*
|
|
* Save envp in environment
|
|
*/
|
|
for (i = 0; envp[i]; ++i)
|
|
{
|
|
putenv ((char *)envp[i]);
|
|
}
|
|
|
|
/*
|
|
* Event loop
|
|
*/
|
|
while (1)
|
|
{
|
|
int command_code;
|
|
int status;
|
|
|
|
/* get a command from foreground process */
|
|
command_code = recv_control (fd);
|
|
|
|
if (DEBUG (verb))
|
|
fprintf (stderr, "DOWN-ROOT: BACKGROUND: received command code: %d\n", command_code);
|
|
|
|
switch (command_code)
|
|
{
|
|
case COMMAND_RUN_SCRIPT:
|
|
status = system (command_line);
|
|
if (system_ok (status)) /* Succeeded */
|
|
{
|
|
if (send_control (fd, RESPONSE_SCRIPT_SUCCEEDED) == -1)
|
|
{
|
|
fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [2]\n");
|
|
goto done;
|
|
}
|
|
}
|
|
else /* Failed */
|
|
{
|
|
if (send_control (fd, RESPONSE_SCRIPT_FAILED) == -1)
|
|
{
|
|
fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [3]\n");
|
|
goto done;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case COMMAND_EXIT:
|
|
goto done;
|
|
|
|
case -1:
|
|
fprintf (stderr, "DOWN-ROOT: BACKGROUND: read error on command channel\n");
|
|
goto done;
|
|
|
|
default:
|
|
fprintf (stderr, "DOWN-ROOT: BACKGROUND: unknown command code: code=%d, exiting\n",
|
|
command_code);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
done:
|
|
if (argv_cat)
|
|
free (argv_cat);
|
|
if (command_line)
|
|
free (command_line);
|
|
if (DEBUG (verb))
|
|
fprintf (stderr, "DOWN-ROOT: BACKGROUND: EXIT\n");
|
|
|
|
return;
|
|
}
|