--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,9 @@
+^build/
+^dev/chroot-test/build/
+
+syntax: glob
+
+*~
+*.pyc
+*.orig
+*.rej
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgtags Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,7 @@
+535502c18eaad098437e49adea1e26a68e4b6d75 release_0.5
+975fb921c3f3ffe7ccde5877f2954a5d1141bb14 release_0.6
+243dd21d0dbc140957afbe7c9e2afb9caaffee37 release_0.7
+1ad9d5841a48a77f68dc5350bd1f941327a6348a release_0.8
+fed42d3f5311c55cab668d6962a61d44ba98645e release_0.9
+8ce190faa5c2b50f63cc5b11e28daf98836498d8 release_1.0
+92cb6640a6417edaf52870c8a97000e11bb8b138 release_1.0.1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/CREDITS Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,24 @@
+mercurial-server is by Paul Crowley <paul@lshift.net>
+
+Thanks to:
+
+Thomas Arendsen Hein <thomas@intevation.de>
+Mathieu PASQUET <kiorky@cryptelium.net>
+Vadim Gelfer <vadim.gelfer@gmail.com>
+Hubert Plociniczak <hubert@lshift.net>
+Christoph Junghans <kleiner_otti@gmx.de>
+Steve Kemp <steve@steve.org.uk>
+Cédric Boutillier <cedric.boutillier@gmail.com>
+Justin B Rye <jbr@edlug.org.uk>
+Wolfgang Karall <office@karall-edv.at>
+Helge Kreutzmann <debian@helgefjell.de>
+"Hideki Yamane \(Debian-JP\)" <henrich@debian.or.jp>
+Michal Simunek <michal.simunek@gmail.com>
+Martin Bagge <brother@bsnet.se>
+Vincenzo Campanella <vinz65@gmail.com>
+Ji ZhengYu <zhengyuji@gmail.com>
+Waldemar Augustyn <waldemar@astyn.com>
+Steven King <kingrst@gmail.com>
+
+This credits file may be incomplete - please remind me about people I
+should add!
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/LICENSE Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,68 @@
+#!/usr/bin/env make -f
+
+PREFIX=/usr/local/share
+LIBDIR=$(PREFIX)/mercurial-server
+DOCDIR=$(PREFIX)/doc/mercurial-server
+ETCDIR=/etc/mercurial-server
+NEWUSER=hg
+DOCBOOK_XSL=/usr/share/xml/docbook/stylesheet/nwalsh
+
+INSTALL=install
+
+build: build/html/index.html pythonbuild
+
+setup-adduser: installfiles adduser inituser
+
+# WARNING: this is experimental
+setup-useradd: installfiles useradd inituser
+
+installetc:
+ $(INSTALL) -d $(DESTDIR)$(ETCDIR)
+ $(INSTALL) -m 644 -t $(DESTDIR)$(ETCDIR) \
+ src/init/conf/access.conf
+ $(INSTALL) -d $(DESTDIR)$(ETCDIR)/remote-hgrc.d
+ $(INSTALL) -m 644 -t $(DESTDIR)$(ETCDIR)/remote-hgrc.d \
+ src/init/conf/remote-hgrc.d/access.rc \
+ src/init/conf/remote-hgrc.d/logging.rc
+ $(INSTALL) -d $(DESTDIR)$(ETCDIR)/keys/root
+ $(INSTALL) -d $(DESTDIR)$(ETCDIR)/keys/users
+
+installdoc: build/html/index.html
+ $(INSTALL) -d $(DESTDIR)$(DOCDIR)
+ $(INSTALL) -m 644 -t $(DESTDIR)$(DOCDIR) README
+ $(INSTALL) -d $(DESTDIR)$(DOCDIR)/html
+ $(INSTALL) -m 644 -t $(DESTDIR)$(DOCDIR)/html build/html/index.html
+
+build/html/index.html: doc/manual.docbook
+ xsltproc --nonet -o $@ $(DOCBOOK_XSL)/html/docbook.xsl $^
+
+build/pdf/manual.pdf: doc/manual.docbook
+ mkdir -p build/pdf
+ fop -xml $^ -xsl $(DOCBOOK_XSL)/fo/docbook.xsl $@
+
+pythonbuild:
+ python setup.py build
+
+pythoninstall:
+ python setup.py install \
+ --install-purelib=$(DESTDIR)$(LIBDIR) \
+ --install-platlib=$(DESTDIR)$(LIBDIR) \
+ --install-scripts=$(DESTDIR)$(LIBDIR) \
+ --install-data=$(DESTDIR)$(LIBDIR)
+
+installfiles: installetc installdoc pythoninstall
+
+adduser:
+ adduser --system --shell /bin/sh --group --disabled-password \
+ --home /var/lib/mercurial-server \
+ --gecos "Mercurial repositories" $(NEWUSER)
+
+# WARNING: this is experimental
+useradd:
+ useradd --system --shell /bin/sh \
+ --home /var/lib/mercurial-server --create-home \
+ --comment "Mercurial repositories" $(NEWUSER)
+
+inituser:
+ su -l -c "$(DESTDIR)$(LIBDIR)/init/hginit $(DESTDIR)$(LIBDIR)" $(NEWUSER)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/NEWS Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,89 @@
+======================
+mercurial-server 1.1
+======================
+
+* New log filename
+* Changed logging format to use JSON/YAML
+* Add the source IP address and other info in the SSH_CONNECTION environment variable
+* Lock log file
+* Make sure authorized_keys file is mode 600
+
+Upgrading: note the changes to the log file format listed above.
+
+======================
+mercurial-server 1.0.1
+======================
+
+* Fix HGRCPATH brokenness - potential security issue
+* Fix rule matching to properly handle the case where we don't know for sure
+* Fix error in documentation
+* Remove whitespace around paths, said to help with TortoiseHG
+* Small refactor of access.py
+* Tidy up file prologues; move credits to CREDITS
+
+Upgrading: repositories whose paths begin or end in white space will no longer
+be accessible; if they exist they must be moved to new names.
+
+====================
+mercurial-server 1.0
+====================
+
+* Add "env" section to .mercurial-server instead of special-casing HGRCPATH
+* Switch to remote-hgrc.d directory instead of single file.
+* Control path of authorized keys file in .mercurial_server
+* Overwrite $HOME with value from /etc/passwd
+* Use Python's ConfigParser instead of too-new mercurial.config
+* Fix very out-of-date comments in hg-ssh
+* Belatedly added NEWS file :-)
+
+Upgrading: move the paths/hgrc entry in .mercurial-server to env/HGRCPATH,
+and add an entry under paths that reads
+"authorized_keys = ~/.ssh/authorized_keys"
+
+====================
+mercurial-server 0.9
+====================
+
+* Switch to supporting DESTDIR prefix in Makefile for easier packaging
+
+====================
+mercurial-server 0.8
+====================
+
+* Remove .deb-specific stuff in Docbook documentation - let the README
+ handle that stuff. Also fixes bad version numbers in there.
+* Move html docs into subdirectory
+* Line wrap README
+
+====================
+mercurial-server 0.7
+====================
+
+* Introduce .mercurial-server file for hg user
+* Remove all restrictions on paths, except for dotfiles in repo paths
+* Automatically create containing dirs for subdir repos
+* Guard against repos in repos
+* Switch to Makefile/setup.py based installer
+* Switch to Docbook based documentation
+* Load purge extension for hgadmin repo
+* Guard against setting up hg user who already has .ssh/authorized_keys
+* Link to real home page
+
+Upgrading: you'll need to create a .mercurial-server file
+for the hg user - a suitable one is in the init directory.
+
+====================
+mercurial-server 0.6
+====================
+
+* Remove hardcoding of init file path in hginit
+* Switch from /usr/lib to /usr/share
+* Install documentation
+* Don't create "hg" user if install root is not root.
+
+====================
+mercurial-server 0.5
+====================
+
+* First numbered release
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/README Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,44 @@
+mercurial-server
+
+mercurial-server gives your developers remote read/write access to
+centralized Mercurial repositories using SSH public key authentication; it
+provides convenient and fine-grained key management and access control.
+
+http://www.lshift.net/mercurial-server.html
+
+Copyright (C) 2008-2010 LShift Ltd.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Though mercurial-server is currently targeted at Debian-based systems such
+as Ubuntu, other users have reported success getting it running on other
+Unix-based systems such as Red Hat. Running it on a non-Unix system such as
+Windows is not supported. You will need root privileges to install it.
+
+The best way to install mercurial-server is using your package management
+system - there are pre-built .deb files on the website. However, there is
+some provision for installing it directly. On Debian based systems such as
+Ubuntu, use the command
+
+ sudo make setup-adduser
+
+On Red Hat and possibly other variants of Unix, try
+
+ sudo make setup-useradd
+
+See doc/manual.docbook for the rest of the documentation.
+
+Paul Crowley, paul@lshift.net, 2010
+
--- a/dev/chroot-test/.hgignore Fri Dec 17 18:16:08 2010 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-^build/
-syntax: glob
-
-*~
-*.pyc
-*.orig
-*.rej
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/manual.docbook Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,535 @@
+<?xml version="1.0" encoding="utf-8"?>
+<article xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+<info>
+ <title>Sharing Mercurial repositories with mercurial-server</title>
+ <author><firstname>Paul</firstname><surname>Crowley</surname></author>
+ <copyright><year>2008-2010</year><holder>Paul Crowley, LShift Ltd</holder></copyright>
+</info>
+<section>
+<title>About mercurial-server</title>
+<para>
+Home page: <link xlink:href="http://www.lshift.net/mercurial-server.html"/>
+</para>
+<para>
+mercurial-server gives your developers remote read/write access to
+centralized <link xlink:href="http://hg-scm.org/">Mercurial</link>
+repositories using SSH public key authentication; it provides convenient
+and fine-grained key management and access control.
+</para>
+<para>
+Though mercurial-server is currently targeted at Debian-based systems such
+as Ubuntu, other users have reported success getting it running on other
+Unix-based systems such as Red Hat. Running it on a non-Unix system such as
+Windows is not supported. You will need root privileges to install it.
+</para>
+</section>
+<section>
+<title>Step by step</title>
+<para>
+mercurial-server authenticates users not using passwords but using SSH
+public keys; everyone who wants access to a mercurial-server repository
+will need such a key. In combination with <command>ssh-agent</command> (or
+equivalents such as the Windows program <link
+xlink:href="http://the.earth.li/~sgtatham/putty/0.60/htmldoc/Chapter9.html#pageant">Pageant</link>),
+this means that users will not need to type in a password to access the
+repository. If you're not familiar with SSH public keys, the <link
+xlink:href="http://sial.org/howto/openssh/publickey-auth/">OpenSSH Public
+Key Authentication tutorial</link> may be helpful.
+</para>
+<section>
+<title>Initial access to mercurial-server</title>
+<para>
+In what follows, we assume that your username is <systemitem
+class="username">jay</systemitem>, that you usually sit at a machine called
+<systemitem class="systemname">spoon</systemitem> and you have
+installed mercurial-server on <systemitem
+class="systemname">jeeves</systemitem> using the package management system (see the README for more on installation). We assume that you have created your SSH public key, set up your SSH agent with this key, and that this key gives you access to <systemitem
+class="systemname">jeeves</systemitem>.
+</para>
+<screen><computeroutput>jay@spoon:~$ </computeroutput><userinput>ssh -A jeeves</userinput>
+<computeroutput>jay@jeeves:~$ </computeroutput><userinput>ssh-add -L > my-key</userinput>
+<computeroutput>jay@jeeves:~$ </computeroutput><userinput>sudo mkdir -p /etc/mercurial-server/keys/root/jay</userinput>
+<computeroutput>jay@jeeves:~$ </computeroutput><userinput>sudo cp my-key /etc/mercurial-server/keys/root/jay/spoon</userinput>
+<computeroutput>jay@jeeves:~$ </computeroutput><userinput>sudo -u hg /usr/share/mercurial-server/refresh-auth</userinput>
+<computeroutput>jay@jeeves:~$ </computeroutput><userinput>exit</userinput>
+<computeroutput>Connection to jeeves closed.
+jay@spoon:~$ </computeroutput></screen>
+<para>
+You can now create repositories on the remote machine and have complete
+read-write access to all of them.
+</para>
+</section>
+<section>
+<title>Creating repositories</title>
+<para>
+To store a repository on the server, clone it over.
+</para>
+<screen><computeroutput>jay@spoon:~$ </computeroutput><userinput>cd myproj</userinput>
+<computeroutput>jay@spoon:~/myproj$ </computeroutput><userinput>hg clone . ssh://hg@jeeves/jays/project</userinput>
+<computeroutput>searching for changes
+remote: adding changesets
+remote: adding manifests
+remote: adding file changes
+remote: added 119 changesets with 284 changes to 61 files
+jay@spoon:~/myproj$ </computeroutput><userinput>hg pull ssh://hg@jeeves/jays/project</userinput>
+<computeroutput>pulling from ssh://hg@jeeves/jays/project
+searching for changes
+no changes found
+<computeroutput>jay@spoon:~/myproj$ </computeroutput><userinput>cd ..</userinput>
+jay@spoon:~$ </computeroutput></screen>
+</section>
+<section>
+<title>Adding other users</title>
+<para>
+At this stage, no-one but you has any access to any repositories you
+create on this system. In order to give anyone else access, you'll need a
+copy of their SSH public key; we'll assume you have that key in
+<filename>~/sam-saucer-key.pub</filename>. To manage access, you make changes to the special <filename
+class='directory'>hgadmin</filename> repository.
+</para>
+<screen><computeroutput>jay@spoon:~$ </computeroutput><userinput>hg clone ssh://hg@jeeves/hgadmin</userinput>
+<computeroutput>destination directory: hgadmin
+no changes found
+updating working directory
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+jay@spoon:~$ </computeroutput><userinput>cd hgadmin</userinput>
+<computeroutput>jay@spoon:~/hgadmin$ </computeroutput><userinput>mkdir -p keys/users/sam</userinput>
+<computeroutput>jay@spoon:~/hgadmin$ </computeroutput><userinput>cp ~/sam-saucer-key.pub keys/users/sam/saucer</userinput>
+<computeroutput>jay@spoon:~/hgadmin$ </computeroutput><userinput>hg add</userinput>
+<computeroutput>adding keys/users/sam/saucer
+jay@spoon:~/hgadmin$ </computeroutput><userinput>hg commit -m "Add Sam's key"</userinput>
+<computeroutput>jay@spoon:~/hgadmin$ </computeroutput><userinput>hg push</userinput>
+<computeroutput>pushing to ssh://hg@jeeves/hgadmin
+searching for changes
+remote: adding changesets
+remote: adding manifests
+remote: adding file changes
+remote: added 1 changesets with 1 changes to 1 files
+jay@spoon:~/hgadmin$ </computeroutput></screen>
+<para>
+Sam can now read and write to your
+<uri>ssh://hg@jeeves/jays/project</uri> repository.
+Most other changes to access control can be made simply by making and
+pushing changes to <filename
+class='directory'>hgadmin</filename>, and you can use Mercurial to
+cooperate with other root users in the normal way.
+</para>
+<para>
+If you prefer, you could give them access by
+logging into <systemitem class="systemname">jeeves</systemitem>,
+putting the key in the right place under <filename
+class='directory'>/etc/mercurial-server/keys</filename>, and re-running
+<userinput>sudo -u hg /usr/share/mercurial-server/refresh-auth</userinput>.
+However, using <filename
+class='directory'>hgadmin</filename> is usually more convenient if you need to make more than a very few changes; it also makes it easier to share administration with others and provides a log of all changes.
+</para>
+</section>
+</section>
+<section>
+<title>Access control</title>
+<para>
+Out of the box, mercurial-server supports two kinds of users: "root" users and normal users. If you followed the steps above, you are a "root" user because your key is under <filename class='directory'>keys/root</filename>, while the other user you gave access to is a normal user since their key is under <filename class='directory'>keys/users</filename>. Keys that are not in either of these directories will by default have no access to anything.
+</para>
+<para>
+Root users can edit <filename
+class='directory'>hgadmin</filename>, create new repositories and read and write to existing ones. Normal users cannot access <filename
+class='directory'>hgadmin</filename> or create new repositories, but they can read and write to any other repository.
+</para>
+<section>
+<title>Using access.conf</title>
+<para>
+mercurial-server offers much more fine-grained access control than this division into two classes of users. Let's suppose you wish to give Pat access to the <filename
+class='directory'>widget</filename> repository, but no other. We first copy Pat's SSH public key into the <filename
+class='directory'>keys/pat</filename> directory in <filename
+class='directory'>hgadmin</filename>. This tells mercurial-server about Pat's key, but gives Pat no access to anything because the key is not under either <filename
+class='directory'>keys/root</filename> or <filename
+class='directory'>keys/users</filename>. To grant this key access, we must give mercurial-server a new access rule, so we create a file in <filename
+class='directory'>hgadmin</filename> called <filename>access.conf</filename>, with the following contents:</para>
+<programlisting># Give Pat access to the "widget" repository
+write repo=widget user=pat/*
+</programlisting>
+<para>
+Pat will have read and write access to the <filename
+class='directory'>widget</filename> repository as soon as we add, commit, and push these files.
+</para>
+<para>
+Each line of <filename>access.conf</filename> has the following syntax:
+</para>
+<programlisting><replaceable>rule</replaceable> <replaceable>condition</replaceable> <replaceable>condition...</replaceable>
+</programlisting>
+<para>
+Blank lines and lines that start with <code>#</code> are ignored. Rule is
+one of
+</para>
+<itemizedlist>
+<listitem>
+<literal>init</literal>: allow reads, writes, and the creation of new repositories
+</listitem>
+<listitem>
+<literal>write</literal>: allow reads and writes
+</listitem>
+<listitem>
+<literal>read</literal>: allow only read operations
+</listitem>
+<listitem>
+<literal>deny</literal>: deny all requests
+</listitem>
+</itemizedlist>
+<para>
+A condition is a globpattern matched against a relative path. The two most
+important conditions are
+</para>
+<itemizedlist>
+<listitem>
+<code>user=<replaceable>globpattern</replaceable></code>: path to the user's key
+</listitem>
+<listitem>
+<code>repo=<replaceable>globpattern</replaceable></code>: path to the repository
+</listitem>
+</itemizedlist>
+<para>
+<code>*</code> only matches one directory level, where <code>**</code>
+matches as many as you want. More precisely, <code>*</code> matches zero or
+more characters not including <code>/</code> while <code>**</code> matches
+zero or more characters including <code>/</code>. So
+<code>projects/*</code> matches <filename
+class='directory'>projects/foo</filename> but not <filename
+class='directory'>projects/foo/bar</filename>, while
+<code>projects/**</code> matches both.
+</para>
+<para>
+When considering a request, mercurial-server steps through all the rules in
+<filename>/etc/mercurial-server/access.conf</filename> and then all the
+rules in <filename>access.conf</filename> in <filename
+class='directory'>hgadmin</filename>
+looking for a rule which matches on every condition. The first match
+determines whether the request will be allowed; if there is no match in
+either file, the request will be denied.
+</para>
+<para>
+By default, <filename>/etc/mercurial-server/access.conf</filename> has the
+following rules:
+</para>
+<programlisting>init user=root/**
+deny repo=hgadmin
+write user=users/**
+</programlisting>
+<para>
+These rules ensure that root users can do any operation on any repository,
+that no other users can access the <filename
+class='directory'>hgadmin</filename> repository,
+and that those with keys in <filename
+class='directory'>keys/users</filename> can read or write to any repository
+but not create repositories. Some examples of how these rules work:
+</para>
+<itemizedlist>
+<listitem>
+User <filename class='directory'>root/jay</filename> creates a repository
+<filename class='directory'>foo/bar/baz</filename>. This matches the first
+rule and so will be allowed.
+</listitem>
+<listitem>
+User <filename class='directory'>root/jay</filename> changes repository
+<filename class='directory'>hgadmin</filename>. Again, this matches the
+first rule and so will be allowed; later rules have no effect.
+</listitem>
+<listitem>
+User <filename class='directory'>users/sam</filename> tries to read
+repository <filename class='directory'>hgadmin</filename>. This does not
+match the first rule, but matches the second, and so will be denied.
+</listitem>
+<listitem>
+User <filename class='directory'>users/sam</filename> tries to create
+repository <filename class='directory'>sams-project</filename>. This does
+not match the first two rules, but matches the third; this is a
+<literal>write</literal> rule, which doesn't grant the privilege to create
+repositories, so the request will be denied.
+</listitem>
+<listitem>
+User <filename class='directory'>users/sam</filename> writes to existing
+repository <filename class='directory'>projects/main</filename>. Again,
+this matches the third rule, which allows the request.
+</listitem>
+<listitem>
+User <filename class='directory'>pat</filename> tries to write to existing
+repository <filename class='directory'>widget</filename>. Until we change
+the <filename>access.conf</filename> file in <filename
+class='directory'>hgadmin</filename>, this will match no rule, and so will
+be denied.
+</listitem>
+<listitem>
+Any request from a user whose key not under the <filename
+class='directory'>keys</filename> directory at all will always be denied,
+no matter what rules are in effect; because of the way SSH authentication
+works, they will be prompted to enter a password, but no password will
+work. This can't be changed.
+</listitem>
+</itemizedlist>
+</section>
+<section>
+<title>/etc/mercurial-server and hgadmin</title>
+<para>
+mercurial-server consults two distinct locations to collect information about what to allow: <filename
+class='directory'>/etc/mercurial-server</filename> and its own <filename
+class='directory'>hgadmin</filename> repository. This is useful for several reasons:
+</para>
+<itemizedlist>
+<listitem>
+Some users may not need the convenience of access control via mercurial; for these users updating <filename
+class='directory'>/etc/mercurial-server</filename> may offer a simpler route.
+</listitem>
+<listitem>
+<filename class='directory'>/etc/mercurial-server</filename> is suitable
+for management with tools such as <link
+xlink:href="http://reductivelabs.com/products/puppet">Puppet</link>
+</listitem>
+<listitem>
+If a change to <filename
+class='directory'>hgadmin</filename> leaves you "locked out", <filename
+class='directory'>/etc/mercurial-server</filename> allows you a way back in.
+</listitem>
+<listitem>
+At install time, all users are "locked out", and so some mechanism to allow some users in is needed.
+</listitem>
+</itemizedlist>
+<para>
+Rules in <filename>/etc/mercurial-server/access.conf</filename> are checked before those in <filename
+class='directory'>hgadmin</filename>, and keys in <filename class='directory'>/etc/mercurial-server/keys</filename> will be present no matter how <filename
+class='directory'>hgadmin</filename> changes.
+</para>
+<para>
+We anticipate that once mercurial-server is successfully installed and
+working you will usually want to use <filename
+class='directory'>hgadmin</filename> for most
+access control tasks. Once you have the right keys and
+<filename>access.conf</filename> set up in <filename
+class='directory'>hgadmin</filename>, you
+can delete <filename>/etc/mercurial-server/access.conf</filename> and all
+of <filename class='directory'>/etc/mercurial-server/keys</filename>,
+turning control entirely over to <filename
+class='directory'>hgadmin</filename>.
+</para>
+<para>
+<filename>/etc/mercurial-server/remote-hgrc.d</filename> is in the
+<systemitem>HGRCPATH</systemitem> for all remote access to mercurial-server
+repositories. This directory contains the hooks that mercurial-server uses for
+access control and logging. You can add hooks to this directory, but obviously
+breaking the existing hooks will disable the relevant functionality and
+isn't advisable.
+</para>
+</section>
+<section>
+<title>File and branch conditions</title>
+<para>
+mercurial-server supports file and branch conditions, which restrict an
+operation depending on what files it modifies and what branch the work is
+on. </para>
+<caution>
+The way these conditions work is subtle and can be counterintuitive. Unless
+you need what they provide, ignore this section, stick to user and repo
+conditions, and then things are likely to work the way you would expect. If
+you do need what they provide, read what follows very carefully.
+</caution>
+<para>
+File and branch conditions are added to the conditions against which a rule
+matches, just like user and repo conditions; they have this form:
+</para>
+<itemizedlist>
+<listitem>
+<code>file=<replaceable>globpattern</replaceable></code>: file within the repo
+</listitem>
+<listitem>
+<code>branch=<replaceable>globpattern</replaceable></code>: Mercurial branch name
+</listitem>
+</itemizedlist>
+<para>
+However, in order to understand what effect adding these conditions will
+have, it helps to understand how and when these rules are applied.
+</para>
+<para>
+The rules file is used to make three decisions:
+</para>
+<itemizedlist>
+<listitem>
+Whether to allow a repository to be created
+</listitem>
+<listitem>
+Whether to allow any access to a repository
+</listitem>
+<listitem>
+Whether to allow a changeset
+</listitem>
+</itemizedlist>
+<para>
+When the first two of these decisions are being made, nothing is known
+about any changsets that might be pushed, and so all file and branch
+conditions automatically succeed for the purpose of such decisions. For the
+third condition, every file changed in the changeset must be allowed by a
+<literal>write</literal> or <literal>init</literal> rule for the changeset
+to be allowed.
+</para>
+<para>
+This means that doing tricky things with file conditions can have
+counterintuitive consequences:
+</para>
+<itemizedlist>
+<listitem>
+<para>You cannot limit read access to a subset of a repository with a <literal>read</literal>
+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 <literal>write</literal> rule, as in this example:</para>
+<programlisting>read repo=specialrepo file=dontwritethis
+write repo=specialrepo
+</programlisting>
+<para>
+allows all users to read <literal>specialrepo</literal>, and to write to all files
+<emphasis>except</emphasis> that any changeset which writes to
+<filename>dontwritethis</filename> will be rejected.
+</para>
+</listitem>
+<listitem>
+For similar reasons, don't give <literal>init</literal> rules file conditions.
+</listitem>
+<listitem>
+<para>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.
+</para>
+<programlisting>write user=docs/* branch=docs file=docs/*
+</programlisting>
+<para>
+This rule grants users whose keys are in the <filename
+class='directory'>docs</filename> subdirectory the power to push changesets
+into any repository only if those changesets are on the
+<literal>docs</literal> branch and they affect only those files directly
+under the <filename class='directory'>docs</filename> directory. However,
+the rules below have more counterintuitive consequences.
+</para>
+<programlisting>write user=docs/* branch=docs
+write user=docs/* file=docs/*
+read user=docs/*
+</programlisting>
+<para>
+These rules grant users whose keys are in the <filename
+class='directory'>docs</filename> subdirectory the power to change any file directly under the <filename class='directory'>docs</filename> directory, or any file at all in the <literal>docs</literal> branch. Indirectly, however, this adds up to the power to change any file on any branch, simply by making the change on the docs branch and then merging the change into another branch.
+</para>
+</listitem>
+</itemizedlist>
+</section>
+</section>
+<section>
+<title>In detail</title>
+<section>
+<title>How mercurial-server works</title>
+<para>
+All of the repositories controlled by mercurial-server are owned by a
+single user, the <systemitem
+class="username">hg</systemitem> user, which is why all URLs for
+mercurial-server repositories start with <uri>ssh://hg@...</uri>.
+Each SSH key that has access to the repository has an entry in
+<filename>~hg/.ssh/authorized_keys</filename>; this is how the SSH daemon
+knows to give that key access. When the user connects over SSH, their
+commands are run in a custom restricted shell; this shell knows which key
+was used to connect, determines what the user is trying to do, checks the
+access rules to decide whether to allow it, and if allowed invokes
+Mercurial internally, without forking.
+</para>
+<para>
+This restricted shell also ensures that certain Mercurial extensions are
+loaded when the user acts on a repository; these extensions check the
+access control rules for any changeset that the user tries to commit, and
+log all pushes and pulls into a per-repository access log.
+</para>
+<para>
+<command>refresh-auth</command> recurses through the <filename
+class='directory'>/etc/mercurial-server/keys</filename> and the <filename
+class='directory'>keys</filename> directory in the
+<filename
+class='directory'>hgadmin</filename> repository, creating an entry in
+<filename>~hg/.ssh/authorized_keys</filename> for each one. This is redone
+automatically whenever a change is pushed to <filename
+class='directory'>hgadmin</filename>.
+</para>
+</section>
+<section>
+<title>Security</title>
+<para>
+mercurial-server relies entirely on <command>sshd</command> to grant access to remote users.
+As a result, it runs no daemons, installs no setuid programs, and no part
+of it runs as <systemitem
+class="username">root</systemitem> except the install process: all programs run as the user
+<systemitem
+class="username">hg</systemitem>. Any attack on mercurial-server can only be started if the attacker
+already has a public key in <filename>~hg/.ssh/authorized_keys</filename>,
+otherwise <command>sshd</command> will bar the way.
+</para>
+<para>
+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 operation itself if access is
+allowed, so users can only read and add to history within repositories;
+they cannot run any other command. In addition, every push and pull is
+logged with a datestamp, changeset ID and the key that performed the
+operation.
+</para>
+<para>
+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, like all software mercurial-server may harbour bugs. Backups are essential!
+</para>
+</section>
+<section>
+<title>Logging</title>
+<para>
+Every successful access is logged in a file called
+<filename>~hg/repos/<replaceable>repository</replaceable>/.hg/mercurial-server.log</filename>. This file is in YAML format for easy parsing, but if you don't like YAML, simply treat each line as a JSON data structure prepended with <code>- </code>. The log records the time as a
+UTC ISO 8601 time, the operation ("push" or "pull"), the path to the key as
+used in the access rules, the SSH connection information (including the source IP address), and the hex changeset IDs.
+</para>
+</section>
+<section>
+<title>Paths and configuration</title>
+<para>
+For security reasons, all mercurial-server code runs as the <systemitem
+class="username">hg</systemitem> user. The first thing this code reads when it starts is <filename>~hg/.mercurial-server</filename>; if this file is absent or corrupt the code won't run. This file specifies all of the file paths that mercurial-server uses. In particular, it specifies that mercurial-server always uses <code>HGRCPATH = /etc/mercurial-server/remote-hgrc.d</code> for remote operations, overriding any system <code>HGRCPATH</code>.
+</para>
+<para>
+By creating such a file with suitable entries, you can run mercurial-server as a user other than <systemitem
+class="username">hg</systemitem>, or install it without root privileges; however I strongly recommend that if you need to do this, you use a user account that is used for no other purpose, and take the time to thoroughly understand how mercurial-server works before you attempt it.
+</para>
+</section>
+<section>
+<title>License</title>
+<para>
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2 of the License, or (at your option)
+any later version.
+</para>
+<para>
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+</para>
+<para>
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc., 51
+Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+</para>
+</section>
+<section>
+<title>Thanks</title>
+<para>
+Thanks for reading this far. If you use mercurial-server, please tell me about
+it.
+</para>
+<para>
+Paul Crowley, <email>paul@lshift.net</email>, 2010
+</para>
+</section>
+</section>
+</article>
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/setup.py Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,23 @@
+# WARNING: this file is NOT meant to be directly executed, but
+# run from the Makefile.
+
+from distutils.core import setup
+
+setup(
+ name="mercurial-server",
+ description="Centralized Mercurial repository manager",
+ url="http://www.lshift.net/mercurial-server.html",
+ version="1.1", # FIXME: infer this
+ package_dir = {'': 'src'},
+ packages = ["mercurialserver"],
+ requires = ["mercurial"], # FIXME: what version?
+ scripts = ['src/hg-ssh', 'src/refresh-auth'],
+ data_files = [
+ ('init', [
+ 'src/init/hginit',
+ 'src/init/dot-mercurial-server',
+ 'src/init/hgadmin-hgrc'
+ ]),
+ ],
+)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hg-ssh Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+
+"""
+hg-ssh - limit access to hg repositories reached via ssh. Part of
+mercurial-server.
+
+It is called by ssh due to an entry in the authorized_keys file,
+with the name for the key passed on the command line.
+
+It uses SSH_ORIGINAL_COMMAND to determine what the user was trying to
+do and to what repository, and then checks each rule in the rule file
+in turn for a matching rule which decides what to do, defaulting to
+disallowing the action.
+
+"""
+
+# enable importing on demand to reduce startup time
+from mercurial import demandimport; demandimport.enable()
+
+from mercurial import dispatch
+
+import sys, os, os.path
+import base64
+from mercurialserver import config, ruleset
+
+def fail(message):
+ sys.stderr.write("mercurial-server: %s\n" % message)
+ sys.exit(-1)
+
+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 getrepo(op, repo):
+ # First canonicalise, then check the string, then the rules
+ # and finally the filesystem.
+ repo = repo.strip().rstrip("/")
+ if len(repo) == 0:
+ fail("path to repository seems to be empty")
+ if repo.startswith("/"):
+ fail("absolute paths are not supported")
+ checkDots(repo)
+ ruleset.rules.set(repo=repo)
+ if not ruleset.rules.allow(op, branch=None, file=None):
+ fail("access denied")
+ checkParents(repo)
+ return repo
+
+config.initExe()
+
+for k,v in config.getEnv():
+ os.environ[k.upper()] = v
+
+if len(sys.argv) == 3 and sys.argv[1] == "--base64":
+ ruleset.rules.set(user = base64.b64decode(sys.argv[2]))
+elif len(sys.argv) == 2:
+ ruleset.rules.set(user = sys.argv[1])
+else:
+ fail("hg-ssh wrongly called, is authorized_keys corrupt? (%s)"
+ % sys.argv)
+
+os.chdir(config.getReposPath())
+
+for f in config.getAccessPaths():
+ if os.path.isfile(f):
+ ruleset.rules.readfile(f)
+
+cmd = os.environ.get('SSH_ORIGINAL_COMMAND', None)
+if cmd is None:
+ fail("direct logins on the hg account prohibited")
+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)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/init/conf/access.conf Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,3 @@
+init user=root/**
+deny repo=hgadmin
+write user=users/**
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/init/conf/remote-hgrc.d/access.rc Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,5 @@
+# Check that a commit meets access control rules before allowing it
+
+[hooks]
+pretxnchangegroup.access = python:mercurialserver.access.hook
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/init/conf/remote-hgrc.d/logging.rc Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,5 @@
+# Log every push and pull to the servelog
+
+[hooks]
+changegroup.aaaaa_servelog = python:mercurialserver.servelog.hook
+outgoing.aaaaa_servelog = python:mercurialserver.servelog.hook
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/init/dot-mercurial-server Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,15 @@
+# WARNING: a .mercurial-server file in your home directory means
+# that refresh-auth can and will trash your ~/.ssh/authorized_keys file.
+
+[paths]
+repos = ~/repos
+authorized_keys = ~/.ssh/authorized_keys
+keys = /etc/mercurial-server/keys:~/repos/hgadmin/keys
+access = /etc/mercurial-server/access.conf:~/repos/hgadmin/access.conf
+
+[env]
+# Use a different hgrc for remote pulls - this way you can set
+# up access.py for everything at once without affecting local operations
+
+HGRCPATH = /etc/mercurial-server/remote-hgrc.d
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/init/hgadmin-hgrc Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,10 @@
+# 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
+changegroup.refreshauth = python:mercurialserver.refreshauth.hook
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/init/hginit Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+set -e
+
+cd
+
+if [ -e .ssh/authorized_keys ] ; then
+ echo "This user already exists with authorized keys, aborting"
+ exit -1
+fi
+
+cp $1/init/dot-mercurial-server .mercurial-server
+mkdir -p repos/hgadmin .ssh
+cd repos/hgadmin
+hg init .
+cp $1/init/hgadmin-hgrc .hg/hgrc
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mercurialserver/__init__.py Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,1 @@
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mercurialserver/access.py Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,28 @@
+"""Mercurial access control hook"""
+
+from mercurial.i18n import _
+import mercurial.util
+import mercurial.node
+
+import os
+from mercurialserver import ruleset
+from mercurialserver import changes
+
+def allow(ctx):
+ branch = ctx.branch()
+ if not ruleset.rules.allow("write", branch=branch, file=None):
+ return False
+ for f in ctx.files():
+ if not ruleset.rules.allow("write", branch=branch, file=f):
+ return False
+ return True
+
+def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
+ if hooktype != 'pretxnchangegroup':
+ raise mercurial.util.Abort(_('config error - hook type "%s" cannot stop '
+ 'incoming changesets') % hooktype)
+ for ctx in changes.changes(repo, node):
+ if not allow(ctx):
+ raise mercurial.util.Abort(_('%s: access denied for changeset %s') %
+ (__name__, mercurial.node.short(ctx.node())))
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mercurialserver/changes.py Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,12 @@
+"""
+Find all the changes in a node in a way portable across Mercurial versions
+"""
+
+def changes(repo, node):
+ start = repo.changectx(node).rev()
+ try:
+ end = len(repo.changelog)
+ except:
+ end = repo.changelog.count()
+ for rev in xrange(start, end):
+ yield repo.changectx(rev)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mercurialserver/config.py Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,52 @@
+"""
+Fix $HOME and read ~/.mercurial-server
+"""
+
+import sys
+import os
+import os.path
+import pwd
+import ConfigParser
+
+globalconfig = None
+
+def _getConf():
+ global globalconfig
+ if globalconfig is None:
+ globalconfig = ConfigParser.RawConfigParser()
+ globalconfig.read(os.path.expanduser("~/.mercurial-server"))
+ return globalconfig
+
+def _getPath(name):
+ return os.path.expanduser(_getConf().get("paths", name))
+
+def _getPaths(name):
+ return [os.path.expanduser(p)
+ for p in _getConf().get("paths", name).split(":")]
+
+def getReposPath(): return _getPath("repos")
+def getAuthorizedKeysPath(): return _getPath("authorized_keys")
+
+def configExists():
+ try:
+ getAuthorizedKeysPath()
+ return True
+ except Exception, e:
+ print e
+ return False
+
+def getKeysPaths(): return _getPaths("keys")
+def getAccessPaths(): return _getPaths("access")
+
+def getEnv(): return _getConf().items("env")
+
+# Work out where we are, don't use config.
+def initExe():
+ global _exePath
+ _exePath = os.path.dirname(os.path.abspath(sys.argv[0]))
+ # Fix $HOME in case of "sudo -u hg refresh-auth"
+ os.environ['HOME'] = pwd.getpwuid(os.geteuid()).pw_dir
+
+def getExePath():
+ return _exePath
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mercurialserver/refreshauth.py Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,63 @@
+"""
+Rewrite ~/.ssh/authorized_keys by recursing through key directories
+"""
+
+import re
+import base64
+import os, stat
+import os.path
+import subprocess
+from mercurialserver import config
+
+goodkey = re.compile("[/A-Za-z0-9._-]+$")
+
+def refreshAuth():
+ akeyfile = config.getAuthorizedKeysPath()
+ wrappercommand = config.getExePath() + "/hg-ssh"
+ prefix='no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,command='
+
+ if os.path.exists(akeyfile):
+ f = open(akeyfile)
+ try:
+ for l in f:
+ if not l.startswith(prefix):
+ raise Exception("Safety check failed, delete %s to continue" % akeyfile)
+ finally:
+ f.close()
+
+ akeys = open(akeyfile + "_new", "w")
+ for keyroot in config.getKeysPaths():
+ kr = keyroot + "/"
+ #print "Processing keyroot", keyroot
+ for root, dirs, files in os.walk(keyroot):
+ for fn in files:
+ ffn = os.path.join(root, fn)
+ if not ffn.startswith(kr):
+ raise Exception("Inconsistent behaviour in os.walk, bailing")
+ #print "Processing file", ffn
+ keyname = ffn[len(kr):]
+ 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]
+ if p.wait() == 0:
+ klines = [l.strip() for l in newkey.split("\n")]
+ else:
+ # Conversion failed, read it directly.
+ kf = open(ffn)
+ try:
+ klines = [l.strip() for l in kf]
+ finally:
+ kf.close()
+ for l in klines:
+ if len(l):
+ akeys.write('%s"%s %s" %s\n' % (prefix, wrappercommand, keyname, l))
+ akeys.close()
+ os.chmod(akeyfile + "_new", stat.S_IRUSR)
+ os.rename(akeyfile + "_new", akeyfile)
+
+def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
+ refreshAuth()
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mercurialserver/ruleset.py Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,86 @@
+"""
+Glob-based, order-based rules matcher that can answer "maybe"
+where the inputs make clear that something is unknown.
+"""
+
+import sys
+import re
+import os
+import os.path
+
+def globmatcher(pattern):
+ p = "[^/]*".join(re.escape(c) for c in pattern.split("*"))
+ # ** means "match recursively" ie "ignore directories"
+ return re.compile(p.replace("[^/]*[^/]*", ".*") + "$")
+
+# Returns True for a definite match
+# False for a definite non-match
+# None where we can't be sure because a key is None
+def rule(pairs):
+ matchers = [(k, globmatcher(v)) for k, v in pairs]
+ def c(kw):
+ for k, m in matchers:
+ if k not in kw:
+ return False
+ kkw = kw[k]
+ if kkw is None:
+ return None
+ if m.match(kkw) is None:
+ return False
+ return True
+ return c
+
+class Ruleset(object):
+ '''Class representing the rules in a rule file'''
+
+ levels = ["init", "write", "read", "deny"]
+
+ def __init__(self):
+ self.rules = []
+ self.preset = {}
+
+ def add(self, action, conditions):
+ self.rules.append((action, conditions))
+
+ def set(self, **kw):
+ self.preset.update(kw)
+
+ def get(self, k):
+ return self.preset.get(k, None)
+
+ def matchrules(self, kw):
+ d = self.preset.copy()
+ d.update(kw)
+ res = set()
+ for a, c in self.rules:
+ m = c(d)
+ if m is None:
+ # "Maybe match" - add it and carry on
+ res.add(a)
+ elif m:
+ # Definite match - add it and stop
+ res.add(a)
+ break
+ return res
+
+ def allow(self, level, **kw):
+ for a in self.matchrules(kw):
+ if a in self.levels:
+ if self.levels.index(a) <= self.levels.index(level):
+ return True
+ return False
+
+ def readfile(self, fn):
+ f = open(fn)
+ try:
+ for l in f:
+ l = l.strip()
+ if len(l) == 0 or l.startswith("#"):
+ continue
+ l = l.split()
+ self.add(l[0], rule([c.split("=", 1) for c in l[1:]]))
+ finally:
+ f.close()
+
+rules = Ruleset()
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mercurialserver/servelog.py Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,37 @@
+"""
+Hook to log changesets pushed and pulled
+"""
+
+from mercurial.i18n import _
+import mercurial.util
+import mercurial.node
+
+import os
+import time
+import fcntl
+import json
+from mercurialserver import ruleset, changes
+
+def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
+ if hooktype == 'changegroup':
+ op = "push"
+ elif hooktype == 'outgoing':
+ op = "pull"
+ else:
+ raise mercurial.util.Abort(_('servelog installed as wrong hook type,'
+ ' must be changegroup or outgoing but is %s') % hooktype)
+ log = open(repo.join("mercurial-server.log"), "a+")
+ try:
+ fcntl.flock(log.fileno(), fcntl.LOCK_EX)
+ log.seek(0, os.SEEK_END)
+ # YAML log file format
+ log.write("- {0}\n".format(json.dumps(dict(
+ timestamp=time.strftime("%Y-%m-%d_%H:%M:%S Z", time.gmtime()),
+ op=op,
+ key=ruleset.rules.get('user'),
+ ssh_connection=os.environ['SSH_CONNECTION'],
+ nodes=[mercurial.node.hex(ctx.node())
+ for ctx in changes.changes(repo, node)],
+ ))))
+ finally:
+ log.close()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/refresh-auth Fri Dec 17 18:19:31 2010 +0000
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""
+Rewrite ~/.ssh/authorized_keys by recursing through key directories
+"""
+
+import sys
+import os
+from mercurialserver import refreshauth, config
+
+if len(sys.argv) != 1:
+ sys.stderr.write("refresh-auth: must be called with no arguments (%s)\n" % sys.argv)
+ sys.exit(-1)
+
+config.initExe()
+
+# To protect the authorized_keys file for innocent users, you have to have
+# a ~/.mercurial-server file to run this.
+if not config.configExists():
+ print >>sys.stderr, "Must be run as the 'hg' user"
+ sys.exit(-1)
+
+refreshauth.refreshAuth()