]> oss.titaniummirror.com Git - repo_shell.git/blobdiff - repo_shell.c
gitcreate: add option to set description
[repo_shell.git] / repo_shell.c
index 6ec08b2f53372ca20251842fc11e5fecce71b7e5..07e5cf7e4b24e0d5a4275fc1b542def979af7052 100644 (file)
@@ -1,3 +1,4 @@
+#include <stdbool.h>
 #include <stdio.h>
 #include <errno.h>
 #include <stdlib.h>
 #include "ini.h"
 #include "utility.h"
 #include "version.h"
+#include "git_acl.h"
+#include "stringutils.h"
 
-#define CFG_FILE "/etc/repo_shell.cfg"
-#define GIT_ACL_FILE "git_acl.cfg"
+#define CFG_FILE "/etc/repo_shell.conf"
 #define SHELL "/bin/bash"
+#define GIT_ACL_FILE ".gitacls"
+
+enum { REPO_UMASK = 027 };
 
 typedef struct {
+  char *user;
   char *svn_root;
   char *git_root;
   char *owner;
+  char *git_acl_file;
+  bool allow_interactive;
 } cfg_t;
 
 static cfg_t cfg;
 
-static uid_t user_uid(char *user)
+/* This is the function for which setuid root is needed for repo_shell */
+static void change_user(char *user)
 {
   struct passwd *pw = getpwnam(user);
 
   if (!pw)
     die("invalid user %s", user);
-  return pw->pw_uid;
+  setgid(pw->pw_gid);
+  setuid(pw->pw_uid);
 }
 
-static void change_user(char *user)
+/* Set the user and group permissions back to the requesting user */
+static void reset_user()
 {
-  /* This is the function for which setuid is required, as root */
-  setuid(user_uid(user));
+  setgid(getgid());
+  setuid(getuid());
 }
 
 static char *dequote(char *arg)
@@ -53,10 +64,10 @@ static char *dequote(char *arg)
   return narg;
 }
 
-static char *add_prefix(char *prefix, char* arg)
+static char *add_prefix(const char *prefix, const char* arg)
 {
-  char *narg = arg;
-        int i;
+  char *narg;
+  int i;
 
   if (arg && prefix && (i = strlen(prefix))) {
     narg = xmalloc(sizeof(char *) * (i + strlen(arg) + 2));
@@ -67,49 +78,18 @@ static char *add_prefix(char *prefix, char* arg)
   return narg;
 }
 
-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_acl(const char *user, const char *repo)
-{
-  /* TODO: Read GIT_ACL_FILE from cfg.owner's home directory.  Look for
-   * the access level afforded user for repo.  A return of 0 means no
-   * access, a return of 1 means read only, and a return of 2 means
-   * read/write.
-   */
-#if 0
-  struct passwd *pw;
-  char *file;
-  int len = strlen(cfg.owner) + strlen(GIT_ACL_FILE) + 8;
-
-  pw = getpwnam(cfg.owner);
-  if (!pw)
-    die("owner %s has no passwd entry?", cfg.owner);
-  len = strlen(pw->pw_dir) + strlen(GIT_ACL_FILE) + 2;
-  file = xmalloc(sizeof(char) * len);
-  sprintf(file, "%s/%s", pw->pw_dir, GIT_ACL_FILE);
-  fprintf(stderr, "[someday check %s for git ACLs]\n", file);
-  free(file);
-#endif
-  return 2; /* assume read/write for now */
-}
-
-static int git_check_access(const char *cmd, const char *repo, const char *user)
+/* Return true if the user's permissions >= those required */
+static bool git_check_access(const char *cmd, const char *repo,
+    const char *user)
 {
-  /* What access is required per the incoming command?
-   * 0=none, 1=read-only, 2=read-write
+  /* What access is required per the incoming command?  Uploading commands
+   * require PERMS_READ_WRITE, while all other commands are assumed to need only
+   * PERMS_READ.
    */
-  int rw = (!strcmp(cmd, "git-upload-pack") ||
-      !strcmp(cmd, "git-upload-archive")) ? 2 : 1;
-
-  /* Return true (1) if the user permissions >= those required */
-  return (git_acl(user, repo) >= rw) ? 1 : 0;
+  perms_t need = !strncmp(cmd, "git-upload", 10) ? PERMS_READ :
+      PERMS_READ_WRITE;
+  perms_t have = git_acl(user, repo, cfg.git_acl_file);
+  return have >= need;
 }
 
 static int do_git_cmd(const char *cmd, char *arg, char *user)
@@ -124,8 +104,9 @@ static int do_git_cmd(const char *cmd, char *arg, char *user)
     die("bad command");
 
   change_user(cfg.owner);
+  umask(REPO_UMASK);
   if (!git_check_access(cmd, arg, user))
-    die("permission denied");
+    die("insufficient ACL permissions");
 
   nargv[0] = cmd;
   nargv[1] = add_prefix(cfg.git_root, arg);
@@ -133,6 +114,7 @@ static int do_git_cmd(const char *cmd, char *arg, char *user)
 
   ret = execvp(nargv[0], (char *const *) nargv);
   /* Code unreached if execv successful */
+  free((char*)nargv[1]);
   free(narg);
   return ret;
 }
@@ -145,6 +127,7 @@ static int do_svnserve_cmd(const char *cmd, char *arg, char *user)
   int ret;
 
   change_user(cfg.owner);
+  umask(REPO_UMASK);
   return execvp(svnserve_argv[0], (char *const *) svnserve_argv);
 }
 
@@ -173,13 +156,15 @@ static int ini_handler(void* user, const char* section, const char* name,
 {
   cfg_t* pconfig = (cfg_t*)user;
 
-  #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
-  if (MATCH("core", "svn_root"))
+  if (!strcmp(name, "svn_root"))
     pconfig->svn_root = xstrdup(value);
-  else if (MATCH("core", "git_root"))
+  else if (!strcmp(name, "git_root")) {
     pconfig->git_root = xstrdup(value);
-  else if (MATCH("core", "owner"))
+    pconfig->git_acl_file = add_prefix(value, GIT_ACL_FILE);
+  } else if (!strcmp(name, "owner"))
     pconfig->owner = xstrdup(value);
+  else if (!strcmp(name, "allow_interactive"))
+    pconfig->allow_interactive = str_has_word(value, pconfig->user);
   else
     return 0;  /* unknown section/name, error */
   return 1;
@@ -192,6 +177,7 @@ int main(int argc, char **argv)
   struct commands *cmd;
   int devnull_fd;
   int count;
+  struct passwd *pw;
 
   /*
    * Always open file descriptors 0/1/2 to avoid clobbering files
@@ -205,52 +191,94 @@ int main(int argc, char **argv)
     die("opening /dev/null failed");
   close (devnull_fd);
 
-  if (argc == 2 && (!strcmp(argv[1], "-v") ||
-      !strcmp(argv[1], "--version"))) {
+  if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
+    fprintf(stderr, "%s is a replacement login shell.\n"
+        "  May be ran from the command line with one of these options:\n"
+        "    -h|--help                 this text\n"
+        "    -v|--version              program version string\n"
+        "    -t|--test <user> <repo>   test access\n"
+        "    -d|--detail <user> <repo> test access, outputting more detail\n"
+        , argv[0]);
+    return 0;
+  }
+
+  if (argc == 2 && (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version"))) {
     fprintf(stderr, "%s\n", version);
     return 0;
   }
 
-  if (argc == 1 && check_ssh_interactive(getuid())) {
-    setuid(getuid());
+  pw = getpwuid(getuid());
+  cfg.user = xstrdup(pw->pw_name);
+  if (ini_parse(CFG_FILE, ini_handler, &cfg) < 0)
+    die("cannot read config file %s", CFG_FILE);
+
+  if (argc == 1) {
+    if (!cfg.allow_interactive) {
+      fprintf(stderr, "\n");
+      die("only repository access is allowed");
+    }
+    reset_user();
     argv[0] = SHELL;
     execvp(argv[0], (char *const *) argv);
+    return 1;
   }
 
-  if (ini_parse(CFG_FILE, ini_handler, &cfg) < 0)
-    die("cannot read config file %s", CFG_FILE);
+  if ((!strcmp(argv[1], "-d") || !strcmp(argv[1], "--detail"))) {
+    perms_t p;
+
+    if (argc !=4)
+      die("usage: %s -d|--detail <user> <repo>", argv[0]);
+    p = git_acl(argv[2], argv[3], cfg.git_acl_file);
+    fprintf(stderr,
+        "user '%s' repo '%s' perms '%s'\n  via userid '%s' repoid '%s'\n",
+        argv[2], argv[3], git_acl_perms_as_str(p), git_acl_last_userid(),
+        git_acl_last_repoid());
+    return 0;
+  }
+
+  if ((!strcmp(argv[1], "-t") || !strcmp(argv[1], "--test"))) {
+    perms_t p;
 
-  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':
+    if (argc !=4)
+      die("usage: %s -t|--test <user> <repo>", argv[0]);
+    p = git_acl(argv[2], argv[3], cfg.git_acl_file);
+    printf("%s\n", git_acl_perms_as_str(p));
+    return 0;
+  }
+
+  if (argc == 3) {
+    /* argv[0] = repo_shell, argv[1] = -c, argv[2] = cmd
+     * cmd = "svnserve -t" or "git-xxx '/path/to/repo.git'"
+     */
+    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;
+      if (strncmp(cmd->name, prog, len))
+        continue;
       arg = NULL;
-      break;
-    case ' ':
-      arg = prog + len + 1;
-      break;
-    default:
-      continue;
+      switch (prog[len]) {
+        case '\0':
+          arg = NULL;
+          break;
+        case ' ':
+          arg = prog + len + 1;
+          break;
+        default:
+          continue;
+      }
+
+      exit(cmd->exec(cmd->name, arg, cfg.user));
     }
-
-    pw = getpwuid(getuid());
-    exit(cmd->exec(cmd->name, arg, pw->pw_name));
   }
 
-  if (!check_ssh_interactive(getuid()))
+  if (!cfg.allow_interactive)
     die("only repository access is allowed");
-
-  setuid(getuid());
+  reset_user();
   cd_to_homedir();
   argv[0] = SHELL;
   execvp(argv[0], (char *const *) argv);