diff --git a/pandora_agents/pc/NT4/tentacle_client.exe b/pandora_agents/pc/NT4/tentacle_client.exe index bc609233b5..9566f8cde2 100644 Binary files a/pandora_agents/pc/NT4/tentacle_client.exe and b/pandora_agents/pc/NT4/tentacle_client.exe differ diff --git a/pandora_agents/pc/Win32/util/tentacle_client.exe b/pandora_agents/pc/Win32/util/tentacle_client.exe index bc609233b5..9566f8cde2 100644 Binary files a/pandora_agents/pc/Win32/util/tentacle_client.exe and b/pandora_agents/pc/Win32/util/tentacle_client.exe differ diff --git a/pandora_agents/pc/Win32/util/tentacle_server.exe b/pandora_agents/pc/Win32/util/tentacle_server.exe index a1b10c214f..95e1e5325e 100644 Binary files a/pandora_agents/pc/Win32/util/tentacle_server.exe and b/pandora_agents/pc/Win32/util/tentacle_server.exe differ diff --git a/pandora_agents/pc/tentacle_client b/pandora_agents/pc/tentacle_client index 003acbdf69..ffed3da859 100644 --- a/pandora_agents/pc/tentacle_client +++ b/pandora_agents/pc/tentacle_client @@ -58,6 +58,8 @@ use strict; use File::Basename; use Getopt::Std; use IO::Select; +use IO::Compress::Zip qw(zip $ZipError); +use IO::Uncompress::Unzip qw(unzip $UnzipError); use Socket (qw(SOCK_STREAM AF_INET AF_INET6)); my $SOCKET_MODULE = eval { require IO::Socket::INET6 } ? 'IO::Socket::INET6' @@ -131,6 +133,12 @@ my $t_ssl_pwd = ''; # Timeout for socket read/write operations in seconds my $t_timeout = 1; +# bind ipaddr +my $t_bind_address = undef; + +# Compress data before sending it through the socket. +my $t_zip = 0; + ################################################################################ ## SUB print_help ## Print help screen. @@ -141,6 +149,7 @@ sub print_help { print ("Tentacle client v$VERSION. See http://www.openideas.info/wiki for protocol description.\n\n"); print ("Options:\n"); print ("\t-a address\tServer address (default $t_address).\n"); + print ("\t-b localaddress\tLocal address to bind.\n"); print ("\t-c\t\tEnable SSL without a client certificate.\n"); print ("\t-e cert\t\tOpenSSL certificate file. Enables SSL.\n"); print ("\t-f ca\t\tVerify that the peer certificate is signed by a ca.\n"); @@ -154,7 +163,8 @@ sub print_help { print ("\t-v\t\tBe verbose.\n"); print ("\t-w\t\tPrompt for OpenSSL private key password.\n"); print ("\t-x pwd\t\tServer password.\n"); - print ("\t-y proxy\tProxy server string (user:password\@address:port).\n\n"); + print ("\t-y proxy\tProxy server string (user:password\@address:port).\n"); + print ("\t-z Compress data.\n\n"); } ################################################################################ @@ -166,7 +176,7 @@ sub parse_options { my $tmp; # Get options - if (getopts ('a:ce:f:ghk:p:qr:t:vwx:y:', \%opts) == 0 || defined ($opts{'h'})) { + if (getopts ('a:b:ce:f:ghk:p:qr:t:vwx:y:z', \%opts) == 0 || defined ($opts{'h'})) { print_help (); exit 1; } @@ -183,6 +193,18 @@ sub parse_options { } + # Bind local address + if (defined ($opts{'b'})) { + $t_bind_address = $opts{'b'}; + if (($t_bind_address !~ /^[a-zA-Z\.][a-zA-Z0-9\.\-]+$/ && ($t_bind_address !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ + || $1 < 0 || $1 > 255 || $2 < 0 || $2 > 255 + || $3 < 0 || $3 > 255 || $4 < 0 || $4 > 255)) && + ($t_address !~ /^[0-9a-f:]+$/o)) { + error ("Local address $t_bind_address is not valid."); + } + + } + # Enable SSL without a client certificate if (defined ($opts{'c'})) { require IO::Socket::SSL; @@ -299,6 +321,11 @@ sub parse_options { error ("Proxy port $t_proxy_port is not valid."); } } + + # Compress data + if (defined ($opts{'z'})) { + $t_zip = 1; + } } ################################################################################ @@ -309,20 +336,42 @@ sub start_client { # Connect to server if ($SOCKET_MODULE ne 'IO::Socket::INET') { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET6, - PeerAddr => $t_address, - PeerPort => $t_port, - Type => SOCK_STREAM - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_address, + PeerPort => $t_port, + LocalAddr => $t_bind_address, + Type => SOCK_STREAM + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_address, + PeerPort => $t_port, + Type => SOCK_STREAM + ); + } } if (! defined ($t_socket)) { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET, - PeerAddr => $t_address, - PeerPort => $t_port, - Type => SOCK_STREAM - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_address, + PeerPort => $t_port, + LocalAddr => $t_bind_address, + Type => SOCK_STREAM + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_address, + PeerPort => $t_port, + Type => SOCK_STREAM + ); + } } if (! defined ($t_socket)) { @@ -344,18 +393,38 @@ sub start_client_proxy { # Connect to proxy if ($SOCKET_MODULE ne 'IO::Socket::INET') { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET6, - PeerAddr => $t_proxy_address, - PeerPort => $t_proxy_port, - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + LocalAddr => $t_bind_address, + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + ); + } } if (! defined ($t_socket)) { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET, - PeerAddr => $t_proxy_address, - PeerPort => $t_proxy_port, - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + LocalAddr => $t_bind_address, + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + ); + } } if (! defined ($t_socket)) { @@ -408,6 +477,8 @@ sub start_ssl { if ($t_ssl_cert eq ''){ IO::Socket::SSL->start_SSL ( $t_socket, + # No authentication + SSL_verify_mode => 0x00, ); } elsif ($t_ssl_ca eq '') { @@ -525,6 +596,46 @@ sub recv_file { print_log ("Received file '$file'"); } +################################################################################ +## SUB zrecv_file +## Receive a compressed file from the server +################################################################################ +sub zrecv_file { + my $data = ''; + my $file = $_[0]; + my $response; + my $size; + my $zdata = ''; + + # Request file + send_data ("ZRECV <$file>\n"); + + # Wait for server response + $response = recv_command (); + if ($response !~ /^ZRECV SIZE (\d+)$/) { + error ("Server responded $response."); + } + + $size = $1; + send_data ("ZRECV OK\n"); + + # Receive file + $zdata = recv_data_block ($size); + if (!unzip(\$zdata => \$data)) { + print_log ("Uncompress error: $UnzipError"); + send_data ("ZRECV ERR\n"); + return; + } + + # Write it to disk + open (FILE, "> $file") || error ("Cannot open file '$file' for writing."); + binmode (FILE); + print (FILE $data); + close (FILE); + + print_log ("Received compressed file '$file'"); +} + ################################################################################ ## SUB send_file ## Send a file to the server @@ -578,6 +689,55 @@ sub send_file { print_log ("File sent"); } +################################################################################ +## SUB zsend_file +## Send a file to the server (compressed) +################################################################################ +sub zsend_file { + my $base_name; + my $data = ''; + my $response = ''; + my $retries; + my $file = $_[0]; + my $size; + my $written; + + # Read the file and compress its contents + if (! zip($file => \$data)) { + send_data ("QUIT\n"); + error ("Compression error: $ZipError"); + return; + } + + $size = length($data); + $base_name = basename ($file); + + # Request to send file + send_data ("ZSEND <$base_name> SIZE $size\n"); + print_log ("Request to send file '$base_name' size ${size}b (compressed)"); + + # Wait for server response + $response = recv_command (); + + # Server rejected the file + if ($response ne "ZSEND OK") { + send_data ("QUIT\n"); + error ("Server responded $response."); + } + + print_log ("Server responded SEND OK"); + send_data ($data); + + # Wait for server response + $response = recv_command (); + if ($response ne "ZSEND OK") { + send_data ("QUIT\n"); + error ("Server responded $response."); + } + + print_log ("File sent"); +} + ################################################################################ # Common functions ################################################################################ @@ -830,13 +990,21 @@ if ($t_recv == 0) { # Send the files foreach $file (@ARGV) { - send_file ($file); + if ($t_zip == 1) { + zsend_file($file); + } else { + send_file ($file); + } } } else { - # Send the files + # Receive the files foreach $file (@ARGV) { - recv_file ($file); + if ($t_zip == 1) { + zrecv_file ($file); + } else { + recv_file ($file); + } } } @@ -882,6 +1050,8 @@ __END__ =item I<-x pwd> B. +=item I<-z> Compress data. + =back =head1 EXIT STATUS diff --git a/pandora_agents/pc/tentacle_server b/pandora_agents/pc/tentacle_server index d6b5d4d3bd..8b27ed35b3 100755 --- a/pandora_agents/pc/tentacle_server +++ b/pandora_agents/pc/tentacle_server @@ -60,6 +60,8 @@ use strict; use warnings; use Getopt::Std; use IO::Select; +use IO::Compress::Zip qw(zip $ZipError); +use IO::Uncompress::Unzip qw(unzip $UnzipError); use threads; use Thread::Semaphore; use POSIX ":sys_wait_h"; @@ -959,6 +961,15 @@ sub serve_connection { print_info ("Request to receive file '$1' from " . $t_client_socket->sockhost ()); send_file ($1); } + elsif ($command =~ /^ZSEND <(.*)> SIZE (\d+)$/) { + print_info ("Request to send compressed file '$1' size ${2}b from " . $t_client_socket->sockhost ()); + zrecv_file ($1, $2); + } + # Client wants to receive a file + elsif ($command =~ /^ZRECV <(.*)>$/) { + print_info ("Request to receive compressed file '$1' from " . $t_client_socket->sockhost ()); + zsend_file ($1); + } # Quit elsif ($command =~ /^QUIT$/) { print_info ("Connection closed from " . $t_client_socket->sockhost ()); @@ -1070,6 +1081,61 @@ sub recv_file { print_info ("Received file '$base_name' size ${size}b from " . $t_client_socket->sockhost ()); } +################################################################################ +## SUB zrecv_file +## Receive a compressed file of size $_[1] and save it in $t_directory as $_[0]. +################################################################################ +sub zrecv_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $size = $_[1]; + my $zdata = ''; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("ZSEND ERR (invalid file name)\n"); + return; + } + + # Check file size, empty files are not allowed + if ($size < 1 || $size > $t_max_size) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " is too big"); + send_data ("ZSEND ERR (file is too big)\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (-f $file && $t_overwrite == 0) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " already exists"); + send_data ("ZSEND ERR (file already exists)\n"); + return; + } + + send_data ("ZSEND OK\n"); + + # Receive file + $zdata = recv_data_block ($size); + if (!unzip(\$zdata => \$data)) { + print_log ("Uncompress error: $UnzipError"); + send_data ("ZSEND ERR\n"); + return; + } + + # Write it to disk + open (FILE, "> $file") || error ("Cannot open file '$file' for writing."); + binmode (FILE); + print (FILE $data); + close (FILE); + + send_data ("ZSEND OK\n"); + print_info ("Received compressed file '$base_name' size ${size}b from " . $t_client_socket->sockhost ()); +} + ################################################################################ ## SUB send_file ## Send a file to the client @@ -1122,6 +1188,57 @@ sub send_file { print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " sent"); } +################################################################################ +## SUB zsend_file +## Send a file to the client +################################################################################ +sub zsend_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $response; + my $size; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("Requested compressed file '$base_name' from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("ZRECV ERR (file has an invalid file name)\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (! -f $file) { + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " does not exist"); + send_data ("ZRECV ERR (file does not exist)\n"); + return; + } + + # Read the file and compress its contents + if (! zip($file => \$data)) { + send_data ("QUIT\n"); + error ("Compression error: $ZipError"); + return; + } + + $size = length($data); + send_data ("ZRECV SIZE $size\n"); + + # Wait for client response + $response = recv_command ($t_block_size); + if ($response ne "ZRECV OK") { + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " not sent"); + return; + } + + # Send the file + send_data ($data); + + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " sent"); +} + ################################################################################ # Common functions ################################################################################ diff --git a/pandora_agents/shellscript/linux/tentacle_client b/pandora_agents/shellscript/linux/tentacle_client index 003acbdf69..ffed3da859 100755 --- a/pandora_agents/shellscript/linux/tentacle_client +++ b/pandora_agents/shellscript/linux/tentacle_client @@ -58,6 +58,8 @@ use strict; use File::Basename; use Getopt::Std; use IO::Select; +use IO::Compress::Zip qw(zip $ZipError); +use IO::Uncompress::Unzip qw(unzip $UnzipError); use Socket (qw(SOCK_STREAM AF_INET AF_INET6)); my $SOCKET_MODULE = eval { require IO::Socket::INET6 } ? 'IO::Socket::INET6' @@ -131,6 +133,12 @@ my $t_ssl_pwd = ''; # Timeout for socket read/write operations in seconds my $t_timeout = 1; +# bind ipaddr +my $t_bind_address = undef; + +# Compress data before sending it through the socket. +my $t_zip = 0; + ################################################################################ ## SUB print_help ## Print help screen. @@ -141,6 +149,7 @@ sub print_help { print ("Tentacle client v$VERSION. See http://www.openideas.info/wiki for protocol description.\n\n"); print ("Options:\n"); print ("\t-a address\tServer address (default $t_address).\n"); + print ("\t-b localaddress\tLocal address to bind.\n"); print ("\t-c\t\tEnable SSL without a client certificate.\n"); print ("\t-e cert\t\tOpenSSL certificate file. Enables SSL.\n"); print ("\t-f ca\t\tVerify that the peer certificate is signed by a ca.\n"); @@ -154,7 +163,8 @@ sub print_help { print ("\t-v\t\tBe verbose.\n"); print ("\t-w\t\tPrompt for OpenSSL private key password.\n"); print ("\t-x pwd\t\tServer password.\n"); - print ("\t-y proxy\tProxy server string (user:password\@address:port).\n\n"); + print ("\t-y proxy\tProxy server string (user:password\@address:port).\n"); + print ("\t-z Compress data.\n\n"); } ################################################################################ @@ -166,7 +176,7 @@ sub parse_options { my $tmp; # Get options - if (getopts ('a:ce:f:ghk:p:qr:t:vwx:y:', \%opts) == 0 || defined ($opts{'h'})) { + if (getopts ('a:b:ce:f:ghk:p:qr:t:vwx:y:z', \%opts) == 0 || defined ($opts{'h'})) { print_help (); exit 1; } @@ -183,6 +193,18 @@ sub parse_options { } + # Bind local address + if (defined ($opts{'b'})) { + $t_bind_address = $opts{'b'}; + if (($t_bind_address !~ /^[a-zA-Z\.][a-zA-Z0-9\.\-]+$/ && ($t_bind_address !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ + || $1 < 0 || $1 > 255 || $2 < 0 || $2 > 255 + || $3 < 0 || $3 > 255 || $4 < 0 || $4 > 255)) && + ($t_address !~ /^[0-9a-f:]+$/o)) { + error ("Local address $t_bind_address is not valid."); + } + + } + # Enable SSL without a client certificate if (defined ($opts{'c'})) { require IO::Socket::SSL; @@ -299,6 +321,11 @@ sub parse_options { error ("Proxy port $t_proxy_port is not valid."); } } + + # Compress data + if (defined ($opts{'z'})) { + $t_zip = 1; + } } ################################################################################ @@ -309,20 +336,42 @@ sub start_client { # Connect to server if ($SOCKET_MODULE ne 'IO::Socket::INET') { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET6, - PeerAddr => $t_address, - PeerPort => $t_port, - Type => SOCK_STREAM - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_address, + PeerPort => $t_port, + LocalAddr => $t_bind_address, + Type => SOCK_STREAM + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_address, + PeerPort => $t_port, + Type => SOCK_STREAM + ); + } } if (! defined ($t_socket)) { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET, - PeerAddr => $t_address, - PeerPort => $t_port, - Type => SOCK_STREAM - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_address, + PeerPort => $t_port, + LocalAddr => $t_bind_address, + Type => SOCK_STREAM + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_address, + PeerPort => $t_port, + Type => SOCK_STREAM + ); + } } if (! defined ($t_socket)) { @@ -344,18 +393,38 @@ sub start_client_proxy { # Connect to proxy if ($SOCKET_MODULE ne 'IO::Socket::INET') { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET6, - PeerAddr => $t_proxy_address, - PeerPort => $t_proxy_port, - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + LocalAddr => $t_bind_address, + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + ); + } } if (! defined ($t_socket)) { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET, - PeerAddr => $t_proxy_address, - PeerPort => $t_proxy_port, - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + LocalAddr => $t_bind_address, + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + ); + } } if (! defined ($t_socket)) { @@ -408,6 +477,8 @@ sub start_ssl { if ($t_ssl_cert eq ''){ IO::Socket::SSL->start_SSL ( $t_socket, + # No authentication + SSL_verify_mode => 0x00, ); } elsif ($t_ssl_ca eq '') { @@ -525,6 +596,46 @@ sub recv_file { print_log ("Received file '$file'"); } +################################################################################ +## SUB zrecv_file +## Receive a compressed file from the server +################################################################################ +sub zrecv_file { + my $data = ''; + my $file = $_[0]; + my $response; + my $size; + my $zdata = ''; + + # Request file + send_data ("ZRECV <$file>\n"); + + # Wait for server response + $response = recv_command (); + if ($response !~ /^ZRECV SIZE (\d+)$/) { + error ("Server responded $response."); + } + + $size = $1; + send_data ("ZRECV OK\n"); + + # Receive file + $zdata = recv_data_block ($size); + if (!unzip(\$zdata => \$data)) { + print_log ("Uncompress error: $UnzipError"); + send_data ("ZRECV ERR\n"); + return; + } + + # Write it to disk + open (FILE, "> $file") || error ("Cannot open file '$file' for writing."); + binmode (FILE); + print (FILE $data); + close (FILE); + + print_log ("Received compressed file '$file'"); +} + ################################################################################ ## SUB send_file ## Send a file to the server @@ -578,6 +689,55 @@ sub send_file { print_log ("File sent"); } +################################################################################ +## SUB zsend_file +## Send a file to the server (compressed) +################################################################################ +sub zsend_file { + my $base_name; + my $data = ''; + my $response = ''; + my $retries; + my $file = $_[0]; + my $size; + my $written; + + # Read the file and compress its contents + if (! zip($file => \$data)) { + send_data ("QUIT\n"); + error ("Compression error: $ZipError"); + return; + } + + $size = length($data); + $base_name = basename ($file); + + # Request to send file + send_data ("ZSEND <$base_name> SIZE $size\n"); + print_log ("Request to send file '$base_name' size ${size}b (compressed)"); + + # Wait for server response + $response = recv_command (); + + # Server rejected the file + if ($response ne "ZSEND OK") { + send_data ("QUIT\n"); + error ("Server responded $response."); + } + + print_log ("Server responded SEND OK"); + send_data ($data); + + # Wait for server response + $response = recv_command (); + if ($response ne "ZSEND OK") { + send_data ("QUIT\n"); + error ("Server responded $response."); + } + + print_log ("File sent"); +} + ################################################################################ # Common functions ################################################################################ @@ -830,13 +990,21 @@ if ($t_recv == 0) { # Send the files foreach $file (@ARGV) { - send_file ($file); + if ($t_zip == 1) { + zsend_file($file); + } else { + send_file ($file); + } } } else { - # Send the files + # Receive the files foreach $file (@ARGV) { - recv_file ($file); + if ($t_zip == 1) { + zrecv_file ($file); + } else { + recv_file ($file); + } } } @@ -882,6 +1050,8 @@ __END__ =item I<-x pwd> B. +=item I<-z> Compress data. + =back =head1 EXIT STATUS diff --git a/pandora_agents/shellscript/mac_osx/tentacle_client b/pandora_agents/shellscript/mac_osx/tentacle_client index 003acbdf69..ffed3da859 100755 --- a/pandora_agents/shellscript/mac_osx/tentacle_client +++ b/pandora_agents/shellscript/mac_osx/tentacle_client @@ -58,6 +58,8 @@ use strict; use File::Basename; use Getopt::Std; use IO::Select; +use IO::Compress::Zip qw(zip $ZipError); +use IO::Uncompress::Unzip qw(unzip $UnzipError); use Socket (qw(SOCK_STREAM AF_INET AF_INET6)); my $SOCKET_MODULE = eval { require IO::Socket::INET6 } ? 'IO::Socket::INET6' @@ -131,6 +133,12 @@ my $t_ssl_pwd = ''; # Timeout for socket read/write operations in seconds my $t_timeout = 1; +# bind ipaddr +my $t_bind_address = undef; + +# Compress data before sending it through the socket. +my $t_zip = 0; + ################################################################################ ## SUB print_help ## Print help screen. @@ -141,6 +149,7 @@ sub print_help { print ("Tentacle client v$VERSION. See http://www.openideas.info/wiki for protocol description.\n\n"); print ("Options:\n"); print ("\t-a address\tServer address (default $t_address).\n"); + print ("\t-b localaddress\tLocal address to bind.\n"); print ("\t-c\t\tEnable SSL without a client certificate.\n"); print ("\t-e cert\t\tOpenSSL certificate file. Enables SSL.\n"); print ("\t-f ca\t\tVerify that the peer certificate is signed by a ca.\n"); @@ -154,7 +163,8 @@ sub print_help { print ("\t-v\t\tBe verbose.\n"); print ("\t-w\t\tPrompt for OpenSSL private key password.\n"); print ("\t-x pwd\t\tServer password.\n"); - print ("\t-y proxy\tProxy server string (user:password\@address:port).\n\n"); + print ("\t-y proxy\tProxy server string (user:password\@address:port).\n"); + print ("\t-z Compress data.\n\n"); } ################################################################################ @@ -166,7 +176,7 @@ sub parse_options { my $tmp; # Get options - if (getopts ('a:ce:f:ghk:p:qr:t:vwx:y:', \%opts) == 0 || defined ($opts{'h'})) { + if (getopts ('a:b:ce:f:ghk:p:qr:t:vwx:y:z', \%opts) == 0 || defined ($opts{'h'})) { print_help (); exit 1; } @@ -183,6 +193,18 @@ sub parse_options { } + # Bind local address + if (defined ($opts{'b'})) { + $t_bind_address = $opts{'b'}; + if (($t_bind_address !~ /^[a-zA-Z\.][a-zA-Z0-9\.\-]+$/ && ($t_bind_address !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ + || $1 < 0 || $1 > 255 || $2 < 0 || $2 > 255 + || $3 < 0 || $3 > 255 || $4 < 0 || $4 > 255)) && + ($t_address !~ /^[0-9a-f:]+$/o)) { + error ("Local address $t_bind_address is not valid."); + } + + } + # Enable SSL without a client certificate if (defined ($opts{'c'})) { require IO::Socket::SSL; @@ -299,6 +321,11 @@ sub parse_options { error ("Proxy port $t_proxy_port is not valid."); } } + + # Compress data + if (defined ($opts{'z'})) { + $t_zip = 1; + } } ################################################################################ @@ -309,20 +336,42 @@ sub start_client { # Connect to server if ($SOCKET_MODULE ne 'IO::Socket::INET') { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET6, - PeerAddr => $t_address, - PeerPort => $t_port, - Type => SOCK_STREAM - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_address, + PeerPort => $t_port, + LocalAddr => $t_bind_address, + Type => SOCK_STREAM + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_address, + PeerPort => $t_port, + Type => SOCK_STREAM + ); + } } if (! defined ($t_socket)) { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET, - PeerAddr => $t_address, - PeerPort => $t_port, - Type => SOCK_STREAM - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_address, + PeerPort => $t_port, + LocalAddr => $t_bind_address, + Type => SOCK_STREAM + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_address, + PeerPort => $t_port, + Type => SOCK_STREAM + ); + } } if (! defined ($t_socket)) { @@ -344,18 +393,38 @@ sub start_client_proxy { # Connect to proxy if ($SOCKET_MODULE ne 'IO::Socket::INET') { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET6, - PeerAddr => $t_proxy_address, - PeerPort => $t_proxy_port, - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + LocalAddr => $t_bind_address, + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + ); + } } if (! defined ($t_socket)) { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET, - PeerAddr => $t_proxy_address, - PeerPort => $t_proxy_port, - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + LocalAddr => $t_bind_address, + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + ); + } } if (! defined ($t_socket)) { @@ -408,6 +477,8 @@ sub start_ssl { if ($t_ssl_cert eq ''){ IO::Socket::SSL->start_SSL ( $t_socket, + # No authentication + SSL_verify_mode => 0x00, ); } elsif ($t_ssl_ca eq '') { @@ -525,6 +596,46 @@ sub recv_file { print_log ("Received file '$file'"); } +################################################################################ +## SUB zrecv_file +## Receive a compressed file from the server +################################################################################ +sub zrecv_file { + my $data = ''; + my $file = $_[0]; + my $response; + my $size; + my $zdata = ''; + + # Request file + send_data ("ZRECV <$file>\n"); + + # Wait for server response + $response = recv_command (); + if ($response !~ /^ZRECV SIZE (\d+)$/) { + error ("Server responded $response."); + } + + $size = $1; + send_data ("ZRECV OK\n"); + + # Receive file + $zdata = recv_data_block ($size); + if (!unzip(\$zdata => \$data)) { + print_log ("Uncompress error: $UnzipError"); + send_data ("ZRECV ERR\n"); + return; + } + + # Write it to disk + open (FILE, "> $file") || error ("Cannot open file '$file' for writing."); + binmode (FILE); + print (FILE $data); + close (FILE); + + print_log ("Received compressed file '$file'"); +} + ################################################################################ ## SUB send_file ## Send a file to the server @@ -578,6 +689,55 @@ sub send_file { print_log ("File sent"); } +################################################################################ +## SUB zsend_file +## Send a file to the server (compressed) +################################################################################ +sub zsend_file { + my $base_name; + my $data = ''; + my $response = ''; + my $retries; + my $file = $_[0]; + my $size; + my $written; + + # Read the file and compress its contents + if (! zip($file => \$data)) { + send_data ("QUIT\n"); + error ("Compression error: $ZipError"); + return; + } + + $size = length($data); + $base_name = basename ($file); + + # Request to send file + send_data ("ZSEND <$base_name> SIZE $size\n"); + print_log ("Request to send file '$base_name' size ${size}b (compressed)"); + + # Wait for server response + $response = recv_command (); + + # Server rejected the file + if ($response ne "ZSEND OK") { + send_data ("QUIT\n"); + error ("Server responded $response."); + } + + print_log ("Server responded SEND OK"); + send_data ($data); + + # Wait for server response + $response = recv_command (); + if ($response ne "ZSEND OK") { + send_data ("QUIT\n"); + error ("Server responded $response."); + } + + print_log ("File sent"); +} + ################################################################################ # Common functions ################################################################################ @@ -830,13 +990,21 @@ if ($t_recv == 0) { # Send the files foreach $file (@ARGV) { - send_file ($file); + if ($t_zip == 1) { + zsend_file($file); + } else { + send_file ($file); + } } } else { - # Send the files + # Receive the files foreach $file (@ARGV) { - recv_file ($file); + if ($t_zip == 1) { + zrecv_file ($file); + } else { + recv_file ($file); + } } } @@ -882,6 +1050,8 @@ __END__ =item I<-x pwd> B. +=item I<-z> Compress data. + =back =head1 EXIT STATUS diff --git a/pandora_agents/unix/DEBIAN/control b/pandora_agents/unix/DEBIAN/control index e58836055a..2e7aeb7cca 100644 --- a/pandora_agents/unix/DEBIAN/control +++ b/pandora_agents/unix/DEBIAN/control @@ -1,5 +1,5 @@ package: pandorafms-agent-unix -Version: 7.0NG.724-180628 +Version: 7.0NG.724-180709 Architecture: all Priority: optional Section: admin diff --git a/pandora_agents/unix/DEBIAN/make_deb_package.sh b/pandora_agents/unix/DEBIAN/make_deb_package.sh index 5f515ecba6..b8477530bf 100644 --- a/pandora_agents/unix/DEBIAN/make_deb_package.sh +++ b/pandora_agents/unix/DEBIAN/make_deb_package.sh @@ -14,7 +14,7 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -pandora_version="7.0NG.724-180628" +pandora_version="7.0NG.724-180709" echo "Test if you has the tools for to make the packages." whereis dpkg-deb | cut -d":" -f2 | grep dpkg-deb > /dev/null diff --git a/pandora_agents/unix/NT4/tentacle_client.exe b/pandora_agents/unix/NT4/tentacle_client.exe index bc609233b5..9566f8cde2 100644 Binary files a/pandora_agents/unix/NT4/tentacle_client.exe and b/pandora_agents/unix/NT4/tentacle_client.exe differ diff --git a/pandora_agents/unix/pandora_agent b/pandora_agents/unix/pandora_agent index 984b9f9d69..042d378994 100755 --- a/pandora_agents/unix/pandora_agent +++ b/pandora_agents/unix/pandora_agent @@ -42,7 +42,7 @@ my $Sem = undef; my $ThreadSem = undef; use constant AGENT_VERSION => '7.0NG.724'; -use constant AGENT_BUILD => '180628'; +use constant AGENT_BUILD => '180709'; # Agent log default file size maximum and instances use constant DEFAULT_MAX_LOG_SIZE => 600000; diff --git a/pandora_agents/unix/pandora_agent.redhat.spec b/pandora_agents/unix/pandora_agent.redhat.spec index a50cf44c05..03ea86e464 100644 --- a/pandora_agents/unix/pandora_agent.redhat.spec +++ b/pandora_agents/unix/pandora_agent.redhat.spec @@ -3,7 +3,7 @@ # %define name pandorafms_agent_unix %define version 7.0NG.724 -%define release 180628 +%define release 180709 Summary: Pandora FMS Linux agent, PERL version Name: %{name} diff --git a/pandora_agents/unix/pandora_agent.spec b/pandora_agents/unix/pandora_agent.spec index f1a0d5328e..ebccbe9ec7 100644 --- a/pandora_agents/unix/pandora_agent.spec +++ b/pandora_agents/unix/pandora_agent.spec @@ -3,7 +3,7 @@ # %define name pandorafms_agent_unix %define version 7.0NG.724 -%define release 180628 +%define release 180709 Summary: Pandora FMS Linux agent, PERL version Name: %{name} diff --git a/pandora_agents/unix/pandora_agent_installer b/pandora_agents/unix/pandora_agent_installer index 4470de6b1c..ce6e75b18b 100755 --- a/pandora_agents/unix/pandora_agent_installer +++ b/pandora_agents/unix/pandora_agent_installer @@ -10,7 +10,7 @@ # ********************************************************************** PI_VERSION="7.0NG.724" -PI_BUILD="180628" +PI_BUILD="180709" OS_NAME=`uname -s` FORCE=0 diff --git a/pandora_agents/unix/tentacle_client b/pandora_agents/unix/tentacle_client index 003acbdf69..ffed3da859 100755 --- a/pandora_agents/unix/tentacle_client +++ b/pandora_agents/unix/tentacle_client @@ -58,6 +58,8 @@ use strict; use File::Basename; use Getopt::Std; use IO::Select; +use IO::Compress::Zip qw(zip $ZipError); +use IO::Uncompress::Unzip qw(unzip $UnzipError); use Socket (qw(SOCK_STREAM AF_INET AF_INET6)); my $SOCKET_MODULE = eval { require IO::Socket::INET6 } ? 'IO::Socket::INET6' @@ -131,6 +133,12 @@ my $t_ssl_pwd = ''; # Timeout for socket read/write operations in seconds my $t_timeout = 1; +# bind ipaddr +my $t_bind_address = undef; + +# Compress data before sending it through the socket. +my $t_zip = 0; + ################################################################################ ## SUB print_help ## Print help screen. @@ -141,6 +149,7 @@ sub print_help { print ("Tentacle client v$VERSION. See http://www.openideas.info/wiki for protocol description.\n\n"); print ("Options:\n"); print ("\t-a address\tServer address (default $t_address).\n"); + print ("\t-b localaddress\tLocal address to bind.\n"); print ("\t-c\t\tEnable SSL without a client certificate.\n"); print ("\t-e cert\t\tOpenSSL certificate file. Enables SSL.\n"); print ("\t-f ca\t\tVerify that the peer certificate is signed by a ca.\n"); @@ -154,7 +163,8 @@ sub print_help { print ("\t-v\t\tBe verbose.\n"); print ("\t-w\t\tPrompt for OpenSSL private key password.\n"); print ("\t-x pwd\t\tServer password.\n"); - print ("\t-y proxy\tProxy server string (user:password\@address:port).\n\n"); + print ("\t-y proxy\tProxy server string (user:password\@address:port).\n"); + print ("\t-z Compress data.\n\n"); } ################################################################################ @@ -166,7 +176,7 @@ sub parse_options { my $tmp; # Get options - if (getopts ('a:ce:f:ghk:p:qr:t:vwx:y:', \%opts) == 0 || defined ($opts{'h'})) { + if (getopts ('a:b:ce:f:ghk:p:qr:t:vwx:y:z', \%opts) == 0 || defined ($opts{'h'})) { print_help (); exit 1; } @@ -183,6 +193,18 @@ sub parse_options { } + # Bind local address + if (defined ($opts{'b'})) { + $t_bind_address = $opts{'b'}; + if (($t_bind_address !~ /^[a-zA-Z\.][a-zA-Z0-9\.\-]+$/ && ($t_bind_address !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ + || $1 < 0 || $1 > 255 || $2 < 0 || $2 > 255 + || $3 < 0 || $3 > 255 || $4 < 0 || $4 > 255)) && + ($t_address !~ /^[0-9a-f:]+$/o)) { + error ("Local address $t_bind_address is not valid."); + } + + } + # Enable SSL without a client certificate if (defined ($opts{'c'})) { require IO::Socket::SSL; @@ -299,6 +321,11 @@ sub parse_options { error ("Proxy port $t_proxy_port is not valid."); } } + + # Compress data + if (defined ($opts{'z'})) { + $t_zip = 1; + } } ################################################################################ @@ -309,20 +336,42 @@ sub start_client { # Connect to server if ($SOCKET_MODULE ne 'IO::Socket::INET') { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET6, - PeerAddr => $t_address, - PeerPort => $t_port, - Type => SOCK_STREAM - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_address, + PeerPort => $t_port, + LocalAddr => $t_bind_address, + Type => SOCK_STREAM + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_address, + PeerPort => $t_port, + Type => SOCK_STREAM + ); + } } if (! defined ($t_socket)) { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET, - PeerAddr => $t_address, - PeerPort => $t_port, - Type => SOCK_STREAM - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_address, + PeerPort => $t_port, + LocalAddr => $t_bind_address, + Type => SOCK_STREAM + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_address, + PeerPort => $t_port, + Type => SOCK_STREAM + ); + } } if (! defined ($t_socket)) { @@ -344,18 +393,38 @@ sub start_client_proxy { # Connect to proxy if ($SOCKET_MODULE ne 'IO::Socket::INET') { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET6, - PeerAddr => $t_proxy_address, - PeerPort => $t_proxy_port, - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + LocalAddr => $t_bind_address, + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET6, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + ); + } } if (! defined ($t_socket)) { - $t_socket = $SOCKET_MODULE->new ( - Domain => AF_INET, - PeerAddr => $t_proxy_address, - PeerPort => $t_proxy_port, - ); + if (defined ($t_bind_address)) { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + LocalAddr => $t_bind_address, + ); + } + else { + $t_socket = $SOCKET_MODULE->new ( + Domain => AF_INET, + PeerAddr => $t_proxy_address, + PeerPort => $t_proxy_port, + ); + } } if (! defined ($t_socket)) { @@ -408,6 +477,8 @@ sub start_ssl { if ($t_ssl_cert eq ''){ IO::Socket::SSL->start_SSL ( $t_socket, + # No authentication + SSL_verify_mode => 0x00, ); } elsif ($t_ssl_ca eq '') { @@ -525,6 +596,46 @@ sub recv_file { print_log ("Received file '$file'"); } +################################################################################ +## SUB zrecv_file +## Receive a compressed file from the server +################################################################################ +sub zrecv_file { + my $data = ''; + my $file = $_[0]; + my $response; + my $size; + my $zdata = ''; + + # Request file + send_data ("ZRECV <$file>\n"); + + # Wait for server response + $response = recv_command (); + if ($response !~ /^ZRECV SIZE (\d+)$/) { + error ("Server responded $response."); + } + + $size = $1; + send_data ("ZRECV OK\n"); + + # Receive file + $zdata = recv_data_block ($size); + if (!unzip(\$zdata => \$data)) { + print_log ("Uncompress error: $UnzipError"); + send_data ("ZRECV ERR\n"); + return; + } + + # Write it to disk + open (FILE, "> $file") || error ("Cannot open file '$file' for writing."); + binmode (FILE); + print (FILE $data); + close (FILE); + + print_log ("Received compressed file '$file'"); +} + ################################################################################ ## SUB send_file ## Send a file to the server @@ -578,6 +689,55 @@ sub send_file { print_log ("File sent"); } +################################################################################ +## SUB zsend_file +## Send a file to the server (compressed) +################################################################################ +sub zsend_file { + my $base_name; + my $data = ''; + my $response = ''; + my $retries; + my $file = $_[0]; + my $size; + my $written; + + # Read the file and compress its contents + if (! zip($file => \$data)) { + send_data ("QUIT\n"); + error ("Compression error: $ZipError"); + return; + } + + $size = length($data); + $base_name = basename ($file); + + # Request to send file + send_data ("ZSEND <$base_name> SIZE $size\n"); + print_log ("Request to send file '$base_name' size ${size}b (compressed)"); + + # Wait for server response + $response = recv_command (); + + # Server rejected the file + if ($response ne "ZSEND OK") { + send_data ("QUIT\n"); + error ("Server responded $response."); + } + + print_log ("Server responded SEND OK"); + send_data ($data); + + # Wait for server response + $response = recv_command (); + if ($response ne "ZSEND OK") { + send_data ("QUIT\n"); + error ("Server responded $response."); + } + + print_log ("File sent"); +} + ################################################################################ # Common functions ################################################################################ @@ -830,13 +990,21 @@ if ($t_recv == 0) { # Send the files foreach $file (@ARGV) { - send_file ($file); + if ($t_zip == 1) { + zsend_file($file); + } else { + send_file ($file); + } } } else { - # Send the files + # Receive the files foreach $file (@ARGV) { - recv_file ($file); + if ($t_zip == 1) { + zrecv_file ($file); + } else { + recv_file ($file); + } } } @@ -882,6 +1050,8 @@ __END__ =item I<-x pwd> B. +=item I<-z> Compress data. + =back =head1 EXIT STATUS diff --git a/pandora_agents/unix/tentacle_server b/pandora_agents/unix/tentacle_server index d6b5d4d3bd..8b27ed35b3 100755 --- a/pandora_agents/unix/tentacle_server +++ b/pandora_agents/unix/tentacle_server @@ -60,6 +60,8 @@ use strict; use warnings; use Getopt::Std; use IO::Select; +use IO::Compress::Zip qw(zip $ZipError); +use IO::Uncompress::Unzip qw(unzip $UnzipError); use threads; use Thread::Semaphore; use POSIX ":sys_wait_h"; @@ -959,6 +961,15 @@ sub serve_connection { print_info ("Request to receive file '$1' from " . $t_client_socket->sockhost ()); send_file ($1); } + elsif ($command =~ /^ZSEND <(.*)> SIZE (\d+)$/) { + print_info ("Request to send compressed file '$1' size ${2}b from " . $t_client_socket->sockhost ()); + zrecv_file ($1, $2); + } + # Client wants to receive a file + elsif ($command =~ /^ZRECV <(.*)>$/) { + print_info ("Request to receive compressed file '$1' from " . $t_client_socket->sockhost ()); + zsend_file ($1); + } # Quit elsif ($command =~ /^QUIT$/) { print_info ("Connection closed from " . $t_client_socket->sockhost ()); @@ -1070,6 +1081,61 @@ sub recv_file { print_info ("Received file '$base_name' size ${size}b from " . $t_client_socket->sockhost ()); } +################################################################################ +## SUB zrecv_file +## Receive a compressed file of size $_[1] and save it in $t_directory as $_[0]. +################################################################################ +sub zrecv_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $size = $_[1]; + my $zdata = ''; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("ZSEND ERR (invalid file name)\n"); + return; + } + + # Check file size, empty files are not allowed + if ($size < 1 || $size > $t_max_size) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " is too big"); + send_data ("ZSEND ERR (file is too big)\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (-f $file && $t_overwrite == 0) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " already exists"); + send_data ("ZSEND ERR (file already exists)\n"); + return; + } + + send_data ("ZSEND OK\n"); + + # Receive file + $zdata = recv_data_block ($size); + if (!unzip(\$zdata => \$data)) { + print_log ("Uncompress error: $UnzipError"); + send_data ("ZSEND ERR\n"); + return; + } + + # Write it to disk + open (FILE, "> $file") || error ("Cannot open file '$file' for writing."); + binmode (FILE); + print (FILE $data); + close (FILE); + + send_data ("ZSEND OK\n"); + print_info ("Received compressed file '$base_name' size ${size}b from " . $t_client_socket->sockhost ()); +} + ################################################################################ ## SUB send_file ## Send a file to the client @@ -1122,6 +1188,57 @@ sub send_file { print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " sent"); } +################################################################################ +## SUB zsend_file +## Send a file to the client +################################################################################ +sub zsend_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $response; + my $size; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("Requested compressed file '$base_name' from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("ZRECV ERR (file has an invalid file name)\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (! -f $file) { + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " does not exist"); + send_data ("ZRECV ERR (file does not exist)\n"); + return; + } + + # Read the file and compress its contents + if (! zip($file => \$data)) { + send_data ("QUIT\n"); + error ("Compression error: $ZipError"); + return; + } + + $size = length($data); + send_data ("ZRECV SIZE $size\n"); + + # Wait for client response + $response = recv_command ($t_block_size); + if ($response ne "ZRECV OK") { + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " not sent"); + return; + } + + # Send the file + send_data ($data); + + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " sent"); +} + ################################################################################ # Common functions ################################################################################ diff --git a/pandora_agents/win32/bin/util/tentacle_client.exe b/pandora_agents/win32/bin/util/tentacle_client.exe index bc609233b5..9566f8cde2 100755 Binary files a/pandora_agents/win32/bin/util/tentacle_client.exe and b/pandora_agents/win32/bin/util/tentacle_client.exe differ diff --git a/pandora_agents/win32/bin/util/tentacle_server.exe b/pandora_agents/win32/bin/util/tentacle_server.exe index a1b10c214f..95e1e5325e 100644 Binary files a/pandora_agents/win32/bin/util/tentacle_server.exe and b/pandora_agents/win32/bin/util/tentacle_server.exe differ diff --git a/pandora_agents/win32/installer/pandora.mpi b/pandora_agents/win32/installer/pandora.mpi index 3a077eee4d..3aeb65940c 100644 --- a/pandora_agents/win32/installer/pandora.mpi +++ b/pandora_agents/win32/installer/pandora.mpi @@ -186,7 +186,7 @@ UpgradeApplicationID {} Version -{180628} +{180709} ViewReadme {Yes} diff --git a/pandora_agents/win32/pandora.cc b/pandora_agents/win32/pandora.cc index cc9f79dc3a..4c8da297e2 100644 --- a/pandora_agents/win32/pandora.cc +++ b/pandora_agents/win32/pandora.cc @@ -30,7 +30,7 @@ using namespace Pandora; using namespace Pandora_Strutils; #define PATH_SIZE _MAX_PATH+1 -#define PANDORA_VERSION ("7.0NG.724(Build 180628)") +#define PANDORA_VERSION ("7.0NG.724(Build 180709)") string pandora_path; string pandora_dir; diff --git a/pandora_agents/win32/versioninfo.rc b/pandora_agents/win32/versioninfo.rc index b8946a07ab..0ca9628da2 100644 --- a/pandora_agents/win32/versioninfo.rc +++ b/pandora_agents/win32/versioninfo.rc @@ -11,7 +11,7 @@ BEGIN VALUE "LegalCopyright", "Artica ST" VALUE "OriginalFilename", "PandoraAgent.exe" VALUE "ProductName", "Pandora FMS Windows Agent" - VALUE "ProductVersion", "(7.0NG.724(Build 180628))" + VALUE "ProductVersion", "(7.0NG.724(Build 180709))" VALUE "FileVersion", "1.0.0.0" END END diff --git a/pandora_console/DEBIAN/control b/pandora_console/DEBIAN/control index 9bf28c3d24..c711f20df3 100644 --- a/pandora_console/DEBIAN/control +++ b/pandora_console/DEBIAN/control @@ -1,5 +1,5 @@ package: pandorafms-console -Version: 7.0NG.724-180628 +Version: 7.0NG.724-180709 Architecture: all Priority: optional Section: admin diff --git a/pandora_console/DEBIAN/make_deb_package.sh b/pandora_console/DEBIAN/make_deb_package.sh index 53cd8083a8..65405dbb1f 100644 --- a/pandora_console/DEBIAN/make_deb_package.sh +++ b/pandora_console/DEBIAN/make_deb_package.sh @@ -14,7 +14,7 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -pandora_version="7.0NG.724-180628" +pandora_version="7.0NG.724-180709" package_pear=0 package_pandora=1 diff --git a/pandora_console/godmode/agentes/configurar_agente.php b/pandora_console/godmode/agentes/configurar_agente.php index bc9b8ee7ec..ed52464dc0 100644 --- a/pandora_console/godmode/agentes/configurar_agente.php +++ b/pandora_console/godmode/agentes/configurar_agente.php @@ -1971,8 +1971,8 @@ switch ($tab) { resizable: true, draggable: true, modal: true, - height: 260, - width: 600, + height: 280, + width: 670, title: 'Changing snmp module name', open: function(){ $('#dialog').html('

Warning
If you change the name of this module, various features
associated with this module, such as network maps,
interface graphs or other network modules, may no longer
work. If you are not completely sure of the process, please
do not change the name of the module.

'); diff --git a/pandora_console/godmode/events/event_edit_filter.php b/pandora_console/godmode/events/event_edit_filter.php index 1909b2aec9..851e5b5e58 100644 --- a/pandora_console/godmode/events/event_edit_filter.php +++ b/pandora_console/godmode/events/event_edit_filter.php @@ -224,9 +224,9 @@ $table->data[1][1] = html_print_select_groups( '', false, '', false, false, 'id_grupo', $strict_user); $table->data[2][0] = '' . __('Group').''; -$table->data[2][1] = html_print_select_groups($config["id_user"], $access, - true, 'id_group', $id_group, '', '', -1, true, false, false, '', - false, false, false, false, 'id_grupo', $strict_user); +$display_all_group = (users_is_admin() || users_can_manage_group_all("AR")); +$table->data[2][1] = html_print_select_groups($config['id_user'], "AR", +$display_all_group, 'id_group', $idGroup, '', '', '', true); $types = get_event_types (); // Expand standard array to add not_normal (not exist in the array, used only for searches) diff --git a/pandora_console/godmode/reporting/reporting_builder.main.php b/pandora_console/godmode/reporting/reporting_builder.main.php index 05cb417933..1c9fbaee8f 100755 --- a/pandora_console/godmode/reporting/reporting_builder.main.php +++ b/pandora_console/godmode/reporting/reporting_builder.main.php @@ -67,10 +67,13 @@ $table->data['name'][1] = html_print_input_text('name', $reportName, __('Name'), 80, 100, true, false, true); $table->data['group'][0] = __('Group'); +$write_groups = users_get_groups_for_select(false, "AR", true, true, false, 'id_grupo'); -$write_groups = users_get_groups_for_select(false, "RW", - true, true, false, 'id_grupo'); + html_print_select_groups($config['id_user'], "AR", + true, 'id_group', $idGroup, '', '', '', true); + + // If the report group is not among the RW groups (special permission) we add it if (!isset($write_groups[$idGroupReport]) && $idGroupReport) { $write_groups[$idGroupReport] = groups_get_name($idGroupReport); diff --git a/pandora_console/godmode/reporting/visual_console_builder.data.php b/pandora_console/godmode/reporting/visual_console_builder.data.php index 1c5aaade37..f1ed16157d 100644 --- a/pandora_console/godmode/reporting/visual_console_builder.data.php +++ b/pandora_console/godmode/reporting/visual_console_builder.data.php @@ -127,12 +127,9 @@ else { $table->data[1][0] = __('Group:'); -// Only display group "All" if user is administrator -// or has "RW" privileges -$display_all_group = (users_is_admin() || users_can_manage_group_all("RW")); $table->data[1][1] = html_print_select_groups($config['id_user'], "RW", - $display_all_group, 'id_group', $idGroup, '', '', '', true); + true, 'id_group', $idGroup, '', '', '', true); $backgrounds_list = list_files( $config['homedir'] . '/images/console/background/', "jpg", 1, 0); $backgrounds_list = array_merge($backgrounds_list, diff --git a/pandora_console/godmode/servers/servers.build_table.php b/pandora_console/godmode/servers/servers.build_table.php index eaeb612826..1090f76490 100644 --- a/pandora_console/godmode/servers/servers.build_table.php +++ b/pandora_console/godmode/servers/servers.build_table.php @@ -101,14 +101,16 @@ foreach ($servers as $server) { switch ($server['type']) { case "snmp": case "event": + case "autoprovision": + case "migration": $data[3] = $server["version"]; - $data[4] = 'N/A'; - $data[5] = 'N/A'; + $data[4] = __('N/A'); + $data[5] = __('N/A'); break; case "export": $data[3] = $server["version"]; $data[4] = $server["modules"] . " ".__('of')." ". $server["modules_total"]; - $data[5] = 'N/A'; + $data[5] = __('N/A'); break; default: $data[3] = $server["version"]; diff --git a/pandora_console/godmode/setup/setup_visuals.php b/pandora_console/godmode/setup/setup_visuals.php index 637f1e5ad8..5d4f0f664a 100755 --- a/pandora_console/godmode/setup/setup_visuals.php +++ b/pandora_console/godmode/setup/setup_visuals.php @@ -685,6 +685,17 @@ $options_full_escale[2] = __('On Boolean graphs'); $table_chars->data[$row][1] = html_print_select($options_full_escale, 'full_scale_option', $config["full_scale_option"], '', '', 0, true, false, false); $row++; + +$table_chars->data[$row][0] = __('Soft graphs:'); +$table_chars->data[$row][0] .= ui_print_help_tip(__('This option may cause performance issues'), true); + +$options_soft_graphs = array(); +$options_soft_graphs[0] = __('Standard mode'); +$options_soft_graphs[1] = __('Classic mode'); + +$table_chars->data[$row][1] = html_print_select($options_soft_graphs, 'type_mode_graph', $config["type_mode_graph"], '', '', 0, true, false, false); +$row++; + $table_chars->data[$row][0] = __('Graph image height'); $table_chars->data[$row][1] = html_print_input_text ('graph_image_height', $config['graph_image_height'], '', 20, 20, true); $row++; diff --git a/pandora_console/images/autoprovision.png b/pandora_console/images/autoprovision.png new file mode 100644 index 0000000000..d2fb85fb42 Binary files /dev/null and b/pandora_console/images/autoprovision.png differ diff --git a/pandora_console/images/migration.png b/pandora_console/images/migration.png new file mode 100644 index 0000000000..06ed1fd83a Binary files /dev/null and b/pandora_console/images/migration.png differ diff --git a/pandora_console/include/config_process.php b/pandora_console/include/config_process.php index 573cf16dfb..a1176d418c 100644 --- a/pandora_console/include/config_process.php +++ b/pandora_console/include/config_process.php @@ -22,7 +22,7 @@ /** * Pandora build version and version */ -$build_version = 'PC180628'; +$build_version = 'PC180709'; $pandora_version = 'v7.0NG.724'; // Do not overwrite default timezone set if defined. diff --git a/pandora_console/include/constants.php b/pandora_console/include/constants.php index a4aaefbbe9..c223738b2a 100644 --- a/pandora_console/include/constants.php +++ b/pandora_console/include/constants.php @@ -364,6 +364,8 @@ define('SERVER_TYPE_MAINFRAME', 15); define('SERVER_TYPE_SYNC', 16); define('SERVER_TYPE_WUX', 17); define('SERVER_TYPE_SYSLOG', 18); +define('SERVER_TYPE_AUTOPROVISION', 19); +define('SERVER_TYPE_MIGRATION', 20); /* REPORTS */ define('REPORT_TOP_N_MAX', 1); diff --git a/pandora_console/include/functions.php b/pandora_console/include/functions.php index 726fd5244f..95892d211a 100644 --- a/pandora_console/include/functions.php +++ b/pandora_console/include/functions.php @@ -3104,20 +3104,57 @@ function series_type_graph_array($data, $show_elements_graph){ if (isset($show_elements_graph['labels']) && is_array($show_elements_graph['labels']) && (count($show_elements_graph['labels']) > 0)){ - $data_return['legend'][$key] = $show_elements_graph['labels'][$value['agent_module_id']] . ' ' ; + $name_legend = $data_return['legend'][$key] = $show_elements_graph['labels'][$value['agent_module_id']] . ' ' ; } else{ if(strpos($key, 'baseline') !== false){ - $data_return['legend'][$key] = $value['agent_alias'] . ' / ' . + $name_legend = $data_return['legend'][$key] = $value['agent_alias'] . ' / ' . $value['module_name'] . ' Baseline '; } else{ - $data_return['legend'][$key] = $value['agent_alias'] . ' / ' . + $name_legend = $data_return['legend'][$key] = $value['agent_alias'] . ' / ' . $value['module_name'] . ': '; } } - if(strpos($key, 'baseline') === false){ + if(($show_elements_graph['fullscale'] || + $show_elements_graph['type_mode_graph'] ) && + strpos($key, 'baseline') === false ){ + $data_return['legend'][$key] .= + __('Min:') . remove_right_zeros( + number_format( + $value['min'], + $config['graph_precision'] + ) + ) . ' ' . + __('Max:') . remove_right_zeros( + number_format( + $value['max'], + $config['graph_precision'] + ) + ) . ' ' . + _('Avg:') . remove_right_zeros( + number_format( + $value['avg'], + $config['graph_precision'] + ) + ) . ' ' . $str; + } + + if($show_elements_graph['compare'] == 'overlapped' && $key == 'sum2'){ + $data_return['color'][$key] = $color_series['overlapped']; + } + else{ + $data_return['color'][$key] = $color_series[$i]; + $i++; + } + } + elseif(!$show_elements_graph['fullscale'] && strpos($key, 'min') !== false || + !$show_elements_graph['fullscale'] && strpos($key, 'max') !== false){ + $data_return['series_type'][$key] = $type_graph; + + $data_return['legend'][$key] = $name_legend; + if($show_elements_graph['type_mode_graph']){ $data_return['legend'][$key] .= __('Min:') . remove_right_zeros( number_format( @@ -3177,13 +3214,7 @@ function series_type_graph_array($data, $show_elements_graph){ __('Percentil') . ' ' . $config['percentil'] . 'ยบ ' . __('of module') . ' '; - if (isset($show_elements_graph['labels']) && is_array($show_elements_graph['labels'])){ - $data_return['legend'][$key] .= $show_elements_graph['labels'][$value['agent_module_id']] . ' ' ; - } - else{ - $data_return['legend'][$key] .= $value['agent_alias'] . ' / ' . - $value['module_name'] . ': ' . ' Value: '; - } + $data_return['legend'][$key] .= $name_legend; $data_return['legend'][$key] .= remove_right_zeros( number_format( $value['data'][0][1], diff --git a/pandora_console/include/functions_agents.php b/pandora_console/include/functions_agents.php index e084c16381..65e27936cd 100644 --- a/pandora_console/include/functions_agents.php +++ b/pandora_console/include/functions_agents.php @@ -25,16 +25,19 @@ require_once($config['homedir'] . '/include/functions_users.php'); /** * Check the agent exists in the DB. - * + * * @param int $id_agent The agent id. * @param boolean $show_disabled Show the agent found althought it is disabled. By default false. - * + * * @return boolean The result to check if the agent is in the DB. */ function agents_check_agent_exists($id_agent, $show_disabled = true) { - $agent = db_get_value_filter('id_agente', 'tagente', - array('id_agente' => $id_agent, 'disabled' => !$show_disabled)); - + $agent = db_get_value_filter( + 'id_agente', + 'tagente', + array('id_agente' => $id_agent, 'disabled' => !$show_disabled) + ); + if (!empty($agent)) { return true; } @@ -2688,7 +2691,7 @@ function agents_get_all_groups_agent ($id_agent, $group = false) { */ function agents_count_agents_filter ($filter = array(), $access = "AR") { $total_agents = agents_get_agents( - array ('id_group' => $id_group), + $filter, array ('COUNT(DISTINCT id_agente) as total'), $access ); diff --git a/pandora_console/include/functions_api.php b/pandora_console/include/functions_api.php index 1d98bcd51b..8979b04b14 100644 --- a/pandora_console/include/functions_api.php +++ b/pandora_console/include/functions_api.php @@ -180,7 +180,13 @@ function returnData($returnType, $data, $separator = ';') { if ($separator == ";") { $separator = null; } - echo json_encode ($data, $separator); + + if(empty($separator)){ + echo json_encode ($data); + } else { + echo json_encode ($data, $separator); + } + break; } } @@ -9335,7 +9341,7 @@ function api_get_total_agents($id_group, $trash1, $trash2, $returnType) { return; } - $total_agents = agents_count_agents_filter(array ('id_group' => $id_group)); + $total_agents = agents_count_agents_filter(array ('id_grupo' => $id_group)); $data = array('type' => 'string', 'data' => $total_agents); returnData($returnType, $data); diff --git a/pandora_console/include/functions_config.php b/pandora_console/include/functions_config.php index 76f646bce2..7e4fd076c4 100644 --- a/pandora_console/include/functions_config.php +++ b/pandora_console/include/functions_config.php @@ -627,7 +627,10 @@ function config_update_config () { if (!config_update_value ('full_scale_option', (int) get_parameter('full_scale_option', 0))) $error_update[] = __('Default full scale (TIP)'); - + + if (!config_update_value ('type_mode_graph', (int) get_parameter('type_mode_graph', 0))) + $error_update[] = __('Default soft graphs'); + if (!config_update_value ('graph_image_height', (int) get_parameter('graph_image_height', 0))) $error_update[] = __('Default height of the chart image'); diff --git a/pandora_console/include/functions_db.php b/pandora_console/include/functions_db.php index 95541218ae..aaf61db8cd 100644 --- a/pandora_console/include/functions_db.php +++ b/pandora_console/include/functions_db.php @@ -601,7 +601,7 @@ function db_get_module_ranges_unknown($id_agente_modulo, $tstart = false, $tend * utimestamp * */ -function db_uncompress_module_data($id_agente_modulo, $tstart = false, $tend = false) { +function db_uncompress_module_data($id_agente_modulo, $tstart = false, $tend = false, $slice_size = false) { global $config; if (!isset($id_agente_modulo)) { @@ -709,7 +709,9 @@ function db_uncompress_module_data($id_agente_modulo, $tstart = false, $tend = f } // Retrieve module_interval to build the template - $slice_size = $module_interval; + if($slice_size === false){ + $slice_size = $module_interval; + } $return = array(); diff --git a/pandora_console/include/functions_graph.php b/pandora_console/include/functions_graph.php index c02be7dcc0..66dc64e639 100644 --- a/pandora_console/include/functions_graph.php +++ b/pandora_console/include/functions_graph.php @@ -240,7 +240,7 @@ function grafico_modulo_sparse_data_chart ( $data_slice = $date_array['period'] / (250 * $params['zoom']); if( $data_module_graph['id_module_type'] == 23 || - $data_module_graph['id_module_type'] == 3 || + $data_module_graph['id_module_type'] == 3 || $data_module_graph['id_module_type'] == 17 || $data_module_graph['id_module_type'] == 10 || $data_module_graph['id_module_type'] == 33 ){ @@ -258,12 +258,11 @@ function grafico_modulo_sparse_data_chart ( } else{ //all points(data) and boolean - if( $params['zoom'] == 5 || - $data_module_graph['id_module_type'] == 2 || - $data_module_graph['id_module_type'] == 6 || + if( $data_module_graph['id_module_type'] == 2 || + $data_module_graph['id_module_type'] == 6 || $data_module_graph['id_module_type'] == 21 || $data_module_graph['id_module_type'] == 18 || - $data_module_graph['id_module_type'] == 9 || + $data_module_graph['id_module_type'] == 9 || $data_module_graph['id_module_type'] == 31 || $data_module_graph['id_module_type'] == 100 ){ @@ -278,19 +277,6 @@ function grafico_modulo_sparse_data_chart ( $data_module_graph['history_db'] ); } - else{ - $data = db_get_all_rows_filter ( - 'tagente_datos', - array ('id_agente_modulo' => (int)$agent_module_id, - "utimestamp > '". $date_array['start_date']. "'", - "utimestamp < '". $date_array['final_date'] . "'", - 'group' => "ROUND(utimestamp / $data_slice)", - 'order' => 'utimestamp ASC'), - array ('sum(datos)/count(datos) as datos', 'min(utimestamp) as utimestamp'), - 'AND', - $data_module_graph['history_db'] - ); - } } if($data === false){ @@ -415,29 +401,58 @@ function grafico_modulo_sparse_data( $params['show_unknown'], $params['percentil'], $series_suffix, - $params['flag_overlapped'] + $params['flag_overlapped'], + false, + $params['type_mode_graph'] ); - - $array_data["sum" . $series_suffix]['agent_module_id']= $agent_module_id; - $array_data["sum" . $series_suffix]['id_module_type'] = $data_module_graph['id_module_type']; - $array_data["sum" . $series_suffix]['agent_name'] = $data_module_graph['agent_name']; - $array_data["sum" . $series_suffix]['module_name'] = $data_module_graph['module_name']; - $array_data["sum" . $series_suffix]['agent_alias'] = $data_module_graph['agent_alias']; } else{ - $array_data = grafico_modulo_sparse_data_chart ( - $agent_module_id, - $date_array, - $data_module_graph, - $params, - $series_suffix - ); + //uncompress data except boolean and string. + if( $data_module_graph['id_module_type'] == 23 || + $data_module_graph['id_module_type'] == 3 || + $data_module_graph['id_module_type'] == 17 || + $data_module_graph['id_module_type'] == 10 || + $data_module_graph['id_module_type'] == 33 || + $data_module_graph['id_module_type'] == 2 || + $data_module_graph['id_module_type'] == 6 || + $data_module_graph['id_module_type'] == 21 || + $data_module_graph['id_module_type'] == 18 || + $data_module_graph['id_module_type'] == 9 || + $data_module_graph['id_module_type'] == 31 || + $data_module_graph['id_module_type'] == 100 ){ + html_debug_prinbt('entra'); + $array_data = grafico_modulo_sparse_data_chart ( + $agent_module_id, + $date_array, + $data_module_graph, + $params, + $series_suffix + ); + } + else{ + $array_data = fullscale_data( + $agent_module_id, + $date_array, + $params['show_unknown'], + $params['percentil'], + $series_suffix, + $params['flag_overlapped'], + $data_slice = $date_array['period'] / (250 * $params['zoom']) + 100, + $params['type_mode_graph'] + ); + } } if($array_data === false){ return false; } + $array_data["sum" . $series_suffix]['agent_module_id']= $agent_module_id; + $array_data["sum" . $series_suffix]['id_module_type'] = $data_module_graph['id_module_type']; + $array_data["sum" . $series_suffix]['agent_name'] = $data_module_graph['agent_name']; + $array_data["sum" . $series_suffix]['module_name'] = $data_module_graph['module_name']; + $array_data["sum" . $series_suffix]['agent_alias'] = $data_module_graph['agent_alias']; + //This is for a specific type of report that consists in passing an interval and doing the average sum and avg. if($params['force_interval'] != ''){ $period_time_interval = $date_array['period'] * 1000; @@ -878,6 +893,11 @@ function grafico_modulo_sparse ($params) { $params['zoom'] = 1; } + if(!isset($params['type_mode_graph'])){ + //$config['type_mode_graph'] + $params['type_mode_graph'] = $config['type_mode_graph']; + } + //XXXX Configurable $params['grid_color'] = '#C1C1C1'; $params['legend_color'] = '#636363'; @@ -1328,6 +1348,11 @@ function graphic_combined_module ( $params['show_unknown'] = false; } + if(!isset($params['type_mode_graph'])){ + //$config['type_mode_graph'] + $params['type_mode_graph'] = 0; + } + $params['graph_combined'] = true; $params_combined['graph_combined'] = true; @@ -1367,7 +1392,8 @@ function graphic_combined_module ( $sources = db_get_all_rows_field_filter( 'tgraph_source', 'id_graph', - $params_combined['id_graph'] + $params_combined['id_graph'], + 'field_order' ); $series = db_get_all_rows_sql( @@ -3947,86 +3973,267 @@ function fullscale_data ( $agent_module_id, $date_array, $show_unknown = 0, $show_percentil = 0, $series_suffix, - $compare = false){ + $compare = false, + $data_slice = false, + $type_mode_graph){ global $config; $data_uncompress = db_uncompress_module_data( $agent_module_id, $date_array['start_date'], - $date_array['final_date'] + $date_array['final_date'], + $data_slice ); $data = array(); $previous_data = 0; - $min_value = PHP_INT_MAX-1; - $max_value = PHP_INT_MIN+1; + //normal + $min_value_total = PHP_INT_MAX; + $max_value_total = -PHP_INT_MAX; + //max + $max_value_min = PHP_INT_MAX; + $max_value_max = -PHP_INT_MAX; + //min + $min_value_min = PHP_INT_MAX; + $min_value_max = -PHP_INT_MAX; + //avg + $avg_value_min = PHP_INT_MAX; + $avg_value_max = -PHP_INT_MAX; + $flag_unknown = 0; $array_percentil = array(); - foreach ($data_uncompress as $k) { - foreach ($k["data"] as $v) { - if (isset($v["type"]) && $v["type"] == 1) { # skip unnecesary virtual data - continue; - } - if($compare){ // * 1000 need js utimestam mlsecond - $real_date = ($v['utimestamp'] + $date_array['period']) * 1000; - } - else{ - $real_date = $v['utimestamp'] * 1000; - } - if ($v["datos"] === NULL) { - // Unknown - if($show_unknown){ - if(!$compare){ - if($flag_unknown){ - $data["unknown" . $series_suffix]['data'][] = array($real_date , 1); + if($data_slice){ + foreach ($data_uncompress as $k) { + $sum_data = 0; + $count_data = 0; + $min_value = PHP_INT_MAX; + $max_value = -PHP_INT_MAX; + $flag_virtual_data = 0; + foreach ($k["data"] as $v) { + if (isset($v["type"]) && $v["type"] == 1) { # skip unnecesary virtual data + continue; + $flag_virtual_data = 1; + } + if($compare){ // * 1000 need js utimestam mlsecond + $real_date = ($v['utimestamp'] + $date_array['period']) * 1000; + } + else{ + $real_date = $v['utimestamp'] * 1000; + } + + if ($v["datos"] === NULL) { + // Unknown + if($show_unknown){ + if(!$compare){ + if($flag_unknown){ + $data["unknown" . $series_suffix]['data'][] = array($real_date , 1); + } + else{ + $data["unknown" . $series_suffix]['data'][] = array( ($real_date - 1) , 0); + $data["unknown" . $series_suffix]['data'][] = array($real_date , 1); + $flag_unknown = 1; + } } - else{ - $data["unknown" . $series_suffix]['data'][] = array( ($real_date - 1) , 0); - $data["unknown" . $series_suffix]['data'][] = array($real_date , 1); - $flag_unknown = 1; + } + $v["datos"] = $previous_data; + } + else { + //normal + $previous_data = $v["datos"]; + if($show_unknown){ + if(!$compare){ + if($flag_unknown){ + $data["unknown" . $series_suffix]['data'][] = array($real_date , 0); + $flag_unknown = 0; + } } } } - $data["sum" . $series_suffix]['data'][] = array($real_date , $previous_data); - } - else { - //normal - $previous_data = $v["datos"]; - $data["sum" . $series_suffix]['data'][] = array($real_date , $v["datos"]); - if($show_unknown){ - if(!$compare){ - if($flag_unknown){ - $data["unknown" . $series_suffix]['data'][] = array($real_date , 0); - $flag_unknown = 0; - } + if(isset($v["datos"]) && $v["datos"]){ + //max + if($v['datos'] >= $max_value){ + $max_value = $v['datos']; } + //min + if($v['datos'] <= $min_value){ + $min_value = $v['datos']; + } + //avg sum + $sum_data += $v["datos"]; } + //avg count + $count_data++; + + if($show_percentil && !$compare){ + $array_percentil[] = $v["datos"]; + } + + $last_data = $v["datos"]; } - if(isset($v["datos"]) && $v["datos"]){ - //max - if($v['datos'] >= $max_value){ - $max_value = $v['datos']; + if(!$flag_virtual_data){ + if($compare){ // * 1000 need js utimestam mlsecond + $real_date = ($k['data'][0]['utimestamp'] + $date_array['period']) * 1000; } - //min - if($v['datos'] <= $min_value){ - $min_value = $v['datos']; + else{ + $real_date = $k['data'][0]['utimestamp'] * 1000; } - //avg sum - $sum_data += $v["datos"]; - } - //avg count - $count_data++; - if($show_percentil && !$compare){ - $array_percentil[] = $v["datos"]; - } + $data["sum" . $series_suffix]['data'][] = array($real_date , $sum_data/$count_data); + if($type_mode_graph && !$params['baseline']){ + $data["min" . $series_suffix]['data'][] = array($real_date , $min_value); + $data["max" . $series_suffix]['data'][] = array($real_date , $max_value); + } + else{ + $data["sum" . $series_suffix]['slice_data'][$real_date]['min'] = $min_value; + $data["sum" . $series_suffix]['slice_data'][$real_date]['avg'] = $sum_data/$count_data; + $data["sum" . $series_suffix]['slice_data'][$real_date]['max'] = $max_value; + } - $last_data = $v["datos"]; + //max_total + if($max_value >= $max_value_total){ + $max_value_total = $max_value; + } + //min_total + if($min_value <= $min_value_total){ + $min_value_total = $min_value; + } + //avg sum_total + $sum_data_total += $sum_data; + + //avg count_total + $count_data_total++; + + if($type_mode_graph && !$params['baseline']){ + /*MIN*/ + //max_min + if($min_value >= $min_value_max){ + $min_value_max = $min_value; + } + //min_min + if($min_value <= $min_value_min){ + $min_value_min = $min_value; + } + //avg sum_min + $sum_data_min += $min_value; + + /*MAX*/ + //max_max + if($max_value >= $max_value_max){ + $max_value_max = $max_value; + } + //min_max + if($max_value <= $max_value_min){ + $max_value_min = $max_value; + } + //avg sum_max + $sum_data_max += $max_value; + + /*AVG*/ + //max_max + if(($sum_data/$count_data) >= $avg_value_max){ + $avg_value_max = $sum_data/$count_data; + } + //min_max + if(($sum_data/$count_data) <= $avg_value_min){ + $avg_value_min = $sum_data/$count_data; + } + //avg sum_max + $sum_data_avg += $sum_data/$count_data; + } + } } + $data["sum" . $series_suffix]['min'] = $min_value_total; + $data["sum" . $series_suffix]['max'] = $max_value_total; + $data["sum" . $series_suffix]['avg'] = $sum_data_total/$count_data_total; + + if($type_mode_graph && !$params['baseline']){ + $data["min" . $series_suffix]['min'] = $min_value_min; + $data["min" . $series_suffix]['max'] = $min_value_max; + $data["min" . $series_suffix]['avg'] = $sum_data_min/$count_data_total; + + $data["max" . $series_suffix]['min'] = $max_value_min; + $data["max" . $series_suffix]['max'] = $max_value_max; + $data["max" . $series_suffix]['avg'] = $sum_data_max/$count_data_total; + + $data["sum" . $series_suffix]['min'] = $avg_value_min; + $data["sum" . $series_suffix]['max'] = $avg_value_max; + $data["sum" . $series_suffix]['avg'] = $sum_data_avg/$count_data_total; + } + } + else{ + foreach ($data_uncompress as $k) { + foreach ($k["data"] as $v) { + if (isset($v["type"]) && $v["type"] == 1) { # skip unnecesary virtual data + continue; + } + if($compare){ // * 1000 need js utimestam mlsecond + $real_date = ($v['utimestamp'] + $date_array['period']) * 1000; + } + else{ + $real_date = $v['utimestamp'] * 1000; + } + + if ($v["datos"] === NULL) { + // Unknown + if($show_unknown){ + if(!$compare){ + if($flag_unknown){ + $data["unknown" . $series_suffix]['data'][] = array($real_date , 1); + } + else{ + $data["unknown" . $series_suffix]['data'][] = array( ($real_date - 1) , 0); + $data["unknown" . $series_suffix]['data'][] = array($real_date , 1); + $flag_unknown = 1; + } + } + } + + $data["sum" . $series_suffix]['data'][] = array($real_date , $previous_data); + } + else { + //normal + $previous_data = $v["datos"]; + $data["sum" . $series_suffix]['data'][] = array($real_date , $v["datos"]); + if($show_unknown){ + if(!$compare){ + if($flag_unknown){ + $data["unknown" . $series_suffix]['data'][] = array($real_date , 0); + $flag_unknown = 0; + } + } + } + } + + if(isset($v["datos"]) && $v["datos"]){ + //max + if($v['datos'] >= $max_value){ + $max_value = $v['datos']; + } + //min + if($v['datos'] <= $min_value){ + $min_value = $v['datos']; + } + //avg sum + $sum_data += $v["datos"]; + } + //avg count + $count_data++; + + if($show_percentil && !$compare){ + $array_percentil[] = $v["datos"]; + } + + $last_data = $v["datos"]; + } + } + + $data["sum" . $series_suffix]['min'] = $min_value; + $data["sum" . $series_suffix]['max'] = $max_value; + $data["sum" . $series_suffix]['avg'] = $sum_data/$count_data; } if($show_percentil && !$compare){ @@ -4064,12 +4271,19 @@ function fullscale_data ( $date_array['final_date'] * 1000, $last_data ); + if($data_slice){ + if($type_mode_graph && !$params['baseline']){ + $data["min" . $series_suffix]['data'][] = array($date_array['final_date'] * 1000 , $min_value); + $data["max" . $series_suffix]['data'][] = array($date_array['final_date'] * 1000 , $max_value); + } + else{ + $data["sum" . $series_suffix]['slice_data'][$date_array['final_date'] * 1000]['min'] = $min_value; + $data["sum" . $series_suffix]['slice_data'][$date_array['final_date'] * 1000]['avg'] = $sum_data/$count_data; + $data["sum" . $series_suffix]['slice_data'][$date_array['final_date'] * 1000]['max'] = $max_value; + } + } } - $data["sum" . $series_suffix]['min'] = $min_value; - $data["sum" . $series_suffix]['max'] = $max_value; - $data["sum" . $series_suffix]['avg'] = $sum_data/$count_data; - return $data; } diff --git a/pandora_console/include/functions_modules.php b/pandora_console/include/functions_modules.php index a0979e4887..59e88d9cdf 100755 --- a/pandora_console/include/functions_modules.php +++ b/pandora_console/include/functions_modules.php @@ -290,7 +290,7 @@ function modules_change_disabled($id_agent_module, $new_value = 1) { return ERR_GENERIC; } } - + /** * Deletes a module from an agent. * @@ -299,20 +299,20 @@ function modules_change_disabled($id_agent_module, $new_value = 1) { * @return True if the module was deleted. False if not. */ function modules_delete_agent_module ($id_agent_module) { - if (empty($id_agent_module)) + if (empty($id_agent_module)) return false; - + if (is_array($id_agent_module)) { $id_agents = db_get_all_rows_sql( sprintf('SELECT id_agente FROM tagente_modulo WHERE id_agente_modulo IN (%s) GROUP BY id_agente', implode(',', $id_agent_module))); - + foreach($id_agents as $k => $v) { $id_agents[$k] = $v['id_agente']; } - + // Update update flags to server side db_process_sql (sprintf('UPDATE tagente SET update_module_count=1, update_alert_count=1 @@ -321,18 +321,18 @@ function modules_delete_agent_module ($id_agent_module) { else { // Read module data $id_agent = modules_get_agentmodule_agent($id_agent_module); - + // Update update flags to server side db_process_sql (sprintf('UPDATE tagente SET update_module_count=1, update_alert_count=1 WHERE id_agente = %s', $id_agent)); } - + $where = array ('id_agent_module' => $id_agent_module); - - $enterprise_include = enterprise_include_once( - 'include/functions_config_agents.php'); - + + enterprise_include_once("include/functions_agents.php"); + $enterprise_include = enterprise_include_once('include/functions_config_agents.php'); + if ($enterprise_include !== ENTERPRISE_NOT_HOOK) { if (is_array($id_agent_module)) { foreach ($id_agent_module as $id_agent_module_item) { @@ -347,9 +347,9 @@ function modules_delete_agent_module ($id_agent_module) { modules_get_agentmodule_name($id_agent_module)); } } - + alerts_delete_alert_agent_module (0, $where); - + db_process_sql_delete ('tgraph_source', $where); db_process_sql_delete ('treport_content', $where); db_process_sql_delete ('tevento', @@ -361,7 +361,7 @@ function modules_delete_agent_module ($id_agent_module) { array ('nombre' => 'delete_pending', 'delete_pending' => 1, 'disabled' => 1), $where); db_process_sql_delete('ttag_module', $where); - + return true; } @@ -1685,17 +1685,22 @@ function modules_get_previous_data ($id_agent_module, $utimestamp = 0, $string = $table = 'tagente_datos'; } - $sql = sprintf ('SELECT * - FROM ' . $table . ' - WHERE id_agente_modulo = %d - AND utimestamp <= %d - ORDER BY utimestamp DESC', - $id_agent_module, $utimestamp, - $utimestamp - SECONDS_2DAY + $sql = sprintf ( + "SELECT * FROM %s + WHERE id_agente_modulo = %d + AND utimestamp = ( SELECT max(utimestamp) + FROM tagente_datos + WHERE id_agente_modulo = %d + AND utimestamp <= %d )", + $table, + $id_agent_module, + $id_agent_module, + $utimestamp ); $search_in_history_db = db_search_in_history_db($utimestamp); - return db_get_row_sql ($sql, $search_in_history_db); + + return db_get_row_sql($sql, $search_in_history_db); } /** diff --git a/pandora_console/include/functions_servers.php b/pandora_console/include/functions_servers.php index 101613adf7..6bd255e88d 100644 --- a/pandora_console/include/functions_servers.php +++ b/pandora_console/include/functions_servers.php @@ -455,6 +455,16 @@ function servers_get_info ($id_server = -1) { $server["type"] = "syslog"; $id_modulo = 0; break; + case SERVER_TYPE_AUTOPROVISION: + $server["img"] = html_print_image ("images/autoprovision.png", true, array ("title" => __('Autoprovision server'))); + $server["type"] = "autoprovision"; + $id_modulo = 0; + break; + case SERVER_TYPE_MIGRATION: + $server["img"] = html_print_image ("images/migration.png", true, array ("title" => __('Migration server'))); + $server["type"] = "migration"; + $id_modulo = 0; + break; default: $server["img"] = ''; $server["type"] = "unknown"; diff --git a/pandora_console/include/functions_visual_map.php b/pandora_console/include/functions_visual_map.php index b4adb5d1f4..9773bd831d 100755 --- a/pandora_console/include/functions_visual_map.php +++ b/pandora_console/include/functions_visual_map.php @@ -2711,12 +2711,19 @@ function visual_map_process_wizard_add_modules ($id_modules, $image, function get_donut_module_data ($id_module) { $mod_values = db_get_value_filter('datos', 'tagente_estado', array('id_agente_modulo' => $id_module)); + $no_data_to_show = false; + if (preg_match("/\r\n/", $mod_values)) { $values = explode("\r\n", $mod_values); } elseif (preg_match("/\n/", $mod_values)) { $values = explode("\n", $mod_values); } + else { + $values=array(__('No data to show').",1"); + $no_data_to_show=true; + } + $colors = array(); $colors[] = "#aa3333"; @@ -2737,7 +2744,12 @@ function get_donut_module_data ($id_module) { if ($data[1] == 0) { $data[1] = __('No data'); } - $values_to_return[$index]['tag_name'] = $data[0] . ": " . $data[1]; + + if ($no_data_to_show) + $values_to_return[$index]['tag_name'] = $data[0]; + else + $values_to_return[$index]['tag_name'] = $data[0] . ": " . $data[1]; + $values_to_return[$index]['color'] = $colors[$index]; $values_to_return[$index]['value'] = (int)$data[1]; $total += (int)$data[1]; diff --git a/pandora_console/include/graphs/flot/pandora.flot.js b/pandora_console/include/graphs/flot/pandora.flot.js index 06e6174e64..ff5882b9df 100644 --- a/pandora_console/include/graphs/flot/pandora.flot.js +++ b/pandora_console/include/graphs/flot/pandora.flot.js @@ -881,6 +881,7 @@ function pandoraFlotArea( graph_id, values, legend, var grid_color = params.grid_color; var background_color = params.backgroundColor; var legend_color = params.legend_color; + var update_legend = {}; //XXXXXX colocar var force_integer = 0; @@ -1553,6 +1554,12 @@ function pandoraFlotArea( graph_id, values, legend, break; } + if(series_type[index] != 'boolean'){ + if(value.slice_data){ + update_legend[index] = value.slice_data; + } + } + //in graph stacked unset percentil if( ! ( (type == 1) && ( /percentil/.test(index) ) == true ) && ! ( (type == 3) && ( /percentil/.test(index) ) == true ) ){ @@ -1582,9 +1589,6 @@ function pandoraFlotArea( graph_id, values, legend, // The first execution, the graph data is the base data datas = data_base; - // minTickSize - var count_data = datas[0].data.length; - var number_ticks = 8; if(vconsole){ number_ticks = 5; @@ -1962,39 +1966,75 @@ function pandoraFlotArea( graph_id, values, legend, if(series.data[j]){ var y = series.data[j][1]; + var x = series.data[j][0] -1 ; } } - var how_bigger = ""; - if (y > 1000000) { - how_bigger = "M"; - y = y / 1000000; - } - else if (y > 1000) { - how_bigger = "K"; - y = y / 1000; - } - else if(y < -1000000) { - how_bigger = "M"; - y = y / 1000000; - } - else if (y < -1000) { - how_bigger = "K"; - y = y / 1000; - } + y_array = format_unit_yaxes(y); - var label_aux = legend[series.label]; + y = y_array['y']; + how_bigger = y_array['unit']; + + var data_legend = []; // The graphs of points type and unknown graphs will dont be updated if (series_type[dataset[k]["label"]] != 'points' && series_type[dataset[k]["label"]] != 'unknown' && series_type[dataset[k]["label"]] != 'percentil' ) { - $('#legend_' + graph_id + ' .legendLabel') - .eq(i).html(label_aux + ' value = ' + - (short_data ? number_format(y, 0, "", short_data) : parseFloat(y)) + - how_bigger + ' ' + unit - ); + if(Object.keys(update_legend).length == 0){ + var label_aux = legend[series.label]; + + $('#legend_' + graph_id + ' .legendLabel') + .eq(i).html(label_aux + ' value = ' + + (short_data ? number_format(y, 0, "", short_data) : parseFloat(y)) + + how_bigger + ' ' + unit + ); + } + else{ + $.each(update_legend, function (index, value) { + if(!value[x]){ + x = x +1; + } + if(value[x].min){ + min_y_array = format_unit_yaxes(value[x].min); + min_y = min_y_array['y']; + min_bigger = min_y_array['unit']; + } + else{ + min_y = 0; + min_bigger = ""; + } + + if(value[x].max){ + max_y_array = format_unit_yaxes(value[x].max); + max_y = max_y_array['y']; + max_bigger = max_y_array['unit']; + } + else{ + max_y = 0; + max_bigger = ""; + } + + if(value[x].avg){ + avg_y_array = format_unit_yaxes(value[x].avg); + avg_y = avg_y_array['y']; + avg_bigger = avg_y_array['unit']; + } + else{ + avg_y = 0; + avg_bigger = ""; + } + + data_legend[index] = + ' Min: ' + (short_data ? number_format(min_y, 0, "", short_data) : parseFloat(min_y)) + min_bigger + + ' Max: ' + (short_data ? number_format(max_y, 0, "", short_data) : parseFloat(max_y)) + max_bigger + + ' Avg: ' + (short_data ? number_format(avg_y, 0, "", short_data) : parseFloat(avg_y)) + avg_bigger; + }); + + var label_aux = legend[series.label] + data_legend[series.label]; + $('#legend_' + graph_id + ' .legendLabel').eq(i).html(label_aux); + } } $('#legend_' + graph_id + ' .legendLabel').eq(i).css('color', legend_color); @@ -2317,6 +2357,33 @@ function pandoraFlotArea( graph_id, values, legend, } } +function format_unit_yaxes(y){ + var how_bigger = []; + + if (y > 1000000) { + how_bigger['unit'] = "M"; + how_bigger['y'] = y / 1000000; + } + else if (y > 1000) { + how_bigger['unit'] = "K"; + how_bigger['y'] = y / 1000; + } + else if(y < -1000000) { + how_bigger['unit'] = "M"; + how_bigger['y'] = y / 1000000; + } + else if (y < -1000) { + how_bigger['unit'] = "K"; + how_bigger['y'] = y / 1000; + } + else{ + how_bigger['unit'] = ""; + how_bigger['y'] = y; + } + + return how_bigger; +} + function adjust_menu(graph_id, plot, parent_height, width, show_legend) { if ($('#'+graph_id+' .xAxis .tickLabel').eq(0).css('width') != undefined) { left_ticks_width = $('#'+graph_id+' .xAxis .tickLabel').eq(0).css('width').split('px')[0]; diff --git a/pandora_console/install.php b/pandora_console/install.php index 18283fcef4..4eb38e4995 100755 --- a/pandora_console/install.php +++ b/pandora_console/install.php @@ -71,7 +71,7 @@
data[0][1] = html_print_select_groups($config['id_user'], - "RR", users_can_manage_group_all(), "group", $group, '', '', 0, true, false, true, + "RR", true, "group", $group, '', '', 0, true, false, true, '', false); //Agent selector diff --git a/pandora_console/operation/agentes/pandora_networkmap.editor.php b/pandora_console/operation/agentes/pandora_networkmap.editor.php index 1db0e2985f..a5c5acc1ec 100644 --- a/pandora_console/operation/agentes/pandora_networkmap.editor.php +++ b/pandora_console/operation/agentes/pandora_networkmap.editor.php @@ -223,13 +223,9 @@ else { 100,true); $table->data[1][0] = __('Group'); - // Only display group "All" if user is administrator - // or has "AR" privileges - - $display_all_group = (users_is_admin() || users_can_manage_group_all("AR")); $table->data[1][1] = html_print_select_groups($config['id_user'], "AR", - $display_all_group, 'id_group', $idGroup, '', '', '', true); + true, 'id_group', $idGroup, '', '', '', true); $table->data[2][0] = __('Node radius'); $table->data[2][1] = html_print_input_text ('node_radius', $node_radius, '', 2, diff --git a/pandora_console/operation/agentes/stat_win.php b/pandora_console/operation/agentes/stat_win.php index faa86242d6..36584dde2a 100644 --- a/pandora_console/operation/agentes/stat_win.php +++ b/pandora_console/operation/agentes/stat_win.php @@ -114,19 +114,9 @@ $alias = db_get_value ("alias","tagente","id_agente",$id_agent); // ACL $permission = false; $agent_group = (int) agents_get_agent_group($agent_id); - $strict_user = (bool) db_get_value("strict_acl", "tusuario", - "id_user", $config['id_user']); - - if (!empty($agent_group)) { - if ($strict_user) { - $permission = tags_check_acl_by_module($id, $config['id_user'], 'RR') === true; - } - else { - $permission = check_acl($config['id_user'], $agent_group, "RR"); - } - } - - if (!$permission) { + $permission = check_acl($config['id_user'], $agent_group, "AR"); + + if (!$permission) { require ($config['homedir'] . "/general/noaccess.php"); exit; } @@ -305,7 +295,7 @@ $alias = db_get_value ("alias","tagente","id_agente",$id_agent); $options[2] = 'x2'; $options[3] = 'x3'; $options[4] = 'x4'; - $options[5] = __('full'); + $options[5] = 'x5'; $data[1] = html_print_select ($options, "zoom", $zoom, '', '', 0, true, false, false); $table->data[] = $data; $table->rowclass[] = ''; diff --git a/pandora_console/pandora_console.redhat.spec b/pandora_console/pandora_console.redhat.spec index 990e17e2a2..b0510868d3 100644 --- a/pandora_console/pandora_console.redhat.spec +++ b/pandora_console/pandora_console.redhat.spec @@ -3,7 +3,7 @@ # %define name pandorafms_console %define version 7.0NG.724 -%define release 180628 +%define release 180709 # User and Group under which Apache is running %define httpd_name httpd diff --git a/pandora_console/pandora_console.spec b/pandora_console/pandora_console.spec index 5762c954ab..4fa22481f1 100644 --- a/pandora_console/pandora_console.spec +++ b/pandora_console/pandora_console.spec @@ -3,7 +3,7 @@ # %define name pandorafms_console %define version 7.0NG.724 -%define release 180628 +%define release 180709 %define httpd_name httpd # User and Group under which Apache is running %define httpd_name apache2 diff --git a/pandora_server/DEBIAN/control b/pandora_server/DEBIAN/control index bb1d8a18af..753760bfdd 100644 --- a/pandora_server/DEBIAN/control +++ b/pandora_server/DEBIAN/control @@ -1,5 +1,5 @@ package: pandorafms-server -Version: 7.0NG.724-180628 +Version: 7.0NG.724-180709 Architecture: all Priority: optional Section: admin diff --git a/pandora_server/DEBIAN/make_deb_package.sh b/pandora_server/DEBIAN/make_deb_package.sh index fd2ab72f9d..3285e841a3 100644 --- a/pandora_server/DEBIAN/make_deb_package.sh +++ b/pandora_server/DEBIAN/make_deb_package.sh @@ -14,7 +14,7 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -pandora_version="7.0NG.724-180628" +pandora_version="7.0NG.724-180709" package_cpan=0 package_pandora=1 diff --git a/pandora_server/FreeBSD/tentacle_server b/pandora_server/FreeBSD/tentacle_server index a8752e2cf5..8b27ed35b3 100755 --- a/pandora_server/FreeBSD/tentacle_server +++ b/pandora_server/FreeBSD/tentacle_server @@ -1,49 +1,1869 @@ -#!/bin/sh - -# ********************************************************************** -# Tentacle Server Daemon launcher for FreeBSD -# (c) 2010-2012 Junichi Satoh +#!/usr/bin/perl +########################################################################## +# Tentacle Server +# See http://www.openideas.info/wiki for protocol description. +# Tentacle have IANA assigned port tpc/41121 as official port. +########################################################################## +# Copyright (c) 2007-2008 Ramon Novoa +# Copyright (c) 2005-2010 Artica Soluciones Tecnologicas S.L # -# ********************************************************************** - -# PROVIDE: tentacle_server -# REQUIRE: LOGIN -# KEYWORD: shutdown - -# Add the following line to /etc/rc.conf to enable `tentacle_server': -# -# tentacle_server_enable="YES" +# tentacle_server.pl Tentacle Server. See http://www.openideas.info/wiki for +# protocol description. # +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +########################################################################## -. "/etc/rc.subr" +package tentacle::server; +=head1 NAME -name="tentacle_server" -rcvar=tentacle_server_enable +tentacle_server - Tentacle Server -# read configuration and set defaults -tentacle_server_enable=${tentacle_server_enable:-"NO"} -tentacle_server_flags=${tentacle_server_flags:-'-a 0.0.0.0 -p 41121 -s /var/spool/pandora/data_in -i.*\.conf:conf\;.*\.md5:md5\;.*\.zip:collections -d'} -tentacle_server_user=${tentacle_server_user:-"pandora"} -load_rc_config $name +=head1 VERSION -PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin +Version 0.6.1 -command=/usr/local/bin/${name} -command_interpreter=/usr/local/bin/perl -procname=$command -pidfile=/var/run/$name.pid +=head1 USAGE -start_postcmd=start_postcmd -stop_postcmd=stop_postcmd +tentacle_server B<< -s F >> [I] -start_postcmd() -{ - pgrep -f -j none "^$command_interpreter $command" > $pidfile +=head1 DESCRIPTION + +B is a server for B, a B file transfer protocol that aims to be: + +=over + +=item * Secure by design. + +=item * Easy to use. + +=item * Versatile and cross-platform. + +=back + +Tentacle was created to replace more complex tools like SCP and FTP for simple file transfer/retrieval, and switch from authentication mechanisms like .netrc, interactive logins and SSH keys to X.509 certificates. Simple password authentication over a SSL secured connection is supported too. + +The client and server (B) are designed to be run from the command line or called from a shell script, and B. + +=cut + +use strict; +use warnings; +use Getopt::Std; +use IO::Select; +use IO::Compress::Zip qw(zip $ZipError); +use IO::Uncompress::Unzip qw(unzip $UnzipError); +use threads; +use Thread::Semaphore; +use POSIX ":sys_wait_h"; +use Time::HiRes qw(usleep); +use Scalar::Util qw(refaddr); +use POSIX qw(strftime); + +# Constants for Win32 services. +use constant WIN32_SERVICE_STOPPED => 0x01; +use constant WIN32_SERVICE_RUNNING => 0x04; + +my $t_libwrap_installed = eval { require Authen::Libwrap } ? 1 : 0; + +if ($t_libwrap_installed) { + Authen::Libwrap->import( qw( hosts_ctl STRING_UNKNOWN ) ); } -stop_postcmd() -{ - rm -f $pidfile +# Log errors, 1 enabled, 0 disabled +my $t_log = 0; + +# Log information, 1 enabled, 0 enabled +my $t_log_hard = 0; + +my $SOCKET_MODULE; +if ($^O eq 'MSWin32') { + # Only support INET on windows + require IO::Socket::INET; + $SOCKET_MODULE = 'IO::Socket::INET'; +} else { + $SOCKET_MODULE = + eval { require IO::Socket::INET6 } ? 'IO::Socket::INET6' + : eval { require IO::Socket::INET } ? 'IO::Socket::INET' + : die $@; } -run_rc_command "$1" +# Service name for Win32. +my $SERVICE_NAME="Tentacle Server"; + +# Service parameters. +my $SERVICE_PARAMS=join(' ', @ARGV); + +# Program version +our $VERSION = '0.6.2'; + +# IPv4 address to listen on +my @t_addresses = ('0', '0.0.0.0'); + +# Block size for socket read/write operations in bytes +my $t_block_size = 1024; + +# Client socket +my $t_client_socket; + +# Run as daemon, 1 true, 0 false +my $t_daemon = 0; + +# Storage directory +my $t_directory = ''; + +# Filters +my @t_filters; + +# Enable (1) or disable (0) insecure mode +my $t_insecure = 0; + +# String containing quoted invalid file name characters +my $t_invalid_chars = '\?\[\]\/\\\=\+\<\>\:\;\'\,\*\~'; + +# Maximum number of simultaneous connections +my $t_max_conn = 10; + +# Maximum file size allowed by the server in bytes +my $t_max_size = 2000000; + +# File overwrite, 1 enabled, 0 disabled +my $t_overwrite = 0; + +# Port to listen on +my $t_port = 41121; + +# Server password +my $t_pwd = ''; + +# Do not output error messages, 1 enabled, 0 disabled +my $t_quiet = 0; + +# Number of retries for socket read/write operations +my $t_retries = 3; + +# Select handler +my $t_select; + +# Semaphore +my $t_sem :shared; + +# Server socket +my @t_server_sockets; + +# Server select handler +my $t_server_select; + +# Use SSL, 1 true, 0 false +my $t_ssl = 0; + +# SSL ca certificate file +my $t_ssl_ca = ''; + +# SSL certificate file +my $t_ssl_cert = ''; + +# SSL private key file +my $t_ssl_key = ''; + +# SSL private key password +my $t_ssl_pwd = ''; + +# Timeout for socket read/write operations in seconds +my $t_timeout = 1; + +# Address to proxy client requests to +my $t_proxy_ip = undef; + +# Port to proxy client requests to +my $t_proxy_port = 41121; + +# Proxy socket +my $t_proxy_socket; + +# Proxy selected handler +my $t_proxy_select; + +# Use libwrap, 1 true, 0 false +my $t_use_libwrap = 0; + +# Program name for libwrap +my $t_program_name = $0; +$t_program_name =~ s/.*\///g; + +# Log file +my $log_file = undef; + +################################################################################ +## SUB print_help +## Print help screen. +################################################################################ +sub print_help { + $" = ','; + + print ("Usage: $0 -s [options]\n\n"); + print ("Tentacle server v$VERSION. See http://www.openideas.info/wiki for protocol description.\n\n"); + print ("Options:\n"); + print ("\t-a ip_addresses\tIP addresses to listen on (default @t_addresses).\n"); + print ("\t \t(Multiple addresses separated by comma can be defined.)\n"); + print ("\t-c number\tMaximum number of simultaneous connections (default $t_max_conn).\n"); + print ("\t-d\t\tRun as daemon.\n"); + print ("\t-e cert\t\tOpenSSL certificate file. Enables SSL.\n"); + print ("\t-f ca_cert\tVerify that the peer certificate is signed by a ca.\n"); + print ("\t-F config_file\tConfiguration file full path.\n"); + print ("\t-h\t\tShow help.\n"); + print ("\t-I\t\tEnable insecure operations (file listing and moving).\n"); + print ("\t-i\t\tFilters.\n"); + print ("\t-k key\t\tOpenSSL private key file.\n"); + print ("\t-l log_file\t\tFile to write logs.\n"); + print ("\t-m size\t\tMaximum file size in bytes (default ${t_max_size}b).\n"); + print ("\t-o\t\tEnable file overwrite.\n"); + print ("\t-p port\t\tPort to listen on (default $t_port).\n"); + print ("\t-q\t\tQuiet. Do now print error messages.\n"); + print ("\t-r number\tNumber of retries for network opertions (default $t_retries).\n"); + print ("\t-S (install|uninstall|run) Manage the win32 service.\n"); + print ("\t-t time\t\tTime-out for network operations in seconds (default ${t_timeout}s).\n"); + print ("\t-v\t\tBe verbose (display errors).\n"); + print ("\t-V\t\tBe verbose on hard way (display errors and other info).\n"); + print ("\t-w\t\tPrompt for OpenSSL private key password.\n"); + print ("\t-x pwd\t\tServer password.\n"); + print ("\t-b ip_address\tProxy requests to the given address.\n"); + print ("\t-g port\t\tProxy requests to the given port.\n"); + print ("\t-T\t\tEnable tcpwrappers support.\n"); + print ("\t \t\t(To use this option, 'Authen::Libwrap' should be installed.)\n\n"); +} + +################################################################################ +## SUB daemonize +## Turn the current process into a daemon. +################################################################################ +sub daemonize { + my $pid; + + require POSIX; + + chdir ('/') || error ("Cannot chdir to /: $!."); + umask 0; + + open (STDIN, '/dev/null') || error ("Cannot read /dev/null: $!."); + + # Do not be verbose when running as a daemon + open (STDOUT, '>/dev/null') || error ("Cannot write to /dev/null: $!."); + open (STDERR, '>/dev/null') || error ("Cannot write to /dev/null: $!."); + + # Fork + $pid = fork (); + if (! defined ($pid)) { + error ("Cannot fork: $!."); + } + + # Parent + if ($pid != 0) { + exit; + } + + # Child + POSIX::setsid () || error ("Cannot start a new session: $!."); +} + +################################################################################ +## SUB parse_options +## Parse command line options and initialize global variables. +################################################################################ +sub parse_options { + my %opts; + my $CONF = {}; + my $token_value; + my $tmp; + my @t_addresses_tmp; + + # Get options + if (getopts ('a:b:c:de:f:F:g:hIi:k:l:m:op:qr:s:S:t:TvVwx:', \%opts) == 0 || defined ($opts{'h'})) { + print_help (); + exit 1; + } + + # The Win32 service must be installed/uninstalled without checking other parameters. + if (defined ($opts{'S'})) { + my $service_action = $opts{'S'}; + if ($^O ne 'MSWin32') { + error ("Windows services are only available on Win32."); + } else { + eval "use Win32::Daemon"; + die($@) if ($@); + + if ($service_action eq 'install') { + install_service(); + } elsif ($service_action eq 'uninstall') { + uninstall_service(); + } + } + } + + # Configuration file + if (defined($opts{'F'})) { + parse_config_file($opts{'F'}, $CONF); + } + + # Address + $token_value = get_config_value($opts{'a'}, $CONF->{'addresses'}); + if (defined ($token_value)) { + @t_addresses = (); + @t_addresses_tmp = split(/,/, $token_value); + + foreach my $t_address (@t_addresses_tmp) { + $t_address =~ s/^ *(.*?) *$/$1/; + if (($t_address ne '0') && + ($t_address !~ /^[a-zA-Z\.]+$/ && ($t_address !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ + || $1 < 0 || $1 > 255 || $2 < 0 || $2 > 255 + || $3 < 0 || $3 > 255 || $4 < 0 || $4 > 255)) && + ($t_address !~ /^[0-9a-f:]+$/o)) { + error ("Address $t_address is not valid."); + } + push @t_addresses, $t_address; + } + } + + # Maximum simultaneous connections + $token_value = get_config_value($opts{'c'}, $CONF->{'max_connections'}); + if (defined ($token_value)) { + $t_max_conn = $token_value; + if ($t_max_conn !~ /^\d+$/ || $t_max_conn < 1) { + error ("Invalid number of maximum simultaneous connections."); + } + } + + # Run as daemon + $token_value = get_config_value($opts{'d'}, $CONF->{'daemon'}, 1); + if (defined ($token_value)) { + if ($^ eq 'MSWin32') { + error ("-d flag not available for this OS."); + } + + $t_daemon = 1; + } + + # Enable SSL + $token_value = get_config_value($opts{'e'}, $CONF->{'ssl_cert'}); + if (defined ($token_value)) { + + require IO::Socket::SSL; + + $t_ssl_cert = $token_value; + if (! -f $t_ssl_cert) { + error ("File $t_ssl_cert does not exist."); + } + + $t_ssl = 1; + } + + # Verify peer certificate + $token_value = get_config_value($opts{'f'}, $CONF->{'ssl_ca'}); + if (defined ($token_value)) { + $t_ssl_ca = $token_value; + if (! -f $t_ssl_ca) { + error ("File $t_ssl_ca does not exist."); + } + } + + # Insecure mode + $token_value = get_config_value($opts{'I'}, $CONF->{'insecure'}, 1); + if (defined ($token_value)) { + $t_insecure = 1; + } + + # Filters (regexp:dir;regexp:dir...) + $token_value = get_config_value($opts{'i'}, $CONF->{'filters'}); + if (defined ($token_value)) { + my @filters = split (';', $token_value); + foreach my $filter (@filters) { + my ($regexp, $dir) = split (':', $filter); + next unless defined ($regexp) && defined ($dir); + + # Remove any trailing / + my $char = chop ($dir); + $dir .= $char if ($char) ne '/'; + + push(@t_filters, [$regexp, $dir]); + } + } + + # SSL private key file + $token_value = get_config_value($opts{'k'}, $CONF->{'ssl_key'}); + if (defined ($token_value)) { + $t_ssl_key = $token_value; + if (! -f $t_ssl_key) { + error ("File $t_ssl_key does not exist."); + } + } + + # Maximum file size + $token_value = get_config_value($opts{'m'}, $CONF->{'max_size'}); + if (defined ($token_value)) { + $t_max_size = $token_value; + if ($t_max_size !~ /^\d+$/ || $t_max_size < 1) { + error ("Invalid maximum file size."); + } + } + + # File overwrite + $token_value = get_config_value($opts{'o'}, $CONF->{'overwrite'}, 1); + if (defined ($token_value)) { + $t_overwrite = 1; + } + + # Port + $token_value = get_config_value($opts{'p'}, $CONF->{'port'}); + if (defined ($token_value)) { + $t_port = $token_value; + if ($t_port !~ /^\d+$/ || $t_port < 1 || $t_port > 65535) { + error ("Port $t_port is not valid."); + } + } + + # Quiet mode + $token_value = get_config_value($opts{'q'}, $CONF->{'quiet'}, 1); + if (defined ($token_value)) { + $t_quiet = 1; + } + + # Retries + $token_value = get_config_value($opts{'r'}, $CONF->{'retries'}); + if (defined ($token_value)) { + $t_retries = $token_value; + if ($t_retries !~ /^\d+$/ || $t_retries < 1) { + error ("Invalid number of retries for network operations."); + } + } + + # Storage directory + $token_value = get_config_value($opts{'s'}, $CONF->{'directory'}); + if (defined ($token_value)) { + + $t_directory = $token_value; + + # Check that directory exists + if (! -d $t_directory) { + error ("Directory $t_directory does not exist."); + } + + # Check directory permissions + if (! -w $t_directory) { + error ("Cannot write to directory $t_directory."); + } + + # Remove the trailing / if present + $tmp = chop ($t_directory); + if ($tmp ne '/') { + $t_directory .= $tmp; + } + } + else { + $token_value = get_config_value($opts{'b'}, $CONF->{'proxy_ip'}); + if (! defined($token_value)) { + print_help (); + exit 1; + } + } + + # Timeout + $token_value = get_config_value($opts{'t'}, $CONF->{'timeout'}); + if (defined ($token_value)) { + $t_timeout = $token_value; + if ($t_timeout !~ /^\d+$/ || $t_timeout < 1) { + error ("Invalid timeout for network operations."); + } + } + + # Read verbose from config file + if (defined($CONF->{'verbose'})) { + if ($CONF->{'verbose'} eq "1") { + $t_log = 1; + } elsif ($CONF->{'verbose'} eq "2") { + $t_log = 1; + $t_log_hard = 1; + } + } + # Be verbose + if (defined ($opts{'v'})) { + $t_log = 1; + $t_log_hard = 0; + } + # Be verbose hard + if (defined ($opts{'V'})) { + $t_log = 1; + $t_log_hard = 1; + } + + # SSL private key password + $token_value = get_config_value($opts{'w'}, $CONF->{'ssl_password'}, 1); + if (defined ($token_value)) { + $t_ssl_pwd = ask_passwd ("Enter private key file password: ", "Enter private key file password again for confirmation: "); + } + + # Server password + $token_value = get_config_value($opts{'x'}, $CONF->{'password'}); + if (defined ($token_value)) { + $t_pwd = $token_value; + } + + #Proxy IP address + $token_value = get_config_value($opts{'b'}, $CONF->{'proxy_ip'}); + if (defined ($token_value)) { + $t_proxy_ip = $token_value; + if ($t_proxy_ip !~ /^[a-zA-Z\.]+$/ && ($t_proxy_ip !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ + || $1 < 0 || $1 > 255 || $2 < 0 || $2 > 255 + || $3 < 0 || $3 > 255 || $4 < 0 || $4 > 255) && + $t_proxy_ip !~ /^[0-9a-f:]+$/o) { + error ("Proxy address $t_proxy_ip is not valid."); + } + } + + # Proxy Port + $token_value = get_config_value($opts{'g'}, $CONF->{'proxy_port'}); + if (defined ($token_value)) { + $t_proxy_port = $token_value; + if ($t_proxy_port !~ /^\d+$/ || $t_proxy_port < 1 || $t_proxy_port > 65535) { + error ("Proxy port $t_port is not valid."); + } + } + + # TCP wrappers support + $token_value = get_config_value($opts{'T'}, $CONF->{'use_libwrap'}, 1); + if (defined ($token_value)) { + if ($t_libwrap_installed) { + $t_use_libwrap = 1; + } else { + error ("Authen::Libwrap is not installed."); + } + } + + # Win32 service management + if (defined ($opts{'S'})) { + my $service_action = $opts{'S'}; + if ($^O ne 'MSWin32') { + error ("Windows services are only available on Win32."); + } else { + eval "use Win32::Daemon"; + die($@) if ($@); + + if ($service_action eq 'run') { + Win32::Daemon::RegisterCallbacks({ + start => \&callback_start, + running => \&callback_running, + stop => \&callback_stop, + }); + Win32::Daemon::StartService(); + exit 0; + } else { + error("Unknown action: $service_action"); + } + } + } + + # Get the config file + $token_value = get_config_value($opts{'l'}, $CONF->{'log_file'}); + if (defined ($token_value)) { + $log_file = $token_value; + } + + # No command lines config values + + # Get the block size + if (defined ($CONF->{'block_size'})) { + if ($t_port !~ /^\d+$/ || $t_port < 1) { + error ("Invalid block size: " . $CONF->{'block_size'} . "."); + } + $t_block_size = $CONF->{'block_size'}; + } + + # Configuration file invalid chars + if (defined ($CONF->{'invalid_chars'})) { + $t_invalid_chars = $CONF->{'invalid_chars'}; + } +} + +################################################################################ +## SUB parse_config_file +## Get all options from a config file. +################################################################################ +sub parse_config_file { + my ($config_file, $CONF) = @_; + + # File should be writable + if (! -r $config_file) { + print "Configuration file $config_file is not readable.\n"; + return; + } + + # Open the file + my $FH; + if (! open ($FH, "< $config_file")) { + print "Cannot open configuration file $config_file.\n"; + return; + } + + # Read the file and only get the well formed lines + while (<$FH>) { + my $buffer_line = $_; + if ($buffer_line =~ /^[a-zA-Z]/){ # begins with letters + if ($buffer_line =~ m/([\w\-\_\.]+)\s+(.*)/){ + $CONF->{$1} = $2 unless $2 eq ""; + } + } + } + + close ($FH); + return; +} + +################################################################################ +## SUB parse_config_file +## Search in command line options and config hash from configuration file +## to get a value (command line is a priority) +################################################################################ +sub get_config_value { + my ($cmd_value, $conf_value, $bool) = @_; + $bool = 0 unless defined($bool); + + return $cmd_value if defined($cmd_value); + # The boolean type value is 1 or undef (0 should be translated like undefP) + if ($bool && defined($conf_value)) { + return undef if ($conf_value ne "1"); + } + return $conf_value; +} + +################################################################################ +## SUB start_proxy +## Open the proxy server socket. +################################################################################ +sub start_proxy { + + # Connect to server + $t_proxy_socket = $SOCKET_MODULE->new ( + PeerAddr => $t_proxy_ip, + PeerPort => $t_proxy_port, + ); + + if (! defined ($t_proxy_socket)) { + error ("Cannot connect to $t_proxy_ip on port $t_proxy_port: $!."); + } + + # Create proxy selector + $t_proxy_select = IO::Select->new (); + $t_proxy_select->add ($t_proxy_socket); + +} + +################################################################################ +## SUB start_server +## Open the server socket. +################################################################################ +sub start_server { + + my $t_server_socket; + + foreach my $t_address (@t_addresses) { + + $t_server_socket = $SOCKET_MODULE->new ( + Listen => $t_max_conn, + LocalAddr => $t_address, + LocalPort => $t_port, + Proto => 'tcp', + ReuseAddr => 1, + ); + + if (! defined ($t_server_socket)) { + print_log ("Cannot open socket for address $t_address on port $t_port: $!."); + next; + } + + print_log ("Server listening on $t_address port $t_port (press to stop)"); + + # Say message if tentacle proxy is enable + if (defined ($t_proxy_ip)) { + print_log ("Proxy Mode enable, data will be sent to $t_proxy_ip port $t_proxy_port"); + } + + push @t_server_sockets, $t_server_socket; + } + + if (!@t_server_sockets) { + error ("Cannot open socket for all addresses on port $t_port: $!."); + } + + $t_server_select = IO::Select->new(); + foreach my $t_server_socket (@t_server_sockets){ + $t_server_select->add($t_server_socket); + } +} + +################################################################################ +## SUB send_data_proxy +## Send data to proxy socket. +################################################################################ +sub send_data_proxy { + my $data = $_[0]; + my $block_size; + my $retries = 0; + my $size; + my $total = 0; + my $written; + + $size = length ($data); + + while (1) { + + # Try to write data to the socket + if ($t_proxy_select->can_write ($t_timeout)) { + + $block_size = ($size - $total) > $t_block_size ? $t_block_size : ($size - $total); + $written = syswrite ($t_proxy_socket, $data, $size - $total, $total); + + # Write error + if (! defined ($written)) { + error ("Connection error from " . $t_proxy_socket->sockhost () . ": $!."); + } + + # EOF + if ($written == 0) { + error ("Connection from " . $t_proxy_socket->sockhost () . " unexpectedly closed."); + } + + $total += $written; + + # Check if all data was written + if ($total == $size) { + return; + } + } + # Retry + else { + $retries++; + if ($retries > $t_retries) { + error ("Connection from " . $t_proxy_socket->sockhost () . " timed out."); + } + } + } +} + +################################################################################ +## SUB close_proxy +## Close the proxy socket. +################################################################################ +sub close_proxy { + $t_proxy_socket->shutdown (2); + $t_proxy_socket->close (); +} + +################################################################################ +## SUB stop_server +## Close the server socket. +################################################################################ +sub stop_server { + + foreach my $t_server_socket (@t_server_sockets) { + $t_server_socket->shutdown (2); + $t_server_socket->close (); + } + print_log ("Server going down"); + + exit 0; +} + +################################################################################ +## SUB start_ssl +## Convert the client socket to an IO::Socket::SSL socket. +################################################################################ +sub start_ssl { + my $err; + + if ($t_ssl_ca eq '') { + IO::Socket::SSL->start_SSL ( + $t_client_socket, + SSL_cert_file => $t_ssl_cert, + SSL_key_file => $t_ssl_key, + SSL_passwd_cb => sub {return $t_ssl_pwd}, + SSL_server => 1, + # Verify peer + SSL_verify_mode => 0x01, + ); + } + else { + IO::Socket::SSL->start_SSL ( + $t_client_socket, + SSL_ca_file => $t_ssl_ca, + SSL_cert_file => $t_ssl_cert, + SSL_key_file => $t_ssl_key, + SSL_passwd_cb => sub {return $t_ssl_pwd}, + SSL_server => 1, + # Fail verification if no peer certificate exists + SSL_verify_mode => 0x03, + ); + } + + $err = IO::Socket::SSL::errstr (); + if ($err ne '') { + error ($err); + } + + print_log ("SSL started for " . $t_client_socket->sockhost ()); +} + +################################################################################ +## SUB accept_connections +## Manage incoming connections. +################################################################################ +sub accept_connections { + my $pid; + my $t_server_socket; + + # Ignore SIGPIPE + $SIG{PIPE} = 'IGNORE'; + + # Start server + start_server (); + + # Initialize semaphore + $t_sem = Thread::Semaphore->new ($t_max_conn); + + while (1) { + my @ready = $t_server_select->can_read; + foreach $t_server_socket (@ready) { + + # Accept connection + $t_client_socket = $t_server_socket->accept (); + + if (! defined ($t_client_socket)) { + next if ($! ne ''); # EINTR + error ("accept: $!."); + } + + print_info ("Client connected from " . $t_client_socket->peerhost ()); + + if ($t_use_libwrap && (! hosts_ctl($t_program_name, $t_client_socket))) { + print_log ("Connection from " . $t_client_socket->peerhost() . " is closed by tcpwrappers."); + $t_client_socket->shutdown (2); + $t_client_socket->close(); + } + else { + + # Create a new thread and serve the client + $t_sem->down(); + my $thr = threads->create(\&serve_client); + if (! defined ($thr)) { + error ("Error creating thread: $!."); + } + $thr->detach(); + $t_client_socket->close (); + } + } + + usleep (1000); + } +} + +################################################################################ +## SUB serve_client +## Serve a connected client. +################################################################################ +sub serve_client() { + + eval { + # Add client socket to select queue + $t_select = IO::Select->new (); + $t_select->add ($t_client_socket); + + # Start SSL + if ($t_ssl == 1) { + start_ssl (); + } + + # Authenticate client + if ($t_pwd ne '') { + auth_pwd (); + } + + # Check if proxy mode is enable + if (defined ($t_proxy_ip)) { + serve_proxy_connection (); + } else { + serve_connection (); + } + }; + + $t_client_socket->shutdown (2); + $t_client_socket->close (); + $t_sem->up(); +} + +################################################################################ +## SUB serve_proxy_connection +## Actuate as a proxy between its client and other tentacle server. +################################################################################ +sub serve_proxy_connection { + + # We are a proxy! Start a connection to the Tentacle Server. + start_proxy(); + + # Forward data between the client and the server. + eval { + my $select = IO::Select->new (); + $select->add($t_proxy_socket); + $select->add($t_client_socket); + while (my @ready = $select->can_read()) { + foreach my $socket (@ready) { + if (refaddr($socket) == refaddr($t_client_socket)) { + my ($read, $data) = recv_data($t_block_size); + return unless defined($data); + send_data_proxy($data); + } + else { + my ($read, $data) = recv_data_proxy($t_block_size); + return unless defined($data); + send_data($data); + } + } + } + }; + + # Close the connection to the Tentacle Server. + close_proxy(); +} + +################################################################################ +## SUB serve_connection +## Read and process commands from the client. +################################################################################ +sub serve_connection { + my $command; + + # Read commands + while ($command = recv_command ($t_block_size)) { + + # Client wants to send a file + if ($command =~ /^SEND <(.*)> SIZE (\d+)$/) { + print_info ("Request to send file '$1' size ${2}b from " . $t_client_socket->sockhost ()); + recv_file ($1, $2); + } + # Client wants to receive a file + elsif ($command =~ /^RECV <(.*)>$/) { + print_info ("Request to receive file '$1' from " . $t_client_socket->sockhost ()); + send_file ($1); + } + elsif ($command =~ /^ZSEND <(.*)> SIZE (\d+)$/) { + print_info ("Request to send compressed file '$1' size ${2}b from " . $t_client_socket->sockhost ()); + zrecv_file ($1, $2); + } + # Client wants to receive a file + elsif ($command =~ /^ZRECV <(.*)>$/) { + print_info ("Request to receive compressed file '$1' from " . $t_client_socket->sockhost ()); + zsend_file ($1); + } + # Quit + elsif ($command =~ /^QUIT$/) { + print_info ("Connection closed from " . $t_client_socket->sockhost ()); + last; + } + # File listing. + elsif ($command =~ /^LS <(.*)>$/) { + if ($t_insecure == 0) { + print_info ("Insecure mode disabled. Rejected request to list files matched by filter $1 from " . $t_client_socket->sockhost ()); + last; + } + + print_info ("Request to list files matched by filter $1 from " . $t_client_socket->sockhost ()); + send_file_list ($1); + } + # Client wants to move a file + elsif ($command =~ /^MV <(.*)>$/) { + if ($t_insecure == 0) { + print_info ("Insecure mode disabled. Rejected request to move file $1 from " . $t_client_socket->sockhost ()); + last; + } + + print_info ("Request to move file '$1' from " . $t_client_socket->sockhost ()); + move_file ($1); + } + # Unknown command + else { + print_log ("Unknown command '$command' from " . $t_client_socket->sockhost ()); + last; + } + } +} + +################################################################################ +## SUB auth_pwd +## Authenticate client with server password. +################################################################################ +sub auth_pwd { + my $client_digest; + my $command; + my $pwd_digest; + + require Digest::MD5; + + # Wait for password + $command = recv_command ($t_block_size); + if ($command !~ /^PASS (.*)$/) { + error ("Client " . $t_client_socket->sockhost () . " did not authenticate."); + } + + $client_digest = $1; + $pwd_digest = Digest::MD5::md5 ($t_pwd); + $pwd_digest = Digest::MD5::md5_hex ($pwd_digest); + + if ($client_digest ne $pwd_digest) { + error ("Invalid password from " . $t_client_socket->sockhost () . "."); + } + + print_log ("Client " . $t_client_socket->sockhost () . " authenticated"); + send_data ("PASS OK\n"); +} + +################################################################################ +## SUB recv_file +## Receive a file of size $_[1] and save it in $t_directory as $_[0]. +################################################################################ +sub recv_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $size = $_[1]; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("SEND ERR (invalid file name)\n"); + return; + } + + # Check file size, empty files are not allowed + if ($size < 1 || $size > $t_max_size) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " is too big"); + send_data ("SEND ERR (file is too big)\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (-f $file && $t_overwrite == 0) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " already exists"); + send_data ("SEND ERR (file already exists)\n"); + return; + } + + send_data ("SEND OK\n"); + + # Receive file + $data = recv_data_block ($size); + + # Write it to disk + open (FILE, "> $file") || error ("Cannot open file '$file' for writing."); + binmode (FILE); + print (FILE $data); + close (FILE); + + send_data ("SEND OK\n"); + print_info ("Received file '$base_name' size ${size}b from " . $t_client_socket->sockhost ()); +} + +################################################################################ +## SUB zrecv_file +## Receive a compressed file of size $_[1] and save it in $t_directory as $_[0]. +################################################################################ +sub zrecv_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $size = $_[1]; + my $zdata = ''; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("ZSEND ERR (invalid file name)\n"); + return; + } + + # Check file size, empty files are not allowed + if ($size < 1 || $size > $t_max_size) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " is too big"); + send_data ("ZSEND ERR (file is too big)\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (-f $file && $t_overwrite == 0) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " already exists"); + send_data ("ZSEND ERR (file already exists)\n"); + return; + } + + send_data ("ZSEND OK\n"); + + # Receive file + $zdata = recv_data_block ($size); + if (!unzip(\$zdata => \$data)) { + print_log ("Uncompress error: $UnzipError"); + send_data ("ZSEND ERR\n"); + return; + } + + # Write it to disk + open (FILE, "> $file") || error ("Cannot open file '$file' for writing."); + binmode (FILE); + print (FILE $data); + close (FILE); + + send_data ("ZSEND OK\n"); + print_info ("Received compressed file '$base_name' size ${size}b from " . $t_client_socket->sockhost ()); +} + +################################################################################ +## SUB send_file +## Send a file to the client +################################################################################ +sub send_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $response; + my $size; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("Requested file '$base_name' from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("RECV ERR (file has an invalid file name)\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (! -f $file) { + print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " does not exist"); + send_data ("RECV ERR (file does not exist)\n"); + return; + } + + $size = -s $file; + send_data ("RECV SIZE $size\n"); + + # Wait for client response + $response = recv_command ($t_block_size); + if ($response ne "RECV OK") { + print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " not sent"); + return; + } + + # Send the file + open (FILE, $file) || error ("Cannot open file '$file' for reading."); + binmode (FILE); + { + local $/ = undef; + $data = ; + } + + send_data ($data); + close (FILE); + + print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " sent"); +} + +################################################################################ +## SUB zsend_file +## Send a file to the client +################################################################################ +sub zsend_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $response; + my $size; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("Requested compressed file '$base_name' from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("ZRECV ERR (file has an invalid file name)\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (! -f $file) { + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " does not exist"); + send_data ("ZRECV ERR (file does not exist)\n"); + return; + } + + # Read the file and compress its contents + if (! zip($file => \$data)) { + send_data ("QUIT\n"); + error ("Compression error: $ZipError"); + return; + } + + $size = length($data); + send_data ("ZRECV SIZE $size\n"); + + # Wait for client response + $response = recv_command ($t_block_size); + if ($response ne "ZRECV OK") { + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " not sent"); + return; + } + + # Send the file + send_data ($data); + + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " sent"); +} + +################################################################################ +# Common functions +################################################################################ + +################################################################################ +## SUB print_log +## Print log messages. +################################################################################ +sub print_log($) { + + my ($msg) = @_; + + return unless ($t_log == 1); + + my $fh = *STDOUT; + if (defined($log_file)) { + open($fh, ">>", $log_file) || die("Starting log failed: $!.\n"); + } + + print ($fh strftime ("%Y-%m-%d %H:%M:%S", localtime()) . "[log]$msg.\n"); + + close ($fh) if (defined($log_file)); + +} + +################################################################################ +## SUB print_log +## Print log messages. +################################################################################ +sub print_info($) { + + my ($msg) = @_; + + return unless ($t_log_hard == 1); + + my $fh = *STDOUT; + if (defined($log_file)) { + open($fh, ">>", $log_file) || die("Starting log failed: $!.\n"); + } + + print ($fh strftime ("%Y-%m-%d %H:%M:%S", localtime()) . "[info]$msg.\n"); + + close ($fh) if (defined($log_file)); + +} + +################################################################################ +## SUB error +## Print an error and exit the program. +################################################################################ +sub error { + + my ($msg) = @_; + + return unless ($t_quiet == 0); + + my $fh = *STDERR; + if (defined($log_file)) { + open($fh, ">>", $log_file) || die("$!\n"); + } + + print ($fh strftime ("%Y-%m-%d %H:%M:%S", localtime()) . "[err]$msg\n"); + + close ($fh) if (defined($log_file)); + + die("\n"); +} + +################################################################################ +## SUB move_file +## Send a file to the client and delete it +################################################################################ +sub move_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $response; + my $size; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("Requested file '$base_name' from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("MV ERR\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (! -f $file) { + print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " does not exist"); + send_data ("MV ERR\n"); + return; + } + + $size = -s $file; + send_data ("MV SIZE $size\n"); + + # Wait for client response + $response = recv_command ($t_block_size); + if ($response ne "MV OK") { + print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " not sent"); + return; + } + + # Send the file + open (FILE, $file) || error ("Cannot open file '$file' for reading."); + binmode (FILE); + + while ($data = ) { + send_data ($data); + } + + close (FILE); + unlink($file); + + print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " sent and deleted"); +} + +################################################################################ +## SUB send_file_list +## Send a list of files to the client after applying the given filter. +################################################################################ +sub send_file_list { + my $filter = $_[0]; + my $data = ''; + my $dir; + my $dh; + my $response; + my $size; + + # Check file name + if ($filter =~ /[$t_invalid_chars]/) { + print_log ("Invalid file listing filter '$filter' from " . $t_client_socket->sockhost ()); + send_data ("LS ERR\n"); + return; + } + + # Apply filters + $dir = "$t_directory/" . apply_filters ($filter); + + # Open the directory. + if (! opendir ($dh, $dir)) { + print_log ("Error opening directory $dir as requested from " . $t_client_socket->sockhost () . ": $!"); + send_data ("LS ERR\n"); + return; + } + + # List files. + while (my $file = readdir ($dh)) { + next if ($file =~ /[$t_invalid_chars]/); # Only list files valid for Tentacle. + $data .= "$file\n"; + } + closedir $dh; + + $size = length ($data); + send_data ("LS SIZE $size\n"); + + # Wait for client response + $response = recv_command ($t_block_size); + if ($response ne "LS OK") { + print_log ("Requested directory listing from " . $t_client_socket->sockhost () . " not sent"); + return; + } + + send_data ($data); + + print_log ("Requested directory listing from " . $t_client_socket->sockhost () . " sent"); +} + +################################################################################ +## SUB recv_data_proxy +## Recv data from proxy socket. +################################################################################ +sub recv_data_proxy { + my $data; + my $read; + my $retries = 0; + my $size = $_[0]; + + while (1) { + + # Try to read data from the socket + if ($t_proxy_select->can_read ($t_timeout)) { + + # Read at most $size bytes + $read = sysread ($t_proxy_socket, $data, $size); + + # Read error + if (! defined ($read)) { + error ("Read error from " . $t_proxy_socket->sockhost () . ": $!."); + } + + # EOF + if ($read == 0) { + error ("Connection from " . $t_proxy_socket->sockhost () . " unexpectedly closed."); + } + + return ($read, $data); + } + + # Retry + $retries++; + + # But check for error conditions first + if ($retries > $t_retries) { + error ("Connection from " . $t_proxy_socket->sockhost () . " timed out."); + } + } +} +################################################################################ +## SUB recv_data +## Read data from the client socket. Returns the number of bytes read and the +## string of bytes as a two element array. +################################################################################ +sub recv_data { + my $data; + my $read; + my $retries = 0; + my $size = $_[0]; + + while (1) { + + # Try to read data from the socket + if ($t_select->can_read ($t_timeout)) { + + # Read at most $size bytes + $read = sysread ($t_client_socket, $data, $size); + + # Read error + if (! defined ($read)) { + error ("Read error from " . $t_client_socket->sockhost () . ": $!."); + } + + # EOF + if ($read == 0) { + error ("Connection from " . $t_client_socket->sockhost () . " unexpectedly closed."); + } + + return ($read, $data); + } + + # Retry + $retries++; + + # But check for error conditions first + if ($retries > $t_retries) { + error ("Connection from " . $t_client_socket->sockhost () . " timed out."); + } + } +} + +################################################################################ +## SUB send_data +## Write data to the client socket. +################################################################################ +sub send_data { + my $data = $_[0]; + my $block_size; + my $retries = 0; + my $size; + my $total = 0; + my $written; + + $size = length ($data); + + while (1) { + + # Try to write data to the socket + if ($t_select->can_write ($t_timeout)) { + + $block_size = ($size - $total) > $t_block_size ? $t_block_size : ($size - $total); + $written = syswrite ($t_client_socket, $data, $block_size, $total); + + # Write error + if (! defined ($written)) { + error ("Connection error from " . $t_client_socket->sockhost () . ": $!."); + } + + # EOF + if ($written == 0) { + error ("Connection from " . $t_client_socket->sockhost () . " unexpectedly closed."); + } + + $total += $written; + + # Check if all data was written + if ($total == $size) { + return; + } + } + # Retry + else { + $retries++; + if ($retries > $t_retries) { + error ("Connection from " . $t_client_socket->sockhost () . " timed out."); + } + } + } +} + +################################################################################ +## SUB recv_command +## Read a command from the client, ended by a new line character. +################################################################################ +sub recv_command { + my $buffer; + my $char; + my $command = ''; + my $read; + my $total = 0; + + while (1) { + + ($read, $buffer) = recv_data ($t_block_size); + $command .= $buffer; + $total += $read; + + # Check if the command is complete + $char = chop ($command); + if ($char eq "\n") { + return $command; + } + + $command .= $char; + + # Avoid overflow + if ($total > $t_block_size) { + error ("Received too much data from " . $t_client_socket->sockhost () . "."); + } + } +} + +################################################################################ +## SUB recv_data_block +## Read $_[0] bytes of data from the client. +################################################################################ +sub recv_data_block { + my $buffer = ''; + my $data = ''; + my $read; + my $size = $_[0]; + my $total = 0; + + while (1) { + + ($read, $buffer) = recv_data ($size - $total); + $data .= $buffer; + $total += $read; + + # Check if all data has been read + if ($total == $size) { + return $data; + } + } +} + +################################################################################ +## SUB ask_passwd +## Asks the user for a password. +################################################################################ +sub ask_passwd { + my $msg1 = $_[0]; + my $msg2 = $_[1]; + my $pwd1; + my $pwd2; + + require Term::ReadKey; + + # Disable keyboard echo + Term::ReadKey::ReadMode('noecho'); + + # Promt for password + print ($msg1); + $pwd1 = Term::ReadKey::ReadLine(0); + print ("\n$msg2"); + $pwd2 = Term::ReadKey::ReadLine(0); + print ("\n"); + + # Restore original settings + Term::ReadKey::ReadMode('restore'); + + if ($pwd1 ne $pwd2) { + print ("Error: passwords do not match.\n"); + exit 1; + } + + # Remove the trailing new line character + chop $pwd1; + + return $pwd1; +} + +################################################################################ +## SUB apply_filters +## Applies filters to the given file. +################################################################################ +sub apply_filters ($) { + my ($file_name) = @_; + + foreach my $filter (@t_filters) { + my ($regexp, $dir) = @{$filter}; + if ($file_name =~ /$regexp/) { + print_log ("File '$file_name' matches filter '$regexp' (changing to directory '$dir')"); + return $dir . '/'; + } + } + + return ''; +} + +################################################################################ +## SUB install_service +## Install the Windows service. +################################################################################ +sub install_service() { + + my $service_path = $0; + my $service_params = $SERVICE_PARAMS; + + # Change the service parameter from 'install' to 'run'. + $service_params =~ s/\-S\s+\S+/\-S run/; + + my %service_hash = ( + machine => '', + name => 'TENTACLESRV', + display => $SERVICE_NAME, + path => $service_path, + user => '', + pwd => '', + description => 'Tentacle Server http://sourceforge.net/projects/tentacled/', + parameters => $service_params + ); + + if (Win32::Daemon::CreateService(\%service_hash)) { + print "Successfully added.\n"; + exit 0; + } else { + print "Failed to add service: " . Win32::FormatMessage(Win32::Daemon::GetLastError()) . "\n"; + exit 1; + } +} + +################################################################################ +## SUB uninstall_service +## Install the Windows service. +################################################################################ +sub uninstall_service() { + if (Win32::Daemon::DeleteService('', 'TENTACLESRV')) { + print "Successfully deleted.\n"; + exit 0; + } else { + print "Failed to delete service: " . Win32::FormatMessage(Win32::Daemon::GetLastError()) . "\n"; + exit 1; + } +} + +################################################################################ +## SUB callback_running +## Windows service callback function for the running event. +################################################################################ +sub callback_running { + + if (Win32::Daemon::State() == WIN32_SERVICE_RUNNING) { + } +} + +################################################################################ +## SUB callback_start +## Windows service callback function for the start event. +################################################################################ +sub callback_start { + + # Accept_connections (); + my $thr = threads->create(\&accept_connections); + if (!defined($thr)) { + Win32::Daemon::State(WIN32_SERVICE_STOPPED); + Win32::Daemon::StopService(); + return; + } + $thr->detach(); + + Win32::Daemon::State(WIN32_SERVICE_RUNNING); +} + +################################################################################ +## SUB callback_stop +## Windows service callback function for the stop event. +################################################################################ +sub callback_stop { + + foreach my $t_server_socket (@t_server_sockets) { + $t_server_socket->shutdown (2); + $t_server_socket->close (); + } + + Win32::Daemon::State(WIN32_SERVICE_STOPPED); + Win32::Daemon::StopService(); +} + +################################################################################ +# Main +################################################################################ + +# Never run as root +if ($> == 0 && $^O ne 'MSWin32') { + print ("Error: for safety reasons $0 cannot be run with root privileges.\n"); + exit 1; +} + +# Parse command line options +parse_options (); + +# Check command line arguments +if ($#ARGV != -1) { + print_help (); + exit 1; +} + +# Show IPv6 status +if ($SOCKET_MODULE eq 'IO::Socket::INET') { + print_log ("IO::Socket::INET6 is not found. IPv6 is disabled."); +} + +# Run as daemon? +if ($t_daemon == 1 && $^O ne 'MSWin32') { + daemonize (); +} + +# Handle ctr-c +if ($^O eq 'MSWin32') { + no warnings; + $SIG{INT2} = \&stop_server; + use warnings; +} +else { + $SIG{INT} = \&stop_server; +} + +# Accept connections +accept_connections(); + +__END__ + +=head1 REQUIRED ARGUMENTES + +=over + +=item B<< -s F >> Root directory to store the files received by the server + +=back + +=head1 OPTIONS + +=over + +=item I<-a ip_address> Address to B on (default I<0.0.0.0>). + +=item I<-c number> B number of simultaneous B (default I<10>). + +=item I<-d> Run as B. + +=item I<-e cert> B file. Enables SSL. + +=item I<-f ca_cert> Verify that the peer certificate is signed by a B. + +=item I<-h> Show B. + +=item I<-i> B. + +=item I<-k key> B file. + +=item I<-m size> B in bytes (default I<2000000b>). + +=item I<-o> Enable file B. + +=item I<-p port> B on (default I<41121>). + +=item I<-q> B. Do now print error messages. + +=item I<-r number> B for network opertions (default I<3>). + +=item I<-t time> B for network operations in B (default I<1s>). + +=item I<-v> Be B. + +=item I<-w> Prompt for B. + +=item I<-x> pwd B. + +=back + +=head1 EXIT STATUS + +=over + +=item 0 on Success + +=item 1 on Error + +=back + +=head1 CONFIGURATION + +Tentacle doesn't use any configurationf files, all the configuration is done by the options passed when it's started. + +=head1 DEPENDENCIES + +L, L, L, L, L + + +=head1 LICENSE + +This is released under the GNU Lesser General Public License. + +=head1 SEE ALSO + +L, L, L, L, L + +Protocol description and more info at: L<< http://openideas.info/wiki/index.php?title=Tentacle >> + +=head1 COPYRIGHT + +Copyright (c) 2005-2010 Artica Soluciones Tecnologicas S.L + +=cut + diff --git a/pandora_server/NetBSD/tentacle_server b/pandora_server/NetBSD/tentacle_server index 8d0cb6501e..8b27ed35b3 100755 --- a/pandora_server/NetBSD/tentacle_server +++ b/pandora_server/NetBSD/tentacle_server @@ -1,50 +1,1869 @@ -#!/bin/sh - -# ********************************************************************** -# Tentacle Server Daemon launcher for NetBSD -# (c) 2013 Hiroki SHIMIZU +#!/usr/bin/perl +########################################################################## +# Tentacle Server +# See http://www.openideas.info/wiki for protocol description. +# Tentacle have IANA assigned port tpc/41121 as official port. +########################################################################## +# Copyright (c) 2007-2008 Ramon Novoa +# Copyright (c) 2005-2010 Artica Soluciones Tecnologicas S.L # -# ********************************************************************** - -# PROVIDE: tentacle_server -# REQUIRE: LOGIN -# KEYWORD: shutdown - -# Add the following line to /etc/rc.conf to enable `tentacle_server': -# -# tentacle_server="YES" +# tentacle_server.pl Tentacle Server. See http://www.openideas.info/wiki for +# protocol description. # +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +########################################################################## -. "/etc/rc.subr" +package tentacle::server; +=head1 NAME -name="tentacle_server" -rcvar=${name} +tentacle_server - Tentacle Server -# read configuration and set defaults -tentacle_server=${tentacle_server:-"NO"} -tentacle_server_flags=${tentacle_server_flags:-"-a 0.0.0.0 -p 41121 -s /var/spool/pandora/data_in -i.*\.conf:conf\;.*\.md5:md5\;.*\.zip:collections -d"} -tentacle_server_user=${tentacle_server_user:-"pandora"} -load_rc_config $name +=head1 VERSION -PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin +Version 0.6.1 -command=/usr/local/bin/${name} +=head1 USAGE -pidfile=/var/run/$name.pid -start_postcmd=start_postcmd -stop_postcmd=stop_postcmd +tentacle_server B<< -s F >> [I] -procname="/usr/pkg/perl" +=head1 DESCRIPTION -start_postcmd() -{ - TENTACLE_PID=`pgrep -f none $name` - echo $TENTACLE_PID > $pidfile +B is a server for B, a B file transfer protocol that aims to be: + +=over + +=item * Secure by design. + +=item * Easy to use. + +=item * Versatile and cross-platform. + +=back + +Tentacle was created to replace more complex tools like SCP and FTP for simple file transfer/retrieval, and switch from authentication mechanisms like .netrc, interactive logins and SSH keys to X.509 certificates. Simple password authentication over a SSL secured connection is supported too. + +The client and server (B) are designed to be run from the command line or called from a shell script, and B. + +=cut + +use strict; +use warnings; +use Getopt::Std; +use IO::Select; +use IO::Compress::Zip qw(zip $ZipError); +use IO::Uncompress::Unzip qw(unzip $UnzipError); +use threads; +use Thread::Semaphore; +use POSIX ":sys_wait_h"; +use Time::HiRes qw(usleep); +use Scalar::Util qw(refaddr); +use POSIX qw(strftime); + +# Constants for Win32 services. +use constant WIN32_SERVICE_STOPPED => 0x01; +use constant WIN32_SERVICE_RUNNING => 0x04; + +my $t_libwrap_installed = eval { require Authen::Libwrap } ? 1 : 0; + +if ($t_libwrap_installed) { + Authen::Libwrap->import( qw( hosts_ctl STRING_UNKNOWN ) ); } -stop_postcmd() -{ - rm -f $pidfile +# Log errors, 1 enabled, 0 disabled +my $t_log = 0; + +# Log information, 1 enabled, 0 enabled +my $t_log_hard = 0; + +my $SOCKET_MODULE; +if ($^O eq 'MSWin32') { + # Only support INET on windows + require IO::Socket::INET; + $SOCKET_MODULE = 'IO::Socket::INET'; +} else { + $SOCKET_MODULE = + eval { require IO::Socket::INET6 } ? 'IO::Socket::INET6' + : eval { require IO::Socket::INET } ? 'IO::Socket::INET' + : die $@; } -run_rc_command "$1" +# Service name for Win32. +my $SERVICE_NAME="Tentacle Server"; + +# Service parameters. +my $SERVICE_PARAMS=join(' ', @ARGV); + +# Program version +our $VERSION = '0.6.2'; + +# IPv4 address to listen on +my @t_addresses = ('0', '0.0.0.0'); + +# Block size for socket read/write operations in bytes +my $t_block_size = 1024; + +# Client socket +my $t_client_socket; + +# Run as daemon, 1 true, 0 false +my $t_daemon = 0; + +# Storage directory +my $t_directory = ''; + +# Filters +my @t_filters; + +# Enable (1) or disable (0) insecure mode +my $t_insecure = 0; + +# String containing quoted invalid file name characters +my $t_invalid_chars = '\?\[\]\/\\\=\+\<\>\:\;\'\,\*\~'; + +# Maximum number of simultaneous connections +my $t_max_conn = 10; + +# Maximum file size allowed by the server in bytes +my $t_max_size = 2000000; + +# File overwrite, 1 enabled, 0 disabled +my $t_overwrite = 0; + +# Port to listen on +my $t_port = 41121; + +# Server password +my $t_pwd = ''; + +# Do not output error messages, 1 enabled, 0 disabled +my $t_quiet = 0; + +# Number of retries for socket read/write operations +my $t_retries = 3; + +# Select handler +my $t_select; + +# Semaphore +my $t_sem :shared; + +# Server socket +my @t_server_sockets; + +# Server select handler +my $t_server_select; + +# Use SSL, 1 true, 0 false +my $t_ssl = 0; + +# SSL ca certificate file +my $t_ssl_ca = ''; + +# SSL certificate file +my $t_ssl_cert = ''; + +# SSL private key file +my $t_ssl_key = ''; + +# SSL private key password +my $t_ssl_pwd = ''; + +# Timeout for socket read/write operations in seconds +my $t_timeout = 1; + +# Address to proxy client requests to +my $t_proxy_ip = undef; + +# Port to proxy client requests to +my $t_proxy_port = 41121; + +# Proxy socket +my $t_proxy_socket; + +# Proxy selected handler +my $t_proxy_select; + +# Use libwrap, 1 true, 0 false +my $t_use_libwrap = 0; + +# Program name for libwrap +my $t_program_name = $0; +$t_program_name =~ s/.*\///g; + +# Log file +my $log_file = undef; + +################################################################################ +## SUB print_help +## Print help screen. +################################################################################ +sub print_help { + $" = ','; + + print ("Usage: $0 -s [options]\n\n"); + print ("Tentacle server v$VERSION. See http://www.openideas.info/wiki for protocol description.\n\n"); + print ("Options:\n"); + print ("\t-a ip_addresses\tIP addresses to listen on (default @t_addresses).\n"); + print ("\t \t(Multiple addresses separated by comma can be defined.)\n"); + print ("\t-c number\tMaximum number of simultaneous connections (default $t_max_conn).\n"); + print ("\t-d\t\tRun as daemon.\n"); + print ("\t-e cert\t\tOpenSSL certificate file. Enables SSL.\n"); + print ("\t-f ca_cert\tVerify that the peer certificate is signed by a ca.\n"); + print ("\t-F config_file\tConfiguration file full path.\n"); + print ("\t-h\t\tShow help.\n"); + print ("\t-I\t\tEnable insecure operations (file listing and moving).\n"); + print ("\t-i\t\tFilters.\n"); + print ("\t-k key\t\tOpenSSL private key file.\n"); + print ("\t-l log_file\t\tFile to write logs.\n"); + print ("\t-m size\t\tMaximum file size in bytes (default ${t_max_size}b).\n"); + print ("\t-o\t\tEnable file overwrite.\n"); + print ("\t-p port\t\tPort to listen on (default $t_port).\n"); + print ("\t-q\t\tQuiet. Do now print error messages.\n"); + print ("\t-r number\tNumber of retries for network opertions (default $t_retries).\n"); + print ("\t-S (install|uninstall|run) Manage the win32 service.\n"); + print ("\t-t time\t\tTime-out for network operations in seconds (default ${t_timeout}s).\n"); + print ("\t-v\t\tBe verbose (display errors).\n"); + print ("\t-V\t\tBe verbose on hard way (display errors and other info).\n"); + print ("\t-w\t\tPrompt for OpenSSL private key password.\n"); + print ("\t-x pwd\t\tServer password.\n"); + print ("\t-b ip_address\tProxy requests to the given address.\n"); + print ("\t-g port\t\tProxy requests to the given port.\n"); + print ("\t-T\t\tEnable tcpwrappers support.\n"); + print ("\t \t\t(To use this option, 'Authen::Libwrap' should be installed.)\n\n"); +} + +################################################################################ +## SUB daemonize +## Turn the current process into a daemon. +################################################################################ +sub daemonize { + my $pid; + + require POSIX; + + chdir ('/') || error ("Cannot chdir to /: $!."); + umask 0; + + open (STDIN, '/dev/null') || error ("Cannot read /dev/null: $!."); + + # Do not be verbose when running as a daemon + open (STDOUT, '>/dev/null') || error ("Cannot write to /dev/null: $!."); + open (STDERR, '>/dev/null') || error ("Cannot write to /dev/null: $!."); + + # Fork + $pid = fork (); + if (! defined ($pid)) { + error ("Cannot fork: $!."); + } + + # Parent + if ($pid != 0) { + exit; + } + + # Child + POSIX::setsid () || error ("Cannot start a new session: $!."); +} + +################################################################################ +## SUB parse_options +## Parse command line options and initialize global variables. +################################################################################ +sub parse_options { + my %opts; + my $CONF = {}; + my $token_value; + my $tmp; + my @t_addresses_tmp; + + # Get options + if (getopts ('a:b:c:de:f:F:g:hIi:k:l:m:op:qr:s:S:t:TvVwx:', \%opts) == 0 || defined ($opts{'h'})) { + print_help (); + exit 1; + } + + # The Win32 service must be installed/uninstalled without checking other parameters. + if (defined ($opts{'S'})) { + my $service_action = $opts{'S'}; + if ($^O ne 'MSWin32') { + error ("Windows services are only available on Win32."); + } else { + eval "use Win32::Daemon"; + die($@) if ($@); + + if ($service_action eq 'install') { + install_service(); + } elsif ($service_action eq 'uninstall') { + uninstall_service(); + } + } + } + + # Configuration file + if (defined($opts{'F'})) { + parse_config_file($opts{'F'}, $CONF); + } + + # Address + $token_value = get_config_value($opts{'a'}, $CONF->{'addresses'}); + if (defined ($token_value)) { + @t_addresses = (); + @t_addresses_tmp = split(/,/, $token_value); + + foreach my $t_address (@t_addresses_tmp) { + $t_address =~ s/^ *(.*?) *$/$1/; + if (($t_address ne '0') && + ($t_address !~ /^[a-zA-Z\.]+$/ && ($t_address !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ + || $1 < 0 || $1 > 255 || $2 < 0 || $2 > 255 + || $3 < 0 || $3 > 255 || $4 < 0 || $4 > 255)) && + ($t_address !~ /^[0-9a-f:]+$/o)) { + error ("Address $t_address is not valid."); + } + push @t_addresses, $t_address; + } + } + + # Maximum simultaneous connections + $token_value = get_config_value($opts{'c'}, $CONF->{'max_connections'}); + if (defined ($token_value)) { + $t_max_conn = $token_value; + if ($t_max_conn !~ /^\d+$/ || $t_max_conn < 1) { + error ("Invalid number of maximum simultaneous connections."); + } + } + + # Run as daemon + $token_value = get_config_value($opts{'d'}, $CONF->{'daemon'}, 1); + if (defined ($token_value)) { + if ($^ eq 'MSWin32') { + error ("-d flag not available for this OS."); + } + + $t_daemon = 1; + } + + # Enable SSL + $token_value = get_config_value($opts{'e'}, $CONF->{'ssl_cert'}); + if (defined ($token_value)) { + + require IO::Socket::SSL; + + $t_ssl_cert = $token_value; + if (! -f $t_ssl_cert) { + error ("File $t_ssl_cert does not exist."); + } + + $t_ssl = 1; + } + + # Verify peer certificate + $token_value = get_config_value($opts{'f'}, $CONF->{'ssl_ca'}); + if (defined ($token_value)) { + $t_ssl_ca = $token_value; + if (! -f $t_ssl_ca) { + error ("File $t_ssl_ca does not exist."); + } + } + + # Insecure mode + $token_value = get_config_value($opts{'I'}, $CONF->{'insecure'}, 1); + if (defined ($token_value)) { + $t_insecure = 1; + } + + # Filters (regexp:dir;regexp:dir...) + $token_value = get_config_value($opts{'i'}, $CONF->{'filters'}); + if (defined ($token_value)) { + my @filters = split (';', $token_value); + foreach my $filter (@filters) { + my ($regexp, $dir) = split (':', $filter); + next unless defined ($regexp) && defined ($dir); + + # Remove any trailing / + my $char = chop ($dir); + $dir .= $char if ($char) ne '/'; + + push(@t_filters, [$regexp, $dir]); + } + } + + # SSL private key file + $token_value = get_config_value($opts{'k'}, $CONF->{'ssl_key'}); + if (defined ($token_value)) { + $t_ssl_key = $token_value; + if (! -f $t_ssl_key) { + error ("File $t_ssl_key does not exist."); + } + } + + # Maximum file size + $token_value = get_config_value($opts{'m'}, $CONF->{'max_size'}); + if (defined ($token_value)) { + $t_max_size = $token_value; + if ($t_max_size !~ /^\d+$/ || $t_max_size < 1) { + error ("Invalid maximum file size."); + } + } + + # File overwrite + $token_value = get_config_value($opts{'o'}, $CONF->{'overwrite'}, 1); + if (defined ($token_value)) { + $t_overwrite = 1; + } + + # Port + $token_value = get_config_value($opts{'p'}, $CONF->{'port'}); + if (defined ($token_value)) { + $t_port = $token_value; + if ($t_port !~ /^\d+$/ || $t_port < 1 || $t_port > 65535) { + error ("Port $t_port is not valid."); + } + } + + # Quiet mode + $token_value = get_config_value($opts{'q'}, $CONF->{'quiet'}, 1); + if (defined ($token_value)) { + $t_quiet = 1; + } + + # Retries + $token_value = get_config_value($opts{'r'}, $CONF->{'retries'}); + if (defined ($token_value)) { + $t_retries = $token_value; + if ($t_retries !~ /^\d+$/ || $t_retries < 1) { + error ("Invalid number of retries for network operations."); + } + } + + # Storage directory + $token_value = get_config_value($opts{'s'}, $CONF->{'directory'}); + if (defined ($token_value)) { + + $t_directory = $token_value; + + # Check that directory exists + if (! -d $t_directory) { + error ("Directory $t_directory does not exist."); + } + + # Check directory permissions + if (! -w $t_directory) { + error ("Cannot write to directory $t_directory."); + } + + # Remove the trailing / if present + $tmp = chop ($t_directory); + if ($tmp ne '/') { + $t_directory .= $tmp; + } + } + else { + $token_value = get_config_value($opts{'b'}, $CONF->{'proxy_ip'}); + if (! defined($token_value)) { + print_help (); + exit 1; + } + } + + # Timeout + $token_value = get_config_value($opts{'t'}, $CONF->{'timeout'}); + if (defined ($token_value)) { + $t_timeout = $token_value; + if ($t_timeout !~ /^\d+$/ || $t_timeout < 1) { + error ("Invalid timeout for network operations."); + } + } + + # Read verbose from config file + if (defined($CONF->{'verbose'})) { + if ($CONF->{'verbose'} eq "1") { + $t_log = 1; + } elsif ($CONF->{'verbose'} eq "2") { + $t_log = 1; + $t_log_hard = 1; + } + } + # Be verbose + if (defined ($opts{'v'})) { + $t_log = 1; + $t_log_hard = 0; + } + # Be verbose hard + if (defined ($opts{'V'})) { + $t_log = 1; + $t_log_hard = 1; + } + + # SSL private key password + $token_value = get_config_value($opts{'w'}, $CONF->{'ssl_password'}, 1); + if (defined ($token_value)) { + $t_ssl_pwd = ask_passwd ("Enter private key file password: ", "Enter private key file password again for confirmation: "); + } + + # Server password + $token_value = get_config_value($opts{'x'}, $CONF->{'password'}); + if (defined ($token_value)) { + $t_pwd = $token_value; + } + + #Proxy IP address + $token_value = get_config_value($opts{'b'}, $CONF->{'proxy_ip'}); + if (defined ($token_value)) { + $t_proxy_ip = $token_value; + if ($t_proxy_ip !~ /^[a-zA-Z\.]+$/ && ($t_proxy_ip !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ + || $1 < 0 || $1 > 255 || $2 < 0 || $2 > 255 + || $3 < 0 || $3 > 255 || $4 < 0 || $4 > 255) && + $t_proxy_ip !~ /^[0-9a-f:]+$/o) { + error ("Proxy address $t_proxy_ip is not valid."); + } + } + + # Proxy Port + $token_value = get_config_value($opts{'g'}, $CONF->{'proxy_port'}); + if (defined ($token_value)) { + $t_proxy_port = $token_value; + if ($t_proxy_port !~ /^\d+$/ || $t_proxy_port < 1 || $t_proxy_port > 65535) { + error ("Proxy port $t_port is not valid."); + } + } + + # TCP wrappers support + $token_value = get_config_value($opts{'T'}, $CONF->{'use_libwrap'}, 1); + if (defined ($token_value)) { + if ($t_libwrap_installed) { + $t_use_libwrap = 1; + } else { + error ("Authen::Libwrap is not installed."); + } + } + + # Win32 service management + if (defined ($opts{'S'})) { + my $service_action = $opts{'S'}; + if ($^O ne 'MSWin32') { + error ("Windows services are only available on Win32."); + } else { + eval "use Win32::Daemon"; + die($@) if ($@); + + if ($service_action eq 'run') { + Win32::Daemon::RegisterCallbacks({ + start => \&callback_start, + running => \&callback_running, + stop => \&callback_stop, + }); + Win32::Daemon::StartService(); + exit 0; + } else { + error("Unknown action: $service_action"); + } + } + } + + # Get the config file + $token_value = get_config_value($opts{'l'}, $CONF->{'log_file'}); + if (defined ($token_value)) { + $log_file = $token_value; + } + + # No command lines config values + + # Get the block size + if (defined ($CONF->{'block_size'})) { + if ($t_port !~ /^\d+$/ || $t_port < 1) { + error ("Invalid block size: " . $CONF->{'block_size'} . "."); + } + $t_block_size = $CONF->{'block_size'}; + } + + # Configuration file invalid chars + if (defined ($CONF->{'invalid_chars'})) { + $t_invalid_chars = $CONF->{'invalid_chars'}; + } +} + +################################################################################ +## SUB parse_config_file +## Get all options from a config file. +################################################################################ +sub parse_config_file { + my ($config_file, $CONF) = @_; + + # File should be writable + if (! -r $config_file) { + print "Configuration file $config_file is not readable.\n"; + return; + } + + # Open the file + my $FH; + if (! open ($FH, "< $config_file")) { + print "Cannot open configuration file $config_file.\n"; + return; + } + + # Read the file and only get the well formed lines + while (<$FH>) { + my $buffer_line = $_; + if ($buffer_line =~ /^[a-zA-Z]/){ # begins with letters + if ($buffer_line =~ m/([\w\-\_\.]+)\s+(.*)/){ + $CONF->{$1} = $2 unless $2 eq ""; + } + } + } + + close ($FH); + return; +} + +################################################################################ +## SUB parse_config_file +## Search in command line options and config hash from configuration file +## to get a value (command line is a priority) +################################################################################ +sub get_config_value { + my ($cmd_value, $conf_value, $bool) = @_; + $bool = 0 unless defined($bool); + + return $cmd_value if defined($cmd_value); + # The boolean type value is 1 or undef (0 should be translated like undefP) + if ($bool && defined($conf_value)) { + return undef if ($conf_value ne "1"); + } + return $conf_value; +} + +################################################################################ +## SUB start_proxy +## Open the proxy server socket. +################################################################################ +sub start_proxy { + + # Connect to server + $t_proxy_socket = $SOCKET_MODULE->new ( + PeerAddr => $t_proxy_ip, + PeerPort => $t_proxy_port, + ); + + if (! defined ($t_proxy_socket)) { + error ("Cannot connect to $t_proxy_ip on port $t_proxy_port: $!."); + } + + # Create proxy selector + $t_proxy_select = IO::Select->new (); + $t_proxy_select->add ($t_proxy_socket); + +} + +################################################################################ +## SUB start_server +## Open the server socket. +################################################################################ +sub start_server { + + my $t_server_socket; + + foreach my $t_address (@t_addresses) { + + $t_server_socket = $SOCKET_MODULE->new ( + Listen => $t_max_conn, + LocalAddr => $t_address, + LocalPort => $t_port, + Proto => 'tcp', + ReuseAddr => 1, + ); + + if (! defined ($t_server_socket)) { + print_log ("Cannot open socket for address $t_address on port $t_port: $!."); + next; + } + + print_log ("Server listening on $t_address port $t_port (press to stop)"); + + # Say message if tentacle proxy is enable + if (defined ($t_proxy_ip)) { + print_log ("Proxy Mode enable, data will be sent to $t_proxy_ip port $t_proxy_port"); + } + + push @t_server_sockets, $t_server_socket; + } + + if (!@t_server_sockets) { + error ("Cannot open socket for all addresses on port $t_port: $!."); + } + + $t_server_select = IO::Select->new(); + foreach my $t_server_socket (@t_server_sockets){ + $t_server_select->add($t_server_socket); + } +} + +################################################################################ +## SUB send_data_proxy +## Send data to proxy socket. +################################################################################ +sub send_data_proxy { + my $data = $_[0]; + my $block_size; + my $retries = 0; + my $size; + my $total = 0; + my $written; + + $size = length ($data); + + while (1) { + + # Try to write data to the socket + if ($t_proxy_select->can_write ($t_timeout)) { + + $block_size = ($size - $total) > $t_block_size ? $t_block_size : ($size - $total); + $written = syswrite ($t_proxy_socket, $data, $size - $total, $total); + + # Write error + if (! defined ($written)) { + error ("Connection error from " . $t_proxy_socket->sockhost () . ": $!."); + } + + # EOF + if ($written == 0) { + error ("Connection from " . $t_proxy_socket->sockhost () . " unexpectedly closed."); + } + + $total += $written; + + # Check if all data was written + if ($total == $size) { + return; + } + } + # Retry + else { + $retries++; + if ($retries > $t_retries) { + error ("Connection from " . $t_proxy_socket->sockhost () . " timed out."); + } + } + } +} + +################################################################################ +## SUB close_proxy +## Close the proxy socket. +################################################################################ +sub close_proxy { + $t_proxy_socket->shutdown (2); + $t_proxy_socket->close (); +} + +################################################################################ +## SUB stop_server +## Close the server socket. +################################################################################ +sub stop_server { + + foreach my $t_server_socket (@t_server_sockets) { + $t_server_socket->shutdown (2); + $t_server_socket->close (); + } + print_log ("Server going down"); + + exit 0; +} + +################################################################################ +## SUB start_ssl +## Convert the client socket to an IO::Socket::SSL socket. +################################################################################ +sub start_ssl { + my $err; + + if ($t_ssl_ca eq '') { + IO::Socket::SSL->start_SSL ( + $t_client_socket, + SSL_cert_file => $t_ssl_cert, + SSL_key_file => $t_ssl_key, + SSL_passwd_cb => sub {return $t_ssl_pwd}, + SSL_server => 1, + # Verify peer + SSL_verify_mode => 0x01, + ); + } + else { + IO::Socket::SSL->start_SSL ( + $t_client_socket, + SSL_ca_file => $t_ssl_ca, + SSL_cert_file => $t_ssl_cert, + SSL_key_file => $t_ssl_key, + SSL_passwd_cb => sub {return $t_ssl_pwd}, + SSL_server => 1, + # Fail verification if no peer certificate exists + SSL_verify_mode => 0x03, + ); + } + + $err = IO::Socket::SSL::errstr (); + if ($err ne '') { + error ($err); + } + + print_log ("SSL started for " . $t_client_socket->sockhost ()); +} + +################################################################################ +## SUB accept_connections +## Manage incoming connections. +################################################################################ +sub accept_connections { + my $pid; + my $t_server_socket; + + # Ignore SIGPIPE + $SIG{PIPE} = 'IGNORE'; + + # Start server + start_server (); + + # Initialize semaphore + $t_sem = Thread::Semaphore->new ($t_max_conn); + + while (1) { + my @ready = $t_server_select->can_read; + foreach $t_server_socket (@ready) { + + # Accept connection + $t_client_socket = $t_server_socket->accept (); + + if (! defined ($t_client_socket)) { + next if ($! ne ''); # EINTR + error ("accept: $!."); + } + + print_info ("Client connected from " . $t_client_socket->peerhost ()); + + if ($t_use_libwrap && (! hosts_ctl($t_program_name, $t_client_socket))) { + print_log ("Connection from " . $t_client_socket->peerhost() . " is closed by tcpwrappers."); + $t_client_socket->shutdown (2); + $t_client_socket->close(); + } + else { + + # Create a new thread and serve the client + $t_sem->down(); + my $thr = threads->create(\&serve_client); + if (! defined ($thr)) { + error ("Error creating thread: $!."); + } + $thr->detach(); + $t_client_socket->close (); + } + } + + usleep (1000); + } +} + +################################################################################ +## SUB serve_client +## Serve a connected client. +################################################################################ +sub serve_client() { + + eval { + # Add client socket to select queue + $t_select = IO::Select->new (); + $t_select->add ($t_client_socket); + + # Start SSL + if ($t_ssl == 1) { + start_ssl (); + } + + # Authenticate client + if ($t_pwd ne '') { + auth_pwd (); + } + + # Check if proxy mode is enable + if (defined ($t_proxy_ip)) { + serve_proxy_connection (); + } else { + serve_connection (); + } + }; + + $t_client_socket->shutdown (2); + $t_client_socket->close (); + $t_sem->up(); +} + +################################################################################ +## SUB serve_proxy_connection +## Actuate as a proxy between its client and other tentacle server. +################################################################################ +sub serve_proxy_connection { + + # We are a proxy! Start a connection to the Tentacle Server. + start_proxy(); + + # Forward data between the client and the server. + eval { + my $select = IO::Select->new (); + $select->add($t_proxy_socket); + $select->add($t_client_socket); + while (my @ready = $select->can_read()) { + foreach my $socket (@ready) { + if (refaddr($socket) == refaddr($t_client_socket)) { + my ($read, $data) = recv_data($t_block_size); + return unless defined($data); + send_data_proxy($data); + } + else { + my ($read, $data) = recv_data_proxy($t_block_size); + return unless defined($data); + send_data($data); + } + } + } + }; + + # Close the connection to the Tentacle Server. + close_proxy(); +} + +################################################################################ +## SUB serve_connection +## Read and process commands from the client. +################################################################################ +sub serve_connection { + my $command; + + # Read commands + while ($command = recv_command ($t_block_size)) { + + # Client wants to send a file + if ($command =~ /^SEND <(.*)> SIZE (\d+)$/) { + print_info ("Request to send file '$1' size ${2}b from " . $t_client_socket->sockhost ()); + recv_file ($1, $2); + } + # Client wants to receive a file + elsif ($command =~ /^RECV <(.*)>$/) { + print_info ("Request to receive file '$1' from " . $t_client_socket->sockhost ()); + send_file ($1); + } + elsif ($command =~ /^ZSEND <(.*)> SIZE (\d+)$/) { + print_info ("Request to send compressed file '$1' size ${2}b from " . $t_client_socket->sockhost ()); + zrecv_file ($1, $2); + } + # Client wants to receive a file + elsif ($command =~ /^ZRECV <(.*)>$/) { + print_info ("Request to receive compressed file '$1' from " . $t_client_socket->sockhost ()); + zsend_file ($1); + } + # Quit + elsif ($command =~ /^QUIT$/) { + print_info ("Connection closed from " . $t_client_socket->sockhost ()); + last; + } + # File listing. + elsif ($command =~ /^LS <(.*)>$/) { + if ($t_insecure == 0) { + print_info ("Insecure mode disabled. Rejected request to list files matched by filter $1 from " . $t_client_socket->sockhost ()); + last; + } + + print_info ("Request to list files matched by filter $1 from " . $t_client_socket->sockhost ()); + send_file_list ($1); + } + # Client wants to move a file + elsif ($command =~ /^MV <(.*)>$/) { + if ($t_insecure == 0) { + print_info ("Insecure mode disabled. Rejected request to move file $1 from " . $t_client_socket->sockhost ()); + last; + } + + print_info ("Request to move file '$1' from " . $t_client_socket->sockhost ()); + move_file ($1); + } + # Unknown command + else { + print_log ("Unknown command '$command' from " . $t_client_socket->sockhost ()); + last; + } + } +} + +################################################################################ +## SUB auth_pwd +## Authenticate client with server password. +################################################################################ +sub auth_pwd { + my $client_digest; + my $command; + my $pwd_digest; + + require Digest::MD5; + + # Wait for password + $command = recv_command ($t_block_size); + if ($command !~ /^PASS (.*)$/) { + error ("Client " . $t_client_socket->sockhost () . " did not authenticate."); + } + + $client_digest = $1; + $pwd_digest = Digest::MD5::md5 ($t_pwd); + $pwd_digest = Digest::MD5::md5_hex ($pwd_digest); + + if ($client_digest ne $pwd_digest) { + error ("Invalid password from " . $t_client_socket->sockhost () . "."); + } + + print_log ("Client " . $t_client_socket->sockhost () . " authenticated"); + send_data ("PASS OK\n"); +} + +################################################################################ +## SUB recv_file +## Receive a file of size $_[1] and save it in $t_directory as $_[0]. +################################################################################ +sub recv_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $size = $_[1]; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("SEND ERR (invalid file name)\n"); + return; + } + + # Check file size, empty files are not allowed + if ($size < 1 || $size > $t_max_size) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " is too big"); + send_data ("SEND ERR (file is too big)\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (-f $file && $t_overwrite == 0) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " already exists"); + send_data ("SEND ERR (file already exists)\n"); + return; + } + + send_data ("SEND OK\n"); + + # Receive file + $data = recv_data_block ($size); + + # Write it to disk + open (FILE, "> $file") || error ("Cannot open file '$file' for writing."); + binmode (FILE); + print (FILE $data); + close (FILE); + + send_data ("SEND OK\n"); + print_info ("Received file '$base_name' size ${size}b from " . $t_client_socket->sockhost ()); +} + +################################################################################ +## SUB zrecv_file +## Receive a compressed file of size $_[1] and save it in $t_directory as $_[0]. +################################################################################ +sub zrecv_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $size = $_[1]; + my $zdata = ''; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("ZSEND ERR (invalid file name)\n"); + return; + } + + # Check file size, empty files are not allowed + if ($size < 1 || $size > $t_max_size) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " is too big"); + send_data ("ZSEND ERR (file is too big)\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (-f $file && $t_overwrite == 0) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " already exists"); + send_data ("ZSEND ERR (file already exists)\n"); + return; + } + + send_data ("ZSEND OK\n"); + + # Receive file + $zdata = recv_data_block ($size); + if (!unzip(\$zdata => \$data)) { + print_log ("Uncompress error: $UnzipError"); + send_data ("ZSEND ERR\n"); + return; + } + + # Write it to disk + open (FILE, "> $file") || error ("Cannot open file '$file' for writing."); + binmode (FILE); + print (FILE $data); + close (FILE); + + send_data ("ZSEND OK\n"); + print_info ("Received compressed file '$base_name' size ${size}b from " . $t_client_socket->sockhost ()); +} + +################################################################################ +## SUB send_file +## Send a file to the client +################################################################################ +sub send_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $response; + my $size; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("Requested file '$base_name' from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("RECV ERR (file has an invalid file name)\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (! -f $file) { + print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " does not exist"); + send_data ("RECV ERR (file does not exist)\n"); + return; + } + + $size = -s $file; + send_data ("RECV SIZE $size\n"); + + # Wait for client response + $response = recv_command ($t_block_size); + if ($response ne "RECV OK") { + print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " not sent"); + return; + } + + # Send the file + open (FILE, $file) || error ("Cannot open file '$file' for reading."); + binmode (FILE); + { + local $/ = undef; + $data = ; + } + + send_data ($data); + close (FILE); + + print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " sent"); +} + +################################################################################ +## SUB zsend_file +## Send a file to the client +################################################################################ +sub zsend_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $response; + my $size; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("Requested compressed file '$base_name' from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("ZRECV ERR (file has an invalid file name)\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (! -f $file) { + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " does not exist"); + send_data ("ZRECV ERR (file does not exist)\n"); + return; + } + + # Read the file and compress its contents + if (! zip($file => \$data)) { + send_data ("QUIT\n"); + error ("Compression error: $ZipError"); + return; + } + + $size = length($data); + send_data ("ZRECV SIZE $size\n"); + + # Wait for client response + $response = recv_command ($t_block_size); + if ($response ne "ZRECV OK") { + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " not sent"); + return; + } + + # Send the file + send_data ($data); + + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " sent"); +} + +################################################################################ +# Common functions +################################################################################ + +################################################################################ +## SUB print_log +## Print log messages. +################################################################################ +sub print_log($) { + + my ($msg) = @_; + + return unless ($t_log == 1); + + my $fh = *STDOUT; + if (defined($log_file)) { + open($fh, ">>", $log_file) || die("Starting log failed: $!.\n"); + } + + print ($fh strftime ("%Y-%m-%d %H:%M:%S", localtime()) . "[log]$msg.\n"); + + close ($fh) if (defined($log_file)); + +} + +################################################################################ +## SUB print_log +## Print log messages. +################################################################################ +sub print_info($) { + + my ($msg) = @_; + + return unless ($t_log_hard == 1); + + my $fh = *STDOUT; + if (defined($log_file)) { + open($fh, ">>", $log_file) || die("Starting log failed: $!.\n"); + } + + print ($fh strftime ("%Y-%m-%d %H:%M:%S", localtime()) . "[info]$msg.\n"); + + close ($fh) if (defined($log_file)); + +} + +################################################################################ +## SUB error +## Print an error and exit the program. +################################################################################ +sub error { + + my ($msg) = @_; + + return unless ($t_quiet == 0); + + my $fh = *STDERR; + if (defined($log_file)) { + open($fh, ">>", $log_file) || die("$!\n"); + } + + print ($fh strftime ("%Y-%m-%d %H:%M:%S", localtime()) . "[err]$msg\n"); + + close ($fh) if (defined($log_file)); + + die("\n"); +} + +################################################################################ +## SUB move_file +## Send a file to the client and delete it +################################################################################ +sub move_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $response; + my $size; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("Requested file '$base_name' from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("MV ERR\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (! -f $file) { + print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " does not exist"); + send_data ("MV ERR\n"); + return; + } + + $size = -s $file; + send_data ("MV SIZE $size\n"); + + # Wait for client response + $response = recv_command ($t_block_size); + if ($response ne "MV OK") { + print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " not sent"); + return; + } + + # Send the file + open (FILE, $file) || error ("Cannot open file '$file' for reading."); + binmode (FILE); + + while ($data = ) { + send_data ($data); + } + + close (FILE); + unlink($file); + + print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " sent and deleted"); +} + +################################################################################ +## SUB send_file_list +## Send a list of files to the client after applying the given filter. +################################################################################ +sub send_file_list { + my $filter = $_[0]; + my $data = ''; + my $dir; + my $dh; + my $response; + my $size; + + # Check file name + if ($filter =~ /[$t_invalid_chars]/) { + print_log ("Invalid file listing filter '$filter' from " . $t_client_socket->sockhost ()); + send_data ("LS ERR\n"); + return; + } + + # Apply filters + $dir = "$t_directory/" . apply_filters ($filter); + + # Open the directory. + if (! opendir ($dh, $dir)) { + print_log ("Error opening directory $dir as requested from " . $t_client_socket->sockhost () . ": $!"); + send_data ("LS ERR\n"); + return; + } + + # List files. + while (my $file = readdir ($dh)) { + next if ($file =~ /[$t_invalid_chars]/); # Only list files valid for Tentacle. + $data .= "$file\n"; + } + closedir $dh; + + $size = length ($data); + send_data ("LS SIZE $size\n"); + + # Wait for client response + $response = recv_command ($t_block_size); + if ($response ne "LS OK") { + print_log ("Requested directory listing from " . $t_client_socket->sockhost () . " not sent"); + return; + } + + send_data ($data); + + print_log ("Requested directory listing from " . $t_client_socket->sockhost () . " sent"); +} + +################################################################################ +## SUB recv_data_proxy +## Recv data from proxy socket. +################################################################################ +sub recv_data_proxy { + my $data; + my $read; + my $retries = 0; + my $size = $_[0]; + + while (1) { + + # Try to read data from the socket + if ($t_proxy_select->can_read ($t_timeout)) { + + # Read at most $size bytes + $read = sysread ($t_proxy_socket, $data, $size); + + # Read error + if (! defined ($read)) { + error ("Read error from " . $t_proxy_socket->sockhost () . ": $!."); + } + + # EOF + if ($read == 0) { + error ("Connection from " . $t_proxy_socket->sockhost () . " unexpectedly closed."); + } + + return ($read, $data); + } + + # Retry + $retries++; + + # But check for error conditions first + if ($retries > $t_retries) { + error ("Connection from " . $t_proxy_socket->sockhost () . " timed out."); + } + } +} +################################################################################ +## SUB recv_data +## Read data from the client socket. Returns the number of bytes read and the +## string of bytes as a two element array. +################################################################################ +sub recv_data { + my $data; + my $read; + my $retries = 0; + my $size = $_[0]; + + while (1) { + + # Try to read data from the socket + if ($t_select->can_read ($t_timeout)) { + + # Read at most $size bytes + $read = sysread ($t_client_socket, $data, $size); + + # Read error + if (! defined ($read)) { + error ("Read error from " . $t_client_socket->sockhost () . ": $!."); + } + + # EOF + if ($read == 0) { + error ("Connection from " . $t_client_socket->sockhost () . " unexpectedly closed."); + } + + return ($read, $data); + } + + # Retry + $retries++; + + # But check for error conditions first + if ($retries > $t_retries) { + error ("Connection from " . $t_client_socket->sockhost () . " timed out."); + } + } +} + +################################################################################ +## SUB send_data +## Write data to the client socket. +################################################################################ +sub send_data { + my $data = $_[0]; + my $block_size; + my $retries = 0; + my $size; + my $total = 0; + my $written; + + $size = length ($data); + + while (1) { + + # Try to write data to the socket + if ($t_select->can_write ($t_timeout)) { + + $block_size = ($size - $total) > $t_block_size ? $t_block_size : ($size - $total); + $written = syswrite ($t_client_socket, $data, $block_size, $total); + + # Write error + if (! defined ($written)) { + error ("Connection error from " . $t_client_socket->sockhost () . ": $!."); + } + + # EOF + if ($written == 0) { + error ("Connection from " . $t_client_socket->sockhost () . " unexpectedly closed."); + } + + $total += $written; + + # Check if all data was written + if ($total == $size) { + return; + } + } + # Retry + else { + $retries++; + if ($retries > $t_retries) { + error ("Connection from " . $t_client_socket->sockhost () . " timed out."); + } + } + } +} + +################################################################################ +## SUB recv_command +## Read a command from the client, ended by a new line character. +################################################################################ +sub recv_command { + my $buffer; + my $char; + my $command = ''; + my $read; + my $total = 0; + + while (1) { + + ($read, $buffer) = recv_data ($t_block_size); + $command .= $buffer; + $total += $read; + + # Check if the command is complete + $char = chop ($command); + if ($char eq "\n") { + return $command; + } + + $command .= $char; + + # Avoid overflow + if ($total > $t_block_size) { + error ("Received too much data from " . $t_client_socket->sockhost () . "."); + } + } +} + +################################################################################ +## SUB recv_data_block +## Read $_[0] bytes of data from the client. +################################################################################ +sub recv_data_block { + my $buffer = ''; + my $data = ''; + my $read; + my $size = $_[0]; + my $total = 0; + + while (1) { + + ($read, $buffer) = recv_data ($size - $total); + $data .= $buffer; + $total += $read; + + # Check if all data has been read + if ($total == $size) { + return $data; + } + } +} + +################################################################################ +## SUB ask_passwd +## Asks the user for a password. +################################################################################ +sub ask_passwd { + my $msg1 = $_[0]; + my $msg2 = $_[1]; + my $pwd1; + my $pwd2; + + require Term::ReadKey; + + # Disable keyboard echo + Term::ReadKey::ReadMode('noecho'); + + # Promt for password + print ($msg1); + $pwd1 = Term::ReadKey::ReadLine(0); + print ("\n$msg2"); + $pwd2 = Term::ReadKey::ReadLine(0); + print ("\n"); + + # Restore original settings + Term::ReadKey::ReadMode('restore'); + + if ($pwd1 ne $pwd2) { + print ("Error: passwords do not match.\n"); + exit 1; + } + + # Remove the trailing new line character + chop $pwd1; + + return $pwd1; +} + +################################################################################ +## SUB apply_filters +## Applies filters to the given file. +################################################################################ +sub apply_filters ($) { + my ($file_name) = @_; + + foreach my $filter (@t_filters) { + my ($regexp, $dir) = @{$filter}; + if ($file_name =~ /$regexp/) { + print_log ("File '$file_name' matches filter '$regexp' (changing to directory '$dir')"); + return $dir . '/'; + } + } + + return ''; +} + +################################################################################ +## SUB install_service +## Install the Windows service. +################################################################################ +sub install_service() { + + my $service_path = $0; + my $service_params = $SERVICE_PARAMS; + + # Change the service parameter from 'install' to 'run'. + $service_params =~ s/\-S\s+\S+/\-S run/; + + my %service_hash = ( + machine => '', + name => 'TENTACLESRV', + display => $SERVICE_NAME, + path => $service_path, + user => '', + pwd => '', + description => 'Tentacle Server http://sourceforge.net/projects/tentacled/', + parameters => $service_params + ); + + if (Win32::Daemon::CreateService(\%service_hash)) { + print "Successfully added.\n"; + exit 0; + } else { + print "Failed to add service: " . Win32::FormatMessage(Win32::Daemon::GetLastError()) . "\n"; + exit 1; + } +} + +################################################################################ +## SUB uninstall_service +## Install the Windows service. +################################################################################ +sub uninstall_service() { + if (Win32::Daemon::DeleteService('', 'TENTACLESRV')) { + print "Successfully deleted.\n"; + exit 0; + } else { + print "Failed to delete service: " . Win32::FormatMessage(Win32::Daemon::GetLastError()) . "\n"; + exit 1; + } +} + +################################################################################ +## SUB callback_running +## Windows service callback function for the running event. +################################################################################ +sub callback_running { + + if (Win32::Daemon::State() == WIN32_SERVICE_RUNNING) { + } +} + +################################################################################ +## SUB callback_start +## Windows service callback function for the start event. +################################################################################ +sub callback_start { + + # Accept_connections (); + my $thr = threads->create(\&accept_connections); + if (!defined($thr)) { + Win32::Daemon::State(WIN32_SERVICE_STOPPED); + Win32::Daemon::StopService(); + return; + } + $thr->detach(); + + Win32::Daemon::State(WIN32_SERVICE_RUNNING); +} + +################################################################################ +## SUB callback_stop +## Windows service callback function for the stop event. +################################################################################ +sub callback_stop { + + foreach my $t_server_socket (@t_server_sockets) { + $t_server_socket->shutdown (2); + $t_server_socket->close (); + } + + Win32::Daemon::State(WIN32_SERVICE_STOPPED); + Win32::Daemon::StopService(); +} + +################################################################################ +# Main +################################################################################ + +# Never run as root +if ($> == 0 && $^O ne 'MSWin32') { + print ("Error: for safety reasons $0 cannot be run with root privileges.\n"); + exit 1; +} + +# Parse command line options +parse_options (); + +# Check command line arguments +if ($#ARGV != -1) { + print_help (); + exit 1; +} + +# Show IPv6 status +if ($SOCKET_MODULE eq 'IO::Socket::INET') { + print_log ("IO::Socket::INET6 is not found. IPv6 is disabled."); +} + +# Run as daemon? +if ($t_daemon == 1 && $^O ne 'MSWin32') { + daemonize (); +} + +# Handle ctr-c +if ($^O eq 'MSWin32') { + no warnings; + $SIG{INT2} = \&stop_server; + use warnings; +} +else { + $SIG{INT} = \&stop_server; +} + +# Accept connections +accept_connections(); + +__END__ + +=head1 REQUIRED ARGUMENTES + +=over + +=item B<< -s F >> Root directory to store the files received by the server + +=back + +=head1 OPTIONS + +=over + +=item I<-a ip_address> Address to B on (default I<0.0.0.0>). + +=item I<-c number> B number of simultaneous B (default I<10>). + +=item I<-d> Run as B. + +=item I<-e cert> B file. Enables SSL. + +=item I<-f ca_cert> Verify that the peer certificate is signed by a B. + +=item I<-h> Show B. + +=item I<-i> B. + +=item I<-k key> B file. + +=item I<-m size> B in bytes (default I<2000000b>). + +=item I<-o> Enable file B. + +=item I<-p port> B on (default I<41121>). + +=item I<-q> B. Do now print error messages. + +=item I<-r number> B for network opertions (default I<3>). + +=item I<-t time> B for network operations in B (default I<1s>). + +=item I<-v> Be B. + +=item I<-w> Prompt for B. + +=item I<-x> pwd B. + +=back + +=head1 EXIT STATUS + +=over + +=item 0 on Success + +=item 1 on Error + +=back + +=head1 CONFIGURATION + +Tentacle doesn't use any configurationf files, all the configuration is done by the options passed when it's started. + +=head1 DEPENDENCIES + +L, L, L, L, L + + +=head1 LICENSE + +This is released under the GNU Lesser General Public License. + +=head1 SEE ALSO + +L, L, L, L, L + +Protocol description and more info at: L<< http://openideas.info/wiki/index.php?title=Tentacle >> + +=head1 COPYRIGHT + +Copyright (c) 2005-2010 Artica Soluciones Tecnologicas S.L + +=cut + diff --git a/pandora_server/bin/tentacle_server b/pandora_server/bin/tentacle_server index d6b5d4d3bd..8b27ed35b3 100755 --- a/pandora_server/bin/tentacle_server +++ b/pandora_server/bin/tentacle_server @@ -60,6 +60,8 @@ use strict; use warnings; use Getopt::Std; use IO::Select; +use IO::Compress::Zip qw(zip $ZipError); +use IO::Uncompress::Unzip qw(unzip $UnzipError); use threads; use Thread::Semaphore; use POSIX ":sys_wait_h"; @@ -959,6 +961,15 @@ sub serve_connection { print_info ("Request to receive file '$1' from " . $t_client_socket->sockhost ()); send_file ($1); } + elsif ($command =~ /^ZSEND <(.*)> SIZE (\d+)$/) { + print_info ("Request to send compressed file '$1' size ${2}b from " . $t_client_socket->sockhost ()); + zrecv_file ($1, $2); + } + # Client wants to receive a file + elsif ($command =~ /^ZRECV <(.*)>$/) { + print_info ("Request to receive compressed file '$1' from " . $t_client_socket->sockhost ()); + zsend_file ($1); + } # Quit elsif ($command =~ /^QUIT$/) { print_info ("Connection closed from " . $t_client_socket->sockhost ()); @@ -1070,6 +1081,61 @@ sub recv_file { print_info ("Received file '$base_name' size ${size}b from " . $t_client_socket->sockhost ()); } +################################################################################ +## SUB zrecv_file +## Receive a compressed file of size $_[1] and save it in $t_directory as $_[0]. +################################################################################ +sub zrecv_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $size = $_[1]; + my $zdata = ''; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("ZSEND ERR (invalid file name)\n"); + return; + } + + # Check file size, empty files are not allowed + if ($size < 1 || $size > $t_max_size) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " is too big"); + send_data ("ZSEND ERR (file is too big)\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (-f $file && $t_overwrite == 0) { + print_log ("File '$base_name' size ${size}b from " . $t_client_socket->sockhost () . " already exists"); + send_data ("ZSEND ERR (file already exists)\n"); + return; + } + + send_data ("ZSEND OK\n"); + + # Receive file + $zdata = recv_data_block ($size); + if (!unzip(\$zdata => \$data)) { + print_log ("Uncompress error: $UnzipError"); + send_data ("ZSEND ERR\n"); + return; + } + + # Write it to disk + open (FILE, "> $file") || error ("Cannot open file '$file' for writing."); + binmode (FILE); + print (FILE $data); + close (FILE); + + send_data ("ZSEND OK\n"); + print_info ("Received compressed file '$base_name' size ${size}b from " . $t_client_socket->sockhost ()); +} + ################################################################################ ## SUB send_file ## Send a file to the client @@ -1122,6 +1188,57 @@ sub send_file { print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " sent"); } +################################################################################ +## SUB zsend_file +## Send a file to the client +################################################################################ +sub zsend_file { + my $base_name = $_[0]; + my $data = ''; + my $file; + my $response; + my $size; + + # Check file name + if ($base_name =~ /[$t_invalid_chars]/) { + print_log ("Requested compressed file '$base_name' from " . $t_client_socket->sockhost () . " has an invalid file name"); + send_data ("ZRECV ERR (file has an invalid file name)\n"); + return; + } + + # Apply filters + $file = "$t_directory/" . apply_filters ($base_name) . $base_name; + + # Check if file exists + if (! -f $file) { + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " does not exist"); + send_data ("ZRECV ERR (file does not exist)\n"); + return; + } + + # Read the file and compress its contents + if (! zip($file => \$data)) { + send_data ("QUIT\n"); + error ("Compression error: $ZipError"); + return; + } + + $size = length($data); + send_data ("ZRECV SIZE $size\n"); + + # Wait for client response + $response = recv_command ($t_block_size); + if ($response ne "ZRECV OK") { + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " not sent"); + return; + } + + # Send the file + send_data ($data); + + print_log ("Requested compressed '$file' from " . $t_client_socket->sockhost () . " sent"); +} + ################################################################################ # Common functions ################################################################################ diff --git a/pandora_server/bin/tentacle_server.exe b/pandora_server/bin/tentacle_server.exe index a1b10c214f..95e1e5325e 100644 Binary files a/pandora_server/bin/tentacle_server.exe and b/pandora_server/bin/tentacle_server.exe differ diff --git a/pandora_server/lib/PandoraFMS/Config.pm b/pandora_server/lib/PandoraFMS/Config.pm index 42b26333c5..4516c6fa07 100644 --- a/pandora_server/lib/PandoraFMS/Config.pm +++ b/pandora_server/lib/PandoraFMS/Config.pm @@ -45,7 +45,7 @@ our @EXPORT = qw( # version: Defines actual version of Pandora Server for this module only my $pandora_version = "7.0NG.724"; -my $pandora_build = "180628"; +my $pandora_build = "180709"; our $VERSION = $pandora_version." ".$pandora_build; # Setup hash diff --git a/pandora_server/lib/PandoraFMS/PluginTools.pm b/pandora_server/lib/PandoraFMS/PluginTools.pm index e417754133..f84f5c25c4 100644 --- a/pandora_server/lib/PandoraFMS/PluginTools.pm +++ b/pandora_server/lib/PandoraFMS/PluginTools.pm @@ -32,7 +32,7 @@ our @ISA = qw(Exporter); # version: Defines actual version of Pandora Server for this module only my $pandora_version = "7.0NG.724"; -my $pandora_build = "180628"; +my $pandora_build = "180709"; our $VERSION = $pandora_version." ".$pandora_build; our %EXPORT_TAGS = ( 'all' => [ qw() ] ); diff --git a/pandora_server/pandora_server.redhat.spec b/pandora_server/pandora_server.redhat.spec index 16ae08e2a2..78b6681285 100644 --- a/pandora_server/pandora_server.redhat.spec +++ b/pandora_server/pandora_server.redhat.spec @@ -3,7 +3,7 @@ # %define name pandorafms_server %define version 7.0NG.724 -%define release 180628 +%define release 180709 Summary: Pandora FMS Server Name: %{name} diff --git a/pandora_server/pandora_server.spec b/pandora_server/pandora_server.spec index 75df7f0fe9..ccac565bc1 100644 --- a/pandora_server/pandora_server.spec +++ b/pandora_server/pandora_server.spec @@ -3,7 +3,7 @@ # %define name pandorafms_server %define version 7.0NG.724 -%define release 180628 +%define release 180709 Summary: Pandora FMS Server Name: %{name} diff --git a/pandora_server/pandora_server_installer b/pandora_server/pandora_server_installer index c9359b3288..c02ef4fdc6 100755 --- a/pandora_server/pandora_server_installer +++ b/pandora_server/pandora_server_installer @@ -9,7 +9,7 @@ # ********************************************************************** PI_VERSION="7.0NG.724" -PI_BUILD="180628" +PI_BUILD="180709" MODE=$1 if [ $# -gt 1 ]; then diff --git a/pandora_server/util/pandora_db.pl b/pandora_server/util/pandora_db.pl index c2f4349c87..03ef6b4a64 100644 --- a/pandora_server/util/pandora_db.pl +++ b/pandora_server/util/pandora_db.pl @@ -34,7 +34,7 @@ use PandoraFMS::Config; use PandoraFMS::DB; # version: define current version -my $version = "7.0NG.724 PS180628"; +my $version = "7.0NG.724 PS180709"; # Pandora server configuration my %conf; diff --git a/pandora_server/util/pandora_manage.pl b/pandora_server/util/pandora_manage.pl index e745159cb7..cffeebc54d 100644 --- a/pandora_server/util/pandora_manage.pl +++ b/pandora_server/util/pandora_manage.pl @@ -36,7 +36,7 @@ use Encode::Locale; Encode::Locale::decode_argv; # version: define current version -my $version = "7.0NG.724 PS180628"; +my $version = "7.0NG.724 PS180709"; # save program name for logging my $progname = basename($0);