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

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'

Related

use of uninitialized value. How can I fix this error?

#!/usr/bin/perl
use Net::SSH::Expect;
use warnings;
use strict;
#my($stdout, $stderr, $exit) = $ssh->cmd("ls -l /home/$usr")
# Making an ssh connection with user-password authentication
# 1) construct the object
my $ssh = Net::SSH::Expect->new (
host => "host",
password=> 'pwd',
user => 'user',
raw_pty => 1
#Expect=>log_file("finally.txt")
);
# 2) logon to the SSH server using those credentials.
# test the login output to make sure we had success
my $login_output = $ssh->login();
if ($login_output !~ /Welcome/) {
die "Login has failed. Login output was $login_output";
}
# disable terminal translations and echo on the SSH server
# executing on the server the stty command:
$ssh->exec("stty raw -echo");
my $stdout = $ssh->send(chr(13));
my $stdout2 = $ssh->send("SDT-FI");
my $stdout3 = $ssh->send("ENG");
my $stdout4 = $ssh->send('SORT FI-WIP "84144"');
my $stdout5 = $ssh->send(chr(13));
my $stdout6 = $ssh->send("OFF");
my $stdout7 = $ssh->send(chr(13));
print($stdout3);
#$expect->log_file("adp-n.txt");
#y $line;
# returns the next line, removing it from the input stream:
# while ( defined ($line = $ssh->read_all()) ) {
# print $line . "\n";
#}
So i am trying to print $stdout3 so i can get information about the output
but i keep getting " use of uninitialized value $stdout3 in print at connnn3.pl line 50"
is there something in my code wrong?
how can i fix this?
UPDATE, SOLVED!
The reason why it was returning "use of uninitialized value" was because the function
send()
Is void, so instead i used
exec()
And that solved it
From the documentation of Net::SSH::Expect:
void send($string) - sends $string to the SSH server, returns nothing
Thus, send obviously returns nothing (void) and that's why you get this warning when trying to print the (non-existing) return value of send. If you want to get data back from the server use peek, eat, read_all or similar as documented.

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.

Resolving issue with Net::OpenSSH and passing multiple commands to a router

I'm working on moving a Perl script that pushed commands to routers. We have turned off telnet, so I'm working on getting SSH to work. After looking at a number of SSH libraries in Perl, I've opted to use Net::OpenSSH. I have no problem logging in and passing commands to the routers, but the problem I'm having is with entering config mode and subsequently passing a command.
The problem is that with each command entered, the underlying system appears to logout then reenter with the next subsequent command. For example with a Juniper router I'm trying to do the following:
edit private
set interfaces xe-1/3/2 description "AVAIL: SOMETHING GOES HERE"
commit
exit
quit
Tailing the syslog from the router I'm seeing something like this...
(...)
UI_LOGIN_EVENT: User 'tools' login, class 'j-remote-user' [65151], ssh-connection 'xxx.xxx.xxx.xxx 42247 xxx.xxx.xxx.xxx 22', client-mode 'cli'
UI_CMDLINE_READ_LINE: User 'tools', command 'edit private '
UI_DBASE_LOGIN_EVENT: User 'tools' entering configuration mode
UI_DBASE_LOGOUT_EVENT: User 'tools' exiting configuration mode
UI_LOGOUT_EVENT: User 'tools' logout
UI_AUTH_EVENT: Authenticated user 'remote' at permission level 'j-remote-user'
UI_LOGIN_EVENT: User 'tools' login, class 'j-remote-user' [65153], ssh-connection 'xxx.xxx.xxx.xxx 42247 xxx.xxx.xxx.xxx 22', client-mode 'cli'
UI_CMDLINE_READ_LINE: User 'tools', command 'set interfaces '
UI_LOGOUT_EVENT: User 'tools' logout
(...)
As you notice I'm getting a LOGOUT_EVENT after each command entered. Of course exiting config mode immediately after entering it causes the set interfaces command to fail as it's no longer in config mode.
The Perl code I'm using is as follows...
#!/usr/bin/perl -w
use strict;
use lib qw(
/usr/local/admin/protect/perl
/usr/local/admin/protect/perl/share/perl/5.10.1
);
use Net::OpenSSH;
my $hostname = "XXXXX";
my $username = "tools";
my $password = "XXXXX";
my $timeout = 60;
my $cmd1 = "edit private";
my $cmd2 = 'set interfaces xe-1/3/2 description "AVAIL: SOMETHING GOES HERE"';
my $cmd3 = "commit";
my $cmd4 = "exit";
my $ssh = Net::OpenSSH->new($hostname, user => $username, password => $password, timeout => $timeout,
master_opts => [-o => "StrictHostKeyChecking=no"]);
$ssh->error and die "Unable to connect to remote host: " . $ssh->error;
my #lines = eval { $ssh->capture($cmd1) };
foreach (#lines) {
print $_;
};
#lines = eval { $ssh->capture($cmd2) };
foreach (#lines) {
print $_;
};
#lines = eval { $ssh->capture($cmd3) };
foreach (#lines) {
print $_;
};
#lines = eval { $ssh->capture($cmd4) };
foreach (#lines) {
print $_;
};
$ssh->system("quit");
The sequence of events is the same as when telnet was used. The only real change was in using SSH objects verses Telnet objects. I'm stumped. Any ideas you could provide would be quite helpful.
[SOLVED, sort of]
The suggestion let Net::Telnet do the driving was the correct one. The following code works...
#!/usr/bin/perl -w
use strict;
use Net::OpenSSH;
use Net::Telnet;
use Data::Dumper;
my $promptEnd = '/\w+[\$\%\#\>]\s{0,1}$/o';
my $cmd1 = "show system uptime | no-more";
my $cmd2 = "show version brief | no-more";
my $hostname = "xxx.xxx";
my $username = "xxxxxxx";
my $password = "xxxxxxx";
my $timeout = 60;
my $ssh = Net::OpenSSH->new(
$hostname,
user => $username,
password => $password,
timeout => $timeout,
master_opts => [ -o => "StrictHostKeyChecking=no" ]
);
$ssh->error and die "Unable to connect to remote host: " . $ssh->error;
my ( $fh, $pid ) = $ssh->open2pty( { stderr_to_stdout => 1 } );
my %params = (
fhopen => $fh,
timeout => $timeout,
errmode => 'return',
);
$conn = Net::Telnet->new(%params);
$conn->waitfor($promptEnd);
#lines = $conn->cmd($cmd1);
foreach (#lines) {
print $_;
}
#lines = $conn->cmd($cmd2);
foreach (#lines) {
print $_;
}
$conn->cmd("quit");
The problem I'm having is that I can't seem to separate the code into subroutines. Once the $conn object is returned from a subroutine, the underlying ssh connection drops. I need to separate this logic in order to not have to rewrite many, many programs and lines of code that relay on this pusher routine. However that problem I'll direct to another question.
[Edit, fully solved]
Just an update in case anyone needs to do something similar.
While the above worked very well when run under a single subroutine, I found that any time I passed the handle to another subroutine, the telnet handle remained open, but the ssh connection dropped.
To solve this I found that if I passed the ssh handle to another subroutine, then later attached the open2pty, and attached Net::Telnet, then I could pass the Net::Telnet handle between subroutines without the underlying ssh connection dropping. This also worked for Net::Telnet::Cisco as well. I have this code working well with Cisco, Juniper, and Brocade routers.
You should also consider adding a few more parameters to the Net::Telnet->new() because it is interacting with ssh rather than a TELNET server.
-telnetmode => 0
-output_record_separator => "\r",
-cmd_remove_mode => 1,
Because there is no TELNET server on remote side, -telnetmode => 0 turns off TELNET negotiation.
The end-of-line is most likely just a carriage-return (i.e. -output_record_separator => "\r") rather than the TCP or TELNET combination of carriage-return linefeed ("\r\n").
Always strip the echoed back input -cmd_remove_mode => 1
There are several possibilities:
Some routers accept having the sequence of commands sent up front via stdin:
my $out = $ssh->capture({stdin_data => join("\r\n", #cmds, '')})
In other cases you will have to use something like Expect to send a command, wait for the prompt to appear again, send another command, etc.
If you were using Net::Telnet before, the Net::OpenSSH docs explain how to integrate both (though I have to admit that combination is not very tested).
Also, some routers provide some way to escape to a full Unix-like shell. I.e., preppending the commands with a bang:
$ssh->capture("!ls");

Getting remotely executed commands output in a veriable using expect in perl

I have a password variable $pw and a command variable $cmd.
$pw=UNIX password of a remote machine.
$cmd=Command to be executed in the remote machine.
now if I run the command using back-tick
I will be able to get some value in the output variable.
now if I want to run the same command through a expect I how to achieve the same. I mean how to get the out put of the command run through a expect in a variable.
my expect function is like:
sub expt($$){
my $cmd;
my $timeout;
($cmd, $pw)=#_;
$expect = Expect->new;
$expect->raw_pty(1);
printDebug("Running the command under expt");
$expect->spawn($cmd)
or die "Cannot spawn $cmd: $!\n";
$expect->expect($timeout,
[ qr/password:/i, #/
sub {
my $self = shift;
$self->send("$pw\n");
exp_continue;
}
],
[qr/Are you sure you want to continue connecting \(yes\/no\)?/
, sub { my $self = shift;
$self->send("yes\n");
exp_continue; }],
[qr/Unix password \(user\):/
, sub { my $self = shift;
$self->send("pw\n");
exp_continue; }
],
);
$expect->soft_close();
return 0;
}
And I am calling the function like
expt($cmd,$pw);
By doing this I am able to execute the script in the remote host but my requirement is to store the output of the remote host in a local variable.
Why not using Net::SSH::Expect ? It would be more close to the first method: you "just" need to do something like that:
my $ssh = Net::SSH::Expect->new (
host => "myserver.com",
user => 'myuser',
raw_pty => 1
);
$ssh->run_ssh() or die "SSH process couldn't start: $!";
($ssh->read_all(2) =~ />\s*\z/) or die "where's the remote prompt?"
$ssh->exec("stty raw -echo");
my $output = $ssh->exec($cmd);
Have a look at Net::SSH::Expect pod documentation, it is quite extensive.