use strict;
use warnings;
use threads;
use threads::shared;
use IO::Handle;
use IO::Socket::INET;
use File::Find;
use File::Path;
use Digest::SHA;
########################################################################
######## ARGUMENT CHECKING
#######################################################################
my $debug = 0;
my $port = 1600;
while ( defined($ARGV[0]) and substr( $ARGV[0],0,1 ) eq "-" ) {
if ( $ARGV[0] eq "-d" ) { $debug = 1; shift; next; }
if ( $ARGV[0] eq "-p" ) { $port = $ARGV[1]; shift; shift; next; }
if ( $ARGV[0] eq "-h" ) { usage(); exit 1; }
print "Warning: Invalid argument '".$ARGV[0]."' ignored.\n";
shift;
}
my $password = $ARGV[0] || "hot34tot";
##########################################################################
######## Thread-safe printing functions
#######################################################################
$|++;
my $semSTDOUT :shared;
sub tprint{
lock $semSTDOUT;
my $msg = shift;
my $tid = shift || threads->tid();
if($tid ne ""){$tid = "($tid)"};
print "RXD+ $tid > $msg \n";
}
sub debug{
if($debug){
lock $semSTDOUT;
my $msg = shift;
my $tid = shift || threads->tid();
if($tid == 0){ $tid = "MAIN";}
if($tid ne ""){$tid = "($tid) "};
print "DEBUG -- " . $tid . $msg . "\n";;
}
}
sub usage
{
print "Usage: rxd [-d][-p Port] Password\n";
}
##########################################################################
##########################################################################
######## SEND\RECEIVE parsing functions
#######################################################################
sub receiveCommand{
my $client = shift;
debug("Waiting for command input");
my $command = "";
my $args = "";
my $buffer = '';
my $i = 0;
while( my $num = sysread($client, $buffer, 100000, length($buffer))) { #attempt to receive 100000 bytes at a time
$i++;
my $len = length($buffer);
debug($num . " bytes received, bringing the total to " . $len);
$command = flagUnwrap("RX", $buffer);
if( $command ne ""){
last;
}
if($i > 999999999){ #infinite loop stopper
debug("Terminating infinite loop, closing connection to $client"); # just in case, but sysread would probably block anyways
last;
}
}
if(length($buffer) < 300){
debug("Raw text received = $buffer");
}else{
debug("Received " . length($buffer) . " bytes from RX");
}
return ($command);
}
sub sendResponse{
my $msg = shift;
my $rc = shift;
my $client = shift;
$msg = flagWrap("RESPONSE", $msg);
my $rcMSG = flagWrap("RETURN CODE", $rc);
my $response = flagWrap("RXD", $msg . $rcMSG);
my $len = length($response);
if($len < 200){
debug("Command complete, sending response '$response'");
}else{
debug("Command complete, sending $len bytes in response.");
}
tprint("Command completed with a return code of $rc", threads->tid());
syswrite($client, $response, length($response)) or debug(">>ERROR<< Couldnt write to client");
debug("sent");
}
##########################################################################
##########################################################################
######## Worker Thread subroutine
#######################################################################
sub processRequest {
my $client;
my $tid = threads->tid;
my $fno = shift;
open $client, "+<&", $fno or threads->exit;
$client->autoflush(1);
debug("Connected to client machine");
my $response = "";
my $returnCode = "";
my $fullCMD = receiveCommand($client);
my $pswd = flagUnwrap("PASSWORD", $fullCMD);
my $command = uc(flagUnwrap("COMMAND", $fullCMD)); #Command ie EXEC
my $args = flagUnwrap("ARGUMENTS", $fullCMD); #arguments for command
tprint("$command $args");
my $additionalData = flagUnwrap("ADDITIONAL DATA", $fullCMD); #like a file
my $shaValue = flagUnwrap("SHA VALUE", $fullCMD);
my $readMoreSize = flagUnwrap("READ SIZE", $fullCMD); #indicating a long file
if($pswd eq $password){
#debug($readMoreSize);
if($readMoreSize ne ""){ #RX is going to try to send more stuff!
debug("Now waiting for the client to send $readMoreSize more bytes");
my $i = 0;
while(length($additionalData) < int($readMoreSize) and $i < 9999999){
$i++;
my $num = sysread($client, $additionalData, $readMoreSize, length($additionalData));
my $len = length($additionalData);
if(defined($num)){
debug($num . " bytes received, bringing the total to " . $len);
}
}
}
my $len = length($additionalData);
if($len > 0){
if($len < 200 and $additionalData !~ /\n/ms){
tprint("Additional data received: $additionalData");
}else{
tprint("Received $len bytes of additional data");
}
}
($response, $returnCode) = executeCommand($command, $args, $additionalData, $client, $shaValue);
}else{
$response = "Incorrect password specified";
$returnCode = 1;
}
#since execprint needs to be able to send while simultaneously executing the command
#cannot store output in a string to send at once
if ($command ne 'EXECPRINT' and $command ne 'GET' and $command ne 'GETB') {
sendResponse($response, $returnCode, $client);
}
debug("Closing connection");
shutdown($client, 2);
close $client;
threads->exit;
}
sub executeCommand{
my $rxdCMD = uc(shift);
my $rxdArgs = shift;
my $rxdData = shift;
my $client = shift;
my $shaValue = shift || "";
my $resp = "";
my $rc = 0;
if($rxdCMD ne ""){
debug("Processing command '$rxdCMD'");
if ( $rxdCMD eq 'EXEC' && $rxdArgs ne '' ) # EXEC(Command)
{
if ( $^O eq "MSWin32" ) {
#$resp = `$rxdArgs 2>>&1`;
open(my $fs, $rxdArgs . " 2>&1|") or threads->exit;
select((select($fs), $| = 1)[0]);
while(my $line = <$fs>){
$resp .= $line;
}
close($fs);
}else {
$rxdArgs =~ s/ \*/ \\\*/g;
#$resp = `$rxdArgs 2>&1`;
open(my $fs, $rxdArgs . " 2>&1|") or threads->exit;
select((select($fs), $| = 1)[0]);
$resp = <$fs>;
close($fs);
}
$rc = int($?>>8);
}
elsif ( $rxdCMD eq 'MSG' && $rxdArgs ne '') # EXEC(Command)
{
print "MESSAGE FROM RX: $rxdArgs\n";
}
elsif ( $rxdCMD eq 'PUT' or $rxdCMD eq 'PUTB' && $rxdArgs ne '' ) # PUT/PUTB(Filespec)
{
my $outfile = "";
(my $unused, $outfile) = getDirs($rxdArgs);
open( PUTFILE, ">$outfile" ) or threads->exit;
if($rxdCMD eq 'PUTB'){
debug("setting binmode for PUTFILE");
binmode (PUTFILE);
}
if ( $rxdData eq "" ){
$rc = 1;
$resp = "File was not received properly";
close PUTFILE;
debug("The file was empty!");
}else{
print PUTFILE $rxdData;
$resp = "File was transferred successfully.";
close PUTFILE;
debug("File written!");
}
my $rxdSHA = $shaValue;
debug("Calculating SHA value of file");
my $val = calculateSHA($outfile);
if($val ne $rxdSHA){
debug("$val did not equal $rxdSHA");
$resp = "The file was transferred successfully, however there are data integrity errors";
$rc = 1;
}else{
debug("SHA values were a match");
}
}
######################### NEW CMDS ##############################################
elsif ( $rxdCMD eq 'EXECPRINT' && $rxdArgs ne '' ) # EXEC(Command)
{
my $rxdBegin = "***RXD BEGIN***";
my $h= "***RESPONSE BEGIN***";
my $f= "***RESPONSE END***";
my $r= "***RETURN CODE BEGIN***";
my $r2= "***RETURN CODE END***";
my $rxdEnd = "***RXD END***";
open(my $fs, $rxdArgs . " 2>&1|") or threads->exit;
select((select($fs), $| = 1)[0]);
syswrite($client, $rxdBegin.$h, length($rxdBegin.$h));
if(defined($fs)){
while(my $line = <$fs>){
syswrite($client, $line, length($line));
}
close ($fs);
$rc = int($?>>8);
}else{
$rc = 1;
}
my $end = $f . $r . $rc . $r2 . $rxdEnd;
syswrite($client, $end, length($end));
tprint("Command completed with a return code of $rc");
}
elsif ( $rxdCMD eq 'GETFILESYSINFO' && $rxdArgs ne '' ) # GETFILESYSINFO(Command)
{
# verify the directory exist
if (-d $rxdArgs) {
# check if trailing slash was added... if not add it
if ( $^O eq "MSWin32" ) {
unless ($rxdArgs =~ /\\$/) {
$rxdArgs .= "\\";
}
} else {
unless ($rxdArgs =~ /\/$/) {
$rxdArgs .= "/";
}
}
# set the SHA object
my $sha = Digest::SHA->new(256);
# use File::Find to step the dir structure
my $string;
my $error;
find({wanted => sub {wanted(\$string, \$sha, \$error);}, no_chdir => 1}, $rxdArgs);
sub wanted {
my $string = shift;
my $sha = shift;
my $error = shift;
# check if current object is a file
if (-f $File::Find::name) {
# Calculate the SHA value
eval {
$$sha->addfile($File::Find::name, "p");
my $value = $$sha->hexdigest;
# calculate the size of the file in bytes
my $size = -s $File::Find::name;
# checking for any XML special chars in the name
my $name = $File::Find::name;
$name =~ s/&/&/g;
$name =~ s/</g;
$name =~ s/>/>/g;
$name =~ s/"/"/g;
# set results to $string and send $string to rx.pl
$$string .= "" . $name . "" . $value . "" . $size . "";
};
if ($@) {
$$error .= $File::Find::name . "\n";
}
}
}
if (defined $error) {
$resp = "Erorrs occured on the following files:\n" . $error;
$rc = 1;
} else {
$resp = "" . $string . "";
$rc = 0;
}
} else {
$resp = "Directory does not exist.";
$rc = 1;
}
}
elsif ($rxdCMD eq 'MKDIR' && $rxdArgs ne '') {
if (mkpath($rxdArgs)) {
$resp = "Made directory \"" . $rxdArgs . "\"";
$rc = 0;
} else {
$resp = "Failed to make directory \"" . $rxdArgs . "\"";
$rc = 1;
}
}
elsif ($rxdCMD eq 'RMDIR' && $rxdArgs ne '') {
if (rmtree($rxdArgs)) {
$resp = "Removed directory \"" . $rxdArgs . "\"";
$rc = 0;
} else {
$resp = "Failed to remove directory \"" . $rxdArgs . "\"";
$rc = 1;
}
}
elsif ($rxdCMD eq 'RMFILES' && $rxdArgs ne '') {
if (rmtree($rxdArgs, {keep_root => 1})) {
$resp = "Removed files in directory \"" . $rxdArgs . "\"";
$rc = 0;
} else {
$resp = "Failed to remove files in directory \"" . $rxdArgs . "\"";
$rc = 1;
}
}
else{
$resp = "Unrecognized command. Pleas refer to the documentation for a list of possible commands.";
$rc = 1;
}
}else{
$rc = 1;
$resp = "Command was not passed successfully.";
}
if($resp eq ''){
if($rc){
$resp = "Command was not completed successfully.";
}else{
$resp = "Command was completed successfully.";
}
}
debug("Command execution completed!");
return ($resp, $rc);
}
sub calculateSHA{
my $file = shift;
my $val = 0;
open(my $fh, $file) or threads->exit;
eval{
my $sha1 = Digest::SHA->new(256);
$sha1->addfile($fh);
$val = $sha1->hexdigest;
};
if($@){
debug("An error occurred calculating the SHA-Value. Cannot verify integrity of file\n$@");
}
close($fh);
return $val;
}
sub getDirs{
my $args = shift;
my $dir1 = "";
my $dir2 = "";
$dir2 = $2 if ($args =~ /"(.+)"\s+"(.+)"/i);
if($dir2 eq ""){
$dir1 = $args;
# $dir2 = $dir1;
}else{
$dir1 = $1 if ($args =~ /"(.+)"\s+"(.+)"/i);
}
debug("Source file is: " . $dir1);
debug("Destination file is: " . $dir2);
return ($dir1, $dir2);
}
sub flagWrap {
my $flag = shift;
my $string = shift;
my $flagPattern = '***';
return $flagPattern . $flag . " BEGIN" . $flagPattern . $string . $flagPattern . $flag . " END" . $flagPattern;
}
sub flagUnwrap{
my $flag = shift;
my $string = shift;
my $flagPattern = '\*\*\*';
my $regexH = $flagPattern . $flag . " BEGIN" . $flagPattern;
my $regexT = $flagPattern . $flag . " END" . $flagPattern;
if($string =~ /$regexH(.+)$regexT/ims){
return $1;
}else{
return "";
}
}
##########################################################################
#############################################################################
######## MAIN THREAD
my $lsn = new IO::Socket::INET(
Listen => 100,
LocalPort => $port,
ReuseAddr => 1,
Blocking => 1,
) or die "Failed to open listening port: $!\n";
print "RXD+ had been started on port $port\n";
debug("Password is '$password'");
while(1) {
my $client = $lsn->accept;
if(!defined($client)){tprint("Could not connect to client"); next;}
debug("Creating a new thread");
my $thd = threads->create('processRequest', fileno($client)) or exit;
$thd ->detach();
debug("New thread created TID: " . $thd->tid());
}
sub shutdownRXD{
exit 0;
}
#############################################################################
END{
print "The RXD server has been shutdown.";
}