dhcp-lease-list.pl revision 1.1.1.1.6.1 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
18 #
19 # 2016-01-19 - updated to better trim the manu string and output the hostnames, sar
20 #
21 # 2016-01-18 - Mainly cosmetics. Eliminated spurious output in "parsable" mode.
22 # Provided for the various conventional lease file locations. (cbp)
23 #
24 # 2019-06-20 - Updated OUI_URL location. -TS
25
26 use strict;
27 use warnings;
28 use POSIX qw(strftime);
29
30 my @LEASES = ('/var/db/dhcpd.leases', '/var/lib/dhcp/dhcpd.leases', '/var/lib/dhcp3/dhcpd.leases');
31 my @all_leases;
32 my @leases;
33
34 my @OUIS = ('/usr/share/misc/oui.txt', '/usr/local/etc/oui.txt');
35 my $OUI_URL = 'http://standards-oui.ieee.org/oui.txt';
36 my $oui;
37
38 my %data;
39
40 my $opt_format = 'human';
41 my $opt_keep = 'active';
42
43 our $total_leases = 0;
44
45 ## Return manufactorer name for specified MAC address (aa:bb:cc:dd:ee:ff).
46 sub get_manufactorer_for_mac($) {
47 my $manu = "-NA-";
48
49 if (defined $oui) {
50 $manu = join('-', ($_[0] =~ /^(..):(..):(..):/));
51 $manu = `grep -i '$manu' $oui | cut -f3`;
52 $manu =~ s/^\s+|\s+$//g;
53 }
54
55 return $manu;
56 }
57
58 ## Read oui.txt or print warning.
59 sub check_oui_file() {
60
61 for my $oui_cand (@OUIS) {
62 if ( -r $oui_cand) {
63 $oui = $oui_cand;
64 last;
65 }
66 }
67
68 if (not defined $oui) {
69 print(STDERR "To get manufacturer names please download $OUI_URL ");
70 print(STDERR "to /usr/local/etc/oui.txt\n");
71 }
72 }
73
74 ## Read current leases file into array.
75 sub read_dhcpd_leases() {
76
77 my $db;
78 for my $db_cand (@LEASES) {
79 if ( -r $db_cand) {
80 $db = $db_cand;
81 last;
82 }
83 }
84 die("Cannot find leases db") unless defined $db;
85 open(F, $db) or die("Cannot open $db: $!");
86 print("Reading leases from $db\n") if $opt_format eq 'human';
87 my $content = join('', <F>);
88 close(F);
89 @all_leases = split(/lease/, $content);
90
91 foreach my $lease (@all_leases) {
92 if ($lease =~ /^\s+([\.\d]+)\s+{.*starts \d+ ([\/\d\ \:]+);.*ends \d+ ([\/\d\ \:]+);.*ethernet ([a-f0-9:]+);/s) {
93 ++$total_leases;
94 }
95 }
96 }
97
98 ## Add manufactor name and sort out obsolet assignements.
99 sub process_leases() {
100 my $gm_now = strftime("%Y/%m/%d %H:%M:%S", gmtime());
101 my %tmp_leases; # for sorting and filtering
102
103 my $counter = $opt_format eq 'human' ? 1 : 0;
104
105 # parse entries
106 foreach my $lease (@all_leases) {
107 # skip invalid lines
108 next if not ($lease =~ /^\s+([\.\d]+)\s+{.*starts \d+ ([\/\d\ \:]+);.*ends \d+ ([\/\d\ \:]+);.*ethernet ([a-f0-9:]+);(.*client-hostname \"(\S+)\";)*/s);
109 # skip outdated lines
110 next if ($opt_keep eq 'active' and $3 lt $gm_now);
111
112 if ($counter) {
113 my $percent = (($counter / $total_leases)*100);
114 printf "Processing: %2d%% complete\r", $percent;
115 ++$counter;
116 }
117
118 my $hostname = "-NA-";
119 if ($6) {
120 $hostname = $6;
121 }
122
123 my $mac = $4;
124 my $date_end = $3;
125 my %entry = (
126 'ip' => $1,
127 'date_begin' => $2,
128 'date_end' => $date_end,
129 'mac' => $mac,
130 'hostname' => $hostname,
131 'manu' => get_manufactorer_for_mac($mac),
132 );
133
134 $entry{'date_begin'} =~ s#\/#-#g; # long live ISO 8601
135 $entry{'date_end'} =~ s#\/#-#g;
136
137 if ($opt_keep eq 'all') {
138 push(@leases, \%entry);
139 } elsif (not defined $tmp_leases{$mac} or $tmp_leases{$mac}{'date_end'} gt $date_end) {
140 $tmp_leases{$mac} = \%entry;
141 }
142 }
143
144 # In case we used the hash to filtered
145 if (%tmp_leases) {
146 foreach (sort keys %tmp_leases) {
147 my $h = $tmp_leases{$_};
148 push(@leases, $h);
149 }
150 }
151
152 # print "\n";
153
154 }
155
156 # Output all valid leases.
157 sub output_leases() {
158 if ($opt_format eq 'human') {
159 printf "%-19s%-16s%-15s%-20s%-20s\n","MAC","IP","hostname","valid until","manufacturer";
160 print("===============================================================================================\n");
161 }
162 foreach (@leases) {
163 if ($opt_format eq 'human') {
164 printf("%-19s%-16s%-14.14s %-20s%-20s\n",
165 $_->{'mac'}, # MAC
166 $_->{'ip'}, # IP address
167 $_->{'hostname'}, # hostname
168 $_->{'date_end'}, # Date
169 $_->{'manu'}); # manufactor name
170 } else {
171 printf("MAC %s IP %s HOSTNAME %s BEGIN %s END %s MANUFACTURER %s\n",
172 $_->{'mac'},
173 $_->{'ip'},
174 $_->{'hostname'},
175 $_->{'date_begin'},
176 $_->{'date_end'},
177 $_->{'manu'});
178 }
179 }
180 }
181
182 # Commandline Processing.
183 sub cli_processing() {
184 while (my $arg = shift(@ARGV)) {
185 if ($arg eq '--help') {
186 print(
187 "Prints active DHCP leases.\n\n".
188 "Usage: $0 [options]\n".
189 " --help shows this help\n".
190 " --parsable machine readable output with full dates\n".
191 " --last prints the last (even if end<now) entry for every MAC\n".
192 " --all prints all entries i.e. more than one per MAC\n".
193 " --lease uses the next argument as the name of the lease file\n".
194 " the default is to try /var/db/dhcpd.leases then\n".
195 " /var/lib/dhcp/dhcpd.leases then\n".
196 " /var/lib/dhcp3/dhcpd.leases\n".
197 "\n");
198 exit(0);
199 } elsif ($arg eq '--parsable') {
200 $opt_format = 'parsable';
201 } elsif ($arg eq '--last') {
202 $opt_keep = 'last';
203 } elsif ($arg eq '--all') {
204 $opt_keep = 'all';
205 } elsif ($arg eq '--lease') {
206 unshift @LEASES, shift(@ARGV);
207 } else {
208 die("Unknown option $arg");
209 }
210 }
211 }
212
213 #
214 # main()
215 #
216 cli_processing();
217 check_oui_file();
218 read_dhcpd_leases();
219 process_leases();
220 output_leases();
221