Home | History | Annotate | Line # | Download | only in contrib
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