#!/usr/bin/perl -w
print "Content-type: text/html\n\n";
my $p=$ENV{QUERY_STRING} || 'perlmoo';

my $page=q{
<title>Anonymous CVS access via ssh</title>
<h1 align=center>Anonymous CVS access via ssh</h1>

<p>
Because the design and the
<a href="/doc/cvs/html-info/cvs_2.html#SEC32">security
ramifications</a> of CVS's pserver scare me, I do not offer traditional
anonymous cvs access to my CVS repository. However, you can access it using
an anonymous CVS over ssh method that I've hacked together.
</p>

<p>
It's pretty easy to use this - just as easy as using pserver I think. There
are two methods you can use; for either method, you will need 
<a href="sshanoncvs">this file</a>.
</p>

<ol>
<li>Download the file, make it executable, put it somewhere, say, 
~/bin/sshanoncvs. Then set the <tt>CVS_RSH</tt> environment variable to point
to it and use cvs as you normally would. For example, just run this command to check out
all of Perlmoo:
<pre>
SSH_AUTH_SOCK= CVS_RSH=~/bin/sshanoncvs cvs -d :ext:cvs@cvs.kitenet.net:/home/cvs/repository checkout perlmoo
</pre>
<li>Or you can just use the file as a ssh key file without making it
executable. Download it, set its permissions to 600, and put it in
~/.ssh/sshanoncvs. Then add the following to your ~/.ssh/config:
<pre>
Host cvs.kitenet.net
User cvs
IdentityFile ~/.ssh/sshanoncvs
ForwardAgent no
ForwardX11 no
Compression yes
</pre>
Then just set CVS_RSH=ssh and checkout as normal:
<pre>
</pre>
SSH_AUTH_SOCK= CVS_RSH=ssh cvs -d :ext:cvs@cvs.kitenet.net:/home/cvs/repository checkout perlmoo
</ol>

Note that either of the two methods above require you be able to use ssh 2.
if you are stuck with ssh 1, you can use method #2, just use 
<a href="identity_anoncvs1.bin">this file</a> instead.

<hr>

<h2>How this works</h2>

<p>
The basic idea is to use CVS's standard rsh method of accessing
repositories. But since rsh is insecure, it uses ssh instead. Sshd can be
configured so people connecting to an account using a specific
authentication key are only allowed to run one pre-defined command, and
cannot use ssh to do anything else on the server. Another similar use of ssh
is by the Debian project's push-mirroring scheme, which is
explained <a href="http://www.debian.org/mirror/explain">here</a>.
</p>

<p>
To make this work, everyone who wants to access the server has to have a
copy of the "private" ssh key. Once you have the key, you can cause the
command to be run on the remote server by telling ssh to use the key as its
identity file. Since ssh 2 uses nicely formatted textual keys as its
private keys, I was able to embed one inside a simple shell script that
runs ssh, telling it to use the same shell script as its identity file.
</p>

<p>
CVS is told to use the sshanoncvs script as a replacement for rsh, so when you
tell cvs to access the remote repository, it runs it, enters "cvs server"
into what it thinks is a shell on the remote host, and expects to be
connecting to a cvs server on the remote end. From there on out the client
side is completly normal CVS.
</p>

<h2>How to set up a similar server</h2>

<p>
On the server side, you need a dedicated user for anonymous cvs, I call mine
just "cvs". This user should not be able to log in, set their password entry
to "*" in <tt>/etc/passwd</tt>. They do need to have a valid login shell
however.
</p>

<p>
The user needs to have a normal ssh key pair generated for them. After
running <tt>ssh-keygen -t dsa</tt> (specify an empty password), 
copy <tt>.ssh/id_dsa.pub</tt> to <tt>.ssh/authorized_keys2</tt> and prepend
this text to the beginning of the line in that file:
<pre>
no-port-forwarding,no-X11-forwarding,no-agent-forwarding,command="/usr/bin/cvs server"
</pre>
One that is set up, any one who tries to ssh in as that user with the proper
"private" key will be dumped right into cvs server and will have to way to get
out or do port forwarding or exploit ssh in other ways.
<p>

<h2>Setting up the repository</h2>

<p>
Once you have all this set up, you're almost there, but there is one more
stumbling block. CVS by default wants to have write access to directories in a
repository before checking anything out of them so it can write lock files.
This won't work for anonymous cvs, because the user you created cannot be
allowed to write to your repository (unless you want anonymous read+write
cvs!). Luckily cvs can be told to write the lock files elsewhere. Just edit
CVSROOT/config and add a "LockDir=/some/dir" line. Then make the directory
you specified, and allow your anonymous cvs user (plus anyone else who needs
to be able to access your repository) to write to it. You will also need to
fiddle with the permissions of the cvs history file, or delete it.
</p>

<h2>Is it really secure?</h2>

<p>
We're pretty sure that the method of using ssh to allow "cvs server" and
only "cvs server" to be executed works as planned. After that, we enter the
murky realm of the cvs client protocol, and how secure that is, is a
matter of some debate. 
</p>

<p>
First of all, be aware that <em>any repository the dedicated cvs user can
access, can be accessed by any remote user</em>. The cvs protocol does not
allow limiting the remote user to a given repository; they can use what
ever cvs root directory they can find. If you have private repositories on
the server, you need to protect them with file permissions.
</p>

<p>
I have been told that if your ssh server is configured to enable the sftp
subsystem, ssh ignores the constraints in the authorized_keys file, and so
remote users will be able to see your entire filesystem. So don't do that
(and make sure that your distribution isn't enabling it by default).
</p>

<p>
Other than that, it should be as secure as any other anonymous cvs setup.
</p>
};

$page=~s/perlmoo/$p/ig;
print $page;
