Perl Socket File Transfer Problems - perl

I have my connection working and data transfer working.... to an extent. I am designing a small program that sets up a client and server socket. Once connected, the client can send files to the server.
My problem is, when I begin to send my "test" file, the server never ends it's while loop. It keeps concatenating data into the output file. Even stranger, the data in the output file is correct, except there is extra white space between the lines.
I know this is because I am not chomping the \n character, and I add another \n character on the server. But if I chomp on the client, then everything is on one line. Therefore the server (regardless of it adding a newline character) outputs it all on one line, because it only received one line. If I chomp on the server side, I get an empty text file... which confuses me.
Furthermore, the server never stops concatenating... it makes an infinite loop even after the client disconnected. The terminal outputs this indefinitely:
Use of uninitialized value $data in concatenation (.) or string at ./tcp_server.pl line 51, <GEN2> line 14.
Here is my code for the server:
#!/usr/bin/perl
# Flushing to STDOUT after each write
$| = 1;
use warnings;
use strict;
use IO::Socket::INET;
use v5.10;
# Server side information
my $listen_port = '7070';
my $protocal = 'tcp';
# Finds IP address of host machine
# Connects to example site on HTTP
my $ip_finder = IO::Socket::INET->new(
PeerAddr=> "www.google.com",
PeerPort=> 80,
Proto => "tcp"
) or die "The IP can not be resolved: $!\n";
# The found IP address of Host
my $ip_address = $ip_finder->sockhost;
# Creating socket for server
my $server = IO::Socket::INET->new (
LocalPort => $listen_port,
Proto => $protocal,
Listen => 5,
Reuse => 1,
) or die "Socket could not be created, failed with error: $!\n"; # Prints error code
print "Socket created using IP: $ip_address\n";
print "Waiting for client connection on port $listen_port\n";
# Accept connection
my $client_socket = $server->accept();
open(my $fh, ">out")
or die "File can not be opened: $!";
while($client_socket) {
# Retrieve client information
my $client_address = $client_socket->peerhost();
my $client_port = $client_socket->peerport();
print "Client accepted: $client_address, $client_port\n";
my $data = <$client_socket>;
print $fh "$data\n";
}
close $fh;
$server->close();
and the client:
#!/usr/bin/perl
# Flushing to STDOUT after each write
$| = 1;
use warnings;
use strict;
use IO::Socket::INET;
use v5.10;
# Client side information
# Works by setting $dest to server address, needs to be on same domain
# my $dest = '<IP goes here>';
# my $dest = '<IP goes here>';
my $dest = '<host>.cselabs.umn.edu';
my $port = '7070';
my $protocal = 'tcp';
my $client = IO::Socket::INET->new (
PeerHost => $dest,
PeerPort => $port,
Proto => $protocol,
) or die "Socket could not be created, failed with error: $!\n"; # Prints error code
print "TCP connection established!\n";
open(my $fh, "<test")
or die "Can't open: $!";
while(my $line = <$fh>) {
print $client $line;
}
close $fh;
# sleep(10);
$client->close();

You don't add newline at the client. The client just reads a line from the file (including newline) and prints it to the socket, but does not add another newline. But at the server you read the line from the socket (including the newline) and add another newline. Also, the loop condition in the server is not correct. You should not check if the socket exists because it will exists even after the connection got closed. Instead you should check for error while reading (that's where you undefined warnings come from). And, it may be better not to read line by line but instead chunks independent from the lines, e.g.
while (read($client_socket, my $data, 8192)) {
print $fh $data;
}

Related

Passing name of file via socket in perl

I have 2 scripts written in perl. First one takes a file and send it via socket to server. The server is my second script - and it saves to a file.
Server save file as a specified name - fixed in code. How to take the name of sending file, and send it to the server, before sending a file?
My code below:
Client:
my $socket = IO::Socket::INET->new(
PeerAddr => $local_host,
PeerPort => $local_port,
Proto => 'tcp',
)or die "Alert!";
my $bandwidth = 1024*5 ; # 5Kb/s -
open my $fh, '<', "$direc/$my_data"
or die "couldn't open the file";
my $buffer ;
while( sysread($fh, $buffer , $bandwidth) ) {
print $socket $buffer ;
sleep(1) ;
}
print "Data send.End \n" ;
close ($fh) ;
close($socket) ;
My server:
my $my_socket = new IO::Socket::INET(
LocalHost => $local_host,
LocalPort => $local_port,
Proto => 'tcp',
Listen => 5,
Reuse => 1
);
die "Couldn't open my_socket $!n " unless $my_socket;
print "You can send the data now \n";
my $accepter = $my_socket->accept();
my $count=0;
#print "$directory.$save_dir/$my_data";
open my $fh, '>', "$direc/$save_dir/$my_data" #my data is the name, and it's "fixed", how to take it from client?
or die "Couldn't open the file";
while(<$accepter>){
chomp;
last if $count++ ==10;
say $fh $_;
}
print "End \n";
close $fh;
close $my_socket;
Having the server write a filename specified by the client is a security risk. The client could tell the server to overwrite files, including the server itself.
Instead, use a UUID for the real filename. Store the client filename / real filename pair elsewhere.
You need to come up with a protocol so the server can distinguish between the filename and content. We could use an existing format such as JSON or YAML, but they require slurping the whole file into memory and encoding the content. You could make something up, like "the first line is the filename", but we can do a little better.
If you want to stream, we can use a stripped down HTTP protocol. Send headers as Key: Value lines. A blank line ends headers and begins sending content. For just a little extra effort we have a simple protocol that's extensible.
Here's the main loop of the server using UUID::Tiny and also autodie.
# Read Key: Value headers until we see a blank line.
my %headers;
while(my $line = <$accepter>) {
chomp $line;
last if $line eq "";
my($key, $value) = split /\s*:\s*/, $line;
$headers{$key} = $value;
}
# Store the file in a random filename. Do not use the client's filename
# to avoid a host of security issues.
my $filename = create_uuid_as_string(UUID_V4);
open my $fh, ">", "incoming/$filename";
# Read the content and save it to the file.
my $buf;
while( read($accepter, $buf, 1024) ) {
print $fh $buf;
}
say "$headers{Filename} was stored in incoming/$filename";
close $my_socket;
And the client simply sends a Filename header before sending the file's content.
open my $fh, '<', $filename;
print $socket "Filename: $filename\n\n";
my $buffer ;
while( sysread($fh, $buffer , $bandwidth) ) {
print $socket $buffer ;
}

How to flush pipe handle obtained from open?

I am trying to flush a pipe handle obtained from open using either autoflush() and flush() methods from the IO::Handle module, but I think it is not working. Here is an example:
host.pl:
use feature qw(say);
use strict;
use warnings;
my $client_pid = open ( my $fh, '|-', 'client.pl' )
or die "Could not open client: $!";
#$fh->autoflush(1); # adding this line does not help
sleep 2;
say "Host: sent message";
print $fh "Hello";
#print $fh "Hello\n"; # adding a newline works fine
$fh->flush() or warn "$!"; # this does not work
sleep 2;
say "Host exits.";
close $fh;
client.pl:
use feature qw(say);
use strict;
use warnings;
say "Client running..";
chomp (my $line = <STDIN>);
say "Client got line: '$line'";
sleep 1;
say "Client exits..";
The output of running host.pl is:
Client running..
Host: sent message
Host exits.
Client got line: 'Hello'
Client exits..
Expected output would be:
Client running..
Host: sent message
Client got line: 'Hello'
Client exits..
Host exits.
I know I can fix this by adding a newline at the end of string to be printed:
print $fh "Hello\n";
but I am curious why $fh->flush() is not working here?
The data is being sent to the client immediately, but the client waits for a newline to arrive.
readline (for which <> is a shortcut in your program) reads until a newline is encountered before returning (although changing $/ can change that behaviour. If you want a call that returns as soon as data is available, use sysread.
use BLOCK_SIZE => 64*1024;
say "Client running..";
while (1) {
my $rv = sysread(\*STDIN, my $buf, BLOCK_SIZE);
die($!) if !defined($rv);
last if !$rv;
say "Got: $buf";
}
Note a single print can result in data being received in multiple chunks. In practice, especially with a socket instead of a pipe, you'd need some way of framing your messages in order to reliably identify them. For example, the following client expects sentinel-terminated messages (the sentinel being a newline):
use BLOCK_SIZE => 64*1024;
say "Client running..";
my $buf = '';
while (1) {
my $rv = sysread(\*STDIN, $buf, BLOCK_SIZE, length($buf));
die($!) if !defined($rv);
last if !$rv;
while ($buf =~ s/^([^\n]*)\n//) {
my $msg = $1;
say "Got: $msg";
}
say "Got a partial message" if length($buf);
}
die("Premature EOF\n") if length($buf);
Try sending:
$fh->autoflush();
print($fh "abc");
sleep(1);
print($fh "def\n");
sleep(1);
print($fh "ghi\njkl\nmno");
sleep(1);
print($fh "pqr\n");
This can be adapted to handle length-prefixed messages or any other message format.

Perl script to read 500 entries from a file as input

I'm trying to run a perl script which reads a text file that contains say 500 entries, read one entry at a time and send a command.
The command is server hostname where the value of hostname is the list of hostnames in the text file.
I'm new to programming,As per my understanding we need to open the file that contains the host name and read it open (ENABLE, "<hostanmes.txt") || die "could not open output file";
use a for loop to read the 512 host names in it for($i=1; $i<=512; $i++)
But I'm not sure how to connect this file to the command server hostname
The program is incomplete.I'm struck and not really sure.Can somebody please help me with this ?
#!/usr/bin/perl
## Library import
use Net::SSH::Expect;
use strict;
use warnings;
use autodie;
print "\n [INFO] script Execution Started \n";
my $ssh = Net::SSH::Expect->new (host => "ip addr",
password=> 'pwd',
user => 'username',
raw_pty => 1);
my $login_output = $ssh->login();
print "\n [INFO] add host rules \n";
open (ENABLE, "<hostanmes.txt") || die "could not open output file";
for($i=1; $i<=512; $i++)
{
my $cfg = $ssh->exec("config");
my $cmd = $ssh->exec("server www.google.com");
my $cmd = $ssh->exec("exit");
}
close(ENABLE);
The essence of the answer is that you can interpolate the value of scalar or array variables into a double-quoted string by just naming them inside the string. For instance
my $x = 42;
print "x = $x\n";
will print
x = 42
Here are some other points about your program
The use for any modules should come after use strict and use warnings, which should ordinarily be the very first lines of a program
It is best practice to use lexical file handles with the three-parameter form of open, and if you have use autodie in place then it is pointless to check the success of the open as it has already been done for you. So
open (ENABLE, "<hostanmes.txt") || die "could not open output file";
should be
open my $enable, '<', 'hostnames.txt';
Unless you need the array indices for another reason, it is best in Perl to iterate over just the array values.
Here is a rewrite of your code that takes into account these points. It looks like it will do what you need
use strict;
use warnings;
use autodie;
use Net::SSH::Expect;
print "\n[INFO] script Execution Started\n";
my $ssh = Net::SSH::Expect->new(
host => "ip addr",
password => 'pwd',
user => 'username',
raw_pty => 1,
);
my $login_output = $ssh->login;
print "\n[INFO] add host rules\n";
open my $enable, '<', 'hostnames.txt';
while (my $server = <$enable>) {
chomp $server;
$ssh->exec('config');
$ssh->exec("server $server");
$ssh->exec('exit');
}
To iterate through ENABLE once you have it you should use a simple while loop:
while(<ENABLE>){
chomp;
//each line read from ENABLE will be stored in $_ per loop
}
This way you do not need a for loop to iterate. So in essence you would run the "server hostname" command in this while loop:
...
while(<ENABLE>) {
chomp;
$ssh->exec("server $_");
}
...
Check here for details.

How can I share Perl data structures through a socket?

In sockets I have written the client server program. First I tried to send the normal string among them it sends fine. After that I tried to send the hash and array values from client to server and server to client. When I print the values using Dumper, it gives me only the reference value. What should I do to get the actual values in client server?
Server Program:
use IO::Socket;
use strict;
use warnings;
my %hash = ( "name" => "pavunkumar " , "age" => 20 ) ;
my $new = \%hash ;
#Turn on System variable for Buffering output
$| = 1;
# Creating a a new socket
my $socket=
IO::Socket::INET->new(LocalPort=>5000,Proto=>'tcp',Localhost =>
'localhost','Listen' => 5 , 'Reuse' => 1 );
die "could not create $! \n" unless ( $socket );
print "\nUDPServer Waiting port 5000\n";
my $new_sock = $socket->accept();
my $host = $new_sock->peerhost();
while(<$new_sock>)
{
#my $line = <$new_sock>;
print Dumper "$host $_";
print $new_sock $new . "\n";
}
print "$host is closed \n" ;
Client Program
use IO::Socket;
use Data::Dumper ;
use warnings ;
use strict ;
my %hash = ( "file" =>"log.txt" , size => "1000kb") ;
my $ref = \%hash ;
# This client for connecting the specified below address and port
# INET function will create the socket file and establish the connection with
# server
my $port = shift || 5000 ;
my $host = shift || 'localhost';
my $recv_data ;
my $send_data;
my $socket = new IO::Socket::INET (
PeerAddr => $host ,
PeerPort => $port ,
Proto => 'tcp', )
or die "Couldn't connect to Server\n";
while (1)
{
my $line = <stdin> ;
print $socket $ref."\n";
if ( $line = <$socket> )
{
print Dumper $line ;
}
else
{
print "Server is closed \n";
last ;
}
}
I have given my sample program about what I am doing. Can any one tell me what I am doing
wrong in this code? And what I need to do for accessing the hash values?
When you say
print $ref;
, you're in part instructing Perl to turn $ref into a string (since only strings can be printed). It turns out that references don't turn into very useful strings by default.
You need to turn $ref into a string that you can send across the wire and then decode on the other side to get the data back out. This process is referred to as "serialization". Data::Dumper's output is actually a valid serialization of its arguments, but the basic serialization module in Perl is Storable.
Procedurally, you can say[1]
use Storable qw(nfreeze); # nfreeze rather than freeze because
# we want a network-portable string
...
print nfreeze($ref);
on one side and
use Storable qw(thaw);
...
my $ref = thaw($line);
on the other.
There's also an OO interface; read the Storable documentation for more information.
[1]: Notice the yaddayaddas. This is incomplete code that merely illustrates the key differences from your code.
Problem is you are sending references to the data, not the data itself. You need to serialize the data somehow. JSON is a very easy way to do that. There is also YAML.

Why does my Perl script complain about "Global symbol "$random_name" requires explicit package name"?

I'm learning Perl and at the same time I'm creating a program to my family events, but when I'm trying to use a array with a randomization process I'm getting some errors, as you could see:
[ubuntu#eeepc:~/Desktop/mail] ./get.pl -h pop.vix.terra.com.br -u nathanpc -p (:D)
Global symbol "$random_name" requires explicit package name at ./get.pl line 17.
Execution of ./get.pl aborted due to compilation errors.
[ubuntu#eeepc:~/Desktop/mail]
And my code is like this:
#!/usr/bin/perl
# import packages
use Net::POP3;
use Getopt::Long;
use Mail::Message;
use List::Util qw(shuffle);
use strict;
use warnings;
# Variable declaration
my $host;
my $user;
my $pass;
my $email_file;
my $msg;
my #array = shuffle(<$random_name>);
# read command line options
# display usage message in case of error
GetOptions ('h|host=s' => \$host,
'u|user=s' => \$user,
'p|pass=s' => \$pass) or die("Input error. Try calling me with: -h <host> -u <username> -p <password>");
# file operations
open($email_file, ">>", "Mail.txt");
open my $random_name, "<", "out.txt";
# initiate connection
# default timeout = 120 sec
my $conn = Net::POP3->new($host) or die("ERROR: Unable to connect.\n");
# login
my $numMsg = $conn->login($user, $pass) or die("ERROR: Unable to login.\n");
# get message numbers
# iterate over list and print first 20 lines of each
if ($numMsg > 0) {
my $msgList = $conn->list();
foreach $msg (keys(%$msgList)) {
my $rawdata = $conn->get($msg);
my $msg_obj = Mail::Message->read($rawdata);
my $body = $msg_obj->body;
print $email_file $body;
print $email_file "\n====================================================\n";
print shift #array;
}
} else {
print "Mailbox is empty.\n";
}
# close connection
$conn->quit();
close($email_file);
close($random_name);
The answers from Greg Hewgill and ~unutbu are correct. I just wanted to add that it's best not to pre-declare variables, that may have helped a tad in understanding what was wrong.
Here is your identical code with some slight changes:
#!/usr/bin/perl
# import packages
use Net::POP3;
use Getopt::Long;
use Mail::Message;
use List::Util qw(shuffle);
use strict;
use warnings;
# read command line options
# display usage message in case of error
my ($host, $user, $pass);
GetOptions ('h|host=s' => \$host,
'u|user=s' => \$user,
'p|pass=s' => \$pass) or die("Input error. Try calling me with: -h <host> -u <username> -p <password>");
# file operations
open (my $email_file, ">>", "Mail.txt") or die ("Error opening Mail.txt for write: $!");
open (my $random_name, "<", "out.txt") or die ("Error opening out.txt for read: $!");
my #array = shuffle(<$random_name>);
close($random_name);
# initiate connection
# default timeout = 120 sec
my $conn = Net::POP3->new($host) or die("ERROR: Unable to connect.\n");
# login
my $numMsg = $conn->login($user, $pass) or die("ERROR: Unable to login.\n");
# get message numbers
# iterate over list and print first 20 lines of each
if ($numMsg > 0) {
my $msgList = $conn->list();
foreach my $msg (keys(%$msgList)) {
my $rawdata = $conn->get($msg);
my $msg_obj = Mail::Message->read($rawdata);
my $body = $msg_obj->body;
print $email_file $body;
print $email_file "\n====================================================\n";
print shift #array;
}
} else {
print "Mailbox is empty.\n";
}
# close connection
$conn->quit();
close($email_file) or die "Error closing Mail.txt from write: $!";
I removed the predeclaration of variables.
I changed the two opens to both use parenthesis and both check for errors.
I moved declaring and setting #array to just after out.txt is opened.
Since $random_file is not needed after #array is set, I close it on the next line.
Lastly, I check for errors when closing Mail.txt which was opened for writing. It is very important to check the return value of close on a file you opened for writing as certain errors, like running out of disk space while writing to the file, will not be seen in the initial open but will be visible by checking that close($fh) returned true.
There is still room for improvement but those were the biggies. I must say though, your code was a pretty good start for someone new to Perl. Using use strict and warnings, the foreach loop to iterate over the keys of the hash as well as Getopt::Long vs trying to parse command line arguments yourself are good to see.
This is the line that is causing the problem.
my #array = shuffle(<$random_name>);
You need to define $random_name before using it. Try
open my $random_name, "<", "out.txt";
my #array = shuffle(<$random_name>);
On line 17, $random_name is not initialised yet. You will want to put this statement after the $random_name file is opened (line 27).