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 a4a8dce448..01bba4e75d 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 4bbac522b1..c9315a31c8 100755 --- a/pandora_agents/unix/pandora_agent +++ b/pandora_agents/unix/pandora_agent @@ -32,6 +32,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 +199,7 @@ my %DefaultConf = ( 'custom_id' => '', 'url_address' => '', 'standby' => 0, + 'cmd_file' => undef, ); my %Conf = %DefaultConf; @@ -284,6 +297,28 @@ 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) ) { + print ">> Failed to open $path ".`whoami`."\n"; + # 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 +717,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; @@ -1362,6 +1404,185 @@ 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. ' . $@); + 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 $return; +} + +################################################################################ +# 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) = @_; + + $std_files = '' unless defined ($std_files); + return 0 if ref($commands) ne "ARRAY"; + + my $err_level; + + foreach my $comm (@{$commands}) { + `($comm) $std_files`; + $err_level = $?>>8; + 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 + ); + + # Precondition not satisfied. + return report_command($ref, $err_level) unless ($err_level == 0); + + # Main run. + $err_level = execute_command_block( + $cmd->{'script'}, + $std_files + ); + + # Script not success. + return report_command($ref, $err_level) unless ($err_level == 0); + + # Check postconditions + $err_level = execute_command_block( + $cmd->{'postconditions'}, + $std_files + ); + + # Return results. + return report_command($ref, $err_level); +} + ################################################################################ # Sleep function ################################################################################ @@ -2999,6 +3220,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 +3368,23 @@ 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 .= "\n"; + $Xml .= " \n"; + $Xml .= " {'name'}."]]>\n"; + $Xml .= " {'error_level'}."]]>\n"; + $Xml .= " {'stdout'}."]]>\n"; + $Xml .= " {'stderr'}."]]>\n"; + $Xml .= " \n"; + $Xml .= "\n"; + } + } + } + # Wait for all the threads $ThreadSem->down ($Conf{'agent_threads'}) if (defined ($ThreadSem) && $Conf{'agent_threads'} > 1); $ThreadSem->up ($Conf{'agent_threads'}) if (defined ($ThreadSem) && $Conf{'agent_threads'} > 1); diff --git a/pandora_agents/unix/pandora_agent.redhat.spec b/pandora_agents/unix/pandora_agent.redhat.spec index 79f2310c11..82c582fd9a 100644 --- a/pandora_agents/unix/pandora_agent.redhat.spec +++ b/pandora_agents/unix/pandora_agent.redhat.spec @@ -99,6 +99,11 @@ if [ ! -e /etc/pandora/collections ]; then ln -s /usr/share/pandora_agent/collections /etc/pandora fi +if [ ! -e /etc/pandora/commands ]; then + mkdir -p /usr/share/pandora_agent/commands + ln -s /usr/share/pandora_agent/commands /etc/pandora +fi + mkdir -p /var/spool/pandora/data_out if [ ! -d /var/log/pandora ]; then mkdir -p /var/log/pandora diff --git a/pandora_agents/unix/pandora_agent.spec b/pandora_agents/unix/pandora_agent.spec index 392fa174ce..638cae7811 100644 --- a/pandora_agents/unix/pandora_agent.spec +++ b/pandora_agents/unix/pandora_agent.spec @@ -90,6 +90,11 @@ fi if [ ! -e /etc/pandora/collections ]; then mkdir /etc/pandora/collections fi + +if [ ! -e /etc/pandora/commands ]; then + mkdir /etc/pandora/commands +fi + cp -aRf /usr/share/pandora_agent/pandora_agent_logrotate /etc/logrotate.d/pandora_agent # Enable the service on SystemD diff --git a/pandora_agents/unix/pandora_agent_installer b/pandora_agents/unix/pandora_agent_installer index cc4b78b51c..04d56ff568 100755 --- a/pandora_agents/unix/pandora_agent_installer +++ b/pandora_agents/unix/pandora_agent_installer @@ -400,6 +400,11 @@ install () { mkdir -p $PANDORA_BASE$PANDORA_HOME/collections chmod -R 700 $PANDORA_BASE$PANDORA_HOME/collections ln -s $PANDORA_BASE_REAL$PANDORA_HOME/collections $PANDORA_BASE$PANDORA_CFG + + echo "Creating the commands directory in $PANDORA_BASE$PANDORA_HOME/commands..." + mkdir -p $PANDORA_BASE$PANDORA_HOME/commands + chmod -R 700 $PANDORA_BASE$PANDORA_HOME/commands + ln -s $PANDORA_BASE_REAL$PANDORA_HOME/commands $PANDORA_BASE$PANDORA_CFG if [ $WITHOUT_TENTACLE_SERVER -eq 0 ] then diff --git a/pandora_server/DEBIAN/make_deb_package.sh b/pandora_server/DEBIAN/make_deb_package.sh index 34f150ed54..7488b762c4 100644 --- a/pandora_server/DEBIAN/make_deb_package.sh +++ b/pandora_server/DEBIAN/make_deb_package.sh @@ -80,7 +80,9 @@ then mkdir -p temp_package/var/spool/pandora/data_in/netflow chmod 770 temp_package/var/spool/pandora/data_in/netflow mkdir -p temp_package/var/spool/pandora/data_in/trans - chmod 770 temp_package/var/spool/pandora/data_in/trans + chmod 770 temp_package/var/spool/pandora/data_in/trans + mkdir -p temp_package/var/spool/pandora/data_in/commands + chmod 770 temp_package/var/spool/pandora/data_in/commands mkdir -p temp_package/var/log/pandora/ chmod 754 temp_package/var/log/pandora/ mkdir -p temp_package/usr/share/pandora_server/conf/ diff --git a/pandora_server/conf/tentacle_server.conf.new b/pandora_server/conf/tentacle_server.conf.new index d4ebe4a71f..992a6b1944 100644 --- a/pandora_server/conf/tentacle_server.conf.new +++ b/pandora_server/conf/tentacle_server.conf.new @@ -21,7 +21,7 @@ daemon 1 # insecure 0 # Filters (regexp:dir;regexp:dir...) -filters .*\.conf:conf;.*\.md5:md5;.*\.zip:collections;.*\.lock:trans +filters .*\.conf:conf;.*\.md5:md5;.*\.zip:collections;.*\.lock:trans;.*\.rcmd:commands # [-m] Maximum file size allowed by the server in bytes #max_size 2000000 diff --git a/pandora_server/pandora_server.redhat.spec b/pandora_server/pandora_server.redhat.spec index 805efb4c41..4b3f236b41 100644 --- a/pandora_server/pandora_server.redhat.spec +++ b/pandora_server/pandora_server.redhat.spec @@ -54,6 +54,8 @@ mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/spool/pandora/data_in/conf mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/spool/pandora/data_in/md5 mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/spool/pandora/data_in/collections mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/spool/pandora/data_in/netflow +mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/spool/pandora/data_in/trans +mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/spool/pandora/data_in/commands mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/log/pandora/ mkdir -p $RPM_BUILD_ROOT%{prefix}/pandora_server/conf/ mkdir -p $RPM_BUILD_ROOT%{_mandir}/man1/ @@ -195,3 +197,5 @@ exit 0 %{_localstatedir}/spool/pandora/data_in/collections %{_localstatedir}/spool/pandora/data_in/conf %{_localstatedir}/spool/pandora/data_in/netflow +%{_localstatedir}/spool/pandora/data_in/trans +%{_localstatedir}/spool/pandora/data_in/commands diff --git a/pandora_server/pandora_server.spec b/pandora_server/pandora_server.spec index e2eb323b24..dffa282728 100644 --- a/pandora_server/pandora_server.spec +++ b/pandora_server/pandora_server.spec @@ -61,6 +61,7 @@ mkdir -p $RPM_BUILD_ROOT/var/spool/pandora/data_in/md5 mkdir -p $RPM_BUILD_ROOT/var/spool/pandora/data_in/collections mkdir -p $RPM_BUILD_ROOT/var/spool/pandora/data_in/netflow mkdir -p $RPM_BUILD_ROOT/var/spool/pandora/data_in/trans +mkdir -p $RPM_BUILD_ROOT/var/spool/pandora/data_in/commands mkdir -p $RPM_BUILD_ROOT/var/log/pandora/ mkdir -p $RPM_BUILD_ROOT%{prefix}/pandora_server/conf/ mkdir -p $RPM_BUILD_ROOT%{prefix}/tentacle/conf/ @@ -198,6 +199,7 @@ rm -Rf /usr/share/man/man1/tentacle_server.1.gz /var/spool/pandora/data_in/netflow /var/spool/pandora/data_in/conf /var/spool/pandora/data_in/trans +/var/spool/pandora/data_in/commands %defattr(-,pandora,root,750) /etc/pandora diff --git a/pandora_server/pandora_server_installer b/pandora_server/pandora_server_installer index 6b1870fadf..90f6fc19c4 100755 --- a/pandora_server/pandora_server_installer +++ b/pandora_server/pandora_server_installer @@ -315,6 +315,8 @@ install () { 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 mkdir -p $DESTDIR$PANDORA_LOG 2> /dev/null chown -R pandora $DESTDIR$PANDORA_LOG 2> /dev/null chmod 2774 $DESTDIR$PANDORA_LOG 2> /dev/null