#!/usr/bin/env python## Copyright 2008 LShift Ltd# Copyright 2005-2007 by Intevation GmbH <intevation@intevation.de># Author(s):# Paul Crowley <paul@lshift.net># Thomas Arendsen Hein <thomas@intevation.de># with ideas from Mathieu PASQUET <kiorky@cryptelium.net>## This software may be used and distributed according to the terms# of the GNU General Public License, incorporated herein by reference."""hg-ssh - limit access to hg repositories reached via ssh. Part ofhg-admin-tools.This script is called by hg-ssh-wrapper with two arguments:hg-ssh <rulefile> <keyname>It expects to find the command the SSH user was trying to run in theenvironment variable SSH_ORIGINAL_COMMAND, and uses it to determinewhat the user was trying to do and to what repository, and then checkseach rule in the rule file in turn for a matching rule which decideswhat to do, defaulting to disallowing the action."""# enable importing on demand to reduce startup timefrom mercurial import demandimport; demandimport.enable()from mercurial import dispatchimport sys, os, redef fail(message): #logfile.write("Fail: %s\n" % message) sys.stderr.write(message + "\n") sys.exit(-1)# Note that this currently disallows dots in path components - if you change it# to allow them ensure that "." and ".." are disallowed in path components.allowedchars = "A-Za-z0-9_-"goodpathre = re.compile("([%s]+/)*[%s]+$" % (allowedchars, allowedchars))def goodpath(path): if goodpathre.match(path) is None: fail("Disallowing path: %s" % path)# Don't put anything except *A-Za-z0-9_- in rule globs or# you'll probably break security. No regexp metachars, not even .# We may fix this later.goodglobre = re.compile("[*/%s]+$" % allowedchars)def globmatch(pattern, match): if goodglobre.match(pattern) is None: fail("Bad glob pattern in auth config: %s" % pattern) pattern = pattern.replace(".", r'\.') pattern = pattern.replace("*", "[%s]*" % allowedchars) return re.compile(pattern + "$").match(match) is not Nonedef testrule(rulefile, keyname, path, applicable): goodpath(keyname) goodpath(path) f = open(rulefile) try: for l in f: l = l.strip() if l == "" or l.startswith("#"): continue rule, rk, rp = l.split() if globmatch(rk, keyname) and globmatch(rp, path): #logfile.write("Used rule: %s\n" % l) return rule in applicable finally: f.close() return Falsedef get_cmd(rulefile, keyname, cmd): if cmd.startswith('hg -R ') and cmd.endswith(' serve --stdio'): path = cmd[6:-14] if testrule(rulefile, keyname, path, set(["allow", "init"])): return ['-R', path, 'serve', '--stdio'] elif cmd.startswith('hg init '): path = cmd[8:] if testrule(rulefile, keyname, path, set(["init"])): return ['init', path] fail("Illegal command %r" % cmd)#logfile = open("/tmp/hg-ssh.%d.txt" % os.getpid(), "w")#logfile.write("Started: %s\n" % sys.argv)if len(sys.argv) != 3: fail("hg-ssh must have exactly two arguments (%s)" % sys.argv)rulefile = sys.argv[1]keyname = sys.argv[2]todispatch = get_cmd(rulefile, keyname, os.environ.get('SSH_ORIGINAL_COMMAND', '?'))dispatch.dispatch(todispatch)