X-Git-Url: https://oss.titaniummirror.com/gitweb?a=blobdiff_plain;f=repo_shell.c;h=07e5cf7e4b24e0d5a4275fc1b542def979af7052;hb=HEAD;hp=6ec08b2f53372ca20251842fc11e5fecce71b7e5;hpb=2657b21c48ee21f9f1667be242d7e85be8161cf9;p=repo_shell.git diff --git a/repo_shell.c b/repo_shell.c index 6ec08b2..07e5cf7 100644 --- a/repo_shell.c +++ b/repo_shell.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -10,32 +11,42 @@ #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 test access\n" + " -d|--detail 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 ", 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 ", 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);