1#!/usr/bin/perl 2# 3# Shows current leases. 4# 5# THIS SCRIPT IS PUBLIC DOMAIN, NO RIGHTS RESERVED! 6# 7# I've removed the email addresses of Christian and vom to avoid 8# putting them on spam lists. If either of you would like to have 9# your email in here please send mail to the DHCP bugs list at ISC. 10# 11# 2008-07-13, Christian Hammers 12# 13# 2009-06-?? - added loading progress counter, pulls hostname, adjusted formatting 14# vom 15# 16# 2013-04-22 - added option to choose lease file, made manufacture information 17# optional, sar 18use strict; 19use warnings; 20use POSIX qw(strftime); 21 22my $LEASES = '/var/db/dhcpd.leases'; 23my @all_leases; 24my @leases; 25 26my @OUIS = ('/usr/share/misc/oui.txt', '/usr/local/etc/oui.txt'); 27my $OUI_URL = 'http://standards.ieee.org/regauth/oui/oui.txt'; 28my $oui; 29 30my %data; 31 32my $opt_format = 'human'; 33my $opt_keep = 'active'; 34 35our $total_leases = 0; 36 37## Return manufactorer name for specified MAC address (aa:bb:cc:dd:ee:ff). 38sub get_manufactorer_for_mac($) { 39 my $manu = "-NA-"; 40 41 if (defined $oui) { 42 $manu = join('-', ($_[0] =~ /^(..):(..):(..):/)); 43 $manu = `grep -i '$manu' $oui | cut -f3`; 44 chomp($manu); 45 } 46 47 return $manu; 48} 49 50## Read oui.txt or print warning. 51sub check_oui_file() { 52 53 for my $oui_cand (@OUIS) { 54 if ( -r $oui_cand) { 55 $oui = $oui_cand; 56 last; 57 } 58 } 59 60 if (not defined $oui) { 61 print(STDERR "To get manufacturer names please download $OUI_URL "); 62 print(STDERR "to /usr/local/etc/oui.txt\n"); 63 } 64} 65 66## Read current leases file into array. 67sub read_dhcpd_leases() { 68 69 open(F, $LEASES) or die("Cannot open $LEASES: $!"); 70 my $content = join('', <F>); 71 close(F); 72 @all_leases = split(/lease/, $content); 73 74 foreach my $lease (@all_leases) { 75 if ($lease =~ /^\s+([\.\d]+)\s+{.*starts \d+ ([\/\d\ \:]+);.*ends \d+ ([\/\d\ \:]+);.*ethernet ([a-f0-9:]+);/s) { 76 ++$total_leases; 77 } 78 } 79} 80 81## Add manufactor name and sort out obsolet assignements. 82sub process_leases() { 83 my $gm_now = strftime("%Y/%m/%d %H:%M:%S", gmtime()); 84 my %tmp_leases; # for sorting and filtering 85 86 my $counter = 1; 87 88 # parse entries 89 foreach my $lease (@all_leases) { 90 # skip invalid lines 91 next if not ($lease =~ /^\s+([\.\d]+)\s+{.*starts \d+ ([\/\d\ \:]+);.*ends \d+ ([\/\d\ \:]+);.*ethernet ([a-f0-9:]+);(.*client-hostname \"(\S+)\";)*/s); 92 # skip outdated lines 93 next if ($opt_keep eq 'active' and $3 lt $gm_now); 94 95 my $percent = (($counter / $total_leases)*100); 96 printf "Processing: %2d%% complete\r", $percent; 97 ++$counter; 98 99 my $hostname = "-NA-"; 100 if ($6) { 101 $hostname = $6; 102 } 103 104 my $mac = $4; 105 my $date_end = $3; 106 my %entry = ( 107 'ip' => $1, 108 'date_begin' => $2, 109 'date_end' => $date_end, 110 'mac' => $mac, 111 'hostname' => $hostname, 112 'manu' => get_manufactorer_for_mac($mac), 113 ); 114 115 $entry{'date_begin'} =~ s#\/#-#g; # long live ISO 8601 116 $entry{'date_end'} =~ s#\/#-#g; 117 118 if ($opt_keep eq 'all') { 119 push(@leases, \%entry); 120 } elsif (not defined $tmp_leases{$mac} or $tmp_leases{$mac}{'date_end'} gt $date_end) { 121 $tmp_leases{$mac} = \%entry; 122 } 123 } 124 125 # In case we used the hash to filtered 126 if (%tmp_leases) { 127 foreach (sort keys %tmp_leases) { 128 my $h = $tmp_leases{$_}; 129 push(@leases, $h); 130 } 131 } 132 133 # print "\n"; 134 135} 136 137# Output all valid leases. 138sub output_leases() { 139 if ($opt_format eq 'human') { 140 printf "%-19s%-16s%-15s%-20s%-20s\n","MAC","IP","hostname","valid until","manufacturer"; 141 print("===============================================================================================\n"); 142 } 143 foreach (@leases) { 144 if ($opt_format eq 'human') { 145 printf("%-19s%-16s%-15s%-20s%-20s\n", 146 $_->{'mac'}, # MAC 147 $_->{'ip'}, # IP address 148 $_->{'hostname'}, # hostname 149 $_->{'date_end'}, # Date 150 $_->{'manu'}); # manufactor name 151 } else { 152 printf("MAC %s IP %s HOSTNAME %s BEGIN %s END %s MANUFACTURER %s\n", 153 $_->{'mac'}, 154 $_->{'ip'}, 155 $_->{'hostname'}, 156 $_->{'date_begin'}, 157 $_->{'date_end'}, 158 $_->{'manu'}); 159 } 160 } 161} 162 163# Commandline Processing. 164sub cli_processing() { 165 while (my $arg = shift(@ARGV)) { 166 if ($arg eq '--help') { 167 print( 168 "Prints active DHCP leases.\n\n". 169 "Usage: $0 [options]\n". 170 " --help shows this help\n". 171 " --parsable machine readable output with full dates\n". 172 " --last prints the last (even if end<now) entry for every MAC\n". 173 " --all prints all entries i.e. more than one per MAC\n". 174 " --lease uses the next argument as the name of the lease file\n". 175 " the default is /var/db/dhcpd.leases\n". 176 "\n"); 177 exit(0); 178 } elsif ($arg eq '--parsable') { 179 $opt_format = 'parsable'; 180 } elsif ($arg eq '--last') { 181 $opt_keep = 'last'; 182 } elsif ($arg eq '--all') { 183 $opt_keep = 'all'; 184 } elsif ($arg eq '--lease') { 185 $LEASES = shift(@ARGV); 186 } else { 187 die("Unknown option $arg"); 188 } 189 } 190} 191 192# 193# main() 194# 195cli_processing(); 196check_oui_file(); 197read_dhcpd_leases(); 198process_leases(); 199output_leases(); 200