#!/usr/bin/perl -I/usr/local/sauron
#
# import-dhcp - imports ISC DHCPD configuration
#
# Copyright (c) Michal Kostenec <kostenec@civ.zcu.cz> 2013-2014.
# Copyright (c) Timo Kokkonen <tjko@iki.fi>  2002,2003.
# $Id: import-dhcp,v 1.12 2003/12/28 19:29:12 tjko Exp $
#
require 5;
use Net::Netmask;
use Getopt::Long;
use Sauron::DB;
use Sauron::Util;
use Sauron::UtilDhcp;
use Sauron::BackEnd;
use Sauron::Sauron;
use Net::IP;

load_config();

##############################

GetOptions("help|h","verbose|v","dir=s","notransaction","chaosnet=s",
	   "global","dhcp6");

if ($opt_help || @ARGV < 2) {
    print "syntax: $0 [options] <servername> <dhcpd.conf file>\n";
    print "options:\n",
          "\t--dhcp6\t\t\timport DHCPv6 configuration\t\n",
          "\t--dir=<directory>\tdirectory where included files are located\n",
	  "\t\t\t\t(if not in directory specified in dhcpd.conf)\n",
	  "\t--chaosnet=<name>\ttreat this shared-network as default VLAN\n",
	  "\t\t\t\t(default is \"CHAOS\")\n",
	  "\t--global\t\timport 'global' entries into server settings\n",
	  "\t--verbose\t\tdisplay more detailed progress reports\n",
	  "\t--help\t\t\tdisplay this help\n\n";
    print "" if ($opt_help);
    exit(1);
}

$opt_verbose = ($opt_verbose ? 1 : 0);
$opt_global = ($opt_global ? 1 : 0);
$servername = $ARGV[0];
$dhcpdf = $ARGV[1];
$user = (getpwuid($<))[0];
$chaosnet = ($opt_chaosnet ? $opt_chaosnet : 'CHAOS');
$opt_dhcp6 = ($opt_dhcp6 ? 1 : 0);


fatal("cannot read dhcpd.conf ($dhcpdf)") unless (-r $dhcpdf);

set_muser($user);
db_connect();

$serverid=get_server_id($servername);
fatal("cannot find server: $servername") unless ($serverid > 0);

if ($dhcpdf =~ /(^.*\/)/) {
    $dir=$1;
} else {
    $dir="./";
}

# parse named.conf
undef %data;
process_dhcpdconf($dhcpdf,\%data,$opt_dhcp6);

unless ($opt_notransaction) {
  db_begin();
  db_ignore_begin_and_commit(1);
}

# global entries
if ($opt_global) {
  fatal("cannot get server record") if (get_server($serverid,\%server) < 0);

  foreach $line (@{$data{GLOBAL}}) {
    next unless $line =~ /;\s*$/;
    $line =~ s/;\s*//;
    print "GLOBAL: $line\n" if ($opt_verbose && !$opt_dhcp6);
    print "GLOBAL6: $line\n" if ($opt_verbose && $opt_dhcp6);
    push @{$server{dhcp}}, [0,$line,'',2] if !$opt_dhcp6;
    push @{$server{dhcp6}}, [0,$line,'',2] if $opt_dhcp6;
  }

  fatal("cannot update server record") if (update_server(\%server) < 0);
}

# VLANs (shared-networks)

$c = keys %{$data{'shared-network'}};
print "Found $c shared-networks (VLANs)\n";

foreach $key (sort keys %{$data{'shared-network'}}) {
  $rec=$data{'shared-network'}->{$key};
  ($net = $key) =~  s/^\"|\"$//g;
  $vlancounter++;
  $name = (valid_texthandle($net) ? $net : "vlan-$vlancounter");
  print "shared-network($vlancounter):  $net ($name)\n" if ($opt_verbose);

  if ($net eq $chaosnet) {
    $vlans{$key}=-1;
    print "Treating shared-network $net as default VLAN\n";
    next;
  }

  undef @q;
  foreach $line (@{$rec}) {
    next unless ($line =~ /;\s*$/);
    $line =~ s/;\s*$//;
    push @q, [0,$line];
  }

  $res = add_vlan({server=>$serverid,name=>$name,description=>$net,
		   dhcp_l=>\@q});
  fatal("cannot add VLAN ($net) ".db_errormsg()) if ($res < 0);
  $vlans{$key}=$res;
}


# networks (subnets)...

unless ($opt_dhcp6) {
    $c = keys %{$data{'subnet'}};
    print "Found $c subnets (nets)\n";

    foreach $key (sort keys %{$data{subnet}}) {
      $rec = $data{subnet}->{$key};
      unless (($net,$mask) = ($key =~ /^(\S+)\s+netmask\s+(\S+)\s*$/)) {
        print "Skipping invalid subnet: $key\n";
        next;
      }

      $nh = new2 Net::Netmask($net,$mask);
      fatal("invalid subnet: $key") unless ($nh);
      $cidr=$nh->desc();

      $netcounter++;
      $name="net-$netcounter";

      undef @q;
      $id=-1;
      foreach $line (@{$rec}) {
        if ($line =~ /^VLAN\s+(\".*\"|\S+)\s*$/) {
          $vlanname=$1;
          fatal("cannot find id for VLAN: $vlanname (bug!)")
        unless (defined $vlans{$vlanname});
          $id =$vlans{$vlanname};
          next;
        }
        next unless ($line =~ /;\s*$/);
        $line =~ s/;\s*$//;
        if ($line =~ /^\s*option\s+routers\s+(\S+)\s*$/) {
          $routerip=$1;
          print "router interface: $routerip\n" if ($opt_verbose);
          $routers{$routerip}=-1;
        } else {
          push @q, [0,$line];
        }
      }

      print "net: $cidr ($name) (vlan id=$id)\n" if ($opt_verbose);
      $res = add_net({server=>$serverid,netname=>$name,name=>$name, net=>$cidr,subnet=>'true',vlan=>$id, dhcp_l=>\@q}) if !$opt_dhcp6;
      $res = add_net({server=>$serverid,netname=>$name,name=>$name, net=>$cidr,subnet=>'true',vlan=>$id, dhcp_l6=>\@q}) if $opt_dhcp6;
      fatal("cannot add NET ($key)") if ($res < 0);
    }
}
else {
    $c = keys %{$data{'subnet6'}};
    print "Found $c subnets (nets)\n";

    foreach $key (sort keys %{$data{subnet6}}) {
      $rec = $data{subnet6}->{$key};
      $net = $key;
      $nh = new Net::IP($net);
      unless ($net) {
        print "Skipping invalid subnet: $net\n";
        next;
      }

      $netcounter++;
      $name="net6-$netcounter";

      undef @q;
      $id=-1;
      foreach $line (@{$rec}) {
        if ($line =~ /^VLAN\s+(\".*\"|\S+)\s*$/) {
          $vlanname=$1;
          fatal("cannot find id for VLAN: $vlanname (bug!)")
        unless (defined $vlans{$vlanname});
          $id =$vlans{$vlanname};
          next;
        }
        next unless ($line =~ /;\s*$/);
        $line =~ s/;\s*$//;
        push @q, [0,$line];
      }

      print "net: $net ($name) (vlan id=$id)\n" if ($opt_verbose);
      $res = add_net({server=>$serverid,netname=>$name,name=>$name,
              net=>$net,subnet=>'true',vlan=>$id, dhcp_l6=>\@q});
      fatal("cannot add NET ($net)") if ($res < 0);
    }
}

# groups

$c = keys %{$data{'group'}};
print "Found $c groups\n";

foreach $key (sort keys %{$data{group}}) {
  $rec = $data{group}->{$key};
  print "group: $key\n" if ($opt_verbose);

  undef @q;
  foreach $line (@{$rec}) {
    next unless ($line =~ /;\s*$/);
    $line =~ s/;\s*$//;
    push @q, [0,$line,''];
  }

  $res = add_group({server=>$serverid,name=>$key,type=>1,dhcp=>\@q}) if !$opt_dhcp6;
  $res = add_group({server=>$serverid,name=>$key,type=>1,dhcp6=>\@q}) if $opt_dhcp6;
  fatal("cannot add GROUP ($key)") if ($res < 0);
  $groups{$key}=$res;
}

# dhcp class

$c = keys %{$data{'class'}};
print "Found $c classes\n";

foreach $key (sort keys %{$data{class}}) {
  $rec = $data{class}->{$key};
  print "class: $key\n" if ($opt_verbose);

  undef @q;
  foreach $line (@{$rec}) {
    next unless ($line =~ /;\s*$/);
    $line =~ s/;\s*$//;
    push @q, [0,$line,''];
  }

  $res = add_group({server=>$serverid,name=>$key,type=>3,dhcp=>\@q}) if !$opt_dhcp6;
  $res = add_group({server=>$serverid,name=>$key,type=>3,dhcp6=>\@q}) if $opt_dhcp6;
  fatal("cannot add CLASS ($key)") if ($res < 0);
  $classes{$key}=$res if $res;
}

# dhcp subclass

$c = keys %{$data{'subclass'}};
print "Found $c subclasses\n";

foreach $key (sort keys %{$data{subclass}}) {
  $rec = $data{subclass}->{$key};
  print "subclass: $key\n" if ($opt_verbose);

  undef @q;
  foreach $line (@{$rec}) {
    next unless ($line =~ /;\s*$/);
    #UWB ~ match pick-first-value (option dhcp-client-identifier, hardware) 
    # 1:00:11:22:33:44:55 = hardware ethernet 00:11:22:33:44:55;  FIXIT;-)
    $line =~ s/^1:// unless ($opt_dhcp6);   
    $line =~ s/://g;   
    $line =~ s/;\s*$//;
    $line = uc($line);
    #sclass2class{00:11:22:33:44:55} = 1
    push @{$sclass2class{$line}}, [undef, $classes{$key}, 2] if $classes{$key};
  }
}


# dhcp pools
$pool_section = (!$opt_dhcp6 ? 'pool' : 'pool6');

$c = keys %{$data{$pool_section}};
print "Found $c pools\n";

foreach $key (sort keys %{$data{$pool_section}}) {
  $rec = $data{$pool_section}->{$key};
  print "pool: $key\n" if ($opt_verbose);

  undef @q;
  foreach $line (@{$rec}) {
    next unless ($line =~ /;\s*$/);
    $line =~ s/;\s*$//;
    if($line =~ /^range[6]?\s+(.*)\s+(.*)/) {
        $range = "$1 - $2";
        next;
    }
    
    push @q, [0,$line,''];
  }

  $res = add_group({server=>$serverid,name=>$key,type=>2,dhcp=>\@q}) if !$opt_dhcp6;
  $res = add_group({server=>$serverid,name=>$key,type=>2,dhcp6=>\@q}) if $opt_dhcp6;
  fatal("cannot add POOL ($key)") if ($res < 0);
  $pools{$range} = $res if $res;
}

# hosts

$c = keys %{$data{'host'}};
print "Found $c hosts\n";

foreach $key (keys %{$data{host}}) {
  $rec = $data{host}->{$key};

  $id=-1; $ip=''; $ether='';
  my @q;

  foreach $line (@{$rec}) {
    if ($line =~ /^GROUP\s+(\S+)\s*$/) {
      $id=$groups{$1};
      unless ($id > 0) {
	warn("cannot find group id for: $line ($1)");
	$id=-1;
      }
      next;
    }
    next unless ($line =~ /;\s*$/);
    $line =~ s/;\s*$//;
    next if ($line =~ /^\s*option\s+domain-name\s+/);
    if ($line =~ /^\s*fixed\-address[6]?\s+(\S.*)\s*$/) {
      $tmp =$1;
      #if ($tmp =~ /^\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s*$/) {
      if(is_ip($tmp)) {
	$ip=$1;
      } else {
	print "Ignoring host with multiple IPs: $key\n";
	next;
      }
    }
    elsif ($line =~ /^\s*hardware\s+ethernet\s+(([0-9a-fA-F]{1,2}(:|\b)){6})\s*$/ ||  
           $line =~ /^\s*host-identifier\s+option\s+dhcp6\.client-id\s+(.*)\s*$/) {
      my @tmp = split(/:/, $1);
      my $mac = "";
      foreach $byte (@tmp){ $mac .= sprintf ("%02s", $byte); }
      $ether=uc($mac);
    } else {
      push @q, [undef, $line, '', 2];
    }

  }
  next unless ($ip && $ether);

  #Add subclass
  if($sclass2class{$ether}) {
    unshift @{$sclass2class{$ether}}, ['SubGroup'];
  }
  else {
    @sclasess = [];
  }

  unshift @q, ['DHCP', 'Comments'] if @q > 0;

  print "host: $key ip=$ip,ether=$ether,groupid=$id\n" if ($opt_verbose && !$opt_dhcp6);
  print "host: $key ip=$ip,duid=$ether,groupid=$id\n" if ($opt_verbose && $opt_dhcp6);
  $hosts{$ip}=[$ether,$id,$key,$sclass2class{$ether}, \@q];

}

print "Querying database...";
undef @list;
db_query("SELECT h.id,h.domain,a.ip FROM hosts h,zones z, a_entries a " .
	 "WHERE z.server=$serverid AND h.zone=z.id AND h.type=1 " .
	 " AND a.host=h.id ORDER BY a.ip",\@list);
print "Found ",int(@list)," hosts\n";

if (@list > 0) {
  for $i (0..$#list) {
    next if ($skiplist{$list[$i][2]});
    $routers{$list[$i][2]}=$i if ($routers{$list[$i][2]}< 0);
    next unless ($hosts{$list[$i][2]});
    if ($list[$i][2] eq $list[$i-1][2] || $list[$i][2] eq $list[$i+1][2]) {
      $matchcount++;
      $hostrefs{$list[$i][2]}=$i;
      $skiplist{$list[$i][2]}=1;
      next;
    }
    $matchcount++;
    $hostrefs{$list[$i][2]}=$i;
  }
}

print "Found $matchcount hosts in database to update\n";

print "Updating hosts...\n";

db_query("SELECT h.ether, h.id from hosts h, zones z " .
         "WHERE z.server=$serverid AND h.zone=z.id AND h.type=1 " .
         "AND h.ether!=\'\'", \@current_ethers);

if (@current_ethers > 0) {
  for $i (0..$#current_ethers) {
    $macs{$current_ethers[$i][0]} = $current_ethers[$i][1];
  }
}

foreach $ip (keys %hostrefs) {
  next unless defined ($hostrefs{$ip});
  $i=$hostrefs{$ip};
  $id=$list[$i][0];
  $domain=$list[$i][1];
  $ip=$list[$i][2];

  if ($macs{$hosts{$ip}[0]} && !$opt_dhcp6){
    print "duplicate found, $hosts{$ip}[0]\n" if ($opt_verbose);
    print "Updating $domain ($ip,$id) <-- ",
          "$macs{$hosts{$ip}[0]},$hosts{$ip}[1],$hosts{$ip}[2]\n"
	    if ($opt_verbose);

    $res = update_host({id=>$id,ether_alias=>$macs{$hosts{$ip}[0]}, grp=>$hosts{$ip}[1], subgroups=> \@{$hosts{$ip}[3]}, dhcp_l=>$hosts{$ip}[4]}) if !$opt_dhcp6;
    $res = update_host({id=>$id,ether_alias=>$macs{$hosts{$ip}[0]}, grp=>$hosts{$ip}[1], subgroups=> \@{$hosts{$ip}[3]}, dhcp_l6=>$hosts{$ip}[4]}) if $opt_dhcp6;
    fatal("cannot update host: $domain ($res)\n".db_errormsg()) if ($res < 0);
    next;
  }
  $macs{$hosts{$ip}[0]} = $id;

  print "Updating $domain ($ip,$id) <-- ",
        "$hosts{$ip}[0],$hosts{$ip}[1],$hosts{$ip}[2]\n" if ($opt_verbose);
  
    $res = update_host({id=>$id,ether=>$hosts{$ip}[0],grp=>$hosts{$ip}[1], subgroups=> \@{$hosts{$ip}[3]}, dhcp_l=>$hosts{$ip}[4]}) if !$opt_dhcp6;
    $res = update_host({id=>$id, duid=>$hosts{$ip}[0],grp=>$hosts{$ip}[1], subgroups=> \@{$hosts{$ip}[3]}, dhcp_l6=>$hosts{$ip}[4]}) if $opt_dhcp6;
  
    fatal("cannot update host: $domain ($res)\n".db_errormsg()) if ($res < 0);
}

print "Updating dynamic pools members\n";

foreach my $pool (keys %pools) {
    $pool =~ /(.*)\s\-\s(.*)/;
    $pool_s = $1;
    $pool_e = $2;
    $pid = $pools{$pool};

    db_query("SELECT h.id,h.domain,a.ip FROM hosts h,zones z, a_entries a " .
         "WHERE z.server=$serverid AND h.zone=z.id AND h.type=1 " .
         " AND a.host=h.id AND a.ip >= inet '$pool_s' AND a.ip <= inet '$pool_e'" . 
         " ORDER BY a.ip",\@pool_members);

    print "Found ",int(@pool_members)," host(s) in range '$pool'\n" if ($opt_verbose);

    my $range_size = (new Net::IP($pool))->size();
    my $hosts_diff = $range_size - int(@pool_members);    

    print "Warning: ", $hosts_diff, " DNS records for pool '$pool' not found!\n" if $hosts_diff > 0;
    print "Warning: Found more DNS records than size of the pool '$pool'. Possible duplicates in different zones!\n" if $hosts_diff < 0;

    foreach my $m (@pool_members) {
        $id = $$m[0];
        $domain = $$m[1];
        $ip = $$m[2];
        print "Updating pool grp $domain ($ip,$id) <-- GRP: $pid\n" if ($opt_verbose);
        $res = update_host({id=>$id,grp=>$pid});
        fatal("cannot update hosts pool: $domain ($res)\n".db_errormsg()) if ($res < 0);
    }
}

unless ($opt_dhcp6) {
    print "Updating routers...\n";
    foreach $ip (keys %routers) {
      next if ($routers{$ip} < 0);
      $i=$routers{$ip};
      $id=$list[$i][0];
      $domain=$list[$i][1];
      print "router interface: $ip $domain (id=$id)\n" if ($opt_verbose);
      fatal("cannot update router info for: $domain ($ip)")
        if (update_host({id=>$id,router=>1}) < 0);
    }
}

unless ($opt_notransaction) {
  db_ignore_begin_and_commit(0);
  fatal("Cannot commit import to database!") if (db_commit() < 0);
}


print "All done.\n";
exit(0);

# eof
