by Steven J. Owens (unless otherwise attributed)
In general, you should not use CVS's pserver mode to access cvs via network. CVS pserver uses an unencrypted connection and any one can sniff your username and password floating by. Instead, use SSH to protect your connection.
There are two ways to use SSH. The generally better way is to use the built-in support for SSH in your CVS client, if you can ssh directly to the box running CVS. Basically, you set up the following environment variables:
export CVSROOT=username@cvs.example.com:/path/to/repository export CVSRSH=/usr/bin/ssh
Then use cvs normally, and cvs will use ssh to log into the cvs box and use local cvs commands to do everything.
However, maybe you have a CVS server that's inside a secured network, using pserver or not, and you have to get it at from the road. You can't directly ssh to the CVS server, you have to ssh to another box inside the network, then from there get to the CVS server. For this, you need to use ssh portforwarding.
To set up ssh portforwarding you use ssh with the -L option:
ssh -Llocalport:remotehost:remoteport
You have to combine this with a normal ssh session. Specifically, for CVS from outside the firewall:
ssh -L2401:192.168.1.60:2401 username@insidefirewall.example.com
Note that the address is the internal IP address; essentially, what you're doing is telling ssh to "connect me to the insidefirewall server, and while you're at it, forward any packets from localhost:2401 to the address 192.168.1.60:2401", where that address is from the perspective of the insidefirewall server."
One neat bit is that 192.168.1.60 can be any machine that insidefirewall.example.com can reach. So you can essentially "bounce" your network connection off insidefirewall.example.com to reach the other box.
There are two gotchas with this approach.
The first is that you have to set up your current archive using a tunnel, so that it shows as having checked out from localhost. Or you have to write a script to crawl through your current archive and change your checkout to do so. Fortunately for you, I've already written that script, see below.
The second is that you'll also have to use tunneling when inside the firewall. Just do it the same way, only instead of tunneling from your desktop to the firewall, tunnel to the cvs server. Or you can use the script below to change your checkout back.
Okay, three gotchas. The third is that unless the firewall box is also the cvs box, your packets will not be encrypted when going across your internal network, from the remote ssh box to the cvs box. You can fix this by doing a two-step tunnel - from the login session at the remote ssh box, tunnel from localhost (which now is the remote ssh box) to the cvs server. You can even look up the man page for ssh and figure out how to actually have ssh, instead of logging you in, log in and immediately execute your second cvs command.
Okay, four gotchas; your tunnel will last only as long as your ssh session does. So if you just log in via ssh and don't do anything on that connection, sooner or later your server will probably notice and disconnect you. I've never had a problem with this myself, I tend to log in, do the CVS stuff I need to do, and log out.
If you would rather have your tunnel stay up for longer periods of time, you can invoke ssh with a shell command, to keep it busy. Apparently this is common enough that O'Reilly's SSH: the Definitive Guide has a warning against having your ssh command exec a shell script that does an infinite loop. Instead, it suggests you exec a shell script that does "sleep 100000".
$ ssh -f -L 2401:example.com:2401 foo@example.com "sleep 10000" foo@example.com's password:$
What's happening here is that the -f option tells ssh, "I'm going to include a shell command at the end, please open the ssh client connection, log in, and run that shell command, putting the ssh client process in background." So the ssh session may sem to disappear, but it's still running, in background, and will continue to do so until the shell command completes in 100,000 second, or just under 28 minutes.
Also note: If you google on this topic, you'll run into lots of stuff about using ssh-agent. ssh-agent is good stuff to get into, later, because ssh-agent avoids having to manually run the ssh command every time you want to portforward. But for now, just keep it simple and run ssh manually.
Here's a perl script you can use to munge the contents of the CVS/Root files in your checkout. Change the appropriate $newroot and $oldroot lines to whatever values you need.
#! /usr/bin/perl -w eval 'exec /usr/bin/perl -S $0 ${1+"$@"}' if 0; #$running_under_some_shell use File::Find (); # for the convenience of &wanted calls, including -eval statements: use vars qw/*name *dir *prune/; *name = *File::Find::name; *dir = *File::Find::dir; *prune = *File::Find::prune; if (!defined($ARGV[0])) { print "switchcvsroot.pl\n\n" ; print " Replace contents of files named Root with\n" ; print " new cvs repository.\n\n" ; print "Usage: switchcvsroot.pl startdirectory\n" ; print "Usage: switchcvsroot.pl startdirectory overwrite\n\n" ; print " The first form displays contents of all Root files below \n startdirectory.\n\n" ; print " The second form displays and overwrites contents of all\ n Root files.\n\n" ; print " (Note that \"overwrite\" is a literal string.\n" ; exit ; } my $newroot = ":ext:foo\@bar.example.com:/var/lib/cvs" ; my $oldroot = ":ext:foo\@baz.example.com:/var/lib/cvs" ; my $startdir = $ARGV[0] ; my $overwrite = 0 ; if (defined($ARGV[1]) && ($ARGV[1] =~ /overwrite/)) { $overwrite = 1 ; } print "Starting in directory \"$startdir\" and overwriting is \"$overwrite\"\n" ; # Traverse desired filesystems File::Find::find({wanted => \&wanted}, $startdir); exit; sub wanted { my $filename = $_ ; if ($filename =~ /^Root$/) { if ($filename =~ /^Root$/) { $/ = "" ; open (IN, $filename) || die "can't open \"$filename\" for read ing\n" ; my $contents =; chomp($contents) ; close ($filename) ; my $answer = "not matched" ; if ($contents eq $oldroot) { $answer = " matched" ; } else { $answer = "not matched" ; } if ($overwrite) { open(OUT, ">$filename") || die "Can't open \"$filename\" for writing.\n" ; print OUT $newroot ; close(OUT) ; } print "$filename ($answer): $contents\n" ; if ($overwrite) { print " overwriting to: $newroot\n" ; } } }