get up to date debian
authorPaul Crowley <paul@lshift.net>
Tue, 13 Oct 2009 15:30:03 +0100
branchdebian
changeset 115 731a72b742db
parent 99 e99262dfa950 (current diff)
parent 114 241475f6440c (diff)
child 116 d99f3169828a
get up to date
doc/PLAN
--- a/.hgtags	Thu May 28 10:43:30 2009 +0100
+++ b/.hgtags	Tue Oct 13 15:30:03 2009 +0100
@@ -1,1 +1,2 @@
 535502c18eaad098437e49adea1e26a68e4b6d75 release_0.5
+975fb921c3f3ffe7ccde5877f2954a5d1141bb14 release_0.6
--- a/README	Thu May 28 10:43:30 2009 +0100
+++ b/README	Tue Oct 13 15:30:03 2009 +0100
@@ -4,7 +4,7 @@
 you choose, identified by ssh keys, with easy key and access management
 based on hg.
 
-http://hg.opensource.lshift.net/mercurial-server/
+http://www.lshift.net/mercurial-server.html
 
 Copyright (C) 2008-2009 LShift Ltd.
 
@@ -40,7 +40,7 @@
 
 To give a user access to the repository, place their key in an
 appropriately-named subdirectory of "/etc/mercurial-server/keys" and run
-"/usr/local/lib/mercurial-server/refresh-auth". You can then control what
+"/usr/local/share/mercurial-server/refresh-auth". You can then control what
 access they have to what repositories by editing the control file
 "/etc/mercurial-server/access.conf", which can match the names of these keys
 against a glob pattern. 
@@ -75,7 +75,7 @@
 (ie the file is called something like
 "/etc/mercurial-server/keys/root/yourname/yourhostname") so that you can
 easily manage users who have a different key on each host they use. Then run
-"/usr/local/lib/mercurial-server/refresh-auth".
+"/usr/local/share/mercurial-server/refresh-auth".
 
 The repository is now ready to use, and you are now the sole user able to
 change and create repositories on this repository host.  
@@ -98,7 +98,7 @@
 "keys/users" subdirectory - these users will be able to read and write to any
 repository (except one - see below) but will not be able to create new
 repositories. As always, when you change "/etc/mercurial-server/keys" you need
-to re-run "/usr/local/lib/mercurial-server/refresh-auth".
+to re-run "/usr/local/share/mercurial-server/refresh-auth".
 
 LOGGING
 
@@ -151,4 +151,5 @@
 Thanks for reading this far. If you use mercurial-server, please tell me about
 it.
 
-Paul Crowley, 2009
+Paul Crowley, paul@lshift.net, 2009
+
--- a/doc/PLAN	Thu May 28 10:43:30 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-Plan: do away with shell scripts, and un-hard-wire the path /usr/local/lib/mercurial-server.
-
-Done:
-
-- move all modules into mercurial_server directory
-- make mercurial_server.paths module; a function sets a global based on sys.argv[0]
-- reduce hg-ssh-wrapper to nothing by moving all functionality into hg-ssh
-- same for refresh-auth
-- move most of do-refresh-auth into a module
-- give that module a hook, and call the hook instead of the exe
-- abolish refresh-auth shell script and rename do-refresh-auth to refresh-auth
-- replace env vars with Python globals
-- change refresh-auth to refer to hg-ssh directly, abolish hg-ssh-wrapper
-
-Todo:
-
-- add comment check and -f flag to refresh-auth
-- change install script to a Python script, abolishing hginit
-
-
--- a/doc/configuring-access	Thu May 28 10:43:30 2009 +0100
+++ b/doc/configuring-access	Tue Oct 13 15:30:03 2009 +0100
@@ -1,10 +1,10 @@
 ACCESS.CONF
 
 Out of the box, there are just two kinds of users: the ones with keys in
-"keys/root" and those in "keys/users". However, you can change this by editing
-"access.conf". There are two "access.conf" files, one in
-"/etc/mercurial-server" and one in "hgadmin"; the two are simply concatenated
-before being read.
+"keys/root" and those in "keys/users". However, you can change this by
+editing "access.conf". There are two "access.conf" files, one in
+"/etc/mercurial-server" and one in "hgadmin"; the two are simply
+concatenated before being read.
 
 Each line of access.conf has the following syntax:
 
@@ -23,13 +23,13 @@
     user=<globpattern> - user's key
     repo=<globpattern> - repo (as the user supplies it)
 
-The first rule in the file which has all its conditions satisfied is used to
-determine whether an action is allowed.
+The first rule in the file which has all its conditions satisfied is used
+to determine whether an action is allowed. If no rule is matched the
+request is denied.
 
-Paths cannot contain any special characters except "/"; glob patterns cannot
-contain any special characters except "/" and "*". "*" matches zero or more
-characters not including "/" while "**" matches zero or more characters
-including "/".
+"*" only matches one directory level, where "**" matches as many as you
+want. More precisely, "*" matches zero or more characters not including "/"
+while "**" matches zero or more characters including "/".
 
 Blank lines and lines that start with "#" are ignored.
 
@@ -40,8 +40,8 @@
     write user=users/**
 
 This means: keys in "root" can do anything; keys in "users" cannot create
-repositories, cannot even read the hgadmin repository, but can read and write
-any other repository; no other key has any access.
+repositories, cannot even read the hgadmin repository, but can read and
+write any other repository; no other key has any access.
 
 More advanced access configuration is covered in file-conditions.
 
--- a/doc/file-conditions	Thu May 28 10:43:30 2009 +0100
+++ b/doc/file-conditions	Tue Oct 13 15:30:03 2009 +0100
@@ -3,10 +3,11 @@
 Read configuring-access before you read this.
 
 mercurial-server supports file and branch conditions, which restrict an
-operation depending on what files it modifies and what branch the work is on.
-However, the way these conditions work is subtle and can be counterintuitive -
-if you want to keep things simple, stick to user and repo conditions, and then
-things are likely to work the way you would expect.
+operation depending on what files it modifies and what branch the work is
+on. However, the way these conditions work is subtle and can be
+counterintuitive - if you want to keep things simple, stick to user and
+repo conditions, and then things are likely to work the way you would
+expect.
 
 File and branch conditions are added to the conditions against which a rule
 matches, just like user and repo conditions; they have this form:
@@ -14,8 +15,8 @@
     file=<globpattern> - file in the repo
     branch=<globpattern> - name of the branch
 
-However, in order to understand what effect adding these conditions will have,
-it helps to understand how and when these rules are applied.
+However, in order to understand what effect adding these conditions will
+have, it helps to understand how and when these rules are applied.
 
 The rules file is used to make four decisions:
 
@@ -24,35 +25,35 @@
 - Whether to allow a changeset on a particular branch at all
 - Whether to allow a changeset to change a particular file
 
-When the first two of these decisions are being made, nothing is known about
-what files might be changed, and so all file conditions automatically succeed
-for the purpose of such decisions. This means that doing tricky things with
-file conditions can have counterintuitive consequences:
+When the first two of these decisions are being made, nothing is known
+about what files might be changed, and so all file conditions automatically
+succeed for the purpose of such decisions. This means that doing tricky
+things with file conditions can have counterintuitive consequences:
 
-- You cannot limit read access to a subset of a repository with a "read" rule
-and a file condition: any user who has access to a repository can read all of
-it and its full history. Such a rule can only have the effect of masking a
-later "write" rule, as in this example:
+- You cannot limit read access to a subset of a repository with a "read"
+rule and a file condition: any user who has access to a repository can read
+all of it and its full history. Such a rule can only have the effect of
+masking a later "write" rule, as in this example:
 
    read repo=specialrepo file=dontwritethis
    write repo=specialrepo
 
-allows all users to read specialrepo, and to write to all files *except* that
-any changeset which writes to "dontwritethis" will be rejected.
+allows all users to read specialrepo, and to write to all files *except*
+that any changeset which writes to "dontwritethis" will be rejected.
 
 - For similar reasons, don't give "init" rules file conditions.
 
-- Don't try to deny write access to a particular file on a particular branch -
-a developer can write to the file on another branch and then merge it in.
-Either deny all writes to the branch from that user, or allow them to write to
-all the files they can write to on any branch. In other words, something like
-this will have the intended effect:
+- Don't try to deny write access to a particular file on a particular
+branch - a developer can write to the file on another branch and then merge
+it in. Either deny all writes to the branch from that user, or allow them
+to write to all the files they can write to on any branch. In other words,
+something like this will have the intended effect:
 
   write user=docs/* branch=docs file=docs/*
 
-But something like this will not have the intended effect; it will effectively
-allow these users to write to any file on any branch, by writing it to "docs"
-first:
+But something like this will not have the intended effect; it will
+effectively allow these users to write to any file on any branch, by
+writing it to "docs" first:
 
   write user=docs/* branch=docs
   write user=docs/* file=docs/*
--- a/doc/how-it-works	Thu May 28 10:43:30 2009 +0100
+++ b/doc/how-it-works	Tue Oct 13 15:30:03 2009 +0100
@@ -1,22 +1,24 @@
 HOW IT WORKS
 
-When a developer attempts to connect to a repository via ssh, the SSH daemon
-searches for a match for that user's key in ~hg/.ssh/authorized_keys. If the
-developer is authorised to connect to the repository they will have an entry
-in this file. The entry includes a "command" prefix which specifies that the
-restricted shell "/usr/local/lib/mercurial-server/hg-ssh" should be used; this
-shell is passed an argument identifying the developer. The shell parses the
-command the developer is trying to execute, and consults a rules file to see
-if that developer is allowed to perform that action on that repository.
+When a developer attempts to connect to a repository via ssh, the SSH
+daemon searches for a match for that user's key in
+~hg/.ssh/authorized_keys. If the developer is authorised to connect to the
+repository they will have an entry in this file. The entry includes a
+"command" prefix which specifies that the restricted shell
+"/usr/local/share/mercurial-server/hg-ssh" should be used; this shell is
+passed an argument identifying the developer. The shell parses the command
+the developer is trying to execute, and consults a rules file to see if
+that developer is allowed to perform that action on that repository.
 
 The file ~hg/.ssh/authorized_keys is generated by "refresh-auth", which
-recurses through two directories of files containing SSH keys and generates an
-entry in authorized_keys for each one, using the name of the key file as the
-identifier for the developer. These keys will live in the "keys" subdirectory
-"/etc/mercurial-server" and the "keys" subdirectory of a repository called
-"hgadmin". A hook in this repository re-runs "refresh-auth" on the most recent
-version after every push.
+recurses through two directories of files containing SSH keys and generates
+an entry in authorized_keys for each one, using the name of the key file as
+the identifier for the developer. These keys will live in the "keys"
+subdirectory "/etc/mercurial-server" and the "keys" subdirectory of a
+repository called "hgadmin". A hook in this repository re-runs
+"refresh-auth" on the most recent version after every push.
 
-Finally, hook in an extension is run for each changeset that is remotely
-committed, which uses the rules file to determine whether to allow the
-changeset.
+When users try to commit new changesets, a hook is run which consults the
+rules file to decide whether to allow the changeset into the repository.
+This can depend not only on the user and the repository, but also the
+branch and files in the changeset.
--- a/doc/security	Thu May 28 10:43:30 2009 +0100
+++ b/doc/security	Tue Oct 13 15:30:03 2009 +0100
@@ -1,21 +1,21 @@
 SECURITY OF MERCURIAL-SERVER
 
-mercurial-server relies entirely on sshd to grant access to remote users. As a
-result, it runs no daemons, installs no setuid programs, and no part of it
-runs as root except the install process: all programs run as the user hg. And
-any attack on mercurial-server can only be started if the Bad Guys already
-have a public key in ~hg/.ssh/authorized_keys, otherwise sshd will bar the
-way. No matter what command the user tries to run on the remote system via
-ssh, mercurial-server is run. 
+mercurial-server relies entirely on sshd to grant access to remote users.
+As a result, it runs no daemons, installs no setuid programs, and no part
+of it runs as root except the install process: all programs run as the user
+hg. And any attack on mercurial-server can only be started if the Bad Guys
+already have a public key in ~hg/.ssh/authorized_keys, otherwise sshd will
+bar the way.
 
-It parses the command line the user asked for, and interprets and runs the
-corresponding hg operation itself if access is allowed, so users can only read
-and add to history within repositories; they cannot run any other hg command.
-In addition, every push and pull is logged with a datestamp, changeset ID and
-the key that performed the operation.
+No matter what command the user tries to run on the remote system via ssh,
+mercurial-server is run. It parses the command line the user asked for, and
+interprets and runs the corresponding hg operation itself if access is
+allowed, so users can only read and add to history within repositories;
+they cannot run any other hg command. In addition, every push and pull is
+logged with a datestamp, changeset ID and the key that performed the
+operation.
 
-However, while the first paragraph holds no matter what bugs mercurial-server
-contains, the second depends on the relevant code being correct; though the
-entire codebase is currently only about twice as long as this README,
-mercurial-server is a fairly new program and may harbour bugs. Backups are
-essential!
+However, while the first paragraph holds no matter what bugs
+mercurial-server contains, the second depends on the relevant code being
+correct; though the entire codebase is short, mercurial-server is a fairly
+new program and may harbour bugs. Backups are essential!
--- a/src/hg-ssh	Thu May 28 10:43:30 2009 +0100
+++ b/src/hg-ssh	Tue Oct 13 15:30:03 2009 +0100
@@ -33,45 +33,53 @@
 
 from mercurial import dispatch
 
-import sys, os
+import sys, os, os.path
+import base64
 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):
-    if path.endswith("/"):
-        path = path[:-1]
-    if not ruleset.goodpath(path):
-        fail("Disallowing path: %s" % path)
-    return path
+def checkDots(path):
+    head, tail = os.path.split(path)
+    if tail.startswith("."):
+        fail("paths cannot contain dot file components")
+    if head:
+        checkDots(head)
+
+def checkParents(path):
+    path = os.path.dirname(path)
+    if path == "":
+        return
+    if os.path.exists(path + "/.hg"):
+        fail("Cannot create repo under existing repo")
+    checkParents(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)
-
-#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)" 
-        % sys.argv)
+def getrepo(op, repo):
+    # First canonicalise, then check the string, then the rules
+    # and finally the filesystem.
+    repo = repo.rstrip("/")
+    if len(repo) == 0:
+        fail("path to repository seems to be empty")
+    if repo.startswith("/"):
+        fail("absolute paths are not supported")
+    checkDots(path)
+    ruleset.rules.set(repo=repo)
+    if not ruleset.rules.allow(op, branch=None, file=None):
+        fail("access denied")
+    checkParents(repo)
+    return repo
 
 paths.setExePath()
-ruleset.rules.set(user = sys.argv[1])
+
+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)
 
 # Use a different hgrc for remote pulls - this way you can set
 # up access.py for everything at once without affecting local operations
@@ -86,4 +94,22 @@
     if os.path.isfile(f):
         ruleset.rules.readfile(f)
 
-try_cmd(os.environ.get('SSH_ORIGINAL_COMMAND', '?'))
+cmd = os.environ.get('SSH_ORIGINAL_COMMAND', None)
+if cmd is None:
+    fail("direct logins on the hg account prohibited")
+elif 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])
+else:
+    fail("illegal command %r" % cmd)
+
--- a/src/init/hgadmin-hgrc	Thu May 28 10:43:30 2009 +0100
+++ b/src/init/hgadmin-hgrc	Tue Oct 13 15:30:03 2009 +0100
@@ -1,6 +1,9 @@
 # WARNING: when these hooks run they will entirely destroy and rewrite
 # ~/.ssh/authorized_keys
 
+[extensions]
+hgext.purge =
+
 [hooks]
 changegroup.aaaab_update = hg update -C default > /dev/null
 changegroup.aaaac_purge = hg purge --all > /dev/null
--- a/src/mercurialserver/refreshauth.py	Thu May 28 10:43:30 2009 +0100
+++ b/src/mercurialserver/refreshauth.py	Tue Oct 13 15:30:03 2009 +0100
@@ -5,12 +5,15 @@
 # file every time it is run
 # WARNING
 
-import sys
+import re
+import base64
 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,10 +41,9 @@
                     raise Exception("Inconsistent behaviour in os.walk, bailing")
                 #print "Processing file", ffn
                 keyname = ffn[len(kr):]
-                if not ruleset.goodpath(keyname):
-                    # ignore any path that contains dodgy characters
-                    #print "Ignoring file", ffn
-                    continue
+                if not goodkey.match(keyname):
+                    # Encode it for safe quoting
+                    keyname = "--base64 " + base64.b64encode(keyname)
                 p = subprocess.Popen(("ssh-keygen", "-i", "-f", ffn), 
                     stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                 newkey = p.communicate()[0]
--- a/src/mercurialserver/ruleset.py	Thu May 28 10:43:30 2009 +0100
+++ b/src/mercurialserver/ruleset.py	Tue Oct 13 15:30:03 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