#!/usr/bin/perl -- # rscp: wrapper for scp(1) over pre-forwarded ports. # Tres Hofmeister Sat Sep 21 15:30:25 MDT 2002 # This script is designed to simplify transferring files to a target # host behind a "relay" host using scp(1) and ssh(1) port forwarding. # The script uses a local port already forwarded to the ssh port (TCP/22) # on the target host. The local port is looked up in the user's ssh # configuration file, which has been configured appropriately. The # script then builds appropriate arguments and runs scp(1). # # Configuration: the user must set up a LocalForward directive for each # target host of interest in $HOME/.ssh/config, which automatically # forwards a local port when the user initially logs in to the relay # host. Note: the hostname used on the command line must match the one # in the LocalForward directive, which must be unique in the file. # # Example host section for .ssh/config, additionally using a host "nickname": # Host relay # HostName relay.sub.domain # LocalForward 20001 rhost1:22 # LocalForward 20002 rhost2:22 # # Any normal scp(1) syntax may be used. # Examples: # [First, generally in a separate window]: ssh relay # [Then]: rscp file1 file2 rhost1:/tmp # [Or]: rscp -r dir1 file2 rhost2:/tmp # [Or]: rscp rhost2:file1 /tmp use strict; my($debug, $user, $host, %lport, @forwarded); # External programs used: scp $ENV{'PATH'} = '/bin:/usr/bin'; # For script debugging; print rather than exec() scp command. $debug = 0; # Command line checks. die "$0: Too few arguments\n" if $#ARGV < 1; die "$0: No target host specified\n" unless grep(/:/, @ARGV); # Parse the user's ssh configuration file. parse_ssh_config(); # Process and modify command line arguments. for (@ARGV) { my($user, $dir, $previous_host); # Is this argument a target? next unless /:/; # Was a username specified? if (/@/) { s/^(.+)@(.+)$/$2/; $user = $1; } # Find and the hostname. ($host, $dir) = /^(.+):(.*)$/; next if $host =~ /\//; # Oops, $host is really a qualified pathname. # Reformat this argument using "localhost" rather than $host. $_ = $user ? "$user@" . "localhost:$dir" : "localhost:$dir"; # Multiple remote hosts make no sense when using a forwarded port. die "Error: multiple remote hosts: $previous_host, $host" if $previous_host and $host ne $previous_host; $previous_host = $host; } # Exit if no port was found matching this host string. unless ($lport{$host}) { my(@matches); print "No LocalForward directive found for \"$host\"\n"; if (@matches = grep(/^$host/, @forwarded) ) { print "Perhaps you meant one of:\n"; print " ", join("\n ", grep(/^$host/, @forwarded) ), "\n"; } else { print "Have you configured \$HOME/.ssh/config for this host?\n"; } exit(1); } # Finally, run scp with the appropriate arguments. print "exec('scp', '-P', $lport{$host}, @ARGV)", "\n" if $debug; exec('scp', '-P', $lport{$host}, @ARGV) unless $debug; # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Subroutines. # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Parse the user's standard ssh configuration file. # Note: the host string for each LocalForward must be unique; # there is really no way to get around this. sub parse_ssh_config { my($tag, $host, $remote, $lport, $rport); # Unfortunately, this can be overidden using ssh -F. my($ssh_config) = $ENV{'HOME'} . "/.ssh/config"; # Open and read the configuration file. open(SSHCONFIG, "$ssh_config") or die "open: $ssh_config: $!\n"; while () { s/\s*#.*//; # Strip comments. s/^\s*//; # Strip leading white space. s/\s*$//; # Strip trailing white space; includes "\n". next if /^\s*$/; # Skip blank lines. # Only LocalForward directives for the ssh port are interesting. next unless /LocalForward.+:22$/; ($tag, $lport, $remote) = split; ($host, $rport) = split(':', $remote); $lport{$host} = $lport; # List of hosts for which we are forwarding port 22. push(@forwarded, $host); } close(SSHCONFIG); }