package Net::SCP; use strict; use vars qw($VERSION @ISA @EXPORT_OK $scp); use Exporter; use Carp; use File::Basename; use String::ShellQuote; use IO::Handle; use Net::SSH qw(sshopen3); use IPC::Open3; @ISA = qw(Exporter); @EXPORT_OK = qw( scp iscp ); $VERSION = '0.02'; $scp = "scp"; =head1 NAME Net::SCP - Perl extension for secure copy protocol =head1 SYNOPSIS #procedural interface use Net::SCP qw(scp iscp); scp($source, $destination); iscp($source, $destination); #shows command, asks for confirmation, and #allows user to type a password on tty #OO interface $scp = Net::SCP->new( "hostname", "username" ); #with named params $scp = Net::SCP->new( { "host"=>$hostname, "user"=>$username } ); $scp->set( cwd => "/dir", verbose => "yes", interact => "yes" ); $scp->get("filename") or die $scp->{errstr}; $scp->put("filename") or die $scp->{errstr}; #Net::FTP-style $scp = Net::SCP->new("hostname"); $scp->login("user"); $scp->cwd("/dir"); $scp->size("file"); $scp->get("file"); $scp->quit; =head1 DESCRIPTION Simple wrappers around ssh and scp commands. =head1 SUBROUTINES =over 4 =item scp SOURCE, DESTINATION Can be called either as a subroutine or a method; however, the subroutine interface is depriciated. Calls scp in batch mode, with the B<-B> B<-p> B<-q> and B<-r> options. Returns false upon error, with a text error message accessable in $scp->{errstr}. Returns false and sets the B attribute if there is an error. =cut sub scp { my $self = ref($_[0]) ? shift : {}; my($src, $dest, $interact) = @_; my $flags = '-p'; $flags .= 'r' unless &_islocal($src) && ! -d $src; my @cmd = ( $scp, $flags, $src, $dest ); if ( ( defined($interact) && $interact ) || ( defined($self->{interact}) && $self->{interact} ) ) { print join(' ', @cmd), "\n"; unless ( &_yesno ) { $self->{errstr} = "User declined"; return 0; } } else { $flags .= 'qB'; } my($reader, $writer, $error ) = ( new IO::Handle, new IO::Handle, new IO::Handle ); $writer->autoflush(1);# $error->autoflush(1); my $pid = open3($writer, $reader, $error, @cmd ); waitpid $pid, 0; if ( $? >> 8 ) { chomp(my $errstr = <$error>); $self->{errstr} = $errstr; 0; } else { 1; } } =item iscp SOURCE, DESTINATION Can be called either as a subroutine or a method; however, the subroutine interface is depriciated. Prints the scp command to be execute, waits for the user to confirm, and (optionally) executes scp, with the B<-p> and B<-r> flags. Returns false and sets the B attribute if there is an error. =cut sub iscp { if ( ref($_[0]) ) { my $self = shift; $self->set( 'interact' => 1 ); $self->scp(@_); } else { scp(@_, 1); } } sub _yesno { print "Proceed [y/N]:"; my $x = scalar(); $x =~ /^y/i; } sub _islocal { shift !~ /^[^:]+:/ } =back =head1 METHODS =over 4 =item new HOSTNAME [ USER ] | HASHREF This is the constructor for a new Net::SCP object. You must specify a hostname, and may optionally provide a user. Alternatively, you may pass a hashref of named params, with the following keys: host - hostname user - username verbose - bool interactive - bool cwd - current working directory on remote server =cut sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self; if ( ref($_[0]) ) { $self = shift; } else { $self = { 'host' => shift, 'user' => ( scalar(@_) ? shift : '' ), 'verbose' => 0, 'interactive' => 0, 'cwd' => '', }; } bless($self, $class); } =item login [USER] Compatibility method. Optionally sets the user. =cut sub login { my($self, $user) = @_; $self->{'user'} = $user; } =item cwd CWD Sets the cwd (used for a subsequent get or put request without a full pathname). =cut sub cwd { my($self, $cwd) = @_; $self->{'cwd'} = $cwd || '/'; } =item get REMOTE_FILE [, LOCAL_FILE] Uses scp to transfer REMOTE_FILE from the remote host. If a local filename is omitted, uses the basename of the remote file. =cut sub get { my($self, $remote, $local) = @_; $remote = $self->{'cwd'}. "/$remote" if $self->{'cwd'} && $remote !~ /^\//; $local ||= basename($remote); my $source = $self->{'host'}. ":$remote"; $source = $self->{'user'}. '@'. $source if $self->{'user'}; scp($source,$local); } =item size FILE Returns the size in bytes for the given file as stored on the remote server. Returns 0 on error, and sets the B attribute. In the case of an actual zero-length file on the remote server, the special value '0e0' is returned, which evaluates to zero when used as a number, but is true. (Implementation note: An ssh connection is established to the remote machine and wc is used to determine the file size.) =cut sub size { my($self, $file) = @_; $file = $self->{'cwd'}. "/$file" if $self->{'cwd'} && $file !~ /^\//; my $host = $self->{'host'}; $host = $self->{'user'}. '@'. $host if $self->{'user'}; my($reader, $writer, $error ) = ( new IO::Handle, new IO::Handle, new IO::Handle ); $writer->autoflush(1);# $error->autoflush(1); #sshopen2($host, $reader, $writer, 'wc', '-c ', shell_quote($file) ); my $pid = sshopen3($host, $writer, $reader, $error, 'wc', '-c ', shell_quote($file) ); waitpid $pid, 0; if ( $? >> 8 ) { chomp(my $errstr = <$error>); $self->{errstr} = $errstr || "wc exited with status ". $?>>8; 0; } else { chomp( my $size = <$reader> || 0 ); if ( $size =~ /^\s+(\d+)/ ) { $1 ? $1 : '0e0'; } else { $self->{errstr} = "unparsable output from remote wc: $size"; 0; } } } =item put LOCAL_FILE [, REMOTE_FILE] Uses scp to trasnfer LOCAL_FILE to the remote host. If a remote filename is omitted, uses the basename of the local file. =cut sub put { my($self, $local, $remote) = @_; $remote ||= basename($local); $remote = $self->{'cwd'}. "/$remote" if $self->{'cwd'} && $remote !~ /^\//; my $dest = $self->{'host'}. ":$remote"; $dest = $self->{'user'}. '@'. $dest if $self->{'user'}; warn "scp $local $dest\n"; scp($local, $dest); } =item binary Compatibility method: does nothing; returns true. =cut sub binary { 1; } =back =head1 AUTHORS Ivan Kohler Anthony Deaver =head1 BUGS Still has no-OO cruft. In order to work around some problems with commercial SSH2, if the source file is on the local system, and is not a directory, the B<-r> flag is omitted. It's probably better just to use SSH1 or OpenSSH The Net::FTP-style OO stuff is kinda lame. And incomplete. =head1 SEE ALSO scp(1), ssh(1) =cut 1;