diff -Nru mercurial-3.1.2/debian/changelog mercurial-3.1.2/debian/changelog --- mercurial-3.1.2/debian/changelog 2017-08-28 16:08:28.000000000 -0400 +++ mercurial-3.1.2/debian/changelog 2018-06-06 11:27:04.000000000 -0400 @@ -1,3 +1,15 @@ +mercurial (3.1.2-2+deb8u5) UNRELEASED; urgency=high + + * Non-maintainer upload by the Security Team. + * CVE-2017-9462: fix remote code execution by using --debugger as a + repository name (Closes: #861243) + * CVE-2017-17458: fix arbitrary code execution with malformed git + repositories + * CVE-2018-1000132: Incorrect Access Control vulnerability in Protocol + server that can result in Unauthorized data access (Closes: #892964) + + -- Antoine Beaupré Wed, 06 Jun 2018 11:27:04 -0400 + mercurial (3.1.2-2+deb8u4) jessie-security; urgency=medium * CVE-2017-1000115: path traversal via symlink diff -Nru mercurial-3.1.2/debian/patches/CVE-2017-17458-1-80d7dbda9294.patch mercurial-3.1.2/debian/patches/CVE-2017-17458-1-80d7dbda9294.patch --- mercurial-3.1.2/debian/patches/CVE-2017-17458-1-80d7dbda9294.patch 1969-12-31 19:00:00.000000000 -0500 +++ mercurial-3.1.2/debian/patches/CVE-2017-17458-1-80d7dbda9294.patch 2018-06-06 11:27:04.000000000 -0400 @@ -0,0 +1,118 @@ +diff -r f445b10dc7fb -r 80d7dbda9294 tests/test-audit-subrepo.t +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/tests/test-audit-subrepo.t Fri Nov 03 19:17:25 2017 +0900 +@@ -0,0 +1,114 @@ ++Test illegal name ++----------------- ++ ++on commit: ++ ++ $ hg init hgname ++ $ cd hgname ++ $ mkdir sub ++ $ hg init sub/.hg ++ $ echo 'sub/.hg = sub/.hg' >> .hgsub ++ $ hg ci -qAm 'add subrepo "sub/.hg"' ++ abort: path 'sub/.hg' is inside nested repo 'sub' ++ [255] ++ ++prepare tampered repo (including the commit above): ++ ++ $ hg import --bypass -qm 'add subrepo "sub/.hg"' - <<'EOF' ++ > diff --git a/.hgsub b/.hgsub ++ > new file mode 100644 ++ > --- /dev/null ++ > +++ b/.hgsub ++ > @@ -0,0 +1,1 @@ ++ > +sub/.hg = sub/.hg ++ > diff --git a/.hgsubstate b/.hgsubstate ++ > new file mode 100644 ++ > --- /dev/null ++ > +++ b/.hgsubstate ++ > @@ -0,0 +1,1 @@ ++ > +0000000000000000000000000000000000000000 sub/.hg ++ > EOF ++ $ cd .. ++ ++on clone (and update): ++ ++ $ hg clone -q hgname hgname2 ++ abort: path 'sub/.hg' is inside nested repo 'sub' ++ [255] ++ ++Test direct symlink traversal ++----------------------------- ++ ++#if symlink ++ ++on commit: ++ ++ $ mkdir hgsymdir ++ $ hg init hgsymdir/root ++ $ cd hgsymdir/root ++ $ ln -s ../out ++ $ hg ci -qAm 'add symlink "out"' ++ $ hg init ../out ++ $ echo 'out = out' >> .hgsub ++BROKEN: should fail ++ $ hg ci -qAm 'add subrepo "out"' ++ $ cd ../.. ++ ++on clone (and update): ++ ++ $ mkdir hgsymdir2 ++BROKEN: should fail to update ++ $ hg clone -q hgsymdir/root hgsymdir2/root ++ $ ls hgsymdir2 ++ out ++ root ++ ++#endif ++ ++Test indirect symlink traversal ++------------------------------- ++ ++#if symlink ++ ++on commit: ++ ++ $ mkdir hgsymin ++ $ hg init hgsymin/root ++ $ cd hgsymin/root ++ $ ln -s ../out ++ $ hg ci -qAm 'add symlink "out"' ++ $ mkdir ../out ++ $ hg init ../out/sub ++ $ echo 'out/sub = out/sub' >> .hgsub ++ $ hg ci -qAm 'add subrepo "out/sub"' ++ abort: path 'out/sub' traverses symbolic link 'out' ++ [255] ++ ++prepare tampered repo (including the commit above): ++ ++ $ hg import --bypass -qm 'add subrepo "out/sub"' - <<'EOF' ++ > diff --git a/.hgsub b/.hgsub ++ > new file mode 100644 ++ > --- /dev/null ++ > +++ b/.hgsub ++ > @@ -0,0 +1,1 @@ ++ > +out/sub = out/sub ++ > diff --git a/.hgsubstate b/.hgsubstate ++ > new file mode 100644 ++ > --- /dev/null ++ > +++ b/.hgsubstate ++ > @@ -0,0 +1,1 @@ ++ > +0000000000000000000000000000000000000000 out/sub ++ > EOF ++ $ cd ../.. ++ ++on clone (and update): ++ ++ $ mkdir hgsymin2 ++ $ hg clone -q hgsymin/root hgsymin2/root ++ abort: path 'out/sub' traverses symbolic link 'out' ++ [255] ++ $ ls hgsymin2 ++ root ++ ++#endif diff -Nru mercurial-3.1.2/debian/patches/CVE-2017-17458-2-071cbeba4212.patch mercurial-3.1.2/debian/patches/CVE-2017-17458-2-071cbeba4212.patch --- mercurial-3.1.2/debian/patches/CVE-2017-17458-2-071cbeba4212.patch 1969-12-31 19:00:00.000000000 -0500 +++ mercurial-3.1.2/debian/patches/CVE-2017-17458-2-071cbeba4212.patch 2018-06-06 11:27:04.000000000 -0400 @@ -0,0 +1,84 @@ + +# HG changeset patch +# User Yuya Nishihara +# Date 1509707570 -32400 +# Node ID 071cbeba421217d722a69a5d614ec934684d62d5 +# Parent 80d7dbda92940c49e0fd66230ae07cd526b3629c +subrepo: disallow symlink traversal across subrepo mount point (SEC) + +It wasn't easy to extend the pathauditor to check symlink traversal across +subrepos because pathauditor._checkfs() rejects a directory having ".hg" +directory. That's why I added the explicit islink() check. + +No idea if this patch is necessary after we've fixed the issue5730 by +splitting submerge() into planning and execution phases. + +Index: b/mercurial/subrepo.py +=================================================================== +--- a/mercurial/subrepo.py 2018-06-06 13:26:24.974553945 -0400 ++++ b/mercurial/subrepo.py 2018-06-06 13:26:59.022810987 -0400 +@@ -330,6 +330,12 @@ def _sanitize(ui, path, ignore): + "in '%s'\n") % dirname) + os.unlink(os.path.join(dirname, f)) + ++def _auditsubrepopath(repo, path): ++ # auditor doesn't check if the path itself is a symlink ++ pathutil.pathauditor(repo.root)(path) ++ if repo.wvfs.islink(path): ++ raise error.Abort(_("subrepo '%s' traverses symbolic link") % path) ++ + def subrepo(ctx, path): + """return instance of the right subrepo class for subrepo in path""" + # subrepo inherently violates our import layering rules +@@ -340,7 +346,7 @@ def subrepo(ctx, path): + import hg as h + hg = h + +- pathutil.pathauditor(ctx._repo.root)(path) ++ _auditsubrepopath(ctx._repo, path) + state = ctx.substate[path] + if state[2] not in types: + raise util.Abort(_('unknown subrepo type %s') % state[2]) +Index: b/tests/test-audit-subrepo.t +=================================================================== +--- a/tests/test-audit-subrepo.t 2018-06-06 13:26:24.974553945 -0400 ++++ b/tests/test-audit-subrepo.t 2018-06-06 13:26:24.974553945 -0400 +@@ -50,17 +50,35 @@ on commit: + $ hg ci -qAm 'add symlink "out"' + $ hg init ../out + $ echo 'out = out' >> .hgsub +-BROKEN: should fail + $ hg ci -qAm 'add subrepo "out"' ++ abort: subrepo 'out' traverses symbolic link ++ [255] ++ ++prepare tampered repo (including the commit above): ++ ++ $ hg import --bypass -qm 'add subrepo "out"' - <<'EOF' ++ > diff --git a/.hgsub b/.hgsub ++ > new file mode 100644 ++ > --- /dev/null ++ > +++ b/.hgsub ++ > @@ -0,0 +1,1 @@ ++ > +out = out ++ > diff --git a/.hgsubstate b/.hgsubstate ++ > new file mode 100644 ++ > --- /dev/null ++ > +++ b/.hgsubstate ++ > @@ -0,0 +1,1 @@ ++ > +0000000000000000000000000000000000000000 out ++ > EOF + $ cd ../.. + + on clone (and update): + + $ mkdir hgsymdir2 +-BROKEN: should fail to update + $ hg clone -q hgsymdir/root hgsymdir2/root ++ abort: subrepo 'out' traverses symbolic link ++ [255] + $ ls hgsymdir2 +- out + root + + #endif diff -Nru mercurial-3.1.2/debian/patches/CVE-2017-9462-77eaf9539499.patch mercurial-3.1.2/debian/patches/CVE-2017-9462-77eaf9539499.patch --- mercurial-3.1.2/debian/patches/CVE-2017-9462-77eaf9539499.patch 1969-12-31 19:00:00.000000000 -0500 +++ mercurial-3.1.2/debian/patches/CVE-2017-9462-77eaf9539499.patch 2018-06-06 11:27:04.000000000 -0400 @@ -0,0 +1,142 @@ + +# HG changeset patch +# User Augie Fackler +# Date 1492021435 25200 +# Node ID 77eaf9539499a1b8be259ffe7ada787d07857f80 +# Parent 68f263f52d2e3e2798b4f1e55cb665c6b043f93b +dispatch: protect against malicious 'hg serve --stdio' invocations (sec) + +Some shared-ssh installations assume that 'hg serve --stdio' is a safe +command to run for minimally trusted users. Unfortunately, the messy +implementation of argument parsing here meant that trying to access a +repo named '--debugger' would give the user a pdb prompt, thereby +sidestepping any hoped-for sandboxing. Serving repositories over HTTP(S) +is unaffected. + +We're not currently hardening any subcommands other than 'serve'. If +your service exposes other commands to users with arbitrary repository +names, it is imperative that you defend against repository names of +'--debugger' and anything starting with '--config'. + +The read-only mode of hg-ssh stopped working because it provided its hook +configuration to "hg serve --stdio" via --config parameter. This is banned for +security reasons now. This patch switches it to directly call ui.setconfig(). +If your custom hosting infrastructure relies on passing --config to +"hg serve --stdio", you'll need to find a different way to get that configuration +into Mercurial, either by using ui.setconfig() as hg-ssh does in this patch, +or by placing an hgrc file someplace where Mercurial will read it. + +mitrandir@fb.com provided some extra fixes for the dispatch code and +for hg-ssh in places that I overlooked. + +Index: b/contrib/hg-ssh +=================================================================== +--- a/contrib/hg-ssh 2018-06-06 14:19:01.786692281 -0400 ++++ b/contrib/hg-ssh 2018-06-06 14:19:01.782692259 -0400 +@@ -32,7 +32,7 @@ command="hg-ssh --read-only repos/*" + # enable importing on demand to reduce startup time + from mercurial import demandimport; demandimport.enable() + +-from mercurial import dispatch ++from mercurial import dispatch, ui as uimod + + import sys, os, shlex + +@@ -61,14 +61,15 @@ def main(): + repo = os.path.normpath(os.path.join(cwd, os.path.expanduser(path))) + if repo in allowed_paths: + cmd = ['-R', repo, 'serve', '--stdio'] ++ req = dispatch.request(cmd) + if readonly: +- cmd += [ +- '--config', +- 'hooks.prechangegroup.hg-ssh=python:__main__.rejectpush', +- '--config', +- 'hooks.prepushkey.hg-ssh=python:__main__.rejectpush' +- ] +- dispatch.dispatch(dispatch.request(cmd)) ++ if not req.ui: ++ req.ui = uimod.ui() ++ req.ui.setconfig('hooks', 'pretxnopen.hg-ssh', ++ 'python:__main__.rejectpush', 'hg-ssh') ++ req.ui.setconfig('hooks', 'prepushkey.hg-ssh', ++ 'python:__main__.rejectpush', 'hg-ssh') ++ dispatch.dispatch(req) + else: + sys.stderr.write('Illegal repository "%s"\n' % repo) + sys.exit(255) +Index: b/mercurial/dispatch.py +=================================================================== +--- a/mercurial/dispatch.py 2018-06-06 14:19:01.786692281 -0400 ++++ b/mercurial/dispatch.py 2018-06-06 14:39:21.029180998 -0400 +@@ -5,6 +5,8 @@ + # This software may be used and distributed according to the terms of the + # GNU General Public License version 2 or any later version. + ++import getopt ++ + from i18n import _ + import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re + import util, commands, hg, fancyopts, extensions, hook, error +@@ -86,6 +88,37 @@ def _runcatch(req): + except ValueError: + pass # happens if called in a thread + ++ realcmd = None ++ try: ++ cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {}) ++ cmd = cmdargs[0] ++ aliases, entry = cmdutil.findcmd(cmd, commands.table, False) ++ realcmd = aliases[0] ++ except (error.UnknownCommand, error.AmbiguousCommand, ++ IndexError, getopt.GetoptError): ++ # Don't handle this here. We know the command is ++ # invalid, but all we're worried about for now is that ++ # it's not a command that server operators expect to ++ # be safe to offer to users in a sandbox. ++ pass ++ if realcmd == 'serve' and '--stdio' in cmdargs: ++ # We want to constrain 'hg serve --stdio' instances pretty ++ # closely, as many shared-ssh access tools want to grant ++ # access to run *only* 'hg -R $repo serve --stdio'. We ++ # restrict to exactly that set of arguments, and prohibit ++ # any repo name that starts with '--' to prevent ++ # shenanigans wherein a user does something like pass ++ # --debugger or --config=ui.debugger=1 as a repo ++ # name. This used to actually run the debugger. ++ if (len(req.args) != 4 or ++ req.args[0] != '-R' or ++ req.args[1].startswith('--') or ++ req.args[2] != 'serve' or ++ req.args[3] != '--stdio'): ++ raise error.Abort( ++ _('potentially unsafe serve --stdio invocation: %r') % ++ (req.args,)) ++ + try: + try: + debugger = 'pdb' +Index: b/tests/test-ssh.t +=================================================================== +--- a/tests/test-ssh.t 2018-06-06 14:19:01.786692281 -0400 ++++ b/tests/test-ssh.t 2018-06-06 14:19:01.786692281 -0400 +@@ -295,6 +295,19 @@ Test (non-)escaping of remote paths with + abort: destination 'a repo' is not empty + [255] + ++Make sure hg is really paranoid in serve --stdio mode. It used to be ++possible to get a debugger REPL by specifying a repo named --debugger. ++ $ hg -R --debugger serve --stdio ++ abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio'] ++ [255] ++ $ hg -R --config=ui.debugger=yes serve --stdio ++ abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio'] ++ [255] ++Abbreviations of 'serve' also don't work, to avoid shenanigans. ++ $ hg -R narf serv --stdio ++ abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio'] ++ [255] ++ + Test hg-ssh using a helper script that will restore PYTHONPATH (which might + have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right + parameters: diff -Nru mercurial-3.1.2/debian/patches/CVE-2018-1000132-db527ae12671-86f9a022ccb8.patch mercurial-3.1.2/debian/patches/CVE-2018-1000132-db527ae12671-86f9a022ccb8.patch --- mercurial-3.1.2/debian/patches/CVE-2018-1000132-db527ae12671-86f9a022ccb8.patch 1969-12-31 19:00:00.000000000 -0500 +++ mercurial-3.1.2/debian/patches/CVE-2018-1000132-db527ae12671-86f9a022ccb8.patch 2018-06-06 11:27:04.000000000 -0400 @@ -0,0 +1,2004 @@ +changeset: 37517:db527ae12671 +branch: stable +parent: 35052:2f427b57bf90 +user: Gregory Szorc +date: Sat Jan 20 14:59:08 2018 -0800 +summary: tests: use argparse in get-with-headers.py + +Index: b/tests/get-with-headers.py +=================================================================== +--- a/tests/get-with-headers.py 2018-06-06 15:59:45.618857105 -0400 ++++ b/tests/get-with-headers.py 2018-06-06 15:59:45.614857083 -0400 +@@ -3,25 +3,48 @@ + """This does HTTP GET requests given a host:port and path and returns + a subset of the headers plus the body of the result.""" + +-import httplib, sys ++from __future__ import absolute_import ++ ++import argparse ++import json ++import os ++import sys ++ ++from mercurial import ( ++ util, ++) ++import httplib + + try: +- import msvcrt, os ++ import msvcrt + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY) + except ImportError: + pass + +-twice = False +-if '--twice' in sys.argv: +- sys.argv.remove('--twice') +- twice = True +-headeronly = False +-if '--headeronly' in sys.argv: +- sys.argv.remove('--headeronly') +- headeronly = True ++stdout = getattr(sys.stdout, 'buffer', sys.stdout) + +-reasons = {'Not modified': 'Not Modified'} # python 2.4 ++parser = argparse.ArgumentParser() ++parser.add_argument('--twice', action='store_true') ++parser.add_argument('--headeronly', action='store_true') ++parser.add_argument('--json', action='store_true') ++parser.add_argument('--hgproto') ++parser.add_argument('--requestheader', nargs='*', default=[], ++ help='Send an additional HTTP request header. Argument ' ++ 'value is
=') ++parser.add_argument('--bodyfile', ++ help='Write HTTP response body to a file') ++parser.add_argument('host') ++parser.add_argument('path') ++parser.add_argument('show', nargs='*') ++ ++args = parser.parse_args() ++ ++twice = args.twice ++headeronly = args.headeronly ++formatjson = args.json ++hgproto = args.hgproto ++requestheaders = args.requestheader + + tag = None + def request(host, path, show): +@@ -30,31 +53,58 @@ def request(host, path, show): + headers = {} + if tag: + headers['If-None-Match'] = tag ++ if hgproto: ++ headers['X-HgProto-1'] = hgproto ++ ++ for header in requestheaders: ++ key, value = header.split('=', 1) ++ headers[key] = value + + conn = httplib.HTTPConnection(host) + conn.request("GET", '/' + path, None, headers) + response = conn.getresponse() +- print response.status, reasons.get(response.reason, response.reason) ++ stdout.write(b'%d %s\n' % (response.status, ++ response.reason.encode('ascii'))) + if show[:1] == ['-']: + show = sorted(h for h, v in response.getheaders() + if h.lower() not in show) + for h in [h.lower() for h in show]: + if response.getheader(h, None) is not None: +- print "%s: %s" % (h, response.getheader(h)) ++ stdout.write(b"%s: %s\n" % (h.encode('ascii'), ++ response.getheader(h).encode('ascii'))) + if not headeronly: +- print +- if response.status != 500: +- data = response.read() +- sys.stdout.write(data) ++ stdout.write(b'\n') ++ data = response.read() ++ ++ if args.bodyfile: ++ bodyfh = open(args.bodyfile, 'wb') ++ else: ++ bodyfh = stdout ++ ++ # Pretty print JSON. This also has the beneficial side-effect ++ # of verifying emitted JSON is well-formed. ++ if formatjson: ++ # json.dumps() will print trailing newlines. Eliminate them ++ # to make tests easier to write. ++ data = json.loads(data) ++ lines = json.dumps(data, sort_keys=True, indent=2).splitlines() ++ for line in lines: ++ bodyfh.write(line.rstrip()) ++ bodyfh.write(b'\n') ++ else: ++ bodyfh.write(data) ++ ++ if args.bodyfile: ++ bodyfh.close() + +- if twice and response.getheader('ETag', None): +- tag = response.getheader('ETag') ++ if twice and response.getheader('ETag', None): ++ tag = response.getheader('ETag') + + return response.status + +-status = request(sys.argv[1], sys.argv[2], sys.argv[3:]) ++status = request(args.host, args.path, args.show) + if twice: +- status = request(sys.argv[1], sys.argv[2], sys.argv[3:]) ++ status = request(args.host, args.path, args.show) + + if 200 <= status <= 305: + sys.exit(0) +Index: b/tests/test-push-http.t +=================================================================== +--- a/tests/test-push-http.t 2018-06-06 15:59:45.618857105 -0400 ++++ b/tests/test-push-http.t 2018-06-06 15:59:45.614857083 -0400 +@@ -133,27 +133,3 @@ expect phase change success + [1] + $ hg rollback + repository tip rolled back to revision 0 (undo serve) +- +-expect authorization error: all users denied +- +- $ echo '[web]' > .hg/hgrc +- $ echo 'push_ssl = false' >> .hg/hgrc +- $ echo 'deny_push = *' >> .hg/hgrc +- $ req +- pushing to http://localhost:$HGPORT/ +- searching for changes +- abort: authorization failed +- % serve errors +- [255] +- +-expect authorization error: some users denied, users must be authenticated +- +- $ echo 'deny_push = unperson' >> .hg/hgrc +- $ req +- pushing to http://localhost:$HGPORT/ +- searching for changes +- abort: authorization failed +- % serve errors +- [255] +- +- $ cd .. +Index: b/hgext/largefiles/uisetup.py +=================================================================== +--- a/hgext/largefiles/uisetup.py 2018-06-06 15:59:45.618857105 -0400 ++++ b/hgext/largefiles/uisetup.py 2018-06-06 15:59:45.614857083 -0400 +@@ -11,7 +11,7 @@ + from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \ + httppeer, merge, scmutil, sshpeer, wireproto, revset, subrepo + from mercurial.i18n import _ +-from mercurial.hgweb import hgweb_mod, webcommands ++from mercurial.hgweb import webcommands + + import overrides + import proto +@@ -136,9 +136,10 @@ def uisetup(ui): + + # make putlfile behave the same as push and {get,stat}lfile behave + # the same as pull w.r.t. permissions checks +- hgweb_mod.perms['putlfile'] = 'push' +- hgweb_mod.perms['getlfile'] = 'pull' +- hgweb_mod.perms['statlfile'] = 'pull' ++ wireproto.permissions['putlfile'] = 'push' ++ wireproto.permissions['getlfile'] = 'pull' ++ wireproto.permissions['statlfile'] = 'pull' ++ wireproto.permissions['lheads'] = 'pull' + + extensions.wrapfunction(webcommands, 'decodepath', overrides.decodepath) + +Index: b/mercurial/hgweb/hgweb_mod.py +=================================================================== +--- a/mercurial/hgweb/hgweb_mod.py 2018-06-06 15:59:45.618857105 -0400 ++++ b/mercurial/hgweb/hgweb_mod.py 2018-06-06 15:59:45.614857083 -0400 +@@ -7,7 +7,7 @@ + # GNU General Public License version 2 or any later version. + + import os, re +-from mercurial import ui, hg, hook, error, encoding, templater, util, repoview ++from mercurial import ui, hg, hook, error, encoding, templater, util, repoview, wireproto + from mercurial.templatefilters import websub + from mercurial.i18n import _ + from common import get_stat, ErrorResponse, permhooks, caching +@@ -16,15 +16,8 @@ from common import HTTP_NOT_FOUND, HTTP_ + from request import wsgirequest + import webcommands, protocol, webutil + +-perms = { +- 'changegroup': 'pull', +- 'changegroupsubset': 'pull', +- 'getbundle': 'pull', +- 'stream_out': 'pull', +- 'listkeys': 'pull', +- 'unbundle': 'push', +- 'pushkey': 'push', +-} ++# Aliased for API compatibility. ++perms = wireproto.permissions + + def makebreadcrumb(url, prefix=''): + '''Return a 'URL breadcrumb' list +@@ -173,8 +166,13 @@ class hgweb(object): + try: + if query: + raise ErrorResponse(HTTP_NOT_FOUND) +- if cmd in perms: +- self.check_perm(req, perms[cmd]) ++ ++ req.checkperm = lambda op: self.check_perm(req, op) ++ # Assume commands with no defined permissions are writes / ++ # for pushes. This is the safest from a security perspective ++ # because it doesn't allow commands with undefined semantics ++ # from bypassing permissions checks. ++ req.checkperm(perms.get(cmd, 'push')) + return protocol.call(self.repo, req, cmd) + except ErrorResponse, inst: + # A client that sends unbundle without 100-continue will +Index: b/mercurial/wireproto.py +=================================================================== +--- a/mercurial/wireproto.py 2018-06-06 15:59:45.618857105 -0400 ++++ b/mercurial/wireproto.py 2018-06-06 15:59:45.614857083 -0400 +@@ -514,6 +514,11 @@ def options(cmd, keys, others): + # list of commands + commands = {} + ++# Maps wire protocol name to operation type. This is used for permissions ++# checking. All defined @wireiprotocommand should have an entry in this ++# dict. ++permissions = {} ++ + def wireprotocommand(name, args=''): + """decorator for wire protocol command""" + def register(func): +@@ -521,6 +526,8 @@ def wireprotocommand(name, args=''): + return func + return register + ++# TODO define a more appropriate permissions type to use for this. ++permissions['batch'] = 'pull' + @wireprotocommand('batch', 'cmds *') + def batch(repo, proto, cmds, others): + repo = repo.filtered("served") +@@ -533,6 +540,17 @@ def batch(repo, proto, cmds, others): + n, v = a.split('=') + vals[n] = unescapearg(v) + func, spec = commands[op] ++ ++ # If the protocol supports permissions checking, perform that ++ # checking on each batched command. ++ # TODO formalize permission checking as part of protocol interface. ++ if util.safehasattr(proto, 'checkperm'): ++ # Assume commands with no defined permissions are writes / for ++ # pushes. This is the safest from a security perspective because ++ # it doesn't allow commands with undefined semantics from ++ # bypassing permissions checks. ++ proto.checkperm(permissions.get(op, 'push')) ++ + if spec: + keys = spec.split() + data = {} +@@ -553,6 +571,7 @@ def batch(repo, proto, cmds, others): + res.append(escapearg(result)) + return ';'.join(res) + ++permissions['between'] = 'pull' + @wireprotocommand('between', 'pairs') + def between(repo, proto, pairs): + pairs = [decodelist(p, '-') for p in pairs.split(" ")] +@@ -561,6 +580,7 @@ def between(repo, proto, pairs): + r.append(encodelist(b) + "\n") + return "".join(r) + ++permissions['branchmap'] = 'pull' + @wireprotocommand('branchmap') + def branchmap(repo, proto): + branchmap = repo.branchmap() +@@ -571,6 +591,7 @@ def branchmap(repo, proto): + heads.append('%s %s' % (branchname, branchnodes)) + return '\n'.join(heads) + ++permissions['branches'] = 'pull' + @wireprotocommand('branches', 'nodes') + def branches(repo, proto, nodes): + nodes = decodelist(nodes) +@@ -614,16 +635,19 @@ def _capabilities(repo, proto): + + # If you are writing an extension and consider wrapping this function. Wrap + # `_capabilities` instead. ++permissions['capabilities'] = 'pull' + @wireprotocommand('capabilities') + def capabilities(repo, proto): + return ' '.join(_capabilities(repo, proto)) + ++permissions['changegroup'] = 'pull' + @wireprotocommand('changegroup', 'roots') + def changegroup(repo, proto, roots): + nodes = decodelist(roots) + cg = changegroupmod.changegroup(repo, nodes, 'serve') + return streamres(proto.groupchunks(cg)) + ++permissions['changegroupsubset'] = 'pull' + @wireprotocommand('changegroupsubset', 'bases heads') + def changegroupsubset(repo, proto, bases, heads): + bases = decodelist(bases) +@@ -631,6 +655,7 @@ def changegroupsubset(repo, proto, bases + cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve') + return streamres(proto.groupchunks(cg)) + ++permissions['debugwireargs'] = 'pull' + @wireprotocommand('debugwireargs', 'one two *') + def debugwireargs(repo, proto, one, two, others): + # only accept optional args from the known set +@@ -643,6 +668,7 @@ def debugwireargs(repo, proto, one, two, + # ensure such options are properly processed in exchange.getbundle. + gboptslist = ['heads', 'common', 'bundlecaps'] + ++permissions['getbundle'] = 'pull' + @wireprotocommand('getbundle', '*') + def getbundle(repo, proto, others): + opts = options('getbundle', gboptsmap.keys(), others) +@@ -658,11 +684,13 @@ def getbundle(repo, proto, others): + cg = exchange.getbundle(repo, 'serve', **opts) + return streamres(proto.groupchunks(cg)) + ++permissions['heads'] = 'pull' + @wireprotocommand('heads') + def heads(repo, proto): + h = repo.heads() + return encodelist(h) + "\n" + ++permissions['hello'] = 'pull' + @wireprotocommand('hello') + def hello(repo, proto): + '''the hello command returns a set of lines describing various +@@ -674,11 +702,13 @@ def hello(repo, proto): + ''' + return "capabilities: %s\n" % (capabilities(repo, proto)) + ++permissions['listkeys'] = 'pull' + @wireprotocommand('listkeys', 'namespace') + def listkeys(repo, proto, namespace): + d = repo.listkeys(encoding.tolocal(namespace)).items() + return pushkeymod.encodekeys(d) + ++permissions['lookup'] = 'pull' + @wireprotocommand('lookup', 'key') + def lookup(repo, proto, key): + try: +@@ -691,10 +721,12 @@ def lookup(repo, proto, key): + success = 0 + return "%s %s\n" % (success, r) + ++permissions['known'] = 'pull' + @wireprotocommand('known', 'nodes *') + def known(repo, proto, nodes, others): + return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes))) + ++permissions['pushkey'] = 'push' + @wireprotocommand('pushkey', 'namespace key old new') + def pushkey(repo, proto, namespace, key, old, new): + # compatibility with pre-1.8 clients which were accidentally +@@ -734,6 +766,7 @@ def _walkstreamfiles(repo): + # this is it's own function so extensions can override it + return repo.store.walk() + ++permissions['stream_out'] = 'pull' + @wireprotocommand('stream_out') + def stream(repo, proto): + '''If the server supports streaming clone, it advertises the "stream" +@@ -801,6 +834,7 @@ def stream(repo, proto): + + return streamres(streamer(repo, entries, total_bytes)) + ++permissions['unbundle'] = 'push' + @wireprotocommand('unbundle', 'heads') + def unbundle(repo, proto, heads): + their_heads = decodelist(heads) +Index: b/tests/test-http.t +=================================================================== +--- a/tests/test-http.t 2018-06-06 15:59:45.618857105 -0400 ++++ b/tests/test-http.t 2018-06-06 15:59:45.614857083 -0400 +@@ -216,40 +216,35 @@ test http authentication + $ hg rollback -q + + $ cut -c38- ../access.log ++ "GET /?cmd=capabilities HTTP/1.1" 401 - ++ "GET /?cmd=capabilities HTTP/1.1" 401 - ++ "GET /?cmd=capabilities HTTP/1.1" 401 - + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip +- "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces +- "GET /?cmd=capabilities HTTP/1.1" 200 - +- "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip +- "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces +- "GET /?cmd=capabilities HTTP/1.1" 200 - +- "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip +- "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks ++ "GET /?cmd=capabilities HTTP/1.1" 401 - + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip +- "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks +- "GET /?cmd=capabilities HTTP/1.1" 200 - ++ "GET /?cmd=capabilities HTTP/1.1" 401 - + "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip +- "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks ++ "GET /?cmd=capabilities HTTP/1.1" 401 - + "GET /?cmd=capabilities HTTP/1.1" 200 - +- "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip +- "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces +- "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces +- "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks +- "GET /?cmd=capabilities HTTP/1.1" 200 - +- "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip +- "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces +- "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces +- "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks ++ "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob) ++ "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob) ++ "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob) ++ "GET /?cmd=capabilities HTTP/1.1" 401 - ++ "GET /?cmd=capabilities HTTP/1.1" 200 - ++ "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob) ++ "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob) ++ "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob) ++ "GET /?cmd=capabilities HTTP/1.1" 401 - + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=branchmap HTTP/1.1" 200 - +- "GET /?cmd=stream_out HTTP/1.1" 401 - + "GET /?cmd=stream_out HTTP/1.1" 200 - + "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks + "GET /?cmd=capabilities HTTP/1.1" 200 - +@@ -259,6 +254,7 @@ test http authentication + "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip + "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces + "GET /?cmd=listkeys HTTP/1.1" 403 - x-hgarg-1:namespace=namespaces ++ "GET /?cmd=capabilities HTTP/1.1" 401 - + "GET /?cmd=capabilities HTTP/1.1" 200 - + "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 + "GET /?cmd=branchmap HTTP/1.1" 200 - +Index: b/tests/test-pull-http.t +=================================================================== +--- a/tests/test-pull-http.t 2018-06-06 15:59:45.618857105 -0400 ++++ b/tests/test-pull-http.t 2018-06-06 15:59:45.614857083 -0400 +@@ -37,7 +37,6 @@ expect error, cloning not allowed + $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log + $ cat hg.pid >> $DAEMON_PIDS + $ hg clone http://localhost:$HGPORT/ test4 +- requesting all changes + abort: authorization failed + [255] + $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS +Index: b/mercurial/hgweb/protocol.py +=================================================================== +--- a/mercurial/hgweb/protocol.py 2018-06-06 15:59:45.618857105 -0400 ++++ b/mercurial/hgweb/protocol.py 2018-06-06 15:59:45.614857083 -0400 +@@ -17,6 +17,7 @@ class webproto(wireproto.abstractserverp + self.req = req + self.response = '' + self.ui = ui ++ self.checkperm = req.checkperm + def getargs(self, args): + knownargs = self._args() + data = {} +Index: b/tests/run-tests.py +=================================================================== +--- a/tests/run-tests.py 2018-06-06 15:59:45.618857105 -0400 ++++ b/tests/run-tests.py 2018-06-06 15:59:45.614857083 -0400 +@@ -614,6 +614,7 @@ class Test(unittest.TestCase): + env["HGPORT"] = str(self._startport) + env["HGPORT1"] = str(self._startport + 1) + env["HGPORT2"] = str(self._startport + 2) ++ env["LOCALIP"] = "127.0.0.1" + env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc') + env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids') + env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"' +--- /dev/null 2018-06-03 11:20:16.376689350 -0400 ++++ mercurial-2.2.2/tests/test-http-permissions.t 2018-06-06 11:17:21.000000000 -0400 +@@ -0,0 +1,1491 @@ ++#require killdaemons ++ ++ $ cat > fakeremoteuser.py << EOF ++ > import os ++ > from mercurial.hgweb import hgweb_mod ++ > from mercurial import wireproto ++ > class testenvhgweb(hgweb_mod.hgweb): ++ > def __call__(self, env, respond): ++ > # Allow REMOTE_USER to define authenticated user. ++ > if r'REMOTE_USER' in os.environ: ++ > env[r'REMOTE_USER'] = os.environ[r'REMOTE_USER'] ++ > # Allow REQUEST_METHOD to override HTTP method ++ > if r'REQUEST_METHOD' in os.environ: ++ > env[r'REQUEST_METHOD'] = os.environ[r'REQUEST_METHOD'] ++ > return super(testenvhgweb, self).__call__(env, respond) ++ > hgweb_mod.hgweb = testenvhgweb ++ > ++ > @wireproto.wireprotocommand('customreadnoperm') ++ > def customread(repo, proto): ++ > return b'read-only command no defined permissions\n' ++ > @wireproto.wireprotocommand('customwritenoperm') ++ > def customwritenoperm(repo, proto): ++ > return b'write command no defined permissions\n' ++ > wireproto.permissions['customreadwithperm'] = 'pull' ++ > @wireproto.wireprotocommand('customreadwithperm') ++ > def customreadwithperm(repo, proto): ++ > return b'read-only command w/ defined permissions\n' ++ > wireproto.permissions['customwritewithperm'] = 'push' ++ > @wireproto.wireprotocommand('customwritewithperm') ++ > def customwritewithperm(repo, proto): ++ > return b'write command w/ defined permissions\n' ++ > EOF ++ ++ $ cat >> $HGRCPATH << EOF ++ > [extensions] ++ > fakeremoteuser = $TESTTMP/fakeremoteuser.py ++ > EOF ++ ++ $ hg init test ++ $ cd test ++ $ echo a > a ++ $ hg ci -Ama ++ adding a ++ $ cd .. ++ $ hg clone test test2 ++ updating to branch default ++ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved ++ $ cd test2 ++ $ echo a >> a ++ $ hg ci -mb ++ $ hg book bm -r 0 ++ $ cd ../test ++ ++web.deny_read=* prevents access to wire protocol for all users ++ ++ $ cat > .hg/hgrc < [web] ++ > deny_read = * ++ > EOF ++ ++ $ hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=capabilities' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=stream_out' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadnoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadwithperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ hg --cwd ../test2 pull http://localhost:$HGPORT/ ++ abort: authorization failed ++ [255] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.deny_read=* with REMOTE_USER set still locks out clients ++ ++ $ REMOTE_USER=authed_user hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=capabilities' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=stream_out' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadnoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadwithperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ hg --cwd ../test2 pull http://localhost:$HGPORT/ ++ abort: authorization failed ++ [255] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.deny_read= denies access to unauthenticated user ++ ++ $ cat > .hg/hgrc < [web] ++ > deny_read = baduser1,baduser2 ++ > EOF ++ ++ $ hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadnoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadwithperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ hg --cwd ../test2 pull http://localhost:$HGPORT/ ++ abort: authorization failed ++ [255] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.deny_read= denies access to users in deny list ++ ++ $ REMOTE_USER=baduser2 hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadnoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadwithperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ hg --cwd ../test2 pull http://localhost:$HGPORT/ ++ abort: authorization failed ++ [255] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.deny_read= allows access to authenticated users not in list ++ ++ $ REMOTE_USER=gooduser hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases' ++ 200 Script output follows ++ ++ cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 1 ++ publishing True (no-eol) ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases' ++ 200 Script output follows ++ ++ cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 1 ++ publishing True (no-eol) ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadnoperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadwithperm' ++ 200 Script output follows ++ ++ read-only command w/ defined permissions ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ hg --cwd ../test2 pull http://localhost:$HGPORT/ ++ pulling from http://localhost:$HGPORT/ ++ searching for changes ++ no changes found ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.allow_read=* allows reads for unauthenticated users ++ ++ $ cat > .hg/hgrc < [web] ++ > allow_read = * ++ > EOF ++ ++ $ hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases' ++ 200 Script output follows ++ ++ cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 1 ++ publishing True (no-eol) ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases' ++ 200 Script output follows ++ ++ cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 1 ++ publishing True (no-eol) ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadnoperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadwithperm' ++ 200 Script output follows ++ ++ read-only command w/ defined permissions ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ hg --cwd ../test2 pull http://localhost:$HGPORT/ ++ pulling from http://localhost:$HGPORT/ ++ searching for changes ++ no changes found ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.allow_read=* allows read for authenticated user ++ ++ $ REMOTE_USER=authed_user hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases' ++ 200 Script output follows ++ ++ cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 1 ++ publishing True (no-eol) ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases' ++ 200 Script output follows ++ ++ cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 1 ++ publishing True (no-eol) ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadnoperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadwithperm' ++ 200 Script output follows ++ ++ read-only command w/ defined permissions ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ hg --cwd ../test2 pull http://localhost:$HGPORT/ ++ pulling from http://localhost:$HGPORT/ ++ searching for changes ++ no changes found ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.allow_read= does not allow unauthenticated users to read ++ ++ $ cat > .hg/hgrc < [web] ++ > allow_read = gooduser ++ > EOF ++ ++ $ hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadnoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadwithperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ hg --cwd ../test2 pull http://localhost:$HGPORT/ ++ abort: authorization failed ++ [255] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.allow_read= does not allow user not in list to read ++ ++ $ REMOTE_USER=baduser hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadnoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadwithperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ hg --cwd ../test2 pull http://localhost:$HGPORT/ ++ abort: authorization failed ++ [255] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.allow_read= allows read from user in list ++ ++ $ REMOTE_USER=gooduser hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases' ++ 200 Script output follows ++ ++ cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 1 ++ publishing True (no-eol) ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases' ++ 200 Script output follows ++ ++ cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 1 ++ publishing True (no-eol) ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadnoperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadwithperm' ++ 200 Script output follows ++ ++ read-only command w/ defined permissions ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ hg --cwd ../test2 pull http://localhost:$HGPORT/ ++ pulling from http://localhost:$HGPORT/ ++ searching for changes ++ no changes found ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.deny_read takes precedence over web.allow_read ++ ++ $ cat > .hg/hgrc < [web] ++ > allow_read = baduser ++ > deny_read = baduser ++ > EOF ++ ++ $ REMOTE_USER=baduser hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadnoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadwithperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ hg --cwd ../test2 pull http://localhost:$HGPORT/ ++ abort: authorization failed ++ [255] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.allowpull=false denies read access to repo ++ ++ $ cat > .hg/hgrc < [web] ++ > allowpull = false ++ > EOF ++ ++ $ hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=capabilities' ++ 401 pull not authorized ++ ++ 0 ++ pull not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases' ++ 401 pull not authorized ++ ++ 0 ++ pull not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases' ++ 401 pull not authorized ++ ++ 0 ++ pull not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadnoperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadwithperm' ++ 401 pull not authorized ++ ++ 0 ++ pull not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ hg --cwd ../test2 pull http://localhost:$HGPORT/ ++ abort: authorization failed ++ [255] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++Attempting a write command with HTTP GET fails ++ ++ $ cat > .hg/hgrc < EOF ++ ++ $ REQUEST_METHOD=GET hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ hg bookmarks ++ no bookmarks set ++ $ hg bookmark -d bm ++ abort: bookmark 'bm' does not exist ++ [255] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++Attempting a write command with an unknown HTTP verb fails ++ ++ $ REQUEST_METHOD=someverb hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ hg bookmarks ++ no bookmarks set ++ $ hg bookmark -d bm ++ abort: bookmark 'bm' does not exist ++ [255] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 405 push requires POST request ++ ++ 0 ++ push requires POST request ++ [1] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++Pushing on a plaintext channel is disabled by default ++ ++ $ cat > .hg/hgrc < EOF ++ ++ $ REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 200 ssl required ++ ++ 0 ++ ssl required ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 200 ssl required ++ ++ 0 ++ ssl required ++ ++ $ hg bookmarks ++ no bookmarks set ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 200 ssl required ++ ++ 0 ++ ssl required ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 200 ssl required ++ ++ 0 ++ ssl required ++ ++Reset server to remove REQUEST_METHOD hack to test hg client ++ ++ $ "$TESTDIR/killdaemons.py" ++ $ hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ no changes found ++ exporting bookmark bm ++ remote: ssl required ++ updating bookmark bm failed! ++ [1] ++ ++ $ hg --cwd ../test2 push http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ remote: ssl required ++ remote: ssl required ++ updating cb9a9f314b8b to public failed! ++ [1] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.deny_push=* denies pushing to unauthenticated users ++ ++ $ cat > .hg/hgrc < [web] ++ > push_ssl = false ++ > deny_push = * ++ > EOF ++ ++ $ REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ hg bookmarks ++ no bookmarks set ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++Reset server to remove REQUEST_METHOD hack to test hg client ++ ++ $ "$TESTDIR/killdaemons.py" ++ $ hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ no changes found ++ exporting bookmark bm ++ abort: authorization failed ++ [255] ++ ++ $ hg --cwd ../test2 push http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ abort: authorization failed ++ [255] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.deny_push=* denies pushing to authenticated users ++ ++ $ REMOTE_USER=someuser REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ hg bookmarks ++ no bookmarks set ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++Reset server to remove REQUEST_METHOD hack to test hg client ++ ++ $ "$TESTDIR/killdaemons.py" ++ $ REMOTE_USER=someuser hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ no changes found ++ exporting bookmark bm ++ abort: authorization failed ++ [255] ++ ++ $ hg --cwd ../test2 push http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ abort: authorization failed ++ [255] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.deny_push= denies pushing to user in list ++ ++ $ cat > .hg/hgrc < [web] ++ > push_ssl = false ++ > deny_push = baduser ++ > EOF ++ ++ $ REMOTE_USER=baduser REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ hg bookmarks ++ no bookmarks set ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++Reset server to remove REQUEST_METHOD hack to test hg client ++ ++ $ "$TESTDIR/killdaemons.py" ++ $ REMOTE_USER=baduser hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ no changes found ++ exporting bookmark bm ++ abort: authorization failed ++ [255] ++ ++ $ hg --cwd ../test2 push http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ abort: authorization failed ++ [255] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.deny_push= denies pushing to user not in list because allow_push isn't set ++ ++ $ REMOTE_USER=gooduser REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ hg bookmarks ++ no bookmarks set ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++Reset server to remove REQUEST_METHOD hack to test hg client ++ ++ $ "$TESTDIR/killdaemons.py" ++ $ REMOTE_USER=gooduser hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ no changes found ++ exporting bookmark bm ++ abort: authorization failed ++ [255] ++ ++ $ hg --cwd ../test2 push http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ abort: authorization failed ++ [255] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.allow_push=* allows pushes from unauthenticated users ++ ++ $ cat > .hg/hgrc < [web] ++ > push_ssl = false ++ > allow_push = * ++ > EOF ++ ++ $ REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 200 Script output follows ++ ++ 1 ++ ++ $ hg bookmarks ++ bm 0:cb9a9f314b8b ++ $ hg book -d bm ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 200 Script output follows ++ ++ write command no defined permissions ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 200 Script output follows ++ ++ write command w/ defined permissions ++ ++Reset server to remove REQUEST_METHOD hack to test hg client ++ ++ $ "$TESTDIR/killdaemons.py" ++ $ hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ no changes found ++ exporting bookmark bm ++ [1] ++ ++ $ hg book -d bm ++ ++ $ hg --cwd ../test2 push http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ remote: adding changesets ++ remote: adding manifests ++ remote: adding file changes ++ remote: added 1 changesets with 1 changes to 1 files ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.allow_push=* allows pushes from authenticated users ++ ++ $ REMOTE_USER=someuser REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 200 Script output follows ++ ++ 1 ++ ++ $ hg bookmarks ++ bm 0:cb9a9f314b8b ++ $ hg book -d bm ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 200 Script output follows ++ ++ write command no defined permissions ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 200 Script output follows ++ ++ write command w/ defined permissions ++ ++Reset server to remove REQUEST_METHOD hack to test hg client ++ ++ $ "$TESTDIR/killdaemons.py" ++ $ cd .. ++ $ rm -rf test2 ++ $ hg clone test test2 ++ updating to branch default ++ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved ++ $ cd test2 ++ $ echo a >> a ++ $ hg ci -mb ++ $ hg book bm -r 0 ++ $ cd ../test ++ $ REMOTE_USER=someuser hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ no changes found ++ exporting bookmark bm ++ [1] ++ ++ $ hg book -d bm ++ ++ $ hg --cwd ../test2 push http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ remote: adding changesets ++ remote: adding manifests ++ remote: adding file changes ++ remote: added 1 changesets with 1 changes to 1 files ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.allow_push= denies push to user not in list ++ ++ $ cat > .hg/hgrc < [web] ++ > push_ssl = false ++ > allow_push = gooduser ++ > EOF ++ ++ $ REMOTE_USER=baduser REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ hg bookmarks ++ no bookmarks set ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++Reset server to remove REQUEST_METHOD hack to test hg client ++ ++ $ "$TESTDIR/killdaemons.py" ++ $ cd .. ++ $ rm -rf test2 ++ $ hg clone test test2 ++ updating to branch default ++ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved ++ $ cd test2 ++ $ echo a >> a ++ $ hg ci -mb ++ $ hg book bm -r 0 ++ $ cd ../test ++ $ REMOTE_USER=baduser hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ no changes found ++ exporting bookmark bm ++ abort: authorization failed ++ [255] ++ ++ $ hg --cwd ../test2 push http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ abort: authorization failed ++ [255] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.allow_push= allows push from user in list ++ ++ $ REMOTE_USER=gooduser REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 200 Script output follows ++ ++ 1 ++ ++ $ hg bookmarks ++ bm 0:cb9a9f314b8b ++ $ hg book -d bm ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 200 Script output follows ++ ++ 1 ++ ++ $ hg bookmarks ++ bm 0:cb9a9f314b8b ++ $ hg book -d bm ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 200 Script output follows ++ ++ write command no defined permissions ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 200 Script output follows ++ ++ write command w/ defined permissions ++ ++Reset server to remove REQUEST_METHOD hack to test hg client ++ ++ $ "$TESTDIR/killdaemons.py" ++ $ cd .. ++ $ rm -rf test2 ++ $ hg clone test test2 ++ updating to branch default ++ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved ++ $ cd test2 ++ $ echo a >> a ++ $ hg ci -mb ++ $ hg book bm -r 0 ++ $ cd ../test ++ $ REMOTE_USER=gooduser hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ no changes found ++ exporting bookmark bm ++ [1] ++ ++ $ hg book -d bm ++ ++ $ hg --cwd ../test2 push http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ remote: adding changesets ++ remote: adding manifests ++ remote: adding file changes ++ remote: added 1 changesets with 1 changes to 1 files ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.deny_push takes precedence over web.allow_push ++ ++ $ cat > .hg/hgrc < [web] ++ > push_ssl = false ++ > allow_push = someuser ++ > deny_push = someuser ++ > EOF ++ ++ $ REMOTE_USER=someuser REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ hg bookmarks ++ no bookmarks set ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritenoperm' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customwritewithperm' ++ 401 push not authorized ++ ++ 0 ++ push not authorized ++ [1] ++ ++Reset server to remove REQUEST_METHOD hack to test hg client ++ ++ $ "$TESTDIR/killdaemons.py" ++ $ cd .. ++ $ rm -rf test2 ++ $ hg clone test test2 ++ updating to branch default ++ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved ++ $ cd test2 ++ $ echo a >> a ++ $ hg ci -mb ++ $ hg book bm -r 0 ++ $ cd ../test ++ $ REMOTE_USER=someuser hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ no changes found ++ exporting bookmark bm ++ abort: authorization failed ++ [255] ++ ++ $ hg --cwd ../test2 push http://localhost:$HGPORT/ ++ pushing to http://localhost:$HGPORT/ ++ searching for changes ++ abort: authorization failed ++ [255] ++ ++ $ "$TESTDIR/killdaemons.py" ++ ++web.allow_push has no effect if web.deny_read is set ++ ++ $ cat > .hg/hgrc < [web] ++ > push_ssl = false ++ > allow_push = * ++ > deny_read = * ++ > EOF ++ ++ $ REQUEST_METHOD=POST REMOTE_USER=someuser hg serve -p $HGPORT -d --pid-file hg.pid ++ $ cat hg.pid > $DAEMON_PIDS ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] ++ ++ $ hg bookmarks ++ no bookmarks set ++ ++ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT '?cmd=customreadnoperm' ++ 401 read not authorized ++ ++ 0 ++ read not authorized ++ [1] diff -Nru mercurial-3.1.2/debian/patches/series mercurial-3.1.2/debian/patches/series --- mercurial-3.1.2/debian/patches/series 2017-08-28 16:08:28.000000000 -0400 +++ mercurial-3.1.2/debian/patches/series 2018-06-06 11:27:04.000000000 -0400 @@ -23,3 +23,7 @@ from_upstream__convert-test-for-shell-injection-in-git-calls.patch from_upstream__convert_pass_absolute_paths_to_git.patch from_upstream__security_201708.patch +CVE-2017-9462-77eaf9539499.patch +CVE-2017-17458-1-80d7dbda9294.patch +CVE-2017-17458-2-071cbeba4212.patch +CVE-2018-1000132-db527ae12671-86f9a022ccb8.patch diff -Nru mercurial-3.1.2/debian/rules mercurial-3.1.2/debian/rules --- mercurial-3.1.2/debian/rules 2015-05-01 13:30:52.000000000 -0400 +++ mercurial-3.1.2/debian/rules 2018-06-06 11:27:04.000000000 -0400 @@ -39,7 +39,7 @@ sed -i.deb-backup -e 's/sleep 1/sleep 2/' $(CURDIR)/tests/test-pull-pull-corruption.t endif - http_proxy='' dh_auto_test -- TESTFLAGS="--verbose --timeout 1440 $(PARALLEL_TEST_JOBS) --blacklist $(CURDIR)/debian/mercurial.test_blacklist" + http_proxy='' dh_auto_test -- TESTFLAGS="--verbose --debug --timeout 1440 $(PARALLEL_TEST_JOBS) --blacklist $(CURDIR)/debian/mercurial.test_blacklist" rename.ul .deb-backup '' $(CURDIR)/tests/* endif