aboutsummaryrefslogtreecommitdiff
path: root/gitosis/serve.py
blob: b1ed54886d0ab0be3e5e8aa6561e42ffdb45b7f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
"""
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 logginglogging.basicConfig(level=logging.DEBUG)
 
import sysosoptparsere
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("^(?P<command>git-(?:receive|upload)-pack) '(?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', 
    ]
 
def main():
    log = logging.getLogger('gitosis.serve.main')
    os.umask(0022)
 
    parser = getParser()
    (optionsargs) = 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.")
 
    log.debug('Got command %(cmd)r' % dict(
        cmd=cmd,
        ))
 
    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()
    try:
        conffile = file(options.config)
    except (IOErrorOSError)e:
        # I trust the exception has the path. 
        die("Unable to read config file: %s." % e)
    try:
        cfg.readfp(conffile)
    finally:
        conffile.close()
 
    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("Read access denied.")
 
        if command in COMMANDS_WRITE:
            # didn't have write access and tried to write 
            die("Write access denied.")
 
    log.debug('Serving %(command)r %(newpath)r' % dict(
        command=command,
        newpath=newpath,
        ))
 
    # put the command back together with the new path 
    newcmd = "%(command)s '%(newpath)s'" % dict(
        command=command,
        newpath=newpath,
        )
    os.execvpe('git-shell'['git-shell', '-c', newcmd]{})
    die("Cannot execute git-shell.")