From 9d524f3b3f6ecb8ee1da8010d52f82f74f4447c7 Mon Sep 17 00:00:00 2001 From: darode Date: Mon, 11 Apr 2011 16:35:45 +0000 Subject: [PATCH] 2011-04-11 Dario Rodriguez * pandora_agent_installer: Added code to install tentacle_server with pandora agent. * AIX/pandora_agent.conf: Added two new parameters proxy_mode and proxy_user. * tentacle_server: Added tentacle server with proxy support. * pandora_agent_daemon: Add feature to stop tentacle_server when pandora agent stops. * Linux/pandora_agent.conf: Added two new parameters proxy_mode and proxy_user. * HP-UX/pandora_agent.conf: Added two new parameters proxy_mode and proxy_user. * pandora_agent: Added support to tentacle_server and proxy_mode. * SunOS/pandora_agent.conf: Added two new parameters proxy_mode and proxy_user. git-svn-id: https://svn.code.sf.net/p/pandora/code/trunk@4185 c3f86ba8-e40f-0410-aaad-9ba5e7f4b01f --- pandora_agents/unix/AIX/pandora_agent.conf | 6 + pandora_agents/unix/ChangeLog | 17 + pandora_agents/unix/HP-UX/pandora_agent.conf | 6 + pandora_agents/unix/Linux/pandora_agent.conf | 6 + pandora_agents/unix/SunOS/pandora_agent.conf | 6 + pandora_agents/unix/pandora_agent | 29 +- pandora_agents/unix/pandora_agent_daemon | 21 + pandora_agents/unix/pandora_agent_installer | 16 +- pandora_agents/unix/tentacle_server | 1473 ++++++++++++++++++ 9 files changed, 1578 insertions(+), 2 deletions(-) create mode 100755 pandora_agents/unix/tentacle_server diff --git a/pandora_agents/unix/AIX/pandora_agent.conf b/pandora_agents/unix/AIX/pandora_agent.conf index 7145f5dc0f..e9d73924c4 100755 --- a/pandora_agents/unix/AIX/pandora_agent.conf +++ b/pandora_agents/unix/AIX/pandora_agent.conf @@ -82,6 +82,12 @@ transfer_mode tentacle # If set to 1 allows the agent to be configured via the web console (Only Enterprise version) # remote_config 1 +# If set to 1 start babel agent in proxy mode +# proxy_mode 1 + +# User which runs tentacle server in proxy mode. Required with if proxy mode is enable (by default babel) +# proxy_user pandora + # User the agent will run as #pandora_user root diff --git a/pandora_agents/unix/ChangeLog b/pandora_agents/unix/ChangeLog index d01d17c1b5..2b7d355bd1 100644 --- a/pandora_agents/unix/ChangeLog +++ b/pandora_agents/unix/ChangeLog @@ -1,3 +1,20 @@ +2011-04-11 Dario Rodriguez + + * pandora_agent_installer: Added code to install tentacle_server with + pandora agent. + * AIX/pandora_agent.conf: Added two new parameters proxy_mode and + proxy_user. + * tentacle_server: Added tentacle server with proxy support. + * pandora_agent_daemon: Add feature to stop tentacle_server when + pandora agent stops. + * Linux/pandora_agent.conf: Added two new parameters proxy_mode and + proxy_user. + * HP-UX/pandora_agent.conf: Added two new parameters proxy_mode and + proxy_user. + * pandora_agent: Added support to tentacle_server and proxy_mode. + * SunOS/pandora_agent.conf: Added two new parameters proxy_mode and + proxy_user. + 2011-04-04 Dario Rodriguez * pandora_agent_installer: Fixed an error with chown in AIX OS. diff --git a/pandora_agents/unix/HP-UX/pandora_agent.conf b/pandora_agents/unix/HP-UX/pandora_agent.conf index 7c768b816c..765abc0358 100755 --- a/pandora_agents/unix/HP-UX/pandora_agent.conf +++ b/pandora_agents/unix/HP-UX/pandora_agent.conf @@ -82,6 +82,12 @@ transfer_mode tentacle # If set to 1 allows the agent to be configured via the web console (Only Enterprise version) # remote_config 1 +# If set to 1 start babel agent in proxy mode +# proxy_mode 1 + +# User which runs tentacle server in proxy mode. Required with if proxy mode is enable (by default babel) +# proxy_user pandora + # User the agent will run as #pandora_user root diff --git a/pandora_agents/unix/Linux/pandora_agent.conf b/pandora_agents/unix/Linux/pandora_agent.conf index 43cddeda88..8f05e7e72c 100755 --- a/pandora_agents/unix/Linux/pandora_agent.conf +++ b/pandora_agents/unix/Linux/pandora_agent.conf @@ -97,6 +97,12 @@ transfer_mode tentacle # If set to 1 allows the agent to be configured via the web console (Only Enterprise version) #remote_config 1 +# If set to 1 start babel agent in proxy mode +# proxy_mode 1 + +# User which runs tentacle server in proxy mode. Required with if proxy mode is enable (by default babel) +# proxy_user pandora + # Number of threads to execute modules in parallel #agent_threads 1 diff --git a/pandora_agents/unix/SunOS/pandora_agent.conf b/pandora_agents/unix/SunOS/pandora_agent.conf index 676bcd055a..e49508583d 100755 --- a/pandora_agents/unix/SunOS/pandora_agent.conf +++ b/pandora_agents/unix/SunOS/pandora_agent.conf @@ -82,6 +82,12 @@ transfer_mode tentacle # If set to 1 allows the agent to be configured via the web console (Only Enterprise version) # remote_config 1 +# If set to 1 start babel agent in proxy mode +# proxy_mode 1 + +# User which runs tentacle server in proxy mode. Required with if proxy mode is enable (by default babel) +# proxy_user pandora + # User the agent will run as #pandora_user root diff --git a/pandora_agents/unix/pandora_agent b/pandora_agents/unix/pandora_agent index 6d14f58896..6baf24a5e5 100755 --- a/pandora_agents/unix/pandora_agent +++ b/pandora_agents/unix/pandora_agent @@ -148,7 +148,9 @@ my %Conf = ( 'agent_threads' => 1, 'udp_server_port' => 41122, 'udp_server_auth_address' => '0.0.0.0', - 'udp_server' => 0 + 'udp_server' => 0, + 'proxy_mode' => 0, + 'proxy_user' => 'pandora' ); # Modules @@ -608,6 +610,28 @@ sub check_remote_config () { `renice "$Conf{'pandora_nice'}" "$PID"`; } +################################################################################ +# SUB launch_tentacle_proxy +# Launchs tentacle server in proxy mode. +################################################################################ +sub launch_tentacle_proxy () { + + # Check if proxy mode is enable. + if ($Conf{'proxy_mode'}) { + + my $auxPid = fork(); + + if (! $auxPid) { + + my $new_process = "sudo -u ".$Conf{'proxy_user'}." tentacle_server -b ".$Conf{'server_ip'}." -g ".$Conf{'server_port'}; + + log_message ('setup', 'Proxy mode enabled'); + + exec ($new_process); + } + } +} + ################################################################################ # Delete old collections and download new collections. ################################################################################ @@ -1379,6 +1403,9 @@ sleep ($Conf{'delayed_startup'}); my $PID = $$; `renice "$Conf{'pandora_nice'}" "$PID"`; +#Launch tentacle server in proxy mode if needed +launch_tentacle_proxy(); + # Add the plugins directory to the PATH $ENV{'PATH'} .= ":$ConfDir/plugins"; diff --git a/pandora_agents/unix/pandora_agent_daemon b/pandora_agents/unix/pandora_agent_daemon index 35f799691a..ee44b9d66d 100755 --- a/pandora_agents/unix/pandora_agent_daemon +++ b/pandora_agents/unix/pandora_agent_daemon @@ -23,6 +23,7 @@ PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin PANDORA_PATH=/etc/pandora DAEMON=/usr/bin/pandora_agent +DAEMON_TENTACLE=/usr/bin/tentacle_server LOGFILE=/var/log/pandora/pandora_agent.log PANDORA_USER=root @@ -59,6 +60,19 @@ pidof_pandora () { echo $PANDORA_PID } +pidof_tentacle () { + COLUMNS=400 + OS_NAME=`uname -s` + if [ $OS_NAME = "HP-UX" ] + then + TENTACLE_PID=`ps -ex | grep "$DAEMON_TENTACLE" | grep -v grep | head -1 | awk '{ print $1 }'` + else + TENTACLE_PID=`ps -Af | grep "$DAEMON_TENTACLE" | grep -v grep | head -1 | awk '{ print $2 }'` + fi + + echo $TENTACLE_PID +} + if [ ! -f $DAEMON ] then echo "Pandora FMS Agent not found at $DAEMON, please check setup" @@ -90,6 +104,13 @@ case "$1" in echo "Stopping Pandora Agent." su $PANDORA_USER -c "kill -9 $PANDORA_PID >/dev/null 2>&1" fi + + TENTACLE_PID=`pidof_tentacle` + if [ ! -z "$TENTACLE_PID" ] + then + echo "Stopping Tentacle Server." + kill $TENTACLE_PID > /dev/null 2>&1 + fi ;; status) diff --git a/pandora_agents/unix/pandora_agent_installer b/pandora_agents/unix/pandora_agent_installer index 6429793ac1..2048ea16a6 100755 --- a/pandora_agents/unix/pandora_agent_installer +++ b/pandora_agents/unix/pandora_agent_installer @@ -23,6 +23,7 @@ PANDORA_CFG=/etc/pandora PANDORA_LOG_DIR=/var/log/pandora PANDORA_LOG=pandora_agent.log TENTACLE=/usr/bin/tentacle_client +TENTACLE_SERVER=/usr/bin/tentacle_server PANDORA_MAN=/usr/share/man MODE=$1 @@ -88,6 +89,7 @@ uninstall () { PANDORA_EXEC_BIN=/usr/local/bin/pandora_agent_exec PANDORA_CFG=/usr/local/etc/pandora TENTACLE=/usr/local/bin/tentacle_client + TENTACLE_SERVER=/usr/local/bin/tentacle_server PANDORA_MAN=/usr/local/man fi @@ -102,6 +104,7 @@ uninstall () { rm -Rf $PANDORA_BASE$PANDORA_TEMP/data_out 2> /dev/null else rm -Rf $PANDORA_BASE$PANDORA_TEMP 2> /dev/null + rm -Rf $PANDORA_BASE$TENTACLE_SERVER 2> /dev/null fi rm -Rf $PANDORA_BASE$PANDORA_CFG/pandora_agent.conf 2> /dev/null @@ -161,6 +164,7 @@ install () { PANDORA_EXEC_BIN=/usr/local/bin/pandora_agent_exec PANDORA_CFG=/usr/local/etc/pandora TENTACLE=/usr/local/bin/tentacle_client + TENTACLE_SERVER=/usr/local/bin/tentacle_server PANDORA_MAN=/usr/local/man fi @@ -317,7 +321,17 @@ install () { cp -r collections $PANDORA_BASE$PANDORA_HOME chmod -R 700 $PANDORA_BASE$PANDORA_HOME/collections ln -s $PANDORA_BASE$PANDORA_HOME/collections $PANDORA_BASE$PANDORA_CFG - + + echo "Copying tentacle server to $PANDORA_BASE$TENTACLE_SERVER" + cp tentacle_server $PANDORA_BASE$TENTACLE_SERVER + chmod 755 $PANDORA_BASE$TENTACLE_SERVER + if [ "$OS_NAME" = "FreeBSD" ] + then + chown $PANDORA_USER:wheel $PANDORA_BASE$TENTACLE_SERVER + else + chown $PANDORA_USER:0 $PANDORA_BASE$TENTACLE_SERVER + fi + echo "Copying tentacle client to $PANDORA_BASE$TENTACLE" cp tentacle_client $PANDORA_BASE$TENTACLE chmod 755 $PANDORA_BASE$TENTACLE diff --git a/pandora_agents/unix/tentacle_server b/pandora_agents/unix/tentacle_server new file mode 100755 index 0000000000..ec20f8b0ce --- /dev/null +++ b/pandora_agents/unix/tentacle_server @@ -0,0 +1,1473 @@ +#!/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 +# +# 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. +########################################################################## + +package tentacle::server; +=head1 NAME + +tentacle_server - Tentacle Server + +=head1 VERSION + +Version 0.3.0 + +=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::Socket::INET; +use Thread::Semaphore; +use POSIX ":sys_wait_h"; + +# Program version +our $VERSION = '0.3.0'; + +# Address to listen on +my $t_address = '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; + +# String containing quoted invalid file name characters +my $t_invalid_chars = '\?\[\]\/\\\=\+\<\>\:\;\'\,\*\~'; + +# Log messages, 1 enabled, 0 disabled +my $t_log = 0; + +# 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; + +# Server socket +my $t_server_socket; + +# 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; + +#Bridge IP to actuate as a proxy +my $t_proxy_ip = undef; + +# Proxy port by default 41121 +my $t_proxy_port = 41121; + +# Proxy socket +my $t_proxy_socket; + +# Proxy selected handler +my $t_proxy_select; + +################################################################################ +## 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_address\tAddress to listen on (default $t_address).\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-h\t\tShow help.\n"); + print ("\t-i\t\tFilters.\n"); + print ("\t-k key\t\tOpenSSL private key file.\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-t time\t\tTime-out for network operations in seconds (default ${t_timeout}s).\n"); + 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\n"); + print ("\t-b proxy_ip_address\t\tProxied server address.\n\n"); + print ("\t-g proxy_port\t\tPort of proxied server.\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 start_win_service +## Turn the current process into a Windows service. +################################################################################ +#sub start_win_service { +# require Win32::Daemon; +# +# # Tell the OS to start the service +# Win32::Daemon::StartService (); +# +# # Wait until the service manager is ready +# while (SERVICE_START_PENDING != Win32::Daemon::State()) { +# sleep (1); +# } +# +# # Tell the service manager we are running +# Win32::Daemon::State (SERVICE_RUNNING); +# +# # Call Win32::Daemon::StopService() when done +#} + +################################################################################ +## SUB parse_options +## Parse command line options and initialize global variables. +################################################################################ +sub parse_options { + my %opts; + my $tmp; + + # Get options + if (getopts ('a:c:de:f:hi:k:m:op:qr:s:t:vwx:b:g:', \%opts) == 0 || defined ($opts{'h'})) { + print_help (); + exit 1; + } + + # Address + if (defined ($opts{'a'})) { + $t_address = $opts{'a'}; + if ($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)) { + error ("Address $t_address is not valid."); + } + } + + # Maximum simultaneous connections + if (defined ($opts{'c'})) { + $t_max_conn = $opts{'c'}; + if ($t_max_conn !~ /^\d+$/ || $t_max_conn < 1) { + error ("Invalid number of maximum simultaneous connections."); + } + } + + # Run as daemon + if (defined ($opts{'d'})) { + if ($^ eq 'MSWin32') { + error ("-d flag not available for this OS."); + } + + $t_daemon = 1; + } + + # Enable SSL + if (defined ($opts{'e'})) { + + require IO::Socket::SSL; + + $t_ssl_cert = $opts{'e'}; + if (! -f $t_ssl_cert) { + error ("File $t_ssl_cert does not exist."); + } + + $t_ssl = 1; + } + + # Verify peer certificate + if (defined ($opts{'f'})) { + $t_ssl_ca = $opts{'f'}; + if (! -f $t_ssl_ca) { + error ("File $t_ssl_ca does not exist."); + } + } + + # Filters (regexp:dir;regexp:dir...) + if (defined ($opts{'i'})) { + my @filters = split (';', $opts{'i'}); + 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 + if (defined ($opts{'k'})) { + $t_ssl_key = $opts{'k'}; + if (! -f $t_ssl_key) { + error ("File $t_ssl_key does not exist."); + } + } + + # Maximum file size + if (defined ($opts{'m'})) { + $t_max_size = $opts{'m'}; + if ($t_max_size !~ /^\d+$/ || $t_max_size < 1) { + error ("Invalid maximum file size."); + } + } + + # File overwrite + if (defined ($opts{'o'})) { + $t_overwrite = 1; + } + + # Port + if (defined ($opts{'p'})) { + $t_port = $opts{'p'}; + if ($t_port !~ /^\d+$/ || $t_port < 1 || $t_port > 65535) { + error ("Port $t_port is not valid."); + } + } + + # Quiet mode + if (defined ($opts{'q'})) { + $t_quiet = 1; + } + + # Retries + if (defined ($opts{'r'})) { + $t_retries = $opts{'r'}; + if ($t_retries !~ /^\d+$/ || $t_retries < 1) { + error ("Invalid number of retries for network operations."); + } + } + + # Storage directory + if (defined ($opts{'s'})) { + + $t_directory = $opts{'s'}; + + # 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 { + if (! defined($opts{'b'})) { + print_help (); + exit 1; + } + } + + # Timeout + if (defined ($opts{'t'})) { + $t_timeout = $opts{'t'}; + if ($t_timeout !~ /^\d+$/ || $t_timeout < 1) { + error ("Invalid timeout for network operations."); + } + } + + # Be verbose + if (defined ($opts{'v'})) { + $t_log = 1; + } + + # SSL private key password + if (defined ($opts{'w'})) { + $t_ssl_pwd = ask_passwd ("Enter private key file password: ", "Enter private key file password again for confirmation: "); + } + + # Server password + if (defined ($opts{'x'})) { + $t_pwd = $opts{'x'}; + } + + #Proxy IP address + if (defined ($opts{'b'})) { + $t_proxy_ip = $opts{'b'}; + 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)) { + error ("Proxy address $t_proxy_ip is not valid."); + } + } + + # Proxy Port + if (defined ($opts{'g'})) { + $t_proxy_port = $opts{'g'}; + if ($t_proxy_port !~ /^\d+$/ || $t_proxy_port < 1 || $t_proxy_port > 65535) { + error ("Proxy port $t_port is not valid."); + } + } +} + +################################################################################ +## SUB sigchld_handler +## Handle child process termination. +################################################################################ +sub sigchld_handler { + + while (waitpid(-1, &WNOHANG) > 0) { + $t_sem->up (); + } + + $SIG{CHLD} = \&sigchld_handler; +} + +################################################################################ +## SUB start_proxy +## Open the server socket. +################################################################################ +sub open_proxy { + + # Connect to server + $t_proxy_socket = IO::Socket::INET->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 { + + $t_server_socket = IO::Socket::INET->new ( + Listen => $t_max_conn, + LocalAddr => $t_address, + LocalPort => $t_port, + Proto => 'tcp', + ReuseAddr => 1, + ); + + if (! defined ($t_server_socket)) { + error ("Cannot open socket for address $t_address on port $t_port: $!."); + } + + 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"); + } + +} + +################################################################################ +## SUB send_data_proxy +## Send data to proxy socket. +################################################################################ +sub send_data_proxy { + my $data = $_[0]; + 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)) { + + $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 + $retries++; + + # But check for error conditions first + 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->close (); + print_log ("Proxy socket closed"); + +} + +################################################################################ +## SUB stop_server +## Close the server socket. +################################################################################ +sub stop_server { + + $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_connection +## Accept an incoming connection and fork. +################################################################################ +sub accept_connection { + my $pid; + + while (1) { + + # Accept connection + $t_client_socket = $t_server_socket->accept (); + + if (! defined ($t_client_socket)) { + + # EINTR + if ($! ne '') { + next; + } + + error ("accept: $!."); + } + + last; + } + + print_log ("Client connected from " . $t_client_socket->sockhost ()); + + # Fork and serve the client + $pid = fork (); + if (! defined ($pid)) { + error ("Cannot fork: $!."); + } + + # Child + if ($pid == 0) { + + # We do not need the server socket + $t_server_socket->close (); + + # 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->close (); + + # Must exit now + exit; + } + + # Parent + $t_client_socket->close (); +} + +################################################################################ +## SUB serve_proxy_connection +## Actuate as a proxy between its client and other tentacle server. +################################################################################ +sub serve_proxy_connection { + my $read; + my $data=1; + + # Start a connection with the other Tentacle Server + open_proxy(); + + my $command; + + # Read commands + while ($command = recv_command ($t_block_size)) { + # Client wants to send a file + if ($command =~ /^SEND <(.*)> SIZE (\d+)$/) { + recv_file_proxy($command, $2); + } + # Client wants to receive a file + elsif ($command =~ /^RECV <(.*)>$/) { + send_file_proxy ($command); + } + # Quit + elsif ($command =~ /^QUIT$/) { + print_log ("Connection closed from " . $t_client_socket->sockhost ()); + + # End proxy connection + send_data_proxy("QUIT\n"); + last; + } + # Unknown command + else { + print_log ("Unknown command '$command' from " . $t_client_socket->sockhost ()); + last; + } + } + + # End a connection with the other 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_log ("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_log ("Request to receive file '$1' from " . $t_client_socket->sockhost ()); + send_file ($1); + } + # Quit + elsif ($command =~ /^QUIT$/) { + print_log ("Connection closed from " . $t_client_socket->sockhost ()); + last; + } + # 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_proxy +## Redirect file from agent to proxy +################################################################################ +sub recv_file_proxy ($$) { + my ($command, $size) = @_; + + # Send command to proxy + print_log ("[PROXY] Host: ".$t_client_socket->sockhost()." send ".$command." to ".$t_proxy_socket->sockhost ()); + send_data_proxy($command."\n"); + + # Proxied server response + my $rc = dump_data($t_proxy_socket, $t_client_socket); + + # Check if there was an error + if ($rc == -1) { + return; + } + + # Client send data to server + $rc = dump_data($t_client_socket, $t_proxy_socket, $size); + + # Check if there was an error + if ($rc == -1) { + return; + } + + # Server says if data was recieved or not + $rc = dump_data($t_proxy_socket, $t_client_socket); + + # Check if there was an error + if ($rc == -1) { + return; + } + +} + +################################################################################ +## 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\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\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\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_log ("Received file '$base_name' size ${size}b from " . $t_client_socket->sockhost ()); +} + +################################################################################ +## SUB send_file_proxy +## Redirect file from agent to proxy +################################################################################ +sub send_file_proxy ($) { + my ($command) = @_; + my $size; + # Send command to proxy + print_log ("[PROXY] ".$t_client_socket->sockhost()." send ".$command." to ".$t_proxy_socket->sockhost ()); + send_data_proxy($command."\n"); + + $command = recv_command_proxy($t_block_size); + + if ($command =~ /^RECV SIZE (\d+)$/) { + $size = $1; + } + + print_log ("[PROXY] ".$t_proxy_socket->sockhost()." send ".$command." to ".$t_client_socket->sockhost ()); + + send_data($command."\n"); + + # Client send OK to server + my $rc = dump_data($t_client_socket, $t_proxy_socket); + + # Check if there was an error + if ($rc == -1) { + return; + } + + # Proxied server send the file to client + $rc = dump_data($t_proxy_socket, $t_client_socket, $size); + + # Check if there was an error + if ($rc == -1) { + return; + } + +} + +################################################################################ +## 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\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\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); + + while ($data = ) { + send_data ($data); + } + + close (FILE); + + print_log ("Requested file '$file' from " . $t_client_socket->sockhost () . " sent"); +} + +################################################################################ +# Common functions +################################################################################ + +################################################################################ +## SUB print_log +## Print log messages. +################################################################################ +sub print_log { + + if ($t_log == 1) { + print (STDOUT "[log] $_[0]\n"); + } +} + +################################################################################ +## SUB error +## Print an error and exit the program. +################################################################################ +sub error { + + if ($t_quiet == 0) { + print (STDERR "[err] $_[0]\n"); + } + + exit 1; +} + +################################################################################ +## 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 $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)) { + + $written = syswrite ($t_client_socket, $data, $size - $total, $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 + $retries++; + + # But check for error conditions first + if ($retries > $t_retries) { + error ("Connection from " . $t_client_socket->sockhost () . " timed out."); + } + } +} + +################################################################################ +## SUB dump_data +## Dump data from a socket to another one. Following Tentacle Protocol. +################################################################################ +sub dump_data($$;$) { + my ($from, $to, $size) = @_; + my $read; + my $data; + my $buffer = ""; + my $written; + my $total = $size; + my $t_select_read = undef; + my $t_select_write = undef; + + # Assign the correct selector for each socket + if ($from == $t_proxy_socket) { + # We must read from t_proxy_socket + $t_select_read = $t_proxy_select; + $t_select_write = $t_select; + } else { + # We must read from t_client_socket + $t_select_read = $t_select; + $t_select_write = $t_proxy_select; + } + + while (1) { + + # Ensure we can read from socket + if ($t_select_read->can_read()) { + # Ensure we can write from socket + if ($t_select_write->can_write()) { + + $read = ""; + $read = sysread ($from, $data, $t_block_size); + + # Read error + if (! defined ($read)) { + error ("Read error from " . $from->sockhost () . ": $!."); + return -1; + } + + # EOF + if ($read == 0) { + error ("Connection from " . $from->sockhost () . " unexpectedly closed."); + return -1; + } + + $written = syswrite ($to, $data, $read); + + if ($written != $read) { + error ("Connection from " . $to->sockhost () . " unexpectedly closed."); + return -1; + } + + $buffer .= $data; + + if (! defined ($size)) { + # If no size defined check for \n + # because a command was sent. + + if ($buffer =~ /\n$/) { + + # Delete CHAR \n + chop($buffer); + + print_log ("[PROXY] ".$from->sockhost()." send ".$buffer." to ".$to->sockhost ()); + + # Returns error if proxy returns error to end connection + if ($buffer =~ /SEND ERR/) { + return -1; + } else { + return 0; + } + } + } else { + # If size is defined check if all bytes were sent + $size = $size - $read; + + if ($size == 0) { + print_log ("[PROXY] ".$from->sockhost()." send ".$total."b to ".$to->sockhost()); + + return 0; + } + } + } + } + } +} + +################################################################################ +## SUB recv_command_proxy +## Read a command from the proxied server, ended by a new line character. +################################################################################ +sub recv_command_proxy { + my $buffer; + my $char; + my $command = ''; + my $read; + my $total = 0; + + while (1) { + + ($read, $buffer) = recv_data_proxy ($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_proxy_socket->sockhost () . "."); + } + } +} + +################################################################################ +## 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 ''; +} + +################################################################################ +# 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; +} + +# Run as daemon? +if ($t_daemon == 1 && $^O ne 'MSWin32') { + daemonize (); +} + +# Handle ctr-c +if ($^O eq 'MSWin32') { + $SIG{INT2} = \&stop_server; +} +else { + $SIG{INT} = \&stop_server; +} + +# Handle SIGCHLD +$SIG{CHLD} = \&sigchld_handler; + +start_server (); + +# Initialize semaphore +$t_sem = Thread::Semaphore->new ($t_max_conn); + +# Accept connections +while (1) { + $t_sem->down (); + accept_connection (); +} + +__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 +