"Fossies" - the Fresh Open Source Software Archive

Member "pandora_server/lib/PandoraFMS/DiscoveryServer.pm" (15 Sep 2021, 59156 Bytes) of package /linux/misc/pandorafms_server-7.0NG.757.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Perl source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "DiscoveryServer.pm" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 7.0NG.755_vs_7.0NG.756.

    1 package PandoraFMS::DiscoveryServer;
    2 ################################################################################
    3 # Pandora FMS Discovery Server.
    4 # Pandora FMS. the Flexible Monitoring System. http://www.pandorafms.org
    5 ################################################################################
    6 # Copyright (c) 2005-2021 Artica Soluciones Tecnologicas S.L
    7 #
    8 # This program is free software; you can redistribute it and/or
    9 # modify it under the terms of the GNU Lesser General Public License
   10 # as published by the Free Software Foundation; version 2
   11 # This program is distributed in the hope that it will be useful,
   12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14 # GNU General Public License for more details.
   15 # You should have received a copy of the GNU General Public License
   16 # along with this program; if not, write to the Free Software
   17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
   18 ################################################################################
   19 
   20 use strict;
   21 use warnings;
   22 
   23 use threads;
   24 use threads::shared;
   25 use Thread::Semaphore;
   26 
   27 use IO::Socket::INET;
   28 use POSIX qw(strftime ceil);
   29 use JSON;
   30 use Encode qw(encode_utf8);
   31 use MIME::Base64;
   32 
   33 # Default lib dir for RPM and DEB packages
   34 use lib '/usr/lib/perl5';
   35 
   36 use PandoraFMS::Tools;
   37 use PandoraFMS::DB;
   38 use PandoraFMS::Core;
   39 use PandoraFMS::ProducerConsumerServer;
   40 use PandoraFMS::GIS;
   41 use PandoraFMS::Recon::Base;
   42 
   43 # Inherits from PandoraFMS::ProducerConsumerServer
   44 our @ISA = qw(PandoraFMS::ProducerConsumerServer);
   45 
   46 # Global variables
   47 my @TaskQueue :shared;
   48 my %PendingTasks :shared;
   49 my $Sem :shared;
   50 my $TaskSem :shared;
   51 
   52 # Some required constants, OS_X from tconfig_os.
   53 use constant {
   54   OS_OTHER => 10,
   55   OS_ROUTER => 17,
   56   OS_SWITCH => 18,
   57   STEP_SCANNING => 1,
   58   STEP_AFT => 2,
   59   STEP_TRACEROUTE => 3,
   60   STEP_GATEWAY => 4,
   61   STEP_MONITORING => 5,
   62   STEP_PROCESSING => 6,
   63   STEP_STATISTICS => 1,
   64   STEP_APP_SCAN => 2,
   65   STEP_CUSTOM_QUERIES => 3,
   66   DISCOVERY_REVIEW => 0,
   67   DISCOVERY_STANDARD => 1,
   68   DISCOVERY_RESULTS => 2,
   69 };
   70 
   71 ################################################################################
   72 # Discovery Server class constructor.
   73 ################################################################################
   74 sub new ($$$$$$) {
   75   my ($class, $config, $dbh) = @_;
   76   
   77   return undef unless (defined($config->{'reconserver'}) && $config->{'reconserver'} == 1)
   78    || (defined($config->{'discoveryserver'}) && $config->{'discoveryserver'} == 1);
   79   
   80   if (! -e $config->{'nmap'}) {
   81     logger ($config, ' [E] ' . $config->{'nmap'} . " needed by " . $config->{'rb_product_name'} . " Discovery Server not found.", 1);
   82     print_message ($config, ' [E] ' . $config->{'nmap'} . " needed by " . $config->{'rb_product_name'} . " Discovery Server not found.", 1);
   83     return undef;
   84   }
   85 
   86   # Initialize semaphores and queues
   87   @TaskQueue = ();
   88   %PendingTasks = ();
   89   $Sem = Thread::Semaphore->new;
   90   $TaskSem = Thread::Semaphore->new (0);
   91   
   92   # Restart automatic recon tasks.
   93   db_do ($dbh, 'UPDATE trecon_task  SET utimestamp = 0 WHERE id_recon_server = ? AND status <> -1 AND interval_sweep > 0',
   94        get_server_id ($dbh, $config->{'servername'}, DISCOVERYSERVER));
   95 
   96   # Reset (but do not restart) manual recon tasks.
   97   db_do ($dbh, 'UPDATE trecon_task  SET status = -1, summary = "cancelled" WHERE id_recon_server = ? AND status <> -1 AND interval_sweep = 0',
   98        get_server_id ($dbh, $config->{'servername'}, DISCOVERYSERVER));
   99 
  100   # Call the constructor of the parent class
  101   my $self = $class->SUPER::new($config, DISCOVERYSERVER, \&PandoraFMS::DiscoveryServer::data_producer, \&PandoraFMS::DiscoveryServer::data_consumer, $dbh);
  102   
  103   bless $self, $class;
  104   return $self;
  105 }
  106 
  107 ################################################################################
  108 # Run.
  109 ################################################################################
  110 sub run ($) {
  111   my $self = shift;
  112   my $pa_config = $self->getConfig ();
  113   my $dbh = $self->getDBH();
  114   
  115   print_message ($pa_config, " [*] Starting " . $pa_config->{'rb_product_name'} . " Discovery Server.", 1);
  116   my $threads = $pa_config->{'recon_threads'};
  117 
  118   # Use hightest value
  119   if ($pa_config->{'discovery_threads'}  > $pa_config->{'recon_threads'}) {
  120     $threads = $pa_config->{'discovery_threads'};
  121   }
  122   $self->setNumThreads($threads);
  123   $self->SUPER::run (\@TaskQueue, \%PendingTasks, $Sem, $TaskSem);
  124 }
  125 
  126 ################################################################################
  127 # Data producer.
  128 ################################################################################
  129 sub data_producer ($) {
  130   my $self = shift;
  131   my ($pa_config, $dbh) = ($self->getConfig (), $self->getDBH ());
  132   
  133   my @tasks;
  134   
  135   my $server_id = get_server_id ($dbh, $pa_config->{'servername'}, $self->getServerType ());
  136   return @tasks unless defined ($server_id);
  137   
  138   # Manual tasks have interval_sweep = 0
  139   # Manual tasks are "forced" like the other, setting the utimestamp to 1
  140   # By default, after create a tasks it takes the utimestamp to 0
  141   # Status -1 means "done".
  142   my @rows;
  143   if (pandora_is_master($pa_config) == 0) {
  144     @rows = get_db_rows ($dbh, 'SELECT * FROM trecon_task 
  145       WHERE id_recon_server = ?
  146       AND disabled = 0
  147       AND ((utimestamp = 0 AND interval_sweep != 0 OR status = 1)
  148         OR (status = -1 AND interval_sweep > 0 AND (utimestamp + interval_sweep) < UNIX_TIMESTAMP()))', $server_id);
  149   } else {
  150     @rows = get_db_rows ($dbh, 'SELECT * FROM trecon_task 
  151       WHERE (id_recon_server = ? OR id_recon_server = ANY(SELECT id_server FROM tserver WHERE status <> 1 AND server_type = ?))
  152       AND disabled = 0
  153       AND ((utimestamp = 0 AND interval_sweep != 0 OR status = 1)
  154         OR (status = -1 AND interval_sweep > 0 AND (utimestamp + interval_sweep) < UNIX_TIMESTAMP()))', $server_id, DISCOVERYSERVER);
  155   }
  156 
  157   foreach my $row (@rows) {
  158     
  159     # Update task status
  160     update_recon_task ($dbh, $row->{'id_rt'}, 1);
  161     
  162     push (@tasks, $row->{'id_rt'});
  163   }
  164   
  165   return @tasks;
  166 }
  167 
  168 ################################################################################
  169 # Data consumer.
  170 ################################################################################
  171 sub data_consumer ($$) {
  172   my ($self, $task_id) = @_;
  173   my ($pa_config, $dbh) = ($self->getConfig (), $self->getDBH ());
  174 
  175   # Get server id.
  176   my $server_id = get_server_id($dbh, $pa_config->{'servername'}, $self->getServerType());
  177 
  178   # Get recon task data 
  179   my $task = get_db_single_row ($dbh, 'SELECT * FROM trecon_task WHERE id_rt = ?', $task_id);   
  180   return -1 unless defined ($task);
  181 
  182   # Is it a recon script?
  183   if (defined ($task->{'id_recon_script'}) && ($task->{'id_recon_script'} != 0)) {
  184     exec_recon_script ($pa_config, $dbh, $task);
  185     return;
  186   } else {
  187     logger($pa_config, 'Starting recon task for net ' . $task->{'subnet'} . '.', 10);
  188   }
  189 
  190   eval {
  191     my @subnets = split(/,/, safe_output($task->{'subnet'}));
  192     my @communities = split(/,/, safe_output($task->{'snmp_community'}));
  193     my @auth_strings = ();
  194     if(defined($task->{'auth_strings'})) {
  195       @auth_strings = split(/,/, safe_output($task->{'auth_strings'}));
  196     }
  197 
  198     my $main_event = pandora_event($pa_config,
  199       "[Discovery] Execution summary",
  200       $task->{'id_group'}, 0, 0, 0, 0, 'system', 0, $dbh
  201     );
  202 
  203     my %cnf_extra;
  204     
  205     my $r = enterprise_hook(
  206       'discovery_generate_extra_cnf',
  207       [
  208         $pa_config,
  209         $dbh, $task,
  210         \%cnf_extra
  211       ]
  212     );
  213     if (defined($r) && $r eq 'ERR') {
  214       # Could not generate extra cnf, skip this task.
  215       return;
  216     }
  217 
  218     if ($task->{'type'} == DISCOVERY_APP_SAP) {
  219       # SAP TASK, retrieve license.
  220       $task->{'sap_license'} = pandora_get_config_value(
  221         $dbh,
  222         'sap_license'
  223       );
  224 
  225       # Retrieve credentials for task (optional).
  226       if (defined($task->{'auth_strings'})
  227         && $task->{'auth_strings'} ne ''
  228       ) {
  229         my $key = credential_store_get_key(
  230           $pa_config,
  231           $dbh,
  232           $task->{'auth_strings'}
  233         );
  234 
  235         # Inside an eval, here it shouln't fail unless bad configured.
  236         $task->{'username'} = $key->{'username'};
  237         $task->{'password'} = $key->{'password'};
  238 
  239       }
  240     }
  241 
  242     if (!is_empty($task->{'recon_ports'})) {
  243       # Accept only valid symbols.
  244       if ($task->{'recon_ports'} !~ /[\d\-\,\ ]+/) {
  245         $task->{'recon_ports'} = '';
  246       }
  247     }
  248 
  249     my $recon = new PandoraFMS::Recon::Base(
  250       communities => \@communities,
  251       dbh => $dbh,
  252       group_id => $task->{'id_group'},
  253       id_os => $task->{'id_os'},
  254       id_network_profile => $task->{'id_network_profile'},
  255       os_detection => $task->{'os_detect'},
  256       parent_detection => $task->{'parent_detection'},
  257       parent_recursion => $task->{'parent_recursion'},
  258       pa_config => $pa_config,
  259       recon_ports => $task->{'recon_ports'},
  260       resolve_names => $task->{'resolve_names'},
  261       snmp_auth_user => $task->{'snmp_auth_user'},
  262       snmp_auth_pass => $task->{'snmp_auth_pass'},
  263       snmp_auth_method => $task->{'snmp_auth_method'},
  264       snmp_checks => $task->{'snmp_checks'},
  265       snmp_enabled => $task->{'snmp_enabled'},
  266       snmp_privacy_method => $task->{'snmp_privacy_method'},
  267       snmp_privacy_pass => $task->{'snmp_privacy_pass'},
  268       snmp_security_level => $task->{'snmp_security_level'},
  269       snmp_timeout => $task->{'snmp_timeout'},
  270       snmp_version => $task->{'snmp_version'},
  271       subnets => \@subnets,
  272       task_id => $task->{'id_rt'},
  273       vlan_cache_enabled => $task->{'vlan_enabled'},
  274       wmi_enabled => $task->{'wmi_enabled'},
  275       rcmd_enabled => $task->{'rcmd_enabled'},
  276       rcmd_timeout => $pa_config->{'rcmd_timeout'},
  277       rcmd_timeout_bin => $pa_config->{'rcmd_timeout_bin'},
  278       auth_strings_array => \@auth_strings,
  279       autoconfiguration_enabled => $task->{'autoconfiguration_enabled'},
  280       main_event_id => $main_event,
  281       server_id => $server_id,
  282       %{$pa_config},
  283       task_data => $task,
  284       public_url => PandoraFMS::Config::pandora_get_tconfig_token($dbh, 'public_url', ''),
  285       %cnf_extra
  286     );
  287 
  288     $recon->scan();
  289 
  290     # Clean tmp file.
  291     if (defined($cnf_extra{'creds_file'})
  292     && -f $cnf_extra{'creds_file'}) {
  293     unlink($cnf_extra{'creds_file'});
  294   }
  295 
  296 
  297     # Clean one shot tasks
  298     if ($task->{'type'} eq DISCOVERY_DEPLOY_AGENTS) {
  299       db_delete_limit($dbh, ' trecon_task ', ' id_rt = ? ', 1, $task->{'id_rt'});   
  300     }
  301   };
  302   if ($@) {
  303     logger(
  304       $pa_config,
  305       'Cannot execute Discovery task: ' . safe_output($task->{'name'}) . $@,
  306       10
  307     );
  308     update_recon_task ($dbh, $task_id, -1);
  309     return;
  310   }
  311 }
  312 
  313 ################################################################################
  314 # Update recon task status.
  315 ################################################################################
  316 sub update_recon_task ($$$) {
  317   my ($dbh, $id_task, $status) = @_;
  318   
  319   db_do ($dbh, 'UPDATE trecon_task SET utimestamp = ?, status = ? WHERE id_rt = ?', time (), $status, $id_task);
  320 } 
  321 
  322 ################################################################################
  323 # Executes recon scripts
  324 ################################################################################
  325 sub exec_recon_script ($$$) {
  326   my ($pa_config, $dbh, $task) = @_;
  327   
  328   # Get recon plugin data   
  329   my $script = get_db_single_row ($dbh, 'SELECT * FROM trecon_script WHERE id_recon_script = ?', $task->{'id_recon_script'});
  330   return -1 unless defined ($script);
  331   
  332   logger($pa_config, 'Executing recon script ' . safe_output($script->{'name'}), 10);
  333   
  334   my $command = safe_output($script->{'script'});
  335   
  336   my $macros = safe_output($task->{'macros'});
  337 
  338   # \r and \n should be escaped for p_decode_json().
  339   $macros =~ s/\n/\\n/g;
  340   $macros =~ s/\r/\\r/g;
  341   my $decoded_macros;
  342   
  343   if ($macros) {
  344     eval {
  345       $decoded_macros = p_decode_json($pa_config, $macros);
  346     };
  347   }
  348   
  349   my $macros_parameters = '';
  350   
  351   # Add module macros as parameter
  352   if(ref($decoded_macros) eq "HASH") {
  353     # Convert the hash to a sorted array
  354     my @sorted_macros;
  355     while (my ($i, $m) = each (%{$decoded_macros})) {
  356       $sorted_macros[$i] = $m;
  357     }
  358 
  359     # Remove the 0 position     
  360     shift @sorted_macros;
  361 
  362     foreach my $m (@sorted_macros) {
  363       $macros_parameters = $macros_parameters . ' "' . $m->{"value"} . '"';
  364     }
  365   }
  366 
  367   my $ent_script = 0;
  368   my $args = enterprise_hook(
  369     'discovery_custom_recon_scripts',
  370     [$pa_config, $dbh, $task, $script]
  371   );
  372   if (!$args) {
  373     $args = '"'.$task->{'id_rt'}.'" ';
  374     $args .= '"'.$task->{'id_group'}.'" ';
  375     $args .= $macros_parameters;
  376   } else {
  377     $ent_script = 1;
  378   }
  379   
  380   if (-x $command) {
  381     my $exec_output = `$command $args`;
  382     logger($pa_config, "Execution output: \n". $exec_output, 10);
  383   } else {
  384     logger($pa_config, "Cannot execute recon task command $command.", 10);
  385   }
  386   
  387   # Only update the timestamp in case something went wrong. The script should set the status.
  388   db_do ($dbh, 'UPDATE trecon_task SET utimestamp = ? WHERE id_rt = ?', time (), $task->{'id_rt'});
  389 
  390   if ($ent_script == 1) {
  391     enterprise_hook('discovery_clean_custom_recon',[$pa_config, $dbh, $task, $script]);
  392   }
  393   
  394   logger($pa_config, 'Done executing recon script ' . safe_output($script->{'name'}), 10);
  395   return 0;
  396 }
  397 
  398 ################################################################################
  399 # Guess the OS using xprobe2 or nmap.
  400 ################################################################################
  401 sub PandoraFMS::Recon::Base::guess_os($$;$) {
  402   my ($self, $device, $string_flag) = @_;
  403 
  404   return $self->{'os_id'}{$device} if defined($self->{'os_id'}{$device});
  405 
  406   $DEVNULL = '/dev/null' if (!defined($DEVNULL));
  407   $DEVNULL = '/NUL' if ($^O =~ /win/i && !defined($DEVNULL));
  408 
  409   # OS detection disabled. Use the device type.
  410   if ($self->{'os_detection'} == 0) {
  411     my $device_type = $self->get_device_type($device);
  412     return OS_OTHER unless defined($device_type);
  413 
  414     return OS_ROUTER if ($device_type eq 'router');
  415     return OS_SWITCH if ($device_type eq 'switch');
  416     return OS_OTHER;
  417   }
  418 
  419   # Use xprobe2 if available
  420   if (-x $self->{'pa_config'}->{'xprobe2'}) {
  421     my $return = `"$self->{pa_config}->{xprobe2}" $device 2>$DEVNULL`;
  422     if ($? == 0) {
  423       if($return =~ /Running OS:(.*)/) {
  424         my $str_os = $1;
  425         return $str_os if is_enabled($string_flag);
  426         return pandora_get_os($self->{'dbh'}, $str_os);
  427       }
  428     }
  429   }
  430   
  431   # Use nmap by default
  432   if (-x $self->{'pa_config'}->{'nmap'}) {
  433     my $return = `"$self->{pa_config}->{nmap}" -F -O $device 2>$DEVNULL`;
  434     return OS_OTHER if ($? != 0);
  435 
  436     if ($return =~ /Aggressive OS guesses:\s*(.*)/) {
  437       my $str_os = $1;
  438       return $str_os if is_enabled($string_flag);
  439       return pandora_get_os($self->{'dbh'}, $str_os);
  440     }
  441   }
  442 
  443   return OS_OTHER;
  444 }
  445 
  446 ################################################################################
  447 # Returns the number of open ports from the given list.
  448 ################################################################################
  449 sub PandoraFMS::Recon::Base::tcp_scan ($$) {
  450   my ($self, $host) = @_;
  451 
  452   return if is_empty($host);
  453   return if is_empty($self->{'recon_ports'});
  454 
  455   my $r = `"$self->{pa_config}->{nmap}" -p$self->{recon_ports} $host`;
  456 
  457   # Same as ""| grep open | wc -l" but multi-OS;
  458   my $open_ports = () = $r =~ /open/gm;
  459 
  460   return $open_ports;
  461 }
  462 
  463 ################################################################################
  464 # Verifies if a module will be normal.
  465 ################################################################################
  466 sub PandoraFMS::Recon::Base::test_module($$) {
  467   my ($self, $addr, $module) = @_;
  468 
  469   # Default values.
  470   my $test = {
  471     %{$module},
  472     'ip_target' => $addr,
  473   };
  474 
  475   if (is_enabled($module->{'__module_component'})) {
  476     # Component. Translate some fields.
  477     $test->{'id_tipo_modulo'} = $module->{'type'};
  478   } else {
  479     # Module.
  480     $module->{'type'} = $module->{'module_type'} if is_empty($module->{'type'});
  481 
  482     if (defined($module->{'type'})) {
  483       if(!defined($self->{'module_types'}{$module->{'type'}})) {
  484         $self->{'module_types'}{$module->{'type'}} = get_module_id(
  485           $self->{'dbh'},$module->{'type'}
  486         );
  487       }
  488 
  489       $test->{'id_tipo_modulo'} = $self->{'module_types'}{$module->{'type'}};
  490     }
  491   }
  492 
  493   my $value;
  494 
  495   # 1. Try to retrieve value.
  496   if ($test->{'id_tipo_modulo'} >= 15 && $test->{'id_tipo_modulo'} <= 18) {
  497     # SNMP
  498     $value = $self->call(
  499       'snmp_get_value',
  500       $test->{'ip_target'},
  501       $test->{'snmp_oid'}
  502     );
  503   } elsif ($test->{'id_tipo_modulo'} == 6) {
  504     # ICMP - alive - already tested.
  505     $value = 1;
  506 
  507   } elsif ($test->{'id_tipo_modulo'} == 7) {
  508     # ICMP - latency
  509     $value = pandora_ping_latency(
  510       $self->{'pa_config'},
  511       $test->{'ip_target'},
  512       $test->{'max_timeout'},
  513       $test->{'max_retries'},
  514     );
  515 
  516   } elsif (($test->{'id_tipo_modulo'} >= 1 && $test->{'id_tipo_modulo'} <= 5)
  517     || ($test->{'id_tipo_modulo'} >= 21 && $test->{'id_tipo_modulo'} <= 23)
  518   ) {
  519     # Generic, plugins. (21-23 ASYNC)
  520     if ($test->{'id_modulo'} == 6) {
  521 
  522       return 0 unless $self->wmi_responds($addr);
  523 
  524       # WMI commands.
  525       $value = $self->call(
  526         'wmi_get_value',
  527         $test->{'ip_target'},
  528         # WMI query.
  529         $test->{'snmp_oid'},
  530         # Column
  531         $test->{'tcp_port'}
  532       );
  533     } elsif(is_enabled($test->{'id_plugin'})) {
  534       # XXX TODO: Test plugins. How to identify arguments? and values?
  535       # Disabled until we can ensure result.
  536       return 0;
  537     }
  538 
  539   } elsif ($test->{'id_tipo_modulo'} >= 34 && $test->{'id_tipo_modulo'} <= 37) {
  540     # Remote command.
  541     return 0 unless $self->rcmd_responds($addr);
  542 
  543     my $target_os;
  544     if ($test->{'custom_string_2'} =~ /inherited/i) {
  545       $target_os = pandora_get_os(
  546         $self->{'dbh'},
  547         $self->{'os_cache'}{$test->{'ip_target'}}
  548       );
  549     } else {
  550       $target_os = pandora_get_os($self->{'dbh'}, $test->{'custom_string_2'});
  551     }
  552 
  553     $value = enterprise_hook(
  554       'remote_execution_module',
  555       [
  556         # pa_config,
  557         $self->{'pa_config'},
  558         # dbh,
  559         $self->{'dbh'},
  560         # module,
  561         $test,
  562         # target_os,
  563         $target_os,
  564         # ip_target,
  565         $test->{'ip_target'},
  566         # tcp_port
  567         $test->{'tcp_port'}
  568       ]
  569     );
  570 
  571     chomp($value);
  572 
  573     return 0 unless defined($value);
  574 
  575   } elsif ($test->{'id_tipo_modulo'} >= 8 && $test->{'id_tipo_modulo'} <= 11) {
  576     # TCP
  577     return 0 unless is_numeric($test->{'tcp_port'})
  578       && $test->{'tcp_port'} > 0
  579       && $test->{'tcp_port'} <= 65535;
  580 
  581     my $result;
  582 
  583     PandoraFMS::NetworkServer::pandora_query_tcp(
  584       $self->{'pa_config'},
  585       $test->{'tcp_port'},
  586       $test->{'ip_target'},
  587       \$result,
  588       \$value,
  589       $test->{'tcp_send'},
  590       $test->{'tcp_rcv'},
  591       $test->{'id_tipo_modulo'},
  592       $test->{'max_timeout'},
  593       $test->{'max_retries'},
  594       '<Discovery testing>',
  595     );
  596 
  597     # Result 0 is OK, 1 failed
  598     return 0 unless defined($result) && $result == 0;
  599     return 0 unless defined($value);
  600 
  601   }
  602 
  603   # Invalid data (empty or not defined)
  604   return 0 if is_empty($value);
  605 
  606   # 2. Check if value matches type definition and fits thresholds.
  607   if (is_in_array(
  608         [1,2,4,5,6,7,8,9,11,15,16,18,21,22,25,30,31,32,34,35,37],
  609         $test->{'id_tipo_modulo'}
  610       )
  611   ) {
  612     # Numeric. Remove " symbols if any.
  613     $value =~ s/\"//g;
  614     return 0 unless is_numeric($value);
  615 
  616     if (is_in_array([2,6,9,18,21,31,35], $test->{'id_tipo_modulo'})) {
  617       # Boolean.
  618       if (!is_enabled($test->{'critical_inverse'})) {
  619         return 0 if $value == 0;
  620       } else {
  621         return 0 if $value != 0;
  622       }
  623     }
  624 
  625     my $thresholds_defined = 0;
  626 
  627     if ((!defined($test->{'min_critical'}) || $test->{'min_critical'} == 0)
  628       && (!defined($test->{'max_critical'}) || $test->{'max_critical'} == 0)
  629     ) {
  630       # In Default 0,0 do not test.or not defined
  631       $thresholds_defined = 0;
  632     } else {
  633       # min or max are diferent from 0
  634       $thresholds_defined = 1;
  635     }
  636 
  637     if ($thresholds_defined > 0) {
  638       # Check thresholds.
  639       if (!is_enabled($test->{'critical_inverse'})) {
  640         return 0 if $value >= $test->{'min_critical'} && $value <= $test->{'max_critical'};
  641       } else {
  642         return 0 if $value < $test->{'min_critical'} && $value > $test->{'max_critical'};
  643       }
  644     }
  645 
  646   } else {
  647     # String.
  648     if (!is_enabled($test->{'critical_inverse'})) {
  649       return 0 if !is_empty($test->{'str_critical'}) && $value =~ /$test->{'str_critical'}/;
  650     } else {
  651       return 0 if !is_empty($test->{'str_critical'}) && $value !~ /$test->{'str_critical'}/;
  652     }
  653 
  654   }
  655 
  656   # Success.
  657   return 1;
  658 
  659 }
  660 
  661 ################################################################################
  662 # Create interface modules for the given agent (if needed).
  663 ################################################################################
  664 sub PandoraFMS::Recon::Base::create_interface_modules($$) {
  665   my ($self, $device) = @_;
  666 
  667   # Add interfaces to the agent if it responds to SNMP.
  668   return unless ($self->is_snmp_discovered($device));
  669   my $community = $self->get_community($device);
  670 
  671   my @output = $self->snmp_get_value_array($device, $PandoraFMS::Recon::Base::IFINDEX);
  672   foreach my $if_index (@output) {
  673     next unless ($if_index =~ /^[0-9]+$/);
  674 
  675     # Check the status of the interface.
  676     my $if_status = $self->snmp_get_value($device, "$PandoraFMS::Recon::Base::IFOPERSTATUS.$if_index");
  677     next unless $if_status == 1;
  678 
  679     # Fill the module description with the IP and MAC addresses.
  680     my $mac = $self->get_if_mac($device, $if_index);
  681     my $ip = $self->get_if_ip($device, $if_index);
  682     my $if_desc = ($mac ne '' ? "MAC $mac " : '') . ($ip ne '' ? "IP $ip" : '');
  683 
  684     # Get the name of the network interface.
  685     my $if_name = $self->snmp_get_value($device, "$PandoraFMS::Recon::Base::IFNAME.$if_index");
  686     $if_name = "if$if_index" unless defined ($if_name);
  687     $if_name =~ s/"//g;
  688     $if_name = clean_blank($if_name);
  689 
  690     # Interface status module.
  691     $self->call(
  692       'add_module',
  693       $device,
  694       {
  695         'id_tipo_modulo' => 18,
  696         'id_modulo' => 2,
  697         'name' => $if_name."_ifOperStatus",
  698         'descripcion' => safe_input(
  699           $if_desc
  700         ),
  701         'ip_target' => $device,
  702         'tcp_send' => $self->{'task_data'}{'snmp_version'},
  703         'custom_string_1' => $self->{'task_data'}{'snmp_privacy_method'},
  704         'custom_string_2' => $self->{'task_data'}{'snmp_privacy_pass'},
  705         'custom_string_3' => $self->{'task_data'}{'snmp_security_level'},
  706         'plugin_parameter' => $self->{'task_data'}{'snmp_auth_method'},
  707         'plugin_user' => $self->{'task_data'}{'snmp_auth_user'},
  708         'plugin_pass' => $self->{'task_data'}{'snmp_auth_pass'},
  709         'snmp_community' => $community,
  710         'snmp_oid' => "$PandoraFMS::Recon::Base::IFOPERSTATUS.$if_index"
  711       }
  712     );
  713 
  714     # Incoming traffic module.
  715     my $if_hc_in_octets = $self->snmp_get_value($device, "$PandoraFMS::Recon::Base::IFHCINOCTECTS.$if_index");
  716     if (defined($if_hc_in_octets)) {
  717       # Use HC counters.
  718       # ifHCInOctets
  719       $self->call(
  720         'add_module',
  721         $device,
  722         {
  723           'id_tipo_modulo' => 16,
  724           'id_modulo' => 2,
  725           'name' => $if_name."_ifHCInOctets",
  726           'descripcion' => safe_input(
  727             'The total number of octets received on the interface, including framing characters. This object is a 64-bit version of ifInOctets.'
  728           ),
  729           'ip_target' => $device,
  730           'tcp_send' => $self->{'task_data'}{'snmp_version'},
  731           'custom_string_1' => $self->{'task_data'}{'snmp_privacy_method'},
  732           'custom_string_2' => $self->{'task_data'}{'snmp_privacy_pass'},
  733           'custom_string_3' => $self->{'task_data'}{'snmp_security_level'},
  734           'plugin_parameter' => $self->{'task_data'}{'snmp_auth_method'},
  735           'plugin_user' => $self->{'task_data'}{'snmp_auth_user'},
  736           'plugin_pass' => $self->{'task_data'}{'snmp_auth_pass'},
  737           'snmp_community' => $community,
  738           'snmp_oid' => "$PandoraFMS::Recon::Base::IFHCINOCTECTS.$if_index"
  739         }
  740       );
  741     } else {
  742       # Use 32b counters.
  743       # ifInOctets
  744       $self->call(
  745         'add_module',
  746         $device,
  747         {
  748           'id_tipo_modulo' => 16,
  749           'id_modulo' => 2,
  750           'name' => $if_name."_ifInOctets",
  751           'descripcion' => safe_input(
  752             'The total number of octets received on the interface, including framing characters.'
  753           ),
  754           'ip_target' => $device,
  755           'tcp_send' => $self->{'task_data'}{'snmp_version'},
  756           'custom_string_1' => $self->{'task_data'}{'snmp_privacy_method'},
  757           'custom_string_2' => $self->{'task_data'}{'snmp_privacy_pass'},
  758           'custom_string_3' => $self->{'task_data'}{'snmp_security_level'},
  759           'plugin_parameter' => $self->{'task_data'}{'snmp_auth_method'},
  760           'plugin_user' => $self->{'task_data'}{'snmp_auth_user'},
  761           'plugin_pass' => $self->{'task_data'}{'snmp_auth_pass'},
  762           'snmp_community' => $community,
  763           'snmp_oid' => "$PandoraFMS::Recon::Base::IFINOCTECTS.$if_index"
  764         }
  765       );
  766     }
  767 
  768     # Outgoing traffic module.
  769     my $if_hc_out_octets = $self->snmp_get_value($device, "$PandoraFMS::Recon::Base::IFHCOUTOCTECTS.$if_index");
  770     if (defined($if_hc_out_octets)) {
  771       # Use HC counters.
  772       # ifHCOutOctets
  773       $self->call(
  774         'add_module',
  775         $device,
  776         {
  777           'id_tipo_modulo' => 16,
  778           'id_modulo' => 2,
  779           'name' => $if_name."_ifHCOutOctets",
  780           'descripcion' => safe_input(
  781             'The total number of octets received on the interface, including framing characters. This object is a 64-bit version of ifOutOctets.'
  782           ),
  783           'ip_target' => $device,
  784           'tcp_send' => $self->{'task_data'}{'snmp_version'},
  785           'custom_string_1' => $self->{'task_data'}{'snmp_privacy_method'},
  786           'custom_string_2' => $self->{'task_data'}{'snmp_privacy_pass'},
  787           'custom_string_3' => $self->{'task_data'}{'snmp_security_level'},
  788           'plugin_parameter' => $self->{'task_data'}{'snmp_auth_method'},
  789           'plugin_user' => $self->{'task_data'}{'snmp_auth_user'},
  790           'plugin_pass' => $self->{'task_data'}{'snmp_auth_pass'},
  791           'snmp_community' => $community,
  792           'snmp_oid' => "$PandoraFMS::Recon::Base::IFHCOUTOCTECTS.$if_index"
  793         }
  794       );
  795     } else { 
  796       # Use 32b counters.
  797       # ifOutOctets
  798       $self->call(
  799         'add_module',
  800         $device,
  801         {
  802           'id_tipo_modulo' => 16,
  803           'id_modulo' => 2,
  804           'name' => $if_name."_ifOutOctets",
  805           'descripcion' => safe_input(
  806             'The total number of octets received on the interface, including framing characters.'
  807           ),
  808           'ip_target' => $device,
  809           'tcp_send' => $self->{'task_data'}{'snmp_version'},
  810           'custom_string_1' => $self->{'task_data'}{'snmp_privacy_method'},
  811           'custom_string_2' => $self->{'task_data'}{'snmp_privacy_pass'},
  812           'custom_string_3' => $self->{'task_data'}{'snmp_security_level'},
  813           'plugin_parameter' => $self->{'task_data'}{'snmp_auth_method'},
  814           'plugin_user' => $self->{'task_data'}{'snmp_auth_user'},
  815           'plugin_pass' => $self->{'task_data'}{'snmp_auth_pass'},
  816           'snmp_community' => $community,
  817           'snmp_oid' => "$PandoraFMS::Recon::Base::IFOUTOCTECTS.$if_index"
  818         }
  819       );
  820     }
  821   }
  822 
  823 }
  824 
  825 ################################################################################
  826 # Add wmi modules to the given host.
  827 ################################################################################
  828 sub PandoraFMS::Recon::Base::create_wmi_modules {
  829   my ($self, $target) = @_;
  830 
  831   # Add modules to the agent if it responds to WMI.
  832   return unless ($self->wmi_responds($target));
  833 
  834   my $key = $self->wmi_credentials_key($target);
  835   my $creds = $self->call('get_credentials', $key);
  836 
  837   # Add modules.
  838   # CPU.
  839   my @cpus = $self->wmi_get_value_array($target, 'SELECT DeviceId FROM Win32_Processor', 0);
  840   foreach my $cpu (@cpus) {
  841     $self->add_module(
  842       $target,
  843       {
  844         'ip_target' => $target,
  845         'snmp_oid' => "SELECT LoadPercentage FROM Win32_Processor WHERE DeviceId=\'$cpu\'",
  846         'plugin_user' => $creds->{'username'},
  847         'plugin_pass' => $creds->{'password'},
  848         'tcp_port' => 1,
  849         'name' => "CPU Load $cpu",
  850         'descripcion' => safe_input("Load for $cpu (%)"),
  851         'id_tipo_modulo' => 1,
  852         'id_modulo' => 6,
  853         'unit' => '%',
  854       }
  855     );
  856   }
  857 
  858   # Memory.
  859   my $mem = $self->wmi_get_value($target, 'SELECT FreePhysicalMemory FROM Win32_OperatingSystem', 0);
  860   if (defined($mem)) {
  861     $self->add_module(
  862       $target,
  863       {
  864         'ip_target' => $target,
  865         'snmp_oid' => "SELECT FreePhysicalMemory, TotalVisibleMemorySize FROM Win32_OperatingSystem",
  866         'plugin_user' => $creds->{'username'},
  867         'plugin_pass' => $creds->{'password'},
  868         'tcp_port' => 0,
  869         'name' => 'FreeMemory',
  870         'descripcion' => safe_input('Free memory'),
  871         'id_tipo_modulo' => 1,
  872         'id_modulo' => 6,
  873         'unit' => 'KB',
  874       }
  875     );
  876   }
  877 
  878   # Disk.
  879   my @units = $self->wmi_get_value_array($target, 'SELECT DeviceID FROM Win32_LogicalDisk', 0);
  880   foreach my $unit (@units) {
  881     $self->add_module(
  882       $target,
  883       {
  884         'ip_target' => $target,
  885         'snmp_oid' => "SELECT FreeSpace FROM Win32_LogicalDisk WHERE DeviceID='$unit'",
  886         'plugin_user' => $creds->{'username'},
  887         'plugin_pass' => $creds->{'password'},
  888         'tcp_port' => 1,
  889         'name' => "FreeDisk $unit",
  890         'descripcion' => safe_input('Available disk space in kilobytes'),
  891         'id_tipo_modulo' => 1,
  892         'id_modulo' => 6,
  893         'unit' => 'KB',
  894       }
  895     );
  896   }
  897 
  898 }
  899 
  900 ################################################################################
  901 # Create network profile modules for the given agent.
  902 ################################################################################
  903 sub PandoraFMS::Recon::Base::create_network_profile_modules($$) {
  904   my ($self, $device) = @_;
  905 
  906   my @template_ids = ();
  907 
  908   if (is_enabled($self->{'task_data'}{'auto_monitor'})) {
  909     # Apply PEN monitoring template (HW).
  910     my @pen_templates= get_pen_templates($self->{'dbh'}, $self->get_pen($device));
  911     # Join.
  912     @template_ids = (@template_ids, @pen_templates);
  913   } else {
  914     # Return if no specific templates are selected.
  915     return if is_empty($self->{'id_network_profile'});
  916   }
  917 
  918   push @template_ids, split /,/, $self->{'id_network_profile'}
  919     unless is_empty($self->{'id_network_profile'});
  920 
  921   my $data = $self->{'agents_found'}{$device};
  922 
  923   foreach my $t_id (@template_ids) {
  924     # 1. Retrieve template info.
  925     my $template = get_nc_profile_advanced($self->{'dbh'}, $t_id);
  926 
  927     # 2. Verify Private Enterprise Number matches (PEN)
  928     if (defined($template->{'pen'})) {
  929       my @penes = split(',', $template->{'pen'});
  930 
  931       next unless (is_in_array(\@penes, $self->get_pen($device)));
  932     }
  933 
  934     # 3. Retrieve module list from target template.
  935     my @np_components = get_db_rows(
  936       $self->{'dbh'},
  937       'SELECT * FROM tnetwork_profile_component WHERE id_np = ?',
  938       $t_id
  939     );
  940 
  941     foreach my $np_component (@np_components) {
  942       # 4. Register each module (candidate). 'add_module' will test them.
  943       my $component = get_db_single_row(
  944         $self->{'dbh'},
  945         'SELECT * FROM tnetwork_component WHERE id_nc = ?',
  946         $np_component->{'id_nc'}
  947       );
  948 
  949       # Tag cleanup.
  950       if (!is_empty($component->{'tags'})) {
  951         my @tags = map {
  952           if ($_ > 0) { $_ }
  953           else {}
  954         } split ',', $component->{'tags'};
  955 
  956         $component->{'tags'} = join ',', @tags;
  957       }
  958 
  959       $component->{'name'} = safe_output($component->{'name'});
  960       if ($component->{'type'} >= 15 && $component->{'type'} <= 18) {
  961         $component->{'snmp_community'} = safe_output($self->get_community($device));
  962         $component->{'tcp_send'} = $self->{'snmp_version'};
  963         $component->{'custom_string_1'} = $self->{'snmp_privacy_method'};
  964         $component->{'custom_string_2'} = $self->{'snmp_privacy_pass'};
  965         $component->{'custom_string_3'} = $self->{'snmp_security_level'};
  966         $component->{'plugin_parameter'} = $self->{'snmp_auth_method'};
  967         $component->{'plugin_user'} = $self->{'snmp_auth_user'};
  968         $component->{'plugin_pass'} = $self->{'snmp_auth_pass'};
  969       }
  970 
  971       if ($component->{'type'} >= 34 && $component->{'type'} <= 37) {
  972         # Update module credentials.
  973         $component->{'custom_string_1'} = $self->rcmd_credentials_key($device);
  974         $component->{'custom_string_2'} = pandora_get_os_by_id(
  975           $self->{'dbh'},
  976           $self->guess_os($device)
  977         );
  978       }
  979 
  980       $component->{'__module_component'} = 1;
  981 
  982       # 3. Try to register module into monitoring list.
  983       $self->call('add_module', $device, $component);
  984     }
  985   }
  986 
  987 }
  988 
  989 ################################################################################
  990 # Retrieve a key from credential store.
  991 ################################################################################
  992 sub PandoraFMS::Recon::Base::get_credentials {
  993   my ($self, $key_index) = @_;
  994 
  995   return credential_store_get_key(
  996     $self->{'pa_config'},
  997     $self->{'dbh'},
  998     $key_index
  999   );
 1000 }
 1001 
 1002 ################################################################################
 1003 # Create agents and modules reported by Recon::Base.
 1004 ################################################################################
 1005 sub PandoraFMS::Recon::Base::report_scanned_agents($;$) {
 1006   my ($self,$force) = @_;
 1007 
 1008   my $force_creation = $force;
 1009   $force_creation = 0 unless (is_enabled($force));
 1010 
 1011   #
 1012   # Creation
 1013   #
 1014 
 1015   if($force_creation == 1
 1016     || (defined($self->{'task_data'}{'review_mode'})
 1017         && $self->{'task_data'}{'review_mode'} == DISCOVERY_RESULTS)
 1018   ) {
 1019 
 1020     # Load cache.
 1021     my @rows = get_db_rows(
 1022       $self->{'dbh'},
 1023       'SELECT * FROM tdiscovery_tmp_agents WHERE `id_rt`=?',
 1024       $self->{'task_data'}{'id_rt'}
 1025     );
 1026 
 1027     # Return if no entries.
 1028     return unless scalar @rows > 0;
 1029 
 1030     my @agents;
 1031 
 1032     my $progress = 0;
 1033     my $step = 100.00 / scalar @rows;
 1034     foreach my $row (@rows) {
 1035       $progress += $step;
 1036       $self->call('update_progress', $progress);
 1037 
 1038       my $name = safe_output($row->{'label'});
 1039       my $checked = 0;
 1040       my $data;
 1041       eval {
 1042         local $SIG{__DIE__};
 1043         $data = p_decode_json($self->{'pa_config'}, decode_base64($row->{'data'}));
 1044       };
 1045       if ($@) {
 1046         $self->call('message', "ERROR JSON: $@", 3);
 1047       }
 1048 
 1049       # Agent could be 'not checked' unless  all modules are selected.
 1050       if (ref($data->{'modules'}) eq 'HASH') {
 1051         my @map = map {
 1052           my $name = $_->{'name'};
 1053           $name = $_->{'nombre'} if is_empty($name);
 1054 
 1055           if (is_enabled($_->{'checked'})
 1056             && $name ne 'Host Alive'
 1057           ) {
 1058             $name;
 1059           } else {}
 1060 
 1061         } values %{$data->{'modules'}};
 1062 
 1063         $checked = scalar  @map;
 1064       }
 1065 
 1066       $checked = $data->{'agent'}{'checked'} if
 1067         is_enabled($data->{'agent'}{'checked'})
 1068         && $checked < $data->{'agent'}{'checked'};
 1069 
 1070       # Register target agent if enabled.
 1071       if (is_enabled($checked)
 1072         || $force_creation
 1073       ) {
 1074         my $parent_id;
 1075         my $os_id = $data->{'agent'}{'id_os'};
 1076         if (is_empty($os_id)) {
 1077           $os_id = $self->guess_os($data->{'agent'}{'direccion'});
 1078         }
 1079 
 1080         $self->call('message', "Agent accepted: ".$data->{'agent'}{'nombre'}, 5);
 1081 
 1082         # Agent creation.
 1083         my $agent_id = $data->{'agent'}{'agent_id'};
 1084         my $agent_learning;
 1085         my $agent_data;
 1086 
 1087         if (defined($agent_id) && $agent_id > 0) {
 1088           $agent_data = get_db_single_row(
 1089             $self->{'dbh'},
 1090             'SELECT * FROM tagente WHERE id_agente = ?',
 1091             $agent_id
 1092           );
 1093           $agent_learning = $agent_data->{'modo'} if ref($agent_data) eq 'HASH';
 1094         }
 1095 
 1096         if (!defined($agent_learning)) {
 1097           # Agent id does not exists or is invalid.
 1098 
 1099           # Check if has been created by another process, if not found.
 1100           $agent_data = PandoraFMS::Core::locate_agent(
 1101             $self->{'pa_config'}, $self->{'dbh'}, $data->{'agent'}{'direccion'}
 1102           ) if ref($agent_data) ne 'HASH';
 1103 
 1104           $agent_id = $agent_data->{'id_agente'} if ref($agent_data) eq 'HASH';
 1105           if (ref($agent_data) eq 'HASH' && $agent_data->{'modo'} != 1) {
 1106             # Agent previously exists, but is not in learning mode, so skip
 1107             # modules scan and jump directly to parent analysis.
 1108             $data->{'agent'}{'agent_id'} = $agent_id;
 1109             push @agents, $data->{'agent'};
 1110             next;
 1111           }
 1112 
 1113           if (!defined($agent_id) || $agent_id <= 0 || !defined($agent_data)) {
 1114             # Agent creation.
 1115             $agent_id = pandora_create_agent(
 1116               $self->{'pa_config'}, $self->{'servername'}, $data->{'agent'}{'nombre'},
 1117               $data->{'agent'}{'direccion'}, $self->{'task_data'}{'id_group'}, $parent_id,
 1118               $os_id, $data->{'agent'}->{'description'},
 1119               $data->{'agent'}{'interval'}, $self->{'dbh'},
 1120               $data->{'agent'}{'timezone_offset'}, undef, undef, undef, undef,
 1121               undef, undef, 1, $data->{'agent'}{'alias'}
 1122             );
 1123 
 1124             # Add found IP addresses to the agent.
 1125             if (ref($data->{'other_ips'}) eq 'ARRAY') {
 1126               foreach my $ip_addr (@{$data->{'other_ips'}}) {
 1127                 my $addr_id = get_addr_id($self->{'dbh'}, $ip_addr);
 1128                 $addr_id = add_address($self->{'dbh'}, $ip_addr) unless ($addr_id > 0);
 1129                 next unless ($addr_id > 0);
 1130 
 1131                 # Assign the new address to the agent
 1132                 my $agent_addr_id = get_agent_addr_id($self->{'dbh'}, $addr_id, $agent_id);
 1133                 if ($agent_addr_id <= 0) {
 1134                   db_do(
 1135                     $self->{'dbh'}, 'INSERT INTO taddress_agent (`id_a`, `id_agent`)
 1136                                       VALUES (?, ?)', $addr_id, $agent_id
 1137                   );
 1138                 }
 1139               }
 1140             }
 1141 
 1142             # Agent autoconfiguration.
 1143             if (is_enabled($self->{'autoconfiguration_enabled'})) {
 1144               my $agent_data = PandoraFMS::DB::get_db_single_row(
 1145                 $self->{'dbh'},
 1146                 'SELECT * FROM tagente WHERE id_agente = ?',
 1147                 $agent_id
 1148               );
 1149 
 1150               # Update agent configuration once, after create agent.
 1151               enterprise_hook(
 1152                 'autoconfigure_agent',
 1153                 [
 1154                   $self->{'pa_config'},
 1155                   $data->{'agent'}{'direccion'},
 1156                   $agent_id,
 1157                   $agent_data,
 1158                   $self->{'dbh'},
 1159                   1
 1160                 ]
 1161               );
 1162             }
 1163 
 1164             if (defined($self->{'main_event_id'})) {
 1165               my $addresses_str = join(
 1166                 ',',
 1167                 $self->get_addresses(safe_output($data->{'agent'}{'nombre'}))
 1168               );
 1169 
 1170               pandora_extended_event(
 1171                 $self->{'pa_config'}, $self->{'dbh'},
 1172                 $self->{'main_event_id'},"[Discovery] New " 
 1173                   . $self->get_device_type(safe_output($data->{'agent'}{'nombre'}))
 1174                   . " found " . $data->{'agent'}{'nombre'} . " (" . $addresses_str
 1175                   . ") Agent $agent_id."
 1176               );
 1177             }
 1178 
 1179             $agent_learning = 1;
 1180           } else {
 1181             # Read from effective agent_id.
 1182             $agent_learning = get_db_value(
 1183               $self->{'dbh'},
 1184               'SELECT modo FROM tagente WHERE id_agente = ?',
 1185               $agent_id
 1186             );
 1187 
 1188             # Update new IPs.
 1189             # Add found IP addresses to the agent.
 1190             if (ref($data->{'other_ips'}) eq 'ARRAY') {
 1191               foreach my $ip_addr (@{$data->{'other_ips'}}) {
 1192                 my $addr_id = get_addr_id($self->{'dbh'}, $ip_addr);
 1193                 $addr_id = add_address($self->{'dbh'}, $ip_addr) unless ($addr_id > 0);
 1194                 next unless ($addr_id > 0);
 1195 
 1196                 # Assign the new address to the agent
 1197                 my $agent_addr_id = get_agent_addr_id($self->{'dbh'}, $addr_id, $agent_id);
 1198                 if ($agent_addr_id <= 0) {
 1199                   db_do(
 1200                     $self->{'dbh'}, 'INSERT INTO taddress_agent (`id_a`, `id_agent`)
 1201                                       VALUES (?, ?)', $addr_id, $agent_id
 1202                   );
 1203                 }
 1204               }
 1205             }
 1206           }
 1207 
 1208           $data->{'agent'}{'agent_id'} = $agent_id;
 1209         }
 1210 
 1211         $data->{'agent'}{'modo'} = $agent_learning;
 1212         $self->call('message', "Agent id: ".$data->{'agent'}{'agent_id'}, 5);
 1213 
 1214         # Create selected modules.
 1215         if(ref($data->{'modules'}) eq "HASH") {
 1216           foreach my $i (keys %{$data->{'modules'}}) {
 1217             my $module = $data->{'modules'}{$i};
 1218 
 1219             $module->{'name'} = $module->{'nombre'} if is_empty($module->{'name'});
 1220 
 1221             # Do not create any modules if the agent is not in learning mode.
 1222             next unless ($agent_learning == 1);
 1223 
 1224             # Host alive is always being created.
 1225             if ($module->{'name'} ne 'Host Alive') {
 1226               next unless (is_enabled($module->{'checked'}) || $force_creation);
 1227             }
 1228 
 1229             $self->call('message', "[$agent_id] Module: ".$module->{'name'}, 5);
 1230 
 1231             my $agentmodule_id = get_db_value(
 1232               $self->{'dbh'},
 1233               'SELECT id_agente_modulo FROM tagente_modulo
 1234                WHERE id_agente = ? AND nombre = ?',
 1235               $agent_id,
 1236               safe_input($module->{'name'})
 1237             );
 1238 
 1239             if (!is_enabled($agentmodule_id)) {
 1240               # Create.
 1241 
 1242               # Delete unwanted fields.
 1243               delete $module->{'agentmodule_id'};
 1244               delete $module->{'checked'};
 1245 
 1246               my $id_tipo_modulo = $module->{'id_tipo_modulo'};
 1247               $id_tipo_modulo = get_module_id($self->{'dbh'}, $module->{'type'})
 1248                 if is_empty($id_tipo_modulo);
 1249 
 1250               my $description = safe_output($module->{'description'});
 1251               $description = '' if is_empty($description);
 1252 
 1253               if (is_enabled($module->{'__module_component'})) {
 1254                 # Module from network component.
 1255                 delete $module->{'__module_component'};
 1256                 $agentmodule_id = pandora_create_module_from_network_component(
 1257                   $self->{'pa_config'},
 1258                   # Send a copy, not original, because of 'deletes'
 1259                   {
 1260                     %{$module},
 1261                     'name' => safe_input($module->{'name'}),
 1262                   },
 1263                   $agent_id,
 1264                   $self->{'dbh'}
 1265                 );
 1266 
 1267                 # Restore.
 1268                 $module->{'__module_component'} = 1;
 1269               } else {
 1270                 # Create module - Direct.
 1271                 my $name = $module->{'name'};
 1272                 delete $module->{'name'};
 1273                 delete $module->{'description'};
 1274                 $agentmodule_id = pandora_create_module_from_hash(
 1275                   $self->{'pa_config'},
 1276                   {
 1277                     %{$module},
 1278                     'id_tipo_modulo' => $id_tipo_modulo,
 1279                     'id_modulo' => $module->{'id_modulo'},
 1280                     'nombre' => safe_input($name),
 1281                     'descripcion' => safe_input($description),
 1282                     'id_agente' => $agent_id,
 1283                     'ip_target' => $data->{'agent'}{'direccion'}
 1284                   },
 1285                   $self->{'dbh'}
 1286                 );
 1287 
 1288                 $module->{'name'} = $name;
 1289                 $module->{'description'} = safe_output($description);
 1290               }
 1291 
 1292               # Restore.
 1293               $module->{'checked'} = 1;
 1294 
 1295               # Store.
 1296               $data->{'modules'}{$i}{'agentmodule_id'} = $agentmodule_id;
 1297 
 1298               $self->call(
 1299                 'message',
 1300                 "[$agent_id] Module: ".$module->{'name'}." ID: $agentmodule_id",
 1301                 5
 1302               );
 1303             }
 1304           }
 1305         }
 1306 
 1307         my $encoded;
 1308         eval {
 1309           local $SIG{__DIE__};
 1310           $encoded = encode_base64(
 1311             p_encode_json($self->{'pa_config'}, $data)
 1312           );
 1313         };
 1314 
 1315         push @agents, $data->{'agent'};
 1316 
 1317         # Update.
 1318         db_do(
 1319           $self->{'dbh'},
 1320           'UPDATE tdiscovery_tmp_agents SET `data` = ? '
 1321           .'WHERE `id_rt` = ? AND `label` = ?',
 1322           $encoded,
 1323           $self->{'task_data'}{'id_rt'},
 1324           $name
 1325         );
 1326 
 1327       }
 1328     }
 1329 
 1330     # Update parent relationships.
 1331     foreach my $agent (@agents) {
 1332       # Avoid processing if does not exist.
 1333       next unless (defined($agent->{'agent_id'}));
 1334 
 1335       # Avoid processing undefined parents.
 1336       next unless defined($agent->{'parent'});
 1337 
 1338       # Get parent id.
 1339       my $parent = PandoraFMS::Core::locate_agent(
 1340         $self->{'pa_config'}, $self->{'dbh'}, $agent->{'parent'}
 1341       );
 1342 
 1343       next unless defined($parent);
 1344 
 1345       # Is the agent in learning mode?
 1346       next unless ($agent->{'modo'} == 1);
 1347 
 1348       # Connect the host to its parent.
 1349       db_do($self->{'dbh'},
 1350         'UPDATE tagente SET id_parent=? WHERE id_agente=?',
 1351         $parent->{'id_agente'}, $agent->{'agent_id'}
 1352       );
 1353     }
 1354 
 1355     # Connect agents.
 1356     my @connections = get_db_rows(
 1357       $self->{'dbh'},
 1358       'SELECT * FROM tdiscovery_tmp_connections WHERE id_rt = ?',
 1359       $self->{'task_data'}{'id_rt'}
 1360     );
 1361 
 1362     foreach my $cn (@connections) {
 1363       $self->call('connect_agents',
 1364         $cn->{'dev_1'},
 1365         $cn->{'if_1'},
 1366         $cn->{'dev_2'},
 1367         $cn->{'if_2'},
 1368         # Force creation if direct.
 1369         $force_creation
 1370       );
 1371     }
 1372 
 1373     # Data creation finished.
 1374     return;
 1375   }
 1376 
 1377 
 1378   #
 1379   # Cleanup previous results.
 1380   #
 1381   $self->call('message', "Cleanup previous results", 6);
 1382   db_do(
 1383     $self->{'dbh'},
 1384     'DELETE FROM tdiscovery_tmp_agents '
 1385     .'WHERE `id_rt` = ?',
 1386     $self->{'task_data'}{'id_rt'}
 1387   );
 1388 
 1389   #
 1390   # Store and review.
 1391   #
 1392 
 1393   $self->call('message', "Storing results", 6);
 1394   my @hosts = keys %{$self->{'agents_found'}};
 1395   $self->{'step'} = STEP_PROCESSING;
 1396   if ((scalar (@hosts)) > 0) {
 1397     my ($progress, $step) = (90, 10.0 / scalar(@hosts)); # From 90% to 100%.
 1398 
 1399     foreach my $addr (keys %{$self->{'agents_found'}}) {
 1400       my $label = $self->{'agents_found'}->{$addr}{'agent'}{'nombre'};
 1401 
 1402       next if is_empty($label);
 1403 
 1404       # Retrieve target agent OS version.
 1405       $self->{'agents_found'}->{$addr}{'agent'}{'id_os'} = $self->guess_os($addr);
 1406 
 1407       $self->call('update_progress', $progress);
 1408       $progress += $step;
 1409       # Store temporally. Wait user approval.
 1410       my $encoded;
 1411 
 1412       eval {
 1413         local $SIG{__DIE__};
 1414         $encoded = encode_base64(
 1415           p_encode_json($self->{'pa_config'}, $self->{'agents_found'}->{$addr})
 1416         );
 1417       };
 1418 
 1419       my $id = get_db_value(
 1420         $self->{'dbh'},
 1421         'SELECT id FROM tdiscovery_tmp_agents WHERE id_rt = ? AND label = ?',
 1422         $self->{'task_data'}{'id_rt'},
 1423         safe_input($label)
 1424       );
 1425       
 1426       if (defined($id)) {
 1427         # Already defined.
 1428         $self->{'agents_found'}{$addr}{'id'} = $id;
 1429 
 1430         db_do(
 1431           $self->{'dbh'},
 1432           'UPDATE tdiscovery_tmp_agents SET `data` = ? '
 1433           .'WHERE `id_rt` = ? AND `label` = ?',
 1434           $encoded,
 1435           $self->{'task_data'}{'id_rt'},
 1436           safe_input($label)
 1437         );
 1438         next;
 1439       }
 1440 
 1441       # Insert.
 1442       $self->{'agents_found'}{$addr}{'id'} = db_insert(
 1443         $self->{'dbh'},
 1444         'id',
 1445         'INSERT INTO tdiscovery_tmp_agents (`id_rt`,`label`,`data`,`created`) '
 1446         .'VALUES (?, ?, ?, now())',
 1447         $self->{'task_data'}{'id_rt'},
 1448         safe_input($label),
 1449         $encoded
 1450       );
 1451     }
 1452   }
 1453 
 1454   if(defined($self->{'task_data'}{'review_mode'})
 1455     && $self->{'task_data'}{'review_mode'} == DISCOVERY_REVIEW
 1456   ) {
 1457     # Notify.
 1458     my $notification = {};
 1459     $notification->{'subject'} = safe_input('Discovery task ');
 1460     $notification->{'subject'} .= $self->{'task_data'}{'name'};
 1461     $notification->{'subject'} .= safe_input(' review pending');
 1462     $notification->{'url'} = ui_get_full_url(
 1463       'index.php?sec=gservers&sec2=godmode/servers/discovery&wiz=tasklist#'
 1464     );
 1465 
 1466     $notification->{'mensaje'} = safe_input(
 1467       'Discovery task (host&devices) \''.safe_output($self->{'task_data'}{'name'})
 1468       .'\' has been completed. Please review the results.'
 1469     );
 1470 
 1471     $notification->{'id_source'} = get_db_value(
 1472       $self->{'dbh'},
 1473       'SELECT id FROM tnotification_source WHERE description = ?',
 1474       safe_input('System status')
 1475     );
 1476 
 1477     # Create message
 1478     my $notification_id = db_process_insert(
 1479       $self->{'dbh'},
 1480       'id_mensaje',
 1481       'tmensajes',
 1482       $notification
 1483     );
 1484 
 1485     if (is_enabled($notification_id)) {
 1486       my @users = notification_get_users($self->{'dbh'}, 'System status');
 1487       my @groups = notification_get_groups($self->{'dbh'}, 'System status');
 1488 
 1489       notification_set_targets(
 1490         $self->{'pa_config'}, $self->{'dbh'},
 1491         $notification_id, \@users, \@groups
 1492       );
 1493     }
 1494   }
 1495 
 1496   $self->call('message', "Completed", 5);
 1497 }
 1498 
 1499 ################################################################################
 1500 # Apply monitoring templates selected to detected agents.
 1501 ################################################################################
 1502 sub PandoraFMS::Recon::Base::apply_monitoring($) {
 1503   my ($self) = @_;
 1504 
 1505   my @hosts = keys %{$self->{'agents_found'}};
 1506 
 1507   my $progress = 80;
 1508 
 1509   if (scalar @hosts > 0) {
 1510     $self->{'step'} = STEP_MONITORING;
 1511     # From 80% to 90%.
 1512     my ($progress, $step) = (80, 10.0 / scalar(@hosts));
 1513     my ($partial, $sub_step) = (0, 100 / scalar(@hosts));
 1514 
 1515     foreach my $label (keys %{$self->{'agents_found'}}) {
 1516       $self->{'c_network_percent'} = $partial;
 1517       $self->{'c_network_name'} = $label;
 1518       $self->call('update_progress', $progress);
 1519       $progress += $step;
 1520       $partial += $sub_step;
 1521       $self->call('message', "Checking modules for $label", 5);
 1522 
 1523       # Monitorization selected.
 1524       $self->call('create_network_profile_modules', $label);
 1525 
 1526       # Monitorization - interfaces
 1527       $self->call('create_interface_modules', $label);
 1528 
 1529       # Monitorization - WMI modules.
 1530       $self->call('create_wmi_modules', $label);
 1531 
 1532     }
 1533     
 1534   }
 1535 
 1536   $self->{'c_network_percent'} = 100;
 1537   $self->call('update_progress', $progress);
 1538 }
 1539 
 1540 ################################################################################
 1541 # Connect the given devices in the Pandora FMS database.
 1542 ################################################################################
 1543 sub PandoraFMS::Recon::Base::connect_agents($$$$$;$) {
 1544   my ($self, $dev_1, $if_1, $dev_2, $if_2, $force) = @_;
 1545 
 1546   if($self->{'task_data'}{'review_mode'} == DISCOVERY_REVIEW
 1547     || is_enabled($force)
 1548   ) {
 1549     # Store in tdiscovery_tmp_connections;
 1550 
 1551     db_process_insert(
 1552       $self->{'dbh'},
 1553       'id',
 1554       'tdiscovery_tmp_connections',
 1555       {
 1556         'id_rt' => $self->{'task_data'}{'id_rt'},
 1557         'dev_1' => $dev_1,
 1558         'if_1'  => $if_1,
 1559         'dev_2' => $dev_2,
 1560         'if_2'  => $if_2,
 1561       }
 1562     );
 1563 
 1564     return;
 1565   }
 1566 
 1567   # Get the agent for the first device.
 1568   my $agent_1 = get_agent_from_addr($self->{'dbh'}, $dev_1);
 1569   if (!defined($agent_1)) {
 1570     $agent_1 = get_agent_from_name($self->{'dbh'}, $dev_1);
 1571   }
 1572   return unless defined($agent_1);
 1573 
 1574   # Get the agent for the second device.
 1575   my $agent_2 = get_agent_from_addr($self->{'dbh'}, $dev_2);
 1576   if (!defined($agent_2)) {
 1577     $agent_2 = get_agent_from_name($self->{'dbh'}, $dev_2);
 1578   }
 1579   return unless defined($agent_2);
 1580 
 1581   # Use ping modules by default.
 1582   $if_1 = 'Host Alive' if ($if_1 eq '');
 1583   $if_2 = 'Host Alive' if ($if_2 eq '');
 1584 
 1585   # Check whether the modules exists.
 1586   my $module_name_1 = $if_1 eq 'Host Alive' ? 'Host Alive' : "${if_1}_ifOperStatus";
 1587   my $module_name_2 = $if_2 eq 'Host Alive' ? 'Host Alive' : "${if_2}_ifOperStatus";
 1588   my $module_id_1 = get_agent_module_id($self->{'dbh'}, $module_name_1, $agent_1->{'id_agente'});
 1589   if ($module_id_1 <= 0) {
 1590     $self->call('message', "ERROR: Module " . safe_output($module_name_1) . " does not exist for agent $dev_1.", 5);
 1591     return;
 1592   }
 1593   my $module_id_2 = get_agent_module_id($self->{'dbh'}, $module_name_2, $agent_2->{'id_agente'});
 1594   if ($module_id_2 <= 0) {
 1595     $self->call('message', "ERROR: Module " . safe_output($module_name_2) . " does not exist for agent $dev_2.", 5);
 1596     return;
 1597   }
 1598 
 1599   # Connect the modules if they are not already connected.
 1600   my $connection_id = get_db_value($self->{'dbh'}, 'SELECT id FROM tmodule_relationship WHERE (module_a = ? AND module_b = ? AND `type` = "direct") OR (module_b = ? AND module_a = ? AND `type` = "direct")', $module_id_1, $module_id_2, $module_id_1, $module_id_2);
 1601   if (! defined($connection_id)) {
 1602     db_do($self->{'dbh'}, 'INSERT INTO tmodule_relationship (`module_a`, `module_b`, `id_rt`) VALUES(?, ?, ?)', $module_id_1, $module_id_2, $self->{'task_id'});
 1603   }
 1604 }
 1605 
 1606 
 1607 ################################################################################
 1608 # Create agents from db_scan. Uses DataServer methods.
 1609 # data = [
 1610 #   'agent_data' => {},
 1611 #   'module_data' => []
 1612 # ]
 1613 ################################################################################
 1614 sub PandoraFMS::Recon::Base::create_agents($$) {
 1615   my ($self, $data) = @_;
 1616 
 1617   my $pa_config = $self->{'pa_config'};
 1618   my $dbh = $self->{'dbh'};
 1619   my $server_id = $self->{'server_id'};
 1620 
 1621   return undef if (ref($data) ne "ARRAY");
 1622 
 1623   foreach my $information (@{$data}) {
 1624     my $agent = $information->{'agent_data'};
 1625     my $modules = $information->{'module_data'};
 1626     my $force_processing = 0;
 1627 
 1628     # Search agent
 1629     my $current_agent = PandoraFMS::Core::locate_agent(
 1630       $pa_config, $dbh, $agent->{'agent_name'}
 1631     );
 1632 
 1633     my $parent_id;
 1634     if (defined($agent->{'parent_agent_name'})) {
 1635       $parent_id = PandoraFMS::Core::locate_agent(
 1636         $pa_config, $dbh, $agent->{'parent_agent_name'}
 1637       );
 1638       if ($parent_id) {
 1639         $parent_id = $parent_id->{'id_agente'};
 1640       }
 1641     }
 1642 
 1643     my $agent_id;
 1644     my $os_id = get_os_id($dbh, $agent->{'os'});
 1645 
 1646     if ($os_id < 0) {
 1647       $os_id = get_os_id($dbh, 'Other');
 1648     }
 1649 
 1650     if (!$current_agent) {
 1651       # Create agent.
 1652       $agent_id = pandora_create_agent(
 1653         $pa_config, $pa_config->{'servername'}, $agent->{'agent_name'},
 1654         $agent->{'address'}, $agent->{'id_group'}, $parent_id,
 1655         $os_id, $agent->{'description'},
 1656         $agent->{'interval'}, $dbh, $agent->{'timezone_offset'}
 1657       );
 1658 
 1659       $current_agent = $parent_id = PandoraFMS::Core::locate_agent(
 1660         $pa_config, $dbh, $agent->{'agent_name'}
 1661       );
 1662 
 1663       $force_processing = 1;
 1664 
 1665     } else {
 1666       $agent_id = $current_agent->{'id_agente'};
 1667     }
 1668 
 1669     if (!defined($agent_id)) {
 1670       return undef;
 1671     }
 1672 
 1673     if (defined($agent->{'address'}) && $agent->{'address'} ne '') {
 1674       pandora_add_agent_address(
 1675         $pa_config, $agent_id, $agent->{'agent_name'},
 1676         $agent->{'address'}, $dbh
 1677       );
 1678     }
 1679 
 1680     # Update agent information
 1681     pandora_update_agent(
 1682       $pa_config, strftime("%Y-%m-%d %H:%M:%S", localtime()), $agent_id,
 1683       $agent->{'os_version'}, $agent->{'agent_version'},
 1684       $agent->{'interval'}, $dbh, undef, $parent_id
 1685     );
 1686 
 1687     # Add modules.
 1688     if (ref($modules) eq "ARRAY") {
 1689       foreach my $module (@{$modules}) {
 1690         next unless ref($module) eq 'HASH';
 1691         # Translate data structure to simulate XML parser return.
 1692         my %data_translated = map { $_ => [ $module->{$_} ] } keys %{$module};
 1693 
 1694         # Process modules.
 1695         PandoraFMS::DataServer::process_module_data (
 1696           $pa_config, \%data_translated,
 1697           $server_id, $current_agent,
 1698           $module->{'name'}, $module->{'type'},
 1699           $agent->{'interval'},
 1700           strftime ("%Y/%m/%d %H:%M:%S", localtime()),
 1701           $dbh, $force_processing
 1702         );
 1703       }
 1704     }
 1705   }
 1706 
 1707 }
 1708 
 1709 ################################################################################
 1710 # Delete already existing connections.
 1711 ################################################################################
 1712 sub PandoraFMS::Recon::Base::delete_connections($) {
 1713   my ($self) = @_;
 1714 
 1715   $self->call('message', "Deleting connections...", 10);
 1716   db_do($self->{'dbh'}, 'DELETE FROM tmodule_relationship WHERE id_rt=?', $self->{'task_id'});
 1717 }
 1718 
 1719 ################################################################################
 1720 # Print log messages.
 1721 ################################################################################
 1722 sub PandoraFMS::Recon::Base::message($$$) {
 1723   my ($self, $message, $verbosity) = @_;
 1724 
 1725   logger($self->{'pa_config'}, "[Recon task " . $self->{'task_id'} . "] $message", $verbosity);
 1726 }
 1727 
 1728 ################################################################################
 1729 # Connect the given hosts to its parent.
 1730 ################################################################################
 1731 sub PandoraFMS::Recon::Base::set_parent($$$) {
 1732   my ($self, $host, $parent) = @_;
 1733 
 1734   return unless ($self->{'parent_detection'} == 1);
 1735 
 1736   # Do not edit 'not scaned' agents.
 1737   return if is_empty($self->{'agents_found'}{$host}{'agent'});
 1738 
 1739   $self->{'agents_found'}{$host}{'agent'}{'parent'} = $parent;
 1740 
 1741   # Add host alive module for parent.
 1742   $self->add_module($parent,
 1743     {
 1744       'ip_target' => $parent,
 1745       'name' => "Host Alive",
 1746       'description' => '',
 1747       'type' => 'remote_icmp_proc',
 1748       'id_modulo' => 2,
 1749     }
 1750   );
 1751 }
 1752 
 1753 ################################################################################
 1754 # Update recon task status.
 1755 ################################################################################
 1756 sub PandoraFMS::Recon::Base::update_progress ($$) {
 1757   my ($self, $progress) = @_;
 1758 
 1759   my $stats = {};
 1760   eval {
 1761     local $SIG{__DIE__};
 1762     if (defined($self->{'summary'}) && $self->{'summary'} ne '') {
 1763       $stats->{'summary'} = $self->{'summary'};
 1764     }
 1765 
 1766     $stats->{'step'} = $self->{'step'};
 1767     $stats->{'c_network_name'} = $self->{'c_network_name'};
 1768     $stats->{'c_network_percent'} = $self->{'c_network_percent'};
 1769 
 1770     # Store progress, last contact and overall status.
 1771     db_do ($self->{'dbh'}, 'UPDATE trecon_task SET utimestamp = ?, status = ?, summary = ? WHERE id_rt = ?',
 1772       time (), $progress, p_encode_json($self->{'pa_config'}, $stats), $self->{'task_id'});
 1773   };
 1774   if ($@) {
 1775     $self->call('message', "Problems updating progress $@", 5);
 1776     db_do ($self->{'dbh'}, 'UPDATE trecon_task SET utimestamp = ?, status = ?, summary = ? WHERE id_rt = ?',
 1777       time (), $progress, "{}", $self->{'task_id'});
 1778   }
 1779 }
 1780 
 1781 1;
 1782 __END__