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/&/&/g;
$$pstring =~ s/</</g;
$$pstring =~ s/>/>/g;
$$pstring =~ s/"/"/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(\$_);