# HG changeset patch # User Paul Crowley # Date 1255359847 -3600 # Node ID 0519745e7a57c540ee6de1adfa27624073123b12 # Parent cd3da73cdf63c65de4c21c588d9b2de9f0f7db76 Much less strict about most things diff -r cd3da73cdf63 -r 0519745e7a57 src/hg-ssh --- a/src/hg-ssh Mon Oct 12 12:08:49 2009 +0100 +++ b/src/hg-ssh Mon Oct 12 16:04:07 2009 +0100 @@ -33,40 +33,40 @@ from mercurial import dispatch -import sys, os +import sys, os, os.path from mercurialserver import ruleset, paths def fail(message): - #logfile.write("Fail: %s\n" % message) - sys.stderr.write(message + "\n") + sys.stderr.write("mercurial-server: %s\n" % message) sys.exit(-1) -def getpath(path): - path = path.rstrip("/") - if not ruleset.goodpath(path): - fail("Disallowing path: %s" % path) - return path +def checkpath(path) + path = os.path.dirname(path) + if path == "": + return + if os.path.exists(path + "/.hg"): + raise ruleset.AccessException() + checkpath(path) -def try_cmd(cmd): - if cmd.startswith('hg -R ') and cmd.endswith(' serve --stdio'): - repo = getpath(cmd[6:-14]) - ruleset.rules.set(repo=repo) - if ruleset.rules.allow("read", branch=None, file=None): - dispatch.dispatch(['-R', repo, 'serve', '--stdio']) - return - elif cmd.startswith('hg init '): - repo = getpath(cmd[8:]) - ruleset.rules.set(repo=repo) - if ruleset.rules.allow("init", branch=None, file=None): - dispatch.dispatch(['init', repo]) - return - fail("Illegal command %r" % cmd) +def getrepo(op, repo): + repo = os.path.normcase(os.path.normpath(repo.rstrip("/"))) + if len(repo) == 0: + fail("path to repository seems to be empty") + if repo.startswith("/"): + fail("absolute paths are not supported") + for component in repo.split("/"): + if component.startswith("."): + fail("paths cannot contain dot file components") + ruleset.rules.set(repo=repo) + ruleset.rules.check(op, branch=None, file=None) + checkpath(repo) + return repo #logfile = open("/tmp/hg-ssh.%d.txt" % os.getpid(), "w") #logfile.write("Started: %s\n" % sys.argv) if len(sys.argv) != 2: - fail("hg-ssh must have exactly one argument (%s)" + fail("hg-ssh wrongly called, is authorized_keys corrupt? (%s)" % sys.argv) paths.setExePath() @@ -85,4 +85,25 @@ if os.path.isfile(f): ruleset.rules.readfile(f) -try_cmd(os.environ.get('SSH_ORIGINAL_COMMAND', '?')) +cmd = os.environ.get('SSH_ORIGINAL_COMMAND', '') +try: + if cmd.startswith('hg -R ') and cmd.endswith(' serve --stdio'): + repo = getrepo("read", cmd[6:-14]) + if not os.path.isdir(repo + "/.hg") + fail("no such repository %s" % repo) + dispatch.dispatch(['-R', repo, 'serve', '--stdio']) + elif cmd.startswith('hg init '): + repo = getrepo("init", cmd[8:]) + if os.path.exists(repo): + fail("%s exists" % repo) + d = os.path.dirname(repo) + if d != "" and not os.path.isdir(d): + os.makedirs(d) + dispatch.dispatch(['init', repo]) + elif cmd == "": + fail("direct logins on the hg account prohibited ") + else: + fail("illegal command %r" % cmd) +except ruleset.AccessException: + fail("access denied") + diff -r cd3da73cdf63 -r 0519745e7a57 src/mercurialserver/refreshauth.py --- a/src/mercurialserver/refreshauth.py Mon Oct 12 12:08:49 2009 +0100 +++ b/src/mercurialserver/refreshauth.py Mon Oct 12 16:04:07 2009 +0100 @@ -5,12 +5,14 @@ # file every time it is run # WARNING -import sys +import re import os import os.path import pwd import subprocess -from mercurialserver import ruleset, paths +from mercurialserver import paths + +goodkey = re.compile("[A-Za-z0-9._-]+$") def refreshAuth(pw_dir): akeyfile = pw_dir + "/.ssh/authorized_keys" @@ -38,9 +40,10 @@ raise Exception("Inconsistent behaviour in os.walk, bailing") #print "Processing file", ffn keyname = ffn[len(kr):] - if not ruleset.goodpath(keyname): + # FIXME: still too strict + if not goodkey.match(keyname) # ignore any path that contains dodgy characters - #print "Ignoring file", ffn + print "Ignoring key that contains banned character:", ffn continue p = subprocess.Popen(("ssh-keygen", "-i", "-f", ffn), stdout=subprocess.PIPE, stderr=subprocess.PIPE) diff -r cd3da73cdf63 -r 0519745e7a57 src/mercurialserver/ruleset.py --- a/src/mercurialserver/ruleset.py Mon Oct 12 12:08:49 2009 +0100 +++ b/src/mercurialserver/ruleset.py Mon Oct 12 16:04:07 2009 +0100 @@ -12,28 +12,10 @@ allowedchars = "A-Za-z0-9_-" -goodpathre = re.compile("([%s]+/)*[%s]+$" % (allowedchars, allowedchars)) -def goodpath(path): - return goodpathre.match(path) is not None - -goodglobre = re.compile("[*/%s]+$" % allowedchars) - -def goodglob(pattern): - return goodglobre.match(pattern) is not None - -# Don't put anything except *A-Za-z0-9_- in rule globs or -# it will match nothing. No regexp metachars, not even . -# We may fix this later. def globmatcher(pattern): - if not goodglob(pattern): - #fail("Bad glob pattern in auth config: %s" % pattern) - # FIXME: report it somehow - return lambda x: False - # Substitution cunning so ** can be different from * - pattern = pattern.replace("*", "[]") - pattern = pattern.replace("[][]", "[/%s]*" % allowedchars) - pattern = pattern.replace("[]", "[%s]*" % allowedchars) - rex = re.compile(pattern + "$") + p = "[^/]*".join(re.escape(c) for c in pattern.split("*")) + # ** means "match recursively" ie "ignore directories" + rex = re.compile(p.replace("[^/]*[^/]*", ".*") + "$") # None matches everything return lambda x: x is None or rex.match(x) is not None @@ -46,6 +28,9 @@ return True return c +class AccessException(Exception): + pass + class Ruleset(object): '''Class representing the rules in a rule file''' @@ -76,6 +61,10 @@ a = self.matchrule(kw) return a in self.levels and self.levels.index(a) <= self.levels.index(level) + def check(self, level, **kw): + if not self.allow(level, **kw): + raise AccessException() + def readfile(self, fn): try: f = open(fn)