# HG changeset patch # User Paul Crowley # Date 1315304218 -3600 # Node ID f597eb3b5aaf04b8ea8e1de5e23846ed9e208667 # Parent 31d5c6236f713ca8fc72099d20101e154dcdcaad# Parent 4e65f8242c0bb8669897ec65306b1cf961596805 Merge default diff -r 31d5c6236f71 -r f597eb3b5aaf .hgtags --- a/.hgtags Sun Dec 19 09:49:18 2010 +0000 +++ b/.hgtags Tue Sep 06 11:16:58 2011 +0100 @@ -4,10 +4,11 @@ 95c9ab8e4bfc6fea6460b3147c3097373eba5d42 debian_0.7 1ad9d5841a48a77f68dc5350bd1f941327a6348a release_0.8 1e4050abb96e72c6324b93709e56a3e135e63ce1 debian_0.8-1 -d42d3f5311c55cab668d6962a61d44ba98645e release_0.9 +fed42d3f5311c55cab668d6962a61d44ba98645e release_0.9 b6887a9b8792bb2b69b428448102140fce121e29 debian_0.9-1 8ce190faa5c2b50f63cc5b11e28daf98836498d8 release_1.0 60c2d676a754e02f1f7656a4b137399438b2ed35 debian_1.0-1 92cb6640a6417edaf52870c8a97000e11bb8b138 release_1.0.1 c7aca86585824f39fa47015d6ebf1bab6a25b82c debian_1.0.1-1 +01eca64f77abbf0da41cd777a365de2a5a02988b release_1.1 32dba1a70a5443cf86a78b5a9471ad7ced034a12 debian_1.1-1 diff -r 31d5c6236f71 -r f597eb3b5aaf NEWS --- a/NEWS Sun Dec 19 09:49:18 2010 +0000 +++ b/NEWS Tue Sep 06 11:16:58 2011 +0100 @@ -40,7 +40,7 @@ * Belatedly added NEWS file :-) Upgrading: move the paths/hgrc entry in .mercurial-server to env/HGRCPATH, -and add an entry under paths that reads +and add an entry under paths that reads "authorized_keys = ~/.ssh/authorized_keys" ==================== @@ -89,4 +89,3 @@ ==================== * First numbered release - diff -r 31d5c6236f71 -r f597eb3b5aaf README --- a/README Sun Dec 19 09:49:18 2010 +0000 +++ b/README Tue Sep 06 11:16:58 2011 +0100 @@ -2,7 +2,7 @@ mercurial-server gives your developers remote read/write access to centralized Mercurial repositories using SSH public key authentication; it -provides convenient and fine-grained key management and access control. +provides convenient and fine-grained key management and access control. http://www.lshift.net/mercurial-server.html @@ -25,7 +25,7 @@ Though mercurial-server is currently targeted at Debian-based systems such as Ubuntu, other users have reported success getting it running on other Unix-based systems such as Red Hat. Running it on a non-Unix system such as -Windows is not supported. You will need root privileges to install it. +Windows is not supported. You will need root privileges to install it. The best way to install mercurial-server is using your package management system - there are pre-built .deb files on the website. However, there is @@ -41,4 +41,3 @@ See doc/manual.docbook for the rest of the documentation. Paul Crowley, paul@lshift.net, 2010 - diff -r 31d5c6236f71 -r f597eb3b5aaf dev/chroot-test/action/go --- a/dev/chroot-test/action/go Sun Dec 19 09:49:18 2010 +0000 +++ b/dev/chroot-test/action/go Tue Sep 06 11:16:58 2011 +0100 @@ -15,7 +15,7 @@ perl -i -pe 's/^Port 22$/Port 2222/' /etc/ssh/sshd_config /etc/init.d/ssh start -ssh-keyscan -p 2222 localhost > /etc/ssh/ssh_known_hosts +ssh-keyscan -t rsa,dsa,ecdsa -p 2222 localhost > /etc/ssh/ssh_known_hosts . ./install-installables @@ -33,4 +33,3 @@ /etc/init.d/ssh stop #touch results - diff -r 31d5c6236f71 -r f597eb3b5aaf dev/chroot-test/action/install-installables diff -r 31d5c6236f71 -r f597eb3b5aaf dev/chroot-test/action/test1 --- a/dev/chroot-test/action/test1 Sun Dec 19 09:49:18 2010 +0000 +++ b/dev/chroot-test/action/test1 Tue Sep 06 11:16:58 2011 +0100 @@ -43,4 +43,9 @@ echo "Pushing changes" hg clone . ssh://chroothg/real/project hg clone nested ssh://chroothg/real/project/nested +cd .. +echo "Creating an mq repository" +hg init qrepo +(cd qrepo ; hg --config extensions.mq= qinit -c) +hg --config extensions.mq= qclone qrepo ssh://chroothg/qrepo echo "Done for user test1" diff -r 31d5c6236f71 -r f597eb3b5aaf dev/chroot-test/copy-installables diff -r 31d5c6236f71 -r f597eb3b5aaf dev/chroot-test/run-test --- a/dev/chroot-test/run-test Sun Dec 19 09:49:18 2010 +0000 +++ b/dev/chroot-test/run-test Tue Sep 06 11:16:58 2011 +0100 @@ -31,6 +31,7 @@ fi if [ -e $BACKING ] ; then echo "Copying deb files into cache" + mkdir -p $BUILDDIR/aptcache/$DEBVERSION cp $BACKING/var/cache/apt/archives/* $BUILDDIR/aptcache/$DEBVERSION || true echo "Deleting old filesystem backing store" rm -rf $BACKING @@ -59,4 +60,4 @@ mount -t proc proc $MOUNT/proc chroot $MOUNT ./action/go - +echo "Completed successfully" diff -r 31d5c6236f71 -r f597eb3b5aaf doc/manual.docbook --- a/doc/manual.docbook Sun Dec 19 09:49:18 2010 +0000 +++ b/doc/manual.docbook Tue Sep 06 11:16:58 2011 +0100 @@ -45,7 +45,7 @@ spoon and you have installed mercurial-server on jeeves using the package management system (see the README for more on installation). We assume that you have created your SSH public key, set up your SSH agent with this key, and that this key gives you access to jeeves. +class="systemname">jeeves. jay@spoon:~$ ssh -A jeeves jay@jeeves:~$ ssh-add -L > my-key @@ -532,4 +532,3 @@ - diff -r 31d5c6236f71 -r f597eb3b5aaf setup.py --- a/setup.py Sun Dec 19 09:49:18 2010 +0000 +++ b/setup.py Tue Sep 06 11:16:58 2011 +0100 @@ -14,10 +14,9 @@ scripts = ['src/hg-ssh', 'src/refresh-auth'], data_files = [ ('init', [ - 'src/init/hginit', - 'src/init/dot-mercurial-server', + 'src/init/hginit', + 'src/init/dot-mercurial-server', 'src/init/hgadmin-hgrc' ]), ], ) - diff -r 31d5c6236f71 -r f597eb3b5aaf src/hg-ssh --- a/src/hg-ssh Sun Dec 19 09:49:18 2010 +0000 +++ b/src/hg-ssh Tue Sep 06 11:16:58 2011 +0100 @@ -19,6 +19,11 @@ from mercurial import dispatch +try: + request = dispatch.request +except AttributeError: + request = list + import sys, os, os.path import base64 from mercurialserver import config, ruleset @@ -27,12 +32,41 @@ sys.stderr.write("mercurial-server: %s\n" % message) sys.exit(-1) -def checkDots(path): +config.initExe() + +for k,v in config.getEnv(): + os.environ[k.upper()] = v + +if len(sys.argv) == 3 and sys.argv[1] == "--base64": + ruleset.rules.set(user = base64.b64decode(sys.argv[2])) +elif len(sys.argv) == 2: + ruleset.rules.set(user = sys.argv[1]) +else: + fail("hg-ssh wrongly called, is authorized_keys corrupt? (%s)" + % sys.argv) + +os.chdir(config.getReposPath()) + +for f in config.getAccessPaths(): + if os.path.isfile(f): + ruleset.rules.readfile(f) + +alloweddots = config.getAllowedDots() + +def dotException(pathtail): + for ex in alloweddots: + splex = ex.split("/") + if len(pathtail) >= len(splex) and pathtail[:len(splex)] == splex: + return True + return False + +def checkDots(path, pathtail = []): head, tail = os.path.split(path) - if tail.startswith("."): - fail("paths cannot contain dot file components") + pathtail = [tail] + pathtail + if tail.startswith(".") and not dotException(pathtail): + fail("paths cannot contain dot file components") if head: - checkDots(head) + checkDots(head, pathtail) def getrepo(op, repo): # First canonicalise, then check the string, then the rules @@ -47,25 +81,6 @@ fail("access denied") return repo -config.initExe() - -for k,v in config.getEnv(): - os.environ[k.upper()] = v - -if len(sys.argv) == 3 and sys.argv[1] == "--base64": - ruleset.rules.set(user = base64.b64decode(sys.argv[2])) -elif len(sys.argv) == 2: - ruleset.rules.set(user = sys.argv[1]) -else: - fail("hg-ssh wrongly called, is authorized_keys corrupt? (%s)" - % sys.argv) - -os.chdir(config.getReposPath()) - -for f in config.getAccessPaths(): - if os.path.isfile(f): - ruleset.rules.readfile(f) - cmd = os.environ.get('SSH_ORIGINAL_COMMAND', None) if cmd is None: fail("direct logins on the hg account prohibited") @@ -73,7 +88,7 @@ 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']) + dispatch.dispatch(request(['-R', repo, 'serve', '--stdio'])) elif cmd.startswith('hg init '): repo = getrepo("init", cmd[8:]) if os.path.exists(repo): @@ -81,7 +96,6 @@ d = os.path.dirname(repo) if d != "" and not os.path.isdir(d): os.makedirs(d) - dispatch.dispatch(['init', repo]) + dispatch.dispatch(request(['init', repo])) else: fail("illegal command %r" % cmd) - diff -r 31d5c6236f71 -r f597eb3b5aaf src/init/conf/remote-hgrc.d/access.rc --- a/src/init/conf/remote-hgrc.d/access.rc Sun Dec 19 09:49:18 2010 +0000 +++ b/src/init/conf/remote-hgrc.d/access.rc Tue Sep 06 11:16:58 2011 +0100 @@ -2,4 +2,3 @@ [hooks] pretxnchangegroup.access = python:mercurialserver.access.hook - diff -r 31d5c6236f71 -r f597eb3b5aaf src/init/dot-mercurial-server --- a/src/init/dot-mercurial-server Sun Dec 19 09:49:18 2010 +0000 +++ b/src/init/dot-mercurial-server Tue Sep 06 11:16:58 2011 +0100 @@ -7,9 +7,12 @@ keys = /etc/mercurial-server/keys:~/repos/hgadmin/keys access = /etc/mercurial-server/access.conf:~/repos/hgadmin/access.conf +[exceptions] +# Allow the creation of mq repositories by default +allowdots = .hg/patches + [env] # Use a different hgrc for remote pulls - this way you can set # up access.py for everything at once without affecting local operations HGRCPATH = /etc/mercurial-server/remote-hgrc.d - diff -r 31d5c6236f71 -r f597eb3b5aaf src/init/hginit --- a/src/init/hginit Sun Dec 19 09:49:18 2010 +0000 +++ b/src/init/hginit Tue Sep 06 11:16:58 2011 +0100 @@ -14,4 +14,3 @@ cd repos/hgadmin hg init . cp $1/init/hgadmin-hgrc .hg/hgrc - diff -r 31d5c6236f71 -r f597eb3b5aaf src/mercurialserver/access.py --- a/src/mercurialserver/access.py Sun Dec 19 09:49:18 2010 +0000 +++ b/src/mercurialserver/access.py Tue Sep 06 11:16:58 2011 +0100 @@ -25,4 +25,3 @@ if not allow(ctx): raise mercurial.util.Abort(_('%s: access denied for changeset %s') % (__name__, mercurial.node.short(ctx.node()))) - diff -r 31d5c6236f71 -r f597eb3b5aaf src/mercurialserver/config.py --- a/src/mercurialserver/config.py Sun Dec 19 09:49:18 2010 +0000 +++ b/src/mercurialserver/config.py Tue Sep 06 11:16:58 2011 +0100 @@ -20,7 +20,7 @@ def _getPath(name): return os.path.expanduser(_getConf().get("paths", name)) -def _getPaths(name): +def _getPaths(name): return [os.path.expanduser(p) for p in _getConf().get("paths", name).split(":")] @@ -40,6 +40,17 @@ def getEnv(): return _getConf().items("env") +def _getdefault(section, option, default, f = lambda x: x): + conf = _getConf() + if conf.has_option(section, option): + return f(conf.get(section, option)) + else: + return default + +def getAllowedDots(): + return _getdefault("exceptions", "allowdots", [], + lambda s: s.split(":")) + # Work out where we are, don't use config. def initExe(): global _exePath @@ -49,4 +60,3 @@ def getExePath(): return _exePath - diff -r 31d5c6236f71 -r f597eb3b5aaf src/mercurialserver/refreshauth.py --- a/src/mercurialserver/refreshauth.py Sun Dec 19 09:49:18 2010 +0000 +++ b/src/mercurialserver/refreshauth.py Tue Sep 06 11:16:58 2011 +0100 @@ -39,7 +39,7 @@ if not goodkey.match(keyname): # Encode it for safe quoting keyname = "--base64 " + base64.b64encode(keyname) - p = subprocess.Popen(("ssh-keygen", "-i", "-f", ffn), + p = subprocess.Popen(("ssh-keygen", "-i", "-f", ffn), stdout=subprocess.PIPE, stderr=subprocess.PIPE) newkey = p.communicate()[0] if p.wait() == 0: @@ -57,7 +57,6 @@ akeys.close() os.chmod(akeyfile + "_new", stat.S_IRUSR) os.rename(akeyfile + "_new", akeyfile) - + def hook(ui, repo, hooktype, node=None, source=None, **kwargs): refreshAuth() - diff -r 31d5c6236f71 -r f597eb3b5aaf src/mercurialserver/ruleset.py --- a/src/mercurialserver/ruleset.py Sun Dec 19 09:49:18 2010 +0000 +++ b/src/mercurialserver/ruleset.py Tue Sep 06 11:16:58 2011 +0100 @@ -13,63 +13,56 @@ # ** means "match recursively" ie "ignore directories" return re.compile(p.replace("[^/]*[^/]*", ".*") + "$") -# Returns True for a definite match -# False for a definite non-match -# None where we can't be sure because a key is None +# Returns 1 for a definite match +# -1 for a definite non-match +# 0 where we can't be sure because a key is None +def rmatch(k, m, kw): + if k not in kw: + return -1 + kkw = kw[k] + if kkw is None: + return 0 + elif m.match(kkw) is None: + return -1 + else: + return 1 + def rule(pairs): matchers = [(k, globmatcher(v)) for k, v in pairs] def c(kw): - for k, m in matchers: - if k not in kw: - return False - kkw = kw[k] - if kkw is None: - return None - if m.match(kkw) is None: - return False - return True + return min(rmatch(k, m, kw) for k, m in matchers) return c class Ruleset(object): '''Class representing the rules in a rule file''' - + levels = ["init", "write", "read", "deny"] def __init__(self): self.rules = [] self.preset = {} - def add(self, action, conditions): - self.rules.append((action, conditions)) - def set(self, **kw): self.preset.update(kw) - + def get(self, k): return self.preset.get(k, None) - - def matchrules(self, kw): + + def allow(self, level, **kw): + levelindex = self.levels.index(level) d = self.preset.copy() d.update(kw) - res = set() for a, c in self.rules: m = c(d) - if m is None: - # "Maybe match" - add it and carry on - res.add(a) - elif m: - # Definite match - add it and stop - res.add(a) - break - return res - - def allow(self, level, **kw): - for a in self.matchrules(kw): - if a in self.levels: - if self.levels.index(a) <= self.levels.index(level): + if m == 1: + # Definite match - what it says goes + return a <= levelindex + elif m == 0: + # "Maybe match" - allow if it says yes, ignore if no + if a <= levelindex: return True return False - + def readfile(self, fn): f = open(fn) try: @@ -78,9 +71,14 @@ if len(l) == 0 or l.startswith("#"): continue l = l.split() - self.add(l[0], rule([c.split("=", 1) for c in l[1:]])) + # Unrecognized actions are off the high end + if l[0] in self.levels: + ix = self.levels.index(l[0]) + else: + ix = len(self.levels) + self.rules.append((ix, + rule([c.split("=", 1) for c in l[1:]]))) finally: f.close() rules = Ruleset() - diff -r 31d5c6236f71 -r f597eb3b5aaf src/mercurialserver/servelog.py --- a/src/mercurialserver/servelog.py Sun Dec 19 09:49:18 2010 +0000 +++ b/src/mercurialserver/servelog.py Tue Sep 06 11:16:58 2011 +0100 @@ -9,7 +9,13 @@ import os import time import fcntl -import json + +try: + import json + json.dumps +except ImportError: + import simplejson as json + from mercurialserver import ruleset, changes def hook(ui, repo, hooktype, node=None, source=None, **kwargs): @@ -25,13 +31,13 @@ fcntl.flock(log.fileno(), fcntl.LOCK_EX) log.seek(0, os.SEEK_END) # YAML log file format - log.write("- {0}\n".format(json.dumps(dict( + log.write("- %s\n" % json.dumps(dict( timestamp=time.strftime("%Y-%m-%d_%H:%M:%S Z", time.gmtime()), op=op, key=ruleset.rules.get('user'), ssh_connection=os.environ['SSH_CONNECTION'], nodes=[mercurial.node.hex(ctx.node()) for ctx in changes.changes(repo, node)], - )))) + ))) finally: log.close()