Net-SCP-Expect-0.16/0000755000076500000240000000000011143071564013374 5ustar erybskistaffNet-SCP-Expect-0.16/CHANGES0000755000076500000240000001443711143071537014403 0ustar erybskistaffRevision history for Perl extension Net-SCP-Expect 0.16 Friday February 6 06:23:51 2009 - Clean distribution of ._* OS X resource forks (i.e. file/..namedfork/rsrc -> ._file) 0.15 Monday February 2 23:01:03 2009 - Support IPv6 host addresses in long and short scp forms. - Add support for -C, -4 and -6 options (compress, force IPv4 and force IPv6, respectively). 0.14 Saturday August 9 20:48:00 2008 - Fixed RT#38081 (password not required if identity_file specified). - Fixed RT#36705 (itest.pl dynamically generates test files during runtime to reduce distribution size). 0.13 Wednesday June 11 05:07:34 2008 - Corrected issue with no exception thrown on failed scp execution after successful login. In general, exception handling should be more robust and behave more consistently now. - If an error_handler is defined, will now immediately return void from a scp() call after the handler has executed. - Fixed RT#18077 (error_handler not handing timeouts properly). Also added immediate return from scp function if customer handler used, as no further actions may be taken. - Fixed RT#22907 (Some options require trailing space). - Fixed RT#22909 (scp command string is underquoted). Use option 'auto_quote' to enable auto-quote scp arguments. Also escapes other single quotes to insure support for path names with single quotes. NOTE: This option is enabled by default, which may break for backward compatibility with scripts that manually quoted input arguments as a work-around for this bug. - Fixed RT#24273 (scp binary is hardcoded, cannot specify a different path to scp). Use option 'scp_path' to specify a different SCP binary. - Fixed RT#34001 (exit value is automatically set to -1 (255) when a program including threads uses 'Net::SCP::Expect' - this breaks Test::*). Removed CHLD signal handler, instead relying on Expect to manage process exit cleanup. - Fixed RT#34503 (Destination can't contain colon ':'). 0.12 Wednesday Nov 24 08:03:33 2004 - Dumb bug fix. 0.11 Mon Nov 22 18:09:29 2004 - Added support for the -i, -s and -o options (identity file, subsystem and option, respectively). - Doc updates and modifications, including a recommendation to use the Net::SFTP module instead of this one if possible. 0.10 Tue Aug 12 13:03:22 2003 - Made the initial password string more generic, allowing text between the word "Password" (or "Passphrase") and the ':' character. Thanks John Vogtle. - Added LICENSE and COPYRIGHT info. - Minor addition to itest.pl script (displays VERSION at start now). 0.09 Sun May 11 12:24:32 2003 - Fixed the Makefile.PL issue - it should actually build and install correctly now. - Minor addition to itest/README file 0.08 Sun Apr 26 17:22:03 2003 - Added a test suite and interactive test program (see the "itest" directory). - Fixed a bug where verbose output was accidentally being picked up by Expect, causing an error. Thanks to Martin Burgess for the spot. - Fixed the regex problems with the scp() method. Thanks to many. - The 'user' option now defaults to $ENV{USER}. - Added a 'protocol' option to set the ssh protocol. Thanks to Emir Rodriguez. - Added a 'terminator' option to specify the character to immediately follow the password. Thanks Rob Coops. - Removed Term::ReadPassword from the prerequisites in the Makefile.PL file, but you'll still need it to run the itest.pl script. - Minor doc changes/additions, including preferred email address change. - Some changes to Makefile.PL. - Added a README file. 0.07 Sun Jul 7 16:08:00 2002 - There was a syntax error (not picked up by "perl -c") with one of the timeout error checks. Thanks to Scott Glaser for the spot and fix. - Added "Term::ReadPassword" to the pre-req's in the Makefile.PL. You can ignore this if you don't care about the test. 0.06 Mon Jun 10 14:00:00 2002 - Fixed a MAJOR bug in which the spawned scp process was being killed by the parent instead of waiting for the operation to complete. Thanks to Ruban Phukan for the spot and *many* thanks to J. David Blackstone for the fix. - Some work on the test program - almost complete. 0.05 Mon Apr 29 15:10:49 2002 - Removed a lame -e check that was causing metacharacters to fail. Any type of non-existent file error will now be treated like any other error. - Modified the error handling yet again. There were two problems that were worked out. I'll just sum up by saying that it should work better now. 0.04 Fri Apr 12 15:52:33 2002 - Documentation fixes and additions (thanks BbBoy) - Added an 'auto_yes' option/method so that you can automatically send 'yes' to any 'yes/no' questions you may receive before finally sending your password (for first time connections, etc). - Added a 'no_check' option to help speed up scp calls by up to 2 seconds per call. Use this only if you're absolutely certain everything is set up correctly. - Created separate timeouts for the 'auto_yes' and 'no_check' options, called 'timeout_auto', and 'timeout_err', respectively. I set their defaults to 1 second (which should be plenty) to improve speed. - The error_handler will now trap errors in the 'expect' call as well as the 'scp' call. - The -q option is *actually* being passed automatically now (oops). Don't fret - this made no difference in any of the testing that I actually did, but there it is for you. 0.03 Wed Apr 10 08:04:17 2002 - Added better error handling in the event that a call to 'scp()' fails - Added an 'error_handler' option/method so that programmers can trap and deal with failed 'scp' calls on their own terms. Just make sure to die, croak, exit, etc. or your program may hang while waiting for terminal input. 0.02 Thu Apr 04 08:44:34 2002 - The -e file test is no longer applied in a remote-to-local scenario - If a login is supplied, then that login is now automatically prepended to the host name, e.g. 'host:file.txt' becomes 'login@host:file.txt', instead of using the current host name. Thanks to Guoben Li for the report. - Regarding the above comment, there is a lot more automatic parsing in general so that you can use less verbose syntax if you want. See the scp method for what now passes as valid syntax. - Tested with Expect 1.15 - seems to work - Fixed this distro so that it unpacks correctly. :) 0.01 Fri Mar 15 14:22:37 2002 - original version; created by h2xs 1.21 with options -A -X -n String Net-SCP-Expect-0.16/Expect.pm0000755000076500000240000004733511143070132015167 0ustar erybskistaff################################################################## # Net::SCP::Expect # # Wrapper for scp, with the ability to send passwords via Expect. # # See POD for more details. ################################################################## package Net::SCP::Expect; use strict; use Expect; use File::Basename; use Carp; use Cwd; use Net::IPv6Addr; BEGIN{ use vars qw/$VERSION/; $VERSION = '0.16'; } # Options added as needed sub new{ my($class,%arg) = @_; my $self = { _host => $arg{host}, _user => $arg{user} || $ENV{'USER'}, _password => $arg{password}, _cipher => $arg{cipher}, _port => $arg{port}, _error_handler => $arg{error_handler}, _preserve => $arg{preserve} || 0, _recursive => $arg{recursive} || 0, _verbose => $arg{verbose} || 0, _auto_yes => $arg{auto_yes} || 0, _terminator => $arg{terminator} || "\n", _timeout => $arg{timeout} || 10, _timeout_auto => $arg{timeout_auto} || 1, _timeout_err => $arg{timeout_err} || undef, _no_check => $arg{no_check} || 0, _protocol => $arg{protocol} || undef, _identity_file => $arg{identity_file} || undef, _option => $arg{option} || undef, _subsystem => $arg{subsystem} || undef, _scp_path => $arg{scp_path} || undef, _auto_quote => $arg{auto_quote} || 1, _compress => $arg{compress} || 0, _force_ipv4 => $arg{force_ipv4} || 0, _force_ipv6 => $arg{force_ipv6} || 0, }; bless($self,$class); } sub _get{ my($self,$attr) = @_; return $self->{"_$attr"}; } sub _set{ my($self,$attr,$val) = @_; croak("No attribute supplied to 'set()' method") unless defined $attr; $self->{"_$attr"} = $val; } sub auto_yes{ my($self,$val) = @_; croak("No value passed to 'auto_yes()' method") unless defined $val; $self->_set('auto_yes',$val); } sub error_handler{ my($self,$sub) = @_; croak("No sub supplied to 'error_handler()' method") unless defined $sub; $self->_set('error_handler',$sub) } sub login{ my($self,$user,$password) = @_; croak("No user supplied to 'login()' method") unless defined $user; croak("No password supplied to 'login()' method") if @_ > 2 && !defined $password; $self->_set('user',$user); $self->_set('password',$password); } sub password{ my($self,$password) = @_; croak("No password supplied to 'password()' method"); $self->_set('password',$password) unless $password; } sub host{ my($self,$host) = @_; croak("No host supplied to 'host()' method") unless $host; # If host is an IPv6 address, strip any enclosing brackets if used $host = substr($host, 1, length($host)-2) if $host && $host =~ /^\[/ && $host =~ /\]$/; $self->_set('host',$host); } sub user{ my($self,$user) = @_; croak("No user supplied to 'user()' method") unless $user; $self->_set('user',$user); } #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # If the hostname is not included as part of the source, it is assumed to # be part of the destination. #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sub scp{ my($self,$from,$to) = @_; my $login = $self->_get('user'); my $password = $self->_get('password'); my $timeout = $self->_get('timeout'); my $timeout_auto = $self->_get('timeout_auto'); my $timeout_err = $self->_get('timeout_err'); my $cipher = $self->_get('cipher'); my $port = $self->_get('port'); my $recursive = $self->_get('recursive'); my $verbose = $self->_get('verbose'); my $preserve = $self->_get('preserve'); my $handler = $self->_get('error_handler'); my $auto_yes = $self->_get('auto_yes'); my $no_check = $self->_get('no_check'); my $terminator = $self->_get('terminator'); my $protocol = $self->_get('protocol'); my $identity_file = $self->_get('identity_file'); my $option = $self->_get('option'); my $subsystem = $self->_get('subsystem'); my $scp_path = $self->_get('scp_path'); my $auto_quote = $self->_get('auto_quote'); my $compress = $self->_get('compress'); my $force_ipv4 = $self->_get('force_ipv4'); my $force_ipv6 = $self->_get('force_ipv6'); ################################################################## # If the second argument is not provided, the remote file will be # given the same (base) name as the local file (or vice-versa). ################################################################## unless($to){ $to = basename($from); } my($host,$dest); # Parse the to/from string. If the $from contains a ':', assume it is a Remote to Local transfer if($from =~ /:/){ ($login,$host,$dest) = $self->_parse_scp_string($from); $from = $login . '@' . $self->_format_host_string($host) . ':'; $from .= "$dest" if $dest; } else{ # Local to Remote transfer ($login,$host,$dest) = $self->_parse_scp_string($to); $to = $login . '@' . $self->_format_host_string($host) . ':'; $to .= "$dest" if $dest; } croak("No login. Can't scp") unless $login; croak("No password or identity file. Can't scp") unless $password || $identity_file; croak("No host specified. Can't scp") unless $host; # Define argument auto-quote my $qt = $auto_quote ? '\'' : ''; # Gather flags. my $flags; $flags .= "-c $qt$cipher$qt " if $cipher; $flags .= "-P $qt$port$qt " if $port; $flags .= "-r " if $recursive; $flags .= "-v " if $verbose; $flags .= "-p " if $preserve; $flags .= "-$qt$protocol$qt " if $protocol; $flags .= "-q "; # Always pass this option (no progress meter) $flags .= "-s $qt$subsystem$qt " if $subsystem; $flags .= "-o $qt$option$qt " if $option; $flags .= "-i $qt$identity_file$qt " if $identity_file; $flags .= "-C " if $compress; $flags .= "-4 " if $force_ipv4; $flags .= "-6 " if $force_ipv6; my $scp = Expect->new; #if($verbose){ $scp->raw_pty(1) } #$scp->debug(1); # Use scp specified by the user, if possible $scp_path = defined $scp_path ? "$qt$scp_path$qt" : "scp "; # Escape quotes if ($auto_quote) { $from =~ s/'/'"'"'/go; $to =~ s/'/'"'"'/go; } my $scp_string = "$scp_path $flags $qt$from$qt $qt$to$qt"; $scp = Expect->spawn($scp_string); unless ($scp) { if($handler){ $handler->($!); return; } else { croak("Couldn't start program: $!"); } } $scp->log_stdout(0); if($auto_yes){ while($scp->expect($timeout_auto,-re=>'[Yy]es\/[Nn]o')){ $scp->send("yes\n"); } } if ($password) { unless($scp->expect($timeout,-re=>'[Pp]assword.*?:|[Pp]assphrase.*?:')){ my $err = $scp->before() || $scp->match(); if($err){ if($handler){ $handler->($err); return; } else { croak("Problem performing scp: $err"); } } $err = "scp timed out while trying to connect to $host"; if($handler){ $handler->($err); return; } else{ croak($err) }; } if($verbose){ print $scp->before() } $password .= $terminator if $terminator; $scp->send($password); } ################################################################ # Check to see if we sent the correct password, or if we got # some other bizarre error. Anything passed back to the # terminal at this point means that something went wrong. # # The exception to this is verbose output, which can mistakenly # be picked up by Expect. ################################################################ my $error; my $eof = 0; unless($no_check || $verbose){ $error = ($scp->expect($timeout_err, [qr/[Pp]ass.*/ => sub{ my $error = $scp->before() || $scp->match(); if($handler){ $handler->($error); return; } else{ croak("Error: Bad password [$error]"); } } ], [qr/\w+.*/ => sub{ my $error = $scp->match() || $scp->before(); if($handler){ $handler->($error); return; } else{ croak("Error: last line returned was: $error"); } } ], ['eof' => sub{ $eof = 1 } ], ))[1]; } else{ $error = ($scp->expect($timeout_err, ['eof' => sub { $eof = 1 }]))[1]; } if($verbose){ print $scp->after(),"\n" } # Ignore error if it was due to scp auto-exiting successfully (which may trigger false positives on some platforms) if ($error && !($eof && $error =~ m/^(2|3)/o)) { if ($handler) { $handler->($error); return; } else { croak("scp processing error occured: $error"); } } # Insure we check exit state of process $scp->hard_close(); if ($scp->exitstatus > 0) { #ignore -1, in case there's a waitpid portability issue if ($handler) { $handler->($scp->exitstatus); return; } else { croak("scp exited with non-success state: " . $scp->exitstatus); } } return 1; } # Break the from/to line into its various parts sub _parse_scp_string{ my($self,$string) = @_; my @parts; my($user,$host,$dest); @parts = split(/@/,$string,2); if(scalar(@parts) == 2){ $user = shift(@parts); } else{ $user = $self->_get("user"); } my $temp = join('',@parts); @parts = split(/:/,$temp); if (@parts) { if (@parts > 1) { $host = join('',@parts[0,1..scalar(@parts)-2]); $dest = $parts[-1]; } else { $host = $parts[0]; } } # scp('file','file') syntax, where local to remote is assumed unless($dest){ $dest = $host; $host = $self->_get("host"); } $host ||= $self->_get("host"); # If host is an IPv6 address, strip any enclosing brackets if used $host = substr($host, 1, length($host)-2) if $host && $host =~ /^\[/ && $host =~ /\]$/; return ($user,$host,$dest); } sub _format_host_string{ my ($self,$host) = @_; # If host is an IPv6 address, verify it is correctly formatted for scp if ($host) { $host = substr($host, 1, length($host)-2) if $host =~ /^\[/ && $host =~ /\]$/; local $@; $host = "[$host]" if eval { Net::IPv6Addr::ipv6_parse($host) }; } return $host; } 1; __END__ =head1 NAME Net::SCP::Expect - Wrapper for scp that allows passwords via Expect. =head1 SYNOPSIS B my $scpe = Net::SCP::Expect->new; $scpe->login('user name', 'password'); $scpe->scp('file','host:/some/dir'); B my $scpe = Net::SCP::Expect->new(host=>'host', user=>'user', password=>'xxxx'); $scpe->scp('file','/some/dir'); # 'file' copied to 'host' at '/some/dir' B my $scpe = Net::SCP::Expect->new(user=>'user',password=>'xxxx'); $scpe->scp('host:/some/dir/filename','newfilename'); B my $scpe = Net::SCP::Expect->new; $scpe->login('user name', 'password'); $scpe->scp('file','[ipv6-host]:/some/dir'); # <-- Important: scp() with explicit IPv6 host in to or from address must use square brackets See the B method for more information on valid syntax. =head1 PREREQUISITES Expect 1.14. May work with earlier versions, but was tested with 1.14 (and now 1.15) only. Term::ReadPassword 0.01 is required if you want to execute the interactive test script. =head1 DESCRIPTION This module is simply a wrapper around the scp call. The primary difference between this module and I is that you may send a password programmatically, instead of being forced to deal with interactive sessions. =head1 USAGE =head2 Bnew(>Ival>, ...B<)> Creates a new object and optionally takes a series of options (see L<"OPTIONS"> below). All L<"OBJECT METHODS"> apply to this constructor. =head1 OBJECT METHODS =head2 B Set this to 1 if you want to automatically pass a 'yes' string to any yes or no questions that you may encounter before actually being asked for a password, e.g. "Are you sure you want to continue connecting (yes/no)?" for first time connections, etc. =head2 BIB<)> This sets up an error handler to catch any problems with a call to 'scp()'. If you do not define an error handler, then a simple 'croak()' call will occur, with the last line sent to the terminal added as part of the error message. The method will immediately return with a void value after your error handler has been called. =head2 BIB<)> Sets the host for the current object =head2 BIB<)> If the login and password are not passed as options to the constructor, they must be passed with this method (or set individually - see 'user' and 'password' methods). If they were already set, this method will overwrite them with the new values. Password will not be changed if only one argument is passed (user). =head2 BIB<)> Sets the password for the current user, or the passphrase for the identify file if identity_file option is specified in the constructor =head2 BIB<)> Sets the user for the current object =head2 B Copies the file from source to destination. If no host is specified, you will be using 'scp' as an expensive form of 'cp'. There are several valid ways to use this method =head3 Local to Remote BIB<);> BIB<);> # Same as previous, with IPv6 host BIB<);> # User already defined BIB<);> # Same as previous, with IPv6 host BIB<);> # User and host already defined BIB<);> # Same as previous BIB<);> # Same as previous; destination will use base name of source =head3 Remote to Local BIB<);> BIB<);> # Same as previous, with IPv6 host BIB<);> BI<[ipv6-host]:source, destination>B<);> # Same as previous, with IPv6 host BI<:source, destination>B<);> =head1 OPTIONS B - Auto-encapsulate all option values and scp from/to arguments in single-quotes to insure that special characters, such as spaces in file names, do not cause inadvertant shell exceptions. Default is enabled. Note: Be aware that this feature may break backward compatibility with scripts that manually quoted input arguments to work around unquoted argument limitations in 0.12 or earlier of this module; in such cases, try disabling it or update your script to take advantage of the auto_quote feature. B - Set this to 1 if you want to automatically pass a 'yes' string to any yes or no questions that you may encounter before actually being asked for a password, e.g. "Are you sure you want to continue connecting (yes/no)?" for first time connections, etc. B - Selects the cipher to use for encrypting the data transfer. B - Compression enable. Passes the -C flag to ssh(1) to enable compression. B - Forces scp to use IPv4 addresses only. B - Forces scp to use IPv6 addresses only. B - Specify the host name. This is now useful for both local-to-remote and remote-to-local transfers. For IPv6 addresses, either regular or square-bracket encapsulated host are allowed (since command-line scp normally expects IPv6 addresses to be encapsulated in square brackets). B - Specify the identify file to use. B - Set this to 1 if you want to turn off error checking. Use this if you're absolutely positive you won't encounter any errors and you want to speed up your scp calls - up to 2 seconds per call (based on the defaults). B