hg-ssh
changeset 0 41ecb5a3172c
child 4 dcd195f3e52c
equal deleted inserted replaced
-1:000000000000 0:41ecb5a3172c
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Copyright 2008 LShift Ltd
       
     4 # Modified by Paul Crowley <paul@lshift.net>
       
     5 # with ideas from  Mathieu PASQUET <kiorky@cryptelium.net>
       
     6 # Copyright 2005-2007 by Intevation GmbH <intevation@intevation.de>
       
     7 # Author(s):
       
     8 # Thomas Arendsen Hein <thomas@intevation.de>
       
     9 #
       
    10 # This software may be used and distributed according to the terms
       
    11 # of the GNU General Public License, incorporated herein by reference.
       
    12 
       
    13 """
       
    14 hg-ssh - a wrapper for ssh access to a limited set of mercurial repos
       
    15 
       
    16 DO NOT USE WITHOUT READING THE SOURCE!  In particular note that we HARDWIRE
       
    17 the path to hgadmin/hg-ssh-access.conf.
       
    18 
       
    19 To be used in ~/.ssh/authorized_keys with the "command" option, see sshd(8):
       
    20 command="hg-ssh keyid" ssh-dss ...
       
    21 (probably together with these other useful options:
       
    22  no-port-forwarding,no-X11-forwarding,no-agent-forwarding)
       
    23 
       
    24 This allows pull/push over ssh to to the repositories given as arguments.
       
    25 
       
    26 If all your repositories are subdirectories of a common directory, you can
       
    27 allow shorter paths with:
       
    28 command="cd path/to/my/repositories && hg-ssh repo1 subdir/repo2"
       
    29 
       
    30 You can use pattern matching of your normal shell, e.g.:
       
    31 command="cd repos && hg-ssh user/thomas/* projects/{mercurial,foo}"
       
    32 """
       
    33 
       
    34 # enable importing on demand to reduce startup time
       
    35 from mercurial import demandimport; demandimport.enable()
       
    36 
       
    37 from mercurial import dispatch
       
    38 
       
    39 import sys, os, re
       
    40 
       
    41 def fail(message):
       
    42     #logfile.write("Fail: %s\n" % message)
       
    43     sys.stderr.write(message + "\n")
       
    44     sys.exit(-1)
       
    45 
       
    46 allowedchars = "A-Za-z0-9_.-"
       
    47 goodpathre = re.compile("([%s]+/)*[%s]+$" % (allowedchars, allowedchars))
       
    48 def goodpath(path):
       
    49     if goodpathre.match(path) is None:
       
    50         fail("Disallowing path: %s" % path)
       
    51 
       
    52 
       
    53 # Don't put anything except *A-Za-z0-9_- in rule globs or
       
    54 # you'll probably break security.  No regexp metachars, not even .
       
    55 # We may fix this later.
       
    56 goodglobre = re.compile("[*/%s]+$" % allowedchars)
       
    57 def globmatch(pattern, match):
       
    58     if goodglobre.match(pattern) is None:
       
    59         fail("Bad glob pattern in auth config: %s" % pattern)
       
    60     pattern = pattern.replace(".", r'\.')
       
    61     pattern = pattern.replace("*", "[%s]*" % allowedchars)
       
    62     return re.compile(pattern + "$").match(match) is not None
       
    63 
       
    64 def testrule(keyname, path, applicable):
       
    65     goodpath(keyname)
       
    66     goodpath(path)
       
    67     f = open("hgadmin/hg-ssh-access.conf")
       
    68     try:
       
    69         for l in f:
       
    70             l = l.strip()
       
    71             if l == "" or l.startswith("#"):
       
    72                 continue
       
    73             rule, rk, rp = l.split()
       
    74             if globmatch(rk, keyname) and globmatch(rp, path):
       
    75                 #logfile.write("Used rule: %s\n" % l)
       
    76                 return rule in applicable
       
    77         return False
       
    78     finally:
       
    79         f.close()
       
    80 
       
    81 def get_cmd(keyname, cmd):
       
    82     if cmd.startswith('hg -R ') and cmd.endswith(' serve --stdio'):
       
    83         path = cmd[6:-14]
       
    84         if testrule(keyname, path, set(["allow", "init"])):
       
    85             return ['-R', path, 'serve', '--stdio']
       
    86     elif cmd.startswith('hg init '):
       
    87         path = cmd[8:]
       
    88         if testrule(keyname, path, set(["init"])):
       
    89             return ['init', path]
       
    90     fail("Illegal command %r" % cmd)
       
    91 
       
    92 #logfile = open("/tmp/hg-ssh.%d.txt" % os.getpid(), "w")
       
    93 #logfile.write("Started: %s\n" % sys.argv)
       
    94 
       
    95 if len(sys.argv) != 2:
       
    96     fail("Error in hg-ssh configuration in .ssh/authorized_keys: too many arguments (%s)" 
       
    97         % sys.argv)
       
    98 
       
    99 keyname = sys.argv[1]
       
   100 todispatch = get_cmd(keyname, os.environ.get('SSH_ORIGINAL_COMMAND', '?'))
       
   101 os.environ["HG_ACL_USER"] = keyname
       
   102 dispatch.dispatch(todispatch)
       
   103