diff -r 4c98440de851 -r 538d6b198f4a hg-ssh --- a/hg-ssh Mon Apr 21 12:37:56 2008 +0100 +++ b/hg-ssh Tue Apr 22 09:46:29 2008 +0100 @@ -2,7 +2,7 @@ # # Copyright 2008 LShift Ltd # Copyright 2005-2007 by Intevation GmbH -# Author(s): +# Authors: # Paul Crowley # Thomas Arendsen Hein # with ideas from Mathieu PASQUET @@ -14,15 +14,18 @@ hg-ssh - limit access to hg repositories reached via ssh. Part of hg-admin-tools. -This script is called by hg-ssh-wrapper with two arguments: - -hg-ssh +This script is called by hg-ssh-wrapper with no arguments - everything +should be in enviroment variables: -It expects to find the command the SSH user was trying to run in the -environment variable SSH_ORIGINAL_COMMAND, and uses it to determine -what the user was trying to do and to what repository, and then checks -each rule in the rule file in turn for a matching rule which decides -what to do, defaulting to disallowing the action. +HG_ACCESS_RULES_FILE identifies the path to the rules file +REMOTE_USER the remote user (which is the key used by ssh) +SSH_ORIGINAL_COMMAND the command the user was trying to run + +It uses SSH_ORIGINAL_COMMAND to determine what the user was trying to +do and to what repository, and then checks each rule in the rule file +in turn for a matching rule which decides what to do, defaulting to +disallowing the action. + """ # enable importing on demand to reduce startup time @@ -30,70 +33,44 @@ from mercurial import dispatch -import sys, os, re +import sys, os +import rules def 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: +def getpath(path): + if path.endswith("/"): + path = path[:-1] + if not rules.goodpath(path): 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 None + return path -def 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 False - -def get_cmd(rulefile, keyname, cmd): +def get_cmd(rules, remoteuser, 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'] + repo = getpath(cmd[6:-14]) + if rules.allow("read", user=remoteuser, repo=repo, file=None): + os.environ["HG_REPO_PATH"] = repo + return ['-R', repo, 'serve', '--stdio'] elif cmd.startswith('hg init '): - path = cmd[8:] - if testrule(rulefile, keyname, path, set(["init"])): - return ['init', path] + repo = getpath(cmd[8:]) + if rules.allow("init", user=remoteuser, repo=repo, file=None): + os.environ["HG_REPO_PATH"] = repo + return ['init', repo] 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)" +if len(sys.argv) != 1: + fail("hg-ssh must have no arguments (%s)" % sys.argv) -rulefile = sys.argv[1] -keyname = sys.argv[2] -todispatch = get_cmd(rulefile, keyname, +rules = rules.Ruleset.readfile(os.environ['HG_ACCESS_RULES_FILE']) +remoteuser = getpath(os.environ['REMOTE_USER']) +todispatch = get_cmd(rules, remoteuser, os.environ.get('SSH_ORIGINAL_COMMAND', '?')) dispatch.dispatch(todispatch)