#!/usr/bin/perl

# modified for ssh+chroot setup by Anton Berezin <tobez@plab.ku.dk>

#--
#   tunable variables
#--

# change this!
$tune_cvs_server_name = "server.host.server.domain";

# where cvs program is located on the client system
$tune_local_cvs_cmd = "/usr/bin/cvs";

# remote pserver port to use
# for explanation, see
#    http://www.prima.eu.org/tobez/cvs-howto.html#inetd
$tune_remote_cvs_port = 2410;

# local pserver port;  you probably don't want to change this
$tune_local_cvs_port = 2401;

# ssh command to use; the default is good for UNIX clients
$tune_ssh_cmd = "ssh1";

# the user on the server side cvs runs as
$tune_ssh_user = "cvs";

#--
#   end of tunable variables
#   there is no need to modify anything below
#--

#
# $Id: scvs,v 1.6 1999/02/09 16:37:04 tim Exp $
#
# (c) 1999, Tim Hemel <tim@n2it.net>
#
# SCVS - "secure cvs"
#
# scvs [ -d cvsroot ] [ cvsoptions ] cvscommand [ cvscommandoptions ]
#
# This program executes a cvs client and lets it run its traffic through an
# encrypted SSH tunnel.
#
# The remote repository can be specified on the commandline, or in the CVSROOT
# environment variable. This should be done with -d cvsroot, where cvsroot is
# the remote repository. For example: :pserver:cvs@cvs.n2it.net:/cvs. This
# option MUST be the FIRST option to scvs if it is given.
#
# After having checked out a file, the CVS/Root file contains the fake tunnel
# cvs server on the localhost. This saves you from using the -d option all the
# time. Be careful however when using both scvs and cvs on the same directory.
# Preferably all cvs traffic should be done with scvs.
#

# TODO:
#
# * Implement better error detection and recovery.
# * Better parsing of the repository (it will not detect strange syntaxes).
# * Better command line option parsing
# * Add more options that are now still environment variables. For example,
#   -S should contain $SSH_DEFAULT_HOST. for example: -S ssh@cvs.n2it.net:22,
#   -S ssh@, -S :22, -S cvs.n2it.net, or -S ssh@:22.
#

#
# Note: there is no way to specify a different port number to cvs. This means
# that all scvs clients on the same machine need to share port 2041.
# This is possible, only the ssh tunneling will fail, so our program should
# detect that and continue anyway.
# A successful connection is then only possible if the other users know the
# password for $SSH_USER@$SSH_HOST, or if there is no password.
#

#############################################################################
# CVS settings

$CVS_CMD=$tune_local_cvs_cmd;

$CVS_PORT=$tune_remote_cvs_port;
$CVS_LOCAL_PORT=$tune_local_cvs_port;

# the values below will be needed only if the repository is specified via the
# command line or the CVSROOT environment variable, and in those cases they
# will be extracted from there. However, for funny results you can uncomment
# these two lines.
# $CVS_HOST="cvs.n2it.net";
# $CVS_USER="tim";

#############################################################################
# SSH settings

# ssh2 does not seem to work with our -L port:host:hostport argument, so make
# sure we will use ssh1.
$SSH_CMD=$tune_ssh_cmd;

# This should be the user on whose behalf the tunneling is made. It is
# typically a user that cannot do any harm, has no password and uses a program
# like nologin (but one that will wait) as a shell.
$SSH_USER=$tune_ssh_user;

# This value should also automagically be set from the repository name.
# However, if that host is not running sshd, you may want to tunnel through
# another host and modify and uncomment the line below.
# $SSH_HOST=$CVS_HOST

# This value is used if the repository cannot be determined from the
# commandline or the CVSROOT variable. Modify this for your local situation.
$SSH_DEFAULT_HOST=$tune_cvs_server_name;

# Port at which the sshd on the remote server runs. Default is 22.
# $SSH_PORT=22;

# This should be left unmodified, as it makes no sense changing this. Unless
# some future version (or perhaps even the current version) of cvs allow you
# to specify the remote repository's port.
$SSH_LOCAL_PORT=$CVS_LOCAL_PORT;


#############################################################################
# & parse_repository ({{rep}})
# . extracts the method, user, host, port and directory from {{rep}}.
# . There are four possibilities:
#   - /path/to/repository
#   - :method:/path/to/repository
#   - :user@hostname:/path/to/repository
#   - :method:user@hostname:/path/to/repository
#
# . This function is far from perfect and will produce strange results with
#   non-standard repositories. Has only been tested for :pserver: method.
#
sub parse_repository
{
  my $rep = $_[0];
  my ($dir,$user,$host,$method);

  # determine the directory
  $rep =~ s/:(\/.*)$// && do { $dir=$1; };
  if (not $dir)
    { $rep =~ s/(\/.*)$// && do {$dir = $1; }; }

  # determine the hostname and the username
  $rep =~ s/:([^:]*)@(.*)$// && do { $user = $1; $host = $2; };
  if (not $host)
    { $rep =~ s/:([^:]+)$// && do { $host = $1; }; }
  
  # all that is left now is the method
  $rep =~ s/^:([^:]*)// && do { $method = $1; };

  # if there is still anything left, we have an error, warn the user
  if ($rep)
    { print STDERR "Warning: repository parsed wrong ('$rep' ignored).\n"; }

  #print STDERR "DEBUG: dir=$dir, user=$user, host=$host, method=$method\n";
  return ($method, $user, $host, $dir);
}


#############################################################################
# main

# should be changed to a general cmdline parsing routine.
# get the repository's name from the commandline or the CVSROOT environment
# variable.
if ($ARGV[0] eq "-d")
{
  $rep="$ARGV[1]";
  shift; shift;
}
else
{
  $rep = $ENV{'CVSROOT'};
}

#print STDERR "DEBUG: rep = $rep\n";

# parse the repository
($method, $user, $host, $dir) = parse_repository $rep;
# print "met: $method, user: $user, host: $host, dir: $dir\n";

# construct the local fake cvs server name.
if ($method) { $cvs_serv = ":$method:"; }
if ($user)   { $cvs_serv .= "$user\@"; }
if ($rep)    { $cvs_serv .= "localhost:"; }
if ($dir)    { $cvs_serv .= $dir; }

# print STDERR "DEBUG: cvs_serv = $cvs_serv\n";

# construct the tunneling command
$SSH_HOST |= $host;
#print STDERR "DEBUG: SSH_HOST=$SSH_HOST\n";
$SSH_HOST |= $SSH_DEFAULT_HOST;
#print STDERR "DEBUG: SSH_HOST=$SSH_HOST\n";

if ($SSH_USER)
  { $ssh_serv="$SSH_USER\@$SSH_HOST"; }
else
  { $ssh_serv="$SSH_HOST"; }

# print "ssh_serv: $ssh_serv\n";

$tunnel_cmd = "$SSH_CMD $ssh_serv -q -x -f"
            . ( ($SSH_PORT) ? " -p $SSH_PORT" : "" )
            . " -L $SSH_LOCAL_PORT:$SSH_HOST:$CVS_PORT open";

# print "tunnel_cmd: $tunnel_cmd\n";

# execute the tunneling, and read the response from the server
open (TUNNELSH,"$tunnel_cmd |") or die "Could not execute $tunnel_cmd!";
chomp ($magicword = <TUNNELSH>);
#print STDERR "magicword = $magicword!\n";

# Now we can call system to execute the cvs command.
  # print STDERR "Doing: $CVS_CMD|", ( ($cvs_serv) ? ('-d |', "$cvs_serv|") : () ) , "@ARGV", "|\n";
$exitcode =
  system "$CVS_CMD", ( ($cvs_serv) ? ('-d', $cvs_serv) : () ) , @ARGV;

if ($exitcode) { print STDERR "Could not execute CVS command!\n"; }

# close the tunnel
#print STDERR "DEBUG: ", ( "$SSH_CMD $ssh_serv -q -x -f"
#            . ( ($SSH_PORT) ? " -p $SSH_PORT" : "" )
#            . " $magicword" );
# print STDERR "before close\n";
system ( "$SSH_CMD $ssh_serv -q -x -f"
            . ( ($SSH_PORT) ? " -p $SSH_PORT" : "" )
            . " $magicword" );

# print STDERR "right after close\n";
