|
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 |