contrib/server-side/svnstsw/src/exec_svnserve.c (118 lines of code) (raw):
/*
* Copyright (c) 2008 BBN Technologies Corp. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of BBN Technologies nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY BBN TECHNOLOGIES AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL BBN TECHNOLOGIES OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <svnstsw/exec_svnserve.h>
static _Bool is_svnserve_path_valid(const char* svnserve_path);
static _Bool is_svn_root_valid(const char* svn_root);
static _Bool is_tunnel_user_valid(const char* tunnel_user);
int
svnstsw_exec_svnserve(const char* svnserve_path, const char* svn_root,
const char* tunnel_user, const char* const* argv,
const char* const* envp)
{
//////////////////////
// path to svnserve //
//////////////////////
// make sure the path is valid
if (!is_svnserve_path_valid(svnserve_path))
return -1;
/////////////////////
// repository root //
/////////////////////
// use the default path if applicable
if ((!svn_root) || (svn_root[0] == '\0'))
svn_root = "/";
// make sure the path is valid
if (!is_svn_root_valid(svn_root))
return -1;
// create a buffer for generating the --root argument
char root_param[strlen("--root=") + strlen(svn_root) + 1];
// generate the --root argument using svn_root
{
int tmp = snprintf(root_param, sizeof(root_param), "--root=%s",
svn_root);
// make sure that we chose the correct size for the buffer
assert(tmp == (sizeof(root_param) - 1));
}
/////////////////
// tunnel user //
/////////////////
// make sure the tunnel user is valid
if (!is_tunnel_user_valid(tunnel_user))
return -1;
// create a buffer for generating the --tunnel-user argument
char tunnel_user_param[strlen("--tunnel-user=")
+ strlen(tunnel_user) + 1];
// generate the --tunnel-user argument using the user's login name
{
int tmp = snprintf(tunnel_user_param, sizeof(tunnel_user_param),
"--tunnel-user=%s", tunnel_user);
// make sure that we chose the correct size for the buffer
assert(tmp == (sizeof(tunnel_user_param) - 1));
}
////////////////////////
// generate arguments //
////////////////////////
// figure out how many arguments we are going to pass to svnserve
size_t argc;
if (!argv)
{
// arg 0 is the path to the svnserve binary, args 1-3 are the
// --root, --tunnel, and --tunnel-user parameters.
argc = 4;
}
else
{
// count the number of arguments in the given argv vector
argc = 0;
while (argv[argc])
++argc;
// we'll tack on the --root, --tunnel, and --tunnel-user
// parameters after the parameters given in argv.
argc += 3;
}
// create an array to hold the arguments (the "+1" is for the null
// terminator)
const char* svnserve_argv[argc + 1];
// fill in the arguments
{
size_t i = 0;
if (!argv)
svnserve_argv[i++] = svnserve_path;
else
for (; argv[i]; ++i)
svnserve_argv[i] = argv[i];
svnserve_argv[i++] = root_param;
svnserve_argv[i++] = "--tunnel";
svnserve_argv[i++] = tunnel_user_param;
svnserve_argv[i] = NULL;
assert(i == argc);
}
//////////////////////////
// generate environment //
//////////////////////////
// make sure we have a valid envp
const char* const svnserve_envp_default[] = {NULL};
const char* const* svnserve_envp = svnserve_envp_default;
if (envp)
svnserve_envp = envp;
///////////////
// call exec //
///////////////
/*
// debug output
fprintf(stderr, "svnserve = %s\n", svnserve_path);
for (int i = 0; svnserve_argv[i]; ++i)
{
fprintf(stderr, "Arg[%i] = %s\n", i, svnserve_argv[i]);
}
for (int i = 0; svnserve_envp[i]; ++i)
{
fprintf(stderr, "Env[%i] = %s\n", i, svnserve_envp[i]);
}
*/
// call execve(). If execve() fails, we return -1 so that the
// caller knows to look at errno.
//
// Unfortunately, argv and envp have to be cast to strip off the
// first const -- see the discussion in the 'rationale' section of
// the execve() specification in POSIX.1-2004. This cast should
// be safe; the specification says, "The argv[] and envp[] arrays
// of pointers and the strings to which those arrays point shall
// not be modified by a call to one of the exec functions, except
// as a consequence of replacing the process image."
//
// Note that exec does not modify the real or effective user ID
// unless svnserve_path refers to an executable with the SUID bit
// set. This means that svnserve's privileges will be the union
// of the real user's privileges and the effective user's
// privileges. It is not possible to limit svnserve's privileges
// to just those of the effective user by calling
// setuid(geteuid()) before exec, because setuid() does not change
// the real UID without superuser privileges. The only way to
// shed the real user's privileges is to give this wrapper
// superuser privileges (set the wrapper's owner to root and
// enable the SUID bit) and call setuid() with the target user's
// UID before calling exec. I don't think this extra effort would
// provide any substantial gain, and it could open the possibility
// of a malicious user gaining superuser privileges.
//
return execve(svnserve_path, (char* const*)svnserve_argv,
(char* const*)svnserve_envp);
}
/**
* @defgroup libsvnstswprvexec exec_svnserve
* @ingroup libsvnstswprv
*
* Helper functions for the implementation of svnstsw_exec_svnserve().
*
* @{
*/
/**
* @brief Makes sure @a svnserve_path is an absolute path and refers
* to an existing regular file.
*
* @param svnserve_path Null-terminated string containing the path to
* check.
*
* @return Returns 1 if there is no error, the path is absolute, and
* the path refers to an existing regular file. Otherwise, sets @p
* errno and returns 0.
*/
_Bool
is_svnserve_path_valid(const char* svnserve_path)
{
// make sure we were given an absolute path
if ((!svnserve_path) || (svnserve_path[0] != '/'))
{
errno = EINVAL;
return 0;
}
// fetch the file details. Note that stat() follows symlinks
struct stat st;
if (stat(svnserve_path, &st) == -1)
return 0;
// is it not a normal file?
if (!S_ISREG(st.st_mode))
{
errno = EINVAL;
return 0;
}
return 1;
}
/**
* @brief Makes sure @a svn_root is an absolute path and refers
* to an existing directory.
*
* @param svn_root Null-terminated string containing the path to
* check.
*
* @return Returns 1 if there is no error, the path is absolute, and
* the path refers to an existing directory. Otherwise, sets @p errno
* and returns 0.
*/
_Bool
is_svn_root_valid(const char* svn_root)
{
// make sure the path is absolute
if ((!svn_root) || (svn_root[0] != '/'))
{
errno = EINVAL;
return 0;
}
// fetch the directory's details. Note that stat() follows
// symlinks
struct stat st;
if (stat(svn_root, &st) == -1)
return 0;
// make sure it is a directory
if (!S_ISDIR(st.st_mode))
{
errno = EINVAL;
return 0;
}
return 1;
}
/**
* @brief Tests @a tunnel_user to make sure it is a valid @p svnserve
* tunnel user name.
*
* Currently just tests whether @a tunnel_user is neither the null
* pointer nor the empty string.
*
* @param tunnel_user Null-terminated string containing the user name
* to check.
*
* @return Returns 1 if there is no error and @a tunnel_user is
* neither the null pointer nor the empty string. Otherwise, sets @p
* errno and returns 0.
* set.
*/
_Bool
is_tunnel_user_valid(const char* tunnel_user)
{
if ((!tunnel_user) || (tunnel_user[0] == '\0'))
{
errno = EINVAL;
return 0;
}
return 1;
}
/**
* @}
*/