src/hg-ssh
author Paul Crowley <paul@lshift.net>
Tue, 30 Aug 2011 15:45:37 +0100
changeset 305 bf58227c168a
parent 304 6e575b602d2b
child 306 f832a8aeef44
permissions -rwxr-xr-x
Small fixup for that fix
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
0
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
     1
#!/usr/bin/env python
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
     2
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
     3
"""
10
524b4a45ef0a wrap overlong lines
Paul Crowley <paul@lshift.net>
parents: 7
diff changeset
     4
hg-ssh - limit access to hg repositories reached via ssh.  Part of
36
b3237aabd0fe Change the name to mercurial-server
Paul Crowley <paul@lshift.net>
parents: 33
diff changeset
     5
mercurial-server.
0
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
     6
214
7374d0147875 Script docstring was full of lies - now up to date
Paul Crowley <paul@lshift.net>
parents: 212
diff changeset
     7
It is called by ssh due to an entry in the authorized_keys file,
7374d0147875 Script docstring was full of lies - now up to date
Paul Crowley <paul@lshift.net>
parents: 212
diff changeset
     8
with the name for the key passed on the command line.
18
538d6b198f4a Big change to support file conditions; format of hg-ssh-access.conf
Paul Crowley <paul@lshift.net>
parents: 15
diff changeset
     9
538d6b198f4a Big change to support file conditions; format of hg-ssh-access.conf
Paul Crowley <paul@lshift.net>
parents: 15
diff changeset
    10
It uses SSH_ORIGINAL_COMMAND to determine what the user was trying to
538d6b198f4a Big change to support file conditions; format of hg-ssh-access.conf
Paul Crowley <paul@lshift.net>
parents: 15
diff changeset
    11
do and to what repository, and then checks each rule in the rule file
538d6b198f4a Big change to support file conditions; format of hg-ssh-access.conf
Paul Crowley <paul@lshift.net>
parents: 15
diff changeset
    12
in turn for a matching rule which decides what to do, defaulting to
538d6b198f4a Big change to support file conditions; format of hg-ssh-access.conf
Paul Crowley <paul@lshift.net>
parents: 15
diff changeset
    13
disallowing the action.
538d6b198f4a Big change to support file conditions; format of hg-ssh-access.conf
Paul Crowley <paul@lshift.net>
parents: 15
diff changeset
    14
0
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
    15
"""
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
    16
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
    17
# enable importing on demand to reduce startup time
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
    18
from mercurial import demandimport; demandimport.enable()
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
    19
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
    20
from mercurial import dispatch
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
    21
304
6e575b602d2b Mercurial 1.9 compatibility
Andrej Krpic <akrpic77@gmail.com>
parents: 303
diff changeset
    22
try:
305
bf58227c168a Small fixup for that fix
Paul Crowley <paul@lshift.net>
parents: 304
diff changeset
    23
    from mercurial.dispatch import request
304
6e575b602d2b Mercurial 1.9 compatibility
Andrej Krpic <akrpic77@gmail.com>
parents: 303
diff changeset
    24
except ImportError:
6e575b602d2b Mercurial 1.9 compatibility
Andrej Krpic <akrpic77@gmail.com>
parents: 303
diff changeset
    25
    request = list
6e575b602d2b Mercurial 1.9 compatibility
Andrej Krpic <akrpic77@gmail.com>
parents: 303
diff changeset
    26
106
0519745e7a57 Much less strict about most things
Paul Crowley <paul@lshift.net>
parents: 103
diff changeset
    27
import sys, os, os.path
107
84e9e33d866b Fixes, plus base64 what you don't trust
Paul Crowley <paul@lshift.net>
parents: 106
diff changeset
    28
import base64
211
0cd59649772c Rename paths.py ot config.py
Paul Crowley <paul@lshift.net>
parents: 165
diff changeset
    29
from mercurialserver import config, ruleset
0
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
    30
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
    31
def fail(message):
106
0519745e7a57 Much less strict about most things
Paul Crowley <paul@lshift.net>
parents: 103
diff changeset
    32
    sys.stderr.write("mercurial-server: %s\n" % message)
0
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
    33
    sys.exit(-1)
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
    34
303
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    35
config.initExe()
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    36
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    37
for k,v in config.getEnv():
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    38
    os.environ[k.upper()] = v
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    39
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    40
if len(sys.argv) == 3 and sys.argv[1] == "--base64":
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    41
    ruleset.rules.set(user = base64.b64decode(sys.argv[2]))
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    42
elif len(sys.argv) == 2:
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    43
    ruleset.rules.set(user = sys.argv[1])
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    44
else:
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    45
    fail("hg-ssh wrongly called, is authorized_keys corrupt? (%s)"
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    46
        % sys.argv)
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    47
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    48
os.chdir(config.getReposPath())
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    49
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    50
for f in config.getAccessPaths():
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    51
    if os.path.isfile(f):
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    52
        ruleset.rules.readfile(f)
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    53
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    54
alloweddots = config.getAllowedDots()
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    55
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    56
def dotException(pathtail):
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    57
    for ex in alloweddots:
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    58
        splex = ex.split("/")
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    59
        if len(pathtail) >= len(splex) and pathtail[:len(splex)] == splex:
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    60
            return True
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    61
    return False
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    62
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    63
def checkDots(path, pathtail = []):
110
69596fffcf7d Less canonicalisation, use os.path to check for dotfiles
Paul Crowley <paul@lshift.net>
parents: 109
diff changeset
    64
    head, tail = os.path.split(path)
303
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    65
    pathtail = [tail] + pathtail
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    66
    if tail.startswith(".") and not dotException(pathtail):
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    67
            fail("paths cannot contain dot file components")
110
69596fffcf7d Less canonicalisation, use os.path to check for dotfiles
Paul Crowley <paul@lshift.net>
parents: 109
diff changeset
    68
    if head:
303
3f9f247b72bd Allow mq clones
Paul Crowley <paul@lshift.net>
parents: 275
diff changeset
    69
        checkDots(head, pathtail)
110
69596fffcf7d Less canonicalisation, use os.path to check for dotfiles
Paul Crowley <paul@lshift.net>
parents: 109
diff changeset
    70
106
0519745e7a57 Much less strict about most things
Paul Crowley <paul@lshift.net>
parents: 103
diff changeset
    71
def getrepo(op, repo):
110
69596fffcf7d Less canonicalisation, use os.path to check for dotfiles
Paul Crowley <paul@lshift.net>
parents: 109
diff changeset
    72
    # First canonicalise, then check the string, then the rules
244
48fab30c38e1 Strip repo name in case of tortoiseHG
Paul Crowley <paul@lshift.net>
parents: 242
diff changeset
    73
    repo = repo.strip().rstrip("/")
106
0519745e7a57 Much less strict about most things
Paul Crowley <paul@lshift.net>
parents: 103
diff changeset
    74
    if len(repo) == 0:
0519745e7a57 Much less strict about most things
Paul Crowley <paul@lshift.net>
parents: 103
diff changeset
    75
        fail("path to repository seems to be empty")
0519745e7a57 Much less strict about most things
Paul Crowley <paul@lshift.net>
parents: 103
diff changeset
    76
    if repo.startswith("/"):
0519745e7a57 Much less strict about most things
Paul Crowley <paul@lshift.net>
parents: 103
diff changeset
    77
        fail("absolute paths are not supported")
117
b6b8a5daf0f4 fix call to checkDots
Paul Crowley <paul@lshift.net>
parents: 110
diff changeset
    78
    checkDots(repo)
106
0519745e7a57 Much less strict about most things
Paul Crowley <paul@lshift.net>
parents: 103
diff changeset
    79
    ruleset.rules.set(repo=repo)
109
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    80
    if not ruleset.rules.allow(op, branch=None, file=None):
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    81
        fail("access denied")
106
0519745e7a57 Much less strict about most things
Paul Crowley <paul@lshift.net>
parents: 103
diff changeset
    82
    return repo
0
41ecb5a3172c separate out executables and data
Paul Crowley <paul@lshift.net>
parents:
diff changeset
    83
108
00b48d7bdfa0 Improve check on whether user supplied a command
Paul Crowley <paul@lshift.net>
parents: 107
diff changeset
    84
cmd = os.environ.get('SSH_ORIGINAL_COMMAND', None)
109
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    85
if cmd is None:
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    86
    fail("direct logins on the hg account prohibited")
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    87
elif cmd.startswith('hg -R ') and cmd.endswith(' serve --stdio'):
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    88
    repo = getrepo("read", cmd[6:-14])
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    89
    if not os.path.isdir(repo + "/.hg"):
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    90
        fail("no such repository %s" % repo)
304
6e575b602d2b Mercurial 1.9 compatibility
Andrej Krpic <akrpic77@gmail.com>
parents: 303
diff changeset
    91
    dispatch.dispatch(request(['-R', repo, 'serve', '--stdio']))
109
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    92
elif cmd.startswith('hg init '):
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    93
    repo = getrepo("init", cmd[8:])
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    94
    if os.path.exists(repo):
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    95
        fail("%s exists" % repo)
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    96
    d = os.path.dirname(repo)
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    97
    if d != "" and not os.path.isdir(d):
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
    98
        os.makedirs(d)
304
6e575b602d2b Mercurial 1.9 compatibility
Andrej Krpic <akrpic77@gmail.com>
parents: 303
diff changeset
    99
    dispatch.dispatch(request(['init', repo]))
109
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
   100
else:
72100d3ed1bd Don't mix exceptions and sys.exit based failures
Paul Crowley <paul@lshift.net>
parents: 108
diff changeset
   101
    fail("illegal command %r" % cmd)