#!/usr/pkg/bin/perl -w
#
# Plugin to monitor dhcp3 leases 
#
# Usage: copy or link into /etc/munin/plugins
#
# Parameters:
#
#       config   (required)
#       autoconf (optional - used by munin-config)
#
# Config variables:
#
#       user         - run by user that can read lease files [root]
#       leasefile    - What file to read leases from
#       configfile   - What file to read configuration from
#
# Requires:
#       Net::Netmask
#       HTTP::Date
#       
# Magic markers (optinal - used by munin-config and some installation
# scripts):
#
#%# family=contrib
#%# capabilities=autoconf


my $ret = undef;

if(! eval "require Net::Netmask") {
    $ret = "Net::Netmask not found";
}
if(! eval "require HTTP::Date") {
    $ret = "HTTP::Date not found";
}

use strict;

my %leases   = ();
my %networks = ();
my %ips      = ();
my $DEBUG    = 0;

my $LEASEFILE  = $ENV{leasefile}  || "/var/lib/dhcp/dhcpd.leases";
my $CONFIGFILE = $ENV{configfile} || "/etc/dhcpd.conf";

if($ARGV[0] and $ARGV[0] eq "autoconf" ) {
    if($ret) {
	print "no ($ret)\n";
	exit 1;
    }
    if(-f $LEASEFILE) {
	if(-r $LEASEFILE) {
	    if(-f $CONFIGFILE) {
		if(-r $CONFIGFILE) {
		    print "yes\n";
		    exit 0;
		} else {
		    print "no (config file not readable)\n";
		}
	    } else {
		print "no (config gile not found)\n";
	    }
	} else {
	    print "no (leasefile not readable)\n";
	}
    } else {
	print "no (leasefile not found)\n";
    }
    exit 1;
}

if (! -f $LEASEFILE and ! -f $CONFIGFILE) {
    print "net.value U\n";
    exit 0;
}

if($DEBUG) {
    print "CONFIGFILE == $CONFIGFILE\nLEASEFILE == $LEASEFILE\n";
}

Net::Netmask->import();
HTTP::Date->import();

parseconfig();

if($ARGV[0] and $ARGV[0] eq "config") {
    print "graph_title dhcp leases\n";
    print "graph_args --base 1000 -v leases -l 0\n";
    print "graph_order ".join(" ",sort(keys(%leases)))."\n";
    foreach my $network (sort(keys %leases)) {
        my $name = $network;
	$name =~ s/_/\./g;
	$name =~ s/\.\./\//g;
	print "$network.label $name\n";
    }
    exit 0;
} 

parseleases();

foreach my $network (sort(keys %leases)) {
    print "$network.value ".$leases{$network}."\n";
}

sub parseconfig {
    open(IN, "<$CONFIGFILE") or exit 4;
    while(<IN>) {
	if(/subnet\s+(\d+\.\d+\.\d+\.\d+)\s+netmask\s+(\d+\.\d+\.\d+\.\d+)/ && ! /^\s*#/) {
	    initnet($1,$2);
	}
    }
    close(IN);
}

sub parseleases {
    my $ip = 0;
    my $abandon = 0;
    my $time    = time(); 
    open(IN, "<$LEASEFILE") or exit 4;
    while(<IN>) {
	if(/lease\s+(\d+\.\d+\.\d+\.\d+)\s+\{/) {
	    print "in $1\n" if $DEBUG;
	    $ip = $1;
	}
	if($ip && /ends\s+\d+\s+([^;]+);/) {
#	    2037/12/31 23:59:59 is max date on perl <= 5.6
	    print "end $1\n" if $DEBUG;
	    my $end = HTTP::Date::str2time($1, "GMT");
	    # we asume that missing $end is valid due to 
	    # restrictions in Time::Local on perl <= 5.6
	    if($end && $end < $time) { 
		print "old $end $time:(\n" if $DEBUG;
		$abandon = 1;
	    }
	}
	if($ip && /^\s*abandoned;$/) {
	    print "abandoned\n" if $DEBUG;
	    $abandon = 1;
	}
	if($ip && /^\s*\}\s*$/) {
	    my $net = checkip($ip);
	    if($net && !$abandon) {
		if(!counted($ip)) {
			$leases{$net}++;
		}
	    }
	    $abandon = 0;
	    $ip      = 0;
	    print "out\n\n" if $DEBUG;
	}
    }
    close(IN);
}

sub initnet {
    my ($net, $mask) = @_;
    my $block = new Net::Netmask($net, $mask);
    $networks{$block->desc()} = $block;
    my $name = $block->desc();
    $name =~ s/\//__/g;
    $name =~ s/\./_/g;
    $leases{$name}   = 0;
}

sub checkip {
    my ($ip) = @_;
    foreach my $block (keys %networks) {
	if($networks{$block}->match($ip)) {
		my $name = $block;
		$name =~ s/\//__/g;
		$name =~ s/\./_/g;
 		return $name;
	}
    }
    return 0;
}

sub counted {
    my ($ip) = @_;
    if($ips{$ip}) {
	print "$ip already counted\n" if $DEBUG;
	return 1;
    }
    $ips{$ip} = $ip;
    print "Counted $ip once!\n" if $DEBUG;
    return 0;
}

exit();
