#!/usr/bin/perl
# nagios: -epn
## pnp4nagios–0.6.11
## Copyright (c) 2005-2010 Joerg Linge (http://www.pnp4nagios.org)
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.



if( $< == 0 ){
    print "dont try this as root \n";
    exit 1;
}

use warnings;
use strict;
use Getopt::Long;
use Time::HiRes qw(gettimeofday tv_interval);
use vars qw ( $TEMPLATE %NAGIOS $t0 $t1 $rt $delayed_write $rrdfile @ds_create $count $line @lines $t @t $name $xact $warn $crit $min $max $unit $act $ds_create $ds_update @rrd_create $rrd_update $dstype);

my %conf = (
    TIMEOUT           => "5",
    CFG_DIR           => "/usr/local/nagios/etc/pnp/",
    USE_RRDs          => 1,
    RRDPATH           => "/usr/local/nagios/share/perfdata",
    RRDTOOL           => "/usr/bin/rrdtool",
    RRD_STORAGE_TYPE  => "SINGLE",
    RRD_HEARTBEAT     => 8640,
    RRA_STEP          => 60,
    RRA_CFG           => "/usr/local/nagios/etc/pnp/rra.cfg",
    STATS_DIR         => "/usr/local/nagios/var/stats",
    LOG_FILE          => "/usr/local/nagios/var",
    LOG_FILE_MAX_SIZE => "10485760",               #Truncate after 10MB
    LOG_LEVEL         => 0,
    XML_ENC           => "UTF-8",
    XML_UPDATE_DELAY  => 0,                        # Write XML only if file is older then XML_UPDATE_DELAY seconds
    RRD_DAEMON_OPTS   => "",
    UOM2TYPE          => { 'c' => 'COUNTER', 'd' => 'DERIVE' },
);

my %const = (
    XML_STRUCTURE_VERSION => "4",
    VERSION               => "0.6.11",
);

#
# Dont change anything below these lines ...
#
#
# "rrdtool create" Syntax
#
my @default_rrd_create = ( "RRA:AVERAGE:0.5:1:2880", "RRA:AVERAGE:0.5:5:2880", "RRA:AVERAGE:0.5:30:4320", "RRA:AVERAGE:0.5:360:5840", "RRA:MAX:0.5:1:2880", "RRA:MAX:0.5:5:2880", "RRA:MAX:0.5:30:4320", "RRA:MAX:0.5:360:5840", "RRA:MIN:0.5:1:2880", "RRA:MIN:0.5:5:2880", "RRA:MIN:0.5:30:4320", "RRA:MIN:0.5:360:5840", );

Getopt::Long::Configure('bundling');
my ( $opt_d, $opt_V, $opt_h, $opt_i, $opt_n, $opt_b );
my $opt_t = $conf{TIMEOUT};                            # Default Timeout
my $opt_c = $conf{CFG_DIR} . "process_perfdata.cfg";
GetOptions(
    "V"          => \$opt_V,
    "version"    => \$opt_V,
    "h"          => \$opt_h,
    "help"       => \$opt_h,
    "i"          => \$opt_i,
    "inetd"      => \$opt_i,
    "b=s"        => \$opt_b,
    "bulk=s"     => \$opt_b,
    "d=s"        => \$opt_d,
    "datatype=s" => \$opt_d,
    "t=i"        => \$opt_t,
    "timeout=i"  => \$opt_t,
    "c=s"        => \$opt_c,
    "config=s"   => \$opt_c,
    "n"          => \$opt_n,
    "npcd"       => \$opt_n
);

parse_config($opt_c);
$opt_t = $conf{TIMEOUT};    # Reset Timeout from config file
my %stats = (
    timet       => time,
    error       => 0,
    invalid     => 0,
    skipped     => 0,
    runtime     => 0,
    rows        => 0,
    create      => 0,
    update      => 0,
    );

#
# RRDs Perl Module Detection
#
if ( $conf{USE_RRDs} == 1 ) {
    unless ( eval "use RRDs;1" ) {
        $conf{USE_RRDs} = 0;
    }
}

print_help()    if ($opt_h);
print_version() if ($opt_V);

init_signals();
alarm($opt_t);
main();

#
# Subs
#
# Main function to switch to the right mode.
sub main {
    $t0 = [gettimeofday];
    my $lines = 0;
    if ($opt_i) {
        print_log( "process_perfdata.pl-$const{VERSION} starting in INETD Mode", 1 );
        %NAGIOS = parse_env();
        $lines = process_perfdata();
    }
    elsif ( $opt_b && !$opt_n ) {
        print_log( "process_perfdata.pl-$const{VERSION} starting in BULK Mode called by Nagios", 1 );
        $lines = process_perfdata_file();
    }
    elsif ( $opt_b && $opt_n ) {
        print_log( "process_perfdata.pl-$const{VERSION} starting in BULK Mode called by NPCD", 1 );
        $lines = process_perfdata_file();
    }
    else {
        print_log( "process_perfdata.pl-$const{VERSION} starting in SYNC Mode", 1 );
        %NAGIOS = parse_env();
        $lines = process_perfdata();
    }
    $t1 = [gettimeofday];
    $rt = tv_interval $t0, $t1;
    $stats{runtime} = $rt;
    $stats{rows} = $lines;
    store_internals();
    print_log( "PNP exiting (runtime ${rt}s) ...", 1 );
    exit 0;
}

#
# Parse %ENV and return a global hash %NAGIOS
#
sub parse_env {
    %NAGIOS = ();
    $NAGIOS{DATATYPE} = "SERVICEPERFDATA";
    if ($opt_i) {
        my @input = <STDIN>;
        foreach my $key (@input) {
            $key = urlencode($key);        # URLencode
            $key =~ /<(NAGIOS_[A-Z 0-9_]+)>(.*)<\/NAGIOS_.*>/;
            $ENV{$1} = $2 if ($2);
        }
    }

    if (( !$ENV{NAGIOS_HOSTNAME} ) and ( !$ENV{ICINGA_HOSTNAME} )) {
        print_log( "Cant find Nagios Environment. Exiting ....", 1 );
        exit 2;
    }
    foreach my $key ( sort keys %ENV ) {
        if ( $key =~ /^(NAGIOS|ICINGA)_(.*)/ ) {
            $NAGIOS{$2} = $ENV{$key};
        }
    }

    if ($opt_d) {
        $NAGIOS{DATATYPE} = $opt_d;
    }

    $NAGIOS{DISP_HOSTNAME}    = $NAGIOS{HOSTNAME};
    $NAGIOS{DISP_SERVICEDESC} = $NAGIOS{SERVICEDESC};
    $NAGIOS{HOSTNAME}         = cleanup( $NAGIOS{HOSTNAME} );
    $NAGIOS{SERVICEDESC}      = cleanup( $NAGIOS{SERVICEDESC} );
    $NAGIOS{PERFDATA}         = $NAGIOS{SERVICEPERFDATA};
    $NAGIOS{CHECK_COMMAND}    = $NAGIOS{SERVICECHECKCOMMAND};

    if ( $NAGIOS{DATATYPE} eq "HOSTPERFDATA" ) {
        $NAGIOS{SERVICEDESC}      = "_HOST_";
        $NAGIOS{DISP_SERVICEDESC} = "Host Perfdata";
        $NAGIOS{PERFDATA}         = $NAGIOS{HOSTPERFDATA};
        $NAGIOS{CHECK_COMMAND}    = $NAGIOS{HOSTCHECKCOMMAND};
    }
    print_log( "Datatype set to '$NAGIOS{DATATYPE}' ", 2 );
    return %NAGIOS;
}

#
# Perfdata sanity check
#
sub process_perfdata {
    if ( !$NAGIOS{PERFDATA} ) {
        print_log( "No Performance Data for $NAGIOS{HOSTNAME} / $NAGIOS{SERVICEDESC} ", 1 );
        if ( !$opt_b ) {
            print_log( "PNP exiting ...", 1 );
            exit 3;
        }
    }

    if ( $NAGIOS{PERFDATA} =~ /^(.*)\s\[(.*)\]$/ ) {
        $NAGIOS{PERFDATA}      = $1;
        $NAGIOS{CHECK_COMMAND} = $2;
        print_log( "Found Perfdata from Distributed Server $NAGIOS{HOSTNAME} / $NAGIOS{SERVICEDESC} ($NAGIOS{PERFDATA})", 1 );
    }
    else {
        print_log( "Found Performance Data for $NAGIOS{HOSTNAME} / $NAGIOS{SERVICEDESC} ($NAGIOS{PERFDATA}) ", 1 );
    }

    $NAGIOS{PERFDATA} =~ s/,/./g;
    $NAGIOS{PERFDATA} =~ s/\s+=/=/g;
    $NAGIOS{PERFDATA} =~ s/=\s+/=/g;
    $NAGIOS{PERFDATA} .= " ";
    parse_perfstring( $NAGIOS{PERFDATA} );
    return 1;
}

#
# Process Perfdata in Bulk Mode
#
sub process_perfdata_file {
    if ( $opt_b =~ /-PID-(\d+)/ ) {
        print_log( "Oops: $opt_b already processed by $1 - please check timeout settings", 0 );
    }

    print_log( "searching for $opt_b", 2 );
    if ( -e "$opt_b" ) {
        my $pdfile = "$opt_b" . "-PID-" . $$;
        print_log( "renaming $opt_b to $pdfile for bulk update", 2 );
        unless ( rename "$opt_b", "$pdfile" ) {
            print_log( "ERROR: rename $opt_b to $pdfile failed", 1 );
            exit 4;
        }

        print_log( "reading $pdfile for bulk update", 2 );
        open( PDFILE, "< $pdfile" );
        my $count = 0;
        while (<PDFILE>) {
            $count++;
            print_log( "Processing Line $count", 2 );
            my @LINE = split(/\t/);
            %ENV = ();    # cleaning ENV
            foreach my $k (@LINE) {
                $k =~ /([A-Z 0-9_]+)::(.*)$/;
                $ENV{ 'NAGIOS_' . $1 } = $2 if ($2);
            }
            if ( $ENV{NAGIOS_SERVICEPERFDATA} || $ENV{NAGIOS_HOSTPERFDATA} ) {
                parse_env();
                process_perfdata();
            }
            else {
                print_log( "No Perfdata. Skipping line $count", 2 );
                $stats{skipped}++;
            }
        }

        print_log( "$count lines processed", 1 );

        if ( unlink("$pdfile") == 1 ) {
            print_log( "$pdfile deleted", 1 );
        }else {
            print_log( "Could not delete $pdfile:$!", 1 );
        }
    return $count;
    }
    else {
        print_log( "ERROR: File $opt_b not found", 1 );
    }
}

#
# Write Data to RRD Files
#
sub data2rrd {

    my @data      = @_;
    my @rrd_state = ();
    my $rrd_storage_type;

    print_log( "data2rrd called", 2 );
    $NAGIOS{XMLFILE}          = $conf{RRDPATH} . "/" . $data[0]{hostname} . "/" . $data[0]{servicedesc} . ".xml";
    $NAGIOS{SERVICEDESC}      = $data[0]{servicedesc};
    $NAGIOS{DISP_SERVICEDESC} = $data[0]{disp_servicedesc};
    $NAGIOS{AUTH_SERVICEDESC} = $data[0]{auth_servicedesc} || "";
    $NAGIOS{AUTH_HOSTNAME}    = $data[0]{auth_hostname} || "";
    $NAGIOS{MULTI_PARENT}     = "";
    $NAGIOS{MULTI_PARENT}     = $data[0]{multi_parent} || "";
    $TEMPLATE                 = $data[0]{template};

    unless ( -d "$conf{RRDPATH}" ) {
        unless ( mkdir "$conf{RRDPATH}" ) {
            print_log( "mkdir $conf{RRDPATH}, permission denied ", 1 );
            print_log( "PNP exiting ...",                          1 );
            exit 5;
        }
    }

    unless ( -d "$conf{RRDPATH}/$NAGIOS{HOSTNAME}" ) {
        unless ( mkdir "$conf{RRDPATH}/$NAGIOS{HOSTNAME}" ) {
            print_log( "mkdir $conf{RRDPATH}/$NAGIOS{HOSTNAME}, permission denied ", 1 );
            print_log( "PNP exiting ...",                                            1 );
            exit 6;
        }
    }

    #
    # Create PHP Template File
    #
    open_template( $NAGIOS{XMLFILE} );

    @ds_create = ();
    $ds_update = '';

    for my $i ( 0 .. $#data ) {
        print_log( " -- Job $i ", 3 );
        my $DS = $i + 1;

        #
        # for each datasource
        #
        for my $job ( sort keys %{ $data[$i] } ) {
            if ( defined $data[$i]{$job} ) {
                print_log( "  -- $job -> $data[$i]{$job}", 3 );
            }
        }

        if ( $data[$i]{rrd_storage_type} eq "MULTIPLE" ) {
            print_log( "DEBUG: MULTIPLE Storage Type", 3 );
            $DS = 1;
            # PNP 0.4.x Template compatibility
            $NAGIOS{RRDFILE} = "";
            
            #
            $rrd_storage_type = "MULTIPLE";
            $rrdfile          = $conf{RRDPATH} . "/" . $data[$i]{hostname} . "/" . $data[$i]{servicedesc} . "_" . $data[$i]{name} . ".rrd";

            # DS is set to 1
            @ds_create = "DS:$DS:$data[$i]{dstype}:$data[$i]{rrd_heartbeat}:$data[$i]{rrd_min}:$data[$i]{rrd_max}";
            $ds_update = ":$data[$i]{value}";
            @rrd_state = write_rrd();
            @ds_create = ();
            $ds_update = "";
        }
        else {
            print_log( "DEBUG: SINGLE Storage Type", 3 );

            # PNP 0.4.x Template compatibility
            $NAGIOS{RRDFILE} = $conf{RRDPATH} . "/" . $data[0]{hostname} . "/" . $data[0]{servicedesc} . ".rrd";

            #
            $rrd_storage_type = "SINGLE";
            $rrdfile          = $conf{RRDPATH} . "/" . $data[$i]{hostname} . "/" . $data[$i]{servicedesc} . ".rrd";
            push( @ds_create, "DS:$DS:$data[$i]{dstype}:$data[$i]{rrd_heartbeat}:$data[$i]{rrd_min}:$data[$i]{rrd_max}" );
            $ds_update = "$ds_update:$data[$i]{value}";
        }

        write_to_template( "TEMPLATE",         $data[0]{template} );
        write_to_template( "RRDFILE",          $rrdfile );
        write_to_template( "RRD_STORAGE_TYPE", $data[$i]{rrd_storage_type} );
        write_to_template( "RRD_HEARTBEAT",    $data[$i]{rrd_heartbeat} );
        write_to_template( "IS_MULTI",         $data[0]{multi} );
        write_to_template( "DS",               $DS );
        write_to_template( "NAME",             $data[$i]{name} );
        write_to_template( "LABEL",            $data[$i]{label} );
        write_to_template( "UNIT",             $data[$i]{uom} );
        write_to_template( "ACT",              $data[$i]{value} );
        write_to_template( "WARN",             $data[$i]{warning} );
        write_to_template( "WARN_MIN",         $data[$i]{warning_min} );
        write_to_template( "WARN_MAX",         $data[$i]{warning_max} );
        write_to_template( "WARN_RANGE_TYPE",  $data[$i]{warning_range_type} );
        write_to_template( "CRIT",             $data[$i]{critical} );
        write_to_template( "CRIT_MIN",         $data[$i]{critical_min} );
        write_to_template( "CRIT_MAX",         $data[$i]{critical_max} );
        write_to_template( "CRIT_RANGE_TYPE",  $data[$i]{critical_range_type} );
        write_to_template( "MIN",              $data[$i]{min} );
        write_to_template( "MAX",              $data[$i]{max} );

    }

    if ( $rrd_storage_type eq "SINGLE" ) {
        @rrd_state = write_rrd();
    }

    write_state_to_template(@rrd_state);
    write_env_to_template();
    close_template( $NAGIOS{XMLFILE} );
}

sub write_rrd {
    my @rrd_create = ();
    my @rrd_state  = ();

    print_log( "DEBUG: TPL-> $TEMPLATE",  3 );
    print_log( "DEBUG: CRE-> @ds_create", 3 );
    print_log( "DEBUG: UPD-> $ds_update", 3 );

    if ( !-e "$rrdfile" ) {
        @rrd_create = parse_rra_config($TEMPLATE);
        if ( $conf{USE_RRDs} == 1 ) {
            print_log( "RRDs::create $rrdfile @rrd_create @ds_create --start=$NAGIOS{TIMET} --step=$conf{RRA_STEP}", 2 );
            RRDs::create( "$rrdfile", @rrd_create, @ds_create, "--start=$NAGIOS{TIMET}", "--step=$conf{RRA_STEP}" );

            my $err = RRDs::error();
            if ($err) {
                print_log( "RRDs::create ERROR $err", 1 );
                @rrd_state = ( 1, $err );
                $stats{error}++;
            }
            else {
                print_log( "$rrdfile created", 2 );
                @rrd_state = ( 0, "just created" );
                $stats{create}++;
            }
        }
        else {
            print_log( "RRDs Perl Modules are not installed. Falling back to rrdtool system call.",                           2 );
            print_log( "$conf{RRDTOOL} create $rrdfile @rrd_create @ds_create --start=$NAGIOS{TIMET} --step=$conf{RRA_STEP}", 2 );
            system("$conf{RRDTOOL} create $rrdfile @rrd_create @ds_create --start=$NAGIOS{TIMET} --step=$conf{RRA_STEP}");
            if ( $? > 0 ) {
                print_log( "rrdtool create returns $?", 1 );
                @rrd_state = ( $?, "create failed" );
                $stats{error}++;
            }
            else {
                print_log( "rrdtool create returns $?", 1 );
                @rrd_state = ( 0, "just created" );
                $stats{create}++;
            }
        }
    }
    else {
        if ( $conf{USE_RRDs} == 1 ) {
            if ( $conf{RRD_DAEMON_OPTS} ) {
                print_log( "RRDs::update --daemon=$conf{RRD_DAEMON_OPTS} $rrdfile $NAGIOS{TIMET}$ds_update", 2 );
                RRDs::update( "--daemon=$conf{RRD_DAEMON_OPTS}", "$rrdfile", "$NAGIOS{TIMET}$ds_update" );
            }
            else {
                print_log( "RRDs::update $rrdfile $NAGIOS{TIMET}$ds_update", 2 );
                RRDs::update( "$rrdfile", "$NAGIOS{TIMET}$ds_update" );
            }
            my $err = RRDs::error();
            if ($err) {
                print_log( "RRDs::update ERROR $err", 1 );
                @rrd_state = ( 1, $err );
                $stats{error}++;
            }
            else {
                print_log( "$rrdfile updated", 2 );
                @rrd_state = ( 0, "successful updated" );
                $stats{update}++;
            }
        }
        else {
            print_log( "RRDs Perl Modules are not installed. Falling back to rrdtool system call.", 2 );
            if ( $conf{RRD_DAEMON_OPTS} ) {
                print_log( "$conf{RRDTOOL} update --daemon=$conf{RRD_DAEMON_OPTS} $rrdfile $NAGIOS{TIMET}$ds_update", 2 );
                system("$conf{RRDTOOL} update --daemon=$conf{RRD_DAEMON_OPTS} $rrdfile $NAGIOS{TIMET}$ds_update");
            }
            else {
                print_log( "$conf{RRDTOOL} update $rrdfile $NAGIOS{TIMET}$ds_update", 2 );
                system("$conf{RRDTOOL} update $rrdfile $NAGIOS{TIMET}$ds_update");
            }
            if ( $? > 0 ) {
                print_log( "rrdtool update returns $?", 1 );
                @rrd_state = ( $?, "update failed" );
                $stats{error}++;
            }
            else {
                print_log( "rrdtool update returns $?", 1 );
                @rrd_state = ( $?, "successful updated" );
                $stats{update}++;
            }
        }
    }
    return @rrd_state;
}

#
# Write Template
#
sub open_template {
    my $xmlfile = shift;
    $delayed_write = 0;
    if( -e $xmlfile ){
        my $mtime = (stat($xmlfile))[9];
        my $t = time();
        my $age = ($t - $mtime);
        if ( $age < $conf{'XML_UPDATE_DELAY'} ){
            print_log( "DEBUG: XML File is $age seconds old. No update needed", 3 );
            $delayed_write = 1;
            return;
        }
        print_log( "DEBUG: XML File is $age seconds old. UPDATE!", 3 );
    }
    open( XML, "> $xmlfile.$$" ) or die "Cant create temporary XML file ", $!;
    print XML "<?xml version=\"1.0\" encoding=\"" . $conf{XML_ENC} . "\" standalone=\"yes\"?>\n";
    print XML "<NAGIOS>\n";
}

#
# Close Template FH
#
sub close_template {
    return if $delayed_write == 1;
    my $xmlfile = shift;
    printf( XML "  <XML>\n" );
    printf( XML "   <VERSION>%d</VERSION>\n", $const{'XML_STRUCTURE_VERSION'} );
    printf( XML "  </XML>\n" );

    printf( XML "</NAGIOS>\n" );
    close(XML);
    rename( "$xmlfile.$$", "$xmlfile" );
}

#
# Add Lines
#
sub write_to_template {
    return if $delayed_write == 1;
    my $tag  = shift;
    my $data = shift;
    if ( !defined $data ) {
        $data = "";
    }

    $data =~ s/&/&amp;/g;
    $data =~ s/</&lt;/g;
    $data =~ s/>/&gt;/g;

    if ( $tag =~ /^TEMPLATE$/ ) {
        printf( XML "  <DATASOURCE>\n" );
        printf( XML "    <%s>%s</%s>\n", $tag, "$data", $tag );
    }
    elsif ( $tag =~ /^MAX$/ ) {
        printf( XML "    <%s>%s</%s>\n", $tag, "$data", $tag );
        printf( XML "  </DATASOURCE>\n" );
    }
    else {
        printf( XML "    <%s>%s</%s>\n", $tag, "$data", $tag );
    }
}

sub write_state_to_template {
    return if $delayed_write == 1;
    my @rrd_state = @_;
    printf( XML "  <RRD>\n" );
    printf( XML "    <RC>%s</RC>\n",   $rrd_state[0] );
    printf( XML "    <TXT>%s</TXT>\n", $rrd_state[1] );
    printf( XML "  </RRD>\n" );
}

#
# Store the complete Nagios ENV
#
sub write_env_to_template {
    return if $delayed_write == 1;
    foreach my $key ( sort keys %NAGIOS ) {
        $NAGIOS{$key} = urlencode($NAGIOS{$key});
        printf( XML "  <NAGIOS_%s>%s</NAGIOS_%s>\n", $key, $NAGIOS{$key}, $key );
    }
}

#
# Recursive Template search 
#
sub adjust_template {
    my $command           = shift;
    my $uom               = shift;
    my $count             = shift;
    my @temp_template = split /\!/, $command;
    my $initial_template = cleanup( $temp_template[0] );
    my $template = cleanup( $temp_template[0] );
    my %CTPL = read_custom_template ( $command, $uom, $count );
    # 
    if ( $CTPL{'TEMPLATE'} ne $initial_template ){
        %CTPL = read_custom_template ( $CTPL{'TEMPLATE'}, $uom, $count );
    }
    return %CTPL;
}


#
# Analyse check_command to find PNP Template .
#
sub read_custom_template {
    my $command           = shift;
    my $uom               = shift;
    my $count             = shift;
    my @dstype_list       = ();
    my $use_min_on_create = 0;
    my $use_max_on_create = 0;
    my $rrd_storage_type  = $conf{'RRD_STORAGE_TYPE'};
    my $rrd_heartbeat     = $conf{'RRD_HEARTBEAT'};

    if ( defined($conf{'UOM2TYPE'}{$uom}) ) {
        $dstype = $conf{'UOM2TYPE'}{$uom};
        print_log( "DEBUG: DSTYPE adjusted to $dstype by UOM", 3 );
    }else {
        $dstype = 'GAUGE'; 
    }

    print_log( "DEBUG: RAW Command -> $command", 3 );
    my @temp_template = split /\!/, $command;
    my $template = cleanup( $temp_template[0] );
    $template = trim($template);
    my $template_cfg = "$conf{CFG_DIR}/check_commands/$template.cfg";
    if ( -e $template_cfg ) {
        print_log( "DEBUG: adjust_template() => $template_cfg", 3 );
        my $initial_dstype = $dstype;
        open FH, "<", $template_cfg;
        while (<FH>) {
            next if /^#/;
            next if /^$/;
            s/#.*//;
            s/ //g;
            if (/^(.*)=(.*)$/) {
                if ( $1 eq "DATATYPE" ) {
                    $dstype = uc($2);
                    $dstype =~ s/ //g;
                    @dstype_list = split /,/, $dstype;
                    if ( exists $dstype_list[$count] && $dstype_list[$count] =~ /^(COUNTER|GAUGE|ABSOLUTE|DERIVE)$/ ) {
                        $dstype = $dstype_list[$count];
                        print_log( "Adapting RRD Datatype to \"$dstype\" as defined in $template_cfg with key $count", 2 );
                    }
                    elsif ( $dstype =~ /^(COUNTER|GAUGE|ABSOLUTE|DERIVE)$/ ) {
                        print_log( "Adapting RRD Datatype to \"$dstype\" as defined in $template_cfg", 2 );
                    }
                    else {
                        print_log( "RRD Datatype \"$dstype\" defined in $template_cfg is invalid", 2 );
                        $dstype = $initial_dstype;
                    }

                }
                if ( $1 eq "CUSTOM_TEMPLATE" ) {
                    print_log( "Adapting Template using ARG $2", 2 );
                    my $i = 1;
                    my @keys = split /,/, $2;
                    foreach my $keys (@keys) {
                        if ( $i == 1 && exists $temp_template[$keys] ) {
                            $template = trim( $temp_template[$keys] );
                            print_log( "Adapting Template to $template.php (added ARG$keys)", 2 );
                        }elsif( exists $temp_template[$keys] ){
                            $template .= "_" . trim( $temp_template[$keys] );
                            print_log( "Adapting Template to $template.php (added ARG$keys)", 2 );
                        }
                        $i++;
                    }
                    print_log( "Adapting Template to $template.php as defined in $template_cfg", 2 );
                }
                if ( $1 eq "USE_MIN_ON_CREATE" && $2 eq "1" ) {
                    $use_min_on_create = 1;
                }
                if ( $1 eq "USE_MAX_ON_CREATE" && $2 eq "1" ) {
                    $use_max_on_create = 1;
                }
                if ( $1 eq "RRD_STORAGE_TYPE" && uc($2) eq "MULTIPLE" ) {
                    $rrd_storage_type = uc($2);
                }
                if ( $1 eq "RRD_HEARTBEAT" ) {
                    $rrd_heartbeat = $2;
                }
            }
        }
        close FH;
    }
    else {
        print_log( "No Custom Template found for $template ($template_cfg) ", 2 );
        print_log( "RRD Datatype is $dstype",                                 3 );
    }
    print_log( "Template is $template.php", 2 );
    my %CTPL = (
        TEMPLATE          => $template,
        DSTYPE            => $dstype,
        RRD_STORAGE_TYPE  => $rrd_storage_type,
        RRD_HEARTBEAT     => $rrd_heartbeat,
        USE_MIN_ON_CREATE => $use_min_on_create,
        USE_MAX_ON_CREATE => $use_max_on_create,
    );
    return %CTPL;
}

#
# Parse process_perfdata.cfg
#
sub parse_config {
    my $config_file = shift;
    my $line        = 0;

    if ( -e $config_file ) {
        open CFG, '<', "$config_file";
        while (<CFG>) {
            $line++;
            chomp;
            s/ //g;
            next if /^#/;
            next if /^$/;
            s/#.*//;

            if (/^(.*)=(.*)$/) {
                if ( defined $conf{$1} ) {
                    $conf{$1} = $2;
                }
            }
        }
        close CFG;
        print_log( "Using Config File $config_file parameters", 2 );
    }
    else {
        print_log( "Config File $config_file not found, using defaults", 2 );
    }
}

#
# Parse rra.cfg
#
sub parse_rra_config {
    my $template     = shift;
    my $rra_template = "";
    if ( -e $conf{'CFG_DIR'} . "/" . $template . ".rra.cfg" ) {
        $rra_template = $conf{'CFG_DIR'} . "/" . $template . ".rra.cfg";
        print_log( "Reading $rra_template", 2 );
    }
    elsif ( -e $conf{'RRA_CFG'} ) {
        $rra_template = $conf{'RRA_CFG'};
        print_log( "Reading $conf{'RRA_CFG'}", 2 );
    }
    else {
        print_log( "No rra.cfg found. Using default values.", 2 );
    }

    if ( $rra_template ne "" ) {
        @rrd_create = ();
        open RRA, "<", $rra_template;
        while (<RRA>) {
            next if /^#/;
            next if /^$/;
            s/#.*//;
            if(/^RRA_STEP=(\d+)/i){
                $conf{'RRA_STEP'} = $1;
                next;
            }
            chomp;
            push @rrd_create, "$_";
        }
        close RRA;
    }
    else {
        @rrd_create = @default_rrd_create;
    }
    return @rrd_create;

}

#
# Function adapted from Nagios::Plugin::Performance
# Thanks to Gavin Carr and Ton Voon
#

sub _parse {
    # Nagios::Plugin::Performance
    my $string     = shift;
    my $tmp_string = $string;
    $string =~ s/^([^=]+)=([\d\.\-]+)([\w\/%]*);?([\d\.\-:~@]+)?;?([\d\.\-:~@]+)?;?([\d\.\-]+)?;?([\d\.\-]+)?;?\s*//;

    if ( $tmp_string eq $string ) {
        print_log( "No pattern match in function _parse($string)", 2 );
        return undef;
    }

    return undef unless ( ( defined $1 && $1 ne "" ) && ( defined $2 && $2 ne "" ) );

    # create hash from all performance data values

    my %p = (
        "label"    => $1,
        "name"     => $1,
        "value"    => $2,
        "uom"      => $3,
        "warning"  => $4,
        "critical" => $5,
        "min"      => $6,
        "max"      => $7
    );
    
    $p{label}  =~ s/[']//g;        # cleanup
    $p{name}   =~ s/[']//g;        # cleanup
    $p{name}   =~ s/[\/\\]/_/g;    # cleanup
    $p{name}   = cleanup($p{name});

    if ( $p{uom} eq "%" ) {
        $p{uom} = "%%";
        print_log( "DEBUG: UOM adjust = $p{uom}", 3 );
    }

    #
    # Check for warning and critical ranges
    #
    if ( $p{warning} && $p{warning} =~ /^([\d\.\-~@]+)?:([\d\.\-~@]+)?$/ ) {
        print_log( "DEBUG: Processing warning ranges ( $p{warning} )", 3 );
        $p{warning_min} = $1;
        $p{warning_max} = $2;
        delete( $p{warning} );
        if ( $p{warning_min} =~ /^@/ ) {
            $p{warning_min} =~ s/@//;
            $p{warning_range_type} = "inside";
        }
        else {
            $p{warning_range_type} = "outside";
        }
    }
    if ( $p{critical} && $p{critical} =~ /^([\d\.\-~@]+)?:([\d\.\-~@]+)?$/ ) {
        print_log( "DEBUG: Processing critical ranges ( $p{critical} )", 3 );
        $p{critical_min} = $1;
        $p{critical_max} = $2;
        delete( $p{critical} );
        if ( $p{critical_min} =~ /^@/ ) {
            $p{critical_min} =~ s/@//;
            $p{critical_range_type} = "inside";
        }
        else {
            $p{critical_range_type} = "outside";
        }
    }
    # Strip Range indicators
    $p{warning}  =~ s/[~@]// if($p{warning});
    $p{critical} =~ s/[~@]// if($p{critical});

    return ( $string, %p );
}

#
# clean Strings
#
sub cleanup {
    my $string = shift;
    if ($string) {
        $string =~ s/[ :\/\\]/_/g;
    }
    return $string;
}

#
# Urlencode 
#
sub urlencode {
    my $string = shift;
    if ($string) {
        $string =~ s/([<>&])/sprintf("%%%02x",ord($1))/eg;        # URLencode;
    }
    return $string;
}

#
# Trim leading whitespaces
#
sub trim {
    my $string = shift;
    $string =~ s/^\s*//g;
    return $string;
}

#
# Parse the Performance String and call data2rrd()
#
sub parse_perfstring {

    #
    # Default RRD Datatype
    # Value will be overwritten by adjust_template()
    #
    my %CTPL = ();
    $dstype = "GAUGE";
    my $perfstring = shift;
    my $is_multi = "0";
    my @perfs;
    my @multi;
    my %p;
    my $use_min_on_create = 0;
    my $use_max_on_create = 0;

    #
    # check_multi
    #
    if ( $perfstring =~ /^([a-zA-Z0-9\.\-_\s]+)::([a-zA-Z0-9\.\-_\s]+)::([^=]+)=/ ) {
        $is_multi = 1;
        print_log( "check_multi Perfdata start", 3 );
        my $count        = 0;
        my $check_multi_blockcount = 0;
        my $multi_parent     = cleanup( $NAGIOS{SERVICEDESC} );
        my $auth_servicedesc = $NAGIOS{DISP_SERVICEDESC};
        while ($perfstring) {
            ( $perfstring, %p ) = _parse($perfstring);
            if ( !$p{label} ) {
                print_log( "Invalid Perfdata detected ", 1 );
                $stats{invalid}++;
                @perfs = ();
                last;
            }
            if ( $p{label} =~ /^([a-zA-Z0-9\.\-_\s]+)::([a-zA-Z0-9\.\-_\s]+)::([^=]+)$/ ) {
                @multi = ( $1, $2, $3 );
                if ( $count == 0 ) {
                    print_log( "DEBUG: First check_multi block", 3 );

                    # Keep servicedesc while processing the first block.
                    $p{servicedesc}      = cleanup( $NAGIOS{SERVICEDESC} );
                    $p{disp_servicedesc} = $NAGIOS{DISP_SERVICEDESC};
                    $p{auth_servicedesc} = $auth_servicedesc;
                    $p{multi}            = 1;
                    $p{multi_parent}     = $multi_parent;
                }
                else {
                    print_log( "DEBUG: A new check_multi block ($count) starts", 3 );
                    $p{servicedesc}      = cleanup( $multi[0] );    # Use the multi servicedesc.
                    $p{multi}            = 2;
                    $p{multi_parent}     = $multi_parent;
                    $p{servicedesc}      = cleanup( $multi[0] );    # Use the multi servicedesc.
                    $p{disp_servicedesc} = cleanup( $multi[0] );    # Use the multi servicedesc.
                    $p{auth_servicedesc} = $auth_servicedesc;
                    data2rrd(@perfs) if ( $#perfs >= 0 );           # Process when a new block starts.
                    @perfs = ();                                    # Clear the perfs array.
                    # reset check_multi block count
                    $check_multi_blockcount = 0;
                }
                %CTPL = adjust_template( $multi[1], $p{uom}, $check_multi_blockcount++ );

                if ( $CTPL{'USE_MAX_ON_CREATE'} == 1 && defined $p{max} ) {
                    $p{rrd_max} = $p{max};
                } 
		else {
                    $p{rrd_max} = "U";
                }
                if ( $CTPL{'USE_MIN_ON_CREATE'} == 1 && defined $p{min} ) {
                    $p{rrd_min} = $p{min};
                }
		elsif( $CTPL{'DSTYPE'} eq 'DERIVE' ){
		    $p{rrd_min} = 0; # Add minimum value 0 if DSTYPE = DERIVE
                } 
		else {
                    $p{rrd_min} = "U";
                }
                $p{template}         = $CTPL{'TEMPLATE'};
                $p{dstype}           = $CTPL{'DSTYPE'};
                $p{rrd_storage_type} = $CTPL{'RRD_STORAGE_TYPE'};
                $p{rrd_heartbeat}    = $CTPL{'RRD_HEARTBEAT'};
                $p{label}            = cleanup( $multi[2] );           # store the original label from check_multi header
                $p{name}             = cleanup( $multi[2] );           # store the original label from check_multi header
                $p{hostname}         = cleanup( $NAGIOS{HOSTNAME} );
                $p{disp_hostname}    = $NAGIOS{DISP_HOSTNAME};
                $p{auth_hostname}    = $NAGIOS{HOSTNAME};
                push @perfs, {%p};
                $count++;
            }
            else {
                print_log( "DEBUG: Next check_multi data for block $count multiblock $check_multi_blockcount", 3 );

                # additional check_multi data
                %CTPL = adjust_template( $multi[1], $p{uom}, $check_multi_blockcount++ );

                if ( $CTPL{'USE_MAX_ON_CREATE'} == 1 && defined $p{max} ) {
                    $p{rrd_max} = $p{max};
                }
                else {
                    $p{rrd_max} = "U";
                }
                if ( $CTPL{'USE_MIN_ON_CREATE'} == 1 && defined $p{min} ) {
                    $p{rrd_min} = $p{min};
                }
		elsif( $CTPL{'DSTYPE'} eq 'DERIVE' ){
		    $p{rrd_min} = 0; # Add minimum value 0 if DSTYPE = DERIVE
                } 
                else {
                    $p{rrd_min} = "U";
                }
                $p{template}         = $CTPL{'TEMPLATE'};
                $p{dstype}           = $CTPL{'DSTYPE'};
                $p{rrd_storage_type} = $CTPL{'RRD_STORAGE_TYPE'};
                $p{rrd_heartbeat}    = $CTPL{'RRD_HEARTBEAT'};
                $p{hostname}         = cleanup( $NAGIOS{HOSTNAME} );
                $p{disp_hostname}    = $NAGIOS{DISP_HOSTNAME};
                $p{auth_hostname}    = $NAGIOS{HOSTNAME};
                if ( $count == 1 ) {
                    $p{servicedesc}      = cleanup( $NAGIOS{SERVICEDESC} );    # Use the servicedesc.
                    $p{disp_servicedesc} = $NAGIOS{DISP_SERVICEDESC};          # Use the servicedesc.
                }
                else {
                    $p{servicedesc}      = cleanup( $multi[0] );               # Use the multi servicedesc.
                    $p{disp_servicedesc} = $multi[0];                          # Use the multi servicedesc.
                }
                $p{multi}        = $is_multi;
                $p{multi_parent} = $multi_parent;
                $p{auth_servicedesc} = $auth_servicedesc;          # Use the servicedesc.
                push @perfs, {%p};
            }
        }
        data2rrd(@perfs) if ( $#perfs >= 0 );
        @perfs = ();
    }
    else {

        #
        # Normal Performance Data
        #
        print_log( "DEBUG: Normal perfdata", 3 );
        my $count = 0;
        while ($perfstring) {
            ( $perfstring, %p ) = _parse($perfstring);
            if ( !$p{label} ) {
                print_log( "Invalid Perfdata detected ", 1 );
                @perfs = ();
                last;
            }
            %CTPL = adjust_template( $NAGIOS{CHECK_COMMAND}, $p{uom}, $count );

            if ( $CTPL{'USE_MAX_ON_CREATE'} == 1 && defined $p{max} ) {
                $p{rrd_max} = $p{max};
            }
            else {
                $p{rrd_max} = "U";
            }
            if ( $CTPL{'USE_MIN_ON_CREATE'} == 1 && defined $p{min} ) {
                $p{rrd_min} = $p{min};
            }
	    elsif( $CTPL{'DSTYPE'} eq 'DERIVE' ){
		$p{rrd_min} = 0; # Add minimum value 0 if DSTYPE = DERIVE
	    } 
            else {
                $p{rrd_min} = "U";
            }

            $p{template}         = $CTPL{'TEMPLATE'};
            $p{dstype}           = $CTPL{'DSTYPE'};
            $p{rrd_storage_type} = $CTPL{'RRD_STORAGE_TYPE'};
            $p{rrd_heartbeat}    = $CTPL{'RRD_HEARTBEAT'};
            $p{multi}            = $is_multi;
            $p{hostname}         = cleanup( $NAGIOS{HOSTNAME} );
            $p{disp_hostname}    = $NAGIOS{DISP_HOSTNAME};
            $p{auth_hostname}    = $NAGIOS{DISP_HOSTNAME};
            $p{servicedesc}      = cleanup( $NAGIOS{SERVICEDESC} );
            $p{disp_servicedesc} = $NAGIOS{DISP_SERVICEDESC};
            $p{auth_servicedesc} = $NAGIOS{DISP_SERVICEDESC};

            push @perfs, {%p};
            $count++;
        }
        data2rrd(@perfs) if ( $#perfs >= 0 );
        @perfs = ();
    }
}

#
# Write to Logfile
#
sub print_log {
    my $out      = shift;
    my $severity = shift;
    if ( $severity <= $conf{LOG_LEVEL} ) {
        open( LOG, ">>" . $conf{LOG_FILE} ) || die "Can't open logfile ($conf{LOG_FILE}) ", $!;
        if ( -s LOG > $conf{LOG_FILE_MAX_SIZE} ) {
            truncate( LOG, 0 );
            printf( LOG "File truncated" );
        }
        my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(time);
        printf( LOG "%02d-%02d-%02d %02d:%02d:%02d [%d] [%d] %s\n", $year + 1900, $mon + 1, $mday, $hour, $min, $sec, $$, $severity, $out );
        close(LOG);
    }
}

#
# Signals and Handlers
#
sub init_signals {
    $SIG{'INT'}  = \&handle_signal;
    $SIG{'QUIT'} = \&handle_signal;
    $SIG{'ALRM'} = \&handle_signal;
    $SIG{'ILL'}  = \&handle_signal;
    $SIG{'ABRT'} = \&handle_signal;
    $SIG{'FPE'}  = \&handle_signal;
    $SIG{'SEGV'} = \&handle_signal;
    $SIG{'TERM'} = \&handle_signal;
    $SIG{'BUS'}  = \&handle_signal;
    $SIG{'SYS'}  = \&handle_signal;
    $SIG{'XCPU'} = \&handle_signal;
    $SIG{'XFSZ'} = \&handle_signal;
    $SIG{'IOT'}  = \&handle_signal;
    $SIG{'PIPE'} = \&handle_signal;
    $SIG{'HUP'}  = \&handle_signal;
}

#
# Handle Signals
#
sub handle_signal {
    my ($signal) = (@_);
    if ( $signal eq "ALRM" ) {
        print_log( "*** TIMEOUT: Timeout after $opt_t secs. ***", 0 );
        if ( $opt_b && !$opt_n ) {
            print_log( "*** TIMEOUT: Deleting current file to avoid loops",   0 );
            print_log( "*** TIMEOUT: Please check your process_perfdata.cfg", 0 );
        }
        elsif ( $opt_b && $opt_n ) {
            print_log( "*** TIMEOUT: Deleting current file to avoid NPCD loops", 0 );
            print_log( "*** TIMEOUT: Please check your npcd.cfg",                0 );
        }
    }
    if ($opt_b) {
        my $pdfile = "$opt_b" . "-PID-" . $$;
        if ( unlink("$pdfile") == 1 ) {
            print_log( "*** TIMEOUT: $pdfile deleted", 0 );
        }
        else {
            print_log( "*** TIMEOUT: Could not delete $pdfile:$!", 0 );
        }
    }
    my $temp_file = "$conf{RRDPATH}/$NAGIOS{HOSTNAME}/$NAGIOS{SERVICEDESC}.xml.$$";
    if ( -f $temp_file ) {
        unlink($temp_file);
    }
    print_log( "*** Timeout while processing Host: \"$NAGIOS{HOSTNAME}\" Service: \"$NAGIOS{SERVICEDESC}\"", 0 );
    print_log( "*** process_perfdata.pl terminated on signal $signal", 0 );
    $t1 = [gettimeofday];
    $rt = tv_interval $t0, $t1;
    $stats{runtime} = $rt;
    exit 7;
}
#
# Store some internal runtime infos
# 
sub store_internals {
    if( ! -w $conf{'STATS_DIR'}){
        print_log("*** ERROR: ".$conf{'STATS_DIR'}." is not writable or does not exist",0);
        return;
    }
    my $statsfile = $conf{'STATS_DIR'}."/".(int $stats{timet} / 60);
    open( STAT, ">> $statsfile" ) or die "Cant create statistic file ", $!;
    printf(STAT "%d %f %d %d %d %d %d %d\n", $stats{timet},$stats{runtime},$stats{rows},$stats{update},$stats{create},$stats{error},$stats{invalid},$stats{skipped}); 
    close(STAT);
    check_internals();
}

#
# Search for statistic files
#
sub check_internals {
    my $file;
    my @files;
    opendir(STATS, $conf{'STATS_DIR'});
    while ( defined ( my $file = readdir STATS) ){
        next if $file =~ /^\.\.?$/; # skip . and ..
        next if $file =~ /-PID-/;   # skip temporary files 
        next if $file == (int $stats{timet} / 60); # skip our current file
        push @files, $file;
    }
    read_internals(@files);
}

#
# Read and aggregate files found by check_internals() 
#
sub read_internals {
    my @files = @_;
    my @chunks;
    foreach my $file (sort { $a <=> $b} @files){
        unless ( rename($conf{'STATS_DIR'}."/".$file, $conf{'STATS_DIR'}."/".$file."-PID-".$$) ){
            print_log( "ERROR: renaming stats file " . $conf{'STATS_DIR'}."/".$file . " to " . $conf{'STATS_DIR'}."/".$file."-PID-".$$ . " failed", 1 );
            next;
        }
        open( STAT, "< ".$conf{'STATS_DIR'}."/".$file."-PID-".$$ );
        %stats = (
            timet       => 0,
            error       => 0,
            invalid     => 0,
            skipped     => 0,
            runtime     => 0,
            rows        => 0,
            create      => 0,
            update      => 0,
        );
        while(<STAT>){
            @chunks = split();
            $stats{timet}    = $chunks[0];
            $stats{runtime} += $chunks[1];
            $stats{rows}    += $chunks[2]; 
            $stats{update}  += $chunks[3];
            $stats{create}  += $chunks[4];
            $stats{error}   += $chunks[5];
            $stats{invalid} += $chunks[6];
            $stats{skipped} += $chunks[7];
        }
        close(STAT);
        unlink($conf{'STATS_DIR'}."/".$file."-PID-".$$);
        process_internals();
    }
}
#
# 
#
sub process_internals {
    $conf{'RRD_STORAGE_TYPE'} = "MULTIPLE";
    %NAGIOS = ( 
        HOSTNAME => '.pnp-internal',
        DISP_HOSTNAME => 'pnp-internal',
        SERVICEDESC => 'runtime',
        DISP_SERVICEDESC => 'runtime',
        TIMET => $stats{timet},
        DATATYPE => 'SERVICEPERFDATA',
        CHECK_COMMAND => 'pnp-runtime',
        PERFDATA => "runtime=".$stats{runtime}."s rows=".$stats{rows}." errors=".$stats{error}." invalid=".$stats{invalid}." skipped=".$stats{skipped} ." update=".$stats{update}. " create=".$stats{create} 
    );
    parse_perfstring(  $NAGIOS{PERFDATA} );
}

#
#
#
sub print_help {
    print "Copyright (c) 2005-2010 Joerg Linge <pitchfork\@pnp4nagios.org>\n";
    print "Use process_perfdata.pl to store Nagios Plugin Performance Data into RRD Databases\n";
    print "\n";
    print "Options:\n";
    print "  -h, --help\n";
    print "     Print detailed help screen\n";
    print "  -V, --version\n";
    print "     Print version information\n";
    print "  -t, --timeout=INTEGER\n";
    print "     Seconds before process_perfdata.pl times out (default: $opt_t)\n";
    print "  -i, --inetd\n";
    print "     Use this Option if process_perfdata.pl is executed by inetd/xinetd.\n";
    print "  -d, --datatype\n";
    print "     Defaults to \"SERVICEPERFDATA\". Use \"HOSTPERFDATA\" to process Perfdata from regular Host Checks\n";
    print "     Only used in default or inetd Mode\n";
    print "  -b, --bulk\n";
    print "     Provide a file for bulk update\n";
    print "  -c, --config\n";
    print "     Optional process_perfdata config file\n";
    print "     Default: /usr/local/nagios/etc/pnp/process_perfdata.cfg \n";
    exit 0;
}

#
#
#
sub print_version {
    print "Version: process_perfdata.pl $const{VERSION}\n";
    print "Copyright (c) 2005-2010 Joerg Linge <pitchfork\@pnp4nagios.org>\n";
    exit 0;
}

