package SrvMngr::Controller::Dhcpd; #---------------------------------------------------------------------- # heading : Network # description : DHCP manager # navigation : 6000 550 # # name : dhcpd, method : get, url : /dhcpd, ctlact : Dhcpd#main # name : dhcpd1, method : get, url : /dhcpd1, ctlact : Dhcpd#do_leases # name : dhcpd2, method : get, url : /dhcpd2, ctlact : Dhcpd#do_winpopup # name : dhcpd3, method : get, url : /dhcpd3, ctlact : Dhcpd#do_scan # name : dhcpd4, method : get, url : /dhcpd4, ctlact : Dhcpd#do_delete_all_leases # name : dhcpd5, method : post, url : /dhcpd5, ctlact : Dhcpd#do_update_config # name : dhcpd6, method : get, url : /dhcpd6, ctlact : Dhcpd#do_delete_one_lease # name : dhcpd7, method : get, url : /dhcpd7, ctlact : Dhcpd#do_refresh_leases # name : dhcpd8, method : get, url : /dhcpd8, ctlact : Dhcpd#do_winpopup # name : dhcpd9, method : get, url : /dhcpd9, ctlact : Dhcpd#do_wol # name : dhcpd10, method : post, url : /dhcpd10, ctlact : Dhcpd#do_update_check # # routes : end # # # Documentation: https://wiki.koozali.org/Dhcpmanager # use strict; use warnings; use Mojo::Base 'Mojolicious::Controller'; use constant FALSE => 0; use constant TRUE => 1; use Locale::gettext; use SrvMngr::I18N; use SrvMngr qw(theme_list init_session); use Data::Dumper; use esmith::util; use esmith::HostsDB; use esmith::AccountsDB; use Net::Ping; use esmith::util::network qw(:all); use Socket qw( inet_aton ); #our $adb = esmith::AccountsDB->open() or die("Unable to open accounts DB"); my %dhcp_data = (); sub main { # # Initial page - full summary of parameters etc # Initial para from the Wiki. # my $c = shift; %dhcp_data = (); do_display($c); } sub do_display { # # Front parameters page # my $c = shift; $c->app->log->info( $c->log_req ); my $title = $c->l("dhcpd_DHCP manager"); my $modul = ''; my $trt = "SETTINGS"; our $db = esmith::ConfigDB->open() or die("Unable to open Configuration DB"); our %sme_conf = $db->get('dhcpd')->props; our %smb_conf = $db->get('smb')->props; $dhcp_data{trt} = $trt; $dhcp_data{"status"} = [[$c->l('dhcpd_ENABLED'),'enabled'], [$c->l('dhcpd_DISABLED'),'disabled'] ]; $dhcp_data{"check"} = [[$c->l('dhcpd_ENABLED'),'enabled'], [$c->l('dhcpd_DISABLED'),'disabled'] ]; if (! $sme_conf{'winscustom'} ) { $sme_conf{'winscustom'} = 'disabled' ; } if (! $sme_conf{'dnscustom'} ) { $sme_conf{'dnscustom'} = 'disabled' ; } if ( ! $sme_conf{'leasetime'} ) { $sme_conf{'leasetime'} = "86400" ; } if (! $sme_conf{'gatewaycustom'} ) { $sme_conf{'gatewaycustom'} = 'disabled' ; } # Accumulate parameters for Configuration DB $dhcp_data{'params'} = \%sme_conf; $dhcp_data{'smbparams'} = \%smb_conf; $c->stash( title => $title, modul => $modul, dhcp_data => \%dhcp_data ); #die("here"); $c->render( template => 'dhcpd' ); } sub do_leases { # # Show a table of the leases # my $c = shift; my $title = $c->l("dhcpd_MANAGING_DHCP_CLIENT"); my $modul = ''; my $trt = "LEASES"; $dhcp_data{"check"} = [[$c->l('dhcpd_ENABLED'),'enabled'], [$c->l('dhcpd_DISABLED'),'disabled'] ]; $dhcp_data{trt} = $trt; $dhcp_data{"first"} = ''; my @leases = get_leases_in_array($c); $c->stash( title => $title, modul => $modul, dhcp_data => \%dhcp_data, leases=> \@leases ); $c->render( template => 'dhcpd' ); } sub do_winpopup { # # call to win pop up # my $c = shift; my $title = $c->l("dhcpd_GLOBAL_WINPOPUP"); my $modul = ''; my $trt = "WINPOPUP"; $dhcp_data{trt} = $trt; $dhcp_data{"first"} = ''; #..... get winpopup details $c->stash( title => $title, modul => $modul, dhcp_data => \%dhcp_data ); $c->render( template => 'dhcpd' ); } sub do_scan { # # call to show scan results # my $c = shift; my $title = $c->l("dhcpd_SCANNING_NETWORK_TITLE"); my $modul = ''; my $trt = "SCAN"; $dhcp_data{trt} = $trt; $dhcp_data{"first"} = ''; # ..... get scan results into stash for passing to html template my $dhcp_scanresults = get_scan_results($c); $c->stash( title => $title, modul => $modul, "dhcp_data" => \%dhcp_data, "scanresults" => $dhcp_scanresults); $c->render( template => 'dhcpd' ); } sub do_update_config { # # Update config dhcp parameters # called through form submit. # my $c = shift; # Input results are in $c->param(). # If parameters do not validate, then return error message. # else write into config DB, and... # signal-event and ...return ok $dhcp_data{"success"} =""; my $ret = Main_Save($c); if ($ret eq 'ok') { $dhcp_data{"success"}="dhcpd_SUCCESSFULLY_SAVED_SETTINGS"; } else { $dhcp_data{"error"}=$ret; } do_display($c); return; } sub do_update_check { #Just update the check parameter my $c = shift; my $dhcpd_check = $c->param ('dhcp_check'); ###Update SME configuration dbase my $dbh_sme = esmith::ConfigDB->open('/home/e-smith/db/configuration'); ##Initiate get method --> create record object my $sme_record = $dbh_sme->get('dhcpd'); $sme_record->set_prop('check' , $dhcpd_check); $dhcp_data{"success"}="dhcpd_SUCCESSFULLY_SAVED_SETTINGS"; do_display($c); return; } sub Main_Save ($){ ##Pull CGI object from parameters array my $q = shift; ###Build Hash of config parameters to update from cgi submit my $dhcpd_status = $q->param ('dhcp_enable'); my $dhcpd_winscustom = $q->param ('dhcp_winscustom'); my $dhcpd_check = $q->param ('dhcp_check'); my $dhcpd_start = $q->param ('dhcp_start'); my $dhcpd_end = $q->param ('dhcp_end'); my $dhcpd_winsserver = $q->param ('dhcp_winsserver'); my $dhcpd_leasetime = $q->param ('dhcp_leasetime'); my $dhcpd_dnscustom = $q->param ('dhcp_dnscustom'); my $dhcpd_dns1server = $q->param ('dhcp_dns1server'); my $dhcpd_dns2server = $q->param ('dhcp_dns2server'); my $dhcpd_dns3server = $q->param ('dhcp_dns3server'); my $dhcpd_gatewaycustom = $q->param ('dhcp_gatewaycustom'); my $dhcpd_gateway = $q->param ('dhcp_gateway'); ###Update SME configuration dbase my $dbh_sme = esmith::ConfigDB->open('/home/e-smith/db/configuration'); ##Initiate get method --> create record object my $sme_record = $dbh_sme->get('dhcpd'); #get localip of server my $local_ip = $dbh_sme->get_value('LocalIP'); ##Set status of service $sme_record->set_prop('status', $dhcpd_status); $sme_record->set_prop('check' , $dhcpd_check); $sme_record->set_prop('winscustom', $dhcpd_winscustom); $sme_record->set_prop('leasetime' , $dhcpd_leasetime); $sme_record->set_prop('dnscustom' , $dhcpd_dnscustom); $sme_record->set_prop('gatewaycustom' , $dhcpd_gatewaycustom); #checkip to the dhcpserver, perform the save in DB configuration or display an error if value != of a valid ip or if dhcp_start is greater than dhcp_end if ($dhcpd_status eq "enabled") { if ( isValidIP ($dhcpd_start) && isValidIP ($dhcpd_end)) { #check if $dhcpd_start is greater than $dhcpd_end and if yes, display an error message. if (inet_aton($dhcpd_start) ge inet_aton($dhcpd_end)) { return $q->l('dhcpd_DHCP_START_GREATER_DHCP_END_ERRORS') . ' (' . $dhcpd_start . '/' . $dhcpd_end .')'; } elsif ( ( (inet_aton($dhcpd_start) le inet_aton($local_ip) ) && ( inet_aton($dhcpd_end)) ge inet_aton($local_ip) ) ) { #display an error if the range of dhcp server include the ip of the server address return $q->l('dhcpd_DHCP_RANGE_MUST_NOT_INCLUDE_SERVER_IP') . ' (' . $local_ip . ')'; } else { #set value my $dhcpd_start = cleanIP($dhcpd_start); my $dhcpd_end = cleanIP($dhcpd_end); $sme_record->set_prop('end', $dhcpd_end); $sme_record->set_prop('start', $dhcpd_start); } } #if $dhcpd_start or $dhcpd_end are not valid ip then display an error else { return $q->l('dhcpd_DHCP_RANGE_WITH_BAD_IP') . ' (' . $dhcpd_start . '/' . $dhcpd_end .')'; } } #checkip to the winserver perform the save in DB configuration or display an error if value != of a valid ip if ($dhcpd_winscustom eq "enabled") { if ( isValidIP ($dhcpd_winsserver) ) { #set value my $dhcpd_winsserver = cleanIP($dhcpd_winsserver); $dbh_sme->set_prop('smb','WINSServer', $dhcpd_winsserver); } else { #if $dhcpd_winsserver is not valid ip then display an error return $q->l('dhcpd_WINSSERVER_BAD_IP') . ' (' . $dhcpd_winsserver .')'; } } elsif ($dhcpd_winscustom eq "disabled") { my $delws = $dbh_sme->get('smb'); $delws->delete_prop('WINSServer'); } #checkip to the dnsserver custom, perform the save in DB configuration or display an error if value != of a valid ip if ($dhcpd_dnscustom eq "enabled") { #check if $dhcpd_dns1server and ( $dhcpd_dns2server are valid ip or $dhcpd_dns2server = null ) if ( isValidIP ($dhcpd_dns1server) && (isValidIP($dhcpd_dns2server) || ( $dhcpd_dns2server eq "") ) && (isValidIP($dhcpd_dns3server) || ( $dhcpd_dns3server eq "") ) ) { #set value my $dhcpd_dns1server = cleanIP($dhcpd_dns1server); $sme_record->set_prop('dns1server' , $dhcpd_dns1server); my $dhcpd_dns2server = cleanIP($dhcpd_dns2server); $sme_record->set_prop('dns2server' , $dhcpd_dns2server); my $dhcpd_dns3server = cleanIP($dhcpd_dns3server); $sme_record->set_prop('dns3server' , $dhcpd_dns3server); } else { ##if $dhcpd_dns1server or $dhcpd_dns2server or $dhcpd_dns3server are not valid ip then display an error return $q->l('dhcpd_DNS_SERVER_WITH_BAD_IP') . ' (' . $dhcpd_dns1server . '/' . $dhcpd_dns2server . '/' . $dhcpd_dns3server .')'; } } #checkip to the gateway_custom perform the save in DB configuration or display an error if value != of a valid ip if ($dhcpd_gatewaycustom eq "enabled") { if ( isValidIP ($dhcpd_gateway) ) { #set value my $dhcpd_gateway = cleanIP($dhcpd_gateway); $sme_record->set_prop('gateway' , $dhcpd_gateway); } else { #if $dhcpd_gateway is not valid ip then display an error return $q->l('dhcpd_GATEWAY_BAD_IP') . ' (' . $dhcpd_gateway .')'; } } # - 4 expand templates # changed to new sme standard signal-event system ("/sbin/e-smith/signal-event","workgroup-update") == 0 or die "Error while saving settings: $!"; return 'ok'; exit; } sub do_delete_all_leases { # # Delete all the specified lease # Called from button at top of leases list panel # my $c = shift; my $ret = delete_all_leases($c); if ($ret == 'ok') { $dhcp_data{"success"}="dhcpd_SUCCESSFULLY_SAVED_SETTINGS"; do_leases($c); } else {$dhcp_data{"error"}=$ret;} return ; } sub do_delete_one_lease { # # Delete the specified lease # Called from link in table of leases # my $c = shift; # Lease in $c->param("lease") # Validate - if not return error message # delete it # If deletion not ok return message # else return "ok" my $ret = delete_one_lease($c); if ($ret == 'ok') { $dhcp_data{"success"}="dhcpd_SUCCESSFULLY_SAVED_SETTINGS"; do_leases($c); } else {$dhcp_data{"error"}=$ret;} return ; } sub get_scan_results { my $c = shift; #...do it return Scan_Local_Network($c); } sub update_config { my $c = shift; #...do it return "ok"; } sub delete_one_lease { my $c = shift; Perform_Del_Lease($c); return "ok"; } sub delete_all_leases { my $c = shift; Perform_del_all_Lease($c); return "ok"; } sub get_leases_in_array { my $c = shift; my @leases = Load_leases($c); # ...do it return @leases; } sub Perform_del_all_Lease ($){ system ('/bin/echo "" > /var/lib/dhcpd/dhcpd.leases') ==0 or die "Error while removing all leases: $!"; system ("/sbin/e-smith/signal-event","workgroup-update") == 0 or die "Error while saving settings: $!"; return 'ok'; exit; } #=============================================================================== #SUBROUTINE: Perform delete lease #=============================================================================== sub Perform_Del_Lease($){ ###Pull CGI object from parameters array my $c = shift; #my $fred = 1/0; ###Pull entry to delete my $ip = $c->param('ip'); my $name = $c->param('name'); my $name_in_file = '/var/lib/dhcpd/dhcpd.leases' ; my $name_tmp_file = '/var/lib/dhcpd/dhcpd.leases.tmp' ; my $name_out_file = '/var/lib/dhcpd/dhcpd.leases~' ; my $del_current = "0" ; open(INFILE,"<$name_in_file") || die "Read Error $name_in_file, Read: $!"; open(OUTFILE,">$name_tmp_file") || die "Write error $name_in_file, Write: $!"; while () { if ( "$_" =~ /lease $ip/ ) { $del_current = "1" ; } if ( $del_current == "0" ) { print OUTFILE "$_" ; } if ( "$_" =~ /}/ ) { $del_current = "0" ; } } rename ($name_tmp_file,$name_in_file) ; system ("/bin/cp","-f","$name_in_file","$name_out_file") ; close(INFILE); close(OUTFILE); # changed to new sme standard signal-event system ("/sbin/e-smith/signal-event","workgroup-update") == 0 or die "Error while saving settings: $!"; ###Return action message return $c->l('SUCCESSFULLY_DELETED_THE_CLIENT') . ' (' . $name . '/' . $ip .')'; } #=============================================================================== #SUBROUTINE: Scan The Local Network #=============================================================================== sub dec2bin { my $str = unpack("B32", pack("N", shift)); return $str; } sub netmask2cidr { my ($mask, $network) = @_; my @octet = split (/\./, $mask); my @bits; my $binmask; my $binoct; my $bitcount=0; foreach (@octet) { $binoct = dec2bin($_); $binmask = $binmask . substr $binoct, -8; } # let's count the 1s @bits = split (//,$binmask); foreach (@bits) { if ($_ eq "1") { $bitcount++; } } my $cidr = $network . "/" . $bitcount; return $cidr; } sub get_mac_address { # From the leases file my ($ip, $file) = @_; open(my $fh, '<', $file) or die "Could not open file '$file' $!"; my $mac_address = undef; my $lease_block; while(my $line = <$fh>) { if($line =~ /lease $ip {/ .. $line =~ /}/) { $lease_block .= $line; if($line =~ /}/) { if($lease_block =~ /hardware ethernet (\S+);/) { $mac_address = $1; last; } else { $lease_block = ''; } } } } close $fh; return $mac_address; } sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s }; sub get_mac_address_by_ping { my ($ip) = trim(@_); # Ping the IP to ensure it's in the ARP table my $ping_result = `ping -c 1 -W 1 $ip`; #return $ping_result; if($ping_result =~ m/1 packets transmitted, 1 received/) { # Fetch the ARP table and find the line with the IP address my $mac_result = `nmap -PR -sn $ip`; #return "*$arp_result *$ip*"; # If found, return the MAC address #if ($arp_result =~ m/.*at ((?:[0-9a-f]{2}\:){5}[0-9a-f]{2})/) { if ($mac_result =~ /MAC Address: (..:..:..:..:..:..) /) { return $1; } else { return "mac not in nmap result";} } else { return "no ping";} return undef; } sub Scan_Local_Network ($) { my $nmap_sme = esmith::ConfigDB->open('/home/e-smith/db/configuration'); my $mask = $nmap_sme->get_value('LocalNetmask'); my $network = $nmap_sme->get_prop('InternalInterface','Network'); #we start to calculate the method to find the cidr (we want to use network/cidr with nmap) my $cidr = netmask2cidr($mask, $network); #ok go to use nmap with nmap -T4 -sP network/cidr my @nmap_output= `/usr/bin/nmap --script smb-os-discovery.nse -p U:137,T:139 $cidr| /bin/grep -Ev "MAC|NetBIOS|OS CPE"| /bin/grep -E "scan|done|Computer|OS"| /bin/sed -e 's/Nmap scan/-- Scan/g'|/bin/sed -e 's/done/Done/g'| /bin/sed -e 's/Nmap//g'|/bin/sed -e 's/|/ /g'`; my @extracted_output; my $leasefile = "/var/lib/dhcpd/dhcpd.leases"; foreach my $line (@nmap_output) { if ($line =~ m/Scan report for ([\w\.-]+) \(([\d\.\:]+)\)/) { my $ip = $2; my $mac = get_mac_address($ip,$leasefile); if (!$mac){ $mac = get_mac_address_by_ping($ip); } # if (!$mac) {$mac = "unknown";} my %pair = ('Network' => $1, 'ip' => $ip, 'mac' => $mac); # $1 => PC Name, $2 => IP Address push @extracted_output, \%pair; } } return \@extracted_output; } #========================================================================= # Procedure qui charge le dhcpd.conf # retourne un tableau contenant les informations #========================================================================= sub Load_leases ($){ my $q = shift; #Definition de variable use vars qw/ @detail $work_line $header /; my $name_file = '/var/lib/dhcpd/dhcpd.leases' ; our @liste_computer = '' ; our $db = esmith::ConfigDB->open() or die("Unable to open Configuration DB"); our %sme_conf = $db->get('dhcpd')->props; #Ouverture du fichier en lecture seule open(FILE,"<$name_file") || die "Read error $name_file, Error: $!" ; #Parcours du fichier dhcpd.leases while () { # suppression des commentaires if ($_ =~ /^#/) { next; } #spurresion des lignes vides if ($_ =~ /^$/) { next; } #supression de la ligne uid de windows if ($_ =~ /uid/) { next; } #supression de la ligne tstp de XP if ($_ =~ /tstp/) { next; } #supression de la ligne "binding state active" if ($_ =~ /binding/) { next; } #supression de la ligne "next binding state" if ($_ =~ /next/) { next; } #suprresion des commentaire et des ; $_ =~ s/\s#.*$//; $_ =~ s/(;|{) *$//; #supression des espace en debut de lignes $_ =~ s/^ *\s//; #Suppression des retours chariot en fin de lignes. chomp $_ ; #Chargement d'un tableau avec le contenu de la ligne. (@detail) = split(/ /, $_) ; #initialisation d'un nouveau poste connect� if ($_ =~ /lease/) { $work_line = "" ; } $header = shift(@detail); $work_line = $work_line . ";" . join(" ",@detail) ; if ($header =~ /}/) { $work_line =~ s/ /;/g; $work_line =~ s/^;//; $work_line =~ s/"//g; $work_line =~ s/;$//; #we want the last entry is the first element of the array unshift(@liste_computer,$work_line) ; } } close(FILE); # Now check if up and extract details my @mac = (); my @count = (); my @results = (); my @liste_connected = (); foreach (@liste_computer){ my @computer = split(/;/, $_) ; #we want to sort elements the array by mac address $computer[11] = uc($computer[11]) ; push @mac, ($computer[11]); my @count; push @count , (grep /$computer[11]/ , @mac); next if (scalar @count > 1); #we start the table if it is a valid IP if ( $computer[0] ) { $computer[12] = uc($computer[12]) ; my $datedeb = $computer[2] . "-" . $computer[3] ; my $datefin = $computer[5] . "-" . $computer[6] ; my $status = 'dhcpd_NOT_CHECKED'; #my $status = '".$q->l('dhcpd_NOT_CHECKED').""; if ( $sme_conf{'check'} =~ /enabled/ ) { use Net::Ping; my($ping_obj) = Net::Ping->new("icmp"); # formatage des dates $computer[3] = substr($computer[3],0,5); $computer[6] = substr($computer[6],0,5); if ($ping_obj->ping("$computer[0]")) { push(@liste_connected,$computer[12]) ; $status = 'dhcpd_ACTIVE_DEVICE'; #alimentation de la liste des connect� } else { $status = 'dhcpd_WAKE_UP_ACTION';} #else { $status = '".$q->l('dhcpd_WAKE_UP_ACTION').""; } my %ipdetails = (ip=>$computer[0],name=>$computer[12],wol=>$status,start=>$datedeb,end=>$datefin,mac=>$computer[11]); push(@results,\%ipdetails); } else { push(@liste_connected,$computer[12]) ; } } return @results; } sub winpopup{ my $c = shift; # Message in $c->param("winpopupmsg") # .... do it return TRUE; } sub do_wol{ my $c = shift; my $retVal = Perform_Wake_Up($c); if ($retVal) {$dhcp_data{"success"} = $retVal;} do_leases($c); return TRUE; } #=============================================================================== #SUBROUTINE: Perform wake UP #=============================================================================== sub Perform_Wake_Up($){ ###Pull CGI object from parameters array my $q = shift; ###Get Mac Adress my $mac = uc($q->param('mac')); my $name = uc($q->param('name')); #Send Wake Up to the station #Old version obsolete since 1.5.0 - system ("/usr/bin/wol","$mac") ; system ("/sbin/ether-wake","$mac") ; ###Return action message return 'dhcpd_SUCCESSFULLY_CLIENT_WOL_REQUEST'; } 1;