blob: 1210ed3ca61a64d0ad55262144b72024a134a0c8 [file] [log] [blame]
/*
* 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 <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/stat.h>
#include <apr_general.h> // for apr_app_initialize()
#include <apr_pools.h> // for apr_pool_create_ex()
#include <apr_getopt.h> // for apr_getopt_long() and friends
#include <svnstsw/svnstsw.h>
#if ! (defined (SVNSERVE) && defined (SVNSERVE_DEFAULT_ROOT) && \
defined (ALLOW_SVNSERVE_ROOT_OVERRIDE) && defined(SVNSTSW_UMASK))
# error required macro not #defined
#endif
/**
* @defgroup svnstsw svnstsw executable
*
* @brief Simple executable that uses the @ref libsvnstsw to start @p
* svnserve in tunnel mode.
*
* The path to @p svnserve is set at compile-time and cannot be
* changed. The @p -R and @p --read-only command-line arguments are
* honored. The @p -r and @p --root command-line arguments can be
* optionally honored via a compile-time setting. All other arguments
* are ignored (if they apply when @p svnserve is in tunnel mode) or
* considered invalid (if they do not apply when @p svnserve is in
* tunnel mode). See the manual page (svnstsw(8)) for details.
*
* @{
*/
///////////////////////////
// COMPILE-TIME SETTINGS //
///////////////////////////
/**
* @brief Path to the @p svnserve executable.
*
* The value of this variable is set by @p configure at build time.
*/
static const char* const Svnserve = SVNSERVE;
/**
* @brief Default Subversion repository virtual root path.
*
* The value of this variable is set by @p configure at build time.
*/
static const char* const Svnserve_default_root = SVNSERVE_DEFAULT_ROOT;
/**
* @brief Whether the @p --root or @p -r command-line arguments are
* honored.
*
* If true, the repository virtual root passed to
* svnstsw_exec_svnserve() can be changed via the @p --root or @p -r
* command-line arguments. If false, the @p --root and @p -r
* command-line arguments are ignored and #Svnserve_default_root is
* always passed to svnstsw_exec_svnserve().
*
* The value of this variable is set by @p configure at build time.
*/
static const _Bool Allow_svnserve_root_override = ALLOW_SVNSERVE_ROOT_OVERRIDE;
/**
* @brief File mode creation mask to apply to new files and
* directories.
*
* The value of this variable is set by @p configure at build time.
*
* @sa umask(), <sys/stat.h>
*/
static const mode_t Svnstsw_umask = SVNSTSW_UMASK();
///////////////////////////////////
// COMMAND-LINE ARGUMENT PARSING //
///////////////////////////////////
/**
* @brief Structure holding the values passed in via the command-line
* arguments.
*/
typedef struct {
/**
* @brief whether @p svnserve should be passed the @p --read-only
* command-line argument
*/
_Bool read_only;
/**
* @brief the path that should be passed to @p svnserve via the @p
* --root command-line argument
*/
const char* root;
} svnstsw_args_t;
/**
* @brief Option code for the @p --tunnel-user command-line argument.
*/
#define SVNSERVE_OPT_TUNNEL_USER 259
/**
* @brief Option code for the @p --version command-line argument.
*/
#define SVNSERVE_OPT_VERSION 260
/**
* @brief Option codes and descriptions for svnstsw's valid
* command-line arguments.
*
* The entire list must be terminated with an entry of nulls.
*
* APR requires that options without abbreviations have codes greater
* than 255.
*/
static const apr_getopt_option_t options[] =
{
{"help", 'h', 0, "display this help"},
{"version", SVNSERVE_OPT_VERSION, 0, "show program version "
"information"},
{"read-only", 'R', 0, "force read only, overriding repository "
"config file"},
{"root", 'r', 1, (ALLOW_SVNSERVE_ROOT_OVERRIDE)
? "root of directory to serve" : "ignored"},
{"tunnel", 't', 0, "ignored"},
{"tunnel-user", SVNSERVE_OPT_TUNNEL_USER, 1, "ignored"},
{0, 0, 0, 0}
};
///////////////////////////
// FUNCTION DECLARATIONS //
///////////////////////////
int main(int argc, const char* const* argv, const char* const* env);
static void parse_args(svnstsw_args_t* args_p, int* argc_p,
const char* const** argv_p, const char* const** env_p);
static void print_usage(const char* argv0, FILE* out);
static void print_executable_and_path(FILE* out);
static _Bool is_equivalent_file(const char* file1, const char* file2);
static apr_pool_t* initialize_apr(int* argc_p, const char* const** argv_p,
const char* const** env_p);
static int abort_on_pool_failure(int retcode);
//////////////////////////
// FUNCTION DEFINITIONS //
//////////////////////////
/**
* @brief Executable entry.
*
* @param argc length of @a argv (not including the null terminator)
*
* @param argv null-terminated array of null-terminated strings
* containing the command-line arguments to this executable (where
* @a argv[0] is the path to the executable)
*
* @param env null-terminated array of null-terminated strings
* containing the environment.
*
* @return Does not return on success. Returns a non-zero value on
* error.
*/
int
main(int argc, const char* const* argv, const char* const* env)
{
// process command-line arguments
svnstsw_args_t args;
parse_args(&args, &argc, &argv, &env);
// are we running with elevated permissions?
if ((getuid() != geteuid()) || (getgid() != getegid()))
{
// Yes, so run some filesystem permissions tests. The purpose
// of these tests is to encourage non-malicious users to
// report security problems to the repository administrator
// before a malicious user comes along.
// can Svnserve be modified or swapped with another
// executable?
errno = 0;
if (svnstsw_fso_is_changeable(Svnserve))
{
if (errno)
perror("Error: svnstsw svnstsw_fso_is_changeable(Svnserve)");
else
fprintf(stderr, "Error: svnstsw: Unsafe execution "
"detected. This program is running with "
"elevated privileges but the user can replace "
"the target executable (%s). Please check "
"filesystem permissions.\n", Svnserve);
return 1;
}
// does the user have the ability to modify the repository?
errno = 0;
if (svnstsw_fso_is_changeable(args.root))
{
if (errno)
perror("Error: svnstsw svnstsw_fso_is_changeable(args.root)");
else
fprintf(stderr, "Error: svnstsw: Unsafe repository "
"filesystem permissions detected. Please "
"check the filesystem permissions on '%s' and "
"its parent directories.\n", args.root);
return 1;
}
}
// quick but incomplete check to see if we're going to run in an
// infinite loop
if (is_equivalent_file(argv[0], Svnserve))
{
fprintf(stderr, "Error: svnstsw: Infinite loop detected. "
"The file to execute (%s) matches this executable (%s).\n",
Svnserve, argv[0]);
return 1;
}
// create a buffer for holding the user's login name
char tunnel_user[svnstsw_get_tunnel_user_name(NULL, 0) + 1];
// fetch the user's login name
{
int tunnel_user_size = svnstsw_get_tunnel_user_name(
tunnel_user, sizeof(tunnel_user));
// handle any errors
if (tunnel_user_size < 0)
{
perror("Error: svnstsw svnstsw_get_tunnel_user_name()");
exit(1);
}
// make sure the buffer was the right size
assert(tunnel_user_size == (sizeof(tunnel_user) - 1));
}
// set the file mode creation mask. By default, configure will
// set Svnstsw_umask such that the read, write, and execute bits
// for other are turned off and the read, write, and execute bits
// for the user and group are allowed. This is to preserve
// confidentiality of information stored in the repository and to
// make sure that SGID installations work properly.
umask(Svnstsw_umask);
// argument vector to use in case the user passed in -R or
// --read-only
const char* const read_only_argv[] = {Svnserve, "--read-only", NULL};
// run svnserve. Note that we're passing an empty environment
if (svnstsw_exec_svnserve(Svnserve, args.root, tunnel_user,
(args.read_only) ? read_only_argv : NULL,
NULL) == -1)
{
// exec failed -- print the error message and return
perror("Error: svnstsw svnstsw_exec_svnserve()");
return 1;
}
// should not be possible to get here
abort();
}
/**
* @brief Processes the command-line arguments passed to main()
*
* @param args_p Pointer to a structure that will hold the results of
* processing the command-line values.
*
* @param argc_p Pointer to the @a argc parameter of main(). The
* value of @a argc might be modified (via a call to APR's
* apr_app_initialize()).
*
* @param argv_p Pointer to the @a argv parameter of main(). The
* value of @a argv might be modified (via a call to APR's
* apr_app_initialize()).
*
* @param env_p Pointer to the @a env parameter of main(). The value
* of @a env might be modified (via a call to APR's
* apr_app_initialize()).
*/
void
parse_args(svnstsw_args_t* args_p, int* argc_p, const char* const** argv_p,
const char* const** env_p)
{
apr_pool_t* pool = initialize_apr(argc_p, argv_p, env_p);
// for convenience so I don't have to constantly dereference the
// variables
int argc = *argc_p;
const char* const* argv = *argv_p;
apr_status_t apr_status = 0;
// initialize getopt
apr_getopt_t *os;
apr_status = apr_getopt_init(&os, pool, argc, argv);
if (apr_status)
{
char buf[1024];
apr_strerror(apr_status, buf, sizeof(buf) - 1);
fprintf(stderr, "%s: Error: cannot initialize getopt: %s\n",
argv[0], buf);
exit(1);
}
// default argument values
args_p->read_only = 0;
args_p->root = Svnserve_default_root;
// parse the arguments
while (1)
{
// read the next argument
int opt;
const char* arg;
apr_status = apr_getopt_long(os, options, &opt, &arg);
// are we done processing arguments?
if (APR_STATUS_IS_EOF(apr_status))
break;
// was there an error with this argument?
if (apr_status != APR_SUCCESS)
{
print_usage(argv[0], stderr);
exit(1);
}
// process the argument
switch (opt)
{
case 'h':
// help
print_usage(argv[0], stdout);
exit(0);
case SVNSERVE_OPT_VERSION:
// version
printf(PACKAGE_NAME " version " PACKAGE_VERSION "\n");
printf("Copyright (c) 2008 BBN Technologies Corp. All "
"rights reserved.\n\n");
print_executable_and_path(stdout);
exit(0);
case 'R':
// read-only
args_p->read_only = 1;
break;
case 'r':
// root
if (Allow_svnserve_root_override)
args_p->root = arg;
break;
case 't':
case SVNSERVE_OPT_TUNNEL_USER:
// ignored
break;
default:
// should not be possible to get here
abort();
}
}
// make sure all arguments were processed
if (os->ind != argc)
{
print_usage(argv[0], stderr);
exit(1);
}
}
/**
* @brief Prints a usage summary to @a out.
*
* @param argv0 the name of this executable
*
* @param out the output stream to write the message to (e.g., @p
* stdout or @p stderr)
*/
void
print_usage(const char* argv0, FILE* out)
{
fprintf(out, "usage: %s [options]\n\n", argv0);
fprintf(out, "Valid options:\n");
// loop through each argument and print its usage
for (apr_size_t i = 0; options[i].name || options[i].optch; ++i)
{
const apr_getopt_option_t* o = options + i;
const _Bool has_short = ((o->optch <= 255) && (o->optch > 0)) ? 1 : 0;
assert(has_short || o->name);
// number of columns before the description
int noutput = 26;
noutput -= fprintf(out, " ");
// print the short option
if (has_short)
noutput -= fprintf(out, "-%c%s",
o->optch, (o->has_arg) ? " ARG" : "");
// print the long option
if (o->name)
noutput -= fprintf(out, "%s--%s%s",
(has_short) ? ", " : "",
o->name, (o->has_arg) ? "=ARG" : "");
// print the description
fprintf(out, " %*s: %s\n", (noutput > 0) ? noutput : 0, "",
o->description);
}
fprintf(out, "\n");
print_executable_and_path(out);
}
/**
* @brief Prints the path to the @p svnserve executable and the
* (default) root path to @a out.
*
* @param out the output stream to write the message to (e.g., @p
* stdout or @p stderr)
*/
void
print_executable_and_path(FILE* out)
{
// first, fetch the user's login name so that we can show the user
// what we are going to pass with the --tunnel-user argument.
// create a buffer for holding the user's login name
char tunnel_user_buf[svnstsw_get_tunnel_user_name(NULL, 0) + 1];
const char* tunnel_user = tunnel_user_buf;
// fetch the user's login name
{
int tunnel_user_size = svnstsw_get_tunnel_user_name(
tunnel_user_buf, sizeof(tunnel_user_buf));
// handle any errors
if (tunnel_user_size < 0)
tunnel_user = "<user>";
// make sure the buffer was the right size
assert(tunnel_user_size == (sizeof(tunnel_user_buf) - 1));
}
// let the user know which executable is run and what parameters
// are passed
fprintf(out, "Executable to run: %s [--read-only] --root=%s --tunnel "
"--tunnel-user=%s\n", Svnserve,
(Allow_svnserve_root_override) ? "<dir>"
: Svnserve_default_root,
tunnel_user);
// if the user can specify the root, let the user know what the
// default is
if (Allow_svnserve_root_override)
fprintf(out, "Default root directory to serve: %s\n",
Svnserve_default_root);
}
/**
* @brief Tests if two file names refer to the same file (like the @p
* -ef operator from @p test).
*
* @param file1 the name of the first file to check
*
* @param file2 the name of the second file to check
*
* @return Returns 1 if the two files identified by @a file1 and @a
* file2 share device and inode numbers. Returns 0 if there is an
* error or if the two file names refer to different files. On error,
* @p errno is set. Error values and conditions are described in the
* specification for stat().
*/
_Bool
is_equivalent_file(const char* file1, const char* file2)
{
struct stat file1_stat;
struct stat file2_stat;
return ((stat(file1, &file1_stat) == 0)
&& (stat(file2, &file2_stat) == 0)
&& (file1_stat.st_dev == file2_stat.st_dev)
&& (file1_stat.st_ino == file2_stat.st_ino));
}
/**
* @brief Initialize the Apache Portable Runtime (APR) and create a
* root pool.
*
* @param argc_p Pointer to the @a argc parameter of main()
*
* @param argv_p Pointer to the @a argv parameter of main()
*
* @param env_p Pointer to the @a env parameter of main()
*
* @return Pointer to the newly-created root APR pool
*/
apr_pool_t*
initialize_apr(int* argc_p, const char* const** argv_p,
const char* const** env_p)
{
apr_status_t apr_status = 0;
// initialize APR.
apr_status = apr_app_initialize(argc_p, argv_p, env_p);
if (apr_status)
{
char buf[1024];
apr_strerror(apr_status, buf, sizeof(buf) - 1);
fprintf(stderr, "%s: Error: cannot initialize APR: %s\n",
(*argv_p)[0], buf);
exit(1);
}
// make sure apr_terminate is called when we exit
if (atexit(apr_terminate) != 0)
{
fprintf(stderr, "%s: Error: atexit(apr_terminate) failed\n",
(*argv_p)[0]);
exit(1);
}
// create an APR pool
apr_pool_t* pool = NULL;
apr_status = apr_pool_create_ex(&pool, NULL, abort_on_pool_failure, NULL);
if (apr_status)
{
char buf[1024];
apr_strerror(apr_status, buf, sizeof(buf) - 1);
fprintf(stderr, "%s: Error: cannot create APR pool: %s\n",
(*argv_p)[0], buf);
exit(1);
}
return pool;
}
/**
* @brief APR pool allocation failure handler which simply aborts.
*
* @param retcode Defined by the Apache Portable Runtime library
* (apr_abortfunc_t).
*
* @return Defined by the Apache Portable Runtime library
* (apr_abortfunc_t).
*/
int
abort_on_pool_failure(int retcode)
{
// Don't translate this string! It requires memory allocation to
// do so! And we don't have any of it...
fprintf(stderr, "Out of memory - terminating application.\n");
abort();
return -1; // prevent compiler warnings
}
/**
* @}
*/