#!/usr/bin/python3

# Simple script to represent in a table some data 
# contained in /var/lib/dhcp/dhcpd.leases 
# (IP and MAC Address, expire time, hostname) 
# and extracted through python-isc-dhcp-leases module.
#
# Copyright (C) 2023 FUSS Project <info@fuss.bz.it>
# Author: Claudio Cavalli <ccavalli@fuss.bz.it>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.


from isc_dhcp_leases import Lease, IscDhcpLeases
import datetime 
import subprocess
import getopt, sys

# Attributes/Options correspondence definitions
attributes = {'-a', '-o'}

# help on line
def usage():
    print("Usage: leases.py [OPTION]")
    print("  -h        print this help")
    print("  -a        all IPs in leases")
    print("  -o        only reachable IPs")
    print()

# Initialize default options
display_all = False
display_only_reachable = False

# Command line argument processing
try:
    opts, args = getopt.getopt(sys.argv[1:], 'aoh')
except getopt.GetoptError as err:
    print(str(err))
    usage()
    sys.exit(2)

for opt, arg in opts:
    if opt == '-h':
        usage()
        sys.exit()
    elif opt == '-a':
        display_all = True
    elif opt == '-o':
        display_only_reachable = True

# Code to execute if either display_only_reachable or display_all is True
if  display_only_reachable or display_all:

    leases = IscDhcpLeases('/var/lib/dhcp/dhcpd.leases')

    # Get the current UTC time
    def time_now():
            n = datetime.datetime.utcnow()
            return datetime.datetime(n.year, n.month, n.day, n.hour, n.minute,
                    n.second + (0 if n.microsecond < 500000 else 1))

    now = time_now()
  
    # Get the current local time
    def current_time():
            n = datetime.datetime.now()
            return datetime.datetime(n.year, n.month, n.day, n.hour, n.minute,
                    n.second + (0 if n.microsecond < 500000 else 1))

    currentime = current_time()

    # Printing the table headers and the horizontal lines that define the structure of the table
    print('+--------------------------------------------------------------------------')
    print('| DHCP ACTIVE LEASES                                                       ')
    print('+-----------------+-------------------+------------------+-----------------')
    print('| IP Address      | MAC Address       | Expires (H:M:S)  | Hostname        ')
    print('+-----------------+-------------------+------------------------------------')

    # Function for printing a single lease entry in a formatted table-like structure
    def print_lease():
        print('| ' + format(lease.ip, '<15') + ' | ' + \
                        format(lease.ethernet, '<17') + ' | ' + \
                        format(str((lease.end - now) if lease.end != 'never' else 'never'), '<16') + ' | ' + \
                        lease.hostname)
    
    # Create a dictionary where the keys are Ethernet (MAC) addresses and the values are the corresponding lease end times
    # Each macaddress will have as its value the last Lease Time
    dict = {}
    for lease in leases.get():
        if lease.valid:
            dict[(lease.ethernet)] = (lease.end) 
    
    mac_list = []

    count = 0

    # Code to execute to get only pingable leases
    if display_only_reachable:
        status = "pingable "
        for lease in leases.get():
            # Checking to avoid duplicate leases
            if lease.ethernet not in mac_list:
                # Control to choose the most recent from duplicate leases for a given macaddress
                if lease.valid and lease.end == dict[lease.ethernet]:
                    ip = str(lease.ip)

                    # Command to be executed
                    command = f"ping -c 1 -w 1 {ip}"
                    # Execute the command using popen
                    process = subprocess.Popen(command, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, shell=True)
                    # Get the output and error (if any) from the command
                    output, error = process.communicate()
                    # Check the return code to see if the command was successful
                    if process.returncode == 0:
                        print_lease()
                        count = count + 1
                        # Add macaddress to mac_list
                        mac_list.append(lease.ethernet)
    
    # Code to execute to get all leases
    elif display_all:
        status = ""        
        for lease in leases.get():
            # Checking to avoid duplicate leases
            if lease.ethernet not in mac_list:
                # Control to choose the most recent from duplicate leases for a given macaddress
                if lease.valid and lease.end == dict[lease.ethernet]:
                    print_lease()
                    count = count + 1
                    # Add macaddress to mac_list
                    mac_list.append(lease.ethernet)

    # Print the final part of the table    
    print('+-----------------+-------------------+------------------+-----------------')
    print('| Total ' + status + 'Active Leases: ' + str(count))
    print('| Report generated : ' + str(currentime))
    print('+--------------------------------------------------------------------------')


else:
    print("No valid options specified. Use -h for help.")

