#!/usr/local/bin/perl

#
# pi, Tue Mar 12 22:08:07 CET 2013
# check_quagga_bgpd -- status of the quagga bgpd and the BGP4 peers
#
# tech/tcpip/routing/quagga/nagios-check
#

use warnings;
use strict;

use Net::Telnet ();
use Config::Tiny;

# global variables
my($prompt, $hostname, $password, $enable);
my($interval) = 300;
my($statefn) = '';
my($debugdir) = '';
my($peercnt);
my($peerconfcnt) = 0;
my(%ip2as);
my(%peer);
my(%peerprio);
my(%peerlvl);
my($res);
my($failres) = '';
my($saveres) = '';
my($myas) = '';
my($alert);

# variables
my($pre, $mat);
my(@pre);
my($t);
my($i);
my($r);
my($err);

# init
&readconf;
#print "prompt: $prompt hostname: $hostname pw: $password ena: $enable\n";

$t = new Net::Telnet (
    Timeout => 1,
    Binmode => 1,
    Port => 2605,
    Telnetmode => 0,
    Errmode => "return",
);
if ( $debugdir ne '' ) {
    $t->output_log($debugdir.'out.log');
    $t->input_log ($debugdir.'in.log');
    $t->dump_log  ($debugdir.'dump.log');
}
$r = $t->open($hostname);
if ( !defined($r) ) {
    print "BGP CRITICAL - no-connection\n";
    exit(2);
}

$t->waitfor(String => "Password: ");
$err = $t->errmsg;
if ( $err ne '' ) {
    print "BGP CRITICAL - waitforpw: $err\n";
    exit(2);
};
$t->print($password);
$t->waitfor(String => "$prompt> ");
$err = $t->errmsg;
if ( $err ne '' ) {
    print "BGP CRITICAL - waitforprompt1: $err\n";
    exit(2);
};
$t->print("enable");
$t->waitfor(String => "Password: ");
$err = $t->errmsg;
if ( $err ne '' ) {
    print "BGP CRITICAL - waitforprompt2: $err\n";
    exit(2);
};
$t->prompt('/'.$prompt.'# /');
$t->cmd($enable);
$err = $t->errmsg;
if ( $err ne '' ) {
    print "BGP CRITICAL - waitforprompt3: $err\n";
    exit(2);
};
$t->cmd('terminal length 0');
$err = $t->errmsg;
if ( $err ne '' ) {
    print "BGP CRITICAL - waitforterm: $err\n";
    exit(2);
};

# zeroth, find the peers in the running config
@pre = $t->cmd('show running-config');
chop(@pre);
chop(@pre);
$res = &parseconf(@pre);
if ( $res ne '' ) {
    print "BGP CRITICAL - $res | -\n"; 
    exit(2);
}

# first, the number of peers
@pre = $t->cmd('show bgp mem');
chop(@pre);
chop(@pre);
$res = &parsemem(@pre);

# second, IPv4
@pre = $t->cmd('show ip bgp summary');
chop(@pre);
chop(@pre);
$res .= &parsebgpsum(@pre);
# print "res: $res\n";

# third, IPv6
@pre = $t->cmd('show ipv6 bgp summary');
chop(@pre);
chop(@pre);
$res .= &parsebgpsum(@pre);
# print "res: $res\n";

$t->print("quit");

# Q: how do we detect crit or warn ?
# A: one or more of
#    crit=<peername>
#    warn=<peername>
#    in the results

# do we have some alert ?
if ( $failres =~ /crit=/ || $peercnt != $peerconfcnt ) {
    $alert='CRITICAL';
}
elsif ( $failres =~ /warn=/ ) {
    $alert='WARNING';
}
else {
    $alert='OK';
}

if ( $statefn ne '' ) {
    my($t) = open(OUT,'>'.$statefn);
    if ( defined($t) ) {
	print OUT "BGPRT $alert - $failres | $saveres\n";
	close(OUT);
    }
    else {
	$failres .= "error writing '$statefn': $!"
    }
}

if ( $failres ne '' ) {
    print "BGP $alert - $failres | $res\n";
}
else {
    print "BGP $alert | $res\n";
}

if ( $alert eq 'CRITICAL' ) {
    exit(2);
}
elsif ( $alert eq 'WARNING' ) {
    exit(1);
}
exit 0;

#####################################################################

# read the config file, prepare the peer hash
sub readconf {
    my $Config = Config::Tiny->new();
    my($t);

    $Config = Config::Tiny->read( '/usr/local/etc/bmon.conf' );
    if ( !defined($Config) ) {
	print "BGP CRITICAL | Config".Config::Tiny->errstr."\n";
	exit(2);
    }

    $t=$Config->{_}->{prompt};
    if ( defined($t) ) {
        $prompt = $t;
    }
    else {
	print "BGP CRITICAL | prompt-missing-in-configfile\n";
	exit(2);
    }

    $t=$Config->{_}->{hostname};
    if ( defined($t) ) {
        $hostname = $t;
    }
    else {
	print "BGP CRITICAL | hostname-missing-in-configfile\n";
	exit(2);
    }

    $t=$Config->{_}->{pw};
    if ( defined($t) ) {
        $password = $t;
    }
    else {
	print "BGP CRITICAL | password-missing-in-configfile\n";
	exit(2);
    }

    $t=$Config->{_}->{enable};
    if ( defined($t) ) {
        $enable = $t;
    }
    else {
	print "BGP CRITICAL | enable-missing-in-configfile\n";
	exit(2);
    }

    # $interval (seconds) or greater
    $t=$Config->{_}->{interval};
    if ( defined($t) && $t > $interval ) {
        $interval = $t;
    }

    # optional
    $t=$Config->{_}->{statefile};
    if ( defined($t) ) {
        $statefn = $t;
    }

    # optional
    $t=$Config->{_}->{debugdir};
    if ( defined($t) ) {
        $debugdir = $t;
    }

}

#####################################################################

sub parseconf {
    my(@pre)= @_;

    my($ip, $as, $peer, $prio, $ipas);
    my(@t);

    foreach $i (0..$#pre) {
	# print "$i: $pre[$i]\n";

	# find my own AS
	if ( $pre[$i] =~ /^router bgp \d+$/ ) {
	    if ( $myas eq '' ) {
		( $myas ) = ( $pre[$i] =~ /^router bgp (\d+)$/ );
	    }
	    else {
		return "parseconf: found second bgp router in line $i";
	    }
	    next;
	}

	# find peers
	if ( $pre[$i] =~ /^ neighbor [0-9a-f\.:]+ remote-as \d+$/ ) {
	    ( $ip, $as ) = ( $pre[$i] =~
		/^ neighbor ([0-9a-f\.:]+) remote-as (\d+)$/
	    );
	    if ( !defined($ip) || !defined($as) ) {
		return "parseconf: did not find IP and AS in line $i";
	    }
	    if ( defined($ip2as{$ip}) ) {
		return "parseconf: found IP and AS a second time in line $i";
	    }
	    else {
		$ip2as{$ip} = $as;
		# preset some values, if no description is found
		$ipas = $ip.' '.$ip2as{$ip};
		$peer{$ipas} = 'unknown'.$peerconfcnt;
		$peerlvl{$ipas} = $peerconfcnt;
		$peerprio{$ipas} = 'crit';
		$peerconfcnt++;
	    }
	    next;
	}

	# find description, prio
	if ( $pre[$i] =~ /^ neighbor [0-9a-f\.:]+ description / ) {
	    @t = split(/\s+/,$pre[$i]);
	    $ip = $t[2];
	    $peer = $t[4];
	    $prio = $t[5];
	    # print "$ip $peer $prio\n";
	    if ( !defined($ip) || !defined($peer) ) {
		return "parseconf: did not find IP and peername in line $i";
	    }
	    if ( !defined($ip2as{$ip}) ) {
		return "parseconf: did not find AS for IP $ip in line $i";
		next;
	    }
	    $ipas = $ip.' '.$ip2as{$ip};
	    $peer{$ipas} = $peer;
	    $peerlvl{$ipas} = $peerconfcnt;
	    if ( defined($prio) ) {
		# TODO: check on valid values 'warn, crit, low'
		$peerprio{$ipas} = $prio;
	    }
	    else {
		$peerprio{$ipas} = 'crit';
	    }

	    # print "peer: $peer ip: $ip as: $as\n";
	    next;
	}
    }

    return '';
}

#####################################################################

sub parsemem {
    my(@pre)= @_;

    my($ncnt);
    # search for '\d+ peers, using \d+ \S+ of memory' line

    foreach $i (@pre) {
	# print "i: $i\n";
	if ( $i =~ /\d+ peers, using \d+ \S+ of memory/ ) {
	    ( $ncnt ) = ( $i =~ /(\d+) peers, using \d+ \S+ of memory/ );
	    if ( defined($ncnt) ) {
		$ncnt--;
		$peercnt=$ncnt;
		if ( $peercnt == $peerconfcnt ) {
		    return "neigh=$ncnt ";
		}
		else {
		    return "neigh=$ncnt;;;$peerconfcnt; ";
		}
	    }
        }
    }
}

sub parsebgpsum {
    my(@pre)= @_;

    my($ip,$as,$upt,$pfx);
    my($off, $t);
    my($res)='';
    my($mode)='';

    if ( $pre[0] eq 'show ip bgp summary' ) {
	$mode='v4';
    }
    elsif ( $pre[0] eq 'show ipv6 bgp summary' ) {
	$mode='v6';
    }
    else {
	# print $pre[0]."\n";
	$failres='unknown-mode';
	return '';
    }

    foreach $i (0..$#pre) {
	# print $mode.": $pre[$i]\n";

	if ( $pre[$i] =~ /^([0-9a-f\.:]+)/ ) {
	    ( $ip ) = ( $pre[$i] =~ /^([0-9a-f\.:]+)/ );
	    # which line do we have to parse ?
	    if ( defined($ip) ) {
		if ( length($ip) > 15 ) {
		    $t = substr($pre[$i+1],18);
		}
		else {
		    $t = substr($pre[$i],18);
		}
		# parse the line
		# print $mode."m: $t\n";
		my(@t)=split(/\s+/,$t);
		if ( $t[0] eq '' ) {
		    shift(@t);
		}
		$as = $t[0];
		$upt= upt2sec($t[6]);
		# print "ip: $ip as: $as\n";
		if ( $upt < $interval ) {
		    $failres .= $mode."age-".$peer{"$ip $as"}."=".$peerlvl{"$ip $as"}.' ';
		}
		$pfx= $t[7];
		if ( $pfx !~ /^\d+$/ ) {
		    # Active, Idle, OpenSent or something else ?
		    $failres .= $peerprio{"$ip $as"}.'='.$peer{"$ip $as"}.
			':'.$pfx.' ';
		}
		# print $mode."p: $ip $as $upt $pfx\n";

		# output in nagios plugin syntax
		# if ( $ip =~ /:/ && $pfx != 0 ) {
		    $saveres .= $mode."rt-".$peer{"$ip $as"}."=$pfx ";
		# }
	    }
	    else {
		# we can not parse this line, skip it
		next;
	    }
        }
	else {
	    next;
	}
    }

    return $res;
}


# IN: string with the session uptime in some obscure format
# OUT: seconds since epoch
sub upt2sec {
    my($inp) = @_;

    # examples: never, 01:43:22, 1d00h57m, 05w4d23h

    # print "inp: $inp\n";
    if ( $inp eq 'never' ) {
	return -1;
    }

    if ( $inp =~ /:/ ) {
	my($h,$m,$s) = split(/:/,$inp);
	if ( !defined($h) ) {
	    return -1;
	}
	return $h * 3600 + $m * 60 + $s;
    }

    if ( $inp =~ /\d+d\d+h\d+m/ ) {
	my($d,$h,$m) = ( $inp =~ /^(\d+)d(\d+)h(\d+)m$/ );
	if ( !defined($d) ) {
	    return -1;
	}
	return $d * 86400 + $h * 3600 + $m * 60;
    }

    if ( $inp =~ /\d+w\d+d\d+h/ ) {
	my($w,$d,$h) = ( $inp =~ /^(\d+)w(\d+)d(\d+)h$/ );
	if ( !defined($w) ) {
	    return -1;
	}
	return $w * 604800 + $d * 86400 + $h * 3600;
    }

    # if we can not parse it, return -1
    return -1;

}

sub byt2byt {
    my($byte, $unit) = @_;

    if ( $unit eq 'bytes' ) {
	return $byte;
    }

    if ( $unit eq 'KiB' ) {
	return $byte * 1024;
    }

    if ( $unit eq 'MiB' ) {
	return $byte * 1024 * 1024;
    }
}

