+++ /dev/null
-#include <stdio.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <sys/types.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <pwd.h>
-#include <string.h>
-
-/* TODO: these should come from a config file */
-static char *svn_repo_root = "/var/lib/svn/";
-static char *git_repo_root = "/var/lib/svn/";
-//static char *repo_user = "repo";
-static char *repo_user = "smckown";
-
-#define alloc_nr(x) (((x)+16)*3/2)
-
-/*
- * Realloc the buffer pointed at by variable 'x' so that it can hold
- * at least 'nr' entries; the number of entries currently allocated
- * is 'alloc', using the standard growing factor alloc_nr() macro.
- *
- * DO NOT USE any expression with side-effect for 'x', 'nr', or 'alloc'.
- */
-#define ALLOC_GROW(x, nr, alloc) \
- do { \
- if ((nr) > alloc) { \
- if (alloc_nr(alloc) < (nr)) \
- alloc = (nr); \
- else \
- alloc = alloc_nr(alloc); \
- x = xrealloc((x), alloc * sizeof(*(x))); \
- } \
- } while (0)
-
-static inline void die(const char *fmt, ...)
-{
- va_list ap;
-
- va_start(ap, fmt);
- fprintf(stderr, "error: ");
- vfprintf(stderr, fmt, ap);
- fprintf(stderr, "\n" );
- va_end(ap);
- exit(1);
-}
-
-char *xstrdup(const char *str)
-{
- char *ret = strdup(str);
- if (!ret)
- die("out of memory");
- return ret;
-}
-
-void *xmalloc(size_t size)
-{
- void *ret;
-
- ret = malloc(size);
- if (!ret && !size)
- ret = malloc(1);
- if (!ret)
- die("out of memory");
- return ret;
-}
-
-void *xrealloc(void *ptr, size_t size)
-{
- void *ret;
-
- ret = realloc(ptr, size);
- if (!ret && !size)
- ret = realloc(ptr, 1);
- if (!ret)
- die("Out of memory, realloc failed");
- return ret;
-}
-
-static uid_t user_uid(char *user)
-{
- struct passwd *pw = getpwnam(user);
-
- if (!pw)
- die("invalid user %s", user);
- return pw->pw_uid;
-}
-
-static void change_user(char *user)
-{
- /* This is the function for which setuid is required, as root */
- setuid(user_uid(user));
-}
-
-static char *dequote(char *arg)
-{
- char* narg = NULL;
-
- if (arg && *arg == '\'') {
- char* end = arg + strlen(arg) - 1;
-
- if (end != arg && *end == '\'') {
- narg = arg + 1;
- *end = '\0';
- }
- }
- return narg;
-}
-
-static char *add_prefix(char *prefix, char* arg)
-{
- int size;
-
- if (arg && prefix && strlen(prefix)) {
- char *n = xmalloc(sizeof(char *) *
- (strlen(prefix) + strlen(arg) + 2));
- strcpy(n, prefix);
- strcat(n, "/");
- strcat(n, arg);
- arg = n;
- }
- return arg;
-}
-
-static int check_ssh_interactive(uid_t uid)
-{
- /* TODO: Check the config file for the user owning uid to see if that
- * user should be able to execute any commands other than those required
- * to support repository access. Return a boolean true/false.
- */
- return 1; /* for now */
-}
-
-static int git_check_access(const char *cmd, const char *arg, const char *user)
-{
- /* TODO: Read some configuration file which maps users and access
- * to a boolean true/false value.
- *
- * The git command can support read and write.
- * git-receive-pack is ok for readers and writers
- * git-upload-pack is ok only for writers
- * git-upload-archive is ok only for writers
- */
- return 1; /* assume OK for now */
-}
-
-static int do_git_cmd(const char *cmd, char *arg, char *user)
-{
- const char *nargv[4];
- char* narg;
- int ret;
-
- if (!(arg = dequote(arg)))
- die("bad argument");
- if (strncmp(cmd, "git-", 4))
- die("bad command");
-
- change_user(repo_user);
- if (!git_check_access(cmd, arg, user))
- die("permission denied");
-
- nargv[0] = cmd;
- nargv[1] = add_prefix(git_repo_root, arg);
- nargv[2] = NULL;
-
- ret = execvp(nargv[0], (char *const *) nargv);
- /* Code unreached if execv successful */
- free(narg);
- return ret;
-}
-
-static int do_svnserve_cmd(const char *cmd, char *arg, char *user)
-{
- const char *svnserve_argv[7] = {
- cmd, "-t", "--root", svn_repo_root, "--tunnel-user", user, NULL
- };
- int ret;
-
- change_user(repo_user);
- return execvp(svnserve_argv[0], (char *const *) svnserve_argv);
-}
-
-#define SPLIT_CMDLINE_BAD_ENDING 1
-#define SPLIT_CMDLINE_UNCLOSED_QUOTE 2
-static const char *split_cmdline_errors[] = {
- "cmdline ends with \\",
- "unclosed quote"
-};
-
-int split_cmdline(char *cmdline, const char ***argv)
-{
- int src, dst, count = 0, size = 16;
- char quoted = 0;
-
- *argv = xmalloc(sizeof(char *) * size);
-
- /* split alias_string */
- (*argv)[count++] = cmdline;
- for (src = dst = 0; cmdline[src];) {
- char c = cmdline[src];
- if (!quoted && isspace(c)) {
- cmdline[dst++] = 0;
- while (cmdline[++src]
- && isspace(cmdline[src]))
- ; /* skip */
- ALLOC_GROW(*argv, count+1, size);
- (*argv)[count++] = cmdline + dst;
- } else if (!quoted && (c == '\'' || c == '"')) {
- quoted = c;
- src++;
- } else if (c == quoted) {
- quoted = 0;
- src++;
- } else {
- if (c == '\\' && quoted != '\'') {
- src++;
- c = cmdline[src];
- if (!c) {
- free(*argv);
- *argv = NULL;
- return -SPLIT_CMDLINE_BAD_ENDING;
- }
- }
- cmdline[dst++] = c;
- src++;
- }
- }
-
- cmdline[dst] = 0;
-
- if (quoted) {
- free(*argv);
- *argv = NULL;
- return -SPLIT_CMDLINE_UNCLOSED_QUOTE;
- }
-
- ALLOC_GROW(*argv, count+1, size);
- (*argv)[count] = NULL;
-
- return count;
-}
-
-const char *split_cmdline_strerror(int split_cmdline_errno) {
- return split_cmdline_errors[-split_cmdline_errno-1];
-}
-
-static void cd_to_homedir(void)
-{
- const char *home = getenv("HOME");
- if (!home)
- die("could not determine user's home directory; HOME is unset");
- if (chdir(home) == -1)
- die("could not chdir to user's home directory");
-}
-
-static struct commands {
- const char *name;
- int (*exec)(const char *cmd, char *arg, char *user);
-} cmd_list[] = {
- { "git-receive-pack", do_git_cmd },
- { "git-upload-pack", do_git_cmd },
- { "git-upload-archive", do_git_cmd },
- { "svnserve", do_svnserve_cmd },
- { NULL },
-};
-
-int main(int argc, char **argv)
-{
- char *prog;
- const char **user_argv;
- struct commands *cmd;
- int devnull_fd;
- int count;
-
- /*
- * Always open file descriptors 0/1/2 to avoid clobbering files
- * in die(). It also avoids not messing up when the pipes are
- * dup'ed onto stdin/stdout/stderr in the child processes we spawn.
- */
- devnull_fd = open("/dev/null", O_RDWR);
- while (devnull_fd >= 0 && devnull_fd <= 2)
- devnull_fd = dup(devnull_fd);
- if (devnull_fd == -1)
- die("opening /dev/null failed");
- close (devnull_fd);
-
- if (argc < 3)
- die("invalid arguments");
- fprintf(stderr, "prog |%s|\n", argv[2]);
- prog = xstrdup(argv[2]);
- if (!strncmp(prog, "git", 3) && isspace(prog[3]))
- /* Accept "git foo" as if the caller said "git-foo". */
- prog[3] = '-';
-
- for (cmd = cmd_list ; cmd->name ; cmd++) {
- int len = strlen(cmd->name);
- char *arg;
- struct passwd *pw;
- if (strncmp(cmd->name, prog, len))
- continue;
- arg = NULL;
- switch (prog[len]) {
- case '\0':
- arg = NULL;
- break;
- case ' ':
- arg = prog + len + 1;
- break;
- default:
- continue;
- }
-
- pw = getpwuid(getuid());
- exit(cmd->exec(cmd->name, arg, pw->pw_name));
- }
-
- if (!check_ssh_interactive(getuid()))
- die("only repository access is allowed");
-
- cd_to_homedir();
- count = split_cmdline(prog, &user_argv);
- if (count >= 0) {
- execvp(user_argv[0], (char *const *) user_argv);
- free(user_argv);
- die("unrecognized command '%s'", argv[2]);
- } else {
- free(prog);
- die("invalid command format '%s': %s", argv[2],
- split_cmdline_strerror(count));
- }
-}