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