669 lines
22 KiB
Perl
669 lines
22 KiB
Perl
package PandoraFMS::SNMPServer;
|
|
##########################################################################
|
|
# Pandora FMS SNMP Console.
|
|
# Pandora FMS. the Flexible Monitoring System. http://www.pandorafms.org
|
|
##########################################################################
|
|
# Copyright (c) 2005-2023 Pandora FMS
|
|
#
|
|
# 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 Time::Local;
|
|
use Time::HiRes qw(usleep);
|
|
use XML::Simple;
|
|
|
|
use Scalar::Util qw(looks_like_number);
|
|
|
|
# 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);
|
|
our @EXPORT = qw(start_snmptrapd);
|
|
|
|
# Global variables
|
|
my @TaskQueue :shared;
|
|
my %PendingTasks :shared;
|
|
my $Sem :shared;
|
|
my %Sources :shared;
|
|
my $SourceSem :shared;
|
|
my $TaskSem :shared;
|
|
|
|
# Trap statistics by agent
|
|
my %AGENTS = ();
|
|
|
|
# Sources silenced by storm protection.
|
|
my %SILENCEDSOURCES = ();
|
|
|
|
# Index and buffer management for trap log files
|
|
my $SNMPTRAPD = { 'log_file' => '', 'fd' => undef, 'idx_file' => '', 'last_line' => 0, 'last_size' => 0, 'read_ahead_line' => '', 'read_ahead_pos' => 0 };
|
|
my $DATASERVER = { 'log_file' => '', 'fd' => undef, 'idx_file' => '', 'last_line' => 0, 'last_size' => 0, 'read_ahead_line' => '', 'read_ahead_pos' => 0 };
|
|
my $BUFFER = { 'log_file' => undef, 'fd' => [], 'idx_file' => undef, 'last_line' => 0, 'last_size' => 0, 'read_ahead_line' => undef, 'read_ahead_pos' => 0 };
|
|
|
|
########################################################################################
|
|
# SNMP Server class constructor.
|
|
########################################################################################
|
|
sub new ($$$) {
|
|
my ($class, $config, $dbh) = @_;
|
|
|
|
return undef unless $config->{'snmpconsole'} == 1;
|
|
|
|
# Start snmptrapd
|
|
if (start_snmptrapd ($config) != 0) {
|
|
return undef;
|
|
}
|
|
|
|
# Wait for the SNMP log file to be available
|
|
$SNMPTRAPD->{'log_file'} = $config->{'snmp_logfile'};
|
|
sleep ($config->{'server_threshold'}) if (! -e $SNMPTRAPD->{'log_file'});
|
|
if (!open ($SNMPTRAPD->{'fd'}, $SNMPTRAPD->{'log_file'})) {
|
|
logger ($config, ' [E] Could not open the SNMP log file ' . $SNMPTRAPD->{'log_file'} . ".", 1);
|
|
print_message ($config, ' [E] Could not open the SNMP log file ' . $SNMPTRAPD->{'log_file'} . ".", 1);
|
|
return 1;
|
|
}
|
|
init_log_file($config, $SNMPTRAPD);
|
|
|
|
# Create the Data Server SNMP log file if it does not exist.
|
|
if (defined($config->{'snmp_extlog'}) && $config->{'snmp_extlog'} ne '') {
|
|
$DATASERVER->{'log_file'} = $config->{'snmp_extlog'};
|
|
open(TMPFD, '>', $DATASERVER->{'log_file'}) && close(TMPFD) if (! -e $DATASERVER->{'log_file'});
|
|
if (!open ($DATASERVER->{'fd'}, $DATASERVER->{'log_file'})) {
|
|
logger ($config, ' [E] Could not open the Data Server SNMP log file ' . $DATASERVER->{'log_file'} . ".", 1);
|
|
print_message ($config, ' [E] Could not open the Data Server SNMP log file ' . $DATASERVER->{'log_file'} . ".", 1);
|
|
return 1;
|
|
}
|
|
init_log_file($config, $DATASERVER);
|
|
}
|
|
|
|
# Initialize semaphores and queues
|
|
@TaskQueue = ();
|
|
%PendingTasks = ();
|
|
$Sem = Thread::Semaphore->new;
|
|
$TaskSem = Thread::Semaphore->new (0);
|
|
$SourceSem = Thread::Semaphore->new (1);
|
|
|
|
# Call the constructor of the parent class
|
|
my $self = $class->SUPER::new($config, SNMPCONSOLE, \&PandoraFMS::SNMPServer::data_producer, \&PandoraFMS::SNMPServer::data_consumer, $dbh);
|
|
|
|
# Save the path of snmptrapd
|
|
$self->{'snmp_trapd'} = $config->{'snmp_trapd'};
|
|
|
|
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'} . " SNMP Console.", 2);
|
|
|
|
# Set the initial date for storm protection.
|
|
$pa_config->{"__storm_ref__"} = time();
|
|
|
|
# Set a server-specific period.
|
|
if ($pa_config->{'snmpconsole_threshold'} > 0) {
|
|
$self->setPeriod($pa_config->{'snmpconsole_threshold'});
|
|
}
|
|
|
|
$self->setNumThreads ($pa_config->{'snmpconsole_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_by_source;
|
|
my @tasks;
|
|
my @buffer;
|
|
|
|
# Reset storm protection counters
|
|
my $curr_time = time ();
|
|
if ($pa_config->{"__storm_ref__"} + $pa_config->{"snmp_storm_timeout"} < $curr_time
|
|
|| $pa_config->{'snmpconsole_lock'} == 1
|
|
) {
|
|
$pa_config->{"__storm_ref__"} = $curr_time;
|
|
%AGENTS = ();
|
|
}
|
|
|
|
# Make a local copy of locked sources.
|
|
$SourceSem->down ();
|
|
my $local_sources = {%Sources};
|
|
$SourceSem->up ();
|
|
|
|
for my $fs (($BUFFER, $SNMPTRAPD, $DATASERVER)) {
|
|
next unless defined($fs->{'fd'});
|
|
reset_if_truncated($pa_config, $fs);
|
|
while (my $line_with_pos = read_snmplogfile($fs)) {
|
|
my $line;
|
|
|
|
$fs->{'last_line'}++;
|
|
($fs->{'last_size'}, $line) = @$line_with_pos;
|
|
|
|
chomp ($line);
|
|
|
|
# Update index file
|
|
if (defined($fs->{'idx_file'})) {
|
|
open(my $idxfd, '>' . $fs->{'idx_file'});
|
|
print $idxfd $fs->{'last_line'} . ' ' . $fs->{'last_size'};
|
|
close $idxfd;
|
|
}
|
|
|
|
# Skip lines other than SNMP Trap logs
|
|
next unless ($line =~ m/^SNMPv[12]\[\*\*\]/);
|
|
|
|
# Storm protection.
|
|
my ($ver, $date, $time, $source, $null) = split(/\[\*\*\]/, $line, 5);
|
|
if ($ver eq "SNMPv2" || $pa_config->{'snmp_pdu_address'} eq '1' ) {
|
|
$source =~ s/(?:(?:TCP|UDP):\s*)?\[?([^] ]+)\]?(?::-?\d+)?(?:\s*->.*)?$/$1/;
|
|
}
|
|
|
|
next unless defined ($source);
|
|
if (! defined ($AGENTS{$source})) {
|
|
$AGENTS{$source}{'count'} = 1;
|
|
$AGENTS{$source}{'event'} = 0;
|
|
if (! defined ($SILENCEDSOURCES{$source})) {
|
|
$SILENCEDSOURCES{$source} = 0;
|
|
}
|
|
} else {
|
|
$AGENTS{$source}{'count'} += 1;
|
|
}
|
|
# Silence source.
|
|
if ((defined ($SILENCEDSOURCES{$source})) && ($SILENCEDSOURCES{$source} > $curr_time)) {
|
|
next;
|
|
}
|
|
if ($pa_config->{'snmp_storm_protection'} > 0 && $AGENTS{$source}{'count'} > $pa_config->{'snmp_storm_protection'}) {
|
|
if ($AGENTS{$source}{'event'} == 0) {
|
|
$SILENCEDSOURCES{$source} = $curr_time + $pa_config->{'snmp_storm_silence_period'};
|
|
my $silenced_time = ($pa_config->{'snmp_storm_silence_period'} eq 0 ? $pa_config->{"snmp_storm_timeout"} : $pa_config->{'snmp_storm_silence_period'});
|
|
pandora_event ($pa_config, "Too many traps coming from $source. Silenced for " . $silenced_time . " seconds.", 0, 0, 4, 0, 0, 'system', 0, $dbh);
|
|
}
|
|
$AGENTS{$source}{'event'} = 1;
|
|
next;
|
|
}
|
|
|
|
# Either buffer or process the trap.
|
|
if (source_lock($pa_config, $source, $local_sources) == 0) {
|
|
push(@buffer, $line);
|
|
} else {
|
|
push (@tasks, $line);
|
|
}
|
|
}
|
|
}
|
|
|
|
# Save the buffer for the next run.
|
|
$BUFFER->{'fd'} = \@buffer;
|
|
|
|
return @tasks;
|
|
}
|
|
|
|
###############################################################################
|
|
# Data consumer.
|
|
###############################################################################
|
|
sub data_consumer ($$) {
|
|
my ($self, $task) = @_;
|
|
my ($pa_config, $server_id, $dbh) = ($self->getConfig(), $self->getServerID(), $self->getDBH());
|
|
|
|
pandora_snmptrapd ($pa_config, $task, $server_id, $dbh);
|
|
|
|
# Unlock.
|
|
if ($pa_config->{'snmpconsole_lock'} == 1) {
|
|
my ($ver, $date, $time, $source, $null) = split(/\[\*\*\]/, $task, 5);
|
|
if ($ver eq "SNMPv2" || $pa_config->{'snmp_pdu_address'} eq '1' ) {
|
|
$source =~ s/(?:(?:TCP|UDP):\s*)?\[?([^] ]+)\]?(?::-?\d+)?(?:\s*->.*)?$/$1/;
|
|
}
|
|
source_unlock($pa_config, $source);
|
|
}
|
|
}
|
|
|
|
##########################################################################
|
|
# Process SNMP log file.
|
|
##########################################################################
|
|
sub pandora_snmptrapd {
|
|
my ($pa_config, $line, $server_id, $dbh) = @_;
|
|
|
|
(my $trap_ver, $line) = split(/\[\*\*\]/, $line, 2);
|
|
|
|
# Process SNMP filter
|
|
return if (matches_filter ($dbh, $pa_config, $line) == 1);
|
|
|
|
logger($pa_config, "Reading trap '$line'", 10);
|
|
my ($date, $time, $source, $oid, $type, $type_desc, $value, $data) = ('', '', '', '', '', '', '', '');
|
|
|
|
if ($trap_ver eq "SNMPv1") {
|
|
($date, $time, $source, $oid, $type, $type_desc, $value, $data) = split(/\[\*\*\]/, $line, 8);
|
|
|
|
$value = limpia_cadena ($value);
|
|
|
|
# Try to save as much information as possible if the trap could not be parsed
|
|
$oid = $type_desc if ($oid eq '' || $oid eq '.');
|
|
|
|
if (!defined($oid)) {
|
|
logger($pa_config, "[W] snmpTrapOID not found (Illegal SNMPv1 trap?)", 5);
|
|
return;
|
|
}
|
|
|
|
} elsif ($trap_ver eq "SNMPv2") {
|
|
($date, $time, $source, $data) = split(/\[\*\*\]/, $line, 4);
|
|
my @data = split(/\t/, $data);
|
|
|
|
shift @data; # Drop unused 1st data.
|
|
$oid = shift @data;
|
|
|
|
if (!defined($oid)) {
|
|
logger($pa_config, "[W] snmpTrapOID not found (Illegal SNMPv2 trap?)", 5);
|
|
return;
|
|
}
|
|
$oid =~ s/.* = OID: //;
|
|
if ($oid =~ m/^\.1\.3\.6\.1\.6\.3\.1\.1\.5\.([1-5])$/) {
|
|
$type = $1 - 1;
|
|
} else {
|
|
$type = 6;
|
|
}
|
|
$data = join("\t", @data);
|
|
}
|
|
|
|
if ($trap_ver eq "SNMPv2" || $pa_config->{'snmp_pdu_address'} eq '1' ) {
|
|
# extract IP address from %b part:
|
|
# * destination part (->[dest_ip]:dest_port) appears in Net-SNMP > 5.3
|
|
# * protocol name (TCP: or UDP:) and bracketted IP addr w/ port number appear in
|
|
# Net-SNMP > 5.1 (Net-SNMP 5.1 has IP addr only).
|
|
# * port number is signed (often negative) in Net-SNMP 5.2
|
|
$source =~ s/(?:(?:TCP|UDP):\s*)?\[?([^] ]+)\]?(?::-?\d+)?(?:\s*->.*)?$/$1/;
|
|
}
|
|
|
|
my $timestamp = $date . ' ' . $time;
|
|
my ($custom_oid, $custom_type, $custom_value) = ('', '', '');
|
|
|
|
# custom_type, custom_value is not used since 4.0 version, all custom data goes on custom_oid
|
|
$custom_oid = $data;
|
|
|
|
#Trap forwarding
|
|
if ($pa_config->{'snmp_forward_trap'}==1) {
|
|
my $trap_data_string = "";
|
|
|
|
#We loop through all the custom data of the received trap, creating the $trap_data_string string to forward the trap properly
|
|
while ($data =~ /([\.\d]+)\s=\s([^:]+):\s([\S ]+)/g) {
|
|
my ($trap_data, $trap_type, $trap_value) = ($1, $2, $3);
|
|
if ($trap_type eq "INTEGER") {
|
|
#FIX for translated traps from IF-MIB.txt MIB
|
|
$trap_value =~ s/\D//g;
|
|
$trap_data_string = $trap_data_string . "$trap_data i $trap_value ";
|
|
}
|
|
elsif ($trap_type eq "UNSIGNED"){
|
|
$trap_data_string = $trap_data_string . "$trap_data u $trap_value ";
|
|
}
|
|
elsif ($trap_type eq "COUNTER32"){
|
|
$trap_data_string = $trap_data_string . "$trap_data c $trap_value ";
|
|
}
|
|
elsif ($trap_type eq "STRING"){
|
|
$trap_data_string = $trap_data_string . "$trap_data s $trap_value ";
|
|
}
|
|
elsif ($trap_type eq "HEX STRING"){
|
|
$trap_data_string = $trap_data_string . "$trap_data x $trap_value ";
|
|
}
|
|
elsif ($trap_type eq "DECIMAL STRING"){
|
|
$trap_data_string = $trap_data_string . "$trap_data d $trap_value ";
|
|
}
|
|
elsif ($trap_type eq "NULLOBJ"){
|
|
$trap_data_string = $trap_data_string . "$trap_data n $trap_value ";
|
|
}
|
|
elsif ($trap_type eq "OBJID"){
|
|
$trap_data_string = $trap_data_string . "$trap_data o $trap_value ";
|
|
}
|
|
elsif ($trap_type eq "TIMETICKS"){
|
|
$trap_data_string = $trap_data_string . "$trap_data t $trap_value ";
|
|
}
|
|
elsif ($trap_type eq "IPADDRESS"){
|
|
$trap_data_string = $trap_data_string . "$trap_data a $trap_value ";
|
|
}
|
|
elsif ($trap_type eq "BITS"){
|
|
$trap_data_string = $trap_data_string . "$trap_data b $trap_value ";
|
|
}
|
|
}
|
|
|
|
#We distinguish between the three different kinds of SNMP forwarding
|
|
if ($pa_config->{'snmp_forward_version'} eq '3') {
|
|
system("snmptrap -v $pa_config->{'snmp_forward_version'} -n \"\" -a $pa_config->{'snmp_forward_authProtocol'} -A $pa_config->{'snmp_forward_authPassword'} -x $pa_config->{'snmp_forward_privProtocol'} -X $pa_config->{'snmp_forward_privPassword'} -l $pa_config->{'snmp_forward_secLevel'} -u $pa_config->{'snmp_forward_secName'} -e $pa_config->{'snmp_forward_engineid'} $pa_config->{'snmp_forward_ip'} '' $oid $trap_data_string");
|
|
}
|
|
elsif ($pa_config->{'snmp_forward_version'} eq '2' || $pa_config->{'snmp_forward_version'} eq '2c') {
|
|
system("snmptrap -v 2c -n \"\" -c $pa_config->{'snmp_forward_community'} $pa_config->{'snmp_forward_ip'} '' $oid $trap_data_string");
|
|
}
|
|
elsif ($pa_config->{'snmp_forward_version'} eq '1') {
|
|
#Because of tne SNMP v1 protocol, we must perform additional steps for creating the trap
|
|
my $value_sending = "";
|
|
my $type_sending = "";
|
|
|
|
if ($value eq ''){
|
|
$value_sending = "\"\"";
|
|
}
|
|
else {
|
|
$value_sending = $value;
|
|
$value_sending =~ s/[\$#@~!&*()\[\];.,:?^ `\\\/]+//g;
|
|
}
|
|
if ($type eq ''){
|
|
$type_sending = "\"\"";
|
|
}
|
|
else{
|
|
$type_sending = $type;
|
|
}
|
|
|
|
system("snmptrap -v 1 -c $pa_config->{'snmp_forward_community'} $pa_config->{'snmp_forward_ip'} $oid \"\" $type_sending $value_sending \"\" $trap_data_string");
|
|
}
|
|
}
|
|
|
|
# Insert the trap into the DB
|
|
if (! defined(enterprise_hook ('snmp_insert_trap', [$pa_config, $source, $oid, $type, $value, $custom_oid, $custom_value, $custom_type, $timestamp, $server_id, $dbh]))) {
|
|
my $trap_id = db_insert ($dbh, 'id_trap', 'INSERT INTO ttrap (timestamp, source, oid, type, value, oid_custom, value_custom, type_custom) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
|
$timestamp, $source, $oid, $type, $value, $custom_oid, $custom_value, $custom_type);
|
|
logger ($pa_config, "Received SNMP Trap from $source", 4);
|
|
|
|
# Evaluate alerts for this trap
|
|
pandora_evaluate_snmp_alerts ($pa_config, $trap_id, $source, $oid, $type, $oid, $value, $custom_oid, $dbh);
|
|
}
|
|
|
|
# Delay the consumption of the next task.
|
|
sleep($pa_config->{'snmp_delay'}) if ($pa_config->{'snmp_delay'} > 0);
|
|
}
|
|
|
|
########################################################################################
|
|
# Returns 1 if the given string matches any SNMP filter, 0 otherwise.
|
|
########################################################################################
|
|
sub matches_filter ($$$) {
|
|
my ($dbh, $pa_config, $string) = @_;
|
|
|
|
my @filter_unique_functions = get_db_rows ($dbh, 'SELECT DISTINCT(unified_filters_id) FROM tsnmp_filter ORDER BY unified_filters_id');
|
|
|
|
foreach my $filter_unique_func (@filter_unique_functions) {
|
|
# Get filters
|
|
my @filters = get_db_rows ($dbh, 'SELECT filter FROM tsnmp_filter WHERE unified_filters_id = ' . $filter_unique_func->{'unified_filters_id'});
|
|
|
|
my $eval_acum = 1;
|
|
foreach my $filter (@filters) {
|
|
my $regexp = safe_output($filter->{'filter'}) ;
|
|
my $eval_result;
|
|
|
|
# eval protects against server down (by invalid regular expressions)
|
|
$eval_result = eval {
|
|
$string =~ m/$regexp/i ;
|
|
};
|
|
|
|
if ($eval_result && $eval_acum) {
|
|
$eval_acum = 1;
|
|
}
|
|
else {
|
|
$eval_acum = 0;
|
|
last;
|
|
}
|
|
}
|
|
|
|
if ($eval_acum) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
########################################################################################
|
|
# Start snmptrapd, attempting to kill it if it is already running. Returns 0 if
|
|
# successful, 1 otherwise.
|
|
########################################################################################
|
|
sub start_snmptrapd ($) {
|
|
my ($config) = @_;
|
|
|
|
my $pid_file = '/var/run/pandora_snmptrapd.pid';
|
|
my $snmptrapd_running = 0;
|
|
|
|
# Manual start of snmptrapd
|
|
if ($config->{'snmp_trapd'} eq 'manual') {
|
|
my $noSNMPTrap = "No SNMP trap daemon configured. Start snmptrapd manually.";
|
|
logger ($config, $noSNMPTrap, 1);
|
|
print_message ($config, " [*] $noSNMPTrap", 1);
|
|
|
|
if (! -f $config->{'snmp_logfile'}) {
|
|
my $noLogFile = "SNMP log file " . $config->{'snmp_logfile'} . " not found.";
|
|
logger ($config, $noLogFile, 1);
|
|
print_message ($config, " [E] $noLogFile", 1);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if ( -e $pid_file && open (PIDFILE, $pid_file)) {
|
|
my $pid = <PIDFILE> + 0;
|
|
close PIDFILE;
|
|
|
|
# Check if snmptrapd is running
|
|
if ($snmptrapd_running = kill (0, $pid)) {
|
|
my $alreadyRunning = "snmptrapd (pid $pid) is already running, attempting to kill it...";
|
|
logger ($config, $alreadyRunning, 1);
|
|
print_message ($config, " [*] $alreadyRunning ", 1);
|
|
kill (9, $pid);
|
|
}
|
|
}
|
|
|
|
# Ignore auth failure traps
|
|
my $snmp_ignore_authfailure = ($config->{'snmp_ignore_authfailure'} eq '1' ? ' -a' : '');
|
|
|
|
# Select agent-addr field of the PDU or PDU source address for V1 traps
|
|
my $address_format = ($config->{'snmp_pdu_address'} eq '0' ? '%a' : '%b');
|
|
|
|
my $snmptrapd_args = ' -t -On -n' . $snmp_ignore_authfailure . ' -Lf ' . $config->{'snmp_logfile'} . ' -p ' . $pid_file;
|
|
$snmptrapd_args .= ' --format1=SNMPv1[**]%4y-%02.2m-%l[**]%02.2h:%02.2j:%02.2k[**]' . $address_format . '[**]%N[**]%w[**]%W[**]%q[**]%v\\\n';
|
|
$snmptrapd_args .= ' --format2=SNMPv2[**]%4y-%02.2m-%l[**]%02.2h:%02.2j:%02.2k[**]%b[**]%v\\\n';
|
|
|
|
if (system ($config->{'snmp_trapd'} . $snmptrapd_args . " >$DEVNULL 2>&1") != 0) {
|
|
my $showError = "Could not start snmptrapd.";
|
|
logger ($config, $showError, 1);
|
|
print_message ($config, " [E] $showError ", 1);
|
|
return 1;
|
|
}
|
|
|
|
print_message ($config, " [*] snmptrapd started and running.", 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
###############################################################################
|
|
# Read SNMP Log file with buffering (to handle multi-line Traps).
|
|
# Return reference of array (file-pos, line-data) if successful, undef othersise.
|
|
###############################################################################
|
|
sub read_snmplogfile($) {
|
|
my ($fs) = @_;
|
|
my $line;
|
|
my $pos;
|
|
|
|
# Reading from a temporary buffer.
|
|
if (ref($fs->{'fd'}) eq 'ARRAY') {
|
|
if ($#{$fs->{'fd'}} < 0) {
|
|
return undef;
|
|
}
|
|
|
|
return [0, shift(@{$fs->{'fd'}})];
|
|
}
|
|
|
|
if(defined($fs->{'read_ahead_line'})) {
|
|
# Restore saved line
|
|
$line = $fs->{'read_ahead_line'};
|
|
$pos = $fs->{'read_ahead_pos'};
|
|
}
|
|
else {
|
|
# No saved line
|
|
my $fd = $fs->{'fd'};
|
|
$line = <$fd>;
|
|
$pos = tell($fs->{'fd'});
|
|
}
|
|
|
|
return undef if (! defined($line));
|
|
|
|
my $retry_count = 0;
|
|
|
|
# More lines ?
|
|
while(1) {
|
|
my $fd = $fs->{'fd'};
|
|
while($fs->{'read_ahead_line'} = <$fd>) {
|
|
|
|
# Get current file position
|
|
$fs->{'read_ahead_pos'} = tell($fs->{'fd'});
|
|
|
|
# Get out of the loop if you find another Trap
|
|
last if($fs->{'read_ahead_line'} =~ /^SNMP/ );
|
|
|
|
# $fs->{'read_ahead_line'} looks continued line...
|
|
|
|
# Append to the line and correct the position
|
|
chomp($line);
|
|
$line .= "$fs->{'read_ahead_line'}";
|
|
$pos = $fs->{'read_ahead_pos'};
|
|
}
|
|
|
|
# if $line looks incomplete, try to get continued line
|
|
# just within 10sec. After that, giving up to complete it
|
|
# and flush $line as it is.
|
|
last if(chomp($line) > 0 || $retry_count++ >= 10);
|
|
|
|
sleep(1);
|
|
}
|
|
|
|
# return fetched line with file position to be saved.
|
|
return [$pos, $line];
|
|
}
|
|
|
|
###############################################################################
|
|
# Initialize the fs structure for a trap log file.
|
|
###############################################################################
|
|
sub init_log_file($$$) {
|
|
my ($config, $fs) = @_;
|
|
|
|
# Process index file, if available
|
|
($fs->{'idx_file'}, $fs->{'last_line'}, $fs->{'last_size'}) = ($fs->{'log_file'} . '.index', 0, 0);
|
|
if (-e $fs->{'idx_file'}) {
|
|
open (my $idxfd, $fs->{'idx_file'}) or return;
|
|
my $idx_data = <$idxfd>;
|
|
close $idxfd;
|
|
($fs->{'last_line'}, $fs->{'last_size'}) = split(/\s+/, $idx_data);
|
|
}
|
|
my $log_size = (stat ($fs->{'log_file'}))[7];
|
|
|
|
# New SNMP log file found
|
|
if ($log_size < $fs->{'last_size'}) {
|
|
unlink ($fs->{'idx_file'});
|
|
($fs->{'last_line'}, $fs->{'last_size'}) = (0, 0);
|
|
}
|
|
|
|
# Skip already processed lines
|
|
read_snmplogfile($fs) for (1..$fs->{'last_line'});
|
|
}
|
|
|
|
###############################################################################
|
|
# Reset the index if the file has been truncated.
|
|
###############################################################################
|
|
sub reset_if_truncated($$) {
|
|
my ($pa_config, $fs) = @_;
|
|
|
|
if (!defined($fs->{'log_file'})) {
|
|
return;
|
|
}
|
|
|
|
my $log_size = (stat ($fs->{'log_file'}))[7];
|
|
|
|
# New SNMP log file found
|
|
if ($log_size < $fs->{'last_size'}) {
|
|
logger ($pa_config, 'File ' . $fs->{'log_file'} . ' was truncated.', 10);
|
|
unlink ($fs->{'idx_file'});
|
|
($fs->{'last_line'}, $fs->{'last_size'}) = (0, 0);
|
|
seek($fs->{'fd'}, 0, 0);
|
|
}
|
|
}
|
|
|
|
##########################################################################
|
|
# Get a lock on the given source. Return 1 on success, 0 otherwise.
|
|
##########################################################################
|
|
sub source_lock($$$) {
|
|
my ($pa_config, $source, $local_sources) = @_;
|
|
|
|
# Locking is disabled.
|
|
if ($pa_config->{'snmpconsole_lock'} == 0) {
|
|
return 1;
|
|
}
|
|
|
|
if (defined($local_sources->{$source})) {
|
|
return 0;
|
|
}
|
|
|
|
$local_sources->{$source} = 1;
|
|
$SourceSem->down ();
|
|
$Sources{$source} = 1;
|
|
$SourceSem->up ();
|
|
|
|
return 1;
|
|
}
|
|
|
|
##########################################################################
|
|
# Remove the lock on the given source.
|
|
##########################################################################
|
|
sub source_unlock {
|
|
my ($pa_config, $source) = @_;
|
|
|
|
# Locking is disabled.
|
|
if ($pa_config->{'snmpconsole_lock'} == 0) {
|
|
return;
|
|
}
|
|
|
|
$SourceSem->down ();
|
|
delete ($Sources{$source});
|
|
$SourceSem->up ();
|
|
}
|
|
|
|
###############################################################################
|
|
# Clean-up when the server is destroyed.
|
|
###############################################################################
|
|
sub DESTROY {
|
|
my $self = shift;
|
|
|
|
if ($self->{'snmp_trapd'} ne 'manual') {
|
|
my $pid_file = '/var/run/pandora_snmptrapd.pid';
|
|
if (-e $pid_file) {
|
|
my $pid = `cat $pid_file 2>$DEVNULL`;
|
|
if (defined($pid) && ("$pid" ne "") && looks_like_number($pid)) {
|
|
system ("kill -9 $pid");
|
|
}
|
|
|
|
unlink ($pid_file);
|
|
}
|
|
}
|
|
}
|
|
|
|
1;
|
|
__END__
|