"Fossies" - the Fresh Open Source Software Archive

Member "pandora_server/lib/PandoraFMS/SNMPServer.pm" (15 Sep 2021, 22598 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 "SNMPServer.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::SNMPServer;
    2 ##########################################################################
    3 # Pandora FMS SNMP Console.
    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 Time::Local;
   28 use Time::HiRes qw(usleep);
   29 use XML::Simple;
   30 
   31 use Scalar::Util qw(looks_like_number);
   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 
   41 # Inherits from PandoraFMS::ProducerConsumerServer
   42 our @ISA = qw(PandoraFMS::ProducerConsumerServer);
   43 
   44 # Global variables
   45 my @TaskQueue :shared;
   46 my %PendingTasks :shared;
   47 my $Sem :shared;
   48 my %Sources :shared;
   49 my $SourceSem :shared;
   50 my $TaskSem :shared;
   51 
   52 # Trap statistics by agent
   53 my %AGENTS = ();
   54 
   55 # Sources silenced by storm protection.
   56 my %SILENCEDSOURCES = ();
   57 
   58 # Index and buffer management for trap log files
   59 my $SNMPTRAPD  = { 'log_file' => '', 'fd' => undef, 'idx_file' => '', 'last_line' => 0, 'last_size' => 0, 'read_ahead_line' => '', 'read_ahead_pos' => 0 };
   60 my $DATASERVER = { 'log_file' => '', 'fd' => undef, 'idx_file' => '', 'last_line' => 0, 'last_size' => 0, 'read_ahead_line' => '', 'read_ahead_pos' => 0 };
   61 my $BUFFER     = { 'log_file' => undef, 'fd' => [], 'idx_file' => undef, 'last_line' => 0, 'last_size' => 0, 'read_ahead_line' => undef, 'read_ahead_pos' => 0 };
   62 
   63 ########################################################################################
   64 # SNMP Server class constructor.
   65 ########################################################################################
   66 sub new ($$$) {
   67     my ($class, $config, $dbh) = @_;
   68 
   69     return undef unless $config->{'snmpconsole'} == 1;
   70 
   71     # Start snmptrapd
   72     if (start_snmptrapd ($config) != 0) {
   73         return undef;
   74     }
   75     
   76     # Wait for the SNMP log file to be available
   77     $SNMPTRAPD->{'log_file'} = $config->{'snmp_logfile'};
   78     sleep ($config->{'server_threshold'}) if (! -e $SNMPTRAPD->{'log_file'});
   79     if (!open ($SNMPTRAPD->{'fd'}, $SNMPTRAPD->{'log_file'})) {
   80         logger ($config, ' [E] Could not open the SNMP log file ' . $SNMPTRAPD->{'log_file'} . ".", 1);
   81         print_message ($config, ' [E] Could not open the SNMP log file ' . $SNMPTRAPD->{'log_file'} . ".", 1);
   82         return 1;
   83     }
   84     init_log_file($config, $SNMPTRAPD);
   85 
   86     # Create the Data Server SNMP log file if it does not exist.
   87     if (defined($config->{'snmp_extlog'}) && $config->{'snmp_extlog'} ne '') {
   88         $DATASERVER->{'log_file'} = $config->{'snmp_extlog'};
   89         open(TMPFD, '>', $DATASERVER->{'log_file'}) && close(TMPFD) if (! -e $DATASERVER->{'log_file'});
   90         if (!open ($DATASERVER->{'fd'}, $DATASERVER->{'log_file'})) {
   91             logger ($config, ' [E] Could not open the Data Server SNMP log file ' . $DATASERVER->{'log_file'} . ".", 1);
   92             print_message ($config, ' [E] Could not open the Data Server SNMP log file ' . $DATASERVER->{'log_file'} . ".", 1);
   93             return 1;
   94         }
   95         init_log_file($config, $DATASERVER);
   96     }
   97 
   98     # Initialize semaphores and queues
   99     @TaskQueue = ();
  100     %PendingTasks = ();
  101     $Sem = Thread::Semaphore->new;
  102     $TaskSem = Thread::Semaphore->new (0);
  103     $SourceSem = Thread::Semaphore->new (1);
  104 
  105     # Call the constructor of the parent class
  106     my $self = $class->SUPER::new($config, SNMPCONSOLE, \&PandoraFMS::SNMPServer::data_producer, \&PandoraFMS::SNMPServer::data_consumer, $dbh);
  107 
  108     # Save the path of snmptrapd
  109     $self->{'snmp_trapd'} = $config->{'snmp_trapd'};
  110 
  111     bless $self, $class;
  112     return $self;
  113 }
  114 
  115 ###############################################################################
  116 # Run.
  117 ###############################################################################
  118 sub run ($) {
  119     my $self = shift;
  120     my $pa_config = $self->getConfig ();
  121 
  122     print_message ($pa_config, " [*] Starting " . $pa_config->{'rb_product_name'} . " SNMP Console.", 2);
  123     
  124     # Set the initial date for storm protection.
  125     $pa_config->{"__storm_ref__"} = time();
  126 
  127     # Set a server-specific period.
  128     if ($pa_config->{'snmpconsole_threshold'} > 0) {
  129         $self->setPeriod($pa_config->{'snmpconsole_threshold'});
  130     }
  131 
  132     $self->setNumThreads ($pa_config->{'snmpconsole_threads'});
  133     $self->SUPER::run (\@TaskQueue, \%PendingTasks, $Sem, $TaskSem);
  134 }
  135 
  136 ###############################################################################
  137 # Data producer.
  138 ###############################################################################
  139 sub data_producer ($) {
  140     my $self = shift;
  141     my ($pa_config, $dbh) = ($self->getConfig (), $self->getDBH ());
  142 
  143     my %tasks_by_source;
  144     my @tasks;
  145     my @buffer;
  146     
  147     # Reset storm protection counters
  148     my $curr_time = time ();
  149     if ($pa_config->{"__storm_ref__"} + $pa_config->{"snmp_storm_timeout"} < $curr_time) {
  150         $pa_config->{"__storm_ref__"} = $curr_time;
  151         %AGENTS = ();
  152     }
  153 
  154     # Make a local copy of locked sources.
  155     $SourceSem->down ();
  156     my $local_sources = {%Sources};
  157     $SourceSem->up ();
  158 
  159     for my $fs (($BUFFER, $SNMPTRAPD, $DATASERVER)) {
  160         next unless defined($fs->{'fd'});
  161         reset_if_truncated($pa_config, $fs);
  162         while (my $line_with_pos = read_snmplogfile($fs)) {
  163             my $line;
  164     
  165             $fs->{'last_line'}++;
  166             ($fs->{'last_size'}, $line) = @$line_with_pos;
  167     
  168             chomp ($line);
  169     
  170             # Update index file
  171             if (defined($fs->{'idx_file'})) {
  172                 open(my $idxfd, '>' . $fs->{'idx_file'});
  173                 print $idxfd $fs->{'last_line'} . ' ' . $fs->{'last_size'};
  174                 close $idxfd;
  175             }
  176     
  177             # Skip lines other than SNMP Trap logs
  178             next unless ($line =~ m/^SNMPv[12]\[\*\*\]/);
  179     
  180             # Storm protection.
  181             my ($ver, $date, $time, $source, $null) = split(/\[\*\*\]/, $line, 5);
  182             if ($ver eq "SNMPv2" || $pa_config->{'snmp_pdu_address'} eq '1' ) {
  183                 $source =~ s/(?:(?:TCP|UDP):\s*)?\[?([^] ]+)\]?(?::-?\d+)?(?:\s*->.*)?$/$1/;
  184             }
  185 
  186             next unless defined ($source);
  187             if (! defined ($AGENTS{$source})) {
  188                 $AGENTS{$source}{'count'} = 1;
  189                 $AGENTS{$source}{'event'} = 0;
  190                 if (! defined ($SILENCEDSOURCES{$source})) {
  191                     $SILENCEDSOURCES{$source} = 0;
  192                 }
  193             } else {
  194                 $AGENTS{$source}{'count'} += 1;
  195             }
  196             # Silence source.
  197             if ((defined ($SILENCEDSOURCES{$source})) && ($SILENCEDSOURCES{$source} > $curr_time)) {
  198                 next;
  199             }
  200             if ($pa_config->{'snmp_storm_protection'} > 0 && $AGENTS{$source}{'count'} > $pa_config->{'snmp_storm_protection'}) {
  201                 if ($AGENTS{$source}{'event'} == 0) {
  202                     $SILENCEDSOURCES{$source} = $curr_time + $pa_config->{'snmp_storm_silence_period'};
  203                     my $silenced_time = ($pa_config->{'snmp_storm_silence_period'} eq 0 ? $pa_config->{"snmp_storm_timeout"} : $pa_config->{'snmp_storm_silence_period'});
  204                     pandora_event ($pa_config, "Too many traps coming from $source. Silenced for " . $silenced_time . " seconds.", 0, 0, 4, 0, 0, 'system', 0, $dbh);
  205                 }
  206                 $AGENTS{$source}{'event'} = 1;
  207                 next;
  208             }
  209 
  210             # Either buffer or process the trap.
  211             if (source_lock($pa_config, $source, $local_sources) == 0) {
  212                 push(@buffer, $line);
  213             } else {
  214                 push (@tasks, $line);
  215             }
  216         }
  217     }
  218 
  219     # Save the buffer for the next run.
  220     $BUFFER->{'fd'} = \@buffer;
  221 
  222     return @tasks;
  223 }
  224 
  225 ###############################################################################
  226 # Data consumer.
  227 ###############################################################################
  228 sub data_consumer ($$) {
  229     my ($self, $task) = @_;
  230     my ($pa_config, $server_id, $dbh) = ($self->getConfig(), $self->getServerID(), $self->getDBH());
  231 
  232     pandora_snmptrapd ($pa_config, $task, $server_id, $dbh);
  233     
  234     # Unlock.
  235     if ($pa_config->{'snmpconsole_lock'} == 1) {
  236         my ($ver, $date, $time, $source, $null) = split(/\[\*\*\]/, $task, 5);
  237         if ($ver eq "SNMPv2" || $pa_config->{'snmp_pdu_address'} eq '1' ) {
  238             $source =~ s/(?:(?:TCP|UDP):\s*)?\[?([^] ]+)\]?(?::-?\d+)?(?:\s*->.*)?$/$1/;
  239         }
  240         source_unlock($pa_config, $source);
  241     }
  242 }
  243 
  244 ##########################################################################
  245 # Process SNMP log file.
  246 ##########################################################################
  247 sub pandora_snmptrapd {
  248     my ($pa_config, $line, $server_id, $dbh) = @_;
  249 
  250     (my $trap_ver, $line) = split(/\[\*\*\]/, $line, 2);
  251 
  252     # Process SNMP filter
  253     return if (matches_filter ($dbh, $pa_config, $line) == 1);
  254 
  255     logger($pa_config, "Reading trap '$line'", 10);
  256     my ($date, $time, $source, $oid, $type, $type_desc, $value, $data) = ('', '', '', '', '', '', '', '');
  257 
  258     if ($trap_ver eq "SNMPv1") {
  259         ($date, $time, $source, $oid, $type, $type_desc, $value, $data) = split(/\[\*\*\]/, $line, 8);
  260 
  261         $value = limpia_cadena ($value);
  262 
  263         # Try to save as much information as possible if the trap could not be parsed
  264         $oid = $type_desc if ($oid eq '' || $oid eq '.');
  265 
  266         if (!defined($oid)) {
  267             logger($pa_config, "[W] snmpTrapOID not found (Illegal SNMPv1 trap?)", 5);
  268             return;
  269         }
  270 
  271     } elsif ($trap_ver eq "SNMPv2") {
  272         ($date, $time, $source, $data) = split(/\[\*\*\]/, $line, 4);
  273         my @data = split(/\t/, $data);
  274 
  275         shift @data; # Drop unused 1st data.
  276         $oid = shift @data;
  277 
  278         if (!defined($oid)) {
  279             logger($pa_config, "[W] snmpTrapOID not found (Illegal SNMPv2 trap?)", 5);
  280             return;
  281         }
  282         $oid =~ s/.* = OID: //;
  283         if ($oid =~ m/^\.1\.3\.6\.1\.6\.3\.1\.1\.5\.([1-5])$/) {
  284             $type = $1 - 1;
  285         } else {
  286             $type = 6;
  287         }
  288         $data = join("\t", @data);
  289     }
  290 
  291     if ($trap_ver eq "SNMPv2" || $pa_config->{'snmp_pdu_address'} eq '1' ) {
  292         # extract IP address from %b part:
  293         #  * destination part (->[dest_ip]:dest_port) appears in Net-SNMP > 5.3
  294         #  * protocol name (TCP: or UDP:) and bracketted IP addr w/ port number appear in
  295         #    Net-SNMP > 5.1 (Net-SNMP 5.1 has IP addr only).
  296         #  * port number is signed (often negative) in Net-SNMP 5.2
  297         $source =~ s/(?:(?:TCP|UDP):\s*)?\[?([^] ]+)\]?(?::-?\d+)?(?:\s*->.*)?$/$1/;
  298     }
  299 
  300     my $timestamp = $date . ' ' . $time;
  301     my ($custom_oid, $custom_type, $custom_value) = ('', '', '');
  302 
  303     # custom_type, custom_value is not used since 4.0 version, all custom data goes on custom_oid
  304     $custom_oid = $data;
  305 
  306     #Trap forwarding
  307     if ($pa_config->{'snmp_forward_trap'}==1) {
  308         my $trap_data_string = "";
  309 
  310         #We loop through all the custom data of the received trap, creating the $trap_data_string string to forward the trap properly
  311         while ($data =~ /([\.\d]+)\s=\s([^:]+):\s([\S ]+)/g) {
  312             my ($trap_data, $trap_type, $trap_value) = ($1, $2, $3);
  313             if ($trap_type eq "INTEGER") {
  314                 #FIX for translated traps from IF-MIB.txt MIB
  315                 $trap_value =~ s/\D//g;
  316                 $trap_data_string = $trap_data_string . "$trap_data i $trap_value ";
  317             }
  318             elsif ($trap_type eq "UNSIGNED"){
  319                 $trap_data_string = $trap_data_string . "$trap_data u $trap_value ";
  320             }
  321             elsif ($trap_type eq "COUNTER32"){
  322                     $trap_data_string = $trap_data_string . "$trap_data c $trap_value ";
  323             }
  324             elsif ($trap_type eq "STRING"){
  325                     $trap_data_string = $trap_data_string . "$trap_data s $trap_value ";
  326             }
  327             elsif ($trap_type eq "HEX STRING"){
  328                     $trap_data_string = $trap_data_string . "$trap_data x $trap_value ";
  329             }
  330             elsif ($trap_type eq "DECIMAL STRING"){
  331                     $trap_data_string = $trap_data_string . "$trap_data d $trap_value ";
  332             }
  333             elsif ($trap_type eq "NULLOBJ"){
  334                     $trap_data_string = $trap_data_string . "$trap_data n $trap_value ";
  335             }
  336             elsif ($trap_type eq "OBJID"){
  337                     $trap_data_string = $trap_data_string . "$trap_data o $trap_value ";
  338             }
  339             elsif ($trap_type eq "TIMETICKS"){
  340                     $trap_data_string = $trap_data_string . "$trap_data t $trap_value ";
  341             }
  342             elsif ($trap_type eq "IPADDRESS"){
  343                     $trap_data_string = $trap_data_string . "$trap_data a $trap_value ";
  344             }
  345             elsif ($trap_type eq "BITS"){
  346                     $trap_data_string = $trap_data_string . "$trap_data b $trap_value ";
  347             }
  348         }
  349 
  350         #We distinguish between the three different kinds of SNMP forwarding
  351         if ($pa_config->{'snmp_forward_version'} eq '3') {
  352             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");
  353         }
  354         elsif ($pa_config->{'snmp_forward_version'} eq '2' || $pa_config->{'snmp_forward_version'} eq '2c') {
  355             system("snmptrap -v 2c -n \"\" -c $pa_config->{'snmp_forward_community'} $pa_config->{'snmp_forward_ip'} '' $oid $trap_data_string");
  356         }
  357         elsif ($pa_config->{'snmp_forward_version'} eq '1') {
  358             #Because of tne SNMP v1 protocol, we must perform additional steps for creating the trap
  359             my $value_sending = "";
  360             my $type_sending = "";
  361 
  362             if ($value eq ''){
  363                 $value_sending = "\"\"";
  364             }
  365             else {
  366                 $value_sending = $value;
  367                 $value_sending =~ s/[\$#@~!&*()\[\];.,:?^ `\\\/]+//g;
  368             }
  369             if ($type eq ''){
  370                 $type_sending = "\"\"";
  371             }
  372             else{
  373                 $type_sending = $type;
  374             }
  375 
  376             system("snmptrap -v 1 -c $pa_config->{'snmp_forward_community'} $pa_config->{'snmp_forward_ip'} $oid \"\" $type_sending $value_sending \"\" $trap_data_string");
  377         }
  378     }
  379 
  380     # Insert the trap into the DB
  381     if (! defined(enterprise_hook ('snmp_insert_trap', [$pa_config, $source, $oid, $type, $value, $custom_oid, $custom_value, $custom_type, $timestamp, $server_id, $dbh]))) {
  382         my $trap_id = db_insert ($dbh, 'id_trap', 'INSERT INTO ttrap (timestamp, source, oid, type, value, oid_custom, value_custom,  type_custom) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
  383                                  $timestamp, $source, $oid, $type, $value, $custom_oid, $custom_value, $custom_type);
  384         logger ($pa_config, "Received SNMP Trap from $source", 4);
  385 
  386         # Evaluate alerts for this trap
  387         pandora_evaluate_snmp_alerts ($pa_config, $trap_id, $source, $oid, $type, $oid, $value, $custom_oid, $dbh);
  388     }
  389 
  390     # Delay the consumption of the next task.
  391     sleep($pa_config->{'snmp_delay'}) if ($pa_config->{'snmp_delay'} > 0);
  392 }
  393 
  394 ########################################################################################
  395 # Returns 1 if the given string matches any SNMP filter, 0 otherwise.
  396 ########################################################################################
  397 sub matches_filter ($$$) {
  398     my ($dbh, $pa_config, $string) = @_;
  399     
  400     my @filter_unique_functions = get_db_rows ($dbh, 'SELECT DISTINCT(unified_filters_id) FROM tsnmp_filter ORDER BY unified_filters_id');
  401     
  402     foreach my $filter_unique_func (@filter_unique_functions) {
  403         # Get filters
  404         my @filters = get_db_rows ($dbh, 'SELECT filter FROM tsnmp_filter WHERE unified_filters_id = ' . $filter_unique_func->{'unified_filters_id'});
  405         
  406         my $eval_acum = 1;
  407         foreach my $filter (@filters) {
  408             my $regexp = safe_output($filter->{'filter'}) ;
  409             my $eval_result;
  410 
  411             # eval protects against server down (by invalid regular expressions)
  412             $eval_result = eval {
  413                 $string =~ m/$regexp/i ;
  414             };
  415 
  416             if ($eval_result && $eval_acum) {
  417                 $eval_acum = 1;
  418             }
  419             else {
  420                 $eval_acum = 0;
  421                 last;
  422             }
  423         }
  424         
  425         if ($eval_acum) {
  426             return 1;
  427         }
  428     }
  429     
  430     return 0;
  431 }
  432 
  433 ########################################################################################
  434 # Start snmptrapd, attempting to kill it if it is already running. Returns 0 if
  435 # successful, 1 otherwise.
  436 ########################################################################################
  437 sub start_snmptrapd ($) {
  438     my ($config) = @_;
  439     
  440     my $pid_file = '/var/run/pandora_snmptrapd.pid';
  441     my $snmptrapd_running = 0;
  442 
  443     # Manual start of snmptrapd
  444     if ($config->{'snmp_trapd'} eq 'manual') {
  445         logger ($config, "No SNMP trap daemon configured. Start snmptrapd manually.", 1);
  446         print_message ($config, " [*] No SNMP trap daemon configured. Start snmptrapd manually.", 1);
  447 
  448         if (! -f $config->{'snmp_logfile'}) {
  449             logger ($config, "SNMP log file " . $config->{'snmp_logfile'} . " not found.", 1);
  450             print_message ($config, " [E] SNMP log file " . $config->{'snmp_logfile'} . " not found.", 1);
  451             return 1;
  452         }
  453         
  454         return 0;
  455     }
  456     
  457     if ( -e $pid_file && open (PIDFILE, $pid_file)) {
  458         my $pid = <PIDFILE> + 0;
  459         close PIDFILE;      
  460 
  461         # Check if snmptrapd is running
  462         if ($snmptrapd_running = kill (0, $pid)) {
  463             logger ($config, "snmptrapd (pid $pid) is already running, attempting to kill it...", 1);
  464             print_message ($config, "snmptrapd (pid $pid) is already running, attempting to kill it...", 1);
  465             kill (9, $pid);
  466         }
  467     }
  468 
  469     # Ignore auth failure traps
  470     my $snmp_ignore_authfailure = ($config->{'snmp_ignore_authfailure'} eq '1' ? ' -a' : '');
  471 
  472     # Select agent-addr field of the PDU or PDU source address for V1 traps
  473     my $address_format = ($config->{'snmp_pdu_address'} eq '0' ? '%a' : '%b');
  474     
  475     my $snmptrapd_args = ' -t -On -n' . $snmp_ignore_authfailure . ' -Lf ' . $config->{'snmp_logfile'} . ' -p ' . $pid_file;
  476     $snmptrapd_args .=  ' --format1=SNMPv1[**]%4y-%02.2m-%l[**]%02.2h:%02.2j:%02.2k[**]' . $address_format . '[**]%N[**]%w[**]%W[**]%q[**]%v\\\n';
  477     $snmptrapd_args .=  ' --format2=SNMPv2[**]%4y-%02.2m-%l[**]%02.2h:%02.2j:%02.2k[**]%b[**]%v\\\n';
  478 
  479     if (system ($config->{'snmp_trapd'} . $snmptrapd_args . " >$DEVNULL 2>&1") != 0) {
  480         logger ($config, " [E] Could not start snmptrapd.", 1);
  481         print_message ($config, " [E] Could not start snmptrapd.", 1);
  482         return 1;
  483     }
  484 
  485     return 0;
  486 }
  487 
  488 ###############################################################################
  489 # Read SNMP Log file with buffering (to handle multi-line Traps).
  490 # Return reference of array (file-pos, line-data) if successful, undef othersise.
  491 ###############################################################################
  492 sub read_snmplogfile($) {
  493     my ($fs) = @_;
  494     my $line;
  495     my $pos;
  496 
  497     # Reading from a temporary buffer.
  498     if (ref($fs->{'fd'}) eq 'ARRAY') {
  499         if ($#{$fs->{'fd'}} < 0) {
  500             return undef;
  501         }
  502 
  503         return [0, shift(@{$fs->{'fd'}})];
  504     }
  505 
  506     if(defined($fs->{'read_ahead_line'})) {
  507         # Restore saved line
  508         $line = $fs->{'read_ahead_line'};
  509         $pos = $fs->{'read_ahead_pos'};
  510     }
  511     else {
  512         # No saved line
  513         my $fd = $fs->{'fd'};
  514         $line = <$fd>;
  515         $pos = tell($fs->{'fd'});
  516     }
  517 
  518     return undef if (! defined($line));
  519 
  520     my $retry_count = 0;
  521 
  522     # More lines ?
  523     while(1) {
  524         my $fd = $fs->{'fd'};
  525         while($fs->{'read_ahead_line'} = <$fd>) {
  526 
  527             # Get current file position
  528             $fs->{'read_ahead_pos'} = tell($fs->{'fd'});
  529 
  530             # Get out of the loop if you find another Trap
  531             last if($fs->{'read_ahead_line'} =~ /^SNMP/ );
  532 
  533             # $fs->{'read_ahead_line'} looks continued line...
  534 
  535             # Append to the line and correct the position
  536             chomp($line);
  537             $line .= "$fs->{'read_ahead_line'}";
  538             $pos = $fs->{'read_ahead_pos'};
  539         }
  540 
  541         # if $line looks incomplete, try to get continued line
  542         # just within 10sec.  After that, giving up to complete it
  543         # and flush $line as it is.
  544         last if(chomp($line) > 0  || $retry_count++ >= 10);
  545 
  546         sleep(1);
  547     }
  548 
  549     # return fetched line with file position to be saved.
  550     return [$pos, $line];
  551 }
  552 
  553 ###############################################################################
  554 # Initialize the fs structure for a trap log file.
  555 ###############################################################################
  556 sub init_log_file($$$) {
  557     my ($config, $fs) = @_;
  558 
  559     # Process index file, if available
  560     ($fs->{'idx_file'}, $fs->{'last_line'}, $fs->{'last_size'}) = ($fs->{'log_file'} . '.index', 0, 0);
  561     if (-e  $fs->{'idx_file'}) {
  562         open (my $idxfd, $fs->{'idx_file'}) or return;
  563         my $idx_data = <$idxfd>;
  564         close $idxfd;
  565         ($fs->{'last_line'}, $fs->{'last_size'}) = split(/\s+/, $idx_data);
  566     }
  567     my $log_size = (stat ($fs->{'log_file'}))[7];
  568 
  569     # New SNMP log file found
  570     if ($log_size < $fs->{'last_size'}) {
  571         unlink ($fs->{'idx_file'});
  572         ($fs->{'last_line'}, $fs->{'last_size'}) = (0, 0);
  573     }
  574 
  575     # Skip already processed lines
  576     read_snmplogfile($fs) for (1..$fs->{'last_line'});
  577 }
  578 
  579 ###############################################################################
  580 # Reset the index if the file has been truncated.
  581 ###############################################################################
  582 sub reset_if_truncated($$) {
  583     my ($pa_config, $fs) = @_;
  584 
  585     if (!defined($fs->{'log_file'})) {
  586         return;
  587     }
  588 
  589     my $log_size = (stat ($fs->{'log_file'}))[7];
  590 
  591     # New SNMP log file found
  592     if ($log_size < $fs->{'last_size'}) {
  593         logger ($pa_config, 'File ' . $fs->{'log_file'} . ' was truncated.', 10);
  594         unlink ($fs->{'idx_file'});
  595         ($fs->{'last_line'}, $fs->{'last_size'}) = (0, 0);
  596         seek($fs->{'fd'}, 0, 0);
  597     }
  598 }
  599 
  600 ##########################################################################
  601 # Get a lock on the given source. Return 1 on success, 0 otherwise.
  602 ##########################################################################
  603 sub source_lock($$$) {
  604     my ($pa_config, $source, $local_sources) = @_;
  605 
  606     # Locking is disabled.
  607     if ($pa_config->{'snmpconsole_lock'} == 0) {
  608         return 1;
  609     }
  610 
  611     if (defined($local_sources->{$source})) {
  612         return 0;
  613     }
  614 
  615     $local_sources->{$source} = 1;
  616     $SourceSem->down ();
  617     $Sources{$source} = 1;
  618     $SourceSem->up ();
  619 
  620     return 1;
  621 }
  622 
  623 ##########################################################################
  624 # Remove the lock on the given source.
  625 ##########################################################################
  626 sub source_unlock {
  627     my ($pa_config, $source) = @_;
  628 
  629     # Locking is disabled.
  630     if ($pa_config->{'snmpconsole_lock'} == 0) {
  631         return;
  632     }
  633 
  634     $SourceSem->down ();
  635     delete ($Sources{$source});
  636     $SourceSem->up ();
  637 }
  638 
  639 ###############################################################################
  640 # Clean-up when the server is destroyed.
  641 ###############################################################################
  642 sub DESTROY {
  643     my $self = shift;
  644 
  645     if ($self->{'snmp_trapd'} ne 'manual') {
  646         my $pid_file = '/var/run/pandora_snmptrapd.pid';
  647         if (-e $pid_file) {
  648             my $pid = `cat $pid_file 2>$DEVNULL`;
  649             if (defined($pid) && ("$pid" ne "") && looks_like_number($pid)) {
  650                     system ("kill -9 $pid");
  651             }
  652 
  653             unlink ($pid_file);
  654         }
  655     }
  656 }
  657 
  658 1;
  659 __END__