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