diff --git a/gitosis/serve.py b/gitosis/serve.py new file mode 100644 index 0000000..cc189ad --- /dev/null +++ b/gitosis/serve.py @@ -0,0 +1,106 @@ +#!/usr/bin/python + +""" +Enforce git-shell to only serve repositories in the given +directory. The client should refer to them without any directory +prefix. Repository names are forced to match ALLOW. +""" + +import logging; logging.basicConfig(level=logging.DEBUG) + +import sys, os, optparse, re +from ConfigParser import RawConfigParser + +from gitosis import access + +def die(msg): + print >>sys.stderr, '%s: %s' % (sys.argv[0], msg) + sys.exit(1) + +def getParser(): + parser = optparse.OptionParser( + usage='%prog [--config=FILE] USER', + description='Allow restricted git operations under DIR', + ) + parser.set_defaults( + config=os.path.expanduser('~/.gitosis.conf'), + ) + parser.add_option('--config', + metavar='FILE', + help='read config from FILE', + ) + return parser + +ALLOW_RE = re.compile("^(?Pgit-(?:receive|upload)-pack) '(?P[a-zA-Z][a-zA-Z0-9@._-]*(/[a-zA-Z][a-zA-Z0-9@._-]*)*)'$") + +COMMANDS_READONLY = [ + 'git-upload-pack', + ] + +COMMANDS_WRITE = [ + 'git-receive-pack', + ] + +def main(): + os.umask(0022) + + parser = getParser() + (options, args) = parser.parse_args() + try: + (user,) = args + except ValueError: + parser.error('Missing argument USER.') + + cmd = os.environ.get('SSH_ORIGINAL_COMMAND', None) + if cmd is None: + die("Need SSH_ORIGINAL_COMMAND in environment.") + + if '\n' in cmd: + die("Command may not contain newlines.") + + match = ALLOW_RE.match(cmd) + if match is None: + die("Command to run looks dangerous") + + cfg = RawConfigParser() + cfg.read(options.config) + + os.chdir(os.path.expanduser('~')) + + command = match.group('command') + if (command not in COMMANDS_WRITE + and command not in COMMANDS_READONLY): + die("Unknown command denied.") + + path = match.group('path') + + # write access is always sufficient + newpath = access.haveAccess( + config=cfg, + user=user, + mode='writable', + path=path) + + if newpath is None: + # didn't have write access + + newpath = access.haveAccess( + config=cfg, + user=user, + mode='readonly', + path=path) + + if newpath is None: + die("Access denied.") + + if command in COMMANDS_WRITE: + # didn't have write access and tried to write + die("Write access denied.") + + # put the command back together with the new path + newcmd = "%(command)s '%(newpath)s'" % dict( + command=command, + newpath=newpath, + ) + os.execve('/usr/bin/git-shell', ['git-shell', '-c', newcmd], {}) + die("Cannot execute git-shell.")