gitosis/gitosis/serve.py
Tommi Virtanen 2e068602d0 Don't git init when it's not needed.
Confusion between pathnames with extensions and without made
gitosis-serve confused when it needs to create a repository
and when not.
2007-09-03 19:24:04 -07:00

155 lines
4 KiB
Python

"""
Enforce git-shell to only serve allowed by access control policy.
directory. The client should refer to them without any extra directory
prefix. Repository names are forced to match ALLOW_RE.
"""
import logging
import sys, os, re
from gitosis import access
from gitosis import repository
from gitosis import app
ALLOW_RE = re.compile("^'(?P<path>[a-zA-Z0-9][a-zA-Z0-9@._-]*(/[a-zA-Z0-9][a-zA-Z0-9@._-]*)*)'$")
COMMANDS_READONLY = [
'git-upload-pack',
]
COMMANDS_WRITE = [
'git-receive-pack',
]
class ServingError(Exception):
"""Serving error"""
def __str__(self):
return '%s' % self.__doc__
class CommandMayNotContainNewlineError(ServingError):
"""Command may not contain newline"""
class UnknownCommandError(ServingError):
"""Unknown command denied"""
class UnsafeArgumentsError(ServingError):
"""Arguments to command look dangerous"""
class AccessDenied(ServingError):
"""Access denied to repository"""
class WriteAccessDenied(AccessDenied):
"""Repository write access denied"""
class ReadAccessDenied(AccessDenied):
"""Repository read access denied"""
def serve(
cfg,
user,
command,
):
if '\n' in command:
raise CommandMayNotContainNewlineError()
try:
verb, args = command.split(None, 1)
except ValueError:
# all known commands take one argument; improve if/when needed
raise UnknownCommandError()
if (verb not in COMMANDS_WRITE
and verb not in COMMANDS_READONLY):
raise UnknownCommandError()
match = ALLOW_RE.match(args)
if match is None:
raise UnsafeArgumentsError()
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:
raise ReadAccessDenied()
if verb in COMMANDS_WRITE:
# didn't have write access and tried to write
raise WriteAccessDenied()
assert not newpath.endswith('.git'), \
'git extension should have been stripped: %r' % newpath
repopath = '%s.git' % newpath
if (not os.path.exists(repopath)
and verb in COMMANDS_WRITE):
# it doesn't exist on the filesystem, but the configuration
# refers to it, we're serving a write request, and the user is
# authorized to do that: create the repository on the fly
repository.init(path=repopath)
# put the verb back together with the new path
newcmd = "%(verb)s '%(newpath)s'" % dict(
verb=verb,
newpath=newpath,
)
return newcmd
class Main(app.App):
def create_parser(self):
parser = super(Main, self).create_parser()
parser.set_usage('%prog [OPTS] USER')
parser.set_description(
'Allow restricted git operations under DIR')
return parser
def handle_args(self, parser, cfg, options, args):
try:
(user,) = args
except ValueError:
parser.error('Missing argument USER.')
log = logging.getLogger('gitosis.serve.main')
os.umask(0022)
cmd = os.environ.get('SSH_ORIGINAL_COMMAND', None)
if cmd is None:
log.error('Need SSH_ORIGINAL_COMMAND in environment.')
sys.exit(1)
log.debug('Got command %(cmd)r' % dict(
cmd=cmd,
))
os.chdir(os.path.expanduser('~'))
try:
newcmd = serve(
cfg=cfg,
user=user,
command=cmd,
)
except ServingError, e:
log.error('%s', e)
sys.exit(1)
log.debug('Serving %s', newcmd)
os.execvpe('git-shell', ['git-shell', '-c', newcmd], {})
log.error('Cannot execute git-shell.')
sys.exit(1)