]> oss.titaniummirror.com Git - repo_shell.git/commitdiff
Initial commit
authorR. Steve McKown <rsmckown@gmail.com>
Sun, 23 Sep 2012 17:44:27 +0000 (11:44 -0600)
committerR. Steve McKown <rsmckown@gmail.com>
Sun, 23 Sep 2012 17:44:27 +0000 (11:44 -0600)
.gitignore [new file with mode: 0644]
repo-shell.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..b5dba6c
--- /dev/null
@@ -0,0 +1,2 @@
+repo-shell
+*.swp
diff --git a/repo-shell.c b/repo-shell.c
new file mode 100644 (file)
index 0000000..6962d35
--- /dev/null
@@ -0,0 +1,332 @@
+#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] = 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) {
+               execv(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));
+       }
+}