Using Perl's Net::Telnet module to retrieve data from upsd.
There is one particular function I'm trying to implement, retrieving the data for a single var.
The problem is only a single line is output, and that line is used to match
Prompt, so it is not output.
Here's raw telnet:
telnet dns1 3493
Trying 192.168.15.1...
Connected to dns1.
Escape character is '^]'.
get var cp1500 ups.test.result
VAR cp1500 ups.test.result "Done and passed"
Connection closed by foreign host.
Here's some code:
#!/usr/bin/perl
use strict;
use warnings;
use Net::Telnet;
my $host = "dns1";
my $model = "cp1500";
my $bvar = "ups.test.result";
my $t = new Net::Telnet (Timeout => 3, Port => 3493, Prompt => "/VAR $model $bvar/");
$t->open($host);
my #ary = $t->cmd("get var $model $bvar");
print #ary,"\n";
This just prints the newline as the array is empty. Prompt is matched else there'd be a timeout error. How can I get that single line of output back for processing in the script?
This is my solution, use Socket instead of Net::Telnet.
#!/usr/bin/perl
use strict;
use warnings;
use Socket;
my $host = 'str003';
my $port = 3493;
my $model = 'cp1350';
my $quer = 'get var';
my $bvar = 'ups.test.result';
my ($sock,$iaddr,$paddr,$send);
$iaddr = inet_aton($host);
$paddr = sockaddr_in($port, $iaddr);
$send = join(' ',$quer,$model,$bvar);
socket($sock, AF_INET, SOCK_STREAM, 6) or die $!;
connect($sock , $paddr) or die "connect failed : $!";
send($sock , "$send\nlogout\n" , 0);
while (my $line = <$sock>)
{
if ($line =~ /^VAR/) {
print "$line\n";
}
}
close($sock);
This is the one where one line of data is returned:
VAR cp1350 ups.test.result "Done and passed"
Related
I am having issues printing Zebra labels from a Perl CGI, where it works on one server but not another. Also, if I run the program from the command line it works on either server. The servers are IIS 7 (don't laugh it's what I'm stuck using).
Here is the code:
use strict;
use Socket;
use CGI qw(:cgi-lib);
use CGI::Carp qw ( fatalsToBrowser );
my %formdata = Vars;
print "Content-type: text/html\r\n\r\n";
# to test running from the command line, hardcode the paramters normally passed from the web interface
# comment these out when running CGI
$formdata{printer} = "zebraprinter.mycompany.com";
$formdata{serials} = "TR16170003|Gerry's Product TR|This is a generic product where all serial numbers start with the letters TR|T~";
# initialize server and port
my $port = 9100;
# create the socket, connect to the port
socket(SOCKET,PF_INET,SOCK_STREAM,(getprotobyname('tcp'))[2]) or myExit("Can't create a socket $!\n");
connect( SOCKET, pack_sockaddr_in($port, inet_aton($formdata{printer}))) or myExit("Can't connect to port $port! \n");
foreach my $serial(split("~", $formdata{serials}))
{
my #ar = split(/\|/, $serial);
my $line;
if ($formdata{printer} =~ /label2/) # small labels
{
$line = "^XA^PRA,A,A^LH5,5^FO10,10^BCN,50,N,N,N,D^FD$ar[0]^FS";
$line .= "^FO300,10^AD,15,12^FDSerial Number:^FS";
$line .= "^FO300,30^AD,15,12^FD$ar[0]^FS^XZ";
}
else # large labels
{
$line = "^XA^PRA,A,A^LH20,20";
$line .= "^FO20,40^FWN^AT,60,10^FD Serial Number: $ar[0]^FS";
$line .= "^FO20,120^FWN^AT,60,10^FD $ar[1]^FS";
# need to hard break and limit long lines
if (length($ar[2]) > 60)
{
my $part = substr($ar[2],0,60);
$line .= "^FO20,200^FWN^AT,60,10^FD Description: $part^FS";
$part = substr($ar[2],61,74);
$line .= "^FO20,260^FWN^AT,60,10^FD$part^FS";
$line .= "^FO50,340^B3N,N,100,Y,N^FD$ar[0]^FS";
}
else
{
$line .= "^FO20,200^FWN^AT,60,10^FD Description: $ar[2]^FS";
$line .= "^FO50,280^B3N,N,100,Y,N^FD$ar[0]^FS";
}
$line .= "^XZ";
# example formatted label
#$line = qq~^XA^PRA,A,A^LH20,20^FO20,40^FWN^AT,60,10^FD Serial Number: $ar[0]^FS^FO20,120^FWN^AT,60,10^FD Product: $ar[1]^FS^FO20,200^FWN^AT,60,10^FD Description: $ar[2]^FS^FO50,280^B3N,N,100,Y,N^FD$ar[0]^FS^XZ~;
}
print SOCKET $line;
}
close SOCKET;
myExit("Labels Printed.");
sub myExit
{
my $msg = shift;
print "<script>alert('$msg')</script>";
exit;
}
I'm guessing it has something to do with opening sockets in a CGI but I don't have a whole lot of experience with that.
Thanks in advance
It turns out that our Zebra printer is sending a response after printing labels and waiting to verify it was delivered, which locked it up. The solution that is working so far is to get the response but also set a short timeout on the socket just in case. Also went up the food chain and used IO::Socket instead of the old Socket library:
use strict;
use IO::Socket;
use CGI qw(:cgi-lib);
use CGI::Carp qw ( fatalsToBrowser );
my %formdata = Vars;
print "Content-type: text/html\r\n\r\n";
# to test running from the command line, hardcode the paramters normally passed from the web interface
# comment these out when running CGI
$formdata{printer} = "zebralabel1.mycompany.com";
$formdata{serials} = "TR16170003|Gerry's Product TR|This is a generic product where all serial numbers start with the letters TR|T~";
# create the socket, connect to the port
my $remote = IO::Socket::INET->new(
Proto => 'tcp',
PeerAddr=> "$formdata{printer}",
PeerPort=> "9100",
ReuseAddr=> 0,
Timeout => 2,
) or myExit("Cannot connect to printer: $!");
$remote->autoflush(1); # Send immediately
my ($serial, $product, $desc) = split(/\|/, $formdata{serials});
# example formatted label
my $line = qq~^XA^PRA,A,A^LH20,20^FO20,40^FWN^AT,60,10^FD Serial Number: $serial^FS^FO20,120^FWN^AT,60,10^FD Product: $product^FS^FO20,200^FWN^AT,60,10^FD Description: $desc^FS^FO50,280^B3N,N,100,Y,N^FD$serial^FS^XZ~;
print $remote $line;
my $dontCare = <remote>;
close $remote;
myExit("Labels Printed.");
sub myExit
{
my $msg = shift;
print "<script>alert('$msg')</script>";
exit;
}
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.
I'm following this guide explaining how to do a server using IO::Async but I'm having issues with my client code. I have it where I send first then receive. This makes me press enter on each client before receiving any data. I figured I'd have to listen till I wanted to type something but I'm not really sure how. Below is my current client code.
use IO::Socket::INET;
# auto-flush on socket
$| = 1;
# create a connecting socket
my $socket = new IO::Socket::INET (
PeerHost => 'localhost',
PeerPort => '12345',
Proto => 'tcp',
);
die "cannot connect to the server $!\n" unless $socket;
print "My chat room client. Version One.\n";
while (1) {
my $data = <STDIN>;
$socket->send($data);
my $response = "";
$socket->recv($response, 1024);
print ">$response";
last if (index($data, "logout") == 0);
}
$socket->close();
I actually had this problem myself a few weeks ago when trying to make a client/server chat for fun.
Put it off until now.
The answer to your problem of having to hit enter to receive data, is that you need to use threads. But even if you use threads, if you do $socket->recv(my $data, 1024) you won't be able to write anything on the command line.
This isn't using your code, but here is my solution after banging my head against a wall for the last 24hrs. I wanted to add this as an answer, because though the question is out there on stackoverflow, none of the answers seemed to show how to use IO::Select.
Here is the server.pl script, it does not use threading:
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket::INET;
use IO::Select;
$| = 1;
my $serv = IO::Socket::INET->new(
LocalAddr => '0.0.0.0',
LocalPort => '5000',
Reuse => 1,
Listen => 1,
);
$serv or die "$!";
print 'server up...';
my $sel = IO::Select->new($serv); #initializing IO::Select with an IO::Handle / Socket
print "\nAwaiting Connections\n";
#can_read ( [ TIMEOUT ] )
#can_write ( [ TIMEOUT ] )
#add ( HANDLES )
#http://perldoc.perl.org/IO/Select.html
while(1){
if(my #ready = $sel->can_read(0)){ #polls the IO::Select object for IO::Handles / Sockets that can be read from
while(my $sock = shift(#ready)){
if($sock == $serv){
my $client = $sock->accept();
my $paddr = $client->peeraddr();
my $pport = $client->peerport();
print "New connection from $paddr on $pport";
$sel->add($client); #Adds new IO::Handle /Socket to IO::Select, so that it can be polled
#for read/writability with can_read and can_write
}
else{
$sock->recv(my $data, 1024) or die "$!";
if($data){
for my $clients ($sel->can_write(0)){
if($clients == $serv){next}
print $clients $data;
}
}
}
}
}
}
And the client.pl, which uses threads:
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket::INET;
use threads;
use IO::Select;
$| = 1;
my $sock = IO::Socket::INET->new("localhost:5000");
$sock or die "$!";
my $sel = IO::Select->new($sock);
print "Connected to Socket ". $sock->peeraddr().":" . $sock->peerport() . "\n";
#This creates a thread that will be used to take info from STDIN and send it out
#through the socket.
threads->create(
sub {
while(1){
my $line = <>;
chomp($line);
for my $out (my #ready = $sel->can_write(0)){
print $out $line;
}
}
}
);
while(1){
if(my #ready = $sel->can_read(0)){
for my $sock(#ready){
$sock->recv(my $data, 1024) or die $!;
print "$data\n" if $data;
}
}
}
There is one other problem that arises though, when the client receives data and prints it to the console, your cursor goes to a new line, leaving behind any characters you had typed.
Hope this helps and answers your question.
For a simple "just send from STDIN, receive to STDOUT" client, you could use any of telnet, nc or socat. These will be simple enough to use for testing.
$ telnet localhost 12345
$ nc localhost 12345
$ socat stdio tcp:localhost:12345
If you actually want to write something in Perl, because you want to use it as an initial base to start a better client from, you probably want to base that on IO::Async. You could then use the netcat-like example here. That will give you a client that looks-and-feels a lot like a simple netcat.
I am guessing you need to set the MSG_DONTWAIT flag on your recv call, and print the response only if it is non-null.
$socket->recv($response, 1024, MSG_DONTWAIT);
print ">$response" if ($response ne "");
My client.pl
#!/usr/bin/perl
use IO::Socket::INET;
use strict;
my $name = '172.20.10.189'; #Server IP
my $port = '7890';
my $socket = IO::Socket::INET->new('PeerAddr' => $name,
'PeerPort' => $port,
'Proto' => 'tcp') or die "Can't create socket ($!)\n";
print "Client sending\n";
while (1) {
my $msg = <STDIN>;
print $socket $msg;
print scalar <$socket>;
}
close $socket
or die "Can't close socket ($!)\n";
My server.pl
#!/usr/bin/perl
use IO::Socket::INET;
use strict;
my $port = "7890";
my $socket = IO::Socket::INET->new('LocalPort' => $port,
'Proto' => 'tcp',
'Listen' => SOMAXCONN)
or die "Can't create socket ($!)\n";
while (my $client = $socket->accept) {
my $name = gethostbyaddr($client->peeraddr, AF_INET);
my $port = $client->peerport;
while (<$client>) {
print "[$name $port] $_";
my #out = `$_`;
print #out;
print $client "$.: #out";
}
close $client
or die "Can't close ($!)\n";
}
die "Can't accept socket ($!)\n";
My client is sending a command (ls -lrt /) to the server and Server is supposed to run that command and send output to the client back.
Problem:-
The command is executed successfully on the server but it sends only first line to the client. If I press any key from client again the next line of output is sent to the client.
Or tell me how to send multiple line output to back to client.
Any help would be appreciated.
Thanks
Abhishek
The Server sends all lines to the client, the client however chooses to read only one line:
print scalar <$socket>;
If you remove the scalar, it should work. However, your architecture is still a security nightmare.
All servers should run in taint mode (-T switch).
Never blindly execute commands that a clients sends you. Only execute commands that pass a very strict validation test, do not run commands that just don't look malicious.
Perhaps you are trying to duplicate SSH, you might want to look at that program instead.
Your server doesn't do any kind of authentication. At least it logs all inputs.
It was a silly mistake... and I have fixed the first issue as follows and got multi-line output on the client side...
Client.pl
#!/usr/bin/perl
use IO::Socket::INET;
use strict;
my $name = '172.20.10.189'; #Server IP
my $port = '7890';
my $socket = IO::Socket::INET->new('PeerAddr' => $name,
'PeerPort' => $port,
'Proto' => 'tcp')
or die "Can't create socket ($!)\n";
print "Client sending\n";
while (1) {
my $msg = <STDIN>;
print $socket $msg;
while (<$socket>)
{
print "\n$_";
}
}
close $socket
or die "Can't close socket ($!)\n";
BUT there is one more issue -
I want my client to keep sending few other commands one after another until I close the client manually and receive output.
The problem is - It receives output of the first command only..
Can anyone now help me on this?
i am calling a perl script client.pl from a main script to capture the output of client.pl
in #output.
is there anyway to avoid the use of these two files so i can use the output of client.pl in main.pl itself
here is my code....
main.pl
=======
my #output = readpipe("client.pl");
client.pl
=========
#! /usr/bin/perl -w
#use strict;
use Socket;
#initialize host and port
my $host = shift || $FTP_SERVER;
my $port = shift || $CLIENT_PORT;
my $proto = getprotobyname('tcp');
#get the port address
my $iaddr = inet_aton($host);
my $paddr = sockaddr_in($port, $iaddr);
#create the socket, connect to the port
socket(SOCKET, PF_INET, SOCK_STREAM, $proto)or die "socket: $!\n";
connect(SOCKET, $paddr) or die "connect: $!\n";
my $line;
while ($line = <SOCKET>)
{
print "$line\n";
}
close SOCKET or die "close: $!";
/rocky..
Put the common code in a package. Use the package in client.pl and main.pl. Chapter 10 of Programming Perl has more information.
Not sure what you are really trying to do, but might worh investigating a package such as Net::FTP ( http://search.cpan.org/perldoc?Net%3A%3AFTP )
you can do two things:
Merge the codes in client.pl and main.pl as your main function does no work other than printing. In case you want to do more from the incoming input data, you should do that in client.pl itself, coz an in-memory array(#output) may run out of RAM while reading large size data across the network.
If you want the output in an array (#output)
sub client {
# intialize ..
my #array = (); #empty array
while ($line = <SOCKET>)
{
push(#array,$line);
}
return #array;
}
#output = client();
print #output;
Other way, you can also use references:
sub client {
# intialize ..
my #array = (); #empty array
while ($line = <SOCKET>)
{
push(#array,$line);
}
return #array;
}
my $output_ref = client();
print #$output_ref; // dereference and print.