Skip to content

Instantly share code, notes, and snippets.

@cycloon
Last active June 6, 2016 15:37
Show Gist options
  • Save cycloon/e83ce4619586f893281575460a90d869 to your computer and use it in GitHub Desktop.
Save cycloon/e83ce4619586f893281575460a90d869 to your computer and use it in GitHub Desktop.
ISC DHCPD status report generator lent and slightly modified from https://markshroyer.com/2007/09/isc-dhcpd-status-report-generator/, author: @markshroyer
#!/usr/bin/perl
# $Id: dhcpd-report.pl,v 1.7 2007/09/18 22:20:37 mshroyer Exp $
=pod
=head1 NAME
dhcpd-report.pl - A DHCPD status report generator
=head1 SYNOPSIS
dhcpd-report.pl [-h] [-f format] [-o output]
=head1 DESCRIPTION
Parses the DHCPD configuration and lease database files
for information about DHCPD reservations and dynamically
assigned leases, then writes a report in either plaintext
or HTML format.
The default locations for the ISC DHCPD configuration and
lease database files are set to /etc/dhcpd.conf and
/var/db/dhcpd.leases, respectively, which are said files'
locations on an OpenBSD 4.1 machine. You can change where
this program looks for these files by modifying the CONF
and LEASES constants near the top of the script.
=head1 OPTIONS
=over 4
=item -h
show a simple help message and exit
=item -f
set output format to either "plain" or "html" (default "plain")
=item -o
write output to a specified filename instead of STDOUT
=back 4
=head1 COPYRIGHT
Copyright (c) 2007, Mark Shroyer
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the
above copyright notice, this list of conditions and
the following disclaimer in the documentation and/or
other materials provided with the distribution.
* The name of Mark Shroyer may not be used to endorse or
promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY MARK SHROYER ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL MARK SHROYER BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=cut
use warnings;
use strict;
### BEGIN CONFIGURATION ##############################################
# Report title
use constant TITLE => 'DHCPD Status for Homestarmy.net';
# Where to find the ISC DHCPD configuration file
use constant CONF => '/etc/dhcpd.conf';
# The path to the ISC DHCPD lease database
use constant LEASES => '/var/db/dhcpd.leases';
# Meta refresh interval (in seconds) for generated HTML page
use constant REFRESH => 30;
### END CONFIGURATION ################################################
use Getopt::Std;
use Pod::Usage;
use Date::Parse;
use Date::Format;
#
# Print a message about program usage and flags
#
sub usage() {
pod2usage({verbose => 1});
exit;
}
#
# Retrieve hash of DHCP reservations from /etc/dhcpd.conf
#
sub get_reservations() {
my @hosts = ();
my $conf = '';
open(DHCPDCONF, '<', CONF);
while (<DHCPDCONF>) {
s/^([^#]*).*$/$1/;
$conf .= $_;
}
close DHCPDCONF;
while ( $conf =~ m/
^\s*host\s+([[:alnum:]-]+)\s*{
\s*hardware\ ethernet
\s+((?:[[:xdigit:]]{2}:){5}[[:xdigit:]]{2})\s*;
\s*fixed-address\s+((?:\d{1,3}\.){3}\d{1,3})\s*;
\s*}\s*$
/mxgo ) {
push @hosts, { name => $1, mac => lc($2), ip => $3 };
}
return @hosts;
}
#
# Retrieve hash of dynamic leases from /var/db/dhcpd.leases.
#
sub get_leases() {
my @hosts = ();
my $leases = '';
open(DHCPDLEASES, '<', LEASES);
while (<DHCPDLEASES>) {
s/^([^#]*).*$/$1/;
$leases .= $_;
}
close DHCPDLEASES;
while ( $leases =~ m/
^\s*lease\s((?:\d{1,3}\.){3}\d{1,3})\s*{
\s*starts\s+\d\s+(\d{4}\/\d{2}\/\d{2}\s\d{2}:\d{2}:\d{2})\s*;
\s*ends\s+\d\s+(\d{4}\/\d{2}\/\d{2}\s\d{2}:\d{2}:\d{2})\s*;
\s*hardware\ ethernet\s+((?:[[:xdigit:]]{2}:){5}[[:xdigit:]]{2})\s*;
(?:\s*uid\s+(?:[[:xdigit:]]{2}:){6}[[:xdigit:]]{2}\s*;)?
(\s*client-hostname\s+"(.*)"\s*;)?
\s*}\s*$
/mxgo ) {
push @hosts, { ip => $1, starts => str2time($2, 'UTC'),
ends => str2time($3, 'UTC'), mac => lc($4), name => $6 };
}
sub sort_starts($$) {
my ($a, $b) = @_;
$$b{starts} <=> $$a{starts};
}
return sort sort_starts @hosts;
}
#
# Create a plaintext report of current DHCPD reservations and leases
#
sub report_plain(\@\@) {
my ($reservations_ref, $leases_ref) = @_;
my @reservations = @$reservations_ref;
my @leases = @$leases_ref;
format DHCPD_TOP =
@|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
uc(TITLE)
@|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ctime(time)
.
format DHCPD =
Reservations in @*:
CONF
Hostname Hardware Address IPv4 Address
--------------------------------------------------------------------------------
@<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<< ~~
{
my $r = shift @reservations;
if ($r) {
( $$r{name}, $$r{mac}, $$r{ip} );
}
else {
( "", "", "" );
}
}
Dynamic leases in @*:
LEASES
Client ID Hardware Address IPv4 Address Issued Active?
--------------------------------------------------------------------------------
@<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<< @||||||||||||||||| @|||||| ~~
{
my $l = shift @leases;
if ($l) {
( $$l{name}, $$l{mac}, $$l{ip}, time2str("%a %m/%d/%y %H:%M", $$l{starts}),
((($$l{starts} < time) and (time < $$l{ends})) ? 'yes' : 'no' ) );
}
else {
( "", "", "", "", "" );
}
}
.
$^ = 'DHCPD_TOP';
$~ = 'DHCPD';
write;
}
#
# Creates an HTML report of current DHCPD reservations and leases
#
sub report_html(\@\@) {
my ($reservations_ref, $leases_ref) = @_;
my @reservations = @$reservations_ref;
my @leases = @$leases_ref;
my $timestr = time2str("%a, %b %e, %Y %r", time);
print <<EOF;
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>${\TITLE}</title>
<meta http-equiv="refresh" content="${\REFRESH}" />
<style type="text/css">
h1 {
text-align: center;
margin-bottom: 10px;
}
p#timestamp {
text-align: center;
}
h2 {
margin-top: 30px;
margin-bottom: 8px;
}
table {
border-collapse: collapse;
vertical-align: baseline;
}
table td, table th {
padding: 3px 4px 1px;
}
table td {
border: 1px solid black;
}
table td.name {
width: 150px;
}
table td.ip {
width: 110px;
}
td.mac {
font-family: monospace;
}
td.date {
text-align: center;
}
</style>
</head>
<body>
<h1>${\TITLE}</h1>
<p id="timestamp">Generated $timestr</p>
<h2>Reservations in ${\CONF}:</h2>
<table>
<tr>
<th>Hostname</th> <th>Hardware Address</th> <th>IPv4 Address</th>
</tr>
EOF
foreach my $r (@reservations) {
my $url = "http://www.coffer.com/mac_find/?string=$$r{mac}";
print "<tr>";
print "<td class=\"name\">$$r{name}</td>";
print "<td class=\"mac\"><a target=\"_new\" href=\"$url\">$$r{mac}</a></td>";
print "<td class=\"ip\">$$r{ip}</td>";
print "</tr>\n";
}
print <<EOF;
</table>
<h2>Dynamic leases in ${\LEASES}:</h2>
<table>
<tr>
<th>Client ID</th> <th>Hardware Address</th> <th>IPv4 Address</th>
<th>Issued</th> <th>Expires</th> <th>Active?</th>
</tr>
EOF
foreach my $l (@leases) {
my $url = "http://www.coffer.com/mac_find/?string=$$l{mac}";
print "<tr>";
print "<td class=\"name\">$$l{name}</td>";
print "<td class=\"mac\"><a target=\"_new\" href=\"$url\">$$l{mac}</a></td>";
print "<td class=\"ip\">$$l{ip}</td>";
print "<td class=\"date\">" . time2str("%a, %b %e, %Y %r", $$l{starts}) . "</td>";
print "<td class=\"date\">" . time2str("%a, %b %e, %Y %r", $$l{ends}) . "</td>";
print "<td class=\"date\">" . ((($$l{starts} < time) and (time < $$l{ends})) ? 'yes' : 'no') . "</td>";
print "</tr>\n";
}
print <<EOF;
</table>
</body>
</html>
EOF
}
my $opt_string = 'hf:o:';
my %opt;
$opt{f} = 'plain';
getopts("$opt_string", \%opt) or usage();
usage() if $opt{h};
usage() if ($opt{f} ne 'plain' and $opt{f} ne 'html');
my @reservations = get_reservations();
my @leases = get_leases();
if ($opt{o}) {
open(OUT, '>', $opt{o}) or die "Could not open output file $opt{o}.";
select OUT;
}
if ($opt{f} eq 'plain') {
report_plain(@reservations, @leases);
}
elsif ($opt{f} eq 'html') {
report_html(@reservations, @leases);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment