pandorafms/pandora_server/lib/PandoraFMS/InventoryServer.pm

283 lines
10 KiB
Perl

package PandoraFMS::InventoryServer;
##########################################################################
# Pandora FMS Inventory Server.
##########################################################################
# Copyright (c) 2007-2023 Pandora FMS
# This code is not free or OpenSource. Please don't redistribute.
##########################################################################
use strict;
use warnings;
use threads;
use threads::shared;
use Thread::Semaphore;
use File::Temp qw(tempfile unlink0);
use POSIX qw(strftime);
use HTML::Entities;
use MIME::Base64;
use JSON;
# UTF-8 flags control with I/O for multibyte characters
use open ":utf8";
# Default lib dir for RPM and DEB packages
BEGIN { push @INC, '/usr/lib/perl5'; }
use PandoraFMS::Tools;
use PandoraFMS::DB;
use PandoraFMS::Core;
use PandoraFMS::ProducerConsumerServer;
# Inherits from PandoraFMS::ProducerConsumerServer
our @ISA = qw(PandoraFMS::ProducerConsumerServer);
# Global variables
my @TaskQueue :shared;
my %PendingTasks :shared;
my $Sem :shared;
my $TaskSem :shared;
########################################################################################
# Inventory Server class constructor.
########################################################################################
sub new ($$;$) {
my ($class, $config, $dbh) = @_;
return undef unless $config->{'inventoryserver'} == 1;
# Initialize semaphores and queues
@TaskQueue = ();
%PendingTasks = ();
$Sem = Thread::Semaphore->new;
$TaskSem = Thread::Semaphore->new (0);
# Call the constructor of the parent class
my $self = $class->SUPER::new($config, INVENTORYSERVER, \&PandoraFMS::InventoryServer::data_producer, \&PandoraFMS::InventoryServer::data_consumer, $dbh);
bless $self, $class;
return $self;
}
###############################################################################
# Run.
###############################################################################
sub run ($) {
my $self = shift;
my $pa_config = $self->getConfig ();
print_message ($pa_config, " [*] Starting " . $pa_config->{'rb_product_name'} . " Inventory Server.", 1);
$self->setNumThreads ($pa_config->{'inventory_threads'});
$self->SUPER::run (\@TaskQueue, \%PendingTasks, $Sem, $TaskSem);
}
###############################################################################
# Data producer.
###############################################################################
sub data_producer ($) {
my $self = shift;
my ($pa_config, $dbh) = ($self->getConfig (), $self->getDBH ());
my @tasks;
my @rows;
if (pandora_is_master($pa_config, $dbh) == 0) {
if ($pa_config->{'dbengine'} ne 'oracle') {
@rows = get_db_rows ($dbh,
'SELECT tagent_module_inventory.id_agent_module_inventory, tagent_module_inventory.flag, tagent_module_inventory.timestamp
FROM tagente, tagent_module_inventory, tmodule_inventory
WHERE tagente.server_name = ?
AND tmodule_inventory.id_module_inventory = tagent_module_inventory.id_module_inventory
AND tmodule_inventory.id_os IS NOT NULL
AND tagente.id_agente = tagent_module_inventory.id_agente
AND tagent_module_inventory.target <> \'\'
AND tagente.disabled = 0
AND (tagent_module_inventory.timestamp = \'1970-01-01 00:00:00\'
OR UNIX_TIMESTAMP(tagent_module_inventory.timestamp) + tagent_module_inventory.interval < UNIX_TIMESTAMP()
OR tagent_module_inventory.flag = 1)
ORDER BY tagent_module_inventory.timestamp ASC',
$pa_config->{'servername'});
}
else {
@rows = get_db_rows ($dbh,
'SELECT tagent_module_inventory.id_agent_module_inventory, tagent_module_inventory.flag, tagent_module_inventory.timestamp
FROM tagente, tagent_module_inventory, tmodule_inventory
WHERE tagente.server_name = ?
AND tmodule_inventory.id_module_inventory = tagent_module_inventory.id_module_inventory
AND tmodule_inventory.id_os IS NOT NULL
AND tagente.id_agente = tagent_module_inventory.id_agente
AND tagent_module_inventory.target IS NOT NULL
AND tagente.disabled = 0
AND (tagent_module_inventory.timestamp = \'1970-01-01 00:00:00\'
OR UNIX_TIMESTAMP(tagent_module_inventory.timestamp) + tagent_module_inventory.' . ${RDBMS_QUOTE} . 'interval' . ${RDBMS_QUOTE} . '< UNIX_TIMESTAMP()
OR tagent_module_inventory.flag = 1)
ORDER BY tagent_module_inventory.timestamp ASC',
$pa_config->{'servername'});
}
}
else {
if ($pa_config->{'dbengine'} ne 'oracle') {
@rows = get_db_rows ($dbh,
'SELECT tagent_module_inventory.id_agent_module_inventory, tagent_module_inventory.flag, tagent_module_inventory.timestamp
FROM tagente, tagent_module_inventory, tmodule_inventory
WHERE (server_name = ? OR server_name NOT IN (SELECT name FROM tserver WHERE status = 1 AND server_type = ?))
AND tmodule_inventory.id_module_inventory = tagent_module_inventory.id_module_inventory
AND tmodule_inventory.id_os IS NOT NULL
AND tagente.id_agente = tagent_module_inventory.id_agente
AND tagent_module_inventory.target <> \'\'
AND tagente.disabled = 0
AND (tagent_module_inventory.timestamp = \'1970-01-01 00:00:00\'
OR UNIX_TIMESTAMP(tagent_module_inventory.timestamp) + tagent_module_inventory.interval < UNIX_TIMESTAMP()
OR tagent_module_inventory.flag = 1)
ORDER BY tagent_module_inventory.timestamp ASC',
$pa_config->{'servername'}, INVENTORYSERVER);
}
else {
@rows = get_db_rows ($dbh,
'SELECT tagent_module_inventory.id_agent_module_inventory, tagent_module_inventory.flag, tagent_module_inventory.timestamp
FROM tagente, tagent_module_inventory, tmodule_inventory
WHERE (server_name = ? OR server_name NOT IN (SELECT name FROM tserver WHERE status = 1 AND server_type = ?))
AND tmodule_inventory.id_module_inventory = tagent_module_inventory.id_module_inventory
AND tmodule_inventory.id_os IS NOT NULL
AND tagente.id_agente = tagent_module_inventory.id_agente
AND tagent_module_inventory.target IS NOT NULL
AND tagente.disabled = 0
AND (tagent_module_inventory.timestamp = \'1970-01-01 00:00:00\'
OR UNIX_TIMESTAMP(tagent_module_inventory.timestamp) + tagent_module_inventory.' . ${RDBMS_QUOTE} . 'interval' . ${RDBMS_QUOTE} . ' < UNIX_TIMESTAMP()
OR tagent_module_inventory.flag = 1)
ORDER BY tagent_module_inventory.timestamp ASC',
$pa_config->{'servername'}, INVENTORYSERVER);
}
}
foreach my $row (@rows) {
# Reset forced execution flag
if ($row->{'flag'} == 1) {
db_do ($dbh, 'UPDATE tagent_module_inventory SET flag = 0 WHERE id_agent_module_inventory = ?', $row->{'id_agent_module_inventory'});
}
push (@tasks, $row->{'id_agent_module_inventory'});
}
return @tasks;
}
###############################################################################
# Data consumer.
###############################################################################
sub data_consumer ($$) {
my ($self, $module_id) = @_;
my ($pa_config, $dbh) = ($self->getConfig (), $self->getDBH ());
my $timeout = $pa_config->{'inventory_timeout'};
# Get inventory module data
my $module = get_db_single_row ($dbh,
'SELECT * FROM tagent_module_inventory, tmodule_inventory
WHERE tagent_module_inventory.id_agent_module_inventory = ?
AND tagent_module_inventory.id_module_inventory = tmodule_inventory.id_module_inventory',
$module_id);
my $command;
my ($fh, $temp_file) = tempfile();
if ($module->{'script_mode'} == '1') {
my $script_file = $module->{'script_path'};
$command = $module->{'interpreter'} . ' ' . $script_file . ' "' . $module->{'target'} . '"';
} else {
# Save script in a temporary file
$fh->print (decode_base64($module->{'code'}));
close ($fh);
set_file_permissions($pa_config, $temp_file, "0777");
# Run the script
$command = $module->{'interpreter'} . ' ' . $temp_file . ' "' . $module->{'target'} . '"';
}
# Try to read the custom fields to use them as arguments into the command
if (defined($module->{'custom_fields'}) && $module->{'custom_fields'} ne '') {
my $decoded_cfields;
eval {
$decoded_cfields = decode_json (decode_base64 ($module->{'custom_fields'}));
};
if ($@) {
logger($pa_config, "Failed to encode received inventory data", 10);
}
if (!defined ($decoded_cfields)) {
logger ($pa_config, "Remote inventory module ".$module->{'name'}." has failed because the custom fields can't be read", 6);
if ($module->{'script_mode'} == '2') {
unlink ($temp_file);
}
return;
}
foreach my $field (@{$decoded_cfields}) {
if ($field->{'secure'}) {
$command .= ' "' . pandora_output_password($pa_config, $field->{'value'}) . '"';
}
else {
$command .= ' "' . $field->{'value'} . '"';
}
}
}
# Add the default user/password arguments to the command
else {
# Initialize macros.
my %macros = (
'_agentcustomfield_\d+_' => undef,
);
my $wmi_user = safe_output(subst_column_macros($module->{"username"}, \%macros, $pa_config, $dbh, undef, $module));
my $wmi_pass = safe_output(pandora_output_password($pa_config, subst_column_macros($module->{"password"}, \%macros, $pa_config, $dbh, undef, $module)));
$command .= ' "' . $wmi_user . '" "' . $wmi_pass . '"';
}
logger ($pa_config, "Inventory execution command $command", 10);
my $data = `$command 2>$DEVNULL`;
# Check for errors
if ($? != 0) {
logger ($pa_config, "Remote inventory module ".$module->{'name'}." has failed with error level $?", 6);
if ($module->{'script_mode'} == '2') {
unlink ($temp_file);
}
return;
}
if ($module->{'script_mode'} == '2') {
unlink ($temp_file);
}
my $utimestamp = time ();
my $timestamp = strftime ("%Y-%m-%d %H:%M:%S", localtime ($utimestamp));
eval {
$data = encode_entities ($data, "'<>&");
};
if ($@) {
logger($pa_config, "Failed to encode received inventory data", 10);
return;
}
# Get previous data from the database
my $inventory_module = get_db_single_row ($dbh,
'SELECT * FROM tagent_module_inventory
WHERE id_agent_module_inventory = ?',
$module_id);
return unless defined ($inventory_module);
process_inventory_module_diff($pa_config, $data,
$inventory_module, $timestamp, $utimestamp, $dbh);
}
1;
__END__