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

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");

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::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'

SSH with X11 forwarding in Perl

I have tried the perl modules Net::SSH:Perl and Net::OpenSSH to no avail. X11 forwarding does work because if I do a "ssh root#host" and execute an X application such as "xterm" I get a window back.
Here are some things I've tried:
$self->{'ssh'} = Net::OpenSSH->new("root:PW#".$hostname);
print $self->{'ssh'}->capture("env"); #The display variable is not set so it won't work
print $self->{'ssh'}->capture("xterm");
Nope
$self->{'ssh'} = Net::OpenSSH->new("root:PW#".$hostname, master_opts => ['-X' => '']);
print $self->{'ssh'}->capture("env"); #The display variable is not set so it won't work
print $self->{'ssh'}->capture("xterm"); #Nope
print $self->{'ssh'}->capture({master_opts => ['-X']}, "xterm"); #Nope
Nope, now for Net::SSH::Perl
$self->{'ssh'} = Net::SSH::Perl->new("$hostname", debug=>0);
$self->{'ssh'}->login("root","pass");
my ($stdout, $stderr, $exit) = $self->{'ssh'}->cmd("xterm"); #Nope
Nope
$self->{'ssh'} = Net::SSH::Perl->new("$hostname", debug=>0, options=>["ForwardX11 yes"]);
$self->{'ssh'}->login("root","pass");
my ($stdout, $stderr, $exit) = $self->{'ssh'}->cmd("xterm"); #Nope
The only thing that actually works is if I do the following, so I know X11 forwarding is working in Perl.
`ssh root#host xterm`
I would rather get the modules working if possible but if I can somehow open up a bidirectional pipe, communicate with SSH and receive data WHEN I WANT TO (similar to how I can $self->{'ssh'}->cmd() and receive the output at any time in my script), I will do that. I just don't know where to start. Anyone else did this before?
The development version of Net::OpenSSH has a new option forward_X11. This seems to work:
my $ssh = Net::OpenSSH->new("localhost", forward_X11 => 1);
print $ssh->capture({forward_X11 => 1}, "env"); # includes DISPLAY=localhost...
print $ssh->capture({forward_X11 => 1}, "xclock"); # starts the xclock program
Note that you have to specify the new option on both the constructor and the actual command.
See also http://www.perlmonks.org/?node_id=1028837

Running AnyEvent under Dancer application

I would like to do some non-blocking SSH to a couple of thousand machines that i'm tracking (my own machines), I have a Dancer application up and running, and I'm willing to use AnyEvent::timer to execute SSH commands asynchronously (each machine has its own polling interval, and I don't want one machine to wait for another to complete with its SSH work).
I'm wondering, what is the best way to act asynchronously in a synchronous environment?
It is not very good idea to run any external commands from within your web scripts.
For one, should your external call block or crash for any reason, it will create bad experience for the user (even it that user is just you).
Then, running external commands as web user may have a lot of security implications - I would think your web user most likely has passwordless ssh set up, doesn't it? What if someone figures out some security hole in your script and manages to use it to ssh into your servers?
Instead, you should create separate service or process which will regularly poll your servers status using ssh (or what else) and save results of that scan into database - Postgres or MySQL.
Then, change your Dancer app to display collected results from database, rather than doing live ssh request. This way it will be very fast and secure.
I could not be a good idea but it is possible. I have a big Dancer application to execute scripts remotely and I'm doing it with fork and Net::SSH2. I tried with thread but there are some modules that are not thread-safe so I recommend to use fork.
I have some comments in my blog http://perlondancer.blogspot.mx/2014/04/executing-remote-commands-from-dancer.html and in this gist is the code example below: https://gist.github.com/johandry/11197516
#!/usr/bin/env perl
use strict;
use warnings;
use Dancer;
use Net::SSH2;
sub execCommand ($$) {
my ( $ssh2, $cmd ) = #_;
my %args=(
timeout => 1_000, # polling timeout
bufsize => 10_240, # read buffer size when polling
);
$ssh2->blocking(1); #needed for ssh->channel
my $chan=$ssh2->channel(); # create SSH2 channel
if ($ssh2->error()) {
return (undef, undef, 100);
}
# exec $cmd (caveat: only one command line can be executed over this channel. No "ls -l;whoami" combo. Use ssh->shell instead.
unless ($chan->exec($cmd)) {
return (undef, undef, 500);
}
# defin polling context: will poll stdout (in) and stderr (ext)
my #poll = ( { handle => $chan, events => ['in','ext'] } );
my %std=(); # hash of strings. store stdout/stderr results
$ssh2->blocking( 0 ); # needed for channel->poll
while(!$chan->eof) { # there still something to read from channel
$ssh2->poll( $args{'timeout'}, [ #poll ] ); # if any event, it will be store into $poll;
my( $n, $buf ); # number of bytes read (n) into buffer (buf)
foreach my $poll ( #poll ) { # for each event
foreach my $ev ( qw( in ext ) ) { #for each stdout/stderr
next unless $poll->{revents}{$ev};
#there are something to read here, into $std{$ev} hash
if( $n = $chan->read( $buf, $args{'bufsize'}, $ev eq 'ext' ) ) { #got n byte into buf for stdout ($ev='in') or stderr ($ev='ext')
$std{$ev}.=$buf;
}
} #done foreach
}
}
$chan->wait_closed(); #not really needed but cleaner
my $exit_code=$chan->exit_status();
$chan->close(); #not really needed but cleaner
$ssh2->blocking(1); # set it back for sanity (future calls)
return ($std{'in'},$std{'ext'},$exit_code);
}
sub execute ($$$$) {
my ($ip, $username, $password, $cmd) = #_;
my $pid = fork();
if ($pid) {
# This is the parent (DANCER)
debug "Process started with PID $pid\n";
} elsif ( $pid == 0 ) {
# This is the child
my $ssh2 = Net::SSH2->new();
$ssh2->connect( $ip ) or debug("Cannot connect to $ip");
my $publicKeyFile = './id_rsa.pub'; # path(setting('appdir'), 'db', 'id_rsa.pub'); # I prefer to copy the public key in your app dir due to permissions issues
my $privateKeyFile = './id_rsa'; # path(setting('appdir'), 'db', 'id_rsa'); # I prefer to copy the private key in your app dir due to permissions issues
if ( $ssh2->auth_publickey( $username, $publicKeyFile, $privateKeyFile, $password ) ) {
my ($stdout, $stderr, $exitcode) = execCommand($ssh2, $cmd);
} else {
debug "Could not authenticate to $ip with $username";
}
$ssh2->disconnect();
} else {
debug "Could not fork: $!\n";
}
}
set logger => "console";
set log => "core";
set show_errors => 1;
get '/uptime/:ip' => sub {
my $username = "the username";
my $password = "the password";
execute(param('ip'), $username, $password, "uptime > /tmp/dancer_example.txt");
return 'uptime is running';
};
dance;
true;
Net::SSH2 can be used asynchronously, but it is quite buggy and crashes often. Forget about using it for running thousands (or just hundreds) of connections in parallel on the same process. It may be ok if you use it wrapped in new processes as recomended by #Johandry, but then you can just run the ssh command using AnyEvent::Util::run_cmd.
Net::OpenSSH is another Perl module that can be used asynchronously. It shouldn't be too difficult to integrate it inside AnyEvent.

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.