Ramon Novoa 8edaad4e5a Push /usr/lib/perl5 to the back of the module search path.
When /usr/lib/perl5 is added to the front of the module search path it
takes precedence over libraries distributed with binary versions of
Pandora FMS. This can be fixed by pushing it to the back instead.
2021-04-15 17:56:02 +02:00

612 lines
22 KiB
Perl

package PandoraFMS::NetworkServer;
##########################################################################
# Pandora FMS Network Server.
# Pandora FMS. the Flexible Monitoring System. http://www.pandorafms.org
##########################################################################
# Copyright (c) 2005-2021 Artica Soluciones Tecnologicas S.L
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; version 2
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
##########################################################################
use strict;
use warnings;
use threads;
use threads::shared;
use Thread::Semaphore;
use IO::Socket::INET6;
use IO::Select;
use HTML::Entities;
use POSIX qw(strftime);
# 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;
########################################################################################
# Network Server class constructor.
########################################################################################
sub new ($$$) {
my ($class, $config, $dbh) = @_;
return undef unless $config->{'networkserver'} == 1;
if (! -e $config->{'snmpget'}) {
logger ($config, ' [E] ' . $config->{'snmpget'} . " needed by " . $config->{'rb_product_name'} . " Network Server not found.", 1);
print_message ($config, ' [E] ' . $config->{'snmpget'} . " needed by " . $config->{'rb_product_name'} . " Network Server not found.", 1);
return undef;
}
# 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, NETWORKSERVER, \&PandoraFMS::NetworkServer::data_producer, \&PandoraFMS::NetworkServer::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'} . " Network Server.", 1);
$self->setNumThreads ($pa_config->{'network_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;
my $network_filter = enterprise_hook ('get_network_filter', [$pa_config]);
if (pandora_is_master($pa_config) == 0) {
@rows = get_db_rows ($dbh, 'SELECT tagente_modulo.id_agente_modulo, tagente_modulo.flag, tagente_estado.current_interval + tagente_estado.last_execution_try AS time_left, last_execution_try
FROM tagente, tagente_modulo, tagente_estado
WHERE server_name = ?
AND tagente_modulo.id_agente = tagente.id_agente
AND tagente.disabled = 0
AND ((tagente_modulo.id_tipo_modulo > 5 AND tagente_modulo.id_tipo_modulo < 19 )
OR (tagente_modulo.id_tipo_modulo > 33 AND tagente_modulo.id_tipo_modulo < 38 )) '
. (defined ($network_filter) ? $network_filter : ' ') .
'AND tagente_modulo.disabled = 0
AND tagente_estado.id_agente_modulo = tagente_modulo.id_agente_modulo
AND (tagente_modulo.flag = 1 OR ((tagente_estado.last_execution_try + tagente_estado.current_interval) < UNIX_TIMESTAMP()))
ORDER BY tagente_modulo.flag DESC, time_left ASC, tagente_estado.last_execution_try ASC ', safe_input($pa_config->{'servername'}));
} else {
@rows = get_db_rows ($dbh, 'SELECT tagente_modulo.id_agente_modulo, tagente_modulo.flag, tagente_estado.last_execution_try, tagente_estado.current_interval + tagente_estado.last_execution_try AS time_left, last_execution_try
FROM tagente, tagente_modulo, tagente_estado
WHERE ((server_name = ?) OR (server_name = ANY(SELECT name FROM tserver WHERE status = 0 AND server_type = ?)))
AND tagente_modulo.id_agente = tagente.id_agente
AND tagente.disabled = 0
AND tagente_modulo.disabled = 0
AND ((tagente_modulo.id_tipo_modulo > 5 AND tagente_modulo.id_tipo_modulo < 19 )
OR (tagente_modulo.id_tipo_modulo > 33 AND tagente_modulo.id_tipo_modulo < 38 )) '
. (defined ($network_filter) ? $network_filter : ' ') .
'AND tagente_estado.id_agente_modulo = tagente_modulo.id_agente_modulo
AND (tagente_modulo.flag = 1 OR ((tagente_estado.last_execution_try + tagente_estado.current_interval) < UNIX_TIMESTAMP()))
ORDER BY tagente_modulo.flag DESC, time_left ASC, tagente_estado.last_execution_try ASC', safe_input($pa_config->{'servername'}), NETWORKSERVER);
}
foreach my $row (@rows) {
# Reset forced execution flag
if ($row->{'flag'} == 1) {
db_do ($dbh, 'UPDATE tagente_modulo SET flag = 0 WHERE id_agente_modulo = ?', $row->{'id_agente_modulo'});
}
push (@tasks, $row->{'id_agente_modulo'});
}
return @tasks;
}
###############################################################################
# Data consumer.
###############################################################################
sub data_consumer ($$) {
my ($self, $task) = @_;
exec_network_module ($self->getConfig (), $task, $self->getServerID (), $self->getDBH ());
}
##########################################################################
# SUB pandora_query_tcp (pa_config, tcp_port. ip_target, result, data, tcp_send,
# tcp_rcv, id_tipo_module, dbh)
# Makes a call to TCP modules to get a value.
##########################################################################
sub pandora_query_tcp ($$$$$$$$$$;$) {
my $pa_config = $_[0];
my $tcp_port = $_[1];
my $ip_target = $_[2];
my $module_result = $_[3];
my $module_data = $_[4];
my $tcp_send = $_[5];
my $tcp_rcv = $_[6];
my $id_tipo_modulo = $_[7];
my $timeout = $_[8];
my $retries = $_[9];
my $module_id = $_[10]; # Only for info purpose
# Adjust timeout and retry values
if ($timeout == 0) {
$timeout = $pa_config->{'tcp_timeout'};
}
if ($retries == 0) {
$retries = $pa_config->{'tcp_checks'};
}
$tcp_send = decode_entities($tcp_send);
$tcp_rcv = decode_entities($tcp_rcv);
my $counter;
for ($counter =0; $counter < $retries; $counter++){
my $temp; my $temp2;
my $tam;
my $handle=IO::Socket::INET6->new(
Proto=>"tcp",
PeerAddr=>$ip_target,
Timeout=>$timeout,
PeerPort=>$tcp_port,
Multihomed=>1,
Blocking=>0 ); # Non blocking !!, very important !
if (defined ($handle)){
# Multi request patch, submitted by Glen Eustace (new zealand)
my @tcp_send = split( /\|/, $tcp_send );
my @tcp_rcv = split( /\|/, $tcp_rcv );
# Add server socket to select queue
my $select = IO::Select->new ();
$select->add ($handle);
next_pair:
$tcp_send = shift( @tcp_send );
$tcp_rcv = shift( @tcp_rcv );
if ((defined ($tcp_send)) && ($tcp_send ne "")){ # its Expected to sending data ?
# Send data
logger ($pa_config,"[INFO] TCP query on port $tcp_port with target $ip_target by module with id $module_id." , 10);
$handle->autoflush(1);
$tcp_send =~ s/\^M/\r\n/g;
# Replace Carriage rerturn and line feed
$handle->send($tcp_send);
}
# we expect to receive data ? (non proc types)
if ((defined ($tcp_rcv) && $tcp_rcv ne "") || (($id_tipo_modulo == 10) || ($id_tipo_modulo ==8) || ($id_tipo_modulo == 11))) {
# Receive data, non-blocking !!!! (VERY IMPORTANT!)
$temp2 = "";
for ($tam = 0; $tam < $timeout; $tam ++) {
if ($select->can_read (1)) {
my $read = sysread ($handle, $temp, 16000);
last if (! defined ($read) || $read == 0); # No more data or something went wrong
$temp2 = $temp2.$temp;
}
}
if ($id_tipo_modulo == 9){ # only for TCP Proc
if ($temp2 =~ /$tcp_rcv/i){ # String match !
if ( @tcp_send ) { # still more pairs
goto next_pair;
}
$$module_data = 1;
$$module_result = 0;
$counter = $retries;
} else {
$$module_data = 0;
$$module_result = 0;
$counter = $retries;
}
} elsif ($id_tipo_modulo == 10 ){ # TCP String (no int conversion)!
$$module_data = $temp2;
$$module_result =0;
} else { # TCP Data numeric (inc or data)
if ($temp2 ne ""){
if ($temp2 =~ /[A-Za-z\.\,\-\/\\\(\)\[\]]/){
$$module_result = 1;
$$module_data = 0; # invalid data
$counter = $retries;
} else {
$$module_data = int($temp2);
$$module_result = 0; # Successful
$counter = $retries;
}
} else {
$$module_result = 1;
$$module_data = 0; # invalid data
$counter = $retries;
}
}
} else { # No expected data to receive, if connected and tcp_proc type successful
if ($id_tipo_modulo == 9){ # TCP Proc
$$module_result = 0;
$$module_data = 1;
$counter = $retries;
}
}
$handle->close();
undef ($handle);
} else { # Cannot connect (open sock failed)
$$module_result = 1; # Fail
if ($id_tipo_modulo == 9){ # TCP Proc
$$module_result = 0;
$$module_data = 0; # Failed, but data exists
$counter = $retries;
}
}
}
}
###############################################################################
# Set commands for SNMP checks depending on OS type
###############################################################################
sub pandora_snmp_get_command ($$$$$$$$$$$) {
my ($snmpget_cmd, $snmp_version, $snmp_retries, $snmp_timeout, $snmp_community, $snmp_target, $snmp_oid, $snmp3_security_level, $snmp3_extra, $snmp_port, $pa_config) = @_;
my $output = "";
# See codes on http://perldoc.perl.org/perlport.html#PLATFORMS
my $OSNAME = $^O;
# This fixes problem in linux snmpget, because v2 must be submitted like 2c
if ($snmp_version eq "2"){
$snmp_version = "2c";
}
if (defined($snmp_port) && ($snmp_port ne "161") && ($snmp_port ne "") && ($snmp_port ne " ") && ($snmp_port ne "0")){
$snmp_target = $snmp_target.":".$snmp_port;
}
# Pandora FMS's console MIB directory
my $mib_dir = $pa_config->{'attachment_dir'} . '/mibs';
# On windows, we need the snmpget command from net-snmp, already present on win agent
# the call is the same than in linux
if (($OSNAME eq "MSWin32") || ($OSNAME eq "MSWin32-x64") || ($OSNAME eq "cygwin")){
if ($snmp_version ne "3"){
$output = `$snmpget_cmd -v $snmp_version -r $snmp_retries -t $snmp_timeout -OUevqt -c $snmp_community $snmp_target $snmp_oid 2>$DEVNULL`;
} else {
$output = `$snmpget_cmd -v $snmp_version -r $snmp_retries -t $snmp_timeout -OUevqt -l $snmp3_security_level $snmp3_extra $snmp_target $snmp_oid 2>$DEVNULL`;
}
}
# by default LINUX/FreeBSD/Solaris calls
else {
if ($snmp_version ne "3"){
$output = `$snmpget_cmd -M+"$mib_dir" -v $snmp_version -r $snmp_retries -t $snmp_timeout -OUevqt -c '$snmp_community' $snmp_target $snmp_oid 2>$DEVNULL`;
} else {
$output = `$snmpget_cmd -M+"$mib_dir" -v $snmp_version -r $snmp_retries -t $snmp_timeout -OUevqt -l $snmp3_security_level $snmp3_extra $snmp_target $snmp_oid 2>$DEVNULL`;
}
}
# Remove starting & ending double quotes, to easily parse numeric data on String types
# Check if output was defined because if the snmpget fails this variable will be undef
if (defined ($output)) {
if ($output =~ /^\"(.*)\"$/){
return $1;
}
else {
return $output;
}
} else {
logger ($pa_config,"[ERROR] Undefined value returned SNMP query. Is the server out of memory?" , 3);
logger ($pa_config,"[ERROR] Snmp Community: $snmp_community SNMP Target: $snmp_target OID: $snmp_oid", 3);
return "";
}
}
##########################################################################
# SUB pandora_query_snmp (pa_config, module)
# Makes a call to SNMP modules to get a value,
##########################################################################
sub pandora_query_snmp ($$$$) {
my ($pa_config, $module, $ip_target, $dbh) = @_;
# Initialize macros.
my %macros = (
'_agentcustomfield_\d+_' => undef,
);
my $snmp_version = $module->{"tcp_send"}; # (1, 2, 2c or 3)
my $snmp3_privacy_method = $module->{"custom_string_1"}; # DES/AES
my $snmp3_privacy_pass = safe_output(pandora_output_password($pa_config, subst_column_macros($module->{"custom_string_2"}, \%macros, $pa_config, $dbh, undef, $module)));
my $snmp3_security_level = $module->{"custom_string_3"}; # noAuthNoPriv|authNoPriv|authPriv
my $snmp3_auth_user = safe_output(subst_column_macros($module->{"plugin_user"}, \%macros, $pa_config, $dbh, undef, $module));
my $snmp3_auth_pass = safe_output(pandora_output_password($pa_config, subst_column_macros($module->{"plugin_pass"}, \%macros, $pa_config, $dbh, undef, $module)));
my $snmp3_auth_method = $module->{"plugin_parameter"}; #MD5/SHA1
my $snmp_community = safe_output(subst_column_macros($module->{"snmp_community"}, \%macros, $pa_config, $dbh, undef, $module));
my $snmp_target = $ip_target;
my $snmp_oid = $module->{"snmp_oid"};
my $snmp_port = $module->{"tcp_port"};
return (undef, 0) unless ($snmp_oid ne '');
if ($snmp_oid =~ m/[a-zA-Z]/) {
$snmp_oid = translate_obj ($pa_config, $dbh, $snmp_oid);
# Could not translate OID, disable the module
if (! defined ($snmp_oid) || $snmp_oid eq '') {
db_do ($dbh, 'UPDATE tagente_modulo SET disabled = 1 WHERE id_agente_modulo = ?', $module->{"id_agente_modulo"});
return (undef, 1);
}
# Update module configuration
db_do ($dbh, 'UPDATE tagente_modulo SET snmp_oid = ? WHERE id_agente_modulo = ?', $snmp_oid, $module->{"id_agente_modulo"});
}
my $snmp_timeout = $module->{"max_timeout"} != 0 ? $module->{"max_timeout"} : $pa_config->{"snmp_timeout"};
my $snmp_retries = $module->{"max_retries"} != 0 ? $module->{"max_retries"} : $pa_config->{'snmp_checks'};
my $module_result = 1; # by default error
my $module_data = 0;
my $output; # Command output
# If not defined, always snmp v1 (standard)
$snmp_version = '1' unless defined($snmp_version);
if ($snmp_version ne '1' && $snmp_version ne '2'
&& $snmp_version ne '2c' && $snmp_version ne '3') {
$snmp_version = '1';
}
my $snmpget_cmd = $pa_config->{"snmpget"};
# SNMP v1, v2 and v2c call
if ($snmp_version ne '3'){
$output = pandora_snmp_get_command ($snmpget_cmd, $snmp_version, $snmp_retries, $snmp_timeout, $snmp_community, $snmp_target, $snmp_oid, "", "", $snmp_port, $pa_config);
if (defined ($output) && $output ne ""){
$module_result = 0;
$module_data = $output;
}
} else {
# SNMP v3 has a very different command syntax
my $snmp3_extra = "";
my $snmp3_execution;
# SNMP v3 no authentication and no privacy
if ($snmp3_security_level eq "noAuthNoPriv"){
$snmp3_extra = " -u '$snmp3_auth_user' ";
}
# SNMP v3 authentication only
if ($snmp3_security_level eq "authNoPriv"){
$snmp3_extra = " -a $snmp3_auth_method -u '$snmp3_auth_user' -A '$snmp3_auth_pass' ";
}
# SNMP v3 privacy AND authentication
if ($snmp3_security_level eq "authPriv"){
$snmp3_extra = " -a $snmp3_auth_method -u '$snmp3_auth_user' -A '$snmp3_auth_pass' -x $snmp3_privacy_method -X '$snmp3_privacy_pass' ";
}
$output = pandora_snmp_get_command ($snmpget_cmd, $snmp_version, $snmp_retries, $snmp_timeout, $snmp_community, $snmp_target, $snmp_oid, $snmp3_security_level, $snmp3_extra, $snmp_port, $pa_config);
if (defined ($output) && $output ne ""){
$module_result = 0;
$module_data = $output;
}
}
chomp ($module_data);
return ($module_data, $module_result);
}
##########################################################################
# SUB exec_network_module (paconfig, id_agente_modulo, dbh )
# Execute network module task
##########################################################################
sub exec_network_module ($$$$) {
my ($pa_config, $id_agente_modulo, $server_id, $dbh) = @_;
# Init variables
my @sql_data;
if ((!defined($id_agente_modulo)) || ($id_agente_modulo eq "")){
return 0;
}
my $module = get_db_single_row ($dbh, 'SELECT * FROM tagente_modulo WHERE id_agente_modulo = ?', $id_agente_modulo);
if (! defined ($module)) {
logger ($pa_config,"[ERROR] Processing data for invalid module", 0);
return 0;
}
my $error = "1";
my $query_sql2;
my $temp=0; my $tam; my $temp2;
my $module_result = 1; # Fail by default
my $module_data = 0;
my $id_agente = $module->{'id_agente'};
my $agent_row = get_db_single_row ($dbh, 'SELECT * FROM tagente WHERE id_agente = ?', $id_agente);
my $agent_name = $agent_row->{'nombre'};
my $agent_os_version = $agent_row->{'os_version'};
my $id_tipo_modulo = $module->{'id_tipo_modulo'};
my $ip_target = $module->{'ip_target'};
my $snmp_oid = $module->{'snmp_oid'};
my $snmp_community = $module->{'snmp_community'};
my $tcp_port = $module->{'tcp_port'};
my $tcp_send = $module->{'tcp_send'};
my $tcp_rcv = $module->{'tcp_rcv'};
my $timeout = $module->{'max_timeout'};
my $retries = $module->{'max_retries'};
my $target_os = pandora_get_os($dbh, $module->{'custom_string_2'});
if (defined($module->{'custom_string_2'})
&& $module->{'custom_string_2'} eq "inherited"
) {
$target_os = $agent_row->{'id_os'};
} elsif (!defined($target_os) || "$target_os" eq '0') {
$target_os = $agent_row->{'id_os'};
}
# Use the agent address by default
if (! defined($ip_target) || $ip_target eq '' || $ip_target eq 'auto') {
$ip_target = $agent_row->{'direccion'};
}
if ((defined($ip_target)) && ($ip_target)) {
# -------------------------------------------------------
# ICMP Modules
# -------------------------------------------------------
if ($id_tipo_modulo == 6){ # ICMP (Connectivity only: Boolean)
$module_data = pandora_ping ($pa_config, $ip_target, $timeout, $retries);
$module_result = 0; # Successful
}
elsif ($id_tipo_modulo == 7){ # ICMP (data for latency in ms)
$module_data = pandora_ping_latency ($pa_config, $ip_target, $timeout, $retries);
if (defined($module_data)) {
$module_result = 0; # Successful
} else {
$module_result = 1; # Unsuccessful: Cannot reach target.
}
}
# -------------------------------------------------------
# SNMP Modules (Proc=18, inc, data, string)
# -------------------------------------------------------
elsif (($id_tipo_modulo == 15) ||
($id_tipo_modulo == 18) ||
($id_tipo_modulo == 16) ||
($id_tipo_modulo == 17)) {
($module_data, $module_result) = pandora_query_snmp ($pa_config, $module, $ip_target, $dbh);
if ($module_result == 0) { # A correct SNMP Query
# SNMP_DATA_PROC
if ($id_tipo_modulo == 18){ #snmp_data_proc
# RFC1213-MIB where it says that: SYNTAX INTEGER { up(1), down(2), testing(3),
# unknown(4), dormant(5), notPresent(6), lowerLayerDown(7) }
if ($module_data ne '1'){ # up state is 1, down state in SNMP is 2 ....
$module_data = 0;
}
}
# SNMP_DATA and SNMP_DATA_INC
elsif (($id_tipo_modulo == 15) || ($id_tipo_modulo == 16) ){
if (!is_numeric($module_data)){
$module_result = 1;
}
}
} else { # Failed SNMP-GET
$module_data = 0;
if ($id_tipo_modulo == 18){ # snmp_proc
# Feature from 10Feb08. If snmp_proc_deadresponse = 1 and cannot contact by an error
# this is a fail monitor
if ($pa_config->{"snmp_proc_deadresponse"} eq "1"){
$module_result = 0;
$module_data = 0;
}
}
}
}
# -------------------------------------------------------
# TCP Module
# -------------------------------------------------------
elsif (($id_tipo_modulo == 8) ||
($id_tipo_modulo == 9) ||
($id_tipo_modulo == 10) ||
($id_tipo_modulo == 11)) { # TCP Module
if ((defined($tcp_port)) && ($tcp_port < 65536) && ($tcp_port > 0)) { # Port check
pandora_query_tcp ($pa_config, $tcp_port, $ip_target, \$module_result, \$module_data, $tcp_send, $tcp_rcv, $id_tipo_modulo, $timeout, $retries, $id_agente_modulo);
} else {
# Invalid port, get no check
$module_result = 1;
}
}
# -------------------------------------------------------
# CMD Module
# -------------------------------------------------------
elsif (($id_tipo_modulo == 34)
|| ($id_tipo_modulo == 35)
|| ($id_tipo_modulo == 36)
|| ($id_tipo_modulo == 37)) { # CMD Module
$module_data = enterprise_hook(
'remote_execution_module',
[
$pa_config,
$dbh,
$module,
$target_os,
$ip_target,
$tcp_port
]
);
if (!defined($module_data) || "$module_data" eq "") {
$module_result = 1;
} else {
# Success.
$module_result = 0;
}
}
}
# Write data section
my $utimestamp = time ();
my $timestamp = strftime ("%Y-%m-%d %H:%M:%S", localtime($utimestamp));
# Is everything goes ok
if ($module_result == 0) {
my %data = ("data" => $module_data);
pandora_process_module ($pa_config, \%data, '', $module, '', $timestamp, $utimestamp, $server_id, $dbh);
if (!defined($agent_os_version) || $agent_os_version eq ''){
$agent_os_version = $pa_config->{'servername'}.'_Net';
}
# Update agent last contact using Pandora version as agent version
pandora_update_agent ($pa_config, $timestamp, $id_agente, undef, undef, -1, $dbh);
} else {
# Modules who cannot connect or something go bad, update last_execution_try field
pandora_update_module_on_error ($pa_config, $module, $dbh);
}
}
1;
__END__