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 log
= logging
.getLogger('gitosis.serve')
20 ALLOW_RE
= re
.compile("^'/*(?P<path>[a-zA-Z0-9][a-zA-Z0-9@._-]*(/[a-zA-Z0-9][a-zA-Z0-9@._-]*)*)'$")
32 class ServingError(Exception):
36 return '%s' % self
.__doc
__
38 class CommandMayNotContainNewlineError(ServingError
):
39 """Command may not contain newline"""
41 class UnknownCommandError(ServingError
):
42 """Unknown command denied"""
44 class UnsafeArgumentsError(ServingError
):
45 """Arguments to command look dangerous"""
47 class AccessDenied(ServingError
):
48 """Access denied to repository"""
50 class WriteAccessDenied(AccessDenied
):
51 """Repository write access denied"""
53 class ReadAccessDenied(AccessDenied
):
54 """Repository read access denied"""
62 raise CommandMayNotContainNewlineError()
65 verb
, args
= command
.split(None, 1)
67 # all known "git-foo" commands take one argument; improve
69 raise UnknownCommandError()
73 subverb
, args
= args
.split(None, 1)
75 # all known "git foo" commands take one argument; improve
77 raise UnknownCommandError()
78 verb
= '%s %s' % (verb
, subverb
)
80 if (verb
not in COMMANDS_WRITE
81 and verb
not in COMMANDS_READONLY
):
82 raise UnknownCommandError()
84 match
= ALLOW_RE
.match(args
)
86 raise UnsafeArgumentsError()
88 path
= match
.group('path')
90 # write access is always sufficient
91 newpath
= access
.haveAccess(
98 # didn't have write access; try once more with the popular
100 newpath
= access
.haveAccess(
105 if newpath
is not None:
107 'Repository %r config has typo "writeable", '
108 +'should be "writable"',
113 # didn't have write access
115 newpath
= access
.haveAccess(
122 raise ReadAccessDenied()
124 if verb
in COMMANDS_WRITE
:
125 # didn't have write access and tried to write
126 raise WriteAccessDenied()
128 (topdir
, relpath
) = newpath
129 assert not relpath
.endswith('.git'), \
130 'git extension should have been stripped: %r' % relpath
131 repopath
= '%s.git' % relpath
132 fullpath
= os
.path
.join(topdir
, repopath
)
133 if not os
.path
.exists(fullpath
):
134 # it doesn't exist on the filesystem, but the configuration
135 # refers to it, we're serving a write request, and the user is
136 # authorized to do that: create the repository on the fly
138 # create leading directories
140 for segment
in repopath
.split(os
.sep
)[:-1]:
141 p
= os
.path
.join(p
, segment
)
144 repository
.init(path
=fullpath
)
145 gitweb
.set_descriptions(
148 generated
= util
.getGeneratedFilesDir(config
=cfg
)
149 gitweb
.generate_project_list(
151 path
=os
.path
.join(generated
, 'projects.list'),
153 gitdaemon
.set_export_ok(
157 # put the verb back together with the new path
158 newcmd
= "%(verb)s '%(path)s'" % dict(
165 def create_parser(self
):
166 parser
= super(Main
, self
).create_parser()
167 parser
.set_usage('%prog [OPTS] USER')
168 parser
.set_description(
169 'Allow restricted git operations under DIR')
172 def handle_args(self
, parser
, cfg
, options
, args
):
176 parser
.error('Missing argument USER.')
178 main_log
= logging
.getLogger('gitosis.serve.main')
181 cmd
= os
.environ
.get('SSH_ORIGINAL_COMMAND', None)
183 main_log
.error('Need SSH_ORIGINAL_COMMAND in environment.')
186 main_log
.debug('Got command %(cmd)r' % dict(
190 os
.chdir(os
.path
.expanduser('~'))
198 except ServingError
, e
:
199 main_log
.error('%s', e
)
202 main_log
.debug('Serving %s', newcmd
)
203 os
.execvp('git', ['git', 'shell', '-c', newcmd
])
204 main_log
.error('Cannot execute git-shell.')