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.
11 from gitosis import access
12 from gitosis import repository
13 from gitosis import gitweb
14 from gitosis import gitdaemon
15 from gitosis import app
16 from gitosis import util
18 ALLOW_RE = re.compile("^'/*(?P<path>[a-zA-Z0-9][a-zA-Z0-9@._-]*(/[a-zA-Z0-9][a-zA-Z0-9@._-]*)*)'$")
28 class ServingError(Exception):
32 return '%s' % self.__doc__
34 class CommandMayNotContainNewlineError(ServingError):
35 """Command may not contain newline"""
37 class UnknownCommandError(ServingError):
38 """Unknown command denied"""
40 class UnsafeArgumentsError(ServingError):
41 """Arguments to command look dangerous"""
43 class AccessDenied(ServingError):
44 """Access denied to repository"""
46 class WriteAccessDenied(AccessDenied):
47 """Repository write access denied"""
49 class ReadAccessDenied(AccessDenied):
50 """Repository read access denied"""
58 raise CommandMayNotContainNewlineError()
61 verb, args = command.split(None, 1)
63 # all known commands take one argument; improve if/when needed
64 raise UnknownCommandError()
66 if (verb not in COMMANDS_WRITE
67 and verb not in COMMANDS_READONLY):
68 raise UnknownCommandError()
70 match = ALLOW_RE.match(args)
72 raise UnsafeArgumentsError()
74 path = match.group('path')
76 # write access is always sufficient
77 newpath = access.haveAccess(
84 # didn't have write access
86 newpath = access.haveAccess(
93 raise ReadAccessDenied()
95 if verb in COMMANDS_WRITE:
96 # didn't have write access and tried to write
97 raise WriteAccessDenied()
99 (topdir, relpath) = newpath
100 assert not relpath.endswith('.git'), \
101 'git extension should have been stripped: %r' % relpath
102 repopath = '%s.git' % relpath
103 fullpath = os.path.join(topdir, repopath)
104 if (not os.path.exists(fullpath)
105 and verb in COMMANDS_WRITE):
106 # it doesn't exist on the filesystem, but the configuration
107 # refers to it, we're serving a write request, and the user is
108 # authorized to do that: create the repository on the fly
110 # create leading directories
112 for segment in repopath.split(os.sep)[:-1]:
113 p = os.path.join(p, segment)
116 repository.init(path=fullpath)
117 gitweb.set_descriptions(
120 generated = util.getGeneratedFilesDir(config=cfg)
121 gitweb.generate_project_list(
123 path=os.path.join(generated, 'projects.list'),
125 gitdaemon.set_export_ok(
129 # put the verb back together with the new path
130 newcmd = "%(verb)s '%(path)s'" % dict(
137 def create_parser(self):
138 parser = super(Main, self).create_parser()
139 parser.set_usage('%prog [OPTS] USER')
140 parser.set_description(
141 'Allow restricted git operations under DIR')
144 def handle_args(self, parser, cfg, options, args):
148 parser.error('Missing argument USER.')
150 log = logging.getLogger('gitosis.serve.main')
153 cmd = os.environ.get('SSH_ORIGINAL_COMMAND', None)
155 log.error('Need SSH_ORIGINAL_COMMAND in environment.')
158 log.debug('Got command %(cmd)r' % dict(
162 os.chdir(os.path.expanduser('~'))
170 except ServingError, e:
174 log.debug('Serving %s', newcmd)
175 os.execvp('git-shell', ['git-shell', '-c', newcmd])
176 log.error('Cannot execute git-shell.')