#!/usr/bin/perl # # Shows current leases. # # THIS SCRIPT IS PUBLIC DOMAIN, NO RIGHTS RESERVED! # # I've removed the email addresses of Christian and vom to avoid # putting them on spam lists. If either of you would like to have # your email in here please send mail to the DHCP bugs list at ISC. # # 2008-07-13, Christian Hammers # # 2009-06-?? - added loading progress counter, pulls hostname, adjusted formatting # vom # # 2013-04-22 - added option to choose lease file, made manufacture information # optional, sar # # 2014-02-18 - added ipv4 address formatting # - changed width on hostname field # - stubbed in support for IPv6 # # 2014-02-19 - added report ordering # # 2014-02-22 - added ipv6 address formatting stub # # 2015-01-20 - changed reporting order on human readable output # use strict; use warnings; use POSIX qw(strftime); my $LEASES = '/var/db/dhcpd.leases'; my @all_leases; my @leases; my @OUIS = ('/usr/share/misc/oui.txt', '/usr/local/etc/oui.txt'); my $OUI_URL = 'http://standards.ieee.org/regauth/oui/oui.txt'; my $oui; my %data; my $opt_format = 'human'; my $opt_keep = 'active'; my $opt_ip = 'ipv4'; my $opt_ipformat = 'yes'; my $opt_order = 'data'; my $ipaddr = ""; our $total_leases = 0; ## Return manufactorer name for specified MAC address (aa:bb:cc:dd:ee:ff). sub get_manufactorer_for_mac($) { my $manu = "-NA-"; if (defined $oui) { $manu = join('-', ($_[0] =~ /^(..):(..):(..):/)); $manu = `grep -i '$manu' $oui | cut -f3`; chomp($manu); } return $manu; } ## Read oui.txt or print warning. sub check_oui_file() { for my $oui_cand (@OUIS) { if ( -r $oui_cand) { $oui = $oui_cand; last; } } if (not defined $oui) { print(STDERR "To get manufacturer names please download $OUI_URL "); print(STDERR "to /usr/local/etc/oui.txt\n"); } } ## Read current leases file into array. sub read_dhcpd_leases() { open(F, $LEASES) or die("Cannot open $LEASES: $!"); my $content = join('', ); close(F); @all_leases = split(/lease/, $content); foreach my $lease (@all_leases) { if ($lease =~ /^\s+([\.\d]+)\s+{.*starts \d+ ([\/\d\ \:]+);.*ends \d+ ([\/\d\ \:]+);.*ethernet ([a-f0-9:]+);/s) { ++$total_leases; } } } ## Format IPv4 address into a "standardized" form sub format_ip4addr() { my ($part1, $part2, $part3, $part4) = split(/\./, $ipaddr, 4); $part1 = substr "000$part1", length("000$part1")-3, 3 ; $part2 = substr "000$part2", length("000$part2")-3, 3 ; $part3 = substr "000$part3", length("000$part3")-3, 3 ; $part4 = substr "000$part4", length("000$part4")-3, 3 ; my $ip4addr = "$part1.$part2.$part3.$part4"; return $ip4addr; } ## Format IPv6 address into a "standardized" form sub format_ip6addr() { my $ip6addr = $ipaddr; return $ip6addr; } ## Add manufacturer name and sort out obsolete assignments. sub process_leases() { my $gm_now = strftime("%Y/%m/%d %H:%M:%S", gmtime()); my %tmp_leases; # for sorting and filtering my $counter = 1; # parse entries foreach my $lease (@all_leases) { # skip invalid lines next if not ($lease =~ /^\s+([\.\d]+)\s+{.*starts \d+ ([\/\d\ \:]+);.*ends \d+ ([\/\d\ \:]+);.*ethernet ([a-f0-9:]+);(.*client-hostname \"(\S+)\";)*/s); # skip outdated lines next if ($opt_keep eq 'active' and $3 lt $gm_now); my $percent = (($counter / $total_leases)*100); printf "Processing: %2d%% complete\r", $percent; ++$counter; my $hostname = "-NA-"; if ($6) { $hostname = $6; } my $mac = $4; my $date_end = $3; if ($opt_ipformat eq 'yes') { if ($opt_ip eq 'ipv4') { $ipaddr = $1; $ipaddr = format_ip4addr(); } else { $ipaddr = $1; $ipaddr = format_ip6addr(); } } else { $ipaddr = $1; } my %entry = ( 'ip' => $ipaddr, 'date_begin' => $2, 'date_end' => $date_end, 'mac' => $mac, 'hostname' => $hostname, 'manu' => get_manufactorer_for_mac($mac), ); $entry{'date_begin'} =~ s#\/#-#g; # long live ISO 8601 $entry{'date_end'} =~ s#\/#-#g; if ($opt_keep eq 'all') { push(@leases, \%entry); } elsif (not defined $tmp_leases{$mac} or $tmp_leases{$mac}{'date_end'} gt $date_end) { $tmp_leases{$mac} = \%entry; } } # In case we used the hash to filtered if (%tmp_leases) { foreach (sort keys %tmp_leases) { my $h = $tmp_leases{$_}; push(@leases, $h); } } # print "\n"; } # Order leases sub order_leases() { my @ordered_leases; if ($opt_order eq 'data') { @ordered_leases = @leases; } elsif ($opt_order eq 'hostname') { @ordered_leases = sort { $a->{hostname} cmp $b->{hostname} or $a->{ip} cmp $b->{ip} } @leases; } elsif ($opt_order eq 'ip') { @ordered_leases = sort { $a->{ip} cmp $b->{ip} } @leases; } elsif ($opt_order eq 'mac') { @ordered_leases = sort { $a->{mac} cmp $b->{mac} } @leases; } elsif ($opt_order eq 'manufacturer') { @ordered_leases = sort { $a->{manu} cmp $b->{manu} or $a->{mac} cmp $b->{mac} } @leases; } elsif ($opt_order eq 'valid') { @ordered_leases = sort { $a->{date_end} cmp $b->{date_end} } @leases; } @leases = @ordered_leases; } # Output all valid leases. sub output_leases() { if ($opt_format eq 'human') { printf "%-17s%-20s%-19s%-25s%-20s\n","IP","valid until","MAC","hostname","manufacturer"; print("==================================================================================================================\n"); } foreach (@leases) { if ($opt_format eq 'human') { printf("%-17s%-20s%-19s%-25s%-20s\n", $_->{'ip'}, # IP address $_->{'date_end'}, # Date $_->{'mac'}, # MAC $_->{'hostname'}, # hostname $_->{'manu'}); # manufacturer name } else { printf("MAC %s IP %s HOSTNAME %s BEGIN %s END %s MANUFACTURER %s\n", $_->{'mac'}, $_->{'ip'}, $_->{'hostname'}, $_->{'date_begin'}, $_->{'date_end'}, $_->{'manu'}); } } } # Usage message sub usage() { print( "Prints active DHCP leases.\n\n". "Usage: $0 [options]\n". " --help shows this help\n". " --parsable machine readable output with full dates\n". " --last prints the last (even if end