+# Make a regex from a glob pattern. Shamelessly stolen from Perl
+# Cookbook chapter 6.9
+sub glob2regex {
+ my $globstr = shift;
+ my %patmap
+ = ( '*' => '.*',
+ '?' => '.',
+ '[' => '[',
+ ']' => ']',
+ );
+ $globstr =~ s{(.)} { $patmap{$1} || "\Q$1" }ge;
+ return '\A' . $globstr . '\z';
+}
+
+#
+# Read config file
+#
+sub parse_configfile {
+ our $tiny = undef;
+
+ # Regexp for boolean values
+ our $off = qr{\A (0|off|false) \s* \z}ixms;
+ our $on = qr{\A (1|on|true) \s* \z}ixms;
+
+ # Mapping between command line options and the corresponding
+ # config file options
+ our %opt2config
+ = ( 'info' => 'output_servicetag',
+ 'extinfo' => 'output_sysinfo',
+ 'postmsg' => 'output_post_message',
+ 'state' => 'output_servicestate',
+ 'shortstate' => 'output_servicestate_abbr',
+ 'show_blacklist' => 'output_blacklist',
+ 'htmlinfo' => 'output_html',
+ 'okinfo' => 'output_ok_verbosity',
+ 'protocol' => 'snmp_version',
+ 'community' => 'snmp_community',
+ 'port' => 'snmp_port',
+ 'ipv6' => 'snmp_use_ipv6',
+ 'tcp' => 'snmp_use_tcp',
+ 'warning' => 'temp_threshold_warning',
+ 'critical' => 'temp_threshold_critical',
+ 'all' => 'check_everything',
+ 'perfdata' => 'performance_data',
+ 'tempunit' => 'temperature_unit',
+ 'timeout' => 'timeout',
+ 'blacklist' => 'blacklist',
+ 'legacy_perfdata' => 'legacy_performance_data',
+ );
+
+ # Load the perl module
+ if ( eval { require Config::Tiny; 1 } ) {
+ $tiny = Config::Tiny->new();
+ }
+ else {
+ print "ERROR: Required perl module 'Config::Tiny' not found\n";
+ exit $E_UNKNOWN;
+ }
+
+ # Read the config file
+ $tiny = Config::Tiny->read($opt{configfile})
+ or do { report('other', (sprintf q{Couldn't read configuration file: %s}, Config::Tiny->errstr()), $E_UNKNOWN);
+ return; };
+
+ # Syntax check
+ foreach my $section (keys %{ $tiny }) {
+ KEYWORD:
+ foreach my $keyword (keys %{ $tiny->{$section} }) {
+ next KEYWORD if $keyword eq 'check_everything';
+ if ($keyword =~ m{\A check_(.+)}xms) {
+ my $c = $1;
+ foreach my $cl (keys %check) {
+ next KEYWORD if $c eq $cl;
+ }
+ }
+ else {
+ LEGAL:
+ foreach my $legal (keys %opt2config) {
+ next KEYWORD if $keyword eq $opt2config{$legal};
+ }
+ }
+ if ($section eq '_') {
+ report('other', qq{CONFIG ERROR: In the global section: Unknown statement "$keyword"}, $E_UNKNOWN);
+ }
+ else {
+ report('other', qq{CONFIG ERROR: Unknown statement "$keyword" in section "$section"}, $E_UNKNOWN);
+ }
+ }
+ }
+
+ # Adjust checks according to statements in the configuration file
+ sub configfile_adjust_checks {
+ my $keyword = shift;
+ CHECK_CONFIG:
+ foreach my $key (keys %check) {
+ my $copt = join '_', 'check', $key;
+ next CHECK_CONFIG if !defined $tiny->{$keyword}->{$copt} or $tiny->{$keyword}->{$copt} eq q{};
+ if ($tiny->{$keyword}->{$copt} =~ m{$on}ixms) {
+ $check{$key} = 1;
+ }
+ elsif ($tiny->{$keyword}->{$copt} =~ m{$off}ixms) {
+ $check{$key} = 0;
+ }
+ else {
+ report('other', "CONFIG ERROR: Rvalue for '$copt' must be boolean (True/False)", $E_UNKNOWN);
+ }
+ }
+ return;
+ }
+
+ # Set blacklist according to statements in the configuration file
+ sub configfile_set_blacklist {
+ my $keyword = shift;
+ if (defined $tiny->{$keyword}->{blacklist} and $tiny->{$keyword}->{blacklist} ne q{}) {
+ # set_blacklist() takes an array ref
+ set_blacklist([$tiny->{$keyword}->{blacklist}]);
+ }
+ return;
+ }
+
+ # Set timeout according to statements in the configuration file
+ sub configfile_set_timeout {
+ my $keyword = shift;
+ if (defined $tiny->{$keyword}->{timeout} and $tiny->{$keyword}->{timeout} ne q{}) {
+ if ($tiny->{$keyword}->{timeout} =~ m{\A \d+ \z}xms) { # integer
+ $opt{timeout} = $tiny->{$keyword}->{timeout};
+ }
+ else {
+ report('other', "CONFIG ERROR: Rvalue for 'timeout' must be a positive integer", $E_UNKNOWN);
+ }
+ }
+ return;
+ }
+
+ # Set a boolean option
+ sub configfile_set_boolean {
+ my ($keyword, $bool) = @_;
+ my $cbool = $opt2config{$bool};
+ if (defined $tiny->{$keyword}->{$cbool} and $tiny->{$keyword}->{$cbool} ne q{}) {
+ if ($tiny->{$keyword}->{$cbool} =~ m{$on}ixms) {
+ $opt{$bool} = 1;
+ }
+ elsif ($tiny->{$keyword}->{$cbool} =~ m{$off}ixms) {
+ $opt{$bool} = 0;
+ }
+ else {
+ report('other', "CONFIG ERROR: Rvalue for '$cbool' must be boolean (True/False)", $E_UNKNOWN);
+ }
+ }
+ return;
+ }
+
+ # Set htmlinfo option from config file
+ sub configfile_set_htmlinfo {
+ my $keyword = shift;
+ my $conf = $opt2config{htmlinfo};
+ if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
+ if ($tiny->{$keyword}->{$conf} =~ m{$on}ixms) {
+ $opt{htmlinfo} = 1;
+ }
+ elsif ($tiny->{$keyword}->{$conf} =~ m{$off}ixms) {
+ $opt{htmlinfo} = undef;
+ }
+ else {
+ $opt{htmlinfo} = $tiny->{$keyword}->{$conf};
+ }
+ }
+ return;
+ }
+
+ # Set OK output verbosity
+ sub configfile_set_ok_verbosity {
+ my $keyword = shift;
+ my $conf = $opt2config{okinfo};
+ if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
+ if ($tiny->{$keyword}->{$conf} =~ m{\A \d+ \z}xms) {
+ $opt{okinfo} = $tiny->{$keyword}->{$conf};
+ }
+ else {
+ report('other', "CONFIG ERROR: Rvalue for '$conf' must be a positive integer", $E_UNKNOWN);
+ }
+ }
+ return;
+ }
+
+ # Set SNMP protocol version from config file
+ sub configfile_set_snmp_version {
+ my $keyword = shift;
+ my $conf = $opt2config{protocol};
+ if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
+ if ($tiny->{$keyword}->{$conf} =~ m{\A 1|2|3 \z}xms) {
+ $opt{protocol} = $tiny->{$keyword}->{$conf};
+ }
+ else {
+ report('other', "CONFIG ERROR: Rvalue for '$conf' must be '1', '2' or '3'", $E_UNKNOWN);
+ }
+ }
+ return;
+ }
+
+ # Set SNMP community name from config file
+ sub configfile_set_snmp_community {
+ my $keyword = shift;
+ my $conf = $opt2config{community};
+ if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
+ $opt{community} = $tiny->{$keyword}->{$conf};
+ }
+ return;
+ }
+
+ # Set SNMP port number from config file
+ sub configfile_set_snmp_port {
+ my $keyword = shift;
+ my $conf = $opt2config{port};
+ if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
+ if ($tiny->{$keyword}->{$conf} =~ m{\A \d+ \z}xms) { # integer
+ $opt{port} = $tiny->{$keyword}->{$conf};
+ }
+ else {
+ report('other', "CONFIG ERROR: Rvalue for '$conf' must be a positive integer", $E_UNKNOWN);
+ }
+ }
+ return;
+ }
+
+ # Set temperature threshold from config file
+ sub configfile_set_temp_threshold {
+ my $keyword = shift;
+ my $level = shift;
+ my $conf = $opt2config{$level};
+ if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
+ $opt{$level} = [$tiny->{$keyword}->{$conf}]; # array ref
+ }
+ return;
+ }
+
+ # Set perfdata from config file
+ sub configfile_set_perfdata {
+ my $keyword = shift;
+ my $conf = $opt2config{perfdata};
+ if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
+ if ($tiny->{$keyword}->{$conf} =~ m{$on}ixms) {
+ $opt{perfdata} = 1;
+ }
+ elsif ($tiny->{$keyword}->{$conf} =~ m{$off}ixms) {
+ $opt{perfdata} = undef;
+ }
+ elsif ($tiny->{$keyword}->{$conf} =~ m{\A minimal|multiline \z}xms) {
+ $opt{perfdata} = $tiny->{$keyword}->{$conf};
+ }
+ else {
+ report('other', "CONFIG ERROR: Rvalue for '$conf' must be either boolean, 'minimal' or 'multiline'", $E_UNKNOWN);
+ }
+ }
+ return;
+ }
+
+ # Set temp unit from config file
+ sub configfile_set_tempunit {
+ my $keyword = shift;
+ my $conf = $opt2config{tempunit};
+ if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
+ if ($tiny->{$keyword}->{$conf} =~ m{\A C|F|K|R \z}ixms) {
+ $opt{tempunit} = $tiny->{$keyword}->{$conf};
+ }
+ else {
+ report('other', "CONFIG ERROR: Rvalue for '$conf' must one of C/F/K/R", $E_UNKNOWN);
+ }
+ }
+ return;
+ }
+
+ # Set postmsg string from config file
+ sub configfile_set_postmsg {
+ my $keyword = shift;
+ my $conf = $opt2config{postmsg};
+ if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
+ $opt{postmsg} = $tiny->{$keyword}->{$conf}; # array ref
+ }
+ return;
+ }
+
+ # Sections in the config file to check for statements
+ my @sections = ();
+
+ # First: Populate the sections array with the global section
+ @sections = ('_');
+
+ # Second: Populate the sections array with host glob pattern (but
+ # not exact match)
+ PATTERN:
+ foreach my $glob (sort keys %{ $tiny }) {
+ next PATTERN if $glob eq '_'; # global section
+ next PATTERN if $glob eq $opt{hostname}; # exact match
+ my $regex = glob2regex($glob); # make regexp
+ if ($opt{hostname} =~ m{$regex}) {
+ push @sections, $glob;
+ }
+ }
+
+ # Third: Populate the sections array with exact hostname
+ if (defined $tiny->{$opt{hostname}}) {
+ push @sections, $opt{hostname};
+ }
+
+ # Loop through the sections array and get options
+ foreach my $sect (@sections) {
+ configfile_adjust_checks($sect);
+ configfile_set_blacklist($sect);
+ configfile_set_timeout($sect);
+ configfile_set_htmlinfo($sect);
+ configfile_set_ok_verbosity($sect);
+ configfile_set_boolean($sect, 'all');
+ configfile_set_boolean($sect, 'info');
+ configfile_set_boolean($sect, 'extinfo');
+ configfile_set_boolean($sect, 'state');
+ configfile_set_boolean($sect, 'shortstate');
+ configfile_set_boolean($sect, 'show_blacklist');
+ configfile_set_boolean($sect, 'ipv6');
+ configfile_set_boolean($sect, 'tcp');
+ configfile_set_boolean($sect, 'legacy_perfdata');
+ configfile_set_snmp_version($sect);
+ configfile_set_snmp_community($sect);
+ configfile_set_snmp_port($sect);
+ configfile_set_temp_threshold($sect, 'warning');
+ configfile_set_temp_threshold($sect, 'critical');
+ configfile_set_perfdata($sect);
+ configfile_set_tempunit($sect);
+ configfile_set_postmsg($sect);
+ }
+
+ return;
+}
+