From 38c207227cb855f0d88038dc087cb6a6993cacd1 Mon Sep 17 00:00:00 2001 From: Rafael Ameijeiras Date: Mon, 13 Jun 2022 17:41:49 +0200 Subject: [PATCH 1/2] ent-8673-13219-r-works-instalador-de-tentacle_server --- tentacle/.gitignore | 4 + tentacle/Makefile.PL | 22 + tentacle/NetBSD/tentacle_server | 1869 ++++++++++++++++++++++++ tentacle/build_tentacle_server.sh | 26 + tentacle/conf/tentacle_server.conf.new | 75 + tentacle/man/man1/tentacle_server.1.gz | Bin 0 -> 1309 bytes tentacle/tentacle_server_installer | 438 ++++++ tentacle/util/tentacle_serverd | 185 +++ tentacle/util/tentacle_serverd.service | 14 + 9 files changed, 2633 insertions(+) create mode 100644 tentacle/.gitignore create mode 100644 tentacle/Makefile.PL create mode 100755 tentacle/NetBSD/tentacle_server create mode 100755 tentacle/build_tentacle_server.sh create mode 100644 tentacle/conf/tentacle_server.conf.new create mode 100644 tentacle/man/man1/tentacle_server.1.gz create mode 100755 tentacle/tentacle_server_installer create mode 100755 tentacle/util/tentacle_serverd create mode 100644 tentacle/util/tentacle_serverd.service diff --git a/tentacle/.gitignore b/tentacle/.gitignore new file mode 100644 index 0000000000..c39a713dcc --- /dev/null +++ b/tentacle/.gitignore @@ -0,0 +1,4 @@ +dist/ +blib/ +Makefile +pm_to_blib \ No newline at end of file diff --git a/tentacle/Makefile.PL b/tentacle/Makefile.PL new file mode 100644 index 0000000000..cc444b8524 --- /dev/null +++ b/tentacle/Makefile.PL @@ -0,0 +1,22 @@ +use 5.000; +use ExtUtils::MakeMaker; + +my %ARGV = map { my @r = split /=/,$_; defined $r[1] or $r[1]=1; @r } @ARGV; +my @exe_files = qw(tentacle_server); + +WriteMakefile( + INSTALLSITELIB => '/usr/lib/perl5', + (($^O eq 'freebsd') + ? (INSTALLSITELIB => '') : () + ), + ($^O eq 'netbsd') ? ( + INSTALLSITELIB => '', + INSTALLSITESCRIPT => '/usr/local/bin', + ) :(), + + NAME => 'PandoraFMS', + AUTHOR => 'Artica ST ', + EXE_FILES => [ @exe_files ], + PMLIBDIRS => [ 'lib' ], + 'dist' => { 'TAR' => 'tar', 'TARFLAGS' => 'cvfz', 'SUFFIX' => '.gz', 'COMPRESS' => 'gzip'} +); diff --git a/tentacle/NetBSD/tentacle_server b/tentacle/NetBSD/tentacle_server new file mode 100755 index 0000000000..d945a134e8 --- /dev/null +++ b/tentacle/NetBSD/tentacle_server @@ -0,0 +1,1869 @@ +#!/usr/bin/perl +########################################################################## +# Tentacle Server +# See https://pandorafms.com/docs/ 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 +# +# tentacle_server.pl Tentacle Server. See https://pandorafms.com/docs/ 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. +########################################################################## + +package tentacle::server; +=head1 NAME + +tentacle_server - Tentacle Server + +=head1 VERSION + +Version 0.6.1 + +=head1 USAGE + +tentacle_server B<< -s F >> [I] + +=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 ) ); +} + +# 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 $@; +} + +# 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 https://pandorafms.com/docs/ 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<< https://pandorafms.com/docs/index.php?title=Pandora:Documentation_en:Tentacle >> + +=head1 COPYRIGHT + +Copyright (c) 2005-2010 Artica Soluciones Tecnologicas S.L + +=cut + diff --git a/tentacle/build_tentacle_server.sh b/tentacle/build_tentacle_server.sh new file mode 100755 index 0000000000..9f833edc99 --- /dev/null +++ b/tentacle/build_tentacle_server.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +CODEHOME=$HOME/code/pandorafms + +# Add build string for nightly builds +if [ "$1" == "nightly" ]; then + LOCAL_VERSION="$VERSION-$BUILD" +else + LOCAL_VERSION=$1 +fi + +if [ ! -d $CODEHOME/tentacle/dist ]; then + mkdir -p $CODEHOME/tentacle/dist || exit 1 +fi + +echo "Creating source tarballs in $(pwd)/dist" + +# Server +cd $CODEHOME && tar zcvf $CODEHOME/tentacle/dist/tentacle_server-$LOCAL_VERSION.tar.gz \ +--exclude \.svn \ +--exclude \.exe \ +--exclude dist \ +--exclude build_tentacle_server.sh \ +tentacle || exit 1 + +exit 0 \ No newline at end of file diff --git a/tentacle/conf/tentacle_server.conf.new b/tentacle/conf/tentacle_server.conf.new new file mode 100644 index 0000000000..1acf082ca2 --- /dev/null +++ b/tentacle/conf/tentacle_server.conf.new @@ -0,0 +1,75 @@ +########################################################################## +# Tentacle Server Parameters +# See https://pandorafms.com/manual/en/documentation/08_technical_reference/09_tentacle +# for protocol description. +# Tentacle have IANA assigned port tpc/41121 as official port. +########################################################################## + +# [-a] IPv4 address to listen on. Several IPs cam be selected separating if by comma. +addresses 0.0.0.0 + +# [-p] Port to listen on +port 41121 + +# [-c] Maximum number of simultaneous connections +# max_connections 10 + +# [-d] Run as daemon. 1 true, 0 false +daemon 1 + +# [-i] Enable insecure mode +# insecure 0 + +# Filters (regexp:dir;regexp:dir...) +filters .*\.conf:conf;.*\.md5:md5;.*\.zip:collections;.*\.lock:trans;.*\.rcmd:commands + +# [-m] Maximum file size allowed by the server in bytes +#max_size 2000000 + +# [-o] Accept files with a repeated name +# overwrite 0 + +# [-q] Do not output error messages. +# quiet 0 + +# [-r] Number of retries for socket read/write operations +# retries 3 + +# [-s] Storage directory +directory /var/spool/pandora/data_in + +# [-b] Address to proxy client requests to +# proxy_ip 127.0.0.1 + +# [-g] Port to proxy client requests to +# proxy_port 41121 + +# [-t] Timeout for socket read/write operations in seconds +# timeout 1 + +# [-v and -V] Verbose level +# 0: Do not display any informative messages +# 1: Display only important messages [-v] +# 2: Display all messages [-V] +# verbose 0 + +# [-l] Log file +log_file /dev/null + +# [-x] Server password +# password PASSWORD + +# [-e] SSL certificate file full path +# ssl_cert /path/to/ssl/cert + +# [-f] SSL CA file full path +# ssl_ca /path/to/ssl/ca + +# [-k] SSL private key file +# ssl_key /path/to/private/key/file + +# [-w] SSL password. Set to 1 to ask for password by command line +# ssl_password 0 + +# [-T] Use libwrap library (Authen::Libwrap perl module) +# use_libwrap 0 \ No newline at end of file diff --git a/tentacle/man/man1/tentacle_server.1.gz b/tentacle/man/man1/tentacle_server.1.gz new file mode 100644 index 0000000000000000000000000000000000000000..eea67284be92f11ac83639c5c5fb9648e2629014 GIT binary patch literal 1309 zcmV+&1>*W2iwFqFhU!ZK19W9>bYWv`WnXh;a&~2ME-?U=R&7rkM-cvgzhcCf+EF-c z8rn3issM2mBwk$3N)rhQtux&M?xpgH|m{t@lO<_TeD~wyu?# z#lo}vh@OCFf7vEzYy964_MJGI@)^VQZ8ujY)z^~ z{wC;BNt#r1xll&?dJUf&xR$R;o4`(?6j8Cg(I`le3Uuw3?<3h?@`J%2RI&?tE^zE~ zDpEF$@_uFA>}fPBjZ{E6g~H0#q2$sU!;vyV(|y}C)+H)zPM`^7Cmp0pk`{Usupox? zW;om*_DO-P8GC(W5{sBG*P2wZ^lMv}(s=0>EE6MZ8TiuaCM3p;mBi-yrgbTE;n&A7 zh8aT0w>pTtja|h!GoGuq3QxpZb6D880cZ?#>lA%FD`KD{HRXy%!gk}d=O?(4&Kd_u z7GfH+tD@WCd2vF=)caQhZUuc-DgYi`)TXRjB-jViSKFdeLohLwRe(q=-f&3z(ng$8 zg%~5Lz|PT3ttu*Pi-m>vSVk+JdL1z1AvEvy_BK;J%YA1g%Gr~a$z)%|p)T<7Y1|dG zUP9*sJG#GkjGyXxoXK5^8#mO*tUL4e7?gSZbQxhatS42NN0*`Yr0n)qlx}gtc!+?D0^!n zq7HJjS2m9q-FNQuFRaCfY6 z{83ksm+ua5V`$9rI+G@5CX<~ajb+APZhxBm&h;D2|7kUcoIcn%kf;Z1gPpT7g5Kj=-6Fv~ z6U+I7NYeiFabh8Btg>6ERvPh0DZg%o4-qctdd<-U|Lxi~Ji|~41dL$?T3kC@%JW-* zOMo0PfupJQTNJH?7VNDiLBG_z&HC+}?h>!7SxRm_h@D-U+R>N8v+?N&clu-u!;`1i z_Z7Yz)O3XjdGq$*_uojKJwEqB-Nc7wR=jW>#jB9&XQJ^OdpkTk`cc3;AV1tEKNazN zw@}a6u)cr#KNXqrDdU(oT*rf3e1h literal 0 HcmV?d00001 diff --git a/tentacle/tentacle_server_installer b/tentacle/tentacle_server_installer new file mode 100755 index 0000000000..469ac326c3 --- /dev/null +++ b/tentacle/tentacle_server_installer @@ -0,0 +1,438 @@ +#!/bin/sh + +# Tentacle Server Installer +# Linux/FreeBSD Version (generic), for SuSe, Debian/Ubuntu and FreeBSD only +# other Linux distros could not work properly without modifications +# Please see http://www.pandorafms.org +# v1.0 Build 13062022 +# This code is licensed under GPL 2.0 license. +# ********************************************************************** + +PI_VERSION="7.0NG.762" +PI_BUILD="220613" + +MODE=$1 +if [ $# -gt 1 ]; then + shift +fi + +# Defaults +PREFIX=/usr +PANDORA_SPOOL=/var/spool/pandora +TENTACLE_SERVER=/etc/init.d/tentacle_serverd +TENTACLE_CFG_DIR=/etc/tentacle +TENTACLE_CFG_FILE=$TENTACLE_CFG_DIR/tentacle_server.conf +TENTACLE_CFG_FILE_DIST=conf/tentacle_server.conf.new +TENTACLE_INIT_SCRIPT=util/tentacle_serverd +PERL=perl +MANDIR=$PREFIX/share/man/man1 +INITDIR=/etc/init.d +WITHOUT_TENTACLE=0 + +# +# set_global_vars +# Check platform and set DISTRO, OS_VERSION and LINUX. +# Also, define some platform sepcific variables (e.g. PANDORA_RC_VAR for (Free|Net)BSD) +# and override some of defaults defined above if needed. +# +set_global_vars () { + # Default + LINUX=NO + OS_VERSION=`uname -r` + DISTRO=`uname -s` + + # set correct value for LINUX_DISTRO + case $DISTRO in + Linux) + # Default for Linux + LINUX=YES + DISTRO="GENERIC" + # Get Linux Distro type and version + # We assume we are on Linux unless told otherwise + if [ -f "/etc/SuSE-release" ] + then + OS_VERSION=`cat /etc/SuSE-release | grep VERSION | cut -f 3 -d " "` + DISTRO=SUSE + elif [ -f "/etc/lsb-release" ] && [ ! -f "/etc/redhat-release" ] + then + OS_VERSION=`cat /etc/lsb-release | grep DISTRIB_RELEASE | cut -f 2 -d "="` + DISTRO=UBUNTU + OS_VERSION="UBUNTU $OS_VERSION" + elif [ -f "/etc/debian_version" ] + then + OS_VERSION=`cat /etc/debian_version` + OS_VERSION="DEBIAN $OS_VERSION" + DISTRO=DEBIAN + elif [ -f "/etc/fedora-release" ] + then + OS_VERSION=`cat /etc/fedora-release | cut -f 4 -d " "` + OS_VERSION="FEDORA $OS_VERSION" + DISTRO=FEDORA + fi + ;; + Darwin|AIX) + # For future reference, Darwin doesn't have /etc/init.d but uses LaunchDaemons. + # AIX doesn't have /etc/init.d + ;; + SunOS) + # Some Solaris and other Unices don't have /etc/init.d, some have /usr/spool instead of /var/spool + DISTRO="Solaris" + ;; + FreeBSD) + PREFIX=/usr/local + TENTACLE_SERVER=$PREFIX/etc/rc.d/tentacle_server + TENTACLE_CFG_DIR=$PREFIX/etc/tentacle + TENTACLE_CFG_FILE=$TENTACLE_CFG_DIR/tentacle_server.conf + TENTACLE_INIT_SCRIPT=$DISTRO/tentacle_server + MANDIR=$PREFIX/man/man1 + INITDIR=$PREFIX/etc/rc.d + PERL=/usr/local/bin/perl + TENTACLE_RC_VAR="tentacle_server_enable" + ;; + NetBSD) + PREFIX=/usr/local + TENTACLE_CFG_DIR=$PREFIX/etc/tentacle + TENTACLE_CFG_FILE=$TENTACLE_CFG_DIR/tentacle_server.conf + TENTACLE_SERVER=/etc/rc.d/tentacle_server + TENTACLE_INIT_SCRIPT=$DISTRO/tentacle_server + PERL=/usr/pkg/bin/perl + INITDIR=/etc/rc.d + PANDORA_RC_VAR="pandora_server" + TENTACLE_RC_VAR="tentacle_server" + ;; + esac +} + +# +# install_startup_script [options...] SRC +# copy SRC into the $INITDIR and do additional required operation according to $DISTRO +# if $INITDIR is not set or empty, do nothing. +# If $DESTDIR is set, skip enabling service +# OPTIONS: +# -s SPRIO specify startup priority for service +# +install_startup_script () { + [ "$INITDIR" ] || return 1 + if [ "$1" = "-s" ] + then + SPRIO=$2 + shift;shift + fi + SRC=$1 + SCRIPT_NAME=`basename $SRC` + + echo "Copying the daemon script into $INITDIR" + [ -d $DESTDIR$INITDIR ] || mkdir -p $DESTDIR$INITDIR + cp $SRC $DESTDIR$INITDIR + + [ "$DESTDIR" ] && return + + case $DISTRO in + UBUNTU|DEBIAN) + echo "Linking startup script to /etc/rc2.d" + update-rc.d $SCRIPT_NAME defaults + ;; + SUSE) + echo "Creating startup daemons" + insserv $SCRIPT_NAME + ;; + FeeBSD|NetBSD) + chmod 555 $DESTDIR$INITDIR/$SCRIPT_NAME + ;; + *) + if [ "$LINUX" = YES ] + then + # Pandora FMS Server install (Other Distros) + INITLV=`grep '[0-9]:initdefault' /etc/inittab | cut -f 2 -d ':'` + : ${INITLV:=2} + echo "Linking startup script to /etc/rc.d/rc$INITLV.d/S$SPRIO$SCRIPT_NAME" + ln -s $INITDIR/$SCRIPT_NAME /etc/rc.d/rc$INITLV.d/S$SPRIO$SCRIPT_NAME + fi + ;; + esac +} + +install () { + set_global_vars + + FORCE=0 + + # parse options + while : + do + case $1 in + --force) FORCE=1;; + --destdir) DESTDIR=$2;shift;; + *) break;; + esac + shift + done + + if [ "$LINUX" = YES ] + then + echo "$DISTRO distribution detected" + else + echo "$DISTRO detected" + fi + + $PERL -v &> /dev/null + if [ $? != 0 ]; then + echo ' ' + echo 'Error, no PERL Interpeter found, please install perl on your system' + exit 1 + fi + + #Check dependenciaes. + if [ $FORCE -eq 0 ]; then + # Execute tools check + execute_cmd "ps --version" 'Checking dependencies: ps' "Error ps not found, please install procps" + execute_cmd "sudo --version" 'Checking dependencies: sudo' "Error sudo not found, please install sudo" + fi + + # install tentacle + $PERL Makefile.PL INSTALLMAN1DIR=none > output 2>&1 + #&& sleep 2 && cat output | grep "found" | wc -l + DEPENDENCIAS=`cat output | grep "found" | wc -l` + + if [ $DEPENDENCIAS -gt 0 ] && [ $FORCE -eq 0 ] + then + echo "You are missing the following dependencies" + echo " " + cat output | awk -F ": prerequisite" '{print $2}' | awk -F " " '{print $1}' + echo "The complete installation guide is at: https://pandorafms.com/docs/" + echo " " + rm output + exit 1 + fi + + echo "Installing binaries and libraries" + make + make DESTDIR=$DESTDIR install + + echo "Checking binaries at /usr/local/bin -> /usr/bin" + if [ ! -f "$DESTDIR/usr/bin/tentacle_server" ] + then + if [ ! -f "$DESTDIR/usr/local/bin/tentacle_server" ] + then + echo "ERROR compiling Tentacle Server from sources. Aborting" + exit 1 + fi + if [ "$DISTRO" != "FreeBSD" ] && [ "$DISTRO" != "NetBSD" ] + then + [ -d $DESTDIR$PREFIX/bin ] || mkdir -p $DESTDIR$PREFIX/bin + ln -s /usr/local/bin/tentacle_server $DESTDIR$PREFIX/bin + + fi + fi + + + echo "Creating common Pandora FMS directories" + id pandora 2> /dev/null + if [ $? -eq 0 ]; then + echo " " + echo "User pandora does exist, make sure the SSH directories are correct" + elif [ "$DESTDIR" ] + then + # chown can fail with fakeroot installation + echo "User 'pandora' does not exist. All chown operations may fail." + echo "You should manualy set proper ownership to $DESTDIR$PANDORA_SPOOL if needed." + echo + else + echo "Are you sure we can create a standard 'pandora' user locally? [y/N]" + read AREYOUSURE + if [ "$AREYOUSURE" = "y" ]; then + if [ "$DISTRO" = "FreeBSD" ] + then + echo "pandora:41121:::::Pandora FMS:/home/pandora:/usr/sbin/nologin:" | adduser -f - -w no 2> /dev/null + else + useradd pandora + mkdir /home/pandora 2> /dev/null + mkdir /home/pandora/.ssh 2> /dev/null + chown -R pandora /home/pandora + fi + else + echo "Please create the 'pandora' user manually according to your authentication scheme, then start again the installation" + echo "Aborting..." + exit 1 + fi + fi + + mkdir -p $DESTDIR$PANDORA_SPOOL/data_in 2> /dev/null + chmod 2770 $DESTDIR$PANDORA_SPOOL/data_in + mkdir $DESTDIR$PANDORA_SPOOL/data_in/conf 2> /dev/null + chmod 2770 $DESTDIR$PANDORA_SPOOL/data_in/conf + mkdir $DESTDIR$PANDORA_SPOOL/data_in/md5 2> /dev/null + chmod 2770 $DESTDIR$PANDORA_SPOOL/data_in/md5 + mkdir $DESTDIR$PANDORA_SPOOL/data_in/collections 2> /dev/null + chmod 2770 $DESTDIR$PANDORA_SPOOL/data_in/collections + mkdir $DESTDIR$PANDORA_SPOOL/data_in/netflow 2> /dev/null + chmod 2770 $DESTDIR$PANDORA_SPOOL/data_in/netflow + mkdir $DESTDIR$PANDORA_SPOOL/data_in/trans 2> /dev/null + chmod 2770 $DESTDIR$PANDORA_SPOOL/data_in/trans + mkdir $DESTDIR$PANDORA_SPOOL/data_in/commands 2> /dev/null + chmod 2770 $DESTDIR$PANDORA_SPOOL/data_in/commands + + + echo "Giving proper permission to /var/spool/pandora" + for group in "www-data" wwwrun www apache + do + IDGROUP=`id -g "$group" 2> /dev/null` + if [ $? -eq 0 ] + then + GROUPNAME=`grep ":$IDGROUP:" /etc/group | awk -F":" '{print $1}'` + break + fi + done + if [ -z "$GROUPNAME" ] + then + echo "No web server user found, some functionality might not perform correctly" + GROUPNAME=0 + fi + # when fakeroot installation, this can fail + chown -R pandora:$GROUPNAME $DESTDIR$PANDORA_SPOOL 2>/dev/null + + chown apache:$GROUPNAME $DESTDIR$PANDORA_SPOOL/data_in/customer_key 2>/dev/null + + # install tentacle_server + install_startup_script -s 80 $TENTACLE_INIT_SCRIPT + + # Create the directory to locate the Tentacle configuration file + echo "Creating setup Tentacle directory in $TENTACLE_CFG_DIR" + mkdir -p $DESTDIR$TENTACLE_CFG_DIR 2> /dev/null + if [ -f "$DESTDIR$TENTACLE_CFG_FILE" ] + then + echo cp $TENTACLE_CFG_FILE_DIST $DESTDIR$TENTACLE_CFG_DIR + cp $TENTACLE_CFG_FILE_DIST $DESTDIR$TENTACLE_CFG_DIR + else + echo cp $TENTACLE_CFG_FILE_DIST $DESTDIR$TENTACLE_CFG_FILE + cp $TENTACLE_CFG_FILE_DIST $DESTDIR$TENTACLE_CFG_FILE + chmod 774 $DESTDIR$TENTACLE_CFG_FILE + fi + + echo "Installing Tentacle Server manual" + [ -d $DESTDIR$MANDIR ] || mkdir -p $DESTDIR$MANDIR + cp man/man1/tentacle_server.1.gz $DESTDIR$MANDIR + + # Fix util paths + sed -i -e "s|directory.*|directory $DESTDIR$PANDORA_SPOOL/data_in|g" $DESTDIR$TENTACLE_CFG_FILE + sed -i -e "s|TENTACLE_PATH=\"/usr/bin\"|TENTACLE_PATH=$DESTDIR$PREFIX/bin|g" $DESTDIR$TENTACLE_SERVER + sed -i -e "s|^TENTACLE_CONFIG_FILE=.*|TENTACLE_CONFIG_FILE=$DESTDIR$TENTACLE_CFG_FILE|g" $DESTDIR$TENTACLE_SERVER + + echo "Tentacle Server installed" + + +} + +uninstall () { + set_global_vars + + # parse options + while : + do + case $1 in + --destdir) DESTDIR=$2;shift;; + *) break;; + esac + shift + done + + if [ "$LINUX" != "YES" ] && [ "$DISTRO" != "FreeBSD" ] && [ "$DISTRO" != "NetBSD" ] + then + echo "This is not a Linux-based distro. Uninstaller is currently not working for your OS" + exit 1 + fi + + + echo "Removing Tentacle Server" + if [ -d $DESTDIR$PANDORA_SPOOL/data_out ]; then + rm -Rf $DESTDIR$PANDORA_SPOOL/data_in + else + rm -Rf $DESTDIR$PANDORA_SPOOL + fi + + echo "If the user Pandora is not being used for any other operations, please delete using the following commands:" + if [ "$DISTRO" != "FreeBSD" ] || [ "$DISTRO" != "NetBSD" ] + then + echo " rmuser pandora" + else + echo " userdel pandora" + echo " rm -Rf /home/pandora/" + fi + + ## Just to clarify here. Some people (like me) are using the pandora user + ## for other purposes and/or using an LDAP-based user management + ## I would hate to have a script clear out this users' information without any notification + + + rm -f $DESTDIR$TENTACLE_CFG_FILE 2> /dev/null + rm -f "$DESTDIR$TENTACLE_CFG_FILE.new" 2> /dev/null + # Do not remove tentacle files if agent is still installed... + [ -e $DESTDIR$PREFIX/bin/pandora_agent ] || rm -f $DESTDIR$PREFIX/bin/tentacle_server 2> /dev/null + [ -e $DESTDIR$PREFIX/bin/pandora_agent ] || rm -f $DESTDIR$PREFIX/bin/tentacle_client 2> /dev/null + if [ "$DESTDIR" ] + then + rm -f $DESTDIR$TENTACLE_SERVER + elif [ "$DISTRO" = "UBUNTU" ] || [ "$DISTRO" = "DEBIAN" ] + then + update-rc.d -f tentacle_serverd remove + fi + + rm -f $DESTDIR/etc/rc2.d/S80tentacle_serverd 2> /dev/null + rm -f $DESTDIR/etc/rc.d/rc3.d/S80tentacle_serverd 2> /dev/null + + echo "Done" + echo $DESTDIR$TENTACLE_SERVER +} + +help () { + echo " --install To install Pandora FMS Servers on this system (You have to be root)" + echo " --uninstall To uninstall and remove Pandora FMS Servers on this System" + echo " " + echo " Additional second parameter (after --install) " + echo " " + echo " --force Ignore dependency problems and do the install" + echo " --destdir DIR Specify root directory for \"fakeroot\" installation" + echo " " +} + +execute_cmd () { + cmd="$1" + msg="$2" + + echo "$msg" + $cmd &>> /dev/null + if [ $? -ne 0 ]; then + echo "Fail" + [ "$3" ] && echo "$3" + echo "Error installing Tentacle server" + exit 1 + else + echo "Ok" + return 0 + fi +} + +##Main + +# Script banner at start +echo " " +echo "Tentacle Server Installer $PI_VERSION $PI_BUILD (c) 2008-2022 Artica ST" +echo "This program is licensed under GPL2 Terms. http://pandorafms.com" +echo " " + +case "$MODE" in + +'--install') + install "$@" + exit 0 + ;; + +'--uninstall') + uninstall "$@" + exit 0 + ;; + +*) + help +esac + diff --git a/tentacle/util/tentacle_serverd b/tentacle/util/tentacle_serverd new file mode 100755 index 0000000000..0c0f35ff97 --- /dev/null +++ b/tentacle/util/tentacle_serverd @@ -0,0 +1,185 @@ +#!/bin/bash +# Copyright (c) 2005-2010 Artica ST +# +# Author: Sancho Lerena 2006-2010 +# +# /etc/init.d/tentacle_server +# +# System startup script for Tentacle Server +# +# Comments to support chkconfig on RedHat Linux +# chkconfig: 2345 90 90 +# description: Tentacle Server startup script +# +# Comments to support LSB init script conventions +### BEGIN INIT INFO +# Provides: tentacle_server +# Required-Start: $network +# Should-Start: $syslog +# Required-Stop: $network +# Should-Stop: $network +# Default-Start: 2 3 5 +# Default-Stop: 0 1 6 +# Short-Description: Tentacle Server startup script +# Description: Tentacle Server startup script +### END INIT INFO + +if [ -x /lib/lsb/init-functions ]; then +. /lib/lsb/init-functions +fi + +# Uses a wait limit before sending a KILL signal, before trying to stop +# Pandora FMS server nicely. Some big systems need some time before close +# all pending tasks / threads. + +export MAXWAIT=60 + +# Check for SUSE status scripts +if [ -f /etc/rc.status ] +then + . /etc/rc.status + rc_reset +else + # Define part of rc functions for non-suse systems + function rc_status () { + RETVAL=$? + case $1 in + -v) RETVAL=0;; + esac + } + function rc_exit () { exit $RETVAL; } + function rc_failed () { RETVAL=${1:-1}; } + RETVAL=0 +fi + +function get_pid { + # This sets COLUMNS to XXX chars, because if command is run + # in a "strech" term, ps aux don't report more than COLUMNS + # characters and this will not work. + COLUMNS=300 + TENTACLE_PID=`ps -Af | grep "$TENTACLE_PATH$TENTACLE_DAEMON" | grep "$TENTACLE_CONFIG_FILE" | grep -v grep | tail -1 | awk '{ print $2 }'` + echo $TENTACLE_PID +} + +function get_all_pid { + # This sets COLUMNS to XXX chars, because if command is run + # in a "strech" term, ps aux don't report more than COLUMNS + # characters and this will not work. + COLUMNS=300 + TENTACLE_PIDS=`ps aux | grep "$TENTACLE_PATH$TENTACLE_DAEMON" | grep -v grep | awk '{ print $2 }'` + TENTACLE_PID="${TENTACLE_PIDS//\\n/' '}" + echo $TENTACLE_PID +} + +# Tentacle server settings +TENTACLE_DAEMON="tentacle_server" +TENTACLE_PATH="/usr/bin" +TENTACLE_USER="pandora" +TENTACLE_CONFIG_FILE="/etc/tentacle/tentacle_server.conf" +TENTACLE_EXT_OPTS="" + +# Set umask to 0002, because group MUST have access to write files to +# use remote file management on Pandora FMS Enterprise. + +umask 0007 + +# Main script +TENTACLE_OPTS="-F $TENTACLE_CONFIG_FILE $TENTACLE_EXT_OPTS" + +# Fix TENTACLE_PATH +case "$TENTACLE_PATH" in + *\/) + ;; + *) + TENTACLE_PATH="${TENTACLE_PATH}/" + ;; +esac + +if [ ! -f "${TENTACLE_PATH}$TENTACLE_DAEMON" ]; then + echo "Tentacle Server not found in ${TENTACLE_PATH}$TENTACLE_DAEMON" + rc_failed 5 # program is not installed + rc_exit +fi + +case "$1" in + start) + TENTACLE_PID=`get_pid` + if [ ! -z "$TENTACLE_PID" ]; then + echo "Tentacle Server is already running with PID $TENTACLE_PID" + rc_exit # running start on a service already running + fi + + # Init the tentacle process + sudo -u $TENTACLE_USER ${TENTACLE_PATH}$TENTACLE_DAEMON $TENTACLE_OPTS + sleep 1 + + TENTACLE_PID=`get_pid` + if [ ! -z "$TENTACLE_PID" ]; then + echo "Tentacle Server is now running with PID $TENTACLE_PID" + rc_status -v + else + echo "Tentacle Server could not be started." + echo "Verify that Tentacle port is not used." + rc_failed 7 # program not running + fi + + ;; + + stop) + TENTACLE_PID=`get_all_pid` + if [ -z "$TENTACLE_PID" ]; then + echo "Tentacle Server does not seem to be running" + rc_exit # running stop on a service already stopped or not running + else + kill $TENTACLE_PID + + COUNTER=0 + while [ $COUNTER -lt $MAXWAIT ] + do + _PID=`get_all_pid` + if [ "$_PID" != "$TENTACLE_PID" ] + # tentacle already stopped + then + COUNTER=$MAXWAIT + fi + COUNTER=`expr $COUNTER + 1` + sleep 1 + done + + if [ "$_PID" = "$TENTACLE_PID" ] + then + kill -9 $TENTACLE_PID > /dev/null 2>&1 + fi + + echo "Stopping Tentacle Server" + rc_status -v + fi + + ;; + + force-reload|restart) + $0 stop + sleep 1 + $0 start + rc_status + ;; + + status) + TENTACLE_PID=`get_pid` + if [ -z "$TENTACLE_PID" ]; then + echo "Tentacle Server is not running." + rc_failed 7 # program is not running + else + echo "Tentacle Server is running with PID $TENTACLE_PID." + rc_status + fi + + ;; + + *) + echo "Usage: $0 {start | stop | restart | status}" + exit 1 + ;; +esac + +rc_exit diff --git a/tentacle/util/tentacle_serverd.service b/tentacle/util/tentacle_serverd.service new file mode 100644 index 0000000000..fb6d2af2e3 --- /dev/null +++ b/tentacle/util/tentacle_serverd.service @@ -0,0 +1,14 @@ +[Unit] +Description=Tentacle server daemon +After=network-online.target +Requires=network.target + +[Service] +Type=forking +ExecStart=/usr/bin/tentacle_server -F /etc/tentacle/tentacle_server.conf +User=pandora +Restart=on-failure +RestartPreventExitStatus=1 + +[Install] +WantedBy=multi-user.target From 838cde88650a7aca5a8467039a06eba329ad6e7d Mon Sep 17 00:00:00 2001 From: Rafael Ameijeiras Date: Tue, 14 Jun 2022 12:43:43 +0200 Subject: [PATCH 2/2] Adding dependencies check for centos7/el8 --- tentacle/tentacle_server_installer | 52 ++++++------------------------ 1 file changed, 9 insertions(+), 43 deletions(-) diff --git a/tentacle/tentacle_server_installer b/tentacle/tentacle_server_installer index 469ac326c3..176a4743e5 100755 --- a/tentacle/tentacle_server_installer +++ b/tentacle/tentacle_server_installer @@ -121,7 +121,7 @@ install_startup_script () { SRC=$1 SCRIPT_NAME=`basename $SRC` - echo "Copying the daemon script into $INITDIR" + echo "Copying the daemon script into $DESTDIR$INITDIR" [ -d $DESTDIR$INITDIR ] || mkdir -p $DESTDIR$INITDIR cp $SRC $DESTDIR$INITDIR @@ -187,56 +187,22 @@ install () { # Execute tools check execute_cmd "ps --version" 'Checking dependencies: ps' "Error ps not found, please install procps" execute_cmd "sudo --version" 'Checking dependencies: sudo' "Error sudo not found, please install sudo" + execute_cmd "perl -MIO::Compress::Zip -le 'pass'" 'Checking dependencies: perl IO::Compress' "Error perl IO::Compress not found, please install perl IO::Compress" fi # install tentacle - $PERL Makefile.PL INSTALLMAN1DIR=none > output 2>&1 - #&& sleep 2 && cat output | grep "found" | wc -l - DEPENDENCIAS=`cat output | grep "found" | wc -l` + [ -d $DESTDIR$PREFIX/bin/ ] || mkdir -p $DESTDIR$PREFIX/bin/ + echo ">Installing the tentacle_server binary to $DESTDIR$PREFIX/bin/..." + cp -f tentacle_server "$DESTDIR$PREFIX/bin/" - if [ $DEPENDENCIAS -gt 0 ] && [ $FORCE -eq 0 ] - then - echo "You are missing the following dependencies" - echo " " - cat output | awk -F ": prerequisite" '{print $2}' | awk -F " " '{print $1}' - echo "The complete installation guide is at: https://pandorafms.com/docs/" - echo " " - rm output - exit 1 - fi - - echo "Installing binaries and libraries" - make - make DESTDIR=$DESTDIR install - - echo "Checking binaries at /usr/local/bin -> /usr/bin" - if [ ! -f "$DESTDIR/usr/bin/tentacle_server" ] - then - if [ ! -f "$DESTDIR/usr/local/bin/tentacle_server" ] - then - echo "ERROR compiling Tentacle Server from sources. Aborting" - exit 1 - fi - if [ "$DISTRO" != "FreeBSD" ] && [ "$DISTRO" != "NetBSD" ] - then - [ -d $DESTDIR$PREFIX/bin ] || mkdir -p $DESTDIR$PREFIX/bin - ln -s /usr/local/bin/tentacle_server $DESTDIR$PREFIX/bin - - fi - fi - + echo ">Installing the tentacle_client binary to $DESTDIR$PREFIX/bin/..." + cp -f tentacle_client "$DESTDIR$PREFIX/bin/" echo "Creating common Pandora FMS directories" id pandora 2> /dev/null if [ $? -eq 0 ]; then echo " " echo "User pandora does exist, make sure the SSH directories are correct" - elif [ "$DESTDIR" ] - then - # chown can fail with fakeroot installation - echo "User 'pandora' does not exist. All chown operations may fail." - echo "You should manualy set proper ownership to $DESTDIR$PANDORA_SPOOL if needed." - echo else echo "Are you sure we can create a standard 'pandora' user locally? [y/N]" read AREYOUSURE @@ -273,7 +239,7 @@ install () { chmod 2770 $DESTDIR$PANDORA_SPOOL/data_in/commands - echo "Giving proper permission to /var/spool/pandora" + echo "Giving proper permission to $DESTDIR$PANDORA_SPOOL/" for group in "www-data" wwwrun www apache do IDGROUP=`id -g "$group" 2> /dev/null` @@ -297,7 +263,7 @@ install () { install_startup_script -s 80 $TENTACLE_INIT_SCRIPT # Create the directory to locate the Tentacle configuration file - echo "Creating setup Tentacle directory in $TENTACLE_CFG_DIR" + echo "Creating setup Tentacle directory in $DESTDIR$TENTACLE_CFG_DIR" mkdir -p $DESTDIR$TENTACLE_CFG_DIR 2> /dev/null if [ -f "$DESTDIR$TENTACLE_CFG_FILE" ] then