diff --git a/pandora_agents/pc/DEBIAN/make_deb_package.sh b/pandora_agents/pc/DEBIAN/make_deb_package.sh
index f44dfec8f4..691918b54e 100644
--- a/pandora_agents/pc/DEBIAN/make_deb_package.sh
+++ b/pandora_agents/pc/DEBIAN/make_deb_package.sh
@@ -36,6 +36,8 @@ mkdir -p temp_package/usr/bin/
mkdir -p temp_package/usr/sbin/
mkdir -p temp_package/etc/pandora/plugins
mkdir -p temp_package/etc/pandora/collections
+mkdir -p temp_package/etc/pandora/trans
+mkdir -p temp_package/etc/pandora/commands
mkdir -p temp_package/etc/init.d/
mkdir -p temp_package/var/log/pandora/
mkdir -p temp_package/usr/share/man/man1/
diff --git a/pandora_agents/pc/pandora_agent_installer b/pandora_agents/pc/pandora_agent_installer
index ab2a00877e..469364abf1 100644
--- a/pandora_agents/pc/pandora_agent_installer
+++ b/pandora_agents/pc/pandora_agent_installer
@@ -324,6 +324,11 @@ 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 Pandora FMS Agent commands to $PANDORA_BASE$PANDORA_HOME/commands..."
+ cp -r commands $PANDORA_BASE$PANDORA_HOME
+ chmod -R 700 $PANDORA_BASE$PANDORA_HOME/commands
+ ln -s $PANDORA_BASE$PANDORA_HOME/commands $PANDORA_BASE$PANDORA_CFG
echo "Copying tentacle server to $PANDORA_BASE$TENTACLE_SERVER"
cp tentacle_server $PANDORA_BASE$TENTACLE_SERVER
diff --git a/pandora_agents/unix/DEBIAN/make_deb_package.sh b/pandora_agents/unix/DEBIAN/make_deb_package.sh
index f28fdab114..74a6e96001 100644
--- a/pandora_agents/unix/DEBIAN/make_deb_package.sh
+++ b/pandora_agents/unix/DEBIAN/make_deb_package.sh
@@ -36,6 +36,8 @@ mkdir -p temp_package/usr/bin/
mkdir -p temp_package/usr/sbin/
mkdir -p temp_package/etc/pandora/plugins
mkdir -p temp_package/etc/pandora/collections
+mkdir -p temp_package/etc/pandora/trans
+mkdir -p temp_package/etc/pandora/commands
mkdir -p temp_package/etc/init.d/
mkdir -p temp_package/lib/systemd/system/
mkdir -p temp_package/var/log/pandora/
diff --git a/pandora_agents/unix/pandora_agent b/pandora_agents/unix/pandora_agent
index a3c40e50f0..0dfcf8848a 100755
--- a/pandora_agents/unix/pandora_agent
+++ b/pandora_agents/unix/pandora_agent
@@ -24,6 +24,7 @@ Version 6.0
use strict;
use warnings;
+use Scalar::Util qw(looks_like_number);
use POSIX qw(strftime floor);
use Sys::Hostname;
use File::Basename;
@@ -32,6 +33,18 @@ use IO::Socket;
use Sys::Syslog;
use Time::Local;
+my $YAML = 0;
+# Dynamic load. Avoid unwanted behaviour.
+eval {
+ eval 'require YAML::Tiny;1' or die('YAML::Tiny lib not found, commands feature won\'t be available');
+};
+if ($@) {
+ $YAML = 0;
+ print STDERR $@;
+} else {
+ $YAML = 1;
+}
+
# Agent XML data
my $Xml;
@@ -187,6 +200,7 @@ my %DefaultConf = (
'custom_id' => '',
'url_address' => '',
'standby' => 0,
+ 'cmd_file' => undef,
);
my %Conf = %DefaultConf;
@@ -268,6 +282,20 @@ sub error ($) {
exit 1;
}
+################################################################################
+# Try to load extra libraries.c
+################################################################################
+sub load_libraries() {
+ # Dynamic load. Avoid unwanted behaviour.
+ eval {eval 'require YAML::Tiny;1' or die('YAML::Tiny lib not found, commands feature won\'t be available');};
+ if ($@) {
+ $YAML = 0;
+ print STDERR $@;
+ } else {
+ $YAML = 1;
+ }
+}
+
################################################################################
# Check a regular expression. Returns 1 if its valid, 0 otherwise.
################################################################################
@@ -284,6 +312,27 @@ sub valid_regexp ($) {
return 1;
}
+################################################################################
+# Reads a file and returns entire content or undef if error.
+################################################################################
+sub read_file {
+ my $path = shift;
+
+ my $_FILE;
+ if( !open($_FILE, "<", $path) ) {
+ # failed to open, return undef
+ return undef;
+ }
+
+ # Slurp configuration file content.
+ my $content = do { local $/; <$_FILE> };
+
+ # Close file
+ close($_FILE);
+
+ return $content;
+}
+
################################################################################
# Recursively delete files and directories.
################################################################################
@@ -682,6 +731,13 @@ sub parse_conf_modules($) {
# Macros
} elsif ($line =~ /^\s*module_macro(\S+)\s+(.*)\s*$/) {
$module->{'macros'}{$1} = $2;
+ # Commands
+ } elsif ($line =~ /^\s*cmd_file\s+(.+)$/) {
+ if (ref ($Conf{'commands'}) ne "HASH") {
+ $Conf{'commands'} = {};
+ }
+ # Initialize empty command hash.
+ $Conf{'commands'}->{$1} = {};
}
}
return;
@@ -1157,14 +1213,14 @@ sub recv_file {
};
if ($@) {
- log_message ('error', "Error retrieving file: File transfer command is not responding.");
+ log_message ('error', "Error retrieving file: '.$file.' File transfer command is not responding.");
exit 1;
}
# Get the errorlevel
my $rc = $? >> 8;
if ($rc != 0) {
- log_message ('error', "Error retrieving file: $output");
+ log_message ('error', "Error retrieving file: '$file' $output");
}
exit $rc;
}
@@ -1362,6 +1418,220 @@ sub check_collections () {
}
}
+################################################################################
+# Check for remote commands defined.
+################################################################################
+sub prepare_remote_commands {
+ if ($YAML == 0) {
+ log_message(
+ 'error',
+ 'Cannot use commands without YAML dependency, please install it.'
+ );
+ return;
+ }
+
+ foreach my $ref (keys %{$Conf{'commands'}}) {
+ my $file_content;
+ my $download = 0;
+ my $rcmd_file = $ConfDir.'/commands/'.$ref.'.rcmd';
+
+ # Check for local .rcmd.done files
+ if (-e $Conf{'temporal'}.'/'.$ref.'.rcmd.done') {
+ # Ignore.
+ delete $Conf{'commands'}->{$ref};
+ next;
+ }
+
+ # Search for local .rcmd file
+ if (-e $rcmd_file) {
+ my $remote_md5_file = $Conf{'temporal'}.'/'.$ref.'.md5';
+
+ $file_content = read_file($rcmd_file);
+ if (recv_file($ref.'.md5', $remote_md5_file) != 0) {
+ # Remote file could not be retrieved, skip.
+ delete $Conf{'commands'}->{$ref};
+ next;
+ }
+
+ my $local_md5 = md5($file_content);
+ my $remote_md5 = md5(read_file($remote_md5_file));
+
+ if ($local_md5 ne $remote_md5) {
+ # Must be downloaded again.
+ $download = 1;
+ }
+ } else {
+ $download = 1;
+ }
+
+ # Search for remote .rcmd file
+ if ($download == 1) {
+ # Download .rcmd file
+ if (recv_file($ref.'.rcmd') != 0) {
+ # Remote file could not be retrieved, skip.
+ delete $Conf{'commands'}->{$ref};
+ next;
+ } else {
+ # Success
+ move($Conf{'temporal'}.'/'.$ref.'.rcmd', $rcmd_file);
+ }
+ }
+
+ # Parse and prepare in memory skel.
+ eval {
+ $Conf{'commands'}->{$ref} = YAML::Tiny->read($rcmd_file);
+ };
+ if ($@) {
+ # Failed.
+ log_message('error', 'Failed to decode command. ' . "\n".$@);
+ delete $Conf{'commands'}->{$ref};
+ next;
+ }
+
+ }
+}
+
+################################################################################
+# Command report.
+################################################################################
+sub report_command {
+ my ($ref, $err_level) = @_;
+
+ # Retrieve content from .stdout and .stderr
+ my $stdout_file = $Conf{'temporal'}.'/'.$ref.'.stdout';
+ my $stderr_file = $Conf{'temporal'}.'/'.$ref.'.stderr';
+
+ my $return;
+ eval {
+ $return = {
+ 'error_level' => $err_level,
+ 'stdout' => read_file($stdout_file),
+ 'stderr' => read_file($stderr_file),
+ };
+
+ $return->{'name'} = $Conf{'commands'}->{$ref}->[0]->{'name'};
+ };
+ if ($@) {
+ log_message('error', 'Failed to report command output. ' . $@);
+ }
+
+ # Cleanup
+ unlink($stdout_file) if (-e $stdout_file);
+ unlink($stderr_file) if (-e $stderr_file);
+
+ # Mark command as done.
+ open (my $R_FILE, '> '.$ConfDir.'/commands/'.$ref.'.rcmd.done');
+ print $R_FILE $err_level;
+ close($R_FILE);
+
+
+ $return->{'stdout'} = '' unless defined ($return->{'stdout'});
+ $return->{'stderr'} = '' unless defined ($return->{'stderr'});
+
+ return $return;
+}
+
+################################################################################
+# Executes a command using defined timeout.
+################################################################################
+sub execute_command_timeout {
+ my ($command, $timeout) = @_;
+
+ if (!looks_like_number($timeout)) {
+ `$command`;
+ } else {
+ `$command`;
+ }
+
+ return $?>>8;
+}
+
+################################################################################
+# Executes a block of commands, returns error level, leaves output in
+# redirection set by $std_files. E.g:
+# $std_files = ' >> /tmp/stdout 2>> /tmp/stderr
+################################################################################
+sub execute_command_block {
+ my ($commands, $std_files, $timeout) = @_;
+
+ return 0 unless defined($commands);
+
+ my $err_level = 0;
+ $std_files = '' unless defined ($std_files);
+
+ if (ref($commands) ne "ARRAY") {
+ return 0 if $commands eq '';
+ $err_level = execute_command_timeout(
+ "($commands) $std_files",
+ $timeout
+ );
+
+ } else {
+ foreach my $comm (@{$commands}) {
+ next unless defined($comm);
+ $err_level = execute_command_timeout(
+ "($comm) $std_files",
+ $timeout
+ );
+
+ last unless ($err_level == 0);
+ }
+ }
+
+ return $err_level;
+}
+
+################################################################################
+# Evalate given command.
+################################################################################
+sub evaluate_command {
+ my ($ref) = @_;
+
+ # Not found.
+ return unless defined $Conf{'commands'}->{$ref};
+
+ # Already completed.
+ return if (-e $ConfDir.'/commands/'.$ref.'.rcmd.done');
+
+ # [0] because how library works.
+ my $cmd = $Conf{'commands'}->{$ref}->[0];
+
+ my $std_files = ' >> '.$Conf{'temporal'}.'/'.$ref.'.stdout ';
+ $std_files .= ' 2>> '.$Conf{'temporal'}.'/'.$ref.'.stderr ';
+
+ # Check preconditions
+ my $err_level;
+
+ $err_level = execute_command_block(
+ $cmd->{'preconditions'},
+ $std_files,
+ $cmd->{'timeout'}
+ );
+
+ # Precondition not satisfied.
+ return report_command($ref, $err_level) unless ($err_level == 0);
+
+ # Main run.
+ $err_level = execute_command_block(
+ $cmd->{'script'},
+ $std_files,
+ $cmd->{'timeout'}
+ );
+
+ # Script not success.
+ return report_command($ref, $err_level) unless ($err_level == 0);
+
+ # Check postconditions
+ $err_level = execute_command_block(
+ $cmd->{'postconditions'},
+ $std_files,
+ $cmd->{'timeout'}
+ );
+
+ # Return results.
+ return report_command($ref, $err_level);
+}
+
################################################################################
# Sleep function
################################################################################
@@ -2985,6 +3255,8 @@ my $iter_base_time = time();
$LogFileIdx = -1;
# Loop
while (1) {
+ load_libraries();
+
if (-e $Conf{'logfile'} && (stat($Conf{'logfile'}))[7] > $Conf{'logsize'}) {
rotate_log();
}
@@ -2999,6 +3271,25 @@ while (1) {
# Check file collections
check_collections () unless ($Conf{'debug'} eq '1');
+ if ($Conf{'debug'} ne '1') {
+ # Check remote commands
+ prepare_remote_commands ();
+
+ # Cleanup old .rcmd.done files.
+ my %registered = map { $_.'.rcmd.done' => 1 } keys %{$Conf{'commands'}};
+ if(opendir(my $dir, $ConfDir.'/commands/')) {
+ while (my $item = readdir($dir)) {
+ # Skip other files.
+ next if ($item !~ /\.rcmd\.done$/);
+
+ # Clean .rcmd.done file if its command is not referenced in conf.
+ if (!defined($registered{$item})) {
+ unlink ($item);
+ }
+ }
+ }
+ }
+
# Launch broker agents
@BrokerPid = ();
my @broker_agents = read_config ('broker_agent');
@@ -3128,6 +3419,24 @@ while (1) {
}
}
+ if (ref ($Conf{'commands'}) eq "HASH") {
+ foreach my $command (keys %{$Conf{'commands'}}) {
+ my $result = evaluate_command($command);
+ if (ref($result) eq "HASH") {
+ # Process command result.
+ $Xml .= "
diff --git a/pandora_console/general/firts_task/custom_graphs.php b/pandora_console/general/first_task/custom_graphs.php similarity index 94% rename from pandora_console/general/firts_task/custom_graphs.php rename to pandora_console/general/first_task/custom_graphs.php index e22187c6f5..d49001449a 100644 --- a/pandora_console/general/firts_task/custom_graphs.php +++ b/pandora_console/general/first_task/custom_graphs.php @@ -13,7 +13,7 @@ // GNU General Public License for more details. global $config; check_login(); -ui_require_css_file('firts_task'); +ui_require_css_file('first_task'); ?> true, 'message' => __('There are no custom graphs defined yet.') ]); @@ -21,7 +21,7 @@ ui_print_info_message(['no_close' => true, 'message' => __('There are no custom
diff --git a/pandora_console/general/firts_task/fields_manager.php b/pandora_console/general/first_task/fields_manager.php similarity index 94% rename from pandora_console/general/firts_task/fields_manager.php rename to pandora_console/general/first_task/fields_manager.php index f81de216db..2ef6e70f55 100755 --- a/pandora_console/general/firts_task/fields_manager.php +++ b/pandora_console/general/first_task/fields_manager.php @@ -13,13 +13,13 @@ // GNU General Public License for more details. global $config; check_login(); -ui_require_css_file('firts_task'); +ui_require_css_file('first_task'); ?> true, 'message' => __('There are no custom fields defined yet.') ]); ?>
diff --git a/pandora_console/general/firts_task/incidents.php b/pandora_console/general/first_task/incidents.php similarity index 95% rename from pandora_console/general/firts_task/incidents.php rename to pandora_console/general/first_task/incidents.php index daa83588e3..20630ff5e3 100644 --- a/pandora_console/general/firts_task/incidents.php +++ b/pandora_console/general/first_task/incidents.php @@ -15,7 +15,7 @@ global $config; global $incident_w; global $incident_m; check_login(); -ui_require_css_file('firts_task'); +ui_require_css_file('first_task'); ?> true, 'message' => __('There are no incidents defined yet.') ]); @@ -25,7 +25,7 @@ if ($incident_w || $incident_m) {
diff --git a/pandora_console/general/firts_task/map_builder.php b/pandora_console/general/first_task/map_builder.php similarity index 95% rename from pandora_console/general/firts_task/map_builder.php rename to pandora_console/general/first_task/map_builder.php index 352f7374e5..509f967195 100755 --- a/pandora_console/general/firts_task/map_builder.php +++ b/pandora_console/general/first_task/map_builder.php @@ -15,7 +15,7 @@ global $config; global $vconsoles_write; global $vconsoles_manage; check_login(); -ui_require_css_file('firts_task'); +ui_require_css_file('first_task'); ui_print_info_message( [ @@ -28,7 +28,7 @@ if ($vconsoles_write || $vconsoles_manage) {
diff --git a/pandora_console/general/firts_task/network_map.php b/pandora_console/general/first_task/network_map.php similarity index 95% rename from pandora_console/general/firts_task/network_map.php rename to pandora_console/general/first_task/network_map.php index 0526f936aa..f1de76c08b 100755 --- a/pandora_console/general/firts_task/network_map.php +++ b/pandora_console/general/first_task/network_map.php @@ -13,7 +13,7 @@ // GNU General Public License for more details. global $config; check_login(); -ui_require_css_file('firts_task'); +ui_require_css_file('first_task'); ?> true, 'message' => __('There are no network map defined yet.') ]); @@ -23,7 +23,7 @@ $networkmap_types = networkmap_get_types($strict_user);
diff --git a/pandora_console/general/first_task/omnishell.php b/pandora_console/general/first_task/omnishell.php new file mode 100644 index 0000000000..948204917c --- /dev/null +++ b/pandora_console/general/first_task/omnishell.php @@ -0,0 +1,56 @@ + + true, 'message' => __('There is no command defined yet.') ]); ?> + +
+ '.__( + 'Omnishell is an enterprise feature which allows you to execute a structured command along any agent in your %s. The only requirement is to have remote configuration enabled in your agent.', + io_safe_output(get_product_name()) + ).'
'; + + echo ''.__( + 'You can execute any command on as many agents you need, and check the execution on all of them using the Omnishell Command View' + ).'
'; + ?> + + + + +diff --git a/pandora_console/general/firts_task/recon_view.php b/pandora_console/general/first_task/recon_view.php similarity index 95% rename from pandora_console/general/firts_task/recon_view.php rename to pandora_console/general/first_task/recon_view.php index 8c59b23f64..9eee373dec 100755 --- a/pandora_console/general/firts_task/recon_view.php +++ b/pandora_console/general/first_task/recon_view.php @@ -13,13 +13,13 @@ // GNU General Public License for more details. global $config; check_login(); -ui_require_css_file('firts_task'); +ui_require_css_file('first_task'); ?> true, 'message' => __('There are no discovery tasks defined yet.') ]); ?>
diff --git a/pandora_console/general/firts_task/service_list.php b/pandora_console/general/first_task/service_list.php similarity index 95% rename from pandora_console/general/firts_task/service_list.php rename to pandora_console/general/first_task/service_list.php index 424bc60400..f4a56a826b 100755 --- a/pandora_console/general/firts_task/service_list.php +++ b/pandora_console/general/first_task/service_list.php @@ -15,14 +15,14 @@ global $config; global $agent_w; check_login(); -ui_require_css_file('firts_task'); +ui_require_css_file('first_task'); ?> true, 'message' => __('There are no services defined yet.') ]); ?>
diff --git a/pandora_console/general/firts_task/snmp_filters.php b/pandora_console/general/first_task/snmp_filters.php similarity index 95% rename from pandora_console/general/firts_task/snmp_filters.php rename to pandora_console/general/first_task/snmp_filters.php index 602702da69..724c9da31f 100755 --- a/pandora_console/general/firts_task/snmp_filters.php +++ b/pandora_console/general/first_task/snmp_filters.php @@ -13,13 +13,13 @@ // GNU General Public License for more details. global $config; check_login(); -ui_require_css_file('firts_task'); +ui_require_css_file('first_task'); ?> true, 'message' => __('There are no SNMP filter defined yet.') ]); ?>
diff --git a/pandora_console/general/firts_task/tags.php b/pandora_console/general/first_task/tags.php similarity index 94% rename from pandora_console/general/firts_task/tags.php rename to pandora_console/general/first_task/tags.php index 38c006d3b2..ea8086357c 100755 --- a/pandora_console/general/firts_task/tags.php +++ b/pandora_console/general/first_task/tags.php @@ -13,13 +13,13 @@ // GNU General Public License for more details. global $config; check_login(); -ui_require_css_file('firts_task'); +ui_require_css_file('first_task'); ?> true, 'message' => __('There are no tags defined yet.') ]); ?>
diff --git a/pandora_console/general/firts_task/transactional_list.php b/pandora_console/general/first_task/transactional_list.php similarity index 95% rename from pandora_console/general/firts_task/transactional_list.php rename to pandora_console/general/first_task/transactional_list.php index 6edc964ad7..40381f2da8 100644 --- a/pandora_console/general/firts_task/transactional_list.php +++ b/pandora_console/general/first_task/transactional_list.php @@ -15,7 +15,7 @@ global $config; global $networkmaps_write; global $networkmaps_manage; check_login(); -ui_require_css_file('firts_task'); +ui_require_css_file('first_task'); ?> true, 'message' => __('There are no transactions defined yet.') ]); @@ -25,7 +25,7 @@ if ($networkmaps_write || $networkmaps_manage) {