2 Enforce git-shell to only serve allowed by access control policy.
3 directory. The client should refer to them without any extra directory
4 prefix. Repository names are forced to match ALLOW_RE.
9 import sys, os, optparse, re
10 from ConfigParser import RawConfigParser
12 from gitosis import access
13 from gitosis import repository
16 print >>sys.stderr, '%s: %s' % (sys.argv[0], msg)
20 parser = optparse.OptionParser(
21 usage='%prog [--config=FILE] USER',
22 description='Allow restricted git operations under DIR',
25 config=os.path.expanduser('~/.gitosis.conf'),
27 parser.add_option('--config',
29 help='read config from FILE',
33 ALLOW_RE = re.compile("^'(?P<path>[a-zA-Z0-9][a-zA-Z0-9@._-]*(/[a-zA-Z0-9][a-zA-Z0-9@._-]*)*)'$")
43 class ServingError(Exception):
47 return '%s' % self.__doc__
49 class CommandMayNotContainNewlineError(ServingError):
50 """Command may not contain newline"""
52 class UnknownCommandError(ServingError):
53 """Unknown command denied"""
55 class UnsafeArgumentsError(ServingError):
56 """Arguments to command look dangerous"""
58 class AccessDenied(ServingError):
59 """Access denied to repository"""
61 class WriteAccessDenied(AccessDenied):
62 """Repository write access denied"""
64 class ReadAccessDenied(AccessDenied):
65 """Repository read access denied"""
73 raise CommandMayNotContainNewlineError()
75 verb, args = command.split(None, 1)
77 if (verb not in COMMANDS_WRITE
78 and verb not in COMMANDS_READONLY):
79 raise UnknownCommandError()
81 match = ALLOW_RE.match(args)
83 raise UnsafeArgumentsError()
85 path = match.group('path')
87 # write access is always sufficient
88 newpath = access.haveAccess(
95 # didn't have write access
97 newpath = access.haveAccess(
104 raise ReadAccessDenied()
106 if verb in COMMANDS_WRITE:
107 # didn't have write access and tried to write
108 raise WriteAccessDenied()
110 if (not os.path.exists(newpath)
111 and verb in COMMANDS_WRITE):
112 # it doesn't exist on the filesystem, but the configuration
113 # refers to it, we're serving a write request, and the user is
114 # authorized to do that: create the repository on the fly
115 assert not newpath.endswith('.git'), \
116 'git extension should have been stripped: %r' % newpath
117 repopath = '%s.git' % newpath
118 repository.init(path=repopath)
120 # put the verb back together with the new path
121 newcmd = "%(verb)s '%(newpath)s'" % dict(
128 logging.basicConfig(level=logging.DEBUG)
129 log = logging.getLogger('gitosis.serve.main')
133 (options, args) = parser.parse_args()
137 parser.error('Missing argument USER.')
139 cmd = os.environ.get('SSH_ORIGINAL_COMMAND', None)
141 die("Need SSH_ORIGINAL_COMMAND in environment.")
143 log.debug('Got command %(cmd)r' % dict(
147 cfg = RawConfigParser()
149 conffile = file(options.config)
150 except (IOError, OSError), e:
151 # I trust the exception has the path.
152 die("Unable to read config file: %s." % e)
158 os.chdir(os.path.expanduser('~'))
166 except ServingError, e:
169 log.debug('Serving %s', newcmd)
170 os.execvpe('git-shell', ['git-shell', '-c', newcmd], {})
171 die("Cannot execute git-shell.")