#!/usr/bin/perl -w
END { print STDERR "Service exit unexpected. Sleeping 30 seconds.\n"; sleep 30; }
use DBI;
use Net::SNMP;

$0 = "bit-mycheckd";
$|++;

my $conffile = $ARGV[0] || "/etc/bit-mycheckd.conf";
my $conf = {};

my $state = {
    'locked'       => 0,
    'locked_since' => 0,
};

snmptrap("bit-mycheckd startup.");

while (1) {
    $conf = readConfig($conffile);


    my $dbh = DBI->connect("DBI:mysql:database=$$conf{mysql_db};host=$$conf{mysql_server}", $$conf{mysql_login}, $$conf{mysql_pass}, { RaiseError => 0, PrintError => 0 }) or die "Can't connect to MySQL: $DBI::errstr ($!)\n";
    $sth = $dbh->prepare("SHOW SLAVE STATUS") or die "Can't prepare: $DBI::errstr ($!)\n";
    $sth->execute() or die "Can't execute: $DBI::errstr ($!)\n";
    my $slave_state = $sth->fetchrow_hashref() or die "Can't fetchrow: $DBI::errstr ($!)\n";
    $sth->finish();

    my $sql_r = $$slave_state{Slave_SQL_Running};
    my $io_r  = $$slave_state{Slave_IO_Running};
    my $db_t  = ($dbh->selectrow_array($$conf{database_check}) || 0);
    
    $dbh->disconnect();


    if (not defined $db_t or $db_t eq "0" or $db_t eq "") {
        dprint("Database Check query failed, returned undefined, zero or empty result.", 1);
        BlockThatShit();

    } else {
        dprint("Database Check query was OK, so we got that going!");

    }


    if ($sql_r ne "Yes") {
        dprint("Slave SQL is NOT running.", 0);

        $$state{sql_r} = time if not defined $$state{sql_r};
        my $now = time; my $delta = $now - $$state{sql_r};
        if ($delta >= $$conf{close_delay}) {
            dprint("Slave SQL has not been running for $delta seconds.", 1);
            BlockThatShit();

        } else {
            dprint("Slave SQL is not running for $delta seconds. Waiting to get to $$conf{close_delay}.", 1);

        }

    } else {
        dprint("Slave SQL is running! So that looks nice.");

    }


    if ($io_r ne "Yes") {
        dprint("Slave IO is NOT running.", 0);

        $$state{io_r} = time if not defined $$state{io_r};
        my $now = time; my $delta = $now - $$state{io_r};
        if ($delta >= $$conf{close_delay}) {
            dprint("Slave IO has not been running for $delta seconds.", 1);
            BlockThatShit();

        } else {
            dprint("Slave IO is not running for $delta seconds. Waiting to get to $$conf{close_delay}.", 1);

        }

    } else {
        dprint("Slave IO is running! So no trouble there it seems.");

    }

    if (($sql_r eq "Yes" and $io_r eq "Yes") and (defined $db_t and $db_t ne "0" and $db_t ne "")) {
        UnblockThatShit();
        sleep $$conf{norm_loop_interval}; # all green
    } else {
        sleep $$conf{hyst_loop_interval}; # SNAFU
    }

}


sub UnblockThatShit {
    if ($$state{locked} != 1) {
        dprint("No unblock necessary. Not blocked.");
        return;
    }

    my $now = time;
    my $delta = $now - $$state{locked_since};
    dprint("Was blocked for $delta seconds! Unblocking...", 1);
    
    $$state{locked} = 0;
    $$state{locked_since} = 0;

    system($$conf{open_ipv4}) == 0 or dprint("Could not open IPv4: $!", 1);
    system($$conf{open_ipv6}) == 0 or dprint("Could not open IPv6: $!", 1);
}


sub BlockThatShit {
    if ($$state{locked} == 1) {
        dprint("Firewall already active. Or it should be.");
        return;
    }

    dprint("FIREWALL ACTIVATED!", 1);
    $$state{locked} = 1;
    $$state{locked_since} = time;

    system($$conf{close_ipv4}) == 0 or dprint("Could not close IPv4: $!", 1);
    system($$conf{close_ipv6}) == 0 or dprint("Could not close IPv6: $!", 1);
}


sub readConfig {
    my ($file) = @_;
    die "Can't read config: '$file': $!\n"
        if (not open(FD, "<$file"));
    my $blob = "";
    { local $/ = undef; $blob = <FD>; close FD; }
    return eval 'my ' . $blob;
}


sub dprint {
    my ($msg, $lvl) = @_;
    defined $lvl or $lvl = 0;

    $msg =~ s#[\r\n]*$#\n#;
    my @l = localtime(time()); $l[5]+=1900; $l[4]++;
    my $tstr = sprintf("%04d-%02d-%02dT%02d:%02d:%02d %s", $l[5], $l[4], $l[3], $l[2], $l[1], $l[0], $msg);
    print STDERR $tstr;

    snmptrap($msg) if ($lvl > 0);
}


sub snmptrap {
    my ($msg) = @_;

    my $snmp_target = '172.16.0.252';
    # https://wiki.bit.nl/wiki/index.php/SNMP_Extensions
    #
    # XXX TODO - eigen 'subtree' voor mycheck-fliep gebruiken?
    #            wellicht zelfs per platform?
    # vereenvoudigt het kunnen 'routen' van die traps in itsatarp.
    #
    my $enterprise  = '1.3.6.1.4.1.8072.9999.9999.12859.0.0';
    my $msgOid      = '1.3.6.1.4.1.8072.9999.9999.12859.0.1.1';
    my $priOid      = '1.3.6.1.4.1.8072.9999.9999.12859.0.1.2';
    my $msgTxt      = $msg;
    my $priVal      = 5;

    my ($sess, $err) = Net::SNMP->session(-hostname => $snmp_target, -port => 162, -version => 1);
    return dprint("Error connecting to SNMP-trap target " . $snmp_target . ": ". $err) if not defined $sess;

    my $result = $sess->trap(
        -enterprise => $enterprise,
        -specifictrap => 1,
        -varbindlist => [ $msgOid, OCTET_STRING, $msgTxt,
                          $priOid, OCTET_STRING, $priVal ],
    );

    return dprint("Error sending trap to SNMP-trap target " . $snmp_target . ": " . $sess->error()) if not $result;
}
