xref: /netbsd-src/external/mpl/dhcp/dist/contrib/dhcp-lease-list.pl (revision f9d8f0dfa6389bc58fe4c9622365c3a57155f0b7)
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
26use strict;
27use warnings;
28use POSIX qw(strftime);
29
30my @LEASES = ('/var/db/dhcpd.leases', '/var/lib/dhcp/dhcpd.leases', '/var/lib/dhcp3/dhcpd.leases');
31my @all_leases;
32my @leases;
33
34my @OUIS = ('/usr/share/misc/oui.txt', '/usr/local/etc/oui.txt');
35my $OUI_URL = 'http://standards-oui.ieee.org/oui.txt';
36my $oui;
37
38my %data;
39
40my $opt_format = 'human';
41my $opt_keep = 'active';
42
43our $total_leases = 0;
44
45## Return manufactorer name for specified MAC address (aa:bb:cc:dd:ee:ff).
46sub 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.
59sub 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.
75sub 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.
99sub 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.
157sub 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.
183sub 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#
216cli_processing();
217check_oui_file();
218read_dhcpd_leases();
219process_leases();
220output_leases();
221