#!/usr/bin/perl -w
#
# v1.0 Dec 31, 1999 Leif Sawyer
#    A quick hack to find a IP/MAC pair from the DHCP server
#    via WWW interface.
#
# v1.5 Jan 3, 2000 Jason Richards
#    Added support for compressed log files
#
# v2.0 Jan 4, 2000 Leif Sawyer
#    Added code to 'subtract a day' so that logs are parsed
#    for the correct date, not the day-behind that logs normally are.
#    Also color-coded the dhcp status for each entry so that problems
#    are easily found.  We're treating DHCPDISCOVER's as unnecessary info.

use Compress::Zlib;
use Time::Local;

$CurLogFile = "/var/log/dhcpd.log";
$OldLogDir = "/var/log/old";
@months=(Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec);

print "Content-type: text/html\n\n";
print "<!-- I am $0 -->\n";

&ReadParse;

if (! keys %in) {
	&Form;
}

@required_fields = ("host", "date", "search", split(/\s*,\s*|\000/, $in{'required_fields'}));
@missing_fields = grep ($in{$_} !~ /\S/, @required_fields);

if (@missing_fields) {
	$errormessage = "You did not provide sufficient information.\nYou are required to fill out the following:\n\n<UL>";
	$errormessage .= join("\n<LI> ", "", @missing_fields);
	$errormessage .= "\n</UL>\n\nPlease go back and fill out the form again.\n";
	&Exit("Insufficient Information", $errormessage);
}

# Untaint so we don't get nasty shell metacharacters.
$myhost = $in{"host"};
$host_orig = $myhost;
$myhost =~ /^([\w\:\.]*)$/;  $myhost = $1; # Untaint it
$myhost =~ s/^\s+//; s/\s+$//;
&Exit("Warning: Illegal usage.", "Illegal characters found in \"host\" variable.") if ($myhost ne $host_orig);

print qq|<!-- Using search = '$in{"search"}' -->\n|;
if ($in{"search"} eq "exact") {
	$search = "\\b" . $myhost . "\\W";
} else {
	$search = $myhost;
}

$unknown_color = "7f7f7f";
$ack_color = "A0FFA0";
$nack_color = "FFA0A0";
$request_color = "A0F0F0";
$offer_color = "A0A0F0";
$release_color = "FFA0FF";

$date = $in{"date"};
print "<!-- Date = '$date' -->\n";
#####
#
# Read and parse the logfile.
#

my($v) = 0;

if ($date == "Current") {
	open(DATA,"<$CurLogFile") || &Exit("Fatal Error", "Can't open logfile $logfile!<br>Contact <a href=\"mailto:root\@gci.net\">root\@gci.net</a>");
	while(<DATA>) {
	    if ( /$search/ ) {

		next if m/DHCPDISCOVER/i;

		my($month,$day,$time,$hostname,$service,$action,$verb1,$hostip,$verb2,$hostmac,$how,$relay) = split /\s+/,$_;

		$data{$v}{"date"} = $month . " " . $day;
		$data{$v}{"time"} = $time;
		$data{$v}{"hostip"} = $hostip;
		$data{$v}{"hostmac"} = $hostmac;
		$data{$v}{"type"} = $unknown_color;

#		print "<!-- $hostip action: $action -->\n";

		if ($action eq "DHCPACK") {
			$data{$v}{"type"} = "$ack_color";
		} elsif ($action eq "DHCPNAK") {
			$data{$v}{"type"} = "$nack_color";
		} elsif ($action eq "DHCPREQUEST") {
			$data{$v}{"type"} = "$request_color";
		} elsif ($action eq "DHCPOFFER") {
			$data{$v}{"type"} = "$offer_color";
		} elsif ($action eq "DHCPRELEASE") {
			$data{$v}{"type"} = "$release_color";
		}

		$v++;
	    }
	}
	close (DATA);
} else {
	$logfile = $OldLogDir . "/dhcpd.log-" . $date . ".gz";

	$gz = gzopen($logfile, "rb") || &Exit("Fatal Error", "Can't open logfile $logfile: $gzerrno<br>Contact <a href=\"mailto:root\@gci.net\">root\@gci.net</a>");

	while($gz->gzreadline($_) > 0) {

	    if (/$myhost/) {

		next if m/DHCPDISCOVER/i;

		my($month,$day,$time,$hostname,$service,$action,$verb1,$hostip,$verb2,$hostmac,$how,$relay) = split /\s+/,$_;

		$data{$v}{"date"} = $month . " " . $day;
		$data{$v}{"time"} = $time;
		$data{$v}{"hostip"} = $hostip;
		$data{$v}{"hostmac"} = $hostmac;
		$data{$v}{"type"} = $unknown_color;

#		print "<!-- $hostip action: $action -->\n";

		if ($action eq "DHCPACK") {
			$data{$v}{"type"} = "$ack_color";
		} elsif ($action eq "DHCPNAK") {
			$data{$v}{"type"} = "$nack_color";
		} elsif ($action eq "DHCPREQUEST") {
			$data{$v}{"type"} = "$request_color";
		} elsif ($action eq "DHCPOFFER") {
			$data{$v}{"type"} = "$offer_color";
		} elsif ($action eq "DHCPRELEASE") {
			$data{$v}{"type"} = "$release_color";
		}

		$v++;
	    }
	}
	$gz->gzclose();
}

$| = 1;

	print qq|
<html>
<!-- Copyright December 1999 Leif Sawyer -->
<!-- email: leif\@gci.net -->
<head><title>DHCP Host info for: $myhost</title></head>
<body><center><h2>DHCP Host info for: <b>$myhost</b></h2>
<hr width="50%">
</center>
<table border=0 align=center>
<tr>
 <td align=center bgcolor=$offer_color>Server Offer</td>
 <td>&nbsp;</td>
 <td align=center bgcolor=$request_color>Client Request</td>
</tr>
<tr>
 <td align=center bgcolor=$ack_color>Server ACK</td>
 <td>&nbsp;</td>
 <td align=center bgcolor=$nack_color>Client/Server NACK</td>
</tr>
<tr>
 <td align=center bgcolor=$release_color>Client Release</td>
 <td>&nbsp;</td>
 <td align=center bgcolor=$unknown_color>Unknown DHCP action</td>
</tr>
</table>
<hr>
<br>
<TABLE BORDER=1 ALIGN=center WIDTH=100%>
<TR>
 <td align=center bgcolor=000060><b><font color=ffffff>Mon/Day</td>
 <td align=center bgcolor=000060><b><font color=ffffff>Time</td>
 <td align=center bgcolor=000060><b><font color=ffffff>IP Address</td>
 <td align=center bgcolor=000060><b><font color=ffffff>MAC Address</td>
</TR>|;

	foreach $v ( sort bynum keys %data ) {

# Post Processing the data...

		print qq|
<TR>
 <td bgcolor=FFFFA0 align=center><b>$data{$v}{"date"}</b></td>
 <td bgcolor=FFFFA0 align=right>$data{$v}{"time"}</td>
 <td bgcolor=FFFFFF align=right>$data{$v}{"hostip"}</td>
 <td bgcolor=$data{$v}{"type"} align=right>$data{$v}{"hostmac"}</td>
</TR>|;
	}

	print qq|
</table><p><p></body></html>\n\n
|;


	exit 0;

##### End of Main Program
########################################################################

sub ReadParse {
    local (*in) = @_ if @_;

    local ($i, $key, $val);

    # Read in text
    if    ($ENV{'REQUEST_METHOD'} eq "GET") { $in = $ENV{'QUERY_STRING'}; }
    elsif ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN,$in,$ENV{'CONTENT_LENGTH'}); }

    @in = split(/&/,$in);

    foreach $i (0 .. $#in) {
	# Convert plus's to spaces
	$in[$i] =~ s/\+/ /g;

	# Split into key and value.  
	($key, $val) = split(/=/,$in[$i],2); # splits on the first =.

	# Convert %XX from hex numbers to alphanumeric
	$key =~ s/%(..)/pack("c",hex($1))/ge;
	$val =~ s/%(..)/pack("c",hex($1))/ge;

	# Associate key and value
	$in{$key} .= "\000" if (defined($in{$key})); # \0 is the multiple separator
	$in{$key} .= $val;
    }

    return 1; # just for fun
}

sub Exit {
    local($errorheader) = shift(@_);

    print "<TITLE>findhost: $errorheader</TITLE>\n";
    print "<body>";
    print "<H1>$errorheader</H1>\n";
    print @_;
    print "<P><HR>Unable to complete action.\n";

    exit(2);
}

sub Form {
print qq|
<html>
<!-- Copyright December 1999 Leif Sawyer for GCI Communications -->
<!-- email: leif\@gci.net -->
<head><title>DHCP Host info finder</title></head>
<body>
<center>
	<h2>DHCP Host info finder</h2>
<hr widht="70%">
</center>
Please enter an IP address or MAC address, then click on the submit button!<br>
You may also choose to search historical data by selecting a date from the dropbox.<br>
<blockquote>IP Addresses should be of the form: 192.168.22.1<br>
MAC Addresses should be of the form: ab:00:1d:0e:ff:c7
</blockquote>
<form action="/cgi-bin/findhost" method=POST>
Search <select name="date">
<option value="current" selected>Current\n\n|;
opendir(MDIR, "$OldLogDir") or die "Can't open $OldLogDir: $!";
@allfiles=readdir(MDIR);
closedir(MDIR);
foreach (reverse sort @allfiles) {
        if (m/.*dhcpd\.log-(\d\d\d\d)(\d\d)(\d\d)\.gz/i) {
		$year = $1; $month = $2; $mday = $3;
		$file = $year . $month . $mday;
		$timestamp = &timelocal(0,0,0,$mday,$month - 1,$year - 1900);
		$timestamp -= 86400;
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($timestamp);
		$year += 1900;
		$date = $months[$mon] . " " . $mday . ", " . $year;
                print qq|<option value="$file">$date\n|;
        }
}

print "</select>\n";
print qq|<input type="text" width="20" name="host"></input>
<blockquote>
<input type=submit value="Find Host">&nbsp;&nbsp;&nbsp;
<select name="search">
<option value="exact" selected>Exact matches only
<option value="partial">Partial matches
</select>
</blockquote>
<hr>|;

exit;
}

sub bynum { $a <=> $b; }

sub commas {
	local($_) = @_;
	1 while s/(.*\d)(\d\d\d)/$1,$2/;
	$_?$_:"0";
}
