/* * 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; } /** * @} */