SSH with X11 forwarding in Perl - 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

Related

Perl print backticks STDOUT

I am writing a very simple bind shell in Perl that is supposed to open a specific port on windows and when I connect to it with ncat, I should be able to write commands from ncat, those be executed on the machine and to return me STDOUT.
Here is my code so far:
use strict; use IO::Socket;
my($sock, $newmsg, $cmd, $MAXLEN, $PORTNO);
$MAXLEN = 2048;
$PORTNO = 4444;
$sock = IO::Socket::INET->new(LocalPort => $PORTNO, Proto => 'udp');
while ($sock->recv($newmsg, $MAXLEN)) {
my($port, $ipaddr) = sockaddr_in($sock->peername);
my #cmd = qx($newmsg);
print(#cmd);
$sock->send(#cmd);
}
If I type something easy like echo Hello world it returns as expected, Hello World. Problem arises when I type dir or something that includes new lines(or so I have guessed, I'm pretty new with perl).
When typing dir, the result gets printed on the perl shell (as expected, due to print(#cmd), but then also prints usage: $sock->send(BUF, [FLAGS, [TO]]) at -e line 1. and exits, but I never receive the message in ncat.
I am running the command from a command shell as a one-liner:
perl -e "use strict; use IO::Socket; my($sock, $newmsg, $cmd, $MAXLEN, $PORTNO); $MAXLEN = 1024; $PORTNO = 4444; $sock = IO::Socket::INET->new(LocalPort => $PORTNO, Proto => 'udp'); while ($sock->recv($newmsg, $MAXLEN)) {my($port, $ipaddr) = sockaddr_in($sock->peername);my #cmd = qx($newmsg);print(#cmd);$sock->send(#cmd);}"
How can I do it so that it prints things as dir?
Your problem is with $sock->send(#cmd). The send method expects the message to be a scalar not an array. You can fix this by the following
my $CRLF="\x0D\x0A"; # Network line break
$sock->send(join $CRLF, #cmd); # Convert #command into a single string

PERL ssh script tty issue

I am trying to write a PERL script to ssh to a remote machine and run some tcptraceroutes. I managed to handle the SSH part fine. However tcptraceroute command requires sudo, and here is where I ran into an issue. Example:
my (#traces, $stderr, $exit) = $sshCali->cmd(tty => 1,
"sudo tcptraceroute $endpoint", interactive => 1, debug => 1);
When I use tty => 1 system complaints with this error:
"not a tty"
When I do not use tty => 1 then I get this:
"sudo: sorry, you must have a tty to run sudo"
Any help will be greatly appreciated. Here is my code:
#!/usr/bin/perl
use strict;
use warnings;
use Net::SSH::Perl;
#vars
my $endpoint = 172.31.100.1;
my #traces;
my $sshCali;
my $hostCali = 'remotemachine';
my $cmd = 'sudo traceroute';
warn "Starting SSH Services:...\n";
$sshCali = Net::SSH::Perl->new($hostCali, interactive => 1, debug => 0) or die "Couldnt establish connection!";
$sshCali -> login("user"); # for non-interactive mode;
print "here\n";
my (#traces, $stderr, $exit) = $sshCali->cmd("sudo tcptraceroute $endpoint", interactive => 1, debug => 1);
#my (#traces, $stderr, $exit) = $sshCali->cmd(tty => 1,"$cmd $endpoint", interactive => 1, debug => 1);
print STDERR "STDERR: $stderr\n" if $stderr;
print "command exit w/ code: $exit\n";
$sshCali -> cmd("exit");
print "NOW ..... Printing the array: TRACES\n";
print #traces;
Thanks in advance.
It is entirely possible to install a module in your home directory and call it inside your script. See this node on Perlmonks for exact steps: Installing Perl modules in home directory

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

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'

How can I fix "unknown terminal type" when connecting with Perl's Net::Telnet?

I got a problem connecting to a SUSE linux machine by Perl's Net::Telnet module.
The code looks like below:
my $t = new Net::Telnet (Timeout => 20);
$t->open($server);
$t->input_log("telnet.log");
$t->login($user, $pass);
my #lines=$t->cmd($command);
print #lines;
The log file looks like below:
Welcome to SUSE Linux Enterprise Server 10 SP1 (x86_64) - Kernel 2.6.16.46-0.12-default (5).
vm-sles10u5 login: <myuser>
Password:
Last login: Thu Feb 25 10:41:07 EST 2010 from <mymachine> on pts/5
tset: unknown terminal type network
Terminal type?
Any suggestions?
If you use the option_log function to log the telnet options received and returned. You will see that Perl does not send the terminal type to the server by default. Server will default the terminal type to "network" for some reasons.
The right way to do this is to set the terminal type on the perl side.
my $termtype = 'vt100'; my $telopt_ttype_ok = '';
my $t = new Net::Telnet (Timeout => 5);
$t->option_callback(\&opt_callback);
$t->option_accept(Do=>Net::Telnet->TELOPT_TTYPE);
$t->suboption_callback(\&subopt_callback);
$t->open($server);
$t->input_log("runRemoteCommand_telnet.log");
$t->login($user, $pass);
$t->cmd($command);
exit 0;
sub opt_callback {
my ($obj, $option, $is_remote, $is_enabled, $was_enabled, $buf_position) = #_;
if ($option == Net::Telnet->TELOPT_TTYPE and $is_enabled and !$is_remote) {
$telopt_ttype_ok = 1;
}
1;
}
sub subopt_callback {
my ($obj, $option, $parameters) = #_;
my ($ors_old, $otm_old);
if ($option == Net::Telnet->TELOPT_TTYPE)
{
$ors_old = $obj->output_record_separator('');
$otm_old = $obj->telnetmode (0);
$obj->print("\xff\xfa", pack("CC", $option, 0), $termtype, "\xff\xf0");
$obj->output_record_separator($ors_old);
$obj->telnetmode ($otm_old);
}
1;
}
Refer to this
See this discussion for the same problem. The solution (untested) that they propose is to set the TERM enviromental variable to a known value, such as TERM=vt100
What works too is to add the question mark to your prompt value and then send as the first command the echo $TERM value. So if your command prompt is '>' and echo $TERM gives 'xterm':
my $t = new Net::Telnet ( Timeout => 3,
Prompt => '/[\?>]/',
Dump_log => $dumplog,
Host => $host,
Input_log => $inputlog,
Output_log => $outputlog,
);
$t->login($username, $password);
my #lines = $t->cmd("xterm");
#lines = $t->cmd("who");
print #lines, "\n";
#lines = $t->cmd("uptime");
print #lines, "\n";
In /etc/profile, you should find the following line:
test -x /usr/bin/tset && /usr/bin/tset -I -Q
This is the line that causing the problem. You can confirm with running the following command:
$>TERM=network
$>/usr/bin/tset -I -Q
tset: unknown terminal type network
Terminal type?
In /usr/share/terminfo/n/, network is a link to ../n/net. I don't know why system does not work for "network".
The temp fix is to change the line in /etc/profile to the following:
test -x /usr/bin/tset && /usr/bin/tset -I -Q -m network:vt100
The above command will map network to vt100.