Page 1 of 1

check_logfile config logfilemissing

Posted: Wed May 09, 2018 11:31 am
by optionstechnology
I am using check_logfile on a linux server

But I want it to alert OK when the file is missing instead of UNKNOWN

There is a option for this --logfilemissing=ok

Which works when I use it from command line, but I am using a config file for these checks, and when I add it to the config file it is ignored

here is my config-

Code: Select all

$logfilemissing = ok;

use POSIX qw(strftime);
my $run_date= strftime "%Y%m%d", localtime;
chomp($run_date);

@searches = (
  {
    logfile => "/usr/log_${run_date}.log",
    criticalpatterns => [
        'problem',
        'ERROR'
    ],
  });
Am I doing something wrong?

Re: check_logfile config logfilemissing

Posted: Wed May 09, 2018 4:35 pm
by npolovenko
Hello, @optionstechnology.

I don't have a lot of experience with this plugin but I'd try to write the filter this way:

Code: Select all

@searches = (
  {
    logfile => "/usr/log_${run_date}.log",
    options => 'logfilemissing = ok', 
    criticalpatterns => [
        'problem',
        'ERROR'
    ],
  });

But also, I'm not sure if "logfilemissing = ok" is even supported:

Code: Select all

* 3.7.2 2015-09-22
  add option logfilemissing=[warning|critical]
I haven't found any mentionings of OK in the documentation or in the changelog.

Re: check_logfile config logfilemissing

Posted: Tue May 22, 2018 2:49 am
by optionstechnology
I tried that but it hasnt fixed the problem

The command definitely accepts an 'ok' state because when I run it all from command line it works fine - it only fails when used via a config file

Re: check_logfile config logfilemissing

Posted: Tue May 22, 2018 11:29 am
by mcapra
I assume you are referring to the check_logfiles plugin located here?
https://labs.consol.de/nagios/check_logfiles/index.html

It would be useful to know which specific version of check_logfiles you're using.

You could try this format from the examples:

Code: Select all

use POSIX qw(strftime);
my $run_date= strftime "%Y%m%d", localtime;
chomp($run_date);

$options = 'logfilemissing=ok';

@searches = (
  {
    logfile => "/usr/log_${run_date}.log",
    criticalpatterns => [
        'problem',
        'ERROR'
    ],
  });

Re: check_logfile config logfilemissing

Posted: Tue May 22, 2018 1:11 pm
by tgriep
Thanks @mcapra for the help.
@optionstechnology, if it works when using the command line option, can you edit the command in Nagios and add that option if changing the config file doesn't work for you?

Re: check_logfile config logfilemissing

Posted: Tue May 29, 2018 10:41 am
by optionstechnology
Thanks @mcapra, I tried it but unfortunately it did not work either

I'm not sure what version it is as there is nothing in the text of the check itself - its the one that comes as default with nrpe

Unfortunately its also not a case of adding the command line - you can either supply arguments from config file, or from the command line, but for some reason not from both

As I have to use a config to get the filename correct I cannot use the command line option

Re: check_logfile config logfilemissing

Posted: Tue May 29, 2018 1:50 pm
by tgriep
Can you post the plugin here so we can view it?

Re: check_logfile config logfilemissing

Posted: Wed May 30, 2018 4:19 am
by optionstechnology
(This is the top half of it)

Code: Select all

#! /usr/bin/perl 
# nagios: -epn

#
# Logfile::Config::Tivoli.pm - Tivoli Config Module
#
# Purpose: Provide a convenient way for loading
#          tivoli config files and
#          return it as hash structure
#
package Nagios::Tivoli::Config::Logfile;

use strict;

sub new {
  my($this, $param ) = @_;
  my $class = ref($this) || $this;

  my $self = {
      formatfile   => '',  # format file with tivoli format definitions,
                           # can be an array of files
      formatstring => '',  # format file content as string
      severity_mappings => {},
      max_continuation_lines => 0, # in case there are %n in among the patterns
      line_buffer => [],   # for continuation lines
      line_buffer_size => 0,
  };
  bless $self, $class;

  $self->set_severity_mapping('fatal', 2);
  $self->set_severity_mapping('critical', 2);
  $self->set_severity_mapping('severe', 2);
  $self->set_severity_mapping('warning', 1);
  $self->set_severity_mapping('minor', 1);
  $self->set_severity_mapping('harmless', 0);
  $self->set_severity_mapping('unknown', 0);

  # parse parameter
  if (ref($param) eq "HASH") {
    for my $key (keys %{$param}) {
      if (!defined $self->{lc $key}) {
        printf STDERR "unrecognized parameter: %s\n", $key;
        return undef;
      } else {
        if (ref($param->{$key}) eq 'HASH') {
          $self->merge_hash($self->{$key}, $param->{$key});
        } else {
          $self->{lc $key} = $param->{$key};
        }
      }
    }
  } elsif (ref($param) eq "") {
    $self->{formatfile} = $param;
  } else {
    printf STDERR "formatfile is a required parameter\n";
  }
  if ((!defined $self->{formatfile} || $self->{formatfile} eq '') &&
      (!defined $self->{formatstring} || $self->{formatstring} eq '')) {
        printf STDERR "please either specify formatfile or formatstring\n";
    return undef;
  }
  if (defined $self->{formatstring} and $self->{formatstring} ne '') {
    $self->{_formatstring} = $self->{formatstring};
  } else {
    $self->{_formatstring} = $self->_read($self->{formatfile});
  }
  if (! $self->{_formatstring}) {
    return undef;
  }
  foreach (keys %{$self->{tivolimapping}}) {
    $self->set_severity_mapping($_, $self->{tivolimapping}->{$_});
  }
  if ($self->_parse) {
    #Data::Dumper::Dumper($self->{formats});
    return $self;
  } else {
    printf STDERR ("parsing failed, see previous messages...");
    return undef;
  }
}

sub _read {
  my $self     = shift;
  my $filename = shift;
  my $content;
  if (ref($filename) eq 'ARRAY') {
    for my $file (@{$filename}) {
      $content .= $self->_read($file);
    }
  } else {
    if (open FMT, $filename) {
      while(<FMT>) {
        $content .= $_;
      }
      close FMT;
    } else {
      printf STDERR "unable to read file %s: %s\n", $filename, $!;
      return undef;
    }
  }
  return($content);
}

sub _parse {
  my $self = shift;
  my $format;
  my $lineno = 0;
  for my $line (split /\n/, $self->{_formatstring}) {
    $lineno++;
    chomp $line;
    $line = $1 if $line =~ /^\s*(.*?)\s*$/;

    next if $line =~ m/^\/\//;
    next if $line eq "";

    if ($line =~ m/^FORMAT/) {
      my($name, $follows, $followname) = 
          $line =~ m/^FORMAT\s+(.*?)\s*(|FOLLOWS\s+(.*?))$/;
      $format= Nagios::Tivoli::Config::Logfile::Format->new({
          name => $name,
          lineno => $lineno,
          severity_mappings => $self->{severity_mappings},
      });
      if (defined $followname) {
        my @follows = split /\s*,\s*/, $followname;
        for my $follow (@follows) {
          if (my $follow_format = $self->get_format_by_name($follow)) {
            $format->inherit($follow_format);
          }
        }
        $format->{follows} = \@follows;
      }
    } elsif ($line =~ m/^END/) {
      if (!defined $format) {
        printf STDERR "found format end without beginning\n";
        return 0;
      }
      if (!defined $format->{pattern}) {
        if (!exists $format->{follows}) {
          printf STDERR "found format without pattern\n";
          return 0;
        }
      }
      $self->add_format($format);
    } elsif (defined $format) {
      if (!defined $format->{pattern}) {
        # %s Specifies a variable string.
        # %t Specifies a variable date of the form 'MMM DD hh:mm:ss'
        # %s+ Specifies one or more variable strings that
        #     are separated by spaces.
        # %s* Specifies zero or more strings separated by white space.
        # %n Specifies a new line (CR).
        #    This applies only to the following adapters:
        #    tecad_logfile_aix4-r1, tecad_logfile_hpux10,
        #    tecad_logfile_linux_ix86, tecad_logfile_linux-ppc,
        #    tecad_logfile_linux-s390, tecad_logfile_solaris2,
        #    and tecad_win.
        $format->{tiv_pattern} = $line;
        $format->{patternlines} = 0;
        if ($line =~ /%n/) {
          $format->{patternlines}++ while $line =~ /%n/g;
          $format->{pattern} = [map { $self->translate_pattern($_) } split /%n/, $line];
          $self->{max_continuation_lines} = $format->{patternlines} unless
              $format->{patternlines} <= $self->{max_continuation_lines};
        } else {
          $format->{pattern} = $self->translate_pattern($line);
        }
      } elsif ($line =~ m/^-(.*?)\s+(.*)$/i) {
        $format->add_variable($1, $2);
      } elsif ($line =~ m/^(.*?)\s+"*(.*?)"*\s*$/) {
        $format->add_slot($1, $2);
      }
    } else {
      printf STDERR "%s is outside of a format definition\n", $line;
      return 0;
    }
  }
  return 1;
}

sub translate_pattern {
  my $self = shift;
  my $tiv_pattern = shift;
  $tiv_pattern =~ s/\\/\\\\/g;          # quote \
  $tiv_pattern =~ s/\(/\\(/g;           # quote (
  $tiv_pattern =~ s/\)/\\)/g;           # quote )
  $tiv_pattern =~ s/%\[\d+\]s/%s/g;     # replace %[2]s with just %s
  $tiv_pattern =~ s/\[/\\[/g;           # quote [
  $tiv_pattern =~ s/\]/\\]/g;           # quote ]
  $tiv_pattern =~ s/\?/\\?/g;           # quote ?
  $tiv_pattern =~ s/\|/\\|/g;           # quote |
  $tiv_pattern =~ s/\-/\\-/g;           # quote -
  #$tiv_pattern =~ s/%s\+/\(.+?\)/g;     # %s+  becomes .+?
  #$tiv_pattern =~ s/%s\*/\(.*?\)/g;     # %s*  becomes .*?
  #$tiv_pattern =~ s/%s/\(\[^\\s\]+?\)/g;  # %s   becomes [^\s]+?
  $tiv_pattern =~ s/%s\+/\([^\\s]*?.+[^\\s]*?\)/g; # %s+ becomes [^\s]*?.+[^\s]*?
  $tiv_pattern =~ s/%s\*\s*$/\(.*\)/g;     # last %s*  becomes .* eats the rest
  $tiv_pattern =~ s/%s\*/\(.*?\)/g;     # %s*  becomes .*? eats as much as necessary
  $tiv_pattern =~ s/%s/\(\[^\\s\]+\)/g;  # %s   becomes [^\s]+?
  #$tiv_pattern =~ s/%n/\\n/g;           # %n   becomes \n
  $tiv_pattern =~ s/[ ]+/\\s\+/g;           # blanks become \s+
  $tiv_pattern =~ s/%n//g;           # %n   becomes \n
  $tiv_pattern =~ s/%t/\(\\w\{3\}\\s+\\d\{1,2\}\\s+\\d\{1,2\}\:\\d\{1,2\}\:\\d\{1,2\}\)/g;
  return $tiv_pattern;
}

sub match {
  my $self = shift;
  my $line = shift;
  if ($self->{line_buffer_size} < $self->{max_continuation_lines} + 1) {
    push(@{$self->{line_buffer}}, $line);
    $self->{line_buffer_size}++;
  } else {
    shift @{$self->{line_buffer}};
    push(@{$self->{line_buffer}}, $line);
  }
#printf STDERR "try: %s\n", $line;
  foreach my $format (reverse @{$self->{'formats'}}) {
    next if ! $format->{can_match};
    #if (($format->{name} ne '*DISCARD*') &&
    #    (! $format->has_slots() || ! $format->get_slot('severity'))) {
    #  next; # ungueltiges format
    #}
    my @matches = ();
#printf STDERR "format %s\n", $format->{name};
#printf STDERR "match /%s/\n", $format->{pattern};
    if (my @matches = $self->match_pattern($line, $format)) {
      my $hit = Nagios::Tivoli::Config::Logfile::Hit->new({
          format => $format,
          logline => $line,
          matches => \@matches,
          format_mappings => $self->{format_mappings},
          severity_mappings => $self->{severity_mappings},
      });
#printf STDERR "hit: %s\n", $line;
      if ($format->{name} eq '*DISCARD*') {
#printf STDERR "discard: %s %s\n", $line, Data::Dumper::Dumper($hit);
        last;
      } else {
#printf STDERR "hit2: %s // %s\n", $hit->{subject}, $format->{name};
        return({
          exit_code   => $hit->get_nagios_severity(),
          severity    => $hit->{severity},
          format_name => $hit->{format_name},
          subject     => $hit->{subject},
          logline     => $line,
          slots       => $hit->{slots},
        });
      }
    }
  }
#printf STDERR "mis: %s\n", $line;
  return({
    exit_code   => $self->get_severity_mapping('HARMLESS'),
    severity    => 'HARMLESS',
    format_name => 'NO MATCHING RULE',
    subject     => 'NO MATCHING RULE',
    logline     => $line,
    slots       => { },
  });
}

sub match_pattern {
  my $self = shift;
  my $line = shift;
  my $format = shift;
  my $pattern = $format->{pattern};
  if (ref($pattern) eq 'ARRAY') {
    my @all_matches = ();
    # 
    my $patterns = scalar(@{$pattern});
    if ($patterns > $self->{line_buffer_size}) {
      # zu wenig zeilen vorhanden
      return ();
    } else {
      my $startidx = $self->{line_buffer_size} - $patterns;
      my $idx = 0;
      while ($idx < $patterns) {
        # pattern[$idx] matched ${$self->{line_buffer}}[$startidx + $idx] ?
        if (my @matches = 
            ${$self->{line_buffer}}[$startidx + $idx] =~ /$pattern->[$idx]/) {
          $idx++;
          push(@all_matches, @matches);
        } else {
          last;
        }
      }
      if ($idx == $patterns) {
        return @all_matches;
      } else {
        return ();
      }
    }
  } else {
    #my @matches = $line =~ /$pattern/;
    my @matches = $format->{matchfunc}($line);
    return @matches;
  }
}

# inherit
#
# copy variable and slot definitions of a followed format to the current format
#
sub inherit {
  my $self = shift;
  my $ancestor = shift;
  $self->merge_hash($self->{variables}, $ancestor->{variables});
  $self->merge_hash($self->{slots}, $ancestor->{slots});
}

# get_severity_mapping
#
# get the numerical nagios level for a tivoli level
#
sub get_severity_mapping {
  my $self = shift;
  my $tivoli_severity = lc shift;
  return $self->{severity_mappings}->{$tivoli_severity};
}

# set_severity_mapping
#
# set the numerical nagios level for a tivoli level
#
sub set_severity_mapping {
  my $self = shift;
  my $tivoli_severity = lc shift;
  my $nagios_severity = shift;
  $self->{severity_mappings}->{$tivoli_severity} = $nagios_severity;
}

# set_format_mappings
#
# set runtime values for LABEL, DEFAULT,...
#
sub set_format_mappings {
    my $self = shift;
    my %mappings = @_;
    foreach (keys %mappings) {
      $self->{format_mappings}->{$_} = $mappings{$_};
    }
}

sub add_format {
  my $self = shift;
  my $format = shift;
  if (($format->{name} ne '*DISCARD*') &&
      (! $format->has_slots() || ! $format->get_slot('severity'))) {
      #printf STDERR "FORMAT %s skipped\n", $format->{name};
    $format->{can_match} = 0;
  } else {
    $format->{can_match} = 1;
  }
  push(@{$self->{formats}}, $format);
}

sub get_format_by_name {
  my $self = shift;
  my $name = shift;
  foreach (@{$self->{formats}}) {
    return $_ if $_->{name} eq $name;
  }
  return undef;
}

sub merge_hash {
    my $self  = shift;
    my $hash1 = shift;
    my $hash2 = shift;

    for my $key (keys %{$hash2}) {
        $hash1->{$key} = $hash2->{$key};
    }
    return($hash1);
}


package Nagios::Tivoli::Config::Logfile::Format;

use strict;
use warnings;
use Carp;
use vars qw(@ISA);

@ISA = qw(Nagios::Tivoli::Config::Logfile);

sub new {
  my($this, $param ) = @_;
  my $class = ref($this) || $this;

  my $self = {
      name => '',
      lineno => 0,
      slots => {},
      variables => {},
      severity_mappings => {},
  };
  bless $self, $class;

  if (ref($param) eq "HASH") {
    for my $key (keys %{$param}) {
      if (!defined $self->{lc $key}) {
        carp("unrecognized parameter: $key");
      } else {
        if (ref($param->{$key}) eq 'HASH') {
          $self->merge_hash($self->{$key}, $param->{$key});
        } else {
          $self->{lc $key} = $param->{$key};
        }
      }
    }
  }
  if (!defined $self->{name}) {
    die "please either specify formatfile or formatstring";
  }
  $self->add_match_closure();
  return $self;
}

sub add_slot {
  my $self = shift;
  my $slot = shift;
  my $value = shift;
  $self->{slots}->{$slot} = $value;
}

sub get_slot {
  my $self = shift;
  my $slot = shift;
  return $self->{slots}->{$slot};
}

sub has_slots {
  my $self = shift;
  return scalar (keys %{$self->{slots}});
}

sub add_variable {
  my $self = shift;
  my $variable = shift;
  my $value = shift;
  $self->{variables}->{$variable} = $value;
}

sub get_variable {
  my $self = shift;
  my $variable = shift;
  return $self->{variables}->{$variable};
}

sub has_variables {
  my $self = shift;
  return scalar (keys %{$self->{variables}});
}

sub add_match_closure {
  my $self = shift;
  # creates a function which keeps the compiled version of self->pattern
  $self->{matchfunc} = eval "sub { local \$_ = shift; return m/\$self->{pattern}/o; }";
}


package Nagios::Tivoli::Config::Logfile::Hit;

use strict;
use warnings;
use Carp;
use vars qw(@ISA);

@ISA = qw(Nagios::Tivoli::Config::Logfile::Format);

sub new {
  my($this, $param ) = @_;
  my $class = ref($this) || $this;

  my $self = {
      format => $param->{format},
      logline => $param->{logline},
      format_mappings => $param->{format_mappings},
      severity_mappings => $param->{severity_mappings},
      matches => {},
      variables => {},
      slots => {},
  };
  bless $self, $class;
  my $matchcnt = 1;
  map { $self->{matches}->{$matchcnt++} = $_; } @{$param->{matches}};
  $self->init();
  return $self;
}

sub init {
  my $self = shift;
  $self->{severity} = $self->{format}->{slots}->{severity};
  $self->{format_name} = $self->{format}->{name};
  $self->merge_hash($self->{variables}, $self->{format}->{variables});
  $self->merge_hash($self->{slots}, $self->{format}->{slots});
  # resolve pattern groups in internal variables
  foreach my $var (keys %{$self->{variables}}) {
    if ($self->{variables}->{$var} =~ /^\$(\d+)/) {
      if (defined $self->{matches}->{$1}) {
        $self->{variables}->{$var} = $self->{matches}->{$1};
      } else {
        printf STDERR "cannot replace \$%d in var %s\n", $1, $var;
      }
    }
  }
  # resolve pattern groups and format reserved words in slots
  foreach my $slot (keys %{$self->{slots}}) {
    if ($self->{slots}->{$slot} =~ /^\$(\d+)/) {
      if (defined $self->{matches}->{$1}) {
        $self->{slots}->{$slot} = $self->{matches}->{$1};
      } else {
        printf STDERR "cannot replace \$%d in slot %s\n", $1, $slot;
      }
    } elsif ($self->{slots}->{$slot} eq 'DEFAULT') {
      if ($slot eq 'hostname') {
        $self->{slots}->{$slot} = $self->{format_mappings}->{hostname};
      } elsif ($slot eq 'fqhostname') {
        $self->{slots}->{$slot} = $self->{format_mappings}->{fqhostname};
      } elsif ($slot eq 'origin') {
        $self->{slots}->{$slot} = $self->{format_mappings}->{origin};
      } else {
        $self->{slots}->{$slot} = 'check_logfiles';
      }
    } elsif ($self->{slots}->{$slot} eq 'LABEL') {
      $self->{slots}->{$slot} = $self->{format_mappings}->{LABEL};
    } elsif ($self->{slots}->{$slot} eq 'FILENAME') {
      $self->{slots}->{$slot} = $self->{format_mappings}->{FILENAME};
    } else {
    }
  }
  foreach my $slot (keys %{$self->{slots}}) {
    if ($self->{slots}->{$slot} =~ /PRINTF/i) {
      $self->{slots}->{$slot} = $self->printf($self->{slots}->{$slot});
    }
  }
  $self->{subject} = $self->{slots}->{msg} || $self->{logline};
  #delete $self->{slots}->{msg};
}

sub printf {
  my $self = shift;
  my $text = shift;
  my @printf = $text =~ m/printf\("(.*?)"\s*,\s*(.*)\)/i;
  my $result = $text;
  my @replacements;
  for my $key (split /\s*,\s*/, $printf[1]) {
    if (defined $self->{variables}->{$key}) {
      push @replacements, $self->{variables}->{$key};
    } elsif (defined $self->{slots}->{$key}) {
      push @replacements, $self->{slots}->{$key};
    } else {
      print STDERR "$key not found\n";
      push @replacements,  '';
    }
  }
  eval {
      $result = sprintf($printf[0], @replacements);
  };
  return($result);
}

sub get_nagios_severity {
  my $self = shift;
  return $self->get_severity_mapping($self->{slots}->{severity});
}


package Nagios::CheckLogfiles;

use strict;
use IO::File;
use File::Basename;
use File::Spec;
use File::Find;
use File::Path;
use Cwd;
use Data::Dumper;
#use Net::Domain qw(hostname hostdomain hostfqdn);
use Socket;
use POSIX qw(strftime);
use IPC::Open2;
use Errno;


use constant GZIP => '/usr/bin/gzip';
my $ERROR_OK = 0;
my $ERROR_WARNING = 1;
my $ERROR_CRITICAL = 2;
my $ERROR_UNKNOWN = 3;

our $ExitCode = $ERROR_OK;
our $ExitMsg = "OK";
my(%ERRORS, $TIMEOUT);
%ERRORS = ( OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 );
$TIMEOUT = 60;

$| = 1;

eval "require Win32;";
#eval "require Net::Domain qw(hostname hostdomain hostfqdn);";
eval "require Net::Domain;";
{
  local $^W = 0; # shut up!
  eval "require 'syscall.ph'";
  eval "require 'sys/resource.ph'";
}

sub new {
  my $class = shift;
  my $params = shift;
  my $self = bless {} , $class;
  return $self->init($params);
}

#
#  Read a hash with parameters
#
sub init {
  my $self = shift;
  my $params = shift;
  my($sec, $min, $hour, $mday, $mon, $year) = (localtime)[0, 1, 2, 3, 4, 5];
  $year += 1900; $mon += 1;
  $self->{tracefile} = $self->system_tempdir().'/check_logfiles.trace';
  $self->{trace} = -e $self->{tracefile} ? 1 : 0;
  $self->{verbose} = $params->{verbose} || 0;
  $self->{htmlencode} = $params->{htmlencode} || 0;
  $self->{seekfilesdir} = $params->{seekfilesdir} || '/var/tmp/check_logfiles';
  $self->{protocolsdir} = $params->{protocolsdir} || '/tmp';
  $self->{scriptpath} = $params->{scriptpath} || '/bin:/sbin:/usr/bin:/usr/sbin';
  $self->{protocolretention} = ($params->{protocolretention} || 7) * 24 * 3600;
  $self->{macros} = $params->{macros};
  $self->{timeout} = $params->{timeout} || 360000;
  $self->{pidfile} = $params->{pidfile};
  $self->{perfdata} = "";
  $self->{searches} = [];
  $self->{selectedsearches} = $params->{selectedsearches} || [];
  $self->{dynamictag} = $params->{dynamictag} || "";
  $self->{cmdlinemacros} = $params->{cmdlinemacros} || {};
  $self->{reset} = $params->{reset} || 0;
  $self->{unstick} = $params->{unstick} || 0;
  $self->{warning} = $params->{warning} || 0;
  $self->{critical} = $params->{critical} || 0;
  $self->{matchlines} = { OK => [], WARNING => [], CRITICAL => [], UNKNOWN => [] };
  $self->init_macros;
  $self->default_options({ prescript => 1, smartprescript => 0,
      supersmartprescript => 0, postscript => 1, smartpostscript => 0,
      supersmartpostscript => 0, report => 'short', maxlength => 4096,
      seekfileerror => 'critical', logfileerror => 'critical', 
      protocolfileerror => 'ok',
      maxmemsize => 0, rotatewait => 0, htmlencode => 0,
      outputhitcount => 1, rununique => 0, preview => 1,
  });
  if ($params->{cfgfile}) {
    if (ref($params->{cfgfile}) eq "ARRAY") {
      # multiple cfgfiles found in a config dir
      my @tmp_searches = ();
      $self->{cfgbase} = $params->{cfgbase} || "check_logfiles";
      $self->late_init_macros;
      foreach my $cfgfile (@{$params->{cfgfile}}) {
        $self->{cfgfile} = $cfgfile;
        if (! $self->init_from_file()) {
          return undef;
        }
        push(@tmp_searches, @{$self->{searches}});
        $self->{searches} = [];
      }
      my %seen = ();
      # newer searches replace searches with the same tag
      @tmp_searches = reverse map { 
        if (! exists $seen{$_->{tag}}) {
          $seen{$_->{tag}}++;
          $_;
        } else {
          ();
        }
      } reverse @tmp_searches;
      $self->{searches} = \@tmp_searches;
      my $uniqueseekfile = undef;
      my $uniqueprotocolfile = undef;
      foreach (@{$self->{searches}}) {
        $_->{cfgbase} = "check_logfiles";
        next if $_->{tag} eq "prescript";
        next if $_->{tag} eq "postscript";
        $_->construct_seekfile();
      }
      #$self->{cfgbase} = (split /\./, basename($params->{cfgfile}->[0]))[0];
      $self->{cfgbase} = "check_logfiles";
    } elsif ($params->{cfgfile} =~ /%0A/) {
      # this must be an encoded flat file
      $self->{cfgfile} = $params->{cfgfile};
      $self->{cfgbase} = "flatfile";
      $self->late_init_macros;
      if (! $self->init_from_file()) {
        return undef;
      }
    } else {
      $self->{cfgfile} = $params->{cfgfile};
      $self->{cfgbase} = (split /\./, basename($self->{cfgfile}))[0];
      $self->late_init_macros;
      if (! $self->init_from_file()) {
        return undef;
      }
    } 
    # if there is a dynamictag parameter then replace template names with
    # template_dynamictagtag
    if (scalar(@{$self->{selectedsearches}})) {
      @{$self->{searches}} = map {
        my $srch = $_;
        if (grep { $srch->{tag} eq $_ } @{$self->{selectedsearches}}) {
          # gilt sowohl fuer normale searches
          $srch;
        } elsif ($srch->{template} && grep { $srch->{template} eq $_ } @{$self->{selectedsearches}}) {
          # als auch fuer template (tag ist hier bereits template."_".tag,
          # wobei tag auf der kommandozeile uebergeben wurde)
          $srch;
        } elsif (grep { $_ =~ /[*?]/ && $srch->{tag} =~ /$_/ } @{$self->{selectedsearches}}) {
          # --selectedsearches "regexp,regexp"
          $srch;
        } elsif ($srch->{tag} eq "prescript") {
          $srch;
        } elsif ($srch->{tag} eq "postscript") {
          $srch;
        } else {
          $self->trace("skipping non-selected search %s", $srch->{tag});
          ();
        }
      } @{$self->{searches}};
    }
  } else {
    $self->{cfgbase} = $params->{cfgbase} || "check_logfiles";
    $self->late_init_macros;
    # first the global options (from the commandline in this case)
    $self->refresh_options($params->{options});
    $self->{seekfilesdir} = $self->relocate_dir("seekfilesdir", $self->{seekfilesdir});
    $self->resolve_macros(\$self->{seekfilesdir});
    foreach (@{$params->{searches}}) {
      $_->{seekfilesdir} = $self->{seekfilesdir};
      $_->{relocate_seekfilesdir} = $self->{relocate_seekfilesdir};
      $_->{scriptpath} = $self->{scriptpath};
      %{$_->{macros}} = %{$self->{macros}};
      $_->{tracefile} = $self->{tracefile};
      $_->{cfgbase} = $self->{cfgbase};
      if (my $search = Nagios::CheckLogfiles::Search->new($_)) {
        # maybe override default search options with global ones (ex. report)
        $search->refresh_default_options($self->get_options('report,seekfileerror,logfileerror,protocolfileerror'));
        push(@{$self->{searches}}, $search);
      } else {
        $ExitCode = $ERROR_UNKNOWN;
        $ExitMsg = sprintf "cannot create %s search %s",
            $_->{type}, $_->{tag};
        return undef;
      }
    }  
  }
  if (defined(&Win32::GetShortPathName) && ($^O =~ /Win/)) {
    # if this is true windows (not cygwin) and if the path exists
    # then transform it to a short form. undef if path does not exist.
    if (my $tmpshortpath = &Win32::GetShortPathName($self->{protocolsdir})) {
      $self->{protocolsdir} = $tmpshortpath;
    }
  }
  if ($self->get_option('report') !~ /^(long|short|html)$/) {
    $ExitCode = $ERROR_UNKNOWN;
    $ExitMsg = sprintf "UNKNOWN - output must be short, long or html";
    return undef;
  }
  $self->{protocolfile} = 
      sprintf "%s/%s.protocol-%04d-%02d-%02d-%02d-%02d-%02d",
      $self->{protocolsdir}, $self->{cfgbase}, 
      $year, $mon, $mday, $hour, $min, $sec;
  $self->{protocololdfiles} = sprintf "%s/%s.protocol-*-*-*-*-*-*",
      $self->{protocolsdir}, $self->{cfgbase};
  $self->{protocolfh} = new IO::File;
  $self->{protocolwritten} = 0;
  $self->{allerrors} = { OK => 0, WARNING => 0, CRITICAL => 0, UNKNOWN => 0 };
  # if parameters update
  if (@{$self->{searches}}) {
    $self->{exitcode} = $ExitCode;
    $self->{exitmessage} = $ExitMsg;
    return $self;
  } else {
    $ExitCode = $ERROR_UNKNOWN;
    $ExitMsg = sprintf "UNKNOWN - configuration incomplete";
    return undef;
  }
}

sub init_from_file {
  my $self = shift;
  my $abscfgfile;
  #
  #  variables from the config file.
  #
  our($seekfilesdir, $protocolsdir, $scriptpath, $protocolretention,
      $prescript, $prescriptparams ,$prescriptstdin, $prescriptdelay,
      $postscript, $postscriptparams, $postscriptstdin, $postscriptdelay,
      @searches, @logs, $tracefile, $options, $report, $timeout, $pidfile,
      $CHECK_LOGFILES_PRIVATESTATE);
  our $MACROS = {};
  if ($^O =~ /MSWin/) {
    $ENV{HOME} = $ENV{USERPROFILE};
  }
  if ($self->{cfgbase} eq "flatfile") {
    $self->{cfgfile} =~ s/\%([A-Fa-f0-9]{2})/pack('C', hex($1))/seg;
    eval $self->{cfgfile};
    if ($@) {
      $ExitCode = $ERROR_UNKNOWN;
      $ExitMsg = sprintf "UNKNOWN - syntax error %s", (split(/\n/, $@))[0];
      return undef;
    }
    $abscfgfile = "/dummy/dummy/".(unpack("H*", $self->{cfgfile}));
  } else {
    if (-f $self->{cfgfile}) {
      $abscfgfile = $self->{cfgfile};
    } elsif (-f $self->{cfgfile}.'.cfg') {
      $abscfgfile = $self->{cfgfile}.'.cfg';
    } elsif (-f $ENV{HOME}.'/'.$self->{cfgfile}) {
      $abscfgfile = $ENV{HOME}.'/'.$self->{cfgfile};
    } elsif (-f $ENV{HOME}.'/'.$self->{cfgfile}.'.cfg') {
      $abscfgfile = $ENV{HOME}.'/'.$self->{cfgfile}.'.cfg';
    } else {
      $ExitCode = $ERROR_UNKNOWN;
      $ExitMsg = sprintf "UNKNOWN - can not load configuration file %s", 
          $self->{cfgfile};
      return undef;
    }
    $abscfgfile = File::Spec->rel2abs($abscfgfile) 
        unless File::Spec->file_name_is_absolute($abscfgfile);
    delete $INC{$abscfgfile}; # this is mostly because of the tests which cache the cfgfile
    eval {
      require $abscfgfile;
    };
    if ($@) {
      $ExitCode = $ERROR_UNKNOWN;
      $ExitMsg = sprintf "UNKNOWN - syntax error %s", (split(/\n/, $@))[0];
      return undef;
    }
    # We might need this for a pidfile
  }

  $self->merge_macros($MACROS); # merge the defaultmacros with macros from the file
  $seekfilesdir ||= $self->{seekfilesdir};
  $protocolsdir ||= $self->{protocolsdir};
  $scriptpath ||= $self->{scriptpath};
  # We might need this for a pidfile
  $self->{abscfgfile} = $abscfgfile;
  $seekfilesdir = $self->relocate_dir("seekfilesdir", $seekfilesdir, dirname(dirname($abscfgfile)));
  return undef if ! $seekfilesdir;
  $protocolsdir = $self->relocate_dir("protocolsdir", $protocolsdir, dirname(dirname($abscfgfile)));
  $scriptpath = $self->relocate_dir("scriptpath", $scriptpath, dirname(dirname($abscfgfile)));
  $self->resolve_macros(\$seekfilesdir);
  $self->resolve_macros(\$protocolsdir);
  $self->resolve_macros(\$scriptpath);

  $self->{tracefile} = $tracefile if $tracefile;
  $self->{trace} = -e $self->{tracefile} ? 1 : 0;
  # already done one level above $self->{cfgbase} = (split /\./, basename($self->{cfgfile}))[0];
  $self->{seekfilesdir} = $seekfilesdir if $seekfilesdir;
  $self->{protocolsdir} = $protocolsdir if $protocolsdir;
  $self->{scriptpath} = $scriptpath if $scriptpath;
  $self->{protocolretention} = ($protocolretention * 24 * 3600) if $protocolretention;
  $self->{prescript} = $prescript if $prescript;
  $self->{prescriptparams} = $prescriptparams if $prescriptparams;
  $self->{prescriptstdin} = $prescriptstdin if $prescriptstdin;
  $self->{prescriptdelay} = $prescriptdelay if $prescriptdelay;
  $self->{postscript} = $postscript if $postscript;
  $self->{postscriptparams} = $postscriptparams if $postscriptparams;
  $self->{postscriptstdin} = $postscriptstdin if $postscriptstdin;
  $self->{postscriptdelay} = $postscriptdelay if $postscriptdelay;
  $self->{timeout} = $timeout || 360000;
  $self->{pidfile} = $pidfile if $pidfile;
  $self->{privatestate} = {};
  $self->refresh_options($options);
  if (@logs) {
    #
    # Since version 1.4 the what/where-array is called @searches.
    # To stay compatible, @logs is still recognized.
    #
    @searches = @logs;
  }
  if ($self->{options}->{prescript}) {
    $_->{scriptpath} = $self->{scriptpath};
    %{$_->{macros}} = %{$self->{macros}};
    $_->{tracefile} = $self->{tracefile};
    $_->{cfgbase} = $self->{cfgbase};
    $_->{script} = $self->{prescript};
    $_->{scriptparams} = $self->{prescriptparams};
    $_->{scriptstdin} = $self->{prescriptstdin};
    $_->{scriptdelay} = $self->{prescriptdelay};   
    $_->{options} = sprintf "%s%sscript",
        $self->{options}->{supersmartprescript} ? "super" : "",
        $self->{options}->{smartprescript} ? "smart" : "";
    $_->{privatestate} = $self->{privatestate};
    my $search = Nagios::CheckLogfiles::Search::Prescript->new($_);
    push(@{$self->{searches}}, $search); 
  }
  foreach (@searches) {
    $_->{seekfilesdir} = $self->{seekfilesdir};
    $_->{relocate_seekfilesdir} = $self->{relocate_seekfilesdir};
    $_->{scriptpath} = $self->{scriptpath};
    %{$_->{macros}} = %{$self->{macros}};
    $_->{tracefile} = $self->{tracefile};
    $_->{cfgbase} = $self->{cfgbase};
    if ((exists $_->{template}) && ! $self->{dynamictag}) {
      # skip templates if they cannot be tagged
      next;
    }
    $_->{dynamictag} = $self->{dynamictag};
    if (my $search = Nagios::CheckLogfiles::Search->new($_)) {
      $search->refresh_options($self->get_options('report,seekfileerror,logfileerror,protocolfileerror'));
      push(@{$self->{searches}}, $search);
      $_->{privatestate}->{$search->{tag}} = $search->{privatestate};
    } else {
      $ExitCode = $ERROR_UNKNOWN;
      $ExitMsg = sprintf "cannot create %s search %s",
          $_->{type}, $_->{tag};
      return undef;
    }
  }
  if ($self->{options}->{postscript}) {
    $_->{scriptpath} = $self->{scriptpath};
    %{$_->{macros}} = %{$self->{macros}};
    $_->{tracefile} = $self->{tracefile};
    $_->{cfgbase} = $self->{cfgbase};
    $_->{script} = $self->{postscript};
    $_->{scriptparams} = $self->{postscriptparams};
    $_->{scriptstdin} = $self->{postscriptstdin};
    $_->{scriptdelay} = $self->{postscriptdelay};   
    $_->{options} = sprintf "%s%sscript",
        $self->{options}->{supersmartpostscript} ? "super" : "",
        $self->{options}->{smartpostscript} ? "smart" : "";
    $_->{privatestate} = $self->{privatestate};
    my $search = Nagios::CheckLogfiles::Search::Postscript->new($_);
    push(@{$self->{searches}}, $search); 
  }
  return $self;
}

sub run {
  my $self = shift;
  if ($self->{reset}) {
    foreach my $search (@{$self->{searches}}) {
      if ($search->{tag} ne "prescript" && $search->{tag} ne "postscript") {
        $search->rewind();
      }
    }
    return $self;
  }
  if ($self->{unstick}) {
    foreach my $search (@{$self->{searches}}) {
      if ($search->{tag} ne "prescript" && $search->{tag} ne "postscript") {
        $search->unstick();
      }
    }
    return $self;
  }
  if ($self->{options}->{rununique}) {
    $self->{pidfile} = $self->{pidfile} || $self->construct_pidfile();
    if (! $self->check_pidfile()) {
      $self->trace("Exiting because another check is already running");
      printf "Exiting because another check is already running\n";
      exit 3;
    }
  }
  if ($self->get_option('rotatewait')) {
    $self->await_while_rotate();
  }
  my $protocol_had_error = 0;
  if (! -w $self->{protocolsdir}) {
    $protocol_had_error = 1;
  }
  foreach my $search (@{$self->{searches}}) {
    if (1) { # there will be a timesrunningout variable
      if ($search->{tag} eq "postscript") {
        $search->{macros}->{CL_SERVICESTATEID} = $self->{exitcode};
        $search->{macros}->{CL_SERVICEOUTPUT} = $self->{exitmessage};
        $search->{macros}->{CL_LONGSERVICEOUTPUT} = 
            $self->{long_exitmessage} || $self->{exitmessage};
        $search->{macros}->{CL_SERVICEPERFDATA} = $self->{perfdata};
        $search->{macros}->{CL_PROTOCOLFILE} = $self->{protocolfile};
        if ($search->{options}->{supersmartscript}) {
          # 
          #  Throw away everything found so far. Supersmart postscripts
          #  have the last word.
          #
          $self->reset_result();        
        }       
      }      
      $search->{verbose} = $self->{verbose};
      $search->{timeout} = $self->{timeout};
      $search->run();
      if (($search->{tag} eq "prescript") && 
          ($search->{options}->{supersmartscript}) &&
          ($search->{exitcode} > 0)) {
        #
        #  Prepare for a premature end. A failed supersmart prescript
        #  will abort the whole script.
        #
        $self->reset_result();
        $self->trace("failed supersmart prescript. aborting...");
      }
      $_->{privatestate}->{$search->{tag}} = $search->{privatestate};
      if ($search->{options}->{protocol}) {
        # must write protocol
        if ($protocol_had_error) {
          $search->addevent($self->get_option('protocolfileerror'),
              sprintf "cannot write protocol file %s! check your filesystem (permissions/usage/integrity) and disk devices", $self->{protocolsdir})
              if lc $self->get_option('protocolfileerror') ne 'ok';
        } else {
          if (scalar(@{$search->{matchlines}->{CRITICAL}}) ||
              scalar(@{$search->{matchlines}->{WARNING}}) ||
              scalar(@{$search->{matchlines}->{UNKNOWN}})) {
            if ($self->{protocolfh}->open($self->{protocolfile}, "a")) {
              foreach (qw(CRITICAL WARNING UNKNOWN)) {
                if (@{$search->{matchlines}->{$_}}) {
                  $self->{protocolfh}->print(sprintf "%s Errors in %s (tag %s)\n",
                      $_, $search->{logbasename}, $search->{tag});
                  foreach (@{$search->{matchlines}->{$_}}) {
                    $self->{protocolfh}->printf("%s\n", $_);
                  }
                }
              }
              $self->{protocolfh}->close();
              $self->{protocolwritten} = 1;
            }
          }
        }
      }
      if ($search->{options}->{count}) {
        foreach (qw(OK WARNING CRITICAL UNKNOWN)) {
          $self->{allerrors}->{$_} += scalar(@{$search->{matchlines}->{$_}});
          if ($search->{lastmsg}->{$_}) {
            $self->{lastmsg}->{$_} = $search->{lastmsg}->{$_};
          }
          foreach my $searchmatch (@{$search->{matchlines}->{$_}}) {
            unshift(@{$self->{matchlines}->{$_}}, $searchmatch);
          }
          while (scalar(@{$self->{matchlines}->{$_}}) >
              $self->get_option("preview")) {
            my $runter = pop(@{$self->{matchlines}->{$_}});
          }
        }
      }
      $self->formulate_result();
      if (($search->{tag} eq "prescript") && 
          ($search->{options}->{supersmartscript}) &&
          ($search->{exitcode} > 0)) {
        #
        #  Failed supersmart prescript. I'm out...
        #
        last;
      } elsif (($search->{tag} eq "postscript") && 
          ($search->{options}->{supersmartscript})) {
        my $codestr = {reverse %ERRORS}->{$search->{exitcode}};
        ($self->{exitmessage}, $self->{perfdata}) = 
            split(/\|/, $search->{lastmsg}->{$codestr}, 2);
        $self->{exitcode} = $search->{exitcode};
      }
    }
  }
  $self->cleanup_protocols();
  if ($self->get_option("htmlencode")) {
    $self->htmlencode(\$self->{exitmessage});
    $self->htmlencode(\$self->{long_exitmessage});
  }
  if ($self->{options}->{rununique}) {
    $self->cleanup_pidfile();
  }
  return $self;
}

sub htmlencode {
  my $self = shift;
  my $pstring = shift;
  return if ! $$pstring;
  $$pstring =~ s/&/&amp/g;
  $$pstring =~ s/</&lt/g;
  $$pstring =~ s/>/&gt/g;
  $$pstring =~ s/"/&quot/g;
}


sub await_while_rotate {
  my $self = shift;
  my($sec, $min, $hour, $mday, $mon, $year) = (localtime)[0, 1, 2, 3, 4, 5];
  if (($min == 0 || $min == 15 || $min == 30 || $min == 45) && $sec < 15) {
    $self->trace("waiting until **:**:15");
    foreach (1..(15 - $sec)) {
      sleep 1;
    }
  }
}

sub formulate_result {
  my $self = shift;
  #
  #  create the summary from all information collected so far
  #
  $self->{hint} = sprintf "(%s", join(", ", grep { $_ }
    ($self->{allerrors}->{CRITICAL} ? 
        sprintf "%d errors", $self->{allerrors}->{CRITICAL} : undef,
    $self->{allerrors}->{WARNING} ? 
        sprintf "%d warnings", $self->{allerrors}->{WARNING} : undef,
    $self->{allerrors}->{UNKNOWN} ? 
        sprintf "%d unknown", $self->{allerrors}->{UNKNOWN} : undef));
  if ($self->{protocolwritten}) {
    $self->{hint} .= sprintf " in %s)", basename($self->{protocolfile});
  } else {
    $self->{hint} .= ")";
  }
  foreach my $level (qw(CRITICAL WARNING UNKNOWN OK)) {
    my $preview;
    my $continue;
    if ($self->get_option("preview") > 1) {
      if (scalar(@{$self->{matchlines}->{$level}}) <
          $self->get_option("preview")) {
        $preview = join(", ", @{$self->{matchlines}->{$level}});
      } else {
        $preview = join(", ", @{$self->{matchlines}->{$level}});
      }
      $continue = scalar(@{$self->{matchlines}->{$level}}) <=
          $self->get_option("preview") ? "" : "...";
    } else {
      $preview = $self->{lastmsg}->{$level};
      $continue = $self->{allerrors}->{$level} == 1 ? "" : "...";
    }
    $self->{exitcode} = $ERRORS{$level};
    if (($level ne "OK") && ($self->{allerrors}->{$level})) {
      $self->{exitmessage} = sprintf "%s%s - %s %s", $level, 
          $self->get_option("outputhitcount") ? " - ".$self->{hint} : "",
          $preview,
          $continue;
      last;
    } else {
      $self->{exitmessage} = sprintf "OK - no errors or warnings";
    }
  }
  $self->{perfdata} = join (" ", 
      map { $_->formulate_perfdata(); if ($_->{perfdata}) {$_->{perfdata}} else {()} }
      @{$self->{searches}});
  if ($self->get_option('report') ne "short") {
    $self->formulate_long_result();
  }
}

sub formulate_long_result {
  my $self = shift;
  my $maxlength = $self->get_option('maxlength');
  $self->{long_exitmessage} = "";
  my $prefix = ($self->get_option('report') eq "html") ?
      "<table style=\"border-collapse: collapse;\">" : "";
  my $suffix = ($self->get_option('report') eq "html") ?
      "</table>" : "";
  my $messagelen = length($prefix) + length($suffix) +
      length($self->{exitmessage});
  my $line = "";
   
  foreach my $search (@{$self->{searches}}) {
    next if $search->{tag} eq 'postscript';
    if (scalar(@{$search->{matchlines}->{CRITICAL}}) ||
        scalar(@{$search->{matchlines}->{WARNING}}) ||
        scalar(@{$search->{matchlines}->{UNKNOWN}})) {
      if ($self->get_option('report') eq "html") {
        $line =
            sprintf "<tr valign=\"top\"><td class=\"service%s\">tag %s</td></tr>",
                ((scalar(@{$search->{matchlines}->{CRITICAL}}) && "CRITICAL") ||
                 (scalar(@{$search->{matchlines}->{WARNING}}) && "WARNING") ||
                 (scalar(@{$search->{matchlines}->{UNKNOWN}}) && "UNKNOWN")),
                $search->{tag};
      } else {
        $line =
            sprintf "tag %s %s\n",
                $search->{tag},
                ((scalar(@{$search->{matchlines}->{CRITICAL}}) && "CRITICAL") ||
                 (scalar(@{$search->{matchlines}->{WARNING}}) && "WARNING") ||
                 (scalar(@{$search->{matchlines}->{UNKNOWN}}) && "UNKNOWN"));
      }
      if ($messagelen + length($line) < $maxlength) {
        $self->{long_exitmessage} .= $line;
        $messagelen += length($line);
      } else {
        last;
      }
      foreach my $level (qw(CRITICAL WARNING UNKNOWN)) {
        foreach my $message (@{$search->{matchlines}->{$level}}) {
          if ($self->get_option('report') eq "html") {
            $message =~ s/</</g;
            $message =~ s/>/>/g;
            $line =
                sprintf "<tr valign=\"top\"><td nowrap width=\"100%%\" class=\"service%s\" style=\"border: 1px solid black;\">%s</td></tr>",
                $level, $message;
          } else {
            $line = sprintf "%s\n", $message;
          }
          if ($messagelen + length($line) < $maxlength) {
            $self->{long_exitmessage} .= $line;
            $messagelen += length($line);
          } else {
            last;
          }
        }
      }
    }
  }
  if ($self->{long_exitmessage}) {
    $self->{long_exitmessage} = sprintf "%s%s%s\n",
        $prefix, $self->{long_exitmessage}, $suffix;
  }
}

sub reset_result {
  my $self = shift;
  $self->{allerrors} = { OK => 0, WARNING => 0, CRITICAL => 0, UNKNOWN => 0 };
  foreach my $search (@{$self->{searches}}) {
    next if $search->{tag} eq 'postscript';
    next if $search->{tag} eq 'prescript';
    $search->{matchlines} = {
        OK => [],
        WARNING => [],
        CRITICAL => [],
        UNKNOWN => [],
    }
  }
}

sub reset {
  my $self = shift;
  $self->{allerrors} = { OK => 0, WARNING => 0, CRITICAL => 0, UNKNOWN => 0 };
  foreach my $level (qw(OK CRITICAL WARNING UNKNOWN)) {
    $self->{lastmsg}->{$level} = "";
  }
  foreach my $search (@{$self->{searches}}) {
    $search->reset();
  }
}

sub cleanup_protocols {
  my $self = shift;
  #
  #  cleanup old protocol files
  #
  #
  if ($self->{protocololdfiles} =~ /[^\\][ ]/) {
    # because Core::glob splits the argument on whitespace
    $self->{protocololdfiles} =~ s/( )/\\$1/g;
  }
  foreach my $oldprotocolfile (glob "$self->{protocololdfiles}") {
    if ((stat $oldprotocolfile)[9] < (time - $self->{protocolretention})) {
      $self->trace("deleting old protocol %s", $oldprotocolfile);
      unlink $oldprotocolfile;
    }
  }
}

sub init_macros {
  my $self = shift;
  my($sec, $min, $hour, $mday, $mon, $year) = (localtime)[0, 1, 2, 3, 4, 5];
  my $cw = $^O =~ /MSWin/ ? 0 : 
      strftime("%V", $sec, $min, $hour, $mday, $mon, $year, -1, -1, -1);
  $year += 1900; $mon += 1;
  #
  #  Set default values for the built-in macros.
  #
  my $DEFAULTMACROS = {
      CL_DATE_YYYY => sprintf("%04d", $year),
      CL_DATE_YY => substr($year,2,2),
      CL_DATE_MM => sprintf("%02d", $mon),
      CL_DATE_DD => sprintf("%02d", $mday),
      CL_DATE_HH => sprintf("%02d", $hour),
      CL_DATE_MI => sprintf("%02d", $min),
      CL_DATE_SS => sprintf("%02d", $sec),
      CL_DATE_TIMESTAMP => sprintf("%10d", time),
      CL_DATE_CW => sprintf("%02d", $cw),
      CL_NSCA_HOST_ADDRESS => "127.0.0.1",
      CL_NSCA_PORT => 5667,
      CL_NSCA_TO_SEC => 10,
      CL_NSCA_CONFIG_FILE => "/usr/local/nagios/etc/send_nsca.cfg",
  };
  if (defined(&Win32::LoginName)) {
    $DEFAULTMACROS->{CL_USERNAME} = &Win32::LoginName();
    $DEFAULTMACROS->{CL_HAS_WIN32} = 1;
  } else {
    $DEFAULTMACROS->{CL_USERNAME} = scalar getpwuid $>;
    $DEFAULTMACROS->{CL_HAS_WIN32} = 0;
  }
  if (defined(&Net::Domain::hostname)) {
    $DEFAULTMACROS->{CL_HOSTNAME} = &Net::Domain::hostname();
    $DEFAULTMACROS->{CL_DOMAIN} = &Net::Domain::hostdomain();
    $DEFAULTMACROS->{CL_FQDN} = &Net::Domain::hostfqdn();
    $DEFAULTMACROS->{CL_HAS_NET_DOMAIN} = 1;
  } else {
    $DEFAULTMACROS->{CL_HOSTNAME} = POSIX::uname();
    $DEFAULTMACROS->{CL_DOMAIN} = "localdomain";
    $DEFAULTMACROS->{CL_FQDN} = POSIX::uname().'.'.'localdomain';
    $DEFAULTMACROS->{CL_HAS_NET_DOMAIN} = 0;
  }
#printf STDERR "%s\n", Data::Dumper::Dumper($DEFAULTMACROS);
  $DEFAULTMACROS->{CL_IPADDRESS} =
      scalar gethostbyname($DEFAULTMACROS->{CL_HOSTNAME}) ?
      inet_ntoa(scalar gethostbyname($DEFAULTMACROS->{CL_HOSTNAME})) :
      '127.0.0.1';
  #
  #  Add self-defined macros to the defaultmacros structure or overwrite
  #  already defined macros.
  #
  if ($self->{macros}) {
    foreach (keys %{$self->{macros}}) {
      $DEFAULTMACROS->{$_} = $self->{macros}->{$_};
    }
  }
  #
  #  Add self-defined macros from the command line 
  #  --macro CL_KAAS="so a kaas" --macro CL_SCHMARRN="so a schmarrn"
  #
  if ($self->{cmdlinemacros}) {
    foreach (keys %{$self->{cmdlinemacros}}) {
      $DEFAULTMACROS->{$_} = $self->{cmdlinemacros}->{$_};
    }
  }
  #
  #  Escape the most commonly used special characters so they will no longer
  #  be treated like special characters in a pattern.
  #
  $self->{macros} = $DEFAULTMACROS;
  return $self;
}

sub late_init_macros {
  # these are macros filled with values that do not exist before
  # the Nagios::CheckLogfiles object has been fully initialized
  my $self = shift;
  $self->{macros}->{CL_SERVICEDESC} = $self->{cfgbase};
  $self->{macros}->{CL_NSCA_SERVICEDESC} = $self->{cfgbase};
  $self->{macros}->{CL_WARNING} = $self->{warning};
  $self->{macros}->{CL_CRITICAL} = $self->{critical};
}

sub merge_macros {
  my $self = shift;
  my $extramacros = shift;
  foreach (keys %{$extramacros}) {
    $self->{macros}->{$_} = $extramacros->{$_};
  }
}

#
#  Resolve macros in a string. 
#  If a second parameter is given, then this string is meant as a regular expression.
#  Escape special characters accordingly.
#
sub resolve_macros {
  my $self = shift;
  my $pstring = shift;
  return if ! defined $$pstring;
  while ($$pstring =~ /\$(.+?)\$/g) {
    my $maybemacro = $1;
    if (exists $self->{macros}->{$maybemacro}) {
      my $macro = $self->{macros}->{$maybemacro};
      $$pstring =~ s/\$$maybemacro\$/$macro/;
    }
  }
}


sub resolve_macros_in_pattern {
  my $self = shift;
  my $pstring = shift;
  return if ! $$pstring;
  while ($$pstring =~ /\$(.+?)\$/g) {
  # das alte bleibt hier stehen als denkmal der schande
  #while ($$pstring =~ /.*\$(\w+)\$.*/g) { 
    my $maybemacro = $1;
    if (exists $self->{macros}->{$maybemacro}) {
      my $macro = $self->{macros}->{$maybemacro};
       #
      #  Escape the most commonly used special characters so they will no longer
      #  be treated like special characters in a pattern.
      #
      $macro =~ s|/|\\/|g;
      $macro =~ s|\-|\\-|g;
      $macro =~ s|\.|\\.|g;
      $$pstring =~ s/\$$maybemacro\$/$macro/;
    }
  }
}

sub default_options {
  my $self = shift;
  my $defaults = shift;
  $self->{defaultoptions} = {};
  while (my($key, $value) = each %{$defaults}) {
    $self->{options}->{$key} = $value;
    $self->{defaultoptions}->{$key} = $value;
  }
}

sub set_options {
  my $self = shift;
  my $options = shift;
  while (my($key, $value) = each %{$options}) {
    $self->{options}->{$key} = $value if $value;
  }
}

sub set_option {
  my $self = shift;
  my $option = shift;
  my $value = shift;
  $self->{options}->{$option} = $value if defined $value;
}

sub get_option {
  my $self = shift;
  my $option = shift;
  return exists $self->{options}->{$option} ?
      $self->{options}->{$option} : undef;
}

sub get_options {
  my $self = shift;
  my $list = shift;
  if (! $list) {
    return $self->{options};
  } else {
    my %h = map {($_, $self->{options}->{$_})} split(',', $list);
    return \%h;
  }
}

sub get_non_default_options {
  my $self = shift;
  my $list = shift;
  if (! $list) {
    my %h = map {
      ($_, $self->{options}->{$_})
    } grep {
      ! exists $self->{defaultoptions}->{$_} ||
          "$self->{defaultoptions}->{$_}" ne "$self->{options}->{$_}";
    } keys %{$self->{options}};
    return \%h;
  } else {
    my %h = map {
      ($_, $self->{options}->{$_})
    } grep {
      ! exists $self->{defaultoptions}->{$_} ||
          "$self->{defaultoptions}->{$_}" ne "$self->{options}->{$_}";
    } split(',', $list);
    return \%h;
  }
}

sub refresh_default_options {
  my $self = shift;
  my $options = shift;
  if ($options) {
    if (ref($options) eq 'HASH') { # already as hash
      foreach my $option (keys %{$options}) {
        my $optarg = $options->{$option};
        if (! exists $self->{defaultoptions}->{$option} ||
            "$self->{defaultoptions}->{$option}" eq "$self->{options}->{$option}") {
          $self->{options}->{$option} = $optarg;
        }
      }
    }
  }
}

sub refresh_options {
  my $self = shift;
  my $options = shift;
  if ($options) {
    if (ref($options) eq 'HASH') { # already as hash
      foreach my $option (keys %{$options}) {
        my $optarg = $options->{$option};
        foreach my $defoption (keys %{$self->{options}}) {
          if ($option eq $defoption) {
            $self->{options}->{$defoption} = $optarg;
          }
        }
      }
    } else { # comes as string
      foreach my $option (split /,/, $options) {
        my $optarg = undef;
        $option =~ s/^\s+//;
        $option =~ s/\s+$//;
        if ($option =~ /(.*)=(.*)/) {
          $option = $1;
          $optarg = $2;
          $optarg =~ s/^"//;
          $optarg =~ s/"$//;
          $optarg =~ s/^'//;
          $optarg =~ s/'$//;
        }
        foreach my $defoption (keys %{$self->{options}}) {
          if ($option eq $defoption) {
            if (defined $optarg) {
              # example: sticky=3600,syslogclient="winhost1.dom"
              $self->{options}->{$defoption} = $optarg;
            } else {
              $self->{options}->{$defoption} = 1;
            }
          } elsif ($option eq 'no'.$defoption) {
            $self->{options}->{$defoption} = 0;
          }
        }
      } 
    }
  } 
  # reset [smart][pre|post]script options if no script should be called 
  foreach my $option (qw(script prescript postscript)) {
    if (exists $self->{options}->{'supersmart'.$option}) {
      $self->{options}->{'smart'.$option} = 1
          if $self->{options}->{'supersmart'.$option};
    }
    if (exists $self->{options}->{'smart'.$option}) {
      $self->{options}->{$option} = 1
          if $self->{options}->{'smart'.$option};
    }
    if (exists $self->{options}->{$option}) {
      if (($self->{options}->{$option}) && ! exists $self->{$option}) {
        $self->{options}->{$option} = 0;
        $self->{options}->{'smart'.$option} = 0;
        $self->{options}->{'supersmart'.$option} = 0;
      }
    }
  }
  if ($self->{options}->{sticky}) {
    if ($self->{options}->{sticky} > 1) {
      $self->{maxstickytime} = $self->{options}->{sticky};
      $self->{options}->{sticky} = 1;
    } else {
      # durch mehrmaliges refresh (seitens des CheckLogfiles-Objekts kann maxstickytime
      # zerschossen werden
      if (! exists $self->{maxstickytime} || $self->{maxstickytime} == 0) {
        $self->{maxstickytime} = 3600 * 24 * 365 * 10;
      }
    }
  }
  if ($self->{options}->{syslogclient}) {
#    $self->{prefilter} = $self->{options}->{syslogclient};
  }
}

sub trace {
  my $self = shift;
  my $format = shift;
  $self->{tracebuffer} = [] unless exists $self->{tracebuffer};
  push(@{$self->{tracebuffer}}, @_);
  if ($self->{verbose}) {
    printf("%s: ", scalar localtime);
    printf($format."\n", @_);
  }
  if ($self->{trace}) {
    my $logfh = new IO::File;
    $logfh->autoflush(1);
    if ($logfh->open($self->{tracefile}, "a")) {
      $logfh->printf("%s: ", scalar localtime);
      $logfh->printf($format, @_);
      $logfh->printf("\n");
      $logfh->close();
    }
  }
}

sub action {
  my $self = shift;
  my $script = shift;
  my $scriptparams = shift;
  my $scriptstdin = shift;
  my $scriptdelay = shift;
  my $smart = shift;
  my $privatestate = shift;
  my $success = 0;
  my $rc = 0;
  my $exitvalue;
  my $signalnum;
  my $dumpedcore;
  my $output;
  my $pid = 0;
  my $wait = 0;
  my $strerror = (qw(OK WARNING CRITICAL UNKNOWN))
      [$self->{macros}->{CL_SERVICESTATEID}];
  my $cmd;
  my @stdinformat = ();
  foreach my $macro (keys %{$self->{macros}}) {
    my $envmacro = $macro;
    if ($envmacro =~ /^CL_/) {
      $envmacro =~ s/^CL_/CHECK_LOGFILES_/;
    } else {
      $envmacro = "CHECK_LOGFILES_".$macro;
    }
    $ENV{$envmacro} = defined($self->{macros}->{$macro}) ? 
        $self->{macros}->{$macro} : "";
  }
  $ENV{CHECK_LOGFILES_SERVICESTATE} = (qw(OK WARNING CRITICAL UNKNOWN))
      [$ENV{CHECK_LOGFILES_SERVICESTATEID}];
  if (ref $script eq "CODE") {
    $self->trace("script is of type %s", ref $script);
    if (ref($scriptparams) eq "ARRAY") {
      foreach (@{$scriptparams}) {
        $self->resolve_macros(\$_) if $_;
      }
    }
    my $stdoutvar;
    *SAVEOUT = *STDOUT;
    eval {
      our $CHECK_LOGFILES_PRIVATESTATE = $privatestate;
      open OUT ,'>',\$stdoutvar;
      *STDOUT = *OUT;
      $exitvalue = &{$script}($scriptparams, $scriptstdin);
    };
    *STDOUT = *SAVEOUT;
    if ($@) {
      $output = $@;
      $success = 0;
      $rc = -1;
      $self->trace("script said: %s", $output);
    } else {
      #$output = $stdoutvar || "";
      $output = defined $stdoutvar ?  $stdoutvar :  "";
      chomp $output;
      $self->trace("script said: %s", $output);
      if ($smart) {
        if (($exitvalue =~ /^\d/) && ($exitvalue >= 0 && $exitvalue <= 3)) {
          $success = 1;
          $rc = $exitvalue;
          $self->trace("script %s exits with code %d", $script, $rc);
        } else {
          $success = 1;
          $rc = -4;
          $self->trace("script %s failed for unknown reasons", $script);
        }
      } else {
        $success = 1;
        $rc = $exitvalue;
        $output = $self->{macros}->{CL_SERVICEOUTPUT};
      }
    }
  } else {
    my $pathsep = ($^O =~ /MSWin/) ? ';' : ':';
    foreach my $dir (split(/$pathsep/, $self->{scriptpath})) {
      if ( -x $dir.'/'.$script || ( -f $dir.'/'.$script && $^O =~ /cygwin|MSWin/ && $script =~ /\.(bat|exe)$/i )) {
        $self->trace(sprintf "found script in %s/%s", $dir, $script);
        $cmd = sprintf "%s/%s", $dir, $script;
        if ($^O =~ /MSWin/) {
          $cmd =~ s/\//\\/g;
          if ($cmd =~ /\s/) {
            if (defined(&Win32::GetShortPathName)) {
              $cmd = &Win32::GetShortPathName($cmd);
            } else {
              $cmd = sprintf "\"%s\"", $cmd;
            }
          }
        } else {
          # need to escape blanks
          if ($cmd =~ /\s/) {
            $cmd =~ s/([ ])/\\$1/g;
          }
        }
        last;
      }
    }
    if ($cmd) {
      if (defined $scriptparams) {
        $self->resolve_macros(\$scriptparams);
        $cmd = sprintf "%s %s", $cmd, $scriptparams;
      }
      $self->trace(sprintf "execute %s", $cmd);
      if (defined $scriptstdin) {
        my $pid = 0;
        my $wait = 0;
        my $maxlines = 100;
        if (! ref($scriptstdin eq "ARRAY")) {
          $scriptstdin = [$scriptstdin];
        }
        foreach (@{$scriptstdin}) {
          $self->resolve_macros(\$_);


Re: check_logfile config logfilemissing

Posted: Wed May 30, 2018 8:39 am
by tgriep
Thanks for posting what you did. It is incomplete so it did not show the option.

I did some further testing and it looks like the plugin is not reading that option from the config file as it seems to be a constant, at least in the version I am testing.
What I had to do is to edit the script and change the constant from

Code: Select all

logfilemissing => 'unknown',
to

Code: Select all

logfilemissing => 'ok',
And then it would return an OK status when the file is missing.
You could create a copy of the plugin as to keep the original around for the default setting if needed.

Re: check_logfile config logfilemissing

Posted: Fri Jun 01, 2018 10:57 am
by optionstechnology
Yip this has fixed it - thanks

can be closed