Transfer file from Remote machine to local machine using Net::OpenSSH - perl

I have built a script which should get a file from remote machine to local machine.
#!/usr/bin/perl
use strict;
use warnings;
use Net::OpenSSH;
use Data::Dumper;
my $local_dir = "/LOCAL/DIR/LOCATION/"
print "[LOCAL DIR]-> $local_dir\n";
my $remote_dir = "/REMOTE/DIR/LOCATION/";
print "[REMOTE DIR]-> $remote_dir\n";
my ($host, $user, $password) = ("remote.machine.ip.address", "userid", "password");
my $ssh = Net::OpenSSH->new($host,
user => $user,
password => $passwd,
master_opts => [-o => "StrictHostKeyChecking=no"]
);
$ssh->error and die "Couldn't establish SSH connection: ". $ssh->error;
my #file = $ssh->capture("cd $remote_dir && ls -1tr | grep Report | tail -1");
print "[FILE]:\n".Dumper(\#file);
$ssh->scp_get({glob => 1}, "$remote_dir$file[0]", $local_dir)
or die "scp failed: " . $ssh->error;
undef $ssh;
In the above code its able to print the Dumper value for #file but unable to get the file in local system.
Here is the error it throws at the end:
[FILE]:
$VAR1 = [
'Report_Managable_20200705.csv
'
];
scp: /REMOTE/DIR/LOCATION/Report_Managable_20200705.csv
protocol error: expected control record
scp failed: scp failed: child exited with code 1 at file_get_test.pl line 22.
Can anybody help me to fix this issue. TIA.

The list returned by $ssh->capture() has new lines at the end of each item. Try use chomp #file to remove the newlines.

Related

Retry SSH to Host if Connection to the Host Fails in Perl

I have a script, which does SSH to the server and execute some command (In this script, for demonstration I am running Perl print statement with Hello message).
Here is my script:
#!/usr/bin/perl
use strict; use warnings;
use Net::OpenSSH;
$Net::OpenSSH::debug = ~0;
BEGIN {
open my $out, '>', '/tmp/debug.txt' or warn $!;
$Net::OpenSSH::debug_fh = $out;
$Net::OpenSSH::debug = -1;
}
my #hosts = ("ipaddress1","ipaddress2");
my $ssh;
my $command = "perl -e 'print \"Hello..\"'";
foreach my $n (#hosts) {
#Here if connection to the host($n) fails, is it possible to retry again
$ssh = Connect($n, "user", "passwd");
$ssh->capture($command);
print "Done execution in Host: $n\n";
}
undef $ssh;
print "**End**\n";
sub Connect {
my ( $host, $user, $passwd ) = #_;
my $ssh = Net::OpenSSH->new($host, user=>$user, password=>$passwd);
$ssh->error and die "Couldn't establish SSH connection: " . $ssh->error;
return $ssh;
}
Whenever I execute this script, sometimes it successfully prints below message:
Done execution in Host: ipaddress1
Done execution in Host: ipaddress2
**End**
But sometimes cannot do ssh to host (either ipaddress1 or ipaddress2) and gives following message:
Couldn't establish SSH connection: unable to establish master SSH connection: master process exited unexpectedly at script.pl ....
Its being get died in Connect subroutine (cause I couldn't trace, opened question here).
So, is there any way if I cannot connect(ssh) to the host, retry can be done after certain period of time (for n number times) instead of printing error message and make the script die?
OpenSSH provides a nice interface for errors. I'd start by looking at the examples on the cpan page. Try the following
foreach my $n (#hosts) {
#Here if connection to the host($n) fails, is it possible to retry again
$ssh = Connect($n, "user", "passwd", 3);
$ssh->capture($command);
print "Done execution in Host: $n\n";
}
undef $ssh;
print "**End**\n";
sub Connect {
my ( $host, $user, $passwd , $retry_limit ) = #_;
my $timeout = 10;
my $con;
while ( $retry_limit-- > 0 )
{
$con = Net::OpenSSH->new($host,
user=>$user,
password=>$passwd,
timeout=> $timeout,
);
last unless $con->error();
}
die "unable to connect ".$con->error() if retry_limit <0;
return $con;
}

Perl Net::SSH2 pubkey authentication issue

I am trying to connect to a remote SSH server using Net::SSH2. Commandline ssh works fine. I can not seem to figure out the correct auth_hostbased parameters, though
This is my code:
use Net::SSH2;
my $ssh = Net::SSH2->new();
$ssh->debug(1);
$ssh->trace(-1);
$ssh->connect('remotehost.remotedomain.tld') or die;
$ssh->auth_hostbased('username',
'ssh-rsa AAAAB3Nz[..]C0JoaFF9 root#myhost',
'-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,FA97214E87562096A7E480C82DAE5EB4
XIMKnj9k[..]kpRo5V
-----END RSA PRIVATE KEY-----',
'myhost.mydomain.tld',
'username',
'keypassword') or die;
The snippet dies # $ssh->auth_hostbased with just a 'Net::SSH2::DESTROY object 0xe17de0'. Setting trace does not seem to matter. Replacing die with $ssh->die_with_error throws a 'die_with_error is not a valid Net::SSH2 macro'. Downloading the current 0.53 version of Net:SSH2 did not work as the script no longer compiles: 'Net::SSH2 object version 0.44 does not match bootstrap parameter 0.53'
Any help on the correct parameter format or an alternative module is appreciated.
Why not using Net::OpenSSH ?
That is a simple ssh wrapper script, i wrote some time ago:
#!/usr/bin/perl
#Simple SSH Remote Executor using Net::OpenSSH Library
use warnings;
use strict;
use Net::OpenSSH;
# see http://search.cpan.org/~salva/Net-OpenSSH-0.62/lib/Net/OpenSSH.pm#DEBUGGING
$Net::OpenSSH::debug = undef;
use Getopt::Long;
my $timeout = 10;
my ($username,$identity,$hostname,$command) = undef;
my $uid=getpwuid($<);
my $ctl_dir=qq{/tmp/.libnet-puppet-$uid};
my $ctl_mode=0700;
if ( ! -d $ctl_dir ) { mkdir( $ctl_dir,$ctl_mode ) };
open my $stderr_fh, '>>', '/dev/null' or die $!;
sub print_help{
print qq{\nusage: $0 [options] -h Hostname
-u username
-i identity
-c command
long options are supported !
};
exit (1);
}
GetOptions ("hostname=s" => \$hostname, # string
"username=s" => \$username, # string
"identity=s" => \$identity, # string
"command=s" => \$command) # string
or print_help;
if ( not defined $username or not defined $identity or not defined $hostname or not defined $command ) { print_help };
my $port = q{22};
my $user = $username;
my $ssh;
my $cmd = qq{$command};
my $options = {
host => $hostname,
user => $user,
port => $port,
default_stderr_fh => $stderr_fh,
ctl_dir => $ctl_dir,
master_opts => [
-o => "UserKnownHostsFile=/dev/null",
-o => "StrictHostKeyChecking=no",
-o => qq{IdentityFile=$identity},
],
timeout => $timeout };
#ALARM Timer timeout handling
$SIG{ALRM} = sub {
printf( "%s\n", qq{invalid-timeout-connecting-to-node-$hostname});
exit(1);
};
#init alarm timer ;-)
alarm( $timeout );
$ssh = Net::OpenSSH->new( %{$options} )
or $ssh->error and die "Couldn't establish SSH connection: ". $ssh->error;
my (#out, $err) = $ssh->capture2({ timeout => 10 }, $cmd);
die("Error: %s\n", $err) if defined $err;
if ( (scalar(#out)) eq 0 ) {
printf( "%s\n", qq{invalid-empty-string-received-by-node-$hostname});
exit(1);
}
foreach my $line ( #out ) {
$line =~ s/^\s{1,}//;
printf ("%s",$line);
}
Install it using cpanm (cpanm Net::OpenSSH) or as debian package "libnet-openssh-perl".
See "man ssh_config" for available master options.
I think that script will be of great help though.
Rgds. Franz

Displaying a portion of the configuration (--More)

I have got this error when i try to connect to my switch !
use Net::OpenSSH;
use warnings;
use Expect;
my $password = 'admin';
my $enable = '';
my $ip = '192.16.25.39';
my $username='user';
my $ssh = Net::OpenSSH->new("$username:$password\#$ip", timeout => 200) ;
$ssh->error and die "unable to connect to remote host: ". $ssh->error;
my $output = $ssh->capture({stdin_data => "enable\n"."admin%\n"."show vlan"."\n"});
if ($output) {print $output . ' ';}
my $line;
print "\n";
# closes the ssh connection
$ssh->close();
I have tried this with the Expect module:
use Net::OpenSSH;
if ($output) {
print $output . ' ';
my $expect = Expect->init($output);
$expect->raw_pty(1);
#$expect->debug(2);
my $debug and $expect->log_stdout(1);
while(<$pty>) {
print "$. $_ "
}
}
which produces this error:
Can't bless non-reference value at /usr/local/share/perl5/Expect.pm line 202 (#1) (F) Only hard references may be blessed. This is how Perl "enforces" encapsulation of objects. See perlobj. Uncaught exception from user code: Can't bless non-reference value at /usr/local/share/perl5/Expect.pm line 202. at /usr/local/share/perl5/Expect.pm line 202. Expect::exp_init("Expect", "\x{d}\x{a}witch>enable\x{d}\x{a}password:\x{d}\x{a}switch#show vlan\x{d}\x{a}\x{d}\x{a}VLA"...) called at b.pl line 19 "
This might be a better approach to your problem. There is a Net::Telnet::Cisco module that simplifies a lot of the interaction with the remote router. Apparently you can first set up an encrypted SSH connection with Net::OpenSSH and then use the filehandle from that connection to start a Net::Telnet::Cisco session.
So I think something like this would be more promising than trying to use Net::OpenSSH directly:
use Net::OpenSSH;
use Net::Telnet::Cisco;
my $password = 'admin';
my $enable = '';
my $ip = '192.16.25.39';
my $username='user';
my $ssh = Net::OpenSSH->new("$username:$password\#$ip", timeout => 200) ;
my ($pty, $pid) = $ssh->open2pty({stderr_to_stdout => 1})
or die "unable to start remote shell: " . $ssh->error;
my $cisco = Net::Telnet::Cisco->new(
-fhopen => $pty,
-telnetmode => 0,
-cmd_remove_mode => 1,
-output_record_separator => "\r");
my #vlan = $cisco->cmd("show vlan");
I am not familiar with the ins and outs of configuring Cisco routers, so you'll have to take it up from here, but this looks to me like a much easier route to get what you need.

Output Lines are missing

I have written small program for getting output from router. but the starting content of the output is missing in output file.
#!C:\strawberry\perl\bin\perl -w
open ( OUTPUT,"> D:\\Routerbkp\\router\\abc.txt" );
use Control::CLI;
# Create the object instance for SSH
$cli = new Control::CLI('SSH');
# Connect to host - Note that with SSH,
# authentication is part of the connection process
$cli->connect( Host => '10.0.0.1',
Username => 'abc',
Password => 'abc',
PrivateKey => 'C:\Users\Administrator\.ssh\key_10.0.0.1_22.pub',
);
# Send a command and read the resulting output
$output1 = $cli->cmd("terminal length 0");
sleep(1);
$output2 = $cli->cmd("show running-config");
sleep(5);
$output8 = $cli->cmd("show alarm current");
sleep(2);
$cli->disconnect;
print OUTPUT $output1;
print OUTPUT $output2;
print OUTPUT $output8;
If you're having a problem with your code, your first port of call is ALWAYS use strict; and use warnings;.
Then - fix that open statement. Try in the style of:
open ( my $output_fh, ">", "D:\\Routerbkp\\router\\abc.txt" ) or die $!;
You probably also want to trap any errors from $cli -> connect() because there's no guarantee that's worked.
my $result = $cli -> connect ( ...
if ( not $result ) { print "Connect failed: ", $cli -> errormode(), ":", $cli -> errormsg(), "\n"; };

Perl Net::SSH::Expect not printing out all expected output

I am using expect in perl to get interface information from my router. When I run the command on the remote router its missing about 10-15 lines that should be there. Not sure why its stopping, any ideas?
#!/usr/bin/perl -w
#use strict;
use warnings;
use Net::SSH::Expect;
my $ssh = Net::SSH::Expect->new (
host => "10.10.10.10",
user => 'user',
password => 'pass'
);
my $login_output = $ssh->login();
if ($login_output !~ /router#/) {
die "Login has failed. Login output was $login_output";
}
#$ssh->run_ssh() or die "SSH process couldn't start: $!";
$ssh->send("show int g2/1");
my $line;
while (defined ($line = $ssh->read_line()) ) {
print $line."\n";
}
Net::SSH::Expect is not reliable. Use other module as Net::OpenSSH, Net::SSH2, Net::SSH::Any or just Expect
use Net::OpenSSH;
my $ssh = Net::OpenSSH->new("10.10.10.10",
user => 'user',
password => 'pass',
timeout => 60 );
my $output = $ssh->capture('show int g2/1');
# or for some non-conforming SSH server implementations rather
# common in network equipment you will have to do...
my $output = $ssh->capture({stdin_data => "show int g2/1\n"});
$ssh->error and die "unable to run remote command: " . $ssh->error;
I suspect since you are dealing with a router, you want to enable raw_pty => 1 like the Net::SSH::Expect documentation suggests. Also, it might be easier for you to use the ->exec calls instead of the ->send + read_line.
For debugging further, pass in the log_stdout to the Net::SSH::Expect constructor and see if you can detect anything awry happening. Why did you comment out 'use strict'? Always 'use strict' and 'use warnings'