From 201e1c79561788c78695fc0894edb614e04ab27d Mon Sep 17 00:00:00 2001 From: Ramon Novoa Date: Thu, 15 Jun 2023 19:22:29 +0200 Subject: [PATCH] Add support for Discovery Apps. --- pandora_server/lib/PandoraFMS/DB.pm | 61 ++- .../lib/PandoraFMS/DiscoveryServer.pm | 387 +++++++++++++++++- 2 files changed, 424 insertions(+), 24 deletions(-) diff --git a/pandora_server/lib/PandoraFMS/DB.pm b/pandora_server/lib/PandoraFMS/DB.pm index c7544de2f7..55df2f3ecc 100644 --- a/pandora_server/lib/PandoraFMS/DB.pm +++ b/pandora_server/lib/PandoraFMS/DB.pm @@ -62,6 +62,7 @@ our @EXPORT = qw( set_update_agent set_update_agentmodule get_action_id + get_action_name get_addr_id get_agent_addr_id get_agent_id @@ -102,6 +103,8 @@ our @EXPORT = qw( get_server_id get_tag_id get_tag_name + get_template_id + get_template_name get_group_name get_template_id get_template_module_id @@ -257,15 +260,28 @@ sub get_console_api_url ($$) { } ######################################################################## -## Return action ID given the action name. +## Return the ID of an alert action given its name. ######################################################################## sub get_action_id ($$) { my ($dbh, $action_name) = @_; - my $rc = get_db_value ($dbh, "SELECT id FROM talert_actions WHERE name = ?", $action_name); + my $rc = get_db_value ($dbh, "SELECT id FROM talert_actions + WHERE name = ?", safe_input($action_name)); return defined ($rc) ? $rc : -1; } +######################################################################## +## Return the name of an alert action given its ID. +######################################################################## +sub get_action_name ($$) { + my ($dbh, $action_id) = @_; + + my $rc = get_db_value ($dbh, "SELECT name FROM talert_actions + WHERE id = ?", safe_input($action_id)); + return defined ($rc) ? $rc : -1; +} + + ######################################################################## ## Return command ID given the command name. ######################################################################## @@ -304,6 +320,29 @@ sub get_agent_ids_from_alias ($$) { return @rc; } +######################################################################## +## Return the ID of an alert template given its name. +######################################################################## +sub get_template_id ($$) { + my ($dbh, $template_name) = @_; + + my $rc = get_db_value ($dbh, "SELECT id FROM talert_templates + WHERE name = ?", safe_input($template_name)); + return defined ($rc) ? $rc : -1; +} + +######################################################################## +## Return the name of an alert template given its ID. +######################################################################## +sub get_template_name ($$) { + my ($dbh, $template_id) = @_; + + my $rc = get_db_value ($dbh, "SELECT name FROM talert_templates + WHERE id = ?", safe_input($template_id)); + return defined ($rc) ? $rc : -1; +} + + ######################################################################## ## Return server ID given the name of server. ######################################################################## @@ -698,24 +737,6 @@ sub get_agent_module_id ($$$) { return defined ($rc) ? $rc : -1; } -########################################################################## -## Return template id given the template name. -########################################################################## -sub get_template_id ($$) { - my ($dbh, $template_name) = @_; - - my $field; - if ($RDBMS eq 'oracle') { - $field = "to_char(name)"; - } - else { - $field = "name"; - } - - my $rc = get_db_value ($dbh, "SELECT id FROM talert_templates WHERE $field = ?", safe_input($template_name)); - return defined ($rc) ? $rc : -1; -} - ########################################################################## ## Return the module template id given the module id and the template id. ########################################################################## diff --git a/pandora_server/lib/PandoraFMS/DiscoveryServer.pm b/pandora_server/lib/PandoraFMS/DiscoveryServer.pm index 835ae1b82a..95cceeea80 100644 --- a/pandora_server/lib/PandoraFMS/DiscoveryServer.pm +++ b/pandora_server/lib/PandoraFMS/DiscoveryServer.pm @@ -68,6 +68,7 @@ use constant { DISCOVERY_REVIEW => 0, DISCOVERY_STANDARD => 1, DISCOVERY_RESULTS => 2, + DISCOVERY_APP => 15, }; ################################################################################ @@ -147,13 +148,13 @@ sub data_producer ($) { WHERE id_recon_server = ? AND disabled = 0 AND ((utimestamp = 0 AND interval_sweep != 0 OR status = 1) - OR (status = -1 AND interval_sweep > 0 AND (utimestamp + interval_sweep) < UNIX_TIMESTAMP()))', $server_id); + OR (status < 0 AND interval_sweep > 0 AND (utimestamp + interval_sweep) < UNIX_TIMESTAMP()))', $server_id); } else { @rows = get_db_rows ($dbh, 'SELECT * FROM trecon_task WHERE (id_recon_server = ? OR id_recon_server NOT IN (SELECT id_server FROM tserver WHERE status = 1 AND server_type = ?)) AND disabled = 0 AND ((utimestamp = 0 AND interval_sweep != 0 OR status = 1) - OR (status = -1 AND interval_sweep > 0 AND (utimestamp + interval_sweep) < UNIX_TIMESTAMP()))', $server_id, DISCOVERYSERVER); + OR (status < 0 AND interval_sweep > 0 AND (utimestamp + interval_sweep) < UNIX_TIMESTAMP()))', $server_id, DISCOVERYSERVER); } foreach my $row (@rows) { @@ -185,7 +186,17 @@ sub data_consumer ($$) { if (defined ($task->{'id_recon_script'}) && ($task->{'id_recon_script'} != 0)) { exec_recon_script ($pa_config, $dbh, $task); return; - } else { + } + # Is it a discovery app? + elsif ($task->{'type'} == DISCOVERY_APP) { + if ($task->{'setup_complete'} == 1) { + exec_recon_app ($pa_config, $dbh, $task); + } else { + logger($pa_config, 'Setup for recon app task ' . $task->{'id_app'} . ' not complete.', 10); + } + return; + } + else { logger($pa_config, 'Starting recon task for net ' . $task->{'subnet'} . '.', 10); } @@ -407,7 +418,375 @@ sub exec_recon_script ($$$) { } ################################################################################ -# Guess the OS using nmap. +# Executes recon apps. +################################################################################ +sub exec_recon_app ($$$) { + my ($pa_config, $dbh, $task) = @_; + + # Get execution, macro and script data. + my @executions = get_db_rows($dbh, 'SELECT * FROM tdiscovery_apps_executions WHERE id_app = ?', $task->{'id_app'}); + my @scripts = get_db_rows($dbh, 'SELECT * FROM tdiscovery_apps_scripts WHERE id_app = ?', $task->{'id_app'}); + + logger($pa_config, 'Executing recon app ID ' . $task->{'id_app'}, 10); + + update_recon_task ($dbh, $task->{'id_rt'}, 0); + + # Configure macros. + my %macros = ( + "__taskMD5__" => md5($task->{'id_rt'}), + "__taskInterval__" => $task->{'interval_sweep'}, + "__taskGroup__" => get_group_name($dbh, $task->{'id_group'}), + "__taskGroupID__" => $task->{'id_group'}, + "__temp__" => $pa_config->{'temporal'}, + "__incomingDir__" => $pa_config->{'incomingdir'}, + "__consoleAPIURL__" => $pa_config->{'console_api_url'}, + "__consoleAPIPass__" => $pa_config->{'console_api_pass'}, + "__consoleUser__" => $pa_config->{'console_user'}, + "__consolePass__" => $pa_config->{'console_pass'}, + get_recon_app_macros($pa_config, $dbh, $task), + get_recon_script_macros($pa_config, $dbh, $task) + ); + + # Dump macros to disk. + dump_recon_app_macros($pa_config, $dbh, $task, \%macros); + + # Run executions. + my $status = -1; + my @summary; + for (my $i = 0; $i < scalar(@executions); $i++) { + my $execution = $executions[$i]; + + # NOTE: Add the redirection before calling subst_alert_macros to prevent it from escaping quotes. + my $cmd = $pa_config->{'plugin_exec'} . ' ' . $task->{'executions_timeout'} . ' ' . + subst_alert_macros(safe_output($execution->{'execution'}) . ' 2>&1', \%macros); + logger($pa_config, 'Executing command for recon app ID ' . $task->{'id_app'} . ': ' . $cmd, 10); + my $output_json = `$cmd`; + + # Something went wrong. + my $rc = $? >> 8; + if ($rc != 0) { + $status = -2; + } + + # Timeout specific mesage. + if ($rc == 124) { + push(@summary, "The execution timed out."); + next; + } + + # No output message. + if (!defined($output_json)) { + push(@summary, "The execution returned no output."); + next; + } + + # Parse the output. + my $output = eval { + local $SIG{'__DIE__'}; + decode_json($output_json); + }; + + # Non-JSON output. + if (!defined($output)) { + push(@summary, $output_json); + next; + } + + # Process monitoring data. + if (defined($output->{'monitoring_data'})) { + my $recon = new PandoraFMS::Recon::Base( + dbh => $dbh, + group_id => $task->{'id_group'}, + id_os => $task->{'id_os'}, + pa_config => $pa_config, + snmp_enabled => 0, + task_id => $task->{'id_rt'}, + task_data => $task, + ); + $recon->create_agents($output->{'monitoring_data'}); + delete($output->{'monitoring_data'}); + } + + # Store output data. + push(@summary, $output); + + # Update the progress. + update_recon_task($dbh, $task->{'id_rt'}, int((100 * ($i + 1)) / scalar(@executions))); + } + + # Parse the output. + my $summary_json = eval { + local $SIG{'__DIE__'}; + encode_json(\@summary); + }; + if (!defined($summary_json)) { + logger($pa_config, 'Invalid summary for recon app ID ' . $task->{'id_app'}, 10); + } else { + db_do($dbh, "UPDATE trecon_task SET summary=? WHERE id_rt=?", $summary_json, $task->{'id_rt'}); + } + + update_recon_task($dbh, $task->{'id_rt'}, $status); + + return; +} + +################################################################################ +# Processe app macros and return them ready to be used by subst_alert_macros. +################################################################################ +sub get_recon_app_macros ($$$) { + my ($pa_config, $dbh, $task) = @_; + my %macros; + + # Get a list of macros for the given task. + my @macro_array = get_db_rows($dbh, 'SELECT * FROM tdiscovery_apps_tasks_macros WHERE id_task = ?', $task->{'id_rt'}); + foreach my $macro_item (@macro_array) { + my $macro_id = safe_output($macro_item->{'id_task'}); + my $macro_name = safe_output($macro_item->{'macro'}); + my $macro_type = $macro_item->{'type'}; + my $macro_value = safe_output($macro_item->{'value'}); + my $computed_value = ''; + + # The value can be a JSON array of values. + my $value_array = eval { + local $SIG{'__DIE__'}; + decode_json($macro_value); + }; + if (defined($value_array) && ref($value_array) eq 'ARRAY') { + # Multi value macro. + my @tmp; + foreach my $value_item (@{$value_array}) { + push(@tmp, get_recon_macro_value($pa_config, $dbh, $macro_type, $value_item)); + } + $computed_value = p_encode_json($pa_config, \@tmp); + if (!defined($computed_value)) { + logger($pa_config, "Error encoding macro $macro_name for task ID " . $task->{'id_rt'}, 10); + next; + } + } else { + # Single value macro. + $computed_value = get_recon_macro_value($pa_config, $dbh, $macro_type, $macro_value); + } + + # Store the computed value. + $macros{$macro_name} = $computed_value; + } + + return %macros; +} + +################################################################################ +# Dump macros that must be saved to disk. +# The macros dictionary is modified in-place. +################################################################################ +sub dump_recon_app_macros ($$$$) { + my ($pa_config, $dbh, $task, $macros) = @_; + + # Get a list of macros that must be dumped to disk. + my @macro_array = get_db_rows($dbh, 'SELECT * FROM tdiscovery_apps_tasks_macros WHERE id_task = ? AND temp_conf = 1', $task->{'id_rt'}); + foreach my $macro_item (@macro_array) { + + # Make sure the macro has already been parsed. + my $macro_name = safe_output($macro_item->{'macro'}); + next unless defined($macros->{$macro_name}); + my $macro_value = $macros->{$macro_name}; + my $macro_id = safe_output($macro_item->{'id_task'}); + + # Save the computed value to a temporary file. + my $temp_dir = $pa_config->{'incomingdir'} . '/discovery/tmp'; + mkdir($temp_dir) if (! -d $temp_dir); + my $fname = $temp_dir . '/' . md5($task->{'id_rt'} . '_' . $macro_name) . '.macro'; + eval { + open(my $fh, '>', $fname) or die($!); + print $fh subst_alert_macros($macro_value, $macros); + close($fh); + }; + if ($@) { + logger($pa_config, "Error writing macro $macro_name for task ID " . $task->{'id_rt'} . " to disk: $@", 10); + next; + } + + # Set the macro value to the temporary file name. + $macros->{$macro_name} = $fname; + } +} + +################################################################################ +# Processe recon script macros and return them ready to be used by +# subst_alert_macros. +################################################################################ +sub get_recon_script_macros ($$$) { + my ($pa_config, $dbh, $task) = @_; + my %macros; + + # Get a list of script macros. + my @macro_array = get_db_rows($dbh, 'SELECT * FROM tdiscovery_apps_scripts WHERE id_app = ?', $task->{'id_app'}); + foreach my $macro_item (@macro_array) { + my $macro_name = safe_output($macro_item->{'macro'}); + my $macro_value = safe_output($macro_item->{'value'}); + + # Compose the full path to the script: /discovery//