#!/usr/bin/perl -I/usr/local/sauron
#
# export-ip-list  generates list of IPs in use for given neworks
#
# Copyright (c) Michal Kostenec <kostenec@civ.zcu.cz> 2013-2014.
# Copyright (c) Timo Kokkonen <tjko@iki.fi>  2004.
# $Id: export-ip-list,v 1.4 2004/04/07 09:43:00 tjko Exp $
#
require 5;
use Getopt::Long;
use Time::Local;
use Net::Netmask;
use Sauron::DB;
use Sauron::Util;
use Sauron::BackEnd;
use Sauron::Sauron;
use Net::IP;

sub optimize_ip_list($);

load_config();

$user = (getpwuid($<))[0];
$host = `hostname`;
$host =~ s/\n//g;
$time_now = localtime;
$tnow = time();
$tmp_extension = ".tmp.$<.$$"; # generate extension for temp files

GetOptions("help|h","verbose|v","inverse|i","optimize|o","netsonly|n","4");

if ($opt_help || @ARGV < 1) {
  print "syntax: $0 [--help] [OPTIONS] <servername> [[networknamemask] ...]\n",
        "\noptions:\n",
#	"\t--inverse\t\tlist IPs not in use\n",
	"\t--4\t\tOnly IPv4 records\n",
	"\t--netsonly\tlist only matching (sub)nets\n",
	"\t--optimize\tcombine continuous IP blocks into CIDRs\n",
	"\t--verbose\tmore verbose output\n",
    "\t\n",
    "*** Warning - optimized output doesn't optimize IPv6 records ***\n\n";

  print "\n" if ($opt_help);
  exit(0);
}

$opt_optimize = ($opt_optimize ? 1 : 0);
$opt_verbose = ($opt_verbose ? 1 : 0);
$opt_inverse = ($opt_inverse ? 1 : 0);
$opt_netsonly = ($opt_netsonly ? 1 : 0);
$opt_4 = ($opt_4 ? 1 : 0);
$servername=shift;
while (($mask=shift)) {  push @masks, $mask; }

db_connect();

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

print "# server: $servername\n# mask(s): ".join(",",@masks).
      "\n# date: ".localtime(time())." by $user\n#\n"  if ($opt_verbose);


# select nets using mask(s) (select all if no masks defined)

my $netblocks = {};
my @blocks;

db_query("SELECT id,net,netname " .
	 "FROM nets " .
	 "WHERE server=$serverid AND dummy=false " .
	 "ORDER BY net",\@allnets);

for $i (0..$#allnets) {
  ($id,$net,$netname) = @{$allnets[$i]};
  $match=0;

  if (@masks > 0) {
    for $j (0..$#masks) {
      $match++ if ($netname =~ /$masks[$j]/);
    }
  } else {
    $match=-1;
  }

  if ($match) {
    my $block = new Net::IP($net);
    if($opt_4) {
        push @nets, $net if $block->version() == 4;
    }
    else {
        push @nets, $net;
    }

    push @blocks, $block if defined $block;
  }
}


my $counter=0;

# print out matching nets if --netsonly used
if ($opt_netsonly) {
  for $i (0..$#nets) {
    print "$nets[$i]\n";
    $counter++;
  }
  print "#\n# $counter CIDRs\n" if ($opt_verbose);
  exit;
}


# select IPs (A records) for this server

db_query("SELECT a.ip,h.domain,z.name,h.expiration " .
	 "FROM zones z JOIN hosts h ON z.id=h.zone " .
	 " JOIN a_entries a ON h.id=a.host " .
	 "WHERE z.server=$serverid AND h.type=1 " .
	 "ORDER BY a.ip",\@ips);

error("No IPs found for this server!") unless (@ips > 0);


# build list of active IPs within selected nets

my @ip6s;

for $i (0..$#ips) {
  ($ip,$host,$domain,$edate)=@{$ips[$i]};
  my $block;

    my $tIP = new Net::IP($ip);
    next if ($tIP->version() == 6 and $opt_4);
    push @ip6s, $ip if $tIP->version == 6 and $opt_optimize;
 
    foreach my $b (@blocks){
        if($b->overlaps($tIP) == $IP_B_IN_A_OVERLAP) {
            unless ($edate > 0 && $edate < $tnow) {
                push @iplist, $ip if ($ip ne $lastip);
                $lastip=$ip;
            }
            
            last;
        }
    }
}

$ipcount=@iplist;
@iplist=optimize_ip_list(\@iplist) if ($opt_optimize);
push @iplist, @ip6s;

# print IPs/CIDRs...

for $i (0..$#iplist) {
  print "$iplist[$i]\n";
  $counter++;
}

print "#\n# $counter CIDRs" .
      ($opt_optimize?" (before optimization $ipcount CIDRs)":"") .
      "\n"
  if ($opt_verbose);


exit 0;

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


sub maxblock($) {
  my($ip) = @_;

  my $mask=1;
  my $i;

  for $i (1..32) {
    $mask = (1 << ($i-1));
    return ($i-1) if ($ip & $mask);
  }
  return ($i-1);
}

sub optimize_ip_list($) {
  my($list) = @_;

  my ($i,$j,$ip,$begin,$end,$last,$len);
  my %iphash;
  my @ranges;
  my @newlist;


  # build hash of IPs (takes care of ordering and duplicates)

  for $i (0..$#{$list}) {
    $ip=$$list[$i];
    $iphash{ip2int($ip)}++;
  }

  # build list of continuous IP ranges...

  my $mode=0;
  foreach $i (sort keys %iphash) {
    if ($mode==0) {
      $begin=$i;
      $mode=1;
    }
    elsif ($mode==1) {
      if ($i > $last+1) {
	$len=$last-$begin;
	#print "range $begin .. $last ($len)\n";
	push @ranges, [$begin,$last];
	$mode=0;
      }
    }

    $last=$i;
  }

  if ($mode==1) {
    $len=$last-$begin;
    #print "range $begin .. $last ($len) LAST\n";
    push @ranges, [$begin,$last];
  }


  # optimize IP ranges...

  for $i (0..$#ranges) {
    ($begin,$end) = @{$ranges[$i]};
    $len=$end-$begin;
    #print "[$begin,$end] $len (".int2ip($begin)." - ".int2ip($end).")\n";

    $ip=$begin;
    while ($ip <= $end) {
      my $l = maxblock($ip);
      while (($l>0) && ((2**$l) > ($end-$ip+1))) { $l-- };
      push @newlist, int2ip($ip)."/".(32-$l);
      #print int2ip($ip)."/".(32-$l)."  ($ip,$l) ".maxblock($ip)."\n";
      $ip+=2**$l;
    }

  }

  return @newlist;
}



# eof

